15 Commits

Author SHA1 Message Date
f5a2777abf feat(rtc+ble): A+B+C 三件套 RTC 偶发连接失败修复 + BLE 配网 reboot 修复 (移植自 Baji)
============ 问题与修复 ============

### 问题 1: 配网模式按 BOOT 进入配网 → 设备 reboot
  日志:
    E BT_OSI: future_new unable to allocate memory for the semaphore.
    assert failed: future_ready future.c:64 (future != NULL)

  根因:
    BLE Bluedroid 协议栈初始化时, future_new 分配 semaphore 失败 → 后续 future_ready
    拿到 NULL → assert. 跟 Baji 的 vQueueDelete(NULL) 同性质 — DRAM 不足.
    Kapi 用 LVGL (~50-80 KB DRAM) + RTC SDK 静态 .bss (~30-50 KB),
    BLE Bluedroid stack 抢不到所需内存.

  修复 (sdkconfig):
    CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST=y
    让 BLE Bluedroid 全局变量动态分配优先走 PSRAM, 释放 ~30 KB DRAM
    给 controller / GATT / WiFi 使用. 修复后 BLE 配网正常.

### A+B+C 三件套 (移植自 Baji commit 70f0cdd, 解决 RTC 偶发连接失败)

  [A] sdkconfig: CONFIG_LWIP_MAX_SOCKETS=10 → 20
    根治火山 RTC SDK 启动时 lwIP socket fd 不足触发 SocketConnection-Lite.c:191
    bind local ip failed → ICE 协商失败 → wait connect bits=0x0 超时.

  [B] application.h/cc + volc_rtc_protocol.h/cc: 失败 3 次后销毁 + 重建 engine
    新增 VolcRtcProtocol::ForceRebuildEngine() public 方法.
    application.cc 加 audio_channel_retry_count_ 重试计数,
    OpenAudioChannel 连续失败 3 次时调用 ForceRebuildEngine 清理 SDK 状态.
    应对 A 修复后仍可能出现的 SDK 内部状态污染 (ICE Agent 异常等).

  [C] volc_rtc_protocol.cc: DIAG_RTC_BIND_ENABLE 一键开关诊断埋点
    在 join_room 前/后 + ForceRebuildEngine 前/后打印 lwIP socket fd / heap /
    psram / WiFi rssi / errno, 偶发失败时直接定位根因.
    验证完成后改 0 关闭, 编译器消除 #if 块, 零运行时开销.

============ 文件改动 ============

  sdkconfig:
    +CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST=y    (BLE 配网修复)
    +CONFIG_LWIP_MAX_SOCKETS=10 → 20             (方案 A)

  main/application.h:
    +int audio_channel_retry_count_ = 0;         (RTC 重试计数)

  main/application.cc:
    OpenAudioChannel 失败处加重试计数 + 连续失败 3 次调 ForceRebuildEngine,
    static_cast<VolcRtcProtocol*>(protocol_.get()) (ESP-IDF 默认 -fno-rtti).

  main/protocols/volc_rtc_protocol.h:
    +void ForceRebuildEngine() 声明.

  main/protocols/volc_rtc_protocol.cc:
    +DIAG_RTC_BIND_ENABLE 开关 + diag_count_used_sockets() 工具函数,
    +OpenAudioChannel Pre-Join / Post-Fail DIAG 埋点,
    +ForceRebuildEngine() 实现 (volc_rtc_stop + volc_rtc_destroy + 等 2 秒 + 触发重建).

============ 关联资源 ============
  Baji commit 70f0cdd: feat(rtc): 偶发连接失败完整修复 (A+B+C 三件套)
  Baji commit bffd316: feat(provisioning): BLE 配网完整修复
  全局 CLAUDE.md "BLE 配网 DRAM 紧张完整排查流程" 章节
  项目记忆 project_ble_provisioning_issues.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:15:34 +08:00
a369796eb6 feat(audio): 设备端软件 loopback ref AEC (路径 D'') 完整实施
N16R8 模组无法跑硬件 ADC 回采 (32-bit STEREO codec + 火山 RTC + 80MHz PSRAM 三者
不可共存, 详见 commit fb4b607 探索教训)。改走软件 loopback ref 方案:
codec 保持 baseline 1ch 16-bit (RTC 链路 100% 稳定), DAC 输出 PCM 软件复制一份作
AEC ref 信号, 用 esp_aec.h 底层同步 API (不启后台任务, 不抢 RTC 调度) 处理。

