manual_ctrl.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. #!/usr/bin/env python3
  2. # set up wheel
  3. import array
  4. import os
  5. import struct
  6. from fcntl import ioctl
  7. from typing import NoReturn
  8. from openpilot.tools.sim.bridge.common import control_cmd_gen
  9. # Iterate over the joystick devices.
  10. print('Available devices:')
  11. for fn in os.listdir('/dev/input'):
  12. if fn.startswith('js'):
  13. print(f' /dev/input/{fn}')
  14. # We'll store the states here.
  15. axis_states: dict[str, float] = {}
  16. button_states: dict[str, float] = {}
  17. # These constants were borrowed from linux/input.h
  18. axis_names = {
  19. 0x00 : 'x',
  20. 0x01 : 'y',
  21. 0x02 : 'z',
  22. 0x03 : 'rx',
  23. 0x04 : 'ry',
  24. 0x05 : 'rz',
  25. 0x06 : 'trottle',
  26. 0x07 : 'rudder',
  27. 0x08 : 'wheel',
  28. 0x09 : 'gas',
  29. 0x0a : 'brake',
  30. 0x10 : 'hat0x',
  31. 0x11 : 'hat0y',
  32. 0x12 : 'hat1x',
  33. 0x13 : 'hat1y',
  34. 0x14 : 'hat2x',
  35. 0x15 : 'hat2y',
  36. 0x16 : 'hat3x',
  37. 0x17 : 'hat3y',
  38. 0x18 : 'pressure',
  39. 0x19 : 'distance',
  40. 0x1a : 'tilt_x',
  41. 0x1b : 'tilt_y',
  42. 0x1c : 'tool_width',
  43. 0x20 : 'volume',
  44. 0x28 : 'misc',
  45. }
  46. button_names = {
  47. 0x120 : 'trigger',
  48. 0x121 : 'thumb',
  49. 0x122 : 'thumb2',
  50. 0x123 : 'top',
  51. 0x124 : 'top2',
  52. 0x125 : 'pinkie',
  53. 0x126 : 'base',
  54. 0x127 : 'base2',
  55. 0x128 : 'base3',
  56. 0x129 : 'base4',
  57. 0x12a : 'base5',
  58. 0x12b : 'base6',
  59. 0x12f : 'dead',
  60. 0x130 : 'a',
  61. 0x131 : 'b',
  62. 0x132 : 'c',
  63. 0x133 : 'x',
  64. 0x134 : 'y',
  65. 0x135 : 'z',
  66. 0x136 : 'tl',
  67. 0x137 : 'tr',
  68. 0x138 : 'tl2',
  69. 0x139 : 'tr2',
  70. 0x13a : 'select',
  71. 0x13b : 'start',
  72. 0x13c : 'mode',
  73. 0x13d : 'thumbl',
  74. 0x13e : 'thumbr',
  75. 0x220 : 'dpad_up',
  76. 0x221 : 'dpad_down',
  77. 0x222 : 'dpad_left',
  78. 0x223 : 'dpad_right',
  79. # XBox 360 controller uses these codes.
  80. 0x2c0 : 'dpad_left',
  81. 0x2c1 : 'dpad_right',
  82. 0x2c2 : 'dpad_up',
  83. 0x2c3 : 'dpad_down',
  84. }
  85. axis_name_list: list[str] = []
  86. button_name_list: list[str] = []
  87. def wheel_poll_thread(q: 'Queue[str]') -> NoReturn:
  88. # Open the joystick device.
  89. fn = '/dev/input/js0'
  90. print(f'Opening {fn}...')
  91. jsdev = open(fn, 'rb')
  92. # Get the device name.
  93. #buf = bytearray(63)
  94. buf = array.array('B', [0] * 64)
  95. ioctl(jsdev, 0x80006a13 + (0x10000 * len(buf)), buf) # JSIOCGNAME(len)
  96. js_name = buf.tobytes().rstrip(b'\x00').decode('utf-8')
  97. print(f'Device name: {js_name}')
  98. # Get number of axes and buttons.
  99. buf = array.array('B', [0])
  100. ioctl(jsdev, 0x80016a11, buf) # JSIOCGAXES
  101. num_axes = buf[0]
  102. buf = array.array('B', [0])
  103. ioctl(jsdev, 0x80016a12, buf) # JSIOCGBUTTONS
  104. num_buttons = buf[0]
  105. # Get the axis map.
  106. buf = array.array('B', [0] * 0x40)
  107. ioctl(jsdev, 0x80406a32, buf) # JSIOCGAXMAP
  108. for _axis in buf[:num_axes]:
  109. axis_name = axis_names.get(_axis, f'unknown(0x{_axis:02x})')
  110. axis_name_list.append(axis_name)
  111. axis_states[axis_name] = 0.0
  112. # Get the button map.
  113. buf = array.array('H', [0] * 200)
  114. ioctl(jsdev, 0x80406a34, buf) # JSIOCGBTNMAP
  115. for btn in buf[:num_buttons]:
  116. btn_name = button_names.get(btn, f'unknown(0x{btn:03x})')
  117. button_name_list.append(btn_name)
  118. button_states[btn_name] = 0
  119. print('%d axes found: %s' % (num_axes, ', '.join(axis_name_list)))
  120. print('%d buttons found: %s' % (num_buttons, ', '.join(button_name_list)))
  121. # Enable FF
  122. import evdev
  123. from evdev import ecodes, InputDevice
  124. device = evdev.list_devices()[0]
  125. evtdev = InputDevice(device)
  126. val = 24000
  127. evtdev.write(ecodes.EV_FF, ecodes.FF_AUTOCENTER, val)
  128. while True:
  129. evbuf = jsdev.read(8)
  130. value, mtype, number = struct.unpack('4xhBB', evbuf)
  131. # print(mtype, number, value)
  132. if mtype & 0x02: # wheel & paddles
  133. axis = axis_name_list[number]
  134. if axis == "z": # gas
  135. fvalue = value / 32767.0
  136. axis_states[axis] = fvalue
  137. normalized = (1 - fvalue) * 50
  138. q.put(control_cmd_gen(f"throttle_{normalized:f}"))
  139. elif axis == "rz": # brake
  140. fvalue = value / 32767.0
  141. axis_states[axis] = fvalue
  142. normalized = (1 - fvalue) * 50
  143. q.put(control_cmd_gen(f"brake_{normalized:f}"))
  144. elif axis == "x": # steer angle
  145. fvalue = value / 32767.0
  146. axis_states[axis] = fvalue
  147. normalized = fvalue
  148. q.put(control_cmd_gen(f"steer_{normalized:f}"))
  149. elif mtype & 0x01: # buttons
  150. if value == 1: # press down
  151. if number in [0, 19]: # X
  152. q.put(control_cmd_gen("cruise_down"))
  153. elif number in [3, 18]: # triangle
  154. q.put(control_cmd_gen("cruise_up"))
  155. elif number in [1, 6]: # square
  156. q.put(control_cmd_gen("cruise_cancel"))
  157. elif number in [10, 21]: # R3
  158. q.put(control_cmd_gen("reverse_switch"))
  159. if __name__ == '__main__':
  160. from multiprocessing import Process, Queue
  161. q: 'Queue[str]' = Queue()
  162. p = Process(target=wheel_poll_thread, args=(q,))
  163. p.start()