Update vizio app_id state attribute to show app config dict in… (#32727)

* bump pyvizio and update app_id to show app config to aid in HA config generation. squashed from multiple commits to make a rebase on dev easier

* bump pyvizio for bug fixes

* fix pyvizio version number

* only return app_id if app is unknown and explicitly create the dict that's returned

* fix tests

* fix docstring for app_id
This commit is contained in:
Raman Gupta 2020-03-25 12:35:36 -04:00 committed by GitHub
parent 4a0a56ebdc
commit d5f4dfdd6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 101 additions and 47 deletions

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.35"], "requirements": ["pyvizio==0.1.44"],
"dependencies": [], "dependencies": [],
"codeowners": ["@raman325"], "codeowners": ["@raman325"],
"config_flow": true, "config_flow": true,

View File

@ -4,8 +4,8 @@ import logging
from typing import Any, Callable, Dict, List, Optional from typing import Any, Callable, Dict, List, Optional
from pyvizio import VizioAsync from pyvizio import VizioAsync
from pyvizio.const import INPUT_APPS, NO_APP_RUNNING, UNKNOWN_APP from pyvizio.api.apps import find_app_name
from pyvizio.helpers import find_app_name from pyvizio.const import APP_HOME, APPS, INPUT_APPS, NO_APP_RUNNING, UNKNOWN_APP
from homeassistant.components.media_player import ( from homeassistant.components.media_player import (
DEVICE_CLASS_SPEAKER, DEVICE_CLASS_SPEAKER,
@ -132,6 +132,7 @@ class VizioDevice(MediaPlayerDevice):
self._is_muted = None self._is_muted = None
self._current_input = None self._current_input = None
self._current_app = None self._current_app = None
self._current_app_config = 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, {})
@ -157,20 +158,6 @@ class VizioDevice(MediaPlayerDevice):
return apps return apps
async def _current_app_name(self) -> Optional[str]:
"""Return name of the currently running app by parsing pyvizio output."""
app = await self._device.get_current_app(log_api_exception=False)
if app in [None, NO_APP_RUNNING]:
return None
if app == UNKNOWN_APP and self._additional_app_configs:
return find_app_name(
await self._device.get_current_app_config(log_api_exception=False),
self._additional_app_configs,
)
return app
async def async_update(self) -> None: async def async_update(self) -> None:
"""Retrieve latest state of the device.""" """Retrieve latest state of the device."""
if not self._model: if not self._model:
@ -202,6 +189,7 @@ class VizioDevice(MediaPlayerDevice):
self._current_input = None self._current_input = None
self._available_inputs = None self._available_inputs = None
self._current_app = None self._current_app = None
self._current_app_config = None
self._available_apps = None self._available_apps = None
return return
@ -237,9 +225,16 @@ class VizioDevice(MediaPlayerDevice):
if not self._available_apps: if not self._available_apps:
self._available_apps = self._apps_list(self._device.get_apps_list()) self._available_apps = self._apps_list(self._device.get_apps_list())
# Attempt to get current app name. If app name is unknown, check list self._current_app_config = await self._device.get_current_app_config(
# of additional apps specified in configuration log_api_exception=False
self._current_app = await self._current_app_name() )
self._current_app = find_app_name(
self._current_app_config, [APP_HOME, *APPS, *self._additional_app_configs]
)
if self._current_app == NO_APP_RUNNING:
self._current_app = None
def _get_additional_app_names(self) -> List[Dict[str, Any]]: def _get_additional_app_names(self) -> List[Dict[str, Any]]:
"""Return list of additional apps that were included in configuration.yaml.""" """Return list of additional apps that were included in configuration.yaml."""
@ -346,8 +341,15 @@ class VizioDevice(MediaPlayerDevice):
@property @property
def app_id(self) -> Optional[str]: def app_id(self) -> Optional[str]:
"""Return the current app.""" """Return the ID of the current app if it is unknown by pyvizio."""
return self._current_app if self._current_app_config and self.app_name == UNKNOWN_APP:
return {
"APP_ID": self._current_app_config.APP_ID,
"NAME_SPACE": self._current_app_config.NAME_SPACE,
"MESSAGE": self._current_app_config.MESSAGE,
}
return None
@property @property
def app_name(self) -> Optional[str]: def app_name(self) -> Optional[str]:

View File

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

View File

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

View File

@ -7,7 +7,7 @@ from .const import (
ACCESS_TOKEN, ACCESS_TOKEN,
APP_LIST, APP_LIST,
CH_TYPE, CH_TYPE,
CURRENT_APP, CURRENT_APP_CONFIG,
CURRENT_INPUT, CURRENT_INPUT,
INPUT_LIST, INPUT_LIST,
INPUT_LIST_WITH_APPS, INPUT_LIST_WITH_APPS,
@ -172,7 +172,7 @@ def vizio_update_with_apps_fixture(vizio_update: pytest.fixture):
"homeassistant.components.vizio.media_player.VizioAsync.get_current_input", "homeassistant.components.vizio.media_player.VizioAsync.get_current_input",
return_value="CAST", return_value="CAST",
), patch( ), patch(
"homeassistant.components.vizio.media_player.VizioAsync.get_current_app", "homeassistant.components.vizio.media_player.VizioAsync.get_current_app_config",
return_value=CURRENT_APP, return_value=CURRENT_APP_CONFIG,
): ):
yield yield

