?
一、環(huán)境介紹
上網(wǎng)方式:采用ESP8266,也可以使用其他設(shè)備代替,只要支持TCP協(xié)議即可。比如:GSM模塊、有線網(wǎng)卡等。
開發(fā)軟件:keil5
硬件連接功能:ESP8266接在STM32的串口3上。通過AT指令與ESP8266進(jìn)行通信。
通過阿里云物聯(lián)網(wǎng)服務(wù)器實(shí)現(xiàn)設(shè)備數(shù)據(jù)遠(yuǎn)程上傳、下發(fā),實(shí)現(xiàn)數(shù)據(jù)交互。
在當(dāng)前使用的開發(fā)板上有4盞LED燈、一個(gè)蜂鳴器、4個(gè)按鍵。
實(shí)現(xiàn)步驟阿里云官方提供了很詳細(xì)的文檔和對(duì)應(yīng)的SDK,可以參考一下。
文檔地址:阿里云登錄 - 歡迎登錄阿里云,安全穩(wěn)定的云計(jì)算服務(wù)平臺(tái)
?
?
?
?
?
?
三、阿里云物聯(lián)網(wǎng)服務(wù)器創(chuàng)建步驟
?
?
說明:如果沒有賬號(hào)的話,先點(diǎn)擊網(wǎng)頁右上角,注冊(cè)一個(gè)賬號(hào),并完成實(shí)名認(rèn)證再繼續(xù)下一步。
?
?
產(chǎn)品名稱根據(jù)自己情況填寫。
?
?
?
設(shè)備信息根據(jù)自己情況填寫。
?
?
?
?
?
?
?
?
下面參數(shù)根據(jù)自己情況填寫。
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
四、向服務(wù)器上傳的數(shù)據(jù)效果
完成網(wǎng)頁端服務(wù)器的創(chuàng)建之后,下面使用STM32開發(fā)板按下按鍵通過ESP8266將煙霧傳感器數(shù)據(jù)上傳到阿里云服務(wù)器。
如果連接成功的話,網(wǎng)頁會(huì)顯示在線狀態(tài)。
?
上傳的數(shù)據(jù)可以在這里查看。
?
?
五、STM32端的MQTT協(xié)議核心代碼
代碼是標(biāo)準(zhǔn)的MQTT協(xié)議代碼,實(shí)現(xiàn)過程可以參考MQTT協(xié)議官方文檔。
?
5.1 mqtt.c代碼
#include "aliyun_mqtt.h"
char MQTT_ClientID[100]; //MQTT_客戶端ID
char MQTT_UserName[100]; //MQTT_用戶名
char MQTT_PassWord[100]; //MQTT_密碼
u8 *mqtt_rxbuf;
u8 *mqtt_txbuf;
u16 mqtt_rxlen;
u16 mqtt_txlen;
u8 _mqtt_txbuf[256];//發(fā)送數(shù)據(jù)緩存區(qū)
u8 _mqtt_rxbuf[256];//接收數(shù)據(jù)緩存區(qū)
typedef enum
{
//名字 值 報(bào)文流動(dòng)方向 描述
M_RESERVED1 =0 , // 禁止 保留
M_CONNECT , // 客戶端到服務(wù)端 客戶端請(qǐng)求連接服務(wù)端
M_CONNACK , // 服務(wù)端到客戶端 連接報(bào)文確認(rèn)
M_PUBLISH , // 兩個(gè)方向都允許 發(fā)布消息
M_PUBACK , // 兩個(gè)方向都允許 QoS 1消息發(fā)布收到確認(rèn)
M_PUBREC , // 兩個(gè)方向都允許 發(fā)布收到(保證交付第一步)
M_PUBREL , // 兩個(gè)方向都允許 發(fā)布釋放(保證交付第二步)
M_PUBCOMP , // 兩個(gè)方向都允許 QoS 2消息發(fā)布完成(保證交互第三步)
M_SUBSCRIBE , // 客戶端到服務(wù)端 客戶端訂閱請(qǐng)求
M_SUBACK , // 服務(wù)端到客戶端 訂閱請(qǐng)求報(bào)文確認(rèn)
M_UNSUBSCRIBE , // 客戶端到服務(wù)端 客戶端取消訂閱請(qǐng)求
M_UNSUBACK , // 服務(wù)端到客戶端 取消訂閱報(bào)文確認(rèn)
M_PINGREQ , // 客戶端到服務(wù)端 心跳請(qǐng)求
M_PINGRESP , // 服務(wù)端到客戶端 心跳響應(yīng)
M_DISCONNECT , // 客戶端到服務(wù)端 客戶端斷開連接
M_RESERVED2 , // 禁止 保留
}_typdef_mqtt_message;
//連接成功服務(wù)器回應(yīng) 20 02 00 00
//客戶端主動(dòng)斷開連接 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};
/*
函數(shù)功能: 初始化阿里云物聯(lián)網(wǎng)服務(wù)器的登錄參數(shù)
*/
//密碼
//clientId*deviceName*productKey#
// *替換為DeviceName #替換為ProductKey 加密密鑰是DeviceSecret 加密方式是HmacSHA1
//PassWord明文=clientIdmq2_iotdeviceNamemq2_iotproductKeya1WLC5GuOfx
//hmacsha1加密網(wǎng)站:http://encode.chahuo.com/
//加密的密鑰:DeviceSecret
void Aliyun_LoginInit(char *ProductKey,char *DeviceName,char *DeviceSecret)
{
sprintf(MQTT_ClientID,"%s|securemode=3,signmethod=hmacsha1|",DeviceName);
sprintf(MQTT_UserName,"%s&%s",DeviceName,ProductKey);
sprintf(MQTT_PassWord,"%s","ebc042f42a9d73ba9ead8456b652e7756895b79d");
}
void MQTT_Init(void)
{
//緩沖區(qū)賦值
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);
//無條件先主動(dòng)斷開
MQTT_Disconnect();
delay_ms(100);
MQTT_Disconnect();
delay_ms(100);
}
/*
函數(shù)功能: 登錄服務(wù)器
函數(shù)返回值: 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;
//可變報(bào)頭+Payload 每個(gè)字段包含兩個(gè)字節(jié)的長(zhǎng)度標(biāo)識(shí)
DataLen = 10 + (ClientIDLen+2) + (UsernameLen+2) + (PasswordLen+2);
//固定報(bào)頭
//控制報(bào)文類型
mqtt_txbuf[mqtt_txlen++] = 0x10; //MQTT Message Type CONNECT
//剩余長(zhǎng)度(不包括固定頭部)
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 );
//可變報(bào)頭
//協(xié)議名
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
//協(xié)議級(jí)別
mqtt_txbuf[mqtt_txlen++] = 4; // MQTT Protocol version = 4
//連接標(biāo)志
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;
}
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(USART3_RX_FLAG)
{
USART3_RX_BUFFER[USART3_RX_CNT]='\0';
sprintf((char *)mqtt_rxbuf,"%s",USART3_RX_BUFFER);
USART3_RX_FLAG=0;
USART3_RX_CNT=0;
}
//CONNECT
if(mqtt_rxbuf[0]==parket_connetAck[0] && mqtt_rxbuf[1]==parket_connetAck[1]) //連接成功
{
return 0;//連接成功
}
}
}
return 1;
}
/*
函數(shù)功能: MQTT訂閱/取消訂閱數(shù)據(jù)打包函數(shù)
函數(shù)參數(shù):
topic 主題
qos 消息等級(jí) 0:最多分發(fā)一次 1: 至少分發(fā)一次 2: 僅分發(fā)一次
whether 訂閱/取消訂閱請(qǐng)求包 (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);//可變報(bào)頭的長(zhǎng)度(2字節(jié))加上有效載荷的長(zhǎng)度
//固定報(bào)頭
//控制報(bào)文類型
if(whether)mqtt_txbuf[mqtt_txlen++] = 0x82; //消息類型和標(biāo)志訂閱
else mqtt_txbuf[mqtt_txlen++] = 0xA2; //取消訂閱
//剩余長(zhǎng)度
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 );
//可變報(bào)頭
mqtt_txbuf[mqtt_txlen++] = 0; //消息標(biāo)識(shí)符 MSB
mqtt_txbuf[mqtt_txlen++] = 0x01; //消息標(biāo)識(shí)符 LSB
//有效載荷
mqtt_txbuf[mqtt_txlen++] = BYTE1(topiclen);//主題長(zhǎng)度 MSB
mqtt_txbuf[mqtt_txlen++] = BYTE0(topiclen);//主題長(zhǎng)度 LSB
memcpy(&mqtt_txbuf[mqtt_txlen],topic,topiclen);
mqtt_txlen += topiclen;
if(whether)
{
mqtt_txbuf[mqtt_txlen++] = qos;//QoS級(jí)別
}
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(USART3_RX_FLAG)
{
USART3_RX_BUFFER[USART3_RX_CNT]='\0';
strcpy((char *)mqtt_rxbuf,(char*)USART3_RX_BUFFER);
USART3_RX_FLAG=0;
USART3_RX_CNT=0;
}
if(mqtt_rxbuf[0]==parket_subAck[0] && mqtt_rxbuf[1]==parket_subAck[1]) //訂閱成功
{
return 0;//訂閱成功
}
}
}
return 1; //失敗
}
//MQTT發(fā)布數(shù)據(jù)打包函數(shù)
//topic 主題
//message 消息
//qos 消息等級(jí)
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;
//有效載荷的長(zhǎng)度這樣計(jì)算:用固定報(bào)頭中的剩余長(zhǎng)度字段的值減去可變報(bào)頭的長(zhǎng)度
//QOS為0時(shí)沒有標(biāo)識(shí)符
//數(shù)據(jù)長(zhǎng)度 主題名 報(bào)文標(biāo)識(shí)符 有效載荷
if(qos) DataLen = (2+topicLength) + 2 + messageLength;
else DataLen = (2+topicLength) + messageLength;
//固定報(bào)頭
//控制報(bào)文類型
mqtt_txbuf[mqtt_txlen++] = 0x30; // MQTT Message Type PUBLISH
//剩余長(zhǎng)度
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);//主題長(zhǎng)度MSB
mqtt_txbuf[mqtt_txlen++] = BYTE0(topicLength);//主題長(zhǎng)度LSB
memcpy(&mqtt_txbuf[mqtt_txlen],topic,topicLength);//拷貝主題
mqtt_txlen += topicLength;
//報(bào)文標(biāo)識(shí)符
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;
}
void MQTT_SentHeart(void)
{
MQTT_SendBuf((u8 *)parket_heart,sizeof(parket_heart));
}
void MQTT_Disconnect(void)
{
MQTT_SendBuf((u8 *)parket_disconnet,sizeof(parket_disconnet));
}
void MQTT_SendBuf(u8 *buf,u16 len)
{
USARTx_DataSend(USART3,buf,len);
}
5.2 mqtt.h代碼
#ifndef __FY_MQTT_H_
#define __FY_MQTT_H_
#include "stm32f10x.h"
#include "string.h"
#include "stdio.h"
#include "stdlib.h"
#include "stdarg.h"
#include "delay.h"
#include "usart.h"
#define BYTE0(dwTemp) (*( char *)(&dwTemp))
#define BYTE1(dwTemp) (*((char *)(&dwTemp) + 1))
#define BYTE2(dwTemp) (*((char *)(&dwTemp) + 2))
#define BYTE3(dwTemp) (*((char *)(&dwTemp) + 3))
extern char MQTT_ClientID[100]; //MQTT_客戶端ID
extern char MQTT_UserName[100]; //MQTT_用戶名
extern char MQTT_PassWord[100]; //MQTT_密碼
//阿里云用戶名初始化
void Aliyun_LoginInit(char *ProductKey,char *DeviceName,char *DeviceSecret);
//MQTT協(xié)議相關(guān)函數(shù)聲明
u8 MQTT_PublishData(char *topic, char *message, u8 qos);
u8 MQTT_SubscribeTopic(char *topic,u8 qos,u8 whether);
void MQTT_Init(void);
u8 MQTT_Connect(char *ClientID,char *Username,char *Password);
void MQTT_SentHeart(void);
void MQTT_Disconnect(void);
void MQTT_SendBuf(u8 *buf,u16 len);
#endif
5.3 main.c代碼
#include "stm32f10x.h"
#include "led.h"
#include "delay.h"
#include "key.h"
#include "usart.h"
#include
#include "timer.h"
#include "bluetooth.h"
#include "esp8266.h"
#include "aliyun_mqtt.h"
//阿里云物聯(lián)網(wǎng)服務(wù)器的設(shè)備證書
#define ProductKey "a1GLM2BFQK0"
#define DeviceName "iot_mq2"
#define DeviceSecret "caa5b2684101072f3dfe0f04688e0a7f"
//訂閱與發(fā)布的主題
#define SET_TOPIC "/sys/a1GLM2BFQK0/iot_mq2/thing/service/property/set"
#define POST_TOPIC "/sys/a1GLM2BFQK0/iot_mq2/thing/event/property/post"
char mqtt_message[200];//上報(bào)數(shù)據(jù)緩存區(qū)
int main()
{
u32 time_cnt=0;
u32 i;
u8 key;
LED_Init();
BEEP_Init();
KEY_Init();
USART1_Init(115200);
TIMER1_Init(72,20000); //超時(shí)時(shí)間20ms
USART2_Init(9600);//串口-藍(lán)牙
TIMER2_Init(72,20000); //超時(shí)時(shí)間20ms
USART3_Init(115200);//串口-WIFI
TIMER3_Init(72,20000); //超時(shí)時(shí)間20ms
USART1_Printf("正在初始化WIFI請(qǐng)稍等.\n");
if(ESP8266_Init())
{
USART1_Printf("ESP8266硬件檢測(cè)錯(cuò)誤.\n");
}
else
{
USART1_Printf("WIFI:%d\n",ESP8266_STA_TCP_Client_Mode("ChinaNet-wbyq","12345678","a1WLC5GuOfx.iot-as-mqtt.cn-shanghai.aliyuncs.com",1883,1));
}
//1. 初始化阿里云登錄參數(shù)
Aliyun_LoginInit(ProductKey,DeviceName,DeviceSecret);
//2. MQTT協(xié)議初始化
MQTT_Init();
//3. 連接阿里云服務(wù)器
while(MQTT_Connect(MQTT_ClientID,MQTT_UserName,MQTT_PassWord))
{
USART1_Printf("阿里云服務(wù)器連接失敗,正在重試...\n");
delay_ms(500);
}
USART1_Printf("阿里云服務(wù)器連接成功.\n");
//3. 訂閱主題
if(MQTT_SubscribeTopic(SET_TOPIC,0,1))
{
USART1_Printf("主題訂閱失敗.\n");
}
else
{
USART1_Printf("主題訂閱成功.\n");
}
while(1)
{
key=KEY_Scan(0);
if(key==2)
{
time_cnt=0;
sprintf(mqtt_message,"{"method":"thing.event.property.post","id":"0000000001","params":{"mq2":55},"version":"1.0.0"}");
MQTT_PublishData(POST_TOPIC,mqtt_message,0);
USART1_Printf("發(fā)送狀態(tài)1\r\n");
}
else if(key==3)
{
time_cnt=0;
sprintf(mqtt_message,"{"method":"thing.event.property.post","id":"0000000001","params":{"mq2":66},"version":"1.0.0"}");
MQTT_PublishData(POST_TOPIC,mqtt_message,0);
USART1_Printf("發(fā)送狀態(tài)0\r\n");
}
if(USART3_RX_FLAG)
{
USART3_RX_BUFFER[USART3_RX_CNT]='\0';
for(i=0;i;i++)>
六、代碼參數(shù)解釋
6.1 設(shè)備證書與發(fā)布訂閱主題
?
設(shè)備證書在創(chuàng)建設(shè)備時(shí)保存過,如果沒有保存可以在下面的頁面里查看對(duì)應(yīng)的值。
?
訂閱的主題在下面頁面可以看到。
SET,GET,POST,ERR。 SET 用于設(shè)置(一般由單片機(jī)端使用), GET 用于獲取(一般由 APP 端使用), Post 用于回復(fù)機(jī)制, ERR 用于錯(cuò)誤。
作為單片機(jī)端用的最多的兩個(gè) TOPIC 就是 SET 與 POST
?
6.2 MQTT登錄的密碼、ID、用戶名、端口號(hào)、域名
MQTT標(biāo)準(zhǔn)的3個(gè)參數(shù)格式在官方文檔有介紹:使用MQTT.fx接入物聯(lián)網(wǎng)平臺(tái) - 阿里云物聯(lián)網(wǎng)平臺(tái) - 阿里云
?
?
?
密碼的組成格式:
clientId*deviceName*productKey#
其中: *替換為DeviceName #替換為ProductKey 加密密鑰是DeviceSecret 加密方式是HmacSHA1
PassWord明文=clientIdmq2_iotdeviceNamemq2_iotproductKeya1WLC5GuOfx
hmacsha1加密網(wǎng)站:http://encode.chahuo.com/
加密的密鑰:DeviceSecret
?
6.3 上傳數(shù)據(jù)
?
這是上傳數(shù)據(jù)的格式:
第一個(gè)參數(shù)是 method:后面所跟的參數(shù)可以由物模型看到。
第二個(gè)參數(shù)id : 因?yàn)樵贫藭?huì)連接很多個(gè)用戶,所以他所下發(fā)的數(shù)據(jù)會(huì)有一個(gè)ID 編號(hào),我們這里任意值都行,我用的 0000000001,這里有要注意的,這個(gè) ID 是多少不重要但是位數(shù)一定不能少。
第三個(gè)是 params:表示上傳的具體數(shù)據(jù),根據(jù)自己云端訂閱的類型上傳。
第四個(gè)是版本號(hào):可以根據(jù)自己實(shí)際版本填。
注意:本篇文章沒有貼ESP8266的底層編程代碼,如果不會(huì)ESP8266底層編程,請(qǐng)看這里:
https://blog.csdn.net/xiaolong1126626497/article/details/107379554
想了解STM32+ESP8266使用MQTT協(xié)議連接OneNET 中國(guó)移動(dòng)服務(wù)器的看這里:
https://blog.csdn.net/xiaolong1126626497/article/details/107385118
工程完整源碼下載: https://download.csdn.net/download/xiaolong1126626497/15803494
審核編輯:符乾江
-
物聯(lián)網(wǎng)
+關(guān)注
關(guān)注
2911文章
44845瀏覽量
375296 -
STM32
+關(guān)注
關(guān)注
2270文章
10918瀏覽量
356821
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論