From 668437741a582ce5464c8bd6a9b592dcb2cb6f83 Mon Sep 17 00:00:00 2001 From: Robert Hillis Date: Wed, 21 Jul 2021 08:09:54 -0400 Subject: [PATCH] Use entity class attributes for Bmw connected drive (#53054) * Use entity class attributes for bmw_connected_driv * forgot the icon --- .../bmw_connected_drive/__init__.py | 26 ++--- .../bmw_connected_drive/binary_sensor.py | 101 ++++++----------- .../bmw_connected_drive/device_tracker.py | 27 +---- .../components/bmw_connected_drive/lock.py | 63 +++-------- .../components/bmw_connected_drive/sensor.py | 103 ++++++------------ 5 files changed, 99 insertions(+), 221 deletions(-) diff --git a/homeassistant/components/bmw_connected_drive/__init__.py b/homeassistant/components/bmw_connected_drive/__init__.py index 599892d6a03..3bd2365f88e 100644 --- a/homeassistant/components/bmw_connected_drive/__init__.py +++ b/homeassistant/components/bmw_connected_drive/__init__.py @@ -21,7 +21,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry, discovery import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import DeviceInfo, Entity +from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import track_utc_time_change from homeassistant.util import slugify import homeassistant.util.dt as dt_util @@ -317,6 +317,8 @@ class BMWConnectedDriveAccount: class BMWConnectedDriveBaseEntity(Entity): """Common base for BMW entities.""" + _attr_should_poll = False + def __init__(self, account, vehicle): """Initialize sensor.""" self._account = account @@ -326,15 +328,11 @@ class BMWConnectedDriveBaseEntity(Entity): "vin": self._vehicle.vin, ATTR_ATTRIBUTION: ATTRIBUTION, } - - @property - def device_info(self) -> DeviceInfo: - """Return info for device registry.""" - return { - "identifiers": {(DOMAIN, self._vehicle.vin)}, - "name": f'{self._vehicle.attributes.get("brand")} {self._vehicle.name}', - "model": self._vehicle.name, - "manufacturer": self._vehicle.attributes.get("brand"), + self._attr_device_info = { + "identifiers": {(DOMAIN, vehicle.vin)}, + "name": f'{vehicle.attributes.get("brand")} {vehicle.name}', + "model": vehicle.name, + "manufacturer": vehicle.attributes.get("brand"), } @property @@ -342,14 +340,6 @@ class BMWConnectedDriveBaseEntity(Entity): """Return the state attributes of the sensor.""" return self._attrs - @property - def should_poll(self): - """Do not poll this class. - - Updates are triggered from BMWConnectedDriveAccount. - """ - return False - def update_callback(self): """Schedule a state update.""" self.schedule_update_ha_state(True) diff --git a/homeassistant/components/bmw_connected_drive/binary_sensor.py b/homeassistant/components/bmw_connected_drive/binary_sensor.py index bebb55bbde0..d7f0d150193 100644 --- a/homeassistant/components/bmw_connected_drive/binary_sensor.py +++ b/homeassistant/components/bmw_connected_drive/binary_sensor.py @@ -76,41 +76,45 @@ class BMWConnectedDriveSensor(BMWConnectedDriveBaseEntity, BinarySensorEntity): super().__init__(account, vehicle) self._attribute = attribute - self._name = f"{self._vehicle.name} {self._attribute}" - self._unique_id = f"{self._vehicle.vin}-{self._attribute}" + self._attr_name = f"{vehicle.name} {attribute}" + self._attr_unique_id = f"{vehicle.vin}-{attribute}" self._sensor_name = sensor_name - self._device_class = device_class - self._icon = icon - self._state = None + self._attr_device_class = device_class + self._attr_icon = icon - @property - def unique_id(self): - """Return the unique ID of the binary sensor.""" - return self._unique_id + def update(self): + """Read new state data from the library.""" + vehicle_state = self._vehicle.state - @property - def name(self): - """Return the name of the binary sensor.""" - return self._name + # device class opening: On means open, Off means closed + if self._attribute == "lids": + _LOGGER.debug("Status of lid: %s", vehicle_state.all_lids_closed) + self._attr_state = not vehicle_state.all_lids_closed + if self._attribute == "windows": + self._attr_state = not vehicle_state.all_windows_closed + # device class lock: On means unlocked, Off means locked + if self._attribute == "door_lock_state": + # Possible values: LOCKED, SECURED, SELECTIVE_LOCKED, UNLOCKED + self._attr_state = vehicle_state.door_lock_state not in [ + LockState.LOCKED, + LockState.SECURED, + ] + # device class light: On means light detected, Off means no light + if self._attribute == "lights_parking": + self._attr_state = vehicle_state.are_parking_lights_on + # device class problem: On means problem detected, Off means no problem + if self._attribute == "condition_based_services": + self._attr_state = not vehicle_state.are_all_cbs_ok + if self._attribute == "check_control_messages": + self._attr_state = vehicle_state.has_check_control_messages + # device class power: On means power detected, Off means no power + if self._attribute == "charging_status": + self._attr_state = vehicle_state.charging_status in [ChargingState.CHARGING] + # device class plug: On means device is plugged in, + # Off means device is unplugged + if self._attribute == "connection_status": + self._attr_state = vehicle_state.connection_status == "CONNECTED" - @property - def icon(self): - """Icon to use in the frontend, if any.""" - return self._icon - - @property - def device_class(self): - """Return the class of the binary sensor.""" - return self._device_class - - @property - def is_on(self): - """Return the state of the binary sensor.""" - return self._state - - @property - def extra_state_attributes(self): - """Return the state attributes of the binary sensor.""" vehicle_state = self._vehicle.state result = self._attrs.copy() @@ -144,40 +148,7 @@ class BMWConnectedDriveSensor(BMWConnectedDriveBaseEntity, BinarySensorEntity): elif self._attribute == "connection_status": result["connection_status"] = vehicle_state.connection_status - return sorted(result.items()) - - def update(self): - """Read new state data from the library.""" - vehicle_state = self._vehicle.state - - # device class opening: On means open, Off means closed - if self._attribute == "lids": - _LOGGER.debug("Status of lid: %s", vehicle_state.all_lids_closed) - self._state = not vehicle_state.all_lids_closed - if self._attribute == "windows": - self._state = not vehicle_state.all_windows_closed - # device class lock: On means unlocked, Off means locked - if self._attribute == "door_lock_state": - # Possible values: LOCKED, SECURED, SELECTIVE_LOCKED, UNLOCKED - self._state = vehicle_state.door_lock_state not in [ - LockState.LOCKED, - LockState.SECURED, - ] - # device class light: On means light detected, Off means no light - if self._attribute == "lights_parking": - self._state = vehicle_state.are_parking_lights_on - # device class problem: On means problem detected, Off means no problem - if self._attribute == "condition_based_services": - self._state = not vehicle_state.are_all_cbs_ok - if self._attribute == "check_control_messages": - self._state = vehicle_state.has_check_control_messages - # device class power: On means power detected, Off means no power - if self._attribute == "charging_status": - self._state = vehicle_state.charging_status in [ChargingState.CHARGING] - # device class plug: On means device is plugged in, - # Off means device is unplugged - if self._attribute == "connection_status": - self._state = vehicle_state.connection_status == "CONNECTED" + self._attr_extra_state_attributes = sorted(result.items()) def _format_cbs_report(self, report): result = {} diff --git a/homeassistant/components/bmw_connected_drive/device_tracker.py b/homeassistant/components/bmw_connected_drive/device_tracker.py index 25adf6cb09f..62b2ed9b9d9 100644 --- a/homeassistant/components/bmw_connected_drive/device_tracker.py +++ b/homeassistant/components/bmw_connected_drive/device_tracker.py @@ -29,15 +29,18 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class BMWDeviceTracker(BMWConnectedDriveBaseEntity, TrackerEntity): """BMW Connected Drive device tracker.""" + _attr_force_update = False + _attr_icon = "mdi:car" + def __init__(self, account, vehicle): """Initialize the Tracker.""" super().__init__(account, vehicle) - self._unique_id = vehicle.vin + self._attr_unique_id = vehicle.vin self._location = ( vehicle.state.gps_position if vehicle.state.gps_position else (None, None) ) - self._name = vehicle.name + self._attr_name = vehicle.name @property def latitude(self): @@ -49,31 +52,11 @@ class BMWDeviceTracker(BMWConnectedDriveBaseEntity, TrackerEntity): """Return longitude value of the device.""" return self._location[1] if self._location else None - @property - def name(self): - """Return the name of the device.""" - return self._name - - @property - def unique_id(self): - """Return the unique ID.""" - return self._unique_id - @property def source_type(self): """Return the source type, eg gps or router, of the device.""" return SOURCE_TYPE_GPS - @property - def icon(self): - """Return the icon to use in the frontend, if any.""" - return "mdi:car" - - @property - def force_update(self): - """All updates do not need to be written to the state machine.""" - return False - def update(self): """Update state of the decvice tracker.""" self._location = ( diff --git a/homeassistant/components/bmw_connected_drive/lock.py b/homeassistant/components/bmw_connected_drive/lock.py index 97c9be7216b..3d27cf833b6 100644 --- a/homeassistant/components/bmw_connected_drive/lock.py +++ b/homeassistant/components/bmw_connected_drive/lock.py @@ -4,7 +4,6 @@ import logging from bimmer_connected.state import LockState from homeassistant.components.lock import LockEntity -from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED from . import DOMAIN as BMW_DOMAIN, BMWConnectedDriveBaseEntity from .const import CONF_ACCOUNT, DATA_ENTRIES @@ -33,50 +32,17 @@ class BMWLock(BMWConnectedDriveBaseEntity, LockEntity): super().__init__(account, vehicle) self._attribute = attribute - self._name = f"{self._vehicle.name} {self._attribute}" - self._unique_id = f"{self._vehicle.vin}-{self._attribute}" + self._attr_name = f"{vehicle.name} {attribute}" + self._attr_unique_id = f"{vehicle.vin}-{attribute}" self._sensor_name = sensor_name - self._state = None - self.door_lock_state_available = ( - DOOR_LOCK_STATE in self._vehicle.available_attributes - ) - - @property - def unique_id(self): - """Return the unique ID of the lock.""" - return self._unique_id - - @property - def name(self): - """Return the name of the lock.""" - return self._name - - @property - def extra_state_attributes(self): - """Return the state attributes of the lock.""" - vehicle_state = self._vehicle.state - result = self._attrs.copy() - - if self.door_lock_state_available: - result["door_lock_state"] = vehicle_state.door_lock_state.value - result["last_update_reason"] = vehicle_state.last_update_reason - return result - - @property - def is_locked(self): - """Return true if lock is locked.""" - if self.door_lock_state_available: - result = self._state == STATE_LOCKED - else: - result = None - return result + self.door_lock_state_available = DOOR_LOCK_STATE in vehicle.available_attributes def lock(self, **kwargs): """Lock the car.""" _LOGGER.debug("%s: locking doors", self._vehicle.name) # Optimistic state set here because it takes some time before the # update callback response - self._state = STATE_LOCKED + self._attr_is_locked = True self.schedule_update_ha_state() self._vehicle.remote_services.trigger_remote_door_lock() @@ -85,18 +51,23 @@ class BMWLock(BMWConnectedDriveBaseEntity, LockEntity): _LOGGER.debug("%s: unlocking doors", self._vehicle.name) # Optimistic state set here because it takes some time before the # update callback response - self._state = STATE_UNLOCKED + self._attr_is_locked = False self.schedule_update_ha_state() self._vehicle.remote_services.trigger_remote_door_unlock() def update(self): """Update state of the lock.""" _LOGGER.debug("%s: updating data for %s", self._vehicle.name, self._attribute) - vehicle_state = self._vehicle.state + if self._vehicle.state.door_lock_state in [LockState.LOCKED, LockState.SECURED]: + self._attr_is_locked = True + else: + self._attr_is_locked = False + if not self.door_lock_state_available: + self._attr_is_locked = None - # Possible values: LOCKED, SECURED, SELECTIVE_LOCKED, UNLOCKED - self._state = ( - STATE_LOCKED - if vehicle_state.door_lock_state in [LockState.LOCKED, LockState.SECURED] - else STATE_UNLOCKED - ) + vehicle_state = self._vehicle.state + result = self._attrs.copy() + if self.door_lock_state_available: + result["door_lock_state"] = vehicle_state.door_lock_state.value + result["last_update_reason"] = vehicle_state.last_update_reason + self._attr_extra_state_attributes = result diff --git a/homeassistant/components/bmw_connected_drive/sensor.py b/homeassistant/components/bmw_connected_drive/sensor.py index 8d44d1290dc..df899496339 100644 --- a/homeassistant/components/bmw_connected_drive/sensor.py +++ b/homeassistant/components/bmw_connected_drive/sensor.py @@ -503,94 +503,46 @@ class BMWConnectedDriveSensor(BMWConnectedDriveBaseEntity, SensorEntity): self._attribute = attribute self._service = service - self._state = None - if self._service: - self._name = ( - f"{self._vehicle.name} {self._service.lower()}_{self._attribute}" - ) - self._unique_id = ( - f"{self._vehicle.vin}-{self._service.lower()}-{self._attribute}" - ) + if service: + self._attr_name = f"{vehicle.name} {service.lower()}_{attribute}" + self._attr_unique_id = f"{vehicle.vin}-{service.lower()}-{attribute}" else: - self._name = f"{self._vehicle.name} {self._attribute}" - self._unique_id = f"{self._vehicle.vin}-{self._attribute}" + self._attr_name = f"{vehicle.name} {attribute}" + self._attr_unique_id = f"{vehicle.vin}-{attribute}" self._attribute_info = attribute_info - - @property - def unique_id(self): - """Return the unique ID of the sensor.""" - return self._unique_id - - @property - def name(self) -> str: - """Return the name of the sensor.""" - return self._name - - @property - def icon(self): - """Icon to use in the frontend, if any.""" - vehicle_state = self._vehicle.state - charging_state = vehicle_state.charging_status in [ChargingState.CHARGING] - - if self._attribute == "charging_level_hv": - return icon_for_battery_level( - battery_level=vehicle_state.charging_level_hv, charging=charging_state - ) - icon = self._attribute_info.get(self._attribute, [None, None, None, None])[0] - return icon - - @property - def entity_registry_enabled_default(self) -> bool: - """Return if the entity should be enabled when first added to the entity registry.""" - enabled_default = self._attribute_info.get( - self._attribute, [None, None, None, True] + self._attr_entity_registry_enabled_default = attribute_info.get( + attribute, [None, None, None, True] )[3] - return enabled_default - - @property - def state(self): - """Return the state of the sensor. - - The return type of this call depends on the attribute that - is configured. - """ - return self._state - - @property - def device_class(self) -> str: - """Get the device class.""" - clss = self._attribute_info.get(self._attribute, [None, None, None, None])[1] - return clss - - @property - def unit_of_measurement(self) -> str: - """Get the unit of measurement.""" - unit = self._attribute_info.get(self._attribute, [None, None, None, None])[2] - return unit + self._attr_device_class = attribute_info.get( + attribute, [None, None, None, None] + )[1] + self._attr_unit_of_measurement = attribute_info.get( + attribute, [None, None, None, None] + )[2] 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._state = getattr(vehicle_state, self._attribute).value + self._attr_state = getattr(vehicle_state, self._attribute).value elif self.unit_of_measurement == VOLUME_GALLONS: value = getattr(vehicle_state, self._attribute) value_converted = self.hass.config.units.volume(value, VOLUME_LITERS) - self._state = round(value_converted) + self._attr_state = round(value_converted) elif self.unit_of_measurement == LENGTH_MILES: value = getattr(vehicle_state, self._attribute) value_converted = self.hass.config.units.length(value, LENGTH_KILOMETERS) - self._state = round(value_converted) + self._attr_state = round(value_converted) elif self._service is None: - self._state = getattr(vehicle_state, self._attribute) + self._attr_state = getattr(vehicle_state, self._attribute) elif self._service == SERVICE_LAST_TRIP: vehicle_last_trip = self._vehicle.state.last_trip if self._attribute == "date_utc": date_str = getattr(vehicle_last_trip, "date") - self._state = dt_util.parse_datetime(date_str).isoformat() + self._attr_state = dt_util.parse_datetime(date_str).isoformat() else: - self._state = getattr(vehicle_last_trip, self._attribute) + self._attr_state = getattr(vehicle_last_trip, self._attribute) elif self._service == SERVICE_ALL_TRIPS: vehicle_all_trips = self._vehicle.state.all_trips for attribute in ( @@ -603,10 +555,21 @@ class BMWConnectedDriveSensor(BMWConnectedDriveBaseEntity, SensorEntity): if self._attribute.startswith(f"{attribute}_"): attr = getattr(vehicle_all_trips, attribute) sub_attr = self._attribute.replace(f"{attribute}_", "") - self._state = getattr(attr, sub_attr) + self._attr_state = getattr(attr, sub_attr) return if self._attribute == "reset_date_utc": date_str = getattr(vehicle_all_trips, "reset_date") - self._state = dt_util.parse_datetime(date_str).isoformat() + self._attr_state = dt_util.parse_datetime(date_str).isoformat() else: - self._state = getattr(vehicle_all_trips, self._attribute) + self._attr_state = getattr(vehicle_all_trips, self._attribute) + + vehicle_state = self._vehicle.state + charging_state = vehicle_state.charging_status in [ChargingState.CHARGING] + + if self._attribute == "charging_level_hv": + self._attr_icon = icon_for_battery_level( + battery_level=vehicle_state.charging_level_hv, charging=charging_state + ) + self._attr_icon = self._attribute_info.get( + self._attribute, [None, None, None, None] + )[0]