WebSocket 通訊協議

以下是一份基於程式碼實作整理的 WebSocket 通訊協議文件,概述客戶端(裝置)與伺服器之間如何透過 WebSocket 進行互動。該文件僅基於所提供的程式碼推斷,實際部署時可能需要結合伺服器端實作進行進一步確認或補充。


1. 總體流程概覽

  1. 裝置端初始化

    • 裝置上電、初始化 Application
      • 初始化音訊編解碼器、顯示螢幕、LED 等
      • 連接網路
      • 建立並初始化實作 Protocol 介面的 WebSocket 協議執行個體(WebsocketProtocol
    • 進入主迴圈等待事件(音訊輸入、音訊輸出、排程任務等)。
  2. 建立 WebSocket 連線

    • 當裝置需要開始語音會話時(例如使用者喚醒、手動按鍵觸發等),呼叫 OpenAudioChannel()
      • 根據編譯設定獲取 WebSocket URL(CONFIG_WEBSOCKET_URL
      • 設定若干請求標頭(Authorization, Protocol-Version, Device-Id, Client-Id
      • 呼叫 Connect() 與伺服器建立 WebSocket 連線
  3. 傳送客戶端 “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)。
  4. 伺服器回覆 “hello”

    • 裝置等待伺服器回傳一條包含 "type": "hello" 的 JSON 訊息,並檢查 "transport": "websocket" 是否匹配。
    • 如果匹配,則認為伺服器已就緒,標記音訊通道開啟成功。
    • 如果在逾時時間(預設 10 秒)內未收到正確回覆,認為連線失敗並觸發網路錯誤回呼。
  5. 後續訊息互動

    • 裝置端和伺服器端之間可傳送兩種主要類型的資料:

      1. 二進位音訊資料(Opus 編碼)
      2. 文字 JSON 訊息(用於傳輸聊天狀態、TTS/STT 事件、IoT 指令等)
    • 在程式碼裡,接收回呼主要分為:

      • OnData(...):
        • binarytrue 時,認為是音訊幀;裝置會將其當作 Opus 資料進行解碼。
        • binaryfalse 時,認為是 JSON 文字,需要在裝置端用 cJSON 進行解析並做相應業務邏輯處理(見下文訊息結構)。
    • 當伺服器或網路出現斷連,回呼 OnDisconnected() 被觸發:

      • 裝置會呼叫 on_audio_channel_closed_(),並最終回到閒置狀態。
  6. 關閉 WebSocket 連線

    • 裝置在需要結束語音會話時,會呼叫 CloseAudioChannel() 主動斷開連線,並回到閒置狀態。
    • 或者如果伺服器端主動斷開,也會引發同樣的回呼流程。

2. 通用請求標頭

在建立 WebSocket 連線時,程式碼範例中設定了以下請求標頭:

  • Authorization: 用於存放存取權杖,形如 "Bearer <token>"
  • Protocol-Version: 固定範例中為 "1"
  • Device-Id: 裝置物理網路卡 MAC 位址
  • Client-Id: 裝置 UUID(可在應用中唯一標識裝置)

這些標頭會隨著 WebSocket 握手一起傳送到伺服器,伺服器可根據需求進行校驗、認證等。


3. JSON 訊息結構

WebSocket 文字幀以 JSON 方式傳輸,以下為常見的 "type" 欄位及其對應業務邏輯。若訊息裡包含未列出的欄位,可能為可選或特定實作細節。

3.1 客戶端→伺服器

  1. Hello

    • 連線成功後,由客戶端傳送,告知伺服器基本參數。
    • 例:
      {
        "type": "hello",
        "version": 1,
        "transport": "websocket",
        "audio_params": {
          "format": "opus",
          "sample_rate": 16000,
          "channels": 1,
          "frame_duration": 60
        }
      }
  2. Listen

    • 表示客戶端開始或停止錄音監聽。
    • 常見欄位:
      • "session_id":會話標識
      • "type": "listen"
      • "state""start", "stop", "detect"(喚醒檢測已觸發)
      • "mode""auto", "manual""realtime",表示識別模式。
    • 例:開始監聽
      {
        "session_id": "xxx",
        "type": "listen",
        "state": "start",
        "mode": "manual"
      }
  3. Abort

    • 終止當前說話(TTS 播放)或語音通道。
    • 例:
      {
        "session_id": "xxx",
        "type": "abort",
        "reason": "wake_word_detected"
      }
    • reason 值可為 "wake_word_detected" 或其他。
  4. Wake Word Detected

    • 用於客戶端向伺服器告知檢測到喚醒詞。
    • 例:
      {
        "session_id": "xxx",
        "type": "listen",
        "state": "detect",
        "text": "你好小明"
      }
  5. IoT

    • 傳送當前裝置的物聯網相關資訊:
      • Descriptors(描述裝置功能、屬性等)
      • States(裝置狀態的即時更新)
    • 例:
      {
        "session_id": "xxx",
        "type": "iot",
        "descriptors": { ... }
      }
      {
        "session_id": "xxx",
        "type": "iot",
        "states": { ... }
      }

3.2 伺服器→客戶端

  1. Hello

    • 伺服器端回傳的握手確認訊息。
    • 必須包含 "type": "hello""transport": "websocket"
    • 可能會帶有 audio_params,表示伺服器期望的音訊參數,或與客戶端對齊的設定。
    • 成功接收後客戶端會設定事件標誌,表示 WebSocket 通道就緒。
  2. STT

    • {"type": "stt", "text": "..."}
    • 表示伺服器端識別到了使用者語音。(例如語音轉文字結果)
    • 裝置可能將此文字顯示到螢幕上,後續再進入回答等流程。
  3. LLM

    • {"type": "llm", "emotion": "happy", "text": "😀"}
    • 伺服器指示裝置調整表情動畫 / UI 表達。
  4. TTS

    • {"type": "tts", "state": "start"}:伺服器準備下發 TTS 音訊,客戶端進入 “speaking” 播放狀態。
    • {"type": "tts", "state": "stop"}:表示本次 TTS 結束。
    • {"type": "tts", "state": "sentence_start", "text": "..."}
      • 讓裝置在介面上顯示當前要播放或朗讀的文字片段(例如用於顯示給使用者)。
  5. IoT

    • {"type": "iot", "commands": [ ... ]}
    • 伺服器向裝置傳送物聯網的動作指令,裝置解析並執行(如開啟燈、設定溫度等)。
  6. 音訊資料:二進位幀

    • 當伺服器傳送音訊二進位幀(Opus 編碼)時,客戶端解碼並播放。
    • 若客戶端正在處於 “listening” (錄音)狀態,收到的音訊幀會被忽略或清空以防衝突。

4. 音訊編解碼

  1. 客戶端傳送錄音資料

    • 音訊輸入經過可能的回音消除、降噪或音量增益後,透過 Opus 編碼打包為二進位幀傳送給伺服器。
    • 如果客戶端每次編碼產生的二進位幀大小為 N 位元組,則會透過 WebSocket 的 binary 訊息傳送這塊資料。
  2. 客戶端播放收到的音訊

    • 收到伺服器的二進位幀時,同樣認定是 Opus 資料。
    • 裝置端會進行解碼,然後交由音訊輸出介面播放。
    • 如果伺服器的音訊取樣率與裝置不一致,會在解碼後再進行重新取樣。

5. 常見狀態流轉

以下簡述裝置端關鍵狀態流轉,與 WebSocket 訊息對應:

  1. IdleConnecting

    • 使用者觸發或喚醒後,裝置呼叫 OpenAudioChannel() → 建立 WebSocket 連線 → 傳送 "type":"hello"
  2. ConnectingListening

    • 成功建立連線後,若繼續執行 SendStartListening(...),則進入錄音狀態。此時裝置會持續編碼麥克風資料並傳送到伺服器。
  3. ListeningSpeaking

    • 收到伺服器 TTS Start 訊息 ({"type":"tts","state":"start"}) → 停止錄音並播放接收到的音訊。
  4. SpeakingIdle

    • 伺服器 TTS Stop ({"type":"tts","state":"stop"}) → 音訊播放結束。若未繼續進入自動監聽,則回到 Idle;如果設定了自動循環,則再度進入 Listening。
  5. Listening / SpeakingIdle(遇到異常或主動中斷)

    • 呼叫 SendAbortSpeaking(...)CloseAudioChannel() → 中斷會話 → 關閉 WebSocket → 狀態回到 Idle。

6. 錯誤處理

  1. 連線失敗

    • 如果 Connect(url) 回傳失敗或在等待伺服器 “hello” 訊息時逾時,觸發 on_network_error_() 回呼。裝置會提示「無法連接到服務」或類似錯誤訊息。
  2. 伺服器斷開

    • 如果 WebSocket 異常斷開,回呼 OnDisconnected()
      • 裝置回呼 on_audio_channel_closed_()
      • 切換到 Idle 或其他重試邏輯。

7. 其它注意事項

  1. 鑑權

    • 裝置透過設定 Authorization: Bearer <token> 提供鑑權,伺服器端需驗證是否有效。
    • 如果權杖過期或無效,伺服器可拒絕握手或在後續斷開。
  2. 會話控制

    • 程式碼中部分訊息包含 session_id,用於區分獨立的對話或操作。伺服端可根據需要對不同會話做分離處理,WebSocket 協議為空。
  3. 音訊負載

    • 程式碼裡預設使用 Opus 格式,並設定 sample_rate = 16000,單聲道。幀時長由 OPUS_FRAME_DURATION_MS 控制,一般為 60ms。可根據頻寬或效能做適當調整。
  4. IoT 指令

    • "type":"iot" 的訊息使用者端程式碼對接 thing_manager 執行具體指令,因裝置客製而不同。伺服器端需確保下發格式與客戶端保持一致。
  5. 錯誤或異常 JSON

    • 當 JSON 中缺少必要欄位,例如 {"type": ...},客戶端會記錄錯誤日誌(ESP_LOGE(TAG, "Missing message type, data: %s", data);),不會執行任何業務。

8. 訊息範例

下面給出一個典型的雙向訊息範例(流程簡化示意):

  1. 客戶端 → 伺服器(握手)

    {
      "type": "hello",
      "version": 1,
      "transport": "websocket",
      "audio_params": {
        "format": "opus",
        "sample_rate": 16000,
        "channels": 1,
        "frame_duration": 60
      }
    }
  2. 伺服器 → 客戶端(握手應答)

    {
      "type": "hello",
      "transport": "websocket",
      "audio_params": {
        "sample_rate": 16000
      }
    }
  3. 客戶端 → 伺服器(開始監聽)

    {
      "session_id": "",
      "type": "listen",
      "state": "start",
      "mode": "auto"
    }

    同時客戶端開始傳送二進位幀(Opus 資料)。

  4. 伺服器 → 客戶端(ASR 結果)

    {
      "type": "stt",
      "text": "使用者說的話"
    }
  5. 伺服器 → 客戶端(TTS開始)

    {
      "type": "tts",
      "state": "start"
    }

    接著伺服器傳送二進位音訊幀給客戶端播放。

  6. 伺服器 → 客戶端(TTS結束)

    {
      "type": "tts",
      "state": "stop"
    }

    客戶端停止播放音訊,若無更多指令,則回到閒置狀態。


9. 總結

本協議透過在 WebSocket 上層傳輸 JSON 文字與二進位音訊幀,完成功能包括音訊流上傳、TTS 音訊播放、語音識別與狀態管理、IoT 指令下發等。其核心特徵:

  • 握手階段:傳送 "type":"hello",等待伺服器回傳。
  • 音訊通道:採用 Opus 編碼的二進位幀雙向傳輸語音流。
  • JSON 訊息:使用 "type" 為核心欄位標識不同業務邏輯,包括 TTS、STT、IoT、WakeWord 等。
  • 可擴充性:可根據實際需求在 JSON 訊息中新增欄位,或在 headers 裡進行額外鑑權。

伺服器與客戶端需提前約定各類訊息的欄位含義、時序邏輯以及錯誤處理規則,方能保證通訊順暢。上述資訊可作為基礎文件,便於後續對接、開發或擴充。