Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- """
- Client library for the LG Smart TV running NetCast 3 or 4.
- LG Smart TV models released in 2012 (NetCast 3.0) and LG Smart TV models
- released in 2013 (NetCast 4.0) are supported.
- For pre 2012 LG TV remote commands are supported by the "hdcp" protocol.
- The client is inspired by the work of
- https://github.com/ubaransel/lgcommander
- """
- import logging
- import requests
- from xml.etree import ElementTree
- _LOGGER = logging.getLogger(__name__)
- __all__ = ['LgNetCastClient', 'LG_COMMAND', 'LG_QUERY', 'LgNetCastError', 'AccessTokenError', 'SessionIdError']
- # LG TV handler
- LG_HANDLE_KEY_INPUT = 'HandleKeyInput'
- LG_HANDLE_MOUSE_MOVE = 'HandleTouchMove'
- LG_HANDLE_MOUSE_CLICK = 'HandleTouchClick'
- LG_HANDLE_TOUCH_WHEEL = 'HandleTouchWheel'
- LG_HANDLE_CHANNEL_CHANGE = 'HandleChannelChange'
- DEFAULT_PORT = 8080
- DEFAULT_TIMEOUT = 5
- class LG_COMMAND(object):
- """LG TV remote control commands."""
- POWER = 8
- NUMBER_0 = 16
- NUMBER_1 = 17
- NUMBER_2 = 18
- NUMBER_3 = 19
- NUMBER_4 = 20
- NUMBER_5 = 21
- NUMBER_6 = 22
- NUMBER_7 = 23
- NUMBER_8 = 24
- NUMBER_9 = 25
- UP = 64
- DOWN = 65
- LEFT = 7
- RIGHT = 6
- OK = 68
- HOME_MENU = 67
- RETURN = 40
- VOLUME_UP = 2
- VOLUME_DOWN = 3
- MUTE_TOGGLE = 9
- CHANNEL_UP = 0
- CHANNEL_DOWN = 1
- BLUE = 97
- GREEN = 113
- RED = 114
- YELLOW = 99
- PLAY = 176
- PAUSE = 186
- STOP = 177
- FAST_FORWARD = 36
- REWIND = 26
- SKIP_FORWARD = 142
- SKIP_BACKWARD = 143
- RECORD = 189
- LIVE_TV = 43
- GUIDE = 169
- INFO = 170
- RATIO = 121
- INPUT = 11
- SUBTITLE = 57
- PROGRAM_LIST = 83
- TELE_TEXT = 32
- MARK = 52
- VIDEO_3D = 400
- LR_3D = 401
- DASH = 76
- PREVIOUS_CHANNEL = 403
- FAVL = 30
- QUICK_MENU = 69
- TEXT_OPTION = 33
- ENERGY_SAVING = 149
- AV_MODE = 48
- SIMPLINK = 126
- EXIT = 91
- AD = 145
- """
- key_code_cursor_ok = 2
- key_code_mm_live_tv = 158
- key_code_netcast = 89
- key_code_power_on_off = 8
- key_code_num_0 = 16
- key_code_num_1 = 17
- key_code_num_2 = 18
- key_code_num_3 = 19
- key_code_num_4 = 20
- key_code_num_5 = 21
- key_code_num_6 = 22
- key_code_num_7 = 23
- key_code_num_8 = 24
- key_code_num_9 = 25
- key_code_mute = 9
- key_code_menu = 67
- key_code_chlist = 83
- key_code_enter = 68
- key_code_ch_up = 0
- key_code_ch_down = 1
- key_code_vol_up = 2
- key_code_vol_down = 3
- key_code_arrow_up = 64
- key_code_arrow_down = 65
- key_code_arrow_left = 7
- key_code_arrow_right = 6
- key_code_red_button = 114
- key_code_green_button = 113
- key_code_yellow_button = 99
- key_code_blue_button = 97
- key_code_dash = 76
- key_code_mm_skip_backward = 143
- key_code_mm_skip_forward = 142
- key_code_av_mode = 48
- key_code_quick_menu = 69
- key_code_teletext = 32
- key_code_input = 11
- key_code_energy_saving = 149
- key_code_simplink = 126
- key_code_t_opt = 33
- key_code_exit_cancel = 91
- key_code_subtitle = 57
- key_code_ratio = 121
- key_code_return = 40
- key_code_info = 170
- key_code_mm_record = 189
- key_code_mm_play = 176
- key_code_mm_pause = 186
- key_code_mm_stop = 177
- key_code_epg = 169
- key_code_guide = 169
- key_code_fav = 30
- key_code_ad = 145
- key_code_confirm = 68
- key_code_flashback = 26
- """
- class LG_QUERY(object):
- """LG TV data queries."""
- CUR_CHANNEL = 'cur_channel'
- CHANNEL_LIST = 'channel_list'
- FAV_LIST = 'fav_list'
- CONTEXT_UI = 'context_ui'
- VOLUME_INFO = 'volume_info'
- SCREEN_IMAGE = 'screen_image'
- IS_3D = 'is_3d'
- class LG_PROTOCOL(object):
- """Supported LG TV protcols."""
- HDCP = 'hdcp'
- ROAP = 'roap'
- class LgNetCastClient(object):
- """LG NetCast TV client using the ROAP or HDCP protocol."""
- HEADER = {'Content-Type': 'application/atom+xml'}
- XML = '<?xml version=\"1.0\" encoding=\"utf-8\"?>'
- KEY = XML + '<auth><type>AuthKeyReq</type></auth>'
- AUTH = XML + '<auth><type>%s</type><value>%s</value></auth>'
- COMMAND = XML + '<command><session>%s</session><type>%s</type>%s</command>'
- def __init__(self, host, access_token, protocol=LG_PROTOCOL.HDCP):
- """Initialize the LG TV client."""
- self.url = 'http://%s:%s/%s/api/' % (host, DEFAULT_PORT, protocol)
- self.access_token = access_token
- self.protocol = protocol
- self._session = None
- def __enter__(self):
- """Context manager method to support with statement."""
- self._session = self._get_session_id()
- return self
- def __exit__(self, exc_type, exc_val, exc_tb):
- """Context manager method to support with statement."""
- self._session = None
- def send_command(self, command):
- _LOGGER.debug('send_command')
- """Send remote control commands to the TV."""
- message = self.COMMAND % (self._session, LG_HANDLE_KEY_INPUT, '<value>%s</value>' % command)
- self._send_to_tv('command', message)
- def change_channel(self, channel):
- """Send change channel command to the TV."""
- if self._session == None:
- self._session = self._get_session_id()
- _LOGGER.debug('change_channel')
- #message_first = self.COMMAND % (self._session, LG_HANDLE_KEY_INPUT, '<value>43</value>')
- #self._send_to_tv('command', message_first)
- #self.send_command(43)
- message = self.COMMAND % (self._session, LG_HANDLE_CHANNEL_CHANGE, channel)
- self._send_to_tv('command', message)
- def query_data(self, query):
- """Query status information from the TV."""
- response = self._send_to_tv('data', payload={'target': query, 'session': self._session})
- if response.status_code == requests.codes.ok:
- data = response.text
- tree = ElementTree.XML(data)
- data_list = []
- for data in tree.iter('data'):
- data_list.append(data)
- return data_list
- def _get_session_id(self):
- """Get the session key for the TV connection.
- If a pair key is defined the session id is requested otherwise display
- the pair key on TV.
- """
- if not self.access_token:
- self._display_pair_key()
- raise AccessTokenError(
- 'No access token specified to create session.')
- message = self.AUTH % ('AuthReq', self.access_token)
- response = self._send_to_tv('auth', message)
- if response.status_code != requests.codes.ok:
- raise SessionIdError('Can not get session id from TV.')
- data = response.text
- tree = ElementTree.XML(data)
- session = tree.find('session').text
- return session
- def _display_pair_key(self):
- """Send message to display the pair key on TV screen."""
- self._send_to_tv('auth', self.KEY)
- def _send_to_tv(self, message_type, message=None, payload=None):
- """Send message of given type to the tv."""
- if message_type == 'command':
- message_type = 'dtv_wifirc'
- url = '%s%s' % (self.url, message_type)
- _LOGGER.debug(url)
- if message:
- response = requests.post(url, data=message, headers=self.HEADER, timeout=DEFAULT_TIMEOUT)
- else:
- response = requests.get(url, params=payload, headers=self.HEADER, timeout=DEFAULT_TIMEOUT)
- _LOGGER.debug(response)
- return response
- class LgNetCastError(Exception):
- """Base class for all exceptions in this module."""
- class AccessTokenError(LgNetCastError):
- """No access token specified to create session."""
- class SessionIdError(LgNetCastError):
- """No session id could be retrieved from TV."""
- """
- 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, PLATFORM_SCHEMA,
- SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP,
- SUPPORT_SELECT_SOURCE, SUPPORT_PLAY, MEDIA_TYPE_CHANNEL, MediaPlayerDevice)
- from homeassistant.const import (
- CONF_HOST, CONF_NAME, CONF_ACCESS_TOKEN,
- STATE_OFF, STATE_PLAYING, STATE_PAUSED, STATE_UNKNOWN)
- from homeassistant import util
- from homeassistant.helpers.script import Script
- CONF_ON_ACTION = 'turn_on_action'
- REQUIREMENTS = ['pylgnetcast-homeassistant==0.2.0.dev0']
- _LOGGER = logging.getLogger(__name__)
- DEFAULT_NAME = 'LG TV Remote'
- MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1)
- MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
- SUPPORT_LGTV = SUPPORT_PAUSE | SUPPORT_VOLUME_STEP | \
- SUPPORT_VOLUME_MUTE | SUPPORT_PREVIOUS_TRACK | \
- SUPPORT_NEXT_TRACK | SUPPORT_TURN_OFF | \
- SUPPORT_SELECT_SOURCE | SUPPORT_PLAY
- PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
- vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
- vol.Optional(CONF_ON_ACTION): cv.SCRIPT_SCHEMA,
- vol.Required(CONF_HOST): cv.string,
- vol.Optional(CONF_ACCESS_TOKEN):
- vol.All(cv.string, vol.Length(max=6)),
- })
- def setup_platform(hass, config, add_entities, discovery_info=None):
- """Set up the LG TV platform."""
- """from pylgnetcast import LgNetCastClient"""
- host = config.get(CONF_HOST)
- access_token = config.get(CONF_ACCESS_TOKEN)
- name = config.get(CONF_NAME)
- turn_on_action = config.get(CONF_ON_ACTION)
- client = LgNetCastClient(host, access_token)
- add_entities([LgTVDevice(client, name, hass, turn_on_action)], True)
- class LgTVDevice(MediaPlayerDevice):
- """Representation of a LG TV."""
- def __init__(self, client, name, hass, on_action):
- """Initialize the LG TV device."""
- self._client = client
- self._name = name
- self._muted = False
- self._on_script = Script(hass, on_action) if on_action else None
- # 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 = []
- 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
- 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
- channel_info = client.query_data('cur_channel')
- if channel_info:
- channel_info = channel_info[0]
- self._channel_name = channel_info.find('name').text
- self._program_name = channel_info.find('name').text
- channel_list = client.query_data('fav_list')
- """
- <envelope>
- <HDCPError>200</HDCPError>
- <HDCPErrorDetail>OK</HDCPErrorDetail>
- <session>535465673</session>
- <group>
- <gname>
- Group A
- </gname>
- <data>
- <type>cable</type>
- <major>3</major>
- <minor>65535</minor>
- <sourceIndex>3</sourceIndex>
- <physicalNum>33</physicalNum>
- <name>Nova TV</name>
- </data>
- </group>
- <group>
- <gname>Group B</gname>
- </group>
- </envelope>
- """
- if channel_list:
- channel_names = []
- chan_list = []
- for channel in channel_list:
- major = channel.find('major').text
- minor = channel.find('minor').text
- sourceIndex = channel.find('sourceIndex').text
- physicalNum = channel.find('physicalNum').text
- chan = '<major>' + major + '</major><minor>' + minor + '</minor><sourceIndex>' + sourceIndex + '</sourceIndex><physicalNum>' + physicalNum + '</physicalNum>'
- chan_list.append(chan)
- channel_name = channel.find('name')
- if channel_name is not None:
- channel_names.append(str(channel_name.text))
- self._sources = dict(zip(channel_names, chan_list))
- # sort source names by the major channel number
- source_tuples = [(k, self._sources[k].find('major'))
- for k in self._sources]
- self._source_names = [n for n, k in source_tuples]
- 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 False #self._muted
- @property
- def volume_level(self):
- """Volume level of the media player (0..1)."""
- """return self._volume / 100.0"""
- return 1
- @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 '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_features(self):
- """Flag media player features that are supported."""
- if self._on_script:
- return SUPPORT_LGTV | SUPPORT_TURN_ON
- return SUPPORT_LGTV
- @property
- def media_image_url(self):
- """URL for obtaining a screen capture."""
- """return self._client.url + 'data?target=screen_image'"""
- return "/local/livetv.jpg"
- def turn_off(self):
- """Turn off media player."""
- self.send_command(8)
- self.schedule_update_ha_state()
- def turn_on(self):
- """Turn on the media player."""
- if self._on_script:
- self._on_script.run()
- def volume_up(self):
- """Volume up the media player."""
- print("volume_up")
- self.send_command(2)
- def volume_down(self):
- """Volume down media player."""
- print("volume_down")
- self.send_command(3)
- def mute_volume(self, mute):
- """Send mute command."""
- self.send_command(9)
- def select_source(self, source):
- """Select input source."""
- _LOGGER.debug('self._sources[source]:')
- _LOGGER.debug(self._sources[source])
- self._client.change_channel(self._sources[source])
- self.schedule_update_ha_state()
- 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 = 'playing'
- self.send_command(33)
- def media_pause(self):
- """Send media pause command to media player."""
- self._playing = False
- self._state = 'paused'
- self.send_command(34)
- def media_next_track(self):
- """Send next track command."""
- print("media_next_track")
- self.send_command(0)
- def media_previous_track(self):
- """Send the previous track command."""
- print("media_previous_track")
- self.send_command(1)
Advertisement
Add Comment
Please, Sign In to add comment