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
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
|
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (C) 2008-2013 Team XBMC
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import sys
import traceback
import time
import struct
import threading
import os
if os.path.exists("../../lib/python"):
sys.path.append("../PS3BDRemote")
sys.path.append("../../lib/python")
from bt.hid import HID
from bt.bt import bt_lookup_name
from xbmcclient import XBMCClient
from ps3 import sixaxis
from ps3_remote import process_keys as process_remote
try:
from ps3 import sixwatch
except Exception as e:
print("Failed to import sixwatch now disabled: " + str(e))
sixwatch = None
try:
import zeroconf
except:
zeroconf = None
ICON_PATH = "../../icons/"
else:
# fallback to system wide modules
from kodi.bt.hid import HID
from kodi.bt.bt import bt_lookup_name
from kodi.xbmcclient import XBMCClient
from kodi.ps3 import sixaxis
from kodi.ps3_remote import process_keys as process_remote
from kodi.defs import *
try:
from kodi.ps3 import sixwatch
except Exception as e:
print("Failed to import sixwatch now disabled: " + str(e))
sixwatch = None
try:
import kodi.zeroconf as zeroconf
except:
zeroconf = None
event_threads = []
def printerr():
trace = ""
exception = ""
exc_list = traceback.format_exception_only (sys.exc_type, sys.exc_value)
for entry in exc_list:
exception += entry
tb_list = traceback.format_tb(sys.exc_info()[2])
for entry in tb_list:
trace += entry
print("%s\n%s" % (exception, trace), "Script Error")
class StoppableThread ( threading.Thread ):
def __init__(self):
threading.Thread.__init__(self)
self._stop = False
self.set_timeout(0)
def stop_thread(self):
self._stop = True
def stop(self):
return self._stop
def close_sockets(self):
if self.isock:
try:
self.isock.close()
except:
pass
self.isock = None
if self.csock:
try:
self.csock.close()
except:
pass
self.csock = None
self.last_action = 0
def set_timeout(self, seconds):
self.timeout = seconds
def reset_timeout(self):
self.last_action = time.time()
def idle_time(self):
return time.time() - self.last_action
def timed_out(self):
if (time.time() - self.last_action) > self.timeout:
return True
else:
return False
class PS3SixaxisThread ( StoppableThread ):
def __init__(self, csock, isock, ipaddr="127.0.0.1"):
StoppableThread.__init__(self)
self.csock = csock
self.isock = isock
self.xbmc = XBMCClient(name="PS3 Sixaxis", icon_file=ICON_PATH + "/bluetooth.png", ip=ipaddr)
self.set_timeout(600)
def run(self):
six = sixaxis.sixaxis(self.xbmc, self.csock, self.isock)
self.xbmc.connect()
self.reset_timeout()
try:
while not self.stop():
if self.timed_out():
raise Exception("PS3 Sixaxis powering off, timed out")
if self.idle_time() > 50:
self.xbmc.connect()
try:
if six.process_socket(self.isock):
self.reset_timeout()
except Exception as e:
print(e)
break
except Exception as e:
printerr()
six.close()
self.close_sockets()
class PS3RemoteThread ( StoppableThread ):
def __init__(self, csock, isock, ipaddr="127.0.0.1"):
StoppableThread.__init__(self)
self.csock = csock
self.isock = isock
self.xbmc = XBMCClient(name="PS3 Blu-Ray Remote", icon_file=ICON_PATH + "/bluetooth.png", ip=ipaddr)
self.set_timeout(600)
self.services = []
self.current_xbmc = 0
def run(self):
self.xbmc.connect()
try:
# start the zeroconf thread if possible
try:
self.zeroconf_thread = ZeroconfThread()
self.zeroconf_thread.add_service('_xbmc-events._udp',
self.zeroconf_service_handler)
self.zeroconf_thread.start()
except Exception as e:
print(str(e))
# main thread loop
while not self.stop():
status = process_remote(self.isock, self.xbmc)
if status == 2: # 2 = socket read timeout
if self.timed_out():
raise Exception("PS3 Blu-Ray Remote powering off, "\
"timed out")
elif status == 3: # 3 = ps and skip +
self.next_xbmc()
elif status == 4: # 4 = ps and skip -
self.previous_xbmc()
elif not status: # 0 = keys are normally processed
self.reset_timeout()
# process_remote() will raise an exception on read errors
except Exception as e:
print(str(e))
self.zeroconf_thread.stop()
self.close_sockets()
def next_xbmc(self):
"""
Connect to the next XBMC instance
"""
self.current_xbmc = (self.current_xbmc + 1) % len( self.services )
self.reconnect()
return
def previous_xbmc(self):
"""
Connect to the previous XBMC instance
"""
self.current_xbmc -= 1
if self.current_xbmc < 0 :
self.current_xbmc = len( self.services ) - 1
self.reconnect()
return
def reconnect(self):
"""
Reconnect to an XBMC instance based on self.current_xbmc
"""
try:
service = self.services[ self.current_xbmc ]
print("Connecting to %s" % service['name'])
self.xbmc.connect( service['address'], service['port'] )
self.xbmc.send_notification("PS3 Blu-Ray Remote", "New Connection", None)
except Exception as e:
print(str(e))
def zeroconf_service_handler(self, event, service):
"""
Zeroconf event handler
"""
if event == zeroconf.SERVICE_FOUND: # new xbmc service detected
self.services.append( service )
elif event == zeroconf.SERVICE_LOST: # xbmc service lost
try:
# search for the service by name, since IP+port isn't available
for s in self.services:
# nuke it, if found
if service['name'] == s['name']:
self.services.remove(s)
break
except:
pass
return
class SixWatch(threading.Thread):
def __init__(self, mac):
threading.Thread.__init__(self)
self.mac = mac
self.daemon = True
self.start()
def run(self):
while True:
try:
sixwatch.main(self.mac)
except Exception as e:
print("Exception caught in sixwatch, restarting: " + str(e))
class ZeroconfThread ( threading.Thread ):
"""
"""
def __init__(self):
threading.Thread.__init__(self)
self._zbrowser = None
self._services = []
def run(self):
if zeroconf:
# create zeroconf service browser
self._zbrowser = zeroconf.Browser()
# add the requested services
for service in self._services:
self._zbrowser.add_service( service[0], service[1] )
# run the event loop
self._zbrowser.run()
return
def stop(self):
"""
Stop the zeroconf browser
"""
try:
self._zbrowser.stop()
except:
pass
return
def add_service(self, type, handler):
"""
Add a new service to search for.
NOTE: Services must be added before thread starts.
"""
self._services.append( [ type, handler ] )
def usage():
print("""
PS3 Sixaxis / Blu-Ray Remote HID Server v0.1
Usage: ps3.py [bdaddress] [XBMC host]
bdaddress => address of local bluetooth device to use (default: auto)
(e.g. aa:bb:cc:dd:ee:ff)
ip address => IP address or hostname of the XBMC instance (default: localhost)
(e.g. 192.168.1.110)
""")
def start_hidd(bdaddr=None, ipaddr="127.0.0.1"):
devices = [ 'PLAYSTATION(R)3 Controller',
'BD Remote Control' ]
hid = HID(bdaddr)
watch = None
if sixwatch:
try:
print("Starting USB sixwatch")
watch = SixWatch(hid.get_local_address())
except Exception as e:
print("Failed to initialize sixwatch" + str(e))
pass
while True:
if hid.listen():
(csock, addr) = hid.get_control_socket()
device_name = bt_lookup_name(addr[0])
if device_name == devices[0]:
# handle PS3 controller
handle_ps3_controller(hid, ipaddr)
elif device_name == devices[1]:
# handle the PS3 remote
handle_ps3_remote(hid, ipaddr)
else:
print("Unknown Device: %s" % (device_name))
def handle_ps3_controller(hid, ipaddr):
print("Received connection from a Sixaxis PS3 Controller")
csock = hid.get_control_socket()[0]
isock = hid.get_interrupt_socket()[0]
sixaxis = PS3SixaxisThread(csock, isock, ipaddr)
add_thread(sixaxis)
sixaxis.start()
return
def handle_ps3_remote(hid, ipaddr):
print("Received connection from a PS3 Blu-Ray Remote")
csock = hid.get_control_socket()[0]
isock = hid.get_interrupt_socket()[0]
isock.settimeout(1)
remote = PS3RemoteThread(csock, isock, ipaddr)
add_thread(remote)
remote.start()
return
def add_thread(thread):
global event_threads
event_threads.append(thread)
def main():
if len(sys.argv)>3:
return usage()
bdaddr = ""
ipaddr = "127.0.0.1"
try:
for addr in sys.argv[1:]:
try:
# ensure that the addr is of the format 'aa:bb:cc:dd:ee:ff'
if "".join([ str(len(a)) for a in addr.split(":") ]) != "222222":
raise Exception("Invalid format")
bdaddr = addr
print("Connecting to Bluetooth device: %s" % bdaddr)
except Exception as e:
try:
ipaddr = addr
print("Connecting to : %s" % ipaddr)
except:
print(str(e))
return usage()
except Exception as e:
pass
print("Starting HID daemon")
start_hidd(bdaddr, ipaddr)
if __name__=="__main__":
try:
main()
finally:
for t in event_threads:
try:
print("Waiting for thread "+str(t)+" to terminate")
t.stop_thread()
if t.isAlive():
t.join()
print("Thread "+str(t)+" terminated")
except Exception as e:
print(str(e))
pass
|