adding documentation, changing pairing flow and cleaning up
This commit is contained in:
parent
31cd3fccad
commit
d63de71130
3 changed files with 195 additions and 18 deletions
105
README.md
105
README.md
|
@ -1,4 +1,107 @@
|
|||
python-lgtv
|
||||
===========
|
||||
|
||||
Simple class for pairing with and controlling your 2012+ LG TV with python. Example included.
|
||||
Simple class for pairing with and controlling your 2012+ LG TV with python.
|
||||
|
||||
You can find all TVs on the network using the `find_tvs` class method.
|
||||
|
||||
```
|
||||
from lg import Remote
|
||||
|
||||
addresses = Remote.find_tvs()
|
||||
```
|
||||
If you have only one, it'll be faster to use the `first_only` parameter.
|
||||
|
||||
```
|
||||
address = Remote.find_tvs(first_only=True)
|
||||
```
|
||||
|
||||
You will need a pairing key, if you already know it you can pass it to the `Remote` class' `__init__` other wise you can reate your remote control instance and it'll request it from the TV. You can then provide this pairing key using the `set_pairing_key` method.
|
||||
|
||||
```
|
||||
remote = Remote(address)
|
||||
# Pairing key will appear on screen
|
||||
key = raw_input('Please enter pairing key: ')
|
||||
remote.set_pairing_key(key)
|
||||
```
|
||||
|
||||
After this you can send single or multiple commands using `send_command` and `send_multiple`.
|
||||
|
||||
```
|
||||
remote.send_command(Remote.VOLUME_UP)
|
||||
|
||||
commands = [Remote.HOME, Remote.EXIT, Remote.MENU]
|
||||
remote.send_multiple(commands)
|
||||
```
|
||||
|
||||
An optional `delay` parameter can be provided to `send_multiple` this will the amount of seconds the control will wait between commands. N.B. Sending commands too fast can cause some of them to be ignored.
|
||||
|
||||
A reference of all the shortcut commands available are below, you are free to send any integer in `send_command` but be wary if you don't know what your are doing.
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
|
|
10
example.py
10
example.py
|
@ -1,10 +1,10 @@
|
|||
from lg import Remote
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
Remote()
|
||||
except Remote.NoPairingKey:
|
||||
key = raw_input('Enter pairing key: ')
|
||||
remote = Remote(key)
|
||||
address = Remote.find_tvs(first_only=True)
|
||||
remote = Remote(address)
|
||||
|
||||
key = raw_input('Enter pairing key: ')
|
||||
remote.set_pairing_key(key)
|
||||
|
||||
remote.send_command(Remote.VOLUME_UP)
|
||||
|
|
98
lg.py
98
lg.py
|
@ -1,6 +1,7 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
import time
|
||||
import socket
|
||||
import httplib
|
||||
|
||||
|
@ -8,10 +9,21 @@ from xml.etree import ElementTree
|
|||
|
||||
|
||||
class Remote():
|
||||
"""
|
||||
Class for initialising communication with, and sending remote
|
||||
commands to a 2012+ LG TV.
|
||||
"""
|
||||
|
||||
def __init__(self, ip_address, pair_key=None):
|
||||
"""
|
||||
Initialise class with IP and optional pair key. If not pair key
|
||||
provided, then the pair request will be sent to the TV and
|
||||
`.set_pairing_key()` must be called before use.
|
||||
"""
|
||||
|
||||
def __init__(self, pair_key=None):
|
||||
self.pair_key = pair_key
|
||||
self.ip_address = self.find_tv()
|
||||
self.ip_address = ip_address
|
||||
|
||||
if not self.ip_address:
|
||||
raise Remote.NoTVFound
|
||||
|
||||
|
@ -19,9 +31,15 @@ class Remote():
|
|||
self.get_session()
|
||||
else:
|
||||
self.request_pair()
|
||||
raise Remote.NoPairingKey
|
||||
|
||||
def find_tv(self, retries=10):
|
||||
@classmethod
|
||||
def find_tvs(cls, attempts=10, first_only=False):
|
||||
"""
|
||||
Create a broadcast socket and listen for LG TVs responding.
|
||||
Returns list of IPs unless `first_only` is true, in which case it
|
||||
will return the first TV found.
|
||||
"""
|
||||
|
||||
request = 'M-SEARCH * HTTP/1.1\r\n' \
|
||||
'HOST: 239.255.255.250:1900\r\n' \
|
||||
'MAN: "ssdp:discover"\r\n' \
|
||||
|
@ -29,26 +47,48 @@ class Remote():
|
|||
'ST: urn:schemas-upnp-org:device:MediaRenderer:1\r\n\r\n'
|
||||
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
sock.settimeout(3)
|
||||
sock.settimeout(1)
|
||||
|
||||
while retries > 0:
|
||||
addresses = []
|
||||
while attempts > 0:
|
||||
sock.sendto(request, ('239.255.255.250', 1900))
|
||||
try:
|
||||
response, address = sock.recvfrom(512)
|
||||
except:
|
||||
retries -= 1
|
||||
attempts -= 1
|
||||
continue
|
||||
|
||||
if re.search('LG', response):
|
||||
sock.close()
|
||||
return address[0]
|
||||
if first_only:
|
||||
sock.close()
|
||||
return address[0]
|
||||
else:
|
||||
addresses.append(address[0])
|
||||
|
||||
retries -= 1
|
||||
attempts -= 1
|
||||
|
||||
sock.close()
|
||||
return None
|
||||
if first_only:
|
||||
raise NoTVFound
|
||||
else:
|
||||
if len(addresses) == 0:
|
||||
raise NoTVFound
|
||||
else:
|
||||
return addresses
|
||||
|
||||
def set_pairing_key(self, pair_key):
|
||||
"""
|
||||
Set the pairing key and initialise the session with the TV
|
||||
"""
|
||||
|
||||
self.pair_key = pair_key
|
||||
self.get_session()
|
||||
|
||||
def make_request(self, endpoint, content, extra_headers={}):
|
||||
"""
|
||||
POST the XML request to the configured TV and parse the response
|
||||
"""
|
||||
|
||||
http = httplib.HTTPConnection(self.ip_address, port=8080)
|
||||
headers = {'Content-Type': 'application/atom+xml'}
|
||||
headers.update(extra_headers)
|
||||
|
@ -58,6 +98,10 @@ class Remote():
|
|||
return tree
|
||||
|
||||
def request_pair(self):
|
||||
"""
|
||||
Request for the TV to display the pairing key on-screen
|
||||
"""
|
||||
|
||||
content = """
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<auth>
|
||||
|
@ -67,6 +111,10 @@ class Remote():
|
|||
self.make_request('/roap/api/auth', content)
|
||||
|
||||
def get_session(self):
|
||||
"""
|
||||
Request to pair with the TV and return the session ID
|
||||
"""
|
||||
|
||||
content = """
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<auth>
|
||||
|
@ -78,6 +126,12 @@ class Remote():
|
|||
return response.find('session').text
|
||||
|
||||
def send_command(self, code):
|
||||
"""
|
||||
Send a remote control key command. Ignores response for now.
|
||||
"""
|
||||
|
||||
if self.pair_key is None:
|
||||
raise Remote.NoPairingKey
|
||||
content = """
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<command>
|
||||
|
@ -87,15 +141,35 @@ class Remote():
|
|||
""".format(code)
|
||||
self.make_request('/roap/api/command', content)
|
||||
|
||||
def send_multiple(self, codes, delay=0.2):
|
||||
"""
|
||||
Send multiple remote control commands with a delay in between. The
|
||||
delay is required as multiple commands can be ignored if too close
|
||||
together.
|
||||
"""
|
||||
|
||||
for code in codes:
|
||||
self.send_command(code)
|
||||
time.sleep(delay)
|
||||
|
||||
# exceptions
|
||||
|
||||
class NoPairingKey(Exception):
|
||||
"""
|
||||
Exception raised when no pairing key is present and action requring one
|
||||
is attempted.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
class NoTVFound(Exception):
|
||||
"""
|
||||
Exception raised when unable to find any LG TVs on the network
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
# command codes
|
||||
# command code shortcuts
|
||||
|
||||
POWER = 1
|
||||
NUM_0 = 2
|
||||
|
|
Loading…
Reference in a new issue