From 86a124df886237ee0d9f7b9f59eacfc952058a51 Mon Sep 17 00:00:00 2001 From: Khoi Hoang <57012152+khoih-prog@users.noreply.github.com> Date: Wed, 24 Feb 2021 02:37:51 -0500 Subject: [PATCH] ### Major Releases v1.2.0 ### Major Releases v1.2.0 1. Configurable **Customs HTML Headers**, including Customs Style, Customs Head Elements, CORS Header. 2. Add support to **ESP32-S2 (ESP32-S2 Saola and AI-Thinker ESP-12K)**. Currently using EEPROM only. To add support to LittleFS and SPIFFS in future releases. 3. Fix Config Portal Bug. 4. Tested with [**Latest ESP32 Core 1.0.5**](https://github.com/espressif/arduino-esp32) for ESP32-based boards. 5. Update examples --- CONTRIBUTING.md | 4 +- README.md | 321 +++++++++++++-- .../AM2315_ESP32_SSL/AM2315_ESP32_SSL.ino | 52 ++- examples/AM2315_ESP32_SSL/defines.h | 46 ++- examples/AM2315_ESP8266/AM2315_ESP8266.ino | 52 ++- examples/AM2315_ESP8266/defines.h | 108 +++-- .../Blynk_WM_Template/Blynk_WM_Template.ino | 14 +- examples/DHT11ESP32/DHT11ESP32.ino | 54 ++- examples/DHT11ESP32/defines.h | 45 +- examples/DHT11ESP32_SSL/DHT11ESP32_SSL.ino | 56 ++- examples/DHT11ESP32_SSL/defines.h | 45 +- examples/DHT11ESP8266/DHT11ESP8266.ino | 58 ++- examples/DHT11ESP8266/defines.h | 118 +++--- .../DHT11ESP8266_Debug/DHT11ESP8266_Debug.ino | 60 ++- examples/DHT11ESP8266_Debug/defines.h | 118 +++--- .../DHT11ESP8266_SSL/DHT11ESP8266_SSL.ino | 56 ++- examples/DHT11ESP8266_SSL/defines.h | 116 +++--- examples/ESP32WM_Config/ESP32WM_Config.ino | 52 ++- examples/ESP32WM_Config/defines.h | 29 +- .../ESP32WM_ForcedConfig.ino | 52 ++- examples/ESP32WM_ForcedConfig/defines.h | 28 +- .../ESP32WM_MRD_Config/ESP32WM_MRD_Config.ino | 24 +- examples/ESP32WM_MRD_Config/defines.h | 34 +- .../ESP32WM_MRD_ForcedConfig.ino | 52 ++- examples/ESP32WM_MRD_ForcedConfig/defines.h | 30 +- .../ESP8266WM_Config/ESP8266WM_Config.ino | 54 ++- examples/ESP8266WM_Config/defines.h | 19 +- .../ESP8266WM_ForcedConfig.ino | 54 ++- examples/ESP8266WM_ForcedConfig/defines.h | 48 ++- .../ESP8266WM_MRD_Config.ino | 52 ++- examples/ESP8266WM_MRD_Config/defines.h | 44 +- .../ESP8266WM_MRD_ForcedConfig.ino | 21 + examples/ESP8266WM_MRD_ForcedConfig/defines.h | 48 ++- keywords.txt | 27 +- library.json | 6 +- library.properties | 6 +- pics/esp32_s2_Core_Unzipped.png | Bin 0 -> 45169 bytes pics/esp32_s2_Toolchain.png | Bin 0 -> 22374 bytes pics/esp32_s2_esptool.png | Bin 0 -> 46738 bytes pics/esp32_s2_tools.png | Bin 0 -> 41911 bytes src/BlynkSimpleEsp32_SSL_WM.h | 360 +++++++++++++--- src/BlynkSimpleEsp32_WM.h | 389 +++++++++++++++--- src/BlynkSimpleEsp8266_SSL_WM.h | 328 ++++++++++++--- src/BlynkSimpleEsp8266_WM.h | 358 +++++++++++++--- 44 files changed, 2645 insertions(+), 793 deletions(-) create mode 100644 pics/esp32_s2_Core_Unzipped.png create mode 100644 pics/esp32_s2_Toolchain.png create mode 100644 pics/esp32_s2_esptool.png create mode 100644 pics/esp32_s2_tools.png diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cf7300c..de46c6a 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 -* `ESP8266` or `ESP32` Core Version (e.g. ESP8266 core v2.7.4 or ESP32 v1.0.4) +* `ESP8266` or `ESP32` Core Version (e.g. ESP8266 core v2.7.4 or ESP32 v1.0.5) * 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: @@ -29,7 +29,7 @@ Please ensure to specify the following: Arduino IDE version: 1.8.13 ESP8266 Core Version 2.7.4 OS: Ubuntu 20.04 LTS -Linux Inspiron 5.4.0-60-generic #67-Ubuntu SMP Tue Jan 5 18:31:36 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux +Linux Inspiron 5.4.0-65-generic #73-Ubuntu SMP Mon Jan 18 17:25:17 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 6e9206d..f9f28dc 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ * [Features](#features) * [Currently supported Boards](#currently-supported-boards) * [Changelog](#changelog) + * [Major Releases v1.2.0](#major-releases-v120) * [Releases v1.1.3](#releases-v113) * [Releases v1.1.2](#releases-v112) * [Releases v1.1.1](#releases-v111) @@ -33,6 +34,21 @@ * [Installation](#installation) * [I) For Arduino IDE](#i-for-arduino-ide) * [II) For VS Code & PlatformIO:](#ii-for-vs-code--platformio) +* [HOWTO Install esp32-s2 core for ESP32-S2 (Saola, AI-Thinker ESP-12K) boards into Arduino IDE)](#howto-install-esp32-s2-core-for-esp32-s2-saola-ai-thinker-esp-12k-boards-into-arduino-ide) + * [1. Save the original esp32 core](#1-save-the-original-esp32-core) + * [2. Download esp32-s2 core](#2-download-esp32-s2-core) + * [2.1 Download zip](#21-download-zip) + * [2.2 Unzip](#22-unzip) + * [2.3 Update esp32-s2 core directories](#23-update-esp32-s2-core-directories) + * [3. Download tools](#3-download-tools) + * [3.1 Download Toolchain for Xtensa (ESP32-S2) based on GCC](#31-download-toolchain-for-xtensa-esp32-s2-based-on-gcc) + * [3.2 Download esptool](#32-download-esptool) + * [3.3 Unzip](#33-unzip) + * [4. Update tools](#4-update-tools) + * [4.1 Update Toolchain](#41-update-toolchain) + * [4.2 Update esptool](#42-update-esptool) + * [5. esp32-s2 WebServer Library Patch](#5-esp32-s2-webserver-library-patch) +* [Note for Platform IO using ESP32 LittleFS](#note-for-platform-io-using-esp32-littlefs) * [HOWTO Use analogRead() with ESP32 running WiFi and/or BlueTooth (BT/BLE)](#howto-use-analogread-with-esp32-running-wifi-andor-bluetooth-btble) * [1. ESP32 has 2 ADCs, named ADC1 and ADC2](#1--esp32-has-2-adcs-named-adc1-and-adc2) * [2. ESP32 ADCs functions](#2-esp32-adcs-functions) @@ -44,6 +60,9 @@ * [ 3. Example of Default Credentials](#3-example-of-default-credentials) * [ 4. How to add dynamic parameters from sketch](#4-how-to-add-dynamic-parameters-from-sketch) * [ 5. If you don't need to add dynamic parameters](#5-if-you-dont-need-to-add-dynamic-parameters) + * [ 6. To use custom HTML Style](#6-to-use-custom-html-style) + * [ 7. To use custom Head Elements](#7-to-use-custom-head-elements) + * [ 8. To use CORS Header](#8-to-use-cors-header) * [Important Notes for using Dynamic Parameters' ids](#important-notes-for-using-dynamic-parameters-ids) * [Important Notes](#important-notes) * [Why using this Blynk_WiFiManager with MultiWiFi-MultiBlynk features](#why-using-this-blynk_wifimanager-with-multiwifi-multiblynk-features) @@ -154,7 +173,9 @@ With version `v1.0.5` or later, you can configure: This [**BlynkESP32_BT_WF** library](https://github.com/khoih-prog/BlynkESP32_BT_WF) currently supports these following boards: - 1. **ESP8266 and ESP32-based boards using EEPROM, SPIFFS or LittleFS**. + 1. **ESP32 using EEPROM, SPIFFS or LittleFS**. + 2. **ESP32-S2 (ESP32-S2 Saola, AI-Thinker ESP-12K, etc.) using EEPROM** + 3. **ESP8266 using EEPROM, SPIFFS or LittleFS**. --- @@ -162,6 +183,14 @@ This [**BlynkESP32_BT_WF** library](https://github.com/khoih-prog/BlynkESP32_BT_ ## Changelog +### Major Releases v1.2.0 + +1. Configurable **Customs HTML Headers**, including Customs Style, Customs Head Elements, CORS Header. +2. Add support to **ESP32-S2 (ESP32-S2 Saola and AI-Thinker ESP-12K)**. Currently using EEPROM only. To add support to LittleFS and SPIFFS in future releases. +3. Fix Config Portal Bug. +4. Tested with [**Latest ESP32 Core 1.0.5**](https://github.com/espressif/arduino-esp32) for ESP32-based boards. +5. Update examples + ### Releases v1.1.3 1. To permit autoreset after configurable timeout if DRD/MRD or non-persistent forced-CP. Check [**Good new feature: Blynk.resetAndEnterConfigPortal() Thanks & question #27**](https://github.com/khoih-prog/Blynk_WM/issues/27) @@ -234,12 +263,13 @@ Thanks to [Thor Johnson](https://github.com/thorathome) to test, suggest and enc ## Prerequisites 1. [`Arduino IDE 1.8.13+`](https://www.arduino.cc/en/Main/Software) -2. [`Blynk library 0.6.1+`](https://github.com/blynkkk/blynk-library/releases) -3. [`ESP32 core 1.0.4+`](https://github.com/espressif/arduino-esp32/releases) for ESP32 boards -4. [`ESP8266 core 2.7.4+`](https://github.com/esp8266/Arduino#installing-with-boards-manager) for ESP8266 boards. To use ESP8266 core 2.7.1+ for LittleFS. -5. [`ESP_DoubleResetDetector library 1.1.1+`](https://github.com/khoih-prog/ESP_DoubleResetDetector) to use DRD feature. To install, check [![arduino-library-badge](https://www.ardu-badge.com/badge/ESP_DoubleResetDetector.svg?)](https://www.ardu-badge.com/ESP_DoubleResetDetector). -6. [`ESP_MultiResetDetector library 1.1.1+`](https://github.com/khoih-prog/ESP_MultiResetDetector) to use MRD feature. To install, check [![arduino-library-badge](https://www.ardu-badge.com/badge/ESP_MultiResetDetector.svg?)](https://www.ardu-badge.com/ESP_MultiResetDetector). -7. [`LittleFS_esp32 v1.0.5+`](https://github.com/lorol/LITTLEFS) to use ESP32 LittleFS. +2. [`Blynk library 0.6.1+`](https://github.com/blynkkk/blynk-library/releases). [![Latest release](https://img.shields.io/github/release/blynkkk/blynk-library.svg)](https://github.com/blynkkk/blynk-library/releases/latest/) +3. [`ESP32 Core 1.0.5+`](https://github.com/espressif/arduino-esp32) for ESP32-based boards. [![Latest release](https://img.shields.io/github/release/espressif/arduino-esp32.svg)](https://github.com/espressif/arduino-esp32/releases/latest/) +4. [`ESP32S2 Core 1.0.5+`](https://github.com/espressif/arduino-esp32/tree/esp32s2) for ESP32S2-based boards. +5. [`ESP8266 Core 2.7.4+`](https://github.com/esp8266/Arduino) for ESP8266-based boards. [![Latest release](https://img.shields.io/github/release/esp8266/Arduino.svg)](https://github.com/esp8266/Arduino/releases/latest/). To use ESP8266 core 2.7.1+ for LittleFS. +6. [`ESP_DoubleResetDetector library 1.1.1+`](https://github.com/khoih-prog/ESP_DoubleResetDetector) to use DRD feature. To install, check [![arduino-library-badge](https://www.ardu-badge.com/badge/ESP_DoubleResetDetector.svg?)](https://www.ardu-badge.com/ESP_DoubleResetDetector). +7. [`ESP_MultiResetDetector library 1.1.1+`](https://github.com/khoih-prog/ESP_MultiResetDetector) to use MRD feature. To install, check [![arduino-library-badge](https://www.ardu-badge.com/badge/ESP_MultiResetDetector.svg?)](https://www.ardu-badge.com/ESP_MultiResetDetector). +8. [`LittleFS_esp32 v1.0.5+`](https://github.com/lorol/LITTLEFS) for ESP32-based boards using LittleFS. To install, check [![arduino-library-badge](https://www.ardu-badge.com/badge/LittleFS_esp32.svg?)](https://www.ardu-badge.com/LittleFS_esp32). --- @@ -288,6 +318,136 @@ Thanks to [Thor Johnson](https://github.com/thorathome) to test, suggest and enc --- --- +## HOWTO Install esp32-s2 core for ESP32-S2 (Saola, AI-Thinker ESP-12K) boards into Arduino IDE + + +These are instructions demonstrating the steps to install esp32-s2 core on Ubuntu machines. For Windows or other OS'es, just follow the the similar principles and steps. + +Assuming you already installed Arduino IDE ESP32 core and the installed directory is + +`/home/your_account/.arduino15/packages/esp32` + + +### 1. Save the original esp32 core + +First, copy the whole original esp32 core to another safe place. Then delete all the sub-directories of + +`/home/your_account/.arduino15/packages/esp32/hardware/esp32/1.0.4` + +--- + +### 2. Download esp32-s2 core + +#### 2.1 Download zip + +Download [**esp32-s2 core**](https://github.com/espressif/arduino-esp32/tree/esp32s2) in the `zip` format: + +`arduino-esp32-esp32s2.zip` + +#### 2.2 Unzip + +

+ +

+ +#### 2.3 Update esp32-s2 core directories + +Copy all subdirectories of esp32-s2 core into `/home/your_account/.arduino15/packages/esp32/hardware/esp32/1.0.4` + +--- + +### 3 Download tools + + +#### 3.1 Download Toolchain for Xtensa (ESP32-S2) based on GCC + +Download [**esp32-s2 Toolchain**](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-guides/tools/idf-tools.html#xtensa-esp32s2-elf) corresponding to your environment (linux-amd64, win64, etc.). + +For example `xtensa-esp32s2-elf-gcc8_4_0-esp-2020r3-linux-amd64.tar.gz`, then un-archive. + + +

+ +

+ +#### 3.2 Download esptool + + +Download [esptool](https://github.com/espressif/esptool/releases) int the `zip` format: + +`esptool-3.0.zip` + +#### 3.3 Unzip + +

+ +

+ +--- + +### 4. Update tools + +#### 4.1 Update Toolchain + +Copy whole `xtensa-esp32s2-elf` directory into `/home/your_account/.arduino15/packages/esp32/hardware/esp32/1.0.4/tools` + + +#### 4.2 Update esptool + +Rename `esptool-3.0` directory to `esptool` + + +Copy whole `esptool` directory into `/home/your_account/.arduino15/packages/esp32/hardware/esp32/1.0.4/tools` + + +

+ +

