Save files appending on same session and new file on new session

This commit is contained in:
Emanuele Trabattoni
2026-04-07 15:53:52 +02:00
parent 668b590d7c
commit 877236ee4e
11 changed files with 1112 additions and 1043 deletions

View File

@@ -1,6 +1,8 @@
#include "datasave.h" #include "datasave.h"
void save_history(const PSRAMVector<ignitionBoxStatus> &history) static constexpr size_t min_free = 1024 * 1024; // minimum free space in SPIFFS to allow saving history (1MB)
void save_history(const PSRAMVector<ignitionBoxStatus> &history, const std::filesystem::path &file_path)
{ {
// Initialize SPIFFS // Initialize SPIFFS
if (!SPIFFS.begin(true)) if (!SPIFFS.begin(true))
@@ -10,13 +12,33 @@ void save_history(const PSRAMVector<ignitionBoxStatus> &history)
vTaskDelay(pdMS_TO_TICKS(5000)); vTaskDelay(pdMS_TO_TICKS(5000));
esp_restart(); esp_restart();
} }
else
{
LOG_INFO("SPIFFS mounted successfully"); LOG_INFO("SPIFFS mounted successfully");
if (SPIFFS.totalBytes() - SPIFFS.usedBytes() < min_free ) // check if at least 1MB is free for saving history
{
LOG_ERROR("Not enough space in SPIFFS to save history");
return; return;
} }
std::ofstream ofs("/spiffs/ignA_history.csv", std::ios::out | std::ios::trunc); std::filesystem::path to_save = file_path;
if (file_path.root_path() != "/spiffs")
to_save = std::filesystem::path("/spiffs") / file_path;
auto save_flags = std::ios::out;
static bool first_save = true;
if (first_save || !SPIFFS.exists(to_save.c_str()))
{
save_flags |= std::ios::trunc; // overwrite existing file
first_save = false;
LOG_INFO("Saving history to SPIFFS, new file: ", to_save.c_str());
}
else
{
save_flags |= std::ios::app; // append to new file
LOG_INFO("Saving history to SPIFFS, appending to existing file: ", to_save.c_str());
}
std::ofstream ofs(to_save, save_flags);
if (ofs.fail()) if (ofs.fail())
{ {
LOG_ERROR("Failed to open file for writing"); LOG_ERROR("Failed to open file for writing");
@@ -24,11 +46,15 @@ void save_history(const PSRAMVector<ignitionBoxStatus> &history)
} }
// write csv header // write csv header
if (first_save)
{
ofs << "TS,\ ofs << "TS,\
EVENTS_12,DLY_12,STAT_12,V_12_1,V_12_2,V_12_3,V_12_4,IGNITION_MODE_12,\ EVENTS_12,DLY_12,STAT_12,V_12_1,V_12_2,V_12_3,V_12_4,IGNITION_MODE_12,\
EVENTS_34,DLY_34,STAT_34,V_34_1,V_34_2,V_34_3,V_34_4,IGNITION_MODE_34,\ EVENTS_34,DLY_34,STAT_34,V_34_1,V_34_2,V_34_3,V_34_4,IGNITION_MODE_34,\
ENGINE_RPM,ADC_READTIME,N_QUEUE_ERRORS"; ENGINE_RPM,ADC_READTIME,N_QUEUE_ERRORS";
for (auto &entry : history) }
for (const auto &entry : history)
{ {
ofs << std::to_string(entry.timestamp) << "," ofs << std::to_string(entry.timestamp) << ","
<< std::to_string(entry.coils12.n_events) << "," << std::to_string(entry.coils12.n_events) << ","
@@ -52,9 +78,9 @@ void save_history(const PSRAMVector<ignitionBoxStatus> &history)
<< std::to_string(entry.n_queue_errors); << std::to_string(entry.n_queue_errors);
ofs << std::endl; ofs << std::endl;
} }
auto written_bytes = ofs.tellp();
ofs.flush(); ofs.flush();
ofs.close(); ofs.close();
LOG_INFO("Ignition A history saved to SPIFFS, bytes written: ", (uint32_t)written_bytes); LOG_INFO("Ignition A history saved to SPIFFS, records written: ", history.size());
SPIFFS.end(); // unmount SPIFFS to ensure data is written and avoid corruption on next mount SPIFFS.end(); // unmount SPIFFS to ensure data is written and avoid corruption on next mount
} }

