Skip to content

Commit

Permalink
Feature: Add initial version of CAN logger (#639)
Browse files Browse the repository at this point in the history
* Add initial version of CAN logger
  • Loading branch information
dalathegreat authored Nov 27, 2024
1 parent 615760a commit 4232da8
Show file tree
Hide file tree
Showing 10 changed files with 184 additions and 6 deletions.
49 changes: 44 additions & 5 deletions Software/Software.ino
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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(" ");
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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);
Expand Down
5 changes: 5 additions & 0 deletions Software/src/datalayer/datalayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions Software/src/devboard/webserver/advanced_battery_html.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>";
Expand Down
58 changes: 58 additions & 0 deletions Software/src/devboard/webserver/can_logging_html.cpp
Original file line number Diff line number Diff line change
@@ -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 changes: 16 additions & 0 deletions Software/src/devboard/webserver/can_logging_html.h
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions Software/src/devboard/webserver/cellmonitor_html.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions Software/src/devboard/webserver/events_html.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
4 changes: 4 additions & 0 deletions Software/src/devboard/webserver/settings_html.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>";
Expand Down
46 changes: 46 additions & 0 deletions Software/src/devboard/webserver/webserver.cpp
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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>";
Expand All @@ -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 "
Expand Down
2 changes: 1 addition & 1 deletion Software/src/inverter/BYD-MODBUS.h
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down

0 comments on commit 4232da8

Please sign in to comment.