ファームウェアコードガイド:ディレクトリ構成、ステートマシン、開発ルール
XiaoZhi ファームウェアは ESP-IDF の C/C++ プロジェクトで、複数のチップ、ボード、ディスプレイ、オーディオデバイス、ネットワークチャネルに対応しています。1 回のビルドで選ばれるボード実装は 1 つだけです。このページは上流 v2.5.2 の AGENTS.md、main/audio/README.md、ソースに基づいており、コードに手を入れる前に正しい場所を見つけるためのものです。
1. ソースマップ
| 場所 | 役割 |
|---|---|
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 の 2 つの実装 |
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 | 公式推奨のボード/バリアントのビルド入口 |
2. 実行の流れ
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() などの入口を公開しています。ボード側のコード(ボタンのコールバックなど)は、これらを通じて動作を起動します。
3. ボード抽象化
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(クラス名) でエクスポートします。1 回のビルドにはちょうど 1 つ必要です。
ボード追加の完全な手順はカスタムボードガイドを参照してください。
4. オーディオ経路
オーディオは AudioService が管理します。コーデックの I/O、Opus のエンコード/デコード、ネットワークキューは、チップ固有の音声処理から分離されています。
- アップリンク:マイク →
AudioCodec→AudioInputTask→AudioEngineのいずれか 1 つ(S3 / P4 / S31 はAfeAudioEngine、それ以外のチップはLiteAudioEngine)→ 16 kHz モノラル PCM →OpusCodecTaskでエンコード → 送信キュー → ネットワーク。 - ダウンリンク:ネットワーク → デコードキュー →
OpusCodecTaskでデコード → 再生キュー →AudioOutputTask→ コーデック → スピーカー。 - Opus のフレーム長は 60 ms です。キューはコンパイル時に容量が固定されたストレージを使います。
エンジンとチップの対応はチップ対応とストレージ仕様を参照してください。
5. プロトコル層
Protocol は、オーディオチャネルの開閉、オーディオ送信、ウェイクワード送信、リスニングの開始/停止、再生の中断、MCP メッセージといった動作を抽象化します。実際のトランスポートは WebsocketProtocol と MqttProtocol です。プロトコルの詳細は WebSocket 通信プロトコル、MQTT + UDP ハイブリッドプロトコル、MCP プロトコルを参照してください。
6. 開発ルール
以下は上流の AGENTS.md からの抜粋です。コードを変更する前に確認してください。
- 変更は焦点を絞り、無関係なフォーマット変更を混ぜないでください。変更した C/C++ ファイルだけを、リポジトリの
.clang-formatで整形します。コードスタイルガイドを参照してください。 - 1 回のビルドでエクスポートする
DECLARE_BOARD(...)は、ちょうど 1 つにします。 - 別のハードウェアに合わせるために、既存ボードのピンを変更しないでください。ボード種別の識別子は 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など。
7. ビルドと検証
先に 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 -vscripts/build.py はローカルの sdkconfig とビルド状態を書き換えます。ボードを切り替えた後は、古いビルドディレクトリが以前のターゲットに対応していると考えないでください。
上流が求める検証は次のとおりです。
- ボード側コードのみの変更:影響を受けるバリアントをビルドし、実機でスモークテストを行う。
- コア、共通ボードコード、オーディオ、プロトコル、ディスプレイ、依存関係、Kconfig、CMake の変更:ホスト側テストを実行し、代表的なチップとネットワーク経路をビルドする。
- オーディオの変更:収音、再生、ウェイク/VAD、割り込み、再接続、該当する AEC モードを検証する。
- UI または assets の変更:ディスプレイなし/OLED/LVGL の各経路とパーティションサイズを検証する。
- ビルドの成功はハードウェア検証ではありません。報告時には、何をテストしたか、実機が必要なものは何かを明記してください。
関連ドキュメント
- ESP-IDF 開発環境のセットアップ:IDF v6.0.1 以上
- カスタムボードガイド:ボード追加の完全な手順
- 拡張ポイント:ネットワーク形態、ウェイクワード、MCP、通知、アセットのカスタマイズ
- トラブルシューティング:ビルド、書き込み、実行時の問題
出典: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。