#!/usr/bin/env python3
# set up wheel
import array
import os
import struct
from fcntl import ioctl
from typing import NoReturn
# Iterate over the joystick devices.
print ( ' Available devices: ' )
for fn in os . listdir ( ' /dev/input ' ) :
if fn . startswith ( ' js ' ) :
print ( f ' /dev/input/ { 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 : ' Queue[str] ' ) - > NoReturn :
# Open the joystick device.
fn = ' /dev/input/js0 '
print ( f ' Opening { 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 ( f ' Device name: { 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 , f ' unknown(0x { _axis : 02x } ) ' )
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 , f ' unknown(0x { btn : 03x } ) ' )
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 )
value , mtype , number = struct . unpack ( ' 4xhBB ' , 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 ( f " throttle_ { normalized : f } " )
elif axis == " rz " : # brake
fvalue = value / 32767.0
axis_states [ axis ] = fvalue
normalized = ( 1 - fvalue ) * 50
q . put ( f " brake_ { normalized : f } " )
elif axis == " x " : # steer angle
fvalue = value / 32767.0
axis_states [ axis ] = fvalue
normalized = fvalue
q . put ( f " steer_ { normalized : f } " )
elif mtype & 0x01 : # buttons
if value == 1 : # press down
if number in [ 0 , 19 ] : # X
q . put ( " cruise_down " )
elif number in [ 3 , 18 ] : # triangle
q . put ( " cruise_up " )
elif number in [ 1 , 6 ] : # square
q . put ( " cruise_cancel " )
elif number in [ 10 , 21 ] : # R3
q . put ( " reverse_switch " )
if __name__ == ' __main__ ' :
from multiprocessing import Process , Queue
q : Queue [ str ] = Queue ( )
p = Process ( target = wheel_poll_thread , args = ( q , ) )
p . start ( )