WLED/usermods/ULC_Battery_Management/usermod_ulc_batterymanagement.h

280 lines
8.7 KiB
C
Raw Normal View History

2023-06-19 20:05:51 +02:00
#pragma once
#include "wled.h"
#include "IP5306_I2C.h"
#include <driver/adc.h>
// 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
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[];
// 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 truncate(float val, byte dec)
{
float x = val * pow(10, dec);
float y = round(x);
float z = x - y;
if ((int)z == 5)
{
y++;
}
x = y / pow(10, dec);
return x;
}
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 "<usermodname>_<usermodparam>" 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";