From c55445697bb18fbaafc5ba425e64b7acd66fe9c7 Mon Sep 17 00:00:00 2001 From: Khoi Hoang <57012152+khoih-prog@users.noreply.github.com> Date: Sun, 25 Apr 2021 02:19:29 -0400 Subject: [PATCH] v1.4.0 to enable scan of WiFi networks ### Major Release v1.4.0 1. Enable scan of WiFi networks for selection in Configuration Portal. Check [PR for v1.3.0 - Enable scan of WiFi networks #10](https://github.com/khoih-prog/WiFiManager_NINA_Lite/pull/10). Now you can select optional **SCAN_WIFI_NETWORKS**, **MANUAL_SSID_INPUT_ALLOWED** to be able to manually input SSID, not only from a scanned SSID lists and **MAX_SSID_IN_LIST** (from 2-15) 2. Fix invalid "blank" Config Data treated as Valid. 3. Permit optionally inputting one set of WiFi SSID/PWD by using `REQUIRE_ONE_SET_SSID_PW == true` 4. Enforce WiFi PWD minimum length of 8 chars 5. Minor enhancement to not display garbage when data is invalid --- README.md | 349 +++++++++++++- .../AM2315_ESP32_SSL/AM2315_ESP32_SSL.ino | 3 +- examples/AM2315_ESP32_SSL/defines.h | 13 + examples/AM2315_ESP8266/AM2315_ESP8266.ino | 3 +- examples/AM2315_ESP8266/defines.h | 13 + .../Blynk_WM_Template/Blynk_WM_Template.ino | 14 +- examples/DHT11ESP32/DHT11ESP32.ino | 3 +- examples/DHT11ESP32/defines.h | 12 + examples/DHT11ESP32_SSL/DHT11ESP32_SSL.ino | 3 +- examples/DHT11ESP32_SSL/defines.h | 13 + examples/DHT11ESP8266/DHT11ESP8266.ino | 3 +- examples/DHT11ESP8266/defines.h | 12 + .../DHT11ESP8266_Debug/DHT11ESP8266_Debug.ino | 3 +- examples/DHT11ESP8266_Debug/defines.h | 13 + .../DHT11ESP8266_SSL/DHT11ESP8266_SSL.ino | 3 +- examples/DHT11ESP8266_SSL/defines.h | 13 + examples/ESP32WM_Config/ESP32WM_Config.ino | 3 +- examples/ESP32WM_Config/defines.h | 13 + .../ESP32WM_ForcedConfig.ino | 3 +- examples/ESP32WM_ForcedConfig/defines.h | 13 + .../ESP32WM_MRD_Config/ESP32WM_MRD_Config.ino | 3 +- examples/ESP32WM_MRD_Config/defines.h | 13 + .../ESP32WM_MRD_ForcedConfig.ino | 3 +- examples/ESP32WM_MRD_ForcedConfig/defines.h | 13 + .../ESP8266WM_Config/ESP8266WM_Config.ino | 3 +- examples/ESP8266WM_Config/defines.h | 13 + .../ESP8266WM_ForcedConfig.ino | 3 +- examples/ESP8266WM_ForcedConfig/defines.h | 13 + .../ESP8266WM_MRD_Config.ino | 3 +- examples/ESP8266WM_MRD_Config/defines.h | 13 + .../ESP8266WM_MRD_ForcedConfig.ino | 3 +- examples/ESP8266WM_MRD_ForcedConfig/defines.h | 13 + library.json | 6 +- library.properties | 4 +- pics/Input_With_Scan.png | Bin 0 -> 31402 bytes pics/Main.png | Bin 29966 -> 29047 bytes pics/Saved.png | Bin 0 -> 35300 bytes src/BlynkSimpleEsp32_SSL_WM.h | 415 ++++++++++++++-- src/BlynkSimpleEsp32_WM.h | 397 +++++++++++++++- src/BlynkSimpleEsp8266_SSL_WM.h | 396 +++++++++++++++- src/BlynkSimpleEsp8266_WM.h | 448 ++++++++++++++++-- 41 files changed, 2090 insertions(+), 177 deletions(-) create mode 100644 pics/Input_With_Scan.png create mode 100644 pics/Saved.png diff --git a/README.md b/README.md index cf454cb..44e7678 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ * [Features](#features) * [Currently supported Boards](#currently-supported-boards) * [Changelog](#changelog) + * [Major Releases v1.4.0](#major-releases-v140) * [Releases v1.3.1](#releases-v131) * [Major Releases v1.3.0](#major-releases-v130) * [Major Releases v1.2.0](#major-releases-v120) @@ -67,6 +68,13 @@ * [ 6. To use custom HTML Style](#6-to-use-custom-html-style) * [ 7. To use custom Head Elements](#7-to-use-custom-head-elements) * [ 8. To use CORS Header](#8-to-use-cors-header) + * [ 9. To use and input only one set of WiFi SSID and PWD](#9-to-use-and-input-only-one-set-of-wifi-ssid-and-pwd) + * [ 9.1 If you need to use and input only one set of WiFi SSID/PWD](#91-if-you-need-to-use-and-input-only-one-set-of-wifi-ssidpwd) + * [ 9.2 If you need to use both sets of WiFi SSID/PWD](#92-if-you-need-to-use-both-sets-of-wifi-ssidpwd) + * [10. To enable auto-scan of WiFi networks for selection in Configuration Portal](#10-to-enable-auto-scan-of-wifi-networks-for-selection-in-configuration-portal) + * [10.1 Enable auto-scan of WiFi networks for selection in Configuration Portal](#101-enable-auto-scan-of-wifi-networks-for-selection-in-configuration-portal) + * [10.2 Disable manually input SSIDs](#102-disable-manually-input-ssids) + * [10.3 Select maximum number of SSIDs in the list](#103-select-maximum-number-of-ssids-in-the-list) * [Important Notes for using Dynamic Parameters' ids](#important-notes-for-using-dynamic-parameters-ids) * [Important Notes](#important-notes) * [Why using this Blynk_WiFiManager with MultiWiFi-MultiBlynk features](#why-using-this-blynk_wifimanager-with-multiwifi-multiblynk-features) @@ -112,6 +120,9 @@ * [6.2. Enter persistent ConfigPortal](#62-enter-persistent-configportal) * [7. ESP8266WM_MRD_ForcedConfig using LITTLEFS with SSL on ESP8266_NODEMCU](#7-esp8266wm_mrd_forcedconfig-using-littlefs-with-ssl-on-esp8266_nodemcu) * [8. ESP32WM_MRD_Config using LITTLEFS with SSL on ESP32S2_DEV](#8-esp32wm_mrd_config-using-littlefs-with-ssl-on-esp32s2_dev) + * [9. ESP32WM_MRD_ForcedConfig using LITTLEFS with SSL on ESP32_DEV to demo WiFi Scan](#9-esp32wm_mrd_forcedconfig-using-littlefs-with-ssl-on-esp32_dev-to-demo-wifi-scan) + * [9.1 MRD/DRD => Open Config Portal](#91-mrddrd--open-config-portal) + * [9.2 Config Data Saved => Connection to Blynk](#92-config-data-saved--connection-to-blynk) * [Debug](#debug) * [Troubleshooting](#troubleshooting) * [Releases](#releases) @@ -189,6 +200,14 @@ This [**BlynkESP32_BT_WF** library](https://github.com/khoih-prog/BlynkESP32_BT_ ## Changelog +### Major Release v1.4.0 + +1. Enable scan of WiFi networks for selection in Configuration Portal. Check [PR for v1.3.0 - Enable scan of WiFi networks #10](https://github.com/khoih-prog/WiFiManager_NINA_Lite/pull/10). Now you can select optional **SCAN_WIFI_NETWORKS**, **MANUAL_SSID_INPUT_ALLOWED** to be able to manually input SSID, not only from a scanned SSID lists and **MAX_SSID_IN_LIST** (from 2-15) +2. Fix invalid "blank" Config Data treated as Valid. +3. Permit optionally inputting one set of WiFi SSID/PWD by using `REQUIRE_ONE_SET_SSID_PW == true` +4. Enforce WiFi PWD minimum length of 8 chars +5. Minor enhancement to not display garbage when data is invalid + ### Releases v1.3.1 1. Fix issue of custom Blynk port (different from 8080 or 9443) not working on ESP32. Check [Custom Blynk port not working for BlynkSimpleEsp32_Async_WM.h #4](https://github.com/khoih-prog/Blynk_Async_WM/issues/4) @@ -286,7 +305,7 @@ Thanks to [Thor Johnson](https://github.com/thorathome) to test, suggest and enc 5. [`ESP8266 Core 2.7.4+`](https://github.com/esp8266/Arduino) for ESP8266-based boards. [![Latest release](https://img.shields.io/github/release/esp8266/Arduino.svg)](https://github.com/esp8266/Arduino/releases/latest/). To use ESP8266 core 2.7.1+ for LittleFS. 6. [`ESP_DoubleResetDetector library 1.1.1+`](https://github.com/khoih-prog/ESP_DoubleResetDetector) to use DRD feature. To install, check [![arduino-library-badge](https://www.ardu-badge.com/badge/ESP_DoubleResetDetector.svg?)](https://www.ardu-badge.com/ESP_DoubleResetDetector). 7. [`ESP_MultiResetDetector library 1.1.1+`](https://github.com/khoih-prog/ESP_MultiResetDetector) to use MRD feature. To install, check [![arduino-library-badge](https://www.ardu-badge.com/badge/ESP_MultiResetDetector.svg?)](https://www.ardu-badge.com/ESP_MultiResetDetector). -8. [`LittleFS_esp32 v1.0.6+`](https://github.com/lorol/LITTLEFS) for ESP32-based boards using LittleFS. To install, check [![arduino-library-badge](https://www.ardu-badge.com/badge/LittleFS_esp32.svg?)](https://www.ardu-badge.com/LittleFS_esp32). +8. [`LittleFS_esp32 v1.0.6+`](https://github.com/lorol/LITTLEFS) for ESP32-based boards using LittleFS. To install, check [![arduino-library-badge](https://www.ardu-badge.com/badge/LittleFS_esp32.svg?)](https://www.ardu-badge.com/LittleFS_esp32). **Notice**: This [`LittleFS_esp32 library`](https://github.com/lorol/LITTLEFS) has been integrated to Arduino [esp32 core v1.0.6](https://github.com/espressif/arduino-esp32/tree/master/libraries/LITTLEFS). --- @@ -843,6 +862,56 @@ Blynk.setCORSHeader("Your Access-Control-Allow-Origin"); ``` +#### 9. To use and input only one set of WiFi SSID and PWD + +#### 9.1 If you need to use and input only one set of WiFi SSID/PWD + +``` +// Permit input only one set of WiFi SSID/PWD. The other can be "NULL or "blank" +// Default is false (if not defined) => must input 2 sets of SSID/PWD +#define REQUIRE_ONE_SET_SSID_PW true +``` +But it's always advisable to use and input both sets for reliability. + +#### 9.2 If you need to use both sets of WiFi SSID/PWD + +``` +// Permit input only one set of WiFi SSID/PWD. The other can be "NULL or "blank" +// Default is false (if not defined) => must input 2 sets of SSID/PWD +#define REQUIRE_ONE_SET_SSID_PW false +``` + +#### 10. To enable auto-scan of WiFi networks for selection in Configuration Portal + +#### 10.1 Enable auto-scan of WiFi networks for selection in Configuration Portal + + +``` +#define SCAN_WIFI_NETWORKS true +``` + +The manual input of SSIDs is default enabled, so that users can input arbitrary SSID, not only from the scanned list. This is for the sample use-cases in which users can input the known SSIDs of another place, then send the boards to that place. The boards can connect to WiFi without users entering Config Portal to re-configure. + +#### 10.2 Disable manually input SSIDs + +``` +// To disable manually input SSID, only from a scanned SSID lists +#define MANUAL_SSID_INPUT_ALLOWED false +``` + +This is for normal use-cases in which users can only select an SSID from a scanned list of SSIDs to avoid typo mistakes and/or security. + +#### 10.3 Select maximum number of SSIDs in the list + +The maximum number of SSIDs in the list is seletable from 2 to 15. If invalid number of SSIDs is selected, the default number of 10 will be used. + + +``` +// From 2-15 +#define MAX_SSID_IN_LIST 8 +``` + + --- --- @@ -962,14 +1031,31 @@ After you connected, please, go to http://192.168.4.1 or the configured AP IP. T

+ Enter your WiFi and Blynk Credentials: +### 1. Without SCAN_WIFI_NETWORKS + +

+ +### 2. With SCAN_WIFI_NETWORKS + + +

+ +

+ + Then click **Save**. The system will auto-restart. You will see the board's built-in LED turned OFF. That means, it's already connected to your Blynk server successfully. +

+ +

