STM32+MQTT原理浅析

知识点参考:

(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中的每一位的作用如下:

MQTT协议详解 三、MQTT控制包(CONNECT)_mqtt.connect_猫想飞的博客-CSDN博客

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

对于阿里云物联网平台,我们通常设置为:
在这里插入图片描述

3 订阅降级(发布者与订阅者)

MQTT之QOS机制分析_mqtt qos_有梦想的伟仔的博客-CSDN博客

直接引用结论:以源头为主
发布端(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

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值