知识点参考:
(1)MQTT协议图解,一文看懂MQTT协议数据包(真实报文数据解析解释)_mqtt协议详解_小黑侠kary的博客-CSDN博客
(2)MQTT之QOS机制分析_mqtt qos_有梦想的伟仔的博客-CSDN博客
(3)Mqtt Qos 深度解读 - 简书 (jianshu.com)
(4)MQTT Qos详解(一)_引壶觞以自酌,眄庭柯以怡颜的博客-CSDN博客
MQTT协议是建立在TCP/IP之上的,它是一种基于客户端-服务器的消息发布/订阅传输协议。至少需要ClientID这个参数,其次为username、password,然后加上IP地址和port端口号(1883)
重要知识点:
1 Qos不同等级的通信方式有什么区别?
1.1 Qos = 0 等级报文:
注意点:此等级的数据报文,publisher不需要进行本地存储,当publisher发送完数据后,直接删除响应的message即可。
发布端(Publisher):192.168.126.1
EMQ服务端(Broker/Server):192.168.126.130
订阅端(Subscriber):192.168.126.128
1.2 Qos = 1 等级报文:
注意点:
(1)publish必须保存msg,这种情况下才能重发数据包;
(2)pubACK回应的数据包中带有标识符信息(放在可变报头里面);
(3)publisher通过比对标识符信息,如果相同说明正确接收到了服务器的回应,会把本地的msg信息删除;
(4)当断网后重新连接,服务器可能会收到两个相同的MQTT报文信息;
在Qos=1这个等级的MQTT一些中,协议本身是不会在服务器这边进行标识符判别的,所以有可能出现重复收到MQTT数据包的问题;
发布端(Publisher):192.168.126.1
EMQ服务端(Broker/Server):192.168.126.130
订阅端(Subscriber):192.168.126.128
1.3 Qos=2 等级报文
注意点:
(1)在Qos=2等级的报文中,协议规定服务器需要对比messageID,所以会有去重的功能;
(2)publisher在本地存储message,Broker收到后也在本地进行存储(持久化存储),但是broker会检查包的标识符,是否又重发的,如果又重发则直接丢弃;
正是因为服务器做了持久化存储,所以需要通知服务器可是释放持久化的资源了。
(3)当设备端收到PUBREC数据包之后,需要向服务器发送PUBREL指令,通知服务器可以释放掉messgeID了;
(4)当设备端收到PUBCOMP之后,这个是服务器来通知Publisher可以删除msg了,从而完成了整个的通信。
发布端(Publisher):192.168.126.1
EMQ服务端(Broker/Server):192.168.126.130
订阅端(Subscriber):192.168.126.128
2、MQTT数据包的格式大致是什么样的?
MQTT数据包格式包括:固定报头、可变报头与数据载荷;
2.1 固定报头
剩余长度 = 可变报头的长度 + 数据载荷的长度;
如何将实际的剩余长度表示为MQTT协议要求的格式(最多4个字节表示):
剩余长度的计算方法可认为是128进制数据,最多4个字节,同时只有7个bit作为数据位,最高位是标志位,如果为1则表示后面的长度还有数据。
报文的控制类型:
根据这个表格得到下面的规律:
不考虑PING、PINGRSP和DISCONNECT三个报文命令,其余报文都是有固定报头和可变报头的。
功能报文类型 | 种类 | 种类特点 |
---|---|---|
CONNECT、PUBLISH、SUBSCRIBE、UNSUBSCRIBE | 发送报文 | (1)固定报头、可变报头和数据载荷,全部都有 |
- | - | - |
CONNACK、PUBACK、PUBREC(Qos1、Qos2) 、PUBREL(Qos2)、PUBCOMP(Qos2)、SUBACK、UNSUBACK | 回应报文 | (1)除了SUBACK有数据载荷(为了指示订阅是否成功),其余的回复命令都只有固定报头和可变报头; |
2.2 可变报头和数据载荷
不同的指令,可变报头的格式不同,可参考数据手册。比如下面CONNECT报文中的可变报头如下:
其中byte8中的每一位的作用如下:
对于阿里云物联网平台,我们通常设置为:
3 订阅降级(发布者与订阅者)
直接引用结论:以源头为主
发布端(Publisher) 和 订阅端(Subscriber):订阅端的QOS等级要基于发布端的QOS等级:
P(QOS0)、S(QOS2) == P(QOS0)、S(QOS1) == P(QOS0)、S(QOS0)
P(QOS1)、S(QOS2) == P(QOS1)、S(QOS1)
4 MQTT数据包基于C语言的具体实现方法是什么?
mqtt.c
/*-------------------------------------------------*/
/* */
/*-------------------------------------------------*/
/* */
/* 实现MQTT协议功能的源文件 */
/* */
/*-------------------------------------------------*/
#include "stm32g0xx_hal.h" //包含需要的头文件
#include "main.h" //包含需要的头文件
#include "mqtt.h" //包含需要的头文件
#include "utils_hmac.h" //包含需要的头文件
#include "delay.h" //包含需要的头文件
#include "usart.h" //包含需要的头文件
MQTT_CB Aliyun_mqtt; //创建一个用于连接阿里云mqtt的结构体
/*----------------------------------------------------------*/
/*函数名:云服务器初始化参数,得到客户端ID,用户名和密码 */
/*参 数:无 */
/*返回值:无 */
/*----------------------------------------------------------*/
void IoT_Parameter_Init(void)
{
char temp[64]; //计算加密的时候,临时使用的缓冲区
memset(&Aliyun_mqtt,0,MQTT_CB_LEN); //连接阿里云mqtt的结构体数据全部清零
sprintf(Aliyun_mqtt.ClientID,"%s|securemode=3,signmethod=hmacsha1|",DEVICENAME); //构建客户端ID,并存入缓冲区
sprintf(Aliyun_mqtt.Username,"%s&%s",DEVICENAME,PRODUCTKEY); //构建用户名,并存入缓冲区
memset(temp,0,64); //临时缓冲区全部清零
sprintf(temp,"clientId%sdeviceName%sproductKey%s",DEVICENAME,DEVICENAME,PRODUCTKEY); //构建加密时的明文
utils_hmac_sha1(temp,strlen(temp),Aliyun_mqtt.Passward,(char *)DEVICESECRE,DEVICESECRET_LEN); //以DeviceSecret为秘钥对temp中的明文,进行hmacsha1加密,结果就是密码,并保存到缓冲区中
sprintf(Aliyun_mqtt.ServerIP,"%s.iot-as-mqtt.cn-shanghai.aliyuncs.com",PRODUCTKEY); //构建服务器域名
Aliyun_mqtt.ServerPort = 1883; //服务器端口号1883
// 被动接收平台下发的数据(MQTT一般都是分片下载)
sprintf(Aliyun_mqtt.Stopic_Buff[0],"/sys/%s/%s/thing/service/property/set",PRODUCTKEY,DEVICENAME); //构建第一个需要订阅的Topic,接受服务器下发数据
sprintf(Aliyun_mqtt.Stopic_Buff[1],"/ota/device/upgrade/%s/%s",PRODUCTKEY,DEVICENAME); //构建第一个需要订阅的Topic,接受OTA升级通知
sprintf(Aliyun_mqtt.Stopic_Buff[2],"/sys/%s/%s/thing/file/download_reply",PRODUCTKEY,DEVICENAME); //构建第一个需要订阅的Topic,接受下载固件
}
/*----------------------------------------------------------*/
/*函数名:MQTT CONNECT报文 鉴权连接 */
/*参 数:无 */
/*返回值:无 */
/*----------------------------------------------------------*/
void MQTT_ConectPack(void)
{
int temp; //计算报文剩余长度时,使用的临时变量
int Remaining_len; //保存报文剩余长度字节
Aliyun_mqtt.MessageID = 0; //报文标识符清零,CONNECT报文虽然不需要添加报文标识符,但是CONNECT报文是第一个发送的报文,在此清零报文标识符,为后续报文做准备
Aliyun_mqtt.Fixed_len = 1; //CONNECT报文,固定报头长度暂定为1
Aliyun_mqtt.Variable_len = 10; //CONNECT报文,可变报头长度=10
Aliyun_mqtt.Payload_len = 2 + strlen(Aliyun_mqtt.ClientID) + 2 + strlen(Aliyun_mqtt.Username) + 2 + strlen(Aliyun_mqtt.Passward); //CONNECT报文,计算负载长度
Remaining_len = Aliyun_mqtt.Variable_len + Aliyun_mqtt.Payload_len; //剩余长度=可变报头长度+负载长度
Aliyun_mqtt.Pack_buff[0]=0x10; //CONNECT报文 固定报头第1个字节 :0x10
do{ //循环处理固定报头中的剩余长度字节,字节量根据剩余字节的真实长度变化
temp = Remaining_len%128; //剩余长度取余128
Remaining_len = Remaining_len/128; //剩余长度取整128
if(Remaining_len>0) temp |= 0x80; //如果Remaining_len大于等于128了 按协议要求位7置位
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len] = temp; //剩余长度字节记录一个数据
Aliyun_mqtt.Fixed_len++; //固定报头总长度+1
}while(Remaining_len>0); //如果Remaining_len>0的话,再次进入循环
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+0]=0x00; //CONNECT报文,可变报头第1个字节 :固定0x00
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+1]=0x04; //CONNECT报文,可变报头第2个字节 :固定0x04
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+2]=0x4D; //CONNECT报文,可变报头第3个字节 :固定0x4D
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+3]=0x51; //CONNECT报文,可变报头第4个字节 :固定0x51
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+4]=0x54; //CONNECT报文,可变报头第5个字节 :固定0x54
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+5]=0x54; //CONNECT报文,可变报头第6个字节 :固定0x54
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+6]=0x04; //CONNECT报文,可变报头第7个字节 :固定0x04
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+7]=0xC2; //CONNECT报文,可变报头第8个字节 :使能用户名和密码校验,不使用遗嘱功能,不保留会话功能
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+8]=0x00; //CONNECT报文,可变报头第9个字节 :保活时间高字节 0x00
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+9]=0x64; //CONNECT报文,可变报头第10个字节:保活时间高字节 0x64 最终值=100s * 1.5 = 150s(2分半)
/* CLIENT_ID */
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+10] = strlen(Aliyun_mqtt.ClientID)/256; //客户端ID长度高字节
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+11] = strlen(Aliyun_mqtt.ClientID)%256; //客户端ID长度低字节
memcpy(&Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+12],Aliyun_mqtt.ClientID,strlen(Aliyun_mqtt.ClientID)); //复制过来客户端ID字串
/* 用户名 */
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+12+strlen(Aliyun_mqtt.ClientID)] = strlen(Aliyun_mqtt.Username)/256; //用户名长度高字节
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+13+strlen(Aliyun_mqtt.ClientID)] = strlen(Aliyun_mqtt.Username)%256; //用户名长度低字节
memcpy(&Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+14+strlen(Aliyun_mqtt.ClientID)],Aliyun_mqtt.Username,strlen(Aliyun_mqtt.Username)); //复制过来用户名字串
/* 密码 */
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+14+strlen(Aliyun_mqtt.ClientID)+strlen(Aliyun_mqtt.Username)] = strlen(Aliyun_mqtt.Passward)/256; //密码长度高字节
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+15+strlen(Aliyun_mqtt.ClientID)+strlen(Aliyun_mqtt.Username)] = strlen(Aliyun_mqtt.Passward)%256; //密码长度低字节
memcpy(&Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+16+strlen(Aliyun_mqtt.ClientID)+strlen(Aliyun_mqtt.Username)],Aliyun_mqtt.Passward,strlen(Aliyun_mqtt.Passward)); //复制过来密码字串
TxDataBuf_Deal(Aliyun_mqtt.Pack_buff, Aliyun_mqtt.Fixed_len + Aliyun_mqtt.Variable_len + Aliyun_mqtt.Payload_len); //加入发送数据缓冲区
}
/*----------------------------------------------------------*/
/*函数名:MQTT DISCONNECT报文 断开连接 */
/*参 数:无 */
/*返回值:无 */
/*----------------------------------------------------------*/
void MQTT_DISCONNECT(void)
{
Aliyun_mqtt.Pack_buff[0]=0xE0; //第1个字节 :固定0xE0
Aliyun_mqtt.Pack_buff[1]=0x00; //第2个字节 :固定0x00
TxDataBuf_Deal(Aliyun_mqtt.Pack_buff, 2); //加入发送数据缓冲区
}
/*----------------------------------------------------------*/
/*函数名:MQTT SUBSCRIBE报文 订阅Topic */
/*参 数:topicbuff:订阅topic报文的缓冲区 */
/*参 数:topicnum:订阅几个topic报文 */
/*参 数:Qs:订阅等级 */
/*返回值:无 */
/*----------------------------------------------------------*/
void MQTT_Subscribe(char topicbuff[TOPIC_NUM][TOPIC_SIZE], int topicnum, unsigned char Qs)
{
int i; //用于for循环
int temp; //计算数据时,使用的临时变量
int Remaining_len; //保存报文剩余长度字节
Aliyun_mqtt.Fixed_len = 1; //SUBSCRIBE报文,固定报头长度暂定为1
Aliyun_mqtt.Variable_len = 2; //SUBSCRIBE报文,可变报头长度=2 2字节报文标识符
Aliyun_mqtt.Payload_len = 0; //SUBSCRIBE报文,负载数据长度暂定为0
for(i=0;i<topicnum;i++) //循环统计topic字符串长度,用来统计负载数据长度
Aliyun_mqtt.Payload_len += strlen(topicbuff[i]); //每次累加1个topic长度
Aliyun_mqtt.Payload_len += 3*topicnum; //负载长度不仅包含topic字符串长度,还有等级0的标识字节,一个topic一个,以及2个字节的字符串长度标识字节,一个topic三个,所以再加上3*S_TOPIC_NUM
Remaining_len = Aliyun_mqtt.Variable_len + Aliyun_mqtt.Payload_len; //计算剩余长度=可变报头长度+负载长度
Aliyun_mqtt.Pack_buff[0]=0x82; //SUBSCRIBE报文 固定报头第1个字节 :0x82
do{ //循环处理固定报头中的剩余长度字节,字节量根据剩余字节的真实长度变化
temp = Remaining_len%128; //剩余长度取余128
Remaining_len = Remaining_len/128; //剩余长度取整128
if(Remaining_len>0) temp |= 0x80; //如果Remaining_len大于等于128了 按协议要求位7置位
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len] = temp; //剩余长度字节记录一个数据
Aliyun_mqtt.Fixed_len++; //固定报头总长度+1
}while(Remaining_len>0); //如果Remaining_len>0的话,再次进入循环
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+0] = Aliyun_mqtt.MessageID/256; //报文标识符高字节
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+1] = Aliyun_mqtt.MessageID%256; //报文标识符低字节
Aliyun_mqtt.MessageID++; //每用一次加1
temp = 0;
for(i=0;i<topicnum;i++){ //循环复制负载topic数据
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+2+temp] = strlen(topicbuff[i])/256; //topic字符串 长度高字节 标识字节
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+3+temp] = strlen(topicbuff[i])%256; //topic字符串 长度低字节 标识字节
memcpy(&Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+4+temp],topicbuff[i],strlen(topicbuff[i])); //复制topic字串
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+4+strlen(topicbuff[i])+temp] = Qs; //订阅等级0
temp += strlen(topicbuff[i]) + 3; //len等于本次循环中添加的数据量 等于 topic字串本身长度 + 2个字节长度标识 + 1个字节订阅等级
}
TxDataBuf_Deal(Aliyun_mqtt.Pack_buff, Aliyun_mqtt.Fixed_len + Aliyun_mqtt.Variable_len + Aliyun_mqtt.Payload_len); //加入发送数据缓冲区
}
/*----------------------------------------------------------*/
/*函数名:MQTT UNSUBSCRIBE报文 取消订阅Topic */
/*参 数:topicbuff:取消订阅topic名称缓冲区 */
/*返回值:无 */
/*----------------------------------------------------------*/
void MQTT_UNSubscribe(char *topicbuff)
{
int temp; //计算数据时,使用的临时变量
int Remaining_len; //保存报文剩余长度字节
Aliyun_mqtt.Fixed_len = 1; //UNSUBSCRIBE报文,固定报头长度暂定为1
Aliyun_mqtt.Variable_len = 2; //UNSUBSCRIBE报文,可变报头长度=2 2字节报文标识符
Aliyun_mqtt.Payload_len = strlen(topicbuff) + 2; //UNSUBSCRIBE报文,负载数据长度 = topic名称长度 + 2字节长度标识
Remaining_len = Aliyun_mqtt.Variable_len + Aliyun_mqtt.Payload_len; //计算剩余长度=可变报头长度+负载长度
Aliyun_mqtt.Pack_buff[0]=0xA2; //UNSUBSCRIBE报文 固定报头第1个字节 :0xA0
do{ //循环处理固定报头中的剩余长度字节,字节量根据剩余字节的真实长度变化
temp = Remaining_len%128; //剩余长度取余128
Remaining_len = Remaining_len/128; //剩余长度取整128
if(Remaining_len>0) temp |= 0x80; //如果Remaining_len大于等于128了 按协议要求位7置位
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len] = temp; //剩余长度字节记录一个数据
Aliyun_mqtt.Fixed_len++; //固定报头总长度+1
}while(Remaining_len>0); //如果Remaining_len>0的话,再次进入循环
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+0] = Aliyun_mqtt.MessageID/256; //报文标识符高字节
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+1] = Aliyun_mqtt.MessageID%256; //报文标识符低字节
Aliyun_mqtt.MessageID++; //每用一次加1
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+2] = strlen(topicbuff)/256; //topic字符串 长度高字节 标识字节
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+3] = strlen(topicbuff)%256; //topic字符串 长度低字节 标识字节
memcpy(&Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+4],topicbuff,strlen(topicbuff)); //复制topic字串
TxDataBuf_Deal(Aliyun_mqtt.Pack_buff, Aliyun_mqtt.Fixed_len + Aliyun_mqtt.Variable_len + Aliyun_mqtt.Payload_len); //加入发送数据缓冲区
}
/*----------------------------------------------------------*/
/*函数名:MQTT PING报文 保活心跳包 */
/*参 数:无 */
/*返回值:无 */
/*----------------------------------------------------------*/
void MQTT_PingREQ(void)
{
Aliyun_mqtt.Pack_buff[0]=0xC0; //第1个字节 :固定0xC0
Aliyun_mqtt.Pack_buff[1]=0x00; //第2个字节 :固定0x00
TxDataBuf_Deal(Aliyun_mqtt.Pack_buff, 2); //加入发送数据缓冲区
}
/*----------------------------------------------------------*/
/*函数名:MQTT PUBLISH报文 等级0 发布数据 */
/*参 数:topic_name:发布数据的topic名称 */
/*参 数:data:数据 */
/*参 数:data_len:数据长度 */
/*返回值:无 */
/*----------------------------------------------------------*/
void MQTT_PublishQs0(char *topic, char *data, int data_len)
{
int temp,Remaining_len;
Aliyun_mqtt.Fixed_len = 1; //PUBLISH等级0报文,固定报头长度暂定为1
Aliyun_mqtt.Variable_len = 2 + strlen(topic); //PUBLISH等级0报文,可变报头长度=2字节(topic长度)标识字节+ topic字符串的长度
Aliyun_mqtt.Payload_len = data_len; //PUBLISH等级0报文,负载数据长度 = data_len
Remaining_len = Aliyun_mqtt.Variable_len + Aliyun_mqtt.Payload_len; //计算剩余长度=可变报头长度+负载长度
Aliyun_mqtt.Pack_buff[0]=0x30; //PUBLISH等级0报文 固定报头第1个字节 :0x0x30
do{ //循环处理固定报头中的剩余长度字节,字节量根据剩余字节的真实长度变化
temp = Remaining_len%128; //剩余长度取余128
Remaining_len = Remaining_len/128; //剩余长度取整128
if(Remaining_len>0) temp |= 0x80; //如果Remaining_len大于等于128了 按协议要求位7置位
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len] = temp; //剩余长度字节记录一个数据
Aliyun_mqtt.Fixed_len++; //固定报头总长度+1
}while(Remaining_len>0); //如果Remaining_len>0的话,再次进入循环
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+0]=strlen(topic)/256; //可变报头第1个字节 :topic长度高字节
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+1]=strlen(topic)%256; //可变报头第2个字节 :topic长度低字节
memcpy(&Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+2],topic,strlen(topic)); //可变报头第3个字节开始 :拷贝topic字符串
memcpy(&Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+2+strlen(topic)],data,data_len); //有效负荷:拷贝data数据
TxDataBuf_Deal(Aliyun_mqtt.Pack_buff, Aliyun_mqtt.Fixed_len + Aliyun_mqtt.Variable_len + Aliyun_mqtt.Payload_len); //加入发送数据缓冲区
}
/*----------------------------------------------------------*/
/*函数名:MQTT PUBLISH报文 等级1 发布数据 */
/*参 数:topic_name:发布数据的topic名称 */
/*参 数:data:数据 */
/*参 数:data_len:数据长度 */
/*返回值:无 */
/*----------------------------------------------------------*/
void MQTT_PublishQs1(char *topic, char *data, int data_len)
{
int temp,Remaining_len;
Aliyun_mqtt.Fixed_len = 1; //PUBLISH等级1报文,固定报头长度暂定为1
Aliyun_mqtt.Variable_len = 2 + 2 + strlen(topic); //PUBLISH等级1报文,可变报头长度=2字节标识符 + 2字节(topic长度)标识字节 + topic字符串的长度
Aliyun_mqtt.Payload_len = data_len; //PUBLISH等级1报文,负载数据长度 = data_len
Remaining_len = Aliyun_mqtt.Variable_len + Aliyun_mqtt.Payload_len; //计算剩余长度=可变报头长度+负载长度
Aliyun_mqtt.Pack_buff[0]=0x32; //PUBLISH等级1报文 固定报头第1个字节 :0x0x32
do{ //循环处理固定报头中的剩余长度字节,字节量根据剩余字节的真实长度变化
temp = Remaining_len%128; //剩余长度取余128
Remaining_len = Remaining_len/128; //剩余长度取整128
if(Remaining_len>0) temp |= 0x80; //如果Remaining_len大于等于128了 按协议要求位7置位
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len] = temp; //剩余长度字节记录一个数据
Aliyun_mqtt.Fixed_len++; //固定报头总长度+1
}while(Remaining_len>0); //如果Remaining_len>0的话,再次进入循环
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+0]=strlen(topic)/256; //可变报头第1个字节 :topic长度高字节
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+1]=strlen(topic)%256; //可变报头第2个字节 :topic长度低字节
memcpy(&Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+2],topic,strlen(topic)); //可变报头第3个字节开始 :拷贝topic字符串
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+2+strlen(topic)] = Aliyun_mqtt.MessageID/256; //报文标识符高字节
Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+3+strlen(topic)] = Aliyun_mqtt.MessageID%256; //报文标识符低字节
Aliyun_mqtt.MessageID++; //每用一次加1
memcpy(&Aliyun_mqtt.Pack_buff[Aliyun_mqtt.Fixed_len+4+strlen(topic)],data,data_len); //有效负荷:拷贝data数据
TxDataBuf_Deal(Aliyun_mqtt.Pack_buff, Aliyun_mqtt.Fixed_len + Aliyun_mqtt.Variable_len + Aliyun_mqtt.Payload_len); //加入发送数据缓冲区
}
/*----------------------------------------------------------*/
/*函数名:处理服务器发来的等级0的推送数据 */
/*参 数:redata:接收的数据 */
/*返回值:无 */
/*----------------------------------------------------------*/
void MQTT_DealPushdata_Qs0(unsigned char *redata, int data_len)
{
int i; //用于for循环
int topic_len; //定义一个变量,存放数据中topic字符串长度 + 2字节topic字符串长度标识字节的计算值
char topic_buff[128]; //存放topic信息
int Remaining_len; //保存报文剩余长度
char Remaining_size; //保存报文剩余长度占用几个字节
for(i=1;i<5;i++){ //循环查看报文剩余长度占用几个字节 最多4个字节
if((redata[i]&0x80)==0){ //位7不是1的话,说明到了报文剩余长度最后的1个字节
Remaining_size = i; //记录i,就是报文剩余长度占用的字节数量
break; //跳出for
}
}
Remaining_len = 0; //剩余长度清零
for(i=Remaining_size;i>0;i--){ //报文剩余长度占用几个字节,就循环计次计算长度
Remaining_len += (redata[i]&0x7f)*powdata(128,i-1); //计算
}
topic_len = redata[Remaining_size+1]*256 + redata[Remaining_size+2] + 2; //topic字符串长度 + 2字节topic字符串长度标识字节
memset(topic_buff,0,128); //清空缓冲区
memcpy(topic_buff,&redata[Remaining_size+3],topic_len-2); //提取topic信息
memset(Aliyun_mqtt.cmdbuff,0,350); //清空缓冲区
memcpy(Aliyun_mqtt.cmdbuff,&redata[1+Remaining_size+topic_len],Remaining_len - topic_len); //拷贝命令数据部分到 Aliyun_mqtt.cmdbuff 缓冲区供后续处理
}
/*----------------------------------------------------------*/
/*函数名:向发送缓冲区添加数据 */
/*参 数:databuff:数据 */
/*参 数:datalen:数据长度 */
/*返回值:无 */
/*----------------------------------------------------------*/
void TxDataBuf_Deal(unsigned char *databuff, int datalen)
{
if(U2_TXBUFF_SIZE - U2_Control.U_TxCounter >= datalen){ //计算发送缓冲区内剩余的空间,还够不够存放本次的数据,够的话进入if
U2_Control.UTxDataInPtr->StartPtr = &U2_TxBuff[U2_Control.U_TxCounter]; //记录本次存放数据的起始位置
}else{ //反之,不够存放本次的数据,进入else
U2_Control.U_TxCounter = 0; //发送缓冲区,存放位置变量清零
U2_Control.UTxDataInPtr->StartPtr = U2_TxBuff; //本次存放数据的起始位置,设置为发送缓冲区开始位置
}
memcpy(U2_Control.UTxDataInPtr->StartPtr,databuff,datalen); //拷贝数据
U2_Control.U_TxCounter += datalen; //发送缓冲区,存放位置累加本次存放的数据量
U2_Control.UTxDataInPtr->EndPtr = &U2_TxBuff[U2_Control.U_TxCounter]; //记录发送缓冲区本次存放的结束位置
U2_Control.UTxDataInPtr++; //数据IN指针下移
if(U2_Control.UTxDataInPtr==U2_Control.UTxDataEndPtr) //如果指针到处理接收数据的结构体数组尾部了
U2_Control.UTxDataInPtr = &U2_Control.UCB_TxBuffPtrCB[0]; //指针归位到处理接收数据的结构体数组的起始位置,也就是0号成员
}
/*----------------------------------------------------------*/
/*函数名:x的y次幂 */
/*参 数:x */
/*参 数:y */
/*返回值:无 */
/*----------------------------------------------------------*/
int powdata(int x, int y)
{
int data,temp,i;
data = 0;
if(y==0){
data = 1;
}else if(y==1){
data = x;
}else{
temp = x;
for(i=0;i<y-1;i++){
data = temp*x;
temp = data;
}
}
return data;
}
mqtt.h
/*-------------------------------------------------*/
/* */
/*-------------------------------------------------*/
/* */
/* 实现MQTT协议功能的头文件 */
/* */
/*-------------------------------------------------*/
#ifndef __MQTT_H
#define __MQTT_H
#define MQTT_TxData(x) u2_TxData(x) //串口2控制数据
#define PRODUCTKEY AliInfoCB.ProductKeyBuff //产品KEY
#define DEVICENAME AliInfoCB.DeviceNameBuff //设备名
#define DEVICESECRE AliInfoCB.DeviceSecretBuff //设备秘钥
#define TOPIC_NUM 3 //需要订阅的最大Topic数量
#define TOPIC_SIZE 64 //存放Topic字符串名称缓冲区长度
typedef struct{
// 下面三个变量数组在Connect报文中需要
char ClientID[64]; //存放客户端ID的缓冲区
char Username[64]; //存放用户名的缓冲区
char Passward[64]; //存放密码的缓冲区
// 存放案例云域名
char ServerIP[64]; //存放服务器IP或是域名
int ServerPort; //存放服务器的端口号
// 发送的报文数据
unsigned char Pack_buff[256]; //发送报文的缓冲区
// 报文的内容,
int MessageID; //记录报文标识符(用来追踪报文数据)
int Fixed_len; //固定报头长度
int Variable_len; //可变报头长度
int Payload_len; //有效负荷长度
char Stopic_Buff[TOPIC_NUM][TOPIC_SIZE]; //包含的是订阅的主题列表
// 为了保证OTA的部分,数组定义的大一点
char cmdbuff[400]; //保存推送的数据中的命令数据部分
int streamId; //OTA升级时,保存streamId数据(阿里云提供的标号)
int streamFileId; //OTA升级时,保存streamFileId(每个设备提供固定的标号,阿里云给设备对应标号的固件)
int streamSize; //OTA升级时,固件总的大小
char OTA_Versionbuff[64]; //OTA升级时,保存新的版本号
int OTA_timers; //OTA升级时,每次下载2048,总共需要下载多少次
int OTA_num; //OTA升级时,保存当前下载到第几次2k数据了
}MQTT_CB;
#define MQTT_CB_LEN sizeof(MQTT_CB) //结构体长度
extern MQTT_CB Aliyun_mqtt; //外部变量声明,用于连接阿里云mqtt的结构体
// 定时方法保活包
extern char Ping_flag; //外部变量声明,ping报文状态 0:正常状态,等待计时时间到,发送Ping报文
//外部变量声明,ping报文状态 1:Ping报文已发送,当收到 服务器回复报文的后 将1置为0
extern char Connect_flag; //外部变量声明,同服务器连接状态 0:还没有连接服务器 1:连接上服务器了
extern char ReConnect_flag; //外部变量声明,重连服务器状态 0:连接还存在 1:连接断开,重连
extern char ConnectPack_flag; //外部变量声明,CONNECT报文状态 1:CONNECT报文成功
extern char SubcribePack_flag; //外部变量声明,订阅报文状态 1:订阅报文成功
void IoT_Parameter_Init(void); //函数声明,云服务器初始化参数,得到客户端ID,用户名和密码
void MQTT_ConectPack(void); //函数声明,MQTT CONNECT报文 鉴权连接
void MQTT_DISCONNECT(void); //函数声明,MQTT DISCONNECT报文 断开连接
void MQTT_Subscribe(char topicbuff[TOPIC_NUM][TOPIC_SIZE],int, unsigned char); //函数声明,MQTT SUBSCRIBE报文 订阅Topic
void MQTT_UNSubscribe(char *); //函数声明,MQTT UNSUBSCRIBE报文 取消订阅Topic
void MQTT_PingREQ(void); //函数声明,MQTT PING报文 保活心跳包
void MQTT_PublishQs0(char *,char *,int); //函数声明,MQTT PUBLISH报文 等级0 发布数据
void MQTT_PublishQs1(char *,char *,int); //函数声明,MQTT PUBLISH报文 等级1 发布数据
void MQTT_DealPushdata_Qs0(unsigned char *,int); //函数声明,处理服务器发来的等级0的推送数据
void TxDataBuf_Deal(unsigned char *, int); //函数声明,向发送缓冲区添加数据
int powdata(int , int);
#endif