Automatically update app list for Vizio SmartTV's (#38641)
This commit is contained in:
parent
9f5baa0bf7
commit
7ff633f531
13 changed files with 225 additions and 47 deletions
|
@ -1,15 +1,24 @@
|
|||
"""The vizio component."""
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from pyvizio.const import APPS
|
||||
from pyvizio.util import gen_apps_list_from_url
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.media_player import DEVICE_CLASS_TV
|
||||
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||
from homeassistant.config_entries import ENTRY_STATE_LOADED, SOURCE_IMPORT, ConfigEntry
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import CONF_APPS, CONF_DEVICE_CLASS, DOMAIN, VIZIO_SCHEMA
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def validate_apps(config: ConfigType) -> ConfigType:
|
||||
"""Validate CONF_APPS is only used when CONF_DEVICE_CLASS == DEVICE_CLASS_TV."""
|
||||
|
@ -47,6 +56,16 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
|
|||
|
||||
async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry) -> bool:
|
||||
"""Load the saved entities."""
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
if (
|
||||
CONF_APPS not in hass.data[DOMAIN]
|
||||
and config_entry.data[CONF_DEVICE_CLASS] == DEVICE_CLASS_TV
|
||||
):
|
||||
coordinator = VizioAppsDataUpdateCoordinator(hass)
|
||||
await coordinator.async_refresh()
|
||||
hass.data[DOMAIN][CONF_APPS] = coordinator
|
||||
|
||||
for platform in PLATFORMS:
|
||||
hass.async_create_task(
|
||||
hass.config_entries.async_forward_entry_setup(config_entry, platform)
|
||||
|
@ -68,4 +87,38 @@ async def async_unload_entry(
|
|||
)
|
||||
)
|
||||
|
||||
# Exclude this config entry because its not unloaded yet
|
||||
if not any(
|
||||
entry.state == ENTRY_STATE_LOADED
|
||||
and entry.entry_id != config_entry.entry_id
|
||||
and entry.data[CONF_DEVICE_CLASS] == DEVICE_CLASS_TV
|
||||
for entry in hass.config_entries.async_entries(DOMAIN)
|
||||
):
|
||||
hass.data[DOMAIN].pop(CONF_APPS)
|
||||
|
||||
if not hass.data[DOMAIN]:
|
||||
hass.data.pop(DOMAIN)
|
||||
|
||||
return unload_ok
|
||||
|
||||
|
||||
class VizioAppsDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
"""Define an object to hold Vizio app config data."""
|
||||
|
||||
def __init__(self, hass: HomeAssistantType) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name=DOMAIN,
|
||||
update_interval=timedelta(days=1),
|
||||
update_method=self._async_update_data,
|
||||
)
|
||||
self.data = APPS
|
||||
|
||||
async def _async_update_data(self) -> List[Dict[str, Any]]:
|
||||
"""Update data via library."""
|
||||
data = await gen_apps_list_from_url(session=async_get_clientsession(self.hass))
|
||||
if not data:
|
||||
raise UpdateFailed
|
||||
return sorted(data, key=lambda app: app["name"])
|
||||
|
|
|
@ -5,6 +5,7 @@ import socket
|
|||
from typing import Any, Dict, Optional
|
||||
|
||||
from pyvizio import VizioAsync, async_guess_device_type
|
||||
from pyvizio.const import APP_HOME
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
|
@ -154,7 +155,15 @@ class VizioOptionsConfigFlow(config_entries.OptionsFlow):
|
|||
default=self.config_entry.options.get(CONF_APPS, {}).get(
|
||||
default_include_or_exclude, []
|
||||
),
|
||||
): cv.multi_select(VizioAsync.get_apps_list()),
|
||||
): cv.multi_select(
|
||||
[
|
||||
APP_HOME["name"],
|
||||
*[
|
||||
app["name"]
|
||||
for app in self.hass.data[DOMAIN][CONF_APPS].data
|
||||
],
|
||||
]
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
"""Constants used by vizio component."""
|
||||
from pyvizio import VizioAsync
|
||||
from pyvizio.const import (
|
||||
DEVICE_CLASS_SPEAKER as VIZIO_DEVICE_CLASS_SPEAKER,
|
||||
DEVICE_CLASS_TV as VIZIO_DEVICE_CLASS_TV,
|
||||
|
@ -101,10 +100,10 @@ VIZIO_SCHEMA = {
|
|||
vol.Optional(CONF_APPS): vol.All(
|
||||
{
|
||||
vol.Exclusive(CONF_INCLUDE, "apps_filter"): vol.All(
|
||||
cv.ensure_list, [vol.All(cv.string, vol.In(VizioAsync.get_apps_list()))]
|
||||
cv.ensure_list, [cv.string]
|
||||
),
|
||||
vol.Exclusive(CONF_EXCLUDE, "apps_filter"): vol.All(
|
||||
cv.ensure_list, [vol.All(cv.string, vol.In(VizioAsync.get_apps_list()))]
|
||||
cv.ensure_list, [cv.string]
|
||||
),
|
||||
vol.Optional(CONF_ADDITIONAL_CONFIGS): vol.All(
|
||||
cv.ensure_list,
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"domain": "vizio",
|
||||
"name": "VIZIO SmartCast",
|
||||
"documentation": "https://www.home-assistant.io/integrations/vizio",
|
||||
"requirements": ["pyvizio==0.1.51"],
|
||||
"requirements": ["pyvizio==0.1.56"],
|
||||
"codeowners": ["@raman325"],
|
||||
"config_flow": true,
|
||||
"zeroconf": ["_viziocast._tcp.local."],
|
||||
|
|
|
@ -5,10 +5,11 @@ from typing import Any, Callable, Dict, List, Optional, Union
|
|||
|
||||
from pyvizio import VizioAsync
|
||||
from pyvizio.api.apps import find_app_name
|
||||
from pyvizio.const import APP_HOME, APPS, INPUT_APPS, NO_APP_RUNNING, UNKNOWN_APP
|
||||
from pyvizio.const import APP_HOME, INPUT_APPS, NO_APP_RUNNING, UNKNOWN_APP
|
||||
|
||||
from homeassistant.components.media_player import (
|
||||
DEVICE_CLASS_SPEAKER,
|
||||
DEVICE_CLASS_TV,
|
||||
SUPPORT_SELECT_SOUND_MODE,
|
||||
MediaPlayerEntity,
|
||||
)
|
||||
|
@ -23,6 +24,7 @@ from homeassistant.const import (
|
|||
STATE_OFF,
|
||||
STATE_ON,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.exceptions import PlatformNotReady
|
||||
from homeassistant.helpers import entity_platform
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
@ -32,6 +34,7 @@ from homeassistant.helpers.dispatcher import (
|
|||
)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.typing import HomeAssistantType
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
||||
from .const import (
|
||||
CONF_ADDITIONAL_CONFIGS,
|
||||
|
@ -78,6 +81,7 @@ async def async_setup_entry(
|
|||
params = {}
|
||||
if not config_entry.options:
|
||||
params["options"] = {CONF_VOLUME_STEP: volume_step}
|
||||
|
||||
include_or_exclude_key = next(
|
||||
(
|
||||
key
|
||||
|
@ -115,7 +119,9 @@ async def async_setup_entry(
|
|||
_LOGGER.warning("Failed to connect to %s", host)
|
||||
raise PlatformNotReady
|
||||
|
||||
entity = VizioDevice(config_entry, device, name, device_class)
|
||||
apps_coordinator = hass.data[DOMAIN].get(CONF_APPS)
|
||||
|
||||
entity = VizioDevice(config_entry, device, name, device_class, apps_coordinator)
|
||||
|
||||
async_add_entities([entity], update_before_add=True)
|
||||
platform = entity_platform.current_platform.get()
|
||||
|
@ -133,10 +139,12 @@ class VizioDevice(MediaPlayerEntity):
|
|||
device: VizioAsync,
|
||||
name: str,
|
||||
device_class: str,
|
||||
apps_coordinator: DataUpdateCoordinator,
|
||||
) -> None:
|
||||
"""Initialize Vizio device."""
|
||||
self._config_entry = config_entry
|
||||
self._async_unsub_listeners = []
|
||||
self._apps_coordinator = apps_coordinator
|
||||
|
||||
self._name = name
|
||||
self._state = None
|
||||
|
@ -150,6 +158,7 @@ class VizioDevice(MediaPlayerEntity):
|
|||
self._available_sound_modes = []
|
||||
self._available_inputs = []
|
||||
self._available_apps = []
|
||||
self._all_apps = apps_coordinator.data if apps_coordinator else None
|
||||
self._conf_apps = config_entry.options.get(CONF_APPS, {})
|
||||
self._additional_app_configs = config_entry.data.get(CONF_APPS, {}).get(
|
||||
CONF_ADDITIONAL_CONFIGS, []
|
||||
|
@ -255,14 +264,15 @@ class VizioDevice(MediaPlayerEntity):
|
|||
|
||||
# Create list of available known apps from known app list after
|
||||
# filtering by CONF_INCLUDE/CONF_EXCLUDE
|
||||
self._available_apps = self._apps_list(self._device.get_apps_list())
|
||||
self._available_apps = self._apps_list([app["name"] for app in self._all_apps])
|
||||
|
||||
self._current_app_config = await self._device.get_current_app_config(
|
||||
log_api_exception=False
|
||||
)
|
||||
|
||||
self._current_app = find_app_name(
|
||||
self._current_app_config, [APP_HOME, *APPS, *self._additional_app_configs]
|
||||
self._current_app_config,
|
||||
[APP_HOME, *self._all_apps, *self._additional_app_configs],
|
||||
)
|
||||
|
||||
if self._current_app == NO_APP_RUNNING:
|
||||
|
@ -286,6 +296,7 @@ class VizioDevice(MediaPlayerEntity):
|
|||
async def _async_update_options(self, config_entry: ConfigEntry) -> None:
|
||||
"""Update options if the update signal comes from this entity."""
|
||||
self._volume_step = config_entry.options[CONF_VOLUME_STEP]
|
||||
# Update so that CONF_ADDITIONAL_CONFIGS gets retained for imports
|
||||
self._conf_apps.update(config_entry.options.get(CONF_APPS, {}))
|
||||
|
||||
async def async_update_setting(
|
||||
|
@ -314,6 +325,18 @@ class VizioDevice(MediaPlayerEntity):
|
|||
)
|
||||
)
|
||||
|
||||
# Register callback for app list updates if device is a TV
|
||||
@callback
|
||||
def apps_list_update():
|
||||
"""Update list of all apps."""
|
||||
self._all_apps = self._apps_coordinator.data
|
||||
self.async_write_ha_state()
|
||||
|
||||
if self._device_class == DEVICE_CLASS_TV:
|
||||
self._async_unsub_listeners.append(
|
||||
self._apps_coordinator.async_add_listener(apps_list_update)
|
||||
)
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Disconnect callbacks when entity is removed."""
|
||||
for listener in self._async_unsub_listeners:
|
||||
|
@ -479,7 +502,7 @@ class VizioDevice(MediaPlayerEntity):
|
|||
)
|
||||
)
|
||||
elif source in self._available_apps:
|
||||
await self._device.launch_app(source)
|
||||
await self._device.launch_app(source, self._all_apps)
|
||||
|
||||
async def async_volume_up(self) -> None:
|
||||
"""Increase volume of the device."""
|
||||
|
|
|
@ -2,7 +2,7 @@ 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.
|
||||
description: Name of an entity to send command to.
|
||||
example: "media_player.vizio_smartcast"
|
||||
setting_type:
|
||||
description: The type of setting to be changed. Available types are listed in the `setting_types` property.
|
||||
|
|
|
@ -1830,7 +1830,7 @@ pyversasense==0.0.6
|
|||
pyvesync==1.1.0
|
||||
|
||||
# homeassistant.components.vizio
|
||||
pyvizio==0.1.51
|
||||
pyvizio==0.1.56
|
||||
|
||||
# homeassistant.components.velux
|
||||
pyvlx==0.2.16
|
||||
|
|
|
@ -857,7 +857,7 @@ pyvera==0.3.9
|
|||
pyvesync==1.1.0
|
||||
|
||||
# homeassistant.components.vizio
|
||||
pyvizio==0.1.51
|
||||
pyvizio==0.1.56
|
||||
|
||||
# homeassistant.components.volumio
|
||||
pyvolumio==0.1.2
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
"""Configure py.test."""
|
||||
import pytest
|
||||
from pyvizio.api.apps import AppConfig
|
||||
from pyvizio.const import DEVICE_CLASS_SPEAKER, MAX_VOLUME
|
||||
|
||||
from .const import (
|
||||
|
@ -57,6 +58,15 @@ def vizio_get_unique_id_fixture():
|
|||
yield
|
||||
|
||||
|
||||
@pytest.fixture(name="vizio_data_coordinator_update", autouse=True)
|
||||
def vizio_data_coordinator_update_fixture():
|
||||
"""Mock get data coordinator update."""
|
||||
with patch(
|
||||
"homeassistant.components.vizio.gen_apps_list_from_url", return_value=APP_LIST,
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture(name="vizio_no_unique_id")
|
||||
def vizio_no_unique_id_fixture():
|
||||
"""Mock no vizio unique ID returrned."""
|
||||
|
@ -191,15 +201,12 @@ def vizio_update_with_apps_fixture(vizio_update: pytest.fixture):
|
|||
with patch(
|
||||
"homeassistant.components.vizio.media_player.VizioAsync.get_inputs_list",
|
||||
return_value=get_mock_inputs(INPUT_LIST_WITH_APPS),
|
||||
), patch(
|
||||
"homeassistant.components.vizio.media_player.VizioAsync.get_apps_list",
|
||||
return_value=APP_LIST,
|
||||
), patch(
|
||||
"homeassistant.components.vizio.media_player.VizioAsync.get_current_input",
|
||||
return_value="CAST",
|
||||
), patch(
|
||||
"homeassistant.components.vizio.media_player.VizioAsync.get_current_app_config",
|
||||
return_value=CURRENT_APP_CONFIG,
|
||||
return_value=AppConfig(**CURRENT_APP_CONFIG),
|
||||
):
|
||||
yield
|
||||
|
||||
|
|
|
@ -72,7 +72,21 @@ INPUT_LIST = ["HDMI", "USB", "Bluetooth", "AUX"]
|
|||
|
||||
CURRENT_APP = "Hulu"
|
||||
CURRENT_APP_CONFIG = {CONF_APP_ID: "3", CONF_NAME_SPACE: 4, CONF_MESSAGE: None}
|
||||
APP_LIST = ["Hulu", "Netflix"]
|
||||
APP_LIST = [
|
||||
{
|
||||
"name": "Hulu",
|
||||
"country": ["*"],
|
||||
"id": ["1"],
|
||||
"config": [{"NAME_SPACE": 4, "APP_ID": "3", "MESSAGE": None}],
|
||||
},
|
||||
{
|
||||
"name": "Netflix",
|
||||
"country": ["*"],
|
||||
"id": ["2"],
|
||||
"config": [{"NAME_SPACE": 1, "APP_ID": "2", "MESSAGE": None}],
|
||||
},
|
||||
]
|
||||
APP_NAME_LIST = [app["name"] for app in APP_LIST]
|
||||
INPUT_LIST_WITH_APPS = INPUT_LIST + ["CAST"]
|
||||
CUSTOM_CONFIG = {CONF_APP_ID: "test", CONF_MESSAGE: None, CONF_NAME_SPACE: 10}
|
||||
ADDITIONAL_APP_CONFIG = {
|
||||
|
|
|
@ -109,12 +109,18 @@ async def test_user_flow_all_fields(
|
|||
assert CONF_APPS not in result["data"]
|
||||
|
||||
|
||||
async def test_speaker_options_flow(hass: HomeAssistantType) -> None:
|
||||
async def test_speaker_options_flow(
|
||||
hass: HomeAssistantType,
|
||||
vizio_connect: pytest.fixture,
|
||||
vizio_bypass_update: pytest.fixture,
|
||||
) -> None:
|
||||
"""Test options config flow for speaker."""
|
||||
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_SPEAKER_CONFIG)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
assert not entry.options
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}, data=MOCK_SPEAKER_CONFIG
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
entry = result["result"]
|
||||
|
||||
result = await hass.config_entries.options.async_init(entry.entry_id, data=None)
|
||||
|
||||
|
@ -131,12 +137,18 @@ async def test_speaker_options_flow(hass: HomeAssistantType) -> None:
|
|||
assert CONF_APPS not in result["data"]
|
||||
|
||||
|
||||
async def test_tv_options_flow_no_apps(hass: HomeAssistantType) -> None:
|
||||
async def test_tv_options_flow_no_apps(
|
||||
hass: HomeAssistantType,
|
||||
vizio_connect: pytest.fixture,
|
||||
vizio_bypass_update: pytest.fixture,
|
||||
) -> None:
|
||||
"""Test options config flow for TV without providing apps option."""
|
||||
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_VALID_TV_CONFIG)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
assert not entry.options
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}, data=MOCK_USER_VALID_TV_CONFIG
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
entry = result["result"]
|
||||
|
||||
result = await hass.config_entries.options.async_init(entry.entry_id, data=None)
|
||||
|
||||
|
@ -156,12 +168,18 @@ async def test_tv_options_flow_no_apps(hass: HomeAssistantType) -> None:
|
|||
assert CONF_APPS not in result["data"]
|
||||
|
||||
|
||||
async def test_tv_options_flow_with_apps(hass: HomeAssistantType) -> None:
|
||||
async def test_tv_options_flow_with_apps(
|
||||
hass: HomeAssistantType,
|
||||
vizio_connect: pytest.fixture,
|
||||
vizio_bypass_update: pytest.fixture,
|
||||
) -> None:
|
||||
"""Test options config flow for TV with providing apps option."""
|
||||
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_VALID_TV_CONFIG)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
assert not entry.options
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}, data=MOCK_USER_VALID_TV_CONFIG
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
entry = result["result"]
|
||||
|
||||
result = await hass.config_entries.options.async_init(entry.entry_id, data=None)
|
||||
|
||||
|
@ -182,14 +200,23 @@ async def test_tv_options_flow_with_apps(hass: HomeAssistantType) -> None:
|
|||
assert result["data"][CONF_APPS] == {CONF_INCLUDE: [CURRENT_APP]}
|
||||
|
||||
|
||||
async def test_tv_options_flow_start_with_volume(hass: HomeAssistantType) -> None:
|
||||
async def test_tv_options_flow_start_with_volume(
|
||||
hass: HomeAssistantType,
|
||||
vizio_connect: pytest.fixture,
|
||||
vizio_bypass_update: pytest.fixture,
|
||||
) -> None:
|
||||
"""Test options config flow for TV with providing apps option after providing volume step in initial config."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data=MOCK_USER_VALID_TV_CONFIG,
|
||||
options={CONF_VOLUME_STEP: VOLUME_STEP},
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}, data=MOCK_USER_VALID_TV_CONFIG
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
await hass.async_block_till_done()
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
entry = result["result"]
|
||||
|
||||
result = await hass.config_entries.options.async_init(
|
||||
entry.entry_id, data={CONF_VOLUME_STEP: VOLUME_STEP}
|
||||
)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
|
||||
assert entry.options
|
||||
assert entry.options == {CONF_VOLUME_STEP: VOLUME_STEP}
|
||||
|
|
|
@ -4,6 +4,7 @@ import pytest
|
|||
from homeassistant.components.media_player.const import DOMAIN as MP_DOMAIN
|
||||
from homeassistant.components.vizio.const import DOMAIN
|
||||
from homeassistant.helpers.typing import HomeAssistantType
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from .const import MOCK_USER_VALID_TV_CONFIG, UNIQUE_ID
|
||||
|
@ -37,7 +38,12 @@ async def test_load_and_unload(
|
|||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
assert len(hass.states.async_entity_ids(MP_DOMAIN)) == 1
|
||||
assert DOMAIN in hass.data
|
||||
assert "apps" in hass.data[DOMAIN]
|
||||
assert isinstance(hass.data[DOMAIN]["apps"], DataUpdateCoordinator)
|
||||
|
||||
assert await hass.config_entries.async_unload(config_entry.entry_id)
|
||||
assert await config_entry.async_unload(hass)
|
||||
await hass.async_block_till_done()
|
||||
assert len(hass.states.async_entity_ids(MP_DOMAIN)) == 0
|
||||
assert "apps" not in hass.data.get(DOMAIN, {})
|
||||
assert DOMAIN not in hass.data
|
||||
|
|
|
@ -8,6 +8,7 @@ import pytest
|
|||
from pytest import raises
|
||||
from pyvizio.api.apps import AppConfig
|
||||
from pyvizio.const import (
|
||||
APPS,
|
||||
DEVICE_CLASS_SPEAKER as VIZIO_DEVICE_CLASS_SPEAKER,
|
||||
DEVICE_CLASS_TV as VIZIO_DEVICE_CLASS_TV,
|
||||
INPUT_APPS,
|
||||
|
@ -51,6 +52,7 @@ from homeassistant.util import dt as dt_util
|
|||
from .const import (
|
||||
ADDITIONAL_APP_CONFIG,
|
||||
APP_LIST,
|
||||
APP_NAME_LIST,
|
||||
CURRENT_APP,
|
||||
CURRENT_APP_CONFIG,
|
||||
CURRENT_EQ,
|
||||
|
@ -358,7 +360,11 @@ async def test_services(
|
|||
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, MP_DOMAIN, "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,
|
||||
|
@ -511,7 +517,7 @@ async def test_setup_with_apps(
|
|||
hass, MOCK_USER_VALID_TV_CONFIG, CURRENT_APP_CONFIG
|
||||
):
|
||||
attr = hass.states.get(ENTITY_ID).attributes
|
||||
_assert_source_list_with_apps(list(INPUT_LIST_WITH_APPS + APP_LIST), attr)
|
||||
_assert_source_list_with_apps(list(INPUT_LIST_WITH_APPS + APP_NAME_LIST), attr)
|
||||
assert CURRENT_APP in attr["source_list"]
|
||||
assert attr["source"] == CURRENT_APP
|
||||
assert attr["app_name"] == CURRENT_APP
|
||||
|
@ -524,6 +530,7 @@ async def test_setup_with_apps(
|
|||
SERVICE_SELECT_SOURCE,
|
||||
{ATTR_INPUT_SOURCE: CURRENT_APP},
|
||||
CURRENT_APP,
|
||||
APP_LIST,
|
||||
)
|
||||
|
||||
|
||||
|
@ -580,13 +587,13 @@ async def test_setup_with_apps_additional_apps_config(
|
|||
_assert_source_list_with_apps(
|
||||
list(
|
||||
INPUT_LIST_WITH_APPS
|
||||
+ APP_LIST
|
||||
+ APP_NAME_LIST
|
||||
+ [
|
||||
app["name"]
|
||||
for app in MOCK_TV_WITH_ADDITIONAL_APPS_CONFIG[CONF_APPS][
|
||||
CONF_ADDITIONAL_CONFIGS
|
||||
]
|
||||
if app["name"] not in APP_LIST
|
||||
if app["name"] not in APP_NAME_LIST
|
||||
]
|
||||
),
|
||||
attr,
|
||||
|
@ -603,6 +610,7 @@ async def test_setup_with_apps_additional_apps_config(
|
|||
SERVICE_SELECT_SOURCE,
|
||||
{ATTR_INPUT_SOURCE: "Netflix"},
|
||||
"Netflix",
|
||||
APP_LIST,
|
||||
)
|
||||
await _test_service(
|
||||
hass,
|
||||
|
@ -649,7 +657,7 @@ async def test_setup_with_unknown_app_config(
|
|||
hass, MOCK_USER_VALID_TV_CONFIG, UNKNOWN_APP_CONFIG
|
||||
):
|
||||
attr = hass.states.get(ENTITY_ID).attributes
|
||||
_assert_source_list_with_apps(list(INPUT_LIST_WITH_APPS + APP_LIST), attr)
|
||||
_assert_source_list_with_apps(list(INPUT_LIST_WITH_APPS + APP_NAME_LIST), attr)
|
||||
assert attr["source"] == UNKNOWN_APP
|
||||
assert attr["app_name"] == UNKNOWN_APP
|
||||
assert attr["app_id"] == UNKNOWN_APP_CONFIG
|
||||
|
@ -666,7 +674,7 @@ async def test_setup_with_no_running_app(
|
|||
hass, MOCK_USER_VALID_TV_CONFIG, vars(AppConfig())
|
||||
):
|
||||
attr = hass.states.get(ENTITY_ID).attributes
|
||||
_assert_source_list_with_apps(list(INPUT_LIST_WITH_APPS + APP_LIST), attr)
|
||||
_assert_source_list_with_apps(list(INPUT_LIST_WITH_APPS + APP_NAME_LIST), attr)
|
||||
assert attr["source"] == "CAST"
|
||||
assert "app_id" not in attr
|
||||
assert "app_name" not in attr
|
||||
|
@ -694,3 +702,35 @@ async def test_setup_tv_without_mute(
|
|||
_assert_sources_and_volume(attr, VIZIO_DEVICE_CLASS_TV)
|
||||
assert "sound_mode" not in attr
|
||||
assert "is_volume_muted" not in attr
|
||||
|
||||
|
||||
async def test_apps_update(
|
||||
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."""
|
||||
with patch(
|
||||
"homeassistant.components.vizio.gen_apps_list_from_url", return_value=None,
|
||||
):
|
||||
async with _cm_for_test_setup_tv_with_apps(
|
||||
hass, MOCK_USER_VALID_TV_CONFIG, vars(AppConfig())
|
||||
):
|
||||
# Check source list, remove TV inputs, and verify that the integration is
|
||||
# using the default APPS list
|
||||
sources = hass.states.get(ENTITY_ID).attributes["source_list"]
|
||||
apps = list(set(sources) - set(INPUT_LIST))
|
||||
assert len(apps) == len(APPS)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.vizio.gen_apps_list_from_url",
|
||||
return_value=APP_LIST,
|
||||
):
|
||||
async_fire_time_changed(hass, dt_util.now() + timedelta(days=2))
|
||||
await hass.async_block_till_done()
|
||||
# Check source list, remove TV inputs, and verify that the integration is
|
||||
# now using the APP_LIST list
|
||||
sources = hass.states.get(ENTITY_ID).attributes["source_list"]
|
||||
apps = list(set(sources) - set(INPUT_LIST))
|
||||
assert len(apps) == len(APP_LIST)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue