Make playback work if order is wrong

This commit is contained in:
Daniel Öster 2025-03-01 18:51:52 +02:00
parent e057df3345
commit 8c44da7070
3 changed files with 148 additions and 139 deletions

View file

@ -41,7 +41,7 @@
//#define TESLA_MODEL_SX_BATTERY
//#define VOLVO_SPA_BATTERY
//#define VOLVO_SPA_HYBRID_BATTERY
#define TEST_FAKE_BATTERY
//#define TEST_FAKE_BATTERY
//#define DOUBLE_BATTERY //Enable this line if you use two identical batteries at the same time (requires CAN_ADDON setup)
//#define SERIAL_LINK_TRANSMITTER //Enable this line to send battery data over RS485 pins to another Lilygo (This LilyGo interfaces with battery)
@ -67,7 +67,7 @@
//#define SERIAL_LINK_RECEIVER //Enable this line to receive battery data over RS485 pins from another Lilygo (This LilyGo interfaces with inverter)
/* Select hardware used for Battery-Emulator */
#define HW_LILYGO
//#define HW_LILYGO
//#define HW_STARK
//#define HW_3LB
//#define HW_DEVKIT
@ -81,7 +81,7 @@
//#define PERIODIC_BMS_RESET //Enable to have the emulator powercycle the connected battery every 24hours via GPIO. Useful for some batteries like Nissan LEAF
//#define REMOTE_BMS_RESET //Enable to allow the emulator to remotely trigger a powercycle of the battery via MQTT. Useful for some batteries like Nissan LEAF
// PERIODIC_BMS_RESET_AT Uses NTP server, internet required. In 24 Hour format WITHOUT leading 0. e.g 0230 should be 230. Time Zone is set in USER_SETTINGS.cpp
#define PERIODIC_BMS_RESET_AT 525
//#define PERIODIC_BMS_RESET_AT 525
/* Shunt/Contactor settings (Optional) */
//#define BMW_SBOX // SBOX relay control & battery current/voltage measurement

View file

