fix(radio): disable Pwr save (RX duty-cycle) — unsafe across mixed firmware

Field report: a user on the stock SF8 "EU/UK (Narrow)" preset saw reception
drop from ~1-5 msg/min to ~1/3h with Pwr save on, unaffected by a better
antenna. Root cause, confirmed against the SX1262 datasheet and RadioLib's
own maintainers (jgromes/RadioLib#1597, closed as inherent chip behaviour):
the SX126x's duty-cycle preamble detection needs the sender's actual
preamble to closely match what we've configured our receiver to expect.
A mismatch — e.g. a repeater still on pre-v1.16 firmware sending 16 symbols
against our 32 — doesn't cost a little sensitivity, it silently drops every
packet from that sender regardless of signal strength. There's no local
parameter fix: shortening minSymbols to tolerate shorter preambles directly
shortens the wake-window's correlator dwell time, trading the preamble
mismatch failure mode for a marginal-signal one instead. Checked whether
IoTThinks' MeshCore fork solved this differently — it doesn't hit the
problem at all, because its "power saving" only light-sleeps the MCU on the
radio's own GPIO interrupt and never touches RX duty-cycle.

examples/companion_radio/MyMesh.h now defines FEAT_RX_POWERSAVE 0, gating
out the Settings row, the Diagnostics RXPS watchdog row, and every call site
that would apply rx_powersave to the radio or CAD auto-enable — including
forcing setPowerSaving(false) unconditionally so a stale rx_powersave=1 byte
from before this change can't do anything either. The real duty-cycle
implementation in RadioLibWrapper/CustomSX1262Wrapper is left in place,
unneutered, for if a network-wide compatibility mechanism (e.g. the
still-unused ADV_FEAT1/FEAT2 advert bits) is ever built to make it safe.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-09-22 20:04:42 +02:00
co-authored by Claude Sonnet 5
parent f8cd24dd4d
commit 26e6bc877a
10 changed files with 119 additions and 14 deletions
@@ -61,6 +61,21 @@ public:
// minSymbols=8 is the reliable preamble-latch count for SF7-12. If the
// configured preamble is too short for a real duty-cycle (senderPreamble <
// 2*minSymbols+1), RadioLib transparently falls back to a continuous receive.
//
// NOT CURRENTLY REACHABLE: FEAT_RX_POWERSAVE (MyMesh.h) is 0, so nothing ever
// calls setPowerSaving(true) and this never runs. Left implemented (matching
// our own preambleLengthForSF(sf) convention, so it's correct for nodes on
// this same convention) rather than removed, because the reason it's disabled
// isn't a bug here: the SX126x's duty-cycle preamble detection needs the
// *actual transmitted* preamble to exactly match what we assume here, a
// confirmed hardware behaviour (SX1262 datasheet 6.1.3; RadioLib issue
// https://github.com/jgromes/RadioLib/issues/1597) that no local parameter
// choice can work around -- it requires a network-wide compatibility
// guarantee we can't currently make. A mismatch doesn't cost a little
// sensitivity, it silently drops every packet from that sender regardless of
// signal strength (2026-09-22 field report: near-total reception loss on a
// stock SF8 preset, unaffected by antenna gain). See MyMesh.h and
// docs/development/roadmap.md before ever flipping FEAT_RX_POWERSAVE back on.
int16_t startPowerSaveRecv() override {
return ((SX126x *)_radio)->startReceiveDutyCycleAuto(preambleLengthForSF(_preamble_sf), 8);
}
+7
View File
@@ -46,6 +46,13 @@ protected:
// stays in RX to receive the packet (RX_DONE on DIO1) — no MCU state machine,
// average RX current cut several-fold. Driven from armRecv()/loop(); falls back
// to continuous RX if the modem doesn't support it.
// Companion-side gating: FEAT_RX_POWERSAVE (examples/companion_radio/MyMesh.h)
// is 0, so nothing on that target ever calls setPowerSaving(true) and this
// path never actually runs there -- the SX126x duty-cycle's preamble
// detection needs the sender's actual preamble to exactly match what we
// configure (confirmed hardware behaviour, not a bug here), which can't be
// guaranteed across a mesh with mixed firmware. See that file before
// re-enabling it anywhere.
bool _power_save = false;
bool _ps_active = false; // is the radio currently armed in duty-cycle mode
int8_t _tx_dbm = 0; // last TX power applied (tracks APC's live value)