From 966d24cae9fd6fac4f2178ad47be5e676a951a1a Mon Sep 17 00:00:00 2001 From: dwimaulina Date: Wed, 29 Jul 2026 14:06:20 +0700 Subject: [PATCH] Upload file project dev15_v2 --- dev15/LittleFSDemo/LittleFSDemo.ino | 45 + dev15/LittleFSDemo/data/config.json | 5 + dev15/LittleFSDemo/data/info.txt | 2 + dev15/LittleFSDemo/data/readme.md | 2 + dev15/battLib.h | 120 +++ dev15/bitmap.h | 277 ++++++ dev15/data/favicon.ico | Bin 0 -> 3606 bytes dev15/data/index.html | 827 +++++++++++++++++ dev15/dev15.ino | 98 ++ dev15/mqtt.h | 211 +++++ dev15/ringkasan.md | 203 ++++ dev15/upsCfg.h | 130 +++ dev15/upsIO.h | 1338 +++++++++++++++++++++++++++ dev15/upsOled.h | 226 +++++ dev15/upsSvr.h | 536 +++++++++++ dev15/webUI.h | 832 +++++++++++++++++ dev15/wifi.h | 106 +++ serialmonitorlog.json | 585 ++++++++++++ 18 files changed, 5543 insertions(+) create mode 100644 dev15/LittleFSDemo/LittleFSDemo.ino create mode 100644 dev15/LittleFSDemo/data/config.json create mode 100644 dev15/LittleFSDemo/data/info.txt create mode 100644 dev15/LittleFSDemo/data/readme.md create mode 100644 dev15/battLib.h create mode 100644 dev15/bitmap.h create mode 100644 dev15/data/favicon.ico create mode 100644 dev15/data/index.html create mode 100644 dev15/dev15.ino create mode 100644 dev15/mqtt.h create mode 100644 dev15/ringkasan.md create mode 100644 dev15/upsCfg.h create mode 100644 dev15/upsIO.h create mode 100644 dev15/upsOled.h create mode 100644 dev15/upsSvr.h create mode 100644 dev15/webUI.h create mode 100644 dev15/wifi.h create mode 100644 serialmonitorlog.json diff --git a/dev15/LittleFSDemo/LittleFSDemo.ino b/dev15/LittleFSDemo/LittleFSDemo.ino new file mode 100644 index 0000000..b1eeecf --- /dev/null +++ b/dev15/LittleFSDemo/LittleFSDemo.ino @@ -0,0 +1,45 @@ +#include +#include +#include + +void listFiles() { + Serial.println("\nListing files in LittleFS:"); + Dir dir = LittleFS.openDir("/"); + while (dir.next()) { + Serial.printf(" %s (%d bytes)\n", dir.fileName().c_str(), dir.fileSize()); + } +} + +void readFile(const char *path) { + File file = LittleFS.open(path, "r"); + if (!file) { + Serial.printf("Failed to open file: %s\n", path); + return; + } + Serial.printf("Contents of %s:\n", path); + while (file.available()) { + Serial.write(file.read()); + } + Serial.println(); + file.close(); +} + +void setup() { + Serial.begin(115200); + delay(500); + + if (!LittleFS.begin()) { + Serial.println("LittleFS mount failed! Did you upload data folder?"); + return; + } + + Serial.println("LittleFS mounted successfully."); + listFiles(); + + // Read demo files + readFile("/info.txt"); + readFile("/config.json"); +} + +void loop() { +} diff --git a/dev15/LittleFSDemo/data/config.json b/dev15/LittleFSDemo/data/config.json new file mode 100644 index 0000000..cbfd158 --- /dev/null +++ b/dev15/LittleFSDemo/data/config.json @@ -0,0 +1,5 @@ +{ + "wifi_ssid": "YourNetwork", + "wifi_pass": "YourPassword", + "mode": "demo" +} diff --git a/dev15/LittleFSDemo/data/info.txt b/dev15/LittleFSDemo/data/info.txt new file mode 100644 index 0000000..e414132 --- /dev/null +++ b/dev15/LittleFSDemo/data/info.txt @@ -0,0 +1,2 @@ +Wemos D1 Mini LittleFS Demo +Uploaded from Arduino IDE using LittleFS Data Upload plugin. diff --git a/dev15/LittleFSDemo/data/readme.md b/dev15/LittleFSDemo/data/readme.md new file mode 100644 index 0000000..1685bbe --- /dev/null +++ b/dev15/LittleFSDemo/data/readme.md @@ -0,0 +1,2 @@ +# LittleFS Demo +This is a sample file stored in internal flash using LittleFS. diff --git a/dev15/battLib.h b/dev15/battLib.h new file mode 100644 index 0000000..1e7eaa3 --- /dev/null +++ b/dev15/battLib.h @@ -0,0 +1,120 @@ +//LiFePO4 4S battery soc and level calculation library + +//todo: + //- battery level will forced after 60-seconds if not passing battPercentageLevelThreshold (optional) + +static uint8_t battPercentageLevelThreshold = 5; //threshold for switch discrete level 0-4 +static uint8_t battLastLevel = 0; +static uint8_t battLastPercentageForLevel = 0; + +//variables +static float battVoltageFullChargeMinimal = 14.4; //used for longevity charge +static float battVoltageFullChargeMaximal = 14.6; //3.60V - 3.65V * 4S = 14.4V - 14.6V (can be used for "topped-off" charge mode, ) +static float battVoltageFullRest = 13.6; //~3.40V - 3.50V * 4S = ~13.6V - 13.7V (can be used for "optimal" charge mode, preferred for longevity) +static float battVoltageUpperZone = 13.3; //Also can be used for "Storage/Long-term Maintenance" charge mode, or after long usage without load ~13.2V - 13.3V (50-60% state of charge) +static float battVoltageLowerZone = 12.9; +//float battVoltageHalf = 13.1; //~3.25V - 3.28V * 4S = ~13.0V - 13.1V, known as nomimal +static float battVoltageEmpty = 10.0; //~2.50V - 2.80V * 4S = ~10.0V - 11.2V +static float battVoltageCritical = 12.0; //Default ~10% => 3.0V * 4S = 12V +static float battVoltageForceShutdown = 11.2; //Default ~5% => 2.8V * 4S = 11.2V +static float battVoltageResumeChargingDefault = 13.4; //Default ~90% = ~3.35V *4S = 13.4V + +//error condition +static float battOverVoltage = 15.3; //3.75V - 3.80V * 4S = 15V - 15.3V +static float battVoltageDetected = 8; +static float battVoltageNotDetected = 7; + +static void resetBatteryLevel() { + battLastLevel = 0; + battLastPercentageForLevel = 0; +} + +static void setBatteryPercentage(uint8_t soc) { + if (soc <= 0) { + battLastLevel = 0; + battLastPercentageForLevel = 0; + return; + } else if (soc >= 100) { + battLastLevel = 4; + battLastPercentageForLevel = 100; + return; + } + + if (soc > battLastPercentageForLevel) { + //charging + if (soc - battLastPercentageForLevel > battPercentageLevelThreshold) { + battLastPercentageForLevel = soc; + if (battLastPercentageForLevel > 75) { + battLastLevel = 4; + } else if (battLastPercentageForLevel > 50) { + battLastLevel = 3; + } else if (battLastPercentageForLevel > 25) { + battLastLevel = 2; + } else if (battLastPercentageForLevel > 10) { + battLastLevel = 1; + } else { + battLastLevel = 0; + } + } + } else { + //depleted or equal + if (battLastPercentageForLevel - soc > battPercentageLevelThreshold) { + battLastPercentageForLevel = soc; + if (battLastPercentageForLevel < 10) { + battLastLevel = 0; + } else if (battLastPercentageForLevel < 25) { + battLastLevel = 1; + } else if (battLastPercentageForLevel < 50) { + battLastLevel = 2; + } else if (battLastPercentageForLevel < 75) { + battLastLevel = 3; + } else { + battLastLevel = 4; + } + } + } +} + +static uint8_t getBatteryPercentage(float voltage) { + float soc = 0; + if (voltage > battVoltageUpperZone) { + soc = 90.0 + (10.0 * ((voltage - battVoltageUpperZone) / (battVoltageFullRest - battVoltageUpperZone))); + if (soc > 100) soc = 100; + } else if (voltage > battVoltageLowerZone && voltage <= battVoltageUpperZone) { + soc = 20.0 + (70.0 * ((voltage - battVoltageLowerZone) / (battVoltageUpperZone - battVoltageLowerZone))); + } else if (voltage > battVoltageEmpty) { + soc = (20.0 * ((voltage - battVoltageEmpty) / (battVoltageLowerZone - battVoltageEmpty))); + } + setBatteryPercentage(soc); + return (uint8_t) soc; +} + +static uint8_t getRawBatteryLevelFromPercentage(uint8_t soc) { + if (soc <= 0) { + return 0; + } else if (soc >= 100) { + return 4; + } + + if (soc > 75) { + return 4; + } else if (soc > 50) { + return 3; + } else if (soc > 25) { + return 2; + } else if (soc > 5) { + return 1; + } else { + return 0; + } + return 0; +} + +static uint8_t getBatteryLevel() { + return battLastLevel; +} + +static uint8_t getBatteryLevel(float voltage) { + getBatteryPercentage(voltage); + return battLastLevel; +} diff --git a/dev15/bitmap.h b/dev15/bitmap.h new file mode 100644 index 0000000..e38bc76 --- /dev/null +++ b/dev15/bitmap.h @@ -0,0 +1,277 @@ +static const unsigned char PROGMEM splash_logo_bitmap[512] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xE0,0xFF,0xE7,0x01,0x80,0xEF,0x03,0xF0,0x39,0xE0,0xFF,0x07,0x00,0x00, + 0x00,0x00,0xF8,0xFF,0xEF,0x03,0x80,0xC7,0x07,0xF8,0x78,0xF8,0xFF,0x1F,0x00,0x00, + 0x00,0x00,0xFC,0xFF,0xCF,0x03,0xC0,0x87,0x0F,0x7C,0x78,0xFC,0xFF,0x3F,0x00,0x00, + 0x00,0x00,0xFC,0xFF,0xC7,0x07,0xC0,0x83,0x1F,0x3E,0x78,0xFC,0xFF,0x3F,0x00,0x00, + 0x00,0x00,0x3E,0x00,0x80,0x07,0xE0,0x03,0x1F,0x1F,0x78,0x3E,0x00,0x3C,0x00,0x00, + 0x00,0x00,0x1E,0x00,0x80,0x0F,0xF0,0x01,0xBE,0x0F,0x78,0x1E,0x00,0x3C,0x00,0x00, + 0x00,0x00,0x1E,0x00,0x00,0x0F,0xF0,0x00,0xFC,0x07,0x78,0x1E,0x00,0x7C,0x00,0x00, + 0x00,0x00,0xFE,0xFF,0x07,0x1F,0xF8,0x00,0xF8,0x03,0x78,0x1E,0x00,0x7C,0x00,0x00, + 0x00,0x00,0xFE,0xFF,0x07,0x1E,0x78,0x00,0xF0,0x03,0x78,0x1E,0x00,0x7C,0x00,0x00, + 0x00,0x00,0xFE,0xFF,0x07,0x3E,0x7C,0x00,0xF8,0x03,0x78,0x1E,0x00,0x7C,0x00,0x00, + 0x00,0x00,0x1E,0x00,0x00,0x3C,0x3C,0x00,0xFC,0x07,0x78,0x1E,0x00,0x7C,0x00,0x00, + 0x00,0x00,0x1E,0x00,0x00,0x78,0x3E,0x00,0xBE,0x0F,0x78,0x1E,0x00,0x3C,0x00,0x00, + 0x00,0x00,0x3E,0x00,0x00,0xF8,0x1E,0x00,0x1F,0x1F,0x78,0x3E,0x00,0x3C,0x00,0x00, + 0x00,0x00,0xFC,0xFF,0x07,0xF0,0x1F,0x80,0x0F,0x3E,0x78,0xFC,0xFF,0x3F,0x00,0x00, + 0x00,0x00,0xFC,0xFF,0x07,0xF0,0x0F,0xC0,0x07,0x7C,0x78,0xFC,0xFF,0x3F,0x00,0x00, + 0x00,0x00,0xF8,0xFF,0x0F,0xE0,0x07,0xE0,0x03,0xF8,0x78,0xF8,0xFF,0x1F,0x00,0x00, + 0x00,0x00,0xF0,0xFF,0x07,0xC0,0x07,0xF0,0x01,0xF0,0x79,0xF0,0xFF,0x07,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xC0,0x9F,0x83,0x83,0xC1,0x3F,0xFF,0xC1,0x61,0xFE,0xFE,0x83,0x81,0xFC,0xF1,0x07, + 0xE0,0xDF,0xC3,0xC3,0xC3,0x7F,0xFF,0xE1,0x61,0xFF,0xFF,0x83,0xC1,0xFE,0xFB,0x07, + 0x60,0xC0,0xC7,0xC3,0xC7,0x60,0x18,0xE0,0x63,0x03,0x60,0x80,0xC1,0x0E,0x1B,0x00, + 0xE0,0xCF,0xC6,0x63,0xC6,0x60,0x18,0x60,0x67,0xFF,0x60,0x80,0xC1,0x0E,0xFB,0x03, + 0xE0,0xDF,0x6E,0x63,0xCC,0x7F,0x18,0x60,0x6E,0xFF,0x60,0x80,0xC1,0xFE,0xF3,0x0F, + 0x00,0xD8,0x6C,0xF3,0xCF,0x3F,0x18,0x60,0x7C,0x03,0x60,0x80,0xC1,0xFE,0x01,0x0C, + 0xE0,0xDF,0x3C,0xFB,0xDF,0x30,0x18,0x60,0x78,0xFF,0x60,0x80,0xFF,0x0E,0xF8,0x0F, + 0xE0,0xDF,0x38,0x1B,0xD8,0x60,0x18,0x60,0x70,0xFE,0x61,0x00,0xFF,0x0E,0xF8,0x07, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 +}; + +static const unsigned char PROGMEM splash_logo_bitmap_inverted[512] = { + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0x1F,0x00,0x18,0xFE,0x7F,0x10,0xFC,0x0F,0xC6,0x1F,0x00,0xF8,0xFF,0xFF, + 0xFF,0xFF,0x07,0x00,0x10,0xFC,0x7F,0x38,0xF8,0x07,0x87,0x07,0x00,0xE0,0xFF,0xFF, + 0xFF,0xFF,0x03,0x00,0x30,0xFC,0x3F,0x78,0xF0,0x83,0x87,0x03,0x00,0xC0,0xFF,0xFF, + 0xFF,0xFF,0x03,0x00,0x38,0xF8,0x3F,0x7C,0xE0,0xC1,0x87,0x03,0x00,0xC0,0xFF,0xFF, + 0xFF,0xFF,0xC1,0xFF,0x7F,0xF8,0x1F,0xFC,0xE0,0xE0,0x87,0xC1,0xFF,0xC3,0xFF,0xFF, + 0xFF,0xFF,0xE1,0xFF,0x7F,0xF0,0x0F,0xFE,0x41,0xF0,0x87,0xE1,0xFF,0xC3,0xFF,0xFF, + 0xFF,0xFF,0xE1,0xFF,0xFF,0xF0,0x0F,0xFF,0x03,0xF8,0x87,0xE1,0xFF,0x83,0xFF,0xFF, + 0xFF,0xFF,0x01,0x00,0xF8,0xE0,0x07,0xFF,0x07,0xFC,0x87,0xE1,0xFF,0x83,0xFF,0xFF, + 0xFF,0xFF,0x01,0x00,0xF8,0xE1,0x87,0xFF,0x0F,0xFC,0x87,0xE1,0xFF,0x83,0xFF,0xFF, + 0xFF,0xFF,0x01,0x00,0xF8,0xC1,0x83,0xFF,0x07,0xFC,0x87,0xE1,0xFF,0x83,0xFF,0xFF, + 0xFF,0xFF,0xE1,0xFF,0xFF,0xC3,0xC3,0xFF,0x03,0xF8,0x87,0xE1,0xFF,0x83,0xFF,0xFF, + 0xFF,0xFF,0xE1,0xFF,0xFF,0x87,0xC1,0xFF,0x41,0xF0,0x87,0xE1,0xFF,0xC3,0xFF,0xFF, + 0xFF,0xFF,0xC1,0xFF,0xFF,0x07,0xE1,0xFF,0xE0,0xE0,0x87,0xC1,0xFF,0xC3,0xFF,0xFF, + 0xFF,0xFF,0x03,0x00,0xF8,0x0F,0xE0,0x7F,0xF0,0xC1,0x87,0x03,0x00,0xC0,0xFF,0xFF, + 0xFF,0xFF,0x03,0x00,0xF8,0x0F,0xF0,0x3F,0xF8,0x83,0x87,0x03,0x00,0xC0,0xFF,0xFF, + 0xFF,0xFF,0x07,0x00,0xF0,0x1F,0xF8,0x1F,0xFC,0x07,0x87,0x07,0x00,0xE0,0xFF,0xFF, + 0xFF,0xFF,0x0F,0x00,0xF8,0x3F,0xF8,0x0F,0xFE,0x0F,0x86,0x0F,0x00,0xF8,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0x3F,0x60,0x7C,0x7C,0x3E,0xC0,0x00,0x3E,0x9E,0x01,0x01,0x7C,0x7E,0x03,0x0E,0xF8, + 0x1F,0x20,0x3C,0x3C,0x3C,0x80,0x00,0x1E,0x9E,0x00,0x00,0x7C,0x3E,0x01,0x04,0xF8, + 0x9F,0x3F,0x38,0x3C,0x38,0x9F,0xE7,0x1F,0x9C,0xFC,0x9F,0x7F,0x3E,0xF1,0xE4,0xFF, + 0x1F,0x30,0x39,0x9C,0x39,0x9F,0xE7,0x9F,0x98,0x00,0x9F,0x7F,0x3E,0xF1,0x04,0xFC, + 0x1F,0x20,0x91,0x9C,0x33,0x80,0xE7,0x9F,0x91,0x00,0x9F,0x7F,0x3E,0x01,0x0C,0xF0, + 0xFF,0x27,0x93,0x0C,0x30,0xC0,0xE7,0x9F,0x83,0xFC,0x9F,0x7F,0x3E,0x01,0xFE,0xF3, + 0x1F,0x20,0xC3,0x04,0x20,0xCF,0xE7,0x9F,0x87,0x00,0x9F,0x7F,0x00,0xF1,0x07,0xF0, + 0x1F,0x20,0xC7,0xE4,0x27,0x9F,0xE7,0x9F,0x8F,0x01,0x9E,0xFF,0x00,0xF1,0x07,0xF8, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF +}; + +static const unsigned char PROGMEM active_data_2x4_in_bitmap[4] = { + 0x02, + 0x02, + 0x03, + 0x02 +}; + +static const unsigned char PROGMEM active_data_2x4_out_bitmap[4] = { + 0x01, + 0x03, + 0x01, + 0x01 +}; + +static const unsigned char PROGMEM power_bitmap[32] = { + 0x80,0x01, + 0x80,0x01, + 0x90,0x09, + 0xB8,0x1D, + 0x9C,0x39, + 0x8E,0x71, + 0x86,0x61, + 0x86,0x61, + 0x86,0x61, + 0x06,0x60, + 0x06,0x60, + 0x0E,0x70, + 0x1C,0x38, + 0x78,0x1E, + 0xF0,0x0F, + 0xE0,0x07 +}; + +static const unsigned char PROGMEM wifi2_lv1_bitmap[32] = { + 0x00,0x00, + 0x00,0x00, + 0x00,0x00, + 0x00,0x00, + 0x00,0x00, + 0x00,0x00, + 0x00,0x00, + 0x00,0x00, + 0x80,0x01, + 0xE0,0x07, + 0x70,0x0E, + 0x00,0x00, + 0x80,0x01, + 0x80,0x01, + 0x00,0x00, + 0x00,0x00 +}; + +static const unsigned char PROGMEM wifi2_lv2_bitmap[32] = { + 0x00,0x00, + 0x00,0x00, + 0x00,0x00, + 0x00,0x00, + 0x00,0x00, + 0xC0,0x03, + 0xF0,0x0F, + 0x3C,0x3C, + 0x8C,0x31, + 0xE0,0x07, + 0x70,0x0E, + 0x00,0x00, + 0x80,0x01, + 0x80,0x01, + 0x00,0x00, + 0x00,0x00 +}; + +static const unsigned char PROGMEM wifi2_lv3_bitmap[32] = { + 0x00,0x00, + 0x00,0x00, + 0xF0,0x0F, + 0xFC,0x3F, + 0x1E,0x78, + 0xC7,0xE3, + 0xF0,0x0F, + 0x3C,0x3C, + 0x8C,0x31, + 0xE0,0x07, + 0x70,0x0E, + 0x00,0x00, + 0x80,0x01, + 0x80,0x01, + 0x00,0x00, + 0x00,0x00 +}; + +static const unsigned char PROGMEM bolt_16x16_bitmap[32] = { + 0x00,0x00, + 0x00,0x01, + 0x00,0x01, + 0x80,0x01, + 0xC0,0x01, + 0xC0,0x01, + 0xE0,0x1F, + 0xE0,0x0F, + 0xF0,0x0F, + 0xF8,0x07, + 0x80,0x03, + 0x80,0x03, + 0x80,0x01, + 0x80,0x01, + 0x80,0x00, + 0x00,0x00 +}; + +static const unsigned char PROGMEM charging_7x8_bitmap[8] = { + 0x3E, + 0x41, + 0x5D, + 0x45, + 0x45, + 0x5D, + 0x41, + 0x3E +}; + + +static const unsigned char PROGMEM charge_fast_7x8_bitmap[8] = { + 0x3E, + 0x7F, + 0x63, + 0x7B, + 0x7B, + 0x63, + 0x7F, + 0x3E +}; + +static const unsigned char PROGMEM warn_16x16_bitmap[32] = { + 0x00,0x00, + 0x80,0x01, + 0xC0,0x03, + 0xC0,0x07, + 0xE0,0x07, + 0x70,0x0E, + 0x70,0x0E, + 0x78,0x1E, + 0x78,0x1E, + 0x7C,0x3E, + 0xFC,0x3F, + 0x7E,0x7E, + 0x7E,0x7E, + 0xFF,0xFF, + 0xFF,0xFF, + 0xFF,0xFF +}; + +static const unsigned char PROGMEM error_16x16_bitmap[32] = { + 0xF0,0x0F, + 0xF8,0x1F, + 0xFC,0x3F, + 0x7E,0x7E, + 0x7F,0xFE, + 0x7F,0xFE, + 0x7F,0xFE, + 0x7F,0xFE, + 0x7F,0xFE, + 0x7F,0xFE, + 0xFF,0xFF, + 0x7F,0xFE, + 0x7E,0x7E, + 0xFC,0x3F, + 0xF8,0x1F, + 0xF0,0x0F +}; + +static const unsigned char PROGMEM fatal_16x16_bitmap[32] = { + 0xF0,0x0F, + 0xF8,0x1F, + 0xFC,0x3F, + 0xFE,0x7F, + 0xDF,0xFB, + 0x8F,0xF1, + 0x1F,0xF8, + 0x3F,0xFC, + 0x3F,0xFC, + 0x1F,0xF8, + 0x8F,0xF1, + 0xDF,0xFB, + 0xFE,0x7F, + 0xFC,0x3F, + 0xF8,0x1F, + 0xF0,0x0F +}; + +static const unsigned char PROGMEM timer_16x16_bitmap[32] = { + 0x80,0x01, + 0x80,0x19, + 0x00,0x38, + 0x08,0x70, + 0x10,0x60, + 0x73,0xC0, + 0xE3,0xC0, + 0xC3,0xC1, + 0x83,0xC1, + 0x03,0xC0, + 0x03,0xC0, + 0x06,0x60, + 0x06,0x60, + 0x1C,0x38, + 0xF8,0x1F, + 0xE0,0x07 +}; diff --git a/dev15/data/favicon.ico b/dev15/data/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c1443b0071a9f858bb06281d3a280e0a29bcf60b GIT binary patch literal 3606 zcmbtXc}$aM82^x?a#-*fM}b0t3Y4oz5jjd}Ia(;C1uEr~T10LI5wEGB@rn`gz;oWy z>71I(WXa}Cwq?O(TbBK`EzAB{wzxmcrppc!oyl*{`*Mm!tA5EZ-}k+Jp5ODl&-Xq@ z1;I_2B?JcxbdMDFcniWpK@b80M&DaK1mOm)#nBpx2ucEzbK&wL88t!Wf-Dn}5b_9N z1Xr$lnKTrE3+91@U#R2h>G5GgY&1k(o}aitE))Ct{w0+JBP=8cKHjtch>QrkCyNMo ziaCD1_as4YJdOwrL1=K`W8&ZZ0vqwkaWSYcXgOrz60|sK(Are%6!Ed6vJI6cbhkFK z{qB2J{%UI}5)&38Uz0Uqi}70w`AAAhMRd9z(j4oPxpLL_WTThh>BQEI{>Q}0Fz-w{ zMCt`-TDFwiP{hd*n_CIXsUMNG;}#ZHZ-(jSf5_hRDMWKaZWI6PX*Oo$w_1uJS41Hv zOU1d{Rx}_cy%3cb?{d!E{a?Z}APB|p-XDD*`U-Bd+;_Ul&%ObR(Tc_@tZsK8U#o`U z&8slI|CsOyx?|r#(AOZ|zK8R(eS~CM?h`*{%EUFJ|JvLo@HR^jo+!nJwVhBZQ?T^R zPn_R7LIJ_r4&m@e5EVC~uQC|{u6(>J`eKfNVGN8A(8A1@7wkazdm1sb!$3MQ)NMMf!0-iets-P z8qUkUtujf0lZSRgPaNfD6V(L`26t_tI#Gia_F6a^tD)CvUWvcOQHuAddk^C6q2t8_in?ngS(NJoB3M&m1YB~ z%1dx`|Mq9%f92d!s3@ObHa_8!AgGe!5k@_gBjp7d)!CkzOhF^K5yJ_a|sPX$|Nzxxx$<)43Na~O?g zd;)V}4hx(u^4dz`ib)SApWNKsR3b00Ewmz@T%CL(-2<^vk?`~J9-;Z?lxzy5VUkn+ zV!vckh$X){A1G2lcxJP&vboue$?u8IA|_081yI|{+9RM zc^M`Yp8Eww7w*E#Kd^HqecoFOW=>a4NTtfTsS$z^)H=1j1s*Lh1y(f zpuQCo6?yY1#jzKruU@P~MVTJOo}sWwlnct!#v?qU?qG%jn$H+sx={bzu9zR`jlF<@WnrTW;>T#t9o z9G>vMchyQdhtlf}0b#kNw7EoZx?l^jJ3l literal 0 HcmV?d00001 diff --git a/dev15/data/index.html b/dev15/data/index.html new file mode 100644 index 0000000..8493ee3 --- /dev/null +++ b/dev15/data/index.html @@ -0,0 +1,827 @@ + + + + + + UPS Dashboard + + + + + +
+ +
+ +

