Files
Misaki ba5e47bc77 这是一次长久的提交:
1. 应用界面增加了返回主页的按钮
2. 修复了gif渲染内存泄漏的严重bug
3. 将PetDao当中的cJSON API替换为cpp_json,完美通过测试
4. 整合已经实现的各种上层建筑,实现了一个宠物对话基本业务应用,用于样品测试展示用
5. 重构了音频播放类,使其更modern,更加便于移植和拓展
2025-10-16 11:36:45 +08:00

461 lines
16 KiB
C++

//
// Created by misaki on 2025/9/2.
//
#include "CppHandle.h"
#include "OTAClass.h"
#include <iostream>
#include <optional>
#include <mbedtls/base64.h>
#include "ToolsClass.h"
#include "WifiConnectors.h"
#include "CommClass.h"
#include "sys_conf_singleton.h"
#include "HttpOtaUpdater.h"
#include "AudioOutput.h"
// OTA相关
HttpOtaUpdater otaUpdater;
void setupOtaCallbacks() {
// 设置进度回调
otaUpdater.setProgressCallback([](int progress, int total) {
ESP_LOGI("OTA", "Progress: %d%%", progress);
});
// 设置状态回调
otaUpdater.setStateCallback([](HttpOtaUpdater::OtaState state, const std::string &message) {
const char *stateNames[] = {
"IDLE", "CONNECTING", "DOWNLOADING", "VERIFYING", "SUCCESS", "FAILED"
};
ESP_LOGI("OTA", "State: %s - %s", stateNames[static_cast<int>(state)], message.c_str());
});
// 设置完成回调
otaUpdater.setFinishCallback([](bool success, const std::string &message) {
if (success) {
ESP_LOGI("OTA", "Completed successfully: %s", message.c_str());
} else {
ESP_LOGE("OTA", "Failed: %s", message.c_str());
}
});
// 如果需要HTTPS,可以在这里设置证书(保留供后期使用)
// otaUpdater.setCACert(my_ca_cert_pem);
// otaUpdater.skipCertCommonNameCheck(true); // 仅用于测试
}
// 把 HTTP 文件完整下载到 PSRAM,返回首地址和长度
uint8_t *download_to_psram(const char *url, size_t *out_len)
{
uint8_t *buf = nullptr;
size_t total = 0;
esp_http_client_config_t cfg = {
.url = url,
.timeout_ms = 10000,
.keep_alive_enable = true,
};
esp_http_client_handle_t client = esp_http_client_init(&cfg);
if (esp_http_client_open(client, 0) != ESP_OK) {
ESP_LOGE("HTTP", "Failed to open HTTP connection");
}
int content_len = esp_http_client_fetch_headers(client);
if (content_len <= 0) {
ESP_LOGE("HTTP", "Content-Length invalid");
goto err;
}
buf = (uint8_t *)heap_caps_malloc(content_len, MALLOC_CAP_SPIRAM);
if (!buf) {
ESP_LOGE("HTTP", "PSRAM malloc %d bytes failed", content_len);
goto err;
}
int read;
do {
read = esp_http_client_read(client, (char *)buf + total, content_len - total);
if (read > 0) total += read;
} while (read > 0 && total < content_len);
if (total != content_len) {
ESP_LOGE("HTTP", "Download incomplete %d/%d", total, content_len);
heap_caps_free(buf);
buf = nullptr;
goto err;
}
*out_len = total;
ESP_LOGI("HTTP", "Download done, %d bytes in PSRAM", total);
err:
esp_http_client_close(client);
esp_http_client_cleanup(client);
return buf;
}
// JSON 数据回调函数
/** 交互所使用的json内容
{
"type": "xxx", // 消息类型
"xxx":"xxx", // 其他数据
......
}
此回调函数为核心业务处理函数!!!
*/
void onJsonData(const cppjson::Json &json) {
// 打印收到的 JSON
ESP_LOGI("JSON_CALLBACK", "收到JSON数据: %s", json.dump().c_str());
// 解析消息类型
const cppjson::Json &type = json["type"];
if (!type.isString()) return;
std::string typeStr = type.asString();
if (typeStr == "greeting") {
ESP_LOGI("JSON_CALLBACK", "收到服务器问候消息");
} else if (typeStr == "heartbeat") {
ESP_LOGI("JSON_CALLBACK", "收到心跳响应");
} else if (typeStr == "response") {
ESP_LOGI("JSON_CALLBACK", "收到服务器响应");
} else if (typeStr == "echo") {
ESP_LOGI("JSON_CALLBACK", "收到回显消息");
} else if (typeStr == "broadcast") {
ESP_LOGI("JSON_CALLBACK", "收到广播消息");
} else if (typeStr == "ota") {
ESP_LOGI("JSON_CALLBACK", "收到OTA消息");
// 进一步处理OTA消息
// 获取OTA中的版本信息
const cppjson::Json &version = json["version"];
if (!version.isString()) return;
std::string versionStr = version.asString();
// 获取OTA中的HTTP URL
const cppjson::Json &url = json["url"];
if (!url.isString()) return;
std::string urlStr = url.asString();
// 告诉服务端,升级开始
cppjson::Json response = cppjson::Json::object();
response.set("type", cppjson::Json("ota_start"));
WebSocketManager::getInstance()->sendJson(response);
otaUpdater.start(urlStr);
} else if (typeStr == "audio") {
ESP_LOGI("JSON_CALLBACK", "收到音频消息");
// 进一步解析音频消息内容
// 获取音频信息
cppjson::Json audio_url = json["audio_url"]; // 音频信息
cppjson::Json audio_size = json["audio_size"]; // 音频信息
if (!audio_url.isObject()) return;
if (!audio_size.isObject()) return;
std::cout << "base64: " << audio_url.asString() << std::endl;
std::cout << "size: " << audio_size.asInt() << std::endl;
size_t size;
const uint8_t *pcm_buf = download_to_psram(audio_url.asString().c_str(), &size);
// 播放音频
AudioOutput::getInstance()->playPcmStream(pcm_buf, size);
}
}
// WebSocket事件回调函数
void onWebSocketEvent(WebSocketEvent event, const std::string &message) {
switch (event) {
case WebSocketEvent::CONNECTED:
ESP_LOGI("EVENT_CALLBACK", "WebSocket已连接: %s", message.c_str());
break;
case WebSocketEvent::DISCONNECTED:
ESP_LOGI("EVENT_CALLBACK", "WebSocket已断开: %s", message.c_str());
break;
case WebSocketEvent::DATA_RECEIVED:
ESP_LOGI("EVENT_CALLBACK", "收到原始数据: %s", message.c_str());
break;
case WebSocketEvent::ERROR:
ESP_LOGE("EVENT_CALLBACK", "WebSocket错误: %s", message.c_str());
break;
}
}
// 发送状态信息函数
void sendStatus() {
cppjson::Json status = cppjson::Json::object();
status.set("type", cppjson::Json("status"));
cppjson::Json data = cppjson::Json::object();
data.set("free_heap", cppjson::Json(esp_get_free_heap_size()))
.set("uptime", cppjson::Json(xTaskGetTickCount() * portTICK_PERIOD_MS / 1000));
status.set("data", data); // 嵌套对象
if (WebSocketManager::getInstance()->sendJson(status)) {
ESP_LOGI("SEND", "已发送状态信息");
} else {
ESP_LOGE("SEND", "发送状态信息失败");
}
}
// 发送问候消息函数
void sendGreeting() {
cppjson::Json greeting = cppjson::Json::object();
greeting.set("type", cppjson::Json("greeting"))
.set("message", cppjson::Json("Hello from ESP32-S3"))
.set("timestamp", cppjson::Json(xTaskGetTickCount() * portTICK_PERIOD_MS / 1000));
if (WebSocketManager::getInstance()->sendJson(greeting)) {
ESP_LOGI("SEND", "已发送问候消息");
} else {
ESP_LOGE("SEND", "发送问候消息失败");
}
}
void websocket_task() {
TickType_t lastStatusTime = 0;
TickType_t lastHeartbeatTime = 0;
TickType_t lastGreetingTime = 0;
while (true) {
TickType_t currentTime = xTaskGetTickCount();
// 检查连接状态
if (!WebSocketManager::getInstance()->isConnected()) {
ESP_LOGI("APP_TASK", "WebSocket未连接,尝试重新连接...");
// 确保WiFi已连接
if (!WifiConnectors::getInstance()->isWifiConnect()) {
ESP_LOGI("APP_TASK", "WiFi未连接,等待WiFi连接...");
vTaskDelay(5000 / portTICK_PERIOD_MS);
continue;
}
if (WebSocketManager::getInstance()->connect()) {
ESP_LOGI("APP_TASK", "重新连接成功");
} else {
ESP_LOGI("APP_TASK", "重新连接失败");
}
vTaskDelay(5000 / portTICK_PERIOD_MS);
continue;
}
// 每10秒发送状态信息
if (currentTime - lastStatusTime > (10000 / portTICK_PERIOD_MS)) {
sendStatus();
lastStatusTime = currentTime;
}
// 每60秒发送问候
if (currentTime - lastGreetingTime > (60000 / portTICK_PERIOD_MS)) {
sendGreeting();
lastGreetingTime = currentTime;
}
vTaskDelay(100 / portTICK_PERIOD_MS);
}
}
void createWebSocket() {
// 等待WiFi连接成功后再连接WebSocket
ESP_LOGI("APP_TASK", "等待WiFi连接...");
while (!WifiConnectors::getInstance()->isWifiConnect()) {
ESP_LOGI("APP_TASK", "WiFi未连接,等待中...");
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
// 保存SN
SysConfJson::getInstance()->saveSN(
ToolsClass::GenerateSN(ToolsClass::getChipMAC(), ToolsClass::getChipSerialNumber()));
// 读取SN
std::string sn = SysConfJson::getInstance()->loadSN();
ESP_LOGI("conf", "loaded sn = %s", sn.c_str());
// 配置WebSocket
WebSocketConfig config;
config.uri = "ws://" + std::string("192.168.1.11") + ":" + std::to_string(8080) + "/ws";
config.auto_reconnect = true; // 自动重连
config.reconnect_interval = 5000; // 重连间隔(毫秒)
config.heartbeat_interval = 30000; // 心跳间隔(毫秒)
config.max_reconnect_attempts = 10; // 最大重连尝试次数
// TODO: 此处通信类存在线程重复创建bug,似乎是来自esp-idf的bug,待查证
// 初始化WebSocket管理器
if (!WebSocketManager::getInstance()->initialize(config)) {
ESP_LOGE("APP_TASK", "WebSocket管理器初始化失败");
vTaskDelete(nullptr);
return;
}
// 设置回调函数
WebSocketManager::getInstance()->setJsonCallback(onJsonData);
WebSocketManager::getInstance()->setEventCallback(onWebSocketEvent);
// 连接WebSocket服务器
ESP_LOGI("APP_TASK", "正在连接WebSocket服务器: %s", config.uri.c_str());
if (!WebSocketManager::getInstance()->connect()) {
ESP_LOGE("APP_TASK", "WebSocket连接失败");
}
// 创建WebSocket任务
ThreadConfig websocket_config;
websocket_config.core_id = 0;
websocket_config.inherit_cfg = true;
websocket_config.name = "websocket_task";
websocket_config.priority = 5;
websocket_config.stack_size = 4096;
std::thread websocket_thread = ThreadManager::createThread(websocket_config, websocket_task);
websocket_thread.detach();
}
#include "LVGLRender.h"
#include "SimpleI2SForwarder.h"
#include "lvpp.h"
#include "BaseApp.h"
LV_FONT_DECLARE(SiYuanHeiTiGoogleBan);
// 使用全局智能指针管理主要对象
static std::shared_ptr<lvgl_cpp::Screen> g_screen;
static std::shared_ptr<lvgl_cpp::HomePage> g_home;
static std::shared_ptr<lvgl_cpp::AppMenu> g_menu;
int pet_Test() {
/*
// 存档
PetDAO dao(SDFileManager::getInstance());
const string filename = "cheese_snow_leopard.json";
cout << "\n===== 存档 =====" << endl;
if (dao.savePet(pet, filename))
cout << "✅ 已保存到 " << filename << endl;
else
cout << "❌ 保存失败" << endl;
// 5. 读档
cout << "\n===== 读档 =====" << endl;
auto loaded = dao.loadPet(filename);
if (!loaded) {
cout << "❌ 读档失败" << endl;
return 0;
}
cout << "✅ 读档成功,继续互动:" << endl;
printPet(*loaded);
// 6. 对新对象继续互动
loaded->neglect(); printPet(*loaded);
loaded->feed(); printPet(*loaded);
*/
return 1;
}
void Cpp_Hand() {
// 打印设备信息
ESP_LOGI("CppHandle::Cpp_Hand", "当前固件版本 %s:", ToolsClass::getDeviceVersion().c_str());
ESP_LOGI("CppHandle::Cpp_Hand", "当前设备MAC地址 %s:", ToolsClass::getChipMAC().c_str());
ESP_LOGI("CppHandle::Cpp_Hand", "当前设备固件序列号 %s:", ToolsClass::getChipSerialNumber().c_str());
SDFileManager::getInstance()->tryInitSDCard(); // 初始化SD卡
LVGLRender::getInstance()->tryToInitRenderGif(); // 初始化lvgl驱动(包括任务循环)
LVGLRender::setFps(60); // 设置lvgl刷新频率
// 创建屏幕
g_screen = std::make_shared<lvgl_cpp::Screen>();
lv_scr_load(g_screen->raw());
// 延迟创建界面组件
auto init_timer = std::make_unique<lvgl_cpp::Timer>([&]() {
ESP_LOGI("MAIN", "Initializing UI components...");
// 创建主页
g_home = std::make_shared<lvgl_cpp::HomePage>(*g_screen);
g_home->bg("pic360.bin", 360, 360)
.onBattery([]() -> uint8_t {
return static_cast<uint8_t>(ToolsClass::getInstance()->getBatteryPer());
})
.onDateTime([](char *buf, const size_t len) {
snprintf(buf, len, "06/25 Tue 14:30");
});
// 创建菜单
g_menu = std::make_shared<lvgl_cpp::AppMenu>(*g_screen);
g_menu->addItem("Calcu", 100, 50)
.addItem("Gif测试", 100, 50)
.addItem("Button", 100, 50)
.addItem("AI测试", 100, 50)
.addItem("长按录音", 100, 50)
.addItem("宠物样例", 100, 50)
.onClick([](const char *name) {
ESP_LOGI("APP", "Launching: %s", name);
// 让工厂创建子应用
auto app = lvgl_cpp::AppFactory::create(name, *g_screen);
if (!app) return;
// 隐藏菜单,显示应用
lv_obj_add_flag(g_menu->raw(), LV_OBJ_FLAG_HIDDEN);
app->onShow();
// 应用退出时回到菜单
app->onExit([app_ptr = app.release()]() {
// 转移所有权到 lambda
lv_obj_clear_flag(g_menu->raw(), LV_OBJ_FLAG_HIDDEN);
lv_obj_del(app_ptr->raw()); // 自毁
});
});
g_menu->onBack([]() {
ESP_LOGI("AppMenu", "返回主页回调执行");
// 添加调试信息
if (g_menu && g_menu->raw()) {
ESP_LOGI("AppMenu", "隐藏菜单");
lv_obj_add_flag(g_menu->raw(), LV_OBJ_FLAG_HIDDEN);
} else {
ESP_LOGE("AppMenu", "菜单对象无效");
}
if (g_home && g_home->raw()) {
ESP_LOGI("AppMenu", "显示主页");
lv_obj_clear_flag(g_home->raw(), LV_OBJ_FLAG_HIDDEN);
} else {
ESP_LOGE("AppMenu", "主页对象无效");
}
// 强制刷新显示
lv_refr_now(nullptr);
});
lv_obj_add_flag(g_menu->raw(), LV_OBJ_FLAG_HIDDEN);
// 安全的事件连接
g_home->onOpenMenu([]() {
if (g_home && g_menu) {
lv_obj_add_flag(g_home->raw(), LV_OBJ_FLAG_HIDDEN);
lv_obj_clear_flag(g_menu->raw(), LV_OBJ_FLAG_HIDDEN);
}
});
ESP_LOGI("MAIN", "UI initialization complete");
}, 1000, true); // 1秒后执行一次
// 注册应用
lvgl_cpp::AppFactory::registerApp("Calcu", [](lvgl_cpp::Obj &p) {
return std::make_unique<AppCalc>(p);
});
lvgl_cpp::AppFactory::registerApp("Gif测试", [](lvgl_cpp::Obj &p) {
return std::make_unique<AppMusic>(p);
});
lvgl_cpp::AppFactory::registerApp("Button", [](lvgl_cpp::Obj &p) {
return std::make_unique<ButtonApp>(p);
});
lvgl_cpp::AppFactory::registerApp("AI测试", [](lvgl_cpp::Obj &p) {
return std::make_unique<WebSocketVoice>(p);
});
lvgl_cpp::AppFactory::registerApp("长按录音", [](lvgl_cpp::Obj &p) {
return std::make_unique<LongPressButtonAnim>(p);
});
lvgl_cpp::AppFactory::registerApp("宠物样例", [](lvgl_cpp::Obj &p) {
return std::make_unique<PetApp>(p);
});
// 连接wifi
WifiConnectors::getInstance()->connectWifi("Misaki-2.4G", "88888888", 5);
// 创建WebSocket
createWebSocket();
// 设置OTA回调
// setupOtaCallbacks();
while (true) {
// 主线程线程循环
ThreadManager::print_sys_memory(); // 打印系统内存使用情况
// ThreadManager::stats_task(); // 打印任务统计信息
// ESP_LOGI("APP_TASK", "Battery is:%ld", ToolsClass::getInstance()->getBatteryPer());
// ESP_LOGI("APP_TASK", "Battery is:%f", ToolsClass::getInstance()->getBatteryVolts());
std::this_thread::sleep_for(std::chrono::seconds(1)); // 休眠 1 秒
}
}