diff --git a/Software/USER_SETTINGS.cpp b/Software/USER_SETTINGS.cpp index ea1418d3..77c5a7d9 100644 --- a/Software/USER_SETTINGS.cpp +++ b/Software/USER_SETTINGS.cpp @@ -1,9 +1,26 @@ #include "USER_SETTINGS.h" +/* This file contains all the battery settings and limits */ +/* They can be defined here, or later on in the WebUI */ + +/* Battery settings */ +volatile uint16_t BATTERY_WH_MAX = + 30000; //Battery size in Wh (Maximum value for most inverters is 65000 [65kWh], you can use larger batteries but do not set value over 65000! +volatile uint16_t MAXPERCENTAGE = + 800; //80.0% , Max percentage the battery will charge to (App will show 100% once this value is reached) +volatile uint16_t MINPERCENTAGE = + 200; //20.0% , Min percentage the battery will discharge to (App will show 0% once this value is reached) +volatile uint16_t MAXCHARGEAMP = + 300; //30.0A , BYD CAN specific setting, Max charge speed in Amp (Some inverters needs to be artificially limited) +volatile uint16_t MAXDISCHARGEAMP = + 300; //30.0A , BYD CAN specific setting, Max discharge speed in Amp (Some inverters needs to be artificially limited) + #ifdef WEBSERVER -#define ENABLE_AP //Comment out this line to turn off the broadcasted AP -const char* ssid = "REPLACE_WITH_YOUR_SSID"; // maximum of 63 characters; -const char* password = "REPLACE_WITH_YOUR_PASSWORD"; // minimum of 8 characters; -const char* ssidAP = "Battery Emulator"; // maximum of 63 characters; -const char* passwordAP = "123456789"; // minimum of 8 characters; set to NULL if you want the access point to be open +volatile uint8_t AccessPointEnabled = + true; //Set to either true or false incase you want the board to enable a direct wifi access point +const char* ssid = "REPLACE_WITH_YOUR_SSID"; // Maximum of 63 characters; +const char* password = "REPLACE_WITH_YOUR_PASSWORD"; // Minimum of 8 characters; +const char* ssidAP = "Battery Emulator"; // Maximum of 63 characters; +const char* passwordAP = "123456789"; // Minimum of 8 characters; set to NULL if you want the access point to be open +const char* versionNumber = "4.4.0"; // The current software version, shown on webserver #endif diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 7cf64c3d..ee6f809b 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -1,9 +1,11 @@ #ifndef __USER_SETTINGS_H__ #define __USER_SETTINGS_H__ +#include -/* This file contains all the user configurable settings for the Battery-Emulator software */ +/* This file contains all the battery/inverter protocol settings Battery-Emulator software */ /* To switch between batteries/inverters, uncomment a line to enable, comment out to disable. */ /* There are also some options for battery limits and extra functionality */ +/* To edit battery specific limits, see also the USER_SETTINGS.cpp file*/ /* Select battery used */ //#define BMW_I3_BATTERY @@ -26,21 +28,9 @@ //#define SOFAR_CAN //Enable this line to emulate a "Sofar Energy Storage Inverter High Voltage BMS General Protocol (Extended Frame)" over CAN bus //#define SOLAX_CAN //Enable this line to emulate a "SolaX Triple Power LFP" over CAN bus -/* Battery settings */ -#define BATTERY_WH_MAX \ - 30000 //Battery size in Wh (Maximum value for most inverters is 65000 [65kWh], you can use larger batteries but do not set value over 65000! -#define MAXPERCENTAGE \ - 800 //80.0% , Max percentage the battery will charge to (App will show 100% once this value is reached) -#define MINPERCENTAGE \ - 200 //20.0% , Min percentage the battery will discharge to (App will show 0% once this value is reached) -#define MAXCHARGEAMP \ - 300 //30.0A , BYD CAN specific setting, Max charge speed in Amp (Some inverters needs to be artificially limited) -#define MAXDISCHARGEAMP \ - 300 //30.0A , BYD CAN specific setting, Max discharge speed in Amp (Some inverters needs to be artificially limited) -//define INTERLOCK_REQUIRED //Nissan LEAF specific setting, if enabled requires both high voltage conenctors to be seated before starting - /* Other options */ #define DEBUG_VIA_USB //Enable this line to have the USB port output serial diagnostic data while program runs +//define INTERLOCK_REQUIRED //Nissan LEAF specific setting, if enabled requires both high voltage conenctors to be seated before starting //#define CONTACTOR_CONTROL //Enable this line to have pins 25,32,33 handle automatic precharge/contactor+/contactor- closing sequence //#define PWM_CONTACTOR_CONTROL //Enable this line to use PWM logic for contactors, which lower power consumption and heat generation //#define DUAL_CAN //Enable this line to activate an isolated secondary CAN Bus using add-on MCP2515 controller (Needed for FoxESS inverters) @@ -48,4 +38,11 @@ //#define SERIAL_LINK_TRANSMITTER //Enable this line to send battery data over RS485 pins to another Lilygo (This LilyGo interfaces with battery) //#define WEBSERVER //Enable this line to enable WiFi, and to run the webserver. See USER_SETTINGS.cpp for the Wifi settings. +/* Battery limits: These are set in the USER_SETTINGS.cpp file, or later on via the Webserver */ +extern volatile uint16_t BATTERY_WH_MAX; +extern volatile uint16_t MAXPERCENTAGE; +extern volatile uint16_t MINPERCENTAGE; +extern volatile uint16_t MAXCHARGEAMP; +extern volatile uint16_t MAXDISCHARGEAMP; +extern volatile uint8_t AccessPointEnabled; #endif diff --git a/Software/src/devboard/webserver/README.md b/Software/src/devboard/webserver/README.md index 4ef9c15e..4965a784 100644 --- a/Software/src/devboard/webserver/README.md +++ b/Software/src/devboard/webserver/README.md @@ -22,9 +22,6 @@ To update the software over the air: ## Future work This section lists a number of features that can be implemented as part of the webserver in the future. -- TODO: display state of charge -- TODO: display battery hardware selected -- TODO: display emulated inverter communication protocol selected - TODO: list all available ssids: scan WiFi Networks https://randomnerdtutorials.com/esp32-useful-wi-fi-functions-arduino/ - TODO: add option to add/change ssid and password and save, connect to the new ssid (Option: save ssid and password using Preferences.h library https://randomnerdtutorials.com/esp32-save-data-permanently-preferences/) - TODO: display WiFi connection strength (https://randomnerdtutorials.com/esp32-useful-wi-fi-functions-arduino/) diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 915c32b5..ec8d1dec 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -42,24 +42,91 @@ 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 + // Configure WiFi + if (AccessPointEnabled) { + 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); + } // Route for root / web page server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) { request->send_P(200, "text/html", index_html, processor); }); + // Route for going to settings web page + server.on("/settings", HTTP_GET, + [](AsyncWebServerRequest* request) { request->send_P(200, "text/html", index_html, settings_processor); }); + + // Route for editing Wh + server.on("/updateBatterySize", HTTP_GET, [](AsyncWebServerRequest* request) { + if (request->hasParam("value")) { + String value = request->getParam("value")->value(); + BATTERY_WH_MAX = value.toInt(); + request->send(200, "text/plain", "Updated successfully"); + } else { + request->send(400, "text/plain", "Bad Request"); + } + }); + + // Route for editing SOCMax + server.on("/updateSocMax", HTTP_GET, [](AsyncWebServerRequest* request) { + if (request->hasParam("value")) { + String value = request->getParam("value")->value(); + MAXPERCENTAGE = value.toInt() * 10; + request->send(200, "text/plain", "Updated successfully"); + } else { + request->send(400, "text/plain", "Bad Request"); + } + }); + + // Route for editing SOCMin + server.on("/updateSocMin", HTTP_GET, [](AsyncWebServerRequest* request) { + if (request->hasParam("value")) { + String value = request->getParam("value")->value(); + MINPERCENTAGE = value.toInt() * 10; + request->send(200, "text/plain", "Updated successfully"); + } else { + request->send(400, "text/plain", "Bad Request"); + } + }); + + // Route for editing MaxChargeA + server.on("/updateMaxChargeA", HTTP_GET, [](AsyncWebServerRequest* request) { + if (request->hasParam("value")) { + String value = request->getParam("value")->value(); + MAXCHARGEAMP = value.toInt() * 10; + request->send(200, "text/plain", "Updated successfully"); + } else { + request->send(400, "text/plain", "Bad Request"); + } + }); + + // Route for editing MaxDischargeA + server.on("/updateMaxDischargeA", HTTP_GET, [](AsyncWebServerRequest* request) { + if (request->hasParam("value")) { + String value = request->getParam("value")->value(); + MAXDISCHARGEAMP = value.toInt() * 10; + request->send(200, "text/plain", "Updated successfully"); + } else { + request->send(400, "text/plain", "Bad Request"); + } + }); + // Send a GET request to /update server.on("/debug", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(200, "text/plain", "Debug: all OK."); }); + // Route to handle reboot command + server.on("/reboot", HTTP_GET, [](AsyncWebServerRequest* request) { + request->send(200, "text/plain", "Rebooting server..."); + //TODO: Should we handle contactors gracefully? Ifdef CONTACTOR_CONTROL then what? + delay(1000); + ESP.restart(); + }); + // Initialize ElegantOTA init_ElegantOTA(); @@ -133,6 +200,9 @@ String processor(const String& var) { // Start a new block with a specific background color content += "
"; + // Show version number + content += "

