1. 完成了对音频播放类的完整C++封装,测试通过
2. 修复了LVGL渲染类当中的一些小bug 3. 增加了一些CPU资源占用的日志打印函数,运行在主线程当中 4. 完善了底层通信类的封装,基于websocket,尚未测试
This commit is contained in:
@@ -1,4 +1,386 @@
|
||||
//
|
||||
// Created by misaki on 2025/9/2.
|
||||
//
|
||||
#include "CommClass.h"
|
||||
#include "CommClass.h"
|
||||
#include <cstring> // 引入字符串处理库头文件
|
||||
#include <chrono> // 时间库头文件
|
||||
#include <memory>
|
||||
#include <esp_event.h>
|
||||
#include <esp_log.h>
|
||||
#include <esp_netif_types.h>
|
||||
|
||||
// 静态成员初始化
|
||||
WebSocketManager* WebSocketManager::instance = nullptr;
|
||||
std::mutex WebSocketManager::instance_mutex;
|
||||
|
||||
// 标签用于日志
|
||||
static const char* TAG = "WebSocketManager";
|
||||
|
||||
|
||||
// 构造函数
|
||||
WebSocketManager::WebSocketManager()
|
||||
: client(nullptr), threads_running(false),
|
||||
connected(false), connecting(false), reconnect_attempts(0),
|
||||
stats{0, 0, 0, 0},
|
||||
wifi(WifiConnectors::getInstance())
|
||||
|
||||
{
|
||||
|
||||
// 初始化统计信息
|
||||
stats = {0, 0, 0, 0};
|
||||
}
|
||||
|
||||
|
||||
// 析构函数
|
||||
WebSocketManager::~WebSocketManager() {
|
||||
disconnect();
|
||||
|
||||
// 停止线程
|
||||
threads_running = false;
|
||||
queue_cv.notify_all();
|
||||
|
||||
if (reconnect_thread.joinable()) {
|
||||
reconnect_thread.join();
|
||||
}
|
||||
|
||||
if (heartbeat_thread.joinable()) {
|
||||
heartbeat_thread.join();
|
||||
}
|
||||
|
||||
if (send_thread.joinable()) {
|
||||
send_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
WebSocketManager* WebSocketManager::getInstance() {
|
||||
std::lock_guard<std::mutex> lock(instance_mutex);
|
||||
if (instance == nullptr) {
|
||||
instance = new WebSocketManager();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool WebSocketManager::initialize(const WebSocketConfig& config) {
|
||||
this->config = config;
|
||||
|
||||
// 注册WiFi事件处理
|
||||
esp_event_handler_instance_t instance;
|
||||
esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
|
||||
&WebSocketManager::wifiEventHandler,
|
||||
this, &instance);
|
||||
|
||||
// 启动发送线程
|
||||
threads_running = true;
|
||||
|
||||
ThreadConfig thread_config;
|
||||
thread_config.name = "WS_Send";
|
||||
thread_config.stack_size = 4096;
|
||||
|
||||
send_thread = ThreadManager::createMemberThread(thread_config, this,
|
||||
&WebSocketManager::sendThread);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WebSocketManager::connect() {
|
||||
if (connecting || connected) {
|
||||
ESP_LOGI(TAG, "Already connected or connecting");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查WiFi连接
|
||||
if (!wifi->isWifiConnect()) {
|
||||
ESP_LOGE(TAG, "WiFi not connected, cannot establish WebSocket connection");
|
||||
if (event_callback) {
|
||||
event_callback(WebSocketEvent::ERROR, "WiFi not connected");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
connecting = true;
|
||||
stats.connection_attempts++;
|
||||
|
||||
// 创建WebSocket客户端
|
||||
if (!createWebSocketClient()) {
|
||||
connecting = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 启动WebSocket客户端
|
||||
esp_err_t err = esp_websocket_client_start(client);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to start WebSocket client: %d", err);
|
||||
destroyWebSocketClient();
|
||||
connecting = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 启动重连和心跳线程
|
||||
ThreadConfig thread_config;
|
||||
thread_config.name = "WS_Reconnect";
|
||||
thread_config.stack_size = 3072;
|
||||
|
||||
reconnect_thread = ThreadManager::createMemberThread(thread_config, this,
|
||||
&WebSocketManager::reconnectThread);
|
||||
|
||||
thread_config.name = "WS_Heartbeat";
|
||||
heartbeat_thread = ThreadManager::createMemberThread(thread_config, this,
|
||||
&WebSocketManager::heartbeatThread);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WebSocketManager::disconnect() {
|
||||
if (client) {
|
||||
esp_websocket_client_stop(client);
|
||||
destroyWebSocketClient();
|
||||
}
|
||||
|
||||
connected = false;
|
||||
connecting = false;
|
||||
|
||||
// 通知事件回调
|
||||
if (event_callback) {
|
||||
event_callback(WebSocketEvent::DISCONNECTED, "Disconnected by user");
|
||||
}
|
||||
}
|
||||
|
||||
bool WebSocketManager::sendJson(cJSON* json) {
|
||||
if (!connected) { // 检查连接状态
|
||||
ESP_LOGE(TAG, "Not connected, cannot send data");
|
||||
cJSON_Delete(json);
|
||||
return false;
|
||||
}
|
||||
|
||||
char* json_str = cJSON_PrintUnformatted(json); // 将JSON对象转换为字符串
|
||||
cJSON_Delete(json); // 释放JSON对象
|
||||
|
||||
if (!json_str) { // 检查转换结果
|
||||
ESP_LOGE(TAG, "Failed to stringify JSON");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(queue_mutex); // 锁定队列
|
||||
send_queue.push(json_str); // 添加到队列
|
||||
free(json_str); // 释放内存
|
||||
|
||||
queue_cv.notify_one(); // 通知发送线程
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WebSocketManager::sendRaw(const std::string& data) {
|
||||
if (!connected) { // 检查连接状态
|
||||
ESP_LOGE(TAG, "Not connected, cannot send data");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(queue_mutex); // 锁定队列
|
||||
send_queue.push(data); // 添加到队列
|
||||
queue_cv.notify_one(); // 通知发送线程
|
||||
return true;
|
||||
}
|
||||
|
||||
void WebSocketManager::setJsonCallback(JsonDataCallback callback) {
|
||||
json_callback = callback;
|
||||
}
|
||||
|
||||
void WebSocketManager::setEventCallback(EventCallback callback) {
|
||||
event_callback = callback;
|
||||
}
|
||||
|
||||
bool WebSocketManager::isConnected() const {
|
||||
return connected;
|
||||
}
|
||||
|
||||
WebSocketConfig WebSocketManager::getConfig() const {
|
||||
return config;
|
||||
}
|
||||
|
||||
void WebSocketManager::updateConfig(const WebSocketConfig& config) {
|
||||
this->config = config;
|
||||
}
|
||||
|
||||
WebSocketManager::Stats WebSocketManager::getStats() const {
|
||||
return stats;
|
||||
}
|
||||
|
||||
void WebSocketManager::websocketEventHandler(void* handler_args, esp_event_base_t base,
|
||||
int32_t event_id, void* event_data) {
|
||||
WebSocketManager* instance = static_cast<WebSocketManager*>(handler_args);
|
||||
esp_websocket_event_data_t* data = (esp_websocket_event_data_t*)event_data;
|
||||
|
||||
switch(event_id) {
|
||||
case WEBSOCKET_EVENT_CONNECTED:
|
||||
instance->connected = true;
|
||||
instance->connecting = false;
|
||||
instance->reconnect_attempts = 0;
|
||||
instance->stats.successful_connections++;
|
||||
|
||||
ESP_LOGI(TAG, "WebSocket connected");
|
||||
if (instance->event_callback) {
|
||||
instance->event_callback(WebSocketEvent::CONNECTED, "Connected successfully");
|
||||
}
|
||||
break;
|
||||
|
||||
case WEBSOCKET_EVENT_DISCONNECTED:
|
||||
instance->connected = false;
|
||||
instance->connecting = false;
|
||||
|
||||
ESP_LOGI(TAG, "WebSocket disconnected");
|
||||
if (instance->event_callback) {
|
||||
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);
|
||||
}
|
||||
break;
|
||||
|
||||
case WEBSOCKET_EVENT_ERROR:
|
||||
instance->connected = false;
|
||||
instance->connecting = false;
|
||||
|
||||
ESP_LOGE(TAG, "WebSocket error");
|
||||
if (instance->event_callback) {
|
||||
instance->event_callback(WebSocketEvent::ERROR, "WebSocket error occurred");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketManager::handleReceivedData(const char* data, int len) {
|
||||
// 尝试解析JSON
|
||||
cJSON* json = cJSON_ParseWithLength(data, len);
|
||||
if (json) {
|
||||
// 成功解析为JSON
|
||||
if (json_callback) {
|
||||
json_callback(json);
|
||||
} else {
|
||||
cJSON_Delete(json);
|
||||
}
|
||||
} else {
|
||||
// 不是JSON格式,作为原始数据处理
|
||||
std::string message(data, len);
|
||||
if (event_callback) {
|
||||
event_callback(WebSocketEvent::DATA_RECEIVED, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketManager::reconnectThread() {
|
||||
while (threads_running) {
|
||||
if (!connected && config.auto_reconnect &&
|
||||
(config.max_reconnect_attempts == 0 ||
|
||||
reconnect_attempts < config.max_reconnect_attempts)) {
|
||||
|
||||
// 检查WiFi连接
|
||||
if (!wifi->isWifiConnect()) {
|
||||
ESP_LOGI(TAG, "WiFi not connected, waiting before WebSocket reconnect");
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(config.reconnect_interval));
|
||||
continue;
|
||||
}
|
||||
|
||||
reconnect_attempts++;
|
||||
ESP_LOGI(TAG, "Attempting to reconnect (%ld/%d)",
|
||||
reconnect_attempts, config.max_reconnect_attempts);
|
||||
|
||||
if (connect()) {
|
||||
ESP_LOGI(TAG, "Reconnection attempt initiated");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Reconnection attempt failed");
|
||||
}
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(config.reconnect_interval));
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketManager::heartbeatThread() {
|
||||
while (threads_running) {
|
||||
if (connected && config.heartbeat_interval > 0) {
|
||||
// 发送心跳消息
|
||||
cJSON* heartbeat = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(heartbeat, "type", "heartbeat");
|
||||
cJSON_AddNumberToObject(heartbeat, "timestamp", esp_log_timestamp());
|
||||
|
||||
sendJson(heartbeat);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(config.heartbeat_interval));
|
||||
} else {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketManager::sendThread() {
|
||||
while (threads_running) {
|
||||
std::unique_lock<std::mutex> lock(queue_mutex);
|
||||
queue_cv.wait(lock, [this]() {
|
||||
return !send_queue.empty() || !threads_running;
|
||||
});
|
||||
|
||||
if (!threads_running) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!send_queue.empty() && connected) {
|
||||
std::string data = std::move(send_queue.front());
|
||||
send_queue.pop();
|
||||
lock.unlock();
|
||||
|
||||
// 发送数据
|
||||
int sent = esp_websocket_client_send_text(client, data.c_str(), static_cast<int>(data.length()), portMAX_DELAY);
|
||||
if (sent >= 0) {
|
||||
stats.messages_sent++;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to send data");
|
||||
// 将数据重新放回队列
|
||||
lock.lock();
|
||||
send_queue.push(std::move(data));
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool WebSocketManager::createWebSocketClient() {
|
||||
esp_websocket_client_config_t ws_config = {};
|
||||
ws_config.uri = config.uri.c_str();
|
||||
ws_config.user_context = this;
|
||||
|
||||
client = esp_websocket_client_init(&ws_config);
|
||||
if (!client) {
|
||||
ESP_LOGE(TAG, "Failed to initialize WebSocket client");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 注册事件处理
|
||||
esp_websocket_register_events(client, WEBSOCKET_EVENT_ANY,
|
||||
&WebSocketManager::websocketEventHandler, this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WebSocketManager::destroyWebSocketClient() {
|
||||
if (client) {
|
||||
esp_websocket_client_stop(client);
|
||||
esp_websocket_client_destroy(client);
|
||||
client = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketManager::wifiEventHandler(void* arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void* event_data) {
|
||||
WebSocketManager* instance = static_cast<WebSocketManager*>(arg);
|
||||
|
||||
if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
|
||||
// WiFi已连接,尝试重新连接WebSocket
|
||||
if (instance->config.auto_reconnect && !instance->connected) {
|
||||
ESP_LOGI(TAG, "WiFi connected, attempting to reconnect WebSocket");
|
||||
instance->connect();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,3 +3,197 @@
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <condition_variable>
|
||||
#include "esp_websocket_client.h"
|
||||
#include "cJSON.h"
|
||||
#include "ThreadManager.h"
|
||||
#include "WifiConnectors.h"
|
||||
|
||||
// WebSocket事件类型
|
||||
enum class WebSocketEvent {
|
||||
CONNECTED,
|
||||
DISCONNECTED,
|
||||
DATA_RECEIVED,
|
||||
ERROR
|
||||
};
|
||||
|
||||
// WebSocket配置
|
||||
struct WebSocketConfig {
|
||||
std::string uri; // WebSocket服务器URI
|
||||
int reconnect_interval = 5000; // 重连间隔(ms)
|
||||
int heartbeat_interval = 30000; // 心跳间隔(ms)
|
||||
int max_reconnect_attempts = 10; // 最大重连次数
|
||||
bool auto_reconnect = true; // 是否自动重连
|
||||
};
|
||||
|
||||
// JSON数据回调
|
||||
using JsonDataCallback = std::function<void(cJSON* json)>;
|
||||
// 事件回调
|
||||
using EventCallback = std::function<void(WebSocketEvent event, const std::string& message)>;
|
||||
|
||||
/* 回调函数示例:
|
||||
// JSON数据回调示例
|
||||
void onJsonData(cJSON* json) {
|
||||
// 处理接收到的JSON数据
|
||||
cJSON* type = cJSON_GetObjectItem(json, "type");
|
||||
if (type && cJSON_IsString(type)) {
|
||||
ESP_LOGI("App", "Received message type: %s", type->valuestring);
|
||||
|
||||
if (strcmp(type->valuestring, "sensor_data") == 0) {
|
||||
cJSON* value = cJSON_GetObjectItem(json, "value");
|
||||
if (value && cJSON_IsNumber(value)) {
|
||||
ESP_LOGI("App", "Sensor value: %.2f", value->valuedouble);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cJSON_Delete(json);
|
||||
}
|
||||
// 事件回调示例
|
||||
void onWebSocketEvent(WebSocketEvent event, const std::string& message) {
|
||||
switch (event) {
|
||||
case WebSocketEvent::CONNECTED:
|
||||
ESP_LOGI("App", "WebSocket connected: %s", message.c_str());
|
||||
break;
|
||||
case WebSocketEvent::DISCONNECTED:
|
||||
ESP_LOGI("App", "WebSocket disconnected: %s", message.c_str());
|
||||
break;
|
||||
case WebSocketEvent::DATA_RECEIVED:
|
||||
ESP_LOGI("App", "Received raw data: %s", message.c_str());
|
||||
break;
|
||||
case WebSocketEvent::ERROR:
|
||||
ESP_LOGE("App", "WebSocket error: %s", message.c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
class WebSocketManager {
|
||||
public:
|
||||
// 获取单例实例
|
||||
static WebSocketManager* getInstance();
|
||||
|
||||
// 删除拷贝构造函数和赋值运算符
|
||||
WebSocketManager(const WebSocketManager&) = delete;
|
||||
WebSocketManager& operator=(const WebSocketManager&) = delete;
|
||||
|
||||
// 初始化WebSocket管理器
|
||||
bool initialize(const WebSocketConfig& config);
|
||||
|
||||
// 连接到WebSocket服务器
|
||||
bool connect();
|
||||
|
||||
// 断开连接
|
||||
void disconnect();
|
||||
|
||||
// 发送JSON数据
|
||||
bool sendJson(cJSON* json);
|
||||
|
||||
// 发送原始字符串数据
|
||||
bool sendRaw(const std::string& data);
|
||||
|
||||
// 设置JSON数据回调
|
||||
void setJsonCallback(JsonDataCallback callback);
|
||||
|
||||
// 设置事件回调
|
||||
void setEventCallback(EventCallback callback);
|
||||
|
||||
// 获取连接状态
|
||||
bool isConnected() const;
|
||||
|
||||
// 获取配置信息
|
||||
WebSocketConfig getConfig() const;
|
||||
|
||||
// 更新配置
|
||||
void updateConfig(const WebSocketConfig& config);
|
||||
|
||||
// 获取统计信息
|
||||
struct Stats {
|
||||
uint32_t messages_sent; /// 发送消息数
|
||||
uint32_t messages_received; /// 接收到的消息数
|
||||
uint32_t connection_attempts; /// 连接尝试次数
|
||||
uint32_t successful_connections; /// 成功连接次数
|
||||
};
|
||||
Stats getStats() const;
|
||||
|
||||
private:
|
||||
WebSocketManager(); // 私有构造函数
|
||||
~WebSocketManager(); // 私有析构函数
|
||||
|
||||
/**
|
||||
* WebSocket事件处理函数
|
||||
* @param handler_args 处理参数
|
||||
* @param base 事件基础
|
||||
* @param event_id 事件ID
|
||||
* @param event_data 事件数据
|
||||
*/
|
||||
static void websocketEventHandler(void* handler_args, esp_event_base_t base,
|
||||
int32_t event_id, void* event_data);
|
||||
|
||||
/**
|
||||
* 处理接收到的数据
|
||||
* @param data 数据
|
||||
* @param len 数据长度
|
||||
*/
|
||||
void handleReceivedData(const char* data, int len);
|
||||
|
||||
// 重连线程函数
|
||||
void reconnectThread();
|
||||
|
||||
// 心跳线程函数
|
||||
void heartbeatThread();
|
||||
|
||||
// 发送线程函数
|
||||
void sendThread();
|
||||
|
||||
// 创建WebSocket客户端
|
||||
bool createWebSocketClient();
|
||||
|
||||
// 销毁WebSocket客户端
|
||||
void destroyWebSocketClient();
|
||||
|
||||
// 处理WiFi事件
|
||||
static void wifiEventHandler(void* arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void* event_data);
|
||||
|
||||
private:
|
||||
static WebSocketManager* instance;
|
||||
static std::mutex instance_mutex;
|
||||
|
||||
esp_websocket_client_handle_t client;
|
||||
WebSocketConfig config;
|
||||
|
||||
// 线程相关
|
||||
std::thread reconnect_thread; /// 重连线程
|
||||
std::thread heartbeat_thread; /// 心跳线程
|
||||
std::thread send_thread; /// 发送线程
|
||||
bool threads_running; /// 线程运行标志
|
||||
|
||||
// 回调函数
|
||||
JsonDataCallback json_callback; /// JSON数据回调
|
||||
EventCallback event_callback; /// 事件回调
|
||||
|
||||
// 状态变量
|
||||
bool connected;
|
||||
bool connecting;
|
||||
uint32_t reconnect_attempts;
|
||||
|
||||
// 统计信息
|
||||
Stats stats;
|
||||
|
||||
// 发送队列
|
||||
std::queue<std::string> send_queue;
|
||||
std::mutex queue_mutex;
|
||||
std::condition_variable queue_cv;
|
||||
|
||||
// WiFi实例
|
||||
WifiConnectors* wifi;
|
||||
};
|
||||
@@ -20,35 +20,6 @@ const auto sleep_time = seconds{
|
||||
5
|
||||
};
|
||||
|
||||
// 下面的这个函数可以放在任何线程中,自动打印出对应线程的信息
|
||||
void print_thread_info(const char *extra = nullptr)
|
||||
{
|
||||
std::stringstream ss;
|
||||
if (extra) {
|
||||
ss << extra;
|
||||
}
|
||||
ss << "Core id: " << xPortGetCoreID()
|
||||
<< ", prio: " << uxTaskPriorityGet(nullptr)
|
||||
<< ", minimum free stack: " << uxTaskGetStackHighWaterMark(nullptr) << " bytes.";
|
||||
ESP_LOGI(pcTaskGetName(nullptr), "%s", ss.str().c_str());
|
||||
}
|
||||
|
||||
void thread_func_inherited()
|
||||
{
|
||||
while (true) {
|
||||
print_thread_info("我是 普普通通的inherited 线程");
|
||||
std::this_thread::sleep_for(sleep_time);
|
||||
}
|
||||
}
|
||||
|
||||
void thread_func_any_core()
|
||||
{
|
||||
while (true) {
|
||||
print_thread_info("我是一个会跑在任意一个核的任务~");
|
||||
std::this_thread::sleep_for(sleep_time);
|
||||
}
|
||||
}
|
||||
|
||||
esp_pthread_cfg_t create_config(const char *name, int core_id, int stack, int prio)
|
||||
{
|
||||
|
||||
@@ -66,6 +37,8 @@ esp_pthread_cfg_t create_config(const char *name, int core_id, int stack, int pr
|
||||
|
||||
#include "LVGLRender.h"
|
||||
#include "SDFileManager.h"
|
||||
#include "AudioOutput.h"
|
||||
|
||||
void OTAClass::Init() {
|
||||
ESP_LOGI("OTA", "Init");
|
||||
|
||||
@@ -75,22 +48,28 @@ void OTAClass::Init() {
|
||||
std::string listing = SDFileManager::getInstance()->lsCommand(".", false, true);
|
||||
ESP_LOGI("SD", "%s", listing.c_str());
|
||||
|
||||
LVGLRender::getInstance()->RenderGif("sequence01.gif");
|
||||
// 切换到music目录
|
||||
SDFileManager::getInstance()->cdCommand("music");
|
||||
std::string pwdPath = SDFileManager::getInstance()->pwdCommand();
|
||||
ESP_LOGI("SD", "%s", pwdPath.c_str());
|
||||
|
||||
// 1. 创建普通函数一个可以运行在任意核上的线程
|
||||
ThreadConfig config1;
|
||||
config1.name = "NormalThread";
|
||||
// config1.core_id = 0; // 不指定运行在哪个核,使其自动选择
|
||||
config1.stack_size = 3072;
|
||||
config1.priority = 5; // 优先级
|
||||
std::thread normal_thread = ThreadManager::createThread(config1, thread_func_any_core);
|
||||
// 列出当前目录内容
|
||||
listing = SDFileManager::getInstance()->lsCommand(".", false, true);
|
||||
ESP_LOGI("SD", "%s", listing.c_str());
|
||||
|
||||
ThreadConfig config2;
|
||||
config2.name = "Thread2";
|
||||
config2.core_id = 1; // 指定运行在核1
|
||||
config2.stack_size = 3072;
|
||||
config2.priority = 5;
|
||||
std::thread thread2 = ThreadManager::createThread(config2, thread_func_inherited);
|
||||
LVGLRender::getInstance()->RenderGif("sequence01m.gif");
|
||||
|
||||
// 设置音量
|
||||
AudioOutput::getInstance()->setVolume(5);
|
||||
|
||||
// 同步播放
|
||||
AudioOutput::getInstance()->playSync("/sdcard/music", "Old_Memory.mp3");
|
||||
|
||||
// 等待5秒
|
||||
// vTaskDelay(pdMS_TO_TICKS(10000));
|
||||
|
||||
// 暂停
|
||||
// AudioOutput::getInstance()->pause();
|
||||
|
||||
|
||||
// 配置Wifi连接线程参数
|
||||
@@ -108,25 +87,23 @@ void OTAClass::Init() {
|
||||
5 // 最大重试次数
|
||||
);
|
||||
|
||||
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
|
||||
);
|
||||
// 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) {
|
||||
std::stringstream ss;
|
||||
ss << "core id: " << xPortGetCoreID()
|
||||
<< ", prio: " << uxTaskPriorityGet(nullptr)
|
||||
<< ", minimum free stack: " << uxTaskGetStackHighWaterMark(nullptr) << " bytes.";
|
||||
ESP_LOGI(pcTaskGetName(nullptr), "%s", ss.str().c_str());
|
||||
std::this_thread::sleep_for(sleep_time);
|
||||
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/9.
|
||||
//
|
||||
|
||||
#include "AudioOutput.h"
|
||||
#include "esp_log.h"
|
||||
#include <memory>
|
||||
#include "PCM5101.h"
|
||||
|
||||
|
||||
// 初始化静态成员
|
||||
AudioOutput* AudioOutput::instance = nullptr;
|
||||
std::mutex AudioOutput::instanceMutex;
|
||||
|
||||
AudioOutput* AudioOutput::getInstance() {
|
||||
std::lock_guard<std::mutex> lock(instanceMutex);
|
||||
if (instance == nullptr) {
|
||||
instance = new AudioOutput();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
AudioOutput::AudioOutput()
|
||||
: currentState(AudioState::IDLE),
|
||||
currentVolume(Volume_MAX - 2),
|
||||
hardwareInitialized(false) {
|
||||
// 初始化硬件
|
||||
init();
|
||||
}
|
||||
|
||||
AudioOutput::~AudioOutput() {
|
||||
// 停止播放并清理资源
|
||||
stop();
|
||||
}
|
||||
|
||||
bool AudioOutput::init() {
|
||||
std::lock_guard<std::mutex> lock(stateMutex);
|
||||
|
||||
if (hardwareInitialized) {
|
||||
ESP_LOGI("AudioOutput", "Audio hardware already initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 初始化SD卡
|
||||
SDFileManager::getInstance()->tryInitSDCard();
|
||||
|
||||
// 初始化音频硬件
|
||||
Audio_Init();
|
||||
hardwareInitialized = true;
|
||||
ESP_LOGI("AudioOutput", "Audio hardware initialized successfully");
|
||||
|
||||
return hardwareInitialized;
|
||||
}
|
||||
|
||||
void AudioOutput::tryInitAudioOutput() {
|
||||
ESP_LOGI("AudioOutput", "Trying to initialize audio output......");
|
||||
}
|
||||
|
||||
|
||||
bool AudioOutput::playSync(const char* directory, const char* fileName) {
|
||||
std::lock_guard<std::mutex> lock(stateMutex);
|
||||
|
||||
if (!hardwareInitialized) {
|
||||
ESP_LOGE("AudioOutput", "Audio hardware not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 停止当前播放 TODO:有bug,需要fix
|
||||
// stop();
|
||||
|
||||
// 播放新文件
|
||||
Play_Music(directory, fileName);
|
||||
|
||||
if (audio_player_get_state() == AUDIO_PLAYER_STATE_PLAYING) {
|
||||
currentState = AudioState::PLAYING;
|
||||
currentFilePath = std::string(directory) + "/" + fileName;
|
||||
return true;
|
||||
}
|
||||
|
||||
currentState = AudioState::ERROR;
|
||||
return false;
|
||||
}
|
||||
|
||||
void AudioOutput::playAsync(const char* directory, const char* fileName, AudioCallback callback) {
|
||||
ThreadConfig config = getThreadConfig("play_async");
|
||||
|
||||
ThreadManager::createThread(config, [this, directory = std::string(directory),
|
||||
fileName = std::string(fileName), callback]() {
|
||||
this->playInternal(directory.c_str(), fileName.c_str(), callback);
|
||||
}).detach();
|
||||
}
|
||||
|
||||
void AudioOutput::playInternal(const char* directory, const char* fileName, AudioCallback callback) {
|
||||
bool success = false;
|
||||
AudioState finalState = AudioState::ERROR;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(stateMutex);
|
||||
|
||||
if (!hardwareInitialized) {
|
||||
ESP_LOGE("AudioOutput", "Audio hardware not initialized");
|
||||
finalState = AudioState::ERROR;
|
||||
} else {
|
||||
// 停止当前播放
|
||||
// stop();
|
||||
|
||||
// 播放新文件
|
||||
Play_Music(directory, fileName);
|
||||
|
||||
if (audio_player_get_state() == AUDIO_PLAYER_STATE_PLAYING) {
|
||||
currentState = AudioState::PLAYING;
|
||||
currentFilePath = std::string(directory) + "/" + fileName;
|
||||
success = true;
|
||||
finalState = AudioState::PLAYING;
|
||||
} else {
|
||||
currentState = AudioState::ERROR;
|
||||
finalState = AudioState::ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback(finalState, success ? currentFilePath.c_str() : nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioOutput::pause() {
|
||||
std::lock_guard<std::mutex> lock(stateMutex);
|
||||
|
||||
if (currentState != AudioState::PLAYING) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Music_pause();
|
||||
|
||||
if (audio_player_get_state() == AUDIO_PLAYER_STATE_PAUSE) {
|
||||
currentState = AudioState::PAUSED;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AudioOutput::resume() {
|
||||
std::lock_guard<std::mutex> lock(stateMutex);
|
||||
|
||||
if (currentState != AudioState::PAUSED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Music_resume();
|
||||
|
||||
if (audio_player_get_state() == AUDIO_PLAYER_STATE_PLAYING) {
|
||||
currentState = AudioState::PLAYING;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AudioOutput::stop() {
|
||||
std::lock_guard<std::mutex> lock(stateMutex);
|
||||
|
||||
if (currentState == AudioState::IDLE || currentState == AudioState::STOPPED) { // 如果当前状态为IDLE或STOPPED,则直接返回成功
|
||||
return true;
|
||||
}
|
||||
|
||||
// 暂停播放
|
||||
Music_pause(); // 内部已经完成关闭文件的操作了
|
||||
|
||||
currentState = AudioState::STOPPED;
|
||||
currentFilePath.clear();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutput::setVolume(uint8_t volume) {
|
||||
std::lock_guard<std::mutex> lock(stateMutex);
|
||||
|
||||
if (volume > Volume_MAX) {
|
||||
ESP_LOGE("AudioOutput", "Volume value %d is out of range (0-%d)", volume, Volume_MAX);
|
||||
return false;
|
||||
}
|
||||
|
||||
Volume_adjustment(volume);
|
||||
currentVolume = volume;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t AudioOutput::getVolume() const {
|
||||
std::lock_guard<std::mutex> lock(stateMutex);
|
||||
return currentVolume;
|
||||
}
|
||||
|
||||
uint8_t AudioOutput::getMaxVolume() const {
|
||||
return Volume_MAX;
|
||||
}
|
||||
|
||||
uint32_t AudioOutput::getDuration() const {
|
||||
return Music_Duration();
|
||||
}
|
||||
|
||||
uint32_t AudioOutput::getElapsed() const {
|
||||
return Music_Elapsed();
|
||||
}
|
||||
|
||||
uint16_t AudioOutput::getEnergy() const {
|
||||
return Music_Energy();
|
||||
}
|
||||
|
||||
AudioState AudioOutput::getState() const {
|
||||
std::lock_guard<std::mutex> lock(stateMutex);
|
||||
return currentState;
|
||||
}
|
||||
|
||||
bool AudioOutput::isPlaying() const {
|
||||
std::lock_guard<std::mutex> lock(stateMutex);
|
||||
return currentState == AudioState::PLAYING;
|
||||
}
|
||||
|
||||
bool AudioOutput::isPaused() const {
|
||||
std::lock_guard<std::mutex> lock(stateMutex);
|
||||
return currentState == AudioState::PAUSED;
|
||||
}
|
||||
|
||||
bool AudioOutput::isStopped() const {
|
||||
std::lock_guard<std::mutex> lock(stateMutex);
|
||||
return currentState == AudioState::STOPPED || currentState == AudioState::IDLE;
|
||||
}
|
||||
|
||||
bool AudioOutput::isFinished() const {
|
||||
return Music_Next_Flag;
|
||||
}
|
||||
|
||||
ThreadConfig AudioOutput::getThreadConfig(const char* operation) {
|
||||
ThreadConfig config;
|
||||
config.name = "audio_" + std::string(operation);
|
||||
config.core_id = -1; // 不绑定核心
|
||||
config.stack_size = 4096;
|
||||
config.priority = 5;
|
||||
config.inherit_cfg = false;
|
||||
return config;
|
||||
}
|
||||
|
||||
void AudioOutput::setState(AudioState newState) {
|
||||
std::lock_guard<std::mutex> lock(stateMutex);
|
||||
currentState = newState;
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
//
|
||||
// Created by misaki on 2025/9/9.
|
||||
//
|
||||
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include "ThreadManager.h"
|
||||
#include "SDFileManager.h"
|
||||
|
||||
// 音频播放状态枚举
|
||||
enum class AudioState {
|
||||
IDLE, // 音频未播放
|
||||
PLAYING, // 音频播放中
|
||||
PAUSED, // 音频暂停中
|
||||
STOPPED, // 音频已停止
|
||||
ERROR // 音频播放错误
|
||||
};
|
||||
|
||||
// 音频播放回调函数类型
|
||||
using AudioCallback = std::function<void(AudioState state, const char* filePath)>;
|
||||
|
||||
/*
|
||||
* 回调函数示例:
|
||||
void audioCallback(AudioState state, const char* filePath) {
|
||||
switch (state) {
|
||||
case AudioState::PLAYING:
|
||||
ESP_LOGI("Example", "Started playing: %s", filePath);
|
||||
break;
|
||||
case AudioState::PAUSED:
|
||||
ESP_LOGI("Example", "Paused: %s", filePath);
|
||||
break;
|
||||
case AudioState::STOPPED:
|
||||
ESP_LOGI("Example", "Stopped: %s", filePath);
|
||||
break;
|
||||
case AudioState::ERROR:
|
||||
ESP_LOGE("Example", "Error playing: %s", filePath);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* 本模块为音频输出模块
|
||||
* 支持同步,异步音频输出
|
||||
*
|
||||
* 注意:底层C驱动任务运行在核0, 请不要把例如lvgl这种高CPU占比的任务放在核0中,避免资源抢占导致播放卡顿
|
||||
*/
|
||||
class AudioOutput {
|
||||
public:
|
||||
// 删除拷贝构造函数和赋值运算符
|
||||
AudioOutput(AudioOutput const&) = delete;
|
||||
AudioOutput& operator=(AudioOutput const&) = delete;
|
||||
|
||||
/**
|
||||
* 获取单例实例
|
||||
* @return AudioOutput实例
|
||||
*/
|
||||
static AudioOutput* getInstance();
|
||||
|
||||
/**
|
||||
* 初始化音频输出
|
||||
* @return 是否成功
|
||||
*/
|
||||
bool init();
|
||||
|
||||
// try to init AudioOutput
|
||||
void tryInitAudioOutput();
|
||||
|
||||
/**
|
||||
* 同步播放音频文件
|
||||
* @param directory 目录路径
|
||||
* @param fileName 文件名
|
||||
* @return 是否成功
|
||||
*/
|
||||
bool playSync(const char* directory, const char* fileName);
|
||||
|
||||
/**
|
||||
* 异步播放音频文件
|
||||
* @param directory 目录路径
|
||||
* @param fileName 文件名
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
void playAsync(const char* directory, const char* fileName, AudioCallback callback = nullptr);
|
||||
|
||||
/**
|
||||
* 暂停播放
|
||||
* @return 是否成功
|
||||
*/
|
||||
bool pause();
|
||||
|
||||
/**
|
||||
* 恢复播放
|
||||
* @return 是否成功
|
||||
*/
|
||||
bool resume();
|
||||
|
||||
/**
|
||||
* 停止播放
|
||||
* @return 是否成功
|
||||
*/
|
||||
bool stop();
|
||||
|
||||
/**
|
||||
* 设置音量
|
||||
* @param volume 音量值 (0-100)
|
||||
* @return 是否成功
|
||||
*/
|
||||
bool setVolume(uint8_t volume);
|
||||
|
||||
/**
|
||||
* 获取当前音量
|
||||
* @return 音量值
|
||||
*/
|
||||
uint8_t getVolume() const;
|
||||
|
||||
/**
|
||||
* 获取最大音量
|
||||
* @return 最大音量值
|
||||
*/
|
||||
uint8_t getMaxVolume() const;
|
||||
|
||||
/**
|
||||
* 获取音频总时长
|
||||
* @return 总时长(毫秒)
|
||||
*/
|
||||
uint32_t getDuration() const;
|
||||
|
||||
/**
|
||||
* 获取已播放时长
|
||||
* @return 已播放时长(毫秒)
|
||||
*/
|
||||
uint32_t getElapsed() const;
|
||||
|
||||
/**
|
||||
* 获取音频能量值
|
||||
* @return 能量值
|
||||
*/
|
||||
uint16_t getEnergy() const;
|
||||
|
||||
/**
|
||||
* 获取当前播放状态
|
||||
* @return 播放状态
|
||||
*/
|
||||
AudioState getState() const;
|
||||
|
||||
/**
|
||||
* 检查是否正在播放
|
||||
* @return 是否正在播放
|
||||
*/
|
||||
bool isPlaying() const;
|
||||
|
||||
/**
|
||||
* 检查是否暂停
|
||||
* @return 是否暂停
|
||||
*/
|
||||
bool isPaused() const;
|
||||
|
||||
/**
|
||||
* 检查是否停止
|
||||
* @return 是否停止
|
||||
*/
|
||||
bool isStopped() const;
|
||||
|
||||
/**
|
||||
* 检查播放是否完成
|
||||
* @return 是否完成
|
||||
*/
|
||||
bool isFinished() const;
|
||||
|
||||
private:
|
||||
// 私有构造函数
|
||||
AudioOutput();
|
||||
~AudioOutput();
|
||||
|
||||
// 初始化线程配置
|
||||
ThreadConfig getThreadConfig(const char* operation);
|
||||
|
||||
// 内部播放实现
|
||||
void playInternal(const char* directory, const char* fileName, AudioCallback callback);
|
||||
|
||||
// 状态转换辅助方法
|
||||
void setState(AudioState newState);
|
||||
|
||||
// 单例实例指针
|
||||
static AudioOutput* instance;
|
||||
static std::mutex instanceMutex;
|
||||
|
||||
// 成员变量
|
||||
mutable std::mutex stateMutex;
|
||||
AudioState currentState;
|
||||
std::string currentFilePath;
|
||||
uint8_t currentVolume;
|
||||
bool hardwareInitialized;
|
||||
};
|
||||
@@ -54,7 +54,7 @@ LVGLRender::LVGLRender() {
|
||||
|
||||
std::thread tick_thread = ThreadManager::createMemberThread(trickConfig, this, &LVGLRender::LVGL_Update);
|
||||
|
||||
tick_thread.detach(); // 线程分离 生命周期跟随主线程结束,线程结束后自动销毁
|
||||
tick_thread.detach(); // 线程分离 生命周期跟随主线程结束,线程结束后自动销毁(核心渲染线程)
|
||||
|
||||
ESP_LOGI("LVGL_Render", "LVGL_Render构造函数...创建LVGL心跳成功...");
|
||||
}
|
||||
@@ -104,36 +104,50 @@ bool LVGLRender::getGifWH(const uint8_t* raw, uint32_t& w, uint32_t& h)
|
||||
void LVGLRender::renderGifInternal(const std::vector<uint8_t>& data,
|
||||
uint32_t w, uint32_t h)
|
||||
{
|
||||
// 构造 lv_img_dsc_t —— 数据指针直接指向 vector 内部
|
||||
static lv_img_dsc_t gif_desc;
|
||||
// 删除旧对象
|
||||
if (current_gif_obj != nullptr) {
|
||||
lv_obj_del(current_gif_obj);
|
||||
current_gif_obj = nullptr;
|
||||
}
|
||||
|
||||
// 保存数据,防止被释放
|
||||
current_gif_data = data;
|
||||
|
||||
// 构造新的描述符(不要 static)
|
||||
lv_img_dsc_t gif_desc = {};
|
||||
gif_desc.header.cf = LV_IMG_CF_RAW_CHROMA_KEYED;
|
||||
gif_desc.header.always_zero = 0;
|
||||
gif_desc.header.reserved = 0;
|
||||
gif_desc.header.w = (lv_coord_t)w;
|
||||
gif_desc.header.h = (lv_coord_t)h;
|
||||
gif_desc.data_size = data.size();
|
||||
gif_desc.data = data.data(); // 指向 vector 内部
|
||||
gif_desc.data_size = current_gif_data.size();
|
||||
gif_desc.data = current_gif_data.data();
|
||||
|
||||
// 创建 lv_gif 对象
|
||||
lv_obj_t* gif = lv_gif_create(lv_scr_act());
|
||||
lv_gif_set_src(gif, &gif_desc);
|
||||
lv_obj_center(gif);
|
||||
// 创建新的 GIF 对象
|
||||
current_gif_obj = lv_gif_create(lv_scr_act());
|
||||
lv_gif_set_src(current_gif_obj, &gif_desc);
|
||||
lv_obj_center(current_gif_obj);
|
||||
|
||||
ESP_LOGI("LVGLRender", "GIF 已渲染到屏幕");
|
||||
ESP_LOGI("LVGLRender", "GIF 已渲染并循环播放");
|
||||
}
|
||||
|
||||
void LVGLRender::RenderGif(const std::string &filename) {
|
||||
std::string fullPath = makeFullPath(filename);
|
||||
if (filename == last_gif_filename) {
|
||||
ESP_LOGW("LVGLRender", "重复加载同一 GIF,忽略");
|
||||
return;
|
||||
}
|
||||
last_gif_filename = filename;
|
||||
|
||||
// 读文件
|
||||
lv_obj_set_style_bg_color(lv_scr_act(), lv_color_black(), 0); // 背景黑色
|
||||
lv_obj_set_style_bg_opa(lv_scr_act(), LV_OPA_COVER, 0); // 透明度
|
||||
|
||||
std::string fullPath = makeFullPath(filename);
|
||||
std::vector<uint8_t> gifBin = readWholeFile(fullPath);
|
||||
if (gifBin.empty()) return;
|
||||
|
||||
// 校验并解析宽高
|
||||
uint32_t w = 0, h = 0;
|
||||
if (!getGifWH(gifBin.data(), w, h)) return;
|
||||
|
||||
// LVGL 渲染函数
|
||||
renderGifInternal(gifBin, w, h);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <core/lv_obj.h>
|
||||
|
||||
class LVGLRender {
|
||||
public:
|
||||
@@ -54,6 +56,10 @@ private:
|
||||
static LVGLRender* LVGLRenderInstance; /// 单例实例
|
||||
static std::mutex instance_mutex; /// 单例锁
|
||||
static uint16_t fps; /// 帧率
|
||||
|
||||
lv_obj_t* current_gif_obj = nullptr; /// 当前GIF对象
|
||||
std::vector<uint8_t> current_gif_data; /// 当前GIF数据
|
||||
std::string last_gif_filename; /// 最后一次渲染的GIF文件名
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -42,6 +42,11 @@ void SDFileManager::init() {
|
||||
is_initialized = true;
|
||||
}
|
||||
|
||||
void SDFileManager::tryInitSDCard() {
|
||||
ESP_LOGI("SDFileManager", "Trying to initialize SD card...");
|
||||
}
|
||||
|
||||
|
||||
bool SDFileManager::writeFileSync(const char* path, const char* data) {
|
||||
std::lock_guard<std::mutex> lock(file_operation_mutex);
|
||||
|
||||
|
||||
@@ -25,6 +25,10 @@ public:
|
||||
|
||||
static SDFileManager* getInstance();
|
||||
|
||||
// try to init sdcard 对外提供的自动初始化,如果已经初始化了,只会打印一个日志,如果没有初始化,会自动初始化,这得益于单例模式的特性
|
||||
// 与一般的初始化函数不同的是,该函数可以被重复调用,不会重复初始化sd卡
|
||||
void tryInitSDCard();
|
||||
|
||||
// 同步文件操作
|
||||
/**
|
||||
* 同步写入文件
|
||||
|
||||
@@ -132,6 +132,34 @@ public:
|
||||
ESP_LOGI(pcTaskGetName(nullptr), "%s", ss.str().c_str());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 打印系统内存信息
|
||||
*/
|
||||
static void print_sys_memory(void)
|
||||
{
|
||||
size_t internal = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
|
||||
size_t spiram = heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
|
||||
printf("Internal(内部): %zu kB, SPIRAM(外部): %zu kB\n", internal / 1024, spiram / 1024);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void stats_task(void)
|
||||
{
|
||||
char stats_buf[2*1024];
|
||||
/* 任务列表 + 绑核信息 */
|
||||
printf("\n-------- vTaskList --------\n");
|
||||
vTaskList(stats_buf);
|
||||
printf("Name State Prio HWM Num Core\n");
|
||||
printf("%s", stats_buf);
|
||||
|
||||
/* 各任务 CPU 使用率(已按核分开统计) */
|
||||
printf("-------- vTaskGetRunTimeStats --------\n");
|
||||
vTaskGetRunTimeStats(stats_buf);
|
||||
printf("Task AbsTime %%Time\n");
|
||||
printf("%s", stats_buf);
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 创建ESP32线程配置
|
||||
|
||||
@@ -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 = 1
|
||||
.priority = 5,
|
||||
.coreID = 0 // 运行在0号核,避免与lvgl抢占资源
|
||||
};
|
||||
ret = audio_player_new(config);
|
||||
if (ret != ESP_OK) {
|
||||
@@ -124,12 +124,25 @@ void Play_Music(const char* directory, const char* fileName)
|
||||
} else {
|
||||
snprintf(filePath, maxPathLength, "%s/%s", directory, fileName);
|
||||
}
|
||||
ESP_LOGD(TAG, "Playing MP3 file: %s", filePath);
|
||||
Music_File = Open_File(filePath);
|
||||
if (!Music_File) {
|
||||
ESP_LOGE(TAG, "Failed to open MP3 file: %s", filePath);
|
||||
return;
|
||||
}
|
||||
|
||||
// 跳过非音频数据
|
||||
uint8_t buf[4];
|
||||
while (fread(buf, 1, 4, Music_File) == 4) {
|
||||
uint32_t head = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
|
||||
if ((head & 0xFFE00000) == 0xFFE00000) { // 找到 MP3 帧头
|
||||
fseek(Music_File, -4, SEEK_CUR); // 回退 4 字节
|
||||
break;
|
||||
}
|
||||
fseek(Music_File, -3, SEEK_CUR); // 逐字节滑动窗口
|
||||
}
|
||||
ESP_LOGI(TAG, "Skipped non-audio data, now at offset %ld", ftell(Music_File));
|
||||
|
||||
expected_event = AUDIO_PLAYER_CALLBACK_EVENT_PLAYING;
|
||||
esp_err_t ret = audio_player_play(Music_File);
|
||||
if (ret != ESP_OK) {
|
||||
|
||||
@@ -197,4 +197,113 @@ extern "C" void app_main(void)
|
||||
std::this_thread::sleep_for(sleep_time);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
```c++
|
||||
#include "WebSocketManager.h"
|
||||
#include "cJSON.h"
|
||||
|
||||
// JSON数据回调示例
|
||||
void onJsonData(cJSON* json) {
|
||||
// 处理接收到的JSON数据
|
||||
cJSON* type = cJSON_GetObjectItem(json, "type");
|
||||
if (type && cJSON_IsString(type)) {
|
||||
ESP_LOGI("App", "Received message type: %s", type->valuestring);
|
||||
|
||||
if (strcmp(type->valuestring, "sensor_data") == 0) {
|
||||
cJSON* value = cJSON_GetObjectItem(json, "value");
|
||||
if (value && cJSON_IsNumber(value)) {
|
||||
ESP_LOGI("App", "Sensor value: %.2f", value->valuedouble);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cJSON_Delete(json);
|
||||
}
|
||||
|
||||
// 事件回调示例
|
||||
void onWebSocketEvent(WebSocketEvent event, const std::string& message) {
|
||||
switch (event) {
|
||||
case WebSocketEvent::CONNECTED:
|
||||
ESP_LOGI("App", "WebSocket connected: %s", message.c_str());
|
||||
break;
|
||||
case WebSocketEvent::DISCONNECTED:
|
||||
ESP_LOGI("App", "WebSocket disconnected: %s", message.c_str());
|
||||
break;
|
||||
case WebSocketEvent::DATA_RECEIVED:
|
||||
ESP_LOGI("App", "Received raw data: %s", message.c_str());
|
||||
break;
|
||||
case WebSocketEvent::ERROR:
|
||||
ESP_LOGE("App", "WebSocket error: %s", message.c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void app_main() {
|
||||
// 初始化WiFi
|
||||
WifiConnectors* wifi = WifiConnectors::getInstance();
|
||||
|
||||
// 连接WiFi(在实际应用中应该在单独线程中进行)
|
||||
ThreadConfig wifi_config;
|
||||
wifi_config.name = "WiFi_Connector";
|
||||
|
||||
auto wifi_thread = ThreadManager::createSingletonThread<WifiConnectors>(
|
||||
wifi_config, &WifiConnectors::connectWifi, "Your_SSID", "Your_Password");
|
||||
|
||||
wifi_thread.join();
|
||||
|
||||
// 获取WebSocket管理器实例
|
||||
WebSocketManager* ws = WebSocketManager::getInstance();
|
||||
|
||||
// 配置WebSocket
|
||||
WebSocketConfig config;
|
||||
config.uri = "ws://your-websocket-server.com/ws";
|
||||
config.auto_reconnect = true;
|
||||
config.reconnect_interval = 5000;
|
||||
config.heartbeat_interval = 30000;
|
||||
config.max_reconnect_attempts = 10;
|
||||
|
||||
// 初始化WebSocket管理器
|
||||
if (!ws->initialize(config)) {
|
||||
ESP_LOGE("App", "Failed to initialize WebSocket manager");
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置回调
|
||||
ws->setJsonCallback(onJsonData);
|
||||
ws->setEventCallback(onWebSocketEvent);
|
||||
|
||||
// 连接WebSocket
|
||||
if (!ws->connect()) {
|
||||
ESP_LOGE("App", "Failed to connect WebSocket");
|
||||
return;
|
||||
}
|
||||
|
||||
// 等待连接建立
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
|
||||
// 发送JSON数据
|
||||
cJSON* json = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(json, "type", "greeting");
|
||||
cJSON_AddStringToObject(json, "message", "Hello from ESP32!");
|
||||
cJSON_AddNumberToObject(json, "timestamp", esp_log_timestamp());
|
||||
|
||||
ws->sendJson(json);
|
||||
|
||||
// 保持运行
|
||||
while (true) {
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
|
||||
// 可以在这里发送其他数据或处理业务逻辑
|
||||
if (ws->isConnected()) {
|
||||
cJSON* status = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(status, "type", "status");
|
||||
cJSON_AddNumberToObject(status, "free_heap", esp_get_free_heap_size());
|
||||
|
||||
ws->sendJson(status);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -33,6 +33,7 @@ idf_component_register(SRCS "Bionic_sphere.c"
|
||||
"../Bionic_Core/OTAClass/OTAClass.cpp" # OTA类库
|
||||
"../Bionic_Core/CommClass/CommClass.cpp" # 通信类库
|
||||
"../Bionic_Core/ToolsClass/ToolsClass.cpp" # 工具类库
|
||||
"../Bionic_Core/ToolsClass/AudioOutput/AudioOutput.cpp" # 音频输出类库
|
||||
"../Bionic_Core/ToolsClass/LVGL_Render/LVGLRender.cpp" # LVGL渲染类库
|
||||
"../Bionic_Core/ToolsClass/SDFileManager/SDFileManager.cpp" # SD文件管理类库
|
||||
"../Bionic_Core/ToolsClass/WifiConnectors/WifiConnectors.cpp" # WIFI连接类库
|
||||
@@ -67,6 +68,7 @@ idf_component_register(SRCS "Bionic_sphere.c"
|
||||
"../Bionic_Core/OTAClass"
|
||||
"../Bionic_Core/CommClass"
|
||||
"../Bionic_Core/ToolsClass"
|
||||
"../Bionic_Core/ToolsClass/AudioOutput"
|
||||
"../Bionic_Core/ToolsClass/LVGL_Render"
|
||||
"../Bionic_Core/ToolsClass/SDFileManager"
|
||||
"../Bionic_Core/ToolsClass/WifiConnectors"
|
||||
|
||||
@@ -1679,7 +1679,7 @@ CONFIG_ESP_SYSTEM_MEMPROT_FEATURE_LOCK=y
|
||||
|
||||
CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32
|
||||
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=2304
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=3584
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
|
||||
CONFIG_ESP_MAIN_TASK_AFFINITY_CPU0=y
|
||||
# CONFIG_ESP_MAIN_TASK_AFFINITY_CPU1 is not set
|
||||
# CONFIG_ESP_MAIN_TASK_AFFINITY_NO_AFFINITY is not set
|
||||
@@ -1902,9 +1902,13 @@ CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=2048
|
||||
CONFIG_FREERTOS_TIMER_QUEUE_LENGTH=10
|
||||
CONFIG_FREERTOS_QUEUE_REGISTRY_SIZE=0
|
||||
CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=1
|
||||
# CONFIG_FREERTOS_USE_TRACE_FACILITY is not set
|
||||
CONFIG_FREERTOS_USE_TRACE_FACILITY=y
|
||||
CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS=y
|
||||
# CONFIG_FREERTOS_USE_LIST_DATA_INTEGRITY_CHECK_BYTES is not set
|
||||
# CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS is not set
|
||||
CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID=y
|
||||
CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y
|
||||
CONFIG_FREERTOS_RUN_TIME_COUNTER_TYPE_U32=y
|
||||
# CONFIG_FREERTOS_RUN_TIME_COUNTER_TYPE_U64 is not set
|
||||
# CONFIG_FREERTOS_USE_APPLICATION_TASK_TAG is not set
|
||||
# end of Kernel
|
||||
|
||||
@@ -1923,6 +1927,7 @@ CONFIG_FREERTOS_TICK_SUPPORT_SYSTIMER=y
|
||||
CONFIG_FREERTOS_CORETIMER_SYSTIMER_LVL1=y
|
||||
# CONFIG_FREERTOS_CORETIMER_SYSTIMER_LVL3 is not set
|
||||
CONFIG_FREERTOS_SYSTICK_USES_SYSTIMER=y
|
||||
CONFIG_FREERTOS_RUN_TIME_STATS_USING_ESP_TIMER=y
|
||||
# CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH is not set
|
||||
# CONFIG_FREERTOS_CHECK_PORT_CRITICAL_COMPLIANCE is not set
|
||||
# end of Port
|
||||
@@ -3216,7 +3221,7 @@ CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y
|
||||
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ=240
|
||||
CONFIG_SYSTEM_EVENT_QUEUE_SIZE=32
|
||||
CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=2304
|
||||
CONFIG_MAIN_TASK_STACK_SIZE=3584
|
||||
CONFIG_MAIN_TASK_STACK_SIZE=8192
|
||||
CONFIG_CONSOLE_UART_DEFAULT=y
|
||||
# CONFIG_CONSOLE_UART_CUSTOM is not set
|
||||
# CONFIG_CONSOLE_UART_NONE is not set
|
||||
|
||||
+16
-4
@@ -1413,7 +1413,7 @@ CONFIG_HTTPD_PURGE_BUF_LEN=32
|
||||
# ESP HTTPS OTA
|
||||
#
|
||||
# CONFIG_ESP_HTTPS_OTA_DECRYPT_CB is not set
|
||||
# CONFIG_ESP_HTTPS_OTA_ALLOW_HTTP is not set
|
||||
CONFIG_ESP_HTTPS_OTA_ALLOW_HTTP=y
|
||||
# end of ESP HTTPS OTA
|
||||
|
||||
#
|
||||
@@ -1902,9 +1902,13 @@ CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=2048
|
||||
CONFIG_FREERTOS_TIMER_QUEUE_LENGTH=10
|
||||
CONFIG_FREERTOS_QUEUE_REGISTRY_SIZE=0
|
||||
CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=1
|
||||
# CONFIG_FREERTOS_USE_TRACE_FACILITY is not set
|
||||
CONFIG_FREERTOS_USE_TRACE_FACILITY=y
|
||||
CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS=y
|
||||
# CONFIG_FREERTOS_USE_LIST_DATA_INTEGRITY_CHECK_BYTES is not set
|
||||
# CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS is not set
|
||||
CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID=y
|
||||
CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y
|
||||
CONFIG_FREERTOS_RUN_TIME_COUNTER_TYPE_U32=y
|
||||
# CONFIG_FREERTOS_RUN_TIME_COUNTER_TYPE_U64 is not set
|
||||
# CONFIG_FREERTOS_USE_APPLICATION_TASK_TAG is not set
|
||||
# end of Kernel
|
||||
|
||||
@@ -1923,6 +1927,7 @@ CONFIG_FREERTOS_TICK_SUPPORT_SYSTIMER=y
|
||||
CONFIG_FREERTOS_CORETIMER_SYSTIMER_LVL1=y
|
||||
# CONFIG_FREERTOS_CORETIMER_SYSTIMER_LVL3 is not set
|
||||
CONFIG_FREERTOS_SYSTICK_USES_SYSTIMER=y
|
||||
CONFIG_FREERTOS_RUN_TIME_STATS_USING_ESP_TIMER=y
|
||||
# CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH is not set
|
||||
# CONFIG_FREERTOS_CHECK_PORT_CRITICAL_COMPLIANCE is not set
|
||||
# end of Port
|
||||
@@ -2635,6 +2640,13 @@ CONFIG_DSP_MAX_FFT_SIZE_4096=y
|
||||
CONFIG_DSP_MAX_FFT_SIZE=4096
|
||||
# end of DSP Library
|
||||
|
||||
#
|
||||
# ESP WebSocket client
|
||||
#
|
||||
# CONFIG_ESP_WS_CLIENT_ENABLE_DYNAMIC_BUFFER is not set
|
||||
# CONFIG_ESP_WS_CLIENT_SEPARATE_TX_LOCK is not set
|
||||
# end of ESP WebSocket client
|
||||
|
||||
#
|
||||
# LVGL configuration
|
||||
#
|
||||
@@ -3184,7 +3196,7 @@ CONFIG_POST_EVENTS_FROM_ISR=y
|
||||
CONFIG_POST_EVENTS_FROM_IRAM_ISR=y
|
||||
CONFIG_GDBSTUB_SUPPORT_TASKS=y
|
||||
CONFIG_GDBSTUB_MAX_TASKS=32
|
||||
# CONFIG_OTA_ALLOW_HTTP is not set
|
||||
CONFIG_OTA_ALLOW_HTTP=y
|
||||
CONFIG_ESP32S3_DEEP_SLEEP_WAKEUP_DELAY=2000
|
||||
CONFIG_ESP_SLEEP_DEEP_SLEEP_WAKEUP_DELAY=2000
|
||||
CONFIG_ESP32S3_RTC_CLK_SRC_INT_RC=y
|
||||
|
||||
Reference in New Issue
Block a user