// // Created by misaki on 2025/9/2. // #include "CppHandle.h" #include "OTAClass.h" #include #include #include #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(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 g_screen; static std::shared_ptr g_home; static std::shared_ptr 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(); lv_scr_load(g_screen->raw()); // 延迟创建界面组件 auto init_timer = std::make_unique([&]() { ESP_LOGI("MAIN", "Initializing UI components..."); // 创建主页 g_home = std::make_shared(*g_screen); g_home->bg("pic360.bin", 360, 360) .onBattery([]() -> uint8_t { return static_cast(ToolsClass::getInstance()->getBatteryPer()); }) .onDateTime([](char *buf, const size_t len) { snprintf(buf, len, "06/25 Tue 14:30"); }); // 创建菜单 g_menu = std::make_shared(*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(p); }); lvgl_cpp::AppFactory::registerApp("Gif测试", [](lvgl_cpp::Obj &p) { return std::make_unique(p); }); lvgl_cpp::AppFactory::registerApp("Button", [](lvgl_cpp::Obj &p) { return std::make_unique(p); }); lvgl_cpp::AppFactory::registerApp("AI测试", [](lvgl_cpp::Obj &p) { return std::make_unique(p); }); lvgl_cpp::AppFactory::registerApp("长按录音", [](lvgl_cpp::Obj &p) { return std::make_unique(p); }); lvgl_cpp::AppFactory::registerApp("宠物样例", [](lvgl_cpp::Obj &p) { return std::make_unique(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 秒 } }