基于stm32 华为云iot设计的智能防盗单车锁【玩转华为云】-4008云顶国际网站

ds小龙哥 发表于 2022/05/06 14:14:43 2022/05/06
【摘要】 近年来随着国民经济的发展,交通拥堵和环境污染问题越来越突出,而自行车对改善交通与环境起到了重要作用。中国本身是一个自行车使用大国,随着自行车的发展,自行车的科技含量越来越高,然而自行车安防问题突出。目前市场上自行车锁大多是传统机械结构车锁,没有实现智能化,急需解决。本文提出一种基于stm32单片机的智能自行车锁(马蹄锁)的设计方法,来提高自行车锁的智能化及安防等级。

一、前言

近年来随着国民经济的发展,交通拥堵和环境污染问题越来越突出,而自行车对改善交通与环境起到了重要作用。中国本身是一个自行车使用大国,随着自行车的发展,自行车的科技含量越来越高,然而自行车安防问题突出。目前市场上自行车锁大多是传统机械结构车锁,没有实现智能化,急需解决。本文提出一种基于stm32单片机的智能自行车锁(马蹄锁)的设计方法,来提高自行车锁的智能化及安防等级。

硬件选项说明:单片机采用stm32f103rct6,gsm模块采用sim800c,完成网络连接、数据上传,gps经纬度解析,短信发送,物联网平台采用华为云iot,作为数据存储端,蓝牙模块采用正点原子低功耗ble蓝牙,支持蓝牙开锁解锁,车辆的状态使用adxl345三轴加速度传感器检测,密码键盘采用电容矩阵键盘。




二、设计思路总结

需要设计一款android手机app,可以远程开锁解锁,手机app对接华为云物联网平台,实现远程与自行车锁完成数据交互,命令下发。智能锁与华为云iot服务器之间的通信协议采用mqtt协议,手机app与华为云iot服务器之间采用http协议。智能锁除了支持远程开锁关锁之外,还支持蓝牙解锁和输入密码开始,设计的app支持蓝牙功能,可以连接智能锁上的蓝牙完成开锁和关锁,如果没有带手机,可以输入密码完成开锁。

车辆的状态检测通过adxl345三轴加速度计检测,如果车辆处于锁定状态,发现车辆被移动了会触发报警,锁里的蜂鸣器会持续响,并且sim800c会向指定的手机号码发送短信,提示车辆可能被盗,同时上传gps经纬度到云端服务器,手机app上可以获取智能锁上传的gps经纬度,调用百度地图显示车辆的位置,方便寻车。

三、硬件选型

(1) 加速度计传感器

adxl345是一款小尺寸、薄型、低功耗、完整的三轴加速度计,提供经过信号调理的电压输出。

说明:cs接高电平则选择iic通信,反之则spi通信。sdo(地址引脚)接高电平,根据手册器件的7位i2c地址是0x1d,后面跟上读取/写位(r/w),则写寄存器为0x3a,读寄存器为0x3b;接低电平,则7位i2c地址是0x53,同理,跟上读写标志位后写寄存器为0xa6,读寄存器为0xa7;

(2) stm32开发板

stm32f103rct6的芯体规格是32位,速度是72mhz,程序存储器容量是256kb,程序存储器类型是flash,ram容量是48k。

(3) ble低功耗蓝牙模块

(4) sim800c

模块特点:

1、支持极限dc5v-18v宽电压输入

2、有电源使能开关引脚en

3、支持锂电池供电接口vbat3.5-4.5v

4、输入支持移动和联通手机卡micro sim卡

5、送51/stm32/arduino驱动例程

1、dc 5v-18v电源输入,推荐使用dc 9v

2、电源开始使能引脚默认使能

3、电源地

4、gsm模块的txd引脚接其它模块的rxd

5、gsm模块的rxd引脚接其它模块的txd

6、数据终端准备

7、内核音频输出引脚

8、内核音频输出引脚

9、锂电池输入引脚,dc 3.5 - 4.5v

10、电源地

11、启动引脚和gnd短路可实现开机自启动

12、电源地

13、rtc外置电池引脚

14、内核振铃提示引脚

15、内合音频输入引脚

16、内核音频输入引脚

加粗的引脚一般都用到。

