Test Rig & CI
polykybd-ctnd is a Raspberry Pi 4 hardware-in-the-loop (HIL) test and deploy station for the PolyKybd keyboard. It lets firmware changes be built in the cloud and then verified against real hardware automatically, with no physical button presses or replugging.
- Repository: github.com/thpoll83/polykybd-ctnd
What it does
The station:
- Flashes QMK firmware to both keyboard halves over USB — fully automated via GPIO-controlled BOOTSEL/RUN lines (a transistor low-side switch on each half’s reset pads) plus per-port USB power switching, so no physical access to a bootloader button is needed.
- Reads QMK’s HID console debug output from the connected keyboard.
- Sends Raw HID commands using the same protocol as PolyKybdHost (see the HID Protocol Reference), and asserts the responses.
- Serves a touch-friendly web UI on a 7” capacitive display (Flask + WebSocket), streaming logs and status in real time and exposing flash/test/update/runner controls.
- Acts as a GitHub Actions self-hosted runner (
runs-on: [self-hosted, polykybd-ctnd]), so CI jobs inqmk_firmwarecan build firmware in the cloud and then run HIL tests on the real keyboard.
How CI uses it
A workflow in qmk_firmware builds the firmware on cloud runners, then hands the resulting images to the station, which flashes both halves and runs the HIL test suite over Raw HID — identity/fresh-boot, language get/list (including the packed list), default layer, ACK/NACK error paths, overlay round-trips, and liveness checks. Each test reports as its own line in the job’s Step Summary, with pass/fail/skip/xfail markers and an annotation per failure.
Tests can carry gate markers (e.g. min_protocol, min_fw, xfail) so a check that depends on firmware not yet deployed is skipped or tolerated rather than failing the run, and un-skips itself automatically once a firmware that satisfies it is flashed.
Forcing master / slave on the rig
On a normal keyboard the USB half becomes the master based on the VBUS pin. On the rig both halves are cabled to the Pi, so both would read VBUS high and both detect as master. The HIL build resolves this at compile time, per side:
-e POLYKYBD_HIL=leftbuilds the master image (left half).-e POLYKYBD_HIL=rightbuilds the slave image (right half) — it callsusb_disconnect()so it does not enumerate as a second keyboard.
The role is fixed at build time and is not read from EE_HANDS. The rig must flash the *_hil_left image to the left half and the *_hil_right image to the right half; flashing a single master image to both sides makes both enumerate as master (a real bug the HIL suite catches).
Self-deploying
The rig keeps itself current: a systemd timer fetches the tracked branch every few minutes and, when it gains commits and the rig is idle, fast-forwards, reinstalls dependencies if needed, and restarts the station — never interrupting an in-progress flash or test run. An UPDATE badge in the touch UI can also trigger this on demand.