fix(lr2021): setTxPower() could leave the receiver down until reboot

RadioLibWrapper::setTxPower() called setOutputPower() and nothing else. On
LR2021 that writes the PA config and TxParams, which are standby-only commands,
and it never re-arms the receiver.

This only affects LR2021 boards. recvRaw() has:

    #if defined(USE_LR2021)
      state = STATE_RX;    // LR2021 stays in Rx after readData
    #else
      state = STATE_IDLE;  // need another startReceive()
    #endif

so on SX126x the next recvRaw() re-arms Rx anyway and the write is harmless,
while on LR2021 startReceive() is never called again on its own. A 'set tx'
issued while the radio was listening could therefore stop reception until the
next reboot. Observed a few times on an LR2021 repeater; recovery required a
power cycle.

Dispatcher's stuck-radio check does not catch it: isInRecvMode() reports the
wrapper's own `state` flag rather than the chip's actual mode, so it still
believes the radio is in Rx, and it only raises ERR_EVENT_STARTRX_TIMEOUT
without attempting recovery.

Fix: on LR2021, drop to standby via idle() before writing the PA config and let
checkRecv() re-arm Rx - the same pattern resetAGC() and
applySideDetectorConfig() already use. Other radios are left untouched.

Affects meshtracker_x1 and meshnology_w12 (both USE_LR2021).

Tested on hardware with an LR2021 repeater: six consecutive 'set tx' changes
(15/18/21/14/19/14), after which the radio kept receiving and forwarding
traffic (rawrx/rxpkts increasing, no missed IRQs). Noise-floor sampling also
kept updating, which only happens while state == STATE_RX.
This commit is contained in:
Fedor Kallay
2026-08-15 02:31:19 +02:00
parent d929643524
commit 114093ee0e
+11
View File
@@ -48,6 +48,17 @@ uint32_t RadioLibWrapper::getRngSeed() {
}
void RadioLibWrapper::setTxPower(int8_t dbm) {
#if defined(USE_LR2021)
// On LR2021, setOutputPower() writes the PA config and TxParams, which are
// standby-only commands. recvRaw() keeps state == STATE_RX after readData on
// this platform ("LR2021 stays in Rx"), so - unlike the SX126x path, which
// falls back to STATE_IDLE and re-arms on the next recvRaw() - nothing calls
// startReceive() again by itself. Writing the PA config while listening could
// therefore leave the receiver down until the next reboot. Drop to standby
// first and let checkRecv() re-arm Rx, as resetAGC() and
// applySideDetectorConfig() already do.
idle();
#endif
_radio->setOutputPower(dbm);
}