core/homeassistant/components/vizio/media_player.py

257 lines
7.8 KiB
Python

"""Vizio SmartCast Device support."""
from datetime import timedelta
import logging
from pyvizio import Vizio
from requests.packages import urllib3
import voluptuous as vol
from homeassistant import util
from homeassistant.components.media_player import PLATFORM_SCHEMA, 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.const import (
CONF_ACCESS_TOKEN,
CONF_DEVICE_CLASS,
CONF_HOST,
CONF_NAME,
STATE_OFF,
STATE_ON,
)
from homeassistant.helpers import config_validation as cv
_LOGGER = logging.getLogger(__name__)
CONF_SUPPRESS_WARNING = "suppress_warning"
CONF_VOLUME_STEP = "volume_step"
DEFAULT_NAME = "Vizio SmartCast"
DEFAULT_VOLUME_STEP = 1
DEFAULT_DEVICE_CLASS = "tv"
DEVICE_ID = "pyvizio"
DEVICE_NAME = "Python Vizio"
ICON = "mdi:television"
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1)
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
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),
}
def validate_auth(config):
"""Validate presence of CONF_ACCESS_TOKEN when CONF_DEVICE_CLASS=tv."""
token = config.get(CONF_ACCESS_TOKEN)
if config[CONF_DEVICE_CLASS] == "tv" and (token is None or token == ""):
raise vol.Invalid(
"When '{}' is 'tv' then '{}' is required.".format(
CONF_DEVICE_CLASS, CONF_ACCESS_TOKEN
),
path=[CONF_ACCESS_TOKEN],
)
return config
PLATFORM_SCHEMA = vol.All(
PLATFORM_SCHEMA.extend(
{
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_SUPPRESS_WARNING, default=False): cv.boolean,
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)
),
}
),
validate_auth,
)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Vizio media player platform."""
host = config[CONF_HOST]
token = config.get(CONF_ACCESS_TOKEN)
name = config[CONF_NAME]
volume_step = config[CONF_VOLUME_STEP]
device_type = config[CONF_DEVICE_CLASS]
device = VizioDevice(host, token, name, volume_step, device_type)
if device.validate_setup() is False:
fail_auth_msg = ""
if token is not None and token != "":
fail_auth_msg = " and auth token is correct"
_LOGGER.error(
"Failed to set up Vizio platform, please check if host "
"is valid and available%s",
fail_auth_msg,
)
return
if config[CONF_SUPPRESS_WARNING]:
_LOGGER.warning(
"InsecureRequestWarning is disabled "
"because of Vizio platform configuration"
)
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
add_entities([device], True)
class VizioDevice(MediaPlayerDevice):
"""Media Player implementation which performs REST requests to device."""
def __init__(self, host, token, name, volume_step, device_type):
"""Initialize Vizio device."""
self._name = name
self._state = None
self._volume_level = None
self._volume_step = volume_step
self._current_input = None
self._available_inputs = None
self._device_type = device_type
self._supported_commands = SUPPORTED_COMMANDS[device_type]
self._device = Vizio(DEVICE_ID, host, DEFAULT_NAME, token, device_type)
self._max_volume = float(self._device.get_max_volume())
@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
def update(self):
"""Retrieve latest state of the device."""
is_on = self._device.get_power_state()
if is_on:
self._state = STATE_ON
volume = self._device.get_current_volume()
if volume is not None:
self._volume_level = float(volume) / self._max_volume
input_ = self._device.get_current_input()
if input_ is not None:
self._current_input = input_.meta_name
inputs = self._device.get_inputs()
if inputs is not None:
self._available_inputs = [input_.name for input_ in inputs]
else:
if is_on is None:
self._state = None
else:
self._state = STATE_OFF
self._volume_level = None
self._current_input = None
self._available_inputs = None
@property
def state(self):
"""Return the state of the device."""
return self._state
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def volume_level(self):
"""Return the volume level of the device."""
return self._volume_level
@property
def source(self):
"""Return current input of the device."""
return self._current_input
@property
def source_list(self):
"""Return list of available inputs of the device."""
return self._available_inputs
@property
def supported_features(self):
"""Flag device features that are supported."""
return self._supported_commands
def turn_on(self):
"""Turn the device on."""
self._device.pow_on()
def turn_off(self):
"""Turn the device off."""
self._device.pow_off()
def mute_volume(self, mute):
"""Mute the volume."""
if mute:
self._device.mute_on()
else:
self._device.mute_off()
def media_previous_track(self):
"""Send previous channel command."""
self._device.ch_down()
def media_next_track(self):
"""Send next channel command."""
self._device.ch_up()
def select_source(self, source):
"""Select input source."""
self._device.input_switch(source)
def volume_up(self):
"""Increasing volume of the device."""
self._device.vol_up(num=self._volume_step)
if self._volume_level is not None:
self._volume_level = min(
1.0, self._volume_level + self._volume_step / self._max_volume
)
def volume_down(self):
"""Decreasing volume of the device."""
self._device.vol_down(num=self._volume_step)
if self._volume_level is not None:
self._volume_level = max(
0.0, self._volume_level - self._volume_step / self._max_volume
)
def validate_setup(self):
"""Validate if host is available and auth token is correct."""
return self._device.get_current_volume() is not None
def set_volume_level(self, volume):
"""Set volume level."""
if self._volume_level is not None:
if volume > self._volume_level:
num = int(self._max_volume * (volume - self._volume_level))
self._volume_level = volume
self._device.vol_up(num=num)
elif volume < self._volume_level:
num = int(self._max_volume * (self._volume_level - volume))
self._volume_level = volume
self._device.vol_down(num=num)