Skip to content

fix(radio): don't hang on a transient SPI failure talking to the LoRa chip - #11234

Draft
ndoo wants to merge 2 commits into
meshtastic:developfrom
meshmy:fix/radio-spi-transient-failure-asserts
Draft

fix(radio): don't hang on a transient SPI failure talking to the LoRa chip#11234
ndoo wants to merge 2 commits into
meshtastic:developfrom
meshmy:fix/radio-spi-transient-failure-asserts

Conversation

@ndoo

@ndoo ndoo commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Problem

assert(err == RADIOLIB_ERR_NONE) / assert(result != RADIOLIB_ERR_WRONG_MODEM) guard SPI communication with the physical LoRa chip in setStandby(), startReceive(), isChannelActive() (plus a few config-time calls in init()) across all five RadioLib interface wrappers (SX126xInterface.cpp, LR11x0Interface.cpp, SX128xInterface.cpp, RF95Interface.cpp, LR20x0Interface.cpp). On STM32WL these hang forever with no diagnostic instead of a logged, recoverable error — a real risk since setStandby()/startReceive()/isChannelActive() run on every RX/TX state transition, i.e. continuously during normal operation, guarding against exactly the kind of transient SPI glitch (noise, marginal power, contention) that's a realistic field failure mode.

Tellingly, the ARCH_PORTDUINO branch sitting right next to each of these already treats the same condition as recoverable (portduino_status.LoRa_in_error = true; instead of asserting) — real hardware just never got the same treatment.

Fix

Converted each assert() to RECORD_CRITICALERROR(): INVALID_RADIO_SETTING for the config-time calls (matching how every neighboring call in the same init() functions — setSpreadingFactor, setBandwidth, setFrequency, etc. — already handles a rejected setting), RADIO_SPI_BUG for the runtime SPI operations in setStandby()/startReceive()/isChannelActive().

SX126xInterface.cpp is the only one of these actually compiled for STM32WL today (RADIOLIB_EXCLUDE_{SX128X,SX127X,LR11X0,LR2021}=1); the other four are identical copy-paste siblings. Fixed all five in the same pass — a future STM32WL variant using a different RadioLib-supported front-end is plausible given this board family's history of gaining hardware support over time (RTC, TCXO options), and it's the same mechanical fix either way.

Test plan

  • pio run -e wio-e5 / pio run -e rak3172 — build clean (exercises SX126xInterface.cpp, the one compiled for STM32WL).
  • pio run -e native-macos — build clean, confirmed all 5 modified files actually compile (portduino builds every radio driver).
  • pio run -e tbeam (RF95/SX1276), pio run -e heltec-wireless-bridge (LR11x0 + LR2021), pio run -e makerpython_nrf52840_sx1280_oled (SX128x) — build clean on real hardware targets, exercising the non-ARCH_PORTDUINO branch (the actual new code path) for all four sibling files not compiled on STM32WL.
  • Hardware (wio-e5): flashed, confirmed clean boot (exercises setStandby()/startReceive() on every boot's radio init) and sent a text message over LoRa (exercises isChannelActive()'s CAD scan before transmit) — device stayed fully responsive throughout, confirming the success path through all six converted call sites in SX126xInterface.cpp is unaffected.
  • The actual transient-SPI-failure condition wasn't reproduced live (would require physically glitching the SPI bus) — this is a mechanical assert→RECORD_CRITICALERROR conversion, matching the already-established pattern from fix(stm32wl): recover from littlefs internal corruption instead of hanging #11230/fix(router): release packets Router::send() declines instead of leaking them #11231/fix(mesh): harden MemoryPool<T,MaxSize>::release() against bad pointers #11232.

🤝 Attestations

  • I have tested that my proposed changes behave as described.
  • I have tested that my proposed changes do not cause any obvious regressions on the following devices:
    • Heltec (Lora32) V3
    • LilyGo T-Deck
    • LilyGo T-Beam
    • RAK WisBlock 4631
    • Seeed Studio T-1000E tracker card
    • Other (please specify below): wio-e5 — build + hardware verified per test plan above. rak3172, tbeam, heltec-wireless-bridge, makerpython_nrf52840_sx1280_oled, native-macos — build-verified only.

… chip

assert(err == RADIOLIB_ERR_NONE)/assert(result != RADIOLIB_ERR_WRONG_MODEM)
guarded SPI communication with the physical radio chip in setStandby(),
startReceive(), and isChannelActive() (plus a few config-time calls) across
all five RadioLib interface wrappers. On STM32WL these hang forever with no
diagnostic instead of a logged, recoverable error - a real risk since these
run on every RX/TX state transition, i.e. continuously during normal
operation, guarding against exactly the kind of transient SPI glitch
(noise, marginal power, contention) that's a realistic field failure mode.

The ARCH_PORTDUINO branch next to each of these already treats the same
condition as recoverable (sets portduino_status.LoRa_in_error instead of
asserting) - real hardware just never got the same treatment. Converted to
RECORD_CRITICALERROR (INVALID_RADIO_SETTING for config calls,
RADIO_SPI_BUG for the runtime SPI ops), matching the treatment every
neighboring call in the same functions (setSpreadingFactor, setBandwidth,
setFrequency, etc.) already gets.

SX126xInterface.cpp is the only one of these actually compiled for STM32WL
today; the other four (LR11x0/SX128x/RF95/LR20x0) are identical copy-paste
siblings, fixed in the same pass since a future STM32WL variant using a
different RadioLib-supported front-end is plausible given this board
family's history of gaining hardware support over time.

Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Andrew Yong <me@ndoo.sg>
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4eff2a5f-b486-4bf6-ab44-28902cd6e8a6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

⚡ Try this PR in the Web Flasher

Note

Building this pull request… the flash button, badges and supported-board
list will appear here automatically once CI finishes.

@NomDeTom

Copy link
Copy Markdown
Collaborator

have you observed this in the wild?

I've got some issues with LR1121 of my own.

@ndoo

ndoo commented Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

I don’t really know if this PR should be merged. It stops the firmware from intentionally crashing, but it leaves the node unreachable by LoRa anyway.

Pros for merging: You can still reach it by BLE/Wi-Fi/USB as available and reboot it from a Meshtastic client device.
Cons for merging: Device doesn’t provide external indication of a crash - which may be desirable if you are looking for a stuck screen/LED.

WDYT?

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.

3 participants