W55MH32使用MQTT协议连接OneNET
更新于 2026年9月14日
本篇总结
本例程在 W55MH32 上通过 MQTT 协议接入中移物联网 OneNET 平台,实现温湿度数据上报与 LED 主题的订阅控制。
硬件平台:本例程基于 W55MH32L-EVB 开发板运行。
采购推荐:点击前往 WIZnet 官方淘宝店铺 选购 W55MH32L-EVB
本示例演示如何在 W55MH32 上通过 MQTT(消息队列遥测传输)协议连接中移物联网 OneNET 平台,实现设备与云端的双向数据交互。设备通过 TCP 连接 OneNET MQTT 服务器,使用 token 鉴权方式登录,发布物模型数据,订阅属性设置与属性上报回复主题,实现远程数据采集与控制。
前置工具准备
开始本例程前,请确认以下工具已安装:
- WIZ UartTool V1.0:串口调试工具:点击下载
- OneNET 平台账号:注册并登录 中移物联网 OneNET,创建产品和设备,获取设备 ID、产品 ID 和 access_key
- token 生成工具:OneNET 提供的 token 生成器,用于生成 MQTT 登录所需的鉴权 token:参考文档
MQTT 协议简介
MQTT 是一种基于 发布/订阅模式的轻量级通信协议,构建于 TCP/IP 协议之上。由 IBM 在 1999 年发布,专为带宽有限、网络不稳定的远程通信场景设计,是物联网领域应用最广泛的协议之一。MQTT 协议的最大优点是:能以最少的代码和有限的带宽,为远程设备提供实时可靠的消息服务。
MQTT 协议的三个核心角色:
- 发布者:向 Broker 发布消息,指定一个主题(Topic)
- 订阅者:向 Broker 订阅感兴趣的主题,接收匹配的消息
- Broker:消息代理,负责接收发布者的消息并转发给订阅者
| 特性 | 说明 |
|---|---|
| 传输层 | 基于 TCP 协议,端口 1883(明文)/ 8883(TLS 加密) |
| 报文类型 | CONNECT、CONNACK、PUBLISH、SUBSCRIBE、PINGREQ 等 14 种 |
| QoS 等级 | 0(至多一次)、1(至少一次)、2(只有一次) |
| 主题层级 | 支持 "/" 分层和 "+"、"#" 通配符 |
OneNET 平台简介
OneNET 是中移物联网有限公司打造的 PaaS 物联网平台,支持 MQTT、HTTP、LwM2M 等多种协议接入。本例程使用 OneNET MQTT 接入方式(mqtts.heclouds.com:1883),采用 token 鉴权(HMAC-SHA1 加密)。设备通过发布数据点上报遥测数据,通过订阅命令主题接收云端控制指令。
硬件连接与拓扑
请注意:
本示例需要访问互联网,请确保 W55MH32 的网络环境及配置能够正常访问互联网(路由器需连接外网)。
- Micro USB 线连接 DEBUG USB 口到 PC(供电 + 调试串口)
- 网线连接开发板 RJ45 网口 到路由器 LAN 口,路由器需连接互联网
│ W55MH32 │──网线──│ 路由器/网关 │──互联网──│ OneNET 平台 │
│ (MQTT │ │ (DHCP/DNS) │ │ mqtts.heclouds.com│
│ Client) │ │ 192.168.1.1 │ │ :1883 │
└───────────┘ └─────────────────┘ └──────────────────┘
↑
DEBUG USB ──→ WIZ UartTool
工程结构
工程位于 SDK 内 1.SDK\ModuleDemo\WZTOE\MQTT_OneNET\,核心文件如下:
MQTT_OneNET/
├── Project/
│ └── Project.uvprojx ← Keil 工程文件
└── USER/
├── main.c ← 硬件初始化 + MQTT 主循环
├── do_mqtt/
│ ├── do_mqtt.c ← MQTT 客户端(mqtt_init / do_mqtt / json_decode)
│ └── do_mqtt.h ← MQTT 连接参数、消息结构
├── do_dns/
│ ├── do_dns.c ← DNS 域名解析
│ └── do_dns.h
├── wiz_interface/
│ ├── wiz_interface.c ← PHY 检测 / 网络信息打印
│ └── wiz_interface.h
├── tim/
│ ├── bsp_tim.c ← TIM3 1ms 定时器(MQTT/DHCP/DNS 时基)
│ └── bsp_tim.h
└── delay/
└── delay.c/h ← SysTick 毫秒延时
本例程相比 DNS 章节的核心新增:MQTT 客户端初始化和 发布/订阅状态机。
步骤1:注册 MQTT 定时中断函数
文件:USER/tim/bsp_tim.c — 将 MilliTimer_Handler() 注册到 1ms 定时器中断中,为 MQTT 协议栈提供时基。同时每 1000ms 执行一次 DHCP 和 DNS 的时间处理。
void TIM3_IRQHandler(void)
{
static uint32_t tim3_1ms_count = 0;
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
{
tim3_1ms_count++;
MilliTimer_Handler();
if (tim3_1ms_count >= 1000)
{
DHCP_time_handler();
DNS_time_handler();
tim3_1ms_count = 0;
}
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}
}
MilliTimer_Handler() 每 1ms 调用一次,为 MQTT 协议栈提供毫秒级时基(保活、超时判断)。DNS_time_handler() 和 DHCP_time_handler() 每 1s 调用一次,为这两个协议栈提供超时计数。
步骤2:配置 OneNET 连接参数
文件:USER/do_mqtt/do_mqtt.c — 定义 MQTT 连接参数,包括服务器域名、端口、客户端 ID、用户名、密码(token)、发布主题、订阅主题、遗嘱主题等。在 OneNET 平台创建产品和设备后,获取设备 ID、产品 ID 和 access_key,并使用 token 生成工具生成鉴权 token。
mqttconn mqtt_params = {
.mqttHostUrl = "mqtts.heclouds.com",
.server_ip = {0},
.port = 1883,
.clientid = "W5100S_W5500",
.username = "70TwP2gxl5",
.passwd = "version=2018-10-31&res=products%2F70TwP2gxl5%2Fdevices%2FW5100S_W5500&et=1791400694&method=sha1&sign=0SchVg6Y2MRYn%2B9zItNZwt%2F%2FN4Y%3D",
.pubtopic = "$sys/70TwP2gxl5/W5100S_W5500/thing/property/post",
.pubtopic_reply = "$sys/70TwP2gxl5/W5100S_W5500/thing/property/post/reply",
.subtopic = "$sys/70TwP2gxl5/W5100S_W5500/thing/property/set",
.subtopic_reply = "$sys/70TwP2gxl5/W5100S_W5500/thing/property/set_reply",
.pubQoS = QOS0,
.willtopic = "/wizchip/will",
.willQoS = QOS0,
.willmsg = "wizchip offline!",
.subQoS = QOS0,
};如果你想自定义
- clientid/username → 填写设备的
device_id(设备 ID) - passwd → OneNET token,格式为
version&res&et&method&sign,sign 由 access_key 通过 HMAC-SHA1 生成 - pubtopic / subtopic → OneNET 物模型的主题,格式为
$sys/{pid}/{device_id}/thing/property/... - willtopic / willmsg → 遗嘱主题和消息,设备异常掉线时由 Broker 代发
步骤3:MQTT 初始化与连接
文件:USER/do_mqtt/do_mqtt.c — mqtt_init() 完成 DNS 解析、TCP 连接、MQTT 客户端初始化和连接参数(含遗嘱)配置。
void mqtt_init(uint8_t sn, uint8_t *send_buf, uint8_t *recv_buf)
{
wiz_NetInfo get_info = {0};
wizchip_getnetinfo(&get_info);
/* DNS 解析 OneNET 服务器域名 */
if (do_dns(send_buf, (uint8_t *)mqtt_params.mqttHostUrl, mqtt_params.server_ip))
{
while (1);
}
NewNetwork(&n, sn);
ConnectNetwork(&n, mqtt_params.server_ip, mqtt_params.port);
MQTTClientInit(&c, &n, 1000, send_buf, MQTT_ETHERNET_MAX_SIZE, recv_buf, MQTT_ETHERNET_MAX_SIZE);
/* 遗嘱参数配置 */
data.willFlag = 0;
willdata.qos = mqtt_params.willQoS;
willdata.topicName.lenstring.data = mqtt_params.willtopic;
willdata.topicName.lenstring.len = strlen(willdata.topicName.lenstring.data);
willdata.message.lenstring.data = mqtt_params.willmsg;
willdata.message.lenstring.len = strlen(willdata.message.lenstring.data);
willdata.retained = 0;
willdata.struct_version = 3;
data.will = willdata;
/* MQTT 连接参数 */
data.MQTTVersion = 4;
data.clientID.cstring = mqtt_params.clientid;
data.username.cstring = mqtt_params.username;
data.password.cstring = mqtt_params.passwd;
data.keepAliveInterval = 30;
data.cleansession = 1;
}
执行逻辑:先通过 do_dns() 解析 OneNET 服务器域名 mqtts.heclouds.com 获取 IP,再建立 TCP 连接、初始化 MQTT 客户端,最后填入遗嘱和连接参数(客户端 ID、用户名、密码、keepAlive 30s、clean session)。
步骤4:do_mqtt() 状态机处理
文件:USER/do_mqtt/do_mqtt.c — do_mqtt() 采用状态机方式运行:连接服务器(CONN)→ 订阅主题(SUB,订阅 set 和 post/reply 两个主题)→ 发布消息(PUB_MESSAGE)→ 保活(KEEPALIVE)→ 接收处理(RECV)。
void do_mqtt(void)
{
uint8_t ret;
switch (run_status)
{
case CONN: {
ret = MQTTConnect(&c, &data);
printf("Connect to the MQTT server: %d.%d.%d.%d:%d\r\n",
mqtt_params.server_ip[0], mqtt_params.server_ip[1],
mqtt_params.server_ip[2], mqtt_params.server_ip[3],
mqtt_params.port);
printf("Connected:%s\r\n\r\n", ret == SUCCESSS ? "success" : "failed");
if (ret != SUCCESSS) run_status = ERR;
else run_status = SUB;
break;
}
case SUB: {
/* 订阅属性设置主题 */
ret = MQTTSubscribe(&c, mqtt_params.subtopic, mqtt_params.subQoS, messageArrived);
printf("Subscribing to %s\r\n", mqtt_params.subtopic);
printf("Subscribed:%s\r\n\r\n", ret == SUCCESSS ? "success" : "failed");
if (ret != SUCCESSS) { run_status = ERR; break; }
/* 订阅属性上报回复主题 */
ret = MQTTSubscribe(&c, mqtt_params.pubtopic_reply, mqtt_params.subQoS, messageArrived);
printf("Subscribing to %s\r\n", mqtt_params.pubtopic_reply);
printf("Subscribed:%s\r\n\r\n", ret == SUCCESSS ? "success" : "failed");
if (ret != SUCCESSS) run_status = ERR;
else run_status = PUB_MESSAGE;
break;
}
case PUB_MESSAGE: {
pubmessage.qos = QOS0;
pubmessage.payload = "{\"id\":\"123\",\"version\":\"1.0\",\"params\":{\"CurrentTemperature\":{\"value\":26.6}}}";
pubmessage.payloadlen = strlen(pubmessage.payload);
ret = MQTTPublish(&c, (char *)&(mqtt_params.pubtopic), &pubmessage);
if (ret != SUCCESSS) { run_status = ERR; }
else {
printf("publish:%s,%s\r\n\r\n", mqtt_params.pubtopic, (char *)pubmessage.payload);
run_status = KEEPALIVE;
}
break;
}
case KEEPALIVE: {
if (MQTTYield(&c, 30) != SUCCESSS) run_status = ERR;
delay_ms(100);
}
/* no break — 继续执行 RECV */
case RECV: {
if (mqtt_recv_flag) {
mqtt_recv_flag = 0;
json_decode(mqtt_recv_msg);
}
delay_ms(100);
break;
}
case ERR:
printf("system ERROR!");
delay_ms(1000);
break;
default:
break;
}
}
发布消息使用 OneNET 物模型 JSON 数据格式:{"id":"123","version":"1.0","params":{"CurrentTemperature":{"value":26.6}}},包含 id、version 和 params 字段。
步骤5:接收消息处理机制
文件:USER/do_mqtt/do_mqtt.c — 当设备收到 OneNET 平台下发的订阅消息后,先进入 messageArrived() 回调函数保存消息,随后主状态机在 RECV 分支中调用 json_decode() 用 cJSON 解析 JSON 数据并完成 LED 控制。
void messageArrived(MessageData *md)
{
char topicname[64] = {0};
char msg[512] = {0};
sprintf(topicname, "%.*s", (int)md->topicName->lenstring.len, md->topicName->lenstring.data);
sprintf(msg, "%.*s", (int)md->message->payloadlen, (char *)md->message->payload);
printf("recv:%s,%s\r\n\r\n", topicname, msg);
/* 只处理 set 主题的消息,post/reply 主题仅打印 */
if (strcmp(topicname, mqtt_params.subtopic) == 0)
{
mqtt_recv_flag = 1;
memset(mqtt_recv_msg, 0, sizeof(mqtt_recv_msg));
memcpy(mqtt_recv_msg, msg, strlen(msg));
}
}
json_decode() 用 cJSON 解析 OneNET 下发的 JSON 指令,提取 id 和 params.LEDSwitch,打印 LED 状态,并往 subtopic_reply 主题发布成功响应。
void json_decode(char *msg)
{
int ret;
char replymsg[128] = {0};
cJSON *id = NULL;
cJSON *jsondata = NULL;
cJSON *params = NULL;
cJSON *LED = NULL;
jsondata = cJSON_Parse(msg);
if (jsondata == NULL) { printf("json parse fail.\r\n"); return; }
id = cJSON_GetObjectItem(jsondata, "id");
params = cJSON_GetObjectItem(jsondata, "params");
LED = cJSON_GetObjectItem(params, "LEDSwitch");
if (LED->valueint) printf("LED ON\r\n");
else printf("LED OFF\r\n");
/* 构造并发布响应 */
sprintf(replymsg, "{\"id\":\"%s\",\"code\":200,\"msg\":\"success\"}", id->valuestring);
printf("reply:%s\r\n", replymsg);
pubmessage.qos = QOS0;
pubmessage.payload = replymsg;
pubmessage.payloadlen = strlen(replymsg);
ret = MQTTPublish(&c, mqtt_params.subtopic_reply, &pubmessage);
if (ret != SUCCESSS) run_status = ERR;
else printf("publish:%s,%s\r\n\r\n", mqtt_params.subtopic_reply, (char *)pubmessage.payload);
cJSON_Delete(jsondata);
}JSON 解析说明
json_decode() 使用 cJSON 库解析OneNET 下发的指令格式为 {"id":"xxx","version":"1.0","params":{"LEDSwitch":1}}。解析后除了打印 LED 状态,还会自动构造响应 {"id":"xxx","code":200,"msg":"success"} 并通过 subtopic_reply 主题回发给 OneNET 平台,确认指令已成功处理。
运行验证
打开 WIZ UartTool V1.0,连接 DEBUG USB 对应 COM 口(115200 / 8N1)。烧录例程运行后,程序首先进行 PHY 链路检测,然后通过 DHCP 获取网络参数,并完成 DNS 解析与 TCP 连接,最后连接 OneNET 平台的 MQTT 服务器进行通信测试。预期串口输出如下:
W55MH32 MQTT OneNET example
PHY link
The current Mbtis speed : 100Mbps
The current Duplex Mode : Full-Duplex
DHCP running
DHCP success!
====================================================================================================
W55MH32 network configuration : DHCP
MAC : 00:08:DC:00:0E:9A
IP : 192.168.1.6
Subnet Mask : 255.255.255.0
Gateway : 192.168.1.1
DNS : 192.168.1.1
====================================================================================================
> Translated mqtts.heclouds.com to 183.230.40.96
Connect to the MQTT server: 183.230.40.96:1883
Connected:success
Subscribing to $sys/70TwP2gxl5/W5100S_W5500/thing/property/set
Subscribed:success
Subscribing to $sys/70TwP2gxl5/W5100S_W5500/thing/property/post/reply
Subscribed:success
publish:$sys/70TwP2gxl5/W5100S_W5500/thing/property/post,{"id":"123","version":"1.0","params":{"CurrentTemperature":{"value":26.6}}}
recv:$sys/70TwP2gxl5/W5100S_W5500/thing/property/post/reply,{"id":"123","code":200,"msg":"success"}
连接成功后,设备会订阅 set 主题和 post/reply 主题,并向 post 主题发布物模型数据。在 OneNET 平台的设备详情页可查看上报的数据,也可通过平台下发属性设置指令。设备收到 set 指令后会解析 JSON、打印 LED 状态,并向 set_reply 主题发布成功响应。


