mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-07-26 14:58:12 +00:00
fix(ui/dashboard): Batt% uses same LiPo curve + low_batt_mv cutoff as top bar
The Clock-page Batt% field used a separate hardcoded linear mapping (3000-4200 mV → 0-100%) that disagreed with the top-bar battery indicator, which uses a piecewise LiPo discharge curve and the user-configurable low_batt_mv cutoff (Settings → Low battery threshold) as the 0% anchor. The two readings could differ by 25+ percentage points for the same voltage, and the dashboard ignored the cutoff setting entirely. Extract battMvToPercent(mv, low_mv) as a file-static helper so both the top-bar indicator and the dashboard field share the same curve and the same cutoff source. Drop the stray BATT_MIN/MAX_MILLIVOLTS #defines that leaked from inside the dashboard code paths into the rest of the TU. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -119,6 +119,43 @@ static const int QUICK_MSGS_MAX = 10;
|
||||
#include "TrailScreen.h"
|
||||
#include "ToolsScreen.h"
|
||||
|
||||
#ifndef BATT_MIN_MILLIVOLTS
|
||||
#define BATT_MIN_MILLIVOLTS 3200
|
||||
#endif
|
||||
|
||||
// LiPo discharge curve: voltage (mV) → raw capacity (%). Shared by the top-bar
|
||||
// battery indicator and the dashboard Batt% field so both report the same
|
||||
// number for the same voltage. low_mv (typically NodePrefs.low_batt_mv, the
|
||||
// user-configurable auto-shutdown threshold in Settings) is rescaled to 0%
|
||||
// so the bar empties at the cutoff the user actually cares about.
|
||||
static int battMvToPercent(int mv, int low_mv) {
|
||||
static const struct { uint16_t mv; uint8_t pct; } CURVE[] = {
|
||||
{3200, 0}, {3300, 3}, {3400, 8}, {3500, 15},
|
||||
{3600, 25}, {3650, 33}, {3700, 45}, {3750, 58},
|
||||
{3800, 68}, {3900, 77}, {4000, 86}, {4100, 93}, {4200, 100}
|
||||
};
|
||||
static const int CURVE_LEN = sizeof(CURVE) / sizeof(CURVE[0]);
|
||||
auto curveAt = [&](int v) -> int {
|
||||
if (v <= (int)CURVE[0].mv) return CURVE[0].pct;
|
||||
if (v >= (int)CURVE[CURVE_LEN-1].mv) return CURVE[CURVE_LEN-1].pct;
|
||||
for (int i = 1; i < CURVE_LEN; i++) {
|
||||
if (v <= (int)CURVE[i].mv) {
|
||||
int span_mv = CURVE[i].mv - CURVE[i-1].mv;
|
||||
int span_pct = CURVE[i].pct - CURVE[i-1].pct;
|
||||
return CURVE[i-1].pct + (v - (int)CURVE[i-1].mv) * span_pct / span_mv;
|
||||
}
|
||||
}
|
||||
return 100;
|
||||
};
|
||||
if (low_mv <= 0) low_mv = BATT_MIN_MILLIVOLTS;
|
||||
int raw_pct = curveAt(mv);
|
||||
int low_pct = curveAt(low_mv);
|
||||
int pct = (low_pct >= 100) ? 0 : (raw_pct - low_pct) * 100 / (100 - low_pct);
|
||||
if (pct < 0) pct = 0;
|
||||
if (pct > 100) pct = 100;
|
||||
return pct;
|
||||
}
|
||||
|
||||
// ── HomeScreen ────────────────────────────────────────────────────────────────
|
||||
class HomeScreen : public UIScreen {
|
||||
enum HomePage {
|
||||
@@ -288,39 +325,8 @@ class HomeScreen : public UIScreen {
|
||||
}
|
||||
|
||||
int renderBatteryIndicator(DisplayDriver& display, uint16_t batteryMilliVolts) {
|
||||
#ifndef BATT_MIN_MILLIVOLTS
|
||||
#define BATT_MIN_MILLIVOLTS 3200
|
||||
#endif
|
||||
// LiPo discharge curve: voltage (mV) → raw capacity (%)
|
||||
static const struct { uint16_t mv; uint8_t pct; } CURVE[] = {
|
||||
{3200, 0}, {3300, 3}, {3400, 8}, {3500, 15},
|
||||
{3600, 25}, {3650, 33}, {3700, 45}, {3750, 58},
|
||||
{3800, 68}, {3900, 77}, {4000, 86}, {4100, 93}, {4200, 100}
|
||||
};
|
||||
static const int CURVE_LEN = sizeof(CURVE) / sizeof(CURVE[0]);
|
||||
|
||||
auto curveAt = [&](int mv) -> int {
|
||||
if (mv <= (int)CURVE[0].mv) return CURVE[0].pct;
|
||||
if (mv >= (int)CURVE[CURVE_LEN-1].mv) return CURVE[CURVE_LEN-1].pct;
|
||||
for (int i = 1; i < CURVE_LEN; i++) {
|
||||
if (mv <= (int)CURVE[i].mv) {
|
||||
int span_mv = CURVE[i].mv - CURVE[i-1].mv;
|
||||
int span_pct = CURVE[i].pct - CURVE[i-1].pct;
|
||||
return CURVE[i-1].pct + (mv - (int)CURVE[i-1].mv) * span_pct / span_mv;
|
||||
}
|
||||
}
|
||||
return 100;
|
||||
};
|
||||
|
||||
int low_mv = (_node_prefs && _node_prefs->low_batt_mv > 0)
|
||||
? (int)_node_prefs->low_batt_mv : BATT_MIN_MILLIVOLTS;
|
||||
int raw_pct = curveAt((int)batteryMilliVolts);
|
||||
int low_pct = curveAt(low_mv);
|
||||
// rescale so low_mv = 0% and 4200mV = 100%
|
||||
int pct = (low_pct >= 100) ? 0
|
||||
: (raw_pct - low_pct) * 100 / (100 - low_pct);
|
||||
if (pct < 0) pct = 0;
|
||||
if (pct > 100) pct = 100;
|
||||
int low_mv = _node_prefs ? (int)_node_prefs->low_batt_mv : 0;
|
||||
int pct = battMvToPercent((int)batteryMilliVolts, low_mv);
|
||||
|
||||
uint8_t mode = (_node_prefs && _node_prefs->batt_display_mode < 3)
|
||||
? _node_prefs->batt_display_mode : 0;
|
||||
@@ -557,21 +563,9 @@ public:
|
||||
} else if (field == DASH_BATT_PCT) {
|
||||
strcpy(label, "Batt");
|
||||
uint16_t mv = _task->getBattMilliVolts();
|
||||
if (mv > 0) {
|
||||
// Simple linear voltage to percentage calculation
|
||||
#ifndef BATT_MIN_MILLIVOLTS
|
||||
#define BATT_MIN_MILLIVOLTS 3000
|
||||
#endif
|
||||
#ifndef BATT_MAX_MILLIVOLTS
|
||||
#define BATT_MAX_MILLIVOLTS 4200
|
||||
#endif
|
||||
int percent = ((mv - BATT_MIN_MILLIVOLTS) * 100) / (BATT_MAX_MILLIVOLTS - BATT_MIN_MILLIVOLTS);
|
||||
if (percent < 0) percent = 0;
|
||||
if (percent > 100) percent = 100;
|
||||
snprintf(val, sizeof(val), "%d%%", percent);
|
||||
} else {
|
||||
strcpy(val, "--");
|
||||
}
|
||||
if (mv > 0) snprintf(val, sizeof(val), "%d%%",
|
||||
battMvToPercent(mv, _node_prefs->low_batt_mv));
|
||||
else strcpy(val, "--");
|
||||
} else if (field == DASH_GPS) {
|
||||
strcpy(label, "GPS");
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
@@ -1438,7 +1432,7 @@ bool UITask::isButtonPressed() const {
|
||||
}
|
||||
|
||||
static void formatDashVal(uint8_t field, char* val, int val_len, uint16_t batt_mv,
|
||||
CayenneLPP* lpp = nullptr) {
|
||||
uint16_t low_batt_mv, CayenneLPP* lpp = nullptr) {
|
||||
val[0] = '\0';
|
||||
switch (field) {
|
||||
case DASH_NONE: return;
|
||||
@@ -1447,20 +1441,8 @@ static void formatDashVal(uint8_t field, char* val, int val_len, uint16_t batt_m
|
||||
else strcpy(val, "--");
|
||||
return;
|
||||
case DASH_BATT_PCT:
|
||||
if (batt_mv > 0) {
|
||||
#ifndef BATT_MIN_MILLIVOLTS
|
||||
#define BATT_MIN_MILLIVOLTS 3000
|
||||
#endif
|
||||
#ifndef BATT_MAX_MILLIVOLTS
|
||||
#define BATT_MAX_MILLIVOLTS 4200
|
||||
#endif
|
||||
int percent = ((batt_mv - BATT_MIN_MILLIVOLTS) * 100) / (BATT_MAX_MILLIVOLTS - BATT_MIN_MILLIVOLTS);
|
||||
if (percent < 0) percent = 0;
|
||||
if (percent > 100) percent = 100;
|
||||
snprintf(val, val_len, "%d%%", percent);
|
||||
} else {
|
||||
strcpy(val, "--");
|
||||
}
|
||||
if (batt_mv > 0) snprintf(val, val_len, "%d%%", battMvToPercent(batt_mv, low_batt_mv));
|
||||
else strcpy(val, "--");
|
||||
return;
|
||||
case DASH_NODES:
|
||||
snprintf(val, val_len, "%d nodes", the_mesh.getNumContacts());
|
||||
@@ -1704,8 +1686,8 @@ void UITask::loop() {
|
||||
if (isLPP(f0) || isLPP(f1)) {
|
||||
_dash_lpp.reset(); sensors.querySensors(0xFF, _dash_lpp); lpp_ptr = &_dash_lpp;
|
||||
}
|
||||
formatDashVal(f0, v0, sizeof(v0), _batt_mv, lpp_ptr);
|
||||
formatDashVal(f1, v1, sizeof(v1), _batt_mv, lpp_ptr);
|
||||
formatDashVal(f0, v0, sizeof(v0), _batt_mv, _node_prefs->low_batt_mv, lpp_ptr);
|
||||
formatDashVal(f1, v1, sizeof(v1), _batt_mv, _node_prefs->low_batt_mv, lpp_ptr);
|
||||
if (v0[0] || v1[0]) {
|
||||
int sv_y = date_y + lk_step;
|
||||
_display->setColor(DisplayDriver::LIGHT);
|
||||
|
||||
Reference in New Issue
Block a user