feat(repeater): default network profile, band-matched to companion freq

Enabling the repeater now defaults to a dedicated radio profile rather
than relaying on the companion's current frequency: same-network
repeating isn't the MeshCore community norm. The default profile is
seeded with a frequency in the same legal band as the companion's own
network (433/868/915 MHz, via defaultRepeaterFreqForBand()) rather than
a flat firmware constant, so it can't land outside what's allowed for
the operator's region.

Also fixes a gap where a true first boot (no prefs file yet) never hit
the DataStore.cpp migration fallback and stayed on repeater_use_profile=0.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-06-20 01:04:35 +02:00
parent aba924bac9
commit afe7b8a0ca
7 changed files with 81 additions and 26 deletions

View File

@@ -447,8 +447,10 @@ On-device repeater for the Solo companion, scoped to the SX1262 boards (Wio
Tracker L1 OLED/e-ink, GAT562 30S). All on `feature/companion-repeater-presets`.
- **Repeater toggle** (`client_repeat`, Tools Repeater) — the companion relays
flood/direct traffic on its **current** frequency (no dedicated band), still
working as a normal companion. `MyMesh::allowPacketForward` gates it; loop
flood/direct traffic, still working as a normal companion. By default it
switches to a dedicated band on enable (see profile note below) rather than
relaying on whatever network it's chatting on. `MyMesh::allowPacketForward`
gates it; loop
detection (`isRepeatLooped`, ported from `simple_repeater`) and an advert
flood-depth cap are always applied. Packet pool bumped 16→32 to match the
repeater workload (a too-small pool starved channel/DM reception once relaying
@@ -464,8 +466,18 @@ Tracker L1 OLED/e-ink, GAT562 30S). All on `feature/companion-repeater-presets`.
equal to the companion = "same network", a different one = drop onto a separate
repeater network. `MyMesh::applyRepeaterRadio()` is the single decision point,
called at boot and on every toggle/edit; `repeaterProfileValid()` gates it.
Schema sentinel `0xC0DE0010`, profile cleared to off if load finds an invalid
config (stray bytes from older files).
Schema sentinel `0xC0DE0010`. **Custom is the default**: a never-configured
device (or one upgrading from a pre-`0x10` file, which has no saved profile)
turns the profile on and seeds `repeater_sf/bw/cr` from `LORA_SF/BW/CR` plus
a band-matched `repeater_freq` — relaying on the same network the operator is
chatting on isn't the MeshCore community norm, so "Current" stays opt-in.
`defaultRepeaterFreqForBand()` (`NodePrefs.h`) buckets the companion's own
`freq` into whichever of the three license-exempt bands MeshCore's app-driven
repeat toggle historically restricted to (433.000 / 869.495 / 918.000 MHz —
see `repeat_freq_ranges` in `MyMesh.cpp`), so the seeded default can't land
outside what's legal for wherever the companion's own network already is.
`LORA_FREQ/BW/SF/CR` fallback `#define`s moved from `MyMesh.h` to
`NodePrefs.h` so `DataStore.cpp`'s migration code can see them too.
- **Politeness knobs** (all opt-in, default off, flood-only — a direct route's
named next hop is never dropped). Shown only while the repeater is on:
- **Skip advert** — don't re-flood adverts (highest-volume flood).

View File

