diff --git a/RotaxMonitor/lib/led/led.h b/RotaxMonitor/lib/led/led.h index a6da94e..0770314 100644 --- a/RotaxMonitor/lib/led/led.h +++ b/RotaxMonitor/lib/led/led.h @@ -37,7 +37,7 @@ public: struct color_t { - uint8_t a, g, r, b; + uint8_t a, r, g, b; }; union color_u diff --git a/RotaxMonitor/platformio.ini b/RotaxMonitor/platformio.ini index e7fb4f6..5ba4f2c 100644 --- a/RotaxMonitor/platformio.ini +++ b/RotaxMonitor/platformio.ini @@ -64,7 +64,7 @@ build_flags = -DARDUINO_USB_MODE=0 -DCONFIG_ASYNC_TCP_MAX_ACK_TIME=5000 -DCONFIG_ASYNC_TCP_PRIORITY=21 - -DCONFIG_ASYNC_TCP_QUEUE_SIZE=64 + -DCONFIG_ASYNC_TCP_QUEUE_SIZE=128 -DCONFIG_ASYNC_TCP_RUNNING_CORE=1 - -DCONFIG_ASYNC_TCP_STACK_SIZE=4096 + -DCONFIG_ASYNC_TCP_STACK_SIZE=8192 -fstack-protector-all diff --git a/RotaxMonitor/src/isr.h b/RotaxMonitor/src/isr.h index bd1bea5..1f1d2e7 100644 --- a/RotaxMonitor/src/isr.h +++ b/RotaxMonitor/src/isr.h @@ -17,7 +17,7 @@ #define CORE_0 0 #define CORE_1 1 #define RT_TASK_STACK 2048 // in words -#define RT_TASK_PRIORITY (configMAX_PRIORITIES - 6) // highest priority after wifi tasks +#define RT_TASK_PRIORITY (configMAX_PRIORITIES - 5) // highest priority after wifi tasks struct isrParams { diff --git a/RotaxMonitor/src/main.cpp b/RotaxMonitor/src/main.cpp index cd5f6e0..daa940e 100644 --- a/RotaxMonitor/src/main.cpp +++ b/RotaxMonitor/src/main.cpp @@ -31,7 +31,7 @@ void setup() // Setup Logger LOG_ATTACH_SERIAL(Serial); - LOG_SET_LEVEL(DebugLogLevel::LVL_INFO); + LOG_SET_LEVEL(DebugLogLevel::LVL_DEBUG); // Print Processor Info LOG_DEBUG("ESP32 Chip:", ESP.getChipModel()); @@ -82,19 +82,20 @@ void loop() led.setStatus(RGBled::LedStatus::INIT); bool running = true; std::mutex fs_mutex; + LITTLEFSGuard fsGuard; //////// INIT SPI PORTS //////// bool spiA_ok = true; bool spiB_ok = true; // Init 2 SPI interfaces - SPIClass SPI_A(FSPI); - spiA_ok = SPI_A.begin(SPI_A_SCK, SPI_A_MISO, SPI_A_MOSI); - SPI_A.setDataMode(SPI_MODE1); // ADS1256 requires SPI mode 1 -#ifdef CH_B_ENABLE - SPIClass SPI_B(HSPI); - spiB_ok = SPI_B.begin(SPI_B_SCK, SPI_B_MISO, SPI_B_MOSI); - SPI_B.setDataMode(SPI_MODE1); // ADS1256 requires SPI mode 1 -#endif +// SPIClass SPI_A(FSPI); +// spiA_ok = SPI_A.begin(SPI_A_SCK, SPI_A_MISO, SPI_A_MOSI); +// SPI_A.setDataMode(SPI_MODE1); // ADS1256 requires SPI mode 1 +// #ifdef CH_B_ENABLE +// SPIClass SPI_B(HSPI); +// spiB_ok = SPI_B.begin(SPI_B_SCK, SPI_B_MISO, SPI_B_MOSI); +// SPI_B.setDataMode(SPI_MODE1); // ADS1256 requires SPI mode 1 +// #endif if (!spiA_ok || !spiB_ok) { LOG_ERROR("Unable to Initialize SPI Busses"); @@ -106,20 +107,20 @@ void loop() // Resources Initialization std::shared_ptr dev = std::make_shared(); - dev->m_spi_a = std::make_unique(SPI_A); - dev->m_spi_b = std::make_unique(SPI_B); + // dev->m_spi_a = std::make_unique(SPI_A); + // dev->m_spi_b = std::make_unique(SPI_B); - // Init ADC_A - dev->m_adc_a = std::make_unique(ADC_A_DRDY, ADS1256::PIN_UNUSED, ADS1256::PIN_UNUSED, ADC_A_CS, 2.5, &SPI_A); - dev->m_adc_b = std::make_unique(ADC_B_DRDY, ADS1256::PIN_UNUSED, ADS1256::PIN_UNUSED, ADC_B_CS, 2.5, &SPI_B); + // // Init ADC_A + // dev->m_adc_a = std::make_unique(ADC_A_DRDY, ADS1256::PIN_UNUSED, ADS1256::PIN_UNUSED, ADC_A_CS, 2.5, &SPI_A); + // dev->m_adc_b = std::make_unique(ADC_B_DRDY, ADS1256::PIN_UNUSED, ADS1256::PIN_UNUSED, ADC_B_CS, 2.5, &SPI_B); - dev->m_adc_a->InitializeADC(); - dev->m_adc_a->setPGA(PGA_1); - dev->m_adc_a->setDRATE(DRATE_7500SPS); + // dev->m_adc_a->InitializeADC(); + // dev->m_adc_a->setPGA(PGA_1); + // dev->m_adc_a->setDRATE(DRATE_7500SPS); - dev->m_adc_b->InitializeADC(); - dev->m_adc_b->setPGA(PGA_1); - dev->m_adc_b->setDRATE(DRATE_7500SPS); + // dev->m_adc_b->InitializeADC(); + // dev->m_adc_b->setPGA(PGA_1); + // dev->m_adc_b->setDRATE(DRATE_7500SPS); const rtIgnitionTask::rtTaskParams taskA_params{ .rt_running = true, @@ -183,8 +184,9 @@ void loop() .rt_queue = nullptr, .dev = dev}; - auto task_A = rtIgnitionTask(taskA_params, 1024, 128, CORE_0, fs_mutex); - auto task_B = rtIgnitionTask(taskA_params, 1024, 128, CORE_1, fs_mutex); + auto task_A = rtIgnitionTask(taskA_params, 4096, 256, CORE_1, fs_mutex); + delay(50); + auto task_B = rtIgnitionTask(taskB_params, 4096, 256, CORE_1, fs_mutex); // Ignition A on Core 0 auto ignA_task_success = task_A.getStatus() == rtIgnitionTask::OK ? pdPASS : pdFAIL; @@ -197,23 +199,43 @@ void loop() esp_restart(); } + const bool tasK_A_rt = task_A.start(); + delay(50); + const bool task_B_rt = task_B.start(); + if (tasK_A_rt != true || task_B_rt != true) + { + led.setStatus(RGBled::LedStatus::ERROR); + LOG_ERROR("Unable to start realtime tasks"); + } else LOG_DEBUG("Real Time Tasks A & B initialized"); led.setStatus(RGBled::LedStatus::OK); - LITTLEFSGuard fsGuard; WebPage webPage(80, LittleFS); // Initialize webserver and Websocket + task_A.onMessage([&webPage](ignitionBoxStatusFiltered sts){ + ArduinoJson::JsonDocument doc; + doc["box_a"] = sts.toJson(); + doc["box_b"] = ArduinoJson::JsonDocument(); + webPage.sendWsData(doc.as()); + }); + + task_B.onMessage([&webPage](ignitionBoxStatusFiltered sts){ + ArduinoJson::JsonDocument doc; + doc["box_a"] = ArduinoJson::JsonDocument(); + doc["box_b"] = sts.toJson(); + webPage.sendWsData(doc.as()); + }); + + task_A.enableSave(true, "ignitionA_test.csv"); + task_B.enableSave(true, "ignitionB_test.csv"); + uint32_t last_loop = millis(); //////////////// INNER LOOP ///////////////////// while (running) { - led.setStatus(RGBled::LedStatus::IDLE); - delay(100); - - if ((millis() - last_loop) > 1000) + if ((millis() - last_loop) > 2000) { clearScreen(); - Serial.println(); printRunningTasksMod(Serial); last_loop = millis(); } diff --git a/RotaxMonitor/src/tasks.cpp b/RotaxMonitor/src/tasks.cpp index 647b66b..bcc7859 100644 --- a/RotaxMonitor/src/tasks.cpp +++ b/RotaxMonitor/src/tasks.cpp @@ -296,20 +296,22 @@ rtIgnitionTask::rtIgnitionTask(const rtTaskParams params, const uint32_t history m_params.rt_queue = m_queue; // create PSram history vectors - m_history_0.resize(history_size); - m_history_1.resize(history_size); + m_history_0 = PSHistory(history_size); + m_history_1 = PSHistory(history_size); // assing active and writable history m_active_history = std::unique_ptr(&m_history_0); m_save_history = std::unique_ptr(&m_history_1); LOG_WARN("Starting Manager for [", m_params.name.c_str(), "]"); - auto task_success = xTaskCreate( + // auto task_success = pdPASS; + auto task_success = xTaskCreatePinnedToCore( rtIgnitionTask_manager, (std::string("man_") + m_params.name).c_str(), - m_params.rt_stack_size, + 8192, (void *)this, - 1, - &m_manager_handle); + m_params.rt_priority >> 2, + &m_manager_handle, + m_core); if (task_success != pdPASS) { @@ -341,10 +343,12 @@ void rtIgnitionTask::run() if (new_data == pdPASS) { + m_last_data = millis(); m_manager_status = rtTaskStatus::RUNNING; // if history buffer is full swap buffers and if enabled save history buffer if (m_counter_status >= m_active_history->size()) { + LOG_DEBUG("Save for Buffer Full: ", m_counter_status); m_counter_status = 0; m_partial_save = false; // reset partial save flag on new data cycle std::swap(m_active_history, m_save_history); @@ -356,13 +360,21 @@ void rtIgnitionTask::run() m_info_filtered.update(m_last_status); (*m_active_history)[m_counter_status] = m_last_status; + if (m_on_message_cb && m_counter_status % 10) + { + m_on_message_cb(m_info_filtered); + } + // update data counter m_counter_status++; } else { - if (millis() - m_last_data > c_idle_time){ - if (m_counter_status > 0 && !m_partial_save){ + if (millis() - m_last_data > c_idle_time) + { + if (m_counter_status > 0 && !m_partial_save) + { + LOG_DEBUG("Save Partial: ", m_counter_status); m_active_history->resize(m_counter_status); saveHistory(*m_active_history, m_history_path); m_active_history->resize(m_max_history); @@ -434,13 +446,18 @@ void rtIgnitionTask::enableSave(const bool enable, const std::filesystem::path f } } +void rtIgnitionTask::onMessage(std::function callaback) +{ + m_on_message_cb = callaback; +} + void rtIgnitionTask::saveHistory(const rtIgnitionTask::PSHistory &history, const std::filesystem::path &file_name) { // Lock filesystem mutex to avoid concurrent access std::lock_guard fs_lock(m_fs_mutex); // Check for free space - if (LittleFS.totalBytes() - LittleFS.usedBytes() < history.size() * sizeof(ignitionBoxStatus) * 200) // check if at least 1MB is free for saving history + if (LittleFS.totalBytes() - LittleFS.usedBytes() < history.size() * sizeof(ignitionBoxStatus)) // check if at least 1MB is free for saving history { LOG_ERROR("Not enough space in SPIFFS to save history"); return; @@ -454,9 +471,8 @@ void rtIgnitionTask::saveHistory(const rtIgnitionTask::PSHistory &history, const // if firt save remove old file and create new auto save_flags = std::ios::out; - if (m_first_save && m_filesystem.exists(file_path.c_str())) + if (m_first_save) { - m_first_save = false; save_flags |= std::ios::trunc; // overwrite existing file m_filesystem.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 Flash, new file:", file_path.c_str()); @@ -477,12 +493,12 @@ void rtIgnitionTask::saveHistory(const rtIgnitionTask::PSHistory &history, const // write csv header if (m_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" + 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" << std::endl; ofs.flush(); + m_first_save = false; } for (const auto &entry : history) diff --git a/RotaxMonitor/src/tasks.h b/RotaxMonitor/src/tasks.h index a37cd9d..bfe9958 100644 --- a/RotaxMonitor/src/tasks.h +++ b/RotaxMonitor/src/tasks.h @@ -14,6 +14,7 @@ #include #include #include +#include // ISR #include "isr.h" @@ -111,6 +112,8 @@ public: void enableSave(const bool enable, const std::filesystem::path filename); + void onMessage(std::function callaback); + private: void saveHistory(const rtIgnitionTask::PSHistory &history, const std::filesystem::path &file_name); @@ -147,7 +150,9 @@ private: ignitionBoxStatus m_last_status; ignitionBoxStatusFiltered m_info_filtered; - static const uint32_t c_idle_time = 2000; // in mS + std::function m_on_message_cb = nullptr; + + static const uint32_t c_idle_time = 10000; // in mS static const uint32_t c_spark_timeout_max = 500; // uS static const uint8_t c_adc_time = 4; // in mS static const uint8_t c_io_time = 2; // in mS diff --git a/RotaxMonitor/src/utils.cpp b/RotaxMonitor/src/utils.cpp index a1fad94..1e59492 100644 --- a/RotaxMonitor/src/utils.cpp +++ b/RotaxMonitor/src/utils.cpp @@ -5,25 +5,69 @@ #include "freertos/FreeRTOS.h" #include "freertos/portable.h" +#include "esp_heap_caps.h" +#include "esp_system.h" +#include "esp_spi_flash.h" +#include "esp_partition.h" +#include "LittleFS.h" + #include #include #include #define FREERTOS_TASK_NUMBER_MAX_NUM 256 // RunTime stats for how many Tasks to be stored -std::string printBits(uint32_t value) { +std::string printBits(uint32_t value) +{ std::string result; - for (int i = 31; i >= 0; i--) { + for (int i = 31; i >= 0; i--) + { // ottieni il singolo bit result += ((value >> i) & 1) ? '1' : '0'; // aggiungi uno spazio ogni 8 bit, tranne dopo l'ultimo - if (i % 8 == 0 && i != 0) { + if (i % 8 == 0 && i != 0) + { result += ' '; } } return result; } +// ANSI colors +#define BAR_WIDTH 30 +#define COLOR_RESET "\033[0m" +#define COLOR_RED "\033[31m" +#define COLOR_GREEN "\033[32m" +#define COLOR_BLUE "\033[34m" +#define COLOR_MAGENTA "\033[35m" +#define COLOR_CYAN "\033[36m" +#define COLOR_YELLOW "\033[33m" +#define COLOR_WHITE "\033[37m" +#define COLOR_LBLUE "\033[94m" + +void printBar(Print &printer, const char *label, size_t used, size_t total, const char *color) +{ + float perc = total > 0 ? ((float)used / total) : 0; + int filled = perc * BAR_WIDTH; + + printer.printf("%s%-12s [" COLOR_RESET, color, label); + + for (int i = 0; i < BAR_WIDTH; i++) + { + if (i < filled) + printer.printf("%s#%s", color, COLOR_RESET); + else + printer.printf("-"); + } + + printer.printf("] %s%6.2f%%%s (%5.3f/%5.3f)MB\n", + color, + perc * 100.0, + COLOR_RESET, + (used / 1024.0f / 1024.0f), + (total / 1024.0f / 1024.0f)); +} + void printRunningTasksMod(Print &printer, std::function orderBy) { static const char *taskStates[] = {"Running", "Ready", "Blocked", "Suspended", "Deleted", "Invalid"}; @@ -53,6 +97,68 @@ void printRunningTasksMod(Print &printer, std::function 0) + { + size_t freePsram = heap_caps_get_free_size(MALLOC_CAP_SPIRAM); + printBar(printer, "PSRAM", totalPsram - freePsram, totalPsram, COLOR_MAGENTA); + } + + printer.printf("\n"); + + // ===== FLASH APP (approssimato) ===== + const esp_partition_t *app_partition = + esp_partition_find_first(ESP_PARTITION_TYPE_APP, + ESP_PARTITION_SUBTYPE_APP_FACTORY, + NULL); + + if (app_partition) + { + size_t totalAPP = app_partition->size; // dimensione reale partizione + size_t sketchSize = ESP.getSketchSize(); + printBar(printer, "FLASH APP", sketchSize, totalAPP, COLOR_CYAN); + } + else + { + printer.printf(COLOR_YELLOW "%-12s [NOT FOUND]\n" COLOR_RESET, "FLASH APP"); + } + + // ===== LITTLEFS (corretto con partition table) ===== + const esp_partition_t *fs_partition = + esp_partition_find_first(ESP_PARTITION_TYPE_DATA, + ESP_PARTITION_SUBTYPE_DATA_LITTLEFS, + "littlefs"); + + if (fs_partition) + { + size_t totalFS = fs_partition->size; // dimensione reale partizione + size_t usedFS = LittleFS.usedBytes(); // spazio usato reale + printBar(printer, "LITTLEFS", usedFS, totalFS, COLOR_YELLOW); + } + else + { + printer.printf(COLOR_YELLOW "%-12s [NOT FOUND]\n" COLOR_RESET, "LITTLEFS"); + } + + // ===== MIN HEAP ===== + size_t minHeap = esp_get_minimum_free_heap_size(); + printer.printf("%s\nMin Heap Ever:%s %u KB\n\n", COLOR_RED, COLOR_RESET, minHeap / 1024); + // Print Runtime Information printer.printf("Tasks: %u, Runtime: %lus, Period: %luus\r\n", uxArraySize, ulTotalRunTime / 1000000, ulCurrentRunTime); @@ -79,4 +185,3 @@ void printRunningTasksMod(Print &printer, std::function