From 795f6d605b0a80a7cbe5fc770b0b071138abd695 Mon Sep 17 00:00:00 2001 From: Khoi Hoang <57012152+khoih-prog@users.noreply.github.com> Date: Thu, 8 Dec 2022 19:03:44 -0500 Subject: [PATCH] v1.0.0 for `ESP32 + W5500` #### Releases v1.0.0 1. Initial coding to port [ESPAsync_WiFiManager](https://github.com/khoih-prog/ESPAsync_WiFiManager) to ESP32 boards using `LwIP W5500 Ethernet`. 2. Use `allman astyle` --- CONTRIBUTING.md | 79 + Images/Configuration_AIO_MQTT.png | Bin 0 -> 36319 bytes Images/Configuration_Standard.png | Bin 0 -> 17154 bytes Images/Info.png | Bin 0 -> 71628 bytes Images/Main.png | Bin 0 -> 13218 bytes Images/Saved.png | Bin 0 -> 2478 bytes LICENSE | 2 +- changelog.md | 33 + .../Async_ConfigOnDoubleReset.ino | 740 ++++++++ .../Async_ConfigOnDoubleReset_TZ.ino | 768 +++++++++ .../Async_ConfigOnSwitch.ino | 933 ++++++++++ examples/Async_ConfigOnSwitch/README.md | 12 + .../Async_ConfigOnSwitchFS.ino | 1135 ++++++++++++ .../Async_ConfigPortalParamsOnSwitch.ino | 1031 +++++++++++ .../Async_ESP32_FSWebServer.ino | 892 ++++++++++ examples/Async_ESP32_FSWebServer/README.md | 81 + .../data/CanadaFlag_1.png | Bin 0 -> 41214 bytes .../data/CanadaFlag_2.png | Bin 0 -> 8311 bytes .../data/CanadaFlag_3.jpg | Bin 0 -> 11156 bytes .../Async_ESP32_FSWebServer/data/edit.htm.gz | Bin 0 -> 4116 bytes .../Async_ESP32_FSWebServer/data/favicon.ico | Bin 0 -> 1150 bytes .../Async_ESP32_FSWebServer/data/graphs.js.gz | Bin 0 -> 1971 bytes .../Async_ESP32_FSWebServer/data/index.htm | 97 ++ .../pics/async-esp32fs.local.png | Bin 0 -> 20520 bytes .../pics/async-esp32fs.local_edit.png | Bin 0 -> 76964 bytes .../Async_ESP32_FSWebServer_DRD.ino | 949 ++++++++++ .../Async_ESP32_FSWebServer_DRD/README.md | 82 + .../data/CanadaFlag_1.png | Bin 0 -> 41214 bytes .../data/CanadaFlag_2.png | Bin 0 -> 8311 bytes .../data/CanadaFlag_3.jpg | Bin 0 -> 11156 bytes .../data/edit.htm.gz | Bin 0 -> 4116 bytes .../data/favicon.ico | Bin 0 -> 1150 bytes .../data/graphs.js.gz | Bin 0 -> 1971 bytes .../data/index.htm | 97 ++ .../pics/async-esp32fs.local.png | Bin 0 -> 20520 bytes .../pics/async-esp32fs.local_edit.png | Bin 0 -> 76964 bytes keywords.txt | 92 + library.json | 56 + library.properties | 12 + platformio/platformio.ini | 102 ++ src/AsyncESP32_W5500_Manager.h | 36 + src/AsyncESP32_W5500_Manager.hpp | 624 +++++++ src/AsyncESP32_W5500_Manager_Debug.h | 95 + src/AsyncESP32_W5500_Manager_Impl.h | 1324 ++++++++++++++ src/utils/TZ.h | 1526 +++++++++++++++++ travis/common.sh | 51 + utils/astyle_library.conf | 70 + utils/restyle.sh | 6 + 48 files changed, 10924 insertions(+), 1 deletion(-) create mode 100644 CONTRIBUTING.md create mode 100644 Images/Configuration_AIO_MQTT.png create mode 100644 Images/Configuration_Standard.png create mode 100644 Images/Info.png create mode 100644 Images/Main.png create mode 100644 Images/Saved.png create mode 100644 changelog.md create mode 100644 examples/Async_ConfigOnDoubleReset/Async_ConfigOnDoubleReset.ino create mode 100644 examples/Async_ConfigOnDoubleReset_TZ/Async_ConfigOnDoubleReset_TZ.ino create mode 100644 examples/Async_ConfigOnSwitch/Async_ConfigOnSwitch.ino create mode 100644 examples/Async_ConfigOnSwitch/README.md create mode 100644 examples/Async_ConfigOnSwitchFS/Async_ConfigOnSwitchFS.ino create mode 100644 examples/Async_ConfigPortalParamsOnSwitch/Async_ConfigPortalParamsOnSwitch.ino create mode 100644 examples/Async_ESP32_FSWebServer/Async_ESP32_FSWebServer.ino create mode 100644 examples/Async_ESP32_FSWebServer/README.md create mode 100644 examples/Async_ESP32_FSWebServer/data/CanadaFlag_1.png create mode 100644 examples/Async_ESP32_FSWebServer/data/CanadaFlag_2.png create mode 100644 examples/Async_ESP32_FSWebServer/data/CanadaFlag_3.jpg create mode 100644 examples/Async_ESP32_FSWebServer/data/edit.htm.gz create mode 100644 examples/Async_ESP32_FSWebServer/data/favicon.ico create mode 100644 examples/Async_ESP32_FSWebServer/data/graphs.js.gz create mode 100644 examples/Async_ESP32_FSWebServer/data/index.htm create mode 100644 examples/Async_ESP32_FSWebServer/pics/async-esp32fs.local.png create mode 100644 examples/Async_ESP32_FSWebServer/pics/async-esp32fs.local_edit.png create mode 100644 examples/Async_ESP32_FSWebServer_DRD/Async_ESP32_FSWebServer_DRD.ino create mode 100644 examples/Async_ESP32_FSWebServer_DRD/README.md create mode 100644 examples/Async_ESP32_FSWebServer_DRD/data/CanadaFlag_1.png create mode 100644 examples/Async_ESP32_FSWebServer_DRD/data/CanadaFlag_2.png create mode 100644 examples/Async_ESP32_FSWebServer_DRD/data/CanadaFlag_3.jpg create mode 100644 examples/Async_ESP32_FSWebServer_DRD/data/edit.htm.gz create mode 100644 examples/Async_ESP32_FSWebServer_DRD/data/favicon.ico create mode 100644 examples/Async_ESP32_FSWebServer_DRD/data/graphs.js.gz create mode 100644 examples/Async_ESP32_FSWebServer_DRD/data/index.htm create mode 100644 examples/Async_ESP32_FSWebServer_DRD/pics/async-esp32fs.local.png create mode 100644 examples/Async_ESP32_FSWebServer_DRD/pics/async-esp32fs.local_edit.png create mode 100644 keywords.txt create mode 100644 library.json create mode 100644 library.properties create mode 100644 platformio/platformio.ini create mode 100644 src/AsyncESP32_W5500_Manager.h create mode 100644 src/AsyncESP32_W5500_Manager.hpp create mode 100644 src/AsyncESP32_W5500_Manager_Debug.h create mode 100644 src/AsyncESP32_W5500_Manager_Impl.h create mode 100644 src/utils/TZ.h create mode 100644 travis/common.sh create mode 100644 utils/astyle_library.conf create mode 100644 utils/restyle.sh diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..000bdac --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,79 @@ +## Contributing to AsyncESP32_W5500_Manager + +### Reporting Bugs + +Please report bugs in AsyncESP32_W5500_Manager if you find them. + +However, before reporting a bug please check through the following: + +* [Existing Open Issues](https://github.com/khoih-prog/AsyncESP32_W5500_Manager/issues) - someone might have already encountered this. + +If you don't find anything, please [open a new issue](https://github.com/khoih-prog/AsyncESP32_W5500_Manager/issues/new). + +### How to submit a bug report + +Please ensure to specify the following: + +* Arduino IDE version (e.g. 1.8.19) or Platform.io version +* Board Core Version (e.g. ESP32 core v2.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: + * Operating system (Windows, Ubuntu, etc.) and the output of `uname -a` + * Network configuration + + +Please be educated, civilized and constructive. Disrespective posts against [GitHub Code of Conduct](https://docs.github.com/en/site-policy/github-terms/github-event-code-of-conduct) will be ignored and deleted. + + +### Example + +``` +Arduino IDE version: 1.8.19 +ESP32_DEV board +ESP32 core v2.0.5 +OS: Ubuntu 20.04 LTS +Linux xy-Inspiron-3593 5.15.0-56-generic #62~20.04.1-Ubuntu SMP Tue Nov 22 21:24:20 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux + +Context: +I encountered a crash while using this library +Steps to reproduce: +1. ... +2. ... +3. ... +4. ... +``` + +### Additional context + +Add any other context about the problem here. + +--- + +### Sending Feature Requests + +Feel free to post feature requests. It's helpful if you can explain exactly why the feature would be useful. + +There are usually some outstanding feature requests in the [existing issues list](https://github.com/khoih-prog/AsyncESP32_W5500_Manager/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement), feel free to add comments to them. + +--- + +### Sending Pull Requests + +Pull Requests with changes and fixes are also welcome! + +Please use the `astyle` to reformat the updated library code as follows (demo for Ubuntu Linux) + +1. Change directory to the library GitHub + +``` +xy@xy-Inspiron-3593:~$ cd Arduino/xy/AsyncESP32_W5500_Manager_GitHub/ +xy@xy-Inspiron-3593:~/Arduino/xy/AsyncESP32_W5500_Manager_GitHub$ +``` + +2. Issue astyle command + +``` +xy@xy-Inspiron-3593:~/Arduino/xy/AsyncESP32_W5500_Manager_GitHub$ bash utils/restyle.sh +``` + diff --git a/Images/Configuration_AIO_MQTT.png b/Images/Configuration_AIO_MQTT.png new file mode 100644 index 0000000000000000000000000000000000000000..75ad6cae61aee038fffedbb4181e0538c6f26ded GIT binary patch literal 36319 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 literal 0 HcmV?d00001 diff --git a/Images/Info.png b/Images/Info.png new file mode 100644 index 0000000000000000000000000000000000000000..2abf11551c33b4bfe21e9b167a3699999235226c GIT binary patch literal 71628 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%0AB~y&vKtm<`yiNT@_UMKa7axzX`e8%n2E10WDXet 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 SfO_`S2bdXK7**eOiufBfPNa4K literal 0 HcmV?d00001 diff --git a/Images/Main.png b/Images/Main.png new file mode 100644 index 0000000000000000000000000000000000000000..6d570d3509eee5e005258bea12d8c076bfbcaf65 GIT binary patch literal 13218 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 literal 0 HcmV?d00001 diff --git a/Images/Saved.png b/Images/Saved.png new file mode 100644 index 0000000000000000000000000000000000000000..a4c89cb9d7076c3e3c9a8e06ba0bd14673eaf9b5 GIT binary patch literal 2478 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 Fe*ystmM8!K literal 0 HcmV?d00001 diff --git a/LICENSE b/LICENSE index ab72b02..4a9150f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 Khoi Hoang +Copyright (c) 2019 Khoi Hoang Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/changelog.md b/changelog.md new file mode 100644 index 0000000..f5f6dc0 --- /dev/null +++ b/changelog.md @@ -0,0 +1,33 @@ +# AsyncWebServer_ESP32_W5500 Library + +[![arduino-library-badge](https://www.ardu-badge.com/badge/AsyncWebServer_ESP32_W5500.svg?)](https://www.ardu-badge.com/AsyncWebServer_ESP32_W5500) +[![GitHub release](https://img.shields.io/github/release/khoih-prog/AsyncWebServer_ESP32_W5500.svg)](https://github.com/khoih-prog/AsyncWebServer_ESP32_W5500/releases) +[![GitHub](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/khoih-prog/AsyncWebServer_ESP32_W5500/blob/main/LICENSE) +[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](#Contributing) +[![GitHub issues](https://img.shields.io/github/issues/khoih-prog/AsyncWebServer_ESP32_W5500.svg)](http://github.com/khoih-prog/AsyncWebServer_ESP32_W5500/issues) + +Donate to my libraries using BuyMeACoffee + + + +--- +--- + +## Table of contents + +* [Changelog](#changelog) + * [Releases v1.0.0](#releases-v162) + + + +--- +--- + +## Changelog + +#### Releases v1.0.0 + +1. Initial coding to port [ESPAsync_WiFiManager](https://github.com/khoih-prog/ESPAsync_WiFiManager) to ESP32 boards using `LwIP W5500 Ethernet`. +2. Use `allman astyle` + + diff --git a/examples/Async_ConfigOnDoubleReset/Async_ConfigOnDoubleReset.ino b/examples/Async_ConfigOnDoubleReset/Async_ConfigOnDoubleReset.ino new file mode 100644 index 0000000..807a796 --- /dev/null +++ b/examples/Async_ConfigOnDoubleReset/Async_ConfigOnDoubleReset.ino @@ -0,0 +1,740 @@ +/**************************************************************************************************************************** + Async_ConfigOnDoubleReset.ino + WebServer_ESP32_W5500 is a library for the ESP32 with Ethernet W5500 to run WebServer + + Based on and modified from ESP32-IDF https://github.com/espressif/esp-idf + Built by Khoi Hoang https://github.com/khoih-prog/WebServer_ESP32_W5500 + + Modified from + 1. Tzapu (https://github.com/tzapu/WiFiManager) + 2. Ken Taylor (https://github.com/kentaylor) + 3. Alan Steremberg (https://github.com/alanswx/ESPAsyncWiFiManager) + 4. Khoi Hoang (https://github.com/khoih-prog/AsyncESP32_W5500_manager) + + Built by Khoi Hoang https://github.com/khoih-prog/AsyncESP32_W5500_Manager + Licensed under MIT license + *****************************************************************************************************************************/ +/**************************************************************************************************************************** + This example will open a configuration portal when the reset button is pressed twice. + This method works well on Wemos boards which have a single reset button on board. It avoids using a pin for launching the configuration portal. + + Settings + There are two values to be set in the sketch. + + DRD_TIMEOUT - Number of seconds to wait for the second reset. Set to 10 in the example. + DRD_ADDRESS - The address in ESP8266 RTC RAM to store the flag. This memory must not be used for other purposes in the same sketch. Set to 0 in the example. + + This example, originally relied on the Double Reset Detector library from https://github.com/datacute/DoubleResetDetector + To support ESP32, use ESP_DoubleResetDetector library from //https://github.com/khoih-prog/ESP_DoubleResetDetector + *****************************************************************************************************************************/ + +#if !( defined(ESP32) ) + #error This code is intended to run on the (ESP32 + W5500) platform! Please check your Tools->Board setting. +#endif + +// Use from 0 to 4. Higher number, more debugging messages and memory usage. +#define _ESPASYNC_ETH_MGR_LOGLEVEL_ 4 + +// To not display stored SSIDs and PWDs on Config Portal, select false. Default is true +// Even the stored Credentials are not display, just leave them all blank to reconnect and reuse the stored Credentials +//#define DISPLAY_STORED_CREDENTIALS_IN_CP false + +////////////////////////////////////////////////////////////// + +// Enter a MAC address and IP address for your controller below. +#define NUMBER_OF_MAC 20 + +byte mac[][NUMBER_OF_MAC] = +{ + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x01 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x02 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x03 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x04 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x05 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x06 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x07 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x08 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x09 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0A }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0B }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0C }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0D }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0E }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0F }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x10 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x11 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x12 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x13 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x14 }, +}; + +// Select the IP address according to your local network +//IPAddress myIP(192, 168, 2, 232); +//IPAddress myGW(192, 168, 2, 1); +//IPAddress mySN(255, 255, 255, 0); + +// Google DNS Server IP +//IPAddress myDNS(8, 8, 8, 8); + +////////////////////////////////////////////////////////// + +// Optional values to override default settings +// Don't change unless you know what you're doing +//#define ETH_SPI_HOST SPI3_HOST +//#define SPI_CLOCK_MHZ 25 + +// Must connect INT to GPIOxx or not working +//#define INT_GPIO 4 + +//#define MISO_GPIO 19 +//#define MOSI_GPIO 23 +//#define SCK_GPIO 18 +//#define CS_GPIO 5 + +////////////////////////////////////////////////////////// + +#include + +//Ported to ESP32 +#include + +// LittleFS has higher priority than SPIFFS +#if ( defined(ESP_ARDUINO_VERSION_MAJOR) && (ESP_ARDUINO_VERSION_MAJOR >= 2) ) + #define USE_LITTLEFS true + #define USE_SPIFFS false +#elif defined(ARDUINO_ESP32C3_DEV) + // For core v1.0.6-, ESP32-C3 only supporting SPIFFS and EEPROM. To use v2.0.0+ for LittleFS + #define USE_LITTLEFS false + #define USE_SPIFFS true +#endif + +#if USE_LITTLEFS + // Use LittleFS + #include "FS.h" + + // Check cores/esp32/esp_arduino_version.h and cores/esp32/core_version.h + //#if ( ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(2, 0, 0) ) //(ESP_ARDUINO_VERSION_MAJOR >= 2) + #if ( defined(ESP_ARDUINO_VERSION_MAJOR) && (ESP_ARDUINO_VERSION_MAJOR >= 2) ) + #if (_ESPASYNC_ETH_MGR_LOGLEVEL_ > 3) + #warning Using ESP32 Core 1.0.6 or 2.0.0+ + #endif + + // The library has been merged into esp32 core from release 1.0.6 + #include // https://github.com/espressif/arduino-esp32/tree/master/libraries/LittleFS + + FS* filesystem = &LittleFS; + #define FileFS LittleFS + #define FS_Name "LittleFS" + #else + #if (_ESPASYNC_ETH_MGR_LOGLEVEL_ > 3) + #warning Using ESP32 Core 1.0.5-. You must install LITTLEFS library + #endif + + // The library has been merged into esp32 core from release 1.0.6 + #include // https://github.com/lorol/LITTLEFS + + FS* filesystem = &LITTLEFS; + #define FileFS LITTLEFS + #define FS_Name "LittleFS" + #endif + +#elif USE_SPIFFS + #include + FS* filesystem = &SPIFFS; + #define FileFS SPIFFS + #define FS_Name "SPIFFS" +#else + // +Use FFat + #include + FS* filesystem = &FFat; + #define FileFS FFat + #define FS_Name "FFat" +#endif +////// + +#define LED_BUILTIN 2 +#define LED_ON HIGH +#define LED_OFF LOW + +// These defines must be put before #include +// to select where to store DoubleResetDetector's variable. +// For ESP32, You must select one to be true (EEPROM or SPIFFS) +// For ESP8266, You must select one to be true (RTC, EEPROM, SPIFFS or LITTLEFS) +// Otherwise, library will use default EEPROM storage + +// These defines must be put before #include +// to select where to store DoubleResetDetector's variable. +// For ESP32, You must select one to be true (EEPROM or SPIFFS) +// Otherwise, library will use default EEPROM storage +#if USE_LITTLEFS + #define ESP_DRD_USE_LITTLEFS true + #define ESP_DRD_USE_SPIFFS false + #define ESP_DRD_USE_EEPROM false +#elif USE_SPIFFS + #define ESP_DRD_USE_LITTLEFS false + #define ESP_DRD_USE_SPIFFS true + #define ESP_DRD_USE_EEPROM false +#else + #define ESP_DRD_USE_LITTLEFS false + #define ESP_DRD_USE_SPIFFS false + #define ESP_DRD_USE_EEPROM true +#endif + +#define DOUBLERESETDETECTOR_DEBUG true //false + +#include //https://github.com/khoih-prog/ESP_DoubleResetDetector + +// Number of seconds after reset during which a +// subseqent reset will be considered a double reset. +#define DRD_TIMEOUT 10 + +// RTC Memory Address for the DoubleResetDetector to use +#define DRD_ADDRESS 0 + +//DoubleResetDetector drd(DRD_TIMEOUT, DRD_ADDRESS); +DoubleResetDetector* drd;////// + +// Onboard LED I/O pin on NodeMCU board +const int PIN_LED = 2; // D4 on NodeMCU and WeMos. GPIO2/ADC12 of ESP32. Controls the onboard LED. + +// You only need to format the filesystem once +//#define FORMAT_FILESYSTEM true +#define FORMAT_FILESYSTEM false + +// Assuming max 49 chars +#define TZNAME_MAX_LEN 50 +#define TIMEZONE_MAX_LEN 50 + +typedef struct +{ + char TZ_Name[TZNAME_MAX_LEN]; // "America/Toronto" + char TZ[TIMEZONE_MAX_LEN]; // "EST5EDT,M3.2.0,M11.1.0" + uint16_t checksum; +} EthConfig; + +EthConfig Ethconfig; + +#define CONFIG_FILENAME F("/eth_cred.dat") +////// + +// Indicates whether ESP has credentials saved from previous session, or double reset detected +bool initialConfig = false; + +// Use false if you don't like to display Available Pages in Information Page of Config Portal +// Comment out or use true to display Available Pages in Information Page of Config Portal +// Must be placed before #include +#define USE_AVAILABLE_PAGES true //false + +// To permit disable/enable StaticIP configuration in Config Portal from sketch. Valid only if DHCP is used. +// You'll loose the feature of dynamically changing from DHCP to static IP, or vice versa +// You have to explicitly specify false to disable the feature. +//#define USE_STATIC_IP_CONFIG_IN_CP false + +// Use false to disable NTP config. Advisable when using Cellphone, Tablet to access Config Portal. +// See Issue 23: On Android phone ConfigPortal is unresponsive (https://github.com/khoih-prog/ESP_WiFiManager/issues/23) +#define USE_ESP_ETH_MANAGER_NTP true //false + +// Just use enough to save memory. On ESP8266, can cause blank ConfigPortal screen +// if using too much memory +#define USING_AFRICA false +#define USING_AMERICA true +#define USING_ANTARCTICA false +#define USING_ASIA false +#define USING_ATLANTIC false +#define USING_AUSTRALIA false +#define USING_EUROPE false +#define USING_INDIAN false +#define USING_PACIFIC false +#define USING_ETC_GMT false + +// Use true to enable CloudFlare NTP service. System can hang if you don't have Internet access while accessing CloudFlare +// See Issue #21: CloudFlare link in the default portal (https://github.com/khoih-prog/ESP_WiFiManager/issues/21) +#define USE_CLOUDFLARE_NTP false + +#define USING_CORS_FEATURE true + +//////////////////////////////////////////// + +// Use USE_DHCP_IP == true for dynamic DHCP IP, false to use static IP which you have to change accordingly to your network +#if (defined(USE_STATIC_IP_CONFIG_IN_CP) && !USE_STATIC_IP_CONFIG_IN_CP) + // Force DHCP to be true + #if defined(USE_DHCP_IP) + #undef USE_DHCP_IP + #endif + #define USE_DHCP_IP true +#else + // You can select DHCP or Static IP here + //#define USE_DHCP_IP true + #define USE_DHCP_IP false +#endif + +#if ( USE_DHCP_IP ) + // Use DHCP + + #if (_ESPASYNC_ETH_MGR_LOGLEVEL_ > 3) + #warning Using DHCP IP + #endif + + IPAddress stationIP = IPAddress(0, 0, 0, 0); + IPAddress gatewayIP = IPAddress(192, 168, 2, 1); + IPAddress netMask = IPAddress(255, 255, 255, 0); + +#else + // Use static IP + + #if (_ESPASYNC_ETH_MGR_LOGLEVEL_ > 3) + #warning Using static IP + #endif + + IPAddress stationIP = IPAddress(192, 168, 2, 232); + IPAddress gatewayIP = IPAddress(192, 168, 2, 1); + IPAddress netMask = IPAddress(255, 255, 255, 0); +#endif + +//////////////////////////////////////////// + + +#define USE_CONFIGURABLE_DNS true + +IPAddress dns1IP = gatewayIP; +IPAddress dns2IP = IPAddress(8, 8, 8, 8); + +#include //https://github.com/khoih-prog/AsyncESP32_W5500_Manager + +#define HTTP_PORT 80 + +/////////////////////////////////////////// +/****************************************** + // Defined in AsyncESP32_W5500_Manager.hpp + typedef struct + { + IPAddress _sta_static_ip; + IPAddress _sta_static_gw; + IPAddress _sta_static_sn; + #if USE_CONFIGURABLE_DNS + IPAddress _sta_static_dns1; + IPAddress _sta_static_dns2; + #endif + } ETH_STA_IPConfig; +******************************************/ + +ETH_STA_IPConfig EthSTA_IPconfig; + +void initSTAIPConfigStruct(ETH_STA_IPConfig &in_EthSTA_IPconfig) +{ + in_EthSTA_IPconfig._sta_static_ip = stationIP; + in_EthSTA_IPconfig._sta_static_gw = gatewayIP; + in_EthSTA_IPconfig._sta_static_sn = netMask; +#if USE_CONFIGURABLE_DNS + in_EthSTA_IPconfig._sta_static_dns1 = dns1IP; + in_EthSTA_IPconfig._sta_static_dns2 = dns2IP; +#endif +} + +void displayIPConfigStruct(ETH_STA_IPConfig in_EthSTA_IPconfig) +{ + LOGERROR3(F("stationIP ="), in_EthSTA_IPconfig._sta_static_ip, ", gatewayIP =", in_EthSTA_IPconfig._sta_static_gw); + LOGERROR1(F("netMask ="), in_EthSTA_IPconfig._sta_static_sn); +#if USE_CONFIGURABLE_DNS + LOGERROR3(F("dns1IP ="), in_EthSTA_IPconfig._sta_static_dns1, ", dns2IP =", in_EthSTA_IPconfig._sta_static_dns2); +#endif +} + +#if USE_ESP_ETH_MANAGER_NTP + +void printLocalTime() +{ + struct tm timeinfo; + + getLocalTime( &timeinfo ); + + // Valid only if year > 2000. + // You can get from timeinfo : tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec + if (timeinfo.tm_year > 100 ) + { + Serial.print("Local Date/Time: "); + Serial.print( asctime( &timeinfo ) ); + } +} + +#endif + +void heartBeatPrint() +{ +#if USE_ESP_ETH_MANAGER_NTP + printLocalTime(); +#else + static int num = 1; + + if (ESP32_W5500_isConnected()) + Serial.print(F("H")); // H means connected to Ethernet + else + Serial.print(F("F")); // F means not connected to Ethernet + + if (num == 80) + { + Serial.println(); + num = 1; + } + else if (num++ % 10 == 0) + { + Serial.print(F(" ")); + } + +#endif +} + +void check_status() +{ + static ulong checkstatus_timeout = 0; + + static ulong current_millis; + +#if USE_ESP_ETH_MANAGER_NTP +#define HEARTBEAT_INTERVAL 60000L +#else +#define HEARTBEAT_INTERVAL 10000L +#endif + + current_millis = millis(); + + // Print hearbeat every HEARTBEAT_INTERVAL (10) seconds. + if ((current_millis > checkstatus_timeout) || (checkstatus_timeout == 0)) + { + heartBeatPrint(); + checkstatus_timeout = current_millis + HEARTBEAT_INTERVAL; + } +} + +int calcChecksum(uint8_t* address, uint16_t sizeToCalc) +{ + uint16_t checkSum = 0; + + for (uint16_t index = 0; index < sizeToCalc; index++) + { + checkSum += * ( ( (byte*) address ) + index); + } + + return checkSum; +} + +bool loadConfigData() +{ + File file = FileFS.open(CONFIG_FILENAME, "r"); + LOGERROR(F("LoadCfgFile ")); + + memset((void *) &Ethconfig, 0, sizeof(Ethconfig)); + memset((void *) &EthSTA_IPconfig, 0, sizeof(EthSTA_IPconfig)); + + if (file) + { + file.readBytes((char *) &Ethconfig, sizeof(Ethconfig)); + file.readBytes((char *) &EthSTA_IPconfig, sizeof(EthSTA_IPconfig)); + file.close(); + + LOGERROR(F("OK")); + + if ( Ethconfig.checksum != calcChecksum( (uint8_t*) &Ethconfig, sizeof(Ethconfig) - sizeof(Ethconfig.checksum) ) ) + { + LOGERROR(F("Ethconfig checksum wrong")); + + return false; + } + + displayIPConfigStruct(EthSTA_IPconfig); + + return true; + } + else + { + LOGERROR(F("failed")); + + return false; + } +} + +void saveConfigData() +{ + File file = FileFS.open(CONFIG_FILENAME, "w"); + LOGERROR(F("SaveCfgFile ")); + + if (file) + { + Ethconfig.checksum = calcChecksum( (uint8_t*) &Ethconfig, sizeof(Ethconfig) - sizeof(Ethconfig.checksum) ); + + file.write((uint8_t*) &Ethconfig, sizeof(Ethconfig)); + + displayIPConfigStruct(EthSTA_IPconfig); + + file.write((uint8_t*) &EthSTA_IPconfig, sizeof(EthSTA_IPconfig)); + file.close(); + + LOGERROR(F("OK")); + } + else + { + LOGERROR(F("failed")); + } +} + +void beginEthernet() +{ + LOGWARN(F("Default SPI pinout:")); + LOGWARN1(F("SPI_HOST:"), ETH_SPI_HOST); + LOGWARN1(F("MOSI:"), MOSI_GPIO); + LOGWARN1(F("MISO:"), MISO_GPIO); + LOGWARN1(F("SCK:"), SCK_GPIO); + LOGWARN1(F("CS:"), CS_GPIO); + LOGWARN1(F("INT:"), INT_GPIO); + LOGWARN1(F("SPI Clock (MHz):"), SPI_CLOCK_MHZ); + LOGWARN(F("=========================")); + + /////////////////////////////////// + + // To be called before ETH.begin() + ESP32_W5500_onEvent(); + + // start the ethernet connection and the server: + // Use DHCP dynamic IP and random mac + uint16_t index = millis() % NUMBER_OF_MAC; + + //bool begin(int MISO_GPIO, int MOSI_GPIO, int SCLK_GPIO, int CS_GPIO, int INT_GPIO, int SPI_CLOCK_MHZ, + // int SPI_HOST, uint8_t *W5500_Mac = W5500_Default_Mac); + //ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST ); + ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST, mac[index] ); +} + +void initEthernet() +{ +#if !( USE_DHCP_IP ) + displayIPConfigStruct(EthSTA_IPconfig); + + // Static IP, leave without this line to get IP via DHCP + //bool config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1 = 0, IPAddress dns2 = 0); + //ETH.config(stationIP, gatewayIP, netMask, dns1IP, dns2IP); + ETH.config(EthSTA_IPconfig._sta_static_ip, EthSTA_IPconfig._sta_static_gw, EthSTA_IPconfig._sta_static_sn, + EthSTA_IPconfig._sta_static_dns1); +#endif + + ESP32_W5500_waitForConnect(); +} + +void setup() +{ + // put your setup code here, to run once: + // initialize the LED digital pin as an output. + pinMode(PIN_LED, OUTPUT); + + Serial.begin(115200); + + while (!Serial && millis() < 5000); + + delay(200); + + Serial.print(F("\nStarting Async_ConfigOnDoubleReset using ")); + Serial.print(FS_Name); + Serial.print(F(" on ")); + Serial.print(ARDUINO_BOARD); + Serial.print(F(" with ")); + Serial.println(SHIELD_TYPE); + Serial.println(ASYNC_ESP32_W5500_MANAGER_VERSION); + Serial.println(ESP_DOUBLE_RESET_DETECTOR_VERSION); + + Serial.setDebugOutput(false); + + if (FORMAT_FILESYSTEM) + FileFS.format(); + + // Format FileFS if not yet + if (!FileFS.begin(true)) + { + Serial.println(F("SPIFFS/LittleFS failed! Already tried formatting.")); + + if (!FileFS.begin()) + { + // prevents debug info from the library to hide err message. + delay(100); + +#if USE_LITTLEFS + Serial.println(F("LittleFS failed!. Please use SPIFFS or EEPROM. Stay forever")); +#else + Serial.println(F("SPIFFS failed!. Please use LittleFS or EEPROM. Stay forever")); +#endif + + while (true) + { + delay(1); + } + } + } + + drd = new DoubleResetDetector(DRD_TIMEOUT, DRD_ADDRESS); + + unsigned long startedAt = millis(); + + beginEthernet(); + + initSTAIPConfigStruct(EthSTA_IPconfig); + + //Local intialization. Once its business is done, there is no need to keep it around + // Use this to default DHCP hostname to ESP32-XXXXXX + //AsyncESP32_W5500_Manager AsyncESP32_W5500_manager(&webServer, &dnsServer); + // Use this to personalize DHCP hostname (RFC952 conformed) + AsyncWebServer webServer(HTTP_PORT); + +#if ( USING_ESP32_S2 || USING_ESP32_C3 ) + AsyncESP32_W5500_Manager AsyncESP32_W5500_manager(&webServer, NULL, "AsyncConfigOnDoubleReset"); +#else + AsyncDNSServer dnsServer; + + AsyncESP32_W5500_Manager AsyncESP32_W5500_manager(&webServer, &dnsServer, "AsyncConfigOnDoubleReset"); +#endif + +#if !USE_DHCP_IP + // Set (static IP, Gateway, Subnetmask, DNS1 and DNS2) or (IP, Gateway, Subnetmask) + AsyncESP32_W5500_manager.setSTAStaticIPConfig(EthSTA_IPconfig); +#endif + +#if USING_CORS_FEATURE + AsyncESP32_W5500_manager.setCORSHeader("Your Access-Control-Allow-Origin"); +#endif + + bool configDataLoaded = false; + + if (loadConfigData()) + { + configDataLoaded = true; + + AsyncESP32_W5500_manager.setConfigPortalTimeout( + 120); //If no access point name has been previously entered disable timeout. + Serial.println(F("Got stored Credentials. Timeout 120s for Config Portal")); + +#if USE_ESP_ETH_MANAGER_NTP + + if ( strlen(Ethconfig.TZ_Name) > 0 ) + { + LOGERROR3(F("Current TZ_Name ="), Ethconfig.TZ_Name, F(", TZ = "), Ethconfig.TZ); + + //configTzTime(Ethconfig.TZ, "pool.ntp.org" ); + configTzTime(Ethconfig.TZ, "time.nist.gov", "0.pool.ntp.org", "1.pool.ntp.org"); + } + else + { + Serial.println(F("Current Timezone is not set. Enter Config Portal to set.")); + } + +#endif + } + + ////////////////////////////////// + + // Connect ETH now if using STA + initEthernet(); + + ////////////////////////////////// + + if (drd->detectDoubleReset()) + { + // DRD, disable timeout. + AsyncESP32_W5500_manager.setConfigPortalTimeout(0); + + Serial.println(F("Open Config Portal without Timeout: Double Reset Detected")); + initialConfig = true; + } + + if (initialConfig) + { + Serial.print(F("Starting configuration portal @ ")); + Serial.println(ETH.localIP()); + + digitalWrite(PIN_LED, LED_ON); // turn the LED on by making the voltage LOW to tell us we are in configuration mode. + + //sets timeout in seconds until configuration portal gets turned off. + //If not specified device will remain in configuration mode until + //switched off via webserver or device is restarted. + //AsyncESP32_W5500_manager.setConfigPortalTimeout(600); + + // Starts an access point + if (!AsyncESP32_W5500_manager.startConfigPortal()) + Serial.println(F("Not connected to ETH network but continuing anyway.")); + else + { + Serial.println(F("ETH network connected...yeey :)")); + } + +#if USE_ESP_ETH_MANAGER_NTP + String tempTZ = AsyncESP32_W5500_manager.getTimezoneName(); + + if (strlen(tempTZ.c_str()) < sizeof(Ethconfig.TZ_Name) - 1) + strcpy(Ethconfig.TZ_Name, tempTZ.c_str()); + else + strncpy(Ethconfig.TZ_Name, tempTZ.c_str(), sizeof(Ethconfig.TZ_Name) - 1); + + const char * TZ_Result = AsyncESP32_W5500_manager.getTZ(Ethconfig.TZ_Name); + + if (strlen(TZ_Result) < sizeof(Ethconfig.TZ) - 1) + strcpy(Ethconfig.TZ, TZ_Result); + else + strncpy(Ethconfig.TZ, TZ_Result, sizeof(Ethconfig.TZ_Name) - 1); + + if ( strlen(Ethconfig.TZ_Name) > 0 ) + { + LOGERROR3(F("Saving current TZ_Name ="), Ethconfig.TZ_Name, F(", TZ = "), Ethconfig.TZ); + + //configTzTime(Ethconfig.TZ, "pool.ntp.org" ); + configTzTime(Ethconfig.TZ, "time.nist.gov", "0.pool.ntp.org", "1.pool.ntp.org"); + } + else + { + LOGERROR(F("Current Timezone Name is not set. Enter Config Portal to set.")); + } + +#endif + + AsyncESP32_W5500_manager.getSTAStaticIPConfig(EthSTA_IPconfig); + + saveConfigData(); + +#if !USE_DHCP_IP + + // Reset to use new Static IP, if different from current ETH.localIP() + if (ETH.localIP() != EthSTA_IPconfig._sta_static_ip) + { + Serial.print(F("Current IP = ")); + Serial.print(ETH.localIP()); + Serial.print(F(". Reset to take new IP = ")); + Serial.println(EthSTA_IPconfig._sta_static_ip); + + ESP.restart(); + delay(2000); + } + +#endif + } + + digitalWrite(PIN_LED, LED_OFF); // Turn led off as we are not in configuration mode. + + startedAt = millis(); + + Serial.print(F("After waiting ")); + Serial.print((float) (millis() - startedAt) / 1000); + Serial.print(F(" secs more in setup(), connection result is ")); + + if (ESP32_W5500_isConnected()) + { + Serial.print(F("connected. Local IP: ")); + Serial.println(ETH.localIP()); + } +} + +void loop() +{ + // Call the double reset detector loop method every so often, + // so that it can recognise when the timeout expires. + // You can also call drd.stop() when you wish to no longer + // consider the next reset as a double reset. + drd->loop(); + + // put your main code here, to run repeatedly + check_status(); +} diff --git a/examples/Async_ConfigOnDoubleReset_TZ/Async_ConfigOnDoubleReset_TZ.ino b/examples/Async_ConfigOnDoubleReset_TZ/Async_ConfigOnDoubleReset_TZ.ino new file mode 100644 index 0000000..81f180c --- /dev/null +++ b/examples/Async_ConfigOnDoubleReset_TZ/Async_ConfigOnDoubleReset_TZ.ino @@ -0,0 +1,768 @@ +/**************************************************************************************************************************** + Async_ConfigOnDoubleReset_TZ.ino + WebServer_ESP32_W5500 is a library for the ESP32 with Ethernet W5500 to run WebServer + + Based on and modified from ESP32-IDF https://github.com/espressif/esp-idf + Built by Khoi Hoang https://github.com/khoih-prog/WebServer_ESP32_W5500 + + Modified from + 1. Tzapu (https://github.com/tzapu/WiFiManager) + 2. Ken Taylor (https://github.com/kentaylor) + 3. Alan Steremberg (https://github.com/alanswx/ESPAsyncWiFiManager) + 4. Khoi Hoang (https://github.com/khoih-prog/AsyncESP32_W5500_manager) + + Built by Khoi Hoang https://github.com/khoih-prog/AsyncESP32_W5500_Manager + Licensed under MIT license + *****************************************************************************************************************************/ +/**************************************************************************************************************************** + This example will open a configuration portal when the reset button is pressed twice. + This method works well on Wemos boards which have a single reset button on board. It avoids using a pin for launching the configuration portal. + + Settings + There are two values to be set in the sketch. + + DRD_TIMEOUT - Number of seconds to wait for the second reset. Set to 10 in the example. + DRD_ADDRESS - The address in ESP8266 RTC RAM to store the flag. This memory must not be used for other purposes in the same sketch. Set to 0 in the example. + + This example, originally relied on the Double Reset Detector library from https://github.com/datacute/DoubleResetDetector + To support ESP32, use ESP_DoubleResetDetector library from //https://github.com/khoih-prog/ESP_DoubleResetDetector + *****************************************************************************************************************************/ + +#if !( defined(ESP32) ) + #error This code is intended to run on the (ESP32 + W5500) platform! Please check your Tools->Board setting. +#endif + +// Use from 0 to 4. Higher number, more debugging messages and memory usage. +#define _ESPASYNC_ETH_MGR_LOGLEVEL_ 4 + +// To not display stored SSIDs and PWDs on Config Portal, select false. Default is true +// Even the stored Credentials are not display, just leave them all blank to reconnect and reuse the stored Credentials +//#define DISPLAY_STORED_CREDENTIALS_IN_CP false + +////////////////////////////////////////////////////////////// + +// Enter a MAC address and IP address for your controller below. +#define NUMBER_OF_MAC 20 + +byte mac[][NUMBER_OF_MAC] = +{ + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x01 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x02 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x03 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x04 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x05 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x06 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x07 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x08 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x09 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0A }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0B }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0C }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0D }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0E }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0F }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x10 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x11 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x12 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x13 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x14 }, +}; + +// Select the IP address according to your local network +//IPAddress myIP(192, 168, 2, 232); +//IPAddress myGW(192, 168, 2, 1); +//IPAddress mySN(255, 255, 255, 0); + +// Google DNS Server IP +//IPAddress myDNS(8, 8, 8, 8); + +////////////////////////////////////////////////////////// + +// Optional values to override default settings +// Don't change unless you know what you're doing +//#define ETH_SPI_HOST SPI3_HOST +//#define SPI_CLOCK_MHZ 25 + +// Must connect INT to GPIOxx or not working +//#define INT_GPIO 4 + +//#define MISO_GPIO 19 +//#define MOSI_GPIO 23 +//#define SCK_GPIO 18 +//#define CS_GPIO 5 + +////////////////////////////////////////////////////////// + +#include + +//Ported to ESP32 +#include + +// LittleFS has higher priority than SPIFFS +#if ( defined(ESP_ARDUINO_VERSION_MAJOR) && (ESP_ARDUINO_VERSION_MAJOR >= 2) ) + #define USE_LITTLEFS true + #define USE_SPIFFS false +#elif defined(ARDUINO_ESP32C3_DEV) + // For core v1.0.6-, ESP32-C3 only supporting SPIFFS and EEPROM. To use v2.0.0+ for LittleFS + #define USE_LITTLEFS false + #define USE_SPIFFS true +#endif + +#if USE_LITTLEFS + // Use LittleFS + #include "FS.h" + + // Check cores/esp32/esp_arduino_version.h and cores/esp32/core_version.h + //#if ( ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(2, 0, 0) ) //(ESP_ARDUINO_VERSION_MAJOR >= 2) + #if ( defined(ESP_ARDUINO_VERSION_MAJOR) && (ESP_ARDUINO_VERSION_MAJOR >= 2) ) + #if (_ESPASYNC_ETH_MGR_LOGLEVEL_ > 3) + #warning Using ESP32 Core 1.0.6 or 2.0.0+ + #endif + + // The library has been merged into esp32 core from release 1.0.6 + #include // https://github.com/espressif/arduino-esp32/tree/master/libraries/LittleFS + + FS* filesystem = &LittleFS; + #define FileFS LittleFS + #define FS_Name "LittleFS" + #else + #if (_ESPASYNC_ETH_MGR_LOGLEVEL_ > 3) + #warning Using ESP32 Core 1.0.5-. You must install LITTLEFS library + #endif + + // The library has been merged into esp32 core from release 1.0.6 + #include // https://github.com/lorol/LITTLEFS + + FS* filesystem = &LITTLEFS; + #define FileFS LITTLEFS + #define FS_Name "LittleFS" + #endif + +#elif USE_SPIFFS + #include + FS* filesystem = &SPIFFS; + #define FileFS SPIFFS + #define FS_Name "SPIFFS" +#else + // +Use FFat + #include + FS* filesystem = &FFat; + #define FileFS FFat + #define FS_Name "FFat" +#endif + +////////////////////////////////////////////////////////////// + +#define LED_BUILTIN 2 +#define LED_ON HIGH +#define LED_OFF LOW + +////////////////////////////////////////////////////////////// + +// These defines must be put before #include +// to select where to store DoubleResetDetector's variable. +// For ESP32, You must select one to be true (EEPROM or SPIFFS) +// For ESP8266, You must select one to be true (RTC, EEPROM, SPIFFS or LITTLEFS) +// Otherwise, library will use default EEPROM storage + +// These defines must be put before #include +// to select where to store DoubleResetDetector's variable. +// For ESP32, You must select one to be true (EEPROM or SPIFFS) +// Otherwise, library will use default EEPROM storage +#if USE_LITTLEFS + #define ESP_DRD_USE_LITTLEFS true + #define ESP_DRD_USE_SPIFFS false + #define ESP_DRD_USE_EEPROM false +#elif USE_SPIFFS + #define ESP_DRD_USE_LITTLEFS false + #define ESP_DRD_USE_SPIFFS true + #define ESP_DRD_USE_EEPROM false +#else + #define ESP_DRD_USE_LITTLEFS false + #define ESP_DRD_USE_SPIFFS false + #define ESP_DRD_USE_EEPROM true +#endif + +#define DOUBLERESETDETECTOR_DEBUG true //false + +#include //https://github.com/khoih-prog/ESP_DoubleResetDetector + +// Number of seconds after reset during which a +// subseqent reset will be considered a double reset. +#define DRD_TIMEOUT 10 + +// RTC Memory Address for the DoubleResetDetector to use +#define DRD_ADDRESS 0 + +//DoubleResetDetector drd(DRD_TIMEOUT, DRD_ADDRESS); +DoubleResetDetector* drd;////// + +// Onboard LED I/O pin on NodeMCU board +const int PIN_LED = 2; // D4 on NodeMCU and WeMos. GPIO2/ADC12 of ESP32. Controls the onboard LED. + +// You only need to format the filesystem once +//#define FORMAT_FILESYSTEM true +#define FORMAT_FILESYSTEM false + +////////////////////////////////////////////////////////////// + +// Assuming max 49 chars +#define TZNAME_MAX_LEN 50 +#define TIMEZONE_MAX_LEN 50 + +typedef struct +{ + char TZ_Name[TZNAME_MAX_LEN]; // "America/Toronto" + char TZ[TIMEZONE_MAX_LEN]; // "EST5EDT,M3.2.0,M11.1.0" + uint16_t checksum; +} EthConfig; + +EthConfig Ethconfig; + +#define CONFIG_FILENAME F("/eth_cred.dat") + +////////////////////////////////////////////////////////////// + +// Indicates whether ESP has credentials saved from previous session, or double reset detected +bool initialConfig = false; + +// Use false if you don't like to display Available Pages in Information Page of Config Portal +// Comment out or use true to display Available Pages in Information Page of Config Portal +// Must be placed before #include +#define USE_AVAILABLE_PAGES true //false + +// From v1.0.10 to permit disable/enable StaticIP configuration in Config Portal from sketch. Valid only if DHCP is used. +// You'll loose the feature of dynamically changing from DHCP to static IP, or vice versa +// You have to explicitly specify false to disable the feature. +//#define USE_STATIC_IP_CONFIG_IN_CP false + +// Use false to disable NTP config. Advisable when using Cellphone, Tablet to access Config Portal. +// See Issue 23: On Android phone ConfigPortal is unresponsive (https://github.com/khoih-prog/ESP_WiFiManager/issues/23) +#define USE_ESP_ETH_MANAGER_NTP true + +// Just use enough to save memory. On ESP8266, can cause blank ConfigPortal screen +// if using too much memory +#define USING_AFRICA false +#define USING_AMERICA true +#define USING_ANTARCTICA false +#define USING_ASIA false +#define USING_ATLANTIC false +#define USING_AUSTRALIA false +#define USING_EUROPE false +#define USING_INDIAN false +#define USING_PACIFIC false +#define USING_ETC_GMT false + +// Use true to enable CloudFlare NTP service. System can hang if you don't have Internet access while accessing CloudFlare +// See Issue #21: CloudFlare link in the default portal (https://github.com/khoih-prog/ESP_WiFiManager/issues/21) +#define USE_CLOUDFLARE_NTP false + +// New in v1.0.11 +#define USING_CORS_FEATURE true + +////////////////////////////////////////////////////////////// + +// Use USE_DHCP_IP == true for dynamic DHCP IP, false to use static IP which you have to change accordingly to your network +#if (defined(USE_STATIC_IP_CONFIG_IN_CP) && !USE_STATIC_IP_CONFIG_IN_CP) + // Force DHCP to be true + #if defined(USE_DHCP_IP) + #undef USE_DHCP_IP + #endif + #define USE_DHCP_IP true +#else + // You can select DHCP or Static IP here + #define USE_DHCP_IP true + //#define USE_DHCP_IP false +#endif + +#if ( USE_DHCP_IP ) + // Use DHCP + + #if (_ESPASYNC_ETH_MGR_LOGLEVEL_ > 3) + #warning Using DHCP IP + #endif + + IPAddress stationIP = IPAddress(0, 0, 0, 0); + IPAddress gatewayIP = IPAddress(192, 168, 2, 1); + IPAddress netMask = IPAddress(255, 255, 255, 0); + +#else + // Use static IP + + #if (_ESPASYNC_ETH_MGR_LOGLEVEL_ > 3) + #warning Using static IP + #endif + + IPAddress stationIP = IPAddress(192, 168, 2, 232); + IPAddress gatewayIP = IPAddress(192, 168, 2, 1); + IPAddress netMask = IPAddress(255, 255, 255, 0); +#endif + +////////////////////////////////////////////////////////////// + +#define USE_CONFIGURABLE_DNS true + +IPAddress dns1IP = gatewayIP; +IPAddress dns2IP = IPAddress(8, 8, 8, 8); + +#include //https://github.com/khoih-prog/AsyncESP32_W5500_Manager + +#define HTTP_PORT 80 + +////////////////////////////////////////////////////////////// + +/****************************************** + // Defined in AsyncESP32_W5500_Manager.hpp + typedef struct + { + IPAddress _sta_static_ip; + IPAddress _sta_static_gw; + IPAddress _sta_static_sn; + #if USE_CONFIGURABLE_DNS + IPAddress _sta_static_dns1; + IPAddress _sta_static_dns2; + #endif + } ETH_STA_IPConfig; +******************************************/ + +ETH_STA_IPConfig EthSTA_IPconfig; + +////////////////////////////////////////////////////////////// + +void initSTAIPConfigStruct(ETH_STA_IPConfig &in_EthSTA_IPconfig) +{ + in_EthSTA_IPconfig._sta_static_ip = stationIP; + in_EthSTA_IPconfig._sta_static_gw = gatewayIP; + in_EthSTA_IPconfig._sta_static_sn = netMask; +#if USE_CONFIGURABLE_DNS + in_EthSTA_IPconfig._sta_static_dns1 = dns1IP; + in_EthSTA_IPconfig._sta_static_dns2 = dns2IP; +#endif +} + +////////////////////////////////////////////////////////////// + +void displayIPConfigStruct(ETH_STA_IPConfig in_EthSTA_IPconfig) +{ + LOGERROR3(F("stationIP ="), in_EthSTA_IPconfig._sta_static_ip, ", gatewayIP =", in_EthSTA_IPconfig._sta_static_gw); + LOGERROR1(F("netMask ="), in_EthSTA_IPconfig._sta_static_sn); +#if USE_CONFIGURABLE_DNS + LOGERROR3(F("dns1IP ="), in_EthSTA_IPconfig._sta_static_dns1, ", dns2IP =", in_EthSTA_IPconfig._sta_static_dns2); +#endif +} + +////////////////////////////////////////////////////////////// + +#if USE_ESP_ETH_MANAGER_NTP +void printLocalTime() +{ + struct tm timeinfo; + + getLocalTime( &timeinfo ); + + // Valid only if year > 2000. + // You can get from timeinfo : tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec + if (timeinfo.tm_year > 100 ) + { + Serial.print("Local Date/Time: "); + Serial.print( asctime( &timeinfo ) ); + } +} +#endif + +////////////////////////////////////////////////////////////// + +void heartBeatPrint() +{ +#if USE_ESP_ETH_MANAGER_NTP + printLocalTime(); +#else + static int num = 1; + + if (ESP32_W5500_isConnected()) + Serial.print(F("H")); // H means connected to Ethernet + else + Serial.print(F("F")); // F means not connected to Ethernet + + if (num == 80) + { + Serial.println(); + num = 1; + } + else if (num++ % 10 == 0) + { + Serial.print(F(" ")); + } + +#endif +} + +////////////////////////////////////////////////////////////// + +void check_status() +{ + static ulong checkstatus_timeout = 0; + + static ulong current_millis; + +#if USE_ESP_ETH_MANAGER_NTP +#define HEARTBEAT_INTERVAL 60000L +#else +#define HEARTBEAT_INTERVAL 10000L +#endif + + current_millis = millis(); + + // Print hearbeat every HEARTBEAT_INTERVAL (10) seconds. + if ((current_millis > checkstatus_timeout) || (checkstatus_timeout == 0)) + { + heartBeatPrint(); + checkstatus_timeout = current_millis + HEARTBEAT_INTERVAL; + } +} + +////////////////////////////////////////////////////////////// + +int calcChecksum(uint8_t* address, uint16_t sizeToCalc) +{ + uint16_t checkSum = 0; + + for (uint16_t index = 0; index < sizeToCalc; index++) + { + checkSum += * ( ( (byte*) address ) + index); + } + + return checkSum; +} + +////////////////////////////////////////////////////////////// + +bool loadConfigData() +{ + File file = FileFS.open(CONFIG_FILENAME, "r"); + LOGERROR(F("LoadCfgFile ")); + + memset((void *) &Ethconfig, 0, sizeof(Ethconfig)); + memset((void *) &EthSTA_IPconfig, 0, sizeof(EthSTA_IPconfig)); + + if (file) + { + file.readBytes((char *) &Ethconfig, sizeof(Ethconfig)); + file.readBytes((char *) &EthSTA_IPconfig, sizeof(EthSTA_IPconfig)); + file.close(); + + LOGERROR(F("OK")); + + if ( Ethconfig.checksum != calcChecksum( (uint8_t*) &Ethconfig, sizeof(Ethconfig) - sizeof(Ethconfig.checksum) ) ) + { + LOGERROR(F("Ethconfig checksum wrong")); + + return false; + } + + displayIPConfigStruct(EthSTA_IPconfig); + + return true; + } + else + { + LOGERROR(F("failed")); + + return false; + } +} + +////////////////////////////////////////////////////////////// + +void saveConfigData() +{ + File file = FileFS.open(CONFIG_FILENAME, "w"); + LOGERROR(F("SaveCfgFile ")); + + if (file) + { + Ethconfig.checksum = calcChecksum( (uint8_t*) &Ethconfig, sizeof(Ethconfig) - sizeof(Ethconfig.checksum) ); + + file.write((uint8_t*) &Ethconfig, sizeof(Ethconfig)); + + displayIPConfigStruct(EthSTA_IPconfig); + + file.write((uint8_t*) &EthSTA_IPconfig, sizeof(EthSTA_IPconfig)); + file.close(); + + LOGERROR(F("OK")); + } + else + { + LOGERROR(F("failed")); + } +} + +////////////////////////////////////////////////////////////// + +void beginEthernet() +{ + LOGWARN(F("Default SPI pinout:")); + LOGWARN1(F("SPI_HOST:"), ETH_SPI_HOST); + LOGWARN1(F("MOSI:"), MOSI_GPIO); + LOGWARN1(F("MISO:"), MISO_GPIO); + LOGWARN1(F("SCK:"), SCK_GPIO); + LOGWARN1(F("CS:"), CS_GPIO); + LOGWARN1(F("INT:"), INT_GPIO); + LOGWARN1(F("SPI Clock (MHz):"), SPI_CLOCK_MHZ); + LOGWARN(F("=========================")); + + /////////////////////////////////// + + // To be called before ETH.begin() + ESP32_W5500_onEvent(); + + // start the ethernet connection and the server: + // Use DHCP dynamic IP and random mac + uint16_t index = millis() % NUMBER_OF_MAC; + + //bool begin(int MISO_GPIO, int MOSI_GPIO, int SCLK_GPIO, int CS_GPIO, int INT_GPIO, int SPI_CLOCK_MHZ, + // int SPI_HOST, uint8_t *W5500_Mac = W5500_Default_Mac); + //ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST ); + ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST, mac[index] ); +} + +////////////////////////////////////////////////////////////// + +void initEthernet() +{ +#if !( USE_DHCP_IP ) + displayIPConfigStruct(EthSTA_IPconfig); + + // Static IP, leave without this line to get IP via DHCP + //bool config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1 = 0, IPAddress dns2 = 0); + //ETH.config(stationIP, gatewayIP, netMask, dns1IP, dns2IP); + ETH.config(EthSTA_IPconfig._sta_static_ip, EthSTA_IPconfig._sta_static_gw, EthSTA_IPconfig._sta_static_sn, + EthSTA_IPconfig._sta_static_dns1); +#endif + + ESP32_W5500_waitForConnect(); +} + +////////////////////////////////////////////////////////////// + +void setup() +{ + // put your setup code here, to run once: + // initialize the LED digital pin as an output. + pinMode(PIN_LED, OUTPUT); + + Serial.begin(115200); + + while (!Serial && millis() < 5000); + + delay(200); + + Serial.print(F("\nStarting Async_ConfigOnDoubleReset_TZ using ")); + Serial.print(FS_Name); + Serial.print(F(" on ")); + Serial.print(ARDUINO_BOARD); + Serial.print(F(" with ")); + Serial.println(SHIELD_TYPE); + Serial.println(ASYNC_ESP32_W5500_MANAGER_VERSION); + Serial.println(ESP_DOUBLE_RESET_DETECTOR_VERSION); + + Serial.setDebugOutput(false); + + if (FORMAT_FILESYSTEM) + FileFS.format(); + + // Format FileFS if not yet +#ifdef ESP32 + + if (!FileFS.begin(true)) +#else + if (!FileFS.begin()) +#endif + { +#ifdef ESP8266 + FileFS.format(); +#endif + + Serial.println(F("SPIFFS/LittleFS failed! Already tried formatting.")); + + if (!FileFS.begin()) + { + // prevents debug info from the library to hide err message. + delay(100); + +#if USE_LITTLEFS + Serial.println(F("LittleFS failed!. Please use SPIFFS or EEPROM. Stay forever")); +#else + Serial.println(F("SPIFFS failed!. Please use LittleFS or EEPROM. Stay forever")); +#endif + + while (true) + { + delay(1); + } + } + } + + drd = new DoubleResetDetector(DRD_TIMEOUT, DRD_ADDRESS); + + unsigned long startedAt = millis(); + + beginEthernet(); + + initSTAIPConfigStruct(EthSTA_IPconfig); + + //Local intialization. Once its business is done, there is no need to keep it around + // Use this to default DHCP hostname to ESP32-XXXXXX + //AsyncESP32_W5500_Manager AsyncESP32_W5500_manager(&webServer, &dnsServer); + // Use this to personalize DHCP hostname (RFC952 conformed) + AsyncWebServer webServer(HTTP_PORT); + +#if ( USING_ESP32_S2 || USING_ESP32_C3 ) + AsyncESP32_W5500_Manager AsyncESP32_W5500_manager(&webServer, NULL, "AsyncConfigOnDoubleReset"); +#else + AsyncDNSServer dnsServer; + + AsyncESP32_W5500_Manager AsyncESP32_W5500_manager(&webServer, &dnsServer, "AsyncConfigOnDoubleReset"); +#endif + +#if !USE_DHCP_IP + // Set (static IP, Gateway, Subnetmask, DNS1 and DNS2) or (IP, Gateway, Subnetmask) + AsyncESP32_W5500_manager.setSTAStaticIPConfig(EthSTA_IPconfig); +#endif + +#if USING_CORS_FEATURE + AsyncESP32_W5500_manager.setCORSHeader("Your Access-Control-Allow-Origin"); +#endif + + bool configDataLoaded = false; + + if (loadConfigData()) + { + configDataLoaded = true; + + AsyncESP32_W5500_manager.setConfigPortalTimeout( + 120); //If no access point name has been previously entered disable timeout. + Serial.println(F("Got stored Credentials. Timeout 120s for Config Portal")); + +#if USE_ESP_ETH_MANAGER_NTP + + if ( strlen(Ethconfig.TZ_Name) > 0 ) + { + LOGERROR3(F("Current TZ_Name ="), Ethconfig.TZ_Name, F(", TZ = "), Ethconfig.TZ); + + //configTzTime(Ethconfig.TZ, "pool.ntp.org" ); + configTzTime(Ethconfig.TZ, "time.nist.gov", "0.pool.ntp.org", "1.pool.ntp.org"); + } + else + { + Serial.println(F("Current Timezone is not set. Enter Config Portal to set.")); + } + +#endif + } + else + { + // Enter CP only if no stored Credentials on flash and file + Serial.println(F("Open Config Portal without Timeout: No stored Credentials.")); + initialConfig = true; + } + + ////////////////////////////////// + + // Connect ETH now if using STA + initEthernet(); + + ////////////////////////////////// + + if (drd->detectDoubleReset()) + { + // DRD, disable timeout. + AsyncESP32_W5500_manager.setConfigPortalTimeout(0); + + Serial.println(F("Open Config Portal without Timeout: Double Reset Detected")); + initialConfig = true; + } + + if (initialConfig) + { + Serial.print(F("Starting configuration portal @ ")); + Serial.println(ETH.localIP()); + + digitalWrite(LED_BUILTIN, LED_ON); // Turn led on as we are in configuration mode. + + //sets timeout in seconds until configuration portal gets turned off. + //If not specified device will remain in configuration mode until + //switched off via webserver or device is restarted. + //AsyncESP32_W5500_manager.setConfigPortalTimeout(600); + + // Starts an access point + if (!AsyncESP32_W5500_manager.startConfigPortal()) + Serial.println(F("Not connected to ETH network but continuing anyway.")); + else + { + Serial.println(F("ETH network connected...yeey :)")); + } + +#if USE_ESP_ETH_MANAGER_NTP + String tempTZ = AsyncESP32_W5500_manager.getTimezoneName(); + + if (strlen(tempTZ.c_str()) < sizeof(Ethconfig.TZ_Name) - 1) + strcpy(Ethconfig.TZ_Name, tempTZ.c_str()); + else + strncpy(Ethconfig.TZ_Name, tempTZ.c_str(), sizeof(Ethconfig.TZ_Name) - 1); + + const char * TZ_Result = AsyncESP32_W5500_manager.getTZ(Ethconfig.TZ_Name); + + if (strlen(TZ_Result) < sizeof(Ethconfig.TZ) - 1) + strcpy(Ethconfig.TZ, TZ_Result); + else + strncpy(Ethconfig.TZ, TZ_Result, sizeof(Ethconfig.TZ_Name) - 1); + + if ( strlen(Ethconfig.TZ_Name) > 0 ) + { + LOGERROR3(F("Saving current TZ_Name ="), Ethconfig.TZ_Name, F(", TZ = "), Ethconfig.TZ); + + //configTzTime(Ethconfig.TZ, "pool.ntp.org" ); + configTzTime(Ethconfig.TZ, "time.nist.gov", "0.pool.ntp.org", "1.pool.ntp.org"); + } + else + { + LOGERROR(F("Current Timezone Name is not set. Enter Config Portal to set.")); + } + +#endif + + AsyncESP32_W5500_manager.getSTAStaticIPConfig(EthSTA_IPconfig); + + saveConfigData(); + } + + digitalWrite(PIN_LED, LED_OFF); // Turn led off as we are not in configuration mode. + + startedAt = millis(); + + Serial.print(F("After waiting ")); + Serial.print((float) (millis() - startedAt) / 1000); + Serial.print(F(" secs more in setup(), connection result is ")); + + if (ESP32_W5500_isConnected()) + { + Serial.print(F("connected. Local IP: ")); + Serial.println(ETH.localIP()); + } +} + +////////////////////////////////////////////////////////////// + +void loop() +{ + // Call the double reset detector loop method every so often, + // so that it can recognise when the timeout expires. + // You can also call drd.stop() when you wish to no longer + // consider the next reset as a double reset. + drd->loop(); + + // put your main code here, to run repeatedly + check_status(); +} diff --git a/examples/Async_ConfigOnSwitch/Async_ConfigOnSwitch.ino b/examples/Async_ConfigOnSwitch/Async_ConfigOnSwitch.ino new file mode 100644 index 0000000..c235a7b --- /dev/null +++ b/examples/Async_ConfigOnSwitch/Async_ConfigOnSwitch.ino @@ -0,0 +1,933 @@ +/**************************************************************************************************************************** + Async_ConfigOnSwitch.ino + WebServer_ESP32_W5500 is a library for the ESP32 with Ethernet W5500 to run WebServer + + Based on and modified from ESP32-IDF https://github.com/espressif/esp-idf + Built by Khoi Hoang https://github.com/khoih-prog/WebServer_ESP32_W5500 + + Modified from + 1. Tzapu (https://github.com/tzapu/WiFiManager) + 2. Ken Taylor (https://github.com/kentaylor) + 3. Alan Steremberg (https://github.com/alanswx/ESPAsyncWiFiManager) + 4. Khoi Hoang (https://github.com/khoih-prog/AsyncESP32_W5500_manager) + + Built by Khoi Hoang https://github.com/khoih-prog/AsyncESP32_W5500_Manager + Licensed under MIT license + *****************************************************************************************************************************/ +/**************************************************************************************************************************** + This example will open a configuration portal when no configuration has been previously entered or when a button is pushed. + It is the easiest scenario for configuration but requires a pin and a button on the ESP8266 device. + The Flash button is convenient for this on NodeMCU devices. + + Also in this example a password is required to connect to the configuration portal + network. This is inconvenient but means that only those who know the password or those + already connected to the target network can access the configuration portal and + the network credentials will be sent from the browser over an encrypted connection and + can not be read by observers. + *****************************************************************************************************************************/ + +#if !( defined(ESP32) ) + #error This code is intended to run on the (ESP32 + W5500) platform! Please check your Tools->Board setting. +#endif + +////////////////////////////////////////////////////////////// + +// Use from 0 to 4. Higher number, more debugging messages and memory usage. +#define _ESPASYNC_ETH_MGR_LOGLEVEL_ 4 + +// To not display stored SSIDs and PWDs on Config Portal, select false. Default is true +// Even the stored Credentials are not display, just leave them all blank to reconnect and reuse the stored Credentials +//#define DISPLAY_STORED_CREDENTIALS_IN_CP false + +////////////////////////////////////////////////////////////// + +// Enter a MAC address and IP address for your controller below. +#define NUMBER_OF_MAC 20 + +byte mac[][NUMBER_OF_MAC] = +{ + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x01 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x02 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x03 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x04 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x05 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x06 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x07 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x08 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x09 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0A }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0B }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0C }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0D }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0E }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0F }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x10 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x11 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x12 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x13 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x14 }, +}; + +// Select the IP address according to your local network +//IPAddress myIP(192, 168, 2, 232); +//IPAddress myGW(192, 168, 2, 1); +//IPAddress mySN(255, 255, 255, 0); + +// Google DNS Server IP +//IPAddress myDNS(8, 8, 8, 8); + +////////////////////////////////////////////////////////// + +// Optional values to override default settings +// Don't change unless you know what you're doing +//#define ETH_SPI_HOST SPI3_HOST +//#define SPI_CLOCK_MHZ 25 + +// Must connect INT to GPIOxx or not working +//#define INT_GPIO 4 + +//#define MISO_GPIO 19 +//#define MOSI_GPIO 23 +//#define SCK_GPIO 18 +//#define CS_GPIO 5 + +////////////////////////////////////////////////////////// + +//For ESP32, To use ESP32 Dev Module, QIO, Flash 4MB/80MHz, Upload 921600 + +//Ported to ESP32 +#include + +////////////////////////////////////////////////////////////// + +// LittleFS has higher priority than SPIFFS +#if ( defined(ESP_ARDUINO_VERSION_MAJOR) && (ESP_ARDUINO_VERSION_MAJOR >= 2) ) + #define USE_LITTLEFS true + #define USE_SPIFFS false +#elif defined(ARDUINO_ESP32C3_DEV) + // For core v1.0.6-, ESP32-C3 only supporting SPIFFS and EEPROM. To use v2.0.0+ for LittleFS + #define USE_LITTLEFS false + #define USE_SPIFFS true +#endif + +#if USE_LITTLEFS + // Use LittleFS + #include "FS.h" + + // Check cores/esp32/esp_arduino_version.h and cores/esp32/core_version.h + //#if ( ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(2, 0, 0) ) //(ESP_ARDUINO_VERSION_MAJOR >= 2) + #if ( defined(ESP_ARDUINO_VERSION_MAJOR) && (ESP_ARDUINO_VERSION_MAJOR >= 2) ) + #if (_ESPASYNC_ETH_MGR_LOGLEVEL_ > 3) + #warning Using ESP32 Core 1.0.6 or 2.0.0+ + #endif + + // The library has been merged into esp32 core from release 1.0.6 + #include // https://github.com/espressif/arduino-esp32/tree/master/libraries/LittleFS + + FS* filesystem = &LittleFS; + #define FileFS LittleFS + #define FS_Name "LittleFS" + #else + #if (_ESPASYNC_ETH_MGR_LOGLEVEL_ > 3) + #warning Using ESP32 Core 1.0.5-. You must install LITTLEFS library + #endif + + // The library has been merged into esp32 core from release 1.0.6 + #include // https://github.com/lorol/LITTLEFS + + FS* filesystem = &LITTLEFS; + #define FileFS LITTLEFS + #define FS_Name "LittleFS" + #endif + +#elif USE_SPIFFS + #include + FS* filesystem = &SPIFFS; + #define FileFS SPIFFS + #define FS_Name "SPIFFS" +#else + // Use FFat + #include + FS* filesystem = &FFat; + #define FileFS FFat + #define FS_Name "FFat" +#endif + +////////////////////////////////////////////////////////////// + +#define LED_BUILTIN 2 +#define LED_ON HIGH +#define LED_OFF LOW + +//See file .../hardware/espressif/esp32/variants/(esp32|doitESP32devkitV1)/pins_arduino.h +#define LED_BUILTIN 2 // Pin D2 mapped to pin GPIO2/ADC12 of ESP32, control on-board LED +#define LED_BUILTIN 2 // Pin D2 mapped to pin GPIO2/ADC12 of ESP32, control on-board LED + +#define PIN_D0 0 // Pin D0 mapped to pin GPIO0/BOOT/ADC11/TOUCH1 of ESP32 +#define PIN_D1 1 // Pin D1 mapped to pin GPIO1/TX0 of ESP32 +#define PIN_D2 2 // Pin D2 mapped to pin GPIO2/ADC12/TOUCH2 of ESP32 +#define PIN_D3 3 // Pin D3 mapped to pin GPIO3/RX0 of ESP32 +#define PIN_D4 4 // Pin D4 mapped to pin GPIO4/ADC10/TOUCH0 of ESP32 +#define PIN_D5 5 // Pin D5 mapped to pin GPIO5/SPISS/VSPI_SS of ESP32 +#define PIN_D6 6 // Pin D6 mapped to pin GPIO6/FLASH_SCK of ESP32 +#define PIN_D7 7 // Pin D7 mapped to pin GPIO7/FLASH_D0 of ESP32 +#define PIN_D8 8 // Pin D8 mapped to pin GPIO8/FLASH_D1 of ESP32 +#define PIN_D9 9 // Pin D9 mapped to pin GPIO9/FLASH_D2 of ESP32 + +#define PIN_D10 10 // Pin D10 mapped to pin GPIO10/FLASH_D3 of ESP32 +#define PIN_D11 11 // Pin D11 mapped to pin GPIO11/FLASH_CMD of ESP32 +#define PIN_D12 12 // Pin D12 mapped to pin GPIO12/HSPI_MISO/ADC15/TOUCH5/TDI of ESP32 +#define PIN_D13 13 // Pin D13 mapped to pin GPIO13/HSPI_MOSI/ADC14/TOUCH4/TCK of ESP32 +#define PIN_D14 14 // Pin D14 mapped to pin GPIO14/HSPI_SCK/ADC16/TOUCH6/TMS of ESP32 +#define PIN_D15 15 // Pin D15 mapped to pin GPIO15/HSPI_SS/ADC13/TOUCH3/TDO of ESP32 +#define PIN_D16 16 // Pin D16 mapped to pin GPIO16/TX2 of ESP32 +#define PIN_D17 17 // Pin D17 mapped to pin GPIO17/RX2 of ESP32 +#define PIN_D18 18 // Pin D18 mapped to pin GPIO18/VSPI_SCK of ESP32 +#define PIN_D19 19 // Pin D19 mapped to pin GPIO19/VSPI_MISO of ESP32 + +#define PIN_D21 21 // Pin D21 mapped to pin GPIO21/SDA of ESP32 +#define PIN_D22 22 // Pin D22 mapped to pin GPIO22/SCL of ESP32 +#define PIN_D23 23 // Pin D23 mapped to pin GPIO23/VSPI_MOSI of ESP32 +#define PIN_D24 24 // Pin D24 mapped to pin GPIO24 of ESP32 +#define PIN_D25 25 // Pin D25 mapped to pin GPIO25/ADC18/DAC1 of ESP32 +#define PIN_D26 26 // Pin D26 mapped to pin GPIO26/ADC19/DAC2 of ESP32 +#define PIN_D27 27 // Pin D27 mapped to pin GPIO27/ADC17/TOUCH7 of ESP32 + +#define PIN_D32 32 // Pin D32 mapped to pin GPIO32/ADC4/TOUCH9 of ESP32 +#define PIN_D33 33 // Pin D33 mapped to pin GPIO33/ADC5/TOUCH8 of ESP32 +#define PIN_D34 34 // Pin D34 mapped to pin GPIO34/ADC6 of ESP32 + +//Only GPIO pin < 34 can be used as output. Pins >= 34 can be only inputs +//See .../cores/esp32/esp32-hal-gpio.h/c +//#define digitalPinIsValid(pin) ((pin) < 40 && esp32_gpioMux[(pin)].reg) +//#define digitalPinCanOutput(pin) ((pin) < 34 && esp32_gpioMux[(pin)].reg) +//#define digitalPinToRtcPin(pin) (((pin) < 40)?esp32_gpioMux[(pin)].rtc:-1) +//#define digitalPinToAnalogChannel(pin) (((pin) < 40)?esp32_gpioMux[(pin)].adc:-1) +//#define digitalPinToTouchChannel(pin) (((pin) < 40)?esp32_gpioMux[(pin)].touch:-1) +//#define digitalPinToDacChannel(pin) (((pin) == 25)?0:((pin) == 26)?1:-1) + +#define PIN_D35 35 // Pin D35 mapped to pin GPIO35/ADC7 of ESP32 +#define PIN_D36 36 // Pin D36 mapped to pin GPIO36/ADC0/SVP of ESP32 +#define PIN_D39 39 // Pin D39 mapped to pin GPIO39/ADC3/SVN of ESP32 + +#define PIN_RX0 3 // Pin RX0 mapped to pin GPIO3/RX0 of ESP32 +#define PIN_TX0 1 // Pin TX0 mapped to pin GPIO1/TX0 of ESP32 + +#define PIN_SCL 22 // Pin SCL mapped to pin GPIO22/SCL of ESP32 +#define PIN_SDA 21 // Pin SDA mapped to pin GPIO21/SDA of ESP32 + +////////////////////////////////////////////////////////////// + +/* Trigger for inititating config mode is Pin D3 and also flash button on NodeMCU + Flash button is convenient to use but if it is pressed it will stuff up the serial port device driver + until the computer is rebooted on windows machines. +*/ +#if ( USING_ESP32_S2 || USING_ESP32_C3 ) + + const int TRIGGER_PIN = PIN_D3; // Pin D3 mapped to pin GPIO03/ADC1-2/TOUCH3 of ESP32-S2 + /* + Alternative trigger pin. Needs to be connected to a button to use this pin. It must be a momentary connection + not connected permanently to ground. Either trigger pin will work. + */ + const int TRIGGER_PIN2 = PIN_D4; // Pin D4 mapped to pin GPIO04/ADC1_3/TOUCH4 of ESP32-S2 + +#else + const int TRIGGER_PIN = PIN_D0; // Pin D0 mapped to pin GPIO0/BOOT/ADC11/TOUCH1 of ESP32 + /* + Alternative trigger pin. Needs to be connected to a button to use this pin. It must be a momentary connection + not connected permanently to ground. Either trigger pin will work. + */ + const int TRIGGER_PIN2 = PIN_D25; // Pin D25 mapped to pin GPIO25/ADC18/DAC1 of ESP32 +#endif + +////////////////////////////////////////////////////////////// + +// You only need to format the filesystem once +//#define FORMAT_FILESYSTEM true +#define FORMAT_FILESYSTEM false + +////////////////////////////////////////////////////////////// + +// Assuming max 49 chars +#define TZNAME_MAX_LEN 50 +#define TIMEZONE_MAX_LEN 50 + +typedef struct +{ + char TZ_Name[TZNAME_MAX_LEN]; // "America/Toronto" + char TZ[TIMEZONE_MAX_LEN]; // "EST5EDT,M3.2.0,M11.1.0" + uint16_t checksum; +} EthConfig; + +EthConfig Ethconfig; + +////////////////////////////////////////////////////////////// + +#define CONFIG_FILENAME F("/eth_cred.dat") + +////////////////////////////////////////////////////////////// + +// Indicates whether ESP has credentials saved from previous session, or double reset detected +bool initialConfig = false; + +// Use false if you don't like to display Available Pages in Information Page of Config Portal +// Comment out or use true to display Available Pages in Information Page of Config Portal +// Must be placed before #include +#define USE_AVAILABLE_PAGES true + +// From v1.0.10 to permit disable/enable StaticIP configuration in Config Portal from sketch. Valid only if DHCP is used. +// You'll loose the feature of dynamically changing from DHCP to static IP, or vice versa +// You have to explicitly specify false to disable the feature. +//#define USE_STATIC_IP_CONFIG_IN_CP false + +// Use false to disable NTP config. Advisable when using Cellphone, Tablet to access Config Portal. +// See Issue 23: On Android phone ConfigPortal is unresponsive (https://github.com/khoih-prog/ESP_WiFiManager/issues/23) +#define USE_ESP_ETH_MANAGER_NTP true + +// Just use enough to save memory. On ESP8266, can cause blank ConfigPortal screen +// if using too much memory +#define USING_AFRICA false +#define USING_AMERICA true +#define USING_ANTARCTICA false +#define USING_ASIA false +#define USING_ATLANTIC false +#define USING_AUSTRALIA false +#define USING_EUROPE false +#define USING_INDIAN false +#define USING_PACIFIC false +#define USING_ETC_GMT false + +// Use true to enable CloudFlare NTP service. System can hang if you don't have Internet access while accessing CloudFlare +// See Issue #21: CloudFlare link in the default portal (https://github.com/khoih-prog/ESP_WiFiManager/issues/21) +#define USE_CLOUDFLARE_NTP false + +// New in v1.0.11 +#define USING_CORS_FEATURE true + +////////////////////////////////////////////////////////////// + +// Use USE_DHCP_IP == true for dynamic DHCP IP, false to use static IP which you have to change accordingly to your network +#if (defined(USE_STATIC_IP_CONFIG_IN_CP) && !USE_STATIC_IP_CONFIG_IN_CP) + // Force DHCP to be true + #if defined(USE_DHCP_IP) + #undef USE_DHCP_IP + #endif + #define USE_DHCP_IP true +#else + // You can select DHCP or Static IP here + //#define USE_DHCP_IP true + #define USE_DHCP_IP false +#endif + +#if ( USE_DHCP_IP ) + // Use DHCP + + #if (_ESPASYNC_ETH_MGR_LOGLEVEL_ > 3) + #warning Using DHCP IP + #endif + + IPAddress stationIP = IPAddress(0, 0, 0, 0); + IPAddress gatewayIP = IPAddress(192, 168, 2, 1); + IPAddress netMask = IPAddress(255, 255, 255, 0); + +#else + // Use static IP + + #if (_ESPASYNC_ETH_MGR_LOGLEVEL_ > 3) + #warning Using static IP + #endif + + IPAddress stationIP = IPAddress(192, 168, 2, 232); + IPAddress gatewayIP = IPAddress(192, 168, 2, 1); + IPAddress netMask = IPAddress(255, 255, 255, 0); +#endif + +////////////////////////////////////////////////////////////// + +#define USE_CONFIGURABLE_DNS true + +IPAddress dns1IP = gatewayIP; +IPAddress dns2IP = IPAddress(8, 8, 8, 8); + +#include //https://github.com/khoih-prog/AsyncESP32_W5500_Manager + +#define HTTP_PORT 80 + +////////////////////////////////////////////////////////////// + +/****************************************** + // Defined in AsyncESP32_W5500_Manager.hpp + typedef struct + { + IPAddress _sta_static_ip; + IPAddress _sta_static_gw; + IPAddress _sta_static_sn; + #if USE_CONFIGURABLE_DNS + IPAddress _sta_static_dns1; + IPAddress _sta_static_dns2; + #endif + } ETH_STA_IPConfig; +******************************************/ + +ETH_STA_IPConfig EthSTA_IPconfig; + +////////////////////////////////////////////////////////////// + +void initSTAIPConfigStruct(ETH_STA_IPConfig &in_EthSTA_IPconfig) +{ + in_EthSTA_IPconfig._sta_static_ip = stationIP; + in_EthSTA_IPconfig._sta_static_gw = gatewayIP; + in_EthSTA_IPconfig._sta_static_sn = netMask; +#if USE_CONFIGURABLE_DNS + in_EthSTA_IPconfig._sta_static_dns1 = dns1IP; + in_EthSTA_IPconfig._sta_static_dns2 = dns2IP; +#endif +} + +////////////////////////////////////////////////////////////// + +void displayIPConfigStruct(ETH_STA_IPConfig in_EthSTA_IPconfig) +{ + LOGERROR3(F("stationIP ="), in_EthSTA_IPconfig._sta_static_ip, ", gatewayIP =", in_EthSTA_IPconfig._sta_static_gw); + LOGERROR1(F("netMask ="), in_EthSTA_IPconfig._sta_static_sn); +#if USE_CONFIGURABLE_DNS + LOGERROR3(F("dns1IP ="), in_EthSTA_IPconfig._sta_static_dns1, ", dns2IP =", in_EthSTA_IPconfig._sta_static_dns2); +#endif +} + +////////////////////////////////////////////////////////////// + +void toggleLED() +{ + //toggle state + digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); +} + +////////////////////////////////////////////////////////////// + +#if USE_ESP_ETH_MANAGER_NTP +void printLocalTime() +{ + struct tm timeinfo; + + getLocalTime( &timeinfo ); + + // Valid only if year > 2000. + // You can get from timeinfo : tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec + if (timeinfo.tm_year > 100 ) + { + Serial.print("Local Date/Time: "); + Serial.print( asctime( &timeinfo ) ); + } +} +#endif + +////////////////////////////////////////////////////////////// + +void heartBeatPrint() +{ +#if USE_ESP_ETH_MANAGER_NTP + printLocalTime(); +#else + static int num = 1; + + if (ESP32_W5500_isConnected()) + Serial.print(F("H")); // H means connected to Ethernet + else + Serial.print(F("F")); // F means not connected to Ethernet + + if (num == 80) + { + Serial.println(); + num = 1; + } + else if (num++ % 10 == 0) + { + Serial.print(F(" ")); + } + +#endif +} + +////////////////////////////////////////////////////////////// + +void check_status() +{ + static ulong checkstatus_timeout = 0; + static ulong LEDstatus_timeout = 0; + + static ulong current_millis; + +#if USE_ESP_ETH_MANAGER_NTP +#define HEARTBEAT_INTERVAL 60000L +#else +#define HEARTBEAT_INTERVAL 10000L +#endif + +#define LED_INTERVAL 2000L + + current_millis = millis(); + + if ((current_millis > LEDstatus_timeout) || (LEDstatus_timeout == 0)) + { + // Toggle LED at LED_INTERVAL = 2s + toggleLED(); + LEDstatus_timeout = current_millis + LED_INTERVAL; + } + + // Print hearbeat every HEARTBEAT_INTERVAL (10) seconds. + if ((current_millis > checkstatus_timeout) || (checkstatus_timeout == 0)) + { + heartBeatPrint(); + checkstatus_timeout = current_millis + HEARTBEAT_INTERVAL; + } +} + +////////////////////////////////////////////////////////////// + +int calcChecksum(uint8_t* address, uint16_t sizeToCalc) +{ + uint16_t checkSum = 0; + + for (uint16_t index = 0; index < sizeToCalc; index++) + { + checkSum += * ( ( (byte*) address ) + index); + } + + return checkSum; +} + +////////////////////////////////////////////////////////////// + +bool loadConfigData() +{ + File file = FileFS.open(CONFIG_FILENAME, "r"); + LOGERROR(F("LoadCfgFile ")); + + memset((void *) &Ethconfig, 0, sizeof(Ethconfig)); + memset((void *) &EthSTA_IPconfig, 0, sizeof(EthSTA_IPconfig)); + + if (file) + { + file.readBytes((char *) &Ethconfig, sizeof(Ethconfig)); + file.readBytes((char *) &EthSTA_IPconfig, sizeof(EthSTA_IPconfig)); + file.close(); + + LOGERROR(F("OK")); + + if ( Ethconfig.checksum != calcChecksum( (uint8_t*) &Ethconfig, sizeof(Ethconfig) - sizeof(Ethconfig.checksum) ) ) + { + LOGERROR(F("Ethconfig checksum wrong")); + + return false; + } + + displayIPConfigStruct(EthSTA_IPconfig); + + return true; + } + else + { + LOGERROR(F("failed")); + + return false; + } +} + +////////////////////////////////////////////////////////////// + +void saveConfigData() +{ + File file = FileFS.open(CONFIG_FILENAME, "w"); + LOGERROR(F("SaveCfgFile ")); + + if (file) + { + Ethconfig.checksum = calcChecksum( (uint8_t*) &Ethconfig, sizeof(Ethconfig) - sizeof(Ethconfig.checksum) ); + + file.write((uint8_t*) &Ethconfig, sizeof(Ethconfig)); + + displayIPConfigStruct(EthSTA_IPconfig); + + file.write((uint8_t*) &EthSTA_IPconfig, sizeof(EthSTA_IPconfig)); + file.close(); + + LOGERROR(F("OK")); + } + else + { + LOGERROR(F("failed")); + } +} + +////////////////////////////////////////////////////////////// + +void beginEthernet() +{ + LOGWARN(F("Default SPI pinout:")); + LOGWARN1(F("SPI_HOST:"), ETH_SPI_HOST); + LOGWARN1(F("MOSI:"), MOSI_GPIO); + LOGWARN1(F("MISO:"), MISO_GPIO); + LOGWARN1(F("SCK:"), SCK_GPIO); + LOGWARN1(F("CS:"), CS_GPIO); + LOGWARN1(F("INT:"), INT_GPIO); + LOGWARN1(F("SPI Clock (MHz):"), SPI_CLOCK_MHZ); + LOGWARN(F("=========================")); + + /////////////////////////////////// + + // To be called before ETH.begin() + ESP32_W5500_onEvent(); + + // start the ethernet connection and the server: + // Use DHCP dynamic IP and random mac + uint16_t index = millis() % NUMBER_OF_MAC; + + //bool begin(int MISO_GPIO, int MOSI_GPIO, int SCLK_GPIO, int CS_GPIO, int INT_GPIO, int SPI_CLOCK_MHZ, + // int SPI_HOST, uint8_t *W5500_Mac = W5500_Default_Mac); + //ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST ); + ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST, mac[index] ); +} + +////////////////////////////////////////////////////////////// + +void initEthernet() +{ +#if !( USE_DHCP_IP ) + displayIPConfigStruct(EthSTA_IPconfig); + + // Static IP, leave without this line to get IP via DHCP + //bool config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1 = 0, IPAddress dns2 = 0); + //ETH.config(stationIP, gatewayIP, netMask, dns1IP, dns2IP); + ETH.config(EthSTA_IPconfig._sta_static_ip, EthSTA_IPconfig._sta_static_gw, EthSTA_IPconfig._sta_static_sn, + EthSTA_IPconfig._sta_static_dns1); +#endif + + ESP32_W5500_waitForConnect(); +} + +////////////////////////////////////////////////////////////// + +void setup() +{ + //set led pin as output + pinMode(LED_BUILTIN, OUTPUT); + + pinMode(TRIGGER_PIN, INPUT_PULLUP); + pinMode(TRIGGER_PIN2, INPUT_PULLUP); + + Serial.begin(115200); + + while (!Serial && millis() < 5000); + + delay(200); + + Serial.print(F("\nStarting Async_ConfigOnSwitch using ")); + Serial.print(FS_Name); + Serial.print(F(" on ")); + Serial.print(ARDUINO_BOARD); + Serial.print(F(" with ")); + Serial.println(SHIELD_TYPE); + Serial.println(ASYNC_ESP32_W5500_MANAGER_VERSION); + + Serial.setDebugOutput(false); + + if (FORMAT_FILESYSTEM) + FileFS.format(); + + // Format FileFS if not yet +#ifdef ESP32 + + if (!FileFS.begin(true)) +#else + if (!FileFS.begin()) +#endif + { +#ifdef ESP8266 + FileFS.format(); +#endif + + Serial.println(F("SPIFFS/LittleFS failed! Already tried formatting.")); + + if (!FileFS.begin()) + { + // prevents debug info from the library to hide err message. + delay(100); + +#if USE_LITTLEFS + Serial.println(F("LittleFS failed!. Please use SPIFFS or EEPROM. Stay forever")); +#else + Serial.println(F("SPIFFS failed!. Please use LittleFS or EEPROM. Stay forever")); +#endif + + while (true) + { + delay(1); + } + } + } + + unsigned long startedAt = millis(); + + beginEthernet(); + + initSTAIPConfigStruct(EthSTA_IPconfig); + + digitalWrite(LED_BUILTIN, LED_ON); // turn the LED on by making the voltage LOW to tell us we are in configuration mode. + + //Local intialization. Once its business is done, there is no need to keep it around + // Use this to default DHCP hostname to ESP32-XXXXXX + //AsyncESP32_W5500_Manager AsyncESP32_W5500_manager(&webServer, &dnsServer); + // Use this to personalize DHCP hostname (RFC952 conformed) + AsyncWebServer webServer(HTTP_PORT); + +#if ( USING_ESP32_S2 || USING_ESP32_C3 ) + AsyncESP32_W5500_Manager AsyncESP32_W5500_manager(&webServer, NULL, "AsyncConfigOnSwitch"); +#else + AsyncDNSServer dnsServer; + + AsyncESP32_W5500_Manager AsyncESP32_W5500_manager(&webServer, &dnsServer, "AsyncConfigOnSwitch"); +#endif + + AsyncESP32_W5500_manager.setDebugOutput(true); + +#if !USE_DHCP_IP + // Set (static IP, Gateway, Subnetmask, DNS1 and DNS2) or (IP, Gateway, Subnetmask) + AsyncESP32_W5500_manager.setSTAStaticIPConfig(EthSTA_IPconfig); +#endif + +#if USING_CORS_FEATURE + AsyncESP32_W5500_manager.setCORSHeader("Your Access-Control-Allow-Origin"); +#endif + + bool configDataLoaded = false; + + if (loadConfigData()) + { + configDataLoaded = true; + + AsyncESP32_W5500_manager.setConfigPortalTimeout( + 120); //If no access point name has been previously entered disable timeout. + Serial.println(F("Got stored Credentials. Timeout 120s for Config Portal")); + +#if USE_ESP_ETH_MANAGER_NTP + + if ( strlen(Ethconfig.TZ_Name) > 0 ) + { + LOGERROR3(F("Current TZ_Name ="), Ethconfig.TZ_Name, F(", TZ = "), Ethconfig.TZ); + + //configTzTime(Ethconfig.TZ, "pool.ntp.org" ); + configTzTime(Ethconfig.TZ, "time.nist.gov", "0.pool.ntp.org", "1.pool.ntp.org"); + } + else + { + Serial.println(F("Current Timezone is not set. Enter Config Portal to set.")); + } + +#endif + } + else + { + // Enter CP only if no stored SSID on flash and file + Serial.println(F("Open Config Portal without Timeout: No stored Credentials.")); + initialConfig = true; + } + + ////////////////////////////////// + + // Connect ETH now if using STA + initEthernet(); + + ////////////////////////////////// + + if (initialConfig) + { + Serial.print(F("Starting configuration portal @ ")); + Serial.println(ETH.localIP()); + + digitalWrite(LED_BUILTIN, LED_ON); // Turn led on as we are in configuration mode. + + //sets timeout in seconds until configuration portal gets turned off. + //If not specified device will remain in configuration mode until + //switched off via webserver or device is restarted. + //AsyncESP32_W5500_manager.setConfigPortalTimeout(600); + + // Starts an access point + if (!AsyncESP32_W5500_manager.startConfigPortal()) + Serial.println(F("Not connected to ETH network but continuing anyway.")); + else + { + Serial.println(F("ETH network connected...yeey :)")); + } + +#if USE_ESP_ETH_MANAGER_NTP + String tempTZ = AsyncESP32_W5500_manager.getTimezoneName(); + + if (strlen(tempTZ.c_str()) < sizeof(Ethconfig.TZ_Name) - 1) + strcpy(Ethconfig.TZ_Name, tempTZ.c_str()); + else + strncpy(Ethconfig.TZ_Name, tempTZ.c_str(), sizeof(Ethconfig.TZ_Name) - 1); + + const char * TZ_Result = AsyncESP32_W5500_manager.getTZ(Ethconfig.TZ_Name); + + if (strlen(TZ_Result) < sizeof(Ethconfig.TZ) - 1) + strcpy(Ethconfig.TZ, TZ_Result); + else + strncpy(Ethconfig.TZ, TZ_Result, sizeof(Ethconfig.TZ_Name) - 1); + + if ( strlen(Ethconfig.TZ_Name) > 0 ) + { + LOGERROR3(F("Saving current TZ_Name ="), Ethconfig.TZ_Name, F(", TZ = "), Ethconfig.TZ); + + //configTzTime(Ethconfig.TZ, "pool.ntp.org" ); + configTzTime(Ethconfig.TZ, "time.nist.gov", "0.pool.ntp.org", "1.pool.ntp.org"); + } + else + { + LOGERROR(F("Current Timezone Name is not set. Enter Config Portal to set.")); + } + +#endif + + AsyncESP32_W5500_manager.getSTAStaticIPConfig(EthSTA_IPconfig); + + saveConfigData(); + } + + digitalWrite(LED_BUILTIN, LED_OFF); // Turn led off as we are not in configuration mode. + + startedAt = millis(); + + Serial.print(F("After waiting ")); + Serial.print((float) (millis() - startedAt) / 1000); + Serial.print(F(" secs more in setup(), connection result is ")); + + if (ESP32_W5500_isConnected()) + { + Serial.print(F("connected. Local IP: ")); + Serial.println(ETH.localIP()); + } +} + +////////////////////////////////////////////////////////////// + +void loop() +{ + // is configuration portal requested? + if ((digitalRead(TRIGGER_PIN) == LOW) || (digitalRead(TRIGGER_PIN2) == LOW)) + { + Serial.println(F("\nConfiguration portal requested.")); + digitalWrite(LED_BUILTIN, LED_ON); // turn the LED on by making the voltage LOW to tell us we are in configuration mode. + + //Local intialization. Once its business is done, there is no need to keep it around + // Use this to default DHCP hostname to ESP32-XXXXXX + //AsyncESP32_W5500_Manager AsyncESP32_W5500_manager(&webServer, &dnsServer); + // Use this to personalize DHCP hostname (RFC952 conformed) + AsyncWebServer webServer(HTTP_PORT); + +#if ( USING_ESP32_S2 || USING_ESP32_C3 ) + AsyncESP32_W5500_Manager AsyncESP32_W5500_manager(&webServer, NULL, "ConfigOnSwitch"); +#else + AsyncDNSServer dnsServer; + + AsyncESP32_W5500_Manager AsyncESP32_W5500_manager(&webServer, &dnsServer, "ConfigOnSwitch"); +#endif + +#if !USE_DHCP_IP +#if USE_CONFIGURABLE_DNS + // Set static IP, Gateway, Subnetmask, DNS1 and DNS2 + AsyncESP32_W5500_manager.setSTAStaticIPConfig(stationIP, gatewayIP, netMask, dns1IP, dns2IP); +#else + // Set static IP, Gateway, Subnetmask, Use auto DNS1 and DNS2. + AsyncESP32_W5500_manager.setSTAStaticIPConfig(stationIP, gatewayIP, netMask); +#endif +#endif + +#if USING_CORS_FEATURE + AsyncESP32_W5500_manager.setCORSHeader("Your Access-Control-Allow-Origin"); +#endif + + //Check if there is stored credentials. + //If not found, device will remain in configuration mode until switched off via webserver. + Serial.println(F("Opening configuration portal. ")); + + if (loadConfigData()) + { + AsyncESP32_W5500_manager.setConfigPortalTimeout( + 120); //If no access point name has been previously entered disable timeout. + Serial.println(F("Got stored Credentials. Timeout 120s for Config Portal")); + } + else + { + // Enter CP only if no stored SSID on flash and file + AsyncESP32_W5500_manager.setConfigPortalTimeout(0); + Serial.println(F("Open Config Portal without Timeout: No stored Credentials.")); + initialConfig = true; + } + + //Starts an access point + //and goes into a blocking loop awaiting configuration + if (!AsyncESP32_W5500_manager.startConfigPortal()) + Serial.println(F("Not connected to ETH network but continuing anyway.")); + else + { + Serial.println(F("ETH network connected...yeey :)")); + Serial.print(F("Local IP: ")); + Serial.println(ETH.localIP()); + } + +#if USE_ESP_ETH_MANAGER_NTP + String tempTZ = AsyncESP32_W5500_manager.getTimezoneName(); + + if (strlen(tempTZ.c_str()) < sizeof(Ethconfig.TZ_Name) - 1) + strcpy(Ethconfig.TZ_Name, tempTZ.c_str()); + else + strncpy(Ethconfig.TZ_Name, tempTZ.c_str(), sizeof(Ethconfig.TZ_Name) - 1); + + const char * TZ_Result = AsyncESP32_W5500_manager.getTZ(Ethconfig.TZ_Name); + + if (strlen(TZ_Result) < sizeof(Ethconfig.TZ) - 1) + strcpy(Ethconfig.TZ, TZ_Result); + else + strncpy(Ethconfig.TZ, TZ_Result, sizeof(Ethconfig.TZ_Name) - 1); + + if ( strlen(Ethconfig.TZ_Name) > 0 ) + { + LOGERROR3(F("Saving current TZ_Name ="), Ethconfig.TZ_Name, F(", TZ = "), Ethconfig.TZ); + + //configTzTime(Ethconfig.TZ, "pool.ntp.org" ); + configTzTime(Ethconfig.TZ, "time.nist.gov", "0.pool.ntp.org", "1.pool.ntp.org"); + } + else + { + LOGERROR(F("Current Timezone Name is not set. Enter Config Portal to set.")); + } + +#endif + + AsyncESP32_W5500_manager.getSTAStaticIPConfig(EthSTA_IPconfig); + + saveConfigData(); + +#if !USE_DHCP_IP + + // Reset to use new Static IP, if different from current ETH.localIP() + if (ETH.localIP() != EthSTA_IPconfig._sta_static_ip) + { + Serial.print(F("Current IP = ")); + Serial.print(ETH.localIP()); + Serial.print(F(". Reset to take new IP = ")); + Serial.println(EthSTA_IPconfig._sta_static_ip); + + ESP.restart(); + delay(2000); + } + +#endif + + digitalWrite(LED_BUILTIN, LED_OFF); // Turn led off as we are not in configuration mode. + } + + // put your main code here, to run repeatedly + check_status(); +} diff --git a/examples/Async_ConfigOnSwitch/README.md b/examples/Async_ConfigOnSwitch/README.md new file mode 100644 index 0000000..66bba7c --- /dev/null +++ b/examples/Async_ConfigOnSwitch/README.md @@ -0,0 +1,12 @@ +# The Config On Switch Example +In this example we initiate the configuration portal to connect an ESP8266 to WiFi by pushing a button. The example requires this version of WiFi Manager. It will not work with the tzapu version. + +## Why Have A Button To Initiate Configuration? +Once the ESP device contains network credentials it will always try to connect to that network in the background and succeed whenever that network becomes visible. The application continues to function and because WiFi networks tend to be flaky only a human has the knowledge of whether a new network is required or it is better to wait for the network to become visible again. Therefore, providing a configuration portal must be human initiated and requiring a button push is a good way to get human input. + +The alternative of automatically going into configuration mode every time a WiFi network becomes invisible will cause the application to stop and the device will sit in configuration mode forever. This is undesirable when a network is temporarily unavailable. + +## Issues With Automatically Going Into Configuration Mode +Providing a timeout on configuration mode is one way to get the application running again when a network becomes temporarily unavailable and no one is around to reboot. However, the application will still not function while waiting for the timeout and the device is vulnerable to sabotage when in config mode. With a push button it will also be vulnerable to sabotage whenever the saboteur has physical access but then it is vulnerable in other ways as well. An assumption that saboteurs will not have physical access to the device is sufficient for most applications. + + diff --git a/examples/Async_ConfigOnSwitchFS/Async_ConfigOnSwitchFS.ino b/examples/Async_ConfigOnSwitchFS/Async_ConfigOnSwitchFS.ino new file mode 100644 index 0000000..37c44d3 --- /dev/null +++ b/examples/Async_ConfigOnSwitchFS/Async_ConfigOnSwitchFS.ino @@ -0,0 +1,1135 @@ +/**************************************************************************************************************************** + Async_ConfigOnSwitchFS.ino + WebServer_ESP32_W5500 is a library for the ESP32 with Ethernet W5500 to run WebServer + + Based on and modified from ESP32-IDF https://github.com/espressif/esp-idf + Built by Khoi Hoang https://github.com/khoih-prog/WebServer_ESP32_W5500 + + Modified from + 1. Tzapu (https://github.com/tzapu/WiFiManager) + 2. Ken Taylor (https://github.com/kentaylor) + 3. Alan Steremberg (https://github.com/alanswx/ESPAsyncWiFiManager) + 4. Khoi Hoang (https://github.com/khoih-prog/AsyncESP32_W5500_manager) + + Built by Khoi Hoang https://github.com/khoih-prog/AsyncESP32_W5500_Manager + Licensed under MIT license + *****************************************************************************************************************************/ +/**************************************************************************************************************************** + This example will open a configuration portal when the reset button is pressed twice. + This method works well on Wemos boards which have a single reset button on board. It avoids using a pin for launching the configuration portal. + + Settings + There are two values to be set in the sketch. + + DRD_TIMEOUT - Number of seconds to wait for the second reset. Set to 10 in the example. + DRD_ADDRESS - The address in ESP8266 RTC RAM to store the flag. This memory must not be used for other purposes in the same sketch. Set to 0 in the example. + + This example, originally relied on the Double Reset Detector library from https://github.com/datacute/DoubleResetDetector + To support ESP32, use ESP_DoubleResetDetector library from //https://github.com/khoih-prog/ESP_DoubleResetDetector + *****************************************************************************************************************************/ +/**************************************************************************************************************************** + This example will open a configuration portal when no configuration has been previously entered or when a button is pushed. + It is the easiest scenario for configuration but requires a pin and a button on the ESP8266 device. + The Flash button is convenient for this on NodeMCU devices. + + Also in this example a password is required to connect to the configuration portal + network. This is inconvenient but means that only those who know the password or those + already connected to the target network can access the configuration portal and + the network credentials will be sent from the browser over an encrypted connection and + can not be read by observers. + *****************************************************************************************************************************/ + +#if !( defined(ESP32) ) + #error This code is intended to run on the (ESP32 + W5500) platform! Please check your Tools->Board setting. +#endif + +////////////////////////////////////////////////////////////// + +// Use from 0 to 4. Higher number, more debugging messages and memory usage. +#define _ESPASYNC_ETH_MGR_LOGLEVEL_ 4 + +// To not display stored SSIDs and PWDs on Config Portal, select false. Default is true +// Even the stored Credentials are not display, just leave them all blank to reconnect and reuse the stored Credentials +//#define DISPLAY_STORED_CREDENTIALS_IN_CP false + +////////////////////////////////////////////////////////////// + +// Enter a MAC address and IP address for your controller below. +#define NUMBER_OF_MAC 20 + +byte mac[][NUMBER_OF_MAC] = +{ + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x01 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x02 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x03 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x04 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x05 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x06 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x07 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x08 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x09 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0A }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0B }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0C }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0D }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0E }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0F }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x10 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x11 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x12 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x13 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x14 }, +}; + +// Select the IP address according to your local network +//IPAddress myIP(192, 168, 2, 232); +//IPAddress myGW(192, 168, 2, 1); +//IPAddress mySN(255, 255, 255, 0); + +// Google DNS Server IP +//IPAddress myDNS(8, 8, 8, 8); + +////////////////////////////////////////////////////////// + +// Optional values to override default settings +// Don't change unless you know what you're doing +//#define ETH_SPI_HOST SPI3_HOST +//#define SPI_CLOCK_MHZ 25 + +// Must connect INT to GPIOxx or not working +//#define INT_GPIO 4 + +//#define MISO_GPIO 19 +//#define MOSI_GPIO 23 +//#define SCK_GPIO 18 +//#define CS_GPIO 5 + +////////////////////////////////////////////////////////// + +#include +// Now support ArduinoJson 6.0.0+ ( tested with v6.14.1 ) +#include // get it from https://arduinojson.org/ or install via Arduino library manager + +//For ESP32, To use ESP32 Dev Module, QIO, Flash 4MB/80MHz, Upload 921600 +//Ported to ESP32 +#include + +////////////////////////////////////////////////////////////// + +// LittleFS has higher priority than SPIFFS +#if ( defined(ESP_ARDUINO_VERSION_MAJOR) && (ESP_ARDUINO_VERSION_MAJOR >= 2) ) + #define USE_LITTLEFS true + #define USE_SPIFFS false +#elif defined(ARDUINO_ESP32C3_DEV) + // For core v1.0.6-, ESP32-C3 only supporting SPIFFS and EEPROM. To use v2.0.0+ for LittleFS + #define USE_LITTLEFS false + #define USE_SPIFFS true +#endif + +#if USE_LITTLEFS + // Use LittleFS + #include "FS.h" + + // Check cores/esp32/esp_arduino_version.h and cores/esp32/core_version.h + //#if ( ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(2, 0, 0) ) //(ESP_ARDUINO_VERSION_MAJOR >= 2) + #if ( defined(ESP_ARDUINO_VERSION_MAJOR) && (ESP_ARDUINO_VERSION_MAJOR >= 2) ) + #if (_ESPASYNC_ETH_MGR_LOGLEVEL_ > 3) + #warning Using ESP32 Core 1.0.6 or 2.0.0+ + #endif + + // The library has been merged into esp32 core from release 1.0.6 + #include // https://github.com/espressif/arduino-esp32/tree/master/libraries/LittleFS + + FS* filesystem = &LittleFS; + #define FileFS LittleFS + #define FS_Name "LittleFS" + #else + #if (_ESPASYNC_ETH_MGR_LOGLEVEL_ > 3) + #warning Using ESP32 Core 1.0.5-. You must install LITTLEFS library + #endif + + // The library has been merged into esp32 core from release 1.0.6 + #include // https://github.com/lorol/LITTLEFS + + FS* filesystem = &LITTLEFS; + #define FileFS LITTLEFS + #define FS_Name "LittleFS" + #endif + +#elif USE_SPIFFS + #include + FS* filesystem = &SPIFFS; + #define FileFS SPIFFS + #define FS_Name "SPIFFS" +#else + // +Use FFat + #include + FS* filesystem = &FFat; + #define FileFS FFat + #define FS_Name "FFat" +#endif + +////////////////////////////////////////////////////////////// + +#define LED_BUILTIN 2 +#define LED_ON HIGH +#define LED_OFF LOW + +////////////////////////////////////////////////////////////// + +//See file .../hardware/espressif/esp32/variants/(esp32|doitESP32devkitV1)/pins_arduino.h +#define LED_BUILTIN 2 // Pin D2 mapped to pin GPIO2/ADC12 of ESP32, control on-board LED +#define PIN_LED 2 // Pin D2 mapped to pin GPIO2/ADC12 of ESP32, control on-board LED + +#define PIN_D0 0 // Pin D0 mapped to pin GPIO0/BOOT/ADC11/TOUCH1 of ESP32 +#define PIN_D1 1 // Pin D1 mapped to pin GPIO1/TX0 of ESP32 +#define PIN_D2 2 // Pin D2 mapped to pin GPIO2/ADC12/TOUCH2 of ESP32 +#define PIN_D3 3 // Pin D3 mapped to pin GPIO3/RX0 of ESP32 +#define PIN_D4 4 // Pin D4 mapped to pin GPIO4/ADC10/TOUCH0 of ESP32 +#define PIN_D5 5 // Pin D5 mapped to pin GPIO5/SPISS/VSPI_SS of ESP32 +#define PIN_D6 6 // Pin D6 mapped to pin GPIO6/FLASH_SCK of ESP32 +#define PIN_D7 7 // Pin D7 mapped to pin GPIO7/FLASH_D0 of ESP32 +#define PIN_D8 8 // Pin D8 mapped to pin GPIO8/FLASH_D1 of ESP32 +#define PIN_D9 9 // Pin D9 mapped to pin GPIO9/FLASH_D2 of ESP32 + +#define PIN_D10 10 // Pin D10 mapped to pin GPIO10/FLASH_D3 of ESP32 +#define PIN_D11 11 // Pin D11 mapped to pin GPIO11/FLASH_CMD of ESP32 +#define PIN_D12 12 // Pin D12 mapped to pin GPIO12/HSPI_MISO/ADC15/TOUCH5/TDI of ESP32 +#define PIN_D13 13 // Pin D13 mapped to pin GPIO13/HSPI_MOSI/ADC14/TOUCH4/TCK of ESP32 +#define PIN_D14 14 // Pin D14 mapped to pin GPIO14/HSPI_SCK/ADC16/TOUCH6/TMS of ESP32 +#define PIN_D15 15 // Pin D15 mapped to pin GPIO15/HSPI_SS/ADC13/TOUCH3/TDO of ESP32 +#define PIN_D16 16 // Pin D16 mapped to pin GPIO16/TX2 of ESP32 +#define PIN_D17 17 // Pin D17 mapped to pin GPIO17/RX2 of ESP32 +#define PIN_D18 18 // Pin D18 mapped to pin GPIO18/VSPI_SCK of ESP32 +#define PIN_D19 19 // Pin D19 mapped to pin GPIO19/VSPI_MISO of ESP32 + +#define PIN_D21 21 // Pin D21 mapped to pin GPIO21/SDA of ESP32 +#define PIN_D22 22 // Pin D22 mapped to pin GPIO22/SCL of ESP32 +#define PIN_D23 23 // Pin D23 mapped to pin GPIO23/VSPI_MOSI of ESP32 +#define PIN_D24 24 // Pin D24 mapped to pin GPIO24 of ESP32 +#define PIN_D25 25 // Pin D25 mapped to pin GPIO25/ADC18/DAC1 of ESP32 +#define PIN_D26 26 // Pin D26 mapped to pin GPIO26/ADC19/DAC2 of ESP32 +#define PIN_D27 27 // Pin D27 mapped to pin GPIO27/ADC17/TOUCH7 of ESP32 + +#define PIN_D32 32 // Pin D32 mapped to pin GPIO32/ADC4/TOUCH9 of ESP32 +#define PIN_D33 33 // Pin D33 mapped to pin GPIO33/ADC5/TOUCH8 of ESP32 +#define PIN_D34 34 // Pin D34 mapped to pin GPIO34/ADC6 of ESP32 + +//Only GPIO pin < 34 can be used as output. Pins >= 34 can be only inputs +//See .../cores/esp32/esp32-hal-gpio.h/c +//#define digitalPinIsValid(pin) ((pin) < 40 && esp32_gpioMux[(pin)].reg) +//#define digitalPinCanOutput(pin) ((pin) < 34 && esp32_gpioMux[(pin)].reg) +//#define digitalPinToRtcPin(pin) (((pin) < 40)?esp32_gpioMux[(pin)].rtc:-1) +//#define digitalPinToAnalogChannel(pin) (((pin) < 40)?esp32_gpioMux[(pin)].adc:-1) +//#define digitalPinToTouchChannel(pin) (((pin) < 40)?esp32_gpioMux[(pin)].touch:-1) +//#define digitalPinToDacChannel(pin) (((pin) == 25)?0:((pin) == 26)?1:-1) + +#define PIN_D35 35 // Pin D35 mapped to pin GPIO35/ADC7 of ESP32 +#define PIN_D36 36 // Pin D36 mapped to pin GPIO36/ADC0/SVP of ESP32 +#define PIN_D39 39 // Pin D39 mapped to pin GPIO39/ADC3/SVN of ESP32 + +#define PIN_RX0 3 // Pin RX0 mapped to pin GPIO3/RX0 of ESP32 +#define PIN_TX0 1 // Pin TX0 mapped to pin GPIO1/TX0 of ESP32 + +#define PIN_SCL 22 // Pin SCL mapped to pin GPIO22/SCL of ESP32 +#define PIN_SDA 21 // Pin SDA mapped to pin GPIO21/SDA of ESP32 + +////////////////////////////////////////////////////////////// + +/* Trigger for inititating config mode is Pin D3 and also flash button on NodeMCU + Flash button is convenient to use but if it is pressed it will stuff up the serial port device driver + until the computer is rebooted on windows machines. +*/ +const int TRIGGER_PIN = PIN_D0; // Pin D0 mapped to pin GPIO0/BOOT/ADC11/TOUCH1 of ESP32 +/* + Alternative trigger pin. Needs to be connected to a button to use this pin. It must be a momentary connection + not connected permanently to ground. Either trigger pin will work. +*/ +#if ( ARDUINO_ESP32C3_DEV ) + const int TRIGGER_PIN2 = PIN_D8; // Pin D8 mapped to pin GPIO8/FLASH_D1 of ESP32 +#else + const int TRIGGER_PIN2 = PIN_D25; // Pin D25 mapped to pin GPIO25/ADC18/DAC1 of ESP32 +#endif + +int pinSda = PIN_SDA; // Pin SDA mapped to pin GPIO21/SDA of ESP32 +int pinScl = PIN_SCL; // Pin SCL mapped to pin GPIO22/SCL of ESP32 + +////////////////////////////////////////////////////////////// + +const char* JSON_CONFIG_FILE = "/ConfigSW.json"; + +// Variables + +// Default configuration values +char thingspeakApiKey[17] = ""; +bool sensorDht22 = true; + +#define ThingSpeakAPI_Label "thingspeakApiKey" +#define SensorDht22_Label "SensorDHT22" +#define PinSDA_Label "PinSda" +#define PinSCL_Label "PinScl" + +////////////////////////////////////////////////////////////// + +// Function Prototypes + +bool readConfigFile(); +bool writeConfigFile(); + +// You only need to format the filesystem once +//#define FORMAT_FILESYSTEM true +#define FORMAT_FILESYSTEM false + +////////////////////////////////////////////////////////////// + +// Assuming max 49 chars +#define TZNAME_MAX_LEN 50 +#define TIMEZONE_MAX_LEN 50 + +typedef struct +{ + char TZ_Name[TZNAME_MAX_LEN]; // "America/Toronto" + char TZ[TIMEZONE_MAX_LEN]; // "EST5EDT,M3.2.0,M11.1.0" + uint16_t checksum; +} EthConfig; + +EthConfig Ethconfig; + +#define CONFIG_FILENAME F("/eth_cred.dat") + +////////////////////////////////////////////////////////////// + +// Indicates whether ESP has credentials saved from previous session +bool initialConfig = false; + +// Use false if you don't like to display Available Pages in Information Page of Config Portal +// Comment out or use true to display Available Pages in Information Page of Config Portal +// Must be placed before #include +#define USE_AVAILABLE_PAGES true + +// From v1.0.10 to permit disable/enable StaticIP configuration in Config Portal from sketch. Valid only if DHCP is used. +// You'll loose the feature of dynamically changing from DHCP to static IP, or vice versa +// You have to explicitly specify false to disable the feature. +//#define USE_STATIC_IP_CONFIG_IN_CP false + +// Use false to disable NTP config. Advisable when using Cellphone, Tablet to access Config Portal. +// See Issue 23: On Android phone ConfigPortal is unresponsive (https://github.com/khoih-prog/ESP_WiFiManager/issues/23) +#define USE_ESP_ETH_MANAGER_NTP true + +// Just use enough to save memory. On ESP8266, can cause blank ConfigPortal screen +// if using too much memory +#define USING_AFRICA false +#define USING_AMERICA true +#define USING_ANTARCTICA false +#define USING_ASIA false +#define USING_ATLANTIC false +#define USING_AUSTRALIA false +#define USING_EUROPE false +#define USING_INDIAN false +#define USING_PACIFIC false +#define USING_ETC_GMT false + +// Use true to enable CloudFlare NTP service. System can hang if you don't have Internet access while accessing CloudFlare +// See Issue #21: CloudFlare link in the default portal (https://github.com/khoih-prog/ESP_WiFiManager/issues/21) +#define USE_CLOUDFLARE_NTP false + +// New in v1.0.11 +#define USING_CORS_FEATURE true + +////////////////////////////////////////////////////////////// + +// Use USE_DHCP_IP == true for dynamic DHCP IP, false to use static IP which you have to change accordingly to your network +#if (defined(USE_STATIC_IP_CONFIG_IN_CP) && !USE_STATIC_IP_CONFIG_IN_CP) + // Force DHCP to be true + #if defined(USE_DHCP_IP) + #undef USE_DHCP_IP + #endif + #define USE_DHCP_IP true +#else + // You can select DHCP or Static IP here + //#define USE_DHCP_IP true + #define USE_DHCP_IP false +#endif + +#if ( USE_DHCP_IP ) + // Use DHCP + + #if (_ESPASYNC_ETH_MGR_LOGLEVEL_ > 3) + #warning Using DHCP IP + #endif + + IPAddress stationIP = IPAddress(0, 0, 0, 0); + IPAddress gatewayIP = IPAddress(192, 168, 2, 1); + IPAddress netMask = IPAddress(255, 255, 255, 0); + +#else + // Use static IP + + #if (_ESPASYNC_ETH_MGR_LOGLEVEL_ > 3) + #warning Using static IP + #endif + + IPAddress stationIP = IPAddress(192, 168, 2, 232); + IPAddress gatewayIP = IPAddress(192, 168, 2, 1); + IPAddress netMask = IPAddress(255, 255, 255, 0); +#endif + +////////////////////////////////////////////////////////////// + +#define USE_CONFIGURABLE_DNS true + +IPAddress dns1IP = gatewayIP; +IPAddress dns2IP = IPAddress(8, 8, 8, 8); + +#include //https://github.com/khoih-prog/AsyncESP32_W5500_Manager + +#define HTTP_PORT 80 + +////////////////////////////////////////////////////////////// + +/****************************************** + // Defined in AsyncESP32_W5500_Manager.hpp + typedef struct + { + IPAddress _sta_static_ip; + IPAddress _sta_static_gw; + IPAddress _sta_static_sn; + #if USE_CONFIGURABLE_DNS + IPAddress _sta_static_dns1; + IPAddress _sta_static_dns2; + #endif + } ETH_STA_IPConfig; +******************************************/ + +ETH_STA_IPConfig EthSTA_IPconfig; + +////////////////////////////////////////////////////////////// + +void initSTAIPConfigStruct(ETH_STA_IPConfig &in_EthSTA_IPconfig) +{ + in_EthSTA_IPconfig._sta_static_ip = stationIP; + in_EthSTA_IPconfig._sta_static_gw = gatewayIP; + in_EthSTA_IPconfig._sta_static_sn = netMask; +#if USE_CONFIGURABLE_DNS + in_EthSTA_IPconfig._sta_static_dns1 = dns1IP; + in_EthSTA_IPconfig._sta_static_dns2 = dns2IP; +#endif +} + +////////////////////////////////////////////////////////////// + +void displayIPConfigStruct(ETH_STA_IPConfig in_EthSTA_IPconfig) +{ + LOGERROR3(F("stationIP ="), in_EthSTA_IPconfig._sta_static_ip, ", gatewayIP =", in_EthSTA_IPconfig._sta_static_gw); + LOGERROR1(F("netMask ="), in_EthSTA_IPconfig._sta_static_sn); +#if USE_CONFIGURABLE_DNS + LOGERROR3(F("dns1IP ="), in_EthSTA_IPconfig._sta_static_dns1, ", dns2IP =", in_EthSTA_IPconfig._sta_static_dns2); +#endif +} + +////////////////////////////////////////////////////////////// + +void toggleLED() +{ + //toggle state + digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); +} + +////////////////////////////////////////////////////////////// + +#if USE_ESP_ETH_MANAGER_NTP +void printLocalTime() +{ + struct tm timeinfo; + + getLocalTime( &timeinfo ); + + // Valid only if year > 2000. + // You can get from timeinfo : tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec + if (timeinfo.tm_year > 100 ) + { + Serial.print("Local Date/Time: "); + Serial.print( asctime( &timeinfo ) ); + } +} +#endif + +////////////////////////////////////////////////////////////// + +void heartBeatPrint() +{ +#if USE_ESP_ETH_MANAGER_NTP + printLocalTime(); +#else + static int num = 1; + + if (ESP32_W5500_isConnected()) + Serial.print(F("H")); // H means connected to Ethernet + else + Serial.print(F("F")); // F means not connected to Ethernet + + if (num == 80) + { + Serial.println(); + num = 1; + } + else if (num++ % 10 == 0) + { + Serial.print(F(" ")); + } + +#endif +} + +////////////////////////////////////////////////////////////// + +void check_status() +{ + static ulong checkstatus_timeout = 0; + static ulong LEDstatus_timeout = 0; + + static ulong current_millis; + +#if USE_ESP_ETH_MANAGER_NTP +#define HEARTBEAT_INTERVAL 60000L +#else +#define HEARTBEAT_INTERVAL 10000L +#endif + +#define LED_INTERVAL 2000L + + current_millis = millis(); + + if ((current_millis > LEDstatus_timeout) || (LEDstatus_timeout == 0)) + { + // Toggle LED at LED_INTERVAL = 2s + toggleLED(); + LEDstatus_timeout = current_millis + LED_INTERVAL; + } + + // Print hearbeat every HEARTBEAT_INTERVAL (10) seconds. + if ((current_millis > checkstatus_timeout) || (checkstatus_timeout == 0)) + { + heartBeatPrint(); + checkstatus_timeout = current_millis + HEARTBEAT_INTERVAL; + } +} + +////////////////////////////////////////////////////////////// + +int calcChecksum(uint8_t* address, uint16_t sizeToCalc) +{ + uint16_t checkSum = 0; + + for (uint16_t index = 0; index < sizeToCalc; index++) + { + checkSum += * ( ( (byte*) address ) + index); + } + + return checkSum; +} + +////////////////////////////////////////////////////////////// + +bool loadConfigData() +{ + File file = FileFS.open(CONFIG_FILENAME, "r"); + LOGERROR(F("LoadCfgFile ")); + + memset((void *) &Ethconfig, 0, sizeof(Ethconfig)); + memset((void *) &EthSTA_IPconfig, 0, sizeof(EthSTA_IPconfig)); + + if (file) + { + file.readBytes((char *) &Ethconfig, sizeof(Ethconfig)); + file.readBytes((char *) &EthSTA_IPconfig, sizeof(EthSTA_IPconfig)); + file.close(); + + LOGERROR(F("OK")); + + if ( Ethconfig.checksum != calcChecksum( (uint8_t*) &Ethconfig, sizeof(Ethconfig) - sizeof(Ethconfig.checksum) ) ) + { + LOGERROR(F("Ethconfig checksum wrong")); + + return false; + } + + displayIPConfigStruct(EthSTA_IPconfig); + + return true; + } + else + { + LOGERROR(F("failed")); + + return false; + } +} + +////////////////////////////////////////////////////////////// + +void saveConfigData() +{ + File file = FileFS.open(CONFIG_FILENAME, "w"); + LOGERROR(F("SaveCfgFile ")); + + if (file) + { + Ethconfig.checksum = calcChecksum( (uint8_t*) &Ethconfig, sizeof(Ethconfig) - sizeof(Ethconfig.checksum) ); + + file.write((uint8_t*) &Ethconfig, sizeof(Ethconfig)); + + displayIPConfigStruct(EthSTA_IPconfig); + + file.write((uint8_t*) &EthSTA_IPconfig, sizeof(EthSTA_IPconfig)); + file.close(); + + LOGERROR(F("OK")); + } + else + { + LOGERROR(F("failed")); + } +} + +////////////////////////////////////////////////////////////// + +bool readConfigFile() +{ + // this opens the config file in read-mode + File f = FileFS.open(JSON_CONFIG_FILE, "r"); + + if (!f) + { + Serial.println(F("Configuration file not found")); + + return false; + } + else + { + // we could open the file + size_t size = f.size(); + // Allocate a buffer to store contents of the file. + std::unique_ptr buf(new char[size + 1]); + + // Read and store file contents in buf + f.readBytes(buf.get(), size); + // Closing file + f.close(); + // Using dynamic JSON buffer which is not the recommended memory model, but anyway + // See https://github.com/bblanchon/ArduinoJson/wiki/Memory%20model + +#if (ARDUINOJSON_VERSION_MAJOR >= 6) + DynamicJsonDocument json(1024); + auto deserializeError = deserializeJson(json, buf.get()); + + if ( deserializeError ) + { + Serial.println(F("JSON parseObject() failed")); + + return false; + } + + serializeJson(json, Serial); +#else + DynamicJsonBuffer jsonBuffer; + // Parse JSON string + JsonObject& json = jsonBuffer.parseObject(buf.get()); + + // Test if parsing succeeds. + if (!json.success()) + { + Serial.println(F("JSON parseObject() failed")); + return false; + } + + json.printTo(Serial); +#endif + + // Parse all config file parameters, override + // local config variables with parsed values + if (json.containsKey(ThingSpeakAPI_Label)) + { + strcpy(thingspeakApiKey, json[ThingSpeakAPI_Label]); + } + + if (json.containsKey(SensorDht22_Label)) + { + sensorDht22 = json[SensorDht22_Label]; + } + + if (json.containsKey(PinSDA_Label)) + { + pinSda = json[PinSDA_Label]; + } + + if (json.containsKey(PinSCL_Label)) + { + pinScl = json[PinSCL_Label]; + } + } + + Serial.println(F("\nConfig file was successfully parsed")); + + return true; +} + +////////////////////////////////////////////////////////////// + +bool writeConfigFile() +{ + Serial.println(F("Saving config file")); + +#if (ARDUINOJSON_VERSION_MAJOR >= 6) + DynamicJsonDocument json(1024); +#else + DynamicJsonBuffer jsonBuffer; + JsonObject& json = jsonBuffer.createObject(); +#endif + + // JSONify local configuration parameters + json[ThingSpeakAPI_Label] = thingspeakApiKey; + json[SensorDht22_Label] = sensorDht22; + json[PinSDA_Label] = pinSda; + json[PinSCL_Label] = pinScl; + + // Open file for writing + File f = FileFS.open(JSON_CONFIG_FILE, "w"); + + if (!f) + { + Serial.println(F("Failed to open config file for writing")); + + return false; + } + +#if (ARDUINOJSON_VERSION_MAJOR >= 6) + serializeJsonPretty(json, Serial); + // Write data to file and close it + serializeJson(json, f); +#else + json.prettyPrintTo(Serial); + // Write data to file and close it + json.printTo(f); +#endif + + f.close(); + + Serial.println(F("\nConfig file was successfully saved")); + + return true; +} + +////////////////////////////////////////////////////////////// + +void beginEthernet() +{ + LOGWARN(F("Default SPI pinout:")); + LOGWARN1(F("SPI_HOST:"), ETH_SPI_HOST); + LOGWARN1(F("MOSI:"), MOSI_GPIO); + LOGWARN1(F("MISO:"), MISO_GPIO); + LOGWARN1(F("SCK:"), SCK_GPIO); + LOGWARN1(F("CS:"), CS_GPIO); + LOGWARN1(F("INT:"), INT_GPIO); + LOGWARN1(F("SPI Clock (MHz):"), SPI_CLOCK_MHZ); + LOGWARN(F("=========================")); + + /////////////////////////////////// + + // To be called before ETH.begin() + ESP32_W5500_onEvent(); + + // start the ethernet connection and the server: + // Use DHCP dynamic IP and random mac + uint16_t index = millis() % NUMBER_OF_MAC; + + //bool begin(int MISO_GPIO, int MOSI_GPIO, int SCLK_GPIO, int CS_GPIO, int INT_GPIO, int SPI_CLOCK_MHZ, + // int SPI_HOST, uint8_t *W5500_Mac = W5500_Default_Mac); + //ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST ); + ETH.begin( MISO_GPIO, MOSI_GPIO, SCK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, ETH_SPI_HOST, mac[index] ); +} + +////////////////////////////////////////////////////////////// + +void initEthernet() +{ +#if !( USE_DHCP_IP ) + displayIPConfigStruct(EthSTA_IPconfig); + + // Static IP, leave without this line to get IP via DHCP + //bool config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1 = 0, IPAddress dns2 = 0); + //ETH.config(stationIP, gatewayIP, netMask, dns1IP, dns2IP); + ETH.config(EthSTA_IPconfig._sta_static_ip, EthSTA_IPconfig._sta_static_gw, EthSTA_IPconfig._sta_static_sn, + EthSTA_IPconfig._sta_static_dns1); +#endif + + ESP32_W5500_waitForConnect(); +} + +////////////////////////////////////////////////////////////// + +void setup() +{ + //set led pin as output + pinMode(LED_BUILTIN, OUTPUT); + + // Put your setup code here, to run once + Serial.begin(115200); + + while (!Serial && millis() < 5000); + + delay(200); + + Serial.print(F("\nStarting Async_ConfigOnSwichFS using ")); + Serial.print(FS_Name); + Serial.print(F(" on ")); + Serial.print(ARDUINO_BOARD); + Serial.print(F(" with ")); + Serial.println(SHIELD_TYPE); + Serial.println(ASYNC_ESP32_W5500_MANAGER_VERSION); + + // Initialize the LED digital pin as an output. + pinMode(PIN_LED, OUTPUT); + // Initialize trigger pins + pinMode(TRIGGER_PIN, INPUT_PULLUP); + pinMode(TRIGGER_PIN2, INPUT_PULLUP); + + if (FORMAT_FILESYSTEM) + { + Serial.println(F("Forced Formatting.")); + FileFS.format(); + } + + // Format FileFS if not yet +#ifdef ESP32 + + if (!FileFS.begin(true)) +#else + if (!FileFS.begin()) +#endif + { +#ifdef ESP8266 + FileFS.format(); +#endif + + Serial.println(F("SPIFFS/LittleFS failed! Already tried formatting.")); + + if (!FileFS.begin()) + { + // prevents debug info from the library to hide err message. + delay(100); + +#if USE_LITTLEFS + Serial.println(F("LittleFS failed!. Please use SPIFFS or EEPROM. Stay forever")); +#else + Serial.println(F("SPIFFS failed!. Please use LittleFS or EEPROM. Stay forever")); +#endif + + while (true) + { + delay(1); + } + } + } + + beginEthernet(); + + initSTAIPConfigStruct(EthSTA_IPconfig); + + if (!readConfigFile()) + { + Serial.println(F("Failed to read ConfigFile, using default values")); + } + + unsigned long startedAt = millis(); + + //Local intialization. Once its business is done, there is no need to keep it around + // Use this to default DHCP hostname to ESP32-XXXXXX + //AsyncESP32_W5500_Manager AsyncESP32_W5500_manager(&webServer, &dnsServer); + // Use this to personalize DHCP hostname (RFC952 conformed) + AsyncWebServer webServer(HTTP_PORT); + +#if ( USING_ESP32_S2 || USING_ESP32_C3 ) + AsyncESP32_W5500_Manager AsyncESP32_W5500_manager(&webServer, NULL, "ConfigOnSwitchFS"); +#else + AsyncDNSServer dnsServer; + + AsyncESP32_W5500_Manager AsyncESP32_W5500_manager(&webServer, &dnsServer, "ConfigOnSwitchFS"); +#endif + + AsyncESP32_W5500_manager.setDebugOutput(true); + +#if !USE_DHCP_IP + // Set (static IP, Gateway, Subnetmask, DNS1 and DNS2) or (IP, Gateway, Subnetmask) + AsyncESP32_W5500_manager.setSTAStaticIPConfig(EthSTA_IPconfig); +#endif + +#if USING_CORS_FEATURE + AsyncESP32_W5500_manager.setCORSHeader("Your Access-Control-Allow-Origin"); +#endif + + bool configDataLoaded = false; + + if (loadConfigData()) + { + configDataLoaded = true; + + AsyncESP32_W5500_manager.setConfigPortalTimeout( + 120); //If no access point name has been previously entered disable timeout. + Serial.println(F("Got stored Credentials. Timeout 120s for Config Portal")); + +#if USE_ESP_ETH_MANAGER_NTP + + if ( strlen(Ethconfig.TZ_Name) > 0 ) + { + LOGERROR3(F("Current TZ_Name ="), Ethconfig.TZ_Name, F(", TZ = "), Ethconfig.TZ); + + //configTzTime(Ethconfig.TZ, "pool.ntp.org" ); + configTzTime(Ethconfig.TZ, "time.nist.gov", "0.pool.ntp.org", "1.pool.ntp.org"); + } + else + { + Serial.println(F("Current Timezone is not set. Enter Config Portal to set.")); + } + +#endif + } + else + { + // Enter CP only if no stored SSID on flash and file + Serial.println(F("Open Config Portal without Timeout: No stored Credentials.")); + initialConfig = true; + } + + + ////////////////////////////////// + + // Connect ETH now if using STA + initEthernet(); + + ////////////////////////////////// + + if (initialConfig) + { + Serial.print(F("Starting configuration portal @ ")); + Serial.println(ETH.localIP()); + + digitalWrite(LED_BUILTIN, LED_ON); // Turn led on as we are in configuration mode. + + //sets timeout in seconds until configuration portal gets turned off. + //If not specified device will remain in configuration mode until + //switched off via webserver or device is restarted. + //AsyncESP32_W5500_manager.setConfigPortalTimeout(600); + + // Starts an access point + if (!AsyncESP32_W5500_manager.startConfigPortal()) + Serial.println(F("Not connected to ETH network but continuing anyway.")); + else + { + Serial.println(F("ETH network connected...yeey :)")); + } + + +#if USE_ESP_ETH_MANAGER_NTP + String tempTZ = AsyncESP32_W5500_manager.getTimezoneName(); + + if (strlen(tempTZ.c_str()) < sizeof(Ethconfig.TZ_Name) - 1) + strcpy(Ethconfig.TZ_Name, tempTZ.c_str()); + else + strncpy(Ethconfig.TZ_Name, tempTZ.c_str(), sizeof(Ethconfig.TZ_Name) - 1); + + const char * TZ_Result = AsyncESP32_W5500_manager.getTZ(Ethconfig.TZ_Name); + + if (strlen(TZ_Result) < sizeof(Ethconfig.TZ) - 1) + strcpy(Ethconfig.TZ, TZ_Result); + else + strncpy(Ethconfig.TZ, TZ_Result, sizeof(Ethconfig.TZ_Name) - 1); + + if ( strlen(Ethconfig.TZ_Name) > 0 ) + { + LOGERROR3(F("Saving current TZ_Name ="), Ethconfig.TZ_Name, F(", TZ = "), Ethconfig.TZ); + + //configTzTime(Ethconfig.TZ, "pool.ntp.org" ); + configTzTime(Ethconfig.TZ, "time.nist.gov", "0.pool.ntp.org", "1.pool.ntp.org"); + } + else + { + LOGERROR(F("Current Timezone Name is not set. Enter Config Portal to set.")); + } + +#endif + + AsyncESP32_W5500_manager.getSTAStaticIPConfig(EthSTA_IPconfig); + + saveConfigData(); + } + + digitalWrite(LED_BUILTIN, LED_OFF); // Turn led off as we are not in configuration mode. + + startedAt = millis(); + + Serial.print(F("After waiting ")); + Serial.print((float) (millis() - startedAt) / 1000); + Serial.print(F(" secs more in setup(), connection result is ")); + + if (ESP32_W5500_isConnected()) + { + Serial.print(F("connected. Local IP: ")); + Serial.println(ETH.localIP()); + } +} + +////////////////////////////////////////////////////////////// + +void loop() +{ + // is configuration portal requested? + if ((digitalRead(TRIGGER_PIN) == LOW) || (digitalRead(TRIGGER_PIN2) == LOW)) + { + Serial.println(F("\nConfiguration portal requested.")); + digitalWrite(LED_BUILTIN, LED_ON); // turn the LED on by making the voltage LOW to tell us we are in configuration mode. + + //Local intialization. Once its business is done, there is no need to keep it around + // Use this to default DHCP hostname to ESP32-XXXXXX + //AsyncESP32_W5500_Manager AsyncESP32_W5500_manager(&webServer, &dnsServer); + // Use this to personalize DHCP hostname (RFC952 conformed) + AsyncWebServer webServer(HTTP_PORT); + AsyncDNSServer dnsServer; + + AsyncESP32_W5500_Manager AsyncESP32_W5500_manager(&webServer, &dnsServer, "ConfigOnSwitchFS"); + + //Check if there is stored credentials. + //If not found, device will remain in configuration mode until switched off via webserver. + Serial.println(F("Opening configuration portal. ")); + + if (loadConfigData()) + { + AsyncESP32_W5500_manager.setConfigPortalTimeout( + 120); //If no access point name has been previously entered disable timeout. + Serial.println(F("Got stored Credentials. Timeout 120s for Config Portal")); + } + else + { + // Enter CP only if no stored SSID on flash and file + Serial.println(F("Open Config Portal without Timeout: No stored Credentials.")); + initialConfig = true; + } + + // Extra parameters to be configured + // After connecting, parameter.getValue() will get you the configured value + // Format: