Fixed MQTT and WS Routing - w/o SSL

This commit is contained in:
2025-10-13 17:34:54 +03:00
parent f696984cd1
commit 956786321a
29 changed files with 2043 additions and 1210 deletions

View File

@@ -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;
}