Skip to content

Commit f451344

Browse files
committed
feat(wifi): Improves WiFiMulti
1 parent 4766608 commit f451344

File tree

4 files changed

+260
-28
lines changed

4 files changed

+260
-28
lines changed

libraries/WiFi/examples/WiFiMultiAdvanced/.skip.esp32h2

Whitespace-only changes.
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* This sketch tries to connect to the best AP available
3+
* and tests for captive portals on open networks
4+
*
5+
*/
6+
7+
#include <WiFi.h>
8+
#include <WiFiMulti.h>
9+
10+
WiFiMulti wifiMulti;
11+
12+
// This is used to test the Internet connection of connected the AP
13+
// Use a non-302 status code to ensure we bypass captive portals. Can be any text in the webpage.
14+
String _testResp = "301 Moved"; // usually http:// is moves to https:// by a 301 code
15+
// You can also set this to a simple test page on your own server to ensure you can reach it,
16+
// like "http://www.mysite.com/test.html"
17+
String _testURL = "http://www.espressif.com"; // Must include "http://" if testing a HTTP host
18+
const int _testPort = 80; // HTTP port
19+
20+
bool testConnection(){
21+
//parse url
22+
int8_t split = _testURL.indexOf('/',7);
23+
String host = _testURL.substring(7, split);
24+
String url = (split < 0) ? "/":_testURL.substring(split,_testURL.length());
25+
log_i("Testing Connection to %s. Test Respponse is \"%s\"",_testURL.c_str(), _testResp.c_str());
26+
// Use WiFiClient class to create TCP connections
27+
WiFiClient client;
28+
if (!client.connect(host.c_str(), _testPort)) {
29+
log_e("Connection failed");
30+
return false;
31+
} else {
32+
log_i("Connected to test host");
33+
}
34+
35+
// This will send the request to the server
36+
client.print(String("GET ") + url + " HTTP/1.1\r\n" +
37+
"Host: " + host + "\r\n" +
38+
"Connection: close\r\n\r\n");
39+
unsigned long timeout = millis();
40+
while (client.available() == 0) {
41+
if (millis() - timeout > 5000) {
42+
log_e(">>>Client timeout!");
43+
client.stop();
44+
return false;
45+
}
46+
}
47+
48+
bool bSuccess = false;
49+
timeout = millis();
50+
while(client.available()) {
51+
if (millis() - timeout < 5000) {
52+
String line = client.readStringUntil('\r');
53+
Serial.println("=============HTTP RESPONSE=============");
54+
Serial.print(line);
55+
Serial.println("\n=======================================");
56+
57+
bSuccess = client.find(_testResp.c_str());
58+
if (bSuccess){
59+
log_i("Success. Found test response");
60+
} else {
61+
log_e("Failed. Can't find test response");
62+
}
63+
return bSuccess;
64+
} else {
65+
log_e("Test Response checking has timed out!");
66+
break;
67+
}
68+
}
69+
return false; // timeout
70+
}
71+
72+
void setup()
73+
{
74+
Serial.begin(115200);
75+
delay(10);
76+
77+
wifiMulti.addAP("ssid_from_AP_1", "your_password_for_AP_1");
78+
wifiMulti.addAP("ssid_from_AP_2", "your_password_for_AP_2");
79+
wifiMulti.addAP("ssid_from_AP_3", "your_password_for_AP_3");
80+
81+
// These options can help when you need ANY kind of wifi connection to get a config file, report errors, etc.
82+
wifiMulti.setStrictMode(false); // Default is true. Library will disconnect and forget currently connected AP if it's not in the AP list.
83+
wifiMulti.setAllowOpenAP(true); // Default is false. True adds open APs to the AP list.
84+
wifiMulti.setConnectionTestCallbackFunc(testConnection); // Attempts to connect to a remote webserver in case of captive portals.
85+
86+
Serial.println("Connecting Wifi...");
87+
if(wifiMulti.run() == WL_CONNECTED) {
88+
Serial.println("");
89+
Serial.println("WiFi connected");
90+
Serial.println("IP address: ");
91+
Serial.println(WiFi.localIP());
92+
}
93+
}
94+
95+
void loop()
96+
{
97+
static bool isConnected = false;
98+
uint8_t WiFiStatus = wifiMulti.run();
99+
100+
if (WiFiStatus == WL_CONNECTED) {
101+
if (!isConnected) {
102+
Serial.println("");
103+
Serial.println("WiFi connected");
104+
Serial.println("IP address: ");
105+
Serial.println(WiFi.localIP());
106+
}
107+
isConnected = true;
108+
} else {
109+
Serial.println("WiFi not connected!");
110+
isConnected = false;
111+
delay(5000);
112+
}
113+
}