建议使用v_in单独供电dc5-18v输入(推荐使用9v),或者vbat供电锂电池两种供电方式这两种供电方式最稳定。如果只是简单调试,也可使用usb-ttl或者开发板的5v直接给模块供电。不过一般电脑或者开发板的功率有限,可能会不稳定。请根据具体情况自己取舍选择合适电源。

3. 手机app软件设计

3.1 通信说明

上位机与设备之间支持通过ble低功耗串口蓝牙进行通信,支持通过网络连接华为云服务器进行通信,手机app下发open_lock和close_lock实现关锁开锁。

3.2 搭建开发环境

上位机软件采用qt框架设计,qt是一个跨平台的c 图形用户界面应用程序框架。qt是一个1991年由qt company开发的跨平台c 图形用户界面应用程序开发框架。它既可以开发gui程序,也可用于开发非gui程序,比如控制台工具和服务器。简单来说,qt可以很轻松的帮你做带界面的软件,甚至不需要你投入很大精力。

qt4008云顶国际集团官网:

qt学习入门实战专栏文章:

qt5.12.6的下载地址:

4. 创建云端设备

4.1 创建产品

登录4008云顶国际集团官网:

直接搜索物联网,打开页面。



4.2 自定义模型






4.3 注册设备




设备创建成功:

{
    "device_id": "6274b1d62d5e854503d3a67e_lock",
    "secret": "12345678"
}

4.4 mqtt设备密匙

创建完产品、设备之后,接下来就需要知道如何通过mqtt协议登陆华为云服务器。
官方的详细介绍在这里:

属性上报格式:

mqtt设备登陆密匙生成地址:

deviceid  6274b1d62d5e854503d3a67e_lock
devicesecret 12345678
clientid  6274b1d62d5e854503d3a67e_lock_0_0_2022050605
username  6274b1d62d5e854503d3a67e_lock
password  334dd7c0c10e47280880e9dd004ae0d8c5abc24dbbc9daa735315722707fe13b

4.5 使用mqtt客户端软件登录

所有的参数已经得到,接下来采用mqtt客户端登录华为云进行测试。
华为云物联网平台的域名是: 161a58a78.iot-mqtts.cn-north-4.myhuaweicloud.com
华为云物联网平台的ip地址是:121.36.42.100
在软件里参数填充正确之后,就看到设备已经连接成功了。
接下来打开设备页面,可以看到设备已经在线了。

4.6 数据上报测试

//订阅主题: 平台下发消息给设备
$oc/devices/6274b1d62d5e854503d3a67e_lock/sys/messages/down
//设备上报数据
$oc/devices/6274b1d62d5e854503d3a67e_lock/sys/properties/report
//上报的属性消息 (一次可以上报多个属性,在json里增加就行了)
{"services": [{"service_id": "lock","properties":{"lock":1}}]}



//订阅主题: 平台下发消息给设备
$oc/devices/6274b1d62d5e854503d3a67e_lock/sys/messages/down
//设备上报数据
$oc/devices/6274b1d62d5e854503d3a67e_lock/sys/properties/report
//上报的属性消息 (一次可以上报多个属性,在json里增加就行了)
{"services": [{"service_id": "lock","properties":{"gps信息":"lat:12.345,lng:45.678"}}]}

4.7 应用侧开发

为了更方便的展示设备数据,与设备完成交互,还需要开发一个配套的上位机,官方提供了应用侧开发的api接口、sdk接口,为了方便通用一点,我这里采用了api接口完成数据交互,上位机软件采用qt开发。

帮助文档地址: ttps://support.huaweicloud.com/api-iothub/iot_06_v5_0034.html

设备属性就是设备上传的传感器状态数据信息,应用侧提供了api接口,可以主动向设备端下发请求指令;设备端收到指令之后需要按照约定的数据格式上报数据;所以,要实现应用层与设备端的数据交互,需要应用层与设备端配合才能完成。


5. stm32开发

5.1 adxl345.c

