mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-07-26 14:58:12 +00:00
fix(repeater): close radio-profile bypass paths, dedupe preset/BW logic
CMD_SET_RADIO_PARAMS and the on-device Settings radio editors (Freq/SF/BW/CR/Preset) called radio_driver.setParams() directly instead of applyRepeaterRadio(), so editing the companion's own radio params while relaying on a dedicated profile silently retuned the live radio off that profile. Both now route through applyRepeaterRadio(), the single decision point already used at boot and by the on-device repeater toggle. Also: overhear-suppression matched queued packets by payload hash alone, so it could cancel a queued direct-route retransmit that happened to share a hash with an overheard flood packet — now scoped to flood-queued entries only. repeaterProfileValid()'s frequency floor (100 MHz) is raised to 150 MHz to match the radio's actual validated range. RepeaterScreen's preset-name lookup now uses the same epsilon-tolerant float comparison as Settings instead of exact ==, avoiding inconsistent "Custom" labeling between the two screens. Dedupe: the BW-options table, the value-column x-offset math, and the default-profile-seeding block had each drifted into two or three independent copies (RepeaterScreen vs Settings, MyMesh constructor vs DataStore.cpp upgrade path); factored into shared helpers in RadioPresets.h / NodePrefs.h. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -364,15 +364,8 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
|
||||
// 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 = 1;
|
||||
_prefs.repeater_freq = defaultRepeaterFreqForBand(_prefs.freq);
|
||||
_prefs.repeater_bw = LORA_BW;
|
||||
_prefs.repeater_sf = LORA_SF;
|
||||
_prefs.repeater_cr = LORA_CR;
|
||||
if (!isValidRepeaterProfile(_prefs.repeater_freq, _prefs.repeater_bw, _prefs.repeater_sf, _prefs.repeater_cr)) {
|
||||
seedDefaultRepeaterProfile(_prefs);
|
||||
}
|
||||
// → 0xC0DE000B: append bot_commands_enabled + quiet-hours. Older files leave
|
||||
// stray bytes here; clamp so upgraders fall back to off / no quiet hours.
|
||||
|
||||
@@ -1309,12 +1309,8 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
||||
_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;
|
||||
// path in DataStore.cpp.
|
||||
seedDefaultRepeaterProfile(_prefs);
|
||||
_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
|
||||
@@ -1868,7 +1864,7 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
_prefs.client_repeat = repeat;
|
||||
savePrefs();
|
||||
|
||||
radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
applyRepeaterRadio(); // companion params, or the repeater profile if relaying with one set
|
||||
// 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).
|
||||
|
||||
@@ -224,10 +224,7 @@ public:
|
||||
|
||||
// True when the optional repeater radio profile is a valid LoRa config.
|
||||
bool repeaterProfileValid() const {
|
||||
return _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;
|
||||
return isValidRepeaterProfile(_prefs.repeater_freq, _prefs.repeater_bw, _prefs.repeater_sf, _prefs.repeater_cr);
|
||||
}
|
||||
// Load the radio with the correct params for the current mode: the repeater
|
||||
// profile when relaying with a valid dedicated profile, otherwise the
|
||||
|
||||
@@ -291,3 +291,23 @@ struct NodePrefs { // persisted to file
|
||||
if (pos < size) buf[pos] = '\0';
|
||||
}
|
||||
};
|
||||
|
||||
// Bounds for a usable repeater radio profile — must match the radio chip's own
|
||||
// validated range (see CustomSX1262Wrapper::getFreqBounds(), 150-960 MHz), so
|
||||
// a profile that passes here is guaranteed to actually take effect on the radio.
|
||||
static inline bool isValidRepeaterProfile(float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||
return freq >= 150.0f && freq <= 960.0f
|
||||
&& sf >= 5 && sf <= 12
|
||||
&& cr >= 5 && cr <= 8
|
||||
&& bw >= 7.0f && bw <= 510.0f;
|
||||
}
|
||||
|
||||
// Seed a never-configured repeater profile in the same band as the companion's
|
||||
// own network (see defaultRepeaterFreqForBand() above for why).
|
||||
static inline void seedDefaultRepeaterProfile(NodePrefs& prefs) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#include <math.h>
|
||||
|
||||
// Community-suggested per-region RF presets (freq/bw/sf/cr), sourced from the
|
||||
// MeshCore app's "suggested_radio_settings" config plus a few community additions.
|
||||
@@ -33,3 +34,17 @@ static const RadioPreset RADIO_PRESETS[] = {
|
||||
{ "Vietnam (Narrow)", 920.250f, 62.5f, 8, 5 },
|
||||
};
|
||||
static const int RADIO_PRESET_COUNT = sizeof(RADIO_PRESETS) / sizeof(RADIO_PRESETS[0]);
|
||||
|
||||
// Standard SX126x/SX127x LoRa bandwidths (kHz) — manual BW fields (companion and
|
||||
// repeater profile alike) cycle through these rather than a free-form value, so
|
||||
// they can't land on an unsupported setting.
|
||||
static const float LORA_BW_OPTS[] = { 7.8f, 10.4f, 15.6f, 20.8f, 31.25f, 41.7f, 62.5f, 125.0f, 250.0f, 500.0f };
|
||||
static const int LORA_BW_OPT_COUNT = sizeof(LORA_BW_OPTS) / sizeof(LORA_BW_OPTS[0]);
|
||||
|
||||
// True if (freq, bw, sf, cr) matches the radio's currently-applied (cur_*) params.
|
||||
// Floats are compared with a small epsilon since they may have round-tripped
|
||||
// through unit conversions (e.g. kHz integers from the companion app).
|
||||
static inline bool radioParamsMatchPreset(float cur_freq, float cur_bw, uint8_t cur_sf, uint8_t cur_cr,
|
||||
float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||
return fabsf(cur_freq - freq) < 0.001f && fabsf(cur_bw - bw) < 0.001f && cur_sf == sf && cur_cr == cr;
|
||||
}
|
||||
|
||||
@@ -45,15 +45,15 @@ class RepeaterScreen : public UIScreen {
|
||||
uint8_t _preset_user_slot[NodePrefs::USER_RADIO_PRESET_MAX];
|
||||
int _preset_user_count = 0;
|
||||
|
||||
static const float* bwOpts(int& count) {
|
||||
static const float OPTS[] = { 7.8f, 10.4f, 15.6f, 20.8f, 31.25f, 41.7f, 62.5f, 125.0f, 250.0f, 500.0f };
|
||||
count = 10;
|
||||
return OPTS;
|
||||
}
|
||||
// Nearest entry in LORA_BW_OPTS to the repeater profile's bw.
|
||||
int rptBwIndex(NodePrefs* p) const {
|
||||
int n; const float* o = bwOpts(n);
|
||||
for (int i = 0; i < n; i++) if (o[i] == p->repeater_bw) return i;
|
||||
return 6; // default to 62.5
|
||||
int best = 0;
|
||||
float best_diff = 1e9f;
|
||||
for (int i = 0; i < LORA_BW_OPT_COUNT; i++) {
|
||||
float diff = fabsf(p->repeater_bw - LORA_BW_OPTS[i]);
|
||||
if (diff < best_diff) { best_diff = diff; best = i; }
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
void buildItems(NodePrefs* p) {
|
||||
@@ -100,13 +100,13 @@ class RepeaterScreen : public UIScreen {
|
||||
const char* rptPresetName(NodePrefs* p) const {
|
||||
for (int i = 0; i < RADIO_PRESET_COUNT; i++) {
|
||||
const RadioPreset& r = RADIO_PRESETS[i];
|
||||
if (r.freq == p->repeater_freq && r.bw == p->repeater_bw && r.sf == p->repeater_sf && r.cr == p->repeater_cr)
|
||||
if (radioParamsMatchPreset(p->repeater_freq, p->repeater_bw, p->repeater_sf, p->repeater_cr, r.freq, r.bw, r.sf, r.cr))
|
||||
return r.name;
|
||||
}
|
||||
for (int i = 0; i < NodePrefs::USER_RADIO_PRESET_MAX; i++) {
|
||||
const NodePrefs::UserRadioPreset& u = p->user_radio_presets[i];
|
||||
if (!u.name[0]) continue;
|
||||
if (u.freq == p->repeater_freq && u.bw == p->repeater_bw && u.sf == p->repeater_sf && u.cr == p->repeater_cr)
|
||||
if (radioParamsMatchPreset(p->repeater_freq, p->repeater_bw, p->repeater_sf, p->repeater_cr, u.freq, u.bw, u.sf, u.cr))
|
||||
return u.name;
|
||||
}
|
||||
return "Custom";
|
||||
@@ -196,7 +196,7 @@ public:
|
||||
display.setCursor(2, y);
|
||||
display.print(itemLabel(item));
|
||||
if (item == IT_RFREQ && sel && _freq_editor.active) {
|
||||
_freq_editor.render(display, valCol(display), y);
|
||||
_freq_editor.render(display, display.valCol(), y);
|
||||
} else {
|
||||
itemValue(item, p, val, sizeof(val));
|
||||
display.drawTextRightAlign(display.width() - reserve - 2, y, val);
|
||||
@@ -209,9 +209,6 @@ public:
|
||||
return (_preset_menu.active || _freq_editor.active) ? 50 : 500;
|
||||
}
|
||||
|
||||
// x where the right-side value column starts (matches Settings' valCol math).
|
||||
static int valCol(DisplayDriver& d) { return d.width() - d.getCharWidth() * 8; }
|
||||
|
||||
bool handleInput(char c) override {
|
||||
NodePrefs* p = _task->getNodePrefs();
|
||||
|
||||
@@ -289,9 +286,9 @@ public:
|
||||
if (left && p->repeater_sf > 5) { p->repeater_sf--; the_mesh.applyRepeaterRadio(); _dirty = true; return true; }
|
||||
}
|
||||
if (item == IT_RBW) {
|
||||
int n; const float* o = bwOpts(n); int idx = rptBwIndex(p);
|
||||
if (right && idx < n - 1) { p->repeater_bw = o[idx + 1]; the_mesh.applyRepeaterRadio(); _dirty = true; return true; }
|
||||
if (left && idx > 0) { p->repeater_bw = o[idx - 1]; the_mesh.applyRepeaterRadio(); _dirty = true; return true; }
|
||||
int idx = rptBwIndex(p);
|
||||
if (right && idx < LORA_BW_OPT_COUNT - 1) { p->repeater_bw = LORA_BW_OPTS[idx + 1]; the_mesh.applyRepeaterRadio(); _dirty = true; return true; }
|
||||
if (left && idx > 0) { p->repeater_bw = LORA_BW_OPTS[idx - 1]; the_mesh.applyRepeaterRadio(); _dirty = true; return true; }
|
||||
}
|
||||
if (item == IT_RCR) {
|
||||
if (right && p->repeater_cr < 8) { p->repeater_cr++; the_mesh.applyRepeaterRadio(); _dirty = true; return true; }
|
||||
|
||||
@@ -102,10 +102,6 @@ class SettingsScreen : public UIScreen {
|
||||
static const int SOUND_COUNT = 4;
|
||||
static const char* AD_SCOPE_LABELS[2];
|
||||
static const int AD_SCOPE_COUNT = 2;
|
||||
// Standard SX126x/SX127x LoRa bandwidths (kHz) — manual BW field cycles through
|
||||
// these rather than a free-form value, so it can't land on an unsupported setting.
|
||||
static const float CUSTOM_BW_OPTS[10];
|
||||
static const int CUSTOM_BW_COUNT = 10;
|
||||
#if FEAT_FULL_REFRESH_SETTING
|
||||
static const char* EINK_FULL_REFRESH_LABELS[5];
|
||||
static const int EINK_FULL_REFRESH_COUNT = 5;
|
||||
@@ -120,7 +116,7 @@ class SettingsScreen : public UIScreen {
|
||||
}
|
||||
|
||||
static bool matchesPreset(NodePrefs* p, float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||
return fabsf(p->freq - freq) < 0.001f && fabsf(p->bw - bw) < 0.001f && p->sf == sf && p->cr == cr;
|
||||
return radioParamsMatchPreset(p->freq, p->bw, p->sf, p->cr, freq, bw, sf, cr);
|
||||
}
|
||||
|
||||
// Display name for the Preset row: a built-in preset, a saved user preset, or
|
||||
@@ -179,14 +175,14 @@ class SettingsScreen : public UIScreen {
|
||||
_task->showAlert("Preset saved", 800);
|
||||
}
|
||||
|
||||
// Nearest entry in CUSTOM_BW_OPTS to the current bw (also used to step left/right).
|
||||
// Nearest entry in LORA_BW_OPTS to the current bw (also used to step left/right).
|
||||
int customBwIndex() {
|
||||
NodePrefs* p = _task->getNodePrefs();
|
||||
float bw = p ? p->bw : 62.5f;
|
||||
int best = 0;
|
||||
float best_diff = 1e9f;
|
||||
for (int i = 0; i < CUSTOM_BW_COUNT; i++) {
|
||||
float diff = fabsf(bw - CUSTOM_BW_OPTS[i]);
|
||||
for (int i = 0; i < LORA_BW_OPT_COUNT; i++) {
|
||||
float diff = fabsf(bw - LORA_BW_OPTS[i]);
|
||||
if (diff < best_diff) { best_diff = diff; best = i; }
|
||||
}
|
||||
return best;
|
||||
@@ -994,8 +990,8 @@ public:
|
||||
}
|
||||
if (_selected == CUSTOM_BW && p) {
|
||||
int idx = customBwIndex();
|
||||
if (right && idx < CUSTOM_BW_COUNT - 1) { p->bw = CUSTOM_BW_OPTS[idx + 1]; _task->applyRadioParams(); _dirty = true; return true; }
|
||||
if (left && idx > 0) { p->bw = CUSTOM_BW_OPTS[idx - 1]; _task->applyRadioParams(); _dirty = true; return true; }
|
||||
if (right && idx < LORA_BW_OPT_COUNT - 1) { p->bw = LORA_BW_OPTS[idx + 1]; _task->applyRadioParams(); _dirty = true; return true; }
|
||||
if (left && idx > 0) { p->bw = LORA_BW_OPTS[idx - 1]; _task->applyRadioParams(); _dirty = true; return true; }
|
||||
}
|
||||
if (_selected == CUSTOM_CR && p) {
|
||||
if (right && p->cr < 8) { p->cr++; _task->applyRadioParams(); _dirty = true; return true; }
|
||||
@@ -1142,4 +1138,3 @@ const char* SettingsScreen::AD_SCOPE_LABELS[2] = { "All", "Zero-hop" };
|
||||
#if FEAT_FULL_REFRESH_SETTING
|
||||
const char* SettingsScreen::EINK_FULL_REFRESH_LABELS[5] = { "off", "5", "10", "20", "30" };
|
||||
#endif
|
||||
const float SettingsScreen::CUSTOM_BW_OPTS[10] = { 7.8f, 10.4f, 15.6f, 20.8f, 31.25f, 41.7f, 62.5f, 125.0f, 250.0f, 500.0f };
|
||||
|
||||
@@ -2125,7 +2125,7 @@ void UITask::applyApc() {
|
||||
|
||||
void UITask::applyRadioParams() {
|
||||
if (_node_prefs == NULL) return;
|
||||
radio_driver.setParams(_node_prefs->freq, _node_prefs->bw, _node_prefs->sf, _node_prefs->cr);
|
||||
the_mesh.applyRepeaterRadio(); // companion params, or the repeater profile if relaying with one set
|
||||
}
|
||||
|
||||
void UITask::applyBrightness() {
|
||||
|
||||
@@ -272,7 +272,7 @@ void Dispatcher::suppressQueuedDuplicate(Packet* pkt) {
|
||||
int n = _mgr->getOutboundTotal();
|
||||
for (int i = 0; i < n; i++) {
|
||||
Packet* q = _mgr->getOutboundByIdx(i);
|
||||
if (q == NULL) continue;
|
||||
if (q == NULL || !q->isRouteFlood()) continue; // only ever suppress a queued flood retransmit
|
||||
uint8_t qh[MAX_HASH_SIZE];
|
||||
q->calculatePacketHash(qh);
|
||||
if (memcmp(h, qh, MAX_HASH_SIZE) == 0) {
|
||||
|
||||
Reference in New Issue
Block a user