fix(ui): community v1.27 feedback — GPS altitude field, noise floor, Msg wake location, units

- Add a separate "Altitude (GPS)" dashboard field (label AltG) alongside
  the existing barometric one (now labelled "Altitude (Baro)") -- the
  original single Altitude field only ever read a barometric sensor's
  CayenneLPP telemetry, showing "--" on any board without one even with
  a perfectly good GPS fix.
- Every altitude reading (baro + GPS, Clock and Lock screen) and the GPS
  home page's "alt" row now go through a shared fmtAlt() helper and
  respect Settings > System > Units, same as every other distance in the
  UI -- none of them checked units_imperial before. Same fix for
  Locator's Radius row, which always showed metric regardless.
- Radio home page's noise floor showed "n/a" whenever Pwr save (RX
  duty-cycle) was on, on the assumption duty-cycle RX can't sample the
  floor. That's stale: RadioLibWrapper already periodically recalibrates
  it (noiseFloorCalibCheck(), every 60s) even under duty-cycle, which is
  exactly why Diagnostics' unconditional read of the same value was
  already showing something real. Radio home page now matches.
- Moved "Msg wake" from Settings > Sound to Settings > Display -- more
  logical home for a display-wake toggle than notifications. Menu-order
  only; NodePrefs storage is unaffected by this enum's ordering.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-09-10 20:12:29 +02:00
