From 1b5cf9eb96485ee2f3b459dc0946588be6aa9e72 Mon Sep 17 00:00:00 2001 From: Jan Kozak Date: Sun, 29 Dec 2024 10:33:59 +0100 Subject: [PATCH] fix scheduled update --- custom_components/hon/__init__.py | 105 ++++++++++++++++++++++-------- custom_components/hon/sensor.py | 5 +- 2 files changed, 81 insertions(+), 29 deletions(-) diff --git a/custom_components/hon/__init__.py b/custom_components/hon/__init__.py index 1e38a2d..a7c505e 100644 --- a/custom_components/hon/__init__.py +++ b/custom_components/hon/__init__.py @@ -1,12 +1,15 @@ import logging +from datetime import timedelta from pathlib import Path from typing import Any import voluptuous as vol # type: ignore[import-untyped] + +from homeassistant.core import HomeAssistant, callback from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_EMAIL, CONF_PASSWORD from homeassistant.helpers import config_validation as cv, aiohttp_client -from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.core import HomeAssistant from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from pyhon import Hon @@ -27,47 +30,93 @@ CONFIG_SCHEMA = vol.Schema( ) -async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up Hon from a config entry.""" session = aiohttp_client.async_get_clientsession(hass) - if (config_dir := hass.config.config_dir) is None: - raise ValueError("Missing Config Dir") - hon = await Hon( - email=entry.data[CONF_EMAIL], - password=entry.data[CONF_PASSWORD], - mobile_id=MOBILE_ID, - session=session, - test_data_path=Path(config_dir), - refresh_token=entry.data.get(CONF_REFRESH_TOKEN, ""), - ).create() + + try: + # Create Hon instance + hon = Hon( + email=entry.data[CONF_EMAIL], + password=entry.data[CONF_PASSWORD], + mobile_id=MOBILE_ID, + session=session, + test_data_path=Path(hass.config.config_dir), + refresh_token=entry.data.get(CONF_REFRESH_TOKEN, ""), + ) + + # Initialize Hon in executor + hon = await hon.create() + + except Exception as exc: + _LOGGER.error("Error creating Hon instance: %s", exc) + raise + + async def async_update_data() -> dict[str, Any]: + """Fetch data from API.""" + try: + for appliance in hon.appliances: + await appliance.update() + return {"last_update": hon.api.auth.refresh_token} + except Exception as exc: + _LOGGER.error("Error updating Hon data: %s", exc) + raise + + coordinator = DataUpdateCoordinator( + hass, + _LOGGER, + name=DOMAIN, + update_method=async_update_data, + update_interval=timedelta(seconds=60), + ) + + def _mqtt_update() -> None: + """Handle MQTT update in event loop.""" + coordinator.async_set_updated_data({"last_update": hon.api.auth.refresh_token}) + + def handle_update(_: Any) -> None: + """Handle updates from MQTT subscription in a thread-safe way.""" + hass.loop.call_soon_threadsafe(_mqtt_update) + + # Subscribe to MQTT updates + hon.subscribe_updates(handle_update) + + # Initial data fetch + await coordinator.async_config_entry_first_refresh() # Save the new refresh token hass.config_entries.async_update_entry( entry, data={**entry.data, CONF_REFRESH_TOKEN: hon.api.auth.refresh_token} ) - coordinator: DataUpdateCoordinator[dict[str, Any]] = DataUpdateCoordinator( - hass, _LOGGER, name=DOMAIN - ) - hon.subscribe_updates(coordinator.async_set_updated_data) - hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.unique_id] = {"hon": hon, "coordinator": coordinator} - for platform in PLATFORMS: - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, platform) - ) + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True -async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: - refresh_token = hass.data[DOMAIN][entry.unique_id]["hon"].api.auth.refresh_token +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + hon = hass.data[DOMAIN][entry.unique_id]["hon"] + # Store refresh token + refresh_token = hon.api.auth.refresh_token + + # Unsubscribe from updates + try: + hon.subscribe_updates(None) # Remove subscription + except Exception as exc: + _LOGGER.warning("Error unsubscribing from updates: %s", exc) + + # Update entry with latest refresh token hass.config_entries.async_update_entry( entry, data={**entry.data, CONF_REFRESH_TOKEN: refresh_token} ) - unload = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) - if unload: - if not hass.data[DOMAIN]: - hass.data.pop(DOMAIN, None) - return unload + + # Unload platforms + unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) + if unload_ok: + hass.data[DOMAIN].pop(entry.unique_id) + + return unload_ok \ No newline at end of file diff --git a/custom_components/hon/sensor.py b/custom_components/hon/sensor.py index 4039282..55f43eb 100644 --- a/custom_components/hon/sensor.py +++ b/custom_components/hon/sensor.py @@ -834,6 +834,7 @@ class HonSensorEntity(HonEntity, SensorEntity): @callback def _handle_coordinator_update(self, update: bool = True) -> None: + """Handle updated data from the coordinator.""" value = self._device.get(self.entity_description.key, "") if self.entity_description.key == "programName": if not (options := self._device.settings.get("startProgram.program")): @@ -844,7 +845,8 @@ class HonSensorEntity(HonEntity, SensorEntity): value = str(get_readable(self.entity_description, value)) if not value and self.entity_description.state_class is not None: self._attr_native_value = 0 - self._attr_native_value = value + else: + self._attr_native_value = value if update: self.async_write_ha_state() @@ -854,6 +856,7 @@ class HonConfigSensorEntity(HonEntity, SensorEntity): @callback def _handle_coordinator_update(self, update: bool = True) -> None: + """Handle updated data from the coordinator.""" sensor = self._device.settings.get(self.entity_description.key, None) value: float | str if self.entity_description.state_class is not None: