mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-03 17:59:27 +02:00
Feature: Add initial version of CAN logger (#639)
* Add initial version of CAN logger
This commit is contained in:
parent
615760afdc
commit
4232da8aec
10 changed files with 184 additions and 6 deletions
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>";
|
||||
|
|
58
Software/src/devboard/webserver/can_logging_html.cpp
Normal file
58
Software/src/devboard/webserver/can_logging_html.cpp
Normal 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();
|
||||
}
|
16
Software/src/devboard/webserver/can_logging_html.h
Normal file
16
Software/src/devboard/webserver/can_logging_html.h
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>";
|
||||
|
|
|
@ -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 "
|
||||
|
|
|
@ -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];
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue