hass-core/homeassistant/components/lamarzocco/coordinator.py
Josef Zweck 42b984ee4f
Migrate lamarzocco to lmcloud 1.1 (#113935)
* migrate to 1.1

* bump to 1.1.1

* fix newlines docstring

* cleanup entity_description fns

* strict generics

* restructure import

* tweaks to generics

* tweaks to generics

* removed exceptions

* move initialization, websocket clean shutdown

* get rid of duplicate entry addign

* bump lmcloud

* re-add calendar, auto on/off switches

* use asdict for diagnostics

* change number generator

* use name as entry title

* also migrate title

* don't migrate title

* remove generics for now

* satisfy mypy

* add s

* adapt

* migrate entry.runtime_data

* remove auto/onoff

* add issue on wrong gw firmware

* silence mypy

* remove breaks in ha version

* parametrize issue test

* Update update.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Update test_config_flow.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* regen snapshots

* mapping steam level

* remove commented code

* fix typo

* coderabbitai availability tweak

* remove microsecond moving

* additonal schedule for coverage

* be more specific on date offset

* keep mappings the same

* config_entry imports sharpened

* remove unneccessary testcase, clenup date moving

* remove superfluous calendar testcase from diag

* guard against future version downgrade

* use new entry for downgrade test

* switch to lmcloud 1.1.11

* revert runtimedata

* revert runtimedata

* version to helper

* conistent Generator

* generator from typing_extensions

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2024-06-10 19:59:39 +02:00

123 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