From 4369a0cc7453a673d4df8fb43230dc8039a492bd Mon Sep 17 00:00:00 2001 From: MarekZegare4 Date: Sun, 21 Jun 2026 23:09:27 +0200 Subject: [PATCH] fix(repeater): validate profile against chip freq bounds; fix digit-editor padding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit isValidRepeaterProfile() hard-coded the 150-960 MHz SX1262 range, which would wrongly reject legal frequencies on any other chip. Take the freq bounds as parameters and pass radio_driver.getFreqBounds() at both call sites (repeaterProfileValid() and the load-time migration), so the chip's own validated range is the single source of truth. DataStore.cpp gains a target.h include for radio_driver (declared extern there). DigitEditor::render() now zero-pads the integer part to int_digits: the cursor addresses place values (100/10/1/0.1…), so a value with fewer integer digits than int_digits would shift every glyph and misplace the highlight. No visual change for the frequency field (always 3 digits). Co-Authored-By: Claude Opus 4.8 --- examples/companion_radio/DataStore.cpp | 4 +++- examples/companion_radio/MyMesh.h | 6 ++++-- examples/companion_radio/NodePrefs.h | 14 +++++++++----- examples/companion_radio/ui-new/DigitEditor.h | 7 ++++++- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 46a66623..be8b637b 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -1,6 +1,7 @@ #include #include "DataStore.h" #include "Features.h" // FEAT_JOYSTICK_ROTATION_SETTING (else `#if !FEAT_…` is always true) +#include // radio_driver — repeater-profile freq bounds (getFreqBounds) #if defined(EXTRAFS) || defined(QSPIFLASH) #define MAX_BLOBRECS 100 @@ -364,7 +365,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 (!isValidRepeaterProfile(_prefs.repeater_freq, _prefs.repeater_bw, _prefs.repeater_sf, _prefs.repeater_cr)) { + float rpt_lo, rpt_hi; radio_driver.getFreqBounds(rpt_lo, rpt_hi); + if (!isValidRepeaterProfile(_prefs.repeater_freq, _prefs.repeater_bw, _prefs.repeater_sf, _prefs.repeater_cr, rpt_lo, rpt_hi)) { seedDefaultRepeaterProfile(_prefs); } // → 0xC0DE000B: append bot_commands_enabled + quiet-hours. Older files leave diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 82b745b6..e8db12d5 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -222,9 +222,11 @@ public: // pins power to the ceiling. bool apcActive() const { return _prefs.tx_apc && !_prefs.client_repeat; } - // True when the optional repeater radio profile is a valid LoRa config. + // True when the optional repeater radio profile is a valid LoRa config for + // this radio (freq bounds come from the chip's own validated range). bool repeaterProfileValid() const { - return isValidRepeaterProfile(_prefs.repeater_freq, _prefs.repeater_bw, _prefs.repeater_sf, _prefs.repeater_cr); + float lo, hi; radio_driver.getFreqBounds(lo, hi); + return isValidRepeaterProfile(_prefs.repeater_freq, _prefs.repeater_bw, _prefs.repeater_sf, _prefs.repeater_cr, lo, hi); } // Load the radio with the correct params for the current mode: the repeater // profile when relaying with a valid dedicated profile, otherwise the diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 0683c9e3..f5adb32b 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -293,11 +293,15 @@ struct NodePrefs { // persisted to file } }; -// 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 +// Bounds for a usable repeater radio profile. The frequency range is passed in +// by the caller from RadioLibWrapper::getFreqBounds() — the radio chip's own +// validated range — so a profile that passes here is guaranteed to actually take +// effect on the radio, instead of a hard-coded cap (was 150-960, the SX1262 +// range) that would wrongly reject legal frequencies on any other chip. SF/BW/CR +// are the chip-independent discrete LoRa sets. +static inline bool isValidRepeaterProfile(float freq, float bw, uint8_t sf, uint8_t cr, + float min_freq, float max_freq) { + return freq >= min_freq && freq <= max_freq && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7.0f && bw <= 510.0f; diff --git a/examples/companion_radio/ui-new/DigitEditor.h b/examples/companion_radio/ui-new/DigitEditor.h index fe1c3e11..b5eb9599 100644 --- a/examples/companion_radio/ui-new/DigitEditor.h +++ b/examples/companion_radio/ui-new/DigitEditor.h @@ -65,7 +65,12 @@ struct DigitEditor { // selected row (ink DARK on a LIGHT selection bar); restores LIGHT after. void render(DisplayDriver& d, int x, int y) { char buf[16]; - snprintf(buf, sizeof(buf), "%.*f", (int)dec_digits, value); + // Zero-pad the integer part to int_digits so the glyph under the cursor is + // always the place it addresses: the cursor maps place values (100, 10, 1, + // 0.1…), so a value with fewer integer digits than int_digits would shift + // every glyph left and misplace the highlight (e.g. "62.500" vs "062.500"). + const int width = (int)int_digits + (dec_digits > 0 ? 1 + (int)dec_digits : 0); + snprintf(buf, sizeof(buf), "%0*.*f", width, (int)dec_digits, value); const int cw = d.getCharWidth(); const int char_idx = cursor < int_digits ? cursor : cursor + 1; // skip '.' for (int k = 0; buf[k]; k++) {