嵌入式物联网协议--MQTT

本文使用MQTT-3.1.1版本

 

目录

一.MQTT简介

1.什么是MQTT

2.MQTT本质

3.MQTT报文类型

4.MQTT Qos质量

二.14个报文详解

1.CONNECT报文(1 C->S  固定报头+可变报头+负载)

1)固定报头:

2)可变报头:

3)有效载荷(clientID长度+clientID数据+username长度+username数据+password长度+password数据)

2.CONNACK报文(2  S->C 固定报头+可变报头)

1)固定报头(0x20+剩余长度(02))

2)可变报头(连接确认标志+连接返回码)

3.DISCONNECT报文(14 C->S 固定报头)

1).固定报头(E0 00)

4.PING报文(12 C->S 固定报头)

1)固定报头(C0 00)

5.PINGRSP报文(13 S->C 固定报头)

1)固定报头(D0 00)

6.SUBSCRIBE报文(8 C->S 固定报头+可变报头+负载)

1)固定报头(0x82+剩余长度)

2)可变报头(00 0A)

3)负载(Topic)

7.SUBACK报文(9 S->C 固定报头+可变报头+负载)

1)固定报头(0x90 + 剩余长度)

2)可变报头(00 0A)

3)负载

8.UNSUBSCRIBE报文(10 C->S 固定报头+可变报头+负载)

1)固定报头(0xA2 +剩余长度)

2)可变报头(00 0A)

3)负载

9.UNSUBACK报文(11 S->C 固定报头+可变报头)

1)固定报头(B0 02)

2)可变报头(00 0A)

10.PUBLISH报文(3  S<=>C 固定报头+可变报头+负载)

2)可变报头

3)负载

4)响应

11.PUBACK报文(4  S<=>C 固定报头+可变报头  QoS1)

1)固定报头(40 02)

2)可变报头

12.PUBREC报文(5  S<=>C 固定报头+可变报头  QoS2 第一步)

1)固定报头(50 02 )

2)可变报头

13.PUBREC报文(6  S<=>C 固定报头+可变报头  QoS2 第二步)

1)固定报头( 62 02)

2)可变报头

14.PUBCOMP报文(7  S<=>C 固定报头+可变报头  QoS2 第三步)

1)固定报头( 70 02)

​2)可变报头


一.MQTT简介

1.什么是MQTT

MQTT 是一个客户端服务端架构的发布/订阅模式的消息传输协议。 它的设计思想是轻巧、 开放、简单、 规范, 因此易于实现。这些特点使得它对很多场景来说都是很好的选择, 包括受限的环境如机器与机器的通信( M2M)以及物联网环境( IoT) , 这些场景要求很小的代码封装或者网络带宽非常昂贵。本协议运行在 TCP/IP,或其它提供了有序、可靠、 双向连接的网络连接上。 它有以下特点:

  1.  使用发布/订阅消息模式,提供了一对多的消息分发和应用之间的解耦。
  2.  消息传输不需要知道负载内容。
  3.  提供三种等级的服务质量: .
  4.  “最多一次”,尽操作环境所能提供的最大努力分发消息。 消息可能会丢失。 例如, 这个等级可用于环境传感器数据,单次的数据丢失没关系, 因为不久之后会再次发送。
  5.  “至少一次”,保证消息可以到达, 但是可能会重复。
  6.  “仅一次”, 保证消息只到达一次。 例如, 这个等级可用在一个计费系统中, 这里如果消息重复或丢失会导致不正确的收费。
  7.  很小的传输消耗和协议数据交换,最大限度减少网络流量
  8.  异常连接断开发生时, 能通知到相关各方。

2.MQTT本质

MQTT是基于TOPIC的订阅与发布,与谁通信就先订阅谁(并不是订阅服务器,服务器是将消息进行转发),服务器会进行推送发布消息 

嵌入式物联网协议--MQTT_第1张图片

3.MQTT报文类型

嵌入式物联网协议--MQTT_第2张图片

4.MQTT Qos质量

嵌入式物联网协议--MQTT_第3张图片

QoS 是消息的发送方( Sender)和接受方( Receiver)之间达成的一个协议:
QoS 0 代表, Sender 发送的一条消息, Receiver 最多能收到一次,也就是说 Sender 尽力向 Receiver 发送消息,如果发送失败,也就算了;
QoS 1 代表, Sender 发送的一条消息, Receiver 至少能收到一次,也就是说 Sender 向 Receiver 发送消息,如果发送失败,会继续重试,直到 Receiver 收到消息为止,但是因为重传的原因, Receiver 有可能会收到重复的消息;
QoS 2 代表, Sender 发送的一条消息, Receiver 确保能收到而且只收到一次,也就是说 Sender 尽力向Receiver 发送消息,如果发送失败,会继续重试,直到 Receiver 收到消息为止,同时保证 Receiver 不会因为消息重传而收到重复的消息。
注:当前主流服务器应用MQTT采用的协议等级大都是0,1。 Qos2太消耗带宽。

 

