246 lines
7.5 KiB
C++
246 lines
7.5 KiB
C++
#pragma once
|
|
|
|
#include <ArduinoJson.h>
|
|
#include <HTTPClient.h>
|
|
#include <Update.h>
|
|
#include <FS.h>
|
|
#include <memory>
|
|
#include <ESPAsyncWebServer.h>
|
|
#include "AppVersion.h"
|
|
|
|
//#define DEFAULT_MANIFEST_URL "https://storage.googleapis.com/boothifier/latest/"
|
|
#define DEFAULT_MANIFEST_URL "https://minio.boothwizard.com/boothifier/latest/"
|
|
#define BUFFER_SIZE 2048 // Reduced from 4096 to use less memory
|
|
// Maximum allowed manifest size (bytes) to protect memory
|
|
#define MAX_MANIFEST_SIZE (64 * 1024)
|
|
|
|
// Number of HTTP retry attempts for transient failures
|
|
#define HTTP_RETRY_COUNT 5 // Increased from 3
|
|
#define HTTP_RETRY_DELAY_MS 1000 // Increased from 500
|
|
|
|
/**
|
|
* @brief Update mode enumeration
|
|
*/
|
|
enum class UpdateMode {
|
|
UPDATE_FILES_ONLY, ///< Update files only, skip firmware
|
|
UPDATE_FIRMWARE_ONLY, ///< Update firmware only, skip files
|
|
UPDATE_BOTH ///< Update both files and firmware (default)
|
|
};
|
|
|
|
// Allow external cancellation
|
|
extern volatile bool g_UpdateCancelFlag;
|
|
|
|
// Global update mode setting
|
|
extern UpdateMode g_UpdateMode;
|
|
|
|
extern TaskHandle_t Update_Task_Handle;
|
|
|
|
/**
|
|
* @brief Version information structure
|
|
*/
|
|
|
|
|
|
//bool versionCompare(Version& remoteVersion, Version& localVersion);
|
|
|
|
extern Version otaVersion;
|
|
|
|
/**
|
|
* @brief File information structure
|
|
*/
|
|
struct FileInfo {
|
|
String remotePath; ///< Path on remote server
|
|
String localPath; ///< Path in local filesystem
|
|
String md5; ///< MD5 hash for verification
|
|
//size_t size; ///< File size in bytes
|
|
};
|
|
|
|
/**
|
|
* @class AppUpdater
|
|
* @brief Handles firmware and filesystem updates
|
|
*/
|
|
class AppUpdater {
|
|
public:
|
|
Version localVersion;
|
|
Version otaVersion;
|
|
// Base URL (bucket) for update resources. Supports either Google Cloud Storage or MinIO (or any HTTP host)
|
|
String baseUrl;
|
|
const char* appName;
|
|
const char* manifestName;
|
|
JsonDocument jsonManifest;
|
|
JsonArray jsonFilesArray;
|
|
|
|
/**
|
|
* @brief Update status enumeration
|
|
*/
|
|
enum class UpdateStatus {
|
|
IDLE, ///< No update in progress
|
|
MESSAGE, ///< Update message
|
|
DOWNLOADING, ///< Downloading files
|
|
VERIFYING, ///< Verifying file integrity
|
|
FILE_SKIPPED, ///< File already up to date
|
|
FILE_SAVED, ///< Saving to filesystem
|
|
MD5_FAILED, ///< MD5 verification failed
|
|
COMPLETE, ///< Update complete
|
|
ERROR ///< Error occurred
|
|
};
|
|
|
|
/**
|
|
* @brief Constructor
|
|
* @param version Current firmware version
|
|
* @param bucket Base URL for updates
|
|
* @param fs Filesystem reference
|
|
*/
|
|
AppUpdater(fs::FS& fs, Version localVersion, const char* bucket, const char* manifestName ="update.json", const char* appBin = "firmware.bin" );
|
|
|
|
/**
|
|
* @brief Change the base URL after construction (e.g. switch between MinIO and GCS)
|
|
*/
|
|
void setBaseUrl(const String& newBaseUrl) { baseUrl = newBaseUrl; }
|
|
|
|
/**
|
|
* @brief Get the currently configured base URL
|
|
*/
|
|
const String& getBaseUrl() const { return baseUrl; }
|
|
|
|
/**
|
|
* @brief Set update mode (files only, firmware only, or both)
|
|
*/
|
|
void setUpdateMode(UpdateMode mode);
|
|
|
|
/**
|
|
* @brief Get current update mode
|
|
*/
|
|
UpdateMode getUpdateMode() const;
|
|
|
|
/**
|
|
* @brief Set progress callback function
|
|
* @param callback Function to call with progress updates
|
|
*/
|
|
void setProgressCallback(void (*callback)( UpdateStatus status, int percentage, const char* message));
|
|
|
|
/**
|
|
* @brief Check for and apply updates
|
|
* @return true if update successful
|
|
*/
|
|
bool IsUpdateAvailable(void);
|
|
|
|
/**
|
|
* @brief Update files from manifest
|
|
* @param manifestPath Path to manifest file
|
|
* @return true if all files updated successfully
|
|
*/
|
|
//bool updateFilesFromManifest(const char* manifestPath = DEFAULT_MANIFEST_URL);
|
|
bool updateFilesArray(void);
|
|
|
|
/**
|
|
* @brief Update single file
|
|
* @param remotePath Remote file path
|
|
* @param localPath Local file path
|
|
* @param expectedMd5 Expected MD5 hash
|
|
* @return true if file updated successfully
|
|
*/
|
|
bool updateFile(const char* remotePath, const char* localPath, const char* expectedMd5);
|
|
|
|
/**
|
|
* @brief Results from checkManifest
|
|
*/
|
|
enum class ManifestCheckResult {
|
|
ERROR_FETCH_FAILED, ///< Failed to fetch manifest
|
|
ERROR_TOO_LARGE, ///< Manifest too large
|
|
ERROR_PARSE_FAILED, ///< Failed to parse manifest JSON
|
|
ERROR_NO_FILES_SECTION, ///< No files section in manifest
|
|
ERROR_NO_VERSION, ///< No version section in manifest
|
|
VERSION_CURRENT, ///< Current version is same or newer
|
|
UPDATE_AVAILABLE ///< New version available
|
|
};
|
|
|
|
/**
|
|
* @brief Get manifest content
|
|
* @param manifestPath Path to manifest file
|
|
* @return Result indicating success, failure, or version status
|
|
*/
|
|
ManifestCheckResult checkManifest(void);
|
|
|
|
bool updateApp(void);
|
|
|
|
String getVersion(void);
|
|
|
|
private:
|
|
typedef void (*ProgressCallback)(UpdateStatus status, int percentage, const char* message);
|
|
ProgressCallback progressCb;
|
|
fs::FS& fileSystem;
|
|
UpdateStatus status;
|
|
std::unique_ptr<uint8_t[]> downloadBuffer;
|
|
bool updateAvailable = false;
|
|
UpdateMode updateMode = UpdateMode::UPDATE_BOTH; // Default to updating both files and firmware
|
|
|
|
|
|
|
|
/**
|
|
* @brief Verify and save file
|
|
* @param stream Input stream
|
|
* @param contentLength Expected content length
|
|
* @param localPath Local file path
|
|
* @param expectedMd5 Expected MD5 hash
|
|
* @return true if successful
|
|
*/
|
|
bool verifyAndSaveFile(WiFiClient* stream, size_t contentLength,
|
|
const char* localPath, const char* remotePath, const char* expectedMd5);
|
|
|
|
/**
|
|
* @brief Update progress callback
|
|
* @param percentage Progress percentage
|
|
* @param newStatus Current status
|
|
*/
|
|
void updateProgress(UpdateStatus newStatus, int percentage, const char* message = nullptr);
|
|
|
|
/**
|
|
* @brief Build a full URL by combining baseUrl and a relative path. Absolute (http/https) paths pass through.
|
|
* Ensures exactly one slash joins base and path. Leading slash on path is stripped.
|
|
*/
|
|
String buildUrl(const char* path) const;
|
|
|
|
String getLocalMD5(const char* filePath);
|
|
};
|
|
|
|
|
|
|
|
// Queue handle for firmware update messages
|
|
extern QueueHandle_t updateMsgQueue;
|
|
|
|
// Message structure for update progress
|
|
struct UpdateMessage {
|
|
String message;
|
|
bool complete;
|
|
int progress;
|
|
};
|
|
|
|
/**
|
|
* @brief Firmware update task
|
|
* @param param Task parameters
|
|
*/
|
|
void firmwareUpdateTask(void* param);
|
|
|
|
void startFirmwareUpdateTask(AsyncEventSource* eventSrc);
|
|
|
|
void loadUpdateJson(void);
|
|
|
|
void updateProgress(AppUpdater::UpdateStatus status, int percentage, const char* message);
|
|
|
|
void sendUpdateMessage(const char* message, bool complete, int progress);
|
|
|
|
void handleUpdateProgress(AsyncWebServerRequest *request);
|
|
|
|
|
|
void startVersionCheckTask();
|
|
|
|
void versionCheckTask(void* parameter);
|
|
|
|
// Convenience functions for setting update mode
|
|
void setGlobalUpdateMode(UpdateMode mode);
|
|
UpdateMode getGlobalUpdateMode();
|
|
void setUpdateModeFilesOnly();
|
|
void setUpdateModeFirmwareOnly();
|
|
void setUpdateModeBoth();
|
|
|