固件代码导览:目录结构、状态机与开发规则

固件代码导览:目录结构、状态机与开发规则

小智固件是一个 ESP-IDF 的 C/C++ 工程,同时支持多种芯片、开发板、屏幕、音频器件和网络通道。一次构建只会选择一个开发板实现。本页按上游 v2.5.2 的 AGENTS.md、main/audio/README.md 和源码整理,帮你在动手改代码前先找对位置。

一、源码地图

位置职责
main/main.cc初始化 NVS,创建 Application 并进入 Run() 主循环
main/application.*主事件循环、协议生命周期、高层行为(唤醒、对话、升级、通知)
main/device_state_machine.*、device_state.h合法的运行状态及状态转换
main/boards/common/板级接口与可复用的硬件/网络辅助类
main/boards/<厂商>/<板>/每个板子的引脚、初始化和构建变体(config.h、config.json)
main/audio/编解码器、音频任务、输入引擎、唤醒词与队列
main/protocols/与传输无关的 Protocol 接口,以及 WebSocket、MQTT/UDP 两种实现
main/display/、main/led/LCD、OLED、表情显示与 LED 的通用实现
main/mcp_server.*设备端 MCP 工具及分发
main/ota.*、main/assets.*、main/settings.*OTA 版本检查与激活、固件升级、assets 分区、NVS 设置
main/Kconfig.projbuild、main/CMakeLists.txt板型与功能配置;源码、板卡、语言、字体和资源的选择
scripts/build.py官方推荐的板型/变体构建入口

二、运行流程

main.cc 初始化 NVS 后调用 Application::GetInstance().Initialize() 与 Run(),Run() 是永不返回的主事件循环。运行状态由 DeviceStateMachine 维护,DeviceState 共有以下取值:

状态含义
kDeviceStateStarting启动中
kDeviceStateWifiConfiguring配网模式,见首次启动
kDeviceStateIdle空闲,等待唤醒或按键
kDeviceStateConnecting正在建立服务器连接
kDeviceStateListening录音上行
kDeviceStateSpeaking播放回复
kDeviceStateNotifying通知提示
kDeviceStateUpgrading固件或资源升级中
kDeviceStateActivating激活流程
kDeviceStateAudioTesting音频测试
kDeviceStateFatalError致命错误

Application 对外提供 ToggleChatState()、StartListening()、StopListening()、WakeWordInvoke()、Alert()、Schedule()、SendMcpMessage() 等入口,板级代码(如按键回调)通过它们触发行为。

三、板级抽象

Board(main/boards/common/board.h)是核心代码依赖的接口,主要纯虚方法有 GetBoardType()、GetAudioCodec()、GetNetwork()、StartNetwork()、GetNetworkStateIcon()、SetPowerSaveLevel()、GetBoardJson()、GetDeviceStatusJson();背光、LED、显示、摄像头和电池则是可选能力。

boards/common/ 里提供了不同网络形态的基类,例如 WifiBoard、Ml307Board、DualNetworkBoard、EthernetBoard、RndisBoard、Nt26Board,以及按键、电源管理、休眠定时器等辅助类。每个具体板子继承其中一个基类,并用 DECLARE_BOARD(类名) 导出,构建中必须恰好有一个。

新增开发板的完整流程见自定义开发板指南。

四、音频路径

音频由 AudioService 管理:编解码器 I/O、Opus 编解码和网络队列与芯片相关的语音处理分离。

  • 上行:麦克风 → AudioCodec → AudioInputTask → 一个 AudioEngine(S3 / P4 / S31 为 AfeAudioEngine,其余芯片为 LiteAudioEngine)→ 16 kHz 单声道 PCM → OpusCodecTask 编码 → 发送队列 → 网络。
  • 下行:网络 → 解码队列 → OpusCodecTask 解码 → 播放队列 → AudioOutputTask → 编解码器 → 喇叭。
  • Opus 帧长 60 ms;队列使用编译期固定容量的存储。

引擎与芯片的对应关系见芯片支持与存储规格。

五、协议层

Protocol 抽象了开启/关闭音频通道、发送音频、发送唤醒词、开始/停止监听、中断播放和 MCP 消息等动作,具体传输由 WebsocketProtocol、MqttProtocol 实现。协议细节见 WebSocket 通信协议、MQTT + UDP 混合协议和 MCP 协议。

六、开发规则

以下规则摘自上游 AGENTS.md,改代码前请先对照:

  • 保持改动聚焦,不要夹带无关格式化;只对改动过的 C/C++ 文件按仓库 .clang-format 格式化,参见代码风格指南。
  • 一次构建必须恰好导出一个 DECLARE_BOARD(...)。
  • 不要为了适配别的硬件去改已有开发板的引脚;请新增一个名称唯一的板子或发布变体,因为板型标识会影响 OTA 兼容。
  • 核心代码只依赖 Board 接口,不依赖具体板类或某个板的 config.h。
  • 摄像头、背光、显示、LED、电池等都按可选能力处理。
  • 运行状态只通过 Application::SetDeviceState() 和状态机修改。
  • 回调可能不在主任务中运行:用 Application::Schedule() 或事件位切回主任务再修改应用状态。
  • 不要阻塞主事件循环和音频任务;音频路径避免无界队列与反复的大块分配。
  • 共享的消息语义放在 Protocol 中;改动协议契约时要同时验证 WebSocket 和 MQTT/UDP。
  • 校验网络输入,保持 cJSON 的所有权约定;NVS 键属于持久化 API,修改需要迁移。
  • 目标相关的功能用 Kconfig 或组件规则约束,不要假定每个芯片都有 PSRAM 或 S3/P4 的资源。
  • 不要手工修改生成物或第三方输出:build/、releases/、managed_components/、components/、sdkconfig*、main/assets/lang_config.h 等。

七、构建与验证

构建前先加载 ESP-IDF 环境(source /path/to/esp-idf/export.sh,用 idf.py --version 确认),再使用上游推荐的命令:

# Discover exact board and variant names
python3 scripts/build.py --list-boards

# Canonical variant build
python3 scripts/build.py <board-directory> --name <variant-name>

# Host-side build tests
python3 -m unittest discover -s scripts/tests -v

scripts/build.py 会改写本地 sdkconfig 和构建状态,切换板子后不要假定旧的构建目录还对应上一个目标。

上游对验证的要求是:

  • 只改板级代码:构建受影响的变体,并在实体硬件上做冒烟测试。
  • 改核心、通用板级、音频、协议、显示、依赖、Kconfig 或 CMake:运行主机侧测试,并构建有代表性的芯片与网络路径。
  • 改音频:验证采集、播放、唤醒/VAD、打断、重连以及适用的 AEC 模式。
  • 改 UI 或 assets:验证无屏/OLED/LVGL 各路径与分区大小。
  • 构建成功不等于硬件验证,汇报时要写明测了什么、哪些还需要实体硬件。

相关文档

对照来源:78/xiaozhi-esp32 v2.5.2 的 AGENTS.md、main/audio/README.md、main/application.h、main/device_state.h、main/boards/common/board.h、main/protocols/protocol.h。