diff --git a/examples/companion_radio/UITask.cpp b/examples/companion_radio/UITask.cpp index 01906f9021..3ca48f1411 100644 --- a/examples/companion_radio/UITask.cpp +++ b/examples/companion_radio/UITask.cpp @@ -132,11 +132,7 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i } } -void UITask::renderBatteryIndicator(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); +void UITask::renderBatteryIndicator(int batteryPercentage) { if (batteryPercentage < 0) batteryPercentage = 0; // Clamp to 0% if (batteryPercentage > 100) batteryPercentage = 100; // Clamp to 100% @@ -211,7 +207,7 @@ void UITask::renderCurrScreen() { _display->print(_node_prefs->node_name); // battery voltage - renderBatteryIndicator(_board->getBattMilliVolts()); + renderBatteryIndicator(_board->getBattPercent()); // freq / sf _display->setCursor(0, 20); diff --git a/examples/companion_radio/UITask.h b/examples/companion_radio/UITask.h index 93a2ef8935..fccee51ea7 100644 --- a/examples/companion_radio/UITask.h +++ b/examples/companion_radio/UITask.h @@ -46,7 +46,7 @@ class UITask { void renderCurrScreen(); void userLedHandler(); - void renderBatteryIndicator(uint16_t batteryMilliVolts); + void renderBatteryIndicator(int batteryPercentage); // Button action handlers void handleButtonAnyPress(); diff --git a/src/MeshCore.h b/src/MeshCore.h index 98134e5056..ab798bab6c 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -1,6 +1,7 @@ #pragma once #include +#include "helpers/Battery.h" #define MAX_HASH_SIZE 8 #define PUB_KEY_SIZE 32 @@ -36,6 +37,11 @@ namespace mesh { class MainBoard { public: virtual uint16_t getBattMilliVolts() = 0; + uint16_t getBattPercent() { + uint16_t mv = getBattMilliVolts(); + return Battery::percent(getBattType(), mv); + } + uint8_t getBattType() { return Battery::BATT_LIPO; }; virtual const char* getManufacturerName() const = 0; virtual void onBeforeTransmit() { } virtual void onAfterTransmit() { } diff --git a/src/helpers/Battery.cpp b/src/helpers/Battery.cpp new file mode 100644 index 0000000000..f9c602f81f --- /dev/null +++ b/src/helpers/Battery.cpp @@ -0,0 +1,17 @@ +#include +#include "Battery.h" + +uint16_t Battery::percent(uint8_t batt, uint16_t mv) { + uint16_t last = mv; // set last to mv so it can never start greater than itself + uint16_t p = 0; + for (int i = 0; i < 101; i++) { // always 101 elements, 100 percentile soc points + // if we match the current value or are between the current and last value + // we return the lower of the the two, giving accuracy +/- 1% soc + if (mv <= last && mv >= Battery::curve[i][batt]) { + p = Battery::curve[i][Battery::percent_col]; + break; + } + last = Battery::curve[i][batt]; + } + return p; +}; \ No newline at end of file diff --git a/src/helpers/Battery.h b/src/helpers/Battery.h new file mode 100644 index 0000000000..03f8ecc809 --- /dev/null +++ b/src/helpers/Battery.h @@ -0,0 +1,43 @@ +/* +helpers/Battery.h defines battery chemistry types and their discharge curves +*/ + +class Battery { +public: + // TYPE is a unique ID for battery chemistry type + // NOTE: enum for curve must match index in table curve + enum TYPE { BATT_LIPO, BATT_LIFEPO4 }; + uint16_t percent(uint8_t batt, uint16_t mv); + + // percent_col is the column index of the battery percentage in the below table + const int percent_col = 2; + + // curve maps the battery voltage in mV to + // the approximate SoC for a typical cell + // Each column represents the mV for a given Soc foe that chemistry + // COL1 = LIPO, COL2 = LIFEPO4, COL3 = percent charge + // reference used https://blog.ampow.com/lipo-voltage-chart/ + // reference used https://cleversolarpower.com/lifepo4-voltage-chart/ + const uint16_t curve[100][3] = { + { 4200, 3400, 100 }, { 4190, 3395, 99 }, { 4180, 3390, 98 }, { 4170, 3385, 97 }, { 4160, 3380, 96 }, + { 4150, 3375, 95 }, { 4142, 3370, 94 }, { 4134, 3365, 93 }, { 4126, 3360, 92 }, { 4118, 3355, 91 }, + { 4110, 3350, 90 }, { 4104, 3347, 89 }, { 4098, 3344, 88 }, { 4092, 3341, 87 }, { 4086, 3338, 86 }, + { 4080, 3335, 85 }, { 4068, 3332, 84 }, { 4056, 3329, 83 }, { 4044, 3326, 82 }, { 4032, 3323, 81 }, + { 4020, 3320, 80 }, { 4012, 0, 79 }, { 4004, 0, 78 }, { 3996, 0, 77 }, { 3988, 0, 76 }, + { 3980, 0, 75 }, { 3974, 0, 74 }, { 3968, 0, 73 }, { 3962, 0, 72 }, { 3956, 0, 71 }, + { 3950, 3300, 70 }, { 3942, 0, 69 }, { 3934, 0, 68 }, { 3926, 0, 67 }, { 3918, 0, 66 }, + { 3910, 0, 65 }, { 3902, 0, 64 }, { 3894, 0, 63 }, { 3886, 0, 62 }, { 3878, 0, 61 }, + { 3870, 3270, 60 }, { 3850, 0, 55 }, { 3848, 0, 54 }, { 3846, 0, 53 }, { 3844, 0, 52 }, + { 3842, 0, 51 }, { 3840, 3260, 50 }, { 3836, 0, 49 }, { 3832, 0, 48 }, { 3828, 0, 47 }, + { 3824, 0, 46 }, { 3820, 0, 45 }, { 3816, 0, 44 }, { 3812, 0, 43 }, { 3808, 0, 42 }, + { 3804, 0, 41 }, { 3800, 3250, 40 }, { 3798, 0, 39 }, { 3796, 0, 38 }, { 3794, 0, 37 }, + { 3792, 0, 36 }, { 3790, 0, 35 }, { 3786, 0, 34 }, { 3782, 0, 33 }, { 3778, 0, 32 }, + { 3774, 0, 31 }, { 3770, 3220, 30 }, { 3766, 0, 29 }, { 3762, 0, 28 }, { 3758, 0, 27 }, + { 3754, 0, 26 }, { 3750, 0, 25 }, { 3746, 0, 24 }, { 3742, 0, 23 }, { 3738, 0, 22 }, + { 3724, 0, 21 }, { 3730, 3200, 20 }, { 3726, 0, 19 }, { 3722, 0, 18 }, { 3718, 0, 17 }, + { 3714, 0, 16 }, { 3710, 0, 15 }, { 3706, 0, 14 }, { 3702, 0, 13 }, { 3698, 0, 12 }, + { 3694, 0, 11 }, { 3690, 3000, 10 }, { 3674, 0, 9 }, { 3658, 0, 8 }, { 3642, 0, 7 }, + { 3626, 0, 6 }, { 3610, 0, 5 }, { 3542, 0, 4 }, { 3474, 0, 3 }, { 3406, 0, 2 }, + { 3338, 0, 1 }, { 3270, 2500, 0 } + }; +}; \ No newline at end of file