#include "webserver.h" // Create AsyncWebServer object on port 80 AsyncWebServer server(80); // Measure OTA progress unsigned long ota_progress_millis = 0; const char index_html[] PROGMEM = R"rawliteral( Battery Emulator

Battery Emulator

%PLACEHOLDER% )rawliteral"; String wifi_state; bool wifi_connected; // Wifi connect time declarations and definition unsigned long wifi_connect_start_time; unsigned long wifi_connect_current_time; const long wifi_connect_timeout = 5000; // Timeout for WiFi connect in milliseconds void init_webserver() { // Configure WiFi #ifdef ENABLE_AP WiFi.mode(WIFI_AP_STA); // Simultaneous WiFi AP and Router connection init_WiFi_AP(); init_WiFi_STA(ssid, password); #else WiFi.mode(WIFI_STA); // Only Router connection init_WiFi_STA(ssid, password); #endif // Route for root / web page server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) { request->send_P(200, "text/html", index_html, processor); }); // Send a GET request to /update server.on("/debug", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(200, "text/plain", "Debug: all OK."); }); // Initialize ElegantOTA init_ElegantOTA(); // Start server server.begin(); } void init_WiFi_AP() { Serial.print("Creating Access Point: "); Serial.println(ssidAP); Serial.print("With password: "); Serial.println(passwordAP); WiFi.softAP(ssidAP, passwordAP); IPAddress IP = WiFi.softAPIP(); Serial.println("Access Point created."); Serial.print("IP address: "); Serial.println(IP); } void init_WiFi_STA(const char* ssid, const char* password) { // Connect to Wi-Fi network with SSID and password Serial.print("Connecting to "); Serial.println(ssid); WiFi.begin(ssid, password); wifi_connect_start_time = millis(); wifi_connect_current_time = wifi_connect_start_time; while ((wifi_connect_current_time - wifi_connect_start_time) <= wifi_connect_timeout && WiFi.status() != WL_CONNECTED) { // do this loop for up to 5000ms // to break the loop when the connection is not established (wrong ssid or password). delay(500); Serial.print("."); wifi_connect_current_time = millis(); } if (WiFi.status() == WL_CONNECTED) { // WL_CONNECTED is assigned when connected to a WiFi network wifi_connected = true; wifi_state = "Connected"; // Print local IP address and start web server Serial.println(""); Serial.print("Connected to WiFi network: "); Serial.println(ssid); Serial.print("IP address: "); Serial.println(WiFi.localIP()); } else { wifi_connected = false; 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."); } } void init_ElegantOTA() { ElegantOTA.begin(&server); // Start ElegantOTA // ElegantOTA callbacks ElegantOTA.onStart(onOTAStart); ElegantOTA.onProgress(onOTAProgress); ElegantOTA.onEnd(onOTAEnd); } String processor(const String& var) { if (var == "PLACEHOLDER") { String content = ""; //Page format content += ""; // Start a new block with a specific background color content += "
"; // Display LED color content += "

LED color: "; switch (LEDcolor) { case GREEN: content += "GREEN

"; break; case YELLOW: content += "YELLOW"; break; case BLUE: content += "BLUE"; break; case RED: content += "RED"; break; case TEST_ALL_COLORS: 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 += "

Wifi status: " + wifi_state + "

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

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

"; } // 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_KANGOO_BATTERY content += "Renault Kangoo"; #endif #ifdef RENAULT_ZOE_BATTERY content += "Renault Zoe"; #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. 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 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 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 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 tempMaxFloat = static_cast(-(65536 - 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(-(65536 - 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 } 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(tempMaxFloat, 1) + " C

"; content += "

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

"; 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

"; } // Close the block content += "
"; content += ""; content += ""; //Script for refreshing page content += ""; return content; } return String(); } void onOTAStart() { // Log when OTA has started Serial.println("OTA update started!"); ESP32Can.CANStop(); bms_status = 5; //Inform inverter that we are updating LEDcolor = BLUE; } void onOTAProgress(size_t current, size_t final) { bms_status = 5; //Inform inverter that we are updating LEDcolor = BLUE; // Log every 1 second if (millis() - ota_progress_millis > 1000) { ota_progress_millis = millis(); Serial.printf("OTA Progress Current: %u bytes, Final: %u bytes\n", current, final); } } void onOTAEnd(bool success) { // Log when OTA has finished if (success) { Serial.println("OTA update finished successfully!"); } else { Serial.println("There was an error during OTA update!"); } bms_status = 5; //Inform inverter that we are updating LEDcolor = BLUE; }