From f7b401a20e9acb7b3705942d81dd82bbf2ebf8eb Mon Sep 17 00:00:00 2001 From: wokar Date: Fri, 20 May 2016 08:27:47 +0200 Subject: [PATCH] Added the lg_netcast platform to control a LG Smart TV running NetCast 3.0 or 4.0 (#2081) * Added the `lgtv` platform to control a LG Smart TV running NetCast 3.0 (LG Smart TV models released in 2012) and NetCast 4.0 (LG Smart TV models released in 2013). * Fixed multi-line docstring closing quotes * Rename lgtv to lg_netcast * Rename lgtv to lg_netcast * Extracted class to control the LG TV into a separate Python package 'pylgnetcast' and changed requirements accordingly. * regenerated requirements_all.txt with script * now uses pylgnetcast v0.2.0 which uses the requests package for the communication with the TV * fixed lint error: Catching too general exception Exception --- .coveragerc | 1 + .../components/media_player/lg_netcast.py | 210 ++++++++++++++++++ requirements_all.txt | 3 + 3 files changed, 214 insertions(+) create mode 100644 homeassistant/components/media_player/lg_netcast.py diff --git a/.coveragerc b/.coveragerc index 29dd544f911..734a5c7b78d 100644 --- a/.coveragerc +++ b/.coveragerc @@ -117,6 +117,7 @@ omit = homeassistant/components/media_player/gpmdp.py homeassistant/components/media_player/itunes.py homeassistant/components/media_player/kodi.py + homeassistant/components/media_player/lg_netcast.py homeassistant/components/media_player/mpd.py homeassistant/components/media_player/onkyo.py homeassistant/components/media_player/panasonic_viera.py diff --git a/homeassistant/components/media_player/lg_netcast.py b/homeassistant/components/media_player/lg_netcast.py new file mode 100644 index 00000000000..fa215731d0d --- /dev/null +++ b/homeassistant/components/media_player/lg_netcast.py @@ -0,0 +1,210 @@ +""" +Support for LG TV running on NetCast 3 or 4. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/media_player.lg_netcast/ +""" +from datetime import timedelta +import logging + +from requests import RequestException +import voluptuous as vol +import homeassistant.helpers.config_validation as cv +from homeassistant.components.media_player import ( + SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, + SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, + SUPPORT_SELECT_SOURCE, MEDIA_TYPE_CHANNEL, MediaPlayerDevice) +from homeassistant.const import ( + CONF_PLATFORM, CONF_HOST, CONF_NAME, CONF_ACCESS_TOKEN, + STATE_OFF, STATE_PLAYING, STATE_PAUSED, STATE_UNKNOWN) +import homeassistant.util as util + +_LOGGER = logging.getLogger(__name__) + +REQUIREMENTS = ['https://github.com/wokar/pylgnetcast/archive/' + 'v0.2.0.zip#pylgnetcast==0.2.0'] + +SUPPORT_LGTV = SUPPORT_PAUSE | SUPPORT_VOLUME_STEP | \ + SUPPORT_VOLUME_MUTE | SUPPORT_PREVIOUS_TRACK | \ + SUPPORT_NEXT_TRACK | SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE + +MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) +MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1) + +DEFAULT_NAME = 'LG TV Remote' + +PLATFORM_SCHEMA = vol.Schema({ + vol.Required(CONF_PLATFORM): "lg_netcast", + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_ACCESS_TOKEN): vol.All(cv.string, vol.Length(max=6)), +}) + + +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the LG TV platform.""" + from pylgnetcast import LgNetCastClient + client = LgNetCastClient(config[CONF_HOST], config[CONF_ACCESS_TOKEN]) + add_devices([LgTVDevice(client, config[CONF_NAME])]) + + +# pylint: disable=too-many-public-methods, abstract-method +# pylint: disable=too-many-instance-attributes +class LgTVDevice(MediaPlayerDevice): + """Representation of a LG TV.""" + + def __init__(self, client, name): + """Initialize the LG TV device.""" + self._client = client + self._name = name + self._muted = False + # Assume that the TV is in Play mode + self._playing = True + self._volume = 0 + self._channel_name = '' + self._program_name = '' + self._state = STATE_UNKNOWN + self._sources = {} + self._source_names = [] + + self.update() + + def send_command(self, command): + """Send remote control commands to the TV.""" + from pylgnetcast import LgNetCastError + try: + with self._client as client: + client.send_command(command) + except (LgNetCastError, RequestException): + self._state = STATE_OFF + + @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS) + def update(self): + """Retrieve the latest data from the LG TV.""" + from pylgnetcast import LgNetCastError + try: + with self._client as client: + self._state = STATE_PLAYING + volume_info = client.query_data('volume_info') + if volume_info: + volume_info = volume_info[0] + self._volume = float(volume_info.find('level').text) + self._muted = volume_info.find('mute').text == 'true' + + channel_info = client.query_data('cur_channel') + if channel_info: + channel_info = channel_info[0] + self._channel_name = channel_info.find('chname').text + self._program_name = channel_info.find('progName').text + + channel_list = client.query_data('channel_list') + if channel_list: + channel_names = [str(c.find('chname').text) for + c in channel_list] + self._sources = dict(zip(channel_names, channel_list)) + # sort source names by the major channel number + source_tuples = [(k, self._sources[k].find('major').text) + for k in self._sources.keys()] + sorted_sources = sorted( + source_tuples, key=lambda channel: int(channel[1])) + self._source_names = [n for n, k in sorted_sources] + except (LgNetCastError, RequestException): + self._state = STATE_OFF + + @property + def name(self): + """Return the name of the device.""" + return self._name + + @property + def state(self): + """Return the state of the device.""" + return self._state + + @property + def is_volume_muted(self): + """Boolean if volume is currently muted.""" + return self._muted + + @property + def volume_level(self): + """Volume level of the media player (0..1).""" + return self._volume / 100.0 + + @property + def source(self): + """Return the current input source.""" + return self._channel_name + + @property + def source_list(self): + """List of available input sources.""" + return self._source_names + + @property + def media_content_type(self): + """Content type of current playing media.""" + return MEDIA_TYPE_CHANNEL + + @property + def media_channel(self): + """Channel currently playing.""" + return self._channel_name + + @property + def media_title(self): + """Title of current playing media.""" + return self._program_name + + @property + def supported_media_commands(self): + """Flag of media commands that are supported.""" + return SUPPORT_LGTV + + def turn_off(self): + """Turn off media player.""" + self.send_command(1) + + def volume_up(self): + """Volume up the media player.""" + self.send_command(24) + + def volume_down(self): + """Volume down media player.""" + self.send_command(25) + + def mute_volume(self, mute): + """Send mute command.""" + self.send_command(26) + + def select_source(self, source): + """Select input source.""" + self._client.change_channel(self._sources[source]) + + def media_play_pause(self): + """Simulate play pause media player.""" + if self._playing: + self.media_pause() + else: + self.media_play() + + def media_play(self): + """Send play command.""" + self._playing = True + self._state = STATE_PLAYING + self.send_command(33) + + def media_pause(self): + """Send media pause command to media player.""" + self._playing = False + self._state = STATE_PAUSED + self.send_command(34) + + def media_next_track(self): + """Send next track command.""" + self.send_command(36) + + def media_previous_track(self): + """Send the previous track command.""" + self.send_command(37) diff --git a/requirements_all.txt b/requirements_all.txt index e277b9268ec..8938a75951a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -142,6 +142,9 @@ https://github.com/theolind/pymysensors/archive/cc5d0b325e13c2b623fa934f69eea7cd # homeassistant.components.notify.googlevoice https://github.com/w1ll1am23/pygooglevoice-sms/archive/7c5ee9969b97a7992fc86a753fe9f20e3ffa3f7c.zip#pygooglevoice-sms==0.0.1 +# homeassistant.components.media_player.lg_netcast +https://github.com/wokar/pylgnetcast/archive/v0.2.0.zip#pylgnetcast==0.2.0 + # homeassistant.components.influxdb influxdb==2.12.0