文章目录
- MQTT协议
-
- [1. 基础知识](#1. 基础知识)
- [2. 报文形式](#2. 报文形式)
- [3. 连接报文](#3. 连接报文)
- [4. 心跳报文](#4. 心跳报文)
- [5. 订阅报文](#5. 订阅报文)
-
- [5.1. 订阅主题报文SUBSCRIBE](#5.1. 订阅主题报文SUBSCRIBE)
- [5.2. 订阅确认SUBACK](#5.2. 订阅确认SUBACK)
- [5.3. 取消订阅UNSUBSCRIBE](#5.3. 取消订阅UNSUBSCRIBE)
- [5.4. 取消订阅确认UNSUBACK](#5.4. 取消订阅确认UNSUBACK)
- [6. 发布报文](#6. 发布报文)
-
- [6.1. 发布消息PUBLISH](#6.1. 发布消息PUBLISH)
- [6.2. 发布确认PUBACK](#6.2. 发布确认PUBACK)
- [7. 阿里云账号创建](#7. 阿里云账号创建)
- [8. 网络调试助手接入阿里云](#8. 网络调试助手接入阿里云)
-
- [8.1. CONNECT连接服务器](#8.1. CONNECT连接服务器)
- [8.2. 心跳请求与心跳响应](#8.2. 心跳请求与心跳响应)
- [8.3. 主题订阅](#8.3. 主题订阅)
- [8.4. 取消订阅](#8.4. 取消订阅)
- [8.5. 发布消息](#8.5. 发布消息)
- [8.6. 发布确认](#8.6. 发布确认)
- [9. 串口调试助手+ESP8266接入阿里云](#9. 串口调试助手+ESP8266接入阿里云)
- [10. 使用正点原子战舰F103连接阿里云](#10. 使用正点原子战舰F103连接阿里云)
MQTT协议
1. 基础知识
-
mqtt简介:
MQTT(
Message Queuing Telemetry Transport
,消息队列遥测传输协议)是一种基于发布/订阅 模式的轻量级 通信协议,由IBM于1999年发布。MQTT专门针对物联网设备开发,是一种低开销 、低带宽占用 的即时通讯协议。该协议构建于TCP/IP
协议上,旨在为低带宽和不稳定网络环境中的物联网设备,提供可靠的网络服务。它的设计思想是简单、开放、规范,易于实现,这些特点使其非常适合机器间通信(M2M)、物联网(IoT)等场景。 -
mqtt特点:
- 使用发布/订阅消息模式,提供了一对多的消息分发和应用之间的解耦;
- 消息传输不需要知道负载内容;
- 提供三种等级的服务质量。
- "最多一次",尽操作环境所能提供的最大努力分发消息,消息可能会丢失。例如,这个等级可用于环境传感器数据,单次的数据丢失没关系,因为不久之后会再次发送。
- "至少一次",保证消息可以到达,但是可能会重复。
- "仅一次",保证消息只到达一次。例如,这个等级可用在一个计费系统中,这里如果消息重复或丢失会导致不正确的收费。
- 很小的传输消耗和协议数据交换,最大限度减少网络流量;
- 异常连接断开发生时,能通知到相关各方。
-
mqtt实现原理:
三种身份: 发布者(Publish)、 代理者(Broker)、 订阅者(Subscribe)
发布者和代理者都是客户端,代理者是服务器。
消息分类: 主题(Topic)和负载(payload)
主题:消息的类型/主题,订阅者订阅(Subscribe)后,就会收到该主题的消息内容(payload);
负载:消息的内容,指订阅者需要的具体内容。
2. 报文形式
-
报文格式:
名称 所属情况 Fixed Header 固定报头 所有控制报文都包含 Variable Header 可变报头 部分控制报文包含 Payload 有效载荷 部分控制报文包含 -
固定报头:
-
控制报文的类型:
字节1 4-7位:
报文类型 功能描述 流动方 向 值 固定报 头 可变报 头 报文标识符 负载 CONNECT 客户端请求连接服务端 C->S 1 有 有 无 有 CONNACK 服务端确认连接建立 S->C 2 有 有 无 有 PUBLISH 发布消息 C<=>S 3 有 有 有 (Qos>0) 有 PUBACK 收到发布消息确认(QoS1等 级) C<=>S 4 有 有 有 无 PUBREC 发布消息收到 (QoS2等级) C<=>S 5 有 有 有 无 PUBREL 发布消息释放 (QoS2等级) C<=>S 6 有 有 有 无 PUBCOMP 发布消息完成 (QoS2等级) C<=>S 7 有 有 有 无 SUBSCRIBE 订阅请求 C->S 8 有 有 有 有 SUBACK 订阅确认 S->C 9 有 有 有 有 UNSUBSCRIBE 取消订阅 C->S 10 有 有 有 有 UNSUBACK 取消订阅确认 S->C 11 有 有 有 无 PINGREQ (保活)心跳请求 C->S 12 有 无 无 无 PINGRESP 心跳响应 S->C 13 有 无 无 无 DISCONNECT 客户端断开连接 C->S 14 有 无 无 无 重点关注加粗的报文类型
字节1 0-3位:
标志位:
字节2... 剩余长度值:
剩余长度等于可变报头的长度(10字节)加上有效载荷的长度;
剩余长度(Remaining Length)表示当前报文剩余部分的字节数,包括可变报头和负载的数据;
剩余长度不包括 用于编码剩余长度字段本身的字节数。
字节长度:
字节数 表示长度的最小值 (字节) 表示长度的最大值 (字节) 1 0 2^7 - 1 = 127 2 2^7 = 128 (注意进位标志) 2^(7*2) - 1 = 16383 3 2^(7*2) = 16384 2^(7*3) - 1 = 2097151 4 2^(7*3) = 2097152 2^(7*4) - 1 = 268435455
3. 连接报文
客户端到服务端的网络连接建立后,客户端发送给服务端的第一条报文必须是 CONNECT 报文。
在一个网络连接上,客户端只能发送一次CONNECT报文。服务端必须将客户端发送的第二个CONNECT报文当作协议违规处理并断开客户端的连接。
-
连接报文帧格式:
功能 字节空间 固定报头 MQTT报文类型+保留位 1 Byte 剩余长度 1 ~ 4 Byte 可变报头 协议名 6 Byte 协议级别 1 Byte 连接标志 1 Byte 保持连接时间 2 Byte 有效载荷 -- -- -
固定报头:
-
可变报头: 协议名(Protocol Name) ,协议级别(Protocol Level) ,连接标志(Connect Flags)和保持连接(Keep Alive)。
协议名(Protocol Name):6个字节保持不变
协议级别(Protocol Level):3.1.1版本的MQTT协议规定
连接标志(Connect Flags) :最常见组合
1100 0010
,即0xC2
保持连接(Keep Alive):以秒为单位的连接时间,最大时间 18小时12分15秒
-
-
有效负载:
CONNECT报文的有效载荷(payload)包含一个或多个以长度为前缀的字段,可变报头中的标志决定是否包含这些字段。如果包含的话,必须按这个顺序出现:客户端标识符 ,遗嘱主题 ,遗嘱消息 ,用户名 ,密码。
-
CONNACK连接响应
功能 byte 1 固定报头 MQTT报文类型+保留位 byte 2 剩余长度 byte 3 可变报头 连接确认标志 byte 4 连接返回码 可变报头返回值:
值 返回码响应 描述 0 0x00连接已接受 连接已被服务端接受 1 0x01连接已拒绝,不支持的协议版本 服务端不支持客户端请求的MQTT协议级别 2 0x02连接已拒绝,不合格的客户端标识符 客户端标识符是正确的UTF-8编码,但服务端不允许使用 3 0x03连接已拒绝,服务端不可用 网络连接已建立,但MQTT服务不可用 4 0x04连接已拒绝,无效的用户名或密码 用户名或密码的数据格式无效 5 0x05连接已拒绝,未授权 客户端未被授权连接到此服务器 6 ~ 255 Reserved 保留 具体结果:
正确 ,返回:
20 02 00 00
,并且保持连接。00 连接成功。
错误 ,返回:20 02 00 04
,并且断开连接。04 无效的用户名和密码。 -
DISCONNECT断开连接
4. 心跳报文
-
PINGREQ:客户端发给服务器
作用:-
在没有任何其它控制报文从客户端发给服务的时,告知服务端客户端还活着;
-
请求服务端发送 响应确认它还活着;
-
使用网络以确认网络连接没有断开。
-
-
保持连接(Keep Alive)是一个以秒为单位的时间间隔,表示为一个16位的字,它是指在客户端传输完成一个控制报文的时刻到发送下一个报文的时刻,两者之间允许空闲的最大时间间隔。客户端负责保证控制报文发送的时间间隔不超过保持连接的值。如果没有任何其它的控制报文可以发送,客户端必须发送一个PINGREQ报文。
客户端发送了PINGREQ报文 之后,如果在合理的时间内仍没有收到服务器发来的PINGRESP报文,它应该关闭到服务端的网络连接。
-
报文形式:
C0 00
-
PINGRESP:服务器发给客户端
作用:服务端发送 PINGRESP 报文响应客户端的 PINGREQ 报文。表示服务端还活着。
-
报文形式:
D0 00
5. 订阅报文
5.1. 订阅主题报文SUBSCRIBE
客户端向服务端发送SUBSCRIBE报文用于创建一个或多个订阅。每个订阅注册客户端关心的一个或多个主题。为了将应用消息转发给那些订阅匹配的主题,服务端发送PUBLISH报文给客户端。 SUBSCRIBE报文也(为每个订阅)指定了最大的QoS等级,服务端根据这个发送应用消息给客户端。
-
固定报头:
82 ?
剩余长度字段:等于SUBSCRIBE可变报头的长度(2字节)加上有效载荷的长度。
-
可变报头:
00 0A
-
有效负载:
5.2. 订阅确认SUBACK
服务端发送SUBACK报文给客户端,用于确认它已收到并且正在处理SUBSCRIBE报文。 SUBACK报文包含一个返▣码清单,它们指定了SUBSCRIBE请求的每个订阅被授予的最大QoS等级。
-
固定报头:
90 ?
-
可变报头:
-
有效负载:
允许的返回码:
0x00 - 最大 Qos0 ;
0x01 - 成功 - 最大 Qos1 ;
0x02 - 成功 - 最大 Qos2 ;
0x80 - Failure 失败 ;
0x00, 0x01, 0x02, 0x80之外的SUBACK返回码是保留的,不能使用。
5.3. 取消订阅UNSUBSCRIBE
客户端发送UNSUBSCRIBE报文给服务端,用于取消订阅主题。
-
固定报头:
UNSUBSCRIBE报文固定报头的第3,2,1,0位是保留位且必须分别设置为
0,0,1,0
。服务端必须认为任何其它的值都是不合法的并关闭网络连接。 -
可变报头:
-
有效负载:
UNSUBSCRIBE报文的有效载荷包含客户端想要取消订阅的主题过滤器列表。UNSUBSCRIBE报文中的主题过滤器必须是连续打包的UTF-8编码字符串。 UNSUBSCRIBE报文的有效载荷必须至少包含一个消息过滤器。没有有效载荷的UNSUBSCRIBE报文是违反协议的。
5.4. 取消订阅确认UNSUBACK
服务端发送UNSUBACK报文给客户端用于确认收到UNSUBSCRIBE报文。
-
固定报头:
-
可变报头:
-
有效负载:
UNSUBACK 报文没有有效载荷。
6. 发布报文
6.1. 发布消息PUBLISH
PUBLISH控制报文是指从客户端向服务端或者服务端向客户端传输一个应用消息。
-
固定报头:
DUP位:
如果DUP标志被设置为0,表示这是客户端或服务端第一次请求发送这个PUBLISH报文。如果DUP标志被设置为1,表示这可能是一个早前报文请求的重发。
客户端或服务端请求重发一个PUBLISH报文时,必须将DUP标志设置为1。对于QoS 0的消息,DUP标志必须设置为0。
QoS等级位:
RETAIN保留标志位:
如果客户端发给服务端的PUBLISH报文的保留(RETAIN)标志被设置为1,服务端必须存储这个应用消息和它的服务质量等级(QoS),以便它可以被分发给未来的主题名匹配的订阅者。
-
可变报头:
可变报头按顺序包含主题名 和报文标识符。
byte 1 Length MSB byte 2 Length LSB byte 3...N 主题名 byte N+1 报文标识符 MSB (Qos = 1/2时) byte N+2 报文标识符 LSB (Qos = 1/2时) 主题名(Topic Name)用于识别有效载荷数据应该被发布到哪一个信息通道。
主题名必须是PUBLISH报文可变报头的第一个字段。
PUBLISH报文中的主题名不能包含通配符。
服务端发送给订阅客户端的PUBLISH报文的主题名必须匹配该订阅的主题过滤器。
-
有效负载:
有效载荷包含将被发布的应用消息。数据的内容和格式是应用特定的。有效载荷的长度这样计算:用固定报头中的剩余长度字段的值减去可变报头的长度。包含零长度有效载荷的PUBLISH报文是合法的。
响应:PUBLISH报文的接收者必须按照根据PUBLISH报文中的QoS等级发送响应。
服务质量等级 预期响应 Qos 0 无响应 Qos 1 PUBACK 报文 Qos 2 PUBREC 报文 动作:
- 客户端使用PUBLISH报文发送应用消息给服务端,目的是分发到其它订阅匹配的客户端。
- 服务端使用PUBLISH报文发送应用消息给每一个订阅匹配的客户端。
6.2. 发布确认PUBACK
PUBACK报文是对QoS 1等级的PUBLISH报文的响应。
-
固定报头:
-
可变报头:
包含等待确认的PUBLISH报文的报文标识符。
-
有效负载:
PUBACK 报文没有有效载荷。
7. 阿里云账号创建
创建过程
-
登录阿里云平台 --->产品--->物联网平台--->管理控制台--->公共实例
-
创建产品--->设备管理--->产品--->创建产品
填写 " 产品名称 "、" 所属品类 "、" 节点类型 "、" 连接方式 "、" 数据格式 "、" 认证方式 ",点击下方保存
-
创建设备--->产品--->管理设备--->添加设备
-
获取三元组: 相当于每个设备的账号和密码
设备--->查看--->右上角(查看)
ProductKey: k1p51foVXYV DeviceName: mseeding DeviceSecret: 2474847d77290ce2fd498a6317615c9c
8. 网络调试助手接入阿里云
8.1. CONNECT连接服务器
-
有效载荷字段中,客户端ID、用户名、密码的合成方法:
-
客户端ID:
c方法: *|securemode=3,signmethod=hmacsha1| //*为设备名称 最终样式: mseeding|securemode=3,signmethod=hmacsha1|
-
用户名:
c方法: *&# //*为设备名称,#是ProductKey 最终样式: mseeding&k1p51foVXYV
-
密码:
c方法:用DeviceSecret做为秘钥对clientId*deviceName*productKey#进行hmacsha1加密后的结果 //*为设备名称,#是ProductKey 计算过程: clientIdmseedingdeviceNamemseedingproductKeyk1p51foVXYV 用DeviceSecret做为秘钥,进行HmacSHA1加密 最终样式: 31c4033811c5772f3ae38bf8f1ba8e1caf0ccd79
-
-
转换成16进制形式:
字符格式 16 进制ASC2编码格式 长度 客户端ID mseeding|securemode=3,signmethod=hmacsha1| 6D 73 65 65 64 69 6E 67 7C 73 65 63 75 72 65 6D 6F 64 65 3D 33 2C 73 69 67 6E 6D 65 74 68 6F 64 3D 68 6D 61 63 73 68 61 31 7C 42 用户名 mseeding&k1p51foVXYV 6D 73 65 65 64 69 6E 67 26 6B 31 70 35 31 66 6F 56 58 59 56 20 密码 31c4033811c5772f3ae38bf8f1ba8e1caf0ccd79 33 31 63 34 30 33 33 38 31 31 63 35 37 37 32 66 33 61 65 33 38 62 66 38 66 31 62 61 38 65 31 63 61 66 30 63 63 64 37 39 40 -
CONNECT 报文帧的合成过程:
c1. 固定报头:2字节 10 ? 2. 可变报头:10字节 00 04 4D 51 54 54 04 C2 01 2C 3. 客户端ID的字节长度42 + 客户端ID:44字节 00 2A 6D 73 65 65 64 69 6E 67 7C 73 65 63 75 72 65 6D 6F 64 65 3D 33 2C 73 69 67 6E 6D 65 74 68 6F 64 3D 68 6D 61 63 73 68 61 31 7C 4. 用户名的字节长度20 + 用户名:22字节 00 14 6D 73 65 65 64 69 6E 67 26 6B 31 70 35 31 66 6F 56 58 59 56 5. 密码的字节长度40 + 密码:42字节 00 28 33 31 63 34 30 33 33 38 31 31 63 35 37 37 32 66 33 61 65 33 38 62 66 38 66 31 62 61 38 65 31 63 61 66 30 63 63 64 37 39 6. 可变报头 + 有效载荷 的总长度 = 118字节 10 76 7. 合成最终报文
最终报文
java10 76 00 04 4D 51 54 54 04 C2 01 2C 00 2A 6D 73 65 65 64 69 6E 67 7C 73 65 63 75 72 65 6D 6F 64 65 3D 33 2C 73 69 67 6E 6D 65 74 68 6F 64 3D 68 6D 61 63 73 68 61 31 7C 00 14 6D 73 65 65 64 69 6E 67 26 6B 31 70 35 31 66 6F 56 58 59 56 00 28 33 31 63 34 30 33 33 38 31 31 63 35 37 37 32 66 33 61 65 33 38 62 66 38 66 31 62 61 38 65 31 63 61 66 30 63 63 64 37 39
-
串口显示
8.2. 心跳请求与心跳响应
-
PINGREQ: 客户端发给服务器 报文形式:
C0 00
PINGRESP:服务器发给客户端 报文形式:
D0 00
8.3. 主题订阅
查看:产品-> Topic类列表 -> 物模型通信Topic, 可查看与物模型通信用到的所有主题。
客户端到服务器: /sys/k1p51foVXYV/${deviceName}/thing/event/property/post
服务器到客户端: /sys/k1p51foVXYV/meseeding/thing/service/property/set
-
订阅报文帧SUBSCRIBE
c1. 固定报头:2字节 82 ? 2. 可变报头:2字节 00 03 3. 要订阅的Topic字节长度 + Topic:54字节 /sys/k1p51foVXYV/mseeding/thing/service/property/set 00 34 2F 73 79 73 2F 6B 31 70 35 31 66 6F 56 58 59 56 2F 6D 73 65 65 64 69 6E 67 2F 74 68 69 6E 67 2F 73 65 72 76 69 63 65 2F 70 72 6F 70 65 72 74 79 2F 73 65 74 4. Qos=0:1字节 00 5. 可变报头 + 有效载荷 的总长度 = 57字节 82 39 6. 合成最终报文
最终报文:
c82 39 00 03 00 34 2F 73 79 73 2F 6B 31 70 35 31 66 6F 56 58 59 56 2F 6D 73 65 65 64 69 6E 67 2F 74 68 69 6E 67 2F 73 65 72 76 69 63 65 2F 70 72 6F 70 65 72 74 79 2F 73 65 74 00
-
上传报文帧SUBSCRIBE
c1. 固定报头:2字节 82 ? 2. 可变报头:2字节 00 04 3. 要订阅的Topic字节长度 + Topic:53字节 /sys/k1p51foVXYV/mseeding/thing/event/property/post 00 33 2F 73 79 73 2F 6B 31 70 35 31 66 6F 56 58 59 56 2F 6D 73 65 65 64 69 6E 67 2F 74 68 69 6E 67 2F 65 76 65 6E 74 2F 70 72 6F 70 65 72 74 79 2F 70 6F 73 74 4. Qos=0:1字节 00 5. 可变报头 + 有效载荷 的总长度 = 56字节 82 38 6. 合成最终报文
最终报文:
c82 38 00 04 00 33 2F 73 79 73 2F 6B 31 70 35 31 66 6F 56 58 59 56 2F 6D 73 65 65 64 69 6E 67 2F 74 68 69 6E 67 2F 65 76 65 6E 74 2F 70 72 6F 70 65 72 74 79 2F 70 6F 73 74 00
-
串口显示:
-
阿里云界面: 设备管理 -> 设备 -> Topic列表
-
订阅确认SUBACK:
90 03 00 03 01
(00 03是自定义的报文标识符。01是返回码,表示订阅成功,且最大Qos等级为1)90 03 00 04 01
(00 04是自定义的报文标识符。01是返回码,表示订阅成功,且最大Qos等级为1)
8.4. 取消订阅
-
取消订阅:
/sys/k1p51foVXYV/mseeding/thing/service/property/set
-
取消订阅UNSUBSCRIBE
c1. 固定报头:2字节 A2 ? 2. 可变报头:2字节 00 03 3. 要取消订阅的Topic字节长度 + Topic:53字节 /sys/k1p51foVXYV/mseeding/thing/event/property/post 00 33 2F 73 79 73 2F 6B 31 70 35 31 66 6F 56 58 59 56 2F 6D 73 65 65 64 69 6E 67 2F 74 68 69 6E 67 2F 65 76 65 6E 74 2F 70 72 6F 70 65 72 74 79 2F 70 6F 73 74 4. 可变报头 + 有效载荷 的总长度 = 55字节 A2 37 5. 合成最终报文
最终报文:
cA2 37 00 03 00 33 2F 73 79 73 2F 6B 31 70 35 31 66 6F 56 58 59 56 2F 6D 73 65 65 64 69 6E 67 2F 74 68 69 6E 67 2F 65 76 65 6E 74 2F 70 72 6F 70 65 72 74 79 2F 70 6F 73 74
-
串口显示:
-
阿里云界面:
-
取消订阅确认:
B0 02 00 03
(00 03是自定义的报文标识符)
8.5. 发布消息
-
服务器发送给客户端
接收的数据:
解析过程:
30
:固定报头PUBLISH;A3 01
: 剩余长度,163字节;00 34
:可变报头,52字节;2F 73 79 73 2F 6B 31 70 35 31 66 6F 56 58 59 56 2F 6D 73 65 65 64 69 6E 67 2F 74 68 69 6E 67 2F 73 65 72 76 69 63 65 2F 70 72 6F 70 65 72 74 79 2F 73 65 74
:/sys/k1p51foVXYV/mseeding/thing/service/property/set
7B 22 6D 65 74 68 6F 64 22 3A 22 74 68 69 6E 67 2E 73 65 72 76 69 63 65 2E 70 72 6F 70 65 72 74 79 2E 73 65 74 22 2C 22 69 64 22 3A 22 31 33 39 32 39 37 37 39 38 39 22 2C 22 70 61 72 61 6D 73 22 3A 7B 22 43 75 72 72 65 6E 74 54 65 6D 70 65 72 61 74 75 72 65 22 3A 32 33 7D 2C 22 76 65 72 73 69 6F 6E 22 3A 22 31 2E 30 2E 30 22 7D
:{"method":"thing.service.property.set","id":"1392977989","params":{"CurrentTemperature":23},"version":"1.0.0"}
-
服务器发送给客户端
解析过程:
{"method":"thing.event.property.post","id":"123456789","params":{"CurrentTemperature":37},"version":"1.0.0"}
7B 22 6D 65 74 68 6F 64 22 3A 22 74 68 69 6E 67 2E 65 76 65 6E 74 2E 70 72 6F 70 65 72 74 79 2E 70 6F 73 74 22 2C 22 69 64 22 3A 22 31 32 33 34 35 36 37 38 39 22 2C 22 70 61 72 61 6D 73 22 3A 7B 22 43 75 72 72 65 6E 74 54 65 6D 70 65 72 61 74 75 72 65 22 3A 33 37 7D 2C 22 76 65 72 73 69 6F 6E 22 3A 22 31 2E 30 2E 30 22 7D (108字节)
固定报头PUBLISH:
30
;剩余长度,163字节:
A1 01
;Topic的字节数51 + Topic:
/sys/k1p51foVXYV/mseeding/thing/event/property/post
00 33 2F 73 79 73 2F 6B 31 70 35 31 66 6F 56 58 59 56 2F 6D 73 65 65 64 69 6E 67 2F 74 68 69 6E 67 2F 65 76 65 6E 74 2F 70 72 6F 70 65 72 74 79 2F 70 6F 73 74
最终报文:
30 A1 01 00 33 2F 73 79 73 2F 6B 31 70 35 31 66 6F 56 58 59 56 2F 6D 73 65 65 64 69 6E 67 2F 74 68 69 6E 67 2F 65 76 65 6E 74 2F 70 72 6F 70 65 72 74 79 2F 70 6F 73 74 7B 22 6D 65 74 68 6F 64 22 3A 22 74 68 69 6E 67 2E 65 76 65 6E 74 2E 70 72 6F 70 65 72 74 79 2E 70 6F 73 74 22 2C 22 69 64 22 3A 22 31 32 33 34 35 36 37 38 39 22 2C 22 70 61 72 61 6D 73 22 3A 7B 22 43 75 72 72 65 6E 74 54 65 6D 70 65 72 61 74 75 72 65 22 3A 33 37 7D 2C 22 76 65 72 73 69 6F 6E 22 3A 22 31 2E 30 2E 30 22 7D
-
串口调试助手和阿里云平台结果:
-
阿里云显示: 设备->查看->物模型数据->温度->参看数据
8.6. 发布确认
MQTT协议规定,PUBACK 是对 Qos=1时的 PUBLISH的发布消息确认。也就是说Qos=1 才有 PUBACK,但阿里云服务器仅支持 Qos=0的POST,所以压根就不会有PUBACK。而用户强行发送Qos=1的POST,会返回错误:40 02 7B 22
,并且不会对本帧的内容进行处理,开关状态不会得到更新。
9. 串口调试助手+ESP8266接入阿里云
-
wifi模块固件烧录
使用正点原子
ATK-ESP-01
模块,进行固件烧录,固件来源于安信可官网安信可官网:
https://docs.ai-thinker.com/
-
选择Wifi模组系列 -> 2. ESP8266系列 -> 3. 各类AT固件 -> 4. 选择第⑦个进行固件下载 -> 5. 开发工具 -> 选择第二个烧录软件
-
把下载的文件加压,找到bin文件,手动输入0x00进行烧录,注意串口选择,烧录完成有提示
-
-
串口发送指令
-
发送
AT+RESTORE
重新启动ESP模块 -
发送
AT+CWMODE=1
配置WIFI模式
-
发送
AT+CIPSNTPCFG=1,8,"cn.ntp.org.cn","ntp.sjtu.edu.cn"
进行服务器设置
-
发送
AT+CWJAP="vivo","Li.12345678"
(输入自己的wifi和密码) 连接wifi
-
发送
AT+MQTTUSERCFG=0,1,"NULL","mseeding&k1p51foVXYV","58445ef1b7fca73e26015e0777d63490a03c18312ab91eae1d65763f1450afe4",0,0,""
设置MQTT属性
登录阿里云,设备界面,查看MQTT连接参数
6. 发送AT+MQTTCLIENTID=0,"k1p51foVXYV.mseeding|securemode=2\,signmethod=hmacsha256\,timestamp=1725277665467|"
(注意要在","
前加上"\"
)设置MQTT ID
-
发送
AT+MQTTCONN=0,"iot-06z00c3rc5nqns1.mqtt.iothub.aliyuncs.com",1883,1
发送MQTT域名,域名获取
-
发送
AT+MQTTSUB=0,"/k1p51foVXYV/mseeding/user/get",1
订阅主题
阿里云界面:
阿里云发送给串口助手的数据:
9. 发送AT+MQTTPUB=0,"/sys/k1p51foVXYV/mseeding/thing/event/property/post","{\"params\":{\"LEDSwitch\":1}}",1,0
发布主题
阿里云显示:
-
-
串口指令集合
c//1. 重新启动ESP模块 AT+RESTORE //2. 配置WIFI模式 AT+CWMODE=1 //3. 进行服务器设置 AT+CIPSNTPCFG=1,8,"cn.ntp.org.cn","ntp.sjtu.edu.cn" //4. 连接wifi AT+CWJAP="vivo","Li.12345678" //5. 设置MQTT属性 AT+MQTTUSERCFG=0,1,"NULL","mseeding&k1p51foVXYV","58445ef1b7fca73e26015e0777d63490a03c18312ab91eae1d65763f1450afe4",0,0,"" //6. 设置MQTT ID AT+MQTTCLIENTID=0,"k1p51foVXYV.mseeding|securemode=2\,signmethod=hmacsha256\,timestamp=1725277665467|" //7. 发送MQTT域名,域名获取 AT+MQTTCONN=0,"iot-06z00c3rc5nqns1.mqtt.iothub.aliyuncs.com",1883,1 //8. 订阅主题 AT+MQTTSUB=0,"/k1p51foVXYV/mseeding/user/get",1 //9. 发布主题 AT+MQTTPUB=0,"/sys/k1p51foVXYV/mseeding/thing/event/property/post","{\"params\":{\"LEDSwitch\":1}}",1,0
10. 使用正点原子战舰F103连接阿里云
-
阿里云相关配置
c#define PRODUCTKEY "k1p51foVXYV" //产品ID #define PRODUCTKEY_LEN strlen(PRODUCTKEY) //产品ID长度 #define DEVICENAME "mseeding" //设备名 #define DEVICENAME_LEN strlen(DEVICENAME) //设备名长度 #define DEVICESECRE "31c4033811c5772f3ae38bf8f1ba8e1caf0ccd79" //设备秘钥 #define DEVICESECRE_LEN strlen(DEVICESECRE) //设备秘钥长度 #define S_TOPIC_NAME "/sys/k1p51foVXYV/mseeding/thing/service/property/set" //需要订阅的主题 #define P_TOPIC_NAME "/sys/k1p51foVXYV/mseeding/thing/event/property/post" //需要发布的主题 void AliIoT_Parameter_Init(void) { char temp[128]; memset(ClientID,128,0); sprintf(ClientID,"%s|securemode=3,signmethod=hmacsha1|",DEVICENAME); ClientID_len = strlen(ClientID); memset(Username,128,0); sprintf(Username,"%s&%s",DEVICENAME,PRODUCTKEY); Username_len = strlen(Username); memset(temp,128,0); sprintf(temp,"clientId%sdeviceName%sproductKey%s",DEVICENAME,DEVICENAME,PRODUCTKEY); utils_hmac_sha1(temp,strlen(temp),Passward,DEVICESECRE,DEVICESECRE_LEN); Passward_len = strlen(Passward); memset(ServerIP,128,0); sprintf(ServerIP,"%s.iot-as-mqtt.cn-shanghai.aliyuncs.com",PRODUCTKEY); ServerPort = 1883; u1_printf("服 务 器:%s:%d\r\n",ServerIP,ServerPort); u1_printf("客户端ID:%s\r\n",ClientID); u1_printf("用 户 名:%s\r\n",Username); u1_printf("密 码:%s\r\n",Passward); }
-
发布消息
c/*-------------------------------------------------------------*/ /* 处理发送缓冲区数据 */ /*-------------------------------------------------------------*/ if(MQTT_TxDataOutPtr != MQTT_TxDataInPtr) //if成立的话,说明发送缓冲区有数据了 { //3种情况可进入if //第1种:0x10 连接报文 //第2种:0x82 订阅报文,且ConnectPack_flag置位,表示连接报文成功 //第3种:SubcribePack_flag置位,说明连接和订阅均成功,其他报文可发 if((MQTT_TxDataOutPtr[2]==0x10)||((MQTT_TxDataOutPtr[2]==0x82)&&(ConnectPack_flag==1))||(SubcribePack_flag==1)) { u1_printf("发送数据:0x%x\r\n",MQTT_TxDataOutPtr[2]); //串口提示信息 MQTT_TxData(MQTT_TxDataOutPtr); //发送数据 MQTT_TxDataOutPtr += TBUFF_UNIT; //指针下移 if(MQTT_TxDataOutPtr==MQTT_TxDataEndPtr) //如果指针到缓冲区尾部了 MQTT_TxDataOutPtr = MQTT_TxDataBuf[0]; //指针归位到缓冲区开头 } }//处理发送缓冲区数据的else if分支结尾 void set_temp_humid(void) { char temp[256]; DHT11_Read_Data(&humiH,&humiL,&tempH,&tempL); sprintf(temp,"{\"method\":\"thing.event.property.post\",\"id\":\"1234556789\",\"params\":{\"humid\":%d,\"temp\":%d},\"version\":\"1.0.0\"}",(humiH*10+humiL),(tempH*10+tempL)); //构建回复温湿度数据 MQTT_PublishQs0(P_TOPIC_NAME,temp,strlen(temp)); //添加数据,发布给服务器 //delay_ms(100); } void MQTT_PublishQs0(char *topic, char *data, int data_len) { int temp,Remaining_len; Fixed_len = 1; //固定报头长度暂时先等于:1字节 Variable_len = 2 + strlen(topic); //可变报头长度:2字节(topic长度)+ topic字符串的长度 Payload_len = data_len; //有效负荷长度:就是data_len Remaining_len = Variable_len + Payload_len; //剩余长度=可变报头长度+负载长度 temp_buff[0]=0x30; //固定报头第1个字节 :固定0x30 do{ //循环处理固定报头中的剩余长度字节,字节量根据剩余字节的真实长度变化 temp = Remaining_len%128; //剩余长度取余128 Remaining_len = Remaining_len/128; //剩余长度取整128 if(Remaining_len>0) temp |= 0x80; //按协议要求位7置位 temp_buff[Fixed_len] = temp; //剩余长度字节记录一个数据 Fixed_len++; //固定报头总长度+1 }while(Remaining_len>0); //如果Remaining_len>0的话,再次进入循环 temp_buff[Fixed_len+0]=strlen(topic)/256; //可变报头第1个字节 :topic长度高字节 temp_buff[Fixed_len+1]=strlen(topic)%256; //可变报头第2个字节 :topic长度低字节 memcpy(&temp_buff[Fixed_len+2],topic,strlen(topic)); //可变报头第3个字节开始 :拷贝topic字符串 memcpy(&temp_buff[Fixed_len+2+strlen(topic)],data,data_len); //有效负荷:拷贝data数据 TxDataBuf_Deal(temp_buff, Fixed_len + Variable_len + Payload_len); //加入发送数据缓冲区 }
-
订阅消息
cif(MQTT_RxDataOutPtr != MQTT_RxDataInPtr) //if成立的话,说明接收缓冲区有数据了 { u1_printf("接收到数据:"); /* 处理CONNACK报文 */ //if判断,如果第一个字节是0x20,表示收到的是CONNACK报文 //接着我们要判断第4个字节,看看CONNECT报文是否成功 if(MQTT_RxDataOutPtr[2]==0x20) { switch(MQTT_RxDataOutPtr[5]) { case 0x00 : u1_printf("CONNECT报文成功\r\n"); //串口输出信息 ConnectPack_flag = 1; //CONNECT报文成功,订阅报文可发 break; //跳出分支case 0x00 case 0x01 : u1_printf("连接已拒绝,不支持的协议版本,准备重启\r\n"); //串口输出信息 Connect_flag = 0; //Connect_flag置零,重启连接 break; //跳出分支case 0x01 case 0x02 : u1_printf("连接已拒绝,不合格的客户端标识符,准备重启\r\n"); //串口输出信息 Connect_flag = 0; //Connect_flag置零,重启连接 break; //跳出分支case 0x02 case 0x03 : u1_printf("连接已拒绝,服务端不可用,准备重启\r\n"); //串口输出信息 Connect_flag = 0; //Connect_flag置零,重启连接 break; //跳出分支case 0x03 case 0x04 : u1_printf("连接已拒绝,无效的用户名或密码,准备重启\r\n"); //串口输出信息 Connect_flag = 0; //Connect_flag置零,重启连接 break; //跳出分支case 0x04 case 0x05 : u1_printf("连接已拒绝,未授权,准备重启\r\n"); //串口输出信息 Connect_flag = 0; //Connect_flag置零,重启连接 break; //跳出分支case 0x05 default : u1_printf("连接已拒绝,未知状态,准备重启\r\n"); //串口输出信息 Connect_flag = 0; //Connect_flag置零,重启连接 break; //跳出分支case default } } //if判断,第一个字节是0x90,表示收到的是SUBACK报文 //接着我们要判断订阅回复,看看是不是成功 else if(MQTT_RxDataOutPtr[2]==0x90) { switch(MQTT_RxDataOutPtr[6]) { case 0x00 : case 0x01 : u1_printf("订阅成功\r\n"); //串口输出信息 SubcribePack_flag = 1; //SubcribePack_flag置1,表示订阅报文成功,其他报文可发送 Ping_flag = 0; //Ping_flag清零 TIM3_ENABLE_30S(); //启动30s的PING定时器 set_temp_humid(); //启动数据传输 TIM2_ENABLE_6S(); //判断开关状态,并发布给服务器 定时器 break; //跳出分支 default : u1_printf("订阅失败,准备重启\r\n"); //串口输出信息 Connect_flag = 0; //Connect_flag置零,重启连接 break; //跳出分支 } } //if判断,第一个字节是0xD0,表示收到的是PINGRESP报文 else if(MQTT_RxDataOutPtr[2]==0xD0) { u1_printf("PING报文回复\r\n"); //串口输出信息 if(Ping_flag==1) { //如果Ping_flag=1,表示第一次发送 Ping_flag = 0; //要清除Ping_flag标志 } else if(Ping_flag>1) { //如果Ping_flag>1,表示是多次发送了,而且是2s间隔的快速发送 Ping_flag = 0; //要清除Ping_flag标志 TIM3_ENABLE_30S(); //PING定时器重回30s的时间 } } //if判断,如果第一个字节是0x30,表示收到的是服务器发来的推送数据 //我们要提取控制命令 else if((MQTT_RxDataOutPtr[2]==0x30)) { u1_printf("服务器等级0推送\r\n"); //串口输出信息 MQTT_DealPushdata_Qs0(MQTT_RxDataOutPtr); //处理等级0推送数据 } MQTT_RxDataOutPtr +=RBUFF_UNIT; //指针下移 if(MQTT_RxDataOutPtr==MQTT_RxDataEndPtr) //如果指针到缓冲区尾部了 MQTT_RxDataOutPtr = MQTT_RxDataBuf[0]; //指针归位到缓冲区开头 }//处理接收缓冲区数据的else if分支结尾
-
接收控制命令
cif(MQTT_CMDOutPtr != MQTT_CMDInPtr) { //if成立的话,说明命令缓冲区有数据了 u1_printf("命令:%s\r\n",&MQTT_CMDOutPtr[2]); //串口输出信息 if(strstr((char *)MQTT_CMDOutPtr+2,"\"params\":{\"LED\":1}")) //云平台下发命令控制LED关 { LED_OFF; } else if(strstr((char *)MQTT_CMDOutPtr+2,"\"params\":{\"LED\":0}")) //云平台下发命令控制LED开 { LED_ON; } MQTT_CMDOutPtr += CBUFF_UNIT; //指针下移 if(MQTT_CMDOutPtr==MQTT_CMDEndPtr) //如果指针到缓冲区尾部了 MQTT_CMDOutPtr = MQTT_CMDBuf[0]; //指针归位到缓冲区开头 }