fix(USB): replace 500 ms host-quiet timer with msp.frameInProgress() predicate#9
Open
fix(USB): replace 500 ms host-quiet timer with msp.frameInProgress() predicate#9
Conversation
…predicate The shared host-quiet gate in Tx_main.cpp's loop() locked permanently under sustained host writes. Reproduced (April 2026) with continuous 25 Hz `MSP_ELRS_BACKPACK_SET_PTR` from the Android app: every received byte reset `last_host_rx_ms`, the 500 ms window never closed, and both the sniffer and wired-CRSF drainers were starved indefinitely. All device->host emit paths froze for as long as the stream lasted. The 500 ms timer was a holdover from pre-arduino-esp32 v2.0.17 days when simultaneous TX+RX on the C3 USB-CDC could stuck the bus. v2.0.17 (#5) fixed that, but the timer outlived its purpose and didn't capture the *actual* original intent: "don't write while a host MSP transaction is in progress." Replace with `msp.frameInProgress()` — true between the magic byte and the CRC, false otherwise. Captures the intent precisely, is rate-independent (1 Hz one-shots and 100 Hz streams behave the same), and self-correcting for new emit paths (any future device->host channel just checks the same predicate). Net effect on src/Tx_main.cpp is roughly -53 / +35 lines: the `USB_HOST_QUIET_GATE_ENABLED` macro, `last_host_rx_ms` static, sniffer's `USB_SNIFFER_QUIET_AFTER_RX_MS` constant, the `host_rx_active` computation block, and the wired CRSF's reuse of the same constant all go away. `host_in_bytes_total` is preserved (used by the OLED dashboard) under the underlying USB feature-flag. * `lib/MSP/msp.h` — add `bool MSP::frameInProgress() const` accessor. * `src/Tx_main.cpp` — drop timer gate, gate drainers on `msp.frameInProgress()`. * `targets/txbp_esp.ini` — drop `USB_SNIFFER_QUIET_AFTER_RX_MS=500`. * `CLAUDE.md` + `docs/esp32c3_usb_backpack.md` — reflect new gate semantics + record the prior-bug context. Companion Android PR snokvist/Waybeam-backpack-android#16 ships a host-side throttle (continuous PTR 25->10 Hz when wired CRSF is active) as an interim mitigation; that throttle should be removed once this firmware fix lands and a release tag is cut. Build clean: ESP32C3_TX_Backpack_via_USB env compiles, flash 50.6%, RAM 15.8% — net flash unchanged. HW-test plan (to be run on flash + verify): 1. Continuous 25 Hz PTR for 60 s with wired CRSF active. Host RX should stay steady at ~50 frames/sec wired (vs. zero pre-fix). 2. Promiscuous sniffer + wired CRSF + 25 Hz PTR concurrent for 60 s. No MSP desync host-side. 3. One-shot MSP request/response (e.g. `getVersion`) under all three concurrent loads. Round-trip completes within timeout. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The shared host-quiet gate in
Tx_main.cpp'sloop()locked permanently under sustained host writes — reproduced with continuous 25 HzMSP_ELRS_BACKPACK_SET_PTRfrom the Android app. Every received byte resetlast_host_rx_ms, the 500 ms window never closed, and both the sniffer and wired-CRSF drainers were starved for as long as the stream lasted. All device→host emit paths froze.This PR replaces the time-based gate with a parser-state predicate (
msp.frameInProgress()), which captures the original intent precisely and is rate-independent.Why this is the right long-term fix
Tx_main.cpp. RemovesUSB_HOST_QUIET_GATE_ENABLED,last_host_rx_ms,USB_SNIFFER_QUIET_AFTER_RX_MS, and thehost_rx_activeshared block. One predicate read at each drainer.msp.sendPacket(..., &Serial)fromProcessMSPPacketFromTXruns synchronously in the sameloop()iteration; drainer checks happen after that returns, with the parser already idle. No race.Diagnostic capture (Android side, pre-fix)
```
16:04:12 rx=58B/1chunks tx=375B/25calls maxWrite=9ms
16:04:13 rx=0B/0chunks tx=360B/24calls maxWrite=3ms
... (rx=0 sustained while tx steady at 25 Hz) ...
16:04:25 rx=0B/0chunks tx=330B/22calls maxWrite=4ms ← continuous off
16:04:26 parser: frames=9 wired=7 ← recovery
16:04:27 parser: frames=53 wired=49 ← back to baseline
```
Host write path was healthy throughout (3-9 ms
maxWrite); C3 simply stopped emitting on USB-IN.What changed
lib/MSP/msp.hbool frameInProgress() const.src/Tx_main.cppmsp.frameInProgress(). Preservehost_in_bytes_total(used by OLED).targets/txbp_esp.iniUSB_SNIFFER_QUIET_AFTER_RX_MS=500.CLAUDE.mddocs/esp32c3_usb_backpack.mdBuild:
ESP32C3_TX_Backpack_via_USBenv compiles. Flash 50.6 % (994 214 B), RAM 15.8 % (51 804 B) — net flash unchanged.Test plan
pio run -t upload --upload-port /dev/ttyACM0).getVersionround-trip under all three concurrent loads. Completes within the 2 s timeout.Companion / follow-up
backpack_pr8_quiet_gate_stall.md.🤖 Generated with Claude Code