2025-06-01 09:25:17 -07:00
|
|
|
|
#pragma once
|
refactor(ui): apply code review fixes — bugs, risk, optimisations, cleanup
Fixes 7 bugs (timing UB on unsigned long, abs() no-op, XBM CRC PROGMEM
scan, CayenneLPP heap thrash ×2, UTF-8 reply prefix, lock-seq reset on
wake), 3 risk items (notif melody stop guard, bot trigger pre-lowercase,
lock-seq cleared on display wake), plus optimisations (fmtMsgAge/
getTextWidth caching, RTTTL builder consolidated to NodePrefs, HP_*
constants and homePageLabel table moved to NodePrefs, DataStore flat
lambda schema, translateUTF8Static factored out) and minor cleanup.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 20:03:45 +02:00
|
|
|
|
#include <cstdint>
|
|
|
|
|
|
#include <stdio.h>
|
2025-04-20 17:40:58 -07:00
|
|
|
|
|
feat(repeater): politeness controls, dedicated radio profile, and diagnostics
Settings > Radio gains a repeater toggle, 16 community-suggested radio
presets plus manual Freq/SF/BW/CR tuning (digit-by-digit Freq editor),
4 persisted user preset slots, and the packet pool bumped 16->32 so
queued retransmits stop starving incoming-packet allocation while
relaying.
Four opt-in politeness knobs (skip-advert, max-hops, yield, min-SNR),
all flood-only and off by default, plus overhear suppression that
cancels a queued retransmit if a peer relays the same packet first.
Adaptive Power Control is suppressed while relaying (pins TX power to
the ceiling) and duty-cycle RX is forced off, since a repeater needs
to hear and relay at consistent power.
A dedicated Tools > Repeater screen consolidates the toggle, the
politeness knobs, and live forwarding stats, plus an optional "Custom"
radio profile — a dedicated frequency/SF/BW/CR for relaying, separate
from the companion's own network, band-matched to the companion
frequency by default and used everywhere a radio change can happen
(boot, on-device toggle, app-driven CMD_SET_RADIO_PARAMS, Settings
radio edits) so the device never silently falls back to the wrong
params mid-relay.
Diagnostics gains the actually-forwarded packet count, real heap-free
via mallinfo(), a reset-counters popup, and hardened loop-detect
bounds. Also: a frequency-floor fix and a float-equality preset-match
fix in the repeater profile logic, deduped BW-table/valCol/profile-
seeding helpers shared between Settings and the Repeater screen, and
a build.sh fix tolerating control characters in `pio project config`'s
JSON dump.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 09:07:00 +02:00
|
|
|
|
// 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-01 20:41:04 -07:00
|
|
|
|
#define TELEM_MODE_DENY 0
|
|
|
|
|
|
#define TELEM_MODE_ALLOW_FLAGS 1 // use contact.flags
|
|
|
|
|
|
#define TELEM_MODE_ALLOW_ALL 2
|
2025-05-05 11:21:55 +10:00
|
|
|
|
|
2025-06-22 16:21:04 +10:00
|
|
|
|
#define ADVERT_LOC_NONE 0
|
|
|
|
|
|
#define ADVERT_LOC_SHARE 1
|
|
|
|
|
|
|
2026-06-07 10:53:17 +02:00
|
|
|
|
#define ADVERT_SOUND_SCOPE_ALL 0
|
|
|
|
|
|
#define ADVERT_SOUND_SCOPE_ZERO_HOP 1
|
|
|
|
|
|
|
2025-06-01 20:41:04 -07:00
|
|
|
|
struct NodePrefs { // persisted to file
|
2025-04-20 17:40:58 -07:00
|
|
|
|
float airtime_factor;
|
|
|
|
|
|
char node_name[32];
|
|
|
|
|
|
float freq;
|
|
|
|
|
|
uint8_t sf;
|
|
|
|
|
|
uint8_t cr;
|
2025-07-16 19:25:28 +10:00
|
|
|
|
uint8_t multi_acks;
|
2025-04-20 17:40:58 -07:00
|
|
|
|
uint8_t manual_add_contacts;
|
|
|
|
|
|
float bw;
|
2026-01-03 20:35:14 +01:00
|
|
|
|
int8_t tx_power_dbm;
|
2025-05-05 11:21:55 +10:00
|
|
|
|
uint8_t telemetry_mode_base;
|
|
|
|
|
|
uint8_t telemetry_mode_loc;
|
2025-05-22 15:26:30 +10:00
|
|
|
|
uint8_t telemetry_mode_env;
|
2025-04-20 17:40:58 -07:00
|
|
|
|
float rx_delay_base;
|
|
|
|
|
|
uint32_t ble_pin;
|
2025-06-22 16:21:04 +10:00
|
|
|
|
uint8_t advert_loc_policy;
|
2025-11-20 18:55:39 -08:00
|
|
|
|
uint8_t buzzer_quiet;
|
2026-05-12 09:17:37 +02:00
|
|
|
|
uint8_t buzzer_volume; // 0=min..4=max, default 4
|
2025-11-29 16:37:10 +08:00
|
|
|
|
uint8_t gps_enabled; // GPS enabled flag (0=disabled, 1=enabled)
|
|
|
|
|
|
uint32_t gps_interval; // GPS read interval in seconds
|
2026-01-13 00:38:20 +11:00
|
|
|
|
uint8_t autoadd_config; // bitmask for auto-add contacts config
|
2026-03-05 18:14:47 +00:00
|
|
|
|
uint8_t rx_boosted_gain; // SX126x RX boosted gain mode (0=power saving, 1=boosted)
|
2026-02-14 15:50:06 +11:00
|
|
|
|
uint8_t client_repeat;
|
2026-02-23 18:26:56 +11:00
|
|
|
|
uint8_t path_hash_mode; // which path mode to use when sending
|
2026-03-03 09:05:53 +01:00
|
|
|
|
uint8_t autoadd_max_hops; // 0 = no limit, 1 = direct (0 hops), N = up to N-1 hops (max 64)
|
2026-04-13 23:11:21 +10:00
|
|
|
|
char default_scope_name[31];
|
|
|
|
|
|
uint8_t default_scope_key[16];
|
2026-05-10 15:18:35 +02:00
|
|
|
|
uint8_t display_brightness; // 0=min..4=max, default 2 (medium)
|
|
|
|
|
|
uint16_t auto_off_secs; // display auto-off: 0=never, else seconds (default 15)
|
2026-05-17 09:55:51 +02:00
|
|
|
|
uint8_t auto_lock; // 0=disabled, 1=lock screen when display turns off
|
2026-05-10 15:18:35 +02:00
|
|
|
|
int8_t tz_offset_hours; // timezone offset from UTC, -12..+14 (default 0)
|
|
|
|
|
|
uint16_t low_batt_mv; // auto-shutdown threshold: 0=disabled, 3000-3500 mV
|
|
|
|
|
|
uint8_t batt_display_mode; // 0=icon, 1=percent, 2=voltage
|
2026-05-11 16:52:24 +02:00
|
|
|
|
char custom_msgs[10][140]; // user-defined quick messages (supports {loc}, {time})
|
2026-05-11 18:56:26 +02:00
|
|
|
|
uint64_t ch_notif_override; // bitmask: bit i = channel i has explicit notification setting
|
|
|
|
|
|
uint64_t ch_notif_muted; // bitmask: bit i = channel i muted (only if override bit set)
|
2026-05-11 19:19:44 +02:00
|
|
|
|
uint8_t dm_show_all; // 0=favourites only (default), 1=all chat contacts
|
|
|
|
|
|
uint8_t room_fav_only; // 0=all room servers (default), 1=favourites only
|
Add ringtone editor, buzzer volume, unread fixes and buzzer notification fixes
- RingtoneEditorScreen: 32-note step sequencer accessible via new Tools page on home screen. U/D=pitch, ENTER=octave cycle, L/R=cursor, MENU=options (play, duration, BPM, insert/delete, save). Notes stored packed in NodePrefs and persisted via DataStore.
- ToolsScreen: new "Tools" home page entry launching the editor
- Buzzer volume: 0-4 bar control in Settings > Sound, persisted to prefs
- Buzzer notifications: fixed notify() firing inside wrong serial-guard branch and before addChannelMsg set the channel index
- Unread counter: fixed newest-first index direction; watermark formula now correct
- OLED brightness: wider range via pre-charge register 0xD9 combined with contrast
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 09:40:37 +02:00
|
|
|
|
uint8_t ringtone_bpm_idx; // index into {60,90,120,150,180}
|
|
|
|
|
|
uint8_t ringtone_len; // number of notes in custom ringtone (0 = use default)
|
|
|
|
|
|
uint8_t ringtone_notes[32]; // packed: bits0-2=pitch, bits3-4=octave-4, bits5-6=dur_idx
|
2026-05-12 09:51:30 +02:00
|
|
|
|
uint16_t home_pages_mask; // bitmask of visible home pages (bit0=Clock..bit8=Shutdown); 0=all visible
|
2026-05-12 20:12:54 +02:00
|
|
|
|
uint8_t bot_enabled; // 0=disabled, 1=DM bot active (responds to all DMs)
|
|
|
|
|
|
uint8_t bot_channel_enabled; // 0=disabled, 1=channel bot active for bot_channel_idx
|
2026-06-29 18:17:38 +02:00
|
|
|
|
uint8_t bot_channel_idx; // channel index for channel bot [del→onChannelRemoved]
|
feat(bot): hardening + auto-responder, query commands, quiet hours
Reply-bot overhaul on top of the trigger/reply auto-reply:
Robustness
- single botTriggerMatches() shared by DM/channel paths; named constants
(BOT_REPLY_COOLDOWN_MS / BOT_SCRATCH) replace magic numbers.
- per-contact DM throttle (8-entry ring) so a second sender isn't starved
while one contact is on cooldown.
- channel anti-loop: skip only when an incoming message equals our own reply
(was: any reply containing the trigger word — which silently killed channel
replies for the common "trigger word in reply" setup).
Features
- away / reply-to-all: a lone "*" trigger matches every message.
- independent DM vs channel triggers (bot_trigger / bot_trigger_ch); "*" works
on the channel too, bounded by cooldown + echo guard.
- query commands: a DM or monitored-channel message is scanned for "!word"
tokens — !ping/!batt/!loc/!time/!temp/!hops/!status/!help — merged into one
combined reply. DM = per-contact throttle, ignores quiet hours; channel =
broadcast, per-channel cooldown, respects quiet hours. !hops uses
getPathHashCount() (0 = direct).
- quiet hours (bot_quiet_start/end, local, wraps midnight) silence push replies.
- reply counter shown in the BotScreen header.
UI / storage
- BotScreen: 9 rows, scrolling list (no more cramming), field I/O via
fieldBuf()/fieldCap().
- prefs bot_commands_enabled / bot_quiet_start / bot_quiet_end / bot_trigger_ch
persisted; schema 0xC0DE000A → 0xC0DE000C with clamping + migration
(bot_trigger_ch seeded from bot_trigger on upgrade).
- docs: tools_screen bot section rewritten; FEATURES.md refreshed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 11:57:27 +02:00
|
|
|
|
char bot_trigger[64]; // DM trigger phrase (case-insensitive contains; "*" = any DM)
|
2026-05-12 20:12:54 +02:00
|
|
|
|
char bot_reply_dm[140]; // auto-reply text for DM
|
|
|
|
|
|
char bot_reply_ch[140]; // auto-reply text for channel
|
feat(bot): hardening + auto-responder, query commands, quiet hours
Reply-bot overhaul on top of the trigger/reply auto-reply:
Robustness
- single botTriggerMatches() shared by DM/channel paths; named constants
(BOT_REPLY_COOLDOWN_MS / BOT_SCRATCH) replace magic numbers.
- per-contact DM throttle (8-entry ring) so a second sender isn't starved
while one contact is on cooldown.
- channel anti-loop: skip only when an incoming message equals our own reply
(was: any reply containing the trigger word — which silently killed channel
replies for the common "trigger word in reply" setup).
Features
- away / reply-to-all: a lone "*" trigger matches every message.
- independent DM vs channel triggers (bot_trigger / bot_trigger_ch); "*" works
on the channel too, bounded by cooldown + echo guard.
- query commands: a DM or monitored-channel message is scanned for "!word"
tokens — !ping/!batt/!loc/!time/!temp/!hops/!status/!help — merged into one
combined reply. DM = per-contact throttle, ignores quiet hours; channel =
broadcast, per-channel cooldown, respects quiet hours. !hops uses
getPathHashCount() (0 = direct).
- quiet hours (bot_quiet_start/end, local, wraps midnight) silence push replies.
- reply counter shown in the BotScreen header.
UI / storage
- BotScreen: 9 rows, scrolling list (no more cramming), field I/O via
fieldBuf()/fieldCap().
- prefs bot_commands_enabled / bot_quiet_start / bot_quiet_end / bot_trigger_ch
persisted; schema 0xC0DE000A → 0xC0DE000C with clamping + migration
(bot_trigger_ch seeded from bot_trigger on upgrade).
- docs: tools_screen bot section rewritten; FEATURES.md refreshed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 11:57:27 +02:00
|
|
|
|
char bot_trigger_ch[64]; // channel trigger phrase (independent of DM; "*" = any channel msg)
|
|
|
|
|
|
uint8_t bot_commands_enabled; // 0=off, 1=answer !ping/!batt/!loc/!time/!help DM commands
|
|
|
|
|
|
uint8_t bot_quiet_start; // quiet-hours start hour, local 0-23 (start==end → disabled)
|
|
|
|
|
|
uint8_t bot_quiet_end; // quiet-hours end hour, local 0-23
|
2026-05-12 20:12:54 +02:00
|
|
|
|
uint8_t clock_hide_seconds; // 0=show HH:MM:SS/refresh 1s (default), 1=hide/refresh 60s
|
2026-05-21 21:04:43 +02:00
|
|
|
|
uint8_t clock_12h; // 0=24h (default), 1=12h with AM/PM
|
2026-05-12 20:12:54 +02:00
|
|
|
|
uint8_t buzzer_auto; // 0=manual (default), 1=auto-mute when BT connected
|
2026-05-13 10:48:42 +02:00
|
|
|
|
struct DmNotifEntry { uint8_t prefix[4]; uint8_t state; }; // state: 0=default,1=muted,2=force-on
|
|
|
|
|
|
static const int DM_NOTIF_TABLE_MAX = 16;
|
2026-06-29 18:17:38 +02:00
|
|
|
|
DmNotifEntry dm_notif[DM_NOTIF_TABLE_MAX]; // 16*5 = 80 bytes [del→onContactRemoved]
|
2026-05-27 22:55:50 +02:00
|
|
|
|
uint8_t dashboard_fields[3]; // 0=None,1=Batt V,2=Temp,3=Hum,4=Pres,5=GPS,6=Alt,7=Lux,8=CO2,9=Nodes,10=Msgs,11=Batt %
|
2026-05-14 16:08:33 +02:00
|
|
|
|
uint32_t advert_auto_interval_sec; // periodic 0-hop advert with GPS: 0=off, else seconds
|
2026-05-15 11:43:38 +02:00
|
|
|
|
// Second melody slot (same packing as ringtone_*)
|
|
|
|
|
|
uint8_t ringtone2_bpm_idx;
|
|
|
|
|
|
uint8_t ringtone2_len;
|
|
|
|
|
|
uint8_t ringtone2_notes[32];
|
2026-06-07 09:13:02 +02:00
|
|
|
|
// Global melodies for notifications: 0=built-in, 1=melody1, 2=melody2, 3=none
|
2026-05-15 11:43:38 +02:00
|
|
|
|
uint8_t notif_melody_dm;
|
|
|
|
|
|
uint8_t notif_melody_ch;
|
2026-06-04 08:46:15 +02:00
|
|
|
|
uint8_t notif_melody_ad;
|
2026-06-07 10:53:17 +02:00
|
|
|
|
// Advert sound filter: 0=all adverts, 1=zero-hop only
|
|
|
|
|
|
uint8_t advert_sound_scope;
|
2026-05-15 11:43:38 +02:00
|
|
|
|
// Per-channel melody override (2 bitmasks, 1 bit per channel)
|
2026-06-29 18:17:38 +02:00
|
|
|
|
uint64_t ch_notif_melody_set; // bit i = channel i has explicit melody [del→onChannelRemoved]
|
2026-05-15 11:43:38 +02:00
|
|
|
|
uint64_t ch_notif_melody_2; // bit i = use melody 2 (else melody 1, when set bit is set)
|
|
|
|
|
|
// Per-DM melody table
|
|
|
|
|
|
struct DmMelodyEntry { uint8_t prefix[4]; uint8_t slot; }; // slot: 0=global,1=melody1,2=melody2
|
|
|
|
|
|
static const int DM_MELODY_TABLE_MAX = 16;
|
2026-06-29 18:17:38 +02:00
|
|
|
|
DmMelodyEntry dm_melody[DM_MELODY_TABLE_MAX]; // [del→onContactRemoved]
|
2026-05-20 21:53:40 +02:00
|
|
|
|
uint8_t use_lemon_font; // 0=default Adafruit font, 1=Lemon font (Unicode, pixel-accurate wrap)
|
|
|
|
|
|
uint8_t display_rotation; // 0-3; only used on e-ink displays
|
2026-05-24 22:26:56 +02:00
|
|
|
|
// Home screen page order: each byte = HomePageBit + 1. 0 terminates the list.
|
|
|
|
|
|
// Validity gated by page_order_set magic (see below) — not by entry value range,
|
|
|
|
|
|
// so a junk byte in 1..HPB_COUNT cannot trigger custom-order mode.
|
|
|
|
|
|
// (Array length = HPB_COUNT, declared as literal so the field offset is stable
|
|
|
|
|
|
// across builds that add new HomePageBit entries.)
|
2026-05-22 23:33:06 +02:00
|
|
|
|
uint8_t page_order[11];
|
2026-05-23 15:20:40 +02:00
|
|
|
|
uint8_t joystick_rotation; // 0-3 steps CW; independent of display_rotation
|
2026-05-24 10:22:03 +02:00
|
|
|
|
uint8_t eink_full_refresh_every; // index into {0,5,10,20,30}: full refresh every N partials (0=off)
|
2026-05-24 19:53:05 +02:00
|
|
|
|
uint8_t page_order_set; // 0xA5 = page_order is user-configured; anything else = use default
|
|
|
|
|
|
static const uint8_t PAGE_ORDER_MAGIC = 0xA5;
|
refactor(ui): apply code review fixes — bugs, risk, optimisations, cleanup
Fixes 7 bugs (timing UB on unsigned long, abs() no-op, XBM CRC PROGMEM
scan, CayenneLPP heap thrash ×2, UTF-8 reply prefix, lock-seq reset on
wake), 3 risk items (notif melody stop guard, bot trigger pre-lowercase,
lock-seq cleared on display wake), plus optimisations (fmtMsgAge/
getTextWidth caching, RTTTL builder consolidated to NodePrefs, HP_*
constants and homePageLabel table moved to NodePrefs, DataStore flat
lambda schema, translateUTF8Static factored out) and minor cleanup.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 20:03:45 +02:00
|
|
|
|
|
2026-05-24 23:00:27 +02:00
|
|
|
|
// Favourites dial: 6 pinned contacts, stored as first 6 bytes of pub_key per slot.
|
|
|
|
|
|
// All-zero = empty slot (probability a real pub_key starts with 6 zero bytes is 2^-48).
|
|
|
|
|
|
// Layout transposes between landscape (3×2) and portrait (2×3).
|
|
|
|
|
|
static const uint8_t FAVOURITES_COUNT = 6;
|
|
|
|
|
|
static const uint8_t FAVOURITE_PREFIX_LEN = 6;
|
2026-06-29 18:17:38 +02:00
|
|
|
|
uint8_t favourite_contacts[FAVOURITES_COUNT][FAVOURITE_PREFIX_LEN]; // [del→onContactRemoved]
|
2026-05-24 23:00:27 +02:00
|
|
|
|
|
2026-05-25 10:48:03 +02:00
|
|
|
|
// GPS trail cadence. Logging on/off is a runtime state (Tools › Trail),
|
|
|
|
|
|
// not a persisted preference.
|
|
|
|
|
|
uint8_t trail_interval_idx; // reserved — sampling cadence is now fixed at TrailStore::SAMPLING_SECS
|
2026-06-04 00:50:42 +02:00
|
|
|
|
uint8_t trail_min_delta_idx; // min-distance gate level (0=finest..3); metres or feet per units_imperial
|
2026-06-04 00:44:56 +02:00
|
|
|
|
uint8_t trail_units_idx; // legacy: old combined speed/pace+unit index (km/h, mph, min/km, min/mi)
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
|
2026-05-27 17:33:52 +02:00
|
|
|
|
uint64_t ch_fav_bitmask; // bit i = channel i is marked as favourite
|
|
|
|
|
|
uint8_t ch_fav_only; // 0=show all channels (default), 1=show favourites only
|
|
|
|
|
|
|
2026-06-04 00:44:56 +02:00
|
|
|
|
// Global measurement system for every distance/speed shown in the UI
|
|
|
|
|
|
// (Nearby, Trail, navigate-to-point). 0=metric (default), 1=imperial.
|
|
|
|
|
|
uint8_t units_imperial;
|
|
|
|
|
|
// Trail Summary readout: 0=speed (km/h or mph), 1=pace (min/km or min/mi).
|
|
|
|
|
|
// The km-vs-mi choice now comes from units_imperial, so this is just the mode.
|
|
|
|
|
|
uint8_t trail_show_pace;
|
2026-06-10 23:30:43 +02:00
|
|
|
|
// Hardware duty-cycle receive (battery saver): 0=continuous RX (default), 1=on.
|
|
|
|
|
|
// The SX126x cycles RX↔sleep on its own and wakes on a preamble — cuts average
|
|
|
|
|
|
// RX current at the cost of a little receive latency. See RadioLibWrapper
|
|
|
|
|
|
// power-save (startReceiveDutyCycleAuto).
|
|
|
|
|
|
uint8_t rx_powersave;
|
|
|
|
|
|
// Adaptive Power Control: 0=off (fixed tx_power_dbm, default), 1=on. When on,
|
|
|
|
|
|
// tx_power_dbm is treated as a ceiling and the radio's actual power is lowered
|
|
|
|
|
|
// at runtime on strong links (good ACK SNR), saving TX energy. Never persisted
|
|
|
|
|
|
// below the ceiling, so disabling restores the user's configured power.
|
|
|
|
|
|
uint8_t tx_apc;
|
2026-06-04 00:44:56 +02:00
|
|
|
|
|
2026-06-14 23:33:16 +02:00
|
|
|
|
// Auto-resend for on-device DMs: number of extra send attempts (0..5) made when
|
|
|
|
|
|
// no end-to-end ACK arrives before the deadline, before the delivery marker
|
|
|
|
|
|
// shows ✗. 0 = no auto-resend (single attempt). Default 2.
|
|
|
|
|
|
uint8_t dm_resend_count;
|
|
|
|
|
|
|
2026-06-21 22:32:59 +02:00
|
|
|
|
// User-saved radio presets, written by the "Save current..." entry in the
|
|
|
|
|
|
// shared preset picker (Settings > Radio and Tools > Repeater both populate
|
|
|
|
|
|
// these same slots). name[0] == '\0' marks an empty slot.
|
feat(repeater): politeness controls, dedicated radio profile, and diagnostics
Settings > Radio gains a repeater toggle, 16 community-suggested radio
presets plus manual Freq/SF/BW/CR tuning (digit-by-digit Freq editor),
4 persisted user preset slots, and the packet pool bumped 16->32 so
queued retransmits stop starving incoming-packet allocation while
relaying.
Four opt-in politeness knobs (skip-advert, max-hops, yield, min-SNR),
all flood-only and off by default, plus overhear suppression that
cancels a queued retransmit if a peer relays the same packet first.
Adaptive Power Control is suppressed while relaying (pins TX power to
the ceiling) and duty-cycle RX is forced off, since a repeater needs
to hear and relay at consistent power.
A dedicated Tools > Repeater screen consolidates the toggle, the
politeness knobs, and live forwarding stats, plus an optional "Custom"
radio profile — a dedicated frequency/SF/BW/CR for relaying, separate
from the companion's own network, band-matched to the companion
frequency by default and used everywhere a radio change can happen
(boot, on-device toggle, app-driven CMD_SET_RADIO_PARAMS, Settings
radio edits) so the device never silently falls back to the wrong
params mid-relay.
Diagnostics gains the actually-forwarded packet count, real heap-free
via mallinfo(), a reset-counters popup, and hardened loop-detect
bounds. Also: a frequency-floor fix and a float-equality preset-match
fix in the repeater profile logic, deduped BW-table/valCol/profile-
seeding helpers shared between Settings and the Repeater screen, and
a build.sh fix tolerating control characters in `pio project config`'s
JSON dump.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 09:07:00 +02:00
|
|
|
|
struct UserRadioPreset {
|
|
|
|
|
|
char name[16];
|
|
|
|
|
|
float freq;
|
|
|
|
|
|
float bw;
|
|
|
|
|
|
uint8_t sf;
|
|
|
|
|
|
uint8_t cr;
|
|
|
|
|
|
};
|
|
|
|
|
|
static const uint8_t USER_RADIO_PRESET_MAX = 4;
|
|
|
|
|
|
UserRadioPreset user_radio_presets[USER_RADIO_PRESET_MAX];
|
|
|
|
|
|
|
2026-06-21 22:32:59 +02:00
|
|
|
|
// Repeater forwarding filters — only consulted when client_repeat is on, via
|
feat(repeater): politeness controls, dedicated radio profile, and diagnostics
Settings > Radio gains a repeater toggle, 16 community-suggested radio
presets plus manual Freq/SF/BW/CR tuning (digit-by-digit Freq editor),
4 persisted user preset slots, and the packet pool bumped 16->32 so
queued retransmits stop starving incoming-packet allocation while
relaying.
Four opt-in politeness knobs (skip-advert, max-hops, yield, min-SNR),
all flood-only and off by default, plus overhear suppression that
cancels a queued retransmit if a peer relays the same packet first.
Adaptive Power Control is suppressed while relaying (pins TX power to
the ceiling) and duty-cycle RX is forced off, since a repeater needs
to hear and relay at consistent power.
A dedicated Tools > Repeater screen consolidates the toggle, the
politeness knobs, and live forwarding stats, plus an optional "Custom"
radio profile — a dedicated frequency/SF/BW/CR for relaying, separate
from the companion's own network, band-matched to the companion
frequency by default and used everywhere a radio change can happen
(boot, on-device toggle, app-driven CMD_SET_RADIO_PARAMS, Settings
radio edits) so the device never silently falls back to the wrong
params mid-relay.
Diagnostics gains the actually-forwarded packet count, real heap-free
via mallinfo(), a reset-counters popup, and hardened loop-detect
bounds. Also: a frequency-floor fix and a float-equality preset-match
fix in the repeater profile logic, deduped BW-table/valCol/profile-
seeding helpers shared between Settings and the Repeater screen, and
a build.sh fix tolerating control characters in `pio project config`'s
JSON dump.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 09:07:00 +02:00
|
|
|
|
// MyMesh::allowPacketForward(). Both default to off (0) so behaviour is
|
2026-06-21 22:32:59 +02:00
|
|
|
|
// unchanged until the user opts in (Tools > Repeater).
|
feat(repeater): politeness controls, dedicated radio profile, and diagnostics
Settings > Radio gains a repeater toggle, 16 community-suggested radio
presets plus manual Freq/SF/BW/CR tuning (digit-by-digit Freq editor),
4 persisted user preset slots, and the packet pool bumped 16->32 so
queued retransmits stop starving incoming-packet allocation while
relaying.
Four opt-in politeness knobs (skip-advert, max-hops, yield, min-SNR),
all flood-only and off by default, plus overhear suppression that
cancels a queued retransmit if a peer relays the same packet first.
Adaptive Power Control is suppressed while relaying (pins TX power to
the ceiling) and duty-cycle RX is forced off, since a repeater needs
to hear and relay at consistent power.
A dedicated Tools > Repeater screen consolidates the toggle, the
politeness knobs, and live forwarding stats, plus an optional "Custom"
radio profile — a dedicated frequency/SF/BW/CR for relaying, separate
from the companion's own network, band-matched to the companion
frequency by default and used everywhere a radio change can happen
(boot, on-device toggle, app-driven CMD_SET_RADIO_PARAMS, Settings
radio edits) so the device never silently falls back to the wrong
params mid-relay.
Diagnostics gains the actually-forwarded packet count, real heap-free
via mallinfo(), a reset-counters popup, and hardened loop-detect
bounds. Also: a frequency-floor fix and a float-equality preset-match
fix in the repeater profile logic, deduped BW-table/valCol/profile-
seeding helpers shared between Settings and the Repeater screen, and
a build.sh fix tolerating control characters in `pio project config`'s
JSON dump.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 09:07:00 +02:00
|
|
|
|
// repeat_skip_adverts: 1 = don't re-flood ADVERT packets (the highest-volume
|
|
|
|
|
|
// flood traffic); messages/acks still relay.
|
|
|
|
|
|
// repeat_max_hops: drop a flood packet once it has already travelled this
|
|
|
|
|
|
// many hops. 0 = no hop limit.
|
|
|
|
|
|
// repeat_delay_boost: extra retransmit-delay multiplier for FORWARDED floods
|
|
|
|
|
|
// only (own sends are unaffected) — a mobile companion yields to better-sited
|
|
|
|
|
|
// fixed repeaters. Effective delay = base * (1 + repeat_delay_boost). 0 = off.
|
|
|
|
|
|
// repeat_min_snr: drop a flood packet received below this SNR (dB), so marginal
|
|
|
|
|
|
// fringe traffic isn't re-flooded. REPEAT_SNR_DISABLED (-128) = off.
|
|
|
|
|
|
// repeat_suppress_dup: 1 = cancel a queued retransmit when the same flood is
|
|
|
|
|
|
// overheard from another node first (less redundant airtime in dense mesh).
|
|
|
|
|
|
uint8_t repeat_skip_adverts;
|
|
|
|
|
|
uint8_t repeat_max_hops;
|
|
|
|
|
|
uint8_t repeat_delay_boost;
|
|
|
|
|
|
int8_t repeat_min_snr;
|
|
|
|
|
|
static const int8_t REPEAT_SNR_DISABLED = -128;
|
|
|
|
|
|
uint8_t repeat_suppress_dup;
|
|
|
|
|
|
|
|
|
|
|
|
// 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 (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;
|
|
|
|
|
|
uint8_t repeater_sf;
|
|
|
|
|
|
uint8_t repeater_cr;
|
|
|
|
|
|
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
// Track positions shared by other nodes via [LOC] messages (LiveTrackStore).
|
|
|
|
|
|
// 0 = ignore shared positions (default), 1 = parse incoming DM/channel [LOC]
|
|
|
|
|
|
// shares and update the live-track table (Nearby "Live" view / map).
|
|
|
|
|
|
uint8_t track_shared_loc;
|
|
|
|
|
|
|
|
|
|
|
|
// Live location sharing — a message-based "beacon". When enabled, the device
|
|
|
|
|
|
// periodically sends a [LOC] message to the chosen target while it moves.
|
|
|
|
|
|
// Configured from the Map (Trail screen) "Live share" menu.
|
|
|
|
|
|
uint8_t loc_share_enabled; // 0=off (default), 1=auto-sharing on
|
|
|
|
|
|
uint8_t loc_share_target_type; // 0=channel, 1=DM contact
|
2026-06-29 18:17:38 +02:00
|
|
|
|
uint8_t loc_share_channel_idx; // target channel index (when target_type==0) [del→onChannelRemoved]
|
|
|
|
|
|
uint8_t loc_share_dm_prefix[6]; // target contact pubkey prefix (when target_type==1) [del→onContactRemoved]
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
uint8_t loc_share_move_idx; // movement gate level (index into locShareMoveMeters)
|
|
|
|
|
|
uint8_t loc_share_interval_idx; // min send interval (index into locShareIntervalSecs)
|
|
|
|
|
|
uint8_t loc_share_heartbeat_idx; // stationary heartbeat (index into locShareHeartbeatSecs)
|
|
|
|
|
|
|
|
|
|
|
|
// Locator — a single geofence around a saved point. When enabled the device
|
|
|
|
|
|
// watches its own GPS fix and beeps + shows an alert when it crosses into
|
|
|
|
|
|
// (arrive) or out of (leave) the radius. The target coordinate/label is a
|
|
|
|
|
|
// snapshot of a chosen waypoint, so the alert survives the waypoint being
|
|
|
|
|
|
// edited or deleted. Configured from Tools › Locator.
|
|
|
|
|
|
uint8_t locator_enabled; // 0=off (default), 1=armed
|
|
|
|
|
|
uint8_t locator_has_target; // 0=no target chosen yet, 1=target set
|
|
|
|
|
|
uint8_t locator_radius_idx; // index into locatorRadiusMeters
|
|
|
|
|
|
uint8_t locator_mode; // 0=arrive, 1=leave, 2=both
|
|
|
|
|
|
int32_t locator_lat_1e6; // target latitude (1e6-scaled; last-known for a contact)
|
|
|
|
|
|
int32_t locator_lon_1e6; // target longitude (1e6-scaled; last-known for a contact)
|
|
|
|
|
|
char locator_label[12]; // target name for the alert text (WAYPOINT_LABEL_LEN)
|
|
|
|
|
|
// Target can be a static waypoint or a live contact: for a contact the engine
|
|
|
|
|
|
// re-reads the latest [LOC] position each evaluation (keyed by pubkey prefix),
|
|
|
|
|
|
// so the geofence follows a moving person ("alert when my friend is near").
|
|
|
|
|
|
uint8_t locator_target_kind; // 0=waypoint (static), 1=live contact
|
2026-06-29 18:17:38 +02:00
|
|
|
|
uint8_t locator_key[6]; // contact pubkey prefix when target_kind==1 [del→onContactRemoved]
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
|
|
|
|
|
|
// Trail auto-pause — when tracking, automatically freeze the trail (timer +
|
|
|
|
|
|
// sampling) after the device has sat still for this long, and resume on the
|
|
|
|
|
|
// next real movement. 0 = off. Index into trailAutoPauseSecs.
|
|
|
|
|
|
uint8_t trail_autopause_idx;
|
|
|
|
|
|
|
|
|
|
|
|
// Locator proximity beeper — when on (and the alert is armed with a target),
|
|
|
|
|
|
// the device ticks while inside the radius and shortens the gap between ticks
|
|
|
|
|
|
// the closer it gets to the target, like a homing beeper. Independent of the
|
|
|
|
|
|
// discrete arrive/leave alert (locator_mode).
|
|
|
|
|
|
uint8_t locator_beeper; // 0=off (default), 1=on
|
|
|
|
|
|
|
2026-06-29 19:19:43 +02:00
|
|
|
|
// GPS averaging for waypoint marking — when set, "Mark here" samples the GPS
|
|
|
|
|
|
// fix for this many seconds and stores the mean position, for a more accurate
|
|
|
|
|
|
// mark than a single instantaneous fix. 0 = off (instant mark, the default).
|
|
|
|
|
|
// Index into gpsAvgSecs(). [Tools › Trail › Settings › Mark avg]
|
|
|
|
|
|
uint8_t gps_avg_idx;
|
|
|
|
|
|
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
// Single source of truth for the live-share option tables (shared by the Map
|
|
|
|
|
|
// UI labels and the auto-send engine in UITask).
|
|
|
|
|
|
static const uint8_t LOC_SHARE_MOVE_COUNT = 4;
|
|
|
|
|
|
static uint16_t locShareMoveMeters(uint8_t idx) {
|
|
|
|
|
|
static const uint16_t M[LOC_SHARE_MOVE_COUNT] = { 50, 100, 250, 500 };
|
|
|
|
|
|
return M[idx < LOC_SHARE_MOVE_COUNT ? idx : 1];
|
|
|
|
|
|
}
|
|
|
|
|
|
static const uint8_t LOC_SHARE_INTERVAL_COUNT = 4;
|
|
|
|
|
|
static uint16_t locShareIntervalSecs(uint8_t idx) {
|
|
|
|
|
|
static const uint16_t S[LOC_SHARE_INTERVAL_COUNT] = { 30, 60, 120, 300 };
|
|
|
|
|
|
return S[idx < LOC_SHARE_INTERVAL_COUNT ? idx : 1];
|
|
|
|
|
|
}
|
|
|
|
|
|
static const uint8_t LOC_SHARE_HEARTBEAT_COUNT = 3;
|
|
|
|
|
|
static uint16_t locShareHeartbeatSecs(uint8_t idx) {
|
|
|
|
|
|
static const uint16_t H[LOC_SHARE_HEARTBEAT_COUNT] = { 0, 300, 900 }; // off / 5 min / 15 min
|
|
|
|
|
|
return H[idx < LOC_SHARE_HEARTBEAT_COUNT ? idx : 0];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Locator option tables (shared by the Tools › Locator UI labels and the
|
|
|
|
|
|
// evaluation engine in UITask).
|
|
|
|
|
|
static const uint8_t LOCATOR_RADIUS_COUNT = 5;
|
|
|
|
|
|
static uint16_t locatorRadiusMeters(uint8_t idx) {
|
|
|
|
|
|
static const uint16_t R[LOCATOR_RADIUS_COUNT] = { 50, 100, 250, 500, 1000 };
|
|
|
|
|
|
return R[idx < LOCATOR_RADIUS_COUNT ? idx : 1];
|
|
|
|
|
|
}
|
|
|
|
|
|
static const uint8_t LOCATOR_MODE_COUNT = 3; // 0=arrive, 1=leave, 2=both
|
|
|
|
|
|
static const char* locatorModeLabel(uint8_t m) {
|
|
|
|
|
|
static const char* L[LOCATOR_MODE_COUNT] = { "Arrive", "Leave", "Both" };
|
|
|
|
|
|
return L[m < LOCATOR_MODE_COUNT ? m : 0];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-29 19:19:43 +02:00
|
|
|
|
// GPS-averaging durations for waypoint marking (seconds). 0 = off (instant).
|
|
|
|
|
|
static const uint8_t GPS_AVG_COUNT = 4;
|
|
|
|
|
|
static uint16_t gpsAvgSecs(uint8_t idx) {
|
|
|
|
|
|
static const uint16_t S[GPS_AVG_COUNT] = { 0, 5, 10, 30 };
|
|
|
|
|
|
return S[idx < GPS_AVG_COUNT ? idx : 0];
|
|
|
|
|
|
}
|
|
|
|
|
|
static const char* gpsAvgLabel(uint8_t idx) {
|
|
|
|
|
|
static const char* L[GPS_AVG_COUNT] = { "Off", "5s", "10s", "30s" };
|
|
|
|
|
|
return L[idx < GPS_AVG_COUNT ? idx : 0];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
// Trail auto-pause delays (seconds). 0 = off.
|
|
|
|
|
|
static const uint8_t TRAIL_AUTOPAUSE_COUNT = 4;
|
|
|
|
|
|
static uint16_t trailAutoPauseSecs(uint8_t idx) {
|
|
|
|
|
|
static const uint16_t S[TRAIL_AUTOPAUSE_COUNT] = { 0, 60, 120, 300 }; // off / 1 / 2 / 5 min
|
|
|
|
|
|
return S[idx < TRAIL_AUTOPAUSE_COUNT ? idx : 0];
|
|
|
|
|
|
}
|
|
|
|
|
|
static const char* trailAutoPauseLabel(uint8_t idx) {
|
|
|
|
|
|
static const char* L[TRAIL_AUTOPAUSE_COUNT] = { "Off", "1m", "2m", "5m" };
|
|
|
|
|
|
return L[idx < TRAIL_AUTOPAUSE_COUNT ? idx : 0];
|
|
|
|
|
|
}
|
|
|
|
|
|
// Movement under this many metres counts as "stationary" for auto-pause.
|
|
|
|
|
|
// Deliberately coarser than the trail min-delta gate (and independent of it)
|
|
|
|
|
|
// so GPS jitter while parked doesn't keep resetting the idle timer. Engine
|
|
|
|
|
|
// tuning only — not persisted.
|
|
|
|
|
|
static const uint16_t TRAIL_AUTOPAUSE_MOVE_M = 15;
|
|
|
|
|
|
|
2026-05-24 20:18:50 +02:00
|
|
|
|
// Tail sentinel written at the end of /new_prefs. Bump the low byte when
|
|
|
|
|
|
// adding/removing/reordering fields in DataStore::savePrefs/loadPrefsInt so
|
|
|
|
|
|
// older saves are detected on load and skipped (zero-init defaults kept).
|
|
|
|
|
|
// High 24 bits identify the file format; low byte is the schema revision.
|
2026-06-29 19:19:43 +02:00
|
|
|
|
static const uint32_t SCHEMA_SENTINEL = 0xC0DE0016;
|
2026-05-24 20:18:50 +02:00
|
|
|
|
|
2026-05-24 22:26:56 +02:00
|
|
|
|
// Bit-index for each home page. Used by page_order (entries store bit+1) and
|
|
|
|
|
|
// by home_pages_mask. Single source of truth — both HomeScreen::pageBit/bitToPage
|
|
|
|
|
|
// and SettingsScreen::homePageBitIndex route their per-enum lookups through these.
|
|
|
|
|
|
enum HomePageBit : uint8_t {
|
2026-05-24 23:00:27 +02:00
|
|
|
|
HPB_CLOCK = 0,
|
|
|
|
|
|
HPB_RECENT = 1,
|
|
|
|
|
|
HPB_RADIO = 2,
|
|
|
|
|
|
HPB_BLUETOOTH = 3,
|
|
|
|
|
|
HPB_ADVERT = 4,
|
|
|
|
|
|
HPB_GPS = 5,
|
|
|
|
|
|
HPB_SENSORS = 6,
|
|
|
|
|
|
HPB_TOOLS = 7,
|
|
|
|
|
|
HPB_SHUTDOWN = 8,
|
|
|
|
|
|
HPB_SETTINGS = 9,
|
|
|
|
|
|
HPB_QUICK_MSG = 10,
|
|
|
|
|
|
HPB_FAVOURITES = 11,
|
|
|
|
|
|
HPB_COUNT = 12,
|
2026-05-24 22:26:56 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-05-24 23:00:27 +02:00
|
|
|
|
// Length of the persisted page_order[] array. Stable across firmware versions
|
|
|
|
|
|
// (don't grow without a schema migration). When HPB_COUNT exceeds this, extra
|
|
|
|
|
|
// visible pages are appended at navigation time via buildVisibleOrder fallback.
|
|
|
|
|
|
static const uint8_t PAGE_ORDER_LEN = 11;
|
|
|
|
|
|
|
refactor(ui): apply code review fixes — bugs, risk, optimisations, cleanup
Fixes 7 bugs (timing UB on unsigned long, abs() no-op, XBM CRC PROGMEM
scan, CayenneLPP heap thrash ×2, UTF-8 reply prefix, lock-seq reset on
wake), 3 risk items (notif melody stop guard, bot trigger pre-lowercase,
lock-seq cleared on display wake), plus optimisations (fmtMsgAge/
getTextWidth caching, RTTTL builder consolidated to NodePrefs, HP_*
constants and homePageLabel table moved to NodePrefs, DataStore flat
lambda schema, translateUTF8Static factored out) and minor cleanup.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 20:03:45 +02:00
|
|
|
|
// Bitmasks for home_pages_mask (bit=1 → page visible; 0 field = all visible).
|
2026-05-24 22:26:56 +02:00
|
|
|
|
// SETTINGS and QUICK_MSG have no mask bit — they're always visible.
|
2026-05-24 23:00:27 +02:00
|
|
|
|
static const uint16_t HP_CLOCK = 1 << HPB_CLOCK;
|
|
|
|
|
|
static const uint16_t HP_RECENT = 1 << HPB_RECENT;
|
|
|
|
|
|
static const uint16_t HP_RADIO = 1 << HPB_RADIO;
|
|
|
|
|
|
static const uint16_t HP_BLUETOOTH = 1 << HPB_BLUETOOTH;
|
|
|
|
|
|
static const uint16_t HP_ADVERT = 1 << HPB_ADVERT;
|
|
|
|
|
|
static const uint16_t HP_GPS = 1 << HPB_GPS;
|
|
|
|
|
|
static const uint16_t HP_SENSORS = 1 << HPB_SENSORS;
|
|
|
|
|
|
static const uint16_t HP_TOOLS = 1 << HPB_TOOLS;
|
|
|
|
|
|
static const uint16_t HP_SHUTDOWN = 1 << HPB_SHUTDOWN;
|
|
|
|
|
|
static const uint16_t HP_FAVOURITES = 1 << HPB_FAVOURITES;
|
|
|
|
|
|
static const uint16_t HP_ALL = 0x01FF | HP_FAVOURITES;
|
refactor(ui): apply code review fixes — bugs, risk, optimisations, cleanup
Fixes 7 bugs (timing UB on unsigned long, abs() no-op, XBM CRC PROGMEM
scan, CayenneLPP heap thrash ×2, UTF-8 reply prefix, lock-seq reset on
wake), 3 risk items (notif melody stop guard, bot trigger pre-lowercase,
lock-seq cleared on display wake), plus optimisations (fmtMsgAge/
getTextWidth caching, RTTTL builder consolidated to NodePrefs, HP_*
constants and homePageLabel table moved to NodePrefs, DataStore flat
lambda schema, translateUTF8Static factored out) and minor cleanup.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 20:03:45 +02:00
|
|
|
|
|
2026-05-24 22:26:56 +02:00
|
|
|
|
// Label for home page by bit-index; returns "" for out-of-range.
|
|
|
|
|
|
// Array indices match HomePageBit values.
|
refactor(ui): apply code review fixes — bugs, risk, optimisations, cleanup
Fixes 7 bugs (timing UB on unsigned long, abs() no-op, XBM CRC PROGMEM
scan, CayenneLPP heap thrash ×2, UTF-8 reply prefix, lock-seq reset on
wake), 3 risk items (notif melody stop guard, bot trigger pre-lowercase,
lock-seq cleared on display wake), plus optimisations (fmtMsgAge/
getTextWidth caching, RTTTL builder consolidated to NodePrefs, HP_*
constants and homePageLabel table moved to NodePrefs, DataStore flat
lambda schema, translateUTF8Static factored out) and minor cleanup.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 20:03:45 +02:00
|
|
|
|
static const char* homePageLabel(uint8_t bit) {
|
2026-05-24 22:26:56 +02:00
|
|
|
|
static const char* labels[HPB_COUNT] = {
|
refactor(ui): apply code review fixes — bugs, risk, optimisations, cleanup
Fixes 7 bugs (timing UB on unsigned long, abs() no-op, XBM CRC PROGMEM
scan, CayenneLPP heap thrash ×2, UTF-8 reply prefix, lock-seq reset on
wake), 3 risk items (notif melody stop guard, bot trigger pre-lowercase,
lock-seq cleared on display wake), plus optimisations (fmtMsgAge/
getTextWidth caching, RTTTL builder consolidated to NodePrefs, HP_*
constants and homePageLabel table moved to NodePrefs, DataStore flat
lambda schema, translateUTF8Static factored out) and minor cleanup.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 20:03:45 +02:00
|
|
|
|
"Clock", "Recent", "Radio", "Bluetooth", "Advert",
|
2026-05-24 23:00:27 +02:00
|
|
|
|
"GPS", "Sensors", "Tools", "Shutdown", "Settings", "Messages", "Favourites"
|
refactor(ui): apply code review fixes — bugs, risk, optimisations, cleanup
Fixes 7 bugs (timing UB on unsigned long, abs() no-op, XBM CRC PROGMEM
scan, CayenneLPP heap thrash ×2, UTF-8 reply prefix, lock-seq reset on
wake), 3 risk items (notif melody stop guard, bot trigger pre-lowercase,
lock-seq cleared on display wake), plus optimisations (fmtMsgAge/
getTextWidth caching, RTTTL builder consolidated to NodePrefs, HP_*
constants and homePageLabel table moved to NodePrefs, DataStore flat
lambda schema, translateUTF8Static factored out) and minor cleanup.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 20:03:45 +02:00
|
|
|
|
};
|
2026-05-24 22:26:56 +02:00
|
|
|
|
return (bit < HPB_COUNT) ? labels[bit] : "";
|
refactor(ui): apply code review fixes — bugs, risk, optimisations, cleanup
Fixes 7 bugs (timing UB on unsigned long, abs() no-op, XBM CRC PROGMEM
scan, CayenneLPP heap thrash ×2, UTF-8 reply prefix, lock-seq reset on
wake), 3 risk items (notif melody stop guard, bot trigger pre-lowercase,
lock-seq cleared on display wake), plus optimisations (fmtMsgAge/
getTextWidth caching, RTTTL builder consolidated to NodePrefs, HP_*
constants and homePageLabel table moved to NodePrefs, DataStore flat
lambda schema, translateUTF8Static factored out) and minor cleanup.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 20:03:45 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void buildRTTTLString(const uint8_t* notes, uint8_t len, uint8_t bpm_idx,
|
|
|
|
|
|
char* buf, int size) {
|
|
|
|
|
|
static const uint16_t BPM_OPTS[] = { 60, 90, 120, 150, 180 };
|
|
|
|
|
|
static const uint8_t DUR_VALS[] = { 4, 8, 16, 32 };
|
|
|
|
|
|
static const char PITCHES[] = { 'p', 'c', 'd', 'e', 'f', 'g', 'a', 'b' };
|
|
|
|
|
|
if (len == 0) { buf[0] = '\0'; return; }
|
|
|
|
|
|
uint16_t bpm = BPM_OPTS[bpm_idx < 5 ? bpm_idx : 2];
|
|
|
|
|
|
int pos = snprintf(buf, size, "Ring:d=8,o=5,b=%u:", bpm);
|
|
|
|
|
|
for (int i = 0; i < len && pos < size - 8; i++) {
|
|
|
|
|
|
if (i > 0 && pos < size - 1) buf[pos++] = ',';
|
|
|
|
|
|
uint8_t pitch = notes[i] & 0x07;
|
|
|
|
|
|
uint8_t octave = ((notes[i] >> 3) & 0x03) + 4;
|
|
|
|
|
|
uint8_t dur_val = DUR_VALS[(notes[i] >> 5) & 0x03];
|
|
|
|
|
|
if (pitch == 0) pos += snprintf(buf + pos, size - pos, "%dp", dur_val);
|
|
|
|
|
|
else pos += snprintf(buf + pos, size - pos, "%d%c%d", dur_val, PITCHES[pitch], octave);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (pos < size) buf[pos] = '\0';
|
|
|
|
|
|
}
|
2026-06-04 08:46:15 +02:00
|
|
|
|
};
|
feat(repeater): politeness controls, dedicated radio profile, and diagnostics
Settings > Radio gains a repeater toggle, 16 community-suggested radio
presets plus manual Freq/SF/BW/CR tuning (digit-by-digit Freq editor),
4 persisted user preset slots, and the packet pool bumped 16->32 so
queued retransmits stop starving incoming-packet allocation while
relaying.
Four opt-in politeness knobs (skip-advert, max-hops, yield, min-SNR),
all flood-only and off by default, plus overhear suppression that
cancels a queued retransmit if a peer relays the same packet first.
Adaptive Power Control is suppressed while relaying (pins TX power to
the ceiling) and duty-cycle RX is forced off, since a repeater needs
to hear and relay at consistent power.
A dedicated Tools > Repeater screen consolidates the toggle, the
politeness knobs, and live forwarding stats, plus an optional "Custom"
radio profile — a dedicated frequency/SF/BW/CR for relaying, separate
from the companion's own network, band-matched to the companion
frequency by default and used everywhere a radio change can happen
(boot, on-device toggle, app-driven CMD_SET_RADIO_PARAMS, Settings
radio edits) so the device never silently falls back to the wrong
params mid-relay.
Diagnostics gains the actually-forwarded packet count, real heap-free
via mallinfo(), a reset-counters popup, and hardened loop-detect
bounds. Also: a frequency-floor fix and a float-equality preset-match
fix in the repeater profile logic, deduped BW-table/valCol/profile-
seeding helpers shared between Settings and the Repeater screen, and
a build.sh fix tolerating control characters in `pio project config`'s
JSON dump.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 09:07:00 +02:00
|
|
|
|
|
2026-06-29 18:13:05 +02:00
|
|
|
|
// ── Serialization tripwire ───────────────────────────────────────────────────
|
|
|
|
|
|
// NodePrefs is written/read field-by-field, in order, by DataStore::savePrefs()
|
|
|
|
|
|
// and loadPrefsInt(); the on-disk format IS the struct's field layout. There is
|
|
|
|
|
|
// no automatic check that those two hand-written sequences match the struct, so
|
|
|
|
|
|
// a forgotten read/write silently misaligns EVERY field after it.
|
|
|
|
|
|
//
|
|
|
|
|
|
// This assert is the manual checkpoint. Changing a data member changes sizeof
|
|
|
|
|
|
// and trips it. When it trips, do ALL of the following, then update the number:
|
|
|
|
|
|
// 1. add the field's rd(...) in DataStore::loadPrefsInt(), in struct order
|
|
|
|
|
|
// 2. add the field's write(...) in DataStore::savePrefs(), in struct order
|
|
|
|
|
|
// 3. clamp it on load (an upgrader's file lacks it → stray bytes)
|
|
|
|
|
|
// 4. bump SCHEMA_SENTINEL's low byte
|
|
|
|
|
|
// (Padding can also shift sizeof; a "false" trip just means re-check + rebump.)
|
|
|
|
|
|
static_assert(sizeof(NodePrefs) == 2488,
|
|
|
|
|
|
"NodePrefs layout changed — sync DataStore save/load + clamp, bump "
|
|
|
|
|
|
"SCHEMA_SENTINEL, then update this size (see steps above).");
|
|
|
|
|
|
|
2026-06-21 23:09:27 +02:00
|
|
|
|
// 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
|
feat(repeater): politeness controls, dedicated radio profile, and diagnostics
Settings > Radio gains a repeater toggle, 16 community-suggested radio
presets plus manual Freq/SF/BW/CR tuning (digit-by-digit Freq editor),
4 persisted user preset slots, and the packet pool bumped 16->32 so
queued retransmits stop starving incoming-packet allocation while
relaying.
Four opt-in politeness knobs (skip-advert, max-hops, yield, min-SNR),
all flood-only and off by default, plus overhear suppression that
cancels a queued retransmit if a peer relays the same packet first.
Adaptive Power Control is suppressed while relaying (pins TX power to
the ceiling) and duty-cycle RX is forced off, since a repeater needs
to hear and relay at consistent power.
A dedicated Tools > Repeater screen consolidates the toggle, the
politeness knobs, and live forwarding stats, plus an optional "Custom"
radio profile — a dedicated frequency/SF/BW/CR for relaying, separate
from the companion's own network, band-matched to the companion
frequency by default and used everywhere a radio change can happen
(boot, on-device toggle, app-driven CMD_SET_RADIO_PARAMS, Settings
radio edits) so the device never silently falls back to the wrong
params mid-relay.
Diagnostics gains the actually-forwarded packet count, real heap-free
via mallinfo(), a reset-counters popup, and hardened loop-detect
bounds. Also: a frequency-floor fix and a float-equality preset-match
fix in the repeater profile logic, deduped BW-table/valCol/profile-
seeding helpers shared between Settings and the Repeater screen, and
a build.sh fix tolerating control characters in `pio project config`'s
JSON dump.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 09:07:00 +02:00
|
|
|
|
&& 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;
|
|
|
|
|
|
}
|