Протокол связи WebSocket

Следующий документ протокола связи WebSocket организован на основе реализации кода, описывающий, как клиенты (устройства) взаимодействуют с серверами через WebSocket. Данный документ выведен исключительно из предоставленного кода и может потребовать дальнейшего подтверждения или дополнения при сочетании с серверной реализацией во время фактического развертывания.


1. Обзор общего процесса

  1. Инициализация на стороне устройства

    • Включение устройства, инициализация Application:
      • Инициализация аудиокодека, дисплея, LED и т.д.
      • Подключение к сети
      • Создание и инициализация экземпляра протокола WebSocket (WebsocketProtocol), реализующего интерфейс Protocol
    • Переход в основной цикл ожидания событий (аудиовход, аудиовыход, запланированные задачи и т.д.).
  2. Установка соединения WebSocket

    • Когда устройство должно начать голосовую сессию (например, пробуждение пользователя, ручной триггер кнопки), вызывается OpenAudioChannel():
      • Получение URL WebSocket из конфигурации компиляции (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 (например, 60мс).
  4. Ответ сервера “hello”

    • Устройство ждет, пока сервер вернет JSON-сообщение, содержащее "type": "hello", и проверяет соответствие "transport": "websocket".
    • Если соответствует, сервер считается готовым, и открытие аудиоканала помечается как успешное.
    • Если правильный ответ не получен в течение времени ожидания (по умолчанию 10 секунд), соединение считается неудачным и запускается обратный вызов сетевой ошибки.
  5. Последующее взаимодействие сообщений

    • Устройство и сервер могут отправлять два основных типа данных:

      1. Бинарные аудиоданные (кодированные Opus)
      2. Текстовые JSON-сообщения (для передачи состояния чата, событий TTS/STT, команд IoT и т.д.)
    • В коде обратные вызовы приема в основном делятся на:

      • OnData(...):
        • Когда binary равно true, считается аудиокадром; устройство обрабатывает его как данные Opus для декодирования.
        • Когда binary равно false, считается 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

    • Отправка 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": [ ... ]}
    • Сервер отправляет команды действий IoT на устройство, устройство анализирует и выполняет (например, включение света, установка температуры и т.д.).
  6. Аудиоданные: Бинарные кадры

    • Когда сервер отправляет бинарные аудиокадры (кодированные Opus), клиент декодирует и воспроизводит.
    • Если клиент находится в состоянии “listening” (запись), полученные аудиокадры игнорируются или очищаются для предотвращения конфликтов.

4. Кодирование/декодирование аудио

  1. Клиент отправляет данные записи

    • Аудиовход, после возможной отмены эха, шумоподавления или усиления громкости, упаковывается через кодирование Opus в бинарные кадры и отправляется на сервер.
    • Если кодирование клиента генерирует бинарные кадры по N байт каждый раз, эти данные отправляются через бинарные сообщения WebSocket.
  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, обычно 60мс. Может быть соответственно отрегулирована в зависимости от пропускной способности или производительности.
  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. Резюме

Этот протокол передает JSON-текст и бинарные аудиокадры поверх WebSocket, завершая функции, включая загрузку аудиопотока, воспроизведение аудио TTS, распознавание речи и управление состоянием, доставку команд IoT и т.д. Основные характеристики:

  • Фаза рукопожатия: Отправка "type":"hello", ожидание возврата сервера.
  • Аудиоканал: Использование бинарных кадров, кодированных Opus, для двунаправленной передачи голосового потока.
  • JSON-сообщения: Использование "type" как основного поля для идентификации различной бизнес-логики, включая TTS, STT, IoT, WakeWord и т.д.
  • Расширяемость: Поля могут быть добавлены в JSON-сообщения или дополнительная аутентификация в заголовках на основе фактических потребностей.

Сервер и клиент должны заранее договориться о значениях полей, логике времени и правилах обработки ошибок для различных типов сообщений, чтобы обеспечить плавную связь. Приведенная выше информация может служить базовой документацией для последующего интерфейсинга, разработки или расширения.