feat(repeater): politeness controls, dedicated radio profile, and diagnostics

Settings > Radio gains a repeater toggle, 16 community-suggested radio
presets plus manual Freq/SF/BW/CR tuning (digit-by-digit Freq editor),
4 persisted user preset slots, and the packet pool bumped 16->32 so
queued retransmits stop starving incoming-packet allocation while
relaying.

Four opt-in politeness knobs (skip-advert, max-hops, yield, min-SNR),
all flood-only and off by default, plus overhear suppression that
cancels a queued retransmit if a peer relays the same packet first.
Adaptive Power Control is suppressed while relaying (pins TX power to
the ceiling) and duty-cycle RX is forced off, since a repeater needs
to hear and relay at consistent power.

A dedicated Tools > Repeater screen consolidates the toggle, the
politeness knobs, and live forwarding stats, plus an optional "Custom"
radio profile — a dedicated frequency/SF/BW/CR for relaying, separate
from the companion's own network, band-matched to the companion
frequency by default and used everywhere a radio change can happen
(boot, on-device toggle, app-driven CMD_SET_RADIO_PARAMS, Settings
radio edits) so the device never silently falls back to the wrong
params mid-relay.

Diagnostics gains the actually-forwarded packet count, real heap-free
via mallinfo(), a reset-counters popup, and hardened loop-detect
bounds. Also: a frequency-floor fix and a float-equality preset-match
fix in the repeater profile logic, deduped BW-table/valCol/profile-
seeding helpers shared between Settings and the Repeater screen, and
a build.sh fix tolerating control characters in `pio project config`'s
JSON dump.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-06-20 09:07:00 +02:00
parent 327e659d51
commit 32c50d1cfe
27 changed files with 1296 additions and 89 deletions

View File

@@ -3,17 +3,20 @@
#if defined(NRF52_PLATFORM)
#include "FreeRTOS.h"
#include "task.h"
#include <malloc.h>
// Same linker symbols + _sbrk() the core's own new.cpp uses to implement
// malloc's heap (see framework-arduinoadafruitnrf52/cores/nRF5/new.cpp)
// there's no separate "free heap" API on this core, so this mirrors it.
extern "C" char* _sbrk(int incr);
// Heap region size from the linker symbols the core's new.cpp uses for sbrk
// (framework-arduinoadafruitnrf52/cores/nRF5/new.cpp). "Used" comes from
// newlib's mallinfo().uordblks (bytes actually outstanding) rather than
// sbrk(0) - __HeapBase: sbrk only ever grows, so a high-water mark there
// counts freed-and-recycled blocks as permanently "used" and free heap looks
// far lower than it really is.
extern unsigned char __HeapBase[];
extern unsigned char __HeapLimit[];
void DeviceDiag::getHeapStats(uint32_t& free_bytes, uint32_t& total_bytes) {
total_bytes = (uint32_t)(__HeapLimit - __HeapBase);
uint32_t used = (uint32_t)((unsigned char*)_sbrk(0) - __HeapBase);
uint32_t used = (uint32_t)mallinfo().uordblks;
free_bytes = (used < total_bytes) ? (total_bytes - used) : 0;
}

View File

@@ -20,6 +20,9 @@ public:
updatePreamble(sf);
}
// From RadioLib's SX1262::setFrequency(): RADIOLIB_CHECK_RANGE(freq, 150.0f, 960.0f, ...).
void getFreqBounds(float& min_mhz, float& max_mhz) const override { min_mhz = 150.0f; max_mhz = 960.0f; }
bool isReceivingPacket() override {
return ((CustomSX1262 *)_radio)->isReceiving();
}

View File

@@ -56,6 +56,13 @@ public:
}
virtual void setParams(float freq, float bw, uint8_t sf, uint8_t cr) = 0;
// RadioLib's own setFrequency() silently rejects values outside the chip's
// validated range and leaves the radio retuned to its previous frequency —
// setParams() above doesn't check that return code, so the UI clamps to this
// instead of letting NodePrefs drift out of sync with the actual radio.
// Default is the generic sanity bound the app's CMD_SET_RADIO_PARAMS already
// uses; chips with a narrower RadioLib-validated range override it.
virtual void getFreqBounds(float& min_mhz, float& max_mhz) const { min_mhz = 150.0f; max_mhz = 2500.0f; }
uint32_t getRngSeed();
void setTxPower(int8_t dbm);
int8_t getTxPower() const { return _tx_dbm; } // actual current power (reflects APC)