Add support for InputSelector trait (#35753)

This commit is contained in:
Joakim Plate 2020-07-19 01:07:32 +02:00 committed by GitHub
parent 2354d0117b
commit 6fa04aa3e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 81 additions and 74 deletions

View file

@ -61,7 +61,6 @@ YOUTUBE_PLAYER_SUPPORT = (
| SUPPORT_PLAY
| SUPPORT_SHUFFLE_SET
| SUPPORT_SELECT_SOUND_MODE
| SUPPORT_SELECT_SOURCE
| SUPPORT_SEEK
)
@ -397,6 +396,7 @@ class DemoTVShowPlayer(AbstractDemoPlayer):
self._cur_episode = 1
self._episode_count = 13
self._source = "dvd"
self._source_list = ["dvd", "youtube"]
@property
def media_content_id(self):
@ -448,6 +448,11 @@ class DemoTVShowPlayer(AbstractDemoPlayer):
"""Return the current input source."""
return self._source
@property
def source_list(self):
"""List of available sources."""
return self._source_list
@property
def supported_features(self):
"""Flag media player features that are supported."""

View file

@ -86,6 +86,7 @@ TRAIT_TEMPERATURE_SETTING = f"{PREFIX_TRAITS}TemperatureSetting"
TRAIT_LOCKUNLOCK = f"{PREFIX_TRAITS}LockUnlock"
TRAIT_FANSPEED = f"{PREFIX_TRAITS}FanSpeed"
TRAIT_MODES = f"{PREFIX_TRAITS}Modes"
TRAIT_INPUTSELECTOR = f"{PREFIX_TRAITS}InputSelector"
TRAIT_OPENCLOSE = f"{PREFIX_TRAITS}OpenClose"
TRAIT_VOLUME = f"{PREFIX_TRAITS}Volume"
TRAIT_ARMDISARM = f"{PREFIX_TRAITS}ArmDisarm"
@ -112,6 +113,7 @@ COMMAND_THERMOSTAT_SET_MODE = f"{PREFIX_COMMANDS}ThermostatSetMode"
COMMAND_LOCKUNLOCK = f"{PREFIX_COMMANDS}LockUnlock"
COMMAND_FANSPEED = f"{PREFIX_COMMANDS}SetFanSpeed"
COMMAND_MODES = f"{PREFIX_COMMANDS}SetModes"
COMMAND_INPUT = f"{PREFIX_COMMANDS}SetInput"
COMMAND_OPENCLOSE = f"{PREFIX_COMMANDS}OpenClose"
COMMAND_SET_VOLUME = f"{PREFIX_COMMANDS}setVolume"
COMMAND_VOLUME_RELATIVE = f"{PREFIX_COMMANDS}volumeRelative"
@ -1213,7 +1215,6 @@ class ModesTrait(_Trait):
commands = [COMMAND_MODES]
SYNONYMS = {
"input source": ["input source", "input", "source"],
"sound mode": ["sound mode", "effects"],
"option": ["option", "setting", "mode", "value"],
}
@ -1230,10 +1231,7 @@ class ModesTrait(_Trait):
if domain != media_player.DOMAIN:
return False
return (
features & media_player.SUPPORT_SELECT_SOURCE
or features & media_player.SUPPORT_SELECT_SOUND_MODE
)
return features & media_player.SUPPORT_SELECT_SOUND_MODE
def sync_attributes(self):
"""Return mode attributes for a sync request."""
@ -1266,13 +1264,6 @@ class ModesTrait(_Trait):
attrs = self.state.attributes
modes = []
if self.state.domain == media_player.DOMAIN:
if media_player.ATTR_INPUT_SOURCE_LIST in attrs:
modes.append(
_generate(
"input source", attrs[media_player.ATTR_INPUT_SOURCE_LIST]
)
)
if media_player.ATTR_SOUND_MODE_LIST in attrs:
modes.append(
_generate("sound mode", attrs[media_player.ATTR_SOUND_MODE_LIST])
@ -1294,11 +1285,6 @@ class ModesTrait(_Trait):
mode_settings = {}
if self.state.domain == media_player.DOMAIN:
if media_player.ATTR_INPUT_SOURCE_LIST in attrs:
mode_settings["input source"] = attrs.get(
media_player.ATTR_INPUT_SOURCE
)
if media_player.ATTR_SOUND_MODE_LIST in attrs:
mode_settings["sound mode"] = attrs.get(media_player.ATTR_SOUND_MODE)
elif self.state.domain == input_select.DOMAIN:
@ -1352,21 +1338,8 @@ class ModesTrait(_Trait):
)
return
requested_source = settings.get("input source")
sound_mode = settings.get("sound mode")
if requested_source:
await self.hass.services.async_call(
media_player.DOMAIN,
media_player.SERVICE_SELECT_SOURCE,
{
ATTR_ENTITY_ID: self.state.entity_id,
media_player.ATTR_INPUT_SOURCE: requested_source,
},
blocking=True,
context=data.context,
)
if sound_mode:
await self.hass.services.async_call(
media_player.DOMAIN,
@ -1380,6 +1353,61 @@ class ModesTrait(_Trait):
)
@register_trait
class InputSelectorTrait(_Trait):
"""Trait to set modes.
https://developers.google.com/assistant/smarthome/traits/inputselector
"""
name = TRAIT_INPUTSELECTOR
commands = [COMMAND_INPUT]
SYNONYMS = {}
@staticmethod
def supported(domain, features, device_class):
"""Test if state is supported."""
if domain == media_player.DOMAIN and (
features & media_player.SUPPORT_SELECT_SOURCE
):
return True
return False
def sync_attributes(self):
"""Return mode attributes for a sync request."""
attrs = self.state.attributes
inputs = [
{"key": source, "names": [{"name_synonym": [source], "lang": "en"}]}
for source in attrs.get(media_player.ATTR_INPUT_SOURCE_LIST, [])
]
payload = {"availableInputs": inputs, "orderedInputs": True}
return payload
def query_attributes(self):
"""Return current modes."""
attrs = self.state.attributes
return {"currentInput": attrs.get(media_player.ATTR_INPUT_SOURCE, "")}
async def execute(self, command, data, params, challenge):
"""Execute an SetInputSource command."""
requested_source = params.get("newInput")
await self.hass.services.async_call(
media_player.DOMAIN,
media_player.SERVICE_SELECT_SOURCE,
{
ATTR_ENTITY_ID: self.state.entity_id,
media_player.ATTR_INPUT_SOURCE: requested_source,
},
blocking=True,
context=data.context,
)
@register_trait
class OpenCloseTrait(_Trait):
"""Trait to open and close a cover.

View file

@ -190,6 +190,7 @@ DEMO_DEVICES = [
"id": "media_player.lounge_room",
"name": {"name": "Lounge room"},
"traits": [
"action.devices.traits.InputSelector",
"action.devices.traits.OnOff",
"action.devices.traits.Modes",
"action.devices.traits.TransportControl",

View file

@ -1313,14 +1313,14 @@ async def test_fan_speed(hass):
assert calls[0].data == {"entity_id": "fan.living_room_fan", "speed": "medium"}
async def test_modes_media_player(hass):
"""Test Media Player Mode trait."""
async def test_inputselector(hass):
"""Test input selector trait."""
assert helpers.get_google_type(media_player.DOMAIN, None) is not None
assert trait.ModesTrait.supported(
assert trait.InputSelectorTrait.supported(
media_player.DOMAIN, media_player.SUPPORT_SELECT_SOURCE, None
)
trt = trait.ModesTrait(
trt = trait.InputSelectorTrait(
hass,
State(
"media_player.living_room",
@ -1340,56 +1340,29 @@ async def test_modes_media_player(hass):
attribs = trt.sync_attributes()
assert attribs == {
"availableModes": [
"availableInputs": [
{"key": "media", "names": [{"name_synonym": ["media"], "lang": "en"}]},
{"key": "game", "names": [{"name_synonym": ["game"], "lang": "en"}]},
{
"name": "input source",
"name_values": [
{"name_synonym": ["input source", "input", "source"], "lang": "en"}
],
"settings": [
{
"setting_name": "media",
"setting_values": [
{"setting_synonym": ["media"], "lang": "en"}
],
},
{
"setting_name": "game",
"setting_values": [{"setting_synonym": ["game"], "lang": "en"}],
},
{
"setting_name": "chromecast",
"setting_values": [
{"setting_synonym": ["chromecast"], "lang": "en"}
],
},
{
"setting_name": "plex",
"setting_values": [{"setting_synonym": ["plex"], "lang": "en"}],
},
],
"ordered": False,
}
]
"key": "chromecast",
"names": [{"name_synonym": ["chromecast"], "lang": "en"}],
},
{"key": "plex", "names": [{"name_synonym": ["plex"], "lang": "en"}]},
],
"orderedInputs": True,
}
assert trt.query_attributes() == {
"currentModeSettings": {"input source": "game"},
"on": True,
"currentInput": "game",
}
assert trt.can_execute(
trait.COMMAND_MODES, params={"updateModeSettings": {"input source": "media"}},
)
assert trt.can_execute(trait.COMMAND_INPUT, params={"newInput": "media"},)
calls = async_mock_service(
hass, media_player.DOMAIN, media_player.SERVICE_SELECT_SOURCE
)
await trt.execute(
trait.COMMAND_MODES,
BASIC_DATA,
{"updateModeSettings": {"input source": "media"}},
{},
trait.COMMAND_INPUT, BASIC_DATA, {"newInput": "media"}, {},
)
assert len(calls) == 1