mirror of
https://github.com/home-assistant/core.git
synced 2025-07-18 02:37:08 +00:00
Use default media player device classes for vizio component (#30802)
* use media player defined device classes instead of custom ones, add options flow test, add timeout options parameter * make options update error more generic * fix config flow options update logic * simplify logic for options update during import * use platform list for load and unload * update private config flow function name and description * fix grammar in strings.json * update mock config variable names to be more accurate * remove timeout conf option, create device_class property * update requirements * update .coveragerc to indicate that config_flow has tests * fix source of device_class property and move constants to const.py * fix grammar in error message * remove redundant device check in async_setup_entry since device connection is checked during config flow * revert change to async_setup_entry, raise ConfigEntryNotReady if device can't be connected to * update error text * add more context to error text
This commit is contained in:
parent
52cee84c2c
commit
8630a076a7
@ -782,7 +782,9 @@ omit =
|
|||||||
homeassistant/components/viaggiatreno/sensor.py
|
homeassistant/components/viaggiatreno/sensor.py
|
||||||
homeassistant/components/vicare/*
|
homeassistant/components/vicare/*
|
||||||
homeassistant/components/vivotek/camera.py
|
homeassistant/components/vivotek/camera.py
|
||||||
homeassistant/components/vizio/*
|
homeassistant/components/vizio/__init__.py
|
||||||
|
homeassistant/components/vizio/const.py
|
||||||
|
homeassistant/components/vizio/media_player.py
|
||||||
homeassistant/components/vlc/media_player.py
|
homeassistant/components/vlc/media_player.py
|
||||||
homeassistant/components/vlc_telnet/media_player.py
|
homeassistant/components/vlc_telnet/media_player.py
|
||||||
homeassistant/components/volkszaehler/sensor.py
|
homeassistant/components/volkszaehler/sensor.py
|
||||||
|
@ -1,49 +1,30 @@
|
|||||||
"""The vizio component."""
|
"""The vizio component."""
|
||||||
|
import asyncio
|
||||||
|
|
||||||
import voluptuous as vol
|
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 SOURCE_IMPORT, ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_DEVICE_CLASS
|
||||||
CONF_ACCESS_TOKEN,
|
|
||||||
CONF_DEVICE_CLASS,
|
|
||||||
CONF_HOST,
|
|
||||||
CONF_NAME,
|
|
||||||
)
|
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
|
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
|
||||||
|
|
||||||
from .const import (
|
from .const import DOMAIN, VIZIO_SCHEMA
|
||||||
CONF_VOLUME_STEP,
|
|
||||||
DEFAULT_DEVICE_CLASS,
|
|
||||||
DEFAULT_NAME,
|
|
||||||
DEFAULT_VOLUME_STEP,
|
|
||||||
DOMAIN,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def validate_auth(config: ConfigType) -> ConfigType:
|
def validate_auth(config: ConfigType) -> ConfigType:
|
||||||
"""Validate presence of CONF_ACCESS_TOKEN when CONF_DEVICE_CLASS=tv."""
|
"""Validate presence of CONF_ACCESS_TOKEN when CONF_DEVICE_CLASS == DEVICE_CLASS_TV."""
|
||||||
token = config.get(CONF_ACCESS_TOKEN)
|
token = config.get(CONF_ACCESS_TOKEN)
|
||||||
if config[CONF_DEVICE_CLASS] == "tv" and not token:
|
if config[CONF_DEVICE_CLASS] == DEVICE_CLASS_TV and not token:
|
||||||
raise vol.Invalid(
|
raise vol.Invalid(
|
||||||
f"When '{CONF_DEVICE_CLASS}' is 'tv' then '{CONF_ACCESS_TOKEN}' is required.",
|
f"When '{CONF_DEVICE_CLASS}' is '{DEVICE_CLASS_TV}' then "
|
||||||
|
f"'{CONF_ACCESS_TOKEN}' is required.",
|
||||||
path=[CONF_ACCESS_TOKEN],
|
path=[CONF_ACCESS_TOKEN],
|
||||||
)
|
)
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
VIZIO_SCHEMA = {
|
|
||||||
vol.Required(CONF_HOST): cv.string,
|
|
||||||
vol.Optional(CONF_ACCESS_TOKEN): cv.string,
|
|
||||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
|
||||||
vol.Optional(CONF_DEVICE_CLASS, default=DEFAULT_DEVICE_CLASS): vol.All(
|
|
||||||
cv.string, vol.Lower, vol.In(["tv", "soundbar"])
|
|
||||||
),
|
|
||||||
vol.Optional(CONF_VOLUME_STEP, default=DEFAULT_VOLUME_STEP): vol.All(
|
|
||||||
vol.Coerce(int), vol.Range(min=1, max=10)
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
DOMAIN: vol.All(
|
DOMAIN: vol.All(
|
||||||
@ -53,6 +34,8 @@ CONFIG_SCHEMA = vol.Schema(
|
|||||||
extra=vol.ALLOW_EXTRA,
|
extra=vol.ALLOW_EXTRA,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
PLATFORMS = ["media_player"]
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
|
async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
|
||||||
"""Component setup, run import config flow for each entry in config."""
|
"""Component setup, run import config flow for each entry in config."""
|
||||||
@ -69,8 +52,9 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
|
|||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool:
|
||||||
"""Load the saved entities."""
|
"""Load the saved entities."""
|
||||||
|
for platform in PLATFORMS:
|
||||||
hass.async_create_task(
|
hass.async_create_task(
|
||||||
hass.config_entries.async_forward_entry_setup(entry, "media_player")
|
hass.config_entries.async_forward_entry_setup(entry, platform)
|
||||||
)
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@ -78,6 +62,13 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool
|
|||||||
|
|
||||||
async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool:
|
async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool:
|
||||||
"""Unload a config entry."""
|
"""Unload a config entry."""
|
||||||
await hass.config_entries.async_forward_entry_unload(entry, "media_player")
|
unload_ok = all(
|
||||||
|
await asyncio.gather(
|
||||||
|
*[
|
||||||
|
hass.config_entries.async_forward_entry_unload(entry, platform)
|
||||||
|
for platform in PLATFORMS
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return True
|
return unload_ok
|
||||||
|
@ -6,6 +6,7 @@ from pyvizio import VizioAsync
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.components.media_player import DEVICE_CLASS_SPEAKER, DEVICE_CLASS_TV
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_ACCESS_TOKEN,
|
CONF_ACCESS_TOKEN,
|
||||||
@ -27,8 +28,8 @@ from .const import (
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def update_schema_defaults(input_dict: Dict[str, Any]) -> vol.Schema:
|
def _config_flow_schema(input_dict: Dict[str, Any]) -> vol.Schema:
|
||||||
"""Update schema defaults based on user input/config dict. Retains info already provided for future form views."""
|
"""Return schema defaults based on user input/config dict. Retain info already provided for future form views by setting them as defaults in schema."""
|
||||||
return vol.Schema(
|
return vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(
|
vol.Required(
|
||||||
@ -38,7 +39,7 @@ def update_schema_defaults(input_dict: Dict[str, Any]) -> vol.Schema:
|
|||||||
vol.Optional(
|
vol.Optional(
|
||||||
CONF_DEVICE_CLASS,
|
CONF_DEVICE_CLASS,
|
||||||
default=input_dict.get(CONF_DEVICE_CLASS, DEFAULT_DEVICE_CLASS),
|
default=input_dict.get(CONF_DEVICE_CLASS, DEFAULT_DEVICE_CLASS),
|
||||||
): vol.All(str, vol.Lower, vol.In(["tv", "soundbar"])),
|
): vol.All(str, vol.Lower, vol.In([DEVICE_CLASS_TV, DEVICE_CLASS_SPEAKER])),
|
||||||
vol.Optional(
|
vol.Optional(
|
||||||
CONF_ACCESS_TOKEN, default=input_dict.get(CONF_ACCESS_TOKEN, "")
|
CONF_ACCESS_TOKEN, default=input_dict.get(CONF_ACCESS_TOKEN, "")
|
||||||
): str,
|
): str,
|
||||||
@ -72,7 +73,7 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
# Store current values in case setup fails and user needs to edit
|
# Store current values in case setup fails and user needs to edit
|
||||||
self.user_schema = update_schema_defaults(user_input)
|
self.user_schema = _config_flow_schema(user_input)
|
||||||
|
|
||||||
# Check if new config entry matches any existing config entries
|
# Check if new config entry matches any existing config entries
|
||||||
for entry in self.hass.config_entries.async_entries(DOMAIN):
|
for entry in self.hass.config_entries.async_entries(DOMAIN):
|
||||||
@ -116,7 +117,7 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
title=user_input[CONF_NAME], data=user_input
|
title=user_input[CONF_NAME], data=user_input
|
||||||
)
|
)
|
||||||
|
|
||||||
schema = self.user_schema or self.import_schema or update_schema_defaults({})
|
schema = self.user_schema or self.import_schema or _config_flow_schema({})
|
||||||
|
|
||||||
return self.async_show_form(step_id="user", data_schema=schema, errors=errors)
|
return self.async_show_form(step_id="user", data_schema=schema, errors=errors)
|
||||||
|
|
||||||
@ -127,20 +128,23 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
if entry.data[CONF_HOST] == import_config[CONF_HOST] and entry.data[
|
if entry.data[CONF_HOST] == import_config[CONF_HOST] and entry.data[
|
||||||
CONF_NAME
|
CONF_NAME
|
||||||
] == import_config.get(CONF_NAME):
|
] == import_config.get(CONF_NAME):
|
||||||
|
new_options = {}
|
||||||
|
|
||||||
if entry.data[CONF_VOLUME_STEP] != import_config[CONF_VOLUME_STEP]:
|
if entry.data[CONF_VOLUME_STEP] != import_config[CONF_VOLUME_STEP]:
|
||||||
new_volume_step = {
|
new_options[CONF_VOLUME_STEP] = import_config[CONF_VOLUME_STEP]
|
||||||
CONF_VOLUME_STEP: import_config[CONF_VOLUME_STEP]
|
|
||||||
}
|
if new_options:
|
||||||
self.hass.config_entries.async_update_entry(
|
self.hass.config_entries.async_update_entry(
|
||||||
entry=entry,
|
entry=entry,
|
||||||
data=entry.data.copy().update(new_volume_step),
|
data=entry.data.copy().update(new_options),
|
||||||
options=entry.options.copy().update(new_volume_step),
|
options=entry.options.copy().update(new_options),
|
||||||
)
|
)
|
||||||
return self.async_abort(reason="updated_volume_step")
|
return self.async_abort(reason="updated_options")
|
||||||
|
|
||||||
return self.async_abort(reason="already_setup")
|
return self.async_abort(reason="already_setup")
|
||||||
|
|
||||||
# Store import values in case setup fails so user can see error
|
# Store import values in case setup fails so user can see error
|
||||||
self.import_schema = update_schema_defaults(import_config)
|
self.import_schema = _config_flow_schema(import_config)
|
||||||
|
|
||||||
return await self.async_step_user(user_input=import_config)
|
return await self.async_step_user(user_input=import_config)
|
||||||
|
|
||||||
|
@ -1,11 +1,76 @@
|
|||||||
"""Constants used by vizio component."""
|
"""Constants used by vizio component."""
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from pyvizio.const import (
|
||||||
|
DEVICE_CLASS_SPEAKER as VIZIO_DEVICE_CLASS_SPEAKER,
|
||||||
|
DEVICE_CLASS_TV as VIZIO_DEVICE_CLASS_TV,
|
||||||
|
)
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.components.media_player import DEVICE_CLASS_SPEAKER, DEVICE_CLASS_TV
|
||||||
|
from homeassistant.components.media_player.const import (
|
||||||
|
SUPPORT_NEXT_TRACK,
|
||||||
|
SUPPORT_PREVIOUS_TRACK,
|
||||||
|
SUPPORT_SELECT_SOURCE,
|
||||||
|
SUPPORT_TURN_OFF,
|
||||||
|
SUPPORT_TURN_ON,
|
||||||
|
SUPPORT_VOLUME_MUTE,
|
||||||
|
SUPPORT_VOLUME_SET,
|
||||||
|
SUPPORT_VOLUME_STEP,
|
||||||
|
)
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_ACCESS_TOKEN,
|
||||||
|
CONF_DEVICE_CLASS,
|
||||||
|
CONF_HOST,
|
||||||
|
CONF_NAME,
|
||||||
|
)
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
CONF_VOLUME_STEP = "volume_step"
|
CONF_VOLUME_STEP = "volume_step"
|
||||||
|
|
||||||
|
DEFAULT_DEVICE_CLASS = DEVICE_CLASS_TV
|
||||||
DEFAULT_NAME = "Vizio SmartCast"
|
DEFAULT_NAME = "Vizio SmartCast"
|
||||||
DEFAULT_VOLUME_STEP = 1
|
DEFAULT_VOLUME_STEP = 1
|
||||||
DEFAULT_DEVICE_CLASS = "tv"
|
|
||||||
DEVICE_ID = "pyvizio"
|
DEVICE_ID = "pyvizio"
|
||||||
|
|
||||||
DOMAIN = "vizio"
|
DOMAIN = "vizio"
|
||||||
|
ICON = {DEVICE_CLASS_TV: "mdi:television", DEVICE_CLASS_SPEAKER: "mdi:speaker"}
|
||||||
|
|
||||||
ICON = {"tv": "mdi:television", "soundbar": "mdi:speaker"}
|
COMMON_SUPPORTED_COMMANDS = (
|
||||||
|
SUPPORT_SELECT_SOURCE
|
||||||
|
| SUPPORT_TURN_ON
|
||||||
|
| SUPPORT_TURN_OFF
|
||||||
|
| SUPPORT_VOLUME_MUTE
|
||||||
|
| SUPPORT_VOLUME_SET
|
||||||
|
| SUPPORT_VOLUME_STEP
|
||||||
|
)
|
||||||
|
|
||||||
|
SUPPORTED_COMMANDS = {
|
||||||
|
DEVICE_CLASS_SPEAKER: COMMON_SUPPORTED_COMMANDS,
|
||||||
|
DEVICE_CLASS_TV: (
|
||||||
|
COMMON_SUPPORTED_COMMANDS | SUPPORT_NEXT_TRACK | SUPPORT_PREVIOUS_TRACK
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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 = {
|
||||||
|
DEVICE_CLASS_SPEAKER: VIZIO_DEVICE_CLASS_SPEAKER,
|
||||||
|
DEVICE_CLASS_TV: VIZIO_DEVICE_CLASS_TV,
|
||||||
|
}
|
||||||
|
|
||||||
|
VIZIO_SCHEMA = {
|
||||||
|
vol.Required(CONF_HOST): cv.string,
|
||||||
|
vol.Optional(CONF_ACCESS_TOKEN): cv.string,
|
||||||
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||||
|
vol.Optional(CONF_DEVICE_CLASS, default=DEFAULT_DEVICE_CLASS): vol.All(
|
||||||
|
cv.string, vol.Lower, vol.In([DEVICE_CLASS_TV, DEVICE_CLASS_SPEAKER])
|
||||||
|
),
|
||||||
|
vol.Optional(CONF_VOLUME_STEP, default=DEFAULT_VOLUME_STEP): vol.All(
|
||||||
|
vol.Coerce(int), vol.Range(min=1, max=10)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1)
|
||||||
|
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
"""Vizio SmartCast Device support."""
|
"""Vizio SmartCast Device support."""
|
||||||
from datetime import timedelta
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Callable, List
|
from typing import Callable, List
|
||||||
|
|
||||||
@ -7,16 +6,6 @@ from pyvizio import VizioAsync
|
|||||||
|
|
||||||
from homeassistant import util
|
from homeassistant import util
|
||||||
from homeassistant.components.media_player import MediaPlayerDevice
|
from homeassistant.components.media_player import MediaPlayerDevice
|
||||||
from homeassistant.components.media_player.const import (
|
|
||||||
SUPPORT_NEXT_TRACK,
|
|
||||||
SUPPORT_PREVIOUS_TRACK,
|
|
||||||
SUPPORT_SELECT_SOURCE,
|
|
||||||
SUPPORT_TURN_OFF,
|
|
||||||
SUPPORT_TURN_ON,
|
|
||||||
SUPPORT_VOLUME_MUTE,
|
|
||||||
SUPPORT_VOLUME_SET,
|
|
||||||
SUPPORT_VOLUME_STEP,
|
|
||||||
)
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_ACCESS_TOKEN,
|
CONF_ACCESS_TOKEN,
|
||||||
@ -35,29 +24,23 @@ 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 .const import CONF_VOLUME_STEP, DEFAULT_VOLUME_STEP, DEVICE_ID, DOMAIN, ICON
|
from .const import (
|
||||||
|
CONF_VOLUME_STEP,
|
||||||
|
DEFAULT_VOLUME_STEP,
|
||||||
|
DEVICE_ID,
|
||||||
|
DOMAIN,
|
||||||
|
ICON,
|
||||||
|
MIN_TIME_BETWEEN_FORCED_SCANS,
|
||||||
|
MIN_TIME_BETWEEN_SCANS,
|
||||||
|
SUPPORTED_COMMANDS,
|
||||||
|
VIZIO_DEVICE_CLASSES,
|
||||||
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1)
|
|
||||||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
|
||||||
|
|
||||||
PARALLEL_UPDATES = 0
|
PARALLEL_UPDATES = 0
|
||||||
|
|
||||||
COMMON_SUPPORTED_COMMANDS = (
|
|
||||||
SUPPORT_SELECT_SOURCE
|
|
||||||
| SUPPORT_TURN_ON
|
|
||||||
| SUPPORT_TURN_OFF
|
|
||||||
| SUPPORT_VOLUME_MUTE
|
|
||||||
| SUPPORT_VOLUME_SET
|
|
||||||
| SUPPORT_VOLUME_STEP
|
|
||||||
)
|
|
||||||
|
|
||||||
SUPPORTED_COMMANDS = {
|
|
||||||
"soundbar": COMMON_SUPPORTED_COMMANDS,
|
|
||||||
"tv": (COMMON_SUPPORTED_COMMANDS | SUPPORT_NEXT_TRACK | SUPPORT_PREVIOUS_TRACK),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistantType,
|
hass: HomeAssistantType,
|
||||||
@ -68,10 +51,10 @@ async def async_setup_entry(
|
|||||||
host = config_entry.data[CONF_HOST]
|
host = config_entry.data[CONF_HOST]
|
||||||
token = config_entry.data.get(CONF_ACCESS_TOKEN)
|
token = config_entry.data.get(CONF_ACCESS_TOKEN)
|
||||||
name = config_entry.data[CONF_NAME]
|
name = config_entry.data[CONF_NAME]
|
||||||
device_type = config_entry.data[CONF_DEVICE_CLASS]
|
device_class = config_entry.data[CONF_DEVICE_CLASS]
|
||||||
|
|
||||||
# If config entry options not set up, set them up, otherwise assign values managed in options
|
# If config entry options not set up, set them up, otherwise assign values managed in options
|
||||||
if CONF_VOLUME_STEP not in config_entry.options:
|
if not config_entry.options:
|
||||||
volume_step = config_entry.data.get(CONF_VOLUME_STEP, DEFAULT_VOLUME_STEP)
|
volume_step = config_entry.data.get(CONF_VOLUME_STEP, DEFAULT_VOLUME_STEP)
|
||||||
hass.config_entries.async_update_entry(
|
hass.config_entries.async_update_entry(
|
||||||
config_entry, options={CONF_VOLUME_STEP: volume_step}
|
config_entry, options={CONF_VOLUME_STEP: volume_step}
|
||||||
@ -84,22 +67,24 @@ async def async_setup_entry(
|
|||||||
host,
|
host,
|
||||||
name,
|
name,
|
||||||
token,
|
token,
|
||||||
device_type,
|
VIZIO_DEVICE_CLASSES[device_class],
|
||||||
session=async_get_clientsession(hass, False),
|
session=async_get_clientsession(hass, False),
|
||||||
)
|
)
|
||||||
|
|
||||||
if not await device.can_connect():
|
if not await device.can_connect():
|
||||||
fail_auth_msg = ""
|
fail_auth_msg = ""
|
||||||
if token:
|
if token:
|
||||||
fail_auth_msg = ", auth token is correct"
|
fail_auth_msg = "and auth token '{token}' are correct."
|
||||||
|
else:
|
||||||
|
fail_auth_msg = "is correct."
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"Failed to set up Vizio platform, please check if host "
|
"Failed to connect to Vizio device, please check if host '{host}'"
|
||||||
"is valid and available, device type is correct%s",
|
"is valid and available. Also check if device class '{device_class}' %s",
|
||||||
fail_auth_msg,
|
fail_auth_msg,
|
||||||
)
|
)
|
||||||
raise PlatformNotReady
|
raise PlatformNotReady
|
||||||
|
|
||||||
entity = VizioDevice(config_entry, device, name, volume_step, device_type)
|
entity = VizioDevice(config_entry, device, name, volume_step, device_class)
|
||||||
|
|
||||||
async_add_entities([entity], True)
|
async_add_entities([entity], True)
|
||||||
|
|
||||||
@ -113,7 +98,7 @@ class VizioDevice(MediaPlayerDevice):
|
|||||||
device: VizioAsync,
|
device: VizioAsync,
|
||||||
name: str,
|
name: str,
|
||||||
volume_step: int,
|
volume_step: int,
|
||||||
device_type: str,
|
device_class: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize Vizio device."""
|
"""Initialize Vizio device."""
|
||||||
self._config_entry = config_entry
|
self._config_entry = config_entry
|
||||||
@ -125,11 +110,11 @@ class VizioDevice(MediaPlayerDevice):
|
|||||||
self._volume_step = volume_step
|
self._volume_step = volume_step
|
||||||
self._current_input = None
|
self._current_input = None
|
||||||
self._available_inputs = None
|
self._available_inputs = None
|
||||||
self._device_type = device_type
|
self._device_class = device_class
|
||||||
self._supported_commands = SUPPORTED_COMMANDS[device_type]
|
self._supported_commands = SUPPORTED_COMMANDS[device_class]
|
||||||
self._device = device
|
self._device = device
|
||||||
self._max_volume = float(self._device.get_max_volume())
|
self._max_volume = float(self._device.get_max_volume())
|
||||||
self._icon = ICON[device_type]
|
self._icon = ICON[device_class]
|
||||||
self._available = True
|
self._available = True
|
||||||
|
|
||||||
@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
|
@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
|
||||||
@ -169,7 +154,8 @@ class VizioDevice(MediaPlayerDevice):
|
|||||||
hass: HomeAssistantType, config_entry: ConfigEntry
|
hass: HomeAssistantType, config_entry: ConfigEntry
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Send update event when when Vizio config entry is updated."""
|
"""Send update event when when Vizio config entry is updated."""
|
||||||
# Move this method to component level if another entity ever gets added for a single config entry. See here: https://github.com/home-assistant/home-assistant/pull/30653#discussion_r366426121
|
# Move this method to component level if another entity ever gets added for a single config entry.
|
||||||
|
# See here: https://github.com/home-assistant/home-assistant/pull/30653#discussion_r366426121
|
||||||
async_dispatcher_send(hass, config_entry.entry_id, config_entry)
|
async_dispatcher_send(hass, config_entry.entry_id, config_entry)
|
||||||
|
|
||||||
async def _async_update_options(self, config_entry: ConfigEntry) -> None:
|
async def _async_update_options(self, config_entry: ConfigEntry) -> None:
|
||||||
@ -253,6 +239,11 @@ class VizioDevice(MediaPlayerDevice):
|
|||||||
"manufacturer": "VIZIO",
|
"manufacturer": "VIZIO",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_class(self):
|
||||||
|
"""Return device class for entity."""
|
||||||
|
return self._device_class
|
||||||
|
|
||||||
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()
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
"already_setup": "This entry has already been setup.",
|
"already_setup": "This entry has already been setup.",
|
||||||
"host_exists": "Vizio component with host already configured.",
|
"host_exists": "Vizio component with host already configured.",
|
||||||
"name_exists": "Vizio component with name already configured.",
|
"name_exists": "Vizio component with name already configured.",
|
||||||
"updated_volume_step": "This entry has already been setup but the volume step size in the config does not match the config entry so the config entry has been updated accordingly."
|
"updated_options": "This entry has already been setup but the options defined in the config do not match the previously imported options values so the config entry has been updated accordingly."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
@ -32,7 +32,8 @@
|
|||||||
"init": {
|
"init": {
|
||||||
"title": "Update Vizo SmartCast Options",
|
"title": "Update Vizo SmartCast Options",
|
||||||
"data": {
|
"data": {
|
||||||
"volume_step": "Volume Step Size"
|
"volume_step": "Volume Step Size",
|
||||||
|
"timeout": "API Request Timeout (seconds)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,12 +6,13 @@ import pytest
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import data_entry_flow
|
from homeassistant import data_entry_flow
|
||||||
from homeassistant.components.vizio import VIZIO_SCHEMA
|
from homeassistant.components.media_player import DEVICE_CLASS_SPEAKER, DEVICE_CLASS_TV
|
||||||
from homeassistant.components.vizio.const import (
|
from homeassistant.components.vizio.const import (
|
||||||
CONF_VOLUME_STEP,
|
CONF_VOLUME_STEP,
|
||||||
DEFAULT_NAME,
|
DEFAULT_NAME,
|
||||||
DEFAULT_VOLUME_STEP,
|
DEFAULT_VOLUME_STEP,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
|
VIZIO_SCHEMA,
|
||||||
)
|
)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_ACCESS_TOKEN,
|
CONF_ACCESS_TOKEN,
|
||||||
@ -27,20 +28,18 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
NAME = "Vizio"
|
NAME = "Vizio"
|
||||||
HOST = "192.168.1.1:9000"
|
HOST = "192.168.1.1:9000"
|
||||||
DEVICE_CLASS_TV = "tv"
|
|
||||||
DEVICE_CLASS_SOUNDBAR = "soundbar"
|
|
||||||
ACCESS_TOKEN = "deadbeef"
|
ACCESS_TOKEN = "deadbeef"
|
||||||
VOLUME_STEP = 2
|
VOLUME_STEP = 2
|
||||||
UNIQUE_ID = "testid"
|
UNIQUE_ID = "testid"
|
||||||
|
|
||||||
MOCK_USER_VALID_TV_ENTRY = {
|
MOCK_USER_VALID_TV_CONFIG = {
|
||||||
CONF_NAME: NAME,
|
CONF_NAME: NAME,
|
||||||
CONF_HOST: HOST,
|
CONF_HOST: HOST,
|
||||||
CONF_DEVICE_CLASS: DEVICE_CLASS_TV,
|
CONF_DEVICE_CLASS: DEVICE_CLASS_TV,
|
||||||
CONF_ACCESS_TOKEN: ACCESS_TOKEN,
|
CONF_ACCESS_TOKEN: ACCESS_TOKEN,
|
||||||
}
|
}
|
||||||
|
|
||||||
MOCK_IMPORT_VALID_TV_ENTRY = {
|
MOCK_IMPORT_VALID_TV_CONFIG = {
|
||||||
CONF_NAME: NAME,
|
CONF_NAME: NAME,
|
||||||
CONF_HOST: HOST,
|
CONF_HOST: HOST,
|
||||||
CONF_DEVICE_CLASS: DEVICE_CLASS_TV,
|
CONF_DEVICE_CLASS: DEVICE_CLASS_TV,
|
||||||
@ -48,16 +47,16 @@ MOCK_IMPORT_VALID_TV_ENTRY = {
|
|||||||
CONF_VOLUME_STEP: VOLUME_STEP,
|
CONF_VOLUME_STEP: VOLUME_STEP,
|
||||||
}
|
}
|
||||||
|
|
||||||
MOCK_INVALID_TV_ENTRY = {
|
MOCK_INVALID_TV_CONFIG = {
|
||||||
CONF_NAME: NAME,
|
CONF_NAME: NAME,
|
||||||
CONF_HOST: HOST,
|
CONF_HOST: HOST,
|
||||||
CONF_DEVICE_CLASS: DEVICE_CLASS_TV,
|
CONF_DEVICE_CLASS: DEVICE_CLASS_TV,
|
||||||
}
|
}
|
||||||
|
|
||||||
MOCK_SOUNDBAR_ENTRY = {
|
MOCK_SPEAKER_CONFIG = {
|
||||||
CONF_NAME: NAME,
|
CONF_NAME: NAME,
|
||||||
CONF_HOST: HOST,
|
CONF_HOST: HOST,
|
||||||
CONF_DEVICE_CLASS: DEVICE_CLASS_SOUNDBAR,
|
CONF_DEVICE_CLASS: DEVICE_CLASS_SPEAKER,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -100,7 +99,7 @@ async def test_user_flow_minimum_fields(hass: HomeAssistantType, vizio_connect)
|
|||||||
user_input={
|
user_input={
|
||||||
CONF_NAME: NAME,
|
CONF_NAME: NAME,
|
||||||
CONF_HOST: HOST,
|
CONF_HOST: HOST,
|
||||||
CONF_DEVICE_CLASS: DEVICE_CLASS_SOUNDBAR,
|
CONF_DEVICE_CLASS: DEVICE_CLASS_SPEAKER,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -108,7 +107,7 @@ async def test_user_flow_minimum_fields(hass: HomeAssistantType, vizio_connect)
|
|||||||
assert result["title"] == NAME
|
assert result["title"] == NAME
|
||||||
assert result["data"][CONF_NAME] == NAME
|
assert result["data"][CONF_NAME] == NAME
|
||||||
assert result["data"][CONF_HOST] == HOST
|
assert result["data"][CONF_HOST] == HOST
|
||||||
assert result["data"][CONF_DEVICE_CLASS] == DEVICE_CLASS_SOUNDBAR
|
assert result["data"][CONF_DEVICE_CLASS] == DEVICE_CLASS_SPEAKER
|
||||||
|
|
||||||
|
|
||||||
async def test_user_flow_all_fields(hass: HomeAssistantType, vizio_connect) -> None:
|
async def test_user_flow_all_fields(hass: HomeAssistantType, vizio_connect) -> None:
|
||||||
@ -139,17 +138,40 @@ async def test_user_flow_all_fields(hass: HomeAssistantType, vizio_connect) -> N
|
|||||||
assert result["data"][CONF_ACCESS_TOKEN] == ACCESS_TOKEN
|
assert result["data"][CONF_ACCESS_TOKEN] == ACCESS_TOKEN
|
||||||
|
|
||||||
|
|
||||||
|
async def test_options_flow(hass: HomeAssistantType) -> None:
|
||||||
|
"""Test options config flow."""
|
||||||
|
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_SPEAKER_CONFIG)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
assert not entry.options
|
||||||
|
|
||||||
|
result = await hass.config_entries.options.async_init(
|
||||||
|
entry.entry_id, context={"source": "test"}, data=None
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result["step_id"] == "init"
|
||||||
|
|
||||||
|
result = await hass.config_entries.options.async_configure(
|
||||||
|
result["flow_id"], user_input={CONF_VOLUME_STEP: VOLUME_STEP},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
assert result["title"] == ""
|
||||||
|
assert result["data"][CONF_VOLUME_STEP] == VOLUME_STEP
|
||||||
|
|
||||||
|
|
||||||
async def test_user_host_already_configured(
|
async def test_user_host_already_configured(
|
||||||
hass: HomeAssistantType, vizio_connect
|
hass: HomeAssistantType, vizio_connect
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test host is already configured during user setup."""
|
"""Test host is already configured during user setup."""
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
data=MOCK_SOUNDBAR_ENTRY,
|
data=MOCK_SPEAKER_CONFIG,
|
||||||
options={CONF_VOLUME_STEP: VOLUME_STEP},
|
options={CONF_VOLUME_STEP: VOLUME_STEP},
|
||||||
)
|
)
|
||||||
entry.add_to_hass(hass)
|
entry.add_to_hass(hass)
|
||||||
fail_entry = MOCK_SOUNDBAR_ENTRY.copy()
|
fail_entry = MOCK_SPEAKER_CONFIG.copy()
|
||||||
fail_entry[CONF_NAME] = "newtestname"
|
fail_entry[CONF_NAME] = "newtestname"
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
@ -160,7 +182,7 @@ async def test_user_host_already_configured(
|
|||||||
assert result["step_id"] == "user"
|
assert result["step_id"] == "user"
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_configure(
|
result = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"], user_input=fail_entry,
|
result["flow_id"], user_input=fail_entry
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
@ -173,12 +195,12 @@ async def test_user_name_already_configured(
|
|||||||
"""Test name is already configured during user setup."""
|
"""Test name is already configured during user setup."""
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
data=MOCK_SOUNDBAR_ENTRY,
|
data=MOCK_SPEAKER_CONFIG,
|
||||||
options={CONF_VOLUME_STEP: VOLUME_STEP},
|
options={CONF_VOLUME_STEP: VOLUME_STEP},
|
||||||
)
|
)
|
||||||
entry.add_to_hass(hass)
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
fail_entry = MOCK_SOUNDBAR_ENTRY.copy()
|
fail_entry = MOCK_SPEAKER_CONFIG.copy()
|
||||||
fail_entry[CONF_HOST] = "0.0.0.0"
|
fail_entry[CONF_HOST] = "0.0.0.0"
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
@ -207,7 +229,7 @@ async def test_user_error_on_could_not_connect(
|
|||||||
assert result["step_id"] == "user"
|
assert result["step_id"] == "user"
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_configure(
|
result = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"], MOCK_USER_VALID_TV_ENTRY
|
result["flow_id"], MOCK_USER_VALID_TV_CONFIG
|
||||||
)
|
)
|
||||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
assert result["errors"] == {"base": "cant_connect"}
|
assert result["errors"] == {"base": "cant_connect"}
|
||||||
@ -225,7 +247,7 @@ async def test_user_error_on_tv_needs_token(
|
|||||||
assert result["step_id"] == "user"
|
assert result["step_id"] == "user"
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_configure(
|
result = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"], MOCK_INVALID_TV_ENTRY
|
result["flow_id"], MOCK_INVALID_TV_CONFIG
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
@ -240,7 +262,7 @@ async def test_import_flow_minimum_fields(
|
|||||||
DOMAIN,
|
DOMAIN,
|
||||||
context={"source": "import"},
|
context={"source": "import"},
|
||||||
data=vol.Schema(VIZIO_SCHEMA)(
|
data=vol.Schema(VIZIO_SCHEMA)(
|
||||||
{CONF_HOST: HOST, CONF_DEVICE_CLASS: DEVICE_CLASS_SOUNDBAR}
|
{CONF_HOST: HOST, CONF_DEVICE_CLASS: DEVICE_CLASS_SPEAKER}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -248,7 +270,7 @@ async def test_import_flow_minimum_fields(
|
|||||||
assert result["title"] == DEFAULT_NAME
|
assert result["title"] == DEFAULT_NAME
|
||||||
assert result["data"][CONF_NAME] == DEFAULT_NAME
|
assert result["data"][CONF_NAME] == DEFAULT_NAME
|
||||||
assert result["data"][CONF_HOST] == HOST
|
assert result["data"][CONF_HOST] == HOST
|
||||||
assert result["data"][CONF_DEVICE_CLASS] == DEVICE_CLASS_SOUNDBAR
|
assert result["data"][CONF_DEVICE_CLASS] == DEVICE_CLASS_SPEAKER
|
||||||
assert result["data"][CONF_VOLUME_STEP] == DEFAULT_VOLUME_STEP
|
assert result["data"][CONF_VOLUME_STEP] == DEFAULT_VOLUME_STEP
|
||||||
|
|
||||||
|
|
||||||
@ -257,7 +279,7 @@ async def test_import_flow_all_fields(hass: HomeAssistantType, vizio_connect) ->
|
|||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
context={"source": "import"},
|
context={"source": "import"},
|
||||||
data=vol.Schema(VIZIO_SCHEMA)(MOCK_IMPORT_VALID_TV_ENTRY),
|
data=vol.Schema(VIZIO_SCHEMA)(MOCK_IMPORT_VALID_TV_CONFIG),
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
@ -275,11 +297,11 @@ async def test_import_entity_already_configured(
|
|||||||
"""Test entity is already configured during import setup."""
|
"""Test entity is already configured during import setup."""
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
data=vol.Schema(VIZIO_SCHEMA)(MOCK_SOUNDBAR_ENTRY),
|
data=vol.Schema(VIZIO_SCHEMA)(MOCK_SPEAKER_CONFIG),
|
||||||
options={CONF_VOLUME_STEP: VOLUME_STEP},
|
options={CONF_VOLUME_STEP: VOLUME_STEP},
|
||||||
)
|
)
|
||||||
entry.add_to_hass(hass)
|
entry.add_to_hass(hass)
|
||||||
fail_entry = vol.Schema(VIZIO_SCHEMA)(MOCK_SOUNDBAR_ENTRY.copy())
|
fail_entry = vol.Schema(VIZIO_SCHEMA)(MOCK_SPEAKER_CONFIG.copy())
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": "import"}, data=fail_entry
|
DOMAIN, context={"source": "import"}, data=fail_entry
|
||||||
|
Loading…
x
Reference in New Issue
Block a user