From 9cd5f62f9bb140745b3fbecc6571d7c1c1211351 Mon Sep 17 00:00:00 2001 From: Alex Hope-O'Connor Date: Sat, 15 Nov 2025 22:22:36 +1000 Subject: [PATCH] Refactor WiFiManager: extract server and handlers into separate modules - Move server lifecycle management to WiFiManagerServer - Move HTTP handlers and rendering to WiFiManagerHandlers - Move DEBUG_WM template definitions to WiFiManagerDebug - Implement lazy initialization of server manager to save memory - Add proper cleanup in destructor and on multiple createServer calls - Update tests to use getServer() and getDNSServer() accessors --- lib/WiFiManager/include/WiFiManager.h | 111 +- lib/WiFiManager/include/WiFiManagerDebug.h | 74 + lib/WiFiManager/include/WiFiManagerHandlers.h | 69 + lib/WiFiManager/include/WiFiManagerServer.h | 66 + lib/WiFiManager/src/WiFiManager.cpp | 1689 +---------------- lib/WiFiManager/src/WiFiManagerHandlers.cpp | 1310 +++++++++++++ lib/WiFiManager/src/WiFiManagerServer.cpp | 173 ++ .../tests/test_dns_lifecycle.cpp | 16 +- .../tests/test_integration.cpp | 5 +- .../tests/test_parameter_values.cpp | 1 + .../tests/test_parameters.cpp | 1 + .../tests/test_portal_lifecycle.cpp | 6 +- .../tests/test_state_transitions.cpp | 4 +- test/test_wifimanager/tests/test_stress.cpp | 5 +- 14 files changed, 1777 insertions(+), 1753 deletions(-) create mode 100644 lib/WiFiManager/include/WiFiManagerDebug.h create mode 100644 lib/WiFiManager/include/WiFiManagerHandlers.h create mode 100644 lib/WiFiManager/include/WiFiManagerServer.h create mode 100644 lib/WiFiManager/src/WiFiManagerHandlers.cpp create mode 100644 lib/WiFiManager/src/WiFiManagerServer.cpp diff --git a/lib/WiFiManager/include/WiFiManager.h b/lib/WiFiManager/include/WiFiManager.h index 381fe80..c86c15b 100644 --- a/lib/WiFiManager/include/WiFiManager.h +++ b/lib/WiFiManager/include/WiFiManager.h @@ -6,7 +6,7 @@ * * @author Creator tzapu * @author tablatronix - * @version 0.0.0 + * @author Alex Hope-O'Connor * @license MIT */ @@ -22,6 +22,8 @@ #include +#include "WiFiManagerParameter.h" + // #define WM_MDNS // includes MDNS, also set MDNS with sethostname // #define WM_FIXERASECONFIG // use erase flash fix // #define WM_ERASE_NVS // esp32 erase(true) will erase NVS @@ -180,55 +182,8 @@ // #pragma message "VER_IDF_STR = " WM_STRING(VER_IDF_STR) // #pragma message "VER_ARDUINO_STR = " WM_STRING(VER_ARDUINO_STR) -#ifndef WIFI_MANAGER_MAX_PARAMS - #define WIFI_MANAGER_MAX_PARAMS 5 // params will autoincrement and realloc by this amount when max is reached -#endif - -#define WFM_LABEL_BEFORE 1 -#define WFM_LABEL_AFTER 2 -#define WFM_NO_LABEL 0 -#define WFM_LABEL_DEFAULT 1 - // WiFiManagerRequestArgs is defined as a nested class inside WiFiManager (after WM_WebServer is defined) -class WiFiManagerParameter { - public: - /** - Create custom parameters that can be added to the WiFiManager setup web page - @id is used for HTTP queries and must not contain spaces nor other special characters - */ - WiFiManagerParameter(); - WiFiManagerParameter(const char *custom); - WiFiManagerParameter(const char *id, const char *label); - WiFiManagerParameter(const char *id, const char *label, const char *defaultValue, int length); - WiFiManagerParameter(const char *id, const char *label, const char *defaultValue, int length, const char *custom); - WiFiManagerParameter(const char *id, const char *label, const char *defaultValue, int length, const char *custom, int labelPlacement); - ~WiFiManagerParameter(); - // WiFiManagerParameter& operator=(const WiFiManagerParameter& rhs); - - const char *getID() const; - const char *getValue() const; - const char *getLabel() const; - const char *getPlaceholder() const; // @deprecated, use getLabel - int getValueLength() const; - int getLabelPlacement() const; - virtual const char *getCustomHTML() const; - void setValue(const char *defaultValue, int length); - - protected: - void init(const char *id, const char *label, const char *defaultValue, int length, const char *custom, int labelPlacement); - - WiFiManagerParameter& operator=(const WiFiManagerParameter&); - const char *_id; - const char *_label; - char *_value; - int _length; - int _labelPlacement; - - const char *_customHTML; - friend class WiFiManager; -}; - // debugging typedef enum { @@ -240,6 +195,10 @@ class WiFiManagerParameter { WM_DEBUG_MAX = 5 // MAX extra dev auditing, var dumps etc (MAX+1 will print timing,mem and frag info) } wm_debuglevel_t; +// Forward declarations +class WiFiManagerServer; +class WiFiManagerHandlers; + class WiFiManager { public: @@ -496,10 +455,12 @@ class WiFiManager // get hostname helper String getWiFiHostname(); - - - std::unique_ptr dnsServer; - std::unique_ptr server; + + // get server instance (for testing) + AsyncWebServer* getServer(); + + // get DNS server instance (for testing) + DNSServer* getDNSServer(); public: // Define WiFiManagerRequestArgs here after AsyncWebServer is available @@ -566,8 +527,12 @@ class WiFiManager } }; + friend class WiFiManagerServer; + friend class WiFiManagerHandlers; + protected: // vars + std::unique_ptr _serverManager; std::vector _menuIds; std::vector _menuIdsParams = {"wifi","param","info","exit"}; std::vector _menuIdsUpdate = {"wifi","param","info","update","exit"}; @@ -722,7 +687,6 @@ protected: bool startAP(); void setupDNSD(); - void setupHTTPServer(); uint8_t connectWifi(String ssid, String pass, bool connect = true); bool setSTAConfig(); @@ -733,33 +697,9 @@ protected: uint8_t waitForConnectResult(uint32_t timeout); void updateConxResult(uint8_t status); - // webserver handlers -public: - void handleNotFound(AsyncWebServerRequest *request); -protected: - void handleRoot(AsyncWebServerRequest *request); - void handleWifi(AsyncWebServerRequest *request, boolean scan); - void handleWifiSave(AsyncWebServerRequest *request); - void handleInfo(AsyncWebServerRequest *request); - void handleReset(AsyncWebServerRequest *request); - void handleExit(AsyncWebServerRequest *request); - void handleClose(AsyncWebServerRequest *request); - void handleErase(AsyncWebServerRequest *request, boolean opt); - void handleParam(AsyncWebServerRequest *request); - void handleWiFiStatus(AsyncWebServerRequest *request); - void handleWiFiScanStatus(AsyncWebServerRequest *request); - void handleRequest(AsyncWebServerRequest *request); - void handleParamSave(AsyncWebServerRequest *request); - void doParamSave(WiFiManagerRequestArgs requestArgs); - - boolean captivePortal(AsyncWebServerRequest *request); + // Config portal lifecycle (handlers moved to WiFiManagerHandlers) boolean configPortalHasTimeout(); uint8_t processConfigPortal(); - void stopCaptivePortal(); - // OTA Update handler - void handleUpdate(AsyncWebServerRequest *request); - void handleUpdating(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); - void handleUpdateDone(AsyncWebServerRequest *request); // wifi platform abstractions @@ -815,21 +755,11 @@ protected: #endif #endif - // output helpers - String getParamOut(); - String getIpForm(String id, String title, String value); - String getScanItemOut(); - String getStaticOut(); - String getHTTPHead(String title, String classes = ""); - String getHTTPEnd(); - String getMenuOut(); - //helpers + //helpers (rendering methods moved to WiFiManagerHandlers) boolean isIp(String str); String toStringIp(IPAddress ip); boolean validApPassword(); String encryptionTypeStr(uint8_t authmode); - void reportStatus(String &page); - String getInfoData(String id); // flags boolean connect = false; @@ -915,6 +845,9 @@ protected: }; +// Include template implementations for DEBUG_WM +#include "WiFiManagerDebug.h" + #endif #endif diff --git a/lib/WiFiManager/include/WiFiManagerDebug.h b/lib/WiFiManager/include/WiFiManagerDebug.h new file mode 100644 index 0000000..3f0a9ac --- /dev/null +++ b/lib/WiFiManager/include/WiFiManagerDebug.h @@ -0,0 +1,74 @@ +/** + * WiFiManagerDebug.h + * + * Debug output macros and templates for WiFiManager + * + * @author tablatronix + * @author Alex Hope-O'Connor + * @license MIT + */ + +#ifndef WiFiManagerDebug_h +#define WiFiManagerDebug_h + +#if defined(ESP8266) || defined(ESP32) + +// Template implementations for DEBUG_WM - must be in header for template instantiation +// This file is included at the end of WiFiManager.h after the class definition +template +void WiFiManager::DEBUG_WM(Generic text) { + DEBUG_WM(WM_DEBUG_NOTIFY,text,""); +} + +template +void WiFiManager::DEBUG_WM(wm_debuglevel_t level,Generic text) { + if(_debugLevel >= level) DEBUG_WM(level,text,""); +} + +template +void WiFiManager::DEBUG_WM(Generic text,Genericb textb) { + DEBUG_WM(WM_DEBUG_NOTIFY,text,textb); +} + +template +void WiFiManager::DEBUG_WM(wm_debuglevel_t level,Generic text,Genericb textb) { + if(!_debug || _debugLevel < level) return; + + if(_debugLevel >= WM_DEBUG_MAX){ + #ifdef ESP8266 + // uint32_t free; + // uint16_t max; + // uint8_t frag; + // ESP.getHeapStats(&free, &max, &frag);// @todo Does not exist in 2.3.0 + // _debugPort.printf("[MEM] free: %5d | max: %5d | frag: %3d%% \n", free, max, frag); + #elif defined ESP32 + // total_free_bytes; ///< Total free bytes in the heap. Equivalent to multi_free_heap_size(). + // total_allocated_bytes; ///< Total bytes allocated to data in the heap. + // largest_free_block; ///< Size of largest free block in the heap. This is the largest malloc-able size. + // minimum_free_bytes; ///< Lifetime minimum free heap size. Equivalent to multi_minimum_free_heap_size(). + // allocated_blocks; ///< Number of (variable size) blocks allocated in the heap. + // free_blocks; ///< Number of (variable size) free blocks in the heap. + // total_blocks; ///< Total number of (variable size) blocks in the heap. + multi_heap_info_t info; + heap_caps_get_info(&info, MALLOC_CAP_INTERNAL); + uint32_t free = info.total_free_bytes; + uint16_t max = info.largest_free_block; + uint8_t frag = 100 - (max * 100) / free; + _debugPort.printf("[MEM] free: %5u | max: %5u | frag: %3u%% \n", free, max, frag); + #endif + } + + _debugPort.print(_debugPrefix); + if(_debugLevel >= debugLvlShow) _debugPort.print("["+(String)level+"] "); + _debugPort.print(text); + if(textb){ + _debugPort.print(" "); + _debugPort.print(textb); + } + _debugPort.println(); +} + +#endif // defined(ESP8266) || defined(ESP32) + +#endif // WiFiManagerDebug_h + diff --git a/lib/WiFiManager/include/WiFiManagerHandlers.h b/lib/WiFiManager/include/WiFiManagerHandlers.h new file mode 100644 index 0000000..a797364 --- /dev/null +++ b/lib/WiFiManager/include/WiFiManagerHandlers.h @@ -0,0 +1,69 @@ +/** + * WiFiManagerHandlers.h + * + * HTTP request handlers and rendering logic for WiFiManager + * Handles all web portal page generation and request processing + * + * @author tablatronix + * @author Alex Hope-O'Connor + * @license MIT + */ + +#ifndef WiFiManagerHandlers_h +#define WiFiManagerHandlers_h + +#if defined(ESP8266) || defined(ESP32) + +#include +#include +#include "WiFiManager.h" // Need full definition for WiFiManagerRequestArgs + +class WiFiManagerHandlers { + public: + WiFiManagerHandlers(WiFiManager* wm); + + // HTTP Request Handlers + void handleRoot(AsyncWebServerRequest *request); + void handleWifi(AsyncWebServerRequest *request, boolean scan); + void handleWifiSave(AsyncWebServerRequest *request); + void handleParam(AsyncWebServerRequest *request); + void handleParamSave(AsyncWebServerRequest *request); + void handleInfo(AsyncWebServerRequest *request); + void handleReset(AsyncWebServerRequest *request); + void handleExit(AsyncWebServerRequest *request); + void handleClose(AsyncWebServerRequest *request); + void handleErase(AsyncWebServerRequest *request, boolean opt); + void handleNotFound(AsyncWebServerRequest *request); + void handleRequest(AsyncWebServerRequest *request); + void handleWiFiStatus(AsyncWebServerRequest *request); + void handleWiFiScanStatus(AsyncWebServerRequest *request); + void handleUpdate(AsyncWebServerRequest *request); + void handleUpdating(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); + void handleUpdateDone(AsyncWebServerRequest *request); + + // Captive Portal + boolean captivePortal(AsyncWebServerRequest *request); + void stopCaptivePortal(); + + // Rendering Methods + String getHTTPHead(String title, String classes = ""); + String getHTTPEnd(); + String getMenuOut(); + String getScanItemOut(); + String getParamOut(); + String getStaticOut(); + String getIpForm(String id, String title, String value); + String getInfoData(String id); + void reportStatus(String &page); + + // Internal helper (needed by handlers) + void doParamSave(WiFiManager::WiFiManagerRequestArgs requestArgs); + + private: + WiFiManager* _wm; +}; + +#endif // defined(ESP8266) || defined(ESP32) + +#endif // WiFiManagerHandlers_h + diff --git a/lib/WiFiManager/include/WiFiManagerServer.h b/lib/WiFiManager/include/WiFiManagerServer.h new file mode 100644 index 0000000..22d30e0 --- /dev/null +++ b/lib/WiFiManager/include/WiFiManagerServer.h @@ -0,0 +1,66 @@ +/** + * WiFiManagerServer.h + * + * Server lifecycle management for WiFiManager + * Handles AsyncWebServer creation, route registration, and shutdown + * + * @author tablatronix + * @author Alex Hope-O'Connor + * @license MIT + */ + +#ifndef WiFiManagerServer_h +#define WiFiManagerServer_h + +#if defined(ESP8266) || defined(ESP32) + +#include +#include +#include +#include + +// Forward declarations +class WiFiManager; + +// Forward declaration - full definition needed in .cpp file +class WiFiManagerHandlers; + +class WiFiManagerServer { + public: + WiFiManagerServer(WiFiManager* wm); + + // Create server instance + void createServer(uint16_t port); + + // Register all routes (takes handler callbacks) + void registerRoutes(); + + // Setup DNS server for captive portal + void setupDNSD(); + + // Process DNS requests (call periodically) + void processDNS(); + + // Shutdown and cleanup server and DNS + void shutdownServer(); + + // Get server instance + AsyncWebServer* getServer() { return server.get(); } + + // Get DNS server instance (for testing) + DNSServer* getDNSServer() { return dnsServer.get(); } + + private: + WiFiManager* _wm; + std::unique_ptr _handlers; + std::unique_ptr server; + std::unique_ptr dnsServer; +}; + +// Include WiFiManagerHandlers.h after class definition to provide full definition +// needed for std::unique_ptr destructor +#include "WiFiManagerHandlers.h" + +#endif // defined(ESP8266) || defined(ESP32) + +#endif // WiFiManagerServer_h diff --git a/lib/WiFiManager/src/WiFiManager.cpp b/lib/WiFiManager/src/WiFiManager.cpp index 607a3f3..cb7fd7a 100644 --- a/lib/WiFiManager/src/WiFiManager.cpp +++ b/lib/WiFiManager/src/WiFiManager.cpp @@ -6,11 +6,12 @@ * * @author Creator tzapu * @author tablatronix - * @version 0.0.0 + * @author Alex Hope-O'Connor * @license MIT */ #include "WiFiManager.h" +#include "WiFiManagerServer.h" #if defined(ESP8266) || defined(ESP32) @@ -112,10 +113,16 @@ void WiFiManager::WiFiManagerInit(){ setMenu(_menuIdsDefault); if(_debug && _debugLevel >= WM_DEBUG_DEV) debugPlatformInfo(); _max_params = WIFI_MANAGER_MAX_PARAMS; + // _serverManager is created lazily when config portal is started to save memory } // destructor WiFiManager::~WiFiManager() { + // Cleanup server if still active (prevents leaks if WiFiManager is destroyed while portal is running) + if (_serverManager) { + _serverManager->shutdownServer(); + } + _end(); // parameters // @todo below belongs to wifimanagerparameter @@ -508,121 +515,17 @@ boolean WiFiManager::configPortalHasTimeout(){ return false; } -void WiFiManager::setupHTTPServer(){ +// setupHTTPServer() moved to WiFiManagerServer module - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(F("Starting Web Portal")); - #endif - - if(_httpPort != 80) { - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_VERBOSE, F("http server started with custom port: "), _httpPort); // @todo not showing ip - #endif - } - - server.reset(new AsyncWebServer(_httpPort)); - - if ( _webservercallback != NULL) { - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_VERBOSE, F("[CB] _webservercallback calling")); - #endif - _webservercallback(); // @CALLBACK - } - - /* Setup httpd callbacks, web pages: root, wifi config pages, SO captive portal detectors and not found. */ - - // Register routes with lambdas - server->on(WM_G(R_root), HTTP_GET, [this](AsyncWebServerRequest *request) { - this->handleRoot(request); - }); - - server->on(WM_G(R_wifi), HTTP_GET, [this](AsyncWebServerRequest *request) { - this->handleWifi(request, true); - }); - - server->on(WM_G(R_wifinoscan), HTTP_GET, [this](AsyncWebServerRequest *request) { - this->handleWifi(request, false); - }); - - server->on(WM_G(R_wifisave), HTTP_POST, [this](AsyncWebServerRequest *request) { - this->handleWifiSave(request); - }); - - server->on(WM_G(R_info), HTTP_GET, [this](AsyncWebServerRequest *request) { - this->handleInfo(request); - }); - - server->on(WM_G(R_param), HTTP_GET, [this](AsyncWebServerRequest *request) { - this->handleParam(request); - }); - - server->on(WM_G(R_paramsave), HTTP_POST, [this](AsyncWebServerRequest *request) { - this->handleParamSave(request); - }); - - server->on(WM_G(R_restart), HTTP_GET, [this](AsyncWebServerRequest *request) { - this->handleReset(request); - }); - - server->on(WM_G(R_exit), HTTP_GET, [this](AsyncWebServerRequest *request) { - this->handleExit(request); - }); - - server->on(WM_G(R_close), HTTP_GET, [this](AsyncWebServerRequest *request) { - this->handleClose(request); - }); - - server->on(WM_G(R_erase), HTTP_GET, [this](AsyncWebServerRequest *request) { - this->handleErase(request, false); - }); - - server->on(WM_G(R_status), HTTP_GET, [this](AsyncWebServerRequest *request) { - this->handleWiFiStatus(request); - }); - - server->on(WM_G(R_scanstatus), HTTP_GET, [this](AsyncWebServerRequest *request) { - this->handleWiFiScanStatus(request); - }); - - // OTA Update routes - server->on(WM_G(R_update), HTTP_GET, [this](AsyncWebServerRequest *request) { - this->handleUpdate(request); - }); - - server->on(WM_G(R_updatedone), HTTP_POST, - [this](AsyncWebServerRequest *request) { - this->handleUpdateDone(request); - }, - [this](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { - this->handleUpdating(request, filename, index, data, len, final); - } - ); - - // 404 handler - server->onNotFound([this](AsyncWebServerRequest *request) { - this->handleNotFound(request); - }); - - server->begin(); // Web server start - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_VERBOSE, F("HTTP server started")); - #endif -} - -void WiFiManager::setupDNSD(){ - dnsServer.reset(new DNSServer()); - - /* Setup the DNS server redirecting all the domains to the apIP */ - dnsServer->setErrorReplyCode(DNSReplyCode::NoError); - #ifdef WM_DEBUG_LEVEL - // DEBUG_WM("dns server started port: ",DNS_PORT); - DEBUG_WM(WM_DEBUG_DEV,F("dns server started with ip: "),WiFi.softAPIP()); // @todo not showing ip - #endif - dnsServer->start(DNS_PORT, F("*"), WiFi.softAPIP()); -} +// setupDNSD() moved to WiFiManagerServer module void WiFiManager::setupConfigPortal() { - setupHTTPServer(); + // Lazy initialization: only create server manager when config portal is actually needed + if (!_serverManager) { + _serverManager = std::make_unique(this); + } + _serverManager->createServer(_httpPort); + _serverManager->registerRoutes(); _lastscan = 0; // reset network scan cache if(_preloadwifiscan) WiFi_scanNetworks(true); // preload wifiscan (async) } @@ -707,7 +610,9 @@ void WiFiManager::startConfigPortal(char const *apName, char const *apPassword) #ifdef WM_DEBUG_LEVEL DEBUG_WM(WM_DEBUG_DEV,F("setupDNSD")); #endif - setupDNSD(); + if (_serverManager) { + _serverManager->setupDNSD(); + } #ifdef WM_DEBUG_LEVEL @@ -796,9 +701,9 @@ boolean WiFiManager::process(){ * @return {[type]} [description] */ uint8_t WiFiManager::processConfigPortal(){ - if(configPortalActive){ + if(configPortalActive && _serverManager){ //DNS handler - dnsServer->processNextRequest(); + _serverManager->processDNS(); } //HTTP handler - AsyncWebServer handles clients automatically, no need to call handleClient() @@ -883,25 +788,24 @@ bool WiFiManager::shutdownConfigPortal(){ if(webPortalActive) return false; - if(configPortalActive && dnsServer){ + if(configPortalActive && _serverManager){ //DNS handler - dnsServer->processNextRequest(); + _serverManager->processDNS(); } //HTTP handler - AsyncWebServer handles clients automatically, no need to call handleClient() - // @todo what is the proper way to shutdown and free the server up - // debug - many open issues aobut port not clearing for use with other servers - // AsyncWebServer doesn't have stop(), just reset the unique_ptr - server.reset(); + // Shutdown server via server module + if (_serverManager) { + _serverManager->shutdownServer(); + // Free the server manager to reclaim memory (will be recreated lazily if needed) + _serverManager.reset(); + } WiFi.scanDelete(); // free wifi scan results if(!configPortalActive) return false; - dnsServer->stop(); // free heap ? - dnsServer.reset(); - // turn off AP // @todo bug workaround // https://github.com/esp8266/Arduino/issues/3793 @@ -1193,229 +1097,7 @@ void WiFiManager::startWPS() { } #endif -String WiFiManager::getHTTPHead(String title, String classes){ - String page; - page += FPSTR(HTTP_HEAD_START); - page.replace(FPSTR(T_v), title); - page += FPSTR(HTTP_SCRIPT); - page += FPSTR(HTTP_STYLE); - page += _customHeadElement; - - String p = FPSTR(HTTP_HEAD_END); - if (_bodyClass != "") { - if (classes != "") { - classes += " "; // add spacing, if necessary - } - classes += _bodyClass; // add class str - } - p.replace(FPSTR(T_c), classes); - page += p; - - if (_customBodyHeader) { - page += _customBodyHeader; - } - - return page; -} - -String WiFiManager::getHTTPEnd() { - String end = FPSTR(HTTP_END); - - if (_customBodyFooter) { - end = String(_customBodyFooter) + end; - } - - return end; -} - -/** - * HTTPD handler for page requests - */ -void WiFiManager::handleRequest(AsyncWebServerRequest *request) { - _webPortalAccessed = millis(); - - // TESTING HTTPD AUTH RFC 2617 - // Note: ESPAsyncWebServer has different auth mechanisms - // May need to implement custom middleware for auth - // For now, auth is commented out as in original - - // Auth handling would go here if needed -} - -/** - * HTTPD CALLBACK root or redirect to captive portal - */ -void WiFiManager::handleRoot(AsyncWebServerRequest *request) { - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_VERBOSE, F("<- HTTP Root")); - #endif - if (captivePortal(request)) return; // If captive portal redirect instead of displaying the page - handleRequest(request); - String page = getHTTPHead(_title, FPSTR(C_root)); // @token options @todo replace options with title - String str = FPSTR(HTTP_ROOT_MAIN); // @todo custom title - str.replace(FPSTR(T_t), _title); - str.replace(FPSTR(T_v), configPortalActive ? _apName : (getWiFiHostname() + " - " + WiFi.localIP().toString())); // use ip if ap is not active for heading @todo use hostname? - page += str; - page += FPSTR(HTTP_PORTAL_OPTIONS); - page += getMenuOut(); - reportStatus(page); - page += getHTTPEnd(); - - request->send(200, FPSTR(HTTP_HEAD_CT), page); - if(_preloadwifiscan) WiFi_scanNetworks(_scancachetime); // preload wifiscan throttled (async) - // @todo buggy, captive portals make a query on every page load, causing this to run every time in addition to the real page load - // I dont understand why, when you are already in the captive portal, I guess they want to know that its still up and not done or gone - // if we can detect these and ignore them that would be great, since they come from the captive portal redirect maybe there is a refferer -} - -/** - * HTTPD CALLBACK Wifi config page handler - */ -void WiFiManager::handleWifi(AsyncWebServerRequest *request, boolean scan) { - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_VERBOSE, F("<- HTTP Wifi")); - DEBUG_WM(WM_DEBUG_DEV, F("handleWifi called, scan="), scan ? "true" : "false"); - #endif - if (captivePortal(request)) { - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_DEV, F("Captive portal redirect")); - #endif - return; // If captive portal redirect instead of displaying the page - } - handleRequest(request); - String page = getHTTPHead(FPSTR(S_titlewifi), FPSTR(C_wifi)); // @token titlewifi - if (scan) { - // Check if refresh is requested - bool forceRefresh = false; - if (request->hasParam("refresh")) { - forceRefresh = true; - } - - // Always use cached data for immediate response (never block) - // Wrap scan output in container for AJAX updates - page += F("
"); - page += getScanItemOut(); - page += F("
"); - - // Start async scan in background if needed (non-blocking) - if(forceRefresh || !_lastscan || (millis()-_lastscan > _scancachetime)){ - if(!_scanInProgress){ - WiFi_scanNetworks(true); // Start async scan - } else { - // Scan already in progress, will update via polling - _scanRequested = true; // Queue another scan if user wants refresh - } - } - } - String pitem = ""; - - pitem = FPSTR(HTTP_FORM_START); - pitem.replace(FPSTR(T_v), F("wifisave")); // set form action - page += pitem; - - pitem = FPSTR(HTTP_FORM_WIFI); - pitem.replace(FPSTR(T_v), WiFi_SSID()); - - if(_showPassword){ - pitem.replace(FPSTR(T_p), WiFi_psk()); - } - else if(WiFi_psk() != ""){ - pitem.replace(FPSTR(T_p), FPSTR(S_passph)); - } - else { - pitem.replace(FPSTR(T_p), ""); - } - - page += pitem; - - page += getStaticOut(); - page += FPSTR(HTTP_FORM_WIFI_END); - if(_paramsInWifi && _paramsCount > 0){ - page += FPSTR(HTTP_FORM_PARAM_HEAD); - page += getParamOut(); - } - page += FPSTR(HTTP_FORM_END); - // Replace refresh button with AJAX version - page += F("
"); - if(_showBack) page += FPSTR(HTTP_BACKBTN); - reportStatus(page); - - // TODO: Known issue - Adding WIFI_POLLING_JS causes silent failure (empty response) - // This is likely due to: - // 1. String size limit: Page is already ~9-10KB, adding ~2.5KB JS may exceed ESPAsyncWebServer limits - // 2. Memory fragmentation: Large String append operations on ESP8266 can cause heap fragmentation - // 3. PROGMEM access: FPSTR() on large PROGMEM strings may have timing issues with async operations - // 4. Response size: ESPAsyncWebServer may silently fail or truncate responses exceeding ~12KB - // Workaround: Temporarily disabled until we can optimize the page size or use chunked responses - // page += FPSTR(WIFI_POLLING_JS); - - page += getHTTPEnd(); - - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_DEV, F("Page length: "), String(page.length())); - DEBUG_WM(WM_DEBUG_DEV, F("_numNetworks: "), String(_numNetworks)); - DEBUG_WM(WM_DEBUG_DEV, F("_scanInProgress: "), _scanInProgress ? "true" : "false"); - DEBUG_WM(WM_DEBUG_DEV, F("_lastscan: "), String(_lastscan)); - DEBUG_WM(WM_DEBUG_DEV, F("About to send response")); - #endif - - request->send(200, FPSTR(HTTP_HEAD_CT), page); - - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_DEV, F("Response sent")); - #endif -} - -/** - * HTTPD CALLBACK Wifi param page handler - */ -void WiFiManager::handleParam(AsyncWebServerRequest *request){ - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_VERBOSE, F("<- HTTP Param")); - #endif - handleRequest(request); - String page = getHTTPHead(FPSTR(S_titleparam), FPSTR(C_param)); // @token titlewifi - - String pitem = ""; - - pitem = FPSTR(HTTP_FORM_START); - pitem.replace(FPSTR(T_v), F("paramsave")); - page += pitem; - - page += getParamOut(); - page += FPSTR(HTTP_FORM_END); - if(_showBack) page += FPSTR(HTTP_BACKBTN); - reportStatus(page); - page += getHTTPEnd(); - - request->send(200, FPSTR(HTTP_HEAD_CT), page); - - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_DEV, F("Sent param page")); - #endif -} - - -String WiFiManager::getMenuOut(){ - String page; - - for(auto menuId :_menuIds ){ - if((String)_menutokens[menuId] == "param" && _paramsCount == 0) continue; // no params set, omit params from menu, @todo this may be undesired by someone, use only menu to force? - if((String)_menutokens[menuId] == "custom" && _customMenuHTML!=NULL){ - page += _customMenuHTML; - continue; - } - page += HTTP_PORTAL_MENU[menuId]; - delay(0); - } - - return page; -} - -// // is it possible in softap mode to detect aps without scanning -// bool WiFiManager::WiFi_scanNetworksForAP(bool force){ -// WiFi_scanNetworks(force); -// } +// Handler and rendering methods moved to WiFiManagerHandlers.cpp void WiFiManager::WiFi_scanComplete(int networksFound){ _lastscan = millis(); @@ -1435,6 +1117,7 @@ bool WiFiManager::WiFi_scanNetworks(){ bool WiFiManager::WiFi_scanNetworks(unsigned int cachetime){ return WiFi_scanNetworks(millis()-_lastscan > cachetime); } + bool WiFiManager::WiFi_scanNetworks(bool force){ #ifdef WM_DEBUG_LEVEL // DEBUG_WM(WM_DEBUG_DEV,_numNetworks,(millis()-_lastscan )); @@ -1519,1123 +1202,6 @@ bool WiFiManager::WiFi_scanNetworks(bool force){ } } -String WiFiManager::WiFiManager::getScanItemOut(){ - String page; - - // Never trigger scans from here - only use cached data - // If no cached data, show message (scan should be started elsewhere if needed) - int n = _numNetworks; - if (n == 0) { - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(F("No networks found")); - #endif - if(_scanInProgress){ - page += F("Scanning for networks...

