mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-03 09:49:32 +02:00
Merge branch 'main' into feature/event-log
This commit is contained in:
commit
4dafe58770
16 changed files with 628 additions and 423 deletions
|
@ -2,40 +2,16 @@
|
|||
#include <Preferences.h>
|
||||
#include "../utils/events.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
|
||||
|
@ -63,6 +39,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); });
|
||||
|
@ -76,10 +54,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) {
|
||||
|
@ -630,9 +607,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!')) { "
|
||||
|
@ -654,348 +629,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>✓</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>✕</span>";
|
||||
}
|
||||
content += " <button onclick='editChargerHVDCEnabled()'>Edit</button></h4>";
|
||||
|
||||
content += "<h4 style='color: white;'>Charger Aux12VDC Enabled: ";
|
||||
if (charger_aux12V_enabled) {
|
||||
content += "<span>✓</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>✕</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
|
||||
ESP32Can.CANStop();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue