These instructions cover firmware builds for macOS, Linux, and Windows, plus static hosting of the WebUSB app through GitHub Pages.
Hardware:
Software:
arduino-clibrew install arduino-cli
Use your package manager if it ships a recent arduino-cli, or download the release archive from Arduino and place arduino-cli on PATH.
Use choco and do a choco install arduino-cli. If you don't have that then install the official Arduino CLI zip or MSI, then ensure arduino-cli.exe is on PATH.
Run once on any platform:
arduino-cli config init
arduino-cli core update-index
arduino-cli core install adafruit:nrf52 --additional-urls https://adafruit.github.io/arduino-board-index/package_adafruit_index.json
From the repository root:
./gen_version.sh # optional: stamp the build with the current git hash + dirty flag (shown in the panel)
arduino-cli compile -b adafruit:nrf52:feather52840 --build-property "build.extra_flags=-DNRF52840_XXAA {build.flags.usb} -DCFG_TUD_HID=4" OpenPuck
This sketch requires CFG_TUD_HID=4 because Steam mode exposes four HID interfaces.
Build provenance. gen_version.sh writes OpenPuck/git_version.h with the current commit's short hash and a dirty flag (1 if the working tree has any tracked change or untracked file). The firmware bakes these in and reports them over WebUSB, so the panel's Build (git) field shows exactly which commit is flashed and whether it was a clean checkout — handy for confirming what's on a board. The header is git-ignored; if you skip the script the build still succeeds and the panel shows unknown. You can also inject the values directly instead of using the script, e.g. append -DOPK_GIT_HASH=\"$(git rev-parse --short=8 HEAD)\" -DOPK_GIT_DIRTY=0 to build.extra_flags.
Find the board port:
arduino-cli board list
Upload:
arduino-cli upload \
-b adafruit:nrf52:feather52840 \
-p /dev/ttyACM0 \
OpenPuck
Replace /dev/ttyACM0 with the actual port. On macOS it is usually /dev/cu.usbmodem*.
Find the COM port:
arduino-cli board list
Upload:
arduino-cli upload `
-b adafruit:nrf52:feather52840 `
-p COM5 `
OpenPuck
Replace COM5 with the actual board port.
Re-flashing firmware does not erase the board's internal LittleFS. The paired-controller bond (bonds.bin) and every saved setting (cfg.bin: USB mode, chord assignments, back-paddle map, mouse sensitivity) survive a fresh build and upload. To bring a board up in a truly clean state — a new unit, a hand-me-down with a stale bond, or a corrupted config — wipe the filesystem with one of:
Build flag (wipe on flash): compile with -DOPK_FACTORY_RESET=1 and upload. This build reformats all persistent storage on boot, so flashing it brings the board up on clean defaults — no console or panel needed:
arduino-cli compile -b adafruit:nrf52:feather52840 --build-property "build.extra_flags=-DNRF52840_XXAA {build.flags.usb} -DCFG_TUD_HID=4 -DOPK_FACTORY_RESET=1" OpenPuck
It wipes on every boot while the flag is compiled in, so this is a one-shot reset tool: flash the reset build once, let it boot (storage erased), then build and flash a normal firmware (without the flag) for regular use.
ERASE when prompted. The board reformats its filesystem and reboots to factory defaults. This works in every USB mode.ERASE-ALL (exact, all caps). Same effect: reformat + reboot.Both reformat the entire internal filesystem (cfg.bin + bonds.bin and anything else), so the action is irreversible and the controller must be re-paired afterwards (see §7).
Note on the serial method: puck (Steam/Lizard) mode drops the CDC console by default to free a USB endpoint for the wake-mouse interface, so the serial port may not be present in that mode. Either arm the one-shot debug CDC first (panel debug-CDC toggle / D console command, which keeps the console for the next boot), or just use the WebUSB Factory erase button, which is available in all modes.
0xB4 = 0x02 when connected.WebUSB requires a secure context. http://localhost qualifies.
cd docs
python3 -m http.server 8008
Open:
http://localhost:8008
cd docs
py -m http.server 8008
Open:
http://localhost:8008