+ + + + +

+ +
+

Power Control:

+ +
+ + + +
+ + +
+ + +
+ +
+ + + +
+ +
+
+
+ + +
+ +
+ + + +
+ + + +
+
+ +
+

Charge Control:

+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + +
+ + +
+

Monitor:

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + + + \ No newline at end of file diff --git a/dev15/dev15.ino b/dev15/dev15.ino new file mode 100644 index 0000000..f64c650 --- /dev/null +++ b/dev15/dev15.ino @@ -0,0 +1,98 @@ +// the setup function runs once when you press reset or power the board + +#include "upsIO.h" +#include "upsSvr.h" +#include "mqtt.h" + +bool printDebug = true; + +bool inOk = false; +int16_t adc = 0; +float voltage = 0; +uint32_t infoTick = 0; + +void setup() { + // initialize digital pin LED_BUILTIN as an output. + Serial.begin(115200); + Serial.println(); + //ESP.wdtDisable(); + + //initUpsIo(true); + initUpsIo(); + //initUpsIo(false, true); + + upsServerInit(); + mqttInit(); + + //CharliplexDemoRoutine(); +} + +// the loop function runs over and over again forever +void loop() { + if (printDebug && (millis()-infoTick>=500)){ + infoTick = millis(); + adc = analogRead(A0);//ioAdc;//analogRead(A0);//ioAdc;// + //voltage = (16.0*((float)adc / 1024)) * 0.983; //use 1.5 MOhm series resistor + voltage = (16.0*((float)adc / 1024)) * 1.0; //use 1.5 MOhm series resistor + Serial.println("VBatt: " + (String(voltage, 2)) + " V"); + + //adc = ioAdcFiltered; + voltage = batteryVoltageUnfiltered;//(16.0*((float)adc / 1024)) * 1.0; //use 1.5 MOhm series resistor + Serial.println("VBatt (Sub-Min): " + (String(voltage, 2)) + " V"); + + //adc = adcValueMin; + voltage = batteryVoltage;//(16.0*((float)adc / 1024)) * 1.0; //use 1.5 MOhm series resistor + Serial.print("VBatt (Min): " + (String(voltage, 2)) + " V"); + Serial.print(" (" + (String(batteryPercentage)) + "%)"); + Serial.println(", LV: " + (String(batteryLevel))); + + + Serial.println("Charge Error: " + (String(chargeError))); + Serial.println("Charge Switch: " + (String(isChargeActive))); + Serial.println("Input Under Voltage: " + (String(inputUnderVoltage))); + Serial.println("Input Voltage Exists: " + (String(inputVoltageKeepup))); + + //inOk = digitalRead(pinInputSense); + //Serial.println("Vin: " + (String)(inOk?"True":"False")); + + //buttonState = !digitalRead(pinInputButton); //inverted + Serial.println("Button: " + (String)(buttonState?"True":"False")); + Serial.println("Button (short): " + (String)(buttonShortPress?"True":"False")); + Serial.println("Button (long): " + (String)(buttonLongPress?"True":"False")); + + Serial.println("Power OUT: " + (String)(powerOutputActive?"True":"False")); + //digitalWrite(pinLed, HIGH); // turn the LED on (HIGH is the voltage level) + //digitalWrite(pinPowerOut, HIGH); + + Serial.println("KEEP POWER: " + (String)(keepPowerState?"True":"False")); + + Serial.println("Display: " + (String)(displayActive?"True":"False")); + + Serial.println("RSSI: " + (String)(rssi)); + + Serial.println("Charge Allowed: " + (String)(chargeAllowed?"True":"False")); + + //for persistent shutdown demo: ============================================================== +// if (persistentShutdown){ +// Serial.println("Shutdown in: " + (String)(getPersistentShutdownTick()/1000000)); +// }else{ +// if (restartAutoPowerOn()) Serial.println("Automatic ON restarted"); +// setPersistentShutdown(10000000UL); +// } + //============================================================================================ + } + + upsServerRoutine(); + mqttRoutine(); + + upsRoutineInterrupt(); + + //delay(2000); // wait for a second + //digitalWrite(pinLed, LOW); // turn the LED off by making the voltage LOW + //digitalWrite(pinPowerOut, LOW); + + + //CharlieplexDemoRoutine(); + //UpsIoRoutine(); + +} diff --git a/dev15/mqtt.h b/dev15/mqtt.h new file mode 100644 index 0000000..b9aeea6 --- /dev/null +++ b/dev15/mqtt.h @@ -0,0 +1,211 @@ +#ifndef MQTT_H +#define MQTT_H + +#include +#include +#include + +const char* mqtt_server = "broker.emqx.io"; +const int mqtt_port = 1883; + +// Topics +const char* mqtt_topic_state = "ups/dev15/state"; +const char* mqtt_topic_cmd = "ups/dev15/cmd"; + +WiFiClient espClient; +PubSubClient mqttClient(espClient); + +unsigned long lastMqttReconnectAttempt = 0; +unsigned long lastMqttPublish = 0; +const unsigned long mqttPublishInterval = 2000; // Publish state every 2 seconds + +// Forward declaration +void mqttPublishState(); + +void mqttCallback(char* topic, byte* payload, unsigned int length) { + Serial.print("Message arrived on topic: "); + Serial.print(topic); + Serial.print(". Message: "); + String messageTemp; + + for (int i = 0; i < length; i++) { + Serial.print((char)payload[i]); + messageTemp += (char)payload[i]; + } + Serial.println(); + + // Parse command + if (String(topic) == mqtt_topic_cmd) { + DynamicJsonDocument doc(256); + DeserializationError error = deserializeJson(doc, payload, length); + if (!error) { + String cmd = doc["cmd"].as(); + String v = doc["val"].as(); + bool updated = false; + + if (cmd == "on") { + bool tgtState = v != "0"; + if (tgtState != powerOutputActive) { + // This function is defined in upsSvr.h / main logic + // Note: mqtt.h is included after or before? Need to ensure it can call setOutputPowerState + // Actually, setOutputPowerState is declared in upsCfg.h maybe? + // If compilation fails, we can just set the flag. Wait, let's use the function since it's global. + setOutputPowerState(tgtState); + updated = true; + } + } else if (cmd == "auto") { + uint8_t tmpAutoOnMode = v.toInt(); + if ((tmpAutoOnMode != cfgAutoOn) && (tmpAutoOnMode <= 2)) { + cfgAutoOn = tmpAutoOnMode; + configChanged = true; + updated = true; + } + } else if (cmd == "shutdown") { + uint16_t tSec = v.toInt(); + if (tSec > 999) tSec = 999; + uint32_t nanoT = ((uint32_t)tSec) * 1000000UL; + if (nanoT < 999999) nanoT = 999999; + bool ok = setPersistentShutdown(nanoT); + if (cfgShutdownInSeconds != tSec && ok) { + cfgShutdownInSeconds = tSec; + configChanged = true; + updated = true; + } + } else if (cmd == "shutlv") { + int16_t lv = v.toInt(); + if (lv >= 0 && lv <= 5) { + if (lv != cfgShutdownSuggestion) { + cfgShutdownSuggestion = lv; + configChanged = true; + updated = true; + } + } + } else if (cmd == "cm") { + int16_t i = v.toInt(); + if (i >= 0 && i <= 9) { + cfgChargeMode = i; + configChanged = true; + updated = true; + } + } else if (cmd == "coil") { + int16_t i = v.toInt(); + if (i >= 0 && i <= 5) { + cfgInputLostTriggerChargeMode = i; + configChanged = true; + updated = true; + } + } else if (cmd == "covp") { + int16_t i = v.toInt(); + if (i > 1) i = 1; + if (i != cfgChargeOvervoltageProtection) { + cfgChargeOvervoltageProtection = i; + configChanged = true; + updated = true; + } + } else if (cmd == "rofc") { + int16_t i = v.toInt(); + if (i > 0 && needFullChargeOnce) { + resetOnGoingFullChargeAfterLineLost(); + updated = true; + } + } else if (cmd == "car") { + int16_t i = v.toInt(); + if (i > 1) i = 1; + if (i != cfgChargeAutomaticReducer) { + cfgChargeAutomaticReducer = i; + configChanged = true; + updated = true; + } + } + + if (updated) { + mqttPublishState(); + } + } + } +} + +boolean mqttReconnect() { + String clientId = "UPS_DEV15_" + String(random(0xffff), HEX); + Serial.print("Attempting MQTT connection as "); + Serial.println(clientId); + if (mqttClient.connect(clientId.c_str())) { + Serial.println("MQTT Connected"); + // Once connected, publish an announcement... + mqttClient.publish("ups/dev15/status", "online"); + // ... and resubscribe + mqttClient.subscribe(mqtt_topic_cmd); + } else { + Serial.print("failed, rc="); + Serial.print(mqttClient.state()); + Serial.println(" try again in 5 seconds"); + } + return mqttClient.connected(); +} + +void mqttInit() { + mqttClient.setServer(mqtt_server, mqtt_port); + mqttClient.setBufferSize(1024); // Increase buffer size for large JSON payloads + mqttClient.setCallback(mqttCallback); +} + +void mqttPublishState() { + DynamicJsonDocument jsonDoc(1024); + String jsonResponse; + + uint8_t lineActiveState = isUpsPoweredFromBattery() ? 2 : isInputLinePowered() ? 1 : isUpsOn() ? 2 : 0; + + jsonDoc["success"] = 2; + jsonDoc["on"] = powerOutputActive ? 1 : 0; + jsonDoc["state"] = isUpsOn() ? 1 : 0; + jsonDoc["v"] = String(batteryVoltage, 2); + jsonDoc["batt"] = batteryLevel; + jsonDoc["battP"] = batteryPercentage; + jsonDoc["line"] = lineActiveState; + + jsonDoc["auto"] = cfgAutoOn; + jsonDoc["battBlink"] = isUpsBatteryIndicatorChargingOrBlink() ? 1 : 0; + jsonDoc["battCritical"] = isUpsBatteryCritical() ? 1 : 0; + jsonDoc["chargeLv"] = getRawBatteryLevelFromPercentage(batteryPercentage); + jsonDoc["battMax"] = 4; + jsonDoc["timedShutdown"] = (persistentShutdown && powerOutputActive) ? 1 : 0; + jsonDoc["shutdownRemain"] = getPersistentShutdownTick() / 1000000; + jsonDoc["shutdownSuggestMode"] = cfgShutdownSuggestion; + jsonDoc["shutdownSuggest"] = getShutdownSuggestionFlag() ? 1 : 0; + jsonDoc["shutLastTimer"] = cfgShutdownInSeconds; + + jsonDoc["chgError"] = chargeError; + jsonDoc["charging"] = getChargingState(); + jsonDoc["chgMode"] = cfgChargeMode; + jsonDoc["chgOVP"] = cfgChargeOvervoltageProtection; + jsonDoc["chgReducer"] = cfgChargeAutomaticReducer; + jsonDoc["chgFullTrig"] = cfgInputLostTriggerChargeMode; + jsonDoc["chgOFC"] = needFullChargeOnce ? 1 : 0; + + serializeJson(jsonDoc, jsonResponse); + mqttClient.publish(mqtt_topic_state, jsonResponse.c_str()); +} + +void mqttRoutine() { + if (!mqttClient.connected()) { + long now = millis(); + if (now - lastMqttReconnectAttempt > 5000) { + lastMqttReconnectAttempt = now; + // Attempt to reconnect + if (mqttReconnect()) { + lastMqttReconnectAttempt = 0; + } + } + } else { + mqttClient.loop(); + + // Publish state periodically + long now = millis(); + if (now - lastMqttPublish > mqttPublishInterval) { + lastMqttPublish = now; + mqttPublishState(); + } + } +} + +#endif diff --git a/dev15/ringkasan.md b/dev15/ringkasan.md new file mode 100644 index 0000000..4b767ea --- /dev/null +++ b/dev15/ringkasan.md @@ -0,0 +1,203 @@ +# Ringkasan WebServer & API — Smart UPS V1.1 + +## Struktur Kode WebServer + +### `upsSvr.h` — Backend (C++ ESP8266) +```cpp +AsyncWebServer server(80); // Server HTTP port 80 +``` + +**Route handlers** (daftar endpoint): +```cpp +server.on("/login", HTTP_GET, handler); // Validasi login +server.on("/logout", HTTP_GET, handler); // Logout +server.on("/state", HTTP_GET, handleGetState); // REST API utama +server.on("/", onRootRequest); // Dashboard HTML +server.on("/*", onRootRequest); // Catch-all → dashboard +``` + +**Auth checking** — manual Base64 decode: +```cpp +const char* http_username = "admin"; +const char* http_password = "admin123"; + +bool checkAuth(request) { + // 1. Ambil header "Authorization" + // 2. Cek apakah diawali "Basic " + // 3. Base64 decode bagian setelah "Basic " + // 4. Split jadi "username:password" + // 5. Bandingkan dengan http_username / http_password +} +``` + +Setiap request ke `/state` dicek dulu: +```cpp +void handleGetState(request) { + if (!checkAuth(request)) { + request->send(401, "application/json", "{\"success\":0}"); // JSON, bukan popup browser + return; + } + // ... baca/tulis parameter dan kirim response JSON ... +} +``` + +**Fungsi utama `handleGetState()`**: +1. Baca parameter URL (`on`, `auto`, `shutdown`, `cm`, dll) +2. Jika ada parameter → eksekusi perintah (misal: nyalakan power) +3. Jika tidak ada parameter → kirim status lengkap sebagai JSON + +### `webUI.h` — Frontend (HTML+JS embedded) +HTML ini disimpan di PROGMEM (flash), fallback jika file LittleFS tidak ada. + +```cpp +const char index_html[] PROGMEM = R"rawliteral( + + ... semua HTML, CSS, JavaScript ... +)rawliteral"; +``` + +### `data/index.html` — Frontend (HTML+JS di LittleFS) +File fisik di LittleFS, sama persis dengan `webUI.h`. Server prioritaskan ini. + +### `upsCfg.h` — Konfigurasi +Simpan & baca pengaturan dari `/cfg.json` di LittleFS. +```cpp +void saveCfg(); // Simpan: auto, shut, chgMode, chgILTC, chgOVP, chgAR +void loadCfg(); // Baca kembali +``` + +--- + +## Alur Request + +### Request tanpa parameter → Refresh status +``` +Browser → GET /state (Authorization: Basic ...) + ← 200 {"success":2, "on":1, "v":"12.5", ...} +``` + +### Request dengan parameter → Eksekusi perintah +``` +Browser → GET /state?on=1 (Authorization: Basic ...) + ← 200 {"success":1, "on":1, ...} +``` + +### Request tanpa auth → Ditolak +``` +Browser → GET /state (tanpa Authorization) + ← 401 {"success":0} +``` + +--- + +## Cara Menggunakan API + +### 1. Baca Status UPS +```bash +curl -u admin:admin123 http://192.168.1.100/state +``` +Response: +```json +{"success":2, "on":1, "state":1, "v":"13.20", "batt":4, "battP":100, "line":1, ...} +``` + +### 2. Nyalakan Power Output +```bash +curl -u admin:admin123 "http://192.168.1.100/state?on=1" +``` + +### 3. Matikan Power Output +```bash +curl -u admin:admin123 "http://192.168.1.100/state?on=0" +``` + +### 4. Shutdown Timer (120 detik) +```bash +curl -u admin:admin123 "http://192.168.1.100/state?shutdown=120" +``` + +### 5. Set Mode Auto ON +```bash +curl -u admin:admin123 "http://192.168.1.100/state?auto=0" # Disabled +curl -u admin:admin123 "http://192.168.1.100/state?auto=1" # Line Powered +curl -u admin:admin123 "http://192.168.1.100/state?auto=2" # Persistent on Line +``` + +### 6. Set Charge Mode (0=Always, 1=90-100%, ..., 9=80-85%) +```bash +curl -u admin:admin123 "http://192.168.1.100/state?cm=1" +``` + +### 7. Set FCoPL (Full Charge on Power Lost) +```bash +curl -u admin:admin123 "http://192.168.1.100/state?coil=0" # Disabled +curl -u admin:admin123 "http://192.168.1.100/state?coil=1" # Always +curl -u admin:admin123 "http://192.168.1.100/state?coil=3" # At battery ≤90% +``` + +### 8. AVR / OVP +```bash +curl -u admin:admin123 "http://192.168.1.100/state?car=1" # AVR ON +curl -u admin:admin123 "http://192.168.1.100/state?car=0" # AVR OFF +curl -u admin:admin123 "http://192.168.1.100/state?covp=1" # OVP ON +``` + +### 9. Set Shutdown Suggestion Level +```bash +curl -u admin:admin123 "http://192.168.1.100/state?shutlv=0" # Disabled +curl -u admin:admin123 "http://192.168.1.100/state?shutlv=1" # On Battery +curl -u admin:admin123 "http://192.168.1.100/state?shutlv=3" # ≤50% Battery +``` + +### 10. Login (untuk testing) +```bash +curl -u admin:admin123 http://192.168.1.100/login +# 200 → {"success":1} +``` + +### 11. Buka Dashboard via Browser +``` +http://192.168.1.100/ +→ Login overlay (isi admin / admin123) +→ Dashboard muncul +→ Klik "Logout" untuk keluar +``` + +--- + +## Respon JSON — Penjelasan Field + +```json +{ + "success": 2, // 1=ada perubahan, 2=refresh biasa + "on": 1, // target power (yg diminta ON/OFF) + "state": 1, // real power (aktual ON/OFF) + "v": "13.20", // tegangan baterai (volt) + "batt": 4, // level baterai (0-4) + "battP": 100, // persentase baterai (0-100) + "line": 1, // 0=OFF, 1=Line/AC, 2=Battery + "auto": 1, // mode auto ON (0/1/2) + "battBlink": 0, // indicator charging blink + "battCritical": 0, // baterai kritis + "timedShutdown": 0, // shutdown timer aktif? + "shutdownRemain": 0, // sisa detik shutdown + "chgError": 0, // 0=normal, 1=batt not detected, 2=short, 4=overvoltage + "charging": 2, // 0=tidak charge, 1=reduced, 2=full charging + "chgMode": 1, // mode charge (0..9) + "chgOVP": 1, // overvoltage protection ON/OFF + "chgReducer": 0, // AVR ON/OFF + "chgFullTrig": 3, // FCoPL mode (0..5) + "chgOFC": 0 // ongoing full charge flag +} +``` + +--- + +## Catatan Penting + +| Hal | Keterangan | +|-----|-----------| +| **Auth** | Basic Auth, username/password hardcoded di `upsSvr.h:16-17` | +| **Login gagal** | Server return `401` → JS tampilkan overlay login (bukan popup browser) | +| **Kompresi** | Ganti username/password dengan mengedit `upsSvr.h` | +| **Rename** | `upsSvr.h` → file backend, `webUI.h` → frontend embedded, `upsCfg.h` → config | diff --git a/dev15/upsCfg.h b/dev15/upsCfg.h new file mode 100644 index 0000000..02edeb2 --- /dev/null +++ b/dev15/upsCfg.h @@ -0,0 +1,130 @@ +#include +#include + +//shutdown suggestion level +#define SHUTDOWN_SUGGESTION_DISABLED 0 +#define SHUTDOWN_SUGGESTION_ON_BATT 1 +#define SHUTDOWN_SUGGESTION_ON_BATT_75 2 +#define SHUTDOWN_SUGGESTION_ON_BATT_50 3 +#define SHUTDOWN_SUGGESTION_ON_BATT_25 4 +#define SHUTDOWN_SUGGESTION_ON_BATT_CRITICAL 5 +static volatile uint8_t cfgShutdownSuggestion = SHUTDOWN_SUGGESTION_ON_BATT_25; + +#define AUTO_POWER_OFF 0 +#define AUTO_POWER_ON 1 +#define AUTO_POWER_PERSIST 2 +static volatile uint8_t cfgAutoOn = AUTO_POWER_ON;//AUTO_POWER_PERSIST;//AUTO_POWER_ON; + +#define SHUTDOWN_DURATION_DEFAULT 120 +static volatile uint16_t cfgShutdownInSeconds = SHUTDOWN_DURATION_DEFAULT;//default time in seconds for shutdown + +//full charge trigger: +#define INPUT_LOST_TRIGGER_CHARGE_NONE 0 +#define INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_ALWAYS 1 //trigger full charge if below 100 percent +#define INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_95 2 //trigger full charge if below 95 percent +#define INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_90 3 //trigger full charge if below 90 percent +#define INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_85 4 //trigger full charge if below 85 percent +#define INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_80 5 //trigger full charge if below 80 percent +static volatile uint8_t cfgInputLostTriggerChargeMode = INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_90; + +#define CHARGE_MODE_ALWAYS 0 +#define CHARGE_MODE_RANGE_90_100 1 +#define CHARGE_MODE_RANGE_85_100 2 +#define CHARGE_MODE_RANGE_80_100 3 +#define CHARGE_MODE_RANGE_90_95 4 +#define CHARGE_MODE_RANGE_85_95 5 +#define CHARGE_MODE_RANGE_80_95 6 +#define CHARGE_MODE_RANGE_85_90 7 +#define CHARGE_MODE_RANGE_80_90 8 +#define CHARGE_MODE_RANGE_80_85 9 +static volatile uint8_t cfgChargeMode = CHARGE_MODE_RANGE_85_90; + +static volatile uint8_t cfgChargeOvervoltageProtection = 1; +static volatile uint8_t cfgChargeAutomaticReducer = 1; + +bool spiffsOk = false; // flag tetap bernama spiffsOk agar kompatibel dengan kode lain + +bool initConfigs(){ + // Setup LittleFS + spiffsOk = LittleFS.begin(); + if (!spiffsOk){ + Serial.println("LittleFS ERROR!"); + }else{ + // DEBUG: List all files in LittleFS + Serial.println("=== LittleFS Files ==="); + Dir dir = LittleFS.openDir("/"); + int fileCount = 0; + while (dir.next()) { + fileCount++; + Serial.print(" FILE: "); + Serial.print(dir.fileName()); + Serial.print(" ["); + Serial.print(dir.fileSize()); + Serial.println(" bytes]"); + } + if (fileCount == 0) Serial.println(" (no files found)"); + Serial.println("=== End LittleFS ==="); + } + return spiffsOk; +} + +void loadCfg(){ + if (!spiffsOk) return; + // Open file for reading + File file = LittleFS.open("/cfg.json", "r"); + if (!file) { + Serial.println("Failed to open config"); + return; + } + + // Parse file contents as JSON + DynamicJsonDocument jsonDoc(255); + DeserializationError error = deserializeJson(jsonDoc, file); + file.close(); + + if (error) { + Serial.println("Failed to parse file settings"); + return; + } + + uint8_t tmpAutoOnMode = jsonDoc["auto"].as(); + cfgAutoOn = tmpAutoOnMode; + if (cfgAutoOn>AUTO_POWER_PERSIST) cfgAutoOn = AUTO_POWER_PERSIST; + + uint8_t v = jsonDoc["shut"].as(); + if (v>SHUTDOWN_SUGGESTION_ON_BATT_CRITICAL) v = SHUTDOWN_SUGGESTION_ON_BATT_25; + cfgShutdownSuggestion = v; + + v = jsonDoc["chgMode"].as(); + if (v>CHARGE_MODE_RANGE_80_85) v = CHARGE_MODE_RANGE_90_100; + cfgChargeMode = v; + + v = jsonDoc["chgILTC"].as(); + if (v>INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_80) v = INPUT_LOST_TRIGGER_CHARGE_NONE; + cfgInputLostTriggerChargeMode = v; + + v = jsonDoc["chgOVP"].as(); + if (v>1) v = 1; + cfgChargeOvervoltageProtection = v; + + v = jsonDoc["chgAR"].as(); + if (v>1) v = 1; + cfgChargeAutomaticReducer = v; +} + +void saveCfg(){ + if (!spiffsOk) return; + DynamicJsonDocument jsonDoc(255); + jsonDoc["auto"] = cfgAutoOn; + jsonDoc["shut"] = cfgShutdownSuggestion; + jsonDoc["chgMode"] = cfgChargeMode; + jsonDoc["chgILTC"] = cfgInputLostTriggerChargeMode; + jsonDoc["chgOVP"] = cfgChargeOvervoltageProtection; + jsonDoc["chgAR"] = cfgChargeAutomaticReducer; + File file = LittleFS.open("/cfg.json", "w"); + if (file) { + uint32_t sz = serializeJson(jsonDoc, file); + file.close(); + Serial.println("Configuration updated!"); + } +} diff --git a/dev15/upsIO.h b/dev15/upsIO.h new file mode 100644 index 0000000..e7615d5 --- /dev/null +++ b/dev15/upsIO.h @@ -0,0 +1,1338 @@ +//UPS CORE ========================================================================================================= +//handle: - Button, state (short hold, long hold) +// - Keep Power State +// - ADC (voltage, battery level) +// - Input OK Threshlod Sense +// - State for: power bad (blinks -- low_output <=> as_input), power ok (led on), charge disable (led off) + +//todo: + // - misc./cosmetic logic: when input line lost and charging error happens (ERROR NO BATTERY) => stop charge then reset error mode state, since it may switch to ERROR SHORTED. Can be ignored since will not happen in real condition, only happened when use voltage source from programmer. (OK) + // - charging state bugs: after power line lost, battery bar blinking and charge control pinout state is LOW, while webserver just normal. + // - charge error no longer blinking the LED in charge control pinout, since OLED are now used (OK) + // - stop charge action at least 2-3 seconds at startup to detect battery (OK) + // - Charge Error no longer blink the charigin led indicator, since now using OLED (OK) + // - Using BMS may cause capacitive reading, thus battery detection innaccurate at startup, need retweak (OK, solved by charge delay at startup) + // - directive to invert input under voltage detection should delayed 1 seconds after keep missing (since switching can cause voltage drops/spikes) - OK + // - known bugs: when no input line and power going off, charging indicator and charging control indicator may still blink or active rather than turned off (OK) + +//known schematic weakness: + // - Positive side control for output power control caused brief activation states when line powered for first time (seems caused by MOSFET effect when difference voltage spiked, since keep powering esp from programmer while trigger input line give same effect). + //Workaround: Use XL4005 EN pinout control connected to GPIO5 with diode (1N4148) and resistor if necessary + +#include "upsOled.h" +#include "battLib.h" + +#ifndef UPSIO_H + #define UPSIO_H +#endif + +#define HOLD_POWER_TICK 4000000UL ///minimum power hold since startup in nanoseconds + +//pinouts: +#define KEEP_POWER_PIN 15 //keep power pinout +//minimum voltage sense pinout +#define MINIMUM_INPUT_PIN 4 //Vin 16V sense (15.6V with 5K6 resistor) +//button pinout +#define BUTTON_INPUT_PIN 16 //button sense +//charge stop or limiter pinout +#define CHARGE_STOP_PIN 2 //same as LED_BUILTIN +//power output activation pinout +#define POWER_OUTPUT_PIN 5 //power out activation + +static uint32_t tickStep = 1; +static uint32_t tickSinceStarted = 0; +static uint32_t tickKeepPower = 0; + +static bool keepPower = false; +static bool keepPowerState = false; + +//for timer routines: +static volatile uint32_t ioAdcTick = 0; +static volatile uint8_t lowRateTick = 0; +static volatile bool lowRateRoutine = false; +#define ADC_CAP_BUFFER_NUM 32 +static volatile uint16_t adcCap[ADC_CAP_BUFFER_NUM]; +static volatile uint8_t adcCapCount = 0; +static volatile uint8_t adcIdx = 0; +static volatile uint16_t ioAdc = 0; +static volatile uint16_t ioAdcPrev = 0; +static volatile uint16_t ioAdcFiltered = 0; +static volatile uint16_t adcValueMin = 0; +//temps +static volatile uint8_t ioLoop = 0; +static volatile uint16_t adcTmp = 0; +static volatile uint8_t adcIdx2 = 0; +static volatile uint8_t adcSubTick = 0; +static volatile uint8_t adcSkipCounter = 0; + +//for battery level calculation +static volatile float adcVoltageBase = 0.0; //base voltage (for addition) +static volatile float adcMaxVoltageCapability = 16.0; //max voltage of ADC can read +static volatile float batteryVoltageUnfiltered = 0; +static volatile float batteryVoltage = 0; +static volatile float batteryLevel = 0; +static volatile uint8_t batteryPercentage = 0; +static volatile float batteryVoltageEnableOn = battVoltageCritical; + +//charge stop mode +#define CHARGE_ACTIVE_LIGHT_OFF 0 //pinout as input (may have effect charge voltage slightly below set since red led is dimmed) +#define CHARGE_ACTIVE_LIGHT_ON 1 //pinout as output, low state +#define CHARGE_STOP_LIGHT_OFF 2 //pinout as output, high state + +static volatile uint8_t chargeMode = CHARGE_ACTIVE_LIGHT_OFF; +static volatile bool isChargeActive = false; + +#define CHARGE_ERROR_NONE 0 +#define CHARGE_ERROR_NO_BATTERY 1 //also partial cell shorted, or battery not detected +#define CHARGE_ERROR_SHORTED 2 //charger output or battery shorted +#define CHARGE_ERROR_OVERVOLTAGE 4 //or also charger error / cannot be stopped +static volatile uint8_t chargeError = CHARGE_ERROR_NONE; +static volatile uint8_t chargeErrorPrev = CHARGE_ERROR_NONE; +static volatile uint8_t chargeManagementMode = 0; +static volatile bool chargeVoltageOK = false;//used for charge logic when charge voltage sense is enough + +static volatile bool powerOutputActive = false; +static volatile bool inputUnderVoltage = false; //input under voltage, or there is no input voltage at all stay for few seconds +static volatile bool inputVoltageKeepup = false;//input voltage ok, status stay for inputVoltageKeepupDuration in nanoseconds +static volatile uint32_t inputVoltageKeepupDuration = 500000UL; +static volatile uint32_t underVoltageTick = 0; +static volatile uint32_t inputVoltageKeepupTick = 0; +static volatile bool underVoltageIntterupted = false; +static volatile bool inputVoltageOK = false;//used for charge logic when input voltage sense is high +static volatile bool inputVoltageOKPrev = false; +static volatile bool startupReady = false; //delay 1 seconds from startup for +static volatile bool inputVoltageReady = false; //delay 1 seconds when input voltage resume +static volatile uint32_t inputVoltageResumeTick = 0; + +//for battery state check routine +#define SHORT_VOLTAGE 2.5 +static volatile uint32_t batteryCheckTick = 0; +static volatile uint32_t batteryCheckSkipTick = 0; +static volatile uint32_t chargeBlinkTick = 0; +static volatile uint32_t chargeStartedTick = 0; +static volatile bool blinkChargeError = false; + +//for input button pattern +static volatile bool buttonIgnore = false; //ignore button flag +static volatile bool buttonState = false; +static volatile bool buttonStatePrev = false; +static volatile bool buttonShortPress = false; //1 sec to power ON or OFF +static volatile bool buttonLongPress = false; //8 sec while powered to reset configuration (wifi setup) +static volatile bool buttonShortPressIgnore = false; +static volatile bool buttonLongPressIgnore = false; +static volatile uint32_t buttonTick = 0; +static volatile uint32_t buttonTickIgnore = 0; + +//for indicators +static volatile uint8_t flashAllBatteryLevel = 0; +static volatile bool indicatorFastBlink = false; //fast shortcut for fast blink (each 100ms) +static volatile bool indicatorMediumBlink = false; +static volatile bool indicatorSlowBlink = false; +static volatile uint32_t indicatorBlinkTick = 0; +static volatile uint8_t indicatorBlinkIndex = 0; //cycled per 10 count for 1 sec +static volatile uint32_t indicatorBlinkTickB = 0; //for fast blink +//wifi indicator +#define NET_STATE_OFF 0 +#define NET_STATE_CONNECTING 1 +#define NET_STATE_CONNECTED 2 +static volatile uint8_t indicatorNetworkState = 0; +static volatile uint8_t indicatorNetworkSignalLevel = 0; +static volatile uint8_t indicatorNetworkBlinkerTX = 0; +static volatile uint8_t indicatorNetworkBlinkerRX = 0; +static volatile bool displayNetworkFlipFlag = false; + +//battery indicator +static volatile uint8_t battIdx = 0; + +//charging mode: +//#define CHARGE_MODE_DEFAULT 0 //charges to full then stop, resumed when capacity lower than 85% +//#define CHARGE_MODE_ALWAYS 1 //keep battery charging all the time +//#define CHARGE_MODE_TOPPED 2 //charges to full then stop, resumed when voltage lower than full rest +//#define CHARGE_MODE_TOPPED_CUSTOM 3 //charges to full then stop, resumed when capacity lower than n-percentage +//#define CHARGE_MODE_RANGED 4 //charges to a-percentage, resumed when capacity lower than b-percentage +static volatile bool chargeAfterInputLost = false;//force charge resume activation when input is resumed +static volatile bool fullChargeAfterInputLost = true; //force full charge after input lost (chargeAfterInputLost should be set to true) +static volatile uint8_t triggerFullChargeAfterInputLostAtPercentage = 90;//capacity left before full charge triggered (fullChargeAfterInputLost should be true) +static volatile bool chargeToFull = true; //will charge to full before stop, if false => wull use chargePercentageForStop +static volatile uint8_t chargePercentageForStop = 100; +static volatile uint8_t chargePercentageForResume = 85; +static volatile bool chargeAlwaysActive = false; //use this to charge continuosly +static volatile bool chargeOvervoltageProtection = true; //activate over voltage protection +static volatile bool chargeReduceAtBatteryFull = true; //try to reduce charge voltage by hardware when reach minimum fullest +static volatile bool chargeInputLost = true; //charge lost flags +static volatile uint32_t chargeWaitBeforeStop = 120000000UL; //in nanoseconds (120000000 = 2 minutes) +static volatile uint32_t chargeWaitStopTick = 0; +static volatile bool needFullChargeOnce = false; + +//critical shutdown +static volatile uint32_t criticalDelayShutdown = 120000000UL; //in nanoseconds (120000000 = 2 minutes) +static volatile uint32_t persistentDelayShutdown = 120000000UL; //in nanoseconds (120000000 = 2 minutes) +static volatile uint32_t criticalShutdownTick = 0; +static volatile uint32_t persistentShutdownTick = 0; +static volatile bool persistentShutdown = false; +static volatile bool criticalShutdown = false; + +//automatic power resume +static volatile bool autoPowerOn = false; //power on when input resumed and in stable powered? +static volatile bool forceAutoPowerAtBatteryError = true; //force power on, even no battery exist or shorted (charge system error) +static volatile uint32_t autoPowerOnDelay = 4000000UL; //default is 4 seconds +static volatile uint32_t autoPowerOnTick = 0; +static volatile bool ongoingToPowerOn = false; //var flag +static volatile bool powerEverLoss = true; + +//static volatile bool interruptEnable = false; + +//when not using interrupt (that caused conflict with asyncwerbserver) +static volatile uint32_t currentNs = 0; +static volatile uint32_t lastNs = 0; + + +//static volatile bool batterySimpleIndicator = false; +static volatile bool batteryBlinkingIndicatorState = false; //blinking shortcut (either on charge active or critical) +static volatile bool batteryLastStateCritical = false; + +static volatile uint32_t displayRefreshDelay = 100000; //10 fps +static volatile uint32_t displayTick = 0; + +static volatile bool onGoingDisplayOff = false; +static volatile uint32_t turnOffDisplayAfter = 0; +static volatile uint32_t turnOffDisplayTick = 0; +static volatile bool displayActive = false; + +static volatile bool displayFlipFlag = false; + +static volatile bool splashEverDisplayed = false; +static volatile bool displaySplashNow = false; +static volatile uint32_t splashDisplayTick = 0; + +static volatile bool chargeAllowed = false; +static volatile uint32_t chargeAllowedTick = 0; +static volatile uint32_t chargeAllowedDelay = 3000000; + +bool isUpsBatteryCritical(){ + //staged + if (batteryLastStateCritical){ + if ((batteryVoltage>battVoltageCritical) && (batteryLevel>0) && (batteryPercentage>12)){ + batteryLastStateCritical = false; + } + }else{ + if ((batteryVoltage 60) { //1:60 sps filter + adcSubTick = 0; + ioAdcFiltered = ioAdc; + } else { + if (adcSkipCounter < 60) { + //feed first 60 cycles + ioAdcFiltered = ioAdc; + adcSkipCounter++; + } else { + return; //skips + } + } + //===================================================== + + //main minimum sub samplings (long filter, 1:32 sps): + + //if have 1:8 changes jumps + if (ioAdc > ioAdcPrev) { + if ((ioAdc - ioAdcPrev) > 128) { + adcCapCount = 0; + adcIdx = 0; + adcValueMin = ioAdc; + } + } else { + if ((ioAdcPrev - ioAdc) > 128) { + adcCapCount = 0; + adcIdx = 0; + adcValueMin = ioAdc; + } + } + ioAdcPrev = ioAdc; + + batteryVoltageUnfiltered = adcVoltageBase + (adcMaxVoltageCapability * ((float)ioAdc / 1024)); + + adcCap[adcIdx] = ioAdc; + + adcCapCount++; + if (adcCapCount > ADC_CAP_BUFFER_NUM) adcCapCount = ADC_CAP_BUFFER_NUM; + if (adcCapCount == 1) { + adcValueMin = ioAdc; + } else if (adcCapCount < ADC_CAP_BUFFER_NUM) { + if (ioAdc < adcValueMin) adcValueMin = ioAdc; + } else { + adcTmp = ioAdc; + if (adcIdx == 0) { + adcIdx2 = ADC_CAP_BUFFER_NUM - 1; + } else { + adcIdx2 = adcIdx - 1; + } + for (ioLoop = 1; ioLoop < ADC_CAP_BUFFER_NUM; ioLoop++) { + if (adcCap[adcIdx2] < adcTmp) adcTmp = adcCap[adcIdx2]; + if (adcIdx2 == 0) { + adcIdx2 = ADC_CAP_BUFFER_NUM - 1; + } else { + adcIdx2--; + } + } + adcValueMin = adcTmp; + + //batteryLevel = getBatteryLevel(); + } + + adcIdx++; + if (adcIdx >= ADC_CAP_BUFFER_NUM) adcIdx = 0; + + batteryVoltage = adcVoltageBase + (adcMaxVoltageCapability * ((float)adcValueMin / 1024)); + batteryPercentage = getBatteryPercentage(batteryVoltage); + batteryLevel = getBatteryLevel(); + +} + +void setDisplayForTurnOff(uint32_t tDelay){ + onGoingDisplayOff = true; + turnOffDisplayTick = tickSinceStarted; + turnOffDisplayAfter = tDelay; +} + +void turnOnDisplay(){ + onGoingDisplayOff = false; + displayActive = true; +} + +void turnOffDisplay(){ + onGoingDisplayOff = false; + displayActive = false; +} + +static void setChargeMode(uint8_t chgMode) { + + if (chgMode == CHARGE_STOP_LIGHT_OFF) { + pinMode(CHARGE_STOP_PIN, OUTPUT); + digitalWrite(CHARGE_STOP_PIN, HIGH); + chargeMode = chgMode; + isChargeActive = false; + needFullChargeOnce = false; + } else if (chgMode == CHARGE_ACTIVE_LIGHT_ON) { + if (chargeAllowed){ + pinMode(CHARGE_STOP_PIN, OUTPUT); + digitalWrite(CHARGE_STOP_PIN, LOW); + chargeMode = chgMode; + isChargeActive = true; + } + } else if (chgMode == CHARGE_ACTIVE_LIGHT_OFF) { + if (chargeAllowed){ + pinMode(CHARGE_STOP_PIN, INPUT); + //digitalWrite(CHARGE_STOP_PIN, LOW); + chargeMode = chgMode; + if (!isChargeActive){ + //reset waiting tick each time charge activated + if (chargeWaitStopTick>0) chargeWaitStopTick = 0; + } + isChargeActive = true; + } + } +} + +uint8_t getChargingState(){ + //0 = false, 1 = reduced, 2 = normal charge + if (inputVoltageKeepup){ + if (chargeMode==CHARGE_ACTIVE_LIGHT_OFF){ + return 1; + }else if (chargeMode==CHARGE_ACTIVE_LIGHT_ON){ + return 2; + } + } + return 0; +} + +static void setKeepPower(bool state) { + keepPower = state; + if (keepPowerState != keepPower) { + keepPowerState = keepPower; + if (keepPowerState) { + pinMode(KEEP_POWER_PIN, OUTPUT); + digitalWrite(KEEP_POWER_PIN, HIGH); + } else { + //pinMode(KEEP_POWER_PIN, OUTPUT); + //digitalWrite(KEEP_POWER_PIN, LOW); + pinMode(KEEP_POWER_PIN, INPUT); + } + } +} + +static bool getKeepPower() { + return keepPower; +} + +static void resetCycle(){ + tickKeepPower = tickSinceStarted; + indicatorBlinkTick = 0; + indicatorFastBlink = false; + indicatorBlinkIndex = 0; +} + +static void keepPowerRecycle(){ + //delay keep power befor turn it off + setKeepPower(true); + keepPower = false; + resetCycle(); +} + +static void setOutputPowerState(bool state){ + if (powerOutputActive != state) { + powerOutputActive = state; + //reset shutdown + criticalShutdown = false; + persistentShutdown = false; + ongoingToPowerOn = false; + //automatically keep power to mcu + + if (powerOutputActive){ + turnOnDisplay(); //automatically turn on the display + setKeepPower(true); //keep power to mcu + }else{ + keepPowerRecycle(); //prepare to turn off mcu power if no input power + } + digitalWrite(POWER_OUTPUT_PIN, powerOutputActive?HIGH:LOW); + } +} + +static void batteryChargeRoutine() { + + //charge management: + if (isChargeActive){ + //check for short + //if (chargeError == CHARGE_ERROR_NONE){ + if (inputVoltageOK && inputVoltageReady && (batteryVoltageUnfilteredbattOverVoltage) ){ + //overvoltage detection + setChargeMode(CHARGE_STOP_LIGHT_OFF);//stop the charge + batteryCheckSkipTick = 0;//reset skip check for faster act + chargeManagementMode = 0; + chargeError = CHARGE_ERROR_OVERVOLTAGE; + //Serial.println("CHARGE_ERROR_OVERVOLTAGE"); + } + //} + + if (chargeError==CHARGE_ERROR_NONE){ + + //stop the charges by mode *********************************************************************************************************** + if (!chargeAlwaysActive){ + if (chargeToFull || needFullChargeOnce){ + if (batteryVoltage>battVoltageFullChargeMinimal){ + //wait n-seconds before stop the charges + if ((chargeWaitStopTick==0) && (chargeWaitBeforeStop>0)) { + chargeWaitStopTick = tickSinceStarted; + }else{ + if (chargeWaitBeforeStop==0){ + setChargeMode(CHARGE_STOP_LIGHT_OFF);//instant stop + needFullChargeOnce = false; + }else{ + if ((tickSinceStarted-chargeWaitStopTick)>=chargeWaitBeforeStop){ + setChargeMode(CHARGE_STOP_LIGHT_OFF);//stop charge + needFullChargeOnce = false; + } + } + } + }else if (batteryVoltage>battVoltageFullChargeMaximal){ + setChargeMode(CHARGE_STOP_LIGHT_OFF); //instant stop + needFullChargeOnce = false; + } + }else{ + //wait n-seconds before stop the charges + if (batteryPercentage>=chargePercentageForStop){ + if ((chargeWaitStopTick==0) && (chargeWaitBeforeStop>0)) { + chargeWaitStopTick = tickSinceStarted; + }else{ + if (chargeWaitBeforeStop==0){ + setChargeMode(CHARGE_STOP_LIGHT_OFF);//instant stop + }else{ + if ((tickSinceStarted-chargeWaitStopTick)>=chargeWaitBeforeStop){ + setChargeMode(CHARGE_STOP_LIGHT_OFF);//stop charge + } + } + } + } + //instant stop if overshoot by +1 percent + if ( (batteryPercentage >= (chargePercentageForStop+1)) || (batteryVoltage>=battVoltageFullChargeMaximal) ){ + setChargeMode(CHARGE_STOP_LIGHT_OFF); + } + } + } + //************************************************************************************************************************************ + + //slowing the charge when reach full using charge with light off (will slightly reduce top voltage) to prolong battery life? ------- + if (isChargeActive){ + //(optional by variable) + if (chargeReduceAtBatteryFull){ + if ((batteryVoltage>battVoltageFullChargeMinimal) || ((!chargeAlwaysActive) && (batteryVoltage>=(chargePercentageForStop-0)))){ + //reduced charge (charge indicator off) + if (chargeMode != CHARGE_ACTIVE_LIGHT_OFF) setChargeMode(CHARGE_ACTIVE_LIGHT_OFF); + }else if ((chargeAlwaysActive && (batteryVoltage<=battVoltageFullRest)) || ( (!chargeAlwaysActive) && (batteryVoltage<(chargePercentageForStop-2)) ) ){ + //normal charge (charge with indicator on) + if (chargeMode != CHARGE_ACTIVE_LIGHT_ON) setChargeMode(CHARGE_ACTIVE_LIGHT_ON); + } + }else{ + //normal charge indicator (on) + if (chargeMode != CHARGE_ACTIVE_LIGHT_ON) setChargeMode(CHARGE_ACTIVE_LIGHT_ON); + } + } + //---------------------------------------------------------------------------------------------------------------------------------- + + } + + }else{ + if (chargeError==CHARGE_ERROR_NONE){ + //resume charge by mode: + //force charge after input lost >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + if (!chargeInputLost && chargeAfterInputLost){ + //if (inputUnderVoltage) { + if (!inputVoltageKeepup) { + chargeInputLost = true; + } + } + + //delayed charge logic again + if (chargeInputLost && inputVoltageKeepup){ + chargeAllowed = false; + chargeAllowedTick = tickSinceStarted; + } + + if (chargeAllowed){ + if (chargeInputLost && chargeAfterInputLost && inputVoltageKeepup){ + if (fullChargeAfterInputLost && (batteryPercentage<=triggerFullChargeAfterInputLostAtPercentage)) needFullChargeOnce = true; + //only resume charge if condition meet below upper voltage criteria + if (chargeToFull || chargeAlwaysActive || needFullChargeOnce){ + //charge with light indicator on + setChargeMode(CHARGE_ACTIVE_LIGHT_ON); + }else{ + if (batteryPercentage>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + //normal charge logic #################################################################### + if (chargeAllowed){ + if (inputVoltageKeepup){ + if (chargeAlwaysActive || needFullChargeOnce){ + setChargeMode(CHARGE_ACTIVE_LIGHT_ON); + }else{ + if (batteryPercentage<=chargePercentageForResume){ + setChargeMode(CHARGE_ACTIVE_LIGHT_ON); + } + } + } + } + //######################################################################################## + } + } +} + +static bool setPersistentShutdown(uint32_t delayNs){ + if (!persistentShutdown && powerOutputActive){ + persistentDelayShutdown = delayNs; + persistentShutdown = true; + persistentShutdownTick = tickSinceStarted; + return true; + } + return false; +} + +static uint32_t getPersistentShutdownTick(){ + if (!persistentShutdown || !powerOutputActive) return 0; + if ((tickSinceStarted-persistentShutdownTick) > persistentDelayShutdown) return 0; + return (persistentDelayShutdown - (tickSinceStarted-persistentShutdownTick)); +} + +static bool autoPowerOnEligible(){ + return (autoPowerOn && !powerOutputActive && (forceAutoPowerAtBatteryError || ( ((batteryVoltage>battVoltageForceShutdown) && (chargeError==CHARGE_ERROR_NONE)) || ((batteryVoltage>battVoltageCritical) && (chargeError==CHARGE_ERROR_OVERVOLTAGE))) ) ); +} + +static bool restartAutoPowerOn(){ + //restart automatic power on when voltage input exists + if (ongoingToPowerOn) return false; + if (startupReady && inputVoltageKeepup && inputVoltageOK && autoPowerOnEligible()){ + ongoingToPowerOn = true; + autoPowerOnTick = tickSinceStarted; + powerEverLoss = false; + return true; + } + return false; +} + +static void powerManagementRoutine(){ + //manage shutdown when battery running very low capacity (below critical) while input lost (1-2 minutes), while instant shutdown (less than 5 sec) when below force shutdown voltage + //if (inputVoltageOK){ + if (inputVoltageKeepup){ + //reset critical shutdown if exists ??????????????????????????????????????????????????????????????????? + if (powerOutputActive && (!persistentShutdown) && criticalShutdown){ + criticalShutdown = false; + criticalShutdownTick = 0; + //stop delayed mcu power detach + if ((!keepPower && keepPowerState)){ + setKeepPower(true); + turnOnDisplay(); + } + } + //????????????????????????????????????????????????????????????????????????????????????????????????????? + //automatic power on (only if battery exists and input power resume) ********************************* + if ((powerEverLoss||ongoingToPowerOn) && powerOutputActive){ + ongoingToPowerOn = false; + powerEverLoss = false; //reset + } + if (autoPowerOnEligible()){ + if (ongoingToPowerOn){ + //wait timer reaching delay + if ((tickSinceStarted-autoPowerOnTick) > autoPowerOnDelay){ + turnOnDisplay(); + ongoingToPowerOn = false; + setOutputPowerState(true); + } + }else{ + if (startupReady && powerEverLoss){ + ongoingToPowerOn = true; + autoPowerOnTick = tickSinceStarted; + powerEverLoss = false; + } + } + } + //**************************************************************************************************** + }else{ + //reset automatic power on timing if exist ****************************************** + if (ongoingToPowerOn){ + ongoingToPowerOn = false; + autoPowerOnTick = 0; + } + powerEverLoss = true; + //*********************************************************************************** + + //start power shutdown countdown when critical, or instant shutdown when voltage <= battVoltageForceShutdown + if (powerOutputActive || keepPowerState){ + if (!(criticalShutdown || persistentShutdown)){ + if (batteryVoltage>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + if (criticalShutdown || persistentShutdown){ + if (persistentShutdown){ + if (criticalShutdown) criticalShutdown = false; //clear critical shutdown flag + if ((tickSinceStarted-persistentShutdownTick) >= persistentDelayShutdown){ + persistentShutdown = false; + persistentShutdownTick = 0; + setOutputPowerState(false); + keepPowerRecycle(); + } + }else if (criticalShutdown){ + if ((tickSinceStarted-criticalShutdownTick) >= criticalDelayShutdown){ + criticalShutdown = false; + criticalShutdownTick = 0; + setOutputPowerState(false); + keepPowerRecycle(); + } + } + } + //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + +} + +static void batteryManagementRoutine() { + //only charge when input voltage threshold detected ------ + if (!inputVoltageKeepup){ + if (isChargeActive){ + setChargeMode(CHARGE_STOP_LIGHT_OFF);//stop the charge again + chargeError==CHARGE_ERROR_NONE; + chargeManagementMode = 0; + } + } + //-------------------------------------------------------- + + //skip check + if (batteryCheckSkipTick > 0) { + if (tickSinceStarted > batteryCheckSkipTick) { + batteryCheckSkipTick = 0; + //Serial.print("VBatt check: "); + //Serial.println(batteryVoltageUnfiltered); + } else { + //if (batteryCheckSkipTick); //reset when overflow 32-bit + return; + } + } + + //used for demo: + //chargeError = CHARGE_ERROR_NONE; + //isChargeActive = false; + + switch (chargeError) { + case CHARGE_ERROR_NONE: { + //if (chargeMode == CHARGE_STOP_LIGHT_OFF) { + if (!isChargeActive) { + //check battery if exists + if (chargeManagementMode == 0) { + chargeManagementMode = 1; + batteryCheckSkipTick = tickSinceStarted + 500000; //add another 0.5s + } else { + if (batteryVoltageUnfiltered <= battVoltageNotDetected) { + chargeManagementMode = 0; + chargeError = CHARGE_ERROR_NO_BATTERY; + } else { + batteryChargeRoutine(); + batteryCheckSkipTick = tickSinceStarted + 500000; //add another 0.5s + } + } + } else { + batteryChargeRoutine(); + batteryCheckSkipTick = tickSinceStarted + 200000; //add another 0.2s + } + } + break; + case CHARGE_ERROR_NO_BATTERY: { + //wait until battery detected to continue charge + //blinkChargeError = false; + blinkChargeError = true; + if (chargeManagementMode == 0) { + //try to charge in case battery dropped too much, then wait 1 sec + if (inputVoltageOK){//input voltage attached / powered + chargeManagementMode = 1; + setChargeMode(CHARGE_ACTIVE_LIGHT_ON); //enable charge + //blinkChargeError = true; + chargeBlinkTick = tickSinceStarted; + chargeStartedTick = tickSinceStarted; + }else{ + //wait for battery voltage raised again (user attach a normal battery) + if (!isChargeActive) setChargeMode(CHARGE_ACTIVE_LIGHT_ON); //enable charge + if (batteryVoltageUnfiltered>battVoltageNotDetected){ + setChargeMode(CHARGE_STOP_LIGHT_OFF); + chargeError = CHARGE_ERROR_NONE; + } + } + //batteryCheckSkipTick = tickSinceStarted + 500000; //skip 0.5 sec + } else if (chargeManagementMode == 1) { + //check if voltage raised > battVoltageNotDetected. If yes, keep for 60 sec + blinkChargeError = true; + if (tickSinceStarted - chargeStartedTick > 60000000UL) { + chargeManagementMode = 2; + chargeStartedTick = tickSinceStarted; + setChargeMode(CHARGE_STOP_LIGHT_OFF); //disable charge + } else { + if (tickSinceStarted-chargeStartedTick>2000000){ //activate after 2 sec of charging. minimal waiting time till charging module voltage raised > 3.0V + if (isChargeActive && (batteryVoltageUnfiltered2000000){ + chargeManagementMode = 3; + chargeStartedTick = tickSinceStarted; + } + } else { + //check the voltage again + chargeManagementMode = 0; + //if (batteryVoltageUnfiltered > battVoltageNotDetected) { + if (batteryVoltageUnfiltered >= battVoltageDetected) { + setChargeMode(CHARGE_STOP_LIGHT_OFF); + chargeError = CHARGE_ERROR_NONE; + chargeStartedTick = tickSinceStarted; + } + } + + //blinking on charge indicator? + if (blinkChargeError) { + //NO LONGER USED +// if (tickSinceStarted - chargeBlinkTick > 100000) { +// chargeBlinkTick = tickSinceStarted; +// if (chargeMode == CHARGE_STOP_LIGHT_OFF) { +// //do nothing +// } else if (chargeMode == CHARGE_ACTIVE_LIGHT_ON) { +// setChargeMode(CHARGE_ACTIVE_LIGHT_OFF); +// } else if (chargeMode == CHARGE_ACTIVE_LIGHT_OFF) { +// setChargeMode(CHARGE_ACTIVE_LIGHT_ON); +// } +// } + //if (chargeMode == CHARGE_STOP_LIGHT_OFF) { + // //do nothing + //} else{ + // setChargeMode(indicatorFastBlink?CHARGE_ACTIVE_LIGHT_ON:CHARGE_ACTIVE_LIGHT_OFF); + //} + } + } + break; + + case CHARGE_ERROR_SHORTED: { + bool blinkChargeError = false; + if (chargeManagementMode == 0) { + //wait 10 seconds before retry or resume charge + setChargeMode(CHARGE_STOP_LIGHT_OFF);//stop the charge + chargeManagementMode = 1; + batteryCheckSkipTick = tickSinceStarted + 10000000UL; //add another 10s + } else if (chargeManagementMode == 1) { + //retry charge again + chargeManagementMode = 2; + setChargeMode(CHARGE_ACTIVE_LIGHT_ON); + batteryCheckSkipTick = tickSinceStarted + 500000UL; //add another 0.5s + } else if (chargeManagementMode == 2) { + //check voltage again (larger than 3 Volt) and act againts error + chargeVoltageOK = batteryVoltageUnfiltered>battVoltageNotDetected; + if (batteryVoltageUnfiltered > SHORT_VOLTAGE) { + chargeManagementMode = 3; + setChargeMode(CHARGE_STOP_LIGHT_OFF);//stop the charge for a while + batteryCheckSkipTick = tickSinceStarted + 2000000UL; //add another 2s + }else{ + chargeManagementMode = 0; + setChargeMode(CHARGE_STOP_LIGHT_OFF);//stop the charge again + } + }else{ + chargeManagementMode = 0; + if (batteryVoltageUnfiltered>battVoltageNotDetected){ + chargeError = CHARGE_ERROR_NONE; + }else{ + chargeError = CHARGE_ERROR_NO_BATTERY; + } + } + } + break; + case CHARGE_ERROR_OVERVOLTAGE: { + //only happened when charging active and over-voltage protection active + //sense also when adc capped at max (>=adcMaxVoltageCapability) or ioAdc >= 1023 + //stop the charging with charge limiter indicator is on + if (chargeManagementMode == 0) { + chargeManagementMode = 1; + setChargeMode(CHARGE_STOP_LIGHT_OFF);//stop the charge + //batteryChargeRoutine(); + batteryCheckSkipTick = tickSinceStarted + 15000000UL; //add another 15s skips + }else if (chargeManagementMode == 1){ + //stop the error after battery level reached below 100% + if (batteryVoltage inputVoltageKeepupDuration){ + inputVoltageKeepup = false; + } + } + + if (inputVoltageOKPrev!=inputVoltageOK){ + inputVoltageOKPrev = inputVoltageOK; + inputVoltageReady = false; + inputVoltageResumeTick = tickSinceStarted; + } + + if (!inputVoltageOK){ + underVoltageTick = tickSinceStarted; + if (startupReady) inputUnderVoltage = true; + } + + if (inputVoltageOK){ + //2 sec for stabilize + if (!inputVoltageReady && ((tickSinceStarted-inputVoltageResumeTick) > 2000000)){ + inputVoltageReady = true; + } + //recheck again + if (inputUnderVoltage && ((tickSinceStarted-underVoltageTick)>2000000)){ + inputUnderVoltage = false; + underVoltageTick = tickSinceStarted;//reset timer + } + } + + //when inputVoltageKeepup but display not ON after system ready, it will be turned on + if (startupReady && inputVoltageKeepup && !displayActive){ + if (!onGoingDisplayOff){ + if (!splashEverDisplayed){ + displaySplashNow = true; + splashDisplayTick = tickSinceStarted; + } + displayActive = true; + } + } + + //button input >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + buttonState = !digitalRead(BUTTON_INPUT_PIN); + if (!buttonState && buttonTickIgnore>0 && tickSinceStarted>buttonTickIgnore) buttonTickIgnore = 0; //ignore button pressed, should be realeased to retrigger action + + if (buttonState && buttonTickIgnore==0){ + if (buttonTick==0) buttonTick = tickSinceStarted; + if (!buttonShortPressIgnore && (tickSinceStarted-buttonTick>1000000UL)) buttonShortPress = true; + if (!buttonLongPressIgnore && (tickSinceStarted-buttonTick>8000000UL)) buttonLongPress = true; + //manage + if (powerOutputActive){ + if (!buttonShortPressIgnore && buttonShortPress){ + buttonShortPressIgnore = true; + setOutputPowerState(false); + //keep power for 4 seconds before total power off (in case saving options and send off signal) + keepPowerRecycle(); //going complete off automatically after keep power cycle countdown 0 + if (inputVoltageKeepup){ + //keep display + //setDisplayForTurnOff(2000000); + //setDisplayForTurnOff(100000); + }else{ + setDisplayForTurnOff(100000); //turn off display after 0.1s + } + Serial.println("POWER OUT: OFF"); + } + }else{ + //power output currently off + //check battery state or power input + if (!buttonShortPressIgnore && buttonShortPress){ + buttonShortPressIgnore = true; + if (!inputUnderVoltage || (batteryVoltage>batteryVoltageEnableOn)){ + turnOnDisplay(); + if (!splashEverDisplayed){ + displaySplashNow = true; + splashDisplayTick = tickSinceStarted; + } + setKeepPower(true); + setOutputPowerState(true); + Serial.println("POWER OUT: ON"); + }else{ + //set battery level to blink, ignore button for 5 seconds + keepPowerRecycle(); + displayFlipFlag = false; + flashAllBatteryLevel = 4; //flash all battery level to give low battery power rejection + buttonTickIgnore = 3000000; //ignore input for 3 seconds + //turn off display after 1 seconds + turnOnDisplay(); + setDisplayForTurnOff(2000000); + + Serial.println("INSUFFICIENT POWER!"); + } + } + } + } + //switching + if (buttonState!=buttonStatePrev){ + buttonStatePrev = buttonState; + buttonTick = tickSinceStarted; + buttonShortPress = false; + buttonShortPressIgnore = false; + buttonLongPressIgnore = false; + buttonLongPress = false; + buttonTick = 0; + } + //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>s +} + +static void setNetworkIndicatorInTransmission(bool rx, bool tx){ + if (tx) indicatorNetworkBlinkerTX = 4; + if (rx) indicatorNetworkBlinkerRX = 4; +} + +static void setNetworkState(uint8_t state){ + if (state!=indicatorNetworkState){ + indicatorNetworkBlinkerTX = 0;//reset blink counter + indicatorNetworkBlinkerRX = 0;//reset blink counter + indicatorNetworkState = state; + } +} + +static void indicatorRoutine(){ + //manage leds indicators timing + //cycle syncer for blink flag and timing + if (tickSinceStarted-indicatorBlinkTick>=100000){ + indicatorBlinkTick = tickSinceStarted; + indicatorFastBlink = !indicatorFastBlink; + //unique blink: 2X fast blink, 1 long off + //indicatorMediumBlink = (indicatorBlinkIndex<=1) || (indicatorBlinkIndex>=3 && indicatorBlinkIndex<=4) || (indicatorBlinkIndex>=6 && indicatorBlinkIndex<=7); + //evenly blink (halved) + if (indicatorFastBlink){ + indicatorMediumBlink = !indicatorMediumBlink; + if (indicatorMediumBlink){ + indicatorSlowBlink = !indicatorSlowBlink; + } + } + indicatorBlinkIndex++; + if (indicatorBlinkIndex>=10) indicatorBlinkIndex = 0; + } +} + +static void upsRoutine(){ + //manage keep power ================================================================================================== + if ((!keepPower && keepPowerState) && ((tickSinceStarted-tickKeepPower)>HOLD_POWER_TICK)){ + setKeepPower(false); + Serial.println("POWER KEEP: OFF"); + } + //==================================================================================================================== + + //turn off display? + if (onGoingDisplayOff){ + if ((tickSinceStarted-turnOffDisplayTick)>=turnOffDisplayAfter){ + displayActive = false; + } + } + + //charge delay start + if (!chargeAllowed){ + if (tickSinceStarted-chargeAllowedTick > chargeAllowedDelay){ + chargeAllowedTick = tickSinceStarted; + chargeAllowed = true; + } + } + + //turn on display when powered by input line (even when programmer attached) + if (!onGoingDisplayOff && !displayActive){ + if (inputVoltageKeepup && startupReady){ + if (!splashEverDisplayed){ + displaySplashNow = true; + splashDisplayTick = tickSinceStarted; + } + displayActive = true; + } + } + +} + +void displayRefresh(){ + + //display program: + if (!displayActive) { + clearDisplay(); + sendDisplay(); + return; + } + + //splash: + if (displaySplashNow){ + splashEverDisplayed = true; + clearDisplay(); + displaySplash(); + sendDisplay(); + if ((tickSinceStarted-splashDisplayTick)>2000000){ + displaySplashNow = false; + } + return; + } + + //blinking battery (weak) + if (flashAllBatteryLevel>0){ + clearDisplay(); + //tick flagging----------------------------------------- + if (displayFlipFlag!=indicatorMediumBlink){ + displayFlipFlag = indicatorMediumBlink; + if (!indicatorMediumBlink) flashAllBatteryLevel--; + } + //------------------------------------------------------ + if (flashAllBatteryLevel>0){ + if (indicatorMediumBlink){ + drawBatteryBlocks(55, 4, 0); + drawBatteryCase(55, 4, 2); + } + String s = "BATTERY LOW"; + //drawString(stringCenterX(s),32, s); + drawString(stringRightSideX(s),32, s); + //drawString(stringCenterX("Please Charge!"),32, "Please Charge!"); + //drawString(10,32, "Please Charge!"); + }else{ + //turn off display + turnOffDisplay(); + } + sendDisplay(); + return; + } + + clearDisplay(); + + //draw power + if (powerOutputActive){ + if (persistentShutdown){ + if (indicatorMediumBlink) drawPower(0,0, true); + }else{ + drawPower(0,0, false); + } + } + + //power in state: + if (inputVoltageKeepup){ + drawBolt(20,0); + } + + //draw battery + if (isChargeActive) { + if (batteryVoltage>=battVoltageFullChargeMinimal){ + drawBatteryBlocks(55, 4, 4); + }else{ + if (batteryLevel>0){ + battIdx = batteryLevel-1; + }else{ + battIdx = 0; + } + if (indicatorSlowBlink){ + drawBatteryBlocks(55, 4, battIdx+1); + }else{ + drawBatteryBlocks(55, 4, battIdx); + } + } + }else{ + if (batteryVoltage1){ + //fast charging + drawCharging(88, 24, false); + } + + //battery errror state: + if (chargeError==CHARGE_ERROR_NO_BATTERY){ + if (indicatorMediumBlink) drawWarning(20,16, 1); //error + if (indicatorMediumBlink) drawString(39, 32, "NO BAT."); + }else if (chargeError==CHARGE_ERROR_SHORTED){ + if (indicatorMediumBlink) drawWarning(20,16, 2); //fatal error + if (indicatorMediumBlink) drawString(39, 32, "SHORT"); + }else if (chargeError==CHARGE_ERROR_OVERVOLTAGE){ + if (indicatorMediumBlink) drawWarning(20,16, 0); //warn + if (indicatorMediumBlink) drawString(39, 32, "OVER."); + } + + //network and tx rx indicator --------------------------- + if (indicatorNetworkState==NET_STATE_CONNECTING){ + if (indicatorMediumBlink) drawWifiAndTransmission(0, 16, indicatorNetworkSignalLevel, false, false); + }else if (indicatorNetworkState==NET_STATE_CONNECTED){ + //indicatorNetworkBlinker + drawWifiAndTransmission(0, 16, indicatorNetworkSignalLevel, indicatorNetworkBlinkerRX>0, indicatorNetworkBlinkerTX>0); + } + //tick flagging----------------------------------------- + if (displayNetworkFlipFlag!=indicatorFastBlink){ + displayNetworkFlipFlag = indicatorFastBlink; + if (indicatorNetworkBlinkerTX>0) indicatorNetworkBlinkerTX--; + if (indicatorNetworkBlinkerRX>0) indicatorNetworkBlinkerRX--; + } + //------------------------------------------------------ + + sendDisplay(); + + //displayDemoRoutine1(); //for screen demo +} + +static void upsRoutineInterrupt(){ + //if (!interruptEnable){ + //currentNs = ESP.getCycleCount(); + //tickSinceStarted += (currentNs-lastNs) * 6;//cycles * 6.25; + //lastNs = currentNs; + currentNs = millis(); + if (currentNs==lastNs) return; //skip this time + tickSinceStarted += (currentNs-lastNs) * 1000; + lastNs = currentNs; + //} + + //startup init + if (!startupReady && (tickSinceStarted>=1000000)){ + startupReady = true; //delay signal from startup + inputVoltageOK = digitalRead(MINIMUM_INPUT_PIN); + if (inputVoltageOK) inputVoltageKeepup = true; + inputUnderVoltage = !inputVoltageOK; + //attachVoltageFallIntterupt(); + } + + //adc filter handler, 16666ns = ~60 samples perseconds + if ((tickSinceStarted - ioAdcTick) > 16666) { + ioAdcTick = tickSinceStarted; + adcReadFiltered(); + inputManagementRoutine(); + upsRoutine(); + lowRateTick++; + if (lowRateTick>=6){ + lowRateTick = 0; + lowRateRoutine = true; + } + } + + //333333 ns = ~4 samples perseconds + //100000 ns = ~10 samples perseconds + //if ((tickSinceStarted - batteryCheckTick) > 100000) { + if (lowRateRoutine) { + lowRateRoutine=false; + //inputVoltageOK = digitalRead(MINIMUM_INPUT_PIN); + //batteryCheckTick = tickSinceStarted; + bool b = isUpsBatteryCritical(); //refresh critical state + indicatorRoutine(); + batteryManagementRoutine(); + powerManagementRoutine(); + + //displayRefresh + displayRefresh(); + } + + +} + +static void initUpsIo() { + //interruptEnable = enableInterrupt; + digitalWrite(POWER_OUTPUT_PIN, LOW);//disable output activation + pinMode(POWER_OUTPUT_PIN, OUTPUT); + digitalWrite(POWER_OUTPUT_PIN, LOW);//disable output activation + powerOutputActive = false; + + //voltage selection + batteryVoltageEnableOn = battVoltageCritical; + + tickSinceStarted = 0; + + chargeAllowed = false; + chargeAllowedTick = 0; + + inputUnderVoltage = false; + inputVoltageKeepup = false; + startupReady = false; + + adcSkipCounter = 0; + resetBatteryLevel();//reset battery soc + + adcReadFiltered();//read once + + initDisplay(false); //init display lib + + //keep power for defined millis + keepPower = false; + if (HOLD_POWER_TICK > 0) { + keepPowerState = true; + pinMode(KEEP_POWER_PIN, OUTPUT); + digitalWrite(KEEP_POWER_PIN, HIGH); + } else { + keepPowerState = false; + pinMode(KEEP_POWER_PIN, INPUT); + } + + pinMode(MINIMUM_INPUT_PIN, INPUT); + inputVoltageKeepup = digitalRead(MINIMUM_INPUT_PIN); + inputUnderVoltage = !inputVoltageKeepup; + + //init charge pins + pinMode(CHARGE_STOP_PIN, OUTPUT); + setChargeMode(CHARGE_STOP_LIGHT_OFF); //disable charge first + chargeError = CHARGE_ERROR_NONE;//clear error + chargeManagementMode = 0; //reset mode + if (inputVoltageKeepup){ + batteryCheckSkipTick = 2000000; //skip 200000ns (2s) + }else{ + batteryCheckSkipTick = 500000; //skip 500000ns (0.5s) + } + + pinMode(BUTTON_INPUT_PIN, INPUT); + + +} + +bool isInputLinePowered(){ + return inputVoltageKeepup; +} + +bool isUpsPoweredFromBattery(){ + return powerOutputActive && !inputVoltageKeepup; + //return (powerOutputActive || keepPowerState) && !inputVoltageKeepup; + //return !inputVoltageKeepup; +} + +bool isUpsOn(){ + return powerOutputActive; +} + +bool isUpsBatteryIndicatorChargingOrBlink(){ + if (!inputVoltageKeepup && isUpsBatteryCritical()) return true; + return batteryBlinkingIndicatorState; + //return false; +} diff --git a/dev15/upsOled.h b/dev15/upsOled.h new file mode 100644 index 0000000..f44005c --- /dev/null +++ b/dev15/upsOled.h @@ -0,0 +1,226 @@ +#include +#include +#include + +#include "bitmap.h" + +#define SCREEN_WIDTH 128 +#define SCREEN_HEIGHT 32 // 0.91 inch OLED is usually 128x32 + +// Custom Pins +#define OLED_SCL 12 // MISO = D6 = OLED_SCL +#define OLED_SDA 13 // MOSI = D7 = OLED_SDA +#define UNUSED_PIN 14 // SCK = D5 (reserved when need reset pin) +#define OLED_RESET -1 // Or 0 if display has a reset pin +U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE, /* clock=*/ OLED_SCL, /* data=*/ OLED_SDA); // pin remapping with ESP8266 HW I2C + +uint8_t displayContrast = 96; +uint8_t battLevelNum = 4; +uint8_t battBlockWidth = 15; uint8_t battBlockHeight = 14; +uint8_t battBlockGap = 3; + +//for demo +uint8_t dispDemoIdx = 0; + +void displaySplash(){ + u8g2.setContrast(255); + //u8g2.drawXBMP(0, 0, 128, 32, splash_logo_bitmap); + u8g2.drawXBMP(0, 0, 128, 32, splash_logo_bitmap_inverted); + u8g2.sendBuffer(); +} + +void drawBatteryBlocks(int8_t x, int8_t y, int8_t nFilled){ + int8_t xp = x+((battBlockWidth+battBlockGap)*battLevelNum); + xp-=battBlockGap; + int8_t yp = y; + u8g2.setDrawColor(1); + for (int8_t i=0; i2) u8g2.drawXBMP(x, y, 16, 16, wifi2_lv3_bitmap); +} + +void drawWifiAndTransmission(int8_t x, int8_t y, uint8_t level, bool inArrow, bool outArrow){ + //power 0 = not drawn + drawWifi(x, y+1, level); + drawActiveTransmission(x+11, y+12, inArrow, outArrow); +} + +void drawPower(int8_t x, int8_t y, bool timed){ + u8g2.setDrawColor(1); + if (timed){ + u8g2.drawXBMP(x, y, 16, 16, timer_16x16_bitmap); + }else{ + u8g2.drawXBMP(x, y, 16, 16, power_bitmap); + } +} + +void drawBolt(int8_t x, int8_t y){ + u8g2.setDrawColor(1); + u8g2.drawXBMP(x, y, 16, 16, bolt_16x16_bitmap); +} + +void drawCharging(int8_t x, int8_t y, bool reduced){ + u8g2.setDrawColor(1); + if (reduced){ + u8g2.drawXBMP(x, y, 7, 8, charging_7x8_bitmap); + }else{ + u8g2.drawXBMP(x, y, 7, 8, charge_fast_7x8_bitmap); + } +} + +void drawWarning(int8_t x, int8_t y, uint8_t mode){ + u8g2.setDrawColor(1); + if (mode==0){ + u8g2.drawXBMP(x, y, 16, 16, warn_16x16_bitmap); + }else if (mode==1){ + u8g2.drawXBMP(x, y, 16, 16, error_16x16_bitmap); + }else if (mode==2){ + u8g2.drawXBMP(x, y, 16, 16, fatal_16x16_bitmap); + } +} + +void initDisplay(bool showSplash){ + pinMode(UNUSED_PIN, INPUT); + u8g2.begin(); + u8g2.setContrast(displayContrast); + if (showSplash){ + displaySplash(); + }else{ + u8g2.setContrast(displayContrast); + u8g2.clearBuffer(); + u8g2.sendBuffer(); + } +} + +void clearDisplay(){ + u8g2.clearBuffer(); +} + +void sendDisplay(){ + u8g2.sendBuffer(); +} + +void drawString(int8_t x, int8_t y, String s){ + u8g2.setFont(u8g2_font_ncenB08_tr); + char char_array[32]; + s.toCharArray(char_array, s.length()+1); + char_array[s.length()] = 0; + u8g2.setDrawColor(1); + u8g2.drawStr(x, y, char_array); +} + +uint8_t stringWidth(String s){ + char char_array[32]; + s.toCharArray(char_array, s.length()+1); + char_array[s.length()] = 0; + u8g2.setFont(u8g2_font_ncenB08_tr); // choose a suitable font + return u8g2.getStrWidth(char_array); +} + +uint8_t stringCenterX(String s){ + char char_array[32]; + s.toCharArray(char_array, s.length()+1); + u8g2.setFont(u8g2_font_ncenB08_tr); // choose a suitable font + uint8_t w = u8g2.getStrWidth(char_array); + return (SCREEN_WIDTH-w)/2; +} + +uint8_t stringRightSideX(String s){ + char char_array[32]; + s.toCharArray(char_array, s.length()+1); + u8g2.setFont(u8g2_font_ncenB08_tr); // choose a suitable font + uint8_t w = u8g2.getStrWidth(char_array); + return (SCREEN_WIDTH-w); +} + +void displayDemoRoutine1(){ + u8g2.setContrast(displayContrast); + u8g2.clearBuffer(); + //drawBatteryBlock(5,5, true); + drawBatteryBlocks(55, 4, dispDemoIdx); + drawBatteryCase(55, 4, 2); + + //u8g2.setDrawColor(1); + //u8g2.setFont(u8g2_font_ncenB08_tr); // choose a suitable font + //u8g2.drawStr(100,32,"100%"); + drawBatteryPercentage(100,32,28, 25*dispDemoIdx); + + if (dispDemoIdx<=3) drawPower(0,0, dispDemoIdx>=2); + if (dispDemoIdx==1 || dispDemoIdx==3) drawBolt(20,0); + + //drawWifi(0, 16, dispDemoIdx); + //drawActiveTransmission(11,28, (dispDemoIdx==1 || dispDemoIdx==3)?true:false, (dispDemoIdx==2 || dispDemoIdx==3)?true:false); + + drawWifiAndTransmission(0, 16, dispDemoIdx, (dispDemoIdx==1 || dispDemoIdx==3)?true:false, (dispDemoIdx==2 || dispDemoIdx==3)?true:false); + + drawCharging(88, 24, dispDemoIdx==4); + + drawWarning(20,16, dispDemoIdx); + u8g2.setFont(u8g2_font_ncenB08_tr); + if (dispDemoIdx==0) u8g2.drawStr(39, 32, "OVER."); + if (dispDemoIdx==1) u8g2.drawStr(39, 32, "N0 BAT."); + if (dispDemoIdx==2) u8g2.drawStr(39, 32, "SHORT"); + + dispDemoIdx++; + if (dispDemoIdx > 4) dispDemoIdx = 0; + u8g2.sendBuffer(); +} diff --git a/dev15/upsSvr.h b/dev15/upsSvr.h new file mode 100644 index 0000000..9810b2a --- /dev/null +++ b/dev15/upsSvr.h @@ -0,0 +1,536 @@ +//todo: +// - more accurate TX/RX indication (for security concern), e.g. favicon is served static, thus not display TX/RX arrow. +// - Login and apikey access for next version + +#include "wifi.h" +#include "upsCfg.h" + +#ifndef UPSIO_H +#include "upsIo.h" +#endif + +//#include //for esp32 +#include //for esp8266 +#include + +const char* http_username = "admin"; +const char* http_password = "admin123"; + +String base64Decode(const String& input) { + const char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + String out; + int buf = 0, bits = 0; + for (size_t i = 0; i < input.length(); i++) { + char c = input[i]; + if (c == '=') break; + const char* p = strchr(b64, c); + if (!p) continue; + buf = (buf << 6) | (p - b64); + bits += 6; + if (bits >= 8) { + bits -= 8; + out += (char)((buf >> bits) & 0xFF); + } + } + return out; +} + +bool checkAuth(AsyncWebServerRequest *request) { + if (!request->hasHeader("Authorization")) return false; + String auth = request->header("Authorization"); + if (!auth.startsWith("Basic ")) return false; + String b64 = auth.substring(6); + b64.trim(); + String decoded = base64Decode(b64); + int sep = decoded.indexOf(':'); + if (sep < 0) return false; + String user = decoded.substring(0, sep); + String pass = decoded.substring(sep + 1); + return user.equals(http_username) && pass.equals(http_password); +} + +int8_t wifiStatePrev = 99; + +bool configChanged = false; + +AsyncWebServer server(80); + +void applyConfigs() { + autoPowerOn = (cfgAutoOn >= AUTO_POWER_ON); //auto power on + + //cfgChargeMode: charge mode: + switch (cfgChargeMode) { + case CHARGE_MODE_ALWAYS: + { + chargeAlwaysActive = true; + } + break; + case CHARGE_MODE_RANGE_90_100: + { + chargeAlwaysActive = false; + chargeToFull = true; + chargePercentageForStop = 100; + chargePercentageForResume = 90; + } + break; + case CHARGE_MODE_RANGE_85_100: + { + chargeAlwaysActive = false; + chargeToFull = true; + chargePercentageForStop = 100; + chargePercentageForResume = 85; + } + break; + case CHARGE_MODE_RANGE_80_100: + { + chargeAlwaysActive = false; + chargeToFull = true; + chargePercentageForStop = 100; + chargePercentageForResume = 80; + } + break; + case CHARGE_MODE_RANGE_90_95: + { + chargeAlwaysActive = false; + chargeToFull = false; + chargePercentageForStop = 95; + chargePercentageForResume = 90; + } + break; + case CHARGE_MODE_RANGE_85_95: + { + chargeAlwaysActive = false; + chargeToFull = true; + chargePercentageForStop = 95; + chargePercentageForResume = 85; + } + break; + case CHARGE_MODE_RANGE_80_95: + { + chargeAlwaysActive = false; + chargeToFull = true; + chargePercentageForStop = 95; + chargePercentageForResume = 80; + } + break; + case CHARGE_MODE_RANGE_85_90: + { + chargeAlwaysActive = false; + chargeToFull = true; + chargePercentageForStop = 90; + chargePercentageForResume = 85; + } + break; + case CHARGE_MODE_RANGE_80_90: + { + chargeAlwaysActive = false; + chargeToFull = true; + chargePercentageForStop = 90; + chargePercentageForResume = 80; + } + break; + case CHARGE_MODE_RANGE_80_85: + { + chargeAlwaysActive = false; + chargeToFull = true; + chargePercentageForStop = 85; + chargePercentageForResume = 80; + } + break; + } + + //cfgInputLostTriggerChargeMode: + switch (cfgInputLostTriggerChargeMode) { + case INPUT_LOST_TRIGGER_CHARGE_NONE: + { + chargeAfterInputLost = false; + } + break; + case INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_ALWAYS: + { + chargeAfterInputLost = true; + fullChargeAfterInputLost = true; + } + break; + case INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_95: + { + chargeAfterInputLost = true; + fullChargeAfterInputLost = false; + triggerFullChargeAfterInputLostAtPercentage = 95; + } + break; + case INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_90: + { + chargeAfterInputLost = true; + fullChargeAfterInputLost = false; + triggerFullChargeAfterInputLostAtPercentage = 90; + } + break; + case INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_85: + { + chargeAfterInputLost = true; + fullChargeAfterInputLost = false; + triggerFullChargeAfterInputLostAtPercentage = 85; + } + break; + case INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_80: + { + chargeAfterInputLost = true; + fullChargeAfterInputLost = false; + triggerFullChargeAfterInputLostAtPercentage = 80; + } + break; + } + + //cfgChargeOvervoltageProtection: + chargeOvervoltageProtection = (cfgChargeOvervoltageProtection > 0); + //cfgChargeAutomaticReducer: + chargeReduceAtBatteryFull = (cfgChargeAutomaticReducer > 0); +} + +bool getShutdownSuggestionFlag() { + if (cfgShutdownSuggestion == SHUTDOWN_SUGGESTION_DISABLED) return false; + if (!startupReady) return false; + switch (cfgShutdownSuggestion) { + case SHUTDOWN_SUGGESTION_ON_BATT: + { + return powerOutputActive && !inputVoltageKeepup; + } + break; + case SHUTDOWN_SUGGESTION_ON_BATT_75: + { + return powerOutputActive && !inputVoltageKeepup && (batteryLevel <= 3); + } + break; + case SHUTDOWN_SUGGESTION_ON_BATT_50: + { + return powerOutputActive && !inputVoltageKeepup && (batteryLevel <= 2); + } + break; + case SHUTDOWN_SUGGESTION_ON_BATT_25: + { + return powerOutputActive && !inputVoltageKeepup && (batteryLevel <= 1); + } + break; + case SHUTDOWN_SUGGESTION_ON_BATT_CRITICAL: + { + return powerOutputActive && !inputVoltageKeepup && (batteryLevel <= 1) && isUpsBatteryCritical(); + } + break; + default: + { + return false; + } + } + + return false; +} + +void handleGetState(AsyncWebServerRequest *request) { + if (!checkAuth(request)) { + setNetworkIndicatorInTransmission(true, false); + AsyncWebServerResponse *response = request->beginResponse(401, "application/json", "{\"success\":0}"); + response->addHeader("WWW-Authenticate", "Basic realm=\"UPS Login\""); + request->send(response); + setNetworkIndicatorInTransmission(true, true); + return; + } + setNetworkIndicatorInTransmission(true, false); + bool isRefresh = true; + String v = ""; + uint8_t i = 0; + + if (request->hasParam("on")) { + isRefresh = false; + v = request->getParam("on")->value(); + bool tgtState = v != "0"; + Serial.print("Switch request: "); + Serial.println(tgtState); + if (tgtState != powerOutputActive) { + setOutputPowerState(tgtState); + Serial.println("Switching..."); + } + } + + if (request->hasParam("auto")) { + isRefresh = false; + v = request->getParam("auto")->value(); + uint8_t tmpAutoOnMode = v.toInt(); + if ((tmpAutoOnMode != cfgAutoOn) && (tmpAutoOnMode <= AUTO_POWER_PERSIST)) { + cfgAutoOn = tmpAutoOnMode; + configChanged = true; + } + } + + if (request->hasParam("shutdown")) { + isRefresh = false; + v = request->getParam("shutdown")->value(); + uint16_t tSec = v.toInt(); + if (tSec > 999) tSec = 999; + uint32_t nanoT = ((uint32_t)tSec) * 1000000UL; + if (nanoT < 999999) nanoT = 999999; + bool ok = setPersistentShutdown(nanoT); + if ((cfgShutdownInSeconds != tSec && ok)) { + cfgShutdownInSeconds = tSec; + configChanged = true; + } + Serial.print("Timed Shutdown request: "); + Serial.println(tSec); + //Serial.println(ok); + } + + if (request->hasParam("shutlv")) { + isRefresh = false; + v = request->getParam("shutlv")->value(); + int16_t lv = v.toInt(); + if (lv >= SHUTDOWN_SUGGESTION_DISABLED && lv <= SHUTDOWN_SUGGESTION_ON_BATT_CRITICAL) { + if (lv != cfgShutdownSuggestion) { + cfgShutdownSuggestion = lv; + configChanged = true; + Serial.print("Shutdown level changed: "); + Serial.println(lv); + } + } else { + Serial.print("Invalid shutdown level: "); + Serial.println(v); + } + } + + //battery management params: + //cfgChargeMode (charge mode): + if (request->hasParam("cm")) { + isRefresh = false; + v = request->getParam("cm")->value(); + i = v.toInt(); + if (i <= CHARGE_MODE_RANGE_80_85) { + cfgChargeMode = i; + configChanged = true; + Serial.print("CM: "); + Serial.println(i); + } + } + + //cfgInputLostTriggerChargeMode (charge on input lost): + if (request->hasParam("coil")) { + isRefresh = false; + v = request->getParam("coil")->value(); + i = v.toInt(); + if (i <= INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_80) { + cfgInputLostTriggerChargeMode = i; + configChanged = true; + Serial.print("COIL: "); + Serial.println(i); + } + } + + //cfgChargeOvervoltageProtection (charge overvoltage protection): + if (request->hasParam("covp")) { + isRefresh = false; + v = request->getParam("covp")->value(); + i = v.toInt(); + if (i > 1) i = 1; + if (i != cfgChargeOvervoltageProtection) { + cfgChargeOvervoltageProtection = i; + configChanged = true; + Serial.print("COVP: "); + Serial.println(i); + } + } + //reset "On-Going Full Charge" triggered after input lost (reset ongoing full charge): + if (request->hasParam("rofc")) { + isRefresh = false; + v = request->getParam("rofc")->value(); + i = v.toInt(); + if (i > 0 && needFullChargeOnce) { + resetOnGoingFullChargeAfterLineLost(); + Serial.print("ROFC: "); + Serial.println(i); + } + } + + //cfgChargeAutomaticReducer (charge automatic reducer) + if (request->hasParam("car")) { + isRefresh = false; + v = request->getParam("car")->value(); + i = v.toInt(); + if (i > 1) i = 1; + if (i != cfgChargeAutomaticReducer) { + cfgChargeAutomaticReducer = i; + configChanged = true; + Serial.print("CAR: "); + Serial.println(i); + } + } + + DynamicJsonDocument jsonDoc(1536); + String jsonResponse; + //String stateOn = isUpsOn()?"1":"0"; + //String autoOn = String(cfgAutoOn); + //String voltageValue = String(batteryVoltage, 2); + //2 battery, 1 Input line or AC, 0 OFF + //String lineActive = isUpsPoweredFromBattery()?"2":isInputLinePowered()?"1":isUpsOn()?"2":"0"; + uint8_t lineActiveState = isUpsPoweredFromBattery() ? 2 : isInputLinePowered() ? 1 + : isUpsOn() ? 2 + : 0; + //String batt = String(batteryLevel); + //String battMax = "4"; + // String battBlink = IsUpsChargingOrBlink()?"1":"0"; + // String chargeLv = IsUpsChargingOrBlink()?(String(UpsBattMinimalLevelUnderChargingOrBlink())):(String(UpsBatteryLevel())); + // String battCritical = IsUpsBatteryCritical()?"1":"0"; + // String isTimedShut = timedShutdownFlag?"1":"0"; + // String onShutRemaining = String(GetShutdownCountdown()); + // String shutdownSuggest = GetShutdownSuggestionFlag()?"1":"0"; + // String shutdownSuggestMode = String(cfgShutdownSuggestion); + // String shutdownLastPeriod = String(timeShutdownLastPeriod); + + //json vars: + jsonDoc["success"] = isRefresh ? 2 : 1; + jsonDoc["on"] = powerOutputActive ? 1 : 0; + ; + jsonDoc["state"] = isUpsOn() ? 1 : 0; + jsonDoc["v"] = String(batteryVoltage, 2); + jsonDoc["batt"] = batteryLevel; + jsonDoc["battP"] = batteryPercentage; + jsonDoc["line"] = lineActiveState; + + jsonDoc["auto"] = cfgAutoOn; + jsonDoc["battBlink"] = isUpsBatteryIndicatorChargingOrBlink() ? 1 : 0; + jsonDoc["battCritical"] = isUpsBatteryCritical() ? 1 : 0; + jsonDoc["chargeLv"] = getRawBatteryLevelFromPercentage(batteryPercentage); //real level + jsonDoc["batt"] = batteryLevel; //staged level + jsonDoc["battMax"] = 4; + jsonDoc["timedShutdown"] = (persistentShutdown && powerOutputActive) ? 1 : 0; + jsonDoc["shutdownRemain"] = getPersistentShutdownTick() / 1000000; + jsonDoc["shutdownSuggestMode"] = cfgShutdownSuggestion; + jsonDoc["shutdownSuggest"] = getShutdownSuggestionFlag() ? 1 : 0; //internal flag raised if cfgShutdownSuggestion fullfill the condition + jsonDoc["shutLastTimer"] = cfgShutdownInSeconds; + //charge configuration: + jsonDoc["chgError"] = chargeError; //error state + jsonDoc["charging"] = getChargingState(); //on charging state + jsonDoc["chgMode"] = cfgChargeMode; + jsonDoc["chgOVP"] = cfgChargeOvervoltageProtection; //over voltage protection + jsonDoc["chgReducer"] = cfgChargeAutomaticReducer; + jsonDoc["chgFullTrig"] = cfgInputLostTriggerChargeMode; + jsonDoc["chgOFC"] = needFullChargeOnce ? 1 : 0; //when input lost and "Ongoing Full Charge" flag triggered + + jsonDoc["vSub"] = String(batteryVoltageUnfiltered, 2); + jsonDoc["chgSwitch"] = isChargeActive ? 1 : 0; + jsonDoc["inputUV"] = inputUnderVoltage ? 1 : 0; + jsonDoc["inputVExist"] = inputVoltageKeepup ? 1 : 0; + jsonDoc["btn"] = buttonState ? 1 : 0; + jsonDoc["btnShort"] = buttonShortPress ? 1 : 0; + jsonDoc["btnLong"] = buttonLongPress ? 1 : 0; + jsonDoc["keepPower"] = keepPowerState ? 1 : 0; + jsonDoc["display"] = displayActive ? 1 : 0; + jsonDoc["rssi"] = rssi; + jsonDoc["chargeAllowed"] = chargeAllowed ? 1 : 0; + + serializeJson(jsonDoc, jsonResponse); + request->send(200, "application/json", jsonResponse); + + setNetworkIndicatorInTransmission(true, true); +} + +void onRootRequest(AsyncWebServerRequest *request) { + setNetworkIndicatorInTransmission(true, false); + Serial.println("onRootRequest"); + bool isOK = false; + if (spiffsOk) { + if (LittleFS.exists("/index.html")) { + isOK = true; + request->send(LittleFS, "/index.html", "text/html"); + setNetworkIndicatorInTransmission(true, true); + } + } + if (!isOK) { + request->send(404, "text/text", "404: Not Found"); + setNetworkIndicatorInTransmission(true, true); + } +} + +void upsServerInit() { + + bool b = initConfigs(); + if (b) { + loadCfg(); + applyConfigs(); + } + + //transfer configuration to ups library: + if (cfgAutoOn >= AUTO_POWER_ON) { + autoPowerOn = true; + restartAutoPowerOn(); + } else { + autoPowerOn = false; + } + + //for favicon + server.serveStatic("/favicon.ico", LittleFS, "/favicon.ico"); + server.serveStatic("*/favicon.ico", LittleFS, "/favicon.ico"); + + //login endpoint: validate credentials + server.on("/login", HTTP_GET, [](AsyncWebServerRequest *request) { + setNetworkIndicatorInTransmission(true, false); + if (!checkAuth(request)) { + request->send(401, "application/json", "{\"success\":0}"); + } else { + request->send(200, "application/json", "{\"success\":1}"); + } + setNetworkIndicatorInTransmission(true, true); + }); + + //logout endpoint: clear basic auth by returning 401 + server.on("/logout", HTTP_GET, [](AsyncWebServerRequest *request) { + setNetworkIndicatorInTransmission(true, false); + AsyncWebServerResponse *response = request->beginResponse(401, "application/json", "{\"success\":0,\"message\":\"Logged out\"}"); + request->send(response); + setNetworkIndicatorInTransmission(true, true); + }); + + //for RESTapi + server.on("/state", HTTP_GET, handleGetState); + + //root + server.on("/", onRootRequest); + server.on("/*", onRootRequest); + //Redirect all not-found requests to the root + server.onNotFound(onRootRequest); + + server.begin(); + Serial.println("Server is running!"); +} + +void upsServerRoutine() { + WiFiManagerRoutine(); + + //updating wifi state: + if (wifiCurrentState != wifiStatePrev) { + wifiStatePrev = wifiCurrentState; + setNetworkState(wifiStatePrev); + } + + //update signal: + indicatorNetworkSignalLevel = wifiSignalLevel; + + //manage auto on mode: + if (cfgAutoOn == AUTO_POWER_PERSIST) { + if (inputVoltageKeepup && inputVoltageOK) { + if (restartAutoPowerOn()) Serial.println("Automatic ON restarted"); + } + } + + //if button long pressed ------------------------------------------- + if (buttonLongPressIgnore && buttonLongPress) { + buttonLongPressIgnore = true; + Serial.println("Long Pressed Button Action is Reserved!"); + //(not used) + } + //------------------------------------------------------------------ + + //saving configuration: + if (configChanged) { + configChanged = false; + saveCfg(); + applyConfigs(); + } +} diff --git a/dev15/webUI.h b/dev15/webUI.h new file mode 100644 index 0000000..94d74f0 --- /dev/null +++ b/dev15/webUI.h @@ -0,0 +1,832 @@ +#pragma once +#include + +const char index_html[] PROGMEM = R"rawliteral( + + + + + + UPS Dashboard + + + + + +
+ +
+ +

