diff --git a/homeassistant/components/google_assistant/const.py b/homeassistant/components/google_assistant/const.py index 47122979173..c1314aeaa41 100644 --- a/homeassistant/components/google_assistant/const.py +++ b/homeassistant/components/google_assistant/const.py @@ -8,6 +8,7 @@ from homeassistant.components import ( fan, group, input_boolean, + input_select, light, lock, media_player, @@ -44,6 +45,7 @@ DEFAULT_EXPOSED_DOMAINS = [ "fan", "group", "input_boolean", + "input_select", "light", "media_player", "scene", @@ -113,6 +115,7 @@ DOMAIN_TO_GOOGLE_TYPES = { fan.DOMAIN: TYPE_FAN, group.DOMAIN: TYPE_SWITCH, input_boolean.DOMAIN: TYPE_SWITCH, + input_select.DOMAIN: TYPE_SENSOR, light.DOMAIN: TYPE_LIGHT, lock.DOMAIN: TYPE_LOCK, media_player.DOMAIN: TYPE_SETTOP, diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py index 3da91b2b612..0a90e542e51 100644 --- a/homeassistant/components/google_assistant/trait.py +++ b/homeassistant/components/google_assistant/trait.py @@ -9,6 +9,7 @@ from homeassistant.components import ( fan, group, input_boolean, + input_select, light, lock, media_player, @@ -1131,11 +1132,15 @@ class ModesTrait(_Trait): SYNONYMS = { "input source": ["input source", "input", "source"], "sound mode": ["sound mode", "effects"], + "option": ["option", "setting", "mode", "value"], } @staticmethod def supported(domain, features, device_class): """Test if state is supported.""" + if domain == input_select.DOMAIN: + return True + if domain != media_player.DOMAIN: return False @@ -1174,15 +1179,20 @@ class ModesTrait(_Trait): attrs = self.state.attributes modes = [] - if media_player.ATTR_INPUT_SOURCE_LIST in attrs: - modes.append( - _generate("input source", attrs[media_player.ATTR_INPUT_SOURCE_LIST]) - ) + 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]) - ) + if media_player.ATTR_SOUND_MODE_LIST in attrs: + modes.append( + _generate("sound mode", attrs[media_player.ATTR_SOUND_MODE_LIST]) + ) + elif self.state.domain == input_select.DOMAIN: + modes.append(_generate("option", attrs[input_select.ATTR_OPTIONS])) payload = {"availableModes": modes} @@ -1194,11 +1204,16 @@ class ModesTrait(_Trait): response = {} mode_settings = {} - if media_player.ATTR_INPUT_SOURCE_LIST in attrs: - mode_settings["input source"] = attrs.get(media_player.ATTR_INPUT_SOURCE) + 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) + 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: + mode_settings["option"] = self.state.state if mode_settings: response["on"] = self.state.state != STATE_OFF @@ -1210,6 +1225,28 @@ class ModesTrait(_Trait): async def execute(self, command, data, params, challenge): """Execute an SetModes command.""" settings = params.get("updateModeSettings") + + if self.state.domain == input_select.DOMAIN: + option = params["updateModeSettings"]["option"] + await self.hass.services.async_call( + input_select.DOMAIN, + input_select.SERVICE_SELECT_OPTION, + { + ATTR_ENTITY_ID: self.state.entity_id, + input_select.ATTR_OPTION: option, + }, + blocking=True, + context=data.context, + ) + + return + if self.state.domain != media_player.DOMAIN: + _LOGGER.info( + "Received an Options command for unrecognised domain %s", + self.state.domain, + ) + return + requested_source = settings.get("input source") sound_mode = settings.get("sound mode") diff --git a/tests/components/google_assistant/test_trait.py b/tests/components/google_assistant/test_trait.py index 801f4c1b5ba..adce4ef877b 100644 --- a/tests/components/google_assistant/test_trait.py +++ b/tests/components/google_assistant/test_trait.py @@ -11,6 +11,7 @@ from homeassistant.components import ( fan, group, input_boolean, + input_select, light, lock, media_player, @@ -1267,8 +1268,8 @@ async def test_fan_speed(hass): assert calls[0].data == {"entity_id": "fan.living_room_fan", "speed": "medium"} -async def test_modes(hass): - """Test Mode trait.""" +async def test_modes_media_player(hass): + """Test Media Player Mode trait.""" assert helpers.get_google_type(media_player.DOMAIN, None) is not None assert trait.ModesTrait.supported( media_player.DOMAIN, media_player.SUPPORT_SELECT_SOURCE, None @@ -1351,6 +1352,72 @@ async def test_modes(hass): assert calls[0].data == {"entity_id": "media_player.living_room", "source": "media"} +async def test_modes_input_select(hass): + """Test Input Select Mode trait.""" + assert helpers.get_google_type(input_select.DOMAIN, None) is not None + assert trait.ModesTrait.supported(input_select.DOMAIN, None, None) + + trt = trait.ModesTrait( + hass, + State( + "input_select.bla", + "abc", + attributes={input_select.ATTR_OPTIONS: ["abc", "123", "xyz"]}, + ), + BASIC_CONFIG, + ) + + attribs = trt.sync_attributes() + assert attribs == { + "availableModes": [ + { + "name": "option", + "name_values": [ + { + "name_synonym": ["option", "setting", "mode", "value"], + "lang": "en", + } + ], + "settings": [ + { + "setting_name": "abc", + "setting_values": [{"setting_synonym": ["abc"], "lang": "en"}], + }, + { + "setting_name": "123", + "setting_values": [{"setting_synonym": ["123"], "lang": "en"}], + }, + { + "setting_name": "xyz", + "setting_values": [{"setting_synonym": ["xyz"], "lang": "en"}], + }, + ], + "ordered": False, + } + ] + } + + assert trt.query_attributes() == { + "currentModeSettings": {"option": "abc"}, + "on": True, + "online": True, + } + + assert trt.can_execute( + trait.COMMAND_MODES, params={"updateModeSettings": {"option": "xyz"}}, + ) + + calls = async_mock_service( + hass, input_select.DOMAIN, input_select.SERVICE_SELECT_OPTION + ) + await trt.execute( + trait.COMMAND_MODES, BASIC_DATA, {"updateModeSettings": {"option": "xyz"}}, {}, + ) + + assert len(calls) == 1 + assert calls[0].data == {"entity_id": "input_select.bla", "option": "xyz"} + + async def test_sound_modes(hass): """Test Mode trait.""" assert helpers.get_google_type(media_player.DOMAIN, None) is not None