2020-07-05 22:09:40 +00:00
|
|
|
"""Define Guardian-specific utilities."""
|
2021-04-20 15:40:41 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2020-07-05 22:09:40 +00:00
|
|
|
import asyncio
|
2022-09-17 21:01:57 +00:00
|
|
|
from collections.abc import Awaitable, Callable, Iterable
|
|
|
|
from dataclasses import dataclass
|
2020-07-05 22:09:40 +00:00
|
|
|
from datetime import timedelta
|
2022-01-11 20:23:26 +00:00
|
|
|
from typing import Any, cast
|
2020-07-05 22:09:40 +00:00
|
|
|
|
|
|
|
from aioguardian import Client
|
|
|
|
from aioguardian.errors import GuardianError
|
|
|
|
|
2022-07-31 20:10:29 +00:00
|
|
|
from homeassistant.config_entries import ConfigEntry
|
|
|
|
from homeassistant.core import HomeAssistant, callback
|
2022-09-17 21:01:57 +00:00
|
|
|
from homeassistant.helpers import entity_registry
|
2022-07-31 20:10:29 +00:00
|
|
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
2020-07-05 22:09:40 +00:00
|
|
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
|
|
|
|
2022-09-29 17:24:52 +00:00
|
|
|
from .const import LOGGER
|
2020-07-05 22:09:40 +00:00
|
|
|
|
|
|
|
DEFAULT_UPDATE_INTERVAL = timedelta(seconds=30)
|
|
|
|
|
2022-07-31 20:10:29 +00:00
|
|
|
SIGNAL_REBOOT_REQUESTED = "guardian_reboot_requested_{0}"
|
|
|
|
|
2020-07-05 22:09:40 +00:00
|
|
|
|
2022-09-17 21:01:57 +00:00
|
|
|
@dataclass
|
|
|
|
class EntityDomainReplacementStrategy:
|
|
|
|
"""Define an entity replacement."""
|
|
|
|
|
|
|
|
old_domain: str
|
|
|
|
old_unique_id: str
|
|
|
|
replacement_entity_id: str
|
|
|
|
breaks_in_ha_version: str
|
|
|
|
remove_old_entity: bool = True
|
|
|
|
|
|
|
|
|
|
|
|
@callback
|
|
|
|
def async_finish_entity_domain_replacements(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
entry: ConfigEntry,
|
|
|
|
entity_replacement_strategies: Iterable[EntityDomainReplacementStrategy],
|
|
|
|
) -> None:
|
|
|
|
"""Remove old entities and create a repairs issue with info on their replacement."""
|
|
|
|
ent_reg = entity_registry.async_get(hass)
|
|
|
|
for strategy in entity_replacement_strategies:
|
|
|
|
try:
|
|
|
|
[registry_entry] = [
|
|
|
|
registry_entry
|
|
|
|
for registry_entry in ent_reg.entities.values()
|
|
|
|
if registry_entry.config_entry_id == entry.entry_id
|
|
|
|
and registry_entry.domain == strategy.old_domain
|
|
|
|
and registry_entry.unique_id == strategy.old_unique_id
|
|
|
|
]
|
|
|
|
except ValueError:
|
|
|
|
continue
|
|
|
|
|
|
|
|
old_entity_id = registry_entry.entity_id
|
|
|
|
if strategy.remove_old_entity:
|
|
|
|
LOGGER.info('Removing old entity: "%s"', old_entity_id)
|
|
|
|
ent_reg.async_remove(old_entity_id)
|
|
|
|
|
|
|
|
|
2020-07-30 15:04:00 +00:00
|
|
|
class GuardianDataUpdateCoordinator(DataUpdateCoordinator[dict]):
|
2020-07-05 22:09:40 +00:00
|
|
|
"""Define an extended DataUpdateCoordinator with some Guardian goodies."""
|
|
|
|
|
2022-07-31 20:10:29 +00:00
|
|
|
config_entry: ConfigEntry
|
|
|
|
|
2020-07-05 22:09:40 +00:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
hass: HomeAssistant,
|
|
|
|
*,
|
2022-07-31 20:10:29 +00:00
|
|
|
entry: ConfigEntry,
|
2020-07-05 22:09:40 +00:00
|
|
|
client: Client,
|
|
|
|
api_name: str,
|
|
|
|
api_coro: Callable[..., Awaitable],
|
|
|
|
api_lock: asyncio.Lock,
|
|
|
|
valve_controller_uid: str,
|
2021-05-20 15:47:30 +00:00
|
|
|
) -> None:
|
2020-07-05 22:09:40 +00:00
|
|
|
"""Initialize."""
|
|
|
|
super().__init__(
|
|
|
|
hass,
|
|
|
|
LOGGER,
|
|
|
|
name=f"{valve_controller_uid}_{api_name}",
|
|
|
|
update_interval=DEFAULT_UPDATE_INTERVAL,
|
|
|
|
)
|
|
|
|
|
|
|
|
self._api_coro = api_coro
|
|
|
|
self._api_lock = api_lock
|
|
|
|
self._client = client
|
2022-07-31 20:10:29 +00:00
|
|
|
self._signal_handler_unsubs: list[Callable[..., None]] = []
|
|
|
|
|
|
|
|
self.config_entry = entry
|
|
|
|
self.signal_reboot_requested = SIGNAL_REBOOT_REQUESTED.format(
|
|
|
|
self.config_entry.entry_id
|
|
|
|
)
|
2020-07-05 22:09:40 +00:00
|
|
|
|
2021-07-22 06:01:05 +00:00
|
|
|
async def _async_update_data(self) -> dict[str, Any]:
|
2020-07-05 22:09:40 +00:00
|
|
|
"""Execute a "locked" API request against the valve controller."""
|
|
|
|
async with self._api_lock, self._client:
|
|
|
|
try:
|
|
|
|
resp = await self._api_coro()
|
|
|
|
except GuardianError as err:
|
2020-08-28 11:50:32 +00:00
|
|
|
raise UpdateFailed(err) from err
|
2022-01-11 20:23:26 +00:00
|
|
|
return cast(dict[str, Any], resp["data"])
|
2022-07-31 20:10:29 +00:00
|
|
|
|
|
|
|
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)
|