二.14个报文详解

1.CONNECT报文(1 C->S  固定报头+可变报头+负载)

1)固定报头:

嵌入式物联网协议--MQTT_第4张图片

  • 字节1:10
  • 字节2: 剩余长度值。对于小于128的值采用单字节编码。更大的值按下面的方式处理。低 7 位有效位用于编码数据, 最高有效位用于指示是否有更多的字节。 因此每个字节可以编码 128 个数值和一个延续位( continuation bit)。剩余长度字段最大 4 个字节。

嵌入式物联网协议--MQTT_第5张图片

MQTT 为了传输数据量减少、避免浪费数据流量,使用了变长编码方式;变长编码规则如下:

嵌入式物联网协议--MQTT_第6张图片

 

假设数据长度:200,第一个字节最大值为 127,对于 200 是一定放不下的,根据上表可知需要两个字节;发送数据结构如下

嵌入式物联网协议--MQTT_第7张图片

当超过128字节的时候,第一个字节表示128的整数倍,第二个字节因为使用了,所以最高位填1.剩余长度转换为16进制后7位数加上最高位1,最终长度是:0x c8 01(代表1个128)

2)可变报头:

CONNECT 报文的可变报头按下列次序包含四个字段:协议名( Protocol Name),协议级别rotocolLevel) ,连接标志( Connect Flags)和保持连接( Keep Alive)。

  • 协议名
    嵌入式物联网协议--MQTT_第8张图片
  • 协议级别

客户端用 8 位的无符号值表示协议的修订版本。对于 3.1.1 版协议,协议级别字段的值是 4(0x04)。 如果发现不支持的协议级别, 服务端必须给发送一个返回码为 0x01(不支持的协议级别) 的 CONNACK 报文响应CONNECT 报文, 然后断开客户端的连接 [MQTT-3.1.2-2]。

  • 连接标志

嵌入式物联网协议--MQTT_第9张图片

  • 保活连接

保持连接( Keep Alive) (保活包) 是一个以秒为单位的时间间隔,表示为一个 16 位的字,它是指在客户端传输完成一个控制报文的时刻到发送下一个报文的时刻, 两者之间允许空闲的最大时间间隔。 客户端负责保证控制报文发送的时间间隔不超过保持连接的值。 如果没有任何其它的控制报文可以发送, 客户端必须发送一个 PINGREQ 报文。

嵌入式物联网协议--MQTT_第10张图片

3)有效载荷(clientID长度+clientID数据+username长度+username数据+password长度+password数据)

DEVICENAME:LED_CONTROL                      

PRODUCTKEY:a1U1OR0WIlM

  • clientID:LED_CONTROL|securemode=3,signmethod=hmacsha1|
  • 16进制clientID(45):00 2D 4C 45 44 5F 43 4F 4E 54 52 4F 4C 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
  • username:LED_CONTROL&a1U1OR0WIlM
  • 16进制username(23):00 17 4C 45 44 5F 43 4F 4E 54 52 4F 4C 26 61 31 55 31 4F 52 30 57 49 6C 4D
  • password

哈希加密之前:clientIdLED_CONTROLdeviceNameLED_CONTROLproductKeya1U1OR0WIlM    加密网址

设备秘钥:XvCiVzzpEooo4mj0EqwJRSUsOihnevri

哈希加密之后:fe205fdd687b474ea68da04a76ffe852f1f5ec41

16进制password(40):00 28 66 65 32 30 35 66 64 64 36 38 37 62 34 37 34 65 61 36 38 64 61 30 34 61 37 36 66 66 65 38 35 32 66 31 66 35 65 63 34 31

 

 

发送connect报文: 10 7C 00 04 4D 51 54 54 04 C2 00 64 00 2D 4C 45 44 5F 43 4F 4E 54 52 4F 4C 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 17 4C 45 44 5F 43 4F 4E 54 52 4F 4C 26 61 31 55 31 4F 52 30 57 49 6C 4D 00 28 66 65 32 30 35 66 64 64 36 38 37 62 34 37 34 65 61 36 38 64 61 30 34 61 37 36 66 66 65 38 35 32 66 31 66 35 65 63 34 31

红:固定字节

黄:剩余长度

绿:可变报头10字节数据

黑:有效载荷

2.CONNACK报文(2  S->C 固定报头+可变报头)

服务器返回:0x20 02 00 00

嵌入式物联网协议--MQTT_第11张图片

1)固定报头(0x20+剩余长度(02))

嵌入式物联网协议--MQTT_第12张图片

2)可变报头(连接确认标志+连接返回码)

嵌入式物联网协议--MQTT_第13张图片

  • 字节1:连接确认标志, 位 7-1 是保留位且必须设置为 0。第 0 (SP)位 是当前会话( Session Present) 标志
  • 字节2:连接返回码
    嵌入式物联网协议--MQTT_第14张图片

 