实测验证有效:
- AI 说话: mic=187 ref=8929 clean=30 → 回声消除 84%
- 用户说话: mic=456 ref=8 clean=456 → passthrough 100% 保留
- 服务端 ASR 正常识别用户语音, AI 正常响应 (📝 USER: + 📝 AI: 字幕完整)
- 无 WiFi pm_coex panic, idle 倒计时稳定

主要变动:

1. main/CMakeLists.txt (4 行)
   - REQUIRES 加 esp-sr (引入 esp_aec.h 底层同步 API)

2. main/application.h (23 行)
   - aec_handle_ / aec_chunk_size_ / ref_ring_buf_ / ref_ring_capacity_ /
     ref_ring_write_idx_ / ref_ring_filled_ / aec_ref_delay_samples_ /
     ref_ring_mutex_ 成员
   - InitAec / DeinitAec / AppendRefSamples / GetDelayedRef / ApplyAEC 函数声明

3. main/application.cc (242 行)
   - include esp_aec.h + esp_heap_caps.h
   - InitAec: lazy 初始化 (Application 构造时不调, ReadAudio 首次走 AEC 路径触发),
     避免开机占内部 SRAM 影响 WiFi 启动; ref_ring_buf 优先 PSRAM 分配 200ms 容量
   - DeinitAec: 析构时清理 aec_handle / ref_ring_buf / ref_ring_mutex
   - AppendRefSamples: DAC PCM 推入 ref ring buffer (mutex 互斥)
   - GetDelayedRef: 从 ref ring buffer 取延迟后 ref (mic 同步用)
   - ApplyAEC: 按 chunk_size 处理, 加 ref 静音检测 (RMS<50 时 passthrough),
     RMS 诊断日志每 2 秒打印一次 (mic/ref/clean)
   - OnAudioOutput 两个分支 (player_pipeline_write / codec->OutputData) 都加
     AppendRefSamples hook, 复制 PCM 到 ref ring buffer
   - ReadAudio: recorder_pipeline 路径加 lazy InitAec + ApplyAEC, target_samples
     取 max(caller_samples, chunk_size) 保持 baseline 20ms PCM 帧大小
   - 析构调 DeinitAec

实施 4 大踩坑 (详见 ~/.claude/projects/.../memory/project_software_aec_implementation.md):

a) portMUX (spinlock) 禁中断与 WiFi pm_coex 模块冲突 → IllegalInstruction panic
   修复: 用 SemaphoreHandle_t (FreeRTOS mutex, 2ms 超时) 替代, 不禁中断

b) AI 静音后 AEC 滤波器维持 echo 模式错误压制用户语音 → ASR 不识别
   修复: ApplyAEC 加 ref 静音检测, ref RMS<50 时 passthrough 不调 aec_process

c) chunk_size (256, 16ms) ≠ caller_samples (320, 20ms) 让上行 PCM 帧大小变 →
   服务端 ASR 不识别非标准帧
   修复: target_samples = max(samples, aec_chunk_size_), 保持 baseline 20ms 帧

d) aec_create 占内部 SRAM (~30-50KB) 影响 WiFi RX buffer 分配 → panic 重启
   修复: lazy init, ReadAudio 首次需要时才创建实例

资源占用 (实测):
- Flash: +59 KB (esp-sr libaec.a)
- Internal SRAM: +35-50 KB (aec_handle_t 工作 buffer)
- PSRAM: +10-15 KB (ref_ring_buf 200ms + 临时 buffer)
- Core 1 CPU: +6-12% (chunk=256, 每 16ms 一次 aec_process)
- 整体评估: 适中, 不影响 RTC/WiFi 等其他功能

自言自语根因辨析 (重要认知更正):
- 火山控制台 "AI 降噪 OFF" 是 NS 不是 AEC, 服务端 AEC 默认 ON 不显示在 UI
- baseline 不自言自语 = 云端 AEC 在兜底
- 自言自语真因常是上行 PCM 数据异常 (如嘟嘟嘟阶段 channel_mask 错位) 触发服务端
  VAD 误判, 不是 echo 太大
- 设备端软件 AEC 是减轻云端负载 + 极端场景兜底, 非必需但工程价值显著

调优指南: aec_ref_delay_samples_ 当前 800 (50ms), 根据 mic 离扬声器距离调
30-80ms, 监听 RMS 中 AI 说话期间 clean 最小为最优

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 15:30:20 +08:00
b1577d8418 fix: 软 RTC 退出待命音三连修 + 尾音延迟
问题与修复(按发现顺序):

