diff --git a/homeassistant/components/subaru/button.py b/homeassistant/components/subaru/button.py index 24ea65eb465d..2e77211698ce 100644 --- a/homeassistant/components/subaru/button.py +++ b/homeassistant/components/subaru/button.py @@ -10,15 +10,14 @@ from homeassistant.components.button import ButtonEntity, ButtonEntityDescriptio from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback -from . import get_device_info from .const import ( SERVICE_REMOTE_START, SERVICE_REMOTE_STOP, VEHICLE_HAS_EV, VEHICLE_HAS_REMOTE_START, - VEHICLE_VIN, ) from .coordinator import SubaruConfigEntry, SubaruDataUpdateCoordinator +from .entity import SubaruEntity from .remote_service import async_call_remote_service @@ -59,10 +58,9 @@ async def async_setup_entry( ) -class SubaruButton(ButtonEntity): +class SubaruButton(SubaruEntity, ButtonEntity): """Class for a Subaru button.""" - _attr_has_entity_name = True entity_description: SubaruButtonEntityDescription def __init__( @@ -73,13 +71,10 @@ class SubaruButton(ButtonEntity): description: SubaruButtonEntityDescription, ) -> None: """Initialize the button for the vehicle.""" + super().__init__(vehicle_info, description.key) self.controller = controller self.coordinator = coordinator - self.vehicle_info = vehicle_info self.entity_description = description - vin = vehicle_info[VEHICLE_VIN] - self._attr_unique_id = f"{vin}_{description.key}" - self._attr_device_info = get_device_info(vehicle_info) @override async def async_press(self) -> None: diff --git a/homeassistant/components/subaru/device_tracker.py b/homeassistant/components/subaru/device_tracker.py index 9ea7929b5dcf..f31ac633893b 100644 --- a/homeassistant/components/subaru/device_tracker.py +++ b/homeassistant/components/subaru/device_tracker.py @@ -7,11 +7,10 @@ from subarulink.const import LATITUDE, LONGITUDE, TIMESTAMP from homeassistant.components.device_tracker import TrackerEntity from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback -from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import get_device_info -from .const import VEHICLE_HAS_REMOTE_SERVICE, VEHICLE_STATUS, VEHICLE_VIN +from .const import VEHICLE_HAS_REMOTE_SERVICE, VEHICLE_STATUS from .coordinator import SubaruConfigEntry, SubaruDataUpdateCoordinator +from .entity import SubaruCoordinatorEntity async def async_setup_entry( @@ -29,23 +28,17 @@ async def async_setup_entry( ) -class SubaruDeviceTracker( - CoordinatorEntity[SubaruDataUpdateCoordinator], TrackerEntity -): +class SubaruDeviceTracker(SubaruCoordinatorEntity, TrackerEntity): """Class for Subaru device tracker.""" _attr_translation_key = "location" - _attr_has_entity_name = True _attr_name = None def __init__( self, vehicle_info: dict, coordinator: SubaruDataUpdateCoordinator ) -> None: """Initialize the device tracker.""" - super().__init__(coordinator) - self.vin = vehicle_info[VEHICLE_VIN] - self._attr_device_info = get_device_info(vehicle_info) - self._attr_unique_id = f"{self.vin}_location" + super().__init__(vehicle_info, coordinator, "location") @property @override @@ -72,8 +65,8 @@ class SubaruDeviceTracker( @property @override def available(self) -> bool: - """Return if entity is available.""" - if vehicle_data := self.coordinator.data.get(self.vin): - if status := vehicle_data.get(VEHICLE_STATUS): - return status.keys() & {LATITUDE, LONGITUDE, TIMESTAMP} - return False + """Return if available; not gated on last_update_success, only on the relevant status keys being present.""" + if not (vehicle_data := (self.coordinator.data or {}).get(self.vin)): + return False + status = vehicle_data.get(VEHICLE_STATUS) or {} + return bool(status.keys() & {LATITUDE, LONGITUDE, TIMESTAMP}) diff --git a/homeassistant/components/subaru/entity.py b/homeassistant/components/subaru/entity.py new file mode 100644 index 000000000000..a9e4ff154615 --- /dev/null +++ b/homeassistant/components/subaru/entity.py @@ -0,0 +1,45 @@ +"""Base entities for the Subaru integration.""" + +from typing import Any, override + +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from . import get_device_info +from .const import VEHICLE_VIN +from .coordinator import SubaruDataUpdateCoordinator + + +class SubaruEntity(Entity): + """Base class for Subaru entities: device_info, unique_id, has_entity_name.""" + + _attr_has_entity_name = True + + def __init__(self, vehicle_info: dict[str, Any], unique_id_suffix: str) -> None: + """Initialize the entity from the vehicle_info dict.""" + self.vehicle_info = vehicle_info + self.vin: str = vehicle_info[VEHICLE_VIN] + self._attr_device_info = get_device_info(vehicle_info) + self._attr_unique_id = f"{self.vin}_{unique_id_suffix}" + + +class SubaruCoordinatorEntity( + CoordinatorEntity[SubaruDataUpdateCoordinator], SubaruEntity +): + """Base class for coordinator-backed Subaru entities.""" + + def __init__( + self, + vehicle_info: dict[str, Any], + coordinator: SubaruDataUpdateCoordinator, + unique_id_suffix: str, + ) -> None: + """Initialize the coordinator-backed entity.""" + super().__init__(coordinator) + SubaruEntity.__init__(self, vehicle_info, unique_id_suffix) + + @property + @override + def available(self) -> bool: + """Return if available; also gates on data for this vehicle being present.""" + return super().available and self.vin in self.coordinator.data diff --git a/homeassistant/components/subaru/lock.py b/homeassistant/components/subaru/lock.py index 62547ee51e5b..362e3ebe4c3d 100644 --- a/homeassistant/components/subaru/lock.py +++ b/homeassistant/components/subaru/lock.py @@ -11,7 +11,6 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_platform from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback -from . import get_device_info from .const import ( ATTR_DOOR, SERVICE_UNLOCK_SPECIFIC_DOOR, @@ -19,9 +18,9 @@ from .const import ( UNLOCK_VALID_DOORS, VEHICLE_HAS_REMOTE_SERVICE, VEHICLE_NAME, - VEHICLE_VIN, ) from .coordinator import SubaruConfigEntry +from .entity import SubaruEntity from .remote_service import async_call_remote_service _LOGGER = logging.getLogger(__name__) @@ -50,7 +49,7 @@ async def async_setup_entry( ) -class SubaruLock(LockEntity): +class SubaruLock(SubaruEntity, LockEntity): """Representation of a Subaru door lock. Note that the Subaru API currently does not support @@ -58,17 +57,13 @@ class SubaruLock(LockEntity): always unknown. """ - _attr_has_entity_name = True _attr_translation_key = "door_locks" def __init__(self, vehicle_info, controller): """Initialize the locks for the vehicle.""" + super().__init__(vehicle_info, "door_locks") self.controller = controller - self.vehicle_info = vehicle_info - vin = vehicle_info[VEHICLE_VIN] self.car_name = vehicle_info[VEHICLE_NAME] - self._attr_unique_id = f"{vin}_door_locks" - self._attr_device_info = get_device_info(vehicle_info) @override async def async_lock(self, **kwargs: Any) -> None: diff --git a/homeassistant/components/subaru/sensor.py b/homeassistant/components/subaru/sensor.py index 1a49fbba510d..4fff8efb10ba 100644 --- a/homeassistant/components/subaru/sensor.py +++ b/homeassistant/components/subaru/sensor.py @@ -27,11 +27,9 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.typing import StateType -from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.util.unit_conversion import DistanceConverter, VolumeConverter from homeassistant.util.unit_system import METRIC_SYSTEM -from . import get_device_info from .const import ( API_GEN_2, API_GEN_3, @@ -42,9 +40,9 @@ from .const import ( VEHICLE_HAS_EV, VEHICLE_HEALTH, VEHICLE_STATUS, - VEHICLE_VIN, ) from .coordinator import SubaruConfigEntry, SubaruDataUpdateCoordinator +from .entity import SubaruCoordinatorEntity _LOGGER = logging.getLogger(__name__) @@ -260,10 +258,9 @@ def create_vehicle_sensors( ] -class SubaruSensor(CoordinatorEntity[SubaruDataUpdateCoordinator], SensorEntity): +class SubaruSensor(SubaruCoordinatorEntity, SensorEntity): """Class for Subaru sensors.""" - _attr_has_entity_name = True entity_description: SubaruSensorEntityDescription def __init__( @@ -273,11 +270,8 @@ class SubaruSensor(CoordinatorEntity[SubaruDataUpdateCoordinator], SensorEntity) description: SubaruSensorEntityDescription, ) -> None: """Initialize the sensor.""" - super().__init__(coordinator) - self.vin = vehicle_info[VEHICLE_VIN] + super().__init__(vehicle_info, coordinator, description.key) self.entity_description = description - self._attr_device_info = get_device_info(vehicle_info) - self._attr_unique_id = f"{self.vin}_{description.key}" @property @override @@ -312,15 +306,6 @@ class SubaruSensor(CoordinatorEntity[SubaruDataUpdateCoordinator], SensorEntity) return FUEL_CONSUMPTION_LITERS_PER_HUNDRED_KILOMETERS return self.entity_description.native_unit_of_measurement - @property - @override - def available(self) -> bool: - """Return if entity is available.""" - last_update_success = super().available - if last_update_success and self.vin not in self.coordinator.data: - return False - return last_update_success - async def _async_migrate_entries( hass: HomeAssistant, config_entry: ConfigEntry