├── .github
├── FUNDING.yml
├── dependabot.yml
└── workflows
│ ├── main.yml
│ ├── release.yml
│ └── stale.yml
├── .gitignore
├── LICENSE
├── README.md
├── data
└── img
│ ├── arduinobootstrapper.png
│ └── jetbrains.png
├── examples
├── ChangeName.cpp
└── ChangeName.h
├── library.json
├── library.properties
├── platformio.ini
├── secrets.ini
└── src
├── BootstrapManager.cpp
├── BootstrapManager.h
├── Configuration.cpp
├── Configuration.h
├── EthManager.cpp
├── EthManager.h
├── Helpers.cpp
├── Helpers.h
├── PingESP.cpp
├── PingESP.h
├── QueueManager.cpp
├── QueueManager.h
├── Secrets.h
├── WifiManager.cpp
└── WifiManager.h
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [sblantipodi]
2 | custom: ["https://www.paypal.com/donate?hosted_button_id=ZEJM8ZLQW5E4A"]
3 |
4 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | ---
2 | version: 2
3 | updates:
4 | - package-ecosystem: github-actions
5 | directory: "/"
6 | schedule:
7 | interval: daily
8 | open-pull-requests-limit: 10
9 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: GitHub Actions CI
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v4
12 | - name: Set up Python
13 | uses: actions/setup-python@v5
14 | with:
15 | python-version: '3.11'
16 | - name: Install dependencies
17 | run: |
18 | python -m pip install --upgrade pip
19 | pip install platformio
20 | python --version
21 | - name: Install submodules
22 | run: |
23 | pwd
24 | git submodule update --init
25 | ls
26 | - name: Setup template config files
27 | run: |
28 | mkdir /home/runner/work/arduino_bootstrapper/arduino_bootstrapper/include
29 | cp /home/runner/work/arduino_bootstrapper/arduino_bootstrapper/examples/ChangeName.cpp /home/runner/work/arduino_bootstrapper/arduino_bootstrapper/src/ChangeName.cpp
30 | cp /home/runner/work/arduino_bootstrapper/arduino_bootstrapper/examples/ChangeName.h /home/runner/work/arduino_bootstrapper/arduino_bootstrapper/include/ChangeName.h
31 | # - name: Setup tmate session
32 | # uses: mxschmitt/action-tmate@v3
33 | - name: Static code analysis
34 | run: platformio check --verbose --severity=high --skip-packages
35 | - name: Run PlatformIO
36 | run: platformio run -e bootstrapper
37 | - name: Creating artifact from BIN file
38 | uses: actions/upload-artifact@v4
39 | with:
40 | name: firmware_build_artifact_arduino_bootstrapper.bin
41 | path: .pio/build/bootstrapper/firmware.bin
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Create Release
2 |
3 | on:
4 | push:
5 | # Sequence of patterns matched against refs/tags
6 | tags:
7 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
8 |
9 | jobs:
10 | build:
11 | env:
12 | commitmsg: ${{ github.event.head_commit.message }}
13 |
14 | name: Create Release
15 | runs-on: ubuntu-latest
16 | steps:
17 | - name: Checkout code
18 | uses: actions/checkout@v4
19 | - name: get commit message
20 | run: |
21 | echo $commitmsg
22 | - name: Create Release
23 | id: create_release
24 | uses: softprops/action-gh-release@v1
25 | with:
26 | token: ${{ secrets.GITHUB_TOKEN }}
27 | tag_name: ${{ github.ref }}
28 | name: Release ${{ github.ref_name }}
29 | body: |
30 | Changes in this Release
31 | ${{ env.commitmsg }}
32 | draft: false
33 | prerelease: false
--------------------------------------------------------------------------------
/.github/workflows/stale.yml:
--------------------------------------------------------------------------------
1 | name: "Close stale issues"
2 | on:
3 | schedule:
4 | - cron: "0 0 * * *"
5 |
6 | jobs:
7 | stale:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/stale@main
11 | with:
12 | repo-token: ${{ secrets.GITHUB_TOKEN }}
13 | stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days'
14 | days-before-stale: 30
15 | days-before-close: 5
16 | labels-to-add-when-unstale: 'keep'
17 | exempt-issue-labels: 'blocked,must,should,keep'
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode
2 | .idea
3 | .pio
4 | src/ChangeName.cpp
5 | include/ChangeName.h
6 | cmake-build-*
7 | CMakeLists.txt
8 | CMakeListsPrivate.txt
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Davide Perini
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Arduino Bootstrapper
2 |
3 | Utility classes for bootstrapping Arduino projects.
4 | In few minutes you will be able to read and write to storage (in JSON format), read and send messages to an MQTT queue, connect to WiFi without hardcoding passwords and more...
5 | `Arduino Bootstrapper` wants to be a starting point for **creating good projects without code duplications**.
6 |
7 | Espressif **`ESP8266/ESP32`** are the default platforms but you can easily add support for other platforms and boards.
8 | _Written for Arduino IDE and PlatformIO._
9 |
10 | [](https://github.com/sblantipodi/arduino_bootstrapper/actions)
11 | [](https://www.ardu-badge.com/Bootstrapper)
12 | [](https://github.com/sblantipodi/arduino_bootstrapper/releases)
13 | [](https://opensource.org/licenses/MIT)
14 | [](https://GitHub.com/sblantipodi/arduino_bootstrapper/graphs/commit-activity)
15 | [](https://www.dpsoftware.org)
16 |
17 | If you like **Arduino Bootstrapper**, give it a star, or fork it and contribute!
18 |
19 | [](https://github.com/sblantipodi/arduino_bootstrapper/stargazers)
20 | [](https://github.com/sblantipodi/arduino_bootstrapper/network)
21 |
22 | ## Credits
23 | - Davide Perini
24 |
25 | ## How to get help if you are stuck?
26 | Open an issue here on Github, your questions could be useful to other users.
27 |
28 | ## How To
29 | There is two way to bootstrap your software using this utilities.
30 | 1) Bootstrap a project from scratch
31 | 2) Import those utilities to your existing project
32 |
33 | #### There are other projects that uses this utility, you can explore their sources here:
34 | [Smart Thermostat](https://github.com/sblantipodi/smart_thermostat), [Solar Station](https://github.com/sblantipodi/solar_station), [Glow Worm Luciferin](https://github.com/sblantipodi/glow_worm_luciferin), [Smart Watch Winder](https://github.com/sblantipodi/smart_watch_winder)
35 |
36 | ## 1) Bootstrap a project from scratch
37 | Clone the bootstrapper
38 | ```
39 | git clone git@github.com:sblantipodi/arduino_bootstrapper.git
40 | ```
41 | Simply edit those file as per description, _few minutes required_
42 |
43 | .
44 | ├── ...
45 | ├── examples # Project src folder
46 | │ ├── ChangeName.cpp # Main file with loop() and setup() function, rename and copy it into your src folder
47 | │ ├── ChangeName.h # Main header file, rename and copy it into your include folder
48 | │ └── ...
49 | ├── src # Folder for core files, edit those files and contribute!
50 | │ ├── BootstrapManager.h # Core header file with utility classes for bootstrapping
51 | │ ├── QueueManager.h # Core header file with utility classes for Queue and MQTT management
52 | │ ├── WifiManager.h # Core header file with utility classes for Wifi and OTA upload management
53 | │ ├── Helpers.h # Core header file with helper classes
54 | │ └── ...
55 | ├── platformio.ini # Configure all the required info (ex: Wifi device name, DNS gateway, ecc.)
56 | ├── secrets.ini.template # Configure password and rename the file in secrets.ini
57 | └── ...
58 |
59 | ***NOTE:***
60 | You should implement those functions that are passed by _*pointer_ to the main bootstrap functions:
61 | ```c++
62 | class BootstrapManager {
63 | ...
64 | public:
65 | void bootstrapSetup(
66 | void (*manageDisconnectionFunction)(),
67 | void (*manageHardwareButton)(),
68 | void (*callback)(char*, byte*, unsigned int)
69 | );
70 | void bootstrapLoop(
71 | void (*manageDisconnectionFunction)(),
72 | void (*manageQueueSubscription)(),
73 | void (*manageHardwareButton)()
74 | );
75 | ...
76 | };
77 | ```
78 |
79 | - `manageDisconnections()` # _OPTIONAL_ put the logic you need in case your microcontroller is disconnected from the network
80 | - `manageHardwareButton()` # _OPTIONAL_ put special instruction for hardware button management during network disconnections
81 | - `manageQueueSubscription()` # subscribe to the desired mqtt topics
82 | - `callback()` # callback function called when a message arrives from the queue
83 |
84 | ## 2) Import those utilities to your existing project
85 | You can import `Arduino Bootstrapper` into your existing projects in two way:
86 | 1) Import via public registries (easyest way to import)
87 | ```
88 | // For PlatformIO
89 | Add `lib_deps` to your `platformio.ini`
90 | lib_deps = ArduinoBootstrapper
91 | ```
92 | ```
93 | // For ArduinoIDE
94 | Simply import the Bootstrapper library from the library manager
95 | ```
96 | 2) Import via git submodules (faster updates to latest releases)
97 | ```
98 | git submodule add https://github.com/sblantipodi/arduino_bootstrapper.git arduino_bootstrapper
99 | ```
100 |
101 | For both importing method you should then add extra dirs to your `platformio.ini`
102 | ```ini
103 | lib_extra_dirs = arduino_bootstrapper
104 | ```
105 | Copy and configure `~/arduino_bootstrapper/secrets.ini.template` into `secrets.ini.template`
106 |
107 | Please include BootrapManager.h into your main header file:
108 | ```c++
109 | #include "BootstrapManager.h"
110 | ```
111 | and initialize the BootstrapManager class:
112 | ```
113 | BootstrapManager bootstrapManager;
114 | ```
115 |
116 | In your `setup()` function add the Wifi, MQTT and OTA bootstrapper
117 | ```
118 | bootstrapManager.bootstrapSetup(manageDisconnections, manageHardwareButton, callback);
119 | ```
120 |
121 | In your `loop()` function add the bootstrap manager function
122 | ```
123 | bootstrapManager.bootstrapLoop(manageDisconnections, manageQueueSubscription, manageHardwareButton);
124 | ```
125 |
126 | Please follow the `Bootstrap a project from scratch` instructions without the initial git clone part.
127 |
128 | #### Enable symlinks in GIT for Windows
129 | This project uses symlinks, Windows does not enable symlinks by default, to enable it, run this cmd from an admin console:
130 | ```bash
131 | export MSYS=winsymlinks:nativestrict
132 | ```
133 |
134 | ## Continuous Integrations
135 | This project supports CI via GitHub Actions.
136 | In the `.github/workflows` folder there are two workflows
137 | - one for automatic release that is triggered when a git tag is pushed
138 | - one for building the project and creating the artifact (binary firmware)
139 |
140 | If you use this syntax:
141 | ```
142 | git tag -a v1.0.0 -m "your commit msg";
143 | git push origin --tags;
144 | ```
145 | text in the commit message will be the description of your release.
146 |
147 | ## Access Point frontend
148 | Arduino Bootstrapper will search for a `secrets.ini`, if you don't configure it, access point is started.
149 | You can connect to the AP with your mobile and go to http://192.168.4.1 to access the gui
150 | that will let you enter all the passwords without the needs of hardcoding them.
151 |
152 |
153 |
154 |
155 |
156 | ## License
157 | This program is licensed under MIT License
158 |
--------------------------------------------------------------------------------
/data/img/arduinobootstrapper.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sblantipodi/arduino_bootstrapper/d1e16f3735be8de256d9829a7ad5bee4f7b76c13/data/img/arduinobootstrapper.png
--------------------------------------------------------------------------------
/data/img/jetbrains.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sblantipodi/arduino_bootstrapper/d1e16f3735be8de256d9829a7ad5bee4f7b76c13/data/img/jetbrains.png
--------------------------------------------------------------------------------
/examples/ChangeName.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | ChangeName.cpp - Main Arduino File - Utility classes for bootstrapping arduino projects with Wifi management,
3 | OTA upload management, memory management, MQTT and queue management.
4 |
5 | Copyright © 2020 - 2024 Davide Perini
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy of
8 | this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in
15 | all copies or substantial portions of the Software.
16 |
17 | You should have received a copy of the MIT License along with this program.
18 | If not, see .
19 | */
20 |
21 | #include "ChangeName.h"
22 |
23 | void setup() {
24 |
25 | Serial.begin(SERIAL_RATE);
26 | // Optional - setup MQTT last will & testament
27 | bootstrapManager.setMQTTWill("path/for/last/will/message","last will payload",lastWillQOS,lastWillRetain,cleanSession);
28 | bootstrapManager.bootstrapSetup(manageDisconnections, manageHardwareButton, callback);
29 |
30 | // ENTER YOUR CODE HERE
31 |
32 | }
33 |
34 | /********************************** MANAGE WIFI AND MQTT DISCONNECTION *****************************************/
35 | void manageDisconnections() {
36 |
37 | }
38 |
39 | /********************************** MQTT SUBSCRIPTIONS *****************************************/
40 | void manageQueueSubscription() {
41 |
42 | // example to topic subscription
43 | bootstrapManager.subscribe(CHANGE_ME_TOPIC);
44 | bootstrapManager.subscribe(CHANGE_ME_JSON_TOPIC);
45 |
46 | }
47 |
48 | /********************************** MANAGE HARDWARE BUTTON *****************************************/
49 | void manageHardwareButton() {
50 |
51 | }
52 |
53 | /********************************** START CALLBACK *****************************************/
54 | void callback(char* topic, byte* payload, unsigned int length) {
55 |
56 | // Transform all messages in a JSON format
57 | JsonDocument json = bootstrapManager.parseQueueMsg(topic, payload, length);
58 |
59 | if(strcmp(topic, CHANGE_ME_TOPIC) == 0) {
60 | String simpleMsg = json[VALUE];
61 | // Serial.println(simpleMsg);
62 | } else if(strcmp(topic, CHANGE_ME_JSON_TOPIC) == 0) {
63 | String simpleMsg = bootstrapManager.jsonDoc[F("ROOT_EXAMPLE")];
64 | // Serial.println(simpleMsg);
65 | }
66 |
67 | }
68 |
69 | /********************************** START MAIN LOOP *****************************************/
70 | void loop() {
71 |
72 | if (isConfigFileOk) {
73 |
74 | // Bootsrap loop() with Wifi, MQTT and OTA functions
75 | bootstrapManager.bootstrapLoop(manageDisconnections, manageQueueSubscription, manageHardwareButton);
76 |
77 | // ENTER YOUR CODE HERE
78 |
79 | Serial.print("Hello World");
80 | delay(1000);
81 |
82 | // Send MSG to the MQTT queue with no retention
83 | bootstrapManager.publish(CHANGE_ME_TOPIC, "SEND SIMPLE MSG TO THE QUEUE", false);
84 |
85 | // Send JSON MSG to the MQTT queue with no retention
86 | JsonObject root = bootstrapManager.getJsonObject();
87 | root["ROOT_EXAMPLE"] = "SEND JSON MSG TO THE QUEUE";
88 | bootstrapManager.publish(CHANGE_ME_JSON_TOPIC, root, false);
89 |
90 | }
91 |
92 | }
--------------------------------------------------------------------------------
/examples/ChangeName.h:
--------------------------------------------------------------------------------
1 | /*
2 | ChangeName.h - Main header
3 |
4 | Copyright © 2020 - 2024 Davide Perini
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | You should have received a copy of the MIT License along with this program.
17 | If not, see .
18 | */
19 |
20 | #include
21 | #include
22 | #include "BootstrapManager.h"
23 |
24 |
25 | /****************** BOOTSTRAP MANAGER ******************/
26 | BootstrapManager bootstrapManager;
27 |
28 |
29 | /**************************** PIN DEFINITIONS **************************************************/
30 | // Define your pin here
31 |
32 |
33 | /************* MQTT TOPICS **************************/
34 | const char* CHANGE_ME_TOPIC = "tele/changeme/CHANGEME";
35 | const char* CHANGE_ME_JSON_TOPIC = "tele/changeme/CHANGEME_JSON";
36 | static int lastWillQOS = 1;
37 | boolean lastWillRetain = false;
38 | boolean cleanSession = false;
39 |
40 |
41 | /********************************** FUNCTION DECLARATION (NEEDED BY PLATFORMIO WHILE COMPILING CPP FILES) *****************************************/
42 | void callback(char* topic, byte* payload, unsigned int length);
43 | void manageDisconnections();
44 | void manageQueueSubscription();
45 | void manageHardwareButton();
46 |
--------------------------------------------------------------------------------
/library.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ArduinoBootstrapper",
3 | "keywords": "bootstrapper, mqtt, wifi, iot",
4 | "description": "Utility classes for bootstrapping arduino projects with Wifi management, OTA upload management, memory management, MQTT and queue management. (ESP8266/ESP32 ready)",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/sblantipodi/arduino_bootstrapper.git"
8 | },
9 | "version": "1.18.3",
10 | "examples": "examples/*.cpp",
11 | "exclude": "tests",
12 | "frameworks": "arduino",
13 | "platforms": [
14 | "espressif8266",
15 | "espressif32"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/library.properties:
--------------------------------------------------------------------------------
1 | name=Bootstrapper
2 | version=1.18.3
3 | author=Davide Perini
4 | maintainer=Davide Perini
5 | sentence=A client library for MQTT messaging.
6 | paragraph=Utility classes for bootstrapping arduino projects with Wifi management, OTA upload management, memory management, MQTT and queue management. (ESP8266/ESP32 ready)
7 | category=Other
8 | url=https://github.com/sblantipodi/arduino_bootstrapper
9 | architectures=*
10 | depends=ArduinoJson, PubSubClient, Adafruit SSD1306
--------------------------------------------------------------------------------
/platformio.ini:
--------------------------------------------------------------------------------
1 | ; PlatformIO Project Configuration File
2 | ;
3 | ; Build options: build flags, source filter
4 | ; Upload options: custom upload port, speed and extra flags
5 | ; Library options: dependencies, extra library storages
6 | ; Advanced options: extra scripting
7 | ;
8 | ; Please visit documentation for the other options and examples
9 | ; https://docs.platformio.org/page/projectconf.html
10 |
11 | [platformio]
12 | extra_configs = secrets.ini
13 |
14 | [env:bootstrapper]
15 | platform = espressif8266@4.2.1 ; for ESP32 use: espressif32
16 | ;platform_packages = platformio/framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git#0e5d358c3c15cff4b12fd89d9e605ff9fa0709a6
17 | board = d1_mini ; for ESP32 Lolin D32 board use: lolin_d32
18 | framework = arduino
19 |
20 | build_flags =
21 | '-D AUTHOR="DPsoftware"'
22 | '-D SERIAL_RATE=115200'
23 | '-D DEBUG_QUEUE_MSG=true'
24 | '-D DISPLAY_ENABLED=false'
25 | '-D WIFI_DEVICE_NAME="ArduinoBootstrapper"'
26 | '-D MICROCONTROLLER_OTA_PORT=8199'
27 | '-D WIFI_SIGNAL_STRENGTH=0'
28 | '-D GATEWAY_IP="192.168.1.1"'
29 | '-D SUBNET_IP="192.168.1.1"'
30 | '-D MICROCONTROLLER_IP="192.168.1.99"'
31 | '-D MQTT_SERVER_IP="192.168.1.3"'
32 | '-D MQTT_SERVER_PORT="1883"'
33 | '-D MAX_RECONNECT=500'
34 | '-D MAX_JSON_OBJECT_SIZE=50'
35 | '-D MQTT_MAX_PACKET_SIZE=1024'
36 | '-D WIFI_SSID="${secrets.wifi_ssid}"'
37 | '-D WIFI_PWD="${secrets.wifi_password}"'
38 | '-D MQTT_USER="${secrets.mqtt_username}"'
39 | '-D MQTT_PWD="${secrets.mqtt_password}"'
40 | '-D OTA_PWD="${secrets.ota_password}"'
41 | '-D ADDITIONAL_PARAM_TEXT="ADDITIONAL PARAM TEXT"'
42 |
43 | monitor_port = COM4
44 | monitor_speed = 115200
45 | monitor_filters = esp8266_exception_decoder ; for ESP32 use: esp32_exception_decoder
46 | board_build.f_cpu = 80000000L ; for ESP32 use: 240000000L, ESP8266 can run @160000000L if you need it
47 | ; upload_protocol = espota
48 | ; upload_port = 192.168.1.99
49 | ; upload_flags =
50 | ; --port=8268
51 | ; --auth=${secrets.ota_password} ; configure secrets.ini for OTA Upload, first upload using COM port is needed
52 | upload_port = COM4
53 | lib_deps =
54 | bblanchon/ArduinoJson
55 | knolleary/PubSubClient
56 |
--------------------------------------------------------------------------------
/secrets.ini:
--------------------------------------------------------------------------------
1 | [secrets]
2 | wifi_ssid=XXX
3 | wifi_password=XXX
4 | mqtt_username=XXX
5 | mqtt_password=XXX
6 | ota_password=XXX
--------------------------------------------------------------------------------
/src/BootstrapManager.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | BoostrapManager.cpp - Main file for bootstrapping arduino projects
3 |
4 | Copyright © 2020 - 2025 Davide Perini
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | You should have received a copy of the MIT License along with this program.
17 | If not, see .
18 | */
19 |
20 | #include "BootstrapManager.h"
21 |
22 | void BootstrapManager::littleFsInit() {
23 | #if defined(ESP8266)
24 | if (!LittleFS.begin()) {
25 | #elif defined(ARDUINO_ARCH_ESP32)
26 | if (!LittleFS.begin(true)) {
27 | #endif
28 | Serial.println("LittleFS mount failed");
29 | return;
30 | }
31 | }
32 |
33 | /********************************** BOOTSTRAP FUNCTIONS FOR SETUP() *****************************************/
34 | void BootstrapManager::bootstrapSetup(void (*manageDisconnections)(), void (*manageHardwareButton)(),
35 | void (*callback)(char *, byte *, unsigned int)) {
36 | littleFsInit();
37 | if (isWifiConfigured() && !forceWebServer) {
38 | isConfigFileOk = true;
39 | // Initialize Wifi manager
40 | wifiManager.setupWiFi(manageDisconnections, manageHardwareButton);
41 | // Initialize Queue Manager
42 | if (mqttIP.length() > 0) {
43 | QueueManager::setupMQTTQueue(callback);
44 | } else {
45 | Serial.println(F("Skip MQTT connection."));
46 | }
47 | // Initialize OTA manager
48 | WifiManager::setupOTAUpload();
49 | } else {
50 | isConfigFileOk = false;
51 | launchWebServerForOTAConfig();
52 | }
53 | #if defined(ARDUINO_ARCH_ESP32)
54 | esp_task_wdt_init(3000, true); //enable panic so ESP32 restarts
55 | esp_task_wdt_add(NULL); //add current thread to WDT watch
56 | #endif
57 | #if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
58 | Serial.setTxTimeoutMs(0);
59 | #endif
60 | }
61 |
62 | #if defined(ARDUINO_ARCH_ESP32)
63 | void eth_event(WiFiEvent_t event) {
64 | switch (event) {
65 | case ARDUINO_EVENT_ETH_START:
66 | ethConnected = true;
67 | break;
68 | case ARDUINO_EVENT_ETH_CONNECTED:
69 | MAC = WiFi.macAddress();
70 | ethConnected = true;
71 | break;
72 | case ARDUINO_EVENT_ETH_GOT_IP:
73 | MAC = WiFi.macAddress();
74 | microcontrollerIP = ETH.localIP().toString();
75 | ethConnected = true;
76 | break;
77 | case ARDUINO_EVENT_ETH_DISCONNECTED:
78 | ethConnected = false;
79 | break;
80 | case ARDUINO_EVENT_ETH_STOP:
81 | ethConnected = false;
82 | break;
83 | default:
84 | break;
85 | }
86 | }
87 | #endif
88 |
89 | /********************************** BOOTSTRAP FUNCTIONS FOR SETUP() *****************************************/
90 | void BootstrapManager::bootstrapSetup(void (*manageDisconnections)(), void (*manageHardwareButton)(),
91 | void (*callback)(char *, byte *, unsigned int), bool waitImprov,
92 | void (*listener)()) {
93 | littleFsInit();
94 | if (isWifiConfigured() && !forceWebServer && (ethd == 0 || ethd == -1)) {
95 | isConfigFileOk = true;
96 | // Initialize Wifi manager
97 | wifiManager.setupWiFi(manageDisconnections, manageHardwareButton);
98 | initMqttOta(callback);
99 | } else if ((ethd == 0 || ethd == -1)){
100 | isConfigFileOk = false;
101 | launchWebServerCustom(waitImprov, listener);
102 | } else {
103 | #if defined(ARDUINO_ARCH_ESP32)
104 | isConfigFileOk = true;
105 | ETH.setHostname(Helpers::string2char(deviceName));
106 | WiFi.onEvent(eth_event);
107 | EthManager::connectToEthernet(ethd);
108 | Serial.println(F("Ethernet connected."));
109 | initMqttOta(callback);
110 | #endif
111 | }
112 | #if defined(ARDUINO_ARCH_ESP32)
113 | esp_task_wdt_init(3000, true); //enable panic so ESP32 restarts
114 | esp_task_wdt_add(NULL); //add current thread to WDT watch
115 | #endif
116 | #if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
117 | Serial.setTxTimeoutMs(0);
118 | #endif
119 | }
120 |
121 | void BootstrapManager::initMqttOta(void (*callback)(char *, byte *, unsigned int)) {
122 | // Initialize Queue Manager
123 | if (mqttIP.length() > 0) {
124 | QueueManager::setupMQTTQueue(callback);
125 | } else {
126 | Serial.println(F("Skip MQTT connection."));
127 | }
128 | // Initialize OTA manager
129 | WifiManager::setupOTAUpload();
130 | }
131 |
132 | /********************************** BOOTSTRAP FUNCTIONS FOR LOOP() *****************************************/
133 | bool rcpResponseSent = false;
134 | void BootstrapManager::bootstrapLoop(void (*manageDisconnections)(), void (*manageQueueSubscription)(), void (*manageHardwareButton)()) {
135 | #if defined(ARDUINO_ARCH_ESP32)
136 | if (millis() - lastMillisForWdt >= 3000) {
137 | lastMillisForWdt = millis();
138 | esp_task_wdt_reset();
139 | }
140 | #endif
141 | #if (IMPROV_ENABLED > 0)
142 | if ((ethd == 0 || ethd == -1)) {
143 | if (!rcpResponseSent && WifiManager::isConnected()) {
144 | rcpResponseSent = true;
145 | wifiManager.sendImprovRPCResponse(0x01, true);
146 | }
147 | if (!temporaryDisableImprove) {
148 | wifiManager.handleImprovPacket();
149 | }
150 | }
151 | #endif
152 | if (ethd == 0 || ethd == -1) {
153 | wifiManager.reconnectToWiFi(manageDisconnections, manageHardwareButton);
154 | }
155 | ArduinoOTA.handle();
156 | if (mqttIP.length() > 0) {
157 | queueManager.queueLoop(manageDisconnections, manageQueueSubscription, manageHardwareButton);
158 | }
159 | }
160 |
161 | /********************************** SET LAST WILL PARAMETERS IN THE Q MANAGER **********************************/
162 | void BootstrapManager::setMQTTWill(const char *topic, const char *payload, const int qos, boolean retain,
163 | boolean cleanSession) {
164 | QueueManager::setMQTTWill(topic, payload, qos, retain, cleanSession);
165 | }
166 |
167 | /********************************** SEND A SIMPLE MESSAGE ON THE QUEUE **********************************/
168 | void BootstrapManager::publish(const char *topic, const char *payload, boolean retained) {
169 | if (DEBUG_QUEUE_MSG) {
170 | Serial.print(F("QUEUE MSG SENT ["));
171 | Serial.print(topic);
172 | Serial.println(F("] "));
173 | Serial.println(payload);
174 | }
175 | QueueManager::publish(topic, payload, retained);
176 | }
177 |
178 | /********************************** SEND A JSON MESSAGE ON THE QUEUE **********************************/
179 | void BootstrapManager::publish(const char *topic, JsonObject objectToSend, boolean retained) {
180 | char buffer[measureJson(objectToSend) + 1];
181 | serializeJson(objectToSend, buffer, sizeof(buffer));
182 | QueueManager::publish(topic, buffer, retained);
183 | if (DEBUG_QUEUE_MSG) {
184 | Serial.print(F("QUEUE MSG SENT ["));
185 | Serial.print(topic);
186 | Serial.println(F("] "));
187 | serializeJsonPretty(objectToSend, Serial);
188 | Serial.println();
189 | }
190 | }
191 |
192 | /********************************** SUBSCRIBE TO A QUEUE TOPIC **********************************/
193 | void BootstrapManager::unsubscribe(const char *topic) {
194 | QueueManager::unsubscribe(topic);
195 | if (DEBUG_QUEUE_MSG) {
196 | Serial.print(F("TOPIC SUBSCRIBED ["));
197 | Serial.print(topic);
198 | Serial.println(F("] "));
199 | }
200 | }
201 |
202 | /********************************** SUBSCRIBE TO A QUEUE TOPIC **********************************/
203 | void BootstrapManager::subscribe(const char *topic) {
204 | QueueManager::subscribe(topic);
205 | if (DEBUG_QUEUE_MSG) {
206 | Serial.print(F("TOPIC SUBSCRIBED ["));
207 | Serial.print(topic);
208 | Serial.println(F("] "));
209 | }
210 | }
211 |
212 | /********************************** SUBSCRIBE TO A QUEUE TOPIC **********************************/
213 | void BootstrapManager::subscribe(const char *topic, uint8_t qos) {
214 | QueueManager::subscribe(topic, qos);
215 | if (DEBUG_QUEUE_MSG) {
216 | Serial.print(F("TOPIC SUBSCRIBED ["));
217 | Serial.print(topic);
218 | Serial.println(F("] "));
219 | }
220 | }
221 |
222 | /********************************** PRINT THE MESSAGE ARRIVING FROM THE QUEUE **********************************/
223 | JsonDocument BootstrapManager::parseQueueMsg(char *topic, byte *payload, unsigned int length) {
224 | if (DEBUG_QUEUE_MSG) {
225 | Serial.print(F("QUEUE MSG ARRIVED ["));
226 | Serial.print(topic);
227 | Serial.println(F("] "));
228 | }
229 | char message[length + 1];
230 | for (unsigned int i = 0; i < length; i++) {
231 | message[i] = (char) payload[i];
232 | }
233 | message[length] = '\0';
234 | DeserializationError error = deserializeJson(jsonDoc, (const byte *) payload, length);
235 | // non json msg
236 | if (error) {
237 | JsonObject root = jsonDoc.to();
238 | root[VALUE] = message;
239 | if (DEBUG_QUEUE_MSG) {
240 | String msg = root[VALUE];
241 | Serial.println(msg);
242 | }
243 | return jsonDoc;
244 | } else { // return json doc
245 | if (DEBUG_QUEUE_MSG) {
246 | serializeJsonPretty(jsonDoc, Serial);
247 | Serial.println();
248 | }
249 | return jsonDoc;
250 | }
251 | }
252 |
253 | /********************************** PRINT THE MESSAGE ARRIVING FROM HTTP **********************************/
254 | JsonDocument BootstrapManager::parseHttpMsg(String payload, unsigned int length) {
255 | char message[length + 1];
256 | for (unsigned int i = 0; i < length; i++) {
257 | message[i] = (char) payload[i];
258 | }
259 | message[length] = '\0';
260 | DeserializationError error = deserializeJson(jsonDoc, payload.c_str(), length);
261 | // non json msg
262 | if (error) {
263 | JsonObject root = jsonDoc.to();
264 | root[VALUE] = message;
265 | if (DEBUG_QUEUE_MSG) {
266 | String msg = root[VALUE];
267 | Serial.println(msg);
268 | }
269 | return jsonDoc;
270 | } else { // return json doc
271 | if (DEBUG_QUEUE_MSG) {
272 | serializeJsonPretty(jsonDoc, Serial);
273 | Serial.println();
274 | }
275 | return jsonDoc;
276 | }
277 | }
278 |
279 | // return a new json object instance
280 | JsonObject BootstrapManager::getJsonObject() {
281 | return jsonDoc.to();
282 | }
283 |
284 | // Blink LED_BUILTIN without bloking delay
285 | [[maybe_unused]] void BootstrapManager::nonBlokingBlink() {
286 | unsigned long currentMillis = millis();
287 | if (currentMillis - previousMillis >= interval && ledTriggered) {
288 | // save the last time you blinked the LED
289 | previousMillis = currentMillis;
290 | // blink led
291 | #if defined(LED_BUILTIN)
292 | digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
293 | #endif
294 | blinkCounter++;
295 | if (blinkCounter >= blinkTimes) {
296 | blinkCounter = 0;
297 | ledTriggered = false;
298 | #if defined(LED_BUILTIN)
299 | digitalWrite(LED_BUILTIN, HIGH);
300 | #endif
301 | }
302 | }
303 | }
304 |
305 | // Print or display microcontroller infos
306 | void BootstrapManager::getMicrocontrollerInfo() {
307 | Helpers::smartPrint(F("Wifi: "));
308 | Helpers::smartPrint(WifiManager::getQuality());
309 | Helpers::smartPrintln(F("%"));
310 | #if defined(ESP8266)
311 | Helpers::smartPrint(F("Heap: ")); Helpers::smartPrint(EspClass::getFreeHeap()/1024); Helpers::smartPrintln(F(" KB"));
312 | Helpers::smartPrint(F("Free Flash: ")); Helpers::smartPrint(EspClass::getFreeSketchSpace()/1024); Helpers::smartPrintln(F(" KB"));
313 | Helpers::smartPrint(F("Frequency: ")); Helpers::smartPrint(EspClass::getCpuFreqMHz()); Helpers::smartPrintln(F("MHz"));
314 | Helpers::smartPrint(F("Flash: ")); Helpers::smartPrint(EspClass::getFlashChipSize()/1024); Helpers::smartPrintln(F(" KB"));
315 | Helpers::smartPrint(F("Sketch: ")); Helpers::smartPrint(EspClass::getSketchSize()/1024); Helpers::smartPrintln(F(" KB"));
316 | Helpers::smartPrint(F("SDK: ")); Helpers::smartPrintln(EspClass::getSdkVersion());
317 | #elif defined(ARDUINO_ARCH_ESP32)
318 | Helpers::smartPrint(F("Heap: "));
319 | Helpers::smartPrint(ESP.getFreeHeap() / 1024);
320 | Helpers::smartPrintln(F(" KB"));
321 | Helpers::smartPrint(F("Free Flash: "));
322 | Helpers::smartPrint(ESP.getFreeSketchSpace() / 1024);
323 | Helpers::smartPrintln(F(" KB"));
324 | Helpers::smartPrint(F("Frequency: "));
325 | Helpers::smartPrint(ESP.getCpuFreqMHz());
326 | Helpers::smartPrintln(F("MHz"));
327 | Helpers::smartPrint(F("Flash: "));
328 | Helpers::smartPrint(ESP.getFlashChipSize() / 1024);
329 | Helpers::smartPrintln(F(" KB"));
330 | Helpers::smartPrint(F("Sketch: "));
331 | Helpers::smartPrint(ESP.getSketchSize() / 1024);
332 | Helpers::smartPrintln(F(" KB"));
333 | Helpers::smartPrint(F("SDK: "));
334 | Helpers::smartPrintln(ESP.getSdkVersion());
335 | #endif
336 | Helpers::smartPrintln(F("MAC: "));
337 | if ((ethd == 0 || ethd == -1)) {
338 | Helpers::smartPrintln(WiFi.macAddress());
339 | } else {
340 | #if defined(ARDUINO_ARCH_ESP32)
341 | Helpers::smartPrintln(ETH.macAddress());
342 | #endif
343 | }
344 | Helpers::smartPrint(F("IP: "));
345 | Helpers::smartPrintln(microcontrollerIP);
346 | // helper.smartPrint(F("Arduino Core: ")); helper.smartPrintln(ESP.getCoreVersion());
347 | Helpers::smartPrintln(F("Last Boot: "));
348 | Helpers::smartPrintln(lastBoot);
349 | Helpers::smartPrintln(F("Last WiFi connection:"));
350 | Helpers::smartPrintln(lastWIFiConnection);
351 | Helpers::smartPrintln(F("Last MQTT connection:"));
352 | Helpers::smartPrintln(lastMQTTConnection);
353 | }
354 |
355 | // Draw screensaver useful for OLED displays
356 | [[maybe_unused]] void BootstrapManager::drawScreenSaver(const String &txt) {
357 | #if (DISPLAY_ENABLED)
358 | if (screenSaverTriggered) {
359 | display.clearDisplay();
360 | for (int i = 0; i < 50; i++) {
361 | display.clearDisplay();
362 | display.setTextSize(2);
363 | display.setCursor(5,17);
364 | display.fillRect(0, 0, display.width(), display.height(), i%2 != 0 ? WHITE : BLACK);
365 | display.setTextColor(i%2 == 0 ? WHITE : BLACK);
366 | display.drawRoundRect(0, 0, display.width()-1, display.height()-1, display.height()/4, i%2 == 0 ? WHITE : BLACK);
367 | display.println(txt);
368 | display.display();
369 | }
370 | display.setTextColor(WHITE);
371 | screenSaverTriggered = false;
372 | }
373 | #endif
374 | }
375 |
376 | // draw some infos about your controller
377 | [[maybe_unused]] void BootstrapManager::drawInfoPage(const String &softwareVersion, const String &author) {
378 | #if (DISPLAY_ENABLED)
379 | yoffset -= 1;
380 | // add/remove 8 pixel for every line yoffset <= -209, if you want to add a line yoffset <= -217
381 | if (yoffset <= -209) {
382 | yoffset = SCREEN_HEIGHT + 6;
383 | lastPageScrollTriggered = true;
384 | }
385 | int effectiveOffset = (yoffset >= 0 && !lastPageScrollTriggered) ? 0 : yoffset;
386 |
387 | if (haVersion[0] != '\0') {
388 | display.drawBitmap((display.width()-HABIGLOGOW)-1, effectiveOffset+5, HABIGLOGO, HABIGLOGOW, HABIGLOGOH, 1);
389 | }
390 | display.setCursor(0, effectiveOffset);
391 | display.setTextSize(1);
392 | display.print(deviceName); display.print(F(" "));
393 | display.println(softwareVersion);
394 | display.println("by " + author);
395 | display.println(F(""));
396 |
397 | if (haVersion[0] != '\0') {
398 | display.print(F("HA: ")); display.print(F("(")); display.print(haVersion); display.println(F(")"));
399 | }
400 |
401 | getMicrocontrollerInfo();
402 |
403 | // add/remove 8 pixel for every line effectiveOffset+175, if you want to add a line effectiveOffset+183
404 | display.drawBitmap((((display.width()/2)-(ARDUINOLOGOW/2))), effectiveOffset+175, ARDUINOLOGO, ARDUINOLOGOW, ARDUINOLOGOH, 1);
405 | #endif
406 | }
407 |
408 | // send the state of your controller to the mqtt queue
409 | [[maybe_unused]] void BootstrapManager::sendState(const char *topic, JsonObject objectToSend, const String &version) {
410 | objectToSend["Whoami"] = deviceName;
411 | objectToSend["IP"] = microcontrollerIP;
412 | objectToSend["MAC"] = MAC;
413 | objectToSend["ver"] = version;
414 | objectToSend["time"] = timedate;
415 | objectToSend["wifi"] = WifiManager::getQuality();
416 |
417 | // publish state only if it has received time from HA
418 | if (timedate != OFF_CMD) {
419 | // This topic should be retained, we don't want unknown values on battery voltage or wifi signal
420 | publish(topic, objectToSend, true);
421 | }
422 | }
423 |
424 | // write json file to storage
425 | void BootstrapManager::writeToLittleFS(const JsonDocument &jDoc, const String &filenameToUse) {
426 | File jsonFile = LittleFS.open("/" + filenameToUse, FILE_WRITE);
427 | if (!jsonFile) {
428 | Helpers::smartPrintln("Failed to open [" + filenameToUse + "] file for writing");
429 | } else {
430 | serializeJsonPretty(jDoc, Serial);
431 | serializeJson(jDoc, jsonFile);
432 | jsonFile.close();
433 | Helpers::smartPrintln("[" + filenameToUse + "] written correctly");
434 | }
435 | }
436 |
437 | // read json file from storage
438 | JsonDocument BootstrapManager::readLittleFS(const String &filenameToUse) {
439 | // Helpers classes
440 | Helpers helper;
441 | #if (DISPLAY_ENABLED)
442 | display.clearDisplay();
443 | display.setCursor(0, 0);
444 | display.setTextSize(1);
445 | #endif
446 | Helpers::smartPrintln(F("Mounting LittleFS..."));
447 | helper.smartDisplay();
448 | File jsonFile = LittleFS.open("/" + filenameToUse, FILE_READ);
449 | if (!jsonFile) {
450 | Helpers::smartPrintln("Failed to open [" + filenameToUse + "] file");
451 | helper.smartDisplay();
452 | }
453 | size_t size = jsonFile.size();
454 | // Allocate a buffer to store contents of the file.
455 | std::unique_ptr buf(new char[size]);
456 | // We don't use String here because ArduinoJson library requires the input
457 | // buffer to be mutable. If you don't use ArduinoJson, you may as well
458 | // use configFile.readString instead.
459 | jsonFile.readBytes(buf.get(), size);
460 | JsonDocument jsonDoc;
461 | auto error = deserializeJson(jsonDoc, buf.get());
462 | if (filenameToUse != "setup.json") serializeJsonPretty(jsonDoc, Serial);
463 | jsonFile.close();
464 | if (error) {
465 | Helpers::smartPrintln("Failed to parse [" + filenameToUse + "] file");
466 | helper.smartDisplay(DELAY_2000);
467 | } else {
468 | Helpers::smartPrintln("[" + filenameToUse + "]\nJSON parsed");
469 | helper.smartDisplay(DELAY_2000);
470 | return jsonDoc;
471 | }
472 | helper.smartDisplay(DELAY_2000);
473 | return jsonDoc;
474 | }
475 |
476 | String BootstrapManager::readValueFromFile(const String &filenameToUse, const String ¶mName) {
477 | String returnStr = "";
478 | if (!LittleFS.begin()) {
479 | Serial.println("LittleFS mount failed");
480 | }
481 | File jsonFile = LittleFS.open("/" + filenameToUse, FILE_READ);
482 | if (!jsonFile) {
483 | Helpers::smartPrintln("Failed to open [" + filenameToUse + "] file");
484 | helper.smartDisplay();
485 | }
486 | size_t size = jsonFile.size();
487 | std::unique_ptr buf(new char[size]);
488 | jsonFile.readBytes(buf.get(), size);
489 | JsonDocument jDoc;
490 | auto error = deserializeJson(jDoc, buf.get());
491 | serializeJsonPretty(jDoc, Serial);
492 | JsonVariant answer = jDoc[paramName];
493 | if (answer.is()) {
494 | returnStr = answer.as();
495 | } else {
496 | auto returnVal = answer.as();
497 | returnStr = String(returnVal);
498 | }
499 | jsonFile.close();
500 | if (error) {
501 | returnStr = "";
502 | }
503 | return returnStr;
504 | }
505 |
506 | // check if wifi is correctly configured
507 | bool BootstrapManager::isWifiConfigured() {
508 | if (WifiManager::isWifiConfigured()) {
509 | deviceName = DEVICE_NAME;
510 | microcontrollerIP = IP_MICROCONTROLLER;
511 | qsid = SSID;
512 | qpass = PASSWORD;
513 | OTApass = OTAPASSWORD;
514 | mqttIP = MQTT_SERVER;
515 | mqttPort = MQTT_PORT;
516 | mqttuser = MQTT_USERNAME;
517 | mqttpass = MQTT_PASSWORD;
518 | additionalParam = PARAM_ADDITIONAL;
519 | return true;
520 | } else {
521 | JsonDocument mydoc = readLittleFS(F("setup.json"));
522 | if (mydoc[F("qsid")].is()) {
523 | Serial.println(F("Storage OK, restoring WiFi and MQTT config."));
524 | microcontrollerIP = Helpers::getValue(mydoc["microcontrollerIP"]);
525 | qsid = Helpers::getValue(mydoc[F("qsid")]);
526 | qpass = Helpers::getValue(mydoc[F("qpass")]);
527 | OTApass = Helpers::getValue(mydoc[F("OTApass")]);
528 | if (OTApass.isEmpty()) {
529 | OTApass = OTAPASSWORD;
530 | }
531 | mqttIP = Helpers::getValue(mydoc[F("mqttIP")]);
532 | mqttPort = Helpers::getValue(mydoc[F("mqttPort")]);
533 | mqttuser = Helpers::getValue(mydoc[F("mqttuser")]);
534 | mqttpass = Helpers::getValue(mydoc[F("mqttpass")]);
535 | additionalParam = Helpers::getValue(mydoc[F("additionalParam")]);
536 | deviceName = Helpers::getValue(mydoc[F("deviceName")]);
537 | ethd = mydoc[F("ethd")].as();
538 | #if defined(ESP8266)
539 | ethd = -1;
540 | #endif
541 | return true;
542 | } else {
543 | Serial.println(F("No setup file"));
544 | }
545 | }
546 | return false;
547 | }
548 |
549 | // if no ssid available, launch web server to get config params via browser
550 | void BootstrapManager::launchWebServerForOTAConfig() {
551 | #if (IMPROV_ENABLED > 0)
552 | manageImprov();
553 | #endif
554 | return WifiManager::launchWebServerForOTAConfig();
555 | }
556 |
557 | void BootstrapManager::manageImprov() {
558 | unsigned long timeNowStatus = 0;
559 | bool switchToWebServer = false;
560 | // If WiFi is not configured, handle improv packet for 15 seconds, then switch to settinigs managed by web server
561 | WiFi.disconnect();
562 | while (((WiFi.localIP()[0] == 0 && WiFi.status() != WL_CONNECTED) && !switchToWebServer) || improvePacketReceived) {
563 | if (millis() > timeNowStatus + IMPROV_ENABLED) {
564 | timeNowStatus = millis();
565 | switchToWebServer = true;
566 | }
567 | wifiManager.manageImprovWifi();
568 | }
569 | }
570 |
571 | void BootstrapManager::launchWebServerCustom(bool waitImprov, void (*listener)()) {
572 | #if (IMPROV_ENABLED > 0)
573 | if (waitImprov) {
574 | manageImprov();
575 | }
576 | #endif
577 | return WifiManager::launchWebServerCustom(listener);
578 | }
579 |
580 | // get the wifi quality
581 | int BootstrapManager::getWifiQuality() {
582 | return WifiManager::getQuality();
583 | }
--------------------------------------------------------------------------------
/src/BootstrapManager.h:
--------------------------------------------------------------------------------
1 | /*
2 | BoostrapManager.cpp - Main header for bootstrapping arduino projects
3 |
4 | Copyright © 2020 - 2025 Davide Perini
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | You should have received a copy of the MIT License along with this program.
17 | If not, see .
18 | */
19 |
20 | #ifndef _DPSOFTWARE_BOOTSTRAP_MANAGER_H
21 | #define _DPSOFTWARE_BOOTSTRAP_MANAGER_H
22 |
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include "Configuration.h"
30 | #include "Helpers.h"
31 | #include "WifiManager.h"
32 | #include "QueueManager.h"
33 | #if defined(ARDUINO_ARCH_ESP32)
34 | #include "EthManager.h"
35 | #include
36 | #endif
37 |
38 | class BootstrapManager {
39 |
40 | private:
41 | WifiManager wifiManager; // WifiManager classes for Wifi management
42 | QueueManager queueManager; // QueueManager classes for MQTT queue management
43 | Helpers helper;
44 | #if defined(ARDUINO_ARCH_ESP32)
45 | unsigned long lastMillisForWdt = millis();
46 | #endif
47 |
48 | public:
49 | JsonDocument jsonDoc;
50 | JsonDocument jsonDocBigSize;
51 | // using JsonDocument = StaticJsonDocument;
52 | JsonDocument parseQueueMsg(char* topic, byte* payload, unsigned int length); // print the message arriving from the queue
53 | JsonDocument parseHttpMsg(String payload, unsigned int length); // print the message arriving from HTTP
54 | void littleFsInit();
55 | void bootstrapSetup(void (*manageDisconnectionFunction)(), void (*manageHardwareButton)(), void (*callback)(char*, byte*, unsigned int)); // bootstrap setup()
56 | void bootstrapSetup(void (*manageDisconnectionFunction)(), void (*manageHardwareButton)(), void (*callback)(char*, byte*, unsigned int), bool waitImprov, void (*listener)()); // bootstrap setup()
57 | void bootstrapLoop(void (*manageDisconnectionFunction)(), void (*manageQueueSubscription)(), void (*manageHardwareButton)()); // bootstrap loop()
58 | static void setMQTTWill(const char *topic, const char *payload, int qos, boolean retain, boolean cleanSession); // set the last will parameters for mqtt
59 | static void publish(const char *topic, const char *payload, boolean retained); // send a message on the queue
60 | static void publish(const char *topic, JsonObject objectToSend, boolean retained); // send a message on the queue
61 | static void unsubscribe(const char *topic); // unsubscribe to a queue topic
62 | static void subscribe(const char *topic); // subscribe to a queue topic
63 | static void subscribe(const char *topic, uint8_t qos); // subscribe to a queue topic with qos 0 or 1
64 | JsonObject getJsonObject(); // return a new json object instance
65 | [[maybe_unused]] static void nonBlokingBlink(); // blink default LED when sending data to the queue
66 | [[maybe_unused]] static void getMicrocontrollerInfo(); // print or display microcontroller's info
67 | [[maybe_unused]] void drawInfoPage(const String& softwareVersion, const String& author); // draw a page with all the microcontroller's info
68 | [[maybe_unused]] void drawScreenSaver(const String& txt); // useful for OLED displays
69 | [[maybe_unused]] static void sendState(const char *topic, JsonObject objectToSend, const String& version); // send microcontroller's info on the queue
70 | [[maybe_unused]] static void writeToLittleFS(const JsonDocument& jDoc, const String& filenameToUse); // write json file to storage
71 | [[maybe_unused]] static JsonDocument readLittleFS(const String& filenameToUse); // read json file from storage
72 | [[maybe_unused]] String readValueFromFile(const String& filenameToUse, const String& paramName); // read a param from a json file
73 | static bool isWifiConfigured(); // check if wifi is correctly configured
74 | void launchWebServerForOTAConfig(); // if no ssid available, launch web server to get config params via browser
75 | void launchWebServerCustom(bool waitImprov, void (*listener)()); // if no ssid available, launch web server to get config params via browser
76 | static int getWifiQuality(); // get the wifi quality
77 | void manageImprov();
78 | static void initMqttOta(void (*callback)(char *, byte *, unsigned int)) ;
79 | };
80 |
81 | #endif
--------------------------------------------------------------------------------
/src/Configuration.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | Configuration.cpp - Configuration impl
3 |
4 | Copyright © 2020 - 2025 Davide Perini
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | You should have received a copy of the MIT License along with this program.
17 | If not, see .
18 | */
19 |
20 | #if (DISPLAY_ENABLED)
21 | #include "Configuration.h"
22 | // Initialize the display
23 | Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
24 | #endif
--------------------------------------------------------------------------------
/src/Configuration.h:
--------------------------------------------------------------------------------
1 | /*
2 | Configuration.h - Config header
3 |
4 | Copyright © 2020 - 2025 Davide Perini
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | You should have received a copy of the MIT License along with this program.
17 | If not, see .
18 | */
19 |
20 | #ifndef _DPSOFTWARE_CONFIG_H
21 | #define _DPSOFTWARE_CONFIG_H
22 |
23 | #if defined(ESP8266)
24 | #include
25 | #include
26 | #include
27 | #include
28 | #elif defined(ARDUINO_ARCH_ESP32)
29 | #include
30 | #include
31 | #include
32 | #include
33 | #include
34 | #endif
35 |
36 | #ifndef AUTHOR
37 | #define AUTHOR "DPsoftware"
38 | #endif
39 |
40 | // Serial rate for debug
41 | #ifndef SERIAL_RATE
42 | #define SERIAL_RATE 115200
43 | #endif
44 |
45 | #ifndef DEBUG_QUEUE_MSG
46 | #define DEBUG_QUEUE_MSG false
47 | #endif
48 |
49 | // Specify if you want to use a display or only Serial
50 | #ifndef DISPLAY_ENABLED
51 | #define DISPLAY_ENABLED false
52 | #endif
53 |
54 | #if (DISPLAY_ENABLED)
55 | #include
56 | // Display specs
57 | #define OLED_RESET LED_BUILTIN // Pin used for integrated D1 Mini blue LED
58 | #define SCREEN_WIDTH 128 // OLED display width, in pixels
59 | #define SCREEN_HEIGHT 64 // OLED display height, in pixels
60 | extern Adafruit_SSD1306 display;
61 | #endif
62 |
63 | // Values greater then 0 enables Improv for that milliseconds period
64 | #ifndef IMPROV_ENABLED
65 | #define IMPROV_ENABLED 0
66 | #endif
67 |
68 | // SENSORNAME will be used as device network name
69 | #ifndef WIFI_DEVICE_NAME
70 | #define WIFI_DEVICE_NAME "ArduinoBootstrapper"
71 | #endif
72 | const char *const DEVICE_NAME = WIFI_DEVICE_NAME;
73 |
74 | // Port for the OTA firmware uplaod
75 | #ifndef MICROCONTROLLER_OTA_PORT
76 | #define MICROCONTROLLER_OTA_PORT 8199
77 | #endif
78 | const int OTA_PORT = MICROCONTROLLER_OTA_PORT;
79 |
80 | // Set wifi power in WIFI_POWER range 0/0.25, set to 0 to reduce PIR false positive due to wifi power, 0 low, 20.5 max.
81 | #ifndef WIFI_SIGNAL_STRENGTH
82 | #define WIFI_SIGNAL_STRENGTH 0
83 | #endif
84 | const double WIFI_POWER = WIFI_SIGNAL_STRENGTH;
85 |
86 | // GATEWAY IP
87 | #ifndef GATEWAY_IP
88 | #define GATEWAY_IP "192.168.1.1" // enter your gateway IP address
89 | #endif
90 | const char *const IP_GATEWAY = GATEWAY_IP;
91 |
92 | // SUBNET IP
93 | #ifndef SUBNET_IP
94 | #define SUBNET_IP "255.255.255.0"
95 | #endif
96 | const char *const IP_SUBNET = SUBNET_IP;
97 |
98 | // DNS IP
99 | #ifndef DNS_IP
100 | #define DNS_IP "8.8.8.8" // enter a DNS server IP if you need domain name access and static IP is selected
101 | // example: 8.8.8.8 for Google DNS server or use the DNS server provided by your ISP (see your gateway configuration)
102 | #endif
103 | const char *const IP_DNS = DNS_IP;
104 |
105 | // STATIC IP FOR THE MICROCONTROLLER
106 | #ifndef MICROCONTROLLER_IP
107 | #define MICROCONTROLLER_IP "192.168.1.99" // enter a valid IP address for static IP or "DHCP" for dynamic IP
108 | #endif
109 | const char *const IP_MICROCONTROLLER = MICROCONTROLLER_IP;
110 |
111 | // MQTT server IP
112 | #ifndef MQTT_SERVER_IP
113 | #define MQTT_SERVER_IP "192.168.1.3"
114 | #endif
115 | const char *const MQTT_SERVER = MQTT_SERVER_IP;
116 |
117 | // MQTT server port
118 | #ifndef MQTT_SERVER_PORT
119 | #define MQTT_SERVER_PORT "1883"
120 | #endif
121 | const char *const MQTT_PORT = MQTT_SERVER_PORT;
122 |
123 | // Maximum number of reconnection (WiFi/MQTT) attemp before powering off peripherals
124 | #ifndef MAX_RECONNECT
125 | #define MAX_RECONNECT 500
126 | #endif
127 |
128 | // Maximum JSON Object Size
129 | #ifndef MAX_JSON_OBJECT_SIZE
130 | #define MAX_JSON_OBJECT_SIZE 50
131 | #endif
132 |
133 | // Maximum JSON Object Size
134 | #ifndef SMALL_JSON_OBJECT_SIZE
135 | #define SMALL_JSON_OBJECT_SIZE 50
136 | #endif
137 |
138 | // Maximum JSON Object Size
139 | #ifndef MQTT_MAX_PACKET_SIZE
140 | #define MQTT_MAX_PACKET_SIZE 1024
141 | #endif
142 |
143 | // MQTT Keep Alive
144 | #ifndef MQTT_KEEP_ALIVE
145 | #define MQTT_KEEP_ALIVE 60
146 | #endif
147 |
148 | // Additional param that can be used for general purpose use
149 | #ifndef ADDITIONAL_PARAM_TEXT
150 | #define ADDITIONAL_PARAM_TEXT "ADDITIONAL PARAM"
151 | #endif
152 |
153 | // Additional param that can be used for general purpose use
154 | #ifndef ADDITIONAL_PARAM
155 | #define ADDITIONAL_PARAM "none"
156 | #endif
157 | const char *const PARAM_ADDITIONAL = ADDITIONAL_PARAM;
158 |
159 | #endif
--------------------------------------------------------------------------------
/src/EthManager.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | EthManager.cpp - Managing WiFi and OTA
3 |
4 | Copyright © 2020 - 2025 Davide Perini
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | You should have received a copy of the MIT License along with this program.
17 | If not, see .
18 | */
19 | #include "EthManager.h"
20 |
21 |
22 | #if defined(ARDUINO_ARCH_ESP32)
23 | /**
24 | * Supported ethernet devices
25 | */
26 | const ethernet_config ethernetDevices[] = {
27 | // No Ethernet
28 | {
29 | },
30 | // QuinLed-ESP32-Ethernet
31 | {
32 |
33 | 0,
34 | 5,
35 | 23,
36 | 18,
37 | ETH_PHY_LAN8720,
38 | ETH_CLOCK_GPIO17_OUT
39 | },
40 | // QuinLed-Dig-Octa Brainboard-32-8L and LilyGO-T-ETH-POE
41 | {
42 | 0,
43 | -1,
44 | 23,
45 | 18,
46 | ETH_PHY_LAN8720,
47 | ETH_CLOCK_GPIO17_OUT
48 | },
49 | // WT32-EHT01
50 | // These pins works well: IO2, IO4, IO12, IO14, IO15
51 | // These not: IO35, IO36, IO39
52 | {
53 | 1,
54 | 16,
55 | 23,
56 | 18,
57 | ETH_PHY_LAN8720,
58 | ETH_CLOCK_GPIO0_IN
59 | },
60 | // ESP32-ETHERNET-KIT-VE
61 | {
62 | 0,
63 | 5,
64 | 23,
65 | 18,
66 | ETH_PHY_IP101,
67 | ETH_CLOCK_GPIO0_IN
68 | },
69 | // ESP32-POE
70 | {
71 | 0,
72 | 12,
73 | 23,
74 | 18,
75 | ETH_PHY_LAN8720,
76 | ETH_CLOCK_GPIO17_OUT
77 | },
78 | // WESP32
79 | {
80 | 0,
81 | -1,
82 | 16,
83 | 17,
84 | ETH_PHY_LAN8720,
85 | ETH_CLOCK_GPIO0_IN
86 | },
87 | // LilyGO-T-POE-Pro
88 | {
89 | 0,
90 | 5,
91 | 23,
92 | 18,
93 | ETH_PHY_LAN8720,
94 | ETH_CLOCK_GPIO0_OUT
95 | },
96 | // ESP32-POE-WROVER
97 | {
98 | 0,
99 | 12,
100 | 23,
101 | 18,
102 | ETH_PHY_LAN8720,
103 | ETH_CLOCK_GPIO0_OUT
104 | }
105 | };
106 |
107 | /**
108 | * Connect to ethernet
109 | * @param deviceNumber to use
110 | */
111 | void EthManager::connectToEthernet(int8_t deviceNumber) {
112 | ETH.begin(
113 | ethernetDevices[deviceNumber].address,
114 | ethernetDevices[deviceNumber].power,
115 | ethernetDevices[deviceNumber].mdc,
116 | ethernetDevices[deviceNumber].mdio,
117 | ethernetDevices[deviceNumber].type,
118 | ethernetDevices[deviceNumber].clk_mode
119 | );
120 | }
121 |
122 | /**
123 | * Deallocate ethernet pins
124 | * @param deviceNumber to deallocate
125 | */
126 | void EthManager::deallocateEthernetPins(int8_t deviceNumber) {
127 | const uint32_t MATRIX_DETACH_OUT_SIG = 0x100;
128 | gpio_matrix_out(ethernetDevices[deviceNumber].address, MATRIX_DETACH_OUT_SIG, false, false);
129 | gpio_matrix_out(ethernetDevices[deviceNumber].power, MATRIX_DETACH_OUT_SIG, false, false);
130 | gpio_matrix_out(ethernetDevices[deviceNumber].mdc, MATRIX_DETACH_OUT_SIG, false, false);
131 | gpio_matrix_out(ethernetDevices[deviceNumber].mdio, MATRIX_DETACH_OUT_SIG, false, false);
132 | gpio_matrix_out(ethernetDevices[deviceNumber].clk_mode, MATRIX_DETACH_OUT_SIG, false, false);
133 | pinMode(ethernetDevices[deviceNumber].address, INPUT);
134 | pinMode(ethernetDevices[deviceNumber].power, INPUT);
135 | pinMode(ethernetDevices[deviceNumber].mdc, INPUT);
136 | pinMode(ethernetDevices[deviceNumber].mdio, INPUT);
137 | pinMode(ethernetDevices[deviceNumber].clk_mode, INPUT);
138 | }
139 |
140 | #endif
141 |
--------------------------------------------------------------------------------
/src/EthManager.h:
--------------------------------------------------------------------------------
1 | /*
2 | EthManager.h - Managing Wifi and OTA
3 |
4 | Copyright © 2020 - 2025 Davide Perini
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | You should have received a copy of the MIT License along with this program.
17 | If not, see .
18 | */
19 |
20 | #include
21 |
22 | #if defined(ARDUINO_ARCH_ESP32)
23 |
24 | #include
25 |
26 | #endif
27 |
28 | #ifndef _DPSOFTWARE_ETH_MANAGER_H
29 | #define _DPSOFTWARE_ETH_MANAGER_H
30 |
31 | #if defined(ARDUINO_ARCH_ESP32)
32 | typedef struct EthConfig {
33 | uint8_t address;
34 | int power;
35 | int mdc;
36 | int mdio;
37 | eth_phy_type_t type;
38 | eth_clock_mode_t clk_mode;
39 | } ethernet_config;
40 |
41 | extern const ethernet_config ethernetDevices[];
42 |
43 | class EthManager {
44 |
45 | public:
46 | static void connectToEthernet(int8_t deviceNumber);
47 | static void deallocateEthernetPins(int8_t deviceNumber);
48 | };
49 |
50 | #endif
51 | #endif
--------------------------------------------------------------------------------
/src/Helpers.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | Helpers.cpp - Helper classes
3 |
4 | Copyright © 2020 - 2025 Davide Perini
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | You should have received a copy of the MIT License along with this program.
17 | If not, see .
18 | */
19 |
20 | #include "Helpers.h"
21 |
22 | bool isConfigFileOk = false;
23 | String lastMQTTConnection = "OFF";
24 | String lastWIFiConnection = "OFF";
25 | String lastBoot = " ";
26 |
27 | bool screenSaverTriggered = false;
28 |
29 | bool lastPageScrollTriggered = false;
30 | int yoffset = 150;
31 | String haVersion = "";
32 | String firmwareVersion = "";
33 | String IP = "";
34 | String MAC = "";
35 | String deviceName = "XXX";
36 | String microcontrollerIP = "XXX";
37 | bool dhcpInUse = true;
38 | #if defined(ARDUINO_ARCH_ESP32)
39 | int8_t ethd = 0;
40 | #else
41 | int8_t ethd = -1;
42 | #endif
43 | String qsid = "XXX";
44 | String qpass = "XXX";
45 | String OTApass = "XXX";
46 | String mqttIP = "XXX";
47 | String mqttPort = "XXX";
48 | String mqttuser = "XXX";
49 | String mqttpass = "XXX";
50 | String mqttWillTopic = "0";
51 | String mqttWillPayload = "0";
52 | int mqttWillQOS = 1;
53 | bool mqttWillRetain = false;
54 | bool mqttCleanSession = true;
55 | bool blockingMqtt = true;
56 | bool mqttConnected = false;
57 | String additionalParam = "XXX";
58 | bool ethConnected = false;
59 |
60 | unsigned long previousMillis = 0;
61 | const unsigned long interval = 200;
62 | bool ledTriggered = false;
63 | int blinkCounter = 0;
64 | const int blinkTimes = 6;
65 |
66 | String timedate = "OFF";
67 | [[maybe_unused]] String date = "OFF";
68 | [[maybe_unused]] String currentime = "OFF";
69 | String ERROR = "ERROR";
70 |
71 | int wifiReconnectAttemp = 0;
72 | int mqttReconnectAttemp = 0;
73 | bool fastDisconnectionManagement = false;
74 | bool forceWebServer = false;
75 | bool temporaryDisableImprove = false;
76 | bool improvePacketReceived = false;
77 |
78 | void Helpers::smartPrint(String msg) {
79 | #if (DISPLAY_ENABLED)
80 | display.print(msg);
81 | #else
82 | Serial.print(msg);
83 | #endif
84 | }
85 |
86 | void Helpers::smartPrintln(String msg) {
87 | #if (DISPLAY_ENABLED)
88 | display.println(msg);
89 | #else
90 | Serial.println(msg);
91 | #endif
92 | }
93 |
94 | void Helpers::smartPrint(int msg) {
95 | #if (DISPLAY_ENABLED)
96 | display.print(msg);
97 | #else
98 | Serial.print(msg);
99 | #endif
100 | }
101 |
102 | void Helpers::smartPrintln(int msg) {
103 | #if (DISPLAY_ENABLED)
104 | display.println(msg);
105 | #else
106 | Serial.println(msg);
107 | #endif
108 | }
109 |
110 | void Helpers::smartDisplay() {
111 | #if (DISPLAY_ENABLED)
112 | display.display();
113 | #endif
114 | }
115 |
116 | void Helpers::smartDisplay(int delayTime) {
117 | #if (DISPLAY_ENABLED)
118 | display.display();
119 | delay(delayTime);
120 | #endif
121 | }
122 |
123 | /*
124 | This function returns a single string separated by a predefined character at a given index. For example:
125 | String split = "hi this is a split test";
126 | String word3 = getValue(split, ' ', 2);
127 | Serial.println(word3);
128 | Should print 'is'. You also can try with index 0 returning 'hi' or safely trying index 5 returning 'test'.
129 | */
130 | String Helpers::getValue(String data, char separator, int index) {
131 | int found = 0;
132 | int strIndex[] = {0, -1};
133 | int maxIndex = data.length() - 1;
134 |
135 | for (int i = 0; i <= maxIndex && found <= index; i++) {
136 | if (data.charAt(i) == separator || i == maxIndex) {
137 | found++;
138 | strIndex[0] = strIndex[1] + 1;
139 | strIndex[1] = (i == maxIndex) ? i + 1 : i;
140 | }
141 | }
142 | return found > index ? data.substring(strIndex[0], strIndex[1]) : "";
143 | }
144 |
145 | // Set date and time based on a timestamp
146 | void Helpers::setDateTime(String timeConst) {
147 | timedate = timeConst;
148 | date = timedate.substring(8, 10) + "/" + timedate.substring(5, 7) + "/" + timedate.substring(0, 4);
149 | currentime = timedate.substring(11, 16);
150 | }
151 |
152 | // Return ON OFF value
153 | String Helpers::isOnOff(JsonDocument json) {
154 | String str = json[VALUE];
155 | return ((str == ON_CMD) || (str == on_CMD)) ? ON_CMD : OFF_CMD;
156 | }
157 |
158 | // Useful for Arduino Json lib to disambiguate types
159 | String Helpers::getValue(String string) {
160 | return string;
161 | }
162 |
163 | // String to char*
164 | char *Helpers::string2char(const String command) {
165 | char *p = const_cast(command.c_str());
166 | return p;
167 | }
168 |
169 | // From version number to Number
170 | long Helpers::versionNumberToNumber(const String &latestReleaseStr) {
171 | long longVersion = 0;
172 | longVersion += Helpers::getValue(latestReleaseStr, '.', 0).toInt() + 1000000;
173 | longVersion += Helpers::getValue(latestReleaseStr, '.', 1).toInt() + 1000;
174 | longVersion += Helpers::getValue(latestReleaseStr, '.', 2).toInt();
175 | return longVersion;
176 | }
177 |
--------------------------------------------------------------------------------
/src/Helpers.h:
--------------------------------------------------------------------------------
1 | /*
2 | Helpers.h - Helper classes
3 |
4 | Copyright © 2020 - 2025 Davide Perini
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | You should have received a copy of the MIT License along with this program.
17 | If not, see .
18 | */
19 |
20 | #ifndef _DPSOFTWARE_HELPERS_UTILS_H
21 | #define _DPSOFTWARE_HELPERS_UTILS_H
22 |
23 | #include
24 | #include "Configuration.h"
25 |
26 |
27 | extern bool isConfigFileOk;
28 | extern String lastMQTTConnection;
29 | extern String lastWIFiConnection;
30 | extern String lastBoot;
31 |
32 | extern bool screenSaverTriggered;
33 |
34 | extern bool lastPageScrollTriggered;
35 | extern int yoffset;
36 |
37 | extern String haVersion;
38 | extern String firmwareVersion;
39 | extern String MAC;
40 | extern String deviceName;
41 | extern String microcontrollerIP;
42 | extern bool dhcpInUse;
43 | extern int8_t ethd;
44 | extern String qsid;
45 | extern String qpass;
46 | extern String OTApass;
47 | extern String mqttIP;
48 | extern String mqttPort;
49 | extern String mqttuser;
50 | extern String mqttpass;
51 | extern String mqttWillTopic;
52 | extern String mqttWillPayload;
53 | extern int mqttWillQOS;
54 | extern bool mqttWillRetain;
55 | extern bool mqttCleanSession;
56 | extern bool mqttConnected;
57 | extern bool blockingMqtt;
58 | extern String additionalParam;
59 | extern bool ethConnected;
60 |
61 | // Blink LED vars
62 | extern unsigned long previousMillis; // will store last time LED was updated
63 | extern const unsigned long interval; // interval at which to blink (milliseconds)
64 | extern bool ledTriggered;
65 | extern int blinkCounter;
66 | extern const int blinkTimes; // 6 equals to 3 blink on and 3 off
67 |
68 | extern String timedate;
69 | [[maybe_unused]] extern String date;
70 | [[maybe_unused]] extern String currentime;
71 | extern String ERROR;
72 |
73 | extern int wifiReconnectAttemp;
74 | extern int mqttReconnectAttemp;
75 | extern bool fastDisconnectionManagement;
76 | extern bool forceWebServer; // if set to true, forces the use of launchWebServerForOTAConfig - added by Pronoe on 02/03/2022
77 |
78 | [[maybe_unused]] const int DELAY_10 = 10;
79 | [[maybe_unused]] const int DELAY_50 = 50;
80 | [[maybe_unused]] const int DELAY_200 = 200;
81 | [[maybe_unused]] const int DELAY_500 = 500;
82 | [[maybe_unused]] const int DELAY_1000 = 1000;
83 | [[maybe_unused]] const int DELAY_1500 = 1500;
84 | [[maybe_unused]] const int DELAY_3000 = 3000;
85 | [[maybe_unused]] const int DELAY_2000 = 2000;
86 | [[maybe_unused]] const int DELAY_4000 = 4000;
87 | [[maybe_unused]] const int DELAY_5000 = 5000;
88 |
89 | [[maybe_unused]] const String ON_CMD = "ON";
90 | [[maybe_unused]] const String OFF_CMD = "OFF";
91 | [[maybe_unused]] const String on_CMD = "on";
92 | [[maybe_unused]] const String off_CMD = "off";
93 | [[maybe_unused]] const String VALUE = "value";
94 | [[maybe_unused]] const String EMPTY_STR = "";
95 | #if defined(ESP8266)
96 | #define FILE_READ "r"
97 | #define FILE_WRITE "w"
98 | #endif
99 |
100 | extern bool temporaryDisableImprove;
101 | extern bool improvePacketReceived;
102 |
103 | // 'arduino', 45x31px
104 | const unsigned char ARDUINOLOGO[] PROGMEM = {
105 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x01, 0xe0, 0x00, 0x01, 0xff, 0x80, 0x0f,
106 | 0xfc, 0x40, 0x07, 0xff, 0xe0, 0x3f, 0xff, 0x00, 0x0f, 0xff, 0xf0, 0x7f, 0xff, 0x80, 0x1f, 0x01,
107 | 0xf8, 0xfc, 0x07, 0xc0, 0x1e, 0x00, 0x7d, 0xf0, 0x03, 0xc0, 0x3c, 0x00, 0x3d, 0xe0, 0x01, 0xe0,
108 | 0x38, 0x00, 0x1f, 0xc0, 0xc0, 0xe0, 0x78, 0x00, 0x1f, 0xc0, 0xc0, 0xf0, 0x78, 0x7e, 0x0f, 0x83,
109 | 0xf0, 0xf0, 0x78, 0x7e, 0x0f, 0x83, 0xf0, 0xf0, 0x78, 0x00, 0x0f, 0x80, 0xc0, 0xf0, 0x38, 0x00,
110 | 0x1f, 0xc0, 0xc0, 0xe0, 0x3c, 0x00, 0x3d, 0xe0, 0x01, 0xe0, 0x3e, 0x00, 0x7d, 0xf0, 0x03, 0xe0,
111 | 0x1f, 0x00, 0xf8, 0xf8, 0x07, 0xc0, 0x0f, 0xe7, 0xf0, 0x7f, 0x3f, 0x80, 0x07, 0xff, 0xe0, 0x3f,
112 | 0xff, 0x00, 0x03, 0xff, 0xc0, 0x1f, 0xfe, 0x00, 0x00, 0xff, 0x00, 0x07, 0xf8, 0x00, 0x00, 0x00,
113 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x7b, 0xec, 0x9e, 0xc9, 0xe0,
114 | 0x1c, 0x4b, 0x2c, 0x8c, 0xeb, 0x20, 0x16, 0x5b, 0x3c, 0x8c, 0xea, 0x30, 0x16, 0x73, 0x3c, 0x8c,
115 | 0xfa, 0x30, 0x1e, 0x5b, 0x2c, 0x8c, 0xdb, 0x30, 0x22, 0x4b, 0x6c, 0x8c, 0xd9, 0x60, 0x23, 0x4f,
116 | 0x83, 0x1e, 0xc8, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
117 | };
118 | #define ARDUINOLOGOW 45
119 | #define ARDUINOLOGOH 31
120 |
121 | // 'home-assistant_big', 44x44px
122 | static const unsigned char HABIGLOGO[] PROGMEM = {
123 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
124 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00,
125 | 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xc0, 0x00, 0x00,
126 | 0x00, 0x00, 0x7f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xf8,
127 | 0x00, 0x00, 0x00, 0x03, 0xf0, 0xfd, 0xe0, 0x00, 0x00, 0x07, 0xe0, 0x7f, 0xe0, 0x00, 0x00, 0x0f,
128 | 0xcf, 0x3f, 0xe0, 0x00, 0x00, 0x1f, 0xcf, 0x3f, 0xe0, 0x00, 0x00, 0x3f, 0xe6, 0x7f, 0xe0, 0x00,
129 | 0x00, 0x7f, 0xe0, 0x7f, 0xe0, 0x00, 0x00, 0xff, 0xf0, 0xff, 0xf0, 0x00, 0x01, 0xff, 0xf0, 0xff,
130 | 0xf8, 0x00, 0x03, 0xff, 0xf0, 0xff, 0xfc, 0x00, 0x07, 0xf0, 0xf0, 0xf0, 0xfe, 0x00, 0x0f, 0xe0,
131 | 0x70, 0xe0, 0x7f, 0x00, 0x1f, 0xce, 0x70, 0xe7, 0x3f, 0x80, 0x1f, 0xcf, 0x70, 0xef, 0x3f, 0x80,
132 | 0x01, 0xce, 0x70, 0xe7, 0x38, 0x00, 0x01, 0xe0, 0x30, 0xc0, 0x78, 0x00, 0x01, 0xf0, 0x10, 0x80,
133 | 0xf8, 0x00, 0x01, 0xff, 0x00, 0x0f, 0xf8, 0x00, 0x01, 0xff, 0x80, 0x0f, 0xf8, 0x00, 0x01, 0xff,
134 | 0xc0, 0x1f, 0xf8, 0x00, 0x01, 0xff, 0xe0, 0x3f, 0xf8, 0x00, 0x01, 0xff, 0xf0, 0x7f, 0xf8, 0x00,
135 | 0x01, 0xff, 0xf0, 0xff, 0xf8, 0x00, 0x01, 0xff, 0xf0, 0xff, 0xf8, 0x00, 0x01, 0xff, 0xf0, 0xff,
136 | 0xf8, 0x00, 0x01, 0xff, 0xf0, 0xff, 0xf8, 0x00, 0x01, 0xff, 0xf0, 0xff, 0xf8, 0x00, 0x01, 0xff,
137 | 0xf0, 0xff, 0xf8, 0x00, 0x01, 0xff, 0xf0, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
138 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
139 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
140 | };
141 | #define HABIGLOGOW 44
142 | #define HABIGLOGOH 44
143 |
144 | class Helpers {
145 |
146 | public:
147 | static void smartPrint(String msg);
148 |
149 | static void smartPrintln(String msg);
150 |
151 | static void smartPrint(int msg);
152 |
153 | static void smartPrintln(int msg);
154 |
155 | void smartDisplay();
156 |
157 | void smartDisplay(int delayTime);
158 |
159 | static String getValue(String data, char separator, int index);
160 |
161 | static String getValue(String string);
162 |
163 | [[maybe_unused]] static long versionNumberToNumber(const String &latestReleaseStr);
164 |
165 | [[maybe_unused]] static char *string2char(String command);
166 |
167 | [[maybe_unused]] static void setDateTime(String timeConst);
168 |
169 | [[maybe_unused]] static String isOnOff(JsonDocument json);
170 |
171 | };
172 |
173 | #endif
174 |
175 |
--------------------------------------------------------------------------------
/src/PingESP.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | PingESP.cpp - Utility class for pining the default gateway from ESP8266,
3 | some routers isn't able to detect connected devices if there is no traffic over the internet or direct to the gateway.
4 | This is helpful as a stay alive class, useful for not triggering security features on routers.
5 |
6 | Copyright © 2020 - 2025 Davide Perini
7 |
8 | Permission is hereby granted, free of charge, to any person obtaining a copy of
9 | this software and associated documentation files (the "Software"), to deal
10 | in the Software without restriction, including without limitation the rights
11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | copies of the Software, and to permit persons to whom the Software is
13 | furnished to do so, subject to the following conditions:
14 |
15 | The above copyright notice and this permission notice shall be included in
16 | all copies or substantial portions of the Software.
17 |
18 | You should have received a copy of the MIT License along with this program.
19 | If not, see .
20 | */
21 |
22 | #if defined(ESP8266)
23 |
24 | #include "PingESP.h"
25 |
26 | extern "C"
27 | extern "C" void esp_yield();
28 | extern "C" void __esp_suspend();
29 |
30 | /**
31 | * Newer Arduino SDK than 3.0.2 does esp_schedule() inside esp_yield(), this break ping functions here.
32 | * Direct alternative would be to use new esp_suspend().
33 | * Continue to use esp_yield() implementation when there is no esp_suspend() function (<= 3.0.2),
34 | * use new esp_suspend() instead (> 3.0.2).
35 | */
36 | extern "C" void suspend_or_yield() {
37 | esp_yield();
38 | }
39 | void __esp_suspend(void) __attribute__((weak, alias("suspend_or_yield")));
40 |
41 | PingESP::PingESP() {}
42 |
43 | byte PingESP::pingError = 0;
44 | byte PingESP::pingSuccess = 0;
45 |
46 | /**
47 | * Ping another device
48 | * @param dest gateway to ping
49 | * @return
50 | */
51 | bool PingESP::ping(IPAddress dest) {
52 | pingError = 0;
53 | pingSuccess = 0;
54 | memset(&pingOptions, 0, sizeof(struct ping_option));
55 | pingOptions.count = 1; // repeat only 1 time
56 | pingOptions.coarse_time = 1;
57 | pingOptions.ip = dest;
58 | pingOptions.recv_function = reinterpret_cast(&PingESP::receivePingCallback);
59 | pingOptions.sent_function = nullptr;
60 | if (ping_start(&pingOptions)) {
61 | // Sleep until the ping is finished
62 | __esp_suspend();
63 | }
64 | return (pingSuccess > 0);
65 | }
66 |
67 | /**
68 | * Ping gateway to keep the connection alive
69 | * @param dest gateway to ping
70 | * @return
71 | */
72 | bool PingESP::ping() {
73 | pingError = 0;
74 | pingSuccess = 0;
75 | memset(&pingOptions, 0, sizeof(struct ping_option));
76 | pingOptions.count = 1; // repeat only 1 time
77 | pingOptions.coarse_time = 1;
78 | pingOptions.ip = WiFi.gatewayIP();
79 | pingOptions.recv_function = reinterpret_cast(&PingESP::receivePingCallback);
80 | pingOptions.sent_function = nullptr;
81 | if (ping_start(&pingOptions)) {
82 | // Sleep until the ping is finished
83 | __esp_suspend();
84 | }
85 | return (pingSuccess > 0);
86 | }
87 |
88 | /**
89 | * Callback from the ESP core ping_start
90 | * @param opt options
91 | * @param resp response
92 | */
93 | void PingESP::receivePingCallback(void *opt, void *resp) {
94 | // ping_resp from ESP core
95 | ping_resp *ping_resp = reinterpret_cast(resp);
96 | // Check for errors
97 | if (ping_resp->ping_err == -1) {
98 | pingError++;
99 | Serial.println(F("PING KO"));
100 | } else {
101 | pingSuccess++;
102 | }
103 | if (pingSuccess + pingError == 1) {
104 | // ping sent, return
105 | esp_schedule();
106 | }
107 | }
108 | #endif
--------------------------------------------------------------------------------
/src/PingESP.h:
--------------------------------------------------------------------------------
1 | /*
2 | PingESP.h - Utility class for pining the default gateway from ESP8266,
3 | some routers aren't able to detect connected devices if there is no traffic over the internet or direct to the gateway.
4 | This is helpful as a stay alive class, useful for not triggering security features on routers.
5 | Usage:
6 | - add the following declaration in your application header file:
7 | #include "PingESP.h"
8 | PingESP pingESP;
9 | - use `pingESP.ping(WiFi.gatewayIP());` in your application code
10 |
11 | Copyright © 2020 - 2025 Davide Perini
12 |
13 | Permission is hereby granted, free of charge, to any person obtaining a copy of
14 | this software and associated documentation files (the "Software"), to deal
15 | in the Software without restriction, including without limitation the rights
16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | copies of the Software, and to permit persons to whom the Software is
18 | furnished to do so, subject to the following conditions:
19 |
20 | The above copyright notice and this permission notice shall be included in
21 | all copies or substantial portions of the Software.
22 |
23 | You should have received a copy of the MIT License along with this program.
24 | If not, see .
25 | */
26 |
27 | #if defined(ESP8266)
28 |
29 | #ifndef PingESP_H
30 | #define PingESP_H
31 |
32 | #include
33 | #include
34 |
35 | extern "C" {
36 | #include
37 | }
38 |
39 | class PingESP {
40 | public:
41 | PingESP();
42 |
43 | bool ping(IPAddress dest);
44 |
45 | bool ping();
46 |
47 | protected:
48 | static void receivePingCallback(void *opt, void *pdata);
49 |
50 | ping_option pingOptions;
51 | static byte pingCount, pingError, pingSuccess;
52 | };
53 |
54 | #endif // PingESP_H
55 |
56 | #endif
--------------------------------------------------------------------------------
/src/QueueManager.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | QueueManager.cpp - Managing MQTT queue
3 |
4 | Copyright © 2020 - 2025 Davide Perini
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | You should have received a copy of the MIT License along with this program.
17 | If not, see .
18 | */
19 |
20 | #include "QueueManager.h"
21 |
22 |
23 | PubSubClient mqttClient(espClient);
24 |
25 | /********************************** SETUP MQTT QUEUE **********************************/
26 | void QueueManager::setupMQTTQueue(void (*callback)(char *, byte *, unsigned int)) {
27 | mqttClient.setServer(IPAddress(Helpers::getValue(mqttIP, '.', 0).toInt(),
28 | Helpers::getValue(mqttIP, '.', 1).toInt(),
29 | Helpers::getValue(mqttIP, '.', 2).toInt(),
30 | Helpers::getValue(mqttIP, '.', 3).toInt()), mqttPort.toInt());
31 | mqttClient.setCallback(callback);
32 | mqttClient.setBufferSize(MQTT_MAX_PACKET_SIZE);
33 | mqttClient.setKeepAlive(MQTT_KEEP_ALIVE);
34 | }
35 |
36 | /********************************** SET LAST WILL PARAMETERS **********************************/
37 | void
38 | QueueManager::setMQTTWill(const char *topic, const char *payload, const int qos, boolean retain, boolean cleanSession) {
39 | mqttWillTopic = topic;
40 | mqttWillPayload = payload;
41 | mqttWillQOS = qos;
42 | mqttWillRetain = retain;
43 | mqttCleanSession = cleanSession;
44 | }
45 |
46 | /********************************** MQTT RECONNECT **********************************/
47 | void QueueManager::mqttReconnect(void (*manageDisconnections)(), void (*manageQueueSubscription)(),
48 | void (*manageHardwareButton)()) {
49 | // how many attemps to MQTT connection
50 | mqttReconnectAttemp = 0;
51 | // Loop until we're reconnected
52 | while (!mqttClient.connected() && (WiFi.status() == WL_CONNECTED || ethd >= 0)) {
53 | #if (DISPLAY_ENABLED)
54 | display.clearDisplay();
55 | display.setTextSize(1);
56 | display.setCursor(0,0);
57 | #endif
58 | if (mqttReconnectAttemp <= 20) {
59 | Helpers::smartPrintln(F("Connecting to"));
60 | Helpers::smartPrintln(F("MQTT Broker..."));
61 | }
62 | helper.smartDisplay();
63 | // Manage hardware button if any
64 | manageHardwareButton();
65 | // Attempt to connect to MQTT server with QoS = 1 (pubsubclient supports QoS 1 for subscribe only, published msg have QoS 0 this is why I implemented a custom solution)
66 | boolean mqttSuccess;
67 | Serial.println("MQTT Last Will Params: ");
68 | Serial.print("willTopic: ");
69 | Serial.println(mqttWillTopic);
70 | Serial.print("willPayload: ");
71 | Serial.println(mqttWillPayload);
72 | Serial.print("qos: ");
73 | Serial.println(mqttWillQOS);
74 | Serial.print("retain: ");
75 | Serial.println(mqttWillRetain);
76 | Serial.print("clean session: ");
77 | Serial.println(mqttCleanSession);
78 | if (mqttuser.isEmpty() || mqttpass.isEmpty()) {
79 | mqttSuccess = mqttClient.connect(Helpers::string2char(deviceName), Helpers::string2char(mqttWillTopic),
80 | mqttWillQOS, mqttWillRetain, Helpers::string2char(mqttWillPayload));
81 | } else {
82 | mqttSuccess = mqttClient.connect(Helpers::string2char(deviceName), Helpers::string2char(mqttuser),
83 | Helpers::string2char(mqttpass), Helpers::string2char(mqttWillTopic), mqttWillQOS,
84 | mqttWillRetain, Helpers::string2char(mqttWillPayload), mqttCleanSession);
85 | }
86 | if (mqttSuccess) {
87 | Helpers::smartPrintln(F(""));
88 | Helpers::smartPrintln(F("MQTT CONNECTED"));
89 | Helpers::smartPrintln(F(""));
90 | Helpers::smartPrintln(F("Reading data from"));
91 | Helpers::smartPrintln(F("the network..."));
92 | helper.smartDisplay();
93 | // Subscribe to MQTT topics
94 | manageQueueSubscription();
95 | delay(DELAY_2000);
96 | mqttReconnectAttemp = 0;
97 | // reset the lastMQTTConnection to off, will be initialized by next time update
98 | lastMQTTConnection = OFF_CMD;
99 | } else {
100 | Helpers::smartPrintln(F("MQTT attempts="));
101 | Helpers::smartPrintln(mqttReconnectAttemp);
102 | helper.smartDisplay();
103 | // after MAX_RECONNECT attemps all peripherals are shut down
104 | if (mqttReconnectAttemp >= MAX_RECONNECT || fastDisconnectionManagement) {
105 | Helpers::smartPrintln(F("Max retry reached, powering off peripherals."));
106 | helper.smartDisplay();
107 | // Manage disconnections, powering off peripherals
108 | manageDisconnections();
109 | } else if (mqttReconnectAttemp > 10000) {
110 | mqttReconnectAttemp = 0;
111 | }
112 | mqttReconnectAttemp++;
113 | // Wait 500 millis before retrying
114 | delay(DELAY_500);
115 | }
116 | if (!blockingMqtt) {
117 | break;
118 | }
119 | }
120 | }
121 |
122 | void QueueManager::queueLoop(void (*manageDisconnections)(), void (*manageQueueSubscription)(),
123 | void (*manageHardwareButton)()) {
124 | if (!mqttClient.connected()) {
125 | mqttConnected = false;
126 | mqttReconnect(manageDisconnections, manageQueueSubscription, manageHardwareButton);
127 | } else {
128 | mqttConnected = true;
129 | }
130 | mqttClient.loop();
131 | }
132 |
133 | /********************************** SEND A MESSAGE ON THE QUEUE **********************************/
134 | void QueueManager::publish(const char *topic, const char *payload, boolean retained) {
135 | mqttClient.publish(topic, payload, retained);
136 | }
137 |
138 | /********************************** SUBSCRIBE TO A QUEUE TOPIC **********************************/
139 | void QueueManager::unsubscribe(const char *topic) {
140 | mqttClient.unsubscribe(topic);
141 | }
142 |
143 | /********************************** SUBSCRIBE TO A QUEUE TOPIC **********************************/
144 | void QueueManager::subscribe(const char *topic) {
145 | mqttClient.subscribe(topic);
146 | }
147 |
148 | /********************************** SUBSCRIBE TO A QUEUE TOPIC **********************************/
149 | void QueueManager::subscribe(const char *topic, uint8_t qos) {
150 | mqttClient.subscribe(topic, qos);
151 | }
152 |
153 |
--------------------------------------------------------------------------------
/src/QueueManager.h:
--------------------------------------------------------------------------------
1 | /*
2 | QueueManager.h - Managing MQTT queue
3 |
4 | Copyright © 2020 - 2025 Davide Perini
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | You should have received a copy of the MIT License along with this program.
17 | If not, see .
18 | */
19 |
20 | #ifndef _DPSOFTWARE_QUEUE_MANAGER_H
21 | #define _DPSOFTWARE_QUEUE_MANAGER_H
22 |
23 | #include
24 | #include "WifiManager.h"
25 |
26 |
27 | class QueueManager {
28 |
29 | private:
30 | Helpers helper;
31 |
32 | public:
33 | static void setupMQTTQueue(void (*callback)(char *, byte *, unsigned int)); // setup the queue
34 | static void setMQTTWill(const char *topic, const char *payload, int qos, boolean retain, boolean cleanSession); // set the last will parameters for mqtt
35 | void mqttReconnect(void (*manageDisconnections)(), void (*manageQueueSubscription)(), void (*manageHardwareButton)()); // manage reconnection on the queue
36 | void queueLoop(void (*manageDisconnections)(), void (*manageQueueSubscription)(), void (*manageHardwareButton)()); // manage queue loop
37 | static void publish(const char *topic, const char *payload, boolean retained); // send a message on the queue
38 | static void unsubscribe(const char *topic); // unsubscribe to a queue topic
39 | static void subscribe(const char *topic); // subscribe to a queue topic
40 | static void subscribe(const char *topic, uint8_t qos); // subscribe to a queue topic with qos 0 or 1
41 |
42 | };
43 |
44 | #endif
--------------------------------------------------------------------------------
/src/Secrets.h:
--------------------------------------------------------------------------------
1 | /*
2 | Secrets.h - Store your secrets here
3 |
4 | Copyright © 2020 - 2025 Davide Perini
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | You should have received a copy of the MIT License along with this program.
17 | If not, see .
18 | */
19 |
20 | #ifndef _DPSOFTWARE_SECRETS_H
21 | #define _DPSOFTWARE_SECRETS_H
22 |
23 | #ifndef WIFI_SSID
24 | #define WIFI_SSID "XXX"
25 | #endif
26 | const char *const SSID = WIFI_SSID;
27 |
28 | #ifndef WIFI_PWD
29 | #define WIFI_PWD "XXX"
30 | #endif
31 | const char *const PASSWORD = WIFI_PWD;
32 |
33 | #ifndef MQTT_USER
34 | #define MQTT_USER "XXX"
35 | #endif
36 | const char *const MQTT_USERNAME = MQTT_USER;
37 |
38 | #ifndef MQTT_PWD
39 | #define MQTT_PWD "XXX"
40 | #endif
41 | const char *const MQTT_PASSWORD = MQTT_PWD;
42 |
43 | #ifndef OTA_PWD
44 | #define OTA_PWD "XXX"
45 | #endif
46 | const char *const OTAPASSWORD = OTA_PWD; //the password you will need to enter for OTA upload
47 |
48 | #endif
--------------------------------------------------------------------------------
/src/WifiManager.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | WifiManager.cpp - Managing WiFi and OTA
3 |
4 | Copyright © 2020 - 2025 Davide Perini
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | You should have received a copy of the MIT License along with this program.
17 | If not, see .
18 | */
19 |
20 | #include "WifiManager.h"
21 |
22 | //Establishing Local server at port 80 whenever required
23 | #if defined(ESP8266)
24 | ESP8266WebServer server(80);
25 | #elif defined(ARDUINO_ARCH_ESP32)
26 | WebServer server(80);
27 | #endif
28 | // WiFiClient
29 | WiFiClient espClient;
30 | // WebServer content
31 | String content;
32 | // WebServer status code
33 | int statusCode;
34 | // WebServer HTML frontend
35 | String htmlString;
36 | byte improvActive; //0: no improv packet received, 1: improv active, 2: provisioning
37 | byte improvError;
38 | char serverDescription[33] = "Luciferin";
39 | char cmDNS[33] = "x";
40 | char clientSSID[33];
41 | char clientPass[65];
42 | unsigned long previousMillisEsp32Reconnect = 0;
43 | unsigned long intervalEsp32Reconnect = 15000;
44 |
45 | /********************************** SETUP WIFI *****************************************/
46 | void WifiManager::setupWiFi(void (*manageDisconnections)(), void (*manageHardwareButton)()) {
47 | wifiReconnectAttemp = 0;
48 | // DPsoftware domotics
49 | #if (DISPLAY_ENABLED)
50 | display.clearDisplay();
51 | display.setTextSize(2);
52 | display.setCursor(5,17);
53 | display.drawRoundRect(0, 0, display.width()-1, display.height()-1, display.height()/4, WHITE);
54 | #endif
55 | Helpers::smartPrintln(F("DPsoftware domotics"));
56 | helper.smartDisplay(DELAY_3000);
57 | #if (DISPLAY_ENABLED)
58 | display.clearDisplay();
59 | display.setTextSize(1);
60 | display.setCursor(0,0);
61 | #endif
62 | Helpers::smartPrintln(F("Connecting to: "));
63 | Helpers::smartPrint(qsid);
64 | Helpers::smartPrintln(F("..."));
65 | helper.smartDisplay(DELAY_2000);
66 | WiFi.persistent(true); // Solve possible wifi init errors (re-add at 6.2.1.16 #4044, #4083)
67 | WiFi.disconnect(true); // Delete SDK wifi config
68 | delay(DELAY_200);
69 | WiFi.mode(WIFI_STA); // Disable AP mode
70 | #if defined(ESP8266)
71 | WiFi.setSleepMode(WIFI_NONE_SLEEP);
72 | #endif
73 | WiFi.setAutoConnect(true);
74 | WiFi.setAutoReconnect(true);
75 | Serial.println(microcontrollerIP);
76 | if (!microcontrollerIP.equals("DHCP")) {
77 | WiFi.config(IPAddress(Helpers::getValue(microcontrollerIP, '.', 0).toInt(),
78 | Helpers::getValue(microcontrollerIP, '.', 1).toInt(),
79 | Helpers::getValue(microcontrollerIP, '.', 2).toInt(),
80 | Helpers::getValue(microcontrollerIP, '.', 3).toInt()),
81 | IPAddress(Helpers::getValue(IP_GATEWAY, '.', 0).toInt(),
82 | Helpers::getValue(IP_GATEWAY, '.', 1).toInt(),
83 | Helpers::getValue(IP_GATEWAY, '.', 2).toInt(),
84 | Helpers::getValue(IP_GATEWAY, '.', 3).toInt()),
85 | IPAddress(Helpers::getValue(IP_SUBNET, '.', 0).toInt(),
86 | Helpers::getValue(IP_SUBNET, '.', 1).toInt(),
87 | Helpers::getValue(IP_SUBNET, '.', 2).toInt(),
88 | Helpers::getValue(IP_SUBNET, '.', 3).toInt()),
89 | IPAddress(Helpers::getValue(IP_DNS, '.', 0).toInt(),
90 | Helpers::getValue(IP_DNS, '.', 1).toInt(),
91 | Helpers::getValue(IP_DNS, '.', 2).toInt(),
92 | Helpers::getValue(IP_DNS, '.', 3).toInt()));
93 | Serial.println(F("Using static IP address"));
94 | dhcpInUse = false;
95 | } else {
96 | Serial.println(F("Using DHCP"));
97 | dhcpInUse = true;
98 | }
99 | #if defined(ESP8266)
100 | WiFi.hostname(Helpers::string2char(deviceName));
101 | // Set wifi power in dbm range 0/0.25, set to 0 to reduce PIR false positive due to wifi power, 0 low, 20.5 max.
102 | WiFi.setOutputPower(WIFI_POWER);
103 | if (microcontrollerIP.equals("DHCP")) {
104 | WiFi.config(0U, 0U, 0U);
105 | }
106 | WiFi.onStationModeDisconnected([](const WiFiEventStationModeDisconnected &event) {
107 | Serial.println(F("WiFi connection is lost."));
108 | WiFi.reconnect();
109 | });
110 | #elif defined(ARDUINO_ARCH_ESP32)
111 | WiFi.setHostname(helper.string2char(deviceName));
112 | btStop();
113 | #endif
114 | // Start wifi connection
115 | WiFi.begin(qsid.c_str(), qpass.c_str());
116 | #if defined(ARDUINO_ARCH_ESP32)
117 | setTxPower();
118 | WiFi.setSleep(false);
119 | #endif
120 | reconnectToWiFi(manageDisconnections, manageHardwareButton);
121 | MAC = WiFi.macAddress();
122 | delay(DELAY_1500);
123 | // reset the lastWIFiConnection to off, will be initialized by next time update
124 | lastWIFiConnection = OFF_CMD;
125 | }
126 |
127 | /**
128 | * Set the TX power based on dBm values.
129 | */
130 | #if defined(ARDUINO_ARCH_ESP32)
131 | void WifiManager::setTxPower() const {
132 | wifi_power_t val = WIFI_POWER_MINUS_1dBm;
133 | if (WIFI_POWER < 0) {
134 | val = WIFI_POWER_MINUS_1dBm;
135 | } else if (WIFI_POWER < 3.5f) {
136 | val = WIFI_POWER_2dBm;
137 | } else if (WIFI_POWER < 6) {
138 | val = WIFI_POWER_5dBm;
139 | } else if (WIFI_POWER < 8) {
140 | val = WIFI_POWER_7dBm;
141 | } else if (WIFI_POWER < 10) {
142 | val = WIFI_POWER_8_5dBm;
143 | } else if (WIFI_POWER < 12) {
144 | val = WIFI_POWER_11dBm;
145 | } else if (WIFI_POWER < 14) {
146 | val = WIFI_POWER_13dBm;
147 | } else if (WIFI_POWER < 16) {
148 | val = WIFI_POWER_15dBm;
149 | } else if (WIFI_POWER < 17.75f) {
150 | val = WIFI_POWER_17dBm;
151 | } else if (WIFI_POWER < 18.75f) {
152 | val = WIFI_POWER_18_5dBm;
153 | } else if (WIFI_POWER < 19.25f) {
154 | val = WIFI_POWER_19dBm;
155 | } else {
156 | val = WIFI_POWER_19_5dBm;
157 | }
158 | WiFi.setTxPower(val);
159 | }
160 | #endif
161 |
162 | void WifiManager::reconnectToWiFi(void (*manageDisconnections)(), void (*manageHardwareButton)()) {
163 | wifiReconnectAttemp = 0;
164 | // loop here until connection
165 | while (WiFi.status() != WL_CONNECTED) {
166 | manageHardwareButton();
167 | delay(DELAY_500);
168 | Serial.print(F("."));
169 | wifiReconnectAttemp++;
170 | if (wifiReconnectAttemp > 10) {
171 | // if fastDisconnectionManagement we need to execute the callback immediately,
172 | // example: power off a watering system can't wait MAX_RECONNECT attemps
173 | if (fastDisconnectionManagement) {
174 | manageDisconnections();
175 | }
176 | #if (DISPLAY_ENABLED)
177 | display.setCursor(0,0);
178 | display.clearDisplay();
179 | display.setTextSize(1);
180 | #endif
181 | Helpers::smartPrint(F("Wifi attemps= "));
182 | Helpers::smartPrintln(wifiReconnectAttemp);
183 | #if defined(ARDUINO_ARCH_ESP32)
184 | // Arduino 2.x for ESP32 seems to not support callback, polling to reconnect.
185 | unsigned long currentMillisEsp32Reconnect = millis();
186 | if (currentMillisEsp32Reconnect - previousMillisEsp32Reconnect >= intervalEsp32Reconnect) {
187 | WiFi.disconnect();
188 | WiFi.begin(qsid.c_str(), qpass.c_str());
189 | setTxPower();
190 | WiFi.setSleep(false);
191 | previousMillisEsp32Reconnect = currentMillisEsp32Reconnect;
192 | }
193 | #endif
194 | if (wifiReconnectAttemp >= MAX_RECONNECT) {
195 | Helpers::smartPrintln(F("Max retry reached, powering off peripherals."));
196 | manageDisconnections();
197 | }
198 | helper.smartDisplay();
199 | } else if (wifiReconnectAttemp > 10000) {
200 | wifiReconnectAttemp = 0;
201 | }
202 | }
203 | if (wifiReconnectAttemp > 0) {
204 | Helpers::smartPrint(F("\nWIFI CONNECTED\nIP Address: "));
205 | microcontrollerIP = WiFi.localIP().toString();
206 | Helpers::smartPrintln(microcontrollerIP);
207 | Helpers::smartPrint(F("nb of attempts: "));
208 | Helpers::smartPrintln(wifiReconnectAttemp);
209 | }
210 | }
211 |
212 | /********************************** SETUP OTA *****************************************/
213 | void WifiManager::setupOTAUpload() {
214 | //OTA SETUP
215 | ArduinoOTA.setPort(OTA_PORT);
216 | // Hostname defaults to esp8266-[ChipID]
217 | ArduinoOTA.setHostname(Helpers::string2char(deviceName));
218 | // No authentication by default
219 | ArduinoOTA.setPassword((const char *) Helpers::string2char(OTApass));
220 | ArduinoOTA.onStart([]() {
221 | Serial.println(F("Starting"));
222 | });
223 | ArduinoOTA.onEnd([]() {
224 | Serial.println(F("End"));
225 | });
226 | ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
227 | Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
228 | });
229 | ArduinoOTA.onError([](ota_error_t error) {
230 | Serial.printf("Error[%u]: ", error);
231 | if (error == OTA_AUTH_ERROR) Serial.println(F("Auth Failed"));
232 | else if (error == OTA_BEGIN_ERROR) Serial.println(F("Begin Failed"));
233 | else if (error == OTA_CONNECT_ERROR) Serial.println(F("Connect Failed"));
234 | else if (error == OTA_RECEIVE_ERROR) Serial.println(F("Receive Failed"));
235 | else if (error == OTA_END_ERROR) Serial.println(F("End Failed"));
236 | });
237 | ArduinoOTA.begin();
238 | }
239 |
240 | /*
241 | Return the quality (Received Signal Strength Indicator) of the WiFi network.
242 | Returns a number between 0 and 100 if WiFi is connected.
243 | Returns -1 if WiFi is disconnected.
244 | */
245 | int WifiManager::getQuality() {
246 | if (WiFi.status() != WL_CONNECTED) {
247 | return -1;
248 | }
249 | int dBm = WiFi.RSSI();
250 | if (dBm <= -100) {
251 | return 0;
252 | }
253 | if (dBm >= -50) {
254 | return 100;
255 | }
256 | return 2 * (dBm + 100);
257 | }
258 |
259 | // check if wifi is correctly configured
260 | bool WifiManager::isWifiConfigured() {
261 | if (strcmp(SSID, "XXX") != 0) {
262 | return true;
263 | }
264 | return false;
265 | }
266 |
267 | // if no ssid available, launch web server to get config params via browser
268 | void WifiManager::launchWebServerForOTAConfig() {
269 | WiFi.disconnect();
270 | Serial.println(F("Turning HotSpot On"));
271 | setupAP();
272 | launchWeb();
273 | while (WiFi.status() != WL_CONNECTED) {
274 | delay(10);
275 | server.handleClient();
276 | }
277 | }
278 |
279 | void WifiManager::launchWebServerCustom(void (*listener)()) {
280 | WiFi.disconnect();
281 | Serial.println(F("Turning HotSpot On"));
282 | WiFi.mode(WIFI_STA);
283 | WiFi.disconnect();
284 | delay(DELAY_200);
285 | WiFi.softAP(WIFI_DEVICE_NAME, "");
286 | listener();
287 | server.begin();
288 | while (WiFi.status() != WL_CONNECTED) {
289 | delay(10);
290 | server.handleClient();
291 | }
292 | }
293 |
294 | // Manage improv wifi
295 | void WifiManager::manageImprovWifi() {
296 | handleImprovPacket();
297 | }
298 |
299 | void WifiManager::launchWeb() {
300 | Serial.println("");
301 | if (WiFi.status() == WL_CONNECTED) {
302 | Serial.println("WiFi connected");
303 | }
304 | Serial.print("Local IP: ");
305 | Serial.println(WiFi.localIP());
306 | Serial.print("SoftAP IP: ");
307 | Serial.println(WiFi.softAPIP());
308 | createWebServer();
309 | server.begin();
310 | Serial.println("Server started");
311 | }
312 |
313 | void WifiManager::setupAP(void) {
314 | WiFi.mode(WIFI_STA);
315 | WiFi.disconnect();
316 | delay(DELAY_200);
317 | int n = WiFi.scanNetworks();
318 | Serial.println("scan done");
319 | if (n == 0) {
320 | Serial.println("no networks found");
321 | } else {
322 | Serial.print(n);
323 | Serial.println(" networks found");
324 | for (int i = 0; i < n; ++i) {
325 | // Print SSID and RSSI for each network found
326 | Serial.print(i + 1);
327 | Serial.print(": ");
328 | Serial.print(WiFi.SSID(i));
329 | Serial.print(" (");
330 | Serial.print(WiFi.RSSI(i));
331 | Serial.print(")");
332 | #if defined(ESP8266)
333 | Serial.println((WiFi.encryptionType(i) == ENC_TYPE_NONE) ? " " : "*");
334 | #elif defined(ARDUINO_ARCH_ESP32)
335 | Serial.println((WiFi.encryptionType(i) == WIFI_AUTH_OPEN) ? " " : "*");
336 | #endif
337 | delay(10);
338 | }
339 | }
340 | Serial.println("");
341 | htmlString = "SSID | RSSI | Enctipted |
";
342 | for (int i = 0; i < n; ++i) {
343 | htmlString += "";
344 | htmlString += "";
345 | htmlString += WiFi.SSID(i);
346 | htmlString += " | ";
347 | htmlString += "";
348 | htmlString += WiFi.RSSI(i);
349 | htmlString += " | ";
350 | htmlString += "";
351 | #if defined(ESP8266)
352 | htmlString += ((WiFi.encryptionType(i) == ENC_TYPE_NONE) ? "PUBLIC" : "ENCRYPTED");
353 | #elif defined(ARDUINO_ARCH_ESP32)
354 | htmlString += ((WiFi.encryptionType(i) == WIFI_AUTH_OPEN) ? "PUBLIC" : "ENCRYPTED");
355 | #endif
356 | htmlString += " | ";
357 | htmlString += "
";
358 | }
359 | htmlString += "
";
360 | delay(100);
361 | WiFi.softAP(WIFI_DEVICE_NAME, "");
362 | launchWeb();
363 | }
364 |
365 | void WifiManager::createWebServer() {
366 | {
367 | server.on("/", []() {
368 | IPAddress ip = WiFi.softAPIP();
369 | String ipStr = String(ip[0]) + '.' + String(ip[1]) + '.' + String(ip[2]) + '.' + String(ip[3]);
370 | content = "\n"
371 | "\n"
372 | " \n"
376 | " \n"
444 | "\n"
445 | "\n"
464 | ""
468 | "";
488 | server.send(200, "text/html", content);
489 | });
490 |
491 | server.on("/setting", []() {
492 | String deviceName = server.arg("deviceName");
493 | String microcontrollerIP = server.arg("microcontrollerIP");
494 | String qsid = server.arg("ssid");
495 | String qpass = server.arg("pass");
496 | String mqttCheckbox = server.arg("mqttCheckbox");
497 | String mqttIP = server.arg("mqttIP");
498 | String mqttPort = server.arg("mqttPort");
499 | String OTApass = server.arg("OTApass");
500 | String mqttuser = server.arg("mqttuser");
501 | String mqttpass = server.arg("mqttpass");
502 | String additionalParam = server.arg("additionalParam");
503 | JsonDocument doc;
504 |
505 | if (deviceName.length() > 0 && qsid.length() > 0 && qpass.length() > 0 && OTApass.length() > 0
506 | && ((mqttCheckbox.length() == 0) || (mqttIP.length() > 0 && mqttPort.length() > 0))) {
507 |
508 | Serial.println("deviceName");
509 | Serial.println(deviceName);
510 | Serial.println("microcontrollerIP");
511 | if (microcontrollerIP.length() == 0) {
512 | microcontrollerIP = "DHCP";
513 | }
514 | Serial.println(microcontrollerIP);
515 | Serial.println("qsid");
516 | Serial.println(qsid);
517 | Serial.println("qpass");
518 | Serial.println(qpass);
519 | Serial.println("OTApass");
520 | Serial.println(OTApass);
521 | Serial.println("mqttIP");
522 | Serial.println(mqttIP);
523 | Serial.println("mqttPort");
524 | Serial.println(mqttPort);
525 | Serial.println("mqttuser");
526 | Serial.println(mqttuser);
527 | Serial.println("mqttpass");
528 | Serial.println(mqttpass);
529 | Serial.println("additionalParam");
530 | Serial.println(additionalParam);
531 |
532 | doc["deviceName"] = deviceName;
533 | doc["microcontrollerIP"] = microcontrollerIP;
534 | doc["qsid"] = qsid;
535 | doc["qpass"] = qpass;
536 | doc["OTApass"] = OTApass;
537 | if (mqttCheckbox.equals("on")) {
538 | doc["mqttIP"] = mqttIP;
539 | doc["mqttPort"] = mqttPort;
540 | doc["mqttuser"] = mqttuser;
541 | doc["mqttpass"] = mqttpass;
542 | } else {
543 | doc["mqttIP"] = "";
544 | doc["mqttPort"] = "";
545 | doc["mqttuser"] = "";
546 | doc["mqttpass"] = "";
547 | }
548 | doc["additionalParam"] = additionalParam;
549 | content = "Success: rebooting the microcontroller using your credentials.";
550 | statusCode = 200;
551 | } else {
552 | content = "Error: missing required fields.";
553 | statusCode = 404;
554 | Serial.println("Sending 404");
555 | }
556 | delay(DELAY_500);
557 | server.sendHeader("Access-Control-Allow-Origin", "*");
558 | server.send(statusCode, "text/plain", content);
559 | delay(DELAY_500);
560 |
561 | // Write to LittleFS
562 | Serial.println(F("Saving setup.json"));
563 | File jsonFile = LittleFS.open("/setup.json", FILE_WRITE);
564 | if (!jsonFile) {
565 | Serial.println("Failed to open [setup.json] file for writing");
566 | } else {
567 | serializeJsonPretty(doc, Serial);
568 | serializeJson(doc, jsonFile);
569 | jsonFile.close();
570 | Serial.println("[setup.json] written correctly");
571 | }
572 | delay(DELAY_200);
573 | #if defined(ARDUINO_ARCH_ESP32)
574 | ESP.restart();
575 | #elif defined(ESP8266)
576 | EspClass::restart();
577 | #endif
578 | });
579 | }
580 | }
581 |
582 | bool WifiManager::isConnected() {
583 | return (WiFi.localIP()[0] != 0 && WiFi.status() == WL_CONNECTED);
584 | }
585 |
586 | void WifiManager::sendImprovStateResponse(uint8_t state, bool error) {
587 | if (!error && improvError > 0 && improvError < 3) sendImprovStateResponse(0x00, true);
588 | if (error) improvError = state;
589 | char out[11] = {'I', 'M', 'P', 'R', 'O', 'V'};
590 | out[6] = IMPROV_VERSION;
591 | out[7] = error ? ImprovPacketType::Error_State : ImprovPacketType::Current_State;
592 | out[8] = 1;
593 | out[9] = state;
594 | uint8_t checksum = 0;
595 | for (uint8_t i = 0; i < 10; i++) checksum += out[i];
596 | out[10] = checksum;
597 | Serial.write((uint8_t *) out, 11);
598 | Serial.write('\n');
599 | Serial.flush();
600 | }
601 |
602 | void WifiManager::sendImprovRPCResponse(byte commandId) {
603 | sendImprovRPCResponse(commandId, false);
604 | }
605 |
606 | void WifiManager::sendImprovRPCResponse(byte commandId, bool forceConnection) {
607 | if (improvError > 0 && improvError < 3) sendImprovStateResponse(0x00, true);
608 | uint8_t packetLen = 12;
609 | char out[64] = {'I', 'M', 'P', 'R', 'O', 'V'};
610 | out[6] = IMPROV_VERSION;
611 | out[7] = ImprovPacketType::RPC_Response;
612 | out[8] = 2; //Length (set below)
613 | out[9] = commandId;
614 | out[10] = 0; //Data len (set below)
615 | out[11] = '\0'; //URL len (set below)
616 | if (isConnected() || forceConnection) {
617 | IPAddress localIP = WiFi.localIP();
618 | uint8_t len = sprintf(out + 12, "http://%d.%d.%d.%d", localIP[0], localIP[1], localIP[2], localIP[3]);
619 | if (len > 24) return; //sprintf fail
620 | out[11] = len;
621 | out[10] = 1 + len;
622 | out[8] = 3 + len; //RPC command type + data len + url len + url
623 | packetLen = 13 + len;
624 | }
625 | uint8_t checksum = 0;
626 | for (uint8_t i = 0; i < packetLen - 1; i++) checksum += out[i];
627 | out[packetLen - 1] = checksum;
628 | Serial.write((uint8_t *) out, packetLen);
629 | Serial.write('\n');
630 | Serial.flush();
631 | improvActive = 1; //no longer provisioning
632 | }
633 |
634 | void WifiManager::sendImprovInfoResponse() {
635 | if (improvError > 0 && improvError < 3) sendImprovStateResponse(0x00, true);
636 | uint8_t packetLen = 12;
637 | char out[128] = {'I', 'M', 'P', 'R', 'O', 'V'};
638 | out[6] = IMPROV_VERSION;
639 | out[7] = ImprovPacketType::RPC_Response;
640 | //out[8] = 2; //Length (set below)
641 | out[9] = ImprovRPCType::Request_Info;
642 | //out[10] = 0; //Data len (set below)
643 | out[11] = 4; //Firmware len ("FIRM")
644 | out[12] = 'F';
645 | out[13] = 'I';
646 | out[14] = 'R';
647 | out[15] = 'M';
648 | uint8_t lengthSum = 17;
649 | uint8_t vlen = sprintf_P(out + lengthSum, firmwareVersion.c_str());
650 | out[16] = vlen;
651 | lengthSum += vlen;
652 | uint8_t hlen = 7;
653 | #ifdef ESP8266
654 | strcpy(out + lengthSum + 1, "esp8266");
655 | #else
656 | hlen = 5;
657 | strcpy(out + lengthSum + 1, "esp32");
658 | #endif
659 | out[lengthSum] = hlen;
660 | lengthSum += hlen + 1;
661 | //Use serverDescription if it has been changed from the default "FIRM", else mDNS name
662 | bool useMdnsName = (strcmp(serverDescription, "FIRM") == 0 && strlen(cmDNS) > 0);
663 | strcpy(out + lengthSum + 1, useMdnsName ? cmDNS : serverDescription);
664 | uint8_t nlen = strlen(useMdnsName ? cmDNS : serverDescription);
665 | out[lengthSum] = nlen;
666 | lengthSum += nlen + 1;
667 | packetLen = lengthSum + 1;
668 | out[8] = lengthSum - 9;
669 | out[10] = lengthSum - 11;
670 | uint8_t checksum = 0;
671 | for (uint8_t i = 0; i < packetLen - 1; i++) checksum += out[i];
672 | out[packetLen - 1] = checksum;
673 | Serial.write((uint8_t *) out, packetLen);
674 | Serial.write('\n');
675 | Serial.flush();
676 | DIMPROV_PRINT("Info checksum");
677 | DIMPROV_PRINTLN(checksum);
678 | }
679 |
680 | void WifiManager::parseWiFiCommand(char *rpcData) {
681 | uint8_t len = rpcData[0];
682 | if (!len || len > 126) return;
683 | uint8_t ssidLen = rpcData[1];
684 | if (ssidLen > len - 1 || ssidLen > 32) return;
685 | memset(clientSSID, 0, 32);
686 | memcpy(clientSSID, rpcData + 2, ssidLen);
687 | memset(clientPass, 0, 64);
688 | if (len > ssidLen + 1) {
689 | uint8_t passLen = rpcData[2 + ssidLen];
690 | memset(clientPass, 0, 64);
691 | memcpy(clientPass, rpcData + 3 + ssidLen, passLen);
692 | }
693 | sendImprovStateResponse(0x03, false); //provisioning
694 | improvActive = 2;
695 | JsonDocument doc;
696 | String devName = String(random(0, 90000));
697 | doc["deviceName"] = String(DEVICE_NAME) + "_" + devName;
698 | doc["microcontrollerIP"] = "DHCP";
699 | doc["qsid"] = clientSSID;
700 | doc["qpass"] = clientPass;
701 | doc["OTApass"] = "";
702 | doc["mqttIP"] = "";
703 | doc["mqttPort"] = "";
704 | doc["mqttuser"] = "";
705 | doc["mqttpass"] = "";
706 | additionalParam = "2";
707 | Serial.println(F("Saving setup.json"));
708 | File jsonFile = LittleFS.open("/setup.json", FILE_WRITE);
709 | if (!jsonFile) {
710 | Serial.println("Failed to open [setup.json] file for writing");
711 | } else {
712 | serializeJsonPretty(doc, Serial);
713 | serializeJson(doc, jsonFile);
714 | jsonFile.close();
715 | }
716 | delay(DELAY_200);
717 | sendImprovRPCResponse(ImprovRPCType::Request_State);
718 | delay(DELAY_200);
719 | sendImprovStateResponse(0x04, false);
720 | delay(DELAY_200);
721 | #if defined(ARDUINO_ARCH_ESP32)
722 | ESP.restart();
723 | #elif defined(ESP8266)
724 | EspClass::restart();
725 | #endif
726 | }
727 |
728 | //blocking function to parse an Improv Serial packet
729 | void WifiManager::handleImprovPacket() {
730 | uint8_t header[6] = {'I', 'M', 'P', 'R', 'O', 'V'};
731 | bool timeout = false;
732 | uint16_t packetByte = 0;
733 | uint8_t packetLen = 9;
734 | uint8_t checksum = 0;
735 | uint8_t waitTime = 255;
736 | uint8_t rpcCommandType = 0;
737 | char rpcData[128];
738 | rpcData[0] = 0;
739 | while (!timeout) {
740 | if (Serial.available() < 1) {
741 | delay(1);
742 | waitTime--;
743 | if (!waitTime) timeout = true;
744 | continue;
745 | }
746 | byte next = Serial.read();
747 | DIMPROV_PRINT("Received improv byte: "); DIMPROV_PRINTF("%x\r\n", next);
748 | switch (packetByte) {
749 | case ImprovPacketByte::Version: {
750 | if (next != IMPROV_VERSION) {
751 | DIMPROV_PRINTLN(F("Invalid version"));
752 | return;
753 | }
754 | break;
755 | }
756 | case ImprovPacketByte::PacketType: {
757 | if (next != ImprovPacketType::RPC_Command) {
758 | DIMPROV_PRINTF("Non RPC-command improv packet type %i\n", next);
759 | return;
760 | }
761 | if (!improvActive) {
762 | improvActive = 1;
763 | improvePacketReceived = true;
764 | }
765 | break;
766 | }
767 | case ImprovPacketByte::Length:
768 | packetLen = 9 + next;
769 | break;
770 | case ImprovPacketByte::RPC_CommandType:
771 | rpcCommandType = next;
772 | break;
773 | default: {
774 | if (packetByte >= packetLen) { //end of packet, check checksum match
775 | if (checksum != next) {
776 | DIMPROV_PRINTF("Got RPC checksum %i, expected %i", next, checksum);
777 | sendImprovStateResponse(0x01, true);
778 | return;
779 | }
780 | switch (rpcCommandType) {
781 | case ImprovRPCType::Command_Wifi:
782 | parseWiFiCommand(rpcData);
783 | break;
784 | case ImprovRPCType::Request_State: {
785 | uint8_t improvState = 0x02; //authorized
786 | if (isWifiConfigured()) improvState = 0x03; //provisioning
787 | if (WiFi.localIP()[0] != 0 && WiFi.status() == WL_CONNECTED) improvState = 0x04; //provisioned
788 | sendImprovStateResponse(improvState, false);
789 | if (improvState == 0x04) sendImprovRPCResponse(ImprovRPCType::Request_State);
790 | break;
791 | }
792 | case ImprovRPCType::Request_Info:
793 | sendImprovInfoResponse();
794 | break;
795 | default: {
796 | DIMPROV_PRINTF("Unknown RPC command %i\n", next);
797 | sendImprovStateResponse(0x02, true);
798 | }
799 | }
800 | return;
801 | }
802 | if (packetByte < 6) { //check header
803 | if (next != header[packetByte]) {
804 | DIMPROV_PRINTLN(F("Invalid improv header"));
805 | return;
806 | }
807 | } else if (packetByte > 9) { //RPC data
808 | rpcData[packetByte - 10] = next;
809 | if (packetByte > 137) return; //prevent buffer overflow
810 | }
811 | }
812 | }
813 | checksum += next;
814 | packetByte++;
815 | }
816 | }
817 |
818 |
819 |
--------------------------------------------------------------------------------
/src/WifiManager.h:
--------------------------------------------------------------------------------
1 | /*
2 | WifiManager.h - Managing Wifi and OTA
3 |
4 | Copyright © 2020 - 2025 Davide Perini
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | You should have received a copy of the MIT License along with this program.
17 | If not, see .
18 | */
19 |
20 | #ifndef _DPSOFTWARE_WIFI_MANAGER_H
21 | #define _DPSOFTWARE_WIFI_MANAGER_H
22 |
23 | #if defined(ESP8266)
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 | #elif defined(ARDUINO_ARCH_ESP32)
30 |
31 | #include
32 | #include
33 | #include
34 | #include
35 | #include
36 |
37 | #endif
38 |
39 | #include
40 | #include
41 | #include
42 | #include "Helpers.h"
43 | #include "Secrets.h"
44 | #include "Configuration.h"
45 |
46 | //Establishing Local server at port 80 whenever required
47 | #if defined(ESP8266)
48 | extern ESP8266WebServer server;
49 | #elif defined(ARDUINO_ARCH_ESP32)
50 | extern WebServer server;
51 | #endif
52 | // WiFi Client
53 | extern WiFiClient espClient;
54 | // WebServer content
55 | extern String content;
56 | // WebServer status code
57 | extern int statusCode;
58 | // WebServer HTML frontend
59 | extern String htmlString;
60 |
61 | #ifdef WLED_DEBUG_IMPROV
62 | #define DIMPROV_PRINT(x) Serial.print(x)
63 | #define DIMPROV_PRINTLN(x) Serial.println(x)
64 | #define DIMPROV_PRINTF(x...) Serial.printf(x)
65 | #else
66 | #define DIMPROV_PRINT(x)
67 | #define DIMPROV_PRINTLN(x)
68 | #define DIMPROV_PRINTF(x...)
69 | #endif
70 | #define IMPROV_VERSION 1
71 |
72 | [[maybe_unused]] void parseWiFiCommand(char *rpcData);
73 |
74 | enum ImprovPacketType {
75 | Current_State = 0x01,
76 | Error_State = 0x02,
77 | RPC_Command = 0x03,
78 | RPC_Response = 0x04
79 | };
80 |
81 | enum ImprovPacketByte {
82 | Version = 6,
83 | PacketType = 7,
84 | Length = 8,
85 | RPC_CommandType = 9
86 | };
87 |
88 | enum ImprovRPCType {
89 | Command_Wifi = 0x01,
90 | Request_State = 0x02,
91 | Request_Info = 0x03
92 | };
93 |
94 | extern byte improvActive; //0: no improv packet received, 1: improv active, 2: provisioning
95 | extern byte improvError;
96 | extern char serverDescription[33];
97 | extern char cmDNS[33];
98 | extern char clientSSID[33];
99 | extern char clientPass[65];
100 |
101 | class WifiManager {
102 |
103 | private:
104 | Helpers helper;
105 |
106 | static void createWebServer();
107 |
108 | static void setupAP();
109 |
110 | static void launchWeb();
111 |
112 | public:
113 | void setupWiFi(void (*manageDisconnections)(), void (*manageHardwareButton)());
114 |
115 | void reconnectToWiFi(void (*manageDisconnections)(), void (*manageHardwareButton)());
116 |
117 | static void setupOTAUpload();
118 |
119 | static int getQuality();
120 |
121 | static bool isWifiConfigured(); // check if wifi is correctly configured
122 | static void launchWebServerForOTAConfig(); // if no ssid available, launch web server to get config params via browser
123 | static void launchWebServerCustom(void (*listener)()); // if no ssid available, launch web server to get config params via browser
124 | void manageImprovWifi(); // if no ssid available, launch web server to get config params via browser
125 | void handleImprovPacket();
126 |
127 | void sendImprovInfoResponse();
128 |
129 | void parseWiFiCommand(char *rpcData);
130 |
131 | void sendImprovRPCResponse(byte commandId);
132 |
133 | void sendImprovRPCResponse(byte commandId, bool forceConnection);
134 |
135 | void sendImprovStateResponse(uint8_t state, bool error);
136 |
137 | static bool isConnected(); // return true if wifi is connected
138 |
139 | void setTxPower() const;
140 | };
141 |
142 | #endif
--------------------------------------------------------------------------------