mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-04 10:19:29 +02:00
Add ability to loop log
This commit is contained in:
parent
48e9ba9118
commit
533bcc0f22
3 changed files with 122 additions and 95 deletions
|
@ -222,6 +222,8 @@ typedef struct {
|
||||||
bool can_logging_active = false;
|
bool can_logging_active = false;
|
||||||
/** uint8_t, enumeration which CAN interface should be used for log playback */
|
/** uint8_t, enumeration which CAN interface should be used for log playback */
|
||||||
uint8_t can_replay_interface = CAN_NATIVE;
|
uint8_t can_replay_interface = CAN_NATIVE;
|
||||||
|
/** bool, determines if CAN replay should loop or not */
|
||||||
|
bool loop_playback = false;
|
||||||
|
|
||||||
} DATALAYER_SYSTEM_INFO_TYPE;
|
} DATALAYER_SYSTEM_INFO_TYPE;
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ String can_replay_processor(void) {
|
||||||
".can-message { background-color: #404E57; margin-bottom: 5px; padding: 10px; border-radius: 5px; font-family: "
|
".can-message { background-color: #404E57; margin-bottom: 5px; padding: 10px; border-radius: 5px; font-family: "
|
||||||
"monospace; }";
|
"monospace; }";
|
||||||
content += "</style>";
|
content += "</style>";
|
||||||
content += "<button onclick='stopPlaybackAndGoToMainPage()'>Stop & Back to main page</button>";
|
content += "<button onclick='stopPlaybackAndGoToMainPage()'>Back to main page</button>";
|
||||||
|
|
||||||
// Start a new block for the CAN messages
|
// Start a new block for the CAN messages
|
||||||
content += "<div style='background-color: #303E47; padding: 20px; border-radius: 15px'>";
|
content += "<div style='background-color: #303E47; padding: 20px; border-radius: 15px'>";
|
||||||
|
@ -63,11 +63,14 @@ String can_replay_processor(void) {
|
||||||
|
|
||||||
content += "<h3>Step 3: Playback control</h3>";
|
content += "<h3>Step 3: Playback control</h3>";
|
||||||
|
|
||||||
|
//Checkbox to see if the user wants the log to repeat once it reaches the end
|
||||||
|
content += "<input type=\"checkbox\" id=\"loopCheckbox\"> Loop ";
|
||||||
|
|
||||||
// Add a button to start playing the log
|
// Add a button to start playing the log
|
||||||
content += "<button onclick='startReplay()'>Start</button> ";
|
content += "<button onclick='startReplay()'>Start</button> ";
|
||||||
|
|
||||||
// Add a button to stop playing the log
|
// Add a button to stop playing the log
|
||||||
content += "<button onclick='startReplay()'>Stop</button> ";
|
content += "<button onclick='stopReplay()'>Stop</button> ";
|
||||||
|
|
||||||
content += "<h3>Uploaded Log Preview:</h3>";
|
content += "<h3>Uploaded Log Preview:</h3>";
|
||||||
content += "<pre id='file-content'></pre>";
|
content += "<pre id='file-content'></pre>";
|
||||||
|
@ -113,9 +116,17 @@ String can_replay_processor(void) {
|
||||||
// Add JavaScript for navigation
|
// Add JavaScript for navigation
|
||||||
content += "<script>";
|
content += "<script>";
|
||||||
content += "function startReplay() {";
|
content += "function startReplay() {";
|
||||||
content += " var xhr = new XMLHttpRequest();";
|
content += " let loop = document.getElementById('loopCheckbox').checked ? 1 : 0;";
|
||||||
content += " xhr.open('GET', '/startReplay', true);";
|
content += " fetch('/startReplay?loop=' + loop, { method: 'GET' })";
|
||||||
content += " xhr.send();";
|
content += " .then(response => response.text())";
|
||||||
|
content += " .then(data => console.log(data))";
|
||||||
|
content += " .catch(error => console.error('Error:', error));";
|
||||||
|
content += "}";
|
||||||
|
content += "function stopReplay() {";
|
||||||
|
content += " fetch(`/stopReplay`, { method: 'GET' })";
|
||||||
|
content += " .then(response => response.text())";
|
||||||
|
content += " .then(data => console.log(data))";
|
||||||
|
content += " .catch(error => console.error('Error:', error));";
|
||||||
content += "}";
|
content += "}";
|
||||||
content += "function sendCANSelection() {";
|
content += "function sendCANSelection() {";
|
||||||
content += " var selectedInterface = document.getElementById('canInterface').value;";
|
content += " var selectedInterface = document.getElementById('canInterface').value;";
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include "../utils/events.h"
|
#include "../utils/events.h"
|
||||||
#include "../utils/led_handler.h"
|
#include "../utils/led_handler.h"
|
||||||
#include "../utils/timer.h"
|
#include "../utils/timer.h"
|
||||||
|
#include "esp_task_wdt.h"
|
||||||
|
|
||||||
// Create AsyncWebServer object on port 80
|
// Create AsyncWebServer object on port 80
|
||||||
AsyncWebServer server(80);
|
AsyncWebServer server(80);
|
||||||
|
@ -50,6 +51,96 @@ void handleFileUpload(AsyncWebServerRequest* request, String filename, size_t in
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void canReplayTask(void* param) {
|
||||||
|
|
||||||
|
std::vector<String> messages;
|
||||||
|
int lastIndex = 0;
|
||||||
|
|
||||||
|
if (importedLogs.length() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split importedLogs into individual messages
|
||||||
|
while (true) {
|
||||||
|
int nextIndex = importedLogs.indexOf("\n", lastIndex);
|
||||||
|
if (nextIndex == -1) {
|
||||||
|
messages.push_back(importedLogs.substring(lastIndex)); // Add last message
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
messages.push_back(importedLogs.substring(lastIndex, nextIndex));
|
||||||
|
lastIndex = nextIndex + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
float firstTimestamp = -1.0;
|
||||||
|
float lastTimestamp = 0.0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < messages.size(); i++) {
|
||||||
|
esp_task_wdt_reset(); // Manually reset watchdog
|
||||||
|
vTaskDelay(1); // Yield control to FreeRTOS
|
||||||
|
|
||||||
|
String line = messages[i];
|
||||||
|
line.trim();
|
||||||
|
if (line.length() == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int timeStart = line.indexOf("(") + 1;
|
||||||
|
int timeEnd = line.indexOf(")");
|
||||||
|
if (timeStart == 0 || timeEnd == -1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
float currentTimestamp = line.substring(timeStart, timeEnd).toFloat();
|
||||||
|
if (firstTimestamp < 0)
|
||||||
|
firstTimestamp = currentTimestamp;
|
||||||
|
|
||||||
|
if ((i > 0) && (currentTimestamp > lastTimestamp)) {
|
||||||
|
float deltaT = (currentTimestamp - lastTimestamp) * 1000;
|
||||||
|
vTaskDelay((int)deltaT / portTICK_PERIOD_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
lastTimestamp = currentTimestamp;
|
||||||
|
|
||||||
|
int interfaceStart = timeEnd + 2;
|
||||||
|
int interfaceEnd = line.indexOf(" ", interfaceStart);
|
||||||
|
if (interfaceEnd == -1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int idStart = interfaceEnd + 1;
|
||||||
|
int idEnd = line.indexOf(" [", idStart);
|
||||||
|
if (idStart == -1 || idEnd == -1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
String messageID = line.substring(idStart, idEnd);
|
||||||
|
int dlcStart = idEnd + 2;
|
||||||
|
int dlcEnd = line.indexOf("]", dlcStart);
|
||||||
|
if (dlcEnd == -1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
String dlc = line.substring(dlcStart, dlcEnd);
|
||||||
|
int dataStart = dlcEnd + 2;
|
||||||
|
String dataBytes = line.substring(dataStart);
|
||||||
|
|
||||||
|
currentFrame.ID = strtol(messageID.c_str(), NULL, 16);
|
||||||
|
currentFrame.DLC = dlc.toInt();
|
||||||
|
|
||||||
|
int byteIndex = 0;
|
||||||
|
char* token = strtok((char*)dataBytes.c_str(), " ");
|
||||||
|
while (token != NULL && byteIndex < currentFrame.DLC) {
|
||||||
|
currentFrame.data.u8[byteIndex++] = strtol(token, NULL, 16);
|
||||||
|
token = strtok(NULL, " ");
|
||||||
|
}
|
||||||
|
|
||||||
|
currentFrame.FD = (datalayer.system.info.can_replay_interface == CANFD_NATIVE) ||
|
||||||
|
(datalayer.system.info.can_replay_interface == CANFD_ADDON_MCP2518);
|
||||||
|
|
||||||
|
transmit_can_frame(¤tFrame, datalayer.system.info.can_replay_interface);
|
||||||
|
vTaskDelay(1); // Yield control after sending frame
|
||||||
|
}
|
||||||
|
} while (datalayer.system.info.loop_playback);
|
||||||
|
|
||||||
|
vTaskDelete(NULL); // Delete task when done
|
||||||
|
}
|
||||||
|
|
||||||
void init_webserver() {
|
void init_webserver() {
|
||||||
|
|
||||||
server.on("/logout", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(401); });
|
server.on("/logout", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(401); });
|
||||||
|
@ -101,102 +192,25 @@ void init_webserver() {
|
||||||
return request->requestAuthentication();
|
return request->requestAuthentication();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<String> messages;
|
if (request->hasParam("loop")) {
|
||||||
int lastIndex = 0;
|
datalayer.system.info.loop_playback = request->getParam("loop")->value().toInt() == 1;
|
||||||
while (true) {
|
|
||||||
int nextIndex = importedLogs.indexOf("\n", lastIndex);
|
|
||||||
if (nextIndex == -1) {
|
|
||||||
messages.push_back(importedLogs.substring(lastIndex)); // Add last message
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
messages.push_back(importedLogs.substring(lastIndex, nextIndex));
|
|
||||||
lastIndex = nextIndex + 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float firstTimestamp = -1.0;
|
// Start the replay task on Core 1
|
||||||
float lastTimestamp = 0.0;
|
xTaskCreatePinnedToCore(canReplayTask, "CAN_Replay", 8192, NULL, 1, NULL, 1);
|
||||||
|
|
||||||
for (size_t i = 0; i < messages.size(); i++) {
|
request->send(200, "text/plain", "CAN replay started!");
|
||||||
String line = messages[i];
|
});
|
||||||
line.trim(); // Remove leading/trailing spaces
|
|
||||||
|
|
||||||
if (line.length() == 0)
|
// Route for stopping the CAN replay
|
||||||
continue; // Skip empty lines
|
server.on("/stopReplay", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||||
|
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
|
||||||
// Extract timestamp
|
return request->requestAuthentication();
|
||||||
int timeStart = line.indexOf("(") + 1;
|
|
||||||
int timeEnd = line.indexOf(")");
|
|
||||||
if (timeStart == 0 || timeEnd == -1)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
float currentTimestamp = line.substring(timeStart, timeEnd).toFloat();
|
|
||||||
|
|
||||||
if (firstTimestamp < 0) {
|
|
||||||
firstTimestamp = currentTimestamp; // Store first message timestamp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate delay (skip for the first message, and incase the log is out of order)
|
datalayer.system.info.loop_playback = false;
|
||||||
if ((i > 0) && (currentTimestamp > lastTimestamp)) {
|
|
||||||
float deltaT = (currentTimestamp - lastTimestamp) * 1000; // Convert seconds to milliseconds
|
|
||||||
|
|
||||||
delay((int)deltaT); // Delay before sending this message
|
request->send(200, "text/plain", "CAN replay stopped!");
|
||||||
}
|
|
||||||
|
|
||||||
lastTimestamp = currentTimestamp;
|
|
||||||
|
|
||||||
// Find the first space after the timestamp to locate the interface (TX# or RX#)
|
|
||||||
int interfaceStart = timeEnd + 2; // Start after ") "
|
|
||||||
int interfaceEnd = line.indexOf(" ", interfaceStart);
|
|
||||||
if (interfaceEnd == -1)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
String canInterface = line.substring(interfaceStart, interfaceEnd); // Extract TX# or RX#
|
|
||||||
|
|
||||||
// Extract CAN ID
|
|
||||||
int idStart = interfaceEnd + 1;
|
|
||||||
int idEnd = line.indexOf(" [", idStart);
|
|
||||||
if (idStart == -1 || idEnd == -1)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
String messageID = line.substring(idStart, idEnd);
|
|
||||||
|
|
||||||
// Extract DLC
|
|
||||||
int dlcStart = idEnd + 2;
|
|
||||||
int dlcEnd = line.indexOf("]", dlcStart);
|
|
||||||
if (dlcEnd == -1)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
String dlc = line.substring(dlcStart, dlcEnd);
|
|
||||||
|
|
||||||
// Extract data bytes
|
|
||||||
int dataStart = dlcEnd + 2;
|
|
||||||
String dataBytes = line.substring(dataStart);
|
|
||||||
|
|
||||||
// Assign values to the CAN frame
|
|
||||||
currentFrame.ID = strtol(messageID.c_str(), NULL, 16);
|
|
||||||
currentFrame.DLC = dlc.toInt();
|
|
||||||
|
|
||||||
// Parse and store data bytes
|
|
||||||
int byteIndex = 0;
|
|
||||||
char* token = strtok((char*)dataBytes.c_str(), " ");
|
|
||||||
while (token != NULL && byteIndex < currentFrame.DLC) { // Use DLC instead of fixed 8
|
|
||||||
currentFrame.data.u8[byteIndex++] = strtol(token, NULL, 16);
|
|
||||||
token = strtok(NULL, " ");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply FD incase interface is set to FD
|
|
||||||
if ((datalayer.system.info.can_replay_interface == CANFD_NATIVE) ||
|
|
||||||
(datalayer.system.info.can_replay_interface == CANFD_ADDON_MCP2518)) {
|
|
||||||
currentFrame.FD = true;
|
|
||||||
} else {
|
|
||||||
currentFrame.FD = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transmit the CAN frame
|
|
||||||
transmit_can_frame(¤tFrame, datalayer.system.info.can_replay_interface);
|
|
||||||
}
|
|
||||||
|
|
||||||
request->send(200, "text/plain", "All CAN messages sent with correct interfaces!");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Route to handle setting the CAN interface for CAN replay
|
// Route to handle setting the CAN interface for CAN replay
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue