Add vizio service to update a device setting (#36739)

* track all settings and add service to update a setting

* sort setting types

* reduce frequency of updates due to the increase in API calls per update

* change dict call to a get in case audio settings aren't available

unlikely to occur but less error prone

* Update if statement to be more consistent

* revert changes to track all settings and store in state machine

* revert one more change

* force setting_type and setting_name to lowercase to make it easier to understand how to make service call

* make service calls even simpler by attempting to transform certain parameters as much as possible
This commit is contained in:
Raman Gupta 2020-08-06 22:43:03 -04:00 committed by GitHub
parent 937d993a67
commit 297dd9bbc2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 104 additions and 25 deletions

View file

@ -27,6 +27,18 @@ from homeassistant.const import (
)
import homeassistant.helpers.config_validation as cv
SERVICE_UPDATE_SETTING = "update_setting"
ATTR_SETTING_TYPE = "setting_type"
ATTR_SETTING_NAME = "setting_name"
ATTR_NEW_VALUE = "new_value"
UPDATE_SETTING_SCHEMA = {
vol.Required(ATTR_SETTING_TYPE): cv.string,
vol.Required(ATTR_SETTING_NAME): cv.string,
vol.Required(ATTR_NEW_VALUE): vol.Or(vol.Coerce(int), cv.string),
}
CONF_ADDITIONAL_CONFIGS = "additional_configs"
CONF_APP_ID = "APP_ID"
CONF_APPS = "apps"
@ -66,6 +78,8 @@ SUPPORTED_COMMANDS = {
VIZIO_SOUND_MODE = "eq"
VIZIO_AUDIO_SETTINGS = "audio"
VIZIO_MUTE_ON = "on"
VIZIO_VOLUME = "volume"
VIZIO_MUTE = "mute"
# Since Vizio component relies on device class, this dict will ensure that changes to
# the values of DEVICE_CLASS_SPEAKER or DEVICE_CLASS_TV don't require changes to pyvizio.

View file

@ -1,7 +1,7 @@
"""Vizio SmartCast Device support."""
from datetime import timedelta
import logging
from typing import Any, Callable, Dict, List, Optional
from typing import Any, Callable, Dict, List, Optional, Union
from pyvizio import VizioAsync
from pyvizio.api.apps import find_app_name
@ -24,6 +24,7 @@ from homeassistant.const import (
STATE_ON,
)
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers import entity_platform
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect,
@ -41,16 +42,20 @@ from .const import (
DEVICE_ID,
DOMAIN,
ICON,
SERVICE_UPDATE_SETTING,
SUPPORTED_COMMANDS,
UPDATE_SETTING_SCHEMA,
VIZIO_AUDIO_SETTINGS,
VIZIO_DEVICE_CLASSES,
VIZIO_MUTE,
VIZIO_MUTE_ON,
VIZIO_SOUND_MODE,
VIZIO_VOLUME,
)
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=10)
SCAN_INTERVAL = timedelta(seconds=30)
PARALLEL_UPDATES = 0
@ -113,6 +118,10 @@ async def async_setup_entry(
entity = VizioDevice(config_entry, device, name, device_class)
async_add_entities([entity], update_before_add=True)
platform = entity_platform.current_platform.get()
platform.async_register_entity_service(
SERVICE_UPDATE_SETTING, UPDATE_SETTING_SCHEMA, "async_update_setting"
)
class VizioDevice(MediaPlayerEntity):
@ -203,10 +212,13 @@ class VizioDevice(MediaPlayerEntity):
audio_settings = await self._device.get_all_settings(
VIZIO_AUDIO_SETTINGS, log_api_exception=False
)
if audio_settings:
self._volume_level = float(audio_settings["volume"]) / self._max_volume
if "mute" in audio_settings:
self._is_volume_muted = audio_settings["mute"].lower() == VIZIO_MUTE_ON
self._volume_level = float(audio_settings[VIZIO_VOLUME]) / self._max_volume
if VIZIO_MUTE in audio_settings:
self._is_volume_muted = (
audio_settings[VIZIO_MUTE].lower() == VIZIO_MUTE_ON
)
else:
self._is_volume_muted = None
@ -274,6 +286,16 @@ class VizioDevice(MediaPlayerEntity):
self._volume_step = config_entry.options[CONF_VOLUME_STEP]
self._conf_apps.update(config_entry.options.get(CONF_APPS, {}))
async def async_update_setting(
self, setting_type: str, setting_name: str, new_value: Union[int, str]
) -> None:
"""Update a setting when update_setting service is called."""
await self._device.set_setting(
setting_type.lower().replace(" ", "_"),
setting_name.lower().replace(" ", "_"),
new_value,
)
async def async_added_to_hass(self):
"""Register callbacks when entity is added."""
# Register callback for when config entry is updated.

View file

@ -0,0 +1,15 @@
update_setting:
description: Update the value of a setting on a particular Vizio media player device.
fields:
entity_id:
description: Name of an entity to send command.
example: "media_player.vizio_smartcast"
setting_type:
description: The type of setting to be changed. Available types are listed in the `setting_types` property.
example: "audio"
setting_name:
description: The name of the setting to be changed. Available settings for a given setting_type are listed in the `<setting_type>_settings` property.
example: "eq"
new_value:
description: The new value for the setting
example: "Music"

View file

@ -41,6 +41,7 @@ from homeassistant.components.vizio.const import (
CONF_APPS,
CONF_VOLUME_STEP,
DOMAIN,
SERVICE_UPDATE_SETTING,
VIZIO_SCHEMA,
)
from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON, STATE_UNAVAILABLE
@ -174,13 +175,14 @@ async def _test_setup_speaker(
unique_id=UNIQUE_ID,
)
audio_settings = {
"volume": int(MAX_VOLUME[VIZIO_DEVICE_CLASS_SPEAKER] / 2),
"mute": "Off",
"eq": CURRENT_EQ,
}
async with _cm_for_test_setup_without_apps(
{
"volume": int(MAX_VOLUME[VIZIO_DEVICE_CLASS_SPEAKER] / 2),
"mute": "Off",
"eq": CURRENT_EQ,
},
vizio_power_state,
audio_settings, vizio_power_state,
):
with patch(
"homeassistant.components.vizio.media_player.VizioAsync.get_current_app_config",
@ -248,6 +250,7 @@ async def _test_setup_failure(hass: HomeAssistantType, config: str) -> None:
async def _test_service(
hass: HomeAssistantType,
domain: str,
vizio_func_name: str,
ha_service_name: str,
additional_service_data: Optional[Dict[str, Any]],
@ -263,7 +266,7 @@ async def _test_service(
f"homeassistant.components.vizio.media_player.VizioAsync.{vizio_func_name}"
) as service_call:
await hass.services.async_call(
MP_DOMAIN, ha_service_name, service_data=service_data, blocking=True,
domain, ha_service_name, service_data=service_data, blocking=True,
)
assert service_call.called
@ -347,29 +350,49 @@ async def test_services(
"""Test all Vizio media player entity services."""
await _test_setup_tv(hass, True)
await _test_service(hass, "pow_on", SERVICE_TURN_ON, None)
await _test_service(hass, "pow_off", SERVICE_TURN_OFF, None)
await _test_service(hass, MP_DOMAIN, "pow_on", SERVICE_TURN_ON, None)
await _test_service(hass, MP_DOMAIN, "pow_off", SERVICE_TURN_OFF, None)
await _test_service(
hass, "mute_on", SERVICE_VOLUME_MUTE, {ATTR_MEDIA_VOLUME_MUTED: True}
hass, MP_DOMAIN, "mute_on", SERVICE_VOLUME_MUTE, {ATTR_MEDIA_VOLUME_MUTED: True}
)
await _test_service(
hass, "mute_off", SERVICE_VOLUME_MUTE, {ATTR_MEDIA_VOLUME_MUTED: False}
hass,
MP_DOMAIN,
"mute_off",
SERVICE_VOLUME_MUTE,
{ATTR_MEDIA_VOLUME_MUTED: False},
)
await _test_service(
hass, "set_input", SERVICE_SELECT_SOURCE, {ATTR_INPUT_SOURCE: "USB"}, "USB"
hass,
MP_DOMAIN,
"set_input",
SERVICE_SELECT_SOURCE,
{ATTR_INPUT_SOURCE: "USB"},
"USB",
)
await _test_service(hass, "vol_up", SERVICE_VOLUME_UP, None)
await _test_service(hass, "vol_down", SERVICE_VOLUME_DOWN, None)
await _test_service(hass, MP_DOMAIN, "vol_up", SERVICE_VOLUME_UP, None)
await _test_service(hass, MP_DOMAIN, "vol_down", SERVICE_VOLUME_DOWN, None)
await _test_service(
hass, "vol_up", SERVICE_VOLUME_SET, {ATTR_MEDIA_VOLUME_LEVEL: 1}
hass, MP_DOMAIN, "vol_up", SERVICE_VOLUME_SET, {ATTR_MEDIA_VOLUME_LEVEL: 1}
)
await _test_service(
hass, "vol_down", SERVICE_VOLUME_SET, {ATTR_MEDIA_VOLUME_LEVEL: 0}
hass, MP_DOMAIN, "vol_down", SERVICE_VOLUME_SET, {ATTR_MEDIA_VOLUME_LEVEL: 0}
)
await _test_service(hass, "ch_up", SERVICE_MEDIA_NEXT_TRACK, None)
await _test_service(hass, "ch_down", SERVICE_MEDIA_PREVIOUS_TRACK, None)
await _test_service(hass, MP_DOMAIN, "ch_up", SERVICE_MEDIA_NEXT_TRACK, None)
await _test_service(hass, MP_DOMAIN, "ch_down", SERVICE_MEDIA_PREVIOUS_TRACK, None)
await _test_service(
hass, "set_setting", SERVICE_SELECT_SOUND_MODE, {ATTR_SOUND_MODE: "Music"}
hass,
MP_DOMAIN,
"set_setting",
SERVICE_SELECT_SOUND_MODE,
{ATTR_SOUND_MODE: "Music"},
)
await _test_service(
hass,
DOMAIN,
"set_setting",
SERVICE_UPDATE_SETTING,
{"setting_type": "Audio", "setting_name": "EQ", "new_value": "Music"},
)
@ -389,7 +412,9 @@ async def test_options_update(
entry=config_entry, options=new_options,
)
assert config_entry.options == updated_options
await _test_service(hass, "vol_up", SERVICE_VOLUME_UP, None, num=VOLUME_STEP)
await _test_service(
hass, MP_DOMAIN, "vol_up", SERVICE_VOLUME_UP, None, num=VOLUME_STEP
)
async def _test_update_availability_switch(
@ -474,6 +499,7 @@ async def test_setup_with_apps(
await _test_service(
hass,
MP_DOMAIN,
"launch_app",
SERVICE_SELECT_SOURCE,
{ATTR_INPUT_SOURCE: CURRENT_APP},
@ -550,6 +576,7 @@ async def test_setup_with_apps_additional_apps_config(
await _test_service(
hass,
MP_DOMAIN,
"launch_app",
SERVICE_SELECT_SOURCE,
{ATTR_INPUT_SOURCE: "Netflix"},
@ -557,6 +584,7 @@ async def test_setup_with_apps_additional_apps_config(
)
await _test_service(
hass,
MP_DOMAIN,
"launch_app_config",
SERVICE_SELECT_SOURCE,
{ATTR_INPUT_SOURCE: CURRENT_APP},