3.DISCONNECT报文(14 C->S 固定报头)

1).固定报头(E0 00)

嵌入式物联网协议--MQTT_第15张图片

  • 字节1:0xE0
  • 字节2:0x00

服务器无返回

4.PING报文(12 C->S 固定报头)

1)固定报头(C0 00)

客户端发送 PINGREQ 报文给服务端的。用于:
1. 在没有任何其它控制报文从客户端发给服务的时,告知服务端客户端还活着。
2. 请求服务端发送 响应确认它还活着。
3. 使用网络以确认网络连接没有断开。
嵌入式物联网协议--MQTT_第16张图片

  • 字节1:0xC0
  • 字节2:0x00  

5.PINGRSP报文(13 S->C 固定报头)

1)固定报头(D0 00)

嵌入式物联网协议--MQTT_第17张图片

  • 字节1:D0
  • 字节2:00

6.SUBSCRIBE报文(8 C->S 固定报头+可变报头+负载)

客户端向服务端发送 SUBSCRIBE 报文用于创建一个或多个订阅。 每个订阅注册客户端关心的一个或多个主题。 为了将应用消息转发给与那些订阅匹配的主题, 服务端发送 PUBLISH 报文给客户端。 SUBSCRIBE报文也(为每个订阅) 指定了最大的 QoS 等级, 服务端根据这个发送应用消息给客户端。(订阅报文就相当于关注up主)

1)固定报头(0x82+剩余长度)

嵌入式物联网协议--MQTT_第18张图片

2)可变报头(00 0A)

嵌入式物联网协议--MQTT_第19张图片

3)负载(Topic)

SUBSCRIBE 报文的有效载荷包含了一个主题过滤器列表, 它们表示客户端想要订阅的主题。每一个过滤器后面跟着一个字节, 这个字节被叫做服务质量要求( Requested QoS) 。它给出了服务端向客户端发送应用消息所允许的最大 QoS 等级。

主题过滤器就是订阅谁,位置在阿里云设备 Topic 列表中→物模型通信 Topic 列表:

嵌入式物联网协议--MQTT_第20张图片

注:MQTT-3.1.1没有用到 服务质量要求( Requested QoS) 字节的高六位。

 

固定报头:82 xx

可变报头:00 0A

TOPIC:/sys/a1U1OR0WIlM/LED_CONTROL/thing/service/property/set

TOPIC(二进制 55):2F 73 79 73 2F 61 31 55 31 4F 52 30 57 49 6C 4D 2F 4C 45 44 5F 43 4F 4E 54 52 4F 4C 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 37 2F 73 79 73 2F 61 31 55 31 4F 52 30 57 49 6C 4D 2F 4C 45 44 5F 43 4F 4E 54 52 4F 4C 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 

Qos:00或者01或者02

发送subscribe报文:82 3C 00 0A 00 37 2F 73 79 73 2F 61 31 55 31 4F 52 30 57 49 6C 4D 2F 4C 45 44 5F 43 4F 4E 54 52 4F 4C 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

7.SUBACK报文(9 S->C 固定报头+可变报头+负载)

SUBACK 报文包含一个返回码清单, 它们指定了 SUBSCRIBE 请求的每个订阅被授予的最大 QoS 等级

1)固定报头(0x90 + 剩余长度)

嵌入式物联网协议--MQTT_第21张图片

2)可变报头(00 0A)

描述了订阅的可变报头的标识符,以上述订阅报文可变报头为例,就是00 0A

3)负载

有效载荷包含一个返回码清单。 每个返回码对应等待确认的 SUBSCRIBE 报文中的一个主题过滤器。返回码的顺序必须和 SUBSCRIBE 报文中主题过滤器的顺序相同。

嵌入式物联网协议--MQTT_第22张图片

8.UNSUBSCRIBE报文(10 C->S 固定报头+可变报头+负载)

1)固定报头(0xA2 +剩余长度)

嵌入式物联网协议--MQTT_第23张图片

剩余长度:等于可变报头的长度加上有效载荷的长度

2)可变报头(00 0A)

是订阅的报文标识符

嵌入式物联网协议--MQTT_第24张图片

3)负载

UNSUBSCRIBE 报文的有效载荷包含客户端想要取消订阅的主题过滤器列表。 UNSUBSCRIBE 报文中的主题过滤器必须是连续打包的。UNSUBSCRIBE 报文的有效载荷必须至少包含一个消息过滤器。

固定报头:A2 xx
可变报头:00 0A

TOPIC:/sys/a1U1OR0WIlM/LED_CONTROL/thing/service/property/set
TOPIC(二进制 55):2F 73 79 73 2F 61 31 55 31 4F 52 30 57 49 6C 4D 2F 4C 45 44 5F 43 4F 4E 54 52 4F 4C 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 
UNsubscribe:A2 3B 00 0A 00 37 2F 73 79 73 2F 61 31 55 31 4F 52 30 57 49 6C 4D 2F 4C 45 44 5F 43 4F 4E 54 52 4F 4C 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 

