From a47e20cb64d0e9c11a34232b2d120dd70197a3f1 Mon Sep 17 00:00:00 2001 From: Misaki Date: Wed, 24 Sep 2025 04:01:23 +0800 Subject: [PATCH] =?UTF-8?q?1.=20=E4=BC=98=E5=8C=96=E4=BA=86cpp=5Fjson?= =?UTF-8?q?=E7=9A=84=E5=86=85=E5=AE=B9=EF=BC=8C=E4=BD=BF=E5=85=B6=E6=9B=B4?= =?UTF-8?q?modern=202.=20=E7=A8=8D=E5=BE=AE=E4=BC=98=E5=8C=96=E4=BA=86?= =?UTF-8?q?=E4=B8=80=E4=B8=8B=E7=B3=BB=E7=BB=9F=E9=85=8D=E7=BD=AE=E7=B1=BB?= =?UTF-8?q?=203.=20=E5=A2=9E=E5=8A=A0=E4=BA=86=E7=B3=BB=E7=BB=9F=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7=EF=BC=8C=E4=BE=BF=E4=BA=8E=E5=8C=BA=E5=88=86?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E7=89=88=E6=9C=AC=EF=BC=8C=E6=96=B9=E4=BE=BF?= =?UTF-8?q?OTA=204.=20=E9=87=8D=E5=86=99OTA=E7=9A=84=E9=80=BB=E8=BE=91?= =?UTF-8?q?=EF=BC=8C=E5=AE=8C=E6=88=90=E4=BA=86Cpp=E7=9A=84OTA=E5=B0=81?= =?UTF-8?q?=E8=A3=85=EF=BC=8C=E6=B5=8B=E8=AF=95=E9=80=9A=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Bionic_Core/CommClass/CommBean.cpp | 4 + Bionic_Core/CommClass/CommBean.h | 7 + Bionic_Core/CommClass/CommClass.cpp | 109 ++++---- Bionic_Core/CommClass/CommClass.h | 8 +- Bionic_Core/CppHandle/CppHandle.cpp | 252 +++++++++++++++++- Bionic_Core/OTAClass/HttpOtaUpdater.cpp | 248 +++++++++++++++++ Bionic_Core/OTAClass/HttpOtaUpdater.h | 217 +++++++++++++++ Bionic_Core/OTAClass/OTAClass.cpp | 231 +--------------- Bionic_Core/ToolsClass/CppJson/cpp_json.h | 62 +++-- .../Sys_Config/sys_conf_singleton.cpp | 4 + .../Sys_Config/sys_conf_singleton.h | 54 ++-- Bionic_Core/ToolsClass/ToolsClass.cpp | 28 ++ Bionic_Core/ToolsClass/ToolsClass.h | 22 +- Lib/OTA_Driver/app_ota.c | 87 ------ Lib/OTA_Driver/app_ota.h | 13 - Lib/SD_Card/SD_MMC.c | 2 +- main/CMakeLists.txt | 5 +- 项目开发日志.md | 13 +- 18 files changed, 908 insertions(+), 458 deletions(-) create mode 100644 Bionic_Core/CommClass/CommBean.cpp create mode 100644 Bionic_Core/CommClass/CommBean.h create mode 100644 Bionic_Core/OTAClass/HttpOtaUpdater.cpp create mode 100644 Bionic_Core/OTAClass/HttpOtaUpdater.h delete mode 100644 Lib/OTA_Driver/app_ota.c delete mode 100644 Lib/OTA_Driver/app_ota.h diff --git a/Bionic_Core/CommClass/CommBean.cpp b/Bionic_Core/CommClass/CommBean.cpp new file mode 100644 index 0000000..7a69ea8 --- /dev/null +++ b/Bionic_Core/CommClass/CommBean.cpp @@ -0,0 +1,4 @@ +// +// Created by misaki on 2025/9/24. +// +#include "CommBean.h" diff --git a/Bionic_Core/CommClass/CommBean.h b/Bionic_Core/CommClass/CommBean.h new file mode 100644 index 0000000..386269c --- /dev/null +++ b/Bionic_Core/CommClass/CommBean.h @@ -0,0 +1,7 @@ +// +// Created by misaki on 2025/9/24. +// +#pragma once + + + diff --git a/Bionic_Core/CommClass/CommClass.cpp b/Bionic_Core/CommClass/CommClass.cpp index 8219f35..c27dc83 100644 --- a/Bionic_Core/CommClass/CommClass.cpp +++ b/Bionic_Core/CommClass/CommClass.cpp @@ -15,7 +15,7 @@ // 静态成员初始化 WebSocketManager* WebSocketManager::instance = nullptr; std::mutex WebSocketManager::instance_mutex; -std::string WebSocketManager::sn = SYS_CONF_JSON().loadSN(); // 获取SN(同时也能初始化文件系统,如果还没有初始化这个文件系统的话) +std::string WebSocketManager::sn = SysConfJson::getInstance()->loadSN(); // 获取SN(同时也能初始化文件系统,如果还没有初始化这个文件系统的话) // 标签用于日志 static const char* TAG = "WebSocketManager"; @@ -72,15 +72,15 @@ bool WebSocketManager::initialize(const WebSocketConfig& ws_config) { this, &wifi_instance); // 启动发送线程 - threads_running = true; - - ThreadConfig thread_config; - thread_config.name = "WS_Send"; - thread_config.stack_size = 4096; - - send_thread = ThreadManager::createMemberThread(thread_config, this, - &WebSocketManager::sendThread); - + // 只在线程未运行时启动发送线程 + if (!send_thread.joinable()) { + threads_running = true; + ThreadConfig thread_config; + thread_config.name = "WS_Send"; + thread_config.stack_size = 4096; + send_thread = ThreadManager::createMemberThread(thread_config, this, + &WebSocketManager::sendThread); + } return true; } @@ -118,17 +118,24 @@ bool WebSocketManager::connect() { } // 启动重连和心跳线程 - ThreadConfig thread_config; - thread_config.name = "WS_Reconnect"; - thread_config.stack_size = 3072; + // 只有在线程未运行时才启动新线程 + if (!reconnect_thread.joinable()) { + ThreadConfig thread_config; + thread_config.name = "WS_Reconnect"; + thread_config.stack_size = 3072; - reconnect_thread = ThreadManager::createMemberThread(thread_config, this, - &WebSocketManager::reconnectThread); + reconnect_thread = ThreadManager::createMemberThread(thread_config, this, + &WebSocketManager::reconnectThread); + } - thread_config.name = "WS_Heartbeat"; - heartbeat_thread = ThreadManager::createMemberThread(thread_config, this, - &WebSocketManager::heartbeatThread); + if (!heartbeat_thread.joinable()) { + ThreadConfig thread_config; + thread_config.name = "WS_Heartbeat"; + thread_config.stack_size = 3072; + heartbeat_thread = ThreadManager::createMemberThread(thread_config, this, + &WebSocketManager::heartbeatThread); + } return true; } @@ -147,25 +154,21 @@ void WebSocketManager::disconnect() { } } -bool WebSocketManager::sendJson(cJSON* json) { +bool WebSocketManager::sendJson(const cppjson::Json& json) { if (!connected) { // 检查连接状态 ESP_LOGE(TAG, "Not connected, cannot send data"); - cJSON_Delete(json); return false; } - - char* json_str = cJSON_PrintUnformatted(json); // 将JSON对象转换为字符串 - cJSON_Delete(json); // 释放JSON对象 - - if (!json_str) { // 检查转换结果 + std::string json_str = json.dump(); // 序列化 JSON + if (json_str.empty() || json_str == "null") { // 检查序列化结果 ESP_LOGE(TAG, "Failed to stringify JSON"); return false; } - std::lock_guard lock(queue_mutex); // 锁定队列 - send_queue.emplace(json_str); // 添加到队列 - free(json_str); // 释放json_str的内存 - + { // 临界区 + std::lock_guard lock(queue_mutex); + send_queue.emplace(std::move(json_str)); // 移动进队列,零拷贝 + } queue_cv.notify_one(); // 通知发送线程 return true; } @@ -241,7 +244,7 @@ void WebSocketManager::websocketEventHandler(void* handler_args, esp_event_base_ } break; - case WEBSOCKET_EVENT_ERROR: + case WEBSOCKET_EVENT_ERROR: // 错误事件 ws_instance->connected = false; ws_instance->connecting = false; @@ -257,20 +260,13 @@ void WebSocketManager::websocketEventHandler(void* handler_args, esp_event_base_ } void WebSocketManager::handleReceivedData(const char* data, const int len) const { - // 尝试解析JSON - if (cJSON* json = cJSON_ParseWithLength(data, len)) { - // 成功解析为JSON - if (json_callback) { - json_callback(json); - } else { - cJSON_Delete(json); - } - } else { - // 不是JSON格式,作为原始数据处理 - std::string message(data, len); - if (event_callback) { - event_callback(WebSocketEvent::DATA_RECEIVED, message); - } + // 解析JSON + cppjson::Json json = cppjson::Json::parse(std::string(data, len)); // 失败会得到空 Json + if (!json.isNull()) { // 解析成功 + if (json_callback) json_callback(json); + } else { // 解析失败,当原始文本处理 + if (event_callback) + event_callback(WebSocketEvent::DATA_RECEIVED, std::string(data, len)); } } @@ -278,7 +274,7 @@ void WebSocketManager::reconnectThread() { while (threads_running) { if (!connected && config.auto_reconnect && (config.max_reconnect_attempts == 0 || - reconnect_attempts < config.max_reconnect_attempts)) { + reconnect_attempts < config.max_reconnect_attempts)) { // 检查重连条件 // 检查WiFi连接 if (!wifi->isWifiConnect()) { @@ -297,7 +293,7 @@ void WebSocketManager::reconnectThread() { ESP_LOGE(TAG, "Reconnection attempt failed"); } } - + // sleep std::this_thread::sleep_for(std::chrono::milliseconds(config.reconnect_interval)); } } @@ -306,11 +302,10 @@ void WebSocketManager::heartbeatThread() { while (threads_running) { if (connected && config.heartbeat_interval > 0) { // 如果处于连接状态且心跳间隔大于0 // 发送心跳消息 - cJSON* heartbeat = cJSON_CreateObject(); - cJSON_AddStringToObject(heartbeat, "type", "heartbeat"); - cJSON_AddNumberToObject(heartbeat, "timestamp", static_cast(esp_log_timestamp())); - - sendJson(heartbeat); + cppjson::Json hb = cppjson::Json::object(); + hb.set("type", cppjson::Json("heartbeat")) + .set("timestamp", cppjson::Json(esp_log_timestamp())); + sendJson(hb); // 已经重载好了,直接塞 std::this_thread::sleep_for(std::chrono::milliseconds(config.heartbeat_interval)); } else { // 否则就让出CPU @@ -364,14 +359,18 @@ bool WebSocketManager::createWebSocketClient() { // 处理sn码逻辑 // 一般情况下sn码会在开机的时候从flash当中读出,因此此处判断是否为空 + sn = SysConfJson::getInstance()->loadSN(); // 再读一次sn码 if (!sn.empty()) { // 如果sn码不为空 // 则在头部加入sn码提交 - esp_websocket_client_append_header(client, "sn", sn.c_str()); + esp_websocket_client_append_header(client, "X-SN", sn.c_str()); + // 同时在头部加入mac码和芯片序列号,方便服务端做验证 + esp_websocket_client_append_header(client, "X-MAC", ToolsClass::getChipMAC().c_str()); + esp_websocket_client_append_header(client, "X-CHIP-ID", ToolsClass::getChipSerialNumber().c_str()); } else { - // 否则在头部加入mac码和芯片序列号 - esp_websocket_client_append_header(client, "mac", ToolsClass::getChipMAC().c_str()); - esp_websocket_client_append_header(client, "chip_id", ToolsClass::getChipSerialNumber().c_str()); + // 如果sn码为空,设备可能存在问题 + ESP_LOGE(TAG, "SN is empty, please check your device"); + return false; } diff --git a/Bionic_Core/CommClass/CommClass.h b/Bionic_Core/CommClass/CommClass.h index 4da6c18..10a97a5 100644 --- a/Bionic_Core/CommClass/CommClass.h +++ b/Bionic_Core/CommClass/CommClass.h @@ -10,7 +10,7 @@ #include #include #include "esp_websocket_client.h" -#include "cJSON.h" +#include "cpp_json.h" #include "ThreadManager.h" #include "WifiConnectors.h" @@ -32,7 +32,7 @@ struct WebSocketConfig { }; // JSON数据回调 -using JsonDataCallback = std::function; +using JsonDataCallback = std::function; // 事件回调 using EventCallback = std::function; @@ -98,8 +98,8 @@ public: // 断开连接 void disconnect(); - // 发送JSON数据,注意此成员函数会释放json数据,调用之后请不要再次释放json数据 - bool sendJson(cJSON* json); + // 发送JSON数据 + bool sendJson(const cppjson::Json& json); // 发送原始字符串数据 bool sendRaw(const std::string& data); diff --git a/Bionic_Core/CppHandle/CppHandle.cpp b/Bionic_Core/CppHandle/CppHandle.cpp index 4363e17..fcc21d6 100644 --- a/Bionic_Core/CppHandle/CppHandle.cpp +++ b/Bionic_Core/CppHandle/CppHandle.cpp @@ -202,10 +202,258 @@ void testMIC() { } + +#include "ToolsClass.h" +#include "WifiConnectors.h" +#include "CommClass.h" +#include "sys_conf_singleton.h" +#include "HttpOtaUpdater.h" +using namespace std::chrono; +const auto sleep_time = seconds{ + 5 +}; +// 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(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); // 仅用于测试 +} + +// JSON 数据回调函数 +/** 交互所使用的json内容 + { + "type": "xxx", // 消息类型 + "xxx":"xxx", // 其他数据 + ...... + } + */ +void onJsonData(const cppjson::Json& json) +{ + // 打印收到的 JSON + ESP_LOGI("JSON_CALLBACK", "收到JSON数据: %s", json.dump().c_str()); + + // 解析消息类型 + const cppjson::Json& type = json["type"]; + if (!type.isString()) return; + + std::string typeStr = type.asString(); + + if (typeStr == "greeting") { + ESP_LOGI("JSON_CALLBACK", "收到服务器问候消息"); + } else if (typeStr == "heartbeat") { + ESP_LOGI("JSON_CALLBACK", "收到心跳响应"); + } else if (typeStr == "response") { + ESP_LOGI("JSON_CALLBACK", "收到服务器响应"); + } else if (typeStr == "echo") { + ESP_LOGI("JSON_CALLBACK", "收到回显消息"); + } else if (typeStr == "broadcast") { + ESP_LOGI("JSON_CALLBACK", "收到广播消息"); + } else if (typeStr == "ota") { + ESP_LOGI("JSON_CALLBACK", "收到OTA消息"); + // 进一步处理OTA消息 + // 获取OTA中的版本信息 + const cppjson::Json& version = json["version"]; + if (!version.isString()) return; + std::string versionStr = version.asString(); + // 获取OTA中的HTTP URL + const cppjson::Json& url = json["url"]; + if (!url.isString()) return; + std::string urlStr = url.asString(); + // 告诉服务端,升级开始 + cppjson::Json response = cppjson::Json::object(); + response.set("type", cppjson::Json("ota_start")); + WebSocketManager::getInstance()->sendJson(response); + otaUpdater.start(urlStr); + } + +} + +// 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() +{ + cppjson::Json status = cppjson::Json::object(); + status.set("type", cppjson::Json("status")); + + cppjson::Json data = cppjson::Json::object(); + data.set("free_heap", cppjson::Json(esp_get_free_heap_size())) + .set("uptime", cppjson::Json(xTaskGetTickCount() * portTICK_PERIOD_MS / 1000)); + + status.set("data", data); // 嵌套对象 + + if (WebSocketManager::getInstance()->sendJson(status)) { + ESP_LOGI("SEND", "已发送状态信息"); + } else { + ESP_LOGE("SEND", "发送状态信息失败"); + } +} + +// 发送问候消息函数 +void sendGreeting() +{ + cppjson::Json greeting = cppjson::Json::object(); + greeting.set("type", cppjson::Json("greeting")) + .set("message", cppjson::Json("Hello from ESP32-S3")) + .set("timestamp", cppjson::Json(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); + } +} + +void createWebSocket() { + // 等待WiFi连接成功后再连接WebSocket + ESP_LOGI("APP_TASK", "等待WiFi连接..."); + while (!WifiConnectors::getInstance()->isWifiConnect()) { + ESP_LOGI("APP_TASK", "WiFi未连接,等待中..."); + vTaskDelay(1000 / portTICK_PERIOD_MS); + } + // 保存SN + SysConfJson::getInstance()->saveSN(ToolsClass::GenerateSN(ToolsClass::getChipMAC(), ToolsClass::getChipSerialNumber())); + // 读取SN + std::string sn = SysConfJson::getInstance()->loadSN(); + ESP_LOGI("conf", "loaded sn = %s", sn.c_str()); + + // 配置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(); +} + void Cpp_Hand() { // testMIC(); // testPetSystem(); - OTAClass oc; - oc.Init(); + ESP_LOGI("CppHandle::Cpp_Hand", "当前固件版本 %s:", ToolsClass::getDeviceVersion().c_str()); + + ESP_LOGI("CppHandle::Cpp_Hand", "当前设备MAC地址 %s:", ToolsClass::getChipMAC().c_str()); + ESP_LOGI("CppHandle::Cpp_Hand", "当前设备固件序列号 %s:", ToolsClass::getChipSerialNumber().c_str()); + + // 连接wifi + WifiConnectors::getInstance()->connectWifi("Misaki-2.4G", "88888888", 5); + + // 创建WebSocket + createWebSocket(); + + // 设置OTA回调 + setupOtaCallbacks(); + + while (true) { // 主线程线程循环 + // ThreadManager::print_sys_memory(); // 打印系统内存使用情况 + // ThreadManager::stats_task(); // 打印任务统计信息 + + std::this_thread::sleep_for(sleep_time); // 休眠5秒 + } } diff --git a/Bionic_Core/OTAClass/HttpOtaUpdater.cpp b/Bionic_Core/OTAClass/HttpOtaUpdater.cpp new file mode 100644 index 0000000..9e82d09 --- /dev/null +++ b/Bionic_Core/OTAClass/HttpOtaUpdater.cpp @@ -0,0 +1,248 @@ +// +// Created by misaki on 2025/9/24. +// +#include "HttpOtaUpdater.h" +#include +#include + +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(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; +} \ No newline at end of file diff --git a/Bionic_Core/OTAClass/HttpOtaUpdater.h b/Bionic_Core/OTAClass/HttpOtaUpdater.h new file mode 100644 index 0000000..9d49512 --- /dev/null +++ b/Bionic_Core/OTAClass/HttpOtaUpdater.h @@ -0,0 +1,217 @@ +// +// Created by misaki on 2025/9/24. +// +#pragma once +#include +#include +#include +#include +#include +#include "ThreadManager.h" + +class HttpOtaUpdater { +public: + // OTA状态枚举 + enum class OtaState { + IDLE, // 空闲状态 + CONNECTING, // 连接中 + DOWNLOADING, // 下载中 + VERIFYING, // 验证中 + SUCCESS, // 成功 + FAILED // 失败 + }; + + // 进度回调函数类型 + using ProgressCallback = std::function; + // 状态回调函数类型 + using StateCallback = std::function; + // 完成回调函数类型 + using FinishCallback = std::function; + + /** + * @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; ///(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(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()); + } +} + */ diff --git a/Bionic_Core/OTAClass/OTAClass.cpp b/Bionic_Core/OTAClass/OTAClass.cpp index 62b5aa3..e5d4eb8 100644 --- a/Bionic_Core/OTAClass/OTAClass.cpp +++ b/Bionic_Core/OTAClass/OTAClass.cpp @@ -3,170 +3,15 @@ // #include "OTAClass.h" #include "esp_log.h" - -#include -#include -#include -#include -#include -#include -#include #include -#include - -using namespace std::chrono; - -const auto sleep_time = seconds{ - 5 -}; - -#include "ThreadManager.h" -#include "WifiConnectors.h" #include - -#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( - // 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); + } diff --git a/Bionic_Core/ToolsClass/CppJson/cpp_json.h b/Bionic_Core/ToolsClass/CppJson/cpp_json.h index 940a019..2b4666c 100644 --- a/Bionic_Core/ToolsClass/CppJson/cpp_json.h +++ b/Bionic_Core/ToolsClass/CppJson/cpp_json.h @@ -9,27 +9,27 @@ */ /** - * 如何使用: - std::string text = R"({"name":"Misaki","age":18,"skills":["C++","RISC-V"]})"; - auto j = cppjson::Json::parse(text); - std::cout << "name = " << j["name"].asString() << '\n'; - j["skills"].append(cppjson::Json("Go")); - std::cout << j.dumpPretty() << '\n'; + * 如何使用:\n + std::string text = R"({"name":"Misaki","age":18,"skills":["C++","RISC-V"]})"; \n + auto j = cppjson::Json::parse(text);\n + std::cout << "name = " << j["name"].asString() << '\n';\n + j["skills"].append(cppjson::Json("Go"));\n + std::cout << j.dumpPretty() << '\n';\n - or: - Json j = Json::object(); - j.set("name", Json("misaki")) - .set("age", Json(24)) - .set("vip", Json(true)) - .set("list", Json::array() - .append(Json(1)) - .append(Json("hello"))); + or:\n + Json j = Json::object();\n + j.set("name", Json("misaki"))\n + .set("age", Json(24))\n + .set("vip", Json(true))\n + .set("list", Json::array()\n + .append(Json(1))\n + .append(Json("hello")));\n - std::cout << j.dumpPretty() << "\n"; + std::cout << j.dumpPretty() << "\n";\n - Json j2 = j["list"]; - for (auto& v : j2) std::cout << v.dump() << " "; - std::cout << "\n"; + Json j2 = j["list"];\n + for (auto& v : j2) std::cout << v.dump() << " ";\n + std::cout << "\n";\n */ // cpp_json.hpp @@ -53,8 +53,14 @@ class Json { public: enum Type { Null, Bool, Number, String, ArrayType, ObjectType }; - /*-------- 构造 ------------------------------------------------*/ + // 构造 Json() noexcept : ptr_(nullptr), owner_(false) {} + + // 支持所有整型(包括 int32_t / uint64_t 等 typedef) + template , int> = 0> + explicit Json(T v) noexcept + : ptr_(cJSON_CreateNumber(static_cast(v))), owner_(true) {} explicit Json(std::nullptr_t) noexcept : Json() {} explicit Json(bool v) : ptr_(cJSON_CreateBool(v)), owner_(true) {} explicit Json(int v) : ptr_(cJSON_CreateNumber(v)), owner_(true) {} @@ -63,11 +69,11 @@ public: explicit Json(const std::string& v):ptr_(cJSON_CreateString(v.c_str())), owner_(true) {} explicit Json(const Array& arr); // 类外实现 explicit Json(const Object& obj); // 类外实现 - /* 接管原始指针,owner=true 会负责 delete */ + // 接管原始指针,owner=true 会负责 delete,切记不要接管后再在外部cJSON_Delete explicit Json(cJSON* raw, bool owner = true) noexcept : ptr_(raw), owner_(owner) {} - /*-------- 拷贝/移动 -------------------------------------------*/ + // 拷贝/移动 Json(const Json& rhs) : ptr_(rhs.ptr_ ? cJSON_Duplicate(rhs.ptr_, 1) : nullptr), owner_(true) {} Json(Json&& rhs) noexcept @@ -75,7 +81,7 @@ public: Json& operator=(Json rhs) noexcept { swap(rhs); return *this; } ~Json() { reset(); } - /*-------- 工厂 ------------------------------------------------*/ + // 工厂 static Json parse(const std::string& text) { cJSON* p = cJSON_Parse(text.c_str()); if (!p) throw std::runtime_error("cppjson::parse failed"); @@ -84,7 +90,7 @@ public: static Json array() { return Json(cJSON_CreateArray(), true); } static Json object() { return Json(cJSON_CreateObject(), true); } - /*-------- 序列化 ----------------------------------------------*/ + // 序列化 [[nodiscard]] std::string dump() const { if (!ptr_) return "null"; char* s = cJSON_PrintUnformatted(ptr_); @@ -100,7 +106,7 @@ public: return ret; } - /*-------- 类型查询 --------------------------------------------*/ + // 类型查询 [[nodiscard]] Type type() const noexcept { if (!ptr_) return Null; switch (ptr_->type & 0xFF) { @@ -120,13 +126,13 @@ public: [[nodiscard]] bool isArray() const noexcept { return type() == ArrayType; } [[nodiscard]] bool isObject() const noexcept { return type() == ObjectType; } - /*-------- 取值 ------------------------------------------------*/ + // 取值 [[nodiscard]] bool asBool() const { if (!isBool()) throw std::runtime_error("not bool"); return cJSON_IsTrue(ptr_); } [[nodiscard]] int asInt() const { if (!isNumber()) throw std::runtime_error("not number"); return static_cast(ptr_->valueint); } [[nodiscard]] double asDouble() const { if (!isNumber()) throw std::runtime_error("not number"); return ptr_->valuedouble; } [[nodiscard]] std::string asString() const { if (!isString()) throw std::runtime_error("not string"); return ptr_->valuestring; } - /*-------- 数组/对象接口 ---------------------------------------*/ + // 数组/对象接口 [[nodiscard]] size_t size() const { if (isArray() || isObject()) return cJSON_GetArraySize(ptr_); return 0; @@ -152,7 +158,7 @@ public: return *this; } - /*-------- 迭代器(只读)---------------------------------------*/ + // 迭代器(只读) template struct iterator_impl { using iterator_category = std::forward_iterator_tag; @@ -194,7 +200,7 @@ private: }; -/*================ 类外实现:Array / Object 构造 =================*/ +// 类外实现:Array / Object 构造 inline Json::Json(const Array& arr) : ptr_(cJSON_CreateArray()), owner_(true) { for (const auto& v : arr) append(v); diff --git a/Bionic_Core/ToolsClass/Sys_Config/sys_conf_singleton.cpp b/Bionic_Core/ToolsClass/Sys_Config/sys_conf_singleton.cpp index 6523485..07270c4 100644 --- a/Bionic_Core/ToolsClass/Sys_Config/sys_conf_singleton.cpp +++ b/Bionic_Core/ToolsClass/Sys_Config/sys_conf_singleton.cpp @@ -3,3 +3,7 @@ // #include "sys_conf_singleton.h" + +// 静态成员变量定义 +SysConfJson* SysConfJson::instance = nullptr; +std::mutex SysConfJson::instanceMutex; diff --git a/Bionic_Core/ToolsClass/Sys_Config/sys_conf_singleton.h b/Bionic_Core/ToolsClass/Sys_Config/sys_conf_singleton.h index 82c0b68..231657b 100644 --- a/Bionic_Core/ToolsClass/Sys_Config/sys_conf_singleton.h +++ b/Bionic_Core/ToolsClass/Sys_Config/sys_conf_singleton.h @@ -13,16 +13,16 @@ #include #include -static constexpr char kSysConfPartName[] = "sys_conf"; - -template class SysConfJson { public: - static SysConfJson& instance() { - static SysConfJson inst; - return inst; + static SysConfJson* getInstance() { + std::lock_guard lock(instanceMutex); + if (instance == nullptr) { + instance = new SysConfJson(); + return instance; + } + return instance; } - /* 把 sn 持久化到 /sn.json,成功返回 true */ bool saveSN(const std::string& sn) { @@ -30,7 +30,6 @@ public: doc.set("sn", cppjson::Json(sn)); // {"sn":"xxxxxx"} return write("sn", doc); // 实际文件 = /sn.json } - /* 读取 sn,如果文件/字段不存在返回空串(绝不抛异常) */ std::string loadSN() { @@ -39,14 +38,12 @@ public: cppjson::Json snNode = j["sn"]; return snNode.isString() ? snNode.asString() : ""; } - /* 直接覆盖写,无 tmp */ bool write(const char* key, const cppjson::Json& j) { const std::string path = buildPath(key); const std::string txt = j.dump(); return writeRaw(path, txt); } - /* 读 */ cppjson::Json read(const char* key) const { std::string content; @@ -58,13 +55,11 @@ public: return {}; } } - /* 删文件 */ bool remove(const char* key) const { const std::string path = buildPath(key); return ::unlink(path.c_str()) == 0; } - /* 列文件 */ [[nodiscard]] std::vector ls() const { std::vector names; @@ -78,35 +73,24 @@ public: ::closedir(dir); return names; } - /* 格式化 */ [[nodiscard]] bool format() const { - ESP_LOGW(TAG, "format <%s>", PartitionLabel); - return esp_vfs_fat_spiflash_format_rw_wl(kMount, PartitionLabel) == ESP_OK; + ESP_LOGW(TAG, "format <%s>", kSysConfPartName); + return esp_vfs_fat_spiflash_format_rw_wl(kMount, kSysConfPartName) == ESP_OK; } - /* 禁止拷贝 */ SysConfJson(const SysConfJson&) = delete; SysConfJson& operator=(const SysConfJson&) = delete; - private: - constexpr static const char* TAG = "SysConfJson"; - constexpr static const char* kMount = "/sys_conf"; - wl_handle_t wl_ = WL_INVALID_HANDLE; - bool mounted_ = false; - static constexpr const char* kFileName = "sys_conf.json"; - std::mutex mtx_; - SysConfJson() { mount(); } ~SysConfJson() { unmount(); } - void mount() { esp_vfs_fat_mount_config_t cfg = { - .format_if_mount_failed = true, + .format_if_mount_failed = true, // 如果 FAT 分区无法挂载,并且此参数为 true,则创建分区表并格式化文件系统。 .max_files = 8, - .allocation_unit_size = CONFIG_WL_SECTOR_SIZE + .allocation_unit_size = CONFIG_WL_SECTOR_SIZE, }; - esp_err_t err = esp_vfs_fat_spiflash_mount_rw_wl(kMount, PartitionLabel, &cfg, &wl_); + esp_err_t err = esp_vfs_fat_spiflash_mount_rw_wl(kMount, kSysConfPartName, &cfg, &wl_); if (err != ESP_OK) { ESP_LOGE(TAG, "mount fail <%s>", esp_err_to_name(err)); return; @@ -114,14 +98,12 @@ private: mounted_ = true; ESP_LOGI(TAG, "FAT mounted at %s", kMount); } - void unmount() { if (!mounted_) return; esp_vfs_fat_spiflash_unmount_rw_wl(kMount, wl_); wl_ = WL_INVALID_HANDLE; mounted_ = false; } - bool writeRaw(const std::string& path, const std::string& txt) { FILE* f = std::fopen(path.c_str(), "wb"); if (!f) return false; @@ -129,7 +111,6 @@ private: std::fclose(f); return ok; } - bool readRaw(const char* key, std::string& out) const { const std::string path = buildPath(key); FILE* f = std::fopen(path.c_str(), "rb"); @@ -146,6 +127,15 @@ private: std::string buildPath(const char* key) const { return std::string(kMount) + "/" + key; } +private: + constexpr static const char* TAG = "SysConfJson"; + constexpr static const char* kMount = "/sys_conf"; + static constexpr char kSysConfPartName[] = "sys_conf"; + wl_handle_t wl_ = WL_INVALID_HANDLE; + bool mounted_ = false; + static constexpr const char* kFileName = "sys_conf.json"; + static SysConfJson* instance; // 单例实例 + static std::mutex instanceMutex; // 单例锁 + }; -#define SYS_CONF_JSON() SysConfJson::instance() \ No newline at end of file diff --git a/Bionic_Core/ToolsClass/ToolsClass.cpp b/Bionic_Core/ToolsClass/ToolsClass.cpp index 96e745e..3fbeffb 100644 --- a/Bionic_Core/ToolsClass/ToolsClass.cpp +++ b/Bionic_Core/ToolsClass/ToolsClass.cpp @@ -10,6 +10,7 @@ #include #include #include +#include static const char *TAG = "ToolsClass"; @@ -39,4 +40,31 @@ std::string ToolsClass::getChipMAC() { return ss.str(); } +// 1. 去掉 mac 中的冒号 +// 2. 拼接 chipID +// 3. 取前 24 字符并转大写 +std::string ToolsClass::GenerateSN(const std::string& mac, const std::string& chipID) +{ + // 1. 去掉 MAC 里的冒号 + std::string plainMac; + plainMac.reserve(mac.size()); + for (char ch : mac) + if (ch != ':') plainMac.push_back(ch); + + // 2. 拼接 + std::string raw = plainMac + chipID; + + // 3. 取前 24 位并转大写 + if (raw.size() > 24) raw.resize(24); + std::transform(raw.begin(), raw.end(), raw.begin(), + [](unsigned char c){ return std::toupper(c); }); + return raw; +} + +std::string ToolsClass::device_version = "Beta0.3"; +std::string ToolsClass::getDeviceVersion() { + return device_version; +} + + diff --git a/Bionic_Core/ToolsClass/ToolsClass.h b/Bionic_Core/ToolsClass/ToolsClass.h index 0bf3e65..6b619ca 100644 --- a/Bionic_Core/ToolsClass/ToolsClass.h +++ b/Bionic_Core/ToolsClass/ToolsClass.h @@ -12,20 +12,36 @@ class ToolsClass { public: /** * 获取当前时间 - * @return + * @return 当前时间 */ static std::string getCurrentTime(); /** * 获取esp32s3的芯片序列号 - * @return + * @return 芯片序列号 */ static std::string getChipSerialNumber(); /** * 获取esp32s3的MAC地址 - * @return + * @return MAC地址 */ static std::string getChipMAC(); + + + /** + * 生成SN码 + * @param mac mac地址 + * @param chipID 芯片ID + * @return 生成的SN码 + */ + static std::string GenerateSN(const std::string& mac, const std::string& chipID); + + /** + * 获取设备版本 + * @return 设备版本 + */ + static std::string getDeviceVersion(); + static std::string device_version; // 设备版本 }; diff --git a/Lib/OTA_Driver/app_ota.c b/Lib/OTA_Driver/app_ota.c deleted file mode 100644 index 5b619d4..0000000 --- a/Lib/OTA_Driver/app_ota.c +++ /dev/null @@ -1,87 +0,0 @@ -// -// Created by misaki on 2025/9/4. -// - -#include "app_ota.h" -#include "esp_log.h" -#include "esp_ota_ops.h" -#include "esp_http_client.h" -#include "esp_https_ota.h" -#include "freertos/task.h" - -static const char *TAG = "app_ota"; -#define DEFAULT_VERSION "0.0.1" - -/* 本地版本号,编译时可由构建系统注入 */ -#ifndef APP_FW_VERSION -#define APP_FW_VERSION DEFAULT_VERSION -#endif - -static const char s_version[] = APP_FW_VERSION; - -const char *app_ota_current_version(void) -{ - return s_version; -} - -/* 简单版本号比较:a.b.c 字符串比较即可 */ -static bool need_update(const char *server_ver) -{ - if (!server_ver) return false; - return strcmp(server_ver, s_version) > 0; -} - -/* HTTP 事件回调,仅打印进度 */ -static esp_err_t http_evt(esp_http_client_event_t *evt) -{ - switch (evt->event_id) { - case HTTP_EVENT_ON_DATA: - /* 数据流直接走 OTA,这里不打印 */ - break; - default: - break; - } - return ESP_OK; -} - -static void ota_task(void *pv) -{ - char url[256]; - strncpy(url, (const char *)pv, sizeof(url) - 1); - url[sizeof(url) - 1] = '\0'; - - ESP_LOGI(TAG, "开始 OTA,URL=%s", url); - - esp_http_client_config_t http_cfg = { - .url = url, - .event_handler = http_evt, - .keep_alive_enable = true, - // 如用 https,把 cert_pem 打开即可 - // .cert_pem = (const char *)server_cert_pem_start, - }; - - esp_https_ota_config_t ota_cfg = { - .http_config = &http_cfg, - }; - - /* 执行升级 */ - esp_err_t ret = esp_https_ota(&ota_cfg); - if (ret == ESP_OK) { - ESP_LOGI(TAG, "OTA 完成,准备重启"); - esp_restart(); - } else { - ESP_LOGE(TAG, "OTA 失败,err=%s", esp_err_to_name(ret)); - } - - vTaskDelete(NULL); -} - -esp_err_t app_ota_start(const char *url) -{ - if (!url) return ESP_ERR_INVALID_ARG; - /* 内部建 Task,栈 8 KB */ - BaseType_t ok = xTaskCreate(ota_task, "ota_task", 8192, (void *)url, 5, NULL); - return ok == pdPASS ? ESP_OK : ESP_FAIL; -} - - diff --git a/Lib/OTA_Driver/app_ota.h b/Lib/OTA_Driver/app_ota.h deleted file mode 100644 index a9c770c..0000000 --- a/Lib/OTA_Driver/app_ota.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// Created by misaki on 2025/9/4. -// - -#pragma once - -#include "esp_err.h" - -/* 一键启动 OTA(阻塞,内部建 Task) */ -esp_err_t app_ota_start(const char *url); - -/* 获取当前运行版本号(返回静态指针) */ -const char *app_ota_current_version(void); \ No newline at end of file diff --git a/Lib/SD_Card/SD_MMC.c b/Lib/SD_Card/SD_MMC.c index b6ee1eb..9587be8 100644 --- a/Lib/SD_Card/SD_MMC.c +++ b/Lib/SD_Card/SD_MMC.c @@ -55,7 +55,7 @@ void SD_Init(void) // If format_if_mount_failed is set to true, SD card will be partitioned and formatted in case when mounting fails. false true esp_vfs_fat_sdmmc_mount_config_t mount_config = { .format_if_mount_failed = true, - .max_files = 5, + .max_files = 10, .allocation_unit_size = 16 * 1024 }; sdmmc_card_t *card; diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index fe12ece..a826c27 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -25,15 +25,16 @@ idf_component_register(SRCS "Bionic_sphere.c" "../Lib/Display/Touch_Driver/esp_lcd_touch/esp_lcd_touch.c" # 触摸屏驱动库 "../Lib/PWR_Key/PWR_Key.c" # PWR按键驱动库 "../Lib/MIC_Driver/MIC_Speech.c" # 录音驱动库 - "../Lib/OTA_Driver/app_ota.c" # OTA驱动库 - "../Lib/OTA_Driver/ota_ws.c" + "../Lib/OTA_Driver/ota_ws.c" # OTA驱动库 # 业务代码(使用Cpp编写) "../Bionic_Core/PetBaseClass/PetBaseClass.cpp" # 宠物基类库 "../Bionic_Core/PetBaseClass/PetInterface.cpp" # 宠物接口层 "../Bionic_Core/PetBaseClass/PetObserver.cpp" # 宠物观察者库 "../Bionic_Core/PetBaseClass/PetDao.cpp" # 宠物数据访问层 "../Bionic_Core/OTAClass/OTAClass.cpp" # OTA类库 + "../Bionic_Core/OTAClass/HttpOtaUpdater.cpp" "../Bionic_Core/CommClass/CommClass.cpp" # 通信类库 + "../Bionic_Core/CommClass/CommBean.cpp" # 通信交互实体 "../Bionic_Core/ToolsClass/ToolsClass.cpp" # 工具类库 "../Bionic_Core/ToolsClass/AudioOutput/AudioOutput.cpp" # 音频输出类库 "../Bionic_Core/ToolsClass/LVGL_Render/LVGLRender.cpp" # LVGL渲染类库 diff --git a/项目开发日志.md b/项目开发日志.md index 8cca29b..36e286f 100644 --- a/项目开发日志.md +++ b/项目开发日志.md @@ -243,7 +243,18 @@ #### Day16 2025.9.23(前两天在忙考研复习) ##### 主要目标:完成具体业务开发&各种优化 实际完成任务: -- [x] 1. 基于cJSON进行了上层现代C++封装,作为脚手架使用 +- [x] 1. 基于cJSON进行了上层现代C++封装(cpp_json),作为脚手架使用 - [x] 2. 在esp32s3剩余的flash当中开辟了一个文件系统,用于保存设备重要配置信息 +#### Day17 2025.9.24 +##### 主要目标:完成具体业务开发&各种优化 +实际完成任务: +- [x] 1. 优化了cpp_json的内容,使其更modern + +- [x] 2. 稍微优化了一下系统配置类 + +- [x] 3. 增加了系统版本号,便于区分系统版本,方便OTA + +- [x] 4. 重写OTA的逻辑,完成了Cpp的OTA封装,测试通过 +