实习智能家居网关项目总结(MQTT与云端连接通信)
项目介绍
在北京聚英翱翔担任助理嵌入式软件工程师期间,我主要跟随导师参与学习了新一代智能家居中控设备的预研工作。项目的目标是实现家庭中控设备与云平台及多类传感器的高效、稳定互联。
在这个过程中,我主要学习了以下几方面的工作:
(1)基于瑞芯微平台,利用 TCP/IP协议和 MQTT 搭建了设备与云平台之间的数据交互通道,保证了设备能够稳定地接收和上报数据。
(2)使用 SQLite 数据库 设计了本地缓存机制,实现了在网络中断时的数据临时存储,并能在网络恢复后进行数据同步。同时,还对设备运行日志进行了实时存储和查询,便于调试和运维。
(3)在底层通信方面,我参与开发基于串口的应用,支持 Modbus 主/从模式,实现了与多类传感器的稳定通信,为后续的多设备接入和扩展提供了基础。
(4)除了功能开发,我还参与了产品的功能验证测试,确保各模块的稳定性,并撰写了项目的设计概要和方案文档,为团队后续开发提供了参考。
1、基于瑞芯微与云平台互联以及本地缓存机制
智能家居设备通常需要和 多种传感器、模块通信(如 ZigBee、Modbus、Wi-Fi、蓝牙),瑞芯微 SoC 集成了多种标准接口(UART、SPI、I2C 等),方便开发者进行外设扩展。
MQTT的连接步骤:
1、底层准备
瑞芯微设备上运行的系统通过 TCP/IP 协议与云端的 MQTT Broker 建立长连接,如果是公网,还需要配置 TLS/SSL 加密,通常端口是 8883。
2、设备发起 CONNECT 报文
中控设备上的 MQTT 客户端(运行在瑞芯微平台上)向 Broker 发送 CONNECT 报文里面包含:
ClientID(通常用设备唯一 ID,确保唯一性)
用户名/密码(可选,用于鉴权)
KeepAlive 心跳周期(例如 30 秒)
Clean Session 标志(是否持久会话)
遗嘱消息 LWT(设备异常掉线时 Broker 自动代发的消息)
3、Broker 响应 CONNACK
Broker 校验成功后返回 CONNACK,表示连接建立成功。
如果鉴权失败或 ClientID 冲突,Broker 会拒绝连接。
4、订阅主题(SUBSCRIBE)
设备(瑞芯微端)订阅一个或多个命令主题
home/gateway/<device_id>/command
这样云平台下发的控制指令就能通过该主题传递到设备
其它主题:telemetry(遥测上报)、command(云端下发)、status(设备状态)、lwt(离线告警)
5、发布消息(PUBLISH)
设备定期向 Broker 发布数据,例如遥测数据:home/gateway/<device_id>/telemetry
6、心跳(PINGREQ / PINGRESP)
设备定时发送PINGREQ,Broker 回复 PINGRESP,保持会话活跃。
超时未响应 → Broker 判定设备掉线 → 触发 LWT。
7、断线重连
网络中断时,设备会触发重连机制(常用指数退避重连策略)。
如果 Clean Session=false,则能恢复上一次的订阅与未送达的 QoS1/QoS2 消息。
瑞芯微可作为发布者和订阅者与服务器通行,以下是连接阿里云的流程:
1、云端准备
在阿里云物联网平台控制台创建产品与设备,拿到设备三元组,记录 MQTT 接入域名。
2、获取与编译 C Link SDK(设备端)
下载SDK代码并编译,移植到瑞芯微平台
3、设备部署
连接阿里云 MQTT、订阅一条命令 Topic、定时发布一条自定义遥测;含事件/收包回调、心跳/自动重连线程。(阿里云提供文档)
代码:
#include <mosquitto.h>
#include <openssl/hmac.h>
#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include <csignal>
#include <cstring>// ==== 替换为你自己的三元组 ====
static const std::string PRODUCT_KEY = "a1AbCdEf123";
static const std::string DEVICE_NAME = "rk3568_gw01";
static const std::string DEVICE_SECRET = "0123456789abcdef0123456789abcdef";
static const std::string REGION = "cn-shanghai";// ==== 生成阿里云签名 ====
std::string hmac_sha256(const std::string &key, const std::string &msg) {unsigned char result[EVP_MAX_MD_SIZE];unsigned int len = 0;HMAC(EVP_sha256(),key.data(), (int)key.size(),(unsigned char*)msg.data(), (int)msg.size(),result, &len);std::string hex;static const char *hexmap = "0123456789abcdef";for(unsigned int i=0;i<len;i++){hex.push_back(hexmap[result[i] >> 4]);hex.push_back(hexmap[result[i] & 0xF]);}return hex;
}int main() {mosquitto_lib_init();std::signal(SIGINT, [](int){ exit(0); });// ====== 1. 组装 MQTT 参数 ======std::string client_id = DEVICE_NAME + "|securemode=3,signmethod=hmacsha256|";std::string username = DEVICE_NAME + "&" + PRODUCT_KEY;// 签名原文:clientId + DeviceName + ProductKeystd::string sign_content = "clientId" + DEVICE_NAME +"deviceName" + DEVICE_NAME +"productKey" + PRODUCT_KEY;std::string password = hmac_sha256(DEVICE_SECRET, sign_content);std::cout << "[INFO] client_id=" << client_id << "\n";std::cout << "[INFO] username=" << username << "\n";std::cout << "[INFO] password=" << password << "\n";// ====== 2. Broker 地址 ======std::string broker = PRODUCT_KEY + ".iot-as-mqtt." + REGION + ".aliyuncs.com";int port = 1883; // 推荐改成 8883 并配置 TLS// ====== 3. 创建客户端并连接 ======mosquitto *mosq = mosquitto_new(client_id.c_str(), true, nullptr);mosquitto_username_pw_set(mosq, username.c_str(), password.c_str());// 回调:连接成功mosquitto_connect_callback_set(mosq, [](struct mosquitto*, void*, int rc){if(rc==0) std::cout << "[MQTT] Connected to Aliyun IoT\n";else std::cout << "[MQTT] Connect failed, rc=" << rc << "\n";});// 回调:收到消息mosquitto_message_callback_set(mosq, [](struct mosquitto*, void*, const struct mosquitto_message *msg){std::string payload((char*)msg->payload, msg->payloadlen);std::cout << "[RX] " << msg->topic << " => " << payload << "\n";});// 建立连接int rc = mosquitto_connect(mosq, broker.c_str(), port, 60);if(rc != MOSQ_ERR_SUCCESS){std::cerr << "Connect error: " << mosquitto_strerror(rc) << "\n";return -1;}mosquitto_loop_start(mosq);// ====== 4. 订阅云下发的 Topic ======std::string sub_topic = "/" + PRODUCT_KEY + "/" + DEVICE_NAME + "/user/get";mosquitto_subscribe(mosq, nullptr, sub_topic.c_str(), 1);// ====== 5. 周期发布数据到云端 ======std::string pub_topic = "/" + PRODUCT_KEY + "/" + DEVICE_NAME + "/user/update";while(true){std::string payload = "{\"temp\":25,\"hum\":60}";mosquitto_publish(mosq, nullptr, pub_topic.c_str(),(int)payload.size(), payload.c_str(), 1, false);std::cout << "[TX] " << payload << "\n";std::this_thread::sleep_for(std::chrono::seconds(5));}mosquitto_loop_stop(mosq, true);mosquitto_destroy(mosq);mosquitto_lib_cleanup();return 0;
}
面试问题
- 为什么选择 MQTT,而不是 HTTP/CoAP?
MQTT 是轻量级发布/订阅协议,消息头小,带宽和功耗占用低;
适合长连接,支持 QoS,能保证消息可靠送达;
HTTP 适合请求-响应,但实时性差,资源消耗大;
CoAP 偏向低功耗终端,但生态与云平台支持度不如 MQTT。
2. MQTT 的 QoS 有哪几种?项目中用哪种?
QoS0:最多一次(可能丢失,实时性高,适合频繁传感器数据);
QoS1:至少一次(可能重复,适合设备状态、控制命令确认);
QoS2:仅一次(最安全,开销大,少用)。
👉 项目中遥测数据用 QoS1,命令/状态上报也多用 QoS1。
3. 什么是遗嘱消息 (LWT)?为什么要用?
设备异常掉线时,Broker 自动代发预先设置的离线消息;
云平台能及时感知设备状态,触发告警或重试;
对智能家居这种实时性要求高的系统很重要。
4. 为什么基于 TCP,而不是 UDP?
TCP 提供可靠传输、顺序控制和丢包重传;
MQTT 协议的可靠性需求必须依赖 TCP;
UDP 轻量但不可靠,通常用于 CoAP/视频流,不适合状态和命令传输。
5. KeepAlive 心跳机制如何工作?
客户端定期发送 PINGREQ;
Broker 回复 PINGRESP,维持会话;
若心跳超时,Broker 认定掉线 → 触发 LWT。
6. 断网后如何保证数据不丢?
本地使用 SQLite 缓存:网络断开时写入本地,恢复后批量补发;
配合 MQTT 的 持久会话 (Clean Session=false) 和 QoS1/2 确保消息不丢。
7. Topic 如何设计?
常用分层结构:
home/gateway/<device_id>/telemetry → 遥测上报
home/gateway/<device_id>/command → 命令下发
home/gateway/<device_id>/status → 状态上报
home/gateway/<device_id>/lwt → 离线告警
这样结构清晰、易于管理和权限控制。
8. 如果云端宕机/设备多并发,如何保证系统稳定?
设置客户端指数退避重连,避免雪崩重连;
使用负载均衡或集群 Broker;
保留会话与 QoS1/2 保证消息可靠送达。
9. 瑞芯微平台在这里的优势是什么?
ARM 架构 CPU,性能足够支撑协议栈和数据处理;
丰富外设接口,方便接入多类传感器;
生态成熟,能快速集成现成 SDK(如阿里云 Link SDK)
10.TCP/IP的作用
TCP 是面向连接的协议,它通过序列号保证顺序,ACK 确认和超时重传保证不丢包,滑动窗口做流量控制,拥塞控制保证网络稳定,所以能实现可靠、有序、不丢失、不重复的传输。
IP 协议负责寻址和路由。设备先通过 DNS 获取云服务器的 IP,然后在数据包头部填入源 IP 和目的 IP。每个路由器根据路由表转发数据包,最终送达目标服务器。需要注意的是,IP 本身是不可靠的“尽力而为”协议,真正的可靠性是由 TCP 来保证的。