+ + +### 5. esp32-s2 WebServer Library Patch + +If you haven't installed a new version with [WebServer.handleClient delay PR #4350](https://github.com/espressif/arduino-esp32/pull/4350) or haven't applied the above mentioned PR, you have to use the following patch. + + +**To be able to run Config Portal on ESP32-S2 boards**, you have to copy the files in [esp32-s2 WebServer Patch](esp32s2_WebServer_Patch/) directory into esp32-s2 WebServer library directory (~/.arduino15/packages/esp32/hardware/esp32/1.0.4/libraries/WebServer). + +Supposing the esp32-s2 version is 1.0.4, these files `WebServer.h/cpp` must be copied into the directory to replace: + +- `~/.arduino15/packages/esp32/hardware/esp32/1.0.4/libraries/WebServer/src/WebServer.h` +- `~/.arduino15/packages/esp32/hardware/esp32/1.0.4/libraries/WebServer/src/WebServer.cpp` + + +--- + +That's it. You're now ready to compile and test for ESP32-S2 now + +--- +--- + +### Note for Platform IO using ESP32 LittleFS + +In Platform IO, to fix the error when using [`LittleFS_esp32 v1.0`](https://github.com/lorol/LITTLEFS) for ESP32-based boards with ESP32 core v1.0.4- (ESP-IDF v3.2-), uncomment the following line + +from + +``` +//#define CONFIG_LITTLEFS_FOR_IDF_3_2 /* For old IDF - like in release 1.0.4 */ +``` + +to + +``` +#define CONFIG_LITTLEFS_FOR_IDF_3_2 /* For old IDF - like in release 1.0.4 */ +``` + +It's advisable to use the latest [`LittleFS_esp32 v1.0.5+`](https://github.com/lorol/LITTLEFS) to avoid the issue. + +Thanks to [Roshan](https://github.com/solroshan) to report the issue in [Error esp_littlefs.c 'utime_p'](https://github.com/khoih-prog/ESPAsync_WiFiManager/issues/28) + +--- +--- + ### HOWTO Use analogRead() with ESP32 running WiFi and/or BlueTooth (BT/BLE) Please have a look at [**ESP_WiFiManager Issue 39: Not able to read analog port when using the autoconnect example**](https://github.com/khoih-prog/ESP_WiFiManager/issues/39) to have more detailed description and solution of the issue. @@ -607,6 +767,31 @@ uint16_t NUM_MENU_ITEMS = 0; ``` +#### 6. To use custom HTML Style + +``` +const char NewCustomsStyle[] /*PROGMEM*/ = ""; + +... + +Blynk.setCustomsStyle(NewCustomsStyle); +``` + +#### 7. To use custom Head Elements + + +``` +Blynk.setCustomsHeadElement(""); +``` + +#### 8. To use CORS Header + +``` +Blynk.setCORSHeader("Your Access-Control-Allow-Origin"); +``` + + --- --- @@ -761,7 +946,7 @@ BLYNK_WRITE(BLYNK_PIN_FORCED_CONFIG) { if (param.asInt()) { - Serial.println( F("CP Button Hit. Rebooting") ); + Serial.println( F("\nCP Button Hit. Rebooting") ); // This will keep CP once, clear after reset, even you didn't enter CP at all. Blynk.resetAndEnterConfigPortal(); @@ -776,7 +961,7 @@ BLYNK_WRITE(BLYNK_PIN_FORCED_PERS_CONFIG) { if (param.asInt()) { - Serial.println( F("Persistent CP Button Hit. Rebooting") ); + Serial.println( F("\nPersistent CP Button Hit. Rebooting") ); // This will keep CP forever, until you successfully enter CP, and Save data to clear the flag. Blynk.resetAndEnterConfigPortalPersistent(); @@ -853,6 +1038,11 @@ void check_status() } } +#if USING_CUSTOMS_STYLE +const char NewCustomsStyle[] /*PROGMEM*/ = ""; +#endif + void setup() { pinMode(LED_BUILTIN, OUTPUT); @@ -902,6 +1092,22 @@ void setup() //Blynk.setSTAStaticIPConfig(IPAddress(192, 168, 2, 220), IPAddress(192, 168, 2, 1), IPAddress(255, 255, 255, 0), // IPAddress(4, 4, 4, 4), IPAddress(8, 8, 8, 8)); +////////////////////////////////////////////// + +#if USING_CUSTOMS_STYLE + Blynk.setCustomsStyle(NewCustomsStyle); +#endif + +#if USING_CUSTOMS_HEAD_ELEMENT + Blynk.setCustomsHeadElement(""); +#endif + +#if USING_CORS_FEATURE + Blynk.setCORSHeader("Your Access-Control-Allow-Origin"); +#endif + + ////////////////////////////////////////////// + // Use this to default DHCP hostname to ESP8266-XXXXXX or ESP32-XXXXXX //Blynk.begin(); // Use this to personalize DHCP hostname (RFC952 conformed) @@ -931,25 +1137,21 @@ void displayCredentials() { Serial.println(F("\nYour stored Credentials :")); - for (int i = 0; i < NUM_MENU_ITEMS; i++) + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) { - Serial.println(String(myMenuItems[i].displayName) + " = " + myMenuItems[i].pdata); + Serial.print(myMenuItems[i].displayName); + Serial.print(F(" = ")); + Serial.println(myMenuItems[i].pdata); } } -#endif -void loop() +void displayCredentialsInLoop() { - Blynk.run(); - timer.run(); - check_status(); - -#if USE_DYNAMIC_PARAMETERS static bool displayedCredentials = false; if (!displayedCredentials) { - for (int i = 0; i < NUM_MENU_ITEMS; i++) + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) { if (!strlen(myMenuItems[i].pdata)) { @@ -963,6 +1165,18 @@ void loop() } } } +} + +#endif + +void loop() +{ + Blynk.run(); + timer.run(); + check_status(); + +#if USE_DYNAMIC_PARAMETERS + displayCredentialsInLoop(); #endif } ``` @@ -1021,8 +1235,15 @@ void loop() // (USE_LITTLEFS == false) and (USE_SPIFFS == true) => using SPIFFS for configuration data in WiFiManager // Those above #define's must be placed before #include -#define USE_LITTLEFS true -#define USE_SPIFFS false +#if ( ARDUINO_ESP32S2_DEV || ARDUINO_FEATHERS2 || ARDUINO_PROS2 || ARDUINO_MICROS2 ) + // Currently, ESP32-S2 only supporting EEPROM. Will fix to support LittleFS and SPIFFS + #define USE_LITTLEFS false + #define USE_SPIFFS false + #warning ESP32-S2 only support supporting EEPROM now. +#else + #define USE_LITTLEFS true + #define USE_SPIFFS false +#endif #if !( USE_SPIFFS || USE_LITTLEFS) // EEPROM_SIZE must be <= 2048 and >= CONFIG_DATA_SIZE (currently 172 bytes) @@ -1031,22 +1252,35 @@ void loop() #define EEPROM_START 0 #endif +///////////////////////////////////////////// + +// Add customs headers from v1.2.0 +#define USING_CUSTOMS_STYLE true +#define USING_CUSTOMS_HEAD_ELEMENT true +#define USING_CORS_FEATURE true + +///////////////////////////////////////////// + // Force some params in Blynk, only valid for library version 1.0.1 and later #define TIMEOUT_RECONNECT_WIFI 10000L #define RESET_IF_CONFIG_TIMEOUT true #define CONFIG_TIMEOUT_RETRYTIMES_BEFORE_RESET 5 +// Config Timeout 120s (default 60s) +#define CONFIG_TIMEOUT 120000L + #define USE_DYNAMIC_PARAMETERS true -// Those above #define's must be placed before #include +// Those above #define's must be placed before #include and +////////////////////////////////////////// -//#define USE_SSL true -#define USE_SSL false +#define USE_SSL true +//#define USE_SSL false #if USE_SSL -#include + #include #else -#include + #include #endif #define PIN_D22 22 // Pin D22 mapped to pin GPIO22/SCL of ESP32 @@ -1150,7 +1384,7 @@ Blynk_WM_Configuration defaultConfig = #ifndef dynamicParams_h #define dynamicParams_h -// USE_DYNAMIC_PARAMETERS defined in defines.h +// USE_DYNAMIC_PARAMETERS defined in defined.h /////////////// Start dynamic Credentials /////////////// @@ -1228,7 +1462,7 @@ The following is the sample terminal output when running example [ESP8266WM_MRD_ ``` Starting ESP8266WM_MRD_Config using LittleFS with SSL on ESP8266_NODEMCU -Blynk_WM SSL for ESP8266 v1.1.3 +Blynk_WM SSL for ESP8266 v1.2.0 ESP_MultiResetDetector v1.1.1 LittleFS Flag read = 0xFFFD0002 multiResetDetectorFlag = 0xFFFD0002 @@ -1308,7 +1542,7 @@ BBBBBB ``` Starting ESP8266WM_MRD_Config using LittleFS with SSL on ESP8266_NODEMCU -Blynk_WM SSL for ESP8266 v1.1.3 +Blynk_WM SSL for ESP8266 v1.2.0 ESP_MultiResetDetector v1.1.1 LittleFS Flag read = 0xFFFC0003 multiResetDetectorFlag = 0xFFFC0003 @@ -1370,7 +1604,7 @@ The following is the sample terminal output when running example [DHT11ESP8266_S ``` Starting DHT11ESP8266_SSL using LittleFS with SSL on ESP8266_NODEMCU -Blynk_WM SSL for ESP8266 v1.1.3 +Blynk_WM SSL for ESP8266 v1.2.0 ESP_DoubleResetDetector v1.1.1 [293] Hostname=ESP8266-DHT11-SSL [316] LoadCfgFile @@ -1428,7 +1662,7 @@ The following is the sample terminal output when running example [ESP32WM_MRD_Co ``` Starting ESP32WM_MRD_Config using LITTLEFS without SSL on ESP32_DEV -Blynk_WM for ESP32 v1.1.3 +Blynk_WM for ESP32 v1.2.0 ESP_MultiResetDetector v1.1.1 LittleFS Flag read = 0xFFFE0001 multiResetDetectorFlag = 0xFFFE0001 @@ -1505,7 +1739,7 @@ BBBBBB ``` Starting ESP32WM_MRD_Config using LITTLEFS without SSL on ESP32_DEV -Blynk_WM for ESP32 v1.1.3 +Blynk_WM for ESP32 v1.2.0 ESP_MultiResetDetector v1.1.1 LittleFS Flag read = 0xFFFC0003 multiResetDetectorFlag = 0xFFFC0003 @@ -1563,7 +1797,7 @@ ets Jun 8 2016 00:22:57 ``` Starting ESP32WM_MRD_Config using LITTLEFS without SSL on ESP32_DEV -Blynk_WM for ESP32 v1.1.3 +Blynk_WM for ESP32 v1.2.0 ESP_MultiResetDetector v1.1.1 LittleFS Flag read = 0xFFFE0001 multiResetDetectorFlag = 0xFFFE0001 @@ -1687,7 +1921,7 @@ The following is the sample terminal output when running example [DHT11ESP8266_S ``` Starting DHT11ESP32_SSL using LITTLEFS with SSL on ESP32_DEV -Blynk_WM SSL for ESP32 v1.1.3 +Blynk_WM SSL for ESP32 v1.2.0 ESP_DoubleResetDetector v1.1.1 [346] Hostname=ESP32-DHT11-SSL [385] LoadCfgFile @@ -1751,7 +1985,7 @@ Blynk.resetAndEnterConfigPortal(); ``` Starting ESP8266WM_MRD_ForcedConfig using LittleFS without SSL on ESP8266_NODEMCU -Blynk_WM for ESP8266 v1.1.3 +Blynk_WM for ESP8266 v1.2.0 ESP_MultiResetDetector v1.1.1 LittleFS Flag read = 0xFFFE0001 multiResetDetectorFlag = 0xFFFE0001 @@ -1837,7 +2071,7 @@ Non-Persistent CP will be removed after first reset, even you didn't enter the C ``` Starting ESP8266WM_MRD_ForcedConfig using LittleFS without SSL on ESP8266_NODEMCU -Blynk_WM for ESP8266 v1.1.3 +Blynk_WM for ESP8266 v1.2.0 ESP_MultiResetDetector v1.1.1 LittleFS Flag read = 0xFFFE0001 multiResetDetectorFlag = 0xFFFE0001 @@ -1919,7 +2153,7 @@ Blynk.resetAndEnterConfigPortalPersistent(); ``` Starting ESP8266WM_MRD_ForcedConfig using LittleFS without SSL on ESP8266_NODEMCU -Blynk_WM for ESP8266 v1.1.3 +Blynk_WM for ESP8266 v1.2.0 ESP_MultiResetDetector v1.1.1 LittleFS Flag read = 0xFFFE0001 multiResetDetectorFlag = 0xFFFE0001 @@ -2006,7 +2240,7 @@ Persistent CP will remain after resets. The only way to get rid of Config Portal ``` Starting ESP8266WM_MRD_ForcedConfig using LittleFS without SSL on ESP8266_NODEMCU -Blynk_WM for ESP8266 v1.1.3 +Blynk_WM for ESP8266 v1.2.0 ESP_MultiResetDetector v1.1.1 LittleFS Flag read = 0xFFFE0001 multiResetDetectorFlag = 0xFFFE0001 @@ -2104,6 +2338,14 @@ Sometimes, the library will only work if you update the board core to the latest ## Releases +### Major Releases v1.2.0 + +1. Configurable **Customs HTML Headers**, including Customs Style, Customs Head Elements, CORS Header. +2. Add support to **ESP32-S2 (ESP32-S2 Saola and AI-Thinker ESP-12K)**. Currently using EEPROM only. To add support to LittleFS and SPIFFS in future releases. +3. Fix Config Portal Bug. +4. Tested with [**Latest ESP32 Core 1.0.5**](https://github.com/espressif/arduino-esp32) for ESP32-based boards. +5. Update examples + ### Releases v1.1.3 1. To permit autoreset after configurable timeout if DRD/MRD or non-persistent forced-CP. Check [**Good new feature: Blynk.resetAndEnterConfigPortal() Thanks & question #27**](https://github.com/khoih-prog/Blynk_WM/issues/27) @@ -2251,6 +2493,7 @@ Submit issues to: [Blynk_WM issues](https://github.com/khoih-prog/Blynk_WM/issue ## TO DO 1. Fix bug. Add enhancement +2. Add support to **ESP32-S2 (ESP32-S2 Saola, AI-Thinker ESP-12K, etc.) using LittleFS and SPIFFS** ## DONE @@ -2281,6 +2524,8 @@ Submit issues to: [Blynk_WM issues](https://github.com/khoih-prog/Blynk_WM/issue 24. Add Table of Contents 25. Add Version String 26. Add functions to control Config Portal from software or Virtual Switches. +27. Add support to **ESP32-S2 (ESP32-S2 Saola, AI-Thinker ESP-12K, etc.) using EEPROM** +28. Configurable **Customs HTML Headers**, including Customs Style, Customs Head Elements, CORS Header --- --- diff --git a/examples/AM2315_ESP32_SSL/AM2315_ESP32_SSL.ino b/examples/AM2315_ESP32_SSL/AM2315_ESP32_SSL.ino index a990225..6da6c12 100644 --- a/examples/AM2315_ESP32_SSL/AM2315_ESP32_SSL.ino +++ b/examples/AM2315_ESP32_SSL/AM2315_ESP32_SSL.ino @@ -7,7 +7,7 @@ Forked from Blynk library v0.6.1 https://github.com/blynkkk/blynk-library/releases Built by Khoi Hoang https://github.com/khoih-prog/Blynk_WM Licensed under MIT license - Version: 1.1.3 + Version: 1.2.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -32,6 +32,7 @@ 1.1.1 K Hoang 16/01/2021 Add functions to control Config Portal from software or Virtual Switches 1.1.2 K Hoang 28/01/2021 Fix Config Portal and Dynamic Params bugs 1.1.3 K Hoang 31/01/2021 To permit autoreset after timeout if DRD/MRD or non-persistent forced-CP + 1.2.0 K Hoang 24/02/2021 Add customs HTML header feature and support to ESP32-S2. *****************************************************************************************************************************/ #include "defines.h" @@ -126,6 +127,11 @@ void check_status() } } +#if USING_CUSTOMS_STYLE +const char NewCustomsStyle[] /*PROGMEM*/ = ""; +#endif + void setup() { Serial.begin(115200); @@ -178,6 +184,22 @@ void setup() //Blynk.setSTAStaticIPConfig(IPAddress(192, 168, 2, 220), IPAddress(192, 168, 2, 1), IPAddress(255, 255, 255, 0), // IPAddress(4, 4, 4, 4), IPAddress(8, 8, 8, 8)); +////////////////////////////////////////////// + +#if USING_CUSTOMS_STYLE + Blynk.setCustomsStyle(NewCustomsStyle); +#endif + +#if USING_CUSTOMS_HEAD_ELEMENT + Blynk.setCustomsHeadElement(""); +#endif + +#if USING_CORS_FEATURE + Blynk.setCORSHeader("Your Access-Control-Allow-Origin"); +#endif + + ////////////////////////////////////////////// + // Use this to default DHCP hostname to ESP8266-XXXXXX or ESP32-XXXXXX //Blynk.begin(); // Use this to personalize DHCP hostname (RFC952 conformed) @@ -220,25 +242,21 @@ void displayCredentials() { Serial.println(F("\nYour stored Credentials :")); - for (int i = 0; i < NUM_MENU_ITEMS; i++) + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) { - Serial.println(String(myMenuItems[i].displayName) + " = " + myMenuItems[i].pdata); + Serial.print(myMenuItems[i].displayName); + Serial.print(F(" = ")); + Serial.println(myMenuItems[i].pdata); } } -#endif -void loop() +void displayCredentialsInLoop() { - Blynk.run(); - timer.run(); - check_status(); - -#if (USE_BLYNK_WM && USE_DYNAMIC_PARAMETERS) static bool displayedCredentials = false; if (!displayedCredentials) { - for (int i = 0; i < NUM_MENU_ITEMS; i++) + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) { if (!strlen(myMenuItems[i].pdata)) { @@ -252,5 +270,17 @@ void loop() } } } +} + +#endif + +void loop() +{ + Blynk.run(); + timer.run(); + check_status(); + +#if (USE_BLYNK_WM && USE_DYNAMIC_PARAMETERS) + displayCredentialsInLoop(); #endif } diff --git a/examples/AM2315_ESP32_SSL/defines.h b/examples/AM2315_ESP32_SSL/defines.h index 727ac92..3587522 100644 --- a/examples/AM2315_ESP32_SSL/defines.h +++ b/examples/AM2315_ESP32_SSL/defines.h @@ -28,8 +28,15 @@ // (USE_LITTLEFS == false) and (USE_SPIFFS == true) => using SPIFFS for configuration data in WiFiManager // Those above #define's must be placed before #include -#define USE_LITTLEFS true -#define USE_SPIFFS false +#if ( ARDUINO_ESP32S2_DEV || ARDUINO_FEATHERS2 || ARDUINO_PROS2 || ARDUINO_MICROS2 ) + // Currently, ESP32-S2 only supporting EEPROM. Will fix to support LittleFS and SPIFFS + #define USE_LITTLEFS false + #define USE_SPIFFS false + #warning ESP32-S2 only support supporting EEPROM now. +#else + #define USE_LITTLEFS true + #define USE_SPIFFS false +#endif #if !( USE_SPIFFS || USE_LITTLEFS) @@ -39,24 +46,36 @@ #define EEPROM_START 0 #endif -// Force some params in Blynk, only valid for library version 1.0.1 and later -#define TIMEOUT_RECONNECT_WIFI 10000L -#define RESET_IF_CONFIG_TIMEOUT true -#define CONFIG_TIMEOUT_RETRYTIMES_BEFORE_RESET 5 - -#define USE_DYNAMIC_PARAMETERS true -// Those above #define's must be placed before #include - //You have to download Blynk WiFiManager Blynk_WM library at //https://github.com/khoih-prog/Blynk_WM // In order to enable (USE_BLYNK_WM = true). Otherwise, use (USE_BLYNK_WM = false) #define USE_BLYNK_WM true -//#define USE_BLYNK_WM false - -//#define USE_SSL true #define USE_SSL false #if USE_BLYNK_WM + + ///////////////////////////////////////////// + + // Add customs headers from v1.2.0 + #define USING_CUSTOMS_STYLE true + #define USING_CUSTOMS_HEAD_ELEMENT true + #define USING_CORS_FEATURE true + + ///////////////////////////////////////////// + + // Force some params in Blynk, only valid for library version 1.0.1 and later + #define TIMEOUT_RECONNECT_WIFI 10000L + #define RESET_IF_CONFIG_TIMEOUT true + + #define CONFIG_TIMEOUT_RETRYTIMES_BEFORE_RESET 5 + + // Config Timeout 120s (default 60s) + #define CONFIG_TIMEOUT 120000L + + #define USE_DYNAMIC_PARAMETERS true + // Those above #define's must be placed before #include and + ////////////////////////////////////////// + #if USE_SSL #include //https://github.com/khoih-prog/Blynk_WM #else @@ -83,7 +102,6 @@ #endif #define USE_LOCAL_SERVER true - //#define USE_LOCAL_SERVER false // If local server #if USE_LOCAL_SERVER diff --git a/examples/AM2315_ESP8266/AM2315_ESP8266.ino b/examples/AM2315_ESP8266/AM2315_ESP8266.ino index f18f303..a6d6dcf 100644 --- a/examples/AM2315_ESP8266/AM2315_ESP8266.ino +++ b/examples/AM2315_ESP8266/AM2315_ESP8266.ino @@ -7,7 +7,7 @@ Forked from Blynk library v0.6.1 https://github.com/blynkkk/blynk-library/releases Built by Khoi Hoang https://github.com/khoih-prog/Blynk_WM Licensed under MIT license - Version: 1.1.3 + Version: 1.2.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -32,6 +32,7 @@ 1.1.1 K Hoang 16/01/2021 Add functions to control Config Portal from software or Virtual Switches 1.1.2 K Hoang 28/01/2021 Fix Config Portal and Dynamic Params bugs 1.1.3 K Hoang 31/01/2021 To permit autoreset after timeout if DRD/MRD or non-persistent forced-CP + 1.2.0 K Hoang 24/02/2021 Add customs HTML header feature and support to ESP32-S2. *****************************************************************************************************************************/ #include "defines.h" @@ -124,6 +125,11 @@ void check_status() } } +#if USING_CUSTOMS_STYLE +const char NewCustomsStyle[] /*PROGMEM*/ = ""; +#endif + void setup() { Serial.begin(115200); @@ -170,6 +176,22 @@ void setup() //Blynk.setSTAStaticIPConfig(IPAddress(192, 168, 2, 220), IPAddress(192, 168, 2, 1), IPAddress(255, 255, 255, 0), // IPAddress(4, 4, 4, 4), IPAddress(8, 8, 8, 8)); +////////////////////////////////////////////// + +#if USING_CUSTOMS_STYLE + Blynk.setCustomsStyle(NewCustomsStyle); +#endif + +#if USING_CUSTOMS_HEAD_ELEMENT + Blynk.setCustomsHeadElement(""); +#endif + +#if USING_CORS_FEATURE + Blynk.setCORSHeader("Your Access-Control-Allow-Origin"); +#endif + + ////////////////////////////////////////////// + // Use this to default DHCP hostname to ESP8266-XXXXXX or ESP32-XXXXXX //Blynk.begin(); // Use this to personalize DHCP hostname (RFC952 conformed) @@ -214,25 +236,21 @@ void displayCredentials() { Serial.println(F("\nYour stored Credentials :")); - for (int i = 0; i < NUM_MENU_ITEMS; i++) + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) { - Serial.println(String(myMenuItems[i].displayName) + " = " + myMenuItems[i].pdata); + Serial.print(myMenuItems[i].displayName); + Serial.print(F(" = ")); + Serial.println(myMenuItems[i].pdata); } } -#endif -void loop() +void displayCredentialsInLoop() { - Blynk.run(); - timer.run(); - check_status(); - -#if (USE_BLYNK_WM && USE_DYNAMIC_PARAMETERS) static bool displayedCredentials = false; if (!displayedCredentials) { - for (int i = 0; i < NUM_MENU_ITEMS; i++) + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) { if (!strlen(myMenuItems[i].pdata)) { @@ -246,5 +264,17 @@ void loop() } } } +} + +#endif + +void loop() +{ + Blynk.run(); + timer.run(); + check_status(); + +#if (USE_BLYNK_WM && USE_DYNAMIC_PARAMETERS) + displayCredentialsInLoop(); #endif } diff --git a/examples/AM2315_ESP8266/defines.h b/examples/AM2315_ESP8266/defines.h index d43171e..f48dd60 100644 --- a/examples/AM2315_ESP8266/defines.h +++ b/examples/AM2315_ESP8266/defines.h @@ -13,7 +13,7 @@ #define defines_h #ifndef ESP8266 -#error This code is intended to run on the ESP8266 platform! Please check your Tools->Board setting. + #error This code is intended to run on the ESP8266 platform! Please check your Tools->Board setting. #endif #define BLYNK_PRINT Serial @@ -34,72 +34,90 @@ //#define USE_SPIFFS true #if USE_LITTLEFS -//LittleFS has higher priority -#define CurrentFileFS "LittleFS" -#ifdef USE_SPIFFS -#undef USE_SPIFFS -#endif -#define USE_SPIFFS false + //LittleFS has higher priority + #define CurrentFileFS "LittleFS" + #ifdef USE_SPIFFS + #undef USE_SPIFFS + #endif + #define USE_SPIFFS false #elif USE_SPIFFS -#define CurrentFileFS "SPIFFS" + #define CurrentFileFS "SPIFFS" #endif #if !( USE_LITTLEFS || USE_SPIFFS) -// EEPROM_SIZE must be <= 4096 and >= CONFIG_DATA_SIZE (currently 172 bytes) -#define EEPROM_SIZE (4 * 1024) -// EEPROM_START + CONFIG_DATA_SIZE must be <= EEPROM_SIZE -#define EEPROM_START 768 + // EEPROM_SIZE must be <= 4096 and >= CONFIG_DATA_SIZE (currently 172 bytes) + #define EEPROM_SIZE (4 * 1024) + // EEPROM_START + CONFIG_DATA_SIZE must be <= EEPROM_SIZE + #define EEPROM_START 768 #endif //You have to download Blynk WiFiManager Blynk_WM library at //https://github.com/khoih-prog/Blynk_WM // In order to enable (USE_BLYNK_WM = true). Otherwise, use (USE_BLYNK_WM = false) #define USE_BLYNK_WM true -//#define USE_BLYNK_WM false #define USE_SSL false #if USE_BLYNK_WM -#define USE_DYNAMIC_PARAMETERS true + ///////////////////////////////////////////// + + // Add customs headers from v1.2.0 + #define USING_CUSTOMS_STYLE true + #define USING_CUSTOMS_HEAD_ELEMENT true + #define USING_CORS_FEATURE true + + ///////////////////////////////////////////// + + // Force some params in Blynk, only valid for library version 1.0.1 and later + #define TIMEOUT_RECONNECT_WIFI 10000L + #define RESET_IF_CONFIG_TIMEOUT true + + #define CONFIG_TIMEOUT_RETRYTIMES_BEFORE_RESET 5 + + // Config Timeout 120s (default 60s) + #define CONFIG_TIMEOUT 120000L + + #define USE_DYNAMIC_PARAMETERS true + // Those above #define's must be placed before #include and + ////////////////////////////////////////// + + #if USE_SSL + #include //https://github.com/khoih-prog/Blynk_WM + #else + #include //https://github.com/khoih-prog/Blynk_WM + #endif + + #include "Credentials.h" + #include "dynamicParams.h" -#if USE_SSL -#include //https://github.com/khoih-prog/Blynk_WM #else -#include //https://github.com/khoih-prog/Blynk_WM -#endif - -#include "Credentials.h" -#include "dynamicParams.h" -#else - -#if USE_SSL -#include -#define BLYNK_HARDWARE_PORT 9443 -#else -#include -#define BLYNK_HARDWARE_PORT 8080 -#endif + #if USE_SSL + #include + #define BLYNK_HARDWARE_PORT 9443 + #else + #include + #define BLYNK_HARDWARE_PORT 8080 + #endif #endif #if !USE_BLYNK_WM -#ifndef LED_BUILTIN -#define LED_BUILTIN 2 // Pin D2 mapped to pin GPIO2/ADC12 of ESP32, control on-board LED -#endif - -#define USE_LOCAL_SERVER true -//#define USE_LOCAL_SERVER false - -// If local server -#if USE_LOCAL_SERVER -char blynk_server[] = "yourname.duckdns.org"; -#endif - -char auth[] = "***"; -char ssid[] = "***"; -char pass[] = "***"; + #ifndef LED_BUILTIN + #define LED_BUILTIN 2 // Pin D2 mapped to pin GPIO2/ADC12 of ESP32, control on-board LED + #endif + + #define USE_LOCAL_SERVER true + + // If local server + #if USE_LOCAL_SERVER + char blynk_server[] = "yourname.duckdns.org"; + #endif + + char auth[] = "***"; + char ssid[] = "***"; + char pass[] = "***"; #endif diff --git a/examples/Blynk_WM_Template/Blynk_WM_Template.ino b/examples/Blynk_WM_Template/Blynk_WM_Template.ino index 0ca7071..aa72cd8 100644 --- a/examples/Blynk_WM_Template/Blynk_WM_Template.ino +++ b/examples/Blynk_WM_Template/Blynk_WM_Template.ino @@ -8,7 +8,7 @@ Forked from Blynk library v0.6.1 https://github.com/blynkkk/blynk-library/releases Built by Khoi Hoang https://github.com/khoih-prog/Blynk_WM Licensed under MIT license - Version: 1.1.3 + Version: 1.2.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -33,6 +33,7 @@ 1.1.1 K Hoang 16/01/2021 Add functions to control Config Portal from software or Virtual Switches 1.1.2 K Hoang 28/01/2021 Fix Config Portal and Dynamic Params bugs 1.1.3 K Hoang 31/01/2021 To permit autoreset after timeout if DRD/MRD or non-persistent forced-CP + 1.2.0 K Hoang 24/02/2021 Add customs HTML header feature and support to ESP32-S2. *****************************************************************************************************************************/ // Sketch uses Arduino IDE-selected ESP32 and ESP8266 to select compile choices @@ -175,8 +176,15 @@ // (USE_LITTLEFS == false) and (USE_SPIFFS == true) => using SPIFFS for configuration data in WiFiManager // Those above #define's must be placed before #include - #define USE_LITTLEFS true - #define USE_SPIFFS false + #if ( ARDUINO_ESP32S2_DEV || ARDUINO_FEATHERS2 || ARDUINO_PROS2 || ARDUINO_MICROS2 ) + // Currently, ESP32-S2 only supporting EEPROM. Will fix to support LittleFS and SPIFFS + #define USE_LITTLEFS false + #define USE_SPIFFS false + #warning ESP32-S2 only support supporting EEPROM now. + #else + #define USE_LITTLEFS true + #define USE_SPIFFS false + #endif #if USE_LITTLEFS #warning Using LittleFS for ESP32 diff --git a/examples/DHT11ESP32/DHT11ESP32.ino b/examples/DHT11ESP32/DHT11ESP32.ino index 1f7f598..2482aeb 100644 --- a/examples/DHT11ESP32/DHT11ESP32.ino +++ b/examples/DHT11ESP32/DHT11ESP32.ino @@ -7,7 +7,7 @@ Forked from Blynk library v0.6.1 https://github.com/blynkkk/blynk-library/releases Built by Khoi Hoang https://github.com/khoih-prog/Blynk_WM Licensed under MIT license - Version: 1.1.3 + Version: 1.2.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -32,6 +32,7 @@ 1.1.1 K Hoang 16/01/2021 Add functions to control Config Portal from software or Virtual Switches 1.1.2 K Hoang 28/01/2021 Fix Config Portal and Dynamic Params bugs 1.1.3 K Hoang 31/01/2021 To permit autoreset after timeout if DRD/MRD or non-persistent forced-CP + 1.2.0 K Hoang 24/02/2021 Add customs HTML header feature and support to ESP32-S2. *****************************************************************************************************************************/ #include "defines.h" @@ -107,6 +108,11 @@ void check_status() } } +#if USING_CUSTOMS_STYLE +const char NewCustomsStyle[] /*PROGMEM*/ = ""; +#endif + void setup() { pinMode(LED_BUILTIN, OUTPUT); @@ -155,6 +161,22 @@ void setup() //Blynk.setSTAStaticIPConfig(IPAddress(192, 168, 2, 220), IPAddress(192, 168, 2, 1), IPAddress(255, 255, 255, 0), // IPAddress(4, 4, 4, 4), IPAddress(8, 8, 8, 8)); +////////////////////////////////////////////// + +#if USING_CUSTOMS_STYLE + Blynk.setCustomsStyle(NewCustomsStyle); +#endif + +#if USING_CUSTOMS_HEAD_ELEMENT + Blynk.setCustomsHeadElement(""); +#endif + +#if USING_CORS_FEATURE + Blynk.setCORSHeader("Your Access-Control-Allow-Origin"); +#endif + + ////////////////////////////////////////////// + // Use this to default DHCP hostname to ESP8266-XXXXXX or ESP32-XXXXXX //Blynk.begin(); // Use this to personalize DHCP hostname (RFC952 conformed) @@ -197,25 +219,21 @@ void displayCredentials() { Serial.println(F("\nYour stored Credentials :")); - for (int i = 0; i < NUM_MENU_ITEMS; i++) + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) { - Serial.println(String(myMenuItems[i].displayName) + " = " + myMenuItems[i].pdata); + Serial.print(myMenuItems[i].displayName); + Serial.print(F(" = ")); + Serial.println(myMenuItems[i].pdata); } } -#endif -void loop() +void displayCredentialsInLoop() { - Blynk.run(); - timer.run(); - check_status(); - -#if (USE_BLYNK_WM && USE_DYNAMIC_PARAMETERS) static bool displayedCredentials = false; if (!displayedCredentials) { - for (int i = 0; i < NUM_MENU_ITEMS; i++) + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) { if (!strlen(myMenuItems[i].pdata)) { @@ -229,5 +247,17 @@ void loop() } } } -#endif +} + +#endif + +void loop() +{ + Blynk.run(); + timer.run(); + check_status(); + +#if (USE_BLYNK_WM && USE_DYNAMIC_PARAMETERS) + displayCredentialsInLoop(); +#endif } diff --git a/examples/DHT11ESP32/defines.h b/examples/DHT11ESP32/defines.h index 1dc467f..f8d0759 100644 --- a/examples/DHT11ESP32/defines.h +++ b/examples/DHT11ESP32/defines.h @@ -28,9 +28,15 @@ // (USE_LITTLEFS == false) and (USE_SPIFFS == true) => using SPIFFS for configuration data in WiFiManager // Those above #define's must be placed before #include -#define USE_LITTLEFS true -#define USE_SPIFFS false - +#if ( ARDUINO_ESP32S2_DEV || ARDUINO_FEATHERS2 || ARDUINO_PROS2 || ARDUINO_MICROS2 ) + // Currently, ESP32-S2 only supporting EEPROM. Will fix to support LittleFS and SPIFFS + #define USE_LITTLEFS false + #define USE_SPIFFS false + #warning ESP32-S2 only support supporting EEPROM now. +#else + #define USE_LITTLEFS true + #define USE_SPIFFS false +#endif #if !( USE_SPIFFS || USE_LITTLEFS) // EEPROM_SIZE must be <= 2048 and >= CONFIG_DATA_SIZE (currently 172 bytes) @@ -39,24 +45,35 @@ #define EEPROM_START 0 #endif -// Force some params in Blynk, only valid for library version 1.0.1 and later -#define TIMEOUT_RECONNECT_WIFI 10000L -#define RESET_IF_CONFIG_TIMEOUT true -#define CONFIG_TIMEOUT_RETRYTIMES_BEFORE_RESET 5 - -#define USE_DYNAMIC_PARAMETERS true -// Those above #define's must be placed before #include - //You have to download Blynk WiFiManager Blynk_WM library at //https://github.com/khoih-prog/Blynk_WM // In order to enable (USE_BLYNK_WM = true). Otherwise, use (USE_BLYNK_WM = false) #define USE_BLYNK_WM true -//#define USE_BLYNK_WM false - -//#define USE_SSL true #define USE_SSL false #if USE_BLYNK_WM + ///////////////////////////////////////////// + + // Add customs headers from v1.2.0 + #define USING_CUSTOMS_STYLE true + #define USING_CUSTOMS_HEAD_ELEMENT true + #define USING_CORS_FEATURE true + + ///////////////////////////////////////////// + + // Force some params in Blynk, only valid for library version 1.0.1 and later + #define TIMEOUT_RECONNECT_WIFI 10000L + #define RESET_IF_CONFIG_TIMEOUT true + + #define CONFIG_TIMEOUT_RETRYTIMES_BEFORE_RESET 5 + + // Config Timeout 120s (default 60s) + #define CONFIG_TIMEOUT 120000L + + #define USE_DYNAMIC_PARAMETERS true + // Those above #define's must be placed before #include and + ////////////////////////////////////////// + #if USE_SSL #include //https://github.com/khoih-prog/Blynk_WM #else diff --git a/examples/DHT11ESP32_SSL/DHT11ESP32_SSL.ino b/examples/DHT11ESP32_SSL/DHT11ESP32_SSL.ino index 2a902cb..83f6b2b 100644 --- a/examples/DHT11ESP32_SSL/DHT11ESP32_SSL.ino +++ b/examples/DHT11ESP32_SSL/DHT11ESP32_SSL.ino @@ -7,7 +7,7 @@ Forked from Blynk library v0.6.1 https://github.com/blynkkk/blynk-library/releases Built by Khoi Hoang https://github.com/khoih-prog/Blynk_WM Licensed under MIT license - Version: 1.1.3 + Version: 1.2.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -32,6 +32,7 @@ 1.1.1 K Hoang 16/01/2021 Add functions to control Config Portal from software or Virtual Switches 1.1.2 K Hoang 28/01/2021 Fix Config Portal and Dynamic Params bugs 1.1.3 K Hoang 31/01/2021 To permit autoreset after timeout if DRD/MRD or non-persistent forced-CP + 1.2.0 K Hoang 24/02/2021 Add customs HTML header feature and support to ESP32-S2. *****************************************************************************************************************************/ #include "defines.h" @@ -107,6 +108,11 @@ void check_status() } } +#if USING_CUSTOMS_STYLE +const char NewCustomsStyle[] /*PROGMEM*/ = ""; +#endif + void setup() { pinMode(LED_BUILTIN, OUTPUT); @@ -155,6 +161,22 @@ void setup() //Blynk.setSTAStaticIPConfig(IPAddress(192, 168, 2, 220), IPAddress(192, 168, 2, 1), IPAddress(255, 255, 255, 0), // IPAddress(4, 4, 4, 4), IPAddress(8, 8, 8, 8)); +////////////////////////////////////////////// + +#if USING_CUSTOMS_STYLE + Blynk.setCustomsStyle(NewCustomsStyle); +#endif + +#if USING_CUSTOMS_HEAD_ELEMENT + Blynk.setCustomsHeadElement(""); +#endif + +#if USING_CORS_FEATURE + Blynk.setCORSHeader("Your Access-Control-Allow-Origin"); +#endif + + ////////////////////////////////////////////// + // Use this to default DHCP hostname to ESP8266-XXXXXX or ESP32-XXXXXX //Blynk.begin(); // Use this to personalize DHCP hostname (RFC952 conformed) @@ -193,29 +215,25 @@ void setup() } #if (USE_BLYNK_WM && USE_DYNAMIC_PARAMETERS) -void displayCredentials(void) +void displayCredentials() { Serial.println(F("\nYour stored Credentials :")); - for (int i = 0; i < NUM_MENU_ITEMS; i++) + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) { - Serial.println(String(myMenuItems[i].displayName) + " = " + myMenuItems[i].pdata); + Serial.print(myMenuItems[i].displayName); + Serial.print(F(" = ")); + Serial.println(myMenuItems[i].pdata); } } -#endif -void loop() +void displayCredentialsInLoop() { - Blynk.run(); - timer.run(); - check_status(); - -#if (USE_BLYNK_WM && USE_DYNAMIC_PARAMETERS) static bool displayedCredentials = false; if (!displayedCredentials) { - for (int i = 0; i < NUM_MENU_ITEMS; i++) + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) { if (!strlen(myMenuItems[i].pdata)) { @@ -229,5 +247,17 @@ void loop() } } } -#endif +} + +#endif + +void loop() +{ + Blynk.run(); + timer.run(); + check_status(); + +#if (USE_BLYNK_WM && USE_DYNAMIC_PARAMETERS) + displayCredentialsInLoop(); +#endif } diff --git a/examples/DHT11ESP32_SSL/defines.h b/examples/DHT11ESP32_SSL/defines.h index 59825ba..bdb983e 100644 --- a/examples/DHT11ESP32_SSL/defines.h +++ b/examples/DHT11ESP32_SSL/defines.h @@ -28,9 +28,15 @@ // (USE_LITTLEFS == false) and (USE_SPIFFS == true) => using SPIFFS for configuration data in WiFiManager // Those above #define's must be placed before #include -#define USE_LITTLEFS true -#define USE_SPIFFS false - +#if ( ARDUINO_ESP32S2_DEV || ARDUINO_FEATHERS2 || ARDUINO_PROS2 || ARDUINO_MICROS2 ) + // Currently, ESP32-S2 only supporting EEPROM. Will fix to support LittleFS and SPIFFS + #define USE_LITTLEFS false + #define USE_SPIFFS false + #warning ESP32-S2 only support supporting EEPROM now. +#else + #define USE_LITTLEFS true + #define USE_SPIFFS false +#endif #if !( USE_SPIFFS || USE_LITTLEFS) // EEPROM_SIZE must be <= 2048 and >= CONFIG_DATA_SIZE (currently 172 bytes) @@ -38,24 +44,36 @@ // EEPROM_START + CONFIG_DATA_SIZE must be <= EEPROM_SIZE #define EEPROM_START 0 #endif -// Force some params in Blynk, only valid for library version 1.0.1 and later -#define TIMEOUT_RECONNECT_WIFI 10000L -#define RESET_IF_CONFIG_TIMEOUT true -#define CONFIG_TIMEOUT_RETRYTIMES_BEFORE_RESET 5 - -#define USE_DYNAMIC_PARAMETERS true -// Those above #define's must be placed before #include //You have to download Blynk WiFiManager Blynk_WM library at //https://github.com/khoih-prog/Blynk_WM // In order to enable (USE_BLYNK_WM = true). Otherwise, use (USE_BLYNK_WM = false) #define USE_BLYNK_WM true -//#define USE_BLYNK_WM false - #define USE_SSL true -//#define USE_SSL false #if USE_BLYNK_WM + ///////////////////////////////////////////// + + // Add customs headers from v1.2.0 + #define USING_CUSTOMS_STYLE true + #define USING_CUSTOMS_HEAD_ELEMENT true + #define USING_CORS_FEATURE true + + ///////////////////////////////////////////// + + // Force some params in Blynk, only valid for library version 1.0.1 and later + #define TIMEOUT_RECONNECT_WIFI 10000L + #define RESET_IF_CONFIG_TIMEOUT true + + #define CONFIG_TIMEOUT_RETRYTIMES_BEFORE_RESET 5 + + // Config Timeout 120s (default 60s) + #define CONFIG_TIMEOUT 120000L + + #define USE_DYNAMIC_PARAMETERS true + // Those above #define's must be placed before #include and + ////////////////////////////////////////// + #if USE_SSL #include //https://github.com/khoih-prog/Blynk_WM #else @@ -82,7 +100,6 @@ #endif #define USE_LOCAL_SERVER true - //#define USE_LOCAL_SERVER false // If local server #if USE_LOCAL_SERVER diff --git a/examples/DHT11ESP8266/DHT11ESP8266.ino b/examples/DHT11ESP8266/DHT11ESP8266.ino index 282d176..16ac6a7 100644 --- a/examples/DHT11ESP8266/DHT11ESP8266.ino +++ b/examples/DHT11ESP8266/DHT11ESP8266.ino @@ -7,7 +7,7 @@ Forked from Blynk library v0.6.1 https://github.com/blynkkk/blynk-library/releases Built by Khoi Hoang https://github.com/khoih-prog/Blynk_WM Licensed under MIT license - Version: 1.1.3 + Version: 1.2.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -32,6 +32,7 @@ 1.1.1 K Hoang 16/01/2021 Add functions to control Config Portal from software or Virtual Switches 1.1.2 K Hoang 28/01/2021 Fix Config Portal and Dynamic Params bugs 1.1.3 K Hoang 31/01/2021 To permit autoreset after timeout if DRD/MRD or non-persistent forced-CP + 1.2.0 K Hoang 24/02/2021 Add customs HTML header feature and support to ESP32-S2. *****************************************************************************************************************************/ #include "defines.h" @@ -107,6 +108,11 @@ void check_status() } } +#if USING_CUSTOMS_STYLE +const char NewCustomsStyle[] /*PROGMEM*/ = ""; +#endif + void setup() { pinMode(PIN_LED, OUTPUT); @@ -155,6 +161,22 @@ void setup() //Blynk.setSTAStaticIPConfig(IPAddress(192, 168, 2, 220), IPAddress(192, 168, 2, 1), IPAddress(255, 255, 255, 0), // IPAddress(4, 4, 4, 4), IPAddress(8, 8, 8, 8)); +////////////////////////////////////////////// + +#if USING_CUSTOMS_STYLE + Blynk.setCustomsStyle(NewCustomsStyle); +#endif + +#if USING_CUSTOMS_HEAD_ELEMENT + Blynk.setCustomsHeadElement(""); +#endif + +#if USING_CORS_FEATURE + Blynk.setCORSHeader("Your Access-Control-Allow-Origin"); +#endif + + ////////////////////////////////////////////// + // Use this to default DHCP hostname to ESP8266-XXXXXX or ESP32-XXXXXX //Blynk.begin(); // Use this to personalize DHCP hostname (RFC952 conformed) @@ -195,29 +217,25 @@ void setup() } #if (USE_BLYNK_WM && USE_DYNAMIC_PARAMETERS) -void displayCredentials(void) +void displayCredentials() { - Serial.println("\nYour stored Credentials :"); + Serial.println(F("\nYour stored Credentials :")); - for (int i = 0; i < NUM_MENU_ITEMS; i++) + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) { - Serial.println(String(myMenuItems[i].displayName) + " = " + myMenuItems[i].pdata); + Serial.print(myMenuItems[i].displayName); + Serial.print(F(" = ")); + Serial.println(myMenuItems[i].pdata); } } -#endif -void loop() +void displayCredentialsInLoop() { - Blynk.run(); - timer.run(); - check_status(); - -#if (USE_BLYNK_WM && USE_DYNAMIC_PARAMETERS) static bool displayedCredentials = false; if (!displayedCredentials) { - for (int i = 0; i < NUM_MENU_ITEMS; i++) + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) { if (!strlen(myMenuItems[i].pdata)) { @@ -231,5 +249,17 @@ void loop() } } } -#endif +} + +#endif + +void loop() +{ + Blynk.run(); + timer.run(); + check_status(); + +#if (USE_BLYNK_WM && USE_DYNAMIC_PARAMETERS) + displayCredentialsInLoop(); +#endif } diff --git a/examples/DHT11ESP8266/defines.h b/examples/DHT11ESP8266/defines.h index 5197afc..07e41db 100644 --- a/examples/DHT11ESP8266/defines.h +++ b/examples/DHT11ESP8266/defines.h @@ -13,7 +13,7 @@ #define defines_h #ifndef ESP8266 -#error This code is intended to run on the ESP8266 platform! Please check your Tools->Board setting. + #error This code is intended to run on the ESP8266 platform! Please check your Tools->Board setting. #endif #define BLYNK_PRINT Serial @@ -34,77 +34,89 @@ //#define USE_SPIFFS true #if USE_LITTLEFS -//LittleFS has higher priority -#define CurrentFileFS "LittleFS" -#ifdef USE_SPIFFS -#undef USE_SPIFFS -#endif -#define USE_SPIFFS false + //LittleFS has higher priority + #define CurrentFileFS "LittleFS" + #ifdef USE_SPIFFS + #undef USE_SPIFFS + #endif + #define USE_SPIFFS false #elif USE_SPIFFS -#define CurrentFileFS "SPIFFS" + #define CurrentFileFS "SPIFFS" #endif #if !( USE_LITTLEFS || USE_SPIFFS) -// EEPROM_SIZE must be <= 4096 and >= CONFIG_DATA_SIZE (currently 172 bytes) -#define EEPROM_SIZE (4 * 1024) -// EEPROM_START + CONFIG_DATA_SIZE must be <= EEPROM_SIZE -#define EEPROM_START 768 + // EEPROM_SIZE must be <= 4096 and >= CONFIG_DATA_SIZE (currently 172 bytes) + #define EEPROM_SIZE (4 * 1024) + // EEPROM_START + CONFIG_DATA_SIZE must be <= EEPROM_SIZE + #define EEPROM_START 768 #endif -// Force some params in Blynk, only valid for library version 1.0.1 and later -#define TIMEOUT_RECONNECT_WIFI 10000L -#define RESET_IF_CONFIG_TIMEOUT true -#define CONFIG_TIMEOUT_RETRYTIMES_BEFORE_RESET 5 - -#define USE_DYNAMIC_PARAMETERS true -// Those above #define's must be placed before #include +////////////////////////////////////////// //You have to download Blynk WiFiManager Blynk_WM library at //https://github.com/khoih-prog/Blynk_WM // In order to enable (USE_BLYNK_WM = true). Otherwise, use (USE_BLYNK_WM = false) -#define USE_BLYNK_WM true -//#define USE_BLYNK_WM false +#define USE_BLYNK_WM true -//#define USE_SSL true -#define USE_SSL false +#define USE_SSL false #if USE_BLYNK_WM -#if USE_SSL -#include //https://github.com/khoih-prog/Blynk_WM -#else -#include //https://github.com/khoih-prog/Blynk_WM -#endif - -#include "Credentials.h" -#include "dynamicParams.h" + ///////////////////////////////////////////// + + // Add customs headers from v1.2.0 + #define USING_CUSTOMS_STYLE true + #define USING_CUSTOMS_HEAD_ELEMENT true + #define USING_CORS_FEATURE true + + ///////////////////////////////////////////// + + // Force some params in Blynk, only valid for library version 1.0.1 and later + #define TIMEOUT_RECONNECT_WIFI 10000L + #define RESET_IF_CONFIG_TIMEOUT true + + #define CONFIG_TIMEOUT_RETRYTIMES_BEFORE_RESET 5 + + // Config Timeout 120s (default 60s) + #define CONFIG_TIMEOUT 120000L + + #define USE_DYNAMIC_PARAMETERS true + + // Those above #define's must be placed before #include and + #if USE_SSL + #include //https://github.com/khoih-prog/Blynk_WM + #else + #include //https://github.com/khoih-prog/Blynk_WM + #endif + + #include "Credentials.h" + #include "dynamicParams.h" #else -#if USE_SSL -#include -#define BLYNK_HARDWARE_PORT 9443 -#else -#include -#define BLYNK_HARDWARE_PORT 8080 -#endif + #if USE_SSL + #include + #define BLYNK_HARDWARE_PORT 9443 + #else + #include + #define BLYNK_HARDWARE_PORT 8080 + #endif #endif #if !USE_BLYNK_WM -#ifndef LED_BUILTIN -#define LED_BUILTIN 2 // Pin D2 control on-board LED -#endif - -#define USE_LOCAL_SERVER true -//#define USE_LOCAL_SERVER false - -// If local server -#if USE_LOCAL_SERVER -char blynk_server[] = "yourname.duckdns.org"; -#endif - -char auth[] = "***"; -char ssid[] = "***"; -char pass[] = "***"; + #ifndef LED_BUILTIN + #define LED_BUILTIN 2 // Pin D2 control on-board LED + #endif + + #define USE_LOCAL_SERVER true + + // If local server + #if USE_LOCAL_SERVER + char blynk_server[] = "yourname.duckdns.org"; + #endif + + char auth[] = "***"; + char ssid[] = "***"; + char pass[] = "***"; #endif diff --git a/examples/DHT11ESP8266_Debug/DHT11ESP8266_Debug.ino b/examples/DHT11ESP8266_Debug/DHT11ESP8266_Debug.ino index 650182e..3d27da7 100644 --- a/examples/DHT11ESP8266_Debug/DHT11ESP8266_Debug.ino +++ b/examples/DHT11ESP8266_Debug/DHT11ESP8266_Debug.ino @@ -7,7 +7,7 @@ Forked from Blynk library v0.6.1 https://github.com/blynkkk/blynk-library/releases Built by Khoi Hoang https://github.com/khoih-prog/Blynk_WM Licensed under MIT license - Version: 1.1.3 + Version: 1.2.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -32,6 +32,7 @@ 1.1.1 K Hoang 16/01/2021 Add functions to control Config Portal from software or Virtual Switches 1.1.2 K Hoang 28/01/2021 Fix Config Portal and Dynamic Params bugs 1.1.3 K Hoang 31/01/2021 To permit autoreset after timeout if DRD/MRD or non-persistent forced-CP + 1.2.0 K Hoang 24/02/2021 Add customs HTML header feature and support to ESP32-S2. *****************************************************************************************************************************/ #include "defines.h" @@ -107,6 +108,11 @@ void check_status() } } +#if USING_CUSTOMS_STYLE +const char NewCustomsStyle[] /*PROGMEM*/ = ""; +#endif + void setup() { pinMode(PIN_LED, OUTPUT); @@ -120,7 +126,7 @@ void setup() #if ( USE_LITTLEFS || USE_SPIFFS) Serial.print(F("\nStarting DHT11ESP8266_Debug using ")); Serial.print(CurrentFileFS); -#else) +#else Serial.print(F("\nStarting DHT11ESP8266_Debug using EEPROM"); #endif @@ -155,6 +161,22 @@ void setup() //Blynk.setSTAStaticIPConfig(IPAddress(192, 168, 2, 220), IPAddress(192, 168, 2, 1), IPAddress(255, 255, 255, 0), // IPAddress(4, 4, 4, 4), IPAddress(8, 8, 8, 8)); +////////////////////////////////////////////// + +#if USING_CUSTOMS_STYLE + Blynk.setCustomsStyle(NewCustomsStyle); +#endif + +#if USING_CUSTOMS_HEAD_ELEMENT + Blynk.setCustomsHeadElement(""); +#endif + +#if USING_CORS_FEATURE + Blynk.setCORSHeader("Your Access-Control-Allow-Origin"); +#endif + + ////////////////////////////////////////////// + // Use this to default DHCP hostname to ESP8266-XXXXXX or ESP32-XXXXXX //Blynk.begin(); // Use this to personalize DHCP hostname (RFC952 conformed) @@ -195,29 +217,25 @@ void setup() } #if (USE_BLYNK_WM && USE_DYNAMIC_PARAMETERS) -void displayCredentials(void) +void displayCredentials() { - Serial.println("\nYour stored Credentials :"); + Serial.println(F("\nYour stored Credentials :")); - for (int i = 0; i < NUM_MENU_ITEMS; i++) + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) { - Serial.println(String(myMenuItems[i].displayName) + " = " + myMenuItems[i].pdata); + Serial.print(myMenuItems[i].displayName); + Serial.print(F(" = ")); + Serial.println(myMenuItems[i].pdata); } } -#endif -void loop() +void displayCredentialsInLoop() { - Blynk.run(); - timer.run(); - check_status(); - -#if (USE_BLYNK_WM && USE_DYNAMIC_PARAMETERS) static bool displayedCredentials = false; if (!displayedCredentials) { - for (int i = 0; i < NUM_MENU_ITEMS; i++) + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) { if (!strlen(myMenuItems[i].pdata)) { @@ -231,5 +249,17 @@ void loop() } } } -#endif +} + +#endif + +void loop() +{ + Blynk.run(); + timer.run(); + check_status(); + +#if (USE_BLYNK_WM && USE_DYNAMIC_PARAMETERS) + displayCredentialsInLoop(); +#endif } diff --git a/examples/DHT11ESP8266_Debug/defines.h b/examples/DHT11ESP8266_Debug/defines.h index 2f546b2..d55c4cc 100644 --- a/examples/DHT11ESP8266_Debug/defines.h +++ b/examples/DHT11ESP8266_Debug/defines.h @@ -13,7 +13,7 @@ #define defines_h #ifndef ESP8266 -#error This code is intended to run on the ESP8266 platform! Please check your Tools->Board setting. + #error This code is intended to run on the ESP8266 platform! Please check your Tools->Board setting. #endif #define BLYNK_PRINT Serial @@ -34,77 +34,89 @@ //#define USE_SPIFFS true #if USE_LITTLEFS -//LittleFS has higher priority -#define CurrentFileFS "LittleFS" -#ifdef USE_SPIFFS -#undef USE_SPIFFS -#endif -#define USE_SPIFFS false + //LittleFS has higher priority + #define CurrentFileFS "LittleFS" + #ifdef USE_SPIFFS + #undef USE_SPIFFS + #endif + #define USE_SPIFFS false #elif USE_SPIFFS -#define CurrentFileFS "SPIFFS" + #define CurrentFileFS "SPIFFS" #endif #if !( USE_LITTLEFS || USE_SPIFFS) -// EEPROM_SIZE must be <= 4096 and >= CONFIG_DATA_SIZE (currently 172 bytes) -#define EEPROM_SIZE (4 * 1024) -// EEPROM_START + CONFIG_DATA_SIZE must be <= EEPROM_SIZE -#define EEPROM_START 768 + // EEPROM_SIZE must be <= 4096 and >= CONFIG_DATA_SIZE (currently 172 bytes) + #define EEPROM_SIZE (4 * 1024) + // EEPROM_START + CONFIG_DATA_SIZE must be <= EEPROM_SIZE + #define EEPROM_START 768 #endif -// Force some params in Blynk, only valid for library version 1.0.1 and later -#define TIMEOUT_RECONNECT_WIFI 10000L -#define RESET_IF_CONFIG_TIMEOUT true -#define CONFIG_TIMEOUT_RETRYTIMES_BEFORE_RESET 5 - -#define USE_DYNAMIC_PARAMETERS true -// Those above #define's must be placed before #include +////////////////////////////////////////// //You have to download Blynk WiFiManager Blynk_WM library at //https://github.com/khoih-prog/Blynk_WM // In order to enable (USE_BLYNK_WM = true). Otherwise, use (USE_BLYNK_WM = false) -#define USE_BLYNK_WM true -//#define USE_BLYNK_WM false +#define USE_BLYNK_WM true -//#define USE_SSL true -#define USE_SSL false +#define USE_SSL false #if USE_BLYNK_WM -#if USE_SSL -#include //https://github.com/khoih-prog/Blynk_WM -#else -#include //https://github.com/khoih-prog/Blynk_WM -#endif - -#include "Credentials.h" -#include "dynamicParams.h" + ///////////////////////////////////////////// + + // Add customs headers from v1.2.0 + #define USING_CUSTOMS_STYLE true + #define USING_CUSTOMS_HEAD_ELEMENT true + #define USING_CORS_FEATURE true + + ///////////////////////////////////////////// + + // Force some params in Blynk, only valid for library version 1.0.1 and later + #define TIMEOUT_RECONNECT_WIFI 10000L + #define RESET_IF_CONFIG_TIMEOUT true + + #define CONFIG_TIMEOUT_RETRYTIMES_BEFORE_RESET 5 + + // Config Timeout 120s (default 60s) + #define CONFIG_TIMEOUT 120000L + + #define USE_DYNAMIC_PARAMETERS true + // Those above #define's must be placed before #include and + + #if USE_SSL + #include //https://github.com/khoih-prog/Blynk_WM + #else + #include //https://github.com/khoih-prog/Blynk_WM + #endif + + #include "Credentials.h" + #include "dynamicParams.h" #else -#if USE_SSL -#include -#define BLYNK_HARDWARE_PORT 9443 -#else -#include -#define BLYNK_HARDWARE_PORT 8080 -#endif + #if USE_SSL + #include + #define BLYNK_HARDWARE_PORT 9443 + #else + #include + #define BLYNK_HARDWARE_PORT 8080 + #endif #endif #if !USE_BLYNK_WM -#ifndef LED_BUILTIN -#define LED_BUILTIN 2 // Pin D2 control on-board LED -#endif - -#define USE_LOCAL_SERVER true -//#define USE_LOCAL_SERVER false - -// If local server -#if USE_LOCAL_SERVER -char blynk_server[] = "yourname.duckdns.org"; -#endif - -char auth[] = "***"; -char ssid[] = "***"; -char pass[] = "***"; + #ifndef LED_BUILTIN + #define LED_BUILTIN 2 // Pin D2 control on-board LED + #endif + + #define USE_LOCAL_SERVER true + + // If local server + #if USE_LOCAL_SERVER + char blynk_server[] = "yourname.duckdns.org"; + #endif + + char auth[] = "***"; + char ssid[] = "***"; + char pass[] = "***"; #endif diff --git a/examples/DHT11ESP8266_SSL/DHT11ESP8266_SSL.ino b/examples/DHT11ESP8266_SSL/DHT11ESP8266_SSL.ino index 3427e03..318efec 100644 --- a/examples/DHT11ESP8266_SSL/DHT11ESP8266_SSL.ino +++ b/examples/DHT11ESP8266_SSL/DHT11ESP8266_SSL.ino @@ -7,7 +7,7 @@ Forked from Blynk library v0.6.1 https://github.com/blynkkk/blynk-library/releases Built by Khoi Hoang https://github.com/khoih-prog/Blynk_WM Licensed under MIT license - Version: 1.1.3 + Version: 1.2.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -32,6 +32,7 @@ 1.1.1 K Hoang 16/01/2021 Add functions to control Config Portal from software or Virtual Switches 1.1.2 K Hoang 28/01/2021 Fix Config Portal and Dynamic Params bugs 1.1.3 K Hoang 31/01/2021 To permit autoreset after timeout if DRD/MRD or non-persistent forced-CP + 1.2.0 K Hoang 24/02/2021 Add customs HTML header feature and support to ESP32-S2. *****************************************************************************************************************************/ #include "defines.h" @@ -107,6 +108,11 @@ void check_status() } } +#if USING_CUSTOMS_STYLE +const char NewCustomsStyle[] /*PROGMEM*/ = ""; +#endif + void setup() { pinMode(PIN_LED, OUTPUT); @@ -154,6 +160,22 @@ void setup() //Blynk.setSTAStaticIPConfig(IPAddress(192, 168, 2, 220), IPAddress(192, 168, 2, 1), IPAddress(255, 255, 255, 0), // IPAddress(4, 4, 4, 4), IPAddress(8, 8, 8, 8)); +////////////////////////////////////////////// + +#if USING_CUSTOMS_STYLE + Blynk.setCustomsStyle(NewCustomsStyle); +#endif + +#if USING_CUSTOMS_HEAD_ELEMENT + Blynk.setCustomsHeadElement(""); +#endif + +#if USING_CORS_FEATURE + Blynk.setCORSHeader("Your Access-Control-Allow-Origin"); +#endif + + ////////////////////////////////////////////// + // Use this to default DHCP hostname to ESP8266-XXXXXX or ESP32-XXXXXX //Blynk.begin(); // Use this to personalize DHCP hostname (RFC952 conformed) @@ -194,29 +216,25 @@ void setup() } #if (USE_BLYNK_WM && USE_DYNAMIC_PARAMETERS) -void displayCredentials(void) +void displayCredentials() { Serial.println(F("\nYour stored Credentials :")); - for (int i = 0; i < NUM_MENU_ITEMS; i++) + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) { - Serial.println(String(myMenuItems[i].displayName) + " = " + myMenuItems[i].pdata); + Serial.print(myMenuItems[i].displayName); + Serial.print(F(" = ")); + Serial.println(myMenuItems[i].pdata); } } -#endif -void loop() +void displayCredentialsInLoop() { - Blynk.run(); - timer.run(); - check_status(); - -#if (USE_BLYNK_WM && USE_DYNAMIC_PARAMETERS) static bool displayedCredentials = false; if (!displayedCredentials) { - for (int i = 0; i < NUM_MENU_ITEMS; i++) + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) { if (!strlen(myMenuItems[i].pdata)) { @@ -230,5 +248,17 @@ void loop() } } } -#endif +} + +#endif + +void loop() +{ + Blynk.run(); + timer.run(); + check_status(); + +#if (USE_BLYNK_WM && USE_DYNAMIC_PARAMETERS) + displayCredentialsInLoop(); +#endif } diff --git a/examples/DHT11ESP8266_SSL/defines.h b/examples/DHT11ESP8266_SSL/defines.h index b27fe94..73d4d27 100644 --- a/examples/DHT11ESP8266_SSL/defines.h +++ b/examples/DHT11ESP8266_SSL/defines.h @@ -13,7 +13,7 @@ #define defines_h #ifndef ESP8266 -#error This code is intended to run on the ESP8266 platform! Please check your Tools->Board setting. + #error This code is intended to run on the ESP8266 platform! Please check your Tools->Board setting. #endif #define BLYNK_PRINT Serial @@ -29,82 +29,92 @@ // Select USE_LITTLEFS (higher priority) or USE_SPIFFS #define USE_LITTLEFS true -//#define USE_LITTLEFS false #define USE_SPIFFS false -//#define USE_SPIFFS true #if USE_LITTLEFS -//LittleFS has higher priority -#define CurrentFileFS "LittleFS" -#ifdef USE_SPIFFS -#undef USE_SPIFFS -#endif -#define USE_SPIFFS false + //LittleFS has higher priority + #define CurrentFileFS "LittleFS" + #ifdef USE_SPIFFS + #undef USE_SPIFFS + #endif + #define USE_SPIFFS false #elif USE_SPIFFS -#define CurrentFileFS "SPIFFS" + #define CurrentFileFS "SPIFFS" #endif #if !( USE_LITTLEFS || USE_SPIFFS) -// EEPROM_SIZE must be <= 4096 and >= CONFIG_DATA_SIZE (currently 172 bytes) -#define EEPROM_SIZE (4 * 1024) -// EEPROM_START + CONFIG_DATA_SIZE must be <= EEPROM_SIZE -#define EEPROM_START 0 + // EEPROM_SIZE must be <= 4096 and >= CONFIG_DATA_SIZE (currently 172 bytes) + #define EEPROM_SIZE (4 * 1024) + // EEPROM_START + CONFIG_DATA_SIZE must be <= EEPROM_SIZE + #define EEPROM_START 0 #endif -// Force some params in Blynk, only valid for library version 1.0.1 and later -#define TIMEOUT_RECONNECT_WIFI 10000L -#define RESET_IF_CONFIG_TIMEOUT true -#define CONFIG_TIMEOUT_RETRYTIMES_BEFORE_RESET 5 - -#define USE_DYNAMIC_PARAMETERS true -// Those above #define's must be placed before #include +////////////////////////////////////////// //You have to download Blynk WiFiManager Blynk_WM library at //https://github.com/khoih-prog/Blynk_WM // In order to enable (USE_BLYNK_WM = true). Otherwise, use (USE_BLYNK_WM = false) #define USE_BLYNK_WM true -//#define USE_BLYNK_WM false #define USE_SSL true -//#define USE_SSL false #if USE_BLYNK_WM -#if USE_SSL -#include //https://github.com/khoih-prog/Blynk_WM -#else -#include //https://github.com/khoih-prog/Blynk_WM -#endif - -#include "Credentials.h" -#include "dynamicParams.h" + ///////////////////////////////////////////// + + // Add customs headers from v1.2.0 + #define USING_CUSTOMS_STYLE true + #define USING_CUSTOMS_HEAD_ELEMENT true + #define USING_CORS_FEATURE true + + ///////////////////////////////////////////// + + // Force some params in Blynk, only valid for library version 1.0.1 and later + #define TIMEOUT_RECONNECT_WIFI 10000L + #define RESET_IF_CONFIG_TIMEOUT true + + #define CONFIG_TIMEOUT_RETRYTIMES_BEFORE_RESET 5 + + // Config Timeout 120s (default 60s) + #define CONFIG_TIMEOUT 120000L + + #define USE_DYNAMIC_PARAMETERS true + // Those above #define's must be placed before #include and + + #if USE_SSL + #include //https://github.com/khoih-prog/Blynk_WM + #else + #include //https://github.com/khoih-prog/Blynk_WM + #endif + + #include "Credentials.h" + #include "dynamicParams.h" #else -#if USE_SSL -#include -#define BLYNK_HARDWARE_PORT 9443 -#else -#include -#define BLYNK_HARDWARE_PORT 8080 -#endif + #if USE_SSL + #include + #define BLYNK_HARDWARE_PORT 9443 + #else + #include + #define BLYNK_HARDWARE_PORT 8080 + #endif #endif #if !USE_BLYNK_WM -#ifndef LED_BUILTIN -#define LED_BUILTIN 2 // Pin D2 control on-board LED -#endif - -#define USE_LOCAL_SERVER true -//#define USE_LOCAL_SERVER false - -// If local server -#if USE_LOCAL_SERVER -char blynk_server[] = "yourname.duckdns.org"; -#endif - -char auth[] = "***"; -char ssid[] = "***"; -char pass[] = "***"; + #ifndef LED_BUILTIN + #define LED_BUILTIN 2 // Pin D2 control on-board LED + #endif + + #define USE_LOCAL_SERVER true + + // If local server + #if USE_LOCAL_SERVER + char blynk_server[] = "yourname.duckdns.org"; + #endif + + char auth[] = "***"; + char ssid[] = "***"; + char pass[] = "***"; #endif diff --git a/examples/ESP32WM_Config/ESP32WM_Config.ino b/examples/ESP32WM_Config/ESP32WM_Config.ino index a240d14..b1552de 100644 --- a/examples/ESP32WM_Config/ESP32WM_Config.ino +++ b/examples/ESP32WM_Config/ESP32WM_Config.ino @@ -7,7 +7,7 @@ Forked from Blynk library v0.6.1 https://github.com/blynkkk/blynk-library/releases Built by Khoi Hoang https://github.com/khoih-prog/Blynk_WM Licensed under MIT license - Version: 1.1.3 + Version: 1.2.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -32,6 +32,7 @@ 1.1.1 K Hoang 16/01/2021 Add functions to control Config Portal from software or Virtual Switches 1.1.2 K Hoang 28/01/2021 Fix Config Portal and Dynamic Params bugs 1.1.3 K Hoang 31/01/2021 To permit autoreset after timeout if DRD/MRD or non-persistent forced-CP + 1.2.0 K Hoang 24/02/2021 Add customs HTML header feature and support to ESP32-S2. *****************************************************************************************************************************/ #include "defines.h" @@ -112,6 +113,11 @@ void check_status() } } +#if USING_CUSTOMS_STYLE +const char NewCustomsStyle[] /*PROGMEM*/ = ""; +#endif + void setup() { pinMode(LED_BUILTIN, OUTPUT); @@ -156,6 +162,22 @@ void setup() //Blynk.setSTAStaticIPConfig(IPAddress(192, 168, 2, 220), IPAddress(192, 168, 2, 1), IPAddress(255, 255, 255, 0), // IPAddress(4, 4, 4, 4), IPAddress(8, 8, 8, 8)); +////////////////////////////////////////////// + +#if USING_CUSTOMS_STYLE + Blynk.setCustomsStyle(NewCustomsStyle); +#endif + +#if USING_CUSTOMS_HEAD_ELEMENT + Blynk.setCustomsHeadElement(""); +#endif + +#if USING_CORS_FEATURE + Blynk.setCORSHeader("Your Access-Control-Allow-Origin"); +#endif + + ////////////////////////////////////////////// + // Use this to default DHCP hostname to ESP8266-XXXXXX or ESP32-XXXXXX //Blynk.begin(); // Use this to personalize DHCP hostname (RFC952 conformed) @@ -185,25 +207,21 @@ void displayCredentials() { Serial.println(F("\nYour stored Credentials :")); - for (int i = 0; i < NUM_MENU_ITEMS; i++) + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) { - Serial.println(String(myMenuItems[i].displayName) + " = " + myMenuItems[i].pdata); + Serial.print(myMenuItems[i].displayName); + Serial.print(F(" = ")); + Serial.println(myMenuItems[i].pdata); } } -#endif -void loop() +void displayCredentialsInLoop() { - Blynk.run(); - timer.run(); - check_status(); - -#if USE_DYNAMIC_PARAMETERS static bool displayedCredentials = false; if (!displayedCredentials) { - for (int i = 0; i < NUM_MENU_ITEMS; i++) + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) { if (!strlen(myMenuItems[i].pdata)) { @@ -217,5 +235,17 @@ void loop() } } } +} + +#endif + +void loop() +{ + Blynk.run(); + timer.run(); + check_status(); + +#if USE_DYNAMIC_PARAMETERS + displayCredentialsInLoop(); #endif } diff --git a/examples/ESP32WM_Config/defines.h b/examples/ESP32WM_Config/defines.h index 8fc2f98..28fdd30 100644 --- a/examples/ESP32WM_Config/defines.h +++ b/examples/ESP32WM_Config/defines.h @@ -25,8 +25,15 @@ // (USE_LITTLEFS == false) and (USE_SPIFFS == true) => using SPIFFS for configuration data in WiFiManager // Those above #define's must be placed before #include -#define USE_LITTLEFS true -#define USE_SPIFFS false +#if ( ARDUINO_ESP32S2_DEV || ARDUINO_FEATHERS2 || ARDUINO_PROS2 || ARDUINO_MICROS2 ) + // Currently, ESP32-S2 only supporting EEPROM. Will fix to support LittleFS and SPIFFS + #define USE_LITTLEFS false + #define USE_SPIFFS false + #warning ESP32-S2 only support supporting EEPROM now. +#else + #define USE_LITTLEFS true + #define USE_SPIFFS false +#endif #if !( USE_SPIFFS || USE_LITTLEFS) @@ -36,13 +43,27 @@ #define EEPROM_START 0 #endif +///////////////////////////////////////////// + +// Add customs headers from v1.2.0 +#define USING_CUSTOMS_STYLE true +#define USING_CUSTOMS_HEAD_ELEMENT true +#define USING_CORS_FEATURE true + +///////////////////////////////////////////// + // Force some params in Blynk, only valid for library version 1.0.1 and later #define TIMEOUT_RECONNECT_WIFI 10000L #define RESET_IF_CONFIG_TIMEOUT true + #define CONFIG_TIMEOUT_RETRYTIMES_BEFORE_RESET 5 -#define USE_DYNAMIC_PARAMETERS true -// Those above #define's must be placed before #include +// Config Timeout 120s (default 60s) +#define CONFIG_TIMEOUT 120000L + +#define USE_DYNAMIC_PARAMETERS true +// Those above #define's must be placed before #include and +////////////////////////////////////////// //#define USE_SSL true #define USE_SSL false diff --git a/examples/ESP32WM_ForcedConfig/ESP32WM_ForcedConfig.ino b/examples/ESP32WM_ForcedConfig/ESP32WM_ForcedConfig.ino index 9a11aaa..7a1abce 100644 --- a/examples/ESP32WM_ForcedConfig/ESP32WM_ForcedConfig.ino +++ b/examples/ESP32WM_ForcedConfig/ESP32WM_ForcedConfig.ino @@ -7,7 +7,7 @@ Forked from Blynk library v0.6.1 https://github.com/blynkkk/blynk-library/releases Built by Khoi Hoang https://github.com/khoih-prog/Blynk_WM Licensed under MIT license - Version: 1.1.3 + Version: 1.2.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -32,6 +32,7 @@ 1.1.1 K Hoang 16/01/2021 Add functions to control Config Portal from software or Virtual Switches 1.1.2 K Hoang 28/01/2021 Fix Config Portal and Dynamic Params bugs 1.1.3 K Hoang 31/01/2021 To permit autoreset after timeout if DRD/MRD or non-persistent forced-CP + 1.2.0 K Hoang 24/02/2021 Add customs HTML header feature and support to ESP32-S2. *****************************************************************************************************************************/ #include "defines.h" @@ -145,6 +146,11 @@ void check_status() } } +#if USING_CUSTOMS_STYLE +const char NewCustomsStyle[] /*PROGMEM*/ = ""; +#endif + void setup() { pinMode(LED_BUILTIN, OUTPUT); @@ -189,6 +195,22 @@ void setup() //Blynk.setSTAStaticIPConfig(IPAddress(192, 168, 2, 220), IPAddress(192, 168, 2, 1), IPAddress(255, 255, 255, 0), // IPAddress(4, 4, 4, 4), IPAddress(8, 8, 8, 8)); +////////////////////////////////////////////// + +#if USING_CUSTOMS_STYLE + Blynk.setCustomsStyle(NewCustomsStyle); +#endif + +#if USING_CUSTOMS_HEAD_ELEMENT + Blynk.setCustomsHeadElement(""); +#endif + +#if USING_CORS_FEATURE + Blynk.setCORSHeader("Your Access-Control-Allow-Origin"); +#endif + + ////////////////////////////////////////////// + // Use this to default DHCP hostname to ESP8266-XXXXXX or ESP32-XXXXXX //Blynk.begin(); // Use this to personalize DHCP hostname (RFC952 conformed) @@ -218,25 +240,21 @@ void displayCredentials() { Serial.println(F("\nYour stored Credentials :")); - for (int i = 0; i < NUM_MENU_ITEMS; i++) + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) { - Serial.println(String(myMenuItems[i].displayName) + " = " + myMenuItems[i].pdata); + Serial.print(myMenuItems[i].displayName); + Serial.print(F(" = ")); + Serial.println(myMenuItems[i].pdata); } } -#endif -void loop() +void displayCredentialsInLoop() { - Blynk.run(); - timer.run(); - check_status(); - -#if USE_DYNAMIC_PARAMETERS static bool displayedCredentials = false; if (!displayedCredentials) { - for (int i = 0; i < NUM_MENU_ITEMS; i++) + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) { if (!strlen(myMenuItems[i].pdata)) { @@ -250,5 +268,17 @@ void loop() } } } +} + +#endif + +void loop() +{ + Blynk.run(); + timer.run(); + check_status(); + +#if USE_DYNAMIC_PARAMETERS + displayCredentialsInLoop(); #endif } diff --git a/examples/ESP32WM_ForcedConfig/defines.h b/examples/ESP32WM_ForcedConfig/defines.h index 672a348..ad67495 100644 --- a/examples/ESP32WM_ForcedConfig/defines.h +++ b/examples/ESP32WM_ForcedConfig/defines.h @@ -26,8 +26,15 @@ // (USE_LITTLEFS == false) and (USE_SPIFFS == true) => using SPIFFS for configuration data in WiFiManager // Those above #define's must be placed before #include -#define USE_LITTLEFS true -#define USE_SPIFFS false +#if ( ARDUINO_ESP32S2_DEV || ARDUINO_FEATHERS2 || ARDUINO_PROS2 || ARDUINO_MICROS2 ) + // Currently, ESP32-S2 only supporting EEPROM. Will fix to support LittleFS and SPIFFS + #define USE_LITTLEFS false + #define USE_SPIFFS false + #warning ESP32-S2 only support supporting EEPROM now. +#else + #define USE_LITTLEFS true + #define USE_SPIFFS false +#endif #if !( USE_SPIFFS || USE_LITTLEFS) @@ -37,16 +44,29 @@ #define EEPROM_START 0 #endif +///////////////////////////////////////////// + +// Add customs headers from v1.2.0 +#define USING_CUSTOMS_STYLE true +#define USING_CUSTOMS_HEAD_ELEMENT true +#define USING_CORS_FEATURE true + +///////////////////////////////////////////// + // Force some params in Blynk, only valid for library version 1.0.1 and later #define TIMEOUT_RECONNECT_WIFI 10000L #define RESET_IF_CONFIG_TIMEOUT true + #define CONFIG_TIMEOUT_RETRYTIMES_BEFORE_RESET 5 +// Config Timeout 120s (default 60s) +#define CONFIG_TIMEOUT 120000L + #define USE_DYNAMIC_PARAMETERS true -// Those above #define's must be placed before #include +// Those above #define's must be placed before #include and +////////////////////////////////////////// #define USE_SSL true -//#define USE_SSL false #if USE_SSL #include diff --git a/examples/ESP32WM_MRD_Config/ESP32WM_MRD_Config.ino b/examples/ESP32WM_MRD_Config/ESP32WM_MRD_Config.ino index 53339d0..3fedf8b 100644 --- a/examples/ESP32WM_MRD_Config/ESP32WM_MRD_Config.ino +++ b/examples/ESP32WM_MRD_Config/ESP32WM_MRD_Config.ino @@ -7,7 +7,7 @@ Forked from Blynk library v0.6.1 https://github.com/blynkkk/blynk-library/releases Built by Khoi Hoang https://github.com/khoih-prog/Blynk_WM Licensed under MIT license - Version: 1.1.3 + Version: 1.2.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -32,6 +32,7 @@ 1.1.1 K Hoang 16/01/2021 Add functions to control Config Portal from software or Virtual Switches 1.1.2 K Hoang 28/01/2021 Fix Config Portal and Dynamic Params bugs 1.1.3 K Hoang 31/01/2021 To permit autoreset after timeout if DRD/MRD or non-persistent forced-CP + 1.2.0 K Hoang 24/02/2021 Add customs HTML header feature and support to ESP32-S2. *****************************************************************************************************************************/ #include "defines.h" @@ -112,6 +113,11 @@ void check_status() } } +#if USING_CUSTOMS_STYLE +const char NewCustomsStyle[] /*PROGMEM*/ = ""; +#endif + void setup() { pinMode(LED_BUILTIN, OUTPUT); @@ -161,6 +167,22 @@ void setup() //Blynk.setSTAStaticIPConfig(IPAddress(192, 168, 2, 220), IPAddress(192, 168, 2, 1), IPAddress(255, 255, 255, 0), // IPAddress(4, 4, 4, 4), IPAddress(8, 8, 8, 8)); +////////////////////////////////////////////// + +#if USING_CUSTOMS_STYLE + Blynk.setCustomsStyle(NewCustomsStyle); +#endif + +#if USING_CUSTOMS_HEAD_ELEMENT + Blynk.setCustomsHeadElement(""); +#endif + +#if USING_CORS_FEATURE + Blynk.setCORSHeader("Your Access-Control-Allow-Origin"); +#endif + + ////////////////////////////////////////////// + // Use this to default DHCP hostname to ESP8266-XXXXXX or ESP32-XXXXXX //Blynk.begin(); // Use this to personalize DHCP hostname (RFC952 conformed) diff --git a/examples/ESP32WM_MRD_Config/defines.h b/examples/ESP32WM_MRD_Config/defines.h index c6576f5..0ccc5db 100644 --- a/examples/ESP32WM_MRD_Config/defines.h +++ b/examples/ESP32WM_MRD_Config/defines.h @@ -60,8 +60,15 @@ // (USE_LITTLEFS == false) and (USE_SPIFFS == true) => using SPIFFS for configuration data in WiFiManager // Those above #define's must be placed before #include -#define USE_LITTLEFS true -#define USE_SPIFFS false +#if ( ARDUINO_ESP32S2_DEV || ARDUINO_FEATHERS2 || ARDUINO_PROS2 || ARDUINO_MICROS2 ) + // Currently, ESP32-S2 only supporting EEPROM. Will fix to support LittleFS and SPIFFS + #define USE_LITTLEFS false + #define USE_SPIFFS false + #warning ESP32-S2 only support supporting EEPROM now. +#else + #define USE_LITTLEFS true + #define USE_SPIFFS false +#endif #if !( USE_SPIFFS || USE_LITTLEFS) // EEPROM_SIZE must be <= 2048 and >= CONFIG_DATA_SIZE (currently 172 bytes) @@ -70,22 +77,35 @@ #define EEPROM_START 0 #endif +///////////////////////////////////////////// + +// Add customs headers from v1.2.0 +#define USING_CUSTOMS_STYLE true +#define USING_CUSTOMS_HEAD_ELEMENT true +#define USING_CORS_FEATURE true + +///////////////////////////////////////////// + // Force some params in Blynk, only valid for library version 1.0.1 and later #define TIMEOUT_RECONNECT_WIFI 10000L #define RESET_IF_CONFIG_TIMEOUT true #define CONFIG_TIMEOUT_RETRYTIMES_BEFORE_RESET 5 +// Config Timeout 120s (default 60s) +#define CONFIG_TIMEOUT 120000L + #define USE_DYNAMIC_PARAMETERS true -// Those above #define's must be placed before #include +// Those above #define's must be placed before #include and +////////////////////////////////////////// -//#define USE_SSL true -#define USE_SSL false +#define USE_SSL true +//#define USE_SSL false #if USE_SSL -#include + #include #else -#include + #include #endif #define PIN_D22 22 // Pin D22 mapped to pin GPIO22/SCL of ESP32 diff --git a/examples/ESP32WM_MRD_ForcedConfig/ESP32WM_MRD_ForcedConfig.ino b/examples/ESP32WM_MRD_ForcedConfig/ESP32WM_MRD_ForcedConfig.ino index 38dd877..2b8a28d 100644 --- a/examples/ESP32WM_MRD_ForcedConfig/ESP32WM_MRD_ForcedConfig.ino +++ b/examples/ESP32WM_MRD_ForcedConfig/ESP32WM_MRD_ForcedConfig.ino @@ -7,7 +7,7 @@ Forked from Blynk library v0.6.1 https://github.com/blynkkk/blynk-library/releases Built by Khoi Hoang https://github.com/khoih-prog/Blynk_WM Licensed under MIT license - Version: 1.1.3 + Version: 1.2.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -32,6 +32,7 @@ 1.1.1 K Hoang 16/01/2021 Add functions to control Config Portal from software or Virtual Switches 1.1.2 K Hoang 28/01/2021 Fix Config Portal and Dynamic Params bugs 1.1.3 K Hoang 31/01/2021 To permit autoreset after timeout if DRD/MRD or non-persistent forced-CP + 1.2.0 K Hoang 24/02/2021 Add customs HTML header feature and support to ESP32-S2. *****************************************************************************************************************************/ #include "defines.h" @@ -145,6 +146,11 @@ void check_status() } } +#if USING_CUSTOMS_STYLE +const char NewCustomsStyle[] /*PROGMEM*/ = ""; +#endif + void setup() { pinMode(LED_BUILTIN, OUTPUT); @@ -194,6 +200,22 @@ void setup() //Blynk.setSTAStaticIPConfig(IPAddress(192, 168, 2, 220), IPAddress(192, 168, 2, 1), IPAddress(255, 255, 255, 0), // IPAddress(4, 4, 4, 4), IPAddress(8, 8, 8, 8)); +////////////////////////////////////////////// + +#if USING_CUSTOMS_STYLE + Blynk.setCustomsStyle(NewCustomsStyle); +#endif + +#if USING_CUSTOMS_HEAD_ELEMENT + Blynk.setCustomsHeadElement(""); +#endif + +#if USING_CORS_FEATURE + Blynk.setCORSHeader("Your Access-Control-Allow-Origin"); +#endif + + ////////////////////////////////////////////// + // Use this to default DHCP hostname to ESP8266-XXXXXX or ESP32-XXXXXX //Blynk.begin(); // Use this to personalize DHCP hostname (RFC952 conformed) @@ -223,25 +245,21 @@ void displayCredentials() { Serial.println(F("\nYour stored Credentials :")); - for (int i = 0; i < NUM_MENU_ITEMS; i++) + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) { - Serial.println(String(myMenuItems[i].displayName) + " = " + myMenuItems[i].pdata); + Serial.print(myMenuItems[i].displayName); + Serial.print(F(" = ")); + Serial.println(myMenuItems[i].pdata); } } -#endif -void loop() +void displayCredentialsInLoop() { - Blynk.run(); - timer.run(); - check_status(); - -#if USE_DYNAMIC_PARAMETERS static bool displayedCredentials = false; if (!displayedCredentials) { - for (int i = 0; i < NUM_MENU_ITEMS; i++) + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) { if (!strlen(myMenuItems[i].pdata)) { @@ -255,5 +273,17 @@ void loop() } } } +} + +#endif + +void loop() +{ + Blynk.run(); + timer.run(); + check_status(); + +#if USE_DYNAMIC_PARAMETERS + displayCredentialsInLoop(); #endif } diff --git a/examples/ESP32WM_MRD_ForcedConfig/defines.h b/examples/ESP32WM_MRD_ForcedConfig/defines.h index 1ba4920..0ccc5db 100644 --- a/examples/ESP32WM_MRD_ForcedConfig/defines.h +++ b/examples/ESP32WM_MRD_ForcedConfig/defines.h @@ -60,8 +60,15 @@ // (USE_LITTLEFS == false) and (USE_SPIFFS == true) => using SPIFFS for configuration data in WiFiManager // Those above #define's must be placed before #include -#define USE_LITTLEFS true -#define USE_SPIFFS false +#if ( ARDUINO_ESP32S2_DEV || ARDUINO_FEATHERS2 || ARDUINO_PROS2 || ARDUINO_MICROS2 ) + // Currently, ESP32-S2 only supporting EEPROM. Will fix to support LittleFS and SPIFFS + #define USE_LITTLEFS false + #define USE_SPIFFS false + #warning ESP32-S2 only support supporting EEPROM now. +#else + #define USE_LITTLEFS true + #define USE_SPIFFS false +#endif #if !( USE_SPIFFS || USE_LITTLEFS) // EEPROM_SIZE must be <= 2048 and >= CONFIG_DATA_SIZE (currently 172 bytes) @@ -70,22 +77,35 @@ #define EEPROM_START 0 #endif +///////////////////////////////////////////// + +// Add customs headers from v1.2.0 +#define USING_CUSTOMS_STYLE true +#define USING_CUSTOMS_HEAD_ELEMENT true +#define USING_CORS_FEATURE true + +///////////////////////////////////////////// + // Force some params in Blynk, only valid for library version 1.0.1 and later #define TIMEOUT_RECONNECT_WIFI 10000L #define RESET_IF_CONFIG_TIMEOUT true #define CONFIG_TIMEOUT_RETRYTIMES_BEFORE_RESET 5 +// Config Timeout 120s (default 60s) +#define CONFIG_TIMEOUT 120000L + #define USE_DYNAMIC_PARAMETERS true -// Those above #define's must be placed before #include +// Those above #define's must be placed before #include and +////////////////////////////////////////// #define USE_SSL true //#define USE_SSL false #if USE_SSL -#include + #include #else -#include + #include #endif #define PIN_D22 22 // Pin D22 mapped to pin GPIO22/SCL of ESP32 diff --git a/examples/ESP8266WM_Config/ESP8266WM_Config.ino b/examples/ESP8266WM_Config/ESP8266WM_Config.ino index c60244c..4761ff6 100644 --- a/examples/ESP8266WM_Config/ESP8266WM_Config.ino +++ b/examples/ESP8266WM_Config/ESP8266WM_Config.ino @@ -7,7 +7,7 @@ Forked from Blynk library v0.6.1 https://github.com/blynkkk/blynk-library/releases Built by Khoi Hoang https://github.com/khoih-prog/Blynk_WM Licensed under MIT license - Version: 1.1.3 + Version: 1.2.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -32,6 +32,7 @@ 1.1.1 K Hoang 16/01/2021 Add functions to control Config Portal from software or Virtual Switches 1.1.2 K Hoang 28/01/2021 Fix Config Portal and Dynamic Params bugs 1.1.3 K Hoang 31/01/2021 To permit autoreset after timeout if DRD/MRD or non-persistent forced-CP + 1.2.0 K Hoang 24/02/2021 Add customs HTML header feature and support to ESP32-S2. *****************************************************************************************************************************/ #include "defines.h" @@ -115,6 +116,11 @@ void check_status() } } +#if USING_CUSTOMS_STYLE +const char NewCustomsStyle[] /*PROGMEM*/ = ""; +#endif + void setup() { pinMode(PIN_LED, OUTPUT); @@ -158,6 +164,22 @@ void setup() //Blynk.setSTAStaticIPConfig(IPAddress(192, 168, 2, 220), IPAddress(192, 168, 2, 1), IPAddress(255, 255, 255, 0), // IPAddress(4, 4, 4, 4), IPAddress(8, 8, 8, 8)); +////////////////////////////////////////////// + +#if USING_CUSTOMS_STYLE + Blynk.setCustomsStyle(NewCustomsStyle); +#endif + +#if USING_CUSTOMS_HEAD_ELEMENT + Blynk.setCustomsHeadElement(""); +#endif + +#if USING_CORS_FEATURE + Blynk.setCORSHeader("Your Access-Control-Allow-Origin"); +#endif + + ////////////////////////////////////////////// + // Use this to default DHCP hostname to ESP8266-XXXXXX or ESP32-XXXXXX //Blynk.begin(); // Use this to personalize DHCP hostname (RFC952 conformed) @@ -184,29 +206,25 @@ void setup() } #if USE_DYNAMIC_PARAMETERS -void displayCredentials(void) +void displayCredentials() { Serial.println(F("\nYour stored Credentials :")); - for (int i = 0; i < NUM_MENU_ITEMS; i++) + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) { - Serial.println(String(myMenuItems[i].displayName) + " = " + myMenuItems[i].pdata); + Serial.print(myMenuItems[i].displayName); + Serial.print(F(" = ")); + Serial.println(myMenuItems[i].pdata); } } -#endif -void loop() +void displayCredentialsInLoop() { - Blynk.run(); - timer.run(); - check_status(); - -#if USE_DYNAMIC_PARAMETERS static bool displayedCredentials = false; if (!displayedCredentials) { - for (int i = 0; i < NUM_MENU_ITEMS; i++) + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) { if (!strlen(myMenuItems[i].pdata)) { @@ -220,5 +238,17 @@ void loop() } } } +} + +#endif + +void loop() +{ + Blynk.run(); + timer.run(); + check_status(); + +#if USE_DYNAMIC_PARAMETERS + displayCredentialsInLoop(); #endif } diff --git a/examples/ESP8266WM_Config/defines.h b/examples/ESP8266WM_Config/defines.h index 3e593a8..809d478 100644 --- a/examples/ESP8266WM_Config/defines.h +++ b/examples/ESP8266WM_Config/defines.h @@ -29,9 +29,7 @@ // Select USE_LITTLEFS (higher priority) or USE_SPIFFS #define USE_LITTLEFS true -//#define USE_LITTLEFS false #define USE_SPIFFS false -//#define USE_SPIFFS true #if USE_LITTLEFS //LittleFS has higher priority @@ -52,15 +50,28 @@ #define EEPROM_START 768 #endif +///////////////////////////////////////////// + +// Add customs headers from v1.2.0 +#define USING_CUSTOMS_STYLE true +#define USING_CUSTOMS_HEAD_ELEMENT true +#define USING_CORS_FEATURE true + +///////////////////////////////////////////// + // Force some params in Blynk, only valid for library version 1.0.1 and later #define TIMEOUT_RECONNECT_WIFI 10000L #define RESET_IF_CONFIG_TIMEOUT true + #define CONFIG_TIMEOUT_RETRYTIMES_BEFORE_RESET 5 +// Config Timeout 120s (default 60s) +#define CONFIG_TIMEOUT 120000L + #define USE_DYNAMIC_PARAMETERS true -// Those above #define's must be placed before #include +// Those above #define's must be placed before #include and +////////////////////////////////////////// -//#define USE_SSL true #define USE_SSL false #if USE_SSL diff --git a/examples/ESP8266WM_ForcedConfig/ESP8266WM_ForcedConfig.ino b/examples/ESP8266WM_ForcedConfig/ESP8266WM_ForcedConfig.ino index a3f8993..fda1a3f 100644 --- a/examples/ESP8266WM_ForcedConfig/ESP8266WM_ForcedConfig.ino +++ b/examples/ESP8266WM_ForcedConfig/ESP8266WM_ForcedConfig.ino @@ -7,7 +7,7 @@ Forked from Blynk library v0.6.1 https://github.com/blynkkk/blynk-library/releases Built by Khoi Hoang https://github.com/khoih-prog/Blynk_WM Licensed under MIT license - Version: 1.1.3 + Version: 1.2.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -32,6 +32,7 @@ 1.1.1 K Hoang 16/01/2021 Add functions to control Config Portal from software or Virtual Switches 1.1.2 K Hoang 28/01/2021 Fix Config Portal and Dynamic Params bugs 1.1.3 K Hoang 31/01/2021 To permit autoreset after timeout if DRD/MRD or non-persistent forced-CP + 1.2.0 K Hoang 24/02/2021 Add customs HTML header feature and support to ESP32-S2. *****************************************************************************************************************************/ #include "defines.h" @@ -148,6 +149,11 @@ void check_status() } } +#if USING_CUSTOMS_STYLE +const char NewCustomsStyle[] /*PROGMEM*/ = ""; +#endif + void setup() { pinMode(PIN_LED, OUTPUT); @@ -191,6 +197,22 @@ void setup() //Blynk.setSTAStaticIPConfig(IPAddress(192, 168, 2, 220), IPAddress(192, 168, 2, 1), IPAddress(255, 255, 255, 0), // IPAddress(4, 4, 4, 4), IPAddress(8, 8, 8, 8)); +////////////////////////////////////////////// + +#if USING_CUSTOMS_STYLE + Blynk.setCustomsStyle(NewCustomsStyle); +#endif + +#if USING_CUSTOMS_HEAD_ELEMENT + Blynk.setCustomsHeadElement(""); +#endif + +#if USING_CORS_FEATURE + Blynk.setCORSHeader("Your Access-Control-Allow-Origin"); +#endif + + ////////////////////////////////////////////// + // Use this to default DHCP hostname to ESP8266-XXXXXX or ESP32-XXXXXX //Blynk.begin(); // Use this to personalize DHCP hostname (RFC952 conformed) @@ -217,29 +239,25 @@ void setup() } #if USE_DYNAMIC_PARAMETERS -void displayCredentials(void) +void displayCredentials() { Serial.println(F("\nYour stored Credentials :")); - for (int i = 0; i < NUM_MENU_ITEMS; i++) + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) { - Serial.println(String(myMenuItems[i].displayName) + " = " + myMenuItems[i].pdata); + Serial.print(myMenuItems[i].displayName); + Serial.print(F(" = ")); + Serial.println(myMenuItems[i].pdata); } } -#endif -void loop() +void displayCredentialsInLoop() { - Blynk.run(); - timer.run(); - check_status(); - -#if USE_DYNAMIC_PARAMETERS static bool displayedCredentials = false; if (!displayedCredentials) { - for (int i = 0; i < NUM_MENU_ITEMS; i++) + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) { if (!strlen(myMenuItems[i].pdata)) { @@ -253,5 +271,17 @@ void loop() } } } +} + +#endif + +void loop() +{ + Blynk.run(); + timer.run(); + check_status(); + +#if USE_DYNAMIC_PARAMETERS + displayCredentialsInLoop(); #endif } diff --git a/examples/ESP8266WM_ForcedConfig/defines.h b/examples/ESP8266WM_ForcedConfig/defines.h index cb7352b..b79b33d 100644 --- a/examples/ESP8266WM_ForcedConfig/defines.h +++ b/examples/ESP8266WM_ForcedConfig/defines.h @@ -13,7 +13,7 @@ #define defines_h #ifndef ESP8266 -#error This code is intended to run on the ESP8266 platform! Please check your Tools->Board setting. + #error This code is intended to run on the ESP8266 platform! Please check your Tools->Board setting. #endif #define BLYNK_PRINT Serial @@ -29,44 +29,56 @@ // Select USE_LITTLEFS (higher priority) or USE_SPIFFS #define USE_LITTLEFS true -//#define USE_LITTLEFS false #define USE_SPIFFS false -//#define USE_SPIFFS true #if USE_LITTLEFS -//LittleFS has higher priority -#define CurrentFileFS "LittleFS" -#ifdef USE_SPIFFS -#undef USE_SPIFFS -#endif -#define USE_SPIFFS false + //LittleFS has higher priority + #define CurrentFileFS "LittleFS" + #ifdef USE_SPIFFS + #undef USE_SPIFFS + #endif + #define USE_SPIFFS false #elif USE_SPIFFS -#define CurrentFileFS "SPIFFS" + #define CurrentFileFS "SPIFFS" #endif #if !( USE_LITTLEFS || USE_SPIFFS) -// EEPROM_SIZE must be <= 4096 and >= CONFIG_DATA_SIZE (currently 172 bytes) -#define EEPROM_SIZE (4 * 1024) -// EEPROM_START + CONFIG_DATA_SIZE must be <= EEPROM_SIZE -#define EEPROM_START 768 + // EEPROM_SIZE must be <= 4096 and >= CONFIG_DATA_SIZE (currently 172 bytes) + #define EEPROM_SIZE (4 * 1024) + // EEPROM_START + CONFIG_DATA_SIZE must be <= EEPROM_SIZE + #define EEPROM_START 768 #endif +///////////////////////////////////////////// + +// Add customs headers from v1.2.0 +#define USING_CUSTOMS_STYLE true +#define USING_CUSTOMS_HEAD_ELEMENT true +#define USING_CORS_FEATURE true + +///////////////////////////////////////////// + // Force some params in Blynk, only valid for library version 1.0.1 and later #define TIMEOUT_RECONNECT_WIFI 10000L #define RESET_IF_CONFIG_TIMEOUT true + #define CONFIG_TIMEOUT_RETRYTIMES_BEFORE_RESET 5 -#define USE_DYNAMIC_PARAMETERS true -// Those above #define's must be placed before #include +// Config Timeout 120s (default 60s) +#define CONFIG_TIMEOUT 120000L + +#define USE_DYNAMIC_PARAMETERS true +// Those above #define's must be placed before #include and +////////////////////////////////////////// //#define USE_SSL true #define USE_SSL false #if USE_SSL -#include + #include #else -#include + #include #endif #define PIN_LED 2 // Pin D4 mapped to pin GPIO2/TXD1 of ESP8266, NodeMCU and WeMoS, control on-board LED diff --git a/examples/ESP8266WM_MRD_Config/ESP8266WM_MRD_Config.ino b/examples/ESP8266WM_MRD_Config/ESP8266WM_MRD_Config.ino index c9df984..ec0c172 100644 --- a/examples/ESP8266WM_MRD_Config/ESP8266WM_MRD_Config.ino +++ b/examples/ESP8266WM_MRD_Config/ESP8266WM_MRD_Config.ino @@ -7,7 +7,7 @@ Forked from Blynk library v0.6.1 https://github.com/blynkkk/blynk-library/releases Built by Khoi Hoang https://github.com/khoih-prog/Blynk_WM Licensed under MIT license - Version: 1.1.3 + Version: 1.2.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -32,6 +32,7 @@ 1.1.1 K Hoang 16/01/2021 Add functions to control Config Portal from software or Virtual Switches 1.1.2 K Hoang 28/01/2021 Fix Config Portal and Dynamic Params bugs 1.1.3 K Hoang 31/01/2021 To permit autoreset after timeout if DRD/MRD or non-persistent forced-CP + 1.2.0 K Hoang 24/02/2021 Add customs HTML header feature and support to ESP32-S2. ********************************************************************************************************************************/ #include "defines.h" @@ -115,6 +116,11 @@ void check_status() } } +#if USING_CUSTOMS_STYLE +const char NewCustomsStyle[] /*PROGMEM*/ = ""; +#endif + void setup() { pinMode(PIN_LED, OUTPUT); @@ -163,6 +169,22 @@ void setup() //Blynk.setSTAStaticIPConfig(IPAddress(192, 168, 2, 220), IPAddress(192, 168, 2, 1), IPAddress(255, 255, 255, 0), // IPAddress(4, 4, 4, 4), IPAddress(8, 8, 8, 8)); +////////////////////////////////////////////// + +#if USING_CUSTOMS_STYLE + Blynk.setCustomsStyle(NewCustomsStyle); +#endif + +#if USING_CUSTOMS_HEAD_ELEMENT + Blynk.setCustomsHeadElement(""); +#endif + +#if USING_CORS_FEATURE + Blynk.setCORSHeader("Your Access-Control-Allow-Origin"); +#endif + + ////////////////////////////////////////////// + // Use this to default DHCP hostname to ESP8266-XXXXXX or ESP32-XXXXXX //Blynk.begin(); // Use this to personalize DHCP hostname (RFC952 conformed) @@ -193,25 +215,21 @@ void displayCredentials() { Serial.println(F("\nYour stored Credentials :")); - for (int i = 0; i < NUM_MENU_ITEMS; i++) + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) { - Serial.println(String(myMenuItems[i].displayName) + " = " + myMenuItems[i].pdata); + Serial.print(myMenuItems[i].displayName); + Serial.print(F(" = ")); + Serial.println(myMenuItems[i].pdata); } } -#endif -void loop() +void displayCredentialsInLoop() { - Blynk.run(); - timer.run(); - check_status(); - -#if USE_DYNAMIC_PARAMETERS static bool displayedCredentials = false; if (!displayedCredentials) { - for (int i = 0; i < NUM_MENU_ITEMS; i++) + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) { if (!strlen(myMenuItems[i].pdata)) { @@ -225,5 +243,17 @@ void loop() } } } +} + +#endif + +void loop() +{ + Blynk.run(); + timer.run(); + check_status(); + +#if USE_DYNAMIC_PARAMETERS + displayCredentialsInLoop(); #endif } diff --git a/examples/ESP8266WM_MRD_Config/defines.h b/examples/ESP8266WM_MRD_Config/defines.h index 5261d1a..add7de4 100644 --- a/examples/ESP8266WM_MRD_Config/defines.h +++ b/examples/ESP8266WM_MRD_Config/defines.h @@ -61,44 +61,56 @@ // Select USE_LITTLEFS (higher priority) or USE_SPIFFS #define USE_LITTLEFS true -//#define USE_LITTLEFS false #define USE_SPIFFS false -//#define USE_SPIFFS true #if USE_LITTLEFS -//LittleFS has higher priority -#define CurrentFileFS "LittleFS" -#ifdef USE_SPIFFS -#undef USE_SPIFFS -#endif -#define USE_SPIFFS false + //LittleFS has higher priority + #define CurrentFileFS "LittleFS" + #ifdef USE_SPIFFS + #undef USE_SPIFFS + #endif + #define USE_SPIFFS false #elif USE_SPIFFS -#define CurrentFileFS "SPIFFS" + #define CurrentFileFS "SPIFFS" #endif #if !( USE_LITTLEFS || USE_SPIFFS) -// EEPROM_SIZE must be <= 4096 and >= CONFIG_DATA_SIZE (currently 172 bytes) -#define EEPROM_SIZE (4 * 1024) -// EEPROM_START + CONFIG_DATA_SIZE must be <= EEPROM_SIZE -#define EEPROM_START 768 + // EEPROM_SIZE must be <= 4096 and >= CONFIG_DATA_SIZE (currently 172 bytes) + #define EEPROM_SIZE (4 * 1024) + // EEPROM_START + CONFIG_DATA_SIZE must be <= EEPROM_SIZE + #define EEPROM_START 768 #endif +///////////////////////////////////////////// + +// Add customs headers from v1.2.0 +#define USING_CUSTOMS_STYLE true +#define USING_CUSTOMS_HEAD_ELEMENT true +#define USING_CORS_FEATURE true + +///////////////////////////////////////////// + // Force some params in Blynk, only valid for library version 1.0.1 and later #define TIMEOUT_RECONNECT_WIFI 10000L #define RESET_IF_CONFIG_TIMEOUT true + #define CONFIG_TIMEOUT_RETRYTIMES_BEFORE_RESET 5 +// Config Timeout 120s (default 60s) +#define CONFIG_TIMEOUT 120000L + #define USE_DYNAMIC_PARAMETERS true -// Those above #define's must be placed before #include +// Those above #define's must be placed before #include and +////////////////////////////////////////// //#define USE_SSL true #define USE_SSL false #if USE_SSL -#include + #include #else -#include + #include #endif #define PIN_LED 2 // Pin D4 mapped to pin GPIO2/TXD1 of ESP8266, NodeMCU and WeMoS, control on-board LED diff --git a/examples/ESP8266WM_MRD_ForcedConfig/ESP8266WM_MRD_ForcedConfig.ino b/examples/ESP8266WM_MRD_ForcedConfig/ESP8266WM_MRD_ForcedConfig.ino index 223cb7d..7abd1e1 100644 --- a/examples/ESP8266WM_MRD_ForcedConfig/ESP8266WM_MRD_ForcedConfig.ino +++ b/examples/ESP8266WM_MRD_ForcedConfig/ESP8266WM_MRD_ForcedConfig.ino @@ -148,6 +148,11 @@ void check_status() } } +#if USING_CUSTOMS_STYLE +const char NewCustomsStyle[] /*PROGMEM*/ = ""; +#endif + void setup() { pinMode(PIN_LED, OUTPUT); @@ -196,6 +201,22 @@ void setup() //Blynk.setSTAStaticIPConfig(IPAddress(192, 168, 2, 220), IPAddress(192, 168, 2, 1), IPAddress(255, 255, 255, 0), // IPAddress(4, 4, 4, 4), IPAddress(8, 8, 8, 8)); +////////////////////////////////////////////// + +#if USING_CUSTOMS_STYLE + Blynk.setCustomsStyle(NewCustomsStyle); +#endif + +#if USING_CUSTOMS_HEAD_ELEMENT + Blynk.setCustomsHeadElement(""); +#endif + +#if USING_CORS_FEATURE + Blynk.setCORSHeader("Your Access-Control-Allow-Origin"); +#endif + + ////////////////////////////////////////////// + // Use this to default DHCP hostname to ESP8266-XXXXXX or ESP32-XXXXXX //Blynk.begin(); // Use this to personalize DHCP hostname (RFC952 conformed) diff --git a/examples/ESP8266WM_MRD_ForcedConfig/defines.h b/examples/ESP8266WM_MRD_ForcedConfig/defines.h index 5261d1a..27507cb 100644 --- a/examples/ESP8266WM_MRD_ForcedConfig/defines.h +++ b/examples/ESP8266WM_MRD_ForcedConfig/defines.h @@ -61,44 +61,56 @@ // Select USE_LITTLEFS (higher priority) or USE_SPIFFS #define USE_LITTLEFS true -//#define USE_LITTLEFS false #define USE_SPIFFS false -//#define USE_SPIFFS true #if USE_LITTLEFS -//LittleFS has higher priority -#define CurrentFileFS "LittleFS" -#ifdef USE_SPIFFS -#undef USE_SPIFFS -#endif -#define USE_SPIFFS false + //LittleFS has higher priority + #define CurrentFileFS "LittleFS" + #ifdef USE_SPIFFS + #undef USE_SPIFFS + #endif + #define USE_SPIFFS false #elif USE_SPIFFS -#define CurrentFileFS "SPIFFS" + #define CurrentFileFS "SPIFFS" #endif #if !( USE_LITTLEFS || USE_SPIFFS) -// EEPROM_SIZE must be <= 4096 and >= CONFIG_DATA_SIZE (currently 172 bytes) -#define EEPROM_SIZE (4 * 1024) -// EEPROM_START + CONFIG_DATA_SIZE must be <= EEPROM_SIZE -#define EEPROM_START 768 + // EEPROM_SIZE must be <= 4096 and >= CONFIG_DATA_SIZE (currently 172 bytes) + #define EEPROM_SIZE (4 * 1024) + // EEPROM_START + CONFIG_DATA_SIZE must be <= EEPROM_SIZE + #define EEPROM_START 768 #endif +///////////////////////////////////////////// + +// Add customs headers from v1.2.0 +#define USING_CUSTOMS_STYLE true +#define USING_CUSTOMS_HEAD_ELEMENT true +#define USING_CORS_FEATURE true + +///////////////////////////////////////////// + // Force some params in Blynk, only valid for library version 1.0.1 and later #define TIMEOUT_RECONNECT_WIFI 10000L #define RESET_IF_CONFIG_TIMEOUT true + #define CONFIG_TIMEOUT_RETRYTIMES_BEFORE_RESET 5 +// Config Timeout 120s (default 60s) +#define CONFIG_TIMEOUT 120000L + #define USE_DYNAMIC_PARAMETERS true -// Those above #define's must be placed before #include +// Those above #define's must be placed before #include and +////////////////////////////////////////// -//#define USE_SSL true -#define USE_SSL false +#define USE_SSL true +//#define USE_SSL false #if USE_SSL -#include + #include #else -#include + #include #endif #define PIN_LED 2 // Pin D4 mapped to pin GPIO2/TXD1 of ESP8266, NodeMCU and WeMoS, control on-board LED diff --git a/keywords.txt b/keywords.txt index a09536e..a174884 100644 --- a/keywords.txt +++ b/keywords.txt @@ -1,6 +1,7 @@ ####################################### # Data types (KEYWORD1) ####################################### + Blynk KEYWORD1 BlynkTimer KEYWORD1 WidgetBridge KEYWORD1 @@ -21,6 +22,7 @@ Blynk_WM_Configuration KEYWORD1 ####################################### # Methods and Functions (KEYWORD2) ####################################### + begin KEYWORD2 config KEYWORD2 run KEYWORD2 @@ -57,8 +59,17 @@ getFullConfigData KEYWORD2 clearConfigData KEYWORD2 resetAndEnterConfigPortal KEYWORD2 resetAndEnterConfigPortalPersistent KEYWORD2 +setCustomsStyle KEYWORD2 +getCustomsStyle KEYWORD2 +setCustomsHeadElement KEYWORD2 +getCustomsHeadElement KEYWORD2 +setCORSHeader KEYWORD2 +getCORSHeader KEYWORD2 + +############################# +# Handler helpers (KEYWORD2) +############################# -# Handler helpers BLYNK_READ KEYWORD2 BLYNK_WRITE KEYWORD2 BLYNK_READ_DEFAULT KEYWORD2 @@ -74,13 +85,19 @@ BLYNK_INPUT KEYWORD2 BLYNK_OUTPUT_DEFAULT KEYWORD2 BLYNK_INPUT_DEFAULT KEYWORD2 -# Variables binding +############################### +# Variables binding (KEYWORD2) +############################### + BLYNK_VAR_INT KEYWORD2 BLYNK_VAR_LONG KEYWORD2 BLYNK_VAR_DOUBLE KEYWORD2 BLYNK_VAR_STRING KEYWORD2 -# Special defines +############################### +# Special defines (KEYWORD2) +############################### + BLYNK_DEBUG KEYWORD2 BLYNK_DEBUG_ALL KEYWORD2 BLYNK_PRINT KEYWORD2 @@ -95,7 +112,9 @@ BLYNK_USE_DIRECT_CONNECT KEYWORD2 BLYNK_MAX_SENDBYTES KEYWORD2 BLYNK_MAX_READBYTES KEYWORD2 -# Periodic actions +############################### +# Periodic actions (KEYWORD2) +############################### #BLYNK_EVERY_N_MILLIS KEYWORD2 #BLYNK_EVERY_N_SECONDS KEYWORD2 #BLYNK_EVERY_N_MINUTES KEYWORD2 diff --git a/library.json b/library.json index d08a9d2..ebb18b7 100644 --- a/library.json +++ b/library.json @@ -1,8 +1,8 @@ { "name": "Blynk_WM", - "version": "1.1.3", - "description": "Library for configuring/auto(re)connecting ESP8266/ESP32 modules to best or available MultiWiFi APs and MultiBlynk servers at runtime. Enable adding dynamic custom parameters from sketch and input using the same Config Portal. Config Portal will be auto-adjusted to match the number of dynamic parameters. Optional default Credentials to be autoloaded into Config Portal to use or change instead of manually input. Static STA IP and DHCP Hostname as well as Config Portal AP channel, IP, SSID, Password can be configured. Multi, Double DetectDetector or Virtual ConfigPortal Switch feature permits entering Config Portal as requested.", - "keywords": "sensors, control, device, smartphone, mobile, app, web, cloud, communication, protocol, iot, m2m, wifi, ble, bluetooth, ethernet, usb, serial, gsm, gprs, 3g, data, esp8266, esp32, http, drd, mrd, double-reset, multi-reset, configportal, portal, credentials", + "version": "1.2.0", + "description": "Library for configuring/auto(re)connecting ESP32 (including ESP32-S2), ESP8266 modules to best or available MultiWiFi APs and MultiBlynk servers at runtime. Enable adding dynamic custom parameters from sketch and input using the same Config Portal (CP). CP will be auto-adjusted to match the number of dynamic parameters. Optional default Credentials to be autoloaded into CP to use or change instead of manually input. Static STA IP and DHCP Hostname as well as Config Portal AP channel, IP, SSID, Password can be configured. Config. Data saved in ESP8266/ESP32 LittleFS, SPIFFS or EEPROM. Multi, Double DetectDetector or Virtual CP Switch feature permits entering CP as requested. Configurable Customs HTML Headers, including Customs Style, Customs Head Elements, CORS Header.", + "keywords": "sensors, control, device, smartphone, mobile, app, web, cloud, communication, protocol, iot, m2m, wifi, ble, bluetooth, ethernet, usb, serial, gsm, gprs, 3g, data, esp8266, esp32, esp32-s2, http, drd, mrd, double-reset, multi-reset, configportal, config-portal, portal, credentials, Manager, DynamicParameters, dynamic-params, dynamic, customs-header", "authors": { "name": "Khoi Hoang", diff --git a/library.properties b/library.properties index b2fd21d..a3bec29 100644 --- a/library.properties +++ b/library.properties @@ -1,10 +1,10 @@ name=Blynk_WiFiManager -version=1.1.3 +version=1.2.0 author=Khoi Hoang license=MIT maintainer=Khoi Hoang -sentence=Simple WiFiManager for Blynk and ESP8266/ESP32 with or without SSL, configuration data saved in either LittleFS, SPIFFS or EEPROM -paragraph=Library for configuring/auto(re)connecting ESP8266/ESP32 modules to best or available MultiWiFi APs and MultiBlynk servers at runtime. Enable adding dynamic custom parameters from sketch and input using the same Config Portal. Config Portal will be auto-adjusted to match the number of dynamic parameters. Optional default Credentials to be autoloaded into Config Portal to use or change instead of manually input. Static STA IP and DHCP Hostname as well as Config Portal AP channel, IP, SSID, Password can be configured. Multi, Double DetectDetector or Virtual ConfigPortal Switch feature permits entering Config Portal as requested. +sentence=Simple WiFiManager for Blynk and ESP32 (including ESP32-S2), ESP8266 with or without SSL, configuration data saved in either LittleFS, SPIFFS or EEPROM +paragraph=Library for configuring/auto(re)connecting ESP32 (including ESP32-S2), ESP8266 modules to best or available MultiWiFi APs and MultiBlynk servers at runtime. Enable adding dynamic custom parameters from sketch and input using the same Config Portal. Config Portal will be auto-adjusted to match the number of dynamic parameters. Optional default Credentials to be autoloaded into Config Portal to use or change instead of manually input. Static STA IP and DHCP Hostname as well as Config Portal AP channel, IP, SSID, Password can be configured. Multi, Double DetectDetector or Virtual ConfigPortal Switch feature permits entering Config Portal as requested. Configurable Customs HTML Headers, including Customs Style, Customs Head Elements, CORS Header. category=Communication url=https://github.com/khoih-prog/Blynk_WM architectures=esp8266,esp32 diff --git a/pics/esp32_s2_Core_Unzipped.png b/pics/esp32_s2_Core_Unzipped.png new file mode 100644 index 0000000000000000000000000000000000000000..76286a20ed5d904cfefaa4e97ff5d6294cfdd67d GIT binary patch literal 45169 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ℑ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 literal 0 HcmV?d00001 diff --git a/pics/esp32_s2_Toolchain.png b/pics/esp32_s2_Toolchain.png new file mode 100644 index 0000000000000000000000000000000000000000..823c235aa3bb8a294111f4f36d37515f8e1e8fe0 GIT binary patch literal 22374 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 Pqz$X1rKbtku>0d*Y{So^ literal 0 HcmV?d00001 diff --git a/pics/esp32_s2_esptool.png b/pics/esp32_s2_esptool.png new file mode 100644 index 0000000000000000000000000000000000000000..a9075ebb5c6dd4d03bdac630bb24cfe07ace01b0 GIT binary patch literal 46738 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

