:本文提出一种基于STM32单片机的智能自行车锁(马蹄锁)的设计方法,来提高自行车锁的智能化及安防等级。 本文分享自华为云社区《基于STM32+华为云IOT设计的智能防盗单车锁【玩转华为云…

本文分享自华为云社区《基于STM32+华为云IOT设计的智能防盗单车锁【玩转华为云】》,作者: DS小龙哥 。

一、前言

近年来随着国民经济的发展,交通拥堵和环境污染问题越来越突出,而自行车对改善交通与环境起到了重要作用。中国本身是一个自行车使用大国,随着自行车的发展,自行车的科技含量越来越高,然而自行车安防问题突出。目前市场上自行车锁大多是传统机械结构车锁,没有实现智能化,急需解决。本文提出一种基于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直接给模块供电。不过一般电脑或者开发板的功率有限,可能会不稳定。请根据具体情况自己取舍选择合适电源。

四. 手机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 注册设备

设备创建成功:

1
2
3
4
  {
"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设备登陆密匙生成地址:

1
2
3
4
5
  DeviceId  6274b1d62d5e854503d3a67e_lock
DeviceSecret 12345678
ClientId 6274b1d62d5e854503d3a67e_lock_0_0_2022050605
Username 6274b1d62d5e854503d3a67e_lock
Password 334dd7c0c10e47280880e9dd004ae0d8c5abc24dbbc9daa735315722707fe13b
4.5 使用MQTT客户端软件登录

所有的参数已经得到,接下来采用MQTT客户端登录华为云进行测试。 华为云物联网平台的域名是: 

1
161a58a78.iot-mqtts.cn-north-4.myhuaweicloud.com

华为云物联网平台的IP地址是:
1
121.36.42.100

在软件里参数填充正确之后,就看到设备已经连接成功了。 接下来打开设备页面,可以看到设备已经在线了。

4.6 数据上报测试
1
2
3
4
5
6
  //订阅主题: 平台下发消息给设备
$oc/devices/6274b1d62d5e854503d3a67e_lock/sys/messages/down
//设备上报数据
$oc/devices/6274b1d62d5e854503d3a67e_lock/sys/properties/report
//上报的属性消息 (一次可以上报多个属性,在json里增加就行了)
{"services": [{"service_id": "lock","properties":{"lock":1}}]}
1
2
3
4
5
6
  //订阅主题: 平台下发消息给设备
$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开发。
帮助文档地址: https://support.huaweicloud.com/api-iothub/iot_06_v5_0034.html
设备属性就是设备���传的传感器状态数据信息,应用侧提供了API接口,可以主动向设备端下发请求指令;设备端收到指令之后需要按照约定的数据格式上报数据;所以,要实现应用层与设备端的数据交互,需要应用层与设备端配合才能完成。

5. STM32开发

5.1 ADXL345.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
  #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*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;
}

//初始化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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
  #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信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
  //华为物联网服务器的设备信息
#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 + (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;
}

更多学习内容,请关注IoT物联网社区,添加华为云IoT小助手微信号(hwc-iot),回复“阅读”获取更多资讯
 
点击关注,第一时间了解华为云新鲜技术~

本文标题: 推荐系列-为了让女朋友运动起来,小伙儿不仅买单车还设计了智能防盗单车锁

本文作者: OSChina

发布时间: 2022年05月11日 05:14

最后更新: 2023年06月29日 07:10

原始链接: https://haoxiang.eu.org/592c11cd/

版权声明: 本文著作权归作者所有,均采用CC BY-NC-SA 4.0许可协议,转载请注明出处!

× 喜欢就赞赏一下呗!
打赏二维码