#include "app.h"
/*
函数功能: 各种硬初始化
继电器模块--dat--->pa4
pb12-----输入引脚,检测模块是否连接或者断开
*/
void hardware_init(void)
{
    rcc->apb2enr|=1<<2;
    gpioa->crl&=0xfff0ffff;
    gpioa->crl|=0x00030000;
    
    rcc->apb2enr|=1<<3;
    gpiob->crh&=0xfff0ffff;
    gpiob->crh|=0x00080000;
}
//////////////////////////////////////////////////////////////////////////////////
//初始化adxl345.
//返回值:0,初始化成功;1,初始化失败.
u8 adxl345_init(void)
{				  
	iic_init();							//初始化iic总线	
	if(adxl345_rd_reg(device_id)==0xe5)	//读取器件id
	{  
		adxl345_wr_reg(data_format,0x2b);	//低电平中断输出,13位全分辨率,输出数据右对齐,16g量程 
		adxl345_wr_reg(bw_rate,0x0a);		//数据输出速度为100hz
		adxl345_wr_reg(power_ctl,0x28);	   	//链接使能,测量模式
		adxl345_wr_reg(int_enable,0x00);	//不使用中断		 
	 	adxl345_wr_reg(ofsx,0x00);
		adxl345_wr_reg(ofsy,0x00);
		adxl345_wr_reg(ofsz,0x00);	
		return 0;
	}			
	return 1;	   								  
}   
//写adxl345寄存器
//addr:寄存器地址
//val:要写入的值
//返回值:无
void adxl345_wr_reg(u8 addr,u8 val) 
{
	iic_start();  				 
	iic_send_byte(adxl_write);     	//发送写器件指令	 
	iic_wait_ack();	   
    iic_send_byte(addr);   			//发送寄存器地址
	iic_wait_ack(); 	 										  		   
	iic_send_byte(val);     		//发送值					   
	iic_wait_ack();  		    	   
    iic_stop();						//产生一个停止条件 	   
}
//读adxl345寄存器
//addr:寄存器地址
//返回值:读到的值
u8 adxl345_rd_reg(u8 addr) 		
{
	u8 temp=0;		 
	iic_start();  				 
	iic_send_byte(adxl_write);	//发送写器件指令	 
	temp=iic_wait_ack();	   
    iic_send_byte(addr);   		//发送寄存器地址
	temp=iic_wait_ack(); 	 										  		   
	iic_start();  	 	   		//重新启动
	iic_send_byte(adxl_read);	//发送读器件指令	 
	temp=iic_wait_ack();	   
    temp=iic_read_byte(0);		//读取一个字节,不继续再读,发送nak 	    	   
    iic_stop();					//产生一个停止条件 	    
	return temp;				//返回读到的值
}  
//读取adxl的平均值
//x,y,z:读取10次后取平均值
void adxl345_rd_avval(short *x,short *y,short *z)
{
	short tx=0,ty=0,tz=0;	   
	u8 i;  
	for(i=0;i<10;i)
	{
		adxl345_rd_xyz(x,y,z);
		delay_ms(10);
		tx =(short)*x;
		ty =(short)*y;
		tz =(short)*z;	   
	}
	*x=tx/10;
	*y=ty/10;
	*z=tz/10;
} 
//自动校准
//xval,yval,zval:x,y,z轴的校准值
void adxl345_auto_adjust(char *xval,char *yval,char *zval)
{
	short tx,ty,tz;
	u8 i;
	short offx=0,offy=0,offz=0;
	adxl345_wr_reg(power_ctl,0x00);	   	//先进入休眠模式.
	delay_ms(100);
	adxl345_wr_reg(data_format,0x2b);	//低电平中断输出,13位全分辨率,输出数据右对齐,16g量程 
	adxl345_wr_reg(bw_rate,0x0a);		//数据输出速度为100hz
	adxl345_wr_reg(power_ctl,0x28);	   	//链接使能,测量模式
	adxl345_wr_reg(int_enable,0x00);	//不使用中断		 
	adxl345_wr_reg(ofsx,0x00);
	adxl345_wr_reg(ofsy,0x00);
	adxl345_wr_reg(ofsz,0x00);
	delay_ms(12);
	for(i=0;i<10;i)
	{
		adxl345_rd_avval(&tx,&ty,&tz);
		offx =tx;
		offy =ty;
		offz =tz;
	}	 		
	offx/=10;
	offy/=10;
	offz/=10;
	*xval=-offx/4;
	*yval=-offy/4;
	*zval=-(offz-256)/4;	  
 	adxl345_wr_reg(ofsx,*xval);
	adxl345_wr_reg(ofsy,*yval);
	adxl345_wr_reg(ofsz,*zval);	
} 
//读取3个轴的数据
//x,y,z:读取到的数据
void adxl345_rd_xyz(short *x,short *y,short *z)
{
	u8 buf[6];
	u8 i;
	iic_start();  				 
	iic_send_byte(adxl_write);	//发送写器件指令	 
	iic_wait_ack();	   
    iic_send_byte(0x32);   		//发送寄存器地址(数据缓存的起始地址为0x32)
	iic_wait_ack(); 	 										  		   
 
 	iic_start();  	 	   		//重新启动
	iic_send_byte(adxl_read);	//发送读器件指令
	iic_wait_ack();
	for(i=0;i<6;i)
	{
		if(i==5)buf[i]=iic_read_byte(0);//读取一个字节,不继续再读,发送nack  
		else buf[i]=iic_read_byte(1);	//读取一个字节,继续读,发送ack 
 	}	        	   
    iic_stop();					//产生一个停止条件
	*x=(short)(((u16)buf[1]<<8)buf[0]); 	    
	*y=(short)(((u16)buf[3]<<8)buf[2]); 	    
	*z=(short)(((u16)buf[5]<<8)buf[4]); 	   
}
//读取adxl345的数据times次,再取平均
//x,y,z:读到的数据
//times:读取多少次
void adxl345_read_average(short *x,short *y,short *z,u8 times)
{
	u8 i;
	short tx,ty,tz;
	*x=0;
	*y=0;
	*z=0;
	if(times)//读取次数不为0
	{
		for(i=0;i<times;i)//连续读取times次
		{
			adxl345_rd_xyz(&tx,&ty,&tz);
			*x =tx;
			*y =ty;
			*z =tz;
			delay_ms(5);
		}
		*x/=times;
		*y/=times;
		*z/=times;
	}
}
//得到角度
//x,y,z:x,y,z方向的重力加速度分量(不需要单位,直接数值即可)
//dir:要获得的角度.0,与z轴的角度;1,与x轴的角度;2,与y轴的角度.
//返回值:角度值.单位0.1°.
short adxl345_get_angle(float x,float y,float z,u8 dir)
{
	float temp;
 	float res=0;
	switch(dir)
	{
		case 0://与自然z轴的角度
 			temp=sqrt((x*xy*y))/z;
 			res=atan(temp);
 			break;
		case 1://与自然x轴的角度
 			temp=x/sqrt((y*yz*z));
 			res=atan(temp);
 			break;
 		case 2://与自然y轴的角度
 			temp=y/sqrt((x*xz*z));
 			res=atan(temp);
 			break;
 	}
	return res*1800/3.14;
}
//初始化iic
void iic_init(void)
{					     
    rcc->apb2enr|=1<<3;		  //先使能外设io portb时钟 							 
	gpiob->crl&=0x00ffffff;	//6/7 推挽输出
	gpiob->crl|=0x33000000;	   
	gpiob->odr|=3<<6;     	//6,7 输出高
}
//产生iic起始信号
void iic_start(void)
{
	sda_out();     //sda线输出
	iic_sda=1;	  	  
	iic_scl=1;
	delay_us(4);
 	iic_sda=0;//start:when clk is high,data change form high to low 
	delay_us(4);
	iic_scl=0;//钳住i2c总线,准备发送或接收数据 
}	  
//产生iic停止信号
void iic_stop(void)
{
	sda_out();//sda线输出
	iic_scl=0;
	iic_sda=0;//stop:when clk is high data change form low to high
 	delay_us(4);
	iic_scl=1; 
	iic_sda=1;//发送i2c总线结束信号
	delay_us(4);							   	
}
//等待应答信号到来
//返回值:1,接收应答失败
//        0,接收应答成功
u8 iic_wait_ack(void)
{
	u8 ucerrtime=0;
	sda_in();      //sda设置为输入  
	iic_sda=1;delay_us(1);	   
	iic_scl=1;delay_us(1);	 
	while(read_sda)
	{
		ucerrtime;
		if(ucerrtime>250)
		{
			iic_stop();
			return 1;
		}
	}
	iic_scl=0;//时钟输出0 	   
	return 0;  
} 
//产生ack应答
void iic_ack(void)
{
	iic_scl=0;
	sda_out();
	iic_sda=0;
	delay_us(2);
	iic_scl=1;
	delay_us(2);
	iic_scl=0;
}
//不产生ack应答		    
void iic_nack(void)
{
	iic_scl=0;
	sda_out();
	iic_sda=1;
	delay_us(2);
	iic_scl=1;
	delay_us(2);
	iic_scl=0;
}					 				     
//iic发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答			  
void iic_send_byte(u8 txd)
{                        
    u8 t;   
	sda_out(); 	    
    iic_scl=0;//拉低时钟开始数据传输
    for(t=0;t<8;t)
    {              
        iic_sda=(txd&0x80)>>7;
        txd<<=1; 	  
		delay_us(2);   //对tea5767这三个延时都是必须的
		iic_scl=1;
		delay_us(2); 
		iic_scl=0;	
		delay_us(2);
    }	 
} 	    
//读1个字节,ack=1时,发送ack,ack=0,发送nack   
u8 iic_read_byte(unsigned char ack)
{
	unsigned char i,receive=0;
	sda_in();//sda设置为输入
    for(i=0;i<8;i )
	{
        iic_scl=0; 
        delay_us(2);
		iic_scl=1;
        receive<<=1;
        if(read_sda)receive;   
		delay_us(1); 
    }					 
    if (!ack)
        iic_nack();//发送nack
    else
        iic_ack(); //发送ack   
    return receive;
}

