55 Commits

Author SHA1 Message Date
2458c4e8bc fix(ble): 关闭 BLE 5.0 特性释放 controller DRAM, 修复配网广播数据 malloc 失败
问题:
  日志:
    E (3731) BLE_INIT: Malloc failed
    E (3731) BT_HCI: CC evt: op=0x2008 (HCI_BLE_WRITE_ADV_DATA), status=0x7
    E (3731) BLE_INIT: Malloc failed
    E (3731) BT_HCI: CC evt: op=0x2009 (HCI_BLE_WRITE_SCAN_RSP), status=0x7
    I (3731) BluetoothProvisioning:  广播启动成功
  代码层"广播启动成功", 但 HCI 层 ADV_DATA + SCAN_RSP_DATA 都没写入 → 广播空数据 → 手机搜不到设备.

根因:
  sdkconfig 同时启用了 BLE 5.0 + 4.2 全套特性, 但项目代码实际只用 4.2 legacy advertising
  (esp_ble_adv_params_t / esp_ble_gap_start_advertising). BLE 5.0 6 个特性总和占用 controller DRAM,
  让 HCI ADV_DATA / SCAN_RSP_DATA 内存池耗尽.

修复:
  sdkconfig 关闭所有 BLE 5.0 特性 (项目实际不用):
    # CONFIG_BT_BLE_50_FEATURES_SUPPORTED is not set
    # CONFIG_BT_BLE_50_EXTEND_ADV_EN is not set
    # CONFIG_BT_BLE_50_PERIODIC_ADV_EN is not set
    # CONFIG_BT_BLE_50_EXTEND_SCAN_EN is not set
    # CONFIG_BT_BLE_50_EXTEND_SYNC_EN is not set
    # CONFIG_BT_BLE_50_DTM_TEST_EN is not set
  保留 CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y (legacy advertising 必需).
  dzbj_ble.c:458 用了 esp_ble_gap_set_preferred_phy (BLE 5.0 API), 但有
  #if (BLE_50_FEATURE_SUPPORT == TRUE) 保护, 关闭后编译自动跳过, 不影响其他功能.

附加: 修复 BOOT 触发配网时音效不响

  wifi_board.cc::ResetWifiConfiguration():
    - 移除直接 vTaskDelay (iot_button 回调跑在 esp_timer task, 实测 vTaskDelay 4000ms 只等了 1.1s)
    - 改用 xTaskCreate("wifi_reset") 独立 task 跑: PlaySound + vTaskDelay(4000) + esp_restart
    - 在重启前播完配网音效

  wifi_board.cc::EnterWifiConfigMode():
    - 移除之前在 BLE 配网成功后重复 PlaySound 的代码 (StartBleProvisioning 内部 Alert 已经调用过)

注: codec output_only 仍保持 NeedsProvisioning() (TX-only + ES8311-only).
  强制 duplex 会让 BLE Bluedroid gatt_init malloc failed → assert reboot loop, 已验证.

待解决 (留待 adaptation_dzbjImg_shar 分支对比):
  - 关闭 BLE 5.0 后手机可搜到设备, 但点击连接失败
  - 配网模式下 P3_LALA_WIFICONFIG 音效仍不响 (StartBleProvisioning 内 Alert 调用但 audio_loop 似乎没消费)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 12:02:44 +08:00
70f0cdd07a feat(rtc): 偶发连接失败完整修复 (A+B+C 三件套)
实测根因 (DIAG 埋点确认): 火山 RTC SDK 启动时一次性申请大量 lwIP socket fd,
默认 CONFIG_LWIP_MAX_SOCKETS=10 不够 SDK 分配, 触发 SocketConnection-Lite.c:191
bind local ip failed → ICE 协商失败 → wait connect bits=0x0 超时.

实测对比:
  修复前: 冷启动 RTC join 30+ 秒超时 × 3 次失败
  修复后: 冷启动 RTC join 1.6 秒成功, 软退出 + 唤醒重连 2.3 秒成功 

修复内容:

[A] sdkconfig: CONFIG_LWIP_MAX_SOCKETS=10 → 20
    根治 lwIP socket fd 不足. 16 是临界值, 20 留 25% 余量应对 burst 场景
    (HTTP 重试 / DNS 查询 / NTP 同步并发). 代价: +6 fd × ~200B = 1.2 KB RAM (忽略).

[B] application.h/cc + volc_rtc_protocol.h/cc: 失败 3 次后销毁 + 重建 engine
    新增 VolcRtcProtocol::ForceRebuildEngine() public 方法.
    OpenAudioChannel 连续失败 3 次时调用 (application.cc:566-573):
      - 销毁 rtc_handle_ + reset SDK 内部状态污染
      - 等待 2 秒让 lwIP 释放残留 socket fd (TIME_WAIT)
      - 触发 Phase 6 重建路径 (rtc_handle_=nullptr → Start())
    应对 A 修复后仍可能出现的 SDK 内部状态错乱 (e.g. ICE Agent 异常).
    本次实测未触发 (A 已解决主要问题), 但保留作为兜底防御.

[C] volc_rtc_protocol.cc: DIAG_RTC_BIND_ENABLE 一键开关诊断埋点
    在 join_room 前/后 + ForceRebuildEngine 前/后打印:
      - lwIP socket fd 使用量 (sockets=N/MAX)
      - heap free + psram free
      - WiFi rssi
      - 失败时的 errno + strerror
    验证完成后改 0 关闭, 编译器消除 #if 块, 零运行时开销.

文件改动:
  sdkconfig                              | LWIP_MAX_SOCKETS 10→20
  main/application.h                     | +audio_channel_retry_count_
  main/application.cc                    | +重试计数 + static_cast → ForceRebuildEngine 调用
  main/protocols/volc_rtc_protocol.h     | +ForceRebuildEngine() 声明
  main/protocols/volc_rtc_protocol.cc    | +DIAG 埋点 + diag_count_used_sockets() + ForceRebuildEngine()

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 10:23:21 +08:00
3e709577f5 docs: 更新 Claude Code 插件高效运用指南 (加 embed-ai-tool 3 个嵌入式 skill)
补充内容:
- 一、资源总览: 新增 "embed-ai-tool 嵌入式专项 Skills (3 个)" 行
- 五、自动触发表: 加 serial-monitor / rtos-debug / static-analysis 触发关键词
- 五、协同表: 加 embed-ai-tool 与插件配合的工作流
- 八、新电脑环境恢复指南:
  * 新增步骤 3.1.5 安装 embed-ai-tool 3 个 skill (git clone + cp)
  * 8.3 恢复内容清单新增 embed-ai-tool 行
  * 可选依赖工具说明 (pyserial / cppcheck / llvm)

3 个 skill 用途:
- serial-monitor: 串口抓包/日志分析 (避免手动复制粘贴日志)
- rtos-debug: FreeRTOS 任务/栈/死锁分析
- static-analysis: cppcheck/clang-tidy/MISRA-C 静态扫描

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 18:17:54 +08:00
b7ee0f9885 chore(docs): 数字人 GIF 资源更新 (ezgif 处理后 + Cubism 工程同步)
新增 Ezgif_Export/ 目录, 存放 8 张 ezgif 处理后的 GIF (240×320 + 抽帧 N=3),
这些是直接喂给在线 EAF Packer 的源文件, 跟 spiffs_image/hiyori-assets.bin 对应.

也包含 m03/m05 源 GIF 更新 (m05 从 7 MB 优化到 1.9 MB, ezgif resize) 和
Cubism 工程 hiyori_free_t03.can3 同步.

ezgif 处理参数 (实测最优):
  - Resize: 240×320 + ImageMagick + coalesce (撤销优化) + 添加透明内边距
  - Frames: Every 3 + Skip every N-th (-33% 帧数)
  - Converter: libvips (无 dithering)
  - Effects: don't stack frames ✓

EAF Packer 配置:
  - 屏幕: 240×320 矩形 + 背景色 #000000 (chroma key 必须黑色)
  - 命名: hiyori_m01 ~ hiyori_m08 (跟代码 emotion_map 对齐)
  - 循环模式: 双点循环 [0, 末帧]

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 18:17:39 +08:00
be788be251 feat(ui): 8 张 EAF 数字人完整接入 (m01~m08 全情绪映射)
数字人从原本 m06+m07 两张 EAF 扩展到 m01~m08 八张, 通过 ezgif 240×320 +
抽帧 N=3 + EAF Packer 配置, 8 张 EAF 总和压缩到 4.32 MB, SPIFFS 当前
4.94 MB 分区直接装下, 无需扩分区也不丢 OTA 升级能力.

