Convert vizio component from sync to async component (#30605)

* add device_info property and move component to async

* use new VizioAsync class to have proper async support

* remove hass from VizioDevice init since it is not needed

* update requirements_all

* missed type hint

* updates based on review

* pyvizio version bump

* additional fixes based on review

* mistake in last commit

* remove device_info property because it can't be used unless this integration has config flow support
This commit is contained in:
Raman Gupta 2020-01-09 21:53:47 -05:00 committed by Martin Hjelmare
parent ef05aa2f39
commit d25aa1f183
5 changed files with 86 additions and 52 deletions

View File

@ -1,4 +1,5 @@
"""The vizio component.""" """The vizio component."""
import voluptuous as vol import voluptuous as vol
from homeassistant.const import ( from homeassistant.const import (
@ -8,6 +9,7 @@ from homeassistant.const import (
CONF_NAME, CONF_NAME,
) )
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.typing import ConfigType
from .const import ( from .const import (
CONF_VOLUME_STEP, CONF_VOLUME_STEP,
@ -17,7 +19,7 @@ from .const import (
) )
def validate_auth(config): 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=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] == "tv" and not token:
@ -25,6 +27,7 @@ def validate_auth(config):
f"When '{CONF_DEVICE_CLASS}' is 'tv' then '{CONF_ACCESS_TOKEN}' is required.", f"When '{CONF_DEVICE_CLASS}' is 'tv' then '{CONF_ACCESS_TOKEN}' is required.",
path=[CONF_ACCESS_TOKEN], path=[CONF_ACCESS_TOKEN],
) )
return config return config

View File

@ -6,7 +6,6 @@ DEFAULT_NAME = "Vizio SmartCast"
DEFAULT_VOLUME_STEP = 1 DEFAULT_VOLUME_STEP = 1
DEFAULT_DEVICE_CLASS = "tv" DEFAULT_DEVICE_CLASS = "tv"
DEVICE_ID = "pyvizio" DEVICE_ID = "pyvizio"
DEVICE_NAME = "Python Vizio"
DOMAIN = "vizio" DOMAIN = "vizio"

View File

@ -2,7 +2,7 @@
"domain": "vizio", "domain": "vizio",
"name": "Vizio SmartCast TV", "name": "Vizio SmartCast TV",
"documentation": "https://www.home-assistant.io/integrations/vizio", "documentation": "https://www.home-assistant.io/integrations/vizio",
"requirements": ["pyvizio==0.0.12"], "requirements": ["pyvizio==0.0.15"],
"dependencies": [], "dependencies": [],
"codeowners": ["@raman325"] "codeowners": ["@raman325"]
} }

View File

