diff --git a/homeassistant/components/alexa/smart_home.py b/homeassistant/components/alexa/smart_home.py index 6721c0038e8..e8ce147472c 100644 --- a/homeassistant/components/alexa/smart_home.py +++ b/homeassistant/components/alexa/smart_home.py @@ -357,6 +357,11 @@ class _AlexaPlaybackController(_AlexaInterface): return 'Alexa.PlaybackController' +class _AlexaInputController(_AlexaInterface): + def name(self): + return 'Alexa.InputController' + + class _AlexaTemperatureSensor(_AlexaInterface): def name(self): return 'Alexa.TemperatureSensor' @@ -475,6 +480,9 @@ class _MediaPlayerCapabilities(_AlexaEntity): if supported & playback_features: yield _AlexaPlaybackController(self.entity) + if supported & media_player.SUPPORT_SELECT_SOURCE: + yield _AlexaInputController(self.entity) + @ENTITY_ADAPTERS.register(scene.DOMAIN) class _SceneCapabilities(_AlexaEntity): @@ -1081,6 +1089,41 @@ def async_api_set_volume(hass, config, request, entity): return api_message(request) +@HANDLERS.register(('Alexa.InputController', 'SelectInput')) +@extract_entity +@asyncio.coroutine +def async_api_select_input(hass, config, request, entity): + """Process a set input request.""" + media_input = request[API_PAYLOAD]['input'] + + # attempt to map the ALL UPPERCASE payload name to a source + source_list = entity.attributes[media_player.ATTR_INPUT_SOURCE_LIST] or [] + for source in source_list: + # response will always be space separated, so format the source in the + # most likely way to find a match + formatted_source = source.lower().replace('-', ' ').replace('_', ' ') + if formatted_source in media_input.lower(): + media_input = source + break + else: + msg = 'failed to map input {} to a media source on {}'.format( + media_input, entity.entity_id) + _LOGGER.error(msg) + return api_error( + request, error_type='INVALID_VALUE', error_message=msg) + + data = { + ATTR_ENTITY_ID: entity.entity_id, + media_player.ATTR_INPUT_SOURCE: media_input, + } + + yield from hass.services.async_call( + entity.domain, media_player.SERVICE_SELECT_SOURCE, + data, blocking=False) + + return api_message(request) + + @HANDLERS.register(('Alexa.Speaker', 'AdjustVolume')) @extract_entity @asyncio.coroutine diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index 7795bc85f1a..c91965a5396 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -1161,6 +1161,52 @@ def test_api_set_volume(hass): assert msg['header']['name'] == 'Response' +@asyncio.coroutine +@pytest.mark.parametrize( + "domain,payload,source_list,idx", [ + ('media_player', 'GAME CONSOLE', ['tv', 'game console'], 1), + ('media_player', 'SATELLITE TV', ['satellite-tv', 'game console'], 0), + ('media_player', 'SATELLITE TV', ['satellite_tv', 'game console'], 0), + ('media_player', 'BAD DEVICE', ['satellite_tv', 'game console'], None), + ] +) +def test_api_select_input(hass, domain, payload, source_list, idx): + """Test api set input process.""" + request = get_new_request( + 'Alexa.InputController', 'SelectInput', 'media_player#test') + + # add payload + request['directive']['payload']['input'] = payload + + # setup test devices + hass.states.async_set( + 'media_player.test', 'off', { + 'friendly_name': "Test media player", + 'source': 'unknown', + 'source_list': source_list, + }) + + call_media_player = async_mock_service(hass, domain, 'select_source') + + msg = yield from smart_home.async_handle_message( + hass, DEFAULT_CONFIG, request) + yield from hass.async_block_till_done() + + assert 'event' in msg + msg = msg['event'] + + # test where no source matches + if idx is None: + assert len(call_media_player) == 0 + assert msg['header']['name'] == 'ErrorResponse' + return + + assert len(call_media_player) == 1 + assert call_media_player[0].data['entity_id'] == 'media_player.test' + assert call_media_player[0].data['source'] == source_list[idx] + assert msg['header']['name'] == 'Response' + + @asyncio.coroutine @pytest.mark.parametrize( "result,adjust", [(0.7, '-5'), (0.8, '5'), (0, '-80')])