diff --git a/homeassistant/components/openevse/__init__.py b/homeassistant/components/openevse/__init__.py index b7dfe6bb086..b2847d8f785 100644 --- a/homeassistant/components/openevse/__init__.py +++ b/homeassistant/components/openevse/__init__.py @@ -4,31 +4,35 @@ from __future__ import annotations from openevsehttp.__main__ import OpenEVSE -from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, Platform from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryError +from homeassistant.exceptions import ConfigEntryNotReady -type OpenEVSEConfigEntry = ConfigEntry[OpenEVSE] +from .coordinator import OpenEVSEConfigEntry, OpenEVSEDataUpdateCoordinator async def async_setup_entry(hass: HomeAssistant, entry: OpenEVSEConfigEntry) -> bool: - """Set up openevse from a config entry.""" - - entry.runtime_data = OpenEVSE( + """Set up OpenEVSE from a config entry.""" + charger = OpenEVSE( entry.data[CONF_HOST], - entry.data.get(CONF_USERNAME, None), - entry.data.get(CONF_PASSWORD, None), + entry.data.get(CONF_USERNAME), + entry.data.get(CONF_PASSWORD), ) + try: - await entry.runtime_data.test_and_get() + await charger.test_and_get() except TimeoutError as ex: - raise ConfigEntryError("Unable to connect to charger") from ex + raise ConfigEntryNotReady("Unable to connect to charger") from ex + + coordinator = OpenEVSEDataUpdateCoordinator(hass, entry, charger) + await coordinator.async_config_entry_first_refresh() + + entry.runtime_data = coordinator await hass.config_entries.async_forward_entry_setups(entry, [Platform.SENSOR]) return True -async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: +async def async_unload_entry(hass: HomeAssistant, entry: OpenEVSEConfigEntry) -> bool: """Unload a config entry.""" return await hass.config_entries.async_unload_platforms(entry, [Platform.SENSOR]) diff --git a/homeassistant/components/openevse/coordinator.py b/homeassistant/components/openevse/coordinator.py new file mode 100644 index 00000000000..84bed399a65 --- /dev/null +++ b/homeassistant/components/openevse/coordinator.py @@ -0,0 +1,51 @@ +"""Data update coordinator for OpenEVSE.""" + +from __future__ import annotations + +from datetime import timedelta +import logging + +from openevsehttp.__main__ import OpenEVSE + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + +SCAN_INTERVAL = timedelta(seconds=30) + +type OpenEVSEConfigEntry = ConfigEntry[OpenEVSEDataUpdateCoordinator] + + +class OpenEVSEDataUpdateCoordinator(DataUpdateCoordinator[None]): + """Class to manage fetching OpenEVSE data.""" + + config_entry: OpenEVSEConfigEntry + + def __init__( + self, + hass: HomeAssistant, + config_entry: OpenEVSEConfigEntry, + charger: OpenEVSE, + ) -> None: + """Initialize coordinator.""" + self.charger = charger + super().__init__( + hass, + _LOGGER, + config_entry=config_entry, + name=DOMAIN, + update_interval=SCAN_INTERVAL, + ) + + async def _async_update_data(self) -> None: + """Fetch data from OpenEVSE charger.""" + try: + await self.charger.update() + except TimeoutError as error: + raise UpdateFailed( + f"Timeout communicating with charger: {error}" + ) from error diff --git a/homeassistant/components/openevse/sensor.py b/homeassistant/components/openevse/sensor.py index c48ada416fa..85b376c4aec 100644 --- a/homeassistant/components/openevse/sensor.py +++ b/homeassistant/components/openevse/sensor.py @@ -35,13 +35,16 @@ from homeassistant.helpers.entity_platform import ( AddConfigEntryEntitiesCallback, AddEntitiesCallback, ) -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType +from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import ConfigEntry from .const import DOMAIN, INTEGRATION_TITLE +from .coordinator import OpenEVSEConfigEntry, OpenEVSEDataUpdateCoordinator _LOGGER = logging.getLogger(__name__) +PARALLEL_UPDATES = 0 + @dataclass(frozen=True, kw_only=True) class OpenEVSESensorDescription(SensorEntityDescription): @@ -174,25 +177,19 @@ async def async_setup_platform( async def async_setup_entry( hass: HomeAssistant, - config_entry: ConfigEntry, + entry: OpenEVSEConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: - """Add sensors for passed config_entry in HA.""" + """Set up OpenEVSE sensors based on config entry.""" + coordinator = entry.runtime_data + identifier = entry.unique_id or entry.entry_id async_add_entities( - ( - OpenEVSESensor( - config_entry.runtime_data, - description, - config_entry.entry_id, - config_entry.unique_id, - ) - for description in SENSOR_TYPES - ), - True, + OpenEVSESensor(coordinator, description, identifier, entry.unique_id) + for description in SENSOR_TYPES ) -class OpenEVSESensor(SensorEntity): +class OpenEVSESensor(CoordinatorEntity[OpenEVSEDataUpdateCoordinator], SensorEntity): """Implementation of an OpenEVSE sensor.""" _attr_has_entity_name = True @@ -200,16 +197,14 @@ class OpenEVSESensor(SensorEntity): def __init__( self, - charger: OpenEVSE, + coordinator: OpenEVSEDataUpdateCoordinator, description: OpenEVSESensorDescription, - entry_id: str, + identifier: str, unique_id: str | None, ) -> None: """Initialize the sensor.""" + super().__init__(coordinator) self.entity_description = description - self.charger = charger - - identifier = unique_id or entry_id self._attr_unique_id = f"{identifier}-{description.key}" self._attr_device_info = DeviceInfo( @@ -222,12 +217,7 @@ class OpenEVSESensor(SensorEntity): } self._attr_device_info[ATTR_SERIAL_NUMBER] = unique_id - async def async_update(self) -> None: - """Get the monitored data from the charger.""" - try: - await self.charger.update() - except TimeoutError: - _LOGGER.warning("Could not update status for %s", self.name) - return - - self._attr_native_value = self.entity_description.value_fn(self.charger) + @property + def native_value(self) -> StateType: + """Return the state of the sensor.""" + return self.entity_description.value_fn(self.coordinator.charger)