diff --git a/libraries/WiFi/examples/WiFiMultiAdvanced/.skip.esp32h2 b/libraries/WiFi/examples/WiFiMultiAdvanced/.skip.esp32h2 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libraries/WiFi/examples/WiFiMultiAdvanced/WiFiMultiAdvanced.ino b/libraries/WiFi/examples/WiFiMultiAdvanced/WiFiMultiAdvanced.ino new file mode 100644 index 00000000000..55a3e4d49a6 --- /dev/null +++ b/libraries/WiFi/examples/WiFiMultiAdvanced/WiFiMultiAdvanced.ino @@ -0,0 +1,64 @@ +/* + * This sketch tries to connect to the best AP available + * and tests for captive portals on open networks + * + */ + +#include +#include +#include + +WiFiMulti wifiMulti; + +// callback used to check Internet connectivity +bool testConnection(){ + HTTPClient http; + http.begin("http://www.espressif.com"); + int httpCode = http.GET(); + // we expect to get a 301 because it will ask to use HTTPS instead of HTTP + if (httpCode == HTTP_CODE_MOVED_PERMANENTLY) return true; + return false; +} + +void setup() +{ + Serial.begin(115200); + delay(10); + + wifiMulti.addAP("ssid_from_AP_1", "your_password_for_AP_1"); + wifiMulti.addAP("ssid_from_AP_2", "your_password_for_AP_2"); + wifiMulti.addAP("ssid_from_AP_3", "your_password_for_AP_3"); + + // These options can help when you need ANY kind of wifi connection to get a config file, report errors, etc. + wifiMulti.setStrictMode(false); // Default is true. Library will disconnect and forget currently connected AP if it's not in the AP list. + wifiMulti.setAllowOpenAP(true); // Default is false. True adds open APs to the AP list. + wifiMulti.setConnectionTestCallbackFunc(testConnection); // Attempts to connect to a remote webserver in case of captive portals. + + Serial.println("Connecting Wifi..."); + if(wifiMulti.run() == WL_CONNECTED) { + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + } +} + +void loop() +{ + static bool isConnected = false; + uint8_t WiFiStatus = wifiMulti.run(); + + if (WiFiStatus == WL_CONNECTED) { + if (!isConnected) { + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + } + isConnected = true; + } else { + Serial.println("WiFi not connected!"); + isConnected = false; + delay(5000); + } +} diff --git a/libraries/WiFi/src/WiFiMulti.cpp b/libraries/WiFi/src/WiFiMulti.cpp index 22ec1ea46f9..b0de3a44484 100644 --- a/libraries/WiFi/src/WiFiMulti.cpp +++ b/libraries/WiFi/src/WiFiMulti.cpp @@ -33,10 +33,9 @@ WiFiMulti::WiFiMulti() ipv6_support = false; } -WiFiMulti::~WiFiMulti() +void WiFiMulti::APlistClean(void) { - for(uint32_t i = 0; i < APlist.size(); i++) { - WifiAPlist_t entry = APlist[i]; + for(auto entry : APlist) { if(entry.ssid) { free(entry.ssid); } @@ -47,17 +46,22 @@ WiFiMulti::~WiFiMulti() APlist.clear(); } +WiFiMulti::~WiFiMulti() +{ + APlistClean(); +} + bool WiFiMulti::addAP(const char* ssid, const char *passphrase) { WifiAPlist_t newAP; - if(!ssid || *ssid == 0x00 || strlen(ssid) > 31) { + if(!ssid || *ssid == '\0' || strlen(ssid) > 31) { // fail SSID too long or missing! log_e("[WIFI][APlistAdd] no ssid or ssid too long"); return false; } - if(passphrase && strlen(passphrase) > 64) { + if(passphrase && strlen(passphrase) > 63) { // fail passphrase too long! log_e("[WIFI][APlistAdd] passphrase too long"); return false; @@ -70,7 +74,7 @@ bool WiFiMulti::addAP(const char* ssid, const char *passphrase) return false; } - if(passphrase && *passphrase != 0x00) { + if(passphrase && *passphrase != '\0') { newAP.passphrase = strdup(passphrase); if(!newAP.passphrase) { log_e("[WIFI][APlistAdd] fail newAP.passphrase == 0"); @@ -80,7 +84,7 @@ bool WiFiMulti::addAP(const char* ssid, const char *passphrase) } else { newAP.passphrase = NULL; } - + newAP.hasFailed = false; APlist.push_back(newAP); log_i("[WIFI][APlistAdd] add SSID: %s", newAP.ssid); return true; @@ -91,9 +95,20 @@ uint8_t WiFiMulti::run(uint32_t connectTimeout) int8_t scanResult; uint8_t status = WiFi.status(); if(status == WL_CONNECTED) { - for(uint32_t x = 0; x < APlist.size(); x++) { - if(WiFi.SSID()==APlist[x].ssid) { + if (!_bWFMInit && _connectionTestCBFunc != NULL){ + if (_connectionTestCBFunc() == true) { + _bWFMInit = true; + return status; + } + } else { + if (!_bStrict) { return status; + } else { + for(auto ap : APlist) { + if(WiFi.SSID() == ap.ssid) { + return status; + } + } } } WiFi.disconnect(false,false); @@ -102,22 +117,27 @@ uint8_t WiFiMulti::run(uint32_t connectTimeout) } scanResult = WiFi.scanNetworks(); - if(scanResult == WIFI_SCAN_RUNNING) { + if (scanResult == WIFI_SCAN_RUNNING) { // scan is running return WL_NO_SSID_AVAIL; - } else if(scanResult >= 0) { + } else if (scanResult >= 0) { // scan done analyze - WifiAPlist_t bestNetwork { NULL, NULL }; + int32_t bestIndex = 0; + WifiAPlist_t bestNetwork { NULL, NULL, false }; int bestNetworkDb = INT_MIN; + int bestNetworkSec = WIFI_AUTH_MAX; uint8_t bestBSSID[6]; int32_t bestChannel = 0; log_i("[WIFI] scan done"); - if(scanResult == 0) { + if (scanResult == 0) { log_e("[WIFI] no networks found"); } else { log_i("[WIFI] %d networks found", scanResult); + + int8_t failCount = 0; + int8_t foundCount = 0; for(int8_t i = 0; i < scanResult; ++i) { String ssid_scan; @@ -127,22 +147,47 @@ uint8_t WiFiMulti::run(uint32_t connectTimeout) int32_t chan_scan; WiFi.getNetworkInfo(i, ssid_scan, sec_scan, rssi_scan, BSSID_scan, chan_scan); + // add any Open WiFi AP to the list, if allowed with setAllowOpenAP(true) + if (_bAllowOpenAP && sec_scan == WIFI_AUTH_OPEN){ + bool found = false; + for(auto check : APlist) { + if (ssid_scan == check.ssid){ + found = true; + break; + } + } + // If we didn't find it, add this Open WiFi AP to the list + if (!found){ + log_i("[WIFI][APlistAdd] adding Open WiFi SSID: %s", ssid_scan.c_str()); + addAP(ssid_scan.c_str()); + } + } bool known = false; - for(uint32_t x = APlist.size() ; x > 0; x--) { - WifiAPlist_t entry = APlist[x-1]; + for(uint32_t x = 0; x < APlist.size(); x++) { + WifiAPlist_t entry = APlist[x]; if(ssid_scan == entry.ssid) { // SSID match - known = true; - if(rssi_scan > bestNetworkDb) { // best network - if(sec_scan == WIFI_AUTH_OPEN || entry.passphrase) { // check for passphrase if not open wlan - bestNetworkDb = rssi_scan; - bestChannel = chan_scan; - memcpy((void*) &bestNetwork, (void*) &entry, sizeof(bestNetwork)); - memcpy((void*) &bestBSSID, (void*) BSSID_scan, sizeof(bestBSSID)); + log_v("known ssid: %s, has failed: %s", entry.ssid, entry.hasFailed ? "yes" : "no"); + foundCount++; + if (!entry.hasFailed){ + known = true; + log_v("rssi_scan: %d, bestNetworkDb: %d", rssi_scan, bestNetworkDb); + if(rssi_scan > bestNetworkDb) { // best network + if(_bAllowOpenAP || (sec_scan == WIFI_AUTH_OPEN || entry.passphrase)) { // check for passphrase if not open wlan + log_v("best network is now: %s", ssid_scan); + bestIndex = x; + bestNetworkSec = sec_scan; + bestNetworkDb = rssi_scan; + bestChannel = chan_scan; + memcpy((void*) &bestNetwork, (void*) &entry, sizeof(bestNetwork)); + memcpy((void*) &bestBSSID, (void*) BSSID_scan, sizeof(bestBSSID)); + } } + break; + } else { + failCount++; } - break; } } @@ -152,8 +197,12 @@ uint8_t WiFiMulti::run(uint32_t connectTimeout) log_d(" %d: [%d][%02X:%02X:%02X:%02X:%02X:%02X] %s (%d) %c", i, chan_scan, BSSID_scan[0], BSSID_scan[1], BSSID_scan[2], BSSID_scan[3], BSSID_scan[4], BSSID_scan[5], ssid_scan.c_str(), rssi_scan, (sec_scan == WIFI_AUTH_OPEN) ? ' ' : '*'); } } + log_v("foundCount = %d, failCount = %d", foundCount, failCount); + // if all the APs in the list have failed, reset the failure flags + if (foundCount == failCount) { + resetFails(); // keeps trying the APs in the list + } } - // clean up ram WiFi.scanDelete(); @@ -163,12 +212,15 @@ uint8_t WiFiMulti::run(uint32_t connectTimeout) if (ipv6_support == true) { WiFi.enableIPv6(); } - WiFi.begin(bestNetwork.ssid, bestNetwork.passphrase, bestChannel, bestBSSID); + WiFi.disconnect(); + delay(10); + WiFi.begin(bestNetwork.ssid, (_bAllowOpenAP && bestNetworkSec == WIFI_AUTH_OPEN) ? NULL : bestNetwork.passphrase, bestChannel, bestBSSID); status = WiFi.status(); + _bWFMInit = true; auto startTime = millis(); // wait for connection, fail, or timeout - while(status != WL_CONNECTED && status != WL_NO_SSID_AVAIL && status != WL_CONNECT_FAILED && (millis() - startTime) <= connectTimeout) { + while(status != WL_CONNECTED && (millis() - startTime) <= connectTimeout) { // && status != WL_NO_SSID_AVAIL && status != WL_CONNECT_FAILED delay(10); status = WiFi.status(); } @@ -180,15 +232,32 @@ uint8_t WiFiMulti::run(uint32_t connectTimeout) log_d("[WIFI] IP: %s", WiFi.localIP().toString().c_str()); log_d("[WIFI] MAC: %s", WiFi.BSSIDstr().c_str()); log_d("[WIFI] Channel: %d", WiFi.channel()); + + if (_connectionTestCBFunc != NULL) { + // We connected to an AP but if it's a captive portal we're not going anywhere. Test it. + if (_connectionTestCBFunc()) { + resetFails(); + } else { + markAsFailed(bestIndex); + WiFi.disconnect(); + delay(10); + status = WiFi.status(); + } + } else { + resetFails(); + } break; case WL_NO_SSID_AVAIL: log_e("[WIFI] Connecting Failed AP not found."); + markAsFailed(bestIndex); break; case WL_CONNECT_FAILED: log_e("[WIFI] Connecting Failed."); + markAsFailed(bestIndex); break; default: log_e("[WIFI] Connecting Failed (%d).", status); + markAsFailed(bestIndex); break; } } else { @@ -210,3 +279,27 @@ uint8_t WiFiMulti::run(uint32_t connectTimeout) void WiFiMulti::enableIPv6(bool state) { ipv6_support = state; } + +void WiFiMulti::markAsFailed(int32_t i) { + APlist[i].hasFailed = true; + log_d("[WIFI] Marked SSID %s as failed", APlist[i].ssid); +} + +void WiFiMulti::resetFails(){ + for(uint32_t i = 0; i < APlist.size(); i++) { + APlist[i].hasFailed = false; + } + log_d("[WIFI] Resetting failure flags"); +} + +void WiFiMulti::setStrictMode(bool bStrict) { + _bStrict = bStrict; +} + +void WiFiMulti::setAllowOpenAP(bool bAllowOpenAP) { + _bAllowOpenAP = bAllowOpenAP; +} + +void WiFiMulti::setConnectionTestCallbackFunc(ConnectionTestCB_t cbFunc) { + _connectionTestCBFunc = cbFunc; +} diff --git a/libraries/WiFi/src/WiFiMulti.h b/libraries/WiFi/src/WiFiMulti.h index d8551fab9dd..75a40c87679 100644 --- a/libraries/WiFi/src/WiFiMulti.h +++ b/libraries/WiFi/src/WiFiMulti.h @@ -32,8 +32,11 @@ typedef struct { char * ssid; char * passphrase; + bool hasFailed; } WifiAPlist_t; +typedef std::function ConnectionTestCB_t; + class WiFiMulti { public: @@ -41,13 +44,36 @@ class WiFiMulti ~WiFiMulti(); bool addAP(const char* ssid, const char *passphrase = NULL); - - void enableIPv6(bool state); uint8_t run(uint32_t connectTimeout=5000); + void enableIPv6(bool state); + + // Force (default: true) to only keep connected or to connect to an AP from the provided WiFiMulti list. + // When bStrict is false, it will keep the last/current connected AP even if not in the WiFiMulti List. + void setStrictMode(bool bStrict = true); + + // allows (true) to connect to ANY open AP, even if not in the user list + // default false (do not connect to an open AP that has not been explicitaly added by the user to list) + void setAllowOpenAP(bool bAllowOpenAP = false); + + // clears the current list of Multi APs and frees the memory + void APlistClean(void); + + // allow the user to define a callback function that will validate the connection to the Internet. + // if the callback returns true, the connection is considered valid and the AP will added to the validated AP list. + // set the callback to NULL to disable the feature and validate any SSID that is in the list. + void setConnectionTestCallbackFunc(ConnectionTestCB_t cbFunc); private: std::vector APlist; bool ipv6_support; + + bool _bStrict = true; + bool _bAllowOpenAP = false; + ConnectionTestCB_t _connectionTestCBFunc = NULL; + bool _bWFMInit = false; + + void markAsFailed(int32_t i); + void resetFails(); }; #endif /* WIFICLIENTMULTI_H_ */