9.UNSUBACK报文(11 S->C 固定报头+可变报头)

1)固定报头(B0 02)

2)可变报头(00 0A)

       UNSUBSCRIBE的 报文标识符

10.PUBLISH报文(3  S<=>C 固定报头+可变报头+负载)

PUBLISH 控制报文是指从客户端向服务端或者服务端向客户端传输一个应用消息。

1)固定报头

嵌入式物联网协议--MQTT_第25张图片

位3:如果 DUP 标志被设置为 0, 表示这是客户端或服务端第一次请求发送这个 PUBLISH 报文。 如果 DUP标志被设置为 1,表示这可能是一个早期报文请求的重发。客户端或服务端请求重发一个 PUBLISH 报文时, 必须将 DUP 标志设置为 1。

但是:对于 QoS0 的消息, DUP 标志必须设置为 0

位0:如果客户端发给服务端的 PUBLISH 报文的保留标志位 0,服务端不能存储这个消息也不能移除或替换任何现存的保留消息。

综上,固定报头:0x30 ??

2)可变报头

嵌入式物联网协议--MQTT_第26张图片

主题名:主题名( Topic Name) 用于识别有效载荷数据应该被发布到哪一个信息通道---------------->  /sys/a1U1OR0WIlM/LED_CONTROL/thing/event/property/post
报文标识符:只有当 QoS 等级是 1 或 2 时,报文标识符( Packet Identifier) 字段才能出现在 PUBLISH 报文中。

嵌入式物联网协议--MQTT_第27张图片

3)负载

有效载荷包含将被发布的应用消息。 数据的内容和格式是应用特定的。 有效载荷的长度这样计算: 用固定报头中的剩余长度字段的值减去可变报头的长度。 包含零长度有效载荷的 PUBLISH 报文是合法的。

有效载荷是一个json数据格式,对于event事件,如下:

嵌入式物联网协议--MQTT_第28张图片

"events": [
{
"identifier": "post", “属性”:”值”
"name": "post",
"type": "info",
"required": true,
"desc": "属性上报",
"method": "thing.event.property.post",
"outputData": [
{
"identifier": "LightSwitch",
"name": "主灯开关",
"dataType": {
"type": "bool",
"specs": {
"0": "关闭",
"1": "开启"
}
}
}
]
}

因此:有效载荷发布的数据位:{"method":"thing.event.property.post","id":"292200613","params":{"LightSwitch":0},"version":"1.0.0"}

 

 

实现:

固定报头:30 xx
可变报头:/sys/a1U1OR0WIlM/LED_CONTROL/thing/event/property/post
可变报头(二进制 54):00 36 2F 73 79 73 2F 61 31 55 31 4F 52 30 57 49 6C 4D 2F 4C 45 44 5F 43 4F 4E 54 52 4F 4C 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 
{"method":"thing.event.property.post","id":"292200613","params":{"LightSwitch":0},"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 32 39 32 32 30 30 36 31 33 22 2C 22 70 61 72 61 6D 73 22 3A 7B 22 4C 69 67 68 74 53 77 69 74 63 68 22 3A 31 7D 2C 22 76 65 72 73 69 6F 6E 22 3A 22 31 2E 30 2E 30 22 7D  


PUBLISH(共159):30 9C 01 00 36 2F 73 79 73 2F 61 31 55 31 4F 52 30 57 49 6C 4D 2F 4C 45 44 5F 43 4F 4E 54 52 4F 4C 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 32 39 32 32 30 30 36 31 33 22 2C 22 70 61 72 61 6D 73 22 3A 7B 22 4C 69 67 68 74 53 77 69 74 63 68 22 3A 31 7D 2C 22 76 65 72 73 69 6F 6E 22 3A 22 31 2E 30 2E 30 22 7D

4)响应

嵌入式物联网协议--MQTT_第29张图片嵌入式物联网协议--MQTT_第30张图片

11.PUBACK报文(4  S<=>C 固定报头+可变报头  QoS1)

PUBACK 报文是对 QoS 1 等级的 PUBLISH 报文的响应。

1)固定报头(40 02)

嵌入式物联网协议--MQTT_第31张图片

2)可变报头

嵌入式物联网协议--MQTT_第32张图片

12.PUBREC报文(5  S<=>C 固定报头+可变报头  QoS2 第一步)

发布消息收到

PUBREC 报文是对 QoS 等级 2 的 PUBLISH 报文的响应。它是 QoS 2 等级协议交换的第二个报文

1)固定报头(50 02 )

嵌入式物联网协议--MQTT_第33张图片

2)可变报头

嵌入式物联网协议--MQTT_第34张图片

13.PUBREC报文(6  S<=>C 固定报头+可变报头  QoS2 第二步)

发布消息释放

1)固定报头( 62 02)

