我是靠谱客的博主 花痴摩托,最近开发中收集的这篇文章主要介绍模块学习4:(1)通过MQTT协议和电信云平台的通信(内附MQTT协议V3.1.1的原版和中文参考资料),觉得挺不错的,现在分享给大家,希望可以做个参考。
概述
这里就不对MQTT协议本身作过多的讲解了,网上也有很多更加详细的对MQTT博文的讲解,当然更直接的就是参看协议文件。
这里直接贴一篇写的比较完整的博客和菜鸟教材对协议的讲解,可以参考学习。
菜鸟教程,MQTT协议入门
MQTT协议详解
之前买了一块超纬电子出的一款开发板,进行ESP8266、以太网、WiFi和MQTT协议连接云平台相关实验的学习。原本MQTT协议的源代码是一个比较大的的工程,适用于多个平台,所以在使用的时候实际上也只用了一部分功能,它对其进行了单独封装,所以可读性和适用性更强,所以我主要参看了这部分代码,实现了我的目的。实现我手上的开发板和电信云平台的交互功能!
MQTT工程包括这么多文件。
主函数
前面的联网,使用以太网或者WIFI都可以,具体过程这里就省略了。直接从连接后的代码开始,当初学习的代码,注释很详细。
switch(getSn_SR(SOCK_TCPS)) //获取TCP链接端口的状态
{
case SOCK_INIT:
if(Connect_flag==0){
ret1 = connect(SOCK_TCPS,ServerIP,ServerPort); //链接服务器
printf("%d.%d.%d.%drn",ServerIP[0],ServerIP[1],ServerIP[2],ServerIP[3]); //串口输出信息
printf("ServerPort = %drn",ServerPort); //串口输出信息
printf("连接服务器返回码:%drn",ret1); //串口输出信息
}
break;
case SOCK_ESTABLISHED: // Socket处于连接建立状态
if(getSn_IR(SOCK_TCPS) & Sn_IR_CON) {
printf("连接已建立rn");
MQTT_Buff_Init(); //初始化接收,发送,命令数据的 缓冲区 以及各状态参数
setSn_IR(SOCK_TCPS, Sn_IR_CON); // Sn_IR的CON位置1,通知W5500连接已建立
Connect_flag = 1; //链接标志=1
}
ret2 = recv(SOCK_TCPS,gDATABUF,DATA_BUF_SIZE); //接收数据
if(ret2 > 0){ //如果ret大于0,表示有数据来
memcpy(&MQTT_RxDataInPtr[2],gDATABUF,ret2); //拷贝数据到接收缓冲区
MQTT_RxDataInPtr[0] = ret2/256; //记录数据长度
MQTT_RxDataInPtr[1] = ret2%256; //记录数据长度
MQTT_RxDataInPtr+=RBUFF_UNIT; //指针下移
if(MQTT_RxDataInPtr==MQTT_RxDataEndPtr) //如果指针到缓冲区尾部了
MQTT_RxDataInPtr = MQTT_RxDataBuf[0]; //指针归位到缓冲区开头
}
if(Connect_flag==1){
/*-------------------------------------------------------------*/
/* 处理发送缓冲区数据 */
/*-------------------------------------------------------------*/
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)){
printf("发送数据:0x%xrn",MQTT_TxDataOutPtr[2]); //串口提示信息
MQTT_TxData(MQTT_TxDataOutPtr); //发送数据
MQTT_TxDataOutPtr += TBUFF_UNIT; //指针下移
if(MQTT_TxDataOutPtr==MQTT_TxDataEndPtr) //如果指针到缓冲区尾部了
MQTT_TxDataOutPtr = MQTT_TxDataBuf[0]; //指针归位到缓冲区开头
}
}
/*-------------------------------------------------------------*/
/* 处理接收缓冲区数据 */
/*-------------------------------------------------------------*/
if(MQTT_RxDataOutPtr != MQTT_RxDataInPtr){ //if成立的话,说明接收缓冲区有数据了
printf("接收到数据: rn");
/*-----------------------------------------------------*/
/* 处理CONNACK报文 */
/*-----------------------------------------------------*/
//if判断,如果一共接收了4个字节,第一个字节是0x20,表示收到的是CONNACK报文
//接着我们要判断第4个字节,看看CONNECT报文是否成功
if(MQTT_RxDataOutPtr[2]==0x20 && ConnectPack_flag == 0){
switch(MQTT_RxDataOutPtr[5]){
case 0x00 : printf("CONNECT报文成功rn"); //串口输出信息
ConnectPack_flag = 1; //CONNECT报文成功,订阅报文可发
// MQTT_Subscribe(S_TOPIC_NAME,1); //发送缓冲区添加订阅topic,等级1
// printf("new MQTT_TxDataOutPtr[6]: %xrn", MQTT_TxDataOutPtr[6]);
break; //跳出分支case 0x00
case 0x01 : printf("连接已拒绝,不支持的协议版本,准备重启rn"); //串口输出信息
NVIC_SystemReset(); //重启
break; //跳出分支case 0x01
case 0x02 : printf("连接已拒绝,不合格的客户端标识符,准备重启rn"); //串口输出信息
NVIC_SystemReset(); //重启
break; //跳出分支case 0x02
case 0x03 : printf("连接已拒绝,服务端不可用,准备重启rn"); //串口输出信息
NVIC_SystemReset(); //重启
break; //跳出分支case 0x03
case 0x04 : printf("连接已拒绝,无效的用户名或密码,准备重启rn"); //串口输出信息
NVIC_SystemReset(); //重启
break; //跳出分支case 0x04
case 0x05 : printf("连接已拒绝,未授权,准备重启rn"); //串口输出信息
NVIC_SystemReset(); //重启
break; //跳出分支case 0x05
default : printf("连接已拒绝,未知状态,准备重启rn"); //串口输出信息
NVIC_SystemReset(); //重启
break; //跳出分支case default
}
}
//if判断,如果一共接收了5个字节,第一个字节是0x90,表示收到的是SUBACK报文
//接着我们要判断订阅回复,看看是不是成功
else if(MQTT_RxDataOutPtr[2]==0x90){
switch(MQTT_RxDataOutPtr[6]){
case 0x00 :
case 0x01 : printf("订阅成功+++rn"); //串口输出信息
SubcribePack_flag = 1; //SubcribePack_flag置1,表示订阅报文成功,其他报文可发送
//TIM3_ENABLE_30S(); //启动30s的PING定时器
break; //跳出分支
default:
printf("订阅失败,准备重启rn"); //串口输出信息
NVIC_SystemReset(); //重启
break; //跳出分支
}
}
//if判断,如果一共接收了2个字节,第一个字节是0xD0,表示收到的是PINGRESP报文
else if(MQTT_RxDataOutPtr[2]==0xD0){
printf("PING报文回复rn");
}
//if判断,如果第一个字节是0x30,表示收到的是服务器发来的推送数据
//我们要提取控制命令
else if(MQTT_RxDataOutPtr[2]==0x30){
printf("服务器等级0推送rn"); //串口输出信息
MQTT_DealPushdata_Qs0(MQTT_RxDataOutPtr); //处理等级0推送数据
}
MQTT_RxDataOutPtr += RBUFF_UNIT; //指针下移
if(MQTT_RxDataOutPtr==MQTT_RxDataEndPtr) //如果指针到缓冲区尾部了
MQTT_RxDataOutPtr = MQTT_RxDataBuf[0]; //指针归位到缓冲区开头
}//处理接收缓冲区数据的else if分支结尾
/*-------------------------------------------------------------*/
/* 处理命令缓冲区数据 */
/*-------------------------------------------------------------*/
if(MQTT_CMDOutPtr != MQTT_CMDInPtr){ //if成立的话,说明命令缓冲区有数据了
if(Upload_data_succeed == 0){
Upload_data();
printf("Upload_data succeed+++ rn");
}
printf("命令:%srn",&MQTT_CMDOutPtr[2]);
MQTT_CMDOutPtr += CBUFF_UNIT; //指针下移
if(MQTT_CMDOutPtr==MQTT_CMDEndPtr) //如果指针到缓冲区尾部了
MQTT_CMDOutPtr = MQTT_CMDBuf[0]; //指针归位到缓冲区开头
}
if(Upload_data_succeed == 0 && SubcribePack_flag == 1){
Upload_data();
Upload_data_succeed = 1;
}
}
break;
case SOCK_CLOSE_WAIT: // Socket处于等待关闭状态
disconnect(SOCK_TCPS);
break;
case SOCK_CLOSED: // Socket处于关闭状态
socket(SOCK_TCPS,Sn_MR_TCP,5000,0x00); // 打开Socket0,打开一个本地端口 mqtt1_flag=0;
NTP_Timeouttimer_Start=1;
break;
}
下面是对应的功能函数,基本上是从原来的工程文件中提取出来的。
云平台的相关参数初始化
/*----------------------------------------------------------*/
/*函数名:云初始化参数,得到客户端ID,用户名和密码 */
/*参 数:无 */
/*返回值:无 */
/*----------------------------------------------------------*/
void IoT_Parameter_Init(void)
{
memset(ClientID,128,0); //客户端ID的缓冲区全部清零
sprintf(ClientID,"%s",PROJECTID); //构建客户端ID,并存入缓冲区
ClientID_len = strlen(ClientID); //计算客户端ID的长度
memset(Username,128,0); //用户名的缓冲区全部清零
sprintf(Username,"%s",DEVICENAME); //构建用户名,并存入缓冲区
Username_len = strlen(Username); //计算用户名的长度
memset(Passward,128,0); //密码的缓冲区全部清零
sprintf(Passward,"%s",DEVICESECRE); //构建密码
Passward_len = strlen(Passward); //计算密码的长度
memset(ServerName,128,0);
sprintf((char*)ServerName,"chongqing-mqtt.ctwing.cn"); //构建服务器域名
ServerPort = 1883; //服务器端口号1883
printf("服 务 器:%srn",ServerName);
printf("客户端ID:%srn",ClientID);
printf("用 户 名:%srn",Username);
printf("密 码:%srn",Passward);
}
连接服务器报文
/*----------------------------------------------------------*/
/*函数名:连接服务器报文 */
/*参 数:无 */
/*返回值:无 */
/*----------------------------------------------------------*/
void MQTT_ConectPack(void)
{
int temp,Remaining_len;
Fixed_len = 1; //连接报文中,固定报头长度暂时先=1
Variable_len = 10; //连接报文中,可变报头长度=10
Payload_len = 2 + ClientID_len + 2 + Username_len + 2 + Passward_len; //连接报文中,负载长度
Remaining_len = Variable_len + Payload_len; //剩余长度=可变报头长度+负载长度
temp_buff[0]=0x10; //固定报头第1个字节 :固定0x01
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]=0x00; //可变报头第1个字节 :固定0x00
temp_buff[Fixed_len+1]=0x04; //可变报头第2个字节 :固定0x04
temp_buff[Fixed_len+2]=0x4D; //可变报头第3个字节 :固定0x4D
temp_buff[Fixed_len+3]=0x51; //可变报头第4个字节 :固定0x51
temp_buff[Fixed_len+4]=0x54; //可变报头第5个字节 :固定0x54
temp_buff[Fixed_len+5]=0x54; //可变报头第6个字节 :固定0x54
temp_buff[Fixed_len+6]=0x04; //可变报头第7个字节 :固定0x04
temp_buff[Fixed_len+7]=0xC2; //可变报头第8个字节 :使能用户名和密码校验,不使用遗嘱,不保留会话
temp_buff[Fixed_len+8]=0x00; //可变报头第9个字节 :保活时间高字节 0x00
temp_buff[Fixed_len+9]=0x64; //可变报头第10个字节:保活时间高字节 0x64 100s
/* CLIENT_ID */
temp_buff[Fixed_len+10] = ClientID_len/256; //客户端ID长度高字节
temp_buff[Fixed_len+11] = ClientID_len%256; //客户端ID长度低字节
memcpy(&temp_buff[Fixed_len+12],ClientID,ClientID_len); //复制过来客户端ID字串
/* 用户名 */
temp_buff[Fixed_len+12+ClientID_len] = Username_len/256; //用户名长度高字节
temp_buff[Fixed_len+13+ClientID_len] = Username_len%256; //用户名长度低字节
memcpy(&temp_buff[Fixed_len+14+ClientID_len],Username,Username_len); //复制过来用户名字串
/* 密码 */
temp_buff[Fixed_len+14+ClientID_len+Username_len] = Passward_len/256; //密码长度高字节
temp_buff[Fixed_len+15+ClientID_len+Username_len] = Passward_len%256; //密码长度低字节
memcpy(&temp_buff[Fixed_len+16+ClientID_len+Username_len],Passward,Passward_len); //复制过来密码字串
TxDataBuf_Deal(temp_buff, Fixed_len + Variable_len + Payload_len); //加入发送数据缓冲区
}
SUBSCRIBE订阅topic报文
/*----------------------------------------------------------*/
/*函数名:SUBSCRIBE订阅topic报文 */
/*参 数:QoS:订阅等级 */
/*参 数:topic_name:订阅topic报文名称 */
/*返回值:无 */
/*----------------------------------------------------------*/
void MQTT_Subscribe(char *topic_name, int QoS)
{
Fixed_len = 2; //SUBSCRIBE报文中,固定报头长度=2
Variable_len = 2; //SUBSCRIBE报文中,可变报头长度=2
Payload_len = 2 + strlen(topic_name) + 1; //计算有效负荷长度 = 2字节(topic_name长度)+ topic_name字符串的长度 + 1字节服务等级
temp_buff[0]=0x82; //第1个字节 :固定0x82
temp_buff[1]=Variable_len + Payload_len; //第2个字节 :可变报头+有效负荷的长度
temp_buff[2]=0x00; //第3个字节 :报文标识符高字节,固定使用0x00
temp_buff[3]=0x01; //第4个字节 :报文标识符低字节,固定使用0x01
temp_buff[4]=strlen(topic_name)/256; //第5个字节 :topic_name长度高字节
temp_buff[5]=strlen(topic_name)%256; //第6个字节 :topic_name长度低字节
memcpy(&temp_buff[6],topic_name,strlen(topic_name)); //第7个字节开始 :复制过来topic_name字串
temp_buff[6+strlen(topic_name)]=QoS; //最后1个字节:订阅等级
TxDataBuf_Deal(temp_buff, Fixed_len + Variable_len + Payload_len); //加入发送数据缓冲区
printf("存储订阅成功=================rn");
}
等级0 发布消息报文
/*----------------------------------------------------------*/
/*函数名:等级0 发布消息报文 */
/*参 数:topic_name:topic名称 */
/*参 数:data:数据 */
/*参 数:data_len:数据长度 */
/*返回值:无 */
/*----------------------------------------------------------*/
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); //加入发送数据缓冲区
}
处理服务器发来的等级0的推送
/*----------------------------------------------------------*/
/*函数名:处理服务器发来的等级0的推送 */
/*参 数:redata:接收的数据 */
/*返回值:无 */
/*----------------------------------------------------------*/
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[RBUFF_UNIT]; //临时缓冲区
unsigned char *data; //redata过来的时候,第一个字节是数据总量,data用于指向redata的第2个字节,真正的数据开始的地方
re_len = redata[0]*256+redata[1]; //获取接收的数据总长度
data = &redata[2]; //data指向redata的第2个字节,真正的数据开始的
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了,说明统计完毕了
printf("本次接收了%d个推送数据rn",pack_num);//串口输出信息
temp_len = totle_len = 0; //各个变量清零
local = 1;
multiplier = 1;
for(i=0;i<pack_num;i++){ //已经统计到了接收的推送个数,开始for循环,取出每个推送的数据
do{
temp = data[totle_len + local];
temp_len += (temp & 127) * multiplier;
multiplier *= 128;
local++;
}while ((temp & 128) != 0);
topic_len = data[local+totle_len]*256+data[local+1+totle_len] + 2; //计算本次推送数据中主题占用的数据量
cmd_len = temp_len-topic_len; //计算本次推送数据中命令数据占用的数据量
cmd_loca = totle_len + local + topic_len; //计算本次推送数据中命令数据开始的位置
memcpy(tempbuff,&data[cmd_loca],cmd_len); //命令数据拷贝出来
CMDBuf_Deal(tempbuff, cmd_len); //加入命令到缓冲区
totle_len += (temp_len+local); //累计已经统计的推送的数据长度
local = 1;
multiplier = 1;
temp_len = 0;
}
}
PING报文,心跳包
/*----------------------------------------------------------*/
/*函数名:PING报文,心跳包 */
/*参 数:无 */
/*返回值:无 */
/*----------------------------------------------------------*/
void MQTT_PingREQ(void)
{
temp_buff[0]=0xC0; //第1个字节 :固定0xC0
temp_buff[1]=0x00; //第2个字节 :固定0x00
TxDataBuf_Deal(temp_buff, 2); //加入数据到缓冲区
处理发送缓冲区
/*----------------------------------------------------------*/
/*函数名:处理发送缓冲区 */
/*参 数:data:数据 */
/*参 数:size:数据长度 */
/*返回值:无 */
/*----------------------------------------------------------*/
void TxDataBuf_Deal(unsigned char *data, int size)
{
int i;
memcpy(&MQTT_TxDataInPtr[2],data,size); //拷贝数据到发送缓冲区
MQTT_TxDataInPtr[0] = size/256; //记录数据长度
MQTT_TxDataInPtr[1] = size%256; //记录数据长度
printf("MQTT_TxDataInPtr=");
for(i = 0; i<size; i++){
printf("%x ", MQTT_TxDataInPtr[i]);
}
MQTT_TxDataInPtr+=TBUFF_UNIT; //指针下移
if(MQTT_TxDataInPtr==MQTT_TxDataEndPtr) //如果指针到缓冲区尾部了
MQTT_TxDataInPtr = MQTT_TxDataBuf[0]; //指针归位到缓冲区开头
}
处理命令缓冲区
/*----------------------------------------------------------*/
/*函数名:处理命令缓冲区 */
/*参 数:data:数据 */
/*参 数:size:数据长度 */
/*返回值:无 */
/*----------------------------------------------------------*/
void CMDBuf_Deal(unsigned char *data, int size)
{
memcpy(&MQTT_CMDInPtr[2],data,size); //拷贝数据到命令缓冲区
MQTT_CMDInPtr[0] = size/256; //记录数据长度
MQTT_CMDInPtr[1] = size%256; //记录数据长度
MQTT_CMDInPtr[size+2] = '