diff --git a/Datasheet/be729405d0b0e6411fd7e01e69acec34184d17a9e57c43ec413389783c890f71_optim.pdf b/Datasheet/be729405d0b0e6411fd7e01e69acec34184d17a9e57c43ec413389783c890f71_optim.pdf new file mode 100644 index 0000000..593de36 Binary files /dev/null and b/Datasheet/be729405d0b0e6411fd7e01e69acec34184d17a9e57c43ec413389783c890f71_optim.pdf differ diff --git a/RotaxMonitor/.gitignore b/RotaxMonitor/.gitignore index 89cc49c..18aafbe 100644 --- a/RotaxMonitor/.gitignore +++ b/RotaxMonitor/.gitignore @@ -3,3 +3,4 @@ .vscode/c_cpp_properties.json .vscode/launch.json .vscode/ipch +unpacked_fs diff --git a/RotaxMonitor/.vscode/extensions.json b/RotaxMonitor/.vscode/extensions.json index 411655e..b397401 100644 --- a/RotaxMonitor/.vscode/extensions.json +++ b/RotaxMonitor/.vscode/extensions.json @@ -1,8 +1,7 @@ { "recommendations": [ "Jason2866.esp-decoder", - "pioarduino.pioarduino-ide", - "platformio.platformio-ide" + "pioarduino.pioarduino-ide" ], "unwantedRecommendations": [ "ms-vscode.cpptools-extension-pack" diff --git a/RotaxMonitor/data/config.json b/RotaxMonitor/data/config.json new file mode 100644 index 0000000..544b7b4 --- /dev/null +++ b/RotaxMonitor/data/config.json @@ -0,0 +1,3 @@ +{ + +} \ No newline at end of file diff --git a/RotaxMonitor/data/index.html b/RotaxMonitor/data/index.html new file mode 100644 index 0000000..71aa277 --- /dev/null +++ b/RotaxMonitor/data/index.html @@ -0,0 +1,188 @@ + + + + + + Astro Rotax Monitor + + + + + + +
+ Waiting for data... +
+ +
+
+

Box_A

+
+

Timestamp: -

+

Data Valid: -

+

Generator voltage: -

+

ADC read time: -

+

Queue errors: -

+
+
+ Engine RPM: - +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyPickup 12Pickup 34
Spark delay--
Spark status--
Soft start status--
Peak P in--
Peak N in--
Peak P out--
Peak N out--
Level spark--
Spark Events--
Missed Events--
+
+ +
+

Box_B

+
+

Timestamp: -

+

Data Valid: -

+

Generator voltage: -

+

ADC read time: -

+

Queue errors: -

+
+
+ Engine RPM: - +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyPickup 12Pickup 34
Spark delay--
Spark status--
Soft start status--
Peak P in--
Peak N in--
Peak P out--
Peak N out--
Level spark--
Spark Events--
Missed Events--
+
+
+ +
+

Upload file to Flash

+

Select a file and upload it to Flash.

+ + +
No file uploaded yet.
+
+ + + + + \ No newline at end of file diff --git a/RotaxMonitor/data/logo_astro_dev.svg b/RotaxMonitor/data/logo_astro_dev.svg new file mode 100644 index 0000000..f504444 --- /dev/null +++ b/RotaxMonitor/data/logo_astro_dev.svg @@ -0,0 +1,306 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RotaxMonitor/data/script.js b/RotaxMonitor/data/script.js new file mode 100644 index 0000000..7ab466c --- /dev/null +++ b/RotaxMonitor/data/script.js @@ -0,0 +1,164 @@ +let ws; +let lastMessageTimestamp = 0; +const IDLE_THRESHOLD_MS = 1000; +const loadingIndicator = document.getElementById("loadingIndicator"); + +function setLoadingIndicator(visible) { + if (!loadingIndicator) { + return; + } + + loadingIndicator.classList.toggle("hidden", !visible); +} + +function updateLoadingState() { + const isConnected = ws && ws.readyState === WebSocket.OPEN; + const idle = Date.now() - lastMessageTimestamp >= IDLE_THRESHOLD_MS; + + setLoadingIndicator(isConnected && idle); +} + +function connectWS() { + ws = new WebSocket("ws://" + location.host + "/ws"); + + ws.onopen = () => { + console.log("WebSocket connesso"); + lastMessageTimestamp = Date.now(); + setLoadingIndicator(false); + }; + + ws.onclose = () => { + console.log("WebSocket disconnesso, retry..."); + setLoadingIndicator(false); + setTimeout(connectWS, 5000); + }; + + ws.onmessage = (event) => { + let data; + + try { + data = JSON.parse(event.data); + } catch (e) { + console.error("Invalid JSON received", e); + return; + } + + lastMessageTimestamp = Date.now(); + setLoadingIndicator(false); + + // Update Box_A + if (data.box_a) { + const boxA = data.box_a; + document.getElementById("a_datavalid").textContent = boxA.datavalid ?? "-"; + document.getElementById("a_timestamp").textContent = boxA.timestamp ?? "-"; + document.getElementById("a_volts_gen").textContent = boxA.volts_gen ?? "-"; + document.getElementById("a_eng_rpm").textContent = boxA.eng_rpm ?? "-"; + document.getElementById("a_adc_read_time").textContent = boxA.adc_read_time ?? "-"; + document.getElementById("a_n_queue_errors").textContent = boxA.n_queue_errors ?? "-"; + + const coils12A = boxA.coils12 || {}; + const coils34A = boxA.coils34 || {}; + + document.getElementById("a_coils12_spark_delay").textContent = coils12A.spark_delay ?? "-"; + document.getElementById("a_coils34_spark_delay").textContent = coils34A.spark_delay ?? "-"; + document.getElementById("a_coils12_spark_status").textContent = coils12A.spark_status ?? "-"; + document.getElementById("a_coils34_spark_status").textContent = coils34A.spark_status ?? "-"; + document.getElementById("a_coils12_sstart_status").textContent = coils12A.sstart_status ?? "-"; + document.getElementById("a_coils34_sstart_status").textContent = coils34A.sstart_status ?? "-"; + document.getElementById("a_coils12_peak_p_in").textContent = coils12A.peak_p_in ?? "-"; + document.getElementById("a_coils34_peak_p_in").textContent = coils34A.peak_p_in ?? "-"; + document.getElementById("a_coils12_peak_n_in").textContent = coils12A.peak_n_in ?? "-"; + document.getElementById("a_coils34_peak_n_in").textContent = coils34A.peak_n_in ?? "-"; + document.getElementById("a_coils12_peak_p_out").textContent = coils12A.peak_p_out ?? "-"; + document.getElementById("a_coils34_peak_p_out").textContent = coils34A.peak_p_out ?? "-"; + document.getElementById("a_coils12_peak_n_out").textContent = coils12A.peak_n_out ?? "-"; + document.getElementById("a_coils34_peak_n_out").textContent = coils34A.peak_n_out ?? "-"; + document.getElementById("a_coils12_level_spark").textContent = coils12A.level_spark ?? "-"; + document.getElementById("a_coils34_level_spark").textContent = coils34A.level_spark ?? "-"; + document.getElementById("a_coils12_n_events").textContent = coils12A.n_events ?? "-"; + document.getElementById("a_coils34_n_events").textContent = coils34A.n_events ?? "-"; + document.getElementById("a_coils12_n_missed_firing").textContent = coils12A.n_missed_firing ?? "-"; + document.getElementById("a_coils34_n_missed_firing").textContent = coils34A.n_missed_firing ?? "-"; + } + + // Update Box_B + if (data.box_b) { + const boxB = data.box_b; + document.getElementById("b_datavalid").textContent = boxB.datavalid ?? "-"; + document.getElementById("b_timestamp").textContent = boxB.timestamp ?? "-"; + document.getElementById("b_volts_gen").textContent = boxB.volts_gen ?? "-"; + document.getElementById("b_eng_rpm").textContent = boxB.eng_rpm ?? "-"; + document.getElementById("b_adc_read_time").textContent = boxB.adc_read_time ?? "-"; + document.getElementById("b_n_queue_errors").textContent = boxB.n_queue_errors ?? "-"; + + const coils12B = boxB.coils12 || {}; + const coils34B = boxB.coils34 || {}; + + document.getElementById("b_coils12_spark_delay").textContent = coils12B.spark_delay ?? "-"; + document.getElementById("b_coils34_spark_delay").textContent = coils34B.spark_delay ?? "-"; + document.getElementById("b_coils12_spark_status").textContent = coils12B.spark_status ?? "-"; + document.getElementById("b_coils34_spark_status").textContent = coils34B.spark_status ?? "-"; + document.getElementById("b_coils12_sstart_status").textContent = coils12B.sstart_status ?? "-"; + document.getElementById("b_coils34_sstart_status").textContent = coils34B.sstart_status ?? "-"; + document.getElementById("b_coils12_peak_p_in").textContent = coils12B.peak_p_in ?? "-"; + document.getElementById("b_coils34_peak_p_in").textContent = coils34B.peak_p_in ?? "-"; + document.getElementById("b_coils12_peak_n_in").textContent = coils12B.peak_n_in ?? "-"; + document.getElementById("b_coils34_peak_n_in").textContent = coils34B.peak_n_in ?? "-"; + document.getElementById("b_coils12_peak_p_out").textContent = coils12B.peak_p_out ?? "-"; + document.getElementById("b_coils34_peak_p_out").textContent = coils34B.peak_p_out ?? "-"; + document.getElementById("b_coils12_peak_n_out").textContent = coils12B.peak_n_out ?? "-"; + document.getElementById("b_coils34_peak_n_out").textContent = coils34B.peak_n_out ?? "-"; + document.getElementById("b_coils12_level_spark").textContent = coils12B.level_spark ?? "-"; + document.getElementById("b_coils34_level_spark").textContent = coils34B.level_spark ?? "-"; + document.getElementById("b_coils12_n_events").textContent = coils12B.n_events ?? "-"; + document.getElementById("b_coils34_n_events").textContent = coils34B.n_events ?? "-"; + document.getElementById("b_coils12_n_missed_firing").textContent = coils12B.n_missed_firing ?? "-"; + document.getElementById("b_coils34_n_missed_firing").textContent = coils34B.n_missed_firing ?? "-"; + } + }; +} + +function start() { + fetch("/start"); +} + +function stop() { + fetch("/stop"); +} + +function uploadLittleFS() { + const fileInput = document.getElementById("littlefsFile"); + const status = document.getElementById("uploadStatus"); + + if (!fileInput || fileInput.files.length === 0) { + if (status) status.textContent = "Select a file first."; + return; + } + + const file = fileInput.files[0]; + const formData = new FormData(); + formData.append("file", file, file.name); + + if (status) status.textContent = "Uploading..."; + + fetch("/upload", { + method: "POST", + body: formData, + }) + .then((resp) => { + if (!resp.ok) { + throw new Error("Upload failed: " + resp.status + " " + resp.statusText); + } + return resp.text(); + }) + .then(() => { + if (status) status.textContent = "Uploaded: " + file.name; + fileInput.value = ""; + }) + .catch((err) => { + if (status) status.textContent = err.message; + }); +} + +setInterval(updateLoadingState, 200); +connectWS(); diff --git a/RotaxMonitor/data/style.css b/RotaxMonitor/data/style.css new file mode 100644 index 0000000..749df1f --- /dev/null +++ b/RotaxMonitor/data/style.css @@ -0,0 +1,221 @@ +:root { + --primary-dark: #0a1929; + --primary-blue: #003585; + --accent-blue: #1e88e5; + --light-bg: #f5f7fa; + --border-color: #d0d6dd; + --text-dark: #1a1a1a; + --text-muted: #666666; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + margin: 0; + padding: 0; + background-color: var(--light-bg); + color: var(--text-dark); +} + +.page-header { + background: linear-gradient(135deg, var(--primary-dark) 0%, #1a3a52 100%); + color: white; + padding: 30px 20px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + margin-bottom: 30px; +} + +.header-content { + max-width: 900px; + margin: 0 auto; + display: flex; + align-items: center; + gap: 20px; +} + +.logo { + height: 50px; + width: auto; + margin: auto; +} + +.page-header h1 { + margin: auto; + margin-top: 20px; + text-align: center; + font-size: 28px; + font-weight: 600; +} + +table { + margin: auto; + border-collapse: collapse; + width: 100%; + max-width: 900px; + background: white; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); + border-radius: 6px; + overflow: hidden; +} + +th, td { + border: 1px solid var(--border-color); + padding: 12px; + font-size: 14px; + text-align: center; +} + +th { + background-color: var(--primary-blue); + color: white; + font-weight: 600; +} + +tr:hover { + background-color: #f9fbfc; +} + +button { + margin: 10px; + padding: 10px 20px; + font-size: 16px; + background-color: var(--primary-blue); + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + transition: background-color 0.2s; +} + +button:hover { + background-color: var(--accent-blue); +} + +.upload-section { + margin: 30px auto 20px; + max-width: 900px; + text-align: left; + padding: 20px; + border: 1px solid var(--border-color); + border-radius: 6px; + background: white; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); +} + +.upload-section h3 { + margin-top: 0; + margin-bottom: 8px; + color: var(--primary-blue); + font-size: 16px; +} + +.upload-section p { + margin: 8px 0; + color: var(--text-muted); + font-size: 14px; +} + +.upload-section input[type="file"] { + margin-top: 8px; + margin-bottom: 12px; +} + +.upload-status { + margin-top: 10px; + font-size: 14px; + color: var(--text-muted); +} + +.loading-indicator { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + margin: 0; + padding: 16px 20px; + font-size: 20px; + color: var(--primary-blue); + border-bottom: 1px solid var(--border-color); + background: white; + width: 100%; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); +} + +.loading-indicator.hidden { + display: none; +} + +.spinner { + width: 16px; + height: 16px; + border: 2px solid transparent; + border-top-color: var(--primary-blue); + border-radius: 50%; + animation: spin 0.8s linear infinite; +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +.tables-container { + display: flex; + gap: 20px; + max-width: 1800px; + margin: 0 auto; + padding: 0 20px; +} + +.box { + flex: 1; + background: white; + padding: 20px; + border-radius: 6px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); +} + +.box h2 { + margin-top: 0; + margin-bottom: 16px; + color: var(--primary-blue); + font-size: 18px; + font-weight: 700; + text-align: center; +} + +.box-data { + margin-bottom: 20px; +} + +.box-data p { + margin: 8px 0; + font-size: 14px; +} + +.box-data strong { + color: var(--primary-blue); +} + +.rpm-highlight { + background: #c6e4fa; + border: 3px double var(--primary-blue); + border-radius: 8px; + padding: 12px 16px; + margin-bottom: 20px; + text-align: center; + font-size: 18px; + font-weight: bold; + color: var(--text-dark); +} + +.rpm-highlight strong { + color: var(--primary-blue); +} + +span { + color: var(--text-dark); +} diff --git a/RotaxMonitor/src/ADS1256.cpp b/RotaxMonitor/lib/ADS1256/ADS1256.cpp similarity index 100% rename from RotaxMonitor/src/ADS1256.cpp rename to RotaxMonitor/lib/ADS1256/ADS1256.cpp diff --git a/RotaxMonitor/src/ADS1256.h b/RotaxMonitor/lib/ADS1256/ADS1256.h similarity index 100% rename from RotaxMonitor/src/ADS1256.h rename to RotaxMonitor/lib/ADS1256/ADS1256.h diff --git a/RotaxMonitor/src/AD5292.cpp b/RotaxMonitor/lib/ADS5292/AD5292.cpp similarity index 100% rename from RotaxMonitor/src/AD5292.cpp rename to RotaxMonitor/lib/ADS5292/AD5292.cpp diff --git a/RotaxMonitor/src/AD5292.h b/RotaxMonitor/lib/ADS5292/AD5292.h similarity index 100% rename from RotaxMonitor/src/AD5292.h rename to RotaxMonitor/lib/ADS5292/AD5292.h diff --git a/RotaxMonitor/lib/led/led.cpp b/RotaxMonitor/lib/led/led.cpp new file mode 100644 index 0000000..9a93a85 --- /dev/null +++ b/RotaxMonitor/lib/led/led.cpp @@ -0,0 +1,32 @@ +#include + +RGBled::RGBled(const uint8_t pin) : m_led(pin) +{ + pinMode(m_led, OUTPUT); + writeStatus(RGBled::ERROR); +} + +RGBled::~RGBled() +{ + pinMode(m_led, INPUT); +} + +void RGBled::setStatus(const LedStatus s) +{ + if (m_status == s) + return; + std::lock_guard lock(m_mutex); + m_status = s; + writeStatus(m_status); +} + +const RGBled::LedStatus RGBled::getSatus(void) +{ + return m_status; +} + +void RGBled::writeStatus(const RGBled::LedStatus s) +{ + RGBled::color_u u{.status = s}; + rgbLedWrite(m_led, u.color.r, u.color.g, u.color.b); +} diff --git a/RotaxMonitor/lib/led/led.h b/RotaxMonitor/lib/led/led.h new file mode 100644 index 0000000..a6da94e --- /dev/null +++ b/RotaxMonitor/lib/led/led.h @@ -0,0 +1,63 @@ +#pragma once + +// System Inlcudes +#include +#include + +#define RED 0x00FF00 +#define GREEN 0xFF0000 +#define BLUE 0x0000FF +#define WHITE 0xFFFFFF +#define YELLOW 0xFFFF00 +#define CYAN 0xFF00FF +#define MAGENTA 0x00FFFF +#define ORANGE 0xA5FF00 +#define PURPLE 0x008080 +#define PINK 0x69FFB4 +#define LIME 0xCD3232 +#define SKY_BLUE 0xCE87EB +#define GOLD 0xD7FF00 +#define TURQUOISE 0xE040D0 +#define INDIGO 0x004B82 +#define GRAY 0x808080 + +class RGBled +{ +public: + enum LedStatus + { + OK = GREEN, + ERROR = RED, + INIT = YELLOW, + DATA_A = CYAN, + DATA_B = MAGENTA, + DATA_ALL = ORANGE, + IDLE = GRAY + }; + + struct color_t + { + uint8_t a, g, r, b; + }; + + union color_u + { + uint32_t status; + color_t color; + }; + +public: + RGBled(const uint8_t pin = 48); + ~RGBled(); + + void setStatus(const LedStatus s); + const LedStatus getSatus(void); + +private: + void writeStatus(const LedStatus s); + +private: + LedStatus m_status = LedStatus::IDLE; + std::mutex m_mutex; + const uint8_t m_led; +}; \ No newline at end of file diff --git a/RotaxMonitor/partitions/default_16MB.csv b/RotaxMonitor/partitions/default_16MB.csv deleted file mode 100644 index de523c3..0000000 --- a/RotaxMonitor/partitions/default_16MB.csv +++ /dev/null @@ -1,6 +0,0 @@ -# Name, Type, SubType, Offset, Size, Flags -nvs, data, nvs, 0x9000, 0x5000, -otadata, data, ota, 0xe000, 0x2000, -app0, app, ota_0, 0x10000, 0x700000, -app1, app, ota_1, 0x710000,0x700000, -spiffs, data, spiffs, 0xE10000,0x1F0000, diff --git a/RotaxMonitor/partitions/no_ota_10mb_littlefs.csv b/RotaxMonitor/partitions/no_ota_10mb_littlefs.csv new file mode 100644 index 0000000..b449d64 --- /dev/null +++ b/RotaxMonitor/partitions/no_ota_10mb_littlefs.csv @@ -0,0 +1,6 @@ +# ESP32 Partition Table +# Name, Type, SubType, Offset, Size +nvs, data, nvs, 0x9000, 0x4000 +phy_init, data, phy, 0xd000, 0x1000 +factory, app, factory, 0x10000, 0x300000 +littlefs, data, littlefs, 0x310000, 0xCF0000 diff --git a/RotaxMonitor/partitions/no_ota_10mb_spiffs.csv b/RotaxMonitor/partitions/no_ota_10mb_spiffs.csv new file mode 100644 index 0000000..c585eb5 --- /dev/null +++ b/RotaxMonitor/partitions/no_ota_10mb_spiffs.csv @@ -0,0 +1,6 @@ +# ESP32 Partition Table +# Name, Type, SubType, Offset, Size +nvs, data, nvs, 0x9000, 0x4000 +phy_init, data, phy, 0xd000, 0x1000 +factory, app, factory, 0x10000, 0x300000 +spiffs, data, spiffs, 0x310000, 0xCF0000 \ No newline at end of file diff --git a/RotaxMonitor/platformio.ini b/RotaxMonitor/platformio.ini index 117d9d2..e7fb4f6 100644 --- a/RotaxMonitor/platformio.ini +++ b/RotaxMonitor/platformio.ini @@ -10,59 +10,61 @@ [env:esp32-s3-devkitc1-n16r8] board = esp32-s3-devkitc1-n16r8 +board_build.partitions = partitions/no_ota_10mb_littlefs.csv +board_build.filesystem = littlefs platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip framework = arduino lib_deps = hideakitai/DebugLog@^0.8.4 bblanchon/ArduinoJson@^7.4.2 hideakitai/PCA95x5@^0.1.3 - adafruit/Adafruit SSD1306@^2.5.16 - garfius/Menu-UI@^1.2.0 - -;Upload protocol configuration + me-no-dev/AsyncTCP@^3.3.2 + me-no-dev/ESPAsyncWebServer@^3.6.0 + adafruit/Adafruit NeoPixel@^1.15.4 upload_protocol = esptool -upload_port = /dev/ttyACM2 +upload_port = /dev/ttyACM1 upload_speed = 921600 - -;Monitor configuration -monitor_port = /dev/ttyACM2 +monitor_port = /dev/ttyACM0 monitor_speed = 921600 - -; Build configuration build_type = release build_flags = - -DARDUINO_USB_CDC_ON_BOOT=0 - -DARDUINO_USB_MODE=0 - -fstack-protector-all - -DCONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=1 - -DCONFIG_FREERTOS_USE_TRACE_FACILITY=1 + -DCORE_DEBUG_LEVEL=1 + -DARDUINO_USB_CDC_ON_BOOT=0 + -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_RUNNING_CORE=1 + -DCONFIG_ASYNC_TCP_STACK_SIZE=4096 + -fstack-protector-all [env:esp32-s3-devkitc1-n16r8-debug] board = ${env:esp32-s3-devkitc1-n16r8.board} +board_build.partitions = ${env:esp32-s3-devkitc1-n16r8.board_build.partitions} +board_build.filesystem = ${env:esp32-s3-devkitc1-n16r8.board_build.filesystem} platform = ${env:esp32-s3-devkitc1-n16r8.platform} framework = ${env:esp32-s3-devkitc1-n16r8.framework} -lib_deps = ${env:esp32-s3-devkitc1-n16r8.lib_deps} - -;Upload protocol configuration +lib_deps = + ${env:esp32-s3-devkitc1-n16r8.lib_deps} + adafruit/Adafruit NeoPixel@^1.15.4 upload_protocol = esptool -upload_port = /dev/ttyACM2 +upload_port = /dev/ttyACM1 upload_speed = 921600 - -;Monitor configuration -monitor_port = /dev/ttyACM2 +monitor_port = /dev/ttyACM0 monitor_speed = 921600 - -; Debug configuration debug_tool = esp-builtin debug_speed = 15000 - -; Build configuration build_type = debug build_flags = - -O0 - -g3 - -ggdb3 - -DCORE_DEBUG_LEVEL=5 - -DARDUINO_USB_CDC_ON_BOOT=0 - -DARDUINO_USB_MODE=0 - -fstack-protector-all + -O0 + -g3 + -ggdb3 + -DCORE_DEBUG_LEVEL=3 + -DARDUINO_USB_CDC_ON_BOOT=0 + -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_RUNNING_CORE=1 + -DCONFIG_ASYNC_TCP_STACK_SIZE=4096 + -fstack-protector-all diff --git a/RotaxMonitor/src/channels.h b/RotaxMonitor/src/channels.h deleted file mode 100644 index f305685..0000000 --- a/RotaxMonitor/src/channels.h +++ /dev/null @@ -1,11 +0,0 @@ -// ADC Channels - -#define A1_RAW 0 -#define A2_RAW 1 -#define B1_RAW 2 -#define B2_RAW 3 - -#define A1_COND 4 -#define A2_COND 5 -#define B1_COND 6 -#define B2_COND 7 diff --git a/RotaxMonitor/src/datasave.cpp b/RotaxMonitor/src/datasave.cpp new file mode 100644 index 0000000..49b9c81 --- /dev/null +++ b/RotaxMonitor/src/datasave.cpp @@ -0,0 +1,221 @@ +#include "datasave.h" +#include + +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) +{ + 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(); + m_count = 0; + m_data_valid = false; +} + +void ignitionBoxStatusAverage::update(const ignitionBoxStatus &new_status) +{ + if (m_count == 0 && !m_data_valid) + { + m_last = new_status; + } + 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 + m_data_valid = true; // set data valid flag after first average is calculated + } +} + +const bool ignitionBoxStatusAverage::get(ignitionBoxStatus &status) const +{ + if (m_data_valid) + { + status = m_last; + } + return m_data_valid; +} + +const ArduinoJson::JsonDocument ignitionBoxStatusAverage::toJson() const +{ + ArduinoJson::JsonDocument doc; + 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; + doc["coils12"]["spark_delay"] = m_last.coils12.spark_delay; + doc["coils12"]["spark_status"] = sparkStatusNames.at(m_last.coils12.spark_status); + doc["coils12"]["peak_p_in"] = m_last.coils12.peak_p_in; + doc["coils12"]["peak_n_in"] = m_last.coils12.peak_n_in; + 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; + doc["coils34"]["spark_status"] = sparkStatusNames.at(m_last.coils34.spark_status); + doc["coils34"]["peak_p_in"] = m_last.coils34.peak_p_in; + doc["coils34"]["peak_n_in"] = m_last.coils34.peak_n_in; + doc["coils34"]["peak_p_out"] = m_last.coils34.peak_p_out; + doc["coils34"]["peak_n_out"] = m_last.coils34.peak_n_out; + doc["coils34"]["sstart_status"] = softStartStatusNames.at(m_last.coils34.sstart_status); + + doc["eng_rpm"] = m_last.eng_rpm; + doc["adc_read_time"] = m_last.adc_read_time; + doc["n_queue_errors"] = m_last.n_queue_errors; + } + return doc; +} + +void saveHistoryTask(void *pvParameters) +{ + const auto *params = static_cast(pvParameters); + const auto &history = *params->history; + const auto &file_path = params->file_path; + if (!params) + { + LOG_ERROR("Invalid parameters for saveHistoryTask"); + return; + } + LOG_DEBUG("Starting saving: ", file_path.c_str()); + save_history(history, file_path); + vTaskDelete(NULL); +} + +void save_history(const PSRAMVector &history, const std::filesystem::path &file_name) +{ + // Initialize SPIFFS + if (!SAVE_HISTORY_TO_LITTLEFS) + return; + + 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 + { + LOG_ERROR("Not enough space in SPIFFS to save history"); + return; + } + + std::filesystem::path file_path = file_name; + if (file_name.root_path() != "/littlefs") + file_path = std::filesystem::path("/littlefs") / file_name; + + auto save_flags = std::ios::out; + if (first_save && LittleFS.exists(file_path.c_str())) + { + first_save = false; + 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()); + } + else + { + save_flags |= std::ios::app; // append to new file + LOG_INFO("Saving history to LittleFS, appending to existing file:", file_path.c_str()); + } + + std::ofstream ofs(file_path, save_flags); + if (ofs.fail()) + { + LOG_ERROR("Failed to open file for writing"); + return; + } + + // 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" + << std::endl; + ofs.flush(); + } + + for (const auto &entry : history) + { + ofs << std::to_string(entry.timestamp) << "," + << std::to_string(entry.coils12.n_events) << "," + << std::to_string(entry.coils12.spark_delay) << "," + << std::string(sparkStatusNames.at(entry.coils12.spark_status)) << "," + << std::to_string(entry.coils12.peak_p_in) << "," + << std::to_string(entry.coils12.peak_n_in) << "," + << std::to_string(entry.coils12.peak_p_out) << "," + << std::to_string(entry.coils12.peak_n_out) << "," + << std::string(softStartStatusNames.at(entry.coils12.sstart_status)) << "," + << std::to_string(entry.coils34.n_events) << "," + << std::to_string(entry.coils34.spark_delay) << "," + << std::string(sparkStatusNames.at(entry.coils34.spark_status)) << "," + << std::to_string(entry.coils34.peak_p_in) << "," + << std::to_string(entry.coils34.peak_n_in) << "," + << std::to_string(entry.coils34.peak_p_out) << "," + << std::to_string(entry.coils34.peak_n_out) << "," + << std::string(softStartStatusNames.at(entry.coils34.sstart_status)) << "," + << std::to_string(entry.eng_rpm) << "," + << std::to_string(entry.adc_read_time) << "," + << std::to_string(entry.n_queue_errors); + ofs << std::endl; + ofs.flush(); + } + + ofs.close(); + LOG_INFO("Ignition A history saved to LittleFS, records written: ", history.size()); +} diff --git a/RotaxMonitor/src/datasave.h b/RotaxMonitor/src/datasave.h new file mode 100644 index 0000000..d834308 --- /dev/null +++ b/RotaxMonitor/src/datasave.h @@ -0,0 +1,62 @@ +#pragma once +#define DEBUGLOG_DEFAULT_LOG_LEVEL_INFO + +// System Includes +#include +#include +#include +#include +#include +#include +#include + +// 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 LittleFS, false to disable +static bool first_save = true; // flag to indicate if this is the first save (to write header) + +struct dataSaveParams +{ + const PSRAMVector *history; + const std::filesystem::path file_path; +}; + +class LITTLEFSGuard +{ +public: + LITTLEFSGuard(); + ~LITTLEFSGuard(); +}; + +class ignitionBoxStatusAverage +{ +private: + ignitionBoxStatus m_last; + uint32_t m_count = 0; + uint32_t m_max_count = 100; // number of samples to average before resetting + bool m_data_valid = false; // flag to indicate if the average data is valid (i.e. at least one sample has been added) + +public: + ignitionBoxStatusAverage() = default; + 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 +void saveHistoryTask(void *pvParameters); +void save_history(const PSRAMVector &history, const std::filesystem::path &file_path); diff --git a/RotaxMonitor/src/datastruct.h b/RotaxMonitor/src/datastruct.h new file mode 100644 index 0000000..5ded4d2 --- /dev/null +++ b/RotaxMonitor/src/datastruct.h @@ -0,0 +1,95 @@ +#pragma once +#include +#include +#include + +// ===================== +// 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 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 softStartStatusNames = { + {NORMAL, "NORMAL"}, + {SOFT_START, "SOFT_START"}, + {ERROR, "ERROR"}, +}; + +struct coilsStatus +{ + int64_t trig_time = 0; + int64_t spark_time = 0; + 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; + 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 + int32_t eng_rpm = 0; + // debug values + uint32_t n_queue_errors = 0; + int32_t adc_read_time = 0; +}; + + +template +using PSRAMVector = std::vector>; diff --git a/RotaxMonitor/src/devices.h b/RotaxMonitor/src/devices.h index e6027e5..d43d2c1 100644 --- a/RotaxMonitor/src/devices.h +++ b/RotaxMonitor/src/devices.h @@ -6,7 +6,6 @@ // Device Libraries #include #include -#include #include // ADC Channel mapping @@ -23,7 +22,6 @@ struct Devices { AD5292 *pot_a = NULL, *pot_b = NULL; ADS1256 *adc_a = NULL, *adc_b = NULL; - Adafruit_SSD1306* lcd = NULL; PCA9555* io = NULL; }; diff --git a/RotaxMonitor/src/isr.cpp b/RotaxMonitor/src/isr.cpp index a0c84c9..c87ad0b 100644 --- a/RotaxMonitor/src/isr.cpp +++ b/RotaxMonitor/src/isr.cpp @@ -4,7 +4,7 @@ // ISR (Pass return bitmask to ISR management function) // one function for each wake up pin conncted to a trigger // ===================== -void trig_isr(void *arg) +void trig_isr_A(void *arg) { const int64_t time_us = esp_timer_get_time(); @@ -36,18 +36,13 @@ void trig_isr(void *arg) xTaskNotifyFromISR(task_handle, params->flag, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); break; case SPARK_FLAG_12: - box->coils34.spark_ok = false; - box->coils12.spark_ok = true; + box->coils12.spark_time = time_us; xTaskNotifyFromISR(task_handle, params->flag, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); - // vTaskNotifyGiveFromISR(task_handle, &xHigherPriorityTaskWoken); break; case SPARK_FLAG_34: - box->coils12.spark_ok = false; - box->coils34.spark_ok = true; box->coils34.spark_time = time_us; xTaskNotifyFromISR(task_handle, params->flag, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); - // vTaskNotifyGiveFromISR(task_handle, &xHigherPriorityTaskWoken); break; default: break; @@ -55,4 +50,53 @@ void trig_isr(void *arg) if (xHigherPriorityTaskWoken) portYIELD_FROM_ISR(); -} \ No newline at end of file +} + + +void trig_isr_B(void *arg) +{ + const int64_t time_us = esp_timer_get_time(); + + // exit if invalid args + if (!arg) + return; + + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + isrParams *params = (isrParams *)arg; + ignitionBoxStatus *box = params->ign_stat; + TaskHandle_t task_handle = params->rt_handle_ptr; + + // exit if task not running + if (!task_handle) + return; + + switch (params->flag) + { + case TRIG_FLAG_12P: + case TRIG_FLAG_12N: + // only on first trigger to avoid multiple firing due to noise, to be fixed with hardware debounce + box->coils12.trig_time = time_us; + xTaskNotifyFromISR(task_handle, params->flag, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); + break; + case TRIG_FLAG_34P: + case TRIG_FLAG_34N: + // only on first trigger to avoid multiple firing due to noise, to be fixed with hardware debounce + box->coils34.trig_time = time_us; + xTaskNotifyFromISR(task_handle, params->flag, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); + break; + case SPARK_FLAG_12: + + box->coils12.spark_time = time_us; + xTaskNotifyFromISR(task_handle, params->flag, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); + break; + case SPARK_FLAG_34: + box->coils34.spark_time = time_us; + xTaskNotifyFromISR(task_handle, params->flag, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); + break; + default: + break; + } + + if (xHigherPriorityTaskWoken) + portYIELD_FROM_ISR(); +} diff --git a/RotaxMonitor/src/isr.h b/RotaxMonitor/src/isr.h index c75e2c0..bd1bea5 100644 --- a/RotaxMonitor/src/isr.h +++ b/RotaxMonitor/src/isr.h @@ -12,95 +12,12 @@ #else #include "pins_test.h" #endif +#include "datastruct.h" #define CORE_0 0 #define CORE_1 1 -#define TASK_STACK 4096 // in words -#define 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_NIL = (1 << 8); -static const uint32_t SPARK_FLAG_12 = (1 << 9); -static const uint32_t SPARK_FLAG_34 = (1 << 10); -static const uint32_t SPARK_FLAG_TIMEOUT = (1 << 11); - -// 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 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 softStartStatusNames = { - {NORMAL, "NORMAL"}, - {SOFT_START, "SOFT_START"}, - {ERROR, "ERROR"}, -}; - -struct coilsStatus -{ - int64_t trig_time = 0; - int64_t spark_time = 0; - int64_t spark_delay = 0; // in microseconds - sparkStatus spark_status = sparkStatus::SPARK_POS_OK; - softStartStatus sstart_status = softStartStatus::NORMAL; - float peak_p_in = 0.0, peak_n_in = 0.0; - float peak_p_out = 0.0, peak_n_out = 0.0; - float trigger_spark = 0.0; - bool spark_ok = false; - 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; - uint32_t n_queue_errors = 0; - uint32_t adc_read_time = 0; -}; +#define RT_TASK_STACK 2048 // in words +#define RT_TASK_PRIORITY (configMAX_PRIORITIES - 6) // highest priority after wifi tasks struct isrParams { @@ -109,4 +26,5 @@ struct isrParams TaskHandle_t rt_handle_ptr; }; -void IRAM_ATTR trig_isr(void *arg); +void IRAM_ATTR trig_isr_A(void *arg); +void IRAM_ATTR trig_isr_B(void *arg); diff --git a/RotaxMonitor/src/main.cpp b/RotaxMonitor/src/main.cpp index ef60521..c45e175 100644 --- a/RotaxMonitor/src/main.cpp +++ b/RotaxMonitor/src/main.cpp @@ -1,35 +1,28 @@ -#define DEBUGLOG_DEFAULT_LOG_LEVEL_INFO +#define DEBUGLOG_DEFAULT_LOG_LEVEL_DEBUG // Arduino Libraries #include #include #include #include +#include +#include // Definitions #include -#include #include +#include +#include #include +#include -// FreeRTOS directives -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" - -// #define CH_B_ENABLE +// Defines to enable channel B +#define CH_B_ENABLE #define TEST -float freqToRPM(float freq) -{ - return freq * 60.0f; // 1 pulse per revolution -} - -void printTaskStats() -{ - char buffer[1024]; - vTaskGetRunTimeStats(buffer); - Serial.println(buffer); -} +// Debug Defines +#define WIFI_SSID "AstroRotaxMonitor" +#define WIFI_PASSWORD "maledettirotax" void setup() { @@ -41,16 +34,38 @@ void setup() LOG_SET_LEVEL(DebugLogLevel::LVL_INFO); // Print Processor Info - LOG_INFO("ESP32 Chip:", ESP.getChipModel()); + LOG_DEBUG("ESP32 Chip:", ESP.getChipModel()); if (psramFound()) { - LOG_INFO("ESP32 PSram Found"); - LOG_INFO("ESP32 PSram:", ESP.getPsramSize()); + LOG_DEBUG("ESP32 PSram Found"); + LOG_DEBUG("ESP32 PSram:", ESP.getPsramSize()); psramInit(); } - LOG_INFO("ESP32 Flash:", ESP.getFlashChipSize()); - LOG_INFO("ESP32 Heap:", ESP.getHeapSize()); - LOG_INFO("ESP32 Sketch:", ESP.getFreeSketchSpace()); + LOG_DEBUG("ESP32 Flash:", ESP.getFlashChipSize()); + LOG_DEBUG("ESP32 Heap:", ESP.getHeapSize()); + LOG_DEBUG("ESP32 Sketch:", ESP.getFreeSketchSpace()); + + // Init Wifi station + LOG_INFO("Initializing WiFi..."); + WiFi.mode(WIFI_AP); + IPAddress local_IP(10, 11, 12, 1); + IPAddress gateway(10, 11, 12, 1); + IPAddress subnet(255, 255, 255, 0); + WiFi.softAPConfig(local_IP, gateway, subnet); + if (WiFi.softAP(WIFI_SSID, WIFI_PASSWORD)) + { + LOG_INFO("WiFi AP Mode Started"); + LOG_INFO("Wifi SSID:", WIFI_SSID); + LOG_INFO("Wifi Password:", WIFI_PASSWORD); + LOG_INFO("WiFi IP:" + WiFi.softAPIP().toString()); + } + else + { + LOG_ERROR("Failed to start WiFi AP Mode"); + LOG_ERROR("5 seconds to restart..."); + vTaskDelay(pdMS_TO_TICKS(5000)); + esp_restart(); + } // Initialize Interrupt pins on PICKUP detectors initTriggerPinsInputs(); @@ -58,65 +73,91 @@ void setup() initSparkPinInputs(); } +////////////////////// MAIN LOOP ////////////////////// void loop() { // global variables + RGBled led; + led.setStatus(RGBled::LedStatus::INIT); bool running = true; - static Devices dev; + const uint32_t max_queue = 128; + const uint32_t filter_k = 10; + PSRAMVector ignA_history_0(max_history); + PSRAMVector ignA_history_1(max_history); + auto *active_history_A = &ignA_history_0; + auto *writable_history_A = &ignA_history_1; + +#ifdef CH_B_ENABLE + PSRAMVector ignB_history_0(max_history); + PSRAMVector ignB_history_1(max_history); + auto *active_history_B = &ignB_history_0; + auto *writable_history_B = &ignB_history_1; +#endif + + // Resources Initialization + Devices dev; // Task handle - static TaskHandle_t trigA_TaskHandle = NULL; - static TaskHandle_t trigB_TaskHandle = NULL; + TaskHandle_t trigA_TaskHandle = NULL; + TaskHandle_t trigB_TaskHandle = NULL; + // Data Queue for real time task to main loop communication + QueueHandle_t rt_taskA_queue = xQueueCreate(max_queue, sizeof(ignitionBoxStatus)); + QueueHandle_t rt_taskB_queue = xQueueCreate(max_queue, sizeof(ignitionBoxStatus)); - static QueueHandle_t rt_taskA_queue = xQueueCreate(10, sizeof(ignitionBoxStatus)); - static QueueHandle_t rt_taskB_queue = xQueueCreate(10, sizeof(ignitionBoxStatus)); - static rtTaskParams taskA_params{ + rtTaskParams taskA_params{ .rt_running = true, .dev = &dev, - .rt_handle_ptr = &trigA_TaskHandle, .rt_queue = rt_taskA_queue, .rt_int = rtTaskInterrupts{ - .isr_ptr = trig_isr, + .isr_ptr = &trig_isr_A, .trig_pin_12p = TRIG_PIN_A12P, .trig_pin_12n = TRIG_PIN_A12N, .trig_pin_34p = TRIG_PIN_A34P, .trig_pin_34n = TRIG_PIN_A34N, .spark_pin_12 = SPARK_PIN_A12, .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_INFO("Task Variables OK"); + .rt_resets = rtTaskResets{.rst_io_peak = RST_EXT_PEAK_DETECT_A, .rst_io_sh = RST_EXT_SAMPLE_HOLD_A}}; #ifdef CH_B_ENABLE - QueueHandle_t rt_taskB_queue = xQueueCreate(10, sizeof(ignitionBoxStatus)); rtTaskParams taskB_params{ .rt_running = true, .dev = &dev, - .rt_handle_ptr = &trigB_TaskHandle, .rt_queue = rt_taskB_queue, .rt_int = rtTaskInterrupts{ - .isr_ptr = trig_isr, + .isr_ptr = &trig_isr_B, .trig_pin_12p = TRIG_PIN_B12P, .trig_pin_12n = TRIG_PIN_B12N, .trig_pin_34p = TRIG_PIN_B34P, .trig_pin_34n = TRIG_PIN_B34N, .spark_pin_12 = SPARK_PIN_B12, .spark_pin_34 = SPARK_PIN_B34}, - .rt_resets = rtTaskResets{.rst_io_12p = RST_EXT_B12P, .rst_io_12n = RST_EXT_B12N, .rst_io_34p = RST_EXT_B34P, .rst_io_34n = RST_EXT_B34N}}; + .rt_resets = rtTaskResets{.rst_io_peak = RST_EXT_PEAK_DETECT_B, .rst_io_sh = RST_EXT_SAMPLE_HOLD_B}}; #endif + 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"); + + // Spi ok flags 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 - #ifndef TEST +#ifdef CH_B_ENABLE +#ifndef TEST 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 +#endif +#endif if (!spiA_ok || !spiB_ok) { LOG_ERROR("Unable to Initialize SPI Busses"); @@ -124,121 +165,182 @@ void loop() vTaskDelay(pdMS_TO_TICKS(5000)); esp_restart(); } - LOG_INFO("Init SPI OK"); + LOG_DEBUG("Init SPI OK"); +#ifndef TEST // Init ADC_A - dev.adc_a = new ADS1256(ADC_A_DRDY, ADS1256::PIN_UNUSED, ADC_A_SYNC, ADC_A_CS, 2.5, &SPI_A); + dev.adc_a = new ADS1256(ADC_A_DRDY, ADS1256::PIN_UNUSED, ADS1256::PIN_UNUSED, ADC_A_CS, 2.5, &SPI_A); dev.adc_a->InitializeADC(); dev.adc_a->setPGA(PGA_1); dev.adc_a->setDRATE(DRATE_7500SPS); - +#endif +#ifdef CH_B_ENABLE #ifndef TEST // Init ADC_B - dev.adc_a = new ADS1256(ADC_B_DRDY, ADC_B_RST, ADC_B_SYNC, ADC_B_CS, 2.5, &SPI_B); + dev.adc_a = new ADS1256(ADC_B_DRDY, ADS1256::PIN_UNUSED, ADS1256::PIN_UNUSED, ADC_B_CS, 2.5, &SPI_B); dev.adc_a->InitializeADC(); dev.adc_a->setPGA(PGA_1); dev.adc_a->setDRATE(DRATE_1000SPS); +#endif #endif - LOG_INFO("Init ADC OK"); + LOG_DEBUG("Init ADC OK"); // Ignition A on Core 0 auto ignA_task_success = pdPASS; ignA_task_success = xTaskCreatePinnedToCore( rtIgnitionTask, - "rtIgnitionTask_boxA", - TASK_STACK, + "rtTask_A", + RT_TASK_STACK, (void *)&taskA_params, - TASK_PRIORITY, + RT_TASK_PRIORITY, &trigA_TaskHandle, CORE_0); + delay(100); // give some time to the thread to start // Ignition B on Core 1 auto ignB_task_success = pdPASS; + #ifdef CH_B_ENABLE ignB_task_success = xTaskCreatePinnedToCore( rtIgnitionTask, - "rtIgnitionTask_boxB", - TASK_STACK, + "rtTask_B", + RT_TASK_STACK, (void *)&taskB_params, - TASK_PRIORITY, // priorità leggermente più alta + RT_TASK_PRIORITY, // priorità leggermente più alta &trigB_TaskHandle, CORE_1); + delay(100); // give some time to the thread to start #endif - if ((ignA_task_success && ignB_task_success) != pdPASS) + if (ignA_task_success != pdPASS || ignB_task_success != pdPASS) { - LOG_ERROR("Unble to initialize ISR task"); + LOG_ERROR("Unable to initialize ISR task"); LOG_ERROR("5 seconds to restart..."); vTaskDelay(pdMS_TO_TICKS(5000)); esp_restart(); } - LOG_INFO("Real Time Tasks A & B initialized"); + LOG_DEBUG("Real Time Tasks A & B initialized"); + led.setStatus(RGBled::LedStatus::OK); - ////////////////////// MAIN LOOP ////////////////////// - clearScreen(); - setCursor(0, 0); - ignitionBoxStatus ignA; - int64_t last = esp_timer_get_time(); - uint32_t missed_firings12 = 0; - uint32_t missed_firings34 = 0; - uint32_t counter = 0; + bool partial_save = false; // flag to indicate if a partial save has been done after a timeout + auto last_data = millis(); + auto last_info = millis(); + uint32_t counter_a = 0; + uint32_t counter_b = 0; + uint32_t wait_count = 0; + + ignitionBoxStatus ign_info_A; + ignitionBoxStatus ign_info_B; + + ignitionBoxStatusAverage ign_info_avg_A(filter_k); + ignitionBoxStatusAverage ign_info_avg_B(filter_k); + + LITTLEFSGuard fsGuard; + WebPage webPage(80, LittleFS); // Initialize webserver and Websocket + + //////////////// INNER LOOP ///////////////////// while (running) { - if (xQueueReceive(rt_taskA_queue, &ignA, pdMS_TO_TICKS(1000)) == pdTRUE) + auto dataA = pdFALSE; + auto dataB = pdFALSE; + + dataA = xQueueReceive(rt_taskA_queue, &ign_info_A, 0); + if (counter_a >= active_history_A->size()) // not concurrent with write task { - float freq = (esp_timer_get_time() - last) / 1000000.0f; // in seconds - freq = freq > 0 ? 1.0f / freq : 0; // Calculate frequency (Hz) - last = esp_timer_get_time(); - - 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", (uint32_t)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", (uint32_t)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", freqToRPM(freq)); - printField("ADC Read Time", (uint32_t)ignA.adc_read_time); - printField("Queue Errors", (uint32_t)ignA.n_queue_errors); + counter_a = 0; + partial_save = false; // reset partial save flag on new data cycle + swapHistory(active_history_A, writable_history_A); + save_history(*writable_history_A, "ignition_historyA.csv"); // directly call the save task function to save without delay } + +#ifdef CH_B_ENABLE + dataB = xQueueReceive(rt_taskB_queue, &ign_info_B, 0); + if (counter_b >= active_history_B->size()) // not concurrent with write task + { + counter_b = 0; + partial_save = false; // reset partial save flag on new data cycle + swapHistory(active_history_B, writable_history_B); + save_history(*writable_history_B, "ignition_historyB.csv"); // directly call the save task function to save without delay + } +#endif + // Update last data + if (dataA == pdTRUE || dataB == pdTRUE) + last_data = millis(); + + // Update Led color + if (dataA == pdTRUE && dataB == pdFALSE) + led.setStatus(RGBled::DATA_A); + else if (dataB == pdTRUE && dataA == pdFALSE) + led.setStatus(RGBled::DATA_B); else + led.setStatus(RGBled::DATA_ALL); + + if (dataA == pdTRUE) { - Serial.println("Waiting for data... "); - delay(500); + (*active_history_A)[counter_a++ % active_history_A->size()] = ign_info_A; + ign_info_avg_A.update(ign_info_A); // update moving average with latest ignition status + // Serial.printf("Data Received A: %d/%d\n\r", counter_a, (*active_history_A).size()); + if (counter_a % filter_k == 0) // send data every 10 samples + { + ArduinoJson::JsonDocument wsData; + wsData["box_a"] = ign_info_avg_A.toJson(); + wsData["box_b"] = JsonObject(); + webPage.sendWsData(wsData.as()); + } } - } +#ifdef CH_B_ENABLE + if (dataB == pdTRUE) + { + (*active_history_B)[counter_b++ % active_history_B->size()] = ign_info_B; + ign_info_avg_B.update(ign_info_B); // update moving average with latest ignition status + // Serial.printf("Data Received B: %d/%d\n\r", counter_b, (*active_history_B).size()); + if (counter_b % filter_k == 0) // send data every 10 samples + { + ArduinoJson::JsonDocument wsData; + wsData["box_a"] = JsonObject(); + wsData["box_b"] = ign_info_avg_B.toJson(); + webPage.sendWsData(wsData.as()); + } + } +#endif + if (dataA == pdFALSE && dataB == pdFALSE && (millis() - last_data) > 2000) + { + if (!partial_save && counter_a > 0) // if timeout occurs but we have unsaved data, save it before next timeout + { + active_history_A->resize(counter_a); // resize active history to actual number of records received to avoid saving empty records + save_history(*active_history_A, "ignition_history_A.csv"); + active_history_A->resize(max_history); // resize back to max history size for next data cycle +#ifdef CH_B_ENABLE + active_history_B->resize(counter_a); // resize active history to actual number of records received to avoid saving empty records + save_history(*active_history_B, "ignition_history_B.csv"); + active_history_B->resize(max_history); // resize back to max history size for next data cycle +#endif + counter_a = 0; // reset counter after saving + counter_b = 0; // reset counter after saving + + partial_save = true; + first_save = true; + } + // Serial.printf("[%d] Waiting for data...\r", wait_count++); + led.setStatus(RGBled::LedStatus::IDLE); + delay(100); + } + + if ((millis() - last_info) > 1000) + { + clearScreen(); + Serial.println(); + printRunningTasksMod(Serial); + last_info = millis(); + } + } //////////////// INNER LOOP ///////////////////// if (trigA_TaskHandle) vTaskDelete(trigA_TaskHandle); if (trigB_TaskHandle) vTaskDelete(trigB_TaskHandle); - ////////////////////// MAIN LOOP ////////////////////// -} + +} ////////////////////// MAIN LOOP ////////////////////// diff --git a/RotaxMonitor/src/pins.h b/RotaxMonitor/src/pins.h index 62a3ded..4d93ef3 100644 --- a/RotaxMonitor/src/pins.h +++ b/RotaxMonitor/src/pins.h @@ -4,19 +4,19 @@ // ===================== // USB (RISERVATA) // ===================== -#define USB_DM 19 -#define USB_DP 20 +#define USB_DM 19 +#define USB_DP 20 // ===================== // UART DEBUG (RISERVATA) // ===================== -#define UART_TX 43 -#define UART_RX 44 +#define UART_TX 43 +#define UART_RX 44 // ===================== // RGB Led // ===================== -#define LED 48 +#define LED 48 // ===================== // STRAPPING CRITICI (NON USARE) @@ -26,85 +26,84 @@ // ===================== // SPI BUS ADC1 (VSPI) // ===================== -#define SPI_A_MOSI 11 -#define SPI_A_MISO 13 -#define SPI_A_SCK 12 +#define SPI_A_MOSI 10 +#define SPI_A_SCK 11 +#define SPI_A_MISO 12 // ===================== // SPI BUS ADC2 (HSPI) // ===================== -#define SPI_B_MOSI 35 -#define SPI_B_MISO 37 -#define SPI_B_SCK 36 +#define SPI_B_MOSI 36 +#define SPI_B_SCK 37 +#define SPI_B_MISO 38 // ===================== // I2C BUS (PCA9555) // ===================== -#define SDA 8 -#define SCL 9 +#define SDA 8 +#define SCL 9 +#define I2C_INT 17 // ===================== // ADC CONTROL // ===================== -#define ADC_A_CS 4 -#define ADC_A_DRDY 5 -#define ADC_A_SYNC 6 +#define ADC_A_CS 14 +#define ADC_A_DRDY 13 -#define ADC_B_CS 14 -#define ADC_B_DRDY 15 -#define ADC_B_SYNC 16 +#define ADC_B_CS 21 +#define ADC_B_DRDY 47 // ===================== // DIGITAL POT // ===================== -#define POT_A_CS 7 -#define POT_B_CS 17 +#define POT_A_CS 18 +#define POT_B_CS 35 // ===================== // TRIGGER INPUT INTERRUPTS // ===================== -#define TRIG_PIN_A12P 18 -#define TRIG_PIN_A12N 21 -#define TRIG_PIN_A34P 1 -#define TRIG_PIN_A34N 2 -#define TRIG_PIN_B12P 38 -#define TRIG_PIN_B12N 39 -#define TRIG_PIN_B34P 40 -#define TRIG_PIN_B34N 41 +#define TRIG_PIN_A12P 6 +#define TRIG_PIN_A12N 7 +#define TRIG_PIN_A34P 15 +#define TRIG_PIN_A34N 16 +#define TRIG_PIN_B12P 42 +#define TRIG_PIN_B12N 41 +#define TRIG_PIN_B34P 40 +#define TRIG_PIN_B34N 39 // ===================== // SPARK DETECT INPUTS // ===================== -#define SPARK_PIN_A12 42 -#define SPARK_PIN_A34 45 // OK (strapping ma consentito) 45 -#define SPARK_PIN_B12 46 // OK (strapping ma consentito) 46 -#define SPARK_PIN_B34 47 +#define SPARK_PIN_A12 4 +#define SPARK_PIN_A34 5 +#define SPARK_PIN_B12 1 +#define SPARK_PIN_B34 2 // ===================== // PCA9555 (I2C EXPANDER) // ===================== // --- RESET LINES --- -#define RST_EXT_A12P 0 -#define RST_EXT_A12N 1 -#define RST_EXT_A34P 2 -#define RST_EXT_A34N 3 -#define RST_EXT_B12P 4 -#define RST_EXT_B12N 5 -#define RST_EXT_B34P 6 -#define RST_EXT_B34N 7 +#define RST_EXT_PEAK_DETECT_A 0 +#define RST_EXT_SAMPLE_HOLD_A 1 +#define RST_EXT_PEAK_DETECT_B 2 +#define RST_EXT_SAMPLE_HOLD_B 3 +#define BTN_3 4 +#define BTN_4 5 +#define BTN_5 6 +#define BTN_6 7 // --- RELAY --- -#define A_EXT_RELAY 8 -#define B_EXT_RELAY 9 +#define EXT_RELAY_A 8 +#define EXT_RELAY_B 9 // --- STATUS / BUTTON --- -#define BTN_3 10 -#define BTN_4 11 -#define STA_1 12 -#define STA_2 13 -#define STA_3 14 -#define STA_4 15 +#define BTN_7 10 +#define BTN_8 11 +#define STA_1 12 +#define STA_2 13 +#define STA_3 14 +#define STA_4 15 // Init Pin Functions inline void initTriggerPinsInputs() diff --git a/RotaxMonitor/src/pins_test.h b/RotaxMonitor/src/pins_test.h index 4db981f..16f196e 100644 --- a/RotaxMonitor/src/pins_test.h +++ b/RotaxMonitor/src/pins_test.h @@ -65,7 +65,7 @@ #define RST_EXT_A34N 3 // --- RELAY --- -#define A_EXT_RELAY 8 +#define EXT_RELAY_A 8 // Init Pin Functions diff --git a/RotaxMonitor/src/psvector.h b/RotaxMonitor/src/psvector.h new file mode 100644 index 0000000..2bf2013 --- /dev/null +++ b/RotaxMonitor/src/psvector.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include "esp_heap_caps.h" + +// Allocator custom per PSRAM +template +struct PSRAMAllocator { + using value_type = T; + + PSRAMAllocator() noexcept {} + + template + PSRAMAllocator(const PSRAMAllocator&) noexcept {} + + T* allocate(std::size_t n) { + void* ptr = heap_caps_malloc(n * sizeof(T), MALLOC_CAP_SPIRAM); + if (!ptr) { + throw std::bad_alloc(); + } + return static_cast(ptr); + } + + void deallocate(T* p, std::size_t) noexcept { + heap_caps_free(p); + } +}; diff --git a/RotaxMonitor/src/tasks.cpp b/RotaxMonitor/src/tasks.cpp index 8a72bc0..948a29f 100644 --- a/RotaxMonitor/src/tasks.cpp +++ b/RotaxMonitor/src/tasks.cpp @@ -2,7 +2,8 @@ #include // Timeout callback for microsecond precision -void spark_timeout_callback(void* arg) { +void spark_timeout_callback(void *arg) +{ TaskHandle_t handle = (TaskHandle_t)arg; xTaskNotify(handle, SPARK_FLAG_TIMEOUT, eSetValueWithOverwrite); } @@ -16,58 +17,65 @@ void rtIgnitionTask(void *pvParameters) LOG_ERROR("Null rt_task_ptr parameters"); vTaskDelete(NULL); } - LOG_INFO("rtTask Params OK"); + // Task Parameters and Devices rtTaskParams *params = (rtTaskParams *)pvParameters; const rtTaskInterrupts rt_int = params->rt_int; // copy to avoid external override const rtTaskResets rt_rst = params->rt_resets; // copy to avoid external override QueueHandle_t rt_queue = params->rt_queue; - TaskHandle_t rt_handle_ptr = *params->rt_handle_ptr; Devices *dev = params->dev; ADS1256 *adc = dev->adc_a; PCA9555 *io = dev->io; + TaskStatus_t rt_task_info; + vTaskGetInfo(NULL, &rt_task_info, pdFALSE, eInvalid); + + const auto rt_task_name = pcTaskGetName(rt_task_info.xHandle); + LOG_INFO("rtTask Params OK [", rt_task_name, "]"); + ignitionBoxStatus ign_box_sts; // Variables for ISR, static to be fixed in memory locations - static isrParams isr_params_t12p{ + isrParams isr_params_t12p{ .flag = TRIG_FLAG_12P, .ign_stat = &ign_box_sts, - .rt_handle_ptr = rt_handle_ptr}; - static isrParams isr_params_t12n{ + .rt_handle_ptr = rt_task_info.xHandle}; + isrParams isr_params_t12n{ .flag = TRIG_FLAG_12N, .ign_stat = &ign_box_sts, - .rt_handle_ptr = rt_handle_ptr}; - static isrParams isr_params_t34p{ + .rt_handle_ptr = rt_task_info.xHandle}; + isrParams isr_params_t34p{ .flag = TRIG_FLAG_34P, .ign_stat = &ign_box_sts, - .rt_handle_ptr = rt_handle_ptr}; - static isrParams isr_params_t34n{ + .rt_handle_ptr = rt_task_info.xHandle}; + isrParams isr_params_t34n{ .flag = TRIG_FLAG_34N, .ign_stat = &ign_box_sts, - .rt_handle_ptr = rt_handle_ptr}; - static isrParams isr_params_sp12{ + .rt_handle_ptr = rt_task_info.xHandle}; + isrParams isr_params_sp12{ .flag = SPARK_FLAG_12, .ign_stat = &ign_box_sts, - .rt_handle_ptr = rt_handle_ptr}; - static isrParams isr_params_sp34{ + .rt_handle_ptr = rt_task_info.xHandle}; + isrParams isr_params_sp34{ .flag = SPARK_FLAG_34, .ign_stat = &ign_box_sts, - .rt_handle_ptr = rt_handle_ptr}; - - LOG_INFO("rtTask ISR Params OK"); + .rt_handle_ptr = rt_task_info.xHandle}; + + LOG_DEBUG("rtTask HDL Params OK, HDL* [", (uint32_t)rt_task_info.xHandle, "]"); + LOG_DEBUG("rtTask ISR Params OK, ISR* [", (uint32_t)rt_int.isr_ptr, "]"); + LOG_DEBUG("rtTask QUE Params OK, QUE* [", (uint32_t)rt_queue, "]"); // Create esp_timer for microsecond precision timeout esp_timer_handle_t timeout_timer; esp_timer_create_args_t timer_args = { .callback = spark_timeout_callback, - .arg = (void*)rt_handle_ptr, + .arg = (void *)rt_task_info.xHandle, .dispatch_method = ESP_TIMER_TASK, - .name = "spark_timeout" - }; + .name = "spark_timeout"}; esp_timer_create(&timer_args, &timeout_timer); + // Attach Pin Interrupts attachInterruptArg(digitalPinToInterrupt(rt_int.trig_pin_12p), rt_int.isr_ptr, (void *)&isr_params_t12p, RISING); attachInterruptArg(digitalPinToInterrupt(rt_int.trig_pin_12n), rt_int.isr_ptr, (void *)&isr_params_t12n, RISING); @@ -76,19 +84,14 @@ void rtIgnitionTask(void *pvParameters) attachInterruptArg(digitalPinToInterrupt(rt_int.spark_pin_12), rt_int.isr_ptr, (void *)&isr_params_sp12, RISING); attachInterruptArg(digitalPinToInterrupt(rt_int.spark_pin_34), rt_int.isr_ptr, (void *)&isr_params_sp34, RISING); - LOG_INFO("rtTask ISR Attach OK"); + LOG_INFO("rtTask ISR Attach OK [", rt_task_name, "]"); - // Compute Reset Pin Bitmask - const uint16_t rst_bitmask = (1 << rt_rst.rst_io_12p) | - (1 << rt_rst.rst_io_12n) | - (1 << rt_rst.rst_io_34p) | - (1 << rt_rst.rst_io_34n); - - LOG_WARN("rtTask Init Correct"); // Global rt_task_ptr variables bool first_cycle = true; bool cycle12 = false; bool cycle34 = false; + int64_t last_cycle_time = 0; + uint32_t n_errors = 0; while (params->rt_running) { @@ -101,62 +104,27 @@ void rtIgnitionTask(void *pvParameters) ULONG_MAX, // pulisci i primi 8 bit &pickup_flag, // valore ricevuto portMAX_DELAY); - + if (first_cycle && pickup_flag != TRIG_FLAG_12P) // skip first cycle because of possible initial noise on pickup signals at startu - { continue; - } - -#ifdef DEBUG - Serial.print("\033[2J"); // clear screen - Serial.print("\033[H"); // cursor home - LOG_INFO("Iteration [", it++, "]"); - - if (!names.contains(pickup_flag)) - { - LOG_ERROR("Wrong Pickup Flag"); - LOG_ERROR("Pickup Flags: ", printBits(pickup_flag).c_str()); - continue; - } - else - { - LOG_INFO("Pickup Trigger: ", names.at(pickup_flag)); - } -#endif // Start microsecond precision timeout timer esp_timer_stop(timeout_timer); // stop timer in case it was running from previous cycle esp_timer_start_once(timeout_timer, spark_timeout_max); - spark_flag = SPARK_FLAG_NIL; // default value in case of timeout, to be set by ISR if spark event occours // WAIT FOR SPARK TO HAPPEN OR TIMEOUT - BaseType_t sp = pdFALSE; - sp = xTaskNotifyWait( - 0x00, // non pulire all'ingresso - ULONG_MAX, // pulisci i primi 8 bit - &spark_flag, // valore ricevuto + xTaskNotifyWait( + 0x00, // non pulire all'ingresso + ULONG_MAX, // pulisci i primi 8 bit + &spark_flag, // valore ricevuto portMAX_DELAY); // wait indefinitely, timeout handled by esp_timer // Handle timeout or spark event - if (spark_flag == SPARK_FLAG_TIMEOUT) { - spark_flag = SPARK_FLAG_NIL; - } else { - // Spark occurred, stop the timer + if (spark_flag != SPARK_FLAG_TIMEOUT) esp_timer_stop(timeout_timer); - } - -#ifdef DEBUG - // LOG_INFO("Spark Flags: ", printBits(spark_flag).c_str()); - LOG_INFO("Spark12:", ign_box_sts.coils12.spark_ok ? "TRUE" : "FALSE"); - LOG_INFO("Spark34:", ign_box_sts.coils34.spark_ok ? "TRUE" : "FALSE"); - if (names.contains(spark_flag)) - LOG_INFO("Spark Trigger:", names.at(spark_flag)); -#endif - xTaskNotifyStateClear(NULL); - ulTaskNotifyValueClear(NULL, 0xFFFFFFFF); // A trigger from pickup 12 is followed by a spark event on 34 or vice versa pickup 34 triggers spark on 12 - if ((pickup_flag == TRIG_FLAG_12P || pickup_flag == TRIG_FLAG_12N) && (spark_flag != SPARK_FLAG_12 && spark_flag != SPARK_FLAG_NIL)) + if ((pickup_flag == TRIG_FLAG_12P || pickup_flag == TRIG_FLAG_12N) && (spark_flag != SPARK_FLAG_12 && spark_flag != SPARK_FLAG_TIMEOUT)) { ign_box_sts.coils12.spark_status = ign_box_sts.coils34.spark_status = sparkStatus::SPARK_SYNC_FAIL; continue; @@ -167,7 +135,14 @@ void rtIgnitionTask(void *pvParameters) switch (pickup_flag) { case TRIG_FLAG_12P: + { first_cycle = false; + // compute engine rpm from cycle time + 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 = (int32_t)(60.0f / (cycle_time / 1000000.0f)); + } case TRIG_FLAG_12N: coils = &ign_box_sts.coils12; break; @@ -184,18 +159,14 @@ void rtIgnitionTask(void *pvParameters) case TRIG_FLAG_34P: { // Timeout not occourred, expected POSITIVE edge spark OCCOURRED - if (spark_flag != SPARK_FLAG_NIL) + if (spark_flag != SPARK_FLAG_TIMEOUT) { - coils->spark_delay = 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 -#ifdef DEBUG - LOG_INFO("Spark on POSITIVE pulse"); - LOG_INFO("Spark Delay Time: ", (int32_t)coils->spark_delay); -#endif } // Timeout occourred, expected POSITIVE edge spark NOT OCCOURRED - else if (spark_flag == SPARK_FLAG_NIL) + else if (spark_flag == SPARK_FLAG_TIMEOUT) { coils->spark_status = sparkStatus::SPARK_NEG_WAIT; coils->sstart_status = softStartStatus::NORMAL; @@ -208,24 +179,20 @@ void rtIgnitionTask(void *pvParameters) { const bool expected_negative = coils->spark_status == sparkStatus::SPARK_NEG_WAIT; // Timeout not occourred, expected NEGATIVE edge spark OCCOURRED - if (spark_flag != SPARK_FLAG_NIL && expected_negative) + if (spark_flag != SPARK_FLAG_TIMEOUT && expected_negative) { - coils->spark_delay = 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; -#ifdef DEBUG - LOG_INFO("Spark on NEGATIVE pulse"); - LOG_INFO("Spark Delay Time: ", (int32_t)coils->spark_delay); -#endif } // Timeout occourred, expected POSITIVE edge spark NOT OCCOURRED - else if (spark_flag == SPARK_FLAG_NIL && expected_negative) + else if (spark_flag == SPARK_FLAG_TIMEOUT && expected_negative) { coils->sstart_status = softStartStatus::ERROR; coils->spark_status = sparkStatus::SPARK_NEG_FAIL; } // Timeout not occouured, unexpected negative edge spark - else if (spark_flag != SPARK_FLAG_NIL && !expected_negative) + else if (spark_flag != SPARK_FLAG_TIMEOUT && !expected_negative) { coils->sstart_status = softStartStatus::SOFT_START; coils->spark_status = sparkStatus::SPARK_NEG_UNEXPECTED; @@ -239,11 +206,6 @@ void rtIgnitionTask(void *pvParameters) break; } default: -#ifdef DEUG - LOG_ERROR("Invalid Interrupt"); - LOG_ERROR("Pickup Flags: ", printBits(pickup_flag).c_str()); - LOG_ERROR("Spark Flags: ", printBits(spark_flag).c_str()); -#endif break; } @@ -251,6 +213,12 @@ 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 { @@ -264,7 +232,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)); @@ -274,6 +242,7 @@ void rtIgnitionTask(void *pvParameters) if (io) { const uint16_t iostat = io->read(); + const uint16_t rst_bitmask = (0x0001 << rt_rst.rst_io_peak); io->write(iostat | rst_bitmask); vTaskDelay(pdMS_TO_TICKS(1)); io->write(iostat & ~rst_bitmask); @@ -283,13 +252,10 @@ 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) { - static uint32_t n_errors = 0; - n_errors++; - ign_box_sts.n_queue_errors = n_errors; - LOG_ERROR("Failed to send to 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; } } } diff --git a/RotaxMonitor/src/tasks.h b/RotaxMonitor/src/tasks.h index c1196eb..542f96d 100644 --- a/RotaxMonitor/src/tasks.h +++ b/RotaxMonitor/src/tasks.h @@ -46,10 +46,8 @@ struct rtTaskInterrupts // RT Task Peak Detector Reset pins struct rtTaskResets { - const uint8_t rst_io_12p; - const uint8_t rst_io_12n; - const uint8_t rst_io_34p; - const uint8_t rst_io_34n; + const uint8_t rst_io_peak; + const uint8_t rst_io_sh; }; // RT task parameters @@ -57,7 +55,6 @@ struct rtTaskParams { bool rt_running; // run flag, false to terminate Devices *dev; - TaskHandle_t* rt_handle_ptr; const QueueHandle_t rt_queue; const rtTaskInterrupts rt_int; // interrupt pins to attach const rtTaskResets rt_resets; // reset ping for peak detectors diff --git a/RotaxMonitor/src/ui.cpp b/RotaxMonitor/src/ui.cpp new file mode 100644 index 0000000..80e31e5 --- /dev/null +++ b/RotaxMonitor/src/ui.cpp @@ -0,0 +1,73 @@ +#include + +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: %06u\n", name, val); +} + +void printField(const char name[], const int32_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); +} diff --git a/RotaxMonitor/src/ui.h b/RotaxMonitor/src/ui.h index 9c801be..36b2a11 100644 --- a/RotaxMonitor/src/ui.h +++ b/RotaxMonitor/src/ui.h @@ -1,36 +1,16 @@ #pragma once #include +#include +#include -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 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); -void setCursor(const uint8_t x, const uint8_t y) -{ - Serial.printf("\033[%d;%d", y, x + 1); - Serial.flush(); -} +void printInfo(const ignitionBoxStatus &info); -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); -} \ No newline at end of file diff --git a/RotaxMonitor/src/utils.cpp b/RotaxMonitor/src/utils.cpp index 222f913..a1fad94 100644 --- a/RotaxMonitor/src/utils.cpp +++ b/RotaxMonitor/src/utils.cpp @@ -1,4 +1,15 @@ #include "utils.h" +#include "freertos_stats.h" +#include "sdkconfig.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/portable.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 result; @@ -12,3 +23,60 @@ std::string printBits(uint32_t value) { } return result; } + +void printRunningTasksMod(Print &printer, std::function orderBy) +{ + static const char *taskStates[] = {"Running", "Ready", "Blocked", "Suspended", "Deleted", "Invalid"}; + + static uint32_t ulRunTimeCounters[FREERTOS_TASK_NUMBER_MAX_NUM]; + static uint32_t ulLastRunTime = 0; + uint32_t ulCurrentRunTime = 0, ulTaskRunTime = 0; + uint32_t ulTotalRunTime = 0; + + std::vector pxTaskStatusArray; + UBaseType_t uxArraySize = 0; + + // Take a snapshot of the number of tasks in case it changes while this function is executing. + uxArraySize = uxTaskGetNumberOfTasks(); + pxTaskStatusArray.resize(uxArraySize); + + // Generate raw status information about each task. + uxArraySize = uxTaskGetSystemState(pxTaskStatusArray.data(), uxArraySize, &ulTotalRunTime); + + if (orderBy == nullptr) + std::sort(pxTaskStatusArray.begin(), pxTaskStatusArray.end(), [](const TaskStatus_t &a, const TaskStatus_t &b) + { return a.xTaskNumber < b.xTaskNumber; }); + else + std::sort(pxTaskStatusArray.begin(), pxTaskStatusArray.end(), orderBy); + + // Compute system total runtime + ulCurrentRunTime = ulTotalRunTime - ulLastRunTime; + ulLastRunTime = ulTotalRunTime; + + // Print Runtime Information + printer.printf("Tasks: %u, Runtime: %lus, Period: %luus\r\n", uxArraySize, ulTotalRunTime / 1000000, ulCurrentRunTime); + + // Print Task Headers + printer.printf("Num\t Name\tLoad\tPrio\t Free\tCore\tState\r\n"); + for (const auto &task : pxTaskStatusArray) + { + + ulTaskRunTime = (task.ulRunTimeCounter - ulRunTimeCounters[task.xTaskNumber]); + ulRunTimeCounters[task.xTaskNumber] = task.ulRunTimeCounter; + ulTaskRunTime = (ulTaskRunTime * 100) / ulCurrentRunTime; // in percentage + + printer.printf( + "%3u\t%16s" + "\t%3lu%%" + "\t%4u\t%5lu" + "\t%4c" + "\t%s\r\n", + task.xTaskNumber, task.pcTaskName, + ulTaskRunTime, + task.uxCurrentPriority, task.usStackHighWaterMark, + (task.xCoreID == tskNO_AFFINITY) ? '*' : ('0' + task.xCoreID), + taskStates[task.eCurrentState]); + } + printer.println(); +} + diff --git a/RotaxMonitor/src/utils.h b/RotaxMonitor/src/utils.h index 2a1bf71..f74653e 100644 --- a/RotaxMonitor/src/utils.h +++ b/RotaxMonitor/src/utils.h @@ -2,5 +2,14 @@ #include #include +#include std::string printBits(uint32_t value); + +void printRunningTasksMod(Print &printer, std::function orderBy = nullptr); + +inline void swapHistory(PSRAMVector* active, PSRAMVector* writable) { + auto *temp = active; + active = writable; // switch active and writable buffers + writable = temp; // ensure writable_history points to the buffer we just filled +} diff --git a/RotaxMonitor/src/webserver.cpp b/RotaxMonitor/src/webserver.cpp new file mode 100644 index 0000000..937be9f --- /dev/null +++ b/RotaxMonitor/src/webserver.cpp @@ -0,0 +1,98 @@ +#include + +WebPage::WebPage(const uint8_t port, fs::FS &filesystem) : m_port(port), m_webserver(AsyncWebServer(port)), m_websocket(AsyncWebSocket("/ws")), m_filesystem(filesystem) +{ + m_websocket.onEvent([this](AsyncWebSocket *server, AsyncWebSocketClient *client, + AwsEventType type, void *arg, uint8_t *data, size_t len) + { onWsEvent(server, client, type, arg, data, len); }); + + m_webserver.addHandler(&m_websocket); + m_webserver.serveStatic("/", m_filesystem, "/").setDefaultFile("index.html"); + + m_webserver.on("/upload", HTTP_POST, + [this](AsyncWebServerRequest *request) + { onUploadRequest(request); }, + [this](AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final) + { onUploadHandler(request, filename, index, data, len, final); } + ); + + m_webserver.begin(); +} + +WebPage::~WebPage() +{ + m_webserver.removeHandler(&m_websocket); + m_webserver.end(); +} + +void WebPage::sendWsData(const String &data){ + if (m_websocket.count()){ + m_websocket.textAll(data); + } +} + +void WebPage::onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) +{ + switch (type) + { + case WS_EVT_CONNECT: + Serial.printf("WS client IP[%s]-ID[%u] CONNECTED\r\n", client->remoteIP().toString().c_str(), client->id()); + break; + case WS_EVT_DISCONNECT: + Serial.printf("WS client ID[%u] DISCONNECTED\r\n", client->remoteIP().toString().c_str(), client->id()); + break; + } +} + +void WebPage::onUploadRequest(AsyncWebServerRequest *request) +{ + if (m_upload_failed) + request->send(500, "text/plain", "Upload failed"); + else + request->send(200, "text/plain", "Upload successful"); +} + +void WebPage::onUploadHandler(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final) +{ + if (index == 0) // only on first iteration to open file + { + m_upload_failed = false; + String safeName = filename; + int slashIndex = safeName.lastIndexOf('/'); + if (slashIndex >= 0) + safeName = safeName.substring(slashIndex + 1); + if (safeName.length() == 0) + { + m_upload_failed = true; + LOG_ERROR("Invalid file name"); + return; + } + + const std::filesystem::path filePath = std::filesystem::path(m_filesystem.mountpoint()) / safeName.c_str(); + if (m_filesystem.exists(filePath.c_str())) + m_filesystem.remove(filePath.c_str()); + + m_upload_file = m_filesystem.open(filePath.c_str(), FILE_WRITE); + if (!m_upload_file) + { + m_upload_failed = true; + LOG_ERROR("Failed to open upload file:", filePath.c_str()); + return; + } + } + + // Actual write of file data + if (!m_upload_failed && m_upload_file) + { + if (m_upload_file.write(data, len) != len) + m_upload_failed = true; + } + + // close the file and save on final call + if (final && m_upload_file) + { + m_upload_file.close(); + if (!m_upload_failed) + LOG_INFO("Uploaded file to LittleFS:", filename.c_str()); + } +} diff --git a/RotaxMonitor/src/webserver.h b/RotaxMonitor/src/webserver.h new file mode 100644 index 0000000..5e230f3 --- /dev/null +++ b/RotaxMonitor/src/webserver.h @@ -0,0 +1,38 @@ +#pragma once +#define DEBUGLOG_DEFAULT_LOG_LEVEL_INFO + +// System includes +#include +#include +#include +#include +#include +#include + +class WebPage +{ + const uint8_t m_port = 80; + fs::FS &m_filesystem; + AsyncWebServer m_webserver; + AsyncWebSocket m_websocket; + bool m_upload_failed = false; + fs::File m_upload_file; + +public: + WebPage(const uint8_t port, fs::FS &filesystem); + ~WebPage(); + + void sendWsData(const String &data); + +private: + void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, + AwsEventType type, void *arg, uint8_t *data, size_t len); + + void onUploadRequest(AsyncWebServerRequest *request); + void onUploadHandler(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final); + + void onStart(AsyncWebServerRequest *request); + void onStop(AsyncWebServerRequest *request); + void onDownload(AsyncWebServerRequest *request); + +}; diff --git a/RotaxMonitorTester/.vscode/extensions.json b/RotaxMonitorTester/.vscode/extensions.json index 411655e..b397401 100644 --- a/RotaxMonitorTester/.vscode/extensions.json +++ b/RotaxMonitorTester/.vscode/extensions.json @@ -1,8 +1,7 @@ { "recommendations": [ "Jason2866.esp-decoder", - "pioarduino.pioarduino-ide", - "platformio.platformio-ide" + "pioarduino.pioarduino-ide" ], "unwantedRecommendations": [ "ms-vscode.cpptools-extension-pack" diff --git a/RotaxMonitorTester/src/main.cpp b/RotaxMonitorTester/src/main.cpp index ea657e7..e2fafd5 100644 --- a/RotaxMonitorTester/src/main.cpp +++ b/RotaxMonitorTester/src/main.cpp @@ -1,3 +1,5 @@ +#define DEBUGLOG_DEFAULT_LOG_LEVEL_DEBUG + #include #include @@ -16,15 +18,16 @@ static uint32_t count = 0; #define SPARK_DLY_MAX 490 #define PAUSE_LONG_MIN 5000 -#define PAUSE_LONG_MAX PAUSE_LONG_MIN*100 +#define PAUSE_LONG_MAX PAUSE_LONG_MIN * 100 -#define RPM_MIN 800 +#define RPM_MIN 250 #define RPM_MAX 5500 -void clearScreen(){ - Serial.print("\033[2J"); // clear screen - Serial.print("\033[H"); // cursor home - Serial.flush(); +void clearScreen() +{ + Serial.print("\033[2J"); // clear screen + Serial.print("\033[H"); // cursor home + Serial.flush(); } static double filtered_rpm = 0; @@ -46,18 +49,44 @@ static const std::map pin2Name = { {State::S_WAIT_10MS, "S_WAIT_10MS"}}; static timerStatus stsA = { - .clock_period_us = (uint32_t)PERIOD_US, - .pause_long_us = 10000, - .pause_short_us = 1000, - .coil_pulse_us = 1000, - .spark_pulse_us = 100, - .spark_delay_us = 50, - .main_task = NULL}; + .clock_period_us = (uint32_t)PERIOD_US, + .pause_long_us = 10000, + .pause_short_us = 1000, + .coil_pulse_us = 1000, + .spark_pulse_us = 100, + .spark_delay_us = 50, + .pins = { + .pin_trig_12p = PIN_TRIG_A12P, + .pin_trig_12n = PIN_TRIG_A12N, + .pin_trig_34p = PIN_TRIG_A34P, + .pin_trig_34n = PIN_TRIG_A34N, + .pin_spark_12 = SPARK_A12, + .pin_spark_34 = SPARK_A34}, + .main_task = NULL}; + +static timerStatus stsB = { + .clock_period_us = (uint32_t)PERIOD_US, + .pause_long_us = 10000, + .pause_short_us = 1000, + .coil_pulse_us = 1000, + .spark_pulse_us = 100, + .spark_delay_us = 50, + .pins = { + .pin_trig_12p = PIN_TRIG_B12P, + .pin_trig_12n = PIN_TRIG_B12N, + .pin_trig_34p = PIN_TRIG_B34P, + .pin_trig_34n = PIN_TRIG_B34N, + .pin_spark_12 = SPARK_B12, + .pin_spark_34 = SPARK_B34}, + .main_task = NULL}; + +static bool isEnabled_A = false; +static bool isEnabled_B = false; void setup() { - Serial.begin(921600); + Serial.begin(115200); delay(1000); LOG_ATTACH_SERIAL(Serial); @@ -76,11 +105,28 @@ void setup() pinMode(SPARK_B34, OUTPUT); pinMode(SPARK_DELAY_POT, ANALOG); - stsA.main_task = xTaskGetCurrentTaskHandleForCore(1); + pinMode(FREQ_POT, ANALOG); + pinMode(ENABLE_PIN_A, INPUT_PULLUP); + pinMode(ENABLE_PIN_B, INPUT_PULLUP); + + // get the task handle for the main loop + stsA.main_task = xTaskGetCurrentTaskHandleForCore(1); + stsB.main_task = xTaskGetCurrentTaskHandleForCore(1); + + // Begin timer with preset fixed frequency timerA = timerBegin(FREQUENCY); + timerB = timerBegin(FREQUENCY); + + // Stop timers because of autostart + timerStop(timerA); + timerStop(timerB); + + // Attach interrupts and call callback every timer expiry timerAttachInterruptArg(timerA, &onTimer, (void *)&stsA); - timerAlarm(timerA, 1, true, 0); + timerAttachInterruptArg(timerB, &onTimer, (void *)&stsB); + timerAlarm(timerA, 1, true, 0); // infinite number of reloads + timerAlarm(timerB, 1, true, 0); LOG_INFO("Setup Complete"); } @@ -90,21 +136,59 @@ void loop() LOG_INFO("Loop: ", count++); uint32_t spark_delay = (uint32_t)(map(analogRead(SPARK_DELAY_POT), 0, 4096, SPARK_DLY_MIN, SPARK_DLY_MAX) / PERIOD_US); stsA.spark_delay_us = spark_delay * PERIOD_US; - if (stsA.spark_delay_us > (SPARK_DLY_MIN + SPARK_DLY_MAX) / 2) { + if (stsA.spark_delay_us > (SPARK_DLY_MIN + SPARK_DLY_MAX) / 2) + { stsA.soft_start = true; stsA.spark_delay_us -= (SPARK_DLY_MIN + SPARK_DLY_MAX) / 2; - } else { + } + else + { stsA.soft_start = false; } + stsB.soft_start = stsA.soft_start; + stsB.spark_delay_us = stsA.spark_delay_us; double new_rpm = (double)(map(analogRead(FREQ_POT), 0, 4096, RPM_MIN, RPM_MAX)); filtered_rpm = filtered_rpm + 0.1 * (new_rpm - filtered_rpm); stsA.pause_long_us = (uint32_t)(60000000.0f / filtered_rpm / 2.0f); + stsB.pause_long_us = stsA.pause_long_us; + + if (isEnabled_A) + LOG_INFO("==== System A is ENABLED ===="); + else + LOG_INFO("==== System A is DISABLED ===="); + + if (isEnabled_B) + LOG_INFO("==== System B is ENABLED ===="); + else + LOG_INFO("==== System B is DISABLED ===="); + LOG_INFO("Spark Delay uS: ", stsA.spark_delay_us, "\tSoft Start: ", stsA.soft_start ? "TRUE" : "FALSE"); LOG_INFO("Engine Rpm: ", (uint32_t)(filtered_rpm)); LOG_INFO("Coil Pulse: ", stsA.coil_pulse_us, "us"); LOG_INFO("Spark Pulse: ", stsA.spark_pulse_us, "us"); + if (digitalRead(ENABLE_PIN_A) == LOW && !isEnabled_A) + { + timerStart(timerA); + isEnabled_A = true; + } + else if (digitalRead(ENABLE_PIN_A) == HIGH && isEnabled_A) + { + timerStop(timerA); + isEnabled_A = false; + } + + if (digitalRead(ENABLE_PIN_B) == LOW && !isEnabled_B) + { + timerStart(timerB); + isEnabled_B = true; + } + else if (digitalRead(ENABLE_PIN_B) == HIGH && isEnabled_B) + { + timerStop(timerB); + isEnabled_B = false; + } delay(100); clearScreen(); diff --git a/RotaxMonitorTester/src/pins.h b/RotaxMonitorTester/src/pins.h index cced640..04427a1 100644 --- a/RotaxMonitorTester/src/pins.h +++ b/RotaxMonitorTester/src/pins.h @@ -1,5 +1,9 @@ #pragma once +// Enable Pin +#define ENABLE_PIN_A 16 +#define ENABLE_PIN_B 15 + ///// Ignition Box A ///// #define PIN_TRIG_A12P 18 #define PIN_TRIG_A12N 19 diff --git a/RotaxMonitorTester/src/timer.cpp b/RotaxMonitorTester/src/timer.cpp index 6504230..90c1ba3 100644 --- a/RotaxMonitorTester/src/timer.cpp +++ b/RotaxMonitorTester/src/timer.cpp @@ -7,20 +7,18 @@ void onTimer(void *arg) BaseType_t xHigherPriorityTaskWoken = pdFALSE; timerStatus *params = (timerStatus *)(arg); TaskHandle_t task = params->main_task; + const timerPins pins = params->pins; // increment state time params->state_time += params->clock_period_us; - digitalWrite(PIN_TRIG_B12P, HIGH); - switch (params->state) { case S_12P: if (params->state_time == params->clock_period_us && !params->coil12p_high) { - // xTaskNotifyFromISR(task, PIN_TRIG_A12P, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); - digitalWrite(PIN_TRIG_A12P, HIGH); + digitalWrite(pins.pin_trig_12p, HIGH); params->coil12p_high = true; wait_sent = false; } @@ -29,21 +27,18 @@ void onTimer(void *arg) { if (params->state_time == params->spark_delay_us) { - // xTaskNotifyFromISR(task, SPARK_A12, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); - digitalWrite(SPARK_A12, HIGH); + digitalWrite(pins.pin_spark_12, HIGH); } if (params->state_time == (params->spark_delay_us + params->spark_pulse_us)) { - // xTaskNotifyFromISR(task, ~SPARK_A12, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); - digitalWrite(SPARK_A12, LOW); + digitalWrite(pins.pin_spark_12, LOW); } } if (params->state_time >= params->coil_pulse_us && params->coil12p_high) { - // xTaskNotifyFromISR(task, ~PIN_TRIG_A12P, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); - digitalWrite(PIN_TRIG_A12P, LOW); + digitalWrite(pins.pin_trig_12p, LOW); params->coil12p_high = false; } @@ -57,8 +52,7 @@ void onTimer(void *arg) case S_12N: if (params->state_time == params->clock_period_us && !params->coil12n_high) { - // xTaskNotifyFromISR(task, PIN_TRIG_A12N, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); - digitalWrite(PIN_TRIG_A12N, HIGH); + digitalWrite(pins.pin_trig_12n, HIGH); params->coil12n_high = true; } @@ -66,21 +60,18 @@ void onTimer(void *arg) { if (params->state_time == params->spark_delay_us) { - // xTaskNotifyFromISR(task, SPARK_A12, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); - digitalWrite(SPARK_A12, HIGH); + digitalWrite(pins.pin_spark_12, HIGH); } if (params->state_time == (params->spark_delay_us + params->spark_pulse_us)) { - // xTaskNotifyFromISR(task, ~SPARK_A12, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); - digitalWrite(SPARK_A12, LOW); + digitalWrite(pins.pin_spark_12, LOW); } } if (params->state_time >= params->coil_pulse_us && params->coil12n_high) { - // xTaskNotifyFromISR(task, ~PIN_TRIG_A12N, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); - digitalWrite(PIN_TRIG_A12N, LOW); + digitalWrite(pins.pin_trig_12n, LOW); params->coil12n_high = false; params->state = S_WAIT_10MS; params->state_time = 0; @@ -90,7 +81,6 @@ void onTimer(void *arg) case S_WAIT_10MS: if (!wait_sent) { - // xTaskNotifyFromISR(task, S_WAIT_10MS, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); wait_sent = true; } if (params->state_time >= params->pause_long_us) @@ -103,8 +93,7 @@ void onTimer(void *arg) case S_34P: if (params->state_time == params->clock_period_us && !params->coil34p_high) { - // xTaskNotifyFromISR(task, PIN_TRIG_A34P, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); - digitalWrite(PIN_TRIG_A34P, HIGH); + digitalWrite(pins.pin_trig_34p, HIGH); params->coil34p_high = true;; wait_sent = false; } @@ -113,21 +102,18 @@ void onTimer(void *arg) { if (params->state_time == params->spark_delay_us) { - // xTaskNotifyFromISR(task, SPARK_A34, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); - digitalWrite(SPARK_A34, HIGH); + digitalWrite(pins.pin_spark_34, HIGH); } if (params->state_time == params->spark_delay_us + params->spark_pulse_us) { - // xTaskNotifyFromISR(task, ~SPARK_A34, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); - digitalWrite(SPARK_A34, LOW); + digitalWrite(pins.pin_spark_34, LOW); } } if (params->state_time >= params->coil_pulse_us && params->coil34p_high) { - // xTaskNotifyFromISR(task, ~PIN_TRIG_A34P, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); - digitalWrite(PIN_TRIG_A34P, LOW); + digitalWrite(pins.pin_trig_34p, LOW); params->coil34p_high = false; } @@ -141,8 +127,7 @@ void onTimer(void *arg) case S_34N: if (params->state_time == params->clock_period_us && !params->coil34n_high) { - // xTaskNotifyFromISR(task, PIN_TRIG_A34N, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); - digitalWrite(PIN_TRIG_A34N, HIGH); + digitalWrite(pins.pin_trig_34n, HIGH); params->coil34n_high = true; } @@ -150,21 +135,18 @@ void onTimer(void *arg) { if (params->state_time == params->spark_delay_us) { - // xTaskNotifyFromISR(task, SPARK_A34, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); - digitalWrite(SPARK_A34, HIGH); + digitalWrite(pins.pin_spark_34, HIGH); } if (params->state_time == params->spark_delay_us + params->spark_pulse_us) { - // xTaskNotifyFromISR(task, ~SPARK_A34, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); - digitalWrite(SPARK_A34, LOW); + digitalWrite(pins.pin_spark_34, LOW); } } if (params->state_time >= params->coil_pulse_us && params->coil34n_high) { - // xTaskNotifyFromISR(task, ~PIN_TRIG_A34N, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); - digitalWrite(PIN_TRIG_A34N, LOW); + digitalWrite(pins.pin_trig_34n, LOW); params->coil34n_high = false; params->state = S_WAIT_10MS_END; params->state_time = 0; @@ -174,7 +156,6 @@ void onTimer(void *arg) case S_WAIT_10MS_END: if (!wait_sent) { - // xTaskNotifyFromISR(task, S_WAIT_10MS_END, eSetValueWithOverwrite, &xHigherPriorityTaskWoken); wait_sent = true; } if (params->state_time >= params->pause_long_us) @@ -184,9 +165,7 @@ void onTimer(void *arg) } break; } - - digitalWrite(PIN_TRIG_B12P, LOW); - + if (xHigherPriorityTaskWoken) portYIELD_FROM_ISR(); } diff --git a/RotaxMonitorTester/src/timer.h b/RotaxMonitorTester/src/timer.h index baf976b..711e33f 100644 --- a/RotaxMonitorTester/src/timer.h +++ b/RotaxMonitorTester/src/timer.h @@ -1,5 +1,7 @@ #pragma once +#define DEBUGLOG_DEFAULT_LOG_LEVEL_DEBUG + #include #include #include "pins.h" @@ -19,6 +21,15 @@ enum State S_WAIT_10MS_END }; +struct timerPins { + const uint8_t pin_trig_12p; + const uint8_t pin_trig_12n; + const uint8_t pin_trig_34p; + const uint8_t pin_trig_34n; + const uint8_t pin_spark_12; + const uint8_t pin_spark_34; +}; + struct timerStatus { State state = State::S_12P; @@ -34,6 +45,7 @@ struct timerStatus bool coil34p_high = false; bool coil12n_high = false; bool coil34n_high = false; + timerPins pins; TaskHandle_t main_task; };