diff --git a/homeassistant/components/bmw_connected_drive/sensor.py b/homeassistant/components/bmw_connected_drive/sensor.py index 1faa858e9d8..daf7569bc77 100644 --- a/homeassistant/components/bmw_connected_drive/sensor.py +++ b/homeassistant/components/bmw_connected_drive/sensor.py @@ -1,12 +1,16 @@ """Support for reading vehicle status from BMW connected drive portal.""" from __future__ import annotations +from copy import copy +from dataclasses import dataclass import logging from bimmer_connected.const import SERVICE_ALL_TRIPS, SERVICE_LAST_TRIP, SERVICE_STATUS from bimmer_connected.state import ChargingState +from bimmer_connected.vehicle import ConnectedDriveVehicle -from homeassistant.components.sensor import SensorEntity +from homeassistant.components.sensor import SensorEntity, SensorEntityDescription +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_UNIT_SYSTEM_IMPERIAL, DEVICE_CLASS_TIMESTAMP, @@ -21,390 +25,399 @@ from homeassistant.const import ( VOLUME_GALLONS, VOLUME_LITERS, ) +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.icon import icon_for_battery_level import homeassistant.util.dt as dt_util +from homeassistant.util.unit_system import UnitSystem -from . import DOMAIN as BMW_DOMAIN, BMWConnectedDriveBaseEntity +from . import ( + DOMAIN as BMW_DOMAIN, + BMWConnectedDriveAccount, + BMWConnectedDriveBaseEntity, +) from .const import CONF_ACCOUNT, DATA_ENTRIES _LOGGER = logging.getLogger(__name__) -SENSOR_TYPES: dict[str, tuple[str | None, str | None, str | None, str | None, bool]] = { - # "": (, , , , ), + +@dataclass +class BMWSensorEntityDescription(SensorEntityDescription): + """Describes BMW sensor entity.""" + + unit_metric: str | None = None + unit_imperial: str | None = None + + +SENSOR_TYPES: dict[str, BMWSensorEntityDescription] = { # --- Generic --- - "charging_time_remaining": ( - "mdi:update", - None, - TIME_HOURS, - TIME_HOURS, - True, + "charging_time_remaining": BMWSensorEntityDescription( + key="charging_time_remaining", + icon="mdi:update", + unit_metric=TIME_HOURS, + unit_imperial=TIME_HOURS, ), - "charging_status": ( - "mdi:battery-charging", - None, - None, - None, - True, + "charging_status": BMWSensorEntityDescription( + key="charging_status", + icon="mdi:battery-charging", ), # No icon as this is dealt with directly as a special case in icon() - "charging_level_hv": ( - None, - None, - PERCENTAGE, - PERCENTAGE, - True, + "charging_level_hv": BMWSensorEntityDescription( + key="charging_level_hv", + unit_metric=PERCENTAGE, + unit_imperial=PERCENTAGE, ), # LastTrip attributes - "date_utc": ( - None, - DEVICE_CLASS_TIMESTAMP, - None, - None, - True, + "date_utc": BMWSensorEntityDescription( + key="date_utc", + device_class=DEVICE_CLASS_TIMESTAMP, ), - "duration": ( - "mdi:timer-outline", - None, - TIME_MINUTES, - TIME_MINUTES, - True, + "duration": BMWSensorEntityDescription( + key="duration", + icon="mdi:timer-outline", + unit_metric=TIME_MINUTES, + unit_imperial=TIME_MINUTES, ), - "electric_distance_ratio": ( - "mdi:percent-outline", - None, - PERCENTAGE, - PERCENTAGE, - False, + "electric_distance_ratio": BMWSensorEntityDescription( + key="electric_distance_ratio", + icon="mdi:percent-outline", + unit_metric=PERCENTAGE, + unit_imperial=PERCENTAGE, + entity_registry_enabled_default=False, ), # AllTrips attributes - "battery_size_max": ( - "mdi:battery-charging-high", - None, - ENERGY_WATT_HOUR, - ENERGY_WATT_HOUR, - False, + "battery_size_max": BMWSensorEntityDescription( + key="battery_size_max", + icon="mdi:battery-charging-high", + unit_metric=ENERGY_WATT_HOUR, + unit_imperial=ENERGY_WATT_HOUR, + entity_registry_enabled_default=False, ), - "reset_date_utc": ( - None, - DEVICE_CLASS_TIMESTAMP, - None, - None, - False, + "reset_date_utc": BMWSensorEntityDescription( + key="reset_date_utc", + device_class=DEVICE_CLASS_TIMESTAMP, + entity_registry_enabled_default=False, ), - "saved_co2": ( - "mdi:tree-outline", - None, - MASS_KILOGRAMS, - MASS_KILOGRAMS, - False, + "saved_co2": BMWSensorEntityDescription( + key="saved_co2", + icon="mdi:tree-outline", + unit_metric=MASS_KILOGRAMS, + unit_imperial=MASS_KILOGRAMS, + entity_registry_enabled_default=False, ), - "saved_co2_green_energy": ( - "mdi:tree-outline", - None, - MASS_KILOGRAMS, - MASS_KILOGRAMS, - False, + "saved_co2_green_energy": BMWSensorEntityDescription( + key="saved_co2_green_energy", + icon="mdi:tree-outline", + unit_metric=MASS_KILOGRAMS, + unit_imperial=MASS_KILOGRAMS, + entity_registry_enabled_default=False, ), # --- Specific --- - "mileage": ( - "mdi:speedometer", - None, - LENGTH_KILOMETERS, - LENGTH_MILES, - True, + "mileage": BMWSensorEntityDescription( + key="mileage", + icon="mdi:speedometer", + unit_metric=LENGTH_KILOMETERS, + unit_imperial=LENGTH_MILES, ), - "remaining_range_total": ( - "mdi:map-marker-distance", - None, - LENGTH_KILOMETERS, - LENGTH_MILES, - True, + "remaining_range_total": BMWSensorEntityDescription( + key="remaining_range_total", + icon="mdi:map-marker-distance", + unit_metric=LENGTH_KILOMETERS, + unit_imperial=LENGTH_MILES, ), - "remaining_range_electric": ( - "mdi:map-marker-distance", - None, - LENGTH_KILOMETERS, - LENGTH_MILES, - True, + "remaining_range_electric": BMWSensorEntityDescription( + key="remaining_range_electric", + icon="mdi:map-marker-distance", + unit_metric=LENGTH_KILOMETERS, + unit_imperial=LENGTH_MILES, ), - "remaining_range_fuel": ( - "mdi:map-marker-distance", - None, - LENGTH_KILOMETERS, - LENGTH_MILES, - True, + "remaining_range_fuel": BMWSensorEntityDescription( + key="remaining_range_fuel", + icon="mdi:map-marker-distance", + unit_metric=LENGTH_KILOMETERS, + unit_imperial=LENGTH_MILES, ), - "max_range_electric": ( - "mdi:map-marker-distance", - None, - LENGTH_KILOMETERS, - LENGTH_MILES, - True, + "max_range_electric": BMWSensorEntityDescription( + key="max_range_electric", + icon="mdi:map-marker-distance", + unit_metric=LENGTH_KILOMETERS, + unit_imperial=LENGTH_MILES, ), - "remaining_fuel": ( - "mdi:gas-station", - None, - VOLUME_LITERS, - VOLUME_GALLONS, - True, + "remaining_fuel": BMWSensorEntityDescription( + key="remaining_fuel", + icon="mdi:gas-station", + unit_metric=VOLUME_LITERS, + unit_imperial=VOLUME_GALLONS, ), # LastTrip attributes - "average_combined_consumption": ( - "mdi:flash", - None, - f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_KILOMETERS}", - f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_MILES}", - True, + "average_combined_consumption": BMWSensorEntityDescription( + key="average_combined_consumption", + icon="mdi:flash", + unit_metric=f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_KILOMETERS}", + unit_imperial=f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_MILES}", ), - "average_electric_consumption": ( - "mdi:power-plug-outline", - None, - f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_KILOMETERS}", - f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_MILES}", - True, + "average_electric_consumption": BMWSensorEntityDescription( + key="average_electric_consumption", + icon="mdi:power-plug-outline", + unit_metric=f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_KILOMETERS}", + unit_imperial=f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_MILES}", ), - "average_recuperation": ( - "mdi:recycle-variant", - None, - f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_KILOMETERS}", - f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_MILES}", - True, + "average_recuperation": BMWSensorEntityDescription( + key="average_recuperation", + icon="mdi:recycle-variant", + unit_metric=f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_KILOMETERS}", + unit_imperial=f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_MILES}", ), - "electric_distance": ( - "mdi:map-marker-distance", - None, - LENGTH_KILOMETERS, - LENGTH_MILES, - True, + "electric_distance": BMWSensorEntityDescription( + key="electric_distance", + icon="mdi:map-marker-distance", + unit_metric=LENGTH_KILOMETERS, + unit_imperial=LENGTH_MILES, ), - "saved_fuel": ( - "mdi:fuel", - None, - VOLUME_LITERS, - VOLUME_GALLONS, - False, + "saved_fuel": BMWSensorEntityDescription( + key="saved_fuel", + icon="mdi:fuel", + unit_metric=VOLUME_LITERS, + unit_imperial=VOLUME_GALLONS, + entity_registry_enabled_default=False, ), - "total_distance": ( - "mdi:map-marker-distance", - None, - LENGTH_KILOMETERS, - LENGTH_MILES, - True, + "total_distance": BMWSensorEntityDescription( + key="total_distance", + icon="mdi:map-marker-distance", + unit_metric=LENGTH_KILOMETERS, + unit_imperial=LENGTH_MILES, ), # AllTrips attributes - "average_combined_consumption_community_average": ( - "mdi:flash", - None, - f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_KILOMETERS}", - f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_MILES}", - False, + "average_combined_consumption_community_average": BMWSensorEntityDescription( + key="average_combined_consumption_community_average", + icon="mdi:flash", + unit_metric=f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_KILOMETERS}", + unit_imperial=f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_MILES}", + entity_registry_enabled_default=False, ), - "average_combined_consumption_community_high": ( - "mdi:flash", - None, - f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_KILOMETERS}", - f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_MILES}", - False, + "average_combined_consumption_community_high": BMWSensorEntityDescription( + key="average_combined_consumption_community_high", + icon="mdi:flash", + unit_metric=f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_KILOMETERS}", + unit_imperial=f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_MILES}", + entity_registry_enabled_default=False, ), - "average_combined_consumption_community_low": ( - "mdi:flash", - None, - f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_KILOMETERS}", - f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_MILES}", - False, + "average_combined_consumption_community_low": BMWSensorEntityDescription( + key="average_combined_consumption_community_low", + icon="mdi:flash", + unit_metric=f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_KILOMETERS}", + unit_imperial=f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_MILES}", + entity_registry_enabled_default=False, ), - "average_combined_consumption_user_average": ( - "mdi:flash", - None, - f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_KILOMETERS}", - f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_MILES}", - True, + "average_combined_consumption_user_average": BMWSensorEntityDescription( + key="average_combined_consumption_user_average", + icon="mdi:flash", + unit_metric=f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_KILOMETERS}", + unit_imperial=f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_MILES}", ), - "average_electric_consumption_community_average": ( - "mdi:power-plug-outline", - None, - f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_KILOMETERS}", - f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_MILES}", - False, + "average_electric_consumption_community_average": BMWSensorEntityDescription( + key="average_electric_consumption_community_average", + icon="mdi:power-plug-outline", + unit_metric=f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_KILOMETERS}", + unit_imperial=f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_MILES}", + entity_registry_enabled_default=False, ), - "average_electric_consumption_community_high": ( - "mdi:power-plug-outline", - None, - f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_KILOMETERS}", - f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_MILES}", - False, + "average_electric_consumption_community_high": BMWSensorEntityDescription( + key="average_electric_consumption_community_high", + icon="mdi:power-plug-outline", + unit_metric=f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_KILOMETERS}", + unit_imperial=f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_MILES}", + entity_registry_enabled_default=False, ), - "average_electric_consumption_community_low": ( - "mdi:power-plug-outline", - None, - f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_KILOMETERS}", - f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_MILES}", - False, + "average_electric_consumption_community_low": BMWSensorEntityDescription( + key="average_electric_consumption_community_low", + icon="mdi:power-plug-outline", + unit_metric=f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_KILOMETERS}", + unit_imperial=f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_MILES}", + entity_registry_enabled_default=False, ), - "average_electric_consumption_user_average": ( - "mdi:power-plug-outline", - None, - f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_KILOMETERS}", - f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_MILES}", - True, + "average_electric_consumption_user_average": BMWSensorEntityDescription( + key="average_electric_consumption_user_average", + icon="mdi:power-plug-outline", + unit_metric=f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_KILOMETERS}", + unit_imperial=f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_MILES}", ), - "average_recuperation_community_average": ( - "mdi:recycle-variant", - None, - f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_KILOMETERS}", - f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_MILES}", - False, + "average_recuperation_community_average": BMWSensorEntityDescription( + key="average_recuperation_community_average", + icon="mdi:recycle-variant", + unit_metric=f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_KILOMETERS}", + unit_imperial=f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_MILES}", + entity_registry_enabled_default=False, ), - "average_recuperation_community_high": ( - "mdi:recycle-variant", - None, - f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_KILOMETERS}", - f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_MILES}", - False, + "average_recuperation_community_high": BMWSensorEntityDescription( + key="average_recuperation_community_high", + icon="mdi:recycle-variant", + unit_metric=f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_KILOMETERS}", + unit_imperial=f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_MILES}", + entity_registry_enabled_default=False, ), - "average_recuperation_community_low": ( - "mdi:recycle-variant", - None, - f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_KILOMETERS}", - f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_MILES}", - False, + "average_recuperation_community_low": BMWSensorEntityDescription( + key="average_recuperation_community_low", + icon="mdi:recycle-variant", + unit_metric=f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_KILOMETERS}", + unit_imperial=f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_MILES}", + entity_registry_enabled_default=False, ), - "average_recuperation_user_average": ( - "mdi:recycle-variant", - None, - f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_KILOMETERS}", - f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_MILES}", - True, + "average_recuperation_user_average": BMWSensorEntityDescription( + key="average_recuperation_user_average", + icon="mdi:recycle-variant", + unit_metric=f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_KILOMETERS}", + unit_imperial=f"{ENERGY_KILO_WATT_HOUR}/100{LENGTH_MILES}", ), - "chargecycle_range_community_average": ( - "mdi:map-marker-distance", - None, - LENGTH_KILOMETERS, - LENGTH_MILES, - False, + "chargecycle_range_community_average": BMWSensorEntityDescription( + key="chargecycle_range_community_average", + icon="mdi:map-marker-distance", + unit_metric=LENGTH_KILOMETERS, + unit_imperial=LENGTH_MILES, + entity_registry_enabled_default=False, ), - "chargecycle_range_community_high": ( - "mdi:map-marker-distance", - None, - LENGTH_KILOMETERS, - LENGTH_MILES, - False, + "chargecycle_range_community_high": BMWSensorEntityDescription( + key="chargecycle_range_community_high", + icon="mdi:map-marker-distance", + unit_metric=LENGTH_KILOMETERS, + unit_imperial=LENGTH_MILES, + entity_registry_enabled_default=False, ), - "chargecycle_range_community_low": ( - "mdi:map-marker-distance", - None, - LENGTH_KILOMETERS, - LENGTH_MILES, - False, + "chargecycle_range_community_low": BMWSensorEntityDescription( + key="chargecycle_range_community_low", + icon="mdi:map-marker-distance", + unit_metric=LENGTH_KILOMETERS, + unit_imperial=LENGTH_MILES, + entity_registry_enabled_default=False, ), - "chargecycle_range_user_average": ( - "mdi:map-marker-distance", - None, - LENGTH_KILOMETERS, - LENGTH_MILES, - True, + "chargecycle_range_user_average": BMWSensorEntityDescription( + key="chargecycle_range_user_average", + icon="mdi:map-marker-distance", + unit_metric=LENGTH_KILOMETERS, + unit_imperial=LENGTH_MILES, ), - "chargecycle_range_user_current_charge_cycle": ( - "mdi:map-marker-distance", - None, - LENGTH_KILOMETERS, - LENGTH_MILES, - True, + "chargecycle_range_user_current_charge_cycle": BMWSensorEntityDescription( + key="chargecycle_range_user_current_charge_cycle", + icon="mdi:map-marker-distance", + unit_metric=LENGTH_KILOMETERS, + unit_imperial=LENGTH_MILES, ), - "chargecycle_range_user_high": ( - "mdi:map-marker-distance", - None, - LENGTH_KILOMETERS, - LENGTH_MILES, - True, + "chargecycle_range_user_high": BMWSensorEntityDescription( + key="chargecycle_range_user_high", + icon="mdi:map-marker-distance", + unit_metric=LENGTH_KILOMETERS, + unit_imperial=LENGTH_MILES, ), - "total_electric_distance_community_average": ( - "mdi:map-marker-distance", - None, - LENGTH_KILOMETERS, - LENGTH_MILES, - False, + "total_electric_distance_community_average": BMWSensorEntityDescription( + key="total_electric_distance_community_average", + icon="mdi:map-marker-distance", + unit_metric=LENGTH_KILOMETERS, + unit_imperial=LENGTH_MILES, + entity_registry_enabled_default=False, ), - "total_electric_distance_community_high": ( - "mdi:map-marker-distance", - None, - LENGTH_KILOMETERS, - LENGTH_MILES, - False, + "total_electric_distance_community_high": BMWSensorEntityDescription( + key="total_electric_distance_community_high", + icon="mdi:map-marker-distance", + unit_metric=LENGTH_KILOMETERS, + unit_imperial=LENGTH_MILES, + entity_registry_enabled_default=False, ), - "total_electric_distance_community_low": ( - "mdi:map-marker-distance", - None, - LENGTH_KILOMETERS, - LENGTH_MILES, - False, + "total_electric_distance_community_low": BMWSensorEntityDescription( + key="total_electric_distance_community_low", + icon="mdi:map-marker-distance", + unit_metric=LENGTH_KILOMETERS, + unit_imperial=LENGTH_MILES, + entity_registry_enabled_default=False, ), - "total_electric_distance_user_average": ( - "mdi:map-marker-distance", - None, - LENGTH_KILOMETERS, - LENGTH_MILES, - False, + "total_electric_distance_user_average": BMWSensorEntityDescription( + key="total_electric_distance_user_average", + icon="mdi:map-marker-distance", + unit_metric=LENGTH_KILOMETERS, + unit_imperial=LENGTH_MILES, + entity_registry_enabled_default=False, ), - "total_electric_distance_user_total": ( - "mdi:map-marker-distance", - None, - LENGTH_KILOMETERS, - LENGTH_MILES, - False, + "total_electric_distance_user_total": BMWSensorEntityDescription( + key="total_electric_distance_user_total", + icon="mdi:map-marker-distance", + unit_metric=LENGTH_KILOMETERS, + unit_imperial=LENGTH_MILES, + entity_registry_enabled_default=False, ), - "total_saved_fuel": ( - "mdi:fuel", - None, - VOLUME_LITERS, - VOLUME_GALLONS, - False, + "total_saved_fuel": BMWSensorEntityDescription( + key="total_saved_fuel", + icon="mdi:fuel", + unit_metric=VOLUME_LITERS, + unit_imperial=VOLUME_GALLONS, + entity_registry_enabled_default=False, ), } -async def async_setup_entry(hass, config_entry, async_add_entities): +DEFAULT_BMW_DESCRIPTION = BMWSensorEntityDescription( + key="", + entity_registry_enabled_default=True, +) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: """Set up the BMW ConnectedDrive sensors from config entry.""" # pylint: disable=too-many-nested-blocks - account = hass.data[BMW_DOMAIN][DATA_ENTRIES][config_entry.entry_id][CONF_ACCOUNT] - entities = [] + unit_system = hass.config.units + account: BMWConnectedDriveAccount = hass.data[BMW_DOMAIN][DATA_ENTRIES][ + config_entry.entry_id + ][CONF_ACCOUNT] + entities: list[BMWConnectedDriveSensor] = [] for vehicle in account.account.vehicles: for service in vehicle.available_state_services: if service == SERVICE_STATUS: - for attribute_name in vehicle.drive_train_attributes: - if attribute_name in vehicle.available_attributes: - device = BMWConnectedDriveSensor( - hass, account, vehicle, attribute_name + entities.extend( + [ + BMWConnectedDriveSensor( + account, vehicle, description, unit_system ) - entities.append(device) + for attribute_name in vehicle.drive_train_attributes + if attribute_name in vehicle.available_attributes + and (description := SENSOR_TYPES.get(attribute_name)) + ] + ) if service == SERVICE_LAST_TRIP: - for attribute_name in vehicle.state.last_trip.available_attributes: - if attribute_name == "date": - device = BMWConnectedDriveSensor( - hass, + entities.extend( + [ + BMWConnectedDriveSensor( + account, vehicle, description, unit_system, service + ) + for attribute_name in vehicle.state.last_trip.available_attributes + if attribute_name != "date" + and (description := SENSOR_TYPES.get(attribute_name)) + ] + ) + if "date" in vehicle.state.last_trip.available_attributes: + entities.append( + BMWConnectedDriveSensor( account, vehicle, - "date_utc", + SENSOR_TYPES["date_utc"], + unit_system, service, ) - entities.append(device) - else: - device = BMWConnectedDriveSensor( - hass, account, vehicle, attribute_name, service - ) - entities.append(device) + ) if service == SERVICE_ALL_TRIPS: for attribute_name in vehicle.state.all_trips.available_attributes: if attribute_name == "reset_date": - device = BMWConnectedDriveSensor( - hass, - account, - vehicle, - "reset_date_utc", - service, + entities.append( + BMWConnectedDriveSensor( + account, + vehicle, + SENSOR_TYPES["reset_date_utc"], + unit_system, + service, + ) ) - entities.append(device) elif attribute_name in ( "average_combined_consumption", "average_electric_consumption", @@ -412,45 +425,60 @@ async def async_setup_entry(hass, config_entry, async_add_entities): "chargecycle_range", "total_electric_distance", ): - for attr in ( - "community_average", - "community_high", - "community_low", - "user_average", - ): - device = BMWConnectedDriveSensor( - hass, + entities.extend( + [ + BMWConnectedDriveSensor( + account, + vehicle, + SENSOR_TYPES[f"{attribute_name}_{attr}"], + unit_system, + service, + ) + for attr in ( + "community_average", + "community_high", + "community_low", + "user_average", + ) + ] + ) + if attribute_name == "chargecycle_range": + entities.extend( + BMWConnectedDriveSensor( + account, + vehicle, + SENSOR_TYPES[f"{attribute_name}_{attr}"], + unit_system, + service, + ) + for attr in ("user_current_charge_cycle", "user_high") + ) + elif attribute_name == "total_electric_distance": + entities.extend( + [ + BMWConnectedDriveSensor( + account, + vehicle, + SENSOR_TYPES[f"{attribute_name}_{attr}"], + unit_system, + service, + ) + for attr in ("user_total",) + ] + ) + else: + if (description := SENSOR_TYPES.get(attribute_name)) is None: + description = copy(DEFAULT_BMW_DESCRIPTION) + description.key = attribute_name + entities.append( + BMWConnectedDriveSensor( account, vehicle, - f"{attribute_name}_{attr}", + description, + unit_system, service, ) - entities.append(device) - if attribute_name == "chargecycle_range": - for attr in ("user_current_charge_cycle", "user_high"): - device = BMWConnectedDriveSensor( - hass, - account, - vehicle, - f"{attribute_name}_{attr}", - service, - ) - entities.append(device) - if attribute_name == "total_electric_distance": - for attr in ("user_total",): - device = BMWConnectedDriveSensor( - hass, - account, - vehicle, - f"{attribute_name}_{attr}", - service, - ) - entities.append(device) - else: - device = BMWConnectedDriveSensor( - hass, account, vehicle, attribute_name, service ) - entities.append(device) async_add_entities(entities, True) @@ -458,52 +486,57 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class BMWConnectedDriveSensor(BMWConnectedDriveBaseEntity, SensorEntity): """Representation of a BMW vehicle sensor.""" - def __init__(self, hass, account, vehicle, attribute: str, service=None): + entity_description: BMWSensorEntityDescription + + def __init__( + self, + account: BMWConnectedDriveAccount, + vehicle: ConnectedDriveVehicle, + description: BMWSensorEntityDescription, + unit_system: UnitSystem, + service: str | None = None, + ) -> None: """Initialize BMW vehicle sensor.""" super().__init__(account, vehicle) + self.entity_description = description - self._attribute = attribute self._service = service if service: - self._attr_name = f"{vehicle.name} {service.lower()}_{attribute}" - self._attr_unique_id = f"{vehicle.vin}-{service.lower()}-{attribute}" + self._attr_name = f"{vehicle.name} {service.lower()}_{description.key}" + self._attr_unique_id = f"{vehicle.vin}-{service.lower()}-{description.key}" else: - self._attr_name = f"{vehicle.name} {attribute}" - self._attr_unique_id = f"{vehicle.vin}-{attribute}" - self._attribute_info = SENSOR_TYPES.get( - attribute, (None, None, None, None, True) - ) - self._attr_entity_registry_enabled_default = self._attribute_info[4] - self._attr_icon = self._attribute_info[0] - self._attr_device_class = self._attribute_info[1] - if hass.config.units.name == CONF_UNIT_SYSTEM_IMPERIAL: - self._attr_native_unit_of_measurement = self._attribute_info[3] + self._attr_name = f"{vehicle.name} {description.key}" + self._attr_unique_id = f"{vehicle.vin}-{description.key}" + + if unit_system.name == CONF_UNIT_SYSTEM_IMPERIAL: + self._attr_native_unit_of_measurement = description.unit_imperial else: - self._attr_native_unit_of_measurement = self._attribute_info[2] + self._attr_native_unit_of_measurement = description.unit_metric def update(self) -> None: """Read new state data from the library.""" _LOGGER.debug("Updating %s", self._vehicle.name) vehicle_state = self._vehicle.state - if self._attribute == "charging_status": - self._attr_native_value = getattr(vehicle_state, self._attribute).value + sensor_key = self.entity_description.key + if sensor_key == "charging_status": + self._attr_native_value = getattr(vehicle_state, sensor_key).value elif self.unit_of_measurement == VOLUME_GALLONS: - value = getattr(vehicle_state, self._attribute) + value = getattr(vehicle_state, sensor_key) value_converted = self.hass.config.units.volume(value, VOLUME_LITERS) self._attr_native_value = round(value_converted) elif self.unit_of_measurement == LENGTH_MILES: - value = getattr(vehicle_state, self._attribute) + value = getattr(vehicle_state, sensor_key) value_converted = self.hass.config.units.length(value, LENGTH_KILOMETERS) self._attr_native_value = round(value_converted) elif self._service is None: - self._attr_native_value = getattr(vehicle_state, self._attribute) + self._attr_native_value = getattr(vehicle_state, sensor_key) elif self._service == SERVICE_LAST_TRIP: vehicle_last_trip = self._vehicle.state.last_trip - if self._attribute == "date_utc": + if sensor_key == "date_utc": date_str = getattr(vehicle_last_trip, "date") self._attr_native_value = dt_util.parse_datetime(date_str).isoformat() else: - self._attr_native_value = getattr(vehicle_last_trip, self._attribute) + self._attr_native_value = getattr(vehicle_last_trip, sensor_key) elif self._service == SERVICE_ALL_TRIPS: vehicle_all_trips = self._vehicle.state.all_trips for attribute in ( @@ -513,21 +546,21 @@ class BMWConnectedDriveSensor(BMWConnectedDriveBaseEntity, SensorEntity): "chargecycle_range", "total_electric_distance", ): - if self._attribute.startswith(f"{attribute}_"): + if sensor_key.startswith(f"{attribute}_"): attr = getattr(vehicle_all_trips, attribute) - sub_attr = self._attribute.replace(f"{attribute}_", "") + sub_attr = sensor_key.replace(f"{attribute}_", "") self._attr_native_value = getattr(attr, sub_attr) return - if self._attribute == "reset_date_utc": + if sensor_key == "reset_date_utc": date_str = getattr(vehicle_all_trips, "reset_date") self._attr_native_value = dt_util.parse_datetime(date_str).isoformat() else: - self._attr_native_value = getattr(vehicle_all_trips, self._attribute) + self._attr_native_value = getattr(vehicle_all_trips, sensor_key) vehicle_state = self._vehicle.state charging_state = vehicle_state.charging_status in [ChargingState.CHARGING] - if self._attribute == "charging_level_hv": + if sensor_key == "charging_level_hv": self._attr_icon = icon_for_battery_level( battery_level=vehicle_state.charging_level_hv, charging=charging_state )