diff --git a/homeassistant/components/homekit_controller/connection.py b/homeassistant/components/homekit_controller/connection.py index 931bd40d64c..139ceef48ad 100644 --- a/homeassistant/components/homekit_controller/connection.py +++ b/homeassistant/components/homekit_controller/connection.py @@ -228,7 +228,9 @@ class HKDevice: _LOGGER.debug( "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 self.available = available for callback_ in self._availability_callbacks: @@ -294,7 +296,6 @@ class HKDevice: await self.pairing.async_populate_accessories_state( force_update=True, attempts=attempts ) - self._async_start_polling() entry.async_on_unload(pairing.dispatcher_connect(self.process_new_events)) entry.async_on_unload( @@ -307,6 +308,12 @@ class HKDevice: 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 # since we know the data is not stale. await self.async_add_new_entities() @@ -711,9 +718,11 @@ class HKDevice: """Stop interacting with device and prepare for removal from hass.""" await self.pairing.shutdown() - await self.hass.config_entries.async_unload_platforms( - self.config_entry, self.platforms - ) + # Skip platform unloading during shutdown to preserve entity states + if not self.hass.is_stopping: + await self.hass.config_entries.async_unload_platforms( + self.config_entry, self.platforms + ) def process_config_changed(self, config_num: int) -> None: """Handle a config change notification from the pairing.""" diff --git a/tests/components/homekit_controller/snapshots/test_init.ambr b/tests/components/homekit_controller/snapshots/test_init.ambr index 95d24957fcb..ea9c638c022 100644 --- a/tests/components/homekit_controller/snapshots/test_init.ambr +++ b/tests/components/homekit_controller/snapshots/test_init.ambr @@ -900,7 +900,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:battery-unknown', + 'original_icon': 'mdi:battery-20', 'original_name': 'eufyCam2-0000 Battery', 'platform': 'homekit_controller', 'previous_unique_id': None, @@ -1156,7 +1156,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:battery-unknown', + 'original_icon': 'mdi:battery-40', 'original_name': 'eufyCam2-000A Battery', 'platform': 'homekit_controller', 'previous_unique_id': None, @@ -1412,7 +1412,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:battery-unknown', + 'original_icon': 'mdi:battery-alert', 'original_name': 'eufyCam2-000A Battery', 'platform': 'homekit_controller', 'previous_unique_id': None, @@ -1848,7 +1848,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:battery-unknown', + 'original_icon': 'mdi:battery', 'original_name': 'Contact Sensor Battery Sensor', 'platform': 'homekit_controller', 'previous_unique_id': None, @@ -2270,7 +2270,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:battery-unknown', + 'original_icon': 'mdi:battery', 'original_name': 'Programmable Switch Battery Sensor', 'platform': 'homekit_controller', 'previous_unique_id': None, @@ -2601,7 +2601,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:battery-unknown', + 'original_icon': 'mdi:battery-80', 'original_name': 'ArloBabyA0 Battery', 'platform': 'homekit_controller', 'previous_unique_id': None, @@ -4473,7 +4473,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:battery-unknown', + 'original_icon': 'mdi:battery', 'original_name': 'Basement Battery', 'platform': 'homekit_controller', 'previous_unique_id': None, @@ -4780,7 +4780,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:battery-unknown', + 'original_icon': 'mdi:battery', 'original_name': 'Basement Window 1 Battery', 'platform': 'homekit_controller', 'previous_unique_id': None, @@ -5037,7 +5037,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:battery-unknown', + 'original_icon': 'mdi:battery', 'original_name': 'Deck Door Battery', 'platform': 'homekit_controller', 'previous_unique_id': None, @@ -5294,7 +5294,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:battery-unknown', + 'original_icon': 'mdi:battery', 'original_name': 'Front Door Battery', 'platform': 'homekit_controller', 'previous_unique_id': None, @@ -5551,7 +5551,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:battery-unknown', + 'original_icon': 'mdi:battery', 'original_name': 'Garage Door Battery', 'platform': 'homekit_controller', 'previous_unique_id': None, @@ -5765,7 +5765,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:battery-unknown', + 'original_icon': 'mdi:battery', 'original_name': 'Living Room Battery', 'platform': 'homekit_controller', 'previous_unique_id': None, @@ -6072,7 +6072,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:battery-unknown', + 'original_icon': 'mdi:battery', 'original_name': 'Living Room Window 1 Battery', 'platform': 'homekit_controller', 'previous_unique_id': None, @@ -6329,7 +6329,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:battery-unknown', + 'original_icon': 'mdi:battery', 'original_name': 'Loft window Battery', 'platform': 'homekit_controller', 'previous_unique_id': None, @@ -6543,7 +6543,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:battery-unknown', + 'original_icon': 'mdi:battery', 'original_name': 'Master BR Battery', 'platform': 'homekit_controller', 'previous_unique_id': None, @@ -6850,7 +6850,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:battery-unknown', + 'original_icon': 'mdi:battery', 'original_name': 'Master BR Window Battery', 'platform': 'homekit_controller', 'previous_unique_id': None, @@ -7462,7 +7462,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:battery-unknown', + 'original_icon': 'mdi:battery', 'original_name': 'Upstairs BR Battery', 'platform': 'homekit_controller', 'previous_unique_id': None, @@ -7769,7 +7769,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:battery-unknown', + 'original_icon': 'mdi:battery', 'original_name': 'Upstairs BR Window Battery', 'platform': 'homekit_controller', 'previous_unique_id': None, @@ -10111,7 +10111,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:battery-unknown', + 'original_icon': 'mdi:battery-60', 'original_name': 'Eve Degree AA11 Battery', 'platform': 'homekit_controller', 'previous_unique_id': None, @@ -11099,7 +11099,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:battery-unknown', + 'original_icon': 'mdi:battery', 'original_name': 'Family Room North Battery', 'platform': 'homekit_controller', 'previous_unique_id': None, @@ -11351,7 +11351,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:battery-unknown', + 'original_icon': 'mdi:battery', 'original_name': 'Kitchen Window Battery', 'platform': 'homekit_controller', 'previous_unique_id': None, @@ -12392,7 +12392,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:battery-unknown', + 'original_icon': 'mdi:battery', 'original_name': 'Laundry Smoke ED78 Battery', 'platform': 'homekit_controller', 'previous_unique_id': None, @@ -12568,7 +12568,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:battery-unknown', + 'original_icon': 'mdi:battery', 'original_name': 'Family Room North Battery', 'platform': 'homekit_controller', 'previous_unique_id': None, @@ -12820,7 +12820,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:battery-unknown', + 'original_icon': 'mdi:battery', 'original_name': 'Kitchen Window Battery', 'platform': 'homekit_controller', 'previous_unique_id': None, @@ -14645,7 +14645,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:battery-unknown', + 'original_icon': 'mdi:battery', 'original_name': 'Laundry Smoke ED78 Battery', 'platform': 'homekit_controller', 'previous_unique_id': None, @@ -16083,7 +16083,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:battery-unknown', + 'original_icon': 'mdi:battery', 'original_name': 'Hue dimmer switch battery', 'platform': 'homekit_controller', 'previous_unique_id': None, @@ -20820,7 +20820,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:battery-unknown', + 'original_icon': 'mdi:battery', 'original_name': 'Master Bath South RYSE Shade Battery', 'platform': 'homekit_controller', 'previous_unique_id': None, @@ -21072,7 +21072,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:battery-unknown', + 'original_icon': 'mdi:battery', 'original_name': 'RYSE SmartShade RYSE Shade Battery', 'platform': 'homekit_controller', 'previous_unique_id': None, @@ -21248,7 +21248,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:battery-unknown', + 'original_icon': 'mdi:battery', 'original_name': 'BR Left RYSE Shade Battery', 'platform': 'homekit_controller', 'previous_unique_id': None, @@ -21420,7 +21420,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:battery-unknown', + 'original_icon': 'mdi:battery-90', 'original_name': 'LR Left RYSE Shade Battery', 'platform': 'homekit_controller', 'previous_unique_id': None, @@ -21592,7 +21592,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:battery-unknown', + 'original_icon': 'mdi:battery', 'original_name': 'LR Right RYSE Shade Battery', 'platform': 'homekit_controller', 'previous_unique_id': None, @@ -21844,7 +21844,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:battery-unknown', + 'original_icon': 'mdi:battery-alert', 'original_name': 'RZSS RYSE Shade Battery', 'platform': 'homekit_controller', 'previous_unique_id': None, diff --git a/tests/components/homekit_controller/test_storage.py b/tests/components/homekit_controller/test_storage.py index 97856c2c784..868a18af1f9 100644 --- a/tests/components/homekit_controller/test_storage.py +++ b/tests/components/homekit_controller/test_storage.py @@ -88,3 +88,32 @@ async def test_storage_is_updated_on_add( # Is saved out to store? await flush_store(entity_map.store) 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