WebSocket通信プロトコル
以下は、コード実装に基づいて整理されたWebSocket通信プロトコル文書で、クライアント(デバイス)とサーバー間のWebSocketを通じた相互作用方法を概説しています。この文書は提供されたコードのみから推測されており、実際の展開時にはサーバー側実装と組み合わせてさらなる確認や補完が必要になる場合があります。
1. 全体プロセス概要
デバイス側初期化
- デバイス起動、
Application
初期化:- オーディオコーデック、ディスプレイ、LEDなどを初期化
- ネットワークに接続
Protocol
インターフェースを実装するWebSocketプロトコルインスタンス(WebsocketProtocol
)を作成・初期化
- イベント待機のメインループに入る(オーディオ入力、オーディオ出力、スケジュールタスクなど)。
- デバイス起動、
WebSocket接続の確立
- デバイスが音声セッションを開始する必要がある時(ユーザーの起動、手動ボタントリガーなど)、
OpenAudioChannel()
を呼び出す:- コンパイル設定からWebSocket URL(
CONFIG_WEBSOCKET_URL
)を取得 - いくつかのリクエストヘッダー(
Authorization
,Protocol-Version
,Device-Id
,Client-Id
)を設定 Connect()
を呼び出してサーバーとのWebSocket接続を確立
- コンパイル設定からWebSocket URL(
- デバイスが音声セッションを開始する必要がある時(ユーザーの起動、手動ボタントリガーなど)、
クライアント"hello"メッセージの送信
- 接続成功後、デバイスは以下の例構造でJSONメッセージを送信:
{ "type": "hello", "version": 1, "transport": "websocket", "audio_params": { "format": "opus", "sample_rate": 16000, "channels": 1, "frame_duration": 60 } }
"frame_duration"
の値はOPUS_FRAME_DURATION_MS
(例:60ms)に対応。
サーバー"hello"応答
- デバイスはサーバーから
"type": "hello"
を含むJSONメッセージの返信を待ち、"transport": "websocket"
が一致するかチェック。 - 一致すればサーバーが準備完了と判断され、オーディオチャネル開通が成功とマーク。
- タイムアウト時間(デフォルト10秒)内に正しい応答を受信しなければ、接続失敗と見なしてネットワークエラーコールバックをトリガー。
- デバイスはサーバーから
その後のメッセージ相互作用
デバイスとサーバー間で送信可能な主要なデータタイプは2つ:
- バイナリオーディオデータ(Opusエンコード)
- テキストJSONメッセージ(チャットステータス、TTS/STTイベント、IoTコマンドなどの送信用)
コード内で、受信コールバックは主に以下に分かれる:
OnData(...)
:binary
がtrue
の時、オーディオフレームと判断;デバイスはOpusデータとしてデコード処理。binary
がfalse
の時、JSONテキストと判断、デバイス側でcJSONを使って解析し、対応する業務ロジック処理が必要(以下のメッセージ構造参照)。
サーバーまたはネットワークが切断された時、
OnDisconnected()
コールバックがトリガー:- デバイスは
on_audio_channel_closed_()
を呼び出し、最終的にアイドル状態に戻る。
- デバイスは
WebSocket接続の切断
- デバイスが音声セッション終了を必要とする時、
CloseAudioChannel()
を呼び出して能動的に切断し、アイドル状態に戻る。 - またはサーバー側が能動的に切断しても、同様のコールバックフローが引き起こされる。
- デバイスが音声セッション終了を必要とする時、
2. 共通リクエストヘッダー
WebSocket接続確立時、コード例では以下のリクエストヘッダーを設定:
Authorization
: アクセストークンを格納、"Bearer <token>"
形式Protocol-Version
: 例では固定で"1"
Device-Id
: デバイス物理ネットワークカードMACアドレスClient-Id
: デバイスUUID(アプリケーション内でデバイスを一意識別可能)
これらのヘッダーはWebSocketハンドシェイクと共にサーバーに送信され、サーバーは必要に応じて検証、認証などを実行可能。
3. JSONメッセージ構造
WebSocketテキストフレームはJSON形式で送信。以下は一般的な"type"
フィールドとその対応する業務ロジック。リストにない他のフィールドは、オプションまたは特定の実装詳細の可能性がある。
3.1 クライアント→サーバー
Hello
- 接続成功後、クライアントが送信し、サーバーに基本パラメータを通知。
- 例:
{ "type": "hello", "version": 1, "transport": "websocket", "audio_params": { "format": "opus", "sample_rate": 16000, "channels": 1, "frame_duration": 60 } }
Listen
- クライアントが録音リスニングの開始または停止を示す。
- 共通フィールド:
"session_id"
: セッション識別子"type": "listen"
"state"
:"start"
,"stop"
,"detect"
(起動検出がトリガーされた)"mode"
:"auto"
,"manual"
または"realtime"
、認識モードを示す。
- 例:リスニング開始
{ "session_id": "xxx", "type": "listen", "state": "start", "mode": "manual" }
Abort
- 現在の発話(TTS再生)または音声チャネルを終了。
- 例:
{ "session_id": "xxx", "type": "abort", "reason": "wake_word_detected" }
reason
値は"wake_word_detected"
またはその他可能。
Wake Word Detected
- クライアントがサーバーに起動語検出を通知するため使用。
- 例:
{ "session_id": "xxx", "type": "listen", "state": "detect", "text": "こんにちは小明" }
IoT
- 現在のデバイスのIoT関連情報を送信:
- Descriptors(デバイス機能、属性などの説明)
- States(デバイス状態のリアルタイム更新)
- 例:または
{ "session_id": "xxx", "type": "iot", "descriptors": { ... } }
{ "session_id": "xxx", "type": "iot", "states": { ... } }
- 現在のデバイスのIoT関連情報を送信:
3.2 サーバー→クライアント
Hello
- サーバー側から返されるハンドシェイク確認メッセージ。
"type": "hello"
と"transport": "websocket"
を含む必要がある。audio_params
を含む場合があり、サーバーが期待するオーディオパラメータまたはクライアントと揃えた設定を示す。- 成功受信後、クライアントはイベントフラグを設定し、WebSocketチャネル準備完了を示す。
STT
{"type": "stt", "text": "..."}
- サーバー側がユーザー音声を認識したことを示す(例:音声テキスト変換結果)
- デバイスはこのテキストを画面に表示し、その後回答などのフローに進む可能性がある。
LLM
{"type": "llm", "emotion": "happy", "text": "😀"}
- サーバーがデバイスに表情アニメーション/UI表現の調整を指示。
TTS
{"type": "tts", "state": "start"}
: サーバーがTTSオーディオの送信を準備、クライアントは"speaking"再生状態に入る。{"type": "tts", "state": "stop"}
: 今回のTTS終了を示す。{"type": "tts", "state": "sentence_start", "text": "..."}
- デバイスのインターフェースに現在再生または読み上げ予定のテキストセグメントを表示させる(例:ユーザーへの表示用)。
IoT
{"type": "iot", "commands": [ ... ]}
- サーバーがデバイスにIoTアクション指令を送信、デバイスが解析・実行(例:ライト点灯、温度設定など)。
オーディオデータ:バイナリフレーム
- サーバーがオーディオバイナリフレーム(Opusエンコード)を送信する時、クライアントはデコードして再生。
- クライアントが"listening"(録音)状態にある場合、受信したオーディオフレームは競合防止のため無視またはクリア。
4. オーディオエンコード/デコード
クライアント録音データ送信
- オーディオ入力は、可能なエコーキャンセレーション、ノイズリダクション、音量ゲイン後、Opusエンコードでバイナリフレームにパッケージングしてサーバーに送信。
- クライアントが毎回エンコードでNバイトのバイナリフレームを生成する場合、WebSocketのbinaryメッセージでこのデータを送信。
クライアント受信オーディオ再生
- サーバーからバイナリフレームを受信した時も、同様にOpusデータと判断。
- デバイス側はデコードを実行し、その後オーディオ出力インターフェースに渡して再生。
- サーバーのオーディオサンプリングレートがデバイスと一致しない場合、デコード後に再サンプリングを実行。
5. 一般的な状態遷移
以下、デバイス側のキー状態遷移を簡潔に説明し、WebSocketメッセージに対応:
Idle → Connecting
- ユーザートリガーまたは起動後、デバイスが
OpenAudioChannel()
を呼び出し → WebSocket接続確立 →"type":"hello"
送信。
- ユーザートリガーまたは起動後、デバイスが
Connecting → Listening
- 接続確立成功後、
SendStartListening(...)
を継続実行すれば、録音状態に入る。この時デバイスはマイクデータを継続的にエンコードしてサーバーに送信。
- 接続確立成功後、
Listening → Speaking
- サーバーTTS Startメッセージ(
{"type":"tts","state":"start"}
)を受信 → 録音停止して受信オーディオを再生。
- サーバーTTS Startメッセージ(
Speaking → Idle
- サーバーTTS Stop(
{"type":"tts","state":"stop"}
)→ オーディオ再生終了。自動リスニング継続がなければIdleに戻る;自動ループ設定があれば再びListeningに入る。
- サーバーTTS Stop(
Listening / Speaking → Idle(例外遭遇または能動中断)
SendAbortSpeaking(...)
またはCloseAudioChannel()
呼び出し → セッション中断 → WebSocket切断 → 状態がIdleに戻る。
6. エラー処理
接続失敗
Connect(url)
が失敗を返すか、サーバー"hello"メッセージ待機時にタイムアウトした場合、on_network_error_()
コールバックをトリガー。デバイスは「サービスに接続できません」または類似のエラーメッセージをプロンプト。
サーバー切断
- WebSocketが異常切断した場合、
OnDisconnected()
コールバック:- デバイスは
on_audio_channel_closed_()
をコールバック - Idleまたはその他のリトライロジックに切り替え。
- デバイスは
- WebSocketが異常切断した場合、
7. その他の注意事項
認証
- デバイスは
Authorization: Bearer <token>
設定で認証を提供、サーバー側は有効性を検証する必要がある。 - トークンが期限切れまたは無効の場合、サーバーはハンドシェイクを拒否または後続で切断可能。
- デバイスは
セッション制御
- コード内の一部メッセージに
session_id
が含まれ、独立した対話または操作を区別するために使用。サーバー側は必要に応じて異なるセッションに対して分離処理を行える、WebSocketプロトコルは空。
- コード内の一部メッセージに
オーディオペイロード
- コードではデフォルトでOpus形式を使用し、
sample_rate = 16000
、モノラル設定。フレーム持続時間はOPUS_FRAME_DURATION_MS
で制御、一般的に60ms。帯域幅または性能に基づいて適当な調整可能。
- コードではデフォルトでOpus形式を使用し、
IoTコマンド
"type":"iot"
メッセージのユーザー側コードはthing_manager
と接続して具体的なコマンドを実行、デバイスカスタマイズによって異なる。サーバー側は送信形式がクライアント側と一致保持を確保する必要がある。
エラーまたは例外JSON
- JSONに必要フィールドが欠けている場合、例:
{"type": ...}
、クライアントはエラーログを記録(ESP_LOGE(TAG, "Missing message type, data: %s", data);
)、いかなる業務も実行しない。
- JSONに必要フィールドが欠けている場合、例:
8. メッセージ例
以下は典型的な双方向メッセージ例(簡略化フロー説明):
クライアント → サーバー(ハンドシェイク)
{ "type": "hello", "version": 1, "transport": "websocket", "audio_params": { "format": "opus", "sample_rate": 16000, "channels": 1, "frame_duration": 60 } }
サーバー → クライアント(ハンドシェイク応答)
{ "type": "hello", "transport": "websocket", "audio_params": { "sample_rate": 16000 } }
クライアント → サーバー(リスニング開始)
{ "session_id": "", "type": "listen", "state": "start", "mode": "auto" }
同時にクライアントはバイナリフレーム(Opusデータ)の送信を開始。
サーバー → クライアント(ASR結果)
{ "type": "stt", "text": "ユーザーが言った言葉" }
サーバー → クライアント(TTS開始)
{ "type": "tts", "state": "start" }
その後サーバーはクライアント再生用にバイナリオーディオフレームを送信。
サーバー → クライアント(TTS終了)
{ "type": "tts", "state": "stop" }
クライアントはオーディオ再生を停止、追加指令がなければアイドル状態に戻る。
9. まとめ
このプロトコルはWebSocket上でJSONテキストとバイナリオーディオフレームを送信し、オーディオストリームアップロード、TTS オーディオ再生、音声認識と状態管理、IoTコマンド配信などの機能を完成させます。コア特徴:
- ハンドシェイクフェーズ:
"type":"hello"
送信、サーバー返信待機。 - オーディオチャネル: Opusエンコードのバイナリフレームで双方向音声ストリーム送信を使用。
- JSONメッセージ:
"type"
をコアフィールドとして異なる業務ロジックを識別、TTS、STT、IoT、WakeWordなど含む。 - 拡張性: 実際の需要に基づいてJSONメッセージにフィールドを追加、またはheadersで追加認証可能。
サーバーとクライアントは事前に各種メッセージのフィールド意味、タイミングロジック、エラー処理ルールについて合意する必要があり、スムーズな通信を保証できます。上記情報は基礎文書として、後続の接続、開発、拡張に便利です。