5.2 sim800.c

#include "sim800c.h"
/*
函数功能:向sim800c模块发送指令
函数参数:
				char *cmd  发送的命令
			  char *check_data 检测返回的数据
返回值: 0表示成功 1表示失败
*/
u8 sim800c_sendcmd(char *cmd,char *check_data)
{
   u16 i,j;
   for(i=0;i<5;i) //测试的总次数
   {
      usart2_rx_flag=0;
      usart2_rx_cnt=0;
			memset(usart2_rx_buffer,0,sizeof(usart2_rx_buffer));
			usartx_stringsend(usart2,cmd); //发送指令
      for(j=0;j<500;j) //等待的时间(ms单位)
      {
          if(usart2_rx_flag)
          {
              usart2_rx_buffer[usart2_rx_cnt]='\0';
              if(strstr((char*)usart2_rx_buffer,check_data))
              {
                  return 0;
              }
              else break;
          }
          delay_ms(20); //一次的时间
      }
   }
   return 1;
}
/*
函数  功能:gsm模块初始化检测
函数返回值:1表示模块检测失败,0表示成功
*/
u8 sim800c_initcheck(void)
{
	  if(sim800c_sendcmd("at\r\n","ok"))return 1;
	  else printf("sim800模块正常!\r\n");
	  
		if(sim800c_sendcmd("ate0\r\n","ok"))return 2;
	  else printf("设置模块不回显成功!\r\n");
	
		if(sim800c_sendcmd("at cgmi\r\n","ok"))return 3;
		else printf("查询制造商名称成功!%s\r\n",usart2_rx_buffer);
	
		if(sim800c_sendcmd("at cgmm\r\n","ok"))return 4;
		else printf("查询模块型号成功!%s\r\n",usart2_rx_buffer);
		
	    delayms(1000);
		delayms(1000);
		if(sim800c_sendcmd("at cnum\r\n"," cnum:"))return 5;
		else printf("获取本机号码成功!%s\r\n",usart2_rx_buffer);
	  /* 返回格式如下:
		 cnum: ""," 8613086989413",145,7,4
		ok
		*/
		return 0;
}
/*
函数  功能:gsm模块短信模式设置
函数返回值:0表示模块设置成功
*/
u8 sim800c_setnotetextmode(void)
{
		if(sim800c_sendcmd("at cscs=\"gsm\"\r\n","ok"))return 1;// "gsm"字符集
		else printf("短信gsm字符集设置成功!\r\n");
	
	  if(sim800c_sendcmd("at cmgf=1\r\n","ok"))return 2; //文本模式
		else printf("短信文本模式设置成功!\r\n");
		return 0;
}
/*
函数功能:发送短信
函数参数:
					num:电话号码
					text:短信内容
函数返回值:0表示发送成功
*/
u8 sim800c_sendnote(u8 *num,u8 *text,u16 len)
{
    char data[50];
    char send_buf[2];
    sprintf(data,"at cmgs=\"%s\"\r\n",num);
    if(sim800c_sendcmd(data,">"))return 1; //设置发送的手机号
    usartx_datasend(usart2,text,len);     //发送短信内容
    send_buf[0] = 0x1a;
    send_buf[1] = '\0';
    if(sim800c_sendcmd(send_buf," cmgs"))return 2; //发送结束符号
    return 0;
}

