Last active
October 18, 2020 18:04
-
-
Save walchko/23d1e6480511892b4e0951254ce5f380 to your computer and use it in GitHub Desktop.
python Linux PS4 joystick
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
#----------------------------------------- | |
# MIT License | |
# Taken from: https://github.com/autorope/donkeycar/blob/dev/donkeycar/parts/controller.py | |
# 3 Jan 2020 | |
# all of the button/axes names were wrong, but it did work | |
# I am using the USB cable and the touch pad/button dives my mouse ... | |
# maybe if I fix that, then the buttons will go back to what they were? | |
#------------------------------------------ | |
# kernel doc: https://www.kernel.org/doc/Documentation/input/joystick-api.txt | |
# | |
# 2. Event Reading | |
# ~~~~~~~~~~~~~~~~ | |
# | |
# struct js_event e; | |
# read (fd, &e, sizeof(e)); | |
# | |
# where js_event is defined as | |
# | |
# struct js_event { | |
# __u32 time; /* event timestamp in milliseconds */ | |
# __s16 value; /* value */ | |
# __u8 type; /* event type */ | |
# __u8 number; /* axis/button number */ | |
# }; | |
# | |
# 2.4 js_event.time | |
# ~~~~~~~~~~~~~~~~~ | |
# | |
# The time an event was generated is stored in ``js_event.time''. It's a time | |
# in milliseconds since ... well, since sometime in the past. This eases the | |
# task of detecting double clicks, figuring out if movement of axis and button | |
# presses happened at the same time, and similar. | |
import os | |
import array | |
import time | |
import struct | |
import logging | |
from pprint import pprint | |
class Joystick(object): | |
''' | |
An interface to a physical joystick | |
''' | |
def __init__(self, dev_fn='/dev/input/js0'): | |
# states hold the current value of the joystick | |
self.axis_states = {} | |
self.button_states = {} | |
# names match button codes to human button understanding | |
self.axis_names = {} | |
self.button_names = {} | |
# mapping of names to buttons/axes for printing | |
self.axis_map = [] | |
self.button_map = [] | |
self.jsdev = None # joystick file descriptor | |
self.dev_fn = dev_fn # joystick file name | |
def __del__(self): | |
# if still open, close it | |
if self.jsdev: | |
self.jsdev.close() | |
self.jsdev = None | |
def init(self): | |
try: | |
from fcntl import ioctl | |
except ModuleNotFoundError: | |
self.num_axes = 0 | |
self.num_buttons = 0 | |
print("no support for fnctl module. joystick not enabled.") | |
return False | |
if not os.path.exists(self.dev_fn): | |
print(self.dev_fn, "is missing") | |
return False | |
''' | |
call once to setup connection to device and map buttons | |
''' | |
# Open the joystick device. | |
# print('Opening %s...' % self.dev_fn) | |
self.jsdev = open(self.dev_fn, 'rb') | |
# Get the device name. | |
buf = array.array('B', [0] * 64) | |
ioctl(self.jsdev, 0x80006a13 + (0x10000 * len(buf)), buf) # JSIOCGNAME(len) | |
self.js_name = buf.tobytes().decode('utf-8') | |
# print('Device name: %s' % self.js_name) | |
# Get number of axes and buttons. | |
buf = array.array('B', [0]) | |
ioctl(self.jsdev, 0x80016a11, buf) # JSIOCGAXES | |
self.num_axes = buf[0] | |
buf = array.array('B', [0]) | |
ioctl(self.jsdev, 0x80016a12, buf) # JSIOCGBUTTONS | |
self.num_buttons = buf[0] | |
# Get the axis map. | |
buf = array.array('B', [0] * 0x40) | |
ioctl(self.jsdev, 0x80406a32, buf) # JSIOCGAXMAP | |
for axis in buf[:self.num_axes]: | |
axis_name = self.axis_names.get(axis, 'unknown(0x%02x)' % axis) | |
self.axis_map.append(axis_name) | |
self.axis_states[axis_name] = 0.0 | |
# Get the button map. | |
buf = array.array('H', [0] * 200) | |
ioctl(self.jsdev, 0x80406a34, buf) # JSIOCGBTNMAP | |
for btn in buf[:self.num_buttons]: | |
btn_name = self.button_names.get(btn, 'unknown(0x%03x)' % btn) | |
self.button_map.append(btn_name) | |
self.button_states[btn_name] = 0 | |
#print('btn', '0x%03x' % btn, 'name', btn_name) | |
self.show_map() | |
return True | |
def show_map(self): | |
''' | |
list the buttons and axis found on this joystick | |
''' | |
print('='*70) | |
print(" {}".format(self.js_name)) | |
print(" File Descriptor: {}".format(self.dev_fn)) | |
print("-"*70) | |
print (' %d axes found:' % (self.num_axes)) | |
print (' %s' % (', '.join(self.axis_map))) | |
print (' %d buttons found:' % (self.num_buttons)) | |
print (' %s' % (', '.join(self.button_map))) | |
print("-"*70) | |
def poll(self): | |
''' | |
query the state of the joystick, returns button which was pressed, if any, | |
and axis which was moved, if any. button_state will be None, 1, or 0 if no changes, | |
pressed, or released. axis_val will be a float from -1 to +1. button and axis will | |
be the string label determined by the axis map in init. | |
''' | |
button = None | |
button_state = None | |
axis = None | |
axis_val = None | |
if self.jsdev is None: | |
print("self.jsdev == None") | |
return button, button_state, axis, axis_val | |
# read the joystick, it will que up events until you read them. If | |
# you do not read them fast enough, the driver will reset and get a | |
# 0x80 below | |
evbuf = self.jsdev.read(8) | |
# print(">> evbuf", evbuf) | |
if evbuf: | |
tval, value, typev, number = struct.unpack('IhBB', evbuf) | |
if typev & 0x80: | |
# ignore initialization event, either because the joystick just | |
# started OR you are not reading it fast enough and the internal | |
# queue is over flowing | |
print(">> initialization event ... ignore values") | |
return button, button_state, axis, axis_val | |
if typev & 0x01: | |
button = self.button_map[number] | |
#print(tval, value, typev, number, button, 'pressed') | |
if button: | |
self.button_states[button] = value | |
button_state = value | |
logging.info("button: %s state: %d" % (button, value)) | |
if typev & 0x02: | |
axis = self.axis_map[number] | |
if axis: | |
fvalue = value / 32767.0 | |
self.axis_states[axis] = fvalue | |
axis_val = fvalue | |
logging.debug("axis: %s val: %f" % (axis, fvalue)) | |
return button, button_state, axis, axis_val | |
class PS4Joystick(Joystick): | |
''' | |
An interface to a physical PS4 joystick available at /dev/input/js0 | |
''' | |
def __init__(self, *args, **kwargs): | |
super(PS4Joystick, self).__init__(*args, **kwargs) | |
self.axis_names = { | |
# 16b floats: -1.0 to 1.0 | |
# Left stick (LS) | |
0x00 : 'LS_x', | |
0x01 : 'LS_y', | |
# Right stick (RS) | |
0x03 : 'RS_x', | |
0x04 : 'RS_y', | |
0x02 : 'L2_axis', | |
0x05 : 'R2_axis', | |
0x10 : 'dpad_leftright', | |
0x11 : 'dpad_updown', | |
0x19 : 'tilt_a', | |
0x1a : 'tilt_b', | |
0x1b : 'tilt_c', | |
0x06 : 'motion_a', | |
0x07 : 'motion_b', | |
0x08 : 'motion_c', | |
} | |
self.button_names = { | |
# 0 - unpushed, 1 - pushed | |
0x134 : 'square', | |
0x130 : 'cross', | |
0x131 : 'circle', | |
0x133 : 'triangle', | |
0x136 : 'L1', | |
0x137 : 'R1', | |
0x138 : 'L2', | |
0x139 : 'R2', | |
0x13d : 'L3', | |
0x13e : 'R3', | |
0x13a : 'share', | |
0x13b : 'options', | |
0x13c : 'PS', | |
} | |
js = PS4Joystick() | |
js.init() | |
# try: | |
# while True: | |
# button, button_state, axis, axis_val = js.poll() | |
# pprint(js.axis_states) | |
# pprint(js.button_states) | |
# # print(axis) | |
# # print(axis_val) | |
# # time.sleep(0.05) | |
# if js.button_states['PS']: | |
# break | |
# except KeyboardInterrupt: | |
# print("\n\nctrl-C") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment