diff --git a/homeassistant/components/vizio/const.py b/homeassistant/components/vizio/const.py index 795f12266fb..43cb993cec3 100644 --- a/homeassistant/components/vizio/const.py +++ b/homeassistant/components/vizio/const.py @@ -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 # the values of DEVICE_CLASS_SPEAKER or DEVICE_CLASS_TV don't require changes to pyvizio. VIZIO_DEVICE_CLASSES = { diff --git a/homeassistant/components/vizio/manifest.json b/homeassistant/components/vizio/manifest.json index 885cfacca41..2436ce6298b 100644 --- a/homeassistant/components/vizio/manifest.json +++ b/homeassistant/components/vizio/manifest.json @@ -2,7 +2,7 @@ "domain": "vizio", "name": "VIZIO SmartCast", "documentation": "https://www.home-assistant.io/integrations/vizio", - "requirements": ["pyvizio==0.1.44"], + "requirements": ["pyvizio==0.1.45"], "dependencies": [], "codeowners": ["@raman325"], "config_flow": true, diff --git a/homeassistant/components/vizio/media_player.py b/homeassistant/components/vizio/media_player.py index d463ebca36a..bb7ae3f75b0 100644 --- a/homeassistant/components/vizio/media_player.py +++ b/homeassistant/components/vizio/media_player.py @@ -9,6 +9,7 @@ from pyvizio.const import APP_HOME, APPS, INPUT_APPS, NO_APP_RUNNING, UNKNOWN_AP from homeassistant.components.media_player import ( DEVICE_CLASS_SPEAKER, + SUPPORT_SELECT_SOUND_MODE, MediaPlayerDevice, ) from homeassistant.config_entries import ConfigEntry @@ -41,7 +42,9 @@ from .const import ( DOMAIN, ICON, SUPPORTED_COMMANDS, + VIZIO_AUDIO_SETTINGS, VIZIO_DEVICE_CLASSES, + VIZIO_SOUND_MODE, ) _LOGGER = logging.getLogger(__name__) @@ -133,6 +136,8 @@ class VizioDevice(MediaPlayerDevice): self._current_input = None self._current_app = None self._current_app_config = None + self._current_sound_mode = None + self._available_sound_modes = None self._available_inputs = [] self._available_apps = [] self._conf_apps = config_entry.options.get(CONF_APPS, {}) @@ -191,17 +196,29 @@ class VizioDevice(MediaPlayerDevice): self._current_app = None self._current_app_config = None self._available_apps = None + self._current_sound_mode = None + self._available_sound_modes = None return self._state = STATE_ON - audio_settings = await self._device.get_all_audio_settings( - log_api_exception=False + audio_settings = await self._device.get_all_settings( + VIZIO_AUDIO_SETTINGS, log_api_exception=False ) if audio_settings is not None: self._volume_level = float(audio_settings["volume"]) / self._max_volume 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) if input_ is not None: self._current_input = input_ @@ -367,7 +384,7 @@ class VizioDevice(MediaPlayerDevice): return self._config_entry.unique_id @property - def device_info(self): + def device_info(self) -> Dict[str, Any]: """Return device registry information.""" return { "identifiers": {(DOMAIN, self._config_entry.unique_id)}, @@ -378,10 +395,27 @@ class VizioDevice(MediaPlayerDevice): } @property - def device_class(self): + def device_class(self) -> str: """Return device class for entity.""" 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: """Turn the device on.""" await self._device.pow_on() diff --git a/requirements_all.txt b/requirements_all.txt index 6724b83d8b4..6ebf1213c63 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1738,7 +1738,7 @@ pyversasense==0.0.6 pyvesync==1.1.0 # homeassistant.components.vizio -pyvizio==0.1.44 +pyvizio==0.1.45 # homeassistant.components.velux pyvlx==0.2.12 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 83255df5714..307113e9b18 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -650,7 +650,7 @@ pyvera==0.3.7 pyvesync==1.1.0 # homeassistant.components.vizio -pyvizio==0.1.44 +pyvizio==0.1.45 # homeassistant.components.html5 pywebpush==1.9.2 diff --git a/tests/components/vizio/conftest.py b/tests/components/vizio/conftest.py index 868bf44a11b..e630f201e12 100644 --- a/tests/components/vizio/conftest.py +++ b/tests/components/vizio/conftest.py @@ -8,7 +8,9 @@ from .const import ( APP_LIST, CH_TYPE, CURRENT_APP_CONFIG, + CURRENT_EQ, CURRENT_INPUT, + EQ_LIST, INPUT_LIST, INPUT_LIST_WITH_APPS, MODEL, @@ -135,11 +137,15 @@ def vizio_update_fixture(): "homeassistant.components.vizio.media_player.VizioAsync.can_connect_with_auth_check", return_value=True, ), 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[DEVICE_CLASS_SPEAKER] / 2), + "eq": CURRENT_EQ, "mute": "Off", }, + ), patch( + "homeassistant.components.vizio.media_player.VizioAsync.get_setting_options", + return_value=EQ_LIST, ), patch( "homeassistant.components.vizio.media_player.VizioAsync.get_current_input", return_value=CURRENT_INPUT, diff --git a/tests/components/vizio/const.py b/tests/components/vizio/const.py index f1ddc4abba6..034fa23a3af 100644 --- a/tests/components/vizio/const.py +++ b/tests/components/vizio/const.py @@ -64,6 +64,9 @@ class MockCompletePairingResponse(object): self.auth_token = auth_token +CURRENT_EQ = "Music" +EQ_LIST = ["Music", "Movie"] + CURRENT_INPUT = "HDMI" INPUT_LIST = ["HDMI", "USB", "Bluetooth", "AUX"] diff --git a/tests/components/vizio/test_media_player.py b/tests/components/vizio/test_media_player.py index f860c1cec4f..7678712db51 100644 --- a/tests/components/vizio/test_media_player.py +++ b/tests/components/vizio/test_media_player.py @@ -21,11 +21,13 @@ from homeassistant.components.media_player import ( ATTR_INPUT_SOURCE, ATTR_MEDIA_VOLUME_LEVEL, ATTR_MEDIA_VOLUME_MUTED, + ATTR_SOUND_MODE, DEVICE_CLASS_SPEAKER, DEVICE_CLASS_TV, DOMAIN as MP_DOMAIN, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, + SERVICE_SELECT_SOUND_MODE, SERVICE_SELECT_SOURCE, SERVICE_TURN_OFF, SERVICE_TURN_ON, @@ -58,9 +60,11 @@ from .const import ( APP_LIST, CURRENT_APP, CURRENT_APP_CONFIG, + CURRENT_EQ, CURRENT_INPUT, CUSTOM_CONFIG, ENTITY_ID, + EQ_LIST, INPUT_LIST, INPUT_LIST_WITH_APPS, MOCK_SPEAKER_APPS_FAILURE, @@ -99,6 +103,11 @@ async def _test_setup( data=vol.Schema(VIZIO_SCHEMA)(MOCK_SPEAKER_CONFIG), unique_id=UNIQUE_ID, ) + dict_to_return = { + "volume": int(MAX_VOLUME[vizio_device_class] / 2), + "mute": "Off", + "eq": CURRENT_EQ, + } else: vizio_device_class = VIZIO_DEVICE_CLASS_TV config_entry = MockConfigEntry( @@ -106,10 +115,17 @@ async def _test_setup( data=vol.Schema(VIZIO_SCHEMA)(MOCK_USER_VALID_TV_CONFIG), unique_id=UNIQUE_ID, ) + dict_to_return = { + "volume": int(MAX_VOLUME[vizio_device_class] / 2), + "mute": "Off", + } with patch( - "homeassistant.components.vizio.media_player.VizioAsync.get_all_audio_settings", - return_value={"volume": int(MAX_VOLUME[vizio_device_class] / 2), "mute": "Off"}, + "homeassistant.components.vizio.media_player.VizioAsync.get_all_settings", + return_value=dict_to_return, + ), patch( + "homeassistant.components.vizio.media_player.VizioAsync.get_setting_options", + return_value=EQ_LIST, ), patch( "homeassistant.components.vizio.media_player.VizioAsync.get_power_state", return_value=vizio_power_state, @@ -130,6 +146,9 @@ async def _test_setup( assert attr["source"] == CURRENT_INPUT if ha_device_class == DEVICE_CLASS_SPEAKER: assert not service_call.called + assert "sound_mode" in attr + else: + assert "sound_mode" not in attr assert ( attr["volume_level"] == float(int(MAX_VOLUME[vizio_device_class] / 2)) @@ -149,7 +168,7 @@ async def _test_setup_with_apps( ) 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_TV] / 2), "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_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(