diff --git a/Bionic_Core/CommClass/CommClass.cpp b/Bionic_Core/CommClass/CommClass.cpp index e8034ed..8219f35 100644 --- a/Bionic_Core/CommClass/CommClass.cpp +++ b/Bionic_Core/CommClass/CommClass.cpp @@ -9,9 +9,13 @@ #include #include +#include "ToolsClass.h" +#include "sys_conf_singleton.h" + // 静态成员初始化 WebSocketManager* WebSocketManager::instance = nullptr; std::mutex WebSocketManager::instance_mutex; +std::string WebSocketManager::sn = SYS_CONF_JSON().loadSN(); // 获取SN(同时也能初始化文件系统,如果还没有初始化这个文件系统的话) // 标签用于日志 static const char* TAG = "WebSocketManager"; @@ -51,21 +55,21 @@ WebSocketManager::~WebSocketManager() { } WebSocketManager* WebSocketManager::getInstance() { - std::lock_guard lock(instance_mutex); + std::lock_guard lock(instance_mutex); // 加锁 if (instance == nullptr) { instance = new WebSocketManager(); } return instance; } -bool WebSocketManager::initialize(const WebSocketConfig& config) { - this->config = config; +bool WebSocketManager::initialize(const WebSocketConfig& ws_config) { + this->config = ws_config; // 注册WiFi事件处理 - esp_event_handler_instance_t instance; + esp_event_handler_instance_t wifi_instance; esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &WebSocketManager::wifiEventHandler, - this, &instance); + this, &wifi_instance); // 启动发送线程 threads_running = true; @@ -159,8 +163,8 @@ bool WebSocketManager::sendJson(cJSON* json) { } std::lock_guard lock(queue_mutex); // 锁定队列 - send_queue.push(json_str); // 添加到队列 - free(json_str); // 释放内存 + send_queue.emplace(json_str); // 添加到队列 + free(json_str); // 释放json_str的内存 queue_cv.notify_one(); // 通知发送线程 return true; @@ -178,11 +182,11 @@ bool WebSocketManager::sendRaw(const std::string& data) { return true; } -void WebSocketManager::setJsonCallback(JsonDataCallback callback) { +void WebSocketManager::setJsonCallback(const JsonDataCallback &callback) { json_callback = callback; } -void WebSocketManager::setEventCallback(EventCallback callback) { +void WebSocketManager::setEventCallback(const EventCallback &callback) { event_callback = callback; } @@ -194,8 +198,8 @@ WebSocketConfig WebSocketManager::getConfig() const { return config; } -void WebSocketManager::updateConfig(const WebSocketConfig& config) { - this->config = config; +void WebSocketManager::updateConfig(const WebSocketConfig& ws_config) { + this->config = ws_config; } WebSocketManager::Stats WebSocketManager::getStats() const { @@ -204,55 +208,57 @@ WebSocketManager::Stats WebSocketManager::getStats() const { void WebSocketManager::websocketEventHandler(void* handler_args, esp_event_base_t base, int32_t event_id, void* event_data) { - WebSocketManager* instance = static_cast(handler_args); - esp_websocket_event_data_t* data = (esp_websocket_event_data_t*)event_data; + auto* ws_instance = static_cast(handler_args); + auto* data = static_cast(event_data); switch(event_id) { case WEBSOCKET_EVENT_CONNECTED: - instance->connected = true; - instance->connecting = false; - instance->reconnect_attempts = 0; - instance->stats.successful_connections++; + ws_instance->connected = true; + ws_instance->connecting = false; + ws_instance->reconnect_attempts = 0; + ws_instance->stats.successful_connections++; ESP_LOGI(TAG, "WebSocket connected"); - if (instance->event_callback) { - instance->event_callback(WebSocketEvent::CONNECTED, "Connected successfully"); + if (ws_instance->event_callback) { + ws_instance->event_callback(WebSocketEvent::CONNECTED, "Connected successfully"); } break; case WEBSOCKET_EVENT_DISCONNECTED: - instance->connected = false; - instance->connecting = false; + ws_instance->connected = false; + ws_instance->connecting = false; ESP_LOGI(TAG, "WebSocket disconnected"); - if (instance->event_callback) { - instance->event_callback(WebSocketEvent::DISCONNECTED, "Disconnected"); + if (ws_instance->event_callback) { + ws_instance->event_callback(WebSocketEvent::DISCONNECTED, "Disconnected"); } break; case WEBSOCKET_EVENT_DATA: if (data->data_len > 0) { - instance->stats.messages_received++; - instance->handleReceivedData((const char*)data->data_ptr, data->data_len); + ws_instance->stats.messages_received++; + ws_instance->handleReceivedData((const char*)data->data_ptr, data->data_len); } break; case WEBSOCKET_EVENT_ERROR: - instance->connected = false; - instance->connecting = false; + ws_instance->connected = false; + ws_instance->connecting = false; ESP_LOGE(TAG, "WebSocket error"); - if (instance->event_callback) { - instance->event_callback(WebSocketEvent::ERROR, "WebSocket error occurred"); + if (ws_instance->event_callback) { + ws_instance->event_callback(WebSocketEvent::ERROR, "WebSocket error occurred"); } break; + default: + ESP_LOGW(TAG, "Unhandled WebSocket event: %ld", event_id); + break; } } -void WebSocketManager::handleReceivedData(const char* data, int len) { +void WebSocketManager::handleReceivedData(const char* data, const int len) const { // 尝试解析JSON - cJSON* json = cJSON_ParseWithLength(data, len); - if (json) { + if (cJSON* json = cJSON_ParseWithLength(data, len)) { // 成功解析为JSON if (json_callback) { json_callback(json); @@ -298,16 +304,16 @@ void WebSocketManager::reconnectThread() { void WebSocketManager::heartbeatThread() { while (threads_running) { - if (connected && config.heartbeat_interval > 0) { + if (connected && config.heartbeat_interval > 0) { // 如果处于连接状态且心跳间隔大于0 // 发送心跳消息 cJSON* heartbeat = cJSON_CreateObject(); cJSON_AddStringToObject(heartbeat, "type", "heartbeat"); - cJSON_AddNumberToObject(heartbeat, "timestamp", esp_log_timestamp()); + cJSON_AddNumberToObject(heartbeat, "timestamp", static_cast(esp_log_timestamp())); sendJson(heartbeat); std::this_thread::sleep_for(std::chrono::milliseconds(config.heartbeat_interval)); - } else { + } else { // 否则就让出CPU std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } } @@ -316,23 +322,24 @@ void WebSocketManager::heartbeatThread() { void WebSocketManager::sendThread() { while (threads_running) { std::unique_lock lock(queue_mutex); - queue_cv.wait(lock, [this]() { + queue_cv.wait(lock, // 传入已持有的 unique_lock + [this]() { // 等待队列非空或者线程停止 return !send_queue.empty() || !threads_running; }); - if (!threads_running) { + if (!threads_running) { // 其实永远不会进入这里,写上只是保险起见 break; } - if (!send_queue.empty() && connected) { + if (!send_queue.empty() && connected) { // 如果队列非空且已连接 std::string data = std::move(send_queue.front()); send_queue.pop(); - lock.unlock(); + lock.unlock(); // 释放锁 // 发送数据 int sent = esp_websocket_client_send_text(client, data.c_str(), static_cast(data.length()), portMAX_DELAY); if (sent >= 0) { - stats.messages_sent++; + stats.messages_sent++; // 发送成功 } else { ESP_LOGE(TAG, "Failed to send data"); // 将数据重新放回队列 @@ -355,6 +362,19 @@ bool WebSocketManager::createWebSocketClient() { return false; } + // 处理sn码逻辑 + // 一般情况下sn码会在开机的时候从flash当中读出,因此此处判断是否为空 + if (!sn.empty()) { // 如果sn码不为空 + // 则在头部加入sn码提交 + esp_websocket_client_append_header(client, "sn", sn.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()); + } + + // 注册事件处理 esp_websocket_register_events(client, WEBSOCKET_EVENT_ANY, &WebSocketManager::websocketEventHandler, this); @@ -371,14 +391,14 @@ void WebSocketManager::destroyWebSocketClient() { } void WebSocketManager::wifiEventHandler(void* arg, esp_event_base_t event_base, - int32_t event_id, void* event_data) { - WebSocketManager* instance = static_cast(arg); + int32_t event_Id, void* event_data) { + auto* ws_instance = static_cast(arg); - if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { + if (event_base == IP_EVENT && event_Id == IP_EVENT_STA_GOT_IP) { // WiFi已连接,尝试重新连接WebSocket - if (instance->config.auto_reconnect && !instance->connected) { + if (ws_instance->config.auto_reconnect && !ws_instance->connected) { ESP_LOGI(TAG, "WiFi connected, attempting to reconnect WebSocket"); - instance->connect(); + ws_instance->connect(); } } } \ No newline at end of file diff --git a/Bionic_Core/CommClass/CommClass.h b/Bionic_Core/CommClass/CommClass.h index a38826d..4da6c18 100644 --- a/Bionic_Core/CommClass/CommClass.h +++ b/Bionic_Core/CommClass/CommClass.h @@ -6,7 +6,6 @@ #include #include - #include #include #include @@ -37,6 +36,11 @@ using JsonDataCallback = std::function; // 事件回调 using EventCallback = std::function; +// 在此说明一下上面的两个回调函数的作用,主要目的还是为了解耦,在handleReceivedData有对这两个回调的调用 +// 对于JSON数据回调,如果来自客户端的数据被解析为了数据,那么就会调用JSON数据回调,并将解析后的json数据传给JSON数据回调,回调函数内部只需要访问并释放json数据 +// 对于WebSocket事件回调,会有所不同,重点需要关注DATA_RECEIVED事件,这是非json数据的情况,其他的见下面的示例 +// 实际上对于本项目,几乎99%的情况都是只使用JSON数据回调 + /* 回调函数示例: // JSON数据回调示例 void onJsonData(cJSON* json) { @@ -86,7 +90,7 @@ public: WebSocketManager& operator=(const WebSocketManager&) = delete; // 初始化WebSocket管理器 - bool initialize(const WebSocketConfig& config); + bool initialize(const WebSocketConfig& ws_config); // 连接到WebSocket服务器 bool connect(); @@ -94,26 +98,26 @@ public: // 断开连接 void disconnect(); - // 发送JSON数据 + // 发送JSON数据,注意此成员函数会释放json数据,调用之后请不要再次释放json数据 bool sendJson(cJSON* json); // 发送原始字符串数据 bool sendRaw(const std::string& data); // 设置JSON数据回调 - void setJsonCallback(JsonDataCallback callback); + void setJsonCallback(const JsonDataCallback &callback); // 设置事件回调 - void setEventCallback(EventCallback callback); + void setEventCallback(const EventCallback &callback); // 获取连接状态 - bool isConnected() const; + [[nodiscard]] bool isConnected() const; // 获取配置信息 - WebSocketConfig getConfig() const; + [[nodiscard]] WebSocketConfig getConfig() const; // 更新配置 - void updateConfig(const WebSocketConfig& config); + void updateConfig(const WebSocketConfig& ws_config); // 获取统计信息 struct Stats { @@ -122,7 +126,7 @@ public: uint32_t connection_attempts; /// 连接尝试次数 uint32_t successful_connections; /// 成功连接次数 }; - Stats getStats() const; + [[nodiscard]] Stats getStats() const; private: WebSocketManager(); // 私有构造函数 @@ -143,7 +147,7 @@ private: * @param data 数据 * @param len 数据长度 */ - void handleReceivedData(const char* data, int len); + void handleReceivedData(const char* data, int len) const; // 重连线程函数 void reconnectThread(); @@ -196,4 +200,14 @@ private: // WiFi实例 WifiConnectors* wifi; + + // sn码和设备信息 + static std::string sn; /// sn码会在设备首次连网后连接服务器后经过服务器计算后返回唯一的sn码 + /// 在首次连接服务器后会提交设备的芯片序列号和mac地址,以用于计算唯一sn码 + /// 在计算后,服务器首先会把计算好的sn码返回给设备,并保存起来 + /// 客户端会将服务器返回的sn码存在设备flash当中 + /// 这样客户端和服务端之间就都有sn码了 + /// 当客户端再次连接服务器的时候,会先从flash中读取sn码,如果sn码存在,则将sn码发送给服务器以建立连接 + /// 如此下来服务器也方便找到设备保存的信息 + std::string device_info = "esp32s3"; /// 可以根据不同的设备进行修改,在本项目中使用的是esp32s3 }; \ No newline at end of file diff --git a/Bionic_Core/CppHandle/CppHandle.cpp b/Bionic_Core/CppHandle/CppHandle.cpp index 67438da..4363e17 100644 --- a/Bionic_Core/CppHandle/CppHandle.cpp +++ b/Bionic_Core/CppHandle/CppHandle.cpp @@ -203,7 +203,7 @@ void testMIC() { } void Cpp_Hand() { - testMIC(); + // testMIC(); // testPetSystem(); OTAClass oc; diff --git a/Bionic_Core/OTAClass/OTAClass.cpp b/Bionic_Core/OTAClass/OTAClass.cpp index a234d71..62b5aa3 100644 --- a/Bionic_Core/OTAClass/OTAClass.cpp +++ b/Bionic_Core/OTAClass/OTAClass.cpp @@ -145,12 +145,28 @@ void websocket_task() { 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()); @@ -164,13 +180,13 @@ void OTAClass::Init() { listing = SDFileManager::getInstance()->lsCommand(".", false, true); ESP_LOGI("SD", "%s", listing.c_str()); - LVGLRender::getInstance()->RenderGif("sequence01m.gif"); + // LVGLRender::getInstance()->RenderGif("gzl_m.gif"); // 设置音量 - AudioOutput::getInstance()->setVolume(5); + // AudioOutput::getInstance()->setVolume(5); // 同步播放 - AudioOutput::getInstance()->playSync("/sdcard/music", "kokoronashi.mp3"); + // AudioOutput::getInstance()->playSync("/sdcard/music", "kokoronashi_8k.mp3"); // // 配置Wifi连接线程参数 diff --git a/Bionic_Core/ToolsClass/CppJson/cpp_json.cpp b/Bionic_Core/ToolsClass/CppJson/cpp_json.cpp new file mode 100644 index 0000000..a724225 --- /dev/null +++ b/Bionic_Core/ToolsClass/CppJson/cpp_json.cpp @@ -0,0 +1,4 @@ +// +// Created by misaki on 2025/9/22. +// +#include "cpp_json.h" \ No newline at end of file diff --git a/Bionic_Core/ToolsClass/CppJson/cpp_json.h b/Bionic_Core/ToolsClass/CppJson/cpp_json.h new file mode 100644 index 0000000..940a019 --- /dev/null +++ b/Bionic_Core/ToolsClass/CppJson/cpp_json.h @@ -0,0 +1,207 @@ +// +// Created by misaki on 2025/9/22. +// +/** + * CppJson.h + * 对cJSON的Cpp封装 + * Code By Misaki + * 2025.9.22 + */ + +/** + * 如何使用: + 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'; + + 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"))); + + std::cout << j.dumpPretty() << "\n"; + + Json j2 = j["list"]; + for (auto& v : j2) std::cout << v.dump() << " "; + std::cout << "\n"; + */ + +// cpp_json.hpp +#pragma once +#include +#include +#include +#include +#include +#include + +namespace cppjson { + +//前向声明 + 别名 +class Json; +using Array = std::vector; // 这里只是别名,不会实例化 +using Object = std::map; + + +class Json { +public: + enum Type { Null, Bool, Number, String, ArrayType, ObjectType }; + + /*-------- 构造 ------------------------------------------------*/ + Json() noexcept : ptr_(nullptr), owner_(false) {} + 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) {} + explicit Json(double v) : ptr_(cJSON_CreateNumber(v)), owner_(true) {} + explicit Json(const char* v) : ptr_(cJSON_CreateString(v)), owner_(true) {} + 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 */ + 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 + : ptr_(rhs.ptr_), owner_(rhs.owner_) { rhs.ptr_ = nullptr; rhs.owner_ = false; } + 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"); + return Json(p, true); + } + 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_); + std::string ret(s ? s : "null"); + if (s) free(s); + return ret; + } + [[nodiscard]] std::string dumpPretty() const { + if (!ptr_) return "null"; + char* s = cJSON_Print(ptr_); + std::string ret(s ? s : "null"); + if (s) free(s); + return ret; + } + + /*-------- 类型查询 --------------------------------------------*/ + [[nodiscard]] Type type() const noexcept { + if (!ptr_) return Null; + switch (ptr_->type & 0xFF) { + case cJSON_False: + case cJSON_True: return Bool; + case cJSON_Number:return Number; + case cJSON_String:return String; + case cJSON_Array: return ArrayType; + case cJSON_Object:return ObjectType; + default: return Null; + } + } + [[nodiscard]] bool isNull() const noexcept { return type() == Null; } + [[nodiscard]] bool isBool() const noexcept { return type() == Bool; } + [[nodiscard]] bool isNumber() const noexcept { return type() == Number; } + [[nodiscard]] bool isString() const noexcept { return type() == String; } + [[nodiscard]] bool isArray() const noexcept { return type() == ArrayType; } + [[nodiscard]] bool isObject() const noexcept { return type() == ObjectType; } + + /*-------- 取值 ------------------------------------------------*/ + [[nodiscard]] bool asBool() const { if (!isBool()) throw std::runtime_error("not bool"); return cJSON_IsTrue(ptr_); } + [[nodiscard]] int asInt() const { if (!isNumber()) throw std::runtime_error("not number"); return static_cast(ptr_->valueint); } + [[nodiscard]] double asDouble() const { if (!isNumber()) throw std::runtime_error("not number"); return ptr_->valuedouble; } + [[nodiscard]] std::string asString() const { if (!isString()) throw std::runtime_error("not string"); return ptr_->valuestring; } + + /*-------- 数组/对象接口 ---------------------------------------*/ + [[nodiscard]] size_t size() const { + if (isArray() || isObject()) return cJSON_GetArraySize(ptr_); + return 0; + } + Json operator[](size_t idx) const { + if (!isArray()) throw std::runtime_error("not array"); + cJSON* it = cJSON_GetArrayItem(ptr_, static_cast(idx)); + return it ? Json(it, false) : Json(); + } + Json operator[](const std::string& key) const { + if (!isObject()) throw std::runtime_error("not object"); + cJSON* it = cJSON_GetObjectItem(ptr_, key.c_str()); + return it ? Json(it, false) : Json(); + } + Json& append(const Json& v) { + if (!isArray()) throw std::runtime_error("not array"); + cJSON_AddItemToArray(ptr_, cJSON_Duplicate(v.ptr_, 1)); + return *this; + } + Json& set(const std::string& key, const Json& v) { + if (!isObject()) throw std::runtime_error("not object"); + cJSON_AddItemToObject(ptr_, key.c_str(), cJSON_Duplicate(v.ptr_, 1)); + return *this; + } + + /*-------- 迭代器(只读)---------------------------------------*/ + template + struct iterator_impl { + using iterator_category = std::forward_iterator_tag; + using value_type = Json; + using difference_type = std::ptrdiff_t; + using pointer = typename std::conditional::type*; + using reference = typename std::conditional::type&; + + iterator_impl(cJSON* h, cJSON* c) : head_(h), cur_(c) {} + reference operator*() { + tmp = std::make_unique(cur_, false); + return *tmp; + } + pointer operator->() { return &(operator*()); } + iterator_impl& operator++() { cur_ = cur_ ? cur_->next : nullptr; return *this; } + friend bool operator==(const iterator_impl& a, const iterator_impl& b) { return a.cur_ == b.cur_; } + friend bool operator!=(const iterator_impl& a, const iterator_impl& b) { return !(a == b); } + private: + cJSON *head_, *cur_; + std::unique_ptr tmp; // 改为智能指针 + }; + using iterator = iterator_impl; + using const_iterator = iterator_impl; + iterator begin() { return iterator(ptr_, ptr_ ? ptr_->child : nullptr); } + iterator end() { return iterator(ptr_, nullptr); } + [[nodiscard]] const_iterator begin() const { return cbegin(); } + [[nodiscard]] const_iterator end() const { return cend(); } + [[nodiscard]] const_iterator cbegin() const { return const_iterator(ptr_, ptr_ ? ptr_->child : nullptr); } + [[nodiscard]] const_iterator cend() const { return const_iterator(ptr_, nullptr); } + + /*-------- 工具 ------------------------------------------------*/ + void swap(Json& rhs) noexcept { std::swap(ptr_, rhs.ptr_); std::swap(owner_, rhs.owner_); } + +private: + cJSON* ptr_ = nullptr; + bool owner_ = false; + + void reset() { if (owner_ && ptr_) cJSON_Delete(ptr_); ptr_ = nullptr; owner_ = true; } +}; + + +/*================ 类外实现:Array / Object 构造 =================*/ +inline Json::Json(const Array& arr) + : ptr_(cJSON_CreateArray()), owner_(true) { + for (const auto& v : arr) append(v); +} +inline Json::Json(const Object& obj) + : ptr_(cJSON_CreateObject()), owner_(true) { + for (const auto& kv : obj) set(kv.first, kv.second); +} + +} // namespace cppjson \ No newline at end of file diff --git a/Bionic_Core/ToolsClass/LVGL_Render/LVGLRender.h b/Bionic_Core/ToolsClass/LVGL_Render/LVGLRender.h index db7e724..17bc433 100644 --- a/Bionic_Core/ToolsClass/LVGL_Render/LVGLRender.h +++ b/Bionic_Core/ToolsClass/LVGL_Render/LVGLRender.h @@ -16,6 +16,9 @@ #include class LVGLRender { +public: + LVGLRender(LVGLRender const&) = delete; // 禁止拷贝构造函数 + LVGLRender& operator=(LVGLRender const&) = delete;// 禁止赋值运算符 public: static std::string makeFullPath(const std::string& filename); @@ -46,10 +49,6 @@ private: private: explicit LVGLRender(); // 构造函数私有化 ~LVGLRender(); - - LVGLRender(LVGLRender const&) = delete; // 拷贝构造函数私有化 - LVGLRender& operator=(LVGLRender const&) = delete;// 赋值运算符私有化 - void LVGL_Update(); // 渲染lvgl上下文(持久性线程) private: diff --git a/Bionic_Core/ToolsClass/SpeechRecognizer/SpeechRecognizer.h b/Bionic_Core/ToolsClass/SpeechRecognizer/SpeechRecognizer.h index 5f630f0..1695eb3 100644 --- a/Bionic_Core/ToolsClass/SpeechRecognizer/SpeechRecognizer.h +++ b/Bionic_Core/ToolsClass/SpeechRecognizer/SpeechRecognizer.h @@ -46,8 +46,8 @@ struct SpeechRecognizerConfig { // 模型路径 std::string model_path = "/sdcard/srmodels"; // 线程配置 - ThreadConfig feed_thread_config = {"SR_Feed", 0, 4096, 3, false}; - ThreadConfig detect_thread_config = {"SR_Detect", 1, 6 * 1024, 5, false}; + ThreadConfig feed_thread_config = {"SR_Feed", 1, 4096, 3, false}; + ThreadConfig detect_thread_config = {"SR_Detect", 0, 6 * 1024, 5, false}; // 识别超时时间(ms) int detection_timeout = 6000; }; diff --git a/Bionic_Core/ToolsClass/Sys_Config/sys_conf_singleton.cpp b/Bionic_Core/ToolsClass/Sys_Config/sys_conf_singleton.cpp new file mode 100644 index 0000000..6523485 --- /dev/null +++ b/Bionic_Core/ToolsClass/Sys_Config/sys_conf_singleton.cpp @@ -0,0 +1,5 @@ +// +// Created by misaki on 2025/9/22. +// + +#include "sys_conf_singleton.h" diff --git a/Bionic_Core/ToolsClass/Sys_Config/sys_conf_singleton.h b/Bionic_Core/ToolsClass/Sys_Config/sys_conf_singleton.h new file mode 100644 index 0000000..82c0b68 --- /dev/null +++ b/Bionic_Core/ToolsClass/Sys_Config/sys_conf_singleton.h @@ -0,0 +1,151 @@ +// +// Created by misaki on 2025/9/22. +// +#pragma once +#include "cpp_json.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static constexpr char kSysConfPartName[] = "sys_conf"; + +template +class SysConfJson { +public: + static SysConfJson& instance() { + static SysConfJson inst; + return inst; + } + + /* 把 sn 持久化到 /sn.json,成功返回 true */ + bool saveSN(const std::string& sn) + { + cppjson::Json doc = cppjson::Json::object(); + doc.set("sn", cppjson::Json(sn)); // {"sn":"xxxxxx"} + return write("sn", doc); // 实际文件 = /sn.json + } + + /* 读取 sn,如果文件/字段不存在返回空串(绝不抛异常) */ + std::string loadSN() + { + cppjson::Json j = read("sn"); // 读 /sn.json + if (!j.isObject()) return ""; // 文件不存在或解析失败 + 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; + if (!readRaw(key, content)) return {}; + try { + return cppjson::Json::parse(content); + } catch (...) { + ESP_LOGE(TAG, "parse %s fail", key); + return {}; + } + } + + /* 删文件 */ + bool remove(const char* key) const { + const std::string path = buildPath(key); + return ::unlink(path.c_str()) == 0; + } + + /* 列文件 */ + [[nodiscard]] std::vector ls() const { + std::vector names; + DIR* dir = ::opendir(kMount); + if (!dir) return names; + struct dirent* entry; + while ((entry = ::readdir(dir)) != nullptr) { + if (entry->d_name[0] == '.') continue; + names.emplace_back(entry->d_name); + } + ::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; + } + + /* 禁止拷贝 */ + 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, + .max_files = 8, + .allocation_unit_size = CONFIG_WL_SECTOR_SIZE + }; + esp_err_t err = esp_vfs_fat_spiflash_mount_rw_wl(kMount, PartitionLabel, &cfg, &wl_); + if (err != ESP_OK) { + ESP_LOGE(TAG, "mount fail <%s>", esp_err_to_name(err)); + return; + } + 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; + bool ok = std::fwrite(txt.data(), 1, txt.size(), f) == txt.size(); + 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"); + if (!f) return false; + std::fseek(f, 0, SEEK_END); + size_t sz = std::ftell(f); + std::fseek(f, 0, SEEK_SET); + out.resize(sz); + bool ok = std::fread(&out[0], 1, sz, f) == sz; + std::fclose(f); + return ok; + } + + std::string buildPath(const char* key) const { + return std::string(kMount) + "/" + key; + } +}; + +#define SYS_CONF_JSON() SysConfJson::instance() \ No newline at end of file diff --git a/Bionic_Core/ToolsClass/ThreadManager/ThreadManager.h b/Bionic_Core/ToolsClass/ThreadManager/ThreadManager.h index adfbc8d..eb60047 100644 --- a/Bionic_Core/ToolsClass/ThreadManager/ThreadManager.h +++ b/Bionic_Core/ToolsClass/ThreadManager/ThreadManager.h @@ -142,8 +142,6 @@ public: printf("Internal(内部): %zu kB, SPIRAM(外部): %zu kB\n", internal / 1024, spiram / 1024); } - - static void stats_task() { char stats_buf[2 * 1024]; // 存储任务列表和绑核信息,占用 2KB 栈空间,调用时需注意 diff --git a/Bionic_Core/ToolsClass/ToolsClass.cpp b/Bionic_Core/ToolsClass/ToolsClass.cpp index 2bef9cc..96e745e 100644 --- a/Bionic_Core/ToolsClass/ToolsClass.cpp +++ b/Bionic_Core/ToolsClass/ToolsClass.cpp @@ -1,4 +1,42 @@ // // Created by misaki on 2025/9/2. // +#include "ToolsClass.h" + +#include +#include +#include +#include +#include +#include +#include + +static const char *TAG = "ToolsClass"; + + +std::string ToolsClass::getChipSerialNumber() { + // 获取芯片序列号(17 字节长的 "Chip ID" (乐鑫官方唯一序列号)) + std::array id{}; + /* 下面这个宏在 ESP32-S3 上展开为 17 byte 序列号字段,直接可用 */ + esp_err_t err = esp_efuse_read_field_blob(ESP_EFUSE_OPTIONAL_UNIQUE_ID, id.data(), id.size() * 8); + if (err != ESP_OK) { + ESP_LOGE(TAG, "read Chip ID failed: %s", esp_err_to_name(err)); + return ""; + } + std::stringstream ss; + for (uint8_t v : id) ss << std::hex << std::setw(2) << std::setfill('0') << static_cast(v); + return ss.str(); +} + +std::string ToolsClass::getChipMAC() { + std::array mac{}; + esp_efuse_mac_get_default(mac.data()); // 读出厂 MAC + std::stringstream ss; + for (size_t i = 0; i < mac.size(); ++i) { + ss << std::hex << std::setw(2) << std::setfill('0') << static_cast(mac[i]); + if (i + 1 != mac.size()) ss << ':'; + } + return ss.str(); +} + diff --git a/Bionic_Core/ToolsClass/ToolsClass.h b/Bionic_Core/ToolsClass/ToolsClass.h index b8e535a..0bf3e65 100644 --- a/Bionic_Core/ToolsClass/ToolsClass.h +++ b/Bionic_Core/ToolsClass/ToolsClass.h @@ -2,11 +2,30 @@ // Created by misaki on 2025/9/2. // #pragma once - +#include /** * 本模块提供各种杂项工具类,基本都来源于对底层驱动的封装 * * */ +class ToolsClass { +public: + /** + * 获取当前时间 + * @return + */ + static std::string getCurrentTime(); + /** + * 获取esp32s3的芯片序列号 + * @return + */ + static std::string getChipSerialNumber(); + + /** + * 获取esp32s3的MAC地址 + * @return + */ + static std::string getChipMAC(); +}; diff --git a/Lib/Audio_Driver/PCM5101.c b/Lib/Audio_Driver/PCM5101.c index 545bba4..36d139f 100644 --- a/Lib/Audio_Driver/PCM5101.c +++ b/Lib/Audio_Driver/PCM5101.c @@ -78,7 +78,7 @@ static void audio_player_callback(audio_player_cb_ctx_t *ctx) { void Audio_Init(void) { i2s_std_config_t std_cfg = { - .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(44100), + .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(16000), // 音频采样率,使用16kHz以减少CPU占用 .slot_cfg = I2S_STD_PHILIP_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), .gpio_cfg = BSP_I2S_GPIO_CFG, }; @@ -91,8 +91,8 @@ void Audio_Init(void) .mute_fn = audio_mute_function, .write_fn = bsp_i2s_write, .clk_set_fn = bsp_i2s_reconfig_clk, - .priority = 3, - .coreID = 0 // 运行在0号核,避免与lvgl抢占资源 + .priority = 4, + .coreID = 1 // 运行在0号核,避免与lvgl抢占资源 }; ret = audio_player_new(config); if (ret != ESP_OK) { diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 6223fab..fe12ece 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -41,6 +41,8 @@ idf_component_register(SRCS "Bionic_sphere.c" "../Bionic_Core/ToolsClass/SpeechRecognizer/SpeechRecognizer.cpp" # 语音识别类库 "../Bionic_Core/ToolsClass/WifiConnectors/WifiConnectors.cpp" # WIFI连接类库 "../Bionic_Core/ToolsClass/ThreadManager/ThreadManager.cpp" # 线程管理类库 + "../Bionic_Core/ToolsClass/Sys_Config/sys_conf_singleton.cpp" # 系统配置类库 + "../Bionic_Core/ToolsClass/CppJson/cpp_json.cpp" # json类库 "../Bionic_Core/CppHandle/CppHandle.cpp" # C++&C兼容库 INCLUDE_DIRS "." "../test/driver_test" @@ -77,6 +79,8 @@ idf_component_register(SRCS "Bionic_sphere.c" "../Bionic_Core/ToolsClass/SpeechRecognizer" "../Bionic_Core/ToolsClass/WifiConnectors" "../Bionic_Core/ToolsClass/ThreadManager" + "../Bionic_Core/ToolsClass/Sys_Config" + "../Bionic_Core/ToolsClass/CppJson" "../Bionic_Core/CppHandle" PRIV_REQUIRES # 私有依赖 driver @@ -94,6 +98,7 @@ idf_component_register(SRCS "Bionic_sphere.c" esp_websocket_client esp_https_ota json + efuse ) target_compile_options(${COMPONENT_LIB} PUBLIC -std=gnu++17) diff --git a/partitions.csv b/partitions.csv index fae0cd9..929aaef 100644 --- a/partitions.csv +++ b/partitions.csv @@ -18,4 +18,5 @@ otadata, data, ota, , 0x2000, phy_init, data, phy, , 0x1000, ota_0, app, ota_0, , 6M, ota_1, app, ota_1, , 6M, -coredump, data, coredump,, 64K, \ No newline at end of file +coredump, data, coredump,, 64K, +sys_conf, data, fat, , 0xF9000, \ No newline at end of file diff --git a/sdkconfig b/sdkconfig index d0f392b..fd4348a 100644 --- a/sdkconfig +++ b/sdkconfig @@ -777,7 +777,8 @@ CONFIG_COMPILER_FLOAT_LIB_FROM_GCCLIB=y CONFIG_COMPILER_OPTIMIZATION_ASSERTION_LEVEL=2 # CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT is not set CONFIG_COMPILER_HIDE_PATHS_MACROS=y -# CONFIG_COMPILER_CXX_EXCEPTIONS is not set +CONFIG_COMPILER_CXX_EXCEPTIONS=y +CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=0 CONFIG_COMPILER_CXX_RTTI=y CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y # CONFIG_COMPILER_STACK_CHECK_MODE_NORM is not set @@ -2995,7 +2996,8 @@ CONFIG_OPTIMIZATION_ASSERTIONS_ENABLED=y # CONFIG_OPTIMIZATION_ASSERTIONS_SILENT is not set # CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED is not set CONFIG_OPTIMIZATION_ASSERTION_LEVEL=2 -# CONFIG_CXX_EXCEPTIONS is not set +CONFIG_CXX_EXCEPTIONS=y +CONFIG_CXX_EXCEPTIONS_EMG_POOL_SIZE=0 CONFIG_STACK_CHECK_NONE=y # CONFIG_STACK_CHECK_NORM is not set # CONFIG_STACK_CHECK_STRONG is not set diff --git a/sdkconfig.old b/sdkconfig.old index 3bfea24..d0f392b 100644 --- a/sdkconfig.old +++ b/sdkconfig.old @@ -560,26 +560,26 @@ CONFIG_SR_MN_EN_NONE=y # # Add Chinese speech commands # -CONFIG_CN_SPEECH_COMMAND_ID0="da kai kong tiao" -CONFIG_CN_SPEECH_COMMAND_ID1="guan bi kong tiao" -CONFIG_CN_SPEECH_COMMAND_ID2="zeng da feng su" -CONFIG_CN_SPEECH_COMMAND_ID3="jian xiao feng su" -CONFIG_CN_SPEECH_COMMAND_ID4="sheng gao yi du" -CONFIG_CN_SPEECH_COMMAND_ID5="jiang di yi du" -CONFIG_CN_SPEECH_COMMAND_ID6="zhi re mo shi" -CONFIG_CN_SPEECH_COMMAND_ID7="zhi leng mo shi" -CONFIG_CN_SPEECH_COMMAND_ID8="song feng mo shi" -CONFIG_CN_SPEECH_COMMAND_ID9="jie neng mo shi" -CONFIG_CN_SPEECH_COMMAND_ID10="chu shi mo shi" -CONFIG_CN_SPEECH_COMMAND_ID11="jian kang mo shi" -CONFIG_CN_SPEECH_COMMAND_ID12="shui mian mo shi" -CONFIG_CN_SPEECH_COMMAND_ID13="da kai lan ya" -CONFIG_CN_SPEECH_COMMAND_ID14="guan bi lan ya" -CONFIG_CN_SPEECH_COMMAND_ID15="kai shi bo fang" -CONFIG_CN_SPEECH_COMMAND_ID16="zan ting bo fang" -CONFIG_CN_SPEECH_COMMAND_ID17="ding shi yi xiao shi" -CONFIG_CN_SPEECH_COMMAND_ID18="da kai dian deng" -CONFIG_CN_SPEECH_COMMAND_ID19="guan bi dian deng" +CONFIG_CN_SPEECH_COMMAND_ID0="" +CONFIG_CN_SPEECH_COMMAND_ID1="" +CONFIG_CN_SPEECH_COMMAND_ID2="" +CONFIG_CN_SPEECH_COMMAND_ID3="" +CONFIG_CN_SPEECH_COMMAND_ID4="" +CONFIG_CN_SPEECH_COMMAND_ID5="" +CONFIG_CN_SPEECH_COMMAND_ID6="" +CONFIG_CN_SPEECH_COMMAND_ID7="" +CONFIG_CN_SPEECH_COMMAND_ID8="" +CONFIG_CN_SPEECH_COMMAND_ID9="" +CONFIG_CN_SPEECH_COMMAND_ID10="" +CONFIG_CN_SPEECH_COMMAND_ID11="" +CONFIG_CN_SPEECH_COMMAND_ID12="" +CONFIG_CN_SPEECH_COMMAND_ID13="" +CONFIG_CN_SPEECH_COMMAND_ID14="" +CONFIG_CN_SPEECH_COMMAND_ID15="" +CONFIG_CN_SPEECH_COMMAND_ID16="" +CONFIG_CN_SPEECH_COMMAND_ID17="" +CONFIG_CN_SPEECH_COMMAND_ID18="" +CONFIG_CN_SPEECH_COMMAND_ID19="" CONFIG_CN_SPEECH_COMMAND_ID20="" CONFIG_CN_SPEECH_COMMAND_ID21="" CONFIG_CN_SPEECH_COMMAND_ID22="" diff --git a/项目开发日志.md b/项目开发日志.md index 6eaf6d3..1f48032 100644 --- a/项目开发日志.md +++ b/项目开发日志.md @@ -232,3 +232,17 @@ - [x] 2. 试着测试了一下LVGL_GIF渲染+音乐播放+语音识别的组合简单优化后, 发现lvgl渲染略显卡顿,语音识别有缓冲区空警告,不过无伤大雅,还需要进一步深度优化。 + +#### Day15 2025.9.19(前两天在忙考研复习) +##### 主要目标:完成具体业务开发&各种优化 +实际完成任务: +- [x] 1. 在优化几个主要的任务的CPU占有中,不断的调整任务优先级,效果并不明显,但突然注意到测试的音频为CD级别品质, + 这种音频一般的MP3解码都非常吃力,而且产品也用不到这种级别的音频,因此只是简单的降低音频品质, + 从44100Hz降低到16KHz,测试结果表明,音频播放任务CPU占有从30%降低到5%,在测试中,其余几个任务都及时地上CPU运行了 + +#### Day16 2025.9.23(前两天在忙考研复习) +##### 主要目标:完成具体业务开发&各种优化 +实际完成任务: +- [x] 1. 基于cJSON进行了上层现代C++封装,作为脚手架使用 + +- [x] 2. 在esp32s3剩余的flash当中开辟了一个文件系统,用于保存设备重要配置信息