/************************************************************************************************************************************** ESP8266WiFiMulti_Impl.h For any WiFi shields, such as ESP32, ESP8266, Portenta_H7, WiFiNINA W101, W102, W13x, or custom, such as ESP8266/ESP32-AT, etc WiFiMulti_Generic is a library to adapt the ESP32/ESP8266 WiFiMulti feature to other WiFi modules Based on and modified from WiFiMulti of ESP32 core: https://github.com/espressif/arduino-esp32 Based on and modified from WiFiMulti of ESP8266 core: https://github.com/esp8266/Arduino Built by Khoi Hoang https://github.com/khoih-prog/WiFiMulti_Generic License under GPL-3.0 This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see Original author: @file ESP8266WiFiMulti.h @date 16.05.2015 @author Markus Sattler Copyright (c) 2015 Markus Sattler. All rights reserved. This file is part of the esp32 core for Arduino environment. Version: 1.2.2 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.0.0 K Hoang 15/02/2020 Initial coding for ESP32, ESP8266, WiFiNINA and ESP_AT modules 1.1.0 K Hoang 16/02/2020 Add support to WiFi101 and many more boards (PortentaH7, megaAVR, Sparkfun SAMD, etc.) 1.1.1 K Hoang 26/04/2020 Fix bug 1.2.0 K Hoang 12/08/2022 Add support to RASPBERRY_PI_PICO_W using CYW4343 WiFi 1.2.1 K Hoang 16/08/2022 Workaround for RP2040W WiFi.status() bug 1.2.2 K Hoang 16/08/2022 Better workaround for RP2040W WiFi.status() bug using ping() to local gateway ***************************************************************************************************************************************/ #pragma once #ifndef _ESP8266_WIFIMULTI_GENERIC_IMPL_H_ #define _ESP8266_WIFIMULTI_GENERIC_IMPL_H_ #include "PolledTimeout.h" #include "WiFiMulti_Generic.h" #include #include /** @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(); WFM_LOGDEBUG("Connected:"); WFM_LOGDEBUG3("SSID:", WiFi.SSID().c_str(), ", IP:", ip); WFM_LOGDEBUG5_SEPARATOR(":", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); WFM_LOGDEBUG3("Channel:", WiFi.channel(), ", RSSI:", WiFi.RSSI()); break; case WL_NO_SSID_AVAIL: WFM_LOGDEBUG("Connecting failed AP not found."); break; case WL_CONNECT_FAILED: WFM_LOGDEBUG("Connecting failed."); break; case WL_WRONG_PASSWORD: WFM_LOGDEBUG("Wrong password."); break; default: WFM_LOGDEBUG1("Connecting failed, status =", 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(const 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) { WFM_LOGDEBUG("Connect failed"); // Return WiFi connect failed return WL_CONNECT_FAILED; } } while (!connectTimeout); WFM_LOGDEBUG("Connect timeout"); return WL_CONNECT_FAILED; } /** @brief Constructor */ WiFiMulti_Generic::WiFiMulti_Generic() : _firstRun(true) { } /** @brief Destructor */ WiFiMulti_Generic::~WiFiMulti_Generic() { // 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 WiFiMulti_Generic::addAP(const char *ssid, const char *passphrase) { return APlistAdd(ssid, passphrase); } /** @brief Remove all Access Points from list */ void WiFiMulti_Generic::cleanAPlist() { APlistClean(); } /** @brief Check if Access Point exists in list @param ssid WiFi SSID @param passphrase WiFi Password @retval true Success @retval false Failure */ bool WiFiMulti_Generic::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 WiFiMulti_Generic::run(const 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())) { WFM_LOGDEBUG("Connecting saved WiFi"); // 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 WiFiMulti_Generic::startScan() { int8_t scanResult; WFM_LOGDEBUG("Start scan"); // 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) { WFM_LOGDEBUG("Scan timeout"); 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 connection status */ wl_status_t WiFiMulti_Generic::connectWiFiMulti(const 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 WFM_LOGDEBUG("Sorted indices: "); for (int8_t i = 0; i < numNetworks; i++) { WFM_LOGDEBUG1("Known networks index =", known[i]); } //WFM_LOGDEBUG0("\n"); // Create indices for AP connection failures uint8_t connectSkipIndex[_APlist.size()]; memset(connectSkipIndex, 0, sizeof(connectSkipIndex)); // 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 (uint8_t j = 0; j < _APlist.size(); j++) { auto &entry = _APlist[j]; // Check SSID if (ssid == entry.ssid) { WFM_LOGDEBUG1("Connecting SSID =", ssid); // Connect to WiFi WiFi.begin(ssid, entry.passphrase, channel, bssid); // Wait for status change if (waitWiFiConnect(connectTimeoutMs) == WL_CONNECTED) { return WL_CONNECTED; } // Failed to connect, skip for hidden SSID connects connectSkipIndex[j] = true; } } } // Try to connect to hidden AP's which are not reported by WiFi scan for (uint8_t i = 0; i < _APlist.size(); i++) { auto &entry = _APlist[i]; if (!connectSkipIndex[i]) { WFM_LOGDEBUG1("Try hidden connect SSID =", entry.ssid); // Connect to WiFi WiFi.begin(entry.ssid, entry.passphrase); // Wait for status change if (waitWiFiConnect(connectTimeoutMs) == WL_CONNECTED) { return WL_CONNECTED; } } } WFM_LOGDEBUG1("Could not connect, SSID =", ssid); // Could 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 WiFiMulti_Generic::APlistAdd(const char *ssid, const char *passphrase) { WifiAPEntry newAP; if (!ssid || (*ssid == 0x00) || (strlen(ssid) > 31)) { // Fail SSID too long or missing! WFM_LOGDEBUG("[APlistAdd] No ssid or ssid too long"); return false; } // For passphrase, max is 63 ascii + null. For psk, 64hex + null. if (passphrase && (strlen(passphrase) > 64)) { // fail passphrase too long! WFM_LOGDEBUG("[APlistAdd] Passphrase too long"); return false; } if (APlistExists(ssid, passphrase)) { WFM_LOGDEBUG1("[APlistAdd] SSID: Already exists, SSID =", ssid); return true; } newAP.ssid = strdup(ssid); if (!newAP.ssid) { WFM_LOGDEBUG("[APlistAdd] Fail newAP.ssid == 0"); return false; } if (passphrase) { newAP.passphrase = strdup(passphrase); } else { newAP.passphrase = strdup(""); } if (!newAP.passphrase) { WFM_LOGDEBUG("[APlistAdd] Fail newAP.passphrase == 0"); free(newAP.ssid); return false; } _APlist.push_back(newAP); WFM_LOGDEBUG1("[APlistAdd] Add SSID: SSID =", newAP.ssid); return true; } /** @brief Check if AP exists in list @param ssid WiFi SSID @param passphrase WiFi Password @return */ bool WiFiMulti_Generic::APlistExists(const char *ssid, const char *passphrase) { if (!ssid || (*ssid == 0x00) || (strlen(ssid) > 31)) { // Fail SSID too long or missing WFM_LOGDEBUG("[APlistExists] No ssid or ssid too long"); 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 WiFiMulti_Generic::APlistClean() { // 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 WiFiMulti_Generic::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(); WFM_LOGDEBUG1("Number of networks found =", 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) { WFM_LOGDEBUG3(" Known => #", i, ", Channel:", channel); } else { WFM_LOGDEBUG3("Unknown => #", i, ", Channel:", chan_scan); } WFM_HEXLOGDEBUG5_SEPARATOR(":", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]); WFM_LOGDEBUG5("SSID:", ssid.c_str(), ", RSSI:", rssi, ", Secured:", (encryptionType == ENC_TYPE_NONE) ? 'n' : 'y'); delay(0); } #endif } #endif // _ESP8266_WIFIMULTI_GENERIC_IMPL_H_