Add valid inputs to alexa InputController (#28483)

* Add supported Inputs for Alexa.InputController.

* Fixed Test.

* Added default parameter for get() per @quthla suggestion.

* Added additional tests, assets call data.

* Added additional tests, asserts call data.

* Accounted for space in input name, added tests to handle space.
This commit is contained in:
ochlocracy 2019-11-25 18:17:12 -05:00 committed by Paulus Schoutsen
parent f53812f261
commit 970a80216d
4 changed files with 225 additions and 6 deletions

View file

@ -35,6 +35,7 @@ from .const import (
DATE_FORMAT,
PERCENTAGE_FAN_MAP,
RANGE_FAN_MAP,
Inputs,
)
from .errors import UnsupportedProperty
@ -115,6 +116,11 @@ class AlexaCapability:
"""Return the Configuration object."""
return []
@staticmethod
def inputs():
"""Applicable only to media players."""
return []
@staticmethod
def supported_operations():
"""Return the supportedOperations object."""
@ -164,6 +170,10 @@ class AlexaCapability:
if supported_operations:
result["supportedOperations"] = supported_operations
inputs = self.inputs()
if inputs:
result["inputs"] = inputs
return result
def serialize_properties(self):
@ -531,6 +541,23 @@ class AlexaInputController(AlexaCapability):
"""Return the Alexa API name of this interface."""
return "Alexa.InputController"
def inputs(self):
"""Return the list of valid supported inputs."""
source_list = self.entity.attributes.get(
media_player.ATTR_INPUT_SOURCE_LIST, []
)
input_list = []
for source in source_list:
formatted_source = (
source.lower().replace("-", "").replace("_", "").replace(" ", "")
)
if formatted_source in Inputs.VALID_SOURCE_NAME_MAP.keys():
input_list.append(
{"name": Inputs.VALID_SOURCE_NAME_MAP[formatted_source]}
)
return input_list
class AlexaTemperatureSensor(AlexaCapability):
"""Implements Alexa.TemperatureSensor.

View file

@ -272,3 +272,84 @@ class Unit:
WEIGHT_OUNCES = "Alexa.Unit.Weight.Ounces"
WEIGHT_POUNDS = "Alexa.Unit.Weight.Pounds"
class Inputs:
"""Valid names for the InputController.
https://developer.amazon.com/docs/device-apis/alexa-property-schemas.html#input
"""
VALID_SOURCE_NAME_MAP = {
"aux": "AUX 1",
"aux1": "AUX 1",
"aux2": "AUX 2",
"aux3": "AUX 3",
"aux4": "AUX 4",
"aux5": "AUX 5",
"aux6": "AUX 6",
"aux7": "AUX 7",
"bluray": "BLURAY",
"cable": "CABLE",
"cd": "CD",
"coax": "COAX 1",
"coax1": "COAX 1",
"coax2": "COAX 2",
"composite": "COMPOSITE 1",
"composite1": "COMPOSITE 1",
"dvd": "DVD",
"game": "GAME",
"gameconsole": "GAME",
"hdradio": "HD RADIO",
"hdmi": "HDMI 1",
"hdmi1": "HDMI 1",
"hdmi2": "HDMI 2",
"hdmi3": "HDMI 3",
"hdmi4": "HDMI 4",
"hdmi5": "HDMI 5",
"hdmi6": "HDMI 6",
"hdmi7": "HDMI 7",
"hdmi8": "HDMI 8",
"hdmi9": "HDMI 9",
"hdmi10": "HDMI 10",
"hdmiarc": "HDMI ARC",
"input": "INPUT 1",
"input1": "INPUT 1",
"input2": "INPUT 2",
"input3": "INPUT 3",
"input4": "INPUT 4",
"input5": "INPUT 5",
"input6": "INPUT 6",
"input7": "INPUT 7",
"input8": "INPUT 8",
"input9": "INPUT 9",
"input10": "INPUT 10",
"ipod": "IPOD",
"line": "LINE 1",
"line1": "LINE 1",
"line2": "LINE 2",
"line3": "LINE 3",
"line4": "LINE 4",
"line5": "LINE 5",
"line6": "LINE 6",
"line7": "LINE 7",
"mediaplayer": "MEDIA PLAYER",
"optical": "OPTICAL 1",
"optical1": "OPTICAL 1",
"optical2": "OPTICAL 2",
"phono": "PHONO",
"playstation": "PLAYSTATION",
"playstation3": "PLAYSTATION 3",
"playstation4": "PLAYSTATION 4",
"satellite": "SATELLITE",
"satellitetv": "SATELLITE",
"smartcast": "SMARTCAST",
"tuner": "TUNER",
"tv": "TV",
"usbdac": "USB DAC",
"video": "VIDEO 1",
"video1": "VIDEO 1",
"video2": "VIDEO 2",
"video3": "VIDEO 3",
"xbox": "XBOX",
}

View file

@ -44,6 +44,7 @@ from .const import (
API_THERMOSTAT_MODES,
API_THERMOSTAT_PRESETS,
Cause,
Inputs,
PERCENTAGE_FAN_MAP,
RANGE_FAN_MAP,
SPEED_FAN_MAP,
@ -461,13 +462,20 @@ async def async_api_select_input(hass, config, directive, context):
media_input = directive.payload["input"]
entity = directive.entity
# attempt to map the ALL UPPERCASE payload name to a source
source_list = entity.attributes[media_player.const.ATTR_INPUT_SOURCE_LIST] or []
# Attempt to map the ALL UPPERCASE payload name to a source.
# Strips trailing 1 to match single input devices.
source_list = entity.attributes.get(media_player.const.ATTR_INPUT_SOURCE_LIST, [])
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():
formatted_source = (
source.lower().replace("-", "").replace("_", "").replace(" ", "")
)
media_input = media_input.lower().replace(" ", "")
if (
formatted_source in Inputs.VALID_SOURCE_NAME_MAP.keys()
and formatted_source == media_input
) or (
media_input.endswith("1") and formatted_source == media_input.rstrip("1")
):
media_input = source
break
else:

View file

@ -1015,6 +1015,109 @@ async def test_media_player_power(hass):
)
async def test_media_player_inputs(hass):
"""Test media player discovery with source list inputs."""
device = (
"media_player.test",
"on",
{
"friendly_name": "Test media player",
"supported_features": SUPPORT_SELECT_SOURCE,
"volume_level": 0.75,
"source_list": [
"foo",
"foo_2",
"hdmi",
"hdmi_2",
"hdmi-3",
"hdmi4",
"hdmi 5",
"HDMI 6",
"hdmi_arc",
"aux",
"input 1",
"tv",
],
},
)
appliance = await discovery_test(device, hass)
assert appliance["endpointId"] == "media_player#test"
assert appliance["displayCategories"][0] == "TV"
assert appliance["friendlyName"] == "Test media player"
capabilities = assert_endpoint_capabilities(
appliance,
"Alexa.InputController",
"Alexa.PowerController",
"Alexa.EndpointHealth",
)
input_capability = get_capability(capabilities, "Alexa.InputController")
assert input_capability is not None
assert {"name": "AUX"} not in input_capability["inputs"]
assert {"name": "AUX 1"} in input_capability["inputs"]
assert {"name": "HDMI 1"} in input_capability["inputs"]
assert {"name": "HDMI 2"} in input_capability["inputs"]
assert {"name": "HDMI 3"} in input_capability["inputs"]
assert {"name": "HDMI 4"} in input_capability["inputs"]
assert {"name": "HDMI 5"} in input_capability["inputs"]
assert {"name": "HDMI 6"} in input_capability["inputs"]
assert {"name": "HDMI ARC"} in input_capability["inputs"]
assert {"name": "FOO 1"} not in input_capability["inputs"]
assert {"name": "TV"} in input_capability["inputs"]
call, _ = await assert_request_calls_service(
"Alexa.InputController",
"SelectInput",
"media_player#test",
"media_player.select_source",
hass,
payload={"input": "HDMI 1"},
)
assert call.data["source"] == "hdmi"
call, _ = await assert_request_calls_service(
"Alexa.InputController",
"SelectInput",
"media_player#test",
"media_player.select_source",
hass,
payload={"input": "HDMI 2"},
)
assert call.data["source"] == "hdmi_2"
call, _ = await assert_request_calls_service(
"Alexa.InputController",
"SelectInput",
"media_player#test",
"media_player.select_source",
hass,
payload={"input": "HDMI 5"},
)
assert call.data["source"] == "hdmi 5"
call, _ = await assert_request_calls_service(
"Alexa.InputController",
"SelectInput",
"media_player#test",
"media_player.select_source",
hass,
payload={"input": "HDMI 6"},
)
assert call.data["source"] == "HDMI 6"
call, _ = await assert_request_calls_service(
"Alexa.InputController",
"SelectInput",
"media_player#test",
"media_player.select_source",
hass,
payload={"input": "TV"},
)
assert call.data["source"] == "tv"
async def test_media_player_speaker(hass):
"""Test media player discovery with device class speaker."""
device = (