feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
#pragma once
|
|
|
|
|
#include <math.h>
|
2026-06-02 23:51:08 +02:00
|
|
|
#include "../GeoUtils.h"
|
2026-06-03 08:32:32 +02:00
|
|
|
#include "NavView.h"
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
|
|
|
|
|
#ifndef M_PI
|
|
|
|
|
#define M_PI 3.14159265358979323846
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
class NearbyScreen : public UIScreen {
|
|
|
|
|
UITask* _task;
|
|
|
|
|
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
int _visible = 4; // updated each render; used by handleInput for scroll clamping
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
|
2026-06-09 15:09:57 +02:00
|
|
|
static const int FILTER_COUNT = 7;
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
static const char* FILTER_LABELS[FILTER_COUNT];
|
|
|
|
|
static const uint8_t FILTER_TYPES[FILTER_COUNT];
|
|
|
|
|
|
2026-05-21 07:57:21 +02:00
|
|
|
// ── nearby list state ────────────────────────────────────────────────────────
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
struct Entry {
|
2026-05-14 16:29:04 +02:00
|
|
|
char name[32];
|
|
|
|
|
int32_t lat_e6, lon_e6;
|
|
|
|
|
float dist_km;
|
|
|
|
|
uint8_t type;
|
2026-05-21 07:57:21 +02:00
|
|
|
int contact_idx;
|
2026-05-21 00:13:36 +02:00
|
|
|
uint32_t lastmod;
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const int MAX_NEARBY = 32;
|
|
|
|
|
Entry _entries[MAX_NEARBY];
|
|
|
|
|
int _count;
|
|
|
|
|
int _sel;
|
|
|
|
|
int _scroll;
|
|
|
|
|
bool _detail;
|
2026-06-03 08:32:32 +02:00
|
|
|
bool _nav = false; // full-screen navigate-to-node view (over detail)
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
int32_t _own_lat, _own_lon;
|
|
|
|
|
bool _own_gps;
|
|
|
|
|
uint8_t _filter;
|
|
|
|
|
|
2026-05-14 16:29:04 +02:00
|
|
|
unsigned long _detail_refresh_ms;
|
2026-06-09 17:15:07 +02:00
|
|
|
unsigned long _list_refresh_ms = 0;
|
2026-05-14 16:29:04 +02:00
|
|
|
static const unsigned long DETAIL_REFRESH_MS = 10000UL;
|
2026-06-09 17:15:07 +02:00
|
|
|
static const unsigned long TIME_LIST_REFRESH_MS = 3000UL;
|
2026-05-14 16:29:04 +02:00
|
|
|
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
PopupMenu _ctx_menu;
|
2026-06-03 14:00:16 +02:00
|
|
|
PopupMenu _opts; // detail-view Options: Navigate / Ping
|
2026-05-29 21:49:48 +02:00
|
|
|
PopupMenu _ping_menu;
|
|
|
|
|
char _ping_time_str[24]; // For storing ping RTT result
|
|
|
|
|
char _ping_snr_out_str[24]; // For storing ping SNR out result
|
|
|
|
|
char _ping_snr_back_str[24];// For storing ping SNR back result
|
|
|
|
|
|
|
|
|
|
// ── ping state ─────────────────────────────────────────────────────────────
|
|
|
|
|
bool _pinging;
|
|
|
|
|
unsigned long _ping_started_ms;
|
|
|
|
|
static const unsigned long PING_TIMEOUT_MS = 3000UL;
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
|
2026-05-21 07:57:21 +02:00
|
|
|
// ── discover sub-screen state ────────────────────────────────────────────────
|
|
|
|
|
bool _discover_mode;
|
|
|
|
|
bool _discovering;
|
|
|
|
|
unsigned long _discover_started_ms;
|
|
|
|
|
static const unsigned long DISCOVER_DURATION_MS = 8000UL;
|
|
|
|
|
|
|
|
|
|
DiscoverResult _dresults[DISCOVER_RESULTS_MAX];
|
|
|
|
|
int _dresult_count;
|
|
|
|
|
int _dscroll;
|
2026-05-21 09:05:15 +02:00
|
|
|
int _dsel;
|
|
|
|
|
bool _ddetail;
|
2026-05-21 07:57:21 +02:00
|
|
|
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
int _d_visible = 2; // updated each render; used by handleInputDiscover
|
2026-05-21 22:08:28 +02:00
|
|
|
|
2026-05-21 07:57:21 +02:00
|
|
|
// ── helpers ──────────────────────────────────────────────────────────────────
|
2026-05-21 09:14:16 +02:00
|
|
|
static void pubKeyToBase64(const uint8_t* key, char* out, int out_len) {
|
|
|
|
|
static const char T[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
|
|
|
int j = 0;
|
|
|
|
|
for (int i = 0; i < PUB_KEY_SIZE && j + 5 < out_len; i += 3) {
|
|
|
|
|
uint32_t b = ((uint32_t)key[i] << 16)
|
|
|
|
|
| (i+1 < PUB_KEY_SIZE ? (uint32_t)key[i+1] << 8 : 0)
|
|
|
|
|
| (i+2 < PUB_KEY_SIZE ? (uint32_t)key[i+2] : 0);
|
|
|
|
|
out[j++] = T[(b >> 18) & 63];
|
|
|
|
|
out[j++] = T[(b >> 12) & 63];
|
|
|
|
|
if (i+1 < PUB_KEY_SIZE) out[j++] = T[(b >> 6) & 63];
|
|
|
|
|
if (i+2 < PUB_KEY_SIZE) out[j++] = T[b & 63];
|
|
|
|
|
}
|
|
|
|
|
out[j] = '\0';
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-02 23:51:08 +02:00
|
|
|
// Geographic helpers live in GeoUtils.h (geo::) — shared with Waypoints /
|
2026-06-04 00:15:36 +02:00
|
|
|
// trail course-over-ground. Call sites use geo:: directly.
|
2026-05-14 16:21:11 +02:00
|
|
|
|
2026-06-14 13:21:59 +02:00
|
|
|
bool useImperial() const { return _task && _task->useImperial(); }
|
|
|
|
|
|
|
|
|
|
// Save the currently-selected list entry as a waypoint (its name as label).
|
|
|
|
|
// Shared by the list context menu and the detail Options menu.
|
|
|
|
|
void saveSelectedWaypoint() {
|
|
|
|
|
if (_sel >= _count) return;
|
|
|
|
|
const Entry& e = _entries[_sel];
|
|
|
|
|
if (e.lat_e6 == 0 && e.lon_e6 == 0) { _task->showAlert("No node GPS", 1000); return; }
|
|
|
|
|
_task->addWaypoint(e.lat_e6, e.lon_e6, e.name); // WaypointStore truncates the label
|
2026-06-04 00:44:56 +02:00
|
|
|
}
|
|
|
|
|
|
2026-05-14 16:29:04 +02:00
|
|
|
static void fmtAge(char* buf, int n, uint32_t lastmod) {
|
|
|
|
|
uint32_t now = rtc_clock.getCurrentTime();
|
|
|
|
|
if (now < lastmod || lastmod == 0) { snprintf(buf, n, "unknown"); return; }
|
|
|
|
|
uint32_t age = now - lastmod;
|
|
|
|
|
if (age < 60) snprintf(buf, n, "%us ago", age);
|
|
|
|
|
else if (age < 3600) snprintf(buf, n, "%um ago", age / 60);
|
|
|
|
|
else if (age < 86400) snprintf(buf, n, "%uh ago", age / 3600);
|
|
|
|
|
else snprintf(buf, n, ">1d ago");
|
|
|
|
|
}
|
|
|
|
|
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
static const char* typeName(uint8_t t) {
|
|
|
|
|
switch (t) {
|
2026-05-14 15:49:23 +02:00
|
|
|
case ADV_TYPE_CHAT: return "Companion";
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
case ADV_TYPE_REPEATER: return "Repeater";
|
|
|
|
|
case ADV_TYPE_ROOM: return "Room";
|
|
|
|
|
case ADV_TYPE_SENSOR: return "Sensor";
|
|
|
|
|
default: return "Unknown";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void refresh() {
|
|
|
|
|
_count = 0;
|
|
|
|
|
_own_lat = _own_lon = 0;
|
2026-06-04 00:12:15 +02:00
|
|
|
_own_gps = _task->currentLocation(_own_lat, _own_lon);
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
|
|
|
|
|
int nc = the_mesh.getNumContacts();
|
|
|
|
|
for (int i = 0; i < nc && _count < MAX_NEARBY; i++) {
|
|
|
|
|
ContactInfo ci;
|
|
|
|
|
if (!the_mesh.getContactByIdx(i, ci)) continue;
|
2026-05-21 07:57:21 +02:00
|
|
|
if (_filter == 0 && !(ci.flags & 1)) continue;
|
2026-06-09 15:09:57 +02:00
|
|
|
if (_filter >= 2 && _filter < FILTER_COUNT - 1 && ci.type != FILTER_TYPES[_filter]) continue;
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
|
|
|
|
|
Entry& e = _entries[_count++];
|
|
|
|
|
strncpy(e.name, ci.name, sizeof(e.name) - 1);
|
|
|
|
|
e.name[sizeof(e.name) - 1] = '\0';
|
2026-05-14 15:49:23 +02:00
|
|
|
e.lat_e6 = ci.gps_lat;
|
|
|
|
|
e.lon_e6 = ci.gps_lon;
|
|
|
|
|
bool remote_gps = (ci.gps_lat != 0 || ci.gps_lon != 0);
|
|
|
|
|
e.dist_km = (_own_gps && remote_gps)
|
2026-06-04 00:15:36 +02:00
|
|
|
? geo::haversineKm(_own_lat, _own_lon, ci.gps_lat, ci.gps_lon)
|
2026-05-14 15:49:23 +02:00
|
|
|
: -1.0f;
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
e.type = ci.type;
|
|
|
|
|
e.contact_idx = i;
|
2026-05-14 16:29:04 +02:00
|
|
|
e.lastmod = ci.lastmod;
|
2026-05-21 00:13:36 +02:00
|
|
|
}
|
|
|
|
|
|
2026-06-09 17:23:50 +02:00
|
|
|
uint32_t _now_ts = rtc_clock.getCurrentTime();
|
2026-05-14 15:49:23 +02:00
|
|
|
for (int i = 0; i < _count - 1; i++) {
|
|
|
|
|
int best = i;
|
|
|
|
|
for (int j = i + 1; j < _count; j++) {
|
2026-06-09 15:09:57 +02:00
|
|
|
if (_filter == FILTER_COUNT - 1) {
|
2026-06-09 17:23:50 +02:00
|
|
|
// Treat lastmod=0 or lastmod>now (RTC not synced) as "unknown" → sort to bottom.
|
|
|
|
|
uint32_t tj = (_entries[j].lastmod > 0 && _now_ts >= _entries[j].lastmod) ? _entries[j].lastmod : 0;
|
|
|
|
|
uint32_t tb = (_entries[best].lastmod > 0 && _now_ts >= _entries[best].lastmod) ? _entries[best].lastmod : 0;
|
2026-06-09 15:09:57 +02:00
|
|
|
if (tj > 0 && (tb == 0 || tj > tb)) best = j; // descending — most recent first
|
|
|
|
|
} else {
|
|
|
|
|
float dj = _entries[j].dist_km, db = _entries[best].dist_km;
|
|
|
|
|
if (dj >= 0.0f && (db < 0.0f || dj < db)) best = j; // ascending — closest first
|
|
|
|
|
}
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
}
|
2026-05-14 15:49:23 +02:00
|
|
|
if (best != i) { Entry tmp = _entries[i]; _entries[i] = _entries[best]; _entries[best] = tmp; }
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_count == 0) {
|
|
|
|
|
_sel = _scroll = 0;
|
|
|
|
|
} else if (_sel >= _count) {
|
|
|
|
|
_sel = _count - 1;
|
|
|
|
|
if (_scroll > _sel) _scroll = _sel;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-21 07:57:21 +02:00
|
|
|
// ── discover sub-screen ──────────────────────────────────────────────────────
|
|
|
|
|
void enterDiscoverMode() {
|
|
|
|
|
_discover_mode = true;
|
|
|
|
|
_discovering = true;
|
2026-05-21 09:05:15 +02:00
|
|
|
_ddetail = false;
|
2026-05-21 07:57:21 +02:00
|
|
|
_discover_started_ms = millis();
|
|
|
|
|
_dresult_count = 0;
|
|
|
|
|
_dscroll = 0;
|
2026-05-21 09:05:15 +02:00
|
|
|
_dsel = 0;
|
2026-05-21 07:57:21 +02:00
|
|
|
the_mesh.sendNodeDiscoverReq();
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-29 21:49:48 +02:00
|
|
|
void resetPingLines() {
|
|
|
|
|
_ping_time_str[0] = '\0';
|
|
|
|
|
_ping_snr_out_str[0] = '\0';
|
|
|
|
|
_ping_snr_back_str[0] = '\0';
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-03 14:00:16 +02:00
|
|
|
// Number of rows the ping menu should currently show: "Send" plus only the
|
|
|
|
|
// result lines that are populated (avoids blank scrollable rows).
|
|
|
|
|
int pingRowCount() const {
|
|
|
|
|
return 1 + (_ping_time_str[0] ? 1 : 0)
|
|
|
|
|
+ (_ping_snr_out_str[0] ? 1 : 0)
|
|
|
|
|
+ (_ping_snr_back_str[0] ? 1 : 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void rebuildPingMenu() {
|
2026-06-04 15:46:57 +02:00
|
|
|
int keep = _ping_menu._sel; // preserve selection across a rebuild
|
2026-05-29 21:49:48 +02:00
|
|
|
_ping_menu.begin("Ping", 4);
|
2026-05-30 23:35:07 +02:00
|
|
|
_ping_menu.addItem("Send");
|
2026-06-03 14:00:16 +02:00
|
|
|
if (_ping_time_str[0]) _ping_menu.addItem(_ping_time_str);
|
|
|
|
|
if (_ping_snr_out_str[0]) _ping_menu.addItem(_ping_snr_out_str);
|
|
|
|
|
if (_ping_snr_back_str[0]) _ping_menu.addItem(_ping_snr_back_str);
|
2026-06-04 15:46:57 +02:00
|
|
|
if (keep > 0 && keep < _ping_menu._count) _ping_menu._sel = keep;
|
2026-05-29 21:49:48 +02:00
|
|
|
}
|
|
|
|
|
|
2026-06-03 14:00:16 +02:00
|
|
|
void openPingMenu() { rebuildPingMenu(); }
|
|
|
|
|
|
2026-05-29 21:49:48 +02:00
|
|
|
bool renderPingMenuIfActive(DisplayDriver& display) {
|
|
|
|
|
if (!_ping_menu.active) return false;
|
|
|
|
|
_ping_menu.render(display);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void closePingMenu(bool clear_task = true) {
|
|
|
|
|
_ping_menu.active = false;
|
|
|
|
|
_pinging = false;
|
|
|
|
|
if (clear_task && _task) _task->clearPing();
|
|
|
|
|
resetPingLines();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void startPingForKey(const uint8_t* pub_key) {
|
|
|
|
|
resetPingLines();
|
2026-05-30 23:35:07 +02:00
|
|
|
snprintf(_ping_time_str, sizeof(_ping_time_str), "RTT: ...");
|
2026-05-29 21:49:48 +02:00
|
|
|
if (_task && _task->startPing(pub_key)) {
|
|
|
|
|
_pinging = true;
|
|
|
|
|
_ping_started_ms = millis();
|
|
|
|
|
} else {
|
2026-06-03 14:11:03 +02:00
|
|
|
snprintf(_ping_time_str, sizeof(_ping_time_str), "RTT: send fail");
|
2026-05-29 21:49:48 +02:00
|
|
|
}
|
2026-06-03 14:11:03 +02:00
|
|
|
if (_ping_menu.active) rebuildPingMenu(); // surface "RTT: ..." immediately
|
2026-05-29 21:49:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void updatePingMenuState() {
|
|
|
|
|
if (!_ping_menu.active || !_task || (!_pinging && !_task->isPingActive())) return;
|
|
|
|
|
|
|
|
|
|
int16_t snr_out = 0, snr_back = 0;
|
|
|
|
|
uint32_t rtt = 0;
|
|
|
|
|
_task->getPingResult(snr_out, snr_back, rtt);
|
|
|
|
|
|
|
|
|
|
if (_pinging && (snr_out != 0 || snr_back != 0 || rtt != 0)) {
|
|
|
|
|
if (rtt > 0 && rtt < 10000) {
|
2026-05-30 23:35:07 +02:00
|
|
|
snprintf(_ping_time_str, sizeof(_ping_time_str), "RTT: %lums", rtt);
|
2026-05-29 21:49:48 +02:00
|
|
|
} else {
|
2026-05-30 23:35:07 +02:00
|
|
|
snprintf(_ping_time_str, sizeof(_ping_time_str), "RTT: timeout");
|
2026-05-29 21:49:48 +02:00
|
|
|
}
|
|
|
|
|
if (snr_out != 0) {
|
|
|
|
|
snprintf(_ping_snr_out_str, sizeof(_ping_snr_out_str), "SNR out: %.1f", snr_out / 4.0f);
|
|
|
|
|
}
|
|
|
|
|
if (snr_back != 0) {
|
|
|
|
|
snprintf(_ping_snr_back_str, sizeof(_ping_snr_back_str), "SNR back: %.1f", snr_back / 4.0f);
|
|
|
|
|
}
|
|
|
|
|
_pinging = false;
|
|
|
|
|
} else if (_pinging && millis() - _ping_started_ms >= PING_TIMEOUT_MS) {
|
2026-05-30 23:35:07 +02:00
|
|
|
snprintf(_ping_time_str, sizeof(_ping_time_str), "RTT: timeout");
|
2026-05-29 21:49:48 +02:00
|
|
|
_ping_snr_out_str[0] = '\0';
|
|
|
|
|
_ping_snr_back_str[0] = '\0';
|
|
|
|
|
_pinging = false;
|
|
|
|
|
if (_task) _task->clearPing();
|
|
|
|
|
}
|
2026-06-03 14:11:03 +02:00
|
|
|
// Keep the menu in sync with the populated result lines (grows as the
|
|
|
|
|
// reply arrives; never shows blank rows).
|
|
|
|
|
if (pingRowCount() != _ping_menu._count) rebuildPingMenu();
|
2026-05-29 21:49:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool handlePingMenuInput(char c, const uint8_t* pub_key, bool allow_enter_to_open = false) {
|
|
|
|
|
if (!_ping_menu.active) {
|
|
|
|
|
if (c == KEY_CONTEXT_MENU || (allow_enter_to_open && c == KEY_ENTER)) {
|
|
|
|
|
openPingMenu();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-30 23:35:07 +02:00
|
|
|
// Rows 1-3 are read-only result fields (RTT, SNR out, SNR back) — swallow
|
|
|
|
|
// UP/DOWN so the highlight stays on the "Ping" action and the user can't
|
|
|
|
|
// ENTER on a result row by accident.
|
|
|
|
|
if (c == KEY_UP || c == KEY_DOWN) return true;
|
|
|
|
|
|
2026-05-29 21:49:48 +02:00
|
|
|
auto res = _ping_menu.handleInput(c);
|
|
|
|
|
if (res == PopupMenu::SELECTED) {
|
|
|
|
|
if (!_pinging && pub_key) {
|
|
|
|
|
startPingForKey(pub_key);
|
|
|
|
|
}
|
|
|
|
|
// Keep the popup open so Ping stays available after completion.
|
|
|
|
|
_ping_menu.active = true;
|
|
|
|
|
} else if (res == PopupMenu::CANCELLED) {
|
|
|
|
|
closePingMenu();
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool selectedStoredPubKey(uint8_t* out_pub_key) const {
|
|
|
|
|
ContactInfo ci;
|
|
|
|
|
if (_task && _sel < _count && _entries[_sel].contact_idx >= 0 &&
|
|
|
|
|
the_mesh.getContactByIdx(_entries[_sel].contact_idx, ci)) {
|
|
|
|
|
memcpy(out_pub_key, ci.id.pub_key, PUB_KEY_SIZE);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const uint8_t* selectedDiscoverPubKey() const {
|
|
|
|
|
return (_dsel < _dresult_count) ? _dresults[_dsel].pub_key : nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool renderDiscoverDetail(DisplayDriver& display) {
|
|
|
|
|
const DiscoverResult& r = _dresults[_dsel];
|
|
|
|
|
const int hdr = display.headerH();
|
|
|
|
|
const char* fullType = (r.type == ADV_TYPE_REPEATER) ? "Repeater" :
|
|
|
|
|
(r.type == ADV_TYPE_SENSOR) ? "Sensor" :
|
|
|
|
|
(r.type == ADV_TYPE_ROOM) ? "Room" : "Node";
|
|
|
|
|
|
|
|
|
|
char label[32];
|
|
|
|
|
if (r.name[0]) { strncpy(label, r.name, 31); label[31] = '\0'; }
|
|
|
|
|
else { snprintf(label, sizeof(label), "[%s]", fullType); }
|
2026-06-14 13:21:59 +02:00
|
|
|
display.drawInvertedHeader(label);
|
2026-05-29 21:49:48 +02:00
|
|
|
|
|
|
|
|
char b64[48];
|
|
|
|
|
pubKeyToBase64(r.pub_key, b64, sizeof(b64));
|
|
|
|
|
{
|
|
|
|
|
int max_chars = (display.width() - 4) / display.getCharWidth();
|
|
|
|
|
int b64_len = strlen(b64);
|
|
|
|
|
char b64_line[48];
|
2026-05-31 00:11:43 +02:00
|
|
|
// Need at least 4 chars (one char + "..." ellipsis) to display anything
|
|
|
|
|
// meaningful; on a very narrow display skip the pubkey line entirely
|
|
|
|
|
// rather than risk a negative strncpy length.
|
|
|
|
|
if (max_chars < 4) {
|
|
|
|
|
b64_line[0] = '\0';
|
|
|
|
|
} else if (b64_len > max_chars) {
|
2026-05-29 21:49:48 +02:00
|
|
|
strncpy(b64_line, b64, max_chars - 3);
|
|
|
|
|
b64_line[max_chars - 3] = '\0';
|
|
|
|
|
strcat(b64_line, "...");
|
|
|
|
|
} else {
|
|
|
|
|
strncpy(b64_line, b64, sizeof(b64_line) - 1);
|
|
|
|
|
b64_line[sizeof(b64_line) - 1] = '\0';
|
|
|
|
|
}
|
2026-05-31 00:11:43 +02:00
|
|
|
if (b64_line[0]) {
|
|
|
|
|
display.setCursor(2, hdr);
|
|
|
|
|
display.print(b64_line);
|
|
|
|
|
}
|
2026-05-29 21:49:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int step = display.lineStep();
|
|
|
|
|
if (step * 5 > display.height() - hdr) step = (display.height() - hdr) / 5;
|
|
|
|
|
char buf[32];
|
|
|
|
|
snprintf(buf, sizeof(buf), "RSSI: %d dBm", (int)r.rssi);
|
|
|
|
|
display.setCursor(2, hdr + step); display.print(buf);
|
2026-05-31 00:11:43 +02:00
|
|
|
snprintf(buf, sizeof(buf), "SNR: %.1f dB", r.snr_x4 / 4.0f);
|
2026-05-29 21:49:48 +02:00
|
|
|
display.setCursor(2, hdr + step * 2); display.print(buf);
|
2026-05-31 00:11:43 +02:00
|
|
|
snprintf(buf, sizeof(buf), "Rem: %.1f dB", r.remote_snr_x4 / 4.0f);
|
2026-05-29 21:49:48 +02:00
|
|
|
display.setCursor(2, hdr + step * 3); display.print(buf);
|
|
|
|
|
display.setCursor(2, hdr + step * 4);
|
|
|
|
|
display.print(r.is_known ? "Status: known" : "Status: new");
|
|
|
|
|
|
|
|
|
|
updatePingMenuState();
|
|
|
|
|
if (renderPingMenuIfActive(display)) return true;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool renderStoredDetail(DisplayDriver& display) {
|
|
|
|
|
const Entry& e = _entries[_sel];
|
|
|
|
|
const int hdr = display.headerH();
|
|
|
|
|
|
2026-06-14 13:21:59 +02:00
|
|
|
display.drawInvertedHeader(e.name);
|
2026-05-29 21:49:48 +02:00
|
|
|
|
|
|
|
|
int step = display.lineStep();
|
|
|
|
|
if (step * 5 > display.height() - hdr) step = (display.height() - hdr) / 5;
|
|
|
|
|
char buf[32];
|
|
|
|
|
snprintf(buf, sizeof(buf), "Lat: %.5f", e.lat_e6 / 1e6);
|
|
|
|
|
display.setCursor(2, hdr); display.print(buf);
|
|
|
|
|
snprintf(buf, sizeof(buf), "Lon: %.5f", e.lon_e6 / 1e6);
|
|
|
|
|
display.setCursor(2, hdr + step); display.print(buf);
|
|
|
|
|
|
|
|
|
|
if (e.dist_km >= 0.0f) {
|
|
|
|
|
char dist[12];
|
2026-06-04 00:44:56 +02:00
|
|
|
geo::fmtDist(dist, sizeof(dist), e.dist_km, useImperial());
|
2026-06-04 00:15:36 +02:00
|
|
|
int az = geo::bearingDeg(_own_lat, _own_lon, e.lat_e6, e.lon_e6);
|
|
|
|
|
snprintf(buf, sizeof(buf), "Dist: %s %s", dist, geo::bearingCardinal(az));
|
2026-05-29 21:49:48 +02:00
|
|
|
} else {
|
|
|
|
|
snprintf(buf, sizeof(buf), "Dist: no GPS");
|
|
|
|
|
}
|
|
|
|
|
display.setCursor(2, hdr + step * 2); display.print(buf);
|
|
|
|
|
snprintf(buf, sizeof(buf), "Type: %s", typeName(e.type));
|
|
|
|
|
display.setCursor(2, hdr + step * 3); display.print(buf);
|
|
|
|
|
char age[16];
|
|
|
|
|
fmtAge(age, sizeof(age), e.lastmod);
|
|
|
|
|
snprintf(buf, sizeof(buf), "Seen: %s", age);
|
|
|
|
|
display.drawTextEllipsized(2, hdr + step * 4, display.width() - 4, buf);
|
|
|
|
|
|
|
|
|
|
updatePingMenuState();
|
2026-06-03 14:00:16 +02:00
|
|
|
if (_opts.active) { _opts.render(display); return true; }
|
2026-05-29 21:49:48 +02:00
|
|
|
if (renderPingMenuIfActive(display)) return true;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-21 07:57:21 +02:00
|
|
|
int renderDiscover(DisplayDriver& display) {
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
int lh = display.getLineHeight();
|
|
|
|
|
int hdr = display.headerH();
|
|
|
|
|
int d_box_h = 2 * lh + 3; // two text rows + padding
|
|
|
|
|
int d_item_h = d_box_h + 2;
|
|
|
|
|
int d_start_y = hdr;
|
|
|
|
|
_d_visible = display.listVisible(d_item_h);
|
|
|
|
|
if (_d_visible < 1) _d_visible = 1;
|
|
|
|
|
|
2026-05-21 09:05:15 +02:00
|
|
|
if (_ddetail) {
|
2026-05-29 21:49:48 +02:00
|
|
|
renderDiscoverDetail(display);
|
|
|
|
|
return _ping_menu.active ? 50 : 5000;
|
2026-05-21 09:05:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── list view ─────────────────────────────────────────────────────────────
|
2026-05-21 07:57:21 +02:00
|
|
|
_dresult_count = the_mesh.getDiscoverResults(_dresults, DISCOVER_RESULTS_MAX);
|
|
|
|
|
|
2026-05-21 08:56:54 +02:00
|
|
|
if (_discovering && millis() - _discover_started_ms >= DISCOVER_DURATION_MS)
|
2026-05-21 07:57:21 +02:00
|
|
|
_discovering = false;
|
|
|
|
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
char title[28];
|
2026-05-21 08:56:54 +02:00
|
|
|
if (_discovering)
|
2026-05-21 07:57:21 +02:00
|
|
|
snprintf(title, sizeof(title), "SCANNING... (%d)", _dresult_count);
|
2026-05-21 08:56:54 +02:00
|
|
|
else if (_dresult_count == 0)
|
|
|
|
|
snprintf(title, sizeof(title), "DISCOVER: none");
|
|
|
|
|
else
|
|
|
|
|
snprintf(title, sizeof(title), "DISCOVER (%d found)", _dresult_count);
|
2026-05-21 07:57:21 +02:00
|
|
|
display.drawTextCentered(display.width() / 2, 0, title);
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
display.fillRect(0, hdr - 1, display.width(), 1);
|
2026-05-21 07:57:21 +02:00
|
|
|
|
|
|
|
|
if (_dresult_count == 0) {
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
display.drawTextCentered(display.width() / 2, display.height() / 2,
|
2026-05-21 07:57:21 +02:00
|
|
|
_discovering ? "Waiting for replies..." : "No nodes found");
|
|
|
|
|
} else {
|
2026-05-21 09:05:15 +02:00
|
|
|
if (_dsel >= _dresult_count) _dsel = _dresult_count - 1;
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
if (_dscroll > _dresult_count - _d_visible)
|
|
|
|
|
_dscroll = _dresult_count > _d_visible ? _dresult_count - _d_visible : 0;
|
2026-05-21 22:08:28 +02:00
|
|
|
if (_dscroll < 0) _dscroll = 0;
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
|
|
|
|
|
for (int i = 0; i < _d_visible && (_dscroll + i) < _dresult_count; i++) {
|
2026-05-21 07:57:21 +02:00
|
|
|
int idx = _dscroll + i;
|
2026-05-21 09:05:15 +02:00
|
|
|
bool sel = (idx == _dsel);
|
2026-05-21 07:57:21 +02:00
|
|
|
const DiscoverResult& r = _dresults[idx];
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
int y = d_start_y + i * d_item_h;
|
2026-05-21 07:57:21 +02:00
|
|
|
|
2026-05-21 09:05:15 +02:00
|
|
|
const char* typeStr = (r.type == ADV_TYPE_REPEATER) ? "Rpt" :
|
|
|
|
|
(r.type == ADV_TYPE_SENSOR) ? "Snsr" :
|
|
|
|
|
(r.type == ADV_TYPE_ROOM) ? "Room" : "?";
|
2026-05-21 08:56:54 +02:00
|
|
|
|
2026-05-21 07:57:21 +02:00
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2026-05-21 09:05:15 +02:00
|
|
|
if (sel) {
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
display.fillRect(0, y, display.width(), d_box_h);
|
2026-05-21 09:05:15 +02:00
|
|
|
display.setColor(DisplayDriver::DARK);
|
|
|
|
|
} else {
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
display.drawRect(0, y, display.width(), d_box_h);
|
|
|
|
|
display.fillRect(1, y + 1, display.width() - 2, lh);
|
2026-05-21 09:05:15 +02:00
|
|
|
display.setColor(DisplayDriver::DARK);
|
|
|
|
|
}
|
2026-05-21 07:57:21 +02:00
|
|
|
|
2026-05-21 09:05:15 +02:00
|
|
|
// header: name left, type right
|
2026-05-21 07:57:21 +02:00
|
|
|
char label[32];
|
2026-05-21 09:05:15 +02:00
|
|
|
if (r.name[0]) { strncpy(label, r.name, 31); label[31] = '\0'; }
|
|
|
|
|
else {
|
|
|
|
|
const char* ft = (r.type == ADV_TYPE_REPEATER) ? "Repeater" :
|
|
|
|
|
(r.type == ADV_TYPE_SENSOR) ? "Sensor" :
|
|
|
|
|
(r.type == ADV_TYPE_ROOM) ? "Room" : "Node";
|
|
|
|
|
snprintf(label, sizeof(label), "[%s]", ft);
|
2026-05-21 07:57:21 +02:00
|
|
|
}
|
|
|
|
|
char filtered[32];
|
|
|
|
|
display.translateUTF8ToBlocks(filtered, label, sizeof(filtered));
|
2026-05-21 08:56:54 +02:00
|
|
|
int tw = display.getTextWidth(typeStr);
|
|
|
|
|
display.drawTextEllipsized(3, y + 1, display.width() - 6 - tw, filtered);
|
|
|
|
|
display.setCursor(display.width() - 3 - tw, y + 1);
|
|
|
|
|
display.print(typeStr);
|
|
|
|
|
|
2026-05-21 09:05:15 +02:00
|
|
|
// body: RSSI + SNR
|
|
|
|
|
display.setColor(sel ? DisplayDriver::DARK : DisplayDriver::LIGHT);
|
|
|
|
|
char sig[24];
|
2026-05-31 00:11:43 +02:00
|
|
|
snprintf(sig, sizeof(sig), "RSSI:%d SNR:%.1f", (int)r.rssi, r.snr_x4 / 4.0f);
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
display.drawTextEllipsized(3, y + lh + 2, display.width() - 6, sig);
|
2026-05-21 07:57:21 +02:00
|
|
|
}
|
|
|
|
|
|
2026-06-14 13:21:59 +02:00
|
|
|
display.drawScrollArrows(d_start_y, d_start_y + (_d_visible - 1) * d_item_h,
|
|
|
|
|
_dscroll > 0, _dscroll + _d_visible < _dresult_count);
|
2026-05-21 07:57:21 +02:00
|
|
|
}
|
|
|
|
|
|
2026-05-29 21:49:48 +02:00
|
|
|
updatePingMenuState();
|
|
|
|
|
|
|
|
|
|
// Show ping popup menu if active
|
|
|
|
|
if (_ping_menu.active) {
|
|
|
|
|
_ping_menu.render(display);
|
|
|
|
|
return 50;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-21 07:57:21 +02:00
|
|
|
return _discovering ? 200 : 2000;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool handleInputDiscover(char c) {
|
2026-05-21 09:05:15 +02:00
|
|
|
if (_ddetail) {
|
2026-05-29 21:49:48 +02:00
|
|
|
if (c == KEY_CANCEL) {
|
|
|
|
|
_ddetail = false;
|
|
|
|
|
closePingMenu();
|
|
|
|
|
return true;
|
2026-05-21 22:08:28 +02:00
|
|
|
}
|
2026-05-29 21:49:48 +02:00
|
|
|
|
|
|
|
|
if (handlePingMenuInput(c, selectedDiscoverPubKey())) return true;
|
2026-05-21 09:05:15 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
2026-05-21 07:57:21 +02:00
|
|
|
if (c == KEY_CANCEL) {
|
|
|
|
|
_discover_mode = false;
|
2026-05-21 09:05:15 +02:00
|
|
|
refresh();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (c == KEY_ENTER && _dresult_count > 0) {
|
|
|
|
|
_ddetail = true;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (c == KEY_CONTEXT_MENU) {
|
|
|
|
|
enterDiscoverMode(); // re-scan
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (c == KEY_UP && _dsel > 0) {
|
|
|
|
|
_dsel--;
|
|
|
|
|
if (_dsel < _dscroll) _dscroll = _dsel;
|
2026-05-21 07:57:21 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
2026-05-21 09:05:15 +02:00
|
|
|
if (c == KEY_DOWN && _dsel < _dresult_count - 1) {
|
|
|
|
|
_dsel++;
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
if (_dsel >= _dscroll + _d_visible) _dscroll = _dsel - _d_visible + 1;
|
2026-05-21 07:57:21 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
public:
|
|
|
|
|
NearbyScreen(UITask* task)
|
2026-05-21 22:08:28 +02:00
|
|
|
: _task(task), _count(0), _sel(0), _scroll(0), _detail(false),
|
|
|
|
|
_own_lat(0), _own_lon(0), _own_gps(false), _filter(0),
|
|
|
|
|
_detail_refresh_ms(0),
|
|
|
|
|
_discover_mode(false), _discovering(false), _discover_started_ms(0),
|
2026-05-29 21:49:48 +02:00
|
|
|
_dresult_count(0), _dscroll(0), _dsel(0), _ddetail(false),
|
|
|
|
|
_pinging(false), _ping_started_ms(0) {
|
|
|
|
|
_ping_time_str[0] = '\0';
|
|
|
|
|
_ping_snr_out_str[0] = '\0';
|
|
|
|
|
_ping_snr_back_str[0] = '\0';
|
|
|
|
|
}
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
|
|
|
|
|
void enter() {
|
|
|
|
|
_sel = _scroll = 0;
|
|
|
|
|
_detail = false;
|
2026-06-03 08:32:32 +02:00
|
|
|
_nav = false;
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
_filter = 0;
|
2026-05-21 07:57:21 +02:00
|
|
|
_discover_mode = false;
|
2026-05-21 09:05:15 +02:00
|
|
|
_ddetail = false;
|
|
|
|
|
_dsel = 0;
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
_ctx_menu.active = false;
|
2026-06-03 14:00:16 +02:00
|
|
|
_opts.active = false;
|
2026-05-29 21:49:48 +02:00
|
|
|
_ping_menu.active = false;
|
|
|
|
|
_pinging = false;
|
|
|
|
|
_ping_time_str[0] = '\0';
|
|
|
|
|
_ping_snr_out_str[0] = '\0';
|
|
|
|
|
_ping_snr_back_str[0] = '\0';
|
|
|
|
|
_task->clearPing();
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
refresh();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int render(DisplayDriver& display) override {
|
|
|
|
|
display.setTextSize(1);
|
|
|
|
|
|
2026-05-21 07:57:21 +02:00
|
|
|
// ── discover sub-screen ──────────────────────────────────────────────────
|
|
|
|
|
if (_discover_mode) return renderDiscover(display);
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
|
2026-05-14 16:29:04 +02:00
|
|
|
// periodic refresh in detail view — preserve selected contact by idx
|
|
|
|
|
if (_detail && millis() - _detail_refresh_ms >= DETAIL_REFRESH_MS) {
|
|
|
|
|
int saved_contact_idx = (_sel < _count) ? _entries[_sel].contact_idx : -1;
|
|
|
|
|
refresh();
|
|
|
|
|
bool found = false;
|
|
|
|
|
if (saved_contact_idx >= 0) {
|
|
|
|
|
for (int i = 0; i < _count; i++) {
|
|
|
|
|
if (_entries[i].contact_idx == saved_contact_idx) { _sel = i; found = true; break; }
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-06-03 08:36:41 +02:00
|
|
|
if (!found) { _detail = false; _nav = false; } // contact gone — drop both views
|
2026-05-14 16:29:04 +02:00
|
|
|
_detail_refresh_ms = millis();
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-03 08:32:32 +02:00
|
|
|
// ── navigate-to-node view ────────────────────────────────────────────────
|
|
|
|
|
if (_nav && _sel < _count) {
|
|
|
|
|
const Entry& e = _entries[_sel];
|
|
|
|
|
int cog; bool cogv = _task->currentCourse(cog);
|
|
|
|
|
navview::draw(display, _own_gps, _own_lat, _own_lon,
|
2026-06-04 00:44:56 +02:00
|
|
|
e.lat_e6, e.lon_e6, e.name, cogv, cog, useImperial());
|
2026-06-03 08:32:32 +02:00
|
|
|
return 1000;
|
|
|
|
|
}
|
|
|
|
|
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
// ── detail view ──────────────────────────────────────────────────────────
|
|
|
|
|
if (_detail && _sel < _count) {
|
2026-05-29 21:49:48 +02:00
|
|
|
renderStoredDetail(display);
|
|
|
|
|
return _ping_menu.active ? 50 : 2000;
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── list view ────────────────────────────────────────────────────────────
|
2026-06-09 17:15:07 +02:00
|
|
|
// In TIME mode re-sort periodically so newly-heard contacts bubble up.
|
|
|
|
|
if (_filter == FILTER_COUNT - 1 &&
|
|
|
|
|
millis() - _list_refresh_ms >= TIME_LIST_REFRESH_MS) {
|
|
|
|
|
refresh();
|
|
|
|
|
_list_refresh_ms = millis();
|
|
|
|
|
}
|
|
|
|
|
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
int item_h = display.lineStep();
|
|
|
|
|
int start_y = display.listStart();
|
|
|
|
|
int dist_col = display.width() - display.getCharWidth() * 7;
|
|
|
|
|
_visible = display.listVisible(item_h);
|
|
|
|
|
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2026-05-21 07:57:21 +02:00
|
|
|
char title[22];
|
|
|
|
|
snprintf(title, sizeof(title), "NEARBY[%s]", FILTER_LABELS[_filter]);
|
|
|
|
|
display.drawTextCentered(display.width() / 2, 0, title);
|
2026-05-22 23:13:27 +02:00
|
|
|
display.fillRect(0, display.headerH() - 1, display.width(), display.sepH());
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
|
|
|
|
|
if (_count == 0) {
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
display.drawTextCentered(display.width() / 2, display.height() / 2 - display.lineStep() / 2, "No contacts found");
|
|
|
|
|
display.drawTextCentered(display.width() / 2, display.height() / 2 + display.lineStep() / 2, "[Enter]=Discover");
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
} else {
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
for (int i = 0; i < _visible && (_scroll + i) < _count; i++) {
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
int idx = _scroll + i;
|
|
|
|
|
bool sel = (idx == _sel);
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
int y = start_y + i * item_h;
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
const Entry& e = _entries[idx];
|
|
|
|
|
|
refactor(ui): drawSelectionRow helper for the 5-line invert/restore pattern
Eight screens repeated the same if/else block to invert the row
background for selected items. Add DisplayDriver::drawSelectionRow that
sets LIGHT, optionally fills the rect, and leaves the colour as DARK
when sel (so the caller's text renders inverted). 61 lines removed
across SettingsScreen, KeyboardWidget (cells + special row), ToolsScreen,
DashboardConfigScreen, BotScreen, RingtoneEditorScreen (note slots +
menu), NearbyScreen, QuickMsgScreen (4 list views).
Card-style rows (QuickMsg hist, NearbyScreen discover, RingtoneEditor
notes display) keep their original drawRect/partial-fill outline for
unselected — they don't match the simple-invert pattern.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 20:33:02 +02:00
|
|
|
display.drawSelectionRow(0, y - 1, display.width(), item_h - 1, sel);
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
|
|
|
|
|
char filt[32];
|
|
|
|
|
display.translateUTF8ToBlocks(filt, e.name, sizeof(filt));
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
display.drawTextEllipsized(2, y, dist_col - 4, filt);
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
|
|
|
|
|
display.setColor(sel ? DisplayDriver::DARK : DisplayDriver::LIGHT);
|
2026-06-09 15:09:57 +02:00
|
|
|
char right[10];
|
|
|
|
|
if (_filter == FILTER_COUNT - 1) {
|
|
|
|
|
uint32_t now = rtc_clock.getCurrentTime();
|
2026-06-09 17:19:06 +02:00
|
|
|
if (e.lastmod == 0 || now < e.lastmod) {
|
|
|
|
|
snprintf(right, sizeof(right), "?");
|
|
|
|
|
} else {
|
|
|
|
|
uint32_t age = now - e.lastmod;
|
|
|
|
|
if (age < 60) snprintf(right, sizeof(right), "%us", age);
|
|
|
|
|
else if (age < 3600) snprintf(right, sizeof(right), "%um", age / 60);
|
|
|
|
|
else snprintf(right, sizeof(right), "%uh", age / 3600);
|
|
|
|
|
}
|
2026-06-09 15:09:57 +02:00
|
|
|
} else {
|
|
|
|
|
if (e.dist_km >= 0.0f) geo::fmtDist(right, sizeof(right), e.dist_km, useImperial());
|
|
|
|
|
else strncpy(right, "?GPS", sizeof(right));
|
|
|
|
|
}
|
2026-06-09 17:15:07 +02:00
|
|
|
// Right-align within the right column (2 px margin from edge).
|
|
|
|
|
display.setCursor(display.width() - display.getTextWidth(right) - 2, y);
|
2026-06-09 15:09:57 +02:00
|
|
|
display.print(right);
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
}
|
|
|
|
|
|
2026-06-14 13:21:59 +02:00
|
|
|
display.drawScrollArrows(start_y, start_y + (_visible - 1) * item_h,
|
|
|
|
|
_scroll > 0, _scroll + _visible < _count);
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_ctx_menu.active) {
|
|
|
|
|
_ctx_menu.render(display);
|
|
|
|
|
return 50;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-21 07:57:21 +02:00
|
|
|
return _count == 0 ? 3000 : 2000;
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool handleInput(char c) override {
|
2026-05-21 07:57:21 +02:00
|
|
|
// ── discover sub-screen ──────────────────────────────────────────────────
|
|
|
|
|
if (_discover_mode) return handleInputDiscover(c);
|
|
|
|
|
|
2026-06-03 08:32:32 +02:00
|
|
|
// ── navigate-to-node view — any nav key returns to detail ─────────────────
|
|
|
|
|
if (_nav) {
|
|
|
|
|
if (c == KEY_CANCEL || c == KEY_LEFT || c == KEY_PREV ||
|
|
|
|
|
c == KEY_RIGHT || c == KEY_NEXT) { _nav = false; }
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-21 07:57:21 +02:00
|
|
|
// ── detail view ─────────────────────────────────────────────────────────
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
if (_detail) {
|
2026-06-03 14:00:16 +02:00
|
|
|
// Options popup (Hold Enter) — Navigate / Ping.
|
|
|
|
|
if (_opts.active) {
|
|
|
|
|
auto res = _opts.handleInput(c);
|
|
|
|
|
if (res == PopupMenu::SELECTED) {
|
2026-06-09 15:09:57 +02:00
|
|
|
int idx = _opts.selectedIndex();
|
|
|
|
|
if (idx == 0) { // Navigate
|
2026-06-03 14:00:16 +02:00
|
|
|
if (_sel < _count) {
|
|
|
|
|
const Entry& e = _entries[_sel];
|
|
|
|
|
if (e.lat_e6 != 0 || e.lon_e6 != 0) _nav = true;
|
|
|
|
|
else _task->showAlert("No node GPS", 1000);
|
|
|
|
|
}
|
2026-06-09 15:09:57 +02:00
|
|
|
} else if (idx == 1) { // Ping
|
2026-06-03 14:00:16 +02:00
|
|
|
uint8_t pk[PUB_KEY_SIZE];
|
|
|
|
|
openPingMenu();
|
|
|
|
|
if (selectedStoredPubKey(pk)) startPingForKey(pk);
|
2026-06-14 13:21:59 +02:00
|
|
|
} else if (idx == 2) { // Save waypoint
|
|
|
|
|
saveSelectedWaypoint();
|
2026-06-03 14:00:16 +02:00
|
|
|
}
|
|
|
|
|
}
|
2026-06-03 08:32:32 +02:00
|
|
|
return true;
|
2026-05-29 21:49:48 +02:00
|
|
|
}
|
|
|
|
|
|
2026-06-03 14:00:16 +02:00
|
|
|
// Ping popup (opened from Options) consumes input while active.
|
|
|
|
|
if (_ping_menu.active) {
|
2026-06-04 15:46:57 +02:00
|
|
|
uint8_t pk[PUB_KEY_SIZE];
|
|
|
|
|
if (selectedStoredPubKey(pk)) handlePingMenuInput(c, pk); // guard: never send to a stale/garbage key
|
|
|
|
|
else closePingMenu();
|
2026-06-03 14:00:16 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
2026-06-03 08:32:32 +02:00
|
|
|
|
2026-06-03 14:00:16 +02:00
|
|
|
if (c == KEY_CANCEL) { _detail = false; closePingMenu(); return true; }
|
|
|
|
|
if (c == KEY_CONTEXT_MENU) {
|
2026-06-09 15:09:57 +02:00
|
|
|
_opts.begin("Options", 3);
|
2026-06-03 14:00:16 +02:00
|
|
|
_opts.addItem("Navigate");
|
|
|
|
|
_opts.addItem("Ping");
|
2026-06-09 15:09:57 +02:00
|
|
|
_opts.addItem("Save waypoint");
|
2026-06-03 14:00:16 +02:00
|
|
|
return true;
|
2026-06-03 08:32:32 +02:00
|
|
|
}
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-21 07:57:21 +02:00
|
|
|
// ── context menu ─────────────────────────────────────────────────────────
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
if (_ctx_menu.active) {
|
|
|
|
|
auto res = _ctx_menu.handleInput(c);
|
2026-06-09 15:09:57 +02:00
|
|
|
if (res == PopupMenu::SELECTED) {
|
|
|
|
|
int sel = _ctx_menu.selectedIndex();
|
|
|
|
|
if (sel == 0) {
|
|
|
|
|
enterDiscoverMode();
|
|
|
|
|
} else if (sel == 1 && _sel < _count) {
|
|
|
|
|
const Entry& e = _entries[_sel];
|
|
|
|
|
if (e.lat_e6 != 0 || e.lon_e6 != 0) _nav = true;
|
|
|
|
|
else _task->showAlert("No node GPS", 1000);
|
2026-06-14 13:21:59 +02:00
|
|
|
} else if (sel == 2) { // Save waypoint
|
|
|
|
|
saveSelectedWaypoint();
|
2026-06-09 15:09:57 +02:00
|
|
|
}
|
|
|
|
|
}
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-21 07:57:21 +02:00
|
|
|
// ── list view ────────────────────────────────────────────────────────────
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
if (c == KEY_CANCEL) { _task->gotoToolsScreen(); return true; }
|
|
|
|
|
if (c == KEY_CONTEXT_MENU) {
|
2026-06-09 15:09:57 +02:00
|
|
|
bool has_node = (_count > 0 && _sel < _count);
|
|
|
|
|
_ctx_menu.begin("Options", has_node ? 3 : 1);
|
2026-05-21 00:13:36 +02:00
|
|
|
_ctx_menu.addItem("Discover nearby");
|
2026-06-09 15:09:57 +02:00
|
|
|
if (has_node) {
|
|
|
|
|
_ctx_menu.addItem("Navigate");
|
|
|
|
|
_ctx_menu.addItem("Save waypoint");
|
|
|
|
|
}
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (c == KEY_UP && _sel > 0) {
|
|
|
|
|
_sel--;
|
|
|
|
|
if (_sel < _scroll) _scroll = _sel;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (c == KEY_DOWN && _sel < _count - 1) {
|
|
|
|
|
_sel++;
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
if (_sel >= _scroll + _visible) _scroll = _sel - _visible + 1;
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
2026-05-21 08:13:13 +02:00
|
|
|
if (c == KEY_ENTER && _count == 0) { enterDiscoverMode(); return true; }
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
if (c == KEY_ENTER && _count > 0) {
|
2026-05-14 16:17:52 +02:00
|
|
|
_detail = true;
|
2026-05-14 16:29:04 +02:00
|
|
|
_detail_refresh_ms = millis();
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (c == KEY_LEFT) {
|
|
|
|
|
_filter = (_filter + FILTER_COUNT - 1) % FILTER_COUNT;
|
|
|
|
|
refresh();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (c == KEY_RIGHT) {
|
|
|
|
|
_filter = (_filter + 1) % FILTER_COUNT;
|
|
|
|
|
refresh();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-06-09 15:09:57 +02:00
|
|
|
const char* NearbyScreen::FILTER_LABELS[7] = { "Fav", "ALL", "Comp", "Rpt", "Room", "Snsr", "TIME" };
|
|
|
|
|
const uint8_t NearbyScreen::FILTER_TYPES[7] = { 0, 0, ADV_TYPE_CHAT, ADV_TYPE_REPEATER, ADV_TYPE_ROOM, ADV_TYPE_SENSOR, 0 };
|