【问题1】AudioLoop guard 冻结 OnAudioOutput → 待命音队列永远不被消费
- 现象: WaitForAudioPlayback 3秒超时, 无声
- 根因: EnterIdleHibernate Step 0 设 hibernating_=true, AudioLoop 顶层
  if(hibernating_) continue 会同时跳过 OnAudioInput + OnAudioOutput,
  PlaySound 入队的 Opus 永远不解码。
- 修复: guard 下沉到 OnAudioInput 入口(仅 input 侧关 codec 有 bad_alloc 风险),
  OnAudioOutput 自带 codec->output_enabled() 保护。

【问题2】volc_rtc_stop 副作用关 I2S 通道 → codec 状态错位
- 现象: 听到杂音而非待命音; i2s_channel_disable "not enabled yet" 错误
- 根因: 火山 RTC SDK 的 stop 内部关闭 ES8311 I2S, 但 codec class 内部
  output_enabled_ 标志仍是 true → 状态错位, PlaySound 写入到 disabled 的 I2S。
- 修复: EnterIdleHibernate 在 PlaySound 前显式 EnableOutput(false→true)
  强制重新激活 I2S, 并灌 200ms silence 覆盖 DMA 残留。

【问题3 - 真因】protocol downlink_is_pcm_ 标志位污染 → Opus 被当 PCM 字节流写出
- 现象: 杂音仍在
- 根因: 火山 RTC 下行音频是 PCM, DataCallback 设 downlink_is_pcm_=true。
  LeaveRoom 没重置这个 flag, 后续 hibernate 中 PlaySound 入队的 Opus 包,
  OnAudioOutput 读到 protocol_->downlink_is_pcm() 返回 true →
  treat_as_pcm=true → 跳过 opus_decoder, 直接把 Opus 编码字节当 int16
  PCM 样本写到 codec → 杂音。
- 修复: VolcRtcProtocol::LeaveRoom 末尾重置 downlink_is_pcm_=false +
  first_downlink_logged_=false。唤醒重连后 DataCallback 收到首包会立即
  重新设置该 flag, 不影响欢迎语 PCM 播放。

【问题4】WaitForAudioPlayback 完成 ≠ DMA 输出完成 → 尾音被截
- 现象: 待命音能听见但提前结束约 1 秒
- 根因: WaitForAudioPlayback 只判断 audio_decode_queue_ 出队完毕。
  OnAudioOutput 是 background_task Schedule 异步执行 codec write,
  队列空 ≠ codec 写完; codec.Write 返回 ≠ I2S DMA + ES8311 FIFO 输出完毕。
- 修复: WaitForAudioPlayback 之后追加 background_task->WaitForCompletion +
  vTaskDelay(1000) 让 DMA 尾音自然衰减, 才关 player_pipeline。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 18:29:09 +08:00
a3a476f857 feat: Phase 6 软退出方案 C+ — 修复待命音无声 + 唤醒重连失败
问题(上次提交 d5239cf 之后实测):
1. EnterIdleHibernate 触发的 LeaveRoom() 顺手关闭了 codec output:
   LeaveRoom → on_audio_channel_closed_ → player_pipeline_close → EnableOutput(false)
   导致后续 PlaySound(P3_KAKA_DAIMING) 入队后 AudioLoop 写不出声音,
   WaitForAudioPlayback 3 秒超时退出, 用户听不到待命音。
2. LeaveRoom 调用 volc_rtc_destroy 后 rtc_handle_ = nullptr,
   WakeFromHibernate → ToggleChatState → OpenAudioChannel 直接返回 false,
   触发 2 秒重试循环, 同时每次失败回退 idle 都重新 PlaySound,
   codec 状态震荡产生杂音, 服务端 AI 任务也无法重新加入房间。

方案 C+ 修复:
- Protocol::LeaveRoom() 新增 bool notify_closed=true 参数 (默认行为不变)。
- VolcRtcProtocol::LeaveRoom(notify_closed):
  * 只 volc_rtc_stop, 不 volc_rtc_destroy, 保留 rtc_handle_ 供唤醒复用。
  * notify_closed=false 时跳过 on_audio_channel_closed_, 不连带关 codec。
- EnterIdleHibernate:
  * 调用 LeaveRoom(false) → codec output 保留。
  * 手动 background_task_->WaitForCompletion + 清空队列 + 关麦克风。
  * SetDeviceState(idle) 后 PlaySound 真正能播出来。
  * WaitForAudioPlayback 完才 player_pipeline_close (这里再正常关 output)。
