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("../PS3BDRemote")
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
37 except Exception as e
:
38 print("Failed to import sixwatch now disabled: " + str(e
))
45 ICON_PATH
= "../../icons/"
47 # fallback to system wide modules
48 from kodi
.bt
.hid
import HID
49 from kodi
.bt
.bt
import bt_lookup_name
50 from kodi
.xbmcclient
import XBMCClient
51 from kodi
.ps3
import sixaxis
52 from kodi
.ps3_remote
import process_keys
as process_remote
53 from kodi
.defs
import *
55 from kodi
.ps3
import sixwatch
56 except Exception as e
:
57 print("Failed to import sixwatch now disabled: " + str(e
))
60 import kodi
.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
):
144 except Exception as e
:
148 except Exception as e
:
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()
173 except Exception as e
:
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
194 except Exception as e
:
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)
227 except Exception as e
:
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
)
259 except Exception as e
:
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())
325 except Exception as e
:
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
)
378 except Exception as e
:
381 print("Connecting to : %s" % ipaddr
)
385 except Exception as e
:
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")
403 except Exception as e
: