From d337784faa7eb62849e3eeefa53209cdbbb657e8 Mon Sep 17 00:00:00 2001 From: Tobias Maier Date: Wed, 1 Apr 2026 20:11:53 +0200 Subject: [PATCH] Moved a lot of global vars to queues --- platformio.ini | 2 +- src/external_interfacing/leds.cpp | 22 ++--- src/global_data/global_data.cpp | 11 +-- src/global_data/global_data.h | 122 +++++++++++++++++------ src/main.cpp | 137 ++++++++++++++----------- src/networking/json_builder.cpp | 39 ++++---- src/networking/json_builder.h | 11 ++- src/networking/networking.cpp | 53 +++++++++- src/networking/webserver.cpp | 159 +++++++++++++++++++++++------- src/sensor/sensor.cpp | 53 ++++++---- src/telemetry/telemetry.cpp | 29 ++++-- src/tools/ota_handler.cpp | 59 +++++++++++ src/tools/ota_handler.h | 14 +++ src/tools/tools.cpp | 4 - src/tools/tools.h | 1 - 15 files changed, 512 insertions(+), 204 deletions(-) create mode 100644 src/tools/ota_handler.cpp create mode 100644 src/tools/ota_handler.h diff --git a/platformio.ini b/platformio.ini index 3a83530..5b716da 100644 --- a/platformio.ini +++ b/platformio.ini @@ -43,7 +43,7 @@ lib_deps = ${env:esp32_base.lib_deps} robtillaart/INA226@ ~0.6.4 upload_protocol = espota -upload_port = 192.168.18.18 +upload_port = 192.168.18.21 build_flags = ${env:esp32_base.build_flags} -DUSE_INA226 [env:native] diff --git a/src/external_interfacing/leds.cpp b/src/external_interfacing/leds.cpp index 3d638b5..28db08b 100644 --- a/src/external_interfacing/leds.cpp +++ b/src/external_interfacing/leds.cpp @@ -4,22 +4,20 @@ #include "../tools/tools.h" #include -extern ActiveErrors active_errors; -extern WaterData water_data; +// TODO: Rewrite so that this task does not do anything smart, just receives data over queue and displays it +// So it just receives: OK, Error, error code X, warning, warning X (blink) void display_task(void* parameter) { - LOG(ELOG_LEVEL_DEBUG, "Starting display tasks"); + LOG(ELOG_LEVEL_DEBUG, "Starting LED tasks"); + led_setup(); while (true) { - if (!is_error(active_errors)) { - // We have no error, refresh status display and wait half a second - ledcWrite(LED_GREEN, 255); - ledcWrite(LED_RED, 0); - } else { - ledcWrite(LED_RED, LED_RED_HIGH); - ledcWrite(LED_GREEN, 0); - } - delay(250); + ledcWrite(LED_RED, 0); + ledcWrite(LED_GREEN, 255); + delay(500); + ledcWrite(LED_RED, LED_RED_HIGH); + ledcWrite(LED_GREEN, 0); + delay(500); } } diff --git a/src/global_data/global_data.cpp b/src/global_data/global_data.cpp index 81ed409..302f00c 100644 --- a/src/global_data/global_data.cpp +++ b/src/global_data/global_data.cpp @@ -1,12 +1,3 @@ #include "global_data.h" -NetworkData wifi_data; -NetworkData ethernet_data; -DeviceTelemetry telemetry; - -SensorData shunt_data; -WaterData water_data; - -OTAStatus ota_status; - -ActiveErrors active_errors = { false, false, false, false, false, false }; \ No newline at end of file +OTAStatus ota_status; \ No newline at end of file diff --git a/src/global_data/global_data.h b/src/global_data/global_data.h index ea6cc09..2bc1e30 100644 --- a/src/global_data/global_data.h +++ b/src/global_data/global_data.h @@ -3,48 +3,112 @@ #pragma once +/** + * @brief Structure to hold sensor data. + * + * Contains voltage and current measurements from the sensor. + */ struct SensorData { - float bus_voltage; - float shunt_voltage; - float shunt_current; + float bus_voltage; /**< Bus voltage in volts */ + float shunt_voltage; /**< Shunt voltage in millivolts */ + float shunt_current; /**< Shunt current in milliamps */ }; +/** + * @brief Structure to hold water data. + * + * Contains water level, volume, and percentage measurements. + */ struct WaterData{ - // Water level in cm - float level; - // Water volume in liters - float liters; - // Percentage - float percentage; + float level; /**< Water level in cm */ + float liters; /**< Water volume in liters */ + float percentage; /**< Water level as a percentage */ }; - +/** + * @brief Structure to hold network data. + * + * Contains network-related information such as IP address and signal strength. + */ struct NetworkData { - String ip_address; - bool link; - float rssi; - String network_name; + char ip_address[16]; /**< IP address of the device (max 15 chars + null terminator) */ + bool link; /**< Whether the network link is active */ + float rssi; /**< Received signal strength indicator */ + char network_name[33]; /**< Name of the network (max 32 chars + null terminator) */ }; +/** + * @brief Structure to hold device telemetry data. + * + * Contains information about the device's internal state. + */ struct DeviceTelemetry { - float heap_used_percent; - int uptime_seconds; - float temperature; + float heap_used_percent; /**< Percentage of heap memory used */ + int uptime_seconds; /**< Uptime of the device in seconds */ + float temperature; /**< Temperature of the device in Celsius */ }; +/** + * @brief Structure to hold active error flags. + * + * Contains boolean flags indicating various error conditions. + */ struct ActiveErrors { - bool voltage_low; - bool voltage_high; - bool current_low; - bool current_high; - bool level_low; - bool level_high; + bool voltage_low; /**< Indicates if the voltage is too low */ + bool voltage_high; /**< Indicates if the voltage is too high */ + bool current_low; /**< Indicates if the current is too low */ + bool current_high; /**< Indicates if the current is too high */ + bool level_low; /**< Indicates if the water level is too low */ + bool level_high; /**< Indicates if the water level is too high */ }; +/** + * @brief Structure to hold OTA (Over-The-Air) update status. + * + * Contains information about the OTA update process. + */ struct OTAStatus { - bool update_available; - Version current_version; - Version latest_version; - int update_progress; - String update_url; -}; \ No newline at end of file + bool update_available; /**< Indicates if an update is available */ + Version current_version; /**< Current version of the firmware */ + Version latest_version; /**< Latest available version of the firmware */ + int update_progress; /**< Progress of the update as a percentage */ + String update_url; /**< URL to download the update */ +}; + +/** + * @brief Structure to hold the current state of the system. + * + * Contains all data structs to represent the current state of the system. + */ +typedef struct { + WaterData waterData; /**< Latest water data */ + SensorData sensorData; /**< Latest sensor data */ + NetworkData wifiData; /**< Latest WiFi network data */ + NetworkData ethernetData; /**< Latest Ethernet network data */ + DeviceTelemetry telemetryData; /**< Latest telemetry data */ + OTAStatus otaStatus; /**< Latest OTA status */ + ActiveErrors activeErrors; /**< Latest active errors */ +} CurrentState; + +// Enum to represent different types of data that can be sent between tasks +typedef enum { + DATA_TYPE_SENSOR, + DATA_TYPE_WATER, + DATA_TYPE_WIFI, + DATA_TYPE_ETHERNET, + DATA_TYPE_TELEMETRY +} DataType; + +// Union to hold different types of data +typedef union { + SensorData sensorData; + WaterData waterData; + NetworkData networkData; + DeviceTelemetry telemetryData; +} DataUnion; + +// Structure to hold data type and the actual data +typedef struct { + DataType type; + DataUnion data; +} DataMessage; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index f5ef096..0a061da 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,6 +12,7 @@ #include #include "global_data/defines.h" +#include "global_data/global_data.h" #include "networking/networking.h" #include "networking/webserver.h" @@ -25,31 +26,41 @@ #include #include "time.h" #include "tools/log.h" +#include "tools/ota_handler.h" #include +#include "esp_heap_caps.h" + #define MYLOG 0 Preferences prefs; -extern DeviceTelemetry telemetry; -extern NetworkData wifi_data; -extern NetworkData ethernet_data; -extern SensorData shunt_data; -extern ActiveErrors active_errors; - extern AsyncWebSocket webSocket; Version current_spiffs_version; +// Variable to store the current state of the system +CurrentState current_state = { + {-999.0f, -999.0f, -999.0f}, + {-999.0f, -999.0f, -999.0f}, + {"0.0.0.0", false, -999.0f, "UNKNOWN"}, // wifiData + {"0.0.0.0", false, -999.0f, "UNKNOWN"}, // ethernetData + {-999.0f, -999, -999.0f}, + {false, {0, 0, 0}, {0, 0, 0}, -999, "UNKNOWN"}, + {false, false, false, false, false, false} +}; + #define FORMAT_LITTLEFS_IF_FAILED true +// Queue for sending data from producers to the processor in main +QueueHandle_t dataQueue = xQueueCreate(5, sizeof(DataMessage)); + +// Queue for sending data from processor to different tasks +QueueHandle_t stateForWebserverQueue = xQueueCreate(2, sizeof(CurrentState)); + void setup() { Logger.registerSerial(MYLOG, ELOG_LEVEL_DEBUG, "Serial"); - - LOG(ELOG_LEVEL_DEBUG, "Init LEDs"); - led_setup(); - LOG(ELOG_LEVEL_DEBUG, "Init Starting prefs and Serial output"); prefs.begin("waterlevel", false); Serial.begin(115200); @@ -72,61 +83,75 @@ void setup() LOG(ELOG_LEVEL_DEBUG, "Current LittleFS Version: %d.%d.%d", current_spiffs_version.major, current_spiffs_version.minor, current_spiffs_version.patch); } LOG(ELOG_LEVEL_DEBUG, "LittleFS initialized"); - - /////////////////////////////// ROUTES /////////////////////////////// - LOG(ELOG_LEVEL_DEBUG, "Route Setup"); - - - LOG(ELOG_LEVEL_DEBUG, "OTA Setup"); - ArduinoOTA - .onStart([]() { - String type; - if (ArduinoOTA.getCommand() == U_FLASH) - type = "sketch"; - else // U_SPIFFS - type = "filesystem"; - - // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() - LOG(ELOG_LEVEL_DEBUG, "Start updating %s", type); }) - .onEnd([]() { LOG(ELOG_LEVEL_DEBUG, "\nEnd"); }) - .onProgress([](unsigned int progress, unsigned int total) { Serial.printf("Progress: %u%%\r", (progress / (total / 100))); }) - .onError([](ota_error_t error) { - Serial.printf("Error[%u]: ", error); - if (error == OTA_AUTH_ERROR) LOG(ELOG_LEVEL_DEBUG, "Auth Failed"); - else if (error == OTA_BEGIN_ERROR) LOG(ELOG_LEVEL_DEBUG, "Begin Failed"); - else if (error == OTA_CONNECT_ERROR) LOG(ELOG_LEVEL_DEBUG, "Connect Failed"); - else if (error == OTA_RECEIVE_ERROR) LOG(ELOG_LEVEL_DEBUG, "Receive Failed"); - else if (error == OTA_END_ERROR) LOG(ELOG_LEVEL_DEBUG, "End Failed"); }); - - LOG(ELOG_LEVEL_DEBUG, "Starting main tasks"); - // Create a queue for water data communication between sensor and webserver tasks - QueueHandle_t webserverWaterDataQueue = xQueueCreate(10, sizeof(WaterData)); - if (webserverWaterDataQueue == NULL) { - LOG(ELOG_LEVEL_ERROR, "Failed to create webserver water data queue"); + + if (dataQueue == NULL) { + LOG(ELOG_LEVEL_ERROR, "Failed to create data queue"); } else { - xTaskCreate(ethernet_task, "EthernetTask", 4096, NULL, 1, NULL); - xTaskCreate(wifi_task, "WiFiTask", 10000, NULL, 1, NULL); - xTaskCreate(read_sensor_task, "ReadSensorTask", 1024 * 4, webserverWaterDataQueue, 1, NULL); - xTaskCreate(collect_internal_telemetry_task, "InternalTelemetryTask", 2048, NULL, 1, NULL); - xTaskCreate(display_task, "DisplayTask", 10000, NULL, 1, NULL); - xTaskCreate(get_time_task, "GetTimeTask", 1024 * 4, NULL, 1, NULL); + xTaskCreate(ethernet_task, "EthernetTask", 1024 * 4, dataQueue, 1, NULL); + xTaskCreate(wifi_task, "WiFiTask", 1024 * 4, dataQueue, 1, NULL); + xTaskCreate(read_sensor_task, "ReadSensorTask", 1024 * 4, dataQueue, 1, NULL); + xTaskCreate(collect_internal_telemetry_task, "InternalTelemetryTask", 1024 * 2, dataQueue, 1, NULL); + xTaskCreate(display_task, "DisplayTask", 1024 * 2, NULL, 1, NULL); + xTaskCreate(get_time_task, "GetTimeTask", 1024 * 2, NULL, 1, NULL); delay(5000); - xTaskCreate(check_update_task, "CheckUpdateTask", 1024 * 8, NULL, 1, NULL); - xTaskCreate(webserver_task, "WebServerTask", 1024 * 8, webserverWaterDataQueue, 1, NULL); + xTaskCreate(check_update_task, "CheckUpdateTask", 1024 * 6, NULL, 1, NULL); + xTaskCreate(webserver_task, "WebServerTask", 1024 * 4, stateForWebserverQueue, 1, NULL); + xTaskCreate(ota_handler_task, "OTAHandlerTask", 1024 * 4, NULL, 3, NULL); } - - LOG(ELOG_LEVEL_DEBUG, "Starting OTA handler"); - ArduinoOTA.begin(); } - - - void loop() { - ArduinoOTA.handle(); + size_t free_heap = heap_caps_get_free_size(MALLOC_CAP_8BIT); + Serial.println(free_heap); + + // Check if there is new data in the queue + DataMessage dataMessage; + if (xQueueReceive(dataQueue, &dataMessage, portMAX_DELAY) == pdTRUE) { + // Decode the data based on its type and update the current state + switch (dataMessage.type) { + case DATA_TYPE_WATER: + current_state.waterData = dataMessage.data.waterData; + LOG(ELOG_LEVEL_DEBUG, "Received water data: level=%F, liters=%F, percentage=%F", + current_state.waterData.level, current_state.waterData.liters, current_state.waterData.percentage); + break; + case DATA_TYPE_SENSOR: + current_state.sensorData = dataMessage.data.sensorData; + LOG(ELOG_LEVEL_DEBUG, "Received sensor data: bus_voltage=%F, shunt_voltage=%F, shunt_current=%F", + current_state.sensorData.bus_voltage, current_state.sensorData.shunt_voltage, current_state.sensorData.shunt_current); + break; + case DATA_TYPE_WIFI: + current_state.wifiData = dataMessage.data.networkData; + LOG(ELOG_LEVEL_DEBUG, "Received WiFi data: ip=%s, link=%d, rssi=%F, name=%s", + current_state.wifiData.ip_address, current_state.wifiData.link, + current_state.wifiData.rssi, current_state.wifiData.network_name); + break; + case DATA_TYPE_ETHERNET: + current_state.ethernetData = dataMessage.data.networkData; + LOG(ELOG_LEVEL_DEBUG, "Received Ethernet data: ip=%s, link=%d, rssi=%F, name=%s", + current_state.ethernetData.ip_address, current_state.ethernetData.link, + current_state.ethernetData.rssi, current_state.ethernetData.network_name); + break; + case DATA_TYPE_TELEMETRY: + current_state.telemetryData = dataMessage.data.telemetryData; + LOG(ELOG_LEVEL_DEBUG, "Received telemetry data: heap=%F, uptime=%d, temperature=%F", + current_state.telemetryData.heap_used_percent, current_state.telemetryData.uptime_seconds, + current_state.telemetryData.temperature); + break; + default: + LOG(ELOG_LEVEL_ERROR, "Unknown data type received"); + break; + } + // Send the updated current_state to the webserver queue + if (xQueueSendToBack(stateForWebserverQueue, ¤t_state, 250 / portTICK_PERIOD_MS) != pdTRUE) { + LOG(ELOG_LEVEL_ERROR, "Failed to send current_state to webserver queue"); + } + } else { + LOG(ELOG_LEVEL_WARNING, "No message received within max wait"); + } + delay(1000); } \ No newline at end of file diff --git a/src/networking/json_builder.cpp b/src/networking/json_builder.cpp index 84e7f53..56217d4 100644 --- a/src/networking/json_builder.cpp +++ b/src/networking/json_builder.cpp @@ -1,37 +1,34 @@ #include "json_builder.h" #include -extern DeviceTelemetry telemetry; -extern NetworkData wifi_data; -extern NetworkData ethernet_data; -extern SensorData shunt_data; - -StaticJsonDocument<128> build_shunt_data_json(SensorData data) { - StaticJsonDocument<128> doc; +// Refactored: no return by value, serialize directly to output String +// Reduced document sizes based on actual JSON output requirements +void build_shunt_data_json(SensorData data, String& output) { + StaticJsonDocument<96> doc; // Reduced from 128: 3 floats ~70-80 bytes doc["bus_voltage"] = data.bus_voltage; doc["shunt_voltage"] = data.shunt_voltage; doc["current"] = data.shunt_current; - return doc; + serializeJson(doc, output); } -StaticJsonDocument<128> build_water_data_json(WaterData data) { - StaticJsonDocument<128> doc; +void build_water_data_json(WaterData data, String& output) { + StaticJsonDocument<96> doc; // Reduced from 128: 3 floats ~70-80 bytes doc["percentage"] = data.percentage; doc["water_height"] = data.level; doc["liters"] = data.liters; - return doc; + serializeJson(doc, output); } -StaticJsonDocument<128> build_telemetry_json(DeviceTelemetry data) { - StaticJsonDocument<128> doc; +void build_telemetry_json(DeviceTelemetry data, String& output) { + StaticJsonDocument<96> doc; // Reduced from 128: 1 int + 2 floats ~70-80 bytes doc["uptime_seconds"] = data.uptime_seconds; doc["heap_percent"] = data.heap_used_percent; doc["temperature"] = data.temperature; - return doc; + serializeJson(doc, output); } -StaticJsonDocument<256> build_network_json(NetworkData wired, NetworkData wireless) { - StaticJsonDocument<256> doc; +void build_network_json(NetworkData wired, NetworkData wireless, String& output) { + StaticJsonDocument<256> doc; // Keep 256: nested objects + strings ~200 bytes doc["wifi"]["ip"] = wireless.ip_address; doc["wifi"]["rssi"] = wireless.rssi; doc["wifi"]["link"] = wireless.link; @@ -41,15 +38,15 @@ StaticJsonDocument<256> build_network_json(NetworkData wired, NetworkData wirele doc["ethernet"]["rssi"] = wired.rssi; doc["ethernet"]["link"] = wired.link; - return doc; + serializeJson(doc, output); } -StaticJsonDocument<128> build_ota_json(OTAStatus status) { - StaticJsonDocument<256> doc; +void build_ota_json(OTAStatus status, String& output) { + StaticJsonDocument<192> doc; // Reduced from 256: bool + 2 version strings + int ~150-170 bytes doc["update_available"] = status.update_available; doc["current_version"] = String(status.current_version.major) + "." + String(status.current_version.minor) + "." + String(status.current_version.patch); doc["new_version"] = String(status.latest_version.major) + "." + String(status.latest_version.minor) + "." + String(status.latest_version.patch); doc["progress"] = status.update_progress; - return doc; -} \ No newline at end of file + serializeJson(doc, output); +} diff --git a/src/networking/json_builder.h b/src/networking/json_builder.h index 6aed2ab..6a2d73c 100644 --- a/src/networking/json_builder.h +++ b/src/networking/json_builder.h @@ -1,8 +1,9 @@ #include #include "../global_data/global_data.h" -StaticJsonDocument<128> build_shunt_data_json(SensorData data); -StaticJsonDocument<128> build_water_data_json(WaterData data); -StaticJsonDocument<128> build_telemetry_json(DeviceTelemetry data); -StaticJsonDocument<256> build_network_json(NetworkData wired, NetworkData wireless); -StaticJsonDocument<128> build_ota_json(OTAStatus status); \ No newline at end of file +// Refactored to pass by reference - no expensive copies on stack +void build_shunt_data_json(SensorData data, String& output); +void build_water_data_json(WaterData data, String& output); +void build_telemetry_json(DeviceTelemetry data, String& output); +void build_network_json(NetworkData wired, NetworkData wireless, String& output); +void build_ota_json(OTAStatus status, String& output); diff --git a/src/networking/networking.cpp b/src/networking/networking.cpp index 205dd68..7a71247 100644 --- a/src/networking/networking.cpp +++ b/src/networking/networking.cpp @@ -4,6 +4,7 @@ #include #include "../global_data/global_data.h" #include +#include "freertos/queue.h" #define ETH_PHY_TYPE ETH_PHY_LAN8720 #define ETH_PHY_ADDR 0 @@ -18,8 +19,9 @@ int64_t mac_address = ESP.getEfuseMac(); uint8_t failed_connection_attempts = 0; -extern NetworkData wifi_data; -extern NetworkData ethernet_data; +NetworkData wifi_data; +NetworkData ethernet_data; + extern Preferences prefs; // Defines the type of connection for which the hostname should be created @@ -45,6 +47,14 @@ const char * get_hostname(HostnameType host_type) { void wifi_task(void* parameter) { + // Extract the queue handle from the task parameters + QueueHandle_t dataQueue = (QueueHandle_t)parameter; + if (dataQueue == NULL) { + LOG(ELOG_LEVEL_ERROR, "Data queue is NULL"); + vTaskDelete(NULL); + return; + } + LOG(ELOG_LEVEL_DEBUG, "Starting WiFi Task"); WiFi.setHostname(get_hostname(Wireless)); while (true) { @@ -72,8 +82,21 @@ void wifi_task(void* parameter) failed_connection_attempts = 0; wifi_data.rssi = WiFi.RSSI(); wifi_data.link = true; - wifi_data.network_name = WiFi.SSID(); - wifi_data.ip_address = WiFi.localIP().toString(); + strncpy(wifi_data.network_name, WiFi.SSID().c_str(), sizeof(wifi_data.network_name) - 1); + wifi_data.network_name[sizeof(wifi_data.network_name) - 1] = '\0'; + strncpy(wifi_data.ip_address, WiFi.localIP().toString().c_str(), sizeof(wifi_data.ip_address) - 1); + wifi_data.ip_address[sizeof(wifi_data.ip_address) - 1] = '\0'; + + // Create a DataMessage for WiFi data + DataMessage dataMessage; + dataMessage.type = DATA_TYPE_WIFI; + dataMessage.data.networkData = wifi_data; + + // Send the WiFi data to the queue + if (xQueueSend(dataQueue, &dataMessage, 0) != pdTRUE) { + LOG(ELOG_LEVEL_ERROR, "Failed to send WiFi data to queue"); + } + LOG(ELOG_LEVEL_DEBUG, "WIFI connected; RSSI: %F, IP Address, %s, SSID: %s", float(WiFi.RSSI()), WiFi.localIP().toString(), prefs.getString(ssid_key, "NOSSID")); delay(1000 * 60); } else { @@ -92,13 +115,33 @@ void wifi_task(void* parameter) void ethernet_task(void* parameter) { + // Extract the queue handle from the task parameters + QueueHandle_t dataQueue = (QueueHandle_t)parameter; + if (dataQueue == NULL) { + LOG(ELOG_LEVEL_ERROR, "Data queue is NULL"); + vTaskDelete(NULL); + return; + } + LOG(ELOG_LEVEL_DEBUG, "Starting Ethernet Task"); ETH.begin(); ETH.setHostname(get_hostname(Ethernet)); while (true) { ethernet_data.link = ETH.linkUp(); ethernet_data.rssi = ETH.linkSpeed(); - ethernet_data.ip_address = ETH.localIP().toString(); + strncpy(ethernet_data.ip_address, ETH.localIP().toString().c_str(), sizeof(ethernet_data.ip_address) - 1); + ethernet_data.ip_address[sizeof(ethernet_data.ip_address) - 1] = '\0'; + + // Create a DataMessage for Ethernet data + DataMessage dataMessage; + dataMessage.type = DATA_TYPE_ETHERNET; + dataMessage.data.networkData = ethernet_data; + + // Send the Ethernet data to the queue + if (xQueueSend(dataQueue, &dataMessage, 0) != pdTRUE) { + LOG(ELOG_LEVEL_ERROR, "Failed to send Ethernet data to queue"); + } + LOG(ELOG_LEVEL_DEBUG, "Ethernet RSSI: %F, IP Address, %s, LINK: %s", float(ethernet_data.rssi), ETH.localIP().toString(), String(ethernet_data.link)); if (ETH.linkUp() && !ETH.isDefault() && ETH.localIP().toString() != "0.0.0.0") { diff --git a/src/networking/webserver.cpp b/src/networking/webserver.cpp index cc56ad6..226d313 100644 --- a/src/networking/webserver.cpp +++ b/src/networking/webserver.cpp @@ -21,23 +21,24 @@ #include "json_builder.h" extern Preferences prefs; -extern DeviceTelemetry telemetry; -extern NetworkData wifi_data; -extern NetworkData ethernet_data; -extern SensorData shunt_data; -extern OTAStatus ota_status; AsyncWebSocket webSocket("/webSocket"); AsyncWebServer server(80); -// Local water data cache + WaterData local_water_data; +SensorData local_sensor_data; +NetworkData local_wifi_data; +NetworkData local_ethernet_data; +DeviceTelemetry local_telemetry; +OTAStatus local_ota_status; + -// Readers-writer lock for local_water_data ReadersWriterLock waterDataLock; - -// Queue to receive water data from the sensor task -QueueHandle_t webserverWaterDataQueue; +ReadersWriterLock sensorDataLock; +ReadersWriterLock networkDataLock; +ReadersWriterLock telemetryDataLock; +ReadersWriterLock otaStatusLock; // ====================== @@ -91,14 +92,16 @@ void setup_routes() { */ void setup_api_endpoints(){ server.on("/sensor_data", HTTP_GET, [](AsyncWebServerRequest* request) { + rwLockAcquireRead(&sensorDataLock); String output; - serializeJson(build_shunt_data_json(shunt_data), output); + build_shunt_data_json(local_sensor_data, output); + rwLockReleaseRead(&sensorDataLock); request->send(200, "application/json", output); }); server.on("/water_data", HTTP_GET, [](AsyncWebServerRequest* request) { rwLockAcquireRead(&waterDataLock); String output; - serializeJson(build_water_data_json(local_water_data), output); + build_water_data_json(local_water_data, output); rwLockReleaseRead(&waterDataLock); request->send(200, "application/json", output); }); @@ -118,33 +121,38 @@ void setup_api_endpoints(){ }); server.on("/telemetry", HTTP_GET, [](AsyncWebServerRequest* request) { + rwLockAcquireRead(&telemetryDataLock); String output; - serializeJson(build_telemetry_json(telemetry), output); + build_telemetry_json(local_telemetry, output); + rwLockReleaseRead(&telemetryDataLock); request->send(200, "application/json", output); }); server.on("/network_info", HTTP_GET, [](AsyncWebServerRequest* request) { + rwLockAcquireRead(&networkDataLock); String output; - serializeJson(build_network_json(ethernet_data, wifi_data), output); + build_network_json(local_ethernet_data, local_wifi_data, output); + rwLockReleaseRead(&networkDataLock); request->send(200, "application/json", output); }); server.on("/ota_update_status", HTTP_GET, [](AsyncWebServerRequest* request) { + rwLockAcquireRead(&otaStatusLock); String output; - serializeJson(build_ota_json(ota_status), output); - request->send(200, "application/json", output); - }); + build_ota_json(local_ota_status, output); + rwLockReleaseRead(&otaStatusLock); + request->send(200, "application/json", output); }); server.on("/run_ota_update", HTTP_GET, [](AsyncWebServerRequest* request) { - if (ota_status.update_progress > -1) { + if (local_ota_status.update_progress > -1) { request->send(200, "text/plain", "OTA Update already in progress"); return; - } else if (!ota_status.update_available) { + } else if (!local_ota_status.update_available) { request->send(200, "text/plain", "No update available"); return; } static TaskArgs_t args = { - .ota_status = ota_status + .ota_status = local_ota_status }; xTaskCreate(run_ota_update_task, "RunOTAUpdate", 1024 * 8, (void *)&args, 1, NULL); @@ -235,16 +243,16 @@ void handle_update_sensor_settings(AsyncWebServerRequest* request) { * @brief Main task for the webserver. * * Initializes all routes, starts the webserver, and sets up local water data. - * Receives water data from the sensor task via a queue and updates local_water_data. + * Receives the current state from the main task via a queue and updates local_water_data. * Runs indefinitely to keep the server active. * - * @param pvParameters Task parameters (expected to be a QueueHandle_t for the water data queue). + * @param pvParameters Task parameters (expected to be a QueueHandle_t for the state queue). */ void webserver_task(void *pvParameters) { // Extract the queue handle from the task parameters - webserverWaterDataQueue = (QueueHandle_t)pvParameters; - if (webserverWaterDataQueue == NULL) { - LOG(ELOG_LEVEL_ERROR, "Webserver water data queue is NULL"); + QueueHandle_t stateQueue = (QueueHandle_t)pvParameters; + if (stateQueue == NULL) { + LOG(ELOG_LEVEL_ERROR, "State queue is NULL"); vTaskDelete(NULL); return; } @@ -252,33 +260,112 @@ void webserver_task(void *pvParameters) { LOG(ELOG_LEVEL_DEBUG, "Setting up routes"); setup_routes(); - // Initialize the readers-writer lock + // Initialize the readers-writer locks if (!rwLockInit(&waterDataLock)) { - LOG(ELOG_LEVEL_ERROR, "Failed to initialize readers-writer lock"); + LOG(ELOG_LEVEL_ERROR, "Failed to initialize water data readers-writer lock"); + vTaskDelete(NULL); + return; + } + + if (!rwLockInit(&sensorDataLock)) { + LOG(ELOG_LEVEL_ERROR, "Failed to initialize sensor data readers-writer lock"); + vTaskDelete(NULL); + return; + } + + if (!rwLockInit(&networkDataLock)) { + LOG(ELOG_LEVEL_ERROR, "Failed to initialize network data readers-writer lock"); + vTaskDelete(NULL); + return; + } + + if (!rwLockInit(&telemetryDataLock)) { + LOG(ELOG_LEVEL_ERROR, "Failed to initialize telemetry data readers-writer lock"); + vTaskDelete(NULL); + return; + } + + if (!rwLockInit(&otaStatusLock)) { + LOG(ELOG_LEVEL_ERROR, "Failed to initialize OTA status readers-writer lock"); vTaskDelete(NULL); return; } // Initialize local water data with static values rwLockAcquireWrite(&waterDataLock); - local_water_data.level = 50.0f; - local_water_data.liters = 100.0f; - local_water_data.percentage = 50.0f; + local_water_data.level = -1.0; + local_water_data.liters = -1.0; + local_water_data.percentage = -1.0; rwLockReleaseWrite(&waterDataLock); + // Initialize local sensor data with static values + rwLockAcquireWrite(&sensorDataLock); + local_sensor_data.bus_voltage = -1.0; + local_sensor_data.shunt_voltage = -1.0; + local_sensor_data.shunt_current = -1.0; + rwLockReleaseWrite(&sensorDataLock); + + // Initialize local network data with static values + rwLockAcquireWrite(&networkDataLock); + strcpy(local_wifi_data.ip_address, "0.0.0.0"); + local_wifi_data.link = false; + local_wifi_data.rssi = -1.0; + strcpy(local_wifi_data.network_name, "UNKNOWN"); + + strcpy(local_ethernet_data.ip_address, "0.0.0.0"); + local_ethernet_data.link = false; + local_ethernet_data.rssi = -1.0; + strcpy(local_ethernet_data.network_name, "UNKNOWN"); + rwLockReleaseWrite(&networkDataLock); + + // Initialize local telemetry data with static values + rwLockAcquireWrite(&telemetryDataLock); + local_telemetry.heap_used_percent = -1.0; + local_telemetry.uptime_seconds = -1; + local_telemetry.temperature = -1.0; + rwLockReleaseWrite(&telemetryDataLock); + + // Initialize local OTA status with static values + rwLockAcquireWrite(&otaStatusLock); + local_ota_status.update_available = false; + local_ota_status.current_version = {0, 0, 0}; + local_ota_status.latest_version = {0, 0, 0}; + local_ota_status.update_progress = -1; + local_ota_status.update_url = "UNKNOWN"; + rwLockReleaseWrite(&otaStatusLock); + LOG(ELOG_LEVEL_DEBUG, "Starting webserver"); server.begin(); while (1) { - // Check if there is new water data in the queue - WaterData newWaterData; - if (xQueueReceive(webserverWaterDataQueue, &newWaterData, 0) == pdTRUE) { + // Check if there is new state data in the queue + CurrentState currentState; + if (xQueueReceive(stateQueue, ¤tState, portMAX_DELAY) == pdTRUE) { // Update local_water_data with the new data from the queue rwLockAcquireWrite(&waterDataLock); - local_water_data = newWaterData; + local_water_data = currentState.waterData; rwLockReleaseWrite(&waterDataLock); + + // Update local_sensor_data with the new data from the queue + rwLockAcquireWrite(&sensorDataLock); + local_sensor_data = currentState.sensorData; + rwLockReleaseWrite(&sensorDataLock); + + // Update local network data with the new data from the queue + rwLockAcquireWrite(&networkDataLock); + local_wifi_data = currentState.wifiData; + local_ethernet_data = currentState.ethernetData; + rwLockReleaseWrite(&networkDataLock); + + // Update local telemetry data with the new data from the queue + rwLockAcquireWrite(&telemetryDataLock); + local_telemetry = currentState.telemetryData; + rwLockReleaseWrite(&telemetryDataLock); + + // Update local OTA status with the new data from the queue + rwLockAcquireWrite(&otaStatusLock); + local_ota_status = currentState.otaStatus; + rwLockReleaseWrite(&otaStatusLock); } - - vTaskDelay(100 / portTICK_PERIOD_MS); // Small delay to reduce CPU usage } } \ No newline at end of file diff --git a/src/sensor/sensor.cpp b/src/sensor/sensor.cpp index 66c83cc..f499b5f 100644 --- a/src/sensor/sensor.cpp +++ b/src/sensor/sensor.cpp @@ -3,7 +3,6 @@ #include #include "Wire.h" #include "../global_data/global_data.h" -#include "../networking/webserver.h" #ifdef USE_INA226 @@ -19,9 +18,8 @@ INA233 ina_sensor(0x40); #include "freertos/task.h" extern Preferences prefs; -extern WaterData water_data; -extern ActiveErrors active_errors; -extern SensorData shunt_data; + +WaterData water_data; // Calibration variables float zero_value = 0.03; // Measured shunt voltage with nothing connected, used to fix measuring offset @@ -49,9 +47,9 @@ void init_sensor(){ void read_sensor_task(void* parameter) { // Extract the queue handle from the task parameters - QueueHandle_t webserverWaterDataQueue = (QueueHandle_t)parameter; - if (webserverWaterDataQueue == NULL) { - LOG(ELOG_LEVEL_ERROR, "Webserver water data queue is NULL"); + QueueHandle_t dataQueue = (QueueHandle_t)parameter; + if (dataQueue == NULL) { + LOG(ELOG_LEVEL_ERROR, "Data queue is NULL"); vTaskDelete(NULL); return; } @@ -69,6 +67,22 @@ void read_sensor_task(void* parameter) LOG(ELOG_LEVEL_DEBUG, "RAW Shunt voltage: %F mV", ina_sensor.getShuntVoltage_mV()); float shunt_current = shunt_voltage / RESISTOR_VALUE; + + + // Build SensorData object + SensorData sensor_data; + sensor_data.bus_voltage = bus_voltage; + sensor_data.shunt_current = shunt_current; + sensor_data.shunt_voltage = shunt_voltage; + + // Send the sensor data to the data queue + DataMessage dataMessageSensor; + dataMessageSensor.type = DATA_TYPE_SENSOR; + dataMessageSensor.data.sensorData = sensor_data; + if (xQueueSend(dataQueue, &dataMessageSensor, 0) != pdTRUE) { + LOG(ELOG_LEVEL_ERROR, "Failed to send water data to queue"); + } + // Get values from storage float sensor_range = prefs.getFloat(level_sensor_range_key, 200); @@ -103,26 +117,29 @@ void read_sensor_task(void* parameter) float liters_raw = max_liters * percentage_raw; int liters = round(liters_raw); - active_errors.current_low = shunt_current < 3.8; - active_errors.current_high = shunt_current > 20.2; - active_errors.voltage_low = bus_voltage < 23; - active_errors.voltage_high = bus_voltage > 25; LOG(ELOG_LEVEL_DEBUG, "Shunt current: %F", shunt_current); LOG(ELOG_LEVEL_DEBUG, "Shunt voltage: %F", shunt_voltage); LOG(ELOG_LEVEL_DEBUG, "Bus voltage: %F", bus_voltage); LOG(ELOG_LEVEL_DEBUG, "cm_over_zero: %F", cm_over_zero); - shunt_data.bus_voltage = bus_voltage; - shunt_data.shunt_voltage = shunt_voltage; - shunt_data.shunt_current = shunt_current; - water_data.level = cm_over_zero; water_data.liters = liters; water_data.percentage = percentage_rounded; - // Send the water data to the webserver task via the queue - if (xQueueSend(webserverWaterDataQueue, &water_data, 0) != pdTRUE) { - LOG(ELOG_LEVEL_ERROR, "Failed to send water data to webserver queue"); + // Send the water data to the data queue + DataMessage dataMessageWater; + dataMessageWater.type = DATA_TYPE_WATER; + dataMessageWater.data.waterData = water_data; + + // Log the data being sent + LOG(ELOG_LEVEL_DEBUG, "Sending water data to queue: level=%F, liters=%F, percentage=%F", + water_data.level, water_data.liters, water_data.percentage); + + if (xQueueSend(dataQueue, &dataMessageWater, 0) != pdTRUE) { + LOG(ELOG_LEVEL_ERROR, "Failed to send water data to queue"); + // Log the queue status + UBaseType_t messagesWaiting = uxQueueMessagesWaiting(dataQueue); + LOG(ELOG_LEVEL_ERROR, "Queue messages waiting: %d", messagesWaiting); } delay(20000); diff --git a/src/telemetry/telemetry.cpp b/src/telemetry/telemetry.cpp index e870a77..20682ec 100644 --- a/src/telemetry/telemetry.cpp +++ b/src/telemetry/telemetry.cpp @@ -2,21 +2,38 @@ #include #include "../global_data/global_data.h" #include "tools/log.h" +#include extern "C" uint8_t temprature_sens_read(); -extern DeviceTelemetry telemetry; - void collect_internal_telemetry_task(void* parameter) { + // Extract the queue handle from the task parameters + QueueHandle_t dataQueue = (QueueHandle_t)parameter; + if (dataQueue == NULL) { + LOG(ELOG_LEVEL_ERROR, "Data queue is NULL"); + vTaskDelete(NULL); + return; + } + LOG(ELOG_LEVEL_DEBUG, "Starting internal telemetry tasks"); while (true) { float heap_usage = (float(ESP.getFreeHeap()) / float(ESP.getHeapSize())) * 100; uint64_t uptime_seconds = millis() / 1000; + float temperature = (temprature_sens_read()-32) / 1.8; - telemetry.heap_used_percent = heap_usage; - telemetry.uptime_seconds = uptime_seconds; - telemetry.temperature = (temprature_sens_read()-32) / 1.8; - delay(60000); + // Create a DataMessage for telemetry data + DataMessage dataMessage; + dataMessage.type = DATA_TYPE_TELEMETRY; + dataMessage.data.telemetryData.heap_used_percent = heap_usage; + dataMessage.data.telemetryData.uptime_seconds = uptime_seconds; + dataMessage.data.telemetryData.temperature = temperature; + + // Send the telemetry data to the queue + if (xQueueSend(dataQueue, &dataMessage, 0) != pdTRUE) { + LOG(ELOG_LEVEL_ERROR, "Failed to send telemetry data to queue"); + } + + delay(10 * 1000); } } \ No newline at end of file diff --git a/src/tools/ota_handler.cpp b/src/tools/ota_handler.cpp new file mode 100644 index 0000000..372a1de --- /dev/null +++ b/src/tools/ota_handler.cpp @@ -0,0 +1,59 @@ +#include +#include +#include "tools/log.h" + +/** + * @brief Task to handle ArduinoOTA updates. + * + * This task initializes the ArduinoOTA library and handles OTA updates. + * It runs indefinitely to process OTA update requests. + * + * @param pvParameters Task parameters (unused). + */ +void ota_handler_task(void *pvParameters) { + (void)pvParameters; // Suppress unused parameter warning + + LOG(ELOG_LEVEL_DEBUG, "Setting up OTA handler"); + + // Configure OTA settings + ArduinoOTA + .onStart([]() { + String type; + if (ArduinoOTA.getCommand() == U_FLASH) { + type = "sketch"; + } else { // U_SPIFFS + type = "filesystem"; + } + + // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() + LOG(ELOG_LEVEL_DEBUG, "Start updating %s", type.c_str()); + }) + .onEnd([]() { + LOG(ELOG_LEVEL_DEBUG, "\nEnd"); + }) + .onProgress([](unsigned int progress, unsigned int total) { + Serial.printf("Progress: %u%%\r", (progress / (total / 100))); + }) + .onError([](ota_error_t error) { + Serial.printf("Error[%u]: ", error); + if (error == OTA_AUTH_ERROR) { + LOG(ELOG_LEVEL_DEBUG, "Auth Failed"); + } else if (error == OTA_BEGIN_ERROR) { + LOG(ELOG_LEVEL_DEBUG, "Begin Failed"); + } else if (error == OTA_CONNECT_ERROR) { + LOG(ELOG_LEVEL_DEBUG, "Connect Failed"); + } else if (error == OTA_RECEIVE_ERROR) { + LOG(ELOG_LEVEL_DEBUG, "Receive Failed"); + } else if (error == OTA_END_ERROR) { + LOG(ELOG_LEVEL_DEBUG, "End Failed"); + } + }); + // Start the OTA handler + ArduinoOTA.begin(); + + // Run indefinitely to handle OTA updates + while (1) { + ArduinoOTA.handle(); + vTaskDelay(250 / portTICK_PERIOD_MS); // Even more frequent handling + } +} diff --git a/src/tools/ota_handler.h b/src/tools/ota_handler.h new file mode 100644 index 0000000..49f2a1f --- /dev/null +++ b/src/tools/ota_handler.h @@ -0,0 +1,14 @@ +#ifndef OTA_HANDLER_H +#define OTA_HANDLER_H + +/** + * @brief Task to handle ArduinoOTA updates. + * + * This task initializes the ArduinoOTA library and handles OTA updates. + * It runs indefinitely to process OTA update requests. + * + * @param pvParameters Task parameters (unused). + */ +void ota_handler_task(void *pvParameters); + +#endif // OTA_HANDLER_H diff --git a/src/tools/tools.cpp b/src/tools/tools.cpp index 5900441..8eeaf9b 100644 --- a/src/tools/tools.cpp +++ b/src/tools/tools.cpp @@ -21,10 +21,6 @@ extern AsyncWebSocket webSocket; extern Version current_spiffs_version; -bool is_error(ActiveErrors active_errors) { - return active_errors.current_high || active_errors.current_low || active_errors.level_high || active_errors.level_low || active_errors.voltage_high || active_errors.voltage_low; -} - String processor(const String& var) { if (var == level_sensor_range_key) { diff --git a/src/tools/tools.h b/src/tools/tools.h index 969a7c8..6ce0454 100644 --- a/src/tools/tools.h +++ b/src/tools/tools.h @@ -6,7 +6,6 @@ void printSuffix(Print* _logOutput, int logLevel); void print_prefix(Print* _logOutput, int logLevel); -bool is_error(ActiveErrors active_errors); String processor(const String& var); void check_update_task(void* parameter); void run_ota_update_task(void* parameter);