-
-
Save rdb/8864666 to your computer and use it in GitHub Desktop.
# Released by rdb under the Unlicense (unlicense.org) | |
# Based on information from: | |
# https://www.kernel.org/doc/Documentation/input/joystick-api.txt | |
import os, struct, array | |
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 : 'throttle', | |
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 = [] | |
# 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))) | |
# Main event loop | |
while True: | |
evbuf = jsdev.read(8) | |
if evbuf: | |
time, value, type, number = struct.unpack('IhBB', evbuf) | |
if type & 0x80: | |
print("(initial)", end="") | |
if type & 0x01: | |
button = button_map[number] | |
if button: | |
button_states[button] = value | |
if value: | |
print("%s pressed" % (button)) | |
else: | |
print("%s released" % (button)) | |
if type & 0x02: | |
axis = axis_map[number] | |
if axis: | |
fvalue = value / 32767.0 | |
axis_states[axis] = fvalue | |
print("%s: %.3f" % (axis, fvalue)) |
I think the JSIOCGBTNMAP IOCTL call has the wrong magic number.
If I'm reading the kernel source correctly, buf should be 0x200 unsigned shorts ((KEY_MAX - BTN_MISC) + 1), not decimal 200.
Also, len(buf) gives the number of elements in buf (0x200). (buf.buffer_info()[1] * buf.itemsize) returns the size in bytes (0x400).
If both assumptions are correct, the magic number should be 0x84006a34, not 0x80406a34.
Also, "throttle" is misspelled as "trottle".
@d-wiles that really depends on which version of the headers you have; I just tried in CentOS 5 and it's even 0x82006a34. Linux is flexible about this, masking out the size bits:
https://github.com/torvalds/linux/blob/6417f03132a6952cd17ddd8eaddbac92b61b17e0/drivers/input/joydev.c#L578
KEY_MAX also used to have a lower value.
Thank you for your work, but you don't have to wait for this command until there's an event on the joystick
I would like to proceed
evbuf = jsdev.read(8).
I want to look at the camera while controlling the RC car. However, there must be an event for the camera to work.
You can probably make it non-blocking by using the lower-level os.open()
instead of open()
with the os.O_RDONLY | os.O_NONBLOCK
flags.
Another way is to use a thread. It will be woken up when there is data available.
Thank you for your feed pack. But I get an error.
(AttributeError: 'int' object has no attribute 'read')
I'm sorry I'm a Python beginner.
It's not that trivial: the os.open()
call is different than built-in open()
, it returns a fd, you have to use it with the other functions from the os module, change all the calls to those instead.
Thank you for answer. But I'm a Python beginner, sorry. I'm not sure exactly which part I need to change. Can you give more detailed feedback?
I checked and instead of os.open there may be an easier alternative, by calling this on the file after the open call:
os.set_blocking(jsdev.fileno(), False)
Of course you'll need to deal with the exception you will probably get when calling read()
without data being available.
Hi, i want to control servo with a joystick by using a Raspberry Pi How can I take input from the joystick and give it to Servo?
basically controlling th servo through the joystick.
Works right out of the box with an xbox one controller on my raspberry pi. Thanks man. Saved me a headache.