diff --git a/homeassistant/components/renault/renault_coordinator.py b/homeassistant/components/renault/renault_coordinator.py index b47a8507030..64e414a9ab7 100644 --- a/homeassistant/components/renault/renault_coordinator.py +++ b/homeassistant/components/renault/renault_coordinator.py @@ -11,11 +11,12 @@ from renault_api.kamereon.exceptions import ( KamereonResponseException, NotSupportedException, ) +from renault_api.kamereon.models import KamereonVehicleDataAttributes from homeassistant.core import HomeAssistant from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -T = TypeVar("T") +T = TypeVar("T", bound=KamereonVehicleDataAttributes) class RenaultDataUpdateCoordinator(DataUpdateCoordinator[T]): diff --git a/homeassistant/components/renault/renault_entities.py b/homeassistant/components/renault/renault_entities.py index 003103d52d3..29d1aa4b860 100644 --- a/homeassistant/components/renault/renault_entities.py +++ b/homeassistant/components/renault/renault_entities.py @@ -3,14 +3,13 @@ from __future__ import annotations from collections.abc import Mapping from dataclasses import dataclass -from typing import Any, Optional, TypeVar, cast - -from renault_api.kamereon.models import KamereonVehicleDataAttributes +from typing import Any, Optional, cast from homeassistant.helpers.entity import Entity, EntityDescription from homeassistant.helpers.typing import StateType from homeassistant.helpers.update_coordinator import CoordinatorEntity +from .renault_coordinator import T from .renault_vehicle import RenaultVehicleProxy @@ -28,8 +27,6 @@ class RenaultEntityDescription(EntityDescription, RenaultRequiredKeysMixin): ATTR_LAST_UPDATE = "last_update" -T = TypeVar("T", bound=KamereonVehicleDataAttributes) - class RenaultDataEntity(CoordinatorEntity[Optional[T]], Entity): """Implementation of a Renault entity with a data coordinator.""" diff --git a/homeassistant/components/renault/renault_vehicle.py b/homeassistant/components/renault/renault_vehicle.py index 8d4cfea53ee..c955e5bfa65 100644 --- a/homeassistant/components/renault/renault_vehicle.py +++ b/homeassistant/components/renault/renault_vehicle.py @@ -2,9 +2,11 @@ from __future__ import annotations import asyncio +from collections.abc import Awaitable +from dataclasses import dataclass from datetime import timedelta import logging -from typing import cast +from typing import Callable, cast from renault_api.kamereon import models from renault_api.renault_vehicle import RenaultVehicle @@ -25,6 +27,20 @@ from .renault_coordinator import RenaultDataUpdateCoordinator LOGGER = logging.getLogger(__name__) +@dataclass +class RenaultCoordinatorDescription: + """Class describing Renault coordinators.""" + + endpoint: str + key: str + update_method: Callable[ + [RenaultVehicle], + Callable[[], Awaitable[models.KamereonVehicleDataAttributes]], + ] + # Optional keys + requires_electricity: bool = False + + class RenaultVehicleProxy: """Handle vehicle communication with Renault servers.""" @@ -61,48 +77,23 @@ class RenaultVehicleProxy: return self._device_info async def async_initialise(self) -> None: - """Load available sensors.""" - if await self.endpoint_available("cockpit"): - self.coordinators["cockpit"] = RenaultDataUpdateCoordinator( + """Load available coordinators.""" + self.coordinators = { + coord.key: RenaultDataUpdateCoordinator( self.hass, LOGGER, # Name of the data. For logging purposes. - name=f"{self.details.vin} cockpit", - update_method=self.get_cockpit, + name=f"{self.details.vin} {coord.key}", + update_method=coord.update_method(self._vehicle), # Polling interval. Will only be polled if there are subscribers. update_interval=self._scan_interval, ) - if await self.endpoint_available("hvac-status"): - self.coordinators["hvac_status"] = RenaultDataUpdateCoordinator( - self.hass, - LOGGER, - # Name of the data. For logging purposes. - name=f"{self.details.vin} hvac_status", - update_method=self.get_hvac_status, - # Polling interval. Will only be polled if there are subscribers. - update_interval=self._scan_interval, + for coord in COORDINATORS + if ( + self.details.supports_endpoint(coord.endpoint) + and (not coord.requires_electricity or self.details.uses_electricity()) ) - if self.details.uses_electricity(): - if await self.endpoint_available("battery-status"): - self.coordinators["battery"] = RenaultDataUpdateCoordinator( - self.hass, - LOGGER, - # Name of the data. For logging purposes. - name=f"{self.details.vin} battery", - update_method=self.get_battery_status, - # Polling interval. Will only be polled if there are subscribers. - update_interval=self._scan_interval, - ) - if await self.endpoint_available("charge-mode"): - self.coordinators["charge_mode"] = RenaultDataUpdateCoordinator( - self.hass, - LOGGER, - # Name of the data. For logging purposes. - name=f"{self.details.vin} charge_mode", - update_method=self.get_charge_mode, - # Polling interval. Will only be polled if there are subscribers. - update_interval=self._scan_interval, - ) + } # Check all coordinators await asyncio.gather( *( @@ -130,24 +121,28 @@ class RenaultVehicleProxy: ) del self.coordinators[key] - async def endpoint_available(self, endpoint: str) -> bool: - """Ensure the endpoint is available to avoid unnecessary queries.""" - return await self._vehicle.supports_endpoint( - endpoint - ) and await self._vehicle.has_contract_for_endpoint(endpoint) - async def get_battery_status(self) -> models.KamereonVehicleBatteryStatusData: - """Get battery status information from vehicle.""" - return await self._vehicle.get_battery_status() - - async def get_charge_mode(self) -> models.KamereonVehicleChargeModeData: - """Get charge mode information from vehicle.""" - return await self._vehicle.get_charge_mode() - - async def get_cockpit(self) -> models.KamereonVehicleCockpitData: - """Get cockpit information from vehicle.""" - return await self._vehicle.get_cockpit() - - async def get_hvac_status(self) -> models.KamereonVehicleHvacStatusData: - """Get hvac status information from vehicle.""" - return await self._vehicle.get_hvac_status() +COORDINATORS: tuple[RenaultCoordinatorDescription, ...] = ( + RenaultCoordinatorDescription( + endpoint="cockpit", + key="cockpit", + update_method=lambda x: x.get_cockpit, + ), + RenaultCoordinatorDescription( + endpoint="hvac-status", + key="hvac_status", + update_method=lambda x: x.get_hvac_status, + ), + RenaultCoordinatorDescription( + endpoint="battery-status", + key="battery", + requires_electricity=True, + update_method=lambda x: x.get_battery_status, + ), + RenaultCoordinatorDescription( + endpoint="charge-mode", + key="charge_mode", + requires_electricity=True, + update_method=lambda x: x.get_charge_mode, + ), +) diff --git a/homeassistant/components/renault/sensor.py b/homeassistant/components/renault/sensor.py index 537d708f391..62903702df0 100644 --- a/homeassistant/components/renault/sensor.py +++ b/homeassistant/components/renault/sensor.py @@ -42,7 +42,8 @@ from .const import ( DEVICE_CLASS_PLUG_STATE, DOMAIN, ) -from .renault_entities import RenaultDataEntity, RenaultEntityDescription, T +from .renault_coordinator import T +from .renault_entities import RenaultDataEntity, RenaultEntityDescription from .renault_hub import RenaultHub @@ -61,7 +62,7 @@ class RenaultSensorEntityDescription( """Class describing Renault sensor entities.""" icon_lambda: Callable[[RenaultSensor[T]], str] | None = None - requires_fuel: bool | None = None + requires_fuel: bool = False value_lambda: Callable[[RenaultSensor[T]], StateType] | None = None