@@ -319,14 +319,14 @@ The counters make the repeater behaviour observable: **Forwarded** confirms the
## Repeater
Turns the companion into a packet **repeater** while it keeps working as a normal companion — no separate firmware. By default it relays on its **current** frequency, or you can give it a dedicated radio profile to switch to while relaying (see **Network** below). Loop-detection and an advert flood-depth cap are always applied. This screen keeps the toggle, the network/profile, and its flood-filter options together; live forwarding stats are on **Tools Diagnostics**.
Turns the companion into a packet **repeater** while it keeps working as a normal companion — no separate firmware. By default, enabling it switches the radio to a dedicated repeater profile rather than relaying on whatever network you're chatting on (see **Network** below) — that matches the MeshCore community norm of repeaters sitting on a standard channel, not a private one. Loop-detection and an advert flood-depth cap are always applied. This screen keeps the toggle, the network/profile, and its flood-filter options together; live forwarding stats are on **Tools Diagnostics**.
Navigate with **UP/DOWN**; change a value with **LEFT/RIGHT** (or **Enter** for toggles). **Cancel/Back** saves and returns to Tools.
| Setting | Options | Notes |
| -------------- | --------------- | -------------------------------------------------------------------------------------------------------------- |
| Repeater | ON / OFF | Master switch. The options below appear only while it is ON. |
| Network | Current / Custom | **Current**: relay on the companion's own frequency (default). **Custom**: enabling the repeater switches the radio to a dedicated profile (below) and disabling restores the companion's settings — so you can drop onto a separate repeater network and come back. Switching to Custom seeds the profile from your current settings. |
| Network | Current / Custom | **Custom** _(default)_: enabling the repeater switches the radio to a dedicated profile (below) and disabling restores the companion's settings — so you can drop onto a separate repeater network and come back. A never-configured device seeds Custom with a frequency in the same band as your own network (433/868/915 MHz region), not a flat one-size-fits-all default — so it can't land outside what's legal for your region. Switching to Custom afterwards (if it was OFF and unconfigured) seeds it from your current settings instead. **Current**: relay on the companion's own frequency — opt-in; not the community norm. |
| Rpt preset | named presets | _(Custom only)_ **Enter** picks a community/saved preset for the repeater profile. |
| Rpt freq | chip range | _(Custom only)_ **Enter** opens the digit-by-digit editor (chip-validated bounds). |
| Rpt SF / BW / CR | 512 / 7.8500 kHz / 58 | _(Custom only)_ **LEFT/RIGHT** to adjust the profile's spreading factor, bandwidth, coding rate. |

View File

@@ -356,16 +356,23 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
rd(&_prefs.repeater_bw, sizeof(_prefs.repeater_bw));
rd(&_prefs.repeater_sf, sizeof(_prefs.repeater_sf));
rd(&_prefs.repeater_cr, sizeof(_prefs.repeater_cr));
// Pre-0x10 files leave stray sentinel bytes here. If the profile fields aren't a
// valid radio config, force the profile off so the repeater can't switch to junk.
// Pre-0x10 files leave stray sentinel bytes here, same as a never-configured
// device. Either way there's no valid saved profile, so default to a profile
// in the same band as the companion's own network (_prefs.freq, already read
// above) rather than "Current" — a repeater silently following the companion
// onto whatever private network it later joins isn't the MeshCore community
// norm; that stays opt-in. Band-matched rather than a flat frequency so the
// default can't land outside what's legal where the companion is set up.
if (_prefs.repeater_use_profile > 1) _prefs.repeater_use_profile = 0;
if (!(_prefs.repeater_freq >= 100.0f && _prefs.repeater_freq <= 960.0f
&& _prefs.repeater_sf >= 5 && _prefs.repeater_sf <= 12
&& _prefs.repeater_cr >= 5 && _prefs.repeater_cr <= 8
&& _prefs.repeater_bw >= 7.0f && _prefs.repeater_bw <= 510.0f)) {
_prefs.repeater_use_profile = 0;
_prefs.repeater_freq = _prefs.repeater_bw = 0.0f;
_prefs.repeater_sf = _prefs.repeater_cr = 0;
_prefs.repeater_use_profile = 1;
_prefs.repeater_freq = defaultRepeaterFreqForBand(_prefs.freq);
_prefs.repeater_bw = LORA_BW;
_prefs.repeater_sf = LORA_SF;
_prefs.repeater_cr = LORA_CR;
}
// → 0xC0DE000B: append bot_commands_enabled + quiet-hours. Older files leave
// stray bytes here; clamp so upgraders fall back to off / no quiet hours.

View File

