[PR #309] Add on-controller paddle profiles and paddle-assign mode #278

Open
opened 2026-10-22 05:08:37 +00:00 by roman · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/safijari/openpuck/pull/309
Author: @cadenabelcannon-ctrl
Created: 10/9/2026
Status: 🔄 Open

Base: main ← Head: paddle-profiles


📝 Commits (1)

  • 2f078e0 Add on-controller paddle profiles and paddle-assign mode

📊 Changes

7 files changed (+497 additions, -2 deletions)

View changed files

📝 OpenPuck/OpenPuck.ino (+4 -0)
📝 OpenPuck/config.cpp (+3 -0)
📝 OpenPuck/haptics.cpp (+15 -2)
📝 OpenPuck/haptics.h (+3 -0)
➕ OpenPuck/paddle_profiles.cpp (+434 -0)
➕ OpenPuck/paddle_profiles.h (+32 -0)
📝 OpenPuck/rf_link.cpp (+6 -0)

📄 Description

Adds back-paddle profiles and a paddle-assign mode that work entirely from the controller, so remapping the paddles on a console no longer needs the board plugged into a computer for the WebUSB panel.

What it does

In the emulated modes (Xbox, Switch, DS4, DS5, and their variants):

Gesture Action
back-4 + RB Next paddle profile (buzzes 1-4 times = profile number)
back-4 + LB Previous paddle profile
back-4 + Start Enter / leave paddle-assign mode (one long buzz)

Paddle-assign mode: no input reaches the host. Tap a paddle (1 buzz), then tap the button it should act as (2 buzzes = saved). Tapping the same paddle twice turns it off (3 buzzes). The mode closes itself after 30 s of no input. Any button can be a target, including LT/RT. QAM maps to Capture on Switch and Touchpad Click on PlayStation.

Steam, Lizard, DirectInput and SInput are untouched: the gesture code returns early when g_etype == ET_NONE.

How it's stored

  • There are 4 profiles per emulated type. The active profile stays in g_type[et].back (cfg.bin), so every mode builder and the WebUSB panel work unchanged. The panel simply edits whichever profile is active.
  • The other profiles live in a new /paddles.bin, which leaves the Cfg layout alone. Older firmware still reads cfg.bin fine.
  • First run seeds profile 1 with the current layout. Profile 2 gets face buttons, profile 3 gets the D-pad, and profile 4 turns the paddles off.
  • Flash writes are deferred 3 s after the last change, so rapid cycling costs one write. saveCfg() also flushes /paddles.bin, so a mode switch or panel save inside that window still keeps the two files in step.

Changes to existing code

  • rf_link.cpp: one call to paddleProfilesOnInput() just before the existing back-4 chord masking. While back-4 is held, it masks LB/RB/Start from g_in and the raw report. In assign mode it blanks all buttons and triggers.
  • haptics.cpp: splits the tail of hapticSteamRumble() into a static rumbleRelay(), and adds hapticFeedback(). This buzzes at a fixed strength, bypassing rumble style/strength and the per-type rumble toggle, so the confirmation is felt even with game rumble off. hapticSteamRumble() behavior is unchanged.
  • config.cpp: saveCfg() calls paddleProfilesSaveIfDirty().
  • OpenPuck.ino: loads profiles after loadCfg() and calls paddleProfilesTask() from loop().

The existing back-4 + face/D-pad mode chords are unaffected.

Testing

  • Hardware: Pro Micro nRF52840 in Switch Pro + gyro mode on a Switch 2, flashed through the panel's local-file firmware update. Profile switching (buzz counts), assign mode and the resulting mappings were verified on the console's controller test screen.
  • Not yet tested on hardware: Xbox and PlayStation modes, and more than one controller on the puck.
  • Host-side simulation: the profile logic was also exercised on the host against stubbed millis/LittleFS/haptics. This covered gesture debounce, wraparound, assign-mode taps (including trigger targets and the same-paddle-twice off), the timeout, persistence across a simulated reboot, and panel edits surviving a profile switch. It also checked that Steam mode is untouched and that nothing fires while the host is suspended.
  • make check passes, and make build adds about 3 KB of flash and 128 B of RAM.

Possible follow-ups

The panel doesn't show which profile is active or let you edit the non-active profiles yet. Happy to add that if you'd like the feature.

Like the rest of the project, this was written with an LLM (Claude).

🤖 Generated with Claude Code

https://claude.ai/code/session_01C5Vr8e7sDDjZ2byXQ7fQMY


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/safijari/openpuck/pull/309 **Author:** [@cadenabelcannon-ctrl](https://github.com/cadenabelcannon-ctrl) **Created:** 10/9/2026 **Status:** 🔄 Open **Base:** `main` ← **Head:** `paddle-profiles` --- ### 📝 Commits (1) - [`2f078e0`](https://github.com/safijari/openpuck/commit/2f078e01954a95ae3da4184e9772bbc43166ffa5) Add on-controller paddle profiles and paddle-assign mode ### 📊 Changes **7 files changed** (+497 additions, -2 deletions) <details> <summary>View changed files</summary> 📝 `OpenPuck/OpenPuck.ino` (+4 -0) 📝 `OpenPuck/config.cpp` (+3 -0) 📝 `OpenPuck/haptics.cpp` (+15 -2) 📝 `OpenPuck/haptics.h` (+3 -0) ➕ `OpenPuck/paddle_profiles.cpp` (+434 -0) ➕ `OpenPuck/paddle_profiles.h` (+32 -0) 📝 `OpenPuck/rf_link.cpp` (+6 -0) </details> ### 📄 Description Adds back-paddle profiles and a paddle-assign mode that work entirely from the controller, so remapping the paddles on a console no longer needs the board plugged into a computer for the WebUSB panel. ## What it does In the emulated modes (Xbox, Switch, DS4, DS5, and their variants): | Gesture | Action | |---|---| | back-4 + RB | Next paddle profile (buzzes 1-4 times = profile number) | | back-4 + LB | Previous paddle profile | | back-4 + Start | Enter / leave paddle-assign mode (one long buzz) | **Paddle-assign mode:** no input reaches the host. Tap a paddle (1 buzz), then tap the button it should act as (2 buzzes = saved). Tapping the same paddle twice turns it off (3 buzzes). The mode closes itself after 30 s of no input. Any button can be a target, including LT/RT. QAM maps to Capture on Switch and Touchpad Click on PlayStation. Steam, Lizard, DirectInput and SInput are untouched: the gesture code returns early when `g_etype == ET_NONE`. ## How it's stored - There are 4 profiles per emulated type. The **active** profile stays in `g_type[et].back` (`cfg.bin`), so every mode builder and the WebUSB panel work unchanged. The panel simply edits whichever profile is active. - The other profiles live in a new `/paddles.bin`, which leaves the `Cfg` layout alone. Older firmware still reads `cfg.bin` fine. - First run seeds profile 1 with the current layout. Profile 2 gets face buttons, profile 3 gets the D-pad, and profile 4 turns the paddles off. - Flash writes are deferred 3 s after the last change, so rapid cycling costs one write. `saveCfg()` also flushes `/paddles.bin`, so a mode switch or panel save inside that window still keeps the two files in step. ## Changes to existing code - `rf_link.cpp`: one call to `paddleProfilesOnInput()` just before the existing back-4 chord masking. While back-4 is held, it masks LB/RB/Start from `g_in` and the raw report. In assign mode it blanks all buttons and triggers. - `haptics.cpp`: splits the tail of `hapticSteamRumble()` into a static `rumbleRelay()`, and adds `hapticFeedback()`. This buzzes at a fixed strength, bypassing rumble style/strength and the per-type rumble toggle, so the confirmation is felt even with game rumble off. `hapticSteamRumble()` behavior is unchanged. - `config.cpp`: `saveCfg()` calls `paddleProfilesSaveIfDirty()`. - `OpenPuck.ino`: loads profiles after `loadCfg()` and calls `paddleProfilesTask()` from `loop()`. The existing back-4 + face/D-pad mode chords are unaffected. ## Testing - **Hardware:** Pro Micro nRF52840 in Switch Pro + gyro mode on a Switch 2, flashed through the panel's local-file firmware update. Profile switching (buzz counts), assign mode and the resulting mappings were verified on the console's controller test screen. - **Not yet tested on hardware:** Xbox and PlayStation modes, and more than one controller on the puck. - **Host-side simulation:** the profile logic was also exercised on the host against stubbed `millis`/LittleFS/haptics. This covered gesture debounce, wraparound, assign-mode taps (including trigger targets and the same-paddle-twice off), the timeout, persistence across a simulated reboot, and panel edits surviving a profile switch. It also checked that Steam mode is untouched and that nothing fires while the host is suspended. - `make check` passes, and `make build` adds about 3 KB of flash and 128 B of RAM. ## Possible follow-ups The panel doesn't show which profile is active or let you edit the non-active profiles yet. Happy to add that if you'd like the feature. Like the rest of the project, this was written with an LLM (Claude). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01C5Vr8e7sDDjZ2byXQ7fQMY --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
Sign in to join this conversation.
No labels
pull-request
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
safijari/openpuck#278
No description provided.