WebSocket 通訊協議
以下是一份基於程式碼實現整理的 WebSocket 通訊協議文件,概述裝置端與伺服器之間如何透過 WebSocket 進行互動。
該文件僅基於所提供的程式碼推斷,實際部署時可能需要結合伺服器端實現進行進一步確認或補充。
1. 總體流程概覽
裝置端初始化
- 裝置上電、初始化
Application:- 初始化音訊編解碼器、顯示屏、LED 等
- 連線網路
- 建立並初始化實現
Protocol介面的 WebSocket 協議例項(WebsocketProtocol)
- 進入主迴圈等待事件(音訊輸入、音訊輸出、排程任務等)。
- 裝置上電、初始化
建立 WebSocket 連線
- 當裝置需要開始語音會話時(例如使用者喚醒、手動按鍵觸發等),呼叫
OpenAudioChannel():- 根據配置獲取 WebSocket URL
- 設定若干請求頭(
Authorization,Protocol-Version,Device-Id,Client-Id) - 呼叫
Connect()與伺服器建立 WebSocket 連線
- 當裝置需要開始語音會話時(例如使用者喚醒、手動按鍵觸發等),呼叫
裝置端傳送 “hello” 訊息
- 連線成功後,裝置會傳送一條 JSON 訊息,示例結構如下:
{ "type": "hello", "version": 1, "features": { "mcp": true, "glyph_push": true }, "text_font": { "bundle": "noto-v1", "charset": "common", "size": 20, "bpp": 4 }, "transport": "websocket", "audio_params": { "format": "opus", "sample_rate": 16000, "channels": 1, "frame_duration": 60 } }- 其中
features欄位為可選,內容根據裝置編譯配置自動生成。例如:"mcp": true表示支援 MCP 協議。 "glyph_push": true和text_font宣告可選的動態文字 glyph 擴充,詳見動態文字 Glyph Push 擴充。frame_duration的值對應OPUS_FRAME_DURATION_MS(例如 60ms)。
伺服器回覆 “hello”
- 裝置等待伺服器返回一條包含
"type": "hello"的 JSON 訊息,並檢查"transport": "websocket"是否匹配。 - 伺服器可選下發
session_id欄位,裝置端收到後會自動記錄。 - 示例:
{ "type": "hello", "transport": "websocket", "session_id": "xxx", "audio_params": { "format": "opus", "sample_rate": 24000, "channels": 1, "frame_duration": 60 } }- 如果匹配,則認為伺服器已就緒,標記音訊通道開啟成功。
- 如果在超時時間(預設 10 秒)內未收到正確回覆,認為連線失敗並觸發網路錯誤回撥。
- 裝置等待伺服器返回一條包含
後續訊息互動
裝置端和伺服器端之間可傳送兩種主要型別的資料:
- 二進位制音訊資料(Opus 編碼)
- 文字 JSON 訊息(用於傳輸聊天狀態、TTS/STT 事件、MCP 協議訊息等)
在程式碼裡,接收回撥主要分為:
OnData(...):- 當
binary為true時,認為是音訊幀;裝置會將其當作 Opus 資料進行解碼。 - 當
binary為false時,認為是 JSON 文字,需要在裝置端用 cJSON 進行解析並做相應業務邏輯處理(如聊天、TTS、MCP 協議訊息等)。
- 當
當伺服器或網路出現斷連,回撥
OnDisconnected()被觸發:- 裝置會呼叫
on_audio_channel_closed_(),並最終回到空閒狀態。
- 裝置會呼叫
關閉 WebSocket 連線
- 裝置在需要結束語音會話時,會呼叫
CloseAudioChannel()主動斷開連線,並回到空閒狀態。 - 或者如果伺服器端主動斷開,也會引發同樣的回撥流程。
- 裝置在需要結束語音會話時,會呼叫
2. 通用請求頭
在建立 WebSocket 連線時,程式碼示例中設定了以下請求頭:
Authorization: 用於存放訪問令牌,形如"Bearer <token>"Protocol-Version: 協議版本號,與 hello 訊息體內的version欄位保持一致Device-Id: 裝置物理網絡卡 MAC 地址Client-Id: 軟體生成的 UUID(擦除 NVS 或重新燒錄完整韌體會重置)
這些頭會隨著 WebSocket 握手一起傳送到伺服器,伺服器可根據需求進行校驗、認證等。
3. 二進位制協議版本
裝置支援多種二進位制協議版本,透過配置中的 version 欄位指定:
3.1 版本1(預設)
直接傳送 Opus 音訊資料,無額外後設資料。Websocket 協議會區分 text 與 binary。
3.2 版本2
使用 BinaryProtocol2 結構:
struct BinaryProtocol2 {
uint16_t version; // 協議版本
uint16_t type; // 訊息型別 (0: OPUS, 1: JSON)
uint32_t reserved; // 保留欄位
uint32_t timestamp; // 時間戳(毫秒,用於伺服器端AEC)
uint32_t payload_size; // 負載大小(位元組)
uint8_t payload[]; // 負載資料
} __attribute__((packed));3.3 版本3
使用 BinaryProtocol3 結構:
struct BinaryProtocol3 {
uint8_t type; // 訊息型別
uint8_t reserved; // 保留欄位
uint16_t payload_size; // 負載大小
uint8_t payload[]; // 負載資料
} __attribute__((packed));4. JSON 訊息結構
WebSocket 文字幀以 JSON 方式傳輸,以下為常見的 "type" 欄位及其對應業務邏輯。若訊息裡包含未列出的欄位,可能為可選或特定實現細節。
4.1 裝置端→伺服器
Hello
- 連線成功後,由裝置端傳送,告知伺服器基本參數。
- 例:
{ "type": "hello", "version": 1, "features": { "mcp": true }, "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
- 用於裝置端向伺服器告知檢測到喚醒詞。
- 在傳送該訊息之前,可提前傳送喚醒詞的 Opus 音訊資料,用於伺服器進行聲紋檢測。
- 例:
{ "session_id": "xxx", "type": "listen", "state": "detect", "text": "你好小明" }
MCP
推薦用於物聯網控制的新一代協議。所有裝置能力發現、工具呼叫等均透過 type: “mcp” 的訊息進行,payload 內部為標準 JSON-RPC 2.0(詳見 MCP 協議文件)。
裝置端到伺服器傳送 result 的例子:
{ "session_id": "xxx", "type": "mcp", "payload": { "jsonrpc": "2.0", "id": 1, "result": { "content": [ { "type": "text", "text": "true" } ], "isError": false } } }
4.2 伺服器→裝置端
Hello
- 伺服器端返回的握手確認訊息。
- 必須包含
"type": "hello"和"transport": "websocket"。 - 可能會帶有
audio_params,表示伺服器期望的音訊參數,或與裝置端對齊的配置。 - 伺服器可選下發
session_id欄位,裝置端收到後會自動記錄。 - 成功接收後裝置端會設定事件標誌,表示 WebSocket 通道就緒。
STT
{"session_id": "xxx", "type": "stt", "text": "..."}- 表示伺服器端識別到了使用者語音。(例如語音轉文字結果)
- 裝置可能將此文字顯示到螢幕上,後續再進入回答等流程。
LLM
{"session_id": "xxx", "type": "llm", "emotion": "happy", "text": "😀"}- 伺服器指示裝置調整表情動畫 / UI 表達。
TTS
{"session_id": "xxx", "type": "tts", "state": "start"}:伺服器準備下發 TTS 音訊,裝置端進入 “speaking” 播放狀態。{"session_id": "xxx", "type": "tts", "state": "stop"}:表示本次 TTS 結束。{"session_id": "xxx", "type": "tts", "state": "sentence_start", "text": "..."}- 讓裝置在介面上顯示當前要播放或朗讀的文字片段(例如用於顯示給使用者)。
MCP
伺服器透過 type: “mcp” 的訊息下發物聯網相關的控制指令或返回撥用結果,payload 結構同上。
伺服器到裝置端傳送 tools/call 的例子:
{ "session_id": "xxx", "type": "mcp", "payload": { "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "self.light.set_rgb", "arguments": { "r": 255, "g": 0, "b": 0 } }, "id": 1 } }
System
- 系統控制命令,常用於遠端升級更新。
- 例:
{ "session_id": "xxx", "type": "system", "command": "reboot" } - 支援的命令:
"reboot":重啟裝置
Custom(可選)
- 自定義訊息,當
CONFIG_RECEIVE_CUSTOM_MESSAGE啟用時支援。 - 例:
{ "session_id": "xxx", "type": "custom", "payload": { "message": "自定義內容" } }
- 自定義訊息,當
音訊資料:二進位制幀
- 當伺服器傳送音訊二進位制幀(Opus 編碼)時,裝置端解碼並播放。
- 若裝置端正在處於 “listening” (錄音)狀態,收到的音訊幀會被忽略或清空以防衝突。
5. 音訊編解碼
裝置端傳送錄音資料
- 音訊輸入經過可能的回聲消除、降噪或音量增益後,透過 Opus 編碼打包為二進位制幀傳送給伺服器。
- 根據協議版本,可能直接傳送 Opus 資料(版本1)或使用帶後設資料的二進位制協議(版本2/3)。
裝置端播放收到的音訊
- 收到伺服器的二進位制幀時,同樣認定是 Opus 資料。
- 裝置端會進行解碼,然後交由音訊輸出介面播放。
- 如果伺服器的音訊取樣率與裝置不一致,會在解碼後再進行重取樣。
6. 常見狀態流轉
以下為常見裝置端關鍵狀態流轉,與 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。
- 呼叫
自動模式狀態流轉圖
stateDiagram direction TB [*] --> kDeviceStateUnknown kDeviceStateUnknown --> kDeviceStateStarting:初始化 kDeviceStateStarting --> kDeviceStateWifiConfiguring:配置WiFi kDeviceStateStarting --> kDeviceStateActivating:啟用裝置 kDeviceStateActivating --> kDeviceStateUpgrading:檢測到新版本 kDeviceStateActivating --> kDeviceStateIdle:啟用完成 kDeviceStateIdle --> kDeviceStateConnecting:開始連線 kDeviceStateConnecting --> kDeviceStateIdle:連線失敗 kDeviceStateConnecting --> kDeviceStateListening:連線成功 kDeviceStateListening --> kDeviceStateSpeaking:開始說話 kDeviceStateSpeaking --> kDeviceStateListening:結束說話 kDeviceStateListening --> kDeviceStateIdle:手動終止 kDeviceStateSpeaking --> kDeviceStateIdle:自動終止
手動模式狀態流轉圖
stateDiagram direction TB [*] --> kDeviceStateUnknown kDeviceStateUnknown --> kDeviceStateStarting:初始化 kDeviceStateStarting --> kDeviceStateWifiConfiguring:配置WiFi kDeviceStateStarting --> kDeviceStateActivating:啟用裝置 kDeviceStateActivating --> kDeviceStateUpgrading:檢測到新版本 kDeviceStateActivating --> kDeviceStateIdle:啟用完成 kDeviceStateIdle --> kDeviceStateConnecting:開始連線 kDeviceStateConnecting --> kDeviceStateIdle:連線失敗 kDeviceStateConnecting --> kDeviceStateListening:連線成功 kDeviceStateIdle --> kDeviceStateListening:開始監聽 kDeviceStateListening --> kDeviceStateIdle:停止監聽 kDeviceStateIdle --> kDeviceStateSpeaking:開始說話 kDeviceStateSpeaking --> kDeviceStateIdle:結束說話
7. 錯誤處理
連線失敗
- 如果
Connect(url)返回失敗或在等待伺服器 “hello” 訊息時超時,觸發on_network_error_()回撥。裝置會提示"無法連線到服務"或類似錯誤資訊。
- 如果
伺服器斷開
- 如果 WebSocket 異常斷開,回撥
OnDisconnected():- 裝置回撥
on_audio_channel_closed_() - 切換到 Idle 或其他重試邏輯。
- 裝置回撥
- 如果 WebSocket 異常斷開,回撥
8. 其它注意事項
鑑權
- 裝置透過設定
Authorization: Bearer <token>提供鑑權,伺服器端需驗證是否有效。 - 如果令牌過期或無效,伺服器可拒絕握手或在後續斷開。
- 裝置透過設定
會話控制
- 程式碼中部分訊息包含
session_id,用於區分獨立的對話或操作。服務端可根據需要對不同會話做分離處理。
- 程式碼中部分訊息包含
音訊負載
- 程式碼裡預設使用 Opus 格式,並設定
sample_rate = 16000,單聲道。幀時長由OPUS_FRAME_DURATION_MS控制,一般為 60ms。可根據頻寬或效能做適當調整。為了獲得更好的音樂播放效果,伺服器下行音訊可能使用 24000 取樣率。
- 程式碼裡預設使用 Opus 格式,並設定
協議版本配置
- 透過設定中的
version欄位配置二進位制協議版本(1、2 或 3) - 版本1:直接傳送 Opus 資料
- 版本2:使用帶時間戳的二進位制協議,適用於伺服器端 AEC
- 版本3:使用簡化的二進位制協議
- 透過設定中的
物聯網控制推薦 MCP 協議
- 裝置與伺服器之間的物聯網能力發現、狀態同步、控制指令等,建議全部透過 MCP 協議(type: “mcp”)實現。原有的 type: “iot” 方案已廢棄。
- MCP 協議可在 WebSocket、MQTT 等多種底層協議上傳輸,具備更好的擴充性和標準化能力。
- 詳細用法請參考 MCP 協議文件 及 MCP 物聯網控制用法。
錯誤或異常 JSON
- 當 JSON 中缺少必要欄位,例如
{"type": ...},裝置端會記錄錯誤日誌(ESP_LOGE(TAG, "Missing message type, data: %s", data);),不會執行任何業務。
- 當 JSON 中缺少必要欄位,例如
9. 訊息示例
下面給出一個典型的雙向訊息示例(流程簡化示意):
裝置端 → 伺服器(握手)
{ "type": "hello", "version": 1, "features": { "mcp": true }, "transport": "websocket", "audio_params": { "format": "opus", "sample_rate": 16000, "channels": 1, "frame_duration": 60 } }伺服器 → 裝置端(握手應答)
{ "type": "hello", "transport": "websocket", "session_id": "xxx", "audio_params": { "format": "opus", "sample_rate": 16000 } }裝置端 → 伺服器(開始監聽)
{ "session_id": "xxx", "type": "listen", "state": "start", "mode": "auto" }同時裝置端開始傳送二進位制幀(Opus 資料)。
伺服器 → 裝置端(ASR 結果)
{ "session_id": "xxx", "type": "stt", "text": "使用者說的話" }伺服器 → 裝置端(TTS開始)
{ "session_id": "xxx", "type": "tts", "state": "start" }接著伺服器傳送二進位制音訊幀給裝置端播放。
伺服器 → 裝置端(TTS結束)
{ "session_id": "xxx", "type": "tts", "state": "stop" }裝置端停止播放音訊,若無更多指令,則回到空閒狀態。
10. 總結
本協議透過在 WebSocket 上層傳輸 JSON 文字與二進位制音訊幀,完成功能包括音訊流上傳、TTS 音訊播放、語音識別與狀態管理、MCP 指令下發等。其核心特徵:
- 握手階段:傳送
"type":"hello",等待伺服器返回。 - 音訊通道:採用 Opus 編碼的二進位制幀雙向傳輸語音流,支援多種協議版本。
- JSON 訊息:使用
"type"為核心欄位標識不同業務邏輯,包括 TTS、STT、MCP、WakeWord、System、Custom 等。 - 擴充性:可根據實際需求在 JSON 訊息中新增欄位,或在 headers 裡進行額外鑑權。
伺服器與裝置端需提前約定各類訊息的欄位含義、時序邏輯以及錯誤處理規則,方能保證通訊順暢。上述資訊可作為基礎文件,便於後續對接、開發或擴充。