+ --- --- @@ -1533,7 +1619,7 @@ The following is the sample terminal output when running example [ESP8266WM_MRD_ ``` Starting ESP8266WM_MRD_Config using LittleFS with SSL on ESP8266_NODEMCU -Blynk_WM SSL for ESP8266 v1.3.1 +Blynk_WM SSL for ESP8266 v1.4.0 ESP_MultiResetDetector v1.1.1 LittleFS Flag read = 0xFFFD0002 multiResetDetectorFlag = 0xFFFD0002 @@ -1613,7 +1699,7 @@ BBBBBB ``` Starting ESP8266WM_MRD_Config using LittleFS with SSL on ESP8266_NODEMCU -Blynk_WM SSL for ESP8266 v1.3.1 +Blynk_WM SSL for ESP8266 v1.4.0 ESP_MultiResetDetector v1.1.1 LittleFS Flag read = 0xFFFC0003 multiResetDetectorFlag = 0xFFFC0003 @@ -1675,7 +1761,7 @@ The following is the sample terminal output when running example [DHT11ESP8266_S ``` Starting DHT11ESP8266_SSL using LittleFS with SSL on ESP8266_NODEMCU -Blynk_WM SSL for ESP8266 v1.3.1 +Blynk_WM SSL for ESP8266 v1.4.0 ESP_DoubleResetDetector v1.1.1 [293] Hostname=ESP8266-DHT11-SSL [316] LoadCfgFile @@ -1733,7 +1819,7 @@ The following is the sample terminal output when running example [ESP32WM_MRD_Co ``` Starting ESP32WM_MRD_Config using LITTLEFS without SSL on ESP32_DEV -Blynk_WM for ESP32 v1.3.1 +Blynk_WM for ESP32 v1.4.0 ESP_MultiResetDetector v1.1.1 LittleFS Flag read = 0xFFFE0001 multiResetDetectorFlag = 0xFFFE0001 @@ -1810,7 +1896,7 @@ BBBBBB ``` Starting ESP32WM_MRD_Config using LITTLEFS without SSL on ESP32_DEV -Blynk_WM for ESP32 v1.3.1 +Blynk_WM for ESP32 v1.4.0 ESP_MultiResetDetector v1.1.1 LittleFS Flag read = 0xFFFC0003 multiResetDetectorFlag = 0xFFFC0003 @@ -1868,7 +1954,7 @@ ets Jun 8 2016 00:22:57 ``` Starting ESP32WM_MRD_Config using LITTLEFS without SSL on ESP32_DEV -Blynk_WM for ESP32 v1.3.1 +Blynk_WM for ESP32 v1.4.0 ESP_MultiResetDetector v1.1.1 LittleFS Flag read = 0xFFFE0001 multiResetDetectorFlag = 0xFFFE0001 @@ -1992,7 +2078,7 @@ The following is the sample terminal output when running example [DHT11ESP8266_S ``` Starting DHT11ESP32_SSL using LITTLEFS with SSL on ESP32_DEV -Blynk_WM SSL for ESP32 v1.3.1 +Blynk_WM SSL for ESP32 v1.4.0 ESP_DoubleResetDetector v1.1.1 [346] Hostname=ESP32-DHT11-SSL [385] LoadCfgFile @@ -2056,7 +2142,7 @@ Blynk.resetAndEnterConfigPortal(); ``` Starting ESP8266WM_MRD_ForcedConfig using LittleFS without SSL on ESP8266_NODEMCU -Blynk_WM for ESP8266 v1.3.1 +Blynk_WM for ESP8266 v1.4.0 ESP_MultiResetDetector v1.1.1 LittleFS Flag read = 0xFFFE0001 multiResetDetectorFlag = 0xFFFE0001 @@ -2142,7 +2228,7 @@ Non-Persistent CP will be removed after first reset, even you didn't enter the C ``` Starting ESP8266WM_MRD_ForcedConfig using LittleFS without SSL on ESP8266_NODEMCU -Blynk_WM for ESP8266 v1.3.1 +Blynk_WM for ESP8266 v1.4.0 ESP_MultiResetDetector v1.1.1 LittleFS Flag read = 0xFFFE0001 multiResetDetectorFlag = 0xFFFE0001 @@ -2224,7 +2310,7 @@ Blynk.resetAndEnterConfigPortalPersistent(); ``` Starting ESP8266WM_MRD_ForcedConfig using LittleFS without SSL on ESP8266_NODEMCU -Blynk_WM for ESP8266 v1.3.1 +Blynk_WM for ESP8266 v1.4.0 ESP_MultiResetDetector v1.1.1 LittleFS Flag read = 0xFFFE0001 multiResetDetectorFlag = 0xFFFE0001 @@ -2311,7 +2397,7 @@ Persistent CP will remain after resets. The only way to get rid of Config Portal ``` Starting ESP8266WM_MRD_ForcedConfig using LittleFS without SSL on ESP8266_NODEMCU -Blynk_WM for ESP8266 v1.3.1 +Blynk_WM for ESP8266 v1.4.0 ESP_MultiResetDetector v1.1.1 LittleFS Flag read = 0xFFFE0001 multiResetDetectorFlag = 0xFFFE0001 @@ -2379,7 +2465,7 @@ The following is the sample terminal output when running example [ESP8266WM_MRD_ ``` Starting ESP8266WM_MRD_ForcedConfig using LittleFS with SSL on ESP8266_NODEMCU -Blynk_WM SSL for ESP8266 v1.3.1 +Blynk_WM SSL for ESP8266 v1.4.0 ESP_MultiResetDetector v1.1.1 [267] Set CustomsStyle to : [289] Set CustomsHeadElement to : @@ -2467,7 +2553,7 @@ The following is the sample terminal output when running example [ESP32WM_MRD_Co ``` Starting ESP32WM_MRD_Config using LITTLEFS with SSL on ESP32S2_DEV -Blynk_WM SSL for ESP32 v1.3.1 +Blynk_WM SSL for ESP32 v1.4.0 ESP_MultiResetDetector v1.1.1 [134394] Set CustomsStyle to : [134417] Set CustomsHeadElement to : @@ -2546,6 +2632,220 @@ Subs Topics = default-mqtt-SubTopic Pubs Topics = default-mqtt-PubTopic ``` +--- + +--- + +### 9. ESP32WM_MRD_ForcedConfig using LITTLEFS with SSL on ESP32_DEV to demo WiFi Scan + +The following is the sample terminal output when running example [ESP32WM_MRD_ForcedConfig](examples/ESP32WM_MRD_ForcedConfig) on **ESP32_DEV** with WiFi Scan for selection in Configuration Portal. + +#### 9.1 MRD/DRD => Open Config Portal + +``` +Starting ESP32WM_MRD_ForcedConfig using LITTLEFS with SSL on ESP32_DEV +Blynk_WM SSL for ESP32 v1.4.0 +ESP_MultiResetDetector v1.1.1 +[228] Set CustomsStyle to : +[250] Set CustomsHeadElement to : +[257] Set CORS Header to : Your Access-Control-Allow-Origin +LittleFS Flag read = 0xFFFC0003 +multiResetDetectorFlag = 0xFFFC0003 +lowerBytes = 0x0003, upperBytes = 0x0003 +multiResetDetected, number of times = 3 +Saving config file... +Saving config file OK +[332] Multi or Double Reset Detected +[488] Hostname=ESP32-Master-Controller +[558] LoadCfgFile +[571] OK +[571] ======= Start Stored Config Data ======= +[571] Hdr=SSL_ESP32,BrdName=ESP8266 +[571] SSID=HueNet1,PW=password +[571] SSID1=HueNet2,PW1=password +[574] Server=blynk-cloud.com,Token=token +[577] Server1=blynk-cloud.com,Token1=token2 +[581] Port=9443 +[583] ======= End Config Data ======= +[586] CCSum=0x2185,RCSum=0x2185 +[620] LoadCredFile +[632] CrR:pdata=default-mqtt-server,len=34 +[632] CrR:pdata=1883,len=6 +[632] CrR:pdata=default-mqtt-username,len=34 +[632] CrR:pdata=default-mqtt-password,len=34 +[635] CrR:pdata=default-mqtt-SubTopic,len=34 +[639] CrR:pdata=default-mqtt-PubTopic,len=34 +[643] OK +[644] CrCCsum=0x29a6,CrRCsum=0x29a6 +[647] Valid Stored Dynamic Data +[650] Hdr=SSL_ESP32,BrdName=ESP8266 +[653] SSID=HueNet1,PW=password +[656] SSID1=HueNet2,PW1=password +[659] Server=blynk-cloud.com,Token=token +[663] Server1=blynk-cloud.com,Token1=token2 +[667] Port=9443 +[668] ======= End Config Data ======= +[671] Check if isForcedCP +[702] LoadCPFile +[713] OK +[713] bg: isForcedConfigPortal = false +[713] bg:Stay forever in CP:DRD/MRD +[713] clearForcedCP +[746] SaveCPFile +[758] OK +[790] SaveBkUpCPFile +[802] OK +[802] Scanning Network +[6310] scanWifiNetworks: Done, Scanned Networks n = 11 +[6310] Sorting +[6310] Removing Dup +[6311] WiFi networks found: +[6312] 1: HueNet, -26dB +[6312] 2: HueNet1, -30dB +[6314] 3: HueNetTek, -32dB +[6316] 4: dragino-1ed63c, -35dB +[6319] 5: HueNet2, -57dB +[6322] 6: bacau, -68dB +[6324] 7: guest_24, -69dB +[6326] 8: rogers786, -87dB +[6328] 9: Rogers 786, -87dB +[6331] 10: Access 2.0, -92dB +[6334] 11: Family Room speaker.o, -96dB +[7197] +stConf:SSID=TestPortal-ESP32,PW=TestPortalPass +[7197] IP=192.168.4.1,ch=6 +[7299] s:millis() = 7299, configTimeout = 127299 +F +Your stored Credentials : +MQTT Server = default-mqtt-server +Port = 1883 +MQTT UserName = default-mqtt-username +MQTT PWD = default-mqtt-password +Subs Topics = default-mqtt-SubTopic +Pubs Topics = default-mqtt-PubTopic +[19720] h: Init menuItemUpdated +[19863] h:mqtt=default-mqtt-server +[19863] h2:myMenuItems[0]=default-mqtt-server +[19877] h:mqpt=1883 +[19877] h2:myMenuItems[1]=1883 +[19891] h:user=default-mqtt-username +[19891] h2:myMenuItems[2]=default-mqtt-username +[19911] h:mqpw=default-mqtt-password +[19911] h2:myMenuItems[3]=default-mqtt-password +[19925] h:subs=default-mqtt-SubTopic +[19925] h2:myMenuItems[4]=default-mqtt-SubTopic +[19939] h:pubs=default-mqtt-PubTopic +[19939] h2:myMenuItems[5]=default-mqtt-PubTopic +[19942] h:Updating LittleFS:/wmssl_conf.dat +[19982] SaveCfgFile +[19982] WCSum=0x34ef +[19996] OK +[20173] SaveBkUpCfgFile +[20184] OK +[20217] SaveCredFile +[20217] CW1:pdata=default-mqtt-server,len=34 +[20228] CW1:pdata=1883,len=6 +[20228] CW1:pdata=default-mqtt-username,len=34 +[20228] CW1:pdata=default-mqtt-password,len=34 +[20228] CW1:pdata=default-mqtt-SubTopic,len=34 +[20232] CW1:pdata=default-mqtt-PubTopic,len=34 +[20237] OK +[20237] CrWCSum=0x29a6 +[20275] SaveBkUpCredFile +[20276] CW2:pdata=default-mqtt-server,len=34 +[20288] CW2:pdata=1883,len=6 +[20288] CW2:pdata=default-mqtt-username,len=34 +[20288] CW2:pdata=default-mqtt-password,len=34 +[20288] CW2:pdata=default-mqtt-SubTopic,len=34 +[20292] CW2:pdata=default-mqtt-PubTopic,len=34 +[20297] OK +[20297] h:Rst +``` + +#### 9.2 Config Data Saved => Connection to Blynk + + +``` +Starting ESP32WM_MRD_ForcedConfig using LITTLEFS with SSL on ESP32_DEV +Blynk_WM SSL for ESP32 v1.4.0 +ESP_MultiResetDetector v1.1.1 +[227] Set CustomsStyle to : +[249] Set CustomsHeadElement to : +[256] Set CORS Header to : Your Access-Control-Allow-Origin +LittleFS Flag read = 0xFFFE0001 +multiResetDetectorFlag = 0xFFFE0001 +lowerBytes = 0x0001, upperBytes = 0x0001 +No multiResetDetected, number of times = 1 +LittleFS Flag read = 0xFFFE0001 +Saving config file... +Saving config file OK +[635] Hostname=ESP32-Master-Controller +[704] LoadCfgFile +[716] OK +[717] ======= Start Stored Config Data ======= +[717] Hdr=SSL_ESP32,BrdName=ESP32-SSL +[717] SSID=HueNet,PW=password +[717] SSID1=HueNet1,PW1=password +[719] Server=account.duckdns.org,Token=token +[726] Server1=account.duckdns.org,Token1=token1 +[732] Port=9443 +[733] ======= End Config Data ======= +[737] CCSum=0x34ef,RCSum=0x34ef +[768] LoadCredFile +[779] CrR:pdata=default-mqtt-server,len=34 +[779] CrR:pdata=1883,len=6 +[779] CrR:pdata=default-mqtt-username,len=34 +[780] CrR:pdata=default-mqtt-password,len=34 +[783] CrR:pdata=default-mqtt-SubTopic,len=34 +[787] CrR:pdata=default-mqtt-PubTopic,len=34 +[791] OK +[791] CrCCsum=0x29a6,CrRCsum=0x29a6 +[795] Valid Stored Dynamic Data +[797] Hdr=SSL_ESP32,BrdName=ESP32-SSL +[801] SSID=HueNet,PW=password +[804] SSID1=HueNet1,PW1=password +[806] Server=account.duckdns.org,Token=token +[813] Server1=account.duckdns.org,Token1=token1 +[819] Port=9443 +[820] ======= End Config Data ======= +[824] Check if isForcedCP +[858] LoadCPFile +[871] OK +[871] bg: noConfigPortal = true +[871] Connecting MultiWifi... +[19266] WiFi connected after time: 1 +[19266] SSID: HueNet, RSSI = -29 +[19266] Channel: 10, IP address: 192.168.2.45 +[19267] bg: WiFi OK. Try Blynk +[19268] + ___ __ __ + / _ )/ /_ _____ / /__ + / _ / / // / _ \/ '_/ + /____/_/\_, /_//_/_/\_\ + /___/ v0.6.1 on ESP32 + +[20282] NTP time: Sun Apr 25 04:52:35 2021 +[20282] BlynkArduinoClient.connect: Connecting to account.duckdns.org:9443 +[22407] Certificate OK +[22412] Ready (ping: 4ms). +[22480] Connected to Blynk Server = account.duckdns.org, Token = token +[22480] bg: WiFi+Blynk OK + +Blynk ESP32 using LittleFS connected +Board Name : ESP32-SSL +Stop multiResetDetecting +Saving config file... +Saving config file OK +B +Your stored Credentials : +MQTT Server = default-mqtt-server +Port = 1883 +MQTT UserName = default-mqtt-username +MQTT PWD = default-mqtt-password +Subs Topics = default-mqtt-SubTopic +Pubs Topics = default-mqtt-PubTopic +RBRBRBRBRBRBRBRB +``` --- --- @@ -2585,6 +2885,14 @@ Sometimes, the library will only work if you update the board core to the latest ## Releases +### Major Release v1.4.0 + +1. Enable scan of WiFi networks for selection in Configuration Portal. Check [PR for v1.3.0 - Enable scan of WiFi networks #10](https://github.com/khoih-prog/WiFiManager_NINA_Lite/pull/10). Now you can select optional **SCAN_WIFI_NETWORKS**, **MANUAL_SSID_INPUT_ALLOWED** to be able to manually input SSID, not only from a scanned SSID lists and **MAX_SSID_IN_LIST** (from 2-15) +2. Fix invalid "blank" Config Data treated as Valid. +3. Permit optionally inputting one set of WiFi SSID/PWD by using `REQUIRE_ONE_SET_SSID_PW == true` +4. Enforce WiFi PWD minimum length of 8 chars +5. Minor enhancement to not display garbage when data is invalid + ### Releases v1.3.1 1. Fix issue of custom Blynk port (different from 8080 or 9443) not working on ESP32. Check [Custom Blynk port not working for BlynkSimpleEsp32_Async_WM.h #4](https://github.com/khoih-prog/Blynk_Async_WM/issues/4) @@ -2751,7 +3059,7 @@ Submit issues to: [Blynk_WM issues](https://github.com/khoih-prog/Blynk_WM/issue ## TO DO 1. Fix bug. Add enhancement -2. Add support to **ESP32-S2 (ESP32-S2 Saola, AI-Thinker ESP-12K, etc.) using LittleFS and SPIFFS** + ## DONE @@ -2786,6 +3094,9 @@ Submit issues to: [Blynk_WM issues](https://github.com/khoih-prog/Blynk_WM/issue 28. Configurable **Customs HTML Headers**, including Customs Style, Customs Head Elements, CORS Header 29. Add support to **ESP32-C3 using EEPROM and SPIFFS** 30. Fix SSL issue with Blynk Cloud Server by using SSL in unsecured mode. +30. Permit optionally inputting one set of WiFi SSID/PWD by using `REQUIRE_ONE_SET_SSID_PW == true` +31. Enforce WiFi PWD minimum length of 8 chars +32. Enable **scan of WiFi networks** for selection in Configuration Portal --- --- @@ -2801,6 +3112,9 @@ Submit issues to: [Blynk_WM issues](https://github.com/khoih-prog/Blynk_WM/issue * [Good new feature: Blynk.resetAndEnterConfigPortal() Thanks & question #27](https://github.com/khoih-prog/Blynk_WM/issues/27) 5. Thanks to [Thor Johnson](https://github.com/thorathome) and [kevinleberge](https://github.com/kevinleberge) to help locate the bugs, discuss the USE_DEFAULT_CONFIG_DATA solution leading to release v1.0.16. See [Can’t load defaults](https://github.com/khoih-prog/Blynk_WM/issues/15) and [Setting "#define USE_DYNAMIC_PARAMETERS false" on Blynk_WM_Template.ino results in compile error](https://github.com/khoih-prog/Blynk_WM/issues/16) 6. Thanks to [komaneko](https://github.com/jjskaife) to report bugs in [Custom Blynk port not working for BlynkSimpleEsp32_Async_WM.h #4](https://github.com/khoih-prog/Blynk_Async_WM/issues/4) leading to v1.3.1 +7. Thanks to [Michael H. "bizprof"](https://github.com/bizprof). With the impressive new feature : + - `Enable scan of WiFi networks for selection in Configuration Portal`. Check [PR for v1.3.0 - Enable scan of WiFi networks #10](https://github.com/khoih-prog/WiFiManager_NINA_Lite/pull/10) leading to v1.4.0 + @@ -2810,7 +3124,10 @@ Submit issues to: [Blynk_WM issues](https://github.com/khoih-prog/Blynk_WM/issue - + + + +
thorathome
⭐️ Thor Johnson

kevinleberge
kevinleberge

jjskaife
komaneko

bizprof
⭐️⭐️ Michael H. "bizprof"

--- diff --git a/examples/AM2315_ESP32_SSL/AM2315_ESP32_SSL.ino b/examples/AM2315_ESP32_SSL/AM2315_ESP32_SSL.ino index 3ef7eea..b48fe3f 100644 --- a/examples/AM2315_ESP32_SSL/AM2315_ESP32_SSL.ino +++ b/examples/AM2315_ESP32_SSL/AM2315_ESP32_SSL.ino @@ -7,7 +7,7 @@ Forked from Blynk library v0.6.1 https://github.com/blynkkk/blynk-library/releases Built by Khoi Hoang https://github.com/khoih-prog/Blynk_WM Licensed under MIT license - Version: 1.3.1 + Version: 1.4.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -36,6 +36,7 @@ 1.3.0 K Hoang 19/04/2021 Add LittleFS and SPIFFS support to ESP32-S2. Add support to ESP32-C3 without LittleFS Fix SSL issue with Blynk Cloud Server 1.3.1 K Hoang 24/04/2021 Fix issue of custom Blynk port (different from 8080 or 9443) not working on ESP32 + 1.4.0 K Hoang 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal *****************************************************************************************************************************/ #include "defines.h" diff --git a/examples/AM2315_ESP32_SSL/defines.h b/examples/AM2315_ESP32_SSL/defines.h index 979f9c7..abe62af 100644 --- a/examples/AM2315_ESP32_SSL/defines.h +++ b/examples/AM2315_ESP32_SSL/defines.h @@ -94,6 +94,19 @@ #define CONFIG_TIMEOUT 120000L #define USE_DYNAMIC_PARAMETERS true + + ///////////////////////////////////////////// + + #define SCAN_WIFI_NETWORKS true + + // To be able to manually input SSID, not from a scanned SSID lists + #define MANUAL_SSID_INPUT_ALLOWED true + + // From 2-15 + #define MAX_SSID_IN_LIST 8 + + ///////////////////////////////////////////// + // Those above #define's must be placed before #include and ////////////////////////////////////////// diff --git a/examples/AM2315_ESP8266/AM2315_ESP8266.ino b/examples/AM2315_ESP8266/AM2315_ESP8266.ino index 17342c2..d75e82b 100644 --- a/examples/AM2315_ESP8266/AM2315_ESP8266.ino +++ b/examples/AM2315_ESP8266/AM2315_ESP8266.ino @@ -7,7 +7,7 @@ Forked from Blynk library v0.6.1 https://github.com/blynkkk/blynk-library/releases Built by Khoi Hoang https://github.com/khoih-prog/Blynk_WM Licensed under MIT license - Version: 1.3.1 + Version: 1.4.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -36,6 +36,7 @@ 1.3.0 K Hoang 19/04/2021 Add LittleFS and SPIFFS support to ESP32-S2. Add support to ESP32-C3 without LittleFS Fix SSL issue with Blynk Cloud Server 1.3.1 K Hoang 24/04/2021 Fix issue of custom Blynk port (different from 8080 or 9443) not working on ESP32 + 1.4.0 K Hoang 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal *****************************************************************************************************************************/ #include "defines.h" diff --git a/examples/AM2315_ESP8266/defines.h b/examples/AM2315_ESP8266/defines.h index f48dd60..bf8e42d 100644 --- a/examples/AM2315_ESP8266/defines.h +++ b/examples/AM2315_ESP8266/defines.h @@ -79,6 +79,19 @@ #define CONFIG_TIMEOUT 120000L #define USE_DYNAMIC_PARAMETERS true + + ///////////////////////////////////////////// + + #define SCAN_WIFI_NETWORKS true + + // To be able to manually input SSID, not from a scanned SSID lists + #define MANUAL_SSID_INPUT_ALLOWED true + + // From 2-15 + #define MAX_SSID_IN_LIST 8 + + ///////////////////////////////////////////// + // Those above #define's must be placed before #include and ////////////////////////////////////////// diff --git a/examples/Blynk_WM_Template/Blynk_WM_Template.ino b/examples/Blynk_WM_Template/Blynk_WM_Template.ino index 6d840ff..88a3253 100644 --- a/examples/Blynk_WM_Template/Blynk_WM_Template.ino +++ b/examples/Blynk_WM_Template/Blynk_WM_Template.ino @@ -8,7 +8,7 @@ Forked from Blynk library v0.6.1 https://github.com/blynkkk/blynk-library/releases Built by Khoi Hoang https://github.com/khoih-prog/Blynk_WM Licensed under MIT license - Version: 1.3.1 + Version: 1.4.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -37,6 +37,7 @@ 1.3.0 K Hoang 19/04/2021 Add LittleFS and SPIFFS support to ESP32-S2. Add support to ESP32-C3 without LittleFS Fix SSL issue with Blynk Cloud Server 1.3.1 K Hoang 24/04/2021 Fix issue of custom Blynk port (different from 8080 or 9443) not working on ESP32 + 1.4.0 K Hoang 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal *****************************************************************************************************************************/ // Sketch uses Arduino IDE-selected ESP32 and ESP8266 to select compile choices @@ -155,6 +156,17 @@ #define USE_DYNAMIC_PARAMETERS true //#define USE_DYNAMIC_PARAMETERS false + ///////////////////////////////////////////// + + #define SCAN_WIFI_NETWORKS true + + // To be able to manually input SSID, not from a scanned SSID lists + #define MANUAL_SSID_INPUT_ALLOWED true + + // From 2-15 + #define MAX_SSID_IN_LIST 8 + + ///////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////// //// COMPILER SWITCH SELECTION - USE DEFAULT CONFIG PORTAL FIELD DATA OR NOT ////////////// diff --git a/examples/DHT11ESP32/DHT11ESP32.ino b/examples/DHT11ESP32/DHT11ESP32.ino index f260845..abc32ad 100644 --- a/examples/DHT11ESP32/DHT11ESP32.ino +++ b/examples/DHT11ESP32/DHT11ESP32.ino @@ -7,7 +7,7 @@ Forked from Blynk library v0.6.1 https://github.com/blynkkk/blynk-library/releases Built by Khoi Hoang https://github.com/khoih-prog/Blynk_WM Licensed under MIT license - Version: 1.3.1 + Version: 1.4.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -36,6 +36,7 @@ 1.3.0 K Hoang 19/04/2021 Add LittleFS and SPIFFS support to ESP32-S2. Add support to ESP32-C3 without LittleFS Fix SSL issue with Blynk Cloud Server 1.3.1 K Hoang 24/04/2021 Fix issue of custom Blynk port (different from 8080 or 9443) not working on ESP32 + 1.4.0 K Hoang 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal *****************************************************************************************************************************/ #include "defines.h" diff --git a/examples/DHT11ESP32/defines.h b/examples/DHT11ESP32/defines.h index 669d026..fe94b22 100644 --- a/examples/DHT11ESP32/defines.h +++ b/examples/DHT11ESP32/defines.h @@ -92,6 +92,18 @@ #define CONFIG_TIMEOUT 120000L #define USE_DYNAMIC_PARAMETERS true + + ///////////////////////////////////////////// + + #define SCAN_WIFI_NETWORKS true + + // To be able to manually input SSID, not from a scanned SSID lists + #define MANUAL_SSID_INPUT_ALLOWED true + + // From 2-15 + #define MAX_SSID_IN_LIST 8 + + ///////////////////////////////////////////// // Those above #define's must be placed before #include and ////////////////////////////////////////// diff --git a/examples/DHT11ESP32_SSL/DHT11ESP32_SSL.ino b/examples/DHT11ESP32_SSL/DHT11ESP32_SSL.ino index faeed19..6a590da 100644 --- a/examples/DHT11ESP32_SSL/DHT11ESP32_SSL.ino +++ b/examples/DHT11ESP32_SSL/DHT11ESP32_SSL.ino @@ -7,7 +7,7 @@ Forked from Blynk library v0.6.1 https://github.com/blynkkk/blynk-library/releases Built by Khoi Hoang https://github.com/khoih-prog/Blynk_WM Licensed under MIT license - Version: 1.3.1 + Version: 1.4.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -36,6 +36,7 @@ 1.3.0 K Hoang 19/04/2021 Add LittleFS and SPIFFS support to ESP32-S2. Add support to ESP32-C3 without LittleFS Fix SSL issue with Blynk Cloud Server 1.3.1 K Hoang 24/04/2021 Fix issue of custom Blynk port (different from 8080 or 9443) not working on ESP32 + 1.4.0 K Hoang 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal *****************************************************************************************************************************/ #include "defines.h" diff --git a/examples/DHT11ESP32_SSL/defines.h b/examples/DHT11ESP32_SSL/defines.h index c3e0cb2..47ac237 100644 --- a/examples/DHT11ESP32_SSL/defines.h +++ b/examples/DHT11ESP32_SSL/defines.h @@ -92,6 +92,19 @@ #define CONFIG_TIMEOUT 120000L #define USE_DYNAMIC_PARAMETERS true + + ///////////////////////////////////////////// + + #define SCAN_WIFI_NETWORKS true + + // To be able to manually input SSID, not from a scanned SSID lists + #define MANUAL_SSID_INPUT_ALLOWED true + + // From 2-15 + #define MAX_SSID_IN_LIST 8 + + ///////////////////////////////////////////// + // Those above #define's must be placed before #include and ////////////////////////////////////////// diff --git a/examples/DHT11ESP8266/DHT11ESP8266.ino b/examples/DHT11ESP8266/DHT11ESP8266.ino index d46db04..6a80d98 100644 --- a/examples/DHT11ESP8266/DHT11ESP8266.ino +++ b/examples/DHT11ESP8266/DHT11ESP8266.ino @@ -7,7 +7,7 @@ Forked from Blynk library v0.6.1 https://github.com/blynkkk/blynk-library/releases Built by Khoi Hoang https://github.com/khoih-prog/Blynk_WM Licensed under MIT license - Version: 1.3.1 + Version: 1.4.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -36,6 +36,7 @@ 1.3.0 K Hoang 19/04/2021 Add LittleFS and SPIFFS support to ESP32-S2. Add support to ESP32-C3 without LittleFS Fix SSL issue with Blynk Cloud Server 1.3.1 K Hoang 24/04/2021 Fix issue of custom Blynk port (different from 8080 or 9443) not working on ESP32 + 1.4.0 K Hoang 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal *****************************************************************************************************************************/ #include "defines.h" diff --git a/examples/DHT11ESP8266/defines.h b/examples/DHT11ESP8266/defines.h index 07e41db..b21867a 100644 --- a/examples/DHT11ESP8266/defines.h +++ b/examples/DHT11ESP8266/defines.h @@ -80,6 +80,18 @@ #define CONFIG_TIMEOUT 120000L #define USE_DYNAMIC_PARAMETERS true + + ///////////////////////////////////////////// + + #define SCAN_WIFI_NETWORKS true + + // To be able to manually input SSID, not from a scanned SSID lists + #define MANUAL_SSID_INPUT_ALLOWED true + + // From 2-15 + #define MAX_SSID_IN_LIST 8 + + ///////////////////////////////////////////// // Those above #define's must be placed before #include and #if USE_SSL diff --git a/examples/DHT11ESP8266_Debug/DHT11ESP8266_Debug.ino b/examples/DHT11ESP8266_Debug/DHT11ESP8266_Debug.ino index 056052f..28359f5 100644 --- a/examples/DHT11ESP8266_Debug/DHT11ESP8266_Debug.ino +++ b/examples/DHT11ESP8266_Debug/DHT11ESP8266_Debug.ino @@ -7,7 +7,7 @@ Forked from Blynk library v0.6.1 https://github.com/blynkkk/blynk-library/releases Built by Khoi Hoang https://github.com/khoih-prog/Blynk_WM Licensed under MIT license - Version: 1.3.1 + Version: 1.4.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -36,6 +36,7 @@ 1.3.0 K Hoang 19/04/2021 Add LittleFS and SPIFFS support to ESP32-S2. Add support to ESP32-C3 without LittleFS Fix SSL issue with Blynk Cloud Server 1.3.1 K Hoang 24/04/2021 Fix issue of custom Blynk port (different from 8080 or 9443) not working on ESP32 + 1.4.0 K Hoang 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal *****************************************************************************************************************************/ #include "defines.h" diff --git a/examples/DHT11ESP8266_Debug/defines.h b/examples/DHT11ESP8266_Debug/defines.h index d55c4cc..f146280 100644 --- a/examples/DHT11ESP8266_Debug/defines.h +++ b/examples/DHT11ESP8266_Debug/defines.h @@ -80,6 +80,19 @@ #define CONFIG_TIMEOUT 120000L #define USE_DYNAMIC_PARAMETERS true + + ///////////////////////////////////////////// + + #define SCAN_WIFI_NETWORKS true + + // To be able to manually input SSID, not from a scanned SSID lists + #define MANUAL_SSID_INPUT_ALLOWED true + + // From 2-15 + #define MAX_SSID_IN_LIST 8 + + ///////////////////////////////////////////// + // Those above #define's must be placed before #include and #if USE_SSL diff --git a/examples/DHT11ESP8266_SSL/DHT11ESP8266_SSL.ino b/examples/DHT11ESP8266_SSL/DHT11ESP8266_SSL.ino index 7ec72a3..4f69c6c 100644 --- a/examples/DHT11ESP8266_SSL/DHT11ESP8266_SSL.ino +++ b/examples/DHT11ESP8266_SSL/DHT11ESP8266_SSL.ino @@ -7,7 +7,7 @@ Forked from Blynk library v0.6.1 https://github.com/blynkkk/blynk-library/releases Built by Khoi Hoang https://github.com/khoih-prog/Blynk_WM Licensed under MIT license - Version: 1.3.1 + Version: 1.4.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -36,6 +36,7 @@ 1.3.0 K Hoang 19/04/2021 Add LittleFS and SPIFFS support to ESP32-S2. Add support to ESP32-C3 without LittleFS Fix SSL issue with Blynk Cloud Server 1.3.1 K Hoang 24/04/2021 Fix issue of custom Blynk port (different from 8080 or 9443) not working on ESP32 + 1.4.0 K Hoang 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal *****************************************************************************************************************************/ #include "defines.h" diff --git a/examples/DHT11ESP8266_SSL/defines.h b/examples/DHT11ESP8266_SSL/defines.h index 73d4d27..735df75 100644 --- a/examples/DHT11ESP8266_SSL/defines.h +++ b/examples/DHT11ESP8266_SSL/defines.h @@ -78,6 +78,19 @@ #define CONFIG_TIMEOUT 120000L #define USE_DYNAMIC_PARAMETERS true + + ///////////////////////////////////////////// + + #define SCAN_WIFI_NETWORKS true + + // To be able to manually input SSID, not from a scanned SSID lists + #define MANUAL_SSID_INPUT_ALLOWED true + + // From 2-15 + #define MAX_SSID_IN_LIST 8 + + ///////////////////////////////////////////// + // Those above #define's must be placed before #include and #if USE_SSL diff --git a/examples/ESP32WM_Config/ESP32WM_Config.ino b/examples/ESP32WM_Config/ESP32WM_Config.ino index e98c763..53f631f 100644 --- a/examples/ESP32WM_Config/ESP32WM_Config.ino +++ b/examples/ESP32WM_Config/ESP32WM_Config.ino @@ -7,7 +7,7 @@ Forked from Blynk library v0.6.1 https://github.com/blynkkk/blynk-library/releases Built by Khoi Hoang https://github.com/khoih-prog/Blynk_WM Licensed under MIT license - Version: 1.3.1 + Version: 1.4.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -36,6 +36,7 @@ 1.3.0 K Hoang 19/04/2021 Add LittleFS and SPIFFS support to ESP32-S2. Add support to ESP32-C3 without LittleFS Fix SSL issue with Blynk Cloud Server 1.3.1 K Hoang 24/04/2021 Fix issue of custom Blynk port (different from 8080 or 9443) not working on ESP32 + 1.4.0 K Hoang 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal *****************************************************************************************************************************/ #include "defines.h" diff --git a/examples/ESP32WM_Config/defines.h b/examples/ESP32WM_Config/defines.h index a16e62b..efb3363 100644 --- a/examples/ESP32WM_Config/defines.h +++ b/examples/ESP32WM_Config/defines.h @@ -82,6 +82,19 @@ #define CONFIG_TIMEOUT 120000L #define USE_DYNAMIC_PARAMETERS true + +///////////////////////////////////////////// + +#define SCAN_WIFI_NETWORKS true + +// To be able to manually input SSID, not from a scanned SSID lists +#define MANUAL_SSID_INPUT_ALLOWED true + +// From 2-15 +#define MAX_SSID_IN_LIST 8 + +///////////////////////////////////////////// + // Those above #define's must be placed before #include and ////////////////////////////////////////// diff --git a/examples/ESP32WM_ForcedConfig/ESP32WM_ForcedConfig.ino b/examples/ESP32WM_ForcedConfig/ESP32WM_ForcedConfig.ino index dc20230..600667c 100644 --- a/examples/ESP32WM_ForcedConfig/ESP32WM_ForcedConfig.ino +++ b/examples/ESP32WM_ForcedConfig/ESP32WM_ForcedConfig.ino @@ -7,7 +7,7 @@ Forked from Blynk library v0.6.1 https://github.com/blynkkk/blynk-library/releases Built by Khoi Hoang https://github.com/khoih-prog/Blynk_WM Licensed under MIT license - Version: 1.3.1 + Version: 1.4.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -36,6 +36,7 @@ 1.3.0 K Hoang 19/04/2021 Add LittleFS and SPIFFS support to ESP32-S2. Add support to ESP32-C3 without LittleFS Fix SSL issue with Blynk Cloud Server 1.3.1 K Hoang 24/04/2021 Fix issue of custom Blynk port (different from 8080 or 9443) not working on ESP32 + 1.4.0 K Hoang 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal *****************************************************************************************************************************/ #include "defines.h" diff --git a/examples/ESP32WM_ForcedConfig/defines.h b/examples/ESP32WM_ForcedConfig/defines.h index 7910f7a..0927223 100644 --- a/examples/ESP32WM_ForcedConfig/defines.h +++ b/examples/ESP32WM_ForcedConfig/defines.h @@ -84,6 +84,19 @@ #define CONFIG_TIMEOUT 120000L #define USE_DYNAMIC_PARAMETERS true + +///////////////////////////////////////////// + +#define SCAN_WIFI_NETWORKS true + +// To be able to manually input SSID, not from a scanned SSID lists +#define MANUAL_SSID_INPUT_ALLOWED true + +// From 2-15 +#define MAX_SSID_IN_LIST 8 + +///////////////////////////////////////////// + // Those above #define's must be placed before #include and ////////////////////////////////////////// diff --git a/examples/ESP32WM_MRD_Config/ESP32WM_MRD_Config.ino b/examples/ESP32WM_MRD_Config/ESP32WM_MRD_Config.ino index 6537f0f..809660f 100644 --- a/examples/ESP32WM_MRD_Config/ESP32WM_MRD_Config.ino +++ b/examples/ESP32WM_MRD_Config/ESP32WM_MRD_Config.ino @@ -7,7 +7,7 @@ Forked from Blynk library v0.6.1 https://github.com/blynkkk/blynk-library/releases Built by Khoi Hoang https://github.com/khoih-prog/Blynk_WM Licensed under MIT license - Version: 1.3.1 + Version: 1.4.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -36,6 +36,7 @@ 1.3.0 K Hoang 19/04/2021 Add LittleFS and SPIFFS support to ESP32-S2. Add support to ESP32-C3 without LittleFS Fix SSL issue with Blynk Cloud Server 1.3.1 K Hoang 24/04/2021 Fix issue of custom Blynk port (different from 8080 or 9443) not working on ESP32 + 1.4.0 K Hoang 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal *****************************************************************************************************************************/ #include "defines.h" diff --git a/examples/ESP32WM_MRD_Config/defines.h b/examples/ESP32WM_MRD_Config/defines.h index cb5bf06..1ab5b6b 100644 --- a/examples/ESP32WM_MRD_Config/defines.h +++ b/examples/ESP32WM_MRD_Config/defines.h @@ -117,6 +117,19 @@ #define CONFIG_TIMEOUT 120000L #define USE_DYNAMIC_PARAMETERS true + +///////////////////////////////////////////// + +#define SCAN_WIFI_NETWORKS true + +// To be able to manually input SSID, not from a scanned SSID lists +#define MANUAL_SSID_INPUT_ALLOWED true + +// From 2-15 +#define MAX_SSID_IN_LIST 8 + +///////////////////////////////////////////// + // Those above #define's must be placed before #include and ////////////////////////////////////////// diff --git a/examples/ESP32WM_MRD_ForcedConfig/ESP32WM_MRD_ForcedConfig.ino b/examples/ESP32WM_MRD_ForcedConfig/ESP32WM_MRD_ForcedConfig.ino index 03d053d..55cef9b 100644 --- a/examples/ESP32WM_MRD_ForcedConfig/ESP32WM_MRD_ForcedConfig.ino +++ b/examples/ESP32WM_MRD_ForcedConfig/ESP32WM_MRD_ForcedConfig.ino @@ -7,7 +7,7 @@ Forked from Blynk library v0.6.1 https://github.com/blynkkk/blynk-library/releases Built by Khoi Hoang https://github.com/khoih-prog/Blynk_WM Licensed under MIT license - Version: 1.3.1 + Version: 1.4.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -36,6 +36,7 @@ 1.3.0 K Hoang 19/04/2021 Add LittleFS and SPIFFS support to ESP32-S2. Add support to ESP32-C3 without LittleFS Fix SSL issue with Blynk Cloud Server 1.3.1 K Hoang 24/04/2021 Fix issue of custom Blynk port (different from 8080 or 9443) not working on ESP32 + 1.4.0 K Hoang 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal *****************************************************************************************************************************/ #include "defines.h" diff --git a/examples/ESP32WM_MRD_ForcedConfig/defines.h b/examples/ESP32WM_MRD_ForcedConfig/defines.h index cb5bf06..74b9663 100644 --- a/examples/ESP32WM_MRD_ForcedConfig/defines.h +++ b/examples/ESP32WM_MRD_ForcedConfig/defines.h @@ -117,6 +117,19 @@ #define CONFIG_TIMEOUT 120000L #define USE_DYNAMIC_PARAMETERS true + +///////////////////////////////////////////// + +#define SCAN_WIFI_NETWORKS true + +// To be able to manually input SSID, not from a scanned SSID lists +#define MANUAL_SSID_INPUT_ALLOWED true + +// From 2-15 +#define MAX_SSID_IN_LIST 8 + +///////////////////////////////////////////// + // Those above #define's must be placed before #include and ////////////////////////////////////////// diff --git a/examples/ESP8266WM_Config/ESP8266WM_Config.ino b/examples/ESP8266WM_Config/ESP8266WM_Config.ino index f3e5289..9db2364 100644 --- a/examples/ESP8266WM_Config/ESP8266WM_Config.ino +++ b/examples/ESP8266WM_Config/ESP8266WM_Config.ino @@ -7,7 +7,7 @@ Forked from Blynk library v0.6.1 https://github.com/blynkkk/blynk-library/releases Built by Khoi Hoang https://github.com/khoih-prog/Blynk_WM Licensed under MIT license - Version: 1.3.1 + Version: 1.4.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -36,6 +36,7 @@ 1.3.0 K Hoang 19/04/2021 Add LittleFS and SPIFFS support to ESP32-S2. Add support to ESP32-C3 without LittleFS Fix SSL issue with Blynk Cloud Server 1.3.1 K Hoang 24/04/2021 Fix issue of custom Blynk port (different from 8080 or 9443) not working on ESP32 + 1.4.0 K Hoang 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal *****************************************************************************************************************************/ #include "defines.h" diff --git a/examples/ESP8266WM_Config/defines.h b/examples/ESP8266WM_Config/defines.h index 3827e85..7a5b309 100644 --- a/examples/ESP8266WM_Config/defines.h +++ b/examples/ESP8266WM_Config/defines.h @@ -69,6 +69,19 @@ #define CONFIG_TIMEOUT 120000L #define USE_DYNAMIC_PARAMETERS true + +///////////////////////////////////////////// + +#define SCAN_WIFI_NETWORKS true + +// To be able to manually input SSID, not from a scanned SSID lists +#define MANUAL_SSID_INPUT_ALLOWED true + +// From 2-15 +#define MAX_SSID_IN_LIST 8 + +///////////////////////////////////////////// + // Those above #define's must be placed before #include and ////////////////////////////////////////// diff --git a/examples/ESP8266WM_ForcedConfig/ESP8266WM_ForcedConfig.ino b/examples/ESP8266WM_ForcedConfig/ESP8266WM_ForcedConfig.ino index 4c1d032..aefca12 100644 --- a/examples/ESP8266WM_ForcedConfig/ESP8266WM_ForcedConfig.ino +++ b/examples/ESP8266WM_ForcedConfig/ESP8266WM_ForcedConfig.ino @@ -7,7 +7,7 @@ Forked from Blynk library v0.6.1 https://github.com/blynkkk/blynk-library/releases Built by Khoi Hoang https://github.com/khoih-prog/Blynk_WM Licensed under MIT license - Version: 1.3.1 + Version: 1.4.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -36,6 +36,7 @@ 1.3.0 K Hoang 19/04/2021 Add LittleFS and SPIFFS support to ESP32-S2. Add support to ESP32-C3 without LittleFS Fix SSL issue with Blynk Cloud Server 1.3.1 K Hoang 24/04/2021 Fix issue of custom Blynk port (different from 8080 or 9443) not working on ESP32 + 1.4.0 K Hoang 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal *****************************************************************************************************************************/ #include "defines.h" diff --git a/examples/ESP8266WM_ForcedConfig/defines.h b/examples/ESP8266WM_ForcedConfig/defines.h index b79b33d..860e53a 100644 --- a/examples/ESP8266WM_ForcedConfig/defines.h +++ b/examples/ESP8266WM_ForcedConfig/defines.h @@ -69,6 +69,19 @@ #define CONFIG_TIMEOUT 120000L #define USE_DYNAMIC_PARAMETERS true + +///////////////////////////////////////////// + +#define SCAN_WIFI_NETWORKS true + +// To be able to manually input SSID, not from a scanned SSID lists +#define MANUAL_SSID_INPUT_ALLOWED true + +// From 2-15 +#define MAX_SSID_IN_LIST 8 + +///////////////////////////////////////////// + // Those above #define's must be placed before #include and ////////////////////////////////////////// diff --git a/examples/ESP8266WM_MRD_Config/ESP8266WM_MRD_Config.ino b/examples/ESP8266WM_MRD_Config/ESP8266WM_MRD_Config.ino index 2cb505f..5b593a1 100644 --- a/examples/ESP8266WM_MRD_Config/ESP8266WM_MRD_Config.ino +++ b/examples/ESP8266WM_MRD_Config/ESP8266WM_MRD_Config.ino @@ -7,7 +7,7 @@ Forked from Blynk library v0.6.1 https://github.com/blynkkk/blynk-library/releases Built by Khoi Hoang https://github.com/khoih-prog/Blynk_WM Licensed under MIT license - Version: 1.3.1 + Version: 1.4.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -36,6 +36,7 @@ 1.3.0 K Hoang 19/04/2021 Add LittleFS and SPIFFS support to ESP32-S2. Add support to ESP32-C3 without LittleFS Fix SSL issue with Blynk Cloud Server 1.3.1 K Hoang 24/04/2021 Fix issue of custom Blynk port (different from 8080 or 9443) not working on ESP32 + 1.4.0 K Hoang 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal ********************************************************************************************************************************/ #include "defines.h" diff --git a/examples/ESP8266WM_MRD_Config/defines.h b/examples/ESP8266WM_MRD_Config/defines.h index add7de4..c0f12cc 100644 --- a/examples/ESP8266WM_MRD_Config/defines.h +++ b/examples/ESP8266WM_MRD_Config/defines.h @@ -101,6 +101,19 @@ #define CONFIG_TIMEOUT 120000L #define USE_DYNAMIC_PARAMETERS true + +///////////////////////////////////////////// + +#define SCAN_WIFI_NETWORKS true + +// To be able to manually input SSID, not from a scanned SSID lists +#define MANUAL_SSID_INPUT_ALLOWED true + +// From 2-15 +#define MAX_SSID_IN_LIST 8 + +///////////////////////////////////////////// + // Those above #define's must be placed before #include and ////////////////////////////////////////// diff --git a/examples/ESP8266WM_MRD_ForcedConfig/ESP8266WM_MRD_ForcedConfig.ino b/examples/ESP8266WM_MRD_ForcedConfig/ESP8266WM_MRD_ForcedConfig.ino index a055b12..d6b81e5 100644 --- a/examples/ESP8266WM_MRD_ForcedConfig/ESP8266WM_MRD_ForcedConfig.ino +++ b/examples/ESP8266WM_MRD_ForcedConfig/ESP8266WM_MRD_ForcedConfig.ino @@ -7,7 +7,7 @@ Forked from Blynk library v0.6.1 https://github.com/blynkkk/blynk-library/releases Built by Khoi Hoang https://github.com/khoih-prog/Blynk_WM Licensed under MIT license - Version: 1.3.1 + Version: 1.4.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -36,6 +36,7 @@ 1.3.0 K Hoang 19/04/2021 Add LittleFS and SPIFFS support to ESP32-S2. Add support to ESP32-C3 without LittleFS Fix SSL issue with Blynk Cloud Server 1.3.1 K Hoang 24/04/2021 Fix issue of custom Blynk port (different from 8080 or 9443) not working on ESP32 + 1.4.0 K Hoang 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal ********************************************************************************************************************************/ #include "defines.h" diff --git a/examples/ESP8266WM_MRD_ForcedConfig/defines.h b/examples/ESP8266WM_MRD_ForcedConfig/defines.h index 27507cb..811e7bd 100644 --- a/examples/ESP8266WM_MRD_ForcedConfig/defines.h +++ b/examples/ESP8266WM_MRD_ForcedConfig/defines.h @@ -101,6 +101,19 @@ #define CONFIG_TIMEOUT 120000L #define USE_DYNAMIC_PARAMETERS true + +///////////////////////////////////////////// + +#define SCAN_WIFI_NETWORKS true + +// To be able to manually input SSID, not from a scanned SSID lists +#define MANUAL_SSID_INPUT_ALLOWED true + +// From 2-15 +#define MAX_SSID_IN_LIST 8 + +///////////////////////////////////////////// + // Those above #define's must be placed before #include and ////////////////////////////////////////// diff --git a/library.json b/library.json index 4e6c97f..86650ba 100644 --- a/library.json +++ b/library.json @@ -1,7 +1,7 @@ { "name": "Blynk_WM", - "version": "1.3.1", - "description": "Library for configuring/auto(re)connecting ESP32 (including ESP32-S2), ESP8266 modules to best or available MultiWiFi APs and MultiBlynk servers at runtime. Enable adding dynamic custom parameters from sketch and input using the same Config Portal (CP). CP will be auto-adjusted to match the number of dynamic parameters. Optional default Credentials to be autoloaded into CP to use or change instead of manually input. Static STA IP and DHCP Hostname as well as Config Portal AP channel, IP, SSID, Password can be configured. Config. Data saved in ESP8266/ESP32 LittleFS, SPIFFS or EEPROM. Multi, Double DetectDetector or Virtual CP Switch feature permits entering CP as requested. Configurable Customs HTML Headers, including Customs Style, Customs Head Elements, CORS Header.", + "version": "1.4.0", + "description": "Library for configuring/auto(re)connecting ESP32 (including ESP32-S2, ESP32-C3), ESP8266 modules to best or available MultiWiFi APs and MultiBlynk servers at runtime. Enable adding dynamic custom parameters from sketch and input using the same Config Portal (CP). CP will be auto-adjusted to match the number of dynamic parameters. Optional default Credentials to be autoloaded into CP to use or change instead of manually input. Static STA IP and DHCP Hostname as well as Config Portal AP channel, IP, SSID, Password can be configured. Config. Data saved in ESP8266/ESP32 LittleFS, SPIFFS or EEPROM. Multi, Double DetectDetector or Virtual CP Switch feature permits entering CP as requested. Configurable Customs HTML Headers, including Customs Style, Customs Head Elements, CORS Header. Now with scanning of WiFi networks for selection in Configuration Portal.", "keywords": "control, device, communication, blynk, iot, wifi, esp8266, esp32, esp32-s2, esp32-c3, drd, mrd, double-reset, multi-reset, config-portal, credentials, dynamic-params, customs-header, smartphone, mobile, app, web, cloud, sensors, m2m, protocol, ble, bluetooth, Manager, DynamicParameters, dynamic, configportal, usb, serial, ethernet, data, http, portal ", "authors": { @@ -45,7 +45,7 @@ { "owner": "lorol", "name": "LittleFS_esp32", - "version": ">=1.0.5", + "version": ">=1.0.6", "platforms": ["espressif32"] } ], diff --git a/library.properties b/library.properties index 9f94a82..5788f82 100644 --- a/library.properties +++ b/library.properties @@ -1,10 +1,10 @@ name=Blynk_WiFiManager -version=1.3.1 +version=1.4.0 author=Khoi Hoang license=MIT maintainer=Khoi Hoang sentence=Simple WiFiManager for Blynk and ESP32 (including ESP32-S2, ESP32-C3), ESP8266 with or without SSL, configuration data saved in either LittleFS, SPIFFS or EEPROM -paragraph=Library for configuring/auto(re)connecting ESP32 (including ESP32-S2, ESP32-C3), ESP8266 modules to best or available MultiWiFi APs and MultiBlynk servers at runtime. Enable adding dynamic custom parameters from sketch and input using the same Config Portal. Config Portal will be auto-adjusted to match the number of dynamic parameters. Optional default Credentials to be autoloaded into Config Portal to use or change instead of manually input. Static STA IP and DHCP Hostname as well as Config Portal AP channel, IP, SSID, Password can be configured. Multi, Double DetectDetector or Virtual ConfigPortal Switch feature permits entering Config Portal as requested. Configurable Customs HTML Headers, including Customs Style, Customs Head Elements, CORS Header. +paragraph=Library for configuring/auto(re)connecting ESP32 (including ESP32-S2, ESP32-C3), ESP8266 modules to best or available MultiWiFi APs and MultiBlynk servers at runtime. Enable adding dynamic custom parameters from sketch and input using the same Config Portal. Config Portal will be auto-adjusted to match the number of dynamic parameters. Optional default Credentials to be autoloaded into Config Portal to use or change instead of manually input. Static STA IP and DHCP Hostname as well as Config Portal AP channel, IP, SSID, Password can be configured. Multi, Double DetectDetector or Virtual ConfigPortal Switch feature permits entering Config Portal as requested. Configurable Customs HTML Headers, including Customs Style, Customs Head Elements, CORS Header. Now with scanning of WiFi networks for selection in Configuration Portal. category=Communication url=https://github.com/khoih-prog/Blynk_WM architectures=esp8266,esp32 diff --git a/pics/Input_With_Scan.png b/pics/Input_With_Scan.png new file mode 100644 index 0000000000000000000000000000000000000000..c6d7f54f73315e54aba4755abb7b232c80798146 GIT binary patch literal 31402 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%0AOPOs!Dp}=w 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 literal 0 HcmV?d00001 diff --git a/pics/Main.png b/pics/Main.png index 0d682625fe15b4f0b1e33d19f388c5f8bbf7b9eb..1a03bfb768943430388269d58f92834f1a720168 100644 GIT binary patch literal 29047 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 IgU`l)00f6_;{X5v literal 29966 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

#{By0Puf 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 diff --git a/pics/Saved.png b/pics/Saved.png new file mode 100644 index 0000000000000000000000000000000000000000..7af090411641c5155aad8213cb1ed0e28a43490e GIT binary patch literal 35300 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 literal 0 HcmV?d00001 diff --git a/src/BlynkSimpleEsp32_SSL_WM.h b/src/BlynkSimpleEsp32_SSL_WM.h index 2a75f9f..fe76caf 100644 --- a/src/BlynkSimpleEsp32_SSL_WM.h +++ b/src/BlynkSimpleEsp32_SSL_WM.h @@ -16,7 +16,7 @@ @date Jan 2015 @brief - Version: 1.3.1 + Version: 1.4.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -45,6 +45,7 @@ 1.3.0 K Hoang 19/04/2021 Add LittleFS and SPIFFS support to ESP32-S2. Add support to ESP32-C3 without LittleFS Fix SSL issue with Blynk Cloud Server 1.3.1 K Hoang 24/04/2021 Fix issue of custom Blynk port (different from 8080 or 9443) not working on ESP32 + 1.4.0 K Hoang 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal ********************************************************************************************************************************/ #ifndef BlynkSimpleEsp32_SSL_WM_h @@ -54,7 +55,7 @@ #error This code is intended to run on the ESP32 platform! Please check your Tools->Board setting. #endif -#define BLYNK_WM_VERSION "Blynk_WM SSL for ESP32 v1.3.1" +#define BLYNK_WM_VERSION "Blynk_WM SSL for ESP32 v1.4.0" #if defined(BLYNK_SSL_USE_LETSENCRYPT) static const char BLYNK_DEFAULT_ROOT_CA[] = @@ -102,6 +103,39 @@ #warning Using EEPROM in BlynkSimpleESP32_SSL_WM.h #endif + +////////////////////////////////////////////// + +// New from v1.4.0 +// KH, Some minor simplification +#if !defined(SCAN_WIFI_NETWORKS) + #define SCAN_WIFI_NETWORKS true //false +#endif + +#if SCAN_WIFI_NETWORKS + #warning Using SCAN_WIFI_NETWORKS + + #if !defined(MANUAL_SSID_INPUT_ALLOWED) + #define MANUAL_SSID_INPUT_ALLOWED true + #endif + + #if !defined(MAX_SSID_IN_LIST) + #define MAX_SSID_IN_LIST 10 + #elif (MAX_SSID_IN_LIST < 2) + #warning Parameter MAX_SSID_IN_LIST defined must be >= 2 - Reset to 10 + #undef MAX_SSID_IN_LIST + #define MAX_SSID_IN_LIST 10 + #elif (MAX_SSID_IN_LIST > 15) + #warning Parameter MAX_SSID_IN_LIST defined must be <= 15 - Reset to 10 + #undef MAX_SSID_IN_LIST + #define MAX_SSID_IN_LIST 10 + #endif +#else + #warning SCAN_WIFI_NETWORKS disabled +#endif + +///////// NEW for DRD ///////////// + #if !defined(USING_MRD) #define USING_MRD false #endif @@ -340,8 +374,6 @@ extern bool LOAD_DEFAULT_CONFIG_DATA; extern Blynk_WM_Configuration defaultConfig; //From v1.0.10, Permit special chars such as # and % - -#if 1 // -- HTML page fragments const char BLYNK_WM_HTML_HEAD_START[] /*PROGMEM*/ = "BlynkSimpleEsp32_SSL_WM"; @@ -349,35 +381,19 @@ const char BLYNK_WM_HTML_HEAD_START[] /*PROGMEM*/ = " const char BLYNK_WM_HTML_HEAD_STYLE[] /*PROGMEM*/ = ""; const char BLYNK_WM_HTML_HEAD_END[] /*PROGMEM*/ = "

\ -
\ -
\ -
\ -
\ -
\ -
\ -
\ -
\ -
\ -
"; - -#else - -// -- HTML page fragments -const char BLYNK_WM_HTML_HEAD[] /*PROGMEM*/ = "BlynkSimpleEsp32_SSL_WM
\ -
\ -
\ -
\ -
\ +
[[input_id]]
\ +
\ +
[[input_id1]]
\ +
\
\
\
\
\
\ -
"; +
"; // DO NOT CHANGE THIS STRING EVER!!!! -#endif +const char BLYNK_WM_HTML_INPUT_ID[] /*PROGMEM*/ = ""; +const char BLYNK_WM_HTML_INPUT_ID1[] /*PROGMEM*/ = ""; const char BLYNK_WM_FLDSET_START[] /*PROGMEM*/ = "
"; const char BLYNK_WM_FLDSET_END[] /*PROGMEM*/ = "
"; @@ -395,6 +411,16 @@ const char BLYNK_WM_HTML_SCRIPT_ITEM[] /*PROGMEM*/ = "udVal('{d}',document.getE const char BLYNK_WM_HTML_SCRIPT_END[] /*PROGMEM*/ = "alert('Updated');}"; const char BLYNK_WM_HTML_END[] /*PROGMEM*/ = ""; +#if SCAN_WIFI_NETWORKS +const char BLYNK_WM_SELECT_START[] /*PROGMEM*/ = "" + String(BLYNK_WM_DATALIST_START) + "'SSIDs'>" + ListOfSSIDs + BLYNK_WM_DATALIST_END); + BLYNK_LOG2(BLYNK_F("pitem:"), pitem); + pitem.replace("[[input_id1]]", "" + String(BLYNK_WM_DATALIST_START) + "'SSIDs'>" + ListOfSSIDs + BLYNK_WM_DATALIST_END); + + BLYNK_LOG2(BLYNK_F("pitem:"), pitem); + +#else + pitem.replace("[[input_id]]", "" + ListOfSSIDs + BLYNK_WM_SELECT_END); +#endif + + root_html_template += pitem + BLYNK_WM_FLDSET_START; + +#else + + pitem = String(BLYNK_WM_HTML_HEAD_END); + pitem.replace("[[input_id]]", BLYNK_WM_HTML_INPUT_ID); + pitem.replace("[[input_id1]]", BLYNK_WM_HTML_INPUT_ID1); + root_html_template += pitem + BLYNK_WM_FLDSET_START; + +#endif // SCAN_WIFI_NETWORKS #if USE_DYNAMIC_PARAMETERS for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) @@ -2307,16 +2448,24 @@ class BlynkWifi result.replace("BlynkSimpleEsp32_SSL_WM", BlynkESP32_WM_config.board_name); } - result.replace("[[id]]", BlynkESP32_WM_config.WiFi_Creds[0].wifi_ssid); - result.replace("[[pw]]", BlynkESP32_WM_config.WiFi_Creds[0].wifi_pw); - result.replace("[[id1]]", BlynkESP32_WM_config.WiFi_Creds[1].wifi_ssid); - result.replace("[[pw1]]", BlynkESP32_WM_config.WiFi_Creds[1].wifi_pw); - result.replace("[[sv]]", BlynkESP32_WM_config.Blynk_Creds[0].blynk_server); - result.replace("[[tk]]", BlynkESP32_WM_config.Blynk_Creds[0].blynk_token); - result.replace("[[sv1]]", BlynkESP32_WM_config.Blynk_Creds[1].blynk_server); - result.replace("[[tk1]]", BlynkESP32_WM_config.Blynk_Creds[1].blynk_token); - result.replace("[[pt]]", String(BlynkESP32_WM_config.blynk_port)); - result.replace("[[nm]]", BlynkESP32_WM_config.board_name); + if (hadConfigData) + { + result.replace("[[id]]", BlynkESP32_WM_config.WiFi_Creds[0].wifi_ssid); + result.replace("[[pw]]", BlynkESP32_WM_config.WiFi_Creds[0].wifi_pw); + result.replace("[[id1]]", BlynkESP32_WM_config.WiFi_Creds[1].wifi_ssid); + result.replace("[[pw1]]", BlynkESP32_WM_config.WiFi_Creds[1].wifi_pw); + result.replace("[[sv]]", BlynkESP32_WM_config.Blynk_Creds[0].blynk_server); + result.replace("[[tk]]", BlynkESP32_WM_config.Blynk_Creds[0].blynk_token); + result.replace("[[sv1]]", BlynkESP32_WM_config.Blynk_Creds[1].blynk_server); + result.replace("[[tk1]]", BlynkESP32_WM_config.Blynk_Creds[1].blynk_token); + result.replace("[[pt]]", String(BlynkESP32_WM_config.blynk_port)); + result.replace("[[nm]]", BlynkESP32_WM_config.board_name); + } + else + { + // Nullify the invalid data to avoid displaying garbage + memset(&BlynkESP32_WM_config, 0, sizeof(BlynkESP32_WM_config)); + } #if USE_DYNAMIC_PARAMETERS // Load default configuration @@ -2561,7 +2710,7 @@ class BlynkWifi BLYNK_LOG1(BLYNK_F("h:Rst")); - // Delay then reset the ESP8266 after save data + // Delay then reset the ESP32 after save data delay(1000); ESP.restart(); } @@ -2576,10 +2725,16 @@ class BlynkWifi #endif void startConfigurationMode() - { + { // turn the LED_BUILTIN ON to tell us we are in configuration mode. digitalWrite(LED_BUILTIN, LED_ON); +#if SCAN_WIFI_NETWORKS + configTimeout = 0; // To allow user input in CP + + WiFiNetworksFound = scanWifiNetworks(&indices); +#endif + if ( (portal_ssid == "") || portal_pass == "" ) { String chipID = String(ESP_getChipId(), HEX); @@ -2639,6 +2794,180 @@ class BlynkWifi configuration_mode = true; } + +#if SCAN_WIFI_NETWORKS + + // Source code adapted from https://github.com/khoih-prog/ESP_WiFiManager/blob/master/src/ESP_WiFiManager-Impl.h + + int _paramsCount = 0; + int _minimumQuality = -1; + bool _removeDuplicateAPs = true; + + ////////////////////////////////////////// + + void swap(int *thisOne, int *thatOne) + { + int tempo; + + tempo = *thatOne; + *thatOne = *thisOne; + *thisOne = tempo; + } + + ////////////////////////////////////////// + + void setMinimumSignalQuality(int quality) + { + _minimumQuality = quality; + } + + ////////////////////////////////////////// + + //if this is true, remove duplicate Access Points - default true + void setRemoveDuplicateAPs(bool removeDuplicates) + { + _removeDuplicateAPs = removeDuplicates; + } + + ////////////////////////////////////////// + + //Scan for WiFiNetworks in range and sort by signal strength + //space for indices array allocated on the heap and should be freed when no longer required + int scanWifiNetworks(int **indicesptr) + { + BLYNK_LOG1(BLYNK_F("Scanning Network")); + + int n = WiFi.scanNetworks(); + + BLYNK_LOG2(BLYNK_F("scanWifiNetworks: Done, Scanned Networks n = "), n); + + //KH, Terrible bug here. WiFi.scanNetworks() returns n < 0 => malloc( negative == very big ) => crash!!! + //In .../esp32/libraries/WiFi/src/WiFiType.h + //#define WIFI_SCAN_RUNNING (-1) + //#define WIFI_SCAN_FAILED (-2) + //if (n == 0) + if (n <= 0) + { + BLYNK_LOG1(BLYNK_F("No network found")); + return (0); + } + else + { + // Allocate space off the heap for indices array. + // This space should be freed when no longer required. + // Don't need to free as whenever CP is done, the system is reset + int* indices = (int *)malloc(n * sizeof(int)); + + if (indices == NULL) + { + BLYNK_LOG1(BLYNK_F("ERROR: Out of memory")); + *indicesptr = NULL; + return (0); + } + + *indicesptr = indices; + + //sort networks + for (int i = 0; i < n; i++) + { + indices[i] = i; + } + + BLYNK_LOG(BLYNK_F("Sorting")); + + // RSSI SORT + // old sort + for (int i = 0; i < n; i++) + { + for (int j = i + 1; j < n; j++) + { + if (WiFi.RSSI(indices[j]) > WiFi.RSSI(indices[i])) + { + //std::swap(indices[i], indices[j]); + // Using locally defined swap() + swap(&indices[i], &indices[j]); + } + } + } + + BLYNK_LOG1(BLYNK_F("Removing Dup")); + + // remove duplicates ( must be RSSI sorted ) + if (_removeDuplicateAPs) + { + String cssid; + + for (int i = 0; i < n; i++) + { + if (indices[i] == -1) + continue; + + cssid = WiFi.SSID(indices[i]); + + for (int j = i + 1; j < n; j++) + { + if (cssid == WiFi.SSID(indices[j])) + { + BLYNK_LOG2("DUP AP:", WiFi.SSID(indices[j])); + indices[j] = -1; // set dup aps to index -1 + } + } + } + } + + for (int i = 0; i < n; i++) + { + if (indices[i] == -1) + continue; // skip dups + + int quality = getRSSIasQuality(WiFi.RSSI(indices[i])); + + if (!(_minimumQuality == -1 || _minimumQuality < quality)) + { + indices[i] = -1; + BLYNK_LOG1(BLYNK_F("Skipping low quality")); + } + } + + BLYNK_LOG1(BLYNK_F("WiFi networks found:")); + + for (int i = 0; i < n; i++) + { + if (indices[i] == -1) + continue; // skip dups + else + BLYNK_LOG6(i+1,": ",WiFi.SSID(indices[i]), ", ", WiFi.RSSI(i), "dB"); + } + + return (n); + } + } + + ////////////////////////////////////////// + + int getRSSIasQuality(int RSSI) + { + int quality = 0; + + if (RSSI <= -100) + { + quality = 0; + } + else if (RSSI >= -50) + { + quality = 100; + } + else + { + quality = 2 * (RSSI + 100); + } + + return quality; + } + + ////////////////////////////////////////// + +#endif }; ////////////////////////////////////////////// diff --git a/src/BlynkSimpleEsp32_WM.h b/src/BlynkSimpleEsp32_WM.h index be74d32..dea3be9 100644 --- a/src/BlynkSimpleEsp32_WM.h +++ b/src/BlynkSimpleEsp32_WM.h @@ -16,7 +16,7 @@ @date Jan 2015 @brief - Version: 1.3.1 + Version: 1.4.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -45,6 +45,7 @@ 1.3.0 K Hoang 19/04/2021 Add LittleFS and SPIFFS support to ESP32-S2. Add support to ESP32-C3 without LittleFS Fix SSL issue with Blynk Cloud Server 1.3.1 K Hoang 24/04/2021 Fix issue of custom Blynk port (different from 8080 or 9443) not working on ESP32 + 1.4.0 K Hoang 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal ********************************************************************************************************************************/ #ifndef BlynkSimpleEsp32_WM_h @@ -54,7 +55,7 @@ #error This code is intended to run on the ESP32 platform! Please check your Tools->Board setting. #endif -#define BLYNK_WM_VERSION "Blynk_WM for ESP32 v1.3.1" +#define BLYNK_WM_VERSION "Blynk_WM for ESP32 v1.4.0" #define BLYNK_SEND_ATOMIC @@ -95,6 +96,37 @@ #warning Using EEPROM in BlynkSimpleESP32_WM.h #endif +////////////////////////////////////////////// + +// New from v1.4.0 +// KH, Some minor simplification +#if !defined(SCAN_WIFI_NETWORKS) + #define SCAN_WIFI_NETWORKS true //false +#endif + +#if SCAN_WIFI_NETWORKS + #warning Using SCAN_WIFI_NETWORKS + + #if !defined(MANUAL_SSID_INPUT_ALLOWED) + #define MANUAL_SSID_INPUT_ALLOWED true + #endif + + #if !defined(MAX_SSID_IN_LIST) + #define MAX_SSID_IN_LIST 10 + #elif (MAX_SSID_IN_LIST < 2) + #warning Parameter MAX_SSID_IN_LIST defined must be >= 2 - Reset to 10 + #undef MAX_SSID_IN_LIST + #define MAX_SSID_IN_LIST 10 + #elif (MAX_SSID_IN_LIST > 15) + #warning Parameter MAX_SSID_IN_LIST defined must be <= 15 - Reset to 10 + #undef MAX_SSID_IN_LIST + #define MAX_SSID_IN_LIST 10 + #endif +#else + #warning SCAN_WIFI_NETWORKS disabled +#endif + +///////// NEW for DRD ///////////// #if !defined(USING_MRD) #define USING_MRD false @@ -264,7 +296,6 @@ extern bool LOAD_DEFAULT_CONFIG_DATA; extern Blynk_WM_Configuration defaultConfig; //From v1.0.10, Permit special chars such as # and % - // -- HTML page fragments const char BLYNK_WM_HTML_HEAD_START[] /*PROGMEM*/ = "BlynkSimpleEsp32_WM"; @@ -272,16 +303,19 @@ const char BLYNK_WM_HTML_HEAD_START[] /*PROGMEM*/ = " const char BLYNK_WM_HTML_HEAD_STYLE[] /*PROGMEM*/ = ""; const char BLYNK_WM_HTML_HEAD_END[] /*PROGMEM*/ = "
\ -
\ -
\ -
\ -
\ +
[[input_id]]
\ +
\ +
[[input_id1]]
\ +
\
\
\
\
\
\ -
"; +
"; // DO NOT CHANGE THIS STRING EVER!!!! + +const char BLYNK_WM_HTML_INPUT_ID[] /*PROGMEM*/ = ""; +const char BLYNK_WM_HTML_INPUT_ID1[] /*PROGMEM*/ = ""; const char BLYNK_WM_FLDSET_START[] /*PROGMEM*/ = "
"; const char BLYNK_WM_FLDSET_END[] /*PROGMEM*/ = "
"; @@ -299,6 +333,16 @@ const char BLYNK_WM_HTML_SCRIPT_ITEM[] /*PROGMEM*/ = "udVal('{d}',document.getE const char BLYNK_WM_HTML_SCRIPT_END[] /*PROGMEM*/ = "alert('Updated');}"; const char BLYNK_WM_HTML_END[] /*PROGMEM*/ = ""; +#if SCAN_WIFI_NETWORKS +const char BLYNK_WM_SELECT_START[] /*PROGMEM*/ = "" + String(BLYNK_WM_DATALIST_START) + "'SSIDs'>" + ListOfSSIDs + BLYNK_WM_DATALIST_END); + BLYNK_LOG2(BLYNK_F("pitem:"), pitem); + pitem.replace("[[input_id1]]", "" + String(BLYNK_WM_DATALIST_START) + "'SSIDs'>" + ListOfSSIDs + BLYNK_WM_DATALIST_END); + + BLYNK_LOG2(BLYNK_F("pitem:"), pitem); + +#else + pitem.replace("[[input_id]]", "" + ListOfSSIDs + BLYNK_WM_SELECT_END); +#endif + + root_html_template += pitem + BLYNK_WM_FLDSET_START; + +#else + + pitem = String(BLYNK_WM_HTML_HEAD_END); + pitem.replace("[[input_id]]", BLYNK_WM_HTML_INPUT_ID); + pitem.replace("[[input_id1]]", BLYNK_WM_HTML_INPUT_ID1); + root_html_template += pitem + BLYNK_WM_FLDSET_START; + +#endif // SCAN_WIFI_NETWORKS #if USE_DYNAMIC_PARAMETERS for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) @@ -2269,16 +2428,24 @@ class BlynkWifi result.replace("BlynkSimpleEsp32_SSL_WM", BlynkESP32_WM_config.board_name); } - result.replace("[[id]]", BlynkESP32_WM_config.WiFi_Creds[0].wifi_ssid); - result.replace("[[pw]]", BlynkESP32_WM_config.WiFi_Creds[0].wifi_pw); - result.replace("[[id1]]", BlynkESP32_WM_config.WiFi_Creds[1].wifi_ssid); - result.replace("[[pw1]]", BlynkESP32_WM_config.WiFi_Creds[1].wifi_pw); - result.replace("[[sv]]", BlynkESP32_WM_config.Blynk_Creds[0].blynk_server); - result.replace("[[tk]]", BlynkESP32_WM_config.Blynk_Creds[0].blynk_token); - result.replace("[[sv1]]", BlynkESP32_WM_config.Blynk_Creds[1].blynk_server); - result.replace("[[tk1]]", BlynkESP32_WM_config.Blynk_Creds[1].blynk_token); - result.replace("[[pt]]", String(BlynkESP32_WM_config.blynk_port)); - result.replace("[[nm]]", BlynkESP32_WM_config.board_name); + if (hadConfigData) + { + result.replace("[[id]]", BlynkESP32_WM_config.WiFi_Creds[0].wifi_ssid); + result.replace("[[pw]]", BlynkESP32_WM_config.WiFi_Creds[0].wifi_pw); + result.replace("[[id1]]", BlynkESP32_WM_config.WiFi_Creds[1].wifi_ssid); + result.replace("[[pw1]]", BlynkESP32_WM_config.WiFi_Creds[1].wifi_pw); + result.replace("[[sv]]", BlynkESP32_WM_config.Blynk_Creds[0].blynk_server); + result.replace("[[tk]]", BlynkESP32_WM_config.Blynk_Creds[0].blynk_token); + result.replace("[[sv1]]", BlynkESP32_WM_config.Blynk_Creds[1].blynk_server); + result.replace("[[tk1]]", BlynkESP32_WM_config.Blynk_Creds[1].blynk_token); + result.replace("[[pt]]", String(BlynkESP32_WM_config.blynk_port)); + result.replace("[[nm]]", BlynkESP32_WM_config.board_name); + } + else + { + // Nullify the invalid data to avoid displaying garbage + memset(&BlynkESP32_WM_config, 0, sizeof(BlynkESP32_WM_config)); + } #if USE_DYNAMIC_PARAMETERS // Load default configuration @@ -2538,10 +2705,16 @@ class BlynkWifi #endif void startConfigurationMode() - { + { // turn the LED_BUILTIN ON to tell us we are in configuration mode. digitalWrite(LED_BUILTIN, LED_ON); +#if SCAN_WIFI_NETWORKS + configTimeout = 0; // To allow user input in CP + + WiFiNetworksFound = scanWifiNetworks(&indices); +#endif + if ( (portal_ssid == "") || portal_pass == "" ) { String chipID = String(ESP_getChipId(), HEX); @@ -2601,6 +2774,180 @@ class BlynkWifi configuration_mode = true; } + +#if SCAN_WIFI_NETWORKS + + // Source code adapted from https://github.com/khoih-prog/ESP_WiFiManager/blob/master/src/ESP_WiFiManager-Impl.h + + int _paramsCount = 0; + int _minimumQuality = -1; + bool _removeDuplicateAPs = true; + + ////////////////////////////////////////// + + void swap(int *thisOne, int *thatOne) + { + int tempo; + + tempo = *thatOne; + *thatOne = *thisOne; + *thisOne = tempo; + } + + ////////////////////////////////////////// + + void setMinimumSignalQuality(int quality) + { + _minimumQuality = quality; + } + + ////////////////////////////////////////// + + //if this is true, remove duplicate Access Points - default true + void setRemoveDuplicateAPs(bool removeDuplicates) + { + _removeDuplicateAPs = removeDuplicates; + } + + ////////////////////////////////////////// + + //Scan for WiFiNetworks in range and sort by signal strength + //space for indices array allocated on the heap and should be freed when no longer required + int scanWifiNetworks(int **indicesptr) + { + BLYNK_LOG1(BLYNK_F("Scanning Network")); + + int n = WiFi.scanNetworks(); + + BLYNK_LOG2(BLYNK_F("scanWifiNetworks: Done, Scanned Networks n = "), n); + + //KH, Terrible bug here. WiFi.scanNetworks() returns n < 0 => malloc( negative == very big ) => crash!!! + //In .../esp32/libraries/WiFi/src/WiFiType.h + //#define WIFI_SCAN_RUNNING (-1) + //#define WIFI_SCAN_FAILED (-2) + //if (n == 0) + if (n <= 0) + { + BLYNK_LOG1(BLYNK_F("No network found")); + return (0); + } + else + { + // Allocate space off the heap for indices array. + // This space should be freed when no longer required. + // Don't need to free as whenever CP is done, the system is reset + int* indices = (int *)malloc(n * sizeof(int)); + + if (indices == NULL) + { + BLYNK_LOG1(BLYNK_F("ERROR: Out of memory")); + *indicesptr = NULL; + return (0); + } + + *indicesptr = indices; + + //sort networks + for (int i = 0; i < n; i++) + { + indices[i] = i; + } + + BLYNK_LOG(BLYNK_F("Sorting")); + + // RSSI SORT + // old sort + for (int i = 0; i < n; i++) + { + for (int j = i + 1; j < n; j++) + { + if (WiFi.RSSI(indices[j]) > WiFi.RSSI(indices[i])) + { + //std::swap(indices[i], indices[j]); + // Using locally defined swap() + swap(&indices[i], &indices[j]); + } + } + } + + BLYNK_LOG1(BLYNK_F("Removing Dup")); + + // remove duplicates ( must be RSSI sorted ) + if (_removeDuplicateAPs) + { + String cssid; + + for (int i = 0; i < n; i++) + { + if (indices[i] == -1) + continue; + + cssid = WiFi.SSID(indices[i]); + + for (int j = i + 1; j < n; j++) + { + if (cssid == WiFi.SSID(indices[j])) + { + BLYNK_LOG2("DUP AP:", WiFi.SSID(indices[j])); + indices[j] = -1; // set dup aps to index -1 + } + } + } + } + + for (int i = 0; i < n; i++) + { + if (indices[i] == -1) + continue; // skip dups + + int quality = getRSSIasQuality(WiFi.RSSI(indices[i])); + + if (!(_minimumQuality == -1 || _minimumQuality < quality)) + { + indices[i] = -1; + BLYNK_LOG1(BLYNK_F("Skipping low quality")); + } + } + + BLYNK_LOG1(BLYNK_F("WiFi networks found:")); + + for (int i = 0; i < n; i++) + { + if (indices[i] == -1) + continue; // skip dups + else + BLYNK_LOG6(i+1,": ",WiFi.SSID(indices[i]), ", ", WiFi.RSSI(i), "dB"); + } + + return (n); + } + } + + ////////////////////////////////////////// + + int getRSSIasQuality(int RSSI) + { + int quality = 0; + + if (RSSI <= -100) + { + quality = 0; + } + else if (RSSI >= -50) + { + quality = 100; + } + else + { + quality = 2 * (RSSI + 100); + } + + return quality; + } + + ////////////////////////////////////////// + +#endif }; ////////////////////////////////////////////// diff --git a/src/BlynkSimpleEsp8266_SSL_WM.h b/src/BlynkSimpleEsp8266_SSL_WM.h index d41ea0f..8bd340f 100644 --- a/src/BlynkSimpleEsp8266_SSL_WM.h +++ b/src/BlynkSimpleEsp8266_SSL_WM.h @@ -16,7 +16,7 @@ @date Jan 2015 @brief - Version: 1.3.1 + Version: 1.4.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -45,6 +45,7 @@ 1.3.0 K Hoang 19/04/2021 Add LittleFS and SPIFFS support to ESP32-S2. Add support to ESP32-C3 without LittleFS Fix SSL issue with Blynk Cloud Server 1.3.1 K Hoang 24/04/2021 Fix issue of custom Blynk port (different from 8080 or 9443) not working on ESP32 + 1.4.0 K Hoang 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal ********************************************************************************************************************************/ #ifndef BlynkSimpleEsp8266_SSL_WM_h @@ -54,7 +55,7 @@ #error This code is intended to run on the ESP8266 platform! Please check your Tools->Board setting. #endif -#define BLYNK_WM_VERSION "Blynk_WM SSL for ESP8266 v1.3.1" +#define BLYNK_WM_VERSION "Blynk_WM SSL for ESP8266 v1.4.0" #include @@ -104,6 +105,38 @@ static const unsigned char BLYNK_DEFAULT_CERT_DER[] PROGMEM = #warning Using EEPROM in BlynkSimpleEsp8266_SSL_WM.h #endif +////////////////////////////////////////////// + +// New from v1.4.0 +// KH, Some minor simplification +#if !defined(SCAN_WIFI_NETWORKS) + #define SCAN_WIFI_NETWORKS true //false +#endif + +#if SCAN_WIFI_NETWORKS + #warning Using SCAN_WIFI_NETWORKS + + #if !defined(MANUAL_SSID_INPUT_ALLOWED) + #define MANUAL_SSID_INPUT_ALLOWED true + #endif + + #if !defined(MAX_SSID_IN_LIST) + #define MAX_SSID_IN_LIST 10 + #elif (MAX_SSID_IN_LIST < 2) + #warning Parameter MAX_SSID_IN_LIST defined must be >= 2 - Reset to 10 + #undef MAX_SSID_IN_LIST + #define MAX_SSID_IN_LIST 10 + #elif (MAX_SSID_IN_LIST > 15) + #warning Parameter MAX_SSID_IN_LIST defined must be <= 15 - Reset to 10 + #undef MAX_SSID_IN_LIST + #define MAX_SSID_IN_LIST 10 + #endif +#else + #warning SCAN_WIFI_NETWORKS disabled +#endif + +///////// NEW for DRD ///////////// + #if !defined(USING_MRD) #define USING_MRD false #endif @@ -356,7 +389,6 @@ extern bool LOAD_DEFAULT_CONFIG_DATA; extern Blynk_WM_Configuration defaultConfig; //From v1.0.10, Permit special chars such as # and % - // -- HTML page fragments const char BLYNK_WM_HTML_HEAD_START[] /*PROGMEM*/ = "BlynkSimpleEsp8266_SSL_WM"; @@ -364,16 +396,19 @@ const char BLYNK_WM_HTML_HEAD_START[] /*PROGMEM*/ = " const char BLYNK_WM_HTML_HEAD_STYLE[] /*PROGMEM*/ = ""; const char BLYNK_WM_HTML_HEAD_END[] /*PROGMEM*/ = "
\ -
\ -
\ -
\ -
\ +
[[input_id]]
\ +
\ +
[[input_id1]]
\ +
\
\
\
\
\
\ -
"; +
"; // DO NOT CHANGE THIS STRING EVER!!!! + +const char BLYNK_WM_HTML_INPUT_ID[] /*PROGMEM*/ = ""; +const char BLYNK_WM_HTML_INPUT_ID1[] /*PROGMEM*/ = ""; const char BLYNK_WM_FLDSET_START[] /*PROGMEM*/ = "
"; const char BLYNK_WM_FLDSET_END[] /*PROGMEM*/ = "
"; @@ -391,6 +426,16 @@ const char BLYNK_WM_HTML_SCRIPT_ITEM[] /*PROGMEM*/ = "udVal('{d}',document.getE const char BLYNK_WM_HTML_SCRIPT_END[] /*PROGMEM*/ = "alert('Updated');}"; const char BLYNK_WM_HTML_END[] /*PROGMEM*/ = ""; +#if SCAN_WIFI_NETWORKS +const char BLYNK_WM_SELECT_START[] /*PROGMEM*/ = "" + String(BLYNK_WM_DATALIST_START) + "'SSIDs'>" + ListOfSSIDs + BLYNK_WM_DATALIST_END); + BLYNK_LOG2(BLYNK_F("pitem:"), pitem); + pitem.replace("[[input_id1]]", "" + String(BLYNK_WM_DATALIST_START) + "'SSIDs'>" + ListOfSSIDs + BLYNK_WM_DATALIST_END); + + BLYNK_LOG2(BLYNK_F("pitem:"), pitem); + +#else + pitem.replace("[[input_id]]", "" + ListOfSSIDs + BLYNK_WM_SELECT_END); +#endif + + root_html_template += pitem + BLYNK_WM_FLDSET_START; + +#else + + pitem = String(BLYNK_WM_HTML_HEAD_END); + pitem.replace("[[input_id]]", BLYNK_WM_HTML_INPUT_ID); + pitem.replace("[[input_id1]]", BLYNK_WM_HTML_INPUT_ID1); + root_html_template += pitem + BLYNK_WM_FLDSET_START; + +#endif // SCAN_WIFI_NETWORKS #if USE_DYNAMIC_PARAMETERS for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) @@ -2297,16 +2459,24 @@ class BlynkWifi result.replace("BlynkSimpleEsp8266_SSL_WM", Blynk8266_WM_config.board_name); } - result.replace("[[id]]", Blynk8266_WM_config.WiFi_Creds[0].wifi_ssid); - result.replace("[[pw]]", Blynk8266_WM_config.WiFi_Creds[0].wifi_pw); - result.replace("[[id1]]", Blynk8266_WM_config.WiFi_Creds[1].wifi_ssid); - result.replace("[[pw1]]", Blynk8266_WM_config.WiFi_Creds[1].wifi_pw); - result.replace("[[sv]]", Blynk8266_WM_config.Blynk_Creds[0].blynk_server); - result.replace("[[tk]]", Blynk8266_WM_config.Blynk_Creds[0].blynk_token); - result.replace("[[sv1]]", Blynk8266_WM_config.Blynk_Creds[1].blynk_server); - result.replace("[[tk1]]", Blynk8266_WM_config.Blynk_Creds[1].blynk_token); - result.replace("[[pt]]", String(Blynk8266_WM_config.blynk_port)); - result.replace("[[nm]]", Blynk8266_WM_config.board_name); + if (hadConfigData) + { + result.replace("[[id]]", Blynk8266_WM_config.WiFi_Creds[0].wifi_ssid); + result.replace("[[pw]]", Blynk8266_WM_config.WiFi_Creds[0].wifi_pw); + result.replace("[[id1]]", Blynk8266_WM_config.WiFi_Creds[1].wifi_ssid); + result.replace("[[pw1]]", Blynk8266_WM_config.WiFi_Creds[1].wifi_pw); + result.replace("[[sv]]", Blynk8266_WM_config.Blynk_Creds[0].blynk_server); + result.replace("[[tk]]", Blynk8266_WM_config.Blynk_Creds[0].blynk_token); + result.replace("[[sv1]]", Blynk8266_WM_config.Blynk_Creds[1].blynk_server); + result.replace("[[tk1]]", Blynk8266_WM_config.Blynk_Creds[1].blynk_token); + result.replace("[[pt]]", String(Blynk8266_WM_config.blynk_port)); + result.replace("[[nm]]", Blynk8266_WM_config.board_name); + } + else + { + // Nullify the invalid data to avoid displaying garbage + memset(&Blynk8266_WM_config, 0, sizeof(Blynk8266_WM_config)); + } #if USE_DYNAMIC_PARAMETERS // Load default configuration @@ -2566,10 +2736,16 @@ class BlynkWifi #endif void startConfigurationMode() - { + { // turn the LED_BUILTIN ON to tell us we are in configuration mode. digitalWrite(LED_BUILTIN, LED_ON); +#if SCAN_WIFI_NETWORKS + configTimeout = 0; // To allow user input in CP + + WiFiNetworksFound = scanWifiNetworks(&indices); +#endif + if ( (portal_ssid == "") || portal_pass == "" ) { String chipID = String(ESP.getChipId(), HEX); @@ -2630,6 +2806,180 @@ class BlynkWifi configuration_mode = true; } + +#if SCAN_WIFI_NETWORKS + + // Source code adapted from https://github.com/khoih-prog/ESP_WiFiManager/blob/master/src/ESP_WiFiManager-Impl.h + + int _paramsCount = 0; + int _minimumQuality = -1; + bool _removeDuplicateAPs = true; + + ////////////////////////////////////////// + + void swap(int *thisOne, int *thatOne) + { + int tempo; + + tempo = *thatOne; + *thatOne = *thisOne; + *thisOne = tempo; + } + + ////////////////////////////////////////// + + void setMinimumSignalQuality(int quality) + { + _minimumQuality = quality; + } + + ////////////////////////////////////////// + + //if this is true, remove duplicate Access Points - default true + void setRemoveDuplicateAPs(bool removeDuplicates) + { + _removeDuplicateAPs = removeDuplicates; + } + + ////////////////////////////////////////// + + //Scan for WiFiNetworks in range and sort by signal strength + //space for indices array allocated on the heap and should be freed when no longer required + int scanWifiNetworks(int **indicesptr) + { + BLYNK_LOG1(BLYNK_F("Scanning Network")); + + int n = WiFi.scanNetworks(); + + BLYNK_LOG2(BLYNK_F("scanWifiNetworks: Done, Scanned Networks n = "), n); + + //KH, Terrible bug here. WiFi.scanNetworks() returns n < 0 => malloc( negative == very big ) => crash!!! + //In .../esp32/libraries/WiFi/src/WiFiType.h + //#define WIFI_SCAN_RUNNING (-1) + //#define WIFI_SCAN_FAILED (-2) + //if (n == 0) + if (n <= 0) + { + BLYNK_LOG1(BLYNK_F("No network found")); + return (0); + } + else + { + // Allocate space off the heap for indices array. + // This space should be freed when no longer required. + // Don't need to free as whenever CP is done, the system is reset + int* indices = (int *)malloc(n * sizeof(int)); + + if (indices == NULL) + { + BLYNK_LOG1(BLYNK_F("ERROR: Out of memory")); + *indicesptr = NULL; + return (0); + } + + *indicesptr = indices; + + //sort networks + for (int i = 0; i < n; i++) + { + indices[i] = i; + } + + BLYNK_LOG(BLYNK_F("Sorting")); + + // RSSI SORT + // old sort + for (int i = 0; i < n; i++) + { + for (int j = i + 1; j < n; j++) + { + if (WiFi.RSSI(indices[j]) > WiFi.RSSI(indices[i])) + { + //std::swap(indices[i], indices[j]); + // Using locally defined swap() + swap(&indices[i], &indices[j]); + } + } + } + + BLYNK_LOG1(BLYNK_F("Removing Dup")); + + // remove duplicates ( must be RSSI sorted ) + if (_removeDuplicateAPs) + { + String cssid; + + for (int i = 0; i < n; i++) + { + if (indices[i] == -1) + continue; + + cssid = WiFi.SSID(indices[i]); + + for (int j = i + 1; j < n; j++) + { + if (cssid == WiFi.SSID(indices[j])) + { + BLYNK_LOG2("DUP AP:", WiFi.SSID(indices[j])); + indices[j] = -1; // set dup aps to index -1 + } + } + } + } + + for (int i = 0; i < n; i++) + { + if (indices[i] == -1) + continue; // skip dups + + int quality = getRSSIasQuality(WiFi.RSSI(indices[i])); + + if (!(_minimumQuality == -1 || _minimumQuality < quality)) + { + indices[i] = -1; + BLYNK_LOG1(BLYNK_F("Skipping low quality")); + } + } + + BLYNK_LOG1(BLYNK_F("WiFi networks found:")); + + for (int i = 0; i < n; i++) + { + if (indices[i] == -1) + continue; // skip dups + else + BLYNK_LOG6(i+1,": ",WiFi.SSID(indices[i]), ", ", WiFi.RSSI(i), "dB"); + } + + return (n); + } + } + + ////////////////////////////////////////// + + int getRSSIasQuality(int RSSI) + { + int quality = 0; + + if (RSSI <= -100) + { + quality = 0; + } + else if (RSSI >= -50) + { + quality = 100; + } + else + { + quality = 2 * (RSSI + 100); + } + + return quality; + } + + ////////////////////////////////////////// + +#endif }; ////////////////////////////////////////////// diff --git a/src/BlynkSimpleEsp8266_WM.h b/src/BlynkSimpleEsp8266_WM.h index 427eb00..a43b458 100644 --- a/src/BlynkSimpleEsp8266_WM.h +++ b/src/BlynkSimpleEsp8266_WM.h @@ -16,7 +16,7 @@ @date Jan 2015 @brief - Version: 1.3.1 + Version: 1.4.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -45,6 +45,7 @@ 1.3.0 K Hoang 19/04/2021 Add LittleFS and SPIFFS support to ESP32-S2. Add support to ESP32-C3 without LittleFS Fix SSL issue with Blynk Cloud Server 1.3.1 K Hoang 24/04/2021 Fix issue of custom Blynk port (different from 8080 or 9443) not working on ESP32 + 1.4.0 K Hoang 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal ********************************************************************************************************************************/ @@ -55,7 +56,7 @@ #error This code is intended to run on the ESP8266 platform! Please check your Tools->Board setting. #endif -#define BLYNK_WM_VERSION "Blynk_WM for ESP8266 v1.3.1" +#define BLYNK_WM_VERSION "Blynk_WM for ESP8266 v1.4.0" #include @@ -90,6 +91,38 @@ #warning Using EEPROM in BlynkSimpleESP8266_WM.h #endif +////////////////////////////////////////////// + +// New from v1.4.0 +// KH, Some minor simplification +#if !defined(SCAN_WIFI_NETWORKS) + #define SCAN_WIFI_NETWORKS true //false +#endif + +#if SCAN_WIFI_NETWORKS + #warning Using SCAN_WIFI_NETWORKS + + #if !defined(MANUAL_SSID_INPUT_ALLOWED) + #define MANUAL_SSID_INPUT_ALLOWED true + #endif + + #if !defined(MAX_SSID_IN_LIST) + #define MAX_SSID_IN_LIST 10 + #elif (MAX_SSID_IN_LIST < 2) + #warning Parameter MAX_SSID_IN_LIST defined must be >= 2 - Reset to 10 + #undef MAX_SSID_IN_LIST + #define MAX_SSID_IN_LIST 10 + #elif (MAX_SSID_IN_LIST > 15) + #warning Parameter MAX_SSID_IN_LIST defined must be <= 15 - Reset to 10 + #undef MAX_SSID_IN_LIST + #define MAX_SSID_IN_LIST 10 + #endif +#else + #warning SCAN_WIFI_NETWORKS disabled +#endif + +///////// NEW for DRD ///////////// + #if !defined(USING_MRD) #define USING_MRD false #endif @@ -256,9 +289,6 @@ extern bool LOAD_DEFAULT_CONFIG_DATA; extern Blynk_WM_Configuration defaultConfig; //From v1.0.10, Permit special chars such as # and % - -// -- HTML page fragments - // -- HTML page fragments const char BLYNK_WM_HTML_HEAD_START[] /*PROGMEM*/ = "BlynkSimpleEsp8266_WM"; @@ -266,16 +296,19 @@ const char BLYNK_WM_HTML_HEAD_START[] /*PROGMEM*/ = " const char BLYNK_WM_HTML_HEAD_STYLE[] /*PROGMEM*/ = ""; const char BLYNK_WM_HTML_HEAD_END[] /*PROGMEM*/ = "
\ -
\ -
\ -
\ -
\ +
[[input_id]]
\ +
\ +
[[input_id1]]
\ +
\
\
\
\
\
\ -
"; +
"; // DO NOT CHANGE THIS STRING EVER!!!! + +const char BLYNK_WM_HTML_INPUT_ID[] /*PROGMEM*/ = ""; +const char BLYNK_WM_HTML_INPUT_ID1[] /*PROGMEM*/ = ""; const char BLYNK_WM_FLDSET_START[] /*PROGMEM*/ = "
"; const char BLYNK_WM_FLDSET_END[] /*PROGMEM*/ = "
"; @@ -293,6 +326,16 @@ const char BLYNK_WM_HTML_SCRIPT_ITEM[] /*PROGMEM*/ = "udVal('{d}',document.getE const char BLYNK_WM_HTML_SCRIPT_END[] /*PROGMEM*/ = "alert('Updated');}"; const char BLYNK_WM_HTML_END[] /*PROGMEM*/ = ""; +#if SCAN_WIFI_NETWORKS +const char BLYNK_WM_SELECT_START[] /*PROGMEM*/ = "" + String(BLYNK_WM_DATALIST_START) + "'SSIDs'>" + ListOfSSIDs + BLYNK_WM_DATALIST_END); + BLYNK_LOG2(BLYNK_F("pitem:"), pitem); + pitem.replace("[[input_id1]]", "" + String(BLYNK_WM_DATALIST_START) + "'SSIDs'>" + ListOfSSIDs + BLYNK_WM_DATALIST_END); + + BLYNK_LOG2(BLYNK_F("pitem:"), pitem); + +#else + pitem.replace("[[input_id]]", "" + ListOfSSIDs + BLYNK_WM_SELECT_END); +#endif + + root_html_template += pitem + BLYNK_WM_FLDSET_START; + +#else + + pitem = String(BLYNK_WM_HTML_HEAD_END); + pitem.replace("[[input_id]]", BLYNK_WM_HTML_INPUT_ID); + pitem.replace("[[input_id1]]", BLYNK_WM_HTML_INPUT_ID1); + root_html_template += pitem + BLYNK_WM_FLDSET_START; + +#endif // SCAN_WIFI_NETWORKS #if USE_DYNAMIC_PARAMETERS for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) @@ -2129,19 +2290,19 @@ class BlynkWifi void serverSendHeaders() { - BLYNK_LOG4(F("serverSendHeaders:WM_HTTP_CACHE_CONTROL:"), WM_HTTP_CACHE_CONTROL, "=", WM_HTTP_NO_STORE); + BLYNK_LOG4(BLYNK_F("serverSendHeaders:WM_HTTP_CACHE_CONTROL:"), WM_HTTP_CACHE_CONTROL, "=", WM_HTTP_NO_STORE); server->sendHeader(WM_HTTP_CACHE_CONTROL, WM_HTTP_NO_STORE); #if USING_CORS_FEATURE // New from v1.2.0, for configure CORS Header, default to WM_HTTP_CORS_ALLOW_ALL = "*" - BLYNK_LOG4(F("serverSendHeaders:WM_HTTP_CORS:"), WM_HTTP_CORS, " : ", _CORS_Header); + BLYNK_LOG4(BLYNK_F("serverSendHeaders:WM_HTTP_CORS:"), WM_HTTP_CORS, " : ", _CORS_Header); server->sendHeader(WM_HTTP_CORS, _CORS_Header); #endif - BLYNK_LOG4(F("serverSendHeaders:WM_HTTP_PRAGMA:"), WM_HTTP_PRAGMA, " : ", WM_HTTP_NO_CACHE); + BLYNK_LOG4(BLYNK_F("serverSendHeaders:WM_HTTP_PRAGMA:"), WM_HTTP_PRAGMA, " : ", WM_HTTP_NO_CACHE); server->sendHeader(WM_HTTP_PRAGMA, WM_HTTP_NO_CACHE); - BLYNK_LOG4(F("serverSendHeaders:WM_HTTP_EXPIRES:"), WM_HTTP_EXPIRES, " : ", "-1"); + BLYNK_LOG4(BLYNK_F("serverSendHeaders:WM_HTTP_EXPIRES:"), WM_HTTP_EXPIRES, " : ", "-1"); server->sendHeader(WM_HTTP_EXPIRES, "-1"); } @@ -2223,16 +2384,24 @@ class BlynkWifi result.replace("BlynkSimpleEsp8266_WM", Blynk8266_WM_config.board_name); } - result.replace("[[id]]", Blynk8266_WM_config.WiFi_Creds[0].wifi_ssid); - result.replace("[[pw]]", Blynk8266_WM_config.WiFi_Creds[0].wifi_pw); - result.replace("[[id1]]", Blynk8266_WM_config.WiFi_Creds[1].wifi_ssid); - result.replace("[[pw1]]", Blynk8266_WM_config.WiFi_Creds[1].wifi_pw); - result.replace("[[sv]]", Blynk8266_WM_config.Blynk_Creds[0].blynk_server); - result.replace("[[tk]]", Blynk8266_WM_config.Blynk_Creds[0].blynk_token); - result.replace("[[sv1]]", Blynk8266_WM_config.Blynk_Creds[1].blynk_server); - result.replace("[[tk1]]", Blynk8266_WM_config.Blynk_Creds[1].blynk_token); - result.replace("[[pt]]", String(Blynk8266_WM_config.blynk_port)); - result.replace("[[nm]]", Blynk8266_WM_config.board_name); + if (hadConfigData) + { + result.replace("[[id]]", Blynk8266_WM_config.WiFi_Creds[0].wifi_ssid); + result.replace("[[pw]]", Blynk8266_WM_config.WiFi_Creds[0].wifi_pw); + result.replace("[[id1]]", Blynk8266_WM_config.WiFi_Creds[1].wifi_ssid); + result.replace("[[pw1]]", Blynk8266_WM_config.WiFi_Creds[1].wifi_pw); + result.replace("[[sv]]", Blynk8266_WM_config.Blynk_Creds[0].blynk_server); + result.replace("[[tk]]", Blynk8266_WM_config.Blynk_Creds[0].blynk_token); + result.replace("[[sv1]]", Blynk8266_WM_config.Blynk_Creds[1].blynk_server); + result.replace("[[tk1]]", Blynk8266_WM_config.Blynk_Creds[1].blynk_token); + result.replace("[[pt]]", String(Blynk8266_WM_config.blynk_port)); + result.replace("[[nm]]", Blynk8266_WM_config.board_name); + } + else + { + // Nullify the invalid data to avoid displaying garbage + memset(&Blynk8266_WM_config, 0, sizeof(Blynk8266_WM_config)); + } #if USE_DYNAMIC_PARAMETERS // Load default configuration @@ -2492,10 +2661,17 @@ class BlynkWifi #endif void startConfigurationMode() - { + { // turn the LED_BUILTIN ON to tell us we are in configuration mode. digitalWrite(LED_BUILTIN, LED_ON); +#if SCAN_WIFI_NETWORKS + configTimeout = 0; // To allow user input in CP + + WiFiNetworksFound = scanWifiNetworks(&indices); +#endif + + if ( (portal_ssid == "") || portal_pass == "" ) { String chipID = String(ESP.getChipId(), HEX); @@ -2556,6 +2732,180 @@ class BlynkWifi configuration_mode = true; } + +#if SCAN_WIFI_NETWORKS + + // Source code adapted from https://github.com/khoih-prog/ESP_WiFiManager/blob/master/src/ESP_WiFiManager-Impl.h + + int _paramsCount = 0; + int _minimumQuality = -1; + bool _removeDuplicateAPs = true; + + ////////////////////////////////////////// + + void swap(int *thisOne, int *thatOne) + { + int tempo; + + tempo = *thatOne; + *thatOne = *thisOne; + *thisOne = tempo; + } + + ////////////////////////////////////////// + + void setMinimumSignalQuality(int quality) + { + _minimumQuality = quality; + } + + ////////////////////////////////////////// + + //if this is true, remove duplicate Access Points - default true + void setRemoveDuplicateAPs(bool removeDuplicates) + { + _removeDuplicateAPs = removeDuplicates; + } + + ////////////////////////////////////////// + + //Scan for WiFiNetworks in range and sort by signal strength + //space for indices array allocated on the heap and should be freed when no longer required + int scanWifiNetworks(int **indicesptr) + { + BLYNK_LOG1(BLYNK_F("Scanning Network")); + + int n = WiFi.scanNetworks(); + + BLYNK_LOG2(BLYNK_F("scanWifiNetworks: Done, Scanned Networks n = "), n); + + //KH, Terrible bug here. WiFi.scanNetworks() returns n < 0 => malloc( negative == very big ) => crash!!! + //In .../esp32/libraries/WiFi/src/WiFiType.h + //#define WIFI_SCAN_RUNNING (-1) + //#define WIFI_SCAN_FAILED (-2) + //if (n == 0) + if (n <= 0) + { + BLYNK_LOG1(BLYNK_F("No network found")); + return (0); + } + else + { + // Allocate space off the heap for indices array. + // This space should be freed when no longer required. + // Don't need to free as whenever CP is done, the system is reset + int* indices = (int *)malloc(n * sizeof(int)); + + if (indices == NULL) + { + BLYNK_LOG1(BLYNK_F("ERROR: Out of memory")); + *indicesptr = NULL; + return (0); + } + + *indicesptr = indices; + + //sort networks + for (int i = 0; i < n; i++) + { + indices[i] = i; + } + + BLYNK_LOG(BLYNK_F("Sorting")); + + // RSSI SORT + // old sort + for (int i = 0; i < n; i++) + { + for (int j = i + 1; j < n; j++) + { + if (WiFi.RSSI(indices[j]) > WiFi.RSSI(indices[i])) + { + //std::swap(indices[i], indices[j]); + // Using locally defined swap() + swap(&indices[i], &indices[j]); + } + } + } + + BLYNK_LOG1(BLYNK_F("Removing Dup")); + + // remove duplicates ( must be RSSI sorted ) + if (_removeDuplicateAPs) + { + String cssid; + + for (int i = 0; i < n; i++) + { + if (indices[i] == -1) + continue; + + cssid = WiFi.SSID(indices[i]); + + for (int j = i + 1; j < n; j++) + { + if (cssid == WiFi.SSID(indices[j])) + { + BLYNK_LOG2("DUP AP:", WiFi.SSID(indices[j])); + indices[j] = -1; // set dup aps to index -1 + } + } + } + } + + for (int i = 0; i < n; i++) + { + if (indices[i] == -1) + continue; // skip dups + + int quality = getRSSIasQuality(WiFi.RSSI(indices[i])); + + if (!(_minimumQuality == -1 || _minimumQuality < quality)) + { + indices[i] = -1; + BLYNK_LOG1(BLYNK_F("Skipping low quality")); + } + } + + BLYNK_LOG1(BLYNK_F("WiFi networks found:")); + + for (int i = 0; i < n; i++) + { + if (indices[i] == -1) + continue; // skip dups + else + BLYNK_LOG6(i+1,": ",WiFi.SSID(indices[i]), ", ", WiFi.RSSI(i), "dB"); + } + + return (n); + } + } + + ////////////////////////////////////////// + + int getRSSIasQuality(int RSSI) + { + int quality = 0; + + if (RSSI <= -100) + { + quality = 0; + } + else if (RSSI >= -50) + { + quality = 100; + } + else + { + quality = 2 * (RSSI + 100); + } + + return quality; + } + + ////////////////////////////////////////// + +#endif }; //////////////////////////////////////////////