Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- Fixing long wireless macros that leave a key repeating
- Summary
- A Keychron K13 Max RGB ANSI running a custom Keychron wireless-QMK build had a repeatable problem: a long Launcher/VIA macro worked over USB but stopped partway through over Bluetooth and 2.4 GHz, leaving one key repeating. Pressing a physical key stopped the repetition.
- The targeted fix was cooperative backpressure at the wireless report queue: when the queue is full, service the radio and queued reports until space becomes available, then enqueue the pending report in order. Do not immediately discard it.
- After installing the candidate, the owner reports repeated successful runs over both Bluetooth and 2.4 GHz. This is useful real-device validation on one keyboard, not proof of compatibility with every Keychron or of lossless operation under every radio fault. 2.4 GHz receiver mode is not Wi-Fi.
- What was going wrong
- Typing a character involves HID state reports, not just one text byte. An ordinary character typically needs a key-down and a key-up report. Capitals and punctuation can add Shift transitions.
- The inspected fork uses a 256-entry ring buffer with 255 usable slots. report_buffer_enqueue() returns false when full. The buffered keyboard, NKRO and consumer-report producers do not handle that failure. A fast macro can therefore silently lose reports.
- Losing a release can leave the computer believing the last key is still held. The computer's normal key-repeat behavior then repeats it. A subsequent physical key press/release supplies a fresh state and can clear the held key.
- The owner's original compact macro serialized to 317 bytes but generated 326 keyboard reports. In an actual-source native simulation with modeled timing and successful radio ACKs, the old firmware dropped 44 reports, reproduced the reported output prefix ending Department:\n iv, and left V held. The candidate admitted all 326 reports in order and ended with every key released.
- 326 reports is not a universal failure threshold. Reports can drain while a macro runs; timing, modifiers, radio interval, ACKs and existing queue occupancy determine whether it fills.
- Why simply adding larger pauses did not reliably address it
- In this revision, an encoded Launcher delay such as {20} executes a blocking wait_ms(20), followed by a cooperative service callback. It does not continuously service the wireless queue throughout the wait. More elapsed time is therefore not equivalent to more queue-draining work.
- Adding delays between every character can reduce producer pressure, but expands the stored macro and can exceed available macro storage. Splitting into immediately chained macros also does not inherently drain the same shared queue. Enlarging the report queue postpones saturation and consumes RAM; it does not establish a sound admission policy.
- The implemented change
- This source-specific implementation changes four files:
- keyboards/keychron/common/wireless/report_buffer.c
- keyboards/keychron/common/wireless/wireless.c
- keyboards/keychron/common/wireless/wireless.h
- The target keymap's config.h
- A target-only define, KEYCHRON_WIRELESS_REPORT_BACKPRESSURE, enables the new behavior. Other keymaps retain their existing path.
- Conceptually:
- if queue has space:
- enqueue normally
- else:
- while queue is full:
- stop if the admission deadline is reached
- stop if transport/link/battery state is no longer eligible
- service the radio's polling/ACK path and existing report queue
- recompute queue indices
- briefly yield if it is still full
- enqueue the pending report in order
- The service helper has a reentrancy guard. It samples the existing physical transport policy, checks that effective transport has not changed, checks wireless eligibility and critical battery state, polls the radio, rechecks eligibility, and services the existing report queue.
- It deliberately does not recursively run matrix scanning, record processing, the full main loop, or wireless connection-event dispatch. In the inspected LKBT51 driver, ACK handling happens directly during radio polling, whereas connection events are queued for later main-loop handling. This distinction must be checked before porting.
- The candidate uses a 1000 ms per-admission deadline, checked between service iterations. This is not a strict wall-clock cap: an existing blocking radio-service iteration can overshoot it. It is also not a whole-macro timeout. Non-full admission adds no servicing loop or wait.
- No macro content, storage size, queue capacity, key assignments or RGB behavior was changed by this narrow fix.
- Porting to the same or another keyboard
- Share the principle and narrow source patch, not a supposedly universal firmware binary. The delivered K13 image also contains this owner's earlier transport/RGB customizations. It is not a generic stock update.
- The inspected source pin is c5d998442710b38650903937249144a0cd728ebe, with earlier custom modifications. The target is keychron/k13_max/ansi/rgb:keychron. The narrow patch is source/installed-to-custom.patch in the accompanying review archive. That patch assumes the installed customized baseline; even the same model on another revision may require adaptation.
- Before implementing elsewhere:
- Identify the exact board, layout, lighting variant, MCU, fork revision and wireless driver. Preserve the working firmware and official recovery path.
- Confirm the symptom in a plain editor: USB succeeds, wireless fails, and a release is missing. Other causes include an unpaired macro key-down, radio FIFO retry exhaustion, or host/application behavior.
- Trace macro generation, queue-full behavior, enqueue callers, retained retry ownership and ACK processing. Some implementations already provide backpressure or asynchronous macro playback.
- Determine which radio/queue tasks can safely run inside a report producer. Do not copy this helper if the other driver requires unsafe event dispatch or has different ACK ownership.
- Add target-scoped admission backpressure with explicit disconnect, transport-change, battery, timeout and reentrancy policies. Preserve report ordering and the outgoing report copy.
- Create failing actual-source tests first. Cover full-queue admission, final releases, Shift/Enter, NKRO/consumer paths if supported, and stalled/disconnected links. Verify unaffected stock paths too.
- Build the unmodified baseline and modified image for the exact board. Check flash/RAM limits, feature retention and image packaging. Review independently before device testing.
- Back up Keymap and Macro separately, plus custom lighting settings. On this fork, VIA validates storage against the firmware build date; a changed date can reset keymaps and erase macros on first boot. Do not assume flashing preserves them or blindly backdate firmware.
- What this does NOT fix or prove
- Successful queue admission is not radio delivery or a host release acknowledgement.
- Sustained radio FIFO_FULL can exhaust the existing finite retries and still lose a release. This separate failure was reproduced with injected faults and was not changed.
- Admission timeout still returns false, which existing void producers do not handle. An unavailable/stalled link can still lose reports, and repeated full admissions can delay playback significantly.
- Connection events are deferred; cached connection state can be stale during synchronous playback. Transport equality is not host-generation fencing.
- Macros remain synchronous. Newly pressed Fn shortcuts are not guaranteed to interrupt playback or cancel it.
- One user's successful tests do not certify all radio conditions, wake/sleep paths, other boards or firmware revisions.
- Practical acceptance tests
- Use a plain editor and harmless text. Keep terminals, password prompts, chat send boxes and command macros out of the test. Preserve exports before changing any test slots.
- Healthy-link correctness
- Disconnect USB for the wireless tests, and confirm the active transport. Test 2.4 GHz and Bluetooth independently; use actual Cable mode as a USB control.
- Run the original previously failing macro 20 times per wireless transport. Let each finish, then wait five seconds without touching the board. Any extra output or repeating key is a failure.
- Compare the complete output with the expected text, including spaces, case, punctuation and blank lines. Absence of a stuck key alone is not enough: missing characters or Shift errors also fail.
- After each run, type abc123 manually. It should be lowercase and normal, with no stuck modifier or missing response.
- Producer-pressure tests
- In a spare macro slot, test compact lowercase strings at 127, 128, 256 characters and the largest size that actually fits. Avoid expanded per-character delays.
- Test repeated capitals and punctuation such as AaZz:!?() to exercise Shift transitions. Test paired Enter events and multiline output too.
- Add distinctive START/END markers where storage permits. The complete ending must arrive; wait afterward to detect a held final key.
- Test consecutive runs, then several activator presses during playback. Treat this separately: synchronous processing may delay or miss those physical presses, and the fix does not promise a queued run for every press.
- Lifecycle and degraded-link tests
- Repeat the original macro after keyboard sleep/wake, a power cycle and a host reconnect. Confirm remaps/macros/RGB remain correct.
- Test transport changes BETWEEN completed macros first. Optional mid-macro slider/disconnect tests belong in a harmless editor and are fault-recovery tests, not a promise of seamless macro transfer.
- Increase distance or disconnect the host deliberately in a controlled test. An interrupted macro need not complete. Record whether the keyboard recovers after reconnect, whether a key repeats, and how long responsiveness takes. Do not interpret these fault cases as proof that the narrow overflow fix guarantees recovery.
- Test at a normal lower battery level without deliberately deep-discharging the battery. Check ordinary typing, mouse/consumer controls if used, RGB, pairing and Launcher configuration for regressions.
- Record transport, macro, repetitions, missing/repeating characters, last key, completion time and recovery steps. A strong practical pass is exact output, no held keys/modifiers, normal typing afterward, and retained settings across repeated runs and lifecycle changes. It is not a mathematical guarantee against every RF failure.
Add Comment
Please, Sign In to add comment