ファームウェアコードガイド:ディレクトリ構成、ステートマシン、開発ルール

ファームウェアコードガイド:ディレクトリ構成、ステートマシン、開発ルール

XiaoZhi ファームウェアは ESP-IDF の C/C++ プロジェクトで、複数のチップ、ボード、ディスプレイ、オーディオデバイス、ネットワークチャネルに対応しています。1 回のビルドで選ばれるボード実装は 1 つだけです。このページは上流 v2.5.2 の AGENTS.md、main/audio/README.md、ソースに基づいており、コードに手を入れる前に正しい場所を見つけるためのものです。

1. ソースマップ

場所役割
main/main.ccNVS を初期化し、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 -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。