Skip to content

Commit b1eebd5

Browse files
committed
Added script to activate safeboot & example
- Added a script (tools/safeboot_activate.py) the triggers a restart into safeboot and allows for OTA within platformIO. - Added an example including ESPConnect & OTA within platformIO The easiest way to use the example is to: - open the folder examples/App_ESPConnect_OTA in Visual Studio Code - Build a factory firmware using the non-OTA environment - upload the factory firmware - connect to the access point with SSID MyAwesomeApp - select a WiFi-network within the Captive Portal - switch to the OTA environment - then try to upload just by hitting the upload button (which is the triggering the safeboot_activate.py script, restarts the esp in safeboot-mode, which is exposing the OTA-port, and Platformio can upload the code over your network)
1 parent 23d740d commit b1eebd5

File tree

8 files changed

+294
-0
lines changed

8 files changed

+294
-0
lines changed
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.DS_Store
2+
/.pio
3+
/.vscode
4+
/logs

examples/App_ESPConnect_OTA/LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2023-2024 Mathieu Carbou
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.

examples/App_ESPConnect_OTA/README.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# SafeBoot Example
2+
3+
Please refer to the SafeBoot tool documentation
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Name ,Type ,SubType ,Offset ,Size ,Flags
2+
nvs ,data ,nvs ,36K ,20K ,
3+
otadata ,data ,ota ,56K ,8K ,
4+
safeboot ,app ,factory ,64K ,640K ,
5+
app ,app ,ota_0 ,704K ,3264K ,
6+
spiff ,data ,spiffs ,3968K ,64K ,
7+
coredump ,data ,coredump ,4032K ,64K ,
+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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+
name = MyAwesomeApp
13+
default_envs = esp32dev, lolin_s2_mini
14+
15+
[env]
16+
platform = https://github.com/pioarduino/platform-espressif32/releases/download/54.03.20-rc1/platform-espressif32.zip
17+
framework = arduino
18+
monitor_filters = esp32_exception_decoder, log2file
19+
monitor_speed = 115200
20+
upload_protocol = esptool
21+
; After initial flashing of the [..].factory.bin, espota can be used for uploading the app
22+
; upload_protocol = espota
23+
; upload_port = MyAwesomeApp.local
24+
lib_compat_mode = strict
25+
lib_ldf_mode = chain
26+
lib_deps =
27+
ESP32Async/AsyncTCP @ 3.3.8
28+
ESP32Async/ESPAsyncWebServer @ 3.7.4
29+
mathieucarbou/MycilaESPConnect @ 9.0.1
30+
mathieucarbou/MycilaSystem @ 4.1.0
31+
build_flags =
32+
-D APP_NAME=\"MyAwesomeApp\"
33+
-Wall -Wextra
34+
-std=c++17
35+
-std=gnu++17
36+
build_unflags =
37+
-std=gnu++11
38+
extra_scripts = post:../../tools/factory.py
39+
board_build.partitions = partitions-4MB-safeboot.csv
40+
board_build.app_partition_name = app
41+
# custom_safeboot_dir = ../..
42+
; custom_safeboot_file = safeboot-esp32dev.bin
43+
44+
; --------------------------------------------------------------------
45+
; ENVIRONMENTs
46+
; --------------------------------------------------------------------
47+
48+
; environment without OTA
49+
[env:esp32dev]
50+
board = esp32dev
51+
custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-esp32dev.bin
52+
53+
; After initial flashing of the [..].factory.bin, espota can be used for uploading the app
54+
[env:esp32dev-ota]
55+
board = esp32dev
56+
extra_scripts = ${env.extra_scripts}
57+
../../tools/safeboot_activate.py
58+
59+
; environment without OTA
60+
[env:lolin_s2_mini]
61+
board = lolin_s2_mini
62+
custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-lolin_s2_mini.bin
63+
64+
; After initial flashing of the [..].factory.bin, espota can be used for uploading the app
65+
[env:lolin_s2_mini-ota]
66+
board = lolin_s2_mini
67+
upload_protocol = espota
68+
; enter mdns-name of the target here
69+
upload_port = MyAwesomeApp.local
70+
extra_scripts = ${env.extra_scripts}
71+
../../tools/safeboot_activate.py
+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// SPDX-License-Identifier: MIT
2+
/*
3+
* Copyright (C) 2023-2025 Mathieu Carbou
4+
*/
5+
#include <MycilaESPConnect.h>
6+
#include <MycilaSystem.h>
7+
8+
AsyncWebServer webServer(80);
9+
Mycila::ESPConnect espConnect(webServer);
10+
11+
void setup() {
12+
Serial.begin(115200);
13+
#if ARDUINO_USB_CDC_ON_BOOT
14+
Serial.setTxTimeoutMs(0);
15+
delay(100);
16+
#else
17+
while (!Serial)
18+
yield();
19+
#endif
20+
21+
// reboot esp into SafeBoot
22+
webServer.on("/safeboot", HTTP_POST, [](AsyncWebServerRequest* request) {
23+
Serial.println("Restarting in SafeBoot mode...");
24+
request->send(200, "text/html", "<META http-equiv=\"refresh\" content=\"10;URL=/\">Restarting in SafeBoot mode...");
25+
Mycila::System::restartFactory("safeboot", 250);
26+
});
27+
28+
// network state listener is required here in async mode
29+
espConnect.listen([](__unused Mycila::ESPConnect::State previous, Mycila::ESPConnect::State state) {
30+
switch (state) {
31+
case Mycila::ESPConnect::State::NETWORK_CONNECTED:
32+
case Mycila::ESPConnect::State::AP_STARTED:
33+
// serve your home page here
34+
webServer.on("/", HTTP_GET, [&](AsyncWebServerRequest* request) {
35+
request->send(200, "text/html", "<h1>MyAwsomeApp</h1><br><form method='POST' action='/safeboot' enctype='multipart/form-data'><input type='submit' value='Restart in SafeBoot mode'></form>");
36+
})
37+
.setFilter([](__unused AsyncWebServerRequest* request) { return espConnect.getState() != Mycila::ESPConnect::State::PORTAL_STARTED; });
38+
webServer.begin();
39+
break;
40+
41+
case Mycila::ESPConnect::State::NETWORK_DISCONNECTED:
42+
webServer.end();
43+
break;
44+
45+
default:
46+
break;
47+
}
48+
});
49+
50+
espConnect.setAutoRestart(true);
51+
espConnect.setBlocking(false);
52+
53+
Serial.println("====> Trying to connect to saved WiFi or will start portal in the background...");
54+
55+
espConnect.begin(APP_NAME, APP_NAME);
56+
57+
Serial.println("====> setup() completed...");
58+
}
59+
60+
void loop() {
61+
espConnect.loop();
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#ifndef Pins_Arduino_h
2+
#define Pins_Arduino_h
3+
4+
#include <stdint.h>
5+
#include "soc/soc_caps.h"
6+
7+
#define USB_VID 0x303a
8+
#define USB_PID 0x1001
9+
#define USB_MANUFACTURER "Waveshare"
10+
#define USB_PRODUCT "ESP32-S3-Tiny"
11+
#define USB_SERIAL "" // Empty string for MAC address
12+
13+
#define MTDO 45
14+
#define MTDI 47
15+
#define MTMS 48
16+
#define MTCK 44
17+
18+
// UART0 pins
19+
static const uint8_t TX = 49;
20+
static const uint8_t RX = 50;
21+
22+
static const uint8_t SDA = -1;
23+
static const uint8_t SCL = -1;
24+
25+
// Mapping based on the ESP32S3 data sheet - alternate for SPI2
26+
static const uint8_t SS = 32; // FSPICS0
27+
static const uint8_t MOSI = 35; // FSPID
28+
static const uint8_t MISO = 34; // FSPIQ
29+
static const uint8_t SCK = 33; // FSPICLK
30+
31+
//static const uint8_t XTAL_32K_N = 22;
32+
//static const uint8_t XTAL_32K_P = 21;
33+
34+
//static const uint8_t SPIWP = 31;
35+
//static const uint8_t SPIHD = 30;
36+
37+
// Mapping based on the ESP32S3 data sheet - alternate for OUTPUT
38+
static const uint8_t GPIO1 = 1;
39+
static const uint8_t GPIO2 = 2;
40+
static const uint8_t GPIO3 = 3;
41+
static const uint8_t GPIO4 = 4;
42+
static const uint8_t GPIO5 = 5;
43+
static const uint8_t GPIO6 = 6;
44+
static const uint8_t GPIO7 = 7;
45+
static const uint8_t GPIO8 = 8;
46+
static const uint8_t GPIO9 = 9;
47+
static const uint8_t GPIO10 = 10;
48+
static const uint8_t GPIO11 = 11;
49+
static const uint8_t GPIO12 = 12;
50+
static const uint8_t GPIO13 = 13;
51+
static const uint8_t GPIO14 = 14;
52+
static const uint8_t GPIO15 = 15;
53+
static const uint8_t GPIO16 = 16;
54+
static const uint8_t GPIO17 = 17;
55+
static const uint8_t GPIO18 = 18;
56+
57+
static const uint8_t GPIO21 = 21;
58+
59+
static const uint8_t GPIO33 = 33;
60+
static const uint8_t GPIO34 = 34;
61+
static const uint8_t GPIO35 = 35;
62+
static const uint8_t GPIO36 = 36;
63+
static const uint8_t GPIO37 = 37;
64+
static const uint8_t GPIO38 = 38;
65+
static const uint8_t GPIO39 = 39;
66+
static const uint8_t GPIO40 = 40;
67+
static const uint8_t GPIO41 = 41;
68+
static const uint8_t GPIO42 = 42;
69+
70+
static const uint8_t GPIO45 = 45;
71+
72+
static const uint8_t GPIO47 = 47;
73+
static const uint8_t GPIO48 = 48;
74+
75+
static const uint8_t RGB_DATA = 38;
76+
77+
#define PIN_RGB_LED RGB_DATA
78+
// BUILTIN_LED can be used in new Arduino API digitalWrite() like in Blink.ino
79+
static const uint8_t LED_BUILTIN = SOC_GPIO_PIN_COUNT + PIN_RGB_LED;
80+
#define BUILTIN_LED LED_BUILTIN // backward compatibility
81+
#define LED_BUILTIN LED_BUILTIN // allow testing #ifdef LED_BUILTIN
82+
// RGB_BUILTIN and RGB_BRIGHTNESS can be used in new Arduino API rgbLedWrite
83+
#define RGB_BUILTIN LED_BUILTIN
84+
#define RGB_BRIGHTNESS 64
85+
#define RGB_BUILTIN_LED_COLOR_ORDER LED_COLOR_ORDER_RGB
86+
87+
#endif /* Pins_Arduino_h */

tools/safeboot_activate.py

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
Import("env")
2+
import sys
3+
import urllib.request
4+
5+
quiet = False
6+
7+
def status(msg):
8+
"""Print status message to stderr"""
9+
if not quiet:
10+
critical(msg)
11+
12+
def critical(msg):
13+
"""Print critical message to stderr"""
14+
sys.stderr.write("safeboot_activate.py: ")
15+
sys.stderr.write(msg)
16+
sys.stderr.write("\n")
17+
18+
# open "/safeboot" on target to restart in SafeBoot-mode
19+
def safeboot_activate(source, target, env):
20+
upload_protocol = env.GetProjectOption("upload_protocol")
21+
upload_port = env.GetProjectOption("upload_port")
22+
if upload_protocol != "espota":
23+
critical("Wrong upload protocol (%s)" % upload_protocol)
24+
raise Exception("Wrong upload protocol!")
25+
else:
26+
status("Trying to activate SafeBoot on: %s" % upload_port)
27+
req = urllib.request.Request('http://'+upload_port+'/safeboot', method='POST')
28+
try:
29+
urllib.request.urlopen(req)
30+
except urllib.error.URLError as e:
31+
critical(e)
32+
# Raise exception when SafeBoot cannot be activated
33+
pass
34+
35+
status("Activated SafeBoot on: %s" % upload_port)
36+
37+
env.AddPreAction("upload", safeboot_activate)
38+
env.AddPreAction("uploadfs", safeboot_activate)
39+
env.AddPreAction("uploadfsota", safeboot_activate)

0 commit comments

Comments
 (0)