1. 完成了对音频播放类的完整C++封装,测试通过

2. 修复了LVGL渲染类当中的一些小bug
3. 增加了一些CPU资源占用的日志打印函数,运行在主线程当中
4. 完善了底层通信类的封装,基于websocket,尚未测试
This commit is contained in:
Misaki
2025-09-12 02:11:50 +08:00
parent 4985fee7c2
commit 97fe13da26
16 changed files with 1297 additions and 85 deletions
+382
View File
@@ -2,3 +2,385 @@
// Created by misaki on 2025/9/2.
//
#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();
}
}
}
+194
View File
@@ -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;
};
+37 -60
View File
@@ -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线程配置
+15 -2
View File
@@ -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) {
+109
View File
@@ -198,3 +198,112 @@ extern "C" void app_main(void)
}
}
```
```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);
}
}
}
```
+2
View File
@@ -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"
+9 -4
View File
@@ -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
View File
@@ -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
+11
View File
@@ -206,3 +206,14 @@
- [x] 3. 修复了硬件厂商提供的驱动的Bug
- [x] 4. 初步定义了宠物基类的抽象信息
#### Day13 2025.9.12(前两天在忙考研复习)
##### 主要目标:完成具体业务开发&各种优化
实际完成任务:
- [x] 1. 完成了对音频播放类的完整C++封装,测试通过
- [x] 2. 修复了LVGL渲染类当中的一些小bug
- [x] 3. 增加了一些CPU资源占用的日志打印函数,运行在主线程当中
- [x] 4. 完善了底层通信类的封装,基于websocket,尚未测试