小智ESP32代码(3):网络通信
在 xiaozhi-esp32 项目中,设备与服务器的通信是核心功能之一。为了实现灵活、可扩展的通信能力,项目通过抽象类 Protocol 定义了通信协议的通用接口,并基于不同的通信协议实现了不同的子类。
这里仅选取以基于 WebSocket 的 WebsocketProtocol 类为例,解析这两个类的功能与实现细节,以及它们与上层应用和底层库的交互方式。
Protocol 类:通信协议的抽象接口
Protocol 类是所有通信协议的基类,它通过纯虚函数和通用成员定义了设备与服务器通信的核心能力。其设计目标是屏蔽底层通信细节,为上层应用提供统一的接口,使得上层应用无需关心具体使用 WebSocket、MQTT 还是其他协议,只需调用统一的方法即可完成通信。
-
事件回调注册
通信过程中会产生多种事件(如接收到音频、JSON 消息、网络错误等),Protocol 类定义了供外部使用的各种事件回调函数的注册接口,从而在事件发生时,通过回调函数机制将事件通知给上层应用:/* ---------------- Protocol 类提供的用来注册各种事件回调函数的接口 ---------------- */ // 注册"接收到音频"回调(上层可在此处理音频播放等逻辑) void OnIncomingAudio(std::function<void(std::unique_ptr<AudioStreamPacket> packet)> callback); // 注册"接收到JSON消息"回调(上层可在此处理指令、状态等文本消息) void OnIncomingJson(std::function<void(const cJSON* root)> callback); // 注册"音频通道打开"回调(通知上层可以开始发送/接收音频) void OnAudioChannelOpened(std::function<void()> callback); // 注册"音频通道关闭"回调 void OnAudioChannelClosed(std::function<void()> callback); // 注册"网络错误"回调(通知上层网络异常) void OnNetworkError(std::function<void(const std::string& message)> callback);/* ---------------- 上层应用在 Application 类中进行回调函数的注册 ---------------- */ void Application::Start() { ...... // 注册"网络错误"回调 protocol_->OnNetworkError([this](const std::string& message) { last_error_message_ = message; xEventGroupSetBits(event_group_, MAIN_EVENT_ERROR); }); // 注册"接收到音频"回调 protocol_->OnIncomingAudio(...); // 注册"音频通道打开"回调 protocol_->OnAudioChannelOpened(...) // 注册"音频通道关闭"回调 protocol_->OnAudioChannelClosed(...) // 注册"接收到JSON消息"回调 protocol_->OnIncomingJson(...) ...... } -
核心通信方法
Protocol类还定义了通信的核心操作,包括连接管理、音频传输、消息发送等。其中纯虚函数需要子类(如 WebsocketProtocol)根据具体协议实现:// 启动协议(如建立连接) virtual bool Start() = 0; // 打开音频通道(准备传输音频) virtual bool OpenAudioChannel() = 0; // 关闭音频通道 virtual void CloseAudioChannel() = 0; // 检查音频通道是否打开 virtual bool IsAudioChannelOpened() const = 0; // 发送音频数据(Opus编码的音频帧) virtual bool SendAudio(std::unique_ptr<AudioStreamPacket> packet) = 0;
WebsocketProtocol 类:基于 WebSocket 的具体实现
WebsocketProtocol 是 Protocol 的子类,基于 WebSocket 协议实现了设备与服务器的通信。它通过 websocket 库实现 WebSocket 连接的建立、数据收发、协议解析等底层细节,并通过 Protocol 定义的接口向上层提供服务。
-
打开音频通道:OpenAudioChannel
这是 WebsocketProtocol 最核心的方法,负责建立 WebSocket 连接、完成握手,并准备音频传输。流程如下:- 读取配置:
从设置中获取 WebSocket 服务器 URL、令牌(token)、协议版本等; - 创建 WebSocket 实例:通过底层网络库(Board::GetInstance().GetNetwork())创建 WebSocket 客户端;
- 设置请求头:添加鉴权信息(Authorization: Bearer
)、设备信息(Device-Id、Client-Id)等: websocket_->SetHeader("Authorization", token.c_str()); websocket_->SetHeader("Device-Id", SystemInfo::GetMacAddress().c_str()); - 注册数据接收回调:区分二进制(音频)和文本(JSON)数据:
- 二进制数据:根据协议版本解析(版本 1 直接是 Opus 数据;版本 2 带时间戳;版本 3 简化格式),并通过 on_incoming_audio_ 回调通知上层;
- 文本数据:解析为 JSON,若为 “hello” 消息则完成握手,否则通过 on_incoming_json_ 回调通知上层;
- 建立连接并发送握手消息:连接服务器后,发送设备 “hello” 消息(包含设备能力、音频参数等);
- 等待服务器响应:通过事件组等待服务器的 “hello” 响应(超时 10 秒),若成功则标记音频通道打开。
值得注意的是,WebSocket 类与 websocket 库交互时,也使用到了回调函数,不过,这一次,WebSocket 类是作为使用者,它通过 websocket 库提供的回调函数注册接口,来对各种事件进行回调注册。
/* ---------------- WebSocket 类注册 websocket 库提供的回调函数 ---------------- */ bool WebsocketProtocol::OpenAudioChannel() { ... // 注册 onData 事件:当 websock 库收到数据时,调用... websocket_->OnData(...) // 注册 onDisconnected 事件:当 websock 断开连接时,调用... websocket_->OnDisconnected(...) } - 读取配置:
-
发送音频:SendAudio
根据协议版本封装 Opus 音频帧,并通过 WebSocket 发送二进制数据。例如,版本 2 会携带时间戳,版本 3 则简化格式:bool WebsocketProtocol::SendAudio(std::unique_ptr<AudioStreamPacket> packet) { // 根据version_封装音频帧(二进制格式) // 调用websocket_->SendBinary(...)发送 } -
发送文本消息:SendText
实现基类的纯虚函数,通过 WebSocket 发送 JSON 文本消息(如唤醒词事件、MCP 指令等):bool WebsocketProtocol::SendText(const std::string& text) { return websocket_->SendText(text); // 调用底层WebSocket库发送文本 }
总结
Protocol 与 WebsocketProtocol 是 xiaozhi-esp32 项目中通信功能的核心:
- Protocol 作为抽象接口,定义了通信的通用能力,实现了 “接口与实现分离”,使得上层应用可以无缝切换不同协议(如 WebSocket、MQTT+UDP);
- WebsocketProtocol 作为具体实现,基于 WebSocket 协议完成了连接管理、数据收发、协议解析等细节,同时通过回调机制与上层解耦;
- 整个设计层次清晰:上层业务逻辑通过 Protocol 接口使用通信功能,WebsocketProtocol 依赖底层网络库实现具体通信,最终实现了灵活、可扩展的设备 - 服务器交互能力。

浙公网安备 33010602011771号