Split webserver into separate files

This commit is contained in:
Daniel 2024-02-10 23:34:25 +02:00
parent 581ca5d705
commit 0b0f66c94a
11 changed files with 438 additions and 410 deletions

View file

@ -16,11 +16,6 @@ volatile uint16_t MAXDISCHARGEAMP =
300; //30.0A , BYD CAN specific setting, Max discharge speed in Amp (Some inverters needs to be artificially limited)
/* Charger settings (Optional, when generator charging) */
// MQTT
#ifdef MQTT
const char* mqtt_user = "REDACTED";
const char* mqtt_password = "REDACTED";
#endif // USE_MQTT
/* Charger settings */
volatile float CHARGER_SET_HV = 384; // Reasonably appropriate 4.0v per cell charging of a 96s pack
volatile float CHARGER_MAX_HV = 420; // Max permissible output (VDC) of charger
@ -37,4 +32,11 @@ 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 uint8_t wifi_channel = 0; // set to 0 for automatic channel selection
// MQTT
#ifdef MQTT
const char* mqtt_user = "REDACTED";
const char* mqtt_password = "REDACTED";
#endif // USE_MQTT
#endif

View file

@ -37,7 +37,7 @@
//#define SERIAL_LINK_RECEIVER //Enable this line to receive battery data over RS485 pins from another Lilygo (This LilyGo interfaces with inverter)
//#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.
//#define LOAD_SAVED_SETTINGS_ON_BOOT //Enable this line to read settings stored via the webserver on boot
#define LOAD_SAVED_SETTINGS_ON_BOOT //Enable this line to read settings stored via the webserver on boot
/* MQTT options */
//#define MQTT // Enable this line to enable MQTT
@ -47,7 +47,6 @@
#define MQTT_PORT 1883
/* Event options*/
#define EVENTLOGGING //Enable this line to log events to the event log
#define DUMMY_EVENT_ENABLED false //Enable this line to have a dummy event that gets logged to test the interface
/* Select charger used (Optional) */

View file

@ -0,0 +1,56 @@
#include "cellmonitor_html.h"
#include <Arduino.h>
String cellmonitor_processor(const String& var) {
if (var == "PLACEHOLDER") {
String content = "";
// Page format
content += "<style>";
content += "body { background-color: black; color: white; }";
content += ".container { display: flex; flex-wrap: wrap; justify-content: space-around; }";
content += ".cell { width: 48%; margin: 1%; padding: 10px; border: 1px solid white; text-align: center; }";
content += ".low-voltage { color: red; }"; // Style for low voltage text
content += ".voltage-values { margin-bottom: 10px; }"; // Style for voltage values section
content += "</style>";
// Start a new block with a specific background color
content += "<div style='background-color: #303E47; padding: 10px; margin-bottom: 10px; border-radius: 50px'>";
// Display max, min, and deviation voltage values
content += "<div class='voltage-values'>";
content += "Max Voltage: " + String(cell_max_voltage) + " mV<br>";
content += "Min Voltage: " + String(cell_min_voltage) + " mV<br>";
int deviation = cell_max_voltage - cell_min_voltage;
content += "Voltage Deviation: " + String(deviation) + " mV";
content += "</div>";
// Visualize the populated cells in forward order using flexbox with conditional text color
content += "<div class='container'>";
for (int i = 0; i < 120; ++i) {
// Skip empty values
if (cellvoltages[i] == 0) {
continue;
}
String cellContent = "Cell " + String(i + 1) + "<br>" + String(cellvoltages[i]) + " mV";
// Check if the cell voltage is below 3000, apply red color
if (cellvoltages[i] < 3000) {
cellContent = "<span class='low-voltage'>" + cellContent + "</span>";
}
content += "<div class='cell'>" + cellContent + "</div>";
}
content += "</div>";
// Close the block
content += "</div>";
content += "<button onclick='goToMainPage()'>Back to main page</button>";
content += "<script>";
content += "function goToMainPage() { window.location.href = '/'; }";
content += "</script>";
return content;
}
return String();
}

View file

@ -0,0 +1,17 @@
#ifndef CELLMONITOR_H
#define CELLMONITOR_H
extern uint16_t cell_max_voltage; //mV, 0-4350
extern uint16_t cell_min_voltage; //mV, 0-4350
extern uint16_t cellvoltages[120]; //mV 0-4350 per cell
/**
* @brief Replaces placeholder with content section in web page
*
* @param[in] var
*
* @return String
*/
String cellmonitor_processor(const String& var);
#endif

View file

@ -0,0 +1,53 @@
#include "events_html.h"
#include <Arduino.h>
const char EVENTS_HTML_START[] = R"=====(
<style>
body { background-color: black; color: white; }
.event-log { display: flex; flex-direction: column; }
.event { display: flex; flex-wrap: wrap; border: 1px solid white; padding: 10px; }
.event > div { flex: 1; min-width: 100px; max-width: 90%; word-break: break-word; }
</style>
<div style='background-color: #303E47; padding: 10px; margin-bottom: 10px;border-radius: 50px'>
<h4 style='color: white;'>Event log:</h4>
<div class="event-log">
<div class="event">
<div>Event Type</div><div>LED Color</div><div>Last Event (seconds ago)</div><div>Count</div><div>Data</div><div>Message</div>
</div>
)=====";
const char EVENTS_HTML_END[] = R"=====(
</div></div>
<button onclick='goToMainPage()'>Back to main page</button>
<script>
function goToMainPage() {
window.location.href = '/';
}
</script>
)=====";
String events_processor(const String& var) {
if (var == "PLACEHOLDER") {
String content = "";
content.reserve(5000);
// Page format
content.concat(FPSTR(EVENTS_HTML_START));
for (int i = 0; i < EVENT_NOF_EVENTS; i++) {
Serial.println("Event: " + String(get_event_enum_string(static_cast<EVENTS_ENUM_TYPE>(i))) +
" count: " + String(entries[i].occurences) + " seconds: " + String(entries[i].timestamp) +
" data: " + String(entries[i].data));
if (entries[i].occurences > 0) {
content.concat("<div class='event'>");
content.concat("<div>" + String(get_event_enum_string(static_cast<EVENTS_ENUM_TYPE>(i))) + "</div>");
content.concat("<div>" + String(get_led_color_display_text(entries[i].led_color)) + "</div>");
content.concat("<div>" + String((millis() / 1000) - entries[i].timestamp) + "</div>");
content.concat("<div>" + String(entries[i].occurences) + "</div>");
content.concat("<div>" + String(entries[i].data) + "</div>");
content.concat("<div>" + String(get_event_message(static_cast<EVENTS_ENUM_TYPE>(i))) + "</div>");
content.concat("</div>"); // End of event row
}
}
content.concat(FPSTR(EVENTS_HTML_END));
return content;
}
return String();
}

View file

@ -0,0 +1,17 @@
#ifndef EVENTS_H
#define EVENTS_H
#include "../utils/events.h"
extern EVENTS_STRUCT_TYPE entries[EVENT_NOF_EVENTS];
/**
* @brief Replaces placeholder with content section in web page
*
* @param[in] var
*
* @return String
*/
String events_processor(const String& var);
#endif

View file