嵌入式物联网协议--MQTT_第35张图片

2)可变报头

嵌入式物联网协议--MQTT_第36张图片

14.PUBCOMP报文(7  S<=>C 固定报头+可变报头  QoS2 第三步)

发布消息完成

PUBCOMP 报文是对 PUBREL 报文的响应。 它是 QoS 2 等级协议交换的第四个也是最后一个报文

1)固定报头( 70 02)

嵌入式物联网协议--MQTT_第37张图片
2)可变报头

嵌入式物联网协议--MQTT_第38张图片

#include "wifi.h"
#include "delay.h"
#include "string.h"
#include "usart2.h"
#include "stdio.h"
#include "usart.h"
#include "mqtt.h"
#include "utils_hmac.h"
#include "timer3.h"
#include "timer1.h"
#include "timer4.h"
#include "mqtt_data_deal.h"

char ConnectPack_flag = 0;
char SubcribePack_flag = 0;
char Ping_flag = 0;
/*********************************临时缓冲区***************************/
unsigned char temp_buff[300];//14个报文存储缓冲区

unsigned char MQTT_TxData_buff[5][300];//发送数据缓冲区
unsigned char *MQTT_TxData_Input; //发送输入缓冲区
unsigned char *MQTT_TxData_Output;//发送输出缓冲区
unsigned char *MQTT_TxData_End;  //发送缓冲区尾

unsigned char MQTT_RxData_buff[5][300];//接收数据缓冲区
unsigned char *MQTT_RxData_Input; //接收输入缓冲区
unsigned char *MQTT_RxData_Output;//接收输出缓冲区
unsigned char *MQTT_RxData_End;  //接收缓冲区尾

unsigned char MQTT_CMD_buff[5][300];//接收数据缓冲区
unsigned char *MQTT_CMD_Input; //接收输入缓冲区
unsigned char *MQTT_CMD_Output;//接收输出缓冲区
unsigned char *MQTT_CMD_End;  //接收缓冲区尾

/*********************************阿里云参数***************************/
char ServerIP[128];
int Port;
char clientID[128];
char username[128];
char password[128];

int clientID_len;
int username_len;
int password_len;

/****************************************************************************
函数名称:void MQTT_Buff_Init(void)
函数参数:无
函数返回值:无
描述: 初始化接收,发送,命令数据的 缓冲区 以及各状态参数
@author:冷瑾瑜
****************************************************************************/
void MQTT_Buff_Init(void)
{
	memset(WIFI_RXBUFF,0,sizeof(WIFI_RXBUFF));
	USART2_RX_LENG = 0;
	//输入输出缓冲区指向地址二维数组第一行
	MQTT_TxData_Input = MQTT_TxData_buff[0];
	MQTT_TxData_Output= MQTT_TxData_Input ;
	MQTT_TxData_End = MQTT_TxData_buff[4];
	
	MQTT_RxData_Input = MQTT_RxData_buff[0];
	MQTT_RxData_Output = MQTT_RxData_Input;
	MQTT_RxData_End = MQTT_RxData_buff[4];
	
	MQTT_CMD_Input = MQTT_CMD_buff[0];
	MQTT_CMD_Output = MQTT_CMD_Input;
	MQTT_CMD_End = MQTT_CMD_buff[4];
	
	MQTT_ConectPack();
	MQTT_Subscribe(0x00); //发送Qos0的报文
 
	
	Ping_flag = ConnectPack_flag = SubcribePack_flag = 0;   
}

