From 12e1e8e7a4abc9a74b66e73fef3dc3ccaefe87fb Mon Sep 17 00:00:00 2001 From: Emanuele Trabattoni Date: Wed, 8 Apr 2026 16:55:03 +0200 Subject: [PATCH] Fixed Filters, split file in html, script and css --- RotaxMonitor/data/index.html | 116 +--------------------------------- RotaxMonitor/data/script.js | 66 +++++++++++++++++++ RotaxMonitor/data/style.css | 29 +++++++++ RotaxMonitor/src/datasave.cpp | 78 +++++++++++++---------- RotaxMonitor/src/datasave.h | 9 ++- RotaxMonitor/src/datastruct.h | 6 +- RotaxMonitor/src/main.cpp | 2 +- RotaxMonitor/src/tasks.cpp | 8 +-- RotaxMonitor/src/ui.cpp | 5 ++ RotaxMonitor/src/ui.h | 3 + 10 files changed, 167 insertions(+), 155 deletions(-) create mode 100644 RotaxMonitor/data/script.js create mode 100644 RotaxMonitor/data/style.css diff --git a/RotaxMonitor/data/index.html b/RotaxMonitor/data/index.html index 783cd00..656686d 100644 --- a/RotaxMonitor/data/index.html +++ b/RotaxMonitor/data/index.html @@ -3,37 +3,7 @@ ESP32 Dashboard - + @@ -44,6 +14,7 @@

Timestamp: -

+

Data Valid: -

Generator voltage: -

Engine RPM: -

ADC read time: -

