Fix HomeKit Controller entity state restore issues for IP/COAP devices (#151087)

This commit is contained in:
J. Nick Koston
2025-08-25 19:50:13 +02:00
committed by GitHub
parent 28e8405622
commit ede948c277
3 changed files with 75 additions and 37 deletions

View File

@@ -228,7 +228,9 @@ class HKDevice:
_LOGGER.debug( _LOGGER.debug(
"Called async_set_available_state with %s for %s", available, self.unique_id "Called async_set_available_state with %s for %s", available, self.unique_id
) )
if self.available == available: # Don't mark entities as unavailable during shutdown to preserve their last known state
# Also skip if the availability state hasn't changed
if (self.hass.is_stopping and not available) or self.available == available:
return return
self.available = available self.available = available
for callback_ in self._availability_callbacks: for callback_ in self._availability_callbacks:
@@ -294,7 +296,6 @@ class HKDevice:
await self.pairing.async_populate_accessories_state( await self.pairing.async_populate_accessories_state(
force_update=True, attempts=attempts force_update=True, attempts=attempts
) )
self._async_start_polling()
entry.async_on_unload(pairing.dispatcher_connect(self.process_new_events)) entry.async_on_unload(pairing.dispatcher_connect(self.process_new_events))
entry.async_on_unload( entry.async_on_unload(
@@ -307,6 +308,12 @@ class HKDevice:
await self.async_process_entity_map() await self.async_process_entity_map()
if transport != Transport.BLE:
# Do a single poll to make sure the chars are
# up to date so we don't restore old data.
await self.async_update()
self._async_start_polling()
# If everything is up to date, we can create the entities # If everything is up to date, we can create the entities
# since we know the data is not stale. # since we know the data is not stale.
await self.async_add_new_entities() await self.async_add_new_entities()
@@ -711,6 +718,8 @@ class HKDevice:
"""Stop interacting with device and prepare for removal from hass.""" """Stop interacting with device and prepare for removal from hass."""
await self.pairing.shutdown() await self.pairing.shutdown()
# Skip platform unloading during shutdown to preserve entity states
if not self.hass.is_stopping:
await self.hass.config_entries.async_unload_platforms( await self.hass.config_entries.async_unload_platforms(
self.config_entry, self.platforms self.config_entry, self.platforms
) )

View File

@@ -900,7 +900,7 @@
'options': dict({ 'options': dict({
}), }),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>, 'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': 'mdi:battery-unknown', 'original_icon': 'mdi:battery-20',
'original_name': 'eufyCam2-0000 Battery', 'original_name': 'eufyCam2-0000 Battery',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
@@ -1156,7 +1156,7 @@
'options': dict({ 'options': dict({
}), }),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>, 'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': 'mdi:battery-unknown', 'original_icon': 'mdi:battery-40',
'original_name': 'eufyCam2-000A Battery', 'original_name': 'eufyCam2-000A Battery',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
@@ -1412,7 +1412,7 @@
'options': dict({ 'options': dict({
}), }),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>, 'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': 'mdi:battery-unknown', 'original_icon': 'mdi:battery-alert',
'original_name': 'eufyCam2-000A Battery', 'original_name': 'eufyCam2-000A Battery',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
@@ -1848,7 +1848,7 @@
'options': dict({ 'options': dict({
}), }),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>, 'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': 'mdi:battery-unknown', 'original_icon': 'mdi:battery',
'original_name': 'Contact Sensor Battery Sensor', 'original_name': 'Contact Sensor Battery Sensor',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
@@ -2270,7 +2270,7 @@
'options': dict({ 'options': dict({
}), }),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>, 'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': 'mdi:battery-unknown', 'original_icon': 'mdi:battery',
'original_name': 'Programmable Switch Battery Sensor', 'original_name': 'Programmable Switch Battery Sensor',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
@@ -2601,7 +2601,7 @@
'options': dict({ 'options': dict({
}), }),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>, 'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': 'mdi:battery-unknown', 'original_icon': 'mdi:battery-80',
'original_name': 'ArloBabyA0 Battery', 'original_name': 'ArloBabyA0 Battery',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
@@ -4473,7 +4473,7 @@
'options': dict({ 'options': dict({
}), }),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>, 'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': 'mdi:battery-unknown', 'original_icon': 'mdi:battery',
'original_name': 'Basement Battery', 'original_name': 'Basement Battery',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
@@ -4780,7 +4780,7 @@
'options': dict({ 'options': dict({
}), }),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>, 'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': 'mdi:battery-unknown', 'original_icon': 'mdi:battery',
'original_name': 'Basement Window 1 Battery', 'original_name': 'Basement Window 1 Battery',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
@@ -5037,7 +5037,7 @@
'options': dict({ 'options': dict({
}), }),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>, 'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': 'mdi:battery-unknown', 'original_icon': 'mdi:battery',
'original_name': 'Deck Door Battery', 'original_name': 'Deck Door Battery',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
@@ -5294,7 +5294,7 @@
'options': dict({ 'options': dict({
}), }),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>, 'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': 'mdi:battery-unknown', 'original_icon': 'mdi:battery',
'original_name': 'Front Door Battery', 'original_name': 'Front Door Battery',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
@@ -5551,7 +5551,7 @@
'options': dict({ 'options': dict({
}), }),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>, 'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': 'mdi:battery-unknown', 'original_icon': 'mdi:battery',
'original_name': 'Garage Door Battery', 'original_name': 'Garage Door Battery',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
@@ -5765,7 +5765,7 @@
'options': dict({ 'options': dict({
}), }),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>, 'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': 'mdi:battery-unknown', 'original_icon': 'mdi:battery',
'original_name': 'Living Room Battery', 'original_name': 'Living Room Battery',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
@@ -6072,7 +6072,7 @@
'options': dict({ 'options': dict({
}), }),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>, 'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': 'mdi:battery-unknown', 'original_icon': 'mdi:battery',
'original_name': 'Living Room Window 1 Battery', 'original_name': 'Living Room Window 1 Battery',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
@@ -6329,7 +6329,7 @@
'options': dict({ 'options': dict({
}), }),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>, 'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': 'mdi:battery-unknown', 'original_icon': 'mdi:battery',
'original_name': 'Loft window Battery', 'original_name': 'Loft window Battery',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
@@ -6543,7 +6543,7 @@
'options': dict({ 'options': dict({
}), }),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>, 'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': 'mdi:battery-unknown', 'original_icon': 'mdi:battery',
'original_name': 'Master BR Battery', 'original_name': 'Master BR Battery',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
@@ -6850,7 +6850,7 @@
'options': dict({ 'options': dict({
}), }),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>, 'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': 'mdi:battery-unknown', 'original_icon': 'mdi:battery',
'original_name': 'Master BR Window Battery', 'original_name': 'Master BR Window Battery',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
@@ -7462,7 +7462,7 @@
'options': dict({ 'options': dict({
}), }),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>, 'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': 'mdi:battery-unknown', 'original_icon': 'mdi:battery',
'original_name': 'Upstairs BR Battery', 'original_name': 'Upstairs BR Battery',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
@@ -7769,7 +7769,7 @@
'options': dict({ 'options': dict({
}), }),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>, 'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': 'mdi:battery-unknown', 'original_icon': 'mdi:battery',
'original_name': 'Upstairs BR Window Battery', 'original_name': 'Upstairs BR Window Battery',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
@@ -10111,7 +10111,7 @@
'options': dict({ 'options': dict({
}), }),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>, 'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': 'mdi:battery-unknown', 'original_icon': 'mdi:battery-60',
'original_name': 'Eve Degree AA11 Battery', 'original_name': 'Eve Degree AA11 Battery',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
@@ -11099,7 +11099,7 @@
'options': dict({ 'options': dict({
}), }),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>, 'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': 'mdi:battery-unknown', 'original_icon': 'mdi:battery',
'original_name': 'Family Room North Battery', 'original_name': 'Family Room North Battery',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
@@ -11351,7 +11351,7 @@
'options': dict({ 'options': dict({
}), }),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>, 'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': 'mdi:battery-unknown', 'original_icon': 'mdi:battery',
'original_name': 'Kitchen Window Battery', 'original_name': 'Kitchen Window Battery',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
@@ -12392,7 +12392,7 @@
'options': dict({ 'options': dict({
}), }),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>, 'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': 'mdi:battery-unknown', 'original_icon': 'mdi:battery',
'original_name': 'Laundry Smoke ED78 Battery', 'original_name': 'Laundry Smoke ED78 Battery',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
@@ -12568,7 +12568,7 @@
'options': dict({ 'options': dict({
}), }),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>, 'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': 'mdi:battery-unknown', 'original_icon': 'mdi:battery',
'original_name': 'Family Room North Battery', 'original_name': 'Family Room North Battery',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
@@ -12820,7 +12820,7 @@
'options': dict({ 'options': dict({
}), }),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>, 'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': 'mdi:battery-unknown', 'original_icon': 'mdi:battery',
'original_name': 'Kitchen Window Battery', 'original_name': 'Kitchen Window Battery',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
@@ -14645,7 +14645,7 @@
'options': dict({ 'options': dict({
}), }),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>, 'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': 'mdi:battery-unknown', 'original_icon': 'mdi:battery',
'original_name': 'Laundry Smoke ED78 Battery', 'original_name': 'Laundry Smoke ED78 Battery',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
@@ -16083,7 +16083,7 @@
'options': dict({ 'options': dict({
}), }),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>, 'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': 'mdi:battery-unknown', 'original_icon': 'mdi:battery',
'original_name': 'Hue dimmer switch battery', 'original_name': 'Hue dimmer switch battery',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
@@ -20820,7 +20820,7 @@
'options': dict({ 'options': dict({
}), }),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>, 'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': 'mdi:battery-unknown', 'original_icon': 'mdi:battery',
'original_name': 'Master Bath South RYSE Shade Battery', 'original_name': 'Master Bath South RYSE Shade Battery',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
@@ -21072,7 +21072,7 @@
'options': dict({ 'options': dict({
}), }),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>, 'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': 'mdi:battery-unknown', 'original_icon': 'mdi:battery',
'original_name': 'RYSE SmartShade RYSE Shade Battery', 'original_name': 'RYSE SmartShade RYSE Shade Battery',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
@@ -21248,7 +21248,7 @@
'options': dict({ 'options': dict({
}), }),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>, 'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': 'mdi:battery-unknown', 'original_icon': 'mdi:battery',
'original_name': 'BR Left RYSE Shade Battery', 'original_name': 'BR Left RYSE Shade Battery',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
@@ -21420,7 +21420,7 @@
'options': dict({ 'options': dict({
}), }),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>, 'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': 'mdi:battery-unknown', 'original_icon': 'mdi:battery-90',
'original_name': 'LR Left RYSE Shade Battery', 'original_name': 'LR Left RYSE Shade Battery',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
@@ -21592,7 +21592,7 @@
'options': dict({ 'options': dict({
}), }),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>, 'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': 'mdi:battery-unknown', 'original_icon': 'mdi:battery',
'original_name': 'LR Right RYSE Shade Battery', 'original_name': 'LR Right RYSE Shade Battery',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,
@@ -21844,7 +21844,7 @@
'options': dict({ 'options': dict({
}), }),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>, 'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': 'mdi:battery-unknown', 'original_icon': 'mdi:battery-alert',
'original_name': 'RZSS RYSE Shade Battery', 'original_name': 'RZSS RYSE Shade Battery',
'platform': 'homekit_controller', 'platform': 'homekit_controller',
'previous_unique_id': None, 'previous_unique_id': None,

View File

@@ -88,3 +88,32 @@ async def test_storage_is_updated_on_add(
# Is saved out to store? # Is saved out to store?
await flush_store(entity_map.store) await flush_store(entity_map.store)
assert hkid in hass_storage[ENTITY_MAP]["data"]["pairings"] assert hkid in hass_storage[ENTITY_MAP]["data"]["pairings"]
async def test_storage_is_saved_on_stop(
hass: HomeAssistant, hass_storage: dict[str, Any], get_next_aid: Callable[[], int]
) -> None:
"""Test entity map storage is saved when Home Assistant stops."""
await setup_test_component(hass, get_next_aid(), create_lightbulb_service)
entity_map: EntityMapStorage = hass.data[ENTITY_MAP]
hkid = "00:00:00:00:00:00"
# Verify the device is in memory
assert hkid in entity_map.storage_data
# Clear the storage to verify it gets saved on stop
del hass_storage[ENTITY_MAP]
# Make a change to trigger a save
entity_map.async_create_or_update_map(hkid, 2, []) # Update config_num
# Simulate Home Assistant stopping (sets the state and fires the event)
await hass.async_stop()
await hass.async_block_till_done()
# Verify the storage was saved
assert ENTITY_MAP in hass_storage
assert hkid in hass_storage[ENTITY_MAP]["data"]["pairings"]
# Verify the updated data was saved
assert hass_storage[ENTITY_MAP]["data"]["pairings"][hkid]["config_num"] == 2