From f20602e11d55b06cbb22bceebe895b13743583ac Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 20 Jul 2021 18:46:39 -1000 Subject: [PATCH] Auto recreate HomeKit TVs when the sources are out of sync (#53208) --- .../components/homekit/accessories.py | 13 ++++++++++ .../components/homekit/type_remotes.py | 24 +++++++++++++++---- .../homekit/test_type_media_players.py | 2 +- tests/components/homekit/test_type_remote.py | 18 ++++++++++++++ 4 files changed, 51 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index 3c843955222..03d00c42a91 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -58,12 +58,14 @@ from .const import ( CONF_LOW_BATTERY_THRESHOLD, DEFAULT_LOW_BATTERY_THRESHOLD, DEVICE_CLASS_PM25, + DOMAIN, EVENT_HOMEKIT_CHANGED, HK_CHARGING, HK_NOT_CHARGABLE, HK_NOT_CHARGING, MANUFACTURER, SERV_BATTERY_SERVICE, + SERVICE_HOMEKIT_RESET_ACCESSORY, TYPE_FAUCET, TYPE_OUTLET, TYPE_SHOWER, @@ -454,6 +456,17 @@ class HomeAccessory(Accessory): ) ) + @ha_callback + def async_reset(self): + """Reset and recreate an accessory.""" + self.hass.async_create_task( + self.hass.services.async_call( + DOMAIN, + SERVICE_HOMEKIT_RESET_ACCESSORY, + {ATTR_ENTITY_ID: self.entity_id}, + ) + ) + @ha_callback def async_stop(self): """Cancel any subscriptions when the bridge is stopped.""" diff --git a/homeassistant/components/homekit/type_remotes.py b/homeassistant/components/homekit/type_remotes.py index e4f18a7c16f..718671dfd1d 100644 --- a/homeassistant/components/homekit/type_remotes.py +++ b/homeassistant/components/homekit/type_remotes.py @@ -87,6 +87,7 @@ class RemoteInputSelectAccessory(HomeAccessory): features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) self.source_key = source_key + self.source_list_key = source_list_key self.sources = [] self.support_select_source = False if features & required_feature: @@ -152,13 +153,26 @@ class RemoteInputSelectAccessory(HomeAccessory): index = self.sources.index(source_name) if self.char_input_source.value != index: self.char_input_source.set_value(index) - elif hk_state: - _LOGGER.warning( - "%s: Sources out of sync. Restart Home Assistant", + return + + possible_sources = new_state.attributes.get(self.source_list_key, []) + if source_name in possible_sources: + _LOGGER.debug( + "%s: Sources out of sync. Rebuilding Accessory", self.entity_id, ) - if self.char_input_source.value != 0: - self.char_input_source.set_value(0) + # Sources are out of sync, recreate the accessory + self.async_reset() + return + + _LOGGER.debug( + "%s: Source %s does not exist the source list: %s", + self.entity_id, + source_name, + possible_sources, + ) + if self.char_input_source.value != 0: + self.char_input_source.set_value(0) @TYPES.register("ActivityRemote") diff --git a/tests/components/homekit/test_type_media_players.py b/tests/components/homekit/test_type_media_players.py index b95903d3e3f..33cac7bcf8a 100644 --- a/tests/components/homekit/test_type_media_players.py +++ b/tests/components/homekit/test_type_media_players.py @@ -242,7 +242,7 @@ async def test_media_player_television(hass, hk_driver, events, caplog): hass.states.async_set(entity_id, STATE_ON, {ATTR_INPUT_SOURCE: "HDMI 5"}) await hass.async_block_till_done() assert acc.char_input_source.value == 0 - assert caplog.records[-2].levelname == "WARNING" + assert caplog.records[-2].levelname == "DEBUG" # Set from HomeKit call_turn_on = async_mock_service(hass, DOMAIN, "turn_on") diff --git a/tests/components/homekit/test_type_remote.py b/tests/components/homekit/test_type_remote.py index e69ebfb29fb..ee71d7f4e3c 100644 --- a/tests/components/homekit/test_type_remote.py +++ b/tests/components/homekit/test_type_remote.py @@ -3,8 +3,10 @@ from homeassistant.components.homekit.const import ( ATTR_KEY_NAME, ATTR_VALUE, + DOMAIN as HOMEKIT_DOMAIN, EVENT_HOMEKIT_TV_REMOTE_KEY_PRESSED, KEY_ARROW_RIGHT, + SERVICE_HOMEKIT_RESET_ACCESSORY, ) from homeassistant.components.homekit.type_remotes import ActivityRemote from homeassistant.components.remote import ( @@ -146,3 +148,19 @@ async def test_activity_remote(hass, hk_driver, events, caplog): assert len(events) == 1 assert events[0].data[ATTR_KEY_NAME] == KEY_ARROW_RIGHT + + call_reset_accessory = async_mock_service( + hass, HOMEKIT_DOMAIN, SERVICE_HOMEKIT_RESET_ACCESSORY + ) + # A wild source appears - The accessory should rebuild itself + hass.states.async_set( + entity_id, + STATE_ON, + { + ATTR_SUPPORTED_FEATURES: SUPPORT_ACTIVITY, + ATTR_CURRENT_ACTIVITY: "Amazon TV", + ATTR_ACTIVITY_LIST: ["TV", "Apple TV", "Amazon TV"], + }, + ) + await hass.async_block_till_done() + assert call_reset_accessory[0].data[ATTR_ENTITY_ID] == entity_id