跳到主要内容

快速讲解卡

把整个项目浓缩成 15 段速记,每段 30 秒能说完一块。可以拿来快速回顾整体脉络,也可以作为对外讲解的素材——配合视觉资源里的图边讲边比划,比单纯念说明书有效得多。

用法:每段开头有 [图 N] 提示该翻到哪张图配合看/讲。


段 1:30 秒电梯介绍

这是一个跑在 ESP32 上的离线唤醒 + 联网对话 的 AI 语音设备固件。用户喊"你好小智"它就醒,醒了之后录音上传到云端大模型,大模型回复的语音流回来设备播给用户听。整个项目支持 110 多块开发板,从面包板上的 ESP32-S3 到带触摸彩屏的旗舰板都能跑。

段 2:项目能力速览

它能做四件事:

  1. 离线唤醒:唤醒词检测在设备本地用 ESP-SR 跑,不联网也能识别"你好小智";
  2. 流式对话:唤醒后语音流式上传,云端流式 ASR + LLM + TTS,端到端延迟一般 800ms 到 1.5 秒;
  3. 设备控制:通过 MCP 协议让大模型反向调用设备能力,比如"把音量调到 80"、"拍一张照片"、"打开桌上的灯";
  4. 远程升级:A/B 双分区 OTA,跑不起来自动回滚,永不变砖。

段 3:整体架构 — [图 1: 架构总览]

看这张图:上半是云端,下半是 ESP32 设备。

设备端最核心的是黄色那个 Application——主调度器。所有子系统都接在它上面。

数据流就两条:

  • 上行:麦克风进来的 PCM 先过 AFE 做回声消除和降噪,再压成 Opus 走 WebSocket 或 MQTT+UDP 发到云端;
  • 下行:云端的 Opus 解码后塞给喇叭。

还有一个 MCP Server——它是设备端的工具接口注册中心,让大模型可以反向控制设备。


段 4:启动流程 — [图 2: 启动时序]

设备从按下开关到能听用户说话总共 3 到 8 秒,分这几步:

  1. Bootloader 跳到固件(100ms)
  2. Board 把硬件初始化好(200-400ms)—— 屏幕、按键、codec 都在这一步亮起来
  3. Application 初始化软件子系统(500ms)
  4. 启动 Wi-Fi 或 4G(1-3 秒,看信号)
  5. 联网后 POST 设备信息到 OTA 服务器,可能拉新资源(500-2000ms)
  6. 协议建链握手(几百毫秒)
  7. 进 Idle 状态,唤醒词开始监听

这个流程的关键点是 Board 一定要先于 Application——后者要知道有什么硬件才能驱动。


段 5:音频处理 — [图 3: 音频数据流]

这是这个项目最值得讲的一块。看图:

上面是上行链:麦克风 → AFE 处理 → Opus 编码 → 发网络。中间用 5 个队列把不同速度的环节解耦,每个环节独立的 FreeRTOS 任务,所以彼此不会卡。

AFE 是用 Espressif 自家的 ESP-SR 库,做三件事:AEC 消回声 + VAD 检测人声 + NS 降噪。它会把"用户开口"信号实时报上来,让设备能打断 TTS。

下面是下行链:网络 → Opus 解码 → 重采样 → 喇叭。注意喇叭播放的 PCM 还会反馈给 AFE 当参考——这就是 AEC 能消回声的基础。


段 6:状态机 — [图 4: 状态机]

整个设备就 11 种"心情",每种心情都有自己该亮什么灯、显示什么、播什么提示音。

几个关键状态:

  • Idle:待机,唤醒词常驻;
  • Listening:在收音上传;
  • Speaking:在播放云端 TTS;
  • Upgrading:跑 OTA 中;
  • WifiConfiguring:等用户配 Wi-Fi。

状态之间的转换是有规则的——比如不能从 Speaking 直接跳到 Upgrading,要经过 Idle。这个规则在 IsValidTransition() 函数集中管控,跳错了会被丢弃并打日志。

这种设计的好处是调试方便——出问题先看日志里状态转换序列,能很快定位是哪一步出错。


段 7:一次完整对话 — [图 5: 唤醒到回复]

看这个时序图,用户说"你好小智,今天天气怎么样":

  1. 第一句"你好小智"被本地 WakeNet 识别,触发唤醒(200-400ms);
  2. 设备状态切到 Listening,开始持续录音上传;
  3. 同时云端流式 ASR 在转文字、LLM 在推理、TTS 在合成——三个组件流水线并行
  4. 云端发"tts.start"信令,设备切到 Speaking,开始播音;
  5. 一段话播完,回 Idle。

整个过程任何时候用户开口都能打断——AFE 持续监测人声,VAD 一报警就中止当前的 Speaking 直接进 Listening。


段 8:通信协议 — [图 6: 协议对比]

项目实现了两套协议,都是 Protocol 抽象基类的不同实现,业务层完全无感。

  • WebSocket:一条 WSS 长连接搞定一切,文本帧走 JSON-RPC,二进制帧走 Opus + 自定义包头。简单,Wi-Fi 场景首选。
  • MQTT + UDP:MQTT 走控制信令,UDP 走音频流(用 AES-128-CTR 加密)。复杂但移动网络下更稳——丢几个 UDP 包不影响信令。

