Rdzleo 672506e7c7 feat(rtc-only): Phase 1 - 通过 CONFIG_BAJI_BADGE_MODE 屏蔽电子吧唧模式
按 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/ 下所有源文件完整保留,无任何物理删除
2026-05-13 10:22:48 +08:00

15 KiB
Raw Blame History

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=nmain 分支默认 =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.cBOOT 按键回调代码本身保留可编译公共功能KEY2 处理代码块用 #ifdef CONFIG_BAJI_BADGE_MODE 包裹

  • main/dzbj/ai_chat_ui.c:清理对吧唧界面的跳转(用 #ifdef 保护,不删代码)

1.4 头文件 stub 处理

对于条件编译后未链接的吧唧模块,其他保留模块若有引用:

  • 头文件本身仍存在(包含 prototype
  • 若调用点未用 #ifdef 保护就会链接报错
  • 解决:在调用点全部加 #ifdef(首选);或在 .h 内提供 stub 实现(次选)

1.5 任务清单

  1. 修改 main/Kconfig.projbuild 新增 CONFIG_BAJI_BADGE_MODE 开关
  2. 修改 main/CMakeLists.txt 把吧唧 srcs 包裹在 if(CONFIG_BAJI_BADGE_MODE)
  3. 修改 main/application.cc#ifdef 保护
  4. 修改 main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc#ifdef 保护
  5. 修改 main/dzbj/ai_chat_ui.c 跳转点加 #ifdef 保护
  6. 修改 main/dzbj/dzbj_button.c KEY2 代码块加 #ifdef 保护
  7. 修改 main/dzbj/sleep_mgr.c 整体用 #ifdef CONFIG_BAJI_BADGE_MODE 包裹Phase 6 改造为 RTC 联动版)
  8. 修改 sdkconfig.defaults(或 sdkconfig.ci)确保 Rtc_AIavatar 默认 CONFIG_BAJI_BADGE_MODE=n
  9. 产出 .planning/milestones/digital_human_rtc/BADGE_MODE_ISOLATION_MAP.md,列出所有 #ifdef 边界位置

完成标志

  • CONFIG_BAJI_BADGE_MODE=nidf.py build 编译通过
  • CONFIG_BAJI_BADGE_MODE=yidf.py build 也编译通过(G7 验收,可恢复双模式
  • 烧录 CONFIG_BAJI_BADGE_MODE=n 版本:开机直接进入 AI 对话界面(无模式选择)
  • main/dzbj/ 下所有源文件仍然存在(未删除)
  • BADGE_MODE_ISOLATION_MAP.md 已生成

风险点

  • C++ 类成员函数无法用 #ifdef 完全屏蔽(如 Application 类的吧唧成员变量),需要把成员变量也用 #ifdef 包裹
  • 头文件相互 include 可能导致循环 #ifdef,必要时改用 forward declaration

产出 commitfeat(kconfig): 引入 CONFIG_BAJI_BADGE_MODE 开关 - 吧唧模式可条件编译屏蔽


Phase 2: 分区表调整

目标:扩容 SPIFFS 到 6MB 装下 3 个 GIF + 背景图。

任务

  1. 修改 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 分区(暂未用)
  1. 验证:idf.py partition-table 总和 = 16MB 减去引导区
  2. 烧录后: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

产出 commitchore(partitions): app 双 OTA 4MB + SPIFFS 6MB为数字人 GIF 扩容)


Phase 3: GIF 资源准备

目标:准备 m03/m06/m07 三个 hiyori GIFgifsicle 处理 + 居中裁剪。

任务

  1. 源 GIF 位置:docs/Rtc_AIavatar/Resources/hiyori_free_zh/Export/m{03,06,07}/hiyori_m{03,06,07}.gif
  2. 用 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
  1. 验证每个 GIF
    • 文件大小(合计 ≤ 5.5MB 留余量)
    • gifsicle --info 检查每帧有 transparent 索引
    • 设备烧录单独测试每个 GIF临时改 bg_gif_demo_start 参数)

完成标志

  • spiffs_image/hiyori_m{03,06,07}.gif 三个文件存在
  • 每个文件 < 2MB三个合计 < 5.5MB
  • 设备烧录后三个 GIF 都能透明显示,无锯齿

产出 commitfeat(assets): 准备 hiyori 三表情 GIFm03/m06/m07+ Python 处理脚本


Phase 4: 情绪 → GIF 映射

目标22 种情绪标签 → 3 个 GIF 的映射表RTC 字幕情绪自动切换 GIF。

任务

  1. 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"},
};
  1. 实现 ai_chat_set_emotion(const char *emotion)
    • 查表 → 调用 bg_gif_demo_switch_gif(path)
    • 静态变量 last_gif_path 去重避免重复加载
  2. bg_gif_demo.cswitch_gif() 接口:
    • 释放旧 GIF PSRAM
    • 加载新 GIF
    • lv_gif_set_src(g_gif_obj, &g_gif_dsc)
    • 重新设置定时器周期 20ms避免恢复默认 10ms
  3. application.cc / volc_rtc_protocol.cc 字幕回调中调用 ai_chat_set_emotion()
  4. 字幕到达时立即触发(不等 is_finallast_subtitle_emotion 去重

完成标志

  • AI 回复"happy你好"时 GIF 切到 m06
  • AI 回复"sad抱歉"时 GIF 切到 m03
  • 切换间无内存泄漏(连续切 50 次 PSRAM 不持续减少)

产出 commitfeat(emotion): 情绪标签 → hiyori GIF 映射 + bg_gif_demo 切换接口


Phase 5: RTC 字幕恢复

目标:屏幕底部半透明字幕显示,不遮挡数字人。

任务

  1. 修改 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)
      • 宽度 300pxwrap 模式
      • 字体 font_puhui_20_4,颜色 0xFFFFFF白色背景半透明更显眼
      • 父容器:半透明黑色 boxlv_obj_set_style_bg_opa(LV_OPA_50)rounded cornerpadding 10px
  2. 创建顺序确保层级:
    • lv_img_create(scr) 背景图(最底层)
    • lv_gif_create(scr) 数字人 GIF
    • lv_obj_create(scr) 字幕容器(最上层)
  3. 字幕长文本自动换行 + 滚动(>3 行截断)

