Skip to content

feat(USB): runtime wired CRSF disable + ESP-NOW TX diag counters#10

Open
snokvist wants to merge 2 commits intofeature/parser-state-quiet-gatefrom
feature/wired-crsf-runtime-ctrl
Open

feat(USB): runtime wired CRSF disable + ESP-NOW TX diag counters#10
snokvist wants to merge 2 commits intofeature/parser-state-quiet-gatefrom
feature/wired-crsf-runtime-ctrl

Conversation

@snokvist
Copy link
Copy Markdown
Owner

Summary

Adds two diagnostic / deployment knobs that fell out of an investigation into ESP-NOW silently failing under load. The investigation itself did not find a firmware bug — root cause turned out to be RF interference between an ELRS receiver placed too close to the C3 SuperMini's WiFi antenna (espnow_tx_ok climbed cleanly while the bound peer received nothing). Even though we don't need either knob to fix the RFI issue, both are independently valuable enough to ship:

  • MSP_WAYBEAM_WIRED_CRSF_CTRL (0x0046) — runtime enable/disable for the wired CRSF receiver path, persisted to NVS (waybeam_bp / wired_en). Empty payload = query; [1]/[0] = set; reply mirrors current state. Useful as a deployment knob: builds without a wired CRSF receiver shouldn't pay the UART1 polling cost, and there's no way to opt out at runtime today.

  • MSP_WAYBEAM_DIAG (0x0047) — read-only firmware counters: espnow_tx_ok (u32), espnow_tx_fail (u32), espnow_tx_last_err (i32), wired_crsf_enabled (u8). Counters bumped from the previously-discarded esp_now_send return code. Useful as production observability: separates queue-acceptance from radio-level failure, which is exactly the diagnostic that ruled out queue starvation in this investigation.

Behaviour notes

The disable toggle is a hard hardware off-switch:

  • WiredCrsfSetEnabled(false) calls gWiredUart.end() — pins released, RX ISR detached.
  • WiredCrsfInit() honours the persisted disable state and never calls gWiredUart.begin() at boot when disabled.
  • When disabled, no UART1 polling, no 0x0044 emit, no Serial.write from this path. Software disable is equivalent to physically pulling the GPIO 20 wire (verified empirically).

Companion: Android tooling

  • tools/backpack_cli.py wired on|off (or no-arg query) and tools/backpack_cli.py diag in snokvist/Waybeam-backpack-android — quick CLI access from a host plugged into the C3.
  • The Android app's Settings tab has a Diagnostics card that drives both MSPs from the phone (toggle Switch + Refresh-diag button + counter readouts).

Targeting

This PR targets feature/parser-state-quiet-gate (the open #9), not master, because both branches have to merge together — they share Tx_main.cpp lines. After #9 merges to master, I'll retarget this PR to master.

Test plan

  • Builds clean: ESP32C3_TX_Backpack_via_USB env, flash unchanged.
  • HW-tested on C3 SuperMini + Galaxy S22:
    • Boots cleanly with both enabled and disabled persisted state.
    • Toggle on→off→on via Android Settings round-trips and persists across replug.
    • MSP_WAYBEAM_DIAG counters round-trip and increment.
    • Disable (software) silences UART1 entirely — verified by independent ESP-NOW behaviour relative to physical wire connection.

Memory note

backpack_rx_close_rf_interference.md in waybeam-coordination records the RFI lesson so future investigations don't burn the same hours we did.

🤖 Generated with Claude Code

snokvist and others added 2 commits April 30, 2026 17:21
Adds two diagnostic / triage knobs surfacing a separate observed bug:
when the wired CRSF receiver is connected (UART1 RX active, drainer
emitting `MSP_WAYBEAM_WIRED_CRSF` 0x0044 over USB-CDC at ~50 Hz),
ESP-NOW outbound `MSP_ELRS_BACKPACK_SET_PTR` (0x0383) and
`MSP_ELRS_BACKPACK_SET_HEAD_TRACKING` (0x030D) injection from the host
no longer reaches the bound peer. Disconnecting the GPIO 20 RX wire
restores ESP-NOW immediately. Existing comment at OnDataRecv flags the
same C3-single-core sensitivity ("we DO NOT call Serial.write from this
WiFi-task callback because that starves the main loop's Serial.read")
— this looks like the symmetric problem: heavy main-loop Serial.write
from the wired drainer starving the WiFi task's ESP-NOW outbound queue.

This commit ships the diagnostic surface and a triage gate; the actual
root-cause fix (most likely a `taskYIELD()` / `vTaskDelay(1)` after
each drain + batched writes to cut the call rate) is a follow-up that
the diagnostics here will validate.

* `MSP_WAYBEAM_WIRED_CRSF_CTRL` (0x0046) — host <-> device. Empty
  payload = query, `[1]`/`[0]` = enable/disable. Reply mirrors current
  state. Persisted to NVS namespace `waybeam_bp` key `wired_en` so a
  disable survives reboot. When disabled: `WiredCrsfPoll()` returns
  immediately, no UART1 reads, no `0x0044` emit, no Serial.write from
  this path. Backwards-compat default = enabled.
* `MSP_WAYBEAM_DIAG` (0x0047) — host -> device query. Reply payload:
  u32 espnow_tx_ok, u32 espnow_tx_fail, i32 espnow_tx_last_err, u8
  wired_crsf_enabled. Counters bumped inside `sendMSPViaEspnow` from
  the previously-discarded `esp_now_send` return code.
* `tools/backpack_cli.py wired on|off|<empty>` and
  `tools/backpack_cli.py diag` (in companion Android repo).

Build clean: ESP32C3_TX_Backpack_via_USB env, flash unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
WiredCrsfSetEnabled previously only gated the app-level poll + drainer.
The UART1 hardware was still attached to GPIO 20/21 with the RX ISR
firing on every received byte. That made the runtime disable silently
ineffective for any test that involved a connected ELRS RX still
clocking data into GPIO 20.

Now SetEnabled(false) calls gWiredUart.end() — full driver teardown,
pins released, no RX ISR. SetEnabled(true) re-attaches via begin().
WiredCrsfInit also respects the persisted state at boot: when NVS says
disabled, UART1 is never even attached.

Required for the runtime A/B test to be a real off-switch: with this
in place, software disable is equivalent to physically pulling the
GPIO 20 wire.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

1 participant