/
wdtv_sim.py
executable file
·316 lines (259 loc) · 9.04 KB
/
wdtv_sim.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
#!/usr/bin/env python
# -*- coding: us-ascii -*-
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
#
"""Emulate WDTV api enough to work with Pebble Skipstone
NOTE Python 2 and Python 3 code.
"""
import cgi
try:
from java.net import InetAddress
except ImportError:
# Not Jython
InetAddress = None
import json # Python 2.6+
import logging
import mimetypes
import os
from pprint import pprint
import socket
try:
import SocketServer
except ImportError:
# Python 3
import socketserver as SocketServer
import struct
import sys
from wsgiref.simple_server import make_server, WSGIServer, WSGIRequestHandler
import pyautogui # https://github.com/asweigart/pyautogui
log = logging.getLogger(__name__)
logging.basicConfig()
log.setLevel(level=logging.DEBUG)
version_tuple = (0, 0, 1)
version = version_string = '%d.%d.%d' % version_tuple
try:
basestring
except NameError:
# python 3
basestring = str
def send_single_keypress(key_name):
print(key_name)
pyautogui.press(key_name)
def send_multiple_keypresses(key_press_list):
for key_tuple in key_press_list:
print(key_tuple)
pyautogui.hotkey(*key_tuple)
def send_presses(presses_to_send):
if isinstance(presses_to_send, basestring):
# assume single key name
command_function = send_single_keypress
else:
# assume list of, key presses (list of tuples)
command_function = send_multiple_keypresses
result = command_function(presses_to_send)
return result
# See https://github.com/Skipstone/Skipstone/blob/master/src/js/src/wdtv.js
# for commands
commands = {
'[': 'prevtrack',
']': 'nexttrack',
'H': [('ctrl', 'shift', 'b'), ('ctrl', 'left')], # rewind - send two sets of controls, like chinavision cvsb-983 remote
'I': [('ctrl', 'shift', 'f'), ('ctrl', 'right')], # forward - send two sets of controls, like chinavision cvsb-983 remote
'M': 'volumemute',
'p': 'playpause',
't': 'stop',
'n': 'enter', # OK
'T': 'browserback',
'u': 'up',
'd': 'down',
'l': 'left',
'r': 'right',
}
"""
From http://www.openremote.org/display/forums/WD+TV+Live+and+Openremote
full mapping list:
w = power
o = home
\\\\ = subtitle
, = audio
[ = prev
t = stop
] = next
H = rewind
p = play_pause
I = forward
T = back
l = cursor left
n = ok
u = cursor up
G = option
r = cursor right
U = prev_page
d = cursor down
D = next_page
M = mute
s = setup
x = a (green)
y = b (red)
z = c (yellow)
A = d (blue)
1 = 1
2 = 2
3 = 3
4 = 4
5 = 5
6 = 6
7 = 7
8 = 8
9 = 9
0 = 0
E = search
X = eject
"""
def not_found(environ, start_response):
"""serves 404s."""
#start_response('404 NOT FOUND', [('Content-Type', 'text/plain')])
#return ['Not Found']
start_response('404 NOT FOUND', [('Content-Type', 'text/html')])
return ['''<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL /??????? was not found on this server.</p>
</body></html>''']
# A relatively simple WSGI application. It's going to print out the
# environment dictionary after being updated by setup_testing_defaults
def simple_app(environ, start_response):
status = '200 OK'
headers = [('Content-type', 'text/plain')]
result = []
path_info = environ['PATH_INFO']
# Returns a dictionary in which the values are lists
get_dict = cgi.parse_qs(environ['QUERY_STRING'])
# POST values
# the environment variable CONTENT_LENGTH may be empty or missing
try:
request_body_size = int(environ.get('CONTENT_LENGTH', 0))
except (ValueError):
request_body_size = 0
# Read POST body
request_body = environ['wsgi.input'].read(request_body_size)
if path_info and path_info == '/cgi-bin/toServerValue.cgi':
log.debug('request_body %r', request_body)
request_body = request_body.decode('utf-8')
log.debug('request_body %r', request_body)
data = json.loads(request_body)
command = data['remote']
#command_function = commands.get(command)
presses_to_send = commands[command]
command_result = send_presses(presses_to_send)
if command_result is None:
# no idea what should be returned however Skipstone doesn't check :-)
command_result = ''
result.append(command_result.encode('utf-8'))
else:
return not_found(environ, start_response)
start_response(status, headers)
return result
class MyWSGIRequestHandler(WSGIRequestHandler):
"""Do not perform Fully Qualified Domain Lookup.
One networks with missing (or poor) DNS, getfqdn can take over 5 secs
EACH network IO"""
def address_string(self):
"""Return the client address formatted for logging.
This version looks up the full hostname using gethostbyaddr(),
and tries to find a name that contains at least one dot.
"""
host, port = self.client_address[:2]
return host # socket.getfqdn(host)
class MyWSGIServer(WSGIServer):
"""Avoid default Python socket server oddities.
1) Do not perform Fully Qualified Domain Lookup.
On networks with missing (or poor) DNS, getfqdn() can take over
5 secs EACH network IO.
2) Do not allow address re-use.
On machines where something is already listening on the requested
port the default Windows socket setting for Python SocketServers
is to allow the bind to succeed (even though it can't then service
any requests).
One possible workaround for Windows is to set the
DisableAddressSharing registry entry:
(HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\Afd\Parameters)
and reboot. This registry setting prevents multiple sockets from binding
to the same port and is essentially enabling SO_EXCLUSIVEADDRUSE on
all sockets. See Java bug 6421091.
"""
allow_reuse_address = False # Use SO_EXCLUSIVEADDRUSE, True only makes sense for testing
def server_bind(self):
"""Override server_bind to store the server name."""
SocketServer.TCPServer.server_bind(self)
host, port = self.socket.getsockname()[:2]
self.server_name = host # socket.getfqdn(host) i.e. use as-is do *not* perform reverse lookup
self.server_port = port
self.setup_environ()
def determine_local_ipaddr():
local_address = None
# Most portable (for modern versions of Python)
if hasattr(socket, 'gethostbyname_ex'):
for ip in socket.gethostbyname_ex(socket.gethostname())[2]:
if not ip.startswith('127.'):
local_address = ip
break
# may be none still (nokia) http://www.skweezer.com/s.aspx/-/pypi~python~org/pypi/netifaces/0~4 http://www.skweezer.com/s.aspx?q=http://stackoverflow.com/questions/166506/finding-local-ip-addresses-using-pythons-stdlib has alonger one
if sys.platform.startswith('linux'):
import fcntl
def get_ip_address(ifname):
ifname = ifname.encode('latin1')
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
return socket.inet_ntoa(fcntl.ioctl(
s.fileno(),
0x8915, # SIOCGIFADDR
struct.pack('256s', ifname[:15])
)[20:24])
if not local_address:
for devname in os.listdir('/sys/class/net/'):
try:
ip = get_ip_address(devname)
if not ip.startswith('127.'):
local_address = ip
break
except IOError:
pass
# Jython / Java approach
if not local_address and InetAddress:
addr = InetAddress.getLocalHost()
hostname = addr.getHostName()
for ip_addr in InetAddress.getAllByName(hostname):
if not ip_addr.isLoopbackAddress():
local_address = ip_addr.getHostAddress()
break
if not local_address:
# really? Oh well lets connect to a remote socket (Google DNS server)
# and see what IP we use them
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('8.8.8.8', 53))
ip = s.getsockname()[0]
s.close()
if not ip.startswith('127.'):
local_address = ip
return local_address
def doit():
# TODO yep, currently hard coded if ran standalone
port = 8000
port = 8080
port = 8777
httpd = make_server('', port, simple_app, server_class=MyWSGIServer, handler_class=MyWSGIRequestHandler)
local_ip = determine_local_ipaddr()
log.info('wdtv simulator %s', version)
log.info('Starting server: %r', (local_ip, port))
log.info('To stop, issue CTRL-C or (Windows) CTRL-Break')
log.info('Configure in Skipstone on phone using address: %s:%d', local_ip, port)
httpd.serve_forever()
def main(argv=None):
if argv is None:
argv = sys.argv
doit()
return 0
if __name__ == "__main__":
sys.exit(main())