From 31cd3fccad439a766c6d96a7083a425a56759161 Mon Sep 17 00:00:00 2001
From: Ryan Grieve <me@ryangrieve.com>
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 = """
+        <?xml version="1.0" encoding="utf-8"?>
+        <auth>
+            <type>AuthKeyReq</type>
+        </auth>
+        """
+        self.make_request('/roap/api/auth', content)
+
+    def get_session(self):
+        content = """
+        <?xml version="1.0" encoding="utf-8"?>
+        <auth>
+            <type>AuthReq</type>
+            <value>{0}</value>
+        </auth>
+        """.format(self.pair_key)
+        response = self.make_request('/roap/api/auth', content)
+        return response.find('session').text
+
+    def send_command(self, code):
+        content = """
+        <?xml version="1.0" encoding="utf-8"?>
+        <command>
+            <name>HandleKeyInput</name>
+            <value>{0}</value>
+        </command>
+        """.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