From 953b5bd07d411aeefb6f15ccfc7fb2b506082a98 Mon Sep 17 00:00:00 2001 From: bonamin Date: Mon, 29 Dec 2025 20:12:54 +0200 Subject: [PATCH] Added Reboot and Manual FW Update commands --- .../CommandHandler/CommandHandler.cpp | 106 ++++++++++++++++++ .../CommandHandler/CommandHandler.hpp | 5 + vesper/src/OTAManager/OTAManager.cpp | 48 ++++++++ vesper/src/OTAManager/OTAManager.hpp | 1 + 4 files changed, 160 insertions(+) diff --git a/vesper/src/Communication/CommandHandler/CommandHandler.cpp b/vesper/src/Communication/CommandHandler/CommandHandler.cpp index 82613ab..6ca0cdf 100644 --- a/vesper/src/Communication/CommandHandler/CommandHandler.cpp +++ b/vesper/src/Communication/CommandHandler/CommandHandler.cpp @@ -1035,6 +1035,12 @@ void CommandHandler::handleSystemCommand(JsonVariant contents, const MessageCont handleSetMqttLogLevelCommand(contents, context); } else if (action == "set_mqtt_enabled") { handleSetMqttEnabledCommand(contents, context); + } else if (action == "restart" || action == "reboot") { + handleRestartCommand(context); + } else if (action == "force_update") { + handleForceUpdateCommand(contents, context); + } else if (action == "custom_update") { + handleCustomUpdateCommand(contents, context); } else { LOG_WARNING("Unknown system action: %s", action.c_str()); sendErrorResponse("system", "Unknown action: " + action, context); @@ -1172,3 +1178,103 @@ void CommandHandler::handleSetMqttEnabledCommand(JsonVariant contents, const Mes } } +// ════════════════════════════════════════════════════════════════════════════ +// RESTART COMMAND +// ════════════════════════════════════════════════════════════════════════════ + +void CommandHandler::handleRestartCommand(const MessageContext& context) { + LOG_WARNING("🔄 Device restart requested via command"); + sendSuccessResponse("restart", "Device will restart in 2 seconds", context); + + // Small delay to ensure response is sent + delay(2000); + + // Restart the ESP32 + ESP.restart(); +} + +// ════════════════════════════════════════════════════════════════════════════ +// FORCE UPDATE COMMAND +// ════════════════════════════════════════════════════════════════════════════ + +void CommandHandler::handleForceUpdateCommand(JsonVariant contents, const MessageContext& context) { + LOG_WARNING("🔄 Force OTA update requested via command"); + + // Check if player is active + if (_player && _player->isPlaying()) { + sendErrorResponse("force_update", "Cannot update while playback is active", context); + LOG_WARNING("Force update rejected - player is active"); + return; + } + + // Get optional channel parameter (defaults to "stable") + String channel = "stable"; + if (contents.containsKey("channel")) { + channel = contents["channel"].as(); + } + + sendSuccessResponse("force_update", + "Starting forced OTA update from channel: " + channel + ". Device may reboot.", context); + + // Small delay to ensure response is sent + delay(1000); + + // Perform the update + bool result = _otaManager.performManualUpdate(channel); + + // Note: If update succeeds, device will reboot and this won't be reached + if (!result) { + LOG_ERROR("Force update failed"); + // Error response may not be received if we already restarted + } +} + +// ════════════════════════════════════════════════════════════════════════════ +// CUSTOM UPDATE COMMAND +// ════════════════════════════════════════════════════════════════════════════ + +void CommandHandler::handleCustomUpdateCommand(JsonVariant contents, const MessageContext& context) { + LOG_WARNING("🔥 Custom OTA update requested via command"); + + // Validate required parameters + if (!contents.containsKey("firmware_url")) { + sendErrorResponse("custom_update", "Missing firmware_url parameter", context); + return; + } + + String firmwareUrl = contents["firmware_url"].as(); + + // Optional parameters + String checksum = contents.containsKey("checksum") ? + contents["checksum"].as() : ""; + size_t fileSize = contents.containsKey("file_size") ? + contents["file_size"].as() : 0; + + // Check if player is active + if (_player && _player->isPlaying()) { + sendErrorResponse("custom_update", "Cannot update while playback is active", context); + LOG_WARNING("Custom update rejected - player is active"); + return; + } + + LOG_INFO("Custom update: URL=%s, Checksum=%s, Size=%u", + firmwareUrl.c_str(), + checksum.isEmpty() ? "none" : checksum.c_str(), + fileSize); + + sendSuccessResponse("custom_update", + "Starting custom OTA update. Device may reboot.", context); + + // Small delay to ensure response is sent + delay(1000); + + // Perform the custom update + bool result = _otaManager.performCustomUpdate(firmwareUrl, checksum, fileSize); + + // Note: If update succeeds, device will reboot and this won't be reached + if (!result) { + LOG_ERROR("Custom update failed"); + // Error response may not be received if we already restarted + } +} + diff --git a/vesper/src/Communication/CommandHandler/CommandHandler.hpp b/vesper/src/Communication/CommandHandler/CommandHandler.hpp index efe29b9..574ddce 100644 --- a/vesper/src/Communication/CommandHandler/CommandHandler.hpp +++ b/vesper/src/Communication/CommandHandler/CommandHandler.hpp @@ -153,4 +153,9 @@ private: // MQTT Control Commands void handleSetMqttEnabledCommand(JsonVariant contents, const MessageContext& context); + + // Device Control Commands + void handleRestartCommand(const MessageContext& context); + void handleForceUpdateCommand(JsonVariant contents, const MessageContext& context); + void handleCustomUpdateCommand(JsonVariant contents, const MessageContext& context); }; diff --git a/vesper/src/OTAManager/OTAManager.cpp b/vesper/src/OTAManager/OTAManager.cpp index 27d6470..a99f4d1 100644 --- a/vesper/src/OTAManager/OTAManager.cpp +++ b/vesper/src/OTAManager/OTAManager.cpp @@ -701,6 +701,54 @@ bool OTAManager::performManualUpdate(const String& channel) { return installFromSD("/firmware/staged_update.bin"); } +// ════════════════════════════════════════════════════════════════════════════ +// CUSTOM FIRMWARE UPDATE +// ════════════════════════════════════════════════════════════════════════════ + +bool OTAManager::performCustomUpdate(const String& firmwareUrl, const String& checksum, size_t fileSize) { + if (_status != Status::IDLE) { + LOG_WARNING("OTA update already in progress"); + return false; + } + + // Check if player is active + if (isPlayerActive()) { + LOG_ERROR("Cannot perform custom update: Player is active"); + setStatus(Status::FAILED, ErrorCode::PLAYER_ACTIVE); + return false; + } + + LOG_INFO("🔥 Starting CUSTOM firmware update..."); + LOG_INFO(" URL: %s", firmwareUrl.c_str()); + LOG_INFO(" Checksum: %s", checksum.isEmpty() ? "NOT PROVIDED" : checksum.c_str()); + LOG_INFO(" File Size: %u bytes", fileSize); + + if (checksum.isEmpty()) { + LOG_WARNING("⚠️ No checksum provided - update will proceed without verification!"); + } + + setStatus(Status::DOWNLOADING); + + // Download firmware from custom URL to SD + if (!downloadToSD(firmwareUrl, checksum, fileSize)) { + LOG_ERROR("Custom firmware download failed"); + return false; + } + + LOG_INFO("✅ Custom firmware downloaded successfully"); + + // Install from SD + bool result = installFromSD("/firmware/staged_update.bin"); + + if (result) { + LOG_INFO("🚀 Custom firmware installed - device will reboot"); + } else { + LOG_ERROR("❌ Custom firmware installation failed"); + } + + return result; +} + // Hardware variant management String OTAManager::getHardwareVariant() const { return _configManager.getHardwareVariant(); diff --git a/vesper/src/OTAManager/OTAManager.hpp b/vesper/src/OTAManager/OTAManager.hpp index ca97fda..f9f2dd8 100644 --- a/vesper/src/OTAManager/OTAManager.hpp +++ b/vesper/src/OTAManager/OTAManager.hpp @@ -77,6 +77,7 @@ public: void checkFirmwareUpdateFromSD(); // Check SD for firmware update bool performManualUpdate(); // Manual update triggered by app bool performManualUpdate(const String& channel); // Manual update from specific channel + bool performCustomUpdate(const String& firmwareUrl, const String& checksum = "", size_t fileSize = 0); // Custom firmware update // Hardware identification String getHardwareVariant() const;