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
+4
View File
@@ -0,0 +1,4 @@
//
// Created by misaki on 2025/9/24.
//
#include "CommBean.h"
+7
View File
@@ -0,0 +1,7 @@
//
// Created by misaki on 2025/9/24.
//
#pragma once
+54 -55
View File
@@ -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<std::mutex> lock(queue_mutex); // 锁定队列
send_queue.emplace(json_str); // 添加到队列
free(json_str); // 释放json_str的内存
{ // 临界区
std::lock_guard<std::mutex> 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<double>(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;
}
+4 -4
View File
@@ -10,7 +10,7 @@
#include <queue>
#include <condition_variable>
#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<void(cJSON* json)>;
using JsonDataCallback = std::function<void(cppjson::Json&)>;
// 事件回调
using EventCallback = std::function<void(WebSocketEvent event, const std::string& message)>;
@@ -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);
+250 -2
View File
@@ -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<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); // 仅用于测试
}
// 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秒
}
}
+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);
}
+34 -28
View File
@@ -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 <typename T,
std::enable_if_t<std::is_integral_v<T>, int> = 0>
explicit Json(T v) noexcept
: ptr_(cJSON_CreateNumber(static_cast<double>(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<int>(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 <bool IsConst>
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);
@@ -3,3 +3,7 @@
//
#include "sys_conf_singleton.h"
// 静态成员变量定义
SysConfJson* SysConfJson::instance = nullptr;
std::mutex SysConfJson::instanceMutex;
@@ -13,16 +13,16 @@
#include <fstream>
#include <mutex>
static constexpr char kSysConfPartName[] = "sys_conf";
template <const char* PartitionLabel>
class SysConfJson {
public:
static SysConfJson& instance() {
static SysConfJson inst;
return inst;
static SysConfJson* getInstance() {
std::lock_guard<std::mutex> lock(instanceMutex);
if (instance == nullptr) {
instance = new SysConfJson();
return instance;
}
return instance;
}
/* 把 sn 持久化到 <mount>/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); // 实际文件 = <mount>/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<std::string> ls() const {
std::vector<std::string> 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<kSysConfPartName>::instance()
+28
View File
@@ -10,6 +10,7 @@
#include <esp_efuse_table.h>
#include <sstream>
#include <iomanip>
#include <algorithm>
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;
}
+19 -3
View File
@@ -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; // 设备版本
};
-87
View File
@@ -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, "开始 OTAURL=%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;
}
-13
View File
@@ -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);
+1 -1
View File
@@ -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;
+3 -2
View File
@@ -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渲染类库
+12 -1
View File
@@ -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封装,测试通过