View File

@ -68,6 +68,7 @@ CURRENT_INPUT = "HDMI"
INPUT_LIST = ["HDMI", "USB", "Bluetooth", "AUX"] INPUT_LIST = ["HDMI", "USB", "Bluetooth", "AUX"]
CURRENT_APP = "Hulu" CURRENT_APP = "Hulu"
CURRENT_APP_CONFIG = {CONF_APP_ID: "3", CONF_NAME_SPACE: 4, CONF_MESSAGE: None}
APP_LIST = ["Hulu", "Netflix"] APP_LIST = ["Hulu", "Netflix"]
INPUT_LIST_WITH_APPS = INPUT_LIST + ["CAST"] INPUT_LIST_WITH_APPS = INPUT_LIST + ["CAST"]
CUSTOM_CONFIG = {CONF_APP_ID: "test", CONF_MESSAGE: None, CONF_NAME_SPACE: 10} CUSTOM_CONFIG = {CONF_APP_ID: "test", CONF_MESSAGE: None, CONF_NAME_SPACE: 10}
@ -75,6 +76,11 @@ ADDITIONAL_APP_CONFIG = {
"name": CURRENT_APP, "name": CURRENT_APP,
CONF_CONFIG: CUSTOM_CONFIG, CONF_CONFIG: CUSTOM_CONFIG,
} }
UNKNOWN_APP_CONFIG = {
"APP_ID": "UNKNOWN",
"NAME_SPACE": 10,
"MESSAGE": None,
}
ENTITY_ID = f"{MP_DOMAIN}.{slugify(NAME)}" ENTITY_ID = f"{MP_DOMAIN}.{slugify(NAME)}"

View File

