diff --git a/libraries/WiFi/examples/WiFIMultiAdvanced/WiFiMultiAdvanced.ino b/libraries/WiFi/examples/WiFIMultiAdvanced/WiFiMultiAdvanced.ino new file mode 100644 index 00000000000..c3ca79eb107 --- /dev/null +++ b/libraries/WiFi/examples/WiFIMultiAdvanced/WiFiMultiAdvanced.ino @@ -0,0 +1,47 @@ +/* + * This sketch tries to connect to the best AP available, and tests for captive portals on open networks + * + */ + +#include +#include + +WiFiMulti wifiMulti; + +bool bConnected = 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.setTestConnection(true); // Default is false. Attempts to connect to a remote webserver in case of captive portals. Most useful with AllowOpenAP = true. + + // Optional - defaults to http://www.amazon.com with testphrase 301 Moved. + // You can also set this to a simple test page on your own server to ensure you can reach it + // wifiMulti.setTestURL("http://www.brainjar.com/java/host/test.html"); // Must include http:// + // wifiMulti.setTestPhrase("200 OK"); // Use a non-302 status code to ensure we bypass captive portals. Can be any text in the webpage. + + 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() +{ + if( wifiMulti.run() != WL_CONNECTED) { + Serial.println("WiFi not connected!"); + delay(5000); + } +} \ No newline at end of file diff --git a/libraries/WiFi/src/WiFiMulti.cpp b/libraries/WiFi/src/WiFiMulti.cpp index 730850333f1..9db3d92e338 100644 --- a/libraries/WiFi/src/WiFiMulti.cpp +++ b/libraries/WiFi/src/WiFiMulti.cpp @@ -90,10 +90,19 @@ 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 (_bTestConnection && !_bWFMInit){ + if (testConnection()) { + _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); delay(10); @@ -104,10 +113,12 @@ uint8_t WiFiMulti::run(uint32_t connectTimeout) 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, NULL }; int bestNetworkDb = INT_MIN; + int bestNetworkSec; uint8_t bestBSSID[6]; int32_t bestChannel = 0; @@ -117,6 +128,9 @@ uint8_t WiFiMulti::run(uint32_t connectTimeout) 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,21 +141,45 @@ uint8_t WiFiMulti::run(uint32_t connectTimeout) WiFi.getNetworkInfo(i, ssid_scan, sec_scan, rssi_scan, BSSID_scan, chan_scan); + if (_bAllowOpenAP && sec_scan == WIFI_AUTH_OPEN){ + bool found = false; + for(uint32_t o = 0; o < APlist.size(); o++) { + WifiAPlist_t check = APlist[o]; + if (ssid_scan == check.ssid){ + found = true; + } + } + if (!found){ + log_i("[WIFI] added %s to APList", ssid_scan); + APlistAdd(ssid_scan.c_str()); + } + } + bool known = false; 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)); + + if(ssid_scan == entry.ssid) { // It's on the list + log_i("known ssid: %s, failed: %i", entry.ssid, entry.fail); + foundCount++; + if (!entry.fail){ + known = true; + log_i("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_i("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; } } @@ -151,21 +189,31 @@ 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_i("foundCount = %d, failCount = %d", foundCount, failCount); + + if (foundCount == failCount){ + failCount = 0; + resetFails(); + } + foundCount = 0; } - + // clean up ram WiFi.scanDelete(); if(bestNetwork.ssid) { - log_i("[WIFI] Connecting BSSID: %02X:%02X:%02X:%02X:%02X:%02X SSID: %s Channal: %d (%d)", bestBSSID[0], bestBSSID[1], bestBSSID[2], bestBSSID[3], bestBSSID[4], bestBSSID[5], bestNetwork.ssid, bestChannel, bestNetworkDb); - - WiFi.begin(bestNetwork.ssid, bestNetwork.passphrase, bestChannel, bestBSSID); + log_i("[WIFI] Connecting BSSID: %02X:%02X:%02X:%02X:%02X:%02X SSID: %s Channel: %d (%d)", bestBSSID[0], bestBSSID[1], bestBSSID[2], bestBSSID[3], bestBSSID[4], bestBSSID[5], bestNetwork.ssid, bestChannel, bestNetworkDb); + + WiFi.disconnect(); + delay(100); + 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) { - delay(10); + while(status != WL_CONNECTED && status != WL_NO_SSID_AVAIL && (millis() - startTime) <= connectTimeout) { // && status != WL_CONNECT_FAILED + delay(500); status = WiFi.status(); } @@ -176,20 +224,39 @@ 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 (_bTestConnection){ + // We connected to an AP but if it's a captive portal we're not going anywhere. Test it. + if (testConnection()){ + resetFails(); + } else { + markAsFailed(bestIndex); + WiFi.disconnect(); + delay(100); + 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 { log_e("[WIFI] no matching wifi found!"); } + delay(2000); //letting wifi stabilize... } else { // start scan log_d("[WIFI] delete old wifi config..."); @@ -199,6 +266,143 @@ uint8_t WiFiMulti::run(uint32_t connectTimeout) // scan wifi async mode WiFi.scanNetworks(true); } - return status; } + +void WiFiMulti::markAsFailed(int32_t i) { + APlist[i].fail = true; + log_i("marked %s as failed",APlist[i].ssid); +} + +void WiFiMulti::resetFails(){ + log_i("resetting failure flags"); + for(uint32_t i = 0; i < APlist.size(); i++) { + APlist[i].fail = false; + } +} + +void WiFiMulti::setStrictMode(bool bStrict) { + _bStrict = bStrict; +} + +void WiFiMulti::setAllowOpenAP(bool bAllowOpenAP) { + _bAllowOpenAP = bAllowOpenAP; +} + +void WiFiMulti::setTestConnection(bool bTestConnection){ + _bTestConnection = bTestConnection; +} +void WiFiMulti::setTestPhrase(const char* testPhrase){ + _testPhrase = testPhrase; +} +void WiFiMulti::setTestURL(String testURL){ + _testURL = testURL; +} + +bool WiFiMulti::testConnection(){ + //parse url + int8_t split = _testURL.indexOf('/',7); + String host = _testURL.substring(7, split); + String url = (split < 0) ? "/":_testURL.substring(split,_testURL.length()); + log_i("Testing connection to %s. Test phrase is \"%s\"",_testURL.c_str(), _testPhrase.c_str()); + // Use WiFiClient class to create TCP connections + WiFiClient client; + const int httpPort = 80; + if (!client.connect(host.c_str(), httpPort)) { + log_e("Connection failed"); + return false; + } else { + log_i("Connected to test host"); + } + + // This will send the request to the server + client.print(String("GET ") + url + " HTTP/1.1\r\n" + + "Host: " + host + "\r\n" + + "Connection: close\r\n\r\n"); + unsigned long timeout = millis(); + while (client.available() == 0) { + if (millis() - timeout > 5000) { + log_e(">>>Client timeout!"); + client.stop(); + return false; + } + } + + bool bSuccess = false; + timeout = millis(); + while(client.available()) { + if (millis() - timeout < 5000) { + bSuccess = client.find(_testPhrase.c_str()); + if (bSuccess){ + log_i("Success. Found test phrase"); + } else { + log_e("Failed. Can't find test phrase"); + } + return bSuccess; + } else { + log_e("Test phrase check timed out!"); + break; + } + } +} + +// ################################################################################## + +bool WiFiMulti::APlistAdd(const char* ssid, const char *passphrase) +{ + + WifiAPlist_t newAP; + + if(!ssid || *ssid == 0x00 || strlen(ssid) > 31) { + // fail SSID to long or missing! + log_e("[WIFI][APlistAdd] no ssid or ssid too long"); + return false; + } + + if(passphrase && strlen(passphrase) > 63) { + // fail passphrase to long! + log_e("[WIFI][APlistAdd] passphrase too long"); + return false; + } + + newAP.ssid = strdup(ssid); + + if(!newAP.ssid) { + log_e("[WIFI][APlistAdd] fail newAP.ssid == 0"); + return false; + } + + if(passphrase && *passphrase != 0x00) { + newAP.passphrase = strdup(passphrase); + if(!newAP.passphrase) { + log_e("[WIFI][APlistAdd] fail newAP.passphrase == 0"); + free(newAP.ssid); + return false; + } + } else { + newAP.passphrase = NULL; + } + + newAP.fail = false; + + APlist.push_back(newAP); + log_i("[WIFI][APlistAdd] added SSID: %s", newAP.ssid); + return true; +} + +void WiFiMulti::APlistClean(void) +{ + for(uint32_t i = 0; i < APlist.size(); i++) { + WifiAPlist_t entry = APlist[i]; + if(entry.ssid) { + free(entry.ssid); + } + if(entry.passphrase) { + free(entry.passphrase); + } + //why doesn't this work??? + //free(entry.fail); + } + APlist.clear(); +} + diff --git a/libraries/WiFi/src/WiFiMulti.h b/libraries/WiFi/src/WiFiMulti.h index 38ddb5d9f95..1a77f20c7fd 100644 --- a/libraries/WiFi/src/WiFiMulti.h +++ b/libraries/WiFi/src/WiFiMulti.h @@ -32,6 +32,7 @@ typedef struct { char * ssid; char * passphrase; + bool fail; } WifiAPlist_t; class WiFiMulti @@ -42,10 +43,31 @@ class WiFiMulti bool addAP(const char* ssid, const char *passphrase = NULL); + void setStrictMode(bool bStrict); + void setAllowOpenAP(bool bAllowOpenAP); + void setTestConnection(bool bTestConnection); + void setTestPhrase(const char* testPhrase); + void setTestURL(String url); + uint8_t run(uint32_t connectTimeout=5000); + void APlistClean(void); + private: std::vector APlist; + + bool _bStrict = true; + bool _bAllowOpenAP = false; + bool _bTestConnection = false; + String _testPhrase = "301 Moved"; + String _testURL = "http://www.amazon.com"; + bool _bWFMInit = false; + + void markAsFailed(int32_t i); + void resetFails(); + + bool testConnection(); + bool APlistAdd(const char* ssid, const char *passphrase = NULL); }; #endif /* WIFICLIENTMULTI_H_ */