- WakeFromHibernate:
  * 先放下 hibernating_ 让 AudioLoop guard 通过, 再 ToggleChatState。
  * 因 rtc_handle_ 仍有效, OpenAudioChannel 走 volc_rtc_start 重启路径,
    on_audio_channel_opened_ 回调重开 player_pipeline + 灌 200ms silence。

编译: kapi.bin 0x2e6330 (3.04MB), 分区 42% 空闲。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 17:43:29 +08:00
d8982c3569 1、新增讲故事和播放音乐的function call配置文件(原小智项目)
2、更新cJSON格式提交AgentConfig参数更新提示词(当前项目暂不需更新提示词)
2026-03-05 17:17:42 +08:00
f011b94efe 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(与实际实现一致);

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 15:43:11 +08:00
dad9fe03ac 更新联网问答Agent参数为公司账户的bot_id 2026-03-05 14:18:58 +08:00
b9bbcc456c 1、新增HTTPS故事播放功能(SendStoryRequest通过蓝牙MAC请求故事API,支持intro+body两段式无缝播放);
2、新增HttpsPlaybackFromUrl通用HTTPS音频下载播放方法,obtain_story同时支持HTTPS URL和WebSocket两种方式;
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;
5、协议层新增OnBotMessage回调,Bot下行消息立即中止HTTPS播放;volc_rtc_protocol移除is_binary依赖改为直接前缀检测,新增info前缀和subv跳过逻辑;
6、新增subtitle字幕消息解析,通过bot_前缀区分USER/AI,用户说话时立即中止HTTPS播放;
7、AbortSpeaking新增HTTPS中止信号和DMA缓冲区flush;
8、Kconfig新增STORY_API_URL故事播放API地址配置;

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 11:27:07 +08:00
d494a1025f 1、新增Function Calling的讲故事功能,可以语音获取小故事 2026-03-02 14:48:42 +08:00
7431a630e4 1、注释了BLE JSON Service 相关实现代码,因为当前蓝牙通讯仍然使用二进制方式进行通讯,只是替换了之前官方的BluFi方式 2026-02-12 16:53:30 +08:00
60a2c7b068 1、所有日志和上报数据的MAC地址从WiFi MAC改为蓝牙MAC(eFuse读取,无需启动蓝牙);
2、电量上报扩展为设备状态上报,新增is_online/volume/brightness字段;
3、新增RTC火山引擎连接状态追踪(检测kDeviceStateDialog);
4、新增重启前上报设备离线状态(OnBeforeRestart虚方法);
5、重命名BATTERY_REPORT为DEVICE_STATUS_REPORT(Kconfig/变量/函数/日志);
6、对齐服务端接口:URL端口和路径修正,JSON字段名battery_level改为battery,移除wifi_rssi;

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 14:38:31 +08:00
94067d4adc 1、本次修改了关于BluFi关键字的日志和方法名称替换为Ble关键字 2026-02-10 15:00:57 +08:00
77c7283d09 重构蓝牙配网: 替换BluFi为自定义GATT Server,修复手机蓝牙不可见问题
核心改动:
- bluetooth_provisioning: 使用 esp_ble_gap_config_adv_data_raw() 原始广播
  替代 BluFi API,采用自定义 GATT Server (Service 0xABF0, Write 0xABF1,
  Notify 0xABF2) 实现二进制配网协议,保留全部WiFi配网业务逻辑
- ble_service: 广播包名称移至 Scan Response,避免超31字节限制;
  GAP事件改用位掩码确保 adv_data 和 scan_rsp 都完成后再启动广播
- application: BLE JSON 服务从 Application 移至 WifiBoard 管理,
  HandleBleJsonCommand 改为接收 BleJsonService 引用参数
- wifi_board: 新增 StartBleJsonProvisioning(),配网入口切换回
  StartBluFiProvisioning() 使用重构后的 GATT Server
- 蓝牙设备名统一为 "Airhub_Ble"
- 配网模式下跳过电量上报,避免无WiFi时HTTP请求失败
- 新增 tests/ble_provision_test.py 配网协议测试脚本

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 14:32:55 +08:00
zyc
02ae116488 fix blue server 2026-02-10 10:54:42 +08:00
a54773f71a Kapi_RTC版本初始化 2026-01-20 16:55:17 +08:00