From d576969ea43544f3ca2ade50ff6b892579ed5aaa Mon Sep 17 00:00:00 2001 From: Yurii Date: Sun, 2 Nov 2025 11:28:46 +0300 Subject: [PATCH] refactor: initial async web server --- lib/WebServerHandlers/UpgradeHandler.h | 195 +++--- platformio.ini | 10 +- src/PortalTask.h | 849 +++++++++---------------- src/main.cpp | 5 +- src/strings.h | 1 + src_data/pages/upgrade.html | 132 +++- 6 files changed, 512 insertions(+), 680 deletions(-) diff --git a/lib/WebServerHandlers/UpgradeHandler.h b/lib/WebServerHandlers/UpgradeHandler.h index 14c7f93..c909fe3 100644 --- a/lib/WebServerHandlers/UpgradeHandler.h +++ b/lib/WebServerHandlers/UpgradeHandler.h @@ -1,6 +1,6 @@ #include -class UpgradeHandler : public RequestHandler { +class UpgradeHandler : public AsyncWebHandler { public: enum class UpgradeType { FIRMWARE = 0, @@ -12,7 +12,7 @@ public: NO_FILE, SUCCESS, PROHIBITED, - ABORTED, + SIZE_MISMATCH, ERROR_ON_START, ERROR_ON_WRITE, ERROR_ON_FINISH @@ -22,27 +22,20 @@ public: UpgradeType type; UpgradeStatus status; String error; + unsigned int written = 0; } UpgradeResult; - typedef std::function CanHandleCallback; - typedef std::function CanUploadCallback; - typedef std::function BeforeUpgradeCallback; - typedef std::function AfterUpgradeCallback; + typedef std::function BeforeUpgradeCallback; + typedef std::function AfterUpgradeCallback; - UpgradeHandler(const char* uri) { - this->uri = uri; - } + UpgradeHandler(AsyncURIMatcher uri) : uri(uri) {} - UpgradeHandler* setCanHandleCallback(CanHandleCallback callback = nullptr) { - this->canHandleCallback = callback; - - return this; - } - - UpgradeHandler* setCanUploadCallback(CanUploadCallback callback = nullptr) { - this->canUploadCallback = callback; - - return this; + bool canHandle(AsyncWebServerRequest *request) const override final { + if (!request->isHTTP()) { + return false; + } + + return this->uri.matches(request); } UpgradeHandler* setBeforeUpgradeCallback(BeforeUpgradeCallback callback = nullptr) { @@ -57,67 +50,55 @@ public: return this; } - #if defined(ARDUINO_ARCH_ESP32) - bool canHandle(WebServer &server, HTTPMethod method, const String &uri) override { - return this->canHandle(method, uri); - } - #endif - - bool canHandle(HTTPMethod method, const String& uri) override { - return method == HTTP_POST && uri.equals(this->uri) && (!this->canHandleCallback || this->canHandleCallback(method, uri)); - } - - #if defined(ARDUINO_ARCH_ESP32) - bool canUpload(WebServer &server, const String &uri) override { - return this->canUpload(uri); - } - #endif - - bool canUpload(const String& uri) override { - return uri.equals(this->uri) && (!this->canUploadCallback || this->canUploadCallback(uri)); - } - - bool handle(WebServer& server, HTTPMethod method, const String& uri) override { + void handleRequest(AsyncWebServerRequest *request) override final { if (this->afterUpgradeCallback) { - this->afterUpgradeCallback(this->firmwareResult, this->filesystemResult); + this->afterUpgradeCallback(request, this->firmwareResult, this->filesystemResult); } this->firmwareResult.status = UpgradeStatus::NONE; this->firmwareResult.error.clear(); + this->firmwareResult.written = 0; this->filesystemResult.status = UpgradeStatus::NONE; this->filesystemResult.error.clear(); - - return true; + this->filesystemResult.written = 0; } - void upload(WebServer& server, const String& uri, HTTPUpload& upload) override { - UpgradeResult* result; - if (upload.name.equals(F("firmware"))) { + void handleUpload(AsyncWebServerRequest *request, const String &fileName, size_t index, uint8_t *data, size_t dataLength, bool isFinal) override final { + UpgradeResult* result = nullptr; + unsigned int fileSize = 0; + + const auto& fwName = request->hasParam("fw[name]", true) ? request->getParam("fw[name]", true)->value() : String(); + const auto& fsName = request->hasParam("fs[name]", true) ? request->getParam("fs[name]", true)->value() : String(); + + if (fileName.equals(fwName)) { result = &this->firmwareResult; + if (request->hasParam("fw[size]", true)) { + fileSize = request->getParam("fw[size]", true)->value().toInt(); + } - } else if (upload.name.equals(F("filesystem"))) { + } else if (fileName.equals(fsName)) { result = &this->filesystemResult; - - } else { + if (request->hasParam("fs[size]", true)) { + fileSize = request->getParam("fs[size]", true)->value().toInt(); + } + } + + if (result == nullptr || result->status != UpgradeStatus::NONE) { return; } - if (result->status != UpgradeStatus::NONE) { - return; - } - - if (this->beforeUpgradeCallback && !this->beforeUpgradeCallback(result->type)) { + if (this->beforeUpgradeCallback && !this->beforeUpgradeCallback(request, result->type)) { result->status = UpgradeStatus::PROHIBITED; return; } - if (!upload.filename.length()) { + if (!fileName.length() || !fileSize) { result->status = UpgradeStatus::NO_FILE; return; } - if (upload.status == UPLOAD_FILE_START) { + if (index == 0) { // reset if (Update.isRunning()) { Update.end(false); @@ -125,91 +106,89 @@ public: } bool begin = false; - #ifdef ARDUINO_ARCH_ESP8266 - Update.runAsync(true); - - if (result->type == UpgradeType::FIRMWARE) { - begin = Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000, U_FLASH); - - } else if (result->type == UpgradeType::FILESYSTEM) { - close_all_fs(); - begin = Update.begin((size_t)FS_end - (size_t)FS_start, U_FS); - } - #elif defined(ARDUINO_ARCH_ESP32) if (result->type == UpgradeType::FIRMWARE) { begin = Update.begin(UPDATE_SIZE_UNKNOWN, U_FLASH); } else if (result->type == UpgradeType::FILESYSTEM) { begin = Update.begin(UPDATE_SIZE_UNKNOWN, U_SPIFFS); } - #endif if (!begin || Update.hasError()) { result->status = UpgradeStatus::ERROR_ON_START; - #ifdef ARDUINO_ARCH_ESP8266 - result->error = Update.getErrorString(); - #else result->error = Update.errorString(); - #endif - Log.serrorln(FPSTR(L_PORTAL_OTA), F("File '%s', on start: %s"), upload.filename.c_str(), result->error.c_str()); + Log.serrorln(FPSTR(L_PORTAL_OTA), F("File '%s', on start: %s"), fileName.c_str(), result->error.c_str()); return; } - Log.sinfoln(FPSTR(L_PORTAL_OTA), F("File '%s', started"), upload.filename.c_str()); + Log.sinfoln(FPSTR(L_PORTAL_OTA), F("File '%s', started"), fileName.c_str()); + } + + if (dataLength) { + result->written += dataLength; - } else if (upload.status == UPLOAD_FILE_WRITE) { - if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { + if (Update.write(data, dataLength) != dataLength) { Update.end(false); - result->status = UpgradeStatus::ERROR_ON_WRITE; - #ifdef ARDUINO_ARCH_ESP8266 - result->error = Update.getErrorString(); - #else result->error = Update.errorString(); - #endif Log.serrorln( FPSTR(L_PORTAL_OTA), - F("File '%s', on writing %d bytes: %s"), - upload.filename.c_str(), upload.totalSize, result->error.c_str() + F("File '%s', on write %d bytes, %d of %d bytes"), + fileName.c_str(), + dataLength, + result->written, + fileSize ); - - } else { - Log.sinfoln(FPSTR(L_PORTAL_OTA), F("File '%s', writed %d bytes"), upload.filename.c_str(), upload.totalSize); + return; } - } else if (upload.status == UPLOAD_FILE_END) { - if (Update.end(true)) { - result->status = UpgradeStatus::SUCCESS; + Log.sinfoln( + FPSTR(L_PORTAL_OTA), + F("File '%s', write %d bytes, %d of %d bytes"), + fileName.c_str(), + dataLength, + result->written, + fileSize + ); + } - Log.sinfoln(FPSTR(L_PORTAL_OTA), F("File '%s': finish"), upload.filename.c_str()); - - } else { - result->status = UpgradeStatus::ERROR_ON_FINISH; - #ifdef ARDUINO_ARCH_ESP8266 - result->error = Update.getErrorString(); - #else - result->error = Update.errorString(); - #endif - - Log.serrorln(FPSTR(L_PORTAL_OTA), F("File '%s', on finish: %s"), upload.filename.c_str(), result->error); - } - - } else if (upload.status == UPLOAD_FILE_ABORTED) { + if (result->written > fileSize || (isFinal && result->written < fileSize)) { Update.end(false); - result->status = UpgradeStatus::ABORTED; + result->status = UpgradeStatus::SIZE_MISMATCH; - Log.serrorln(FPSTR(L_PORTAL_OTA), F("File '%s': aborted"), upload.filename.c_str()); + Log.serrorln( + FPSTR(L_PORTAL_OTA), + F("File '%s', size mismatch: %d of %d bytes"), + fileName.c_str(), + result->written, + fileSize + ); + return; + } + + if (isFinal) { + if (!Update.end(true)) { + result->status = UpgradeStatus::ERROR_ON_FINISH; + result->error = Update.errorString(); + + Log.serrorln(FPSTR(L_PORTAL_OTA), F("File '%s', on finish: %s"), fileName.c_str(), result->error); + return; + } + + result->status = UpgradeStatus::SUCCESS; + Log.sinfoln(FPSTR(L_PORTAL_OTA), F("File '%s': finish"), fileName.c_str()); } } + bool isRequestHandlerTrivial() const override final { + return false; + } + protected: - CanHandleCallback canHandleCallback; - CanUploadCallback canUploadCallback; BeforeUpgradeCallback beforeUpgradeCallback; AfterUpgradeCallback afterUpgradeCallback; - const char* uri = nullptr; + AsyncURIMatcher uri; UpgradeResult firmwareResult{UpgradeType::FIRMWARE, UpgradeStatus::NONE}; UpgradeResult filesystemResult{UpgradeType::FILESYSTEM, UpgradeStatus::NONE}; diff --git a/platformio.ini b/platformio.ini index 2cdeed2..3efa718 100644 --- a/platformio.ini +++ b/platformio.ini @@ -17,6 +17,9 @@ core_dir = .pio version = 1.5.6 framework = arduino lib_deps = + ESP32Async/AsyncTCP + ;ESP32Async/ESPAsyncWebServer + https://github.com/ESP32Async/ESPAsyncWebServer#main bblanchon/ArduinoJson@^7.4.2 ;ihormelnyk/OpenTherm Library@^1.1.5 https://github.com/Laxilef/opentherm_library#esp32_timer @@ -34,6 +37,9 @@ build_flags = ;-D DEBUG_ESP_CORE -D DEBUG_ESP_WIFI -D DEBUG_ESP_HTTP_SERVER -D DEBUG_ESP_PORT=Serial -D BUILD_VERSION='"${this.version}"' -D BUILD_ENV='"$PIOENV"' + -D CONFIG_ASYNC_TCP_STACK_SIZE=4096 + -D ARDUINOJSON_USE_DOUBLE=0 + -D ARDUINOJSON_USE_LONG_LONG=0 -D DEFAULT_SERIAL_ENABLED=${secrets.serial_enabled} -D DEFAULT_SERIAL_BAUD=${secrets.serial_baud} -D DEFAULT_TELNET_ENABLED=${secrets.telnet_enabled} @@ -98,7 +104,7 @@ board_build.partitions = esp32_partitions.csv lib_deps = ${env.lib_deps} laxilef/ESP32Scheduler@^1.0.1 -nimble_lib = h2zero/NimBLE-Arduino@2.3.3 +nimble_lib = https://github.com/h2zero/NimBLE-Arduino lib_ignore = extra_scripts = post:tools/esp32.py @@ -234,7 +240,7 @@ build_flags = ${esp32_defaults.build_flags} -D ARDUINO_USB_MODE=0 -D ARDUINO_USB_CDC_ON_BOOT=1 - -D CONFIG_BT_NIMBLE_EXT_ADV=1 + ;-D CONFIG_BT_NIMBLE_EXT_ADV=1 -D USE_BLE=1 -D DEFAULT_OT_IN_GPIO=35 -D DEFAULT_OT_OUT_GPIO=36 diff --git a/src/PortalTask.h b/src/PortalTask.h index 0968de8..d97cece 100644 --- a/src/PortalTask.h +++ b/src/PortalTask.h @@ -1,18 +1,10 @@ //#define PORTAL_CACHE "max-age=86400" #define PORTAL_CACHE nullptr -#ifdef ARDUINO_ARCH_ESP8266 -#include -#include -#include -using WebServer = ESP8266WebServer; -#else #include -#include +#include +#include +#include #include -#endif -#include -#include -#include #include #include @@ -26,14 +18,11 @@ extern MqttTask* tMqtt; class PortalTask : public LeanTask { public: PortalTask(bool _enabled = false, unsigned long _interval = 0) : LeanTask(_enabled, _interval) { - this->webServer = new WebServer(80); - this->bufferedWebServer = new BufferedWebServer(this->webServer, 32u); + this->webServer = new AsyncWebServer(80); this->dnsServer = new DNSServer(); } ~PortalTask() { - delete this->bufferedWebServer; - if (this->webServer != nullptr) { this->stopWebServer(); delete this->webServer; @@ -48,8 +37,7 @@ public: protected: const unsigned int changeStateInterval = 5000; - WebServer* webServer = nullptr; - BufferedWebServer* bufferedWebServer = nullptr; + AsyncWebServer* webServer = nullptr; DNSServer* dnsServer = nullptr; bool webServerEnabled = false; @@ -74,208 +62,131 @@ protected: void setup() { this->dnsServer->setTTL(0); this->dnsServer->setErrorReplyCode(DNSReplyCode::NoError); - this->webServer->enableETag(true, [](FS &fs, const String &fName) -> const String { - char buf[32]; - { - MD5Builder md5; - md5.begin(); - md5.add(fName); - md5.add(" " BUILD_ENV " " BUILD_VERSION " " __DATE__ " " __TIME__); - md5.calculate(); - md5.getChars(buf); + + // auth middleware + static AsyncMiddlewareFunction authMiddleware([](AsyncWebServerRequest *request, ArMiddlewareNext next) { + if (network->isApEnabled() || !settings.portal.auth || !strlen(settings.portal.password)) { + return next(); } - String etag; - etag.reserve(34); - etag += '\"'; - etag.concat(buf, 32); - etag += '\"'; - return etag; + if (request->authenticate(settings.portal.login, settings.portal.password, PROJECT_NAME)) { + return next(); + } + + return request->requestAuthentication(AsyncAuthType::AUTH_BASIC, PROJECT_NAME, "Authentication failed"); }); // index page - /*auto indexPage = (new DynamicPage("/", &LittleFS, "/pages/index.html")) - ->setTemplateCallback([](const char* var) -> String { - String result; - - if (strcmp(var, "ver") == 0) { - result = BUILD_VERSION; - } - - return result; - }); - this->webServer->addHandler(indexPage);*/ - this->webServer->addHandler(new StaticPage("/", &LittleFS, F("/pages/index.html"), PORTAL_CACHE)); + this->webServer->on("/", HTTP_GET, [](AsyncWebServerRequest *request) { + request->redirect("/index.html"); + }); + this->webServer->serveStatic("/index.html", LittleFS, "/pages/index.html", PORTAL_CACHE); // dashboard page - auto dashboardPage = (new StaticPage("/dashboard.html", &LittleFS, F("/pages/dashboard.html"), PORTAL_CACHE)) - ->setBeforeSendCallback([this]() { - if (this->isAuthRequired() && !this->isValidCredentials()) { - this->webServer->requestAuthentication(BASIC_AUTH); - return false; - } - - return true; - }); - this->webServer->addHandler(dashboardPage); + this->webServer->serveStatic("/dashboard.html", LittleFS, "/pages/dashboard.html", PORTAL_CACHE) + .addMiddleware(&authMiddleware); // restart - this->webServer->on(F("/restart.html"), HTTP_GET, [this]() { - if (this->isAuthRequired() && !this->isValidCredentials()) { - this->webServer->requestAuthentication(BASIC_AUTH); - return; - } - + this->webServer->on("/restart.html", HTTP_GET, [](AsyncWebServerRequest *request) { vars.actions.restart = true; - this->webServer->sendHeader(F("Location"), "/"); - this->webServer->send(302); - }); + request->redirect("/"); + }).addMiddleware(&authMiddleware); // network settings page - auto networkPage = (new StaticPage("/network.html", &LittleFS, F("/pages/network.html"), PORTAL_CACHE)) - ->setBeforeSendCallback([this]() { - if (this->isAuthRequired() && !this->isValidCredentials()) { - this->webServer->requestAuthentication(BASIC_AUTH); - return false; - } - - return true; - }); - this->webServer->addHandler(networkPage); + this->webServer->serveStatic("/network.html", LittleFS, "/pages/network.html", PORTAL_CACHE) + .addMiddleware(&authMiddleware); // settings page - auto settingsPage = (new StaticPage("/settings.html", &LittleFS, F("/pages/settings.html"), PORTAL_CACHE)) - ->setBeforeSendCallback([this]() { - if (this->isAuthRequired() && !this->isValidCredentials()) { - this->webServer->requestAuthentication(BASIC_AUTH); - return false; - } - - return true; - }); - this->webServer->addHandler(settingsPage); + this->webServer->serveStatic("/settings.html", LittleFS, "/pages/settings.html", PORTAL_CACHE) + .addMiddleware(&authMiddleware); // sensors page - auto sensorsPage = (new StaticPage("/sensors.html", &LittleFS, F("/pages/sensors.html"), PORTAL_CACHE)) - ->setBeforeSendCallback([this]() { - if (this->isAuthRequired() && !this->isValidCredentials()) { - this->webServer->requestAuthentication(BASIC_AUTH); - return false; - } - - return true; - }); - this->webServer->addHandler(sensorsPage); + this->webServer->serveStatic("/sensors.html", LittleFS, "/pages/sensors.html", PORTAL_CACHE) + .addMiddleware(&authMiddleware); // upgrade page - auto upgradePage = (new StaticPage("/upgrade.html", &LittleFS, F("/pages/upgrade.html"), PORTAL_CACHE)) - ->setBeforeSendCallback([this]() { - if (this->isAuthRequired() && !this->isValidCredentials()) { - this->webServer->requestAuthentication(BASIC_AUTH); - return false; - } - - return true; - }); - this->webServer->addHandler(upgradePage); + this->webServer->serveStatic("/upgrade.html", LittleFS, "/pages/upgrade.html", PORTAL_CACHE) + .addMiddleware(&authMiddleware); // OTA - auto upgradeHandler = (new UpgradeHandler("/api/upgrade"))->setCanUploadCallback([this](const String& uri) { - if (this->isAuthRequired() && !this->isValidCredentials()) { - this->webServer->sendHeader(F("Connection"), F("close")); - this->webServer->send(401); - return false; - } - - return true; - })->setBeforeUpgradeCallback([](UpgradeHandler::UpgradeType type) -> bool { + auto upgradeHandler = (new UpgradeHandler("/api/upgrade")) + ->setBeforeUpgradeCallback([](AsyncWebServerRequest *request, UpgradeHandler::UpgradeType type) -> bool { if (vars.states.restarting) { return false; } vars.states.upgrading = true; return true; - })->setAfterUpgradeCallback([this](const UpgradeHandler::UpgradeResult& fwResult, const UpgradeHandler::UpgradeResult& fsResult) { - unsigned short status = 200; + + })->setAfterUpgradeCallback([](AsyncWebServerRequest *request, const UpgradeHandler::UpgradeResult& fwResult, const UpgradeHandler::UpgradeResult& fsResult) { + unsigned short status = 406; if (fwResult.status == UpgradeHandler::UpgradeStatus::SUCCESS || fsResult.status == UpgradeHandler::UpgradeStatus::SUCCESS) { + status = 202; vars.actions.restart = true; - - } else { - status = 400; } - - String response = F("{\"firmware\": {\"status\": "); - response.concat((short int) fwResult.status); - response.concat(F(", \"error\": \"")); - response.concat(fwResult.error); - response.concat(F("\"}, \"filesystem\": {\"status\": ")); - response.concat((short int) fsResult.status); - response.concat(F(", \"error\": \"")); - response.concat(fsResult.error); - response.concat(F("\"}}")); - this->webServer->send(status, F("application/json"), response); - vars.states.upgrading = false; + + // make response + auto *response = new AsyncJsonResponse(); + auto& doc = response->getRoot(); + + auto fwDoc = doc["firmware"].to(); + fwDoc["status"] = static_cast(fwResult.status); + fwDoc["error"] = fwResult.error; + + auto fsDoc = doc["filesystem"].to(); + fsDoc["status"] = static_cast(fsResult.status); + fsDoc["error"] = fsResult.error; + + // send response + response->setLength(); + response->setCode(status); + request->send(response); }); this->webServer->addHandler(upgradeHandler); // backup - this->webServer->on(F("/api/backup/save"), HTTP_GET, [this]() { - if (this->isAuthRequired() && !this->isValidCredentials()) { - return this->webServer->send(401); - } - - JsonDocument doc; + this->webServer->on("/api/backup/save", HTTP_GET, [](AsyncWebServerRequest *request) { + // make response + auto *response = new AsyncJsonResponse(); + auto& doc = response->getRoot(); + // add network settings auto networkDoc = doc[FPSTR(S_NETWORK)].to(); networkSettingsToJson(networkSettings, networkDoc); + // add main settings auto settingsDoc = doc[FPSTR(S_SETTINGS)].to(); settingsToJson(settings, settingsDoc); + // add sensors settings for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) { auto sensorsettingsDoc = doc[FPSTR(S_SENSORS)][sensorId].to(); sensorSettingsToJson(sensorId, Sensors::settings[sensorId], sensorsettingsDoc); } - doc.shrinkToFit(); - - this->webServer->sendHeader(F("Content-Disposition"), F("attachment; filename=\"backup.json\"")); - this->bufferedWebServer->send(200, F("application/json"), doc); - }); - - this->webServer->on(F("/api/backup/restore"), HTTP_POST, [this]() { - if (this->isAuthRequired() && !this->isValidCredentials()) { - return this->webServer->send(401); - } + // send response + response->addHeader("Content-Disposition", "attachment; filename=\"backup.json\""); + response->setLength(); + request->send(response); + }).addMiddleware(&authMiddleware); + // backup restore + auto& brHandler = this->webServer->on("/api/backup/restore", HTTP_POST, [](AsyncWebServerRequest *request, JsonVariant &requestDoc) { if (vars.states.restarting) { - return this->webServer->send(503); + return request->send(503); } - const String& plain = this->webServer->arg(0); - Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Request /api/backup/restore %d bytes: %s"), plain.length(), plain.c_str()); - - if (plain.length() < 5) { - this->webServer->send(406); - return; - - } else if (plain.length() > 2536) { - this->webServer->send(413); - return; - } - - JsonDocument doc; - DeserializationError dErr = deserializeJson(doc, plain); - - if (dErr != DeserializationError::Ok || doc.isNull() || !doc.size()) { - this->webServer->send(400); - return; + if (requestDoc.isNull() || !requestDoc.size()) { + return request->send(400); } + // prepare request bool changed = false; - if (!doc[FPSTR(S_NETWORK)].isNull() && jsonToNetworkSettings(doc[FPSTR(S_NETWORK)], networkSettings)) { + + // restore network settings + if (!requestDoc[FPSTR(S_NETWORK)].isNull() && jsonToNetworkSettings(requestDoc[FPSTR(S_NETWORK)], networkSettings)) { fsNetworkSettings.update(); network->setHostname(networkSettings.hostname) ->setStaCredentials(networkSettings.sta.ssid, networkSettings.sta.password, networkSettings.sta.channel) @@ -290,13 +201,15 @@ protected: changed = true; } - if (!doc[FPSTR(S_SETTINGS)].isNull() && jsonToSettings(doc[FPSTR(S_SETTINGS)], settings)) { + // restore main settings + if (!requestDoc[FPSTR(S_SETTINGS)].isNull() && jsonToSettings(requestDoc[FPSTR(S_SETTINGS)], settings)) { fsSettings.update(); changed = true; } - if (!doc[FPSTR(S_SENSORS)].isNull()) { - for (auto sensor : doc[FPSTR(S_SENSORS)].as()) { + // restore sensors settings + if (!requestDoc[FPSTR(S_SENSORS)].isNull()) { + for (auto sensor : requestDoc[FPSTR(S_SENSORS)].as()) { if (!isDigit(sensor.key().c_str())) { continue; } @@ -312,72 +225,54 @@ protected: } } } - - doc.clear(); - doc.shrinkToFit(); + requestDoc.clear(); + + // send response + request->send(changed ? 201 : 200); if (changed) { vars.actions.restart = true; } - - this->webServer->send(changed ? 201 : 200); }); + brHandler.setMaxContentLength(3072); + brHandler.addMiddleware(&authMiddleware); // network - this->webServer->on(F("/api/network/settings"), HTTP_GET, [this]() { - if (this->isAuthRequired() && !this->isValidCredentials()) { - return this->webServer->send(401); - } - - JsonDocument doc; + this->webServer->on("/api/network/settings", HTTP_GET, [](AsyncWebServerRequest *request) { + // make response + auto *response = new AsyncJsonResponse(); + auto& doc = response->getRoot(); networkSettingsToJson(networkSettings, doc); - doc.shrinkToFit(); - this->bufferedWebServer->send(200, F("application/json"), doc); - }); + // send response + response->setLength(); + request->send(response); + }).addMiddleware(&authMiddleware); - this->webServer->on(F("/api/network/settings"), HTTP_POST, [this]() { - if (this->isAuthRequired() && !this->isValidCredentials()) { - return this->webServer->send(401); - } - + auto& nsHandler = this->webServer->on("/api/network/settings", HTTP_POST, [](AsyncWebServerRequest *request, JsonVariant &requestDoc) { if (vars.states.restarting) { - return this->webServer->send(503); + return request->send(503); } - const String& plain = this->webServer->arg(0); - Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Request /api/network/settings %d bytes: %s"), plain.length(), plain.c_str()); - - if (plain.length() < 5) { - this->webServer->send(406); - return; - - } else if (plain.length() > 512) { - this->webServer->send(413); - return; + if (requestDoc.isNull() || !requestDoc.size()) { + return request->send(400); } - JsonDocument doc; - DeserializationError dErr = deserializeJson(doc, plain); + // prepare request + bool changed = jsonToNetworkSettings(requestDoc, networkSettings); + requestDoc.clear(); - if (dErr != DeserializationError::Ok || doc.isNull() || !doc.size()) { - this->webServer->send(400); - return; - } + // make response + auto *response = new AsyncJsonResponse(); + auto& responseDoc = response->getRoot(); + networkSettingsToJson(networkSettings, responseDoc); - bool changed = jsonToNetworkSettings(doc, networkSettings); - doc.clear(); - doc.shrinkToFit(); - - networkSettingsToJson(networkSettings, doc); - doc.shrinkToFit(); - - this->bufferedWebServer->send(changed ? 201 : 200, F("application/json"), doc); + // send response + response->setLength(); + response->setCode(changed ? 201 : 200); + request->send(response); if (changed) { - doc.clear(); - doc.shrinkToFit(); - fsNetworkSettings.update(); network->setHostname(networkSettings.hostname) ->setStaCredentials(networkSettings.sta.ssid, networkSettings.sta.password, networkSettings.sta.channel) @@ -392,298 +287,240 @@ protected: ->reconnect(); } }); + nsHandler.setMaxContentLength(512); + nsHandler.addMiddleware(&authMiddleware); - this->webServer->on(F("/api/network/scan"), HTTP_GET, [this]() { - if (this->isAuthRequired() && !this->isValidCredentials()) { - return this->webServer->send(401); - } - + this->webServer->on("/api/network/scan", HTTP_GET, [](AsyncWebServerRequest *request) { auto apCount = WiFi.scanComplete(); - if (apCount <= 0) { - if (apCount != WIFI_SCAN_RUNNING) { - #ifdef ARDUINO_ARCH_ESP8266 - WiFi.scanNetworks(true, true); - #else - WiFi.scanNetworks(true, true, true); - #endif - } - - this->webServer->send(404); - return; - } - - JsonDocument doc; - for (short int i = 0; i < apCount; i++) { - const String& ssid = WiFi.SSID(i); - doc[i][FPSTR(S_SSID)] = ssid; - doc[i][FPSTR(S_BSSID)] = WiFi.BSSIDstr(i); - doc[i][FPSTR(S_SIGNAL_QUALITY)] = NetworkMgr::rssiToSignalQuality(WiFi.RSSI(i)); - doc[i][FPSTR(S_CHANNEL)] = WiFi.channel(i); - doc[i][FPSTR(S_HIDDEN)] = !ssid.length(); - #ifdef ARDUINO_ARCH_ESP8266 - const bss_info* info = WiFi.getScanInfoByIndex(i); - doc[i][FPSTR(S_AUTH)] = info->authmode; - #else - doc[i][FPSTR(S_AUTH)] = WiFi.encryptionType(i); - #endif - } - doc.shrinkToFit(); - this->bufferedWebServer->send(200, F("application/json"), doc); + switch (apCount) { + case WIFI_SCAN_FAILED: + WiFi.scanNetworks(true, true, false); + request->send(404); + break; - WiFi.scanDelete(); - }); + case WIFI_SCAN_RUNNING: + request->send(202); + break; + + default: + // make respponse + auto *response = new AsyncJsonResponse(true); + auto& doc = response->getRoot(); + for (short int i = 0; i < apCount; i++) { + doc[i][FPSTR(S_SSID)] = WiFi.SSID(i); + doc[i][FPSTR(S_BSSID)] = WiFi.BSSIDstr(i); + doc[i][FPSTR(S_SIGNAL_QUALITY)] = NetworkMgr::rssiToSignalQuality(WiFi.RSSI(i)); + doc[i][FPSTR(S_CHANNEL)] = WiFi.channel(i); + doc[i][FPSTR(S_HIDDEN)] = !WiFi.SSID(i).length(); + doc[i][FPSTR(S_AUTH)] = WiFi.encryptionType(i); + } + + // send response + response->setLength(); + request->send(response); + + // clear + WiFi.scanDelete(); + } + }).addMiddleware(&authMiddleware); // settings - this->webServer->on(F("/api/settings"), HTTP_GET, [this]() { - if (this->isAuthRequired() && !this->isValidCredentials()) { - return this->webServer->send(401); - } - - JsonDocument doc; + this->webServer->on("/api/settings", HTTP_GET, [](AsyncWebServerRequest *request) { + // make response + auto *response = new AsyncJsonResponse(); + auto& doc = response->getRoot(); settingsToJson(settings, doc); - doc.shrinkToFit(); - - this->bufferedWebServer->send(200, F("application/json"), doc); - }); - this->webServer->on(F("/api/settings"), HTTP_POST, [this]() { - if (this->isAuthRequired() && !this->isValidCredentials()) { - return this->webServer->send(401); - } - + // send response + response->setLength(); + request->send(response); + }).addMiddleware(&authMiddleware); + + auto& sHandler = this->webServer->on("/api/settings", HTTP_POST, [](AsyncWebServerRequest *request, JsonVariant &requestDoc) { if (vars.states.restarting) { - return this->webServer->send(503); + return request->send(503); } - const String& plain = this->webServer->arg(0); - Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Request /api/settings %d bytes: %s"), plain.length(), plain.c_str()); - - if (plain.length() < 5) { - this->webServer->send(406); - return; - - } else if (plain.length() > 2536) { - this->webServer->send(413); - return; + if (requestDoc.isNull() || !requestDoc.size()) { + return request->send(400); } - JsonDocument doc; - DeserializationError dErr = deserializeJson(doc, plain); + // prepare request + bool changed = jsonToSettings(requestDoc, settings); + requestDoc.clear(); - if (dErr != DeserializationError::Ok || doc.isNull() || !doc.size()) { - this->webServer->send(400); - return; - } + // make response + auto *response = new AsyncJsonResponse(); + auto& responseDoc = response->getRoot(); + settingsToJson(settings, responseDoc); - bool changed = jsonToSettings(doc, settings); - doc.clear(); - doc.shrinkToFit(); - - settingsToJson(settings, doc); - doc.shrinkToFit(); - - this->bufferedWebServer->send(changed ? 201 : 200, F("application/json"), doc); + // send response + response->setLength(); + response->setCode(changed ? 201 : 200); + request->send(response); if (changed) { - doc.clear(); - doc.shrinkToFit(); - fsSettings.update(); tMqtt->resetPublishedSettingsTime(); } }); + sHandler.setMaxContentLength(3072); + sHandler.addMiddleware(&authMiddleware); // sensors list - this->webServer->on(F("/api/sensors"), HTTP_GET, [this]() { - if (this->isAuthRequired() && !this->isValidCredentials()) { - return this->webServer->send(401); - } + this->webServer->on("/api/sensors", HTTP_GET, [](AsyncWebServerRequest *request) { + // make response + bool detailed = request->hasParam("detailed"); + auto *response = new AsyncJsonResponse(true); + auto& responseDoc = response->getRoot(); - bool detailed = false; - if (this->webServer->hasArg(F("detailed"))) { - detailed = this->webServer->arg(F("detailed")).toInt() > 0; - } - - JsonDocument doc; + // add sensors for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) { if (detailed) { auto& sSensor = Sensors::settings[sensorId]; - doc[sensorId][FPSTR(S_ENABLED)] = sSensor.enabled; - doc[sensorId][FPSTR(S_NAME)] = sSensor.name; - doc[sensorId][FPSTR(S_PURPOSE)] = static_cast(sSensor.purpose); - sensorResultToJson(sensorId, doc[sensorId]); + responseDoc[sensorId][FPSTR(S_ENABLED)] = sSensor.enabled; + responseDoc[sensorId][FPSTR(S_NAME)] = sSensor.name; + responseDoc[sensorId][FPSTR(S_PURPOSE)] = static_cast(sSensor.purpose); + sensorResultToJson(sensorId, responseDoc[sensorId]); } else { - doc[sensorId] = Sensors::settings[sensorId].name; + responseDoc[sensorId] = Sensors::settings[sensorId].name; } } - doc.shrinkToFit(); - this->bufferedWebServer->send(200, F("application/json"), doc); - }); + // send response + response->setLength(); + request->send(response); + }).addMiddleware(&authMiddleware); // sensor settings - this->webServer->on(F("/api/sensor"), HTTP_GET, [this]() { - if (this->isAuthRequired() && !this->isValidCredentials()) { - return this->webServer->send(401); - } - - if (!this->webServer->hasArg(F("id"))) { - return this->webServer->send(400); + this->webServer->on("/api/sensor", HTTP_GET, [](AsyncWebServerRequest *request) { + if (!request->hasParam("id")) { + return request->send(400); } - auto id = this->webServer->arg(F("id")); + const auto& id = request->getParam("id")->value(); if (!isDigit(id.c_str())) { - return this->webServer->send(400); + return request->send(400); } uint8_t sensorId = id.toInt(); - id.clear(); if (!Sensors::isValidSensorId(sensorId)) { - return this->webServer->send(404); + return request->send(404); } - JsonDocument doc; + auto *response = new AsyncJsonResponse(); + auto& doc = response->getRoot(); sensorSettingsToJson(sensorId, Sensors::settings[sensorId], doc); - doc.shrinkToFit(); - this->bufferedWebServer->send(200, F("application/json"), doc); - }); + + response->setLength(); + request->send(response); + }).addMiddleware(&authMiddleware); - this->webServer->on(F("/api/sensor"), HTTP_POST, [this]() { - if (this->isAuthRequired() && !this->isValidCredentials()) { - return this->webServer->send(401); - } - + auto& ssHandler = this->webServer->on("/api/sensor", HTTP_POST, [](AsyncWebServerRequest *request, JsonVariant &requestDoc) { if (vars.states.restarting) { - return this->webServer->send(503); + return request->send(503); } - #ifdef ARDUINO_ARCH_ESP8266 - if (!this->webServer->hasArg(F("id")) || this->webServer->args() != 1) { - return this->webServer->send(400); + if (requestDoc.isNull() || !requestDoc.size()) { + return request->send(400); } - #else - if (!this->webServer->hasArg(F("id")) || this->webServer->args() != 2) { - return this->webServer->send(400); - } - #endif - auto id = this->webServer->arg(F("id")); + if (!request->hasParam("id")) { + return request->send(400); + } + + auto& id = request->getParam("id")->value(); if (!isDigit(id.c_str())) { - return this->webServer->send(400); + return request->send(400); } uint8_t sensorId = id.toInt(); - id.clear(); if (!Sensors::isValidSensorId(sensorId)) { - return this->webServer->send(404); + return request->send(404); } - - auto plain = this->webServer->arg(1); - Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Request /api/sensor/?id=%hhu %d bytes: %s"), sensorId, plain.length(), plain.c_str()); - - if (plain.length() < 5) { - return this->webServer->send(406); - - } else if (plain.length() > 1024) { - return this->webServer->send(413); - } - - bool changed = false; + + // prepare request auto prevSettings = Sensors::settings[sensorId]; - { - JsonDocument doc; - DeserializationError dErr = deserializeJson(doc, plain); - plain.clear(); + bool changed = jsonToSensorSettings(sensorId, requestDoc, Sensors::settings[sensorId]); + requestDoc.clear(); - if (dErr != DeserializationError::Ok || doc.isNull() || !doc.size()) { - return this->webServer->send(400); - } + // make response + auto *response = new AsyncJsonResponse(); + auto& responseDoc = response->getRoot(); - if (jsonToSensorSettings(sensorId, doc, Sensors::settings[sensorId])) { - changed = true; - } - } - - { - JsonDocument doc; - auto& sSettings = Sensors::settings[sensorId]; - sensorSettingsToJson(sensorId, sSettings, doc); - doc.shrinkToFit(); - - this->bufferedWebServer->send(changed ? 201 : 200, F("application/json"), doc); - } + auto& sSettings = Sensors::settings[sensorId]; + sensorSettingsToJson(sensorId, sSettings, responseDoc); + + // send response + response->setLength(); + response->setCode(changed ? 201 : 200); + request->send(response); if (changed) { tMqtt->reconfigureSensor(sensorId, prevSettings); fsSensorsSettings.update(); } }); + ssHandler.setMaxContentLength(1024); + ssHandler.addMiddleware(&authMiddleware); // vars - this->webServer->on(F("/api/vars"), HTTP_GET, [this]() { - JsonDocument doc; + this->webServer->on("/api/vars", HTTP_GET, [](AsyncWebServerRequest *request) { + // make response + auto *response = new AsyncJsonResponse(); + auto& doc = response->getRoot(); varsToJson(vars, doc); - doc.shrinkToFit(); - this->bufferedWebServer->send(200, F("application/json"), doc); + // send response + response->setLength(); + request->send(response); }); - this->webServer->on(F("/api/vars"), HTTP_POST, [this]() { - if (this->isAuthRequired() && !this->isValidCredentials()) { - return this->webServer->send(401); + auto& vHandler = this->webServer->on("/api/vars", HTTP_POST, [](AsyncWebServerRequest *request, JsonVariant &requestDoc) { + if (vars.states.restarting) { + return request->send(503); } - const String& plain = this->webServer->arg(0); - Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Request /api/vars %d bytes: %s"), plain.length(), plain.c_str()); - - if (plain.length() < 5) { - this->webServer->send(406); - return; - - } else if (plain.length() > 1536) { - this->webServer->send(413); - return; + if (requestDoc.isNull() || !requestDoc.size()) { + return request->send(400); } - JsonDocument doc; - DeserializationError dErr = deserializeJson(doc, plain); + // prepare request + bool changed = jsonToVars(requestDoc, vars); + requestDoc.clear(); - if (dErr != DeserializationError::Ok || doc.isNull() || !doc.size()) { - this->webServer->send(400); - return; - } - - bool changed = jsonToVars(doc, vars); - doc.clear(); - doc.shrinkToFit(); - - varsToJson(vars, doc); - doc.shrinkToFit(); + // make response + auto *response = new AsyncJsonResponse(); + auto& responseDoc = response->getRoot(); + varsToJson(vars, responseDoc); - this->bufferedWebServer->send(changed ? 201 : 200, F("application/json"), doc); + // send response + response->setLength(); + response->setCode(changed ? 201 : 200); + request->send(response); if (changed) { - doc.clear(); - doc.shrinkToFit(); - tMqtt->resetPublishedVarsTime(); } }); + vHandler.setMaxContentLength(1536); + vHandler.addMiddleware(&authMiddleware); - this->webServer->on(F("/api/info"), HTTP_GET, [this]() { - bool isConnected = network->isConnected(); - - JsonDocument doc; + this->webServer->on("/api/info", HTTP_GET, [](AsyncWebServerRequest *request) { + // make response + auto *response = new AsyncJsonResponse(); + auto& doc = response->getRoot(); auto docSystem = doc[FPSTR(S_SYSTEM)].to(); docSystem[FPSTR(S_RESET_REASON)] = getResetReason(); docSystem[FPSTR(S_UPTIME)] = millis() / 1000; + bool isConnected = network->isConnected(); auto docNetwork = doc[FPSTR(S_NETWORK)].to(); docNetwork[FPSTR(S_HOSTNAME)] = networkSettings.hostname; docNetwork[FPSTR(S_MAC)] = network->getStaMac(); @@ -700,16 +537,8 @@ protected: docBuild[FPSTR(S_VERSION)] = BUILD_VERSION; docBuild[FPSTR(S_DATE)] = __DATE__ " " __TIME__; docBuild[FPSTR(S_ENV)] = BUILD_ENV; - #ifdef ARDUINO_ARCH_ESP8266 docBuild[FPSTR(S_CORE)] = ESP.getCoreVersion(); docBuild[FPSTR(S_SDK)] = ESP.getSdkVersion(); - #elif ARDUINO_ARCH_ESP32 - docBuild[FPSTR(S_CORE)] = ESP.getCoreVersion(); - docBuild[FPSTR(S_SDK)] = ESP.getSdkVersion(); - #else - docBuild[FPSTR(S_CORE)] = 0; - docBuild[FPSTR(S_SDK)] = 0; - #endif auto docHeap = doc[FPSTR(S_HEAP)].to(); docHeap[FPSTR(S_TOTAL)] = getTotalHeap(); @@ -717,59 +546,39 @@ protected: docHeap[FPSTR(S_MIN_FREE)] = getFreeHeap(true); docHeap[FPSTR(S_MAX_FREE_BLOCK)] = getMaxFreeBlockHeap(); docHeap[FPSTR(S_MIN_MAX_FREE_BLOCK)] = getMaxFreeBlockHeap(true); + + auto docPsram = doc[FPSTR(S_PSRAM)].to(); + docPsram[FPSTR(S_TOTAL)] = ESP.getPsramSize(); + docPsram[FPSTR(S_FREE)] = ESP.getFreePsram(); + docPsram[FPSTR(S_MIN_FREE)] = ESP.getMinFreePsram(); + docPsram[FPSTR(S_MAX_FREE_BLOCK)] = ESP.getMaxAllocPsram(); auto docChip = doc[FPSTR(S_CHIP)].to(); - #ifdef ARDUINO_ARCH_ESP8266 - docChip[FPSTR(S_MODEL)] = esp_is_8285() ? F("ESP8285") : F("ESP8266"); - docChip[FPSTR(S_REV)] = 0; - docChip[FPSTR(S_CORES)] = 1; - docChip[FPSTR(S_FREQ)] = ESP.getCpuFreqMHz(); - #elif ARDUINO_ARCH_ESP32 docChip[FPSTR(S_MODEL)] = ESP.getChipModel(); docChip[FPSTR(S_REV)] = ESP.getChipRevision(); docChip[FPSTR(S_CORES)] = ESP.getChipCores(); docChip[FPSTR(S_FREQ)] = ESP.getCpuFreqMHz(); - #else - docChip[FPSTR(S_MODEL)] = 0; - docChip[FPSTR(S_REV)] = 0; - docChip[FPSTR(S_CORES)] = 0; - docChip[FPSTR(S_FREQ)] = 0; - #endif auto docFlash = doc[FPSTR(S_FLASH)].to(); - #ifdef ARDUINO_ARCH_ESP8266 - docFlash[FPSTR(S_SIZE)] = ESP.getFlashChipSize(); - docFlash[FPSTR(S_REAL_SIZE)] = ESP.getFlashChipRealSize(); - #elif ARDUINO_ARCH_ESP32 docFlash[FPSTR(S_SIZE)] = ESP.getFlashChipSize(); docFlash[FPSTR(S_REAL_SIZE)] = docFlash[FPSTR(S_SIZE)]; - #else - docFlash[FPSTR(S_SIZE)] = 0; - docFlash[FPSTR(S_REAL_SIZE)] = 0; - #endif - doc.shrinkToFit(); - - this->bufferedWebServer->send(200, F("application/json"), doc); + // send response + response->setLength(); + request->send(response); }); - this->webServer->on(F("/api/debug"), HTTP_GET, [this]() { - JsonDocument doc; + this->webServer->on("/api/debug", HTTP_GET, [](AsyncWebServerRequest *request) { + // make response + auto *response = new AsyncJsonResponse(); + auto& doc = response->getRoot(); auto docBuild = doc[FPSTR(S_BUILD)].to(); docBuild[FPSTR(S_VERSION)] = BUILD_VERSION; docBuild[FPSTR(S_DATE)] = __DATE__ " " __TIME__; docBuild[FPSTR(S_ENV)] = BUILD_ENV; - #ifdef ARDUINO_ARCH_ESP8266 docBuild[FPSTR(S_CORE)] = ESP.getCoreVersion(); docBuild[FPSTR(S_SDK)] = ESP.getSdkVersion(); - #elif ARDUINO_ARCH_ESP32 - docBuild[FPSTR(S_CORE)] = ESP.getCoreVersion(); - docBuild[FPSTR(S_SDK)] = ESP.getSdkVersion(); - #else - docBuild[FPSTR(S_CORE)] = 0; - docBuild[FPSTR(S_SDK)] = 0; - #endif auto docHeap = doc[FPSTR(S_HEAP)].to(); docHeap[FPSTR(S_TOTAL)] = getTotalHeap(); @@ -779,44 +588,17 @@ protected: docHeap[FPSTR(S_MIN_MAX_FREE_BLOCK)] = getMaxFreeBlockHeap(true); auto docChip = doc[FPSTR(S_CHIP)].to(); - #ifdef ARDUINO_ARCH_ESP8266 - docChip[FPSTR(S_MODEL)] = esp_is_8285() ? F("ESP8285") : F("ESP8266"); - docChip[FPSTR(S_REV)] = 0; - docChip[FPSTR(S_CORES)] = 1; - docChip[FPSTR(S_FREQ)] = ESP.getCpuFreqMHz(); - #elif ARDUINO_ARCH_ESP32 docChip[FPSTR(S_MODEL)] = ESP.getChipModel(); docChip[FPSTR(S_REV)] = ESP.getChipRevision(); docChip[FPSTR(S_CORES)] = ESP.getChipCores(); docChip[FPSTR(S_FREQ)] = ESP.getCpuFreqMHz(); - #else - docChip[FPSTR(S_MODEL)] = 0; - docChip[FPSTR(S_REV)] = 0; - docChip[FPSTR(S_CORES)] = 0; - docChip[FPSTR(S_FREQ)] = 0; - #endif auto docFlash = doc[FPSTR(S_FLASH)].to(); - #ifdef ARDUINO_ARCH_ESP8266 - docFlash[FPSTR(S_SIZE)] = ESP.getFlashChipSize(); - docFlash[FPSTR(S_REAL_SIZE)] = ESP.getFlashChipRealSize(); - #elif ARDUINO_ARCH_ESP32 docFlash[FPSTR(S_SIZE)] = ESP.getFlashChipSize(); docFlash[FPSTR(S_REAL_SIZE)] = docFlash[FPSTR(S_SIZE)]; - #else - docFlash[FPSTR(S_SIZE)] = 0; - docFlash[FPSTR(S_REAL_SIZE)] = 0; - #endif - #if defined(ARDUINO_ARCH_ESP32) auto reason = esp_reset_reason(); if (reason != ESP_RST_UNKNOWN && reason != ESP_RST_POWERON && reason != ESP_RST_SW) { - #elif defined(ARDUINO_ARCH_ESP8266) - auto reason = ESP.getResetInfoPtr()->reason; - if (reason != REASON_DEFAULT_RST && reason != REASON_SOFT_RESTART && reason != REASON_EXT_SYS_RST) { - #else - if (false) { - #endif auto docCrash = doc[FPSTR(S_CRASH)].to(); docCrash[FPSTR(S_REASON)] = getResetReason(); docCrash[FPSTR(S_CORE)] = CrashRecorder::ext.core; @@ -836,27 +618,26 @@ protected: docCrash[FPSTR(S_EPC)] = epcStr; } } - - doc.shrinkToFit(); - - this->webServer->sendHeader(F("Content-Disposition"), F("attachment; filename=\"debug.json\"")); - this->bufferedWebServer->send(200, F("application/json"), doc, true); - }); + // send response + response->addHeader("Content-Disposition", "attachment; filename=\"debug.json\""); + response->setLength(); + request->send(response); + }).addMiddleware(&authMiddleware); // not found - this->webServer->onNotFound([this]() { - Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Page not found, uri: %s"), this->webServer->uri().c_str()); + this->webServer->onNotFound([this](AsyncWebServerRequest *request) { + const auto& url = request->url(); + Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Page not found, uri: %s"), url.c_str()); - const String& uri = this->webServer->uri(); - if (uri.equals(F("/"))) { - this->webServer->send(200, F("text/plain"), F("The file system is not flashed!")); + if (url.equals("/")) { + request->send(200, "text/plain", "The file system is not flashed!"); } else if (network->isApEnabled()) { - this->onCaptivePortal(); + this->onCaptivePortal(request); } else { - this->webServer->send(404, F("text/plain"), F("Page not found")); + request->send(404, "text/plain", "Page not found"); } }); @@ -868,10 +649,6 @@ protected: void loop() { // web server if (!this->stateWebServer() && (network->isApEnabled() || network->isConnected()) && millis() - this->webServerChangeState >= this->changeStateInterval) { - #ifdef ARDUINO_ARCH_ESP32 - this->delay(250); - #endif - this->startWebServer(); Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Started: AP up or STA connected")); @@ -885,10 +662,6 @@ protected: } } - #ifdef ARDUINO_ARCH_ESP8266 - ::optimistic_yield(1000); - #endif - } else if (this->stateWebServer() && !network->isApEnabled() && !network->isStaEnabled()) { this->stopWebServer(); Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Stopped: AP and STA down")); @@ -900,10 +673,6 @@ protected: Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("mDNS disabled")); } - - #ifdef ARDUINO_ARCH_ESP8266 - ::optimistic_yield(1000); - #endif } // Disabling mDNS if disabled in settings @@ -918,79 +687,45 @@ protected: if (!this->stateDnsServer() && !network->isConnected() && network->isApEnabled() && this->stateWebServer()) { this->startDnsServer(); Log.straceln(FPSTR(L_PORTAL_DNSSERVER), F("Started: AP up")); - - #ifdef ARDUINO_ARCH_ESP8266 - ::optimistic_yield(1000); - #endif } else if (this->stateDnsServer() && (network->isConnected() || !network->isApEnabled() || !this->stateWebServer())) { this->stopDnsServer(); Log.straceln(FPSTR(L_PORTAL_DNSSERVER), F("Stopped: AP down/STA connected")); - - #ifdef ARDUINO_ARCH_ESP8266 - ::optimistic_yield(1000); - #endif } if (this->stateDnsServer()) { this->dnsServer->processNextRequest(); - - #ifdef ARDUINO_ARCH_ESP8266 - ::optimistic_yield(1000); - #endif } - if (this->stateWebServer()) { - #ifdef ARDUINO_ARCH_ESP32 - // Fix ERR_CONNECTION_RESET for Chrome based browsers - auto& client = this->webServer->client(); - if (!client.getNoDelay()) { - client.setNoDelay(true); - } - #endif - - this->webServer->handleClient(); - } - - if (!this->stateDnsServer() && !this->stateWebServer()) { + if (!this->stateDnsServer()) { this->delay(250); } } - bool isAuthRequired() { - return !network->isApEnabled() && settings.portal.auth && strlen(settings.portal.password); - } + void onCaptivePortal(AsyncWebServerRequest *request) { + const auto& url = request->url(); - bool isValidCredentials() { - return this->webServer->authenticate(settings.portal.login, settings.portal.password); - } - - void onCaptivePortal() { - const String& uri = this->webServer->uri(); - - if (uri.equals(F("/connecttest.txt"))) { - this->webServer->sendHeader(F("Location"), F("http://logout.net")); - this->webServer->send(302); + if (url.equals("/connecttest.txt")) { + request->redirect("http://logout.net", 302); Log.straceln(FPSTR(L_PORTAL_CAPTIVE), F("Redirect to http://logout.net with 302 code")); - } else if (uri.equals(F("/wpad.dat"))) { - this->webServer->send(404); + } else if (url.equals(F("/wpad.dat"))) { + request->send(404); Log.straceln(FPSTR(L_PORTAL_CAPTIVE), F("Send empty page with 404 code")); - } else if (uri.equals(F("/success.txt"))) { - this->webServer->send(200); + } else if (url.equals(F("/success.txt"))) { + request->send(200); Log.straceln(FPSTR(L_PORTAL_CAPTIVE), F("Send empty page with 200 code")); } else { - String portalUrl = F("http://"); + String portalUrl = "http://"; portalUrl += network->getApIp().toString().c_str(); portalUrl += '/'; - this->webServer->sendHeader(F("Location"), portalUrl); - this->webServer->send(302); + request->redirect(portalUrl, 302); Log.straceln(FPSTR(L_PORTAL_CAPTIVE), F("Redirect to portal page with 302 code")); } @@ -1006,9 +741,6 @@ protected: } this->webServer->begin(); - #ifdef ARDUINO_ARCH_ESP8266 - this->webServer->getServer().setNoDelay(true); - #endif this->webServerEnabled = true; this->webServerChangeState = millis(); } @@ -1018,8 +750,7 @@ protected: return; } - //this->webServer->handleClient(); - this->webServer->stop(); + this->webServer->end(); this->webServerEnabled = false; this->webServerChangeState = millis(); } diff --git a/src/main.cpp b/src/main.cpp index b415649..ff50fa5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,3 @@ -#define ARDUINOJSON_USE_DOUBLE 0 -#define ARDUINOJSON_USE_LONG_LONG 0 - #include #include #include @@ -216,7 +213,7 @@ void setup() { tRegulator = new RegulatorTask(true, 10000); Scheduler.start(tRegulator); - tPortal = new PortalTask(true, 0); + tPortal = new PortalTask(true, 10); Scheduler.start(tPortal); tMain = new MainTask(true, 100); diff --git a/src/strings.h b/src/strings.h index 268d73e..ca85f6a 100644 --- a/src/strings.h +++ b/src/strings.h @@ -164,6 +164,7 @@ const char S_POWER[] PROGMEM = "power"; const char S_PREFIX[] PROGMEM = "prefix"; const char S_PROTOCOL_VERSION[] PROGMEM = "protocolVersion"; const char S_PURPOSE[] PROGMEM = "purpose"; +const char S_PSRAM[] PROGMEM = "psram"; const char S_P_FACTOR[] PROGMEM = "p_factor"; const char S_P_MULTIPLIER[] PROGMEM = "p_multiplier"; const char S_REAL_SIZE[] PROGMEM = "realSize"; diff --git a/src_data/pages/upgrade.html b/src_data/pages/upgrade.html index 5cf0c85..097c5a1 100644 --- a/src_data/pages/upgrade.html +++ b/src_data/pages/upgrade.html @@ -62,19 +62,19 @@
-
@@ -108,7 +108,125 @@ lang.build(); setupRestoreBackupForm('#restore'); - setupUpgradeForm('#upgrade'); + + const upgradeForm = document.querySelector('#upgrade'); + if (upgradeForm) { + upgradeForm.reset(); + const statusToText = (status) => { + switch (status) { + case 0: + return "None"; + case 1: + return "No file"; + case 2: + return "Success"; + case 3: + return "Prohibited"; + case 4: + return "Size mismatch"; + case 5: + return "Error on start"; + case 6: + return "Error on write"; + case 7: + return "Error on finish"; + default: + return "Unknown"; + } + }; + + upgradeForm.addEventListener('submit', async (event) => { + event.preventDefault(); + + hide('.fwResult'); + hide('.fsResult'); + + let button = upgradeForm.querySelector('button[type="submit"]'); + button.textContent = i18n('button.uploading'); + button.setAttribute('disabled', true); + button.setAttribute('aria-busy', true); + + try { + let fd = new FormData(); + + const fw = upgradeForm.querySelector("[name='fw[file]']").files; + if (fw.length > 0) { + fd.append("fw[name]", fw[0].name); + fd.append("fw[size]", fw[0].size); + fd.append("fw[file]", fw[0]); + } + + const fs = upgradeForm.querySelector("[name='fs[file]']").files; + if (fs.length > 0) { + fd.append("fs[name]", fs[0].name); + fd.append("fs[size]", fs[0].size); + fd.append("fs[file]", fs[0]); + } + + let response = await fetch(upgradeForm.action, { + method: "POST", + cache: "no-cache", + credentials: "include", + body: fd + }); + + if (response.status != 202 && response.status != 406) { + throw new Error('Response not valid'); + } + + const result = await response.json(); + let resItem = upgradeForm.querySelector('.fwResult'); + if (resItem && result.firmware.status > 1) { + resItem.textContent = statusToText(result.firmware.status); + resItem.classList.remove('hidden'); + + if (result.firmware.status == 2) { + resItem.classList.remove('failed'); + resItem.classList.add('success'); + } else { + resItem.classList.remove('success'); + resItem.classList.add('failed'); + + if (result.firmware.error != "") { + resItem.textContent += `: ${result.firmware.error}`; + } + } + } + + resItem = upgradeForm.querySelector('.fsResult'); + if (resItem && result.filesystem.status > 1) { + resItem.textContent = statusToText(result.filesystem.status); + resItem.classList.remove('hidden'); + + if (result.filesystem.status == 2) { + resItem.classList.remove('failed'); + resItem.classList.add('success'); + } else { + resItem.classList.remove('success'); + resItem.classList.add('failed'); + + if (result.filesystem.error != "") { + resItem.textContent += `: ${result.filesystem.error}`; + } + } + } + + } catch (err) { + console.log(err); + button.textContent = i18n('button.error'); + button.classList.add('failed'); + + } finally { + setTimeout(() => { + button.removeAttribute('aria-busy'); + button.removeAttribute('disabled'); + button.classList.remove('success', 'failed'); + button.textContent = i18n(button.dataset.i18n); + upgradeForm.reset(); + }, 10000); + } + }); + } });