5.3 mqtt信息

//华为物联网服务器的设备信息
#define mqtt_clientid "62381267575fb713ee164ad2_xl_1_0_0_2022032106"
#define mqtt_username "62381267575fb713ee164ad2_xl_1"
#define mqtt_password "124344feff3e3d96ff6af13cf36af36766619ff1eeee40e99cbae9b7b9739fe4"
//订阅与发布的主题
#define set_topic  "$oc/devices/62381267575fb713ee164ad2_xl_1/sys/messages/down"  //订阅
#define post_topic "$oc/devices/62381267575fb713ee164ad2_xl_1/sys/properties/report"  //发布
//设置连接的路由器信息
#define connect_wifi  "abc"   //将要连接的路由器名称 --不要出现中文、空格等特殊字符
#define connect_pass "1234567890"       //将要连接的路由器密码
#define connect_server_ip "a161a58a78.iot-mqtts.cn-north-4.myhuaweicloud.com"   //服务器ip地址
#define connect_server_port 1883            //服务器端口号
u8 *mqtt_rxbuf;
u8 *mqtt_txbuf;
u16 mqtt_rxlen;
u16 mqtt_txlen;
u8 _mqtt_txbuf[256];//发送数据缓存区
u8 _mqtt_rxbuf[256];//接收数据缓存区
typedef enum
{
	//名字 	    值 			报文流动方向 	描述
	m_reserved1	=0	,	//	禁止	保留
	m_connect		,	//	客户端到服务端	客户端请求连接服务端
	m_connack		,	//	服务端到客户端	连接报文确认
	m_publish		,	//	两个方向都允许	发布消息
	m_puback		,	//	两个方向都允许	qos 1消息发布收到确认
	m_pubrec		,	//	两个方向都允许	发布收到(保证交付第一步)
	m_pubrel		,	//	两个方向都允许	发布释放(保证交付第二步)
	m_pubcomp		,	//	两个方向都允许	qos 2消息发布完成(保证交互第三步)
	m_subscribe		,	//	客户端到服务端	客户端订阅请求
	m_suback		,	//	服务端到客户端	订阅请求报文确认
	m_unsubscribe	,	//	客户端到服务端	客户端取消订阅请求
	m_unsuback		,	//	服务端到客户端	取消订阅报文确认
	m_pingreq		,	//	客户端到服务端	心跳请求
	m_pingresp		,	//	服务端到客户端	心跳响应
	m_disconnect	,	//	客户端到服务端	客户端断开连接
	m_reserved2		,	//	禁止	保留
}_typdef_mqtt_message;
//连接成功服务器回应 20 02 00 00
//客户端主动断开连接 e0 00
const u8 parket_connetack[] = {0x20,0x02,0x00,0x00};
const u8 parket_disconnet[] = {0xe0,0x00};
const u8 parket_heart[] = {0xc0,0x00};
const u8 parket_heart_reply[] = {0xc0,0x00};
const u8 parket_suback[] = {0x90,0x03};
void mqtt_init(void)
{
    //缓冲区赋值
	mqtt_rxbuf = _mqtt_rxbuf;
    mqtt_rxlen = sizeof(_mqtt_rxbuf);
	mqtt_txbuf = _mqtt_txbuf;
    mqtt_txlen = sizeof(_mqtt_txbuf);
	memset(mqtt_rxbuf,0,mqtt_rxlen);
	memset(mqtt_txbuf,0,mqtt_txlen);
	
	//无条件先主动断开
	mqtt_disconnect();
    delay_ms(100);
	mqtt_disconnect();
    delay_ms(100);
}
/*
函数功能: 登录服务器
函数返回值: 0表示成功 1表示失败
*/
u8 mqtt_connect(char *clientid,char *username,char *password)
{
    u8 i,j;
    int clientidlen = strlen(clientid);
    int usernamelen = strlen(username);
    int passwordlen = strlen(password);
    int datalen;
	mqtt_txlen=0;
	//可变报头 payload  每个字段包含两个字节的长度标识
    datalen = 10  (clientidlen2)  (usernamelen2)  (passwordlen2);
	
	//固定报头
	//控制报文类型
    mqtt_txbuf[mqtt_txlen] = 0x10;		//mqtt message type connect
	//剩余长度(不包括固定头部)
	do
	{
		u8 encodedbyte = datalen % 128;
		datalen = datalen / 128;
		// if there are more data to encode, set the top bit of this byte
		if ( datalen > 0 )
			encodedbyte = encodedbyte | 128;
		mqtt_txbuf[mqtt_txlen] = encodedbyte;
	}while ( datalen > 0 );
    	
	//可变报头
	//协议名
    mqtt_txbuf[mqtt_txlen] = 0;        	// protocol name length msb    
    mqtt_txbuf[mqtt_txlen] = 4;           // protocol name length lsb    
    mqtt_txbuf[mqtt_txlen] = 'm';        	// ascii code for m    
    mqtt_txbuf[mqtt_txlen] = 'q';        	// ascii code for q    
    mqtt_txbuf[mqtt_txlen] = 't';        	// ascii code for t    
    mqtt_txbuf[mqtt_txlen] = 't';        	// ascii code for t    
	//协议级别
    mqtt_txbuf[mqtt_txlen] = 4;        		// mqtt protocol version = 4   对于 3.1.1 版协议,协议级别字段的值是 4(0x04)   
	//连接标志
    mqtt_txbuf[mqtt_txlen] = 0xc2;        	// conn flags 
    mqtt_txbuf[mqtt_txlen] = 0;        		// keep-alive time length msb    
    mqtt_txbuf[mqtt_txlen] = 100;        	// keep-alive time length lsb  100s心跳包    保活时间
	
    mqtt_txbuf[mqtt_txlen] = byte1(clientidlen);// client id length msb    
    mqtt_txbuf[mqtt_txlen] = byte0(clientidlen);// client id length lsb  	
	memcpy(&mqtt_txbuf[mqtt_txlen],clientid,clientidlen);
    mqtt_txlen  = clientidlen;
    
    if(usernamelen > 0)
    {   
        mqtt_txbuf[mqtt_txlen] = byte1(usernamelen);		//username length msb    
        mqtt_txbuf[mqtt_txlen] = byte0(usernamelen);    	//username length lsb    
		memcpy(&mqtt_txbuf[mqtt_txlen],username,usernamelen);
        mqtt_txlen  = usernamelen;
    }
    
    if(passwordlen > 0)
    {    
        mqtt_txbuf[mqtt_txlen] = byte1(passwordlen);		//password length msb    
        mqtt_txbuf[mqtt_txlen] = byte0(passwordlen);    	//password length lsb  
		memcpy(&mqtt_txbuf[mqtt_txlen],password,passwordlen);
        mqtt_txlen  = passwordlen; 
    }    
	
  
    memset(mqtt_rxbuf,0,mqtt_rxlen);
    mqtt_sendbuf(mqtt_txbuf,mqtt_txlen);
    for(j=0;j<10;j)
    {
        delay_ms(50);
        if(usart2_rx_flag)
        {
            memcpy((char *)mqtt_rxbuf,usart2_rx_buffer,usart2_rx_cnt);
            
            //memcpy
           
             for(i=0;i<usart2_rx_cnt;i)printf("%#x ",usart2_rx_buffer[i]);
            
            usart2_rx_flag=0;
            usart2_rx_cnt=0;
        }
        //connect
        if(mqtt_rxbuf[0]==parket_connetack[0] && mqtt_rxbuf[1]==parket_connetack[1]) //连接成功			   
        {
            return 0;//连接成功
        }
    }
    
	return 1;
}
/*
函数功能: mqtt订阅/取消订阅数据打包函数
函数参数:
    topic       主题   
    qos         消息等级 0:最多分发一次  1: 至少分发一次  2: 仅分发一次
    whether     订阅/取消订阅请求包 (1表示订阅,0表示取消订阅)
返回值: 0表示成功 1表示失败
*/
u8 mqtt_subscribetopic(char *topic,u8 qos,u8 whether)
{    
    u8 i,j;
	mqtt_txlen=0;
    int topiclen = strlen(topic);
	
	int datalen = 2  (topiclen2)  (whether?1:0);//可变报头的长度(2字节)加上有效载荷的长度
	//固定报头
	//控制报文类型
    if(whether)mqtt_txbuf[mqtt_txlen] = 0x82; //消息类型和标志订阅
    else	mqtt_txbuf[mqtt_txlen] = 0xa2;    //取消订阅
	//剩余长度
	do
	{
		u8 encodedbyte = datalen % 128;
		datalen = datalen / 128;
		// if there are more data to encode, set the top bit of this byte
		if ( datalen > 0 )
			encodedbyte = encodedbyte | 128;
		mqtt_txbuf[mqtt_txlen] = encodedbyte;
	}while ( datalen > 0 );	
	
	//可变报头
    mqtt_txbuf[mqtt_txlen] = 0;			//消息标识符 msb
    mqtt_txbuf[mqtt_txlen] = 0x0a;        //消息标识符 lsb
	//有效载荷
    mqtt_txbuf[mqtt_txlen] = byte1(topiclen);//主题长度 msb
    mqtt_txbuf[mqtt_txlen] = byte0(topiclen);//主题长度 lsb   
	memcpy(&mqtt_txbuf[mqtt_txlen],topic,topiclen);
    mqtt_txlen  = topiclen;
    
    if(whether)
    {
       mqtt_txbuf[mqtt_txlen] = qos;//qos级别
    }
    
    for(i=0;i<10;i)
    {
        memset(mqtt_rxbuf,0,mqtt_rxlen);
		mqtt_sendbuf(mqtt_txbuf,mqtt_txlen);
        for(j=0;j<10;j)
        {
            delay_ms(50);
            if(usart2_rx_flag)
			{
                memcpy((char *)mqtt_rxbuf,(char*)usart2_rx_buffer,usart2_rx_cnt);
				usart2_rx_flag=0;
				usart2_rx_cnt=0;
			}
			
			if(mqtt_rxbuf[0]==parket_suback[0] && mqtt_rxbuf[1]==parket_suback[1]) //订阅成功			   
			{
				return 0;//订阅成功
			}
        }
    }
	return 1; //失败
}
//mqtt发布数据打包函数
//topic   主题 
//message 消息
//qos     消息等级 
u8 mqtt_publishdata(char *topic, char *message, u8 qos)
{  
    int topiclength = strlen(topic);    
    int messagelength = strlen(message);     
    static u16 id=0;
	int datalen;
	mqtt_txlen=0;
	//有效载荷的长度这样计算:用固定报头中的剩余长度字段的值减去可变报头的长度
	//qos为0时没有标识符
	//数据长度             主题名   报文标识符   有效载荷
    if(qos)	datalen = (2topiclength)  2  messagelength;       
    else	datalen = (2topiclength)  messagelength;   
    //固定报头
	//控制报文类型
    mqtt_txbuf[mqtt_txlen] = 0x30;    // mqtt message type publish  
	//剩余长度
	do
	{
		u8 encodedbyte = datalen % 128;
		datalen = datalen / 128;
		// if there are more data to encode, set the top bit of this byte
		if ( datalen > 0 )
			encodedbyte = encodedbyte | 128;
		mqtt_txbuf[mqtt_txlen] = encodedbyte;
	}while ( datalen > 0 );	
	
    mqtt_txbuf[mqtt_txlen] = byte1(topiclength);//主题长度msb
    mqtt_txbuf[mqtt_txlen] = byte0(topiclength);//主题长度lsb 
	memcpy(&mqtt_txbuf[mqtt_txlen],topic,topiclength);//拷贝主题
    mqtt_txlen  = topiclength;
        
	//报文标识符
    if(qos)
    {
        mqtt_txbuf[mqtt_txlen] = byte1(id);
        mqtt_txbuf[mqtt_txlen] = byte0(id);
        id;
    }
	memcpy(&mqtt_txbuf[mqtt_txlen],message,messagelength);
    mqtt_txlen  = messagelength;
        
	mqtt_sendbuf(mqtt_txbuf,mqtt_txlen);
    return mqtt_txlen;
}
【4008云顶国际集团的版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区),文章链接,文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。