按 GSD 框架 .planning/milestones/digital_human_rtc/ 规划完成 Phase 1。 源代码全部保留,通过 Kconfig 开关 + CMakeLists 条件编译 + #ifdef 调用点保护 实现"吧唧模式不进固件但代码可恢复"。 ## 核心变更 ### Kconfig 开关(默认关闭) - 新增 CONFIG_BAJI_BADGE_MODE(main/Kconfig.projbuild) - sdkconfig.defaults 默认 =n - =y 时恢复双模式(电子吧唧 + AI 对话) - =n 时仅 AI 数字人 RTC 模式 ### CMakeLists 剥离(剥离式不重写) - 9 个 dzbj/ 吧唧专属 + 9 个 ui/screens/ 吧唧 UI 进入 if(CONFIG_BAJI_BADGE_MODE) 条件块 - 公共保留: dzbj/lcd.c, ai_chat_ui.c, sprite_demo.c, dual_gif_demo.c, bg_gif_demo.c, pages_pwm.c, dzbj_init.c, fatfs.c - 修正 PLAN 漏判:dzbj_init/fatfs 公共化(AI 模式调用 dzbj_hw_display_init/DecodeImg) ### 调用点 #ifdef 保护 - application.cc: L20 include, L63-66 background_task, L536 device_mode 分支 - movecall_moji_esp32s3.cc: dzbj headers, init_spiffs_image_list extern, dzbj_boot_click_handler extern, device_mode_is_badge 分支, InitializeBadgeMode, InitializeBadgeModeButtons, mode_switch_combo 注册, device_mode_in_switch_suppress - 保留公共 extern: ai_chat_screen_init, ai_chat_resume_animation, pwm_init ### 整体文件级 #ifdef 包裹 - dzbj/dzbj_button.c/h - dzbj/sleep_mgr.c - sleep_mgr/include/sleep_mgr.h ### 6 个文件显式 #include "sdkconfig.h" - ESP-IDF 不会 force-include,必须手动 include 才能拿到 CONFIG_* 宏 ## G7 验收双向编译 - =n 模式 build: ✅ EXIT=0(数字人 RTC 单一形态) - =y 模式 build: ✅ EXIT=0(双模式恢复可用) ## 固件大小变化 | 段 | =n | =y | 节省 | |----|-----|------|------| | .text | 2.03 MB | 2.06 MB | 27 KB | | .rodata | 2.48 MB | 3.87 MB | 1.39 MB | | Total | 4.63 MB | 6.05 MB | 1.45 MB | ## GSD 文档(同时提交) - .planning/milestones/digital_human_rtc/MILESTONE.md - .planning/milestones/digital_human_rtc/ROADMAP.md - .planning/milestones/digital_human_rtc/INTEL.md - .planning/milestones/digital_human_rtc/phases/phase_01_kconfig_isolation/PLAN.md - .planning/milestones/digital_human_rtc/phases/phase_01_kconfig_isolation/SIZE_REPORT.md - .planning/milestones/digital_human_rtc/phases/phase_01_kconfig_isolation/BADGE_MODE_ISOLATION_MAP.md - 编译大小原始数据: size_*.txt ## 已知事项 - =n 固件 4.63 MB 仍 > 4 MB 目标,Phase 2 调整分区 + Phase 3 物理移除图片资源解决 - main/dzbj/ 下所有源文件完整保留,无任何物理删除
15 KiB
ROADMAP — 数字人 RTC 项目
7 个阶段,按依赖关系串行。每个阶段产生原子 commit,可独立 revert。
阶段总览
Phase 1 (Kconfig 屏蔽吧唧) ──┐
├─→ Phase 3 (GIF 资源准备) ──┐
Phase 2 (分区表调整) ──┘ │
├─→ Phase 4 (情绪→GIF 映射)
│
└─→ Phase 5 (字幕恢复)
│
Phase 6 (RTC 空闲超时联动)
│
Phase 7 (集成测试 + 推送)
Phase 1: Kconfig 屏蔽电子吧唧模式 ⚠️ 结构性变更(条件编译,不删源码)
目标:通过 Kconfig 开关 + CMakeLists 条件编译 + 调用点 #ifdef 保护,让吧唧模式代码不进固件但保留在仓库中。Rtc_AIavatar 分支默认 CONFIG_BAJI_BADGE_MODE=n,main 分支默认 =y 保持双模式可恢复。
1.1 新增 Kconfig 开关
修改 main/Kconfig.projbuild,新增:
menu "Baji RTC Toy Configuration"
config BAJI_BADGE_MODE
bool "Enable electronic badge mode (双模式电子吧唧)"
default n
help
启用电子吧唧模式(图片浏览、APP传图、设备间分享、KEY2按键等)。
关闭后仅保留 AI 对话 + 数字人 RTC 功能,节省固件体积。
源代码不会被删除,可随时重新启用。
endmenu
并在 Rtc_AIavatar 分支的 sdkconfig.defaults 中追加:
CONFIG_BAJI_BADGE_MODE=n
1.2 CMakeLists.txt 条件化
修改 main/CMakeLists.txt,将吧唧专属 srcs 包裹在条件块中:
# AI 对话 + 数字人 RTC 核心(始终编译)
set(srcs
"main.cc"
"application.cc"
"ota.cc"
"bluetooth_provisioning.cc"
# ... RTC 协议、Opus、I2S、LCD、字幕等
"dzbj/lcd.c"
"dzbj/ai_chat_ui.c"
"dzbj/bg_gif_demo.c"
"dzbj/dual_gif_demo.c"
"dzbj/sprite_demo.c"
)
# 电子吧唧模式专属(条件编译)
if(CONFIG_BAJI_BADGE_MODE)
list(APPEND srcs
"dzbj/device_mode.c"
"dzbj/dzbj_ble.c"
"dzbj/ble_transfer.c"
"dzbj/dzbj_button.c" # KEY2 部分,BOOT 单键回调在公共模块
"dzbj/pages.c"
"dzbj/fatfs.c"
"dzbj/pages_pwm.c"
"dzbj/dzbj_battery.c"
"dzbj/dzbj_init.c"
"dzbj/sleep_mgr.c"
# UI Screens
"ui/screens/ui_ScreenHome.c"
"ui/screens/ui_ScreenImg.c"
"ui/screens/ui_ScreenSet.c"
"ui/screens/ui_ScreenPeiwang.c"
"ui/screens/ui_ScreenImageShar.c"
"ui/screens/ui_ScreenImageReception.c"
"ui/screens/ui_ScreenSharing.c"
"ui/screens/ui_ScreenReceiving.c"
"ui/screens/ui_ScreenUpdate.c"
)
endif()
1.3 调用点 #ifdef 保护
在所有引用吧唧符号的位置加保护,源代码不删除:
-
main/application.cc:#ifdef CONFIG_BAJI_BADGE_MODE #include "dzbj/device_mode.h" #endif void Application::Start() { // ... 公共代码 ... #ifdef CONFIG_BAJI_BADGE_MODE if (device_mode_get() == MODE_BADGE) { InitBadgeMode(); return; } #endif InitAiMode(); } -
main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc:所有 dzbj header include、初始化、BOOT+KEY2 组合键回调全部用#ifdef CONFIG_BAJI_BADGE_MODE包裹 -
main/dzbj/dzbj_button.c:BOOT 按键回调代码本身保留可编译(公共功能),KEY2 处理代码块用#ifdef CONFIG_BAJI_BADGE_MODE包裹 -
main/dzbj/ai_chat_ui.c:清理对吧唧界面的跳转(用#ifdef保护,不删代码)
1.4 头文件 stub 处理
对于条件编译后未链接的吧唧模块,其他保留模块若有引用:
- 头文件本身仍存在(包含 prototype)
- 若调用点未用
#ifdef保护就会链接报错 - 解决:在调用点全部加
#ifdef(首选);或在 .h 内提供 stub 实现(次选)
1.5 任务清单
- 修改
main/Kconfig.projbuild新增CONFIG_BAJI_BADGE_MODE开关 - 修改
main/CMakeLists.txt把吧唧 srcs 包裹在if(CONFIG_BAJI_BADGE_MODE)内 - 修改
main/application.cc加#ifdef保护 - 修改
main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc加#ifdef保护 - 修改
main/dzbj/ai_chat_ui.c跳转点加#ifdef保护 - 修改
main/dzbj/dzbj_button.cKEY2 代码块加#ifdef保护 - 修改
main/dzbj/sleep_mgr.c整体用#ifdef CONFIG_BAJI_BADGE_MODE包裹(Phase 6 改造为 RTC 联动版) - 修改
sdkconfig.defaults(或sdkconfig.ci)确保 Rtc_AIavatar 默认CONFIG_BAJI_BADGE_MODE=n - 产出
.planning/milestones/digital_human_rtc/BADGE_MODE_ISOLATION_MAP.md,列出所有#ifdef边界位置
完成标志:
- ✅
CONFIG_BAJI_BADGE_MODE=n时idf.py build编译通过 - ✅
CONFIG_BAJI_BADGE_MODE=y时idf.py build也编译通过(G7 验收,可恢复双模式) - ✅ 烧录
CONFIG_BAJI_BADGE_MODE=n版本:开机直接进入 AI 对话界面(无模式选择) - ✅
main/dzbj/下所有源文件仍然存在(未删除) - ✅
BADGE_MODE_ISOLATION_MAP.md已生成
风险点:
- C++ 类成员函数无法用
#ifdef完全屏蔽(如 Application 类的吧唧成员变量),需要把成员变量也用#ifdef包裹 - 头文件相互 include 可能导致循环
#ifdef,必要时改用 forward declaration
产出 commit:feat(kconfig): 引入 CONFIG_BAJI_BADGE_MODE 开关 - 吧唧模式可条件编译屏蔽
Phase 2: 分区表调整
目标:扩容 SPIFFS 到 6MB 装下 3 个 GIF + 背景图。
任务:
- 修改
partitions.csv:
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x4000
otadata, data, ota, 0xD000, 0x2000
phy_init, data, phy, 0xF000, 0x1000
ota_0, app, ota_0, 0x10000, 0x400000 # 4MB(从 6.5MB 缩)
ota_1, app, ota_1, 0x410000, 0x400000 # 4MB
storage, data, spiffs, 0x810000, 0x600000 # 6MB(从 2.875MB 扩)
# 删除 64KB model 分区(暂未用)
- 验证:
idf.py partition-table总和 = 16MB 减去引导区 - 烧录后:
heap_caps_get_free_size(MALLOC_CAP_SPIRAM)不变,SPIFFS 显示 6MB
完成标志:
- ✅
idf.py partition-table总和不超 Flash 大小 - ✅ 编译后固件 .bin < 4MB(确认应用分区够装)
- ✅ 烧录后
esp_spiffs_info("storage", &total, &used)返回 total ≈ 6MB
产出 commit:chore(partitions): app 双 OTA 4MB + SPIFFS 6MB(为数字人 GIF 扩容)
Phase 3: GIF 资源准备
目标:准备 m03/m06/m07 三个 hiyori GIF,gifsicle 处理 + 居中裁剪。
任务:
- 源 GIF 位置:
docs/Rtc_AIavatar/Resources/hiyori_free_zh/Export/m{03,06,07}/hiyori_m{03,06,07}.gif - 用 PIL 遍历所有帧找 bbox(吸取 PoC 经验):
# tools/sprite_poc/prepare_hiyori_3gifs.py(新写)
# 对每个 GIF:
# 1. PIL ImageSequence.Iterator 找全帧 bbox
# 2. 计算裁剪框(含全部动作幅度,宽 240,高从脚底向上 320)
# 3. gifsicle --crop X,Y+WxH --resize 240x320 -O3 --colors 256
# 4. 输出到 spiffs_image/hiyori_m{03,06,07}.gif
- 验证每个 GIF:
- 文件大小(合计 ≤ 5.5MB 留余量)
gifsicle --info检查每帧有 transparent 索引- 设备烧录单独测试每个 GIF(临时改
bg_gif_demo_start参数)
完成标志:
- ✅
spiffs_image/hiyori_m{03,06,07}.gif三个文件存在 - ✅ 每个文件 < 2MB,三个合计 < 5.5MB
- ✅ 设备烧录后三个 GIF 都能透明显示,无锯齿
产出 commit:feat(assets): 准备 hiyori 三表情 GIF(m03/m06/m07)+ Python 处理脚本
Phase 4: 情绪 → GIF 映射
目标:22 种情绪标签 → 3 个 GIF 的映射表,RTC 字幕情绪自动切换 GIF。
任务:
- 在
main/dzbj/ai_chat_ui.c设计映射表:
typedef struct {
const char *emotion; // RTC 协议情绪标签
const char *gif_path; // SPIFFS GIF 路径
} emotion_gif_map_t;
static const emotion_gif_map_t emotion_gif_table[] = {
// 默认/积极 → m06 轻松
{"neutral", "/spiflash/hiyori_m06.gif"},
{"happy", "/spiflash/hiyori_m06.gif"},
{"laughing", "/spiflash/hiyori_m06.gif"},
{"funny", "/spiflash/hiyori_m06.gif"},
{"cool", "/spiflash/hiyori_m06.gif"},
{"loving", "/spiflash/hiyori_m06.gif"},
{"relaxed", "/spiflash/hiyori_m06.gif"},
{"delicious", "/spiflash/hiyori_m06.gif"},
{"silly", "/spiflash/hiyori_m06.gif"},
{"winking", "/spiflash/hiyori_m06.gif"},
{"kissy", "/spiflash/hiyori_m06.gif"},
{"confident", "/spiflash/hiyori_m06.gif"},
// 思考/疲倦 → m07 睡眠
{"sleepy", "/spiflash/hiyori_m07.gif"},
{"thinking", "/spiflash/hiyori_m07.gif"},
{"confused", "/spiflash/hiyori_m07.gif"},
{"embarrassed","/spiflash/hiyori_m07.gif"},
// 负面/严肃 → m03 中等
{"sad", "/spiflash/hiyori_m03.gif"},
{"crying", "/spiflash/hiyori_m03.gif"},
{"angry", "/spiflash/hiyori_m03.gif"},
{"surprised", "/spiflash/hiyori_m03.gif"},
{"shocked", "/spiflash/hiyori_m03.gif"},
{"serious", "/spiflash/hiyori_m03.gif"},
};
- 实现
ai_chat_set_emotion(const char *emotion):- 查表 → 调用
bg_gif_demo_switch_gif(path) - 静态变量
last_gif_path去重避免重复加载
- 查表 → 调用
- 在
bg_gif_demo.c加switch_gif()接口:- 释放旧 GIF PSRAM
- 加载新 GIF
lv_gif_set_src(g_gif_obj, &g_gif_dsc)- 重新设置定时器周期 20ms(避免恢复默认 10ms)
- 在
application.cc/volc_rtc_protocol.cc字幕回调中调用ai_chat_set_emotion() - 字幕到达时立即触发(不等 is_final),用
last_subtitle_emotion去重
完成标志:
- ✅ AI 回复"(happy)你好"时 GIF 切到 m06
- ✅ AI 回复"(sad)抱歉"时 GIF 切到 m03
- ✅ 切换间无内存泄漏(连续切 50 次 PSRAM 不持续减少)
产出 commit:feat(emotion): 情绪标签 → hiyori GIF 映射 + bg_gif_demo 切换接口
Phase 5: RTC 字幕恢复
目标:屏幕底部半透明字幕显示,不遮挡数字人。
任务:
- 修改
main/dzbj/ai_chat_ui.c:- 第 165 行删除
lv_obj_add_flag(chat_label, LV_OBJ_FLAG_HIDDEN) - 第 342 行删除
if (USE_BG_GIF_POC) return - 调整
chat_label创建参数:lv_obj_align(chat_label, LV_ALIGN_BOTTOM_MID, 0, -70)- 宽度 300px,wrap 模式
- 字体
font_puhui_20_4,颜色 0xFFFFFF(白色,背景半透明更显眼) - 父容器:半透明黑色 box(
lv_obj_set_style_bg_opa(LV_OPA_50)),rounded corner,padding 10px
- 第 165 行删除
- 创建顺序确保层级:
lv_img_create(scr)背景图(最底层)lv_gif_create(scr)数字人 GIFlv_obj_create(scr)字幕容器(最上层)
- 字幕长文本自动换行 + 滚动(>3 行截断)
完成标志:
- ✅ AI 回复时字幕实时显示在屏幕底部,半透明背景
- ✅ 字幕不遮挡数字人头部
- ✅ 长文本超过 3 行时合理截断或滚动
产出 commit:feat(subtitle): RTC 字幕恢复 - 屏幕底部半透明,避让数字人
Phase 6: RTC 空闲超时联动
目标:60s 无对话 → 自动断 RTC + 熄屏;旧 sleep_mgr 代码用 #ifdef CONFIG_BAJI_BADGE_MODE 保留可恢复。
任务:
main/dzbj/sleep_mgr.c整体用#ifdef CONFIG_BAJI_BADGE_MODE包裹(Phase 1 已做)—— 代码保留可参考- 不删除 CMakeLists.txt 中对应 srcs(Phase 1 已包裹在
if(CONFIG_BAJI_BADGE_MODE)内) - 在
main/application.cc中新增RTC 空闲超时逻辑(不依赖 sleep_mgr):- 复用现有
listening_idle_ticks_机制 - 60s 阈值触发时:
- 调用
CloseAudioChannel()(断 RTC) - 调用
pwm_set_brightness(0)熄屏 - 暂停 LVGL 刷新
- 设置
rtc_screen_off_ = true(新变量,避免与吧唧 sleep_mgr 全局状态冲突)
- 调用
- 复用现有
- 唤醒路径:
- BOOT 按键回调 → 检查
rtc_screen_off_→ 恢复亮度 + 重连 RTC - 长按可选:触发 WiFi 重置(与配网逻辑不冲突)
- BOOT 按键回调 → 检查
- 字幕/GIF 状态在熄屏前清空(避免唤醒后残留)
RTC 空闲超时逻辑与吧唧 sleep_mgr 的隔离:
- 吧唧的
sleep_mgr_init/notify_activity/is_screen_off全部在#ifdef CONFIG_BAJI_BADGE_MODE内 - 新增的 RTC 空闲超时逻辑在 application.cc 中独立实现,使用独立的状态变量
- 这样两种模式的低功耗机制完全独立,互不干扰,将来如果再启用吧唧模式不会冲突
完成标志:
- ✅ 60s 无 RTC 交互 → 自动断开 + 熄屏
- ✅ BOOT 单击 → 屏幕亮起 + 重连 RTC(数字人 GIF 重新加载)
- ✅ 系统稳定运行 30 分钟无内存累积
- ✅ sleep_mgr.c 源代码仍在仓库中(可通过 Kconfig 重新启用)
产出 commit:feat(idle): 新增 RTC 空闲超时联动熄屏(保留 sleep_mgr 源码可恢复)
Phase 7: 集成测试 + 推送
目标:端到端验证 MILESTONE.md 第 6 节全部验收项,推送到 gitea + GitHub。
任务:
- 整机端到端测试(按 MILESTONE.md 成功标准清单逐项验证)
- 内存/CPU 监控:
heap_caps_print_heap_info(MALLOC_CAP_INTERNAL)heap_caps_print_heap_info(MALLOC_CAP_SPIRAM)- 30 分钟持续对话压测
- 用
idf.py size对比阉割前后固件大小 - 更新
docs/Rtc_AIavatar/数字人表情渲染方案_云端预渲染+BLE+OTA.md章节:阉割成果汇报 - 提交 + 推送:
git push origin Rtc_AIavatar(gitea)git push https://github.com/Leo-z8/Baji_Rtc_Toy.git Rtc_AIavatar
完成标志:
- ✅ MILESTONE.md 第 6 节成功标准全部 ✓
- ✅ gitea + GitHub 远程已同步
- ✅ 文档更新完成
产出 commit:docs(milestone): 数字人 RTC 项目完成 - 验收报告 + 性能数据
阶段依赖与并行性
- Phase 1 ⊥ Phase 2(独立,可并行做)
- Phase 3 依赖 Phase 2(需要 6MB SPIFFS)
- Phase 4/5 依赖 Phase 1(dzbj 模块清理完成)+ Phase 3(GIF 资源就位)
- Phase 4 ⊥ Phase 5(情绪映射和字幕显示独立,可并行)
- Phase 6 依赖 Phase 1(清理 sleep_mgr 调用点)
- Phase 7 必须最后
建议串行执行顺序:1 → 2 → 3 → 4 → 5 → 6 → 7(最稳)
当前状态
| Phase | 状态 |
|---|---|
| Phase 1 | ⏳ 待启动 |
| Phase 2 | ⏳ 待启动 |
| Phase 3 | ⏳ 待启动 |
| Phase 4 | ⏳ 待启动 |
| Phase 5 | ⏳ 待启动 |
| Phase 6 | ⏳ 待启动 |
| Phase 7 | ⏳ 待启动 |