1. 优化了cpp_json的内容,使其更modern
2. 稍微优化了一下系统配置类 3. 增加了系统版本号,便于区分系统版本,方便OTA 4. 重写OTA的逻辑,完成了Cpp的OTA封装,测试通过
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
//
|
||||||
|
// Created by misaki on 2025/9/24.
|
||||||
|
//
|
||||||
|
#include "CommBean.h"
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
//
|
||||||
|
// Created by misaki on 2025/9/24.
|
||||||
|
//
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
// 静态成员初始化
|
// 静态成员初始化
|
||||||
WebSocketManager* WebSocketManager::instance = nullptr;
|
WebSocketManager* WebSocketManager::instance = nullptr;
|
||||||
std::mutex WebSocketManager::instance_mutex;
|
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";
|
static const char* TAG = "WebSocketManager";
|
||||||
@@ -72,15 +72,15 @@ bool WebSocketManager::initialize(const WebSocketConfig& ws_config) {
|
|||||||
this, &wifi_instance);
|
this, &wifi_instance);
|
||||||
|
|
||||||
// 启动发送线程
|
// 启动发送线程
|
||||||
|
// 只在线程未运行时启动发送线程
|
||||||
|
if (!send_thread.joinable()) {
|
||||||
threads_running = true;
|
threads_running = true;
|
||||||
|
|
||||||
ThreadConfig thread_config;
|
ThreadConfig thread_config;
|
||||||
thread_config.name = "WS_Send";
|
thread_config.name = "WS_Send";
|
||||||
thread_config.stack_size = 4096;
|
thread_config.stack_size = 4096;
|
||||||
|
|
||||||
send_thread = ThreadManager::createMemberThread(thread_config, this,
|
send_thread = ThreadManager::createMemberThread(thread_config, this,
|
||||||
&WebSocketManager::sendThread);
|
&WebSocketManager::sendThread);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,17 +118,24 @@ bool WebSocketManager::connect() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 启动重连和心跳线程
|
// 启动重连和心跳线程
|
||||||
|
// 只有在线程未运行时才启动新线程
|
||||||
|
if (!reconnect_thread.joinable()) {
|
||||||
ThreadConfig thread_config;
|
ThreadConfig thread_config;
|
||||||
thread_config.name = "WS_Reconnect";
|
thread_config.name = "WS_Reconnect";
|
||||||
thread_config.stack_size = 3072;
|
thread_config.stack_size = 3072;
|
||||||
|
|
||||||
reconnect_thread = ThreadManager::createMemberThread(thread_config, this,
|
reconnect_thread = ThreadManager::createMemberThread(thread_config, this,
|
||||||
&WebSocketManager::reconnectThread);
|
&WebSocketManager::reconnectThread);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!heartbeat_thread.joinable()) {
|
||||||
|
ThreadConfig thread_config;
|
||||||
thread_config.name = "WS_Heartbeat";
|
thread_config.name = "WS_Heartbeat";
|
||||||
|
thread_config.stack_size = 3072;
|
||||||
|
|
||||||
heartbeat_thread = ThreadManager::createMemberThread(thread_config, this,
|
heartbeat_thread = ThreadManager::createMemberThread(thread_config, this,
|
||||||
&WebSocketManager::heartbeatThread);
|
&WebSocketManager::heartbeatThread);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,25 +154,21 @@ void WebSocketManager::disconnect() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WebSocketManager::sendJson(cJSON* json) {
|
bool WebSocketManager::sendJson(const cppjson::Json& json) {
|
||||||
if (!connected) { // 检查连接状态
|
if (!connected) { // 检查连接状态
|
||||||
ESP_LOGE(TAG, "Not connected, cannot send data");
|
ESP_LOGE(TAG, "Not connected, cannot send data");
|
||||||
cJSON_Delete(json);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
std::string json_str = json.dump(); // 序列化 JSON
|
||||||
char* json_str = cJSON_PrintUnformatted(json); // 将JSON对象转换为字符串
|
if (json_str.empty() || json_str == "null") { // 检查序列化结果
|
||||||
cJSON_Delete(json); // 释放JSON对象
|
|
||||||
|
|
||||||
if (!json_str) { // 检查转换结果
|
|
||||||
ESP_LOGE(TAG, "Failed to stringify JSON");
|
ESP_LOGE(TAG, "Failed to stringify JSON");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(queue_mutex); // 锁定队列
|
{ // 临界区
|
||||||
send_queue.emplace(json_str); // 添加到队列
|
std::lock_guard<std::mutex> lock(queue_mutex);
|
||||||
free(json_str); // 释放json_str的内存
|
send_queue.emplace(std::move(json_str)); // 移动进队列,零拷贝
|
||||||
|
}
|
||||||
queue_cv.notify_one(); // 通知发送线程
|
queue_cv.notify_one(); // 通知发送线程
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -241,7 +244,7 @@ void WebSocketManager::websocketEventHandler(void* handler_args, esp_event_base_
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WEBSOCKET_EVENT_ERROR:
|
case WEBSOCKET_EVENT_ERROR: // 错误事件
|
||||||
ws_instance->connected = false;
|
ws_instance->connected = false;
|
||||||
ws_instance->connecting = 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 {
|
void WebSocketManager::handleReceivedData(const char* data, const int len) const {
|
||||||
// 尝试解析JSON
|
// 解析JSON
|
||||||
if (cJSON* json = cJSON_ParseWithLength(data, len)) {
|
cppjson::Json json = cppjson::Json::parse(std::string(data, len)); // 失败会得到空 Json
|
||||||
// 成功解析为JSON
|
if (!json.isNull()) { // 解析成功
|
||||||
if (json_callback) {
|
if (json_callback) json_callback(json);
|
||||||
json_callback(json);
|
} else { // 解析失败,当原始文本处理
|
||||||
} else {
|
if (event_callback)
|
||||||
cJSON_Delete(json);
|
event_callback(WebSocketEvent::DATA_RECEIVED, std::string(data, len));
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 不是JSON格式,作为原始数据处理
|
|
||||||
std::string message(data, len);
|
|
||||||
if (event_callback) {
|
|
||||||
event_callback(WebSocketEvent::DATA_RECEIVED, message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,7 +274,7 @@ void WebSocketManager::reconnectThread() {
|
|||||||
while (threads_running) {
|
while (threads_running) {
|
||||||
if (!connected && config.auto_reconnect &&
|
if (!connected && config.auto_reconnect &&
|
||||||
(config.max_reconnect_attempts == 0 ||
|
(config.max_reconnect_attempts == 0 ||
|
||||||
reconnect_attempts < config.max_reconnect_attempts)) {
|
reconnect_attempts < config.max_reconnect_attempts)) { // 检查重连条件
|
||||||
|
|
||||||
// 检查WiFi连接
|
// 检查WiFi连接
|
||||||
if (!wifi->isWifiConnect()) {
|
if (!wifi->isWifiConnect()) {
|
||||||
@@ -297,7 +293,7 @@ void WebSocketManager::reconnectThread() {
|
|||||||
ESP_LOGE(TAG, "Reconnection attempt failed");
|
ESP_LOGE(TAG, "Reconnection attempt failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// sleep
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(config.reconnect_interval));
|
std::this_thread::sleep_for(std::chrono::milliseconds(config.reconnect_interval));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -306,11 +302,10 @@ void WebSocketManager::heartbeatThread() {
|
|||||||
while (threads_running) {
|
while (threads_running) {
|
||||||
if (connected && config.heartbeat_interval > 0) { // 如果处于连接状态且心跳间隔大于0
|
if (connected && config.heartbeat_interval > 0) { // 如果处于连接状态且心跳间隔大于0
|
||||||
// 发送心跳消息
|
// 发送心跳消息
|
||||||
cJSON* heartbeat = cJSON_CreateObject();
|
cppjson::Json hb = cppjson::Json::object();
|
||||||
cJSON_AddStringToObject(heartbeat, "type", "heartbeat");
|
hb.set("type", cppjson::Json("heartbeat"))
|
||||||
cJSON_AddNumberToObject(heartbeat, "timestamp", static_cast<double>(esp_log_timestamp()));
|
.set("timestamp", cppjson::Json(esp_log_timestamp()));
|
||||||
|
sendJson(hb); // 已经重载好了,直接塞
|
||||||
sendJson(heartbeat);
|
|
||||||
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(config.heartbeat_interval));
|
std::this_thread::sleep_for(std::chrono::milliseconds(config.heartbeat_interval));
|
||||||
} else { // 否则就让出CPU
|
} else { // 否则就让出CPU
|
||||||
@@ -364,14 +359,18 @@ bool WebSocketManager::createWebSocketClient() {
|
|||||||
|
|
||||||
// 处理sn码逻辑
|
// 处理sn码逻辑
|
||||||
// 一般情况下sn码会在开机的时候从flash当中读出,因此此处判断是否为空
|
// 一般情况下sn码会在开机的时候从flash当中读出,因此此处判断是否为空
|
||||||
|
sn = SysConfJson::getInstance()->loadSN(); // 再读一次sn码
|
||||||
if (!sn.empty()) { // 如果sn码不为空
|
if (!sn.empty()) { // 如果sn码不为空
|
||||||
// 则在头部加入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 {
|
else {
|
||||||
// 否则在头部加入mac码和芯片序列号
|
// 如果sn码为空,设备可能存在问题
|
||||||
esp_websocket_client_append_header(client, "mac", ToolsClass::getChipMAC().c_str());
|
ESP_LOGE(TAG, "SN is empty, please check your device");
|
||||||
esp_websocket_client_append_header(client, "chip_id", ToolsClass::getChipSerialNumber().c_str());
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
#include <queue>
|
#include <queue>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include "esp_websocket_client.h"
|
#include "esp_websocket_client.h"
|
||||||
#include "cJSON.h"
|
#include "cpp_json.h"
|
||||||
#include "ThreadManager.h"
|
#include "ThreadManager.h"
|
||||||
#include "WifiConnectors.h"
|
#include "WifiConnectors.h"
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ struct WebSocketConfig {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// JSON数据回调
|
// 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)>;
|
using EventCallback = std::function<void(WebSocketEvent event, const std::string& message)>;
|
||||||
|
|
||||||
@@ -98,8 +98,8 @@ public:
|
|||||||
// 断开连接
|
// 断开连接
|
||||||
void disconnect();
|
void disconnect();
|
||||||
|
|
||||||
// 发送JSON数据,注意此成员函数会释放json数据,调用之后请不要再次释放json数据
|
// 发送JSON数据
|
||||||
bool sendJson(cJSON* json);
|
bool sendJson(const cppjson::Json& json);
|
||||||
|
|
||||||
// 发送原始字符串数据
|
// 发送原始字符串数据
|
||||||
bool sendRaw(const std::string& data);
|
bool sendRaw(const std::string& data);
|
||||||
|
|||||||
@@ -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() {
|
void Cpp_Hand() {
|
||||||
// testMIC();
|
// testMIC();
|
||||||
// testPetSystem();
|
// testPetSystem();
|
||||||
|
|
||||||
OTAClass oc;
|
ESP_LOGI("CppHandle::Cpp_Hand", "当前固件版本 %s:", ToolsClass::getDeviceVersion().c_str());
|
||||||
oc.Init();
|
|
||||||
|
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秒
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
@@ -3,170 +3,15 @@
|
|||||||
//
|
//
|
||||||
#include "OTAClass.h"
|
#include "OTAClass.h"
|
||||||
#include "esp_log.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/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 <string>
|
||||||
|
|
||||||
#include "LVGLRender.h"
|
|
||||||
#include "SDFileManager.h"
|
#include "SDFileManager.h"
|
||||||
#include "AudioOutput.h"
|
|
||||||
#include "CommClass.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() {
|
void OTAClass::Init() {
|
||||||
ESP_LOGI("OTA", "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);
|
std::string listing = SDFileManager::getInstance()->lsCommand(".", false, true);
|
||||||
ESP_LOGI("SD", "%s", listing.c_str());
|
ESP_LOGI("SD", "%s", listing.c_str());
|
||||||
@@ -205,84 +50,10 @@ void OTAClass::Init() {
|
|||||||
// );
|
// );
|
||||||
// wifi_thread.detach();
|
// 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
|
// 启动OTA更新线程,前提是已经连接WiFi
|
||||||
void OTAClass::Update() {
|
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,27 +9,27 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 如何使用:
|
* 如何使用:\n
|
||||||
std::string text = R"({"name":"Misaki","age":18,"skills":["C++","RISC-V"]})";
|
std::string text = R"({"name":"Misaki","age":18,"skills":["C++","RISC-V"]})"; \n
|
||||||
auto j = cppjson::Json::parse(text);
|
auto j = cppjson::Json::parse(text);\n
|
||||||
std::cout << "name = " << j["name"].asString() << '\n';
|
std::cout << "name = " << j["name"].asString() << '\n';\n
|
||||||
j["skills"].append(cppjson::Json("Go"));
|
j["skills"].append(cppjson::Json("Go"));\n
|
||||||
std::cout << j.dumpPretty() << '\n';
|
std::cout << j.dumpPretty() << '\n';\n
|
||||||
|
|
||||||
or:
|
or:\n
|
||||||
Json j = Json::object();
|
Json j = Json::object();\n
|
||||||
j.set("name", Json("misaki"))
|
j.set("name", Json("misaki"))\n
|
||||||
.set("age", Json(24))
|
.set("age", Json(24))\n
|
||||||
.set("vip", Json(true))
|
.set("vip", Json(true))\n
|
||||||
.set("list", Json::array()
|
.set("list", Json::array()\n
|
||||||
.append(Json(1))
|
.append(Json(1))\n
|
||||||
.append(Json("hello")));
|
.append(Json("hello")));\n
|
||||||
|
|
||||||
std::cout << j.dumpPretty() << "\n";
|
std::cout << j.dumpPretty() << "\n";\n
|
||||||
|
|
||||||
Json j2 = j["list"];
|
Json j2 = j["list"];\n
|
||||||
for (auto& v : j2) std::cout << v.dump() << " ";
|
for (auto& v : j2) std::cout << v.dump() << " ";\n
|
||||||
std::cout << "\n";
|
std::cout << "\n";\n
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// cpp_json.hpp
|
// cpp_json.hpp
|
||||||
@@ -53,8 +53,14 @@ class Json {
|
|||||||
public:
|
public:
|
||||||
enum Type { Null, Bool, Number, String, ArrayType, ObjectType };
|
enum Type { Null, Bool, Number, String, ArrayType, ObjectType };
|
||||||
|
|
||||||
/*-------- 构造 ------------------------------------------------*/
|
// 构造
|
||||||
Json() noexcept : ptr_(nullptr), owner_(false) {}
|
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(std::nullptr_t) noexcept : Json() {}
|
||||||
explicit Json(bool v) : ptr_(cJSON_CreateBool(v)), owner_(true) {}
|
explicit Json(bool v) : ptr_(cJSON_CreateBool(v)), owner_(true) {}
|
||||||
explicit Json(int v) : ptr_(cJSON_CreateNumber(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 std::string& v):ptr_(cJSON_CreateString(v.c_str())), owner_(true) {}
|
||||||
explicit Json(const Array& arr); // 类外实现
|
explicit Json(const Array& arr); // 类外实现
|
||||||
explicit Json(const Object& obj); // 类外实现
|
explicit Json(const Object& obj); // 类外实现
|
||||||
/* 接管原始指针,owner=true 会负责 delete */
|
// 接管原始指针,owner=true 会负责 delete,切记不要接管后再在外部cJSON_Delete
|
||||||
explicit Json(cJSON* raw, bool owner = true) noexcept
|
explicit Json(cJSON* raw, bool owner = true) noexcept
|
||||||
: ptr_(raw), owner_(owner) {}
|
: ptr_(raw), owner_(owner) {}
|
||||||
|
|
||||||
/*-------- 拷贝/移动 -------------------------------------------*/
|
// 拷贝/移动
|
||||||
Json(const Json& rhs)
|
Json(const Json& rhs)
|
||||||
: ptr_(rhs.ptr_ ? cJSON_Duplicate(rhs.ptr_, 1) : nullptr), owner_(true) {}
|
: ptr_(rhs.ptr_ ? cJSON_Duplicate(rhs.ptr_, 1) : nullptr), owner_(true) {}
|
||||||
Json(Json&& rhs) noexcept
|
Json(Json&& rhs) noexcept
|
||||||
@@ -75,7 +81,7 @@ public:
|
|||||||
Json& operator=(Json rhs) noexcept { swap(rhs); return *this; }
|
Json& operator=(Json rhs) noexcept { swap(rhs); return *this; }
|
||||||
~Json() { reset(); }
|
~Json() { reset(); }
|
||||||
|
|
||||||
/*-------- 工厂 ------------------------------------------------*/
|
// 工厂
|
||||||
static Json parse(const std::string& text) {
|
static Json parse(const std::string& text) {
|
||||||
cJSON* p = cJSON_Parse(text.c_str());
|
cJSON* p = cJSON_Parse(text.c_str());
|
||||||
if (!p) throw std::runtime_error("cppjson::parse failed");
|
if (!p) throw std::runtime_error("cppjson::parse failed");
|
||||||
@@ -84,7 +90,7 @@ public:
|
|||||||
static Json array() { return Json(cJSON_CreateArray(), true); }
|
static Json array() { return Json(cJSON_CreateArray(), true); }
|
||||||
static Json object() { return Json(cJSON_CreateObject(), true); }
|
static Json object() { return Json(cJSON_CreateObject(), true); }
|
||||||
|
|
||||||
/*-------- 序列化 ----------------------------------------------*/
|
// 序列化
|
||||||
[[nodiscard]] std::string dump() const {
|
[[nodiscard]] std::string dump() const {
|
||||||
if (!ptr_) return "null";
|
if (!ptr_) return "null";
|
||||||
char* s = cJSON_PrintUnformatted(ptr_);
|
char* s = cJSON_PrintUnformatted(ptr_);
|
||||||
@@ -100,7 +106,7 @@ public:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*-------- 类型查询 --------------------------------------------*/
|
// 类型查询
|
||||||
[[nodiscard]] Type type() const noexcept {
|
[[nodiscard]] Type type() const noexcept {
|
||||||
if (!ptr_) return Null;
|
if (!ptr_) return Null;
|
||||||
switch (ptr_->type & 0xFF) {
|
switch (ptr_->type & 0xFF) {
|
||||||
@@ -120,13 +126,13 @@ public:
|
|||||||
[[nodiscard]] bool isArray() const noexcept { return type() == ArrayType; }
|
[[nodiscard]] bool isArray() const noexcept { return type() == ArrayType; }
|
||||||
[[nodiscard]] bool isObject() const noexcept { return type() == ObjectType; }
|
[[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]] 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]] 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]] 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]] std::string asString() const { if (!isString()) throw std::runtime_error("not string"); return ptr_->valuestring; }
|
||||||
|
|
||||||
/*-------- 数组/对象接口 ---------------------------------------*/
|
// 数组/对象接口
|
||||||
[[nodiscard]] size_t size() const {
|
[[nodiscard]] size_t size() const {
|
||||||
if (isArray() || isObject()) return cJSON_GetArraySize(ptr_);
|
if (isArray() || isObject()) return cJSON_GetArraySize(ptr_);
|
||||||
return 0;
|
return 0;
|
||||||
@@ -152,7 +158,7 @@ public:
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*-------- 迭代器(只读)---------------------------------------*/
|
// 迭代器(只读)
|
||||||
template <bool IsConst>
|
template <bool IsConst>
|
||||||
struct iterator_impl {
|
struct iterator_impl {
|
||||||
using iterator_category = std::forward_iterator_tag;
|
using iterator_category = std::forward_iterator_tag;
|
||||||
@@ -194,7 +200,7 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/*================ 类外实现:Array / Object 构造 =================*/
|
// 类外实现:Array / Object 构造
|
||||||
inline Json::Json(const Array& arr)
|
inline Json::Json(const Array& arr)
|
||||||
: ptr_(cJSON_CreateArray()), owner_(true) {
|
: ptr_(cJSON_CreateArray()), owner_(true) {
|
||||||
for (const auto& v : arr) append(v);
|
for (const auto& v : arr) append(v);
|
||||||
|
|||||||
@@ -3,3 +3,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#include "sys_conf_singleton.h"
|
#include "sys_conf_singleton.h"
|
||||||
|
|
||||||
|
// 静态成员变量定义
|
||||||
|
SysConfJson* SysConfJson::instance = nullptr;
|
||||||
|
std::mutex SysConfJson::instanceMutex;
|
||||||
|
|||||||
@@ -13,16 +13,16 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
static constexpr char kSysConfPartName[] = "sys_conf";
|
|
||||||
|
|
||||||
template <const char* PartitionLabel>
|
|
||||||
class SysConfJson {
|
class SysConfJson {
|
||||||
public:
|
public:
|
||||||
static SysConfJson& instance() {
|
static SysConfJson* getInstance() {
|
||||||
static SysConfJson inst;
|
std::lock_guard<std::mutex> lock(instanceMutex);
|
||||||
return inst;
|
if (instance == nullptr) {
|
||||||
|
instance = new SysConfJson();
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 把 sn 持久化到 <mount>/sn.json,成功返回 true */
|
/* 把 sn 持久化到 <mount>/sn.json,成功返回 true */
|
||||||
bool saveSN(const std::string& sn)
|
bool saveSN(const std::string& sn)
|
||||||
{
|
{
|
||||||
@@ -30,7 +30,6 @@ public:
|
|||||||
doc.set("sn", cppjson::Json(sn)); // {"sn":"xxxxxx"}
|
doc.set("sn", cppjson::Json(sn)); // {"sn":"xxxxxx"}
|
||||||
return write("sn", doc); // 实际文件 = <mount>/sn.json
|
return write("sn", doc); // 实际文件 = <mount>/sn.json
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 读取 sn,如果文件/字段不存在返回空串(绝不抛异常) */
|
/* 读取 sn,如果文件/字段不存在返回空串(绝不抛异常) */
|
||||||
std::string loadSN()
|
std::string loadSN()
|
||||||
{
|
{
|
||||||
@@ -39,14 +38,12 @@ public:
|
|||||||
cppjson::Json snNode = j["sn"];
|
cppjson::Json snNode = j["sn"];
|
||||||
return snNode.isString() ? snNode.asString() : "";
|
return snNode.isString() ? snNode.asString() : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 直接覆盖写,无 tmp */
|
/* 直接覆盖写,无 tmp */
|
||||||
bool write(const char* key, const cppjson::Json& j) {
|
bool write(const char* key, const cppjson::Json& j) {
|
||||||
const std::string path = buildPath(key);
|
const std::string path = buildPath(key);
|
||||||
const std::string txt = j.dump();
|
const std::string txt = j.dump();
|
||||||
return writeRaw(path, txt);
|
return writeRaw(path, txt);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 读 */
|
/* 读 */
|
||||||
cppjson::Json read(const char* key) const {
|
cppjson::Json read(const char* key) const {
|
||||||
std::string content;
|
std::string content;
|
||||||
@@ -58,13 +55,11 @@ public:
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 删文件 */
|
/* 删文件 */
|
||||||
bool remove(const char* key) const {
|
bool remove(const char* key) const {
|
||||||
const std::string path = buildPath(key);
|
const std::string path = buildPath(key);
|
||||||
return ::unlink(path.c_str()) == 0;
|
return ::unlink(path.c_str()) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 列文件 */
|
/* 列文件 */
|
||||||
[[nodiscard]] std::vector<std::string> ls() const {
|
[[nodiscard]] std::vector<std::string> ls() const {
|
||||||
std::vector<std::string> names;
|
std::vector<std::string> names;
|
||||||
@@ -78,35 +73,24 @@ public:
|
|||||||
::closedir(dir);
|
::closedir(dir);
|
||||||
return names;
|
return names;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 格式化 */
|
/* 格式化 */
|
||||||
[[nodiscard]] bool format() const {
|
[[nodiscard]] bool format() const {
|
||||||
ESP_LOGW(TAG, "format <%s>", PartitionLabel);
|
ESP_LOGW(TAG, "format <%s>", kSysConfPartName);
|
||||||
return esp_vfs_fat_spiflash_format_rw_wl(kMount, PartitionLabel) == ESP_OK;
|
return esp_vfs_fat_spiflash_format_rw_wl(kMount, kSysConfPartName) == ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 禁止拷贝 */
|
/* 禁止拷贝 */
|
||||||
SysConfJson(const SysConfJson&) = delete;
|
SysConfJson(const SysConfJson&) = delete;
|
||||||
SysConfJson& operator=(const SysConfJson&) = delete;
|
SysConfJson& operator=(const SysConfJson&) = delete;
|
||||||
|
|
||||||
private:
|
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() { mount(); }
|
||||||
~SysConfJson() { unmount(); }
|
~SysConfJson() { unmount(); }
|
||||||
|
|
||||||
void mount() {
|
void mount() {
|
||||||
esp_vfs_fat_mount_config_t cfg = {
|
esp_vfs_fat_mount_config_t cfg = {
|
||||||
.format_if_mount_failed = true,
|
.format_if_mount_failed = true, // 如果 FAT 分区无法挂载,并且此参数为 true,则创建分区表并格式化文件系统。
|
||||||
.max_files = 8,
|
.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) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGE(TAG, "mount fail <%s>", esp_err_to_name(err));
|
ESP_LOGE(TAG, "mount fail <%s>", esp_err_to_name(err));
|
||||||
return;
|
return;
|
||||||
@@ -114,14 +98,12 @@ private:
|
|||||||
mounted_ = true;
|
mounted_ = true;
|
||||||
ESP_LOGI(TAG, "FAT mounted at %s", kMount);
|
ESP_LOGI(TAG, "FAT mounted at %s", kMount);
|
||||||
}
|
}
|
||||||
|
|
||||||
void unmount() {
|
void unmount() {
|
||||||
if (!mounted_) return;
|
if (!mounted_) return;
|
||||||
esp_vfs_fat_spiflash_unmount_rw_wl(kMount, wl_);
|
esp_vfs_fat_spiflash_unmount_rw_wl(kMount, wl_);
|
||||||
wl_ = WL_INVALID_HANDLE;
|
wl_ = WL_INVALID_HANDLE;
|
||||||
mounted_ = false;
|
mounted_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool writeRaw(const std::string& path, const std::string& txt) {
|
bool writeRaw(const std::string& path, const std::string& txt) {
|
||||||
FILE* f = std::fopen(path.c_str(), "wb");
|
FILE* f = std::fopen(path.c_str(), "wb");
|
||||||
if (!f) return false;
|
if (!f) return false;
|
||||||
@@ -129,7 +111,6 @@ private:
|
|||||||
std::fclose(f);
|
std::fclose(f);
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool readRaw(const char* key, std::string& out) const {
|
bool readRaw(const char* key, std::string& out) const {
|
||||||
const std::string path = buildPath(key);
|
const std::string path = buildPath(key);
|
||||||
FILE* f = std::fopen(path.c_str(), "rb");
|
FILE* f = std::fopen(path.c_str(), "rb");
|
||||||
@@ -146,6 +127,15 @@ private:
|
|||||||
std::string buildPath(const char* key) const {
|
std::string buildPath(const char* key) const {
|
||||||
return std::string(kMount) + "/" + key;
|
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()
|
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
#include <esp_efuse_table.h>
|
#include <esp_efuse_table.h>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
static const char *TAG = "ToolsClass";
|
static const char *TAG = "ToolsClass";
|
||||||
|
|
||||||
@@ -39,4 +40,31 @@ std::string ToolsClass::getChipMAC() {
|
|||||||
return ss.str();
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -12,20 +12,36 @@ class ToolsClass {
|
|||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* 获取当前时间
|
* 获取当前时间
|
||||||
* @return
|
* @return 当前时间
|
||||||
*/
|
*/
|
||||||
static std::string getCurrentTime();
|
static std::string getCurrentTime();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取esp32s3的芯片序列号
|
* 获取esp32s3的芯片序列号
|
||||||
* @return
|
* @return 芯片序列号
|
||||||
*/
|
*/
|
||||||
static std::string getChipSerialNumber();
|
static std::string getChipSerialNumber();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取esp32s3的MAC地址
|
* 获取esp32s3的MAC地址
|
||||||
* @return
|
* @return MAC地址
|
||||||
*/
|
*/
|
||||||
static std::string getChipMAC();
|
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; // 设备版本
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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);
|
|
||||||
@@ -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
|
// 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 = {
|
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
|
||||||
.format_if_mount_failed = true,
|
.format_if_mount_failed = true,
|
||||||
.max_files = 5,
|
.max_files = 10,
|
||||||
.allocation_unit_size = 16 * 1024
|
.allocation_unit_size = 16 * 1024
|
||||||
};
|
};
|
||||||
sdmmc_card_t *card;
|
sdmmc_card_t *card;
|
||||||
|
|||||||
+3
-2
@@ -25,15 +25,16 @@ idf_component_register(SRCS "Bionic_sphere.c"
|
|||||||
"../Lib/Display/Touch_Driver/esp_lcd_touch/esp_lcd_touch.c" # 触摸屏驱动库
|
"../Lib/Display/Touch_Driver/esp_lcd_touch/esp_lcd_touch.c" # 触摸屏驱动库
|
||||||
"../Lib/PWR_Key/PWR_Key.c" # PWR按键驱动库
|
"../Lib/PWR_Key/PWR_Key.c" # PWR按键驱动库
|
||||||
"../Lib/MIC_Driver/MIC_Speech.c" # 录音驱动库
|
"../Lib/MIC_Driver/MIC_Speech.c" # 录音驱动库
|
||||||
"../Lib/OTA_Driver/app_ota.c" # OTA驱动库
|
"../Lib/OTA_Driver/ota_ws.c" # OTA驱动库
|
||||||
"../Lib/OTA_Driver/ota_ws.c"
|
|
||||||
# 业务代码(使用Cpp编写)
|
# 业务代码(使用Cpp编写)
|
||||||
"../Bionic_Core/PetBaseClass/PetBaseClass.cpp" # 宠物基类库
|
"../Bionic_Core/PetBaseClass/PetBaseClass.cpp" # 宠物基类库
|
||||||
"../Bionic_Core/PetBaseClass/PetInterface.cpp" # 宠物接口层
|
"../Bionic_Core/PetBaseClass/PetInterface.cpp" # 宠物接口层
|
||||||
"../Bionic_Core/PetBaseClass/PetObserver.cpp" # 宠物观察者库
|
"../Bionic_Core/PetBaseClass/PetObserver.cpp" # 宠物观察者库
|
||||||
"../Bionic_Core/PetBaseClass/PetDao.cpp" # 宠物数据访问层
|
"../Bionic_Core/PetBaseClass/PetDao.cpp" # 宠物数据访问层
|
||||||
"../Bionic_Core/OTAClass/OTAClass.cpp" # OTA类库
|
"../Bionic_Core/OTAClass/OTAClass.cpp" # OTA类库
|
||||||
|
"../Bionic_Core/OTAClass/HttpOtaUpdater.cpp"
|
||||||
"../Bionic_Core/CommClass/CommClass.cpp" # 通信类库
|
"../Bionic_Core/CommClass/CommClass.cpp" # 通信类库
|
||||||
|
"../Bionic_Core/CommClass/CommBean.cpp" # 通信交互实体
|
||||||
"../Bionic_Core/ToolsClass/ToolsClass.cpp" # 工具类库
|
"../Bionic_Core/ToolsClass/ToolsClass.cpp" # 工具类库
|
||||||
"../Bionic_Core/ToolsClass/AudioOutput/AudioOutput.cpp" # 音频输出类库
|
"../Bionic_Core/ToolsClass/AudioOutput/AudioOutput.cpp" # 音频输出类库
|
||||||
"../Bionic_Core/ToolsClass/LVGL_Render/LVGLRender.cpp" # LVGL渲染类库
|
"../Bionic_Core/ToolsClass/LVGL_Render/LVGLRender.cpp" # LVGL渲染类库
|
||||||
|
|||||||
@@ -243,7 +243,18 @@
|
|||||||
#### Day16 2025.9.23(前两天在忙考研复习)
|
#### Day16 2025.9.23(前两天在忙考研复习)
|
||||||
##### 主要目标:完成具体业务开发&各种优化
|
##### 主要目标:完成具体业务开发&各种优化
|
||||||
实际完成任务:
|
实际完成任务:
|
||||||
- [x] 1. 基于cJSON进行了上层现代C++封装,作为脚手架使用
|
- [x] 1. 基于cJSON进行了上层现代C++封装(cpp_json),作为脚手架使用
|
||||||
|
|
||||||
- [x] 2. 在esp32s3剩余的flash当中开辟了一个文件系统,用于保存设备重要配置信息
|
- [x] 2. 在esp32s3剩余的flash当中开辟了一个文件系统,用于保存设备重要配置信息
|
||||||
|
|
||||||
|
#### Day17 2025.9.24
|
||||||
|
##### 主要目标:完成具体业务开发&各种优化
|
||||||
|
实际完成任务:
|
||||||
|
- [x] 1. 优化了cpp_json的内容,使其更modern
|
||||||
|
|
||||||
|
- [x] 2. 稍微优化了一下系统配置类
|
||||||
|
|
||||||
|
- [x] 3. 增加了系统版本号,便于区分系统版本,方便OTA
|
||||||
|
|
||||||
|
- [x] 4. 重写OTA的逻辑,完成了Cpp的OTA封装,测试通过
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user