Skip to content

Add PS5 DualSense controller support on Windows - #26

Open
ovilikaris-dotcom wants to merge 16 commits into
mainfrom
feature/ps5-controller-support
Open

ovilikaris-dotcom wants to merge 16 commits into
mainfrom
feature/ps5-controller-support

Conversation

@ovilikaris-dotcom

Copy link
Copy Markdown

Summary

Adds PS5 DualSense controller support on Windows through an SDL/Pygame bridge.

The bridge translates the DualSense controls into the existing teleop layout:

  • Options → Start/Menu
  • Circle → B
  • Triangle → Y
  • Cross → A
  • L2/R2 → LT/RT
  • Sticks and D-pad retain the same actions

The bridge requires neutral controls when connecting or reconnecting and sends a disconnected state when controller input is lost.

Validation

  • Full Docker test run before the compatibility correction: 324 tests, 0 errors, 0 failures, 0 skipped.
  • Final controller and lint tests after the correction: 9 passed.
  • Windows Python 3.13.7 with Pygame 2.6.1.
  • PS5 DualSense detected over Bluetooth as DualSense Wireless Controller.
  • All buttons, sticks, triggers, D-pad and joint groups: PASS.
  • Options enable/disable: PASS.
  • Cross return-to-zero: PASS.
  • Controller disconnect safely disabled teleoperation: PASS.
  • Bluetooth reconnect remained disabled until Options was pressed again: PASS.

Dependency

Depends on #24. Retarget this PR to main after #24 merges.

yassinsolim and others added 11 commits September 26, 2026 02:47
Replace the placeholder with the five-joint arm exported from
full-arm-smaller.SLDASM. The tools in scripts/cad regenerate the URDF
and meshes after CAD changes. demo_mode:=true sweeps each joint in turn
and reports a pass/fail row per joint on /diagnostics.

Supersedes #7.
Drive the five joints with an Xbox controller through placeholder MKS
SERVO42D/57D CAN drives, simulated until the real drives are connected.
On Windows, a Python-only XInput bridge sends the controller state to
the new wslg-teleop Compose service over UDP. The RViz camera now
follows the tool so the end of the arm stays in view.
# Conflicts:
#	waybionic_bringup/launch/ground_station.launch.py
# Conflicts:
#	waybionic_bringup/CMakeLists.txt
demo_mode:=True or 1 started joint_state_publisher_gui next to the demo,
because the launch compared the raw text with 'true'. And/Not substitutions
parse booleans the same way IfCondition does. The joint demo test now uses
True and fails without this change.

joint_demo rejects a speed_deg_s of zero or less at startup instead of never
finishing a move, and receives use_sim_time like the other nodes. The CAD
script's help now says which export --check-moves compares.
# Conflicts:
#	waybionic_bringup/launch/ground_station.launch.py
follow_camera:=false stopped the camera follower, so RViz lost the
view_focus frame it orbits. The follower now always runs and holds its
default focus when follow is false, and a launch test checks the frame.

teleop and joy_source use And/Or/Equals substitutions, so teleop:=True
works like IfCondition; the teleop test now uses True. The new nodes
receive use_sim_time, and the UDP receiver reads at most 100 packets per
poll so a flood cannot starve its other callbacks.
@coderabbitai

coderabbitai Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 59 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 0f3f4925-5d2e-4968-a3d2-cddf42c784d3

📥 Commits

Reviewing files that changed from the base of the PR and between b80d6c2 and 7973952.


📒 Files selected for processing (5)
  • BuildInstructions.md
  • README.md
  • waybionic_teleop/setup.py
  • waybionic_teleop/test/test_sdl_bridge.py
  • waybionic_teleop/waybionic_teleop/sdl_bridge.py

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

yassinsolim
yassinsolim previously approved these changes Oct 3, 2026

@yassinsolim yassinsolim left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Karis, this is a clean addition. The bridge sends the same packets as the XInput bridge, and requiring released controls on every connect and reconnect is a good safety touch.

What I checked:

  • The waybionic_teleop tests pass in the ROS image (67, including flake8 and pep257).
  • With the real Pygame 2.6.1, every CONTROLLER_AXIS_* and CONTROLLER_BUTTON_* constant and every controller call the bridge uses exists, and the bridge starts without a controller and stops cleanly on Ctrl+C.
  • It merges cleanly with #22 and with #27 (Cartesian teleop). On a DualSense, L1 is the axis lock and the D-pad tilts in the Cartesian group, so nothing else is needed.

Please add the Python version note (inline) before this merges, since the install step fails on Python 3.14.

Small follow-ups, not blocking:

  • joy_udp_receiver still says "run xinput_bridge on the host" when no packets arrive. It could name both bridges.
  • package.xml still describes the package as Xbox-only, while setup.py was updated.
  • The bridge prints nothing while it can't find a controller (inline).

Comment thread BuildInstructions.md
PowerShell window, install Pygame once and start the SDL bridge:

```powershell
python -m pip install pygame==2.6.1

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pygame 2.6.1 is still the latest release, and its Windows wheels stop at Python 3.13. On Python 3.14, pip tries to build it from source, which fails without the SDL development files. Please say "Python 3.13 or older" here. pygame-ce has newer wheels, but its _sdl2.controller module differs, so it isn't a drop-in replacement.

message = f'Controller connected: {name}'
break
if self.controller is None:
return *NEUTRAL, False, message

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When no controller is found, message is still None, so after the first line the console says nothing. The XInput bridge prints "No controller detected; connect an Xbox controller". A similar line here would tell people the bridge is running but can't see the controller, for example when it's paired over Bluetooth but not connected.

@yassinsolim
yassinsolim changed the base branch from feature/xbox-teleop to main October 11, 2026 02:27
@yassinsolim
yassinsolim dismissed their stale review October 11, 2026 02:27

The base branch was changed.

@yassinsolim

Copy link
Copy Markdown
Member

Karis, I merged main into this branch so it can go to main now that the stack has landed. Nothing was rewritten, so pull before you push again.

  • Main now sends rumble back to whichever Windows bridge sent the last controller packet, so the DualSense bridge reads those packets and rumbles the controller, using the same check as the Xbox bridge. Two new tests cover it.
  • BuildInstructions now says scripts\windows-demo.cmd opens the Xbox bridge, so close that window before starting the DualSense bridge.

The base is now main, and the full suite passes (1035 tests). There's no DualSense here, so please check each row of the controls table and rumble at a limit over USB and Bluetooth.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants