Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Webserver cleanup + Event timestamps #169

Merged
merged 3 commits into from
Feb 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions Software/Software.ino
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,7 @@ void setup() {
init_webserver();
#endif

#ifdef EVENTLOGGING
init_events();
#endif

init_CAN();

Expand Down
12 changes: 7 additions & 5 deletions Software/USER_SETTINGS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
3 changes: 1 addition & 2 deletions Software/USER_SETTINGS.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) */
Expand Down
14 changes: 6 additions & 8 deletions Software/src/devboard/utils/events.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,17 @@ void init_events(void) {
entries[i].timestamp = 0;
entries[i].data = 0;
entries[i].occurences = 0;
entries[i].led_color = RED; // Most events are RED
entries[i].led_color = RED; // Most events are RED, critical errors
}

// YELLOW events below
// YELLOW warning events below
entries[EVENT_12V_LOW].led_color = YELLOW;
entries[EVENT_CAN_WARNING].led_color = YELLOW;
entries[EVENT_CELL_DEVIATION_HIGH].led_color = YELLOW;
entries[EVENT_KWH_PLAUSIBILITY_ERROR].led_color = YELLOW;
}

void set_event(EVENTS_ENUM_TYPE event, uint8_t data) {
#ifdef EVENTLOGGING
if (event >= EVENT_NOF_EVENTS) {
event = EVENT_UNKNOWN_EVENT_SET;
}
Expand All @@ -42,7 +41,6 @@ void set_event(EVENTS_ENUM_TYPE event, uint8_t data) {
Serial.println("Set event: " + String(get_event_enum_string(event)) + ". Has occured " +
String(entries[event].occurences) + " times");
#endif
#endif
}

void update_event_timestamps(void) {
Expand All @@ -61,13 +59,13 @@ static void update_led_color(EVENTS_ENUM_TYPE event) {
const char* get_led_color_display_text(u_int8_t led_color) {
switch (led_color) {
case RED:
return "RED";
return "Error";
case YELLOW:
return "YELLOW";
return "Warning";
case GREEN:
return "GREEN";
return "Info";
case BLUE:
return "BLUE";
return "Debug";
default:
return "UNKNOWN";
}
Expand Down
4 changes: 0 additions & 4 deletions Software/src/devboard/webserver/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@ This section lists a number of features that can be implemented as part of the w

- 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/)
- TODO: display CAN state (indicate if there is a communication error)
- TODO: display battery errors in battery diagnosis tab
- TODO: display inverter errors in battery diagnosis tab
- TODO: add functionality to turn WiFi AP off
- TODO: fix IP address on home network (https://randomnerdtutorials.com/esp32-static-fixed-ip-address-arduino-ide/)
- TODO: set hostname (https://randomnerdtutorials.com/esp32-set-custom-hostname-arduino/)
Expand Down
56 changes: 56 additions & 0 deletions Software/src/devboard/webserver/cellmonitor_html.cpp
Original file line number Diff line number Diff line change
@@ -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();
}
20 changes: 20 additions & 0 deletions Software/src/devboard/webserver/cellmonitor_html.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#ifndef CELLMONITOR_H
#define CELLMONITOR_H

#include <Arduino.h>
#include <stdint.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
91 changes: 91 additions & 0 deletions Software/src/devboard/webserver/events_html.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#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'>
<div class="event-log">
<div class="event">
<div>Event Type</div><div>Severity</div><div>Last Event</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 displayEventLog(){document.querySelector(".event-log");var i=(new Date).getTime()/1e3;document.querySelectorAll(".event").forEach(function(e){var n=e.querySelector(".last-event-seconds-ago"),t=e.querySelector(".timestamp");if(n&&t){var o=parseInt(n.innerText,10),a=parseFloat(t.innerText),r=new Date(1e3*(i-a+o)).toLocaleString();n.innerText=r}})}function goToMainPage(){window.location.href="/"}window.onload=function(){displayEventLog()}</script>
)=====";

/* The above <script> section is minified to save storage and increase performance, here is the full function:
<script>
function displayEventLog() {
var eventLogElement = document.querySelector('.event-log');

// Get the current time on the client side
var currentTime = new Date().getTime() / 1000; // Convert milliseconds to seconds

// Loop through the events and update the "Last Event" column
var events = document.querySelectorAll('.event');
events.forEach(function(event) {
var secondsAgoElement = event.querySelector('.last-event-seconds-ago');
var timestampElement = event.querySelector('.timestamp');

if (secondsAgoElement && timestampElement) {
var secondsAgo = parseInt(secondsAgoElement.innerText, 10);
var uptimeTimestamp = parseFloat(timestampElement.innerText); // Parse as float to handle seconds with decimal parts

// Calculate the actual system time based on the client-side current time
var actualTime = new Date((currentTime - uptimeTimestamp + secondsAgo) * 1000);

// Format the date and time
var formattedTime = actualTime.toLocaleString();

// Update the "Last Event" column with the formatted time
secondsAgoElement.innerText = formattedTime;
}
});
}

// Call the displayEventLog function when the page is loaded
window.onload = function() {
displayEventLog();
};

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 class='last-event-seconds-ago'>" + 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 class='timestamp'>" + String(entries[i].timestamp) + "</div>");
content.concat("</div>"); // End of event row
}
}
content.concat(FPSTR(EVENTS_HTML_END));
return content;
}
return String();
}
17 changes: 17 additions & 0 deletions Software/src/devboard/webserver/events_html.h
Original file line number Diff line number Diff line change
@@ -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
29 changes: 29 additions & 0 deletions Software/src/devboard/webserver/index_html.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const char index_html[] = R"rawliteral(
<!DOCTYPE HTML><html><head><title>Battery Emulator</title><meta name="viewport" content="width=device-width,initial-scale=1"><style>html{font-family:Arial;display:inline-block;text-align:center}h2{font-size:3rem}p{font-size:3rem}body{max-width:600px;margin:0 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%</body></html>
)rawliteral";

/* The above code is minified to increase performance. Here is the full HTML function:
<!DOCTYPE HTML><html>
<head>
<title>Battery Emulator</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<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>
*/
Loading
Loading