From a2c60d9015382bec293899904e0f4c37c967c33c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 20 Oct 2023 12:46:18 -1000 Subject: [PATCH] Only callback when value or status changes for processing HKC events (#102370) --- .../homekit_controller/connection.py | 4 +-- .../components/homekit_controller/entity.py | 6 ++++- tests/components/homekit_controller/common.py | 7 ++++++ .../homekit_controller/test_sensor.py | 25 +++++++++++++++++-- 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/homekit_controller/connection.py b/homeassistant/components/homekit_controller/connection.py index 923dfd8f96b..1d0eb9cdd83 100644 --- a/homeassistant/components/homekit_controller/connection.py +++ b/homeassistant/components/homekit_controller/connection.py @@ -833,10 +833,8 @@ class HKDevice: # Process any stateless events (via device_triggers) async_fire_triggers(self, new_values_dict) - self.entity_map.process_changes(new_values_dict) - to_callback: set[CALLBACK_TYPE] = set() - for aid_iid in new_values_dict: + for aid_iid in self.entity_map.process_changes(new_values_dict): if callbacks := self._subscriptions.get(aid_iid): to_callback.update(callbacks) diff --git a/homeassistant/components/homekit_controller/entity.py b/homeassistant/components/homekit_controller/entity.py index 7511f95e283..d1f48a67e7f 100644 --- a/homeassistant/components/homekit_controller/entity.py +++ b/homeassistant/components/homekit_controller/entity.py @@ -219,7 +219,11 @@ class HomeKitEntity(Entity): @property def available(self) -> bool: """Return True if entity is available.""" - return self._accessory.available and self.service.available + return self._accessory.available and all( + c.available + for c in self.service.characteristics + if (self._aid, c.iid) in self.all_characteristics + ) @property def device_info(self) -> DeviceInfo: diff --git a/tests/components/homekit_controller/common.py b/tests/components/homekit_controller/common.py index c3e6b5505d3..a5219fe7018 100644 --- a/tests/components/homekit_controller/common.py +++ b/tests/components/homekit_controller/common.py @@ -155,6 +155,13 @@ class Helper: assert state is not None return state + async def async_set_aid_iid_status( + self, aid_iid_status: list[tuple[int, int, int]] + ) -> None: + """Set the status of a set of aid/iid pairs.""" + self.pairing.testing.set_aid_iid_status(aid_iid_status) + await self.hass.async_block_till_done() + @callback def async_assert_service_values( self, service: str, characteristics: dict[str, Any] diff --git a/tests/components/homekit_controller/test_sensor.py b/tests/components/homekit_controller/test_sensor.py index 6c9ad008703..829fe8e3cdc 100644 --- a/tests/components/homekit_controller/test_sensor.py +++ b/tests/components/homekit_controller/test_sensor.py @@ -311,10 +311,9 @@ async def test_sensor_unavailable(hass: HomeAssistant, utcnow) -> None: """Test a sensor becoming unavailable.""" helper = await setup_test_component(hass, create_switch_with_sensor) - # Find the energy sensor and mark it as offline outlet = helper.accessory.services.first(service_type=ServicesTypes.OUTLET) + on_char = outlet[CharacteristicsTypes.ON] realtime_energy = outlet[CharacteristicsTypes.VENDOR_KOOGEEK_REALTIME_ENERGY] - realtime_energy.status = HapStatusCode.UNABLE_TO_COMMUNICATE # Helper will be for the primary entity, which is the outlet. Make a helper for the sensor. energy_helper = Helper( @@ -325,10 +324,32 @@ async def test_sensor_unavailable(hass: HomeAssistant, utcnow) -> None: helper.config_entry, ) + # Find the outlet on char and mark it as offline + await helper.async_set_aid_iid_status( + [ + ( + helper.accessory.aid, + on_char.iid, + HapStatusCode.UNABLE_TO_COMMUNICATE.value, + ) + ] + ) + # Outlet has non-responsive characteristics so should be unavailable state = await helper.poll_and_get_state() assert state.state == "unavailable" + # Find the energy sensor and mark it as offline + await helper.async_set_aid_iid_status( + [ + ( + energy_helper.accessory.aid, + realtime_energy.iid, + HapStatusCode.UNABLE_TO_COMMUNICATE.value, + ) + ] + ) + # Energy sensor has non-responsive characteristics so should be unavailable state = await energy_helper.poll_and_get_state() assert state.state == "unavailable"