Fixed MQTT and WS Routing - w/o SSL
This commit is contained in:
@@ -1,33 +1,109 @@
|
||||
#include "OTAManager.hpp"
|
||||
#include "../ConfigManager/ConfigManager.hpp"
|
||||
#include "../Logging/Logging.hpp"
|
||||
#include "../Player/Player.hpp"
|
||||
#include <nvs_flash.h>
|
||||
#include <nvs.h>
|
||||
|
||||
OTAManager::OTAManager(ConfigManager& configManager)
|
||||
: _configManager(configManager)
|
||||
, _fileManager(nullptr)
|
||||
, _player(nullptr)
|
||||
, _status(Status::IDLE)
|
||||
, _lastError(ErrorCode::NONE)
|
||||
, _availableVersion(0.0f)
|
||||
, _minVersion(0.0f)
|
||||
, _expectedFileSize(0)
|
||||
, _updateAvailable(false)
|
||||
, _availableChecksum("")
|
||||
, _updateChannel("stable")
|
||||
, _isMandatory(false)
|
||||
, _isEmergency(false)
|
||||
, _progressCallback(nullptr)
|
||||
, _statusCallback(nullptr) {
|
||||
, _statusCallback(nullptr)
|
||||
, _scheduledCheckTimer(NULL) {
|
||||
}
|
||||
|
||||
OTAManager::~OTAManager() {
|
||||
if (_scheduledCheckTimer != NULL) {
|
||||
xTimerStop(_scheduledCheckTimer, 0);
|
||||
xTimerDelete(_scheduledCheckTimer, portMAX_DELAY);
|
||||
_scheduledCheckTimer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void OTAManager::begin() {
|
||||
LOG_INFO("OTA Manager initialized");
|
||||
setStatus(Status::IDLE);
|
||||
|
||||
// Create timer for scheduled checks (checks every minute if it's 3:00 AM)
|
||||
_scheduledCheckTimer = xTimerCreate(
|
||||
"OTA_Schedule",
|
||||
pdMS_TO_TICKS(60000), // Check every minute
|
||||
pdTRUE, // Auto-reload (periodic)
|
||||
this, // Timer ID (pass OTAManager instance)
|
||||
scheduledCheckCallback
|
||||
);
|
||||
|
||||
if (_scheduledCheckTimer != NULL) {
|
||||
xTimerStart(_scheduledCheckTimer, 0);
|
||||
LOG_INFO("OTA scheduled check timer started (will check at 3:00 AM)");
|
||||
} else {
|
||||
LOG_ERROR("Failed to create OTA scheduled check timer!");
|
||||
}
|
||||
}
|
||||
|
||||
void OTAManager::setFileManager(FileManager* fm) {
|
||||
_fileManager = fm;
|
||||
}
|
||||
|
||||
void OTAManager::setPlayer(Player* player) {
|
||||
_player = player;
|
||||
}
|
||||
|
||||
// ✅ NEW: Static timer callback for scheduled checks
|
||||
void OTAManager::scheduledCheckCallback(TimerHandle_t xTimer) {
|
||||
OTAManager* ota = static_cast<OTAManager*>(pvTimerGetTimerID(xTimer));
|
||||
|
||||
// Get current time
|
||||
time_t now = time(nullptr);
|
||||
struct tm* timeinfo = localtime(&now);
|
||||
|
||||
// Only proceed if it's exactly 3:00 AM
|
||||
if (timeinfo->tm_hour == 3 && timeinfo->tm_min == 0) {
|
||||
LOG_INFO("🕒 3:00 AM - Running scheduled OTA check");
|
||||
|
||||
// Check if player is idle before proceeding
|
||||
if (!ota->isPlayerActive()) {
|
||||
LOG_INFO("✅ Player is idle - checking for emergency updates");
|
||||
ota->checkForEmergencyUpdates();
|
||||
} else {
|
||||
LOG_WARNING("⚠️ Player is active - skipping scheduled update check");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ NEW: Check for emergency updates only (called by scheduled timer)
|
||||
void OTAManager::checkForEmergencyUpdates() {
|
||||
if (_status != Status::IDLE) {
|
||||
LOG_WARNING("OTA check already in progress");
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO("Checking for EMERGENCY updates only...");
|
||||
checkForUpdates("stable"); // Check stable channel
|
||||
|
||||
// Only proceed if emergency flag is set
|
||||
if (_updateAvailable && _isEmergency) {
|
||||
LOG_INFO("🚨 EMERGENCY update detected during scheduled check - updating immediately");
|
||||
update("stable");
|
||||
} else if (_updateAvailable && _isMandatory) {
|
||||
LOG_INFO("⚠️ Mandatory update available, but will wait for next boot");
|
||||
} else {
|
||||
LOG_INFO("✅ No emergency updates available");
|
||||
}
|
||||
}
|
||||
|
||||
void OTAManager::checkForUpdates() {
|
||||
// Boot-time check: only check stable channel for emergency/mandatory updates
|
||||
checkForUpdates("stable");
|
||||
@@ -39,6 +115,12 @@ void OTAManager::checkForUpdates(const String& channel) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 🔥 CRITICAL: Check network connectivity before attempting HTTP requests
|
||||
if (WiFi.status() != WL_CONNECTED && !ETH.linkUp()) {
|
||||
LOG_WARNING("OTA check skipped - no network connectivity");
|
||||
return;
|
||||
}
|
||||
|
||||
setStatus(Status::CHECKING_VERSION);
|
||||
LOG_INFO("Checking for firmware updates in %s channel for %s...",
|
||||
channel.c_str(), _configManager.getHardwareVariant().c_str());
|
||||
@@ -118,7 +200,7 @@ void OTAManager::notifyProgress(size_t current, size_t total) {
|
||||
}
|
||||
}
|
||||
|
||||
// Enhanced version checking with channel support and multiple servers
|
||||
// ✅ ENHANCED: Version checking with full validation
|
||||
bool OTAManager::checkVersion(const String& channel) {
|
||||
std::vector<String> servers = _configManager.getUpdateServers();
|
||||
auto& updateConfig = _configManager.getUpdateConfig();
|
||||
@@ -167,6 +249,16 @@ bool OTAManager::checkVersion(const String& channel) {
|
||||
_updateChannel = doc["channel"].as<String>();
|
||||
_isMandatory = doc["mandatory"].as<bool>();
|
||||
_isEmergency = doc["emergency"].as<bool>();
|
||||
_minVersion = doc["minVersion"].as<float>(); // ✅ NEW
|
||||
_expectedFileSize = doc["fileSize"].as<size_t>(); // ✅ NEW
|
||||
|
||||
// ✅ NEW: Validate channel matches requested
|
||||
if (_updateChannel != channel) {
|
||||
LOG_ERROR("OTA: Channel mismatch! Requested: %s, Got: %s",
|
||||
channel.c_str(), _updateChannel.c_str());
|
||||
_lastError = ErrorCode::CHANNEL_MISMATCH;
|
||||
continue; // Try next server
|
||||
}
|
||||
|
||||
// Validate hardware variant matches
|
||||
String hwVariant = doc["hardwareVariant"].as<String>();
|
||||
@@ -177,6 +269,16 @@ bool OTAManager::checkVersion(const String& channel) {
|
||||
continue; // Try next server
|
||||
}
|
||||
|
||||
// ✅ NEW: Check minVersion compatibility
|
||||
float currentVersion = getCurrentVersion();
|
||||
if (_minVersion > 0.0f && currentVersion < _minVersion) {
|
||||
LOG_ERROR("OTA: Current version %.1f is below minimum required %.1f",
|
||||
currentVersion, _minVersion);
|
||||
LOG_ERROR("OTA: Intermediate update required first - cannot proceed");
|
||||
_lastError = ErrorCode::VERSION_TOO_LOW;
|
||||
continue; // Try next server
|
||||
}
|
||||
|
||||
if (_availableVersion == 0.0f) {
|
||||
LOG_ERROR("OTA: Invalid version in metadata from %s", baseUrl.c_str());
|
||||
continue; // Try next server
|
||||
@@ -188,6 +290,8 @@ bool OTAManager::checkVersion(const String& channel) {
|
||||
}
|
||||
|
||||
LOG_INFO("OTA: Successfully got metadata from %s", baseUrl.c_str());
|
||||
LOG_INFO("OTA: Expected file size: %u bytes, Min version: %.1f",
|
||||
_expectedFileSize, _minVersion);
|
||||
return true; // Success!
|
||||
} else {
|
||||
LOG_ERROR("OTA: Server %s failed after %d retries. HTTP error: %d",
|
||||
@@ -202,7 +306,7 @@ bool OTAManager::checkVersion(const String& channel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Enhanced download and install with channel support and multiple servers
|
||||
// ✅ ENHANCED: Download and install with size validation
|
||||
bool OTAManager::downloadAndInstall(const String& channel) {
|
||||
std::vector<String> servers = _configManager.getUpdateServers();
|
||||
|
||||
@@ -213,7 +317,7 @@ bool OTAManager::downloadAndInstall(const String& channel) {
|
||||
LOG_INFO("OTA: Trying firmware download from server %d/%d: %s",
|
||||
serverIndex + 1, servers.size(), baseUrl.c_str());
|
||||
|
||||
if (downloadToSD(firmwareUrl, _availableChecksum)) {
|
||||
if (downloadToSD(firmwareUrl, _availableChecksum, _expectedFileSize)) {
|
||||
// Success! Now install from SD
|
||||
return installFromSD("/firmware/staged_update.bin");
|
||||
} else {
|
||||
@@ -226,9 +330,7 @@ bool OTAManager::downloadAndInstall(const String& channel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OTAManager::downloadToSD(const String& url, const String& expectedChecksum) {
|
||||
// This method now receives the exact firmware URL from downloadAndInstall
|
||||
// The server selection logic is handled there
|
||||
bool OTAManager::downloadToSD(const String& url, const String& expectedChecksum, size_t expectedSize) {
|
||||
if (!_fileManager) {
|
||||
LOG_ERROR("FileManager not set!");
|
||||
setStatus(Status::FAILED, ErrorCode::DOWNLOAD_FAILED);
|
||||
@@ -260,6 +362,24 @@ bool OTAManager::downloadToSD(const String& url, const String& expectedChecksum)
|
||||
return false;
|
||||
}
|
||||
|
||||
// ✅ NEW: Validate file size against metadata
|
||||
if (expectedSize > 0 && (size_t)contentLength != expectedSize) {
|
||||
LOG_ERROR("OTA: File size mismatch! Expected: %u, Got: %d", expectedSize, contentLength);
|
||||
setStatus(Status::FAILED, ErrorCode::SIZE_MISMATCH);
|
||||
http.end();
|
||||
return false;
|
||||
}
|
||||
|
||||
// ✅ NEW: Check available SD card space
|
||||
if (!checkAvailableSpace(contentLength)) {
|
||||
LOG_ERROR("OTA: Insufficient SD card space for update");
|
||||
setStatus(Status::FAILED, ErrorCode::INSUFFICIENT_SPACE);
|
||||
http.end();
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_INFO("OTA: Starting download of %d bytes...", contentLength);
|
||||
|
||||
// Open file for writing
|
||||
File file = SD.open(tempPath.c_str(), FILE_WRITE);
|
||||
if (!file) {
|
||||
@@ -272,8 +392,9 @@ bool OTAManager::downloadToSD(const String& url, const String& expectedChecksum)
|
||||
WiFiClient* stream = http.getStreamPtr();
|
||||
uint8_t buffer[1024];
|
||||
size_t written = 0;
|
||||
size_t lastLoggedPercent = 0;
|
||||
|
||||
while (http.connected() && written < contentLength) {
|
||||
while (http.connected() && written < (size_t)contentLength) {
|
||||
size_t available = stream->available();
|
||||
if (available) {
|
||||
size_t toRead = min(available, sizeof(buffer));
|
||||
@@ -289,7 +410,17 @@ bool OTAManager::downloadToSD(const String& url, const String& expectedChecksum)
|
||||
return false;
|
||||
}
|
||||
written += bytesWritten;
|
||||
|
||||
// ✅ IMPROVED: Progress reporting with percentage
|
||||
notifyProgress(written, contentLength);
|
||||
|
||||
// Log progress every 10%
|
||||
size_t currentPercent = (written * 100) / contentLength;
|
||||
if (currentPercent >= lastLoggedPercent + 10) {
|
||||
LOG_INFO("OTA: Download progress: %u%% (%u/%u bytes)",
|
||||
currentPercent, written, contentLength);
|
||||
lastLoggedPercent = currentPercent;
|
||||
}
|
||||
}
|
||||
}
|
||||
yield();
|
||||
@@ -298,13 +429,13 @@ bool OTAManager::downloadToSD(const String& url, const String& expectedChecksum)
|
||||
file.close();
|
||||
http.end();
|
||||
|
||||
if (written != contentLength) {
|
||||
LOG_ERROR("Download incomplete: %d/%d bytes", written, contentLength);
|
||||
if (written != (size_t)contentLength) {
|
||||
LOG_ERROR("Download incomplete: %u/%d bytes", written, contentLength);
|
||||
setStatus(Status::FAILED, ErrorCode::DOWNLOAD_FAILED);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_INFO("Download complete (%d bytes)", written);
|
||||
LOG_INFO("Download complete (%u bytes)", written);
|
||||
|
||||
// Verify checksum
|
||||
if (!verifyChecksum(tempPath, expectedChecksum)) {
|
||||
@@ -522,7 +653,7 @@ bool OTAManager::performManualUpdate(const String& channel) {
|
||||
String firmwareUrl = buildFirmwareUrl(channel);
|
||||
|
||||
// Download to SD first
|
||||
if (!downloadToSD(firmwareUrl, _availableChecksum)) {
|
||||
if (!downloadToSD(firmwareUrl, _availableChecksum, _expectedFileSize)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -537,7 +668,6 @@ String OTAManager::getHardwareVariant() const {
|
||||
|
||||
void OTAManager::setHardwareVariant(const String& variant) {
|
||||
LOG_WARNING("OTAManager::setHardwareVariant is deprecated. Use ConfigManager::setHardwareVariant instead");
|
||||
// For backward compatibility, we could call configManager, but it's better to use ConfigManager directly
|
||||
}
|
||||
|
||||
// URL builders for multi-channel architecture
|
||||
@@ -556,6 +686,47 @@ String OTAManager::buildFirmwareUrl(const String& channel) const {
|
||||
return buildChannelUrl(channel) + "firmware.bin";
|
||||
}
|
||||
|
||||
// ✅ NEW: Check if player is currently active
|
||||
bool OTAManager::isPlayerActive() const {
|
||||
if (!_player) {
|
||||
// If player reference not set, assume it's safe to update
|
||||
return false;
|
||||
}
|
||||
|
||||
// Player is active if it's playing or paused (not stopped)
|
||||
return _player->isCurrentlyPlaying() || _player->isCurrentlyPaused();
|
||||
}
|
||||
|
||||
// ✅ NEW: Check if SD card has enough free space
|
||||
bool OTAManager::checkAvailableSpace(size_t requiredBytes) const {
|
||||
if (!_fileManager) {
|
||||
LOG_WARNING("OTA: FileManager not set, cannot check available space");
|
||||
return true; // Assume it's okay if we can't check
|
||||
}
|
||||
|
||||
// Add 10% safety margin
|
||||
size_t requiredWithMargin = requiredBytes + (requiredBytes / 10);
|
||||
|
||||
// Get SD card info
|
||||
uint64_t totalBytes = SD.totalBytes();
|
||||
uint64_t usedBytes = SD.usedBytes();
|
||||
uint64_t freeBytes = totalBytes - usedBytes;
|
||||
|
||||
LOG_INFO("OTA: SD card space - Total: %llu MB, Used: %llu MB, Free: %llu MB",
|
||||
totalBytes / (1024 * 1024),
|
||||
usedBytes / (1024 * 1024),
|
||||
freeBytes / (1024 * 1024));
|
||||
|
||||
if (freeBytes < requiredWithMargin) {
|
||||
LOG_ERROR("OTA: Insufficient space! Required: %u bytes (+10%% margin), Available: %llu bytes",
|
||||
requiredWithMargin, freeBytes);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_INFO("OTA: Sufficient space available for update");
|
||||
return true;
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// HEALTH CHECK IMPLEMENTATION
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
@@ -599,5 +770,11 @@ bool OTAManager::isHealthy() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if scheduled timer is running
|
||||
if (_scheduledCheckTimer == NULL || xTimerIsTimerActive(_scheduledCheckTimer) == pdFALSE) {
|
||||
LOG_DEBUG("OTAManager: Unhealthy - Scheduled check timer not running");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* This class manages over-the-air firmware updates with safe, reliable
|
||||
* update mechanisms, version checking, and comprehensive error handling.
|
||||
*
|
||||
* 📋 VERSION: 2.0 (Enhanced OTA management)
|
||||
* 📋 VERSION: 2.1 (Enhanced with scheduled checks and full validation)
|
||||
* 📅 DATE: 2025
|
||||
* 👨💻 AUTHOR: Advanced Bell Systems
|
||||
* ═══════════════════════════════════════════════════════════════════════════════════
|
||||
@@ -24,9 +24,11 @@
|
||||
#include <mbedtls/md.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <functional>
|
||||
#include <time.h>
|
||||
#include "../FileManager/FileManager.hpp"
|
||||
|
||||
class ConfigManager; // Forward declaration
|
||||
class Player; // Forward declaration for idle check
|
||||
|
||||
class OTAManager {
|
||||
public:
|
||||
@@ -48,7 +50,11 @@ public:
|
||||
WRITE_FAILED,
|
||||
VERIFICATION_FAILED,
|
||||
CHECKSUM_MISMATCH,
|
||||
METADATA_PARSE_FAILED
|
||||
METADATA_PARSE_FAILED,
|
||||
SIZE_MISMATCH,
|
||||
VERSION_TOO_LOW,
|
||||
CHANNEL_MISMATCH,
|
||||
PLAYER_ACTIVE
|
||||
};
|
||||
|
||||
// Callback types
|
||||
@@ -56,11 +62,16 @@ public:
|
||||
using StatusCallback = std::function<void(Status status, ErrorCode error)>;
|
||||
|
||||
explicit OTAManager(ConfigManager& configManager);
|
||||
~OTAManager();
|
||||
|
||||
void begin();
|
||||
void setFileManager(FileManager* fm);
|
||||
void setPlayer(Player* player); // NEW: Set player reference for idle check
|
||||
|
||||
void checkForUpdates();
|
||||
void checkForUpdates(const String& channel); // Check specific channel
|
||||
void checkForEmergencyUpdates(); // NEW: Scheduled emergency-only check
|
||||
|
||||
void update();
|
||||
void update(const String& channel); // Update from specific channel
|
||||
void checkFirmwareUpdateFromSD(); // Check SD for firmware update
|
||||
@@ -92,9 +103,12 @@ public:
|
||||
private:
|
||||
ConfigManager& _configManager;
|
||||
FileManager* _fileManager;
|
||||
Player* _player; // NEW: Player reference for idle check
|
||||
Status _status;
|
||||
ErrorCode _lastError;
|
||||
float _availableVersion;
|
||||
float _minVersion; // NEW: Minimum required version
|
||||
size_t _expectedFileSize; // NEW: Expected firmware file size
|
||||
bool _updateAvailable;
|
||||
String _availableChecksum;
|
||||
String _updateChannel;
|
||||
@@ -104,6 +118,10 @@ private:
|
||||
ProgressCallback _progressCallback;
|
||||
StatusCallback _statusCallback;
|
||||
|
||||
// NEW: Scheduled check timer
|
||||
TimerHandle_t _scheduledCheckTimer;
|
||||
static void scheduledCheckCallback(TimerHandle_t xTimer);
|
||||
|
||||
void setStatus(Status status, ErrorCode error = ErrorCode::NONE);
|
||||
void notifyProgress(size_t current, size_t total);
|
||||
bool checkVersion();
|
||||
@@ -111,11 +129,15 @@ private:
|
||||
bool checkChannelsMetadata();
|
||||
bool downloadAndInstall();
|
||||
bool downloadAndInstall(const String& channel);
|
||||
bool downloadToSD(const String& url, const String& expectedChecksum);
|
||||
bool downloadToSD(const String& url, const String& expectedChecksum, size_t expectedSize); // NEW: Added size param
|
||||
bool verifyChecksum(const String& filePath, const String& expectedChecksum);
|
||||
String calculateSHA256(const String& filePath);
|
||||
bool installFromSD(const String& filePath);
|
||||
String buildChannelUrl(const String& channel) const;
|
||||
String buildMetadataUrl(const String& channel) const;
|
||||
String buildFirmwareUrl(const String& channel) const;
|
||||
|
||||
// NEW: Helper methods
|
||||
bool isPlayerActive() const;
|
||||
bool checkAvailableSpace(size_t requiredBytes) const;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user