Add support for Vizio sound mode (#33200)

* add sound mode support for devices that support it

* make setting and unsetting flag better

* move eq and audio settings into constants

* fix missed statement to use constant instead of hardcoded string

* further fixes based on review

* bump pyvizio version to include newly identified app
This commit is contained in:
Raman Gupta 2020-04-02 22:48:19 -04:00 committed by GitHub
parent f25321e010
commit 081b822d25
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 79 additions and 11 deletions

View file

@ -63,6 +63,9 @@ SUPPORTED_COMMANDS = {
), ),
} }
VIZIO_SOUND_MODE = "eq"
VIZIO_AUDIO_SETTINGS = "audio"
# Since Vizio component relies on device class, this dict will ensure that changes to # 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. # the values of DEVICE_CLASS_SPEAKER or DEVICE_CLASS_TV don't require changes to pyvizio.
VIZIO_DEVICE_CLASSES = { VIZIO_DEVICE_CLASSES = {

View file

@ -2,7 +2,7 @@
"domain": "vizio", "domain": "vizio",
"name": "VIZIO SmartCast", "name": "VIZIO SmartCast",
"documentation": "https://www.home-assistant.io/integrations/vizio", "documentation": "https://www.home-assistant.io/integrations/vizio",
"requirements": ["pyvizio==0.1.44"], "requirements": ["pyvizio==0.1.45"],
"dependencies": [], "dependencies": [],
"codeowners": ["@raman325"], "codeowners": ["@raman325"],
"config_flow": true, "config_flow": true,

View file

@ -9,6 +9,7 @@ from pyvizio.const import APP_HOME, APPS, INPUT_APPS, NO_APP_RUNNING, UNKNOWN_AP
from homeassistant.components.media_player import ( from homeassistant.components.media_player import (
DEVICE_CLASS_SPEAKER, DEVICE_CLASS_SPEAKER,
SUPPORT_SELECT_SOUND_MODE,
MediaPlayerDevice, MediaPlayerDevice,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
@ -41,7 +42,9 @@ from .const import (
DOMAIN, DOMAIN,
ICON, ICON,
SUPPORTED_COMMANDS, SUPPORTED_COMMANDS,
VIZIO_AUDIO_SETTINGS,
VIZIO_DEVICE_CLASSES, VIZIO_DEVICE_CLASSES,
VIZIO_SOUND_MODE,
) )
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -133,6 +136,8 @@ class VizioDevice(MediaPlayerDevice):
self._current_input = None self._current_input = None
self._current_app = None self._current_app = None
self._current_app_config = None self._current_app_config = None
self._current_sound_mode = None
self._available_sound_modes = None
self._available_inputs = [] self._available_inputs = []
self._available_apps = [] self._available_apps = []
self._conf_apps = config_entry.options.get(CONF_APPS, {}) self._conf_apps = config_entry.options.get(CONF_APPS, {})
@ -191,17 +196,29 @@ class VizioDevice(MediaPlayerDevice):
self._current_app = None self._current_app = None
self._current_app_config = None self._current_app_config = None
self._available_apps = None self._available_apps = None
self._current_sound_mode = None
self._available_sound_modes = None
return return
self._state = STATE_ON self._state = STATE_ON
audio_settings = await self._device.get_all_audio_settings( audio_settings = await self._device.get_all_settings(
log_api_exception=False VIZIO_AUDIO_SETTINGS, log_api_exception=False
) )
if audio_settings is not None: if audio_settings is not None:
self._volume_level = float(audio_settings["volume"]) / self._max_volume self._volume_level = float(audio_settings["volume"]) / self._max_volume
self._is_muted = audio_settings["mute"].lower() == "on" self._is_muted = audio_settings["mute"].lower() == "on"
if VIZIO_SOUND_MODE in audio_settings:
self._supported_commands |= SUPPORT_SELECT_SOUND_MODE
self._current_sound_mode = audio_settings[VIZIO_SOUND_MODE]
if self._available_sound_modes is None:
self._available_sound_modes = await self._device.get_setting_options(
VIZIO_AUDIO_SETTINGS, VIZIO_SOUND_MODE
)
else:
self._supported_commands ^= SUPPORT_SELECT_SOUND_MODE
input_ = await self._device.get_current_input(log_api_exception=False) input_ = await self._device.get_current_input(log_api_exception=False)
if input_ is not None: if input_ is not None:
self._current_input = input_ self._current_input = input_
@ -367,7 +384,7 @@ class VizioDevice(MediaPlayerDevice):
return self._config_entry.unique_id return self._config_entry.unique_id
@property @property
def device_info(self): def device_info(self) -> Dict[str, Any]:
"""Return device registry information.""" """Return device registry information."""
return { return {
"identifiers": {(DOMAIN, self._config_entry.unique_id)}, "identifiers": {(DOMAIN, self._config_entry.unique_id)},
@ -378,10 +395,27 @@ class VizioDevice(MediaPlayerDevice):
} }
@property @property
def device_class(self): def device_class(self) -> str:
"""Return device class for entity.""" """Return device class for entity."""
return self._device_class return self._device_class
@property
def sound_mode(self) -> Optional[str]:
"""Name of the current sound mode."""
return self._current_sound_mode
@property
def sound_mode_list(self) -> Optional[List[str]]:
"""List of available sound modes."""
return self._available_sound_modes
async def async_select_sound_mode(self, sound_mode):
"""Select sound mode."""
if sound_mode in self._available_sound_modes:
await self._device.set_setting(
VIZIO_AUDIO_SETTINGS, VIZIO_SOUND_MODE, sound_mode
)
async def async_turn_on(self) -> None: async def async_turn_on(self) -> None:
"""Turn the device on.""" """Turn the device on."""
await self._device.pow_on() await self._device.pow_on()

View file

@ -1738,7 +1738,7 @@ pyversasense==0.0.6
pyvesync==1.1.0 pyvesync==1.1.0
# homeassistant.components.vizio # homeassistant.components.vizio
pyvizio==0.1.44 pyvizio==0.1.45
# homeassistant.components.velux # homeassistant.components.velux
pyvlx==0.2.12 pyvlx==0.2.12

View file

@ -650,7 +650,7 @@ pyvera==0.3.7
pyvesync==1.1.0 pyvesync==1.1.0
# homeassistant.components.vizio # homeassistant.components.vizio
pyvizio==0.1.44 pyvizio==0.1.45
# homeassistant.components.html5 # homeassistant.components.html5
pywebpush==1.9.2 pywebpush==1.9.2

View file

@ -8,7 +8,9 @@ from .const import (
APP_LIST, APP_LIST,
CH_TYPE, CH_TYPE,
CURRENT_APP_CONFIG, CURRENT_APP_CONFIG,
CURRENT_EQ,
CURRENT_INPUT, CURRENT_INPUT,
EQ_LIST,
INPUT_LIST, INPUT_LIST,
INPUT_LIST_WITH_APPS, INPUT_LIST_WITH_APPS,
MODEL, MODEL,
@ -135,11 +137,15 @@ def vizio_update_fixture():
"homeassistant.components.vizio.media_player.VizioAsync.can_connect_with_auth_check", "homeassistant.components.vizio.media_player.VizioAsync.can_connect_with_auth_check",
return_value=True, return_value=True,
), patch( ), patch(
"homeassistant.components.vizio.media_player.VizioAsync.get_all_audio_settings", "homeassistant.components.vizio.media_player.VizioAsync.get_all_settings",
return_value={ return_value={
"volume": int(MAX_VOLUME[DEVICE_CLASS_SPEAKER] / 2), "volume": int(MAX_VOLUME[DEVICE_CLASS_SPEAKER] / 2),
"eq": CURRENT_EQ,
"mute": "Off", "mute": "Off",
}, },
), patch(
"homeassistant.components.vizio.media_player.VizioAsync.get_setting_options",
return_value=EQ_LIST,
), patch( ), patch(
"homeassistant.components.vizio.media_player.VizioAsync.get_current_input", "homeassistant.components.vizio.media_player.VizioAsync.get_current_input",
return_value=CURRENT_INPUT, return_value=CURRENT_INPUT,

View file

@ -64,6 +64,9 @@ class MockCompletePairingResponse(object):
self.auth_token = auth_token self.auth_token = auth_token
CURRENT_EQ = "Music"
EQ_LIST = ["Music", "Movie"]
CURRENT_INPUT = "HDMI" CURRENT_INPUT = "HDMI"
INPUT_LIST = ["HDMI", "USB", "Bluetooth", "AUX"] INPUT_LIST = ["HDMI", "USB", "Bluetooth", "AUX"]

View file

@ -21,11 +21,13 @@ from homeassistant.components.media_player import (
ATTR_INPUT_SOURCE, ATTR_INPUT_SOURCE,
ATTR_MEDIA_VOLUME_LEVEL, ATTR_MEDIA_VOLUME_LEVEL,
ATTR_MEDIA_VOLUME_MUTED, ATTR_MEDIA_VOLUME_MUTED,
ATTR_SOUND_MODE,
DEVICE_CLASS_SPEAKER, DEVICE_CLASS_SPEAKER,
DEVICE_CLASS_TV, DEVICE_CLASS_TV,
DOMAIN as MP_DOMAIN, DOMAIN as MP_DOMAIN,
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_NEXT_TRACK,
SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK,
SERVICE_SELECT_SOUND_MODE,
SERVICE_SELECT_SOURCE, SERVICE_SELECT_SOURCE,
SERVICE_TURN_OFF, SERVICE_TURN_OFF,
SERVICE_TURN_ON, SERVICE_TURN_ON,
@ -58,9 +60,11 @@ from .const import (
APP_LIST, APP_LIST,
CURRENT_APP, CURRENT_APP,
CURRENT_APP_CONFIG, CURRENT_APP_CONFIG,
CURRENT_EQ,
CURRENT_INPUT, CURRENT_INPUT,
CUSTOM_CONFIG, CUSTOM_CONFIG,
ENTITY_ID, ENTITY_ID,
EQ_LIST,
INPUT_LIST, INPUT_LIST,
INPUT_LIST_WITH_APPS, INPUT_LIST_WITH_APPS,
MOCK_SPEAKER_APPS_FAILURE, MOCK_SPEAKER_APPS_FAILURE,
@ -99,6 +103,11 @@ async def _test_setup(
data=vol.Schema(VIZIO_SCHEMA)(MOCK_SPEAKER_CONFIG), data=vol.Schema(VIZIO_SCHEMA)(MOCK_SPEAKER_CONFIG),
unique_id=UNIQUE_ID, unique_id=UNIQUE_ID,
) )
dict_to_return = {
"volume": int(MAX_VOLUME[vizio_device_class] / 2),
"mute": "Off",
"eq": CURRENT_EQ,
}
else: else:
vizio_device_class = VIZIO_DEVICE_CLASS_TV vizio_device_class = VIZIO_DEVICE_CLASS_TV
config_entry = MockConfigEntry( config_entry = MockConfigEntry(
@ -106,10 +115,17 @@ async def _test_setup(
data=vol.Schema(VIZIO_SCHEMA)(MOCK_USER_VALID_TV_CONFIG), data=vol.Schema(VIZIO_SCHEMA)(MOCK_USER_VALID_TV_CONFIG),
unique_id=UNIQUE_ID, unique_id=UNIQUE_ID,
) )
dict_to_return = {
"volume": int(MAX_VOLUME[vizio_device_class] / 2),
"mute": "Off",
}
with patch( with patch(
"homeassistant.components.vizio.media_player.VizioAsync.get_all_audio_settings", "homeassistant.components.vizio.media_player.VizioAsync.get_all_settings",
return_value={"volume": int(MAX_VOLUME[vizio_device_class] / 2), "mute": "Off"}, return_value=dict_to_return,
), patch(
"homeassistant.components.vizio.media_player.VizioAsync.get_setting_options",
return_value=EQ_LIST,
), patch( ), patch(
"homeassistant.components.vizio.media_player.VizioAsync.get_power_state", "homeassistant.components.vizio.media_player.VizioAsync.get_power_state",
return_value=vizio_power_state, return_value=vizio_power_state,
@ -130,6 +146,9 @@ async def _test_setup(
assert attr["source"] == CURRENT_INPUT assert attr["source"] == CURRENT_INPUT
if ha_device_class == DEVICE_CLASS_SPEAKER: if ha_device_class == DEVICE_CLASS_SPEAKER:
assert not service_call.called assert not service_call.called
assert "sound_mode" in attr
else:
assert "sound_mode" not in attr
assert ( assert (
attr["volume_level"] attr["volume_level"]
== float(int(MAX_VOLUME[vizio_device_class] / 2)) == float(int(MAX_VOLUME[vizio_device_class] / 2))
@ -149,7 +168,7 @@ async def _test_setup_with_apps(
) )
with patch( with patch(
"homeassistant.components.vizio.media_player.VizioAsync.get_all_audio_settings", "homeassistant.components.vizio.media_player.VizioAsync.get_all_settings",
return_value={ return_value={
"volume": int(MAX_VOLUME[VIZIO_DEVICE_CLASS_TV] / 2), "volume": int(MAX_VOLUME[VIZIO_DEVICE_CLASS_TV] / 2),
"mute": "Off", "mute": "Off",
@ -351,6 +370,9 @@ async def test_services(
) )
await _test_service(hass, "ch_up", SERVICE_MEDIA_NEXT_TRACK, None) 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, "ch_down", SERVICE_MEDIA_PREVIOUS_TRACK, None)
await _test_service(
hass, "set_setting", SERVICE_SELECT_SOUND_MODE, {ATTR_SOUND_MODE: "Music"}
)
async def test_options_update( async def test_options_update(