2024-01-31 09:37:55 +00:00
|
|
|
"""Data coordinators for the ring integration."""
|
2024-03-08 14:05:07 +00:00
|
|
|
|
2024-01-31 09:37:55 +00:00
|
|
|
from asyncio import TaskGroup
|
|
|
|
from collections.abc import Callable
|
|
|
|
import logging
|
2024-04-11 08:10:56 +00:00
|
|
|
from typing import TypeVar, TypeVarTuple
|
2024-01-31 09:37:55 +00:00
|
|
|
|
2024-04-11 08:10:56 +00:00
|
|
|
from ring_doorbell import AuthenticationError, Ring, RingDevices, RingError, RingTimeout
|
2024-01-31 09:37:55 +00:00
|
|
|
|
|
|
|
from homeassistant.core import HomeAssistant
|
|
|
|
from homeassistant.exceptions import ConfigEntryAuthFailed
|
|
|
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
|
|
|
|
|
|
|
from .const import NOTIFICATIONS_SCAN_INTERVAL, SCAN_INTERVAL
|
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
2024-04-11 08:10:56 +00:00
|
|
|
_R = TypeVar("_R")
|
|
|
|
_Ts = TypeVarTuple("_Ts")
|
|
|
|
|
2024-01-31 09:37:55 +00:00
|
|
|
|
|
|
|
async def _call_api(
|
2024-04-11 08:10:56 +00:00
|
|
|
hass: HomeAssistant, target: Callable[[*_Ts], _R], *args: *_Ts, msg_suffix: str = ""
|
|
|
|
) -> _R:
|
2024-01-31 09:37:55 +00:00
|
|
|
try:
|
|
|
|
return await hass.async_add_executor_job(target, *args)
|
2024-03-14 09:44:17 +00:00
|
|
|
except AuthenticationError as err:
|
2024-01-31 09:37:55 +00:00
|
|
|
# Raising ConfigEntryAuthFailed will cancel future updates
|
|
|
|
# and start a config flow with SOURCE_REAUTH (async_step_reauth)
|
|
|
|
raise ConfigEntryAuthFailed from err
|
2024-03-14 09:44:17 +00:00
|
|
|
except RingTimeout as err:
|
2024-01-31 09:37:55 +00:00
|
|
|
raise UpdateFailed(
|
|
|
|
f"Timeout communicating with API{msg_suffix}: {err}"
|
|
|
|
) from err
|
2024-03-14 09:44:17 +00:00
|
|
|
except RingError as err:
|
2024-01-31 09:37:55 +00:00
|
|
|
raise UpdateFailed(f"Error communicating with API{msg_suffix}: {err}") from err
|
|
|
|
|
|
|
|
|
2024-04-11 08:10:56 +00:00
|
|
|
class RingDataCoordinator(DataUpdateCoordinator[RingDevices]):
|
2024-01-31 09:37:55 +00:00
|
|
|
"""Base class for device coordinators."""
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
hass: HomeAssistant,
|
2024-03-14 09:44:17 +00:00
|
|
|
ring_api: Ring,
|
2024-01-31 09:37:55 +00:00
|
|
|
) -> None:
|
|
|
|
"""Initialize my coordinator."""
|
|
|
|
super().__init__(
|
|
|
|
hass,
|
|
|
|
name="devices",
|
|
|
|
logger=_LOGGER,
|
|
|
|
update_interval=SCAN_INTERVAL,
|
|
|
|
)
|
2024-03-14 09:44:17 +00:00
|
|
|
self.ring_api: Ring = ring_api
|
2024-01-31 09:37:55 +00:00
|
|
|
self.first_call: bool = True
|
|
|
|
|
2024-04-11 08:10:56 +00:00
|
|
|
async def _async_update_data(self) -> RingDevices:
|
2024-01-31 09:37:55 +00:00
|
|
|
"""Fetch data from API endpoint."""
|
|
|
|
update_method: str = "update_data" if self.first_call else "update_devices"
|
|
|
|
await _call_api(self.hass, getattr(self.ring_api, update_method))
|
|
|
|
self.first_call = False
|
2024-04-11 08:10:56 +00:00
|
|
|
devices: RingDevices = self.ring_api.devices()
|
2024-01-31 09:37:55 +00:00
|
|
|
subscribed_device_ids = set(self.async_contexts())
|
2024-04-11 08:10:56 +00:00
|
|
|
for device in devices.all_devices:
|
|
|
|
# Don't update all devices in the ring api, only those that set
|
|
|
|
# their device id as context when they subscribed.
|
|
|
|
if device.id in subscribed_device_ids:
|
|
|
|
try:
|
|
|
|
async with TaskGroup() as tg:
|
|
|
|
if device.has_capability("history"):
|
2024-01-31 09:37:55 +00:00
|
|
|
tg.create_task(
|
|
|
|
_call_api(
|
|
|
|
self.hass,
|
2024-04-11 08:10:56 +00:00
|
|
|
lambda device: device.history(limit=10),
|
|
|
|
device,
|
|
|
|
msg_suffix=f" for device {device.name}", # device_id is the mac
|
2024-01-31 09:37:55 +00:00
|
|
|
)
|
|
|
|
)
|
2024-04-11 08:10:56 +00:00
|
|
|
tg.create_task(
|
|
|
|
_call_api(
|
|
|
|
self.hass,
|
|
|
|
device.update_health_data,
|
|
|
|
msg_suffix=f" for device {device.name}",
|
|
|
|
)
|
|
|
|
)
|
|
|
|
except ExceptionGroup as eg:
|
|
|
|
raise eg.exceptions[0] # noqa: B904
|
2024-01-31 09:37:55 +00:00
|
|
|
|
2024-04-11 08:10:56 +00:00
|
|
|
return devices
|
2024-01-31 09:37:55 +00:00
|
|
|
|
|
|
|
|
|
|
|
class RingNotificationsCoordinator(DataUpdateCoordinator[None]):
|
|
|
|
"""Global notifications coordinator."""
|
|
|
|
|
2024-03-14 09:44:17 +00:00
|
|
|
def __init__(self, hass: HomeAssistant, ring_api: Ring) -> None:
|
2024-01-31 09:37:55 +00:00
|
|
|
"""Initialize my coordinator."""
|
|
|
|
super().__init__(
|
|
|
|
hass,
|
|
|
|
logger=_LOGGER,
|
|
|
|
name="active dings",
|
|
|
|
update_interval=NOTIFICATIONS_SCAN_INTERVAL,
|
|
|
|
)
|
2024-03-14 09:44:17 +00:00
|
|
|
self.ring_api: Ring = ring_api
|
2024-01-31 09:37:55 +00:00
|
|
|
|
2024-04-11 08:10:56 +00:00
|
|
|
async def _async_update_data(self) -> None:
|
2024-01-31 09:37:55 +00:00
|
|
|
"""Fetch data from API endpoint."""
|
|
|
|
await _call_api(self.hass, self.ring_api.update_dings)
|