W5500 如何通过 MQTT 协议连接阿里云
一、 简介:
1、 开发环境与连接平台:
本文主要介绍 W5500 如何通过 MQTT 协议将设备连接到阿里云 IoT,并通过 MQTT 协议实
现通信。MQTT 协议是基于 TCP 的协议,所以我们只需要在单片机端实现 TCP 客户端代码之
后就很容易移植 MQTT 了, +W5500 实现 TCP 客户端的代码我们以前已经实现过,程序下载
地址为(http://www.w5500.com/)
软件环境:Windows
硬件环境:STM32F103+W5500
开发工具:Keil uVision5
调试工具:Wireshark、串口调试助手
连接平台:阿里云-华东 2 节点(https://www.aliyun.com)
2、 MQTT 简介:
MQTT 官网地址:(http://mqtt.org/)
1) MQTT 协议特点
MQTT 是一个基于客户端-服务器的消息发布/订阅传输协议。MQTT 协议是轻量、简单、
开放和易于实现的,这些特点使它适用范围非常广泛。在很多情况下,包括受限的环境中,
如:机器与机器(M2M)通信和物联网(IoT)。其在,通过卫星链路通信传感器、偶尔拨号
的医疗设备、智能家居、及一些小型化设备中已广泛使用。
MQTT 协议当前版本为,2014 年发布的 MQTT v3.1.1。除标准版外,还有一个简化版 MQTT-
SN,该协议主要针对嵌入式设备,这些设备一般工作于百 TCP/IP 网络,如:ZigBee。
MQTT 协议运行在 TCP/IP 或其他网络协议,提供有序、无损、双向连接。其特点包括:
使用的发布/订阅消息模式,它提供了一对多消息分发,以实现与应用程序的解耦。
对负载内容屏蔽的消息传输机制。
对传输消息有三种服务质量(QoS):
最多一次,这一级别会发生消息丢失或重复,消息发布依赖于底层 TCP/IP 网络。
即:<=1
至多一次,这一级别会确保消息到达,但消息可能会重复。即:>=1
只有一次,确保消息只有一次到达。即:=1。在一些要求比较严格的计费系统中,
可以使用此级别
数据传输和协议交换的最小化(协议头部只有 2 字节),以减少网络流量
通知机制,异常中断时通知传输双方
2) MQTT 协议原理及实现方式
实现 MQTT 协议需要:客户端和服务器端
MQTT 协议中有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者
(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者
可以同时是订阅者。
MQTT 传输的消息分为:主题(Topic)和消息的内容(payload)两部分
Topic,可以理解为消息的类型,订阅者订阅(Subscribe)后,就会收到该主题的消息
内容(payload)
payload,可以理解为消息的内容,是指订阅者具体要使用的内容
二、 连接
1. 阿里云连接步骤:
1) 以 aliyun 账号直接进入 IoT 控制台,如果还没有开通阿里云物联网套件服务,则
申请开通
2) 接入引导
(1)、创建产品
(2)、添加设备
(3)、获取设备的 Topic
创建产品
初步进入控制台后,需要创建产品。点击创建产品。产品相当于某一类设备的集合,用
户可以根据产品管理其设备等。
产品名称:对产品命名,例如可以填写产品型号。产品名称在账号内保持唯一。
productKey:阿里云 IoT 为产品颁发的全局唯一标识符
添加设备:
创建完产品之后,可以为该产品添加设备。进入产品管理页面下的设备管理,点击添加
设备。
说明:用户可以自定义设备名称(即 deviceName),这个名称即可作为设备唯一标
识符,用户可以基于该设备名称与 IoT Hub 进行通信,需要指出的是,用户需要保
证 deviceName 产品内唯一。
设备证书:添加设备之后,物联网套件为设备颁发的唯一标识符,设备证书用于设
备认证以及设备通信,详细的请参考设备接入文档。
deviceName:用户自定义设备唯一标识符,用于设备认证以及设备通信,用户保证
产品维度内唯一。
deviceSecret:物联网套件为设备颁发的设备秘钥,用于认证加密,与 deviceName
或者 deviceId 成对出现。
获取设备的 Topic
添加设备之后,可以获取设备的 Topic。点击 Topic 列表
说明:创建产品之后,物联网套件都会为产品默认定义三个 Topic 类。那么,在添
加设备之后,每个设备都会默认有三个 Topic,即图中所示。如果想要增加、修改、
删除 Topic,请到消息通信重新定义 Topic 类。
设 备 可 以 基 于 Topic 列 表 中 的 Topic 进 行 Pub/Sub 通 信 , 例 如 列 表 中 有
/1000118502/test9/update,且设备拥有的权限是发布,这就意味着设备可以往这
个 Topic 发布消息;同样,列表中/1000118502/test9/get,权限是订阅,这就意味
着设备可以从这个 Topic 订阅消息。
设备接入
获得 productKey、设备证书以及设备的 Topic 这些参数,就可以基于 aliyun IoT device
SDK for C 将设备连接上 IoT Hub 并进行通信,具体请参考《MQTT 配置》部分
2. MQTT 移植步骤:
MQTT 代码源码下载地址:(http://www.eclipse.org/paho/)
MQTT 的移植非常简单,将 C/C++ MQTT Embedded clients 的代码添加到工程中,然后
我们只需要再次封装 4 个函数即可:
int transport_sendPacketBuffer(unsigned char* buf, int buflen);
通过网络以 TCP 的方式发送数据;
int transport_getdata(unsigned char* buf, int count);
TCP 方式从服务器端读取数据,该函数目前属于阻塞函数;
int transport_open(void);
打开一个网络接口,其实就是和服务器建立一个 TCP 连接;
int transport_close(void);
关闭网络接口。
如果已经移植好了 socket 方式的 TCP 客户端的程序,那么这几个函数的封装也是非常
简单的,程序代码如下所示:
1 /**
2 * @brief 通过 TCP 方式发送数据到 TCP 服务器
3 * @param buf 数据首地址
4 * @param buflen 数据长度
5 * @retval 小于 0 表示发送失败
6 */
7
8 /*订阅消息*/
9 int Subscribe_sendPacketBuffer(unsigned char* buf, int buflen)
10 {
11 return send(SOCK_TCPS,buf,buflen);
12 }
13
14 /*发布消息*/
15 int Published_sendPacketBuffer(unsigned char* buf, int buflen)
16 {
17 return send(SOCK_TCPC,buf,buflen);
18 }
19
20 /**
21 * @brief 阻塞方式接收 TCP 服务器发送的数据
22 * @param buf 数据存储首地址·
23 * @param count 数据缓冲区长度
24 * @retval 小于 0 表示接收数据失败
25 */
26 int Subscribe_getdata(unsigned char* buf, int count)
27 {
28
29 return recv(SOCK_TCPS,buf,count);
30
31 }
32
33 int Published_getdata(unsigned char* buf, int count)
34 {
35 return recv(SOCK_TCPC,buf,count);
36
37 }
38
39 /**
40 * @brief 打开一个 socket 并连接到服务器
41 * @param 无
42 * @retval 小于 0 表示打开失败
43 */
44 int Subscribe_open(void)
45 {
46 int32_t ret;
47 //新建一个 socket 并绑定本地端口 5000
48 ret = socket(SOCK_TCPS,Sn_MR_TCP,50000,0x00);
49 if (ret != 1) {
50 printf("%d:Socket Error\r\n",SOCK_TCPS);
51 while (1);
52 } else {
53 printf("%d:Opened\r\n",SOCK_TCPS);
54 }
55
56
57 while (getSn_SR(SOCK_TCPS)!=SOCK_ESTABLISHED) {
58 printf("connecting\r\n");
60 //连接 TCP 服务器÷
61 ret = connect(SOCK_TCPS,server_ip,1883);
62 //端口必须为 1883
63 }
63 if (ret != 1) {
64 printf("%d:Socket Connect Error\r\n",SOCK_TCPS);
65 while (1);
66 } else {
67 printf("%d:Connected\r\n",SOCK_TCPS);
68 }
69 return 0;
70 }
71
72 int Published_open(void)
73 {
74 int32_t ret;
75
76 ret = socket(SOCK_TCPC,Sn_MR_TCP,5001,0x00);
77
78 if (ret != 1) {
79 printf("%d:Socket1 Error1\r\n",SOCK_TCPC);
80 while (1);
81 } else {
82 printf("%d:socket1 Opened\r\n",SOCK_TCPC);
83 }
84
85
86 while (getSn_SR(SOCK_TCPC)!=SOCK_ESTABLISHED) {
87 ret = connect(SOCK_TCPC,server_ip,1883);
88 //端口必须为 1883
89 }
90 if (ret != 1) {
91 printf("%d:Socket Connect1 Error\r\n",SOCK_TCPC);
92 while (1);
93 } else {
94 printf("%d:Connected1\r\n",SOCK_TCPC);
95 }
96 return 0;
97 }
98
99 }
100
101 /**
102 * @brief 关闭 socket
103 * @param 无
104 * @retval 小于 0 表示关闭失败
105 */
106 int Subscribe_close(void)
107 {
108 disconnect(SOCK_TCPS);
109 printf("close0\n\r");
110
111 while (getSn_SR(SOCK_TCPC)!=SOCK_CLOSED) {
112 ;
113 }
114 return 0;
115 }
116
117
118 int Published_close(void)
119 {
120 disconnect(SOCK_TCPC);
121 printf("close1\n\r");
122
123 while (getSn_SR(SOCK_TCPC)!=SOCK_CLOSED) {
124 ;
125 }
126 return 0;
127 }
3、
MQTT 配置
1) MQTT 连接参数说明
举例:
2) MQTT 与阿里云连接函数:
参考阿里云内 MQTT 设备接入手册,计算出设备连接的各项参数,例如下列程序中框中的部
分为本例程 MQTT 与阿里云连接的参数的配置,详细内容如下:
clientId = 192.168.207.115
deviceName = MQTT1
productKey = TKKMt4nMF8U
timestamp = 789(毫秒值)
signmethod = hmacsha1(算法类型)
deviceSecret = secret
那么使用 tcp 方式提交给 mqtt 参数分别如下:
(1) mqttClientId:clientId+"|securemode=3,signmethod=hmacsha1,timestamp=789|"
clientId=192.168.207.115|securemode=3,signmethod=hmacsha1,timestamp=789|
(2) keepalive 时间需要设置超过 60 秒以上,否则会拒绝连接。
(3) Cleansession 为 1;
(4) mqttUsername: deviceName+"&"+productKey
username = "MQTT1&TKKMt4nMF8U"
(5) password=hmacsha1("secret","clientId192.168.207.115deviceNameMQTT1productKe
yTKKMt4nMF8Utimestamp789").toHexString();
最 后 是 二 进 制 转 16 制 字 符 串 大 小 写 不 敏 感 。 这 个 例 子 结 果 为
9076b0ebc04dba8a8ebba1f0003552dbc862c9b9
MQTT 连接函数原型,tcp_client.c 文件中的 MQTT_CON_ALI 函数中调用 make_con_msg
函数并通过阿里云设备的参数,设置 MQTT 连接阿里云函数的参数:
1 void make_con_msg(char* clientID,int keepalive, uint8 cleansession,
2 char*username,char* password,unsigned char*buf,int
3 buflen)
4 {
5 int32_t len,rc;
6 MQTTPacket_connectData data = MQTTPacket_connectData_initializer;
7 data.clientID.cstring = clientID;
8 data.keepAliveInterval = keepalive;
9 data.cleansession = cleansession;
10 data.username.cstring = username;
11 data.password.cstring = password;
12 len = MQTTSerialize_connect(buf, buflen, &data);
13 //构造链接报文
14 return;
15 }
MQTT 连接过程:
1 void MQTT_CON_ALI(void)
2 {
3 int len;
4 int type;
5 switch (getSn_SR(0)) {
6 //获取 socket0 的状态
7 case SOCK_INIT:
8 //Socket 处于初始化完成(打开)状态
9 connect(0, server_ip,server_port);
10 //配置 Sn_CR 为 CONNECT,并向 TCP 服务器发出连接请求¢
11
12
13 break;
14 case SOCK_ESTABLISHED: //
15 Socket 处于连接建立状态
16 if (getSn_IR(0) & Sn_IR_CON) {
17 setSn_IR(0, Sn_IR_CON); //
18 Sn_IR 的 CON 位置 1,通知 W5500 连接已建立
19
20 }
21 memset(msgbuf,0,sizeof(msgbuf));
22 if ((len=getSn_RX_RSR(0))==0) {
23 if (1==CONNECT_FLAG) {
24 printf("send connect\r\n");
25
26 /*MQTTÆ 拼接连接报文
27 *根据阿里云平台 MQTT 设备接入手册配置
28
29 */
30
31 //void make_con_msg(char* clientID,int keepalive,
32 uint8 cleansession,char*username,
33 char* password,unsigned char*buf,
34 int buflen)
35 make_con_msg("192.168.207.115|securemode=3,
36 signmethod=hmacsha1,timestamp=789|",180,
37 1,"MQTT1&TKKMt4nMF8U",
38 "9076b0ebc04dba8a8ebba1f0003552dbc862c9b9"
39 ,msgbuf,sizeof(msgbuf));
40
41
42 //printf(" server_ip: %d.%d.%d.%d\r\n", server_ip[0],
43 server_ip[1],server_ip[2],server_ip[3]);
44 //printf("connect ALY\r\n");
45 CONNECT_FLAG = 0;
46 send(0,msgbuf,sizeof(msgbuf));
47 Delay_s(2);
48 while ((len=getSn_RX_RSR(0))==0) {
49 Delay_s(2);
50 send(0,msgbuf,sizeof(msgbuf));
51 };
52 recv(0,msgbuf,len);
53 while (mqtt_decode_msg(msgbuf)!=CONNACK) {
54 //判断是不是 CONNACK
55 printf("wait ack\r\n");
56 }
57 } else if (SUB_FLAG == 1) {
58 memset(msgbuf,0,sizeof(msgbuf));
59 make_sub_msg(topic,msgbuf,sizeof(msgbuf));
60 // make_pub_msg(topic,msgbuf,sizeof(msgbuf),"hello");
61 send(0,msgbuf,sizeof(msgbuf));
62 // 接收到数据后再回给服务器,完成数据回环
63
64 SUB_FLAG = 0;
65 Delay_s(2);
66 while ((len=getSn_RX_RSR(0))==0) {
67 Delay_s(2);
68 send(0,msgbuf,sizeof(msgbuf));
69 };
70 recv(0,msgbuf,len);
71 while (mqtt_decode_msg(msgbuf)!=SUBACK) {
72 //判断是不是 SUBACK
73 printf("wait suback\r\n");
74 }
75 TIM_Cmd(TIM2, ENABLE);
76 printf("send sub\r\n");
77
78 }
79 #if 1
80 else {
81 //count++;
82 // Delay_s(2);
83 if (count>10000) {
84 count = 0;
85 make_ping_msg(msgbuf,sizeof(msgbuf));