这是一次长久的提交:

1. 应用界面增加了返回主页的按钮
2. 修复了gif渲染内存泄漏的严重bug
3. 将PetDao当中的cJSON API替换为cpp_json,完美通过测试
4. 整合已经实现的各种上层建筑,实现了一个宠物对话基本业务应用,用于样品测试展示用
5. 重构了音频播放类,使其更modern,更加便于移植和拓展
This commit is contained in:
Misaki
2025-10-16 11:36:45 +08:00
parent 801138631e
commit ba5e47bc77
38 changed files with 2487 additions and 2008 deletions
+198 -462
View File
@@ -4,217 +4,20 @@
#include "CppHandle.h"
#include "OTAClass.h"
#include "PetBaseClass.h"
#include "PetDao.h"
#include <iostream>
#include <optional>
void testPetSystem() {
std::cout << "Test point1" << std::endl;
// 创建阶段策略
auto stageStrategy = std::make_unique<PetStageStrategy>();
stageStrategy->addStage(PetStageType::PET_STAGE_YOUNG, "models/young.obj");
stageStrategy->addStage(PetStageType::PET_STAGE_ADULT, "models/adult.obj");
stageStrategy->addStage(PetStageType::PET_STAGE_OLD, "models/old.obj");
stageStrategy->addStageAudio(PetStageType::PET_STAGE_YOUNG, "audio/young.mp3");
stageStrategy->addStageAudio(PetStageType::PET_STAGE_ADULT, "audio/adult.mp3");
stageStrategy->addStageAudio(PetStageType::PET_STAGE_OLD, "audio/old.mp3");
// 创建动作策略
auto actionStrategy = std::make_unique<PetActionStrategy>();
actionStrategy->addAction(PetActionType::PET_ACTION_EAT, "models/eat.obj");
actionStrategy->addAction(PetActionType::PET_ACTION_HAPPY, "models/happy.obj");
actionStrategy->addAction(PetActionType::PET_ACTION_SLEEP, "models/sleep.obj");
actionStrategy->addAction(PetActionType::PET_ACTION_ANGRY, "models/angry.obj");
actionStrategy->addAction(PetActionType::PET_ACTION_SAD, "models/sad.obj");
actionStrategy->addAction(PetActionType::PET_ACTION_EVOLVE, "models/evolve.obj");
actionStrategy->addAction(PetActionType::PET_ACTION_TOUCH, "models/touch.obj");
actionStrategy->addActionAudio(PetActionType::PET_ACTION_EAT, "audio/eat.mp3");
actionStrategy->addActionAudio(PetActionType::PET_ACTION_HAPPY, "audio/happy.mp3");
actionStrategy->addActionAudio(PetActionType::PET_ACTION_SLEEP, "audio/sleep.mp3");
std::cout << "Test point2" << std::endl;
// 创建宠物信息
PetBaseInfo info;
info.pet_name = "芝士雪豹";
info.pet_hp = 100;
info.pet_density = 50;
info.pet_identity = "我是顶真";
// 创建宠物
auto pet = std::make_shared<PetBase>(info, std::move(stageStrategy), std::move(actionStrategy));
std::cout << "Test point3" << std::endl;
// 创建音频观察者
auto audioStrategy = std::make_shared<PetAudioStrategy>();
audioStrategy->setAudioCallback([](const std::string& audioPath) {
std::cout << "Playing audio: " << audioPath << std::endl;
});
audioStrategy->subscribe(pet);
std::cout << "Test point4" << std::endl;
// 创建渲染器观察者
auto rendererStrategy = std::make_shared<PetRendererStrategy>(
pet->getStageStrategy(),
pet->getActionStrategy()
);
rendererStrategy->setRenderCallback([](const std::string& modelPath) {
std::cout << "Rendering model: " << modelPath << std::endl;
});
rendererStrategy->subscribe(pet);
std::cout << "Test point5" << std::endl;
// 执行一些动作
std::cout << "=== Testing basic actions ===" << std::endl;
pet->feed();
pet->play();
pet->touch();
std::cout << "Test point6" << std::endl;
// 检查当前状态
std::cout << "Current HP: " << pet->getPetInfo().pet_hp << std::endl;
std::cout << "Current density: " << pet->getPetInfo().pet_density << std::endl;
std::cout << "Current stage: " << static_cast<int>(pet->getCurrentStage()) << std::endl;
std::cout << "Current action: " << static_cast<int>(pet->getCurrentAction()) << std::endl;
std::cout << "Test point7" << std::endl;
// 测试进化
std::cout << "\n=== Testing evolution ===" << std::endl;
// 直接修改亲密度来测试进化
PetBaseInfo newInfo = pet->getPetInfo();
newInfo.pet_density = 100; // 达到进化条件
pet->setPetInfo(newInfo);
std::cout << "Test point8" << std::endl;
if (pet->checkEvolution()) {
std::cout << "Evolution successful!" << std::endl;
} else {
std::cout << "Evolution failed!" << std::endl;
}
// 进一步增加亲密度到150,尝试再次进化
newInfo.pet_density = 150;
pet->setPetInfo(newInfo);
if (pet->checkEvolution()) {
std::cout << "Second evolution successful!" << std::endl;
} else {
std::cout << "Second evolution failed!" << std::endl;
}
std::cout << "Final stage: " << static_cast<int>(pet->getCurrentStage()) << std::endl;
PetDAO petDAO(SDFileManager::getInstance());
petDAO.savePet(pet, "my_pet.json");
// 列出所有宠物文件
auto petFiles = petDAO.listPetFiles();
for (const auto& file : petFiles) {
std::cout << "Pet file: " << file << std::endl;
}
std::cout << SDFileManager::getInstance()->catCommand("/sdcard/pet_data/my_pet.json") << std::endl;
}
#include "SpeechRecognizer.h"
#include <nvs.h>
#include <nvs_flash.h>
// 命令回调函数
void commandCallback(int command_id, const std::string& phrase, float probability) {
ESP_LOGI("Example", "Received command: ID=%d, Phrase='%s', Probability=%.2f",
command_id, phrase.c_str(), probability);
// 根据命令执行相应操作
switch (command_id) {
case 0:
ESP_LOGI("Example", "执行命令0");
// 执行命令0的操作
break;
case 1:
ESP_LOGI("Example", "执行命令1");
// 执行命令1的操作
break;
case 2:
ESP_LOGI("Example", "执行命令2");
// 执行命令2的操作
break;
default:
ESP_LOGI("Example", "未知的命令ID: %d", command_id);
break;
}
}
// 状态回调函数
void stateCallback(const std::string& state) {
ESP_LOGI("Example", "状态改变到: %s", state.c_str());
}
#include "SDFileManager.h"
void testMIC() {
// 初始化NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
// 初始化SD卡管理器
SDFileManager::getInstance()->tryInitSDCard();
// 获取SpeechRecognizer实例
SpeechRecognizer* recognizer = SpeechRecognizer::getInstance();
// 配置识别器
SpeechRecognizerConfig config;
config.enable_vad = true;
config.vad_mode = VAD_MODE_3; // 更高的VAD灵敏度
config.model_path = "/sdcard/srmodels";
// 初始化
if (!recognizer->init(config)) {
ESP_LOGE("main", "Failed to initialize speech recognizer");
return;
}
// 添加自定义命令
std::vector<std::pair<int, std::string>> commands = {
{0, "kai deng"}, // 开灯
{1, "guan deng"}, // 关灯
{2, "ti gao liang du"}, // 提高亮度
{3, "jiang di liang du"}, // 降低亮度
{4, "bo fang yin yue"}, // 播放音乐
{5, "ting zhi bo fang"} // 停止播放
};
if (!recognizer->addCommands(commands)) {
ESP_LOGE("main", "Failed to add some commands");
}
// 注册回调函数
recognizer->registerCommandCallback(commandCallback);
recognizer->registerStateCallback(stateCallback);
// 开始识别
if (!recognizer->start()) {
ESP_LOGE("main", "Failed to start speech recognition");
return;
}
ESP_LOGI("main", "Speech recognition system started successfully");
}
#include <mbedtls/base64.h>
#include "ToolsClass.h"
#include "WifiConnectors.h"
#include "CommClass.h"
#include "sys_conf_singleton.h"
#include "HttpOtaUpdater.h"
using namespace std::chrono;
const auto sleep_time = seconds{
5
};
#include "AudioOutput.h"
// OTA相关
HttpOtaUpdater otaUpdater;
void setupOtaCallbacks() {
// 设置进度回调
otaUpdater.setProgressCallback([](int progress, int total) {
@@ -222,15 +25,15 @@ void setupOtaCallbacks() {
});
// 设置状态回调
otaUpdater.setStateCallback([](HttpOtaUpdater::OtaState state, const std::string& message) {
const char* stateNames[] = {
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) {
otaUpdater.setFinishCallback([](bool success, const std::string &message) {
if (success) {
ESP_LOGI("OTA", "Completed successfully: %s", message.c_str());
} else {
@@ -243,6 +46,55 @@ void setupOtaCallbacks() {
// 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内容
{
@@ -250,14 +102,14 @@ void setupOtaCallbacks() {
"xxx":"xxx", // 其他数据
......
}
此回调函数为核心业务处理函数!!!
*/
void onJsonData(const cppjson::Json& json)
{
void onJsonData(const cppjson::Json &json) {
// 打印收到的 JSON
ESP_LOGI("JSON_CALLBACK", "收到JSON数据: %s", json.dump().c_str());
// 解析消息类型
const cppjson::Json& type = json["type"];
const cppjson::Json &type = json["type"];
if (!type.isString()) return;
std::string typeStr = type.asString();
@@ -276,11 +128,11 @@ void onJsonData(const cppjson::Json& json)
ESP_LOGI("JSON_CALLBACK", "收到OTA消息");
// 进一步处理OTA消息
// 获取OTA中的版本信息
const cppjson::Json& version = json["version"];
const cppjson::Json &version = json["version"];
if (!version.isString()) return;
std::string versionStr = version.asString();
// 获取OTA中的HTTP URL
const cppjson::Json& url = json["url"];
const cppjson::Json &url = json["url"];
if (!url.isString()) return;
std::string urlStr = url.asString();
// 告诉服务端,升级开始
@@ -288,12 +140,25 @@ void onJsonData(const cppjson::Json& json)
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) {
void onWebSocketEvent(WebSocketEvent event, const std::string &message) {
switch (event) {
case WebSocketEvent::CONNECTED:
ESP_LOGI("EVENT_CALLBACK", "WebSocket已连接: %s", message.c_str());
@@ -311,16 +176,15 @@ void onWebSocketEvent(WebSocketEvent event, const std::string& message) {
}
// 发送状态信息函数
void sendStatus()
{
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));
.set("uptime", cppjson::Json(xTaskGetTickCount() * portTICK_PERIOD_MS / 1000));
status.set("data", data); // 嵌套对象
status.set("data", data); // 嵌套对象
if (WebSocketManager::getInstance()->sendJson(status)) {
ESP_LOGI("SEND", "已发送状态信息");
@@ -330,8 +194,7 @@ void sendStatus()
}
// 发送问候消息函数
void sendGreeting()
{
void sendGreeting() {
cppjson::Json greeting = cppjson::Json::object();
greeting.set("type", cppjson::Json("greeting"))
.set("message", cppjson::Json("Hello from ESP32-S3"))
@@ -343,6 +206,7 @@ void sendGreeting()
ESP_LOGE("SEND", "发送问候消息失败");
}
}
void websocket_task() {
TickType_t lastStatusTime = 0;
TickType_t lastHeartbeatTime = 0;
@@ -392,7 +256,8 @@ void createWebSocket() {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
// 保存SN
SysConfJson::getInstance()->saveSN(ToolsClass::GenerateSN(ToolsClass::getChipMAC(), ToolsClass::getChipSerialNumber()));
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());
@@ -400,15 +265,15 @@ void createWebSocket() {
// 配置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.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(NULL);
vTaskDelete(nullptr);
return;
}
@@ -432,248 +297,120 @@ void createWebSocket() {
std::thread websocket_thread = ThreadManager::createThread(websocket_config, websocket_task);
websocket_thread.detach();
}
#include "LVGLRender.h"
#include <lvgl.h>
#define TAG "BIN_TEST"
/* 从 SD 卡读任意 .bin 到堆 → 直接显示 */
void test_show_bin(const char* fileName)
{
ESP_LOGI(TAG, "=== fast bin test: %s ===", fileName);
/* 1. 读文件 */
std::string path = "/sdcard/" + std::string(fileName);
std::string data = SDFileManager::getInstance()->readFileSync(path.c_str());
if (data.empty()) {
ESP_LOGE(TAG, "read fail");
return;
}
size_t sz = data.size();
ESP_LOGI(TAG, "file size = %zu", sz);
ESP_LOG_BUFFER_HEX(TAG, data.data(), 16); // 头 16 字节
/* 2. 拷到堆(LVGL 长期持有)*/
void* buf = heap_caps_malloc(sz, MALLOC_CAP_8BIT);
memcpy(buf, data.data(), sz);
/* 3. 离线量好的尺寸(先填 320×240 测试,不对再改)*/
uint32_t w = 720;
uint32_t h = 720;
if (sz != w * h * 2) { // RGB565 每像素 2 字节
ESP_LOGW(TAG, "size mismatch, expect %ld, got %zu", w * h * 2, sz);
}
/* 4. 一次性描述符 */
static lv_img_dsc_t dsc;
dsc.data = static_cast<const uint8_t *>(buf);
dsc.header.cf = LV_IMG_CF_TRUE_COLOR;
dsc.header.w = w;
dsc.header.h = h;
dsc.data_size = sz;
/* 5. 直接显示(不经过 PNG 解码器)*/
lv_obj_t* img = lv_img_create(lv_scr_act());
lv_img_set_src(img, &dsc);
lv_obj_center(img);
ESP_LOGI(TAG, "bin displayed, w=%ld h=%ld", w, h);
}
#include "SimpleI2SForwarder.h"
#include "lvpp.h"
#include "BaseApp.h"
LV_FONT_DECLARE(SiYuanHeiTiGoogleBan);
// 使用全局智能指针管理主要对象
static std::shared_ptr<lvgl_cpp::Screen> g_screen;
using namespace lvgl_cpp;
struct AppCalc : BaseApp {
using BaseApp::BaseApp;
void onShow() override {
Label(*this).text("Calculator").center();
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;
}
};
struct AppMusic : BaseApp {
using BaseApp::BaseApp;
void onShow() override {
btn_.emplace(*this);
gif.emplace(*this);
btn_->size(180, 60)
.align(LV_ALIGN_CENTER, nullptr, 0, 80)
.on(LV_EVENT_CLICKED, [&](lv_event_t*) {
/* 居中 GIF,背景黑 */
lv_obj_set_style_bg_color(this->raw(), lv_color_black(), 0);
lv_obj_set_style_bg_opa(this->raw(), LV_OPA_COVER, 0);
gif->src("small_-min.gif")
.center();
});
cout << "✅ 读档成功,继续互动:" << endl;
printPet(*loaded);
/* 按钮文字 */
lv_obj_t* label = lv_label_create(btn_->raw());
lv_obj_set_style_text_font(label, &SiYuanHeiTiGoogleBan, LV_PART_MAIN);
lv_label_set_text(label, "Gif测试");
lv_obj_center(label);
}
private:
std::optional<lvgl_cpp::Button> btn_; // c++17 轻量 RAII
std::optional<lvgl_cpp::Gif> gif;
};
// 6. 对新对象继续互动
loaded->neglect(); printPet(*loaded);
loaded->feed(); printPet(*loaded);
*/
return 1;
}
class ButtonApp : public BaseApp {
public:
using BaseApp::BaseApp; // 继承 exit 按钮机制
void onShow() override {
/* 居中按钮:180×60 px,圆角,现代蓝 */
btn_.emplace(*this);
btn_->size(180, 60)
.align(LV_ALIGN_CENTER, nullptr, 0, 80)
.on(LV_EVENT_CLICKED, [](lv_event_t*) {
lvgl_cpp::Toast::show("咕咕嘎嘎!",
lvgl_cpp::Toast::Type::INFO,
1000);
});
LV_FONT_DECLARE(SiYuanHeiTiGoogleBan);
/* 按钮文字 */
lv_obj_t* label = lv_label_create(btn_->raw());
lv_obj_set_style_text_font(label, &SiYuanHeiTiGoogleBan, LV_PART_MAIN);
lv_label_set_text(label, "按我");
lv_obj_center(label);
}
private:
std::optional<lvgl_cpp::Button> btn_; // c++17 轻量 RAII
};
static std::shared_ptr<HomePage> g_home;
static std::shared_ptr<AppMenu> g_menu;
void Cpp_Hand() {
// testMIC();
// testPetSystem();
SDFileManager::getInstance()->tryInitSDCard();
// 打印设备信息
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刷新频率
LVGLRender::setFps(60); // 设置lvgl刷新频率
/* LVGL 已经初始化,屏幕驱动已注册 */
// auto scr = lvgl_cpp::Screen{}; // RAII,离开作用域自动 del
// lv_scr_load(scr.raw()); // 让 LVGL 把它当活动屏幕
// lvgl_cpp::Image img(scr);
// img.bin("pic_no_alp_swap.bin", 720, 720)
// .center();
//
// /* 1. 创建按钮 */
// /* 1. 黑色文字样式 */
// static lvgl_cpp::Style txt_style;
// txt_style.text_color(lv_color_black());
//
// /* 2. 按钮 */
// lvgl_cpp::Button btn{scr};
// btn.size(150, 60).pos(40, 40);
// /* 3. 先创建 Label 对象并保存,再链式调 */
// lvgl_cpp::Label lbl{btn}; // 一定要存实例
// lbl.text("Click Me")
// .add_style(&txt_style.s, LV_PART_MAIN); // 样式作用到文字本身
// /* 3. 注册点击事件 */
// btn.on(LV_EVENT_CLICKED, [](lv_event_t*){
// lvgl_cpp::Toast::show("Saved successfully !");
// /* 2. 警告,3 s,顶部 */
// lvgl_cpp::Toast::show("SD card missing", lvgl_cpp::Toast::Type::WARN, 3000, LV_ALIGN_TOP_MID, 20);
//
// /* 3. 错误,1.5 s,底部右侧 */
// lvgl_cpp::Toast::show("Network error", lvgl_cpp::Toast::Type::ERROR, 1500, LV_ALIGN_BOTTOM_RIGHT, -30);
// ESP_LOGI("BTN", "pressed");
// });
//
// /* 1. 消息框 */
// // const char* btns[] = {"Yes", "No", ""};
// // auto mbox = lvgl_cpp::MsgBox::create("Hint", "Delete file ?", btns);
//
// /* 3. 进度条(双向 + 动画) */
// lvgl_cpp::Bar bar(scr);
// bar.range(0, 100)
// .start_value(20) // 左端
// .value(80, LV_ANIM_ON) // 右端带动画
// .anim_time(500)
// .size(200, 15)
// .align(LV_ALIGN_CENTER, nullptr, 0, 40);
//
// /* 4. 折线:画一个 △ */
// std::vector<lv_point_t> triangle = {{0,0}, {40,0}, {20,40}, {0,0}};
// lvgl_cpp::Line line(scr);
// line.points(triangle)
// .y_invert(false)
// .align(LV_ALIGN_CENTER, nullptr, 0, 100);
//
// lvgl_cpp::Battery bat(scr);
// bat.size(60, 30) // 手机经典尺寸
// .percent(true) // 显示 50%
// .onRead([]() -> uint8_t { // 替换成你的 ADC/INA219 回调
// static uint8_t v = 100;
// if (v) --v;
// return v;
// })
// .align(LV_ALIGN_TOP_RIGHT, -10, 10); // 九宫格对齐
//
// /* 3. 日期时间 */
// lvgl_cpp::DateTime dt(scr);
// dt.format("%m/%d %a %H:%M")
// .onRead([](char* buf, size_t len){
// /* 这里用 SNTP / RTC 填充,示例直接给假时间 */
// snprintf(buf, len, "06/25 Tue 14:30");
// })
// .align(LV_ALIGN_BOTTOM_MID, nullptr, 0, -10);
// lvgl_cpp::ColorPicker cp(scr);
// cp.size(120, 120)
// .align(LV_ALIGN_BOTTOM_MID, nullptr, 0, -20)
// .on(LV_EVENT_VALUE_CHANGED, [&](lv_event_t*){
// lv_color_t c = cp.color();
// ESP_LOGI("CP", "rgb=%d,%d,%d", c.ch.red, c.ch.green_h, c.ch.blue);
// });
// 1. 创建屏幕
// 创建屏幕
g_screen = std::make_shared<lvgl_cpp::Screen>();
lv_scr_load(g_screen->raw());
// 2. 延迟创建界面组件
// 延迟创建界面组件
auto init_timer = std::make_unique<lvgl_cpp::Timer>([&]() {
ESP_LOGI("MAIN", "Initializing UI components...");
// 创建主页
g_home = std::make_shared<HomePage>(*g_screen);
g_home = std::make_shared<lvgl_cpp::HomePage>(*g_screen);
g_home->bg("pic360.bin", 360, 360)
.onBattery([]() -> uint8_t {
static uint8_t v = 100;
if (v) --v;
return 88;
})
.onDateTime([](char* buf, size_t len) {
snprintf(buf, len, "06/25 Tue 14:30");
});
// 创建菜单
g_menu = std::make_shared<AppMenu>(*g_screen);
g_menu->addItem("Calcu", 100, 50)
.addItem("Gif测试", 100, 50)
.addItem("Button", 100, 50)
.onClick([](const char* name) {
ESP_LOGI("APP", "Launching: %s", name);
/* 1. 让工厂创建子应用 */
auto app = AppFactory::create(name, *g_screen);
if (!app) return;
/* 2. 隐藏菜单,显示应用 */
lv_obj_add_flag(g_menu->raw(), LV_OBJ_FLAG_HIDDEN);
app->onShow();
/* 3. 应用退出时回到菜单 */
app->onExit([app_ptr = app.release()]() { // 转移所有权到 lambda
lv_obj_clear_flag(g_menu->raw(), LV_OBJ_FLAG_HIDDEN);
lv_obj_del(app_ptr->raw()); // 自毁
.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) {
@@ -681,44 +418,43 @@ void Cpp_Hand() {
lv_obj_clear_flag(g_menu->raw(), LV_OBJ_FLAG_HIDDEN);
}
});
ESP_LOGI("MAIN", "UI initialization complete");
}, 1000, true); // 1秒后执行一次
// 注册应用
AppFactory::registerApp("Calcu", [](Obj& p) {
lvgl_cpp::AppFactory::registerApp("Calcu", [](lvgl_cpp::Obj &p) {
return std::make_unique<AppCalc>(p);
});
AppFactory::registerApp("Gif测试", [](Obj& p) {
lvgl_cpp::AppFactory::registerApp("Gif测试", [](lvgl_cpp::Obj &p) {
return std::make_unique<AppMusic>(p);
});
AppFactory::registerApp("Button", [](lvgl_cpp::Obj& p) {
lvgl_cpp::AppFactory::registerApp("Button", [](lvgl_cpp::Obj &p) {
return std::make_unique<ButtonApp>(p);
});
// test_show_bin("pic_no_alp_swap.bin");
// LVGLRender::getInstance()->RenderGif("sequence02mmm.gif");
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());
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);
WifiConnectors::getInstance()->connectWifi("Misaki-2.4G", "88888888", 5);
// 创建WebSocket
// createWebSocket();
createWebSocket();
// 设置OTA回调
// setupOtaCallbacks();
while (true) { // 主线程线程循环
// ThreadManager::print_sys_memory(); // 打印系统内存使用情况
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(sleep_time); // 休眠5
// 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
}
}