mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-11-01 10:21:59 +00:00
feat: app-only settings on the device: privacy, adding contacts, advanced radio
- Settings schema gains Privacy (position in adverts, who can ask for status / location / sensors), Adding contacts (all or by type, max hops, replace oldest) and Advanced radio (path hash size, double ACK, listen before talk, interference threshold, RX boost, RX delay, airtime factor); both UIs build their rows from it - Auto-advert carries position only when Position in adverts is on - ui-new accordion holds 16 sections and 128 rows - docs: settings, tools, feature matrix Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -61,6 +61,8 @@ build flag it needs.
|
||||
| Contact details | yes | + compass rose, path hops | + compass rose, path hops | + compass rose, path hops | card with actions |
|
||||
| Contact expiry / prune | yes | yes | yes | yes | yes |
|
||||
| Remote admin | yes | yes | yes | yes | yes |
|
||||
| Adding contacts (auto-add by type, hops, replace oldest) | Settings › Contacts | same | same | same | Messages & contacts |
|
||||
| Privacy (position in adverts, telemetry requests) | Settings › Privacy | same | same | same | Settings › Privacy |
|
||||
|
||||
### Navigation (`ENV_INCLUDE_GPS`)
|
||||
|
||||
@@ -84,6 +86,7 @@ build flag it needs.
|
||||
| Melodies (`PIN_BUZZER`) | Ringtones tool | same | same | wide editor | Settings › Sound |
|
||||
| Status: Radio, GPS, Power, Mesh | yes, 32 min history | yes, 8 h charts | yes, 8 h charts | tiles with charts | Diagnostics app (+ noise chart) |
|
||||
| Sky view (`GPS_SKYVIEW`) | Status › Sky | same | same | + side panel | GPS app: Sky, Signal, Details |
|
||||
| Advanced radio (hash size, double ACK, CAD, interference, RX boost, RX delay, airtime) | end of Settings › Radio | same | same | same | Radio › Advanced |
|
||||
| Battery curve | Settings | same | same | same | Settings › Power, draggable chart |
|
||||
| QR code of the manual | Status › System, Enter | same | same | same | Settings › About |
|
||||
|
||||
|
||||
+45
-3
@@ -10,10 +10,11 @@ board supports are shown.
|
||||
| **Display** | Brightness, auto-off, wake on message, the message alert (banner, corner envelope or off), lock screen, the lock clock's look and the [PIN](./lock.md), battery as icon / % / volts, time zone, clock format and seconds; on e-ink also rotation and full refresh |
|
||||
| **Sound** | Buzzer on / off / auto (quiet while the app is connected), volume, quiet hours, the melody for messages, channels and adverts |
|
||||
| **Home Pages** | Order of the home pages, and which are shown |
|
||||
| **Radio** | TX power, preset, frequency, SF / BW / CR, saved presets, Auto pwr, the scope list |
|
||||
| **Radio** | TX power, preset, frequency, SF / BW / CR, saved presets, Auto pwr, the scope list, then the advanced options (below) |
|
||||
| **System** | Device name, low-battery shutdown, GPS power saving, units, the battery curve, reboot |
|
||||
| **Keyboard** | ABC or T9 layout, the two scripts, CardKB mode |
|
||||
| **Contacts** | Show all or favourites only (DMs, channels, rooms), favourites on top, contact expiry and prune |
|
||||
| **Contacts** | Show all or favourites only (DMs, channels, rooms), favourites on top, contact expiry and prune, which heard nodes are added |
|
||||
| **Privacy** | Whether your adverts carry your position, and who may ask for your status, location and sensor readings |
|
||||
| **Messages** | Automatic resend of direct messages, the ten quick messages |
|
||||
|
||||
A few notes:
|
||||
@@ -32,6 +33,46 @@ A few notes:
|
||||
10 mV, **Reset** goes back to LiPo. The current voltage is shown, so a point
|
||||
can be set against what the battery reads.
|
||||
|
||||
### Adding contacts
|
||||
|
||||
A node you hear is added to your contacts according to **Auto-add**:
|
||||
|
||||
- **All**: every node heard.
|
||||
- **By type**: only the types switched on below it (companions, repeaters,
|
||||
rooms, sensors). With none on, nodes are added only by hand (Nearby ›
|
||||
**Add contact**).
|
||||
|
||||
**Up to** limits how far away an added node may be: **Direct** (heard with
|
||||
no repeater) or a number of hops. **Replace old** makes room when the list is
|
||||
full by dropping the oldest contact that isn't a favourite; without it, new
|
||||
nodes aren't added until you make room.
|
||||
|
||||
### Privacy
|
||||
|
||||
- **Advert pos** (Position in adverts): your adverts carry your position,
|
||||
including the automatic ones. Off, nodes see your name but not where you
|
||||
are. Live sharing and `{loc}` in a message still send it, because you send
|
||||
those on purpose.
|
||||
- **Ask status / loc / sens**: who may request your telemetry: **Nobody**,
|
||||
**Allowed** (the contacts you gave that permission, for now in the phone
|
||||
app) or **Everyone**. Status is the battery; a request for location or
|
||||
sensors is answered only where status is allowed too.
|
||||
|
||||
### Advanced radio
|
||||
|
||||
At the end of **Radio**. The defaults suit almost everyone; change them only
|
||||
if your mesh agrees on it.
|
||||
|
||||
| Setting | Does |
|
||||
| --- | --- |
|
||||
| **Hash size** | Bytes per repeater in a message's path: 1 (the default) fits the most hops, 2 or 3 tell repeaters apart in a big mesh |
|
||||
| **Double ACK** | Sends each acknowledgement twice, for a lossy link |
|
||||
| **LBT (CAD)** | Listen before talk: waits for a quiet channel before sending |
|
||||
| **Interf.** | Treats the channel as busy when the signal is this far above the noise floor |
|
||||
| **RX boost** | The radio's boosted receive gain: hears weaker signals for a little more power (SX126x radios) |
|
||||
| **RX delay** | How long a repeated packet waits, so the best path wins; off by default |
|
||||
| **Airtime** | The pause after sending, as a multiple of the time on air |
|
||||
|
||||
> [!NOTE]
|
||||
> **Wio Tracker L2:** one list of pages:
|
||||
>
|
||||
@@ -39,7 +80,8 @@ A few notes:
|
||||
> Power (battery, GPS, and the **Battery curve** as a chart whose points you
|
||||
> drag), Sound (with the melody editor), Keyboard, Messages
|
||||
> & contacts.
|
||||
> - **Connections**: Radio, Bluetooth, WiFi, GPS.
|
||||
> - **Connections**: Radio (with **Advanced** at the bottom), Privacy,
|
||||
> Bluetooth, WiFi, GPS. Adding contacts is in Messages & contacts.
|
||||
> - **Map & data**: Map (trail, live sharing and arrival alert options),
|
||||
> Storage.
|
||||
> - **System**: Name, Time, Units, Firmware update, About; Reboot and Power off.
|
||||
|
||||
+2
-2
@@ -64,8 +64,8 @@ room's saved login.
|
||||
|
||||
## Auto-advert
|
||||
|
||||
**Tools › Auto-advert** sends an advert with your position every 30 seconds
|
||||
to 1 hour, so others see you in their Nearby list. With it on at both ends
|
||||
**Tools › Auto-advert** sends your advert every 30 seconds to 1 hour (with
|
||||
your position if Settings › Privacy › **Advert pos** is on), so others see you in their Nearby list. With it on at both ends
|
||||
and Settings › Sound › **AD sound** set, each device beeps when it hears the
|
||||
other: a hands-free "still in range".
|
||||
|
||||
|
||||
@@ -3484,7 +3484,8 @@ void MyMesh::loop() {
|
||||
}
|
||||
|
||||
if (_prefs.advert_auto_interval_sec > 0 && millisHasNowPassed(_next_auto_advert_ms)) {
|
||||
mesh::Packet* pkt = (sensors.node_lat != 0 || sensors.node_lon != 0)
|
||||
// The position only where Privacy lets adverts carry it, and once there is one.
|
||||
mesh::Packet* pkt = (_prefs.advert_loc_policy != ADVERT_LOC_NONE && (sensors.node_lat != 0 || sensors.node_lon != 0))
|
||||
? createSelfAdvert(_prefs.node_name, sensors.node_lat, sensors.node_lon)
|
||||
: createSelfAdvert(_prefs.node_name);
|
||||
if (pkt) sendZeroHop(pkt);
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
// uint8_t holding that index; with a `values` table the field (1, 2 or 4 bytes,
|
||||
// `size`) holds values[index] instead -- seconds, millivolts, an hour offset,
|
||||
// or {1, 0} for a switch whose field is stored inverted. count == 2 with no
|
||||
// option labeller is an on/off switch.
|
||||
// option labeller is an on/off switch. A `bit` mask makes it a switch over that
|
||||
// one bit of a uint8_t field; size FLOAT_X100 is a float field with values in
|
||||
// hundredths.
|
||||
|
||||
#include <stddef.h>
|
||||
#include "../NodePrefs.h"
|
||||
@@ -21,28 +23,33 @@ namespace settings {
|
||||
// Pages (one screen each) and the sections on them, in display order.
|
||||
// PG_NAV is the Map's options (its trail / live share / target tools),
|
||||
// PG_ADVERT sits in Nearby's advert popup; the rest are Settings pages.
|
||||
enum Page : uint8_t { PG_NAV, PG_ADVERT, PG_DEVICE, PG_MESSAGES, PG_SOUND, PG_COUNT };
|
||||
// PG_PRIVACY and PG_RADIO (Radio's advanced options) are Settings pages too.
|
||||
enum Page : uint8_t { PG_NAV, PG_ADVERT, PG_DEVICE, PG_MESSAGES, PG_SOUND, PG_PRIVACY, PG_RADIO, PG_COUNT };
|
||||
enum Section : uint8_t { SEC_TRAIL, SEC_LIVE_SHARE, SEC_LOCATOR, SEC_ADVERT,
|
||||
SEC_DISPLAY, SEC_POWER, SEC_TIME, SEC_UNITS,
|
||||
SEC_MESSAGES, SEC_CONTACTS,
|
||||
SEC_SOUND, SEC_SOUND_FOR, SEC_QUIET, SEC_COUNT };
|
||||
SEC_MESSAGES, SEC_CONTACTS, SEC_AUTO_ADD,
|
||||
SEC_SOUND, SEC_SOUND_FOR, SEC_QUIET,
|
||||
SEC_PRIVACY, SEC_TELEMETRY, SEC_RADIO_ADV, SEC_COUNT };
|
||||
|
||||
static const char* pageTitle(uint8_t p) {
|
||||
static const char* T[PG_COUNT] = { "Map options", "Advert", "Display & power", "Messages & contacts", "Sound" };
|
||||
static const char* T[PG_COUNT] = { "Map options", "Advert", "Display & power", "Messages & contacts", "Sound",
|
||||
"Privacy", "Advanced radio" };
|
||||
return p < PG_COUNT ? T[p] : "";
|
||||
}
|
||||
static const char* sectionTitle(uint8_t s) {
|
||||
static const char* T[SEC_COUNT] = { "TRAIL", "LIVE SHARE", "ARRIVAL ALERT", "AUTOMATIC",
|
||||
"DISPLAY", "POWER", "TIME", "UNITS",
|
||||
"MESSAGES", "CONTACTS",
|
||||
"SOUND", "PLAYS FOR", "QUIET HOURS" };
|
||||
"MESSAGES", "CONTACTS", "ADDING CONTACTS",
|
||||
"SOUND", "PLAYS FOR", "QUIET HOURS",
|
||||
"ADVERTS", "WHO CAN ASK FOR", "ADVANCED" };
|
||||
return s < SEC_COUNT ? T[s] : "";
|
||||
}
|
||||
static uint8_t sectionPage(uint8_t s) {
|
||||
static const uint8_t P[SEC_COUNT] = { PG_NAV, PG_NAV, PG_NAV, PG_ADVERT,
|
||||
PG_DEVICE, PG_DEVICE, PG_DEVICE, PG_DEVICE,
|
||||
PG_MESSAGES, PG_MESSAGES,
|
||||
PG_SOUND, PG_SOUND, PG_SOUND };
|
||||
PG_MESSAGES, PG_MESSAGES, PG_MESSAGES,
|
||||
PG_SOUND, PG_SOUND, PG_SOUND,
|
||||
PG_PRIVACY, PG_PRIVACY, PG_RADIO };
|
||||
return s < SEC_COUNT ? P[s] : PG_DEVICE;
|
||||
}
|
||||
|
||||
@@ -57,8 +64,10 @@ struct Setting {
|
||||
// Side effect after a change (nullptr: none). The caller saves the prefs.
|
||||
void (*changed)(UiCore& core);
|
||||
const int32_t* values; // stored value per index; nullptr: the index itself
|
||||
uint8_t size; // field bytes (1, 2, 4)
|
||||
uint8_t size; // field bytes (1, 2, 4), or FLOAT_X100
|
||||
uint8_t bit; // nonzero: a switch over this bit of a uint8_t field
|
||||
};
|
||||
static const uint8_t FLOAT_X100 = 0x84; // size: a float, values in hundredths
|
||||
|
||||
// ── Value labels ──────────────────────────────────────────────────────────────
|
||||
#ifndef TRAIL_FIXED_MIN_DELTA_M
|
||||
@@ -152,12 +161,40 @@ static void optAdvertScope(uint8_t v, char* b, int n, const NodePrefs&) {
|
||||
snprintf(b, n, "%s", v == ADVERT_SOUND_SCOPE_ZERO_HOP ? "Direct" : "All");
|
||||
}
|
||||
|
||||
static void optTelemetry(uint8_t v, char* b, int n, const NodePrefs&) {
|
||||
static const char* const L[] = { "Nobody", "Allowed", "Everyone" }; // TELEM_MODE_*
|
||||
snprintf(b, n, "%s", L[v < 3 ? v : 0]);
|
||||
}
|
||||
static void optAutoAdd(uint8_t v, char* b, int n, const NodePrefs&) { snprintf(b, n, "%s", v ? "By type" : "All"); }
|
||||
// autoadd_max_hops: 0 = any, 1 = direct only, N = up to N-1 hops.
|
||||
static const int32_t MAX_HOPS[] = { 0, 1, 2, 3, 4, 6, 9 };
|
||||
static void optMaxHops(uint8_t v, char* b, int n, const NodePrefs&) {
|
||||
int32_t h = MAX_HOPS[v];
|
||||
if (h == 0) snprintf(b, n, "Any");
|
||||
else if (h == 1) snprintf(b, n, "Direct");
|
||||
else snprintf(b, n, "%ld hop%s", (long)(h - 1), h == 2 ? "" : "s");
|
||||
}
|
||||
static void optHashSize(uint8_t v, char* b, int n, const NodePrefs&) { snprintf(b, n, "%u byte%s", v + 1, v ? "s" : ""); }
|
||||
static const int32_t INT_THRESH[] = { 0, 8, 10, 12, 14, 16, 20 };
|
||||
static void optIntThresh(uint8_t v, char* b, int n, const NodePrefs&) {
|
||||
if (INT_THRESH[v] == 0) snprintf(b, n, "Off"); else snprintf(b, n, "%ld dB", (long)INT_THRESH[v]);
|
||||
}
|
||||
static const int32_t RX_DELAY[] = { 0, 200, 500, 1000, 2000 }; // rx_delay_base, x100
|
||||
static void optRxDelay(uint8_t v, char* b, int n, const NodePrefs&) {
|
||||
if (RX_DELAY[v] == 0) snprintf(b, n, "Off"); else snprintf(b, n, "%ld", (long)(RX_DELAY[v] / 100));
|
||||
}
|
||||
static const int32_t AIRTIME[] = { 0, 50, 100, 200, 300, 500, 900 }; // airtime_factor, x100
|
||||
static void optAirtime(uint8_t v, char* b, int n, const NodePrefs&) {
|
||||
snprintf(b, n, "%ld.%ld x", (long)(AIRTIME[v] / 100), (long)(AIRTIME[v] % 100 / 10));
|
||||
}
|
||||
|
||||
// ── Side effects ──────────────────────────────────────────────────────────────
|
||||
static void rearmLocator(UiCore& c) { c.locator.reset(); }
|
||||
static void restartShareClock(UiCore& c) { c.live_share.restartClock(); } // a new length starts over
|
||||
static void applyDisplay(UiCore& c) { c.host()->applyDisplayPrefs(); }
|
||||
static void applyGpsDuty(UiCore& c) { c.applyGpsInterval(); }
|
||||
static void applySound(UiCore& c) { c.host()->applySoundPrefs(); }
|
||||
static void applyRxGain(UiCore& c) { if (c.prefs()) radio_driver.setRxBoostedGainMode(c.prefs()->rx_boosted_gain); }
|
||||
|
||||
#define NP_OFF(f) (uint16_t)offsetof(NodePrefs, f)
|
||||
#define NP_SIZE(f) (uint8_t)sizeof(((NodePrefs*)0)->f)
|
||||
@@ -166,6 +203,8 @@ static void applySound(UiCore& c) { c.host()->applySoundPrefs(); }
|
||||
#define IDX(label, hint, sec, f, count, opt, chg) { label, hint, sec, NP_OFF(f), count, opt, chg, nullptr, 1 }
|
||||
#define SW(label, hint, sec, f, chg) { label, hint, sec, NP_OFF(f), 2, nullptr, chg, nullptr, 1 }
|
||||
#define MAP(label, hint, sec, f, vals, opt, chg) { label, hint, sec, NP_OFF(f), COUNT_OF(vals), opt, chg, vals, NP_SIZE(f) }
|
||||
#define MAPF(label, hint, sec, f, vals, opt) { label, hint, sec, NP_OFF(f), COUNT_OF(vals), opt, nullptr, vals, FLOAT_X100 }
|
||||
#define BIT(label, hint, sec, f, mask) { label, hint, sec, NP_OFF(f), 2, nullptr, nullptr, nullptr, 1, mask }
|
||||
static const Setting ALL[] = {
|
||||
#ifndef TRAIL_FIXED_MIN_DELTA_M // else every sample past a fixed jitter gate is kept
|
||||
IDX("Point spacing", "Between trail points", SEC_TRAIL, trail_min_delta_idx,
|
||||
@@ -194,7 +233,7 @@ static const Setting ALL[] = {
|
||||
NodePrefs::LOCATOR_MODE_COUNT, optLocMode, rearmLocator),
|
||||
SW("Proximity beeper", "Ticks faster when closer", SEC_LOCATOR, locator_beeper, nullptr),
|
||||
|
||||
MAP("Auto-advert", "Advert with your position", SEC_ADVERT, advert_auto_interval_sec, AUTO_ADVERT,
|
||||
MAP("Auto-advert", "Your advert, on a timer", SEC_ADVERT, advert_auto_interval_sec, AUTO_ADVERT,
|
||||
optAutoAdvert, nullptr),
|
||||
|
||||
IDX("Brightness", nullptr, SEC_DISPLAY, display_brightness, 5, optBrightness, applyDisplay),
|
||||
@@ -221,6 +260,15 @@ static const Setting ALL[] = {
|
||||
NodePrefs::CONTACT_EXPIRY_COUNT, optExpiry, nullptr),
|
||||
SW("Favourites first", "In contact and node lists", SEC_CONTACTS, fav_sort, nullptr),
|
||||
|
||||
// manual_add_contacts bit 0: 0 adds every node heard, 1 only the types below.
|
||||
IDX("Add heard nodes", "Every node, or only these types", SEC_AUTO_ADD, manual_add_contacts, 2, optAutoAdd, nullptr),
|
||||
BIT("Companions", nullptr, SEC_AUTO_ADD, autoadd_config, 0x02),
|
||||
BIT("Repeaters", nullptr, SEC_AUTO_ADD, autoadd_config, 0x04),
|
||||
BIT("Rooms", nullptr, SEC_AUTO_ADD, autoadd_config, 0x08),
|
||||
BIT("Sensors", nullptr, SEC_AUTO_ADD, autoadd_config, 0x10),
|
||||
MAP("Up to", "How far away a node may be", SEC_AUTO_ADD, autoadd_max_hops, MAX_HOPS, optMaxHops, nullptr),
|
||||
BIT("Replace oldest", "When the list is full; not favourites", SEC_AUTO_ADD, autoadd_config, 0x01),
|
||||
|
||||
// On / Off / Auto spans two fields (soundctl::setMode): the frontend's own row.
|
||||
IDX("Volume", nullptr, SEC_SOUND, buzzer_volume, 5, optVolume, applySound),
|
||||
SW("Quiet hours", "Muted, screen dark; alarms ring", SEC_QUIET, quiet_hours, nullptr),
|
||||
@@ -231,6 +279,23 @@ static const Setting ALL[] = {
|
||||
IDX("Channels", nullptr, SEC_SOUND_FOR, notif_melody_ch, soundctl::SOUND_COUNT, optSound, nullptr),
|
||||
IDX("Adverts", "A node announcing itself", SEC_SOUND_FOR, notif_melody_ad, soundctl::SOUND_COUNT, optSound, nullptr),
|
||||
IDX("Advert sound for", "Direct: no repeaters", SEC_SOUND_FOR, advert_sound_scope, 2, optAdvertScope, nullptr),
|
||||
|
||||
SW("Position in adverts", "Your adverts carry your location", SEC_PRIVACY, advert_loc_policy, nullptr),
|
||||
// TELEM_MODE_*: "Allowed" is the contacts given that permission.
|
||||
IDX("Status", "Battery; the two below need it", SEC_TELEMETRY, telemetry_mode_base, 3, optTelemetry, nullptr),
|
||||
IDX("Location", nullptr, SEC_TELEMETRY, telemetry_mode_loc, 3, optTelemetry, nullptr),
|
||||
IDX("Sensors", nullptr, SEC_TELEMETRY, telemetry_mode_env, 3, optTelemetry, nullptr),
|
||||
|
||||
IDX("Path hash size", "Bytes per repeater in a path", SEC_RADIO_ADV, path_hash_mode, 3, optHashSize, nullptr),
|
||||
SW("Double ACK", "Send each acknowledgement twice", SEC_RADIO_ADV, multi_acks, nullptr),
|
||||
SW("Listen before talk", "Wait for a quiet channel (CAD)", SEC_RADIO_ADV, cad_enabled, nullptr),
|
||||
MAP("Interference", "Busy above the noise floor by", SEC_RADIO_ADV, interference_threshold, INT_THRESH,
|
||||
optIntThresh, nullptr),
|
||||
#if defined(USE_SX1262) || defined(USE_SX1268) || defined(USE_LR2021)
|
||||
SW("Boosted RX gain", "Hears more, uses a little more power", SEC_RADIO_ADV, rx_boosted_gain, applyRxGain),
|
||||
#endif
|
||||
MAPF("RX delay", "Repeaters wait to pick the best path", SEC_RADIO_ADV, rx_delay_base, RX_DELAY, optRxDelay),
|
||||
MAPF("Airtime factor", "Pause after sending, times airtime", SEC_RADIO_ADV, airtime_factor, AIRTIME, optAirtime),
|
||||
};
|
||||
static const int COUNT = (int)(sizeof(ALL) / sizeof(ALL[0]));
|
||||
|
||||
@@ -249,8 +314,19 @@ static const struct { uint16_t offset; const char* text; } SHORT_LABELS[] = {
|
||||
{ NP_OFF(quiet_to), " until" }, { NP_OFF(notif_melody_dm), "DM sound" },
|
||||
{ NP_OFF(notif_melody_ch), "Ch sound" }, { NP_OFF(notif_melody_ad), "AD sound" },
|
||||
{ NP_OFF(advert_sound_scope), "AD scope" },
|
||||
{ NP_OFF(manual_add_contacts), "Auto-add" }, { NP_OFF(autoadd_max_hops), " up to" },
|
||||
{ NP_OFF(advert_loc_policy), "Advert pos" }, { NP_OFF(telemetry_mode_base), "Ask status" },
|
||||
{ NP_OFF(telemetry_mode_loc), "Ask loc" }, { NP_OFF(telemetry_mode_env), "Ask sens" },
|
||||
{ NP_OFF(path_hash_mode), "Hash size" }, { NP_OFF(multi_acks), "Double ACK" },
|
||||
{ NP_OFF(cad_enabled), "LBT (CAD)" }, { NP_OFF(interference_threshold), "Interf." },
|
||||
{ NP_OFF(rx_boosted_gain), "RX boost" }, { NP_OFF(rx_delay_base), "RX delay" },
|
||||
{ NP_OFF(airtime_factor), "Airtime" },
|
||||
};
|
||||
static const char* shortLabel(const Setting& s) {
|
||||
if (s.bit) { // the auto-add types share one field
|
||||
static const char* const B[] = { "Replace old", " companions", " repeaters", " rooms", " sensors" };
|
||||
for (int i = 0; i < 5; i++) if (s.bit == (1 << i)) return B[i];
|
||||
}
|
||||
for (auto& l : SHORT_LABELS) if (l.offset == s.offset) return l.text;
|
||||
return s.label;
|
||||
}
|
||||
@@ -262,12 +338,16 @@ static int indexOf(uint16_t offset) {
|
||||
#undef IDX
|
||||
#undef SW
|
||||
#undef MAP
|
||||
#undef MAPF
|
||||
#undef BIT
|
||||
#undef COUNT_OF
|
||||
#undef NP_SIZE
|
||||
#undef NP_OFF
|
||||
|
||||
static int32_t readRaw(const NodePrefs& p, const Setting& s) {
|
||||
const uint8_t* f = (const uint8_t*)&p + s.offset;
|
||||
if (s.bit) return (f[0] & s.bit) ? 1 : 0;
|
||||
if (s.size == FLOAT_X100) { float v; memcpy(&v, f, 4); return (int32_t)(v * 100 + (v < 0 ? -0.5f : 0.5f)); }
|
||||
bool sgn = false; // a table with negative values is over a signed field
|
||||
for (uint8_t i = 0; s.values && i < s.count; i++) if (s.values[i] < 0) sgn = true;
|
||||
if (s.size == 2) { uint16_t v; memcpy(&v, f, 2); return sgn ? (int32_t)(int16_t)v : (int32_t)v; }
|
||||
@@ -276,6 +356,8 @@ static int32_t readRaw(const NodePrefs& p, const Setting& s) {
|
||||
}
|
||||
static void writeRaw(NodePrefs& p, const Setting& s, int32_t v) {
|
||||
uint8_t* f = (uint8_t*)&p + s.offset;
|
||||
if (s.bit) { f[0] = v ? (f[0] | s.bit) : (f[0] & ~s.bit); return; }
|
||||
if (s.size == FLOAT_X100) { float w = v / 100.0f; memcpy(f, &w, 4); return; }
|
||||
if (s.size == 2) { uint16_t w = (uint16_t)v; memcpy(f, &w, 2); }
|
||||
else if (s.size == 4) { uint32_t w = (uint32_t)v; memcpy(f, &w, 4); }
|
||||
else f[0] = (uint8_t)v;
|
||||
|
||||
@@ -152,6 +152,7 @@ public:
|
||||
}
|
||||
|
||||
UiCoreHost* host() { return _host; }
|
||||
NodePrefs* prefs() { return _prefs; }
|
||||
|
||||
// ════ MyMesh::Listener ════════════════════════════════════════════════════
|
||||
|
||||
|
||||
@@ -96,13 +96,19 @@ void UITask::buildRadio() {
|
||||
paramRows(g, p, radioctl::currentPreset(p), p->freq, p->sf, p->bw, p->cr, onRadioDropdown, IDS, onRadioFreq,
|
||||
"Higher = longer range");
|
||||
groupNote(body, "Everyone you talk to needs the same settings.");
|
||||
fill(&UITask::radioGroup, 1, 3); // the rest as the loop goes
|
||||
fill(&UITask::radioGroup, 1, 4); // the rest as the loop goes
|
||||
}
|
||||
|
||||
void UITask::radioGroup(int i) {
|
||||
using namespace radioview;
|
||||
NodePrefs* p = _prefs;
|
||||
if (i == 2) { buildRadioExtras(_body); return; } // my presets, scopes (RadioExtras.h)
|
||||
if (i == 3) { // the schema's advanced options, a page of their own
|
||||
lv_obj_t* g = group(_body, nullptr);
|
||||
listRow(g, LV_SYMBOL_SETTINGS " Advanced", "Path hash size, ACKs, listen before talk, delays",
|
||||
onOpenSchemaPage, (void*)(uintptr_t)settings::PG_RADIO);
|
||||
return;
|
||||
}
|
||||
lv_obj_t* g = group(_body, "TRANSMIT");
|
||||
int o = 0;
|
||||
for (int v = TX_MIN; v <= TX_MAX; v++) o += snprintf(s_opts + o, OPTS_LEN - o, v > TX_MIN ? "\n%d dBm" : "%d dBm", v);
|
||||
|
||||
@@ -2399,6 +2399,8 @@ void UITask::back() {
|
||||
if (_settings_page == settings::PG_NAV && s_opts_from_map) {
|
||||
openMap(true);
|
||||
if (s_nav_section >= 0) navToolsPopup(); // one section's options: back into Map tools
|
||||
} else if (_settings_page == settings::PG_RADIO) {
|
||||
showRadio();
|
||||
} else {
|
||||
showSettings();
|
||||
}
|
||||
@@ -3548,6 +3550,7 @@ static lv_obj_t* switchRow(lv_obj_t* parent, const char* text, const char* sub,
|
||||
#include "MapRegions.h"
|
||||
#include "HomeScreen.h"
|
||||
#include "ClockScreen.h"
|
||||
static void onOpenSchemaPage(lv_event_t* e); // Radio > Advanced
|
||||
#include "RadioScreen.h"
|
||||
#include "WifiScreen.h"
|
||||
#include "ChannelScreen.h"
|
||||
@@ -4536,11 +4539,13 @@ void UITask::buildAboutPage(lv_obj_t* body) {
|
||||
// (fill); a popup's list gets them all now.
|
||||
static lv_obj_t* s_schema_body = nullptr;
|
||||
static bool s_schema_one = false; // one section: its own page, no heading
|
||||
static bool s_schema_bare = false; // no section headings
|
||||
|
||||
void UITask::schemaRows(lv_obj_t* body, uint8_t page) {
|
||||
for (lv_obj_t*& c : s_sec_card) c = nullptr;
|
||||
s_schema_body = body;
|
||||
s_schema_one = page == settings::PG_NAV && s_nav_section >= 0;
|
||||
s_schema_bare = s_schema_one || page == settings::PG_RADIO; // one section: the page title says it
|
||||
uint8_t sec = 0xFF;
|
||||
bool first = true;
|
||||
for (int i = 0; i < settings::COUNT; i++) {
|
||||
@@ -4557,7 +4562,7 @@ void UITask::schemaRows(lv_obj_t* body, uint8_t page) {
|
||||
// The group of the section setting i starts, with its rows.
|
||||
void UITask::schemaSection(int i) {
|
||||
uint8_t sec = settings::ALL[i].section;
|
||||
lv_obj_t* card = s_sec_card[sec] = group(s_schema_body, s_schema_one ? nullptr : settings::sectionTitle(sec));
|
||||
lv_obj_t* card = s_sec_card[sec] = group(s_schema_body, s_schema_bare ? nullptr : settings::sectionTitle(sec));
|
||||
if (sec == settings::SEC_SOUND) buildSoundRows(card, true); // On / Off / Auto
|
||||
for (int k = i; k < settings::COUNT && settings::ALL[k].section == sec; k++) {
|
||||
if (settings::ALL[k].offset == offsetof(NodePrefs, lock_compact)) continue; // ui-new's lock look; the slide card has one
|
||||
@@ -4684,7 +4689,7 @@ void UITask::settingsGroup(int i) {
|
||||
snprintf(sub, sizeof(sub), aa == ma ? "%s" : "%s + %s", SCRIPT[ma], SCRIPT[aa]);
|
||||
}
|
||||
listRow(g, LV_SYMBOL_KEYBOARD " Keyboard", sub, onOpenSchemaPage, (void*)(uintptr_t)PG_KEYBOARD);
|
||||
listRow(g, LV_SYMBOL_ENVELOPE " Messages & contacts", "Resend, expiry, quick messages",
|
||||
listRow(g, LV_SYMBOL_ENVELOPE " Messages & contacts", "Resend, adding, expiry, quick messages",
|
||||
onOpenSchemaPage, (void*)(uintptr_t)settings::PG_MESSAGES);
|
||||
break;
|
||||
|
||||
@@ -4696,6 +4701,8 @@ void UITask::settingsGroup(int i) {
|
||||
if (pi >= 0) radioctl::presetAt(_prefs, pi, pn, f, b, sf, cr);
|
||||
snprintf(sub, sizeof(sub), "%s - %.3f MHz, %d dBm", pn, _prefs->freq, _prefs->tx_power_dbm);
|
||||
listRow(g, UI_SYMBOL_RADIO " Radio", sub, onOpenRadio, NULL);
|
||||
listRow(g, LV_SYMBOL_EYE_CLOSE " Privacy", "Position in adverts, who can ask for telemetry",
|
||||
onOpenSchemaPage, (void*)(uintptr_t)settings::PG_PRIVACY);
|
||||
}
|
||||
bluetoothRow(g);
|
||||
wifiRow(g);
|
||||
|
||||
@@ -22,15 +22,15 @@ public:
|
||||
|
||||
enum Result { IGNORED, HANDLED, ACTIVATED };
|
||||
|
||||
static const int MAX_SECTIONS = 8; // collapse bitmask is 8 bits
|
||||
static const int MAX_ROWS = 64;
|
||||
static const int MAX_SECTIONS = 16; // collapse bitmask is 16 bits
|
||||
static const int MAX_ROWS = 128;
|
||||
|
||||
// (Re)initialise from per-section item counts. `collapsed` sets the initial
|
||||
// fold state for every section (Settings opens folded, so default true).
|
||||
void begin(const uint8_t* section_sizes, int section_count, bool collapsed = true) {
|
||||
_count = section_count > MAX_SECTIONS ? MAX_SECTIONS : section_count;
|
||||
for (int i = 0; i < _count; i++) _sizes[i] = section_sizes[i];
|
||||
_collapsed = collapsed ? 0xFF : 0x00;
|
||||
_collapsed = collapsed ? 0xFFFF : 0x0000;
|
||||
_sel = 0; _scroll = 0;
|
||||
rebuild();
|
||||
}
|
||||
@@ -68,7 +68,7 @@ public:
|
||||
// the cursor never jumps to an unrelated row when items appear/disappear.
|
||||
void toggle(int sec) {
|
||||
if (sec < 0 || sec >= _count) return;
|
||||
_collapsed ^= (uint8_t)(1u << sec);
|
||||
_collapsed ^= (uint16_t)(1u << sec);
|
||||
rebuild();
|
||||
for (int i = 0; i < _vis_count; i++)
|
||||
if (_rows[i].sec == sec && _rows[i].item < 0) { _sel = i; break; }
|
||||
@@ -77,7 +77,7 @@ public:
|
||||
private:
|
||||
uint8_t _sizes[MAX_SECTIONS];
|
||||
int _count = 0;
|
||||
uint8_t _collapsed = 0xFF;
|
||||
uint16_t _collapsed = 0xFFFF;
|
||||
Row _rows[MAX_ROWS];
|
||||
int _vis_count = 0;
|
||||
int _sel = 0, _scroll = 0;
|
||||
|
||||
@@ -46,6 +46,7 @@ class SettingsScreen : public UIScreen {
|
||||
#endif
|
||||
TX_APC,
|
||||
SCOPE_NAME,
|
||||
SCHEMA_RADIO, // the advanced radio options
|
||||
// System section
|
||||
SECTION_SYSTEM,
|
||||
DEVICE_NAME,
|
||||
@@ -61,7 +62,9 @@ class SettingsScreen : public UIScreen {
|
||||
KEYBOARD_CARDKB_COMPACT,
|
||||
#endif
|
||||
// Contacts section
|
||||
SECTION_CONTACTS, DM_FILTER, CH_FILTER, ROOM_FILTER, SCHEMA_CONTACTS, PRUNE_NOW,
|
||||
SECTION_CONTACTS, DM_FILTER, CH_FILTER, ROOM_FILTER, SCHEMA_CONTACTS, PRUNE_NOW, SCHEMA_AUTO_ADD,
|
||||
// Privacy section: what adverts carry, who may ask for telemetry
|
||||
SECTION_PRIVACY, SCHEMA_PRIVACY,
|
||||
// Messages section
|
||||
SECTION_MESSAGES,
|
||||
SCHEMA_MESSAGES,
|
||||
@@ -83,8 +86,8 @@ class SettingsScreen : public UIScreen {
|
||||
bool _keep_place = false; // set when opening a screen from a row, so Back lands on it
|
||||
|
||||
AccordionList _acc;
|
||||
static const int NUM_SECTIONS = 8;
|
||||
static const int MAX_PER_SEC = 16;
|
||||
static const int NUM_SECTIONS = 9;
|
||||
static const int MAX_PER_SEC = 20;
|
||||
uint8_t _sec_items[NUM_SECTIONS][MAX_PER_SEC]; // SettingItem per (section, row)
|
||||
uint8_t _sec_count[NUM_SECTIONS];
|
||||
uint8_t _sec_header[NUM_SECTIONS]; // the SECTION_* enum for each section
|
||||
@@ -136,7 +139,8 @@ class SettingsScreen : public UIScreen {
|
||||
item == SECTION_HOME_PAGES ||
|
||||
item == SECTION_RADIO || item == SECTION_SYSTEM ||
|
||||
item == SECTION_KEYBOARD ||
|
||||
item == SECTION_CONTACTS || item == SECTION_MESSAGES;
|
||||
item == SECTION_CONTACTS || item == SECTION_MESSAGES ||
|
||||
item == SECTION_PRIVACY;
|
||||
}
|
||||
|
||||
const char* sectionName(int item) const {
|
||||
@@ -148,6 +152,7 @@ class SettingsScreen : public UIScreen {
|
||||
if (item == SECTION_KEYBOARD) return "Keyboard";
|
||||
if (item == SECTION_CONTACTS) return "Contacts";
|
||||
if (item == SECTION_MESSAGES) return "Messages";
|
||||
if (item == SECTION_PRIVACY) return "Privacy";
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -178,7 +183,8 @@ class SettingsScreen : public UIScreen {
|
||||
|
||||
static bool isSchemaGroup(int item) {
|
||||
return item == SCHEMA_DISPLAY || item == SCHEMA_SOUND || item == SCHEMA_SYSTEM ||
|
||||
item == SCHEMA_CONTACTS || item == SCHEMA_MESSAGES;
|
||||
item == SCHEMA_CONTACTS || item == SCHEMA_MESSAGES || item == SCHEMA_RADIO || item == SCHEMA_AUTO_ADD ||
|
||||
item == SCHEMA_PRIVACY;
|
||||
}
|
||||
// Which schema sections a placeholder stands for.
|
||||
static bool schemaIn(int group, uint8_t sec) {
|
||||
@@ -188,6 +194,9 @@ class SettingsScreen : public UIScreen {
|
||||
case SCHEMA_SOUND: return sec == SEC_SOUND || sec == SEC_QUIET || sec == SEC_SOUND_FOR;
|
||||
case SCHEMA_SYSTEM: return sec == SEC_POWER || sec == SEC_UNITS;
|
||||
case SCHEMA_CONTACTS: return sec == SEC_CONTACTS;
|
||||
case SCHEMA_AUTO_ADD: return sec == SEC_AUTO_ADD;
|
||||
case SCHEMA_RADIO: return sec == SEC_RADIO_ADV;
|
||||
case SCHEMA_PRIVACY: return sec == SEC_PRIVACY || sec == SEC_TELEMETRY;
|
||||
case SCHEMA_MESSAGES: return sec == SEC_MESSAGES;
|
||||
}
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user