main/dzbj/ai_chat_ui_eaf.c:
  - 新版/旧版 MMAP 自动检测 (header[8] == 0x14 → entry 32B; 0x10 → 28B),
    兼容在线 EAF Packer 两种导出格式 (FSIZE/FOFFSET 偏移自动适配).
  - find_cache_index_by_name 加 fallback: 找不到精确匹配时返回 cache[0],
    PoC 阶段单张 EAF 也能验证全部情绪触发.
  - emotion_map 22 情绪 → 8 张 EAF 重排:
    * 默认/积极组 12 → m01..m05 均分 (每张 2-3 种)
    * 思考/疲倦组 5 → m06
    * 负面/严肃组 3 → m07
    * 惊讶组 2 → m08
  - 数字人对齐 GFX_ALIGN_CENTER → GFX_ALIGN_BOTTOM_MID, 240×320 在
    360×360 圆屏贴底显示, 顶部 40px 透明露出背景图, 视觉跟之前
    360×360 全屏 EAF 一致 (脚部贴底, 字幕 z-index 上层覆盖底部 56px).

spiffs_image:
  - hiyori-assets.bin: 956 KB (m06+m07) → 4.53 MB (m01~m08 + index.json)
  - 删除原 GIF (hiyori_m{03,06,07}.gif), EAF 已替代不需要烧到设备.

实测数据 (Baji 2026-05-20):
  m01=814KB m02=516KB m03=563KB m04=559KB m05=606KB m06=423KB m07=379KB m08=566KB
  8 张 EAF 总和: 4.32 MB
  SPIFFS 占用: 4.58 MB / 4.64 MB 可用 = 1.3% 余量 (临界, 未来加资源需要规划)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 18:17:09 +08:00
c6ecdb124c feat(audio): 同步 Kapi 软件 AEC + NULL crash fix 到数字人项目
来源: Kapi commit 63b21fd (NULL fix) + a369796 (软件 AEC) 完整移植

改动:
- components/common/src/volc_rtc.c
  on_global_error 加 message ? message : "(null)" 防御.
  解决: idle ≥10 分钟后服务端 session 超时 / NAT 表过期触发的
       printf("%s", NULL) → strlen panic → 设备重启.

- main/CMakeLists.txt
  REQUIRES 末尾加 esp-sr (提供 esp_aec.h 同步 API).

- main/application.h
  +7 个 AEC 成员 + 5 个函数声明 (recorder/player pipeline 后).

- main/application.cc
  +InitAec / DeinitAec / AppendRefSamples / GetDelayedRef / ApplyAEC (~170 行).
  OnAudioOutput 三个 codec output 位置都加 AppendRefSamples hook.
  ReadAudio 两条路径 (recorder_pipeline + codec 直读) 加 lazy InitAec
  + ApplyAEC + target_samples = max(samples, chunk_size).
  PHASE8_DIAG_ENABLE 默认 0 (诊断埋点已完成根因定位, 关闭减少日志噪声).

实测效果 (30 分钟设备验证):
  AI 说话:    mic=285 ref=8310 clean=31 (消除 89%)
  AI 说话:    mic=660 ref=7489 clean=57 (消除 91%)
  AI 说话:    mic=156 ref=2748 clean=0  (消除 100%)
  用户说话:   mic=224 ref=8 clean=224   (passthrough 正常)

资源占用:
  Flash       +59 KB (+1.2%)
  Internal SRAM  +35-50 KB (+10-15%)
  PSRAM       +10-15 KB (<0.2%)
  Core 1 CPU  +6-12% @240MHz

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 16:54:55 +08:00
22b7a70d7d fix: 同步 Kapi 软 RTC 退出五连修到数字人项目(待命音 + 欢迎语杂音)
从 Kapi commit b1577d8 / a3a476f 完整移植 5 个修复,覆盖三类问题:
1. 开机/唤醒后按 BOOT 进 RTC 房间,欢迎语前 1-3 秒杂音
2. 软 RTC 退出(41s 无对话触发 Dialog watchdog)后待命音"卡卡正在待命"无声/杂音/被截
3. 软退出后按 BOOT 唤醒,欢迎语前杂音

【修复 1】OnAudioChannelOpened EnableOutput(true) 后立刻灌 200ms silence
  - 防止 I2S DMA 启动后到 RTC 真实 PCM 到达 1-3s 空窗的杂音

【修复 2】LeaveRoom 加 notify_closed 参数(默认 true 不变老路径)
  - hibernate 路径传 false 跳过 on_audio_channel_closed_ 回调
  - 避免回调链 player_pipeline_close → EnableOutput(false) 误关 codec
    导致待命音无声

【修复 3】LeaveRoom 不再 volc_rtc_destroy, 保留 rtc_handle_
  - 唤醒时 OpenAudioChannel 直接 volc_rtc_start 复用 handle, 不死循环
  - 服务端 AI 任务无需 destroy 也会按 180s 兜底机制清理

【修复 4 - 最隐蔽】LeaveRoom 末尾重置 downlink_is_pcm_ = false
  - 火山 RTC 下行是 PCM, DataCallback 设 downlink_is_pcm_=true
  - 不重置 → PlaySound 的 Opus 包被 OnAudioOutput 当成 raw PCM 字节流
    直接写 codec → 杂音而非待命音
  - 唤醒重连后 DataCallback 收下一包会自动重置, 不影响欢迎语

【修复 5】OnAudioInput 入口加 hibernating_ guard
  - hibernate 期间禁用输入侧, 防止访问关闭的 codec → std::bad_alloc abort
  - 不冻结 OnAudioOutput, 让待命音队列正常被消费

【EnterIdleHibernate 重写】套用 Kapi 新顺序:
  Step 0: hibernating_=true + 50ms (让 OnAudioInput guard 生效)
  Step 1: LeaveRoom(false) (codec output 保留)
  Step 2: background_task->WaitForCompletion
  Step 3: 清空 audio_decode_queue_
  Step 4: EnableInput(false) + close recorder_pipeline
  Step 5: 强制 esp_pm 禁用 Light Sleep
  Step 5.5: EnableOutput(false→true) + 200ms silence (清 LeaveRoom 副作用)
  Step 6: SetDeviceState(idle) → PlaySound 待命音
  Step 7: WaitForAudioPlayback (队列消费完毕)
  Step 7.5: background_task->WaitForCompletion + vTaskDelay(1000)
            (DMA + ES8311 FIFO + 功放尾音衰减, 防尾音截断)
  Step 8: player_pipeline_close
  Step 9: NVS idle_cycles_++
  Step 10: 显示字幕"已自动退出RTC对话..."(数字人特有, 保留)

【WakeFromHibernate】调整 hibernating_=false 顺序
  - 先放下 hibernating_, 让 ToggleChatState 期间 OnAudioInput guard 通过
  - 否则 ToggleChatState 期间音频上行迟迟不开

编译: kapi.bin 0x41c000 (4.21MB), 分区 25% 空闲。
实测三项全通: 欢迎语干净 + 待命音清晰完整 + 唤醒欢迎语干净。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 10:11:36 +08:00
eceadda807 feat(ui): Phase 10 step 1+2 - 背景图 + 中文字幕 + 数字人透明
完成数字人模式 UI 的"背景图叠加 + 实时字幕"功能。所有改动基于 EAF
框架(Phase 10 commit 31982ba),保持 0 个 lv_* UI 函数链接进固件。

Step 1: JPG 背景图叠加
- ai_chat_ui_eaf.c 加 esp_jpeg 解码 Background_360x360.jpg →
  RGB565 buffer (252KB PSRAM) → gfx_img_create 作为底层
- z-index 通过创建顺序控制: 背景 → 数字人 anim → 字幕 label
- 选项 A 保留 JPG (~20KB SPIFFS) 比选项 B (252KB .bin) 省 232KB

