2 # -*- coding: utf-8 -*-
4 # Copyright (C) 2008-2013 Team XBMC
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License along
17 # with this program; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
27 if os.path.exists("../../lib/python"):
28 sys.path.append("../PS3 BD Remote")
29 sys.path.append("../../lib/python")
30 from bt.hid import HID
31 from bt.bt import bt_lookup_name
32 from xbmcclient import XBMCClient
33 from ps3 import sixaxis
34 from ps3_remote import process_keys as process_remote
36 from ps3 import sixwatch
38 print "Failed to import sixwatch now disabled: " + str(e)
45 ICON_PATH = "../../icons/"
47 # fallback to system wide modules
48 from xbmc.bt.hid import HID
49 from xbmc.bt.bt import bt_lookup_name
50 from xbmc.xbmcclient import XBMCClient
51 from xbmc.ps3 import sixaxis
52 from xbmc.ps3_remote import process_keys as process_remote
53 from xbmc.defs import *
55 from xbmc.ps3 import sixwatch
57 print "Failed to import sixwatch now disabled: " + str(e)
60 import xbmc.zeroconf as zeroconf
70 exc_list = traceback.format_exception_only (sys.exc_type, sys.exc_value)
71 for entry in exc_list:
73 tb_list = traceback.format_tb(sys.exc_info()[2])
76 print("%s\n%s" % (exception, trace), "Script Error")
79 class StoppableThread ( threading.Thread ):
81 threading.Thread.__init__(self)
85 def stop_thread(self):
91 def close_sockets(self):
106 def set_timeout(self, seconds):
107 self.timeout = seconds
109 def reset_timeout(self):
110 self.last_action = time.time()
113 return time.time() - self.last_action
116 if (time.time() - self.last_action) > self.timeout:
122 class PS3SixaxisThread ( StoppableThread ):
123 def __init__(self, csock, isock, ipaddr="127.0.0.1"):
124 StoppableThread.__init__(self)
127 self.xbmc = XBMCClient(name="PS3 Sixaxis", icon_file=ICON_PATH + "/bluetooth.png", ip=ipaddr)
128 self.set_timeout(600)
131 six = sixaxis.sixaxis(self.xbmc, self.csock, self.isock)
135 while not self.stop():
138 raise Exception("PS3 Sixaxis powering off, timed out")
139 if self.idle_time() > 50:
142 if six.process_socket(self.isock):
154 class PS3RemoteThread ( StoppableThread ):
155 def __init__(self, csock, isock, ipaddr="127.0.0.1"):
156 StoppableThread.__init__(self)
159 self.xbmc = XBMCClient(name="PS3 Blu-Ray Remote", icon_file=ICON_PATH + "/bluetooth.png", ip=ipaddr)
160 self.set_timeout(600)
162 self.current_xbmc = 0
167 # start the zeroconf thread if possible
169 self.zeroconf_thread = ZeroconfThread()
170 self.zeroconf_thread.add_service('_xbmc-events._udp',
171 self.zeroconf_service_handler)
172 self.zeroconf_thread.start()
177 while not self.stop():
178 status = process_remote(self.isock, self.xbmc)
180 if status == 2: # 2 = socket read timeout
182 raise Exception("PS3 Blu-Ray Remote powering off, "\
184 elif status == 3: # 3 = ps and skip +
187 elif status == 4: # 4 = ps and skip -
190 elif not status: # 0 = keys are normally processed
193 # process_remote() will raise an exception on read errors
197 self.zeroconf_thread.stop()
202 Connect to the next XBMC instance
204 self.current_xbmc = (self.current_xbmc + 1) % len( self.services )
208 def previous_xbmc(self):
210 Connect to the previous XBMC instance
212 self.current_xbmc -= 1
213 if self.current_xbmc < 0 :
214 self.current_xbmc = len( self.services ) - 1
220 Reconnect to an XBMC instance based on self.current_xbmc
223 service = self.services[ self.current_xbmc ]
224 print "Connecting to %s" % service['name']
225 self.xbmc.connect( service['address'], service['port'] )
226 self.xbmc.send_notification("PS3 Blu-Ray Remote", "New Connection", None)
230 def zeroconf_service_handler(self, event, service):
232 Zeroconf event handler
234 if event == zeroconf.SERVICE_FOUND: # new xbmc service detected
235 self.services.append( service )
237 elif event == zeroconf.SERVICE_LOST: # xbmc service lost
239 # search for the service by name, since IP+port isn't available
240 for s in self.services:
242 if service['name'] == s['name']:
243 self.services.remove(s)
249 class SixWatch(threading.Thread):
250 def __init__(self, mac):
251 threading.Thread.__init__(self)
258 sixwatch.main(self.mac)
260 print "Exception caught in sixwatch, restarting: " + str(e)
262 class ZeroconfThread ( threading.Thread ):
267 threading.Thread.__init__(self)
268 self._zbrowser = None
273 # create zeroconf service browser
274 self._zbrowser = zeroconf.Browser()
276 # add the requested services
277 for service in self._services:
278 self._zbrowser.add_service( service[0], service[1] )
288 Stop the zeroconf browser
291 self._zbrowser.stop()
296 def add_service(self, type, handler):
298 Add a new service to search for.
299 NOTE: Services must be added before thread starts.
301 self._services.append( [ type, handler ] )
306 PS3 Sixaxis / Blu-Ray Remote HID Server v0.1
308 Usage: ps3.py [bdaddress] [XBMC host]
310 bdaddress => address of local bluetooth device to use (default: auto)
311 (e.g. aa:bb:cc:dd:ee:ff)
312 ip address => IP address or hostname of the XBMC instance (default: localhost)
316 def start_hidd(bdaddr=None, ipaddr="127.0.0.1"):
317 devices = [ 'PLAYSTATION(R)3 Controller',
318 'BD Remote Control' ]
323 print "Starting USB sixwatch"
324 watch = SixWatch(hid.get_local_address())
326 print "Failed to initialize sixwatch" + str(e)
331 (csock, addr) = hid.get_control_socket()
332 device_name = bt_lookup_name(addr[0])
333 if device_name == devices[0]:
334 # handle PS3 controller
335 handle_ps3_controller(hid, ipaddr)
336 elif device_name == devices[1]:
337 # handle the PS3 remote
338 handle_ps3_remote(hid, ipaddr)
340 print "Unknown Device: %s" % (device_name)
342 def handle_ps3_controller(hid, ipaddr):
343 print "Received connection from a Sixaxis PS3 Controller"
344 csock = hid.get_control_socket()[0]
345 isock = hid.get_interrupt_socket()[0]
346 sixaxis = PS3SixaxisThread(csock, isock, ipaddr)
351 def handle_ps3_remote(hid, ipaddr):
352 print "Received connection from a PS3 Blu-Ray Remote"
353 csock = hid.get_control_socket()[0]
354 isock = hid.get_interrupt_socket()[0]
356 remote = PS3RemoteThread(csock, isock, ipaddr)
361 def add_thread(thread):
363 event_threads.append(thread)
371 for addr in sys.argv[1:]:
373 # ensure that the addr is of the format 'aa:bb:cc:dd:ee:ff'
374 if "".join([ str(len(a)) for a in addr.split(":") ]) != "222222":
375 raise Exception("Invalid format")
377 print "Connecting to Bluetooth device: %s" % bdaddr
381 print "Connecting to : %s" % ipaddr
388 print "Starting HID daemon"
389 start_hidd(bdaddr, ipaddr)
391 if __name__=="__main__":
395 for t in event_threads:
397 print "Waiting for thread "+str(t)+" to terminate"
401 print "Thread "+str(t)+" terminated"