常见问题
Q: OneNET 的 token 鉴权方式如何生成
OneNET token 格式为 version&res&et&method&sign。其中 res 为资源路径(products/{pid}/devices/{device_id}),et 为过期时间戳,method 为加密方法(sha1/sha256),sign 由 access_key 通过 HMAC 加密生成。可使用 OneNET 官方提供的 token 生成工具生成。
Q: MQTT 连接失败如何排查
首先确认网络能正常访问互联网;检查 DNS 是否成功解析 mqtts.heclouds.com;确认 clientid、username、passwd 配置正确(passwd 为 token);检查 token 是否过期(et 字段);OneNET 平台确认设备已创建且处于启用状态。
Q: 为什么要订阅 post/reply 主题
设备向 post 主题发布数据后,OneNET 平台会向 post/reply 主题回复处理结果(含 code、msg)。订阅该主题可确认数据上报是否成功。另外,设备收到 set 指令后也要往 set_reply 主题回复,否则平台会认为设备未响应。
Q: OneNET 物模型的 Topic 命名规则是什么
OneNET 物模型使用 $sys/{pid}/{device_id}/thing/... 格式。属性上报:$sys/{pid}/{device_id}/thing/property/post;属性设置(设备订阅接收指令):$sys/{pid}/{device_id}/thing/property/set;属性设置回复:$sys/{pid}/{device_id}/thing/property/set_reply;属性上报回复:$sys/{pid}/{device_id}/thing/property/post/reply。其中 pid 为产品 ID,device_id 为设备 ID。
Q: 发布的 JSON 数据格式有什么要求
OneNET 物模型要求 JSON 数据包含 id、version、params 字段。params 中为属性键值对,属性名需与平台定义的物模型属性标识一致。例如 {"id":"123","version":"1.0","params":{"CurrentTemperature":{"value":26.6}}}。
总结
本文讲解了如何在 W55MH32 芯片上通过 MQTT 协议连接中移物联网 OneNET 平台,通过实战例程展示了完整的接入流程:
- MQTT 协议:基于发布/订阅模式的轻量级物联网通信协议,运行在 TCP 之上
- OneNET 接入:DNS 解析 → TCP 连接 → token 鉴权登录 → 订阅(set + post/reply)→ 发布
- 关键 API:
MQTTConnect / MQTTSubscribe / MQTTPublish实现连接、订阅和发布 - 消息收发:
messageArrived()回调接收,json_decode()用 cJSON 解析 OneNET 下发的 JSON 指令并回复 - 数据格式:使用 OneNET 物模型 JSON 格式,包含 id、version、params 字段