/****************************************************************************
函数名称:void AliIoT_Parameter_Init(void)
函数参数:无
函数返回值:无
描述:阿里云初始化参数
@author:冷瑾瑜
****************************************************************************/
void AliIoT_Parameter_Init(void)
{
	char temp[128] = {0};
	
	//ServerIP  PORT
	memset(ServerIP,0,sizeof(ServerIP));
	sprintf(ServerIP,"%s.iot-as-mqtt.cn-shanghai.aliyuncs.com",PRODUCTKEY);
	Port = 1883;
	
	//clientID
	memset(clientID,0,sizeof(clientID));
	sprintf(clientID,"%s|securemode=3,signmethod=hmacsha1|",DEVICENAME);
	clientID_len = strlen(clientID);
	//username
	memset(username,0,sizeof(username));
	sprintf(username,"%s&%s",DEVICENAME,PRODUCTKEY);
	username_len = strlen(username);
	//password
	memset(password,0,sizeof(password));
	sprintf(temp,"clientId%sdeviceName%sproductKey%s",DEVICENAME,DEVICENAME,PRODUCTKEY);
	//哈希加密
	utils_hmac_sha1(temp,strlen(temp),password,DEVICESECRE,DEVICESECRE_LEN);
	password_len = strlen(password);
	
	u1_printf("********************输出调试信息**********************\r\n");
	u1_printf("ServerIP = %s   Port = %d  \r\n",ServerIP,Port);
	u1_printf("clientID = %s\r\n",clientID);
	u1_printf("username = %s\r\n",username);
	u1_printf("password = %s\r\n",password);
	u1_printf("******************************************************\r\n");
	
}
/****************************************************************************
函数名称:void MQTT_ConectPack(void)
函数参数:无
函数返回值:无
描述:连接服务器报文    固定报头+可变报头+负载
@author:冷瑾瑜
****************************************************************************/
void MQTT_ConectPack(void)
{
	int i = 0;
	int length = 0,temp = 0,remain_len,buff_count = 1;
	memset(temp_buff,0,300);
	//固定报头
	temp_buff[0] = 0x10;
			//变长度编码
	length = 10 + clientID_len + 2 + username_len + 2 + password_len + 2;
  remain_len =	length;
	
	do
	{
		temp = length % 128;  //将每个字节最大位取出
		remain_len = remain_len /128;
		if(remain_len > 0){temp |= 0x80;} //数据超过128,需要将最高位置位,这个字节使你用到的第几个字节,如果是1就是基字节
			
		temp_buff[buff_count] = temp;
		buff_count++;
		
	}while(remain_len > 0);//判断几个字节
	//可变报头
	temp_buff[buff_count+0]=0x00;                 
	temp_buff[buff_count+1]=0x04;   
	temp_buff[buff_count+2]=0x4D;	 
	temp_buff[buff_count+3]=0x51;	 
	temp_buff[buff_count+4]=0x54;	 
	temp_buff[buff_count+5]=0x54;	 
	temp_buff[buff_count+6]=0x04;	 
	temp_buff[buff_count+7]=0xC2;	//可变报头第8个字节 :使能用户名和密码校验,不使用遗嘱,不保留会话
	temp_buff[buff_count+8]=0x00; 	//可变报头第9个字节 :保活时间高字节 0x00
	temp_buff[buff_count+9]=0x64;	//可变报头第10个字节:保活时间高字节 0x64   100s
	//负载
		//clientID 
  temp_buff[buff_count+10]=clientID_len / 256;	
	temp_buff[buff_count+11]=clientID_len % 256;	
	memcpy(&temp_buff[buff_count+12],clientID,clientID_len);
		//username
  temp_buff[buff_count+11+ clientID_len + 1]=username_len / 256;	
	temp_buff[buff_count+12+ clientID_len + 1]=username_len % 256;	
	memcpy(&temp_buff[buff_count+13+ clientID_len + 1],username,username_len);
		//password
	temp_buff[buff_count+13+ clientID_len +  username_len + 1]=password_len / 256;	
	temp_buff[buff_count+14+ clientID_len +  username_len + 1]=password_len % 256;	
	memcpy(&temp_buff[buff_count + 15+ clientID_len + username_len + 1],password,password_len);
	
	TxDataBuf_Deal(temp_buff,buff_count + length);  
	
//	for(i = 0;i < buff_count + length;i++)
//	{
//		u1_printf("%x ",temp_buff[i]);
//	}
//	u1_printf("\r\n");
//	Delay_ms(3000);
 
}
/****************************************************************************
函数名称:void MQTT_CONNACK(void)
函数参数:无
函数返回值:无
描述:连接报文响应
@author:冷瑾瑜
****************************************************************************/
void MQTT_CONNACK(void)
{
	if(MQTT_RxData_Output[2]==0x20)
		{      //connect报文         			
			switch(MQTT_RxData_Output[5]){					
			case 0x00 : u1_printf("CONNECT报文成功\r\n");                         
							ConnectPack_flag = 1;                                        
						break;                                                                                                    
			case 0x01 : u1_printf("连接已拒绝,不支持的协议版本,准备重启\r\n");    
						Link_mode = 0;                                             
						break;                                                      
			case 0x02 : u1_printf("连接已拒绝,不合格的客户端标识符,准备重启\r\n");  
						Link_mode = 0;                                           
						break;                                                       
			case 0x03 : u1_printf("连接已拒绝,服务端不可用,准备重启\r\n");       
						Link_mode = 0;                                            
						break;                                                      
			case 0x04 : u1_printf("连接已拒绝,无效的用户名或密码,准备重启\r\n");   
						Link_mode = 0;                                            			
						break;                                                      
			case 0x05 : u1_printf("连接已拒绝,未授权,准备重启\r\n");              
						Link_mode = 0;                                            					
						break;                                                       
			default   : u1_printf("连接已拒绝,未知状态,准备重启\r\n");             
						Link_mode = 0;                                             	
						break;                                                        	
		}
	}			
}
/****************************************************************************
函数名称:void MQTT_Subscribe(int QoS)
函数参数:QoS:订阅等级    
函数返回值:无
描述:SUBSCRIBE订阅topic报文     
@author:冷瑾瑜
****************************************************************************/
void MQTT_Subscribe(int QoS)
{
	
	int remain = 2 + 2 + 1 + S_TOPIC_LEN;
	
	memset(temp_buff,0,300);
	temp_buff[0] = 0x82;
	temp_buff[1] = remain;//自动转换为16机制
	temp_buff[2] = 0x00;
	temp_buff[3] = 0x0A;
	temp_buff[4] = S_TOPIC_LEN / 256;
	temp_buff[5] = S_TOPIC_LEN % 256;
	memcpy(&temp_buff[6],S_TOPIC,S_TOPIC_LEN);
	temp_buff[6 + S_TOPIC_LEN ] = QoS;
	
  TxDataBuf_Deal(temp_buff,remain + 2);
}
/****************************************************************************
函数名称:void MQTT_SUBACK(void)
函数参数:无    
函数返回值:无
描述:  订阅报文响应  
@author:冷瑾瑜
****************************************************************************/
void MQTT_SUBACK(void)
{
	if(MQTT_RxData_Output[2]==0x90)  //SubcribePack报文
	{
			switch(MQTT_RxData_Output[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定时器
				//		LED1_State();                         //判断开关状态,并发布给服务器  
						break;                                //跳出分支                                             
			default   : u1_printf("订阅失败,准备重启\r\n");  //串口输出信息 
						Link_mode = 0;                     //Connect_flag置零,重启连接
						break;                                //跳出分支 								
		}
	}
}
/****************************************************************************
函数名称:void MQTT_UNSUBSCRIBE(void)
函数参数:无    
函数返回值:无
描述:  取消报文订阅  
@author:冷瑾瑜
****************************************************************************/
void MQTT_UNSUBSCRIBE(void)
{
	int remain = 2 + 2 + S_TOPIC_LEN;
	memset(temp_buff,0,300);
	temp_buff[0] = 0xA2;
	temp_buff[1] = remain;
	temp_buff[2] = 0x00;
	temp_buff[3] = 0x0A;
	temp_buff[4] = S_TOPIC_LEN / 256;
	temp_buff[5] = S_TOPIC_LEN % 256;
	memcpy(&temp_buff[6],S_TOPIC,S_TOPIC_LEN);
	
	TxDataBuf_Deal(temp_buff,remain + 2);
}

/****************************************************************************
函数名称:void MQTT_UNSUBACKE(void)
函数参数:无    
函数返回值:无
描述:  取消报文订阅响应  
@author:冷瑾瑜
****************************************************************************/
void MQTT_UNSUBACKE(void)
{
	if(MQTT_RxData_Output[2]==0xB0 && MQTT_RxData_Output[3] == 0x02 && MQTT_RxData_Output[4] == 0x00 && MQTT_RxData_Output[5] == 0x0A)
	{
		u1_printf("取消报文订阅成功\r\n");
	} 
}
/****************************************************************************
函数名称:void MQTT_PingREQ(void)
函数参数:无     
函数返回值:无
描述:PING报文,心跳包   
@author:冷瑾瑜
****************************************************************************/
void MQTT_PingREQ(void)
{
	memset(temp_buff,0,300);
	temp_buff[0] = 0xC0;
	temp_buff[1] = 0x00;
	
	TxDataBuf_Deal(temp_buff,2); //将数据添加至发送缓冲区
}

/****************************************************************************
函数名称:void MQTT_PingRSP(void)
函数参数:无     
函数返回值:无
描述:PING报文,心跳包   
@author:冷瑾瑜
****************************************************************************/
void MQTT_PingRSP(void)
{
	if(MQTT_RxData_Output[2]==0xD0)  //ping报文回复
	{ 
			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的时间
			}		
	}			

}

