diff --git a/homeassistant/components/homekit/type_remotes.py b/homeassistant/components/homekit/type_remotes.py index cd06d6b2d08..1dfcb0f91a3 100644 --- a/homeassistant/components/homekit/type_remotes.py +++ b/homeassistant/components/homekit/type_remotes.py @@ -18,7 +18,7 @@ from homeassistant.const import ( SERVICE_TURN_ON, STATE_ON, ) -from homeassistant.core import callback +from homeassistant.core import State, callback from .accessories import TYPES, HomeAccessory from .const import ( @@ -96,7 +96,7 @@ class RemoteInputSelectAccessory(HomeAccessory, ABC): self.sources = [] self.support_select_source = False if features & required_feature: - sources = state.attributes.get(source_list_key, []) + sources = self._get_ordered_source_list_from_state(state) if len(sources) > MAXIMUM_SOURCES: _LOGGER.warning( "%s: Reached maximum number of sources (%s)", @@ -143,6 +143,21 @@ class RemoteInputSelectAccessory(HomeAccessory, ABC): serv_input.configure_char(CHAR_CURRENT_VISIBILITY_STATE, value=False) _LOGGER.debug("%s: Added source %s", self.entity_id, source) + def _get_ordered_source_list_from_state(self, state: State) -> list[str]: + """Return ordered source list while preserving order with duplicates removed. + + Some integrations have duplicate sources in the source list + which will make the source list conflict as HomeKit requires + unique source names. + """ + seen = set() + sources: list[str] = [] + for source in state.attributes.get(self.source_list_key, []): + if source not in seen: + sources.append(source) + seen.add(source) + return sources + @abstractmethod def set_on_off(self, value): """Move switch state to value if call came from HomeKit.""" @@ -169,7 +184,7 @@ class RemoteInputSelectAccessory(HomeAccessory, ABC): self.char_input_source.set_value(index) return - possible_sources = new_state.attributes.get(self.source_list_key, []) + possible_sources = self._get_ordered_source_list_from_state(new_state) if source in possible_sources: index = possible_sources.index(source) if index >= MAXIMUM_SOURCES: diff --git a/tests/components/homekit/test_type_media_players.py b/tests/components/homekit/test_type_media_players.py index 30b9bc77f5d..e815a25ee7d 100644 --- a/tests/components/homekit/test_type_media_players.py +++ b/tests/components/homekit/test_type_media_players.py @@ -512,3 +512,48 @@ async def test_media_player_television_max_sources(hass, hk_driver, events, capl ) await hass.async_block_till_done() assert acc.char_input_source.value == 0 + + +async def test_media_player_television_duplicate_sources( + hass, hk_driver, events, caplog +): + """Test if television accessory with duplicate sources.""" + entity_id = "media_player.television" + sources = ["MUSIC", "HDMI", "SCREEN MIRRORING", "HDMI", "MUSIC"] + hass.states.async_set( + entity_id, + None, + { + ATTR_DEVICE_CLASS: MediaPlayerDeviceClass.TV, + ATTR_SUPPORTED_FEATURES: 3469, + ATTR_MEDIA_VOLUME_MUTED: False, + ATTR_INPUT_SOURCE: "HDMI", + ATTR_INPUT_SOURCE_LIST: sources, + }, + ) + await hass.async_block_till_done() + acc = TelevisionMediaPlayer(hass, hk_driver, "MediaPlayer", entity_id, 2, None) + await acc.run() + await hass.async_block_till_done() + + assert acc.aid == 2 + assert acc.category == 31 # Television + + assert acc.char_active.value == 0 + assert acc.char_remote_key.value == 0 + assert acc.char_input_source.value == 1 + assert acc.char_mute.value is False + + hass.states.async_set( + entity_id, + None, + { + ATTR_DEVICE_CLASS: MediaPlayerDeviceClass.TV, + ATTR_SUPPORTED_FEATURES: 3469, + ATTR_MEDIA_VOLUME_MUTED: False, + ATTR_INPUT_SOURCE: "MUSIC", + ATTR_INPUT_SOURCE_LIST: sources, + }, + ) + await hass.async_block_till_done() + assert acc.char_input_source.value == 0