Update sensors for Subaru integration (#66996)
* Update sensor.py * Change "EV Time to Fully Charged" type to datetime object (HA 2022.2) * Validate types before accessing dict entries * Test handling of invalid data from Subaru * Bump to subarulink 0.4.2 * Incorporate style suggestion * Update sensor.py to use SensorEntity * isort tests * Remove SubaruSensor.current_value * Fix isort errors * Resolve conflict from previous PR (add locks) * Fix linting errors in config_flow.py * Incorporate PR review comments for sensor * Incorporate PR review comments for sensor * Make 3rd party library responsible for API data parsing * Add type annotations to sensor.py * Incorporate PR review comments * Incorporate PR review comments * Set _attr_has_entity_name = True for sensors
This commit is contained in:
parent
35fa73eee9
commit
9058b5b9c3
10 changed files with 225 additions and 272 deletions
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
|||
|
||||
from datetime import datetime
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from subarulink import (
|
||||
Controller as SubaruAPI,
|
||||
|
@ -16,6 +17,7 @@ import voluptuous as vol
|
|||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_DEVICE_ID, CONF_PASSWORD, CONF_PIN, CONF_USERNAME
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
||||
|
||||
from .const import CONF_COUNTRY, CONF_UPDATE_ENABLED, DOMAIN
|
||||
|
@ -36,7 +38,9 @@ class SubaruConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
self.config_data = {CONF_PIN: None}
|
||||
self.controller = None
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle the start of the config flow."""
|
||||
error = None
|
||||
|
||||
|
@ -117,7 +121,9 @@ class SubaruConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
_LOGGER.debug("Successfully authenticated with Subaru API")
|
||||
self.config_data.update(data)
|
||||
|
||||
async def async_step_two_factor(self, user_input=None):
|
||||
async def async_step_two_factor(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Select contact method and request 2FA code from Subaru."""
|
||||
error = None
|
||||
if user_input:
|
||||
|
@ -143,7 +149,9 @@ class SubaruConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
step_id="two_factor", data_schema=data_schema, errors=error
|
||||
)
|
||||
|
||||
async def async_step_two_factor_validate(self, user_input=None):
|
||||
async def async_step_two_factor_validate(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Validate received 2FA code with Subaru."""
|
||||
error = None
|
||||
if user_input:
|
||||
|
@ -166,7 +174,9 @@ class SubaruConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
step_id="two_factor_validate", data_schema=data_schema, errors=error
|
||||
)
|
||||
|
||||
async def async_step_pin(self, user_input=None):
|
||||
async def async_step_pin(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle second part of config flow, if required."""
|
||||
error = None
|
||||
if user_input and self.controller.update_saved_pin(user_input[CONF_PIN]):
|
||||
|
@ -193,7 +203,9 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
|||
"""Initialize options flow."""
|
||||
self.config_entry = config_entry
|
||||
|
||||
async def async_step_init(self, user_input=None):
|
||||
async def async_step_init(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle options flow."""
|
||||
if user_input is not None:
|
||||
return self.async_create_entry(title="", data=user_input)
|
||||
|
|
|
@ -31,7 +31,7 @@ VEHICLE_STATUS = "status"
|
|||
|
||||
API_GEN_1 = "g1"
|
||||
API_GEN_2 = "g2"
|
||||
MANUFACTURER = "Subaru Corp."
|
||||
MANUFACTURER = "Subaru"
|
||||
|
||||
PLATFORMS = [
|
||||
Platform.LOCK,
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
"""Base class for all Subaru Entities."""
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN, MANUFACTURER, VEHICLE_NAME, VEHICLE_VIN
|
||||
|
||||
|
||||
class SubaruEntity(CoordinatorEntity):
|
||||
"""Representation of a Subaru Entity."""
|
||||
|
||||
def __init__(self, vehicle_info, coordinator):
|
||||
"""Initialize the Subaru Entity."""
|
||||
super().__init__(coordinator)
|
||||
self.car_name = vehicle_info[VEHICLE_NAME]
|
||||
self.vin = vehicle_info[VEHICLE_VIN]
|
||||
self.entity_type = "entity"
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return name."""
|
||||
return f"{self.car_name} {self.entity_type}"
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return a unique ID."""
|
||||
return f"{self.vin}_{self.entity_type}"
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return the device_info of the device."""
|
||||
return DeviceInfo(
|
||||
identifiers={(DOMAIN, self.vin)},
|
||||
manufacturer=MANUFACTURER,
|
||||
name=self.car_name,
|
||||
)
|
|
@ -3,7 +3,7 @@
|
|||
"name": "Subaru",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/subaru",
|
||||
"requirements": ["subarulink==0.5.0"],
|
||||
"requirements": ["subarulink==0.6.0"],
|
||||
"codeowners": ["@G-Two"],
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["stdiomask", "subarulink"]
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
"""Support for Subaru sensors."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import subarulink.const as sc
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
DEVICE_CLASSES,
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
|
@ -14,128 +20,133 @@ from homeassistant.const import (
|
|||
PERCENTAGE,
|
||||
PRESSURE_HPA,
|
||||
TEMP_CELSIUS,
|
||||
TIME_MINUTES,
|
||||
VOLUME_GALLONS,
|
||||
VOLUME_LITERS,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.util.unit_conversion import DistanceConverter, VolumeConverter
|
||||
from homeassistant.util.unit_system import (
|
||||
IMPERIAL_SYSTEM,
|
||||
LENGTH_UNITS,
|
||||
PRESSURE_UNITS,
|
||||
TEMPERATURE_UNITS,
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
DataUpdateCoordinator,
|
||||
)
|
||||
from homeassistant.util.unit_conversion import DistanceConverter, VolumeConverter
|
||||
from homeassistant.util.unit_system import IMPERIAL_SYSTEM, LENGTH_UNITS, PRESSURE_UNITS
|
||||
|
||||
from . import get_device_info
|
||||
from .const import (
|
||||
API_GEN_2,
|
||||
DOMAIN,
|
||||
ENTRY_COORDINATOR,
|
||||
ENTRY_VEHICLES,
|
||||
ICONS,
|
||||
VEHICLE_API_GEN,
|
||||
VEHICLE_HAS_EV,
|
||||
VEHICLE_HAS_SAFETY_SERVICE,
|
||||
VEHICLE_STATUS,
|
||||
VEHICLE_VIN,
|
||||
)
|
||||
from .entity import SubaruEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Fuel consumption units
|
||||
FUEL_CONSUMPTION_LITERS_PER_HUNDRED_KILOMETERS = "L/100km"
|
||||
FUEL_CONSUMPTION_MILES_PER_GALLON = "mi/gal"
|
||||
|
||||
L_PER_GAL = VolumeConverter.convert(1, VOLUME_GALLONS, VOLUME_LITERS)
|
||||
KM_PER_MI = DistanceConverter.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
|
||||
# Sensor 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,
|
||||
},
|
||||
SensorEntityDescription(
|
||||
key=sc.ODOMETER,
|
||||
icon="mdi:road-variant",
|
||||
name="Odometer",
|
||||
native_unit_of_measurement=LENGTH_KILOMETERS,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
]
|
||||
|
||||
# Sensor data available to "Subaru Safety Plus" subscribers with Gen2 vehicles
|
||||
# Sensors 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: SensorDeviceClass.PRESSURE,
|
||||
SENSOR_FIELD: sc.TIRE_PRESSURE_FL,
|
||||
SENSOR_UNITS: PRESSURE_HPA,
|
||||
},
|
||||
{
|
||||
SENSOR_TYPE: "Tire Pressure FR",
|
||||
SENSOR_CLASS: SensorDeviceClass.PRESSURE,
|
||||
SENSOR_FIELD: sc.TIRE_PRESSURE_FR,
|
||||
SENSOR_UNITS: PRESSURE_HPA,
|
||||
},
|
||||
{
|
||||
SENSOR_TYPE: "Tire Pressure RL",
|
||||
SENSOR_CLASS: SensorDeviceClass.PRESSURE,
|
||||
SENSOR_FIELD: sc.TIRE_PRESSURE_RL,
|
||||
SENSOR_UNITS: PRESSURE_HPA,
|
||||
},
|
||||
{
|
||||
SENSOR_TYPE: "Tire Pressure RR",
|
||||
SENSOR_CLASS: SensorDeviceClass.PRESSURE,
|
||||
SENSOR_FIELD: sc.TIRE_PRESSURE_RR,
|
||||
SENSOR_UNITS: PRESSURE_HPA,
|
||||
},
|
||||
{
|
||||
SENSOR_TYPE: "External Temp",
|
||||
SENSOR_CLASS: SensorDeviceClass.TEMPERATURE,
|
||||
SENSOR_FIELD: sc.EXTERNAL_TEMP,
|
||||
SENSOR_UNITS: TEMP_CELSIUS,
|
||||
},
|
||||
{
|
||||
SENSOR_TYPE: "12V Battery Voltage",
|
||||
SENSOR_CLASS: SensorDeviceClass.VOLTAGE,
|
||||
SENSOR_FIELD: sc.BATTERY_VOLTAGE,
|
||||
SENSOR_UNITS: ELECTRIC_POTENTIAL_VOLT,
|
||||
},
|
||||
SensorEntityDescription(
|
||||
key=sc.AVG_FUEL_CONSUMPTION,
|
||||
icon="mdi:leaf",
|
||||
name="Avg Fuel Consumption",
|
||||
native_unit_of_measurement=FUEL_CONSUMPTION_LITERS_PER_HUNDRED_KILOMETERS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=sc.DIST_TO_EMPTY,
|
||||
icon="mdi:gas-station",
|
||||
name="Range",
|
||||
native_unit_of_measurement=LENGTH_KILOMETERS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=sc.TIRE_PRESSURE_FL,
|
||||
device_class=SensorDeviceClass.PRESSURE,
|
||||
name="Tire Pressure FL",
|
||||
native_unit_of_measurement=PRESSURE_HPA,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=sc.TIRE_PRESSURE_FR,
|
||||
device_class=SensorDeviceClass.PRESSURE,
|
||||
name="Tire Pressure FR",
|
||||
native_unit_of_measurement=PRESSURE_HPA,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=sc.TIRE_PRESSURE_RL,
|
||||
device_class=SensorDeviceClass.PRESSURE,
|
||||
name="Tire Pressure RL",
|
||||
native_unit_of_measurement=PRESSURE_HPA,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=sc.TIRE_PRESSURE_RR,
|
||||
device_class=SensorDeviceClass.PRESSURE,
|
||||
name="Tire Pressure RR",
|
||||
native_unit_of_measurement=PRESSURE_HPA,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=sc.EXTERNAL_TEMP,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="External Temp",
|
||||
native_unit_of_measurement=TEMP_CELSIUS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=sc.BATTERY_VOLTAGE,
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
name="12V Battery Voltage",
|
||||
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
]
|
||||
|
||||
# Sensor data available to "Subaru Safety Plus" subscribers with PHEV vehicles
|
||||
# Sensors 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: SensorDeviceClass.BATTERY,
|
||||
SENSOR_FIELD: sc.EV_STATE_OF_CHARGE_PERCENT,
|
||||
SENSOR_UNITS: PERCENTAGE,
|
||||
},
|
||||
{
|
||||
SENSOR_TYPE: "EV Time to Full Charge",
|
||||
SENSOR_CLASS: SensorDeviceClass.TIMESTAMP,
|
||||
SENSOR_FIELD: sc.EV_TIME_TO_FULLY_CHARGED,
|
||||
SENSOR_UNITS: TIME_MINUTES,
|
||||
},
|
||||
SensorEntityDescription(
|
||||
key=sc.EV_DISTANCE_TO_EMPTY,
|
||||
icon="mdi:ev-station",
|
||||
name="EV Range",
|
||||
native_unit_of_measurement=LENGTH_MILES,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=sc.EV_STATE_OF_CHARGE_PERCENT,
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
name="EV Battery Level",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=sc.EV_TIME_TO_FULLY_CHARGED_UTC,
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
name="EV Time to Full Charge",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
|
@ -145,123 +156,111 @@ async def async_setup_entry(
|
|||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""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]
|
||||
entry = hass.data[DOMAIN][config_entry.entry_id]
|
||||
coordinator = entry[ENTRY_COORDINATOR]
|
||||
vehicle_info = entry[ENTRY_VEHICLES]
|
||||
entities = []
|
||||
for vin in vehicle_info:
|
||||
entities.extend(create_vehicle_sensors(vehicle_info[vin], coordinator))
|
||||
async_add_entities(entities, True)
|
||||
for info in vehicle_info.values():
|
||||
entities.extend(create_vehicle_sensors(info, coordinator))
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
def create_vehicle_sensors(vehicle_info, coordinator):
|
||||
def create_vehicle_sensors(
|
||||
vehicle_info, coordinator: DataUpdateCoordinator
|
||||
) -> list[SubaruSensor]:
|
||||
"""Instantiate all available sensors for the vehicle."""
|
||||
sensors_to_add = []
|
||||
sensor_descriptions_to_add = []
|
||||
if vehicle_info[VEHICLE_HAS_SAFETY_SERVICE]:
|
||||
sensors_to_add.extend(SAFETY_SENSORS)
|
||||
sensor_descriptions_to_add.extend(SAFETY_SENSORS)
|
||||
|
||||
if vehicle_info[VEHICLE_API_GEN] == API_GEN_2:
|
||||
sensors_to_add.extend(API_GEN_2_SENSORS)
|
||||
sensor_descriptions_to_add.extend(API_GEN_2_SENSORS)
|
||||
|
||||
if vehicle_info[VEHICLE_HAS_EV]:
|
||||
sensors_to_add.extend(EV_SENSORS)
|
||||
sensor_descriptions_to_add.extend(EV_SENSORS)
|
||||
|
||||
return [
|
||||
SubaruSensor(
|
||||
vehicle_info,
|
||||
coordinator,
|
||||
s[SENSOR_TYPE],
|
||||
s[SENSOR_CLASS],
|
||||
s[SENSOR_FIELD],
|
||||
s[SENSOR_UNITS],
|
||||
description,
|
||||
)
|
||||
for s in sensors_to_add
|
||||
for description in sensor_descriptions_to_add
|
||||
]
|
||||
|
||||
|
||||
class SubaruSensor(SubaruEntity, SensorEntity):
|
||||
class SubaruSensor(
|
||||
CoordinatorEntity[DataUpdateCoordinator[dict[str, Any]]], SensorEntity
|
||||
):
|
||||
"""Class for Subaru sensors."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self, vehicle_info, coordinator, entity_type, sensor_class, data_field, api_unit
|
||||
):
|
||||
self,
|
||||
vehicle_info: dict,
|
||||
coordinator: DataUpdateCoordinator,
|
||||
description: SensorEntityDescription,
|
||||
) -> None:
|
||||
"""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
|
||||
super().__init__(coordinator)
|
||||
self.vin = vehicle_info[VEHICLE_VIN]
|
||||
self.entity_description = description
|
||||
self._attr_device_info = get_device_info(vehicle_info)
|
||||
self._attr_unique_id = f"{self.vin}_{description.name}"
|
||||
|
||||
@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 native_value(self):
|
||||
def native_value(self) -> None | int | float:
|
||||
"""Return the state of the sensor."""
|
||||
self.current_value = self.get_current_value()
|
||||
vehicle_data = self.coordinator.data[self.vin]
|
||||
current_value = vehicle_data[VEHICLE_STATUS].get(self.entity_description.key)
|
||||
unit = self.entity_description.native_unit_of_measurement
|
||||
unit_system = self.hass.config.units
|
||||
|
||||
if self.current_value is None:
|
||||
if 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 unit in LENGTH_UNITS:
|
||||
return round(unit_system.length(current_value, unit), 1)
|
||||
|
||||
if self.api_unit in LENGTH_UNITS:
|
||||
if unit in PRESSURE_UNITS and unit_system == IMPERIAL_SYSTEM:
|
||||
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),
|
||||
unit_system.pressure(current_value, unit),
|
||||
1,
|
||||
)
|
||||
|
||||
if (
|
||||
self.api_unit in FUEL_CONSUMPTION_UNITS
|
||||
and self.hass.config.units == IMPERIAL_SYSTEM
|
||||
unit
|
||||
in [
|
||||
FUEL_CONSUMPTION_LITERS_PER_HUNDRED_KILOMETERS,
|
||||
FUEL_CONSUMPTION_MILES_PER_GALLON,
|
||||
]
|
||||
and unit_system == IMPERIAL_SYSTEM
|
||||
):
|
||||
return round((100.0 * L_PER_GAL) / (KM_PER_MI * self.current_value), 1)
|
||||
return round((100.0 * L_PER_GAL) / (KM_PER_MI * current_value), 1)
|
||||
|
||||
return self.current_value
|
||||
return current_value
|
||||
|
||||
@property
|
||||
def native_unit_of_measurement(self):
|
||||
def native_unit_of_measurement(self) -> str | None:
|
||||
"""Return the unit_of_measurement of the device."""
|
||||
if self.api_unit in TEMPERATURE_UNITS:
|
||||
return self.hass.config.units.temperature_unit
|
||||
unit = self.entity_description.native_unit_of_measurement
|
||||
|
||||
if self.api_unit in LENGTH_UNITS:
|
||||
if unit in LENGTH_UNITS:
|
||||
return self.hass.config.units.length_unit
|
||||
|
||||
if self.api_unit in PRESSURE_UNITS:
|
||||
if 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 unit in [
|
||||
FUEL_CONSUMPTION_LITERS_PER_HUNDRED_KILOMETERS,
|
||||
FUEL_CONSUMPTION_MILES_PER_GALLON,
|
||||
]:
|
||||
if self.hass.config.units == IMPERIAL_SYSTEM:
|
||||
return FUEL_CONSUMPTION_MPG
|
||||
return FUEL_CONSUMPTION_L_PER_100KM
|
||||
return FUEL_CONSUMPTION_MILES_PER_GALLON
|
||||
|
||||
return self.api_unit
|
||||
return unit
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
|
@ -270,15 +269,3 @@ class SubaruSensor(SubaruEntity, SensorEntity):
|
|||
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
|
||||
|
|
|
@ -2332,7 +2332,7 @@ streamlabswater==1.0.1
|
|||
stringcase==1.2.0
|
||||
|
||||
# homeassistant.components.subaru
|
||||
subarulink==0.5.0
|
||||
subarulink==0.6.0
|
||||
|
||||
# homeassistant.components.solarlog
|
||||
sunwatcher==0.2.1
|
||||
|
|
|
@ -1611,7 +1611,7 @@ stookalert==0.1.4
|
|||
stringcase==1.2.0
|
||||
|
||||
# homeassistant.components.subaru
|
||||
subarulink==0.5.0
|
||||
subarulink==0.6.0
|
||||
|
||||
# homeassistant.components.solarlog
|
||||
sunwatcher==0.2.1
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
"""Sample API response data for tests."""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from homeassistant.components.subaru.const import (
|
||||
API_GEN_1,
|
||||
API_GEN_2,
|
||||
|
@ -46,10 +48,12 @@ VEHICLE_DATA = {
|
|||
},
|
||||
}
|
||||
|
||||
MOCK_DATETIME = datetime.fromtimestamp(1595560000, timezone.utc)
|
||||
|
||||
VEHICLE_STATUS_EV = {
|
||||
"status": {
|
||||
"AVG_FUEL_CONSUMPTION": 2.3,
|
||||
"BATTERY_VOLTAGE": "12.0",
|
||||
"BATTERY_VOLTAGE": 12.0,
|
||||
"DISTANCE_TO_EMPTY_FUEL": 707,
|
||||
"DOOR_BOOT_LOCK_STATUS": "UNKNOWN",
|
||||
"DOOR_BOOT_POSITION": "CLOSED",
|
||||
|
@ -63,21 +67,17 @@ VEHICLE_STATUS_EV = {
|
|||
"DOOR_REAR_LEFT_POSITION": "CLOSED",
|
||||
"DOOR_REAR_RIGHT_LOCK_STATUS": "UNKNOWN",
|
||||
"DOOR_REAR_RIGHT_POSITION": "CLOSED",
|
||||
"EV_CHARGER_STATE_TYPE": "CHARGING_STOPPED",
|
||||
"EV_CHARGER_STATE_TYPE": "CHARGING",
|
||||
"EV_CHARGE_SETTING_AMPERE_TYPE": "MAXIMUM",
|
||||
"EV_CHARGE_VOLT_TYPE": "CHARGE_LEVEL_1",
|
||||
"EV_DISTANCE_TO_EMPTY": 17,
|
||||
"EV_DISTANCE_TO_EMPTY": 1,
|
||||
"EV_IS_PLUGGED_IN": "UNLOCKED_CONNECTED",
|
||||
"EV_STATE_OF_CHARGE_MODE": "EV_MODE",
|
||||
"EV_STATE_OF_CHARGE_PERCENT": "100",
|
||||
"EV_TIME_TO_FULLY_CHARGED": "65535",
|
||||
"EV_VEHICLE_TIME_DAYOFWEEK": "6",
|
||||
"EV_VEHICLE_TIME_HOUR": "14",
|
||||
"EV_VEHICLE_TIME_MINUTE": "20",
|
||||
"EV_VEHICLE_TIME_SECOND": "39",
|
||||
"EXT_EXTERNAL_TEMP": "21.5",
|
||||
"EV_STATE_OF_CHARGE_PERCENT": 20,
|
||||
"EV_TIME_TO_FULLY_CHARGED_UTC": MOCK_DATETIME,
|
||||
"EXT_EXTERNAL_TEMP": 21.5,
|
||||
"ODOMETER": 1234,
|
||||
"POSITION_HEADING_DEGREE": "150",
|
||||
"POSITION_HEADING_DEGREE": 150,
|
||||
"POSITION_SPEED_KMPH": "0",
|
||||
"POSITION_TIMESTAMP": 1595560000.0,
|
||||
"SEAT_BELT_STATUS_FRONT_LEFT": "BELTED",
|
||||
|
@ -100,7 +100,7 @@ VEHICLE_STATUS_EV = {
|
|||
"SEAT_OCCUPATION_STATUS_THIRD_RIGHT": "UNKNOWN",
|
||||
"TIMESTAMP": 1595560000.0,
|
||||
"TRANSMISSION_MODE": "UNKNOWN",
|
||||
"TYRE_PRESSURE_FRONT_LEFT": 2550,
|
||||
"TYRE_PRESSURE_FRONT_LEFT": 0,
|
||||
"TYRE_PRESSURE_FRONT_RIGHT": 2550,
|
||||
"TYRE_PRESSURE_REAR_LEFT": 2450,
|
||||
"TYRE_PRESSURE_REAR_RIGHT": 2350,
|
||||
|
@ -121,10 +121,11 @@ VEHICLE_STATUS_EV = {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
VEHICLE_STATUS_G2 = {
|
||||
"status": {
|
||||
"AVG_FUEL_CONSUMPTION": 2.3,
|
||||
"BATTERY_VOLTAGE": "12.0",
|
||||
"BATTERY_VOLTAGE": 12.0,
|
||||
"DISTANCE_TO_EMPTY_FUEL": 707,
|
||||
"DOOR_BOOT_LOCK_STATUS": "UNKNOWN",
|
||||
"DOOR_BOOT_POSITION": "CLOSED",
|
||||
|
@ -138,9 +139,9 @@ VEHICLE_STATUS_G2 = {
|
|||
"DOOR_REAR_LEFT_POSITION": "CLOSED",
|
||||
"DOOR_REAR_RIGHT_LOCK_STATUS": "UNKNOWN",
|
||||
"DOOR_REAR_RIGHT_POSITION": "CLOSED",
|
||||
"EXT_EXTERNAL_TEMP": "21.5",
|
||||
"EXT_EXTERNAL_TEMP": None,
|
||||
"ODOMETER": 1234,
|
||||
"POSITION_HEADING_DEGREE": "150",
|
||||
"POSITION_HEADING_DEGREE": 150,
|
||||
"POSITION_SPEED_KMPH": "0",
|
||||
"POSITION_TIMESTAMP": 1595560000.0,
|
||||
"SEAT_BELT_STATUS_FRONT_LEFT": "BELTED",
|
||||
|
@ -188,18 +189,14 @@ EXPECTED_STATE_EV_IMPERIAL = {
|
|||
"AVG_FUEL_CONSUMPTION": "102.3",
|
||||
"BATTERY_VOLTAGE": "12.0",
|
||||
"DISTANCE_TO_EMPTY_FUEL": "439.3",
|
||||
"EV_CHARGER_STATE_TYPE": "CHARGING_STOPPED",
|
||||
"EV_CHARGER_STATE_TYPE": "CHARGING",
|
||||
"EV_CHARGE_SETTING_AMPERE_TYPE": "MAXIMUM",
|
||||
"EV_CHARGE_VOLT_TYPE": "CHARGE_LEVEL_1",
|
||||
"EV_DISTANCE_TO_EMPTY": "17",
|
||||
"EV_DISTANCE_TO_EMPTY": "1",
|
||||
"EV_IS_PLUGGED_IN": "UNLOCKED_CONNECTED",
|
||||
"EV_STATE_OF_CHARGE_MODE": "EV_MODE",
|
||||
"EV_STATE_OF_CHARGE_PERCENT": "100",
|
||||
"EV_TIME_TO_FULLY_CHARGED": "unknown",
|
||||
"EV_VEHICLE_TIME_DAYOFWEEK": "6",
|
||||
"EV_VEHICLE_TIME_HOUR": "14",
|
||||
"EV_VEHICLE_TIME_MINUTE": "20",
|
||||
"EV_VEHICLE_TIME_SECOND": "39",
|
||||
"EV_STATE_OF_CHARGE_PERCENT": "20",
|
||||
"EV_TIME_TO_FULLY_CHARGED_UTC": "2020-07-24T03:06:40+00:00",
|
||||
"EXT_EXTERNAL_TEMP": "70.7",
|
||||
"ODOMETER": "766.8",
|
||||
"POSITION_HEADING_DEGREE": "150",
|
||||
|
@ -207,7 +204,7 @@ EXPECTED_STATE_EV_IMPERIAL = {
|
|||
"POSITION_TIMESTAMP": 1595560000.0,
|
||||
"TIMESTAMP": 1595560000.0,
|
||||
"TRANSMISSION_MODE": "UNKNOWN",
|
||||
"TYRE_PRESSURE_FRONT_LEFT": "37.0",
|
||||
"TYRE_PRESSURE_FRONT_LEFT": "0.0",
|
||||
"TYRE_PRESSURE_FRONT_RIGHT": "37.0",
|
||||
"TYRE_PRESSURE_REAR_LEFT": "35.5",
|
||||
"TYRE_PRESSURE_REAR_RIGHT": "34.1",
|
||||
|
@ -221,18 +218,14 @@ EXPECTED_STATE_EV_METRIC = {
|
|||
"AVG_FUEL_CONSUMPTION": "2.3",
|
||||
"BATTERY_VOLTAGE": "12.0",
|
||||
"DISTANCE_TO_EMPTY_FUEL": "707",
|
||||
"EV_CHARGER_STATE_TYPE": "CHARGING_STOPPED",
|
||||
"EV_CHARGER_STATE_TYPE": "CHARGING",
|
||||
"EV_CHARGE_SETTING_AMPERE_TYPE": "MAXIMUM",
|
||||
"EV_CHARGE_VOLT_TYPE": "CHARGE_LEVEL_1",
|
||||
"EV_DISTANCE_TO_EMPTY": "27.4",
|
||||
"EV_DISTANCE_TO_EMPTY": "1.6",
|
||||
"EV_IS_PLUGGED_IN": "UNLOCKED_CONNECTED",
|
||||
"EV_STATE_OF_CHARGE_MODE": "EV_MODE",
|
||||
"EV_STATE_OF_CHARGE_PERCENT": "100",
|
||||
"EV_TIME_TO_FULLY_CHARGED": "unknown",
|
||||
"EV_VEHICLE_TIME_DAYOFWEEK": "6",
|
||||
"EV_VEHICLE_TIME_HOUR": "14",
|
||||
"EV_VEHICLE_TIME_MINUTE": "20",
|
||||
"EV_VEHICLE_TIME_SECOND": "39",
|
||||
"EV_STATE_OF_CHARGE_PERCENT": "20",
|
||||
"EV_TIME_TO_FULLY_CHARGED_UTC": "2020-07-24T03:06:40+00:00",
|
||||
"EXT_EXTERNAL_TEMP": "21.5",
|
||||
"ODOMETER": "1234",
|
||||
"POSITION_HEADING_DEGREE": "150",
|
||||
|
@ -240,7 +233,7 @@ EXPECTED_STATE_EV_METRIC = {
|
|||
"POSITION_TIMESTAMP": 1595560000.0,
|
||||
"TIMESTAMP": 1595560000.0,
|
||||
"TRANSMISSION_MODE": "UNKNOWN",
|
||||
"TYRE_PRESSURE_FRONT_LEFT": "2550",
|
||||
"TYRE_PRESSURE_FRONT_LEFT": "0",
|
||||
"TYRE_PRESSURE_FRONT_RIGHT": "2550",
|
||||
"TYRE_PRESSURE_REAR_LEFT": "2450",
|
||||
"TYRE_PRESSURE_REAR_RIGHT": "2350",
|
||||
|
@ -250,6 +243,7 @@ EXPECTED_STATE_EV_METRIC = {
|
|||
"longitude": -100.0,
|
||||
}
|
||||
|
||||
|
||||
EXPECTED_STATE_EV_UNAVAILABLE = {
|
||||
"AVG_FUEL_CONSUMPTION": "unavailable",
|
||||
"BATTERY_VOLTAGE": "unavailable",
|
||||
|
@ -261,11 +255,7 @@ EXPECTED_STATE_EV_UNAVAILABLE = {
|
|||
"EV_IS_PLUGGED_IN": "unavailable",
|
||||
"EV_STATE_OF_CHARGE_MODE": "unavailable",
|
||||
"EV_STATE_OF_CHARGE_PERCENT": "unavailable",
|
||||
"EV_TIME_TO_FULLY_CHARGED": "unavailable",
|
||||
"EV_VEHICLE_TIME_DAYOFWEEK": "unavailable",
|
||||
"EV_VEHICLE_TIME_HOUR": "unavailable",
|
||||
"EV_VEHICLE_TIME_MINUTE": "unavailable",
|
||||
"EV_VEHICLE_TIME_SECOND": "unavailable",
|
||||
"EV_TIME_TO_FULLY_CHARGED_UTC": "unavailable",
|
||||
"EXT_EXTERNAL_TEMP": "unavailable",
|
||||
"ODOMETER": "unavailable",
|
||||
"POSITION_HEADING_DEGREE": "unavailable",
|
||||
|
|
|
@ -71,7 +71,8 @@ TEST_OPTIONS = {
|
|||
CONF_UPDATE_ENABLED: True,
|
||||
}
|
||||
|
||||
TEST_ENTITY_ID = "sensor.test_vehicle_2_odometer"
|
||||
TEST_DEVICE_NAME = "test_vehicle_2"
|
||||
TEST_ENTITY_ID = f"sensor.{TEST_DEVICE_NAME}_odometer"
|
||||
|
||||
|
||||
def advance_time_to_next_fetch(hass):
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
"""Test Subaru sensors."""
|
||||
from unittest.mock import patch
|
||||
|
||||
from homeassistant.components.subaru.const import VEHICLE_NAME
|
||||
from homeassistant.components.subaru.sensor import (
|
||||
API_GEN_2_SENSORS,
|
||||
EV_SENSORS,
|
||||
SAFETY_SENSORS,
|
||||
SENSOR_FIELD,
|
||||
SENSOR_TYPE,
|
||||
)
|
||||
from homeassistant.util import slugify
|
||||
from homeassistant.util.unit_system import IMPERIAL_SYSTEM
|
||||
|
@ -16,13 +13,14 @@ from .api_responses import (
|
|||
EXPECTED_STATE_EV_IMPERIAL,
|
||||
EXPECTED_STATE_EV_METRIC,
|
||||
EXPECTED_STATE_EV_UNAVAILABLE,
|
||||
TEST_VIN_2_EV,
|
||||
VEHICLE_DATA,
|
||||
VEHICLE_STATUS_EV,
|
||||
)
|
||||
from .conftest import MOCK_API_FETCH, MOCK_API_GET_DATA, advance_time_to_next_fetch
|
||||
|
||||
VEHICLE_NAME = VEHICLE_DATA[TEST_VIN_2_EV][VEHICLE_NAME]
|
||||
from .conftest import (
|
||||
MOCK_API_FETCH,
|
||||
MOCK_API_GET_DATA,
|
||||
TEST_DEVICE_NAME,
|
||||
advance_time_to_next_fetch,
|
||||
)
|
||||
|
||||
|
||||
async def test_sensors_ev_imperial(hass, ev_entry):
|
||||
|
@ -59,9 +57,9 @@ def _assert_data(hass, expected_state):
|
|||
expected_states = {}
|
||||
for item in sensor_list:
|
||||
expected_states[
|
||||
f"sensor.{slugify(f'{VEHICLE_NAME} {item[SENSOR_TYPE]}')}"
|
||||
] = expected_state[item[SENSOR_FIELD]]
|
||||
f"sensor.{slugify(f'{TEST_DEVICE_NAME} {item.name}')}"
|
||||
] = expected_state[item.key]
|
||||
|
||||
for sensor in expected_states:
|
||||
for sensor, value in expected_states.items():
|
||||
actual = hass.states.get(sensor)
|
||||
assert actual.state == expected_states[sensor]
|
||||
assert actual.state == value
|
||||
|
|
Loading…
Add table
Reference in a new issue