diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index d62c55b2c..b5dcae5d9 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -1,5 +1,6 @@ #include "UITask.h" #include +#include #include "../MyMesh.h" #include "target.h" #ifdef WIFI_SSID @@ -102,12 +103,7 @@ class HomeScreen : public UIScreen { void renderBatteryIndicator(DisplayDriver& display, uint16_t batteryMilliVolts) { - // Convert millivolts to percentage - const int minMilliVolts = 3000; // Minimum voltage (e.g., 3.0V) - const int maxMilliVolts = 4200; // Maximum voltage (e.g., 4.2V) - int batteryPercentage = ((batteryMilliVolts - minMilliVolts) * 100) / (maxMilliVolts - minMilliVolts); - if (batteryPercentage < 0) batteryPercentage = 0; // Clamp to 0% - if (batteryPercentage > 100) batteryPercentage = 100; // Clamp to 100% + int batteryPercentage = batteryPercentFromMilliVolts(batteryMilliVolts); // battery icon int iconWidth = 24; @@ -336,8 +332,20 @@ class HomeScreen : public UIScreen { strcpy(name, "gps"); sprintf(buf, "%.4f %.4f", lat, lon); break; case LPP_VOLTAGE: - r.readVoltage(v); - strcpy(name, "voltage"); sprintf(buf, "%6.2f", v); + r.readVoltage(v); // v is in volts + + if (channel == TELEM_CHANNEL_SELF) { + // This is our own battery voltage + uint16_t batteryMilliVolts = (uint16_t)(v * 1000.0f + 0.5f); // convert V -> mV + int pct = batteryPercentFromMilliVolts(batteryMilliVolts); + + strcpy(name, "battery"); + sprintf(buf, "%4.2fV %3d%%", v, pct); + } else { + // Other voltage sensor + strcpy(name, "voltage"); + sprintf(buf, "%6.2f", v); + } break; case LPP_CURRENT: r.readCurrent(v); diff --git a/src/helpers/Battery.cpp b/src/helpers/Battery.cpp new file mode 100644 index 000000000..579ebbd73 --- /dev/null +++ b/src/helpers/Battery.cpp @@ -0,0 +1,70 @@ +#include +#include "Battery.h" + +// Build the OCV table from the configured macro. +// 11 entries: 100%, 90%, ..., 0%. +static const uint16_t kOcvTable[] = { OCV_ARRAY }; +static const size_t kOcvTableSize = sizeof(kOcvTable) / sizeof(kOcvTable[0]); + +#if defined(__cplusplus) && __cplusplus >= 201103L +static_assert(kOcvTableSize == 11, + "OCV_ARRAY must contain exactly 11 entries: 100%, 90%, ..., 0%."); +#endif + +int batteryPercentFromMilliVolts(uint16_t batteryMilliVolts) { + const uint8_t stepPct = 10; // distance between table entries (100 → 90 → ... → 0) + const size_t n = kOcvTableSize; // should be 11 + + if (NUM_CELLS_IN_SERIES > 1) { + // Adjust the input voltage to per-cell basis + batteryMilliVolts /= NUM_CELLS_IN_SERIES; + } + + if (n != 11 || batteryMilliVolts <= 0) { + // Error: invalid OCV_ARRAY table size or voltage + return -1; + } + + // Above or equal to "full" voltage → clamp to 100% + if (batteryMilliVolts >= kOcvTable[0]) { + return 100; + } + + // Below or equal to "empty" voltage → clamp to 0% + if (batteryMilliVolts <= kOcvTable[n - 1]) { + return 0; + } + + // Find the segment [i, i+1] where: + // vHigh >= batteryMilliVolts >= vLow + // and map that to [pctHigh, pctLow] = [100 - 10*i, 100 - 10*(i+1)] + for (size_t i = 0; i < n - 1; ++i) { + uint16_t vHigh = kOcvTable[i]; // higher voltage, higher % + uint16_t vLow = kOcvTable[i + 1]; // lower voltage, lower % + + if (batteryMilliVolts <= vHigh && batteryMilliVolts >= vLow) { + uint8_t pctHigh = 100 - i * stepPct; + uint8_t pctLow = 100 - (i + 1) * stepPct; + + uint16_t dv = vHigh - vLow; + if (dv == 0) { + return pctLow; + } + + // How far are we from the low voltage towards the high, as a fraction of the segment? + uint16_t pv = batteryMilliVolts - vLow; // in [0, dv] + + // Interpolate percentage within this 10% band. + uint8_t deltaPct = (uint32_t)pv * stepPct / dv; + int pct = pctLow + deltaPct; + + // Clamp to [0, 100] + if (pct < 0) pct = 0; + if (pct > 100) pct = 100; + return pct; + } + } + + // Should be unreachable if the table is monotonic and cases above are handled. + return -1; +} diff --git a/src/helpers/Battery.h b/src/helpers/Battery.h new file mode 100644 index 000000000..0ce174233 --- /dev/null +++ b/src/helpers/Battery.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include + +// ----------------------------------------------------------------------------- +// Open Circuit Voltage (OCV) map configuration +// +// OCV_ARRAY must expand to 11 integer millivolt values, corresponding to: +// +// 100%, 90%, 80%, 70%, 60%, 50%, 40%, 30%, 20%, 10%, 0% +// +// in *descending* voltage order. +// +// ----------------------------------------------------------------------------- + +#ifndef OCV_ARRAY + #ifdef CELL_TYPE_LIFEPO4 + #define OCV_ARRAY 3400, 3350, 3320, 3290, 3270, 3260, 3250, 3230, 3200, 3120, 3000 + #elif defined(CELL_TYPE_LEADACID) + #define OCV_ARRAY 2120, 2090, 2070, 2050, 2030, 2010, 1990, 1980, 1970, 1960, 1950 + #elif defined(CELL_TYPE_ALKALINE) + #define OCV_ARRAY 1580, 1400, 1350, 1300, 1280, 1250, 1230, 1190, 1150, 1100, 1000 + #elif defined(CELL_TYPE_NIMH) + #define OCV_ARRAY 1400, 1300, 1280, 1270, 1260, 1250, 1240, 1230, 1210, 1150, 1000 + #elif defined(CELL_TYPE_LTO) + #define OCV_ARRAY 2700, 2560, 2540, 2520, 2500, 2460, 2420, 2400, 2380, 2320, 1500 + #else + // Default Li-Ion / Li-Po + #define OCV_ARRAY 4190, 4050, 3990, 3890, 3800, 3720, 3630, 3530, 3420, 3300, 3100 + #endif +#endif + +/** + * Number of cells in series (for multi-cell battery packs) + * Default to 1 if not defined. + */ +#ifndef NUM_CELLS_IN_SERIES + #define NUM_CELLS_IN_SERIES 1 +#endif + + +/** + * Convert a battery voltage (in millivolts) to approximate state-of-charge (%), + * using the OCV curve defined by OCV_ARRAY. + * + * Assumes: + * - OCV_ARRAY has 11 entries, in descending order. + * - These correspond to 100, 90, 80, ..., 0 percent. + * - The input batteryMilliVolts is in the same scale as the OCV values. + * + * Returns an integer percentage in [0, 100] or -1 on error. + */ +int batteryPercentFromMilliVolts(uint16_t batteryMilliVolts);