Files
Misaki a47e20cb64 1. 优化了cpp_json的内容,使其更modern
2. 稍微优化了一下系统配置类
3. 增加了系统版本号,便于区分系统版本,方便OTA
4. 重写OTA的逻辑,完成了Cpp的OTA封装,测试通过
2025-09-24 04:01:23 +08:00

248 lines
7.2 KiB
C++

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