完成标志

  • AI 回复时字幕实时显示在屏幕底部,半透明背景
  • 字幕不遮挡数字人头部
  • 长文本超过 3 行时合理截断或滚动

产出 commitfeat(subtitle): RTC 字幕恢复 - 屏幕底部半透明,避让数字人


Phase 6: RTC 空闲超时联动

目标60s 无对话 → 自动断 RTC + 熄屏;旧 sleep_mgr 代码用 #ifdef CONFIG_BAJI_BADGE_MODE 保留可恢复。

任务

  1. main/dzbj/sleep_mgr.c 整体用 #ifdef CONFIG_BAJI_BADGE_MODE 包裹Phase 1 已做)—— 代码保留可参考
  2. 不删除 CMakeLists.txt 中对应 srcsPhase 1 已包裹在 if(CONFIG_BAJI_BADGE_MODE) 内)
  3. main/application.cc新增RTC 空闲超时逻辑(不依赖 sleep_mgr
    • 复用现有 listening_idle_ticks_ 机制
    • 60s 阈值触发时:
      • 调用 CloseAudioChannel()(断 RTC
      • 调用 pwm_set_brightness(0) 熄屏
      • 暂停 LVGL 刷新
      • 设置 rtc_screen_off_ = true新变量,避免与吧唧 sleep_mgr 全局状态冲突)
  4. 唤醒路径:
    • BOOT 按键回调 → 检查 rtc_screen_off_ → 恢复亮度 + 重连 RTC
    • 长按可选:触发 WiFi 重置(与配网逻辑不冲突)
  5. 字幕/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 重新启用)

产出 commitfeat(idle): 新增 RTC 空闲超时联动熄屏(保留 sleep_mgr 源码可恢复)


Phase 7: 集成测试 + 推送

目标:端到端验证 MILESTONE.md 第 6 节全部验收项,推送到 gitea + GitHub。

任务

  1. 整机端到端测试(按 MILESTONE.md 成功标准清单逐项验证)
  2. 内存/CPU 监控:
    • heap_caps_print_heap_info(MALLOC_CAP_INTERNAL)
    • heap_caps_print_heap_info(MALLOC_CAP_SPIRAM)
    • 30 分钟持续对话压测
  3. idf.py size 对比阉割前后固件大小
  4. 更新 docs/Rtc_AIavatar/数字人表情渲染方案_云端预渲染+BLE+OTA.md 章节:阉割成果汇报
  5. 提交 + 推送:
    • git push origin Rtc_AIavatargitea
    • git push https://github.com/Leo-z8/Baji_Rtc_Toy.git Rtc_AIavatar

完成标志

  • MILESTONE.md 第 6 节成功标准全部 ✓
  • gitea + GitHub 远程已同步
  • 文档更新完成

产出 commitdocs(milestone): 数字人 RTC 项目完成 - 验收报告 + 性能数据


阶段依赖与并行性

  • Phase 1 ⊥ Phase 2独立可并行做
  • Phase 3 依赖 Phase 2需要 6MB SPIFFS
  • Phase 4/5 依赖 Phase 1dzbj 模块清理完成)+ Phase 3GIF 资源就位)
  • 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 待启动