@@ -26,6 +26,263 @@ The idea is not new: [Tasmota also uses a SafeBoot partition](https://tasmota.gi
26
26
- [ Options matrix] ( #options-matrix )
27
27
- [ Default board options] ( #default-board-options )
28
28
29
+ ![ ] ( https://private-user-images.githubusercontent.com/61346/426535795-7eda5f6e-7900-4380-921f-8e54fb2b2e2c.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDI5MDgwOTEsIm5iZiI6MTc0MjkwNzc5MSwicGF0aCI6Ii82MTM0Ni80MjY1MzU3OTUtN2VkYTVmNmUtNzkwMC00MzgwLTkyMWYtOGU1NGZiMmIyZTJjLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAzMjUlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMzI1VDEzMDMxMVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPThkNTVjMTJjNzZmNzdiYWZkZjkxZWE4ZTkxOWM4MTQ1MzliZTFhNzRkZmU5NzY5MTk2MWJmMjQyYzJiN2Y1OTAmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.7vsJcZDQrAa4Z_G4R663ENGhQDNTleVThGd6x8GAnGo )
30
+
31
+ ## Overview
32
+
33
+ Usually, a normal partition table when supporting OTA updates on a 4MB ESP32 looks like this:
34
+
35
+ ```
36
+ # Name, Type, SubType, Offset, Size, Flags
37
+ nvs, data, nvs, 0x9000, 0x5000,
38
+ otadata, data, ota, 0xE000, 0x2000,
39
+ app0, app, ota_0, 0x10000, 0x1F0000,
40
+ app1, app, ota_1, 0x200000, 0x1F0000,
41
+ spiffs, data, spiffs, 0x3F0000, 0x10000,
42
+ ```
43
+
44
+ which can also be written as:
45
+
46
+ ```
47
+ # Name ,Type ,SubType ,Offset ,Size ,Flags
48
+ nvs ,data ,nvs ,36K ,20K ,
49
+ otadata ,data ,ota ,56K ,8K ,
50
+ app0 ,app ,ota_0 ,64K ,1984K ,
51
+ app1 ,app ,ota_1 ,2048K ,1984K ,
52
+ spiffs ,data ,spiffs ,4032K ,64K ,
53
+ ```
54
+
55
+ Because of the need to have 2 partitions with the same size, the firmware is then limited to only 2MB in this case when the ESP has 4MB flash.
56
+ 2MB is left unused (the OTA process will switch to the updated partition once completed).
57
+
58
+ ** A SafeBoot partition is a small bootable recovery partition allowing you to flash the firmware.**
59
+ Consequently, the firmware can take all the remaining space on the flash.
60
+
61
+ ** The SafeBoot partition is 655360 bytes only.**
62
+
63
+ ** Example for 4MB partition** with a SafeBoot partition and an application size of 3MB:
64
+
65
+ ```
66
+ # Name, Type, SubType, Offset, Size, Flags
67
+ nvs, data, nvs, 0x9000, 0x5000,
68
+ otadata, data, ota, 0xE000, 0x2000,
69
+ safeboot, app, factory, 0x10000, 0xA0000,
70
+ app, app, ota_0, 0xB0000, 0x330000,
71
+ spiffs, data, spiffs, 0x3E0000, 0x10000,
72
+ coredump, data, coredump, 0x3F0000, 0x10000,
73
+ ```
74
+
75
+ which can also be written as:
76
+
77
+ ```
78
+ # Name ,Type ,SubType ,Offset ,Size ,Flags
79
+ nvs ,data ,nvs ,36K ,20K ,
80
+ otadata ,data ,ota ,56K ,8K ,
81
+ safeboot ,app ,factory ,64K ,640K ,
82
+ app ,app ,ota_0 ,704K ,3264K ,
83
+ spiffs ,data ,spiffs ,3968K ,64K ,
84
+ coredump ,data ,coredump ,4032K ,64K ,
85
+ ```
86
+
87
+ ** Example for 8Mb partition** with a SafeBoot partition and an application size of 7MB:
88
+
89
+ ```
90
+ # Name, Type, SubType, Offset, Size, Flags
91
+ nvs, data, nvs, 0x9000, 0x5000,
92
+ otadata, data, ota, 0xE000, 0x2000,
93
+ safeboot, app, factory, 0x10000, 0xA0000,
94
+ app, app, ota_0, 0xB0000, 0x730000,
95
+ spiffs data, spiffs, 0x7E0000, 0x10000,
96
+ coredump, data, coredump, 0x7F0000, 0x10000,
97
+ ```
98
+
99
+ which can also be written as:
100
+
101
+ ```
102
+ # Name ,Type ,SubType ,Offset ,Size ,Flags
103
+ nvs ,data ,nvs ,36K ,20K ,
104
+ otadata ,data ,ota ,56K ,8K ,
105
+ safeboot ,app ,factory ,64K ,640K ,
106
+ app ,app ,ota_0 ,704K ,7312K ,
107
+ spiffs ,data ,spiffs ,8128K ,64K ,
108
+ coredump ,data ,coredump ,8192K ,64K ,
109
+ ```
110
+
111
+ The SafeBoot partition is also automatically booted when the firmware is missing.
112
+
113
+ ## How it works
114
+
115
+ 1 . When a user wants to update the app firmware, we have to tell the app to reboot in recovery mode.
116
+
117
+ 2 . Once booted in recovery mode, an Access Point is created with the SSID ` SafeBoot ` .
118
+
119
+ [ ![ ] ( https://mathieu.carbou.me/MycilaSafeBoot/safeboot-ssid.jpeg )] ( https://mathieu.carbou.me/MycilaSafeBoot/safeboot-ssid.jpeg )
120
+
121
+ 3 . Connect to the Access Point.
122
+
123
+ 4 . Now, you can flash the new firmware, either with ` ArduinoOTA ` or from the web page by going to ` http://192.168.4.1 `
124
+
125
+ 5 . After the flash is successful, the ESP will reboot in the new firmware.
126
+
127
+ SafeBoot partition also supports [ MycilaESPConnect] ( https://github.com/mathieucarbou/MycilaESPConnect ) , which means if your application saves some network settings (WiFi SSID, Ethernet or WiFi static IP, etc), they will be reused.
128
+
129
+ ## How to integrate the SafeBoot in your project
130
+
131
+ In the PIO file, some settings are added to specify the partition table and the SafeBoot location and the script to generate the factory image.
132
+
133
+ ``` ini
134
+ extra_scripts = post:factory.py
135
+ board_build.partitions = partitions-4MB-safeboot.csv
136
+ board_build.app_partition_name = app
137
+ custom_safeboot_url = https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-esp32dev.bin
138
+ ```
139
+
140
+ It is also possible to point to a folder if you download the SafeBoot project locally:
141
+
142
+ ``` ini
143
+ custom_safeboot_dir = ../../tools/SafeBoot
144
+ ```
145
+
146
+ It is also possible to point to a pre-downloaded safeoot image:
147
+
148
+ ``` ini
149
+ custom_safeboot_file = safeboot.bin
150
+ ```
151
+
152
+ You can find in the [ Project Releases] ( https://github.com/mathieucarbou/MycilaSafeBoot/releases ) the list of available SafeBoot images, with the Python script to add to your build.
153
+
154
+ ## How to build the SafeBoot firmware image
155
+
156
+ Go inside ` tools/SafeBoot ` and run:
157
+
158
+ ``` bash
159
+ > pio run -e esp32dev
160
+ ```
161
+
162
+ If your board does not exist, you can specify it like this:
163
+
164
+ ``` bash
165
+ > SAFEBOOT_BOARD=my-board pio run -e safeboot
166
+ ```
167
+
168
+ ` SAFEBOOT_BOARD ` is the environment variable to specify the board to build the SafeBoot firmware for.
169
+
170
+ At the end you should see these lines:
171
+
172
+ ```
173
+ Firmware size valid: 619744 <= 655360
174
+ SafeBoot firmware created: /Users/mat/Data/Workspace/me/MycilaSafeBoot/.pio/build/dev/safeboot.bin
175
+ ```
176
+
177
+ ## SafeBoot Example
178
+
179
+ Go inside ` examples/App ` and execute:
180
+
181
+ ``` bash
182
+ > pio run
183
+ ```
184
+
185
+ You should see at the end of the build something like:
186
+
187
+ ```
188
+ Generating factory image for serial flashing
189
+ Downloading SafeBoot image from https://github.com/mathieucarbou/MycilaSafeBoot/releases/download/latest/safeboot-esp32dev.bin
190
+ Offset | File
191
+ - 0x1000 | /Users/mat/Data/Workspace/me/MycilaSafeBoot/examples/App/.pio/build/esp32dev/bootloader.bin
192
+ - 0x8000 | /Users/mat/Data/Workspace/me/MycilaSafeBoot/examples/App/.pio/build/esp32dev/partitions.bin
193
+ - 0xe000 | /Users/mat/.platformio/packages/framework-arduinoespressif32@src-17df1753722b7b9e1913598420d4e038/tools/partitions/boot_app0.bin
194
+ - 0x10000 | /Users/mat/Data/Workspace/me/MycilaSafeBoot/examples/App/.pio/build/esp32dev/safeboot.bin
195
+ - 0xb0000 | /Users/mat/Data/Workspace/me/MycilaSafeBoot/examples/App/.pio/build/esp32dev/firmware.bin
196
+
197
+ [...]
198
+
199
+ Wrote 0x1451a0 bytes to file /Users/mat/Data/Workspace/me/MycilaSafeBoot/examples/App/.pio/build/esp32dev/firmware.factory.bin, ready to flash to offset 0x0
200
+ Factory image generated: /Users/mat/Data/Workspace/me/MycilaSafeBoot/examples/App/.pio/build/esp32dev/firmware.factory.bin
201
+ ```
202
+
203
+ the ` factory.py ` script generates a complete factory image named ` firmware.factory.bin ` with all this content.
204
+
205
+ Flash this factory image on an ESP32:
206
+
207
+ ``` bash
208
+ esptool.py write_flash 0x0 .pio/build/esp32dev/firmware.factory.bin
209
+ ```
210
+
211
+ Restart the ESP.
212
+ The app loads, shows a button to restart in SafeBoot mode.
213
+ After clicking on it, the ESP will reboot into SafeBoot mode.
214
+ From there, you can access the web page to flash a new firmware, even from another application.
215
+
216
+ ## How to reboot in SafeBoot mode from the app
217
+
218
+ You can use [ MycilaSystem] ( https://github.com/mathieucarbou/MycilaSystem ) :
219
+
220
+ ``` cpp
221
+ #include < MycilaSystem.h>
222
+
223
+ espConnect.saveConfiguration(); // if you want to save ESPConnect settings for network
224
+ Mycila::System::restartFactory ("safeboot");
225
+ ```
226
+
227
+ or this custom code:
228
+
229
+ ```cpp
230
+ #include <esp_ota_ops.h>
231
+ #include <esp_partition.h>
232
+
233
+ const esp_partition_t* partition = esp_partition_find_first(esp_partition_type_t::ESP_PARTITION_TYPE_APP, esp_partition_subtype_t::ESP_PARTITION_SUBTYPE_APP_FACTORY, partitionName);
234
+ if (partition) {
235
+ esp_ota_set_boot_partition(partition);
236
+ esp_restart();
237
+ return true;
238
+ } else {
239
+ ESP_LOGE("SafeBoot", "SafeBoot partition not found");
240
+ return false;
241
+ }
242
+ ```
243
+
244
+ ## Configuration options to manage build size
245
+
246
+ Squezing everything into the SafeBoot partition (655360 bytes only) is a tight fit especially on ethernet enabled boards.
247
+
248
+ Disabling the logging capabilites saves about 12 kbytes in the final build. Just comment out ` SAFEBOOT_LOGGING ` in ` platformio.ini ` .
249
+
250
+ ``` ini
251
+ ; -D SAFEBOOT_LOGGING
252
+ ```
253
+
254
+ Disabling mDNS saves about 24 kbytes. Enable both [ ...] \_ NO_DNS options in ` platformio.ini ` to reduce the build size:
255
+
256
+ ``` ini
257
+ -D ESPCONNECT_NO_MDNS
258
+ -D MYCILA_SAFEBOOT_NO_MDNS# MycilaSafeBoot
259
+
260
+ [![Latest Release] (https://img.shields.io/github/release/mathieucarbou/MycilaSafeBoot.svg)](https://GitHub.com/mathieucarbou/MycilaSafeBoot/releases/)
261
+ [![Download] (https://img.shields.io/badge/Download-safeboot-green.svg)](https://github.com/mathieucarbou/MycilaSafeBoot/releases)
262
+
263
+ [![License: MIT] (https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
264
+ [![Contributor Covenant] (https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](code_of_conduct.md)
265
+
266
+ [![Build] (https://github.com/mathieucarbou/MycilaSafeBoot/actions/workflows/build.yml/badge.svg)](https://github.com/mathieucarbou/MycilaSafeBoot/actions/workflows/build.yml)
267
+ [![GitHub latest commit] (https://badgen.net/github/last-commit/mathieucarbou/MycilaSafeBoot)](https://GitHub.com/mathieucarbou/MycilaSafeBoot/commit/)
268
+ [![Gitpod Ready-to-Code] (https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo =gitpod)](https://gitpod.io/# https://github.com/mathieucarbou/MycilaSafeBoot)
269
+
270
+ MycilaSafeBoot is a Web OTA recovery partition for ESP32 / Arduino.
271
+
272
+ It allows to have only one application partition to use the maximum available flash size.
273
+
274
+ The idea is not new: [Tasmota also uses a SafeBoot partition](https://tasmota.github.io/docs/Safeboot/).
275
+
276
+ - [Overview](# overview)
277
+ - [How it works](# how-it-works)
278
+ - [How to integrate the SafeBoot in your project](# how-to-integrate-the-safeboot-in-your-project)
279
+ - [How to build the SafeBoot firmware image](# how-to-build-the-safeboot-firmware-image)
280
+ - [SafeBoot Example](# safeboot-example)
281
+ - [How to reboot in SafeBoot mode from the app](# how-to-reboot-in-safeboot-mode-from-the-app)
282
+ - [Configuration options to manage build size](# configuration-options-to-manage-build-size)
283
+ - [Options matrix](# options-matrix)
284
+ - [Default board options](# default-board-options)
285
+
29
286
[![] (https://mathieu.carbou.me/MycilaSafeBoot/safeboot-ssid.jpeg)](https://mathieu.carbou.me/MycilaSafeBoot/safeboot-ssid.jpeg)
30
287
31
288
# # Overview
@@ -301,3 +558,49 @@ Disabling mDNS saves about 24 kbytes. Enable both [...]\_NO_DNS options in `plat
301
558
| wemos_d1_uno32 | ✅ | ✅ | ❌ |
302
559
| wipy3 | ✅ | ❌ | ❌ |
303
560
| wt32-eth01 | ❌ | ❌ | ✅ |
561
+
562
+ ```
563
+
564
+ ### Options matrix
565
+
566
+ | Board | mDNS: on, logger: on | mDNS: on, logger: off | mDNS: off, logger: off |
567
+ | -------------------- | -------------------- | --------------------- | ---------------------- |
568
+ | denky_d4 | NOT SUPPORTED | OK | OK |
569
+ | esp32-c3-devkitc-02 | OK | OK | OK |
570
+ | esp32-c6-devkitc-1 | NOT SUPPORTED | NOT SUPPORTED | OK |
571
+ | esp32-gateway | NOT SUPPORTED | OK | OK |
572
+ | esp32-poe | NOT SUPPORTED | NOT SUPPORTED | OK |
573
+ | esp32-poe-iso | NOT SUPPORTED | NOT SUPPORTED | OK |
574
+ | esp32-s2-saola-1 | OK | OK | OK |
575
+ | esp32-s3-devkitc-1 | OK | OK | OK |
576
+ | esp32-solo1 | OK | OK | OK |
577
+ | esp32dev | OK | OK | OK |
578
+ | esp32s3box | OK | OK | OK |
579
+ | lilygo-t-eth-lite-s3 | OK | OK | OK |
580
+ | lolin_s2_mini | OK | OK | OK |
581
+ | tinypico | NOT SUPPORTED | OK | OK |
582
+ | wemos_d1_uno32 | OK | OK | OK |
583
+ | wipy3 | NOT SUPPORTED | OK | OK |
584
+ | wt32-eth01 | NOT SUPPORTED | NOT SUPPORTED | OK |
585
+
586
+ ## Default board options
587
+
588
+ | Board | mDNS | Logging |
589
+ | :------------------- | :--: | :-----: |
590
+ | denky_d4 | ✅ | ❌ |
591
+ | esp32-c3-devkitc-02 | ✅ | ✅ |
592
+ | esp32-c6-devkitc-1 | ❌ | ❌ |
593
+ | esp32-gateway | ✅ | ❌ |
594
+ | esp32-poe | ❌ | ❌ |
595
+ | esp32-poe-iso | ❌ | ❌ |
596
+ | esp32-s2-saola-1 | ✅ | ✅ |
597
+ | esp32-s3-devkitc-1 | ✅ | ✅ |
598
+ | esp32-solo1 | ✅ | ✅ |
599
+ | esp32dev | ✅ | ✅ |
600
+ | esp32s3box | ✅ | ✅ |
601
+ | lilygo-t-eth-lite-s3 | ✅ | ✅ |
602
+ | lolin_s2_mini | ✅ | ✅ |
603
+ | tinypico | ✅ | ❌ |
604
+ | wemos_d1_uno32 | ✅ | ✅ |
605
+ | wipy3 | ✅ | ❌ |
606
+ | wt32-eth01 | ❌ | ❌ |
0 commit comments