#pragma once #include "wled.h" #include "IP5306_I2C.h" #include // the frequency to check battery voltage, 5 seconds #ifndef USERMOD_BATTERY_MEASUREMENT_INTERVAL #define USERMOD_BATTERY_MEASUREMENT_INTERVAL 5000 #endif #ifndef USERMOD_BATTERY_ADC_PRECISION #define USERMOD_BATTERY_ADC_PRECISION 4095.0f #endif #ifndef USERMOD_BATTERY_ADC_REF #define USERMOD_BATTERY_ADC_REF 3.3f #endif #ifndef USERMOD_BATTERY_ADC_R1 #define USERMOD_BATTERY_ADC_R1 8.0f #endif #ifndef USERMOD_BATTERY_ADC_R2 #define USERMOD_BATTERY_ADC_R2 2.5f #endif #ifndef USERMOD_BATTERY_MIN_VOLTAGE #define USERMOD_BATTERY_MIN_VOLTAGE 2.6f #endif #ifndef USERMOD_BATTERY_MAX_VOLTAGE #define USERMOD_BATTERY_MAX_VOLTAGE 4.2f #endif #ifndef USERMOD_IP5306_SDA #define USERMOD_IP5306_SDA 32 #endif #ifndef USERMOD_IP5306_SCL #define USERMOD_IP5306_SCL 33 #endif #ifndef USERMOD_ABL_BATTERY #define USERMOD_ABL_BATTERY 1250 #endif #ifndef USERMOD_ABL_EXT #define USERMOD_ABL_EXT 2500 #endif // 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; } class UsermodULCBatteryManagement : public Usermod { private: IP5306 *ip5306 = nullptr; bool initDone = false; bool initializing = true; // GPIO pin used for battery measurment (with a default compile-time fallback) int8_t ip5306_sda = USERMOD_IP5306_SDA; int8_t ip5306_scl = USERMOD_IP5306_SCL; // how often do we measure? unsigned long readingInterval = USERMOD_BATTERY_MEASUREMENT_INTERVAL; unsigned long nextReadTime = 0; unsigned long lastReadTime = 0; uint32_t ablBattery = USERMOD_ABL_BATTERY; uint32_t ablExt = USERMOD_ABL_EXT; // battery min. voltage float minBatteryVoltage = USERMOD_BATTERY_MIN_VOLTAGE; // battery max. voltage float maxBatteryVoltage = USERMOD_BATTERY_MAX_VOLTAGE; uint8_t batteryLevel = 0; bool isCharging = false; bool isPluggedIn = false; // strings to reduce flash memory usage (used more than twice) static const char _name[]; static const char _readInterval[]; static const char _maxCurrentBattery[]; static const char _maxCurrentExt[]; public: void setup() { DEBUG_PRINTLN(F("Allocating ip5306 pins...")); PinManagerPinType pins[2] = { { ip5306_sda, true }, { ip5306_scl, true } }; if (pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { ip5306 = new IP5306(ip5306_sda, ip5306_scl); DEBUG_PRINTLN(F("IP5306 allocation succeeded.")); } else { if (ip5306_sda >= 0 && ip5306_scl >= 0) DEBUG_PRINTLN(F("IP5306 allocation failed.")); ip5306_sda = -1; // allocation failed ip5306_scl = -1; } nextReadTime = millis() + 5000; lastReadTime = millis(); if (ip5306 != nullptr) { //set battery voltage ip5306->set_battery_voltage(BATT_VOLTAGE_0); //4.2V //Voltage Vout for charging ip5306->charger_under_voltage(VOUT_5); //4.7V //set charging complete current ip5306->end_charge_current(CURRENT_200); //400mA //set cutoff voltage ip5306->set_charging_stop_voltage(CUT_OFF_VOLTAGE_3); // 4.2/4.305/4.35/4.395 V //enable low battery shutdown mode ip5306->low_battery_shutdown(DISABLE); //allow boost even after removing Vin ip5306->boost_after_vin(ENABLE); //allow auto power on after load detection ip5306->power_on_load(DISABLE); //enable boost mode ip5306->boost_mode(ENABLE); } initDone = true; } void loop() { if(strip.isUpdating()) return; // check the battery level every USERMOD_BATTERY_MEASUREMENT_INTERVAL (ms) if (millis() < nextReadTime) return; nextReadTime = millis() + readingInterval; lastReadTime = millis(); initializing = false; if (ip5306 != nullptr) { isPluggedIn = ip5306->check_charging_status(); isCharging = !ip5306->check_battery_status(); batteryLevel = ip5306->get_battery_charge(); if (isPluggedIn) { strip.ablMilliampsMax = ablExt; } else { strip.ablMilliampsMax = ablBattery; } } } /* * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. * Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI. * Below it is shown how this could be used for e.g. a light sensor */ void addToJsonInfo(JsonObject& root) { JsonObject user = root["u"]; if (user.isNull()) user = root.createNestedObject("u"); // info modal display names JsonArray batteryPercentage = user.createNestedArray("Battery Level"); JsonArray batteryState = user.createNestedArray("Battery State"); if (initializing) { batteryPercentage.add((nextReadTime - millis()) / 1000); batteryPercentage.add(" sec"); //batteryVoltage.add((nextReadTime - millis()) / 1000); //batteryVoltage.add(" sec"); batteryState.add((nextReadTime - millis()) / 1000); batteryState.add(" sec"); return; } if(batteryLevel < 0) { batteryPercentage.add(F("invalid")); } else { batteryPercentage.add(batteryLevel); } batteryPercentage.add(F(" %")); if (isPluggedIn && isCharging) { batteryState.add("Charging"); } else if (isPluggedIn && !isCharging) { batteryState.add("Charged"); } else if (!isPluggedIn) { batteryState.add("Discharging"); } } /** * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). * Values in the state object may be modified by connected clients */ //void addToJsonState(JsonObject &root) //{ //} /** * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). * Values in the state object may be modified by connected clients * Read "_" from json state and and change settings (i.e. GPIO pin) used. */ //void readFromJsonState(JsonObject &root) { // if (!initDone) return; // prevent crash on boot applyPreset() //} /** * addToConfig() (called from set.cpp) stores persistent properties to cfg.json */ void addToConfig(JsonObject &root) { JsonObject battery = root.createNestedObject(FPSTR(_name)); // usermodname //battery["minBatteryVoltage"] = minBatteryVoltage; // usermodparam //battery["maxBatteryVoltage"] = maxBatteryVoltage; // usermodparam battery[FPSTR(_readInterval)] = readingInterval; battery[FPSTR(_maxCurrentBattery)] = ablBattery; battery[FPSTR(_maxCurrentExt)] = ablExt; DEBUG_PRINTLN(F("Battery config saved.")); } /** * readFromConfig() is called before setup() to populate properties from values stored in cfg.json * * The function should return true if configuration was successfully loaded or false if there was no configuration. */ bool readFromConfig(JsonObject &root) { JsonObject battery = root[FPSTR(_name)]; if (battery.isNull()) { DEBUG_PRINT(FPSTR(_name)); DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); return false; } readingInterval = battery[FPSTR(_readInterval)] | readingInterval; readingInterval = max(3000, (int)readingInterval); // minimum repetition is >5000ms (5s) ablBattery = battery[FPSTR(_maxCurrentBattery)] | ablBattery; ablExt = battery[FPSTR(_maxCurrentExt)] | ablExt; DEBUG_PRINT(FPSTR(_name)); return !battery[FPSTR(_readInterval)].isNull(); } uint16_t getId() { return USERMOD_ID_ULC_BATTERYMANAGEMENT; } }; // strings to reduce flash memory usage (used more than twice) const char UsermodULCBatteryManagement::_name[] PROGMEM = "Battery-level"; const char UsermodULCBatteryManagement::_readInterval[] PROGMEM = "Battery status update interval"; const char UsermodULCBatteryManagement::_maxCurrentBattery[] PROGMEM = "Max current when on battery"; const char UsermodULCBatteryManagement::_maxCurrentExt[] PROGMEM = "Max current when on charger";