add cad.busycap knob for hilltop repeaters

This commit is contained in:
liquidraver
2026-07-13 10:29:50 +02:00
parent 9eb33e27dc
commit 432336867b
15 changed files with 119 additions and 32 deletions
+8
View File
@@ -73,6 +73,14 @@ FP has already bottomed out. Probes sample the operating level and **both
neighbours** (op1 more sensitive, op+1 less sensitive; op weighted half, each
neighbour a quarter) so the staircase can read the local curve *slope*:
- **Airtime protection** (highest priority): step up when the operating
level's *total* busy rate — real traffic plus false positives — exceeds
the busy cap (`set cad.busycap`, percent; default **25%**, 0 = off). The
knee logic only minimises *false* busy, but on a congested hilltop most
busy verdicts are real distant traffic we'd win on capture anyway;
deferring for all of it starves the node's own airtime. Self-targeting —
a quiet node's busy rate never reaches the cap, so only a genuinely busy
backbone backs off. Shown as `bc:` in `get cad`.
- **Step up** (less sensitive) when the level above is markedly cleaner —
FP drops ≥5%/level (`CAD_KNEE_SLOPE_PERMILLE`). That means we're on the
steep part below the knee; climb toward it.
+2 -1
View File
@@ -208,7 +208,7 @@ All `set uplink.*` changes are saved immediately and only applied after reboot.
| `get gps duty` | Now-effective GPS duty interval in seconds (`always on (0)` when continuous) |
| `get meshtimesync` | Mesh time-sync state + live dry-run: on/off, eligible voter count, votes for/against, consensus skew and radius, would-be verdict (`ok`/`in-band`/`step±N`/`abstain (reason)`/`hold (reason)`; a recent clock set — manual or GPS — shows as `hold (suppressed)`, and a backward step a forward-only role would refuse is annotated `(skipped: forward-only)`), step counters, suppression countdown, and a per-sender evidence table (`prefix hops count skew E`, `E` = counted toward the verdict above). Entries that count print first, so a size-capped reply never hides the ones that explain the summary; if the table doesn't fully fit, a trailing `+N more` shows how many were left out. Sensing runs even while off, so this works as a dry-run before enabling. Over remote admin the reply is truncated to the packet size (summary always fits); the full table needs the USB CLI. |
| `get dc.restarts` | Duty-cycle preamble false-positive re-arm counter (RxTimeout re-arms + parked-RX watchdog recoveries). High values mean the preamble detector is tripping on noise/interference without real packets arriving — inflates RX-on time and drains battery; packets are never lost to it. Reset by `clear stats`. |
| `get cad` | Adaptive-CAD status + per-level probe statistics: auto on/off, operating detPeak offset and absolute peak (with family base), probe interval, then one line per probed level with probe/busy/fp/tp counts and false-positive rate. Probing runs even while `cad.auto` is off (dry-run), so this is the observation tool for picking a site-appropriate detPeak. See `ADAPTIVE_CAD.md`. Not available on SX127x boards (no hardware CAD). |
| `get cad` | Adaptive-CAD status: header (`a` auto on/off, `o` operating detPeak offset, `pk` absolute peak with family base, `iv` probe interval, `bc` busy cap), then a 3-rung window around the operating offset (`*` marks it) with probe/busy/fp/tp counts and false-positive rate — the three levels the knee controller reads. Probing runs even while `cad.auto` is off (dry-run), so this is the observation tool for picking a site-appropriate detPeak. See `ADAPTIVE_CAD.md`. Not available on SX127x boards (no hardware CAD). |
| `get adc.multiplier` | Battery voltage ADC calibration multiplier |
| `get bootloader.ver` | Bootloader version string |
| `get public.key` | *(USB only)* Node's public key as hex |
@@ -256,6 +256,7 @@ Changes are persisted immediately unless noted. Some require a reboot.
| `set cad.auto <on\|off>` | default **on** | Adaptive CAD: let the staircase controller move the operating detPeak offset based on probe statistics. On by default (repeaters and companions); at the default 15 s probe interval it responds to environment change in ~12 h. Turn off to observe/hand-tune via `get cad` + `set cad.offset`. See `ADAPTIVE_CAD.md`. |
| `set cad.offset <n>` | 8 to 12, default 0 | Operating detPeak offset from the chip family's per-SF base (SX126x: SF+13; LR11xx/LR20xx: 5668 table). Negative = more sensitive LBT (catches weaker signals, risks false busy), positive = less sensitive. Wide range so dense hilltops / quiet valleys can settle far from base. The per-family absolute clamp in the driver (SX126x 1540, LR 4890) is a firmware guardrail against a CAD that never/always fires, not a chip limit (`cadDetPeak` is a full `uint8_t`). Applied live; the auto staircase may move it later if `cad.auto` is on. |
| `set cad.probe.interval <sec>` | 0 (off) or 10255, default **15** | Seconds between calibration CAD probes. Default 15 s → ~12 h staircase response. 0 disables probing entirely (also freezes auto adaptation). |
| `set cad.busycap <pct>` | 0 (off) or 1090, default **25** | Airtime-protection cap: the max percentage of TX attempts the node will let CAD defer before the staircase backs off to a less sensitive detPeak — counting **real** traffic, not just false positives. On a congested hilltop most busy verdicts are distant traffic won on capture anyway, so deferring for all of it starves the node's own airtime. Self-targeting: a quiet node's busy rate never reaches the cap. Shown as `bc:` in `get cad`. 0 disables the cap (pure knee-seeking). |
| `set cad.reset` | | Clear the accumulated per-level CAD probe statistics (RAM only; also cleared automatically on any radio parameter change). |
| `set prv.key <hex>` | 64-char hex (32-byte key) | Replace private key; derive new identity *(reboot to apply)* |
@@ -740,6 +740,15 @@ void ZephyrDataStore::loadPrefs(NodePrefs &prefs)
prefs.cad_probe_interval = 10;
}
}
/* Offset 158: cad_busycap (ZephCore extension, percent; 0 = off).
* Absent in pre-existing files keep the in-RAM default (25). */
if (off < len) {
prefs.cad_busycap = buf[off++];
if (prefs.cad_busycap > 90) {
prefs.cad_busycap = 90;
}
}
}
void ZephyrDataStore::savePrefs(const NodePrefs &prefs)
@@ -827,7 +836,9 @@ void ZephyrDataStore::savePrefs(const NodePrefs &prefs)
buf[off++] = (uint8_t)prefs.cad_offset;
/* Offset 157: cad_probe_interval (ZephCore extension, seconds) */
buf[off++] = prefs.cad_probe_interval;
/* Total: 158 bytes */
/* Offset 158: cad_busycap (ZephCore extension, percent) */
buf[off++] = prefs.cad_busycap;
/* Total: 159 bytes */
bool ok = atomicReplaceFile(PREFS_FILE, buf, off);
LOG_DBG("savePrefs: wrote %s, ok=%d (%d bytes), name='%.16s'",
+54 -21
View File
@@ -48,6 +48,7 @@ LoRaRadioBase::LoRaRadioBase(const struct device *lora_dev, MainBoard &board,
_rx_head(0), _rx_tail(0),
_noise_floor(DEFAULT_NOISE_FLOOR), _calibration_threshold(0), _ema_unguarded(0),
_cad_auto(false), _cad_offset(0), _cad_probe_interval_s(0),
_cad_busycap_pct(0),
_cad_last_probe_ms(0), _cad_last_decay_ms(0), _cad_probe_rr(0),
_rx_duty_cycle_enabled(IS_ENABLED(CONFIG_ZEPHCORE_LORA_RX_DUTY_CYCLE)),
_rx_boost_enabled(true),
@@ -948,7 +949,7 @@ bool LoRaRadioBase::isChannelActive(int threshold)
/* ── Adaptive CAD (LBT detPeak calibration) ───────────────────────────── */
void LoRaRadioBase::setCadParams(bool auto_enabled, int8_t offset,
uint16_t probe_interval_s)
uint16_t probe_interval_s, uint8_t busycap_pct)
{
if (offset < CAD_LEVEL_MIN) offset = CAD_LEVEL_MIN;
if (offset > CAD_LEVEL_MAX) offset = CAD_LEVEL_MAX;
@@ -956,10 +957,12 @@ void LoRaRadioBase::setCadParams(bool auto_enabled, int8_t offset,
_cad_auto = auto_enabled;
_cad_offset = offset;
_cad_probe_interval_s = probe_interval_s;
_cad_busycap_pct = busycap_pct;
hwCadSetPeakOffset(_cad_offset);
LOG_INF("cad: auto=%d offset=%d probe_interval=%us",
(int)auto_enabled, (int)offset, (unsigned)probe_interval_s);
LOG_INF("cad: auto=%d offset=%d probe_interval=%us busycap=%u%%",
(int)auto_enabled, (int)offset, (unsigned)probe_interval_s,
(unsigned)busycap_pct);
}
void LoRaRadioBase::resetCadStats()
@@ -1014,24 +1017,49 @@ void LoRaRadioBase::cadStaircaseStep()
* bottomed out), using slopes so the decision is site-floor-independent. */
int oi = _cad_offset - CAD_LEVEL_MIN;
/* Per-level FP rate in permille, or -1 when too few samples to trust. */
auto rate = [&](int idx) -> int {
if (idx < 0 || idx >= CAD_NUM_LEVELS) {
auto warm = [&](int idx) -> bool {
return idx >= 0 && idx < CAD_NUM_LEVELS &&
_cad_stats[idx].probes >= CAD_STEP_MIN_PROBES;
};
/* Per-level FALSE-positive rate in permille, or -1 when too few samples. */
auto fp_rate = [&](int idx) -> int {
if (!warm(idx)) {
return -1;
}
CadLevelStats &s = _cad_stats[idx];
if (s.probes < CAD_STEP_MIN_PROBES) {
return (int)(((uint32_t)_cad_stats[idx].fp * 1000U)
/ _cad_stats[idx].probes);
};
/* Per-level TOTAL busy (defer) rate in permille — false + real traffic. */
auto busy_rate = [&](int idx) -> int {
if (!warm(idx)) {
return -1;
}
return (int)(((uint32_t)s.fp * 1000U) / s.probes);
return (int)(((uint32_t)_cad_stats[idx].busy * 1000U)
/ _cad_stats[idx].probes);
};
int r_op = rate(oi);
int r_op = fp_rate(oi);
if (r_op < 0) {
return; /* operating level not warm yet — no basis to step */
}
int r_up = rate(oi + 1); /* one step less sensitive */
int r_dn = rate(oi - 1); /* frontier, one step more sensitive */
int b_op = busy_rate(oi);
int r_up = fp_rate(oi + 1); /* one step less sensitive */
int r_dn = fp_rate(oi - 1); /* frontier, one step more sensitive */
/* Airtime protection (highest priority): if the operating level defers
* too large a fraction of TX attempts real traffic included back off
* to a less sensitive detPeak. On a congested hilltop most of that busy
* is distant traffic we'd win on capture anyway; deferring for all of it
* just starves our own airtime. Cap is `set cad.busycap` percent (0 =
* off); only binds on genuinely busy channels. */
int cap_permille = (int)_cad_busycap_pct * 10;
if (cap_permille && _cad_offset < CAD_LEVEL_MAX && b_op > cap_permille) {
_cad_offset++;
hwCadSetPeakOffset(_cad_offset);
LOG_INF("cad: step up -> offset %d (airtime, busy %d cap %d)",
(int)_cad_offset, b_op, cap_permille);
return;
}
/* Step UP (less sensitive) when the level above is markedly cleaner —
* we're on the steep part of the curve, below the knee. */
@@ -1047,15 +1075,19 @@ void LoRaRadioBase::cadStaircaseStep()
/* Step DOWN (more sensitive) only on a flat plateau that is already
* clean: the frontier is no worse than operating (nothing to lose) AND
* FP here is low enough that reclaiming sensitivity is cheap. The clean
* guard is what keeps a flat-but-noisy curve from descending to the
* sensitive rail there, holding position is the least-bad move. */
* guard keeps a flat-but-noisy curve from descending to the sensitive
* rail; the busy-hysteresis guard keeps us from descending into the
* airtime cap and bouncing straight back up. */
int b_dn = busy_rate(oi - 1);
bool busy_ok = (cap_permille == 0) ||
(b_dn <= cap_permille - CAD_BUSY_DEFER_HYST_PERMILLE);
if (_cad_offset > CAD_LEVEL_MIN && r_dn >= 0 &&
r_dn - r_op < CAD_KNEE_SLOPE_PERMILLE &&
r_op <= CAD_PLATEAU_CLEAN_PERMILLE) {
r_op <= CAD_PLATEAU_CLEAN_PERMILLE && busy_ok) {
_cad_offset--;
hwCadSetPeakOffset(_cad_offset);
LOG_INF("cad: step down -> offset %d (op %d dn %d)",
(int)_cad_offset, r_op, r_dn);
LOG_INF("cad: step down -> offset %d (op %d dn %d busy %d)",
(int)_cad_offset, r_op, r_dn, b_dn);
return;
}
@@ -1199,16 +1231,17 @@ int LoRaRadioBase::formatCadStatus(char *buf, int cap)
}
/* Terse on purpose — remote replies are capped at ~160 B over LoRa.
* Header: a:on o:1 pk:22(b21/4s) iv:15s
* Header: a:on o:1 pk:22(b21/4s) iv:15s bc:25%
* a auto on/off o offset pk operating peak
* b family base 4s symbols iv probe interval
* b family base 4s symbols iv probe interval bc busy cap
* Level: *+1(22) 22p 18b 16f 2t 72%
* '*' = operating rung level(peak) probes busy fp tp fp-rate%%. */
n += snprintf(buf + n, cap > n ? cap - n : 0,
"a:%s o:%d pk:%d(b%u/4s) iv:%us",
"a:%s o:%d pk:%d(b%u/4s) iv:%us bc:%u%%",
_cad_auto ? "on" : "off", (int)_cad_offset,
(int)base + _cad_offset, base,
(unsigned)_cad_probe_interval_s);
(unsigned)_cad_probe_interval_s,
(unsigned)_cad_busycap_pct);
/* Only the 3 rungs around the operating offset — the far rungs are mildly
* irrelevant; what matters is where we sit on the ladder. The window is
+2 -1
View File
@@ -111,7 +111,7 @@ public:
/* Adaptive CAD (LBT detPeak calibration) */
void setCadParams(bool auto_enabled, int8_t offset,
uint16_t probe_interval_s) override;
uint16_t probe_interval_s, uint8_t busycap_pct) override;
void cadMaintenance() override;
int8_t getCadOffset() const override { return _cad_offset; }
void resetCadStats() override;
@@ -211,6 +211,7 @@ protected:
bool _cad_auto; /* staircase acts on the stats */
int8_t _cad_offset; /* operating detPeak offset (levels) */
uint16_t _cad_probe_interval_s; /* 0 = probing disabled */
uint8_t _cad_busycap_pct; /* airtime cap: max % TX deferred (0 = off) */
int64_t _cad_last_probe_ms;
int64_t _cad_last_decay_ms;
uint8_t _cad_probe_rr; /* round-robin index (sweep) / frontier mix counter */
+11
View File
@@ -54,6 +54,17 @@
#define CAD_KNEE_SLOPE_PERMILLE 50 /* >=5%/level FP change = steep */
#define CAD_PLATEAU_CLEAN_PERMILLE 50 /* <=5% FP = clean enough to descend */
#define CAD_STEP_MIN_PROBES 120 /* per-level samples before a step call */
/* Airtime-protection cap on the TOTAL busy (defer) rate — false positives AND
* real traffic. The knee controller only minimises *false* busy, but on a
* congested hilltop most busy verdicts are real distant traffic we'd never
* actually collide with (capture effect), and deferring for all of it starves
* the node's own airtime. When the operating level's busy rate exceeds the
* cap, step UP (less sensitive) regardless of FP self-targeting, since a
* quiet node's busy rate never reaches it. HYST keeps a descend from bouncing
* straight back into the cap. The cap itself is a per-node pref
* (`cad_busycap`, percent, `set cad.busycap`; default 25, 0 = off) since it is
* a policy call (airtime vs. collision/capture), not a physical constant. */
#define CAD_BUSY_DEFER_HYST_PERMILLE 100 /* descend only if frontier busy <= cap-10% */
#define CAD_PROBE_RSSI_GUARD 7 /* dB above floor = channel visibly busy, skip probe */
#define CAD_STATS_DECAY_MS (6UL * 3600UL * 1000UL) /* halve counters every 6 h */
+5 -1
View File
@@ -208,6 +208,8 @@ bool RepeaterDataStore::loadPrefs(NodePrefs& prefs) {
fs_read(&file, &prefs.cad_auto, sizeof(prefs.cad_auto));
fs_read(&file, &prefs.cad_offset, sizeof(prefs.cad_offset));
fs_read(&file, &prefs.cad_probe_interval, sizeof(prefs.cad_probe_interval));
/* cad_busycap absent in <301-byte files; EOF read keeps default 25 */
fs_read(&file, &prefs.cad_busycap, sizeof(prefs.cad_busycap));
fs_close(&file);
@@ -242,6 +244,7 @@ bool RepeaterDataStore::loadPrefs(NodePrefs& prefs) {
if (prefs.cad_auto > 1) prefs.cad_auto = 0;
if (prefs.cad_offset < CAD_OFFSET_MIN || prefs.cad_offset > CAD_OFFSET_MAX) prefs.cad_offset = 0;
if (prefs.cad_probe_interval != 0 && prefs.cad_probe_interval < 10) prefs.cad_probe_interval = 10;
if (prefs.cad_busycap > 90) prefs.cad_busycap = 90;
/* One-time format upgrade: old files (< 294 bytes) never saved the ZephCore
* extension fields, and stored path_hash_mode/loop_detect as zero padding.
@@ -341,10 +344,11 @@ bool RepeaterDataStore::savePrefs(const NodePrefs& prefs) {
fs_write(&file, &prefs.flood_max_advert, sizeof(prefs.flood_max_advert));
/* Mesh time sync on/off (offset 296) */
fs_write(&file, &prefs.meshtimesync, sizeof(prefs.meshtimesync));
/* Adaptive CAD (offsets 297-299) */
/* Adaptive CAD (offsets 297-300) */
fs_write(&file, &prefs.cad_auto, sizeof(prefs.cad_auto));
fs_write(&file, &prefs.cad_offset, sizeof(prefs.cad_offset));
fs_write(&file, &prefs.cad_probe_interval, sizeof(prefs.cad_probe_interval));
fs_write(&file, &prefs.cad_busycap, sizeof(prefs.cad_busycap));
ret = fs_sync(&file);
fs_close(&file);
+1 -1
View File
@@ -185,7 +185,7 @@ protected:
}
void applyCadPrefs() override {
_radio->setCadParams(_prefs.cad_auto != 0, _prefs.cad_offset,
_prefs.cad_probe_interval);
_prefs.cad_probe_interval, _prefs.cad_busycap);
}
void resetCadStats() override {
_radio->resetCadStats();
+1 -1
View File
@@ -154,7 +154,7 @@ protected:
}
void applyCadPrefs() override {
_radio->setCadParams(_prefs.cad_auto != 0, _prefs.cad_offset,
_prefs.cad_probe_interval);
_prefs.cad_probe_interval, _prefs.cad_busycap);
}
void resetCadStats() override {
_radio->resetCadStats();
+13
View File
@@ -124,6 +124,7 @@ void CommonCLI::loadPrefs(const char* path) {
ok = ok && prefs_read(&file, &_prefs->cad_auto, sizeof(_prefs->cad_auto)); // 297
ok = ok && prefs_read(&file, &_prefs->cad_offset, sizeof(_prefs->cad_offset)); // 298
ok = ok && prefs_read(&file, &_prefs->cad_probe_interval, sizeof(_prefs->cad_probe_interval)); // 299
ok = ok && prefs_read(&file, &_prefs->cad_busycap, sizeof(_prefs->cad_busycap)); // 300
if (!ok) {
LOG_WRN("Prefs file %s truncated, some fields use defaults", path);
@@ -175,6 +176,7 @@ void CommonCLI::loadPrefs(const char* path) {
if (_prefs->cad_probe_interval != 0 && _prefs->cad_probe_interval < 10) {
_prefs->cad_probe_interval = 10;
}
_prefs->cad_busycap = constrain(_prefs->cad_busycap, (uint8_t)0, (uint8_t)90);
LOG_INF("Loaded prefs from %s", path);
}
@@ -246,6 +248,7 @@ void CommonCLI::savePrefs(const char* path) {
fs_write(&file, &_prefs->cad_auto, sizeof(_prefs->cad_auto));
fs_write(&file, &_prefs->cad_offset, sizeof(_prefs->cad_offset));
fs_write(&file, &_prefs->cad_probe_interval, sizeof(_prefs->cad_probe_interval));
fs_write(&file, &_prefs->cad_busycap, sizeof(_prefs->cad_busycap));
fs_close(&file);
LOG_INF("Saved prefs to %s", path);
@@ -682,6 +685,16 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
savePrefs();
strcpy(reply, "OK");
}
} else if (memcmp(config, "cad.busycap ", 12) == 0) {
int val = atoi(&config[12]);
if (val != 0 && (val < 10 || val > 90)) {
strcpy(reply, "Error: busycap is 0 (off) or 10-90 percent");
} else {
_prefs->cad_busycap = (uint8_t)val;
_callbacks->applyCadPrefs();
savePrefs();
strcpy(reply, "OK");
}
} else if (memcmp(config, "cad.reset", 9) == 0) {
_callbacks->resetCadStats();
strcpy(reply, "OK - CAD probe stats cleared");
+2
View File
@@ -80,6 +80,7 @@ struct NodePrefs {
uint8_t cad_auto; // 1 = adaptive-CAD staircase acts on probe stats (default off = dry-run)
int8_t cad_offset; // operating detPeak offset from family base (-4..4)
uint8_t cad_probe_interval; // seconds between CAD probes (0 = probing off, default 60)
uint8_t cad_busycap; // airtime-protection: max % of TX attempts deferred before backing off detPeak (0 = off, default 25)
/* ---- Companion-only fields ---- */
uint8_t manual_add_contacts;
@@ -154,6 +155,7 @@ static inline void initNodePrefs(NodePrefs* prefs) {
prefs->cad_auto = 1; // Default ON — adaptive staircase acts on probe stats
prefs->cad_offset = 0; // Start at family base detPeak (SF+13 on SX126x)
prefs->cad_probe_interval = 15; // 15 s → staircase responds to change in ~1-2 h
prefs->cad_busycap = 25; // back off detPeak once >25% of TX attempts are deferred
prefs->wake_on_msg = 1; // Default ON — wake display when message arrives
prefs->v_contact_enabled = 1; // Default ON — v-contact loopback admin chat (companion)
prefs->v_battery_alert_mv = 0xFFFF; // Sentinel: derive from board auto-shutdown threshold
+2 -1
View File
@@ -44,8 +44,9 @@ public:
/* Adaptive CAD (LBT detPeak calibration). Default no-ops for radios
* without hardware CAD (SX127x). */
virtual void setCadParams(bool auto_enabled, int8_t offset,
uint16_t probe_interval_s) {
uint16_t probe_interval_s, uint8_t busycap_pct) {
(void)auto_enabled; (void)offset; (void)probe_interval_s;
(void)busycap_pct;
}
/* One housekeeping tick of the CAD calibrator: maybe run a probe,
* update stats, maybe step the staircase (auto mode). */
+4 -2
View File
@@ -719,7 +719,8 @@ public:
void applyCadPrefs() override {
lora_radio.setCadParams(companion_mesh.prefs.cad_auto != 0,
companion_mesh.prefs.cad_offset,
companion_mesh.prefs.cad_probe_interval);
companion_mesh.prefs.cad_probe_interval,
companion_mesh.prefs.cad_busycap);
}
void resetCadStats() override {
lora_radio.resetCadStats();
@@ -1567,7 +1568,8 @@ int main(void)
lora_radio.enableRxDutyCycle(companion_mesh.prefs.rx_duty_cycle != 0);
lora_radio.setCadParams(companion_mesh.prefs.cad_auto != 0,
companion_mesh.prefs.cad_offset,
companion_mesh.prefs.cad_probe_interval);
companion_mesh.prefs.cad_probe_interval,
companion_mesh.prefs.cad_busycap);
#ifdef CONFIG_ZEPHCORE_APC
ui_set_radio_runtime(
lora_radio.getEffectiveTxPower(),
+1 -1
View File
@@ -631,7 +631,7 @@ int main(void)
lora_radio.setRxBoost(prefs->rx_boost != 0);
lora_radio.enableRxDutyCycle(prefs->rx_duty_cycle != 0);
lora_radio.setCadParams(prefs->cad_auto != 0, prefs->cad_offset,
prefs->cad_probe_interval);
prefs->cad_probe_interval, prefs->cad_busycap);
/* Feed initial UI state from loaded prefs */
ui_set_node_name(prefs->node_name);
+1 -1
View File
@@ -610,7 +610,7 @@ int main(void)
lora_radio.setRxBoost(prefs->rx_boost != 0);
lora_radio.enableRxDutyCycle(prefs->rx_duty_cycle != 0);
lora_radio.setCadParams(prefs->cad_auto != 0, prefs->cad_offset,
prefs->cad_probe_interval);
prefs->cad_probe_interval, prefs->cad_busycap);
/* Feed initial UI state from loaded prefs */
ui_set_node_name(prefs->node_name);