為了讓女朋友運動起來,小夥兒不僅買單車還設計了智慧防盜單車鎖
摘要:本文提出一種基於STM32微控制器的智慧腳踏車鎖(馬蹄鎖)的設計方法,來提高腳踏車鎖的智慧化及安防等級。
本文分享自華為雲社群《基於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官網: http://www.qt.io/
QT學習入門實戰專欄文章: http://blog.csdn.net/xiaolong1126626497/category_11400392.html
QT5.12.6的下載地址:
http://download.qt.io/archive/qt/5.12/5.12.6/
4、建立雲端裝置
4.1 建立產品
登入官網: http://www.huaweicloud.com/product/iothub.html
直接搜尋物聯網,開啟頁面。
4.2 自定義模型
4.3 註冊裝置
裝置建立成功:
{
"device_id": "6274b1d62d5e854503d3a67e_lock",
"secret": "12345678"
}
4.4 MQTT裝置密匙
建立完產品、裝置之後,接下來就需要知道如何通過MQTT協議登陸華為雲伺服器。
屬性上報格式:
http://support.huaweicloud.com/api-iothub/iot_06_v5_3010.html
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開發。
幫助文件地址: http://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*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
#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 + (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),回覆“閱讀”獲取更多資訊
- 帶你掌握 C 中三種類成員初始化方式
- 實踐GoF的設計模式:工廠方法模式
- DCM:一個能夠改善所有應用資料互動場景的中介軟體新秀
- 手繪圖解java類載入原理
- 關於加密通道規範,你真正用的是TLS,而非SSL
- 程式碼重構,真的只有複雜化一條路嗎?
- 解讀分散式排程平臺Airflow在華為雲MRS中的實踐
- 透過例項demo帶你認識gRPC
- 帶你聚焦GaussDB(DWS)儲存時遊標使用
- 傳統到敏捷的轉型中,誰更適合做Scrum Master?
- 輕鬆解決研發知識管理難題
- Java中觀察者模式與委託,還在傻傻分不清
- 如何使用Python實現影象融合及加法運算?
- 什麼是強化學習?
- 探索開源工作流引擎Azkaban在MRS中的實踐
- GaussDB(DWS) NOT IN優化技術解密:排他分析場景400倍效能提升
- Java中觀察者模式與委託,還在傻傻分不清
- Java中的執行緒到底有哪些安全策略
- 一圖詳解java-class類檔案原理
- Java中的執行緒到底有哪些安全策略