1
0
mirror of https://github.com/esp8266/Arduino.git synced 2025-07-02 14:22:55 +03:00

Redesign ESP8266WiFiMulti.[cpp|h]

Fixed critical issues WiFiMulti library:
- WiFi scan timeout implemented to prevent endless connect loop
- Fallback implemented on WiFi connect failure to prevent endless loop
- Fast WiFi connection at startup
- Improved debug prints
- Doxygen added
- Code maturing
- Example update

Make functions not related to ESP8266WiFiMulti class static
Revert static functions startScan and printWiFiScan()
Use PolledTimeout.h to protect while loops
Move static functions beginning of the file
Add connect timeout to example
This commit is contained in:
Erriez
2020-09-30 21:44:42 +02:00
parent cc042b99d1
commit fceb390a1a
3 changed files with 626 additions and 349 deletions

View File

@ -1,267 +1,512 @@
/**
*
* @file ESP8266WiFiMulti.cpp
* @date 16.05.2015
* @author Markus Sattler
*
* Copyright (c) 2015 Markus Sattler. All rights reserved.
* This file is part of the esp8266 core for Arduino environment.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "ESP8266WiFiMulti.h"
#include <limits.h>
#include <string.h>
ESP8266WiFiMulti::ESP8266WiFiMulti() {
}
ESP8266WiFiMulti::~ESP8266WiFiMulti() {
APlistClean();
}
bool ESP8266WiFiMulti::addAP(const char* ssid, const char *passphrase) {
return APlistAdd(ssid, passphrase);
}
void ESP8266WiFiMulti::cleanAPlist(void) {
APlistClean();
}
bool ESP8266WiFiMulti::existsAP(const char* ssid, const char *passphrase) {
return APlistExists(ssid, passphrase);
}
wl_status_t ESP8266WiFiMulti::run(uint32_t connectTimeoutMs) {
wl_status_t status = WiFi.status();
if(status == WL_DISCONNECTED || status == WL_NO_SSID_AVAIL || status == WL_IDLE_STATUS || status == WL_CONNECT_FAILED) {
int8_t scanResult = WiFi.scanComplete();
if(scanResult == WIFI_SCAN_RUNNING) {
// scan is running, do nothing yet
status = WL_NO_SSID_AVAIL;
return status;
}
if(scanResult == 0) {
// scan done, no ssids found. Start another scan.
DEBUG_WIFI_MULTI("[WIFI] scan done\n");
DEBUG_WIFI_MULTI("[WIFI] no networks found\n");
WiFi.scanDelete();
DEBUG_WIFI_MULTI("\n\n");
delay(0);
WiFi.disconnect();
DEBUG_WIFI_MULTI("[WIFI] start scan\n");
// scan wifi async mode
WiFi.scanNetworks(true);
return status;
}
if(scanResult > 0) {
// scan done, analyze
WifiAPEntry bestNetwork { NULL, NULL };
int bestNetworkDb = INT_MIN;
uint8 bestBSSID[6];
int32_t bestChannel;
DEBUG_WIFI_MULTI("[WIFI] scan done\n");
delay(0);
DEBUG_WIFI_MULTI("[WIFI] %d networks found\n", scanResult);
for(int8_t i = 0; i < scanResult; ++i) {
String ssid_scan;
int32_t rssi_scan;
uint8_t sec_scan;
uint8_t* BSSID_scan;
int32_t chan_scan;
bool hidden_scan;
WiFi.getNetworkInfo(i, ssid_scan, sec_scan, rssi_scan, BSSID_scan, chan_scan, hidden_scan);
bool known = false;
for(auto entry : APlist) {
if(ssid_scan == entry.ssid) { // SSID match
known = true;
if(rssi_scan > bestNetworkDb) { // best network
if(sec_scan == ENC_TYPE_NONE || entry.passphrase) { // check for passphrase if not open wlan
bestNetworkDb = rssi_scan;
bestChannel = chan_scan;
bestNetwork = entry;
memcpy((void*) &bestBSSID, (void*) BSSID_scan, sizeof(bestBSSID));
}
}
break;
}
}
if(known) {
DEBUG_WIFI_MULTI(" ---> ");
} else {
DEBUG_WIFI_MULTI(" ");
}
DEBUG_WIFI_MULTI(" %d: [%d][%02X:%02X:%02X:%02X:%02X:%02X] %s (%d) %c\n", 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 == ENC_TYPE_NONE) ? ' ' : '*');
delay(0);
}
// clean up ram
WiFi.scanDelete();
DEBUG_WIFI_MULTI("\n\n");
delay(0);
if(bestNetwork.ssid) {
DEBUG_WIFI_MULTI("[WIFI] Connecting BSSID: %02X:%02X:%02X:%02X:%02X:%02X SSID: %s Channel: %d (%d)\n", bestBSSID[0], bestBSSID[1], bestBSSID[2], bestBSSID[3], bestBSSID[4], bestBSSID[5], bestNetwork.ssid, bestChannel, bestNetworkDb);
WiFi.begin(bestNetwork.ssid, bestNetwork.passphrase, bestChannel, bestBSSID);
status = WiFi.status();
auto startTime = millis();
// wait for connection, fail, or timeout
while(status != WL_CONNECTED && status != WL_NO_SSID_AVAIL && status != WL_CONNECT_FAILED && (millis() - startTime) <= connectTimeoutMs) {
delay(10);
status = WiFi.status();
}
#ifdef DEBUG_ESP_WIFI
IPAddress ip;
uint8_t * mac;
switch(status) {
case WL_CONNECTED:
ip = WiFi.localIP();
mac = WiFi.BSSID();
DEBUG_WIFI_MULTI("[WIFI] Connecting done.\n");
DEBUG_WIFI_MULTI("[WIFI] SSID: %s\n", WiFi.SSID().c_str());
DEBUG_WIFI_MULTI("[WIFI] IP: %d.%d.%d.%d\n", ip[0], ip[1], ip[2], ip[3]);
DEBUG_WIFI_MULTI("[WIFI] MAC: %02X:%02X:%02X:%02X:%02X:%02X\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
DEBUG_WIFI_MULTI("[WIFI] Channel: %d\n", WiFi.channel());
break;
case WL_NO_SSID_AVAIL:
DEBUG_WIFI_MULTI("[WIFI] Connecting Failed AP not found.\n");
break;
case WL_CONNECT_FAILED:
DEBUG_WIFI_MULTI("[WIFI] Connecting Failed.\n");
break;
default:
DEBUG_WIFI_MULTI("[WIFI] Connecting Failed (%d).\n", status);
break;
}
#endif
} else {
DEBUG_WIFI_MULTI("[WIFI] no matching wifi found!\n");
}
return status;
}
// scan failed, or some other condition not handled above. Start another scan.
DEBUG_WIFI_MULTI("[WIFI] delete old wifi config...\n");
WiFi.disconnect();
DEBUG_WIFI_MULTI("[WIFI] start scan\n");
// scan wifi async mode
WiFi.scanNetworks(true);
}
return status;
}
// ##################################################################################
bool ESP8266WiFiMulti::APlistAdd(const char* ssid, const char *passphrase) {
WifiAPEntry newAP;
if(!ssid || *ssid == 0x00 || strlen(ssid) > 32) {
// fail SSID too long or missing!
DEBUG_WIFI_MULTI("[WIFI][APlistAdd] no ssid or ssid too long\n");
return false;
}
//for passphrase, max is 63 ascii + null. For psk, 64hex + null.
if(passphrase && strlen(passphrase) > 64) {
// fail passphrase too long!
DEBUG_WIFI_MULTI("[WIFI][APlistAdd] passphrase too long\n");
return false;
}
if(APlistExists(ssid, passphrase)) {
DEBUG_WIFI_MULTI("[WIFI][APlistAdd] SSID: %s already exists\n", ssid);
return true;
}
newAP.ssid = strdup(ssid);
if(!newAP.ssid) {
DEBUG_WIFI_MULTI("[WIFI][APlistAdd] fail newAP.ssid == 0\n");
return false;
}
if(passphrase) {
newAP.passphrase = strdup(passphrase);
} else {
newAP.passphrase = strdup("");
}
if(!newAP.passphrase) {
DEBUG_WIFI_MULTI("[WIFI][APlistAdd] fail newAP.passphrase == 0\n");
free(newAP.ssid);
return false;
}
APlist.push_back(newAP);
DEBUG_WIFI_MULTI("[WIFI][APlistAdd] add SSID: %s\n", newAP.ssid);
return true;
}
bool ESP8266WiFiMulti::APlistExists(const char* ssid, const char *passphrase) {
if(!ssid || *ssid == 0x00 || strlen(ssid) > 32) {
// fail SSID too long or missing!
DEBUG_WIFI_MULTI("[WIFI][APlistExists] no ssid or ssid too long\n");
return false;
}
for(auto entry : APlist) {
if(!strcmp(entry.ssid, ssid)) {
if(!passphrase) {
if(!strcmp(entry.passphrase, "")) {
return true;
}
} else {
if(!strcmp(entry.passphrase, passphrase)) {
return true;
}
}
}
}
return false;
}
void ESP8266WiFiMulti::APlistClean(void) {
for(auto entry : APlist) {
if(entry.ssid) {
free(entry.ssid);
}
if(entry.passphrase) {
free(entry.passphrase);
}
}
APlist.clear();
}
/**
*
* @file ESP8266WiFiMulti.cpp
* @date 30.09.2020
* @author Markus Sattler, Erriez
*
* Copyright (c) 2015-2020 Markus Sattler. All rights reserved.
* This file is part of the esp8266 core for Arduino environment.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "PolledTimeout.h"
#include "ESP8266WiFiMulti.h"
#include <limits.h>
#include <string.h>
/**
* @brief Print WiFi status
* @details
* Macro DEBUG_ESP_WIFI and DEBUG_ESP_PORT must be configured
* @param status
* WiFi status
*/
static void printWiFiStatus(wl_status_t status)
{
#ifdef DEBUG_ESP_WIFI
IPAddress ip;
uint8_t *mac;
switch (status) {
case WL_CONNECTED:
ip = WiFi.localIP();
mac = WiFi.BSSID();
DEBUG_WIFI_MULTI("[WIFIM] Connected:\n");
DEBUG_WIFI_MULTI("[WIFIM] SSID: %s\n", WiFi.SSID().c_str());
DEBUG_WIFI_MULTI("[WIFIM] IP: %d.%d.%d.%d\n", ip[0], ip[1], ip[2], ip[3]);
DEBUG_WIFI_MULTI("[WIFIM] MAC: %02X:%02X:%02X:%02X:%02X:%02X\n",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
DEBUG_WIFI_MULTI("[WIFIM] CH: %d\n", WiFi.channel());
DEBUG_WIFI_MULTI("[WIFIM] RSSI: %d\n", WiFi.RSSI());
break;
case WL_NO_SSID_AVAIL:
DEBUG_WIFI_MULTI("[WIFIM] Connecting failed AP not found.\n");
break;
case WL_CONNECT_FAILED:
DEBUG_WIFI_MULTI("[WIFIM] Connecting failed.\n");
break;
default:
DEBUG_WIFI_MULTI("[WIFIM] Connecting failed (%d).\n", status);
break;
}
#else
// Suppress warning unused variable
(void)(status);
#endif
}
/**
* @brief Wait for WiFi connect status change, protected with timeout
* @param connectTimeoutMs
* WiFi connection timeout in ms
* @return
* WiFi connection status
*/
static wl_status_t waitWiFiConnect(uint32_t connectTimeoutMs)
{
wl_status_t status;
// Set WiFi connect timeout
using esp8266::polledTimeout::oneShotMs;
oneShotMs connectTimeout(connectTimeoutMs);
// Wait for WiFi status change or timeout
do {
// Refresh watchdog
delay(0);
// Get WiFi status
status = WiFi.status();
// Check status
if (status == WL_CONNECTED) {
// Connected, print WiFi status
printWiFiStatus(status);
// Return WiFi status
return status;
} else if (status == WL_CONNECT_FAILED) {
DEBUG_WIFI_MULTI("[WIFIM] Connect failed\n");
// Return WiFi connect failed
return WL_CONNECT_FAILED;
}
} while (!connectTimeout);
DEBUG_WIFI_MULTI("[WIFIM] Connect timeout\n");
return WL_CONNECT_FAILED;
}
/**
* @brief Constructor
*/
ESP8266WiFiMulti::ESP8266WiFiMulti() : _firstRun(true)
{
}
/**
* @brief Destructor
*/
ESP8266WiFiMulti::~ESP8266WiFiMulti()
{
// Cleanup memory
APlistClean();
}
/**
* @brief Add Access Point
* @param ssid
* WiFi SSID char array, max 32 characters + NULL character
* @param passphrase
* WiFi password char array, max 63 characters + NULL character
* @retval true
* Success
* @retval false
* Failure
*/
bool ESP8266WiFiMulti::addAP(const char *ssid, const char *passphrase)
{
return APlistAdd(ssid, passphrase);
}
/**
* @brief Remove all Access Points from list
*/
void ESP8266WiFiMulti::cleanAPlist(void)
{
APlistClean();
}
/**
* @brief Check if Access Point exists in list
* @param ssid
* WiFi SSID
* @param passphrase
* WiFi Password
* @retval true
* Success
* @retval false
* Failure
*/
bool ESP8266WiFiMulti::existsAP(const char *ssid, const char *passphrase)
{
return APlistExists(ssid, passphrase);
}
/**
* @brief Keep WiFi connected to Access Point with strongest WiFi signal (RSSI)
* @param connectTimeoutMs
* Timeout in ms per WiFi connection (excluding fixed 5 seconds scan timeout)
* @return
* WiFi status
*/
wl_status_t ESP8266WiFiMulti::run(uint32_t connectTimeoutMs)
{
int8_t scanResult;
wl_status_t status;
// Fast connect to previous WiFi on startup
if (_firstRun) {
_firstRun = false;
// Check if previous WiFi connection saved
if (strlen(WiFi.SSID().c_str())) {
DEBUG_WIFI_MULTI("[WIFIM] Connecting saved WiFi\n");
// Connect to previous saved WiFi
WiFi.begin();
// Wait for status change
status = waitWiFiConnect(connectTimeoutMs);
}
}
// Check connection state
status = WiFi.status();
if (status == WL_CONNECTED) {
// Already connected
return status;
}
// Start WiFi scan
scanResult = startScan();
if (scanResult < 0) {
// No WiFi scan results
return WL_NO_SSID_AVAIL;
}
// Try to connect to multiple WiFi's with strongest signal (RSSI)
return connectWiFiMulti(connectTimeoutMs);
}
/**
* @brief Start WiFi scan
* @retval >0
* Number of detected WiFi SSID's
* @retval 0
* No WiFi connections found
* @retval -2
* WiFi scan failed
*/
int8_t ESP8266WiFiMulti::startScan()
{
int8_t scanResult;
DEBUG_WIFI_MULTI("[WIFIM] Start scan\n");
// Clean previous scan
WiFi.scanDelete();
// Remove previous WiFi SSID/password
WiFi.disconnect();
// Start wifi scan in async mode
WiFi.scanNetworks(true);
// Set WiFi scan timeout
using esp8266::polledTimeout::oneShotMs;
oneShotMs scanTimeout(WIFI_SCAN_TIMEOUT_MS);
// Wait for WiFi scan change or timeout
do {
// Refresh watchdog
delay(0);
// Check scan timeout which may occur when scan does not report completion
if (scanTimeout) {
DEBUG_WIFI_MULTI("[WIFIM] Scan timeout\n");
return WIFI_SCAN_FAILED;
}
// Get scan result
scanResult = WiFi.scanComplete();
} while (scanResult < 0);
// Print WiFi scan result
printWiFiScan();
// Return (positive) number of detected WiFi networks
return scanResult;
}
/**
* @brief Connect to multiple WiFi's
* @param connectTimeoutMs
* WiFi connect timeout in ms
* @return
* WiFi conneciton status
*/
wl_status_t ESP8266WiFiMulti::connectWiFiMulti(uint32_t connectTimeoutMs)
{
int8_t scanResult;
String ssid;
int32_t rssi;
uint8_t encType;
uint8_t *bssid;
int32_t channel;
bool hidden;
// Get scan results
scanResult = WiFi.scanComplete();
// Find known WiFi networks
uint8_t known[_APlist.size()];
uint8_t numNetworks = 0;
for (int8_t i = 0; i < scanResult; i++) {
// Get network information
WiFi.getNetworkInfo(i, ssid, encType, rssi, bssid, channel, hidden);
// Check if the WiFi network contains an entry in AP list
for (auto entry : _APlist) {
// Check SSID
if (ssid == entry.ssid) {
// Known network
known[numNetworks++] = i;
}
}
}
// Sort WiFi networks by RSSI
for (int i = 0; i < numNetworks; i++) {
for (int j = i + 1; j < numNetworks; j++) {
if (WiFi.RSSI(known[j]) > WiFi.RSSI(known[i])) {
int8_t tmp;
// Swap indices
tmp = known[i];
known[i] = known[j];
known[j] = tmp;
}
}
}
// Print sorted indices
DEBUG_WIFI_MULTI("[WIFIM] Sorted indices: ");
for (int8_t i = 0; i < numNetworks; i++) {
DEBUG_WIFI_MULTI("%d ", known[i]);
}
DEBUG_WIFI_MULTI("\n");
// Connect to known WiFi AP's sorted by RSSI
for (int8_t i = 0; i < numNetworks; i++) {
// Get network information
WiFi.getNetworkInfo(known[i], ssid, encType, rssi, bssid, channel, hidden);
for (auto entry : _APlist) {
// Check SSID
if (ssid == entry.ssid) {
DEBUG_WIFI_MULTI("[WIFIM] Connecting %s\n", ssid);
// Connect to WiFi
WiFi.begin(ssid, entry.passphrase, channel, bssid);
// Wait for status change
if (waitWiFiConnect(connectTimeoutMs) == WL_CONNECTED) {
return WL_CONNECTED;
}
}
}
}
DEBUG_WIFI_MULTI("[WIFIM] Could not connect\n", ssid);
// Coult not connect to any WiFi network
return WL_CONNECT_FAILED;
}
// ##################################################################################
/**
* @brief Add WiFi connection to internal AP list
* @param ssid
* WiFi SSID
* @param passphrase
* WiFi Password
* @retval true
* Success
* @retval false
* Failure
*/
bool ESP8266WiFiMulti::APlistAdd(const char *ssid, const char *passphrase)
{
WifiAPEntry newAP;
if (!ssid || (*ssid == 0x00) || (strlen(ssid) > 32)) {
// Fail SSID too long or missing!
DEBUG_WIFI_MULTI("[WIFIM][APlistAdd] No ssid or ssid too long\n");
return false;
}
// For passphrase, max is 63 ascii + null. For psk, 64hex + null.
if (passphrase && (strlen(passphrase) > 64)) {
// fail passphrase too long!
DEBUG_WIFI_MULTI("[WIFIM][APlistAdd] Passphrase too long\n");
return false;
}
if (APlistExists(ssid, passphrase)) {
DEBUG_WIFI_MULTI("[WIFIM][APlistAdd] SSID: %s already exists\n", ssid);
return true;
}
newAP.ssid = strdup(ssid);
if (!newAP.ssid) {
DEBUG_WIFI_MULTI("[WIFIM][APlistAdd] Fail newAP.ssid == 0\n");
return false;
}
if (passphrase) {
newAP.passphrase = strdup(passphrase);
} else {
newAP.passphrase = strdup("");
}
if (!newAP.passphrase) {
DEBUG_WIFI_MULTI("[WIFIM][APlistAdd] Fail newAP.passphrase == 0\n");
free(newAP.ssid);
return false;
}
_APlist.push_back(newAP);
DEBUG_WIFI_MULTI("[WIFIM][APlistAdd] Add SSID: %s\n", newAP.ssid);
return true;
}
/**
* @brief Check if AP exists in list
* @param ssid
* WiFi SSID
* @param passphrase
* WiFi Password
* @return
*/
bool ESP8266WiFiMulti::APlistExists(const char *ssid, const char *passphrase)
{
if (!ssid || (*ssid == 0x00) || (strlen(ssid) > 32)) {
// Fail SSID too long or missing
DEBUG_WIFI_MULTI("[WIFIM][APlistExists] No ssid or ssid too long\n");
return false;
}
for (auto entry : _APlist) {
if (!strcmp(entry.ssid, ssid)) {
if (!passphrase) {
if (!strcmp(entry.passphrase, "")) {
return true;
}
} else {
if (!strcmp(entry.passphrase, passphrase)) {
return true;
}
}
}
}
return false;
}
/**
* @brief Remove all AP's from list
*/
void ESP8266WiFiMulti::APlistClean(void)
{
// Remove all entries from APlist
for (auto entry : _APlist) {
if (entry.ssid) {
free(entry.ssid);
}
if (entry.passphrase) {
free(entry.passphrase);
}
}
_APlist.clear();
}
/**
* @brief Print WiFi scan results
* @details
* Macro DEBUG_ESP_WIFI and DEBUG_ESP_PORT must be configured
*/
void ESP8266WiFiMulti::printWiFiScan()
{
#ifdef DEBUG_ESP_WIFI
String ssid;
int32_t rssi;
uint8_t encryptionType;
uint8_t* bssid;
int32_t channel;
bool hidden;
int8_t scanResult;
scanResult = WiFi.scanComplete();
DEBUG_WIFI_MULTI("[WIFIM] %d networks found:\n", scanResult);
// Print unsorted scan results
for (int8_t i = 0; i < scanResult; i++) {
bool known = false;
WiFi.getNetworkInfo(i, ssid, encryptionType, rssi, bssid, channel, hidden);
for(auto entry : _APlist) {
if(ssid == entry.ssid) {
// SSID match
known = true;
}
}
if (known) {
DEBUG_WIFI_MULTI(" --->");
} else {
DEBUG_WIFI_MULTI(" ");
}
DEBUG_WIFI_MULTI(" %d: [CH %02d] [%02X:%02X:%02X:%02X:%02X:%02X] %ddBm %c %s\n",
i,
channel,
bssid[0], bssid[1], bssid[2],
bssid[3], bssid[4], bssid[5],
rssi,
(encryptionType == ENC_TYPE_NONE) ? ' ' : '*',
ssid.c_str());
delay(0);
}
#endif
}