Implement EqualizerController in Alexa for media_player. (#30159)
This commit is contained in:
parent
4ea42c2479
commit
25f78dd1a9
5 changed files with 248 additions and 1 deletions
|
@ -120,7 +120,20 @@ class AlexaCapability:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def configuration():
|
def configuration():
|
||||||
"""Return the configuration object."""
|
"""Return the configuration object.
|
||||||
|
|
||||||
|
Applicable to the ThermostatController, SecurityControlPanel, ModeController, RangeController,
|
||||||
|
and EventDetectionSensor.
|
||||||
|
"""
|
||||||
|
return []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def configurations():
|
||||||
|
"""Return the configurations object.
|
||||||
|
|
||||||
|
The plural configurations object is different that the singular configuration object.
|
||||||
|
Applicable to EqualizerController interface.
|
||||||
|
"""
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -177,6 +190,11 @@ class AlexaCapability:
|
||||||
if configuration:
|
if configuration:
|
||||||
result["configuration"] = configuration
|
result["configuration"] = configuration
|
||||||
|
|
||||||
|
# The plural configurations object is different than the singular configuration object above.
|
||||||
|
configurations = self.configurations()
|
||||||
|
if configurations:
|
||||||
|
result["configurations"] = configurations
|
||||||
|
|
||||||
semantics = self.semantics()
|
semantics = self.semantics()
|
||||||
if semantics:
|
if semantics:
|
||||||
result["semantics"] = semantics
|
result["semantics"] = semantics
|
||||||
|
@ -1356,3 +1374,55 @@ class AlexaEventDetectionSensor(AlexaCapability):
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AlexaEqualizerController(AlexaCapability):
|
||||||
|
"""Implements Alexa.EqualizerController.
|
||||||
|
|
||||||
|
https://developer.amazon.com/en-US/docs/alexa/device-apis/alexa-equalizercontroller.html
|
||||||
|
"""
|
||||||
|
|
||||||
|
def name(self):
|
||||||
|
"""Return the Alexa API name of this interface."""
|
||||||
|
return "Alexa.EqualizerController"
|
||||||
|
|
||||||
|
def properties_supported(self):
|
||||||
|
"""Return what properties this entity supports.
|
||||||
|
|
||||||
|
Either bands, mode or both can be specified. Only mode is supported at this time.
|
||||||
|
"""
|
||||||
|
return [{"name": "mode"}]
|
||||||
|
|
||||||
|
def get_property(self, name):
|
||||||
|
"""Read and return a property."""
|
||||||
|
if name != "mode":
|
||||||
|
raise UnsupportedProperty(name)
|
||||||
|
|
||||||
|
sound_mode = self.entity.attributes.get(media_player.ATTR_SOUND_MODE)
|
||||||
|
if sound_mode and sound_mode.upper() in (
|
||||||
|
"MOVIE",
|
||||||
|
"MUSIC",
|
||||||
|
"NIGHT",
|
||||||
|
"SPORT",
|
||||||
|
"TV",
|
||||||
|
):
|
||||||
|
return sound_mode.upper()
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def configurations(self):
|
||||||
|
"""Return the sound modes supported in the configurations object.
|
||||||
|
|
||||||
|
Valid Values for modes are: MOVIE, MUSIC, NIGHT, SPORT, TV.
|
||||||
|
"""
|
||||||
|
configurations = None
|
||||||
|
sound_mode_list = self.entity.attributes.get(media_player.ATTR_SOUND_MODE_LIST)
|
||||||
|
if sound_mode_list:
|
||||||
|
supported_sound_modes = []
|
||||||
|
for sound_mode in sound_mode_list:
|
||||||
|
if sound_mode.upper() in ("MOVIE", "MUSIC", "NIGHT", "SPORT", "TV"):
|
||||||
|
supported_sound_modes.append({"name": sound_mode.upper()})
|
||||||
|
|
||||||
|
configurations = {"modes": {"supported": supported_sound_modes}}
|
||||||
|
|
||||||
|
return configurations
|
||||||
|
|
|
@ -196,3 +196,11 @@ class Inputs:
|
||||||
"video3": "VIDEO 3",
|
"video3": "VIDEO 3",
|
||||||
"xbox": "XBOX",
|
"xbox": "XBOX",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VALID_SOUND_MODE_MAP = {
|
||||||
|
"movie": "MOVIE",
|
||||||
|
"music": "MUSIC",
|
||||||
|
"night": "NIGHT",
|
||||||
|
"sport": "SPORT",
|
||||||
|
"tv": "TV",
|
||||||
|
}
|
||||||
|
|
|
@ -42,6 +42,7 @@ from .capabilities import (
|
||||||
AlexaContactSensor,
|
AlexaContactSensor,
|
||||||
AlexaDoorbellEventSource,
|
AlexaDoorbellEventSource,
|
||||||
AlexaEndpointHealth,
|
AlexaEndpointHealth,
|
||||||
|
AlexaEqualizerController,
|
||||||
AlexaEventDetectionSensor,
|
AlexaEventDetectionSensor,
|
||||||
AlexaInputController,
|
AlexaInputController,
|
||||||
AlexaLockController,
|
AlexaLockController,
|
||||||
|
@ -522,6 +523,9 @@ class MediaPlayerCapabilities(AlexaEntity):
|
||||||
if supported & media_player.const.SUPPORT_PLAY_MEDIA:
|
if supported & media_player.const.SUPPORT_PLAY_MEDIA:
|
||||||
yield AlexaChannelController(self.entity)
|
yield AlexaChannelController(self.entity)
|
||||||
|
|
||||||
|
if supported & media_player.const.SUPPORT_SELECT_SOUND_MODE:
|
||||||
|
yield AlexaEqualizerController(self.entity)
|
||||||
|
|
||||||
yield AlexaEndpointHealth(self.hass, self.entity)
|
yield AlexaEndpointHealth(self.hass, self.entity)
|
||||||
yield Alexa(self.hass)
|
yield Alexa(self.hass)
|
||||||
|
|
||||||
|
|
|
@ -1352,3 +1352,43 @@ async def async_api_seek(hass, config, directive, context):
|
||||||
return directive.response(
|
return directive.response(
|
||||||
name="StateReport", namespace="Alexa.SeekController", payload=payload
|
name="StateReport", namespace="Alexa.SeekController", payload=payload
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@HANDLERS.register(("Alexa.EqualizerController", "SetMode"))
|
||||||
|
async def async_api_set_eq_mode(hass, config, directive, context):
|
||||||
|
"""Process a SetMode request for EqualizerController."""
|
||||||
|
mode = directive.payload["mode"]
|
||||||
|
entity = directive.entity
|
||||||
|
data = {ATTR_ENTITY_ID: entity.entity_id}
|
||||||
|
|
||||||
|
sound_mode_list = entity.attributes.get(media_player.const.ATTR_SOUND_MODE_LIST)
|
||||||
|
if sound_mode_list and mode.lower() in sound_mode_list:
|
||||||
|
data[media_player.const.ATTR_SOUND_MODE] = mode.lower()
|
||||||
|
else:
|
||||||
|
msg = "failed to map sound mode {} to a mode on {}".format(
|
||||||
|
mode, entity.entity_id
|
||||||
|
)
|
||||||
|
raise AlexaInvalidValueError(msg)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
entity.domain,
|
||||||
|
media_player.SERVICE_SELECT_SOUND_MODE,
|
||||||
|
data,
|
||||||
|
blocking=False,
|
||||||
|
context=context,
|
||||||
|
)
|
||||||
|
|
||||||
|
return directive.response()
|
||||||
|
|
||||||
|
|
||||||
|
@HANDLERS.register(("Alexa.EqualizerController", "AdjustBands"))
|
||||||
|
@HANDLERS.register(("Alexa.EqualizerController", "ResetBands"))
|
||||||
|
@HANDLERS.register(("Alexa.EqualizerController", "SetBands"))
|
||||||
|
async def async_api_bands_directive(hass, config, directive, context):
|
||||||
|
"""Handle an AdjustBands, ResetBands, SetBands request.
|
||||||
|
|
||||||
|
Only mode directives are currently supported for the EqualizerController.
|
||||||
|
"""
|
||||||
|
# Currently bands directives are not supported.
|
||||||
|
msg = "Entity does not support directive"
|
||||||
|
raise AlexaInvalidDirectiveError(msg)
|
||||||
|
|
|
@ -9,6 +9,7 @@ from homeassistant.components.media_player.const import (
|
||||||
SUPPORT_PLAY_MEDIA,
|
SUPPORT_PLAY_MEDIA,
|
||||||
SUPPORT_PREVIOUS_TRACK,
|
SUPPORT_PREVIOUS_TRACK,
|
||||||
SUPPORT_SEEK,
|
SUPPORT_SEEK,
|
||||||
|
SUPPORT_SELECT_SOUND_MODE,
|
||||||
SUPPORT_SELECT_SOURCE,
|
SUPPORT_SELECT_SOURCE,
|
||||||
SUPPORT_STOP,
|
SUPPORT_STOP,
|
||||||
SUPPORT_TURN_OFF,
|
SUPPORT_TURN_OFF,
|
||||||
|
@ -2912,3 +2913,127 @@ async def test_input_number_float(hass):
|
||||||
"value",
|
"value",
|
||||||
instance="input_number.value",
|
instance="input_number.value",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_media_player_eq_modes(hass):
|
||||||
|
"""Test media player discovery with sound mode list."""
|
||||||
|
device = (
|
||||||
|
"media_player.test",
|
||||||
|
"on",
|
||||||
|
{
|
||||||
|
"friendly_name": "Test media player",
|
||||||
|
"supported_features": SUPPORT_SELECT_SOUND_MODE,
|
||||||
|
"sound_mode": "tv",
|
||||||
|
"sound_mode_list": ["movie", "music", "night", "sport", "tv", "rocknroll"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
appliance = await discovery_test(device, hass)
|
||||||
|
|
||||||
|
assert appliance["endpointId"] == "media_player#test"
|
||||||
|
assert appliance["friendlyName"] == "Test media player"
|
||||||
|
|
||||||
|
capabilities = assert_endpoint_capabilities(
|
||||||
|
appliance,
|
||||||
|
"Alexa",
|
||||||
|
"Alexa.EqualizerController",
|
||||||
|
"Alexa.PowerController",
|
||||||
|
"Alexa.EndpointHealth",
|
||||||
|
)
|
||||||
|
|
||||||
|
eq_capability = get_capability(capabilities, "Alexa.EqualizerController")
|
||||||
|
assert eq_capability is not None
|
||||||
|
assert "modes" in eq_capability["configurations"]
|
||||||
|
|
||||||
|
eq_modes = eq_capability["configurations"]["modes"]
|
||||||
|
assert {"name": "rocknroll"} not in eq_modes["supported"]
|
||||||
|
assert {"name": "ROCKNROLL"} not in eq_modes["supported"]
|
||||||
|
|
||||||
|
for mode in ("MOVIE", "MUSIC", "NIGHT", "SPORT", "TV"):
|
||||||
|
assert {"name": mode} in eq_modes["supported"]
|
||||||
|
|
||||||
|
call, _ = await assert_request_calls_service(
|
||||||
|
"Alexa.EqualizerController",
|
||||||
|
"SetMode",
|
||||||
|
"media_player#test",
|
||||||
|
"media_player.select_sound_mode",
|
||||||
|
hass,
|
||||||
|
payload={"mode": mode},
|
||||||
|
)
|
||||||
|
assert call.data["sound_mode"] == mode.lower()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_media_player_sound_mode_list_none(hass):
|
||||||
|
"""Test EqualizerController bands directive not supported."""
|
||||||
|
device = (
|
||||||
|
"media_player.test",
|
||||||
|
"on",
|
||||||
|
{
|
||||||
|
"friendly_name": "Test media player",
|
||||||
|
"supported_features": SUPPORT_SELECT_SOUND_MODE,
|
||||||
|
"sound_mode": "unknown",
|
||||||
|
"sound_mode_list": None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
appliance = await discovery_test(device, hass)
|
||||||
|
assert appliance["endpointId"] == "media_player#test"
|
||||||
|
assert appliance["friendlyName"] == "Test media player"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_media_player_eq_bands_not_supported(hass):
|
||||||
|
"""Test EqualizerController bands directive not supported."""
|
||||||
|
device = (
|
||||||
|
"media_player.test_bands",
|
||||||
|
"on",
|
||||||
|
{
|
||||||
|
"friendly_name": "Test media player",
|
||||||
|
"supported_features": SUPPORT_SELECT_SOUND_MODE,
|
||||||
|
"sound_mode": "tv",
|
||||||
|
"sound_mode_list": ["movie", "music", "night", "sport", "tv", "rocknroll"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await discovery_test(device, hass)
|
||||||
|
|
||||||
|
context = Context()
|
||||||
|
|
||||||
|
# Test for SetBands Error
|
||||||
|
request = get_new_request(
|
||||||
|
"Alexa.EqualizerController", "SetBands", "media_player#test_bands"
|
||||||
|
)
|
||||||
|
request["directive"]["payload"] = {"bands": [{"name": "BASS", "value": -2}]}
|
||||||
|
msg = await smart_home.async_handle_message(hass, DEFAULT_CONFIG, request, context)
|
||||||
|
|
||||||
|
assert "event" in msg
|
||||||
|
msg = msg["event"]
|
||||||
|
assert msg["header"]["name"] == "ErrorResponse"
|
||||||
|
assert msg["header"]["namespace"] == "Alexa"
|
||||||
|
assert msg["payload"]["type"] == "INVALID_DIRECTIVE"
|
||||||
|
|
||||||
|
# Test for AdjustBands Error
|
||||||
|
request = get_new_request(
|
||||||
|
"Alexa.EqualizerController", "AdjustBands", "media_player#test_bands"
|
||||||
|
)
|
||||||
|
request["directive"]["payload"] = {
|
||||||
|
"bands": [{"name": "BASS", "levelDelta": 3, "levelDirection": "UP"}]
|
||||||
|
}
|
||||||
|
msg = await smart_home.async_handle_message(hass, DEFAULT_CONFIG, request, context)
|
||||||
|
|
||||||
|
assert "event" in msg
|
||||||
|
msg = msg["event"]
|
||||||
|
assert msg["header"]["name"] == "ErrorResponse"
|
||||||
|
assert msg["header"]["namespace"] == "Alexa"
|
||||||
|
assert msg["payload"]["type"] == "INVALID_DIRECTIVE"
|
||||||
|
|
||||||
|
# Test for ResetBands Error
|
||||||
|
request = get_new_request(
|
||||||
|
"Alexa.EqualizerController", "ResetBands", "media_player#test_bands"
|
||||||
|
)
|
||||||
|
request["directive"]["payload"] = {
|
||||||
|
"bands": [{"name": "BASS", "levelDelta": 3, "levelDirection": "UP"}]
|
||||||
|
}
|
||||||
|
msg = await smart_home.async_handle_message(hass, DEFAULT_CONFIG, request, context)
|
||||||
|
|
||||||
|
assert "event" in msg
|
||||||
|
msg = msg["event"]
|
||||||
|
assert msg["header"]["name"] == "ErrorResponse"
|
||||||
|
assert msg["header"]["namespace"] == "Alexa"
|
||||||
|
assert msg["payload"]["type"] == "INVALID_DIRECTIVE"
|
||||||
|
|
Loading…
Add table
Reference in a new issue