#!/usr/bin/env python3 # set up wheel import array import os import struct from fcntl import ioctl # Iterate over the joystick devices. print('Available devices:') for fn in os.listdir('/dev/input'): if fn.startswith('js'): print(' /dev/input/%s' % (fn)) # We'll store the states here. axis_states = {} button_states = {} # These constants were borrowed from linux/input.h axis_names = { 0x00 : 'x', 0x01 : 'y', 0x02 : 'z', 0x03 : 'rx', 0x04 : 'ry', 0x05 : 'rz', 0x06 : 'trottle', 0x07 : 'rudder', 0x08 : 'wheel', 0x09 : 'gas', 0x0a : 'brake', 0x10 : 'hat0x', 0x11 : 'hat0y', 0x12 : 'hat1x', 0x13 : 'hat1y', 0x14 : 'hat2x', 0x15 : 'hat2y', 0x16 : 'hat3x', 0x17 : 'hat3y', 0x18 : 'pressure', 0x19 : 'distance', 0x1a : 'tilt_x', 0x1b : 'tilt_y', 0x1c : 'tool_width', 0x20 : 'volume', 0x28 : 'misc', } button_names = { 0x120 : 'trigger', 0x121 : 'thumb', 0x122 : 'thumb2', 0x123 : 'top', 0x124 : 'top2', 0x125 : 'pinkie', 0x126 : 'base', 0x127 : 'base2', 0x128 : 'base3', 0x129 : 'base4', 0x12a : 'base5', 0x12b : 'base6', 0x12f : 'dead', 0x130 : 'a', 0x131 : 'b', 0x132 : 'c', 0x133 : 'x', 0x134 : 'y', 0x135 : 'z', 0x136 : 'tl', 0x137 : 'tr', 0x138 : 'tl2', 0x139 : 'tr2', 0x13a : 'select', 0x13b : 'start', 0x13c : 'mode', 0x13d : 'thumbl', 0x13e : 'thumbr', 0x220 : 'dpad_up', 0x221 : 'dpad_down', 0x222 : 'dpad_left', 0x223 : 'dpad_right', # XBox 360 controller uses these codes. 0x2c0 : 'dpad_left', 0x2c1 : 'dpad_right', 0x2c2 : 'dpad_up', 0x2c3 : 'dpad_down', } axis_map = [] button_map = [] def wheel_poll_thread(q): # Open the joystick device. fn = '/dev/input/js0' print('Opening %s...' % fn) jsdev = open(fn, 'rb') # Get the device name. #buf = bytearray(63) buf = array.array('B', [0] * 64) ioctl(jsdev, 0x80006a13 + (0x10000 * len(buf)), buf) # JSIOCGNAME(len) js_name = buf.tobytes().rstrip(b'\x00').decode('utf-8') print('Device name: %s' % js_name) # Get number of axes and buttons. buf = array.array('B', [0]) ioctl(jsdev, 0x80016a11, buf) # JSIOCGAXES num_axes = buf[0] buf = array.array('B', [0]) ioctl(jsdev, 0x80016a12, buf) # JSIOCGBUTTONS num_buttons = buf[0] # Get the axis map. buf = array.array('B', [0] * 0x40) ioctl(jsdev, 0x80406a32, buf) # JSIOCGAXMAP for axis in buf[:num_axes]: axis_name = axis_names.get(axis, 'unknown(0x%02x)' % axis) axis_map.append(axis_name) axis_states[axis_name] = 0.0 # Get the button map. buf = array.array('H', [0] * 200) ioctl(jsdev, 0x80406a34, buf) # JSIOCGBTNMAP for btn in buf[:num_buttons]: btn_name = button_names.get(btn, 'unknown(0x%03x)' % btn) button_map.append(btn_name) button_states[btn_name] = 0 print('%d axes found: %s' % (num_axes, ', '.join(axis_map))) print('%d buttons found: %s' % (num_buttons, ', '.join(button_map))) # Enable FF import evdev # pylint: disable=import-error from evdev import ecodes, InputDevice # pylint: disable=import-error device = evdev.list_devices()[0] evtdev = InputDevice(device) val = 24000 evtdev.write(ecodes.EV_FF, ecodes.FF_AUTOCENTER, val) while True: evbuf = jsdev.read(8) time, value, mtype, number = struct.unpack('IhBB', evbuf) # print(mtype, number, value) if mtype & 0x02: # wheel & paddles axis = axis_map[number] if axis == "z": # gas fvalue = value / 32767.0 axis_states[axis] = fvalue normalized = (1 - fvalue) * 50 q.put(str("throttle_%f" % normalized)) if axis == "rz": # brake fvalue = value / 32767.0 axis_states[axis] = fvalue normalized = (1 - fvalue) * 50 q.put(str("brake_%f" % normalized)) if axis == "x": # steer angle fvalue = value / 32767.0 axis_states[axis] = fvalue normalized = fvalue q.put(str("steer_%f" % normalized)) if mtype & 0x01: # buttons if number in [0,19]: # X if value == 1: # press down q.put(str("cruise_down")) if number in [3,18]: # triangle if value == 1: # press down q.put(str("cruise_up")) if number in [1,6]: # square if value == 1: # press down q.put(str("cruise_cancel")) if number in [10,21]: # R3 if value == 1: # press down q.put(str("reverse_switch")) if __name__ == '__main__': from multiprocessing import Process p = Process(target=wheel_poll_thread) p.start()