政安晨的个人主页:政安晨
欢迎 👍点赞✍评论⭐收藏
希望政安晨的博客能够对您有所裨益,如有不足之处,欢迎在评论区提出指正!
小智AI作为一款基于ESP32开发板的嵌入式语音助手,其通信模块采用了MQTT协议与UDP协议相结合的方式,实现了低延迟、高可靠性的数据传输。本文将深入解析小智AI嵌入式终端项目中MQTT+UDP通信交互的设计思路和代码实现,帮助开发者更好地理解其技术细节。
目录
一、MQTT+UDP通信模式的设计背景
1.1 MQTT协议的优势
MQTT(Message Queuing Telemetry Transport)是一种轻量级的发布/订阅消息传输协议,广泛应用于物联网场景。其主要特点包括:
- 低带宽占用:适合资源受限的嵌入式设备。
- 可靠性:支持QoS(Quality of Service)机制,确保消息可靠传递。
- 灵活性:通过主题(Topic)实现消息的分类和分发。
然而,MQTT协议在实时性要求较高的场景下可能表现不足,例如音频流传输。
1.2 UDP协议的引入
UDP(User Datagram Protocol)是一种无连接的传输协议,具有以下特点:
- 低延迟:无需建立连接,适合实时数据传输。
- 简单高效:减少了协议开销,提升了传输效率。
- 不可靠性:缺乏确认机制,可能导致数据丢失或乱序。
为了解决MQTT协议在实时性上的不足,小智AI项目在音频流传输中引入了UDP协议,结合MQTT协议完成信令交互,形成了高效的混合通信模式。
二、MQTT+UDP通信交互的整体架构
2.1 系统架构概述
小智AI的通信模块采用分层设计,主要包括以下部分:
- 应用层:负责调用通信接口,处理用户请求。
- 业务逻辑层:由
MqttProtocol
类实现,负责MQTT信令交互和UDP通道管理。 - 通信层:通过MQTT协议完成信令交互,通过UDP协议传输音频数据。
- 底层驱动:基于ESP-IDF框架提供的网络接口,完成数据收发。
业务流程:
三、核心代码解析
3.1 MQTT信令交互
3.1.1 功能描述
在音频流传输之前,设备需要通过MQTT协议与服务器进行信令交互,协商UDP通道的相关参数(如服务器地址、端口、加密密钥等)。
3.1.2 代码解析
以下是关键代码片段:
void MqttProtocol::Start() {
StartMqttClient(false);
}
bool MqttProtocol::StartMqttClient(bool report_error) {
mqtt_ = Board::GetInstance().CreateMqtt();
mqtt_->OnMessage([this](const std::string& topic, const std::string& payload) {
cJSON* root = cJSON_Parse(payload.c_str());
if (root == nullptr) {
ESP_LOGE(TAG, "Failed to parse json message %s", payload.c_str());
return;
}
cJSON* type = cJSON_GetObjectItem(root, "type");
if (strcmp(type->valuestring, "hello") == 0) {
ParseServerHello(root);
}
});
mqtt_->Connect(endpoint_, 8883, client_id_, username_, password_);
}
代码解析:
- 创建MQTT客户端:通过
Board::GetInstance().CreateMqtt()
创建MQTT客户端实例。 - 注册回调函数:在接收到消息时,解析JSON数据并根据消息类型执行相应操作。
- 连接服务器:调用
mqtt_->Connect
方法连接到指定的MQTT服务器。
3.2 UDP通道管理
3.2.1 功能描述
在完成信令交互后,设备通过UDP协议传输音频数据。UDP通道的管理包括初始化、数据加密、解密以及错误处理。
3.2.2 代码解析
以下是关键代码片段:
void MqttProtocol::OpenAudioChannel() {
std::string message = "{";
message += "\"type\":\"hello\",";
message += "\"version\": 3,";
message += "\"transport\":\"udp\",";
message += "\"audio_params\":{";
message += "\"format\":\"opus\", \"sample_rate\":16000, \"channels\":1, \"frame_duration\":" + std::to_string(OPUS_FRAME_DURATION_MS);
message += "}}";
SendText(message);
EventBits_t bits = xEventGroupWaitBits(event_group_handle_, MQTT_PROTOCOL_SERVER_HELLO_EVENT, pdTRUE, pdFALSE, pdMS_TO_TICKS(10000));
if (!(bits & MQTT_PROTOCOL_SERVER_HELLO_EVENT)) {
ESP_LOGE(TAG, "Failed to receive server hello");
SetError(Lang::Strings::SERVER_TIMEOUT);
return false;
}
}
代码解析:
- 发送信令消息:构造
hello
消息并通过SendText
方法发送给服务器。 - 等待响应:通过事件组等待服务器返回的
hello
响应。 - 错误处理:如果超时未收到响应,则设置错误状态。
3.3 音频数据加密与传输
3.3.1 功能描述
为了保证音频数据的安全性,小智AI项目采用了AES加密算法对数据进行加密和解密。
3.3.2 代码解析
以下是关键代码片段:
void MqttProtocol::SendAudio(const std::vector<uint8_t>& data) {
std::lock_guard<std::mutex> lock(channel_mutex_);
if (udp_ == nullptr) {
return;
}
std::string nonce(aes_nonce_);
*(uint16_t*)&nonce[2] = htons(data.size());
*(uint32_t*)&nonce[12] = htonl(++local_sequence_);
std::string encrypted;
encrypted.resize(aes_nonce_.size() + data.size());
memcpy(encrypted.data(), nonce.data(), nonce.size());
size_t nc_off = 0;
uint8_t stream_block[16] = {0};
mbedtls_aes_crypt_ctr(&aes_ctx_, data.size(), &nc_off, (uint8_t*)nonce.c_str(), stream_block,
(uint8_t*)data.data(), (uint8_t*)&encrypted[nonce.size()]);
udp_->Send(encrypted);
}
代码解析:
- 生成随机数(Nonce):用于AES加密的初始化向量。
- 加密数据:使用
mbedtls_aes_crypt_ctr
函数对音频数据进行加密。 - 发送数据:通过UDP客户端发送加密后的数据。
四、异常处理与可靠性保障
4.1 数据完整性校验
在接收音频数据时,设备会校验数据包的序列号,确保数据按顺序到达。如果发现乱序或重复的数据包,则丢弃该数据包。
if (sequence < remote_sequence_) {
ESP_LOGW(TAG, "Received audio packet with old sequence: %lu, expected: %lu", sequence, remote_sequence_);
return;
}
4.2 断电保护
在升级过程中,如果设备意外断电,可能导致固件损坏。为此,小智AI采用了双分区设计,即使升级失败,设备仍可回滚到旧版本。
4.3 日志记录
为了便于排查问题,小智AI在升级过程中记录详细日志,包括错误码、时间戳等信息。
五、实际应用场景
5.1 实时语音交互
通过MQTT+UDP通信模式,小智AI能够实现实时语音交互,满足用户对低延迟的需求。
5.2 多语言支持
通过OTA升级,小智AI可以推送新的语言包,满足不同地区用户的需求。
六、总结
小智AI的MQTT+UDP通信模式以其高效、可靠的设计,为用户提供了便捷的通信方式。
其实MQTT+UDP的方式,非常好地保证了小智AI对话时打断地实时性,笔者测试多种通信方式,发现小智选择的这种通信方式对于实时响应是非常高效的。
嘻嘻。