core/homeassistant/components/schlage/coordinator.py

76 lines
2.5 KiB
Python

"""DataUpdateCoordinator for the Schlage integration."""
from __future__ import annotations
import asyncio
from dataclasses import dataclass
from pyschlage import Lock, Schlage
from pyschlage.exceptions import Error as SchlageError, NotAuthorizedError
from pyschlage.log import LockLog
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DOMAIN, LOGGER, UPDATE_INTERVAL
@dataclass
class LockData:
"""Container for cached lock data from the Schlage API."""
lock: Lock
logs: list[LockLog]
@dataclass
class SchlageData:
"""Container for cached data from the Schlage API."""
locks: dict[str, LockData]
class SchlageDataUpdateCoordinator(DataUpdateCoordinator[SchlageData]):
"""The Schlage data update coordinator."""
def __init__(self, hass: HomeAssistant, username: str, api: Schlage) -> None:
"""Initialize the class."""
super().__init__(
hass, LOGGER, name=f"{DOMAIN} ({username})", update_interval=UPDATE_INTERVAL
)
self.api = api
async def _async_update_data(self) -> SchlageData:
"""Fetch the latest data from the Schlage API."""
try:
locks = await self.hass.async_add_executor_job(self.api.locks)
except NotAuthorizedError as ex:
raise ConfigEntryAuthFailed from ex
except SchlageError as ex:
raise UpdateFailed("Failed to refresh Schlage data") from ex
lock_data = await asyncio.gather(
*(
self.hass.async_add_executor_job(self._get_lock_data, lock)
for lock in locks
)
)
return SchlageData(
locks={ld.lock.device_id: ld for ld in lock_data},
)
def _get_lock_data(self, lock: Lock) -> LockData:
logs: list[LockLog] = []
previous_lock_data = None
if self.data and (previous_lock_data := self.data.locks.get(lock.device_id)):
# Default to the previous data, in case a refresh fails.
# It's not critical if we don't have the freshest data.
logs = previous_lock_data.logs
try:
logs = lock.logs()
except NotAuthorizedError as ex:
raise ConfigEntryAuthFailed from ex
except SchlageError as ex:
LOGGER.debug('Failed to read logs for lock "%s": %s', lock.name, ex)
return LockData(lock=lock, logs=logs)