/mission_control/sw3/joystick.py

https://github.com/ncsurobotics/seawolf5
Python | 219 lines | 152 code | 49 blank | 18 comment | 25 complexity | 159192e7c133fcf3d711aea06be96d62 MD5 | raw file
  1. """
  2. Linux joystick driver created using Linux joystick API
  3. https://www.kernel.org/doc/Documentation/input/joystick-api.txt
  4. """
  5. from __future__ import division, print_function
  6. import copy
  7. import glob
  8. import math
  9. import struct
  10. def get_devices():
  11. """ Return a list of connected joystick devices
  12. """
  13. return glob.glob("/dev/js*") + glob.glob("/dev/input/js*")
  14. class JoystickDriver(object):
  15. EVENT_BUTTON = 0x01
  16. EVENT_AXIS = 0x02
  17. EVENT_INIT = 0x80
  18. EVENT_FORMAT = "@IhBB" # [uint][short][char][char]
  19. EVENT_SIZE = struct.calcsize(EVENT_FORMAT)
  20. def __init__(self, device_path):
  21. self.path = device_path
  22. self.dev = open(self.path, "rb")
  23. self.dev.flush()
  24. self.struct = struct.Struct(JoystickDriver.EVENT_FORMAT)
  25. def _unpack(self, s):
  26. unpacked = self.struct.unpack(s)
  27. return dict(zip(("time", "value", "type", "number"), unpacked))
  28. def get_event(self):
  29. s = self.dev.read(JoystickDriver.EVENT_SIZE)
  30. return self._unpack(s)
  31. def close(self):
  32. self.dev.close()
  33. class Button(object):
  34. def __init__(self, number, name=None):
  35. self.number = number
  36. self.name = name
  37. self.value = 0
  38. def __repr__(self):
  39. return "{name}: {val}".format(name=self.name, val=self.value)
  40. class Axis(object):
  41. def __init__(self, axis, name=None):
  42. self.axis = axis
  43. self.name = name
  44. self.x = 0
  45. self.y = 0
  46. @property
  47. def magnitude(self):
  48. """ Returns the current magnitude of the stick
  49. """
  50. scalar = 1.0 / 32767
  51. return math.sqrt(math.pow(self.x, 2) + math.pow(self.y, 2)) * scalar
  52. @property
  53. def angle_degrees(self):
  54. """ Returns the angle of the stick in degrees, from -180 to 180, with 0 at due east
  55. """
  56. return math.degrees(self.angle_radians)
  57. @property
  58. def angle_radians(self):
  59. """ Returns the angle of the stick in radians, from -pi to pi, with 0 at due east
  60. """
  61. x, y = self.x, -self.y
  62. if x == 0:
  63. if y > 0:
  64. return math.pi / 2
  65. else:
  66. return -math.pi / 2
  67. return math.atan2(y, x)
  68. @property
  69. def bearing_degrees(self):
  70. """ Returns the angle of the stick in degrees, from -180 to 180, with 0 at due north
  71. """
  72. x, y = self.x, -self.y
  73. if x == 0 and y < 0:
  74. return -180
  75. elif x == 0:
  76. return 0
  77. base = math.atan(float(abs(y)) / abs(x))
  78. angle = base * 360 / (2 * math.pi)
  79. angle = 90 - angle
  80. if x >= 0 and y >= 0:
  81. return angle
  82. elif x <= 0 and y >= 0:
  83. return -angle
  84. elif x <= 0 and y <= 0:
  85. return -180 + angle
  86. else:
  87. return 180 - angle
  88. @property
  89. def bearing_radians(self):
  90. """ Returns the angle of the stick in degrees, from -pi to pi, with 0 at due north
  91. """
  92. return math.radians(self.bearing_degrees)
  93. def __repr__(self):
  94. return "{name}: ({x}, {y})".format(name=self.name, x=self.x, y=self.y)
  95. class Joystick(object):
  96. def __init__(self, device_path, mapping):
  97. self.joystick = JoystickDriver(device_path)
  98. self.mapping = mapping
  99. self.axes = dict()
  100. self.buttons = dict()
  101. for i in self.mapping:
  102. if isinstance(i, Button):
  103. self.buttons[i.number] = i
  104. elif isinstance(i, Axis):
  105. self.axes[i.axis[0]] = i
  106. self.axes[i.axis[1]] = i
  107. def poll(self):
  108. # Here be magic
  109. event = None
  110. event_type = JoystickDriver.EVENT_INIT
  111. while event_type & JoystickDriver.EVENT_INIT:
  112. event = self.joystick.get_event()
  113. event_type = event["type"]
  114. event_number = event["number"]
  115. event_value = event["value"]
  116. if event_type & JoystickDriver.EVENT_BUTTON:
  117. button = self.buttons[event_number]
  118. button.value = event_value
  119. return copy.deepcopy(button)
  120. elif event_type & JoystickDriver.EVENT_AXIS:
  121. axis = self.axes[event_number]
  122. if event_number == axis.axis[0]:
  123. axis.x = event_value
  124. else:
  125. axis.y = event_value
  126. return copy.deepcopy(axis)
  127. else:
  128. raise Exception("Unsupported event")
  129. def close(self):
  130. self.joystick.close()
  131. LOGITECH = (
  132. Axis((0, 1), "leftStick"),
  133. Axis((2, 3), "rightStick"),
  134. Axis((4, 5), "hat"),
  135. Button(0, "button1"),
  136. Button(1, "button2"),
  137. Button(2, "button3"),
  138. Button(3, "button4"),
  139. Button(4, "button5"),
  140. Button(5, "button6"),
  141. Button(6, "button7"),
  142. Button(7, "button8"),
  143. Button(8, "button9"),
  144. Button(9, "button10"),
  145. Button(10, "leftStickButton"),
  146. Button(11, "rightStickButton")
  147. )
  148. STEERINGWHEEL = (
  149. Axis((0, 1), "wheelAndThrottle"),
  150. Axis((2, 3), "padX"),
  151. Axis((4, 5), "padY"),
  152. Button(0, "button1"),
  153. Button(1, "button2"),
  154. Button(2, "button3"),
  155. Button(3, "button4"),
  156. Button(4, "button5"),
  157. Button(5, "button6"),
  158. Button(6, "button7"),
  159. Button(7, "button8"),
  160. Button(8, "button9"),
  161. Button(9, "button10")
  162. )
  163. # Testing purposes only
  164. if __name__ == '__main__':
  165. available = get_devices()
  166. if len(available) < 1:
  167. print("No joystick available")
  168. testj = Joystick(available[0], LOGITECH)
  169. while True:
  170. print(testj.poll())