diff --git a/homeassistant/components/subaru/config_flow.py b/homeassistant/components/subaru/config_flow.py index 79c412c8f85..6d1d5015ed3 100644 --- a/homeassistant/components/subaru/config_flow.py +++ b/homeassistant/components/subaru/config_flow.py @@ -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) diff --git a/homeassistant/components/subaru/const.py b/homeassistant/components/subaru/const.py index 3ad7dd58af5..dc9a2224860 100644 --- a/homeassistant/components/subaru/const.py +++ b/homeassistant/components/subaru/const.py @@ -31,7 +31,7 @@ VEHICLE_STATUS = "status" API_GEN_1 = "g1" API_GEN_2 = "g2" -MANUFACTURER = "Subaru Corp." +MANUFACTURER = "Subaru" PLATFORMS = [ Platform.LOCK, diff --git a/homeassistant/components/subaru/entity.py b/homeassistant/components/subaru/entity.py deleted file mode 100644 index 2bdb1425b2d..00000000000 --- a/homeassistant/components/subaru/entity.py +++ /dev/null @@ -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, - ) diff --git a/homeassistant/components/subaru/manifest.json b/homeassistant/components/subaru/manifest.json index 0123f26f916..6bae6e8422d 100644 --- a/homeassistant/components/subaru/manifest.json +++ b/homeassistant/components/subaru/manifest.json @@ -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"] diff --git a/homeassistant/components/subaru/sensor.py b/homeassistant/components/subaru/sensor.py index f1a1ea96382..672fe6b0dcc 100644 --- a/homeassistant/components/subaru/sensor.py +++ b/homeassistant/components/subaru/sensor.py @@ -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 diff --git a/requirements_all.txt b/requirements_all.txt index 04e4320779b..11a7b3630a8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -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 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1dc842fa02f..76970ca4307 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -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 diff --git a/tests/components/subaru/api_responses.py b/tests/components/subaru/api_responses.py index b6a79ab8829..bd107f4bb37 100644 --- a/tests/components/subaru/api_responses.py +++ b/tests/components/subaru/api_responses.py @@ -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", diff --git a/tests/components/subaru/conftest.py b/tests/components/subaru/conftest.py index 53bd04e7e55..492ec06c1e8 100644 --- a/tests/components/subaru/conftest.py +++ b/tests/components/subaru/conftest.py @@ -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): diff --git a/tests/components/subaru/test_sensor.py b/tests/components/subaru/test_sensor.py index 6ad5e729290..3f0cb773461 100644 --- a/tests/components/subaru/test_sensor.py +++ b/tests/components/subaru/test_sensor.py @@ -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