From 0e346b72b692fc7b9763362f27a975472e55f299 Mon Sep 17 00:00:00 2001 From: Lukas Schmid Date: Mon, 19 Jun 2023 20:05:51 +0200 Subject: [PATCH] Added NCSA Universal Light Controller --- platformio.ini | 13 + usermods/ULC_Battery_Management/IP5306_I2C.h | 584 ++++++++++++++++++ usermods/ULC_Battery_Management/readme.md | 11 + .../usermod_ulc_batterymanagement.h | 279 +++++++++ .../ULC_Battery_Management/usermods_list.cpp | 31 + wled00/const.h | 1 + wled00/usermods_list.cpp | 11 + 7 files changed, 930 insertions(+) create mode 100644 usermods/ULC_Battery_Management/IP5306_I2C.h create mode 100644 usermods/ULC_Battery_Management/readme.md create mode 100644 usermods/ULC_Battery_Management/usermod_ulc_batterymanagement.h create mode 100644 usermods/ULC_Battery_Management/usermods_list.cpp diff --git a/platformio.ini b/platformio.ini index 8838b9a7..2603c0fb 100644 --- a/platformio.ini +++ b/platformio.ini @@ -40,6 +40,7 @@ default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, lolin_ ; default_envs = esp32dev_qio80 ; default_envs = esp32_eth_ota1mapp ; default_envs = esp32s2_saola +; default_envs = esp32_ulc src_dir = ./wled00 data_dir = ./wled00/data @@ -779,3 +780,15 @@ lib_deps = ${esp32.lib_deps} TFT_eSPI @ ^2.3.70 board_build.partitions = ${esp32.default_partitions} + + +# ------------------------------------------------------------------------------ +# NetCube Systems Universal Light Controller +# ------------------------------------------------------------------------------ +[env:esp32_ulc] +board = esp32-poe +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_ULC -D RLYPIN=-1 -D WLED_USE_ETHERNET -D WLED_ETH_DEFAULT=6 -D LEDPIN=14 -D DEFAULT_LED_COUNT=37 -D APL_MILLIAMPS_DEFAULT=65000 -D BTNPIN=-1 -D WLED_DISABLE_BLYNK -D IRPIN=-1 -D USERMOD_ULC_BATTERYMANAGEMENT +lib_deps = ${esp32.lib_deps} +platform = espressif32@3.2 +board_build.partitions = ${esp32.default_partitions} \ No newline at end of file diff --git a/usermods/ULC_Battery_Management/IP5306_I2C.h b/usermods/ULC_Battery_Management/IP5306_I2C.h new file mode 100644 index 00000000..2a9038ce --- /dev/null +++ b/usermods/ULC_Battery_Management/IP5306_I2C.h @@ -0,0 +1,584 @@ +/*@brief library to interact with IP5306 over I2C communication +*/ + +#ifndef _IP5306_I2C_H_ +#define _IP5306_I2C_H_ + +#include //I2C library +#include "Arduino.h" +#include + +/*macros definition*/ +#define ENABLE 1 +#define DISABLE 0 + +#define IP5306_ADDRESS 0x75 //7 bit slave address + +/****************************Registers*****************************/ +#define SYS_CTL0 0x00 +#define SYS_CTL1 0x01 +#define SYS_CTL2 0x02 + +#define Charger_CTL0 0x20 +#define Charger_CTL1 0x21 +#define Charger_CTL2 0x22 +#define Charger_CTL3 0x23 + +#define CHG_DIG_CTL0 0x24 + +#define REG_READ0 0x70 +#define REG_READ1 0x71 +#define REG_READ2 0x72 +#define REG_UNDEF1 0x75 +#define REG_READ3 0x77 +#define REG_UNDEF2 0x78 + +#define BAT_VADC_DAT0 0xA2 +#define BAT_VADC_DAT1 0xA3 + +#define BAT_IADC_DAT0 0xA4 +#define BAT_IADC_DAT1 0xA5 + +/************************bit definitions***************************/ +union { + struct { + uint8_t BUTTON_SHUTDOWN : 1; + uint8_t SET_BOOST_OUTPUT_ENABLE : 1; + uint8_t POWER_ON_LOAD : 1; + uint8_t RSVD : 1; + uint8_t CHARGER_ENABLE : 1; + uint8_t BOOST_ENABLE : 1; + uint8_t RSVD2 : 2; + } bits; + + uint8_t reg_byte; +} reg_SYS_CTL0_t; + +union { + struct { + uint8_t LOW_BATTERY_SHUTDOWN_ENABLE : 1; + uint8_t RSVD : 1; + uint8_t BOOST_AFTER_VIN : 1; + uint8_t RSVD2 : 2; + uint8_t SHORT_PRESS_BOOST_SWITCH_ENABLE : 1; + uint8_t FLASHLIGHT_CTRL_SIGNAL_SELECTION : 1; + uint8_t BOOST_CTRL_SIGNAL_SELECTION : 1; + } bits; + uint8_t reg_byte; +} reg_SYS_CTL1_t; + +union { + struct { + uint8_t RSVD : 2; + uint8_t LIGHT_LOAD_SHUTDOWN_TIME : 2; + uint8_t LONG_PRESS_TIME : 1; + uint8_t RSVD2 : 3; + } bits; + uint8_t reg_byte; +} reg_SYS_CTL2_t; + +#define SHUTDOWN_8s 0 +#define SHUTDOWN_32s 1 +#define SHUTDOWN_16s 2 +#define SHUTDOWN_64s 3 + +union { + struct { + uint8_t CHARGING_FULL_STOP_VOLTAGE : 2; + uint8_t RSVD : 6; + } bits; + uint8_t reg_byte; +} reg_Charger_CTL0_t; + +#define CUT_OFF_VOLTAGE_0 0 // 4.14/4.26/4.305/4.35 V +#define CUT_OFF_VOLTAGE_1 1 // 4.17/4.275/4.32/4.365 V +#define CUT_OFF_VOLTAGE_2 2 // 4.185/4.29/4.335/4.38 V +#define CUT_OFF_VOLTAGE_3 3 // 4.2/4.305/4.35/4.395 V + +union { + struct { + uint8_t RSVD : 2; + uint8_t CHARGE_UNDER_VOLTAGE_LOOP : 3; + uint8_t RSVD2 : 1; + uint8_t END_CHARGE_CURRENT_DETECTION : 2; + } bits; + uint8_t reg_byte; +} reg_Charger_CTL1_t; + +#define CURRENT_200 0 +#define CURRENT_400 1 +#define CURRENT_500 2 +#define CURRENT_600 3 + +#define VOUT_0 0 //4.45 +#define VOUT_1 1 //4.5 +#define VOUT_2 2 //4.55 +#define VOUT_3 3 //4.6 +#define VOUT_4 4 //4.65 +#define VOUT_5 5 //4.7 +#define VOUT_6 6 //4.75 +#define VOUT_7 7 //4.8 + +union { + struct { + uint8_t VOLTAGE_PRESSURE : 2; + uint8_t BATTERY_VOLTAGE : 2; + uint8_t RSVD : 4; + } bits; + uint8_t reg_byte; +} reg_Charger_CTL2_t; + +#define BATT_VOLTAGE_3 3 //4.4 +#define BATT_VOLTAGE_2 2 //4.35 +#define BATT_VOLTAGE_1 1 //4.3 +#define BATT_VOLTAGE_0 0 //4.2 + +#define Pressurized_42 3 +#define Pressurized_28 2 +#define Pressurized_14 1 +#define Not_pressurized 0 + +union { + struct { + uint8_t RSVD : 5; + uint8_t CHARGE_CC_LOOP : 1; + uint8_t RSVD2 : 2; + } bits; + uint8_t reg_byte; +} reg_Charger_CTL3_t; + +union { + struct { + uint8_t VIN_CURRENT : 5; + uint8_t RSVD : 3; + } bits; + uint8_t reg_byte; +} reg_CHG_DIG_CTL0_t; + +union { + struct { + uint8_t RSVD : 3; + uint8_t CHARGE_ENABLE : 1; + uint8_t RSVD2 : 4; + } bits; + uint8_t reg_byte; +} reg_READ0_t; + +union { + struct { + uint8_t RSVD : 3; + uint8_t BATTERY_STATUS : 1; + uint8_t RSVD2 : 4; + } bits; + uint8_t reg_byte; +} reg_READ1_t; + +union { + struct { + uint8_t RSVD : 2; + uint8_t LOAD_LEVEL : 1; + uint8_t RSVD2 : 5; + } bits; + uint8_t reg_byte; +} reg_READ2_t; + +union { + struct { + uint8_t SHORT_PRESS_DETECT : 1; + uint8_t LONG_PRESS_DETECT : 1; + uint8_t DOUBLE_PRESS_DETECT : 1; + uint8_t RSVD : 5; + } bits; + uint8_t reg_byte; +} reg_READ3_t; + +class IP5306{ + public: + /*@brief initialize i2c + @param sda and scl pin number + */ + IP5306(uint8_t sda_pin=21, uint8_t scl_pin=22) { + Wire.begin(sda_pin,scl_pin); + + /*initialize register data*/ + reg_SYS_CTL0_t.reg_byte = i2c_read(IP5306_ADDRESS,SYS_CTL0); + reg_SYS_CTL1_t.reg_byte = i2c_read(IP5306_ADDRESS,SYS_CTL1); + reg_SYS_CTL2_t.reg_byte = i2c_read(IP5306_ADDRESS,SYS_CTL2); + + reg_Charger_CTL0_t.reg_byte = i2c_read(IP5306_ADDRESS,Charger_CTL0); + reg_Charger_CTL1_t.reg_byte = i2c_read(IP5306_ADDRESS,Charger_CTL1); + reg_Charger_CTL2_t.reg_byte = i2c_read(IP5306_ADDRESS,Charger_CTL2); + reg_Charger_CTL3_t.reg_byte = i2c_read(IP5306_ADDRESS,Charger_CTL3); + + } + + /*@brief write data to register + @param register address to write to,data to be written + and i2c address of the device + @retval None + */ + void i2c_write(uint8_t slave_address, uint8_t register_address, uint8_t data) { + + Wire.beginTransmission(slave_address); + Wire.write(register_address); + Wire.write(data); + Wire.endTransmission(); + } + + /*@brief read the register value + @param register address to read,and i2c address of the device + @retval current register data + */ + uint8_t i2c_read(uint8_t slave_address, uint8_t register_address) { + + uint8_t c = 0; + Wire.beginTransmission(slave_address); + Wire.write(register_address); + Wire.endTransmission(); + + Wire.requestFrom(slave_address, 1); + + while (Wire.available()) { // slave may send less than requested + c = Wire.read(); // receive a byte as character + } + + return c; + } + + /*@brief select boost mode + @param enable or disable bit + @retval None + @note Default value - enable + */ + void boost_mode(uint8_t boost_en) { + reg_SYS_CTL0_t.bits.BOOST_ENABLE = boost_en; + + i2c_write(IP5306_ADDRESS,SYS_CTL0,reg_SYS_CTL0_t.reg_byte); + } + + /*@brief select charger mode + @param enable or disable bit + @retval None + @note Default value - enable + */ + void charger_mode(uint8_t charger_en) { + reg_SYS_CTL0_t.bits.CHARGER_ENABLE = charger_en; + + i2c_write(IP5306_ADDRESS,SYS_CTL0,reg_SYS_CTL0_t.reg_byte); + } + + /*@brief select auto power on once load detected + @param enable or disable bit + @retval None + @note Default value - enable + */ + void power_on_load(uint8_t power_on_en) { + reg_SYS_CTL0_t.bits.POWER_ON_LOAD = power_on_en; + + i2c_write(IP5306_ADDRESS,SYS_CTL0,reg_SYS_CTL0_t.reg_byte); + } + + /*@brief boost o/p normally open function + @param enable or disable bit + @retval None + @note Default value - enable + */ + void boost_output(uint8_t output_val) { + reg_SYS_CTL0_t.bits.SET_BOOST_OUTPUT_ENABLE = output_val; + + i2c_write(IP5306_ADDRESS,SYS_CTL0,reg_SYS_CTL0_t.reg_byte); + } + + /*@brief eneter shutdown mode using button + @param enable or disable bit + @retval None + @note Default value - disable + */ + void button_shutdown(uint8_t shutdown_val) { + reg_SYS_CTL0_t.bits.BUTTON_SHUTDOWN = shutdown_val; + + i2c_write(IP5306_ADDRESS,SYS_CTL0,reg_SYS_CTL0_t.reg_byte); + } + + /*@brief boost control mode using button + @param enable or disable bit + @retval None + @note Default value - disable + */ + void boost_ctrl_signal(uint8_t press_val) { + reg_SYS_CTL1_t.bits.BOOST_CTRL_SIGNAL_SELECTION = press_val; + + i2c_write(IP5306_ADDRESS,SYS_CTL1,reg_SYS_CTL1_t.reg_byte); + } + + /*@brief flashlight control mode using button + @param enable or disable bit + @retval None + @note Default value - disable + */ + void flashlight_ctrl_signal(uint8_t press_val) { + reg_SYS_CTL1_t.bits.FLASHLIGHT_CTRL_SIGNAL_SELECTION = press_val; + + i2c_write(IP5306_ADDRESS,SYS_CTL1,reg_SYS_CTL1_t.reg_byte); + } + + /*@brief + @param enable or disable bit + @retval None + @note Default value - disable + */ + void short_press_boost(uint8_t boost_en) { + reg_SYS_CTL1_t.bits.SHORT_PRESS_BOOST_SWITCH_ENABLE = boost_en; + + i2c_write(IP5306_ADDRESS,SYS_CTL1,reg_SYS_CTL1_t.reg_byte); + } + + /*@brief keep boost mode on after input supply removal + @param enable or disable bit + @retval None + @note Default value - enable + */ + void boost_after_vin(uint8_t val) { + reg_SYS_CTL1_t.bits.BOOST_AFTER_VIN = val; + + i2c_write(IP5306_ADDRESS,SYS_CTL1,reg_SYS_CTL1_t.reg_byte); + } + + /*@brief shutdown if battery voltage raches 3V + @param enable or disable bit + @retval None + @note Default value - enable + */ + void low_battery_shutdown(uint8_t shutdown_en) { + reg_SYS_CTL1_t.bits.LOW_BATTERY_SHUTDOWN_ENABLE = shutdown_en; + + i2c_write(IP5306_ADDRESS,SYS_CTL1,reg_SYS_CTL1_t.reg_byte); + } + + /*@brief set long press timing + @param long press time value - 0 for 2s , 1 for 3s + @retval None + @note Default value - disable + */ + void set_long_press_time(uint8_t press_time_val) { + reg_SYS_CTL2_t.bits.LONG_PRESS_TIME = press_time_val; + + i2c_write(IP5306_ADDRESS,SYS_CTL2,reg_SYS_CTL2_t.reg_byte); + } + + /*@brief set light load shutdown timing + @param shutdown time value - 0 for 8s, 1 for 32s, 2 for 16s and 3 for 64s + @retval None + @note Default value - disable + */ + void set_light_load_shutdown_time(uint8_t shutdown_time) { + reg_SYS_CTL2_t.bits.LIGHT_LOAD_SHUTDOWN_TIME = shutdown_time; + + i2c_write(IP5306_ADDRESS,SYS_CTL2,reg_SYS_CTL2_t.reg_byte); + } + + /*@brief set charging cutoff voltage range for battery + @param voltage range value - 0 for 4.14/4.26/4.305/4.35 V + 1 for 4.17/4.275/4.32/4.365 V + 2 for 4.185/4.29/4.335/4.38 V + 3 for 4.2/4.305/4.35/4.395 V + @retval None + @note Default value - 2 + */ + void set_charging_stop_voltage(uint8_t voltage_val) { + reg_Charger_CTL0_t.bits.CHARGING_FULL_STOP_VOLTAGE = voltage_val; + + i2c_write(IP5306_ADDRESS,Charger_CTL0,reg_Charger_CTL0_t.reg_byte); + } + + /*@brief set charging complete current detection + @param current value - 3 : 600mA + 2 : 500mA + 1 : 400mA + 0 : 200mA + + @retval None + @note Default value - 1 + */ + void end_charge_current(uint8_t current_val) { + reg_Charger_CTL1_t.bits.END_CHARGE_CURRENT_DETECTION = current_val; + + i2c_write(IP5306_ADDRESS,Charger_CTL1,reg_Charger_CTL1_t.reg_byte); + } + + /*@brief set voltage Vout for charging + @param voltage value - 111:4.8 + 110:4.75 + 101:4.7 + 100:4.65 + 011:4.6 + 010:4.55 + 001:4.5 + 000:4.45 + @retval None + @note Default value - 101 + */ + void charger_under_voltage(uint8_t voltage_val) { + reg_Charger_CTL1_t.bits.CHARGE_UNDER_VOLTAGE_LOOP = voltage_val; + + i2c_write(IP5306_ADDRESS,Charger_CTL1,reg_Charger_CTL1_t.reg_byte); + } + + /*@brief set battery voltage + @param voltage value - 00:4.2 + 01:4.3 + 02:4.35 + 03:4.4 + @retval None + @note Default value - 00 + */ + void set_battery_voltage(uint8_t voltage_val) { + reg_Charger_CTL2_t.bits.BATTERY_VOLTAGE = voltage_val; + + i2c_write(IP5306_ADDRESS,Charger_CTL2,reg_Charger_CTL2_t.reg_byte); + } + + /*@brief set constant voltage charging setting + @param voltage value - 11: Pressurized 42mV + 10: Pressurized 28mV + 01: Pressurized 14mV + 00: Not pressurized + @retval None + @note Default value - 01 + @note :4.30V/4.35V/4.4V It is recommended to pressurize 14mV + 4.2V It is recommended to pressurize 28mV + */ + void set_voltage_pressure(uint8_t voltage_val) { + reg_Charger_CTL2_t.bits.VOLTAGE_PRESSURE = voltage_val; + + i2c_write(IP5306_ADDRESS,Charger_CTL2,reg_Charger_CTL2_t.reg_byte); + } + + /*@brief set constant current charging setting + @param value - 1:VIN end CC Constant current + 0:BAT end CC Constant current + @retval None + @note Default value - 1 + */ + void set_cc_loop(uint8_t current_val) { + reg_Charger_CTL3_t.bits.CHARGE_CC_LOOP = current_val; + + i2c_write(IP5306_ADDRESS,Charger_CTL3,reg_Charger_CTL3_t.reg_byte); + } + + /*@brief get current charging status + @param None + @retval integer representation of charging status(1 - charging is on and 0 if charging is off) + */ + uint8_t check_charging_status(void) { + reg_READ0_t.reg_byte = i2c_read(IP5306_ADDRESS,REG_READ0); + + return reg_READ0_t.bits.CHARGE_ENABLE; + } + + /*@brief get current battery status + @param None + @retval integer representation of battery status(1 - fully charged and 0 if still charging) + */ + uint8_t check_battery_status(void) { + reg_READ1_t.reg_byte = i2c_read(IP5306_ADDRESS,REG_READ1); + + return reg_READ1_t.bits.BATTERY_STATUS; + } + + /*@brief get output load level + @param None + @retval integer representation of load status(1 - light load and 0 if heavy load) + */ + uint8_t check_load_level(void) { + reg_READ2_t.reg_byte = i2c_read(IP5306_ADDRESS,REG_READ2); + + return reg_READ2_t.bits.LOAD_LEVEL; + } + + /*@brief detect if a short press occurred + @param None + @retval button status + @note clear this bit after reading by writing 1 + */ + uint8_t short_press_detect(void) { + reg_READ3_t.reg_byte = i2c_read(IP5306_ADDRESS,REG_READ3); + + return reg_READ3_t.bits.SHORT_PRESS_DETECT; + } + + /*@brief detect if a long press occurred + @param None + @retval button status + @note clear this bit after reading by writing 1 + */ + uint8_t long_press_detect(void) { + reg_READ3_t.reg_byte = i2c_read(IP5306_ADDRESS,REG_READ3); + + return reg_READ3_t.bits.LONG_PRESS_DETECT; + } + + /*@brief detect if a double press occurred + @param None + @retval button status + @note clear this bit after reading by writing 1 + */ + uint8_t double_press_detect(void) { + reg_READ3_t.reg_byte = i2c_read(IP5306_ADDRESS,REG_READ3); + + return reg_READ3_t.bits.DOUBLE_PRESS_DETECT; + } + + uint8_t get_battery_charge(void) { + uint8_t charge = i2c_read(IP5306_ADDRESS,REG_UNDEF2); + + switch (charge & 0xF0) { + case 0xF0: + return 0; + case 0xE0: + return 25; + case 0xC0: + return 50; + case 0x80: + return 75; + case 0x00: + return 100; + default: + return charge & 0xF0; + } + } + + double get_battery_voltage(void) { + uint8_t low = i2c_read(IP5306_ADDRESS,BAT_VADC_DAT0); + uint8_t high = i2c_read(IP5306_ADDRESS,BAT_VADC_DAT1); + double vadc; + + if ((high & 0x20) == 0x20) { + vadc = 2600 - ((~low)+(~(high & 0x1F)) * 256 + 1) * 0.26855; + } else { + vadc = 2600 + (low+high * 256) * 0.26855; + } + + return vadc; + } + + double get_battery_current(void) { + uint8_t low = i2c_read(IP5306_ADDRESS,BAT_IADC_DAT0); + uint8_t high = i2c_read(IP5306_ADDRESS,BAT_IADC_DAT1); + double iadc; + + if ((high & 0x20) == 0x20) { + char a = ~low; + char b = (~(high & 0x1F) & 0x1F); + int c = b * 256 + a + 1; + iadc = c * 0.745985; + } else { + iadc = (high * 256 + low) * 0.745985; + } + + return iadc; + } +}; + + +#endif diff --git a/usermods/ULC_Battery_Management/readme.md b/usermods/ULC_Battery_Management/readme.md new file mode 100644 index 00000000..ad76bd36 --- /dev/null +++ b/usermods/ULC_Battery_Management/readme.md @@ -0,0 +1,11 @@ +# NetCube Systems Universal Light Controller - Battery Management Usermod + +This usermod will read status from an IP5306-I2C battery management soc, and monitor the battery voltage via and voltage divider. + +## Installation + +Copy the example `platformio_override.ini` to the root directory. This file should be placed in the same directory as `platformio.ini`. + +### Define Your Options + +* `USERMOD_ULC_BATTERYMANAGEMENT` - define this to have this user mod included wled00\usermods_list.cpp \ No newline at end of file diff --git a/usermods/ULC_Battery_Management/usermod_ulc_batterymanagement.h b/usermods/ULC_Battery_Management/usermod_ulc_batterymanagement.h new file mode 100644 index 00000000..571da8a7 --- /dev/null +++ b/usermods/ULC_Battery_Management/usermod_ulc_batterymanagement.h @@ -0,0 +1,279 @@ +#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 + +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 "_" 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"; diff --git a/usermods/ULC_Battery_Management/usermods_list.cpp b/usermods/ULC_Battery_Management/usermods_list.cpp new file mode 100644 index 00000000..8682f8da --- /dev/null +++ b/usermods/ULC_Battery_Management/usermods_list.cpp @@ -0,0 +1,31 @@ +#include "wled.h" +/* + * Register your v2 usermods here! + */ + +/* + * Add/uncomment your usermod filename here (and once more below) + * || || || + * \/ \/ \/ + */ +//#include "usermod_v2_example.h" +#if (defined(USERMOD_ULC_BATTERYMANAGEMENT) && defined(ARDUINO_ARCH_ESP32)) +#include "../usermods/ULC_Battery_Management/usermod_ulc_batterymanagement.h" +#endif + +//#include "usermod_v2_empty.h" + +void registerUsermods() +{ + /* + * Add your usermod class name here + * || || || + * \/ \/ \/ + */ + //usermods.add(new MyExampleUsermod()); +#if (defined(USERMOD_ULC_BATTERYMANAGEMENT) && defined(ARDUINO_ARCH_ESP32)) + usermods.add(new UsermodULCBatteryManagement()); +#endif + + //usermods.add(new UsermodRenameMe()); +} \ No newline at end of file diff --git a/wled00/const.h b/wled00/const.h index 14369e22..024fee2f 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -133,6 +133,7 @@ #define USERMOD_ID_PWM_OUTPUTS 38 //Usermod "usermod_pwm_outputs.h #define USERMOD_ID_SHT 39 //Usermod "usermod_sht.h #define USERMOD_ID_KLIPPER 40 // Usermod Klipper percentage +#define USERMOD_ID_ULC_BATTERYMANAGEMENT 41 //Usermod "usermod_ulc_batterymanagement.h" //Access point behavior #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot diff --git a/wled00/usermods_list.cpp b/wled00/usermods_list.cpp index 07847502..d3640268 100644 --- a/wled00/usermods_list.cpp +++ b/wled00/usermods_list.cpp @@ -189,6 +189,13 @@ #include "../usermods/pwm_outputs/usermod_pwm_outputs.h" #endif +#ifdef USERMOD_SHT +#include "../usermods/sht/usermod_sht.h" +#endif + +#if (defined(USERMOD_ULC_BATTERYMANAGEMENT) && defined(ARDUINO_ARCH_ESP32)) +#include "../usermods/ULC_Battery_Management/usermod_ulc_batterymanagement.h" +#endif void registerUsermods() { @@ -357,4 +364,8 @@ void registerUsermods() #ifdef USERMOD_SHT usermods.add(new ShtUsermod()); #endif + + #if (defined(USERMOD_ULC_BATTERYMANAGEMENT) && defined(ARDUINO_ARCH_ESP32)) + usermods.add(new UsermodULCBatteryManagement()); + #endif }