@@ -1307,6 +1307,14 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
_prefs.bw = LORA_BW;
_prefs.cr = LORA_CR;
_prefs.tx_power_dbm = LORA_TX_POWER;
// Repeater profile default for a true first boot (no prefs file yet, so
// loadPrefs() below is a no-op) — same band-matched seed as the upgrade
// path in DataStore.cpp, kept in sync rather than left at memset's 0/Current.
_prefs.repeater_use_profile = 1;
_prefs.repeater_freq = defaultRepeaterFreqForBand(_prefs.freq);
_prefs.repeater_bw = LORA_BW;
_prefs.repeater_sf = LORA_SF;
_prefs.repeater_cr = LORA_CR;
_prefs.gps_enabled = 0; // GPS disabled by default
_prefs.gps_interval = 0; // No automatic GPS updates by default
_prefs.display_brightness = 2; // medium brightness by default

View File

@@ -41,18 +41,7 @@ class UITask;
/* ---------------------------------- CONFIGURATION ------------------------------------- */
#ifndef LORA_FREQ
#define LORA_FREQ 915.0
#endif
#ifndef LORA_BW
#define LORA_BW 250
#endif
#ifndef LORA_SF
#define LORA_SF 10
#endif
#ifndef LORA_CR
#define LORA_CR 5
#endif
// LORA_FREQ/BW/SF/CR fallbacks now live in NodePrefs.h (DataStore.cpp needs them too).
#ifndef LORA_TX_POWER
#define LORA_TX_POWER 20
#endif

View File

@@ -2,6 +2,37 @@
#include <cstdint>
#include <stdio.h>
// Firmware's own boot-default radio params — used both as the companion's
// initial freq/sf/bw/cr (MyMesh.cpp) and, here, as the seed for a never-
// configured repeater profile (DataStore.cpp). Kept above any per-board
// target.h include so a board can still override via -D before this file
// is reached.
#ifndef LORA_FREQ
#define LORA_FREQ 915.0
#endif
#ifndef LORA_BW
#define LORA_BW 250
#endif
#ifndef LORA_SF
#define LORA_SF 10
#endif
#ifndef LORA_CR
#define LORA_CR 5
#endif
// Bucket a companion frequency into whichever of the three license-exempt
// bands MeshCore's app-driven repeat toggle historically restricted to (see
// repeat_freq_ranges in MyMesh.cpp: 433.000 / 869.495 / 918.000 MHz). Used to
// seed a never-configured repeater profile in the same legal band as the
// companion's own network (MyMesh.cpp begin(), DataStore.cpp) — a flat
// single frequency could land outside the bands allowed where the operator
// actually lives.
static inline float defaultRepeaterFreqForBand(float companion_freq) {
if (companion_freq < 500.0f) return 433.000f;
if (companion_freq < 890.0f) return 869.495f;
return 918.000f;
}
#define TELEM_MODE_DENY 0
#define TELEM_MODE_ALLOW_FLAGS 1 // use contact.flags
#define TELEM_MODE_ALLOW_ALL 2
@@ -176,7 +207,11 @@ struct NodePrefs { // persisted to file
// Optional dedicated radio profile for repeater mode. When repeater_use_profile
// is 1, enabling the repeater switches the radio to repeater_freq/bw/sf/cr and
// disabling restores the companion's freq/bw/sf/cr (the fields above). 0 = the
// repeater stays on the current companion frequency (default — no switch).
// repeater stays on the current companion frequency. Default (a never-configured
// device) is 1, seeded via defaultRepeaterFreqForBand(freq) + LORA_SF/BW/CR —
// repeating on whatever private network the companion later joins isn't the
// community norm, and the seed stays in the same legal band as the companion's
// own network rather than a flat frequency.
uint8_t repeater_use_profile;
float repeater_freq;
float repeater_bw;

View File

@@ -6,10 +6,14 @@
// and keeps the relaying controls next to the numbers that show them working.
//
// Network modes:
// - Current — repeat on the companion's current frequency (default).
// - Current — repeat on the companion's current frequency. Opt-in only:
// relaying on whatever network the operator is chatting on
// isn't the MeshCore community norm.
// - Custom — enabling the repeater switches the radio to a dedicated profile
// (preset or manual), and disabling restores the companion params.
// Set the profile equal to the companion's for "same network".
// Default for a never-configured device, seeded with a frequency
// in the same band as the companion's own network (so the default
// can't land outside what's legal for the operator's region).
// Included by UITask.cpp.
#include <helpers/ui/DisplayDriver.h>