From 206a19ac412119fcdd3bcd92650261cdfeea237b Mon Sep 17 00:00:00 2001 From: Wastl Kraus Date: Wed, 17 Sep 2025 13:15:47 +0200 Subject: [PATCH] add OTA Update to web_client --- examples/web_client/web_client.ino | 117 +++++++++++++- library.properties | 2 +- src/ota_update.h | 249 +++++++++++++++++++++++++++++ 3 files changed, 364 insertions(+), 4 deletions(-) create mode 100644 src/ota_update.h diff --git a/examples/web_client/web_client.ino b/examples/web_client/web_client.ino index 3bf66f7..635b644 100644 --- a/examples/web_client/web_client.ino +++ b/examples/web_client/web_client.ino @@ -7,9 +7,11 @@ */ #include +#include #include #include +#include #include #include @@ -62,6 +64,8 @@ void handleWebSocketMessage(void *arg, uint8_t *data, size_t len); void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len); bool connectToWiFi(); void printWiFiStatus(); +void setupOTA(); +void handleOTAUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); // void setup() @@ -79,15 +83,18 @@ void setup() if (wifi_connected) { + // Setup OTA first + setupOTA(); + // Init WebSockets and Webserver USB_SERIAL.println("\nStarting Webserver..."); - + ws.onEvent(onWsEvent); server.addHandler(&ws); server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { request->send_P(200, "text/html", index_html); }); - + server.begin(); USB_SERIAL.println("HTTP server started."); @@ -96,7 +103,7 @@ void setup() else { USB_SERIAL.println("\n*** WARNING: WiFi connection failed! ***"); - USB_SERIAL.println("*** Web interface not available ***"); + USB_SERIAL.println("*** Web interface and OTA not available ***"); USB_SERIAL.println("*** Only serial control available ***"); } @@ -215,6 +222,87 @@ void loop() } } +// Setup OTA Update functionality +void setupOTA() +{ + USB_SERIAL.println("Setting up OTA Update..."); + + // Serve OTA update page + server.on("/update", HTTP_GET, [](AsyncWebServerRequest *request) + { request->send_P(200, "text/html", ota_html); }); + + // Handle OTA update upload + server.on("/update", HTTP_POST, [](AsyncWebServerRequest *request) + { + bool shouldReboot = !Update.hasError(); + + AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", + shouldReboot ? "OK" : "FAIL"); + + response->addHeader("Connection", "close"); + request->send(response); + + if (shouldReboot) { + USB_SERIAL.println("OTA Update successful! Rebooting..."); + delay(1000); + ESP.restart(); + } else { + USB_SERIAL.println("OTA Update failed!"); + } }, handleOTAUpload); + + USB_SERIAL.println("OTA Update ready at: /update"); +} + +// Handle OTA upload process +void handleOTAUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) +{ + static unsigned long ota_progress_millis = 0; + + if (!index) + { + // Safety: Ensure motor is stopped during update + motor01.sendCommand(DSHOT_CMD_MOTOR_STOP); + setArmingStatus(false); + + USB_SERIAL.printf("OTA Update Start: %s\n", filename.c_str()); + + if (!Update.begin(UPDATE_SIZE_UNKNOWN)) + { + Update.printError(USB_SERIAL); + return; + } + } + + if (len) + { + if (Update.write(data, len) != len) + { + Update.printError(USB_SERIAL); + return; + } + + // Print progress every 2 seconds to avoid spam + if (millis() - ota_progress_millis > 2000) + { + size_t progress = index + len; + USB_SERIAL.printf("OTA Progress: %zu bytes\n", progress); + ota_progress_millis = millis(); + } + } + + if (final) + { + if (Update.end(true)) + { + USB_SERIAL.printf("OTA Update Success: %zu bytes\n", index + len); + } + else + { + Update.printError(USB_SERIAL); + } + } +} + // Connect to WiFi network bool connectToWiFi() { @@ -262,6 +350,7 @@ void printWiFiStatus() USB_SERIAL.printf("MAC Address: %s\n", WiFi.macAddress().c_str()); USB_SERIAL.println("***********************************************"); USB_SERIAL.printf("Web Interface: http://%s\n", WiFi.localIP().toString().c_str()); + USB_SERIAL.printf("OTA Update: http://%s/update\n", WiFi.localIP().toString().c_str()); USB_SERIAL.println("***********************************************"); } else @@ -305,10 +394,12 @@ void printMenu() if (wifi_connected) { USB_SERIAL.printf(" Web Interface: http://%s \n", WiFi.localIP().toString().c_str()); + USB_SERIAL.printf(" OTA Update: http://%s/update \n", WiFi.localIP().toString().c_str()); } else { USB_SERIAL.println(" Web Interface: NOT AVAILABLE "); + USB_SERIAL.println(" OTA Update: NOT AVAILABLE "); } USB_SERIAL.println("***********************************************"); @@ -321,6 +412,7 @@ void printMenu() USB_SERIAL.println(" info - Show motor info"); USB_SERIAL.println(" wifi - Show WiFi status"); USB_SERIAL.println(" reconnect - Reconnect to WiFi"); + USB_SERIAL.println(" ota - Show OTA info"); if (IS_BIDIRECTIONAL) { USB_SERIAL.println(" rpm - Get telemetry data"); @@ -362,6 +454,24 @@ void handleSerialInput(const String &input) return; } + if (input == "ota") + { + if (wifi_connected) + { + USB_SERIAL.println(" "); + USB_SERIAL.println("=== OTA UPDATE INFO ==="); + USB_SERIAL.printf("OTA Update URL: http://%s/update\n", WiFi.localIP().toString().c_str()); + USB_SERIAL.printf("Free Sketch Space: %u bytes\n", ESP.getFreeSketchSpace()); + USB_SERIAL.printf("Sketch Size: %u bytes\n", ESP.getSketchSize()); + USB_SERIAL.println("========================"); + } + else + { + USB_SERIAL.println("OTA Update not available - WiFi not connected!"); + } + return; + } + if (input == "reconnect") { USB_SERIAL.println("Reconnecting to WiFi..."); @@ -431,6 +541,7 @@ void handleSerialInput(const String &input) if (wifi_connected) { USB_SERIAL.printf("IP Address: %s\n", WiFi.localIP().toString().c_str()); + USB_SERIAL.printf("OTA URL: http://%s/update\n", WiFi.localIP().toString().c_str()); } return; } diff --git a/library.properties b/library.properties index 5aee282..6ea3a91 100644 --- a/library.properties +++ b/library.properties @@ -8,4 +8,4 @@ paragraph=This library can control a BlHeli_S by using encoded DShot commands. F category=Signal Input/Output url=https://github.com/derdoktor667/DShotRMT architectures=esp32 -provides_includes=DShotRMT.h, DShotCommandManager.h, dshot_commands.h, web_content.h \ No newline at end of file +provides_includes=DShotRMT.h, DShotCommandManager.h, dshot_commands.h, web_content.h, ota_update.h \ No newline at end of file diff --git a/src/ota_update.h b/src/ota_update.h new file mode 100644 index 0000000..695426e --- /dev/null +++ b/src/ota_update.h @@ -0,0 +1,249 @@ +/** + * @file ota_update.h + * @brief DShot signal generation using ESP32 RMT with bidirectional support + * @author Wastl Kraus + * @date 2025-09-13 + * @license MIT + */ + +#pragma once + +// OTA Update HTML +const char *ota_html = R"rawliteral( + + + + + OTA Update - DShotRMT + + + + + +
+

OTA Firmware Update

+ +
+ WARNING: Stop motors before starting update! +
+ +
+
+ + +
+ +
+ +
+
0%
+
+ +
+ +
+ + + + + +)rawliteral";