core/homeassistant/components/lamarzocco/coordinator.py

124 lines
4.5 KiB
Python

"""Coordinator for La Marzocco API."""
from collections.abc import Callable, Coroutine
from datetime import timedelta
import logging
from time import time
from typing import Any
from lmcloud.client_bluetooth import LaMarzoccoBluetoothClient
from lmcloud.client_cloud import LaMarzoccoCloudClient
from lmcloud.client_local import LaMarzoccoLocalClient
from lmcloud.exceptions import AuthFail, RequestNotSuccessful
from lmcloud.lm_machine import LaMarzoccoMachine
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_MODEL, CONF_NAME, EVENT_HOMEASSISTANT_STOP
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DOMAIN
SCAN_INTERVAL = timedelta(seconds=30)
FIRMWARE_UPDATE_INTERVAL = 3600
STATISTICS_UPDATE_INTERVAL = 300
_LOGGER = logging.getLogger(__name__)
class LaMarzoccoUpdateCoordinator(DataUpdateCoordinator[None]):
"""Class to handle fetching data from the La Marzocco API centrally."""
config_entry: ConfigEntry
def __init__(
self,
hass: HomeAssistant,
cloud_client: LaMarzoccoCloudClient,
local_client: LaMarzoccoLocalClient | None,
bluetooth_client: LaMarzoccoBluetoothClient | None,
) -> None:
"""Initialize coordinator."""
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=SCAN_INTERVAL)
self.local_connection_configured = local_client is not None
assert self.config_entry.unique_id
self.device = LaMarzoccoMachine(
model=self.config_entry.data[CONF_MODEL],
serial_number=self.config_entry.unique_id,
name=self.config_entry.data[CONF_NAME],
cloud_client=cloud_client,
local_client=local_client,
bluetooth_client=bluetooth_client,
)
self._last_firmware_data_update: float | None = None
self._last_statistics_data_update: float | None = None
self._local_client = local_client
async def async_setup(self) -> None:
"""Set up the coordinator."""
if self._local_client is not None:
_LOGGER.debug("Init WebSocket in background task")
self.config_entry.async_create_background_task(
hass=self.hass,
target=self.device.websocket_connect(
notify_callback=lambda: self.async_set_updated_data(None)
),
name="lm_websocket_task",
)
async def websocket_close(_: Any | None = None) -> None:
if (
self._local_client is not None
and self._local_client.websocket is not None
and self._local_client.websocket.open
):
self._local_client.terminating = True
await self._local_client.websocket.close()
self.config_entry.async_on_unload(
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP, websocket_close
)
)
self.config_entry.async_on_unload(websocket_close)
async def _async_update_data(self) -> None:
"""Fetch data from API endpoint."""
await self._async_handle_request(self.device.get_config)
if (
self._last_firmware_data_update is None
or (self._last_firmware_data_update + FIRMWARE_UPDATE_INTERVAL) < time()
):
await self._async_handle_request(self.device.get_firmware)
self._last_firmware_data_update = time()
if (
self._last_statistics_data_update is None
or (self._last_statistics_data_update + STATISTICS_UPDATE_INTERVAL) < time()
):
await self._async_handle_request(self.device.get_statistics)
self._last_statistics_data_update = time()
_LOGGER.debug("Current status: %s", str(self.device.config))
async def _async_handle_request[**_P](
self,
func: Callable[_P, Coroutine[None, None, None]],
*args: _P.args,
**kwargs: _P.kwargs,
) -> None:
try:
await func()
except AuthFail as ex:
msg = "Authentication failed."
_LOGGER.debug(msg, exc_info=True)
raise ConfigEntryAuthFailed(msg) from ex
except RequestNotSuccessful as ex:
_LOGGER.debug(ex, exc_info=True)
raise UpdateFailed(f"Querying API failed. Error: {ex}") from ex