mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Automatically update app list for Vizio SmartTV's (#38641)
This commit is contained in:
parent
9f5baa0bf7
commit
7ff633f531
@ -1,15 +1,24 @@
|
|||||||
"""The vizio component."""
|
"""The vizio component."""
|
||||||
import asyncio
|
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
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.media_player import DEVICE_CLASS_TV
|
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 import config_validation as cv
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
|
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
|
from .const import CONF_APPS, CONF_DEVICE_CLASS, DOMAIN, VIZIO_SCHEMA
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def validate_apps(config: ConfigType) -> ConfigType:
|
def validate_apps(config: ConfigType) -> ConfigType:
|
||||||
"""Validate CONF_APPS is only used when CONF_DEVICE_CLASS == DEVICE_CLASS_TV."""
|
"""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:
|
async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry) -> bool:
|
||||||
"""Load the saved entities."""
|
"""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:
|
for platform in PLATFORMS:
|
||||||
hass.async_create_task(
|
hass.async_create_task(
|
||||||
hass.config_entries.async_forward_entry_setup(config_entry, platform)
|
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
|
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 typing import Any, Dict, Optional
|
||||||
|
|
||||||
from pyvizio import VizioAsync, async_guess_device_type
|
from pyvizio import VizioAsync, async_guess_device_type
|
||||||
|
from pyvizio.const import APP_HOME
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
@ -154,7 +155,15 @@ class VizioOptionsConfigFlow(config_entries.OptionsFlow):
|
|||||||
default=self.config_entry.options.get(CONF_APPS, {}).get(
|
default=self.config_entry.options.get(CONF_APPS, {}).get(
|
||||||
default_include_or_exclude, []
|
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."""
|
"""Constants used by vizio component."""
|
||||||
from pyvizio import VizioAsync
|
|
||||||
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,
|
||||||
@ -101,10 +100,10 @@ VIZIO_SCHEMA = {
|
|||||||
vol.Optional(CONF_APPS): vol.All(
|
vol.Optional(CONF_APPS): vol.All(
|
||||||
{
|
{
|
||||||
vol.Exclusive(CONF_INCLUDE, "apps_filter"): 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(
|
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(
|
vol.Optional(CONF_ADDITIONAL_CONFIGS): vol.All(
|
||||||
cv.ensure_list,
|
cv.ensure_list,
|
||||||
|
@ -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.51"],
|
"requirements": ["pyvizio==0.1.56"],
|
||||||
"codeowners": ["@raman325"],
|
"codeowners": ["@raman325"],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"zeroconf": ["_viziocast._tcp.local."],
|
"zeroconf": ["_viziocast._tcp.local."],
|
||||||
|
@ -5,10 +5,11 @@ from typing import Any, Callable, Dict, List, Optional, Union
|
|||||||
|
|
||||||
from pyvizio import VizioAsync
|
from pyvizio import VizioAsync
|
||||||
from pyvizio.api.apps import find_app_name
|
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 (
|
from homeassistant.components.media_player import (
|
||||||
DEVICE_CLASS_SPEAKER,
|
DEVICE_CLASS_SPEAKER,
|
||||||
|
DEVICE_CLASS_TV,
|
||||||
SUPPORT_SELECT_SOUND_MODE,
|
SUPPORT_SELECT_SOUND_MODE,
|
||||||
MediaPlayerEntity,
|
MediaPlayerEntity,
|
||||||
)
|
)
|
||||||
@ -23,6 +24,7 @@ from homeassistant.const import (
|
|||||||
STATE_OFF,
|
STATE_OFF,
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
)
|
)
|
||||||
|
from homeassistant.core import callback
|
||||||
from homeassistant.exceptions import PlatformNotReady
|
from homeassistant.exceptions import PlatformNotReady
|
||||||
from homeassistant.helpers import entity_platform
|
from homeassistant.helpers import entity_platform
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
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.entity import Entity
|
||||||
from homeassistant.helpers.typing import HomeAssistantType
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_ADDITIONAL_CONFIGS,
|
CONF_ADDITIONAL_CONFIGS,
|
||||||
@ -78,6 +81,7 @@ async def async_setup_entry(
|
|||||||
params = {}
|
params = {}
|
||||||
if not config_entry.options:
|
if not config_entry.options:
|
||||||
params["options"] = {CONF_VOLUME_STEP: volume_step}
|
params["options"] = {CONF_VOLUME_STEP: volume_step}
|
||||||
|
|
||||||
include_or_exclude_key = next(
|
include_or_exclude_key = next(
|
||||||
(
|
(
|
||||||
key
|
key
|
||||||
@ -115,7 +119,9 @@ async def async_setup_entry(
|
|||||||
_LOGGER.warning("Failed to connect to %s", host)
|
_LOGGER.warning("Failed to connect to %s", host)
|
||||||
raise PlatformNotReady
|
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)
|
async_add_entities([entity], update_before_add=True)
|
||||||
platform = entity_platform.current_platform.get()
|
platform = entity_platform.current_platform.get()
|
||||||
@ -133,10 +139,12 @@ class VizioDevice(MediaPlayerEntity):
|
|||||||
device: VizioAsync,
|
device: VizioAsync,
|
||||||
name: str,
|
name: str,
|
||||||
device_class: str,
|
device_class: str,
|
||||||
|
apps_coordinator: DataUpdateCoordinator,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize Vizio device."""
|
"""Initialize Vizio device."""
|
||||||
self._config_entry = config_entry
|
self._config_entry = config_entry
|
||||||
self._async_unsub_listeners = []
|
self._async_unsub_listeners = []
|
||||||
|
self._apps_coordinator = apps_coordinator
|
||||||
|
|
||||||
self._name = name
|
self._name = name
|
||||||
self._state = None
|
self._state = None
|
||||||
@ -150,6 +158,7 @@ class VizioDevice(MediaPlayerEntity):
|
|||||||
self._available_sound_modes = []
|
self._available_sound_modes = []
|
||||||
self._available_inputs = []
|
self._available_inputs = []
|
||||||
self._available_apps = []
|
self._available_apps = []
|
||||||
|
self._all_apps = apps_coordinator.data if apps_coordinator else None
|
||||||
self._conf_apps = config_entry.options.get(CONF_APPS, {})
|
self._conf_apps = config_entry.options.get(CONF_APPS, {})
|
||||||
self._additional_app_configs = config_entry.data.get(CONF_APPS, {}).get(
|
self._additional_app_configs = config_entry.data.get(CONF_APPS, {}).get(
|
||||||
CONF_ADDITIONAL_CONFIGS, []
|
CONF_ADDITIONAL_CONFIGS, []
|
||||||
@ -255,14 +264,15 @@ class VizioDevice(MediaPlayerEntity):
|
|||||||
|
|
||||||
# Create list of available known apps from known app list after
|
# Create list of available known apps from known app list after
|
||||||
# filtering by CONF_INCLUDE/CONF_EXCLUDE
|
# 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(
|
self._current_app_config = await self._device.get_current_app_config(
|
||||||
log_api_exception=False
|
log_api_exception=False
|
||||||
)
|
)
|
||||||
|
|
||||||
self._current_app = find_app_name(
|
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:
|
if self._current_app == NO_APP_RUNNING:
|
||||||
@ -286,6 +296,7 @@ class VizioDevice(MediaPlayerEntity):
|
|||||||
async def _async_update_options(self, config_entry: ConfigEntry) -> None:
|
async def _async_update_options(self, config_entry: ConfigEntry) -> None:
|
||||||
"""Update options if the update signal comes from this entity."""
|
"""Update options if the update signal comes from this entity."""
|
||||||
self._volume_step = config_entry.options[CONF_VOLUME_STEP]
|
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, {}))
|
self._conf_apps.update(config_entry.options.get(CONF_APPS, {}))
|
||||||
|
|
||||||
async def async_update_setting(
|
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:
|
async def async_will_remove_from_hass(self) -> None:
|
||||||
"""Disconnect callbacks when entity is removed."""
|
"""Disconnect callbacks when entity is removed."""
|
||||||
for listener in self._async_unsub_listeners:
|
for listener in self._async_unsub_listeners:
|
||||||
@ -479,7 +502,7 @@ class VizioDevice(MediaPlayerEntity):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
elif source in self._available_apps:
|
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:
|
async def async_volume_up(self) -> None:
|
||||||
"""Increase volume of the device."""
|
"""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.
|
description: Update the value of a setting on a particular Vizio media player device.
|
||||||
fields:
|
fields:
|
||||||
entity_id:
|
entity_id:
|
||||||
description: Name of an entity to send command.
|
description: Name of an entity to send command to.
|
||||||
example: "media_player.vizio_smartcast"
|
example: "media_player.vizio_smartcast"
|
||||||
setting_type:
|
setting_type:
|
||||||
description: The type of setting to be changed. Available types are listed in the `setting_types` property.
|
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
|
pyvesync==1.1.0
|
||||||
|
|
||||||
# homeassistant.components.vizio
|
# homeassistant.components.vizio
|
||||||
pyvizio==0.1.51
|
pyvizio==0.1.56
|
||||||
|
|
||||||
# homeassistant.components.velux
|
# homeassistant.components.velux
|
||||||
pyvlx==0.2.16
|
pyvlx==0.2.16
|
||||||
|
@ -857,7 +857,7 @@ pyvera==0.3.9
|
|||||||
pyvesync==1.1.0
|
pyvesync==1.1.0
|
||||||
|
|
||||||
# homeassistant.components.vizio
|
# homeassistant.components.vizio
|
||||||
pyvizio==0.1.51
|
pyvizio==0.1.56
|
||||||
|
|
||||||
# homeassistant.components.volumio
|
# homeassistant.components.volumio
|
||||||
pyvolumio==0.1.2
|
pyvolumio==0.1.2
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
"""Configure py.test."""
|
"""Configure py.test."""
|
||||||
import pytest
|
import pytest
|
||||||
|
from pyvizio.api.apps import AppConfig
|
||||||
from pyvizio.const import DEVICE_CLASS_SPEAKER, MAX_VOLUME
|
from pyvizio.const import DEVICE_CLASS_SPEAKER, MAX_VOLUME
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
@ -57,6 +58,15 @@ def vizio_get_unique_id_fixture():
|
|||||||
yield
|
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")
|
@pytest.fixture(name="vizio_no_unique_id")
|
||||||
def vizio_no_unique_id_fixture():
|
def vizio_no_unique_id_fixture():
|
||||||
"""Mock no vizio unique ID returrned."""
|
"""Mock no vizio unique ID returrned."""
|
||||||
@ -191,15 +201,12 @@ def vizio_update_with_apps_fixture(vizio_update: pytest.fixture):
|
|||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.vizio.media_player.VizioAsync.get_inputs_list",
|
"homeassistant.components.vizio.media_player.VizioAsync.get_inputs_list",
|
||||||
return_value=get_mock_inputs(INPUT_LIST_WITH_APPS),
|
return_value=get_mock_inputs(INPUT_LIST_WITH_APPS),
|
||||||
), patch(
|
|
||||||
"homeassistant.components.vizio.media_player.VizioAsync.get_apps_list",
|
|
||||||
return_value=APP_LIST,
|
|
||||||
), patch(
|
), patch(
|
||||||
"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_config",
|
"homeassistant.components.vizio.media_player.VizioAsync.get_current_app_config",
|
||||||
return_value=CURRENT_APP_CONFIG,
|
return_value=AppConfig(**CURRENT_APP_CONFIG),
|
||||||
):
|
):
|
||||||
yield
|
yield
|
||||||
|
|
||||||
|
@ -72,7 +72,21 @@ 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}
|
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"]
|
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}
|
||||||
ADDITIONAL_APP_CONFIG = {
|
ADDITIONAL_APP_CONFIG = {
|
||||||
|
@ -109,12 +109,18 @@ async def test_user_flow_all_fields(
|
|||||||
assert CONF_APPS not in result["data"]
|
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."""
|
"""Test options config flow for speaker."""
|
||||||
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_SPEAKER_CONFIG)
|
result = await hass.config_entries.flow.async_init(
|
||||||
entry.add_to_hass(hass)
|
DOMAIN, context={"source": SOURCE_USER}, data=MOCK_SPEAKER_CONFIG
|
||||||
|
)
|
||||||
assert not entry.options
|
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)
|
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"]
|
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."""
|
"""Test options config flow for TV without providing apps option."""
|
||||||
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_VALID_TV_CONFIG)
|
result = await hass.config_entries.flow.async_init(
|
||||||
entry.add_to_hass(hass)
|
DOMAIN, context={"source": SOURCE_USER}, data=MOCK_USER_VALID_TV_CONFIG
|
||||||
|
)
|
||||||
assert not entry.options
|
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)
|
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"]
|
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."""
|
"""Test options config flow for TV with providing apps option."""
|
||||||
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_VALID_TV_CONFIG)
|
result = await hass.config_entries.flow.async_init(
|
||||||
entry.add_to_hass(hass)
|
DOMAIN, context={"source": SOURCE_USER}, data=MOCK_USER_VALID_TV_CONFIG
|
||||||
|
)
|
||||||
assert not entry.options
|
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)
|
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]}
|
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."""
|
"""Test options config flow for TV with providing apps option after providing volume step in initial config."""
|
||||||
entry = MockConfigEntry(
|
result = await hass.config_entries.flow.async_init(
|
||||||
domain=DOMAIN,
|
DOMAIN, context={"source": SOURCE_USER}, data=MOCK_USER_VALID_TV_CONFIG
|
||||||
data=MOCK_USER_VALID_TV_CONFIG,
|
|
||||||
options={CONF_VOLUME_STEP: VOLUME_STEP},
|
|
||||||
)
|
)
|
||||||
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
|
||||||
assert entry.options == {CONF_VOLUME_STEP: VOLUME_STEP}
|
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.media_player.const import DOMAIN as MP_DOMAIN
|
||||||
from homeassistant.components.vizio.const import DOMAIN
|
from homeassistant.components.vizio.const import DOMAIN
|
||||||
from homeassistant.helpers.typing import HomeAssistantType
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
from .const import MOCK_USER_VALID_TV_CONFIG, UNIQUE_ID
|
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)
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(hass.states.async_entity_ids(MP_DOMAIN)) == 1
|
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()
|
await hass.async_block_till_done()
|
||||||
assert len(hass.states.async_entity_ids(MP_DOMAIN)) == 0
|
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 pytest import raises
|
||||||
from pyvizio.api.apps import AppConfig
|
from pyvizio.api.apps import AppConfig
|
||||||
from pyvizio.const import (
|
from pyvizio.const import (
|
||||||
|
APPS,
|
||||||
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,
|
||||||
INPUT_APPS,
|
INPUT_APPS,
|
||||||
@ -51,6 +52,7 @@ from homeassistant.util import dt as dt_util
|
|||||||
from .const import (
|
from .const import (
|
||||||
ADDITIONAL_APP_CONFIG,
|
ADDITIONAL_APP_CONFIG,
|
||||||
APP_LIST,
|
APP_LIST,
|
||||||
|
APP_NAME_LIST,
|
||||||
CURRENT_APP,
|
CURRENT_APP,
|
||||||
CURRENT_APP_CONFIG,
|
CURRENT_APP_CONFIG,
|
||||||
CURRENT_EQ,
|
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_on", SERVICE_TURN_ON, None)
|
||||||
await _test_service(hass, MP_DOMAIN, "pow_off", SERVICE_TURN_OFF, None)
|
await _test_service(hass, MP_DOMAIN, "pow_off", SERVICE_TURN_OFF, None)
|
||||||
await _test_service(
|
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(
|
await _test_service(
|
||||||
hass,
|
hass,
|
||||||
@ -511,7 +517,7 @@ async def test_setup_with_apps(
|
|||||||
hass, MOCK_USER_VALID_TV_CONFIG, CURRENT_APP_CONFIG
|
hass, MOCK_USER_VALID_TV_CONFIG, CURRENT_APP_CONFIG
|
||||||
):
|
):
|
||||||
attr = hass.states.get(ENTITY_ID).attributes
|
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 CURRENT_APP in attr["source_list"]
|
||||||
assert attr["source"] == CURRENT_APP
|
assert attr["source"] == CURRENT_APP
|
||||||
assert attr["app_name"] == CURRENT_APP
|
assert attr["app_name"] == CURRENT_APP
|
||||||
@ -524,6 +530,7 @@ async def test_setup_with_apps(
|
|||||||
SERVICE_SELECT_SOURCE,
|
SERVICE_SELECT_SOURCE,
|
||||||
{ATTR_INPUT_SOURCE: CURRENT_APP},
|
{ATTR_INPUT_SOURCE: CURRENT_APP},
|
||||||
CURRENT_APP,
|
CURRENT_APP,
|
||||||
|
APP_LIST,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -580,13 +587,13 @@ async def test_setup_with_apps_additional_apps_config(
|
|||||||
_assert_source_list_with_apps(
|
_assert_source_list_with_apps(
|
||||||
list(
|
list(
|
||||||
INPUT_LIST_WITH_APPS
|
INPUT_LIST_WITH_APPS
|
||||||
+ APP_LIST
|
+ APP_NAME_LIST
|
||||||
+ [
|
+ [
|
||||||
app["name"]
|
app["name"]
|
||||||
for app in MOCK_TV_WITH_ADDITIONAL_APPS_CONFIG[CONF_APPS][
|
for app in MOCK_TV_WITH_ADDITIONAL_APPS_CONFIG[CONF_APPS][
|
||||||
CONF_ADDITIONAL_CONFIGS
|
CONF_ADDITIONAL_CONFIGS
|
||||||
]
|
]
|
||||||
if app["name"] not in APP_LIST
|
if app["name"] not in APP_NAME_LIST
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
attr,
|
attr,
|
||||||
@ -603,6 +610,7 @@ async def test_setup_with_apps_additional_apps_config(
|
|||||||
SERVICE_SELECT_SOURCE,
|
SERVICE_SELECT_SOURCE,
|
||||||
{ATTR_INPUT_SOURCE: "Netflix"},
|
{ATTR_INPUT_SOURCE: "Netflix"},
|
||||||
"Netflix",
|
"Netflix",
|
||||||
|
APP_LIST,
|
||||||
)
|
)
|
||||||
await _test_service(
|
await _test_service(
|
||||||
hass,
|
hass,
|
||||||
@ -649,7 +657,7 @@ async def test_setup_with_unknown_app_config(
|
|||||||
hass, MOCK_USER_VALID_TV_CONFIG, UNKNOWN_APP_CONFIG
|
hass, MOCK_USER_VALID_TV_CONFIG, UNKNOWN_APP_CONFIG
|
||||||
):
|
):
|
||||||
attr = hass.states.get(ENTITY_ID).attributes
|
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["source"] == UNKNOWN_APP
|
||||||
assert attr["app_name"] == UNKNOWN_APP
|
assert attr["app_name"] == UNKNOWN_APP
|
||||||
assert attr["app_id"] == UNKNOWN_APP_CONFIG
|
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())
|
hass, MOCK_USER_VALID_TV_CONFIG, vars(AppConfig())
|
||||||
):
|
):
|
||||||
attr = hass.states.get(ENTITY_ID).attributes
|
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 attr["source"] == "CAST"
|
||||||
assert "app_id" not in attr
|
assert "app_id" not in attr
|
||||||
assert "app_name" 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_sources_and_volume(attr, VIZIO_DEVICE_CLASS_TV)
|
||||||
assert "sound_mode" not in attr
|
assert "sound_mode" not in attr
|
||||||
assert "is_volume_muted" 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…
x
Reference in New Issue
Block a user