View File

@@ -6,19 +6,34 @@
#include <SPIFFS.h> #include <SPIFFS.h>
#include <string> #include <string>
#include <fstream> #include <fstream>
#include <filesystem>
// Project Includes // Project Includes
#include "isr.h" #include "isr.h"
#include "psvector.h" #include "psvector.h"
const uint32_t max_history = 1024;
const bool SAVE_HISTORY_TO_SPIFFS = true; // Set to true to enable saving history to SPIFFS, false to disable const bool SAVE_HISTORY_TO_SPIFFS = true; // Set to true to enable saving history to SPIFFS, false to disable
struct dataSaveParams
{
PSRAMVector<ignitionBoxStatus> *history;
const std::filesystem::path file_path;
};
void save_history(const PSRAMVector<ignitionBoxStatus> &history, const std::filesystem::path& file_path);
static void saveHistoryTask(void *pvParameters) static void saveHistoryTask(void *pvParameters)
{ {
auto *history = static_cast<PSRAMVector<ignitionBoxStatus> *>(pvParameters); auto *params = static_cast<dataSaveParams *>(pvParameters);
save_history(*history); auto &history = *params->history;
vTaskDelete(NULL); auto &file_path = params->file_path;
if (!params) {
LOG_ERROR("Invalid parameters for saveHistoryTask");
return;
} }
void save_history(const PSRAMVector<ignitionBoxStatus> &history); save_history(history, file_path);
history.reserve(max_history); // ensure writable buffer has the correct size
vTaskDelete(NULL);
}

View File

@@ -0,0 +1,90 @@
#pragma once
#include <Arduino.h>
#include <map>
// =====================
// Event Flags (bitmask)
// =====================
static const uint32_t TRIG_FLAG_12P = (1 << 0);
static const uint32_t TRIG_FLAG_12N = (1 << 1);
static const uint32_t TRIG_FLAG_34P = (1 << 2);
static const uint32_t TRIG_FLAG_34N = (1 << 3);
static const uint32_t SPARK_FLAG_TIMEOUT = (1 << 8);
static const uint32_t SPARK_FLAG_12 = (1 << 9);
static const uint32_t SPARK_FLAG_34 = (1 << 10);
// Spark Status
enum sparkStatus
{
SPARK_POS_OK,
SPARK_NEG_OK,
SPARK_POS_SKIP,
SPARK_NEG_SKIP,
SPARK_POS_WAIT,
SPARK_NEG_WAIT,
SPARK_POS_FAIL,
SPARK_NEG_FAIL,
SPARK_POS_UNEXPECTED,
SPARK_NEG_UNEXPECTED,
SPARK_SYNC_FAIL,
};
static const std::map<const sparkStatus, const char *> sparkStatusNames = {
{SPARK_POS_OK, "SPARK_POS_OK"},
{SPARK_NEG_OK, "SPARK_NEG_OK"},
{SPARK_POS_SKIP, "SPARK_POS_SKIP"},
{SPARK_NEG_SKIP, "SPARK_NEG_SKIP"},
{SPARK_POS_WAIT, "SPARK_POS_WAIT"},
{SPARK_NEG_WAIT, "SPARK_NEG_WAIT"},
{SPARK_POS_FAIL, "SPARK_POS_FAIL"},
{SPARK_NEG_FAIL, "SPARK_NEG_FAIL"},
{SPARK_POS_UNEXPECTED, "SPARK_POS_UNEXPECTED"},
{SPARK_NEG_UNEXPECTED, "SPARK_NEG_UNEXPECTED"},
{SPARK_SYNC_FAIL, "SPARK_SYNC_FAIL"},
};
enum softStartStatus
{
NORMAL,
SOFT_START,
ERROR,
};
const std::map<const softStartStatus, const char *> softStartStatusNames = {
{NORMAL, "NORMAL"},
{SOFT_START, "SOFT_START"},
{ERROR, "ERROR"},
};
struct coilsStatus
{
int64_t trig_time = 0;
int64_t spark_time = 0;
uint32_t spark_delay = 0; // in microseconds
sparkStatus spark_status = sparkStatus::SPARK_POS_OK;
softStartStatus sstart_status = softStartStatus::NORMAL;
float peak_p_in = 0.0;
float peak_n_in = 0.0;
float peak_p_out = 0.0;
float peak_n_out = 0.0;
float level_spark = 0.0;
uint32_t n_events = 0;
uint32_t n_missed_firing = 0;
};
// Task internal Status
struct ignitionBoxStatus
{
int64_t timestamp = 0;
// coils pairs for each ignition
coilsStatus coils12;
coilsStatus coils34;
// voltage from generator
float volts_gen = 0.0;
// enine rpm
uint32_t eng_rpm = 0;
// debug values
uint32_t n_queue_errors = 0;
uint32_t adc_read_time = 0;
};