@ -1,13 +1,13 @@
"""Tests for Vizio config flow.""" """Tests for Vizio config flow."""
from datetime import timedelta from datetime import timedelta
import logging import logging
from typing import Any, Dict from typing import Any, Dict, Optional
from unittest.mock import call from unittest.mock import call
from asynctest import patch from asynctest import patch
import pytest import pytest
from pytest import raises from pytest import raises
from pyvizio._api.apps import AppConfig from pyvizio.api.apps import AppConfig
from pyvizio.const import ( from pyvizio.const import (
DEVICE_CLASS_SPEAKER as VIZIO_DEVICE_CLASS_SPEAKER, DEVICE_CLASS_SPEAKER as VIZIO_DEVICE_CLASS_SPEAKER,
DEVICE_CLASS_TV as VIZIO_DEVICE_CLASS_TV, DEVICE_CLASS_TV as VIZIO_DEVICE_CLASS_TV,
@ -57,6 +57,7 @@ from .const import (
ADDITIONAL_APP_CONFIG, ADDITIONAL_APP_CONFIG,
APP_LIST, APP_LIST,
CURRENT_APP, CURRENT_APP,
CURRENT_APP_CONFIG,
CURRENT_INPUT, CURRENT_INPUT,
CUSTOM_CONFIG, CUSTOM_CONFIG,
ENTITY_ID, ENTITY_ID,
@ -71,6 +72,7 @@ from .const import (
MOCK_USER_VALID_TV_CONFIG, MOCK_USER_VALID_TV_CONFIG,
NAME, NAME,
UNIQUE_ID, UNIQUE_ID,
UNKNOWN_APP_CONFIG,
VOLUME_STEP, VOLUME_STEP,
) )
@ -80,7 +82,7 @@ _LOGGER = logging.getLogger(__name__)
async def _test_setup( async def _test_setup(
hass: HomeAssistantType, ha_device_class: str, vizio_power_state: bool hass: HomeAssistantType, ha_device_class: str, vizio_power_state: Optional[bool]
) -> None: ) -> None:
"""Test Vizio Device entity setup.""" """Test Vizio Device entity setup."""
if vizio_power_state: if vizio_power_state:
@ -112,7 +114,7 @@ async def _test_setup(
"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,
), patch( ), patch(
"homeassistant.components.vizio.media_player.VizioAsync.get_current_app", "homeassistant.components.vizio.media_player.VizioAsync.get_current_app_config",
) as service_call: ) as service_call:
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id) assert await hass.config_entries.async_setup(config_entry.entry_id)
@ -136,7 +138,10 @@ async def _test_setup(
async def _test_setup_with_apps( async def _test_setup_with_apps(
hass: HomeAssistantType, device_config: Dict[str, Any], app: str hass: HomeAssistantType,
device_config: Dict[str, Any],
app: Optional[str],
app_config: Dict[str, Any],
) -> None: ) -> None:
"""Test Vizio Device with apps entity setup.""" """Test Vizio Device with apps entity setup."""
config_entry = MockConfigEntry( config_entry = MockConfigEntry(
@ -152,12 +157,9 @@ async def _test_setup_with_apps(
), patch( ), patch(
"homeassistant.components.vizio.media_player.VizioAsync.get_power_state", "homeassistant.components.vizio.media_player.VizioAsync.get_power_state",
return_value=True, return_value=True,
), patch(
"homeassistant.components.vizio.media_player.VizioAsync.get_current_app",
return_value=app,
), patch( ), patch(
"homeassistant.components.vizio.media_player.VizioAsync.get_current_app_config", "homeassistant.components.vizio.media_player.VizioAsync.get_current_app_config",
return_value=AppConfig(**ADDITIONAL_APP_CONFIG["config"]), return_value=AppConfig(**app_config),
): ):
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id) assert await hass.config_entries.async_setup(config_entry.entry_id)
@ -193,11 +195,20 @@ async def _test_setup_with_apps(
list_to_test.remove(app_to_remove) list_to_test.remove(app_to_remove)
assert attr["source_list"] == list_to_test assert attr["source_list"] == list_to_test
if app:
assert app in attr["source_list"] or app == UNKNOWN_APP assert app in attr["source_list"] or app == UNKNOWN_APP
if app == UNKNOWN_APP:
assert attr["source"] == ADDITIONAL_APP_CONFIG["name"]
else:
assert attr["source"] == app assert attr["source"] == app
assert attr["app_name"] == app
if app == UNKNOWN_APP:
assert attr["app_id"] == app_config
else:
assert "app_id" not in attr
else:
assert attr["source"] == "CAST"
assert "app_id" not in attr
assert "app_name" not in attr
assert ( assert (
attr["volume_level"] attr["volume_level"]
== float(int(MAX_VOLUME[VIZIO_DEVICE_CLASS_TV] / 2)) == float(int(MAX_VOLUME[VIZIO_DEVICE_CLASS_TV] / 2))
@ -222,7 +233,7 @@ async def _test_service(
hass: HomeAssistantType, hass: HomeAssistantType,
vizio_func_name: str, vizio_func_name: str,
ha_service_name: str, ha_service_name: str,
additional_service_data: dict, additional_service_data: Optional[Dict[str, Any]],
*args, *args,
**kwargs, **kwargs,
) -> None: ) -> None:
@ -363,8 +374,8 @@ async def test_options_update(
async def _test_update_availability_switch( async def _test_update_availability_switch(
hass: HomeAssistantType, hass: HomeAssistantType,
initial_power_state: bool, initial_power_state: Optional[bool],
final_power_state: bool, final_power_state: Optional[bool],
caplog: pytest.fixture, caplog: pytest.fixture,
) -> None: ) -> None:
now = dt_util.utcnow() now = dt_util.utcnow()
@ -431,7 +442,9 @@ async def test_setup_with_apps(
caplog: pytest.fixture, caplog: pytest.fixture,
) -> None: ) -> None:
"""Test device setup with apps.""" """Test device setup with apps."""
await _test_setup_with_apps(hass, MOCK_USER_VALID_TV_CONFIG, CURRENT_APP) await _test_setup_with_apps(
hass, MOCK_USER_VALID_TV_CONFIG, CURRENT_APP, CURRENT_APP_CONFIG
)
await _test_service( await _test_service(
hass, hass,
"launch_app", "launch_app",
@ -448,7 +461,9 @@ async def test_setup_with_apps_include(
caplog: pytest.fixture, caplog: pytest.fixture,
) -> None: ) -> None:
"""Test device setup with apps and apps["include"] in config.""" """Test device setup with apps and apps["include"] in config."""
await _test_setup_with_apps(hass, MOCK_TV_WITH_INCLUDE_CONFIG, CURRENT_APP) await _test_setup_with_apps(
hass, MOCK_TV_WITH_INCLUDE_CONFIG, CURRENT_APP, CURRENT_APP_CONFIG
)
async def test_setup_with_apps_exclude( async def test_setup_with_apps_exclude(
@ -458,7 +473,9 @@ async def test_setup_with_apps_exclude(
caplog: pytest.fixture, caplog: pytest.fixture,
) -> None: ) -> None:
"""Test device setup with apps and apps["exclude"] in config.""" """Test device setup with apps and apps["exclude"] in config."""
await _test_setup_with_apps(hass, MOCK_TV_WITH_EXCLUDE_CONFIG, CURRENT_APP) await _test_setup_with_apps(
hass, MOCK_TV_WITH_EXCLUDE_CONFIG, CURRENT_APP, CURRENT_APP_CONFIG
)
async def test_setup_with_apps_additional_apps_config( async def test_setup_with_apps_additional_apps_config(
@ -468,7 +485,12 @@ async def test_setup_with_apps_additional_apps_config(
caplog: pytest.fixture, caplog: pytest.fixture,
) -> None: ) -> None:
"""Test device setup with apps and apps["additional_configs"] in config.""" """Test device setup with apps and apps["additional_configs"] in config."""
await _test_setup_with_apps(hass, MOCK_TV_WITH_ADDITIONAL_APPS_CONFIG, UNKNOWN_APP) await _test_setup_with_apps(
hass,
MOCK_TV_WITH_ADDITIONAL_APPS_CONFIG,
ADDITIONAL_APP_CONFIG["name"],
ADDITIONAL_APP_CONFIG["config"],
)
await _test_service( await _test_service(
hass, hass,
@ -508,3 +530,27 @@ def test_invalid_apps_config(hass: HomeAssistantType):
with raises(vol.Invalid): with raises(vol.Invalid):
vol.Schema(vol.All(VIZIO_SCHEMA, validate_apps))(MOCK_SPEAKER_APPS_FAILURE) vol.Schema(vol.All(VIZIO_SCHEMA, validate_apps))(MOCK_SPEAKER_APPS_FAILURE)
async def test_setup_with_unknown_app_config(
hass: HomeAssistantType,
vizio_connect: pytest.fixture,
vizio_update_with_apps: pytest.fixture,
caplog: pytest.fixture,
) -> None:
"""Test device setup with apps where app config returned is unknown."""
await _test_setup_with_apps(
hass, MOCK_USER_VALID_TV_CONFIG, UNKNOWN_APP, UNKNOWN_APP_CONFIG
)
async def test_setup_with_no_running_app(
hass: HomeAssistantType,
vizio_connect: pytest.fixture,
vizio_update_with_apps: pytest.fixture,
caplog: pytest.fixture,
) -> None:
"""Test device setup with apps where no app is running."""
await _test_setup_with_apps(
hass, MOCK_USER_VALID_TV_CONFIG, None, vars(AppConfig())
)