libraries/WiFi/src/WiFiMulti.cpp

Lines changed: 119 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,9 @@ WiFiMulti::WiFiMulti()
3333
ipv6_support = false;
3434
}
3535

36-
WiFiMulti::~WiFiMulti()
36+
void WiFiMulti::APlistClean(void)
3737
{
38-
for(uint32_t i = 0; i < APlist.size(); i++) {
39-
WifiAPlist_t entry = APlist[i];
38+
for(auto entry : APlist) {
4039
if(entry.ssid) {
4140
free(entry.ssid);
4241
}
@@ -47,17 +46,22 @@ WiFiMulti::~WiFiMulti()
4746
APlist.clear();
4847
}
4948

49+
WiFiMulti::~WiFiMulti()
50+
{
51+
APlistClean();
52+
}
53+
5054
bool WiFiMulti::addAP(const char* ssid, const char *passphrase)
5155
{
5256
WifiAPlist_t newAP;
5357

54-
if(!ssid || *ssid == 0x00 || strlen(ssid) > 31) {
58+
if(!ssid || *ssid == '\0' || strlen(ssid) > 31) {
5559
// fail SSID too long or missing!
5660
log_e("[WIFI][APlistAdd] no ssid or ssid too long");
5761
return false;
5862
}
5963

60-
if(passphrase && strlen(passphrase) > 64) {
64+
if(passphrase && strlen(passphrase) > 63) {
6165
// fail passphrase too long!
6266
log_e("[WIFI][APlistAdd] passphrase too long");
6367
return false;
@@ -70,7 +74,7 @@ bool WiFiMulti::addAP(const char* ssid, const char *passphrase)
7074
return false;
7175
}
7276

73-
if(passphrase && *passphrase != 0x00) {
77+
if(passphrase && *passphrase != '\0') {
7478
newAP.passphrase = strdup(passphrase);
7579
if(!newAP.passphrase) {
7680
log_e("[WIFI][APlistAdd] fail newAP.passphrase == 0");
@@ -80,7 +84,7 @@ bool WiFiMulti::addAP(const char* ssid, const char *passphrase)
8084
} else {
8185
newAP.passphrase = NULL;
8286
}
83-
87+
newAP.hasFailed = false;
8488
APlist.push_back(newAP);
8589
log_i("[WIFI][APlistAdd] add SSID: %s", newAP.ssid);
8690
return true;
@@ -91,9 +95,20 @@ uint8_t WiFiMulti::run(uint32_t connectTimeout)
9195
int8_t scanResult;
9296
uint8_t status = WiFi.status();
9397
if(status == WL_CONNECTED) {
94-
for(uint32_t x = 0; x < APlist.size(); x++) {
95-
if(WiFi.SSID()==APlist[x].ssid) {
98+
if (!_bWFMInit && _connectionTestCBFunc != NULL){
99+
if (_connectionTestCBFunc() == true) {
100+
_bWFMInit = true;
101+
return status;
102+
}
103+
} else {
104+
if (!_bStrict) {
96105
return status;
106+
} else {
107+
for(auto ap : APlist) {
108+
if(WiFi.SSID() == ap.ssid) {
109+
return status;
110+
}
111+
}
97112
}
98113
}
99114
WiFi.disconnect(false,false);
@@ -102,22 +117,27 @@ uint8_t WiFiMulti::run(uint32_t connectTimeout)
102117
}
103118

104119
scanResult = WiFi.scanNetworks();
105-
if(scanResult == WIFI_SCAN_RUNNING) {
120+
if (scanResult == WIFI_SCAN_RUNNING) {
106121
// scan is running
107122
return WL_NO_SSID_AVAIL;
108-
} else if(scanResult >= 0) {
123+
} else if (scanResult >= 0) {
109124
// scan done analyze
110-
WifiAPlist_t bestNetwork { NULL, NULL };
125+
int32_t bestIndex = 0;
126+
WifiAPlist_t bestNetwork { NULL, NULL, false };
111127
int bestNetworkDb = INT_MIN;
128+
int bestNetworkSec;
112129
uint8_t bestBSSID[6];
113130
int32_t bestChannel = 0;
114131

115132
log_i("[WIFI] scan done");
116133

117-
if(scanResult == 0) {
134+
if (scanResult == 0) {
118135
log_e("[WIFI] no networks found");
119136
} else {
120137
log_i("[WIFI] %d networks found", scanResult);
138+
139+
int8_t failCount = 0;
140+
int8_t foundCount = 0;
121141
for(int8_t i = 0; i < scanResult; ++i) {
122142

123143
String ssid_scan;
@@ -127,22 +147,47 @@ uint8_t WiFiMulti::run(uint32_t connectTimeout)
127147
int32_t chan_scan;
128148

129149
WiFi.getNetworkInfo(i, ssid_scan, sec_scan, rssi_scan, BSSID_scan, chan_scan);
150+
// add any Open WiFi AP to the list, if allowed with setAllowOpenAP(true)
151+
if (_bAllowOpenAP && sec_scan == WIFI_AUTH_OPEN){
152+
bool found = false;
153+
for(auto check : APlist) {
154+
if (ssid_scan == check.ssid){
155+
found = true;
156+
break;
157+
}
158+
}
159+
// If we didn't find it, add this Open WiFi AP to the list
160+
if (!found){
161+
log_i("[WIFI][APlistAdd] adding Open WiFi SSID: %s", ssid_scan.c_str());
162+
addAP(ssid_scan.c_str());
163+
}
164+
}
130165

131166
bool known = false;
132-
for(uint32_t x = APlist.size() ; x > 0; x--) {
133-
WifiAPlist_t entry = APlist[x-1];
167+
for(uint32_t x = 0; x < APlist.size(); x++) {
168+
WifiAPlist_t entry = APlist[x];
134169

135170
if(ssid_scan == entry.ssid) { // SSID match
136-
known = true;
137-
if(rssi_scan > bestNetworkDb) { // best network
138-
if(sec_scan == WIFI_AUTH_OPEN || entry.passphrase) { // check for passphrase if not open wlan
139-
bestNetworkDb = rssi_scan;
140-
bestChannel = chan_scan;
141-
memcpy((void*) &bestNetwork, (void*) &entry, sizeof(bestNetwork));
142-
memcpy((void*) &bestBSSID, (void*) BSSID_scan, sizeof(bestBSSID));
171+
log_v("known ssid: %s, has failed: %s", entry.ssid, entry.hasFailed ? "yes" : "no");
172+
foundCount++;
173+
if (!entry.hasFailed){
174+
known = true;
175+
log_v("rssi_scan: %d, bestNetworkDb: %d", rssi_scan, bestNetworkDb);
176+
if(rssi_scan > bestNetworkDb) { // best network
177+
if(_bAllowOpenAP || (sec_scan == WIFI_AUTH_OPEN || entry.passphrase)) { // check for passphrase if not open wlan
178+
log_v("best network is now: %s", ssid_scan);
179+
bestIndex = x;
180+
bestNetworkSec = sec_scan;
181+
bestNetworkDb = rssi_scan;
182+
bestChannel = chan_scan;
183+
memcpy((void*) &bestNetwork, (void*) &entry, sizeof(bestNetwork));
184+
memcpy((void*) &bestBSSID, (void*) BSSID_scan, sizeof(bestBSSID));
185+
}
143186
}
187+
break;
188+
} else {
189+
failCount++;
144190
}
145-
break;
146191
}
147192
}
148193

@@ -152,8 +197,12 @@ uint8_t WiFiMulti::run(uint32_t connectTimeout)
152197
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) ? ' ' : '*');
153198
}
154199
}
200+
log_v("foundCount = %d, failCount = %d", foundCount, failCount);
201+
// if all the APs in the list have failed, reset the failure flags
202+
if (foundCount == failCount) {
203+
resetFails(); // keeps trying the APs in the list
204+
}
155205
}
156-
157206
// clean up ram
158207
WiFi.scanDelete();
159208

@@ -163,12 +212,15 @@ uint8_t WiFiMulti::run(uint32_t connectTimeout)
163212
if (ipv6_support == true) {
164213
WiFi.enableIPv6();
165214
}
166-
WiFi.begin(bestNetwork.ssid, bestNetwork.passphrase, bestChannel, bestBSSID);
215+
WiFi.disconnect();
216+
delay(10);
217+
WiFi.begin(bestNetwork.ssid, (_bAllowOpenAP && bestNetworkSec == WIFI_AUTH_OPEN) ? NULL : bestNetwork.passphrase, bestChannel, bestBSSID);
167218
status = WiFi.status();
219+
_bWFMInit = true;
168220

169221
auto startTime = millis();
170222
// wait for connection, fail, or timeout
171-
while(status != WL_CONNECTED && status != WL_NO_SSID_AVAIL && status != WL_CONNECT_FAILED && (millis() - startTime) <= connectTimeout) {
223+
while(status != WL_CONNECTED && (millis() - startTime) <= connectTimeout) { // && status != WL_NO_SSID_AVAIL && status != WL_CONNECT_FAILED
172224
delay(10);
173225
status = WiFi.status();
174226
}
@@ -180,15 +232,32 @@ uint8_t WiFiMulti::run(uint32_t connectTimeout)
180232
log_d("[WIFI] IP: %s", WiFi.localIP().toString().c_str());
181233
log_d("[WIFI] MAC: %s", WiFi.BSSIDstr().c_str());
182234
log_d("[WIFI] Channel: %d", WiFi.channel());
235+
236+
if (_connectionTestCBFunc != NULL) {
237+
// We connected to an AP but if it's a captive portal we're not going anywhere. Test it.
238+
if (_connectionTestCBFunc()) {
239+
resetFails();
240+
} else {
241+
markAsFailed(bestIndex);
242+
WiFi.disconnect();
243+
delay(10);
244+
status = WiFi.status();
245+
}
246+
} else {
247+
resetFails();
248+
}
183249
break;
184250
case WL_NO_SSID_AVAIL:
185251
log_e("[WIFI] Connecting Failed AP not found.");
252+
markAsFailed(bestIndex);
186253
break;
187254
case WL_CONNECT_FAILED:
188255
log_e("[WIFI] Connecting Failed.");
256+
markAsFailed(bestIndex);
189257
break;
190258
default:
191259
log_e("[WIFI] Connecting Failed (%d).", status);
260+
markAsFailed(bestIndex);
192261
break;
193262
}
194263
} else {
@@ -210,3 +279,27 @@ uint8_t WiFiMulti::run(uint32_t connectTimeout)
210279
void WiFiMulti::enableIPv6(bool state) {
211280
ipv6_support = state;
212281
}
282+
283+
void WiFiMulti::markAsFailed(int32_t i) {
284+
APlist[i].hasFailed = true;
285+
log_d("[WIFI] Marked SSID %s as failed", APlist[i].ssid);
286+
}
287+
288+
void WiFiMulti::resetFails(){
289+
for(uint32_t i = 0; i < APlist.size(); i++) {
290+
APlist[i].hasFailed = false;
291+
}
292+
log_d("[WIFI] Resetting failure flags");
293+
}
294+
295+
void WiFiMulti::setStrictMode(bool bStrict) {
296+
_bStrict = bStrict;
297+
}
298+
299+
void WiFiMulti::setAllowOpenAP(bool bAllowOpenAP) {
300+
_bAllowOpenAP = bAllowOpenAP;
301+
}
302+
303+
void WiFiMulti::setConnectionTestCallbackFunc(ConnectionTestCB_t cbFunc) {
304+
_connectionTestCBFunc = cbFunc;
305+
}

0 commit comments

Comments
 (0)