@@ -58,16 +29,6 @@ - - Trigger time - - - - - - - Spark time - - - - - Spark delay - @@ -122,77 +83,6 @@
- - + \ No newline at end of file diff --git a/RotaxMonitor/data/script.js b/RotaxMonitor/data/script.js new file mode 100644 index 0000000..db58a84 --- /dev/null +++ b/RotaxMonitor/data/script.js @@ -0,0 +1,66 @@ +let ws; + +function connectWS() { + ws = new WebSocket("ws://" + location.host + "/ws"); + + ws.onopen = () => { + console.log("WebSocket connesso"); + }; + + ws.onclose = () => { + console.log("WebSocket disconnesso, retry..."); + setTimeout(connectWS, 5000); + }; + + ws.onmessage = (event) => { + let data; + + try { + data = JSON.parse(event.data); + } catch (e) { + console.error("Invalid JSON received", e); + return; + } + + document.getElementById("datavalid").textContent = data.datavalid ?? "-"; + document.getElementById("timestamp").textContent = data.timestamp ?? "-"; + document.getElementById("volts_gen").textContent = data.volts_gen ?? "-"; + document.getElementById("eng_rpm").textContent = data.eng_rpm ?? "-"; + document.getElementById("adc_read_time").textContent = data.adc_read_time ?? "-"; + document.getElementById("n_queue_errors").textContent = data.n_queue_errors ?? "-"; + + const coils12 = data.coils12 || {}; + const coils34 = data.coils34 || {}; + + document.getElementById("coils12_spark_delay").textContent = coils12.spark_delay ?? "-"; + document.getElementById("coils34_spark_delay").textContent = coils34.spark_delay ?? "-"; + document.getElementById("coils12_spark_status").textContent = coils12.spark_status ?? "-"; + document.getElementById("coils34_spark_status").textContent = coils34.spark_status ?? "-"; + document.getElementById("coils12_sstart_status").textContent = coils12.sstart_status ?? "-"; + document.getElementById("coils34_sstart_status").textContent = coils34.sstart_status ?? "-"; + document.getElementById("coils12_peak_p_in").textContent = coils12.peak_p_in ?? "-"; + document.getElementById("coils34_peak_p_in").textContent = coils34.peak_p_in ?? "-"; + document.getElementById("coils12_peak_n_in").textContent = coils12.peak_n_in ?? "-"; + document.getElementById("coils34_peak_n_in").textContent = coils34.peak_n_in ?? "-"; + document.getElementById("coils12_peak_p_out").textContent = coils12.peak_p_out ?? "-"; + document.getElementById("coils34_peak_p_out").textContent = coils34.peak_p_out ?? "-"; + document.getElementById("coils12_peak_n_out").textContent = coils12.peak_n_out ?? "-"; + document.getElementById("coils34_peak_n_out").textContent = coils34.peak_n_out ?? "-"; + document.getElementById("coils12_level_spark").textContent = coils12.level_spark ?? "-"; + document.getElementById("coils34_level_spark").textContent = coils34.level_spark ?? "-"; + document.getElementById("coils12_n_events").textContent = coils12.n_events ?? "-"; + document.getElementById("coils34_n_events").textContent = coils34.n_events ?? "-"; + document.getElementById("coils12_n_missed_firing").textContent = coils12.n_missed_firing ?? "-"; + document.getElementById("coils34_n_missed_firing").textContent = coils34.n_missed_firing ?? "-"; + }; +} + +function start() { + fetch("/start"); +} + +function stop() { + fetch("/stop"); +} + +connectWS(); diff --git a/RotaxMonitor/data/style.css b/RotaxMonitor/data/style.css new file mode 100644 index 0000000..ea261fa --- /dev/null +++ b/RotaxMonitor/data/style.css @@ -0,0 +1,29 @@ +body { + font-family: Arial; + text-align: center; + margin-top: 40px; +} + +table { + margin: auto; + border-collapse: collapse; + width: 100%; + max-width: 900px; +} + +th, td { + border: 1px solid #ccc; + padding: 10px; + font-size: 16px; + text-align: left; +} + +th { + background-color: #f4f4f4; +} + +button { + margin: 10px; + padding: 10px 20px; + font-size: 16px; +} diff --git a/RotaxMonitor/src/datasave.cpp b/RotaxMonitor/src/datasave.cpp index 37fc778..148c955 100644 --- a/RotaxMonitor/src/datasave.cpp +++ b/RotaxMonitor/src/datasave.cpp @@ -1,7 +1,20 @@ #include "datasave.h" +#include static const size_t min_free = 1024 * 1024; // minimum free space in SPIFFS to allow saving history (1MB) +void ignitionBoxStatusAverage::filter(int32_t &old, const int32_t value, const uint32_t k) +{ + float alpha = 1.0f / (float)k; + old = old + (int32_t)(alpha * (float)(value - old)); +} + +void ignitionBoxStatusAverage::filter(float &old, const float value, const uint32_t k) +{ + float alpha = 1.0f / (float)k; + old = old + (float)(alpha * (float)(value - old)); +} + void ignitionBoxStatusAverage::reset() { m_last = ignitionBoxStatus(); @@ -11,40 +24,37 @@ void ignitionBoxStatusAverage::reset() void ignitionBoxStatusAverage::update(const ignitionBoxStatus &new_status) { - if (m_count == 0) + if (m_count == 0 && !m_data_valid) { m_last = new_status; } - else - { - // simple moving average calculation - m_last.timestamp = new_status.timestamp; // keep timestamp of latest status - - m_last.coils12.n_events = new_status.coils12.n_events; // sum events instead of averaging - m_last.coils12.n_missed_firing = new_status.coils12.n_missed_firing; // sum missed firings instead of averaging - m_last.coils12.spark_status = new_status.coils12.spark_status; // take latest spark status - m_last.coils12.sstart_status = new_status.coils12.sstart_status; // take latest soft start status - m_last.coils12.spark_delay += (uint32_t)(0.1f * (float)(new_status.coils12.spark_delay - m_last.coils12.spark_delay)); // incremental average calculation - m_last.coils12.peak_p_in += 1.0f / m_max_count * (new_status.coils12.peak_p_in - m_last.coils12.peak_p_in); // incremental average calculation - m_last.coils12.peak_n_in += 1.0f / m_max_count * (new_status.coils12.peak_n_in - m_last.coils12.peak_n_in); // incremental average calculation - m_last.coils12.peak_p_out += 1.0f / m_max_count * (new_status.coils12.peak_p_out - m_last.coils12.peak_p_out); // incremental average calculation - m_last.coils12.peak_n_out += 1.0f / m_max_count * (new_status.coils12.peak_n_out - m_last.coils12.peak_n_out); // 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 - m_last.coils34.spark_delay += (uint32_t)(0.1f * (float)(new_status.coils34.spark_delay - m_last.coils34.spark_delay)); // incremental average calculation - m_last.coils34.peak_p_in += 1.0f / m_max_count * (new_status.coils34.peak_p_in - m_last.coils34.peak_p_in); // incremental average calculation - m_last.coils34.peak_n_in += 1.0f / m_max_count * (new_status.coils34.peak_n_in - m_last.coils34.peak_n_in); // incremental average calculation - m_last.coils34.peak_p_out += 1.0f / m_max_count * (new_status.coils34.peak_p_out - m_last.coils34.peak_p_out); // incremental average calculation - m_last.coils34.peak_n_out += 1.0f / m_max_count * (new_status.coils34.peak_n_out - m_last.coils34.peak_n_out); // incremental average calculation - - m_last.eng_rpm += (uint32_t)(0.1f * (float)(new_status.eng_rpm - m_last.eng_rpm)); // incremental average calculation - m_last.adc_read_time += (uint32_t)(0.1f * (float)(new_status.adc_read_time - m_last.adc_read_time)); // 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 - } m_count++; + // simple moving average calculation + m_last.timestamp = new_status.timestamp; // keep timestamp of latest status + + m_last.coils12.n_events = new_status.coils12.n_events; // sum events instead of averaging + m_last.coils12.n_missed_firing = new_status.coils12.n_missed_firing; // sum missed firings instead of averaging + m_last.coils12.spark_status = new_status.coils12.spark_status; // take latest spark status + m_last.coils12.sstart_status = new_status.coils12.sstart_status; // take latest soft start status + filter(m_last.coils12.spark_delay, new_status.coils12.spark_delay, m_max_count); // incremental average calculation + filter(m_last.coils12.peak_p_in, new_status.coils12.peak_p_in, m_max_count); // incremental average calculation + filter(m_last.coils12.peak_n_in, new_status.coils12.peak_n_in, m_max_count); // incremental average calculation + 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 + 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 + if (m_count >= m_max_count) { m_count = 0; // reset count after reaching max samples to average @@ -67,6 +77,7 @@ const ArduinoJson::JsonDocument ignitionBoxStatusAverage::toJson() const if (m_data_valid) { doc["timestamp"] = m_last.timestamp; + doc["datavalid"] = m_data_valid ? "TRUE" : "FALSE"; doc["coils12"]["n_events"] = m_last.coils12.n_events; doc["coils12"]["n_missed_firing"] = m_last.coils12.n_missed_firing; @@ -77,7 +88,7 @@ const ArduinoJson::JsonDocument ignitionBoxStatusAverage::toJson() const doc["coils12"]["peak_p_out"] = m_last.coils12.peak_p_out; doc["coils12"]["peak_n_out"] = m_last.coils12.peak_n_out; doc["coils12"]["sstart_status"] = softStartStatusNames.at(m_last.coils12.sstart_status); - + doc["coils34"]["n_events"] = m_last.coils34.n_events; doc["coils34"]["n_missed_firing"] = m_last.coils34.n_missed_firing; doc["coils34"]["spark_delay"] = m_last.coils34.spark_delay; @@ -113,8 +124,9 @@ void saveHistoryTask(void *pvParameters) void save_history(const PSRAMVector &history, const std::filesystem::path &file_name) { // Initialize SPIFFS - if (!SAVE_HISTORY_TO_SPIFFS) return; - //auto spiffs_guard = SPIFFSGuard(); // use RAII guard to ensure SPIFFS is properly mounted and unmounted + if (!SAVE_HISTORY_TO_SPIFFS) + return; + // auto spiffs_guard = SPIFFSGuard(); // use RAII guard to ensure SPIFFS is properly mounted and unmounted if (SPIFFS.totalBytes() - SPIFFS.usedBytes() < min_free) // check if at least 1MB is free for saving history { diff --git a/RotaxMonitor/src/datasave.h b/RotaxMonitor/src/datasave.h index 21851b6..9297f4c 100644 --- a/RotaxMonitor/src/datasave.h +++ b/RotaxMonitor/src/datasave.h @@ -53,12 +53,19 @@ 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; + } void reset(); void update(const ignitionBoxStatus &new_status); const bool get(ignitionBoxStatus &status) const; const ArduinoJson::JsonDocument toJson() const; + +private: + void filter(int32_t &old, const int32_t value, const uint32_t k); + void filter(float &old, const float value, const uint32_t k); }; // Task and function declarations diff --git a/RotaxMonitor/src/datastruct.h b/RotaxMonitor/src/datastruct.h index 3903976..9936313 100644 --- a/RotaxMonitor/src/datastruct.h +++ b/RotaxMonitor/src/datastruct.h @@ -61,7 +61,7 @@ struct coilsStatus { int64_t trig_time = 0; int64_t spark_time = 0; - uint32_t spark_delay = 0; // in microseconds + int32_t spark_delay = 0; // in microseconds sparkStatus spark_status = sparkStatus::SPARK_POS_OK; softStartStatus sstart_status = softStartStatus::NORMAL; float peak_p_in = 0.0; @@ -83,8 +83,8 @@ struct ignitionBoxStatus // voltage from generator float volts_gen = 0.0; // enine rpm - uint32_t eng_rpm = 0; + int32_t eng_rpm = 0; // debug values uint32_t n_queue_errors = 0; - uint32_t adc_read_time = 0; + int32_t adc_read_time = 0; }; diff --git a/RotaxMonitor/src/main.cpp b/RotaxMonitor/src/main.cpp index c793436..8185102 100644 --- a/RotaxMonitor/src/main.cpp +++ b/RotaxMonitor/src/main.cpp @@ -258,7 +258,7 @@ void loop() ignA_avg.update(ign_info); // update moving average with latest ignition status Serial.print("Data Received: " + String(counter) + "/" + String(hist.size()) + '\r'); - if (ws.count() > 0 && counter % max_queue == 0) + if (ws.count() > 0 && counter % 10 == 0) // send data every 10 samples { Serial.println(); LOG_INFO("Sending average ignition status to websocket clients..."); diff --git a/RotaxMonitor/src/tasks.cpp b/RotaxMonitor/src/tasks.cpp index afc3b82..86b9681 100644 --- a/RotaxMonitor/src/tasks.cpp +++ b/RotaxMonitor/src/tasks.cpp @@ -157,7 +157,7 @@ void rtIgnitionTask(void *pvParameters) auto current_time = esp_timer_get_time(); auto cycle_time = current_time - last_cycle_time; last_cycle_time = current_time; - ign_box_sts.eng_rpm = (uint32_t)(60.0f / (cycle_time / 1000000.0f)); + ign_box_sts.eng_rpm = (int32_t)(60.0f / (cycle_time / 1000000.0f)); } case TRIG_FLAG_12N: coils = &ign_box_sts.coils12; @@ -177,7 +177,7 @@ void rtIgnitionTask(void *pvParameters) // Timeout not occourred, expected POSITIVE edge spark OCCOURRED if (spark_flag != SPARK_FLAG_TIMEOUT) { - coils->spark_delay = (uint32_t)(coils->spark_time - coils->trig_time); + coils->spark_delay = (int32_t)(coils->spark_time - coils->trig_time); coils->sstart_status = softStartStatus::NORMAL; // because spark on positive edge coils->spark_status = sparkStatus::SPARK_POS_OK; // do not wait for spark on negative edge } @@ -197,7 +197,7 @@ void rtIgnitionTask(void *pvParameters) // Timeout not occourred, expected NEGATIVE edge spark OCCOURRED if (spark_flag != SPARK_FLAG_TIMEOUT && expected_negative) { - coils->spark_delay = (uint32_t)(coils->spark_time - coils->trig_time); + coils->spark_delay = (int32_t)(coils->spark_time - coils->trig_time); coils->sstart_status = softStartStatus::SOFT_START; coils->spark_status = sparkStatus::SPARK_NEG_OK; } @@ -248,7 +248,7 @@ void rtIgnitionTask(void *pvParameters) ign_box_sts.coils12.peak_n_out = adcReadChannel(adc, ADC_CH_PEAK_12N_OUT); ign_box_sts.coils34.peak_p_out = adcReadChannel(adc, ADC_CH_PEAK_34P_OUT); ign_box_sts.coils34.peak_n_out = adcReadChannel(adc, ADC_CH_PEAK_34N_OUT); - ign_box_sts.adc_read_time = (uint32_t)(esp_timer_get_time() - start_adc_read); + ign_box_sts.adc_read_time = (int32_t)(esp_timer_get_time() - start_adc_read); } else // simulate adc read timig vTaskDelay(pdMS_TO_TICKS(1)); diff --git a/RotaxMonitor/src/ui.cpp b/RotaxMonitor/src/ui.cpp index 331a339..80e31e5 100644 --- a/RotaxMonitor/src/ui.cpp +++ b/RotaxMonitor/src/ui.cpp @@ -14,6 +14,11 @@ void setCursor(const uint8_t x, const uint8_t y) } void printField(const char name[], const uint32_t val) +{ + Serial.printf("%15s: %06u\n", name, val); +} + +void printField(const char name[], const int32_t val) { Serial.printf("%15s: %06d\n", name, val); } diff --git a/RotaxMonitor/src/ui.h b/RotaxMonitor/src/ui.h index 2d439ec..8fdf35b 100644 --- a/RotaxMonitor/src/ui.h +++ b/RotaxMonitor/src/ui.h @@ -7,6 +7,7 @@ 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 int32_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); @@ -60,6 +61,7 @@ static const std::string htmlTest = R"rawliteral(

Timestamp: -

+

Data Valid: -

Generator voltage: -

Engine RPM: -

ADC read time: -

@@ -153,6 +155,7 @@ static const std::string htmlTest = R"rawliteral( return; } + document.getElementById("datavalid").textContent = data.datavalid ?? "-"; document.getElementById("timestamp").textContent = data.timestamp ?? "-"; document.getElementById("volts_gen").textContent = data.volts_gen ?? "-"; document.getElementById("eng_rpm").textContent = data.eng_rpm ?? "-";