diff --git a/lib/WiFiManager/include/WiFiManager.h b/lib/WiFiManager/include/WiFiManager.h
index e57fe66..a562351 100644
--- a/lib/WiFiManager/include/WiFiManager.h
+++ b/lib/WiFiManager/include/WiFiManager.h
@@ -595,6 +595,11 @@ class WiFiManager
unsigned long _startscan = 0; // ms for timing wifi scans
unsigned long _startconn = 0; // ms for timing wifi connects
+ // async scan state management
+ bool _scanInProgress = false; // flag indicating scan is currently running
+ bool _scanRequested = false; // flag indicating scan was requested but not yet started
+ unsigned long _scanRequestTime = 0; // ms when scan was requested
+
// async reboot/abort scheduling
bool _rebootScheduled = false; // flag for scheduled reboot
unsigned long _rebootTime = 0; // ms when reboot should occur
@@ -697,7 +702,6 @@ class WiFiManager
public:
boolean _preloadwifiscan = false; // preload wifiscan if true
unsigned int _scancachetime = 30000; // ms cache time for preload scans
- boolean _asyncScan = false; // perform wifi network scan async
protected:
@@ -749,6 +753,7 @@ protected:
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);
@@ -776,8 +781,9 @@ protected:
String WiFi_SSID(bool persistent = true) const;
String WiFi_psk(bool persistent = true) const;
bool WiFi_scanNetworks();
- bool WiFi_scanNetworks(bool force,bool async);
- bool WiFi_scanNetworks(unsigned int cachetime,bool async);
+ bool WiFi_scanNetworks(bool force); // Always async - returns false if scan started but not complete
+ bool WiFi_scanNetworks(bool force,bool async); // async parameter ignored - always async
+ bool WiFi_scanNetworks(unsigned int cachetime,bool async); // async parameter ignored - always async
bool WiFi_scanNetworks(unsigned int cachetime);
void WiFi_scanComplete(int networksFound);
bool WiFiSetCountry();
diff --git a/lib/WiFiManager/include/wm_consts_en.h b/lib/WiFiManager/include/wm_consts_en.h
index 5ba4939..cf703fb 100644
--- a/lib/WiFiManager/include/wm_consts_en.h
+++ b/lib/WiFiManager/include/wm_consts_en.h
@@ -59,6 +59,7 @@ const char R_exit[] PROGMEM = "/exit";
const char R_close[] PROGMEM = "/close";
const char R_erase[] PROGMEM = "/erase";
const char R_status[] PROGMEM = "/status";
+const char R_scanstatus[] PROGMEM = "/wifi/scanstatus";
const char R_update[] PROGMEM = "/update";
const char R_updatedone[] PROGMEM = "/u";
diff --git a/lib/WiFiManager/src/WiFiManager.cpp b/lib/WiFiManager/src/WiFiManager.cpp
index e1adcae..d3c4744 100644
--- a/lib/WiFiManager/src/WiFiManager.cpp
+++ b/lib/WiFiManager/src/WiFiManager.cpp
@@ -687,6 +687,10 @@ void WiFiManager::setupHTTPServer(){
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);
@@ -727,7 +731,7 @@ void WiFiManager::setupDNSD(){
void WiFiManager::setupConfigPortal() {
setupHTTPServer();
_lastscan = 0; // reset network scan cache
- if(_preloadwifiscan) WiFi_scanNetworks(true,true); // preload wifiscan , async
+ if(_preloadwifiscan) WiFi_scanNetworks(true); // preload wifiscan (async)
}
boolean WiFiManager::startConfigPortal() {
@@ -892,6 +896,31 @@ boolean WiFiManager::process(){
#if defined(WM_MDNS) && defined(ESP8266)
MDNS.update();
#endif
+
+ // Poll WiFi scan status (non-blocking)
+ // Check if scan is in progress and poll for completion
+ if(_scanInProgress){
+ int8_t scanStatus = WiFi.scanComplete();
+ if(scanStatus >= 0){
+ // Scan completed
+ WiFi_scanComplete(scanStatus);
+ }
+ else if(scanStatus == WIFI_SCAN_FAILED){
+ // Scan failed
+ #ifdef WM_DEBUG_LEVEL
+ DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] scan failed during polling"));
+ #endif
+ _scanInProgress = false;
+ _scanRequested = false;
+ }
+ // If WIFI_SCAN_RUNNING, continue polling next time (non-blocking)
+ }
+
+ // Handle queued scan requests
+ if(_scanRequested && !_scanInProgress){
+ WiFi_scanNetworks(true);
+ _scanRequested = false;
+ }
if(webPortalActive || (configPortalActive && !_configPortalIsBlocking)){
// if timed out or abort, break
@@ -1400,7 +1429,7 @@ void WiFiManager::handleRoot(AsyncWebServerRequest *request) {
page += getHTTPEnd();
request->send(200, FPSTR(HTTP_HEAD_CT), page);
- if(_preloadwifiscan) WiFi_scanNetworks(_scancachetime, true); // preload wifiscan throttled, async
+ 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
@@ -1421,8 +1450,22 @@ void WiFiManager::handleWifi(AsyncWebServerRequest *request, boolean scan) {
if (request->hasParam("refresh")) {
forceRefresh = true;
}
- WiFi_scanNetworks(forceRefresh, false); //wifiscan, force if arg refresh
+
+ // 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 = "";
@@ -1452,9 +1495,111 @@ void WiFiManager::handleWifi(AsyncWebServerRequest *request, boolean scan) {
page += getParamOut();
}
page += FPSTR(HTTP_FORM_END);
- page += FPSTR(HTTP_SCAN_LINK);
+ // Replace refresh button with AJAX version
+ page += F("
");
if(_showBack) page += FPSTR(HTTP_BACKBTN);
reportStatus(page);
+
+ // Add JavaScript for AJAX polling
+ page += F("");
+
page += getHTTPEnd();
request->send(200, FPSTR(HTTP_HEAD_CT), page);
@@ -1518,6 +1663,8 @@ String WiFiManager::getMenuOut(){
void WiFiManager::WiFi_scanComplete(int networksFound){
_lastscan = millis();
_numNetworks = networksFound;
+ _scanInProgress = false;
+ _scanRequested = false;
#ifdef WM_DEBUG_LEVEL
DEBUG_WM(WM_DEBUG_VERBOSE,F("WiFi Scan ASYNC completed"), "in "+(String)(_lastscan - _startscan)+" ms");
DEBUG_WM(WM_DEBUG_VERBOSE,F("WiFi Scan ASYNC found:"),_numNetworks);
@@ -1525,18 +1672,22 @@ void WiFiManager::WiFi_scanComplete(int networksFound){
}
bool WiFiManager::WiFi_scanNetworks(){
- return WiFi_scanNetworks(false,false);
+ return WiFi_scanNetworks(false);
}
bool WiFiManager::WiFi_scanNetworks(unsigned int cachetime,bool async){
- return WiFi_scanNetworks(millis()-_lastscan > cachetime,async);
+ // async parameter ignored - always async now
+ return WiFi_scanNetworks(millis()-_lastscan > cachetime);
}
bool WiFiManager::WiFi_scanNetworks(unsigned int cachetime){
- return WiFi_scanNetworks(millis()-_lastscan > cachetime,false);
+ return WiFi_scanNetworks(millis()-_lastscan > cachetime);
}
bool WiFiManager::WiFi_scanNetworks(bool force,bool async){
+ // async parameter ignored - always async now
+ return WiFi_scanNetworks(force);
+}
+bool WiFiManager::WiFi_scanNetworks(bool force){
#ifdef WM_DEBUG_LEVEL
- // DEBUG_WM(WM_DEBUG_DEV,"scanNetworks async:",async == true);
// DEBUG_WM(WM_DEBUG_DEV,_numNetworks,(millis()-_lastscan ));
// DEBUG_WM(WM_DEBUG_DEV,"scanNetworks force:",force == true);
#endif
@@ -1553,77 +1704,88 @@ bool WiFiManager::WiFi_scanNetworks(bool force,bool async){
force = true;
}
+ // If scan already in progress, don't start another one
+ if(_scanInProgress){
+ #ifdef WM_DEBUG_LEVEL
+ DEBUG_WM(WM_DEBUG_VERBOSE,F("WiFi Scan already in progress"));
+ #endif
+ return false; // Scan in progress, not complete yet
+ }
+
if(force){
- int8_t res;
_startscan = millis();
- if(async && _asyncScan){
- #ifdef ESP8266
- #ifndef WM_NOASYNC // no async available < 2.4.0
- #ifdef WM_DEBUG_LEVEL
- DEBUG_WM(WM_DEBUG_VERBOSE,F("WiFi Scan ASYNC started"));
- #endif
- using namespace std::placeholders; // for `_1`
- WiFi.scanNetworksAsync(std::bind(&WiFiManager::WiFi_scanComplete,this,_1));
- #else
- DEBUG_WM(WM_DEBUG_VERBOSE,F("WiFi Scan SYNC started"));
- res = WiFi.scanNetworks();
- #endif
- #else
+ _scanRequestTime = millis();
+
+ #ifdef ESP8266
+ #ifndef WM_NOASYNC // no async available < 2.4.0
#ifdef WM_DEBUG_LEVEL
- DEBUG_WM(WM_DEBUG_VERBOSE,F("WiFi Scan ASYNC started"));
- #endif
- res = WiFi.scanNetworks(true);
+ DEBUG_WM(WM_DEBUG_VERBOSE,F("WiFi Scan ASYNC started"));
+ #endif
+ using namespace std::placeholders; // for `_1`
+ WiFi.scanNetworksAsync(std::bind(&WiFiManager::WiFi_scanComplete,this,_1));
+ _scanInProgress = true;
+ return false; // Scan started, not complete yet
+ #else
+ // ESP8266 < 2.4.0 - no async available, but we can't block here
+ // Return false and let process() handle it
+ #ifdef WM_DEBUG_LEVEL
+ DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] Async scan not available on this ESP8266 core"));
#endif
return false;
- }
- else{
- DEBUG_WM(WM_DEBUG_VERBOSE,F("WiFi Scan SYNC started"));
- res = WiFi.scanNetworks();
- }
- if(res == WIFI_SCAN_FAILED){
- #ifdef WM_DEBUG_LEVEL
- DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] scan failed"));
#endif
- }
- else if(res == WIFI_SCAN_RUNNING){
+ #else // ESP32
#ifdef WM_DEBUG_LEVEL
- DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] scan waiting"));
+ DEBUG_WM(WM_DEBUG_VERBOSE,F("WiFi Scan ASYNC started"));
#endif
- while(WiFi.scanComplete() == WIFI_SCAN_RUNNING){
- #ifdef WM_DEBUG_LEVEL
- DEBUG_WM(WM_DEBUG_ERROR,".");
- #endif
- delay(100);
+ int8_t res = WiFi.scanNetworks(true);
+ if(res == WIFI_SCAN_RUNNING){
+ _scanInProgress = true;
+ return false; // Scan started, not complete yet
}
- _numNetworks = WiFi.scanComplete();
- }
- else if(res >=0 ) _numNetworks = res;
- _lastscan = millis();
- #ifdef WM_DEBUG_LEVEL
- DEBUG_WM(WM_DEBUG_VERBOSE,F("WiFi Scan completed"), "in "+(String)(_lastscan - _startscan)+" ms");
+ else if(res == WIFI_SCAN_FAILED){
+ #ifdef WM_DEBUG_LEVEL
+ DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] scan failed"));
+ #endif
+ _scanInProgress = false;
+ return false;
+ }
+ else if(res >= 0){
+ // Scan completed immediately (unlikely but possible)
+ _numNetworks = res;
+ _lastscan = millis();
+ _scanInProgress = false;
+ #ifdef WM_DEBUG_LEVEL
+ DEBUG_WM(WM_DEBUG_VERBOSE,F("WiFi Scan completed immediately"), "in "+(String)(_lastscan - _startscan)+" ms");
+ #endif
+ return true;
+ }
+ return false;
#endif
- return true;
}
else {
#ifdef WM_DEBUG_LEVEL
DEBUG_WM(WM_DEBUG_VERBOSE,F("Scan is cached"),(String)(millis()-_lastscan )+" ms ago");
#endif
+ return true; // Using cached data
}
- return false;
}
String WiFiManager::WiFiManager::getScanItemOut(){
String page;
- if(!_numNetworks) WiFi_scanNetworks(); // scan in case this gets called before any scans
-
+ // 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
- page += FPSTR(S_nonetworks); // @token nonetworks
- page += F("
");
+ if(_scanInProgress){
+ page += F("Scanning for networks...
");
+ } else {
+ page += FPSTR(S_nonetworks); // @token nonetworks
+ page += F("
");
+ }
}
else {
#ifdef WM_DEBUG_LEVEL
@@ -1860,6 +2022,110 @@ void WiFiManager::handleWiFiStatus(AsyncWebServerRequest *request){
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
*/
@@ -3954,7 +4220,7 @@ String WiFiManager::WiFi_psk(bool persistent) const {
WiFi.reconnect();
#endif
}
- else if(event == ARDUINO_EVENT_WIFI_SCAN_DONE && _asyncScan){
+ else if(event == ARDUINO_EVENT_WIFI_SCAN_DONE){
uint16_t scans = WiFi.scanComplete();
WiFi_scanComplete(scans);
}