@ -0,0 +1,26 @@
const char index_html[] = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
<title>Battery Emulator</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" href="favicon.png">
<style>
html {font-family: Arial; display: inline-block; text-align: center;}
h2 {font-size: 3.0rem;}
p {font-size: 3.0rem;}
body {max-width: 600px; margin:0px auto; padding-bottom: 25px;}
.switch {position: relative; display: inline-block; width: 120px; height: 68px}
.switch input {display: none}
.slider {position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; border-radius: 6px}
.slider:before {position: absolute; content: ""; height: 52px; width: 52px; left: 8px; bottom: 8px; background-color: #fff; -webkit-transition: .4s; transition: .4s; border-radius: 3px}
input:checked+.slider {background-color: #b30000}
input:checked+.slider:before {-webkit-transform: translateX(52px); -ms-transform: translateX(52px); transform: translateX(52px)}
</style>
</head>
<body>
<h2>Battery Emulator</h2>
%PLACEHOLDER%
</script>
</body>
</html>
)rawliteral";

View file

@ -0,0 +1,238 @@
#include "settings_html.h"
#include <Arduino.h>
String settings_processor(const String& var) {
if (var == "PLACEHOLDER") {
String content = "";
//Page format
content += "<style>";
content += "body { background-color: black; color: white; }";
content += "</style>";
// Start a new block with a specific background color
content += "<div style='background-color: #303E47; padding: 10px; margin-bottom: 10px;border-radius: 50px'>";
// Show current settings with edit buttons and input fields
content += "<h4 style='color: white;'>Battery capacity: <span id='BATTERY_WH_MAX'>" + String(BATTERY_WH_MAX) +
" Wh </span> <button onclick='editWh()'>Edit</button></h4>";
content += "<h4 style='color: white;'>SOC max percentage: " + String(MAXPERCENTAGE / 10.0, 1) +
" </span> <button onclick='editSocMax()'>Edit</button></h4>";
content += "<h4 style='color: white;'>SOC min percentage: " + String(MINPERCENTAGE / 10.0, 1) +
" </span> <button onclick='editSocMin()'>Edit</button></h4>";
content += "<h4 style='color: white;'>Max charge speed: " + String(MAXCHARGEAMP / 10.0, 1) +
" A </span> <button onclick='editMaxChargeA()'>Edit</button></h4>";
content += "<h4 style='color: white;'>Max discharge speed: " + String(MAXDISCHARGEAMP / 10.0, 1) +
" A </span> <button onclick='editMaxDischargeA()'>Edit</button></h4>";
// Close the block
content += "</div>";
#ifdef TEST_FAKE_BATTERY
// Start a new block with blue background color
content += "<div style='background-color: #2E37AD; padding: 10px; margin-bottom: 10px;border-radius: 50px'>";
float voltageFloat = static_cast<float>(battery_voltage) / 10.0; // Convert to float and divide by 10
content += "<h4 style='color: white;'>Fake battery voltage: " + String(voltageFloat, 1) +
" V </span> <button onclick='editFakeBatteryVoltage()'>Edit</button></h4>";
// Close the block
content += "</div>";
#endif
#if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER
// Start a new block with orange background color
content += "<div style='background-color: #FF6E00; padding: 10px; margin-bottom: 10px;border-radius: 50px'>";
content += "<h4 style='color: white;'>Charger HVDC Enabled: ";
if (charger_HV_enabled) {
content += "<span>&#10003;</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
}
content += " <button onclick='editChargerHVDCEnabled()'>Edit</button></h4>";
content += "<h4 style='color: white;'>Charger Aux12VDC Enabled: ";
if (charger_aux12V_enabled) {
content += "<span>&#10003;</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
}
content += " <button onclick='editChargerAux12vEnabled()'>Edit</button></h4>";
content += "<h4 style='color: white;'>Charger Voltage Setpoint: " + String(charger_setpoint_HV_VDC, 1) +
" V </span> <button onclick='editChargerSetpointVDC()'>Edit</button></h4>";
content += "<h4 style='color: white;'>Charger Current Setpoint: " + String(charger_setpoint_HV_IDC, 1) +
" A </span> <button onclick='editChargerSetpointIDC()'>Edit</button></h4>";
// Close the block
content += "</div>";
#endif
content += "<script>";
content += "function editWh() {";
content += "var value = prompt('How much energy the battery can store. Enter new Wh value (1-65000):');";
content += "if (value !== null) {";
content += " if (value >= 1 && value <= 65000) {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/updateBatterySize?value=' + value, true);";
content += " xhr.send();";
content += " } else {";
content += " alert('Invalid value. Please enter a value between 1 and 65000.');";
content += " }";
content += "}";
content += "}";
content += "function editSocMax() {";
content +=
"var value = prompt('Inverter will see fully charged (100pct)SOC when this value is reached. Enter new maximum "
"SOC value that battery will charge to (50.0-100.0):');";
content += "if (value !== null) {";
content += " if (value >= 50 && value <= 100) {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/updateSocMax?value=' + value, true);";
content += " xhr.send();";
content += " } else {";
content += " alert('Invalid value. Please enter a value between 50.0 and 100.0');";
content += " }";
content += "}";
content += "}";
content += "function editSocMin() {";
content +=
"var value = prompt('Inverter will see completely discharged (0pct)SOC when this value is reached. Enter new "
"minimum SOC value that battery will discharge to (0-50.0):');";
content += "if (value !== null) {";
content += " if (value >= 0 && value <= 50) {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/updateSocMin?value=' + value, true);";
content += " xhr.send();";
content += " } else {";
content += " alert('Invalid value. Please enter a value between 0 and 50.0');";
content += " }";
content += "}";
content += "}";
content += "function editMaxChargeA() {";
content +=
"var value = prompt('BYD CAN specific setting, some inverters needs to be artificially limited. Enter new "
"maximum charge current in A (0-1000.0):');";
content += "if (value !== null) {";
content += " if (value >= 0 && value <= 1000) {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/updateMaxChargeA?value=' + value, true);";
content += " xhr.send();";
content += " } else {";
content += " alert('Invalid value. Please enter a value between 0 and 1000.0');";
content += " }";
content += "}";
content += "}";
content += "function editMaxDischargeA() {";
content +=
"var value = prompt('BYD CAN specific setting, some inverters needs to be artificially limited. Enter new "
"maximum discharge current in A (0-1000.0):');";
content += "if (value !== null) {";
content += " if (value >= 0 && value <= 1000) {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/updateMaxDischargeA?value=' + value, true);";
content += " xhr.send();";
content += " } else {";
content += " alert('Invalid value. Please enter a value between 0 and 1000.0');";
content += " }";
content += "}";
content += "}";
#ifdef TEST_FAKE_BATTERY
content += "function editFakeBatteryVoltage() {";
content += " var value = prompt('Enter new fake battery voltage');";
content += "if (value !== null) {";
content += " if (value >= 0 && value <= 5000) {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/updateFakeBatteryVoltage?value=' + value, true);";
content += " xhr.send();";
content += " } else {";
content += " alert('Invalid value. Please enter a value between 0 and 1000');";
content += " }";
content += "}";
content += "}";
#endif
#if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER
content += "function editChargerHVDCEnabled() {";
content += " var value = prompt('Enable or disable HV DC output. Enter 1 for enabled, 0 for disabled');";
content += " if (value !== null) {";
content += " if (value == 0 || value == 1) {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/updateChargerHvEnabled?value=' + value, true);";
content += " xhr.send();";
content += " }";
content += " } else {";
content += " alert('Invalid value. Please enter 1 or 0');";
content += " }";
content += "}";
content += "function editChargerAux12vEnabled() {";
content +=
"var value = prompt('Enable or disable low voltage 12v auxiliary DC output. Enter 1 for enabled, 0 for "
"disabled');";
content += "if (value !== null) {";
content += " if (value == 0 || value == 1) {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/updateChargerAux12vEnabled?value=' + value, true);";
content += " xhr.send();";
content += " } else {";
content += " alert('Invalid value. Please enter 1 or 0');";
content += " }";
content += "}";
content += "}";
content += "function editChargerSetpointVDC() {";
content +=
"var value = prompt('Set charging voltage. Input will be validated against inverter and/or charger "
"configuration parameters, but use sensible values like 200 to 420.');";
content += "if (value !== null) {";
content += " if (value >= 0 && value <= 1000) {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/updateChargeSetpointV?value=' + value, true);";
content += " xhr.send();";
content += " } else {";
content += " alert('Invalid value. Please enter a value between 0 and 1000');";
content += " }";
content += "}";
content += "}";
content += "function editChargerSetpointIDC() {";
content +=
"var value = prompt('Set charging amperage. Input will be validated against inverter and/or charger "
"configuration parameters, but use sensible values like 6 to 48.');";
content += "if (value !== null) {";
content += " if (value >= 0 && value <= 1000) {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/updateChargeSetpointA?value=' + value, true);";
content += " xhr.send();";
content += " } else {";
content += " alert('Invalid value. Please enter a value between 0 and 100');";
content += " }";
content += "}";
content += "}";
content += "function editChargerSetpointEndI() {";
content +=
"var value = prompt('Set amperage that terminates charge as being sufficiently complete. Input will be "
"validated against inverter and/or charger configuration parameters, but use sensible values like 1-5.');";
content += "if (value !== null) {";
content += " if (value >= 0 && value <= 1000) {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/updateChargeEndA?value=' + value, true);";
content += " xhr.send();";
content += " } else {";
content += " alert('Invalid value. Please enter a value between 0 and 100');";
content += " }";
content += "}";
content += "}";
#endif
content += "</script>";
content += "<button onclick='goToMainPage()'>Back to main page</button>";
content += "<script>";
content += "function goToMainPage() { window.location.href = '/'; }";
content += "</script>";
return content;
}
return String();
}

View file

@ -0,0 +1,16 @@
#ifndef SETTINGS_H
#define SETTINGS_H
#include "../../../USER_SETTINGS.h" // Needed for WiFi ssid and password
extern uint16_t battery_voltage; //V+1, 0-500.0 (0-5000)
/**
* @brief Replaces placeholder with content section in web page
*
* @param[in] var
*
* @return String
*/
String settings_processor(const String& var);
#endif

View file

@ -1,40 +1,16 @@
#include "webserver.h"
#include <Preferences.h>
Preferences preferences3;
// 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(
<!DOCTYPE HTML><html>
<head>
<title>Battery Emulator</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" href="favicon.png">
<style>
html {font-family: Arial; display: inline-block; text-align: center;}
h2 {font-size: 3.0rem;}
p {font-size: 3.0rem;}
body {max-width: 600px; margin:0px auto; padding-bottom: 25px;}
.switch {position: relative; display: inline-block; width: 120px; height: 68px}
.switch input {display: none}
.slider {position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; border-radius: 6px}
.slider:before {position: absolute; content: ""; height: 52px; width: 52px; left: 8px; bottom: 8px; background-color: #fff; -webkit-transition: .4s; transition: .4s; border-radius: 3px}
input:checked+.slider {background-color: #b30000}
input:checked+.slider:before {-webkit-transform: translateX(52px); -ms-transform: translateX(52px); transform: translateX(52px)}
</style>
</head>
<body>
<h2>Battery Emulator</h2>
%PLACEHOLDER%
</script>
</body>
</html>
)rawliteral";
#include "cellmonitor_html.h"
#include "events_html.h"
#include "index_html.cpp"
#include "settings_html.h"
enum WifiState {
INIT, //before connecting first time
@ -62,6 +38,8 @@ void init_webserver() {
}
init_WiFi_STA(ssid, password, wifi_channel);
String content = index_html;
// Route for root / web page
server.on("/", HTTP_GET,
[](AsyncWebServerRequest* request) { request->send_P(200, "text/html", index_html, processor); });
@ -75,10 +53,9 @@ void init_webserver() {
request->send_P(200, "text/html", index_html, cellmonitor_processor);
});
#ifdef EVENTLOGGING
// Route for going to event log web page
server.on("/events", HTTP_GET,
[](AsyncWebServerRequest* request) { request->send_P(200, "text/html", index_html, events_processor); });
#endif
// Route for editing Wh
server.on("/updateBatterySize", HTTP_GET, [](AsyncWebServerRequest* request) {
@ -629,9 +606,7 @@ String processor(const String& var) {
content += "function goToUpdatePage() { window.location.href = '/update'; }";
content += "function goToCellmonitorPage() { window.location.href = '/cellmonitor'; }";
content += "function goToSettingsPage() { window.location.href = '/settings'; }";
#ifdef EVENTLOGGING
content += "function goToEventsPage() { window.location.href = '/events'; }";
#endif
content +=
"function promptToReboot() { if (window.confirm('Are you sure you want to reboot the emulator? NOTE: If "
"emulator is handling contactors, they will open during reboot!')) { "
@ -653,348 +628,6 @@ String processor(const String& var) {
return String();
}
String settings_processor(const String& var) {
if (var == "PLACEHOLDER") {
String content = "";
//Page format
content += "<style>";
content += "body { background-color: black; color: white; }";
content += "</style>";
// Start a new block with a specific background color
content += "<div style='background-color: #303E47; padding: 10px; margin-bottom: 10px;border-radius: 50px'>";
// Show current settings with edit buttons and input fields
content += "<h4 style='color: white;'>Battery capacity: <span id='BATTERY_WH_MAX'>" + String(BATTERY_WH_MAX) +
" Wh </span> <button onclick='editWh()'>Edit</button></h4>";
content += "<h4 style='color: white;'>SOC max percentage: " + String(MAXPERCENTAGE / 10.0, 1) +
" </span> <button onclick='editSocMax()'>Edit</button></h4>";
content += "<h4 style='color: white;'>SOC min percentage: " + String(MINPERCENTAGE / 10.0, 1) +
" </span> <button onclick='editSocMin()'>Edit</button></h4>";
content += "<h4 style='color: white;'>Max charge speed: " + String(MAXCHARGEAMP / 10.0, 1) +
" A </span> <button onclick='editMaxChargeA()'>Edit</button></h4>";
content += "<h4 style='color: white;'>Max discharge speed: " + String(MAXDISCHARGEAMP / 10.0, 1) +
" A </span> <button onclick='editMaxDischargeA()'>Edit</button></h4>";
// Close the block
content += "</div>";
#ifdef TEST_FAKE_BATTERY
// Start a new block with blue background color
content += "<div style='background-color: #2E37AD; padding: 10px; margin-bottom: 10px;border-radius: 50px'>";
float voltageFloat = static_cast<float>(battery_voltage) / 10.0; // Convert to float and divide by 10
content += "<h4 style='color: white;'>Fake battery voltage: " + String(voltageFloat, 1) +
" V </span> <button onclick='editFakeBatteryVoltage()'>Edit</button></h4>";
// Close the block
content += "</div>";
#endif
#if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER
// Start a new block with orange background color
content += "<div style='background-color: #FF6E00; padding: 10px; margin-bottom: 10px;border-radius: 50px'>";
content += "<h4 style='color: white;'>Charger HVDC Enabled: ";
if (charger_HV_enabled) {
content += "<span>&#10003;</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
}
content += " <button onclick='editChargerHVDCEnabled()'>Edit</button></h4>";
content += "<h4 style='color: white;'>Charger Aux12VDC Enabled: ";
if (charger_aux12V_enabled) {
content += "<span>&#10003;</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
}
content += " <button onclick='editChargerAux12vEnabled()'>Edit</button></h4>";
content += "<h4 style='color: white;'>Charger Voltage Setpoint: " + String(charger_setpoint_HV_VDC, 1) +
" V </span> <button onclick='editChargerSetpointVDC()'>Edit</button></h4>";
content += "<h4 style='color: white;'>Charger Current Setpoint: " + String(charger_setpoint_HV_IDC, 1) +
" A </span> <button onclick='editChargerSetpointIDC()'>Edit</button></h4>";
// Close the block
content += "</div>";
#endif
content += "<script>";
content += "function editWh() {";
content += "var value = prompt('How much energy the battery can store. Enter new Wh value (1-65000):');";
content += "if (value !== null) {";
content += " if (value >= 1 && value <= 65000) {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/updateBatterySize?value=' + value, true);";
content += " xhr.send();";
content += " } else {";
content += " alert('Invalid value. Please enter a value between 1 and 65000.');";
content += " }";
content += "}";
content += "}";
content += "function editSocMax() {";
content +=
"var value = prompt('Inverter will see fully charged (100pct)SOC when this value is reached. Enter new maximum "
"SOC value that battery will charge to (50.0-100.0):');";
content += "if (value !== null) {";
content += " if (value >= 50 && value <= 100) {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/updateSocMax?value=' + value, true);";
content += " xhr.send();";
content += " } else {";
content += " alert('Invalid value. Please enter a value between 50.0 and 100.0');";
content += " }";
content += "}";
content += "}";
content += "function editSocMin() {";
content +=
"var value = prompt('Inverter will see completely discharged (0pct)SOC when this value is reached. Enter new "
"minimum SOC value that battery will discharge to (0-50.0):');";
content += "if (value !== null) {";
content += " if (value >= 0 && value <= 50) {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/updateSocMin?value=' + value, true);";
content += " xhr.send();";
content += " } else {";
content += " alert('Invalid value. Please enter a value between 0 and 50.0');";
content += " }";
content += "}";
content += "}";
content += "function editMaxChargeA() {";
content +=
"var value = prompt('BYD CAN specific setting, some inverters needs to be artificially limited. Enter new "
"maximum charge current in A (0-1000.0):');";
content += "if (value !== null) {";
content += " if (value >= 0 && value <= 1000) {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/updateMaxChargeA?value=' + value, true);";
content += " xhr.send();";
content += " } else {";
content += " alert('Invalid value. Please enter a value between 0 and 1000.0');";
content += " }";
content += "}";
content += "}";
content += "function editMaxDischargeA() {";
content +=
"var value = prompt('BYD CAN specific setting, some inverters needs to be artificially limited. Enter new "
"maximum discharge current in A (0-1000.0):');";
content += "if (value !== null) {";
content += " if (value >= 0 && value <= 1000) {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/updateMaxDischargeA?value=' + value, true);";
content += " xhr.send();";
content += " } else {";
content += " alert('Invalid value. Please enter a value between 0 and 1000.0');";
content += " }";
content += "}";
content += "}";
#ifdef TEST_FAKE_BATTERY
content += "function editFakeBatteryVoltage() {";
content += " var value = prompt('Enter new fake battery voltage');";
content += "if (value !== null) {";
content += " if (value >= 0 && value <= 5000) {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/updateFakeBatteryVoltage?value=' + value, true);";
content += " xhr.send();";
content += " } else {";
content += " alert('Invalid value. Please enter a value between 0 and 1000');";
content += " }";
content += "}";
content += "}";
#endif
#if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER
content += "function editChargerHVDCEnabled() {";
content += " var value = prompt('Enable or disable HV DC output. Enter 1 for enabled, 0 for disabled');";
content += " if (value !== null) {";
content += " if (value == 0 || value == 1) {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/updateChargerHvEnabled?value=' + value, true);";
content += " xhr.send();";
content += " }";
content += " } else {";
content += " alert('Invalid value. Please enter 1 or 0');";
content += " }";
content += "}";
content += "function editChargerAux12vEnabled() {";
content +=
"var value = prompt('Enable or disable low voltage 12v auxiliary DC output. Enter 1 for enabled, 0 for "
"disabled');";
content += "if (value !== null) {";
content += " if (value == 0 || value == 1) {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/updateChargerAux12vEnabled?value=' + value, true);";
content += " xhr.send();";
content += " } else {";
content += " alert('Invalid value. Please enter 1 or 0');";
content += " }";
content += "}";
content += "}";
content += "function editChargerSetpointVDC() {";
content +=
"var value = prompt('Set charging voltage. Input will be validated against inverter and/or charger "
"configuration parameters, but use sensible values like 200 to 420.');";
content += "if (value !== null) {";
content += " if (value >= 0 && value <= 1000) {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/updateChargeSetpointV?value=' + value, true);";
content += " xhr.send();";
content += " } else {";
content += " alert('Invalid value. Please enter a value between 0 and 1000');";
content += " }";
content += "}";
content += "}";
content += "function editChargerSetpointIDC() {";
content +=
"var value = prompt('Set charging amperage. Input will be validated against inverter and/or charger "
"configuration parameters, but use sensible values like 6 to 48.');";
content += "if (value !== null) {";
content += " if (value >= 0 && value <= 1000) {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/updateChargeSetpointA?value=' + value, true);";
content += " xhr.send();";
content += " } else {";
content += " alert('Invalid value. Please enter a value between 0 and 100');";
content += " }";
content += "}";
content += "}";
content += "function editChargerSetpointEndI() {";
content +=
"var value = prompt('Set amperage that terminates charge as being sufficiently complete. Input will be "
"validated against inverter and/or charger configuration parameters, but use sensible values like 1-5.');";
content += "if (value !== null) {";
content += " if (value >= 0 && value <= 1000) {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/updateChargeEndA?value=' + value, true);";
content += " xhr.send();";
content += " } else {";
content += " alert('Invalid value. Please enter a value between 0 and 100');";
content += " }";
content += "}";
content += "}";
#endif
content += "</script>";
content += "<button onclick='goToMainPage()'>Back to main page</button>";
content += "<script>";
content += "function goToMainPage() { window.location.href = '/'; }";
content += "</script>";
return content;
}
return String();
}
String cellmonitor_processor(const String& var) {
if (var == "PLACEHOLDER") {
String content = "";
// Page format
content += "<style>";
content += "body { background-color: black; color: white; }";
content += ".container { display: flex; flex-wrap: wrap; justify-content: space-around; }";
content += ".cell { width: 48%; margin: 1%; padding: 10px; border: 1px solid white; text-align: center; }";
content += ".low-voltage { color: red; }"; // Style for low voltage text
content += ".voltage-values { margin-bottom: 10px; }"; // Style for voltage values section
content += "</style>";
// Start a new block with a specific background color
content += "<div style='background-color: #303E47; padding: 10px; margin-bottom: 10px; border-radius: 50px'>";
// Display max, min, and deviation voltage values
content += "<div class='voltage-values'>";
content += "Max Voltage: " + String(cell_max_voltage) + " mV<br>";
content += "Min Voltage: " + String(cell_min_voltage) + " mV<br>";
int deviation = cell_max_voltage - cell_min_voltage;
content += "Voltage Deviation: " + String(deviation) + " mV";
content += "</div>";
// Visualize the populated cells in forward order using flexbox with conditional text color
content += "<div class='container'>";
for (int i = 0; i < 120; ++i) {
// Skip empty values
if (cellvoltages[i] == 0) {
continue;
}
String cellContent = "Cell " + String(i + 1) + "<br>" + String(cellvoltages[i]) + " mV";
// Check if the cell voltage is below 3000, apply red color
if (cellvoltages[i] < 3000) {
cellContent = "<span class='low-voltage'>" + cellContent + "</span>";
}
content += "<div class='cell'>" + cellContent + "</div>";
}
content += "</div>";
// Close the block
content += "</div>";
content += "<button onclick='goToMainPage()'>Back to main page</button>";
content += "<script>";
content += "function goToMainPage() { window.location.href = '/'; }";
content += "</script>";
return content;
}
return String();
}
#ifdef EVENTLOGGING
const char EVENTS_HTML_START[] PROGMEM = R"=====(
<style>
body { background-color: black; color: white; }
.event-log { display: flex; flex-direction: column; }
.event { display: flex; flex-wrap: wrap; border: 1px solid white; padding: 10px; }
.event > div { flex: 1; min-width: 100px; max-width: 90%; word-break: break-word; }
</style>
<div style='background-color: #303E47; padding: 10px; margin-bottom: 10px;border-radius: 50px'>
<h4 style='color: white;'>Event log:</h4>
<div class="event-log">
<div class="event">
<div>Event Type</div><div>LED Color</div><div>Last Event (seconds ago)</div><div>Count</div><div>Data</div><div>Message</div>
</div>
)=====";
const char EVENTS_HTML_END[] PROGMEM = R"=====(
</div></div>
<button onclick='goToMainPage()'>Back to main page</button>
<script>
function goToMainPage() {
window.location.href = '/';
}
</script>
)=====";
String events_processor(const String& var) {
if (var == "PLACEHOLDER") {
String content = "";
content.reserve(5000);
// Page format
content.concat(FPSTR(EVENTS_HTML_START));
for (int i = 0; i < EVENT_NOF_EVENTS; i++) {
Serial.println("Event: " + String(get_event_enum_string(static_cast<EVENTS_ENUM_TYPE>(i))) +
" count: " + String(entries[i].occurences) + " seconds: " + String(entries[i].timestamp) +
" data: " + String(entries[i].data));
if (entries[i].occurences > 0) {
content.concat("<div class='event'>");
content.concat("<div>" + String(get_event_enum_string(static_cast<EVENTS_ENUM_TYPE>(i))) + "</div>");
content.concat("<div>" + String(get_led_color_display_text(entries[i].led_color)) + "</div>");
content.concat("<div>" + String((millis() / 1000) - entries[i].timestamp) + "</div>");
content.concat("<div>" + String(entries[i].occurences) + "</div>");
content.concat("<div>" + String(entries[i].data) + "</div>");
content.concat("<div>" + String(get_event_message(static_cast<EVENTS_ENUM_TYPE>(i))) + "</div>");
content.concat("</div>"); // End of event row
}
}
content.concat(FPSTR(EVENTS_HTML_END));
return content;
}
return String();
}
#endif
void onOTAStart() {
// Log when OTA has started
Serial.println("OTA update started!");

View file

@ -12,7 +12,6 @@
#include "../../lib/me-no-dev-ESPAsyncWebServer/src/ESPAsyncWebServer.h"
#include "../../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#include "../config.h" // Needed for LED defines
#include "../utils/events.h"
#ifdef MQTT
#include "../mqtt/mqtt.h"
#endif
@ -37,7 +36,6 @@ extern uint16_t cellvoltages[120]; //mV 0-4350 per cell
extern uint8_t LEDcolor; //Enum, 0-10
extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false
extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false
extern EVENTS_STRUCT_TYPE entries[EVENT_NOF_EVENTS];
extern const char* ssid;
extern const char* password;
@ -120,33 +118,6 @@ 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 Replaces placeholder with content section in web page
*
* @param[in] var
*
* @return String
*/
String cellmonitor_processor(const String& var);
/**
* @brief Replaces placeholder with content section in web page
*
* @param[in] var
*
* @return String
*/
String events_processor(const String& var);
/**
* @brief Executes on OTA start
*