From 31cd3fccad439a766c6d96a7083a425a56759161 Mon Sep 17 00:00:00 2001 From: Ryan Grieve Date: Thu, 3 Jul 2014 23:39:52 +0100 Subject: [PATCH] initial commit --- README.md | 2 +- example.py | 10 ++++ lg.py | 164 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 example.py create mode 100644 lg.py diff --git a/README.md b/README.md index 293e04c..43e2efa 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ python-lgtv =========== -Simple class for pairing with and sending remote control commands to 2012+ LG TVs +Simple class for pairing with and controlling your 2012+ LG TV with python. Example included. diff --git a/example.py b/example.py new file mode 100644 index 0000000..2d03203 --- /dev/null +++ b/example.py @@ -0,0 +1,10 @@ +from lg import Remote + +if __name__ == "__main__": + try: + Remote() + except Remote.NoPairingKey: + key = raw_input('Enter pairing key: ') + remote = Remote(key) + + remote.send_command(Remote.VOLUME_UP) diff --git a/lg.py b/lg.py new file mode 100644 index 0000000..1667459 --- /dev/null +++ b/lg.py @@ -0,0 +1,164 @@ +from __future__ import unicode_literals + +import re +import socket +import httplib + +from xml.etree import ElementTree + + +class Remote(): + + def __init__(self, pair_key=None): + self.pair_key = pair_key + self.ip_address = self.find_tv() + if not self.ip_address: + raise Remote.NoTVFound + + if self.pair_key: + self.get_session() + else: + self.request_pair() + raise Remote.NoPairingKey + + def find_tv(self, retries=10): + request = 'M-SEARCH * HTTP/1.1\r\n' \ + 'HOST: 239.255.255.250:1900\r\n' \ + 'MAN: "ssdp:discover"\r\n' \ + 'MX: 2\r\n' \ + 'ST: urn:schemas-upnp-org:device:MediaRenderer:1\r\n\r\n' + + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.settimeout(3) + + while retries > 0: + sock.sendto(request, ('239.255.255.250', 1900)) + try: + response, address = sock.recvfrom(512) + except: + retries -= 1 + continue + + if re.search('LG', response): + sock.close() + return address[0] + + retries -= 1 + + sock.close() + return None + + def make_request(self, endpoint, content, extra_headers={}): + http = httplib.HTTPConnection(self.ip_address, port=8080) + headers = {'Content-Type': 'application/atom+xml'} + headers.update(extra_headers) + http.request("POST", endpoint, content, headers=headers) + response = http.getresponse() + tree = ElementTree.XML(response.read()) + return tree + + def request_pair(self): + content = """ + + + AuthKeyReq + + """ + self.make_request('/roap/api/auth', content) + + def get_session(self): + content = """ + + + AuthReq + {0} + + """.format(self.pair_key) + response = self.make_request('/roap/api/auth', content) + return response.find('session').text + + def send_command(self, code): + content = """ + + + HandleKeyInput + {0} + + """.format(code) + self.make_request('/roap/api/command', content) + + # exceptions + + class NoPairingKey(Exception): + pass + + class NoTVFound(Exception): + pass + + # command codes + + POWER = 1 + NUM_0 = 2 + NUM_1 = 3 + NUM_2 = 4 + NUM_3 = 5 + NUM_4 = 6 + NUM_5 = 7 + NUM_6 = 8 + NUM_7 = 9 + NUM_8 = 10 + NUM_9 = 11 + UP = 12 + DOWN = 13 + LEFT = 14 + RIGHT = 15 + OK = 20 + HOME = 21 + MENU = 22 + BACK = 23 + VOLUME_UP = 24 + VOLUME_DOWN = 25 + MUTE = 26 + CHANNEL_UP = 27 + CHANNEL_DOWN = 28 + BLUE = 29 + GREEN = 30 + RED = 31 + YELLOW = 32 + PLAY = 33 + PAUSE = 34 + STOP = 35 + FF = 36 + REW = 37 + SKIP_FF = 38 + SKIP_REW = 39 + REC = 40 + REC_LIST = 41 + LIVE = 43 + EPG = 44 + INFO = 45 + ASPECT = 46 + EXT = 47 + PIP = 48 + SUBTITLE = 49 + PROGRAM_LIST = 50 + TEXT = 51 + MARK = 52 + _3D = 400 + _3D_LR = 401 + DASH = 402 + PREV = 403 + FAV = 404 + QUICK_MENU = 405 + TEXT_OPTION = 406 + AUDIO_DESC = 407 + NETCAST = 408 + ENERGY_SAVE = 409 + AV = 410 + SIMPLINK = 411 + EXIT = 412 + RESERVE = 413 + PIP_CHANNEL_UP = 414 + PIP_CHANNEL_DOWN = 415 + PIP_SWITCH = 416 + APPS = 417