|
|
4 hónapja | |
|---|---|---|
| .. | ||
| README.md | 4 hónapja | |
| frame.py | 4 hónapja | |
| input_source.py | 4 hónapja | |
| openctrl.py | 4 hónapja | |
| run.sh | 4 hónapja | |
| scpair.py | 4 hónapja | |
| setup.sh | 4 hónapja | |
| ui.py | 4 hónapja | |
Turn a Steam Deck into a Steam Controller 2. The Deck's built-in controls are grabbed (detached) and
streamed over USB-CDC to an OpenController nRF52840 (firmware in ../OpenController/),
which relays them over 2.4 GHz to a puck — a real Valve puck or an OpenPuck — so the
puck presents a Steam Controller 2 to whatever host it's plugged into.
Deck controls ──libusb detach + raw DeckState──▶ openctrl.py ──USB-CDC──▶ nRF (28DE:1302) ──RF──▶ puck ──USB──▶ host
The Deck controller is USB 28DE:1205; Steam reads its 64-byte "DeckState" report on interface 2.
You can't block Steam by reading /dev/hidraw3 (it's broadcast), so openctrl uses libusb
(pyusb) to detach the kernel driver from the controller's HID interfaces and read EP 0x83
directly — that genuinely takes the pad away from Steam (so Steam/QAM forward instead of opening
the Deck UI) and decodes the raw report correctly (trackpads, sticks, triggers, gyro, all buttons —
no more trackpad→stick/dpad bleed or Start/Select swap). Stopping forwarding reattaches it to Steam.
Modeled on ~/Work/joverlay. (--evdev is a lower-fidelity fallback that can't detach at the USB level.)
Calibrate the report layout for your unit: sudo python3 input_source.py live-dumps the raw
DeckState and shows which bytes/bits change as you press each control — edit DECK_BTN_MAP / the
analog offsets in input_source.py if anything's off.
| File | What |
|---|---|
openctrl.py |
Main app: opens the nRF CDC port, shows the touchscreen UI, forwards input. |
ui.py |
Fullscreen pygame UI — a tappable tile per paired puck. |
input_source.py |
Reads the Deck controls — libusb DeckSource (detach + raw decode) + evdev fallback + capture tool. |
frame.py |
The USB-CDC wire protocol (kept in lock-step with ../OpenController/deck_input.*). |
scpair.py |
Linux hidraw pairing tool (the on-Deck equivalent of pairtui). |
run.sh |
uv launcher (pyserial+pygame+pyusb) — add this as a non-Steam game. |
setup.sh |
Per-boot sudo: opens /dev/ttyACM* + the controller's usbfs node (read-only SteamOS). |
uv (no system install, read-only SteamOS friendly)run.sh launches the app via uv, which provisions its own Python and
the two deps (pyserial + pygame, both prebuilt wheels) into uv's cache — nothing touches the
read-only rootfs. Input is read with pure-stdlib evdev ioctls, so there's no evdev C-extension to
compile.
# one-time: install uv (lands in ~/.local/bin, no root)
curl -LsSf https://astral.sh/uv/install.sh | sh
./run.sh # fullscreen touchscreen UI (first run pulls deps)
./run.sh --debug # headless status print (bring-up)
./run.sh --hidraw # also forward gyro + trackpads (offsets unverified, see notes)
Add run.sh as a non-Steam game (Target = run.sh, Start In = this folder). In the shortcut's
Properties → Controller, set Disable Steam Input so Steam doesn't fight us for the built-in pad.
/dev/input/event* is already granted to the active-session user (logind ACL), so the evdev grab works
without any setup. The nRF CDC (/dev/ttyACM*) is not, so if you get a serial permission error, run
once per boot from Desktop Mode:
sudo ./setup.sh # chmod a+rw /dev/ttyACM* (+ /dev/hidraw* for --hidraw); not persistent
(We can't install a udev rule because /etc is read-only and you can't disable that.)
Pairing writes a shared key to both devices, so both must be on USB at the same time, on one computer. Two ways:
28DE:1302, a "Steam
Controller") and the puck (28DE:1304), then pair as usual — ../pairtui/pairtui.py already treats
1302 as a dockable controller and 1304 as the puck.On the Deck (Linux): plug both into the Deck and run
python3 scpair.py list # show puck slots + controllers
python3 scpair.py pair # pair into the first free slot
python3 scpair.py unpair --slot 0
pair mints a fresh key, writes the puck slot (0xA2) and the controller bond (esb/bond via
0xEE/0xEF), then reboots the controller into wireless (0x95). Move the puck to your host.
Tap a puck tile that shows ● available to detach the Deck controls and start forwarding. Tap
anywhere (or unplug the puck) to hand control back to the Deck. Esc/Q quits.
Validate the RF link first with ./run.sh --debug (headless): you want a line like
link=True fwd=False ch=18 [0 FXB99602xxxxx LIVE]. Keep the puck on USB (so it beacons) and the nRF
plugged in.
hid-steam hidraw report (--hidraw). The report offsets
in input_source.HidrawImu are a documented starting point and must be confirmed against a real
Deck capture — until then forwarding works with buttons/sticks/triggers (evdev) and zero IMU.EVIOCGRAB detaches cleanly in Desktop Mode. In Game Mode,
Steam Input owns the controller, so detach behaves differently; run from Desktop Mode for now.input_source.KEYMAP / EvdevSource._apply; adjust if your Deck maps
face buttons or back paddles differently.../OpenController/ctrl_link.cpp) adopts the session
address the puck advertises in its E1 host frame; this matches OpenPuck exactly and should match a
real puck (OpenPuck reproduces the real puck's E1). If a real puck won't connect, the E1 field
offsets are the first thing to check on a capture.