hOn/custom_components/hon/lock.py
Vadym Melnychuk 047800ccbf fix(thread-safety): use schedule_update_ha_state in async entity methods
Replace self.async_write_ha_state() with self.schedule_update_ha_state()
in async command handlers across climate, fan, light, lock and switch
entities. HA Core 2026.5.x enforces that async_write_ha_state() is only
called from the event loop, but pyhon-revived delivers MQTT push updates
from another thread, causing 'Detected blocking call'/thread-safety
errors on 2026.5.4.

See https://developers.home-assistant.io/docs/asyncio_thread_safety/#async_write_ha_state
Fixes mmalolepszy/hon-revived#44

Cherry-picked from hon-revived PR #43 (head VadymMelnychuk:async-write-ha-state-fix).
(cherry picked from commit e99a734c2dd07ca216f452a006b1605a1529a710)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 10:49:47 +03:00

87 lines
3 KiB
Python

import logging
from typing import Any
from homeassistant.components.lock import LockEntity, LockEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.core import HomeAssistant
from pyhon.parameter.base import HonParameter
from pyhon.parameter.range import HonParameterRange
from .const import DOMAIN
from .entity import HonEntity
_LOGGER = logging.getLogger(__name__)
LOCKS: dict[str, tuple[LockEntityDescription, ...]] = {
"AP": (
LockEntityDescription(
key="lockStatus",
name="Lock Status",
translation_key="mode",
),
),
}
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
entities = []
for device in hass.data[DOMAIN][entry.unique_id]["hon"].appliances:
for description in LOCKS.get(device.appliance_type, []):
if (
f"settings.{description.key}" not in device.available_settings
or device.get(description.key) is None
):
continue
entity = HonLockEntity(hass, entry, device, description)
entities.append(entity)
async_add_entities(entities)
class HonLockEntity(HonEntity, LockEntity):
entity_description: LockEntityDescription
@property
def is_locked(self) -> bool | None:
"""Return a boolean for the state of the lock."""
return bool(self._device.get(self.entity_description.key, 0) == 1)
async def async_lock(self, **kwargs: Any) -> None:
"""Lock method."""
setting = self._device.settings.get(f"settings.{self.entity_description.key}")
if type(setting) == HonParameter or setting is None:
return
setting.value = setting.max if isinstance(setting, HonParameterRange) else 1
self.schedule_update_ha_state()
await self._device.commands["settings"].send()
self.coordinator.async_set_updated_data({})
async def async_unlock(self, **kwargs: Any) -> None:
"""Unlock method."""
setting = self._device.settings[f"settings.{self.entity_description.key}"]
if type(setting) == HonParameter:
return
setting.value = setting.min if isinstance(setting, HonParameterRange) else 0
self.schedule_update_ha_state()
await self._device.commands["settings"].send()
self.coordinator.async_set_updated_data({})
@property
def available(self) -> bool:
"""Return True if entity is available."""
return (
super().available
and int(self._device.get("remoteCtrValid", 1)) == 1
and self._device.connection
)
@callback
def _handle_coordinator_update(self, update: bool = True) -> None:
self._attr_is_locked = self.is_locked
if update:
self.schedule_update_ha_state()