Skip to content
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: 2 additions & 3 deletions custom_components/hon/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@
BinarySensorEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import callback
from homeassistant.core import callback, HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import HomeAssistantType

from .const import DOMAIN
from .entity import HonEntity
Expand Down Expand Up @@ -317,7 +316,7 @@ class HonBinarySensorEntityDescription(BinarySensorEntityDescription):


async def async_setup_entry(
hass: HomeAssistantType, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
entities = []
for device in hass.data[DOMAIN][entry.unique_id]["hon"].appliances:
Expand Down
8 changes: 4 additions & 4 deletions custom_components/hon/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import HomeAssistantType
from homeassistant.core import HomeAssistant
from pyhon.appliance import HonAppliance

from .const import DOMAIN
Expand Down Expand Up @@ -56,7 +56,7 @@


async def async_setup_entry(
hass: HomeAssistantType, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
entities: list[HonButtonType] = []
for device in hass.data[DOMAIN][entry.unique_id]["hon"].appliances:
Expand Down Expand Up @@ -88,7 +88,7 @@ def available(self) -> bool:

class HonDeviceInfo(HonEntity, ButtonEntity):
def __init__(
self, hass: HomeAssistantType, entry: ConfigEntry, device: HonAppliance
self, hass: HomeAssistant, entry: ConfigEntry, device: HonAppliance
) -> None:
super().__init__(hass, entry, device)

Expand All @@ -108,7 +108,7 @@ async def async_press(self) -> None:

class HonDataArchive(HonEntity, ButtonEntity):
def __init__(
self, hass: HomeAssistantType, entry: ConfigEntry, device: HonAppliance
self, hass: HomeAssistant, entry: ConfigEntry, device: HonAppliance
) -> None:
super().__init__(hass, entry, device)

Expand Down
17 changes: 10 additions & 7 deletions custom_components/hon/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,8 @@
ATTR_TEMPERATURE,
UnitOfTemperature,
)
from homeassistant.core import callback
from homeassistant.core import callback, HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import HomeAssistantType
from pyhon.appliance import HonAppliance
from pyhon.parameter.range import HonParameterRange

Expand Down Expand Up @@ -104,7 +103,7 @@ class HonClimateEntityDescription(ClimateEntityDescription):


async def async_setup_entry(
hass: HomeAssistantType, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
entities = []
entity: HonClimateEntity | HonACClimateEntity
Expand All @@ -130,7 +129,7 @@ class HonACClimateEntity(HonEntity, ClimateEntity):

def __init__(
self,
hass: HomeAssistantType,
hass: HomeAssistant,
entry: ConfigEntry,
device: HonAppliance,
description: HonACClimateEntityDescription,
Expand Down Expand Up @@ -199,7 +198,7 @@ async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
self._attr_hvac_mode = hvac_mode
if hvac_mode == HVACMode.OFF:
await self._device.commands["stopProgram"].send()
self._device.sync_command("stopProgram", "settings")
self._device.settings["settings.onOffStatus"].value = "2"
else:
self._device.settings["settings.onOffStatus"].value = "1"
setting = self._device.settings["settings.machMode"]
Expand All @@ -217,6 +216,10 @@ async def async_turn_on(self, **kwargs: Any) -> None:
self._device.sync_command("startProgram", "settings")

async def async_turn_off(self, **kwargs: Any) -> None:
fix_param = self._device.commands["stopProgram"].parameters.get("windDirectionVerticalPositionSequence")
if fix_param and fix_param.value == "0":
fix_param.value = "2"
_LOGGER.warning("🔧 Patched 'windDirectionVerticalPositionSequence' from '0' to '2'")
await self._device.commands["stopProgram"].send()
self._device.sync_command("stopProgram", "settings")

Expand Down Expand Up @@ -282,7 +285,7 @@ async def async_set_swing_mode(self, swing_mode: str) -> None:
if swing_mode in [SWING_OFF, SWING_HORIZONTAL] and vertical.value == "8":
vertical.value = "5"
if swing_mode in [SWING_OFF, SWING_VERTICAL] and horizontal.value == "7":
horizontal.value = "0"
horizontal.value = "2"
self._attr_swing_mode = swing_mode
await self._device.commands["settings"].send()
self.async_write_ha_state()
Expand All @@ -299,7 +302,7 @@ class HonClimateEntity(HonEntity, ClimateEntity):

def __init__(
self,
hass: HomeAssistantType,
hass: HomeAssistant,
entry: ConfigEntry,
device: HonAppliance,
description: HonClimateEntityDescription,
Expand Down
5 changes: 2 additions & 3 deletions custom_components/hon/entity.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
from typing import Optional, Any

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import callback
from homeassistant.core import callback, HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.typing import HomeAssistantType
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
)
Expand All @@ -20,7 +19,7 @@ class HonEntity(CoordinatorEntity[DataUpdateCoordinator[dict[str, Any]]]):

def __init__(
self,
hass: HomeAssistantType,
hass: HomeAssistant,
entry: ConfigEntry,
device: HonAppliance,
description: Optional[HonEntityDescription] = None,
Expand Down
7 changes: 3 additions & 4 deletions custom_components/hon/fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@
FanEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import callback
from homeassistant.core import callback, HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import HomeAssistantType
from homeassistant.util.percentage import (
percentage_to_ranged_value,
ranged_value_to_percentage,
Expand All @@ -36,7 +35,7 @@


async def async_setup_entry(
hass: HomeAssistantType, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
entities = []
for device in hass.data[DOMAIN][entry.unique_id]["hon"].appliances:
Expand All @@ -56,7 +55,7 @@ class HonFanEntity(HonEntity, FanEntity):

def __init__(
self,
hass: HomeAssistantType,
hass: HomeAssistant,
entry: ConfigEntry,
device: HonAppliance,
description: FanEntityDescription,
Expand Down
7 changes: 3 additions & 4 deletions custom_components/hon/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@
ATTR_BRIGHTNESS,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import callback
from homeassistant.core import callback, HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import HomeAssistantType
from pyhon.appliance import HonAppliance
from pyhon.parameter.range import HonParameterRange

Expand Down Expand Up @@ -53,7 +52,7 @@


async def async_setup_entry(
hass: HomeAssistantType, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
entities = []
for device in hass.data[DOMAIN][entry.unique_id]["hon"].appliances:
Expand All @@ -73,7 +72,7 @@ class HonLightEntity(HonEntity, LightEntity):

def __init__(
self,
hass: HomeAssistantType,
hass: HomeAssistant,
entry: ConfigEntry,
device: HonAppliance,
description: LightEntityDescription,
Expand Down
5 changes: 2 additions & 3 deletions custom_components/hon/lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@

from homeassistant.components.lock import LockEntity, LockEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import callback
from homeassistant.core import callback, HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import HomeAssistantType
from pyhon.parameter.base import HonParameter
from pyhon.parameter.range import HonParameterRange

Expand All @@ -26,7 +25,7 @@


async def async_setup_entry(
hass: HomeAssistantType, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
entities = []
for device in hass.data[DOMAIN][entry.unique_id]["hon"].appliances:
Expand Down
Loading