Skip to content

Resync HID command stream after a stale report (fixes #228) - #436

Open
OmleNessumsa wants to merge 1 commit into
the-via:mainfrom
OmleNessumsa:fix/hid-resync
Open

OmleNessumsa wants to merge 1 commit into
the-via:mainfrom
OmleNessumsa:fix/hid-resync

Conversation

@OmleNessumsa

Copy link
Copy Markdown

Problem

KeyboardAPI._hidCommand writes one report, reads exactly one report and throws Receiving incorrect response for command when the echo does not match. The mismatched report is never drained. As soon as one stray reply enters the stream (a second tab or client holding the same device, a concurrent command), every following response is shifted by one: the reply to command N is read as the answer to command N+1. Because the next command is already on the wire before the late reply lands, fastForwardGlobalBuffer cannot discard it, and the stream stays shifted until the page is reloaded.

This is the pattern in #228 (Keebio Quefrency) and it reproduces identically on a Keychron Q3 Pro (0x3434:0x0630, protocol 11). Sample from the error log, every entry is the previous command's reply:

DYNAMIC_KEYMAP_MACRO_GET_BUFFER_SIZE  cmd 13     -> resp 1 0 11 ...   (GET_PROTOCOL_VERSION)
GET_KEYBOARD_VALUE                    cmd 2 4    -> resp 13 4 106 ... (macro buffer size)
CUSTOM_MENU_GET_VALUE                 cmd 8 3 1  -> resp 2 4 0 ...

367 of 386 logged errors in a 16 s session followed this exact off-by-one, across 8 automatic retries.

Fix

  • _hidCommand discards a reply whose echo belongs to another command and reads again, bounded by RESYNC_MAX_STALE_READS (4) and a 250 ms per-read timeout.
  • A reply starting with 0xFF (QMK id_unhandled) is still treated as the real answer to this command and fails immediately, so unsupported commands cannot stall the queue.
  • The WebHID shim gains readWithTimeout(ms), which removes its own waiter on timeout so a report that never arrives cannot wedge later reads.

No behaviour change on a healthy stream: the first read matches and the loop body never runs.

Verification

  • Keyboard and browser were ruled out first: the same board answers every command with exactly one report in 3–8 ms both over hidapi and over WebHID (40/40 rapid commands aligned).
  • With this patch the board loads its full 92-key keymap and 4 layers with an empty error log; a remap through the UI was read back from the board over raw HID.
  • tsc --noEmit passes. Prettier applied to the added code only (the shim file was not prettier-clean before this change, its existing formatting is left untouched).

Fixes #228

🤖 Generated with Claude Code

https://claude.ai/code/session_01VYbTK39cVBDbhebBZ5UQWg

KeyboardAPI._hidCommand wrote one report, read exactly one report and threw
"Receiving incorrect response for command" on an echo mismatch, without
ever draining the mismatched report. A single stray reply (a second tab or
client on the same device, a concurrent command) therefore shifted every
following response by one, and because the next command was already sent
before the late reply arrived, fastForwardGlobalBuffer could not discard
it. The stream stayed shifted until the page was reloaded (the-via#228).

_hidCommand now discards replies whose echo belongs to another command and
reads again, bounded by RESYNC_MAX_STALE_READS (4) and a 250 ms per-read
timeout. A reply starting with 0xFF (QMK id_unhandled) is still treated as
the real answer and fails immediately, so unsupported commands cannot
stall the queue. The WebHID shim gains readWithTimeout(), which removes
its own waiter on timeout.

Verified on a Keychron Q3 Pro (0x3434:0x0630, protocol 11) on macOS 26.5
with Chrome 153: the previously permanent off-by-one no longer occurs and
the full keymap loads with an empty error log.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VYbTK39cVBDbhebBZ5UQWg

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error log flodded with - 'Receiving incorrect response for command' Errors

1 participant