"); - } else { - page += FPSTR(S_nonetworks); // @token nonetworks - page += F("

"); - } - } - else { - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(n,F("networks found")); - #endif - //sort networks - int indices[n]; - for (int i = 0; i < n; i++) { - indices[i] = i; - } - - // RSSI SORT - for (int i = 0; i < n; i++) { - for (int j = i + 1; j < n; j++) { - if (WiFi.RSSI(indices[j]) > WiFi.RSSI(indices[i])) { - std::swap(indices[i], indices[j]); - } - } - } - - /* test std:sort - std::sort(indices, indices + n, [](const int & a, const int & b) -> bool - { - return WiFi.RSSI(a) > WiFi.RSSI(b); - }); - */ - - // remove duplicates ( must be RSSI sorted ) - if (_removeDuplicateAPs) { - String cssid; - for (int i = 0; i < n; i++) { - if (indices[i] == -1) continue; - cssid = WiFi.SSID(indices[i]); - for (int j = i + 1; j < n; j++) { - if (cssid == WiFi.SSID(indices[j])) { - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_VERBOSE,F("DUP AP:"),WiFi.SSID(indices[j])); - #endif - indices[j] = -1; // set dup aps to index -1 - } - } - } - } - - // token precheck, to speed up replacements on large ap lists - String HTTP_ITEM_STR = FPSTR(HTTP_ITEM); - - // toggle icons with percentage - HTTP_ITEM_STR.replace("{qp}", FPSTR(HTTP_ITEM_QP)); - HTTP_ITEM_STR.replace("{h}",_scanDispOptions ? "" : "h"); - HTTP_ITEM_STR.replace("{qi}", FPSTR(HTTP_ITEM_QI)); - HTTP_ITEM_STR.replace("{h}",_scanDispOptions ? "h" : ""); - - // set token precheck flags - bool tok_r = HTTP_ITEM_STR.indexOf(FPSTR(T_r)) > 0; - bool tok_R = HTTP_ITEM_STR.indexOf(FPSTR(T_R)) > 0; - bool tok_e = HTTP_ITEM_STR.indexOf(FPSTR(T_e)) > 0; - bool tok_q = HTTP_ITEM_STR.indexOf(FPSTR(T_q)) > 0; - bool tok_i = HTTP_ITEM_STR.indexOf(FPSTR(T_i)) > 0; - - //display networks in page - for (int i = 0; i < n; i++) { - if (indices[i] == -1) continue; // skip dups - - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_VERBOSE,F("AP: "),(String)WiFi.RSSI(indices[i]) + " " + (String)WiFi.SSID(indices[i])); - #endif - - int rssiperc = getRSSIasQuality(WiFi.RSSI(indices[i])); - uint8_t enc_type = WiFi.encryptionType(indices[i]); - - if (_minimumQuality == -1 || _minimumQuality < rssiperc) { - String item = HTTP_ITEM_STR; - if(WiFi.SSID(indices[i]) == ""){ - // Serial.println(WiFi.BSSIDstr(indices[i])); - continue; // No idea why I am seeing these, lets just skip them for now - } - item.replace(FPSTR(T_V), htmlEntities(WiFi.SSID(indices[i]))); // ssid no encoding - item.replace(FPSTR(T_v), htmlEntities(WiFi.SSID(indices[i]),true)); // ssid no encoding - if(tok_e) item.replace(FPSTR(T_e), encryptionTypeStr(enc_type)); - if(tok_r) item.replace(FPSTR(T_r), (String)rssiperc); // rssi percentage 0-100 - if(tok_R) item.replace(FPSTR(T_R), (String)WiFi.RSSI(indices[i])); // rssi db - if(tok_q) item.replace(FPSTR(T_q), (String)int(round(map(rssiperc,0,100,1,4)))); //quality icon 1-4 - if(tok_i){ - if (enc_type != WM_WIFIOPEN) { - item.replace(FPSTR(T_i), F("l")); - } else { - item.replace(FPSTR(T_i), ""); - } - } - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_DEV,item); - #endif - page += item; - delay(0); - } else { - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_VERBOSE,F("Skipping , does not meet _minimumQuality")); - #endif - } - - } - page += FPSTR(HTTP_BR); - } - - return page; -} - -String WiFiManager::getIpForm(String id, String title, String value){ - String item = FPSTR(HTTP_FORM_LABEL); - item += FPSTR(HTTP_FORM_PARAM); - item.replace(FPSTR(T_i), id); - item.replace(FPSTR(T_n), id); - item.replace(FPSTR(T_p), FPSTR(T_t)); - // item.replace(FPSTR(T_p), default); - item.replace(FPSTR(T_t), title); - item.replace(FPSTR(T_l), F("15")); - item.replace(FPSTR(T_v), value); - item.replace(FPSTR(T_c), ""); - return item; -} - -String WiFiManager::getStaticOut(){ - String page; - if ((_staShowStaticFields || _sta_static_ip) && _staShowStaticFields>=0) { - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_DEV,F("_staShowStaticFields")); - #endif - page += FPSTR(HTTP_FORM_STATIC_HEAD); - // @todo how can we get these accurate settings from memory , wifi_get_ip_info does not seem to reveal if struct ip_info is static or not - page += getIpForm(FPSTR(S_ip),FPSTR(S_staticip),(_sta_static_ip ? _sta_static_ip.toString() : "")); // @token staticip - // WiFi.localIP().toString(); - page += getIpForm(FPSTR(S_gw),FPSTR(S_staticgw),(_sta_static_gw ? _sta_static_gw.toString() : "")); // @token staticgw - // WiFi.gatewayIP().toString(); - page += getIpForm(FPSTR(S_sn),FPSTR(S_subnet),(_sta_static_sn ? _sta_static_sn.toString() : "")); // @token subnet - // WiFi.subnetMask().toString(); - } - - if((_staShowDns || _sta_static_dns) && _staShowDns>=0){ - page += getIpForm(FPSTR(S_dns),FPSTR(S_staticdns),(_sta_static_dns ? _sta_static_dns.toString() : "")); // @token dns - } - - if(page!="") page += FPSTR(HTTP_BR); // @todo remove these, use css - - return page; -} - -String WiFiManager::getParamOut(){ - String page; - - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_DEV,F("getParamOut"),_paramsCount); - #endif - - if(_paramsCount > 0){ - - String HTTP_PARAM_temp = FPSTR(HTTP_FORM_LABEL); - HTTP_PARAM_temp += FPSTR(HTTP_FORM_PARAM); - bool tok_I = HTTP_PARAM_temp.indexOf(FPSTR(T_I)) > 0; - bool tok_i = HTTP_PARAM_temp.indexOf(FPSTR(T_i)) > 0; - bool tok_n = HTTP_PARAM_temp.indexOf(FPSTR(T_n)) > 0; - bool tok_p = HTTP_PARAM_temp.indexOf(FPSTR(T_p)) > 0; - bool tok_t = HTTP_PARAM_temp.indexOf(FPSTR(T_t)) > 0; - bool tok_l = HTTP_PARAM_temp.indexOf(FPSTR(T_l)) > 0; - bool tok_v = HTTP_PARAM_temp.indexOf(FPSTR(T_v)) > 0; - bool tok_c = HTTP_PARAM_temp.indexOf(FPSTR(T_c)) > 0; - - char valLength[5]; - - for (int i = 0; i < _paramsCount; i++) { - //Serial.println((String)_params[i]->_length); - if (_params[i] == NULL || _params[i]->_length > 99999) { - // try to detect param scope issues, doesnt always catch but works ok - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] WiFiManagerParameter is out of scope")); - #endif - return ""; - } - } - - // add the extra parameters to the form - for (int i = 0; i < _paramsCount; i++) { - // label before or after, @todo this could be done via floats or CSS and eliminated - String pitem; - switch (_params[i]->getLabelPlacement()) { - case WFM_LABEL_BEFORE: - pitem = FPSTR(HTTP_FORM_LABEL); - pitem += FPSTR(HTTP_FORM_PARAM); - break; - case WFM_LABEL_AFTER: - pitem = FPSTR(HTTP_FORM_PARAM); - pitem += FPSTR(HTTP_FORM_LABEL); - break; - default: - // WFM_NO_LABEL - pitem = FPSTR(HTTP_FORM_PARAM); - break; - } - - // Input templating - // "
"; - // if no ID use customhtml for item, else generate from param string - if (_params[i]->getID() != NULL) { - if(tok_I)pitem.replace(FPSTR(T_I), (String)FPSTR(S_parampre)+(String)i); // T_I id number - if(tok_i)pitem.replace(FPSTR(T_i), _params[i]->getID()); // T_i id name - if(tok_n)pitem.replace(FPSTR(T_n), _params[i]->getID()); // T_n id name alias - if(tok_p)pitem.replace(FPSTR(T_p), FPSTR(T_t)); // T_p replace legacy placeholder token - if(tok_t)pitem.replace(FPSTR(T_t), _params[i]->getLabel()); // T_t title/label - snprintf(valLength, 5, "%d", _params[i]->getValueLength()); - if(tok_l)pitem.replace(FPSTR(T_l), valLength); // T_l value length - if(tok_v)pitem.replace(FPSTR(T_v), _params[i]->getValue()); // T_v value - if(tok_c)pitem.replace(FPSTR(T_c), _params[i]->getCustomHTML()); // T_c meant for additional attributes, not html, but can stuff - } else { - pitem = _params[i]->getCustomHTML(); - } - - page += pitem; - } - } - - return page; -} - -void WiFiManager::handleWiFiStatus(AsyncWebServerRequest *request){ - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_VERBOSE, F("<- HTTP WiFi status ")); - #endif - handleRequest(request); - String page; - // String page = "{\"result\":true,\"count\":1}"; - #ifdef WM_JSTEST - page = FPSTR(HTTP_JS); - #endif - request->send(200, FPSTR(HTTP_HEAD_CT), page); -} - -/** - * HTTPD CALLBACK WiFi scan status endpoint - returns JSON with scan status and network list - */ -void WiFiManager::handleWiFiScanStatus(AsyncWebServerRequest *request){ - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_VERBOSE, F("<- HTTP WiFi scan status")); - #endif - handleRequest(request); - - String json = "{"; - json += "\"scanning\":"; - json += _scanInProgress ? "true" : "false"; - json += ",\"count\":"; - json += String(_numNetworks); - json += ",\"lastscan\":"; - json += String(_lastscan); - - // If scan is complete and we have networks, include network list - if(!_scanInProgress && _numNetworks > 0){ - json += ",\"networks\":["; - - int n = _numNetworks; - // Sort networks by RSSI (same logic as getScanItemOut) - int indices[n]; - for (int i = 0; i < n; i++) { - indices[i] = i; - } - - // RSSI SORT - for (int i = 0; i < n; i++) { - for (int j = i + 1; j < n; j++) { - if (WiFi.RSSI(indices[j]) > WiFi.RSSI(indices[i])) { - std::swap(indices[i], indices[j]); - } - } - } - - // Remove duplicates (must be RSSI sorted) - if (_removeDuplicateAPs) { - String cssid; - for (int i = 0; i < n; i++) { - if (indices[i] == -1) continue; - cssid = WiFi.SSID(indices[i]); - for (int j = i + 1; j < n; j++) { - if (cssid == WiFi.SSID(indices[j])) { - indices[j] = -1; // set dup aps to index -1 - } - } - } - } - - bool first = true; - // Display networks in JSON - for (int i = 0; i < n; i++) { - if (indices[i] == -1) continue; // skip dups - - String ssid = WiFi.SSID(indices[i]); - if(ssid == "") continue; // Skip empty SSIDs - - int rssi = WiFi.RSSI(indices[i]); - int rssiperc = getRSSIasQuality(rssi); - uint8_t enc_type = WiFi.encryptionType(indices[i]); - - // Apply minimum quality filter - if (_minimumQuality != -1 && _minimumQuality >= rssiperc) { - continue; // Skip if doesn't meet minimum quality - } - - if(!first) json += ","; - first = false; - - json += "{"; - json += "\"ssid\":\""; - // Escape JSON special characters in SSID - String escapedSSID = ssid; - escapedSSID.replace("\\", "\\\\"); - escapedSSID.replace("\"", "\\\""); - escapedSSID.replace("\n", "\\n"); - escapedSSID.replace("\r", "\\r"); - escapedSSID.replace("\t", "\\t"); - json += escapedSSID; - json += "\""; - json += ",\"rssi\":"; - json += String(rssi); - json += ",\"quality\":"; - json += String(rssiperc); - json += ",\"encryption\":\""; - json += encryptionTypeStr(enc_type); - json += "\""; - json += ",\"enc_type\":"; - json += String(enc_type); - json += "}"; - } - - json += "]"; - } else { - json += ",\"networks\":[]"; - } - - json += "}"; - - request->send(200, "application/json", json); -} - -/** - * HTTPD CALLBACK save form and redirect to WLAN config page again - */ -void WiFiManager::handleWifiSave(AsyncWebServerRequest *request) { - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_VERBOSE, F("<- HTTP WiFi save ")); - DEBUG_WM(WM_DEBUG_DEV, F("Method:"), request->method() == HTTP_GET ? FPSTR(S_GET) : FPSTR(S_POST)); - #endif - handleRequest(request); - - // BUILD REQUEST ARGS STRUCT EARLY - captures all arguments while request is active - WiFiManagerRequestArgs requestArgs(request); - - //SAVE/connect here - if (request->hasParam("s", true)) { - _ssid = request->getParam("s", true)->value().c_str(); - } - if (request->hasParam("p", true)) { - _pass = request->getParam("p", true)->value().c_str(); - } - - if(_ssid == "" && _pass != ""){ - _ssid = WiFi_SSID(true); // password change, placeholder ssid, @todo compare pass to old?, confirm ssid is clean - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_VERBOSE, F("Detected WiFi password change")); - #endif - } - - #ifdef WM_DEBUG_LEVEL - String requestinfo = "SERVER_REQUEST\n----------------\n"; - requestinfo += "URI: "; - requestinfo += request->url(); - requestinfo += "\nMethod: "; - requestinfo += (request->method() == HTTP_GET) ? "GET" : "POST"; - requestinfo += "\nArguments: "; - requestinfo += request->params(); - requestinfo += "\n"; - for (size_t i = 0; i < request->params(); i++) { - const AsyncWebParameter* p = request->getParam(i); - requestinfo += " " + p->name() + ": " + p->value() + "\n"; - } - - DEBUG_WM(WM_DEBUG_MAX, requestinfo); - #endif - - // set static ips from request args - if (request->hasParam(FPSTR(S_ip), true)) { - String ip = request->getParam(FPSTR(S_ip), true)->value(); - optionalIPFromString(&_sta_static_ip, ip.c_str()); - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_DEV, F("static ip:"), ip); - #endif - } - if (request->hasParam(FPSTR(S_gw), true)) { - String gw = request->getParam(FPSTR(S_gw), true)->value(); - optionalIPFromString(&_sta_static_gw, gw.c_str()); - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_DEV, F("static gateway:"), gw); - #endif - } - if (request->hasParam(FPSTR(S_sn), true)) { - String sn = request->getParam(FPSTR(S_sn), true)->value(); - optionalIPFromString(&_sta_static_sn, sn.c_str()); - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_DEV, F("static netmask:"), sn); - #endif - } - if (request->hasParam(FPSTR(S_dns), true)) { - String dns = request->getParam(FPSTR(S_dns), true)->value(); - optionalIPFromString(&_sta_static_dns, dns.c_str()); - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_DEV, F("static DNS:"), dns); - #endif - } - - if (_presavewificallback != NULL) { - _presavewificallback(); // @CALLBACK - } - - if(_paramsInWifi) doParamSave(requestArgs); - - String page; - - if(_ssid == ""){ - page = getHTTPHead(FPSTR(S_titlewifisettings), FPSTR(C_wifi)); // @token titleparamsaved - page += FPSTR(HTTP_PARAMSAVED); - } - else { - page = getHTTPHead(FPSTR(S_titlewifisaved), FPSTR(C_wifi)); // @token titlewifisaved - page += FPSTR(HTTP_SAVED); - } - - if(_showBack) page += FPSTR(HTTP_BACKBTN); - page += getHTTPEnd(); - - AsyncWebServerResponse *response = request->beginResponse(200, FPSTR(HTTP_HEAD_CT), page); - response->addHeader(FPSTR(HTTP_HEAD_CORS), FPSTR(HTTP_HEAD_CORS_ALLOW_ALL)); // @HTTPHEAD send cors - request->send(response); - - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_DEV, F("Sent wifi save page")); - #endif - - connect = true; //signal ready to connect/reset process in processConfigPortal -} - -void WiFiManager::handleParamSave(AsyncWebServerRequest *request) { - - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_VERBOSE, F("<- HTTP Param save ")); - #endif - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_DEV, F("Method:"), request->method() == HTTP_GET ? FPSTR(S_GET) : FPSTR(S_POST)); - #endif - handleRequest(request); - - // BUILD REQUEST ARGS STRUCT - WiFiManagerRequestArgs requestArgs(request); - - doParamSave(requestArgs); - - String page = getHTTPHead(FPSTR(S_titleparamsaved), FPSTR(C_param)); // @token titleparamsaved - page += FPSTR(HTTP_PARAMSAVED); - if(_showBack) page += FPSTR(HTTP_BACKBTN); - page += getHTTPEnd(); - - request->send(200, FPSTR(HTTP_HEAD_CT), page); - - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_DEV, F("Sent param save page")); - #endif -} - -void WiFiManager::doParamSave(WiFiManagerRequestArgs requestArgs){ - // @todo use new callback for before paramsaves, is this really needed? - if ( _presaveparamscallback != NULL) { - _presaveparamscallback(); // @CALLBACK - } - - //parameters - if(_paramsCount > 0){ - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_VERBOSE,F("Parameters")); - DEBUG_WM(WM_DEBUG_VERBOSE,FPSTR(D_HR)); - #endif - - for (int i = 0; i < _paramsCount; i++) { - if (_params[i] == NULL || _params[i]->_length > 99999) { - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] WiFiManagerParameter is out of scope")); - #endif - break; // @todo might not be needed anymore - } - - String value; - - // Check if this is a custom HTML parameter (ID is null) - if (_params[i]->getID() == nullptr) { - // For custom HTML parameters, try to get from RequestArgs - String name = (String)FPSTR(S_parampre)+(String)i; - - if (requestArgs.hasArg(name)) { - value = requestArgs.getArg(name); - } else { - continue; // Skip if not found - } - } else { - // Regular parameter - use RequestArgs - String name = (String)FPSTR(S_parampre)+(String)i; - if(requestArgs.hasArg(name)) { - value = requestArgs.getArg(name); - } else { - value = requestArgs.getArg(_params[i]->getID()); - } - } - - //store it in params array - value.toCharArray(_params[i]->_value, _params[i]->_length+1); // length+1 null terminated - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_VERBOSE,(String)_params[i]->getID() + ":",value); - #endif - } - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_VERBOSE,FPSTR(D_HR)); - #endif - } - - if ( _saveparamscallback != NULL) { - _saveparamscallback(requestArgs); // @CALLBACK - PASS RequestArgs - } - -} - -/** - * HTTPD CALLBACK info page - */ -void WiFiManager::handleInfo(AsyncWebServerRequest *request) { - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_VERBOSE, F("<- HTTP Info")); - #endif - handleRequest(request); - String page = getHTTPHead(FPSTR(S_titleinfo), FPSTR(C_info)); // @token titleinfo - reportStatus(page); - - uint16_t infos = 0; - - //@todo convert to enum or refactor to strings - //@todo wrap in build flag to remove all info code for memory saving - #ifdef ESP8266 - infos = 28; - String infoids[] = { - F("esphead"), - F("uptime"), - F("chipid"), - F("fchipid"), - F("idesize"), - F("flashsize"), - F("corever"), - F("bootver"), - F("cpufreq"), - F("freeheap"), - F("memsketch"), - F("memsmeter"), - F("lastreset"), - F("wifihead"), - F("conx"), - F("stassid"), - F("staip"), - F("stagw"), - F("stasub"), - F("dnss"), - F("host"), - F("stamac"), - F("autoconx"), - F("wifiaphead"), - F("apssid"), - F("apip"), - F("apbssid"), - F("apmac") - }; - - #elif defined(ESP32) - // add esp_chip_info ? - infos = 27; - String infoids[] = { - F("esphead"), - F("uptime"), - F("chipid"), - F("chiprev"), - F("idesize"), - F("flashsize"), - F("cpufreq"), - F("freeheap"), - F("memsketch"), - F("memsmeter"), - F("lastreset"), - F("temp"), - // F("hall"), - F("wifihead"), - F("conx"), - F("stassid"), - F("staip"), - F("stagw"), - F("stasub"), - F("dnss"), - F("host"), - F("stamac"), - F("apssid"), - F("wifiaphead"), - F("apip"), - F("apmac"), - F("aphost"), - F("apbssid") - }; - #endif - - for(size_t i=0; i"); - - page += F("

About


"); - page += getInfoData("aboutver"); - page += getInfoData("aboutarduinover"); - page += getInfoData("aboutidfver"); - page += getInfoData("aboutdate"); - page += F("
"); - - if(_showInfoUpdate){ - page += HTTP_PORTAL_MENU[8]; - page += HTTP_PORTAL_MENU[9]; - } - if(_showInfoErase) page += FPSTR(HTTP_ERASEBTN); - if(_showBack) page += FPSTR(HTTP_BACKBTN); - page += FPSTR(HTTP_HELP); - page += getHTTPEnd(); - - request->send(200, FPSTR(HTTP_HEAD_CT), page); - - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_DEV, F("Sent info page")); - #endif -} - -String WiFiManager::getInfoData(String id){ - - String p; - if(id==F("esphead")){ - p = FPSTR(HTTP_INFO_esphead); - #ifdef ESP32 - p.replace(FPSTR(T_1), (String)ESP.getChipModel()); - #endif - } - else if(id==F("wifihead")){ - p = FPSTR(HTTP_INFO_wifihead); - p.replace(FPSTR(T_1),getModeString(WiFi.getMode())); - } - else if(id==F("uptime")){ - // subject to rollover! - p = FPSTR(HTTP_INFO_uptime); - p.replace(FPSTR(T_1),(String)(millis() / 1000 / 60)); - p.replace(FPSTR(T_2),(String)((millis() / 1000) % 60)); - } - else if(id==F("chipid")){ - p = FPSTR(HTTP_INFO_chipid); - p.replace(FPSTR(T_1),String(WIFI_getChipId(),HEX)); - } - #ifdef ESP32 - else if(id==F("chiprev")){ - p = FPSTR(HTTP_INFO_chiprev); - String rev = (String)ESP.getChipRevision(); - #ifdef _SOC_EFUSE_REG_H_ - String revb = (String)(REG_READ(EFUSE_BLK0_RDATA3_REG) >> (EFUSE_RD_CHIP_VER_RESERVE_S)&&EFUSE_RD_CHIP_VER_RESERVE_V); - p.replace(FPSTR(T_1),rev+"
"+revb); - #else - p.replace(FPSTR(T_1),rev); - #endif - } - #endif - #ifdef ESP8266 - else if(id==F("fchipid")){ - p = FPSTR(HTTP_INFO_fchipid); - p.replace(FPSTR(T_1),(String)ESP.getFlashChipId()); - } - #endif - else if(id==F("idesize")){ - p = FPSTR(HTTP_INFO_idesize); - p.replace(FPSTR(T_1),(String)ESP.getFlashChipSize()); - } - else if(id==F("flashsize")){ - #ifdef ESP8266 - p = FPSTR(HTTP_INFO_flashsize); - p.replace(FPSTR(T_1),(String)ESP.getFlashChipRealSize()); - #elif defined ESP32 - p = FPSTR(HTTP_INFO_psrsize); - p.replace(FPSTR(T_1),(String)ESP.getPsramSize()); - #endif - } - else if(id==F("corever")){ - #ifdef ESP8266 - p = FPSTR(HTTP_INFO_corever); - p.replace(FPSTR(T_1),(String)ESP.getCoreVersion()); - #endif - } - #ifdef ESP8266 - else if(id==F("bootver")){ - p = FPSTR(HTTP_INFO_bootver); - p.replace(FPSTR(T_1),(String)system_get_boot_version()); - } - #endif - else if(id==F("cpufreq")){ - p = FPSTR(HTTP_INFO_cpufreq); - p.replace(FPSTR(T_1),(String)ESP.getCpuFreqMHz()); - } - else if(id==F("freeheap")){ - p = FPSTR(HTTP_INFO_freeheap); - p.replace(FPSTR(T_1),(String)ESP.getFreeHeap()); - } - else if(id==F("memsketch")){ - p = FPSTR(HTTP_INFO_memsketch); - p.replace(FPSTR(T_1),(String)(ESP.getSketchSize())); - p.replace(FPSTR(T_2),(String)(ESP.getSketchSize()+ESP.getFreeSketchSpace())); - } - else if(id==F("memsmeter")){ - p = FPSTR(HTTP_INFO_memsmeter); - p.replace(FPSTR(T_1),(String)(ESP.getSketchSize())); - p.replace(FPSTR(T_2),(String)(ESP.getSketchSize()+ESP.getFreeSketchSpace())); - } - else if(id==F("lastreset")){ - #ifdef ESP8266 - p = FPSTR(HTTP_INFO_lastreset); - p.replace(FPSTR(T_1),(String)ESP.getResetReason()); - #elif defined(ESP32) && defined(_ROM_RTC_H_) - // requires #include - p = FPSTR(HTTP_INFO_lastreset); - for(int i=0;i<2;i++){ - int reason = rtc_get_reset_reason(i); - String tok = (String)T_ss+(String)(i+1)+(String)T_es; - switch (reason) - { - //@todo move to array - case 1 : p.replace(tok,F("Vbat power on reset"));break; - case 3 : p.replace(tok,F("Software reset digital core"));break; - case 4 : p.replace(tok,F("Legacy watch dog reset digital core"));break; - case 5 : p.replace(tok,F("Deep Sleep reset digital core"));break; - case 6 : p.replace(tok,F("Reset by SLC module, reset digital core"));break; - case 7 : p.replace(tok,F("Timer Group0 Watch dog reset digital core"));break; - case 8 : p.replace(tok,F("Timer Group1 Watch dog reset digital core"));break; - case 9 : p.replace(tok,F("RTC Watch dog Reset digital core"));break; - case 10 : p.replace(tok,F("Instrusion tested to reset CPU"));break; - case 11 : p.replace(tok,F("Time Group reset CPU"));break; - case 12 : p.replace(tok,F("Software reset CPU"));break; - case 13 : p.replace(tok,F("RTC Watch dog Reset CPU"));break; - case 14 : p.replace(tok,F("for APP CPU, reseted by PRO CPU"));break; - case 15 : p.replace(tok,F("Reset when the vdd voltage is not stable"));break; - case 16 : p.replace(tok,F("RTC Watch dog reset digital core and rtc module"));break; - default : p.replace(tok,F("NO_MEAN")); - } - } - #endif - } - else if(id==F("apip")){ - p = FPSTR(HTTP_INFO_apip); - p.replace(FPSTR(T_1),WiFi.softAPIP().toString()); - } - else if(id==F("apmac")){ - p = FPSTR(HTTP_INFO_apmac); - p.replace(FPSTR(T_1),(String)WiFi.softAPmacAddress()); - } - #ifdef ESP32 - else if(id==F("aphost")){ - p = FPSTR(HTTP_INFO_aphost); - p.replace(FPSTR(T_1),WiFi.softAPgetHostname()); - } - #endif - #ifndef WM_NOSOFTAPSSID - #ifdef ESP8266 - else if(id==F("apssid")){ - p = FPSTR(HTTP_INFO_apssid); - p.replace(FPSTR(T_1),htmlEntities(WiFi.softAPSSID())); - } - #endif - #endif - else if(id==F("apbssid")){ - p = FPSTR(HTTP_INFO_apbssid); - p.replace(FPSTR(T_1),(String)WiFi.BSSIDstr()); - } - // softAPgetHostname // esp32 - // softAPSubnetCIDR - // softAPNetworkID - // softAPBroadcastIP - - else if(id==F("stassid")){ - p = FPSTR(HTTP_INFO_stassid); - p.replace(FPSTR(T_1),htmlEntities((String)WiFi_SSID())); - } - else if(id==F("staip")){ - p = FPSTR(HTTP_INFO_staip); - p.replace(FPSTR(T_1),WiFi.localIP().toString()); - } - else if(id==F("stagw")){ - p = FPSTR(HTTP_INFO_stagw); - p.replace(FPSTR(T_1),WiFi.gatewayIP().toString()); - } - else if(id==F("stasub")){ - p = FPSTR(HTTP_INFO_stasub); - p.replace(FPSTR(T_1),WiFi.subnetMask().toString()); - } - else if(id==F("dnss")){ - p = FPSTR(HTTP_INFO_dnss); - p.replace(FPSTR(T_1),WiFi.dnsIP().toString()); - } - else if(id==F("host")){ - p = FPSTR(HTTP_INFO_host); - #ifdef ESP32 - p.replace(FPSTR(T_1),WiFi.getHostname()); - #else - p.replace(FPSTR(T_1),WiFi.hostname()); - #endif - } - else if(id==F("stamac")){ - p = FPSTR(HTTP_INFO_stamac); - p.replace(FPSTR(T_1),WiFi.macAddress()); - } - else if(id==F("conx")){ - p = FPSTR(HTTP_INFO_conx); - p.replace(FPSTR(T_1),WiFi.isConnected() ? FPSTR(S_y) : FPSTR(S_n)); - } - #ifdef ESP8266 - else if(id==F("autoconx")){ - p = FPSTR(HTTP_INFO_autoconx); - p.replace(FPSTR(T_1),WiFi.getAutoConnect() ? FPSTR(S_enable) : FPSTR(S_disable)); - } - #endif - #if defined(ESP32) && !defined(WM_NOTEMP) - else if(id==F("temp")){ - // temperature is not calibrated, varying large offsets are present, use for relative temp changes only - p = FPSTR(HTTP_INFO_temp); - p.replace(FPSTR(T_1),(String)temperatureRead()); - p.replace(FPSTR(T_2),(String)((temperatureRead()+32)*1.8f)); - } - // else if(id==F("hall")){ - // p = FPSTR(HTTP_INFO_hall); - // p.replace(FPSTR(T_1),(String)hallRead()); // hall sensor reads can cause issues with adcs - // } - #endif - else if(id==F("aboutver")){ - p = FPSTR(HTTP_INFO_aboutver); - p.replace(FPSTR(T_1),FPSTR(WM_VERSION_STR)); - } - else if(id==F("aboutarduinover")){ - #ifdef VER_ARDUINO_STR - p = FPSTR(HTTP_INFO_aboutarduino); - p.replace(FPSTR(T_1),String(VER_ARDUINO_STR)); - #endif - } - // else if(id==F("aboutidfver")){ - // #ifdef VER_IDF_STR - // p = FPSTR(HTTP_INFO_aboutidf); - // p.replace(FPSTR(T_1),String(VER_IDF_STR)); - // #endif - // } - else if(id==F("aboutsdkver")){ - p = FPSTR(HTTP_INFO_sdkver); - #ifdef ESP32 - p.replace(FPSTR(T_1),(String)esp_get_idf_version()); - // p.replace(FPSTR(T_1),(String)system_get_sdk_version()); // deprecated - #else - p.replace(FPSTR(T_1),(String)system_get_sdk_version()); - #endif - } - else if(id==F("aboutdate")){ - p = FPSTR(HTTP_INFO_aboutdate); - p.replace(FPSTR(T_1),String(__DATE__ " " __TIME__)); - } - return p; -} - -/** - * HTTPD CALLBACK exit - schedules config portal shutdown on next process() call - */ -void WiFiManager::handleExit(AsyncWebServerRequest *request) { - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_VERBOSE, F("<- HTTP Exit")); - #endif - handleRequest(request); - String page = getHTTPHead(FPSTR(S_titleexit), FPSTR(C_exit)); // @token titleexit - page += FPSTR(S_exiting); // @token exiting - page += getHTTPEnd(); - // ('Logout', 401, {'WWW-Authenticate': 'Basic realm="Login required"'}) - AsyncWebServerResponse *response = request->beginResponse(200, FPSTR(HTTP_HEAD_CT), page); - response->addHeader(F("Cache-Control"), F("no-cache, no-store, must-revalidate")); // @HTTPHEAD send cache - request->send(response); - - // Schedule abort after response is sent - _abortScheduled = true; - _abortTime = millis() + EXIT_DELAY_MS; -} - -/** - * HTTPD CALLBACK reset page - */ -void WiFiManager::handleReset(AsyncWebServerRequest *request) { - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_VERBOSE, F("<- HTTP Reset")); - #endif - handleRequest(request); - String page = getHTTPHead(FPSTR(S_titlereset), FPSTR(C_restart)); //@token titlereset - page += FPSTR(S_resetting); //@token resetting - page += getHTTPEnd(); - - request->send(200, FPSTR(HTTP_HEAD_CT), page); - - // Schedule reboot after response is sent - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(F("RESETTING ESP")); - #endif - _rebootScheduled = true; - _rebootTime = millis() + REBOOT_DELAY_MS; -} - -/** - * HTTPD CALLBACK erase page - */ - -void WiFiManager::handleErase(AsyncWebServerRequest *request, boolean opt) { - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_NOTIFY, F("<- HTTP Erase")); - #endif - handleRequest(request); - String page = getHTTPHead(FPSTR(S_titleerase), FPSTR(C_erase)); // @token titleerase - - bool ret = erase(opt); - - if(ret) page += FPSTR(S_resetting); // @token resetting - else { - page += FPSTR(S_error); // @token erroroccur - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_ERROR, F("[ERROR] WiFi EraseConfig failed")); - #endif - } - - page += getHTTPEnd(); - request->send(200, FPSTR(HTTP_HEAD_CT), page); - - if(ret){ - // Schedule reboot after response is sent - _rebootScheduled = true; - _rebootTime = millis() + ERASE_REBOOT_DELAY_MS; - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(F("RESETTING ESP")); - #endif - } -} - -/** - * HTTPD CALLBACK 404 - */ -void WiFiManager::handleNotFound(AsyncWebServerRequest *request) { - if (captivePortal(request)) return; // If captive portal redirect instead of displaying the page - handleRequest(request); - String message = FPSTR(S_notfound); // @token notfound - - bool verbose404 = false; // show info in 404 body, uri,method, args - if(verbose404){ - message += FPSTR(S_uri); // @token uri - message += request->url(); - message += FPSTR(S_method); // @token method - message += (request->method() == HTTP_GET) ? FPSTR(S_GET) : FPSTR(S_POST); - message += FPSTR(S_args); // @token args - message += request->params(); - message += F("\n"); - - for (size_t i = 0; i < request->params(); i++) { - const AsyncWebParameter* p = request->getParam(i); - message += " " + p->name() + ": " + p->value() + "\n"; - } - } - AsyncWebServerResponse *response = request->beginResponse(404, FPSTR(HTTP_HEAD_CT2), message); - response->addHeader(F("Cache-Control"), F("no-cache, no-store, must-revalidate")); // @HTTPHEAD send cache - response->addHeader(F("Pragma"), F("no-cache")); - response->addHeader(F("Expires"), F("-1")); - request->send(response); -} - -/** - * HTTPD redirector - * Redirect to captive portal if we got a request for another domain. - * Return true in that case so the page handler do not try to handle the request again. - */ -boolean WiFiManager::captivePortal(AsyncWebServerRequest *request) { - - if(!_enableCaptivePortal || !configPortalActive) return false; // skip redirections if cp not enabled or not in ap mode - - String serverLoc = toStringIp(request->client()->localIP()); - - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_DEV, "-> " + request->host()); - DEBUG_WM(WM_DEBUG_DEV, "serverLoc " + serverLoc); - #endif - - // fallback for ipv6 bug - if(serverLoc == "0.0.0.0"){ - if ((WiFi.status()) != WL_CONNECTED) - serverLoc = toStringIp(WiFi.softAPIP()); - else - serverLoc = toStringIp(WiFi.localIP()); - } - - if(_httpPort != 80) serverLoc += ":" + (String)_httpPort; // add port if not default - bool doredirect = serverLoc != request->host(); // redirect if hostheader not server ip, prevent redirect loops - - if (doredirect) { - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_VERBOSE, F("<- Request redirected to captive portal")); - DEBUG_WM(WM_DEBUG_DEV, "serverLoc " + serverLoc); - DEBUG_WM(WM_DEBUG_DEV, "Original URL " + request->url()); - #endif - // Preserve the original path in the redirect - String redirectUrl = (String)F("http://") + serverLoc + request->url(); - if (request->params() > 0) { - redirectUrl += F("?"); - for (size_t i = 0; i < request->params(); i++) { - if (i > 0) redirectUrl += F("&"); - redirectUrl += request->getParam(i)->name() + F("=") + request->getParam(i)->value(); - } - } - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_DEV, "Redirect URL " + redirectUrl); - #endif - request->redirect(redirectUrl); - return true; - } - return false; -} - -void WiFiManager::stopCaptivePortal(){ - _enableCaptivePortal= false; - // @todo maybe disable configportaltimeout(optional), or just provide callback for user -} - -// HTTPD CALLBACK, handle close, stop captive portal, if not enabled undefined -void WiFiManager::handleClose(AsyncWebServerRequest *request){ - DEBUG_WM(WM_DEBUG_VERBOSE, F("Disabling Captive Portal")); - stopCaptivePortal(); - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_VERBOSE, F("<- HTTP close")); - #endif - handleRequest(request); - String page = getHTTPHead(FPSTR(S_titleclose), FPSTR(C_close)); // @token titleclose - page += FPSTR(S_closing); // @token closing - page += getHTTPEnd(); - request->send(200, FPSTR(HTTP_HEAD_CT), page); -} - -void WiFiManager::reportStatus(String &page){ - // updateConxResult(WiFi.status()); // @todo: this defeats the purpose of last result, update elsewhere or add logic here - DEBUG_WM(WM_DEBUG_DEV,F("[WIFI] reportStatus prev:"),getWLStatusString(_lastconxresult)); - DEBUG_WM(WM_DEBUG_DEV,F("[WIFI] reportStatus current:"),getWLStatusString(WiFi.status())); - String str; - if (WiFi_SSID() != ""){ - if (WiFi.status()==WL_CONNECTED){ - str = FPSTR(HTTP_STATUS_ON); - str.replace(FPSTR(T_i),WiFi.localIP().toString()); - str.replace(FPSTR(T_v),htmlEntities(WiFi_SSID())); - } - else { - str = FPSTR(HTTP_STATUS_OFF); - str.replace(FPSTR(T_v),htmlEntities(WiFi_SSID())); - if(_lastconxresult == WL_STATION_WRONG_PASSWORD){ - // wrong password - str.replace(FPSTR(T_c),"D"); // class - str.replace(FPSTR(T_r),FPSTR(HTTP_STATUS_OFFPW)); - } - else if(_lastconxresult == WL_NO_SSID_AVAIL){ - // connect failed, or ap not found - str.replace(FPSTR(T_c),"D"); - str.replace(FPSTR(T_r),FPSTR(HTTP_STATUS_OFFNOAP)); - } - else if(_lastconxresult == WL_CONNECT_FAILED){ - // connect failed - str.replace(FPSTR(T_c),"D"); - str.replace(FPSTR(T_r),FPSTR(HTTP_STATUS_OFFFAIL)); - } - else if(_lastconxresult == WL_CONNECTION_LOST){ - // connect failed, MOST likely 4WAY_HANDSHAKE_TIMEOUT/incorrect password, state is ambiguous however - str.replace(FPSTR(T_c),"D"); - str.replace(FPSTR(T_r),FPSTR(HTTP_STATUS_OFFFAIL)); - } - else{ - str.replace(FPSTR(T_c),""); - str.replace(FPSTR(T_r),""); - } - } - } - else { - str = FPSTR(HTTP_STATUS_NONE); - } - page += str; -} - // PUBLIC // METHODS @@ -3261,6 +1827,14 @@ String WiFiManager::getWiFiHostname(){ #endif } +AsyncWebServer* WiFiManager::getServer() { + return _serverManager ? _serverManager->getServer() : nullptr; +} + +DNSServer* WiFiManager::getDNSServer() { + return _serverManager ? _serverManager->getDNSServer() : nullptr; +} + /** * [setTitle description] * @param String title, set app title @@ -3449,58 +2023,7 @@ String WiFiManager::getWiFiPass(bool persistent){ // DEBUG // @todo fix DEBUG_WM(0,0); -template -void WiFiManager::DEBUG_WM(Generic text) { - DEBUG_WM(WM_DEBUG_NOTIFY,text,""); -} - -template -void WiFiManager::DEBUG_WM(wm_debuglevel_t level,Generic text) { - if(_debugLevel >= level) DEBUG_WM(level,text,""); -} - -template -void WiFiManager::DEBUG_WM(Generic text,Genericb textb) { - DEBUG_WM(WM_DEBUG_NOTIFY,text,textb); -} - -template -void WiFiManager::DEBUG_WM(wm_debuglevel_t level,Generic text,Genericb textb) { - if(!_debug || _debugLevel < level) return; - - if(_debugLevel >= WM_DEBUG_MAX){ - #ifdef ESP8266 - // uint32_t free; - // uint16_t max; - // uint8_t frag; - // ESP.getHeapStats(&free, &max, &frag);// @todo Does not exist in 2.3.0 - // _debugPort.printf("[MEM] free: %5d | max: %5d | frag: %3d%% \n", free, max, frag); - #elif defined ESP32 - // total_free_bytes; ///< Total free bytes in the heap. Equivalent to multi_free_heap_size(). - // total_allocated_bytes; ///< Total bytes allocated to data in the heap. - // largest_free_block; ///< Size of largest free block in the heap. This is the largest malloc-able size. - // minimum_free_bytes; ///< Lifetime minimum free heap size. Equivalent to multi_minimum_free_heap_size(). - // allocated_blocks; ///< Number of (variable size) blocks allocated in the heap. - // free_blocks; ///< Number of (variable size) free blocks in the heap. - // total_blocks; ///< Total number of (variable size) blocks in the heap. - multi_heap_info_t info; - heap_caps_get_info(&info, MALLOC_CAP_INTERNAL); - uint32_t free = info.total_free_bytes; - uint16_t max = info.largest_free_block; - uint8_t frag = 100 - (max * 100) / free; - _debugPort.printf("[MEM] free: %5u | max: %5u | frag: %3u%% \n", free, max, frag); - #endif - } - - _debugPort.print(_debugPrefix); - if(_debugLevel >= debugLvlShow) _debugPort.print("["+(String)level+"] "); - _debugPort.print(text); - if(textb){ - _debugPort.print(" "); - _debugPort.print(textb); - } - _debugPort.println(); -} +// DEBUG_WM template implementations moved to WiFiManagerDebug.h /** * [debugSoftAPConfig description] @@ -3991,135 +2514,7 @@ void WiFiManager::WiFi_autoReconnect(){ #endif } -// Called when /update is requested -void WiFiManager::handleUpdate(AsyncWebServerRequest *request) { - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_VERBOSE, F("<- Handle update")); - #endif - if (captivePortal(request)) return; // If captive portal redirect instead of displaying the page - String page = getHTTPHead(_title, FPSTR(C_update)); // @token options - String str = FPSTR(HTTP_ROOT_MAIN); - str.replace(FPSTR(T_t), _title); - str.replace(FPSTR(T_v), configPortalActive ? _apName : (getWiFiHostname() + " - " + WiFi.localIP().toString())); // use ip if ap is not active for heading - page += str; - - page += FPSTR(HTTP_UPDATE); - page += getHTTPEnd(); - - request->send(200, FPSTR(HTTP_HEAD_CT), page); -} - -// upload via /u POST - AsyncWebServer upload handler -void WiFiManager::handleUpdating(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { - static unsigned long _configPortalTimeoutSAV = 0; - static bool timeoutSaved = false; - - if (!index) { - // Upload start - if (!timeoutSaved) { - _configPortalTimeoutSAV = _configPortalTimeout; - timeoutSaved = true; - } - _configPortalTimeout = 0; // Disable timeout during upload - - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_VERBOSE, F("[OTA] Update file: "), filename.c_str()); - #endif - - // Call pre-OTA callback - if (_preotaupdatecallback != NULL) { - _preotaupdatecallback(); - } - - #ifdef ESP8266 - WiFiUDP::stopAll(); - uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; - #elif defined(ESP32) - uint32_t maxSketchSpace = UPDATE_SIZE_UNKNOWN; - #endif - - if (!Update.begin(maxSketchSpace)) { - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_ERROR, F("[ERROR] OTA Update ERROR"), Update.getError()); - #endif - request->send(500, "text/plain", "Update.begin failed"); - _configPortalTimeout = _configPortalTimeoutSAV; - timeoutSaved = false; - return; - } - } - - // Write chunk - if (len) { - if (Update.write(data, len) != len) { - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_ERROR, F("[ERROR] OTA Update WRITE ERROR"), Update.getError()); - #endif - request->send(500, "text/plain", "Update.write failed"); - _configPortalTimeout = _configPortalTimeoutSAV; - timeoutSaved = false; - return; - } - } - - // Upload complete - if (final) { - if (Update.end(true)) { - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_VERBOSE, F("\n\n[OTA] OTA FILE END bytes: "), (String)index); - #endif - // Response will be sent by handleUpdateDone - } else { - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_ERROR, F("[ERROR] OTA Update END ERROR"), Update.getError()); - #endif - request->send(500, "text/plain", "Update.end failed"); - } - - // Restore timeout - _configPortalTimeout = _configPortalTimeoutSAV; - timeoutSaved = false; - } -} - -// upload and ota done, show status -void WiFiManager::handleUpdateDone(AsyncWebServerRequest *request) { - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_VERBOSE, F("<- Handle update done")); - #endif - - String page = getHTTPHead(FPSTR(S_options), FPSTR(C_update)); // @token options - String str = FPSTR(HTTP_ROOT_MAIN); - str.replace(FPSTR(T_t), _title); - str.replace(FPSTR(T_v), configPortalActive ? _apName : WiFi.localIP().toString()); // use ip if ap is not active for heading - page += str; - - if (Update.hasError()) { - page += FPSTR(HTTP_UPDATE_FAIL); - #ifdef ESP32 - page += "OTA Error: " + (String)Update.errorString(); - #else - page += "OTA Error: " + (String)Update.getError(); - #endif - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(F("[OTA] update failed")); - #endif - } - else { - page += FPSTR(HTTP_UPDATE_SUCCESS); - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(F("[OTA] update ok")); - #endif - } - page += getHTTPEnd(); - - request->send(200, FPSTR(HTTP_HEAD_CT), page); - - // Schedule reboot if successful - if (!Update.hasError()) { - _rebootScheduled = true; - _rebootTime = millis() + REBOOT_DELAY_MS; - } -} +// OTA update handlers moved to WiFiManagerHandlers.cpp +// Removed: handleUpdate, handleUpdating, handleUpdateDone #endif diff --git a/lib/WiFiManager/src/WiFiManagerHandlers.cpp b/lib/WiFiManager/src/WiFiManagerHandlers.cpp new file mode 100644 index 0000000..ed7bf7a --- /dev/null +++ b/lib/WiFiManager/src/WiFiManagerHandlers.cpp @@ -0,0 +1,1310 @@ +/** + * WiFiManagerHandlers.cpp + * + * HTTP request handlers and rendering logic implementation + * + * @author tablatronix + * @author Alex Hope-O'Connor + * @license MIT + */ + +#include "WiFiManagerHandlers.h" +#include "WiFiManager.h" + +#if defined(ESP8266) || defined(ESP32) + +WiFiManagerHandlers::WiFiManagerHandlers(WiFiManager* wm) : _wm(wm) { +} + +// Rendering Methods + +String WiFiManagerHandlers::getHTTPHead(String title, String classes){ + String page; + page += FPSTR(HTTP_HEAD_START); + page.replace(FPSTR(T_v), title); + page += FPSTR(HTTP_SCRIPT); + page += FPSTR(HTTP_STYLE); + page += _wm->_customHeadElement; + + String p = FPSTR(HTTP_HEAD_END); + if (_wm->_bodyClass != "") { + if (classes != "") { + classes += " "; // add spacing, if necessary + } + classes += _wm->_bodyClass; // add class str + } + p.replace(FPSTR(T_c), classes); + page += p; + + if (_wm->_customBodyHeader) { + page += _wm->_customBodyHeader; + } + + return page; +} + +String WiFiManagerHandlers::getHTTPEnd() { + String end = FPSTR(HTTP_END); + + if (_wm->_customBodyFooter) { + end = String(_wm->_customBodyFooter) + end; + } + + return end; +} + +String WiFiManagerHandlers::getMenuOut(){ + String page; + + for(auto menuId : _wm->_menuIds ){ + if((String)_menutokens[menuId] == "param" && _wm->_paramsCount == 0) continue; // no params set, omit params from menu, @todo this may be undesired by someone, use only menu to force? + if((String)_menutokens[menuId] == "custom" && _wm->_customMenuHTML!=NULL){ + page += _wm->_customMenuHTML; + continue; + } + page += HTTP_PORTAL_MENU[menuId]; + delay(0); + } + + return page; +} + +String WiFiManagerHandlers::getScanItemOut(){ + String page; + + // Never trigger scans from here - only use cached data + // If no cached data, show message (scan should be started elsewhere if needed) + int n = _wm->_numNetworks; + if (n == 0) { + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(F("No networks found")); + #endif + if(_wm->_scanInProgress){ + page += F("Scanning for networks...

"); + } else { + page += FPSTR(S_nonetworks); // @token nonetworks + page += F("

"); + } + } + else { + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(n,F("networks found")); + #endif + //sort networks + int indices[n]; + for (int i = 0; i < n; i++) { + indices[i] = i; + } + + // RSSI SORT + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n; j++) { + if (WiFi.RSSI(indices[j]) > WiFi.RSSI(indices[i])) { + std::swap(indices[i], indices[j]); + } + } + } + + // remove duplicates ( must be RSSI sorted ) + if (_wm->_removeDuplicateAPs) { + String cssid; + for (int i = 0; i < n; i++) { + if (indices[i] == -1) continue; + cssid = WiFi.SSID(indices[i]); + for (int j = i + 1; j < n; j++) { + if (cssid == WiFi.SSID(indices[j])) { + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_VERBOSE,F("DUP AP:"),WiFi.SSID(indices[j])); + #endif + indices[j] = -1; // set dup aps to index -1 + } + } + } + } + + // token precheck, to speed up replacements on large ap lists + String HTTP_ITEM_STR = FPSTR(HTTP_ITEM); + + // toggle icons with percentage + HTTP_ITEM_STR.replace("{qp}", FPSTR(HTTP_ITEM_QP)); + HTTP_ITEM_STR.replace("{h}",_wm->_scanDispOptions ? "" : "h"); + HTTP_ITEM_STR.replace("{qi}", FPSTR(HTTP_ITEM_QI)); + HTTP_ITEM_STR.replace("{h}",_wm->_scanDispOptions ? "h" : ""); + + // set token precheck flags + bool tok_r = HTTP_ITEM_STR.indexOf(FPSTR(T_r)) > 0; + bool tok_R = HTTP_ITEM_STR.indexOf(FPSTR(T_R)) > 0; + bool tok_e = HTTP_ITEM_STR.indexOf(FPSTR(T_e)) > 0; + bool tok_q = HTTP_ITEM_STR.indexOf(FPSTR(T_q)) > 0; + bool tok_i = HTTP_ITEM_STR.indexOf(FPSTR(T_i)) > 0; + + //display networks in page + for (int i = 0; i < n; i++) { + if (indices[i] == -1) continue; // skip dups + + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_VERBOSE,F("AP: "),(String)WiFi.RSSI(indices[i]) + " " + (String)WiFi.SSID(indices[i])); + #endif + + int rssiperc = _wm->getRSSIasQuality(WiFi.RSSI(indices[i])); + uint8_t enc_type = WiFi.encryptionType(indices[i]); + + if (_wm->_minimumQuality == -1 || _wm->_minimumQuality < rssiperc) { + String item = HTTP_ITEM_STR; + if(WiFi.SSID(indices[i]) == ""){ + continue; // No idea why I am seeing these, lets just skip them for now + } + item.replace(FPSTR(T_V), _wm->htmlEntities(WiFi.SSID(indices[i]))); // ssid no encoding + item.replace(FPSTR(T_v), _wm->htmlEntities(WiFi.SSID(indices[i]),true)); // ssid no encoding + if(tok_e) item.replace(FPSTR(T_e), _wm->encryptionTypeStr(enc_type)); + if(tok_r) item.replace(FPSTR(T_r), (String)rssiperc); // rssi percentage 0-100 + if(tok_R) item.replace(FPSTR(T_R), (String)WiFi.RSSI(indices[i])); // rssi db + if(tok_q) item.replace(FPSTR(T_q), (String)int(round(map(rssiperc,0,100,1,4)))); //quality icon 1-4 + if(tok_i){ + if (enc_type != WM_WIFIOPEN) { + item.replace(FPSTR(T_i), F("l")); + } else { + item.replace(FPSTR(T_i), ""); + } + } + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_DEV,item); + #endif + page += item; + delay(0); + } else { + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_VERBOSE,F("Skipping , does not meet _minimumQuality")); + #endif + } + + } + page += FPSTR(HTTP_BR); + } + + return page; +} + +String WiFiManagerHandlers::getIpForm(String id, String title, String value){ + String item = FPSTR(HTTP_FORM_LABEL); + item += FPSTR(HTTP_FORM_PARAM); + item.replace(FPSTR(T_i), id); + item.replace(FPSTR(T_n), id); + item.replace(FPSTR(T_p), FPSTR(T_t)); + item.replace(FPSTR(T_t), title); + item.replace(FPSTR(T_l), F("15")); + item.replace(FPSTR(T_v), value); + item.replace(FPSTR(T_c), ""); + return item; +} + +String WiFiManagerHandlers::getStaticOut(){ + String page; + if ((_wm->_staShowStaticFields || _wm->_sta_static_ip) && _wm->_staShowStaticFields>=0) { + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_DEV,F("_staShowStaticFields")); + #endif + page += FPSTR(HTTP_FORM_STATIC_HEAD); + page += getIpForm(FPSTR(S_ip),FPSTR(S_staticip),(_wm->_sta_static_ip ? _wm->_sta_static_ip.toString() : "")); + page += getIpForm(FPSTR(S_gw),FPSTR(S_staticgw),(_wm->_sta_static_gw ? _wm->_sta_static_gw.toString() : "")); + page += getIpForm(FPSTR(S_sn),FPSTR(S_subnet),(_wm->_sta_static_sn ? _wm->_sta_static_sn.toString() : "")); + } + + if((_wm->_staShowDns || _wm->_sta_static_dns) && _wm->_staShowDns>=0){ + page += getIpForm(FPSTR(S_dns),FPSTR(S_staticdns),(_wm->_sta_static_dns ? _wm->_sta_static_dns.toString() : "")); + } + + if(page!="") page += FPSTR(HTTP_BR); + + return page; +} + +String WiFiManagerHandlers::getParamOut(){ + String page; + + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_DEV,F("getParamOut"),_wm->_paramsCount); + #endif + + if(_wm->_paramsCount > 0){ + + String HTTP_PARAM_temp = FPSTR(HTTP_FORM_LABEL); + HTTP_PARAM_temp += FPSTR(HTTP_FORM_PARAM); + bool tok_I = HTTP_PARAM_temp.indexOf(FPSTR(T_I)) > 0; + bool tok_i = HTTP_PARAM_temp.indexOf(FPSTR(T_i)) > 0; + bool tok_n = HTTP_PARAM_temp.indexOf(FPSTR(T_n)) > 0; + bool tok_p = HTTP_PARAM_temp.indexOf(FPSTR(T_p)) > 0; + bool tok_t = HTTP_PARAM_temp.indexOf(FPSTR(T_t)) > 0; + bool tok_l = HTTP_PARAM_temp.indexOf(FPSTR(T_l)) > 0; + bool tok_v = HTTP_PARAM_temp.indexOf(FPSTR(T_v)) > 0; + bool tok_c = HTTP_PARAM_temp.indexOf(FPSTR(T_c)) > 0; + + char valLength[5]; + + for (int i = 0; i < _wm->_paramsCount; i++) { + if (_wm->_params[i] == NULL || _wm->_params[i]->getValueLength() > 99999) { + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] WiFiManagerParameter is out of scope")); + #endif + return ""; + } + } + + // add the extra parameters to the form + for (int i = 0; i < _wm->_paramsCount; i++) { + String pitem; + switch (_wm->_params[i]->getLabelPlacement()) { + case WFM_LABEL_BEFORE: + pitem = FPSTR(HTTP_FORM_LABEL); + pitem += FPSTR(HTTP_FORM_PARAM); + break; + case WFM_LABEL_AFTER: + pitem = FPSTR(HTTP_FORM_PARAM); + pitem += FPSTR(HTTP_FORM_LABEL); + break; + default: + pitem = FPSTR(HTTP_FORM_PARAM); + break; + } + + if (_wm->_params[i]->getID() != NULL) { + if(tok_I)pitem.replace(FPSTR(T_I), (String)FPSTR(S_parampre)+(String)i); + if(tok_i)pitem.replace(FPSTR(T_i), _wm->_params[i]->getID()); + if(tok_n)pitem.replace(FPSTR(T_n), _wm->_params[i]->getID()); + if(tok_p)pitem.replace(FPSTR(T_p), FPSTR(T_t)); + if(tok_t)pitem.replace(FPSTR(T_t), _wm->_params[i]->getLabel()); + snprintf(valLength, 5, "%d", _wm->_params[i]->getValueLength()); + if(tok_l)pitem.replace(FPSTR(T_l), valLength); + if(tok_v)pitem.replace(FPSTR(T_v), _wm->_params[i]->getValue()); + if(tok_c)pitem.replace(FPSTR(T_c), _wm->_params[i]->getCustomHTML()); + } else { + pitem = _wm->_params[i]->getCustomHTML(); + } + + page += pitem; + } + } + + return page; +} + +String WiFiManagerHandlers::getInfoData(String id){ + String p; + if(id==F("esphead")){ + p = FPSTR(HTTP_INFO_esphead); + #ifdef ESP32 + p.replace(FPSTR(T_1), (String)ESP.getChipModel()); + #endif + } + else if(id==F("wifihead")){ + p = FPSTR(HTTP_INFO_wifihead); + p.replace(FPSTR(T_1),_wm->getModeString(WiFi.getMode())); + } + else if(id==F("uptime")){ + p = FPSTR(HTTP_INFO_uptime); + p.replace(FPSTR(T_1),(String)(millis() / 1000 / 60)); + p.replace(FPSTR(T_2),(String)((millis() / 1000) % 60)); + } + else if(id==F("chipid")){ + p = FPSTR(HTTP_INFO_chipid); + #ifdef ESP8266 + p.replace(FPSTR(T_1),String(ESP.getChipId(),HEX)); + #elif defined(ESP32) + p.replace(FPSTR(T_1),String((uint32_t)ESP.getEfuseMac(),HEX)); + #endif + } + #ifdef ESP32 + else if(id==F("chiprev")){ + p = FPSTR(HTTP_INFO_chiprev); + String rev = (String)ESP.getChipRevision(); + #ifdef _SOC_EFUSE_REG_H_ + String revb = (String)(REG_READ(EFUSE_BLK0_RDATA3_REG) >> (EFUSE_RD_CHIP_VER_RESERVE_S)&&EFUSE_RD_CHIP_VER_RESERVE_V); + p.replace(FPSTR(T_1),rev+"
"+revb); + #else + p.replace(FPSTR(T_1),rev); + #endif + } + #endif + #ifdef ESP8266 + else if(id==F("fchipid")){ + p = FPSTR(HTTP_INFO_fchipid); + p.replace(FPSTR(T_1),(String)ESP.getFlashChipId()); + } + #endif + else if(id==F("idesize")){ + p = FPSTR(HTTP_INFO_idesize); + p.replace(FPSTR(T_1),(String)ESP.getFlashChipSize()); + } + else if(id==F("flashsize")){ + #ifdef ESP8266 + p = FPSTR(HTTP_INFO_flashsize); + p.replace(FPSTR(T_1),(String)ESP.getFlashChipRealSize()); + #elif defined ESP32 + p = FPSTR(HTTP_INFO_psrsize); + p.replace(FPSTR(T_1),(String)ESP.getPsramSize()); + #endif + } + else if(id==F("corever")){ + #ifdef ESP8266 + p = FPSTR(HTTP_INFO_corever); + p.replace(FPSTR(T_1),(String)ESP.getCoreVersion()); + #endif + } + #ifdef ESP8266 + else if(id==F("bootver")){ + p = FPSTR(HTTP_INFO_bootver); + p.replace(FPSTR(T_1),(String)system_get_boot_version()); + } + #endif + else if(id==F("cpufreq")){ + p = FPSTR(HTTP_INFO_cpufreq); + p.replace(FPSTR(T_1),(String)ESP.getCpuFreqMHz()); + } + else if(id==F("freeheap")){ + p = FPSTR(HTTP_INFO_freeheap); + p.replace(FPSTR(T_1),(String)ESP.getFreeHeap()); + } + else if(id==F("memsketch")){ + p = FPSTR(HTTP_INFO_memsketch); + p.replace(FPSTR(T_1),(String)(ESP.getSketchSize())); + p.replace(FPSTR(T_2),(String)(ESP.getSketchSize()+ESP.getFreeSketchSpace())); + } + else if(id==F("memsmeter")){ + p = FPSTR(HTTP_INFO_memsmeter); + p.replace(FPSTR(T_1),(String)(ESP.getSketchSize())); + p.replace(FPSTR(T_2),(String)(ESP.getSketchSize()+ESP.getFreeSketchSpace())); + } + else if(id==F("lastreset")){ + #ifdef ESP8266 + p = FPSTR(HTTP_INFO_lastreset); + p.replace(FPSTR(T_1),(String)ESP.getResetReason()); + #elif defined(ESP32) && defined(_ROM_RTC_H_) + p = FPSTR(HTTP_INFO_lastreset); + for(int i=0;i<2;i++){ + int reason = rtc_get_reset_reason(i); + String tok = (String)T_ss+(String)(i+1)+(String)T_es; + switch (reason) + { + case 1 : p.replace(tok,F("Vbat power on reset"));break; + case 3 : p.replace(tok,F("Software reset digital core"));break; + case 4 : p.replace(tok,F("Legacy watch dog reset digital core"));break; + case 5 : p.replace(tok,F("Deep Sleep reset digital core"));break; + case 6 : p.replace(tok,F("Reset by SLC module, reset digital core"));break; + case 7 : p.replace(tok,F("Timer Group0 Watch dog reset digital core"));break; + case 8 : p.replace(tok,F("Timer Group1 Watch dog reset digital core"));break; + case 9 : p.replace(tok,F("RTC Watch dog Reset digital core"));break; + case 10 : p.replace(tok,F("Instrusion tested to reset CPU"));break; + case 11 : p.replace(tok,F("Time Group reset CPU"));break; + case 12 : p.replace(tok,F("Software reset CPU"));break; + case 13 : p.replace(tok,F("RTC Watch dog Reset CPU"));break; + case 14 : p.replace(tok,F("for APP CPU, reseted by PRO CPU"));break; + case 15 : p.replace(tok,F("Reset when the vdd voltage is not stable"));break; + case 16 : p.replace(tok,F("RTC Watch dog reset digital core and rtc module"));break; + default : p.replace(tok,F("NO_MEAN")); + } + } + #endif + } + else if(id==F("apip")){ + p = FPSTR(HTTP_INFO_apip); + p.replace(FPSTR(T_1),WiFi.softAPIP().toString()); + } + else if(id==F("apmac")){ + p = FPSTR(HTTP_INFO_apmac); + p.replace(FPSTR(T_1),(String)WiFi.softAPmacAddress()); + } + #ifdef ESP32 + else if(id==F("aphost")){ + p = FPSTR(HTTP_INFO_aphost); + p.replace(FPSTR(T_1),WiFi.softAPgetHostname()); + } + #endif + #ifndef WM_NOSOFTAPSSID + #ifdef ESP8266 + else if(id==F("apssid")){ + p = FPSTR(HTTP_INFO_apssid); + p.replace(FPSTR(T_1),_wm->htmlEntities(WiFi.softAPSSID())); + } + #endif + #endif + else if(id==F("apbssid")){ + p = FPSTR(HTTP_INFO_apbssid); + p.replace(FPSTR(T_1),(String)WiFi.BSSIDstr()); + } + else if(id==F("stassid")){ + p = FPSTR(HTTP_INFO_stassid); + p.replace(FPSTR(T_1),_wm->htmlEntities((String)_wm->WiFi_SSID())); + } + else if(id==F("staip")){ + p = FPSTR(HTTP_INFO_staip); + p.replace(FPSTR(T_1),WiFi.localIP().toString()); + } + else if(id==F("stagw")){ + p = FPSTR(HTTP_INFO_stagw); + p.replace(FPSTR(T_1),WiFi.gatewayIP().toString()); + } + else if(id==F("stasub")){ + p = FPSTR(HTTP_INFO_stasub); + p.replace(FPSTR(T_1),WiFi.subnetMask().toString()); + } + else if(id==F("dnss")){ + p = FPSTR(HTTP_INFO_dnss); + p.replace(FPSTR(T_1),WiFi.dnsIP().toString()); + } + else if(id==F("host")){ + p = FPSTR(HTTP_INFO_host); + #ifdef ESP32 + p.replace(FPSTR(T_1),WiFi.getHostname()); + #else + p.replace(FPSTR(T_1),WiFi.hostname()); + #endif + } + else if(id==F("stamac")){ + p = FPSTR(HTTP_INFO_stamac); + p.replace(FPSTR(T_1),WiFi.macAddress()); + } + else if(id==F("conx")){ + p = FPSTR(HTTP_INFO_conx); + p.replace(FPSTR(T_1),WiFi.isConnected() ? FPSTR(S_y) : FPSTR(S_n)); + } + #ifdef ESP8266 + else if(id==F("autoconx")){ + p = FPSTR(HTTP_INFO_autoconx); + p.replace(FPSTR(T_1),WiFi.getAutoConnect() ? FPSTR(S_enable) : FPSTR(S_disable)); + } + #endif + #if defined(ESP32) && !defined(WM_NOTEMP) + else if(id==F("temp")){ + p = FPSTR(HTTP_INFO_temp); + p.replace(FPSTR(T_1),(String)temperatureRead()); + p.replace(FPSTR(T_2),(String)((temperatureRead()+32)*1.8f)); + } + #endif + else if(id==F("aboutver")){ + p = FPSTR(HTTP_INFO_aboutver); + p.replace(FPSTR(T_1),FPSTR(WM_VERSION_STR)); + } + else if(id==F("aboutarduinover")){ + #ifdef VER_ARDUINO_STR + p = FPSTR(HTTP_INFO_aboutarduino); + p.replace(FPSTR(T_1),String(VER_ARDUINO_STR)); + #endif + } + else if(id==F("aboutsdkver")){ + p = FPSTR(HTTP_INFO_sdkver); + #ifdef ESP32 + p.replace(FPSTR(T_1),(String)esp_get_idf_version()); + #else + p.replace(FPSTR(T_1),(String)system_get_sdk_version()); + #endif + } + else if(id==F("aboutdate")){ + p = FPSTR(HTTP_INFO_aboutdate); + p.replace(FPSTR(T_1),String(__DATE__ " " __TIME__)); + } + return p; +} + +void WiFiManagerHandlers::reportStatus(String &page){ + _wm->DEBUG_WM(WM_DEBUG_DEV,F("[WIFI] reportStatus prev:"),_wm->getWLStatusString(_wm->_lastconxresult)); + _wm->DEBUG_WM(WM_DEBUG_DEV,F("[WIFI] reportStatus current:"),_wm->getWLStatusString(WiFi.status())); + String str; + if (_wm->WiFi_SSID() != ""){ + if (WiFi.status()==WL_CONNECTED){ + str = FPSTR(HTTP_STATUS_ON); + str.replace(FPSTR(T_i),WiFi.localIP().toString()); + str.replace(FPSTR(T_v),_wm->htmlEntities(_wm->WiFi_SSID())); + } + else { + str = FPSTR(HTTP_STATUS_OFF); + str.replace(FPSTR(T_v),_wm->htmlEntities(_wm->WiFi_SSID())); + if(_wm->_lastconxresult == _wm->WL_STATION_WRONG_PASSWORD){ + str.replace(FPSTR(T_c),"D"); + str.replace(FPSTR(T_r),FPSTR(HTTP_STATUS_OFFPW)); + } + else if(_wm->_lastconxresult == WL_NO_SSID_AVAIL){ + str.replace(FPSTR(T_c),"D"); + str.replace(FPSTR(T_r),FPSTR(HTTP_STATUS_OFFNOAP)); + } + else if(_wm->_lastconxresult == WL_CONNECT_FAILED){ + str.replace(FPSTR(T_c),"D"); + str.replace(FPSTR(T_r),FPSTR(HTTP_STATUS_OFFFAIL)); + } + else if(_wm->_lastconxresult == WL_CONNECTION_LOST){ + str.replace(FPSTR(T_c),"D"); + str.replace(FPSTR(T_r),FPSTR(HTTP_STATUS_OFFFAIL)); + } + else{ + str.replace(FPSTR(T_c),""); + str.replace(FPSTR(T_r),""); + } + } + } + else { + str = FPSTR(HTTP_STATUS_NONE); + } + page += str; +} + +// Captive Portal + +boolean WiFiManagerHandlers::captivePortal(AsyncWebServerRequest *request) { + + if(!_wm->_enableCaptivePortal || !_wm->configPortalActive) return false; + + String serverLoc = _wm->toStringIp(request->client()->localIP()); + + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_DEV, "-> " + request->host()); + _wm->DEBUG_WM(WM_DEBUG_DEV, "serverLoc " + serverLoc); + #endif + + // fallback for ipv6 bug + if(serverLoc == "0.0.0.0"){ + if ((WiFi.status()) != WL_CONNECTED) + serverLoc = _wm->toStringIp(WiFi.softAPIP()); + else + serverLoc = _wm->toStringIp(WiFi.localIP()); + } + + if(_wm->_httpPort != 80) serverLoc += ":" + (String)_wm->_httpPort; + bool doredirect = serverLoc != request->host(); + + if (doredirect) { + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_VERBOSE, F("<- Request redirected to captive portal")); + _wm->DEBUG_WM(WM_DEBUG_DEV, "serverLoc " + serverLoc); + _wm->DEBUG_WM(WM_DEBUG_DEV, "Original URL " + request->url()); + #endif + String redirectUrl = (String)F("http://") + serverLoc + request->url(); + if (request->params() > 0) { + redirectUrl += F("?"); + for (size_t i = 0; i < request->params(); i++) { + if (i > 0) redirectUrl += F("&"); + redirectUrl += request->getParam(i)->name() + F("=") + request->getParam(i)->value(); + } + } + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_DEV, "Redirect URL " + redirectUrl); + #endif + request->redirect(redirectUrl); + return true; + } + return false; +} + +void WiFiManagerHandlers::stopCaptivePortal(){ + _wm->_enableCaptivePortal = false; +} + +// HTTP Handlers + +void WiFiManagerHandlers::handleRequest(AsyncWebServerRequest *request) { + _wm->_webPortalAccessed = millis(); +} + +void WiFiManagerHandlers::handleRoot(AsyncWebServerRequest *request) { + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_VERBOSE, F("<- HTTP Root")); + #endif + if (captivePortal(request)) return; + handleRequest(request); + String page = getHTTPHead(_wm->_title, FPSTR(C_root)); + String str = FPSTR(HTTP_ROOT_MAIN); + str.replace(FPSTR(T_t), _wm->_title); + str.replace(FPSTR(T_v), _wm->configPortalActive ? _wm->_apName : (_wm->getWiFiHostname() + " - " + WiFi.localIP().toString())); + page += str; + page += FPSTR(HTTP_PORTAL_OPTIONS); + page += getMenuOut(); + reportStatus(page); + page += getHTTPEnd(); + + request->send(200, FPSTR(HTTP_HEAD_CT), page); + if(_wm->_preloadwifiscan) _wm->WiFi_scanNetworks(_wm->_scancachetime); +} + +void WiFiManagerHandlers::handleWifi(AsyncWebServerRequest *request, boolean scan) { + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_VERBOSE, F("<- HTTP Wifi")); + _wm->DEBUG_WM(WM_DEBUG_DEV, F("handleWifi called, scan="), scan ? "true" : "false"); + #endif + if (captivePortal(request)) { + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_DEV, F("Captive portal redirect")); + #endif + return; + } + handleRequest(request); + String page = getHTTPHead(FPSTR(S_titlewifi), FPSTR(C_wifi)); + if (scan) { + bool forceRefresh = false; + if (request->hasParam("refresh")) { + forceRefresh = true; + } + + page += F("
"); + page += getScanItemOut(); + page += F("
"); + + if(forceRefresh || !_wm->_lastscan || (millis()-_wm->_lastscan > _wm->_scancachetime)){ + if(!_wm->_scanInProgress){ + _wm->WiFi_scanNetworks(true); + } else { + _wm->_scanRequested = true; + } + } + } + String pitem = ""; + + pitem = FPSTR(HTTP_FORM_START); + pitem.replace(FPSTR(T_v), F("wifisave")); + page += pitem; + + pitem = FPSTR(HTTP_FORM_WIFI); + pitem.replace(FPSTR(T_v), _wm->WiFi_SSID()); + + if(_wm->_showPassword){ + pitem.replace(FPSTR(T_p), _wm->WiFi_psk()); + } + else if(_wm->WiFi_psk() != ""){ + pitem.replace(FPSTR(T_p), FPSTR(S_passph)); + } + else { + pitem.replace(FPSTR(T_p), ""); + } + + page += pitem; + + page += getStaticOut(); + page += FPSTR(HTTP_FORM_WIFI_END); + if(_wm->_paramsInWifi && _wm->_paramsCount > 0){ + page += FPSTR(HTTP_FORM_PARAM_HEAD); + page += getParamOut(); + } + page += FPSTR(HTTP_FORM_END); + page += F("
"); + if(_wm->_showBack) page += FPSTR(HTTP_BACKBTN); + reportStatus(page); + + page += getHTTPEnd(); + + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_DEV, F("Page length: "), String(page.length())); + _wm->DEBUG_WM(WM_DEBUG_DEV, F("_numNetworks: "), String(_wm->_numNetworks)); + _wm->DEBUG_WM(WM_DEBUG_DEV, F("_scanInProgress: "), _wm->_scanInProgress ? "true" : "false"); + _wm->DEBUG_WM(WM_DEBUG_DEV, F("_lastscan: "), String(_wm->_lastscan)); + _wm->DEBUG_WM(WM_DEBUG_DEV, F("About to send response")); + #endif + + request->send(200, FPSTR(HTTP_HEAD_CT), page); + + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_DEV, F("Response sent")); + #endif +} + +void WiFiManagerHandlers::handleParam(AsyncWebServerRequest *request){ + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_VERBOSE, F("<- HTTP Param")); + #endif + handleRequest(request); + String page = getHTTPHead(FPSTR(S_titleparam), FPSTR(C_param)); + + String pitem = ""; + + pitem = FPSTR(HTTP_FORM_START); + pitem.replace(FPSTR(T_v), F("paramsave")); + page += pitem; + + page += getParamOut(); + page += FPSTR(HTTP_FORM_END); + if(_wm->_showBack) page += FPSTR(HTTP_BACKBTN); + reportStatus(page); + page += getHTTPEnd(); + + request->send(200, FPSTR(HTTP_HEAD_CT), page); + + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_DEV, F("Sent param page")); + #endif +} + +void WiFiManagerHandlers::handleWifiSave(AsyncWebServerRequest *request) { + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_VERBOSE, F("<- HTTP WiFi save ")); + _wm->DEBUG_WM(WM_DEBUG_DEV, F("Method:"), request->method() == HTTP_GET ? FPSTR(S_GET) : FPSTR(S_POST)); + #endif + handleRequest(request); + + WiFiManager::WiFiManagerRequestArgs requestArgs(request); + + if (request->hasParam("s", true)) { + _wm->_ssid = request->getParam("s", true)->value().c_str(); + } + if (request->hasParam("p", true)) { + _wm->_pass = request->getParam("p", true)->value().c_str(); + } + + if(_wm->_ssid == "" && _wm->_pass != ""){ + _wm->_ssid = _wm->WiFi_SSID(true); + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_VERBOSE, F("Detected WiFi password change")); + #endif + } + + #ifdef WM_DEBUG_LEVEL + String requestinfo = "SERVER_REQUEST\n----------------\n"; + requestinfo += "URI: "; + requestinfo += request->url(); + requestinfo += "\nMethod: "; + requestinfo += (request->method() == HTTP_GET) ? "GET" : "POST"; + requestinfo += "\nArguments: "; + requestinfo += request->params(); + requestinfo += "\n"; + for (size_t i = 0; i < request->params(); i++) { + const AsyncWebParameter* p = request->getParam(i); + requestinfo += " " + p->name() + ": " + p->value() + "\n"; + } + + _wm->DEBUG_WM(WM_DEBUG_MAX, requestinfo); + #endif + + if (request->hasParam(FPSTR(S_ip), true)) { + String ip = request->getParam(FPSTR(S_ip), true)->value(); + _wm->optionalIPFromString(&_wm->_sta_static_ip, ip.c_str()); + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_DEV, F("static ip:"), ip); + #endif + } + if (request->hasParam(FPSTR(S_gw), true)) { + String gw = request->getParam(FPSTR(S_gw), true)->value(); + _wm->optionalIPFromString(&_wm->_sta_static_gw, gw.c_str()); + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_DEV, F("static gateway:"), gw); + #endif + } + if (request->hasParam(FPSTR(S_sn), true)) { + String sn = request->getParam(FPSTR(S_sn), true)->value(); + _wm->optionalIPFromString(&_wm->_sta_static_sn, sn.c_str()); + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_DEV, F("static netmask:"), sn); + #endif + } + if (request->hasParam(FPSTR(S_dns), true)) { + String dns = request->getParam(FPSTR(S_dns), true)->value(); + _wm->optionalIPFromString(&_wm->_sta_static_dns, dns.c_str()); + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_DEV, F("static DNS:"), dns); + #endif + } + + if (_wm->_presavewificallback != NULL) { + _wm->_presavewificallback(); + } + + if(_wm->_paramsInWifi) doParamSave(requestArgs); + + String page; + + if(_wm->_ssid == ""){ + page = getHTTPHead(FPSTR(S_titlewifisettings), FPSTR(C_wifi)); + page += FPSTR(HTTP_PARAMSAVED); + } + else { + page = getHTTPHead(FPSTR(S_titlewifisaved), FPSTR(C_wifi)); + page += FPSTR(HTTP_SAVED); + } + + if(_wm->_showBack) page += FPSTR(HTTP_BACKBTN); + page += getHTTPEnd(); + + AsyncWebServerResponse *response = request->beginResponse(200, FPSTR(HTTP_HEAD_CT), page); + response->addHeader(FPSTR(HTTP_HEAD_CORS), FPSTR(HTTP_HEAD_CORS_ALLOW_ALL)); + request->send(response); + + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_DEV, F("Sent wifi save page")); + #endif + + _wm->connect = true; +} + +void WiFiManagerHandlers::handleParamSave(AsyncWebServerRequest *request) { + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_VERBOSE, F("<- HTTP Param save ")); + _wm->DEBUG_WM(WM_DEBUG_DEV, F("Method:"), request->method() == HTTP_GET ? FPSTR(S_GET) : FPSTR(S_POST)); + #endif + handleRequest(request); + + WiFiManager::WiFiManagerRequestArgs requestArgs(request); + + doParamSave(requestArgs); + + String page = getHTTPHead(FPSTR(S_titleparamsaved), FPSTR(C_param)); + page += FPSTR(HTTP_PARAMSAVED); + if(_wm->_showBack) page += FPSTR(HTTP_BACKBTN); + page += getHTTPEnd(); + + request->send(200, FPSTR(HTTP_HEAD_CT), page); + + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_DEV, F("Sent param save page")); + #endif +} + +void WiFiManagerHandlers::doParamSave(WiFiManager::WiFiManagerRequestArgs requestArgs){ + if ( _wm->_presaveparamscallback != NULL) { + _wm->_presaveparamscallback(); + } + + if(_wm->_paramsCount > 0){ + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_VERBOSE,F("Parameters")); + _wm->DEBUG_WM(WM_DEBUG_VERBOSE,FPSTR(D_HR)); + #endif + + for (int i = 0; i < _wm->_paramsCount; i++) { + if (_wm->_params[i] == NULL || _wm->_params[i]->getValueLength() > 99999) { + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] WiFiManagerParameter is out of scope")); + #endif + break; + } + + String value; + + if (_wm->_params[i]->getID() == nullptr) { + String name = (String)FPSTR(S_parampre)+(String)i; + + if (requestArgs.hasArg(name)) { + value = requestArgs.getArg(name); + } else { + continue; + } + } else { + String name = (String)FPSTR(S_parampre)+(String)i; + if(requestArgs.hasArg(name)) { + value = requestArgs.getArg(name); + } else { + value = requestArgs.getArg(_wm->_params[i]->getID()); + } + } + + _wm->_params[i]->setValue(value.c_str(), value.length()); + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_VERBOSE,(String)_wm->_params[i]->getID() + ":",value); + #endif + } + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_VERBOSE,FPSTR(D_HR)); + #endif + } + + if ( _wm->_saveparamscallback != NULL) { + _wm->_saveparamscallback(requestArgs); + } + +} + +void WiFiManagerHandlers::handleInfo(AsyncWebServerRequest *request) { + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_VERBOSE, F("<- HTTP Info")); + #endif + handleRequest(request); + String page = getHTTPHead(FPSTR(S_titleinfo), FPSTR(C_info)); + reportStatus(page); + + uint16_t infos = 0; + + #ifdef ESP8266 + infos = 28; + String infoids[] = { + F("esphead"), + F("uptime"), + F("chipid"), + F("fchipid"), + F("idesize"), + F("flashsize"), + F("corever"), + F("bootver"), + F("cpufreq"), + F("freeheap"), + F("memsketch"), + F("memsmeter"), + F("lastreset"), + F("wifihead"), + F("conx"), + F("stassid"), + F("staip"), + F("stagw"), + F("stasub"), + F("dnss"), + F("host"), + F("stamac"), + F("autoconx"), + F("wifiaphead"), + F("apssid"), + F("apip"), + F("apbssid"), + F("apmac") + }; + + #elif defined(ESP32) + infos = 27; + String infoids[] = { + F("esphead"), + F("uptime"), + F("chipid"), + F("chiprev"), + F("idesize"), + F("flashsize"), + F("cpufreq"), + F("freeheap"), + F("memsketch"), + F("memsmeter"), + F("lastreset"), + F("temp"), + F("wifihead"), + F("conx"), + F("stassid"), + F("staip"), + F("stagw"), + F("stasub"), + F("dnss"), + F("host"), + F("stamac"), + F("apssid"), + F("wifiaphead"), + F("apip"), + F("apmac"), + F("aphost"), + F("apbssid") + }; + #endif + + for(size_t i=0; i"); + + page += F("

