feat: update version to 1.2.3; modify HTML files to reflect new version; enhance firmware update process and UI improvements

This commit is contained in:
Manuel Weiser 2025-02-18 14:18:14 +01:00
parent 8b246e180b
commit 2cab24403e
12 changed files with 99 additions and 240 deletions

View File

@ -12,7 +12,7 @@
<div style="display: flex; align-items: center; gap: 2rem;"> <div style="display: flex; align-items: center; gap: 2rem;">
<img src="/logo.png" alt="FilaMan Logo" class="logo"> <img src="/logo.png" alt="FilaMan Logo" class="logo">
<div class="logo-text"> <div class="logo-text">
<h1>FilaMan<span class="version">v1.2.1</span></h1> <h1>FilaMan<span class="version">v1.2.2</span></h1>
<h4>Filament Management Tool</h4> <h4>Filament Management Tool</h4>
</div> </div>
</div> </div>

View File

@ -12,7 +12,7 @@
<div style="display: flex; align-items: center; gap: 2rem;"> <div style="display: flex; align-items: center; gap: 2rem;">
<img src="/logo.png" alt="FilaMan Logo" class="logo"> <img src="/logo.png" alt="FilaMan Logo" class="logo">
<div class="logo-text"> <div class="logo-text">
<h1>FilaMan<span class="version">v1.2.1</span></h1> <h1>FilaMan<span class="version">v1.2.2</span></h1>
<h4>Filament Management Tool</h4> <h4>Filament Management Tool</h4>
</div> </div>
</div> </div>

View File

@ -12,7 +12,7 @@
<div style="display: flex; align-items: center; gap: 2rem;"> <div style="display: flex; align-items: center; gap: 2rem;">
<img src="/logo.png" alt="FilaMan Logo" class="logo"> <img src="/logo.png" alt="FilaMan Logo" class="logo">
<div class="logo-text"> <div class="logo-text">
<h1>FilaMan<span class="version">v1.2.1</span></h1> <h1>FilaMan<span class="version">v1.2.2</span></h1>
<h4>Filament Management Tool</h4> <h4>Filament Management Tool</h4>
</div> </div>
</div> </div>

View File

@ -12,7 +12,7 @@
<div style="display: flex; align-items: center; gap: 2rem;"> <div style="display: flex; align-items: center; gap: 2rem;">
<img src="/logo.png" alt="FilaMan Logo" class="logo"> <img src="/logo.png" alt="FilaMan Logo" class="logo">
<div class="logo-text"> <div class="logo-text">
<h1>FilaMan<span class="version">v1.2.1</span></h1> <h1>FilaMan<span class="version">v1.2.2</span></h1>
<h4>Filament Management Tool</h4> <h4>Filament Management Tool</h4>
</div> </div>
</div> </div>

View File