@ -34,11 +34,9 @@ String can_replay_processor(void) {
content += "<label for='canInterface'>CAN Interface:</label>";
content += "<select id='canInterface' name='canInterface'>";
content += "<option value='" + String(CAN_NATIVE) + "' " +
(datalayer.system.info.can_replay_interface == CAN_NATIVE ? "selected" : "") +
">CAN Native</option>";
(datalayer.system.info.can_replay_interface == CAN_NATIVE ? "selected" : "") + ">CAN Native</option>";
content += "<option value='" + String(CANFD_NATIVE) + "' " +
(datalayer.system.info.can_replay_interface == CANFD_NATIVE ? "selected" : "") +
">CANFD Native</option>";
(datalayer.system.info.can_replay_interface == CANFD_NATIVE ? "selected" : "") + ">CANFD Native</option>";
content += "<option value='" + String(CAN_ADDON_MCP2515) + "' " +
(datalayer.system.info.can_replay_interface == CAN_ADDON_MCP2515 ? "selected" : "") +
">CAN Addon MCP2515</option>";
@ -83,9 +81,13 @@ content += "const progressBar = document.getElementById('progress-bar');";
content += "const progressContainer = document.getElementById('progress');";
content += "let selectedFile = null;";
content += "dropArea.addEventListener('dragover', (e) => { e.preventDefault(); dropArea.style.background = '#f0f0f0'; });";
content +=
"dropArea.addEventListener('dragover', (e) => { e.preventDefault(); dropArea.style.background = '#f0f0f0'; });";
content += "dropArea.addEventListener('dragleave', () => { dropArea.style.background = 'white'; });";
content += "dropArea.addEventListener('drop', (e) => { e.preventDefault(); dropArea.style.background = 'white'; if (e.dataTransfer.files.length > 0) { fileInput.files = e.dataTransfer.files; selectedFile = fileInput.files[0]; }});";
content +=
"dropArea.addEventListener('drop', (e) => { e.preventDefault(); dropArea.style.background = 'white'; if "
"(e.dataTransfer.files.length > 0) { fileInput.files = e.dataTransfer.files; selectedFile = fileInput.files[0]; "
"}});";
content += "fileInput.addEventListener('change', () => { selectedFile = fileInput.files[0]; });";
@ -95,8 +97,13 @@ content += "const formData = new FormData();";
content += "formData.append('file', selectedFile);";
content += "const xhr = new XMLHttpRequest();";
content += "xhr.open('POST', '/import_can_log', true);";
content += "xhr.upload.onprogress = (event) => { if (event.lengthComputable) { const percent = (event.loaded / event.total) * 100; progressContainer.style.display = 'block'; progressBar.style.width = percent + '%'; }};";
content += "xhr.onload = () => { if (xhr.status === 200) { alert('File uploaded successfully!'); progressBar.style.width = '100%'; const reader = new FileReader(); reader.onload = function (e) { fileContent.textContent = e.target.result; }; reader.readAsText(selectedFile); } else { alert('Upload failed! Server error.'); }};";
content +=
"xhr.upload.onprogress = (event) => { if (event.lengthComputable) { const percent = (event.loaded / event.total) "
"* 100; progressContainer.style.display = 'block'; progressBar.style.width = percent + '%'; }};";
content +=
"xhr.onload = () => { if (xhr.status === 200) { alert('File uploaded successfully!'); progressBar.style.width = "
"'100%'; const reader = new FileReader(); reader.onload = function (e) { fileContent.textContent = "
"e.target.result; }; reader.readAsText(selectedFile); } else { alert('Upload failed! Server error.'); }};";
content += "xhr.send(formData);";
content += "});";
content += "</script>";

View file

@ -38,20 +38,18 @@ CAN_frame currentFrame = {.FD = false,
.ID = 0x12F,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
void handleFileUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
void handleFileUpload(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len,
bool final) {
if (!index) {
importedLogs = ""; // Clear previous logs
Serial.printf("Receiving file: %s\n", filename.c_str());
logging.printf("Receiving file: %s\n", filename.c_str());
}
// Append received data to the string (RAM storage)
importedLogs += String((char*)data).substring(0, len);
if (final) {
Serial.println("Upload Complete!");
Serial.println("Imported Log Data:");
Serial.println(importedLogs); // Display contents for debugging (TODO: Remove these prints when feature works)
//datalayer.system.info.logged_can_messages = importedLogs;
logging.println("Upload Complete!");
request->send(200, "text/plain", "File uploaded successfully");
}
}
@ -126,12 +124,14 @@ server.on("/startReplay", HTTP_GET, [](AsyncWebServerRequest* request) {
String line = messages[i];
line.trim(); // Remove leading/trailing spaces
if (line.length() == 0) continue; // Skip empty lines
if (line.length() == 0)
continue; // Skip empty lines
// Extract timestamp
int timeStart = line.indexOf("(") + 1;
int timeEnd = line.indexOf(")");
if (timeStart == 0 || timeEnd == -1) continue;
if (timeStart == 0 || timeEnd == -1)
continue;
float currentTimestamp = line.substring(timeStart, timeEnd).toFloat();
@ -139,9 +139,10 @@ server.on("/startReplay", HTTP_GET, [](AsyncWebServerRequest* request) {
firstTimestamp = currentTimestamp; // Store first message timestamp
}
// Calculate delay (skip for the first message)
if (i > 0) {
// Calculate delay (skip for the first message, and incase the log is out of order)
if ((i > 0) && (currentTimestamp > lastTimestamp)) {
float deltaT = (currentTimestamp - lastTimestamp) * 1000; // Convert seconds to milliseconds
delay((int)deltaT); // Delay before sending this message
}
@ -150,21 +151,24 @@ server.on("/startReplay", HTTP_GET, [](AsyncWebServerRequest* request) {
// 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;
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;
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;
if (dlcEnd == -1)
continue;
String dlc = line.substring(dlcStart, dlcEnd);
@ -191,8 +195,6 @@ server.on("/startReplay", HTTP_GET, [](AsyncWebServerRequest* request) {
request->send(200, "text/plain", "All CAN messages sent with correct interfaces!");
});
// Route to handle setting the CAN interface for CAN replay
server.on("/setCANInterface", HTTP_GET, [](AsyncWebServerRequest* request) {
if (request->hasParam("interface")) {
@ -226,12 +228,12 @@ server.on("/startReplay", HTTP_GET, [](AsyncWebServerRequest* request) {
});
// Define the handler to import can log
server.on("/import_can_log", HTTP_POST,[](AsyncWebServerRequest *request) {
server.on(
"/import_can_log", HTTP_POST,
[](AsyncWebServerRequest* request) {
request->send(200, "text/plain", "Ready to receive file."); // Response when request is made
},
handleFileUpload
);
handleFileUpload);
#ifndef LOG_CAN_TO_SD
// Define the handler to export can log