About


"); + page += getInfoData("aboutver"); + page += getInfoData("aboutarduinover"); + page += getInfoData("aboutidfver"); + page += getInfoData("aboutdate"); + page += F("
"); + + if(_wm->_showInfoUpdate){ + page += HTTP_PORTAL_MENU[8]; + page += HTTP_PORTAL_MENU[9]; + } + if(_wm->_showInfoErase) page += FPSTR(HTTP_ERASEBTN); + if(_wm->_showBack) page += FPSTR(HTTP_BACKBTN); + page += FPSTR(HTTP_HELP); + page += getHTTPEnd(); + + request->send(200, FPSTR(HTTP_HEAD_CT), page); + + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_DEV, F("Sent info page")); + #endif +} + +void WiFiManagerHandlers::handleExit(AsyncWebServerRequest *request) { + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_VERBOSE, F("<- HTTP Exit")); + #endif + handleRequest(request); + String page = getHTTPHead(FPSTR(S_titleexit), FPSTR(C_exit)); + page += FPSTR(S_exiting); + page += getHTTPEnd(); + AsyncWebServerResponse *response = request->beginResponse(200, FPSTR(HTTP_HEAD_CT), page); + response->addHeader(F("Cache-Control"), F("no-cache, no-store, must-revalidate")); + request->send(response); + + _wm->_abortScheduled = true; + _wm->_abortTime = millis() + _wm->EXIT_DELAY_MS; +} + +void WiFiManagerHandlers::handleReset(AsyncWebServerRequest *request) { + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_VERBOSE, F("<- HTTP Reset")); + #endif + handleRequest(request); + String page = getHTTPHead(FPSTR(S_titlereset), FPSTR(C_restart)); + page += FPSTR(S_resetting); + page += getHTTPEnd(); + + request->send(200, FPSTR(HTTP_HEAD_CT), page); + + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(F("RESETTING ESP")); + #endif + _wm->_rebootScheduled = true; + _wm->_rebootTime = millis() + _wm->REBOOT_DELAY_MS; +} + +void WiFiManagerHandlers::handleErase(AsyncWebServerRequest *request, boolean opt) { + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_NOTIFY, F("<- HTTP Erase")); + #endif + handleRequest(request); + String page = getHTTPHead(FPSTR(S_titleerase), FPSTR(C_erase)); + + bool ret = _wm->erase(opt); + + if(ret) page += FPSTR(S_resetting); + else { + page += FPSTR(S_error); + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_ERROR, F("[ERROR] WiFi EraseConfig failed")); + #endif + } + + page += getHTTPEnd(); + request->send(200, FPSTR(HTTP_HEAD_CT), page); + + if(ret){ + _wm->_rebootScheduled = true; + _wm->_rebootTime = millis() + _wm->ERASE_REBOOT_DELAY_MS; + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(F("RESETTING ESP")); + #endif + } +} + +void WiFiManagerHandlers::handleClose(AsyncWebServerRequest *request){ + _wm->DEBUG_WM(WM_DEBUG_VERBOSE, F("Disabling Captive Portal")); + stopCaptivePortal(); + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_VERBOSE, F("<- HTTP close")); + #endif + handleRequest(request); + String page = getHTTPHead(FPSTR(S_titleclose), FPSTR(C_close)); + page += FPSTR(S_closing); + page += getHTTPEnd(); + request->send(200, FPSTR(HTTP_HEAD_CT), page); +} + +void WiFiManagerHandlers::handleNotFound(AsyncWebServerRequest *request) { + if (captivePortal(request)) return; + handleRequest(request); + String message = FPSTR(S_notfound); + + bool verbose404 = false; + if(verbose404){ + message += FPSTR(S_uri); + message += request->url(); + message += FPSTR(S_method); + message += (request->method() == HTTP_GET) ? FPSTR(S_GET) : FPSTR(S_POST); + message += FPSTR(S_args); + message += request->params(); + message += F("\n"); + + for (size_t i = 0; i < request->params(); i++) { + const AsyncWebParameter* p = request->getParam(i); + message += " " + p->name() + ": " + p->value() + "\n"; + } + } + AsyncWebServerResponse *response = request->beginResponse(404, FPSTR(HTTP_HEAD_CT2), message); + response->addHeader(F("Cache-Control"), F("no-cache, no-store, must-revalidate")); + response->addHeader(F("Pragma"), F("no-cache")); + response->addHeader(F("Expires"), F("-1")); + request->send(response); +} + +void WiFiManagerHandlers::handleWiFiStatus(AsyncWebServerRequest *request){ + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_VERBOSE, F("<- HTTP WiFi status ")); + #endif + handleRequest(request); + String page; + #ifdef WM_JSTEST + page = FPSTR(HTTP_JS); + #endif + request->send(200, FPSTR(HTTP_HEAD_CT), page); +} + +void WiFiManagerHandlers::handleWiFiScanStatus(AsyncWebServerRequest *request){ + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_VERBOSE, F("<- HTTP WiFi scan status")); + #endif + handleRequest(request); + + String json = "{"; + json += "\"scanning\":"; + json += _wm->_scanInProgress ? "true" : "false"; + json += ",\"count\":"; + json += String(_wm->_numNetworks); + json += ",\"lastscan\":"; + json += String(_wm->_lastscan); + + if(!_wm->_scanInProgress && _wm->_numNetworks > 0){ + json += ",\"networks\":["; + + int n = _wm->_numNetworks; + int indices[n]; + for (int i = 0; i < n; i++) { + indices[i] = i; + } + + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n; j++) { + if (WiFi.RSSI(indices[j]) > WiFi.RSSI(indices[i])) { + std::swap(indices[i], indices[j]); + } + } + } + + if (_wm->_removeDuplicateAPs) { + String cssid; + for (int i = 0; i < n; i++) { + if (indices[i] == -1) continue; + cssid = WiFi.SSID(indices[i]); + for (int j = i + 1; j < n; j++) { + if (cssid == WiFi.SSID(indices[j])) { + indices[j] = -1; + } + } + } + } + + bool first = true; + for (int i = 0; i < n; i++) { + if (indices[i] == -1) continue; + + if (!first) json += ","; + first = false; + + json += "{"; + json += "\"ssid\":\"" + _wm->htmlEntities(WiFi.SSID(indices[i]), true) + "\","; + json += "\"rssi\":" + String(WiFi.RSSI(indices[i])) + ","; + json += "\"encryption\":" + String(WiFi.encryptionType(indices[i])); + json += "}"; + } + json += "]"; + } + + json += "}"; + + AsyncWebServerResponse *response = request->beginResponse(200, "application/json", json); + response->addHeader(F("Cache-Control"), F("no-cache")); + request->send(response); +} + +void WiFiManagerHandlers::handleUpdate(AsyncWebServerRequest *request) { + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_VERBOSE, F("<- Handle update")); + #endif + if (captivePortal(request)) return; + String page = getHTTPHead(_wm->_title, FPSTR(C_update)); + String str = FPSTR(HTTP_ROOT_MAIN); + str.replace(FPSTR(T_t), _wm->_title); + str.replace(FPSTR(T_v), _wm->configPortalActive ? _wm->_apName : (_wm->getWiFiHostname() + " - " + WiFi.localIP().toString())); + page += str; + + page += FPSTR(HTTP_UPDATE); + page += getHTTPEnd(); + + request->send(200, FPSTR(HTTP_HEAD_CT), page); +} + +void WiFiManagerHandlers::handleUpdating(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { + static unsigned long _configPortalTimeoutSAV = 0; + static bool timeoutSaved = false; + + if (!index) { + if (!timeoutSaved) { + _configPortalTimeoutSAV = _wm->_configPortalTimeout; + timeoutSaved = true; + } + _wm->_configPortalTimeout = 0; + + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_VERBOSE, F("[OTA] Update file: "), filename.c_str()); + #endif + + if (_wm->_preotaupdatecallback != NULL) { + _wm->_preotaupdatecallback(); + } + + #ifdef ESP8266 + WiFiUDP::stopAll(); + uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; + #elif defined(ESP32) + uint32_t maxSketchSpace = UPDATE_SIZE_UNKNOWN; + #endif + + if (!Update.begin(maxSketchSpace)) { + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_ERROR, F("[ERROR] OTA Update ERROR"), Update.getError()); + #endif + request->send(500, "text/plain", "Update.begin failed"); + _wm->_configPortalTimeout = _configPortalTimeoutSAV; + timeoutSaved = false; + return; + } + } + + if (len) { + if (Update.write(data, len) != len) { + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_ERROR, F("[ERROR] OTA Update WRITE ERROR"), Update.getError()); + #endif + request->send(500, "text/plain", "Update.write failed"); + _wm->_configPortalTimeout = _configPortalTimeoutSAV; + timeoutSaved = false; + return; + } + } + + if (final) { + if (Update.end(true)) { + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_VERBOSE, F("\n\n[OTA] OTA FILE END bytes: "), (String)index); + #endif + } else { + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_ERROR, F("[ERROR] OTA Update END ERROR"), Update.getError()); + #endif + request->send(500, "text/plain", "Update.end failed"); + } + + _wm->_configPortalTimeout = _configPortalTimeoutSAV; + timeoutSaved = false; + } +} + +void WiFiManagerHandlers::handleUpdateDone(AsyncWebServerRequest *request) { + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_VERBOSE, F("<- Handle update done")); + #endif + + String page = getHTTPHead(FPSTR(S_options), FPSTR(C_update)); + String str = FPSTR(HTTP_ROOT_MAIN); + str.replace(FPSTR(T_t), _wm->_title); + str.replace(FPSTR(T_v), _wm->configPortalActive ? _wm->_apName : WiFi.localIP().toString()); + page += str; + + if (Update.hasError()) { + page += FPSTR(HTTP_UPDATE_FAIL); + #ifdef ESP32 + page += "OTA Error: " + (String)Update.errorString(); + #else + page += "OTA Error: " + (String)Update.getError(); + #endif + } else { + page += FPSTR(HTTP_UPDATE_SUCCESS); + } + + page += getHTTPEnd(); + request->send(200, FPSTR(HTTP_HEAD_CT), page); + + if (!Update.hasError()) { + delay(1000); + ESP.restart(); + } +} + +#endif // defined(ESP8266) || defined(ESP32) + diff --git a/lib/WiFiManager/src/WiFiManagerServer.cpp b/lib/WiFiManager/src/WiFiManagerServer.cpp new file mode 100644 index 0000000..ea4fac7 --- /dev/null +++ b/lib/WiFiManager/src/WiFiManagerServer.cpp @@ -0,0 +1,173 @@ +/** + * WiFiManagerServer.cpp + * + * Server lifecycle management implementation + * + * @author tablatronix + * @author Alex Hope-O'Connor + * @license MIT + */ + +#include "WiFiManagerServer.h" +#include "WiFiManager.h" +#include "WiFiManagerHandlers.h" // Full definition needed for unique_ptr + +#if defined(ESP8266) || defined(ESP32) + +WiFiManagerServer::WiFiManagerServer(WiFiManager* wm) + : _wm(wm), _handlers(std::make_unique(wm)) { +} + +void WiFiManagerServer::createServer(uint16_t port) { + // If server already exists, shutdown first to prevent leaks and connection drops + if (server) { + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_VERBOSE, F("Server already exists, shutting down first")); + #endif + shutdownServer(); + } + + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(F("Starting Web Portal")); + #endif + + if(port != 80) { + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_VERBOSE, F("http server started with custom port: "), port); // @todo not showing ip + #endif + } + + server.reset(new AsyncWebServer(port)); +} + +void WiFiManagerServer::registerRoutes() { + if (!server) { + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_ERROR, F("[ERROR] Server not created, call createServer() first")); + #endif + return; + } + + if (_wm->_webservercallback != NULL) { + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_VERBOSE, F("[CB] _webservercallback calling")); + #endif + _wm->_webservercallback(); // @CALLBACK + } + + /* Setup httpd callbacks, web pages: root, wifi config pages, SO captive portal detectors and not found. */ + + // Register routes with lambdas + server->on(WM_G(R_root), HTTP_GET, [this](AsyncWebServerRequest *request) { + this->_handlers->handleRoot(request); + }); + + server->on(WM_G(R_wifi), HTTP_GET, [this](AsyncWebServerRequest *request) { + this->_handlers->handleWifi(request, true); + }); + + server->on(WM_G(R_wifinoscan), HTTP_GET, [this](AsyncWebServerRequest *request) { + this->_handlers->handleWifi(request, false); + }); + + server->on(WM_G(R_wifisave), HTTP_POST, [this](AsyncWebServerRequest *request) { + this->_handlers->handleWifiSave(request); + }); + + server->on(WM_G(R_info), HTTP_GET, [this](AsyncWebServerRequest *request) { + this->_handlers->handleInfo(request); + }); + + server->on(WM_G(R_param), HTTP_GET, [this](AsyncWebServerRequest *request) { + this->_handlers->handleParam(request); + }); + + server->on(WM_G(R_paramsave), HTTP_POST, [this](AsyncWebServerRequest *request) { + this->_handlers->handleParamSave(request); + }); + + server->on(WM_G(R_restart), HTTP_GET, [this](AsyncWebServerRequest *request) { + this->_handlers->handleReset(request); + }); + + server->on(WM_G(R_exit), HTTP_GET, [this](AsyncWebServerRequest *request) { + this->_handlers->handleExit(request); + }); + + server->on(WM_G(R_close), HTTP_GET, [this](AsyncWebServerRequest *request) { + this->_handlers->handleClose(request); + }); + + server->on(WM_G(R_erase), HTTP_GET, [this](AsyncWebServerRequest *request) { + this->_handlers->handleErase(request, false); + }); + + server->on(WM_G(R_status), HTTP_GET, [this](AsyncWebServerRequest *request) { + this->_handlers->handleWiFiStatus(request); + }); + + server->on(WM_G(R_scanstatus), HTTP_GET, [this](AsyncWebServerRequest *request) { + this->_handlers->handleWiFiScanStatus(request); + }); + + // OTA Update routes + server->on(WM_G(R_update), HTTP_GET, [this](AsyncWebServerRequest *request) { + this->_handlers->handleUpdate(request); + }); + + server->on(WM_G(R_updatedone), HTTP_POST, + [this](AsyncWebServerRequest *request) { + this->_handlers->handleUpdateDone(request); + }, + [this](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { + this->_handlers->handleUpdating(request, filename, index, data, len, final); + } + ); + + // 404 handler + server->onNotFound([this](AsyncWebServerRequest *request) { + this->_handlers->handleNotFound(request); + }); + + server->begin(); // Web server start + #ifdef WM_DEBUG_LEVEL + _wm->DEBUG_WM(WM_DEBUG_VERBOSE, F("HTTP server started")); + #endif +} + +void WiFiManagerServer::setupDNSD() { + dnsServer.reset(new DNSServer()); + + /* Setup the DNS server redirecting all the domains to the apIP */ + dnsServer->setErrorReplyCode(DNSReplyCode::NoError); + #ifdef WM_DEBUG_LEVEL + // DEBUG_WM("dns server started port: ",DNS_PORT); + _wm->DEBUG_WM(WM_DEBUG_DEV, F("dns server started with ip: "), WiFi.softAPIP()); // @todo not showing ip + #endif + const uint8_t DNS_PORT = 53; + dnsServer->start(DNS_PORT, F("*"), WiFi.softAPIP()); +} + +void WiFiManagerServer::processDNS() { + if (dnsServer) { + dnsServer->processNextRequest(); + } +} + +void WiFiManagerServer::shutdownServer() { + // @todo what is the proper way to shutdown and free the server up + // debug - many open issues aobut port not clearing for use with other servers + // AsyncWebServer doesn't have stop(), just reset the unique_ptr + if (server) { + server.reset(); + } + + // Shutdown DNS server + if (dnsServer) { + dnsServer->stop(); // free heap ? + dnsServer.reset(); + } +} + +#endif + diff --git a/test/test_wifimanager/tests/test_dns_lifecycle.cpp b/test/test_wifimanager/tests/test_dns_lifecycle.cpp index 41f3fe5..9563be8 100644 --- a/test/test_wifimanager/tests/test_dns_lifecycle.cpp +++ b/test/test_wifimanager/tests/test_dns_lifecycle.cpp @@ -15,13 +15,13 @@ void test_dns_server_created() { // Wait for DNS server to be created unsigned long start = millis(); - while (!wm.dnsServer && (millis() - start < 1000)) { + while (!wm.getDNSServer() && (millis() - start < 1000)) { wm.process(); delay(10); } // DNS server should be created when portal starts - TEST_ASSERT_NOT_NULL(wm.dnsServer); + TEST_ASSERT_NOT_NULL(wm.getDNSServer()); wm.stopConfigPortal(); @@ -40,13 +40,13 @@ void test_dns_server_cleanup() { // Wait for DNS server to be created unsigned long start = millis(); - while (!wm.dnsServer && (millis() - start < 1000)) { + while (!wm.getDNSServer() && (millis() - start < 1000)) { wm.process(); delay(10); } // Verify DNS server exists - TEST_ASSERT_NOT_NULL(wm.dnsServer); + TEST_ASSERT_NOT_NULL(wm.getDNSServer()); // Stop portal wm.stopConfigPortal(); @@ -72,11 +72,11 @@ void test_dns_server_lifecycle_cycles() { wm.process(); unsigned long start = millis(); - while (!wm.dnsServer && (millis() - start < 1000)) { + while (!wm.getDNSServer() && (millis() - start < 1000)) { wm.process(); delay(10); } - TEST_ASSERT_NOT_NULL(wm.dnsServer); + TEST_ASSERT_NOT_NULL(wm.getDNSServer()); wm.stopConfigPortal(); wm.process(); @@ -87,11 +87,11 @@ void test_dns_server_lifecycle_cycles() { wm.process(); start = millis(); - while (!wm.dnsServer && (millis() - start < 1000)) { + while (!wm.getDNSServer() && (millis() - start < 1000)) { wm.process(); delay(10); } - TEST_ASSERT_NOT_NULL(wm.dnsServer); + TEST_ASSERT_NOT_NULL(wm.getDNSServer()); wm.stopConfigPortal(); wm.process(); diff --git a/test/test_wifimanager/tests/test_integration.cpp b/test/test_wifimanager/tests/test_integration.cpp index 2a529b0..ddaf56f 100644 --- a/test/test_wifimanager/tests/test_integration.cpp +++ b/test/test_wifimanager/tests/test_integration.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "../test_main.h" // Test autoConnect fallback flow @@ -145,8 +146,8 @@ void test_portal_with_parameters_and_infrastructure() { TEST_ASSERT_TRUE(wm.getConfigPortalActive()); // Verify infrastructure - TEST_ASSERT_NOT_NULL(wm.server); - TEST_ASSERT_NOT_NULL(wm.dnsServer); + TEST_ASSERT_NOT_NULL(wm.getServer()); + TEST_ASSERT_NOT_NULL(wm.getDNSServer()); // Verify parameters TEST_ASSERT_EQUAL(2, wm.getParametersCount()); diff --git a/test/test_wifimanager/tests/test_parameter_values.cpp b/test/test_wifimanager/tests/test_parameter_values.cpp index a982d2c..33a335e 100644 --- a/test/test_wifimanager/tests/test_parameter_values.cpp +++ b/test/test_wifimanager/tests/test_parameter_values.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "../test_main.h" // Test parameter value set directly diff --git a/test/test_wifimanager/tests/test_parameters.cpp b/test/test_wifimanager/tests/test_parameters.cpp index c296b92..bb676f5 100644 --- a/test/test_wifimanager/tests/test_parameters.cpp +++ b/test/test_wifimanager/tests/test_parameters.cpp @@ -1,6 +1,7 @@ #include #include #include +#include // Test custom parameters void test_create_parameter() { diff --git a/test/test_wifimanager/tests/test_portal_lifecycle.cpp b/test/test_wifimanager/tests/test_portal_lifecycle.cpp index 97936e3..bab2cbc 100644 --- a/test/test_wifimanager/tests/test_portal_lifecycle.cpp +++ b/test/test_wifimanager/tests/test_portal_lifecycle.cpp @@ -107,10 +107,10 @@ void test_config_portal_infrastructure() { delay(200); // Give time for initialization // Verify server object exists (must check while portal is active) - TEST_ASSERT_NOT_NULL_MESSAGE(wm.server, "Server object should exist when portal is active"); + TEST_ASSERT_NOT_NULL_MESSAGE(wm.getServer(), "Server object should exist when portal is active"); // Verify DNS server exists - TEST_ASSERT_NOT_NULL_MESSAGE(wm.dnsServer, "DNS server object should exist when portal is active"); + TEST_ASSERT_NOT_NULL_MESSAGE(wm.getDNSServer(), "DNS server object should exist when portal is active"); // Verify WiFi is in AP mode WiFiMode_t mode = WiFi.getMode(); @@ -145,7 +145,7 @@ void test_start_web_portal() { wm.process(); // Verify server object exists - TEST_ASSERT_NOT_NULL(wm.server); + TEST_ASSERT_NOT_NULL(wm.getServer()); wm.stopWebPortal(); diff --git a/test/test_wifimanager/tests/test_state_transitions.cpp b/test/test_wifimanager/tests/test_state_transitions.cpp index 109d887..a72763e 100644 --- a/test/test_wifimanager/tests/test_state_transitions.cpp +++ b/test/test_wifimanager/tests/test_state_transitions.cpp @@ -76,8 +76,8 @@ void test_state_consistency_during_portal() { // Verify state consistency TEST_ASSERT_TRUE(wm.getConfigPortalActive()); - TEST_ASSERT_NOT_NULL(wm.server); - TEST_ASSERT_NOT_NULL(wm.dnsServer); + TEST_ASSERT_NOT_NULL(wm.getServer()); + TEST_ASSERT_NOT_NULL(wm.getDNSServer()); // Process multiple times for (int i = 0; i < 10; i++) { diff --git a/test/test_wifimanager/tests/test_stress.cpp b/test/test_wifimanager/tests/test_stress.cpp index 5d9b257..6642ef9 100644 --- a/test/test_wifimanager/tests/test_stress.cpp +++ b/test/test_wifimanager/tests/test_stress.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "../test_main.h" // Test many start/stop cycles for memory leaks @@ -56,8 +57,8 @@ void test_long_running_portal() { TEST_ASSERT_TRUE(wm.getConfigPortalActive()); // Verify infrastructure remains - TEST_ASSERT_NOT_NULL(wm.server); - TEST_ASSERT_NOT_NULL(wm.dnsServer); + TEST_ASSERT_NOT_NULL(wm.getServer()); + TEST_ASSERT_NOT_NULL(wm.getDNSServer()); delay(10); }