1. 完成了语音识别的C++业务层封装,测试通过
2. 试着测试了一下LVGL_GIF渲染+音乐播放+语音识别的组合简单优化后,
发现lvgl渲染略显卡顿,语音识别有缓冲区空警告,不过无伤大雅,还需要进一步深度优化。
This commit is contained in:
@@ -9,9 +9,13 @@
|
|||||||
#include <esp_log.h>
|
#include <esp_log.h>
|
||||||
#include <esp_netif_types.h>
|
#include <esp_netif_types.h>
|
||||||
|
|
||||||
|
#include "ToolsClass.h"
|
||||||
|
#include "sys_conf_singleton.h"
|
||||||
|
|
||||||
// 静态成员初始化
|
// 静态成员初始化
|
||||||
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(同时也能初始化文件系统,如果还没有初始化这个文件系统的话)
|
||||||
|
|
||||||
// 标签用于日志
|
// 标签用于日志
|
||||||
static const char* TAG = "WebSocketManager";
|
static const char* TAG = "WebSocketManager";
|
||||||
@@ -51,21 +55,21 @@ WebSocketManager::~WebSocketManager() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
WebSocketManager* WebSocketManager::getInstance() {
|
WebSocketManager* WebSocketManager::getInstance() {
|
||||||
std::lock_guard<std::mutex> lock(instance_mutex);
|
std::lock_guard<std::mutex> lock(instance_mutex); // 加锁
|
||||||
if (instance == nullptr) {
|
if (instance == nullptr) {
|
||||||
instance = new WebSocketManager();
|
instance = new WebSocketManager();
|
||||||
}
|
}
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WebSocketManager::initialize(const WebSocketConfig& config) {
|
bool WebSocketManager::initialize(const WebSocketConfig& ws_config) {
|
||||||
this->config = config;
|
this->config = ws_config;
|
||||||
|
|
||||||
// 注册WiFi事件处理
|
// 注册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,
|
esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
|
||||||
&WebSocketManager::wifiEventHandler,
|
&WebSocketManager::wifiEventHandler,
|
||||||
this, &instance);
|
this, &wifi_instance);
|
||||||
|
|
||||||
// 启动发送线程
|
// 启动发送线程
|
||||||
threads_running = true;
|
threads_running = true;
|
||||||
@@ -159,8 +163,8 @@ bool WebSocketManager::sendJson(cJSON* json) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(queue_mutex); // 锁定队列
|
std::lock_guard<std::mutex> lock(queue_mutex); // 锁定队列
|
||||||
send_queue.push(json_str); // 添加到队列
|
send_queue.emplace(json_str); // 添加到队列
|
||||||
free(json_str); // 释放内存
|
free(json_str); // 释放json_str的内存
|
||||||
|
|
||||||
queue_cv.notify_one(); // 通知发送线程
|
queue_cv.notify_one(); // 通知发送线程
|
||||||
return true;
|
return true;
|
||||||
@@ -178,11 +182,11 @@ bool WebSocketManager::sendRaw(const std::string& data) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebSocketManager::setJsonCallback(JsonDataCallback callback) {
|
void WebSocketManager::setJsonCallback(const JsonDataCallback &callback) {
|
||||||
json_callback = callback;
|
json_callback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebSocketManager::setEventCallback(EventCallback callback) {
|
void WebSocketManager::setEventCallback(const EventCallback &callback) {
|
||||||
event_callback = callback;
|
event_callback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,8 +198,8 @@ WebSocketConfig WebSocketManager::getConfig() const {
|
|||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebSocketManager::updateConfig(const WebSocketConfig& config) {
|
void WebSocketManager::updateConfig(const WebSocketConfig& ws_config) {
|
||||||
this->config = config;
|
this->config = ws_config;
|
||||||
}
|
}
|
||||||
|
|
||||||
WebSocketManager::Stats WebSocketManager::getStats() const {
|
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,
|
void WebSocketManager::websocketEventHandler(void* handler_args, esp_event_base_t base,
|
||||||
int32_t event_id, void* event_data) {
|
int32_t event_id, void* event_data) {
|
||||||
WebSocketManager* instance = static_cast<WebSocketManager*>(handler_args);
|
auto* ws_instance = static_cast<WebSocketManager*>(handler_args);
|
||||||
esp_websocket_event_data_t* data = (esp_websocket_event_data_t*)event_data;
|
auto* data = static_cast<esp_websocket_event_data_t *>(event_data);
|
||||||
|
|
||||||
switch(event_id) {
|
switch(event_id) {
|
||||||
case WEBSOCKET_EVENT_CONNECTED:
|
case WEBSOCKET_EVENT_CONNECTED:
|
||||||
instance->connected = true;
|
ws_instance->connected = true;
|
||||||
instance->connecting = false;
|
ws_instance->connecting = false;
|
||||||
instance->reconnect_attempts = 0;
|
ws_instance->reconnect_attempts = 0;
|
||||||
instance->stats.successful_connections++;
|
ws_instance->stats.successful_connections++;
|
||||||
|
|
||||||
ESP_LOGI(TAG, "WebSocket connected");
|
ESP_LOGI(TAG, "WebSocket connected");
|
||||||
if (instance->event_callback) {
|
if (ws_instance->event_callback) {
|
||||||
instance->event_callback(WebSocketEvent::CONNECTED, "Connected successfully");
|
ws_instance->event_callback(WebSocketEvent::CONNECTED, "Connected successfully");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WEBSOCKET_EVENT_DISCONNECTED:
|
case WEBSOCKET_EVENT_DISCONNECTED:
|
||||||
instance->connected = false;
|
ws_instance->connected = false;
|
||||||
instance->connecting = false;
|
ws_instance->connecting = false;
|
||||||
|
|
||||||
ESP_LOGI(TAG, "WebSocket disconnected");
|
ESP_LOGI(TAG, "WebSocket disconnected");
|
||||||
if (instance->event_callback) {
|
if (ws_instance->event_callback) {
|
||||||
instance->event_callback(WebSocketEvent::DISCONNECTED, "Disconnected");
|
ws_instance->event_callback(WebSocketEvent::DISCONNECTED, "Disconnected");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WEBSOCKET_EVENT_DATA:
|
case WEBSOCKET_EVENT_DATA:
|
||||||
if (data->data_len > 0) {
|
if (data->data_len > 0) {
|
||||||
instance->stats.messages_received++;
|
ws_instance->stats.messages_received++;
|
||||||
instance->handleReceivedData((const char*)data->data_ptr, data->data_len);
|
ws_instance->handleReceivedData((const char*)data->data_ptr, data->data_len);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WEBSOCKET_EVENT_ERROR:
|
case WEBSOCKET_EVENT_ERROR:
|
||||||
instance->connected = false;
|
ws_instance->connected = false;
|
||||||
instance->connecting = false;
|
ws_instance->connecting = false;
|
||||||
|
|
||||||
ESP_LOGE(TAG, "WebSocket error");
|
ESP_LOGE(TAG, "WebSocket error");
|
||||||
if (instance->event_callback) {
|
if (ws_instance->event_callback) {
|
||||||
instance->event_callback(WebSocketEvent::ERROR, "WebSocket error occurred");
|
ws_instance->event_callback(WebSocketEvent::ERROR, "WebSocket error occurred");
|
||||||
}
|
}
|
||||||
break;
|
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
|
// 尝试解析JSON
|
||||||
cJSON* json = cJSON_ParseWithLength(data, len);
|
if (cJSON* json = cJSON_ParseWithLength(data, len)) {
|
||||||
if (json) {
|
|
||||||
// 成功解析为JSON
|
// 成功解析为JSON
|
||||||
if (json_callback) {
|
if (json_callback) {
|
||||||
json_callback(json);
|
json_callback(json);
|
||||||
@@ -298,16 +304,16 @@ void WebSocketManager::reconnectThread() {
|
|||||||
|
|
||||||
void WebSocketManager::heartbeatThread() {
|
void WebSocketManager::heartbeatThread() {
|
||||||
while (threads_running) {
|
while (threads_running) {
|
||||||
if (connected && config.heartbeat_interval > 0) {
|
if (connected && config.heartbeat_interval > 0) { // 如果处于连接状态且心跳间隔大于0
|
||||||
// 发送心跳消息
|
// 发送心跳消息
|
||||||
cJSON* heartbeat = cJSON_CreateObject();
|
cJSON* heartbeat = cJSON_CreateObject();
|
||||||
cJSON_AddStringToObject(heartbeat, "type", "heartbeat");
|
cJSON_AddStringToObject(heartbeat, "type", "heartbeat");
|
||||||
cJSON_AddNumberToObject(heartbeat, "timestamp", esp_log_timestamp());
|
cJSON_AddNumberToObject(heartbeat, "timestamp", static_cast<double>(esp_log_timestamp()));
|
||||||
|
|
||||||
sendJson(heartbeat);
|
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 {
|
} else { // 否则就让出CPU
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -316,23 +322,24 @@ void WebSocketManager::heartbeatThread() {
|
|||||||
void WebSocketManager::sendThread() {
|
void WebSocketManager::sendThread() {
|
||||||
while (threads_running) {
|
while (threads_running) {
|
||||||
std::unique_lock<std::mutex> lock(queue_mutex);
|
std::unique_lock<std::mutex> lock(queue_mutex);
|
||||||
queue_cv.wait(lock, [this]() {
|
queue_cv.wait(lock, // 传入已持有的 unique_lock
|
||||||
|
[this]() { // 等待队列非空或者线程停止
|
||||||
return !send_queue.empty() || !threads_running;
|
return !send_queue.empty() || !threads_running;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!threads_running) {
|
if (!threads_running) { // 其实永远不会进入这里,写上只是保险起见
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!send_queue.empty() && connected) {
|
if (!send_queue.empty() && connected) { // 如果队列非空且已连接
|
||||||
std::string data = std::move(send_queue.front());
|
std::string data = std::move(send_queue.front());
|
||||||
send_queue.pop();
|
send_queue.pop();
|
||||||
lock.unlock();
|
lock.unlock(); // 释放锁
|
||||||
|
|
||||||
// 发送数据
|
// 发送数据
|
||||||
int sent = esp_websocket_client_send_text(client, data.c_str(), static_cast<int>(data.length()), portMAX_DELAY);
|
int sent = esp_websocket_client_send_text(client, data.c_str(), static_cast<int>(data.length()), portMAX_DELAY);
|
||||||
if (sent >= 0) {
|
if (sent >= 0) {
|
||||||
stats.messages_sent++;
|
stats.messages_sent++; // 发送成功
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGE(TAG, "Failed to send data");
|
ESP_LOGE(TAG, "Failed to send data");
|
||||||
// 将数据重新放回队列
|
// 将数据重新放回队列
|
||||||
@@ -355,6 +362,19 @@ bool WebSocketManager::createWebSocketClient() {
|
|||||||
return false;
|
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,
|
esp_websocket_register_events(client, WEBSOCKET_EVENT_ANY,
|
||||||
&WebSocketManager::websocketEventHandler, this);
|
&WebSocketManager::websocketEventHandler, this);
|
||||||
@@ -371,14 +391,14 @@ void WebSocketManager::destroyWebSocketClient() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WebSocketManager::wifiEventHandler(void* arg, esp_event_base_t event_base,
|
void WebSocketManager::wifiEventHandler(void* arg, esp_event_base_t event_base,
|
||||||
int32_t event_id, void* event_data) {
|
int32_t event_Id, void* event_data) {
|
||||||
WebSocketManager* instance = static_cast<WebSocketManager*>(arg);
|
auto* ws_instance = static_cast<WebSocketManager*>(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
|
// 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");
|
ESP_LOGI(TAG, "WiFi connected, attempting to reconnect WebSocket");
|
||||||
instance->connect();
|
ws_instance->connect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
@@ -37,6 +36,11 @@ using JsonDataCallback = std::function<void(cJSON* json)>;
|
|||||||
// 事件回调
|
// 事件回调
|
||||||
using EventCallback = std::function<void(WebSocketEvent event, const std::string& message)>;
|
using EventCallback = std::function<void(WebSocketEvent event, const std::string& message)>;
|
||||||
|
|
||||||
|
// 在此说明一下上面的两个回调函数的作用,主要目的还是为了解耦,在handleReceivedData有对这两个回调的调用
|
||||||
|
// 对于JSON数据回调,如果来自客户端的数据被解析为了数据,那么就会调用JSON数据回调,并将解析后的json数据传给JSON数据回调,回调函数内部只需要访问并释放json数据
|
||||||
|
// 对于WebSocket事件回调,会有所不同,重点需要关注DATA_RECEIVED事件,这是非json数据的情况,其他的见下面的示例
|
||||||
|
// 实际上对于本项目,几乎99%的情况都是只使用JSON数据回调
|
||||||
|
|
||||||
/* 回调函数示例:
|
/* 回调函数示例:
|
||||||
// JSON数据回调示例
|
// JSON数据回调示例
|
||||||
void onJsonData(cJSON* json) {
|
void onJsonData(cJSON* json) {
|
||||||
@@ -86,7 +90,7 @@ public:
|
|||||||
WebSocketManager& operator=(const WebSocketManager&) = delete;
|
WebSocketManager& operator=(const WebSocketManager&) = delete;
|
||||||
|
|
||||||
// 初始化WebSocket管理器
|
// 初始化WebSocket管理器
|
||||||
bool initialize(const WebSocketConfig& config);
|
bool initialize(const WebSocketConfig& ws_config);
|
||||||
|
|
||||||
// 连接到WebSocket服务器
|
// 连接到WebSocket服务器
|
||||||
bool connect();
|
bool connect();
|
||||||
@@ -94,26 +98,26 @@ public:
|
|||||||
// 断开连接
|
// 断开连接
|
||||||
void disconnect();
|
void disconnect();
|
||||||
|
|
||||||
// 发送JSON数据
|
// 发送JSON数据,注意此成员函数会释放json数据,调用之后请不要再次释放json数据
|
||||||
bool sendJson(cJSON* json);
|
bool sendJson(cJSON* json);
|
||||||
|
|
||||||
// 发送原始字符串数据
|
// 发送原始字符串数据
|
||||||
bool sendRaw(const std::string& data);
|
bool sendRaw(const std::string& data);
|
||||||
|
|
||||||
// 设置JSON数据回调
|
// 设置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 {
|
struct Stats {
|
||||||
@@ -122,7 +126,7 @@ public:
|
|||||||
uint32_t connection_attempts; /// 连接尝试次数
|
uint32_t connection_attempts; /// 连接尝试次数
|
||||||
uint32_t successful_connections; /// 成功连接次数
|
uint32_t successful_connections; /// 成功连接次数
|
||||||
};
|
};
|
||||||
Stats getStats() const;
|
[[nodiscard]] Stats getStats() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
WebSocketManager(); // 私有构造函数
|
WebSocketManager(); // 私有构造函数
|
||||||
@@ -143,7 +147,7 @@ private:
|
|||||||
* @param data 数据
|
* @param data 数据
|
||||||
* @param len 数据长度
|
* @param len 数据长度
|
||||||
*/
|
*/
|
||||||
void handleReceivedData(const char* data, int len);
|
void handleReceivedData(const char* data, int len) const;
|
||||||
|
|
||||||
// 重连线程函数
|
// 重连线程函数
|
||||||
void reconnectThread();
|
void reconnectThread();
|
||||||
@@ -196,4 +200,14 @@ private:
|
|||||||
|
|
||||||
// WiFi实例
|
// WiFi实例
|
||||||
WifiConnectors* 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
|
||||||
};
|
};
|
||||||
@@ -203,7 +203,7 @@ void testMIC() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Cpp_Hand() {
|
void Cpp_Hand() {
|
||||||
testMIC();
|
// testMIC();
|
||||||
// testPetSystem();
|
// testPetSystem();
|
||||||
|
|
||||||
OTAClass oc;
|
OTAClass oc;
|
||||||
|
|||||||
@@ -145,12 +145,28 @@ void websocket_task() {
|
|||||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
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", "当前固件版本 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());
|
||||||
@@ -164,13 +180,13 @@ void OTAClass::Init() {
|
|||||||
listing = SDFileManager::getInstance()->lsCommand(".", false, true);
|
listing = SDFileManager::getInstance()->lsCommand(".", false, true);
|
||||||
ESP_LOGI("SD", "%s", listing.c_str());
|
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连接线程参数
|
// // 配置Wifi连接线程参数
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
//
|
||||||
|
// Created by misaki on 2025/9/22.
|
||||||
|
//
|
||||||
|
#include "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 <cJSON.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace cppjson {
|
||||||
|
|
||||||
|
//前向声明 + 别名
|
||||||
|
class Json;
|
||||||
|
using Array = std::vector<Json>; // 这里只是别名,不会实例化
|
||||||
|
using Object = std::map<std::string, Json>;
|
||||||
|
|
||||||
|
|
||||||
|
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<int>(ptr_->valueint); }
|
||||||
|
[[nodiscard]] double asDouble() const { if (!isNumber()) throw std::runtime_error("not number"); return ptr_->valuedouble; }
|
||||||
|
[[nodiscard]] std::string asString() const { if (!isString()) throw std::runtime_error("not string"); return ptr_->valuestring; }
|
||||||
|
|
||||||
|
/*-------- 数组/对象接口 ---------------------------------------*/
|
||||||
|
[[nodiscard]] size_t size() const {
|
||||||
|
if (isArray() || isObject()) return cJSON_GetArraySize(ptr_);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
Json operator[](size_t idx) const {
|
||||||
|
if (!isArray()) throw std::runtime_error("not array");
|
||||||
|
cJSON* it = cJSON_GetArrayItem(ptr_, static_cast<int>(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 <bool IsConst>
|
||||||
|
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<IsConst, const Json, Json>::type*;
|
||||||
|
using reference = typename std::conditional<IsConst, const Json, Json>::type&;
|
||||||
|
|
||||||
|
iterator_impl(cJSON* h, cJSON* c) : head_(h), cur_(c) {}
|
||||||
|
reference operator*() {
|
||||||
|
tmp = std::make_unique<Json>(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<Json> tmp; // 改为智能指针
|
||||||
|
};
|
||||||
|
using iterator = iterator_impl<false>;
|
||||||
|
using const_iterator = iterator_impl<true>;
|
||||||
|
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
|
||||||
@@ -16,6 +16,9 @@
|
|||||||
#include <core/lv_obj.h>
|
#include <core/lv_obj.h>
|
||||||
|
|
||||||
class LVGLRender {
|
class LVGLRender {
|
||||||
|
public:
|
||||||
|
LVGLRender(LVGLRender const&) = delete; // 禁止拷贝构造函数
|
||||||
|
LVGLRender& operator=(LVGLRender const&) = delete;// 禁止赋值运算符
|
||||||
public:
|
public:
|
||||||
static std::string makeFullPath(const std::string& filename);
|
static std::string makeFullPath(const std::string& filename);
|
||||||
|
|
||||||
@@ -46,10 +49,6 @@ private:
|
|||||||
private:
|
private:
|
||||||
explicit LVGLRender(); // 构造函数私有化
|
explicit LVGLRender(); // 构造函数私有化
|
||||||
~LVGLRender();
|
~LVGLRender();
|
||||||
|
|
||||||
LVGLRender(LVGLRender const&) = delete; // 拷贝构造函数私有化
|
|
||||||
LVGLRender& operator=(LVGLRender const&) = delete;// 赋值运算符私有化
|
|
||||||
|
|
||||||
void LVGL_Update(); // 渲染lvgl上下文(持久性线程)
|
void LVGL_Update(); // 渲染lvgl上下文(持久性线程)
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ struct SpeechRecognizerConfig {
|
|||||||
// 模型路径
|
// 模型路径
|
||||||
std::string model_path = "/sdcard/srmodels";
|
std::string model_path = "/sdcard/srmodels";
|
||||||
// 线程配置
|
// 线程配置
|
||||||
ThreadConfig feed_thread_config = {"SR_Feed", 0, 4096, 3, false};
|
ThreadConfig feed_thread_config = {"SR_Feed", 1, 4096, 3, false};
|
||||||
ThreadConfig detect_thread_config = {"SR_Detect", 1, 6 * 1024, 5, false};
|
ThreadConfig detect_thread_config = {"SR_Detect", 0, 6 * 1024, 5, false};
|
||||||
// 识别超时时间(ms)
|
// 识别超时时间(ms)
|
||||||
int detection_timeout = 6000;
|
int detection_timeout = 6000;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
//
|
||||||
|
// Created by misaki on 2025/9/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "sys_conf_singleton.h"
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
//
|
||||||
|
// Created by misaki on 2025/9/22.
|
||||||
|
//
|
||||||
|
#pragma once
|
||||||
|
#include "cpp_json.h"
|
||||||
|
#include <esp_vfs_fat.h>
|
||||||
|
#include <wear_levelling.h>
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <fstream>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
static constexpr char kSysConfPartName[] = "sys_conf";
|
||||||
|
|
||||||
|
template <const char* PartitionLabel>
|
||||||
|
class SysConfJson {
|
||||||
|
public:
|
||||||
|
static SysConfJson& instance() {
|
||||||
|
static SysConfJson inst;
|
||||||
|
return inst;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 把 sn 持久化到 <mount>/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); // 实际文件 = <mount>/sn.json
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 读取 sn,如果文件/字段不存在返回空串(绝不抛异常) */
|
||||||
|
std::string loadSN()
|
||||||
|
{
|
||||||
|
cppjson::Json j = read("sn"); // 读 <mount>/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<std::string> ls() const {
|
||||||
|
std::vector<std::string> 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<kSysConfPartName>::instance()
|
||||||
@@ -142,8 +142,6 @@ public:
|
|||||||
printf("Internal(内部): %zu kB, SPIRAM(外部): %zu kB\n", internal / 1024, spiram / 1024);
|
printf("Internal(内部): %zu kB, SPIRAM(外部): %zu kB\n", internal / 1024, spiram / 1024);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static void stats_task()
|
static void stats_task()
|
||||||
{
|
{
|
||||||
char stats_buf[2 * 1024]; // 存储任务列表和绑核信息,占用 2KB 栈空间,调用时需注意
|
char stats_buf[2 * 1024]; // 存储任务列表和绑核信息,占用 2KB 栈空间,调用时需注意
|
||||||
|
|||||||
@@ -1,4 +1,42 @@
|
|||||||
//
|
//
|
||||||
// Created by misaki on 2025/9/2.
|
// Created by misaki on 2025/9/2.
|
||||||
//
|
//
|
||||||
|
#include "ToolsClass.h"
|
||||||
|
|
||||||
|
#include <esp_mac.h>
|
||||||
|
#include <esp_efuse.h>
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include <array>
|
||||||
|
#include <esp_efuse_table.h>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
|
static const char *TAG = "ToolsClass";
|
||||||
|
|
||||||
|
|
||||||
|
std::string ToolsClass::getChipSerialNumber() {
|
||||||
|
// 获取芯片序列号(17 字节长的 "Chip ID" (乐鑫官方唯一序列号))
|
||||||
|
std::array<uint8_t, 17> 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<int>(v);
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ToolsClass::getChipMAC() {
|
||||||
|
std::array<uint8_t, 6> 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<int>(mac[i]);
|
||||||
|
if (i + 1 != mac.size()) ss << ':';
|
||||||
|
}
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,30 @@
|
|||||||
// Created by misaki on 2025/9/2.
|
// Created by misaki on 2025/9/2.
|
||||||
//
|
//
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include <string>
|
||||||
/**
|
/**
|
||||||
* 本模块提供各种杂项工具类,基本都来源于对底层驱动的封装
|
* 本模块提供各种杂项工具类,基本都来源于对底层驱动的封装
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
class ToolsClass {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* 获取当前时间
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
static std::string getCurrentTime();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取esp32s3的芯片序列号
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
static std::string getChipSerialNumber();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取esp32s3的MAC地址
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
static std::string getChipMAC();
|
||||||
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ static void audio_player_callback(audio_player_cb_ctx_t *ctx) {
|
|||||||
void Audio_Init(void)
|
void Audio_Init(void)
|
||||||
{
|
{
|
||||||
i2s_std_config_t std_cfg = {
|
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),
|
.slot_cfg = I2S_STD_PHILIP_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),
|
||||||
.gpio_cfg = BSP_I2S_GPIO_CFG,
|
.gpio_cfg = BSP_I2S_GPIO_CFG,
|
||||||
};
|
};
|
||||||
@@ -91,8 +91,8 @@ void Audio_Init(void)
|
|||||||
.mute_fn = audio_mute_function,
|
.mute_fn = audio_mute_function,
|
||||||
.write_fn = bsp_i2s_write,
|
.write_fn = bsp_i2s_write,
|
||||||
.clk_set_fn = bsp_i2s_reconfig_clk,
|
.clk_set_fn = bsp_i2s_reconfig_clk,
|
||||||
.priority = 3,
|
.priority = 4,
|
||||||
.coreID = 0 // 运行在0号核,避免与lvgl抢占资源
|
.coreID = 1 // 运行在0号核,避免与lvgl抢占资源
|
||||||
};
|
};
|
||||||
ret = audio_player_new(config);
|
ret = audio_player_new(config);
|
||||||
if (ret != ESP_OK) {
|
if (ret != ESP_OK) {
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ idf_component_register(SRCS "Bionic_sphere.c"
|
|||||||
"../Bionic_Core/ToolsClass/SpeechRecognizer/SpeechRecognizer.cpp" # 语音识别类库
|
"../Bionic_Core/ToolsClass/SpeechRecognizer/SpeechRecognizer.cpp" # 语音识别类库
|
||||||
"../Bionic_Core/ToolsClass/WifiConnectors/WifiConnectors.cpp" # WIFI连接类库
|
"../Bionic_Core/ToolsClass/WifiConnectors/WifiConnectors.cpp" # WIFI连接类库
|
||||||
"../Bionic_Core/ToolsClass/ThreadManager/ThreadManager.cpp" # 线程管理类库
|
"../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兼容库
|
"../Bionic_Core/CppHandle/CppHandle.cpp" # C++&C兼容库
|
||||||
INCLUDE_DIRS "."
|
INCLUDE_DIRS "."
|
||||||
"../test/driver_test"
|
"../test/driver_test"
|
||||||
@@ -77,6 +79,8 @@ idf_component_register(SRCS "Bionic_sphere.c"
|
|||||||
"../Bionic_Core/ToolsClass/SpeechRecognizer"
|
"../Bionic_Core/ToolsClass/SpeechRecognizer"
|
||||||
"../Bionic_Core/ToolsClass/WifiConnectors"
|
"../Bionic_Core/ToolsClass/WifiConnectors"
|
||||||
"../Bionic_Core/ToolsClass/ThreadManager"
|
"../Bionic_Core/ToolsClass/ThreadManager"
|
||||||
|
"../Bionic_Core/ToolsClass/Sys_Config"
|
||||||
|
"../Bionic_Core/ToolsClass/CppJson"
|
||||||
"../Bionic_Core/CppHandle"
|
"../Bionic_Core/CppHandle"
|
||||||
PRIV_REQUIRES # 私有依赖
|
PRIV_REQUIRES # 私有依赖
|
||||||
driver
|
driver
|
||||||
@@ -94,6 +98,7 @@ idf_component_register(SRCS "Bionic_sphere.c"
|
|||||||
esp_websocket_client
|
esp_websocket_client
|
||||||
esp_https_ota
|
esp_https_ota
|
||||||
json
|
json
|
||||||
|
efuse
|
||||||
)
|
)
|
||||||
|
|
||||||
target_compile_options(${COMPONENT_LIB} PUBLIC -std=gnu++17)
|
target_compile_options(${COMPONENT_LIB} PUBLIC -std=gnu++17)
|
||||||
|
|||||||
+2
-1
@@ -18,4 +18,5 @@ otadata, data, ota, , 0x2000,
|
|||||||
phy_init, data, phy, , 0x1000,
|
phy_init, data, phy, , 0x1000,
|
||||||
ota_0, app, ota_0, , 6M,
|
ota_0, app, ota_0, , 6M,
|
||||||
ota_1, app, ota_1, , 6M,
|
ota_1, app, ota_1, , 6M,
|
||||||
coredump, data, coredump,, 64K,
|
coredump, data, coredump,, 64K,
|
||||||
|
sys_conf, data, fat, , 0xF9000,
|
||||||
|
@@ -777,7 +777,8 @@ CONFIG_COMPILER_FLOAT_LIB_FROM_GCCLIB=y
|
|||||||
CONFIG_COMPILER_OPTIMIZATION_ASSERTION_LEVEL=2
|
CONFIG_COMPILER_OPTIMIZATION_ASSERTION_LEVEL=2
|
||||||
# CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT is not set
|
# CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT is not set
|
||||||
CONFIG_COMPILER_HIDE_PATHS_MACROS=y
|
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_CXX_RTTI=y
|
||||||
CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y
|
CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y
|
||||||
# CONFIG_COMPILER_STACK_CHECK_MODE_NORM is not set
|
# 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_SILENT is not set
|
||||||
# CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED is not set
|
# CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED is not set
|
||||||
CONFIG_OPTIMIZATION_ASSERTION_LEVEL=2
|
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_NONE=y
|
||||||
# CONFIG_STACK_CHECK_NORM is not set
|
# CONFIG_STACK_CHECK_NORM is not set
|
||||||
# CONFIG_STACK_CHECK_STRONG is not set
|
# CONFIG_STACK_CHECK_STRONG is not set
|
||||||
|
|||||||
+20
-20
@@ -560,26 +560,26 @@ CONFIG_SR_MN_EN_NONE=y
|
|||||||
#
|
#
|
||||||
# Add Chinese speech commands
|
# Add Chinese speech commands
|
||||||
#
|
#
|
||||||
CONFIG_CN_SPEECH_COMMAND_ID0="da kai kong tiao"
|
CONFIG_CN_SPEECH_COMMAND_ID0=""
|
||||||
CONFIG_CN_SPEECH_COMMAND_ID1="guan bi kong tiao"
|
CONFIG_CN_SPEECH_COMMAND_ID1=""
|
||||||
CONFIG_CN_SPEECH_COMMAND_ID2="zeng da feng su"
|
CONFIG_CN_SPEECH_COMMAND_ID2=""
|
||||||
CONFIG_CN_SPEECH_COMMAND_ID3="jian xiao feng su"
|
CONFIG_CN_SPEECH_COMMAND_ID3=""
|
||||||
CONFIG_CN_SPEECH_COMMAND_ID4="sheng gao yi du"
|
CONFIG_CN_SPEECH_COMMAND_ID4=""
|
||||||
CONFIG_CN_SPEECH_COMMAND_ID5="jiang di yi du"
|
CONFIG_CN_SPEECH_COMMAND_ID5=""
|
||||||
CONFIG_CN_SPEECH_COMMAND_ID6="zhi re mo shi"
|
CONFIG_CN_SPEECH_COMMAND_ID6=""
|
||||||
CONFIG_CN_SPEECH_COMMAND_ID7="zhi leng mo shi"
|
CONFIG_CN_SPEECH_COMMAND_ID7=""
|
||||||
CONFIG_CN_SPEECH_COMMAND_ID8="song feng mo shi"
|
CONFIG_CN_SPEECH_COMMAND_ID8=""
|
||||||
CONFIG_CN_SPEECH_COMMAND_ID9="jie neng mo shi"
|
CONFIG_CN_SPEECH_COMMAND_ID9=""
|
||||||
CONFIG_CN_SPEECH_COMMAND_ID10="chu shi mo shi"
|
CONFIG_CN_SPEECH_COMMAND_ID10=""
|
||||||
CONFIG_CN_SPEECH_COMMAND_ID11="jian kang mo shi"
|
CONFIG_CN_SPEECH_COMMAND_ID11=""
|
||||||
CONFIG_CN_SPEECH_COMMAND_ID12="shui mian mo shi"
|
CONFIG_CN_SPEECH_COMMAND_ID12=""
|
||||||
CONFIG_CN_SPEECH_COMMAND_ID13="da kai lan ya"
|
CONFIG_CN_SPEECH_COMMAND_ID13=""
|
||||||
CONFIG_CN_SPEECH_COMMAND_ID14="guan bi lan ya"
|
CONFIG_CN_SPEECH_COMMAND_ID14=""
|
||||||
CONFIG_CN_SPEECH_COMMAND_ID15="kai shi bo fang"
|
CONFIG_CN_SPEECH_COMMAND_ID15=""
|
||||||
CONFIG_CN_SPEECH_COMMAND_ID16="zan ting bo fang"
|
CONFIG_CN_SPEECH_COMMAND_ID16=""
|
||||||
CONFIG_CN_SPEECH_COMMAND_ID17="ding shi yi xiao shi"
|
CONFIG_CN_SPEECH_COMMAND_ID17=""
|
||||||
CONFIG_CN_SPEECH_COMMAND_ID18="da kai dian deng"
|
CONFIG_CN_SPEECH_COMMAND_ID18=""
|
||||||
CONFIG_CN_SPEECH_COMMAND_ID19="guan bi dian deng"
|
CONFIG_CN_SPEECH_COMMAND_ID19=""
|
||||||
CONFIG_CN_SPEECH_COMMAND_ID20=""
|
CONFIG_CN_SPEECH_COMMAND_ID20=""
|
||||||
CONFIG_CN_SPEECH_COMMAND_ID21=""
|
CONFIG_CN_SPEECH_COMMAND_ID21=""
|
||||||
CONFIG_CN_SPEECH_COMMAND_ID22=""
|
CONFIG_CN_SPEECH_COMMAND_ID22=""
|
||||||
|
|||||||
@@ -232,3 +232,17 @@
|
|||||||
|
|
||||||
- [x] 2. 试着测试了一下LVGL_GIF渲染+音乐播放+语音识别的组合简单优化后,
|
- [x] 2. 试着测试了一下LVGL_GIF渲染+音乐播放+语音识别的组合简单优化后,
|
||||||
发现lvgl渲染略显卡顿,语音识别有缓冲区空警告,不过无伤大雅,还需要进一步深度优化。
|
发现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当中开辟了一个文件系统,用于保存设备重要配置信息
|
||||||
|
|||||||
Reference in New Issue
Block a user