小智ESP32代码(3):网络通信

在 xiaozhi-esp32 项目中,设备与服务器的通信是核心功能之一。为了实现灵活、可扩展的通信能力,项目通过抽象类 Protocol 定义了通信协议的通用接口,并基于不同的通信协议实现了不同的子类。
这里仅选取以基于 WebSocket 的 WebsocketProtocol 类为例,解析这两个类的功能与实现细节,以及它们与上层应用和底层库的交互方式。

Protocol 类:通信协议的抽象接口

Protocol 类是所有通信协议的基类,它通过纯虚函数和通用成员定义了设备与服务器通信的核心能力。其设计目标是屏蔽底层通信细节,为上层应用提供统一的接口,使得上层应用无需关心具体使用 WebSocket、MQTT 还是其他协议,只需调用统一的方法即可完成通信。

  1. 事件回调注册
    通信过程中会产生多种事件(如接收到音频、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(...)
        ......
    }
    
  2. 核心通信方法
    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 定义的接口向上层提供服务。

  1. 打开音频通道: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(...)
    }
    
  2. 发送音频:SendAudio
    根据协议版本封装 Opus 音频帧,并通过 WebSocket 发送二进制数据。例如,版本 2 会携带时间戳,版本 3 则简化格式:

    bool WebsocketProtocol::SendAudio(std::unique_ptr<AudioStreamPacket> packet) {
        // 根据version_封装音频帧(二进制格式)
        // 调用websocket_->SendBinary(...)发送
    }
    
  3. 发送文本消息: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 依赖底层网络库实现具体通信,最终实现了灵活、可扩展的设备 - 服务器交互能力。
posted @ 2025-08-27 16:15  icuic  阅读(72)  评论(0)    收藏  举报