From 31acd5d5ece1a8978e3c1830d972ebc950f40366 Mon Sep 17 00:00:00 2001 From: tobimai Date: Sun, 24 May 2026 16:22:07 +0200 Subject: [PATCH] Webserver prefs via queues --- platformio.ini | 2 +- src/global_data/global_data.h | 18 ++++++- src/main.cpp | 59 +++++++++++++++++++-- src/networking/webserver.cpp | 99 ++++++++++++++++++++++++++++------- src/networking/webserver.h | 8 +++ 5 files changed, 160 insertions(+), 26 deletions(-) diff --git a/platformio.ini b/platformio.ini index 5b716da..0fd70d4 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.21 +upload_port = 192.168.10.158 build_flags = ${env:esp32_base.build_flags} -DUSE_INA226 [env:native] diff --git a/src/global_data/global_data.h b/src/global_data/global_data.h index 7a6f1e6..de04a83 100644 --- a/src/global_data/global_data.h +++ b/src/global_data/global_data.h @@ -97,9 +97,24 @@ typedef enum { DATA_TYPE_WIFI, DATA_TYPE_ETHERNET, DATA_TYPE_TELEMETRY, - DATA_TYPE_OTA + DATA_TYPE_OTA, + DATA_TYPE_PREFERENCES } DataType; +/** + * @brief Structure to hold sensor-related preferences. + * + * Contains calibration and configuration values for the sensor and network. + */ +typedef struct { + float level_sensor_range; /**< Maximum range of the level sensor in cm */ + float water_level_min; /**< Minimum water level in cm */ + float water_level_max; /**< Maximum water level in cm */ + float water_volume; /**< Total volume of the tank in liters */ + char ssid[33]; /**< WiFi SSID (max 32 chars + null terminator) */ + char wifi_password[65]; /**< WiFi password (max 64 chars + null terminator) */ +} PreferencesData; + // Union to hold different types of data typedef union { SensorData sensorData; @@ -107,6 +122,7 @@ typedef union { NetworkData networkData; DeviceTelemetry telemetryData; OTAStatus otaStatus; + PreferencesData preferencesData; } DataUnion; // Structure to hold data type and the actual data diff --git a/src/main.cpp b/src/main.cpp index e653c58..14ba5a8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -56,6 +56,9 @@ QueueHandle_t dataQueue = xQueueCreate(5, sizeof(DataMessage)); // Queue for sending data from processor to different tasks QueueHandle_t stateForWebserverQueue = xQueueCreate(2, sizeof(CurrentState)); +// Queue for sending preferences from main to webserver +QueueHandle_t prefsForWebserverQueue = xQueueCreate(2, sizeof(DataMessage)); + void setup() { Logger.registerSerial(MYLOG, ELOG_LEVEL_DEBUG, "Serial"); @@ -63,6 +66,26 @@ void setup() prefs.begin("waterlevel", false); Serial.begin(115200); + // Read initial preferences and send to webserver + if (prefsForWebserverQueue != NULL) { + DataMessage prefsMessage; + prefsMessage.type = DATA_TYPE_PREFERENCES; + prefsMessage.data.preferencesData.level_sensor_range = prefs.getFloat(level_sensor_range_key, 200); + prefsMessage.data.preferencesData.water_level_min = prefs.getFloat(water_level_min_key, 0); + prefsMessage.data.preferencesData.water_level_max = prefs.getFloat(water_level_max_key, 200); + prefsMessage.data.preferencesData.water_volume = prefs.getFloat(water_volume_key, 10000.0f); + strncpy(prefsMessage.data.preferencesData.ssid, prefs.getString(ssid_key, "").c_str(), sizeof(prefsMessage.data.preferencesData.ssid) - 1); + prefsMessage.data.preferencesData.ssid[sizeof(prefsMessage.data.preferencesData.ssid) - 1] = '\0'; + strncpy(prefsMessage.data.preferencesData.wifi_password, prefs.getString(wifi_password_key, "").c_str(), sizeof(prefsMessage.data.preferencesData.wifi_password) - 1); + prefsMessage.data.preferencesData.wifi_password[sizeof(prefsMessage.data.preferencesData.wifi_password) - 1] = '\0'; + + if (xQueueSend(prefsForWebserverQueue, &prefsMessage, 100 / portTICK_PERIOD_MS) != pdTRUE) { + LOG(ELOG_LEVEL_ERROR, "Failed to send initial preferences to webserver queue"); + } else { + LOG(ELOG_LEVEL_DEBUG, "Sent initial preferences to webserver"); + } + } + LOG(ELOG_LEVEL_DEBUG, "Beginning LittleFS"); LittleFS.begin(FORMAT_LITTLEFS_IF_FAILED); @@ -90,7 +113,13 @@ void setup() xTaskCreate(display_task, "DisplayTask", 1024 * 2, NULL, 1, NULL); xTaskCreate(get_time_task, "GetTimeTask", 1024 * 2, NULL, 1, NULL); xTaskCreate(check_update_task, "CheckUpdateTask", 1024 * 6, dataQueue, 1, NULL); - xTaskCreate(webserver_task, "WebServerTask", 1024 * 4, stateForWebserverQueue, 1, NULL); + // Create webserver queues struct + WebserverQueues webserverQueues = { + .stateQueue = stateForWebserverQueue, + .dataQueue = dataQueue, + .prefsQueue = prefsForWebserverQueue + }; + xTaskCreate(webserver_task, "WebServerTask", 1024 * 4, &webserverQueues, 1, NULL); xTaskCreate(ota_handler_task, "OTAHandlerTask", 1024 * 4, NULL, 3, NULL); } } @@ -140,13 +169,35 @@ void loop() current_state.otaStatus.current_version.major, current_state.otaStatus.current_version.minor, current_state.otaStatus.current_version.patch, current_state.otaStatus.latest_version.major, current_state.otaStatus.latest_version.minor, current_state.otaStatus.latest_version.patch); break; + case DATA_TYPE_PREFERENCES: + // Store preferences in flash and forward to webserver + LOG(ELOG_LEVEL_DEBUG, "Received preference update request"); + + // Write to preferences storage + prefs.putFloat(level_sensor_range_key, dataMessage.data.preferencesData.level_sensor_range); + prefs.putFloat(water_level_min_key, dataMessage.data.preferencesData.water_level_min); + prefs.putFloat(water_level_max_key, dataMessage.data.preferencesData.water_level_max); + prefs.putFloat(water_volume_key, dataMessage.data.preferencesData.water_volume); + prefs.putString(ssid_key, dataMessage.data.preferencesData.ssid); + prefs.putString(wifi_password_key, dataMessage.data.preferencesData.wifi_password); + + // Forward to webserver so it can update its local copy + if (prefsForWebserverQueue != NULL) { + if (xQueueSend(prefsForWebserverQueue, &dataMessage, 100 / portTICK_PERIOD_MS) != pdTRUE) { + LOG(ELOG_LEVEL_ERROR, "Failed to forward preferences to webserver queue"); + } + } + LOG(ELOG_LEVEL_DEBUG, "Stored preferences and forwarded to webserver"); + 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"); + // Send the updated current_state to the webserver queue (for non-preference messages) + if (dataMessage.type != DATA_TYPE_PREFERENCES) { + 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"); diff --git a/src/networking/webserver.cpp b/src/networking/webserver.cpp index 11df3f4..f367743 100644 --- a/src/networking/webserver.cpp +++ b/src/networking/webserver.cpp @@ -21,10 +21,12 @@ #include "../global_data/defines.h" #include "json_builder.h" -extern Preferences prefs; - AsyncWebServer server(80); +// Queue handles (initialized in webserver_task) +static QueueHandle_t g_dataQueue = NULL; +static QueueHandle_t g_prefsQueue = NULL; + WaterData local_water_data; SensorData local_sensor_data; @@ -33,6 +35,13 @@ NetworkData local_ethernet_data; DeviceTelemetry local_telemetry; OTAStatus local_ota_status; +// Local preference storage for template processing +float local_level_sensor_range = -1.0f; +float local_water_level_min = 0.0f; +float local_water_level_max = 200.0f; +float local_water_volume = 10000.0f; +char local_ssid[33] = ""; +char local_wifi_password[65] = ""; ReadersWriterLock waterDataLock; ReadersWriterLock sensorDataLock; @@ -58,15 +67,15 @@ ReadersWriterLock otaStatusLock; String processor(const String& var) { if (var == level_sensor_range_key) { - return String(prefs.getFloat(level_sensor_range_key, -1)); + return String(local_level_sensor_range); } else if (var == water_level_min_key) { - return String(prefs.getFloat(water_level_min_key, -1)); + return String(local_water_level_min); } else if (var == water_level_max_key) { - return String(prefs.getFloat(water_level_max_key, -1)); + return String(local_water_level_max); } else if (var == water_volume_key) { - return String(prefs.getFloat(water_volume_key, -1)); + return String(local_water_volume); } else if (var == ssid_key) { - return String(prefs.getString(ssid_key, "")); + return String(local_ssid); } return String(""); @@ -197,7 +206,8 @@ void setup_api_endpoints(){ /** * @brief Handles the update of WiFi credentials. * - * Validates and updates the SSID and password in preferences. + * Validates and sends SSID and password update request to main task. + * Main task will write to preferences and distribute to tasks. * * @param request The web server request object. */ @@ -206,8 +216,22 @@ void handle_update_wifi_credentials(AsyncWebServerRequest* request) { LOG(ELOG_LEVEL_DEBUG, "Updating SSID config"); const AsyncWebParameter* ssid_param = request->getParam(ssid_key, true); const AsyncWebParameter* password_param = request->getParam(wifi_password_key, true); - prefs.putString(ssid_key, ssid_param->value().c_str()); - prefs.putString(wifi_password_key, password_param->value().c_str()); + + // Send preference update to main task + DataMessage prefsMessage; + prefsMessage.type = DATA_TYPE_PREFERENCES; + prefsMessage.data.preferencesData.level_sensor_range = local_level_sensor_range; // Keep existing values + prefsMessage.data.preferencesData.water_level_min = local_water_level_min; + prefsMessage.data.preferencesData.water_level_max = local_water_level_max; + prefsMessage.data.preferencesData.water_volume = local_water_volume; + strncpy(prefsMessage.data.preferencesData.ssid, ssid_param->value().c_str(), sizeof(prefsMessage.data.preferencesData.ssid) - 1); + prefsMessage.data.preferencesData.ssid[sizeof(prefsMessage.data.preferencesData.ssid) - 1] = '\0'; + strncpy(prefsMessage.data.preferencesData.wifi_password, password_param->value().c_str(), sizeof(prefsMessage.data.preferencesData.wifi_password) - 1); + prefsMessage.data.preferencesData.wifi_password[sizeof(prefsMessage.data.preferencesData.wifi_password) - 1] = '\0'; + + if (g_dataQueue != NULL && xQueueSend(g_dataQueue, &prefsMessage, 100 / portTICK_PERIOD_MS) != pdTRUE) { + LOG(ELOG_LEVEL_ERROR, "Failed to send WiFi credentials update to main"); + } } else { request->send(400, "text/plain", "Missing parameters"); return; @@ -245,12 +269,23 @@ void handle_update_sensor_settings(AsyncWebServerRequest* request) { LOG(ELOG_LEVEL_DEBUG, "range_float:%D:", range_float); - prefs.putFloat(level_sensor_range_key, range_float); - prefs.putFloat(water_level_min_key, level_min_float); - prefs.putFloat(water_level_max_key, level_max_float); - prefs.putFloat(water_volume_key, liters_float); + // Send preference update to main task + DataMessage prefsMessage; + prefsMessage.type = DATA_TYPE_PREFERENCES; + prefsMessage.data.preferencesData.level_sensor_range = range_float; + prefsMessage.data.preferencesData.water_level_min = level_min_float; + prefsMessage.data.preferencesData.water_level_max = level_max_float; + prefsMessage.data.preferencesData.water_volume = liters_float; + strncpy(prefsMessage.data.preferencesData.ssid, local_ssid, sizeof(prefsMessage.data.preferencesData.ssid) - 1); + prefsMessage.data.preferencesData.ssid[sizeof(prefsMessage.data.preferencesData.ssid) - 1] = '\0'; + strncpy(prefsMessage.data.preferencesData.wifi_password, local_wifi_password, sizeof(prefsMessage.data.preferencesData.wifi_password) - 1); + prefsMessage.data.preferencesData.wifi_password[sizeof(prefsMessage.data.preferencesData.wifi_password) - 1] = '\0'; + + if (g_dataQueue != NULL && xQueueSend(g_dataQueue, &prefsMessage, 100 / portTICK_PERIOD_MS) != pdTRUE) { + LOG(ELOG_LEVEL_ERROR, "Failed to send sensor settings update to main"); + } - LOG(ELOG_LEVEL_DEBUG, "range_float_after:%D:", prefs.getFloat(level_sensor_range_key, -1.0)); + LOG(ELOG_LEVEL_DEBUG, "Sent sensor settings update to main"); } else { LOG(ELOG_LEVEL_DEBUG, "!!!! FAIL lo"); for (int i = 0; i < params; i++) { @@ -276,17 +311,22 @@ void handle_update_sensor_settings(AsyncWebServerRequest* request) { * 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 state queue). + * @param pvParameters Task parameters (expected to be a WebserverQueues* containing stateQueue, dataQueue, and prefsQueue). */ void webserver_task(void *pvParameters) { - // Extract the queue handle from the task parameters - QueueHandle_t stateQueue = (QueueHandle_t)pvParameters; - if (stateQueue == NULL) { - LOG(ELOG_LEVEL_ERROR, "State queue is NULL"); + // Extract the queue handles from the task parameters + WebserverQueues* queues = (WebserverQueues*)pvParameters; + if (queues == NULL || queues->stateQueue == NULL) { + LOG(ELOG_LEVEL_ERROR, "Queue parameters are NULL"); vTaskDelete(NULL); return; } + // Store queue handles in file-scoped variables + g_dataQueue = queues->dataQueue; + g_prefsQueue = queues->prefsQueue; + QueueHandle_t stateQueue = queues->stateQueue; + LOG(ELOG_LEVEL_DEBUG, "Setting up routes"); setup_routes(); @@ -369,6 +409,25 @@ void webserver_task(void *pvParameters) { server.begin(); while (1) { + // Check if there is new preference data in the queue + DataMessage prefsMessage; + if (g_prefsQueue != NULL && xQueueReceive(g_prefsQueue, &prefsMessage, 0) == pdTRUE) { + if (prefsMessage.type == DATA_TYPE_PREFERENCES) { + // Update local preference values + local_level_sensor_range = prefsMessage.data.preferencesData.level_sensor_range; + local_water_level_min = prefsMessage.data.preferencesData.water_level_min; + local_water_level_max = prefsMessage.data.preferencesData.water_level_max; + local_water_volume = prefsMessage.data.preferencesData.water_volume; + strncpy(local_ssid, prefsMessage.data.preferencesData.ssid, sizeof(local_ssid) - 1); + local_ssid[sizeof(local_ssid) - 1] = '\0'; + strncpy(local_wifi_password, prefsMessage.data.preferencesData.wifi_password, sizeof(local_wifi_password) - 1); + local_wifi_password[sizeof(local_wifi_password) - 1] = '\0'; + LOG(ELOG_LEVEL_DEBUG, "Updated preferences: range=%F, min=%F, max=%F, volume=%F, ssid=%s", + local_level_sensor_range, local_water_level_min, local_water_level_max, + local_water_volume, local_ssid); + } + } + // Check if there is new state data in the queue CurrentState currentState; if (xQueueReceive(stateQueue, ¤tState, portMAX_DELAY) == pdTRUE) { diff --git a/src/networking/webserver.h b/src/networking/webserver.h index 4a69d7e..832a96a 100644 --- a/src/networking/webserver.h +++ b/src/networking/webserver.h @@ -3,9 +3,17 @@ #include #include +#include extern AsyncWebServer server; +// Structure to hold queue handles for the webserver task +typedef struct { + QueueHandle_t stateQueue; + QueueHandle_t dataQueue; + QueueHandle_t prefsQueue; +} WebserverQueues; + void setup_api_endpoints(); void setup_routes(); void webserver_task(void *pvParameters);