写点什么

基于 STM32+ 华为云 IOT 设计的智能防盗单车锁

作者:DS小龙哥
  • 2022 年 7 月 05 日
  • 本文字数:12199 字

    阅读完需:约 40 分钟

一、前言

近年来随着国民经济的发展,交通拥堵和环境污染问题越来越突出,而自行车对改善交通与环境起到了重要作用。中国本身是一个自行车使用大国,随着自行车的发展,自行车的科技含量越来越高,然而自行车安防问题突出。目前市场上自行车锁大多是传统机械结构车锁,没有实现智能化,急需解决。本文提出一种基于 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 可以很轻松的帮你做带界面的软件,甚至不需要你投入很大精力。


QT 官网: https://www.qt.io/



QT 学习入门实战专栏文章: https://blog.csdn.net/xiaolong1126626497/category_11400392.html


QT5.12.6 的下载地址:https://download.qt.io/archive/qt/5.12/5.12.6/

4. 创建云端设备

4.1 创建产品

登录官网: https://www.huaweicloud.com/product/iothub.html


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





4.2 自定义模型







4.3 注册设备




设备创建成功:


{    "device_id": "6274b1d62d5e854503d3a67e_lock",    "secret": "12345678"}
复制代码

4.4 MQTT 设备密匙

创建完产品、设备之后,接下来就需要知道如何通过 MQTT 协议登陆华为云服务器。官方的详细介绍在这里:https://support.huaweicloud.com/devg-iothub/iot_01_2127.html#ZH-CN_TOPIC_0240834853__zh-cn_topic_0251997880_li365284516112


属性上报格式:https://support.huaweicloud.com/api-iothub/iot_06_v5_3010.html




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



DeviceId  6274b1d62d5e854503d3a67e_lockDeviceSecret 12345678ClientId  6274b1d62d5e854503d3a67e_lock_0_0_2022050605Username  6274b1d62d5e854503d3a67e_lockPassword  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 开发

STM32 连接华为云 IOT 的工程代码 Get: https://download.csdn.net/download/xiaolong1126626497/81993720

5.1 ADXL345.c

#include "app.h"
/*函数功能: 各种硬初始化继电器模块--DAT--->PA4PB12-----输入引脚,检测模块是否连接或者断开
*/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*x+y*y))/z; res=atan(temp); break; case 1://与自然X轴的角度 temp=x/sqrt((y*y+z*z)); res=atan(temp); break; case 2://与自然Y轴的角度 temp=y/sqrt((x*x+z*z)); res=atan(temp); break; } return res*1800/3.14;}
//初始化IICvoid 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 00const 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 + (ClientIDLen+2) + (UsernameLen+2) + (PasswordLen+2); //固定报头 //控制报文类型 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 + (topiclen+2) + (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 = (2+topicLength) + 2 + messageLength; else DataLen = (2+topicLength) + 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;}
复制代码


发布于: 2022 年 07 月 05 日阅读数: 21
用户头像

DS小龙哥

关注

之所以觉得累,是因为说的比做的多。 2022.01.06 加入

熟悉C/C++、51单片机、STM32、Linux应用开发、Linux驱动开发、音视频开发、QT开发. 目前已经完成的项目涉及音视频、物联网、智能家居、工业控制领域

评论

发布
暂无评论
基于STM32+华为云IOT设计的智能防盗单车锁_7月月更_DS小龙哥_InfoQ写作社区