View File

@@ -12,98 +12,13 @@
#else #else
#include "pins_test.h" #include "pins_test.h"
#endif #endif
#include "datastruct.h"
#define CORE_0 0 #define CORE_0 0
#define CORE_1 1 #define CORE_1 1
#define RT_TASK_STACK 4096 // in words #define RT_TASK_STACK 4096 // in words
#define RT_TASK_PRIORITY (configMAX_PRIORITIES - 4) // highest priority after wifi tasks #define RT_TASK_PRIORITY (configMAX_PRIORITIES - 4) // highest priority after wifi tasks
// =====================
// Event Flags (bitmask)
// =====================
static const uint32_t TRIG_FLAG_12P = (1 << 0);
static const uint32_t TRIG_FLAG_12N = (1 << 1);
static const uint32_t TRIG_FLAG_34P = (1 << 2);
static const uint32_t TRIG_FLAG_34N = (1 << 3);
static const uint32_t SPARK_FLAG_TIMEOUT = (1 << 8);
static const uint32_t SPARK_FLAG_12 = (1 << 9);
static const uint32_t SPARK_FLAG_34 = (1 << 10);
// Spark Status
enum sparkStatus
{
SPARK_POS_OK,
SPARK_NEG_OK,
SPARK_POS_SKIP,
SPARK_NEG_SKIP,
SPARK_POS_WAIT,
SPARK_NEG_WAIT,
SPARK_POS_FAIL,
SPARK_NEG_FAIL,
SPARK_POS_UNEXPECTED,
SPARK_NEG_UNEXPECTED,
SPARK_SYNC_FAIL,
};
static const std::map<const sparkStatus, const char *> sparkStatusNames = {
{SPARK_POS_OK, "SPARK_POS_OK"},
{SPARK_NEG_OK, "SPARK_NEG_OK"},
{SPARK_POS_SKIP, "SPARK_POS_SKIP"},
{SPARK_NEG_SKIP, "SPARK_NEG_SKIP"},
{SPARK_POS_WAIT, "SPARK_POS_WAIT"},
{SPARK_NEG_WAIT, "SPARK_NEG_WAIT"},
{SPARK_POS_FAIL, "SPARK_POS_FAIL"},
{SPARK_NEG_FAIL, "SPARK_NEG_FAIL"},
{SPARK_POS_UNEXPECTED, "SPARK_POS_UNEXPECTED"},
{SPARK_NEG_UNEXPECTED, "SPARK_NEG_UNEXPECTED"},
{SPARK_SYNC_FAIL, "SPARK_SYNC_FAIL"},
};
enum softStartStatus
{
NORMAL,
SOFT_START,
ERROR,
};
const std::map<const softStartStatus, const char *> softStartStatusNames = {
{NORMAL, "NORMAL"},
{SOFT_START, "SOFT_START"},
{ERROR, "ERROR"},
};
struct coilsStatus
{
int64_t trig_time = 0;
int64_t spark_time = 0;
uint32_t spark_delay = 0; // in microseconds
sparkStatus spark_status = sparkStatus::SPARK_POS_OK;
softStartStatus sstart_status = softStartStatus::NORMAL;
float peak_p_in = 0.0;
float peak_n_in = 0.0;
float peak_p_out = 0.0;
float peak_n_out = 0.0;
float level_spark = 0.0;
uint32_t n_events = 0;
};
// Task internal Status
struct ignitionBoxStatus
{
int64_t timestamp = 0;
// coils pairs for each ignition
coilsStatus coils12;
coilsStatus coils34;
// voltage from generator
float volts_gen = 0.0;
// enine rpm
uint32_t eng_rpm = 0;
// debug values
uint32_t n_queue_errors = 0;
uint32_t adc_read_time = 0;
};
struct isrParams struct isrParams
{ {
const uint32_t flag; const uint32_t flag;

View File

@@ -9,7 +9,6 @@
// Definitions // Definitions
#include <tasks.h> #include <tasks.h>
#include <devices.h> #include <devices.h>
#include <psvector.h>
#include <datasave.h> #include <datasave.h>
#include <ui.h> #include <ui.h>
@@ -51,13 +50,12 @@ void loop()
{ {
// global variables // global variables
bool running = true; bool running = true;
const uint32_t max_history = 1024;
const uint32_t max_queue = 128;
const uint32_t max_queue = 128;
PSRAMVector<ignitionBoxStatus> ignA_history_0(max_history); PSRAMVector<ignitionBoxStatus> ignA_history_0(max_history);
PSRAMVector<ignitionBoxStatus> ignA_history_1(max_history); PSRAMVector<ignitionBoxStatus> ignA_history_1(max_history);
auto &active_history = ignA_history_0; auto *active_history = &ignA_history_0;
auto &writable_history = ignA_history_1; auto *writable_history = &ignA_history_1;
// Resources Initialization // Resources Initialization
static Devices dev; static Devices dev;
@@ -177,71 +175,50 @@ void loop()
////////////////////// MAIN LOOP ////////////////////// ////////////////////// MAIN LOOP //////////////////////
clearScreen(); clearScreen();
setCursor(0, 0); setCursor(0, 0);
bool partial_save = false; // flag to indicate if a partial save has been done after a timeout
uint32_t counter = 0; uint32_t counter = 0;
ignitionBoxStatus ignA; ignitionBoxStatus ign_info;
int64_t last = esp_timer_get_time(); int64_t last = esp_timer_get_time();
uint32_t missed_firings12 = 0; uint32_t missed_firings12 = 0;
uint32_t missed_firings34 = 0; uint32_t missed_firings34 = 0;
while (running) while (running)
{ {
if (counter == active_history.size()) if (counter >= active_history->size())
{ {
counter = 0; counter = 0;
partial_save = false; // reset partial save flag on new data cycle
std::swap(active_history, writable_history); // switch active and writable buffers std::swap(active_history, writable_history); // switch active and writable buffers
dataSaveParams save_params{
.history = writable_history,
.file_path = "ignition_history.csv"
};
if (SAVE_HISTORY_TO_SPIFFS) if (SAVE_HISTORY_TO_SPIFFS)
xTaskCreate( xTaskCreate(
saveHistoryTask, saveHistoryTask,
"saveHistoryTask", "saveHistoryTask",
RT_TASK_STACK / 2, RT_TASK_STACK / 2,
&writable_history, &save_params,
RT_TASK_PRIORITY + 1, // higher priority to ensure it runs asap after buffer switch RT_TASK_PRIORITY - 10, // higher priority to ensure it runs asap after buffer switch
NULL); NULL);
} }
if (xQueueReceive(rt_taskA_queue, &ignA, pdMS_TO_TICKS(1000)) == pdTRUE) if (xQueueReceive(rt_taskA_queue, &ign_info, pdMS_TO_TICKS(1000)) == pdTRUE)
{ {
if (ignA.coils12.spark_status == sparkStatus::SPARK_POS_FAIL || ignA.coils12.spark_status == sparkStatus::SPARK_NEG_FAIL) printInfo(ign_info);
missed_firings12++; auto &hist = *active_history;
if (ignA.coils34.spark_status == sparkStatus::SPARK_POS_FAIL || ignA.coils34.spark_status == sparkStatus::SPARK_NEG_FAIL) hist[counter++ % active_history->size()] = ign_info;
missed_firings34++;
clearScreen();
setCursor(0, 0);
printField("++ Timestamp", (uint32_t)ignA.timestamp);
Serial.println("========== Coils 12 =============");
printField("Events", ignA.coils12.n_events);
printField("Missed Firing", missed_firings12);
printField("Spark Dly", (uint32_t)ignA.coils12.spark_delay);
printField("Spark Sts", sparkStatusNames.at(ignA.coils12.spark_status));
printField("Peak P_IN", ignA.coils12.peak_p_in);
printField("Peak N_IN", ignA.coils12.peak_n_in);
printField("Peak P_OUT", ignA.coils12.peak_p_out);
printField("Peak N_OUT", ignA.coils12.peak_n_out);
printField("Soft Start ", softStartStatusNames.at(ignA.coils12.sstart_status));
Serial.println("========== Coils 34 =============");
printField("Events", ignA.coils34.n_events);
printField("Missed Firing", missed_firings34);
printField("Spark Dly", (uint32_t)ignA.coils34.spark_delay);
printField("Spark Sts", sparkStatusNames.at(ignA.coils34.spark_status));
printField("Peak P_IN", ignA.coils34.peak_p_in);
printField("Peak N_IN", ignA.coils34.peak_n_in);
printField("Peak P_OUT", ignA.coils34.peak_p_out);
printField("Peak N_OUT", ignA.coils34.peak_n_out);
printField("Soft Start ", softStartStatusNames.at(ignA.coils34.sstart_status));
Serial.println("========== END =============");
Serial.println();
printField("Engine RPM", ignA.eng_rpm);
printField("ADC Read Time", ignA.adc_read_time);
printField("Queue Errors", ignA.n_queue_errors);
active_history[counter++ % active_history.size()] = ignA;
} }
else else
{ {
Serial.println("Waiting for data... "); Serial.println("Waiting for data... ");
if (!partial_save && counter > 0) // if timeout occurs but we have unsaved data, save it before next timeout
{
counter = 0; // reset counter after saving
partial_save = true;
Serial.println("Saving history to SPIFFS...");
save_history(*active_history, "ignition_history.csv");
}
delay(500); delay(500);
} }
} }

View File

@@ -230,6 +230,11 @@ void rtIgnitionTask(void *pvParameters)
cycle12 = false; cycle12 = false;
cycle34 = false; cycle34 = false;
if (ign_box_sts.coils12.spark_status == sparkStatus::SPARK_POS_FAIL || ign_box_sts.coils12.spark_status == sparkStatus::SPARK_NEG_FAIL)
ign_box_sts.coils12.n_missed_firing++;
if (ign_box_sts.coils34.spark_status == sparkStatus::SPARK_POS_FAIL || ign_box_sts.coils34.spark_status == sparkStatus::SPARK_NEG_FAIL)
ign_box_sts.coils34.n_missed_firing++;
// read adc channels: pickup12, out12 [ pos + neg ] // read adc channels: pickup12, out12 [ pos + neg ]
if (adc) // read only if adc initialized if (adc) // read only if adc initialized
{ {

68
RotaxMonitor/src/ui.cpp Normal file
View File

@@ -0,0 +1,68 @@
#include <ui.h>
void clearScreen()
{
Serial.print("\033[2J"); // clear screen
Serial.print("\033[H"); // cursor home
Serial.flush();
}
void setCursor(const uint8_t x, const uint8_t y)
{
Serial.printf("\033[%d;%d", y, x + 1);
Serial.flush();
}
void printField(const char name[], const uint32_t val)
{
Serial.printf("%15s: %06d\n", name, val);
}
void printField(const char name[], const int64_t val)
{
Serial.printf("%15s: %06u\n", name, (uint64_t)val);
}
void printField(const char name[], const float val)
{
Serial.printf("%15s: %4.2f\n", name, val);
}
void printField(const char name[], const char *val)
{
Serial.printf("%15s: %s\n", name, val);
}
void printInfo(const ignitionBoxStatus &info)
{
clearScreen();
setCursor(0, 0);
printField("++ Timestamp ++", (uint32_t)info.timestamp);
Serial.println("========== Coils 12 =============");
printField("Events", info.coils12.n_events);
printField("Events Missed", info.coils12.n_missed_firing);
printField("Spark Dly", (uint32_t)info.coils12.spark_delay);
printField("Spark Sts", sparkStatusNames.at(info.coils12.spark_status));
printField("Peak P_IN", info.coils12.peak_p_in);
printField("Peak N_IN", info.coils12.peak_n_in);
printField("Peak P_OUT", info.coils12.peak_p_out);
printField("Peak N_OUT", info.coils12.peak_n_out);
printField("Soft Start ", softStartStatusNames.at(info.coils12.sstart_status));
Serial.println("========== Coils 34 =============");
printField("Events", info.coils34.n_events);
printField("Events Missed", info.coils34.n_missed_firing);
printField("Spark Dly", (uint32_t)info.coils34.spark_delay);
printField("Spark Sts", sparkStatusNames.at(info.coils34.spark_status));
printField("Peak P_IN", info.coils34.peak_p_in);
printField("Peak N_IN", info.coils34.peak_n_in);
printField("Peak P_OUT", info.coils34.peak_p_out);
printField("Peak N_OUT", info.coils34.peak_n_out);
printField("Soft Start ", softStartStatusNames.at(info.coils34.sstart_status));
Serial.println("============ END ===============");
Serial.println();
printField("Engine RPM", info.eng_rpm);
printField("ADC Read Time", info.adc_read_time);
printField("Queue Errors", info.n_queue_errors);
}

View File

@@ -1,36 +1,13 @@
#pragma once #pragma once
#include <Arduino.h> #include <Arduino.h>
#include <datastruct.h>
void clearScreen() void clearScreen();
{ void setCursor(const uint8_t x, const uint8_t y);
Serial.print("\033[2J"); // clear screen void printField(const char name[], const uint32_t val);
Serial.print("\033[H"); // cursor home void printField(const char name[], const int64_t val);
Serial.flush(); void printField(const char name[], const float val);
} void printField(const char name[], const char *val);
void setCursor(const uint8_t x, const uint8_t y) void printInfo(const ignitionBoxStatus &info);
{
Serial.printf("\033[%d;%d", y, x + 1);
Serial.flush();
}
void printField(const char name[], const uint32_t val)
{
Serial.printf("%15s: %06d\n", name, val);
}
void printField(const char name[], const int64_t val)
{
Serial.printf("%15s: %06u\n", name, (uint64_t)val);
}
void printField(const char name[], const float val)
{
Serial.printf("%15s: %4.2f\n", name, val);
}
void printField(const char name[], const char *val)
{
Serial.printf("%15s: %s\n", name, val);
}

View File

@@ -4,10 +4,3 @@
#include <string> #include <string>
std::string printBits(uint32_t value); std::string printBits(uint32_t value);
static void saveHistoryTask(void *pvParameters)
{
auto *history = static_cast<PSRAMVector<ignitionBoxStatus> *>(pvParameters);
save_history(*history);
vTaskDelete(NULL);
}

File diff suppressed because it is too large Load Diff