LittleFS mount OK, updated interface, upload to littlefs from browser

This commit is contained in:
Emanuele Trabattoni
2026-04-09 13:41:50 +02:00
parent de9ffe40e5
commit 1e068476af
12 changed files with 686 additions and 72 deletions

View File

@@ -1,7 +1,26 @@
#include "datasave.h"
#include <math.h>
static const size_t min_free = 1024 * 1024; // minimum free space in SPIFFS to allow saving history (1MB)
static const size_t min_free = 1024 * 1024; // minimum free space in LittleFS to allow saving history (1MB)
LITTLEFSGuard::LITTLEFSGuard()
{
if (!LittleFS.begin(true, "/littlefs", 10, "littlefs"))
{
LOG_ERROR("Failed to mount LittleFS");
}
else
{
LOG_INFO("LittleFS mounted successfully");
LOG_INFO("LittleFS Free KBytes:", (LittleFS.totalBytes() - LittleFS.usedBytes()) /1024);
}
}
LITTLEFSGuard::~LITTLEFSGuard()
{
LittleFS.end();
LOG_INFO("LittleFS unmounted successfully");
}
void ignitionBoxStatusAverage::filter(int32_t &old, const int32_t value, const uint32_t k)
{
@@ -42,18 +61,18 @@ void ignitionBoxStatusAverage::update(const ignitionBoxStatus &new_status)
filter(m_last.coils12.peak_p_out, new_status.coils12.peak_p_out, m_max_count); // incremental average calculation
filter(m_last.coils12.peak_n_out, new_status.coils12.peak_n_out, m_max_count); // incremental average calculation
m_last.coils34.n_events = new_status.coils34.n_events; // sum events instead of averaging
m_last.coils34.n_missed_firing = new_status.coils34.n_missed_firing; // sum missed firings instead of averaging
m_last.coils34.spark_status = new_status.coils34.spark_status; // take latest spark status
m_last.coils34.sstart_status = new_status.coils34.sstart_status; // take latest soft start status
filter(m_last.coils34.spark_delay, new_status.coils34.spark_delay, m_max_count); // incremental average calculation
m_last.coils34.n_events = new_status.coils34.n_events; // sum events instead of averaging
m_last.coils34.n_missed_firing = new_status.coils34.n_missed_firing; // sum missed firings instead of averaging
m_last.coils34.spark_status = new_status.coils34.spark_status; // take latest spark status
m_last.coils34.sstart_status = new_status.coils34.sstart_status; // take latest soft start status
filter(m_last.coils34.spark_delay, new_status.coils34.spark_delay, m_max_count); // incremental average calculation
filter(m_last.coils34.peak_p_in, new_status.coils34.peak_p_in, m_max_count); // incremental average calculation
filter(m_last.coils34.peak_n_in, new_status.coils34.peak_n_in, m_max_count); // incremental average calculation
filter(m_last.coils34.peak_p_out, new_status.coils34.peak_p_out, m_max_count); // incremental average calculation
filter(m_last.coils34.peak_n_out, new_status.coils34.peak_n_out, m_max_count); // incremental average calculation
filter(m_last.eng_rpm, new_status.eng_rpm, m_max_count); // incremental average calculation // incremental average calculation
filter(m_last.adc_read_time, m_last.adc_read_time, m_max_count); // incremental average calculation
m_last.n_queue_errors = new_status.n_queue_errors; // take last of queue errors since it's a cumulative count of errors in the queue, not an average value
filter(m_last.eng_rpm, new_status.eng_rpm, m_max_count); // incremental average calculation // incremental average calculation
filter(m_last.adc_read_time, m_last.adc_read_time, m_max_count); // incremental average calculation
m_last.n_queue_errors = new_status.n_queue_errors; // take last of queue errors since it's a cumulative count of errors in the queue, not an average value
if (m_count >= m_max_count)
{
@@ -126,7 +145,8 @@ void save_history(const PSRAMVector<ignitionBoxStatus> &history, const std::file
// Initialize SPIFFS
if (!SAVE_HISTORY_TO_LITTLEFS)
return;
// auto spiffs_guard = LITTLEFSGuard(); // use RAII guard to ensure SPIFFS is properly mounted and unmounted
auto littlefs_guard = LITTLEFSGuard(); // use RAII guard to ensure LittleFS is properly mounted and unmounted
if (LittleFS.totalBytes() - LittleFS.usedBytes() < min_free) // check if at least 1MB is free for saving history
{
@@ -142,7 +162,7 @@ void save_history(const PSRAMVector<ignitionBoxStatus> &history, const std::file
if (first_save && LittleFS.exists(file_path.c_str()))
{
first_save = false;
save_flags |= std::ios::trunc; // overwrite existing file
save_flags |= std::ios::trunc; // overwrite existing file
LittleFS.remove(file_path.c_str()); // ensure file is removed before saving to avoid issues with appending to existing file in SPIFFS
LOG_INFO("Saving history to LittleFS, new file:", file_path.c_str());
}

View File

@@ -4,19 +4,19 @@
// System Includes
#include <Arduino.h>
#include <DebugLog.h>
#include <LittleFS.h>
#include <string>
#include <fstream>
#include <filesystem>
#include <ArduinoJson.h>
#include <filesystem>
#include <LittleFS.h>
// Project Includes
#include "isr.h"
#include "psvector.h"
const uint32_t max_history = 256;
const bool SAVE_HISTORY_TO_LITTLEFS = false; // Set to true to enable saving history to SPIFFS, false to disable
static bool first_save = true; // flag to indicate if this is the first save (to write header)
const bool SAVE_HISTORY_TO_LITTLEFS = false; // Set to true to enable saving history to LittleFS, false to disable
static bool first_save = true; // flag to indicate if this is the first save (to write header)
struct dataSaveParams
{
@@ -27,20 +27,8 @@ struct dataSaveParams
class LITTLEFSGuard
{
public:
LITTLEFSGuard()
{
if (!LittleFS.begin(true))
{
LOG_ERROR("Failed to mount LittleFS");
}
LOG_INFO("SPIFFS mounted successfully");
}
~LITTLEFSGuard()
{
LittleFS.end();
LOG_INFO("LittleFS unmounted successfully");
}
LITTLEFSGuard();
~LITTLEFSGuard();
};
class ignitionBoxStatusAverage
@@ -53,7 +41,8 @@ private:
public:
ignitionBoxStatusAverage() = default;
ignitionBoxStatusAverage(const uint32_t max_count) : m_max_count(max_count) {
ignitionBoxStatusAverage(const uint32_t max_count) : m_max_count(max_count)
{
m_data_valid = false;
m_count = 0;
}

View File

@@ -1,4 +1,4 @@
#define DEBUGLOG_DEFAULT_LOG_LEVEL_INFO
#define DEBUGLOG_DEFAULT_LOG_LEVEL_DEBUG
// Arduino Libraries
#include <Arduino.h>
@@ -16,6 +16,9 @@
#include <datasave.h>
#include <ui.h>
static File uploadFile;
static bool uploadFailed = false;
// FreeRTOS directives
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
@@ -61,11 +64,6 @@ void setup()
LOG_DEBUG("ESP32 Heap:", ESP.getHeapSize());
LOG_DEBUG("ESP32 Sketch:", ESP.getFreeSketchSpace());
// Initialize Interrupt pins on PICKUP detectors
initTriggerPinsInputs();
// Initialize Interrupt pins on SPARK detectors
initSparkPinInputs();
// Init Wifi station
LOG_INFO("Initializing WiFi...");
WiFi.mode(WIFI_AP);
@@ -87,6 +85,11 @@ void setup()
vTaskDelay(pdMS_TO_TICKS(5000));
esp_restart();
}
// Initialize Interrupt pins on PICKUP detectors
initTriggerPinsInputs();
// Initialize Interrupt pins on SPARK detectors
initSparkPinInputs();
}
void loop()
@@ -123,7 +126,15 @@ void loop()
.spark_pin_34 = SPARK_PIN_A34},
.rt_resets = rtTaskResets{.rst_io_12p = RST_EXT_A12P, .rst_io_12n = RST_EXT_A12N, .rst_io_34p = RST_EXT_A34P, .rst_io_34n = RST_EXT_A34N}};
LOG_DEBUG("Task Variables OK");
if (!rt_taskA_queue || !rt_taskB_queue)
{
LOG_ERROR("Unable To Create task queues");
LOG_ERROR("5 seconds to restart...");
vTaskDelay(pdMS_TO_TICKS(5000));
esp_restart();
}
else
LOG_DEBUG("Task Variables OK");
#ifdef CH_B_ENABLE
QueueHandle_t rt_taskB_queue = xQueueCreate(max_queue, sizeof(ignitionBoxStatus));
@@ -190,6 +201,7 @@ void loop()
RT_TASK_PRIORITY,
&trigA_TaskHandle,
CORE_0);
delay(100);
// Ignition B on Core 1
auto ignB_task_success = pdPASS;
@@ -202,11 +214,12 @@ void loop()
RT_TASK_PRIORITY, // priorità leggermente più alta
&trigB_TaskHandle,
CORE_1);
delay(100);
#endif
if ((ignA_task_success && ignB_task_success) != pdPASS)
if (ignA_task_success != pdPASS || ignB_task_success != pdPASS)
{
LOG_ERROR("Una ble to initialize ISR task");
LOG_ERROR("Unable to initialize ISR task");
LOG_ERROR("5 seconds to restart...");
vTaskDelay(pdMS_TO_TICKS(5000));
esp_restart();
@@ -219,7 +232,8 @@ void loop()
uint32_t counter = 0;
uint32_t wait_count = 0;
ignitionBoxStatus ign_info;
ignitionBoxStatusAverage ign_info_avg(filter_k);
ignitionBoxStatusAverage ign_info_avg(filter_k);
LITTLEFSGuard fsGuard;
// Initialize Web page
AsyncWebServer server(80);
@@ -227,6 +241,67 @@ void loop()
ws.onEvent(onWsEvent);
server.addHandler(&ws);
server.serveStatic("/", LittleFS, "/").setDefaultFile("index.html");
server.on("/upload", HTTP_POST,
[](AsyncWebServerRequest *request) {
if (uploadFailed)
{
request->send(500, "text/plain", "Upload failed");
}
else
{
request->send(200, "text/plain", "Upload successful");
}
},
[](AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final) {
if (index == 0)
{
uploadFailed = false;
String safeName = filename;
int slashIndex = safeName.lastIndexOf('/');
if (slashIndex >= 0)
{
safeName = safeName.substring(slashIndex + 1);
}
if (safeName.length() == 0)
{
uploadFailed = true;
return;
}
String filePath = "/" + safeName;
if (LittleFS.exists(filePath))
{
LittleFS.remove(filePath);
}
uploadFile = LittleFS.open(filePath, FILE_WRITE);
if (!uploadFile)
{
uploadFailed = true;
LOG_ERROR("Failed to open upload file:", filePath);
return;
}
}
if (!uploadFailed && uploadFile)
{
if (uploadFile.write(data, len) != len)
{
uploadFailed = true;
}
}
if (final && uploadFile)
{
uploadFile.close();
if (!uploadFailed)
{
LOG_INFO("Uploaded file to LittleFS:", filename);
}
}
});
server.begin();
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
@@ -253,12 +328,12 @@ void loop()
auto &hist = *active_history;
hist[counter++ % active_history->size()] = ign_info;
ign_info_avg.update(ign_info); // update moving average with latest ignition status
Serial.print("Data Received: " + String(counter) + "/" + String(hist.size()) + '\r');
Serial.print("\033[2K Data Received: " + String(counter) + "/" + String(hist.size()) + '\r');
if (ws.count() > 0 && counter % 10 == 0) // send data every 10 samples
if (ws.count() > 0 && counter % filter_k == 0) // send data every 10 samples
{
Serial.println();
LOG_INFO("Sending average ignition status to websocket clients...");
LOG_DEBUG("Sending average ignition status to websocket clients...");
auto msg = ign_info_avg.toJson().as<String>();
ws.textAll(msg);
}

View File

@@ -110,7 +110,6 @@ void rtIgnitionTask(void *pvParameters)
#ifdef DEBUG
Serial.print("\033[2J"); // clear screen
Serial.print("\033[H"); // cursor home
LOG_INFO("Iteration [", it++, "]");
if (!names.contains(pickup_flag))
{
@@ -267,9 +266,11 @@ void rtIgnitionTask(void *pvParameters)
// send essage to main loop with ignition info, by copy so local static variable is ok
if (rt_queue)
{
ign_box_sts.timestamp = esp_timer_get_time(); // update data timestamp
if (xQueueSendToBack(rt_queue, (void *)&ign_box_sts, 0) != pdPASS)
ign_box_sts.n_queue_errors = ++n_errors;
if (xQueueSendToBack(rt_queue, (void *)&ign_box_sts, 0) != pdPASS)
ign_box_sts.n_queue_errors = ++n_errors;
}
}
}
// Delete the timeout timer

View File

View File