diff --git a/homeassistant/components/guardian/__init__.py b/homeassistant/components/guardian/__init__.py index da22066d7aa..58d70667cdf 100644 --- a/homeassistant/components/guardian/__init__.py +++ b/homeassistant/components/guardian/__init__.py @@ -137,6 +137,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # so we use a lock to ensure that only one API request is reaching it at a time: api_lock = asyncio.Lock() + async def async_init_coordinator( + coordinator: GuardianDataUpdateCoordinator, + ) -> None: + """Initialize a GuardianDataUpdateCoordinator.""" + await coordinator.async_initialize() + await coordinator.async_config_entry_first_refresh() + # Set up GuardianDataUpdateCoordinators for the valve controller: valve_controller_coordinators: dict[str, GuardianDataUpdateCoordinator] = {} init_valve_controller_tasks = [] @@ -151,13 +158,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: api ] = GuardianDataUpdateCoordinator( hass, + entry=entry, client=client, api_name=api, api_coro=api_coro, api_lock=api_lock, valve_controller_uid=entry.data[CONF_UID], ) - init_valve_controller_tasks.append(coordinator.async_refresh()) + init_valve_controller_tasks.append(async_init_coordinator(coordinator)) await asyncio.gather(*init_valve_controller_tasks) @@ -352,6 +360,7 @@ class PairedSensorManager: coordinator = self.coordinators[uid] = GuardianDataUpdateCoordinator( self._hass, + entry=self._entry, client=self._client, api_name=f"{API_SENSOR_PAIRED_SENSOR_STATUS}_{uid}", api_coro=lambda: cast( @@ -422,7 +431,7 @@ class GuardianEntity(CoordinatorEntity[GuardianDataUpdateCoordinator]): @callback def _async_update_from_latest_data(self) -> None: - """Update the entity. + """Update the entity's underlying data. This should be extended by Guardian platforms. """ diff --git a/homeassistant/components/guardian/binary_sensor.py b/homeassistant/components/guardian/binary_sensor.py index eb6d49c3ec1..766e5d961e8 100644 --- a/homeassistant/components/guardian/binary_sensor.py +++ b/homeassistant/components/guardian/binary_sensor.py @@ -137,7 +137,7 @@ class PairedSensorBinarySensor(PairedSensorEntity, BinarySensorEntity): @callback def _async_update_from_latest_data(self) -> None: - """Update the entity.""" + """Update the entity's underlying data.""" if self.entity_description.key == SENSOR_KIND_LEAK_DETECTED: self._attr_is_on = self.coordinator.data["wet"] elif self.entity_description.key == SENSOR_KIND_MOVED: diff --git a/homeassistant/components/guardian/button.py b/homeassistant/components/guardian/button.py index 01efb7deba4..740cce43c62 100644 --- a/homeassistant/components/guardian/button.py +++ b/homeassistant/components/guardian/button.py @@ -15,6 +15,7 @@ from homeassistant.components.button import ( from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -111,3 +112,5 @@ class GuardianButton(ValveControllerEntity, ButtonEntity): raise HomeAssistantError( f'Error while pressing button "{self.entity_id}": {err}' ) from err + + async_dispatcher_send(self.hass, self.coordinator.signal_reboot_requested) diff --git a/homeassistant/components/guardian/sensor.py b/homeassistant/components/guardian/sensor.py index 5b4c621cce6..05de437b10a 100644 --- a/homeassistant/components/guardian/sensor.py +++ b/homeassistant/components/guardian/sensor.py @@ -128,7 +128,7 @@ class PairedSensorSensor(PairedSensorEntity, SensorEntity): @callback def _async_update_from_latest_data(self) -> None: - """Update the entity.""" + """Update the entity's underlying data.""" if self.entity_description.key == SENSOR_KIND_BATTERY: self._attr_native_value = self.coordinator.data["battery"] elif self.entity_description.key == SENSOR_KIND_TEMPERATURE: @@ -142,7 +142,7 @@ class ValveControllerSensor(ValveControllerEntity, SensorEntity): @callback def _async_update_from_latest_data(self) -> None: - """Update the entity.""" + """Update the entity's underlying data.""" if self.entity_description.key == SENSOR_KIND_TEMPERATURE: self._attr_native_value = self.coordinator.data["temperature"] elif self.entity_description.key == SENSOR_KIND_UPTIME: diff --git a/homeassistant/components/guardian/util.py b/homeassistant/components/guardian/util.py index 2cedcf9c1e4..c88d6762e51 100644 --- a/homeassistant/components/guardian/util.py +++ b/homeassistant/components/guardian/util.py @@ -9,21 +9,28 @@ from typing import Any, cast from aioguardian import Client from aioguardian.errors import GuardianError -from homeassistant.core import HomeAssistant +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import LOGGER DEFAULT_UPDATE_INTERVAL = timedelta(seconds=30) +SIGNAL_REBOOT_REQUESTED = "guardian_reboot_requested_{0}" + class GuardianDataUpdateCoordinator(DataUpdateCoordinator[dict]): """Define an extended DataUpdateCoordinator with some Guardian goodies.""" + config_entry: ConfigEntry + def __init__( self, hass: HomeAssistant, *, + entry: ConfigEntry, client: Client, api_name: str, api_coro: Callable[..., Awaitable], @@ -41,6 +48,12 @@ class GuardianDataUpdateCoordinator(DataUpdateCoordinator[dict]): self._api_coro = api_coro self._api_lock = api_lock self._client = client + self._signal_handler_unsubs: list[Callable[..., None]] = [] + + self.config_entry = entry + self.signal_reboot_requested = SIGNAL_REBOOT_REQUESTED.format( + self.config_entry.entry_id + ) async def _async_update_data(self) -> dict[str, Any]: """Execute a "locked" API request against the valve controller.""" @@ -50,3 +63,26 @@ class GuardianDataUpdateCoordinator(DataUpdateCoordinator[dict]): except GuardianError as err: raise UpdateFailed(err) from err return cast(dict[str, Any], resp["data"]) + + async def async_initialize(self) -> None: + """Initialize the coordinator.""" + + @callback + def async_reboot_requested() -> None: + """Respond to a reboot request.""" + self.last_update_success = False + self.async_update_listeners() + + self._signal_handler_unsubs.append( + async_dispatcher_connect( + self.hass, self.signal_reboot_requested, async_reboot_requested + ) + ) + + @callback + def async_teardown() -> None: + """Tear the coordinator down appropriately.""" + for unsub in self._signal_handler_unsubs: + unsub() + + self.config_entry.async_on_unload(async_teardown)