refactor: enhance OTA update process with improved handling of full image updates and SPIFFS data

This commit is contained in:
Manuel Weiser 2025-02-20 14:45:34 +01:00
parent e140f8e003
commit 7b89b04621
3 changed files with 127 additions and 101 deletions

View File

@ -59,6 +59,12 @@ build_flags =
-DOTA_PARTITION_SUBTYPE=0x10 -DOTA_PARTITION_SUBTYPE=0x10
-DPARTITION_TABLE_OFFSET=0x8000 -DPARTITION_TABLE_OFFSET=0x8000
-DPARTITION_TABLE_SIZE=0x1000 -DPARTITION_TABLE_SIZE=0x1000
-DCONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=1
-DCONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP=1
-DCONFIG_BOOTLOADER_SKIP_VALIDATE_ON_POWER_ON=1
-DCONFIG_BOOTLOADER_RESERVE_RTC_SIZE=0x1000
-DCONFIG_PARTITION_TABLE_OFFSET=0x8000
-DCONFIG_PARTITION_TABLE_MD5=y
extra_scripts = extra_scripts =
scripts/extra_script.py scripts/extra_script.py
@ -66,7 +72,6 @@ extra_scripts =
pre:scripts/pre_spiffs.py ; wird als zweites ausgeführt pre:scripts/pre_spiffs.py ; wird als zweites ausgeführt
pre:scripts/combine_html.py ; wird als drittes ausgeführt pre:scripts/combine_html.py ; wird als drittes ausgeführt
scripts/gzip_files.py scripts/gzip_files.py
# scripts/create_full_bin.py # Nicht mehr benötigt, da full.bin im Workflow erstellt wird
; Remove or comment out the targets line ; Remove or comment out the targets line
;targets = buildfs, build ;targets = buildfs, build

View File

@ -1,48 +0,0 @@
import os
import shutil
from platformio import util
Import("env")
def create_full_bin(source, target, env):
# Get paths
firmware_path = str(target[0])
build_dir = os.path.dirname(firmware_path)
project_dir = env.get("PROJECT_DIR")
# Create full binary
firmware_name = os.path.basename(firmware_path)
spiffs_bin = os.path.join(build_dir, "spiffs.bin")
full_bin = os.path.join(build_dir, "full.bin")
print("Creating full.bin...")
# Check if files exist
if not os.path.exists(firmware_path):
print("Error: Firmware binary not found!")
return
if not os.path.exists(spiffs_bin):
print("Error: SPIFFS binary not found!")
return
# Calculate padding size
firmware_size = os.path.getsize(firmware_path)
padding_size = 0x3D0000 - firmware_size # SPIFFS start address - firmware size
# Create full binary
with open(full_bin, 'wb') as outfile:
# Copy firmware
with open(firmware_path, 'rb') as f:
outfile.write(f.read())
# Add padding
outfile.write(b'\xFF' * padding_size)
# Copy SPIFFS
with open(spiffs_bin, 'rb') as f:
outfile.write(f.read())
print(f"Created full.bin ({os.path.getsize(full_bin)} bytes)")
# Register the callback
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", create_full_bin)

View File