Software version: " + String(versionNumber) + "

"; + // Display LED color content += "

LED color: "; switch (LEDcolor) { @@ -159,6 +229,8 @@ String processor(const String& var) { content += "

Wifi status: " + wifi_state + "

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

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

"; + // Get and display the signal strength (RSSI) + content += "

Signal Strength: " + String(WiFi.RSSI()) + " dBm

"; } // Close the block content += "
"; @@ -180,6 +252,9 @@ String processor(const String& var) { #ifdef PYLON_CAN content += "Pylontech battery over CAN bus"; #endif +#ifdef SERIAL_LINK_TRANSMITTER + content += "Serial link to another LilyGo board"; +#endif #ifdef SMA_CAN content += "BYD Battery-Box H 8.9kWh, 7 mod over CAN bus"; #endif @@ -213,6 +288,9 @@ String processor(const String& var) { #ifdef RENAULT_ZOE_BATTERY content += "Renault Zoe"; #endif +#ifdef SERIAL_LINK_RECEIVER + content += "Serial link to another LilyGo board"; +#endif #ifdef TESLA_MODEL_3_BATTERY content += "Tesla Model S/3/X/Y"; #endif @@ -302,8 +380,22 @@ String processor(const String& var) { content += ""; content += ""; + content += " "; + content += ""; + content += " "; + content += ""; content += ""; //Script for refreshing page @@ -316,16 +408,122 @@ String processor(const String& var) { return String(); } +String settings_processor(const String& var) { + if (var == "PLACEHOLDER") { + String content = ""; + //Page format + content += ""; + + // Start a new block with a specific background color + content += "
"; + + // Show current settings with edit buttons and input fields + content += "