@ -1,8 +1,10 @@
"""Vizio SmartCast Device support.""" """Vizio SmartCast Device support."""
from datetime import timedelta from datetime import timedelta
import logging import logging
from typing import Any, Callable, Dict, List
from pyvizio import Vizio from pyvizio import VizioAsync
import voluptuous as vol import voluptuous as vol
from homeassistant import util from homeassistant import util
@ -25,9 +27,12 @@ from homeassistant.const import (
STATE_OFF, STATE_OFF,
STATE_ON, STATE_ON,
) )
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
from . import VIZIO_SCHEMA, validate_auth from . import VIZIO_SCHEMA, validate_auth
from .const import CONF_VOLUME_STEP, DEFAULT_NAME, DEVICE_ID, ICON from .const import CONF_VOLUME_STEP, DEVICE_ID, ICON
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -52,32 +57,43 @@ SUPPORTED_COMMANDS = {
PLATFORM_SCHEMA = vol.All(PLATFORM_SCHEMA.extend(VIZIO_SCHEMA), validate_auth) PLATFORM_SCHEMA = vol.All(PLATFORM_SCHEMA.extend(VIZIO_SCHEMA), validate_auth)
def setup_platform(hass, config, add_entities, discovery_info=None): async def async_setup_platform(
hass: HomeAssistantType,
config: ConfigType,
async_add_entities: Callable[[List[Entity], bool], None],
discovery_info: Dict[str, Any] = None,
):
"""Set up the Vizio media player platform.""" """Set up the Vizio media player platform."""
host = config[CONF_HOST] host = config[CONF_HOST]
token = config.get(CONF_ACCESS_TOKEN) token = config.get(CONF_ACCESS_TOKEN)
name = config[CONF_NAME] name = config[CONF_NAME]
volume_step = config[CONF_VOLUME_STEP] volume_step = config[CONF_VOLUME_STEP]
device_type = config[CONF_DEVICE_CLASS] device_type = config[CONF_DEVICE_CLASS]
device = VizioDevice(host, token, name, volume_step, device_type)
if not device.validate_setup(): device = VizioAsync(
DEVICE_ID, host, name, token, device_type, async_get_clientsession(hass, False)
)
if not await device.can_connect():
fail_auth_msg = "" fail_auth_msg = ""
if token: if token:
fail_auth_msg = " and auth token is correct" fail_auth_msg = ", auth token is correct"
_LOGGER.error( _LOGGER.error(
"Failed to set up Vizio platform, please check if host " "Failed to set up Vizio platform, please check if host "
"is valid and available%s", "is valid and available, device type is correct%s",
fail_auth_msg, fail_auth_msg,
) )
return return
add_entities([device], True) async_add_entities([VizioDevice(device, name, volume_step, device_type)], True)
class VizioDevice(MediaPlayerDevice): class VizioDevice(MediaPlayerDevice):
"""Media Player implementation which performs REST requests to device.""" """Media Player implementation which performs REST requests to device."""
def __init__(self, host, token, name, volume_step, device_type): def __init__(
self, device: VizioAsync, name: str, volume_step: int, device_type: str
) -> None:
"""Initialize Vizio device.""" """Initialize Vizio device."""
self._name = name self._name = name
@ -88,31 +104,32 @@ class VizioDevice(MediaPlayerDevice):
self._available_inputs = None self._available_inputs = None
self._device_type = device_type self._device_type = device_type
self._supported_commands = SUPPORTED_COMMANDS[device_type] self._supported_commands = SUPPORTED_COMMANDS[device_type]
self._device = Vizio(DEVICE_ID, host, DEFAULT_NAME, token, device_type) self._device = device
self._max_volume = float(self._device.get_max_volume()) self._max_volume = float(self._device.get_max_volume())
self._unique_id = None self._unique_id = None
self._icon = ICON[device_type] self._icon = ICON[device_type]
@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS) @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
def update(self): async def async_update(self) -> None:
"""Retrieve latest state of the device.""" """Retrieve latest state of the device."""
is_on = self._device.get_power_state()
if not self._unique_id: if not self._unique_id:
self._unique_id = self._device.get_esn() self._unique_id = await self._device.get_esn()
is_on = await self._device.get_power_state()
if is_on: if is_on:
self._state = STATE_ON self._state = STATE_ON
volume = self._device.get_current_volume() volume = await self._device.get_current_volume()
if volume is not None: if volume is not None:
self._volume_level = float(volume) / self._max_volume self._volume_level = float(volume) / self._max_volume
input_ = self._device.get_current_input() input_ = await self._device.get_current_input()
if input_ is not None: if input_ is not None:
self._current_input = input_.meta_name self._current_input = input_.meta_name
inputs = self._device.get_inputs() inputs = await self._device.get_inputs()
if inputs is not None: if inputs is not None:
self._available_inputs = [input_.name for input_ in inputs] self._available_inputs = [input_.name for input_ in inputs]
@ -127,100 +144,115 @@ class VizioDevice(MediaPlayerDevice):
self._available_inputs = None self._available_inputs = None
@property @property
def state(self): def state(self) -> str:
"""Return the state of the device.""" """Return the state of the device."""
return self._state return self._state
@property @property
def name(self): def name(self) -> str:
"""Return the name of the device.""" """Return the name of the device."""
return self._name return self._name
@property @property
def icon(self): def icon(self) -> str:
"""Return the icon of the device.""" """Return the icon of the device."""
return self._icon return self._icon
@property @property
def volume_level(self): def volume_level(self) -> float:
"""Return the volume level of the device.""" """Return the volume level of the device."""
return self._volume_level return self._volume_level
@property @property
def source(self): def source(self) -> str:
"""Return current input of the device.""" """Return current input of the device."""
return self._current_input return self._current_input
@property @property
def source_list(self): def source_list(self) -> List:
"""Return list of available inputs of the device.""" """Return list of available inputs of the device."""
return self._available_inputs return self._available_inputs
@property @property
def supported_features(self): def supported_features(self) -> int:
"""Flag device features that are supported.""" """Flag device features that are supported."""
return self._supported_commands return self._supported_commands
@property @property
def unique_id(self): def unique_id(self) -> str:
"""Return the unique id of the device.""" """Return the unique id of the device."""
return self._unique_id return self._unique_id
def turn_on(self): async def async_turn_on(self) -> None:
"""Turn the device on.""" """Turn the device on."""
self._device.pow_on()
def turn_off(self): await self._device.pow_on()
async def async_turn_off(self) -> None:
"""Turn the device off.""" """Turn the device off."""
self._device.pow_off()
def mute_volume(self, mute): await self._device.pow_off()
async def async_mute_volume(self, mute: bool) -> None:
"""Mute the volume.""" """Mute the volume."""
if mute: if mute:
self._device.mute_on() await self._device.mute_on()
else: else:
self._device.mute_off() await self._device.mute_off()
def media_previous_track(self): async def async_media_previous_track(self) -> None:
"""Send previous channel command.""" """Send previous channel command."""
self._device.ch_down()
def media_next_track(self): await self._device.ch_down()
async def async_media_next_track(self) -> None:
"""Send next channel command.""" """Send next channel command."""
self._device.ch_up()
def select_source(self, source): await self._device.ch_up()
async def async_select_source(self, source: str) -> None:
"""Select input source.""" """Select input source."""
self._device.input_switch(source)
def volume_up(self): await self._device.input_switch(source)
async def async_volume_up(self) -> None:
"""Increasing volume of the device.""" """Increasing volume of the device."""
self._device.vol_up(num=self._volume_step)
await self._device.vol_up(self._volume_step)
if self._volume_level is not None: if self._volume_level is not None:
self._volume_level = min( self._volume_level = min(
1.0, self._volume_level + self._volume_step / self._max_volume 1.0, self._volume_level + self._volume_step / self._max_volume
) )
def volume_down(self): async def async_volume_down(self) -> None:
"""Decreasing volume of the device.""" """Decreasing volume of the device."""
self._device.vol_down(num=self._volume_step)
await self._device.vol_down(self._volume_step)
if self._volume_level is not None: if self._volume_level is not None:
self._volume_level = max( self._volume_level = max(
0.0, self._volume_level - self._volume_step / self._max_volume 0.0, self._volume_level - self._volume_step / self._max_volume
) )
def validate_setup(self): async def async_set_volume_level(self, volume: float) -> None:
"""Validate if host is available and auth token is correct."""
return self._device.can_connect()
def set_volume_level(self, volume):
"""Set volume level.""" """Set volume level."""
if self._volume_level is not None: if self._volume_level is not None:
if volume > self._volume_level: if volume > self._volume_level:
num = int(self._max_volume * (volume - self._volume_level)) num = int(self._max_volume * (volume - self._volume_level))
await self._device.vol_up(num)
self._volume_level = volume self._volume_level = volume
self._device.vol_up(num=num)
elif volume < self._volume_level: elif volume < self._volume_level:
num = int(self._max_volume * (self._volume_level - volume)) num = int(self._max_volume * (self._volume_level - volume))
await self._device.vol_down(num)
self._volume_level = volume self._volume_level = volume
self._device.vol_down(num=num)

View File

@ -1696,7 +1696,7 @@ pyversasense==0.0.6
pyvesync==1.1.0 pyvesync==1.1.0
# homeassistant.components.vizio # homeassistant.components.vizio
pyvizio==0.0.12 pyvizio==0.0.15
# homeassistant.components.velux # homeassistant.components.velux
pyvlx==0.2.12 pyvlx==0.2.12