From 8498c80d9eea33965baa9d6fa912f92761f76892 Mon Sep 17 00:00:00 2001 From: Khoi Hoang <57012152+khoih-prog@users.noreply.github.com> Date: Sat, 24 Apr 2021 20:26:05 -0400 Subject: [PATCH] v1.3.0 to enable scan of WiFi networks ### Major Release v1.3.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-6 for ESP8266-AT or 2-15 for other) 2. Minor enhancement to not display garbage when data is invalid 3. Tested with new [Arduino Core for STM32 v2.0.0](https://github.com/stm32duino/Arduino_Core_STM32) and add support to new STM32L5 boards. --- CONTRIBUTING.md | 6 +- README.md | 236 +++++++++++-- examples/MKR1000_WiFi101/MKR1000_WiFi101.ino | 5 +- examples/MKR1000_WiFi101/defines.h | 14 +- .../MKR1000_WiFi101_MQTT.ino | 5 +- examples/MKR1000_WiFi101_MQTT/defines.h | 14 +- examples/SAMD_WiFi/SAMD_WiFi.ino | 5 +- examples/SAMD_WiFi/defines.h | 25 +- examples/SAMD_WiFi_MQTT/SAMD_WiFi_MQTT.ino | 5 +- examples/SAMD_WiFi_MQTT/defines.h | 21 +- examples/SAM_DUE_WiFi/SAM_DUE_WiFi.ino | 5 +- examples/SAM_DUE_WiFi/defines.h | 19 +- .../SAM_DUE_WiFi_MQTT/SAM_DUE_WiFi_MQTT.ino | 5 +- examples/SAM_DUE_WiFi_MQTT/defines.h | 21 +- examples/STM32_WiFi/STM32_WiFi.ino | 5 +- examples/STM32_WiFi/defines.h | 28 +- examples/STM32_WiFi_MQTT/STM32_WiFi_MQTT.ino | 5 +- examples/STM32_WiFi_MQTT/defines.h | 30 +- examples/Teensy_WiFi/Teensy_WiFi.ino | 5 +- examples/Teensy_WiFi/defines.h | 19 +- .../Teensy_WiFi_MQTT/Teensy_WiFi_MQTT.ino | 5 +- examples/Teensy_WiFi_MQTT/defines.h | 21 +- examples/nRF52_WiFi/defines.h | 19 +- examples/nRF52_WiFi/nRF52_WiFi.ino | 5 +- examples/nRF52_WiFi_MQTT/defines.h | 21 +- examples/nRF52_WiFi_MQTT/nRF52_WiFi_MQTT.ino | 5 +- library.json | 2 +- library.properties | 2 +- pics/ESP_AT_Input_With_Scan.png | Bin 0 -> 19580 bytes pics/Input_With_Scan.png | Bin 0 -> 36371 bytes src/WiFiManager_Generic_Lite_DUE.h | 326 ++++++++++++++++- src/WiFiManager_Generic_Lite_Debug.h | 5 +- src/WiFiManager_Generic_Lite_SAMD.h | 328 ++++++++++++++++- src/WiFiManager_Generic_Lite_STM32.h | 332 +++++++++++++++++- src/WiFiManager_Generic_Lite_Teensy.h | 326 ++++++++++++++++- src/WiFiManager_Generic_Lite_nRF52.h | 326 ++++++++++++++++- 36 files changed, 2037 insertions(+), 164 deletions(-) create mode 100644 pics/ESP_AT_Input_With_Scan.png create mode 100644 pics/Input_With_Scan.png diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c8248de1..bf4bc31c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,7 +15,7 @@ If you don't find anything, please [open a new issue](https://github.com/khoih-p Please ensure to specify the following: * Arduino IDE version (e.g. 1.8.13) or Platform.io version -* Board Core Version (e.g. Arduino SAMDUE core v1.6.12, ESP8266 core v2.7.4, etc.) +* Board Core Version (e.g. Arduino SAMD core v1.8.11, ESP8266 core v2.7.4, etc.) * Contextual information (e.g. what you were trying to achieve) * Simplest possible steps to reproduce * Anything that might be relevant in your opinion, such as: @@ -27,9 +27,9 @@ Please ensure to specify the following: ``` Arduino IDE version: 1.8.13 -Arduino SAM DUE Core Version 1.6.12 +Arduino SAMD Core Version 1.8.11 OS: Ubuntu 20.04 LTS -Linux xy-Inspiron-3593 5.4.0-71-generic #79-Ubuntu SMP Wed Mar 24 10:56:57 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux +Linux xy-Inspiron-3593 5.4.0-72-generic #80-Ubuntu SMP Mon Apr 12 17:35:00 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux Context: The board couldn't autoreconnect to Local Blynk Server after router power recycling. diff --git a/README.md b/README.md index 02da5a1e..c11b10c0 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ * [Currently supported Boards](#currently-supported-boards) * [Currently supported WiFi shields/modules](#currently-supported-wifi-shieldsmodules) * [Changelog](#changelog) + * [Major Release v1.3.0](#major-release-v130) * [Release v1.2.0](#release-v120) * [Release v1.1.3](#release-v113) * [Release v1.1.2](#release-v112) @@ -38,6 +39,8 @@ * [5. For Adafruit SAMD boards](#5-for-adafruit-samd-boards) * [6. For Seeeduino SAMD boards](#6-for-seeeduino-samd-boards) * [7. For STM32 boards](#7-for-stm32-boards) + * [7.1. For STM32 boards to use LAN8720](#71-for-stm32-boards-to-use-lan8720) + * [7.2. For STM32 boards to use Serial1](#72-for-stm32-boards-to-use-serial1) * [How It Works](#how-it-works) * [How to use](#how-to-use) * [ 1. Basic usage](#1-basic-usage) @@ -65,6 +68,9 @@ * [11. Teensy_WiFi](examples/Teensy_WiFi) * [12. Teensy_WiFi_MQTT](examples/Teensy_WiFi_MQTT) * [So, how it works?](#so-how-it-works) + * [1. Without SCAN_WIFI_NETWORKS](#1-without-scan_wifi_networks) + * [2. With SCAN_WIFI_NETWORKS](#2-with-scan_wifi_networks) + * [3. With SCAN_WIFI_NETWORKS for ESP8266-AT and ESP32-AT shield](#3-with-scan_wifi_networks-for-esp8266-at-and-esp32-at-shield) * [Important Notes](#important-notes) * [How to use default Credentials and have them pre-loaded onto Config Portal](#how-to-use-default-credentials-and-have-them-pre-loaded-onto-config-portal) * [1. To always load Default Credentials and override Config Portal data](#1-to-always-load-default-credentials-and-override-config-portal-data) @@ -78,13 +84,17 @@ * [3. File Credentials.h](#3-file-credentialsh) * [4. File dynamicParams.h](#4-file-dynamicparamsh) * [Debug Terminal Output Samples](#debug-terminal-output-samples) - * [1. Open Config Portal](#1-open-config-portal) - * [2. Received data from Config Portal](#2-received-data-from-config-portal) - * [3. Got valid Credential from Config Portal, then connected to WiFi](#3-got-valid-credential-from-config-portal-then-connected-to-wifi) - * [4. Lost a WiFi and autoconnect to another WiFi AP](#4-lost-a-wifi-and-autoconnect-to-another-wifi-ap) - * [5. Reopen Config Portal if can't connect to any of the 2 WiFi APs](#5-reopen-config-portal-if-cant-connect-to-any-of-the-2-wifi-aps) - * [6. DRD Not Detected](#6-drd-not-detected) - * [7. DRD detected and Config Portal is forcefully opened](#7drd-detected-and-config-portal-is-forcefully-opened) + * [1. SAMD_WiFi example on Nano-33 IoT](#1-samd_wifi-example-on-nano-33-iot) + * [1.1 Open Config Portal](#11-open-config-portal) + * [1.2 Received data from Config Portal](#12-received-data-from-config-portal) + * [1.3 Got valid Credential from Config Portal, then connected to WiFi](#13-got-valid-credential-from-config-portal-then-connected-to-wifi) + * [1.4 Lost a WiFi and autoconnect to another WiFi AP](#14-lost-a-wifi-and-autoconnect-to-another-wifi-ap) + * [1.5 Reopen Config Portal if can't connect to any of the 2 WiFi APs](#15-reopen-config-portal-if-cant-connect-to-any-of-the-2-wifi-aps) + * [1.6 DRD Not Detected](#16-drd-not-detected) + * [1.7 DRD detected and Config Portal is forcefully opened](#17-drd-detected-and-config-portal-is-forcefully-opened) + * [2. SAMD_WiFi on ITSYBITSY_M4 using Custom WiFi Library](#2-samd_wifi-example-on-itsybitsy_m4-using-custom-wifi-library) + * [2.1 Open Config Portal](#21-open-config-portal) + * [2.2 Got valid Credential from Config Portal, then connected to WiFi](#22-got-valid-credential-from-config-portal-then-connected-to-wifi) * [Debug](#debug) * [Troubleshooting](#troubleshooting) * [Releases](#releases) @@ -133,6 +143,7 @@ New recent features: - Control Config Portal from software or Virtual Switches - To permit autoreset after configurable timeout if DRD/MRD or non-persistent forced-CP - Use new nRF52 LittleFS, SAMD FlashStorage_SAMD and STM32F/L/H/G/WB/MP1 FlashStorage_STM32 features +- **Scan WiFi networks** for selection in Configuration Portal #### Currently supported Boards @@ -157,7 +168,7 @@ This [**WiFiManager_Generic_Lite** library](https://github.com/khoih-prog/WiFiMa - Nucleo-64 - Discovery - Generic STM32F0, STM32F1, STM32F2, STM32F3, STM32F4, STM32F7 (with 64+K Flash): x8 and up - - STM32L0, STM32L1, STM32L4 + - STM32L0, STM32L1, STM32L4, STM32L5 - STM32G0, STM32G4 - STM32H7 - STM32WB @@ -181,6 +192,13 @@ This [**WiFiManager_Generic_Lite** library](https://github.com/khoih-prog/WiFiMa ## Changelog +### Major Release v1.3.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-6 for ESP8266-AT or 2-15 for other) +2. Minor enhancement to not display garbage when data is invalid +3. Tested with new [Arduino Core for STM32 v2.0.0](https://github.com/stm32duino/Arduino_Core_STM32) and add support to new STM32L5 boards. + + ### Release v1.2.0 1. Permit optionally inputting one set of WiFi SSID/PWD by using `REQUIRE_ONE_SET_SSID_PW == true` @@ -226,7 +244,7 @@ This [**WiFiManager_Generic_Lite** library](https://github.com/khoih-prog/WiFiMa ## Prerequisites 1. [`Arduino IDE 1.8.13+` for Arduino](https://www.arduino.cc/en/Main/Software) - 2. [`Arduino Core for STM32 v1.9.0+`](https://github.com/stm32duino/Arduino_Core_STM32) for STM32 boards. [![GitHub release](https://img.shields.io/github/release/stm32duino/Arduino_Core_STM32.svg)](https://github.com/stm32duino/Arduino_Core_STM32/releases/latest) + 2. [`Arduino Core for STM32 v2.0.0+`](https://github.com/stm32duino/Arduino_Core_STM32) for STM32 boards. [![GitHub release](https://img.shields.io/github/release/stm32duino/Arduino_Core_STM32.svg)](https://github.com/stm32duino/Arduino_Core_STM32/releases/latest) 3. [`Teensy core 1.51`](https://www.pjrc.com/teensy/td_download.html) for Teensy (4.1, 4.0, 3.6, 3.5, 3,2, 3.1, 3.0, LC) boards 4. [`Arduino SAM DUE core 1.6.12+`](https://github.com/arduino/ArduinoCore-sam) for SAM DUE ARM Cortex-M3 boards 5. [`Arduino SAMD core 1.8.11+`](https://www.arduino.cc/en/Guide/ArduinoM0) for SAMD ARM Cortex-M0+ boards. [![GitHub release](https://img.shields.io/github/release/arduino/ArduinoCore-samd.svg)](https://github.com/arduino/ArduinoCore-samd/releases/latest) @@ -398,6 +416,30 @@ This file must be copied into the directory: #### 7. For STM32 boards +#### 7.1. For STM32 boards to use LAN8720 + +To use LAN8720 on some STM32 boards + +- **Nucleo-144 (F429ZI, NUCLEO_F746NG, NUCLEO_F746ZG, NUCLEO_F756ZG)** +- **Discovery (DISCO_F746NG)** +- **STM32F4 boards (BLACK_F407VE, BLACK_F407VG, BLACK_F407ZE, BLACK_F407ZG, BLACK_F407VE_Mini, DIYMORE_F407VGT, FK407M1)** + +you have to copy the files [stm32f4xx_hal_conf_default.h](Packages_Patches/STM32/hardware/stm32/1.9.0/system/STM32F4xx) and [stm32f7xx_hal_conf_default.h](Packages_Patches/STM32/hardware/stm32/1.9.0/system/STM32F7xx) into STM32 stm32 directory (~/.arduino15/packages/STM32/hardware/stm32/1.9.0/system) to overwrite the old files. + +Supposing the STM32 stm32 core version is 1.9.0. These files must be copied into the directory: + +- `~/.arduino15/packages/STM32/hardware/stm32/1.9.0/system/STM32F4xx/stm32f4xx_hal_conf_default.h` for STM32F4. +- `~/.arduino15/packages/STM32/hardware/stm32/1.9.0/system/STM32F7xx/stm32f7xx_hal_conf_default.h` for Nucleo-144 STM32F7. + +Whenever a new version is installed, remember to copy this file into the new version directory. For example, new version is x.yy.zz, +theses files must be copied into the corresponding directory: + +- `~/.arduino15/packages/STM32/hardware/stm32/x.yy.zz/system/STM32F4xx/stm32f4xx_hal_conf_default.h` +- `~/.arduino15/packages/STM32/hardware/stm32/x.yy.zz/system/STM32F7xx/stm32f7xx_hal_conf_default.h + + +#### 7.2. For STM32 boards to use Serial1 + **To use Serial1 on some STM32 boards without Serial1 definition (Nucleo-144 NUCLEO_F767ZI, Nucleo-64 NUCLEO_L053R8, etc.) boards**, you have to copy the files [STM32 variant.h](Packages_Patches/STM32/hardware/stm32/1.9.0) into STM32 stm32 directory (~/.arduino15/packages/STM32/hardware/stm32/1.9.0). You have to modify the files corresponding to your boards, this is just an illustration how to do. Supposing the STM32 stm32 core version is 1.9.0. These files must be copied into the directory: @@ -599,7 +641,7 @@ Once Credentials / WiFi network information is saved in the host non-volatile me #### 11. To use and input only one set of WiFi SSID and PWD -#### 11.1 If you need to use and input only one set of WiFi SSID/PWD. +#### 11.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" @@ -616,6 +658,36 @@ But it's always advisable to use and input both sets for reliability. #define REQUIRE_ONE_SET_SSID_PW false ``` +#### 12. To enable auto-scan of WiFi networks for selection in Configuration Portal + +#### 12.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. + +#### 12.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. + +#### 12.3 Select maximum number of SSIDs in the list + +The maximum number of SSIDs in the list is seletable from 2 to 15 (for ESP8266/ESP32-AT shields, from 2-6). If invalid number of SSIDs is selected, the default number of 10 will be used. + + +``` +// From 2-15 +#define MAX_SSID_IN_LIST 8 +``` + --- --- @@ -649,10 +721,29 @@ After you connected, please, go to http://192.168.4.1 or newly configured AP IP, Enter your credentials, +### 1. Without SCAN_WIFI_NETWORKS + +

+ +

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

+ +

+ + +### 3. With SCAN_WIFI_NETWORKS for ESP8266-AT and ESP32-AT shield + +The number of SSIDs in scanned list is limited at max 6. +

- +

+ then click `Save`.

