mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-09-30 22:56:40 +00:00
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:
@@ -287,7 +287,14 @@ bool MyMesh::getCADEnabled() const {
|
||||
// _prefs.cad_enabled itself has no UI/CLI exposure yet on companion_radio
|
||||
// (unlike simple_repeater's CommonCLI `cad` command) — it's wired and
|
||||
// persisted for a future manual override, but always 0 today.
|
||||
// rx_powersave is never actually applied to the radio while FEAT_RX_POWERSAVE
|
||||
// is 0 (see MyMesh.h) -- don't let a stale persisted byte from before that
|
||||
// still auto-enable CAD here for a duty-cycle mode that isn't running.
|
||||
#if FEAT_RX_POWERSAVE
|
||||
return _prefs.cad_enabled || (_prefs.rx_powersave && !_prefs.client_repeat);
|
||||
#else
|
||||
return _prefs.cad_enabled;
|
||||
#endif
|
||||
}
|
||||
|
||||
int MyMesh::calcRxDelay(float score, uint32_t air_time) const {
|
||||
@@ -1991,7 +1998,11 @@ void MyMesh::begin(bool has_display) {
|
||||
applyRepeaterRadio(); // companion params, or the repeater profile if relaying with one set
|
||||
applyApc(); // sets TX power to the ceiling and arms APC if enabled
|
||||
radio_driver.setRxBoostedGainMode(_prefs.rx_boosted_gain);
|
||||
#if FEAT_RX_POWERSAVE
|
||||
radio_driver.setPowerSaving(_prefs.rx_powersave && !_prefs.client_repeat); // duty-cycle RX off while repeating (must hear all traffic)
|
||||
#else
|
||||
radio_driver.setPowerSaving(false); // see MyMesh.h FEAT_RX_POWERSAVE -- ignore any stale persisted rx_powersave byte
|
||||
#endif
|
||||
board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain);
|
||||
board.setLoRaFemPaGainEnabled(_prefs.radio_fem_txgain);
|
||||
MESH_DEBUG_PRINTLN("RX Boosted Gain Mode: %s",
|
||||
@@ -2482,7 +2493,11 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
// Keep the "repeating ⇒ continuous RX, full TX power" invariants when repeat
|
||||
// is toggled via the app, mirroring the on-device path (a repeater must hear
|
||||
// all traffic and relay at consistent power).
|
||||
#if FEAT_RX_POWERSAVE
|
||||
radio_driver.setPowerSaving(_prefs.rx_powersave && !_prefs.client_repeat);
|
||||
#else
|
||||
radio_driver.setPowerSaving(false); // see MyMesh.h FEAT_RX_POWERSAVE -- ignore any stale persisted rx_powersave byte
|
||||
#endif
|
||||
applyApc(); // pins power to the ceiling; apcActive() keeps it there while repeating
|
||||
MESH_DEBUG_PRINTLN("OK: CMD_SET_RADIO_PARAMS: f=%d, bw=%d, sf=%d, cr=%d", freq, bw, (uint32_t)sf,
|
||||
(uint32_t)cr);
|
||||
|
||||
@@ -11,6 +11,20 @@ class UITask;
|
||||
/*------------ Frame Protocol --------------*/
|
||||
#define FIRMWARE_VER_CODE 13
|
||||
|
||||
// Hardware RX duty-cycle ("Pwr save") is disabled: the SX126x's duty-cycle
|
||||
// preamble detection needs the *actual transmitted* preamble to exactly match
|
||||
// what we're configured to expect (confirmed hardware behaviour, see SX1262
|
||||
// datasheet 6.1.3 and https://github.com/jgromes/RadioLib/issues/1597) --
|
||||
// something we can't guarantee across a mesh with mixed firmware. 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, unaffected by antenna). The real duty-cycle implementation
|
||||
// (RadioLibWrapper::armRecv()/CustomSX1262Wrapper::startPowerSaveRecv()) is
|
||||
// left in place for if a network-wide compatibility mechanism ever lands --
|
||||
// flipping this back to 1 requires solving that first, not just re-adding the
|
||||
// Settings toggle. See docs/development/roadmap.md for the full writeup.
|
||||
#define FEAT_RX_POWERSAVE 0
|
||||
|
||||
// Fallback only -- every real build (local or CI) goes through build.sh, which
|
||||
// always injects its own FIRMWARE_BUILD_DATE (today's date at build time).
|
||||
// __DATE__ is the compiler's own "Mmm dd yyyy" build-date macro, so a
|
||||
|
||||
@@ -144,12 +144,14 @@ class DiagnosticsScreen : public UIScreen {
|
||||
}
|
||||
addRow("Errors", buf);
|
||||
|
||||
#if FEAT_RX_POWERSAVE
|
||||
// RX duty-cycle watchdog recovery counts since boot/reset (soft re-arm /
|
||||
// hard chip reset). Always 0/0 on radios or profiles that never arm
|
||||
// duty-cycle power-save (repeaters force it off — see applyPowerSave()).
|
||||
snprintf(buf, sizeof(buf), "%lu/%lu", (unsigned long)radio_driver.getRxPsWatchdogSoftCount(),
|
||||
(unsigned long)radio_driver.getRxPsWatchdogHardCount());
|
||||
addRow("RXPS wd s/h", buf);
|
||||
#endif
|
||||
}
|
||||
|
||||
void buildSystemLines() {
|
||||
|
||||
@@ -61,7 +61,9 @@ class SettingsScreen : public UIScreen {
|
||||
TX_POWER,
|
||||
RADIO_PRESET,
|
||||
CUSTOM_FREQ, CUSTOM_SF, CUSTOM_BW, CUSTOM_CR,
|
||||
#if FEAT_RX_POWERSAVE
|
||||
POWER_SAVE,
|
||||
#endif
|
||||
TX_APC,
|
||||
SCOPE_NAME,
|
||||
// System section
|
||||
@@ -556,12 +558,14 @@ class SettingsScreen : public UIScreen {
|
||||
snprintf(buf, sizeof(buf), "%d", p ? (int)p->cr : 0);
|
||||
display.setCursor(valCol(display), y);
|
||||
display.print(buf);
|
||||
#if FEAT_RX_POWERSAVE
|
||||
} else if (item == POWER_SAVE) {
|
||||
display.print("Pwr save");
|
||||
display.setCursor(valCol(display), y);
|
||||
// Forced off (and locked) while the repeater is on — it must hear all traffic.
|
||||
if (p && p->client_repeat) display.print("--");
|
||||
else display.print((p && p->rx_powersave) ? "ON" : "OFF");
|
||||
#endif
|
||||
} else if (item == TX_APC) {
|
||||
display.print("Auto pwr");
|
||||
display.setCursor(valCol(display), y);
|
||||
@@ -1078,6 +1082,7 @@ public:
|
||||
if (_selected == CUSTOM_SF && p && dir && RadioParamsEditor::stepSF(p->sf, dir)) { _task->applyRadioParams(); _dirty = true; return true; }
|
||||
if (_selected == CUSTOM_BW && p && dir && RadioParamsEditor::stepBW(p->bw, dir)) { _task->applyRadioParams(); _dirty = true; return true; }
|
||||
if (_selected == CUSTOM_CR && p && dir && RadioParamsEditor::stepCR(p->cr, dir)) { _task->applyRadioParams(); _dirty = true; return true; }
|
||||
#if FEAT_RX_POWERSAVE
|
||||
if (_selected == POWER_SAVE && p && (left || right || enter)) {
|
||||
if (p->client_repeat) { _task->showAlert("Off while repeating", 900); return true; }
|
||||
p->rx_powersave ^= 1;
|
||||
@@ -1085,6 +1090,7 @@ public:
|
||||
_dirty = true;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
if (_selected == TX_APC && p && (left || right || enter)) {
|
||||
if (p->client_repeat) { _task->showAlert("Off while repeating", 900); return true; }
|
||||
p->tx_apc ^= 1;
|
||||
|
||||
@@ -3805,10 +3805,14 @@ void UITask::applyTxPower() {
|
||||
|
||||
void UITask::applyPowerSave() {
|
||||
if (_node_prefs == NULL) return;
|
||||
#if FEAT_RX_POWERSAVE
|
||||
// A repeater must hear every packet to relay it, so duty-cycle RX (which sleeps
|
||||
// between preamble checks) is forced off while repeating — the user's pref is
|
||||
// kept and restored when the repeater is switched off.
|
||||
radio_driver.setPowerSaving(_node_prefs->rx_powersave && !_node_prefs->client_repeat);
|
||||
#else
|
||||
radio_driver.setPowerSaving(false); // see MyMesh.h FEAT_RX_POWERSAVE -- ignore any stale persisted rx_powersave byte
|
||||
#endif
|
||||
}
|
||||
|
||||
void UITask::applyApc() {
|
||||
|
||||
Reference in New Issue
Block a user