簡介
這裏主要講的是進行MQTT移植,對於阿里雲物聯網的連接的開發,可以使用阿里雲提供的代碼,也可以使用標準的MQTT協議代碼。
參考了網上很多的帖子,經過多方嘗試,發現這兩種方法都不可行,就採用了自己的方法,先用wireshark抓網絡包,然後讓esp8266模擬這種網絡包。
MQTT.fx接入物聯網平臺
這部分可以參考阿里雲物聯網文檔,即
阿里雲物聯網平臺 > 最佳實踐 > 設備接入 > 使用MQTT.fx接入物聯網平臺
通過wireshark監控計算機的端口1883,抓取到的MQTT數據幀有如下類型:
- Connect Command
- Connect Ack
- Subscribe Request
- Subscribe Ack
- Publish Message
- Publish Ack
- Ping Request
- Ping Respose
其中,本地發給阿里雲數據幀1,則阿里雲返回數據幀2;本地發給阿里雲數據幀3,則阿里雲返回數據幀4;本地發給阿里雲5,則阿里雲返回數據幀6(前提是QoS=1);本地發給阿里雲數據幀7,則阿里雲返回數據幀8。當阿里雲向下傳輸數據幀5時,本地不做應答。
參考MQTT協議版本3,在抓取到的數據的基礎上,仿寫了類似的數據幀實現了相應的功能。
生成MQTT數據幀
在程序中定義瞭如下幾個函數,完成connect、subscribe、publish和ping功能。
u8 iot_connect(void);
u8 iot_subscribe(void);
u8 iot_sendheart(void);
u8 iot_publishdata(float temp, float humi);
u16 cGetDataPUBLISH(u8 *buff,u8 qos,u8 *topic,u8 *msg);
注意事項
在mqtt.c文件中,用到了malloc.h文件,因爲在給一個buf申請內存之前,要初始化MCU的存儲空間。也就是keil工程要添加sram.c、sram.h、XMRAM.h和XMRAM.lib文件,在main函數中要有:
my_mem_init(SRAMIN); //初始化內部內存池
相關程序源碼
啥也別說了,直接上代碼。
mqtt.c
#include "mqtt.h"
#include <stdlib.h>
#include "string.h"
#include "malloc.h"
#include "usart3.h"
#include "usart.h"
#include "delay.h"
//連接成功服務器迴應 20 02 00 00
//客戶端主動斷開連接 e0 00
const u8 mqtt_connetAck[] = {0x20,0x02,0x00,0x00};
const u8 mqtt_pubAck[] = {0x40,0x02,0x00,0x01};
const u8 mqtt_disconnet[] = {0xe0,0x00};
const u8 mqtt_heart[] = {0xc0,0x00};
const u8 mqtt_heartAck[] = {0xd0,0x00};
const u8 mqtt_subAck[] = {0x90,0x03};
uint16_t buflen=200;
unsigned char buf[200];
char ClientID[128];
u8 ClientID_len;
char Username[128];
u8 Username_len;
char Password[128];
u8 Password_len;
u8 iot_connect(void)
{
uint32_t buflen;
u16 i;
u8* buf;
buf=mymalloc(SRAMIN,200); //申請200字節內存
GetDataConnet(buf);
buflen = buf[1]+2;
// 連接兩次不成功則返回0
u8 cnt=2; // 嘗試發送次數
u8 wait=30; // 等待時間
while(cnt--)
{
USART3_RX_STA=0;
HAL_UART_Transmit(&UART1_Handler, buf, buflen,1000); // for DEBUG
HAL_UART_Transmit(&UART3_Handler, buf, buflen,1000);
while(wait--)
{
if(USART3_RX_STA&0x8000) // 收到應答信號,uart1轉發該應答信號
{
if(USART3_RX_BUF[0]==mqtt_connetAck[0] && USART3_RX_BUF[1]==mqtt_connetAck[1]) //連接成功
{
myfree(SRAMIN,buf); //釋放內存
USART3_RX_STA=0;
printf("\r\nconnect successful!\r\n");
return 1; // 連接成功
}
}
delay_ms(100);
}
}
myfree(SRAMIN,buf); //釋放內存
USART3_RX_STA=0;
return 0;
}
u8 iot_subscribe(void)
{
uint32_t buflen;
u16 i;
u8* buf;
buf=mymalloc(SRAMIN,200); //申請200字節內存
GetDataSUBSCRIBE(buf,S_TOPIC_NAME,2,0);
buflen = buf[1]+2;
// 連接兩次不成功則返回0
u8 cnt=2; // 嘗試發送次數
u8 wait=30; // 等待時間
while(cnt--)
{
USART3_RX_STA=0;
HAL_UART_Transmit(&UART1_Handler, buf, buflen,1000); // For DEBUG
HAL_UART_Transmit(&UART3_Handler, buf, buflen,1000);
while(wait--)
{
if(USART3_RX_STA&0x8000) // 收到應答信號,uart1轉發該應答信號
{
if(USART3_RX_BUF[0]==mqtt_subAck[0] && USART3_RX_BUF[1]==mqtt_subAck[1]) //連接成功
{
myfree(SRAMIN,buf); //釋放內存
USART3_RX_STA=0;
printf("\r\nsubscribe successful!\r\n");
return 1; // 連接成功
}
}
delay_ms(100);
}
}
myfree(SRAMIN,buf); //釋放內存
USART3_RX_STA=0;
return 0;
}
u8 iot_sendheart(void)
{
uint32_t buflen;
u16 i;
u8* buf;
buf=mymalloc(SRAMIN,20); //申請20字節內存
// GetDataPINGREQ(buf);
buf[0]=mqtt_heart[0];
buf[1]=mqtt_heart[1];
buflen = 2;
// 連接兩次不成功則返回0
u8 cnt=2; // 嘗試發送次數
u16 wait=300; // 等待時間
while(cnt--)
{
USART3_RX_STA=0;
HAL_UART_Transmit(&UART1_Handler, buf, buflen,1000); // For DEBUG
HAL_UART_Transmit(&UART3_Handler, buf, buflen,1000);
while(wait--)
{
if(USART3_RX_STA&0x8000) // 收到應答信號,uart1轉發該應答信號
{
if(USART3_RX_BUF[0]==mqtt_heartAck[0] && USART3_RX_BUF[1]==mqtt_heartAck[1]) //連接成功
{
myfree(SRAMIN,buf); //釋放內存
USART3_RX_STA=0;
printf("\r\nsendheart successful! (cnt,wait)=%d,%d\r\n", cnt,wait);
return 1; // 連接成功
}
}
delay_ms(10);
}
}
myfree(SRAMIN,buf); //釋放內存
USART3_RX_STA=0;
printf("\r\nsendheart failed! (cnt,wait)=%d,%d\r\n", cnt,wait);
return 0;
}
u8 iot_publishdata(float temp, float humi)
{
uint32_t buflen;
u16 i;
u8* buf;
buf=mymalloc(SRAMIN,200); //申請200字節內存
u8* msg;
msg=mymalloc(SRAMIN,200); //申請200字節內存
sprintf((char*)msg,"{\"method\":\"thing.event.property.post\",\"id\":\"477655683\",\"params\":{\"temperature\":%.1f,\"humidity\":%.1f}}", temp, humi);
buflen = cGetDataPUBLISH(buf,1,P_TOPIC_NAME,msg);
printf("publishdata: topiclen=%d,msglen=%d\r\n",strlen(P_TOPIC_NAME),strlen(msg));
// 連接兩次不成功則返回0
int cnt=2; // 嘗試發送次數
int wait=300; // 等待時間
while(cnt>0)
{
USART3_RX_STA=0;
HAL_UART_Transmit(&UART1_Handler, buf, buflen,1000); // For DEBUG
HAL_UART_Transmit(&UART3_Handler, buf, buflen,1000);
while(wait>0)
{
if(USART3_RX_STA&0x8000) // 收到應答信號,uart1轉發該應答信號
{
if(USART3_RX_BUF[0]==mqtt_pubAck[0] && USART3_RX_BUF[1]==mqtt_pubAck[1]) //發送成功
{
myfree(SRAMIN,msg); //釋放內存
myfree(SRAMIN,buf); //釋放內存
USART3_RX_STA=0;
printf("\r\npublish data successful! (cnt,wait)=%d,%d\r\n", cnt,wait);
return 1; // 連接成功
}
}
delay_ms(10);
wait--;
}
cnt--;
}
myfree(SRAMIN,msg); //釋放內存
myfree(SRAMIN,buf); //釋放內存
USART3_RX_STA=0;
printf("\r\npublish data failed! (cnt,wait)=%d,%d\r\n", cnt,wait);
return 0;
}
unsigned char GetDataFixedHead(unsigned char MesType,unsigned char DupFlag,unsigned char QosLevel,unsigned char Retain)
{
unsigned char dat = 0;
dat = (MesType & 0x0f) << 4;
dat |= (DupFlag & 0x01) << 3;
dat |= (QosLevel & 0x03) << 1;
dat |= (Retain & 0x01);
return dat;
}
void GetDataConnet(unsigned char *buff)//獲取連接的數據包正確連接返回20 02 00 00
{
unsigned int i,len,lennum = 0;
unsigned char *msg;
buff[0] = GetDataFixedHead(MQTT_TypeCONNECT,0,0,0);
buff[2] = 0x00;
buff[3] = 0x04;
buff[4] = 'M';
buff[5] = 'Q';
buff[6] = 'T';
buff[7] = 'T';
buff[8] = 0x04;//協議級別 Protocol Level
buff[9] = 0 | (MQTT_StaCleanSession << 1) | (MQTT_StaWillFlag << 1)
| (MQTT_StaWillQoS << 3) | (MQTT_StaWillRetain << 5)
| (MQTT_StaPasswordFlag << 6) |(MQTT_StaUserNameFlag << 7);//連接標誌
buff[10] = MQTT_KeepAlive >> 8;
buff[11] = MQTT_KeepAlive;
len = strlen(MQTT_ClientIdentifier);
buff[12] = len >> 8;
buff[13] = len;
msg = MQTT_ClientIdentifier;
for(i = 0;i<len;i++)
{
buff[14+i] = msg[i];
}
lennum += len;
if(MQTT_StaWillFlag)
{
len = strlen(MQTT_WillTopic);
buff[13 + lennum + 1] = len >> 8;
buff[13 + lennum + 2] = len;
lennum += 2;
msg = MQTT_WillTopic;
for(i = 0;i<len;i++)
{
buff[14+lennum+i] = msg[i];
}
lennum += len;
len = strlen(MQTT_WillMessage);
buff[12] = len >> 8;
buff[13] = len;
lennum += 2;
msg = MQTT_WillMessage;
for(i = 0;i<len;i++)
{
buff[14+lennum+i] = msg[i];
}
lennum += len;
}
if(MQTT_StaUserNameFlag)
{
len = strlen(MQTT_UserName);
buff[13 + lennum + 1] = len >> 8;
buff[13 + lennum + 2] = len;
lennum += 2;
msg = MQTT_UserName;
for(i = 0;i<len;i++)
{
buff[14+lennum+i] = msg[i];
}
lennum += len;
}
if(MQTT_StaPasswordFlag)
{
len = strlen(MQTT_Password);
buff[13 + lennum + 1] = len >> 8;
buff[13 + lennum + 2] = len;
lennum += 2;
msg = MQTT_Password;
for(i = 0;i<len;i++)
{
buff[14+lennum+i] = msg[i];
}
lennum += len;
}
buff[1] = 13 + lennum - 1;
}
void GetDataDisConnet(unsigned char *buff)//獲取斷開連接的數據包
{
buff[0] = 0xe0;
buff[1] = 0;
}
void GetDataPINGREQ(unsigned char *buff)//心跳請求的數據包成功返回d0 00
{
buff[0] = 0xc0;
buff[1] = 0;
}
//訂閱主題的數據包
//Num:主題序號
//RequestedQoS:服務質量要求0,1或2
//成功返回90 0x 00 Num RequestedQoS
void GetDataSUBSCRIBE(unsigned char *buff,const char *dat,unsigned int Num,unsigned char RequestedQoS)
{
unsigned int i,len = 0,lennum = 0;
buff[0] = 0x82;
len = strlen(dat);
buff[2] = Num>>8;
buff[3] = Num;
buff[4] = len>>8;
buff[5] = len;
for(i = 0;i<len;i++)
{
buff[6+i] = dat[i];
}
lennum = len;
buff[6 + lennum ] = RequestedQoS;
buff[1] = lennum + 5;
}
void GetDataPUBLISH(unsigned char *buff,unsigned char dup, unsigned char qos,unsigned char retain,const char *topic ,const char *msg)//獲取發佈消息的數據包
{
unsigned int i,len=0,lennum=0;
buff[0] = GetDataFixedHead(MQTT_TypePUBLISH,dup,qos,retain);
len = strlen(topic);
buff[2] = len>>8;
buff[3] = len;
for(i = 0;i<len;i++)
{
buff[4+i] = topic[i];
}
lennum = len;
len = strlen(msg);
for(i = 0;i<len;i++)
{
buff[4+i+lennum] = msg[i];
}
lennum += len;
buff[1] = lennum + 2;
}
u16 cGetDataPUBLISH(u8 *buff,u8 qos,u8 *topic,u8 *msg)//獲取發佈消息的數據包
{
int topiclen = strlen(topic);
int msglen = strlen(msg);
u16 framelen;
u16 len=0;
static u16 id=0; // message ID
if(qos) framelen = (2+topiclen) + 2 + msglen;
else framelen = (2+topiclen) + msglen;
// qos=1 則mqtt服務器有ACK信號返回
buff[len++] = GetDataFixedHead(MQTT_TypePUBLISH,0,qos,0); // MQTT Message Type PUBLISH
// Rest length
do
{
u8 encodedByte = framelen % 128;
framelen = framelen / 128;
// if there are more data to encode, set the top bit of this byte, see MQTT-3.1.1 manual
if(framelen > 0)
encodedByte = encodedByte | 128;
buff[len++] = encodedByte;
}while(framelen > 0);
buff[len++] = topiclen / 256; // MSB
buff[len++] = topiclen % 256; // LSB
memcpy(&buff[len], topic, topiclen);
len += topiclen;
if(qos)
{
buff[len++] = id / 256;
buff[len++] = id % 256;
id++;
}
memcpy(&buff[len], msg, msglen);
len += msglen;
return len;
}
mqtt.h
#ifndef __MQTT_H
#define __MQTT_H
#include "sys.h"
#include <string.h>
#include "transport.h"
#define IOT_DOMAIN_NAME "productkey.iot-as-mqtt.cn-shanghai.aliyuncs.com"
#define IOT_PORTNUM "1883"
#define PRODUCTKEY "<productkey>"
#define PRODUCTKEY_LEN strlen(PRODUCTKEY)
#define DEVICENAME "<devicename>"
#define DEVICENAME_LEN strlen(DEVICENAME)
#define DEVICESECRE "<devicesecre>"
#define DEVICESECRE_LEN strlen(DEVICESECRE)
#define S_TOPIC_NAME "/sys/<productkey>/<devicename>/thing/service/property/set"
#define P_TOPIC_NAME "/sys/<productkey>/<devicename>/thing/event/property/post"
extern uint16_t buflen;
extern unsigned char buf[200];
u8 iot_connect(void);
u8 iot_subscribe(void);
u8 iot_sendheart(void);
u8 iot_publishdata(float temp, float humi);
u16 cGetDataPUBLISH(u8 *buff,u8 qos,u8 *topic,u8 *msg);
///////////////////////////////////////////////////////////////////////////////////
#define MQTT_TypeCONNECT 1//請求連接
#define MQTT_TypeCONNACK 2//請求應答
#define MQTT_TypePUBLISH 3//發佈消息
#define MQTT_TypePUBACK 4//發佈應答
#define MQTT_TypePUBREC 5//發佈已接收,保證傳遞1
#define MQTT_TypePUBREL 6//發佈釋放,保證傳遞2
#define MQTT_TypePUBCOMP 7//發佈完成,保證傳遞3
#define MQTT_TypeSUBSCRIBE 8//訂閱請求
#define MQTT_TypeSUBACK 9//訂閱應答
#define MQTT_TypeUNSUBSCRIBE 10//取消訂閱
#define MQTT_TypeUNSUBACK 11//取消訂閱應答
#define MQTT_TypePINGREQ 12//ping請求
#define MQTT_TypePINGRESP 13//ping響應
#define MQTT_TypeDISCONNECT 14//斷開連接
#define MQTT_StaCleanSession 1 //清理會話
#define MQTT_StaWillFlag 0 //遺囑標誌
#define MQTT_StaWillQoS 0 //遺囑QoS連接標誌的第4和第3位。
#define MQTT_StaWillRetain 0 //遺囑保留
#define MQTT_StaUserNameFlag 1 //用戶名標誌 User Name Flag
#define MQTT_StaPasswordFlag 1 //密碼標誌 Password Flag
#define MQTT_KeepAlive 60
#define MQTT_ClientIdentifier "<devicename>|securemode=3,signmethod=hmacsha1|" //客戶端標識符 Client Identifier
#define MQTT_WillTopic "" //遺囑主題 Will Topic
#define MQTT_WillMessage "" //遺囑消息 Will Message
#define MQTT_UserName "<devicename>&<productkey>" //用戶名 User Name
#define MQTT_Password "<password>" //密碼 Password
unsigned char GetDataFixedHead(unsigned char MesType,unsigned char DupFlag,unsigned char QosLevel,unsigned char Retain);
void GetDataPUBLISH(unsigned char *buff,unsigned char dup, unsigned char qos,unsigned char retain,const char *topic ,const char *msg);//獲取發佈消息的數據包
void GetDataSUBSCRIBE(unsigned char *buff,const char *dat,unsigned int Num,unsigned char RequestedQoS);//訂閱主題的數據包 Num:主題序號 RequestedQoS:服務質量要求0,1或2
void GetDataDisConnet(unsigned char *buff);//獲取斷開連接的數據包
void GetDataConnet(unsigned char *buff);//獲取連接的數據包正確連接返回20 02 00 00
void GetDataPINGREQ(unsigned char *buff);//心跳請求的數據包成功返回d0 00
#endif