/****************************************************************************
函数名称:void MQTT_PublishPUBACK(void)
函数参数:无    
函数返回值:无
描述:处理发布响应(QOS1)    
@author:冷瑾瑜
****************************************************************************/
void MQTT_PublishPUBACK(void)
{
	if(MQTT_RxData_Output[2]==0x40 && MQTT_RxData_Output[3]==0x02)   
	{ 
			u1_printf("收到发布消息确认\r\n"); 		   
	}	
		//可加上报文标识符	
}
/****************************************************************************
函数名称:void MQTT_PublishPUBREC(void)
函数参数:无    
函数返回值:无
描述:发布消息收到    
@author:冷瑾瑜
****************************************************************************/
void MQTT_PublishPUBREC(void)
{
	if(MQTT_RxData_Output[2]==0x50 && MQTT_RxData_Output[3]==0x02)   
	{ 
			u1_printf("发布消息收到\r\n"); 		   
	}
			//可加上报文标识符		
}
/****************************************************************************
函数名称:void MQTT_PublishPUBREL(void)
函数参数:无    
函数返回值:无
描述:发布消息释放    
@author:冷瑾瑜
****************************************************************************/
void MQTT_PublishPUBREL(void)
{
	if(MQTT_RxData_Output[2]==0x62 && MQTT_RxData_Output[3]==0x02)   
	{ 
			u1_printf("发布消息释放\r\n"); 		   
	}
			//可加上报文标识符		
}
/****************************************************************************
函数名称:void MQTT_PublishPUBCOMP(void)
函数参数:无    
函数返回值:无
描述:发布消息完成    
@author:冷瑾瑜
****************************************************************************/
void MQTT_PublishPUBCOMP(void)
{
	if(MQTT_RxData_Output[2]==0x70 && MQTT_RxData_Output[3]==0x02)   
	{ 
			u1_printf("发布消息完成\r\n"); 		   
	}
			//可加上报文标识符		
}
/****************************************************************************
函数名称:void MQTT_PublishQs0(void)
函数参数:topic_name:topic名称    data:数据   data_len:数据长度      
函数返回值:无
描述:等级0 发布消息报文    
@author:冷瑾瑜
****************************************************************************/
void MQTT_PublishQs0(char *data,int data_len)
{
	//Info Temp;
		//Temp = MQTT_LightSwitch(1); //点灯任务
//	u1_printf("%d %d",P_TOPIC_LEN,Temp.length);
	int leng = 0,remain = 0;
	char count = 1,temp = 0;

	leng = 2 + P_TOPIC_LEN +  data_len;  //156
	remain = leng;
	memset(temp_buff,0,300);
  temp_buff[0] = 0x30;
	do
	{
		temp = remain % 128;//28
	  remain = remain / 128;  //判断是否进位 1
		
		if(remain > 0){temp |= 0x80;}
		temp_buff[count++] = temp;
	}while(remain > 0);
	temp_buff[count+0] = P_TOPIC_LEN / 128;
	temp_buff[count+1] = P_TOPIC_LEN % 128;
	
	memcpy(&temp_buff[count+2],P_TOPIC,P_TOPIC_LEN);
	memcpy(&temp_buff[count+2+P_TOPIC_LEN],data,data_len);
		
	TxDataBuf_Deal(temp_buff,leng + 1 + count); //将数据添加至发送缓冲区
}
/****************************************************************************
函数名称:void MQTT_DealPushdata_Qs0(unsigned char *redata)
函数参数:redata:接收的数据    
函数返回值:无
描述:处理服务器发来的等级0的推送  服务器下发的第一个字节是30,紧跟的几个字节是变长度  
@author:冷瑾瑜
bug: re_len接收存在问题 
解决办法:将data[1] = 0;认定每个pack< 128
****************************************************************************/
void MQTT_DealPushdata_Qs0(unsigned char *redata)
{
	int  re_len;               	           //定义一个变量,存放接收的数据总长度
	int  pack_num;                         //定义一个变量,当多个推送一起过来时,保存推送的个数
    int  temp,temp_len;                    //定义一个变量,暂存数据
    int  totle_len;                        //定义一个变量,存放已经统计的推送的总数据量
	int  topic_len;              	       //定义一个变量,存放推送中主题的长度
	int  cmd_len;                          //定义一个变量,存放推送中包含的命令数据的长度
	int  cmd_loca;                         //定义一个变量,存放推送中包含的命令的起始位置
	int  i;                                //定义一个变量,用于for循环
	int  local,multiplier;
	unsigned char tempbuff[300];	   //临时缓冲区
	unsigned char *data;                   //redata过来的时候,第一个字节是数据总量,data用于指向redata的第2个字节,真正的数据开始的地方
		
	re_len = redata[0]*256+redata[1];                               //获取接收的数据总长度	
//	u1_printf("re_len = %d\r\n",re_len);
//		for(i = 0;i < re_len + 1 ;i++)
//		{
//			u1_printf("%c",redata[i]);
//		}
//			u1_printf("\r\n");
	data = &redata[2];                                              //data指向redata的第2个字节,真正的数据开始的 
//		u1_printf("data = %c\r\n",data);
	pack_num = temp_len = totle_len = temp = 0;                	    //各个变量清零
	local = 1;
	multiplier = 1;
	do{
		pack_num++;                                     			//开始循环统计推送的个数,每次循环推送的个数+1	
		do{
			temp = data[totle_len + local];   
			temp_len += (temp & 127) * multiplier;
			multiplier *= 128;
			local++;
		}while ((temp & 128) != 0);
		totle_len += (temp_len + local);                          	//累计统计的总的推送的数据长度
		re_len -= (temp_len + local) ;                              //接收的数据总长度 减去 本次统计的推送的总长度      
		local = 1;
		multiplier = 1;
		temp_len = 0;
	}while(re_len!=0);                                  			//如果接收的数据总长度等于0了,说明统计完毕了
	u1_printf("本次接收了%d个推送数据\r\n",pack_num);//串口输出信息
	temp_len = totle_len = 0;                		            	//各个变量清零
	local = 1;
	multiplier = 1;
	for(i=0;i

 

你可能感兴趣的:(物联网通信协议,物联网,mqtt,网络,阿里云)