Skip to content

Commit 6aa7c5e

Browse files
authored
feature: Allow http usage on get function. (#32)
1 parent e45eaba commit 6aa7c5e

6 files changed

Lines changed: 41371 additions & 40977 deletions

File tree

webscreen/binary_loader.h

Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
/**
2+
* @file binary_loader.h
3+
* @brief ESP32 binary firmware loader from SD card
4+
*
5+
* @details
6+
* This module enables loading and flashing compiled ESP32 firmware (.bin files)
7+
* from SD card to the OTA (Over-The-Air) partition, then rebooting into the
8+
* new firmware.
9+
*
10+
* Features:
11+
* - OTA update from SD card instead of network
12+
* - Binary validation and CRC checking
13+
* - Partition management
14+
* - Automatic reboot after successful flash
15+
* - Error recovery to current firmware on failure
16+
*
17+
* Use Case:
18+
* Store multiple compiled applications on SD card and switch between them
19+
* via webscreen.json configuration without recompiling or reflashing via USB.
20+
*
21+
* Example webscreen.json:
22+
* @code{.json}
23+
* {
24+
* "settings": { "wifi": {...}, "mqtt": {...} },
25+
* "screen": { "background": "#000000", "foreground": "#FFFFFF" },
26+
* "bin": "/apps/sensor_dashboard.bin" // Load this binary on boot
27+
* }
28+
* @endcode
29+
*
30+
* @warning Flashing incorrect binary can brick the device - ensure binary
31+
* is compiled for the correct ESP32 variant and partition scheme
32+
* @note Requires sufficient OTA partition space in partition table
33+
* @note Original firmware can be restored by removing "bin" from webscreen.json
34+
* and power cycling the device
35+
*/
36+
37+
#ifndef BINARY_LOADER_H
38+
#define BINARY_LOADER_H
39+
40+
/******************************************************************************
41+
* Includes
42+
******************************************************************************/
43+
#include <Arduino.h>
44+
#include <SD_MMC.h>
45+
#include <Update.h>
46+
#include <esp_ota_ops.h>
47+
#include <esp_partition.h>
48+
49+
/******************************************************************************
50+
* Configuration
51+
******************************************************************************/
52+
53+
#define BINARY_LOADER_BUFFER_SIZE 4096 ///< Buffer size for SD read operations
54+
#define BINARY_LOADER_MAX_SIZE (2 * 1024 * 1024) ///< Max binary size: 2MB
55+
56+
/******************************************************************************
57+
* Error Codes
58+
******************************************************************************/
59+
60+
typedef enum {
61+
BINARY_LOADER_OK = 0, ///< Success
62+
BINARY_LOADER_ERR_FILE_NOT_FOUND, ///< Binary file not found on SD
63+
BINARY_LOADER_ERR_FILE_TOO_LARGE, ///< Binary exceeds maximum size
64+
BINARY_LOADER_ERR_FILE_READ, ///< Failed to read from SD card
65+
BINARY_LOADER_ERR_OTA_BEGIN, ///< Failed to begin OTA update
66+
BINARY_LOADER_ERR_OTA_WRITE, ///< Failed to write to OTA partition
67+
BINARY_LOADER_ERR_OTA_END, ///< Failed to finalize OTA update
68+
BINARY_LOADER_ERR_VALIDATION, ///< Binary validation failed
69+
BINARY_LOADER_ERR_NO_OTA_PARTITION, ///< No OTA partition available
70+
} binary_loader_error_t;
71+
72+
/******************************************************************************
73+
* Public Functions
74+
******************************************************************************/
75+
76+
/**
77+
* @brief Load and flash binary firmware from SD card
78+
*
79+
* @details
80+
* This function performs the following steps:
81+
* 1. Opens binary file from SD card
82+
* 2. Validates file size
83+
* 3. Initiates OTA update process
84+
* 4. Reads binary in chunks and writes to OTA partition
85+
* 5. Validates written data
86+
* 6. Sets new boot partition
87+
* 7. Reboots into new firmware
88+
*
89+
* @param bin_path Path to .bin file on SD card (e.g., "/apps/my_app.bin")
90+
* @return Error code (BINARY_LOADER_OK on success)
91+
*
92+
* @note This function will reboot the device if successful
93+
* @note On failure, device continues running current firmware
94+
*
95+
* Example:
96+
* @code{.cpp}
97+
* binary_loader_error_t err = binary_loader_load_and_flash("/apps/sensor.bin");
98+
* if (err == BINARY_LOADER_OK) {
99+
* // This code won't run - device will reboot
100+
* } else {
101+
* Serial.printf("Failed to load binary: %d\n", err);
102+
* // Continue with fallback behavior
103+
* }
104+
* @endcode
105+
*/
106+
binary_loader_error_t binary_loader_load_and_flash(const char *bin_path);
107+
108+
/**
109+
* @brief Get error description string
110+
*
111+
* @param error Error code
112+
* @return Human-readable error description
113+
*
114+
* Example:
115+
* @code{.cpp}
116+
* binary_loader_error_t err = binary_loader_load_and_flash("/app.bin");
117+
* if (err != BINARY_LOADER_OK) {
118+
* Serial.println(binary_loader_get_error_string(err));
119+
* }
120+
* @endcode
121+
*/
122+
const char *binary_loader_get_error_string(binary_loader_error_t error);
123+
124+
/**
125+
* @brief Get information about OTA partitions
126+
*
127+
* @details Logs information about:
128+
* - Current running partition
129+
* - Next OTA partition
130+
* - Available space
131+
*
132+
* Useful for debugging partition issues.
133+
*/
134+
void binary_loader_print_partition_info();
135+
136+
/******************************************************************************
137+
* Implementation
138+
******************************************************************************/
139+
140+
const char *binary_loader_get_error_string(binary_loader_error_t error) {
141+
switch (error) {
142+
case BINARY_LOADER_OK:
143+
return "Success";
144+
case BINARY_LOADER_ERR_FILE_NOT_FOUND:
145+
return "Binary file not found on SD card";
146+
case BINARY_LOADER_ERR_FILE_TOO_LARGE:
147+
return "Binary file exceeds maximum size";
148+
case BINARY_LOADER_ERR_FILE_READ:
149+
return "Failed to read binary file from SD card";
150+
case BINARY_LOADER_ERR_OTA_BEGIN:
151+
return "Failed to begin OTA update";
152+
case BINARY_LOADER_ERR_OTA_WRITE:
153+
return "Failed to write to OTA partition";
154+
case BINARY_LOADER_ERR_OTA_END:
155+
return "Failed to finalize OTA update";
156+
case BINARY_LOADER_ERR_VALIDATION:
157+
return "Binary validation failed";
158+
case BINARY_LOADER_ERR_NO_OTA_PARTITION:
159+
return "No OTA partition available";
160+
default:
161+
return "Unknown error";
162+
}
163+
}
164+
165+
void binary_loader_print_partition_info() {
166+
Serial.println("=== OTA Partition Information ===");
167+
168+
// Get current running partition
169+
const esp_partition_t *running = esp_ota_get_running_partition();
170+
if (running) {
171+
Serial.printf("Running partition: %s (type=%d, subtype=%d, size=%d bytes)\n",
172+
running->label, running->type, running->subtype, running->size);
173+
}
174+
175+
// Get next OTA partition
176+
const esp_partition_t *next = esp_ota_get_next_update_partition(NULL);
177+
if (next) {
178+
Serial.printf("Next OTA partition: %s (type=%d, subtype=%d, size=%d bytes)\n",
179+
next->label, next->type, next->subtype, next->size);
180+
} else {
181+
Serial.println("No OTA partition available!");
182+
}
183+
184+
// Get boot partition
185+
const esp_partition_t *boot = esp_ota_get_boot_partition();
186+
if (boot) {
187+
Serial.printf("Boot partition: %s\n", boot->label);
188+
}
189+
190+
Serial.println("================================");
191+
}
192+
193+
binary_loader_error_t binary_loader_load_and_flash(const char *bin_path) {
194+
Serial.println("=== Binary Loader ===");
195+
Serial.printf("Loading binary from: %s\n", bin_path);
196+
197+
// Step 1: Open binary file from SD card
198+
File binFile = SD_MMC.open(bin_path, FILE_READ);
199+
if (!binFile) {
200+
Serial.printf("ERROR: Failed to open file: %s\n", bin_path);
201+
return BINARY_LOADER_ERR_FILE_NOT_FOUND;
202+
}
203+
204+
size_t fileSize = binFile.size();
205+
Serial.printf("Binary file size: %u bytes (%.2f KB)\n", fileSize, fileSize / 1024.0);
206+
207+
// Step 2: Validate file size
208+
if (fileSize == 0) {
209+
binFile.close();
210+
Serial.println("ERROR: Binary file is empty");
211+
return BINARY_LOADER_ERR_FILE_NOT_FOUND;
212+
}
213+
214+
if (fileSize > BINARY_LOADER_MAX_SIZE) {
215+
binFile.close();
216+
Serial.printf("ERROR: Binary too large (%u bytes, max %u bytes)\n",
217+
fileSize, BINARY_LOADER_MAX_SIZE);
218+
return BINARY_LOADER_ERR_FILE_TOO_LARGE;
219+
}
220+
221+
// Step 3: Get next OTA partition
222+
const esp_partition_t *update_partition = esp_ota_get_next_update_partition(NULL);
223+
if (!update_partition) {
224+
binFile.close();
225+
Serial.println("ERROR: No OTA partition available");
226+
binary_loader_print_partition_info();
227+
return BINARY_LOADER_ERR_NO_OTA_PARTITION;
228+
}
229+
230+
Serial.printf("Target OTA partition: %s (size: %u bytes)\n",
231+
update_partition->label, update_partition->size);
232+
233+
if (fileSize > update_partition->size) {
234+
binFile.close();
235+
Serial.printf("ERROR: Binary (%u bytes) exceeds partition size (%u bytes)\n",
236+
fileSize, update_partition->size);
237+
return BINARY_LOADER_ERR_FILE_TOO_LARGE;
238+
}
239+
240+
// Step 4: Begin OTA update
241+
Serial.println("Initializing OTA update...");
242+
if (!Update.begin(fileSize, U_FLASH)) {
243+
binFile.close();
244+
Serial.printf("ERROR: Update.begin() failed - %s\n", Update.errorString());
245+
return BINARY_LOADER_ERR_OTA_BEGIN;
246+
}
247+
248+
// Step 5: Read and write binary in chunks
249+
Serial.println("Writing binary to OTA partition...");
250+
uint8_t *buffer = (uint8_t *)malloc(BINARY_LOADER_BUFFER_SIZE);
251+
if (!buffer) {
252+
binFile.close();
253+
Update.abort();
254+
Serial.println("ERROR: Failed to allocate buffer");
255+
return BINARY_LOADER_ERR_OTA_BEGIN;
256+
}
257+
258+
size_t bytesWritten = 0;
259+
size_t lastProgressPercent = 0;
260+
261+
while (binFile.available()) {
262+
size_t bytesRead = binFile.read(buffer, BINARY_LOADER_BUFFER_SIZE);
263+
if (bytesRead == 0) {
264+
break; // End of file
265+
}
266+
267+
size_t written = Update.write(buffer, bytesRead);
268+
if (written != bytesRead) {
269+
free(buffer);
270+
binFile.close();
271+
Update.abort();
272+
Serial.printf("ERROR: Write failed - expected %u bytes, wrote %u bytes\n",
273+
bytesRead, written);
274+
return BINARY_LOADER_ERR_OTA_WRITE;
275+
}
276+
277+
bytesWritten += written;
278+
279+
// Print progress
280+
size_t progressPercent = (bytesWritten * 100) / fileSize;
281+
if (progressPercent >= lastProgressPercent + 10) {
282+
Serial.printf("Progress: %u%% (%u / %u bytes)\n",
283+
progressPercent, bytesWritten, fileSize);
284+
lastProgressPercent = progressPercent;
285+
}
286+
}
287+
288+
free(buffer);
289+
binFile.close();
290+
291+
Serial.printf("Total bytes written: %u / %u\n", bytesWritten, fileSize);
292+
293+
// Step 6: Finalize OTA update
294+
Serial.println("Finalizing OTA update...");
295+
if (!Update.end(true)) { // true = set new boot partition
296+
Serial.printf("ERROR: Update.end() failed - %s\n", Update.errorString());
297+
return BINARY_LOADER_ERR_OTA_END;
298+
}
299+
300+
// Step 7: Validate
301+
if (!Update.isFinished()) {
302+
Serial.println("ERROR: Update not finished");
303+
return BINARY_LOADER_ERR_VALIDATION;
304+
}
305+
306+
Serial.println("=== Binary Flashed Successfully ===");
307+
Serial.println("Rebooting in 2 seconds...");
308+
delay(2000);
309+
310+
// Step 8: Reboot into new firmware
311+
ESP.restart();
312+
313+
// This code won't be reached
314+
return BINARY_LOADER_OK;
315+
}
316+
317+
#endif // BINARY_LOADER_H
1.02 KB
Binary file not shown.
8.98 KB
Binary file not shown.

0 commit comments

Comments
 (0)