From 8dd17451495fa96cc87fe6f9a145d7c041b35e86 Mon Sep 17 00:00:00 2001 From: Maximilian Mewes Date: Thu, 5 Jan 2023 19:48:53 +0100 Subject: [PATCH] =?UTF-8?q?Add=20base=20battery=20=F0=9F=94=8B=20class,=20?= =?UTF-8?q?Add=20Lipo,=20Lion=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- usermods/Battery/battery.h | 149 +++++++++++++++++ usermods/Battery/battery_defaults.h | 11 +- usermods/Battery/lion.h | 37 +++++ usermods/Battery/lipo.h | 51 ++++++ usermods/Battery/usermod_v2_Battery.h | 221 +++++--------------------- 5 files changed, 290 insertions(+), 179 deletions(-) create mode 100644 usermods/Battery/battery.h create mode 100644 usermods/Battery/lion.h create mode 100644 usermods/Battery/lipo.h diff --git a/usermods/Battery/battery.h b/usermods/Battery/battery.h new file mode 100644 index 000000000..f3daf05c4 --- /dev/null +++ b/usermods/Battery/battery.h @@ -0,0 +1,149 @@ +#ifndef UMBBattery_h +#define UMBBattery_h + +#include "battery_defaults.h" + +/** + * Battery base class + * all other battery classes should inherit from this + */ +class Battery +{ + private: + + protected: + float minVoltage = USERMOD_BATTERY_MIN_VOLTAGE; + float maxVoltage = USERMOD_BATTERY_MAX_VOLTAGE; + unsigned int capacity = USERMOD_BATTERY_TOTAL_CAPACITY; // current capacity + float voltage = this->maxVoltage; // current voltage + int8_t level = 100; // current level + float calibration = USERMOD_BATTERY_CALIBRATION; // offset or calibration value to fine tune the calculated voltage + + float linearMapping(float v, float min, float max, float oMin = 0.0f, float oMax = 100.0f) + { + return (v-min) * (oMax-oMin) / (max-min) + oMin; + } + + public: + Battery() + { + + } + + /** + * Corresponding battery curves + * calculates the capacity in % (0-100) with given voltage and possible voltage range + */ + virtual float mapVoltage(float v, float min, float max) = 0; + // { + // example implementation, linear mapping + // return (v-min) * 100 / (max-min); + // }; + + virtual void calculateAndSetLevel(float voltage) = 0; + + + + /* + * + * Getter and Setter + * + */ + + /* + * Get lowest configured battery voltage + */ + virtual float getMinVoltage() + { + return this->minVoltage; + } + + /* + * Set lowest battery voltage + * can't be below 0 volt + */ + virtual void setMinVoltage(float voltage) + { + this->minVoltage = max(0.0f, voltage); + } + + /* + * Get highest configured battery voltage + */ + virtual float getMaxVoltage() + { + return this->maxVoltage; + } + + /* + * Set highest battery voltage + * can't be below minVoltage + */ + virtual void setMaxVoltage(float voltage) + { + #ifdef USERMOD_BATTERY_USE_LIPO + this->maxVoltage = max(getMinVoltage()+0.7f, voltage); + #else + this->maxVoltage = max(getMinVoltage()+1.0f, voltage); + #endif + } + + /* + * Get the capacity of all cells in parralel sumed up + * unit: mAh + */ + unsigned int getCapacity() + { + return this->capacity; + } + + void setCapacity(unsigned int capacity) + { + this->capacity = capacity; + } + + float getVoltage() + { + return this->voltage; + } + + /** + * check if voltage is within specified voltage range, allow 10% over/under voltage + */ + void setVoltage(float voltage) + { + this->voltage = ( (voltage < this->getMinVoltage() * 0.85f) || (voltage > this->getMaxVoltage() * 1.1f) ) + ? -1.0f + : voltage; + } + + float getLevel() + { + return this->level; + } + + void setLevel(float level) + { + this->level = constrain(level, 0.0f, 110.0f);; + } + + /* + * Get the configured calibration value + * a offset value to fine-tune the calculated voltage. + */ + virtual float getCalibration() + { + return calibration; + } + + /* + * Set the voltage calibration offset value + * a offset value to fine-tune the calculated voltage. + */ + virtual void setCalibration(float offset) + { + calibration = offset; + } +}; + +#endif \ No newline at end of file diff --git a/usermods/Battery/battery_defaults.h b/usermods/Battery/battery_defaults.h index c682cb45d..73f14f62a 100644 --- a/usermods/Battery/battery_defaults.h +++ b/usermods/Battery/battery_defaults.h @@ -14,6 +14,15 @@ #define USERMOD_BATTERY_MEASUREMENT_INTERVAL 30000 #endif + +/* Default Battery Type + * 1 = Lipo + * 2 = Lion + */ +#ifndef USERMOB_BATTERY_DEFAULT_TYPE + #define USERMOB_BATTERY_DEFAULT_TYPE 1 +#endif + // default for 18650 battery // https://batterybro.com/blogs/18650-wholesale-battery-reviews/18852515-when-to-recycle-18650-batteries-and-how-to-start-a-collection-center-in-your-vape-shop // Discharge voltage: 2.5 volt + .1 for personal safety @@ -69,4 +78,4 @@ #ifndef USERMOD_BATTERY_LOW_POWER_INDICATOR_DURATION #define USERMOD_BATTERY_LOW_POWER_INDICATOR_DURATION 5 -#endif \ No newline at end of file +#endif diff --git a/usermods/Battery/lion.h b/usermods/Battery/lion.h new file mode 100644 index 000000000..e8d78cc72 --- /dev/null +++ b/usermods/Battery/lion.h @@ -0,0 +1,37 @@ +#ifndef UMBLion_h +#define UMBLion_h + +#include "battery_defaults.h" +#include "battery.h" + +/** + * Lion Battery + * + */ +class Lion : public Battery +{ + private: + + public: + Lion() : Battery() + { + + } + + float mapVoltage(float v, float min, float max) override + { + return 0.0f; + }; + + void calculateAndSetLevel(float voltage) override + { + + }; + + virtual void setMaxVoltage(float voltage) override + { + this->maxVoltage = max(getMinVoltage()+1.0f, voltage); + } +}; + +#endif \ No newline at end of file diff --git a/usermods/Battery/lipo.h b/usermods/Battery/lipo.h new file mode 100644 index 000000000..4e9b0be7c --- /dev/null +++ b/usermods/Battery/lipo.h @@ -0,0 +1,51 @@ +#ifndef UMBLipo_h +#define UMBLipo_h + +#include "battery_defaults.h" +#include "battery.h" + +/** + * Lipo Battery + * + */ +class Lipo : public Battery +{ + private: + + public: + Lipo() : Battery() + { + + } + + /** + * LiPo batteries have a differnt dischargin curve, see + * https://blog.ampow.com/lipo-voltage-chart/ + */ + float mapVoltage(float v, float min, float max) override + { + float lvl = 0.0f; + lvl = this->linearMapping(v, min, max); // basic mapping + + if (lvl < 40.0f) + lvl = this->linearMapping(lvl, 0, 40, 0, 12); // last 45% -> drops very quickly + else { + if (lvl < 90.0f) + lvl = this->linearMapping(lvl, 40, 90, 12, 95); // 90% ... 40% -> almost linear drop + else // level > 90% + lvl = this->linearMapping(lvl, 90, 105, 95, 100); // highest 15% -> drop slowly + } + }; + + void calculateAndSetLevel(float voltage) override + { + this->setLevel(this->mapVoltage(voltage, this->getMinVoltage(), this->getMaxVoltage())); + }; + + virtual void setMaxVoltage(float voltage) override + { + this->maxVoltage = max(getMinVoltage()+0.7f, voltage); + } +}; + +#endif \ No newline at end of file diff --git a/usermods/Battery/usermod_v2_Battery.h b/usermods/Battery/usermod_v2_Battery.h index ac34a7e4d..4c77ca5dd 100644 --- a/usermods/Battery/usermod_v2_Battery.h +++ b/usermods/Battery/usermod_v2_Battery.h @@ -2,6 +2,9 @@ #include "wled.h" #include "battery_defaults.h" +#include "battery.h" +#include "lion.h" +#include "lipo.h" /* * Usermod by Maximilian Mewes @@ -15,28 +18,12 @@ class UsermodBattery : public Usermod private: // battery pin can be defined in my_config.h int8_t batteryPin = USERMOD_BATTERY_MEASUREMENT_PIN; + // Battery object + Battery* bat; // how often to read the battery voltage unsigned long readingInterval = USERMOD_BATTERY_MEASUREMENT_INTERVAL; unsigned long nextReadTime = 0; unsigned long lastReadTime = 0; - // battery min. voltage - float minBatteryVoltage = USERMOD_BATTERY_MIN_VOLTAGE; - // battery max. voltage - float maxBatteryVoltage = USERMOD_BATTERY_MAX_VOLTAGE; - // all battery cells summed up - unsigned int totalBatteryCapacity = USERMOD_BATTERY_TOTAL_CAPACITY; - // raw analog reading - float rawValue = 0.0f; - // calculated voltage - float voltage = maxBatteryVoltage; - // mapped battery level based on voltage - int8_t batteryLevel = 100; - // offset or calibration value to fine tune the calculated voltage - float calibration = USERMOD_BATTERY_CALIBRATION; - - // time left estimation feature - // bool calculateTimeLeftEnabled = USERMOD_BATTERY_CALCULATE_TIME_LEFT_ENABLED; - // float estimatedTimeLeft = 0.0; // auto shutdown/shutoff/master off feature bool autoOffEnabled = USERMOD_BATTERY_AUTO_OFF_ENABLED; @@ -63,14 +50,6 @@ class UsermodBattery : public Usermod static const char _preset[]; static const char _duration[]; static const char _init[]; - - - // custom map function - // https://forum.arduino.cc/t/floating-point-using-map-function/348113/2 - double mapf(double x, double in_min, double in_max, double out_min, double out_max) - { - return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; - } float dot2round(float x) { @@ -94,8 +73,8 @@ class UsermodBattery : public Usermod { if (!lowPowerIndicatorEnabled) return; if (batteryPin < 0) return; // no measurement - if (lowPowerIndicationDone && lowPowerIndicatorReactivationThreshold <= batteryLevel) lowPowerIndicationDone = false; - if (lowPowerIndicatorThreshold <= batteryLevel) return; + if (lowPowerIndicationDone && lowPowerIndicatorReactivationThreshold <= bat->getLevel()) lowPowerIndicationDone = false; + if (lowPowerIndicatorThreshold <= bat->getLevel()) return; if (lowPowerIndicationDone) return; if (lowPowerActivationTime <= 1) { lowPowerActivationTime = millis(); @@ -139,6 +118,16 @@ class UsermodBattery : public Usermod pinMode(batteryPin, INPUT); #endif + // this could also be handled with a factory class but for only 2 types now it should be sufficient + if(USERMOB_BATTERY_DEFAULT_TYPE == 1) { + bat = new Lipo(); + } else + if(USERMOB_BATTERY_DEFAULT_TYPE == 2) { + bat = new Lion(); + } else { + bat = new Lipo(); + } + nextReadTime = millis() + readingInterval; lastReadTime = millis(); @@ -174,8 +163,9 @@ class UsermodBattery : public Usermod if (batteryPin < 0) return; // nothing to read - initializing = false; - + initializing = false; + float voltage = -1.0f; + float rawValue = 0.0f; #ifdef ARDUINO_ARCH_ESP32 // use calibrated millivolts analogread on esp32 (150 mV ~ 2450 mV) rawValue = analogReadMilliVolts(batteryPin); @@ -188,40 +178,15 @@ class UsermodBattery : public Usermod rawValue = analogRead(batteryPin); // calculate the voltage - voltage = ((rawValue / getAdcPrecision()) * maxBatteryVoltage) + calibration; + voltage = ((rawValue / getAdcPrecision()) * bat->getMaxVoltage()) + bat->getCalibration(); #endif - // check if voltage is within specified voltage range, allow 10% over/under voltage - voltage = ((voltage < minBatteryVoltage * 0.85f) || (voltage > maxBatteryVoltage * 1.1f)) ? -1.0f : voltage; + bat->setVoltage(voltage); // translate battery voltage into percentage - /* - the standard "map" function doesn't work - https://www.arduino.cc/reference/en/language/functions/math/map/ notes and warnings at the bottom - */ - #ifdef USERMOD_BATTERY_USE_LIPO - batteryLevel = mapf(voltage, minBatteryVoltage, maxBatteryVoltage, 0, 100); // basic mapping - // LiPo batteries have a differnt dischargin curve, see - // https://blog.ampow.com/lipo-voltage-chart/ - if (batteryLevel < 40.0f) - batteryLevel = mapf(batteryLevel, 0, 40, 0, 12); // last 45% -> drops very quickly - else { - if (batteryLevel < 90.0f) - batteryLevel = mapf(batteryLevel, 40, 90, 12, 95); // 90% ... 40% -> almost linear drop - else // level > 90% - batteryLevel = mapf(batteryLevel, 90, 105, 95, 100); // highest 15% -> drop slowly - } - #else - batteryLevel = mapf(voltage, minBatteryVoltage, maxBatteryVoltage, 0, 100); - #endif - if (voltage > -1.0f) batteryLevel = constrain(batteryLevel, 0.0f, 110.0f); - - // if (calculateTimeLeftEnabled) { - // float currentBatteryCapacity = totalBatteryCapacity; - // estimatedTimeLeft = (currentBatteryCapacity/strip.currentMilliamps)*60; - // } + bat->calculateAndSetLevel(voltage); // Auto off -- Master power off - if (autoOffEnabled && (autoOffThreshold >= batteryLevel)) + if (autoOffEnabled && (autoOffThreshold >= bat->getLevel())) turnOff(); // SmartHome stuff @@ -254,16 +219,6 @@ class UsermodBattery : public Usermod // info modal display names JsonArray infoPercentage = user.createNestedArray(F("Battery level")); JsonArray infoVoltage = user.createNestedArray(F("Battery voltage")); - // if (calculateTimeLeftEnabled) - // { - // JsonArray infoEstimatedTimeLeft = user.createNestedArray(F("Estimated time left")); - // if (initializing) { - // infoEstimatedTimeLeft.add(FPSTR(_init)); - // } else { - // infoEstimatedTimeLeft.add(estimatedTimeLeft); - // infoEstimatedTimeLeft.add(F(" min")); - // } - // } JsonArray infoNextUpdate = user.createNestedArray(F("Next update")); infoNextUpdate.add((nextReadTime - millis()) / 1000); @@ -275,17 +230,17 @@ class UsermodBattery : public Usermod return; } - if (batteryLevel < 0) { + if (bat->getLevel() < 0) { infoPercentage.add(F("invalid")); } else { - infoPercentage.add(batteryLevel); + infoPercentage.add(bat->getLevel()); } infoPercentage.add(F(" %")); - if (voltage < 0) { + if (bat->getVoltage() < 0) { infoVoltage.add(F("invalid")); } else { - infoVoltage.add(dot2round(voltage)); + infoVoltage.add(dot2round(bat->getVoltage())); } infoVoltage.add(F(" V")); } @@ -298,7 +253,7 @@ class UsermodBattery : public Usermod /* void addToJsonState(JsonObject& root) { - + // TBD } */ @@ -310,6 +265,7 @@ class UsermodBattery : public Usermod /* void readFromJsonState(JsonObject& root) { + // TBD } */ @@ -356,18 +312,17 @@ class UsermodBattery : public Usermod battery[F("pin")] = batteryPin; #endif - // battery[F("time-left")] = calculateTimeLeftEnabled; - battery[F("min-voltage")] = minBatteryVoltage; - battery[F("max-voltage")] = maxBatteryVoltage; - battery[F("capacity")] = totalBatteryCapacity; - battery[F("calibration")] = calibration; + battery[F("min-voltage")] = bat->getMinVoltage(); + battery[F("max-voltage")] = bat->getMaxVoltage(); + battery[F("capacity")] = bat->getCapacity(); + battery[F("calibration")] = bat->getCalibration(); battery[FPSTR(_readInterval)] = readingInterval; - JsonObject ao = battery.createNestedObject(F("auto-off")); // auto off section + JsonObject ao = battery.createNestedObject(F("auto-off")); // auto off section ao[FPSTR(_enabled)] = autoOffEnabled; ao[FPSTR(_threshold)] = autoOffThreshold; - JsonObject lp = battery.createNestedObject(F("indicator")); // low power section + JsonObject lp = battery.createNestedObject(F("indicator")); // low power section lp[FPSTR(_enabled)] = lowPowerIndicatorEnabled; lp[FPSTR(_preset)] = lowPowerIndicatorPreset; // dropdown trickery (String)lowPowerIndicatorPreset; lp[FPSTR(_threshold)] = lowPowerIndicatorThreshold; @@ -432,11 +387,11 @@ class UsermodBattery : public Usermod #ifdef ARDUINO_ARCH_ESP32 newBatteryPin = battery[F("pin")] | newBatteryPin; #endif - // calculateTimeLeftEnabled = battery[F("time-left")] | calculateTimeLeftEnabled; - setMinBatteryVoltage(battery[F("min-voltage")] | minBatteryVoltage); - setMaxBatteryVoltage(battery[F("max-voltage")] | maxBatteryVoltage); - setTotalBatteryCapacity(battery[F("capacity")] | totalBatteryCapacity); - setCalibration(battery[F("calibration")] | calibration); + + bat->setMinVoltage(battery[F("min-voltage")] | bat->getMinVoltage()); + bat->setMaxVoltage(battery[F("max-voltage")] | bat->getMaxVoltage()); + bat->setCapacity(battery[F("capacity")] | bat->getCapacity()); + bat->setCalibration(battery[F("calibration")] | bat->getCalibration()); setReadingInterval(battery[FPSTR(_readInterval)] | readingInterval); JsonObject ao = battery[F("auto-off")]; @@ -479,7 +434,8 @@ class UsermodBattery : public Usermod } /* - * Generate a preset sample for low power indication + * TBD: Generate a preset sample for low power indication + * a button on the config page would be cool, currently not possible */ void generateExamplePreset() { @@ -539,60 +495,6 @@ class UsermodBattery : public Usermod readingInterval = max((unsigned long)3000, newReadingInterval); } - - /* - * Get lowest configured battery voltage - */ - float getMinBatteryVoltage() - { - return minBatteryVoltage; - } - - /* - * Set lowest battery voltage - * can't be below 0 volt - */ - void setMinBatteryVoltage(float voltage) - { - minBatteryVoltage = max(0.0f, voltage); - } - - /* - * Get highest configured battery voltage - */ - float getMaxBatteryVoltage() - { - return maxBatteryVoltage; - } - - /* - * Set highest battery voltage - * can't be below minBatteryVoltage - */ - void setMaxBatteryVoltage(float voltage) - { - #ifdef USERMOD_BATTERY_USE_LIPO - maxBatteryVoltage = max(getMinBatteryVoltage()+0.7f, voltage); - #else - maxBatteryVoltage = max(getMinBatteryVoltage()+1.0f, voltage); - #endif - } - - - /* - * Get the capacity of all cells in parralel sumed up - * unit: mAh - */ - unsigned int getTotalBatteryCapacity() - { - return totalBatteryCapacity; - } - - void setTotalBatteryCapacity(unsigned int capacity) - { - totalBatteryCapacity = capacity; - } - /* * Get the choosen adc precision * esp8266 = 10bit resolution = 1024.0f @@ -609,43 +511,6 @@ class UsermodBattery : public Usermod #endif } - /* - * Get the calculated voltage - * formula: (adc pin value / adc precision * max voltage) + calibration - */ - float getVoltage() - { - return voltage; - } - - /* - * Get the mapped battery level (0 - 100) based on voltage - * important: voltage can drop when a load is applied, so its only an estimate - */ - int8_t getBatteryLevel() - { - return batteryLevel; - } - - /* - * Get the configured calibration value - * a offset value to fine-tune the calculated voltage. - */ - float getCalibration() - { - return calibration; - } - - /* - * Set the voltage calibration offset value - * a offset value to fine-tune the calculated voltage. - */ - void setCalibration(float offset) - { - calibration = offset; - } - - /* * Get auto-off feature enabled status * is auto-off enabled, true/false