@@ -1425,13 +1516,15 @@ uint16_t NUM_MENU_ITEMS = 0; ### Debug Terminal output Samples +#### 1. SAMD_WiFi example on Nano-33 IoT + This is the terminal output when running [**SAMD_WiFi**](examples/SAMD_WiFi) example on **Nano-33 IoT**: -#### 1. Open Config Portal +#### 1.1 Open Config Portal ``` Start SAMD_WiFi on SAMD_NANO_33_IOT with WiFiNINA using WiFiNINA_Generic Library -WiFiManager_Generic_Lite v1.2.0 +WiFiManager_Generic_Lite v1.3.0 [WG] Hostname=SAMD-Master-Controller Flag read = 0xffffffff No doubleResetDetected @@ -1479,7 +1572,7 @@ ClearFlag write = 0xd0d04321 FFFFFFFFF ``` -#### 2. Received data from Config Portal +#### 1.2 Received data from Config Portal ``` [WG] h: Init menuItemUpdated :6 @@ -1528,11 +1621,11 @@ FFFFFFFFF [WG] h:Rst ``` -#### 3. Got valid Credential from Config Portal, then connected to WiFi +#### 1.3 Got valid Credential from Config Portal, then connected to WiFi ``` Start SAMD_WiFi on SAMD_NANO_33_IOT with WiFiNINA using WiFiNINA_Generic Library -WiFiManager_Generic_Lite v1.2.0 +WiFiManager_Generic_Lite v1.3.0 [WG] Hostname=SAMD-Master-Controller Flag read = 0xd0d04321 No doubleResetDetected @@ -1573,7 +1666,7 @@ HHHHHHHHHH HHHHHHHHHH HHHHHHHHHH HHHHHHHHHH HHHHHHHHHH HHHHHHHHHH HHHHHHHHHH HHH HHHHHHHHHH HHHHHHHHHH HHHHHHHHHH HHHHHHHHHH HHHHHHHHHH HHHHHHHHHH HHHHHHHHHH HHHHHHHHHH ``` -#### 4. Lost a WiFi and autoconnect to another WiFi AP +#### 1.4 Lost a WiFi and autoconnect to another WiFi AP ``` [WG] r:Check&WLost <=== Lost primary WiFi @@ -1592,11 +1685,11 @@ WiFi-begin: return2 = 3 HHHHHHHHHH HHHHHHHHHH ``` -#### 5. Reopen Config Portal if can't connect to any of the 2 WiFi APs +#### 1.5 Reopen Config Portal if can't connect to any of the 2 WiFi APs ``` Start SAMD_WiFi on SAMD_NANO_33_IOT with WiFiNINA using WiFiNINA_Generic Library -WiFiManager_Generic_Lite v1.2.0 +WiFiManager_Generic_Lite v1.3.0 [WG] Hostname=SAMD-Master-Controller Flag read = 0xd0d04321 No doubleResetDetected @@ -1656,11 +1749,11 @@ MQTT Server = new_mqtt.duckdns.org FF ``` -#### 6. DRD Not Detected: +#### 1.6 DRD Not Detected: ``` Start SAMD_WiFi on SAMD_NANO_33_IOT with WiFiNINA using WiFiNINA_Generic Library -WiFiManager_Generic_Lite v1.2.0 +WiFiManager_Generic_Lite v1.3.0 [WG] Hostname=SAMD-Master-Controller Flag read = 0xd0d04321 No doubleResetDetected @@ -1712,11 +1805,11 @@ MQTT Server = new_mqtt.duckdns.org HHHHHHHHH HHHHHHHHHH ``` -#### 7.DRD detected and Config Portal is forcefully opened +#### 1.7 DRD detected and Config Portal is forcefully opened ``` Start SAMD_WiFi on SAMD_NANO_33_IOT with WiFiNINA using WiFiNINA_Generic Library -WiFiManager_Generic_Lite v1.2.0 +WiFiManager_Generic_Lite v1.3.0 [WG] Hostname=SAMD-Master-Controller Flag read = 0xd0d01234 doubleResetDetected @@ -1761,6 +1854,88 @@ Port = 8080 MQTT Server = new_mqtt.duckdns.org ``` + +--- + +#### 2. SAMD_WiFi on ITSYBITSY_M4 using Custom WiFi Library + +This is the terminal output when running [**SAMD_WiFi**](examples/SAMD_WiFi) example on **Adafruit SAMD51 ITSYBITSY_M4**: + +#### 2.1 Open Config Portal + +

+ +

+ + +``` +Start SAMD_WiFi on ITSYBITSY_M4 with Custom using Custom WiFi Library +WiFiManager_Generic_Lite v1.3.0 +[ESP_AT] Use ES8266-AT Command +WiFi shield init done +[WG] Hostname=SAMD-Master-Controller +Flag read = 0xffffffff +No doubleResetDetected +SetFlag write = 0xd0d01234 +[WG] CCSum=0xe11e,RCSum=0xffffffff +[WG] InitCfgFile,sz=236 +[WG] SaveEEPROM,Sz=1024,DataSz=0,WCSum=0xdb3 +[WG] bg: isForcedConfigPortal = false +[WG] bg:Stay forever in CP:No ConfigDat +[WG] Scanning Network +[WG] scanWifiNetworks: Done, Scanned Networks n = 9 +[WG] Sorting +[WG] Removing Dup +[WG] DUP AP:Waterhome +[WG] DUP AP: +[WG] WiFi networks found: +[WG] 1: HueNet1, -44dB +[WG] 2: HueNetTek, -32dB +[WG] 3: HueNet2, -58dB +[WG] 4: bacau, -82dB +[WG] 5: dlink-4F96, -86dB +[WG] 6: Waterhome, -88dB +[WG] 7: , -84dB +[WG] 9: BELL627, -63dB +[WG] SSID=WIFI_GENERIC_51F485,PW=MyWIFI_GENERIC_51F485 +[WG] IP=192.168.4.1,CH=1 +[WG] s:configTimeout = 0 +Stop doubleResetDetecting +ClearFlag write = 0xd0d04321 +F +``` + +#### 2.2 Got valid Credential from Config Portal, then connected to WiFi + +``` +Start SAMD_WiFi on ITSYBITSY_M4 with Custom using Custom WiFi Library +WiFiManager_Generic_Lite v1.3.0 +[ESP_AT] Use ES8266-AT Command +WiFi shield init done +[WG] Hostname=SAMD-Master-Controller +Flag read = 0xd0d04321 +No doubleResetDetected +SetFlag write = 0xd0d01234 +[WG] CCSum=0x14eb,RCSum=0x14eb +[WG] ======= Start Stored Config Data ======= +[WG] Hdr=WIFI_GENERIC,SSID=HueNet1,PW=12345678 +[WG] SSID1=HueNet2,PW1=12345678 +[WG] BName=Itsy-Bitsy-SAMD51 +[WG] ConMultiWifi +[WG] First connection, Using index=0 +[WG] con2WF:SSID=HueNet1,PW=12345678 +[WG] Remaining retry_time=3 +[WG] WOK, lastConnectedIndex=0 +[WG] con2WF:OK +[WG] IP=192.168.2.154 +[WG] SSID=HueNet1,RSSI=-40 +[WG] IP=192.168.2.154 +[WG] b:WOK +Stop doubleResetDetecting +ClearFlag write = 0xd0d04321 +HHH +``` + --- --- @@ -1791,6 +1966,12 @@ Sometimes, the library will only work if you update the `Generic WiFi module/shi ## Releases +### Major Release v1.3.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-6 for ESP8266-AT or 2-15 for other) +2. Minor enhancement to not display garbage when data is invalid +3. Tested with new [Arduino Core for STM32 v2.0.0](https://github.com/stm32duino/Arduino_Core_STM32) and add support to new STM32L5 boards. + ### Release v1.2.0 1. Permit optionally inputting one set of WiFi SSID/PWD by using `REQUIRE_ONE_SET_SSID_PW == true` @@ -1871,6 +2052,7 @@ Submit issues to: [WiFiManager_Generic_Lite issues](https://github.com/khoih-pro 22. Add functions to control Config Portal from software or Virtual Switches. 23. Permit optionally inputting one set of WiFi SSID/PWD by using `REQUIRE_ONE_SET_SSID_PW == true` 24. Enforce WiFi Password minimum length of 8 chars +25. Enable scan of WiFi networks for selection in Configuration Portal --- --- @@ -1880,15 +2062,17 @@ Submit issues to: [WiFiManager_Generic_Lite issues](https://github.com/khoih-pro Please help contribute to this project and add your name here. 1. Thanks to [Max Gerhardt in GitHub](https://github.com/maxgerhardt) and [in PIO](https://community.platformio.org/u/maxgerhardt) for the useful fix [**attachInterrupt() on wifi101 unidentified**](https://community.platformio.org/t/attachinterrupt-on-wifi101-unidentified/17543) to enable STM32F/L/H/G/WB/MP1 using ATWINC1500/WiFi101, leading to new v1.0.2 -2. Thanks to [Michael "bizprof"](https://github.com/bizprof) to report bugs in +2. Thanks to [Michael H. "bizprof"](https://github.com/bizprof) to report bugs in - [SAMD MultiWiFi issues when first WiFi SSID configured in CP is invalid or not available #6](https://github.com/khoih-prog/WiFiManager_NINA_Lite/issues/6) leading to v1.1.2. - [WiFiManager connection attempt to unconfigured ("blank") SSID after restart on SAMD #8](https://github.com/khoih-prog/WiFiManager_NINA_Lite/issues/8) leading to v1.1.3 and v1.2.0 +3. Again thanks to [Michael H. "bizprof"](https://github.com/bizprof) to be `collaborator, co-author/maintainer` of this library. 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.3.0 - +
maxgerhardt
Maximilian Gerhardt

bizprof
Michael "bizprof"

bizprof
⭐️⭐️ Michael H. "bizprof"