@ -1056,6 +1056,7 @@ input[type="submit"]:disabled,
box-shadow: 0 2px 4px rgba(0,0,0,0.1); box-shadow: 0 2px 4px rgba(0,0,0,0.1);
margin: 0 auto; margin: 0 auto;
width: 400px; width: 400px;
text-align: center;
} }
.update-form input[type="file"] { .update-form input[type="file"] {
margin-bottom: 15px; margin-bottom: 15px;
@ -1063,6 +1064,7 @@ input[type="submit"]:disabled,
padding: 8px; padding: 8px;
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 4px; border-radius: 4px;
background: white;
} }
.update-form input[type="submit"] { .update-form input[type="submit"] {
background-color: #4CAF50; background-color: #4CAF50;
@ -1072,6 +1074,7 @@ input[type="submit"]:disabled,
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
font-size: 16px; font-size: 16px;
transition: background-color 0.3s;
} }
.update-form input[type="submit"]:hover { .update-form input[type="submit"]:hover {
background-color: #45a049; background-color: #45a049;
@ -1083,8 +1086,10 @@ input[type="submit"]:disabled,
.warning { .warning {
background-color: var(--primary-color); background-color: var(--primary-color);
border: 1px solid #ffe0b2; border: 1px solid #ffe0b2;
color: #e65100; color: white;
padding: 15px; padding: 15px;
margin: 20px 0; margin: 20px auto;
border-radius: 4px; border-radius: 4px;
max-width: 600px;
text-align: center;
} }

View File

@ -1,4 +1,3 @@
<!-- head --><!DOCTYPE html> <!-- head --><!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
@ -13,7 +12,7 @@
<div style="display: flex; align-items: center; gap: 2rem;"> <div style="display: flex; align-items: center; gap: 2rem;">
<img src="/logo.png" alt="FilaMan Logo" class="logo"> <img src="/logo.png" alt="FilaMan Logo" class="logo">
<div class="logo-text"> <div class="logo-text">
<h1>FilaMan<span class="version">v1.2.1</span></h1> <h1>FilaMan<span class="version">v1.2.2</span></h1>
<h4>Filament Management Tool</h4> <h4>Filament Management Tool</h4>
</div> </div>
</div> </div>
@ -42,14 +41,13 @@
<div class="warning"> <div class="warning">
<strong>Warning:</strong> Please do not turn off or restart the device during the update. <strong>Warning:</strong> Please do not turn off or restart the device during the update.
Configuration files will be automatically backed up and restored after the update. The device will restart automatically after the update.
</div> </div>
<div class="update-form">
<div class="update-form" style="align-items: center;"> <form id="updateForm" enctype='multipart/form-data'>
<form id="updateForm" enctype='multipart/form-data' style="text-align: center;"></form>
<input type='file' name='update' accept='.bin' required> <input type='file' name='update' accept='.bin' required>
<input type='submit' value='Firmware Update starten'> <input type='submit' value='Start Firmware Update'>
</form> </form>
</div> </div>
@ -60,10 +58,21 @@
</div> </div>
<script> <script>
// Hide status indicators during update
const statusContainer = document.querySelector('.status-container');
if (statusContainer) {
statusContainer.style.display = 'none';
}
document.getElementById('updateForm').addEventListener('submit', async (e) => { document.getElementById('updateForm').addEventListener('submit', async (e) => {
e.preventDefault(); e.preventDefault();
const form = e.target; const form = e.target;
const file = form.update.files[0]; const file = form.update.files[0];
if (!file) {
alert('Please select a firmware file.');
return;
}
const formData = new FormData(); const formData = new FormData();
formData.append('update', file); formData.append('update', file);
@ -91,21 +100,24 @@
try { try {
let response = this.responseText; let response = this.responseText;
try { try {
// Versuche als JSON zu parsen
const jsonResponse = JSON.parse(response); const jsonResponse = JSON.parse(response);
response = jsonResponse.message; response = jsonResponse.message;
if (jsonResponse.restart) { if (jsonResponse.restart) {
// Wenn Gerät neustartet, warte und lade dann neu status.textContent = response + " Redirecting in 20 seconds...";
status.textContent = response + " Weiterleitung in 5 Sekunden..."; let countdown = 20;
setTimeout(() => { const timer = setInterval(() => {
countdown--;
if (countdown <= 0) {
clearInterval(timer);
window.location.href = '/'; window.location.href = '/';
}, 5000); } else {
status.textContent = response + ` Redirecting in ${countdown} seconds...`;
}
}, 1000);
} }
} catch (e) { } catch (e) {
// Wenn kein JSON, nutze response als Text
if (!isNaN(response)) { if (!isNaN(response)) {
// Wenn es eine Zahl ist, update den Fortschritt
const percent = parseInt(response); const percent = parseInt(response);
progress.style.width = percent + '%'; progress.style.width = percent + '%';
progress.textContent = percent + '%'; progress.textContent = percent + '%';
@ -121,7 +133,7 @@
form.querySelector('input[type=submit]').disabled = false; form.querySelector('input[type=submit]').disabled = false;
} }
} catch (error) { } catch (error) {
status.textContent = 'Fehler: ' + error.message; status.textContent = 'Error: ' + error.message;
status.classList.add('error'); status.classList.add('error');
status.style.display = 'block'; status.style.display = 'block';
form.querySelector('input[type=submit]').disabled = false; form.querySelector('input[type=submit]').disabled = false;
@ -129,7 +141,7 @@
}; };
xhr.onerror = function() { xhr.onerror = function() {
status.textContent = 'Update fehlgeschlagen: Netzwerkfehler'; status.textContent = 'Update failed: Network error';
status.classList.add('error'); status.classList.add('error');
status.style.display = 'block'; status.style.display = 'block';
form.querySelector('input[type=submit]').disabled = false; form.querySelector('input[type=submit]').disabled = false;

View File

@ -12,7 +12,7 @@
<div style="display: flex; align-items: center; gap: 2rem;"> <div style="display: flex; align-items: center; gap: 2rem;">
<img src="/logo.png" alt="FilaMan Logo" class="logo"> <img src="/logo.png" alt="FilaMan Logo" class="logo">
<div class="logo-text"> <div class="logo-text">
<h1>FilaMan<span class="version">v1.2.1</span></h1> <h1>FilaMan<span class="version">v1.2.2</span></h1>
<h4>Filament Management Tool</h4> <h4>Filament Management Tool</h4>
</div> </div>
</div> </div>

View File

@ -12,7 +12,7 @@
<div style="display: flex; align-items: center; gap: 2rem;"> <div style="display: flex; align-items: center; gap: 2rem;">
<img src="/logo.png" alt="FilaMan Logo" class="logo"> <img src="/logo.png" alt="FilaMan Logo" class="logo">
<div class="logo-text"> <div class="logo-text">
<h1>FilaMan<span class="version">v1.2.1</span></h1> <h1>FilaMan<span class="version">v1.2.2</span></h1>
<h4>Filament Management Tool</h4> <h4>Filament Management Tool</h4>
</div> </div>
</div> </div>

View File

@ -1,6 +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, factory, 0x10000, 0x1E0000, app0, app, ota_0, 0x10000, 0x1E0000,
app1, app, ota_0, 0x1F0000, 0x1E0000, app1, app, ota_1, 0x1F0000, 0x1E0000,
spiffs, data, spiffs, 0x3D0000, 0x30000, spiffs, data, spiffs, 0x3D0000, 0x30000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x5000
3 otadata data ota 0xe000 0x2000
4 app0 app factory ota_0 0x10000 0x1E0000
5 app1 app ota_0 ota_1 0x1F0000 0x1E0000
6 spiffs data spiffs 0x3D0000 0x30000

View File

@ -9,7 +9,7 @@
; https://docs.platformio.org/page/projectconf.html ; https://docs.platformio.org/page/projectconf.html
[common] [common]
version = "1.2.2" version = "1.2.3"
[env:esp32dev] [env:esp32dev]
platform = espressif32 platform = espressif32
@ -52,6 +52,13 @@ build_flags =
-DARDUINO_EVENT_RUNNING_CORE=1 -DARDUINO_EVENT_RUNNING_CORE=1
-DCONFIG_OPTIMIZATION_LEVEL_DEBUG=1 -DCONFIG_OPTIMIZATION_LEVEL_DEBUG=1
-DCONFIG_ESP32_PANIC_PRINT_REBOOT -DCONFIG_ESP32_PANIC_PRINT_REBOOT
-DCONFIG_ARDUINO_OTA_READSIZE=1024
-DCONFIG_ASYNC_TCP_RUNNING_CORE=1
-DCONFIG_ASYNC_TCP_USE_WDT=0
-DCONFIG_LWIP_TCP_MSS=1460
-DOTA_PARTITION_SUBTYPE=0x10
-DPARTITION_TABLE_OFFSET=0x8000
-DPARTITION_TABLE_SIZE=0x1000
extra_scripts = extra_scripts =
scripts/extra_script.py scripts/extra_script.py

View File

@ -3,219 +3,44 @@
#include <Update.h> #include <Update.h>
#include <SPIFFS.h> #include <SPIFFS.h>
#include "commonFS.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) { void handleOTAUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
static bool updateStarted = false; static size_t contentLength = 0;
static size_t totalBytes = 0;
static size_t totalSize = 0;
if (!index) { if (!index) {
updateStarted = false; contentLength = request->contentLength();
totalBytes = 0; Serial.printf("Update size: %u bytes\n", contentLength);
totalSize = request->contentLength();
// Check minimum heap size if (contentLength == 0) {
if (ESP.getFreeHeap() < MIN_FREE_HEAP) { request->send(400, "application/json", "{\"status\":\"error\",\"message\":\"Invalid file size\"}");
request->send(500, "text/plain", "Not enough memory available");
return; return;
} }
if (totalSize == 0) { if (!Update.begin(contentLength)) {
request->send(400, "text/plain", "Invalid file size"); Serial.printf("Not enough space: %u required\n", contentLength);
request->send(400, "application/json", "{\"status\":\"error\",\"message\":\"Not enough space available\"}");
return; return;
} }
Serial.printf("Update size: %u bytes\n", totalSize); Serial.println("Update started");
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(totalSize)) {
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");
// Send initial progress
request->send(200, "text/plain", "0");
}
if (!updateStarted) {
request->send(500, "text/plain", "Update not properly started");
return;
}
// Write update data
if (Update.write(data, len) != len) { if (Update.write(data, len) != len) {
Serial.printf("Update.write failed: %s\n", Update.errorString()); Update.printError(Serial);
Update.abort(); request->send(400, "application/json", "{\"status\":\"error\",\"message\":\"Error writing update\"}");
request->send(500, "text/plain", "Error during update");
return; return;
} }
totalBytes += len;
// Send progress update
if (!final) {
int progress = (totalBytes * 100) / totalSize;
request->send(200, "text/plain", String(progress));
}
if (final) { if (final) {
if (!Update.end(true)) { if (Update.end(true)) {
Serial.printf("Update.end failed: %s\n", Update.errorString()); Serial.println("Update complete");
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, "application/json", "{\"status\": \"success\", \"message\": \"Update successful but config restore failed. Device will restart...\", \"restart\": true}");
delay(2000);
ESP.restart();
return;
}
if (!restoreConfigs()) {
Serial.println("Failed to restore configs");
}
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(2000); delay(1000);
ESP.restart(); ESP.restart();
} else {
Update.printError(Serial);
request->send(400, "application/json", "{\"status\":\"error\",\"message\":\"Update failed\"}");
}
} }
} }

View File

@ -164,7 +164,7 @@ void setupWebserver(AsyncWebServer &server) {
// Route für about // Route für about
server.on("/about", HTTP_GET, [](AsyncWebServerRequest *request){ server.on("/about", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Anfrage für /about erhalten"); Serial.println("Anfrage für /about erhalten");
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/about.html.gz", "text/html"); AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/index.html.gz", "text/html");
response->addHeader("Content-Encoding", "gzip"); response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL); response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response); request->send(response);
@ -338,8 +338,13 @@ void setupWebserver(AsyncWebServer &server) {
Serial.println("RFID.js gesendet"); Serial.println("RFID.js gesendet");
}); });
// Route für Firmware Update // Route for Firmware Update
server.on("/upgrade", HTTP_GET, [](AsyncWebServerRequest *request) { server.on("/upgrade", HTTP_GET, [](AsyncWebServerRequest *request) {
// During OTA, reduce memory usage
ws.enable(false); // Temporarily disable WebSocket
ws.cleanupClients();
Serial.println("Request for /upgrade received");
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/upgrade.html.gz", "text/html"); AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/upgrade.html.gz", "text/html");
response->addHeader("Content-Encoding", "gzip"); response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL); response->addHeader("Cache-Control", CACHE_CONTROL);
@ -350,7 +355,12 @@ void setupWebserver(AsyncWebServer &server) {
[](AsyncWebServerRequest *request) { [](AsyncWebServerRequest *request) {
// The response will be sent from handleOTAUpload when the upload is complete // The response will be sent from handleOTAUpload when the upload is complete
}, },
handleOTAUpload [](AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) {
// Free memory before handling update
ws.enable(false);
ws.cleanupClients();
handleOTAUpload(request, filename, index, data, len, final);
}
); );
// Fehlerbehandlung für nicht gefundene Seiten // Fehlerbehandlung für nicht gefundene Seiten