数字人透明: esp_emote_gfx local patch (gfx_anim.c::gfx_anim_render_24bit_pixels)
- 根因: 在线 EAF Packer 默认导出 24-bit 模式,工具不暴露 bit_depth
  选项,alpha 滑块拉到 0 无法保存,导致 GIF 透明像素被烘焙成屏幕背景
  色 (黑色 RGB888 #000000)
- 解决: 在 24-bit 渲染函数加 chroma key,跳过近黑像素让背景图露出
- 阈值演化 v1 (0x0000) → v3 (16) → v4 (24),最终 RGB888 ≤ (24,24,24)
- 保留 R/G/B AND 关系(三分量都小才透明),保护数字人本体暗色不破洞
- 双字节序判定,兼容 disp_config_t.flags.swap = true

Step 2: 中文字幕 (gfx_label + LVGL bitmap font 方案 A)
- 字体方案对比 3 方案后选方案 A(C 数组 XIP from Flash):
  • A: 1.4MB Flash + 0 RAM (推荐)
  • B: xiaozhi-fonts .bin 1.18MB SPIFFS + 1.18MB PSRAM
  • C: 自转 .bin ~2.8MB 总占用
- extern const lv_font_t font_puhui_20_4 → gfx_label_set_font 直接喂
- linker 副作用: 仅引入 7 个 LVGL 函数 ~2.2KB(lv_font_get_bitmap_fmt_txt
  / lv_mem_* 幽灵符号),无 lv_obj/lv_disp/lv_indev 等 UI 框架函数
- 字幕参数: 300×56 (2 行限制) + 行间距 4 + 贴底 y_ofs=-4
- GFX_LABEL_LONG_WRAP 字符级断行(中文友好),CENTER 居中
- 流式 TTS 节流 50ms(比 LVGL 100ms 短,EAF 渲染更快)

工具脚本 (tools/patch_eaf_transparency.py)
- 探索性脚本:解析 hiyori-assets.bin 尝试修补 EAF palette alpha
- 实际未生效(工具导出 24-bit 无 palette),保留作为 EAF bin layout
  解析参考

固件大小: 2.75MB → 4.30MB(+1.55MB = 字体 1.4MB + 字幕代码 + 背景图代码)
分区余量: 50% → 25% (1.42MB 空闲,安全)

完整踩坑经验已沉淀到 ~/.claude/CLAUDE.md §13 + 项目 memory。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 17:38:31 +08:00
4de9f2ba61 feat(assets): 添加 hiyori-assets.bin(Phase 10 EAF 数字人资源包)
ESP Emote GFX Packer 在线工具(emote-gfx-gen-tool-dev.pages.dev)
打包产物,含:
- index.json (286 B): 表情元数据
- hiyori_m06.eaf (505 KB): 默认/积极情绪动画
- hiyori_m07.eaf (450 KB): 思考/疲倦情绪动画
  (m03 待补,sad/angry 暂用 m07 fallback)

sdkconfig 同步: 新 esp_emote_gfx Kconfig 选项的默认值
(HEATSHRINK + JPEG decode + tri-edge AA 等)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 15:56:33 +08:00
0d0bc33192 chore(deps): managed_components 自动更新
idf.py reconfigure 触发的 idf-component-manager 版本刷新副作用:
- espressif/dl_fft, esp_jpeg, freetype 等组件的小版本更新

不影响项目功能,与 Phase 10 无直接关系。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 15:56:33 +08:00
86200f5e3a chore(deps): 添加 esp_emote_gfx + esp_mmap_assets 组件(Phase 10 依赖)
Phase 10 数字人模式 LVGL → EAF 切换所需的两个新组件:
- espressif2022/esp_emote_gfx v3.0.5
  轻量软件渲染 UI 框架(gfx_emote_init/gfx_disp_add/gfx_anim/gfx_label)
- espressif/esp_mmap_assets v2.0.0
  资源打包加载(虽然 use_fs 模式 buggy,我们绕过它直接 fopen,
  但保留组件以便后续 mmap partition 模式启用)

gfx_touch.c 含我们的 local shim 兼容 esp_lcd_touch v1.1.2 旧 API
(已在前一个 commit 31982ba 中说明)。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 15:56:32 +08:00
31982ba7b9 feat(ui): Phase 10 - 数字人模式 LVGL → esp_emote_gfx 完整切换
 验证完成:
- 音频卡顿明显改善(用户实测)
- 数字人 hiyori 动画正常显示
- nm 验证:固件中 0 个 lv_*/lvgl_* 函数符号
- kapi.bin: 4.7MB → 2.75MB(-42%)

关键改动:
- main/dzbj/ai_chat_ui_eaf.c (404 行新增):
  完全替代 LVGL 版 ai_chat_ui.c,提供同名 C API(ai_chat_screen_init
  / set_status / set_emotion / set_chat_message / resume_animation)。
  AiChatDisplay C++ 桥接层无需改动。
  内部用 gfx_emote_init + gfx_disp_add + gfx_anim + mmap_assets。
- main/CMakeLists.txt:双轨编译
  CONFIG_BAJI_BADGE_MODE=y → ai_chat_ui.c (LVGL) + bg_gif_demo.c
  CONFIG_BAJI_BADGE_MODE=n → ai_chat_ui_eaf.c (esp_emote_gfx)
- main/dzbj/dzbj_init.c:EAF 模式跳过 lvgl_lcd_init() 调用
- main/dzbj/lcd.c/h:暴露 lcd_io_handle 给 EAF 注册 IO 完成回调

踩坑修复(commit message 留档供后续参考):
1. esp_mmap_assets v2.0.0 在 use_fs=true 模式下 mmap_assets_get_mem()
   返回的是文件内偏移量而非 RAM 指针(fseek bug + offset 没加
   data_section_start),导致 LoadProhibited panic。
   解决:完全绕过 mmap_assets,自己 fopen + 解析 MMAP bin 头
   (layout: 头 16B + 每 entry 28B + data 段每文件 2B magic + 数据)。
2. esp_emote_gfx 期望 esp_lcd_touch v2.x 新 API,项目用 v1.1.2 旧 API。
   在 managed_components 内 gfx_touch.c 加 shim 桥接(local patch,
   reconfigure 后需 reapply)。
3. EAF format magic 是 0x89 'EAF'(gfx_eaf_dec.h),不是 0x5A5A
   (那是 esp_mmap_assets 内部文件分隔符)。
4. SPIFFS 需要在 ai_chat_screen_init 入口自动挂载(不能依赖
   bg_gif_demo 的惰性挂载,那个已被 CONFIG 排除)。

依赖增量:
- espressif2022/esp_emote_gfx: ~3.0.5
- espressif/esp_mmap_assets: * (仅用于声明依赖,运行时被绕过)

数字人模式核心 UI 范围:
- 显示数字人动画  (hiyori_m06/m07, 居中循环)
- 情绪 → GIF 映射  (23 情绪 → 2 EAF,sad/angry 暂用 m07,m03 待补)
- 字幕/状态文字: stub (字体接驳留待后续,需打包 .bin 字体到资源)
- 触摸: 不支持(PoC 阶段不需要)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 15:53:21 +08:00
3a1111e99d plan(rtc-only): Phase 9 取消 + Phase 10/11/12 规划(LVGL → esp_emote_gfx)
Phase 9 三轮增量优化(jitter buffer / codec init / Core 1 绑定)效果不
明显,用户决策完整切 EAF 验证 GIF 抢资源假设。

Phase 9 → CANCELLED:
- v1 jitter buffer device_state 判错(漏 kDeviceStateDialog)
- v1 ES7210 重试破坏 ES8311 init 导致开机播报无声
- v2 修正 device_state 后 jitter 工作但仍卡
- v3 background_task 绑 Core 1 + DIAG-5 未硬件验证
- 所有代码改动 git restore 回滚(无 commit),Phase 8 DIAG 埋点保留
- CANCELLED.md 记录教训

Phase 10 新增(数字人模式 LVGL → esp_emote_gfx 完整切换):
- 添加 espressif2022/esp_emote_gfx ~3.0.5 依赖(已 reconfigure 拉取)
- API 风险扫清:GFX_LABEL_LONG_WRAP 支持中文换行、
  gfx_font_lv_load_from_binary 兼容 LVGL bitmap font
- 双轨编译:CONFIG_BAJI_BADGE_MODE=y 保 LVGL,=n 走 EAF
- PLAN.md 含 10 个子任务从依赖到完整 UI 切换
- 预估 3-5 天

Phase 11 占位:LVGL 释放的 ~40KB DRAM + ~80KB PSRAM 投到 WiFi
缓冲扩容(STATIC_RX 10→16、DYN_RX/TX 32→48、RX_BA_WIN 6→16)+
Opus/RTC SDK jitter buffer 扩容

Phase 12 占位:原 Phase 10 集成测试 + 推送,重编号

ROADMAP 同步更新,依赖关系矫正。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 13:37:34 +08:00
3dc6cadf49 diag(rtc-only): Phase 8 - 音频卡顿根因诊断埋点 + 数据采集报告
数字人 RTC 模式音频卡顿根因定位。通过 4 类 ESP_LOGW 埋点采集运行时
数据,对照表格判定根因,输出 Phase 9 实施分支决策。

埋点实现(main/application.cc,PHASE8_DIAG_ENABLE 宏开关,关闭后零开销):
- DIAG-1 queue 深度:3 处(出队 + WebSocket 入队 + RTC 入队),50ms 节流
- DIAG-2 codec->OutputData 写入耗时:>15ms 阈值告警
- DIAG-3 WiFi RSSI:OnClockTimer 1Hz
- DIAG-4 heap 快照 + 碎片率:OnClockTimer 1Hz

实测结论(见 DIAG_REPORT.md):用户感知卡顿 = 两个独立根因
- A. 开机播报阶段 ③' codec init 时序缺陷(ES7210 I2C 失败 +
  126 次 write_slow 集中在 2-13s)
- B. RTC 对话阶段 ⑤ Opus/WebSocket 应用层帧到达抖动
  (queue 突发堆积 19 + queue=0 出现 58 次,但 codec 写入 0 次 slow)