co-authored by Claude Sonnet 5
parent 6e0e981048
commit aa73400414
6 changed files with 49 additions and 19 deletions
@@ -40,12 +40,13 @@ Up to three data fields are shown below the date separator. Each field displays
| Humidity | Hum | % from onboard sensor | | Humidity | Hum | % from onboard sensor |
| Pressure | Pres | hPa from onboard sensor | | Pressure | Pres | hPa from onboard sensor |
| GPS | GPS | `lat lon` decimal degrees, or `no fix` | | GPS | GPS | `lat lon` decimal degrees, or `no fix` |
| Altitude | Alt | metres from onboard sensor (GPS or barometric) | | Altitude (Baro) | Alt | metres/feet (per Settings System Units) from onboard barometric sensor (`--` without one) |
| Luminosity | Lux | lux from onboard sensor | | Luminosity | Lux | lux from onboard sensor |
| CO₂ | CO2 | ppm from onboard sensor | | CO₂ | CO2 | ppm from onboard sensor |
| Contacts | Nodes | Total contacts in the mesh | | Contacts | Nodes | Total contacts in the mesh |
| Messages | Msgs | Total unread message count | | Messages | Msgs | Total unread message count |
| Satellites | Sats | GPS satellite count (or `--` without GPS) | | Satellites | Sats | GPS satellite count (or `--` without GPS) |
| Altitude (GPS) | AltG | metres/feet (per Settings System Units) from the GPS fix (or `no fix`) |
Sensor fields show `--` when the sensor is not connected or has no data. Sensor fields show `--` when the sensor is not connected or has no data.
@@ -27,6 +27,7 @@ Press **Cancel/Back** to save and return to the home screen.
| Display rotation _(e-ink only)_ | 0° / 90° / 180° / 270° | Applied immediately | | Display rotation _(e-ink only)_ | 0° / 90° / 180° / 270° | Applied immediately |
| Joystick rotation _(e-ink only)_ | 0° / 90° / 180° / 270° | Rotates input mapping independently of display rotation; useful for custom enclosures | | Joystick rotation _(e-ink only)_ | 0° / 90° / 180° / 270° | Rotates input mapping independently of display rotation; useful for custom enclosures |
| Full refresh interval _(e-ink only)_ | OFF / 5 / 10 / 20 / 30 | Partial refreshes between full clears; reduces ghosting on long sessions | | Full refresh interval _(e-ink only)_ | OFF / 5 / 10 / 20 / 30 | Partial refreshes between full clears; reduces ghosting on long sessions |
| Msg wake | ON / OFF | Whether an incoming message turns the display back on when it was off and no phone/app is connected (default ON — today's behaviour either way). |
--- ---
@@ -40,7 +41,6 @@ Press **Cancel/Back** to save and return to the home screen.
| Channel Melody | built-in / Melody 1 / Melody 2 / None | Notification sound for incoming channel messages. `None` disables the sound for this event. | | Channel Melody | built-in / Melody 1 / Melody 2 / None | Notification sound for incoming channel messages. `None` disables the sound for this event. |
| AD sound | built-in / Melody 1 / Melody 2 / None | Sound played whenever an **advert** is received from *any* node — pairs with Auto-Advert as an audible "in range" heartbeat (see Tools Auto-Advert). `None` disables the sound for this event. | | AD sound | built-in / Melody 1 / Melody 2 / None | Sound played whenever an **advert** is received from *any* node — pairs with Auto-Advert as an audible "in range" heartbeat (see Tools Auto-Advert). `None` disables the sound for this event. |
| AD scope | All / Zero-hop | Filters the AD sound so it plays for every advert or only for local zero-hop adverts. | | AD scope | All / Zero-hop | Filters the AD sound so it plays for every advert or only for local zero-hop adverts. |
| Msg wake | ON / OFF | Whether an incoming message turns the display back on when it was off and no phone/app is connected (default ON — today's behaviour either way). |
Melody 1 and Melody 2 are custom sequences editable in **Tools Ringtone Editor**. Melody 1 and Melody 2 are custom sequences editable in **Tools Ringtone Editor**.
@@ -16,7 +16,8 @@ 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_SATS = 12; static const uint8_t DASH_SATS = 12;
static const uint8_t DASH_COUNT = 13; static const uint8_t DASH_ALT_GPS = 13;
static const uint8_t DASH_COUNT = 14;
class DashboardConfigScreen : public UIScreen { class DashboardConfigScreen : public UIScreen {
UITask* _task; UITask* _task;
@@ -81,5 +82,6 @@ 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 %", "Sats" "GPS", "Altitude (Baro)", "Lux", "CO2", "Contacts", "Messages", "Batt %", "Sats",
"Altitude (GPS)"
}; };
@@ -92,8 +92,7 @@ public:
break; break;
case K_RADIUS: { case K_RADIUS: {
uint16_t r = NodePrefs::locatorRadiusMeters(_prefs ? _prefs->locator_radius_idx : 1); uint16_t r = NodePrefs::locatorRadiusMeters(_prefs ? _prefs->locator_radius_idx : 1);
if (r < 1000) snprintf(buf, n, "%um", (unsigned)r); geo::fmtDist(buf, n, r / 1000.0f, _task && _task->useImperial());
else snprintf(buf, n, "%.1fkm", r / 1000.0f);
break; break;
} }
case K_MODE: case K_MODE:
@@ -35,6 +35,7 @@ class SettingsScreen : public UIScreen {
#if FEAT_FULL_REFRESH_SETTING #if FEAT_FULL_REFRESH_SETTING
EINK_FULL_REFRESH, EINK_FULL_REFRESH,
#endif #endif
MSG_WAKE,
// Sound section // Sound section
SECTION_SOUND, SECTION_SOUND,
BUZZER, BUZZER,
@@ -43,7 +44,6 @@ class SettingsScreen : public UIScreen {
CH_MELODY, CH_MELODY,
AD_SOUND, AD_SOUND,
AD_SOUND_SCOPE, AD_SOUND_SCOPE,
MSG_WAKE,
// Home pages section // Home pages section
SECTION_HOME_PAGES, SECTION_HOME_PAGES,
HOME_CLOCK, HOME_FAVOURITES, HOME_RADIO, HOME_BT, HOME_ADVERT, HOME_CLOCK, HOME_FAVOURITES, HOME_RADIO, HOME_BT, HOME_ADVERT,
+40 -12
View File
@@ -299,7 +299,15 @@ static int drawClockTime(DisplayDriver& d, int top_y, const struct tm* ti,
// ── HomeScreen ──────────────────────────────────────────────────────────────── // ── HomeScreen ────────────────────────────────────────────────────────────────
// Forward declaration to be able to call formatDashVal from HomeScreen::render() // Forward declaration to be able to call formatDashVal from HomeScreen::render()
static void formatDashVal(uint8_t field, char* val, int val_len, uint16_t batt_mv, static void formatDashVal(uint8_t field, char* val, int val_len, uint16_t batt_mv,
uint16_t low_batt_mv, int unread, CayenneLPP* lpp = nullptr); uint16_t low_batt_mv, int unread, bool imperial, CayenneLPP* lpp = nullptr);
// Altitude (baro or GPS) respects Settings > System > Units, same as every
// other distance in the UI -- unlike geo::fmtDist, never switches to km/mi
// regardless of magnitude, since altitude is always discussed in the small unit.
static void fmtAlt(char* buf, int n, float meters, bool imperial) {
if (imperial) snprintf(buf, n, "%.0fft", meters * 3.28084f);
else snprintf(buf, n, "%.0fm", meters);
}
class HomeScreen : public UIScreen { class HomeScreen : public UIScreen {
enum HomePage { enum HomePage {
@@ -899,6 +907,17 @@ public:
else strcpy(val, "--"); else strcpy(val, "--");
#else #else
strcpy(val, "--"); strcpy(val, "--");
#endif
} else if (field == DASH_ALT_GPS) {
strcpy(label, "AltG");
#if ENV_INCLUDE_GPS == 1
LocationProvider* loc = sensors.getLocationProvider();
if (loc && loc->isValid())
fmtAlt(val, sizeof(val), loc->getAltitude() / 1000.0f, _node_prefs && _node_prefs->units_imperial);
else
strcpy(val, "no fix");
#else
strcpy(val, "--");
#endif #endif
} else if (field == DASH_NODES) { } else if (field == DASH_NODES) {
strcpy(label, "Nodes"); strcpy(label, "Nodes");
@@ -927,7 +946,7 @@ public:
case LPP_TEMPERATURE: r.readTemperature(v); snprintf(val, sizeof(val), "%.1f\xf8""C", v); break; case LPP_TEMPERATURE: r.readTemperature(v); snprintf(val, sizeof(val), "%.1f\xf8""C", v); break;
case LPP_RELATIVE_HUMIDITY: r.readRelativeHumidity(v); snprintf(val, sizeof(val), "%.0f%%", v); break; case LPP_RELATIVE_HUMIDITY: r.readRelativeHumidity(v); snprintf(val, sizeof(val), "%.0f%%", v); break;
case LPP_BAROMETRIC_PRESSURE: r.readPressure(v); snprintf(val, sizeof(val), "%.0fhPa", v); break; case LPP_BAROMETRIC_PRESSURE: r.readPressure(v); snprintf(val, sizeof(val), "%.0fhPa", v); break;
case LPP_ALTITUDE: r.readAltitude(v); snprintf(val, sizeof(val), "%.0fm", v); break; case LPP_ALTITUDE: r.readAltitude(v); fmtAlt(val, sizeof(val), v, _node_prefs && _node_prefs->units_imperial); break;
case LPP_LUMINOSITY: r.readLuminosity(v); snprintf(val, sizeof(val), "%.0flux", v); break; case LPP_LUMINOSITY: r.readLuminosity(v); snprintf(val, sizeof(val), "%.0flux", v); break;
case LPP_CONCENTRATION: r.readConcentration(v); snprintf(val, sizeof(val), "%.0fppm", v); break; case LPP_CONCENTRATION: r.readConcentration(v); snprintf(val, sizeof(val), "%.0fppm", v); break;
} }
@@ -986,8 +1005,8 @@ public:
int unread = (f0 == DASH_MSGS || f1 == DASH_MSGS) int unread = (f0 == DASH_MSGS || f1 == DASH_MSGS)
? _task->getDMUnreadTotal() + _task->getChannelUnreadCount() + _task->getRoomUnreadCount() : 0; ? _task->getDMUnreadTotal() + _task->getChannelUnreadCount() + _task->getRoomUnreadCount() : 0;
uint16_t batt_mv = _task->getBattMilliVolts(); uint16_t batt_mv = _task->getBattMilliVolts();
formatDashVal(f0, v0, sizeof(v0), batt_mv, _node_prefs->low_batt_mv, unread, lpp_ptr); formatDashVal(f0, v0, sizeof(v0), batt_mv, _node_prefs->low_batt_mv, unread, _node_prefs->units_imperial, lpp_ptr);
formatDashVal(f1, v1, sizeof(v1), batt_mv, _node_prefs->low_batt_mv, unread, lpp_ptr); formatDashVal(f1, v1, sizeof(v1), batt_mv, _node_prefs->low_batt_mv, unread, _node_prefs->units_imperial, lpp_ptr);
if (v0[0] || v1[0]) { if (v0[0] || v1[0]) {
int sv_y = date_y + step; int sv_y = date_y + step;
display.setColor(DisplayDriver::LIGHT); display.setColor(DisplayDriver::LIGHT);
@@ -1039,11 +1058,12 @@ public:
snprintf(tmp, sizeof(tmp),"TX: %ddBm", radio_driver.getTxPower()); // live value (reflects APC) snprintf(tmp, sizeof(tmp),"TX: %ddBm", radio_driver.getTxPower()); // live value (reflects APC)
display.print(tmp); display.print(tmp);
display.setCursor(0, content_y + step * 3); display.setCursor(0, content_y + step * 3);
if (radio_driver.getPowerSaving()) { // duty-cycle RX doesn't sample the floor // Was gated to "n/a" while duty-cycle RX (Pwr save) was active, on the
snprintf(tmp, sizeof(tmp),"Noise floor: n/a"); // assumption that the floor only gets sampled during continuous RX --
} else { // stale since RadioLibWrapper's periodic recalibration (noiseFloorCalibCheck(),
snprintf(tmp, sizeof(tmp),"Noise floor: %d", radio_driver.getNoiseFloor()); // NF_CALIB_INTERVAL_MS) started keeping it fresh even under duty-cycle,
} // same live value Diagnostics already showed unconditionally.
snprintf(tmp, sizeof(tmp),"Noise floor: %d", radio_driver.getNoiseFloor());
display.print(tmp); display.print(tmp);
} else if (_page == HomePage::BLUETOOTH) { } else if (_page == HomePage::BLUETOOTH) {
display.setColor(DisplayDriver::LIGHT); display.setColor(DisplayDriver::LIGHT);
@@ -1105,7 +1125,7 @@ public:
display.drawTextRightAlign(display.width()-1, y, buf); display.drawTextRightAlign(display.width()-1, y, buf);
y += step; y += step;
display.drawTextLeftAlign(0, y, "alt"); display.drawTextLeftAlign(0, y, "alt");
snprintf(buf, sizeof(buf),"%.2f", nmea->getAltitude()/1000.); fmtAlt(buf, sizeof(buf), nmea->getAltitude() / 1000.0f, _node_prefs && _node_prefs->units_imperial);
display.drawTextRightAlign(display.width()-1, y, buf); display.drawTextRightAlign(display.width()-1, y, buf);
y += step; y += step;
} }
@@ -2240,7 +2260,7 @@ bool UITask::isButtonPressed() const {
} }
static void formatDashVal(uint8_t field, char* val, int val_len, uint16_t batt_mv, static void formatDashVal(uint8_t field, char* val, int val_len, uint16_t batt_mv,
uint16_t low_batt_mv, int unread, CayenneLPP* lpp) { uint16_t low_batt_mv, int unread, bool imperial, CayenneLPP* lpp) {
val[0] = '\0'; val[0] = '\0';
switch (field) { switch (field) {
case DASH_NONE: return; case DASH_NONE: return;
@@ -2273,8 +2293,16 @@ static void formatDashVal(uint8_t field, char* val, int val_len, uint16_t batt_m
else strcpy(val, "--"); else strcpy(val, "--");
return; return;
} }
case DASH_ALT_GPS: {
LocationProvider* loc = sensors.getLocationProvider();
if (loc && loc->isValid())
fmtAlt(val, val_len, loc->getAltitude() / 1000.0f, imperial);
else strcpy(val, "no fix");
return;
}
#else #else
case DASH_SATS: case DASH_SATS:
case DASH_ALT_GPS:
strcpy(val, "--"); strcpy(val, "--");
return; return;
#endif #endif
@@ -2301,7 +2329,7 @@ static void formatDashVal(uint8_t field, char* val, int val_len, uint16_t batt_m
case LPP_TEMPERATURE: r.readTemperature(v); snprintf(val, val_len, "%.1f\xf8""C", v); return; case LPP_TEMPERATURE: r.readTemperature(v); snprintf(val, val_len, "%.1f\xf8""C", v); return;
case LPP_RELATIVE_HUMIDITY: r.readRelativeHumidity(v); snprintf(val, val_len, "%.0f%%", v); return; case LPP_RELATIVE_HUMIDITY: r.readRelativeHumidity(v); snprintf(val, val_len, "%.0f%%", v); return;
case LPP_BAROMETRIC_PRESSURE: r.readPressure(v); snprintf(val, val_len, "%.0fhPa", v); return; case LPP_BAROMETRIC_PRESSURE: r.readPressure(v); snprintf(val, val_len, "%.0fhPa", v); return;
case LPP_ALTITUDE: r.readAltitude(v); snprintf(val, val_len, "%.0fm", v); return; case LPP_ALTITUDE: r.readAltitude(v); fmtAlt(val, val_len, v, imperial); return;
case LPP_LUMINOSITY: r.readLuminosity(v); snprintf(val, val_len, "%.0flux", v); return; case LPP_LUMINOSITY: r.readLuminosity(v); snprintf(val, val_len, "%.0flux", v); return;
case LPP_CONCENTRATION: r.readConcentration(v); snprintf(val, val_len, "%.0fppm", v); return; case LPP_CONCENTRATION: r.readConcentration(v); snprintf(val, val_len, "%.0fppm", v); return;
} }