Протокол связи WebSocket
Следующий документ протокола связи WebSocket организован на основе реализации кода, описывающий, как клиенты (устройства) взаимодействуют с серверами через WebSocket. Данный документ выведен исключительно из предоставленного кода и может потребовать дальнейшего подтверждения или дополнения при сочетании с серверной реализацией во время фактического развертывания.
1. Обзор общего процесса
Инициализация на стороне устройства
- Включение устройства, инициализация
Application
:- Инициализация аудиокодека, дисплея, LED и т.д.
- Подключение к сети
- Создание и инициализация экземпляра протокола WebSocket (
WebsocketProtocol
), реализующего интерфейсProtocol
- Переход в основной цикл ожидания событий (аудиовход, аудиовыход, запланированные задачи и т.д.).
- Включение устройства, инициализация
Установка соединения WebSocket
- Когда устройство должно начать голосовую сессию (например, пробуждение пользователя, ручной триггер кнопки), вызывается
OpenAudioChannel()
:- Получение URL WebSocket из конфигурации компиляции (
CONFIG_WEBSOCKET_URL
) - Установка нескольких заголовков запроса (
Authorization
,Protocol-Version
,Device-Id
,Client-Id
) - Вызов
Connect()
для установки соединения WebSocket с сервером
- Получение URL WebSocket из конфигурации компиляции (
- Когда устройство должно начать голосовую сессию (например, пробуждение пользователя, ручной триггер кнопки), вызывается
Отправка клиентского сообщения “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мс).
Ответ сервера “hello”
- Устройство ждет, пока сервер вернет JSON-сообщение, содержащее
"type": "hello"
, и проверяет соответствие"transport": "websocket"
. - Если соответствует, сервер считается готовым, и открытие аудиоканала помечается как успешное.
- Если правильный ответ не получен в течение времени ожидания (по умолчанию 10 секунд), соединение считается неудачным и запускается обратный вызов сетевой ошибки.
- Устройство ждет, пока сервер вернет JSON-сообщение, содержащее
Последующее взаимодействие сообщений
Устройство и сервер могут отправлять два основных типа данных:
- Бинарные аудиоданные (кодированные 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.
Клиент воспроизводит полученное аудио
- При получении бинарных кадров от сервера они также считаются данными 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
, обычно 60мс. Может быть соответственно отрегулирована в зависимости от пропускной способности или производительности.
- Код по умолчанию использует формат 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. Резюме
Этот протокол передает JSON-текст и бинарные аудиокадры поверх WebSocket, завершая функции, включая загрузку аудиопотока, воспроизведение аудио TTS, распознавание речи и управление состоянием, доставку команд IoT и т.д. Основные характеристики:
- Фаза рукопожатия: Отправка
"type":"hello"
, ожидание возврата сервера. - Аудиоканал: Использование бинарных кадров, кодированных Opus, для двунаправленной передачи голосового потока.
- JSON-сообщения: Использование
"type"
как основного поля для идентификации различной бизнес-логики, включая TTS, STT, IoT, WakeWord и т.д. - Расширяемость: Поля могут быть добавлены в JSON-сообщения или дополнительная аутентификация в заголовках на основе фактических потребностей.
Сервер и клиент должны заранее договориться о значениях полей, логике времени и правилах обработки ошибок для различных типов сообщений, чтобы обеспечить плавную связь. Приведенная выше информация может служить базовой документацией для последующего интерфейсинга, разработки или расширения.