Created
March 25, 2021 04:53
-
-
Save dattranfiot/1da0dcee1db4907205cf734094441e3a to your computer and use it in GitHub Desktop.
drive car around with PS4 Bluetooth controller
This file contains hidden or 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
| import os | |
| import array | |
| import time | |
| import struct | |
| import logging | |
| from jetracer.nvidia_racecar import NvidiaRacecar | |
| import time | |
| STEERING_OFFSET = 0.1 | |
| STEERING_GAIN = -0.35 | |
| THROTTLE_GAIN = 0.2 | |
| class Joystick(object): | |
| ''' | |
| An interface to a physical joystick | |
| ''' | |
| def __init__(self, dev_fn='/dev/input/js0'): | |
| self.axis_states = {} | |
| self.button_states = {} | |
| self.axis_names = {} | |
| self.button_names = {} | |
| self.axis_map = [] | |
| self.button_map = [] | |
| self.jsdev = None | |
| self.dev_fn = dev_fn | |
| 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) | |
| return True | |
| def show_map(self): | |
| ''' | |
| list the buttons and axis found on this joystick | |
| ''' | |
| print('%d axes found: %s' % (self.num_axes, ', '.join(self.axis_map))) | |
| print('%d buttons found: %s' % (self.num_buttons, ', '.join(self.button_map))) | |
| 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: | |
| return button, button_state, axis, axis_val | |
| # Main event loop | |
| evbuf = self.jsdev.read(8) | |
| if evbuf: | |
| tval, value, typev, number = struct.unpack('IhBB', evbuf) | |
| if typev & 0x80: | |
| # ignore initialization event | |
| 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 = { | |
| 0x00: 'left_stick_horz', | |
| 0x01: 'left_stick_vert', | |
| 0x02: 'right_stick_horz', | |
| 0x05: 'right_stick_vert', | |
| 0x03: 'left_trigger_axis', | |
| 0x04: 'right_trigger_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 = { | |
| 0x130: 'square', | |
| 0x131: 'cross', | |
| 0x132: 'circle', | |
| 0x133: 'triangle', | |
| 0x134: 'L1', | |
| 0x135: 'R1', | |
| 0x136: 'L2', | |
| 0x137: 'R2', | |
| 0x13a: 'L3', | |
| 0x13b: 'R3', | |
| 0x13d: 'pad', | |
| 0x138: 'share', | |
| 0x139: 'options', | |
| 0x13c: 'PS', | |
| } | |
| if __name__ == "__main__": | |
| car = NvidiaRacecar() | |
| car.throttle_gain = THROTTLE_GAIN | |
| car.steering_offset = STEERING_OFFSET | |
| car.steering_gain = STEERING_GAIN | |
| car.throttle = 0 | |
| car.steering = 0 | |
| js = PS4Joystick('/dev/input/js0') | |
| js.init() | |
| while True: | |
| button, button_state, axis, axis_val = js.poll() | |
| if button is not None or axis is not None: | |
| if axis is 'left_stick_horz': | |
| car.steering = axis_val | |
| if axis is 'right_stick_vert': | |
| car.throttle = axis_val |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment