mirror of
https://github.com/home-assistant/core.git
synced 2026-05-08 17:49:37 +01:00
Allow selecting input source on SmartThings TVs (#160034)
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
This commit is contained in:
@@ -18,6 +18,26 @@ from . import FullDevice, SmartThingsConfigEntry
|
||||
from .const import MAIN
|
||||
from .entity import SmartThingsEntity
|
||||
|
||||
MEDIA_SOURCE_ID_TO_HA_KEY: dict[str, str] = {
|
||||
"AM": "am",
|
||||
"BT": "bluetooth",
|
||||
"CD": "cd",
|
||||
"D.IN": "digital_input",
|
||||
"HDMI": "hdmi",
|
||||
"HDMI1": "hdmi1",
|
||||
"HDMI2": "hdmi2",
|
||||
"HDMI3": "hdmi3",
|
||||
"HDMI4": "hdmi4",
|
||||
"HDMI5": "hdmi5",
|
||||
"HDMI6": "hdmi6",
|
||||
"USB": "usb",
|
||||
"WIFI": "wifi",
|
||||
"digitalTv": "digital_tv",
|
||||
"dtv": "digital_tv",
|
||||
"melon": "melon",
|
||||
"youtube": "youtube",
|
||||
}
|
||||
|
||||
MEDIA_PLAYER_CAPABILITIES = (
|
||||
Capability.AUDIO_MUTE,
|
||||
Capability.AUDIO_VOLUME,
|
||||
@@ -72,6 +92,7 @@ class SmartThingsMediaPlayer(SmartThingsEntity, MediaPlayerEntity):
|
||||
"""Define a SmartThings media player."""
|
||||
|
||||
_attr_name = None
|
||||
_attr_translation_key = "media_player"
|
||||
|
||||
def __init__(self, client: SmartThings, device: FullDevice) -> None:
|
||||
"""Initialize the media_player class."""
|
||||
@@ -87,6 +108,7 @@ class SmartThingsMediaPlayer(SmartThingsEntity, MediaPlayerEntity):
|
||||
Capability.MEDIA_PLAYBACK_REPEAT,
|
||||
Capability.MEDIA_PLAYBACK_SHUFFLE,
|
||||
Capability.SAMSUNG_VD_AUDIO_INPUT_SOURCE,
|
||||
Capability.SAMSUNG_VD_MEDIA_INPUT_SOURCE,
|
||||
Capability.SWITCH,
|
||||
},
|
||||
)
|
||||
@@ -95,6 +117,43 @@ class SmartThingsMediaPlayer(SmartThingsEntity, MediaPlayerEntity):
|
||||
device.device.components[MAIN].user_category
|
||||
or device.device.components[MAIN].manufacturer_category,
|
||||
)
|
||||
self._source_to_smartthings_id: dict[str, str] = {}
|
||||
|
||||
def _update_attr(self) -> None:
|
||||
"""Update the attributes."""
|
||||
self._build_source_map()
|
||||
|
||||
def _build_source_map(self) -> None:
|
||||
"""Build the source mapping from HA key to SmartThings ID."""
|
||||
raw_sources = self._get_raw_source_list()
|
||||
if not raw_sources:
|
||||
self._source_to_smartthings_id = {}
|
||||
return
|
||||
self._source_to_smartthings_id = {
|
||||
MEDIA_SOURCE_ID_TO_HA_KEY.get(source_id, source_id): source_id
|
||||
for source_id in raw_sources
|
||||
}
|
||||
|
||||
def _get_raw_source_list(self) -> list[str] | None:
|
||||
"""Get the raw source list from the device."""
|
||||
if self.supports_capability(Capability.SAMSUNG_VD_MEDIA_INPUT_SOURCE):
|
||||
sources_map = self.get_attribute_value(
|
||||
Capability.SAMSUNG_VD_MEDIA_INPUT_SOURCE,
|
||||
Attribute.SUPPORTED_INPUT_SOURCES_MAP,
|
||||
)
|
||||
if not sources_map:
|
||||
return None
|
||||
return [source["id"] for source in sources_map]
|
||||
if self.supports_capability(Capability.MEDIA_INPUT_SOURCE):
|
||||
return self.get_attribute_value(
|
||||
Capability.MEDIA_INPUT_SOURCE, Attribute.SUPPORTED_INPUT_SOURCES
|
||||
)
|
||||
if self.supports_capability(Capability.SAMSUNG_VD_AUDIO_INPUT_SOURCE):
|
||||
return self.get_attribute_value(
|
||||
Capability.SAMSUNG_VD_AUDIO_INPUT_SOURCE,
|
||||
Attribute.SUPPORTED_INPUT_SOURCES,
|
||||
)
|
||||
return None
|
||||
|
||||
def _determine_features(self) -> MediaPlayerEntityFeature:
|
||||
flags = (
|
||||
@@ -120,7 +179,9 @@ class SmartThingsMediaPlayer(SmartThingsEntity, MediaPlayerEntity):
|
||||
flags |= (
|
||||
MediaPlayerEntityFeature.TURN_ON | MediaPlayerEntityFeature.TURN_OFF
|
||||
)
|
||||
if self.supports_capability(Capability.MEDIA_INPUT_SOURCE):
|
||||
if self.supports_capability(
|
||||
Capability.SAMSUNG_VD_MEDIA_INPUT_SOURCE
|
||||
) or self.supports_capability(Capability.MEDIA_INPUT_SOURCE):
|
||||
flags |= MediaPlayerEntityFeature.SELECT_SOURCE
|
||||
if self.supports_capability(Capability.MEDIA_PLAYBACK_SHUFFLE):
|
||||
flags |= MediaPlayerEntityFeature.SHUFFLE_SET
|
||||
@@ -209,11 +270,19 @@ class SmartThingsMediaPlayer(SmartThingsEntity, MediaPlayerEntity):
|
||||
|
||||
async def async_select_source(self, source: str) -> None:
|
||||
"""Select source."""
|
||||
await self.execute_device_command(
|
||||
Capability.MEDIA_INPUT_SOURCE,
|
||||
Command.SET_INPUT_SOURCE,
|
||||
argument=source,
|
||||
)
|
||||
smartthings_source = self._source_to_smartthings_id.get(source, source)
|
||||
if self.supports_capability(Capability.SAMSUNG_VD_MEDIA_INPUT_SOURCE):
|
||||
await self.execute_device_command(
|
||||
Capability.SAMSUNG_VD_MEDIA_INPUT_SOURCE,
|
||||
Command.SET_INPUT_SOURCE,
|
||||
argument=smartthings_source,
|
||||
)
|
||||
else:
|
||||
await self.execute_device_command(
|
||||
Capability.MEDIA_INPUT_SOURCE,
|
||||
Command.SET_INPUT_SOURCE,
|
||||
argument=smartthings_source,
|
||||
)
|
||||
|
||||
async def async_set_shuffle(self, shuffle: bool) -> None:
|
||||
"""Set shuffle mode."""
|
||||
@@ -309,29 +378,30 @@ class SmartThingsMediaPlayer(SmartThingsEntity, MediaPlayerEntity):
|
||||
@property
|
||||
def source(self) -> str | None:
|
||||
"""Input source."""
|
||||
if self.supports_capability(Capability.MEDIA_INPUT_SOURCE):
|
||||
return self.get_attribute_value(
|
||||
if self.supports_capability(Capability.SAMSUNG_VD_MEDIA_INPUT_SOURCE):
|
||||
raw = self.get_attribute_value(
|
||||
Capability.SAMSUNG_VD_MEDIA_INPUT_SOURCE, Attribute.INPUT_SOURCE
|
||||
)
|
||||
elif self.supports_capability(Capability.MEDIA_INPUT_SOURCE):
|
||||
raw = self.get_attribute_value(
|
||||
Capability.MEDIA_INPUT_SOURCE, Attribute.INPUT_SOURCE
|
||||
)
|
||||
if self.supports_capability(Capability.SAMSUNG_VD_AUDIO_INPUT_SOURCE):
|
||||
return self.get_attribute_value(
|
||||
elif self.supports_capability(Capability.SAMSUNG_VD_AUDIO_INPUT_SOURCE):
|
||||
raw = self.get_attribute_value(
|
||||
Capability.SAMSUNG_VD_AUDIO_INPUT_SOURCE, Attribute.INPUT_SOURCE
|
||||
)
|
||||
return None
|
||||
else:
|
||||
raw = None
|
||||
if raw is None:
|
||||
return None
|
||||
return MEDIA_SOURCE_ID_TO_HA_KEY.get(raw, raw)
|
||||
|
||||
@property
|
||||
def source_list(self) -> list[str] | None:
|
||||
"""List of input sources."""
|
||||
if self.supports_capability(Capability.MEDIA_INPUT_SOURCE):
|
||||
return self.get_attribute_value(
|
||||
Capability.MEDIA_INPUT_SOURCE, Attribute.SUPPORTED_INPUT_SOURCES
|
||||
)
|
||||
if self.supports_capability(Capability.SAMSUNG_VD_AUDIO_INPUT_SOURCE):
|
||||
return self.get_attribute_value(
|
||||
Capability.SAMSUNG_VD_AUDIO_INPUT_SOURCE,
|
||||
Attribute.SUPPORTED_INPUT_SOURCES,
|
||||
)
|
||||
return None
|
||||
if not self._source_to_smartthings_id:
|
||||
return None
|
||||
return list(self._source_to_smartthings_id)
|
||||
|
||||
@property
|
||||
def shuffle(self) -> bool | None:
|
||||
|
||||
@@ -197,6 +197,42 @@
|
||||
"name": "[%key:component::light::title%]"
|
||||
}
|
||||
},
|
||||
"media_player": {
|
||||
"media_player": {
|
||||
"state_attributes": {
|
||||
"source": {
|
||||
"state": {
|
||||
"am": "AM",
|
||||
"analog1": "Analog 1",
|
||||
"analog2": "Analog 2",
|
||||
"analog3": "Analog 3",
|
||||
"aux": "AUX",
|
||||
"bluetooth": "Bluetooth",
|
||||
"cd": "CD",
|
||||
"coaxial": "Coaxial",
|
||||
"digital": "Digital",
|
||||
"digital_input": "Digital input",
|
||||
"digital_tv": "Digital TV",
|
||||
"fm": "FM",
|
||||
"hdmi": "HDMI",
|
||||
"hdmi1": "HDMI 1",
|
||||
"hdmi2": "HDMI 2",
|
||||
"hdmi3": "HDMI 3",
|
||||
"hdmi4": "HDMI 4",
|
||||
"hdmi5": "HDMI 5",
|
||||
"hdmi6": "HDMI 6",
|
||||
"melon": "Melon",
|
||||
"network": "Network",
|
||||
"optical": "Optical",
|
||||
"phono": "Phono",
|
||||
"usb": "USB",
|
||||
"wifi": "Wi-Fi",
|
||||
"youtube": "YouTube"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"number": {
|
||||
"cool_select_plus_temperature": {
|
||||
"name": "CoolSelect+ temperature"
|
||||
|
||||
@@ -36,11 +36,11 @@
|
||||
"name": "PlayStation 4"
|
||||
},
|
||||
{
|
||||
"id": "HDMI4",
|
||||
"id": "HDMI2",
|
||||
"name": "HT-CT370"
|
||||
},
|
||||
{
|
||||
"id": "HDMI4",
|
||||
"id": "HDMI3",
|
||||
"name": "HT-CT370"
|
||||
}
|
||||
],
|
||||
@@ -53,7 +53,7 @@
|
||||
},
|
||||
"mediaInputSource": {
|
||||
"supportedInputSources": {
|
||||
"value": ["digitalTv", "HDMI1", "HDMI4", "HDMI4"],
|
||||
"value": ["digitalTv", "HDMI1", "HDMI2", "HDMI3"],
|
||||
"timestamp": "2021-10-16T15:18:11.622Z"
|
||||
},
|
||||
"inputSource": {
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <MediaPlayerEntityFeature: 284045>,
|
||||
'translation_key': None,
|
||||
'translation_key': 'media_player',
|
||||
'unique_id': '01b28624-5907-c8bc-0325-8ad23f03a637_main',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
@@ -64,8 +64,8 @@
|
||||
'source_list': list([
|
||||
'wifi',
|
||||
'bluetooth',
|
||||
'HDMI1',
|
||||
'HDMI2',
|
||||
'hdmi1',
|
||||
'hdmi2',
|
||||
'digital',
|
||||
]),
|
||||
}),
|
||||
@@ -94,7 +94,7 @@
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <MediaPlayerEntityFeature: 23949>,
|
||||
'translation_key': None,
|
||||
'translation_key': 'media_player',
|
||||
'unique_id': 'afcf3b91-0000-1111-2222-ddff2a0a6577_main',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
@@ -111,8 +111,8 @@
|
||||
'source_list': list([
|
||||
'wifi',
|
||||
'bluetooth',
|
||||
'HDMI1',
|
||||
'HDMI2',
|
||||
'hdmi1',
|
||||
'hdmi2',
|
||||
'digital',
|
||||
]),
|
||||
'supported_features': <MediaPlayerEntityFeature: 23949>,
|
||||
@@ -159,7 +159,7 @@
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <MediaPlayerEntityFeature: 318477>,
|
||||
'translation_key': None,
|
||||
'translation_key': 'media_player',
|
||||
'unique_id': 'c9276e43-fe3c-88c3-1dcc-2eb79e292b8c_main',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
@@ -216,7 +216,7 @@
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <MediaPlayerEntityFeature: 21517>,
|
||||
'translation_key': None,
|
||||
'translation_key': 'media_player',
|
||||
'unique_id': 'c85fced9-c474-4a47-93c2-037cc7829536_main',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
@@ -273,7 +273,7 @@
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <MediaPlayerEntityFeature: 21901>,
|
||||
'translation_key': None,
|
||||
'translation_key': 'media_player',
|
||||
'unique_id': '0d94e5db-8501-2355-eb4f-214163702cac_main',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
@@ -286,7 +286,7 @@
|
||||
'is_volume_muted': False,
|
||||
'media_artist': '',
|
||||
'media_title': '',
|
||||
'source': 'HDMI1',
|
||||
'source': 'hdmi1',
|
||||
'supported_features': <MediaPlayerEntityFeature: 21901>,
|
||||
'volume_level': 0.17,
|
||||
}),
|
||||
@@ -331,7 +331,7 @@
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <MediaPlayerEntityFeature: 1420>,
|
||||
'translation_key': None,
|
||||
'translation_key': 'media_player',
|
||||
'unique_id': 'a75cb1e1-03fd-3c77-ca9f-d4e56c4096c6_main',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
@@ -359,10 +359,10 @@
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'source_list': list([
|
||||
'digitalTv',
|
||||
'HDMI1',
|
||||
'HDMI4',
|
||||
'HDMI4',
|
||||
'digital_tv',
|
||||
'hdmi1',
|
||||
'hdmi2',
|
||||
'hdmi3',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
@@ -390,7 +390,7 @@
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <MediaPlayerEntityFeature: 23997>,
|
||||
'translation_key': None,
|
||||
'translation_key': 'media_player',
|
||||
'unique_id': '4588d2d9-a8cf-40f4-9a0b-ed5dfbaccda1_main',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
@@ -401,12 +401,12 @@
|
||||
'device_class': 'tv',
|
||||
'friendly_name': '[TV] Samsung 8 Series (49)',
|
||||
'is_volume_muted': True,
|
||||
'source': 'HDMI1',
|
||||
'source': 'hdmi1',
|
||||
'source_list': list([
|
||||
'digitalTv',
|
||||
'HDMI1',
|
||||
'HDMI4',
|
||||
'HDMI4',
|
||||
'digital_tv',
|
||||
'hdmi1',
|
||||
'hdmi2',
|
||||
'hdmi3',
|
||||
]),
|
||||
'supported_features': <MediaPlayerEntityFeature: 23997>,
|
||||
'volume_level': 0.13,
|
||||
|
||||
@@ -15,11 +15,13 @@ from homeassistant.components.media_player import (
|
||||
ATTR_MEDIA_VOLUME_MUTED,
|
||||
DOMAIN as MEDIA_PLAYER_DOMAIN,
|
||||
SERVICE_SELECT_SOURCE,
|
||||
MediaPlayerEntityFeature,
|
||||
RepeatMode,
|
||||
)
|
||||
from homeassistant.components.smartthings.const import MAIN
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_SUPPORTED_FEATURES,
|
||||
SERVICE_MEDIA_NEXT_TRACK,
|
||||
SERVICE_MEDIA_PAUSE,
|
||||
SERVICE_MEDIA_PLAY,
|
||||
@@ -325,7 +327,7 @@ async def test_select_source(
|
||||
devices: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test media player stop command."""
|
||||
"""Test media player select source command."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
await hass.services.async_call(
|
||||
@@ -339,10 +341,105 @@ async def test_select_source(
|
||||
Capability.MEDIA_INPUT_SOURCE,
|
||||
Command.SET_INPUT_SOURCE,
|
||||
MAIN,
|
||||
"digital",
|
||||
argument="digital",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_fixture", ["vd_stv_2017_k"])
|
||||
async def test_vd_capability_select_source(
|
||||
hass: HomeAssistant,
|
||||
devices: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test media player select source command using Samsung VD capability."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
state = hass.states.get("media_player.tv_samsung_8_series_49")
|
||||
assert state is not None
|
||||
assert MediaPlayerEntityFeature.SELECT_SOURCE in MediaPlayerEntityFeature(
|
||||
state.attributes[ATTR_SUPPORTED_FEATURES]
|
||||
)
|
||||
|
||||
await hass.services.async_call(
|
||||
MEDIA_PLAYER_DOMAIN,
|
||||
SERVICE_SELECT_SOURCE,
|
||||
{
|
||||
ATTR_ENTITY_ID: "media_player.tv_samsung_8_series_49",
|
||||
ATTR_INPUT_SOURCE: "hdmi1",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
devices.execute_device_command.assert_called_once_with(
|
||||
"4588d2d9-a8cf-40f4-9a0b-ed5dfbaccda1",
|
||||
Capability.SAMSUNG_VD_MEDIA_INPUT_SOURCE,
|
||||
Command.SET_INPUT_SOURCE,
|
||||
MAIN,
|
||||
argument="HDMI1",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_fixture", ["vd_stv_2017_k"])
|
||||
async def test_select_source_legacy_raw_id(
|
||||
hass: HomeAssistant,
|
||||
devices: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test select source falls back to raw ID when not found in source map.
|
||||
|
||||
When a legacy/raw source ID (e.g. 'HDMI1') is passed directly instead of the
|
||||
slugified HA name ('hdmi1'), it should be forwarded as-is to SmartThings.
|
||||
"""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
await hass.services.async_call(
|
||||
MEDIA_PLAYER_DOMAIN,
|
||||
SERVICE_SELECT_SOURCE,
|
||||
{
|
||||
ATTR_ENTITY_ID: "media_player.tv_samsung_8_series_49",
|
||||
ATTR_INPUT_SOURCE: "HDMI1",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
devices.execute_device_command.assert_called_once_with(
|
||||
"4588d2d9-a8cf-40f4-9a0b-ed5dfbaccda1",
|
||||
Capability.SAMSUNG_VD_MEDIA_INPUT_SOURCE,
|
||||
Command.SET_INPUT_SOURCE,
|
||||
MAIN,
|
||||
argument="HDMI1",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_fixture", ["vd_stv_2017_k"])
|
||||
async def test_vd_capability_source_update(
|
||||
hass: HomeAssistant,
|
||||
devices: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test source state update using Samsung VD capability."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
state = hass.states.get("media_player.tv_samsung_8_series_49")
|
||||
assert state is not None
|
||||
assert MediaPlayerEntityFeature.SELECT_SOURCE in MediaPlayerEntityFeature(
|
||||
state.attributes[ATTR_SUPPORTED_FEATURES]
|
||||
)
|
||||
assert state.attributes[ATTR_INPUT_SOURCE] == "hdmi1"
|
||||
|
||||
# Update source to dtv
|
||||
await trigger_update(
|
||||
hass,
|
||||
devices,
|
||||
"4588d2d9-a8cf-40f4-9a0b-ed5dfbaccda1",
|
||||
Capability.SAMSUNG_VD_MEDIA_INPUT_SOURCE,
|
||||
Attribute.INPUT_SOURCE,
|
||||
"dtv",
|
||||
)
|
||||
|
||||
state = hass.states.get("media_player.tv_samsung_8_series_49")
|
||||
assert state is not None
|
||||
assert state.attributes[ATTR_INPUT_SOURCE] == "digital_tv"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_fixture", ["hw_q80r_soundbar"])
|
||||
@pytest.mark.parametrize(
|
||||
("shuffle", "argument"),
|
||||
|
||||
Reference in New Issue
Block a user