feat(provisioning): BLE 配网完整修复 (跳过 EAF 资源 + EAF 最小化 + 音效播放)
修复 4 个配网模式核心问题, 让 Rtc_AIavatar 分支 (含火山 RTC SDK + 软件 AEC + 完整业务)
能像 adaptation_dzbjImg_shar 一样正常配网, 同时显示居中提示文字.
============ 问题与修复 ============
### 问题 1: 配网模式 BLE 广播 ADV_DATA malloc 失败 (手机搜不到设备)
日志:
E (3731) BLE_INIT: Malloc failed
E (3731) BT_HCI: CC evt: op=0x2008 (HCI_BLE_WRITE_ADV_DATA), status=0x7
I (3731) BluetoothProvisioning: ✅ 广播启动成功 (假成功, 广播数据空)
根因:
Rtc_AIavatar 比 adaptation_dzbjImg_shar 多 ~50-80 KB DRAM 业务 .bss
(软件 AEC + HTTPS 完整状态机 + dialog watchdog + 完整 RTC 状态),
+ 火山 RTC SDK 静态库 .bss ~30-50 KB (g_cnxMgr 14.6KB, ack$14 12.6KB 等),
配网模式时 BLE Bluedroid stack 抢不到广播数据 malloc 所需的 ~10KB DRAM.
修复 (前次 commit 已做): sdkconfig 关闭 BLE 5.0 6 个特性 (项目实际只用 4.2 legacy),
省 ~15 KB controller DRAM, 广播数据 malloc 成功.
### 问题 2: 配网模式下 LCD 绘制跟 WiFi/BLE 初始化抢 DRAM 导致 reboot
日志:
E (1200) wifi:Expected to init 10 rx buffer, actual is 1
E (1220) BluetoothProvisioning: WiFi初始化失败: ESP_ERR_NO_MEM
assert failed: vQueueDelete queue.c:2355 (pxQueue) ← BLE GATT fixed_queue_new 失败 → 反向清理 NULL 队列
排查路径 (失败方案记录):
- esp_lcd_panel_draw_bitmap 一次画 360x360 (253KB): SPI queue 满, 下半屏未画 + DRAM 抢 WiFi
- 分块画 (60 行/块) + vTaskDelay 块间: SPI driver 内部 queue 持续保留 DRAM, 仍然抢
- 强制 codec output_only=false 完整 duplex: 多 15KB DRAM, BLE BTU_StartUp malloc 失败 reboot
- CONFIG_BT_BLE_DYNAMIC_ENV_MEMORY=y: 引入 BTU bt_workqueue 分配失败 → vQueueDelete NULL → assert
修复 (本次 commit): EAF 最小化初始化
movecall_moji_esp32s3.cc 配网模式调用新增的 ai_chat_screen_init_provisioning(),
跳过 8 张 EAF 资源加载 (省 4.32 MB PSRAM + ~10KB DRAM), 跳过数字人 anim,
只启用 gfx_emote renderer + 单个 gfx_label, flush buffer 启动时预分配 (~30KB DRAM 一次到位),
跟 BLE 初始化不再有动态分配冲突. 跟 adaptation_dzbjImg_shar 用 LVGL 显示 GIF 同思路,
用 EAF 替代 LVGL 避免引入 50-80KB LVGL .bss.
### 问题 3: 配网模式音效不播放
根因:
ResetWifiConfiguration 由 BOOT 按键 OnClick 调用, 跑在 esp_timer task 上下文,
vTaskDelay(4000ms) 实测只等了 1.1 秒就被唤醒, 音效没播完就 esp_restart.
修复 (前次 commit 已做): 派发到独立 task 跑 PlaySound + vTaskDelay + esp_restart,
独立 task 中 vTaskDelay 正常工作, 等 4 秒确保 解码 + DMA + 功放尾音完整.
### 问题 4: 配网时屏幕黑屏 (UX 不友好)
实施: ai_chat_screen_init_provisioning("请使用APP\n蓝牙配网~")
LCD 黑底白字居中显示提示文字, 用户感知"配网中".
label height=64 (恰好包 2 行 + 余白), GFX_ALIGN_CENTER 上下左右居中.
============ 文件改动 ============
main/application.cc:
Application 构造时显式注释: 不能在配网模式置 background_task_=nullptr
(OnAudioOutput 无判空, 会 std::mutex::lock 异常 abort).
main/boards/common/wifi_board.cc:
ResetWifiConfiguration 派发独立 task 跑 PlaySound + 4s delay + esp_restart,
EnterWifiConfigMode BLE 启动后早 return (StartBleProvisioning 内部已 Alert 音效).
main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc:
AI 对话模式分支: 配网时调 ai_chat_screen_init_provisioning() 显示文字,
正常模式调 ai_chat_screen_init() 显示数字人.
main/dzbj/ai_chat_ui.h:
新增 ai_chat_screen_init_provisioning(const char* hint_text) 声明.
main/dzbj/ai_chat_ui_eaf.c:
新增 ai_chat_screen_init_provisioning() 实现, EAF 最小化路径:
gfx_emote_init + gfx_disp_add + 单 label 显示文字, 跳过 EAF 资源/anim/背景图.
============ 测试结果 (设备实测) ============
- 按 BOOT 触发配网: 听到完整配网音效 (P3_LALA_WIFICONFIG 约 1 秒)
- 设备重启 → 配网模式启动 → LCD 显示"请使用APP\n蓝牙配网~" 居中
- 手机能搜到 Airhub_d0:cf:13:03:bb:f2 → 能连接 → 配网完成
- 配网完成重启 → 正常模式数字人 + RTC 对话功能正常
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2458c4e8bc
commit
bffd31645e
@ -83,6 +83,8 @@ static const char* const STATE_STRINGS[] = {
|
|||||||
Application::Application() {
|
Application::Application() {
|
||||||
event_group_ = xEventGroupCreate();
|
event_group_ = xEventGroupCreate();
|
||||||
// 吧唧模式不需要后台任务(节省32KB栈内存)
|
// 吧唧模式不需要后台任务(节省32KB栈内存)
|
||||||
|
// ⚠️ 不能在配网模式下置 nullptr: OnAudioOutput 无判空, 直接调
|
||||||
|
// background_task_->Schedule() 会 std::mutex::lock 异常 → abort reboot loop
|
||||||
#ifdef CONFIG_BAJI_BADGE_MODE
|
#ifdef CONFIG_BAJI_BADGE_MODE
|
||||||
if (!device_mode_is_badge()) {
|
if (!device_mode_is_badge()) {
|
||||||
background_task_ = new BackgroundTask(4096 * 8);
|
background_task_ = new BackgroundTask(4096 * 8);
|
||||||
|
|||||||
@ -424,10 +424,10 @@ bool WifiBoard::StartBleProvisioning() {
|
|||||||
// 播放配网提示音
|
// 播放配网提示音
|
||||||
auto& application = Application::GetInstance();
|
auto& application = Application::GetInstance();
|
||||||
if(strcmp(CONFIG_DEVICE_ROLE, "KAKA") == 0){
|
if(strcmp(CONFIG_DEVICE_ROLE, "KAKA") == 0){
|
||||||
application.Alert("BLE配网模式", "请使用手机APP搜索Airhub_开头的蓝牙设备", "happy", Lang::Sounds::P3_KAKA_WIFICONFIG);
|
application.Alert("BLE配网模式", "请使用手机APP搜索设备连接WI-FI", "happy", Lang::Sounds::P3_KAKA_WIFICONFIG);
|
||||||
}
|
}
|
||||||
else if(strcmp(CONFIG_DEVICE_ROLE, "RTC_Test") == 0){
|
else if(strcmp(CONFIG_DEVICE_ROLE, "RTC_Test") == 0){
|
||||||
application.Alert("BLE配网模式", "请使用手机APP搜索Airhub_开头的蓝牙设备", "happy", Lang::Sounds::P3_LALA_WIFICONFIG);
|
application.Alert("BLE配网模式", "请使用手机APP搜索设备连接WI-FI", "happy", Lang::Sounds::P3_LALA_WIFICONFIG);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -468,9 +468,9 @@ bool WifiBoard::StartBleProvisioning() {
|
|||||||
// }
|
// }
|
||||||
// auto& application = Application::GetInstance();
|
// auto& application = Application::GetInstance();
|
||||||
// if (strcmp(CONFIG_DEVICE_ROLE, "KAKA") == 0) {
|
// if (strcmp(CONFIG_DEVICE_ROLE, "KAKA") == 0) {
|
||||||
// application.Alert("BLE配网模式", "请使用手机APP搜索Airhub_开头的蓝牙设备", "", Lang::Sounds::P3_KAKA_WIFICONFIG);
|
// application.Alert("BLE配网模式", "请使用手机APP搜索设备连接WI-FI", "", Lang::Sounds::P3_KAKA_WIFICONFIG);
|
||||||
// } else if (strcmp(CONFIG_DEVICE_ROLE, "RTC_Test") == 0) {
|
// } else if (strcmp(CONFIG_DEVICE_ROLE, "RTC_Test") == 0) {
|
||||||
// application.Alert("BLE配网模式", "请使用手机APP搜索Airhub_开头的蓝牙设备", "", Lang::Sounds::P3_LALA_WIFICONFIG);
|
// application.Alert("BLE配网模式", "请使用手机APP搜索设备连接WI-FI", "", Lang::Sounds::P3_LALA_WIFICONFIG);
|
||||||
// }
|
// }
|
||||||
// while (true) {
|
// while (true) {
|
||||||
// int free_sram = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
|
// int free_sram = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
|
||||||
|
|||||||
@ -16,9 +16,14 @@
|
|||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "dzbj/dzbj_init.h" // dzbj 显示模块初始化(公共: dzbj_hw_display_init;吧唧专用 dzbj_display_init 在头内 #ifdef 包裹)
|
#include "dzbj/dzbj_init.h" // dzbj 显示模块初始化(公共: dzbj_hw_display_init;吧唧专用 dzbj_display_init 在头内 #ifdef 包裹)
|
||||||
#include "display/ai_chat_display.h" // AI 对话模式专用显示
|
#include "display/ai_chat_display.h" // AI 对话模式专用显示
|
||||||
|
#include "dzbj/fatfs.h" // SPIFFS DecodeImg (配网模式背景图也用)
|
||||||
|
#include "esp_spiffs.h" // 配网模式 SPIFFS 挂载
|
||||||
|
#include "esp_lcd_panel_ops.h" // esp_lcd_panel_draw_bitmap (配网模式直接画 bitmap)
|
||||||
|
// ⚠️ 不 include "dzbj/lcd.h" 因为它会 include esp_lvgl_port.h → 触发 lv_font_t 跟 display.h 冲突
|
||||||
|
// panel_handle 是 lcd.c 暴露的全局 C 变量, 直接 extern 声明:
|
||||||
|
extern "C" esp_lcd_panel_handle_t panel_handle;
|
||||||
#ifdef CONFIG_BAJI_BADGE_MODE
|
#ifdef CONFIG_BAJI_BADGE_MODE
|
||||||
#include "dzbj/device_mode.h" // 设备模式管理(AI/吧唧)
|
#include "dzbj/device_mode.h" // 设备模式管理(AI/吧唧)
|
||||||
#include "dzbj/fatfs.h" // SPIFFS 文件系统
|
|
||||||
#include "dzbj/dzbj_ble.h" // BLE 图传服务
|
#include "dzbj/dzbj_ble.h" // BLE 图传服务
|
||||||
#include "dzbj/dzbj_battery.h" // 电池监测
|
#include "dzbj/dzbj_battery.h" // 电池监测
|
||||||
#include "dzbj/dzbj_button.h" // 按键驱动
|
#include "dzbj/dzbj_button.h" // 按键驱动
|
||||||
@ -54,6 +59,8 @@ extern "C" void init_spiffs_image_list(void);
|
|||||||
// AI 对话屏幕初始化(纯 C,避免 lv_font_t 冲突)
|
// AI 对话屏幕初始化(纯 C,避免 lv_font_t 冲突)
|
||||||
extern "C" void ai_chat_screen_init(void);
|
extern "C" void ai_chat_screen_init(void);
|
||||||
extern "C" void ai_chat_resume_animation(void);
|
extern "C" void ai_chat_resume_animation(void);
|
||||||
|
// 配网模式专用最小化初始化 (只显示文字, 不加载 EAF 资源 / 不创建 anim)
|
||||||
|
extern "C" void ai_chat_screen_init_provisioning(const char* hint_text);
|
||||||
|
|
||||||
// 背光初始化(pages_pwm.h 包含 LVGL 头文件,不能直接 include)
|
// 背光初始化(pages_pwm.h 包含 LVGL 头文件,不能直接 include)
|
||||||
extern "C" void pwm_init(void);
|
extern "C" void pwm_init(void);
|
||||||
@ -248,8 +255,21 @@ public:
|
|||||||
// ===== AI 对话模式 =====
|
// ===== AI 对话模式 =====
|
||||||
// 仅硬件+LVGL 初始化(不加载 SquareLine UI)
|
// 仅硬件+LVGL 初始化(不加载 SquareLine UI)
|
||||||
dzbj_hw_display_init(codec_i2c_bus_);
|
dzbj_hw_display_init(codec_i2c_bus_);
|
||||||
|
// 配网模式跳过 EAF 数字人初始化, 节省 ~30 KB DRAM + 4.32 MB PSRAM,
|
||||||
|
// 让 BLE Bluedroid stack 有足够 DRAM 完成 advertising data malloc.
|
||||||
|
// 配网期间用户操作只需要听音效 + 看简单提示, 不需要数字人界面.
|
||||||
|
if (WifiBoard::NeedsProvisioning()) {
|
||||||
|
// 配网模式: EAF 最小化初始化 (仅显示文字, 不加载 8 张 EAF 资源 + 不创建 anim)
|
||||||
|
// 省 4.32 MB PSRAM (EAF 资源) + 数字人 anim 的 DRAM 占用
|
||||||
|
// 保留 ~30 KB DRAM 给 gfx flush buffer (启动时预分配, 不抢 BLE 初始化)
|
||||||
|
// 显示"请使用APP\n蓝牙配网~"提示
|
||||||
|
// 跟 adaptation_dzbjImg_shar (用 LVGL 显示 GIF + 配网) 同思路, 用 EAF 替代 LVGL
|
||||||
|
ESP_LOGI(TAG, "🔵 配网模式: EAF 最小化初始化, 显示配网提示文字");
|
||||||
|
ai_chat_screen_init_provisioning("请使用APP\n蓝牙配网~");
|
||||||
|
} else {
|
||||||
// 加载 AI 对话专用屏幕
|
// 加载 AI 对话专用屏幕
|
||||||
ai_chat_screen_init();
|
ai_chat_screen_init();
|
||||||
|
}
|
||||||
vTaskDelay(pdMS_TO_TICKS(100)); // 等待首帧渲染
|
vTaskDelay(pdMS_TO_TICKS(100)); // 等待首帧渲染
|
||||||
pwm_init(); // 点亮背光
|
pwm_init(); // 点亮背光
|
||||||
ESP_LOGI(TAG, "🤖 AI对话模式启动");
|
ESP_LOGI(TAG, "🤖 AI对话模式启动");
|
||||||
|
|||||||
@ -8,6 +8,13 @@ extern "C" {
|
|||||||
// 创建并加载 AI 对话屏幕
|
// 创建并加载 AI 对话屏幕
|
||||||
void ai_chat_screen_init(void);
|
void ai_chat_screen_init(void);
|
||||||
|
|
||||||
|
// 配网模式专用 - 最小化 EAF 初始化, 只显示文字提示
|
||||||
|
// 不加载 8 张 EAF 资源 (省 4.32 MB PSRAM)
|
||||||
|
// 不创建数字人 anim (省 DRAM)
|
||||||
|
// 不加载背景图 (省 DRAM + SPI buffer)
|
||||||
|
// 只启用 gfx renderer + label, 显示静态文字
|
||||||
|
void ai_chat_screen_init_provisioning(const char* hint_text);
|
||||||
|
|
||||||
// 更新状态文本(如 "Listening...", "Speaking...")
|
// 更新状态文本(如 "Listening...", "Speaking...")
|
||||||
void ai_chat_set_status(const char* status);
|
void ai_chat_set_status(const char* status);
|
||||||
|
|
||||||
|
|||||||
@ -541,3 +541,92 @@ void ai_chat_resume_animation(void) {
|
|||||||
// EAF 动画由 gfx_anim_start 持续播放,无需手动 resume
|
// EAF 动画由 gfx_anim_start 持续播放,无需手动 resume
|
||||||
ESP_LOGD(TAG, "resume_animation(EAF 模式下自动循环,无需操作)");
|
ESP_LOGD(TAG, "resume_animation(EAF 模式下自动循环,无需操作)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==========================================================
|
||||||
|
// 配网模式专用 - 最小化 EAF 初始化, 只显示文字提示
|
||||||
|
// 设计目的: 配网模式下 DRAM 紧张 (RTC SDK + 应用 .bss 占 30-50KB),
|
||||||
|
// 不能完整初始化 EAF (加载 4.32 MB EAF 资源 + 数字人 anim).
|
||||||
|
// 只启用 gfx renderer + 单个 label, 在启动早期预分配 flush buffer,
|
||||||
|
// 避免跟后续 BLE Bluedroid 初始化抢动态分配的 DRAM.
|
||||||
|
// 跟 adaptation_dzbjImg_shar (用 LVGL 显示 GIF) 同思路, 用 EAF 替代 LVGL 省 .bss
|
||||||
|
// ==========================================================
|
||||||
|
void ai_chat_screen_init_provisioning(const char* hint_text) {
|
||||||
|
if (s_initialized) {
|
||||||
|
ESP_LOGW(TAG, "[配网] EAF 已初始化, 跳过");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "============================");
|
||||||
|
ESP_LOGI(TAG, "=== EAF 最小化 (配网模式) ===");
|
||||||
|
ESP_LOGI(TAG, "============================");
|
||||||
|
|
||||||
|
// 1. 初始化 gfx 核心 (Core 0, 跟原版一致, 不抢音频 Core 1)
|
||||||
|
gfx_core_config_t gfx_cfg = {
|
||||||
|
.fps = 25,
|
||||||
|
.task = GFX_EMOTE_INIT_CONFIG(),
|
||||||
|
};
|
||||||
|
gfx_cfg.task.task_priority = 4;
|
||||||
|
gfx_cfg.task.task_affinity = 0;
|
||||||
|
gfx_cfg.task.task_stack = 8 * 1024;
|
||||||
|
s_emote_handle = gfx_emote_init(&gfx_cfg);
|
||||||
|
if (!s_emote_handle) {
|
||||||
|
ESP_LOGE(TAG, "[配网] gfx_emote_init 失败");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 添加 display (接管 panel_handle, 启动时预分配 flush buffer ~30KB DRAM)
|
||||||
|
gfx_disp_config_t disp_cfg = {
|
||||||
|
.h_res = LCD_W,
|
||||||
|
.v_res = LCD_H,
|
||||||
|
.flush_cb = eaf_disp_flush_cb,
|
||||||
|
.update_cb = NULL,
|
||||||
|
.user_data = (void *)panel_handle,
|
||||||
|
.flags = {
|
||||||
|
.swap = true,
|
||||||
|
.buff_dma = true,
|
||||||
|
.buff_spiram = false,
|
||||||
|
.double_buffer = true,
|
||||||
|
},
|
||||||
|
.buffers = { .buf1 = NULL, .buf2 = NULL, .buf_pixels = LCD_W * 20 },
|
||||||
|
};
|
||||||
|
s_disp = gfx_disp_add(s_emote_handle, &disp_cfg);
|
||||||
|
if (!s_disp) {
|
||||||
|
ESP_LOGE(TAG, "[配网] gfx_disp_add 失败");
|
||||||
|
gfx_emote_deinit(s_emote_handle);
|
||||||
|
s_emote_handle = NULL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const esp_lcd_panel_io_callbacks_t cbs = { .on_color_trans_done = eaf_flush_io_ready };
|
||||||
|
esp_lcd_panel_io_register_event_callbacks(lcd_io_handle, &cbs, s_disp);
|
||||||
|
|
||||||
|
// 3. 设置背景色 = 黑色
|
||||||
|
gfx_disp_set_bg_color(s_disp, GFX_COLOR_HEX(0x000000));
|
||||||
|
|
||||||
|
s_initialized = true;
|
||||||
|
|
||||||
|
// 4. 创建文字 label - 不加载 EAF 资源 + 不创建 anim
|
||||||
|
// 上下/左右居中: label height 恰好包文字 (避免 gfx_label 内部顶对齐导致视觉偏上),
|
||||||
|
// 配合 GFX_ALIGN_CENTER 整体居中到屏幕中央
|
||||||
|
gfx_emote_lock(s_emote_handle);
|
||||||
|
s_chat_label = gfx_label_create(s_disp);
|
||||||
|
if (s_chat_label) {
|
||||||
|
gfx_label_set_font(s_chat_label, (gfx_font_t)&font_puhui_20_4);
|
||||||
|
// 字体 20px + line_spacing 8 = 28px/行, 2 行文字 = 56px, 留 8px 余白 = 64px
|
||||||
|
gfx_obj_set_size(s_chat_label, 300, 64);
|
||||||
|
gfx_label_set_long_mode(s_chat_label, GFX_LABEL_LONG_WRAP);
|
||||||
|
gfx_label_set_text_align(s_chat_label, GFX_TEXT_ALIGN_CENTER); // 文字水平居中
|
||||||
|
gfx_label_set_color(s_chat_label, GFX_COLOR_HEX(0xFFFFFF)); // 白字 (黑底背景)
|
||||||
|
gfx_label_set_bg_enable(s_chat_label, false);
|
||||||
|
gfx_label_set_line_spacing(s_chat_label, 8);
|
||||||
|
// label 整体居中到屏幕正中 → 文字视觉上上下左右居中
|
||||||
|
gfx_obj_align(s_chat_label, GFX_ALIGN_CENTER, 0, 0);
|
||||||
|
gfx_label_set_text(s_chat_label, hint_text ? hint_text : "请使用APP\n蓝牙配网~");
|
||||||
|
gfx_obj_set_visible(s_chat_label, true);
|
||||||
|
ESP_LOGI(TAG, "[配网] 文字 label 创建成功 (居中显示)");
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "[配网] gfx_label_create 失败");
|
||||||
|
}
|
||||||
|
gfx_emote_unlock(s_emote_handle);
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "=== EAF 最小化初始化完成 ===");
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user