請求方式:GET
URL: http://api.heclouds.com/devices/device_id/datapoints
服務(wù)器或上位機(jī)下發(fā)主題報文(控制下位機(jī)):
API函數(shù):
請求方式:POST
URL: http://api.heclouds.com/mqtt?topic=xxx
以上2個網(wǎng)絡(luò)通訊的API函數(shù)至關(guān)重要,就是實(shí)現(xiàn)常規(guī)情況下OneNet物聯(lián)網(wǎng)開發(fā)的關(guān)鍵性技術(shù)支持。(情況允許的條件下,建議讀者朋友們?nèi)ズ煤醚凶x一下技術(shù)文檔,將會為之后的開發(fā)大大助力)
三、下位機(jī)外設(shè)驅(qū)動
3.1 ESP8266模塊
作者采用的ESP8266模塊為ESP8266NodeMCU,是需要進(jìn)行燒入AT固件,才能實(shí)現(xiàn)目標(biāo)網(wǎng)絡(luò)通訊。作為常見的物聯(lián)網(wǎng)開發(fā)模塊,ESP8266的出現(xiàn)大大降低了物聯(lián)網(wǎng)開發(fā)的難度系數(shù),也普及了物聯(lián)網(wǎng)的發(fā)展。
AT指令最早在藍(lán)牙模塊上接觸過,所謂AT指令實(shí)質(zhì)上就是一些起控制作用的特殊字符串。模塊可以通過AT指令控制搭配使用源代碼API函數(shù)開發(fā),總體開發(fā)速度快,難度較低。
不同廠商芯片的AT固件可能有所不同,但是指令基本一致(作者使用的是樂鑫的)。
說明:由于篇幅有限,這里就不和大家單獨(dú)詳細(xì)介紹AT指令。指令的詳細(xì)參數(shù)及使用說明請參考官方文檔:ESP8266 AT指令集。
3.2 OLED模塊
本項(xiàng)目中0.96寸OLED模塊的使用僅為顯示DHT11傳感器采集到的溫濕度信息,以此來對比是否和服務(wù)器端以及上位機(jī)APP端的數(shù)據(jù)一致性。對其使用有不是太了解的讀者朋友可以參考,作者另一篇基礎(chǔ)教學(xué)博客:(2條消息) 【強(qiáng)烈推薦】基于stm32的OLED各種顯示實(shí)現(xiàn)(含動態(tài)圖)_混分巨獸龍某某的博客-CSDN博客_oled顯示圖片程序 【強(qiáng)烈推薦】基于stm32的OLED各種顯示實(shí)現(xiàn)(含動態(tài)圖)_混分巨獸龍某某的博客-CSDN博客_oled顯示圖片程序")
本項(xiàng)目的代碼都是基于作者以前基礎(chǔ)教學(xué)上的項(xiàng)目代碼搭建而成,保證讀者朋友可以實(shí)現(xiàn)快速復(fù)現(xiàn)。
3.3 DHT11模塊
本項(xiàng)目中DHT11為下位機(jī)MCU采集周圍環(huán)境溫度和濕度的傳感器,當(dāng)然,條件允許的情況下還可以附加很多環(huán)境傳感器(比如:煙霧傳感器,環(huán)境光傳感器,二氧化碳傳感器等等)。當(dāng)然得益于OneNet平臺的布局,本項(xiàng)目教學(xué)的底層邏輯支持讀者朋友的自我DIY,實(shí)現(xiàn)自主化的物聯(lián)網(wǎng)產(chǎn)品設(shè)計(jì)。
DHT11模塊驅(qū)動參考博客:基于stm32的太空人溫濕度時鐘項(xiàng)目——DHT11(HAL庫)_混分巨獸龍某某的博客-CSDN博客
3.4 KEY和LED
KEY和LED都是源于作者正點(diǎn)原子精英版開發(fā)板上自備的(如果和作者同款開發(fā)板移植開發(fā)將會特別簡單快速),屬于最基本的GPIO操作相信各位應(yīng)該都是掌握的
特別注意:
(1)這里的KEY按鍵從設(shè)計(jì)邏輯上就可以看出應(yīng)該是需要采用外部中斷的;
(2)KEY按下之后會改變LED的亮滅狀態(tài),為了同步上位機(jī)此時的LED狀態(tài),所以需要觸發(fā)串口通訊中斷(考慮嵌套中斷情況時候中斷優(yōu)先級的安排)。
四、CubeMX配置
1、RCC配置外部高速晶振(精度更高)——HSE;
2、SYS配置:Debug設(shè)置成Serial Wire(否則可能導(dǎo)致芯片自鎖);
3、TIM2配置:由上面可知DHT11的使用需要us級的延遲函數(shù),HAL庫自帶只有ms的,所以需要自己設(shè)計(jì)一個定時器;
4、I2C2配置:作為OLED的通訊方式;
5、UART1和UART3配置:MCU分別與電腦和ESP8266通訊(記得開啟串口通信中斷);
6、設(shè)置KEY0按鍵PE4為外部中斷(根據(jù)自己的開發(fā)板來確定)
7、GPIO配置:PE0設(shè)置為DHT11的DATA端,PE5為LED,并且設(shè)置ESP8266的EN和RST(PB7和PB9);
8、時鐘樹配置
五、代碼與解析
5.1 OLED與DHT11模塊代碼
受篇幅限制OLED與DHT11部分的代碼,這里就不展示了。如果有不懂這部分原理與代碼的讀者朋友可以參考本人的另一篇博客。博客地址:基于stm32的太空人溫濕度時鐘項(xiàng)目——DHT11(HAL庫)_混分巨獸龍某某的博客-CSDN博客
5.2 ESP8266模塊代碼
ESP8266部分的代碼主要是借助串口通訊AT指令與ESP8266模塊(刷入AT固件的)與OneNet平臺進(jìn)行信息交互(包含ESP8266初始化、數(shù)據(jù)發(fā)送,指令發(fā)送和數(shù)據(jù)緩存清除等)。
esp8266.h代碼:
#ifndef _ESP8266_H_
#define _ESP8266_H_
#include "main.h"
#include "usart.h"
#include<string.h>
#include<stdio.h>
#include<stdbool.h>
#define ESP8266_WIFI_INFO "AT+CWJAP=\"NJUST\",\"768541ly\"\r\n" //連接上自己的wifi熱點(diǎn):WiFi名和密碼
#define ESP8266_ONENET_INFO "AT+CIPSTART=\"TCP\",\"183.230.40.39\",6002\r\n" //連接上OneNet的MQTT
#define OK 0 //接收完成標(biāo)志
#define OUTTIME 1 //接收未完成標(biāo)志
void ESP8266_Clear(void); //清空緩存
void ESP8266_Init(void); //esp8266初始化
_Bool ESP8266_SendCmd(char *cmd, char *res);//發(fā)送數(shù)據(jù)
unsigned char *ESP8266_GetIPD(unsigned short timeOut);
void ESP8266_SendData(unsigned char *data, unsigned short len);
#endif
esp8266.c代碼:
#include "esp8266.h"
unsigned char ESP8266_Buf[128]; //定義一個數(shù)組作為esp8266的數(shù)據(jù)緩沖區(qū)
unsigned short esp8266_cnt = 0, esp8266_cntPre = 0; //定義兩個計(jì)數(shù)值:此次和上一次
unsigned char a_esp8266_buf;
/**
* @brief esp8266初始化
* @param 無
* @retval 無
*/
void ESP8266_Init(void)
{
ESP8266_Clear();
printf("1. 測試AT啟動\r\n"); //AT:測試AT啟動
while(ESP8266_SendCmd("AT\r\n", "OK"))
HAL_Delay(500);
printf("2. 設(shè)置WiFi模式(CWMODE)\r\n"); //查詢/設(shè)置 Wi-Fi 模式:設(shè)置WiFi模式為Station模式
while(ESP8266_SendCmd("AT+CWMODE=1\r\n", "OK"))
HAL_Delay(500);
printf("3. AT+CWDHCP\r\n"); //啟用/禁用 DHCP
while(ESP8266_SendCmd("AT+CWDHCP=1,1\r\n", "OK"))
HAL_Delay(500);
printf("4. 連接WiFi熱點(diǎn)(CWJAP)\r\n");
while(ESP8266_SendCmd(ESP8266_WIFI_INFO, "GOT IP"))
HAL_Delay(500);
printf("5. 建立TCP連接(CIPSTART)\r\n");
while(ESP8266_SendCmd(ESP8266_ONENET_INFO, "CONNECT"))
HAL_Delay(500);
printf("6. ESP8266 Init OK\r\n");
}
/**
* @brief 清空緩存
* @param 無
* @retval 無
*/
void ESP8266_Clear(void)
{
memset(ESP8266_Buf, 0, sizeof(ESP8266_Buf)); //將數(shù)組中的元素全部初始化為0,
}
/**
* @brief 等待接收完成
* @param 無
* @retval OK:表示接收完成;OUTTIME:表示接收超時完成
* 進(jìn)行循環(huán)調(diào)用,檢測接收是否完成
*/
_Bool ESP8266_WaitRecive(void)
{
if(esp8266_cnt == 0) //如果當(dāng)前接收計(jì)數(shù)為0 則說明沒有處于接收數(shù)據(jù)中,所以直接跳出,結(jié)束函數(shù)
return OUTTIME;
if(esp8266_cnt == esp8266_cntPre) //如果上一次的值和這次相同,則說明接收完畢
{
esp8266_cnt = 0; //清0接收計(jì)數(shù)
return OK; //返回接收完成標(biāo)志
}
else //如果不相同,則將此次賦值給上一次,并返回接收未完成標(biāo)志
{
esp8266_cntPre = esp8266_cnt;
return OUTTIME;
}
}
/**
* @brief 發(fā)送命令
* @param cmd:表示命令;res:需要檢查的返回指令
* @retval 0:表示成功;1:表示失敗
*/
_Bool ESP8266_SendCmd(char *cmd, char *res)
{
unsigned char timeOut = 200;
HAL_UART_Transmit(&huart3, (unsigned char *)cmd, strlen((const char *)cmd),0xffff);
while(timeOut--)
{
if(ESP8266_WaitRecive() == OK) //如果收到數(shù)據(jù)
{
printf("%s",ESP8266_Buf);
if(strstr((const char *)ESP8266_Buf, res) != NULL) //如果檢索到關(guān)鍵詞,清空緩存
{
ESP8266_Clear();
return 0;
}
}
HAL_Delay(10);
}
return 1;
}
/**
* @brief 數(shù)據(jù)發(fā)送
* @param data:待發(fā)送的數(shù)據(jù);len:待發(fā)送的數(shù)據(jù)長度
* @retval 無
*/
void ESP8266_SendData(unsigned char *data, unsigned short len)
{
char cmdBuf[32];
ESP8266_Clear(); //清空接收緩存
sprintf(cmdBuf, "AT+CIPSEND=%d\r\n", len); //發(fā)送命令,sprintf()函數(shù)用于將格式化的數(shù)據(jù)寫入字符串
if(!ESP8266_SendCmd(cmdBuf, ">")) //收到‘>’時可以發(fā)送數(shù)據(jù)
{
HAL_UART_Transmit(&huart3, data, len,0xffff); //發(fā)送設(shè)備連接請求數(shù)據(jù)
}
}
/**
* @brief 獲取平臺返回的數(shù)據(jù)
* @param 等待的時間
* @retval 平臺返回的數(shù)據(jù),不同網(wǎng)絡(luò)設(shè)備返回的格式不同,需要進(jìn)行調(diào)試,如:ESP8266的返回格式為:"+IPD,x:yyy",x表示數(shù)據(jù)長度,yyy表示數(shù)據(jù)內(nèi)容
*/
unsigned char *ESP8266_GetIPD(unsigned short timeOut)
{
char *ptrIPD = NULL;
do
{
if(ESP8266_WaitRecive() == OK) //如果接收完成
{
ptrIPD = strstr((char *)ESP8266_Buf, "IPD,"); //搜索“IPD”頭
if(ptrIPD == NULL) //如果沒找到,可能是IPD頭的延遲,還是需要等待一會,但不會超過設(shè)定的時間
{
//UsartPrintf(USART_DEBUG, "\"IPD\" not found\r\n");
}
else
{
ptrIPD = strchr(ptrIPD, ':'); //找到':'
if(ptrIPD != NULL)
{
ptrIPD++;
return (unsigned char *)(ptrIPD);
}
else
return NULL;
}
}
HAL_Delay(5); //延時等待
} while(timeOut--);
return NULL; //超時還未找到,返回空指針
}
/**
* @brief 串口2收發(fā)中斷回調(diào)函數(shù)
* @param
* @retval
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(esp8266_cnt >= 255) //溢出判斷,超過一個字節(jié)
{
esp8266_cnt = 0;
memset(ESP8266_Buf,0x00,sizeof(ESP8266_Buf));
HAL_UART_Transmit(&huart3, (uint8_t *)"數(shù)據(jù)溢出", 10,0xFFFF);
}
else
{
ESP8266_Buf[esp8266_cnt++] = a_esp8266_buf; //接收數(shù)據(jù)轉(zhuǎn)存
}
HAL_UART_Receive_IT(&huart3, (uint8_t *)&a_esp8266_buf, 1); //再開啟接收中斷
}
代碼總結(jié):
ESP8266模塊的代碼基于HAL庫實(shí)現(xiàn),主要是利用AT指令去使下位機(jī)(STM32+ESP8266)連接上WIFI,并且與OneNet平臺進(jìn)行MQTT協(xié)議通信(TCP連接IP地址和對應(yīng)端口)。
特別注意:
使用ESP8266進(jìn)行通訊時,當(dāng)數(shù)據(jù)量較大的時候一定要編寫緩存清除代碼(否則,很有可能出現(xiàn)死機(jī)等情況)。當(dāng)然,這個時候可以搭配**SD NAND(貼片式TF卡)**去存儲傳輸?shù)臄?shù)據(jù)流。同時,利用這些保存在SD卡中的數(shù)據(jù),可以在下位機(jī)制作精美的數(shù)據(jù)歷史信息UI,極大的拓展了產(chǎn)品價值。
5.3 OneNet與Cjson代碼
OneNet部分的代碼就是實(shí)現(xiàn)MQTT協(xié)議去傳輸數(shù)據(jù)流給OneNet平臺,并且訂閱上位機(jī)發(fā)送的Topic主題,利用Cjson代碼去解析收到的數(shù)據(jù)信息,根據(jù)上位機(jī)發(fā)送Topic主題對應(yīng)的數(shù)據(jù)控制下位機(jī)MCU實(shí)現(xiàn)操作(這里訂閱的主題為{“LED_SW”},LED控制主題,各位可以根據(jù)自己的情況改動)。
onenet.c代碼:
#include "onenet.h"
#include "dht11.h"
#include <string.h>
#include <stdio.h>
//CJSON庫
#include "cJSON.h"
#define PROID "549063" //產(chǎn)品ID
#define AUTH_INFO "environment" //鑒權(quán)信息
#define DEVID "1004695102" //設(shè)備ID
extern unsigned char esp8266_buf[128];
//float sht20_info_tempreture = 12;
//float sht20_info_humidity = 15;
extern int tempreture;
extern int humidity;
//==========================================================
// 函數(shù)名稱: OneNet_DevLink
//
// 函數(shù)功能: 與onenet創(chuàng)建連接
//
// 入口參數(shù): 無
//
// 返回參數(shù): 1-成功 0-失敗
//
// 說明: 與onenet平臺建立連接
//==========================================================
_Bool OneNet_DevLink(void)
{
MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0}; //協(xié)議包,協(xié)議類型初始化
unsigned char *dataPtr;
_Bool status = 1;
printf("OneNet_DevLink\r\n"
"PROID: %s, AUIF: %s, DEVID:%s\r\n"
, PROID, AUTH_INFO, DEVID);
if(MQTT_PacketConnect(PROID, AUTH_INFO, DEVID, 256, 0, MQTT_QOS_LEVEL0, NULL, NULL, 0, &mqttPacket) == 0)
{
ESP8266_SendData(mqttPacket._data, mqttPacket._len); //上傳平臺
dataPtr = ESP8266_GetIPD(250); //等待平臺響應(yīng)
if(dataPtr != NULL)
{
if(MQTT_UnPacketRecv(dataPtr) == MQTT_PKT_CONNACK)
{
switch(MQTT_UnPacketConnectAck(dataPtr))
{
case 0:printf("Tips: 連接成功\r\n");status = 0;break;
case 1:printf("WARN: 連接失?。簠f(xié)議錯誤\r\n");break;
case 2:printf("WARN: 連接失?。悍欠ǖ腸lientid\r\n");break;
case 3:printf("WARN: 連接失?。悍?wù)器失敗\r\n");break;
case 4:printf("WARN: 連接失?。河脩裘蛎艽a錯誤\r\n");break;
case 5:printf("WARN: 連接失?。悍欠ㄦ溄?比如token非法)\r\n");break;
default:printf("ERR: 連接失?。何粗e誤\r\n");break;
}
}
}
MQTT_DeleteBuffer(&mqttPacket); //刪包
}
else
printf("WARN: MQTT_PacketConnect Failed\r\n");
return status;
}
unsigned char OneNet_FillBuf(char *buf)
{
char text[32];
uint16_t LED1_FLAG = !HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_5); //讀取當(dāng)前LED1的狀態(tài)
memset(text, 0, sizeof(text));
strcpy(buf, ",;");
memset(text, 0, sizeof(text));
sprintf(text, "Tempreture,%d;", tempreture);
strcat(buf, text);
memset(text, 0, sizeof(text));
sprintf(text, "Humidity,%d;", humidity);
strcat(buf, text);
// memset(text, 0, sizeof(text));
// sprintf(text, "key:%d;", LED1_FLAG);
// strcat(buf, text);
memset(text, 0, sizeof(text));
sprintf(text, "LED,%d", LED1_FLAG);
strcat(buf, text);
printf("buf_mqtt=%s\r\n",buf);
return strlen(buf);
}
//==========================================================
// 函數(shù)名稱: OneNet_SendData
//
// 函數(shù)功能: 上傳數(shù)據(jù)到平臺
//
// 入口參數(shù): type:發(fā)送數(shù)據(jù)的格式
//
// 返回參數(shù): 無
//
// 說明:
//==========================================================
void OneNet_SendData(void)
{
MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0}; //協(xié)議包
char buf[128];
short body_len = 0, i = 0;
printf("Tips: OneNet_SendData-MQTT\r\n");
memset(buf, 0, sizeof(buf));
body_len = OneNet_FillBuf(buf);
if(body_len)
{
if(MQTT_PacketSaveData(DEVID, body_len, NULL, 5, &mqttPacket) == 0) //封包
{
for(; i < body_len; i++)
mqttPacket._data[mqttPacket._len++] = buf[i];
ESP8266_SendData(mqttPacket._data, mqttPacket._len);
printf("Send %d Bytes\r\n", mqttPacket._len);
MQTT_DeleteBuffer(&mqttPacket); //刪包
}
else
printf("WARN: EDP_NewBuffer Failed\r\n");
}
}
//==========================================================
// 函數(shù)名稱: OneNet_RevPro
//
// 函數(shù)功能: 平臺返回?cái)?shù)據(jù)檢測
//
// 入口參數(shù): dataPtr:平臺返回的數(shù)據(jù)
//
// 返回參數(shù): 無
//
// 說明:
//==========================================================
void OneNet_RevPro(unsigned char *cmd)
{
MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0}; //協(xié)議包
char *req_payload = NULL;
char *cmdid_topic = NULL;
unsigned short topic_len = 0;
unsigned short req_len = 0;
unsigned char type = 0;
unsigned char qos = 0;
static unsigned short pkt_id = 0;
short result = 0;
char *dataPtr = NULL;
char numBuf[10];
int num = 0;
cJSON* cjson;
int value;
type = MQTT_UnPacketRecv(cmd);
switch(type)
{
case MQTT_PKT_CMD: //命令下發(fā)
result = MQTT_UnPacketCmd(cmd, &cmdid_topic, &req_payload, &req_len); //解出topic和消息體
if(result == 0)
{
printf("cmdid: %s, req: %s, req_len: %d\r\n", cmdid_topic, req_payload, req_len);
if(MQTT_PacketCmdResp(cmdid_topic, req_payload, &mqttPacket) == 0) //命令回復(fù)組包
{
printf("Tips: Send CmdResp\r\n");
ESP8266_SendData(mqttPacket._data, mqttPacket._len); //回復(fù)命令
MQTT_DeleteBuffer(&mqttPacket); //刪包
}
}
break;
case MQTT_PKT_PUBLISH: //接收的Publish消息
result = MQTT_UnPacketPublish(cmd, &cmdid_topic, &topic_len, &req_payload, &req_len, &qos, &pkt_id);
if(result == 0)
{
printf("topic: %s, topic_len: %d, payload: %s, payload_len: %d\r\n",
cmdid_topic, topic_len, req_payload, req_len);
//JSON字符串到cJSON格式
cjson = cJSON_Parse(req_payload);
//判斷cJSON_Parse函數(shù)返回值確定是否打包成功
if(cjson == NULL){
// printf("json pack into cjson error...");
printf("json pack into cjson error...\r\n");
}
else{
//獲取字段值
//cJSON_GetObjectltem返回的是一個cJSON結(jié)構(gòu)體所以我們可以通過函數(shù)返回結(jié)構(gòu)體的方式選擇返回類型!
value = cJSON_GetObjectItem(cjson,"LED")->valueint;
if(value) HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, GPIO_PIN_RESET);
else HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, GPIO_PIN_SET);
}
//delete cjson
cJSON_Delete(cjson);
switch(qos)
{
case 1: //收到publish的qos為1,設(shè)備需要回復(fù)Ack
if(MQTT_PacketPublishAck(pkt_id, &mqttPacket) == 0)
{
printf("Tips: Send PublishAck\r\n");
ESP8266_SendData(mqttPacket._data, mqttPacket._len);
MQTT_DeleteBuffer(&mqttPacket);
}
break;
case 2: //收到publish的qos為2,設(shè)備先回復(fù)Rec
//平臺回復(fù)Rel,設(shè)備再回復(fù)Comp
if(MQTT_PacketPublishRec(pkt_id, &mqttPacket) == 0)
{
printf( "Tips: Send PublishRec\r\n");
ESP8266_SendData(mqttPacket._data, mqttPacket._len);
MQTT_DeleteBuffer(&mqttPacket);
}
break;
default:
break;
}
}
break;
case MQTT_PKT_PUBACK: //發(fā)送Publish消息,平臺回復(fù)的Ack
if(MQTT_UnPacketPublishAck(cmd) == 0)
printf("Tips: MQTT Publish Send OK\r\n");
break;
default:
result = -1;
break;
}
ESP8266_Clear(); //清空緩存
if(result == -1)
return;
dataPtr = strchr(req_payload, '}'); //搜索'}'
if(dataPtr != NULL && result != -1) //如果找到了
{
dataPtr++;
while(*dataPtr >= '0' && *dataPtr <= '9') //判斷是否是下發(fā)的命令控制數(shù)據(jù)
{
numBuf[num++] = *dataPtr++;
}
numBuf[num] = 0;
num = atoi((const char *)numBuf); //轉(zhuǎn)為數(shù)值形式
}
if(type == MQTT_PKT_CMD || type == MQTT_PKT_PUBLISH)
{
MQTT_FreeBuffer(cmdid_topic);
MQTT_FreeBuffer(req_payload);
}
}
/************************************************************/
//==========================================================
// 函數(shù)名稱: OneNet_Subscribe
//
// 函數(shù)功能: 訂閱
//
// 入口參數(shù): topics:訂閱的topic
// topic_cnt:topic個數(shù)
//
// 返回參數(shù): SEND_TYPE_OK-成功 SEND_TYPE_SUBSCRIBE-需要重發(fā)
//
// 說明:
//==========================================================
void OneNet_Subscribe(const char *topics[], unsigned char topic_cnt)
{
unsigned char i = 0;
MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0}; //協(xié)議包
for(; i < topic_cnt; i++)
// UsartPrintf(USART_DEBUG, "Subscribe Topic: %s\r\n", topics[i]);
printf("Subscribe Topic: %s\r\n", topics[i]);
if(MQTT_PacketSubscribe(MQTT_SUBSCRIBE_ID, MQTT_QOS_LEVEL2, topics, topic_cnt, &mqttPacket) == 0)
{
ESP8266_SendData(mqttPacket._data, mqttPacket._len); //向平臺發(fā)送訂閱請求
MQTT_DeleteBuffer(&mqttPacket); //刪包
}
}
①、注意這3個數(shù)據(jù)(包含:產(chǎn)品ID、創(chuàng)建的某個設(shè)備ID與該設(shè)備鑒權(quán)信息)替換為自己OneNet賬號下的信息 ;
#include "onenet.h"
#include "dht11.h"
#include <string.h>
#include <stdio.h>
//CJSON庫
#include "cJSON.h"
#define PROID "549063" //產(chǎn)品ID
#define AUTH_INFO "environment" //鑒權(quán)信息
#define DEVID "1004695102" //設(shè)備ID
②、在OneNet_FillBuf(char *buf)函數(shù)中創(chuàng)建自己的數(shù)據(jù)流與需要同步的控制Topic主題(LED);
unsigned char OneNet_FillBuf(char *buf)
{
char text[32];
uint16_t LED1_FLAG = !HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_5); //讀取當(dāng)前LED1的狀態(tài)
memset(text, 0, sizeof(text));
strcpy(buf, ",;");
memset(text, 0, sizeof(text));
sprintf(text, "Tempreture,%d;", tempreture);
strcat(buf, text);
memset(text, 0, sizeof(text));
sprintf(text, "Humidity,%d;", humidity);
strcat(buf, text);
// memset(text, 0, sizeof(text));
// sprintf(text, "key:%d;", LED1_FLAG);
// strcat(buf, text);
memset(text, 0, sizeof(text));
sprintf(text, "LED,%d", LED1_FLAG);
strcat(buf, text);
printf("buf_mqtt=%s\r\n",buf);
return strlen(buf);
}
③、利用Cjson代碼去解析上位機(jī)發(fā)送的關(guān)鍵字(LED),然后通過關(guān)鍵字后對應(yīng)的數(shù)字覺得下位機(jī)操作;
//JSON字符串到cJSON格式
cjson = cJSON_Parse(req_payload);
//判斷cJSON_Parse函數(shù)返回值確定是否打包成功
if(cjson == NULL){
//printf("json pack into cjson error...");
printf("json pack into cjson error...\r\n");
}
else{
//獲取字段值
//cJSON_GetObjectltem返回的是一個cJSON結(jié)構(gòu)體所以我們可以通過函數(shù)返回結(jié)構(gòu)體的方式選擇返回類型!
value = cJSON_GetObjectItem(cjson,"LED")->valueint;
if(value) HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, GPIO_PIN_RESET);
else HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, GPIO_PIN_SET);
}
//delete cjson
cJSON_Delete(cjson);
Cjson代碼:
Cjson其實(shí)是一種常用的網(wǎng)絡(luò)信息解析代碼,讀者朋友在使用的時候不一定需要徹底讀懂。只需要學(xué)會利用Cjson去解析服務(wù)器發(fā)送的數(shù)據(jù)信息以及Cjson代碼的移植。
cJSON是一個使用C語言編寫的JSON數(shù)據(jù)解析器,具有超輕便,可移植,單文件的特點(diǎn),使用MIT開源協(xié)議。
Cjson的下載地址:GitHub - DaveGamble/cJSON: Ultralightweight JSON parser in ANSI C
5.4 MQTT代碼
5.4.1 MQTT介紹
MQTT(Message Queuing Telemetry Transport,消息隊(duì)列遙測傳輸協(xié)議),是一種基于發(fā)布/訂閱(publish/subscribe)模式的“輕量級”通訊協(xié)議(因此MQTT常用于物聯(lián)網(wǎng)開發(fā)中的低功耗長期在線通訊),該協(xié)議構(gòu)建于TCP/IP協(xié)議上,由IBM在1999年發(fā)布。
MQTT最大優(yōu)點(diǎn):用極少的代碼和有限的帶寬,為連接遠(yuǎn)程設(shè)備提供實(shí)時可靠的消息服務(wù)。
5.4.2? MQTT實(shí)現(xiàn)原理
實(shí)現(xiàn)MQTT協(xié)議需要客戶端和服務(wù)器端通訊完成,在通訊過程中,MQTT協(xié)議中有三種身份:發(fā)布者(Publish)、代理(Broker)(服務(wù)器)、訂閱者(Subscribe)。其中,消息的發(fā)布者和訂閱者都是客戶端,消息代理是服務(wù)器,消息發(fā)布者可以同時是訂閱者。
MQTT傳輸?shù)南⒎譃椋?*主題(Topic)**和負(fù)載(payload)兩部分:
- (1)Topic,可以理解為消息的類型,訂閱者訂閱(Subscribe)后,就會收到該主題的消息內(nèi)容(payload);
- (2)payload,可以理解為消息的內(nèi)容,是指訂閱者具體要使用的內(nèi)容。
MQTT詳細(xì)介紹:MQTT 入門介紹 | 菜鳥教程 (runoob.com)")
5.4.3 MQTT代碼與使用
對于初學(xué)者來說直接完成MQTT協(xié)議的編寫時不現(xiàn)實(shí)的,而且實(shí)際開發(fā)過程中大部分都是對MQTT代碼進(jìn)行局部修改。OneNet社區(qū)平臺提供了很多MQTT協(xié)議代碼,以供開發(fā)者直接使用(相當(dāng)于基于API函數(shù)開發(fā))。同時,OneNet社區(qū)平臺也有許多開發(fā)者提供了各式各樣框架下的OneNet物聯(lián)網(wǎng)開發(fā)方案與代碼(可以直接借鑒使用)。
OneNet社區(qū)中的開發(fā)實(shí)例代碼(文末代碼開源中打包了這些實(shí)例代碼):
文章來源:http://www.zghlxwxcb.cn/news/detail-848372.html
MqttKit.c:文章來源地址http://www.zghlxwxcb.cn/news/detail-848372.html
//協(xié)議頭文件
#include "MqttKit.h"
//C庫
#include <string.h>
#include <stdio.h>
#define CMD_TOPIC_PREFIX "$creq"
//==========================================================
// 函數(shù)名稱: EDP_NewBuffer
//
// 函數(shù)功能: 申請內(nèi)存
//
// 入口參數(shù): edpPacket:包結(jié)構(gòu)體
// size:大小
//
// 返回參數(shù): 無
//
// 說明: 1.可使用動態(tài)分配來分配內(nèi)存
// 2.可使用局部或全局?jǐn)?shù)組來指定內(nèi)存
//==========================================================
void MQTT_NewBuffer(MQTT_PACKET_STRUCTURE *mqttPacket, uint32 size)
{
uint32 i = 0;
if(mqttPacket->_data == NULL)
{
mqttPacket->_memFlag = MEM_FLAG_ALLOC;
mqttPacket->_data = (uint8 *)MQTT_MallocBuffer(size);
if(mqttPacket->_data != NULL)
{
mqttPacket->_len = 0;
mqttPacket->_size = size;
for(; i < mqttPacket->_size; i++)
mqttPacket->_data[i] = 0;
}
}
else
{
mqttPacket->_memFlag = MEM_FLAG_STATIC;
for(; i < mqttPacket->_size; i++)
mqttPacket->_data[i] = 0;
mqttPacket->_len = 0;
if(mqttPacket->_size < size)
mqttPacket->_data = NULL;
}
}
//==========================================================
// 函數(shù)名稱: MQTT_DeleteBuffer
//
// 函數(shù)功能: 釋放數(shù)據(jù)內(nèi)存
//
// 入口參數(shù): edpPacket:包結(jié)構(gòu)體
//
// 返回參數(shù): 無
//
// 說明:
//==========================================================
void MQTT_DeleteBuffer(MQTT_PACKET_STRUCTURE *mqttPacket)
{
if(mqttPacket->_memFlag == MEM_FLAG_ALLOC)
MQTT_FreeBuffer(mqttPacket->_data);
mqttPacket->_data = NULL;
mqttPacket->_len = 0;
mqttPacket->_size = 0;
mqttPacket->_memFlag = MEM_FLAG_NULL;
}
int32 MQTT_DumpLength(size_t len, uint8 *buf)
{
int32 i = 0;
for(i = 1; i <= 4; ++i)
{
*buf = len % 128;
len >>= 7;
if(len > 0)
{
*buf |= 128;
++buf;
}
else
{
return i;
}
}
return -1;
}
int32 MQTT_ReadLength(const uint8 *stream, int32 size, uint32 *len)
{
int32 i;
const uint8 *in = stream;
uint32 multiplier = 1;
*len = 0;
for(i = 0; i < size; ++i)
{
*len += (in[i] & 0x7f) * multiplier;
if(!(in[i] & 0x80))
{
return i + 1;
}
multiplier <<= 7;
if(multiplier >= 2097152) //128 * *128 * *128
{
return -2; // error, out of range
}
}
return -1; // not complete
}
//==========================================================
// 函數(shù)名稱: MQTT_UnPacketRecv
//
// 函數(shù)功能: MQTT數(shù)據(jù)接收類型判斷
//
// 入口參數(shù): dataPtr:接收的數(shù)據(jù)指針
//
// 返回參數(shù): 0-成功 其他-失敗原因
//
// 說明:
//==========================================================
uint8 MQTT_UnPacketRecv(uint8 *dataPtr)
{
uint8 status = 255;
uint8 type = dataPtr[0] >> 4; //類型檢查
if(type < 1 || type > 14)
return status;
if(type == MQTT_PKT_PUBLISH)
{
uint8 *msgPtr;
uint32 remain_len = 0;
msgPtr = dataPtr + MQTT_ReadLength(dataPtr + 1, 4, &remain_len) + 1;
if(remain_len < 2 || dataPtr[0] & 0x01) //retain
return 255;
if(remain_len < ((uint16)msgPtr[0] << 8 | msgPtr[1]) + 2)
return 255;
if(strstr((int8 *)msgPtr + 2, CMD_TOPIC_PREFIX) != NULL) //如果是命令下發(fā)
status = MQTT_PKT_CMD;
else
status = MQTT_PKT_PUBLISH;
}
else
status = type;
return status;
}
//==========================================================
// 函數(shù)名稱: MQTT_PacketConnect
//
// 函數(shù)功能: 連接消息組包
//
// 入口參數(shù): user:用戶名:產(chǎn)品ID
// password:密碼:鑒權(quán)信息或apikey
// devid:設(shè)備ID
// cTime:連接保持時間
// clean_session:離線消息清除標(biāo)志
// qos:重發(fā)標(biāo)志
// will_topic:異常離線topic
// will_msg:異常離線消息
// will_retain:消息推送標(biāo)志
// mqttPacket:包指針
//
// 返回參數(shù): 0-成功 其他-失敗
//
// 說明:
//==========================================================
uint8 MQTT_PacketConnect(const int8 *user, const int8 *password, const int8 *devid,
uint16 cTime, uint1 clean_session, uint1 qos,
const int8 *will_topic, const int8 *will_msg, int32 will_retain,
MQTT_PACKET_STRUCTURE *mqttPacket)
{
uint8 flags = 0;
uint8 will_topic_len = 0;
uint16 total_len = 15;
int16 len = 0, devid_len = strlen(devid);
if(!devid)
return 1;
total_len += devid_len + 2;
//斷線后,是否清理離線消息:1-清理 0-不清理--------------------------------------------
if(clean_session)
{
flags |= MQTT_CONNECT_CLEAN_SESSION;
}
//異常掉線情況下,服務(wù)器發(fā)布的topic------------------------------------------------------
if(will_topic)
{
flags |= MQTT_CONNECT_WILL_FLAG;
will_topic_len = strlen(will_topic);
total_len += 4 + will_topic_len + strlen(will_msg);
}
//qos級別--主要用于PUBLISH(發(fā)布態(tài))消息的,保證消息傳遞的次數(shù)-----------------------------
switch((unsigned char)qos)
{
case MQTT_QOS_LEVEL0:
flags |= MQTT_CONNECT_WILL_QOS0; //最多一次
break;
case MQTT_QOS_LEVEL1:
flags |= (MQTT_CONNECT_WILL_FLAG | MQTT_CONNECT_WILL_QOS1); //最少一次
break;
case MQTT_QOS_LEVEL2:
flags |= (MQTT_CONNECT_WILL_FLAG | MQTT_CONNECT_WILL_QOS2); //只有一次
break;
default:
return 2;
}
//主要用于PUBLISH(發(fā)布態(tài))的消息,表示服務(wù)器要保留這次推送的信息,如果有新的訂閱者出現(xiàn),就把這消息推送給它。如果不設(shè)那么推送至當(dāng)前訂閱的就釋放了
if(will_retain)
{
flags |= (MQTT_CONNECT_WILL_FLAG | MQTT_CONNECT_WILL_RETAIN);
}
//賬號為空 密碼為空---------------------------------------------------------------------
if(!user || !password)
{
return 3;
}
flags |= MQTT_CONNECT_USER_NAME | MQTT_CONNECT_PASSORD;
total_len += strlen(user) + strlen(password) + 4;
//分配內(nèi)存-----------------------------------------------------------------------------
MQTT_NewBuffer(mqttPacket, total_len);
if(mqttPacket->_data == NULL)
return 4;
memset(mqttPacket->_data, 0, total_len);
/*************************************固定頭部***********************************************/
//固定頭部----------------------連接請求類型---------------------------------------------
mqttPacket->_data[mqttPacket->_len++] = MQTT_PKT_CONNECT << 4;
//固定頭部----------------------剩余長度值-----------------------------------------------
len = MQTT_DumpLength(total_len - 5, mqttPacket->_data + mqttPacket->_len);
if(len < 0)
{
MQTT_DeleteBuffer(mqttPacket);
return 5;
}
else
mqttPacket->_len += len;
/*************************************可變頭部***********************************************/
//可變頭部----------------------協(xié)議名長度 和 協(xié)議名--------------------------------------
mqttPacket->_data[mqttPacket->_len++] = 0;
mqttPacket->_data[mqttPacket->_len++] = 4;
mqttPacket->_data[mqttPacket->_len++] = 'M';
mqttPacket->_data[mqttPacket->_len++] = 'Q';
mqttPacket->_data[mqttPacket->_len++] = 'T';
mqttPacket->_data[mqttPacket->_len++] = 'T';
//可變頭部----------------------protocol level 4-----------------------------------------
mqttPacket->_data[mqttPacket->_len++] = 4;
//可變頭部----------------------連接標(biāo)志(該函數(shù)開頭處理的數(shù)據(jù))-----------------------------
mqttPacket->_data[mqttPacket->_len++] = flags;
//可變頭部----------------------保持連接的時間(秒)----------------------------------------
mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(cTime);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(cTime);
/*************************************消息體************************************************/
//消息體----------------------------devid長度、devid-------------------------------------
mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(devid_len);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(devid_len);
strncat((int8 *)mqttPacket->_data + mqttPacket->_len, devid, devid_len);
mqttPacket->_len += devid_len;
//消息體----------------------------will_flag 和 will_msg---------------------------------
if(flags & MQTT_CONNECT_WILL_FLAG)
{
unsigned short mLen = 0;
if(!will_msg)
will_msg = "";
mLen = strlen(will_topic);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(mLen);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(mLen);
strncat((int8 *)mqttPacket->_data + mqttPacket->_len, will_topic, mLen);
mqttPacket->_len += mLen;
mLen = strlen(will_msg);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(mLen);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(mLen);
strncat((int8 *)mqttPacket->_data + mqttPacket->_len, will_msg, mLen);
mqttPacket->_len += mLen;
}
//消息體----------------------------use---------------------------------------------------
if(flags & MQTT_CONNECT_USER_NAME)
{
unsigned short user_len = strlen(user);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(user_len);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(user_len);
strncat((int8 *)mqttPacket->_data + mqttPacket->_len, user, user_len);
mqttPacket->_len += user_len;
}
//消息體----------------------------password----------------------------------------------
if(flags & MQTT_CONNECT_PASSORD)
{
unsigned short psw_len = strlen(password);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(psw_len);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(psw_len);
strncat((int8 *)mqttPacket->_data + mqttPacket->_len, password, psw_len);
mqttPacket->_len += psw_len;
}
return 0;
}
//==========================================================
// 函數(shù)名稱: MQTT_PacketDisConnect
//
// 函數(shù)功能: 斷開連接消息組包
//
// 入口參數(shù): mqttPacket:包指針
//
// 返回參數(shù): 0-成功 1-失敗
//
// 說明:
//==========================================================
uint1 MQTT_PacketDisConnect(MQTT_PACKET_STRUCTURE *mqttPacket)
{
MQTT_NewBuffer(mqttPacket, 2);
if(mqttPacket->_data == NULL)
return 1;
/*************************************固定頭部***********************************************/
//固定頭部----------------------頭部消息-------------------------------------------------
mqttPacket->_data[mqttPacket->_len++] = MQTT_PKT_DISCONNECT << 4;
//固定頭部----------------------剩余長度值-----------------------------------------------
mqttPacket->_data[mqttPacket->_len++] = 0;
return 0;
}
//==========================================================
// 函數(shù)名稱: MQTT_UnPacketConnectAck
//
// 函數(shù)功能: 連接消息解包
//
// 入口參數(shù): rev_data:接收的數(shù)據(jù)
//
// 返回參數(shù): 1、255-失敗 其他-平臺的返回碼
//
// 說明:
//==========================================================
uint8 MQTT_UnPacketConnectAck(uint8 *rev_data)
{
if(rev_data[1] != 2)
return 1;
if(rev_data[2] == 0 || rev_data[2] == 1)
return rev_data[3];
else
return 255;
}
//==========================================================
// 函數(shù)名稱: MQTT_PacketSaveData
//
// 函數(shù)功能: 數(shù)據(jù)點(diǎn)上傳組包
//
// 入口參數(shù): devid:設(shè)備ID(可為空)
// send_buf:json緩存buf
// send_len:json總長
// type_bin_head:bin文件的消息頭
// type:類型
//
// 返回參數(shù): 0-成功 1-失敗
//
// 說明:
//==========================================================
uint1 MQTT_PacketSaveData(const int8 *devid, int16 send_len, int8 *type_bin_head, uint8 type, MQTT_PACKET_STRUCTURE *mqttPacket)
{
if(MQTT_PacketPublish(MQTT_PUBLISH_ID, "$dp", NULL, send_len + 3, MQTT_QOS_LEVEL1, 0, 1, mqttPacket) == 0)
{
mqttPacket->_data[mqttPacket->_len++] = type; //類型
mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(send_len);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(send_len);
}
else
return 1;
return 0;
}
//==========================================================
// 函數(shù)名稱: MQTT_PacketSaveBinData
//
// 函數(shù)功能: 為禁止文件上傳組包
//
// 入口參數(shù): name:數(shù)據(jù)流名字
// file_len:文件長度
// mqttPacket:包指針
//
// 返回參數(shù): 0-成功 1-失敗
//
// 說明:
//==========================================================
uint1 MQTT_PacketSaveBinData(const int8 *name, int16 file_len, MQTT_PACKET_STRUCTURE *mqttPacket)
{
uint1 result = 1;
int8 *bin_head = NULL;
uint8 bin_head_len = 0;
int8 *payload = NULL;
int32 payload_size = 0;
bin_head = (int8 *)MQTT_MallocBuffer(13 + strlen(name));
if(bin_head == NULL)
return result;
sprintf(bin_head, "{\"ds_id\":\"%s\"}", name);
bin_head_len = strlen(bin_head);
payload_size = 7 + bin_head_len + file_len;
payload = (int8 *)MQTT_MallocBuffer(payload_size - file_len);
if(payload == NULL)
{
MQTT_FreeBuffer(bin_head);
return result;
}
payload[0] = 2; //類型
payload[1] = MOSQ_MSB(bin_head_len);
payload[2] = MOSQ_LSB(bin_head_len);
memcpy(payload + 3, bin_head, bin_head_len);
payload[bin_head_len + 3] = (file_len >> 24) & 0xFF;
payload[bin_head_len + 4] = (file_len >> 16) & 0xFF;
payload[bin_head_len + 5] = (file_len >> 8) & 0xFF;
payload[bin_head_len + 6] = file_len & 0xFF;
if(MQTT_PacketPublish(MQTT_PUBLISH_ID, "$dp", payload, payload_size, MQTT_QOS_LEVEL1, 0, 1, mqttPacket) == 0)
result = 0;
MQTT_FreeBuffer(bin_head);
MQTT_FreeBuffer(payload);
return result;
}
//==========================================================
// 函數(shù)名稱: MQTT_UnPacketCmd
//
// 函數(shù)功能: 命令下發(fā)解包
//
// 入口參數(shù): rev_data:接收的數(shù)據(jù)指針
// cmdid:cmdid-uuid
// req:命令
//
// 返回參數(shù): 0-成功 其他-失敗原因
//
// 說明:
//==========================================================
uint8 MQTT_UnPacketCmd(uint8 *rev_data, int8 **cmdid, int8 **req, uint16 *req_len)
{
int8 *dataPtr = strchr((int8 *)rev_data + 6, '/'); //加6是跳過頭信息
uint32 remain_len = 0;
if(dataPtr == NULL) //未找到'/'
return 1;
dataPtr++; //跳過'/'
MQTT_ReadLength(rev_data + 1, 4, &remain_len); //讀取剩余字節(jié)
*cmdid = (int8 *)MQTT_MallocBuffer(37); //cmdid固定36字節(jié),多分配一個結(jié)束符的位置
if(*cmdid == NULL)
return 2;
memset(*cmdid, 0, 37); //全部清零
memcpy(*cmdid, (const int8 *)dataPtr, 36); //復(fù)制cmdid
dataPtr += 36;
*req_len = remain_len - 44; //命令長度 = 剩余長度(remain_len) - 2 - 5($creq) - 1(\) - cmdid長度
*req = (int8 *)MQTT_MallocBuffer(*req_len + 1); //分配命令長度+1
if(*req == NULL)
{
MQTT_FreeBuffer(*cmdid);
return 3;
}
memset(*req, 0, *req_len + 1); //清零
memcpy(*req, (const int8 *)dataPtr, *req_len); //復(fù)制命令
return 0;
}
//==========================================================
// 函數(shù)名稱: MQTT_PacketCmdResp
//
// 函數(shù)功能: 命令回復(fù)組包
//
// 入口參數(shù): cmdid:cmdid
// req:命令
// mqttPacket:包指針
//
// 返回參數(shù): 0-成功 1-失敗
//
// 說明:
//==========================================================
uint1 MQTT_PacketCmdResp(const int8 *cmdid, const int8 *req, MQTT_PACKET_STRUCTURE *mqttPacket)
{
uint16 cmdid_len = strlen(cmdid);
uint16 req_len = strlen(req);
_Bool status = 0;
int8 *payload = MQTT_MallocBuffer(cmdid_len + 7);
if(payload == NULL)
return 1;
memset(payload, 0, cmdid_len + 7);
memcpy(payload, "$crsp/", 6);
strncat(payload, cmdid, cmdid_len);
if(MQTT_PacketPublish(MQTT_PUBLISH_ID, payload, req, strlen(req), MQTT_QOS_LEVEL0, 0, 1, mqttPacket) == 0)
status = 0;
else
status = 1;
MQTT_FreeBuffer(payload);
return status;
}
//==========================================================
// 函數(shù)名稱: MQTT_PacketSubscribe
//
// 函數(shù)功能: Subscribe消息組包
//
// 入口參數(shù): pkt_id:pkt_id
// qos:消息重發(fā)次數(shù)
// topics:訂閱的消息
// topics_cnt:訂閱的消息個數(shù)
// mqttPacket:包指針
//
// 返回參數(shù): 0-成功 其他-失敗
//
// 說明:
//==========================================================
uint8 MQTT_PacketSubscribe(uint16 pkt_id, enum MqttQosLevel qos, const int8 *topics[], uint8 topics_cnt, MQTT_PACKET_STRUCTURE *mqttPacket)
{
uint32 topic_len = 0, remain_len = 0;
int16 len = 0;
uint8 i = 0;
if(pkt_id == 0)
return 1;
//計(jì)算topic長度-------------------------------------------------------------------------
for(; i < topics_cnt; i++)
{
if(topics[i] == NULL)
return 2;
topic_len += strlen(topics[i]);
}
//2 bytes packet id + topic filter(2 bytes topic + topic length + 1 byte reserve)------
remain_len = 2 + 3 * topics_cnt + topic_len;
//分配內(nèi)存------------------------------------------------------------------------------
MQTT_NewBuffer(mqttPacket, remain_len + 5);
if(mqttPacket->_data == NULL)
return 3;
/*************************************固定頭部***********************************************/
//固定頭部----------------------頭部消息-------------------------------------------------
mqttPacket->_data[mqttPacket->_len++] = MQTT_PKT_SUBSCRIBE << 4 | 0x02;
//固定頭部----------------------剩余長度值-----------------------------------------------
len = MQTT_DumpLength(remain_len, mqttPacket->_data + mqttPacket->_len);
if(len < 0)
{
MQTT_DeleteBuffer(mqttPacket);
return 4;
}
else
mqttPacket->_len += len;
/*************************************payload***********************************************/
//payload----------------------pkt_id---------------------------------------------------
mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(pkt_id);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(pkt_id);
//payload----------------------topic_name-----------------------------------------------
for(i = 0; i < topics_cnt; i++)
{
topic_len = strlen(topics[i]);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(topic_len);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(topic_len);
strncat((int8 *)mqttPacket->_data + mqttPacket->_len, topics[i], topic_len);
mqttPacket->_len += topic_len;
mqttPacket->_data[mqttPacket->_len++] = qos & 0xFF;
}
return 0;
}
//==========================================================
// 函數(shù)名稱: MQTT_UnPacketSubscrebe
//
// 函數(shù)功能: Subscribe的回復(fù)消息解包
//
// 入口參數(shù): rev_data:接收到的信息
//
// 返回參數(shù): 0-成功 其他-失敗
//
// 說明:
//==========================================================
uint8 MQTT_UnPacketSubscribe(uint8 *rev_data)
{
uint8 result = 255;
if(rev_data[2] == MOSQ_MSB(MQTT_SUBSCRIBE_ID) && rev_data[3] == MOSQ_LSB(MQTT_SUBSCRIBE_ID))
{
switch(rev_data[4])
{
case 0x00:
case 0x01:
case 0x02:
//MQTT Subscribe OK
result = 0;
break;
case 0x80:
//MQTT Subscribe Failed
result = 1;
break;
default:
//MQTT Subscribe UnKnown Err
result = 2;
break;
}
}
return result;
}
//==========================================================
// 函數(shù)名稱: MQTT_PacketUnSubscribe
//
// 函數(shù)功能: UnSubscribe消息組包
//
// 入口參數(shù): pkt_id:pkt_id
// qos:消息重發(fā)次數(shù)
// topics:訂閱的消息
// topics_cnt:訂閱的消息個數(shù)
// mqttPacket:包指針
//
// 返回參數(shù): 0-成功 其他-失敗
//
// 說明:
//==========================================================
uint8 MQTT_PacketUnSubscribe(uint16 pkt_id, const int8 *topics[], uint8 topics_cnt, MQTT_PACKET_STRUCTURE *mqttPacket)
{
uint32 topic_len = 0, remain_len = 0;
int16 len = 0;
uint8 i = 0;
if(pkt_id == 0)
return 1;
//計(jì)算topic長度-------------------------------------------------------------------------
for(; i < topics_cnt; i++)
{
if(topics[i] == NULL)
return 2;
topic_len += strlen(topics[i]);
}
//2 bytes packet id, 2 bytes topic length + topic + 1 byte reserve---------------------
remain_len = 2 + (topics_cnt << 1) + topic_len;
//分配內(nèi)存------------------------------------------------------------------------------
MQTT_NewBuffer(mqttPacket, remain_len + 5);
if(mqttPacket->_data == NULL)
return 3;
/*************************************固定頭部***********************************************/
//固定頭部----------------------頭部消息-------------------------------------------------
mqttPacket->_data[mqttPacket->_len++] = MQTT_PKT_UNSUBSCRIBE << 4 | 0x02;
//固定頭部----------------------剩余長度值-----------------------------------------------
len = MQTT_DumpLength(remain_len, mqttPacket->_data + mqttPacket->_len);
if(len < 0)
{
MQTT_DeleteBuffer(mqttPacket);
return 4;
}
else
mqttPacket->_len += len;
/*************************************payload***********************************************/
//payload----------------------pkt_id---------------------------------------------------
mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(pkt_id);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(pkt_id);
//payload----------------------topic_name-----------------------------------------------
for(i = 0; i < topics_cnt; i++)
{
topic_len = strlen(topics[i]);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(topic_len);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(topic_len);
strncat((int8 *)mqttPacket->_data + mqttPacket->_len, topics[i], topic_len);
mqttPacket->_len += topic_len;
}
return 0;
}
//==========================================================
// 函數(shù)名稱: MQTT_UnPacketUnSubscribe
//
// 函數(shù)功能: UnSubscribe的回復(fù)消息解包
//
// 入口參數(shù): rev_data:接收到的信息
//
// 返回參數(shù): 0-成功 其他-失敗
//
// 說明:
//==========================================================
uint1 MQTT_UnPacketUnSubscribe(uint8 *rev_data)
{
uint1 result = 1;
if(rev_data[2] == MOSQ_MSB(MQTT_UNSUBSCRIBE_ID) && rev_data[3] == MOSQ_LSB(MQTT_UNSUBSCRIBE_ID))
{
result = 0;
}
return result;
}
//==========================================================
// 函數(shù)名稱: MQTT_PacketPublish
//
// 函數(shù)功能: Pulish消息組包
//
// 入口參數(shù): pkt_id:pkt_id
// topic:發(fā)布的topic
// payload:消息體
// payload_len:消息體長度
// qos:重發(fā)次數(shù)
// retain:離線消息推送
// own:
// mqttPacket:包指針
//
// 返回參數(shù): 0-成功 其他-失敗
//
// 說明:
//==========================================================
uint8 MQTT_PacketPublish(uint16 pkt_id, const int8 *topic,
const int8 *payload, uint32 payload_len,
enum MqttQosLevel qos, int32 retain, int32 own,
MQTT_PACKET_STRUCTURE *mqttPacket)
{
uint32 total_len = 0, topic_len = 0;
uint32 data_len = 0;
int32 len = 0;
uint8 flags = 0;
//pkt_id檢查----------------------------------------------------------------------------
if(pkt_id == 0)
return 1;
//$dp為系統(tǒng)上傳數(shù)據(jù)點(diǎn)的指令--------------------------------------------------------------
for(topic_len = 0; topic[topic_len] != '\0'; ++topic_len)
{
if((topic[topic_len] == '#') || (topic[topic_len] == '+'))
return 2;
}
//Publish消息---------------------------------------------------------------------------
flags |= MQTT_PKT_PUBLISH << 4;
//retain標(biāo)志----------------------------------------------------------------------------
if(retain)
flags |= 0x01;
//總長度--------------------------------------------------------------------------------
total_len = topic_len + payload_len + 2;
//qos級別--主要用于PUBLISH(發(fā)布態(tài))消息的,保證消息傳遞的次數(shù)-----------------------------
switch(qos)
{
case MQTT_QOS_LEVEL0:
flags |= MQTT_CONNECT_WILL_QOS0; //最多一次
break;
case MQTT_QOS_LEVEL1:
flags |= 0x02; //最少一次
total_len += 2;
break;
case MQTT_QOS_LEVEL2:
flags |= 0x04; //只有一次
total_len += 2;
break;
default:
return 3;
}
//分配內(nèi)存------------------------------------------------------------------------------
if(payload != NULL)
{
if(payload[0] == 2)
{
uint32 data_len_t = 0;
while(payload[data_len_t++] != '}');
data_len_t -= 3;
data_len = data_len_t + 7;
data_len_t = payload_len - data_len;
MQTT_NewBuffer(mqttPacket, total_len + 3 - data_len_t);
if(mqttPacket->_data == NULL)
return 4;
memset(mqttPacket->_data, 0, total_len + 3 - data_len_t);
}
else
{
MQTT_NewBuffer(mqttPacket, total_len + 3);
if(mqttPacket->_data == NULL)
return 4;
memset(mqttPacket->_data, 0, total_len + 3);
}
}
else
{
MQTT_NewBuffer(mqttPacket, total_len + 3);
if(mqttPacket->_data == NULL)
return 4;
memset(mqttPacket->_data, 0, total_len + 3);
}
/*************************************固定頭部***********************************************/
//固定頭部----------------------頭部消息-------------------------------------------------
mqttPacket->_data[mqttPacket->_len++] = flags;
//固定頭部----------------------剩余長度值-----------------------------------------------
len = MQTT_DumpLength(total_len, mqttPacket->_data + mqttPacket->_len);
if(len < 0)
{
MQTT_DeleteBuffer(mqttPacket);
return 5;
}
else
mqttPacket->_len += len;
/*************************************可變頭部***********************************************/
//可變頭部----------------------寫入topic長度、topic-------------------------------------
mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(topic_len);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(topic_len);
strncat((int8 *)mqttPacket->_data + mqttPacket->_len, topic, topic_len);
mqttPacket->_len += topic_len;
if(qos != MQTT_QOS_LEVEL0)
{
mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(pkt_id);
mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(pkt_id);
}
//可變頭部----------------------寫入payload----------------------------------------------
if(payload != NULL)
{
if(payload[0] == 2)
{
memcpy((int8 *)mqttPacket->_data + mqttPacket->_len, payload, data_len);
mqttPacket->_len += data_len;
}
else
{
memcpy((int8 *)mqttPacket->_data + mqttPacket->_len, payload, payload_len);
mqttPacket->_len += payload_len;
}
}
return 0;
}
//==========================================================
// 函數(shù)名稱: MQTT_UnPacketPublish
//
// 函數(shù)功能: Publish消息解包
//
// 入口參數(shù): flags:MQTT相關(guān)標(biāo)志信息
// pkt:指向可變頭部
// size:固定頭部中的剩余長度信息
//
// 返回參數(shù): 0-成功 其他-失敗原因
//
// 說明:
//==========================================================
uint8 MQTT_UnPacketPublish(uint8 *rev_data, int8 **topic, uint16 *topic_len, int8 **payload, uint16 *payload_len, uint8 *qos, uint16 *pkt_id)
{
const int8 flags = rev_data[0] & 0x0F;
uint8 *msgPtr;
uint32 remain_len = 0;
const int8 dup = flags & 0x08;
*qos = (flags & 0x06) >> 1;
msgPtr = rev_data + MQTT_ReadLength(rev_data + 1, 4, &remain_len) + 1;
if(remain_len < 2 || flags & 0x01) //retain
return 255;
*topic_len = (uint16)msgPtr[0] << 8 | msgPtr[1];
if(remain_len < *topic_len + 2)
return 255;
if(strstr((int8 *)msgPtr + 2, CMD_TOPIC_PREFIX) != NULL) //如果是命令下發(fā)
return MQTT_PKT_CMD;
switch(*qos)
{
case MQTT_QOS_LEVEL0: // qos0 have no packet identifier
if(0 != dup)
return 255;
*topic = MQTT_MallocBuffer(*topic_len + 1); //為topic分配內(nèi)存
if(*topic == NULL)
**自我介紹一下,小編13年上海交大畢業(yè),曾經(jīng)在小公司待過,也去過華為、OPPO等大廠,18年進(jìn)入阿里一直到現(xiàn)在。**
**深知大多數(shù)嵌入式工程師,想要提升技能,往往是自己摸索成長或者是報班學(xué)習(xí),但對于培訓(xùn)機(jī)構(gòu)動則幾千的學(xué)費(fèi),著實(shí)壓力不小。自己不成體系的自學(xué)效果低效又漫長,而且極易碰到天花板技術(shù)停滯不前!**
**因此收集整理了一份《2024年嵌入式&物聯(lián)網(wǎng)開發(fā)全套學(xué)習(xí)資料》,初衷也很簡單,就是希望能夠幫助到想自學(xué)提升又不知道該從何學(xué)起的朋友,同時減輕大家的負(fù)擔(dān)。**
**既有適合小白學(xué)習(xí)的零基礎(chǔ)資料,也有適合3年以上經(jīng)驗(yàn)的小伙伴深入學(xué)習(xí)提升的進(jìn)階課程,基本涵蓋了95%以上嵌入式&物聯(lián)網(wǎng)開發(fā)知識點(diǎn),真正體系化!**
**由于文件比較大,這里只是將部分目錄大綱截圖出來,每個節(jié)點(diǎn)里面都包含大廠面經(jīng)、學(xué)習(xí)筆記、源碼講義、實(shí)戰(zhàn)項(xiàng)目、講解視頻,并且后續(xù)會持續(xù)更新**
**如果你覺得這些內(nèi)容對你有幫助,可以+V:Vip1104z獲?。。?! (備注:嵌入式)**
<img src="https://
# 最后
**資料整理不易,覺得有幫助的朋友可以幫忙點(diǎn)贊分享支持一下小編~**
**你的支持,我的動力;祝各位前程似錦,offer不斷,步步高升?。?!**
QTT_UnPacketPublish
//
// 函數(shù)功能: Publish消息解包
//
// 入口參數(shù): flags:MQTT相關(guān)標(biāo)志信息
// pkt:指向可變頭部
// size:固定頭部中的剩余長度信息
//
// 返回參數(shù): 0-成功 其他-失敗原因
//
// 說明:
//==========================================================
uint8 MQTT_UnPacketPublish(uint8 *rev_data, int8 **topic, uint16 *topic_len, int8 **payload, uint16 *payload_len, uint8 *qos, uint16 *pkt_id)
{
const int8 flags = rev_data[0] & 0x0F;
uint8 *msgPtr;
uint32 remain_len = 0;
const int8 dup = flags & 0x08;
*qos = (flags & 0x06) >> 1;
msgPtr = rev_data + MQTT_ReadLength(rev_data + 1, 4, &remain_len) + 1;
if(remain_len < 2 || flags & 0x01) //retain
return 255;
*topic_len = (uint16)msgPtr[0] << 8 | msgPtr[1];
if(remain_len < *topic_len + 2)
return 255;
if(strstr((int8 *)msgPtr + 2, CMD_TOPIC_PREFIX) != NULL) //如果是命令下發(fā)
return MQTT_PKT_CMD;
switch(*qos)
{
case MQTT_QOS_LEVEL0: // qos0 have no packet identifier
if(0 != dup)
return 255;
*topic = MQTT_MallocBuffer(*topic_len + 1); //為topic分配內(nèi)存
if(*topic == NULL)
**自我介紹一下,小編13年上海交大畢業(yè),曾經(jīng)在小公司待過,也去過華為、OPPO等大廠,18年進(jìn)入阿里一直到現(xiàn)在。**
**深知大多數(shù)嵌入式工程師,想要提升技能,往往是自己摸索成長或者是報班學(xué)習(xí),但對于培訓(xùn)機(jī)構(gòu)動則幾千的學(xué)費(fèi),著實(shí)壓力不小。自己不成體系的自學(xué)效果低效又漫長,而且極易碰到天花板技術(shù)停滯不前!**
**因此收集整理了一份《2024年嵌入式&物聯(lián)網(wǎng)開發(fā)全套學(xué)習(xí)資料》,初衷也很簡單,就是希望能夠幫助到想自學(xué)提升又不知道該從何學(xué)起的朋友,同時減輕大家的負(fù)擔(dān)。**
[外鏈圖片轉(zhuǎn)存中...(img-nfFe5JgU-1712383617303)]
[外鏈圖片轉(zhuǎn)存中...(img-ETMuxvPD-1712383617305)]
[外鏈圖片轉(zhuǎn)存中...(img-QtSnjIya-1712383617306)]
**既有適合小白學(xué)習(xí)的零基礎(chǔ)資料,也有適合3年以上經(jīng)驗(yàn)的小伙伴深入學(xué)習(xí)提升的進(jìn)階課程,基本涵蓋了95%以上嵌入式&物聯(lián)網(wǎng)開發(fā)知識點(diǎn),真正體系化!**
[外鏈圖片轉(zhuǎn)存中...(img-QlMEqNSw-1712383617307)]
[外鏈圖片轉(zhuǎn)存中...(img-Q5oCRtDn-1712383617308)]
**由于文件比較大,這里只是將部分目錄大綱截圖出來,每個節(jié)點(diǎn)里面都包含大廠面經(jīng)、學(xué)習(xí)筆記、源碼講義、實(shí)戰(zhàn)項(xiàng)目、講解視頻,并且后續(xù)會持續(xù)更新**
**如果你覺得這些內(nèi)容對你有幫助,可以+V:Vip1104z獲?。。?! (備注:嵌入式)**
<img src="https://
# 最后
**資料整理不易,覺得有幫助的朋友可以幫忙點(diǎn)贊分享支持一下小編~**
**你的支持,我的動力;祝各位前程似錦,offer不斷,步步高升?。?!**
**[更多資料點(diǎn)擊此處獲qu??!](https://bbs.csdn.net/topics/618376385)**
到了這里,關(guān)于基于STM32與OneNet平臺的智能家居系統(tǒng)設(shè)計(jì)(代碼開源含自制APP代碼)_onenet 編程的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!