Compare commits

...
Sign in to create a new pull request.

4 commits

Author SHA1 Message Date
ae65251cb4 Fix controllers 2026-01-10 23:19:32 -07:00
b04a6d29ca
Add license 2025-12-14 14:47:49 -07:00
e41a1746ba
Add references 2025-12-14 14:08:03 -07:00
22cfe62c41
Add gyro branch ref 2025-12-14 14:04:28 -07:00
3 changed files with 39 additions and 14 deletions

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Joey Yakimowich-Payne
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -15,7 +15,7 @@ Raspberry Pi Pico firmware that emulates a Switch Pro controller over USB and a
5. Connect the Pico to the Switch (dock USB-A or USB-C OTG); the Switch should see it as a wired Pro Controller.
## Planned features
- IMU support for motion controls (gyro/accelerometer passthrough for controllers that support it to the Switch).
- IMU support for motion controls (gyro/accelerometer passthrough for controllers that support it to the Switch) (WIP branch on `gyrov3`).
## Limitations
- No motion controls/IMU passthrough yet (planned).
@ -228,6 +228,12 @@ with SwitchUARTClient("/dev/cu.usbserial-0001") as client:
### Linux tips
- You may need udev permissions for `/dev/ttyUSB*`/`/dev/ttyACM*` (add user to `dialout`/`uucp` or use `udev` rules).
## References
- GP2040-CE (controller firmware ecosystem): https://github.com/OpenStickCommunity/GP2040-CE
- nxbt (Switch controller research/tools): https://github.com/Brikwerk/nxbt
- Nintendo Switch Reverse Engineering notes: https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
- `hid-nintendo` driver reference: https://github.com/DanielOgorchock/linux/blob/ogorchock/drivers/hid/hid-nintendo.c
## Troubleshooting
- **No input on Switch**: verify UART wiring (Pico GPIO4/5), baud matches both sides, Pico flashed with current firmware, and `Pro Controller Wired Communication` is enabled on the Switch.
- **Constant buzzing rumble**: the bridge filters small rumble payloads; ensure baud isnt dropping bytes. Try lowering rumble scale in `switch_pico_bridge.controller_uart_bridge` if needed.

View file

@ -214,6 +214,7 @@ class ControllerContext:
last_rumble_energy: float = 0.0
rumble_active: bool = False
axis_offsets: Dict[int, int] = field(default_factory=dict)
swap_abxy: bool = False
def capture_stick_offsets(controller: sdl2.SDL_GameController) -> Dict[int, int]:
@ -395,12 +396,9 @@ def toggle_abxy_for_context(ctx: ControllerContext, config: BridgeConfig, consol
if config.swap_abxy_global:
console.print("[yellow]Global --swap-abxy is enabled; disable it to use per-controller toggles.[/yellow]")
return
swapped = ctx.stable_id in config.swap_abxy_ids
swapped = ctx.swap_abxy
action = "standard" if swapped else "swapped"
if swapped:
config.swap_abxy_ids.discard(ctx.stable_id)
else:
config.swap_abxy_ids.add(ctx.stable_id)
ctx.swap_abxy = not swapped
console.print(
f"[cyan]Controller {ctx.controller_index} ({controller_display_name(ctx)}, inst {ctx.instance_id}) now using {action} ABXY layout.[/cyan]"
)
@ -425,7 +423,7 @@ def prompt_swap_abxy_controller(
table.add_column("GUID")
table.add_column("Layout", justify="center")
for idx, ctx in enumerate(controllers):
swapped = config.swap_abxy_global or (ctx.stable_id in config.swap_abxy_ids)
swapped = config.swap_abxy_global or ctx.swap_abxy
state = "Swapped" if swapped else "Standard"
if config.swap_abxy_global:
state += " (global)"
@ -988,8 +986,7 @@ def open_initial_contexts(
console.print(f"[red]Failed to open controller {index}: {exc}[/red]")
continue
stable_id = guid
if index in config.swap_abxy_indices:
config.swap_abxy_ids.add(stable_id)
should_swap = index in config.swap_abxy_indices or stable_id in config.swap_abxy_ids
uart = open_uart_or_warn(port, args.baud, console) if port else None
if uart:
uarts.append(uart)
@ -1007,6 +1004,7 @@ def open_initial_contexts(
stable_id=stable_id,
port=port,
uart=uart,
swap_abxy=should_swap,
)
if config.zero_sticks:
zero_context_sticks(ctx, console)
@ -1058,7 +1056,7 @@ def handle_button_event(
return
current_button_map = (
config.button_map_swapped
if (config.swap_abxy_global or ctx.stable_id in config.swap_abxy_ids)
if (config.swap_abxy_global or ctx.swap_abxy)
else config.button_map_default
)
button = event.cbutton.button
@ -1103,9 +1101,8 @@ def handle_device_added(
console.print(f"[red]Hotplug open failed for controller {idx}: {exc}[/red]")
return
stable_id = guid
# Promote any index-based swap flags to stable IDs on first sight.
if idx in config.swap_abxy_indices:
config.swap_abxy_ids.add(stable_id)
# Determine if this controller should have swapped ABXY based on index or GUID
should_swap = idx in config.swap_abxy_indices or stable_id in config.swap_abxy_ids
uart = open_uart_or_warn(port, args.baud, console) if port else None
if uart:
uarts.append(uart)
@ -1123,6 +1120,7 @@ def handle_device_added(
stable_id=stable_id,
port=port,
uart=uart,
swap_abxy=should_swap,
)
if config.zero_sticks:
zero_context_sticks(ctx, console)
@ -1163,7 +1161,7 @@ def service_contexts(
for ctx in list(contexts.values()):
current_button_map = (
config.button_map_swapped
if (config.swap_abxy_global or ctx.stable_id in config.swap_abxy_ids)
if (config.swap_abxy_global or ctx.swap_abxy)
else config.button_map_default
)
poll_controller_buttons(ctx, current_button_map)