ps3joy.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 # Software License Agreement (BSD License)
3 #
4 # Copyright (c) 2009, Willow Garage, Inc.
5 # All rights reserved.
6 #
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions
9 # are met:
10 #
11 # * Redistributions of source code must retain the above copyright
12 # notice, this list of conditions and the following disclaimer.
13 # * Redistributions in binary form must reproduce the above
14 # copyright notice, this list of conditions and the following
15 # disclaimer in the documentation and/or other materials provided
16 # with the distribution.
17 # * Neither the name of the Willow Garage nor the names of its
18 # contributors may be used to endorse or promote products derived
19 # from this software without specific prior written permission.
20 #
21 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 # POSSIBILITY OF SUCH DAMAGE.
33 
34 from __future__ import print_function
35 from bluetooth import *
36 import select
37 import fcntl
38 import os
39 import time
40 import sys
41 import traceback
42 import subprocess
43 
44 L2CAP_PSM_HIDP_CTRL = 17
45 L2CAP_PSM_HIDP_INTR = 19
46 inactivity_timout_string = "--inactivity-timeout"
47 no_disable_bluetoothd_string = "--no-disable-bluetoothd"
48 redirect_output_string = "--redirect-output"
49 continuous_motion_output_string = "--continuous-output"
50 
51 
52 class uinput:
53  EV_KEY = 1
54  EV_REL = 2
55  EV_ABS = 3
56  BUS_USB = 3
57  ABS_MAX = 0x3f
58 
59 
60 class uinputjoy:
61  def open_uinput(self):
62  for name in ["/dev/input/uinput", "/dev/misc/uinput", "/dev/uinput"]:
63  try:
64  return os.open(name, os.O_WRONLY)
65  break
66  except Exception as e:
67  pass
68  return None
69 
70  def __init__(self, buttons, axes, axmin, axmax, axfuzz, axflat):
71  self.file = self.open_uinput()
72  if self.file is None:
73  print("Trying to modprobe uinput.", file=sys.stderr)
74  os.system("modprobe uinput > /dev/null 2>&1")
75  time.sleep(1) # uinput isn't ready to go right away.
76  self.file = self.open_uinput()
77  if self.file is None:
78  print("Can't open uinput device. Is it accessible by this user? "
79  "Did you mean to run as root?", file=sys.stderr)
80  raise IOError
81 
82  UI_SET_EVBIT = 0x40045564
83  UI_SET_KEYBIT = 0x40045565
84  UI_SET_RELBIT = 0x40045566
85  UI_DEV_CREATE = 0x5501
86  UI_SET_RELBIT = 0x40045566
87  UI_SET_ABSBIT = 0x40045567
88  uinput_user_dev = "80sHHHHi" + (uinput.ABS_MAX+1)*4*'i'
89 
90  if len(axes) != len(axmin) or len(axes) != len(axmax):
91  raise Exception("uinputjoy.__init__: axes, axmin and axmax should have same length")
92 
93  absmin = [0] * (uinput.ABS_MAX+1)
94  absmax = [0] * (uinput.ABS_MAX+1)
95  absfuzz = [2] * (uinput.ABS_MAX+1)
96  absflat = [4] * (uinput.ABS_MAX+1)
97  for i in range(0, len(axes)):
98  absmin[axes[i]] = axmin[i]
99  absmax[axes[i]] = axmax[i]
100  absfuzz[axes[i]] = axfuzz[i]
101  absflat[axes[i]] = axflat[i]
102 
103  os.write(self.file,
104  struct.pack(uinput_user_dev, "Sony Playstation SixAxis/DS3",
105  uinput.BUS_USB, 0x054C, 0x0268, 0, 0, *(absmax + absmin + absfuzz + absflat)))
106 
107  fcntl.ioctl(self.file, UI_SET_EVBIT, uinput.EV_KEY)
108 
109  for b in buttons:
110  fcntl.ioctl(self.file, UI_SET_KEYBIT, b)
111 
112  for a in axes:
113  fcntl.ioctl(self.file, UI_SET_EVBIT, uinput.EV_ABS)
114  fcntl.ioctl(self.file, UI_SET_ABSBIT, a)
115 
116  fcntl.ioctl(self.file, UI_DEV_CREATE)
117 
118  self.value = [None] * (len(buttons) + len(axes))
119  self.type = [uinput.EV_KEY] * len(buttons) + [uinput.EV_ABS] * len(axes)
120  self.code = buttons + axes
121 
122  def update(self, value):
123  input_event = "LLHHi"
124  t = time.time()
125  th = int(t)
126  tl = int((t - th) * 1000000)
127  if len(value) != len(self.value):
128  print("Unexpected length for value in update (%i instead of %i). "
129  "This is a bug." % (len(value), len(self.value)), file=sys.stderr)
130  for i in range(0, len(value)):
131  if value[i] != self.value[i]:
132  os.write(self.file, struct.pack(input_event, th, tl, self.type[i], self.code[i], value[i]))
133  self.value = list(value)
134 
135 
136 class BadJoystickException(Exception):
137  def __init__(self):
138  Exception.__init__(self, "Unsupported joystick.")
139 
140 
141 class decoder:
142  def __init__(self, inactivity_timeout=float(1e3000), continuous_motion_output=False):
143  # buttons=[uinput.BTN_SELECT, uinput.BTN_THUMBL, uinput.BTN_THUMBR, uinput.BTN_START,
144  # uinput.BTN_FORWARD, uinput.BTN_RIGHT, uinput.BTN_BACK, uinput.BTN_LEFT,
145  # uinput.BTN_TL, uinput.BTN_TR, uinput.BTN_TL2, uinput.BTN_TR2,
146  # uinput.BTN_X, uinput.BTN_A, uinput.BTN_B, uinput.BTN_Y,
147  # uinput.BTN_MODE]
148  # axes=[uinput.ABS_X, uinput.ABS_Y, uinput.ABS_Z, uinput.ABS_RX,
149  # uinput.ABS_RX, uinput.ABS_RY, uinput.ABS_PRESSURE, uinput.ABS_DISTANCE,
150  # uinput.ABS_THROTTLE, uinput.ABS_RUDDER, uinput.ABS_WHEEL, uinput.ABS_GAS,
151  # uinput.ABS_HAT0Y, uinput.ABS_HAT1Y, uinput.ABS_HAT2Y, uinput.ABS_HAT3Y,
152  # uinput.ABS_TILT_X, uinput.ABS_TILT_Y, uinput.ABS_MISC, uinput.ABS_RZ,
153  # ]
154  buttons = range(0x100, 0x111)
155  axes = range(0, 20)
156  axmin = [0] * 20
157  axmax = [255] * 20
158  axfuzz = [2] * 20
159  axflat = [4] * 20
160  for i in range(-4, 0): # Gyros have more bits than other axes
161  axmax[i] = 1023
162  axfuzz[i] = 4
163  axflat[i] = 4
164  if continuous_motion_output:
165  axfuzz[i] = 0
166  axflat[i] = 0
167  for i in range(4, len(axmin)-4): # Buttons should be zero when not pressed
168  axmin[i] = -axmax[i]
169  self.joy = uinputjoy(buttons, axes, axmin, axmax, axfuzz, axflat)
170  self.axmid = [sum(pair)/2 for pair in zip(axmin, axmax)]
171  self.fullstop() # Probably useless because of uinput startup bug
172  self.outlen = len(buttons) + len(axes)
173  self.inactivity_timeout = inactivity_timeout
174 
175  step_active = 1
176  step_idle = 2
177  step_error = 3
178 
179  def step(self, rawdata): # Returns true if the packet was legal
180  if len(rawdata) == 50:
181  joy_coding = "!1B2x3B1x4B4x12B15x4H"
182  data = list(struct.unpack(joy_coding, rawdata))
183  prefix = data.pop(0)
184  if prefix != 161:
185  print("Unexpected prefix (%i). Is this a PS3 Dual Shock or Six Axis?" % prefix, file=sys.stderr)
186  return self.step_error
187  out = []
188  for j in range(0, 2): # Split out the buttons.
189  curbyte = data.pop(0)
190  for k in range(0, 8):
191  out.append(int((curbyte & (1 << k)) != 0))
192  out = out + data
193  self.joy.update(out)
194  axis_motion = [
195  abs(out[17:][i] - self.axmid[i]) > 20 for i in range(0, len(out)-17-4)
196  ] # 17 buttons, 4 inertial sensors
197  if any(out[0:17]) or any(axis_motion):
198  return self.step_active
199  return self.step_idle
200  elif len(rawdata) == 13:
201  print("Your bluetooth adapter is not supported. "
202  "Does it support Bluetooth 2.0?", file=sys.stderr)
203  raise BadJoystickException()
204  else:
205  print("Unexpected packet length (%i). "
206  "Is this a PS3 Dual Shock or Six Axis?" % len(rawdata), file=sys.stderr)
207  return self.step_error
208 
209  def fullstop(self):
210  self.joy.update([0] * 17 + self.axmid)
211 
212  def run(self, intr, ctrl):
213  activated = False
214  try:
215  self.fullstop()
216  lastactivitytime = lastvalidtime = time.time()
217  while True:
218  (rd, wr, err) = select.select([intr], [], [], 0.1)
219  curtime = time.time()
220  if len(rd) + len(wr) + len(err) == 0: # Timeout
221  ctrl.send("\x53\xf4\x42\x03\x00\x00") # Try activating the stream.
222  else: # Got a frame.
223  if not activated:
224  print("Connection activated")
225  activated = True
226  try:
227  rawdata = intr.recv(128)
228  except BluetoothError as s:
229  print("Got Bluetooth error %s. Disconnecting." % s)
230  return
231  if len(rawdata) == 0: # Orderly shutdown of socket
232  print("Joystick shut down the connection, battery may be discharged.")
233  return
234  stepout = self.step(rawdata)
235  if stepout != self.step_error:
236  lastvalidtime = curtime
237  if stepout == self.step_active:
238  lastactivitytime = curtime
239  if curtime - lastactivitytime > self.inactivity_timeout:
240  print("Joystick inactive for %.0f seconds. "
241  "Disconnecting to save battery." % self.inactivity_timeout)
242  return
243  if curtime - lastvalidtime >= 0.1:
244  # Zero all outputs if we don't hear a valid frame for 0.1 to 0.2 seconds
245  self.fullstop()
246  if curtime - lastvalidtime >= 5:
247  # Disconnect if we don't hear a valid frame for 5 seconds
248  print("No valid data for 5 seconds. Disconnecting. "
249  "This should not happen, please report it.")
250  return
251  time.sleep(0.005) # No need to blaze through the loop when there is an error
252  finally:
253  self.fullstop()
254 
255 
256 class Quit(Exception):
257  def __init__(self, errorcode):
258  Exception.__init__(self)
259  self.errorcode = errorcode
260 
261 
263  # Check if hci0 is up and pscanning, take action as necessary.
264  proc = subprocess.Popen(['hciconfig'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
265  (out, err) = proc.communicate()
266  if out.find('UP') == -1:
267  os.system("hciconfig hci0 up > /dev/null 2>&1")
268  if out.find('PSCAN') == -1:
269  os.system("hciconfig hci0 pscan > /dev/null 2>&1")
270 
271 
273  def __init__(self, decoder):
274  self.decoder = decoder
275  self.shutdown = False
276 
277  def prepare_bluetooth_socket(self, port):
278  sock = BluetoothSocket(L2CAP)
279  return self.prepare_socket(sock, port)
280 
281  def prepare_net_socket(self, port):
282  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
283  return self.prepare_socket(sock, port)
284 
285  def prepare_socket(self, sock, port):
286  first_loop = True
287  while True:
288  try:
289  sock.bind(("", port))
290  except Exception as e:
291  print(repr(e))
292  if first_loop:
293  print("Error binding to socket, will retry every 5 seconds. "
294  "Do you have another ps3joy.py running? This error occurs on "
295  "some distributions (such as Ubuntu Karmic). "
296  "Please read http://www.ros.org/wiki/ps3joy/Troubleshooting for solutions.",
297  file=sys.stderr)
298  first_loop = False
299  time.sleep(0.5)
300  continue
301  sock.listen(1)
302  return sock
303 
304  def listen_net(self, intr_port, ctrl_port):
305  intr_sock = self.prepare_net_socket(intr_port)
306  ctrl_sock = self.prepare_net_socket(ctrl_port)
307  self.listen(intr_sock, ctrl_sock)
308 
309  def listen_bluetooth(self):
310  intr_sock = self.prepare_bluetooth_socket(L2CAP_PSM_HIDP_INTR)
311  ctrl_sock = self.prepare_bluetooth_socket(L2CAP_PSM_HIDP_CTRL)
312  self.listen(intr_sock, ctrl_sock)
313 
314  def listen(self, intr_sock, ctrl_sock):
315  self.n = 0
316  while not self.shutdown:
317  print("Waiting for connection. Disconnect your PS3 joystick from USB and press the pairing button.")
318  try:
319  intr_sock.settimeout(5)
320  ctrl_sock.settimeout(1)
321  while True:
322  try:
323  (intr, (idev, iport)) = intr_sock.accept()
324  break
325  except Exception as e:
326  if str(e) == 'timed out':
328  else:
329  raise
330 
331  try:
332  try:
333  (ctrl, (cdev, cport)) = ctrl_sock.accept()
334  except Exception as e:
335  print("Got interrupt connection without control connection. Giving up on it.", file=sys.stderr)
336  continue
337  try:
338  if idev == cdev:
339  self.decoder.run(intr, ctrl)
340  print("Connection terminated.")
341  else:
342  print("Simultaneous connection from two different devices. Ignoring both.", file=sys.stderr)
343  finally:
344  ctrl.close()
345  finally:
346  intr.close()
347  except BadJoystickException:
348  pass
349  except KeyboardInterrupt:
350  print("CTRL+C detected. Exiting.")
351  quit(0)
352  except Exception as e:
353  traceback.print_exc()
354  print("Caught exception: %s" % str(e), file=sys.stderr)
355  time.sleep(1)
356  print()
357 
358 
359 def usage(errcode):
360  print("usage: ps3joy.py [" + inactivity_timout_string + "=<n>] [" + no_disable_bluetoothd_string + "] "
361  "[" + redirect_output_string + "] [" + continuous_motion_output_string + "]=<f>")
362  print("<n>: inactivity timeout in seconds (saves battery life).")
363  print("<f>: file name to redirect output to.")
364  print("Unless "+no_disable_bluetoothd_string+" is specified, bluetoothd will be stopped.")
365  raise Quit(errcode)
366 
367 
368 def is_arg_with_param(arg, prefix):
369  if not arg.startswith(prefix):
370  return False
371  if not arg.startswith(prefix+"="):
372  print("Expected '=' after "+prefix)
373  print()
374  usage(1)
375  return True
376 
377 
378 if __name__ == "__main__":
379  errorcode = 0
380  try:
381  # Get Root Privileges
382  euid = os.geteuid()
383  if euid != 0:
384  args = ['sudo', sys.executable] + sys.argv + [os.environ]
385  os.execlpe('sudo', *args)
386  if euid != 0:
387  raise SystemExit("Root Privlages Required.")
388 
389  inactivity_timeout = float(1e3000)
390  disable_bluetoothd = True
391  continuous_output = False
392 
393  for arg in sys.argv[1:]: # Be very tolerant in case we are roslaunched.
394  if arg == "--help":
395  usage(0)
396  elif is_arg_with_param(arg, inactivity_timout_string):
397  str_value = arg[len(inactivity_timout_string)+1:]
398  try:
399  inactivity_timeout = float(str_value)
400  if inactivity_timeout < 0:
401  print("Inactivity timeout must be positive.")
402  print()
403  usage(1)
404  except ValueError:
405  print("Error parsing inactivity timeout: " + str_value)
406  print()
407  usage(1)
408  elif arg == no_disable_bluetoothd_string:
409  disable_bluetoothd = False
410  elif arg == continuous_motion_output_string:
411  continuous_output = True
412  elif is_arg_with_param(arg, redirect_output_string):
413  str_value = arg[len(redirect_output_string) + 1:]
414  try:
415  print("Redirecting output to:", str_value)
416  sys.stdout = open(str_value, "a", 1)
417  except IOError as e:
418  print("Error opening file to redirect output:", str_value)
419  raise Quit(1)
420  sys.stderr = sys.stdout
421  else:
422  print("Ignoring parameter: '%s'" % arg)
423 
424  if disable_bluetoothd:
425  os.system("/etc/init.d/bluetooth stop > /dev/null 2>&1")
426  time.sleep(1) # Give the socket time to be available.
427  try:
428  while os.system("hciconfig hci0 > /dev/null 2>&1") != 0:
429  print("No bluetooth dongle found or bluez rosdep not installed. Will retry in 5 seconds.",
430  file=sys.stderr)
431  time.sleep(5)
432  if inactivity_timeout == float(1e3000):
433  print("No inactivity timeout was set. (Run with --help for details.)")
434  else:
435  print("Inactivity timeout set to %.0f seconds." % inactivity_timeout)
436  cm = connection_manager(decoder(inactivity_timeout=inactivity_timeout,
437  continuous_motion_output=continuous_output))
438  cm.listen_bluetooth()
439  finally:
440  if disable_bluetoothd:
441  os.system("/etc/init.d/bluetooth start > /dev/null 2>&1")
442  except Quit as e:
443  errorcode = e.errorcode
444  except KeyboardInterrupt:
445  print("CTRL+C detected. Exiting.")
446  exit(errorcode)
ps3joy.uinputjoy.value
value
Definition: ps3joy.py:118
ps3joy.decoder.inactivity_timeout
inactivity_timeout
Definition: ps3joy.py:173
ps3joy.uinputjoy.file
file
Definition: ps3joy.py:71
ps3joy.uinputjoy.type
type
Definition: ps3joy.py:119
ps3joy.decoder
Definition: ps3joy.py:141
ps3joy.decoder.step_active
int step_active
Definition: ps3joy.py:175
ps3joy.decoder.step
def step(self, rawdata)
Definition: ps3joy.py:179
ps3joy.uinputjoy.open_uinput
def open_uinput(self)
Definition: ps3joy.py:61
ps3joy.connection_manager.__init__
def __init__(self, decoder)
Definition: ps3joy.py:273
ps3joy.connection_manager.prepare_socket
def prepare_socket(self, sock, port)
Definition: ps3joy.py:285
ps3joy.uinputjoy
Definition: ps3joy.py:60
ps3joy.decoder.step_idle
int step_idle
Definition: ps3joy.py:176
ps3joy.decoder.axmid
axmid
Definition: ps3joy.py:170
ps3joy.check_hci_status
def check_hci_status()
Definition: ps3joy.py:262
ps3joy.decoder.joy
joy
Definition: ps3joy.py:169
ps3joy.connection_manager.listen
def listen(self, intr_sock, ctrl_sock)
Definition: ps3joy.py:314
ps3joy.connection_manager.decoder
decoder
Definition: ps3joy.py:274
ps3joy.connection_manager.prepare_net_socket
def prepare_net_socket(self, port)
Definition: ps3joy.py:281
ps3joy.uinput
Definition: ps3joy.py:52
ps3joy.decoder.outlen
outlen
Definition: ps3joy.py:172
ps3joy.Quit.errorcode
errorcode
Definition: ps3joy.py:259
ps3joy.connection_manager
Definition: ps3joy.py:272
ps3joy.is_arg_with_param
def is_arg_with_param(arg, prefix)
Definition: ps3joy.py:368
ps3joy.connection_manager.prepare_bluetooth_socket
def prepare_bluetooth_socket(self, port)
Definition: ps3joy.py:277
ps3joy.connection_manager.n
n
Definition: ps3joy.py:315
ps3joy.uinputjoy.__init__
def __init__(self, buttons, axes, axmin, axmax, axfuzz, axflat)
Definition: ps3joy.py:70
ps3joy.uinputjoy.code
code
Definition: ps3joy.py:120
ps3joy.decoder.fullstop
def fullstop(self)
Definition: ps3joy.py:209
ps3joy.connection_manager.listen_net
def listen_net(self, intr_port, ctrl_port)
Definition: ps3joy.py:304
ps3joy.connection_manager.shutdown
shutdown
Definition: ps3joy.py:275
ps3joy.connection_manager.listen_bluetooth
def listen_bluetooth(self)
Definition: ps3joy.py:309
ps3joy.Quit.__init__
def __init__(self, errorcode)
Definition: ps3joy.py:257
ps3joy.BadJoystickException.__init__
def __init__(self)
Definition: ps3joy.py:137
ps3joy.usage
def usage(errcode)
Definition: ps3joy.py:359
ps3joy.BadJoystickException
Definition: ps3joy.py:136
ps3joy.decoder.__init__
def __init__(self, inactivity_timeout=float(1e3000), continuous_motion_output=False)
Definition: ps3joy.py:142
ps3joy.Quit
Definition: ps3joy.py:256
ps3joy.uinputjoy.update
def update(self, value)
Definition: ps3joy.py:122
ps3joy.decoder.run
def run(self, intr, ctrl)
Definition: ps3joy.py:212
ps3joy.decoder.step_error
int step_error
Definition: ps3joy.py:177


ps3joy
Author(s): Blaise Gassend, pascal@pabr.org, Melonee Wise
autogenerated on Sun May 2 2021 02:17:55