8Yud1gI%Kro*@FfsEj9MO!fGU zX;W?DK~rSxQTkHi4u#Q3S;<5&gs7tPMEp}1Lh3T^u(YcNw+Ztf(&Zp_{mXgx_hgt68SENED(; zz?hADkRy}RX{JQMQc0ReemudQMmgO3mVsDIx0O&b65H9y^Ko~MZ^YkkTPOPMGxas^ z4>`v4-La<;#N^ix<1K)LaV9CmwIdvo$d{n(Ogr82J@Y*%6MkPV9B-=avC9n79=E z6X7O^xk-He6dAFKdEh)Ms>QzFo=|d(*CwXK@IvyXW>N6iQd3pIptOe! zcI{V5Xo{jo8plqvzpLMeE$qvyBXSI@dapy#N-nEZ>tydv} zkpIQa!xKW8^UUYx!ouMk1D5$|M(PZtmmrJOub+2RX_ym+TTh zjxaSTVTXK+_lo$=Y4A_)gV()C7_Bj`8%YkgqNu5`TZ_ZZZH@~oL0xm2trsFTQJaVL zY8$2DJ`=189%BvF4&(Q%9X?-M+Nkq1fmeO6zEtTQv~-W(1Fd_xREuc*gvivoU8)T~ zEjDv6(ttXWn)wv`YNRk7ltAzUaQ>4DcCS+U`64@Dr1q=S5P1+Ex16C0mV9Z4R2NP{ z#Vud7j#LQU1#Coyy5c9h|LKH0n!7tJ>JqEqxUfjh(u_N_MVaFZ3C&VfQ}HJ&*}pyp zn+t!=CLu!2Yf5gN>uZ*0qkbkj(AB_O{Wj1sVkuzGF+D9^6PK-Rnp8cLM%}_O;hNnk zkn>d|1y&@O6Xmk`$}lic@pDr&IU!T4mX=Kd@GetYNd691#)Vc2Z?NyBd1tASljm7m ze;yevYUdGPWo;c8hyqBx&4?v1yn#HmDR%Vn%%n@}7STLnTVnlmn?xy4wHDPbtQ+fm zqbzM|%0OIPocWrp+tizmbf*c|VJo0wFNHnZ|1KDX+h7k$xd+gvvgyV3ja);l=Z$*d z$jh)9=(q9w#~#$O{m+~#m>0h(0hcdW>ja>mMBfE=34%Tc>9Bn&Ws zArJ@y1B17>Hwc6RR4jqgYe4Ym59(=R2~BBGc3J zKHdI({hs{^4bmq(D$02{r+u`nps%}8-!!4zeB$GG{AQ~*%NFCssiY!GIUbb){_QV^PQPbHv~;v$&ifr*s^SU2RNkm9 zwF6lhSX`X(>XWC(Mxk?QUu0w-Z@Lt#cF)E&LK*Zq9*S`?N={AZaguh(#vD-O=v{3~ z_bX`^R#eNC_HQs%jd>hR^=%8Wd>b$QyOtKj4&QYUrlx?4=|Uk~-$bsoNfWN_KXp;` z9}i0AS^Fl+kNWH|1`ZFr?$18~&WNbaClB4B3J6Zj&3$z_$=-LBGXx|j32BsG{-wj? zE2^jx0kU00xWr%w@|H}zqQHeszN*0Uln5QIkCeVE$)I$yr`4?GfVQ)B+HvTsR!n{w z1ezu=b@Rl>I9r>WHVX>c7usz|PAjn5O(TTvq97UYR_XOtF(JYigqlglSz4%SpeRub zh9nX1uqG;VS~8Ya3?eIRJlD2Qwl3i3Q*yp;j~F&6t7~BJPd3m+&vOLiuBh%D1Jh6%ywUZ7!I}tWh^BOP_ua0vlD->3jVV2F) z88*FcF}Q{vC_~tv^mKVt9dxmk-Lv%eu`Xc%B#e^Y>yiK1^5p~>2pvr~DW^{yhHOT1 za&hY2W%&YC?bD(Aj0o6UqkD2X!EBl9^~GN}dw(NgON*P?xX^BSsnGrG%yP2W77&{T z`fna2RV2v8wE;MTdU|>Y|NT?k79i055NuNC2CP7zx8E<0jn&F7DBuAI_P*P%C@4g_ zcg08aUJ$cyCCtC zAZ256Board setting. #endif -#define BLYNK_WM_VERSION "Blynk_WM SSL for ESP32 v1.1.3" +#define BLYNK_WM_VERSION "Blynk_WM SSL for ESP32 v1.2.0" #if defined(BLYNK_SSL_USE_LETSENCRYPT) -static const char BLYNK_DEFAULT_ROOT_CA[] = -#include + static const char BLYNK_DEFAULT_ROOT_CA[] = + #include #else -static const char BLYNK_DEFAULT_ROOT_CA[] = -#include + static const char BLYNK_DEFAULT_ROOT_CA[] = + #include #endif #include @@ -272,9 +273,13 @@ typedef struct } MenuItem; #if USE_DYNAMIC_PARAMETERS + #warning Using Dynamic Parameters + ///NEW extern uint16_t NUM_MENU_ITEMS; extern MenuItem myMenuItems []; bool *menuItemUpdated = NULL; +#else + #warning Not using Dynamic Parameters #endif #define SSID_MAX_LEN 32 @@ -301,15 +306,20 @@ typedef struct // Configurable items besides fixed Header #define NUM_CONFIGURABLE_ITEMS ( 2 + (2 * NUM_WIFI_CREDENTIALS) + (2 * NUM_BLYNK_CREDENTIALS) ) + +#define HEADER_MAX_LEN 16 +#define BOARD_NAME_MAX_LEN 24 + typedef struct Configuration { - char header [16]; + char header [HEADER_MAX_LEN]; WiFi_Credentials WiFi_Creds [NUM_WIFI_CREDENTIALS]; Blynk_Credentials Blynk_Creds [NUM_BLYNK_CREDENTIALS]; int blynk_port; - char board_name [24]; + char board_name [BOARD_NAME_MAX_LEN]; int checkSum; } Blynk_WM_Configuration; + // Currently CONFIG_DATA_SIZE = ( 48 + (96 * NUM_WIFI_CREDENTIALS) + (68 * NUM_BLYNK_CREDENTIALS) ) = 376 uint16_t CONFIG_DATA_SIZE = sizeof(Blynk_WM_Configuration); @@ -320,6 +330,27 @@ extern Blynk_WM_Configuration defaultConfig; //From v1.0.10, Permit special chars such as # and % +#if 1 +// -- HTML page fragments + +const char BLYNK_WM_HTML_HEAD_START[] /*PROGMEM*/ = "BlynkSimpleEsp32_SSL_WM"; + +const char BLYNK_WM_HTML_HEAD_STYLE[] /*PROGMEM*/ = ""; + +const char BLYNK_WM_HTML_HEAD_END[] /*PROGMEM*/ = "

