diff --git a/Software/Software.ino b/Software/Software.ino index d88ef46b..721eabde 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -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); diff --git a/Software/src/datalayer/datalayer.h b/Software/src/datalayer/datalayer.h index dcf75b7b..81814336 100644 --- a/Software/src/datalayer/datalayer.h +++ b/Software/src/datalayer/datalayer.h @@ -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 { diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index 9404b36f..b3cba77f 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -9,6 +9,10 @@ String advanced_battery_processor(const String& var) { //Page format content += ""; content += ""; diff --git a/Software/src/devboard/webserver/can_logging_html.cpp b/Software/src/devboard/webserver/can_logging_html.cpp new file mode 100644 index 00000000..59f03823 --- /dev/null +++ b/Software/src/devboard/webserver/can_logging_html.cpp @@ -0,0 +1,58 @@ +#include "can_logging_html.h" +#include +#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 += ""; + content += " "; + content += " "; + content += ""; + + // Start a new block for the CAN messages + content += "
"; + + // 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 += "
" + singleMessage + "
"; + startIndex = endIndex + 1; // Move past the newline character + endIndex = messages.indexOf('\n', startIndex); + } + } + + content += "
"; + + // Add JavaScript for navigation + content += ""; + return content; + } + return String(); +} diff --git a/Software/src/devboard/webserver/can_logging_html.h b/Software/src/devboard/webserver/can_logging_html.h new file mode 100644 index 00000000..f208c3ff --- /dev/null +++ b/Software/src/devboard/webserver/can_logging_html.h @@ -0,0 +1,16 @@ +#ifndef CANLOGGER_H +#define CANLOGGER_H + +#include +#include + +/** + * @brief Replaces placeholder with content section in web page + * + * @param[in] var + * + * @return String + */ +String can_logger_processor(const String& var); + +#endif diff --git a/Software/src/devboard/webserver/cellmonitor_html.cpp b/Software/src/devboard/webserver/cellmonitor_html.cpp index 21a5c1df..f37f5d9d 100644 --- a/Software/src/devboard/webserver/cellmonitor_html.cpp +++ b/Software/src/devboard/webserver/cellmonitor_html.cpp @@ -8,6 +8,10 @@ String cellmonitor_processor(const String& var) { // Page format content += " diff --git a/Software/src/devboard/webserver/settings_html.cpp b/Software/src/devboard/webserver/settings_html.cpp index caac64d4..484e5021 100644 --- a/Software/src/devboard/webserver/settings_html.cpp +++ b/Software/src/devboard/webserver/settings_html.cpp @@ -8,6 +8,10 @@ String settings_processor(const String& var) { //Page format content += ""; content += ""; diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 360787d2..227dee8c 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -1,5 +1,6 @@ #include "webserver.h" #include +#include #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 += ""; // Start a new block with a specific background color @@ -874,6 +918,7 @@ String processor(const String& var) { content += " "; content += " "; content += " "; + content += " "; content += " "; content += " "; content += ""; @@ -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 " diff --git a/Software/src/inverter/BYD-MODBUS.h b/Software/src/inverter/BYD-MODBUS.h index 487c9784..d1326609 100644 --- a/Software/src/inverter/BYD-MODBUS.h +++ b/Software/src/inverter/BYD-MODBUS.h @@ -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];