完全排除:① CPU 争抢、② PSRAM 带宽、④ WiFi 丢包(RSSI -24~-33dBm
极强)、⑥ 内存碎片(heap 全程稳定)

Phase 9 推荐分支 B'(双线修复,原 A/C 的 EAF 方案不适用):
- 9.1 应用层 jitter buffer(fill-threshold + drain)—— 解 B
- 9.2 开机 codec init 时序修复(ES7210 reset + ready 等待)—— 解 A
- 估时 1 天

ROADMAP 同步:Phase 7 矫正为 battery_psm(实际状态)、Phase 8 新增
诊断、Phase 9 占位待 Phase 8 决策、原"集成测试"挪到 Phase 10。
新增 .planning/STATE.md 记录 roadmap evolution。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 11:40:42 +08:00
4b3206dca3 docs(rtc-only): 对比报告 v2.1-v2.4 — 卡顿真凶定位 + 触顶诊断 + P4 升级路径
v2.1 §11.10 真凶定位:
- 通过 Baji+Kapi 同 Wi-Fi 对比日志, 证明卡顿不是网络问题
- Baji WiFi 缓冲 8/10 vs Kapi 16/16, 加上 LVGL/GIF 抢 PSRAM 总线
- 同 BSSID 同信号下 Baji reor=1790, Kapi reor=0~226 (8 倍差距)
- 新增 Phase 7.8/7.9 修复方向

v2.2 §11.11 GIF 4 方案评估:
- 方案 A (GIF EMBED 转 C 数组)  否决: 占 +2MB 爆 OTA 分区, PSRAM 带宽不变
- 方案 B (通话期暂停 GIF)  强推荐: 20 行 / 0 内存代价 / reor 1800→500
- 方案 C (RGB565 帧序列)  否决: Flash 不够
- 方案 D (PNG 序列 + lv_anim) ⚠️ 复杂可考虑
- 新增 Phase 7.10/7.11

v2.3 §11.12 硬件触顶诊断:
- 用户最终需求(说话期 GIF + 触摸放大渐变 + RTC)超 ESP32-S3 极限
- 全并发 PSRAM 带宽需求 40MB/s, 接近物理极限 60MB/s 的 67%
- 给出 3 条路径: A 分模式策略 / B 极限优化 / C 换 ESP32-P4
- 新增 Phase 7.12 + Phase 8

v2.4 §11.13 ESP32-P4 完整需求容量评估:
- 几十套 GIF Flash 占用: 30 套 21MB / 100 套 70MB → P4 需 32/64MB
- PSRAM 32MB 按需 LRU 缓存, 同时驻留 10 套 GIF 仅占 5MB
- PPA 硬件 2D 加速: 触摸缩放从 30% CPU + 30MB/s PSRAM 降到零 CPU
- 外挂 ESP32-C6: WiFi 6 / BLE 5.3 (比 S3 WiFi 4 抗丢包更强)
- BOM 升级 ¥20→¥53/台 (+¥30), 开发周期 3-6 个月
- 结论: 长期产品强烈建议直接立项 P4 v2, S3 v1 作快速上市验证
- Phase 8 细化为 8.1-8.6 子任务

文档累计 1335 行, 完整覆盖"S3 优化探索 → 触顶诊断 → P4 升级"决策链。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 16:51:34 +08:00
244f28a0ab docs(rtc-only): 新增官方 Korvo-2 方案全面对比分析报告
基于 5 方对照(ES7210 datasheet + 电子吧唧原理图 + Korvo-2 V3.1.2 原理图 +
官方 pipeline.c + 当前 box_audio_codec.cc)输出 826 行分析报告:

- §一~五: 音频管线/RTC 编解码/任务线程/本地音频兼容性/抗抖动机制对比
- §六/补充·一~五: 麦克风收音根因分析 + 硬件 100% 等效证据 + GPIO 全对照 +
  ES7210 + AEC 完整启用方案(方案 D 2mic+1ref / 方案 E 1mic+1ref)
- §七~十: 实施路径/风险/执行建议
- §十一 决策记录: 基于 internal SRAM 仅剩 44KB 实测 + 服务端已有
  VAD/打断/AI 降噪 → 暂不实施方案 E,先走低风险高收益路径(服务端调优 +
  mic gain 27→33dB + Phase 7.5 网络优化)
- §11.9 交叉引用 Kapi_Rtc_toy 项目,指出 Kapi 资源更宽松更适合 AFE 实验
- §十二 版本历史 v1 → v2.0

关键结论:
- 你的板子 = 官方 Korvo-2 精简版(AEC 电路元件值 100% 相同, 音频 GPIO 100% 一致)
- 麦克风收音不准 95% 是网络/物理/服务端原因, 仅 5% 是缺设备端 AEC
- ESP-ADF 重构不推荐(工作量 3-4 周, 等同重写整个项目)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 14:37:05 +08:00
4b7b1949d4 perf(rtc-only): Phase 6 收尾 - 卡顿优化 + PowerSaveTimer 守卫 + 开机加速
代码改动:
- AudioLoop 加 vTaskDelay(1),让出 Core 1 idle task 防 WiFi/RTC 饥饿
- BackgroundTask 优先级 2 → 5,提升 Opus 解码实时性
- LVGL 刷新 5ms → 16ms (60Hz),CPU 占用降 60%
- GIF 定时器 20ms → 33ms (3 处),PSRAM 流量减半
- AI 字幕推送 100ms 节流,避免 LVGL 锁争抢
- EnterIdleHibernate 清空 audio_decode_queue_,防 standby_sound 残留误触发首帧
- PowerSaveTimer OnEnterSleepMode 加 device_state 守卫,拦截 dialog/connecting
  期间关功放(修复欢迎语期间被静音 bug)
- 取消开机 ADC 阻塞采样,开机播报响应从 6 秒缩到 < 3 秒

新增规划:
- Phase 7 占位文档:电量保护 + PowerSaveTimer 重构 + 唤醒杂音根治 + RTC 抖动缓解

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 11:38:48 +08:00
b8a5fe958f feat(rtc-only): Phase 6 - RTC 空闲软休眠(B+C 双源 + 真退房 + 字幕提示 + 内存兜底)
按 GSD 框架 .planning/milestones/digital_human_rtc/phases/phase_06_idle_hibernate/
规划完成 Phase 6 软退出 RTC 机制。替代旧的"40s 硬重启退出"方案。

## 核心变更

### 1. 倒计时刷新(B+C 双源方案)

| 方案 | 监听源 | 实施位置 | 状态 |
|------|--------|---------|------|
| A 扬声器流 | I2S/PCM 输出 | application.cc audio output 3 处 | **宏关闭**(PHASE6_ENABLE_AUDIO_FALLBACK) |
| **B 字幕监听** | RTC subtitle 消息 | application.cc:1300 subtitle 分支 | **启用** |
| **C 智能体状态** | RTC conv_status 消息 | application.cc:1260 conv_status 分支 | **启用** |

复用现有 DIALOG_IDLE_COUNTDOWN_SECONDS=40 不新增常量。

### 2. 真退出 RTC 房间(释放 License)

- 新增 Protocol 基类虚函数 LeaveRoom(默认回退到 CloseAudioChannel)
- VolcRtcProtocol::LeaveRoom 覆写:volc_rtc_stop + volc_rtc_destroy
  - 火山官方文档明确:真退房必须 leaveRoom + destroyRTCEngine
  - CloseAudioChannel 只 stop 不够(真人仍在房间继续计费)
- 服务端 AI 任务在 180s 内自动清理(火山平台机制)

### 3. EnterIdleHibernate / WakeFromHibernate

EnterIdleHibernate 流程(严格顺序):
1. protocol_->LeaveRoom()                  # 真退房
2. codec->EnableInput/Output(false)        # 重置 codec 状态机
3. recorder_pipeline_close()
4. hibernating_.store(true)                # 关键:先设标志阻止 PowerSaveTimer
5. esp_pm_configure(light_sleep=false)     # 双保险禁用 Light Sleep
6. SetDeviceState(kDeviceStateIdle)
7. idle_cycles_++ + NVS 持久化
8. 字幕"已自动退出RTC对话,按BOOT键重新连接RTC"(5 次重试间隔 200ms)

WakeFromHibernate 流程:
1. 检查 idle_cycles_ >= 50 → 硬重启清理碎片(兜底)
2. 清空字幕
3. ToggleChatState → OpenAudioChannel → 自动重建 rtc_handle_
4. RTC 重新加入房间(实测 2-3s 完成)

### 4. CanEnterSleepMode 加 hibernating 检查

防止 hibernate 期间 PowerSaveTimer 触发 esp_pm_configure(light_sleep=true)
导致 I2C 总线进入低功耗 → 唤醒后 ES7210/ES8311 通信失败 abort。

### 5. Dialog Watchdog 触发动作改造

旧:esp_restart() 整机重启(黑屏 15-25s + WiFi 重连)
新:Schedule(EnterIdleHibernate) 软退房(不熄屏 + 字幕提示)

### 6. BOOT 唤醒走 WakeFromHibernate 路径

iot_button 回调中检测 IsHibernating(),派发到独立 task 执行
WakeFromHibernate(避免阻塞 esp_timer 任务,CLAUDE.md 经验)。

### 7. OpenAudioChannel 适配重建

LeaveRoom 销毁 rtc_handle_ 后,OpenAudioChannel 头部检测 NULL
触发 Start() 异步重建,轮询 5s 等待就绪。NVS 缓存 device_secret
所以重建通常 100ms 完成。

## 实测验证(用户协作)

| 阶段 | 时间 |
|------|------|
| 40s 触发软休眠 |  |
| LeaveRoom 真退房 |  "✓ 已真退出 RTC 房间(leaveRoom + destroyRTCEngine)" |
| 屏幕保持 + 字幕显示 |  "已自动退出RTC对话,按BOOT键重新连接RTC" |
| BOOT 按键唤醒 |  |
| RTC 实例重建 |  100ms |
| RTC 重新加入房间 |  2-3s |
| 连续 2 次软休眠+唤醒 |  无 abort/I2C 失败 |
| 时间对比 | 旧硬重启 15-25s → 软休眠 3-5s(省 80%) |

## 6 个关键踩坑修复(详见 HIBERNATE_REPORT.md)

1. codec 状态机未重置 → 唤醒后 I2C abort
2. PowerSaveTimer Light Sleep 干扰 I2C 总线
3. hibernating_ 设置时序错误
4. dynamic_cast 在 -fno-rtti 下编译失败 → 改基类虚函数
5. LeaveRoom 后 OpenAudioChannel 直接失败 → 加重建逻辑
6. 字幕 LVGL 锁竞争 → 推迟到最后 + 5 次重试

## 文档产出(同时提交)

- .planning/.../phase_06_idle_hibernate/PLAN.md(含实施变更记录 V1-V6)
- .planning/.../phase_06_idle_hibernate/HIBERNATE_REPORT.md(验证报告)
- .planning/.../ROADMAP.md(Phase 1-5  + Phase 6 进行中状态更新)
- docs/Rtc_AIavatar/数字人表情渲染方案_云端预渲染+BLE+OTA.md
  新增第 19 章 RTC 空闲倒计时方案选型与软退出(9 小节)
- docs/Rtc_AIavatar/RTC软退出方案_移植参考.md
  完整移植参考(10 章 + 3 附录,可移植到其他火山 RTC 项目)
- docs/Rtc_AIavatar/音频卡顿_全局资源分析.md
  全局资源分析 + 13 项优化建议(不改代码)
2026-05-13 17:28:36 +08:00
f2be9922b6 feat(rtc-only): Phase 5 - RTC 字幕显示恢复(透明背景 + 2 行黑字 + 锁优化)
按 GSD 框架 .planning/milestones/digital_human_rtc/phases/phase_05_subtitle_restore/
规划完成 Phase 5 字幕显示恢复。

## 核心变更(main/dzbj/ai_chat_ui.c)

### 1. chat_container 重构

- 新增 static chat_container 变量(lv_obj 父容器)
- 尺寸 320×56(= 2 行字 + padding 4px*2)
- 位置 LV_ALIGN_BOTTOM_MID 距底 10px
- 完全透明背景(LV_OPA_TRANSP),无灰底
- 初始 HIDDEN,有内容时显示

### 2. chat_label 改造

- 黑色文本(用户反馈白字在浅色背景上不清晰)
- 尺寸 312×48 限制最多 2 行
- LV_LABEL_LONG_WRAP → LV_LABEL_LONG_DOT,超出 2 行自动 ... 截断
- font_puhui_20_4 中文字体不变

### 3. ai_chat_set_chat_message() 实现

原为空函数(PoC 期间 return),本 Phase 完整实现:
- 锁外去重:static last_content[256],相同内容直接返回
- lvgl_port_lock 200ms → 500ms(GIF 解码繁忙时给予更长等待)
- 内容空时隐藏容器,非空显示
- 成功更新后缓存 last_content

### 4. z-index 修复

bg_gif_demo_start() 后立即 lv_obj_move_foreground(chat_container)
否则 bg_img/gif_obj 后于 chat_container 创建会遮挡字幕

## 实测验证(用户协作)

60s 对话期间:
-  AI 字幕完整推送 3 次(含 54 字符长字幕)
-  LVGL 锁超时 14 次 → 0 次(锁外去重生效)
-  表情切换 + 字幕同步工作
-  长字幕自动 2 行截断
-  无 abort/重启

## 调用链(已对接 application.cc 现有逻辑,无需改协议层)

RTC 字幕 → display->SetChatMessage(role, msg)
  → AiChatDisplay::SetChatMessage
  → ai_chat_set_chat_message() ← 本 Phase 实现

## GSD 文档

- PLAN.md
- SUBTITLE_REPORT.md(含锁优化对比 + 布局规划 + 用户决策记录)
2026-05-13 13:35:41 +08:00
497c1b4654 feat(rtc-only): Phase 4 - 情绪标签 → 数字人 hiyori GIF 映射 + 切换接口
按 GSD 框架 .planning/milestones/digital_human_rtc/phases/phase_04_emotion_mapping/
规划完成 Phase 4 情绪映射 + 运行时切换。

## 核心变更

### bg_gif_demo 新增运行时切换接口

```c
esp_err_t bg_gif_demo_switch_gif(const char *new_gif_path);
```

实现要点:
- 先 heap_caps_free(g_gif_data) 释放旧 PSRAM,再加载新 GIF,单峰仅一份
- 内部 static last_gif_path 去重(相同路径直接返回 ESP_OK)
- 切换后立即 lv_timer_set_period(timer, 20) 防止 lv_gif_set_src 重建为默认 10ms
- LVGL 锁保护 200ms 超时

### 22 情绪 → 3 hiyori GIF 映射表

定义在 main/dzbj/ai_chat_ui.c USE_BG_GIF_POC 包裹内:

| GIF | 情绪标签 (个数) |
|-----|----------------|
| m06 (默认/积极) | neutral, happy, laughing, funny, loving, relaxed, delicious, kissy, confident, silly, blink, curious (12) |
| m07 (思考/疲倦) | sleepy, thinking, confused, embarrassed, dizzy (5) |
| m03 (负面/严肃) | sad, crying, angry, surprised, shocked (5) |

22 个标准情绪 100% 覆盖,未映射的情绪默认 fallback 到 m06。

### ai_chat_set_emotion 改造

PoC 模式下优先切换数字人大图(不再切隐藏的 emoji 200x89):

```c
#ifdef USE_BG_GIF_POC
if (bg_gif_demo_is_running()) {
    const char *path = find_hiyori_gif(emotion);
    bg_gif_demo_switch_gif(path);
    return;
}
#endif
// 非 PoC 模式 fallback emoji 路径保留
```

## 调用链(已与现有 RTC 字幕解析对接,无需改 application.cc)

RTC 字幕 → application.cc:1419 display->SetEmotion(emo)
  → AiChatDisplay::SetEmotion (display/ai_chat_display.cc)
  → ai_chat_set_emotion (dzbj/ai_chat_ui.c)
  → bg_gif_demo_switch_gif (PoC 模式)
  → 数字人 hiyori GIF 切换

## 烧录验证(用户实测)

监控 60s 期间捕获情绪切换:
- neutral / happy → m06(已在播,去重)
- thinking → m07 切换成功 (590ms 延迟)
- confused → m07 去重跳过
- AI 回复结束 → 自动回到 neutral
- 无 abort / 重启

m06 ↔ m07 切换屏幕可视确认,m03 走相同代码路径无需重复测试。

## GSD 文档(同时提交)

- .planning/milestones/digital_human_rtc/phases/phase_04_emotion_mapping/PLAN.md
- .planning/milestones/digital_human_rtc/phases/phase_04_emotion_mapping/EMOTION_REPORT.md
2026-05-13 11:59:38 +08:00
7d1c7dc1f0 feat(rtc-only): Phase 3 - 数字人 GIF 资源准备(hiyori m03/m06/m07,209x360)
按 GSD 框架 .planning/milestones/digital_human_rtc/phases/phase_03_gif_resources/
规划完成 Phase 3 数字人表情 GIF 资源处理。

## 处理方式(与 PoC 阶段 hiyori_m05.gif 一致)

```bash
gifsicle --resize _x360 -O3 input.gif -o output.gif
```

- 高度 = LCD 360px,宽度按原比例自动算 → 209px
- 不裁剪(保持源 GIF 完整人物)
- 不加 --lossy / --colors(保留 256 色,画质优先)
- 只用 -O3 优化文件大小

## 处理结果

| GIF | 用途 | 源 | 处理后 | 节省 |
|-----|------|-----|--------|------|
| m03 | 负面/严肃 | 407×700 3.3MB | 209×360 1.15MB | 66% |
| m06 | 默认/积极 | 407×700 1.3MB | 209×360 0.44MB | 66% |
| m07 | 思考/疲倦 | 407×700 1.2MB | 209×360 0.40MB | 66% |
| 合计 | — | 5.7MB | 1.94MB | 66% |

## 决策过程(避免后续重复犯错)

Phase 3 初稿曾尝试裁剪到 240×320 + PIL 全帧 bbox 居中裁剪,
用户烧录后反馈"视觉感官不好"——角色被横向压扁(240×320 纵横比 0.75
vs 源 407×700 纵横比 0.583)。回归 PoC 等比例缩放方式后效果与 PoC 一致。

PoC 处理标准已写入用户级 feedback memory(feedback_hiyori_gif_processing.md),
后续 hiyori GIF 处理一律用本方式,除非用户主动要求修改。

## 显示效果(用户已目视确认)

LCD 360×360 居中显示 209×360 GIF:
- 垂直方向: 360 = 360,完全充满
- 横向: 209 < 360,左右各 75.5px 留边显示背景图
- 角色比例: 完整保留源 GIF 的 407:700 = 0.582 纵横比,人物细高自然

## 删除项

- spiffs_image/hiyori_m05.gif (2.3MB) 已删除 - 被 m06/m07/m03 替代
  文件历史保留在 git,可通过 git show eb96130:spiffs_image/hiyori_m05.gif 恢复

## 默认表情切换

main/dzbj/ai_chat_ui.c:234:
- PoC: bg_gif_demo_start(..., "/spiflash/hiyori_m05.gif")
- Phase 3: bg_gif_demo_start(..., "/spiflash/hiyori_m06.gif")

## 烧录运行时验证

- 烧录后 0 次重启(连续监控 18 秒)
- BG_GIF: GIF 已加载到 PSRAM: /spiflash/hiyori_m06.gif (441.8 KB)
- AudioCodec: Audio codec started(首次冷启动直接成功)
- 用户目视确认显示效果良好

## GSD 文档(同时提交)

- .planning/milestones/digital_human_rtc/phases/phase_03_gif_resources/PLAN.md
- .planning/milestones/digital_human_rtc/phases/phase_03_gif_resources/GIF_REPORT.md

## SPIFFS 容量

新 SPIFFS 4.94MB 当前实际占用 ~2MB(40%),余量 ~2.94MB 充足。
2026-05-13 11:42:30 +08:00
ce7a3aad63 feat(rtc-only): Phase 2 - 16MB Flash 分区调整(OTA 5.5MB + SPIFFS 4.9MB)
按 GSD 框架 .planning/milestones/digital_human_rtc/phases/phase_02_partition_resize/
规划完成 Phase 2 分区表调整,为 Phase 3 数字人 GIF 资源扩容铺路。

## 分区表变更

| 分区 | 旧 | 新 | 变化 |
|------|-----|-----|------|
| model | 0x10000 / 64KB | — 已移除 | -64KB |
| ota_0 | 0x20000 / 6.5MB | 0x10000 / 5.5MB | -1MB |
| ota_1 | 0x6A0000 / 6.5MB | 0x590000 / 5.5MB | -1MB |
| storage | 0xD20000 / 2.875MB | 0xB10000 / 4.9375MB | +2.0625MB |

合计:16MB(不变),SPIFFS 净扩容 2.0625MB

## 决策依据

- 当前 =n 固件 4.63MB,5.5MB ota 留 19% 成长 buffer
- SPIFFS 4.9MB 为 3 个 hiyori GIF + 背景图扩容(Phase 3 预算 ~3.9MB)
- 移除未使用的 64KB model 分区
- NVS 分区位置/大小不变,WiFi 凭据等数据完整保留

## 验收结果(详见 PARTITION_REPORT.md)

- =n 编译: kapi.bin 4.63MB ≤ 5.5MB ota_0
- =y 双模式编译:链接通过但 6.05MB > 5.5MB ota(**预期降级**,
  数字人 RTC 单一形态只用 =n 固件,Phase 3 资源精简后或可恢复)
- 烧录运行时验证:
   Running partition: ota_0
   NVS 数据完整保留(WiFi 凭据/设备配置)
   SPIFFS 自动挂载,hiyori_m05.gif 加载成功
   AI 对话模式启动,Codec 工作,WiFi 连接,RTC WebSocket 协议初始化
   free_heap=5.2MB(无 regression)

## GSD 文档(同时提交)

- .planning/milestones/digital_human_rtc/phases/phase_02_partition_resize/PLAN.md
- .planning/milestones/digital_human_rtc/phases/phase_02_partition_resize/PARTITION_REPORT.md
2026-05-13 11:00:35 +08:00
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
eb96130fc9 feat(Rtc_AIavatar): 数字人透明 GIF 显示方案 PoC 完成(背景图+透明GIF叠加)
源代码变更:
- main/dzbj/bg_gif_demo.c/h: 方案 C 最终实现 - JPG 背景图(lv_img) + 透明 GIF(lv_gif) 叠加
- main/dzbj/dual_gif_demo.c/h: 方案 B 中间产物 - 双 GIF 循环切换
- main/dzbj/sprite_demo.c/h: 方案 A 已弃用 - DMA 直写 GRAM 与 LVGL 争抢 LCD IO 失败
- main/dzbj/ai_chat_ui.c: 集成 USE_BG_GIF_POC 开关,加载背景图+透明 GIF
- main/dzbj/lcd.c: panel_handle 移除 static,便于其他模块访问
- main/CMakeLists.txt: 新增 3 个 dzbj 模块编译

资源新增:
- spiffs_image/Background_360x360.jpg: 设备背景图(20KB)
- spiffs_image/hiyori_m05.gif: Cubism Editor 直接导出的透明 GIF(2.3MB)
- docs/Rtc_AIavatar/: Live2D 模型(Hiyori/Haru) + 32 段 Haru GIF + 方案文档第18章 PoC 实战记录
- tools/sprite_poc/: Python GIF→RGB565 转换脚本

踩坑要点(详见 docs/Rtc_AIavatar 第18章):
- PIL Image.quantize() 会破坏 RGBA 透明度,必须改用 gifsicle
- PIL 保存动画 GIF 仅第1帧有透明,后续帧不透明 - LVGL gifdec 按帧读取
- Cubism Editor 直接导出 GIF 才能逐帧保留透明信息(FREE 版限制部分模型)
- gifsicle --lossy 会严重锯齿化,去掉只保留 --colors 256 + -O3 即可
- 裁剪居中需用全帧 bbox 不能只看第1帧(Live2D 角色每帧位置有偏移)
- LVGL 默认不支持 PNG,背景图用 JPG + esp_jpeg 解码到 RGB565 buffer
- 透明 GIF 显示黑色背景: gifdec.c canvas 初始化 alpha 须改为 0x00
2026-05-12 17:14:49 +08:00
6208930001 chore: 更新插件指南/sdkconfig/IDF串口配置 + 从仓库移除 .DS_Store
变更清单:
1、Claude Code 插件指南:新增 GSD 执行框架章节 + 第三方 Skills(think/health)说明
   + 新电脑环境恢复指南完善(增加 GSD 安装步骤、第三方 Skills 数量更新为 7 个)
2、sdkconfig:ESP-IDF SoC 配置项更新(GDMA/LCDCAM/CPU watchpoint 格式调整)
3、.vscode/settings.json:IDF 串口号从 usbmodem834101 更新为 usbmodem834401
4、从 Git 仓库移除 3 个 .DS_Store(.gitignore 已有规则但早期被跟踪)
   - .DS_Store
   - main/.DS_Store
   - main/ui/.DS_Store

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-11 14:10:36 +08:00
7868efed9b docs: 插件指南新增 GSD 执行框架章节 + GSD 项目参考文档
1、Claude Code 插件指南新增第九章:GSD 与工具链协作说明
   - 任务规模自动匹配(小事直接做/中事 gsd-quick/大事 gsd-plan)
   - GSD 与 hw-driver-workflow 的分层协作关系
   - GSD 常用命令速查表
   - 防上下文腐烂覆盖范围说明
2、插件指南恢复指南新增步骤 3.2(GSD 安装命令)
3、新增 docs/Get Shit Done项目.html(GSD 项目介绍参考文档)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 13:15:01 +08:00
b35d86f84c docs: 更新 Claude Code 插件指南 + 新增开发参考文档
Claude Code插件高效运用指南.md:
1、补充遗漏的内置 Skills(schedule/update-config/keybindings-help)
2、新增第三方 Skills 记录(find-skills/tmux/summarize/tavily-research/embedded-systems)
3、新增第八章:新电脑环境恢复指南(含 ESP-IDF v5.4.2 安装步骤)
4、新增步骤 3.1:第三方 Skills 安装命令

新增文档:
5、Claude Code 十个最值得装的 Skills copy.md — 微信文章内容存档
6、资深嵌入式工程师开发思维.md — 嵌入式开发核心思维与注意事项

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 10:09:08 +08:00
0bdf7be875 feat: 应援灯防撕裂优化 - DMA直接填充GRAM + LVGL flush拦截 + PWM黑屏遮蔽
应援灯颜色切换从 LVGL 渲染改为直接 DMA 填充 GRAM,彻底消除 LVGL 刷新竞争:

lcd.c/lcd.h:
- 新增 lcd_fill_color_with_buf() 直接 DMA 分条填充全屏纯色
- 新增 TEON(0x35) 启用 TE 内部同步信号
- 新增 lcd_read_scanline()/lcd_wait_vsync_timeout() VSYNC 读取接口
  (实测 QSPI 模式下 TESLRD 始终返回 0xFFFF,软件 VSYNC 不可用)

ui_ScreenSet.c:
- LVGL flush 回调拦截:进入应援灯时替换为空操作,退出时恢复
  解决 LVGL 周期刷新覆盖 DMA 颜色导致红色方块残留的问题
- DMA 缓冲区生命周期管理:进入时分配,退出时释放
- 颜色切换 PWM=0 黑屏遮蔽:DMA 期间完全熄灭背光,撕裂不可见
- 滑块交互优化:拖动期间锁定其他按钮 + PWM 50ms 节流
- 手动滑动检测替代 LVGL 手势(layer_top 上手势不可靠)

.gitignore: 排除 docs/*.pdf 文档文件

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 15:18:41 +08:00
99d7e910f1 补充提交:无关紧要的文件修改,不用理会 2026-03-27 10:59:33 +08:00
0735d45e52 feat: 从按键版迁移APP传图、设备间图片分享/接收、组合键模式切换功能
## 功能迁移清单(从 Dzbj_ESP32_S3_Key → Baji_Rtc_Toy)

### 1. 设备间BLE图片传输(GATT Client + 协议)
- 新增 ble_transfer.c/h:发送方 GATT Client 扫描→连接→MTU协商→分包写入
- 接收方复用现有 GATT Server(IMAGE_WRITE 0x0B01),协议完全兼容
- 发送完成/失败自动跳转 Img 界面并关闭蓝牙

### 2. APP传图显示 Update 界面
- 新增 ui_ScreenUpdate.c/h:更新进度界面(Gengxin背景 + Update_GIF动画)
- dzbj_ble.c WRITE_EVT 中通过 ble_transfer_is_receiving() 区分 APP传图 vs 设备间传输
- APP传图 → ScreenUpdate,设备间传输 → ScreenReceiving

### 3. KEY2 按键功能入口(iot_button 单击/双击/长按)
- KEY2 单击:开蓝牙 → Peiwang 配对界面(APP传图)
- KEY2 双击:接收模式 → ScreenImageReception(等待配对)
- KEY2 长按:发送模式 → ScreenImageShar(等待配对)
- 按键参数与按键版对齐:long_press_time=1200ms, short_press_time=300ms

### 4. BOOT+KEY2 组合键模式切换(替代 BOOT 长按3秒)
- BOOT 2秒长按 + KEY2 同时按下 → 触发模式切换
- 消除单键长按的误触发问题
- AI模式和吧唧模式均注册组合键

### 5. 按键上下文状态机
- btn_context_t 枚举:HOME/IMG/SET/PEIWANG/IMAGE_SHAR/IMAGE_RECEPTION/SHARING/RECEIVING/UPDATE
- 所有界面切换点(手势/按键/BLE自动跳转)同步设置 context
- BOOT 单击按 context 分发:Home无操作、Img/Set返回Home、配对退出蓝牙、传输等待取消

### 6. 新增 UI 界面(6个Screen + 7张图片)
- ScreenPeiwang:蓝牙配对等待
- ScreenUpdate:APP传图更新中
- ScreenImageShar:发送方等待配对
- ScreenImageReception:接收方等待配对
- ScreenSharing:发送方传输中
- ScreenReceiving:接收方接收中

### 7. 其他适配
- BLE 广播改为按需启动(dzbj_ble_start/stop/is_active)
- sleep_mgr 移除 KEY2 唤醒(仅 BOOT 唤醒屏幕)
- device_mode 新增模式切换按键抑制(防止重启后立即触发)
- battery_ui 电池指示器组件
- sdkconfig 启用 BLE GATTC 支持

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 10:55:17 +08:00
32c3dc69bc fix: 滑动切换图片时自动跳过解码失败的无效图片
1、update_ui_ImgBle 返回类型 void → bool,解码成功返回 true,失败返回 false;
2、左滑/右滑手势回调添加 for 循环,解码失败自动跳到下一张(最多尝试 10 张防死循环);
3、更新最新设备运行日志;

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 18:22:53 +08:00
4e2f6906f9 feat: 启用 BLE 5.0 2M PHY 图传加速 + 移除未使用的 BluFi 组件 + BLE 断连内存泄漏修复
1、dzbj_ble.c 新增 BLE 5.0 2M PHY 请求(连接时自动协商,不支持则回退 1M);
2、dzbj_ble.c 新增 PHY 更新事件日志(tx_phy/rx_phy: 1=1M, 2=2M, 3=Coded);
3、dzbj_ble.c 断连时清理未完成的图片传输状态,释放 img_data/filepath/file_img 防止内存泄漏;
4、移除未使用的 BluFi 组件依赖(Kconfig select、编译检查、sdkconfig),解除与 BLE 5.0 的兼容冲突;
5、sdkconfig.defaults 及生产环境配置统一启用 BLE 5.0 + 保留 BLE 4.2 legacy advertising 兼容;
6、新增 BLE 图片传输问题分析与优化建议文档(含 UniApp vs Flutter 对比分析,供 APP 开发者参考);

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 17:12:35 +08:00
af58123d66 1、更新Claude插件高效运用的使用方法;
2、更新电子吧唧AI触屏版 → 按键版迁移总结
2026-03-23 10:30:44 +08:00
919bf8f28f feat: GIF动画表情系统 + 情绪映射增强 + HTTPS音频中止修复
一、新增功能:
1、新增8种GIF动画表情(200x89) + 3种叠加图标(45x45),实现22种情绪标签到GIF的映射表;
2、新增30+组英文近义词情绪fallback映射(如worried→sad),防止AI使用非标准标签时GIF无法切换;
3、新增HTTPS中止后诊断日志,自动追踪前20帧音频处理流程便于定位无声问题;

二、Bug修复:
4、修复HTTPS播放中止后RTC音频解码参数未恢复(16000/60→8000/20),通过background_task_串行化恢复;
5、修复AbortHttpsPlayback解码器竞态崩溃,将重置/恢复/DMA flush全部串行化执行;
6、修复LVGL gifdec不支持无全局颜色表GIF的问题,支持仅使用局部颜色表的压缩GIF;
7、修复GIF透明区域显示黑色方块,canvas初始alpha改为0x00;
8、修复lv_gif定时器gif对象为NULL时的空指针崩溃;

三、优化:
9、情绪标签从等待is_final改为第一条字幕即时触发GIF切换,新增去重和回复结束自动恢复neutral;
10、对话状态表情映射优化:THINKING→thinking、ANSWERING→happy、INTERRUPTED→surprised;
11、CPU核心绑定:LVGL任务Core0,音频循环Core1,避免GIF解码与音频争抢;
12、中文情绪词映射扩展,新增担心/心疼/着急等映射;

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 15:28:14 +08:00
da098bf482 1、新增了在日志打印中接收字幕详细信息的代码 2026-03-17 09:45:29 +08:00
e2ec9f36aa 1、启用LVGL GIF解码器(CONFIG_LV_USE_GIF=y),支持吧唧模式GIF图片BLE传输和播放;
2、LVGL内存管理切换为系统malloc(CONFIG_LV_MEM_CUSTOM=y),使gifdec大块分配走PSRAM避免内部堆不足;
3、pages.h stub头文件补充pages_cleanup_gif()声明,修复ui_ScreenImg.c编译隐式声明错误;

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 17:36:18 +08:00
23613e1cd3 1、新增obtain_music Function Calling音乐播放功能,4处协议分支均支持URL直播和音乐API两种HTTPS方式;
2、Kconfig新增MUSIC_API_URL音乐播放API地址配置;
3、重构SendStoryRequest和SendMusicRequest为HttpsApiPlayback通用方法,消除故事/音乐播放~440行重复代码;
4、修正4处obtain_story注释和日志:WebSocket描述改为HTTPS API(与实际实现一致);
2026-03-05 15:44:47 +08:00
ccea0c681c feat: HTTPS故事播放 + RTC/HTTPS双向音频切换状态机 + 协议层优化
1、新增HTTPS故事播放功能:SendStoryRequest通过蓝牙MAC请求故事API,支持intro+body两段式无缝播放,替换原WebSocket故事请求方式;
2、新增HttpsPlaybackFromUrl通用HTTPS音频下载播放方法,支持JSON格式Opus帧流式解码播放;
3、新增RTC↔HTTPS双向音频切换三标志位状态机(opus_playback_active_/https_playback_active_/https_playback_abort_),HTTPS播放期间静默丢弃RTC PCM包,OnAudioOutput捕获is_opus_frame防止残留Opus帧杂音;
4、新增AbortHttpsPlayback中止方法,使用独立高优先级任务(priority=10)执行DMA flush;AbortSpeaking也新增DMA缓冲区flush确保扬声器立即静音;
5、协议层新增OnBotMessage回调,非字幕Bot下行消息立即中止HTTPS播放;volc_rtc_protocol移除is_binary依赖改为直接前缀检测,新增info前缀识别,subv字幕排除on_bot_message_由subtitle handler单独处理;
6、subtitle字幕USER/AI区分从CONFIG_VOLC_DEVICE_NAME比较改为bot_前缀判断,用户说话时立即中止HTTPS播放;
7、Kconfig新增STORY_API_URL故事播放API地址配置;
8、设备注册RTC服务时,设备名称从Wi-Fi MAC地址改为使用蓝牙MAC地址

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 13:45:52 +08:00
1e7ba0763a 1、更新了postman参数、约束和语音请求讲故事的Function Call参数;
2、字幕增加了情绪标签,并且emoji表情跟随情绪和设备状态而更新;
2026-03-02 17:27:03 +08:00
04d26d7cee 1、本次新增了Function Calling的讲故事功能,可以语音获取小故事 2026-03-02 15:34:41 +08:00
7082d2a818 补充提交postman请求参数 2026-03-02 15:12:28 +08:00
98e40ac163 feat: 火山RTC字幕文本实时显示 + 字幕日志精简
1. DataCallback 新增 subv(字幕)二进制消息前缀识别,字幕数据正确解析并转发到应用层
2. OnIncomingJson 处理 type:"subtitle",通过 userId 区分用户语音识别(STT)和 AI 回复文本
3. 字幕日志简化:subv 消息仅打印"接收下行二进制消息(字幕)",不展开 JSON 内容
4. 新增postman请求的参数

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 15:11:34 +08:00
0a6b35d979 1、新增提交postman请求的Open_API参数(稳定无报错版本) 2026-03-02 10:12:59 +08:00
58c33e3cb4 feat: AI对话模式emoji表情系统 + 中文字体 + 分区优化
1. 新增AI对话LVGL界面(ai_chat_ui),支持emoji图片 + 状态文本 + 聊天消息
2. 新增7个emoji表情资源(64×64 PNG C数组):neutral/happy/sad/angry/crying/funny/laughing
3. 新增阿里巴巴普惠体20px 4bpp中文字体(GB2312字符集)
4. 利用火山RTC会话状态(VOLC_MSG_CONV_STATUS)驱动emoji切换:
   - LISTENING→happy, THINKING→neutral, ANSWERING→laughing
   - INTERRUPTED→funny, ANSWER_FINISH→happy
5. 设备状态emoji映射:Listening→happy, Speaking→laughing, Dialog→happy
6. 配网模式显示happy emoji
7. 分区优化:model 3MB→64KB,OTA 5MB→6.5MB
8. 编译优化:-Og→-Os,移除SimSun CJK字体

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 18:14:19 +08:00
38b71ce221 chore: 添加 esp_jpeg 托管组件到仓库
将 ESP Component Registry 自动下载的 espressif__esp_jpeg 组件纳入版本管理,
避免其他开发者拉取后需要重新下载。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 10:32:38 +08:00
c4de88d7ee chore: 移除 pages_stub.c(已被 dzbj/pages.c 替代)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 10:23:28 +08:00
14776acb0a feat: 完成 AI/吧唧双模式完全隔离重构 + 触摸坐标日志 + SPIFFS 预烧录
## 核心变更

### 1. 双模式完全隔离 (Phase 2+4)
- 拆分 InitializeButtons() 为 InitializeBadgeModeButtons() + InitializeAiModeButtons()
- 构造函数按 device_mode 分支:吧唧模式不创建 PowerSaveTimer/BackgroundTask
- 吧唧模式不注册音量/故事按键回调,避免调用 GetAudioCodec() 崩溃
- GPIO0 由 iot_button 统一处理,dzbj_button 仅注册 KEY2(GPIO4)
- SetDeviceState() 中 background_task_ 空指针保护

### 2. 吧唧模式 BOOT 按键崩溃修复
- 新增 dzbj_boot_click_handler()(C 函数,避免 lvgl.h 与 display.h 冲突)
- 移植 dzbj 的唤醒屏幕/退出手电筒/返回Home 完整逻辑

### 3. esp_timer 阻塞 LVGL 渲染修复
- iot_button 回调在 esp_timer 任务中执行,vTaskDelay 会阻塞 lv_tick_inc
- 改为 xTaskCreate 派发到独立 FreeRTOS 任务,避免冻结 LVGL 渲染

### 4. 触摸坐标日志 + SPIFFS 预烧录
- esp_lvgl_port_touch.c 添加触摸坐标打印
- CMakeLists.txt 添加 spiffs_create_partition_image 自动打包 spiffs_image/

### 5. dzbj 模块文件新增
- device_mode: NVS 设备模式管理 (AI=0/吧唧=1)
- dzbj_button: GPIO4 KEY2 中断 + BOOT 点击处理
- dzbj_ble: BLE GATT 图传服务 (0x0B00)
- dzbj_battery: ADC 电池电压监测
- sleep_mgr: 10s 超时熄屏低功耗管理
- pages: 图片浏览/GIF播放/PWM亮度
- fatfs: SPIFFS 文件管理

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 10:23:04 +08:00
bcfd35b9b8 chore: 从 Git 索引中移除 build/ 和 sdkconfig.old
build/ 目录和 sdkconfig.old 之前已被提交到仓库,
.gitignore 只能忽略未跟踪文件。
使用 git rm --cached 从索引中移除,本地文件不受影响。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 17:10:45 +08:00
c24a9bc162 feat: 集成 dzbj LVGL 显示模块 + 配网模式内存优化
阶段1: 将 dzbj 项目的 LVGL 8.3.11 LCD 显示集成到 AI小智 主项目,
开机显示 ScreenHome 界面,同时优化配网模式下的内存使用,
确保 WiFi+BLE+LVGL 三者共存运行。

## 新增功能

### dzbj 显示模块集成
- 新增 main/dzbj/ 目录,移植 LCD 驱动(ST77916 QSPI)、触摸驱动(CST816S)、
  LVGL 初始化和 SquareLine Studio UI 界面
- I2C 总线共享:dzbj 触摸控制器复用主项目的 I2C_NUM_1 总线
- GPIO 冲突解决:LED(GPIO21)、Touch1(GPIO1)、Touch4(GPIO7) 改为 NC,
  电池 ADC 从 GPIO6 改为 GPIO3
- 添加 LVGL、esp_lcd_st77916、esp_lcd_touch_cst816s 等组件依赖
- managed_components 纳入版本管理

### 配网模式轻量化启动
- BoxAudioCodec: 新增 output_only 模式,仅创建 I2S TX 通道(省 ~13KB DMA)
  跳过 ES7210 ADC 初始化(省 ~2-4KB)
- AudioCodec: 新增 StartOutputOnly() 方法,仅启用扬声器输出
- Application: 配网模式跳过 Opus 编码器、输入重采样器、协议初始化、
  天气位置检测等网络业务
- 板级构造函数: 配网模式跳过电池检测、IMU传感器、PowerSaveTimer

### WifiBoard 配网流程修复
- NeedsProvisioning() 静态方法: 读取 NVS force_ap 和 SSID 列表,
  用于提前判断配网模式
- force_ap 竞态修复: 构造函数不再清零 force_ap,改在 StartNetwork() 清零,
  确保 NeedsProvisioning() 能正确读到 force_ap=1
- Application 缓存 provisioning_mode_ 成员变量,避免重复读 NVS

### BLE 配网重启修复
- 配网成功后用 esp_timer 延迟重启替代 xTaskCreate,
  避免内存紧张时任务创建失败导致设备不重启
- 注释掉 WiFi 连接成功后的 MAC 地址发送步骤

### sdkconfig 内存优化
- BT_ALLOCATION_FROM_SPIRAM_FIRST=y (BLE 动态分配优先 PSRAM)
- SPIRAM_MALLOC_RESERVE_INTERNAL=32768
- NVS_ALLOCATE_CACHE_IN_SPIRAM=y
- WiFi 静态缓冲区数量优化 (RX=10, TX=8)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 17:07:51 +08:00
d5fcb917b1 补充提交dzbj文件夹 2026-02-27 10:44:58 +08:00