\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ +
"; + +#else + // -- HTML page fragments const char BLYNK_WM_HTML_HEAD[] /*PROGMEM*/ = "BlynkSimpleEsp32_SSL_WM"; + void setCustomsStyle(const char* CustomsStyle = BLYNK_WM_HTML_HEAD_STYLE) + { + BLYNK_WM_HTML_HEAD_CUSTOMS_STYLE = CustomsStyle; + BLYNK_LOG2(F("Set CustomsStyle to : "), BLYNK_WM_HTML_HEAD_CUSTOMS_STYLE); + } + + ////////////////////////////////////// + + const char* getCustomsStyle() + { + BLYNK_LOG2(F("Get CustomsStyle = "), BLYNK_WM_HTML_HEAD_CUSTOMS_STYLE); + return BLYNK_WM_HTML_HEAD_CUSTOMS_STYLE; + } +#endif + + ////////////////////////////////////// + +#if USING_CUSTOMS_HEAD_ELEMENT + //sets a custom element to add to head, like a new style tag + void setCustomsHeadElement(const char* CustomsHeadElement = NULL) + { + _CustomsHeadElement = CustomsHeadElement; + BLYNK_LOG2(F("Set CustomsHeadElement to : "), _CustomsHeadElement); + } + + ////////////////////////////////////// + + const char* getCustomsHeadElement() + { + BLYNK_LOG2(F("Get CustomsHeadElement = "), _CustomsHeadElement); + return _CustomsHeadElement; + } +#endif + + ////////////////////////////////////// + +#if USING_CORS_FEATURE + void setCORSHeader(const char* CORSHeaders = NULL) + { + _CORS_Header = CORSHeaders; + + BLYNK_LOG2(F("Set CORS Header to : "), _CORS_Header); + } + + ////////////////////////////////////// + + const char* getCORSHeader() + { + BLYNK_LOG2(F("Get CORS Header = "), _CORS_Header); + return _CORS_Header; + } +#endif + + ////////////////////////////////////// + private: + WebServer *server; bool configuration_mode = false; @@ -918,7 +1044,26 @@ class BlynkWifi IPAddress static_DNS1 = IPAddress(0, 0, 0, 0); IPAddress static_DNS2 = IPAddress(0, 0, 0, 0); +///////////////////////////////////// + + // Add customs headers from v1.2.0 + +#if USING_CUSTOMS_STYLE + const char* BLYNK_WM_HTML_HEAD_CUSTOMS_STYLE = NULL; +#endif + +#if USING_CUSTOMS_HEAD_ELEMENT + const char* _CustomsHeadElement = NULL; +#endif + +#if USING_CORS_FEATURE + const char* _CORS_Header = WM_HTTP_CORS_ALLOW_ALL; //"*"; +#endif + + ////////////////////////////////////// + #define RFC952_HOSTNAME_MAXLEN 24 + char RFC952_hostname[RFC952_HOSTNAME_MAXLEN + 1]; char* getRFC952_hostname(const char* iHostname) @@ -2039,12 +2184,30 @@ class BlynkWifi } ////////////////////////////////////////////// - - void createHTML(String &root_html_template) + + // NEW + void createHTML(String& root_html_template) { String pitem; - root_html_template = String(BLYNK_WM_HTML_HEAD) + BLYNK_WM_FLDSET_START; + root_html_template = BLYNK_WM_HTML_HEAD_START; + + #if USING_CUSTOMS_STYLE + // Using Customs style when not NULL + if (BLYNK_WM_HTML_HEAD_CUSTOMS_STYLE) + root_html_template += BLYNK_WM_HTML_HEAD_CUSTOMS_STYLE; + else + root_html_template += BLYNK_WM_HTML_HEAD_STYLE; + #else + root_html_template += BLYNK_WM_HTML_HEAD_STYLE; + #endif + + #if USING_CUSTOMS_HEAD_ELEMENT + if (_CustomsHeadElement) + root_html_template += _CustomsHeadElement; + #endif + + root_html_template += String(BLYNK_WM_HTML_HEAD_END) + BLYNK_WM_FLDSET_START; #if USE_DYNAMIC_PARAMETERS for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) @@ -2076,6 +2239,26 @@ class BlynkWifi return; } + + ////////////////////////////////////////////// + + void serverSendHeaders() + { + BLYNK_LOG4(F("serverSendHeaders:WM_HTTP_CACHE_CONTROL:"), WM_HTTP_CACHE_CONTROL, "=", WM_HTTP_NO_STORE); + server->sendHeader(WM_HTTP_CACHE_CONTROL, WM_HTTP_NO_STORE); + +#if USING_CORS_FEATURE + // New from v1.2.0, for configure CORS Header, default to WM_HTTP_CORS_ALLOW_ALL = "*" + BLYNK_LOG4(F("serverSendHeaders:WM_HTTP_CORS:"), WM_HTTP_CORS, " : ", _CORS_Header); + server->sendHeader(WM_HTTP_CORS, _CORS_Header); +#endif + + BLYNK_LOG4(F("serverSendHeaders:WM_HTTP_PRAGMA:"), WM_HTTP_PRAGMA, " : ", WM_HTTP_NO_CACHE); + server->sendHeader(WM_HTTP_PRAGMA, WM_HTTP_NO_CACHE); + + BLYNK_LOG4(F("serverSendHeaders:WM_HTTP_EXPIRES:"), WM_HTTP_EXPIRES, " : ", "-1"); + server->sendHeader(WM_HTTP_EXPIRES, "-1"); + } ////////////////////////////////////////////// @@ -2090,10 +2273,14 @@ class BlynkWifi if (key == "" && value == "") { + // New from v1.2.0 + serverSendHeaders(); + ////// + String result; createHTML(result); - //BLYNK_LOG1(BLYNK_F("hR: replace result")); + //BLYNK_LOG1(BLYNK_F("h:repl")); // Reset configTimeout to stay here until finished. configTimeout = 0; @@ -2101,12 +2288,12 @@ class BlynkWifi if ( RFC952_hostname[0] != 0 ) { // Replace only if Hostname is valid - result.replace("BlynkSimpleEsp32_WM", RFC952_hostname); + result.replace("BlynkSimpleEsp32_SSL_WM", RFC952_hostname); } else if ( BlynkESP32_WM_config.board_name[0] != 0 ) { // Or replace only if board_name is valid. Otherwise, keep intact - result.replace("BlynkSimpleEsp32_WM", BlynkESP32_WM_config.board_name); + result.replace("BlynkSimpleEsp32_SSL_WM", BlynkESP32_WM_config.board_name); } result.replace("[[id]]", BlynkESP32_WM_config.WiFi_Creds[0].wifi_ssid); @@ -2119,7 +2306,7 @@ class BlynkWifi result.replace("[[tk1]]", BlynkESP32_WM_config.Blynk_Creds[1].blynk_token); result.replace("[[pt]]", String(BlynkESP32_WM_config.blynk_port)); result.replace("[[nm]]", BlynkESP32_WM_config.board_name); - + #if USE_DYNAMIC_PARAMETERS // Load default configuration for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) @@ -2132,7 +2319,7 @@ class BlynkWifi } #endif - server->send(200, "text/html", result); + server->send(200, WM_HTTP_HEAD_TEXT_HTML, result); return; } @@ -2178,97 +2365,127 @@ class BlynkWifi static bool pt_Updated = false; static bool nm_Updated = false; - if (!id_Updated && (key == String("id"))) + if (key == String("id")) { - id_Updated = true; - number_items_Updated++; + if (!id_Updated) + { + id_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(BlynkESP32_WM_config.WiFi_Creds[0].wifi_ssid) - 1) strcpy(BlynkESP32_WM_config.WiFi_Creds[0].wifi_ssid, value.c_str()); else strncpy(BlynkESP32_WM_config.WiFi_Creds[0].wifi_ssid, value.c_str(), sizeof(BlynkESP32_WM_config.WiFi_Creds[0].wifi_ssid) - 1); } - else if (!pw_Updated && (key == String("pw"))) + else if (key == String("pw")) { - pw_Updated = true; - number_items_Updated++; + if (!pw_Updated) + { + pw_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(BlynkESP32_WM_config.WiFi_Creds[0].wifi_pw) - 1) strcpy(BlynkESP32_WM_config.WiFi_Creds[0].wifi_pw, value.c_str()); else strncpy(BlynkESP32_WM_config.WiFi_Creds[0].wifi_pw, value.c_str(), sizeof(BlynkESP32_WM_config.WiFi_Creds[0].wifi_pw) - 1); } - else if (!id1_Updated && (key == String("id1"))) + else if (key == String("id1")) { - id1_Updated = true; - number_items_Updated++; + if (!id1_Updated) + { + id1_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(BlynkESP32_WM_config.WiFi_Creds[1].wifi_ssid) - 1) strcpy(BlynkESP32_WM_config.WiFi_Creds[1].wifi_ssid, value.c_str()); else strncpy(BlynkESP32_WM_config.WiFi_Creds[1].wifi_ssid, value.c_str(), sizeof(BlynkESP32_WM_config.WiFi_Creds[1].wifi_ssid) - 1); } - else if (!pw1_Updated && (key == String("pw1"))) + else if (key == String("pw1")) { - pw1_Updated = true; - number_items_Updated++; + if (!pw1_Updated) + { + pw1_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(BlynkESP32_WM_config.WiFi_Creds[1].wifi_pw) - 1) strcpy(BlynkESP32_WM_config.WiFi_Creds[1].wifi_pw, value.c_str()); else strncpy(BlynkESP32_WM_config.WiFi_Creds[1].wifi_pw, value.c_str(), sizeof(BlynkESP32_WM_config.WiFi_Creds[1].wifi_pw) - 1); } - else if (!sv_Updated && (key == String("sv"))) + else if (key == String("sv")) { - sv_Updated = true; - number_items_Updated++; + if (!sv_Updated) + { + sv_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(BlynkESP32_WM_config.Blynk_Creds[0].blynk_server) - 1) strcpy(BlynkESP32_WM_config.Blynk_Creds[0].blynk_server, value.c_str()); else strncpy(BlynkESP32_WM_config.Blynk_Creds[0].blynk_server, value.c_str(), sizeof(BlynkESP32_WM_config.Blynk_Creds[0].blynk_server) - 1); } - else if (!tk_Updated && (key == String("tk"))) + else if (key == String("tk")) { - tk_Updated = true; - number_items_Updated++; + if (!tk_Updated) + { + tk_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(BlynkESP32_WM_config.Blynk_Creds[0].blynk_token) - 1) strcpy(BlynkESP32_WM_config.Blynk_Creds[0].blynk_token, value.c_str()); else strncpy(BlynkESP32_WM_config.Blynk_Creds[0].blynk_token, value.c_str(), sizeof(BlynkESP32_WM_config.Blynk_Creds[0].blynk_token) - 1); } - else if (!sv1_Updated && (key == String("sv1"))) + else if (key == String("sv1")) { - sv1_Updated = true; - number_items_Updated++; + if (!sv1_Updated) + { + sv1_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(BlynkESP32_WM_config.Blynk_Creds[1].blynk_server) - 1) strcpy(BlynkESP32_WM_config.Blynk_Creds[1].blynk_server, value.c_str()); else strncpy(BlynkESP32_WM_config.Blynk_Creds[1].blynk_server, value.c_str(), sizeof(BlynkESP32_WM_config.Blynk_Creds[1].blynk_server) - 1); } - else if (!tk1_Updated && (key == String("tk1"))) + else if (key == String("tk1")) { - tk1_Updated = true; - number_items_Updated++; + if (!tk1_Updated) + { + tk1_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(BlynkESP32_WM_config.Blynk_Creds[1].blynk_token) - 1) strcpy(BlynkESP32_WM_config.Blynk_Creds[1].blynk_token, value.c_str()); else strncpy(BlynkESP32_WM_config.Blynk_Creds[1].blynk_token, value.c_str(), sizeof(BlynkESP32_WM_config.Blynk_Creds[1].blynk_token) - 1); } - else if (!pt_Updated && (key == String("pt"))) + else if (key == String("pt")) { - pt_Updated = true; - number_items_Updated++; + if (!pt_Updated) + { + pt_Updated = true; + number_items_Updated++; + } BlynkESP32_WM_config.blynk_port = value.toInt(); } - else if (!nm_Updated && (key == String("nm"))) + else if (key == String("nm")) { - nm_Updated = true; - number_items_Updated++; + if (!nm_Updated) + { + nm_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(BlynkESP32_WM_config.board_name) - 1) strcpy(BlynkESP32_WM_config.board_name, value.c_str()); @@ -2277,31 +2494,37 @@ class BlynkWifi } #if USE_DYNAMIC_PARAMETERS - for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) + else { - if ( !menuItemUpdated[i] && (key == myMenuItems[i].id) ) + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) { - BLYNK_LOG4(BLYNK_F("h:"), myMenuItems[i].id, BLYNK_F("="), value.c_str() ); - - menuItemUpdated[i] = true; - - number_items_Updated++; + if ( !menuItemUpdated[i] && (key == myMenuItems[i].id) ) + { + BLYNK_LOG4(BLYNK_F("h:"), myMenuItems[i].id, BLYNK_F("="), value.c_str() ); + + if (!menuItemUpdated[i]) + { + menuItemUpdated[i] = true; + number_items_Updated++; + } - // Actual size of pdata is [maxlen + 1] - memset(myMenuItems[i].pdata, 0, myMenuItems[i].maxlen + 1); + // Actual size of pdata is [maxlen + 1] + memset(myMenuItems[i].pdata, 0, myMenuItems[i].maxlen + 1); - if ((int) strlen(value.c_str()) < myMenuItems[i].maxlen) - strcpy(myMenuItems[i].pdata, value.c_str()); - else - strncpy(myMenuItems[i].pdata, value.c_str(), myMenuItems[i].maxlen); -#if ( BLYNK_WM_DEBUG > 2) - BLYNK_LOG4(BLYNK_F("h2:myMenuItems["), i, BLYNK_F("]="), myMenuItems[i].pdata ); -#endif + if ((int) strlen(value.c_str()) < myMenuItems[i].maxlen) + strcpy(myMenuItems[i].pdata, value.c_str()); + else + strncpy(myMenuItems[i].pdata, value.c_str(), myMenuItems[i].maxlen); + #if ( BLYNK_WM_DEBUG > 2) + BLYNK_LOG4(BLYNK_F("h2:myMenuItems["), i, BLYNK_F("]="), myMenuItems[i].pdata ); + #endif + break; + } } } #endif - server->send(200, "text/html", "OK"); + server->send(200, WM_HTTP_HEAD_TEXT_HTML, "OK"); #if USE_DYNAMIC_PARAMETERS if (number_items_Updated == NUM_CONFIGURABLE_ITEMS + NUM_MENU_ITEMS) @@ -2336,10 +2559,13 @@ class BlynkWifi ////////////////////////////////////////////// +#ifndef CONFIG_TIMEOUT + #warning Default CONFIG_TIMEOUT = 60s + #define CONFIG_TIMEOUT 60000L +#endif + void startConfigurationMode() { -#define CONFIG_TIMEOUT 60000L - // turn the LED_BUILTIN ON to tell us we are in configuration mode. digitalWrite(LED_BUILTIN, LED_ON); diff --git a/src/BlynkSimpleEsp32_WM.h b/src/BlynkSimpleEsp32_WM.h index 8135099..d949cd2 100644 --- a/src/BlynkSimpleEsp32_WM.h +++ b/src/BlynkSimpleEsp32_WM.h @@ -16,7 +16,7 @@ @date Jan 2015 @brief - Version: 1.1.3 + Version: 1.2.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -41,6 +41,7 @@ 1.1.1 K Hoang 16/01/2021 Add functions to control Config Portal from software or Virtual Switches 1.1.2 K Hoang 28/01/2021 Fix Config Portal and Dynamic Params bugs 1.1.3 K Hoang 31/01/2021 To permit autoreset after timeout if DRD/MRD or non-persistent forced-CP + 1.2.0 K Hoang 24/02/2021 Add customs HTML header feature and support to ESP32-S2. ********************************************************************************************************************************/ #ifndef BlynkSimpleEsp32_WM_h @@ -50,7 +51,7 @@ #error This code is intended to run on the ESP32 platform! Please check your Tools->Board setting. #endif -#define BLYNK_WM_VERSION "Blynk_WM for ESP32 v1.1.3" +#define BLYNK_WM_VERSION "Blynk_WM for ESP32 v1.2.0" #define BLYNK_SEND_ATOMIC @@ -237,15 +238,20 @@ typedef struct // Configurable items besides fixed Header #define NUM_CONFIGURABLE_ITEMS ( 2 + (2 * NUM_WIFI_CREDENTIALS) + (2 * NUM_BLYNK_CREDENTIALS) ) + +#define HEADER_MAX_LEN 16 +#define BOARD_NAME_MAX_LEN 24 + typedef struct Configuration { - char header [16]; + char header [HEADER_MAX_LEN]; WiFi_Credentials WiFi_Creds [NUM_WIFI_CREDENTIALS]; Blynk_Credentials Blynk_Creds [NUM_BLYNK_CREDENTIALS]; int blynk_port; - char board_name [24]; + char board_name [BOARD_NAME_MAX_LEN]; int checkSum; } Blynk_WM_Configuration; + // Currently CONFIG_DATA_SIZE = ( 48 + (96 * NUM_WIFI_CREDENTIALS) + (68 * NUM_BLYNK_CREDENTIALS) ) = 376 uint16_t CONFIG_DATA_SIZE = sizeof(Blynk_WM_Configuration); @@ -257,9 +263,12 @@ extern Blynk_WM_Configuration defaultConfig; //From v1.0.10, Permit special chars such as # and % // -- HTML page fragments -const char BLYNK_WM_HTML_HEAD[] /*PROGMEM*/ = "BlynkSimpleEsp32_WM
\ + +const char BLYNK_WM_HTML_HEAD_START[] /*PROGMEM*/ = "BlynkSimpleEsp32_WM"; + +const char BLYNK_WM_HTML_HEAD_STYLE[] /*PROGMEM*/ = ""; + +const char BLYNK_WM_HTML_HEAD_END[] /*PROGMEM*/ = "
\
\
\
\ @@ -270,6 +279,7 @@ body{text-align: center;}button{background-color:#16A1E7;color:#fff;line-height:
\
\
"; + const char BLYNK_WM_FLDSET_START[] /*PROGMEM*/ = "
"; const char BLYNK_WM_FLDSET_END[] /*PROGMEM*/ = "
"; const char BLYNK_WM_HTML_PARAM[] /*PROGMEM*/ = "
"; @@ -285,7 +295,24 @@ udVal('pt',document.getElementById('pt').value);udVal('nm',document.getElementBy const char BLYNK_WM_HTML_SCRIPT_ITEM[] /*PROGMEM*/ = "udVal('{d}',document.getElementById('{d}').value);"; const char BLYNK_WM_HTML_SCRIPT_END[] /*PROGMEM*/ = "alert('Updated');}"; const char BLYNK_WM_HTML_END[] /*PROGMEM*/ = ""; -/// + +////////////////////////////////////////// + +//KH Add repeatedly used const +//KH, from v1.2.0 +const char WM_HTTP_HEAD_CL[] PROGMEM = "Content-Length"; +const char WM_HTTP_HEAD_TEXT_HTML[] PROGMEM = "text/html"; +const char WM_HTTP_HEAD_TEXT_PLAIN[] PROGMEM = "text/plain"; + +const char WM_HTTP_CACHE_CONTROL[] PROGMEM = "Cache-Control"; +const char WM_HTTP_NO_STORE[] PROGMEM = "no-cache, no-store, must-revalidate"; +const char WM_HTTP_PRAGMA[] PROGMEM = "Pragma"; +const char WM_HTTP_NO_CACHE[] PROGMEM = "no-cache"; +const char WM_HTTP_EXPIRES[] PROGMEM = "Expires"; +const char WM_HTTP_CORS[] PROGMEM = "Access-Control-Allow-Origin"; +const char WM_HTTP_CORS_ALLOW_ALL[] PROGMEM = "*"; + +////////////////////////////////////////// #define BLYNK_SERVER_HARDWARE_PORT 8080 @@ -303,6 +330,8 @@ class BlynkWifi : Base(transp) {} + ////////////////////////////////////////// + void connectWiFi(const char* ssid, const char* pass) { BLYNK_LOG2(BLYNK_F("Con2:"), ssid); @@ -336,6 +365,8 @@ class BlynkWifi displayWiFiData(); } + ////////////////////////////////////////// + void config(const char* auth, const char* domain = BLYNK_DEFAULT_DOMAIN, uint16_t port = BLYNK_DEFAULT_PORT) @@ -343,6 +374,8 @@ class BlynkWifi Base::begin(auth); this->conn.begin(domain, port); } + + ////////////////////////////////////////// void config(const char* auth, IPAddress ip, @@ -351,6 +384,8 @@ class BlynkWifi Base::begin(auth); this->conn.begin(ip, port); } + + ////////////////////////////////////////// void begin(const char* auth, const char* ssid, @@ -362,6 +397,8 @@ class BlynkWifi config(auth, domain, port); while (this->connect() != true) {} } + + ////////////////////////////////////////// void begin(const char* auth, const char* ssid, @@ -582,7 +619,14 @@ class BlynkWifi retryTimes = 0; if (server) + { server->handleClient(); + +#if ( ARDUINO_ESP32S2_DEV || ARDUINO_FEATHERS2 || ARDUINO_PROS2 || ARDUINO_MICROS2 ) + // Fix ESP32-S2 issue with WebServer (https://github.com/espressif/arduino-esp32/issues/4348) + delay(1); +#endif + } return; } @@ -656,12 +700,14 @@ class BlynkWifi void setHostname() { +#if !( ARDUINO_ESP32S2_DEV || ARDUINO_FEATHERS2 || ARDUINO_PROS2 || ARDUINO_MICROS2 ) if (RFC952_hostname[0] != 0) { // See https://github.com/espressif/arduino-esp32/issues/2537 WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE); WiFi.setHostname(RFC952_hostname); } +#endif } void setConfigPortalIP(IPAddress portalIP = IPAddress(192, 168, 4, 1)) @@ -824,9 +870,76 @@ class BlynkWifi ESP.restart(); } - ////////////////////////////////////////////// + ////////////////////////////////////// + + // Add customs headers from v1.2.0 + + // New from v1.2.0, for configure CORS Header, default to WM_HTTP_CORS_ALLOW_ALL = "*" + +#if USING_CUSTOMS_STYLE + //sets a custom style, such as color + // ""; + void setCustomsStyle(const char* CustomsStyle = BLYNK_WM_HTML_HEAD_STYLE) + { + BLYNK_WM_HTML_HEAD_CUSTOMS_STYLE = CustomsStyle; + BLYNK_LOG2(F("Set CustomsStyle to : "), BLYNK_WM_HTML_HEAD_CUSTOMS_STYLE); + } + + ////////////////////////////////////// + + const char* getCustomsStyle() + { + BLYNK_LOG2(F("Get CustomsStyle = "), BLYNK_WM_HTML_HEAD_CUSTOMS_STYLE); + return BLYNK_WM_HTML_HEAD_CUSTOMS_STYLE; + } +#endif + + ////////////////////////////////////// + +#if USING_CUSTOMS_HEAD_ELEMENT + //sets a custom element to add to head, like a new style tag + void setCustomsHeadElement(const char* CustomsHeadElement = NULL) + { + _CustomsHeadElement = CustomsHeadElement; + BLYNK_LOG2(F("Set CustomsHeadElement to : "), _CustomsHeadElement); + } + + ////////////////////////////////////// + + const char* getCustomsHeadElement() + { + BLYNK_LOG2(F("Get CustomsHeadElement = "), _CustomsHeadElement); + return _CustomsHeadElement; + } +#endif + + ////////////////////////////////////// + +#if USING_CORS_FEATURE + void setCORSHeader(const char* CORSHeaders = NULL) + { + _CORS_Header = CORSHeaders; + + BLYNK_LOG2(F("Set CORS Header to : "), _CORS_Header); + } + + ////////////////////////////////////// + + const char* getCORSHeader() + { + BLYNK_LOG2(F("Get CORS Header = "), _CORS_Header); + return _CORS_Header; + } +#endif + + ////////////////////////////////////// + private: + WebServer *server; bool configuration_mode = false; @@ -858,7 +971,26 @@ class BlynkWifi IPAddress static_DNS1 = IPAddress(0, 0, 0, 0); IPAddress static_DNS2 = IPAddress(0, 0, 0, 0); +///////////////////////////////////// + + // Add customs headers from v1.2.0 + +#if USING_CUSTOMS_STYLE + const char* BLYNK_WM_HTML_HEAD_CUSTOMS_STYLE = NULL; +#endif + +#if USING_CUSTOMS_HEAD_ELEMENT + const char* _CustomsHeadElement = NULL; +#endif + +#if USING_CORS_FEATURE + const char* _CORS_Header = WM_HTTP_CORS_ALLOW_ALL; //"*"; +#endif + + ////////////////////////////////////// + #define RFC952_HOSTNAME_MAXLEN 24 + char RFC952_hostname[RFC952_HOSTNAME_MAXLEN + 1]; char* getRFC952_hostname(const char* iHostname) @@ -1912,7 +2044,7 @@ class BlynkWifi #endif ////////////////////////////////////////////// - + bool connectMultiBlynk() { #define BLYNK_CONNECT_TIMEOUT_MS 10000L @@ -1921,8 +2053,8 @@ class BlynkWifi { config(BlynkESP32_WM_config.Blynk_Creds[i].blynk_token, BlynkESP32_WM_config.Blynk_Creds[i].blynk_server, BLYNK_SERVER_HARDWARE_PORT); - - if (connect(BLYNK_CONNECT_TIMEOUT_MS) ) + + if (connect(BLYNK_CONNECT_TIMEOUT_MS) ) { BLYNK_LOG4(BLYNK_F("Connected to Blynk Server = "), BlynkESP32_WM_config.Blynk_Creds[i].blynk_server, BLYNK_F(", Token = "), BlynkESP32_WM_config.Blynk_Creds[i].blynk_token); @@ -1978,6 +2110,86 @@ class BlynkWifi } ////////////////////////////////////////////// + +#if 1 + // NEW + void createHTML(String& root_html_template) + { + String pitem; + + root_html_template = BLYNK_WM_HTML_HEAD_START; + + #if USING_CUSTOMS_STYLE + // Using Customs style when not NULL + if (BLYNK_WM_HTML_HEAD_CUSTOMS_STYLE) + root_html_template += BLYNK_WM_HTML_HEAD_CUSTOMS_STYLE; + else + root_html_template += BLYNK_WM_HTML_HEAD_STYLE; + #else + root_html_template += BLYNK_WM_HTML_HEAD_STYLE; + #endif + + #if USING_CUSTOMS_HEAD_ELEMENT + if (_CustomsHeadElement) + root_html_template += _CustomsHeadElement; + #endif + + root_html_template += String(BLYNK_WM_HTML_HEAD_END) + BLYNK_WM_FLDSET_START; + +#if USE_DYNAMIC_PARAMETERS + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) + { + pitem = String(BLYNK_WM_HTML_PARAM); + + pitem.replace("{b}", myMenuItems[i].displayName); + pitem.replace("{v}", myMenuItems[i].id); + pitem.replace("{i}", myMenuItems[i].id); + + root_html_template += pitem; + } +#endif + + root_html_template += String(BLYNK_WM_FLDSET_END) + BLYNK_WM_HTML_BUTTON + BLYNK_WM_HTML_SCRIPT; + +#if USE_DYNAMIC_PARAMETERS + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) + { + pitem = String(BLYNK_WM_HTML_SCRIPT_ITEM); + + pitem.replace("{d}", myMenuItems[i].id); + + root_html_template += pitem; + } +#endif + + root_html_template += String(BLYNK_WM_HTML_SCRIPT_END) + BLYNK_WM_HTML_END; + + return; + } + + ////////////////////////////////////////////// + + void serverSendHeaders() + { + BLYNK_LOG4(F("serverSendHeaders:WM_HTTP_CACHE_CONTROL:"), WM_HTTP_CACHE_CONTROL, "=", WM_HTTP_NO_STORE); + server->sendHeader(WM_HTTP_CACHE_CONTROL, WM_HTTP_NO_STORE); + +#if USING_CORS_FEATURE + // New from v1.2.0, for configure CORS Header, default to WM_HTTP_CORS_ALLOW_ALL = "*" + BLYNK_LOG4(F("serverSendHeaders:WM_HTTP_CORS:"), WM_HTTP_CORS, " : ", _CORS_Header); + server->sendHeader(WM_HTTP_CORS, _CORS_Header); +#endif + + BLYNK_LOG4(F("serverSendHeaders:WM_HTTP_PRAGMA:"), WM_HTTP_PRAGMA, " : ", WM_HTTP_NO_CACHE); + server->sendHeader(WM_HTTP_PRAGMA, WM_HTTP_NO_CACHE); + + BLYNK_LOG4(F("serverSendHeaders:WM_HTTP_EXPIRES:"), WM_HTTP_EXPIRES, " : ", "-1"); + server->sendHeader(WM_HTTP_EXPIRES, "-1"); + } + + ////////////////////////////////////////////// + +#else void createHTML(String &root_html_template) { @@ -2015,6 +2227,8 @@ class BlynkWifi return; } + +#endif ////////////////////////////////////////////// @@ -2029,10 +2243,14 @@ class BlynkWifi if (key == "" && value == "") { + // New from v1.2.0 + serverSendHeaders(); + ////// + String result; createHTML(result); - //BLYNK_LOG1(BLYNK_F("hR: replace result")); + //BLYNK_LOG1(BLYNK_F("h:repl")); // Reset configTimeout to stay here until finished. configTimeout = 0; @@ -2040,12 +2258,12 @@ class BlynkWifi if ( RFC952_hostname[0] != 0 ) { // Replace only if Hostname is valid - result.replace("BlynkSimpleEsp32_WM", RFC952_hostname); + result.replace("BlynkSimpleEsp32_SSL_WM", RFC952_hostname); } else if ( BlynkESP32_WM_config.board_name[0] != 0 ) { // Or replace only if board_name is valid. Otherwise, keep intact - result.replace("BlynkSimpleEsp32_WM", BlynkESP32_WM_config.board_name); + result.replace("BlynkSimpleEsp32_SSL_WM", BlynkESP32_WM_config.board_name); } result.replace("[[id]]", BlynkESP32_WM_config.WiFi_Creds[0].wifi_ssid); @@ -2058,7 +2276,7 @@ class BlynkWifi result.replace("[[tk1]]", BlynkESP32_WM_config.Blynk_Creds[1].blynk_token); result.replace("[[pt]]", String(BlynkESP32_WM_config.blynk_port)); result.replace("[[nm]]", BlynkESP32_WM_config.board_name); - + #if USE_DYNAMIC_PARAMETERS // Load default configuration for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) @@ -2071,7 +2289,7 @@ class BlynkWifi } #endif - server->send(200, "text/html", result); + server->send(200, WM_HTTP_HEAD_TEXT_HTML, result); return; } @@ -2117,97 +2335,127 @@ class BlynkWifi static bool pt_Updated = false; static bool nm_Updated = false; - if (!id_Updated && (key == String("id"))) + if (key == String("id")) { - id_Updated = true; - number_items_Updated++; + if (!id_Updated) + { + id_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(BlynkESP32_WM_config.WiFi_Creds[0].wifi_ssid) - 1) strcpy(BlynkESP32_WM_config.WiFi_Creds[0].wifi_ssid, value.c_str()); else strncpy(BlynkESP32_WM_config.WiFi_Creds[0].wifi_ssid, value.c_str(), sizeof(BlynkESP32_WM_config.WiFi_Creds[0].wifi_ssid) - 1); } - else if (!pw_Updated && (key == String("pw"))) + else if (key == String("pw")) { - pw_Updated = true; - number_items_Updated++; + if (!pw_Updated) + { + pw_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(BlynkESP32_WM_config.WiFi_Creds[0].wifi_pw) - 1) strcpy(BlynkESP32_WM_config.WiFi_Creds[0].wifi_pw, value.c_str()); else strncpy(BlynkESP32_WM_config.WiFi_Creds[0].wifi_pw, value.c_str(), sizeof(BlynkESP32_WM_config.WiFi_Creds[0].wifi_pw) - 1); } - else if (!id1_Updated && (key == String("id1"))) + else if (key == String("id1")) { - id1_Updated = true; - number_items_Updated++; + if (!id1_Updated) + { + id1_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(BlynkESP32_WM_config.WiFi_Creds[1].wifi_ssid) - 1) strcpy(BlynkESP32_WM_config.WiFi_Creds[1].wifi_ssid, value.c_str()); else strncpy(BlynkESP32_WM_config.WiFi_Creds[1].wifi_ssid, value.c_str(), sizeof(BlynkESP32_WM_config.WiFi_Creds[1].wifi_ssid) - 1); } - else if (!pw1_Updated && (key == String("pw1"))) + else if (key == String("pw1")) { - pw1_Updated = true; - number_items_Updated++; + if (!pw1_Updated) + { + pw1_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(BlynkESP32_WM_config.WiFi_Creds[1].wifi_pw) - 1) strcpy(BlynkESP32_WM_config.WiFi_Creds[1].wifi_pw, value.c_str()); else strncpy(BlynkESP32_WM_config.WiFi_Creds[1].wifi_pw, value.c_str(), sizeof(BlynkESP32_WM_config.WiFi_Creds[1].wifi_pw) - 1); } - else if (!sv_Updated && (key == String("sv"))) + else if (key == String("sv")) { - sv_Updated = true; - number_items_Updated++; + if (!sv_Updated) + { + sv_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(BlynkESP32_WM_config.Blynk_Creds[0].blynk_server) - 1) strcpy(BlynkESP32_WM_config.Blynk_Creds[0].blynk_server, value.c_str()); else strncpy(BlynkESP32_WM_config.Blynk_Creds[0].blynk_server, value.c_str(), sizeof(BlynkESP32_WM_config.Blynk_Creds[0].blynk_server) - 1); } - else if (!tk_Updated && (key == String("tk"))) + else if (key == String("tk")) { - tk_Updated = true; - number_items_Updated++; + if (!tk_Updated) + { + tk_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(BlynkESP32_WM_config.Blynk_Creds[0].blynk_token) - 1) strcpy(BlynkESP32_WM_config.Blynk_Creds[0].blynk_token, value.c_str()); else strncpy(BlynkESP32_WM_config.Blynk_Creds[0].blynk_token, value.c_str(), sizeof(BlynkESP32_WM_config.Blynk_Creds[0].blynk_token) - 1); } - else if (!sv1_Updated && (key == String("sv1"))) + else if (key == String("sv1")) { - sv1_Updated = true; - number_items_Updated++; + if (!sv1_Updated) + { + sv1_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(BlynkESP32_WM_config.Blynk_Creds[1].blynk_server) - 1) strcpy(BlynkESP32_WM_config.Blynk_Creds[1].blynk_server, value.c_str()); else strncpy(BlynkESP32_WM_config.Blynk_Creds[1].blynk_server, value.c_str(), sizeof(BlynkESP32_WM_config.Blynk_Creds[1].blynk_server) - 1); } - else if (!tk1_Updated && (key == String("tk1"))) + else if (key == String("tk1")) { - tk1_Updated = true; - number_items_Updated++; + if (!tk1_Updated) + { + tk1_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(BlynkESP32_WM_config.Blynk_Creds[1].blynk_token) - 1) strcpy(BlynkESP32_WM_config.Blynk_Creds[1].blynk_token, value.c_str()); else strncpy(BlynkESP32_WM_config.Blynk_Creds[1].blynk_token, value.c_str(), sizeof(BlynkESP32_WM_config.Blynk_Creds[1].blynk_token) - 1); } - else if (!pt_Updated && (key == String("pt"))) + else if (key == String("pt")) { - pt_Updated = true; - number_items_Updated++; + if (!pt_Updated) + { + pt_Updated = true; + number_items_Updated++; + } BlynkESP32_WM_config.blynk_port = value.toInt(); } - else if (!nm_Updated && (key == String("nm"))) + else if (key == String("nm")) { - nm_Updated = true; - number_items_Updated++; + if (!nm_Updated) + { + nm_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(BlynkESP32_WM_config.board_name) - 1) strcpy(BlynkESP32_WM_config.board_name, value.c_str()); @@ -2216,31 +2464,37 @@ class BlynkWifi } #if USE_DYNAMIC_PARAMETERS - for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) + else { - if ( !menuItemUpdated[i] && (key == myMenuItems[i].id) ) + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) { - BLYNK_LOG4(BLYNK_F("h:"), myMenuItems[i].id, BLYNK_F("="), value.c_str() ); - - menuItemUpdated[i] = true; - - number_items_Updated++; + if ( !menuItemUpdated[i] && (key == myMenuItems[i].id) ) + { + BLYNK_LOG4(BLYNK_F("h:"), myMenuItems[i].id, BLYNK_F("="), value.c_str() ); + + if (!menuItemUpdated[i]) + { + menuItemUpdated[i] = true; + number_items_Updated++; + } - // Actual size of pdata is [maxlen + 1] - memset(myMenuItems[i].pdata, 0, myMenuItems[i].maxlen + 1); + // Actual size of pdata is [maxlen + 1] + memset(myMenuItems[i].pdata, 0, myMenuItems[i].maxlen + 1); - if ((int) strlen(value.c_str()) < myMenuItems[i].maxlen) - strcpy(myMenuItems[i].pdata, value.c_str()); - else - strncpy(myMenuItems[i].pdata, value.c_str(), myMenuItems[i].maxlen); -#if ( BLYNK_WM_DEBUG > 2) - BLYNK_LOG4(BLYNK_F("h2:myMenuItems["), i, BLYNK_F("]="), myMenuItems[i].pdata ); -#endif + if ((int) strlen(value.c_str()) < myMenuItems[i].maxlen) + strcpy(myMenuItems[i].pdata, value.c_str()); + else + strncpy(myMenuItems[i].pdata, value.c_str(), myMenuItems[i].maxlen); + #if ( BLYNK_WM_DEBUG > 2) + BLYNK_LOG4(BLYNK_F("h2:myMenuItems["), i, BLYNK_F("]="), myMenuItems[i].pdata ); + #endif + break; + } } } #endif - server->send(200, "text/html", "OK"); + server->send(200, WM_HTTP_HEAD_TEXT_HTML, "OK"); #if USE_DYNAMIC_PARAMETERS if (number_items_Updated == NUM_CONFIGURABLE_ITEMS + NUM_MENU_ITEMS) @@ -2275,10 +2529,13 @@ class BlynkWifi ////////////////////////////////////////////// +#ifndef CONFIG_TIMEOUT + #warning Default CONFIG_TIMEOUT = 60s + #define CONFIG_TIMEOUT 60000L +#endif + void startConfigurationMode() { -#define CONFIG_TIMEOUT 60000L - // turn the LED_BUILTIN ON to tell us we are in configuration mode. digitalWrite(LED_BUILTIN, LED_ON); diff --git a/src/BlynkSimpleEsp8266_SSL_WM.h b/src/BlynkSimpleEsp8266_SSL_WM.h index 7f4490a..6870481 100644 --- a/src/BlynkSimpleEsp8266_SSL_WM.h +++ b/src/BlynkSimpleEsp8266_SSL_WM.h @@ -16,7 +16,7 @@ @date Jan 2015 @brief - Version: 1.1.3 + Version: 1.2.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -41,6 +41,7 @@ 1.1.1 K Hoang 16/01/2021 Add functions to control Config Portal from software or Virtual Switches 1.1.2 K Hoang 28/01/2021 Fix Config Portal and Dynamic Params bugs 1.1.3 K Hoang 31/01/2021 To permit autoreset after timeout if DRD/MRD or non-persistent forced-CP + 1.2.0 K Hoang 24/02/2021 Add customs HTML header feature and support to ESP32-S2. ********************************************************************************************************************************/ #ifndef BlynkSimpleEsp8266_SSL_WM_h @@ -50,7 +51,7 @@ #error This code is intended to run on the ESP8266 platform! Please check your Tools->Board setting. #endif -#define BLYNK_WM_VERSION "Blynk_WM SSL for ESP8266 v1.1.3" +#define BLYNK_WM_VERSION "Blynk_WM SSL for ESP8266 v1.2.0" #include @@ -86,17 +87,17 @@ static const unsigned char BLYNK_DEFAULT_CERT_DER[] PROGMEM = #if USE_LITTLEFS #define FileFS LittleFS - #warning Using LittleFS in BlynkSimpleESP8266_WM.h + #warning Using LittleFS in BlynkSimpleEsp8266_SSL_WM.h #else #define FileFS SPIFFS - #warning Using SPIFFS in BlynkSimpleESP8266_WM.h + #warning Using SPIFFS in BlynkSimpleEsp8266_SSL_WM.h #endif #include #include #else #include - #warning Using EEPROM in BlynkSimpleESP8266_WM.h + #warning Using EEPROM in BlynkSimpleEsp8266_SSL_WM.h #endif #if !defined(USING_MRD) @@ -319,15 +320,20 @@ typedef struct // Configurable items besides fixed Header #define NUM_CONFIGURABLE_ITEMS ( 2 + (2 * NUM_WIFI_CREDENTIALS) + (2 * NUM_BLYNK_CREDENTIALS) ) + +#define HEADER_MAX_LEN 16 +#define BOARD_NAME_MAX_LEN 24 + typedef struct Configuration { - char header [16]; + char header [HEADER_MAX_LEN]; WiFi_Credentials WiFi_Creds [NUM_WIFI_CREDENTIALS]; Blynk_Credentials Blynk_Creds [NUM_BLYNK_CREDENTIALS]; int blynk_port; - char board_name [24]; + char board_name [BOARD_NAME_MAX_LEN]; int checkSum; } Blynk_WM_Configuration; + // Currently CONFIG_DATA_SIZE = ( 48 + (96 * NUM_WIFI_CREDENTIALS) + (68 * NUM_BLYNK_CREDENTIALS) ) = 376 uint16_t CONFIG_DATA_SIZE = sizeof(Blynk_WM_Configuration); @@ -339,9 +345,12 @@ extern Blynk_WM_Configuration defaultConfig; //From v1.0.10, Permit special chars such as # and % // -- HTML page fragments -const char BLYNK_WM_HTML_HEAD[] /*PROGMEM*/ = "BlynkSimpleEsp8266_SSL_WM
\ + +const char BLYNK_WM_HTML_HEAD_START[] /*PROGMEM*/ = "BlynkSimpleEsp8266_SSL_WM"; + +const char BLYNK_WM_HTML_HEAD_STYLE[] /*PROGMEM*/ = ""; + +const char BLYNK_WM_HTML_HEAD_END[] /*PROGMEM*/ = "
\
\
\
\ @@ -352,6 +361,7 @@ body{text-align: center;}button{background-color:#16A1E7;color:#fff;line-height:
\
\
"; + const char BLYNK_WM_FLDSET_START[] /*PROGMEM*/ = "
"; const char BLYNK_WM_FLDSET_END[] /*PROGMEM*/ = "
"; const char BLYNK_WM_HTML_PARAM[] /*PROGMEM*/ = "
"; @@ -367,7 +377,23 @@ udVal('pt',document.getElementById('pt').value);udVal('nm',document.getElementBy const char BLYNK_WM_HTML_SCRIPT_ITEM[] /*PROGMEM*/ = "udVal('{d}',document.getElementById('{d}').value);"; const char BLYNK_WM_HTML_SCRIPT_END[] /*PROGMEM*/ = "alert('Updated');}"; const char BLYNK_WM_HTML_END[] /*PROGMEM*/ = ""; -/// + +////////////////////////////////////////// + +//KH Add repeatedly used const +//KH, from v1.2.0 +const char WM_HTTP_HEAD_CL[] = "Content-Length"; +const char WM_HTTP_HEAD_TEXT_HTML[] = "text/html"; +const char WM_HTTP_HEAD_TEXT_PLAIN[] = "text/plain"; +const char WM_HTTP_CACHE_CONTROL[] = "Cache-Control"; +const char WM_HTTP_NO_STORE[] = "no-cache, no-store, must-revalidate"; +const char WM_HTTP_PRAGMA[] = "Pragma"; +const char WM_HTTP_NO_CACHE[] = "no-cache"; +const char WM_HTTP_EXPIRES[] = "Expires"; +const char WM_HTTP_CORS[] = "Access-Control-Allow-Origin"; +const char WM_HTTP_CORS_ALLOW_ALL[] = "*"; + +////////////////////////////////////////// #define BLYNK_SERVER_HARDWARE_PORT 9443 @@ -912,6 +938,74 @@ class BlynkWifi delay(1000); ESP.reset(); } + + ////////////////////////////////////// + + // Add customs headers from v1.2.0 + + // New from v1.2.0, for configure CORS Header, default to WM_HTTP_CORS_ALLOW_ALL = "*" + +#if USING_CUSTOMS_STYLE + //sets a custom style, such as color + // ""; + void setCustomsStyle(const char* CustomsStyle = BLYNK_WM_HTML_HEAD_STYLE) + { + BLYNK_WM_HTML_HEAD_CUSTOMS_STYLE = CustomsStyle; + BLYNK_LOG2(F("Set CustomsStyle to : "), BLYNK_WM_HTML_HEAD_CUSTOMS_STYLE); + } + + ////////////////////////////////////// + + const char* getCustomsStyle() + { + BLYNK_LOG2(F("Get CustomsStyle = "), BLYNK_WM_HTML_HEAD_CUSTOMS_STYLE); + return BLYNK_WM_HTML_HEAD_CUSTOMS_STYLE; + } +#endif + + ////////////////////////////////////// + +#if USING_CUSTOMS_HEAD_ELEMENT + //sets a custom element to add to head, like a new style tag + void setCustomsHeadElement(const char* CustomsHeadElement = NULL) + { + _CustomsHeadElement = CustomsHeadElement; + BLYNK_LOG2(F("Set CustomsHeadElement to : "), _CustomsHeadElement); + } + + ////////////////////////////////////// + + const char* getCustomsHeadElement() + { + BLYNK_LOG2(F("Get CustomsHeadElement = "), _CustomsHeadElement); + return _CustomsHeadElement; + } +#endif + + ////////////////////////////////////// + +#if USING_CORS_FEATURE + void setCORSHeader(const char* CORSHeaders = NULL) + { + _CORS_Header = CORSHeaders; + + BLYNK_LOG2(F("Set CORS Header to : "), _CORS_Header); + } + + ////////////////////////////////////// + + const char* getCORSHeader() + { + BLYNK_LOG2(F("Get CORS Header = "), _CORS_Header); + return _CORS_Header; + } +#endif + + ////////////////////////////////////// + private: @@ -948,7 +1042,26 @@ class BlynkWifi IPAddress static_DNS1 = IPAddress(0, 0, 0, 0); IPAddress static_DNS2 = IPAddress(0, 0, 0, 0); +///////////////////////////////////// + + // Add customs headers from v1.2.0 + +#if USING_CUSTOMS_STYLE + const char* BLYNK_WM_HTML_HEAD_CUSTOMS_STYLE = NULL; +#endif + +#if USING_CUSTOMS_HEAD_ELEMENT + const char* _CustomsHeadElement = NULL; +#endif + +#if USING_CORS_FEATURE + const char* _CORS_Header = WM_HTTP_CORS_ALLOW_ALL; //"*"; +#endif + + ////////////////////////////////////// + #define RFC952_HOSTNAME_MAXLEN 24 + char RFC952_hostname[RFC952_HOSTNAME_MAXLEN + 1]; char* getRFC952_hostname(const char* iHostname) @@ -2061,13 +2174,29 @@ class BlynkWifi ////////////////////////////////////////////// - ////////////////////////////////////////////// - - void createHTML(String &root_html_template) + // NEW + void createHTML(String& root_html_template) { String pitem; - root_html_template = String(BLYNK_WM_HTML_HEAD) + BLYNK_WM_FLDSET_START; + root_html_template = BLYNK_WM_HTML_HEAD_START; + + #if USING_CUSTOMS_STYLE + // Using Customs style when not NULL + if (BLYNK_WM_HTML_HEAD_CUSTOMS_STYLE) + root_html_template += BLYNK_WM_HTML_HEAD_CUSTOMS_STYLE; + else + root_html_template += BLYNK_WM_HTML_HEAD_STYLE; + #else + root_html_template += BLYNK_WM_HTML_HEAD_STYLE; + #endif + + #if USING_CUSTOMS_HEAD_ELEMENT + if (_CustomsHeadElement) + root_html_template += _CustomsHeadElement; + #endif + + root_html_template += String(BLYNK_WM_HTML_HEAD_END) + BLYNK_WM_FLDSET_START; #if USE_DYNAMIC_PARAMETERS for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) @@ -2099,7 +2228,27 @@ class BlynkWifi return; } - + + ////////////////////////////////////////////// + + void serverSendHeaders() + { + BLYNK_LOG4(F("serverSendHeaders:WM_HTTP_CACHE_CONTROL:"), WM_HTTP_CACHE_CONTROL, "=", WM_HTTP_NO_STORE); + server->sendHeader(WM_HTTP_CACHE_CONTROL, WM_HTTP_NO_STORE); + +#if USING_CORS_FEATURE + // New from v1.2.0, for configure CORS Header, default to WM_HTTP_CORS_ALLOW_ALL = "*" + BLYNK_LOG4(F("serverSendHeaders:WM_HTTP_CORS:"), WM_HTTP_CORS, " : ", _CORS_Header); + server->sendHeader(WM_HTTP_CORS, _CORS_Header); +#endif + + BLYNK_LOG4(F("serverSendHeaders:WM_HTTP_PRAGMA:"), WM_HTTP_PRAGMA, " : ", WM_HTTP_NO_CACHE); + server->sendHeader(WM_HTTP_PRAGMA, WM_HTTP_NO_CACHE); + + BLYNK_LOG4(F("serverSendHeaders:WM_HTTP_EXPIRES:"), WM_HTTP_EXPIRES, " : ", "-1"); + server->sendHeader(WM_HTTP_EXPIRES, "-1"); + } + ////////////////////////////////////////////// void handleRequest() @@ -2113,6 +2262,10 @@ class BlynkWifi if (key == "" && value == "") { + // New from v1.2.0 + serverSendHeaders(); + ////// + String result; createHTML(result); @@ -2124,12 +2277,12 @@ class BlynkWifi if ( RFC952_hostname[0] != 0 ) { // Replace only if Hostname is valid - result.replace("BlynkSimpleEsp8266_WM", RFC952_hostname); + result.replace("BlynkSimpleEsp8266_SSL_WM", RFC952_hostname); } else if ( Blynk8266_WM_config.board_name[0] != 0 ) { // Or replace only if board_name is valid. Otherwise, keep intact - result.replace("BlynkSimpleEsp8266_WM", Blynk8266_WM_config.board_name); + result.replace("BlynkSimpleEsp8266_SSL_WM", Blynk8266_WM_config.board_name); } result.replace("[[id]]", Blynk8266_WM_config.WiFi_Creds[0].wifi_ssid); @@ -2155,7 +2308,7 @@ class BlynkWifi } #endif - server->send(200, "text/html", result); + server->send(200, WM_HTTP_HEAD_TEXT_HTML, result); return; } @@ -2201,97 +2354,127 @@ class BlynkWifi static bool pt_Updated = false; static bool nm_Updated = false; - if (!id_Updated && (key == String("id"))) + if (key == String("id")) { - id_Updated = true; - number_items_Updated++; + if (!id_Updated) + { + id_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(Blynk8266_WM_config.WiFi_Creds[0].wifi_ssid) - 1) strcpy(Blynk8266_WM_config.WiFi_Creds[0].wifi_ssid, value.c_str()); else strncpy(Blynk8266_WM_config.WiFi_Creds[0].wifi_ssid, value.c_str(), sizeof(Blynk8266_WM_config.WiFi_Creds[0].wifi_ssid) - 1); } - else if (!pw_Updated && (key == String("pw"))) + else if (key == String("pw")) { - pw_Updated = true; - number_items_Updated++; + if (!pw_Updated) + { + pw_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(Blynk8266_WM_config.WiFi_Creds[0].wifi_pw) - 1) strcpy(Blynk8266_WM_config.WiFi_Creds[0].wifi_pw, value.c_str()); else strncpy(Blynk8266_WM_config.WiFi_Creds[0].wifi_pw, value.c_str(), sizeof(Blynk8266_WM_config.WiFi_Creds[0].wifi_pw) - 1); } - else if (!id1_Updated && (key == String("id1"))) + else if (key == String("id1")) { - id1_Updated = true; - number_items_Updated++; + if (!id1_Updated) + { + id1_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(Blynk8266_WM_config.WiFi_Creds[1].wifi_ssid) - 1) strcpy(Blynk8266_WM_config.WiFi_Creds[1].wifi_ssid, value.c_str()); else strncpy(Blynk8266_WM_config.WiFi_Creds[1].wifi_ssid, value.c_str(), sizeof(Blynk8266_WM_config.WiFi_Creds[1].wifi_ssid) - 1); } - else if (!pw1_Updated && (key == String("pw1"))) + else if (key == String("pw1")) { - pw1_Updated = true; - number_items_Updated++; + if (!pw1_Updated) + { + pw1_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(Blynk8266_WM_config.WiFi_Creds[1].wifi_pw) - 1) strcpy(Blynk8266_WM_config.WiFi_Creds[1].wifi_pw, value.c_str()); else strncpy(Blynk8266_WM_config.WiFi_Creds[1].wifi_pw, value.c_str(), sizeof(Blynk8266_WM_config.WiFi_Creds[1].wifi_pw) - 1); } - else if (!sv_Updated && (key == String("sv"))) + else if (key == String("sv")) { - sv_Updated = true; - number_items_Updated++; + if (!sv_Updated) + { + sv_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(Blynk8266_WM_config.Blynk_Creds[0].blynk_server) - 1) strcpy(Blynk8266_WM_config.Blynk_Creds[0].blynk_server, value.c_str()); else strncpy(Blynk8266_WM_config.Blynk_Creds[0].blynk_server, value.c_str(), sizeof(Blynk8266_WM_config.Blynk_Creds[0].blynk_server) - 1); } - else if (!tk_Updated && (key == String("tk"))) + else if (key == String("tk")) { - tk_Updated = true; - number_items_Updated++; + if (!tk_Updated) + { + tk_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(Blynk8266_WM_config.Blynk_Creds[0].blynk_token) - 1) strcpy(Blynk8266_WM_config.Blynk_Creds[0].blynk_token, value.c_str()); else strncpy(Blynk8266_WM_config.Blynk_Creds[0].blynk_token, value.c_str(), sizeof(Blynk8266_WM_config.Blynk_Creds[0].blynk_token) - 1); } - else if (!sv1_Updated && (key == String("sv1"))) + else if (key == String("sv1")) { - sv1_Updated = true; - number_items_Updated++; + if (!sv1_Updated) + { + sv1_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(Blynk8266_WM_config.Blynk_Creds[1].blynk_server) - 1) strcpy(Blynk8266_WM_config.Blynk_Creds[1].blynk_server, value.c_str()); else strncpy(Blynk8266_WM_config.Blynk_Creds[1].blynk_server, value.c_str(), sizeof(Blynk8266_WM_config.Blynk_Creds[1].blynk_server) - 1); } - else if (!tk1_Updated && (key == String("tk1"))) + else if (key == String("tk1")) { - tk1_Updated = true; - number_items_Updated++; + if (!tk1_Updated) + { + tk1_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(Blynk8266_WM_config.Blynk_Creds[1].blynk_token) - 1) strcpy(Blynk8266_WM_config.Blynk_Creds[1].blynk_token, value.c_str()); else strncpy(Blynk8266_WM_config.Blynk_Creds[1].blynk_token, value.c_str(), sizeof(Blynk8266_WM_config.Blynk_Creds[1].blynk_token) - 1); } - else if (!pt_Updated && (key == String("pt"))) + else if (key == String("pt")) { - pt_Updated = true; - number_items_Updated++; + if (!pt_Updated) + { + pt_Updated = true; + number_items_Updated++; + } Blynk8266_WM_config.blynk_port = value.toInt(); } - else if (!nm_Updated && (key == String("nm"))) + else if (key == String("nm")) { - nm_Updated = true; - number_items_Updated++; + if (!nm_Updated) + { + nm_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(Blynk8266_WM_config.board_name) - 1) strcpy(Blynk8266_WM_config.board_name, value.c_str()); @@ -2300,31 +2483,37 @@ class BlynkWifi } #if USE_DYNAMIC_PARAMETERS - for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) + else { - if ( !menuItemUpdated[i] && (key == myMenuItems[i].id) ) + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) { - BLYNK_LOG4(BLYNK_F("h:"), myMenuItems[i].id, BLYNK_F("="), value.c_str() ); - - menuItemUpdated[i] = true; - - number_items_Updated++; + if ( !menuItemUpdated[i] && (key == myMenuItems[i].id) ) + { + BLYNK_LOG4(BLYNK_F("h:"), myMenuItems[i].id, BLYNK_F("="), value.c_str() ); + + if (!menuItemUpdated[i]) + { + menuItemUpdated[i] = true; + number_items_Updated++; + } - // Actual size of pdata is [maxlen + 1] - memset(myMenuItems[i].pdata, 0, myMenuItems[i].maxlen + 1); + // Actual size of pdata is [maxlen + 1] + memset(myMenuItems[i].pdata, 0, myMenuItems[i].maxlen + 1); - if ((int) strlen(value.c_str()) < myMenuItems[i].maxlen) - strcpy(myMenuItems[i].pdata, value.c_str()); - else - strncpy(myMenuItems[i].pdata, value.c_str(), myMenuItems[i].maxlen); -#if ( BLYNK_WM_DEBUG > 2) - BLYNK_LOG4(BLYNK_F("h2:myMenuItems["), i, BLYNK_F("]="), myMenuItems[i].pdata ); -#endif + if ((int) strlen(value.c_str()) < myMenuItems[i].maxlen) + strcpy(myMenuItems[i].pdata, value.c_str()); + else + strncpy(myMenuItems[i].pdata, value.c_str(), myMenuItems[i].maxlen); + #if ( BLYNK_WM_DEBUG > 2) + BLYNK_LOG4(BLYNK_F("h2:myMenuItems["), i, BLYNK_F("]="), myMenuItems[i].pdata ); + #endif + break; + } } } #endif - server->send(200, "text/html", "OK"); + server->send(200, WM_HTTP_HEAD_TEXT_HTML, "OK"); #if USE_DYNAMIC_PARAMETERS if (number_items_Updated == NUM_CONFIGURABLE_ITEMS + NUM_MENU_ITEMS) @@ -2359,10 +2548,13 @@ class BlynkWifi ////////////////////////////////////////////// +#ifndef CONFIG_TIMEOUT + #warning Default CONFIG_TIMEOUT = 60s + #define CONFIG_TIMEOUT 60000L +#endif + void startConfigurationMode() { -#define CONFIG_TIMEOUT 60000L - // turn the LED_BUILTIN ON to tell us we are in configuration mode. digitalWrite(LED_BUILTIN, LED_ON); diff --git a/src/BlynkSimpleEsp8266_WM.h b/src/BlynkSimpleEsp8266_WM.h index da24b1c..eda8bc5 100644 --- a/src/BlynkSimpleEsp8266_WM.h +++ b/src/BlynkSimpleEsp8266_WM.h @@ -16,7 +16,7 @@ @date Jan 2015 @brief - Version: 1.1.3 + Version: 1.2.0 Version Modified By Date Comments ------- ----------- ---------- ----------- @@ -41,6 +41,7 @@ 1.1.1 K Hoang 16/01/2021 Add functions to control Config Portal from software or Virtual Switches 1.1.2 K Hoang 28/01/2021 Fix Config Portal and Dynamic Params bugs 1.1.3 K Hoang 31/01/2021 To permit autoreset after timeout if DRD/MRD or non-persistent forced-CP + 1.2.0 K Hoang 24/02/2021 Add customs HTML header feature and support to ESP32-S2. ********************************************************************************************************************************/ @@ -51,7 +52,7 @@ #error This code is intended to run on the ESP8266 platform! Please check your Tools->Board setting. #endif -#define BLYNK_WM_VERSION "Blynk_WM for ESP8266 v1.1.3" +#define BLYNK_WM_VERSION "Blynk_WM for ESP8266 v1.2.0" #include @@ -229,15 +230,20 @@ typedef struct // Configurable items besides fixed Header #define NUM_CONFIGURABLE_ITEMS ( 2 + (2 * NUM_WIFI_CREDENTIALS) + (2 * NUM_BLYNK_CREDENTIALS) ) + +#define HEADER_MAX_LEN 16 +#define BOARD_NAME_MAX_LEN 24 + typedef struct Configuration { - char header [16]; + char header [HEADER_MAX_LEN]; WiFi_Credentials WiFi_Creds [NUM_WIFI_CREDENTIALS]; Blynk_Credentials Blynk_Creds [NUM_BLYNK_CREDENTIALS]; int blynk_port; - char board_name [24]; + char board_name [BOARD_NAME_MAX_LEN]; int checkSum; } Blynk_WM_Configuration; + // Currently CONFIG_DATA_SIZE = ( 48 + (96 * NUM_WIFI_CREDENTIALS) + (68 * NUM_BLYNK_CREDENTIALS) ) = 376 uint16_t CONFIG_DATA_SIZE = sizeof(Blynk_WM_Configuration); @@ -249,9 +255,14 @@ extern Blynk_WM_Configuration defaultConfig; //From v1.0.10, Permit special chars such as # and % // -- HTML page fragments -const char BLYNK_WM_HTML_HEAD[] /*PROGMEM*/ = "BlynkSimpleEsp8266_WM
\ + +// -- HTML page fragments + +const char BLYNK_WM_HTML_HEAD_START[] /*PROGMEM*/ = "BlynkSimpleEsp8266_WM"; + +const char BLYNK_WM_HTML_HEAD_STYLE[] /*PROGMEM*/ = ""; + +const char BLYNK_WM_HTML_HEAD_END[] /*PROGMEM*/ = "
\
\
\
\ @@ -262,6 +273,7 @@ body{text-align: center;}button{background-color:#16A1E7;color:#fff;line-height:
\
\
"; + const char BLYNK_WM_FLDSET_START[] /*PROGMEM*/ = "
"; const char BLYNK_WM_FLDSET_END[] /*PROGMEM*/ = "
"; const char BLYNK_WM_HTML_PARAM[] /*PROGMEM*/ = "
"; @@ -277,7 +289,23 @@ udVal('pt',document.getElementById('pt').value);udVal('nm',document.getElementBy const char BLYNK_WM_HTML_SCRIPT_ITEM[] /*PROGMEM*/ = "udVal('{d}',document.getElementById('{d}').value);"; const char BLYNK_WM_HTML_SCRIPT_END[] /*PROGMEM*/ = "alert('Updated');}"; const char BLYNK_WM_HTML_END[] /*PROGMEM*/ = ""; -/// + +////////////////////////////////////////// + +//KH Add repeatedly used const +//KH, from v1.2.0 +const char WM_HTTP_HEAD_CL[] = "Content-Length"; +const char WM_HTTP_HEAD_TEXT_HTML[] = "text/html"; +const char WM_HTTP_HEAD_TEXT_PLAIN[] = "text/plain"; +const char WM_HTTP_CACHE_CONTROL[] = "Cache-Control"; +const char WM_HTTP_NO_STORE[] = "no-cache, no-store, must-revalidate"; +const char WM_HTTP_PRAGMA[] = "Pragma"; +const char WM_HTTP_NO_CACHE[] = "no-cache"; +const char WM_HTTP_EXPIRES[] = "Expires"; +const char WM_HTTP_CORS[] = "Access-Control-Allow-Origin"; +const char WM_HTTP_CORS_ALLOW_ALL[] = "*"; + +////////////////////////////////////////// #define BLYNK_SERVER_HARDWARE_PORT 8080 @@ -800,8 +828,77 @@ class BlynkWifi delay(1000); ESP.reset(); } + + ////////////////////////////////////// + + // Add customs headers from v1.2.0 + + // New from v1.2.0, for configure CORS Header, default to WM_HTTP_CORS_ALLOW_ALL = "*" + +#if USING_CUSTOMS_STYLE + //sets a custom style, such as color + // ""; + void setCustomsStyle(const char* CustomsStyle = BLYNK_WM_HTML_HEAD_STYLE) + { + BLYNK_WM_HTML_HEAD_CUSTOMS_STYLE = CustomsStyle; + BLYNK_LOG2(F("Set CustomsStyle to : "), BLYNK_WM_HTML_HEAD_CUSTOMS_STYLE); + } + + ////////////////////////////////////// + + const char* getCustomsStyle() + { + BLYNK_LOG2(F("Get CustomsStyle = "), BLYNK_WM_HTML_HEAD_CUSTOMS_STYLE); + return BLYNK_WM_HTML_HEAD_CUSTOMS_STYLE; + } +#endif + + ////////////////////////////////////// + +#if USING_CUSTOMS_HEAD_ELEMENT + //sets a custom element to add to head, like a new style tag + void setCustomsHeadElement(const char* CustomsHeadElement = NULL) + { + _CustomsHeadElement = CustomsHeadElement; + BLYNK_LOG2(F("Set CustomsHeadElement to : "), _CustomsHeadElement); + } + + ////////////////////////////////////// + + const char* getCustomsHeadElement() + { + BLYNK_LOG2(F("Get CustomsHeadElement = "), _CustomsHeadElement); + return _CustomsHeadElement; + } +#endif + + ////////////////////////////////////// + +#if USING_CORS_FEATURE + void setCORSHeader(const char* CORSHeaders = NULL) + { + _CORS_Header = CORSHeaders; + + BLYNK_LOG2(F("Set CORS Header to : "), _CORS_Header); + } + + ////////////////////////////////////// + + const char* getCORSHeader() + { + BLYNK_LOG2(F("Get CORS Header = "), _CORS_Header); + return _CORS_Header; + } +#endif + + ////////////////////////////////////// + private: + ESP8266WebServer *server; bool configuration_mode = false; @@ -833,7 +930,26 @@ class BlynkWifi IPAddress static_DNS1 = IPAddress(0, 0, 0, 0); IPAddress static_DNS2 = IPAddress(0, 0, 0, 0); +///////////////////////////////////// + + // Add customs headers from v1.2.0 + +#if USING_CUSTOMS_STYLE + const char* BLYNK_WM_HTML_HEAD_CUSTOMS_STYLE = NULL; +#endif + +#if USING_CUSTOMS_HEAD_ELEMENT + const char* _CustomsHeadElement = NULL; +#endif + +#if USING_CORS_FEATURE + const char* _CORS_Header = WM_HTTP_CORS_ALLOW_ALL; //"*"; +#endif + + ////////////////////////////////////// + #define RFC952_HOSTNAME_MAXLEN 24 + char RFC952_hostname[RFC952_HOSTNAME_MAXLEN + 1]; char* getRFC952_hostname(const char* iHostname) @@ -1949,6 +2065,86 @@ class BlynkWifi } ////////////////////////////////////////////// + +#if 1 + // NEW + void createHTML(String& root_html_template) + { + String pitem; + + root_html_template = BLYNK_WM_HTML_HEAD_START; + + #if USING_CUSTOMS_STYLE + // Using Customs style when not NULL + if (BLYNK_WM_HTML_HEAD_CUSTOMS_STYLE) + root_html_template += BLYNK_WM_HTML_HEAD_CUSTOMS_STYLE; + else + root_html_template += BLYNK_WM_HTML_HEAD_STYLE; + #else + root_html_template += BLYNK_WM_HTML_HEAD_STYLE; + #endif + + #if USING_CUSTOMS_HEAD_ELEMENT + if (_CustomsHeadElement) + root_html_template += _CustomsHeadElement; + #endif + + root_html_template += String(BLYNK_WM_HTML_HEAD_END) + BLYNK_WM_FLDSET_START; + +#if USE_DYNAMIC_PARAMETERS + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) + { + pitem = String(BLYNK_WM_HTML_PARAM); + + pitem.replace("{b}", myMenuItems[i].displayName); + pitem.replace("{v}", myMenuItems[i].id); + pitem.replace("{i}", myMenuItems[i].id); + + root_html_template += pitem; + } +#endif + + root_html_template += String(BLYNK_WM_FLDSET_END) + BLYNK_WM_HTML_BUTTON + BLYNK_WM_HTML_SCRIPT; + +#if USE_DYNAMIC_PARAMETERS + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) + { + pitem = String(BLYNK_WM_HTML_SCRIPT_ITEM); + + pitem.replace("{d}", myMenuItems[i].id); + + root_html_template += pitem; + } +#endif + + root_html_template += String(BLYNK_WM_HTML_SCRIPT_END) + BLYNK_WM_HTML_END; + + return; + } + + ////////////////////////////////////////////// + + void serverSendHeaders() + { + BLYNK_LOG4(F("serverSendHeaders:WM_HTTP_CACHE_CONTROL:"), WM_HTTP_CACHE_CONTROL, "=", WM_HTTP_NO_STORE); + server->sendHeader(WM_HTTP_CACHE_CONTROL, WM_HTTP_NO_STORE); + +#if USING_CORS_FEATURE + // New from v1.2.0, for configure CORS Header, default to WM_HTTP_CORS_ALLOW_ALL = "*" + BLYNK_LOG4(F("serverSendHeaders:WM_HTTP_CORS:"), WM_HTTP_CORS, " : ", _CORS_Header); + server->sendHeader(WM_HTTP_CORS, _CORS_Header); +#endif + + BLYNK_LOG4(F("serverSendHeaders:WM_HTTP_PRAGMA:"), WM_HTTP_PRAGMA, " : ", WM_HTTP_NO_CACHE); + server->sendHeader(WM_HTTP_PRAGMA, WM_HTTP_NO_CACHE); + + BLYNK_LOG4(F("serverSendHeaders:WM_HTTP_EXPIRES:"), WM_HTTP_EXPIRES, " : ", "-1"); + server->sendHeader(WM_HTTP_EXPIRES, "-1"); + } + + ////////////////////////////////////////////// + +#else void createHTML(String &root_html_template) { @@ -1986,7 +2182,8 @@ class BlynkWifi return; } - +#endif + ////////////////////////////////////////////// void handleRequest() @@ -2000,6 +2197,10 @@ class BlynkWifi if (key == "" && value == "") { + // New from v1.2.0 + serverSendHeaders(); + ////// + String result; createHTML(result); @@ -2042,7 +2243,7 @@ class BlynkWifi } #endif - server->send(200, "text/html", result); + server->send(200, WM_HTTP_HEAD_TEXT_HTML, result); return; } @@ -2088,97 +2289,127 @@ class BlynkWifi static bool pt_Updated = false; static bool nm_Updated = false; - if (!id_Updated && (key == String("id"))) + if (key == String("id")) { - id_Updated = true; - number_items_Updated++; + if (!id_Updated) + { + id_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(Blynk8266_WM_config.WiFi_Creds[0].wifi_ssid) - 1) strcpy(Blynk8266_WM_config.WiFi_Creds[0].wifi_ssid, value.c_str()); else strncpy(Blynk8266_WM_config.WiFi_Creds[0].wifi_ssid, value.c_str(), sizeof(Blynk8266_WM_config.WiFi_Creds[0].wifi_ssid) - 1); } - else if (!pw_Updated && (key == String("pw"))) + else if (key == String("pw")) { - pw_Updated = true; - number_items_Updated++; + if (!pw_Updated) + { + pw_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(Blynk8266_WM_config.WiFi_Creds[0].wifi_pw) - 1) strcpy(Blynk8266_WM_config.WiFi_Creds[0].wifi_pw, value.c_str()); else strncpy(Blynk8266_WM_config.WiFi_Creds[0].wifi_pw, value.c_str(), sizeof(Blynk8266_WM_config.WiFi_Creds[0].wifi_pw) - 1); } - else if (!id1_Updated && (key == String("id1"))) + else if (key == String("id1")) { - id1_Updated = true; - number_items_Updated++; + if (!id1_Updated) + { + id1_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(Blynk8266_WM_config.WiFi_Creds[1].wifi_ssid) - 1) strcpy(Blynk8266_WM_config.WiFi_Creds[1].wifi_ssid, value.c_str()); else strncpy(Blynk8266_WM_config.WiFi_Creds[1].wifi_ssid, value.c_str(), sizeof(Blynk8266_WM_config.WiFi_Creds[1].wifi_ssid) - 1); } - else if (!pw1_Updated && (key == String("pw1"))) + else if (key == String("pw1")) { - pw1_Updated = true; - number_items_Updated++; + if (!pw1_Updated) + { + pw1_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(Blynk8266_WM_config.WiFi_Creds[1].wifi_pw) - 1) strcpy(Blynk8266_WM_config.WiFi_Creds[1].wifi_pw, value.c_str()); else strncpy(Blynk8266_WM_config.WiFi_Creds[1].wifi_pw, value.c_str(), sizeof(Blynk8266_WM_config.WiFi_Creds[1].wifi_pw) - 1); } - else if (!sv_Updated && (key == String("sv"))) + else if (key == String("sv")) { - sv_Updated = true; - number_items_Updated++; + if (!sv_Updated) + { + sv_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(Blynk8266_WM_config.Blynk_Creds[0].blynk_server) - 1) strcpy(Blynk8266_WM_config.Blynk_Creds[0].blynk_server, value.c_str()); else strncpy(Blynk8266_WM_config.Blynk_Creds[0].blynk_server, value.c_str(), sizeof(Blynk8266_WM_config.Blynk_Creds[0].blynk_server) - 1); } - else if (!tk_Updated && (key == String("tk"))) + else if (key == String("tk")) { - tk_Updated = true; - number_items_Updated++; + if (!tk_Updated) + { + tk_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(Blynk8266_WM_config.Blynk_Creds[0].blynk_token) - 1) strcpy(Blynk8266_WM_config.Blynk_Creds[0].blynk_token, value.c_str()); else strncpy(Blynk8266_WM_config.Blynk_Creds[0].blynk_token, value.c_str(), sizeof(Blynk8266_WM_config.Blynk_Creds[0].blynk_token) - 1); } - else if (!sv1_Updated && (key == String("sv1"))) + else if (key == String("sv1")) { - sv1_Updated = true; - number_items_Updated++; + if (!sv1_Updated) + { + sv1_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(Blynk8266_WM_config.Blynk_Creds[1].blynk_server) - 1) strcpy(Blynk8266_WM_config.Blynk_Creds[1].blynk_server, value.c_str()); else strncpy(Blynk8266_WM_config.Blynk_Creds[1].blynk_server, value.c_str(), sizeof(Blynk8266_WM_config.Blynk_Creds[1].blynk_server) - 1); } - else if (!tk1_Updated && (key == String("tk1"))) + else if (key == String("tk1")) { - tk1_Updated = true; - number_items_Updated++; + if (!tk1_Updated) + { + tk1_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(Blynk8266_WM_config.Blynk_Creds[1].blynk_token) - 1) strcpy(Blynk8266_WM_config.Blynk_Creds[1].blynk_token, value.c_str()); else strncpy(Blynk8266_WM_config.Blynk_Creds[1].blynk_token, value.c_str(), sizeof(Blynk8266_WM_config.Blynk_Creds[1].blynk_token) - 1); } - else if (!pt_Updated && (key == String("pt"))) + else if (key == String("pt")) { - pt_Updated = true; - number_items_Updated++; + if (!pt_Updated) + { + pt_Updated = true; + number_items_Updated++; + } Blynk8266_WM_config.blynk_port = value.toInt(); } - else if (!nm_Updated && (key == String("nm"))) + else if (key == String("nm")) { - nm_Updated = true; - number_items_Updated++; + if (!nm_Updated) + { + nm_Updated = true; + number_items_Updated++; + } if (strlen(value.c_str()) < sizeof(Blynk8266_WM_config.board_name) - 1) strcpy(Blynk8266_WM_config.board_name, value.c_str()); @@ -2187,31 +2418,37 @@ class BlynkWifi } #if USE_DYNAMIC_PARAMETERS - for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) + else { - if ( !menuItemUpdated[i] && (key == myMenuItems[i].id) ) + for (uint16_t i = 0; i < NUM_MENU_ITEMS; i++) { - BLYNK_LOG4(BLYNK_F("h:"), myMenuItems[i].id, BLYNK_F("="), value.c_str() ); - - menuItemUpdated[i] = true; - - number_items_Updated++; + if ( !menuItemUpdated[i] && (key == myMenuItems[i].id) ) + { + BLYNK_LOG4(BLYNK_F("h:"), myMenuItems[i].id, BLYNK_F("="), value.c_str() ); + + if (!menuItemUpdated[i]) + { + menuItemUpdated[i] = true; + number_items_Updated++; + } - // Actual size of pdata is [maxlen + 1] - memset(myMenuItems[i].pdata, 0, myMenuItems[i].maxlen + 1); + // Actual size of pdata is [maxlen + 1] + memset(myMenuItems[i].pdata, 0, myMenuItems[i].maxlen + 1); - if ((int) strlen(value.c_str()) < myMenuItems[i].maxlen) - strcpy(myMenuItems[i].pdata, value.c_str()); - else - strncpy(myMenuItems[i].pdata, value.c_str(), myMenuItems[i].maxlen); -#if ( BLYNK_WM_DEBUG > 2) - BLYNK_LOG4(BLYNK_F("h2:myMenuItems["), i, BLYNK_F("]="), myMenuItems[i].pdata ); -#endif + if ((int) strlen(value.c_str()) < myMenuItems[i].maxlen) + strcpy(myMenuItems[i].pdata, value.c_str()); + else + strncpy(myMenuItems[i].pdata, value.c_str(), myMenuItems[i].maxlen); + #if ( BLYNK_WM_DEBUG > 2) + BLYNK_LOG4(BLYNK_F("h2:myMenuItems["), i, BLYNK_F("]="), myMenuItems[i].pdata ); + #endif + break; + } } } #endif - server->send(200, "text/html", "OK"); + server->send(200, WM_HTTP_HEAD_TEXT_HTML, "OK"); #if USE_DYNAMIC_PARAMETERS if (number_items_Updated == NUM_CONFIGURABLE_ITEMS + NUM_MENU_ITEMS) @@ -2246,10 +2483,13 @@ class BlynkWifi ////////////////////////////////////////////// +#ifndef CONFIG_TIMEOUT + #warning Default CONFIG_TIMEOUT = 60s + #define CONFIG_TIMEOUT 60000L +#endif + void startConfigurationMode() { -#define CONFIG_TIMEOUT 60000L - // turn the LED_BUILTIN ON to tell us we are in configuration mode. digitalWrite(LED_BUILTIN, LED_ON);