Battery capacity: " + String(BATTERY_WH_MAX) + + " Wh

"; + content += "

SOC max percentage: " + String(MAXPERCENTAGE / 10.0, 1) + + "

"; + content += "

SOC min percentage: " + String(MINPERCENTAGE / 10.0, 1) + + "

"; + content += "

Max charge speed: " + String(MAXCHARGEAMP / 10.0, 1) + + " A

"; + content += "

Max discharge speed: " + String(MAXDISCHARGEAMP / 10.0, 1) + + " A

"; + + content += ""; + + // Close the block + content += "
"; + + content += ""; + 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 + bms_status = UPDATING; //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 + bms_status = UPDATING; //Inform inverter that we are updating LEDcolor = BLUE; // Log every 1 second if (millis() - ota_progress_millis > 1000) { @@ -341,6 +539,6 @@ void onOTAEnd(bool success) { } else { Serial.println("There was an error during OTA update!"); } - bms_status = 5; //Inform inverter that we are updating + bms_status = UPDATING; //Inform inverter that we are updating LEDcolor = BLUE; } diff --git a/Software/src/devboard/webserver/webserver.h b/Software/src/devboard/webserver/webserver.h index eb99c06b..b948d351 100644 --- a/Software/src/devboard/webserver/webserver.h +++ b/Software/src/devboard/webserver/webserver.h @@ -33,6 +33,7 @@ extern const char* ssid; extern const char* password; extern const char* ssidAP; extern const char* passwordAP; +extern const char* versionNumber; /** * @brief Initialization function for the webserver. @@ -80,6 +81,15 @@ void init_ElegantOTA(); */ String processor(const String& var); +/** + * @brief Replaces placeholder with content section in web page + * + * @param[in] var + * + * @return String + */ +String settings_processor(const String& var); + /** * @brief Executes on OTA start *