Feature: Add initial version of CAN logger (#639)

* Add initial version of CAN logger
This commit is contained in:
Daniel Öster 2024-11-27 20:00:16 +02:00 committed by GitHub
parent 615760afdc
commit 4232da8aec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 184 additions and 6 deletions

View file

@ -78,7 +78,7 @@ ACAN2517FD canfd(MCP2517_CS, SPI, MCP2517_INT);
// ModbusRTU parameters
#ifdef MODBUS_INVERTER_SELECTED
#define MB_RTU_NUM_VALUES 30000
#define MB_RTU_NUM_VALUES 13100
uint16_t mbPV[MB_RTU_NUM_VALUES]; // Process variable memory
// Create a ModbusRTU server instance listening on Serial2 with 2000ms timeout
ModbusServerRTU MBserver(Serial2, 2000);
@ -623,6 +623,7 @@ void init_equipment_stop_button() {
enum frameDirection { MSG_RX, MSG_TX }; //RX = 0, TX = 1
void print_can_frame(CAN_frame frame, frameDirection msgDir);
void print_can_frame(CAN_frame frame, frameDirection msgDir) {
#ifdef DEBUG_CAN_DATA // If enabled in user settings, print out the CAN messages via USB
uint8_t i = 0;
Serial.print(millis());
Serial.print(" ");
@ -637,6 +638,48 @@ void print_can_frame(CAN_frame frame, frameDirection msgDir) {
Serial.print(" ");
}
Serial.println(" ");
#endif //#DEBUG_CAN_DATA
if (datalayer.system.info.can_logging_active) { // If user clicked on CAN Logging page in webserver, start recording
char message_string[128]; // Buffer to hold the message string
int offset = 0; // Keeps track of the current position in the buffer
// Add timestamp
offset += snprintf(message_string + offset, sizeof(message_string) - offset, "%lu ", millis());
// Add direction
offset +=
snprintf(message_string + offset, sizeof(message_string) - offset, "%s ", (msgDir == MSG_RX) ? "RX" : "TX");
// Add ID and DLC
offset += snprintf(message_string + offset, sizeof(message_string) - offset, "%X %u ", frame.ID, frame.DLC);
// Add data bytes
for (uint8_t i = 0; i < frame.DLC; i++) {
offset += snprintf(message_string + offset, sizeof(message_string) - offset, "%s%X ",
frame.data.u8[i] < 16 ? "0" : "", frame.data.u8[i]);
}
// Add linebreak
offset += snprintf(message_string + offset, sizeof(message_string) - offset, "\n");
// Ensure the string is null-terminated
message_string[sizeof(message_string) - 1] = '\0';
// Append the message string to the system info structure
size_t current_len =
strnlen(datalayer.system.info.logged_can_messages, sizeof(datalayer.system.info.logged_can_messages));
size_t available_space =
sizeof(datalayer.system.info.logged_can_messages) - current_len - 1; // Space left for new data
if (available_space < strlen(message_string) + 1) {
// Not enough space, reset and start from the beginning
current_len = 0;
datalayer.system.info.logged_can_messages[0] = '\0';
}
strncat(datalayer.system.info.logged_can_messages, message_string, available_space);
}
}
#ifdef CAN_FD
@ -1103,9 +1146,7 @@ void transmit_can(CAN_frame* tx_frame, int interface) {
if (!allowed_to_send_CAN) {
return;
}
#ifdef DEBUG_CAN_DATA
print_can_frame(*tx_frame, frameDirection(MSG_TX));
#endif //DEBUG_CAN_DATA
switch (interface) {
case CAN_NATIVE:
@ -1160,9 +1201,7 @@ void transmit_can(CAN_frame* tx_frame, int interface) {
}
void receive_can(CAN_frame* rx_frame, int interface) {
#ifdef DEBUG_CAN_DATA
print_can_frame(*rx_frame, frameDirection(MSG_RX));
#endif //DEBUG_CAN_DATA
if (interface == can_config.battery) {
receive_can_battery(*rx_frame);

View file

@ -131,6 +131,11 @@ typedef struct {
char battery_protocol[64] = {0};
/** array with type of inverter used, for displaying on webserver */
char inverter_protocol[64] = {0};
/** array with incoming CAN messages, for displaying on webserver */
char logged_can_messages[15000] = {0};
/** bool, determines if CAN messages should be logged for webserver */
bool can_logging_active = false;
} DATALAYER_SYSTEM_INFO_TYPE;
typedef struct {

View file

@ -9,6 +9,10 @@ String advanced_battery_processor(const String& var) {
//Page format
content += "<style>";
content += "body { background-color: black; color: white; }";
content +=
"button { background-color: #505E67; color: white; border: none; padding: 10px 20px; margin-bottom: 20px; "
"cursor: pointer; border-radius: 10px; }";
content += "button:hover { background-color: #3A4A52; }";
content += "</style>";
content += "<button onclick='goToMainPage()'>Back to main page</button>";

View file

@ -0,0 +1,58 @@
#include "can_logging_html.h"
#include <Arduino.h>
#include "../../datalayer/datalayer.h"
String can_logger_processor(const String& var) {
if (var == "X") {
datalayer.system.info.can_logging_active =
true; // Signal to main loop that we should log messages. Disabled by default for performance reasons
String content = "";
// Page format
content += "<style>";
content += "body { background-color: black; color: white; font-family: Arial, sans-serif; }";
content +=
"button { background-color: #505E67; color: white; border: none; padding: 10px 20px; margin-bottom: 20px; "
"cursor: pointer; border-radius: 10px; }";
content += "button:hover { background-color: #3A4A52; }";
content +=
".can-message { background-color: #404E57; margin-bottom: 5px; padding: 10px; border-radius: 5px; font-family: "
"monospace; }";
content += "</style>";
content += "<button onclick='refreshPage()'>Refresh data</button> ";
content += "<button onclick='exportLogs()'>Export to .txt</button> ";
content += "<button onclick='stopLoggingAndGoToMainPage()'>Back to main page</button>";
// Start a new block for the CAN messages
content += "<div style='background-color: #303E47; padding: 20px; border-radius: 15px'>";
// Check for messages
if (datalayer.system.info.logged_can_messages[0] == 0) {
content += "CAN logger started! Refresh page to display incoming(RX) and outgoing(TX) messages";
} else {
// Split the messages using the newline character
String messages = String(datalayer.system.info.logged_can_messages);
int startIndex = 0;
int endIndex = messages.indexOf('\n');
while (endIndex != -1) {
// Extract a single message and wrap it in a styled div
String singleMessage = messages.substring(startIndex, endIndex);
content += "<div class='can-message'>" + singleMessage + "</div>";
startIndex = endIndex + 1; // Move past the newline character
endIndex = messages.indexOf('\n', startIndex);
}
}
content += "</div>";
// Add JavaScript for navigation
content += "<script>";
content += "function refreshPage(){ location.reload(true); }";
content += "function exportLogs() { window.location.href = '/export_logs'; }";
content += "function stopLoggingAndGoToMainPage() {";
content += " fetch('/stop_logging').then(() => window.location.href = '/');";
content += "}";
content += "</script>";
return content;
}
return String();
}

View file

@ -0,0 +1,16 @@
#ifndef CANLOGGER_H
#define CANLOGGER_H
#include <Arduino.h>
#include <string>
/**
* @brief Replaces placeholder with content section in web page
*
* @param[in] var
*
* @return String
*/
String can_logger_processor(const String& var);
#endif

View file

@ -8,6 +8,10 @@ String cellmonitor_processor(const String& var) {
// Page format
content += "<style>";
content += "body { background-color: black; color: white; }";
content +=
"button { background-color: #505E67; color: white; border: none; padding: 10px 20px; margin-bottom: 20px; "
"cursor: pointer; border-radius: 10px; }";
content += "button:hover { background-color: #3A4A52; }";
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

View file

@ -5,6 +5,8 @@ const char EVENTS_HTML_START[] = R"=====(
)=====";
const char EVENTS_HTML_END[] = R"=====(
</div></div>
<style> button { background-color: #505E67; color: white; border: none; padding: 10px 20px; margin-bottom: 20px; cursor: pointer; border-radius: 10px; }
button:hover { background-color: #3A4A52; }</style>
<button onclick="askClear()">Clear all events</button>
<button onclick="home()">Back to main page</button>
<style>.event:nth-child(even){background-color:#455a64}.event:nth-child(odd){background-color:#394b52}</style><script>function showEvent(){document.querySelectorAll(".event").forEach(function(e){var n=e.querySelector(".sec-ago");n&&(n.innerText=new Date(Date.now()-(4294967296*+n.innerText.split(";")[0]+ +n.innerText.split(";")[1])).toLocaleString())})}function askClear(){window.confirm("Are you sure you want to clear all events?")&&(window.location.href="/clearevents")}function home(){window.location.href="/"}window.onload=function(){showEvent()}</script>

View file

@ -8,6 +8,10 @@ String settings_processor(const String& var) {
//Page format
content += "<style>";
content += "body { background-color: black; color: white; }";
content +=
"button { background-color: #505E67; color: white; border: none; padding: 10px 20px; margin-bottom: 20px; "
"cursor: pointer; border-radius: 10px; }";
content += "button:hover { background-color: #3A4A52; }";
content += "</style>";
content += "<button onclick='goToMainPage()'>Back to main page</button>";

View file

@ -1,5 +1,6 @@
#include "webserver.h"
#include <Preferences.h>
#include <ctime>
#include "../../datalayer/datalayer.h"
#include "../../datalayer/datalayer_extended.h"
#include "../../lib/bblanchon-ArduinoJson/ArduinoJson.h"
@ -14,6 +15,7 @@ AsyncWebServer server(80);
unsigned long ota_progress_millis = 0;
#include "advanced_battery_html.h"
#include "can_logging_html.h"
#include "cellmonitor_html.h"
#include "events_html.h"
#include "index_html.cpp"
@ -56,6 +58,44 @@ void init_webserver() {
request->send_P(200, "text/html", index_html, advanced_battery_processor);
});
// Route for going to CAN logging web page
server.on("/canlog", HTTP_GET, [](AsyncWebServerRequest* request) {
request->send_P(200, "text/html", index_html, can_logger_processor);
});
// Define the handler to stop logging
server.on("/stop_logging", HTTP_GET, [](AsyncWebServerRequest* request) {
datalayer.system.info.can_logging_active = false;
request->send_P(200, "text/plain", "Logging stopped");
});
// Define the handler to export logs
server.on("/export_logs", HTTP_GET, [](AsyncWebServerRequest* request) {
String logs = String(datalayer.system.info.logged_can_messages);
if (logs.length() == 0) {
logs = "No logs available.";
}
// Get the current time
time_t now = time(nullptr);
struct tm timeinfo;
localtime_r(&now, &timeinfo);
// Ensure time retrieval was successful
char filename[32];
if (strftime(filename, sizeof(filename), "canlog_%H-%M-%S.txt", &timeinfo)) {
// Valid filename created
} else {
// Fallback filename if automatic timestamping failed
strcpy(filename, "battery_emulator_can_log.txt");
}
// Use request->send with dynamic headers
AsyncWebServerResponse* response = request->beginResponse(200, "text/plain", logs);
response->addHeader("Content-Disposition", String("attachment; filename=\"") + String(filename) + "\"");
request->send(response);
});
// Route for going to cellmonitor web page
server.on("/cellmonitor", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
@ -443,6 +483,10 @@ String processor(const String& var) {
//Page format
content += "<style>";
content += "body { background-color: black; color: white; }";
content +=
"button { background-color: #505E67; color: white; border: none; padding: 10px 20px; margin-bottom: 20px; "
"cursor: pointer; border-radius: 10px; }";
content += "button:hover { background-color: #3A4A52; }";
content += "</style>";
// Start a new block with a specific background color
@ -874,6 +918,7 @@ String processor(const String& var) {
content += "<button onclick='OTA()'>Perform OTA update</button> ";
content += "<button onclick='Settings()'>Change Settings</button> ";
content += "<button onclick='Advanced()'>More Battery Info</button> ";
content += "<button onclick='CANlog()'>CAN logger</button> ";
content += "<button onclick='Cellmon()'>Cellmonitor</button> ";
content += "<button onclick='Events()'>Events</button> ";
content += "<button onclick='askReboot()'>Reboot Emulator</button>";
@ -898,6 +943,7 @@ String processor(const String& var) {
content += "function Cellmon() { window.location.href = '/cellmonitor'; }";
content += "function Settings() { window.location.href = '/settings'; }";
content += "function Advanced() { window.location.href = '/advanced'; }";
content += "function CANlog() { window.location.href = '/canlog'; }";
content += "function Events() { window.location.href = '/events'; }";
content +=
"function askReboot() { if (window.confirm('Are you sure you want to reboot the emulator? NOTE: If "

View file

@ -4,7 +4,7 @@
#define MODBUS_INVERTER_SELECTED
#define MB_RTU_NUM_VALUES 30000
#define MB_RTU_NUM_VALUES 13100
#define MAX_POWER 40960 //BYD Modbus specific value
extern uint16_t mbPV[MB_RTU_NUM_VALUES];