feat(ui): add Sats dashboard field

New DASH_SATS clock/lock-screen dashboard field, reading
LocationProvider::satellitesCount() (already used by the GPS Tools
panel, just not previously exposed as a dashboard option). Follows the
existing DASH_GPS pattern: always selectable, "--" when built without
GPS support. Added to both dashboard-render paths (HomeScreen's CLOCK
page inline loop and formatDashVal(), shared by the LOCK page).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-09-08 11:04:14 +02:00
co-authored by Claude Sonnet 5
parent dd87265d29
commit 57e029ccc4
2 changed files with 43 additions and 10 deletions
@@ -15,7 +15,8 @@ static const uint8_t DASH_CO2 = 8;
static const uint8_t DASH_NODES = 9; static const uint8_t DASH_NODES = 9;
static const uint8_t DASH_MSGS = 10; static const uint8_t DASH_MSGS = 10;
static const uint8_t DASH_BATT_PCT = 11; static const uint8_t DASH_BATT_PCT = 11;
static const uint8_t DASH_COUNT = 12; static const uint8_t DASH_SATS = 12;
static const uint8_t DASH_COUNT = 13;
class DashboardConfigScreen : public UIScreen { class DashboardConfigScreen : public UIScreen {
UITask* _task; UITask* _task;
@@ -80,5 +81,5 @@ public:
const char* DashboardConfigScreen::OPTION_NAMES[DASH_COUNT] = { const char* DashboardConfigScreen::OPTION_NAMES[DASH_COUNT] = {
"None", "Batt V", "Temp", "Humidity", "Pressure", "None", "Batt V", "Temp", "Humidity", "Pressure",
"GPS", "Altitude", "Lux", "CO2", "Contacts", "Messages", "Batt %" "GPS", "Altitude", "Lux", "CO2", "Contacts", "Messages", "Batt %", "Sats"
}; };
+40 -8
View File
@@ -435,7 +435,12 @@ class HomeScreen : public UIScreen {
return order[((cur + dir) % n + n) % n]; return order[((cur + dir) % n + n) % n];
} }
int renderBatteryIndicator(DisplayDriver& display, uint16_t batteryMilliVolts) { // reserve_left: how much width from x=0 must stay clear of status icons --
// the node name on every other page (name_min below), or the LOCK page's
// own clock (see the LOCK branch in render(), which passes its actual
// footprint here so the icon row sheds low-priority icons instead of
// drawing over the clock). -1 = use the normal name reserve.
int renderBatteryIndicator(DisplayDriver& display, uint16_t batteryMilliVolts, int reserve_left = -1) {
int low_mv = _node_prefs ? (int)_node_prefs->low_batt_mv : 0; int low_mv = _node_prefs ? (int)_node_prefs->low_batt_mv : 0;
int pct = battMvToPercent((int)batteryMilliVolts, low_mv); int pct = battMvToPercent((int)batteryMilliVolts, low_mv);
@@ -513,7 +518,7 @@ class HomeScreen : public UIScreen {
}; };
int x = battLeftX; int x = battLeftX;
const int name_min = display.getCharWidth() * 5; // always keep ~5 chars for the name const int name_min = (reserve_left >= 0) ? reserve_left : display.getCharWidth() * 5;
for (const Sicon& s : icons) { for (const Sicon& s : icons) {
if (!s.active) continue; if (!s.active) continue;
int ix = x - ind - ind_gap; int ix = x - ind - ind_gap;
@@ -748,7 +753,19 @@ public:
// Hidden on fullscreen pages (CLOCK). // Hidden on fullscreen pages (CLOCK).
if (_page != CLOCK) { if (_page != CLOCK) {
display.setColor(DisplayDriver::LIGHT); display.setColor(DisplayDriver::LIGHT);
int rightEdge = renderBatteryIndicator(display, _task->getBattMilliVolts()); int lock_reserve = -1;
if (_page == LOCK) {
// The lock screen's own clock shares row 0 with this title bar --
// reserve its real footprint instead of the usual name_min, so the
// (already priority-ordered) status icons shed low-priority ones as
// needed and never draw over it. Built-in font is fixed-width, so a
// worst-case digit string measures this without the actual time.
bool tall = display.height() > display.width();
display.setTextSize(tall ? 4 : 2);
lock_reserve = display.getTextWidth(tall ? "88" : "88:88");
display.setTextSize(1);
}
int rightEdge = renderBatteryIndicator(display, _task->getBattMilliVolts(), lock_reserve);
display.setColor(DisplayDriver::LIGHT); display.setColor(DisplayDriver::LIGHT);
if (_page != LOCK) { if (_page != LOCK) {
@@ -873,6 +890,15 @@ public:
strcpy(val, "no fix"); strcpy(val, "no fix");
#else #else
strcpy(val, "--"); strcpy(val, "--");
#endif
} else if (field == DASH_SATS) {
strcpy(label, "Sats");
#if ENV_INCLUDE_GPS == 1
LocationProvider* loc = sensors.getLocationProvider();
if (loc) snprintf(val, sizeof(val), "%ld", loc->satellitesCount());
else strcpy(val, "--");
#else
strcpy(val, "--");
#endif #endif
} else if (field == DASH_NODES) { } else if (field == DASH_NODES) {
strcpy(label, "Nodes"); strcpy(label, "Nodes");
@@ -938,11 +964,7 @@ public:
struct tm* ti = gmtime(&t); struct tm* ti = gmtime(&t);
char buf[12]; char buf[12];
bool h12 = _node_prefs && _node_prefs->clock_12h; bool h12 = _node_prefs && _node_prefs->clock_12h;
// top_y=lh, not 0: the title bar (battery + status icons, drawn above int date_y = drawClockTime(display, 0, ti, h12, /*show_sec*/false);
// for every _page != CLOCK, LOCK included) already occupies row 0 --
// starting the clock there overlapped it, worst on portrait e-ink's
// huge (size-4) HH/MM block, which fills most of the narrow width.
int date_y = drawClockTime(display, lh, ti, h12, /*show_sec*/false);
display.setTextSize(1); display.setTextSize(1);
static const char* wd[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"}; static const char* wd[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
static const char* mo[] = {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"}; static const char* mo[] = {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
@@ -2244,6 +2266,16 @@ static void formatDashVal(uint8_t field, char* val, int val_len, uint16_t batt_m
else strcpy(val, "no fix"); else strcpy(val, "no fix");
return; return;
} }
case DASH_SATS: {
LocationProvider* loc = sensors.getLocationProvider();
if (loc) snprintf(val, val_len, "%ld sats", loc->satellitesCount());
else strcpy(val, "--");
return;
}
#else
case DASH_SATS:
strcpy(val, "--");
return;
#endif #endif
default: break; default: break;
} }