@@ -1913,6 +2097,6 @@ If you want to contribute to this project: ### Copyright -Copyright 2021- Khoi Hoang +Copyright 2021- Khoi Hoang - Michale H. diff --git a/examples/MKR1000_WiFi101/MKR1000_WiFi101.ino b/examples/MKR1000_WiFi101/MKR1000_WiFi101.ino index 1e5cc1a5..65782d2c 100644 --- a/examples/MKR1000_WiFi101/MKR1000_WiFi101.ino +++ b/examples/MKR1000_WiFi101/MKR1000_WiFi101.ino @@ -8,7 +8,7 @@ Built by Khoi Hoang https://github.com/khoih-prog/WiFiManager_Generic_Lite Licensed under MIT license - Version: 1.2.0 + Version: 1.3.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -19,7 +19,8 @@ Add customs HTML header feature. Fix bug. 1.1.2 K Hoang 30/03/2021 Fix MultiWiFi connection bug. 1.1.3 K Hoang 12/04/2021 Fix invalid "blank" Config Data treated as Valid. - 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.3.0 Michael H 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal *****************************************************************************************************************************/ #include "defines.h" diff --git a/examples/MKR1000_WiFi101/defines.h b/examples/MKR1000_WiFi101/defines.h index 7b187ad3..1bae1b6a 100644 --- a/examples/MKR1000_WiFi101/defines.h +++ b/examples/MKR1000_WiFi101/defines.h @@ -89,9 +89,19 @@ // 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 +#define REQUIRE_ONE_SET_SSID_PW false -#define USE_DYNAMIC_PARAMETERS true +#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 ///////////////////////////////////////////// diff --git a/examples/MKR1000_WiFi101_MQTT/MKR1000_WiFi101_MQTT.ino b/examples/MKR1000_WiFi101_MQTT/MKR1000_WiFi101_MQTT.ino index a2e6252a..ed48cb2f 100644 --- a/examples/MKR1000_WiFi101_MQTT/MKR1000_WiFi101_MQTT.ino +++ b/examples/MKR1000_WiFi101_MQTT/MKR1000_WiFi101_MQTT.ino @@ -8,7 +8,7 @@ Built by Khoi Hoang https://github.com/khoih-prog/WiFiManager_Generic_Lite Licensed under MIT license - Version: 1.2.0 + Version: 1.3.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -19,7 +19,8 @@ Add customs HTML header feature. Fix bug. 1.1.2 K Hoang 30/03/2021 Fix MultiWiFi connection bug. 1.1.3 K Hoang 12/04/2021 Fix invalid "blank" Config Data treated as Valid. - 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.3.0 Michael H 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal *****************************************************************************************************************************/ /**************************************************************************************************************************** You have to modify file ./libraries/Adafruit_MQTT_Library/Adafruit_MQTT.cpp as follows to avoid dtostrf error, if exists diff --git a/examples/MKR1000_WiFi101_MQTT/defines.h b/examples/MKR1000_WiFi101_MQTT/defines.h index 1f48f598..7e3766d2 100644 --- a/examples/MKR1000_WiFi101_MQTT/defines.h +++ b/examples/MKR1000_WiFi101_MQTT/defines.h @@ -89,9 +89,19 @@ // 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 +#define REQUIRE_ONE_SET_SSID_PW false -#define USE_DYNAMIC_PARAMETERS true +#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 ///////////////////////////////////////////// diff --git a/examples/SAMD_WiFi/SAMD_WiFi.ino b/examples/SAMD_WiFi/SAMD_WiFi.ino index 0529c5d3..3681d0b7 100644 --- a/examples/SAMD_WiFi/SAMD_WiFi.ino +++ b/examples/SAMD_WiFi/SAMD_WiFi.ino @@ -8,7 +8,7 @@ Built by Khoi Hoang https://github.com/khoih-prog/WiFiManager_Generic_Lite Licensed under MIT license - Version: 1.2.0 + Version: 1.3.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -19,7 +19,8 @@ Add customs HTML header feature. Fix bug. 1.1.2 K Hoang 30/03/2021 Fix MultiWiFi connection bug. 1.1.3 K Hoang 12/04/2021 Fix invalid "blank" Config Data treated as Valid. - 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.3.0 Michael H 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal *****************************************************************************************************************************/ #include "defines.h" diff --git a/examples/SAMD_WiFi/defines.h b/examples/SAMD_WiFi/defines.h index 14de2dac..827a4a4f 100644 --- a/examples/SAMD_WiFi/defines.h +++ b/examples/SAMD_WiFi/defines.h @@ -17,7 +17,7 @@ #define DEBUG_WIFI_WEBSERVER_PORT Serial #define WIFI_GENERIC_DEBUG_OUTPUT Serial -#define _WIFI_GENERIC_LOGLEVEL_ 4 +#define _WIFI_GENERIC_LOGLEVEL_ 1 #define DRD_GENERIC_DEBUG true @@ -152,9 +152,9 @@ ///////////////////////////////////////////// -#define USE_WIFI_NINA true +#define USE_WIFI_NINA false #define USE_WIFI101 false -#define USE_WIFI_CUSTOM false +#define USE_WIFI_CUSTOM true #if USE_WIFI_NINA @@ -232,13 +232,30 @@ ///////////////////////////////////////////// +#define SCAN_WIFI_NETWORKS true + +// To be able to manually input SSID, not from a scanned SSID lists +#define MANUAL_SSID_INPUT_ALLOWED true + +///////////////////////////////////////////// + #if (USE_WIFI_CUSTOM && USE_ESP_AT_SHIELD) - // ESP-AT can use longer than 2K HTML and DYNAMIC_PARAMETERS + // ESP-AT can't use longer than 2K HTML and DYNAMIC_PARAMETERS #undef USE_DYNAMIC_PARAMETERS #define USE_DYNAMIC_PARAMETERS false #warning Disable USE_DYNAMIC_PARAMETERS for ESP_AT_SHIELD + + // From 2-6 to keep HTML short for ESP8266-AT. Limited 6 in WiFiEspAT library anyway + #define MAX_SSID_IN_LIST 6 + +#else + + // From 2-15 + #define MAX_SSID_IN_LIST 8 #endif +///////////////////////////////////////////// + #include #define HOST_NAME "SAMD-Master-Controller" diff --git a/examples/SAMD_WiFi_MQTT/SAMD_WiFi_MQTT.ino b/examples/SAMD_WiFi_MQTT/SAMD_WiFi_MQTT.ino index 55d18f45..79eb5e5a 100644 --- a/examples/SAMD_WiFi_MQTT/SAMD_WiFi_MQTT.ino +++ b/examples/SAMD_WiFi_MQTT/SAMD_WiFi_MQTT.ino @@ -8,7 +8,7 @@ Built by Khoi Hoang https://github.com/khoih-prog/WiFiManager_Generic_Lite Licensed under MIT license - Version: 1.2.0 + Version: 1.3.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -19,7 +19,8 @@ Add customs HTML header feature. Fix bug. 1.1.2 K Hoang 30/03/2021 Fix MultiWiFi connection bug. 1.1.3 K Hoang 12/04/2021 Fix invalid "blank" Config Data treated as Valid. - 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.3.0 Michael H 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal *****************************************************************************************************************************/ /**************************************************************************************************************************** You have to modify file ./libraries/Adafruit_MQTT_Library/Adafruit_MQTT.cpp as follows to avoid dtostrf error, if exists diff --git a/examples/SAMD_WiFi_MQTT/defines.h b/examples/SAMD_WiFi_MQTT/defines.h index 155452c6..b3d41c6d 100644 --- a/examples/SAMD_WiFi_MQTT/defines.h +++ b/examples/SAMD_WiFi_MQTT/defines.h @@ -245,13 +245,30 @@ ///////////////////////////////////////////// +#define SCAN_WIFI_NETWORKS true + +// To be able to manually input SSID, not from a scanned SSID lists +#define MANUAL_SSID_INPUT_ALLOWED true + +///////////////////////////////////////////// + #if (USE_WIFI_CUSTOM && USE_ESP_AT_SHIELD) - // ESP-AT can use longer than 2K HTML and DYNAMIC_PARAMETERS + // ESP-AT can't use longer than 2K HTML and DYNAMIC_PARAMETERS #undef USE_DYNAMIC_PARAMETERS #define USE_DYNAMIC_PARAMETERS false - #error Can't use with ESP_AT_SHIELD + #warning Disable USE_DYNAMIC_PARAMETERS for ESP_AT_SHIELD + + // From 2-6 to keep HTML short for ESP8266-AT. Limited 6 in WiFiEspAT library anyway + #define MAX_SSID_IN_LIST 6 + +#else + + // From 2-15 + #define MAX_SSID_IN_LIST 8 #endif +///////////////////////////////////////////// + #include #define HOST_NAME "SAMD-MQTT-Controller" diff --git a/examples/SAM_DUE_WiFi/SAM_DUE_WiFi.ino b/examples/SAM_DUE_WiFi/SAM_DUE_WiFi.ino index e08df274..e43f6d61 100644 --- a/examples/SAM_DUE_WiFi/SAM_DUE_WiFi.ino +++ b/examples/SAM_DUE_WiFi/SAM_DUE_WiFi.ino @@ -8,7 +8,7 @@ Built by Khoi Hoang https://github.com/khoih-prog/WiFiManager_Generic_Lite Licensed under MIT license - Version: 1.2.0 + Version: 1.3.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -19,7 +19,8 @@ Add customs HTML header feature. Fix bug. 1.1.2 K Hoang 30/03/2021 Fix MultiWiFi connection bug. 1.1.3 K Hoang 12/04/2021 Fix invalid "blank" Config Data treated as Valid. - 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.3.0 Michael H 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal *****************************************************************************************************************************/ #include "defines.h" #include "Credentials.h" diff --git a/examples/SAM_DUE_WiFi/defines.h b/examples/SAM_DUE_WiFi/defines.h index a20c2cf3..53784cf7 100644 --- a/examples/SAM_DUE_WiFi/defines.h +++ b/examples/SAM_DUE_WiFi/defines.h @@ -148,13 +148,30 @@ ///////////////////////////////////////////// +#define SCAN_WIFI_NETWORKS true + +// To be able to manually input SSID, not from a scanned SSID lists +#define MANUAL_SSID_INPUT_ALLOWED true + +///////////////////////////////////////////// + #if (USE_WIFI_CUSTOM && USE_ESP_AT_SHIELD) - // ESP-AT can use longer than 2K HTML and DYNAMIC_PARAMETERS + // ESP-AT can't use longer than 2K HTML and DYNAMIC_PARAMETERS #undef USE_DYNAMIC_PARAMETERS #define USE_DYNAMIC_PARAMETERS false #warning Disable USE_DYNAMIC_PARAMETERS for ESP_AT_SHIELD + + // From 2-6 to keep HTML short for ESP8266-AT. Limited 6 in WiFiEspAT library anyway + #define MAX_SSID_IN_LIST 6 + +#else + + // From 2-15 + #define MAX_SSID_IN_LIST 8 #endif +///////////////////////////////////////////// + #include #define HOST_NAME "DUE-Master-Controller" diff --git a/examples/SAM_DUE_WiFi_MQTT/SAM_DUE_WiFi_MQTT.ino b/examples/SAM_DUE_WiFi_MQTT/SAM_DUE_WiFi_MQTT.ino index 8c5f075b..616a37e6 100644 --- a/examples/SAM_DUE_WiFi_MQTT/SAM_DUE_WiFi_MQTT.ino +++ b/examples/SAM_DUE_WiFi_MQTT/SAM_DUE_WiFi_MQTT.ino @@ -8,7 +8,7 @@ Built by Khoi Hoang https://github.com/khoih-prog/WiFiManager_Generic_Lite Licensed under MIT license - Version: 1.2.0 + Version: 1.3.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -19,7 +19,8 @@ Add customs HTML header feature. Fix bug. 1.1.2 K Hoang 30/03/2021 Fix MultiWiFi connection bug. 1.1.3 K Hoang 12/04/2021 Fix invalid "blank" Config Data treated as Valid. - 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.3.0 Michael H 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal *****************************************************************************************************************************/ /**************************************************************************************************************************** You have to modify file ./libraries/Adafruit_MQTT_Library/Adafruit_MQTT.cpp as follows to avoid dtostrf error diff --git a/examples/SAM_DUE_WiFi_MQTT/defines.h b/examples/SAM_DUE_WiFi_MQTT/defines.h index 98428d02..452e4281 100644 --- a/examples/SAM_DUE_WiFi_MQTT/defines.h +++ b/examples/SAM_DUE_WiFi_MQTT/defines.h @@ -149,13 +149,30 @@ ///////////////////////////////////////////// +#define SCAN_WIFI_NETWORKS true + +// To be able to manually input SSID, not from a scanned SSID lists +#define MANUAL_SSID_INPUT_ALLOWED true + +///////////////////////////////////////////// + #if (USE_WIFI_CUSTOM && USE_ESP_AT_SHIELD) - // ESP-AT can use longer than 2K HTML and DYNAMIC_PARAMETERS + // ESP-AT can't use longer than 2K HTML and DYNAMIC_PARAMETERS #undef USE_DYNAMIC_PARAMETERS #define USE_DYNAMIC_PARAMETERS false - #error Can't use with ESP_AT_SHIELD + #warning Disable USE_DYNAMIC_PARAMETERS for ESP_AT_SHIELD + + // From 2-6 to keep HTML short for ESP8266-AT. Limited 6 in WiFiEspAT library anyway + #define MAX_SSID_IN_LIST 6 + +#else + + // From 2-15 + #define MAX_SSID_IN_LIST 8 #endif +///////////////////////////////////////////// + #include #define HOST_NAME "DUE-MQTT-Controller" diff --git a/examples/STM32_WiFi/STM32_WiFi.ino b/examples/STM32_WiFi/STM32_WiFi.ino index 49ed6baa..27545801 100644 --- a/examples/STM32_WiFi/STM32_WiFi.ino +++ b/examples/STM32_WiFi/STM32_WiFi.ino @@ -8,7 +8,7 @@ Built by Khoi Hoang https://github.com/khoih-prog/WiFiManager_Generic_Lite Licensed under MIT license - Version: 1.2.0 + Version: 1.3.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -19,7 +19,8 @@ Add customs HTML header feature. Fix bug. 1.1.2 K Hoang 30/03/2021 Fix MultiWiFi connection bug. 1.1.3 K Hoang 12/04/2021 Fix invalid "blank" Config Data treated as Valid. - 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.3.0 Michael H 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal *****************************************************************************************************************************/ #include "defines.h" #include "Credentials.h" diff --git a/examples/STM32_WiFi/defines.h b/examples/STM32_WiFi/defines.h index 3dcae71d..2b913549 100644 --- a/examples/STM32_WiFi/defines.h +++ b/examples/STM32_WiFi/defines.h @@ -21,9 +21,9 @@ #define DRD_GENERIC_DEBUG true -#if ( defined(STM32F0) || defined(STM32F1) || defined(STM32F2) || defined(STM32F3) ||defined(STM32F4) || defined(STM32F7) || \ - defined(STM32L0) || defined(STM32L1) || defined(STM32L4) || defined(STM32H7) ||defined(STM32G0) || defined(STM32G4) || \ - defined(STM32WB) || defined(STM32MP1) ) +#if ( defined(STM32F0) || defined(STM32F1) || defined(STM32F2) || defined(STM32F3) ||defined(STM32F4) || defined(STM32F7) || \ + defined(STM32L0) || defined(STM32L1) || defined(STM32L4) || defined(STM32H7) ||defined(STM32G0) || defined(STM32G4) || \ + defined(STM32WB) || defined(STM32MP1) || defined(STM32L5) ) #if defined(WIFI_GENERIC_USE_STM32) #undef WIFI_GENERIC_USE_STM32 #undef WIFI_USE_STM32 @@ -81,6 +81,9 @@ #elif defined(STM32L4) #warning STM32L4 board selected #define BOARD_TYPE "STM32L4" + #elif defined(STM32L5) + #warning STM32L5 board selected + #define BOARD_TYPE "STM32L5" #elif defined(STM32H7) #warning STM32H7 board selected #define BOARD_TYPE "STM32H7" @@ -208,13 +211,30 @@ ///////////////////////////////////////////// +#define SCAN_WIFI_NETWORKS true + +// To be able to manually input SSID, not from a scanned SSID lists +#define MANUAL_SSID_INPUT_ALLOWED true + +///////////////////////////////////////////// + #if (USE_WIFI_CUSTOM && USE_ESP_AT_SHIELD) - // ESP-AT can use longer than 2K HTML and DYNAMIC_PARAMETERS + // ESP-AT can't use longer than 2K HTML and DYNAMIC_PARAMETERS #undef USE_DYNAMIC_PARAMETERS #define USE_DYNAMIC_PARAMETERS false #warning Disable USE_DYNAMIC_PARAMETERS for ESP_AT_SHIELD + + // From 2-6 to keep HTML short for ESP8266-AT. Limited 6 in WiFiEspAT library anyway + #define MAX_SSID_IN_LIST 6 + +#else + + // From 2-15 + #define MAX_SSID_IN_LIST 8 #endif +///////////////////////////////////////////// + #include #define HOST_NAME "STM32-Master-Controller" diff --git a/examples/STM32_WiFi_MQTT/STM32_WiFi_MQTT.ino b/examples/STM32_WiFi_MQTT/STM32_WiFi_MQTT.ino index 0963e3d4..0937c64d 100644 --- a/examples/STM32_WiFi_MQTT/STM32_WiFi_MQTT.ino +++ b/examples/STM32_WiFi_MQTT/STM32_WiFi_MQTT.ino @@ -8,7 +8,7 @@ Built by Khoi Hoang https://github.com/khoih-prog/WiFiManager_Generic_Lite Licensed under MIT license - Version: 1.2.0 + Version: 1.3.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -19,7 +19,8 @@ Add customs HTML header feature. Fix bug. 1.1.2 K Hoang 30/03/2021 Fix MultiWiFi connection bug. 1.1.3 K Hoang 12/04/2021 Fix invalid "blank" Config Data treated as Valid. - 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.3.0 Michael H 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal *****************************************************************************************************************************/ /**************************************************************************************************************************** You have to modify file ./libraries/Adafruit_MQTT_Library/Adafruit_MQTT.cpp as follows to avoid dtostrf error diff --git a/examples/STM32_WiFi_MQTT/defines.h b/examples/STM32_WiFi_MQTT/defines.h index 63ec347d..77d91764 100644 --- a/examples/STM32_WiFi_MQTT/defines.h +++ b/examples/STM32_WiFi_MQTT/defines.h @@ -21,9 +21,9 @@ #define DRD_GENERIC_DEBUG true -#if ( defined(STM32F0) || defined(STM32F1) || defined(STM32F2) || defined(STM32F3) ||defined(STM32F4) || defined(STM32F7) || \ - defined(STM32L0) || defined(STM32L1) || defined(STM32L4) || defined(STM32H7) ||defined(STM32G0) || defined(STM32G4) || \ - defined(STM32WB) || defined(STM32MP1) ) +#if ( defined(STM32F0) || defined(STM32F1) || defined(STM32F2) || defined(STM32F3) ||defined(STM32F4) || defined(STM32F7) || \ + defined(STM32L0) || defined(STM32L1) || defined(STM32L4) || defined(STM32H7) ||defined(STM32G0) || defined(STM32G4) || \ + defined(STM32WB) || defined(STM32MP1) || defined(STM32L5) ) #if defined(WIFI_GENERIC_USE_STM32) #undef WIFI_GENERIC_USE_STM32 #undef WIFI_USE_STM32 @@ -81,6 +81,9 @@ #elif defined(STM32L4) #warning STM32L4 board selected #define BOARD_TYPE "STM32L4" + #elif defined(STM32L5) + #warning STM32L5 board selected + #define BOARD_TYPE "STM32L5" #elif defined(STM32H7) #warning STM32H7 board selected #define BOARD_TYPE "STM32H7" @@ -209,13 +212,30 @@ ///////////////////////////////////////////// +#define SCAN_WIFI_NETWORKS true + +// To be able to manually input SSID, not from a scanned SSID lists +#define MANUAL_SSID_INPUT_ALLOWED true + +///////////////////////////////////////////// + #if (USE_WIFI_CUSTOM && USE_ESP_AT_SHIELD) - // ESP-AT can use longer than 2K HTML and DYNAMIC_PARAMETERS + // ESP-AT can't use longer than 2K HTML and DYNAMIC_PARAMETERS #undef USE_DYNAMIC_PARAMETERS #define USE_DYNAMIC_PARAMETERS false - #error Can't use with ESP_AT_SHIELD + #warning Disable USE_DYNAMIC_PARAMETERS for ESP_AT_SHIELD + + // From 2-6 to keep HTML short for ESP8266-AT. Limited 6 in WiFiEspAT library anyway + #define MAX_SSID_IN_LIST 6 + +#else + + // From 2-15 + #define MAX_SSID_IN_LIST 8 #endif +///////////////////////////////////////////// + #include #define HOST_NAME "STM32-MQTT-Controller" diff --git a/examples/Teensy_WiFi/Teensy_WiFi.ino b/examples/Teensy_WiFi/Teensy_WiFi.ino index 19de50db..700a8bc5 100644 --- a/examples/Teensy_WiFi/Teensy_WiFi.ino +++ b/examples/Teensy_WiFi/Teensy_WiFi.ino @@ -8,7 +8,7 @@ Built by Khoi Hoang https://github.com/khoih-prog/WiFiManager_Generic_Lite Licensed under MIT license - Version: 1.2.0 + Version: 1.3.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -19,7 +19,8 @@ Add customs HTML header feature. Fix bug. 1.1.2 K Hoang 30/03/2021 Fix MultiWiFi connection bug. 1.1.3 K Hoang 12/04/2021 Fix invalid "blank" Config Data treated as Valid. - 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.3.0 Michael H 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal *****************************************************************************************************************************/ #include "defines.h" diff --git a/examples/Teensy_WiFi/defines.h b/examples/Teensy_WiFi/defines.h index 967fb7f0..51a60e3d 100644 --- a/examples/Teensy_WiFi/defines.h +++ b/examples/Teensy_WiFi/defines.h @@ -160,13 +160,30 @@ ///////////////////////////////////////////// +#define SCAN_WIFI_NETWORKS true + +// To be able to manually input SSID, not from a scanned SSID lists +#define MANUAL_SSID_INPUT_ALLOWED true + +///////////////////////////////////////////// + #if (USE_WIFI_CUSTOM && USE_ESP_AT_SHIELD) - // ESP-AT can use longer than 2K HTML and DYNAMIC_PARAMETERS + // ESP-AT can't use longer than 2K HTML and DYNAMIC_PARAMETERS #undef USE_DYNAMIC_PARAMETERS #define USE_DYNAMIC_PARAMETERS false #warning Disable USE_DYNAMIC_PARAMETERS for ESP_AT_SHIELD + + // From 2-6 to keep HTML short for ESP8266-AT. Limited 6 in WiFiEspAT library anyway + #define MAX_SSID_IN_LIST 6 + +#else + + // From 2-15 + #define MAX_SSID_IN_LIST 8 #endif +///////////////////////////////////////////// + #include #define HOST_NAME "Teensy-Master-Controller" diff --git a/examples/Teensy_WiFi_MQTT/Teensy_WiFi_MQTT.ino b/examples/Teensy_WiFi_MQTT/Teensy_WiFi_MQTT.ino index 01a8cacc..438a44de 100644 --- a/examples/Teensy_WiFi_MQTT/Teensy_WiFi_MQTT.ino +++ b/examples/Teensy_WiFi_MQTT/Teensy_WiFi_MQTT.ino @@ -8,7 +8,7 @@ Built by Khoi Hoang https://github.com/khoih-prog/WiFiManager_Generic_Lite Licensed under MIT license - Version: 1.2.0 + Version: 1.3.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -19,7 +19,8 @@ Add customs HTML header feature. Fix bug. 1.1.2 K Hoang 30/03/2021 Fix MultiWiFi connection bug. 1.1.3 K Hoang 12/04/2021 Fix invalid "blank" Config Data treated as Valid. - 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.3.0 Michael H 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal *****************************************************************************************************************************/ /**************************************************************************************************************************** You have to modify file ./libraries/Adafruit_MQTT_Library/Adafruit_MQTT.cpp as follows to avoid dtostrf error diff --git a/examples/Teensy_WiFi_MQTT/defines.h b/examples/Teensy_WiFi_MQTT/defines.h index fafe31e3..262c1d9f 100644 --- a/examples/Teensy_WiFi_MQTT/defines.h +++ b/examples/Teensy_WiFi_MQTT/defines.h @@ -161,13 +161,30 @@ ///////////////////////////////////////////// +#define SCAN_WIFI_NETWORKS true + +// To be able to manually input SSID, not from a scanned SSID lists +#define MANUAL_SSID_INPUT_ALLOWED true + +///////////////////////////////////////////// + #if (USE_WIFI_CUSTOM && USE_ESP_AT_SHIELD) - // ESP-AT can use longer than 2K HTML and DYNAMIC_PARAMETERS + // ESP-AT can't use longer than 2K HTML and DYNAMIC_PARAMETERS #undef USE_DYNAMIC_PARAMETERS #define USE_DYNAMIC_PARAMETERS false - #error Can't use with ESP_AT_SHIELD + #warning Disable USE_DYNAMIC_PARAMETERS for ESP_AT_SHIELD + + // From 2-6 to keep HTML short for ESP8266-AT. Limited 6 in WiFiEspAT library anyway + #define MAX_SSID_IN_LIST 6 + +#else + + // From 2-15 + #define MAX_SSID_IN_LIST 8 #endif +///////////////////////////////////////////// + #include #define HOST_NAME "Teensy-MQTT-Controller" diff --git a/examples/nRF52_WiFi/defines.h b/examples/nRF52_WiFi/defines.h index 218606cb..d32e9d24 100644 --- a/examples/nRF52_WiFi/defines.h +++ b/examples/nRF52_WiFi/defines.h @@ -161,13 +161,30 @@ ///////////////////////////////////////////// +#define SCAN_WIFI_NETWORKS true + +// To be able to manually input SSID, not from a scanned SSID lists +#define MANUAL_SSID_INPUT_ALLOWED true + +///////////////////////////////////////////// + #if (USE_WIFI_CUSTOM && USE_ESP_AT_SHIELD) - // ESP-AT can use longer than 2K HTML and DYNAMIC_PARAMETERS + // ESP-AT can't use longer than 2K HTML and DYNAMIC_PARAMETERS #undef USE_DYNAMIC_PARAMETERS #define USE_DYNAMIC_PARAMETERS false #warning Disable USE_DYNAMIC_PARAMETERS for ESP_AT_SHIELD + + // From 2-6 to keep HTML short for ESP8266-AT. Limited 6 in WiFiEspAT library anyway + #define MAX_SSID_IN_LIST 6 + +#else + + // From 2-15 + #define MAX_SSID_IN_LIST 8 #endif +///////////////////////////////////////////// + #include #define HOST_NAME "nRF52-Master-Controller" diff --git a/examples/nRF52_WiFi/nRF52_WiFi.ino b/examples/nRF52_WiFi/nRF52_WiFi.ino index ebe5cca0..c46807b3 100644 --- a/examples/nRF52_WiFi/nRF52_WiFi.ino +++ b/examples/nRF52_WiFi/nRF52_WiFi.ino @@ -8,7 +8,7 @@ Built by Khoi Hoang https://github.com/khoih-prog/WiFiManager_Generic_Lite Licensed under MIT license - Version: 1.2.0 + Version: 1.3.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -19,7 +19,8 @@ Add customs HTML header feature. Fix bug. 1.1.2 K Hoang 30/03/2021 Fix MultiWiFi connection bug. 1.1.3 K Hoang 12/04/2021 Fix invalid "blank" Config Data treated as Valid. - 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.3.0 Michael H 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal *****************************************************************************************************************************/ #include "defines.h" #include "Credentials.h" diff --git a/examples/nRF52_WiFi_MQTT/defines.h b/examples/nRF52_WiFi_MQTT/defines.h index 053b27fa..563c6fe3 100644 --- a/examples/nRF52_WiFi_MQTT/defines.h +++ b/examples/nRF52_WiFi_MQTT/defines.h @@ -167,13 +167,30 @@ ///////////////////////////////////////////// +#define SCAN_WIFI_NETWORKS true + +// To be able to manually input SSID, not from a scanned SSID lists +#define MANUAL_SSID_INPUT_ALLOWED true + +///////////////////////////////////////////// + #if (USE_WIFI_CUSTOM && USE_ESP_AT_SHIELD) - // ESP-AT can use longer than 2K HTML and DYNAMIC_PARAMETERS + // ESP-AT can't use longer than 2K HTML and DYNAMIC_PARAMETERS #undef USE_DYNAMIC_PARAMETERS #define USE_DYNAMIC_PARAMETERS false - #error Can't use with ESP_AT_SHIELD + #warning Disable USE_DYNAMIC_PARAMETERS for ESP_AT_SHIELD + + // From 2-6 to keep HTML short for ESP8266-AT. Limited 6 in WiFiEspAT library anyway + #define MAX_SSID_IN_LIST 6 + +#else + + // From 2-15 + #define MAX_SSID_IN_LIST 8 #endif +///////////////////////////////////////////// + #include #define HOST_NAME "nRF52-MQTT-Controller" diff --git a/examples/nRF52_WiFi_MQTT/nRF52_WiFi_MQTT.ino b/examples/nRF52_WiFi_MQTT/nRF52_WiFi_MQTT.ino index d459560f..2d0ef704 100644 --- a/examples/nRF52_WiFi_MQTT/nRF52_WiFi_MQTT.ino +++ b/examples/nRF52_WiFi_MQTT/nRF52_WiFi_MQTT.ino @@ -9,7 +9,7 @@ Built by Khoi Hoang https://github.com/khoih-prog/WiFiManager_Generic_Lite Licensed under MIT license - Version: 1.2.0 + Version: 1.3.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -20,7 +20,8 @@ Add customs HTML header feature. Fix bug. 1.1.2 K Hoang 30/03/2021 Fix MultiWiFi connection bug. 1.1.3 K Hoang 12/04/2021 Fix invalid "blank" Config Data treated as Valid. - 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.3.0 Michael H 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal *****************************************************************************************************************************/ /**************************************************************************************************************************** diff --git a/library.json b/library.json index 41c4ff54..8c73c98b 100644 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "WiFiManager_Generic_Lite", - "version": "1.2.0", + "version": "1.3.0", "keywords": "wifi, wi-fi, multi-wifi, WiFiNINA, WiFi101, ESP32-AT, ESP8266-AT, Teensy, SAM-DUE, SAMD, STM32, nRF52, Credentials, config-portal, Double-Reset, dynamic-params, LittleFS, FlashStorage, FlashStorage-SAMD, FlashStorage-STM32", "description": "Library to configure MultiWiFi/Credentials at runtime for Teensy, SAM DUE, SAMD21, SAMD51, STM32F/L/H/G/WB/MP1, nRF52, etc. boards running Generic WiFi (WiFiNINA, WiFi101, ESP8266-AT, ESP32-AT, etc.) modules/shields. You can also specify DHCP HostName, static AP and STA IP. Use much less memory compared to full-fledge WiFiManager. Config Portal will be auto-adjusted to match the number of dynamic custom parameters. Optional default Credentials to be autoloaded into Config Portal to use or change instead of manually input. Credentials are saved in LittleFS/InternalFS, (emulated-)EEPROM, FlashStorage_SAMD, FlashStorage_STM32 or DueFlashStorage. New powerful-yet-simple-to-use feature to enable adding dynamic custom parameters from sketch and input using the same Config Portal. DoubleDetectDetector as well as Virtual Switches feature permits entering Config Portal as requested.", "authors": diff --git a/library.properties b/library.properties index 86c69568..0f7bfc33 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=WiFiManager_Generic_Lite -version=1.2.0 +version=1.3.0 author=Khoi Hoang maintainer=Khoi Hoang license=MIT diff --git a/pics/ESP_AT_Input_With_Scan.png b/pics/ESP_AT_Input_With_Scan.png new file mode 100644 index 0000000000000000000000000000000000000000..65173d1476c422c936bffaffc51f305103e95271 GIT binary patch literal 19580 zcmdSBbx@UW*FU-e1C-D*1GVQlMzL~O?VrDK%k3@2`eBFNbc}&1Qi*cxvhl% z4gN#16BJiMMMWK(k)4JgvFt@&+ACTa+B?0rH9#mDJJ{PB*y_FOxrIPHM2HJNS8^U* zn=lfNFFH>7!aVVt4rXsJXv?Th@SZ8k3L_r z)$7;2wJvrV8X9bDY_&)TX=!os)`=RY-QC^r@bLUEU-tI)`1F>3HaD-Vu8s{4508w@ z44|+|o0^(xRomZ1y>|ERT@w=%w%dt>m=s0^203|o8ZTc?*P^i6z;{0i&HnoJ>x&Fi zBCh%QdE~bO)Ht&r;a7sGa*sj>_Dr!EV^IVW1(*KgHW3f)5r|ZjtDC-B1NYa?4z&$C zJ3A`*>b%Zd0$N&$SlFbbo12@-5fQ8gZ4qQbKK2{qv04n=++)9f`JHpq)32Q!@5aW) z@?W0q4#IlrG-sPma>@FnoxP;|Yk&6bejaMl$ zM%PqV_p#P_U0&Ryl6it=H}m7Vo}S)8Gtrm6zCIFO*I)W9O$qbu(WE@ipI*moZEZCL z;?ph2#-Qw=&IA{l*sYinx|VNJZg&ODQUTEj;)kd=#;(gy|x>Mt)&k9QY0Cu$DY zMoNxkZ}dEDwx_43Cn6@M325u?&N3P8?Ja^uUTYa^Y$T_pm0*l6F(ct|mZQcY;d75k zbU)fk&B@8}@p+-35LQu9@&3IKuG{@*<}bCilaiB@{r$yVU0pLW)Ya8-L@Xw2t4d2F z68lSxWYN)sa`Bb>Ki+>D5fOnw!sDlYz2~9e;!yI1IfMh4Gr7$i<$MBg<<*hSmovA%}<-Ux(WiG-;2xiI$5R?^eZUXC^hMy ztO*VXXoGJ^OG`^gNH{w`huc(56dC-Sn>$+VuzGs9nXZDw>emy?>*gpX<_F)XtDBaa zyK=I>y4Vrh$Y|^6`1|*7K8F>Yv5xy}rV-K6sxB_a%l%om$QHX2^-fhb_hB*blE3&! zz^r?;HDfNO+&MMn1u+2W_WJc}kJAIKN^7#lC!|_R(km+~k3vz)Zo}4KM7JAUayYIL z_%@Px%i&}Y2yE}|ef;<_qE#O<7;d#3Vz;P>)BVWW(XqU`n#;{$S*Wg@{Bn9b*MZq` z`rF%<5YiBT|K+KM%gH)7-1ACUN?$RGT=@_*MC$Iywlj~L+o@AB8MFScPti;|H*Vgn zu$oG$V@QpT9*U$>b=;inJjl;2nYWp1R<6U4?q`Ypcs|zG$HK}w@Z-lLKEC=DkwJ>+ zc11-+3kwVRJOrWKlDpX9#spO0XXe9CT=$F}yHs^NMC1p$61jPKcyg;kWfHlR>jYt& zjg6Pa@VjSX;^Iur%v9^LI%w7h*6AI*%^(MTjXh%LYaUn8x|G@mB{ma ze`tAm8Mf8?)3vX<`}VCgvprPAg|5WF3eps9K9Btv z?j>!ce}|sG)#UzP1A8h)h#n3>DjJ$rNYh!A4{H5cvX?!(ySYO2c-&lE!6dxu+qE}; zLzEB`6Z5N|LGYFscDg!U{La@f>P`ycy7nfj>og-X^WtF4s*IZ8!Grjin3pCC$zB(& zlm7Ps?l!4#Zab}y?r)%UndN@{`sB$I>*JmIwcRSj^c~DZ%Zcg_zP@yn=Vxb`vdLW+ zoliRIJx+blAN+>H3h4SENKnaBwh{Y!Zbx5r_43TO=LqrIWR_z;)y!Y{A9-)uHZ7=|`bTS<`=8 z^67sRbh;;~%Bz`aLA|)Xqz3=a?L$vd`?k&|A=+wI}&&}-u{ydh9I@|QRtbr{} z;Iz|c8QGd?gm3DY+r}W}>)`8e>+jzkF4E(5-4j?x%D%3R!jt*t%^N2p$EBXs6@}yD zq)6>%-QvlDX15A61anN%M?eXmo-0zo`&Z2<*UNS`5F}k3Jv1D3anv<5=$b#P0YYUzLL{rq)wpKl=Eea^UubD-du~qh9&7>#*>NPm1(jcliHnaE z>D}~IJZ7`yAoB9MeE04h%1sPvL&vpY=EsjS0A$QZU}Jr}y(u0)E?S*?(29_*$e3GO za|gNrwTPHF5ngXyrRi8-Uw`3um;9NHb+I9ZLis=v?e_(k9$Dki3^j0^+}ADOV0mKJ6B2Opn`18sg&OGBGj5Wm-+vwlb=Jef1o&M<#)z2&xEd^{so9P@UY3x%v3` z01?_dI`pZerKN?0gxD>|E6qn(NJ54(rQ_k!)L*{D5_H{P8B7)O?dg#t3GwYgIw~Zm zU|Z)nIytGXslg;;3FrgSJ)Ao6fpbpaul1)TGLWT00?VR|>OBKi5X z8#hk=Rzjj>brjJOUA^WJZ#HoMsuO>IH!#-!sfeKb6&Di&KOw^;9ymvBIW>Q^~ThIsN$+n#k?cHgBt{sv4JhxH;uD`}3A#Zv>6v z$=-6~MV-^8ww~VH@bF8Y*NmE#uguMf?%hit76UFFLc*)h606&_GvAI$!gI9YNZ?cD zvYQznuTkT;z5%JfvF+D13!F5O-zzDR$7a_1!v_&XPk(=}$#__^S?}g#Vr-lp5D<`;N5|~~ z=?D)%fh&4c@b>NZ^V37Rf=eia5_Y-%Y1y*QgdQFqss);{$;ly?(Qh^eTWTIaImQv0 zUtH{GU0+%nE;eXCEC5l_e&FxSljQ3ws-zU5$!LU6{1+W84*hxK?O#-|+1D{QFOM4n z1$Zu>^Hxe<8Zc(h^Aj4%iHQk2J3B=M1ynLv&tI*rHgH8c? zE=s#V%4Ki7HT@l!6x7GaR(U3`Q%8qXtkg5Zj+kG+#2oFP(bAd%xmMmBnw(6+ zQpgG20j>xzLVX~JYGZ4AGUH2nMIL8ndO_t}zk_#0ZB7aq85u1Bl_0Ih;W9THE(;0^ z3t#?OTRU4!_9Ef5{nHWq6pHwo84;W5001-K#{*JQubn^Q3=H`|e_7DN!@`Q)k9Q0^ z<0c?0E2cchYMiWqX#vX}F4Y~Aj*5zcB+IVp5gVK!H#Y1}>YTSVU{U)goxShl!fvBd zpef#88WIvRdoM3P-(zKf#>i+9xK=PBTU%EbBP;9P-g3XTwl-9DJcFx_Ae|f!1fXFxjXw}5GN!i0-79w?7Ali(MI4iQs>4#Ha=Ww zqt}GKF<#X_I9LYCXYCIFFHRuvTtL97`nsef67Jgt5GN*m6oRO}jV1tYO{p6*jlOv~ zImf4`Elo|h`1t&82VdaLAZ;SKa@V#|9@^*rn719_hfQssv8F|L)YsQ{c5{o4j4U%B z*@17-jLIzb=S4Tyu!2Qj=P*g0Ll{(mo1whyB zTIh@iI2buEx11R5?6iOo>FVl&lA>bY@>6#ks>NhYG^kFqD&C)fG2Im$1zNkj zD~pl^z{A|Fm)qaOik^$oBxu> z?O+W^AnaDPQ8!5w!9D4ISj<;aizQ2wbv41kT|n7@0(W*DujCa{2LJ`mg8UvdxbMU1 zxb`wjCXp6n3QKUfXtB7MrHRPH<2<0#7w|h^j)m0V_3Q18m;1gzz*|~db5OHVy!jT&c6Lyli4_4)^8c7z~6rFi;W*2q>OB>X+*3D~pR) zdkMT!B5)Wjmm`M7ditAWG^2l$Zuzdm#n~|se~9HXR)u zC#S;|v`kiO*#A*@JIEQn_2@&s3IJqKaEB08w6wI@^>Ul0cTuICcx(YMGcz;2&bCF# zxeQ~aAR($#QXW!Zuxd-Q1A)DL=MJ~ihGrAtb&R^y3f^92xioCnrxq>}=maVP*C7 z^mK^jKGLN_4A6kt^mps&-^h;+20uT4lk7L7zw0Q0rGaQb7f|7AQw>x7{UwkmpeHPv z7Kt>gY@1qIppIm?9d69EgfzGxvv6O61$B8kbvZsUk(ik1;qE?Mv*lrY0ZTwPxz;!~ zRx|r2TRwf53Kbs@4?x=$)HYafpeW_XA09&r0VIM*-`LpjIN5VKSc`p0uU+G40Z8L| zxKRh;6gT+^6_Kjg&GPiAY!Xiu=tNi>fWz(>=Jy_+Lb|%MwJy8rrN%|T!r#0>fb(SB z`^YgAR4QowhK5U~hO@LUUwFU;fpEj@+=Q=`k?8`Ggi0nNA`(K%-_?5ccr8TBz|c7| zQdL_!-qNxNHCe~&d><&#HRSJ51vkg5(m8$soJzx$tqm8!ZbCWZ27VF}(%qLKX$26v zJu9L_{$?fbax9Vst;Bg-_h5tx0)phpt8cC0RN$A9^11>@)xvKa*&rq0Nq*<8Z@s;M zRW=7}P~{IPy+I#?ag_D>v#_G#Zrv_15fRWtY;0@@PoQ5WuU^Tis5q|vW{{QbuB_x> zV=IHgodlE;J`5C<*+exP+0UjXgMn;$z+zC{dWReH^G%a@7mx)YQ1^Ft^{kkCA%>Wy zDFYDFc^Tw5w{g2Y@813Z%)q?jS9?1h2S;UX?HMpaqpk!_&od4(VcJM+L6ApZLF_@O zU+fR*xbsa74Mo@8*>PK>qoElY8>6D2z!HS(1k*v^%&h$&X?Av&%V`7jpyg!k2)MA% zY*5K~ND8f{>OoB!k}8VOjz8pNW)8*bOBcm-RFIa=e}Rd7{mqHhvXZNMQgyYfy!>4< zVG8W)H*UNT6>Xol1;uip*Nu^K?fP|K0jpQ5B$GJ1?R@-L|sDWAqrb#NG#0Cfj=s?+AA$JtR4?08+Bhr0SG2yHN}0oF&}MZP0vVv>D`MNLOn5*`p=QG%I} zTWJd}P3~O*z1xWZn@2~^2b%!uzL=!Nm}!zyQudCH8-sb#adCvli{de8jkT`(rr+Km ztwqvHO15*_EfCzlKmX^CyD=Zx&++l`pFb(+>7^tk^8n=Gu7MejmRrO}Mn(cXAo~m~ z4@jIc4}&aYw5X`4(^AjOM7jZ~EFm#*r%XVTZqs)Zfw?8^yMkcuL9}!(?p9b%m>L>B zvz`_Z6a!nQ0BXR-I49^{uo8TIe6R)E+S~U5o6yk#HhAq8I)GDM@fvWeJ3H5fc1N_X zjC)|kYZM_ZB_$=2Vq!5oE-Wv=;;K|%nTdvH2!X@f|I#P`8YVuz2H$m!y+Oylo^;SXi*6MDX+_O09|m4xwyCt z3=ANtuKWmi?CpK2EQD8p|5RC1Bb&f+R~da8J<|u`$O#C3=0Y$LXR+O)OqJ%w%8Eq) z-o^PTyy*Lr4zRR71_kNq>f)FIyFXa!6`~BMS)kXcKLde>*68i)dv%G-UeH_sB+99b zHtEk~MhlOL0b_X>nifu&=}^1k*{uvOqa-pEGo@SrrbY1?)JwC05d{PR{~8(|=3rq7 zOT_UPh8P41a=hI_I>N~kYYsIEQlseW*Q4{psrzr3lo*gxH>y=TcKL8UYGjjnv z7x1)3nuGt3Chhd|^8@w)Lb@pdHUNZnGS=++Ix!ZOT$A<{Q#%w{1++jMOVTl=A>&#9^I0HZJXG_afd*P3*3v6zF zK7%Q)0Ltw|OlYY7uX=`!y$_giuBw=%sPHP09=UVy4qSfXm~td z0-sSQ|SZU=ys5RRE_x=@lWxB_(aJp7{Zm zU}bf6nhKKAAdLx!FN^*!EO>X50OnYug620$?*wiqMi!RhuV1Yp$G|oO8wng(S{j<| zLr9l~hQPo(HKQOUz;YKs9f6V#WhFm9A6KKNzu&AsvlAL*K>Mi!u>FOv0uUI}VBj;W zNumc2G!zty?U(xs8!ouO=%J_oh=mR96^pTFf}jCoeSDh1zJaK3-pB^Z4*AZ>$qAKe zts)l024KDX3?e&DJ7s%+-@(bLaDn3v!hHnPz|nWA|BZ_i2A>?;K9ueUTB=(VlW9_W z6X7u0Gi?n75V|76Q(EkiJv8+69c^u+u&?g!?w+1}D@XzpJ->fD+S-qTmw`QHc zl^4P!E_w;9c;OB{V+uB6AOjj8Ag%z?pbb%F;!sH7^Lmr;F2qNXo~Y9GH`O*k9pUA@ zZ>UE9F$*wJ>rz4@`fSDGVQgI^3HHqt#-ij(V9>5SH#F;zuQ2StfhUfdL{>q65X2)9E< ztYE5j^T+$>m(zE-5J7#=Liix~x``7Q7I=-KONehqsRadm(4lSm4DJ&A`ax4Dxjlj~ z4=M%SiwL?KSb(*B(ZJ2_UHQZFAZ%X--=FG7GX4oY*p`p?`(JXYpc21f0t4v|;Sf@~ z+x5TCAN&VcUfo{(bWiWV0EIW?(SuDSfy64CIiJ~nRh^Z^2dzXHEt8WMKR(<63M0ge z$kxNaz#x*TW8+w%vIoro^t4LTQczG3xE<}^HVLeoUFe=(Lw??Wn;0D(9dxkuj9{!( z^AogOWPynk>VTHHoa}64V`J9Tp4S8~D8p}ELx$G{q{(3-R*q1h1Ej^K2- zeD@gJ|0TVOy!`W7#DJupD1;3?o+L$>>$1^+$_IceR0t=!yu2!3VO}bT@lN9P)EU{=-oC0;>t@=8ik~dRPz>oH2LY*Px z6cCcrN&e|t>H47u$@pRF?z|2J0C;21QEnp6=}TdqCg`pPIR0w^S0r3qs=~wXb#WsE z7fZOolZ2Fq+s{+Ux1L0b`%+z_Wpd^JgQQyMxDPZW!3j}n&&Lg5bo1uTfHaWBc_n;8 z;=0j$8*>XqYkOm-Xst&fjFZ`I7#*VTIR9OurSeR+Qm?9i-2ubt4@ zgxLW)FA{D1(1&T0HG7u7^kvyYB8{rDwGsQ{bx!kl{jT?ADQPfIWSSw;fZL%dXfzxVf8jLnhS@*iGtKevRXIy8 zesu}{A-nay;>--mE=u!Pl)N|Aco1&Ng$9@AM*K!I_yPQCrdy=zNyBDu`rkDca&F3C z3*N>)fJTa;pKs0L*8O&NO8xWGc~e4D$(;+dTT;{~0%A(?nqO?p@}l!Uc{I$0|h-nDmc&%n1;Jj6})?4q?5m+FbhP?aIBdeY!=>D3$&QJH9X z5>kKJIa8UU)9s{Yu2?tgQHn|?{IHm?*tt`TIF46pfa1-K(Y~DP^_gnBUNLd^v$yjO ze`rsB?4GtQ<92*X*VfwV5^L4l8;4CtH6`tF8bcvEjq0q^XjV&9vK?%{ekQ zh$CbTE}X{Am3!6Z2-RN1$m;n%l02Ir6;n-&khrisQg}MfE_LwdFJJo(uIC$`v#yL* zi0VGG!tZBez(DAzFS%2M`3YzV^qy3kddarucur5G^o!)e)ItK+#nHkYSIOF^qk4px1h6ZY@G- zc6B~YjX8Ld_>H_>%wfL9nJm6!u401WK#;d>tnERf(y}$VW;U|10t2!7gl9^i%-Udl z4V62tnL$UZ$H`hJ-K_X}?Sg&Kfh#;bC@aEJ^#R|jKfZN ze|o5URG^BQm$T%hmMA7>H6q(nBNZCbTYsS$DXX!Z^x@AZA7LOxijt0w?hXb=M*ZjoOpMAbFsil^a-TyJhK}eZ@ z%bL09nle^Tp9u>(`sC_Z10#kDMV`rXPWIEj1Bcf)88cOv@N(|QKKN?y?2Q$W*C?~B zMvy(Xos0-d%TT;HdJUA~D~;x;uAR+PI|U3E%Jb?npj$jQGelirZDx9TjNv`j*YN^}b* zBN13)l{p?dTRi)EIxAFVSc73Wz87Rjn3q!!z-j|#ROyDzXwhPnG8q@9Oh0wiC}fq;y-k_$Axm)4-#k7miNofb#nEE#gx7MJ`^ZD;fZn79Oo->%XoR<>HpiJ^C%!! zep}X-!1iJ_W)MmKzWRaPd%SAXv=_+gT*)2a3<9fy89R7bTU)b9%A+;p$r{f~E~J^d zkM0%+az+T_;6CA$U=EoTYr9Qnq0?jfTQDKfetxI2tGKuO^$`oVFFL;0$O0QWzPdp2d=YrGBMIU5?-7-XGtyK>fEw- zJ?Ypm`{^}y&TOvb^3z*#BshL(apw>}+;Sj=qcPOFX>f_e)0~^9NUi%Z29p=3t6qQY zV0Efw@9^8|VqhQ8zXa0yae3e~G1(B?lZCcEmM~qNPk64l_^t6yE5?GWha`i-o#@5-7kvSpc-di@x|)j6-5D6S$=+~LwNEHaa+*t ztKZ@uc${VrZP9_+*|JUAfCoWnTdPJEDVh;U32I*o(p!}F_ZwD-Zg7{fO37+RlzZ$J z2@Xgey^}{%EZJ;7`N!}0u1U{Jyk2b?bTi4t+D;payR4|g}S5{6& zgnY5W027juE>;HV{Dl?He*%qt6zV6O2FCvk10V!g-7p{f5K9h6xqlocY+-Z_928J6 z1vak+H>_bC6bnYCU@-8!Cug|t`4VEFrahWzs5+x@aTjbxO-)T0G&~p)5~U2ke*L-) z2NC#F06-vn1EXZherHkK`p&0eiAt1dncRQc+3z-gMn&b0FUrHVT4NjJ)UM8VGkn5U ztk!WXN7B=}U%8{(|7^Pjh10Nr4uvvaMLB`oHO_kd!XmiUm|df+VqI8V#(l3yvB)XO zFj_9oC+dJCyruF!ZK|n|(ERMI!fI47_%6^4^Y?#EkO@tZWOnzBbH<*)TY>(I3&y2)xu+KMmEX8Vh4j6^hm#FSZ|A zw+)V~E=u+EJP15xuK69cT$p6iE;R4s(>gIx_ZxEz4EHLxLrduG*xDBV%ghXOn05PG_c?exx`e3{fJKChA2Np z`MO-yOnZ7}=I;zi{I>#FckhORIp!inMMu{TU6CI@(w=|5aoKunswvcDUDW4fe<+Pk z8tCh05-LBQ9TmB3ruk%Qdv<8JIXc~5E9)L>pmyR|L;nobl;e-XCK=qO$EJrr8sf2J z{1j|_druc?@@|jG#7rDWmxvmVcFAq27L=AMNuj#F+{ys=7eqJ=9J0|W&uuQlbm*OH z$fl`s+pkj z)O=zRrdOp87f#OF-t&dbDrTTfRcg^uV7Pzx7EBd>l+!y%p`gRQoy+GFM!A;6WpdaM z9IH|HqUYFSXxBL1vINC&IIkycmAhfeey&evC~LU#>?vB)Vi_^7*|)jmbj$UVbM;fT zg+aPWt#B;CBZCFY63y}v zcgfT*wbODlnxAN*XUWec>>pWa5Ovbide!(g7Lo8&odzinkvtx{LKV5ojaHUp{0b|- zRvLEem{s=t@Ea?5-|z{zh}0ETlj*!p&Sru%bx#VDtJ|bU%X_&Kj#zOQcL5YYco{i*T`#$O(LN~5xu()T{5QXB>oiP0;XjyO#YX?@~A=ol}_ zm44N-x|#ogeUtcjD&d+PFD*aS;uUB9!rSanTg|*I@hd^YE9QaeQ-cqM`k8jWyUY$J z*GmscCYWs?ow`H=?etH6YsK};+G3ZxHw+8DZ_PBv|FUIzzI3vVEf{f|N#-P-9{yWU);$ylkL^vm)?%N!ol)2|X--<*^|x9Xpi z#=xfwX7*v6h5;`SI$~R%C<8TMAex$(z|_!{lg?;&6TF0fo$iI9SYjVVq6*g)f{^1u==Yb_js#wp@~N9Q!82;o8+oRxSVdS#C}(mnB=in6Az2-Vl6T6 zPt9ix54R;Vx9k}iveK4m%}=>*SDsk4A7-o;SNE70^dwZy4>Km(<_Eg?janT-zU6p12Hbv|i9;em@XQDTUl3mpc zHord9KYqoV!7fdi8Sp6kCVXJ+^Iqg67BX*Z{TCj-PSc8g9S;tha8*7<)H1rKPF8E# z`Gf^X2zz67wa5D}Yd&Uk+I)Q?u{pL-BZ5FMBpwpy6_j&Hw*`na;oQX%AI-si{(6XH zvB86$t0DnQki%ufMKd7Jz)<4ph02_g(O}lsMMo%`PgK|YaNT45*34gILS|{_BS^BR1}LcYhAK}eQa%7tI}I^RK^8y zML2A=Q1l90*!!?f@9uXD9K z^e)uR|HxS-o4hT@iHK8?^wquAT<2PDQ6LKnx-vR0%XBcm`$x*B-uz=UGH({bI_g*1 z;cSN@vmZ*lv}z;s%qP=I4D#4@W_nldel-~sTXap6+8?u!{iDN3Dc7& zTkUAXg1><8wYRtHGMX* zXXEXg)Av}n5i6p4uE7Q)jlP_ZX`clc%7iKviOKLbcpOH)RB~Ufx6i&_k(=-Lm~Dbd zk?0l)?8L1C&Zu_D##9Y6f3ReLw0T!$eo< zliyi|ISNhrttFen#9S_X%NObR$XSs!(Zi{D$bwnhGm^xaUoLuQi)^BwYYKX`F|;2q z-<4EJc5C&h;Lpr7GRgjac8?2Li}#0ISWCR&rSqQ*rIC)VsKd35vN<}E#hz58^C=$o z#MU6~idWKq6ywr9oJ6TCL)56|c8BZ+lkNyc*F0#TD)>y3+K$OPxLj#myv<}L zqdaHMR~!1%I%1vYWTnR&YaXLft3!s=%bI)p{$R6TD__yf(bJ^V-7vO5B31$za*{`P z<=G$1`A~1xJ=L{ftRA}@F;N#yUTC=c$E73VXA9F~+J*eF^1d0vsi0$rrrDTr3F6Od zdU;-cZMx<+Z!&pW&Hg$HIVj|e>s;q-ZGfI~{>N7l{@6l(FCS&1l6ecQ#x4IcXdCI) zmS?$0FA-(YXCc;mAK}qZF!go&n-05nfIJcsL!ysRTZ4_Yn{8~zPu?d3-D)3ZaCEeJ zjuwYa8=e%!E_SDunE$bF&3T;ud$N|qA>;GVt9`F$+Snz}o*cOr<&1SBO}%Wmq^P~H zYfQx%JlJhm&+2SAPBIRk9N+ESo3r{MX3XI!FCa^7chjuv!_iG&&(oUokCrF1XCFUe zV~}|(%802+nBw_@;cVl+h%SgqMip{-u=ol6ZT&Ms<2@MldQLRb|Hti^=xxF2*Q8wG zF1ga`PfXdJKCJ89@$IvBN|O2KKb3o9Jz8uCRwI@>l4!TF>=7Jd7pip=^4|JrY)5Yrhf54!Xahg4gf_2?$NqxVVBe5#fZUII#l;tt7L{JQMTh?El1DsM zaw6f@cDvf`)Ni9&LdF`F4Lk68&-BWyh9@q^Y+d&jm|w8cJ_5=>`nZj5K~57_i1&oQ z>>9OtcINY^ME+kinUyGLe~}ZZ3!vV;_d~loYJt~ldsg}|U#fxPaECPdc2%kP&gJOv${34U{?7LA5`=VUr2>*e)$hnRjVbCd8Fw5RyNwO7_jFbB);C~nE5#6E~s{C^i z@67ZW42t%6r}Z8|ZEZZ4=@6q$QfuW$U=~Feel8oX3_f;hSK)YItF42M#u0U%6#k0E zw7crI=W=(3@$1VBd?$wD?5+b7OYNQG$9LXu_LmwIT2-4p@m45WHy4VuD-=zpedyg2 zmM32!Qfs^Gm_I0Y=PHD4^c7a+r}k=Vw>FRR@6};k`NZ0pza|~y{#0i&C@uLGl1Cq- zMt$%cD{CGbT3OlLWHb34s^Nw&YCp;Q?a#fSJf0x!ste)fz>XHqM*)%@6K5)4Q3i=( zLr;fDeCsdiEF(j@V;9Ch{%JYZBwuV5VlaO9R$o9e>ut{l_E(#^j~B$!V&CUZX<~KR zge19*s1$v$1exE-JoO?O8xgM16uee;|5YZz^E@?v^!{-C42voEFI(YkJuG1x{B5uI zxU0}FJUb_e@0>2v;N>`+OpPj&*HlCqO~`bl1l73QxR$>bEUDTvASZ?1kWmgtaKN8( zb93Xai90v;drc6RIU?FaKu9?IH$qBuaF_KjKgDk91593t2n)kV2n=7HKP|WC;pK&i zA82%MuZ=vdwLnP2c-wDMuQNDYX<}hi=k2twU*#yn2eXUF!&_l4 z24iEgK4pi|L%3DmlT(A{D#@_g~i1z_zc&5iAOmbiI1SO`Zvep z(Us5L)zl`r=Zgx(N!*ZX*#+ly{&UKoQI1WIa+2< z6p7>M{FO>j|6H_$ww!#?P-nEBaqU}^{jCqKH?~Y*78)KN2OGF#6dwC$qpbF*3{<+e zTB*L1>cpE(b_wEL?ukppd6)jVEBGsY&W%xdGv9m|6S;Ic)>V5rMIf* zrR_W86kkH2HU)(e4xWMxWx6=5okxyMZJ#tS@7&fejq6_B4?`f({`$KBsUnLrQ=a^N zGRj44Ma}v7+65Z7-!TPWMkw7S+!v{ zo#xyj2~WJRYW!w!sLpk!trnB@7yc{;env)@^bOUU@P_VVjm@UX+Y~j8Mfv)vAmf}R zp87*)MEw)=3b&@2mlY-q;dhF4CYeACB~Ml#yo2Kz4Du57w)qiQKO|+_QaE|SWh|}? z;Qvvz=@5(Dk>;|8nTnO+!i=IfSb{r+$KA)T51V{#I~|ETDwmn5Jkef8QBZo%;A9c; z+8kdzr5XB_nHZa-l%4w>OOQZ9{gDcb$ynQ`_(Yrv7a%m60a$_(bIA4aC8xT!NK-uu zI{U{#k`0VWB(9oGp&^Q4hywwmZtdKqQQW`5ijv=|o1DwV(YxazRW^32BW zEdJT3Z}83OJN?!gTtrot8tn@miP5qCu|NAM**E&0#vThy)nnp)$V{lFa9ixk#_Zmlu9d{;!A!BnJ-;tL^x97qd@VSi0CSqtV66KT zUtNg0Z>JI}sr#;(zRq^#*N5ZvQxj0DvgQkSD}C!!MC^9EuzuW&N7tw_iV8|#yrFFNoM9%s$=<`Lsl&-Y}FVcX<)VnrLHt(IM^c>&efjpEwkMI=B1NFp1x{t2i`T zQ-kOCCT%)5`Oz=qJ1-K74f?*2Gg&3ON+p=*a*$xM-5A8_>B}Lf#K`wXYn2xF_}*P_ zvw)9$r<;v3%xmKhl-`gYHo$F#{c5=)cKaK4*)h-5o>s8_{_aMe%C#F!9~) z9vJ*P;w9&?r74??U#5qVU2H=5`5BGxE(;^y`@GK!e70}!$h;ewNjXNJ2Z{BkZ{pYW zi>lZz{Z?;#QdpuR@2z_+zorbEMObM|@QgI%CWvO)C_l#ua}F;m{`$xxP1lQ1fs(8e z6upc|W%jd>)kUj}ayKPP>W#k1Bw5r>xv)}2x_3Vcc)Pq#4MZ%DeJc{8wK`HM(z4&Q z$_KdMh$^TA8JWnH%1OUB2Qo1#@i;+pxjc@?$ooAunHtj} zouF@H_JdpVEc0vJIejxPtoROh781D#H(V1E!zA46G+F6hJP+cVFRiZ&ui|+;H{RMm zNqlP7XWyK|ZyAB*>C0!qN}zBqt!%@Soer}siqGn*mq@t6TgXNeC%ASA1W|QXrh7#W zorRG^?yQfG*85XEbu*K3-|3t$@v=9~XvdlKI<2(-pE^!l?af_gGDF6f-X(PPCvG(c zcD$K?9LhU%N9u7g*tk6uR(q%RI5(Y`4Lg_OMLSt_jCed4?wN)puu3(&aQPnY88?Sy zd_;J{kugx7Rd>5Hba~j-UdcVV$@FVDvvhS{mztdpg*M0OP?JVg z+X4fZM)_=sWYCKnNBbquWE6aS7wslsEL5xh<4?8xW76lph5!G0D)zr$dX#eG+`GGmEF2}h z*iJ&<8w*KQw7soZ5_5BVay_2e8w*r*`^9;b){F|R0r#=MmT)Kh=vn_|t$2E%80k{| zEyMn#h}BT2(Ud=z{f>LzxhT^$89FDXB64rBVn*US+*?EY10C2BW1kausi)N5OGl!< zwmop?KB@4n_2PZzN8Z1EHotV3IG_C*5kzSZfR!Acr1%5uEVGRE=maO4L4xUdvrpBQ&jz%_Mm1pES+C&d%;S@s2H2)+7LLMMFJS1J0O%ew^ph zW$Tm*vQ6e|@HmMkl?*zdxBY{iRP@2)LGWI3Pw0F`X6@&BL0stp#y8Es`{1uupf#)> zeM&xQyG1sez>jxoUgNfJy|+85_tCiNHrN5Q>_&c{9c#k>Q}G|kM&%q5&4j+PT;atA+&dpH;mFub|68k!VL^ZUW^ z#>>db(a~biG{`nzv%{ENl8&K`q2{@ox7Nc9U(2r|;=-f6wA9kkjB@r_cjPf&??22i zz`MRb{a89~wW=H3~#ROdBpVJ+Kk& z5C2Hq9(9O$sA0>OccwRokLiseE637W<5?JtpFXSNjr?lq;ebObIqEwswxRfIuRk(V zwzCNIZjugmb38UjptPElM5zucPmUIiId@<`m{l9}@cGDcRL11V#y_ySNI35s;uyPi zu3M2PouCFD+#K(@MTt+Y-L|%m2FeBmA zaAq&MJ*ZEZ;{bo*v#4+IL)3QHGYTUYm5x=W`b7M2V%SA`95HzLj<(sSQUj=|9XzU*uBN z*X^D7VYtBUh~a35wbIwq`s>%$&U1feiJbQN;<0t@8(8~+ei-w1f$C<&`PYsLlI{?u z4B2#y%2_CXdve=*+q4vBoM47B!W1QO}PI%mP%j>uqgHOW>Mo%=Dc{?v2pI))L0*M^++`M|6;y0YXk{T;Gvs;K%<()$JiI=tb_%?Z6 zA}%+%jO##sC-IE6gwrXzxVpA4bs%1a5jBOGrUhu!`8jMP|TZFK*j%FyH)_QPG;aa#|i`JKFYx3IK~^$ngpcXi^1Xn)3} zFZR!kgb}I!MXCE#8i&Rtwojq0u1*B-AELus3ZY*uZPrcy%pH3=fq{WI^$7i2Hk%E5 zItZcvABGM>2tx-UgrS2FLKr#-Aq*Xa5W>(w2=yFa0dN7{M=%vr>;M1&07*qoM6N<$ Ef(SCIH~;_u literal 0 HcmV?d00001 diff --git a/pics/Input_With_Scan.png b/pics/Input_With_Scan.png new file mode 100644 index 0000000000000000000000000000000000000000..ad9a1178b11524fa52a76bece2a21301c3b22292 GIT binary patch literal 36371 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 setting. #endif -#define WIFI_MANAGER_GENERIC_LITE_VERSION "WiFiManager_Generic_Lite v1.2.0" +#define WIFI_MANAGER_GENERIC_LITE_VERSION "WiFiManager_Generic_Lite v1.3.0" #if (USE_WIFI_NINA || USE_WIFI101) #include @@ -62,6 +63,34 @@ DueFlashStorage dueFlashStorageData; #define USING_CORS_FEATURE false #endif +////////////////////////////////////////////// + +// New from v1.3.0 +// KH, Some minor simplification +#if !defined(SCAN_WIFI_NETWORKS) + #define SCAN_WIFI_NETWORKS true //false +#endif + +#if 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 ///////////// // These defines must be put before #include // to select where to store DoubleResetDetector_Generic's variable. @@ -149,12 +178,15 @@ const char WIFI_GENERIC_HTML_HEAD_START[] /*PROGMEM*/ = "div,input{padding:5px;font-size:1em;}input{width:95%;}body{text-align: center;}button{background-color:#16A1E7;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%;}fieldset{border-radius:0.3rem;margin:0px;}"; -const char WIFI_GENERIC_HTML_HEAD_END[] /*PROGMEM*/ = "
\ -
\ -
\ -
\ -
\ -
"; +const char WIFI_GENERIC_HTML_HEAD_END[] /*PROGMEM*/ = "
\ +
[[input_id]]
\ +
\ +
[[input_id1]]
\ +
\ +
"; // DO NOT CHANGE THIS STRING EVER!!!! + +const char WIFI_GENERIC_HTML_INPUT_ID[] /*PROGMEM*/ = ""; +const char WIFI_GENERIC_HTML_INPUT_ID1[] /*PROGMEM*/ = ""; const char WIFI_GENERIC_FLDSET_START[] /*PROGMEM*/ = "
"; const char WIFI_GENERIC_FLDSET_END[] /*PROGMEM*/ = "
"; @@ -171,6 +203,16 @@ const char WIFI_GENERIC_HTML_SCRIPT_ITEM[] /*PROGMEM*/ = "udVal('{d}',document. const char WIFI_GENERIC_HTML_SCRIPT_END[] /*PROGMEM*/ = "alert('Updated');}"; const char WIFI_GENERIC_HTML_END[] /*PROGMEM*/ = ""; +#if SCAN_WIFI_NETWORKS +const char WIFI_GENERIC_SELECT_START[] /*PROGMEM*/ = "" + String(WIFI_GENERIC_DATALIST_START) + "'SSIDs'>" + ListOfSSIDs + WIFI_GENERIC_DATALIST_END); + WG_LOGDEBUG1(F("pitem:"), pitem); + pitem.replace("[[input_id1]]", "" + String(WIFI_GENERIC_DATALIST_START) + "'SSIDs'>" + ListOfSSIDs + WIFI_GENERIC_DATALIST_END); + + WG_LOGDEBUG1(F("pitem:"), pitem); + +#else + pitem.replace("[[input_id]]", "" + ListOfSSIDs + WIFI_GENERIC_SELECT_END); +#endif + + root_html_template += pitem + WIFI_GENERIC_FLDSET_START; + +#else + + pitem = String(WIFI_GENERIC_HTML_HEAD_END); + pitem.replace("[[input_id]]", WIFI_GENERIC_HTML_INPUT_ID); + pitem.replace("[[input_id1]]", WIFI_GENERIC_HTML_INPUT_ID1); + root_html_template += pitem + WIFI_GENERIC_FLDSET_START; + +#endif // SCAN_WIFI_NETWORKS #if USE_DYNAMIC_PARAMETERS for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) @@ -1558,11 +1664,22 @@ class WiFiManager_Generic_Lite result.replace("DUE_WM_Lite", WIFI_GENERIC_config.board_name); } - result.replace("[[id]]", WIFI_GENERIC_config.WiFi_Creds[0].wifi_ssid); - result.replace("[[pw]]", WIFI_GENERIC_config.WiFi_Creds[0].wifi_pw); - result.replace("[[id1]]", WIFI_GENERIC_config.WiFi_Creds[1].wifi_ssid); - result.replace("[[pw1]]", WIFI_GENERIC_config.WiFi_Creds[1].wifi_pw); - result.replace("[[nm]]", WIFI_GENERIC_config.board_name); + if (hadConfigData) + { + result.replace("[[id]]", WIFI_GENERIC_config.WiFi_Creds[0].wifi_ssid); + result.replace("[[pw]]", WIFI_GENERIC_config.WiFi_Creds[0].wifi_pw); + result.replace("[[id1]]", WIFI_GENERIC_config.WiFi_Creds[1].wifi_ssid); + result.replace("[[pw1]]", WIFI_GENERIC_config.WiFi_Creds[1].wifi_pw); + result.replace("[[nm]]", WIFI_GENERIC_config.board_name); + } + else + { + result.replace("[[id]]", ""); + result.replace("[[pw]]", ""); + result.replace("[[id1]]", ""); + result.replace("[[pw1]]", ""); + result.replace("[[nm]]", ""); + } #if USE_DYNAMIC_PARAMETERS for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) @@ -1737,6 +1854,12 @@ class WiFiManager_Generic_Lite void startConfigurationMode() { +#if SCAN_WIFI_NETWORKS + configTimeout = 0; // To allow user input in CP + + WiFiNetworksFound = scanWifiNetworks(&indices); +#endif + WiFi.config(portal_apIP); if ( (portal_ssid == "") || portal_pass == "" ) @@ -1800,6 +1923,179 @@ class WiFiManager_Generic_Lite 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) + { + WG_LOGDEBUG(F("Scanning Network")); + + int n = WiFi.scanNetworks(); + + WG_LOGDEBUG1(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) + { + WG_LOGDEBUG(F("No network found")); + return (0); + } + else + { + // Allocate space off the heap for indices array. + // This space should be freed when no longer required. + int* indices = (int *)malloc(n * sizeof(int)); + + if (indices == NULL) + { + WG_LOGDEBUG(F("ERROR: Out of memory")); + *indicesptr = NULL; + return (0); + } + + *indicesptr = indices; + + //sort networks + for (int i = 0; i < n; i++) + { + indices[i] = i; + } + + WG_LOGDEBUG(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]); + } + } + } + + WG_LOGDEBUG(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])) + { + WG_LOGDEBUG1("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; + WG_LOGDEBUG(F("Skipping low quality")); + } + } + + WG_LOGDEBUG(F("WiFi networks found:")); + + for (int i = 0; i < n; i++) + { + if (indices[i] == -1) + continue; // skip dups + else + WG_LOGDEBUG5(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/WiFiManager_Generic_Lite_Debug.h b/src/WiFiManager_Generic_Lite_Debug.h index 3b6c4ec3..c4f7bb33 100644 --- a/src/WiFiManager_Generic_Lite_Debug.h +++ b/src/WiFiManager_Generic_Lite_Debug.h @@ -9,7 +9,7 @@ Built by Khoi Hoang https://github.com/khoih-prog/WiFiManager_Generic_Lite Licensed under MIT license - Version: 1.2.0 + Version: 1.3.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -20,7 +20,8 @@ Add customs HTML header feature. Fix bug. 1.1.2 K Hoang 30/03/2021 Fix MultiWiFi connection bug. 1.1.3 K Hoang 12/04/2021 Fix invalid "blank" Config Data treated as Valid. - 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.3.0 Michael H 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal *****************************************************************************************************************************/ #ifndef WiFiManager_Generic_Lite_Debug_h diff --git a/src/WiFiManager_Generic_Lite_SAMD.h b/src/WiFiManager_Generic_Lite_SAMD.h index a375fa23..905850c9 100644 --- a/src/WiFiManager_Generic_Lite_SAMD.h +++ b/src/WiFiManager_Generic_Lite_SAMD.h @@ -8,7 +8,7 @@ Built by Khoi Hoang https://github.com/khoih-prog/WiFiManager_Generic_Lite Licensed under MIT license - Version: 1.2.0 + Version: 1.3.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -19,7 +19,8 @@ Add customs HTML header feature. Fix bug. 1.1.2 K Hoang 30/03/2021 Fix MultiWiFi connection bug. 1.1.3 K Hoang 12/04/2021 Fix invalid "blank" Config Data treated as Valid. - 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.3.0 Michael H 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal *****************************************************************************************************************************/ #ifndef WiFiManager_Generic_Lite_SAMD_h @@ -38,7 +39,7 @@ #error This code is intended to run on the SAMD platform! Please check your Tools->Board setting. #endif -#define WIFI_MANAGER_GENERIC_LITE_VERSION "WiFiManager_Generic_Lite v1.2.0" +#define WIFI_MANAGER_GENERIC_LITE_VERSION "WiFiManager_Generic_Lite v1.3.0" #if (USE_WIFI_NINA || USE_WIFI101) #include @@ -63,6 +64,34 @@ #define USING_CORS_FEATURE false #endif +////////////////////////////////////////////// + +// New from v1.3.0 +// KH, Some minor simplification +#if !defined(SCAN_WIFI_NETWORKS) + #define SCAN_WIFI_NETWORKS true //false +#endif + +#if 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 ///////////// // These defines must be put before #include // to select where to store DoubleResetDetector_Generic's variable. @@ -148,12 +177,15 @@ const char WIFI_GENERIC_HTML_HEAD_START[] /*PROGMEM*/ = "div,input{padding:5px;font-size:1em;}input{width:95%;}body{text-align: center;}button{background-color:#16A1E7;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%;}fieldset{border-radius:0.3rem;margin:0px;}"; -const char WIFI_GENERIC_HTML_HEAD_END[] /*PROGMEM*/ = "
\ -
\ -
\ -
\ -
\ -
"; +const char WIFI_GENERIC_HTML_HEAD_END[] /*PROGMEM*/ = "
\ +
[[input_id]]
\ +
\ +
[[input_id1]]
\ +
\ +
"; // DO NOT CHANGE THIS STRING EVER!!!! + +const char WIFI_GENERIC_HTML_INPUT_ID[] /*PROGMEM*/ = ""; +const char WIFI_GENERIC_HTML_INPUT_ID1[] /*PROGMEM*/ = ""; const char WIFI_GENERIC_FLDSET_START[] /*PROGMEM*/ = "
"; const char WIFI_GENERIC_FLDSET_END[] /*PROGMEM*/ = "
"; @@ -170,6 +202,16 @@ const char WIFI_GENERIC_HTML_SCRIPT_ITEM[] /*PROGMEM*/ = "udVal('{d}',document. const char WIFI_GENERIC_HTML_SCRIPT_END[] /*PROGMEM*/ = "alert('Updated');}"; const char WIFI_GENERIC_HTML_END[] /*PROGMEM*/ = ""; +#if SCAN_WIFI_NETWORKS +const char WIFI_GENERIC_SELECT_START[] /*PROGMEM*/ = "" + String(WIFI_GENERIC_DATALIST_START) + "'SSIDs'>" + ListOfSSIDs + WIFI_GENERIC_DATALIST_END); + WG_LOGDEBUG1(F("pitem:"), pitem); + pitem.replace("[[input_id1]]", "" + String(WIFI_GENERIC_DATALIST_START) + "'SSIDs'>" + ListOfSSIDs + WIFI_GENERIC_DATALIST_END); - root_html_template += String(WIFI_GENERIC_HTML_HEAD_END) + WIFI_GENERIC_FLDSET_START; + WG_LOGDEBUG1(F("pitem:"), pitem); + +#else + pitem.replace("[[input_id]]", "" + ListOfSSIDs + WIFI_GENERIC_SELECT_END); +#endif + + root_html_template += pitem + WIFI_GENERIC_FLDSET_START; + +#else + + pitem = String(WIFI_GENERIC_HTML_HEAD_END); + pitem.replace("[[input_id]]", WIFI_GENERIC_HTML_INPUT_ID); + pitem.replace("[[input_id1]]", WIFI_GENERIC_HTML_INPUT_ID1); + root_html_template += pitem + WIFI_GENERIC_FLDSET_START; + +#endif // SCAN_WIFI_NETWORKS #if USE_DYNAMIC_PARAMETERS for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) @@ -1534,12 +1640,23 @@ class WiFiManager_Generic_Lite // Or replace only if board_name is valid. Otherwise, keep intact result.replace("SAMD_WM_NINA_Lite", WIFI_GENERIC_config.board_name); } - - result.replace("[[id]]", WIFI_GENERIC_config.WiFi_Creds[0].wifi_ssid); - result.replace("[[pw]]", WIFI_GENERIC_config.WiFi_Creds[0].wifi_pw); - result.replace("[[id1]]", WIFI_GENERIC_config.WiFi_Creds[1].wifi_ssid); - result.replace("[[pw1]]", WIFI_GENERIC_config.WiFi_Creds[1].wifi_pw); - result.replace("[[nm]]", WIFI_GENERIC_config.board_name); + + if (hadConfigData) + { + result.replace("[[id]]", WIFI_GENERIC_config.WiFi_Creds[0].wifi_ssid); + result.replace("[[pw]]", WIFI_GENERIC_config.WiFi_Creds[0].wifi_pw); + result.replace("[[id1]]", WIFI_GENERIC_config.WiFi_Creds[1].wifi_ssid); + result.replace("[[pw1]]", WIFI_GENERIC_config.WiFi_Creds[1].wifi_pw); + result.replace("[[nm]]", WIFI_GENERIC_config.board_name); + } + else + { + result.replace("[[id]]", ""); + result.replace("[[pw]]", ""); + result.replace("[[id1]]", ""); + result.replace("[[pw1]]", ""); + result.replace("[[nm]]", ""); + } #if USE_DYNAMIC_PARAMETERS for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) @@ -1714,6 +1831,12 @@ class WiFiManager_Generic_Lite void startConfigurationMode() { +#if SCAN_WIFI_NETWORKS + configTimeout = 0; // To allow user input in CP + + WiFiNetworksFound = scanWifiNetworks(&indices); +#endif + WiFi.config(portal_apIP); if ( (portal_ssid == "") || portal_pass == "" ) @@ -1777,6 +1900,179 @@ class WiFiManager_Generic_Lite 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) + { + WG_LOGDEBUG(F("Scanning Network")); + + int n = WiFi.scanNetworks(); + + WG_LOGDEBUG1(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) + { + WG_LOGDEBUG(F("No network found")); + return (0); + } + else + { + // Allocate space off the heap for indices array. + // This space should be freed when no longer required. + int* indices = (int *)malloc(n * sizeof(int)); + + if (indices == NULL) + { + WG_LOGDEBUG(F("ERROR: Out of memory")); + *indicesptr = NULL; + return (0); + } + + *indicesptr = indices; + + //sort networks + for (int i = 0; i < n; i++) + { + indices[i] = i; + } + + WG_LOGDEBUG(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]); + } + } + } + + WG_LOGDEBUG(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])) + { + WG_LOGDEBUG1("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; + WG_LOGDEBUG(F("Skipping low quality")); + } + } + + WG_LOGDEBUG(F("WiFi networks found:")); + + for (int i = 0; i < n; i++) + { + if (indices[i] == -1) + continue; // skip dups + else + WG_LOGDEBUG5(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/WiFiManager_Generic_Lite_STM32.h b/src/WiFiManager_Generic_Lite_STM32.h index cd54cc49..046b588e 100644 --- a/src/WiFiManager_Generic_Lite_STM32.h +++ b/src/WiFiManager_Generic_Lite_STM32.h @@ -8,7 +8,7 @@ Built by Khoi Hoang https://github.com/khoih-prog/WiFiManager_Generic_Lite Licensed under MIT license - Version: 1.2.0 + Version: 1.3.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -19,15 +19,16 @@ Add customs HTML header feature. Fix bug. 1.1.2 K Hoang 30/03/2021 Fix MultiWiFi connection bug. 1.1.3 K Hoang 12/04/2021 Fix invalid "blank" Config Data treated as Valid. - 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.3.0 Michael H 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal *****************************************************************************************************************************/ #ifndef WiFiManager_Generic_Lite_STM32_h #define WiFiManager_Generic_Lite_STM32_h -#if ( defined(STM32F0) || defined(STM32F1) || defined(STM32F2) || defined(STM32F3) ||defined(STM32F4) || defined(STM32F7) || \ - defined(STM32L0) || defined(STM32L1) || defined(STM32L4) || defined(STM32H7) ||defined(STM32G0) || defined(STM32G4) || \ - defined(STM32WB) || defined(STM32MP1) ) +#if ( defined(STM32F0) || defined(STM32F1) || defined(STM32F2) || defined(STM32F3) ||defined(STM32F4) || defined(STM32F7) || \ + defined(STM32L0) || defined(STM32L1) || defined(STM32L4) || defined(STM32H7) ||defined(STM32G0) || defined(STM32G4) || \ + defined(STM32WB) || defined(STM32MP1) || defined(STM32L5) ) #if defined(WIFI_GENERIC_USE_STM32) #undef WIFI_GENERIC_USE_STM32 #endif @@ -38,7 +39,7 @@ #error This code is intended to run on STM32 platform! Please check your Tools->Board setting. #endif -#define WIFI_MANAGER_GENERIC_LITE_VERSION "WiFiManager_Generic_Lite v1.2.0" +#define WIFI_MANAGER_GENERIC_LITE_VERSION "WiFiManager_Generic_Lite v1.3.0" #if (USE_WIFI_NINA || USE_WIFI101) #include @@ -62,6 +63,34 @@ #define USING_CORS_FEATURE false #endif +////////////////////////////////////////////// + +// New from v1.3.0 +// KH, Some minor simplification +#if !defined(SCAN_WIFI_NETWORKS) + #define SCAN_WIFI_NETWORKS true //false +#endif + +#if 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 ///////////// // These defines must be put before #include // to select where to store DoubleResetDetector_Generic's variable. @@ -149,12 +178,15 @@ const char WIFI_GENERIC_HTML_HEAD_START[] /*PROGMEM*/ = "div,input{padding:5px;font-size:1em;}input{width:95%;}body{text-align: center;}button{background-color:#16A1E7;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%;}fieldset{border-radius:0.3rem;margin:0px;}"; -const char WIFI_GENERIC_HTML_HEAD_END[] /*PROGMEM*/ = "
\ -
\ -
\ -
\ -
\ -
"; +const char WIFI_GENERIC_HTML_HEAD_END[] /*PROGMEM*/ = "
\ +
[[input_id]]
\ +
\ +
[[input_id1]]
\ +
\ +
"; // DO NOT CHANGE THIS STRING EVER!!!! + +const char WIFI_GENERIC_HTML_INPUT_ID[] /*PROGMEM*/ = ""; +const char WIFI_GENERIC_HTML_INPUT_ID1[] /*PROGMEM*/ = ""; const char WIFI_GENERIC_FLDSET_START[] /*PROGMEM*/ = "
"; const char WIFI_GENERIC_FLDSET_END[] /*PROGMEM*/ = "
"; @@ -171,6 +203,16 @@ const char WIFI_GENERIC_HTML_SCRIPT_ITEM[] /*PROGMEM*/ = "udVal('{d}',document. const char WIFI_GENERIC_HTML_SCRIPT_END[] /*PROGMEM*/ = "alert('Updated');}"; const char WIFI_GENERIC_HTML_END[] /*PROGMEM*/ = ""; +#if SCAN_WIFI_NETWORKS +const char WIFI_GENERIC_SELECT_START[] /*PROGMEM*/ = "" + String(WIFI_GENERIC_DATALIST_START) + "'SSIDs'>" + ListOfSSIDs + WIFI_GENERIC_DATALIST_END); + WG_LOGDEBUG1(F("pitem:"), pitem); + pitem.replace("[[input_id1]]", "" + String(WIFI_GENERIC_DATALIST_START) + "'SSIDs'>" + ListOfSSIDs + WIFI_GENERIC_DATALIST_END); + + WG_LOGDEBUG1(F("pitem:"), pitem); + +#else + pitem.replace("[[input_id]]", "" + ListOfSSIDs + WIFI_GENERIC_SELECT_END); +#endif + + root_html_template += pitem + WIFI_GENERIC_FLDSET_START; + +#else + + pitem = String(WIFI_GENERIC_HTML_HEAD_END); + pitem.replace("[[input_id]]", WIFI_GENERIC_HTML_INPUT_ID); + pitem.replace("[[input_id1]]", WIFI_GENERIC_HTML_INPUT_ID1); + root_html_template += pitem + WIFI_GENERIC_FLDSET_START; + +#endif // SCAN_WIFI_NETWORKS #if USE_DYNAMIC_PARAMETERS for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) @@ -1529,11 +1635,22 @@ class WiFiManager_Generic_Lite result.replace("STM32_WM_Lite", WIFI_GENERIC_config.board_name); } - result.replace("[[id]]", WIFI_GENERIC_config.WiFi_Creds[0].wifi_ssid); - result.replace("[[pw]]", WIFI_GENERIC_config.WiFi_Creds[0].wifi_pw); - result.replace("[[id1]]", WIFI_GENERIC_config.WiFi_Creds[1].wifi_ssid); - result.replace("[[pw1]]", WIFI_GENERIC_config.WiFi_Creds[1].wifi_pw); - result.replace("[[nm]]", WIFI_GENERIC_config.board_name); + if (hadConfigData) + { + result.replace("[[id]]", WIFI_GENERIC_config.WiFi_Creds[0].wifi_ssid); + result.replace("[[pw]]", WIFI_GENERIC_config.WiFi_Creds[0].wifi_pw); + result.replace("[[id1]]", WIFI_GENERIC_config.WiFi_Creds[1].wifi_ssid); + result.replace("[[pw1]]", WIFI_GENERIC_config.WiFi_Creds[1].wifi_pw); + result.replace("[[nm]]", WIFI_GENERIC_config.board_name); + } + else + { + result.replace("[[id]]", ""); + result.replace("[[pw]]", ""); + result.replace("[[id1]]", ""); + result.replace("[[pw1]]", ""); + result.replace("[[nm]]", ""); + } #if USE_DYNAMIC_PARAMETERS for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) @@ -1708,6 +1825,12 @@ class WiFiManager_Generic_Lite void startConfigurationMode() { +#if SCAN_WIFI_NETWORKS + configTimeout = 0; // To allow user input in CP + + WiFiNetworksFound = scanWifiNetworks(&indices); +#endif + WiFi.config(portal_apIP); if ( (portal_ssid == "") || portal_pass == "" ) @@ -1771,6 +1894,179 @@ class WiFiManager_Generic_Lite 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) + { + WG_LOGDEBUG(F("Scanning Network")); + + int n = WiFi.scanNetworks(); + + WG_LOGDEBUG1(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) + { + WG_LOGDEBUG(F("No network found")); + return (0); + } + else + { + // Allocate space off the heap for indices array. + // This space should be freed when no longer required. + int* indices = (int *)malloc(n * sizeof(int)); + + if (indices == NULL) + { + WG_LOGDEBUG(F("ERROR: Out of memory")); + *indicesptr = NULL; + return (0); + } + + *indicesptr = indices; + + //sort networks + for (int i = 0; i < n; i++) + { + indices[i] = i; + } + + WG_LOGDEBUG(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]); + } + } + } + + WG_LOGDEBUG(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])) + { + WG_LOGDEBUG1("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; + WG_LOGDEBUG(F("Skipping low quality")); + } + } + + WG_LOGDEBUG(F("WiFi networks found:")); + + for (int i = 0; i < n; i++) + { + if (indices[i] == -1) + continue; // skip dups + else + WG_LOGDEBUG5(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 }; #endif //WiFiManager_Generic_Lite_STM32_h diff --git a/src/WiFiManager_Generic_Lite_Teensy.h b/src/WiFiManager_Generic_Lite_Teensy.h index 903578b7..5acc94ca 100644 --- a/src/WiFiManager_Generic_Lite_Teensy.h +++ b/src/WiFiManager_Generic_Lite_Teensy.h @@ -8,7 +8,7 @@ Built by Khoi Hoang https://github.com/khoih-prog/WiFiManager_Generic_Lite Licensed under MIT license - Version: 1.2.0 + Version: 1.3.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -19,7 +19,8 @@ Add customs HTML header feature. Fix bug. 1.1.2 K Hoang 30/03/2021 Fix MultiWiFi connection bug. 1.1.3 K Hoang 12/04/2021 Fix invalid "blank" Config Data treated as Valid. - 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.3.0 Michael H 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal *****************************************************************************************************************************/ #ifndef WiFiManager_Generic_Lite_Teensy_h @@ -39,7 +40,7 @@ #error Teensy 2.0 not supported yet #endif -#define WIFI_MANAGER_GENERIC_LITE_VERSION "WiFiManager_Generic_Lite v1.2.0" +#define WIFI_MANAGER_GENERIC_LITE_VERSION "WiFiManager_Generic_Lite v1.3.0" #if (USE_WIFI_NINA || USE_WIFI101) #include @@ -62,6 +63,34 @@ #define USING_CORS_FEATURE false #endif +////////////////////////////////////////////// + +// New from v1.3.0 +// KH, Some minor simplification +#if !defined(SCAN_WIFI_NETWORKS) + #define SCAN_WIFI_NETWORKS true //false +#endif + +#if 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 ///////////// // These defines must be put before #include // to select where to store DoubleResetDetector_Generic's variable. @@ -148,12 +177,15 @@ const char WIFI_GENERIC_HTML_HEAD_START[] /*PROGMEM*/ = "div,input{padding:5px;font-size:1em;}input{width:95%;}body{text-align: center;}button{background-color:#16A1E7;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%;}fieldset{border-radius:0.3rem;margin:0px;}"; -const char WIFI_GENERIC_HTML_HEAD_END[] /*PROGMEM*/ = "
\ -
\ -
\ -
\ -
\ -
"; +const char WIFI_GENERIC_HTML_HEAD_END[] /*PROGMEM*/ = "
\ +
[[input_id]]
\ +
\ +
[[input_id1]]
\ +
\ +
"; // DO NOT CHANGE THIS STRING EVER!!!! + +const char WIFI_GENERIC_HTML_INPUT_ID[] /*PROGMEM*/ = ""; +const char WIFI_GENERIC_HTML_INPUT_ID1[] /*PROGMEM*/ = ""; const char WIFI_GENERIC_FLDSET_START[] /*PROGMEM*/ = "
"; const char WIFI_GENERIC_FLDSET_END[] /*PROGMEM*/ = "
"; @@ -170,6 +202,16 @@ const char WIFI_GENERIC_HTML_SCRIPT_ITEM[] /*PROGMEM*/ = "udVal('{d}',document. const char WIFI_GENERIC_HTML_SCRIPT_END[] /*PROGMEM*/ = "alert('Updated');}"; const char WIFI_GENERIC_HTML_END[] /*PROGMEM*/ = ""; +#if SCAN_WIFI_NETWORKS +const char WIFI_GENERIC_SELECT_START[] /*PROGMEM*/ = "" + String(WIFI_GENERIC_DATALIST_START) + "'SSIDs'>" + ListOfSSIDs + WIFI_GENERIC_DATALIST_END); + WG_LOGDEBUG1(F("pitem:"), pitem); + pitem.replace("[[input_id1]]", "" + String(WIFI_GENERIC_DATALIST_START) + "'SSIDs'>" + ListOfSSIDs + WIFI_GENERIC_DATALIST_END); + + WG_LOGDEBUG1(F("pitem:"), pitem); + +#else + pitem.replace("[[input_id]]", "" + ListOfSSIDs + WIFI_GENERIC_SELECT_END); +#endif + + root_html_template += pitem + WIFI_GENERIC_FLDSET_START; + +#else + + pitem = String(WIFI_GENERIC_HTML_HEAD_END); + pitem.replace("[[input_id]]", WIFI_GENERIC_HTML_INPUT_ID); + pitem.replace("[[input_id1]]", WIFI_GENERIC_HTML_INPUT_ID1); + root_html_template += pitem + WIFI_GENERIC_FLDSET_START; + +#endif // SCAN_WIFI_NETWORKS #if USE_DYNAMIC_PARAMETERS for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) @@ -1551,11 +1657,22 @@ class WiFiManager_Generic_Lite result.replace("Teensy_WM_Lite", WIFI_GENERIC_config.board_name); } - result.replace("[[id]]", WIFI_GENERIC_config.WiFi_Creds[0].wifi_ssid); - result.replace("[[pw]]", WIFI_GENERIC_config.WiFi_Creds[0].wifi_pw); - result.replace("[[id1]]", WIFI_GENERIC_config.WiFi_Creds[1].wifi_ssid); - result.replace("[[pw1]]", WIFI_GENERIC_config.WiFi_Creds[1].wifi_pw); - result.replace("[[nm]]", WIFI_GENERIC_config.board_name); + if (hadConfigData) + { + result.replace("[[id]]", WIFI_GENERIC_config.WiFi_Creds[0].wifi_ssid); + result.replace("[[pw]]", WIFI_GENERIC_config.WiFi_Creds[0].wifi_pw); + result.replace("[[id1]]", WIFI_GENERIC_config.WiFi_Creds[1].wifi_ssid); + result.replace("[[pw1]]", WIFI_GENERIC_config.WiFi_Creds[1].wifi_pw); + result.replace("[[nm]]", WIFI_GENERIC_config.board_name); + } + else + { + result.replace("[[id]]", ""); + result.replace("[[pw]]", ""); + result.replace("[[id1]]", ""); + result.replace("[[pw1]]", ""); + result.replace("[[nm]]", ""); + } #if USE_DYNAMIC_PARAMETERS for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) @@ -1748,6 +1865,12 @@ class WiFiManager_Generic_Lite void startConfigurationMode() { +#if SCAN_WIFI_NETWORKS + configTimeout = 0; // To allow user input in CP + + WiFiNetworksFound = scanWifiNetworks(&indices); +#endif + WiFi.config(portal_apIP); if ( (portal_ssid == "") || portal_pass == "" ) @@ -1819,6 +1942,179 @@ class WiFiManager_Generic_Lite 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) + { + WG_LOGDEBUG(F("Scanning Network")); + + int n = WiFi.scanNetworks(); + + WG_LOGDEBUG1(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) + { + WG_LOGDEBUG(F("No network found")); + return (0); + } + else + { + // Allocate space off the heap for indices array. + // This space should be freed when no longer required. + int* indices = (int *)malloc(n * sizeof(int)); + + if (indices == NULL) + { + WG_LOGDEBUG(F("ERROR: Out of memory")); + *indicesptr = NULL; + return (0); + } + + *indicesptr = indices; + + //sort networks + for (int i = 0; i < n; i++) + { + indices[i] = i; + } + + WG_LOGDEBUG(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]); + } + } + } + + WG_LOGDEBUG(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])) + { + WG_LOGDEBUG1("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; + WG_LOGDEBUG(F("Skipping low quality")); + } + } + + WG_LOGDEBUG(F("WiFi networks found:")); + + for (int i = 0; i < n; i++) + { + if (indices[i] == -1) + continue; // skip dups + else + WG_LOGDEBUG5(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/WiFiManager_Generic_Lite_nRF52.h b/src/WiFiManager_Generic_Lite_nRF52.h index 2264f8ad..8dffe5b8 100644 --- a/src/WiFiManager_Generic_Lite_nRF52.h +++ b/src/WiFiManager_Generic_Lite_nRF52.h @@ -8,7 +8,7 @@ Built by Khoi Hoang https://github.com/khoih-prog/WiFiManager_Generic_Lite Licensed under MIT license - Version: 1.2.0 + Version: 1.3.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -19,7 +19,8 @@ Add customs HTML header feature. Fix bug. 1.1.2 K Hoang 30/03/2021 Fix MultiWiFi connection bug. 1.1.3 K Hoang 12/04/2021 Fix invalid "blank" Config Data treated as Valid. - 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.2.0 K Hoang 14/04/2021 Optional one set of WiFi Credentials. Enforce WiFi PWD minimum 8 chars + 1.3.0 Michael H 24/04/2021 Enable scan of WiFi networks for selection in Configuration Portal *****************************************************************************************************************************/ #ifndef WiFiManager_Generic_Lite_nRF52_h @@ -36,7 +37,7 @@ #error This code is intended to run on the nRF52 platform! Please check your Tools->Board setting. #endif -#define WIFI_MANAGER_GENERIC_LITE_VERSION "WiFiManager_Generic_Lite v1.2.0" +#define WIFI_MANAGER_GENERIC_LITE_VERSION "WiFiManager_Generic_Lite v1.3.0" #if (USE_WIFI_NINA || USE_WIFI101) #include @@ -65,6 +66,34 @@ File file(InternalFS); #define USING_CORS_FEATURE false #endif +////////////////////////////////////////////// + +// New from v1.3.0 +// KH, Some minor simplification +#if !defined(SCAN_WIFI_NETWORKS) + #define SCAN_WIFI_NETWORKS true //false +#endif + +#if 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 ///////////// // These defines must be put before #include // to select where to store DoubleResetDetector_Generic's variable. @@ -153,12 +182,15 @@ const char WIFI_GENERIC_HTML_HEAD_START[] /*PROGMEM*/ = "div,input{padding:5px;font-size:1em;}input{width:95%;}body{text-align: center;}button{background-color:#16A1E7;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%;}fieldset{border-radius:0.3rem;margin:0px;}"; -const char WIFI_GENERIC_HTML_HEAD_END[] /*PROGMEM*/ = "
\ -
\ -
\ -
\ -
\ -
"; +const char WIFI_GENERIC_HTML_HEAD_END[] /*PROGMEM*/ = "
\ +
[[input_id]]
\ +
\ +
[[input_id1]]
\ +
\ +
"; // DO NOT CHANGE THIS STRING EVER!!!! + +const char WIFI_GENERIC_HTML_INPUT_ID[] /*PROGMEM*/ = ""; +const char WIFI_GENERIC_HTML_INPUT_ID1[] /*PROGMEM*/ = ""; const char WIFI_GENERIC_FLDSET_START[] /*PROGMEM*/ = "
"; const char WIFI_GENERIC_FLDSET_END[] /*PROGMEM*/ = "
"; @@ -175,6 +207,16 @@ const char WIFI_GENERIC_HTML_SCRIPT_ITEM[] /*PROGMEM*/ = "udVal('{d}',document. const char WIFI_GENERIC_HTML_SCRIPT_END[] /*PROGMEM*/ = "alert('Updated');}"; const char WIFI_GENERIC_HTML_END[] /*PROGMEM*/ = ""; +#if SCAN_WIFI_NETWORKS +const char WIFI_GENERIC_SELECT_START[] /*PROGMEM*/ = "" + String(WIFI_GENERIC_DATALIST_START) + "'SSIDs'>" + ListOfSSIDs + WIFI_GENERIC_DATALIST_END); + WG_LOGDEBUG1(F("pitem:"), pitem); + pitem.replace("[[input_id1]]", "" + String(WIFI_GENERIC_DATALIST_START) + "'SSIDs'>" + ListOfSSIDs + WIFI_GENERIC_DATALIST_END); + + WG_LOGDEBUG1(F("pitem:"), pitem); + +#else + pitem.replace("[[input_id]]", "" + ListOfSSIDs + WIFI_GENERIC_SELECT_END); +#endif + + root_html_template += pitem + WIFI_GENERIC_FLDSET_START; + +#else + + pitem = String(WIFI_GENERIC_HTML_HEAD_END); + pitem.replace("[[input_id]]", WIFI_GENERIC_HTML_INPUT_ID); + pitem.replace("[[input_id1]]", WIFI_GENERIC_HTML_INPUT_ID1); + root_html_template += pitem + WIFI_GENERIC_FLDSET_START; + +#endif // SCAN_WIFI_NETWORKS #if USE_DYNAMIC_PARAMETERS for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) @@ -1750,11 +1856,22 @@ class WiFiManager_Generic_Lite result.replace("nRF52_WM_Lite", WIFI_GENERIC_config.board_name); } - result.replace("[[id]]", WIFI_GENERIC_config.WiFi_Creds[0].wifi_ssid); - result.replace("[[pw]]", WIFI_GENERIC_config.WiFi_Creds[0].wifi_pw); - result.replace("[[id1]]", WIFI_GENERIC_config.WiFi_Creds[1].wifi_ssid); - result.replace("[[pw1]]", WIFI_GENERIC_config.WiFi_Creds[1].wifi_pw); - result.replace("[[nm]]", WIFI_GENERIC_config.board_name); + if (hadConfigData) + { + result.replace("[[id]]", WIFI_GENERIC_config.WiFi_Creds[0].wifi_ssid); + result.replace("[[pw]]", WIFI_GENERIC_config.WiFi_Creds[0].wifi_pw); + result.replace("[[id1]]", WIFI_GENERIC_config.WiFi_Creds[1].wifi_ssid); + result.replace("[[pw1]]", WIFI_GENERIC_config.WiFi_Creds[1].wifi_pw); + result.replace("[[nm]]", WIFI_GENERIC_config.board_name); + } + else + { + result.replace("[[id]]", ""); + result.replace("[[pw]]", ""); + result.replace("[[id1]]", ""); + result.replace("[[pw1]]", ""); + result.replace("[[nm]]", ""); + } #if USE_DYNAMIC_PARAMETERS for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) @@ -1929,6 +2046,12 @@ class WiFiManager_Generic_Lite void startConfigurationMode() { +#if SCAN_WIFI_NETWORKS + configTimeout = 0; // To allow user input in CP + + WiFiNetworksFound = scanWifiNetworks(&indices); +#endif + WiFi.config(portal_apIP); if ( (portal_ssid == "") || portal_pass == "" ) @@ -1992,6 +2115,179 @@ class WiFiManager_Generic_Lite 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) + { + WG_LOGDEBUG(F("Scanning Network")); + + int n = WiFi.scanNetworks(); + + WG_LOGDEBUG1(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) + { + WG_LOGDEBUG(F("No network found")); + return (0); + } + else + { + // Allocate space off the heap for indices array. + // This space should be freed when no longer required. + int* indices = (int *)malloc(n * sizeof(int)); + + if (indices == NULL) + { + WG_LOGDEBUG(F("ERROR: Out of memory")); + *indicesptr = NULL; + return (0); + } + + *indicesptr = indices; + + //sort networks + for (int i = 0; i < n; i++) + { + indices[i] = i; + } + + WG_LOGDEBUG(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]); + } + } + } + + WG_LOGDEBUG(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])) + { + WG_LOGDEBUG1("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; + WG_LOGDEBUG(F("Skipping low quality")); + } + } + + WG_LOGDEBUG(F("WiFi networks found:")); + + for (int i = 0; i < n; i++) + { + if (indices[i] == -1) + continue; // skip dups + else + WG_LOGDEBUG5(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 };