+ + + + +

+ +
+

Power Control:

+ +
+ + + +
+ + +
+ + +
+ +
+ + + +
+ +
+
+
+ + +
+ +
+ + + +
+ + + +
+
+ +
+

Charge Control:

+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + +
+ + +
+

Monitor:

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + + + +)rawliteral"; diff --git a/dev15/wifi.h b/dev15/wifi.h new file mode 100644 index 0000000..4b7e6e2 --- /dev/null +++ b/dev15/wifi.h @@ -0,0 +1,106 @@ +#include +#include + +bool useStaticIP = false; +#ifndef STASSID + #define STASSID "DWI 2319" + #define STAPSK "*65S265u" +#endif + +const char* ssid = STASSID; +const char* password = STAPSK; +String staticIP = "192.168.0.100"; +String staticGateway = "192.168.0.1"; +String staticSubnet = "255.255.255.0"; +String staticDNS = "192.168.0.1"; +String mdnsPrefix = "SMART-UPS"; //for fast access like http://smart-ups.local + +static volatile bool wifiStarted = false; +static volatile uint32_t wifiNextCheckTick = 0; +static volatile bool wifiConnected = false; +static volatile bool wifiConnecting = false; +static volatile uint8_t wifiCurrentState = 0; +static volatile bool setWifiConfig = false; + +static volatile uint8_t wifiSignalLevel = 0; //0=none, 1=low, 2=medium, 2=high + +long rssi = -999; + +void WiFiManagerRoutine() { + int8_t wifiState = WiFi.status(); + bool isWifiConnected = wifiState == WL_CONNECTED; + if (isWifiConnected) wifiConnecting = false; + if (wifiConnected != isWifiConnected){ + setWifiConfig = true; //reset config on reconnect + wifiConnected = isWifiConnected; + if (wifiConnected) wifiCurrentState = 2; //connected + } + + //wifi signal update + rssi = WiFi.RSSI(); + if (rssi <= -80){ + wifiSignalLevel = 1; + }else if (rssi > -80 && rssi <= -70){ + wifiSignalLevel = 2; + }else if (rssi > -70 && rssi <= -60){ + wifiSignalLevel = 3; + }else{ + wifiSignalLevel = 3; + } + + if (!isWifiConnected){ + if (wifiState==WL_NO_SSID_AVAIL || wifiState==WL_CONNECT_FAILED || wifiState==WL_CONNECTION_LOST) { + wifiConnecting = false; + wifiCurrentState=0; + wifiSignalLevel = 0; + } + + if (!wifiStarted || (millis() - wifiNextCheckTick) >= 30000UL){ //check each 30seconds + wifiStarted = true; + Serial.print("Connecting to "); + Serial.println(ssid); + WiFi.setOutputPower(20.5);//set to max dbm for reliability (for highest RF power output, supply current ~ 80mA) + //WiFi.setOutputPower(0);//(for lowest RF power output, supply current ~ 70mA) + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + wifiNextCheckTick = millis(); //next 30 seconds check again + wifiConnecting = true; + wifiCurrentState = 1; //connecting + } + }else{ + if (!wifiStarted){ + wifiStarted = true; + setWifiConfig = true; + } + if (setWifiConfig){ + //set connection on first time connected + setWifiConfig = false; + + WiFi.setAutoReconnect(true); + WiFi.persistent(true); + + if (useStaticIP){ + IPAddress ip, gateway, subnet, dns; + bool b = ip.fromString(staticIP); + b = gateway.fromString(staticGateway); + b = subnet.fromString(staticSubnet); + b = dns.fromString(staticDNS); + WiFi.config(ip, gateway, subnet, dns); + } + + Serial.print("Connected to "); + Serial.println(ssid); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + if (MDNS.begin(mdnsPrefix)) { + Serial.print("MDNS responder started as "); + Serial.print(mdnsPrefix); + Serial.println(".local"); + } + } + + MDNS.update(); + + } + +} diff --git a/serialmonitorlog.json b/serialmonitorlog.json new file mode 100644 index 0000000..885a477 --- /dev/null +++ b/serialmonitorlog.json @@ -0,0 +1,585 @@ +����g����+�)ł���TiI�=�����P�VBatt: 13.58 V +VBatt (Sub-Min): 13.53 V +VBatt (Min): 13.06 V (48%), LV: 2.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -54 +Charge Allowed: False +VBatt: 13.56 V +VBatt (Sub-Min): 13.53 V +VBatt (Min): 13.06 V (48%), LV: 2.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -54 +Charge Allowed: False +VBatt: 13.56 V +VBatt (Sub-Min): 13.44 V +VBatt (Min): 13.06 V (48%), LV: 2.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -58 +Charge Allowed: False +VBatt: 13.58 V +VBatt (Sub-Min): 13.44 V +VBatt (Min): 13.06 V (48%), LV: 2.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -54 +Charge Allowed: False +VBatt: 13.58 V +VBatt (Sub-Min): 13.44 V +VBatt (Min): 13.06 V (48%), LV: 2.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -59 +Charge Allowed: False +VBatt: 13.64 V +VBatt (Sub-Min): 13.50 V +VBatt (Min): 13.06 V (48%), LV: 2.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -62 +Charge Allowed: False +VBatt: 13.64 V +VBatt (Sub-Min): 13.50 V +VBatt (Min): 13.06 V (48%), LV: 2.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -62 +Charge Allowed: False +VBatt: 13.50 V +VBatt (Sub-Min): 13.50 V +VBatt (Min): 13.06 V (48%), LV: 2.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -53 +Charge Allowed: False +VBatt: 13.58 V +VBatt (Sub-Min): 13.50 V +VBatt (Min): 13.06 V (48%), LV: 2.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -60 +Charge Allowed: False +VBatt: 13.56 V +VBatt (Sub-Min): 13.52 V +VBatt (Min): 13.17 V (67%), LV: 3.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -63 +Charge Allowed: False +VBatt: 13.59 V +VBatt (Sub-Min): 13.52 V +VBatt (Min): 13.17 V (67%), LV: 3.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -63 +Charge Allowed: False +VBatt: 13.56 V +VBatt (Sub-Min): 13.53 V +VBatt (Min): 13.17 V (67%), LV: 3.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -61 +Charge Allowed: False +VBatt: 13.58 V +VBatt (Sub-Min): 13.53 V +VBatt (Min): 13.17 V (67%), LV: 3.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -63 +Charge Allowed: False +VBatt: 13.66 V +VBatt (Sub-Min): 13.53 V +VBatt (Min): 13.17 V (67%), LV: 3.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -54 +Charge Allowed: False +VBatt: 13.66 V +VBatt (Sub-Min): 13.53 V +VBatt (Min): 13.17 V (67%), LV: 3.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -60 +Charge Allowed: False +VBatt: 13.61 V +VBatt (Sub-Min): 13.53 V +VBatt (Min): 13.17 V (67%), LV: 3.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -53 +Charge Allowed: False +VBatt: 13.58 V +VBatt (Sub-Min): 13.55 V +VBatt (Min): 13.17 V (67%), LV: 3.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -59 +Charge Allowed: False +VBatt: 13.55 V +VBatt (Sub-Min): 13.55 V +VBatt (Min): 13.17 V (67%), LV: 3.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -59 +Charge Allowed: False +VBatt: 13.56 V +VBatt (Sub-Min): 13.33 V +VBatt (Min): 13.17 V (67%), LV: 3.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -61 +Charge Allowed: False +VBatt: 13.59 V +VBatt (Sub-Min): 13.33 V +VBatt (Min): 13.17 V (67%), LV: 3.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -57 +Charge Allowed: False +VBatt: 13.56 V +VBatt (Sub-Min): 13.53 V +VBatt (Min): 13.17 V (67%), LV: 3.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -61 +Charge Allowed: False +VBatt: 13.56 V +VBatt (Sub-Min): 13.53 V +VBatt (Min): 13.17 V (67%), LV: 3.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -62 +Charge Allowed: False +VBatt: 13.44 V +VBatt (Sub-Min): 13.33 V +VBatt (Min): 13.17 V (67%), LV: 3.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -56 +Charge Allowed: False +VBatt: 13.58 V +VBatt (Sub-Min): 13.33 V +VBatt (Min): 13.17 V (67%), LV: 3.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -53 +Charge Allowed: False +VBatt: 13.56 V +VBatt (Sub-Min): 13.45 V +VBatt (Min): 13.17 V (67%), LV: 3.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -53 +Charge Allowed: False +VBatt: 13.66 V +VBatt (Sub-Min): 13.45 V +VBatt (Min): 13.17 V (67%), LV: 3.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -53 +Charge Allowed: False +VBatt: 13.55 V +VBatt (Sub-Min): 13.47 V +VBatt (Min): 13.17 V (67%), LV: 3.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -53 +Charge Allowed: False +VBatt: 13.56 V +VBatt (Sub-Min): 13.47 V +VBatt (Min): 13.17 V (67%), LV: 3.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -53 +Charge Allowed: False +VBatt: 13.58 V +VBatt (Sub-Min): 13.47 V +VBatt (Min): 13.17 V (67%), LV: 3.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -54 +Charge Allowed: False +VBatt: 13.53 V +VBatt (Sub-Min): 13.55 V +VBatt (Min): 13.17 V (67%), LV: 3.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -54 +Charge Allowed: False +VBatt: 13.64 V +VBatt (Sub-Min): 13.55 V +VBatt (Min): 13.17 V (67%), LV: 3.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -54 +Charge Allowed: False +VBatt: 13.58 V +VBatt (Sub-Min): 13.47 V +VBatt (Min): 13.17 V (67%), LV: 3.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -54 +Charge Allowed: False +VBatt: 13.58 V +VBatt (Sub-Min): 13.47 V +VBatt (Min): 13.17 V (67%), LV: 3.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -54 +Charge Allowed: False +VBatt: 13.56 V +VBatt (Sub-Min): 13.55 V +VBatt (Min): 13.31 V (90%), LV: 4.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -54 +Charge Allowed: False +VBatt: 13.69 V +VBatt (Sub-Min): 13.55 V +VBatt (Min): 13.31 V (90%), LV: 4.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -54 +Charge Allowed: False +VBatt: 13.66 V +VBatt (Sub-Min): 13.52 V +VBatt (Min): 13.31 V (90%), LV: 4.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -58 +Charge Allowed: False +VBatt: 13.72 V +VBatt (Sub-Min): 13.52 V +VBatt (Min): 13.31 V (90%), LV: 4.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -58 +Charge Allowed: False +VBatt: 13.56 V +VBatt (Sub-Min): 13.33 V +VBatt (Min): 13.31 V (90%), LV: 4.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -57 +Charge Allowed: False +VBatt: 13.61 V +VBatt (Sub-Min): 13.33 V +VBatt (Min): 13.31 V (90%), LV: 4.00 +Charge Error: 0 +Charge Switch: 0 +Input Under Voltage: 0 +Input Voltage Exists: 1 +Button: False +Button (short): False +Button (long): False +Power OUT: True +KEEP POWER: True +Display: True +RSSI: -55 +Charge Allowed: False