From e5fecde7f5feb40d408bb4d6665c1b521af165b7 Mon Sep 17 00:00:00 2001 From: Tobias Maier Date: Mon, 30 Mar 2026 17:08:36 +0200 Subject: [PATCH] Factored out webserver stuff to own task/file --- src/main.cpp | 93 ++-------------- src/networking/responses.cpp | 76 ------------- src/networking/responses.h | 4 - src/networking/webserver.cpp | 205 +++++++++++++++++++++++++++++++++++ src/networking/webserver.h | 13 +++ 5 files changed, 225 insertions(+), 166 deletions(-) delete mode 100644 src/networking/responses.cpp delete mode 100644 src/networking/responses.h create mode 100644 src/networking/webserver.cpp create mode 100644 src/networking/webserver.h diff --git a/src/main.cpp b/src/main.cpp index bfd6baf..60a716d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,6 @@ #include "SPIFFS.h" #include #include -#include #include #include #include @@ -15,6 +14,7 @@ #include "global_data/defines.h" #include "networking/networking.h" +#include "networking/webserver.h" #include "external_interfacing/leds.h" #include "sensor/sensor.h" @@ -22,7 +22,6 @@ #include "tools/tools.h" #include #include "networking/json_builder.h" -#include "networking/responses.h" #include #include "time.h" #include "tools/log.h" @@ -39,10 +38,10 @@ extern NetworkData ethernet_data; extern SensorData shunt_data; extern ActiveErrors active_errors; +extern AsyncWebSocket webSocket; + Version current_spiffs_version; -AsyncWebServer server(80); -AsyncWebSocket webSocket("/webSocket"); #define FORMAT_LITTLEFS_IF_FAILED true void setup() @@ -59,7 +58,7 @@ void setup() delay(500); LOG(ELOG_LEVEL_DEBUG, "Init Sensor"); init_sensor(); - + LOG(ELOG_LEVEL_DEBUG, "Beginning LittleFS"); LittleFS.begin(FORMAT_LITTLEFS_IF_FAILED); @@ -78,85 +77,6 @@ void setup() /////////////////////////////// ROUTES /////////////////////////////// LOG(ELOG_LEVEL_DEBUG, "Route Setup"); - // Normal HTML stuff - server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(LittleFS, "/status.html", "text/html", false, processor); }); - - server.on("/settings", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(LittleFS, "/settings.html", "text/html", false, processor); }); - - server.on("/export", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(LittleFS, "/data_export.html", "text/html", false); }); - - server.on("/logic.js", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(LittleFS, "/logic.js", "application/javascript", false, processor); }); - - server.on("/update", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(LittleFS, "/update_progress.html", "text/html", false, processor); }); - - // API stuff - internal - server.on("/update_wifi_credentials", HTTP_POST, [](AsyncWebServerRequest* request) { - // For settings SSID - if (request->hasParam(ssid_key, true) && request->hasParam(wifi_password_key, true)) { - 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()); - - } else { - request->send(400, "text/plain", "Missing parameters"); // TODO add proper error messages - } - request->send(LittleFS, "/settings.html", "text/html", false, processor); // TODO add proper return templating - }); - - server.on("/update_sensor_settings", HTTP_POST, [](AsyncWebServerRequest* request) { - int params = request->params(); - - if (request->hasParam(level_sensor_range_key, true) && request->hasParam(water_level_min_key, true) && request->hasParam(water_level_max_key, true) && request->hasParam(water_volume_key, true)) { - LOG(ELOG_LEVEL_DEBUG, "Updating Sensor config"); - const AsyncWebParameter* range_param = request->getParam(level_sensor_range_key, true); - const AsyncWebParameter* level_min_param = request->getParam(water_level_min_key, true); - const AsyncWebParameter* level_max_param = request->getParam(water_level_max_key, true); - const AsyncWebParameter* liters_param = request->getParam(water_volume_key, true); - - String range_str = range_param->value(); - String level_min_str = level_min_param->value(); - String level_max_str = level_max_param->value(); - String liters_str = liters_param->value(); - - float range_float = range_str.toFloat(); - float level_min_float = level_min_str.toFloat(); - float level_max_float = level_max_str.toFloat(); - float liters_float = liters_str.toFloat(); - - 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); - - LOG(ELOG_LEVEL_DEBUG, "range_float_after:%D:", prefs.getFloat(level_sensor_range_key, -1.0)); - } else { - LOG(ELOG_LEVEL_DEBUG, "!!!! FAIL lo"); - for (int i = 0; i < params; i++) { - const AsyncWebParameter* p = request->getParam(i); - if (p->isFile()) { // p->isPost() is also true - LOG(ELOG_LEVEL_DEBUG, "POST[%s]: %s\n", p->name().c_str(), p->value().c_str()); - } else if (p->isPost()) { - LOG(ELOG_LEVEL_DEBUG, "POST[%s]: %s\n", p->name().c_str(), p->value().c_str()); - } else { - LOG(ELOG_LEVEL_DEBUG, "GET[%s]: %s\n", p->name().c_str(), p->value().c_str()); - } - } - request->send(400, "text/plain", "Missing parameters"); // TODO add proper error messages - } - request->send(LittleFS, "/settings.html", "text/html", false); // TODO add proper return templating - }); - - setup_api_endpoints(); - webSocket.onEvent(onWsEvent); - server.addHandler(&webSocket); - - server.on("/chota.css", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(LittleFS, "/chota.css", "text/css", false); }); - server.on("/gauge.js", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(LittleFS, "/gauge.js", "application/javascript", false); }); - LOG(ELOG_LEVEL_DEBUG, "OTA Setup"); ArduinoOTA @@ -190,14 +110,15 @@ void setup() delay(5000); xTaskCreate(check_update_task, "CheckUpdateTask", 1024 * 8, NULL, 1, NULL); + xTaskCreate(webserver_task, "WebServerTask", 1024 * 8, NULL, 1, NULL); - LOG(ELOG_LEVEL_DEBUG, "Starting webserver"); - server.begin(); LOG(ELOG_LEVEL_DEBUG, "Starting OTA handler"); ArduinoOTA.begin(); } + + void loop() { ArduinoOTA.handle(); diff --git a/src/networking/responses.cpp b/src/networking/responses.cpp deleted file mode 100644 index cf44ede..0000000 --- a/src/networking/responses.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#include "responses.h" - -#include -#include -#include "AsyncJson.h" -#include -#include -#include "json_builder.h" -#include "../tools/tools.h" -#include - -extern WaterData water_data; -extern DeviceTelemetry telemetry; -extern NetworkData wifi_data; -extern NetworkData ethernet_data; -extern SensorData shunt_data; -extern OTAStatus ota_status; - -extern AsyncWebServer server; - -void setup_api_endpoints(){ - server.on("/sensor_data", HTTP_GET, [](AsyncWebServerRequest* request) { - String output; - serializeJson(build_shunt_data_json(shunt_data), output); - request->send(200, "application/json", output); }); - - server.on("/water_data", HTTP_GET, [](AsyncWebServerRequest* request) { - String output; - serializeJson(build_water_data_json(water_data), output); - request->send(200, "application/json", output); }); - - server.on("/raw_percent", HTTP_GET, [](AsyncWebServerRequest* request) { - String output; - output = water_data.percentage; - request->send(200, "text/raw", output); }); - - server.on("/raw_level", HTTP_GET, [](AsyncWebServerRequest* request) { - String output; - output = water_data.level; - request->send(200, "text/raw", output); }); - - - server.on("/telemetry", HTTP_GET, [](AsyncWebServerRequest* request) { - String output; - serializeJson(build_telemetry_json(telemetry), output); - request->send(200, "application/json", output); }); - - server.on("/network_info", HTTP_GET, [](AsyncWebServerRequest* request) { - String output; - serializeJson(build_network_json(ethernet_data, wifi_data), output); - request->send(200, "application/json", output); - }); - - server.on("/ota_update_status", HTTP_GET, [](AsyncWebServerRequest* request) { - String output; - serializeJson(build_ota_json(ota_status), output); - request->send(200, "application/json", output); - }); - - server.on("/run_ota_update", HTTP_GET, [](AsyncWebServerRequest* request) { - if (ota_status.update_progress > -1) { - request->send(200, "text/plain", "OTA Update already in progress"); - return; - } else if (!ota_status.update_available) { - request->send(200, "text/plain", "No update available"); - return; - } - - static TaskArgs_t args = { - .ota_status = ota_status - }; - - xTaskCreate(run_ota_update_task, "RunOTAUpdate", 1024 * 8, (void *)&args, 1, NULL); - request->send(LittleFS, "/update_progress.html", "text/html", false, processor); - }); -} \ No newline at end of file diff --git a/src/networking/responses.h b/src/networking/responses.h deleted file mode 100644 index 2625d26..0000000 --- a/src/networking/responses.h +++ /dev/null @@ -1,4 +0,0 @@ -#include -#include - -void setup_api_endpoints(); \ No newline at end of file diff --git a/src/networking/webserver.cpp b/src/networking/webserver.cpp new file mode 100644 index 0000000..6af4cdb --- /dev/null +++ b/src/networking/webserver.cpp @@ -0,0 +1,205 @@ +#include "webserver.h" + +#include +#include +#include +#include +#include +#include +#include "tools/log.h" +#include "AsyncJson.h" +#include +#include + +#include "../global_data/global_data.h" +#include "../tools/tools.h" +#include "../global_data/defines.h" +#include "json_builder.h" + +extern Preferences prefs; +extern WaterData water_data; +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); + + +// ====================== +// Webserver Setup +// ====================== + +/** + * @brief Sets up all routes for the webserver. + * + * Configures routes for serving HTML pages, handling form submissions, + * REST API endpoints, WebSocket connections, and static assets. + * + * @note Calls setup_api_endpoints() to configure API-specific routes. + */ +void setup_routes() { + server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(LittleFS, "/status.html", "text/html", false, processor); }); + + server.on("/settings", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(LittleFS, "/settings.html", "text/html", false, processor); }); + + server.on("/export", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(LittleFS, "/data_export.html", "text/html", false); }); + + server.on("/logic.js", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(LittleFS, "/logic.js", "application/javascript", false, processor); }); + + server.on("/update", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(LittleFS, "/update_progress.html", "text/html", false, processor); }); + + // API stuff - internal + server.on("/update_wifi_credentials", HTTP_POST, [](AsyncWebServerRequest* request) { + // For settings SSID + if (request->hasParam(ssid_key, true) && request->hasParam(wifi_password_key, true)) { + 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()); + + } else { + request->send(400, "text/plain", "Missing parameters"); // TODO add proper error messages + } + request->send(LittleFS, "/settings.html", "text/html", false, processor); // TODO add proper return templating + }); + + server.on("/update_sensor_settings", HTTP_POST, [](AsyncWebServerRequest* request) { + int params = request->params(); + + if (request->hasParam(level_sensor_range_key, true) && request->hasParam(water_level_min_key, true) && request->hasParam(water_level_max_key, true) && request->hasParam(water_volume_key, true)) { + LOG(ELOG_LEVEL_DEBUG, "Updating Sensor config"); + const AsyncWebParameter* range_param = request->getParam(level_sensor_range_key, true); + const AsyncWebParameter* level_min_param = request->getParam(water_level_min_key, true); + const AsyncWebParameter* level_max_param = request->getParam(water_level_max_key, true); + const AsyncWebParameter* liters_param = request->getParam(water_volume_key, true); + + String range_str = range_param->value(); + String level_min_str = level_min_param->value(); + String level_max_str = level_max_param->value(); + String liters_str = liters_param->value(); + + float range_float = range_str.toFloat(); + float level_min_float = level_min_str.toFloat(); + float level_max_float = level_max_str.toFloat(); + float liters_float = liters_str.toFloat(); + + 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); + + LOG(ELOG_LEVEL_DEBUG, "range_float_after:%D:", prefs.getFloat(level_sensor_range_key, -1.0)); + } else { + LOG(ELOG_LEVEL_DEBUG, "!!!! FAIL lo"); + for (int i = 0; i < params; i++) { + const AsyncWebParameter* p = request->getParam(i); + if (p->isFile()) { // p->isPost() is also true + LOG(ELOG_LEVEL_DEBUG, "POST[%s]: %s\n", p->name().c_str(), p->value().c_str()); + } else if (p->isPost()) { + LOG(ELOG_LEVEL_DEBUG, "POST[%s]: %s\n", p->name().c_str(), p->value().c_str()); + } else { + LOG(ELOG_LEVEL_DEBUG, "GET[%s]: %s\n", p->name().c_str(), p->value().c_str()); + } + } + request->send(400, "text/plain", "Missing parameters"); // TODO add proper error messages + } + request->send(LittleFS, "/settings.html", "text/html", false); // TODO add proper return templating + }); + + setup_api_endpoints(); + webSocket.onEvent(onWsEvent); + server.addHandler(&webSocket); + + server.on("/chota.css", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(LittleFS, "/chota.css", "text/css", false); }); + server.on("/gauge.js", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(LittleFS, "/gauge.js", "application/javascript", false); }); +} + +/** + * @brief Configures REST API endpoints for data retrieval. + * + * Sets up endpoints to fetch sensor data, water data, telemetry, + * network information, and OTA update status in JSON format. + * + * @note These endpoints are used by the frontend to dynamically + * display device status and configuration. + */ +void setup_api_endpoints(){ + server.on("/sensor_data", HTTP_GET, [](AsyncWebServerRequest* request) { + String output; + serializeJson(build_shunt_data_json(shunt_data), output); + request->send(200, "application/json", output); }); + + server.on("/water_data", HTTP_GET, [](AsyncWebServerRequest* request) { + String output; + serializeJson(build_water_data_json(water_data), output); + request->send(200, "application/json", output); }); + + server.on("/raw_percent", HTTP_GET, [](AsyncWebServerRequest* request) { + String output; + output = water_data.percentage; + request->send(200, "text/raw", output); }); + + server.on("/raw_level", HTTP_GET, [](AsyncWebServerRequest* request) { + String output; + output = water_data.level; + request->send(200, "text/raw", output); }); + + server.on("/telemetry", HTTP_GET, [](AsyncWebServerRequest* request) { + String output; + serializeJson(build_telemetry_json(telemetry), output); + request->send(200, "application/json", output); }); + + server.on("/network_info", HTTP_GET, [](AsyncWebServerRequest* request) { + String output; + serializeJson(build_network_json(ethernet_data, wifi_data), output); + request->send(200, "application/json", output); + }); + + server.on("/ota_update_status", HTTP_GET, [](AsyncWebServerRequest* request) { + String output; + serializeJson(build_ota_json(ota_status), output); + request->send(200, "application/json", output); + }); + + server.on("/run_ota_update", HTTP_GET, [](AsyncWebServerRequest* request) { + if (ota_status.update_progress > -1) { + request->send(200, "text/plain", "OTA Update already in progress"); + return; + } else if (!ota_status.update_available) { + request->send(200, "text/plain", "No update available"); + return; + } + + static TaskArgs_t args = { + .ota_status = ota_status + }; + + xTaskCreate(run_ota_update_task, "RunOTAUpdate", 1024 * 8, (void *)&args, 1, NULL); + request->send(LittleFS, "/update_progress.html", "text/html", false, processor); + }); +} + +/** + * @brief Main task for the webserver. + * + * Initializes all routes and starts the webserver. + * Runs indefinitely to keep the server active. + * + * @param pvParameters Task parameters (unused). + */ +void webserver_task(void *pvParameters) { + LOG(ELOG_LEVEL_DEBUG, "Setting up routes"); + setup_routes(); + + LOG(ELOG_LEVEL_DEBUG, "Starting webserver"); + server.begin(); + while (1) { + vTaskDelay(portMAX_DELAY); + } +} \ No newline at end of file diff --git a/src/networking/webserver.h b/src/networking/webserver.h new file mode 100644 index 0000000..621a9c1 --- /dev/null +++ b/src/networking/webserver.h @@ -0,0 +1,13 @@ +#ifndef ASYNC_WEBSERVER_H +#define ASYNC_WEBSERVER_H +#endif // ASYNC_WEBSERVER_H + +#include +#include + +extern AsyncWebServer server; + +void setup_api_endpoints(); +void setup_routes(); +void webserver_task(void *pvParameters); +