mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 03:07:37 +00:00
Support Next/Previous for InputSelector (#38378)
* Support Next/Previous for InputSelector * Update homeassistant/components/google_assistant/trait.py Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io> * Adjust to match new version of _next_selected Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
This commit is contained in:
parent
aaad986002
commit
39e6bca682
@ -97,6 +97,7 @@ ERR_NOT_SUPPORTED = "notSupported"
|
||||
ERR_PROTOCOL_ERROR = "protocolError"
|
||||
ERR_UNKNOWN_ERROR = "unknownError"
|
||||
ERR_FUNCTION_NOT_SUPPORTED = "functionNotSupported"
|
||||
ERR_UNSUPPORTED_INPUT = "unsupportedInput"
|
||||
|
||||
ERR_ALREADY_DISARMED = "alreadyDisarmed"
|
||||
ERR_ALREADY_ARMED = "alreadyArmed"
|
||||
|
@ -1,5 +1,6 @@
|
||||
"""Implement the Google Smart Home traits."""
|
||||
import logging
|
||||
from typing import List, Optional
|
||||
|
||||
from homeassistant.components import (
|
||||
alarm_control_panel,
|
||||
@ -68,6 +69,7 @@ from .const import (
|
||||
ERR_CHALLENGE_NOT_SETUP,
|
||||
ERR_FUNCTION_NOT_SUPPORTED,
|
||||
ERR_NOT_SUPPORTED,
|
||||
ERR_UNSUPPORTED_INPUT,
|
||||
ERR_VALUE_OUT_OF_RANGE,
|
||||
)
|
||||
from .error import ChallengeNeeded, SmartHomeError
|
||||
@ -114,6 +116,8 @@ 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_NEXT_INPUT = f"{PREFIX_COMMANDS}NextInput"
|
||||
COMMAND_PREVIOUS_INPUT = f"{PREFIX_COMMANDS}PreviousInput"
|
||||
COMMAND_OPENCLOSE = f"{PREFIX_COMMANDS}OpenClose"
|
||||
COMMAND_SET_VOLUME = f"{PREFIX_COMMANDS}setVolume"
|
||||
COMMAND_VOLUME_RELATIVE = f"{PREFIX_COMMANDS}volumeRelative"
|
||||
@ -145,6 +149,20 @@ def _google_temp_unit(units):
|
||||
return "C"
|
||||
|
||||
|
||||
def _next_selected(items: List[str], selected: Optional[str]) -> Optional[str]:
|
||||
"""Return the next item in a item list starting at given value.
|
||||
|
||||
If selected is missing in items, None is returned
|
||||
"""
|
||||
try:
|
||||
index = items.index(selected)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
next_item = 0 if index == len(items) - 1 else index + 1
|
||||
return items[next_item]
|
||||
|
||||
|
||||
class _Trait:
|
||||
"""Represents a Trait inside Google Assistant skill."""
|
||||
|
||||
@ -1395,7 +1413,7 @@ class InputSelectorTrait(_Trait):
|
||||
"""
|
||||
|
||||
name = TRAIT_INPUTSELECTOR
|
||||
commands = [COMMAND_INPUT]
|
||||
commands = [COMMAND_INPUT, COMMAND_NEXT_INPUT, COMMAND_PREVIOUS_INPUT]
|
||||
|
||||
SYNONYMS = {}
|
||||
|
||||
@ -1428,7 +1446,20 @@ class InputSelectorTrait(_Trait):
|
||||
|
||||
async def execute(self, command, data, params, challenge):
|
||||
"""Execute an SetInputSource command."""
|
||||
sources = self.state.attributes.get(media_player.ATTR_INPUT_SOURCE_LIST) or []
|
||||
source = self.state.attributes.get(media_player.ATTR_INPUT_SOURCE)
|
||||
|
||||
if command == COMMAND_INPUT:
|
||||
requested_source = params.get("newInput")
|
||||
elif command == COMMAND_NEXT_INPUT:
|
||||
requested_source = _next_selected(sources, source)
|
||||
elif command == COMMAND_PREVIOUS_INPUT:
|
||||
requested_source = _next_selected(list(reversed(sources)), source)
|
||||
else:
|
||||
raise SmartHomeError(ERR_NOT_SUPPORTED, "Unsupported command")
|
||||
|
||||
if requested_source not in sources:
|
||||
raise SmartHomeError(ERR_UNSUPPORTED_INPUT, "Unsupported input")
|
||||
|
||||
await self.hass.services.async_call(
|
||||
media_player.DOMAIN,
|
||||
|
@ -24,6 +24,7 @@ from homeassistant.components import (
|
||||
)
|
||||
from homeassistant.components.climate import const as climate
|
||||
from homeassistant.components.google_assistant import const, error, helpers, trait
|
||||
from homeassistant.components.google_assistant.error import SmartHomeError
|
||||
from homeassistant.components.humidifier import const as humidifier
|
||||
from homeassistant.config import async_process_ha_core_config
|
||||
from homeassistant.const import (
|
||||
@ -1428,6 +1429,87 @@ async def test_inputselector(hass):
|
||||
assert calls[0].data == {"entity_id": "media_player.living_room", "source": "media"}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"sources,source,source_next,source_prev",
|
||||
[
|
||||
(["a"], "a", "a", "a"),
|
||||
(["a", "b"], "a", "b", "b"),
|
||||
(["a", "b", "c"], "a", "b", "c"),
|
||||
],
|
||||
)
|
||||
async def test_inputselector_nextprev(hass, sources, source, source_next, source_prev):
|
||||
"""Test input selector trait."""
|
||||
trt = trait.InputSelectorTrait(
|
||||
hass,
|
||||
State(
|
||||
"media_player.living_room",
|
||||
media_player.STATE_PLAYING,
|
||||
attributes={
|
||||
media_player.ATTR_INPUT_SOURCE_LIST: sources,
|
||||
media_player.ATTR_INPUT_SOURCE: source,
|
||||
},
|
||||
),
|
||||
BASIC_CONFIG,
|
||||
)
|
||||
|
||||
assert trt.can_execute("action.devices.commands.NextInput", params={})
|
||||
assert trt.can_execute("action.devices.commands.PreviousInput", params={})
|
||||
|
||||
calls = async_mock_service(
|
||||
hass, media_player.DOMAIN, media_player.SERVICE_SELECT_SOURCE
|
||||
)
|
||||
await trt.execute(
|
||||
"action.devices.commands.NextInput", BASIC_DATA, {}, {},
|
||||
)
|
||||
await trt.execute(
|
||||
"action.devices.commands.PreviousInput", BASIC_DATA, {}, {},
|
||||
)
|
||||
|
||||
assert len(calls) == 2
|
||||
assert calls[0].data == {
|
||||
"entity_id": "media_player.living_room",
|
||||
"source": source_next,
|
||||
}
|
||||
assert calls[1].data == {
|
||||
"entity_id": "media_player.living_room",
|
||||
"source": source_prev,
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"sources,source", [(None, "a"), (["a", "b"], None), (["a", "b"], "c")]
|
||||
)
|
||||
async def test_inputselector_nextprev_invalid(hass, sources, source):
|
||||
"""Test input selector trait."""
|
||||
trt = trait.InputSelectorTrait(
|
||||
hass,
|
||||
State(
|
||||
"media_player.living_room",
|
||||
media_player.STATE_PLAYING,
|
||||
attributes={
|
||||
media_player.ATTR_INPUT_SOURCE_LIST: sources,
|
||||
media_player.ATTR_INPUT_SOURCE: source,
|
||||
},
|
||||
),
|
||||
BASIC_CONFIG,
|
||||
)
|
||||
|
||||
with pytest.raises(SmartHomeError):
|
||||
await trt.execute(
|
||||
"action.devices.commands.NextInput", BASIC_DATA, {}, {},
|
||||
)
|
||||
|
||||
with pytest.raises(SmartHomeError):
|
||||
await trt.execute(
|
||||
"action.devices.commands.PreviousInput", BASIC_DATA, {}, {},
|
||||
)
|
||||
|
||||
with pytest.raises(SmartHomeError):
|
||||
await trt.execute(
|
||||
"action.devices.commands.InvalidCommand", BASIC_DATA, {}, {},
|
||||
)
|
||||
|
||||
|
||||
async def test_modes_input_select(hass):
|
||||
"""Test Input Select Mode trait."""
|
||||
assert helpers.get_google_type(input_select.DOMAIN, None) is not None
|
||||
|
Loading…
x
Reference in New Issue
Block a user