The `set freq <mhz>` handler saved the new frequency to prefs, then
reverted _prefs->freq to the old value in RAM to keep the running radio
on the old params until reboot. But _prefs is the source of truth for
every savePrefs(), so:
- `get freq` / `get radio` reported the old value right after a
successful `set freq`;
- the next savePrefs() from any other `set` command (e.g. `set rxduty`)
rewrote the old freq back over the persisted new one, losing the
change on reboot.
Mirror the `set radio` handler: keep _prefs->freq = f and freeze the
running radio on the old freq via freezeRadioParams() instead of
reverting the pref. Semantics unchanged (applies at reboot; the live
radio stays on the old freq until then); _prefs now correctly retains
the new value so `get` reflects it and later saves can't clobber it.
freezeRadioParams() is implemented for the Repeater and Room Server
roles; the CommonCLI base default is a no-op, so other roles are
unaffected.
Verified on Seeed XIAO MG24 + Wio-SX1262 (Repeater role, built from
release v20260704.220543 + this patch): `set freq 903.0` is retained by
`get freq` immediately and survives an intervening `set rxduty` + reboot.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a board-aware battery SOC system replacing the two independent
linear approximations that existed in the UI helpers.
New helpers/battery_curve.{h,c}: a 21-point (5% step) OCV lookup table
with integer linear interpolation. The default generic LiPo curve is a
weak symbol — any board can override it by dropping a battery_curve.c
into its board directory. CMakeLists.txt selects the board-specific file
when present, falling back to the generic.
Board.h gains getBattPercent() (default 0); ZephyrBoard implements it
via battery_curve_lookup().
Board-specific curves added for boards with measured cell data:
t1000_e, rak_wismesh_tag, thinknode_m6, sensecap_solar, wio_tracker_l1.
ADC multiplier corrections applied across all nRF52840 boards:
- Boards using a correctly-derived 3600×ratio formula get +0.5% to
compensate for nRF SAADC gain error (7200→7236, 6300→6332, etc.)
- rak_wismesh_tag, rak3401_1watt, gat562_30s had multipliers copied
from Arduino's 3.0V AREF formula; corrected to 3600×1.73×1.005=6259
- xiao_nrf52840 (10911) left unchanged — empirically calibrated value
above the theoretical, assumed already correct for that hardware
get adc.multiplier now reports current mV reading and the board's
curve 100% target, making field calibration self-guiding.
- track real RTC sync source on the joystick Time screen
- gate joystick-only UI helpers behind stub headers so non-joystick builds skip the extra code without #ifdef at every call site.
stolen from Zephyr main:
1. Issue StopTimerOnPreamble=1 before SetRxDutyCycle so the chip's
timer is not reset on every preamble detect (per §13.1 of the
datasheet). Without this, duty cycle effectively never sleeps in
noisy RF and current draw spikes.
2. On IRQ_RX_TX_TIMEOUT during duty-cycle RX, re-arm via
sx126x_restart_rx() instead of falling through to set_sleep().
The old path silently killed duty cycle after the first preamble
false-positive.
3. On recv_duty_cycle(NULL) cancel, wake the radio before issuing
SetStandby — BUSY stays asserted during the sleep phase and the
standby command was being dropped.
Also adds a dc_timeout_restarts atomic counter incremented on the Fix 2
path, exposed end-to-end: sx126x_ext.h accessors → LoRaRadioBase vtable
→ SX126xRadio override → CommonCLICallbacks → RepeaterMesh. Query via
`get dc.restarts` on the repeater CLI; cleared by `clear stats`. High
values indicate a noisy environment or a too-loose preamble threshold.
(+increase ESP BT stack because future zephyr pin advance will trip that mine)
- RegionMap::findMatch(): fix 1-byte stack overflow in tmp[] with bounded memcpy
- Packet::readFrom(): add bounds checks before reading transport codes
- Mesh::sendFlood(): release packet on TRACE type and invalid hash_size early returns
- CompanionMesh: add cleanupSignState() called from BLE disconnect to free sign buffer
- ZephyrCompanionUSB: add 2s timeout for partial V3 frames to prevent parser stall
- ui_mesh_actions: replace volatile bool with Zephyr atomic_t for cross-thread state
- CommonCLI: wrap ~40 fs_read() calls in prefs_read() helper with truncation warning