From 047800ccbfb6b0bd1a9edf753744af68bcc2ecaf Mon Sep 17 00:00:00 2001 From: Vadym Melnychuk Date: Mon, 1 Jun 2026 12:03:07 +0300 Subject: [PATCH] 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 --- custom_components/hon/climate.py | 16 ++++++++-------- custom_components/hon/fan.py | 4 ++-- custom_components/hon/light.py | 4 ++-- custom_components/hon/lock.py | 4 ++-- custom_components/hon/switch.py | 12 ++++++------ 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/custom_components/hon/climate.py b/custom_components/hon/climate.py index eadcbfe..9cbc2ce 100644 --- a/custom_components/hon/climate.py +++ b/custom_components/hon/climate.py @@ -196,7 +196,7 @@ class HonACClimateEntity(HonEntity, ClimateEntity): self._device.settings["settings.tempSel"].value = str(int(temperature)) await self._device.commands["settings"].send() - self.async_write_ha_state() + self.schedule_update_ha_state() @property def hvac_mode(self) -> HVACMode: @@ -235,7 +235,7 @@ class HonACClimateEntity(HonEntity, ClimateEntity): await self.async_set_preset_mode(HON_HVAC_PROGRAM[hvac_mode]) return await self._device.commands["settings"].send() - self.async_write_ha_state() + self.schedule_update_ha_state() async def async_turn_on(self, **kwargs: Any) -> None: await self._device.commands["startProgram"].send() @@ -260,7 +260,7 @@ class HonACClimateEntity(HonEntity, ClimateEntity): self.coordinator.async_set_updated_data({}) self._attr_preset_mode = preset_mode await self._device.commands["startProgram"].send() - self.async_write_ha_state() + self.schedule_update_ha_state() @property def fan_modes(self) -> list[str]: @@ -291,7 +291,7 @@ class HonACClimateEntity(HonEntity, ClimateEntity): self._device.settings["settings.windSpeed"].value = str(fan_modes[fan_mode]) self._attr_fan_mode = fan_mode await self._device.commands["settings"].send() - self.async_write_ha_state() + self.schedule_update_ha_state() @property def swing_mode(self) -> str | None: @@ -328,7 +328,7 @@ class HonACClimateEntity(HonEntity, ClimateEntity): horizontal.value = "0" self._attr_swing_mode = swing_mode await self._device.commands["settings"].send() - self.async_write_ha_state() + self.schedule_update_ha_state() @callback def _handle_coordinator_update(self, update: bool = True) -> None: @@ -397,7 +397,7 @@ class HonClimateEntity(HonEntity, ClimateEntity): return self._device.settings[self.entity_description.key].value = str(int(temperature)) await self._device.commands["settings"].send() - self.async_write_ha_state() + self.schedule_update_ha_state() @property def hvac_mode(self) -> HVACMode: @@ -414,7 +414,7 @@ class HonClimateEntity(HonEntity, ClimateEntity): else: await self._device.commands["startProgram"].send() self._attr_hvac_mode = hvac_mode - self.async_write_ha_state() + self.schedule_update_ha_state() async def async_turn_on(self) -> None: """Set the HVAC State to on.""" @@ -453,7 +453,7 @@ class HonClimateEntity(HonEntity, ClimateEntity): self._attr_preset_mode = preset_mode self.coordinator.async_set_updated_data({}) await self._device.commands[command].send() - self.async_write_ha_state() + self.schedule_update_ha_state() def _set_temperature_bound(self) -> None: temperature = self._device.settings[self.entity_description.key] diff --git a/custom_components/hon/fan.py b/custom_components/hon/fan.py index ae07b4b..f5a5fc4 100644 --- a/custom_components/hon/fan.py +++ b/custom_components/hon/fan.py @@ -85,7 +85,7 @@ class HonFanEntity(HonEntity, FanEntity): mode = math.ceil(percentage_to_ranged_value(self._speed_range, percentage)) self._device.settings[self.entity_description.key].value = mode await self._device.commands[self._command].send() - self.async_write_ha_state() + self.schedule_update_ha_state() @property def is_on(self) -> bool | None: @@ -112,7 +112,7 @@ class HonFanEntity(HonEntity, FanEntity): """Turn the entity off.""" self._device.settings[self.entity_description.key].value = 0 await self._device.commands[self._command].send() - self.async_write_ha_state() + self.schedule_update_ha_state() @callback def _handle_coordinator_update(self, update: bool = True) -> None: diff --git a/custom_components/hon/light.py b/custom_components/hon/light.py index 63160bc..b3e514d 100644 --- a/custom_components/hon/light.py +++ b/custom_components/hon/light.py @@ -110,7 +110,7 @@ class HonLightEntity(HonEntity, LightEntity): else: light.value = light.max await self._device.commands[self._command].send() - self.async_write_ha_state() + self.schedule_update_ha_state() async def async_turn_off(self, **kwargs: Any) -> None: """Instruct the light to turn off.""" @@ -119,7 +119,7 @@ class HonLightEntity(HonEntity, LightEntity): raise ValueError() light.value = light.min await self._device.commands[self._command].send() - self.async_write_ha_state() + self.schedule_update_ha_state() @property def brightness(self) -> int | None: diff --git a/custom_components/hon/lock.py b/custom_components/hon/lock.py index f4881a8..e44597f 100644 --- a/custom_components/hon/lock.py +++ b/custom_components/hon/lock.py @@ -56,7 +56,7 @@ class HonLockEntity(HonEntity, LockEntity): if type(setting) == HonParameter or setting is None: return setting.value = setting.max if isinstance(setting, HonParameterRange) else 1 - self.async_write_ha_state() + self.schedule_update_ha_state() await self._device.commands["settings"].send() self.coordinator.async_set_updated_data({}) @@ -66,7 +66,7 @@ class HonLockEntity(HonEntity, LockEntity): if type(setting) == HonParameter: return setting.value = setting.min if isinstance(setting, HonParameterRange) else 0 - self.async_write_ha_state() + self.schedule_update_ha_state() await self._device.commands["settings"].send() self.coordinator.async_set_updated_data({}) diff --git a/custom_components/hon/switch.py b/custom_components/hon/switch.py index 5edf30e..4018327 100644 --- a/custom_components/hon/switch.py +++ b/custom_components/hon/switch.py @@ -445,7 +445,7 @@ class HonSwitchEntity(HonEntity, SwitchEntity): if type(setting) == HonParameter: return setting.value = setting.max if isinstance(setting, HonParameterRange) else 1 - self.async_write_ha_state() + self.schedule_update_ha_state() await self._device.commands["settings"].send() self.coordinator.async_set_updated_data({}) @@ -454,7 +454,7 @@ class HonSwitchEntity(HonEntity, SwitchEntity): if type(setting) == HonParameter: return setting.value = setting.min if isinstance(setting, HonParameterRange) else 0 - self.async_write_ha_state() + self.schedule_update_ha_state() await self._device.commands["settings"].send() self.coordinator.async_set_updated_data({}) @@ -492,14 +492,14 @@ class HonControlSwitchEntity(HonEntity, SwitchEntity): self.coordinator.async_set_updated_data({}) await self._device.commands[self.entity_description.turn_on_key].send() self._device.attributes[self.entity_description.key] = True - self.async_write_ha_state() + self.schedule_update_ha_state() async def async_turn_off(self, **kwargs: Any) -> None: self._device.sync_command(self.entity_description.turn_off_key, "settings") self.coordinator.async_set_updated_data({}) await self._device.commands[self.entity_description.turn_off_key].send() self._device.attributes[self.entity_description.key] = False - self.async_write_ha_state() + self.schedule_update_ha_state() @property def available(self) -> bool: @@ -542,7 +542,7 @@ class HonConfigSwitchEntity(HonEntity, SwitchEntity): return setting.value = setting.max if isinstance(setting, HonParameterRange) else "1" self.coordinator.async_set_updated_data({}) - self.async_write_ha_state() + self.schedule_update_ha_state() async def async_turn_off(self, **kwargs: Any) -> None: setting = self._device.settings[self.entity_description.key] @@ -550,7 +550,7 @@ class HonConfigSwitchEntity(HonEntity, SwitchEntity): return setting.value = setting.min if isinstance(setting, HonParameterRange) else "0" self.coordinator.async_set_updated_data({}) - self.async_write_ha_state() + self.schedule_update_ha_state() @callback def _handle_coordinator_update(self, update: bool = True) -> None: