Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix scheduled update #274

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 101 additions & 31 deletions custom_components/hon/__init__.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -27,47 +30,114 @@
)


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:
# Initialize Hon instance in executor
def init_hon():
"""Initialize Hon instance."""
return 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, ""),
)

# Create Hon instance in executor
hon = await hass.async_add_executor_job(init_hon)
# Create and initialize
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 _handle_mqtt_update(_: Any) -> None:
"""Handle MQTT updates."""
try:
coordinator.async_set_updated_data({"last_update": hon.api.auth.refresh_token})
except Exception as exc:
_LOGGER.error("Error handling MQTT update: %s", exc)

def handle_update(msg: Any) -> None:
"""Handle updates from MQTT subscription in a thread-safe way."""
try:
hass.loop.call_soon_threadsafe(_handle_mqtt_update, msg)
except Exception as exc:
_LOGGER.error("Error scheduling MQTT update: %s", exc)

# Subscribe to MQTT updates with error handling
try:
hon.subscribe_updates(handle_update)
except Exception as exc:
_LOGGER.error("Error subscribing to MQTT updates: %s", exc)

# Initial data fetch
try:
await coordinator.async_config_entry_first_refresh()
except Exception as exc:
_LOGGER.error("Error during initial refresh: %s", exc)
raise

# 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."""
try:
hon = hass.data[DOMAIN][entry.unique_id]["hon"]

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
# 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 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
except Exception as exc:
_LOGGER.error("Error unloading entry: %s", exc)
return False
5 changes: 3 additions & 2 deletions custom_components/hon/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"domain": "hon",
"name": "Haier hOn",
"codeowners": [
"@Andre0512"
"@Andre0512",
"@galvani"
],
"config_flow": true,
"documentation": "https://github.com/Andre0512/hon/",
Expand All @@ -11,5 +12,5 @@
"requirements": [
"pyhOn==0.17.5"
],
"version": "0.14.0"
"version": "0.14.1"
}
5 changes: 4 additions & 1 deletion custom_components/hon/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")):
Expand All @@ -844,7 +845,8 @@ def _handle_coordinator_update(self, update: bool = True) -> None:
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()

Expand All @@ -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:
Expand Down