1. 优化了cpp_json的内容,使其更modern

2. 稍微优化了一下系统配置类
3. 增加了系统版本号,便于区分系统版本,方便OTA
4. 重写OTA的逻辑,完成了Cpp的OTA封装,测试通过
This commit is contained in:
Misaki
2025-09-24 04:01:23 +08:00
parent 6c4749ba0c
commit a47e20cb64
18 changed files with 908 additions and 458 deletions
+248
View File
@@ -0,0 +1,248 @@
//
// Created by misaki on 2025/9/24.
//
#include "HttpOtaUpdater.h"
#include <esp_http_client.h>
#include <cstring>
HttpOtaUpdater::HttpOtaUpdater()
: m_currentState(OtaState::IDLE)
, m_isUpdating(false)
, m_cert_pem(nullptr)
, m_skip_cert_common_name_check(false)
, m_totalSize(0)
, m_downloadedSize(0) {
}
HttpOtaUpdater::~HttpOtaUpdater() {
stop();
}
bool HttpOtaUpdater::start(const std::string& url) {
if (m_isUpdating) {
ESP_LOGE(TAG, "OTA update is already in progress");
return false;
}
if (url.empty()) {
ESP_LOGE(TAG, "URL is empty");
return false;
}
m_url = url;
m_errorMessage.clear();
m_totalSize = 0;
m_downloadedSize = 0;
// 配置线程参数
ThreadConfig config;
config.name = "ota_task";
config.stack_size = 8192;
config.priority = 6;
config.core_id = -1; // 不绑定特定核心
// 使用ThreadManager创建线程
m_otaThread = ThreadManager::createMemberThread(config, this, &HttpOtaUpdater::otaTask);
if (!m_otaThread.joinable()) {
ESP_LOGE(TAG, "Failed to create OTA thread");
updateState(OtaState::FAILED, "Failed to create thread");
return false;
}
m_isUpdating = true;
updateState(OtaState::CONNECTING, "Starting OTA update");
return true;
}
void HttpOtaUpdater::stop() {
if (m_isUpdating && m_otaThread.joinable()) {
// 注意:实际OTA过程很难安全中断,这里只是标记状态并分离线程
m_isUpdating = false;
m_otaThread.detach(); // 分离线程,让系统自动回收资源
}
}
bool HttpOtaUpdater::isUpdating() const {
return m_isUpdating;
}
void HttpOtaUpdater::setProgressCallback(const ProgressCallback &callback) {
m_progressCallback = callback;
}
void HttpOtaUpdater::setStateCallback(const StateCallback &callback) {
m_stateCallback = callback;
}
void HttpOtaUpdater::setFinishCallback(const FinishCallback &callback) {
m_finishCallback = callback;
}
void HttpOtaUpdater::setCACert(const char* cert_pem) {
m_cert_pem = cert_pem;
}
void HttpOtaUpdater::skipCertCommonNameCheck(bool skip) {
m_skip_cert_common_name_check = skip;
}
HttpOtaUpdater::OtaState HttpOtaUpdater::getCurrentState() const {
return m_currentState;
}
std::string HttpOtaUpdater::getErrorMessage() const {
return m_errorMessage;
}
void HttpOtaUpdater::otaTask() {
ThreadManager::printThreadInfo("OTA任务启动");
performOta(m_url);
}
void HttpOtaUpdater::performOta(const std::string& url) {
ESP_LOGI(TAG, "Starting OTA update from: %s", url.c_str());
// 配置HTTP客户端
esp_http_client_config_t http_config = {};
http_config.url = url.c_str();
http_config.event_handler = httpEventHandle;
http_config.user_data = this;
http_config.keep_alive_enable = true;
http_config.timeout_ms = 30000;
http_config.buffer_size = 2048;
// 设置证书(如果提供)- 保留供后期使用
if (m_cert_pem) {
http_config.cert_pem = m_cert_pem;
ESP_LOGI(TAG, "Using custom CA certificate for HTTPS");
}
// 设置证书检查 - 保留供后期使用
http_config.skip_cert_common_name_check = m_skip_cert_common_name_check;
if (m_skip_cert_common_name_check) {
ESP_LOGW(TAG, "Skipping certificate common name check");
}
// 配置HTTPS OTA
esp_https_ota_config_t ota_config = {};
ota_config.http_config = &http_config;
updateState(OtaState::DOWNLOADING, "Connecting to server");
esp_err_t ret = esp_https_ota(&ota_config);
if (ret == ESP_OK) {
ESP_LOGI(TAG, "OTA update successful");
updateState(OtaState::SUCCESS, "OTA update completed successfully");
if (m_finishCallback) {
m_finishCallback(true, "OTA update completed successfully");
}
// 等待一段时间后重启
ESP_LOGI(TAG, "Preparing to restart system...");
vTaskDelay(pdMS_TO_TICKS(2000));
esp_restart();
} else {
m_errorMessage = "OTA failed with error: " + std::string(esp_err_to_name(ret));
ESP_LOGE(TAG, "%s", m_errorMessage.c_str());
updateState(OtaState::FAILED, m_errorMessage);
if (m_finishCallback) {
m_finishCallback(false, m_errorMessage);
}
}
m_isUpdating = false;
}
void HttpOtaUpdater::updateState(OtaState state, const std::string& message) {
m_currentState = state;
if (m_stateCallback) {
m_stateCallback(state, message);
}
switch (state) {
case OtaState::CONNECTING:
ESP_LOGI(TAG, "State: CONNECTING - %s", message.c_str());
break;
case OtaState::DOWNLOADING:
ESP_LOGI(TAG, "State: DOWNLOADING - %s", message.c_str());
break;
case OtaState::VERIFYING:
ESP_LOGI(TAG, "State: VERIFYING - %s", message.c_str());
break;
case OtaState::SUCCESS:
ESP_LOGI(TAG, "State: SUCCESS - %s", message.c_str());
break;
case OtaState::FAILED:
ESP_LOGE(TAG, "State: FAILED - %s", message.c_str());
break;
default:
break;
}
}
esp_err_t HttpOtaUpdater::httpEventHandle(esp_http_client_event_t* event) {
auto* updater = static_cast<HttpOtaUpdater*>(event->user_data);
if (!updater) {
return ESP_FAIL;
}
switch (event->event_id) {
case HTTP_EVENT_ON_CONNECTED:
ESP_LOGI(TAG, "HTTP connected");
updater->updateState(OtaState::DOWNLOADING, "Connected to server");
break;
case HTTP_EVENT_ON_HEADER: {
// 获取文件总大小
if (strcasecmp(event->header_key, "Content-Length") == 0) {
updater->m_totalSize = atoi(event->header_value);
ESP_LOGI(TAG, "Total file size: %d bytes", updater->m_totalSize);
}
break;
}
case HTTP_EVENT_ON_DATA: {
if (event->data_len > 0) {
updater->m_downloadedSize += event->data_len;
// 更新进度
if (updater->m_totalSize > 0 && updater->m_progressCallback) {
int progress = (updater->m_downloadedSize * 100) / updater->m_totalSize;
updater->m_progressCallback(progress, 100);
}
// 定期打印下载进度
if (updater->m_downloadedSize % (100 * 1024) < event->data_len) { // 每100KB打印一次
ESP_LOGI(TAG, "Downloaded: %d/%d bytes (%.1f%%)",
updater->m_downloadedSize, updater->m_totalSize,
(updater->m_downloadedSize * 100.0) / updater->m_totalSize);
}
}
break;
}
case HTTP_EVENT_ON_FINISH:
ESP_LOGI(TAG, "HTTP download finished");
updater->updateState(OtaState::VERIFYING, "Download completed, verifying firmware");
break;
case HTTP_EVENT_DISCONNECTED:
ESP_LOGI(TAG, "HTTP disconnected");
break;
case HTTP_EVENT_ERROR:
ESP_LOGE(TAG, "HTTP error occurred");
updater->m_errorMessage = "HTTP error occurred";
break;
default:
break;
}
return ESP_OK;
}
+217
View File
@@ -0,0 +1,217 @@
//
// Created by misaki on 2025/9/24.
//
#pragma once
#include <esp_https_ota.h>
#include <esp_ota_ops.h>
#include <esp_log.h>
#include <string>
#include <functional>
#include "ThreadManager.h"
class HttpOtaUpdater {
public:
// OTA状态枚举
enum class OtaState {
IDLE, // 空闲状态
CONNECTING, // 连接中
DOWNLOADING, // 下载中
VERIFYING, // 验证中
SUCCESS, // 成功
FAILED // 失败
};
// 进度回调函数类型
using ProgressCallback = std::function<void(int progress, int total)>;
// 状态回调函数类型
using StateCallback = std::function<void(OtaState state, const std::string& message)>;
// 完成回调函数类型
using FinishCallback = std::function<void(bool success, const std::string& message)>;
/**
* @brief 构造函数
*/
HttpOtaUpdater();
/**
* @brief 析构函数
*/
~HttpOtaUpdater();
/**
* @brief 开始OTA升级
* @param url HTTP下载链接
* @return true-成功开始升级,false-失败
*/
bool start(const std::string& url);
/**
* @brief 停止OTA升级
*/
void stop();
/**
* @brief 检查是否正在升级
* @return true-正在升级,false-空闲
*/
[[nodiscard]] bool isUpdating() const;
/**
* @brief 设置进度回调
* @param callback 回调函数
*/
void setProgressCallback(const ProgressCallback &callback);
/**
* @brief 设置状态回调
* @param callback 回调函数
*/
void setStateCallback(const StateCallback &callback);
/**
* @brief 设置完成回调
* @param callback 回调函数
*/
void setFinishCallback(const FinishCallback &callback);
/**
* @brief 设置CA证书(用于HTTPS,保留供后期使用)
* @param cert_pem PEM格式的证书内容
*/
void setCACert(const char* cert_pem);
/**
* @brief 跳过证书通用名检查(仅用于测试,保留供后期使用)
* @param skip true-跳过检查,false-不跳过
*/
void skipCertCommonNameCheck(bool skip);
/**
* @brief 获取当前状态
* @return 当前OTA状态
*/
[[nodiscard]] OtaState getCurrentState() const;
/**
* @brief 获取错误信息
* @return 错误信息字符串
*/
[[nodiscard]] std::string getErrorMessage() const;
// 禁止拷贝和赋值
HttpOtaUpdater(const HttpOtaUpdater&) = delete;
HttpOtaUpdater& operator=(const HttpOtaUpdater&) = delete;
private:
/**
* @brief OTA任务主函数
*/
void otaTask();
/**
* @brief 执行OTA升级
* @param url 下载链接
*/
void performOta(const std::string& url);
/**
* @brief 更新状态
* @param state 新状态
* @param message 状态信息
*/
void updateState(OtaState state, const std::string& message = "");
/**
* @brief HTTPS OTA事件处理函数
* @param event 事件
*/
static esp_err_t httpEventHandle(esp_http_client_event_t* event);
// 成员变量
std::string m_url; ///<! 下载链接
OtaState m_currentState; ///<! 当前状态
std::string m_errorMessage; ///<! 错误信息
bool m_isUpdating; ///<! 是否正在升级
std::thread m_otaThread; ///<! OTA线程
// HTTP配置(保留供后期使用)
const char* m_cert_pem; ///<! CA证书
bool m_skip_cert_common_name_check; ///<! 跳过证书通用名检查
// 回调函数
ProgressCallback m_progressCallback;///<! 进度回调
StateCallback m_stateCallback; ///<! 状态回调
FinishCallback m_finishCallback; ///<! 完成回调
// 进度相关变量
int m_totalSize; ///<! 总大小
int m_downloadedSize; ///<! 已下载大小
// 日志标签
static constexpr auto TAG = "HttpOtaUpdater";
};
/** 使用示例
// 创建全局OTA更新器实例
HttpOtaUpdater otaUpdater;
void setupOtaCallbacks() {
// 设置进度回调
otaUpdater.setProgressCallback([](int progress, int total) {
ESP_LOGI("OTA", "Progress: %d%%", progress);
});
// 设置状态回调
otaUpdater.setStateCallback([](HttpOtaUpdater::OtaState state, const std::string& message) {
const char* stateNames[] = {
"IDLE", "CONNECTING", "DOWNLOADING", "VERIFYING", "SUCCESS", "FAILED"
};
ESP_LOGI("OTA", "State: %s - %s", stateNames[static_cast<int>(state)], message.c_str());
});
// 设置完成回调
otaUpdater.setFinishCallback([](bool success, const std::string& message) {
if (success) {
ESP_LOGI("OTA", "Completed successfully: %s", message.c_str());
} else {
ESP_LOGE("OTA", "Failed: %s", message.c_str());
}
});
// 如果需要HTTPS,可以在这里设置证书(保留供后期使用)
// otaUpdater.setCACert(my_ca_cert_pem);
// otaUpdater.skipCertCommonNameCheck(true); // 仅用于测试
}
// 在websocket消息处理函数中调用
void onOtaUrlReceived(const std::string& otaUrl) {
ESP_LOGI("OTA", "Received OTA URL: %s", otaUrl.c_str());
if (otaUpdater.isUpdating()) {
ESP_LOGW("OTA", "OTA update is already in progress");
return;
}
if (otaUpdater.start(otaUrl)) {
ESP_LOGI("OTA", "OTA update started successfully");
} else {
ESP_LOGE("OTA", "Failed to start OTA update");
}
}
// 检查OTA状态
void checkOtaStatus() {
if (otaUpdater.isUpdating()) {
ESP_LOGI("OTA", "OTA update in progress, state: %d",
static_cast<int>(otaUpdater.getCurrentState()));
} else {
ESP_LOGI("OTA", "No OTA update in progress");
}
std::string error = otaUpdater.getErrorMessage();
if (!error.empty()) {
ESP_LOGE("OTA", "Last error: %s", error.c_str());
}
}
*/
+1 -230
View File
@@ -3,170 +3,15 @@
//
#include "OTAClass.h"
#include "esp_log.h"
#include <iostream>
#include <thread>
#include <chrono>
#include <memory>
#include <sstream>
#include <pthread.h>
#include <esp_pthread.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
using namespace std::chrono;
const auto sleep_time = seconds{
5
};
#include "ThreadManager.h"
#include "WifiConnectors.h"
#include <string>
#include "LVGLRender.h"
#include "SDFileManager.h"
#include "AudioOutput.h"
#include "CommClass.h"
// JSON数据回调函数
void onJsonData(cJSON* json) {
// 打印接收到的JSON数据
char* jsonStr = cJSON_Print(json);
ESP_LOGI("JSON_CALLBACK", "收到JSON数据: %s", jsonStr);
free(jsonStr);
// 解析消息类型并处理
cJSON* type = cJSON_GetObjectItem(json, "type");
if (type && cJSON_IsString(type)) {
if (strcmp(type->valuestring, "greeting") == 0) {
ESP_LOGI("JSON_CALLBACK", "收到服务器问候消息");
} else if (strcmp(type->valuestring, "heartbeat") == 0) {
ESP_LOGI("JSON_CALLBACK", "收到心跳响应");
} else if (strcmp(type->valuestring, "response") == 0) {
ESP_LOGI("JSON_CALLBACK", "收到服务器响应");
} else if (strcmp(type->valuestring, "echo") == 0) {
ESP_LOGI("JSON_CALLBACK", "收到回显消息");
} else if (strcmp(type->valuestring, "broadcast") == 0) {
ESP_LOGI("JSON_CALLBACK", "收到广播消息");
}
}
// 记得删除cJSON对象
cJSON_Delete(json);
}
// WebSocket事件回调函数
void onWebSocketEvent(WebSocketEvent event, const std::string& message) {
switch (event) {
case WebSocketEvent::CONNECTED:
ESP_LOGI("EVENT_CALLBACK", "WebSocket已连接: %s", message.c_str());
break;
case WebSocketEvent::DISCONNECTED:
ESP_LOGI("EVENT_CALLBACK", "WebSocket已断开: %s", message.c_str());
break;
case WebSocketEvent::DATA_RECEIVED:
ESP_LOGI("EVENT_CALLBACK", "收到原始数据: %s", message.c_str());
break;
case WebSocketEvent::ERROR:
ESP_LOGE("EVENT_CALLBACK", "WebSocket错误: %s", message.c_str());
break;
}
}
// 发送状态信息函数
void sendStatus() {
cJSON* status = cJSON_CreateObject();
cJSON_AddStringToObject(status, "type", "status");
cJSON* data = cJSON_CreateObject();
cJSON_AddNumberToObject(data, "free_heap", esp_get_free_heap_size());
cJSON_AddNumberToObject(data, "uptime", xTaskGetTickCount() * portTICK_PERIOD_MS / 1000);
cJSON_AddItemToObject(status, "data", data);
if (WebSocketManager::getInstance()->sendJson(status)) {
ESP_LOGI("SEND", "已发送状态信息");
} else {
ESP_LOGE("SEND", "发送状态信息失败");
}
}
// 发送问候消息函数
void sendGreeting() {
cJSON* greeting = cJSON_CreateObject();
cJSON_AddStringToObject(greeting, "type", "greeting");
cJSON_AddStringToObject(greeting, "message", "Hello from ESP32-S3");
cJSON_AddNumberToObject(greeting, "timestamp", xTaskGetTickCount() * portTICK_PERIOD_MS / 1000);
if (WebSocketManager::getInstance()->sendJson(greeting)) {
ESP_LOGI("SEND", "已发送问候消息");
} else {
ESP_LOGE("SEND", "发送问候消息失败");
}
}
void websocket_task() {
TickType_t lastStatusTime = 0;
TickType_t lastHeartbeatTime = 0;
TickType_t lastGreetingTime = 0;
while (true) {
TickType_t currentTime = xTaskGetTickCount();
// 检查连接状态
if (!WebSocketManager::getInstance()->isConnected()) {
ESP_LOGI("APP_TASK", "WebSocket未连接,尝试重新连接...");
// 确保WiFi已连接
if (!WifiConnectors::getInstance()->isWifiConnect()) {
ESP_LOGI("APP_TASK", "WiFi未连接,等待WiFi连接...");
vTaskDelay(5000 / portTICK_PERIOD_MS);
continue;
}
if (WebSocketManager::getInstance()->connect()) {
ESP_LOGI("APP_TASK", "重新连接成功");
} else {
ESP_LOGI("APP_TASK", "重新连接失败");
}
vTaskDelay(5000 / portTICK_PERIOD_MS);
continue;
}
// 每10秒发送状态信息
if (currentTime - lastStatusTime > (10000 / portTICK_PERIOD_MS)) {
sendStatus();
lastStatusTime = currentTime;
}
// 每60秒发送问候
if (currentTime - lastGreetingTime > (60000 / portTICK_PERIOD_MS)) {
sendGreeting();
lastGreetingTime = currentTime;
}
vTaskDelay(100 / portTICK_PERIOD_MS);
}
}
#include "ToolsClass.h"
#include "cpp_json.h"
#include "sys_conf_singleton.h"
using namespace cppjson;
void OTAClass::Init() {
ESP_LOGI("OTA", "Init");
ESP_LOGI("OTAClass::Init", "当前固件版本 1.0.1");
ESP_LOGI("OTAClass::Init", "当前设备MAC地址 %s", ToolsClass::getChipMAC().c_str());
ESP_LOGI("OTAClass::Init", "当前设备固件序列号 %s", ToolsClass::getChipSerialNumber().c_str());
auto& fs = SYS_CONF_JSON();
if (!fs.saveSN("9f897fe7b6b952ac")) {
ESP_LOGE("conf", "saveSN failed");
}
std::string sn = fs.loadSN();
ESP_LOGI("conf", "loaded sn = %s", sn.c_str());
// 列出当前目录内容
std::string listing = SDFileManager::getInstance()->lsCommand(".", false, true);
ESP_LOGI("SD", "%s", listing.c_str());
@@ -205,84 +50,10 @@ void OTAClass::Init() {
// );
// wifi_thread.detach();
WifiConnectors::getInstance()->connectWifi("Misaki-2.4G", "88888888", 5);
// 等待WiFi连接成功后再连接WebSocket
ESP_LOGI("APP_TASK", "等待WiFi连接...");
while (!WifiConnectors::getInstance()->isWifiConnect()) {
ESP_LOGI("APP_TASK", "WiFi未连接,等待中...");
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
// 配置WebSocket
WebSocketConfig config;
config.uri = "ws://" + std::string("192.168.1.11") + ":" + std::to_string(8080) + "/ws";
config.auto_reconnect = true; // 自动重连
config.reconnect_interval = 5000; // 重连间隔(毫秒)
config.heartbeat_interval = 30000; // 心跳间隔(毫秒)
config.max_reconnect_attempts = 10; // 最大重连尝试次数
// TODO: 此处通信类存在线程重复创建bug,似乎是来自esp-idf的bug,待查证
// 初始化WebSocket管理器
// if (!WebSocketManager::getInstance()->initialize(config)) {
// ESP_LOGE("APP_TASK", "WebSocket管理器初始化失败");
// vTaskDelete(NULL);
// return;
// }
// 设置回调函数
// WebSocketManager::getInstance()->setJsonCallback(onJsonData);
// WebSocketManager::getInstance()->setEventCallback(onWebSocketEvent);
// 连接WebSocket服务器
// ESP_LOGI("APP_TASK", "正在连接WebSocket服务器: %s", config.uri.c_str());
// if (!WebSocketManager::getInstance()->connect()) {
// ESP_LOGE("APP_TASK", "WebSocket连接失败");
// }
// 创建WebSocket任务
// ThreadConfig websocket_config;
// websocket_config.core_id = 0;
// websocket_config.inherit_cfg = true;
// websocket_config.name = "websocket_task";
// websocket_config.priority = 5;
// websocket_config.stack_size = 4096;
// std::thread websocket_thread = ThreadManager::createThread(websocket_config, websocket_task);
// websocket_thread.detach();
// ThreadConfig ota_config;
// ota_config.name = "OTA";
// ota_config.stack_size = 4096;
// ota_config.priority = 6;
// ota_config.core_id = 0;
// std::thread ota_thread = ThreadManager::createMemberThread<OTAClass>(
// ota_config,
// this,
// &OTAClass::Update
// );
while (true) { // 主线程线程循环
ThreadManager::print_sys_memory(); // 打印系统内存使用情况
ThreadManager::stats_task(); // 打印任务统计信息
std::this_thread::sleep_for(sleep_time); // 休眠5秒
}
}
#include "ota_ws.h"
// 启动OTA更新线程,前提是已经连接WiFi
void OTAClass::Update() {
// 测试OTA服务器192.168.1.11
while (true) {
if (WifiConnectors::getInstance()->isWifiConnect()) { // 如果Wifi已连接
ota_ws_start("192.168.1.11", 8080);
// 启动完就退出,删除自身
break;
}else {
std::this_thread::sleep_for(sleep_time);
}
}
vTaskDelete(nullptr);
}