切协议只改 Application::InitializeProtocol() 里的一行代码,其它都不动。


段 9:MCP 工具协议 — [图 7: MCP 时序]

这是这个项目最现代化的部分。MCP(Model Context Protocol)是去年才出来的协议,定义了大模型怎么调用外部工具。

设备端的 McpServer 注册了一堆工具:调音量、调亮度、拍照、切主题、控制灯……

流程是这样:

  1. 设备和云端建链后,云端 MCP Client 问"你有什么工具";
  2. 设备返回工具元数据(名字、参数、范围);
  3. 大模型看到这些工具描述就知道"我能让设备干什么";
  4. 用户说"调到 80",大模型决定调 self.set_volume({volume: 80})
  5. 设备收到 JSON-RPC 请求,校验参数范围,然后派到主线程执行硬件操作;
  6. 结果回 JSON-RPC reply 给大模型。

关键设计:工具回调一定在主循环执行,保证硬件操作(GPIO/I²C)的线程安全。


段 10:OTA 升级 — [图 8: OTA 流程]

它怎么能远程升级又不变砖?看这张图:

  1. Flash 切两个对称的 OTA 分区,ota_0 和 ota_1;
  2. 任何时候只跑一个,升级时把新固件下到另一个分区
  3. 下载完成 + SHA-256 校验 + 芯片型号检查通过;
  4. esp_ota_set_boot_partition 切启动指针 → 重启;
  5. 新固件启动第一件事是开 1 秒看门狗——如果跑得正常,调 MarkCurrentVersionValid() 关掉看门狗;
  6. 如果跑挂了(看门狗触发),Bootloader 自动回滚到上一个分区

这就是防砖的核心。再加上激活流程用 eFuse 内的 32 字节序列号 HMAC 签名,能防止山寨设备绕过认证。


段 11:配网 — [图 9: 三种配网]

三种配网方式可以同时启用:

  1. 热点配网:设备开 Wi-Fi 热点,手机连过去用浏览器填 SSID/密码——零依赖;
  2. BluFi 蓝牙配网:手机 App 用蓝牙发 SSID/密码——体验最好;
  3. 声波配网:手机喇叭播放 AFSK 调制的声音,设备麦克风听到解调——最神奇。

一套设备可以同时支持 3 种,给用户最大选择。


段 12:板级抽象 — [图 10: 板级类图]

110 块板子是怎么不打架的?看这个类图:

顶层是 Board 抽象基类,定义了"所有板子都得回答什么问题"——比如 GetAudioCodec() / GetDisplay()

第二层按网络栈分:

  • WifiBoard — Wi-Fi 板
  • Ml307Board — 4G 板
  • DualNetworkBoard — 双栈板(运行时切换走重启)

第三层是具体板子,比如 CompactWifiBoardEspBox3Board。每个板子的 .cc 文件末尾用 DECLARE_BOARD 宏注册自己,CMake 在编译期选其中一个,零运行时开销。

这种设计的好处:加新板子完全不影响其它 109 个板——只要新增一个目录、改一处 Kconfig、改一处 CMakeLists。


段 13:文件结构 — [图 11: 文件依赖]

这张图看清核心文件之间的依赖关系。application.cc 是中央枢纽,所有子系统都通过抽象接口接进来。

几个改动场景:

  • 加 MCP 工具:板子目录加一个 controller,独立的;
  • 加新协议protocols/ 加一个文件 + 改 Application 一行;
  • 加新音频 codeccodecs/ 加一个文件 + 改 CMakeLists;
  • 加新板子:boards/ 加一个目录,对其它板零影响。

这种"开口在抽象层、改动在具体实现"的架构是项目能撑住 110 块板的关键。


段 14:用到的技术(关键词列表)

整个项目用到的硬技术,按层从高到低:

  • C++ 17 现代特性std::functionstd::variantstd::optional、RAII、智能指针
  • FreeRTOS 多任务:Task、Queue、EventGroup、Notify、Mutex
  • 状态机模式 + 监听者模式
  • 抽象基类 + 多态:协议、Codec、Display、LED、Board 全用了
  • JSON-RPC 2.0 + MCP 协议
  • WebSocket / MQTT / UDP / HTTP
  • AES-128-CTR 流加密
  • HMAC-SHA256 签名 + eFuse 硬件密钥
  • A/B OTA 双分区 + 防回滚
  • ESP-SR 语音前端(AEC/VAD/NS/WakeNet)
  • Opus 编解码 + 重采样
  • LVGL 图形库 + 自研 EmoteEngine 动画
  • WS2812B(RMT 外设)+ PWM LED(LEDC 外设)
  • CMake 板子派发 + Kconfig 可视化配置
  • ESP-IDF Component Manager 包管理

段 15:可以扩展的方向

这套架构留了哪些扩展点?

  1. 加自家的 MCP 工具:注册到 McpServer 就行——比如让 LLM 控制家里的智能音箱、查股票、记事;
  2. 接私有 LLM:服务器侧改实现,设备端不变;
  3. 加多模态能力:摄像头已经做了 take_photo 工具,可以继续加视频流;
  4. 移植到非 ESP32 平台:只需要替换 Board 抽象层的实现;
  5. 加自定义唤醒词:项目已支持 Sherpa-ONNX 跑用户自训练的模型。