Skip to content

Commit

Permalink
Improves WiFiMulti (#9139)
Browse files Browse the repository at this point in the history
* feat(wifi): Improves WiFiMulti

* fix(wifi): Fixes Initialization of Security Mode

* feat(wifi): simplifies the example by using HTTPClient

* fix(WiFi): fixes a type in the commentaries
  • Loading branch information
SuGlider committed Jan 23, 2024
1 parent 39043b8 commit cceebb5
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 28 deletions.
Empty file.
64 changes: 64 additions & 0 deletions libraries/WiFi/examples/WiFiMultiAdvanced/WiFiMultiAdvanced.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* This sketch tries to connect to the best AP available
* and tests for captive portals on open networks
*
*/

#include <WiFi.h>
#include <WiFiMulti.h>
#include <HTTPClient.h>

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);
}
}
145 changes: 119 additions & 26 deletions libraries/WiFi/src/WiFiMulti.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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;
Expand All @@ -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");
Expand All @@ -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;
Expand All @@ -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);
Expand All @@ -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;
Expand All @@ -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;
}
}

Expand All @@ -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();

Expand All @@ -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();
}
Expand All @@ -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 {
Expand All @@ -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;
}
30 changes: 28 additions & 2 deletions libraries/WiFi/src/WiFiMulti.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,48 @@
typedef struct {
char * ssid;
char * passphrase;
bool hasFailed;
} WifiAPlist_t;

typedef std::function<bool(void)> ConnectionTestCB_t;

class WiFiMulti
{
public:
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<WifiAPlist_t> 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_ */

0 comments on commit cceebb5

Please sign in to comment.