[PR #235] Add MODE_WAKE: smart-power-on wake personality (Lenovo Alt+P) #254

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

📋 Pull Request Information

Original PR: https://github.com/safijari/openpuck/pull/235
Author: @Saffsanity
Created: 8/16/2026
Status: 🔄 Open

Base: main ← Head: mode-wake-v2


📝 Commits (7)

  • a13fea1 Add MODE_WAKE: boot-keyboard personality for firmware smart power on
  • 643384f Surface MODE_WAKE in the panel, README, and CODE_MAP
  • b2822ba Document wake mode: setup guide for smart-power-on hosts
  • 36e1b44 MODE_WAKE: configurable hotkey + generic "Keyboard wake" naming
  • b36c1ac WAKE_AUTO_REARM: rearm-on-connect + instant arming on rearm boots
  • c8fa60b Review fixes: gesture-only rearm marker, wrap-safe window, off-exemption, hotkey validation
  • 7e703b5 WAKE_AUTO_REARM: mark timer/dead-bus rearms when nothing was on the air

📊 Changes

15 files changed (+1264 additions, -45 deletions)

View changed files

📝 CODE_MAP.md (+30 -2)
📝 OpenPuck/OpenPuck.ino (+37 -15)
📝 OpenPuck/config.cpp (+57 -7)
📝 OpenPuck/config.h (+36 -1)
📝 OpenPuck/controllers.cpp (+3 -0)
📝 OpenPuck/haptics.cpp (+152 -3)
➕ OpenPuck/mode_wake.cpp (+449 -0)
➕ OpenPuck/mode_wake.h (+198 -0)
📝 OpenPuck/rf_link.cpp (+39 -7)
📝 OpenPuck/rf_link.h (+5 -0)
📝 OpenPuck/serial_console.cpp (+1 -1)
📝 OpenPuck/webusb_config.cpp (+25 -3)
📝 README.md (+1 -0)
➕ docs/WAKE_MODE.md (+172 -0)
📝 docs/index.html (+59 -6)

📄 Description

Summary

Adds MODE_WAKE (mode 10): a bare boot-protocol keyboard personality that powers on a fully shut-down (S5) host through firmware "smart power on" (Lenovo: Alt+P from the always-on USB port). Arm it, shut down, and the next controller power-button press types the hotkey and boots the machine — by the time the OS is up, the puck is a normal puck again and the controller has reconnected by itself. One press, end to end. USB remote wakeup only covers S3 sleep; this covers real power-on.

Nothing changes for anyone who doesn't use it: mode 10 is only entered explicitly (panel / console x10 / assignable chord), WAKE_AUTO_REARM is compile-time off, the new suspend-policy logic is inert outside the wake flow, and the cfg layout is byte-identical (a dead reserved byte is repurposed; old cfg.bin files load unchanged).

User guide: docs/WAKE_MODE.md — BIOS setup, the designated port, LED diagnostics, auto-rearm trade-offs, adapting the hotkey to other vendors.

How it works

  • Why a separate personality: the S5 embedded controller runs a minimal USB host stack written against real keyboards, so it sees exactly one boot-protocol keyboard — no wake mouse, WebUSB, or CDC (reuses the clean-PS bareHid gating). Also endpoint budget: the puck composite already uses 6 of 7 IN endpoints.
  • Arming requires ~5 s of observed RF-link silence (the quiet clock runs only while the radio is on air), so switching modes with a controller still connected can never type into the live session. The wake gesture is the connect up-edge, latched 2 s until the EC's keyboard is ready.
  • The press is shaped like a human keystroke — modifier alone first, a long held chord restated while held, repeated — because the tested EC reads but ignores a minimal combined report. All timings are #defines (mode_wake.h), as is the hotkey for other vendors.
  • Post-fire handoff grace (persisted one-shot in Cfg.wakeHandoff — flash, because this board class's bootloader wipes .noinit RAM on reset): during POST the bus looks suspended or dead, and without the grace the standing suspend power-off kills the controller that just triggered the wake, while WAKE_AUTO_REARM would swap the puck for a keyboard mid-boot. The return boot also skips the USB mount wait and opens the RF connect cooldown, so the triggering controller relinks in under 0.5 s (measured 0.424 s) instead of giving up during POST.
  • WAKE_AUTO_REARM (opt-in) is sleep-aware: an OS entering S3 arms DEVICE_REMOTE_WAKEUP before suspending (the existing wake-from-S3 path depends on it); a shutdown never does. The decision is sampled once per down-episode from a mounted host, because the tested EC re-arms the feature itself during its S5 port takeover. Both observed takeover styles are handled — suspend-style (connection kept) and bus-reset-style (TinyUSB's suspended() can never read true again; a dead-bus clock measured from bus loss covers it).
  • Controller power-off works across both takeover styles (suspend-edge path + was-mounted-then-bus-dead path), with per-slot linked-at-send retry masks for the no-ACK off relay: each slot's own link drop is its delivery receipt, and a controller that connects after a send — the wake gesture, or one the user just re-powered — can never be targeted by a retry.

Validation

Hardware-validated end-to-end on a Lenovo ThinkCentre M90q Gen 6 (SteamOS), including the WAKE_AUTO_REARM=1 build, across a dozen power cycles: S5 wake (single press, controller self-reconnects), S3 sleep (puck stays a puck; controller wake-from-sleep keeps working), controller auto-power-off at shutdown, arming-race negative test, and the escape chord. Not yet exercised on a Windows host. Five multi-agent adversarial review rounds were run during development with a converging finding count; every confirmed finding is fixed in this diff. Full development history, hardware captures, and review records: #234.

  • clang-format-18 clean; trailing-comment lint adds zero new violations (a handful pre-exist upstream in touched files)
  • Both WAKE_AUTO_REARM variants compile clean (arduino-cli, adafruit:nrf52 1.7.0)
  • CI has not run yet (first-time contributor) — maintainer: the Format/Build workflow runs need approval

Open question for maintainer

Identity is 28DE:574B — Valve VID with an invented PID, following the ReversePuck 28DE:1302 precedent. Happy to change (pid.codes would be the principled alternative if you'd rather avoid another invented Valve PID).


🔄 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/235 **Author:** [@Saffsanity](https://github.com/Saffsanity) **Created:** 8/16/2026 **Status:** 🔄 Open **Base:** `main` ← **Head:** `mode-wake-v2` --- ### 📝 Commits (7) - [`a13fea1`](https://github.com/safijari/openpuck/commit/a13fea1c7623d42f0163a85b85bf55360e358229) Add MODE_WAKE: boot-keyboard personality for firmware smart power on - [`643384f`](https://github.com/safijari/openpuck/commit/643384f8ee56bc244d986a89b3c41f4cad856e67) Surface MODE_WAKE in the panel, README, and CODE_MAP - [`b2822ba`](https://github.com/safijari/openpuck/commit/b2822bab942e3ffdc4295d0eff64fb01ee60df31) Document wake mode: setup guide for smart-power-on hosts - [`36e1b44`](https://github.com/safijari/openpuck/commit/36e1b4492327f8b4d4934caacf830c1fe781e448) MODE_WAKE: configurable hotkey + generic "Keyboard wake" naming - [`b36c1ac`](https://github.com/safijari/openpuck/commit/b36c1ac0ddc848c6b5d38dcc5ace28ef719a23dd) WAKE_AUTO_REARM: rearm-on-connect + instant arming on rearm boots - [`c8fa60b`](https://github.com/safijari/openpuck/commit/c8fa60b4381aa4450a0a52ccfe6409c87500bfd7) Review fixes: gesture-only rearm marker, wrap-safe window, off-exemption, hotkey validation - [`7e703b5`](https://github.com/safijari/openpuck/commit/7e703b51df0bd2380e5ee51c475951c708c465ca) WAKE_AUTO_REARM: mark timer/dead-bus rearms when nothing was on the air ### 📊 Changes **15 files changed** (+1264 additions, -45 deletions) <details> <summary>View changed files</summary> 📝 `CODE_MAP.md` (+30 -2) 📝 `OpenPuck/OpenPuck.ino` (+37 -15) 📝 `OpenPuck/config.cpp` (+57 -7) 📝 `OpenPuck/config.h` (+36 -1) 📝 `OpenPuck/controllers.cpp` (+3 -0) 📝 `OpenPuck/haptics.cpp` (+152 -3) ➕ `OpenPuck/mode_wake.cpp` (+449 -0) ➕ `OpenPuck/mode_wake.h` (+198 -0) 📝 `OpenPuck/rf_link.cpp` (+39 -7) 📝 `OpenPuck/rf_link.h` (+5 -0) 📝 `OpenPuck/serial_console.cpp` (+1 -1) 📝 `OpenPuck/webusb_config.cpp` (+25 -3) 📝 `README.md` (+1 -0) ➕ `docs/WAKE_MODE.md` (+172 -0) 📝 `docs/index.html` (+59 -6) </details> ### 📄 Description ## Summary Adds **MODE_WAKE** (mode 10): a bare boot-protocol keyboard personality that powers on a **fully shut-down (S5)** host through firmware "smart power on" (Lenovo: Alt+P from the always-on USB port). Arm it, shut down, and the next controller power-button press types the hotkey and boots the machine — by the time the OS is up, the puck is a normal puck again and the controller has reconnected by itself. **One press, end to end.** USB remote wakeup only covers S3 sleep; this covers real power-on. **Nothing changes for anyone who doesn't use it**: mode 10 is only entered explicitly (panel / console `x10` / assignable chord), `WAKE_AUTO_REARM` is compile-time off, the new suspend-policy logic is inert outside the wake flow, and the cfg layout is byte-identical (a dead reserved byte is repurposed; old `cfg.bin` files load unchanged). **User guide:** [docs/WAKE_MODE.md](https://github.com/Saffsanity/openpuck/blob/mode-wake-v2/docs/WAKE_MODE.md) — BIOS setup, the designated port, LED diagnostics, auto-rearm trade-offs, adapting the hotkey to other vendors. ## How it works - **Why a separate personality:** the S5 embedded controller runs a minimal USB host stack written against real keyboards, so it sees exactly one boot-protocol keyboard — no wake mouse, WebUSB, or CDC (reuses the clean-PS `bareHid` gating). Also endpoint budget: the puck composite already uses 6 of 7 IN endpoints. - **Arming** requires ~5 s of *observed* RF-link silence (the quiet clock runs only while the radio is on air), so switching modes with a controller still connected can never type into the live session. The **wake gesture** is the connect up-edge, latched 2 s until the EC's keyboard is ready. - **The press is shaped like a human keystroke** — modifier alone first, a long held chord restated while held, repeated — because the tested EC reads but *ignores* a minimal combined report. All timings are `#define`s (`mode_wake.h`), as is the hotkey for other vendors. - **Post-fire handoff grace** (persisted one-shot in `Cfg.wakeHandoff` — flash, because this board class's bootloader wipes `.noinit` RAM on reset): during POST the bus looks suspended or dead, and without the grace the standing suspend power-off kills the controller that just triggered the wake, while `WAKE_AUTO_REARM` would swap the puck for a keyboard mid-boot. The return boot also skips the USB mount wait and opens the RF connect cooldown, so the triggering controller relinks in **under 0.5 s** (measured 0.424 s) instead of giving up during POST. - **`WAKE_AUTO_REARM` (opt-in) is sleep-aware:** an OS entering S3 arms `DEVICE_REMOTE_WAKEUP` before suspending (the existing wake-from-S3 path depends on it); a shutdown never does. The decision is sampled once per down-episode *from a mounted host*, because the tested EC re-arms the feature itself during its S5 port takeover. Both observed takeover styles are handled — suspend-style (connection kept) and bus-reset-style (TinyUSB's `suspended()` can never read true again; a dead-bus clock measured from bus loss covers it). - **Controller power-off** works across both takeover styles (suspend-edge path + was-mounted-then-bus-dead path), with per-slot linked-at-send retry masks for the no-ACK off relay: each slot's own link drop is its delivery receipt, and a controller that connects *after* a send — the wake gesture, or one the user just re-powered — can never be targeted by a retry. ## Validation Hardware-validated end-to-end on a **Lenovo ThinkCentre M90q Gen 6 (SteamOS)**, including the `WAKE_AUTO_REARM=1` build, across a dozen power cycles: S5 wake (single press, controller self-reconnects), S3 sleep (puck stays a puck; controller wake-from-sleep keeps working), controller auto-power-off at shutdown, arming-race negative test, and the escape chord. Not yet exercised on a Windows host. Five multi-agent adversarial review rounds were run during development with a converging finding count; every confirmed finding is fixed in this diff. Full development history, hardware captures, and review records: #234. - [x] clang-format-18 clean; trailing-comment lint adds zero new violations (a handful pre-exist upstream in touched files) - [x] Both `WAKE_AUTO_REARM` variants compile clean (arduino-cli, adafruit:nrf52 1.7.0) - [ ] CI has not run yet (first-time contributor) — maintainer: the Format/Build workflow runs need approval ## Open question for maintainer Identity is `28DE:574B` — Valve VID with an invented PID, following the ReversePuck `28DE:1302` precedent. Happy to change (pid.codes would be the principled alternative if you'd rather avoid another invented Valve PID). --- <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#254
No description provided.