From 35b2b008c8cdd0344dec84fb628dd54dd94d1e86 Mon Sep 17 00:00:00 2001 From: Jakub <106778416+MarekZegare4@users.noreply.github.com> Date: Sun, 10 May 2026 19:47:12 +0200 Subject: [PATCH] Battery %: use LiPo discharge curve with variable shutdown threshold Replace linear voltage-to-percent formula with a LiPo discharge curve lookup table and linear interpolation. The curve is then rescaled so that low_batt_mv (configurable shutdown threshold) maps to 0% and 4200mV maps to 100%, preserving the non-linear shape regardless of the chosen threshold. Co-Authored-By: Claude Sonnet 4.6 --- examples/companion_radio/ui-new/UITask.cpp | 35 +++++++++++++++++----- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 86ed9982..6381c7fe 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -558,13 +558,34 @@ class HomeScreen : public UIScreen { #ifndef BATT_MIN_MILLIVOLTS #define BATT_MIN_MILLIVOLTS 3200 #endif -#ifndef BATT_MAX_MILLIVOLTS - #define BATT_MAX_MILLIVOLTS 4200 -#endif - // 0% anchor: low_batt_mv if set, otherwise BATT_MIN_MILLIVOLTS - int minMv = (_node_prefs && _node_prefs->low_batt_mv > 0) - ? (int)_node_prefs->low_batt_mv : BATT_MIN_MILLIVOLTS; - int pct = ((int)batteryMilliVolts - minMv) * 100 / (BATT_MAX_MILLIVOLTS - minMv); + // 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;