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>
This commit is contained in:
Vadym Melnychuk 2026-06-01 12:03:07 +03:00 committed by Alexander Tihoniuk
parent 0fb3e2dc00
commit 047800ccbf
5 changed files with 20 additions and 20 deletions

View file

@ -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]

View file

@ -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:

View file

@ -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:

View file

@ -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({})

View file

@ -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: