diff --git a/.coveragerc b/.coveragerc index fcb09069267..2d1bff462b9 100644 --- a/.coveragerc +++ b/.coveragerc @@ -350,6 +350,7 @@ omit = homeassistant/components/media_player/sonos.py homeassistant/components/media_player/spotify.py homeassistant/components/media_player/squeezebox.py + homeassistant/components/media_player/vizio.py homeassistant/components/media_player/vlc.py homeassistant/components/media_player/volumio.py homeassistant/components/media_player/yamaha.py diff --git a/homeassistant/components/media_player/vizio.py b/homeassistant/components/media_player/vizio.py new file mode 100644 index 00000000000..4ae8f037a4f --- /dev/null +++ b/homeassistant/components/media_player/vizio.py @@ -0,0 +1,189 @@ +""" +Vizio SmartCast TV support. + +Usually only 2016+ models come with SmartCast capabilities. +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/media_player.vizio/ +""" +import logging +from datetime import timedelta + +import voluptuous as vol + +import homeassistant.util as util +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, + SUPPORT_TURN_ON, + SUPPORT_TURN_OFF, + SUPPORT_SELECT_SOURCE, + SUPPORT_PREVIOUS_TRACK, + SUPPORT_NEXT_TRACK, + SUPPORT_VOLUME_MUTE, + SUPPORT_VOLUME_STEP, + MediaPlayerDevice +) +from homeassistant.const import ( + STATE_UNKNOWN, + STATE_OFF, + STATE_ON, + CONF_NAME, + CONF_HOST, + CONF_ACCESS_TOKEN +) +from homeassistant.helpers import config_validation as cv + +REQUIREMENTS = ['pyvizio==0.0.2'] + +_LOGGER = logging.getLogger(__name__) + +CONF_SUPPRESS_WARNING = 'suppress_warning' +CONF_VOLUME_STEP = 'volume_step' + +ICON = 'mdi:television' +DEFAULT_NAME = 'Vizio SmartCast' +DEFAULT_VOLUME_STEP = 1 +DEVICE_NAME = 'Python Vizio' +DEVICE_ID = 'pyvizio' +MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) +MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1) +SUPPORTED_COMMANDS = SUPPORT_TURN_ON | SUPPORT_TURN_OFF \ + | SUPPORT_SELECT_SOURCE \ + | SUPPORT_NEXT_TRACK | SUPPORT_PREVIOUS_TRACK \ + | SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_STEP + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_HOST): cv.string, + vol.Required(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_VOLUME_STEP, default=DEFAULT_VOLUME_STEP): + vol.All(vol.Coerce(int), vol.Range(min=1, max=10)), +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the VizioTV media player platform.""" + host = config.get(CONF_HOST) + token = config.get(CONF_ACCESS_TOKEN) + name = config.get(CONF_NAME) + volume_step = config.get(CONF_VOLUME_STEP) + + device = VizioDevice(host, token, name, volume_step) + if device.validate_setup() is False: + _LOGGER.error('Failed to setup Vizio TV platform, ' + 'please check if host and API key are correct.') + return False + + if config.get(CONF_SUPPRESS_WARNING): + import requests + from requests.packages.urllib3.exceptions import InsecureRequestWarning + _LOGGER.warning('InsecureRequestWarning is disabled ' + 'because of Vizio platform configuration.') + requests.packages.urllib3.disable_warnings(InsecureRequestWarning) + add_devices([device], True) + + +class VizioDevice(MediaPlayerDevice): + """Media Player implementation which performs REST requests to TV.""" + + def __init__(self, host, token, name, volume_step): + """Initialize Vizio device.""" + import pyvizio + self._device = pyvizio.Vizio(DEVICE_ID, host, DEFAULT_NAME, token) + self._name = name + self._state = STATE_UNKNOWN + self._volume_level = None + self._volume_step = volume_step + self._current_input = None + self._available_inputs = None + + @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS) + def update(self): + """Retrieve latest state of the TV.""" + is_on = self._device.get_power_state() + if is_on is None: + self._state = STATE_UNKNOWN + return + elif is_on is False: + self._state = STATE_OFF + else: + self._state = STATE_ON + + self._volume_level = self._device.get_current_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 = [] + for input_ in inputs: + self._available_inputs.append(input_.name) + + @property + def state(self): + """Return the state of the TV.""" + return self._state + + @property + def name(self): + """Return the name of the TV.""" + return self._name + + @property + def volume_level(self): + """Return the volume level of the TV.""" + return self._volume_level + + @property + def source(self): + """Return current input of the TV.""" + return self._current_input + + @property + def source_list(self): + """Return list of available inputs of the TV.""" + return self._available_inputs + + @property + def supported_features(self): + """Flag TV features that are supported.""" + return SUPPORTED_COMMANDS + + def turn_on(self): + """Turn the TV player on.""" + self._device.pow_on() + + def turn_off(self): + """Turn the TV player 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 TV.""" + self._device.vol_up(num=self._volume_step) + + def volume_down(self): + """Decreasing volume of the TV.""" + self._device.vol_down(num=self._volume_step) + + def validate_setup(self): + """Validating if host is available and key is correct.""" + return self._device.get_current_volume() is not None diff --git a/requirements_all.txt b/requirements_all.txt index 7c7f35a7fb8..e553a6aa2ab 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -772,6 +772,9 @@ pyunifi==2.13 # homeassistant.components.vera pyvera==0.2.34 +# homeassistant.components.media_player.vizio +pyvizio==0.0.2 + # homeassistant.components.velux pyvlx==0.1.3