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 秒)內未收到正確回覆,認為連線失敗並觸發網路錯誤回呼。
- 裝置等待伺服器回傳一條包含
後續訊息互動
裝置端和伺服器端之間可傳送兩種主要類型的資料:
- 二進位音訊資料(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
- 傳送當前裝置的物聯網相關資訊:
- Descriptors(描述裝置功能、屬性等)
- States(裝置狀態的即時更新)
- 例:或
{ "session_id": "xxx", "type": "iot", "descriptors": { ... } }
{ "session_id": "xxx", "type": "iot", "states": { ... } }
- 傳送當前裝置的物聯網相關資訊:
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": [ ... ]}
- 伺服器向裝置傳送物聯網的動作指令,裝置解析並執行(如開啟燈、設定溫度等)。
音訊資料:二進位幀
- 當伺服器傳送音訊二進位幀(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 裡進行額外鑑權。
伺服器與客戶端需提前約定各類訊息的欄位含義、時序邏輯以及錯誤處理規則,方能保證通訊順暢。上述資訊可作為基礎文件,便於後續對接、開發或擴充。