From c121f774d54688f98611f9170770027b8ef6b283 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 29 Dec 2023 18:29:54 +0200 Subject: [PATCH 1/4] Add battery statistics to webserver --- Software/src/devboard/webserver/webserver.cpp | 72 ++++++++++++++++++- Software/src/devboard/webserver/webserver.h | 19 ++++- 2 files changed, 87 insertions(+), 4 deletions(-) diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 3573aee2..b3c6d359 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -9,9 +9,9 @@ unsigned long ota_progress_millis = 0; const char index_html[] PROGMEM = R"rawliteral( - Battery Emulator Web Server + Battery Emulator - + -

Battery Emulator Web Server

+

Battery Emulator

%PLACEHOLDER% @@ -120,6 +120,11 @@ void init_ElegantOTA() { String processor(const String& var) { if (var == "PLACEHOLDER") { String content = ""; + //Page format + content += ""; + // Display LED color content += "

LED color: "; switch (LEDcolor) { @@ -147,6 +152,67 @@ String processor(const String& var) { if (wifi_connected == true) { content += "

IP: " + WiFi.localIP().toString() + "

"; } + // Assuming SOC is an integer + float socFloat = static_cast(SOC) / 100.0; // Convert to float and divide by 100 + float sohFloat = static_cast(StateOfHealth) / 100.0; // Convert to float and divide by 100 + float voltageFloat = static_cast(battery_voltage) / 10.0; // Convert to float and divide by 10 + float tempMaxFloat = static_cast(temperature_max) / 10.0; // Convert to float and divide by 10 + float tempMinFloat = static_cast(temperature_min) / 10.0; // Convert to float and divide by 10 + char socString[10]; + char sohString[10]; + char voltageString[10]; + char tempMaxString[10]; + char tempMinString[10]; + + // Format decimals + dtostrf(socFloat, 6, 2, socString); + dtostrf(sohFloat, 6, 2, sohString); + dtostrf(voltageFloat, 6, 1, voltageString); + dtostrf(tempMaxFloat, 6, 1, tempMaxString); + dtostrf(tempMinFloat, 6, 1, tempMinString); + + //Display battery statistics + content += "

SOC: " + String(socString) +"

"; + content += "

SOH: " + String(sohString) +"

"; + content += "

Voltage: " + String(voltageString) + " V

"; + content += "

Current: " + String(battery_current) + " A

"; + content += "

Power: " + String(stat_batt_power) + " W

"; + content += "

Total capacity: " + String(capacity_Wh) + " Wh

"; + content += "

Remaining capacity: " + String(remaining_capacity_Wh) + " Wh

"; + content += "

Max discharge power: " + String(max_target_discharge_power) + " W

"; + content += "

Max charge power: " + String(max_target_charge_power) + " W

"; + content += "

Cell max: " + String(cell_max_voltage) + " mV

"; + content += "

Cell min: " + String(cell_min_voltage) + " mV

"; + content += "

Temperature max: " + String(tempMaxString) + " C

"; + content += "

Temperature min: " + String(tempMinString) + " C

"; + content += "

BMS Status: " + String(bms_status) + " -

"; + if(bms_status == 3){ + content += "

BMS Status: OK

"; + } + else{ + content += "

BMS Status: FAULT

"; + } + if(bms_char_dis_status == 2){ + content += "

Battery charging!

"; + } + else if (bms_char_dis_status == 1){ + content += "

Battery discharging!

"; + } + else{ //0 idle + content += "

Battery idle

"; + } + + content += ""; + content += ""; + + + //Script for refreshing page + content += ""; + return content; } return String(); diff --git a/Software/src/devboard/webserver/webserver.h b/Software/src/devboard/webserver/webserver.h index f599cea9..e5e485b1 100644 --- a/Software/src/devboard/webserver/webserver.h +++ b/Software/src/devboard/webserver/webserver.h @@ -9,7 +9,24 @@ #include "../../lib/me-no-dev-ESPAsyncWebServer/src/ESPAsyncWebServer.h" #include "../config.h" // Needed for LED defines -extern uint8_t LEDcolor; // Enum, 0-10 +extern uint16_t SOC; //SOC%, 0-100.00 (0-10000) +extern uint16_t StateOfHealth; //SOH%, 0-100.00 (0-10000) +extern uint16_t battery_voltage; //V+1, 0-500.0 (0-5000) +extern uint16_t battery_current; //A+1, Goes thru convert2unsignedint16 function (5.0A = 50, -5.0A = 65485) +extern uint16_t capacity_Wh; //Wh, 0-60000 +extern uint16_t remaining_capacity_Wh; //Wh, 0-60000 +extern uint16_t max_target_discharge_power; //W, 0-60000 +extern uint16_t max_target_charge_power; //W, 0-60000 +extern uint16_t bms_status; //Enum, 0-5 +extern uint16_t bms_char_dis_status; //Enum, 0-2 +extern uint16_t stat_batt_power; //W, Goes thru convert2unsignedint16 function (5W = 5, -5W = 65530) +extern uint16_t temperature_min; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385) +extern uint16_t temperature_max; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385) +extern uint16_t cell_max_voltage; //mV, 0-4350 +extern uint16_t cell_min_voltage; //mV, 0-4350 +extern uint8_t LEDcolor; //Enum, 0-10 +extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false + extern const char* ssid; extern const char* password; extern const char* ssidAP; From 81df81acefdf95d39f8443da3ae880551157fe33 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 29 Dec 2023 20:03:07 +0200 Subject: [PATCH 2/4] Finte tune UI --- Software/src/devboard/webserver/webserver.cpp | 118 +++++++++++++----- 1 file changed, 90 insertions(+), 28 deletions(-) diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index b3c6d359..cd731c7f 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -93,7 +93,7 @@ void init_WiFi_STA(const char* ssid, const char* password) { } if (WiFi.status() == WL_CONNECTED) { // WL_CONNECTED is assigned when connected to a WiFi network wifi_connected = true; - wifi_state = "connected"; + wifi_state = "Connected"; // Print local IP address and start web server Serial.println(""); Serial.print("Connected to WiFi network: "); @@ -102,7 +102,7 @@ void init_WiFi_STA(const char* ssid, const char* password) { Serial.println(WiFi.localIP()); } else { wifi_connected = false; - wifi_state = "not connected"; + wifi_state = "Not connected"; Serial.print("Not connected to WiFi network: "); Serial.println(ssid); Serial.println("Please check WiFi network name and password, and if WiFi network is available."); @@ -125,6 +125,9 @@ String processor(const String& var) { content += "body { background-color: black; color: white; }"; content += ""; + // Start a new block with a specific background color + content += "
"; + // Display LED color content += "

LED color: "; switch (LEDcolor) { @@ -141,23 +144,86 @@ String processor(const String& var) { content += "RED

"; break; case TEST_ALL_COLORS: - content += "RAINBOW"; + content += "RGB Testing loop"; break; default: break; } // Display ssid of network connected to and, if connected to the WiFi, its own IP content += "

SSID: " + String(ssid) + "

"; - content += "

status: " + wifi_state + "

"; + content += "

Wifi status: " + wifi_state + "

"; if (wifi_connected == true) { content += "

IP: " + WiFi.localIP().toString() + "

"; } - // Assuming SOC is an integer - float socFloat = static_cast(SOC) / 100.0; // Convert to float and divide by 100 - float sohFloat = static_cast(StateOfHealth) / 100.0; // Convert to float and divide by 100 - float voltageFloat = static_cast(battery_voltage) / 10.0; // Convert to float and divide by 10 - float tempMaxFloat = static_cast(temperature_max) / 10.0; // Convert to float and divide by 10 - float tempMinFloat = static_cast(temperature_min) / 10.0; // Convert to float and divide by 10 + // Close the block + content += "
"; + + // Start a new block with a specific background color + content += "
"; + + // Display which components are used + content += "

Inverter protocol: "; +#ifdef BYD_CAN + content += "BYD Battery-Box Premium HVS over CAN Bus"; +#endif +#ifdef BYD_MODBUS + content += "BYD 11kWh HVM battery over Modbus RTU"; +#endif +#ifdef LUNA2000_MODBUS + content += "Luna2000 battery over Modbus RTU"; +#endif +#ifdef PYLON_CAN + content += "Pylontech battery over CAN bus"; +#endif +#ifdef SMA_CAN + content += "BYD Battery-Box H 8.9kWh, 7 mod over CAN bus"; +#endif +#ifdef SOFAR_CAN + content += "Sofar Energy Storage Inverter High Voltage BMS General Protocol (Extended Frame) over CAN bus"; +#endif +#ifdef SOLAX_CAN + content += "SolaX Triple Power LFP over CAN bus"; +#endif + content += "

"; + + content += "

Battery protocol: "; +#ifdef BMW_I3_BATTERY + content += "BMW i3"; +#endif +#ifdef CHADEMO_BATTERY + content += "Chademo V2X mode"; +#endif +#ifdef IMIEV_CZERO_ION_BATTERY + content += "I-Miev / C-Zero / Ion Triplet"; +#endif +#ifdef KIA_HYUNDAI_64_BATTERY + content += "Kia/Hyundai 64kWh"; +#endif +#ifdef NISSAN_LEAF_BATTERY + content += "Nissan LEAF"; +#endif +#ifdef RENAULT_ZOE_BATTERY + content += "Renault Zoe / Kangoo"; +#endif +#ifdef TESLA_MODEL_3_BATTERY + content += "Tesla Model S/3/X/Y"; +#endif +#ifdef TEST_FAKE_BATTERY + content += "Fake battery for testing purposes"; +#endif + content += "

"; + // Close the block + content += "
"; + + // Start a new block with a specific background color + content += "
"; + + // Display battery statistics within this block + float socFloat = static_cast(SOC) / 100.0; // Convert to float and divide by 100 + float sohFloat = static_cast(StateOfHealth) / 100.0; // Convert to float and divide by 100 + float voltageFloat = static_cast(battery_voltage) / 10.0; // Convert to float and divide by 10 + float tempMaxFloat = static_cast(temperature_max) / 10.0; // Convert to float and divide by 10 + float tempMinFloat = static_cast(temperature_min) / 10.0; // Convert to float and divide by 10 char socString[10]; char sohString[10]; char voltageString[10]; @@ -171,12 +237,11 @@ String processor(const String& var) { dtostrf(tempMaxFloat, 6, 1, tempMaxString); dtostrf(tempMinFloat, 6, 1, tempMinString); - //Display battery statistics - content += "

SOC: " + String(socString) +"

"; - content += "

SOH: " + String(sohString) +"

"; - content += "

Voltage: " + String(voltageString) + " V

"; - content += "

Current: " + String(battery_current) + " A

"; - content += "

Power: " + String(stat_batt_power) + " W

"; + content += "

SOC: " + String(socString) + "

"; + content += "

SOH: " + String(sohString) + "

"; + content += "

Voltage: " + String(voltageString) + " V

"; + content += "

Current: " + String(battery_current) + " A

"; + content += "

Power: " + String(stat_batt_power) + " W

"; content += "

Total capacity: " + String(capacity_Wh) + " Wh

"; content += "

Remaining capacity: " + String(remaining_capacity_Wh) + " Wh

"; content += "

Max discharge power: " + String(max_target_discharge_power) + " W

"; @@ -185,32 +250,29 @@ String processor(const String& var) { content += "

Cell min: " + String(cell_min_voltage) + " mV

"; content += "

Temperature max: " + String(tempMaxString) + " C

"; content += "

Temperature min: " + String(tempMinString) + " C

"; - content += "

BMS Status: " + String(bms_status) + " -

"; - if(bms_status == 3){ + if (bms_status == 3) { content += "

BMS Status: OK

"; - } - else{ + } else { content += "

BMS Status: FAULT

"; } - if(bms_char_dis_status == 2){ + if (bms_char_dis_status == 2) { content += "

Battery charging!

"; - } - else if (bms_char_dis_status == 1){ - content += "

Battery discharging!

"; - } - else{ //0 idle + } else if (bms_char_dis_status == 1) { + content += "

Battery discharging!

"; + } else { //0 idle content += "

Battery idle

"; } + // Close the block + content += "
"; content += ""; content += ""; - //Script for refreshing page content += ""; return content; From 5ba2a20f4e671acf47fc9d07e732b0efa1d6b605 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 30 Dec 2023 10:45:53 +0200 Subject: [PATCH 3/4] Fix negative value handling --- Software/src/devboard/webserver/webserver.cpp | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index cd731c7f..dcc79da6 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -126,7 +126,7 @@ String processor(const String& var) { content += ""; // Start a new block with a specific background color - content += "
"; + content += "
"; // Display LED color content += "

LED color: "; @@ -159,7 +159,7 @@ String processor(const String& var) { content += "

"; // Start a new block with a specific background color - content += "
"; + content += "
"; // Display which components are used content += "

Inverter protocol: "; @@ -216,16 +216,35 @@ String processor(const String& var) { content += "

"; // Start a new block with a specific background color - content += "
"; + content += "
"; // Display battery statistics within this block float socFloat = static_cast(SOC) / 100.0; // Convert to float and divide by 100 float sohFloat = static_cast(StateOfHealth) / 100.0; // Convert to float and divide by 100 float voltageFloat = static_cast(battery_voltage) / 10.0; // Convert to float and divide by 10 - float tempMaxFloat = static_cast(temperature_max) / 10.0; // Convert to float and divide by 10 - float tempMinFloat = static_cast(temperature_min) / 10.0; // Convert to float and divide by 10 + float currentFloat = 0; + if (battery_current > 32767) { //Handle negative values on this unsigned value + currentFloat = static_cast(-(65535 - battery_current)) / 10.0; // Convert to float and divide by 10 + } else { + currentFloat = static_cast(battery_current) / 10.0; // Convert to float and divide by 10 + } + + float tempMaxFloat = 0; + float tempMinFloat = 0; + if (temperature_max > 32767) { //Handle negative values on this unsigned value + tempMaxFloat = static_cast(-(65535 - temperature_max)) / 10.0; // Convert to float and divide by 10 + } else { + tempMaxFloat = static_cast(temperature_max) / 10.0; // Convert to float and divide by 10 + } + if (temperature_min > 32767) { //Handle negative values on this unsigned value + tempMinFloat = static_cast(-(65535 - temperature_min)) / 10.0; // Convert to float and divide by 10 + } else { + tempMinFloat = static_cast(temperature_min) / 10.0; // Convert to float and divide by 10 + } + char socString[10]; char sohString[10]; + char currentString[10]; char voltageString[10]; char tempMaxString[10]; char tempMinString[10]; @@ -234,13 +253,14 @@ String processor(const String& var) { dtostrf(socFloat, 6, 2, socString); dtostrf(sohFloat, 6, 2, sohString); dtostrf(voltageFloat, 6, 1, voltageString); + dtostrf(currentFloat, 6, 1, currentString); dtostrf(tempMaxFloat, 6, 1, tempMaxString); dtostrf(tempMinFloat, 6, 1, tempMinString); content += "

SOC: " + String(socString) + "

"; content += "

SOH: " + String(sohString) + "

"; content += "

Voltage: " + String(voltageString) + " V

"; - content += "

Current: " + String(battery_current) + " A

"; + content += "

Current: " + String(currentString) + " A

"; content += "

Power: " + String(stat_batt_power) + " W

"; content += "

Total capacity: " + String(capacity_Wh) + " Wh

"; content += "

Remaining capacity: " + String(remaining_capacity_Wh) + " Wh

"; From e9aa9652baf860a0595d9f9d7ad2ae90144c5dd6 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 31 Dec 2023 17:07:03 +0200 Subject: [PATCH 4/4] Add color. Make memory safe --- Software/Software.ino | 1 + Software/src/devboard/webserver/webserver.cpp | 62 +++++++++++-------- Software/src/devboard/webserver/webserver.h | 3 +- 3 files changed, 38 insertions(+), 28 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index 0a139cba..bea32ea4 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -490,6 +490,7 @@ void handle_LED_state() { // BMS in fault state overrides everything if (bms_status == FAULT) { + LEDcolor = RED; pixels.setPixelColor(0, pixels.Color(255, 0, 0)); // Red LED full brightness } diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index dcc79da6..be806778 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -159,7 +159,7 @@ String processor(const String& var) { content += "
"; // Start a new block with a specific background color - content += "
"; + content += "
"; // Display which components are used content += "

Inverter protocol: "; @@ -215,8 +215,27 @@ String processor(const String& var) { // Close the block content += "

"; - // Start a new block with a specific background color - content += "
"; + // Start a new block with a specific background color. Color changes depending on BMS status + switch (LEDcolor) { + case GREEN: + content += "
"; + break; + case YELLOW: + content += "
"; + break; + case BLUE: + content += "
"; + break; + case RED: + content += "
"; + break; + case TEST_ALL_COLORS: //Blue in test mode + content += "
"; + break; + default: //Some new color, make background green + content += "
"; + break; + } // Display battery statistics within this block float socFloat = static_cast(SOC) / 100.0; // Convert to float and divide by 100 @@ -228,7 +247,12 @@ String processor(const String& var) { } else { currentFloat = static_cast(battery_current) / 10.0; // Convert to float and divide by 10 } - + float powerFloat = 0; + if (stat_batt_power > 32767) { //Handle negative values on this unsigned value + powerFloat = static_cast(-(65535 - stat_batt_power)); + } else { + powerFloat = static_cast(stat_batt_power); + } float tempMaxFloat = 0; float tempMinFloat = 0; if (temperature_max > 32767) { //Handle negative values on this unsigned value @@ -241,35 +265,19 @@ String processor(const String& var) { } else { tempMinFloat = static_cast(temperature_min) / 10.0; // Convert to float and divide by 10 } - - char socString[10]; - char sohString[10]; - char currentString[10]; - char voltageString[10]; - char tempMaxString[10]; - char tempMinString[10]; - - // Format decimals - dtostrf(socFloat, 6, 2, socString); - dtostrf(sohFloat, 6, 2, sohString); - dtostrf(voltageFloat, 6, 1, voltageString); - dtostrf(currentFloat, 6, 1, currentString); - dtostrf(tempMaxFloat, 6, 1, tempMaxString); - dtostrf(tempMinFloat, 6, 1, tempMinString); - - content += "

SOC: " + String(socString) + "

"; - content += "

SOH: " + String(sohString) + "

"; - content += "

Voltage: " + String(voltageString) + " V

"; - content += "

Current: " + String(currentString) + " A

"; - content += "

Power: " + String(stat_batt_power) + " W

"; + content += "

SOC: " + String(socFloat, 2) + "

"; + content += "

SOH: " + String(sohFloat, 2) + "

"; + content += "

Voltage: " + String(voltageFloat, 1) + " V

"; + content += "

Current: " + String(currentFloat, 1) + " A

"; + content += "

Power: " + String(powerFloat, 0) + " W

"; content += "

Total capacity: " + String(capacity_Wh) + " Wh

"; content += "

Remaining capacity: " + String(remaining_capacity_Wh) + " Wh

"; content += "

Max discharge power: " + String(max_target_discharge_power) + " W

"; content += "

Max charge power: " + String(max_target_charge_power) + " W

"; content += "

Cell max: " + String(cell_max_voltage) + " mV

"; content += "

Cell min: " + String(cell_min_voltage) + " mV

"; - content += "

Temperature max: " + String(tempMaxString) + " C

"; - content += "

Temperature min: " + String(tempMinString) + " C

"; + content += "

Temperature max: " + String(tempMaxFloat, 1) + " C

"; + content += "

Temperature min: " + String(tempMinFloat, 1) + " C

"; if (bms_status == 3) { content += "

BMS Status: OK

"; } else { diff --git a/Software/src/devboard/webserver/webserver.h b/Software/src/devboard/webserver/webserver.h index e5e485b1..e5e736d4 100644 --- a/Software/src/devboard/webserver/webserver.h +++ b/Software/src/devboard/webserver/webserver.h @@ -25,7 +25,8 @@ extern uint16_t temperature_max; //C+1, Goes thru convert2unsignedint16 funct extern uint16_t cell_max_voltage; //mV, 0-4350 extern uint16_t cell_min_voltage; //mV, 0-4350 extern uint8_t LEDcolor; //Enum, 0-10 -extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false +extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false +extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false extern const char* ssid; extern const char* password;