feat: implement OTA update functionality with web interface; update partition settings and build configuration
This commit is contained in:
parent
175d614d1f
commit
3c783c9844
@ -1,5 +1,6 @@
|
|||||||
# Name, Type, SubType, Offset, Size, Flags
|
# Name, Type, SubType, Offset, Size, Flags
|
||||||
nvs, data, nvs, 0x9000, 0x5000,
|
nvs, data, nvs, 0x9000, 0x5000,
|
||||||
otadata, data, ota, 0xe000, 0x2000,
|
otadata, data, ota, 0xe000, 0x2000,
|
||||||
app0, app, ota_0, 0x10000, 0x280000,
|
app0, app, factory, 0x10000, 0x1E0000,
|
||||||
spiffs, data, spiffs, 0x290000, 0x170000,
|
app1, app, ota_0, 0x1F0000, 0x1E0000,
|
||||||
|
spiffs, data, spiffs, 0x3D0000, 0x30000,
|
|
@ -34,9 +34,8 @@ board_build.filesystem = spiffs
|
|||||||
; Update partition settings
|
; Update partition settings
|
||||||
board_build.partitions = partitions.csv
|
board_build.partitions = partitions.csv
|
||||||
board_upload.flash_size = 4MB
|
board_upload.flash_size = 4MB
|
||||||
; Remove these as they're now defined in partitions.csv
|
board_build.flash_mode = dio
|
||||||
; board_build.spiffs.partition = 2M
|
board_upload.flash_freq = "40m"
|
||||||
; board_build.spiffs.upload_size = 2M
|
|
||||||
|
|
||||||
build_flags =
|
build_flags =
|
||||||
-Os
|
-Os
|
||||||
@ -45,8 +44,15 @@ build_flags =
|
|||||||
-DNDEBUG
|
-DNDEBUG
|
||||||
-mtext-section-literals
|
-mtext-section-literals
|
||||||
'-D VERSION="${common.version}"'
|
'-D VERSION="${common.version}"'
|
||||||
-DESPASYNCHTTPUPDATESERVER_PRETTY
|
-DASYNCWEBSERVER_REGEX
|
||||||
|
-DCORE_DEBUG_LEVEL=1
|
||||||
|
-DCONFIG_ARDUHAL_LOG_COLORS=1
|
||||||
|
-DOTA_DEBUG=1
|
||||||
|
-DARDUINO_RUNNING_CORE=1
|
||||||
|
-DARDUINO_EVENT_RUNNING_CORE=1
|
||||||
|
-DCONFIG_OPTIMIZATION_LEVEL_DEBUG=1
|
||||||
|
-DCONFIG_ESP32_PANIC_PRINT_REBOOT
|
||||||
|
|
||||||
extra_scripts =
|
extra_scripts =
|
||||||
scripts/extra_script.py
|
scripts/extra_script.py
|
||||||
pre:scripts/pre_build.py ; wird zuerst ausgeführt
|
pre:scripts/pre_build.py ; wird zuerst ausgeführt
|
||||||
|
211
src/ota.cpp
211
src/ota.cpp
@ -1,3 +1,214 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include "ota.h"
|
#include "ota.h"
|
||||||
|
#include <Update.h>
|
||||||
|
#include <SPIFFS.h>
|
||||||
|
#include "commonFS.h"
|
||||||
|
#include <esp_task_wdt.h>
|
||||||
|
#include <esp_int_wdt.h>
|
||||||
|
#include <esp_pthread.h>
|
||||||
|
#include <esp_ota_ops.h>
|
||||||
|
|
||||||
|
// Reduzierter Puffer für Dateioperationen
|
||||||
|
const size_t BUFFER_SIZE = 128;
|
||||||
|
const size_t MIN_FREE_HEAP = 32768; // 32KB minimum free heap
|
||||||
|
|
||||||
|
// Files to backup before update
|
||||||
|
const char* CONFIG_FILES[] = {
|
||||||
|
"/bambu_credentials.json",
|
||||||
|
"/spoolman_url.json"
|
||||||
|
};
|
||||||
|
const int CONFIG_FILES_COUNT = sizeof(CONFIG_FILES) / sizeof(CONFIG_FILES[0]);
|
||||||
|
|
||||||
|
bool backupConfigs() {
|
||||||
|
if (!SPIFFS.begin(true)) {
|
||||||
|
Serial.println("Failed to mount SPIFFS");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < CONFIG_FILES_COUNT; i++) {
|
||||||
|
if (SPIFFS.exists(CONFIG_FILES[i])) {
|
||||||
|
String backupFile = String(CONFIG_FILES[i]) + ".bak";
|
||||||
|
|
||||||
|
if (SPIFFS.exists(backupFile)) {
|
||||||
|
SPIFFS.remove(backupFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
File sourceFile = SPIFFS.open(CONFIG_FILES[i], "r");
|
||||||
|
File destFile = SPIFFS.open(backupFile, "w");
|
||||||
|
|
||||||
|
if (!sourceFile || !destFile) {
|
||||||
|
Serial.printf("Failed to open files for backup: %s\n", CONFIG_FILES[i]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verwenden Sie einen kleineren Puffer
|
||||||
|
uint8_t* buf = (uint8_t*)malloc(BUFFER_SIZE);
|
||||||
|
if (!buf) {
|
||||||
|
Serial.println("Failed to allocate buffer");
|
||||||
|
sourceFile.close();
|
||||||
|
destFile.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t len = 0;
|
||||||
|
bool success = true;
|
||||||
|
while ((len = sourceFile.read(buf, BUFFER_SIZE)) > 0) {
|
||||||
|
if (destFile.write(buf, len) != len) {
|
||||||
|
Serial.println("Write failed");
|
||||||
|
success = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(buf);
|
||||||
|
sourceFile.close();
|
||||||
|
destFile.close();
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool restoreConfigs() {
|
||||||
|
if (!SPIFFS.begin(true)) {
|
||||||
|
Serial.println("Failed to mount SPIFFS");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success = true;
|
||||||
|
for (int i = 0; i < CONFIG_FILES_COUNT; i++) {
|
||||||
|
String backupFile = String(CONFIG_FILES[i]) + ".bak";
|
||||||
|
if (SPIFFS.exists(backupFile)) {
|
||||||
|
if (SPIFFS.exists(CONFIG_FILES[i])) {
|
||||||
|
SPIFFS.remove(CONFIG_FILES[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
File sourceFile = SPIFFS.open(backupFile, "r");
|
||||||
|
File destFile = SPIFFS.open(CONFIG_FILES[i], "w");
|
||||||
|
|
||||||
|
if (!sourceFile || !destFile) {
|
||||||
|
Serial.printf("Failed to open files for restore: %s\n", CONFIG_FILES[i]);
|
||||||
|
success = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verwenden Sie einen kleineren Puffer
|
||||||
|
uint8_t* buf = (uint8_t*)malloc(BUFFER_SIZE);
|
||||||
|
if (!buf) {
|
||||||
|
Serial.println("Failed to allocate buffer");
|
||||||
|
sourceFile.close();
|
||||||
|
destFile.close();
|
||||||
|
success = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t len = 0;
|
||||||
|
while ((len = sourceFile.read(buf, BUFFER_SIZE)) > 0) {
|
||||||
|
if (destFile.write(buf, len) != len) {
|
||||||
|
Serial.println("Write failed");
|
||||||
|
success = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(buf);
|
||||||
|
sourceFile.close();
|
||||||
|
destFile.close();
|
||||||
|
|
||||||
|
SPIFFS.remove(backupFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleOTAUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
|
||||||
|
static bool updateStarted = false;
|
||||||
|
static size_t totalBytes = 0;
|
||||||
|
|
||||||
|
if (!index) {
|
||||||
|
updateStarted = false;
|
||||||
|
totalBytes = 0;
|
||||||
|
|
||||||
|
// Check minimum heap size
|
||||||
|
if (ESP.getFreeHeap() < MIN_FREE_HEAP) {
|
||||||
|
request->send(500, "text/plain", "Not enough memory available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t updateSize = request->contentLength();
|
||||||
|
if (updateSize == 0) {
|
||||||
|
request->send(400, "text/plain", "Invalid file size");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.printf("Update size: %u bytes\n", updateSize);
|
||||||
|
Serial.printf("Free space: %u bytes\n", ESP.getFreeSketchSpace());
|
||||||
|
Serial.printf("Free heap: %u bytes\n", ESP.getFreeHeap());
|
||||||
|
|
||||||
|
// Backup configs first
|
||||||
|
if (!backupConfigs()) {
|
||||||
|
request->send(500, "text/plain", "Failed to backup configuration");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close all files and unmount SPIFFS
|
||||||
|
SPIFFS.end();
|
||||||
|
|
||||||
|
// Begin update with minimal configuration
|
||||||
|
if (!Update.begin(updateSize)) {
|
||||||
|
Serial.printf("Update.begin failed: %s\n", Update.errorString());
|
||||||
|
request->send(500, "text/plain", "Failed to start update");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStarted = true;
|
||||||
|
Serial.println("Update process started");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!updateStarted) {
|
||||||
|
request->send(500, "text/plain", "Update not properly started");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write update data
|
||||||
|
if (Update.write(data, len) != len) {
|
||||||
|
Serial.printf("Update.write failed: %s\n", Update.errorString());
|
||||||
|
Update.abort();
|
||||||
|
request->send(500, "text/plain", "Error during update");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
totalBytes += len;
|
||||||
|
if (totalBytes % (32 * 1024) == 0) {
|
||||||
|
Serial.printf("Progress: %u bytes\n", totalBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (final) {
|
||||||
|
if (!Update.end(true)) {
|
||||||
|
Serial.printf("Update.end failed: %s\n", Update.errorString());
|
||||||
|
request->send(500, "text/plain", "Update failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to restore configs
|
||||||
|
if (!SPIFFS.begin(true)) {
|
||||||
|
Serial.println("Failed to mount SPIFFS for restore");
|
||||||
|
request->send(200, "text/plain", "Update successful but config restore failed. Device will restart...");
|
||||||
|
delay(2000);
|
||||||
|
ESP.restart();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!restoreConfigs()) {
|
||||||
|
Serial.println("Failed to restore configs");
|
||||||
|
}
|
||||||
|
|
||||||
|
request->send(200, "text/plain", "Update successful. Device will restart...");
|
||||||
|
delay(2000);
|
||||||
|
ESP.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
#ifndef OTA_H
|
#ifndef OTA_H
|
||||||
#define OTA_H
|
#define OTA_H
|
||||||
|
|
||||||
|
#include <ESPAsyncWebServer.h>
|
||||||
|
|
||||||
|
void handleOTAUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);
|
||||||
|
|
||||||
#endif
|
#endif
|
106
src/website.cpp
106
src/website.cpp
@ -337,7 +337,111 @@ void setupWebserver(AsyncWebServer &server) {
|
|||||||
request->send(response);
|
request->send(response);
|
||||||
Serial.println("RFID.js gesendet");
|
Serial.println("RFID.js gesendet");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Route für OTA Updates
|
||||||
|
server.on("/ota", HTTP_GET, [](AsyncWebServerRequest *request) {
|
||||||
|
Serial.println("Anfrage für /ota erhalten");
|
||||||
|
|
||||||
|
String html = R"(
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Firmware Update</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/style.css">
|
||||||
|
<style>
|
||||||
|
.progress-container {
|
||||||
|
width: 100%;
|
||||||
|
margin: 20px 0;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.progress-bar {
|
||||||
|
width: 0%;
|
||||||
|
height: 20px;
|
||||||
|
background-color: #4CAF50;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 20px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.status {
|
||||||
|
margin: 10px 0;
|
||||||
|
padding: 10px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.error { background-color: #ffebee; color: #c62828; }
|
||||||
|
.success { background-color: #e8f5e9; color: #2e7d32; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h2>Firmware Update</h2>
|
||||||
|
<form id="updateForm" enctype='multipart/form-data'>
|
||||||
|
<input type='file' name='update' accept='.bin' required>
|
||||||
|
<input type='submit' value='Update Firmware'>
|
||||||
|
</form>
|
||||||
|
<div class="progress-container">
|
||||||
|
<div class="progress-bar">0%</div>
|
||||||
|
</div>
|
||||||
|
<div class="status"></div>
|
||||||
|
<script>
|
||||||
|
document.getElementById('updateForm').addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const form = e.target;
|
||||||
|
const file = form.update.files[0];
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('update', file);
|
||||||
|
|
||||||
|
const progress = document.querySelector('.progress-bar');
|
||||||
|
const progressContainer = document.querySelector('.progress-container');
|
||||||
|
const status = document.querySelector('.status');
|
||||||
|
|
||||||
|
progressContainer.style.display = 'block';
|
||||||
|
status.style.display = 'none';
|
||||||
|
status.className = 'status';
|
||||||
|
form.querySelector('input[type=submit]').disabled = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/update', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
onUploadProgress: (e) => {
|
||||||
|
const percent = (e.loaded / e.total) * 100;
|
||||||
|
progress.style.width = percent + '%';
|
||||||
|
progress.textContent = Math.round(percent) + '%';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.text();
|
||||||
|
status.textContent = result;
|
||||||
|
status.classList.add(response.ok ? 'success' : 'error');
|
||||||
|
status.style.display = 'block';
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.reload();
|
||||||
|
}, 5000);
|
||||||
|
} else {
|
||||||
|
form.querySelector('input[type=submit]').disabled = false;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
status.textContent = 'Update failed: ' + error.message;
|
||||||
|
status.classList.add('error');
|
||||||
|
status.style.display = 'block';
|
||||||
|
form.querySelector('input[type=submit]').disabled = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)";
|
||||||
|
request->send(200, "text/html", html);
|
||||||
|
});
|
||||||
|
|
||||||
|
server.on("/update", HTTP_POST,
|
||||||
|
[](AsyncWebServerRequest *request) {
|
||||||
|
// The response will be sent from handleOTAUpload when the upload is complete
|
||||||
|
},
|
||||||
|
handleOTAUpload
|
||||||
|
);
|
||||||
|
|
||||||
// Fehlerbehandlung für nicht gefundene Seiten
|
// Fehlerbehandlung für nicht gefundene Seiten
|
||||||
server.onNotFound([](AsyncWebServerRequest *request){
|
server.onNotFound([](AsyncWebServerRequest *request){
|
||||||
Serial.print("404 - Nicht gefunden: ");
|
Serial.print("404 - Nicht gefunden: ");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user