@ -28,11 +28,9 @@ void handleOTAUpload(AsyncWebServerRequest *request, String filename, size_t ind
static size_t contentLength = 0; static size_t contentLength = 0;
static bool isFullImage = false; static bool isFullImage = false;
static uint32_t currentOffset = 0; static uint32_t currentOffset = 0;
static uint8_t *spiffsBuffer = nullptr;
// Flash layout constants from partitions.csv static size_t spiffsSize = 0x30000; // 192KB SPIFFS size
static const uint32_t FLASH_SIZE = 0x400000; // 4MB total static size_t spiffsStored = 0;
static const uint32_t APP_SIZE = 0x1E0000; // Size per app partition
static const uint32_t SPIFFS_OFFSET = 0x3D0000; // SPIFFS start
if (!index) { if (!index) {
contentLength = request->contentLength(); contentLength = request->contentLength();
@ -48,77 +46,148 @@ void handleOTAUpload(AsyncWebServerRequest *request, String filename, size_t ind
tasksAreStopped = true; tasksAreStopped = true;
} }
isFullImage = (contentLength >= SPIFFS_OFFSET); isFullImage = (contentLength > 0x300000); // Über 3MB ist ein full image
if (!isFullImage) { if (isFullImage) {
// Regular firmware update must not exceed app partition size // Alloziere Buffer für SPIFFS-Daten
if (contentLength > APP_SIZE) { spiffsBuffer = (uint8_t*)malloc(spiffsSize);
Serial.printf("Firmware too large: 0x%X > 0x%X\n", contentLength, APP_SIZE); if (!spiffsBuffer) {
request->send(400, "application/json", "{\"status\":\"error\",\"message\":\"Firmware too large\"}"); Serial.println("Failed to allocate SPIFFS buffer");
request->send(400, "application/json", "{\"status\":\"error\",\"message\":\"Memory allocation failed\"}");
return; return;
} }
memset(spiffsBuffer, 0xFF, spiffsSize);
spiffsStored = 0;
if (!Update.begin(contentLength)) { // Starte Update mit der Firmware-Größe
Serial.printf("Not enough space for firmware: %u required\n", contentLength); if (!Update.begin(0x3D0000, U_FLASH)) { // Nur bis zum SPIFFS-Start
request->send(400, "application/json", "{\"status\":\"error\",\"message\":\"Not enough space available\"}");
return;
}
Serial.printf("Firmware update started (size: 0x%X)\n", contentLength);
} else {
// Full image update
if (contentLength > FLASH_SIZE) {
Serial.printf("Image too large: 0x%X > 0x%X\n", contentLength, FLASH_SIZE);
request->send(400, "application/json", "{\"status\":\"error\",\"message\":\"Image too large\"}");
return;
}
if (!Update.begin(FLASH_SIZE, U_FLASH)) {
Serial.println("Could not begin full image update"); Serial.println("Could not begin full image update");
free(spiffsBuffer);
spiffsBuffer = nullptr;
request->send(400, "application/json", "{\"status\":\"error\",\"message\":\"Could not start full update\"}"); request->send(400, "application/json", "{\"status\":\"error\",\"message\":\"Could not start full update\"}");
return; return;
} }
Serial.printf("Full image update started (size: 0x%X)\n", contentLength); } else {
// Normales Firmware-Update
if (!Update.begin(contentLength)) {
request->send(400, "application/json", "{\"status\":\"error\",\"message\":\"Not enough space available\"}");
return;
} }
currentOffset = 0;
} }
if (Update.write(data, len) != len) { currentOffset = 0;
Serial.printf("%s update started\n", isFullImage ? "Full image" : "Firmware");
}
if (isFullImage && currentOffset >= 0x3D0000) {
// SPIFFS-Daten sammeln
size_t spiffsOffset = currentOffset - 0x3D0000;
if (spiffsOffset < spiffsSize) {
size_t copyLen = min(len, spiffsSize - spiffsOffset);
memcpy(spiffsBuffer + spiffsOffset, data, copyLen);
spiffsStored += copyLen;
Serial.printf("Stored SPIFFS data: offset=0x%X, len=%u\n", spiffsOffset, copyLen);
}
// Nur die Firmware-Daten an Update.write() übergeben
return;
}
// Schreibe Firmware-Daten
size_t writeLen = isFullImage ? min(len, 0x3D0000 - currentOffset) : len;
if (writeLen > 0) {
if (Update.write(data, writeLen) != writeLen) {
String errorMsg = Update.errorString(); String errorMsg = Update.errorString();
if (errorMsg != "No Error") { if (errorMsg != "No Error") {
Update.printError(Serial); Update.printError(Serial);
Serial.printf("Error at offset: 0x%X of 0x%X bytes\n", currentOffset, contentLength); if (spiffsBuffer) {
free(spiffsBuffer);
spiffsBuffer = nullptr;
}
request->send(400, "application/json", "{\"status\":\"error\",\"message\":\"Error writing update: " + errorMsg + "\"}"); request->send(400, "application/json", "{\"status\":\"error\",\"message\":\"Error writing update: " + errorMsg + "\"}");
return; return;
} }
} }
// Progress logging
if ((currentOffset % 0x40000) == 0) { // Log every 256KB
Serial.printf("Update progress: 0x%X of 0x%X bytes (%.1f%%)\n",
currentOffset,
contentLength,
(currentOffset * 100.0) / contentLength);
} }
currentOffset += len; currentOffset += len;
// Progress logging
if ((currentOffset % 0x40000) == 0) {
Serial.printf("Update progress: 0x%X of 0x%X bytes (%.1f%%)\n",
currentOffset, contentLength, (currentOffset * 100.0) / contentLength);
}
if (final) { if (final) {
if (Update.end(true)) { bool success = true;
Serial.printf("Update complete: 0x%X bytes written\n", currentOffset);
request->send(200, "application/json", "{\"status\":\"success\",\"message\":\"Update successful! Device will restart...\",\"restart\":true}"); // Beende Firmware-Update
delay(1000); if (!Update.end(true)) {
ESP.restart(); success = false;
} else {
String errorMsg = Update.errorString(); String errorMsg = Update.errorString();
if (errorMsg != "No Error") { if (errorMsg != "No Error") {
Update.printError(Serial); Update.printError(Serial);
request->send(400, "application/json", "{\"status\":\"error\",\"message\":\"Update failed: " + errorMsg + "\"}"); if (spiffsBuffer) {
} else { free(spiffsBuffer);
spiffsBuffer = nullptr;
}
request->send(400, "application/json", "{\"status\":\"error\",\"message\":\"Firmware update failed: " + errorMsg + "\"}");
return;
}
}
// SPIFFS aktualisieren wenn es ein full image war
if (success && isFullImage && spiffsBuffer && spiffsStored > 0) {
Serial.printf("Starting SPIFFS update (size: 0x%X)\n", spiffsStored);
if (SPIFFS.begin(true)) {
SPIFFS.end(); // Unmount first
}
if (!Update.begin(spiffsSize, U_SPIFFS)) {
Serial.println("Could not begin SPIFFS update");
free(spiffsBuffer);
spiffsBuffer = nullptr;
request->send(400, "application/json", "{\"status\":\"error\",\"message\":\"SPIFFS update failed to start\"}");
return;
}
if (Update.write(spiffsBuffer, spiffsSize) != spiffsSize) {
Serial.println("SPIFFS Write Failed");
Update.printError(Serial);
free(spiffsBuffer);
spiffsBuffer = nullptr;
request->send(400, "application/json", "{\"status\":\"error\",\"message\":\"SPIFFS write failed\"}");
return;
}
if (!Update.end(true)) {
Serial.println("SPIFFS End Failed");
Update.printError(Serial);
free(spiffsBuffer);
spiffsBuffer = nullptr;
request->send(400, "application/json", "{\"status\":\"error\",\"message\":\"SPIFFS finish failed\"}");
return;
}
// Cleanup
free(spiffsBuffer);
spiffsBuffer = nullptr;
// Verify SPIFFS
if (!SPIFFS.begin(true)) {
Serial.println("SPIFFS mount after update failed");
request->send(400, "application/json", "{\"status\":\"error\",\"message\":\"SPIFFS verification failed\"}");
return;
}
SPIFFS.end();
}
// Alles erfolgreich
Serial.printf("Update complete: 0x%X bytes written\n", currentOffset);
request->send(200, "application/json", "{\"status\":\"success\",\"message\":\"Update successful! Device will restart...\",\"restart\":true}"); request->send(200, "application/json", "{\"status\":\"success\",\"message\":\"Update successful! Device will restart...\",\"restart\":true}");
delay(1000); delay(500);
ESP.restart(); ESP.restart();
} }
}
}
} }