Allow all sources and sound modes to be selected in google assistant (#30418)

* Simplify media player source list

Google don't need a whitelisted set of modes anymore. So let's just forward any mode that we have.

* Report current mode with the setting name, not a synonym

* Refactor mode generation to support other modes

* Support sound mode as mode as well

* Adjust failing test now with sound modes
This commit is contained in:
Joakim Plate
2020-01-04 00:39:03 +01:00
committed by GitHub
parent d6e230e66b
commit 8dc57a3700
3 changed files with 160 additions and 162 deletions

View File

@@ -1121,98 +1121,9 @@ class ModesTrait(_Trait):
name = TRAIT_MODES
commands = [COMMAND_MODES]
# Google requires specific mode names and settings. Here is the full list.
# https://developers.google.com/actions/reference/smarthome/traits/modes
# All settings are mapped here as of 2018-11-28 and can be used for other
# entity types.
HA_TO_GOOGLE = {media_player.ATTR_INPUT_SOURCE: "input source"}
SUPPORTED_MODE_SETTINGS = {
"xsmall": ["xsmall", "extra small", "min", "minimum", "tiny", "xs"],
"small": ["small", "half"],
"large": ["large", "big", "full"],
"xlarge": ["extra large", "xlarge", "xl"],
"Cool": ["cool", "rapid cool", "rapid cooling"],
"Heat": ["heat"],
"Low": ["low"],
"Medium": ["medium", "med", "mid", "half"],
"High": ["high"],
"Auto": ["auto", "automatic"],
"Bake": ["bake"],
"Roast": ["roast"],
"Convection Bake": ["convection bake", "convect bake"],
"Convection Roast": ["convection roast", "convect roast"],
"Favorite": ["favorite"],
"Broil": ["broil"],
"Warm": ["warm"],
"Off": ["off"],
"On": ["on"],
"Normal": [
"normal",
"normal mode",
"normal setting",
"standard",
"schedule",
"original",
"default",
"old settings",
],
"None": ["none"],
"Tap Cold": ["tap cold"],
"Cold Warm": ["cold warm"],
"Hot": ["hot"],
"Extra Hot": ["extra hot"],
"Eco": ["eco"],
"Wool": ["wool", "fleece"],
"Turbo": ["turbo"],
"Rinse": ["rinse", "rinsing", "rinse wash"],
"Away": ["away", "holiday"],
"maximum": ["maximum"],
"media player": ["media player"],
"chromecast": ["chromecast"],
"tv": [
"tv",
"television",
"tv position",
"television position",
"watching tv",
"watching tv position",
"entertainment",
"entertainment position",
],
"am fm": ["am fm", "am radio", "fm radio"],
"internet radio": ["internet radio"],
"satellite": ["satellite"],
"game console": ["game console"],
"antifrost": ["antifrost", "anti-frost"],
"boost": ["boost"],
"Clock": ["clock"],
"Message": ["message"],
"Messages": ["messages"],
"News": ["news"],
"Disco": ["disco"],
"antifreeze": ["antifreeze", "anti-freeze", "anti freeze"],
"balanced": ["balanced", "normal"],
"swing": ["swing"],
"media": ["media", "media mode"],
"panic": ["panic"],
"ring": ["ring"],
"frozen": ["frozen", "rapid frozen", "rapid freeze"],
"cotton": ["cotton", "cottons"],
"blend": ["blend", "mix"],
"baby wash": ["baby wash"],
"synthetics": ["synthetic", "synthetics", "compose"],
"hygiene": ["hygiene", "sterilization"],
"smart": ["smart", "intelligent", "intelligence"],
"comfortable": ["comfortable", "comfort"],
"manual": ["manual"],
"energy saving": ["energy saving"],
"sleep": ["sleep"],
"quick wash": ["quick wash", "fast wash"],
"cold": ["cold"],
"airsupply": ["airsupply", "air supply"],
"dehumidification": ["dehumidication", "dehumidify"],
"game": ["game", "game mode"],
SYNONYMS = {
"input source": ["input source", "input", "source"],
"sound mode": ["sound mode", "effects"],
}
@staticmethod
@@ -1221,42 +1132,51 @@ class ModesTrait(_Trait):
if domain != media_player.DOMAIN:
return False
return features & media_player.SUPPORT_SELECT_SOURCE
return (
features & media_player.SUPPORT_SELECT_SOURCE
or features & media_player.SUPPORT_SELECT_SOUND_MODE
)
def sync_attributes(self):
"""Return mode attributes for a sync request."""
sources_list = self.state.attributes.get(
media_player.ATTR_INPUT_SOURCE_LIST, []
)
modes = []
sources = {}
if sources_list:
sources = {
"name": self.HA_TO_GOOGLE.get(media_player.ATTR_INPUT_SOURCE),
"name_values": [{"name_synonym": ["input source"], "lang": "en"}],
def _generate(name, settings):
mode = {
"name": name,
"name_values": [
{"name_synonym": self.SYNONYMS.get(name, [name]), "lang": "en"}
],
"settings": [],
"ordered": False,
}
for source in sources_list:
if source in self.SUPPORTED_MODE_SETTINGS:
src = source
synonyms = self.SUPPORTED_MODE_SETTINGS.get(src)
elif source.lower() in self.SUPPORTED_MODE_SETTINGS:
src = source.lower()
synonyms = self.SUPPORTED_MODE_SETTINGS.get(src)
else:
continue
sources["settings"].append(
for setting in settings:
mode["settings"].append(
{
"setting_name": src,
"setting_values": [{"setting_synonym": synonyms, "lang": "en"}],
"setting_name": setting,
"setting_values": [
{
"setting_synonym": self.SYNONYMS.get(
setting, [setting]
),
"lang": "en",
}
],
}
)
if sources:
modes.append(sources)
return mode
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 media_player.ATTR_SOUND_MODE_LIST in attrs:
modes.append(
_generate("sound mode", attrs[media_player.ATTR_SOUND_MODE_LIST])
)
payload = {"availableModes": modes}
return payload
@@ -1267,14 +1187,12 @@ class ModesTrait(_Trait):
response = {}
mode_settings = {}
if attrs.get(media_player.ATTR_INPUT_SOURCE_LIST):
mode_settings.update(
{
media_player.ATTR_INPUT_SOURCE: attrs.get(
media_player.ATTR_INPUT_SOURCE
)
}
)
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 mode_settings:
response["on"] = self.state.state != STATE_OFF
response["online"] = True
@@ -1285,25 +1203,32 @@ class ModesTrait(_Trait):
async def execute(self, command, data, params, challenge):
"""Execute an SetModes command."""
settings = params.get("updateModeSettings")
requested_source = settings.get(
self.HA_TO_GOOGLE.get(media_player.ATTR_INPUT_SOURCE)
)
requested_source = settings.get("input source")
sound_mode = settings.get("sound mode")
if requested_source:
for src in self.state.attributes.get(media_player.ATTR_INPUT_SOURCE_LIST):
if src.lower() == requested_source.lower():
source = src
await self.hass.services.async_call(
media_player.DOMAIN,
media_player.SERVICE_SELECT_SOURCE,
{
ATTR_ENTITY_ID: self.state.entity_id,
media_player.ATTR_INPUT_SOURCE: requested_source,
},
blocking=True,
context=data.context,
)
await self.hass.services.async_call(
media_player.DOMAIN,
media_player.SERVICE_SELECT_SOURCE,
{
ATTR_ENTITY_ID: self.state.entity_id,
media_player.ATTR_INPUT_SOURCE: source,
},
blocking=True,
context=data.context,
)
if sound_mode:
await self.hass.services.async_call(
media_player.DOMAIN,
media_player.SERVICE_SELECT_SOUND_MODE,
{
ATTR_ENTITY_ID: self.state.entity_id,
media_player.ATTR_SOUND_MODE: sound_mode,
},
blocking=True,
context=data.context,
)
@register_trait