280 lines
8.1 KiB
Python
280 lines
8.1 KiB
Python
"""Support for Subaru sensors."""
|
|
import subarulink.const as sc
|
|
|
|
from homeassistant.components.sensor import DEVICE_CLASSES, SensorEntity
|
|
from homeassistant.const import (
|
|
DEVICE_CLASS_BATTERY,
|
|
DEVICE_CLASS_PRESSURE,
|
|
DEVICE_CLASS_TEMPERATURE,
|
|
DEVICE_CLASS_TIMESTAMP,
|
|
DEVICE_CLASS_VOLTAGE,
|
|
LENGTH_KILOMETERS,
|
|
LENGTH_MILES,
|
|
PERCENTAGE,
|
|
PRESSURE_HPA,
|
|
TEMP_CELSIUS,
|
|
TIME_MINUTES,
|
|
VOLT,
|
|
VOLUME_GALLONS,
|
|
VOLUME_LITERS,
|
|
)
|
|
from homeassistant.util.distance import convert as dist_convert
|
|
from homeassistant.util.unit_system import (
|
|
IMPERIAL_SYSTEM,
|
|
LENGTH_UNITS,
|
|
PRESSURE_UNITS,
|
|
TEMPERATURE_UNITS,
|
|
)
|
|
from homeassistant.util.volume import convert as vol_convert
|
|
|
|
from .const import (
|
|
API_GEN_2,
|
|
DOMAIN,
|
|
ENTRY_COORDINATOR,
|
|
ENTRY_VEHICLES,
|
|
ICONS,
|
|
VEHICLE_API_GEN,
|
|
VEHICLE_HAS_EV,
|
|
VEHICLE_HAS_SAFETY_SERVICE,
|
|
VEHICLE_STATUS,
|
|
)
|
|
from .entity import SubaruEntity
|
|
|
|
L_PER_GAL = vol_convert(1, VOLUME_GALLONS, VOLUME_LITERS)
|
|
KM_PER_MI = dist_convert(1, LENGTH_MILES, LENGTH_KILOMETERS)
|
|
|
|
# Fuel Economy Constants
|
|
FUEL_CONSUMPTION_L_PER_100KM = "L/100km"
|
|
FUEL_CONSUMPTION_MPG = "mi/gal"
|
|
FUEL_CONSUMPTION_UNITS = [FUEL_CONSUMPTION_L_PER_100KM, FUEL_CONSUMPTION_MPG]
|
|
|
|
SENSOR_TYPE = "type"
|
|
SENSOR_CLASS = "class"
|
|
SENSOR_FIELD = "field"
|
|
SENSOR_UNITS = "units"
|
|
|
|
# Sensor data available to "Subaru Safety Plus" subscribers with Gen1 or Gen2 vehicles
|
|
SAFETY_SENSORS = [
|
|
{
|
|
SENSOR_TYPE: "Odometer",
|
|
SENSOR_CLASS: None,
|
|
SENSOR_FIELD: sc.ODOMETER,
|
|
SENSOR_UNITS: LENGTH_KILOMETERS,
|
|
},
|
|
]
|
|
|
|
# Sensor data available to "Subaru Safety Plus" subscribers with Gen2 vehicles
|
|
API_GEN_2_SENSORS = [
|
|
{
|
|
SENSOR_TYPE: "Avg Fuel Consumption",
|
|
SENSOR_CLASS: None,
|
|
SENSOR_FIELD: sc.AVG_FUEL_CONSUMPTION,
|
|
SENSOR_UNITS: FUEL_CONSUMPTION_L_PER_100KM,
|
|
},
|
|
{
|
|
SENSOR_TYPE: "Range",
|
|
SENSOR_CLASS: None,
|
|
SENSOR_FIELD: sc.DIST_TO_EMPTY,
|
|
SENSOR_UNITS: LENGTH_KILOMETERS,
|
|
},
|
|
{
|
|
SENSOR_TYPE: "Tire Pressure FL",
|
|
SENSOR_CLASS: DEVICE_CLASS_PRESSURE,
|
|
SENSOR_FIELD: sc.TIRE_PRESSURE_FL,
|
|
SENSOR_UNITS: PRESSURE_HPA,
|
|
},
|
|
{
|
|
SENSOR_TYPE: "Tire Pressure FR",
|
|
SENSOR_CLASS: DEVICE_CLASS_PRESSURE,
|
|
SENSOR_FIELD: sc.TIRE_PRESSURE_FR,
|
|
SENSOR_UNITS: PRESSURE_HPA,
|
|
},
|
|
{
|
|
SENSOR_TYPE: "Tire Pressure RL",
|
|
SENSOR_CLASS: DEVICE_CLASS_PRESSURE,
|
|
SENSOR_FIELD: sc.TIRE_PRESSURE_RL,
|
|
SENSOR_UNITS: PRESSURE_HPA,
|
|
},
|
|
{
|
|
SENSOR_TYPE: "Tire Pressure RR",
|
|
SENSOR_CLASS: DEVICE_CLASS_PRESSURE,
|
|
SENSOR_FIELD: sc.TIRE_PRESSURE_RR,
|
|
SENSOR_UNITS: PRESSURE_HPA,
|
|
},
|
|
{
|
|
SENSOR_TYPE: "External Temp",
|
|
SENSOR_CLASS: DEVICE_CLASS_TEMPERATURE,
|
|
SENSOR_FIELD: sc.EXTERNAL_TEMP,
|
|
SENSOR_UNITS: TEMP_CELSIUS,
|
|
},
|
|
{
|
|
SENSOR_TYPE: "12V Battery Voltage",
|
|
SENSOR_CLASS: DEVICE_CLASS_VOLTAGE,
|
|
SENSOR_FIELD: sc.BATTERY_VOLTAGE,
|
|
SENSOR_UNITS: VOLT,
|
|
},
|
|
]
|
|
|
|
# Sensor data available to "Subaru Safety Plus" subscribers with PHEV vehicles
|
|
EV_SENSORS = [
|
|
{
|
|
SENSOR_TYPE: "EV Range",
|
|
SENSOR_CLASS: None,
|
|
SENSOR_FIELD: sc.EV_DISTANCE_TO_EMPTY,
|
|
SENSOR_UNITS: LENGTH_MILES,
|
|
},
|
|
{
|
|
SENSOR_TYPE: "EV Battery Level",
|
|
SENSOR_CLASS: DEVICE_CLASS_BATTERY,
|
|
SENSOR_FIELD: sc.EV_STATE_OF_CHARGE_PERCENT,
|
|
SENSOR_UNITS: PERCENTAGE,
|
|
},
|
|
{
|
|
SENSOR_TYPE: "EV Time to Full Charge",
|
|
SENSOR_CLASS: DEVICE_CLASS_TIMESTAMP,
|
|
SENSOR_FIELD: sc.EV_TIME_TO_FULLY_CHARGED,
|
|
SENSOR_UNITS: TIME_MINUTES,
|
|
},
|
|
]
|
|
|
|
|
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
|
"""Set up the Subaru sensors by config_entry."""
|
|
coordinator = hass.data[DOMAIN][config_entry.entry_id][ENTRY_COORDINATOR]
|
|
vehicle_info = hass.data[DOMAIN][config_entry.entry_id][ENTRY_VEHICLES]
|
|
entities = []
|
|
for vin in vehicle_info:
|
|
entities.extend(create_vehicle_sensors(vehicle_info[vin], coordinator))
|
|
async_add_entities(entities, True)
|
|
|
|
|
|
def create_vehicle_sensors(vehicle_info, coordinator):
|
|
"""Instantiate all available sensors for the vehicle."""
|
|
sensors_to_add = []
|
|
if vehicle_info[VEHICLE_HAS_SAFETY_SERVICE]:
|
|
sensors_to_add.extend(SAFETY_SENSORS)
|
|
|
|
if vehicle_info[VEHICLE_API_GEN] == API_GEN_2:
|
|
sensors_to_add.extend(API_GEN_2_SENSORS)
|
|
|
|
if vehicle_info[VEHICLE_HAS_EV]:
|
|
sensors_to_add.extend(EV_SENSORS)
|
|
|
|
return [
|
|
SubaruSensor(
|
|
vehicle_info,
|
|
coordinator,
|
|
s[SENSOR_TYPE],
|
|
s[SENSOR_CLASS],
|
|
s[SENSOR_FIELD],
|
|
s[SENSOR_UNITS],
|
|
)
|
|
for s in sensors_to_add
|
|
]
|
|
|
|
|
|
class SubaruSensor(SubaruEntity, SensorEntity):
|
|
"""Class for Subaru sensors."""
|
|
|
|
def __init__(
|
|
self, vehicle_info, coordinator, entity_type, sensor_class, data_field, api_unit
|
|
):
|
|
"""Initialize the sensor."""
|
|
super().__init__(vehicle_info, coordinator)
|
|
self.hass_type = "sensor"
|
|
self.current_value = None
|
|
self.entity_type = entity_type
|
|
self.sensor_class = sensor_class
|
|
self.data_field = data_field
|
|
self.api_unit = api_unit
|
|
|
|
@property
|
|
def device_class(self):
|
|
"""Return the class of this device, from component DEVICE_CLASSES."""
|
|
if self.sensor_class in DEVICE_CLASSES:
|
|
return self.sensor_class
|
|
return None
|
|
|
|
@property
|
|
def icon(self):
|
|
"""Return the icon of the sensor."""
|
|
if not self.device_class:
|
|
return ICONS.get(self.entity_type)
|
|
return None
|
|
|
|
@property
|
|
def state(self):
|
|
"""Return the state of the sensor."""
|
|
self.current_value = self.get_current_value()
|
|
|
|
if self.current_value is None:
|
|
return None
|
|
|
|
if self.api_unit in TEMPERATURE_UNITS:
|
|
return round(
|
|
self.hass.config.units.temperature(self.current_value, self.api_unit), 1
|
|
)
|
|
|
|
if self.api_unit in LENGTH_UNITS:
|
|
return round(
|
|
self.hass.config.units.length(self.current_value, self.api_unit), 1
|
|
)
|
|
|
|
if (
|
|
self.api_unit in PRESSURE_UNITS
|
|
and self.hass.config.units == IMPERIAL_SYSTEM
|
|
):
|
|
return round(
|
|
self.hass.config.units.pressure(self.current_value, self.api_unit),
|
|
1,
|
|
)
|
|
|
|
if (
|
|
self.api_unit in FUEL_CONSUMPTION_UNITS
|
|
and self.hass.config.units == IMPERIAL_SYSTEM
|
|
):
|
|
return round((100.0 * L_PER_GAL) / (KM_PER_MI * self.current_value), 1)
|
|
|
|
return self.current_value
|
|
|
|
@property
|
|
def unit_of_measurement(self):
|
|
"""Return the unit_of_measurement of the device."""
|
|
if self.api_unit in TEMPERATURE_UNITS:
|
|
return self.hass.config.units.temperature_unit
|
|
|
|
if self.api_unit in LENGTH_UNITS:
|
|
return self.hass.config.units.length_unit
|
|
|
|
if self.api_unit in PRESSURE_UNITS:
|
|
if self.hass.config.units == IMPERIAL_SYSTEM:
|
|
return self.hass.config.units.pressure_unit
|
|
return PRESSURE_HPA
|
|
|
|
if self.api_unit in FUEL_CONSUMPTION_UNITS:
|
|
if self.hass.config.units == IMPERIAL_SYSTEM:
|
|
return FUEL_CONSUMPTION_MPG
|
|
return FUEL_CONSUMPTION_L_PER_100KM
|
|
|
|
return self.api_unit
|
|
|
|
@property
|
|
def available(self):
|
|
"""Return if entity is available."""
|
|
last_update_success = super().available
|
|
if last_update_success and self.vin not in self.coordinator.data:
|
|
return False
|
|
return last_update_success
|
|
|
|
def get_current_value(self):
|
|
"""Get raw value from the coordinator."""
|
|
value = self.coordinator.data[self.vin][VEHICLE_STATUS].get(self.data_field)
|
|
if value in sc.BAD_SENSOR_VALUES:
|
|
value = None
|
|
if isinstance(value, str):
|
|
if "." in value:
|
|
value = float(value)
|
|
else:
|
|
value = int(value)
|
|
return value
|