From 1f733a74ef078b413fc8542dcf886ebf82e89b70 Mon Sep 17 00:00:00 2001 From: Prabhaav Pillai Date: Tue, 20 Oct 2026 11:47:50 -0400 Subject: [PATCH] Add DualShock 4 profile to joystick controller configurations --- openpilot/tools/joystick/devices.py | 32 ++++++++++++++++++++ openpilot/tools/joystick/joystick_control.py | 6 +++- openpilot/tools/joystick/joystick_probe.py | 15 ++++----- 3 files changed, 43 insertions(+), 10 deletions(-) create mode 100644 openpilot/tools/joystick/devices.py diff --git a/openpilot/tools/joystick/devices.py b/openpilot/tools/joystick/devices.py new file mode 100644 index 0000000000..b5182a824c --- /dev/null +++ b/openpilot/tools/joystick/devices.py @@ -0,0 +1,32 @@ +"""Find gamepads even when udev does not create /dev/input/by-id links.""" + +from pathlib import Path +import re + +import inputs + + +def refresh_gamepads() -> None: + inputs.devices = inputs.DeviceManager() + known = {pad.get_char_device_path() for pad in inputs.devices.gamepads} + try: + devices = Path('/proc/bus/input/devices').read_text().split('\n\n') + except OSError: + return + + for device in devices: + handlers = re.search(r'^H: Handlers=(.*)$', device, re.MULTILINE) + if handlers is None or not any(re.fullmatch(r'js\d+', h) for h in handlers[1].split()): + continue + event = next((h for h in handlers[1].split() if re.fullmatch(r'event\d+', h)), None) + if event is None: + continue + path = Path('/dev/input') / event + if str(path) not in known and path.exists(): + # inputs only discovers devices through udev links; comma may have none for Bluetooth HID. + try: + pad = inputs.GamePad(inputs.devices, '/dev/input/by-id/usb-fallback-event-joystick', str(path)) + except OSError: + continue + inputs.devices.gamepads.append(pad) + known.add(str(path)) diff --git a/openpilot/tools/joystick/joystick_control.py b/openpilot/tools/joystick/joystick_control.py index ef488713d5..32e12d2f0c 100755 --- a/openpilot/tools/joystick/joystick_control.py +++ b/openpilot/tools/joystick/joystick_control.py @@ -13,6 +13,7 @@ from openpilot.common.realtime import Ratekeeper from openpilot.common.swaglog import cloudlog from openpilot.common.hardware import HARDWARE from openpilot.tools.lib.kbhit import KBHit +from openpilot.tools.joystick.devices import refresh_gamepads EXPO = 0.4 @@ -21,6 +22,7 @@ CONTROLLER_PROFILES = { 'Stadia': {'name': 'Stadia', 'steer': 'ABS_X', 'accel': 'ABS_RZ', 'lo': 0., 'hi': 255.}, 'X-Box': {'name': 'Xbox', 'steer': 'ABS_X', 'accel': 'ABS_RY', 'lo': -32768., 'hi': 32767.}, 'DualSense': {'name': 'DualSense', 'steer': 'ABS_X', 'accel': 'ABS_RY', 'lo': 0., 'hi': 255.}, + 'Sony Interactive Entertainment Wireless Controller': {'name': 'DualShock 4', 'steer': 'ABS_X', 'accel': 'ABS_RZ', 'lo': 0., 'hi': 255.}, } DEFAULT_PROFILE = 'X-Box' @@ -56,6 +58,8 @@ class Joystick: self.cancel_button = 'BTN_NORTH' # BTN_NORTH=X/triangle self.is_pc = HARDWARE.get_device_type() == 'pc' self._last_scan = 0. + if not self.is_pc: + refresh_gamepads() self._load_profile() def _load_profile(self): @@ -86,7 +90,7 @@ class Joystick: if now - self._last_scan < 1.0: return self._last_scan = now - inputs.devices = inputs.DeviceManager() + refresh_gamepads() if not self.is_pc and inputs.devices.gamepads: self._load_profile() diff --git a/openpilot/tools/joystick/joystick_probe.py b/openpilot/tools/joystick/joystick_probe.py index 081bedf278..c99cd1c283 100644 --- a/openpilot/tools/joystick/joystick_probe.py +++ b/openpilot/tools/joystick/joystick_probe.py @@ -69,19 +69,16 @@ TROUBLESHOOTING * All values stay at 0 while a stick moves -> the code column will still change; if it doesn't, that physical axis isn't emitting events under any code. """ -import sys - -try: - from inputs import get_gamepad, devices, UnpluggedError -except Exception as e: - print(f"inputs import failed: {e}") - sys.exit(1) +import inputs +from openpilot.tools.joystick.devices import refresh_gamepads +from inputs import get_gamepad, UnpluggedError +refresh_gamepads() print("Detected gamepads:") -if not devices.gamepads: +if not inputs.devices.gamepads: print(" -> 'inputs' does not see your controller (driver/enumeration issue)") else: - for g in devices.gamepads: + for g in inputs.devices.gamepads: print(f" {g}") print("\nMove ONE stick at a time, fully both ways. Note the CODE + range for:")