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"
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
if (!SPIFFS.begin(true))
@@ -10,25 +12,49 @@ void save_history(const PSRAMVector<ignitionBoxStatus> &history)
vTaskDelay(pdMS_TO_TICKS(5000));
esp_restart();
}
else
LOG_INFO("SPIFFS mounted successfully");
if (SPIFFS.totalBytes() - SPIFFS.usedBytes() < min_free ) // check if at least 1MB is free for saving history
{
LOG_INFO("SPIFFS mounted successfully");
LOG_ERROR("Not enough space in SPIFFS to save history");
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())
{
LOG_ERROR("Failed to open file for writing");
return;
}
//write csv header
ofs << "TS,\
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,\
ENGINE_RPM,ADC_READTIME,N_QUEUE_ERRORS";
for (auto &entry : history)
// write csv header
if (first_save)
{
ofs << "TS,\
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,\
ENGINE_RPM,ADC_READTIME,N_QUEUE_ERRORS";
}
for (const auto &entry : history)
{
ofs << std::to_string(entry.timestamp) << ","
<< 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);
ofs << std::endl;
}
auto written_bytes = ofs.tellp();
ofs.flush();
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
}

View File

@@ -6,19 +6,34 @@
#include <SPIFFS.h>
#include <string>
#include <fstream>
#include <filesystem>
// Project Includes
#include "isr.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
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)
{
auto *history = static_cast<PSRAMVector<ignitionBoxStatus> *>(pvParameters);
save_history(*history);
auto *params = static_cast<dataSaveParams *>(pvParameters);
auto &history = *params->history;
auto &file_path = params->file_path;
if (!params) {
LOG_ERROR("Invalid parameters for saveHistoryTask");
return;
}
save_history(history, file_path);
history.reserve(max_history); // ensure writable buffer has the correct size
vTaskDelete(NULL);
}
void save_history(const PSRAMVector<ignitionBoxStatus> &history);

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
#include "pins_test.h"
#endif
#include "datastruct.h"
#define CORE_0 0
#define CORE_1 1
#define RT_TASK_STACK 4096 // in words
#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
{
const uint32_t flag;

View File

@@ -9,7 +9,6 @@
// Definitions
#include <tasks.h>
#include <devices.h>
#include <psvector.h>
#include <datasave.h>
#include <ui.h>
@@ -51,13 +50,12 @@ void loop()
{
// global variables
bool running = true;
const uint32_t max_history = 1024;
const uint32_t max_queue = 128;
PSRAMVector<ignitionBoxStatus> ignA_history_0(max_history);
PSRAMVector<ignitionBoxStatus> ignA_history_1(max_history);
auto &active_history = ignA_history_0;
auto &writable_history = ignA_history_1;
auto *active_history = &ignA_history_0;
auto *writable_history = &ignA_history_1;
// Resources Initialization
static Devices dev;
@@ -177,71 +175,50 @@ void loop()
////////////////////// MAIN LOOP //////////////////////
clearScreen();
setCursor(0, 0);
bool partial_save = false; // flag to indicate if a partial save has been done after a timeout
uint32_t counter = 0;
ignitionBoxStatus ignA;
ignitionBoxStatus ign_info;
int64_t last = esp_timer_get_time();
uint32_t missed_firings12 = 0;
uint32_t missed_firings34 = 0;
while (running)
{
if (counter == active_history.size())
if (counter >= active_history->size())
{
counter = 0;
partial_save = false; // reset partial save flag on new data cycle
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)
xTaskCreate(
saveHistoryTask,
"saveHistoryTask",
RT_TASK_STACK / 2,
&writable_history,
RT_TASK_PRIORITY + 1, // higher priority to ensure it runs asap after buffer switch
&save_params,
RT_TASK_PRIORITY - 10, // higher priority to ensure it runs asap after buffer switch
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)
missed_firings12++;
if (ignA.coils34.spark_status == sparkStatus::SPARK_POS_FAIL || ignA.coils34.spark_status == sparkStatus::SPARK_NEG_FAIL)
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;
printInfo(ign_info);
auto &hist = *active_history;
hist[counter++ % active_history->size()] = ign_info;
}
else
{
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);
}
}

View File

@@ -230,6 +230,11 @@ void rtIgnitionTask(void *pvParameters)
cycle12 = 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 ]
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
#include <Arduino.h>
#include <datastruct.h>
void clearScreen()
{
Serial.print("\033[2J"); // clear screen
Serial.print("\033[H"); // cursor home
Serial.flush();
}
void clearScreen();
void setCursor(const uint8_t x, const uint8_t y);
void printField(const char name[], const uint32_t val);
void printField(const char name[], const int64_t val);
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)
{
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);

View File

@@ -4,10 +4,3 @@
#include <string>
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