Guest User

Untitled

a guest
Sep 16th, 2019
515
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 16.58 KB | None | 0 0
  1. """
  2. Client library for the LG Smart TV running NetCast 3 or 4.
  3. LG Smart TV models released in 2012 (NetCast 3.0) and LG Smart TV models
  4. released in 2013 (NetCast 4.0) are supported.
  5. For pre 2012 LG TV remote commands are supported by the "hdcp" protocol.
  6. The client is inspired by the work of
  7. https://github.com/ubaransel/lgcommander
  8. """
  9. import logging
  10. import requests
  11. from xml.etree import ElementTree
  12.  
  13. _LOGGER = logging.getLogger(__name__)
  14.  
  15. __all__ = ['LgNetCastClient', 'LG_COMMAND', 'LG_QUERY', 'LgNetCastError', 'AccessTokenError', 'SessionIdError']
  16.  
  17.  
  18. # LG TV handler
  19. LG_HANDLE_KEY_INPUT = 'HandleKeyInput'
  20. LG_HANDLE_MOUSE_MOVE = 'HandleTouchMove'
  21. LG_HANDLE_MOUSE_CLICK = 'HandleTouchClick'
  22. LG_HANDLE_TOUCH_WHEEL = 'HandleTouchWheel'
  23. LG_HANDLE_CHANNEL_CHANGE = 'HandleChannelChange'
  24.  
  25. DEFAULT_PORT = 8080
  26. DEFAULT_TIMEOUT = 5
  27.  
  28.  
  29. class LG_COMMAND(object):
  30. """LG TV remote control commands."""
  31. POWER = 8
  32. NUMBER_0 = 16
  33. NUMBER_1 = 17
  34. NUMBER_2 = 18
  35. NUMBER_3 = 19
  36. NUMBER_4 = 20
  37. NUMBER_5 = 21
  38. NUMBER_6 = 22
  39. NUMBER_7 = 23
  40. NUMBER_8 = 24
  41. NUMBER_9 = 25
  42. UP = 64
  43. DOWN = 65
  44. LEFT = 7
  45. RIGHT = 6
  46. OK = 68
  47. HOME_MENU = 67
  48. RETURN = 40
  49. VOLUME_UP = 2
  50. VOLUME_DOWN = 3
  51. MUTE_TOGGLE = 9
  52. CHANNEL_UP = 0
  53. CHANNEL_DOWN = 1
  54. BLUE = 97
  55. GREEN = 113
  56. RED = 114
  57. YELLOW = 99
  58. PLAY = 176
  59. PAUSE = 186
  60. STOP = 177
  61. FAST_FORWARD = 36
  62. REWIND = 26
  63. SKIP_FORWARD = 142
  64. SKIP_BACKWARD = 143
  65. RECORD = 189
  66. LIVE_TV = 43
  67. GUIDE = 169
  68. INFO = 170
  69. RATIO = 121
  70. INPUT = 11
  71. SUBTITLE = 57
  72. PROGRAM_LIST = 83
  73. TELE_TEXT = 32
  74. MARK = 52
  75. VIDEO_3D = 400
  76. LR_3D = 401
  77. DASH = 76
  78. PREVIOUS_CHANNEL = 403
  79. FAVL = 30
  80. QUICK_MENU = 69
  81. TEXT_OPTION = 33
  82. ENERGY_SAVING = 149
  83. AV_MODE = 48
  84. SIMPLINK = 126
  85. EXIT = 91
  86. AD = 145
  87. """
  88. key_code_cursor_ok = 2
  89. key_code_mm_live_tv = 158
  90. key_code_netcast = 89
  91.  
  92. key_code_power_on_off = 8
  93. key_code_num_0 = 16
  94. key_code_num_1 = 17
  95. key_code_num_2 = 18
  96. key_code_num_3 = 19
  97. key_code_num_4 = 20
  98. key_code_num_5 = 21
  99. key_code_num_6 = 22
  100. key_code_num_7 = 23
  101. key_code_num_8 = 24
  102. key_code_num_9 = 25
  103. key_code_mute = 9
  104. key_code_menu = 67
  105. key_code_chlist = 83
  106. key_code_enter = 68
  107. key_code_ch_up = 0
  108. key_code_ch_down = 1
  109. key_code_vol_up = 2
  110. key_code_vol_down = 3
  111. key_code_arrow_up = 64
  112. key_code_arrow_down = 65
  113. key_code_arrow_left = 7
  114. key_code_arrow_right = 6
  115. key_code_red_button = 114
  116. key_code_green_button = 113
  117. key_code_yellow_button = 99
  118. key_code_blue_button = 97
  119. key_code_dash = 76
  120. key_code_mm_skip_backward = 143
  121. key_code_mm_skip_forward = 142
  122. key_code_av_mode = 48
  123. key_code_quick_menu = 69
  124. key_code_teletext = 32
  125. key_code_input = 11
  126. key_code_energy_saving = 149
  127. key_code_simplink = 126
  128. key_code_t_opt = 33
  129. key_code_exit_cancel = 91
  130. key_code_subtitle = 57
  131. key_code_ratio = 121
  132. key_code_return = 40
  133. key_code_info = 170
  134. key_code_mm_record = 189
  135. key_code_mm_play = 176
  136. key_code_mm_pause = 186
  137. key_code_mm_stop = 177
  138. key_code_epg = 169
  139. key_code_guide = 169
  140. key_code_fav = 30
  141. key_code_ad = 145
  142. key_code_confirm = 68
  143. key_code_flashback = 26
  144. """
  145.  
  146. class LG_QUERY(object):
  147. """LG TV data queries."""
  148. CUR_CHANNEL = 'cur_channel'
  149. CHANNEL_LIST = 'channel_list'
  150. FAV_LIST = 'fav_list'
  151. CONTEXT_UI = 'context_ui'
  152. VOLUME_INFO = 'volume_info'
  153. SCREEN_IMAGE = 'screen_image'
  154. IS_3D = 'is_3d'
  155.  
  156.  
  157. class LG_PROTOCOL(object):
  158. """Supported LG TV protcols."""
  159. HDCP = 'hdcp'
  160. ROAP = 'roap'
  161.  
  162.  
  163. class LgNetCastClient(object):
  164. """LG NetCast TV client using the ROAP or HDCP protocol."""
  165.  
  166. HEADER = {'Content-Type': 'application/atom+xml'}
  167. XML = '<?xml version=\"1.0\" encoding=\"utf-8\"?>'
  168. KEY = XML + '<auth><type>AuthKeyReq</type></auth>'
  169. AUTH = XML + '<auth><type>%s</type><value>%s</value></auth>'
  170. COMMAND = XML + '<command><session>%s</session><type>%s</type>%s</command>'
  171.  
  172. def __init__(self, host, access_token, protocol=LG_PROTOCOL.HDCP):
  173. """Initialize the LG TV client."""
  174. self.url = 'http://%s:%s/%s/api/' % (host, DEFAULT_PORT, protocol)
  175. self.access_token = access_token
  176. self.protocol = protocol
  177. self._session = None
  178.  
  179. def __enter__(self):
  180. """Context manager method to support with statement."""
  181. self._session = self._get_session_id()
  182. return self
  183.  
  184. def __exit__(self, exc_type, exc_val, exc_tb):
  185. """Context manager method to support with statement."""
  186. self._session = None
  187.  
  188. def send_command(self, command):
  189. _LOGGER.debug('send_command')
  190. """Send remote control commands to the TV."""
  191. message = self.COMMAND % (self._session, LG_HANDLE_KEY_INPUT, '<value>%s</value>' % command)
  192. self._send_to_tv('command', message)
  193.  
  194. def change_channel(self, channel):
  195. """Send change channel command to the TV."""
  196. if self._session == None:
  197. self._session = self._get_session_id()
  198. _LOGGER.debug('change_channel')
  199.  
  200. #message_first = self.COMMAND % (self._session, LG_HANDLE_KEY_INPUT, '<value>43</value>')
  201. #self._send_to_tv('command', message_first)
  202.  
  203. #self.send_command(43)
  204. message = self.COMMAND % (self._session, LG_HANDLE_CHANNEL_CHANGE, channel)
  205. self._send_to_tv('command', message)
  206.  
  207. def query_data(self, query):
  208. """Query status information from the TV."""
  209. response = self._send_to_tv('data', payload={'target': query, 'session': self._session})
  210. if response.status_code == requests.codes.ok:
  211. data = response.text
  212. tree = ElementTree.XML(data)
  213. data_list = []
  214. for data in tree.iter('data'):
  215. data_list.append(data)
  216. return data_list
  217.  
  218. def _get_session_id(self):
  219. """Get the session key for the TV connection.
  220. If a pair key is defined the session id is requested otherwise display
  221. the pair key on TV.
  222. """
  223. if not self.access_token:
  224. self._display_pair_key()
  225. raise AccessTokenError(
  226. 'No access token specified to create session.')
  227. message = self.AUTH % ('AuthReq', self.access_token)
  228. response = self._send_to_tv('auth', message)
  229. if response.status_code != requests.codes.ok:
  230. raise SessionIdError('Can not get session id from TV.')
  231. data = response.text
  232. tree = ElementTree.XML(data)
  233. session = tree.find('session').text
  234. return session
  235.  
  236. def _display_pair_key(self):
  237. """Send message to display the pair key on TV screen."""
  238. self._send_to_tv('auth', self.KEY)
  239.  
  240. def _send_to_tv(self, message_type, message=None, payload=None):
  241. """Send message of given type to the tv."""
  242.  
  243. if message_type == 'command':
  244. message_type = 'dtv_wifirc'
  245. url = '%s%s' % (self.url, message_type)
  246. _LOGGER.debug(url)
  247. if message:
  248. response = requests.post(url, data=message, headers=self.HEADER, timeout=DEFAULT_TIMEOUT)
  249. else:
  250. response = requests.get(url, params=payload, headers=self.HEADER, timeout=DEFAULT_TIMEOUT)
  251.  
  252. _LOGGER.debug(response)
  253. return response
  254.  
  255.  
  256. class LgNetCastError(Exception):
  257. """Base class for all exceptions in this module."""
  258.  
  259.  
  260. class AccessTokenError(LgNetCastError):
  261. """No access token specified to create session."""
  262.  
  263.  
  264. class SessionIdError(LgNetCastError):
  265. """No session id could be retrieved from TV."""
  266.  
  267. """
  268. Support for LG TV running on NetCast 3 or 4.
  269. For more details about this platform, please refer to the documentation at
  270. https://home-assistant.io/components/media_player.lg_netcast/
  271. """
  272. from datetime import timedelta
  273. """import logging"""
  274.  
  275. from requests import RequestException
  276. import voluptuous as vol
  277.  
  278. import homeassistant.helpers.config_validation as cv
  279. from homeassistant.components.media_player import (
  280. SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, PLATFORM_SCHEMA,
  281. SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP,
  282. SUPPORT_SELECT_SOURCE, SUPPORT_PLAY, MEDIA_TYPE_CHANNEL, MediaPlayerDevice)
  283. from homeassistant.const import (
  284. CONF_HOST, CONF_NAME, CONF_ACCESS_TOKEN,
  285. STATE_OFF, STATE_PLAYING, STATE_PAUSED, STATE_UNKNOWN)
  286. from homeassistant import util
  287. from homeassistant.helpers.script import Script
  288.  
  289. CONF_ON_ACTION = 'turn_on_action'
  290.  
  291. REQUIREMENTS = ['pylgnetcast-homeassistant==0.2.0.dev0']
  292.  
  293. _LOGGER = logging.getLogger(__name__)
  294.  
  295. DEFAULT_NAME = 'LG TV Remote'
  296.  
  297. MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1)
  298. MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
  299.  
  300. SUPPORT_LGTV = SUPPORT_PAUSE | SUPPORT_VOLUME_STEP | \
  301. SUPPORT_VOLUME_MUTE | SUPPORT_PREVIOUS_TRACK | \
  302. SUPPORT_NEXT_TRACK | SUPPORT_TURN_OFF | \
  303. SUPPORT_SELECT_SOURCE | SUPPORT_PLAY
  304.  
  305. PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
  306. vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
  307. vol.Optional(CONF_ON_ACTION): cv.SCRIPT_SCHEMA,
  308. vol.Required(CONF_HOST): cv.string,
  309. vol.Optional(CONF_ACCESS_TOKEN):
  310. vol.All(cv.string, vol.Length(max=6)),
  311. })
  312.  
  313.  
  314. def setup_platform(hass, config, add_entities, discovery_info=None):
  315. """Set up the LG TV platform."""
  316. """from pylgnetcast import LgNetCastClient"""
  317. host = config.get(CONF_HOST)
  318. access_token = config.get(CONF_ACCESS_TOKEN)
  319. name = config.get(CONF_NAME)
  320. turn_on_action = config.get(CONF_ON_ACTION)
  321.  
  322. client = LgNetCastClient(host, access_token)
  323. add_entities([LgTVDevice(client, name, hass, turn_on_action)], True)
  324.  
  325.  
  326. class LgTVDevice(MediaPlayerDevice):
  327. """Representation of a LG TV."""
  328.  
  329. def __init__(self, client, name, hass, on_action):
  330. """Initialize the LG TV device."""
  331. self._client = client
  332. self._name = name
  333. self._muted = False
  334. self._on_script = Script(hass, on_action) if on_action else None
  335. # Assume that the TV is in Play mode
  336. self._playing = True
  337. self._volume = 0
  338. self._channel_name = ''
  339. self._program_name = ''
  340. self._state = STATE_UNKNOWN
  341. self._sources = {}
  342. self._source_names = []
  343.  
  344. def send_command(self, command):
  345. """Send remote control commands to the TV."""
  346. """from pylgnetcast import LgNetCastError"""
  347. try:
  348. with self._client as client:
  349. client.send_command(command)
  350. except (LgNetCastError, RequestException):
  351. self._state = STATE_OFF
  352.  
  353. def update(self):
  354. """Retrieve the latest data from the LG TV."""
  355. """from pylgnetcast import LgNetCastError"""
  356. try:
  357. with self._client as client:
  358. self._state = STATE_PLAYING
  359.  
  360. channel_info = client.query_data('cur_channel')
  361. if channel_info:
  362. channel_info = channel_info[0]
  363. self._channel_name = channel_info.find('name').text
  364. self._program_name = channel_info.find('name').text
  365.  
  366. channel_list = client.query_data('fav_list')
  367. """
  368. <envelope>
  369. <HDCPError>200</HDCPError>
  370. <HDCPErrorDetail>OK</HDCPErrorDetail>
  371. <session>535465673</session>
  372. <group>
  373. <gname>
  374. Group A
  375. </gname>
  376. <data>
  377. <type>cable</type>
  378. <major>3</major>
  379. <minor>65535</minor>
  380. <sourceIndex>3</sourceIndex>
  381. <physicalNum>33</physicalNum>
  382. <name>Nova TV</name>
  383. </data>
  384. </group>
  385. <group>
  386. <gname>Group B</gname>
  387. </group>
  388. </envelope>
  389. """
  390. if channel_list:
  391. channel_names = []
  392. chan_list = []
  393. for channel in channel_list:
  394. major = channel.find('major').text
  395. minor = channel.find('minor').text
  396. sourceIndex = channel.find('sourceIndex').text
  397. physicalNum = channel.find('physicalNum').text
  398. chan = '<major>' + major + '</major><minor>' + minor + '</minor><sourceIndex>' + sourceIndex + '</sourceIndex><physicalNum>' + physicalNum + '</physicalNum>'
  399. chan_list.append(chan)
  400. channel_name = channel.find('name')
  401. if channel_name is not None:
  402. channel_names.append(str(channel_name.text))
  403.  
  404. self._sources = dict(zip(channel_names, chan_list))
  405. # sort source names by the major channel number
  406. source_tuples = [(k, self._sources[k].find('major'))
  407. for k in self._sources]
  408. self._source_names = [n for n, k in source_tuples]
  409. except (LgNetCastError, RequestException):
  410. self._state = STATE_OFF
  411.  
  412. @property
  413. def name(self):
  414. """Return the name of the device."""
  415. return self._name
  416.  
  417. @property
  418. def state(self):
  419. """Return the state of the device."""
  420. return self._state
  421.  
  422. @property
  423. def is_volume_muted(self):
  424. """Boolean if volume is currently muted."""
  425. return False #self._muted
  426.  
  427. @property
  428. def volume_level(self):
  429. """Volume level of the media player (0..1)."""
  430. """return self._volume / 100.0"""
  431. return 1
  432.  
  433. @property
  434. def source(self):
  435. """Return the current input source."""
  436. return self._channel_name
  437.  
  438. @property
  439. def source_list(self):
  440. """List of available input sources."""
  441. return self._source_names
  442.  
  443. @property
  444. def media_content_type(self):
  445. """Content type of current playing media."""
  446. return 'channel'
  447.  
  448. @property
  449. def media_channel(self):
  450. """Channel currently playing."""
  451. return self._channel_name
  452.  
  453. @property
  454. def media_title(self):
  455. """Title of current playing media."""
  456. return self._program_name
  457.  
  458. @property
  459. def supported_features(self):
  460. """Flag media player features that are supported."""
  461. if self._on_script:
  462. return SUPPORT_LGTV | SUPPORT_TURN_ON
  463. return SUPPORT_LGTV
  464.  
  465. @property
  466. def media_image_url(self):
  467. """URL for obtaining a screen capture."""
  468. """return self._client.url + 'data?target=screen_image'"""
  469. return "/local/livetv.jpg"
  470.  
  471. def turn_off(self):
  472. """Turn off media player."""
  473. self.send_command(8)
  474. self.schedule_update_ha_state()
  475.  
  476. def turn_on(self):
  477. """Turn on the media player."""
  478. if self._on_script:
  479. self._on_script.run()
  480.  
  481. def volume_up(self):
  482. """Volume up the media player."""
  483. print("volume_up")
  484. self.send_command(2)
  485.  
  486. def volume_down(self):
  487. """Volume down media player."""
  488. print("volume_down")
  489. self.send_command(3)
  490.  
  491. def mute_volume(self, mute):
  492. """Send mute command."""
  493. self.send_command(9)
  494.  
  495. def select_source(self, source):
  496. """Select input source."""
  497. _LOGGER.debug('self._sources[source]:')
  498. _LOGGER.debug(self._sources[source])
  499. self._client.change_channel(self._sources[source])
  500. self.schedule_update_ha_state()
  501.  
  502. def media_play_pause(self):
  503. """Simulate play pause media player."""
  504. if self._playing:
  505. self.media_pause()
  506. else:
  507. self.media_play()
  508.  
  509. def media_play(self):
  510. """Send play command."""
  511. self._playing = True
  512. self._state = 'playing'
  513. self.send_command(33)
  514.  
  515. def media_pause(self):
  516. """Send media pause command to media player."""
  517. self._playing = False
  518. self._state = 'paused'
  519. self.send_command(34)
  520.  
  521. def media_next_track(self):
  522. """Send next track command."""
  523. print("media_next_track")
  524. self.send_command(0)
  525.  
  526. def media_previous_track(self):
  527. """Send the previous track command."""
  528. print("media_previous_track")
  529. self.send_command(1)
Advertisement
Add Comment
Please, Sign In to add comment