当前位置:网站首页>STM32+ESP8266+MQTT协议连接OneNet物联网平台

STM32+ESP8266+MQTT协议连接OneNet物联网平台

2022-07-06 10:17:00 华为云

一、环境介绍

单片机采用: STM32F103C8T6

上网方式: 采用ESP8266,也可以使用其他设备代替,只要支持TCP协议即可。比如:GSM模块、有线网卡等。

开发软件: keil5

硬件连接功能: ESP8266接在STM32的串口3上。通过AT指令与ESP8266进行通信。

二、功能介绍

2.1 功能说明

通过OneNet物联网服务器实现设备数据远程上传、下发,实现数据交互(不清楚OneNet物联网服务器功能的可以百度一下进入官网看简介)。之前的OneNet服务器不支持标准MQTT协议登录的,现在官网更新之后支持标准的MQTT协议,本篇文章介绍使用STM32+ESP8266使用标准MQTT协议登录Onenet服务器,实现数据交互。实现步骤OneNet官方提供了很详细的文档,可以参考一下。

文档地址:OneNET-中国移动物联网开放平台

img![点击并拖拽以移动]

img![点击并拖拽以移动]

2.2 硬件资源

在当前使用的开发板上有4盏LED灯、一个蜂鸣器、4个按键,ESP8266型号是ESP-12F,STM32型号是:STM32F103C8T6。

img![点击并拖拽以移动]

三、OneNet支持的MQTT协议版本

目前OneNet服务器支持MQTT 3.1.1版本,MQTT协议官网: http://mqtt.org/?spm=a2c4g.11186623.2.11.19083f86gxhJ7h

报文支持情况: 支持connect、subscribe、publish、ping、unsubscribe、disconnect等报文,不支持pubrec、pubrel、pubcomp报文。

img![点击并拖拽以移动]

四、登录OneNet服务器创建物联网产品

没有注册账号的,需要提前登录官网注册账号,再进入下面步骤:

img![点击并拖拽以移动]

img![点击并拖拽以移动]

img![点击并拖拽以移动]

这里根据自己产品情况填写。

img![点击并拖拽以移动]

img![点击并拖拽以移动]

img![点击并拖拽以移动]

产品创建成功之后,点击产品名称,跳转页面,继续添加设备。

img![点击并拖拽以移动]

img![点击并拖拽以移动]

img![点击并拖拽以移动]

img![点击并拖拽以移动]

img![点击并拖拽以移动]

img![点击并拖拽以移动]

img![点击并拖拽以移动]

img![点击并拖拽以移动]

img![点击并拖拽以移动]

img![点击并拖拽以移动]

img![点击并拖拽以移动]

img![点击并拖拽以移动]

img![点击并拖拽以移动]

img![点击并拖拽以移动]

img![点击并拖拽以移动]

img![点击并拖拽以移动]

下面选择仪表盘的数据来源,根据自己创建的数据点选择。

img![点击并拖拽以移动]
创建一个文本控件,显示数据点更新的时间,方便调试。

img![点击并拖拽以移动]

img![点击并拖拽以移动]

img![点击并拖拽以移动]
img![点击并拖拽以移动]

OneNte有手机版本的APP,登录之后也可以看到该页面。

下载地址:https://open.iot.10086.cn/doc/book/device-develop/multpro/sdk-doc-tool/APP.html

img![点击并拖拽以移动]

img![点击并拖拽以移动]

下面是手机上登录APP看到的界面效果:

img![点击并拖拽以移动]
img![点击并拖拽以移动]

五、OneNet服务器MQTT登录地址与订阅主题相关格式介绍

官网介绍文档地址: 设备连接_开发者文档_OneNET

img![点击并拖拽以移动]

5.1 MQTT服务器登录地址

img![点击并拖拽以移动]

目前MQTT协议支持两个IP地址和端口号,一个需要加密、一个不需要加密。

注意:单片机上移植加密算法很麻烦,这里采用不需要加密的端口。(IP地址: 183.230.40.96 端口: 1883)

img![点击并拖拽以移动]

5.2 MQTT登录的:设备ID、用户名称、密码 格式参数

img![点击并拖拽以移动]

img![点击并拖拽以移动]

img![点击并拖拽以移动]

上面图片里说明了,OneNet的设备参数与标准MQTT协议的登录参数对应关系。 OneNet的设备参数,在设备页面可以去查看。

登录密码生成看下面步骤:

img![点击并拖拽以移动]

img![点击并拖拽以移动]

img![点击并拖拽以移动]

注意:该工具在win10系统运行可能会提示非信任程序,点击任要运行即可。

下面是生成MQTT登录密匙的工具使用示例。

img点击并拖拽以移动编辑

注意:工具中填的参数说明请看文档介绍。

res选项参数的格式: products/{产品ID}/devices/{设备名称}

et是设置token过期时间:算出1970-1-1到你想要设置的到期时间,单位是秒,填入即可。

比如: 超时时间设置为2020-07-20 ,那么,这里填入的秒就是:1970-1-1到2020-07-20之间的秒单位时间。

Linux下代码:

#include <stdio.h>#include <time.h> #include <time.h>int main(){    time_t time_sec;    time_sec=time(NULL);  //当前的秒单位时间--UTC时间	printf("当前时间(秒):%ld\n",time_sec);	printf("加30天的时间(秒):%ld\n",time_sec+30*24*60*60);	return 0;}

key的参数格式: 就是设备创建之后,在设备详情页的key

工具生成的结果值,直接当做MQTT登录的密码。

5.3 主题订阅格式

文档地址:协议规范_开发者文档_OneNET

img![点击并拖拽以移动]

img![点击并拖拽以移动]

5.4 设备保活时间

img![点击并拖拽以移动]

img![点击并拖拽以移动]

5.5 向服务器传数据点

img![点击并拖拽以移动]

img![点击并拖拽以移动]

img![点击并拖拽以移动]

六、核心代码

6.1 matt.c代码

#include "mqtt.h"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 00const 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(USART3_RX_FLAG)        {            memcpy((char *)mqtt_rxbuf,USART3_RX_BUFFER,USART3_RX_CNT);                        //memcpy                        for(i=0;i<USART3_RX_CNT;i++)USART1_Printf("%#x ",USART3_RX_BUFFER[i]);                        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;}/*函数功能: 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(USART3_RX_FLAG)			{                memcpy((char *)mqtt_rxbuf,(char*)USART3_RX_BUFFER,USART3_RX_CNT);				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发布数据打包函数//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;}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);}	

6.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))    //用户名初始化void OneNet_LoginInit(char *ProductKey,char *DeviceName,char *DeviceSecret);//MQTT协议相关函数声明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

6.3 main.c 主函数代码

#include "stm32f10x.h"#include "led.h"#include "delay.h"#include "key.h"#include "usart.h"#include <string.h>#include "timer.h"#include "esp8266.h"#include "mqtt.h"/*序号 符号 编码1 + %2B2 空格%203 / %2F4 ? %3F5 % %256 # %237 & %268 = %3D*///OneNet物联网服务器的设备信息#define MQTT_ClientID "mq2"#define MQTT_UserName "361594"#define MQTT_PassWord "version=2018-10-31&res=products%2F361594%2Fdevices%2Fmq2&et=1597492895&method=sha1&sign=uqvA0KkjXw0FlN01aT6fWrGBLGw%3D"//订阅与发布的主题//格式:$sys/{产品ID}/{设备名称}/##define SET_TOPIC  "$sys/361594/mq2/#"  //订阅设备所有信息//格式: $sys/{产品ID}/{设备名称}/dp/post/json#define POST_TOPIC "$sys/361594/mq2/dp/post/json"  //发布char mqtt_message[200];//上报数据缓存区int main(){   u32 time_cnt=0;   u32 i;   u8 key;   LED_Init();   BEEP_Init();   KEY_Init();   USART1_Init(115200);   TIMER1_Init(72,20000); //超时时间20ms   USART3_Init(115200);//串口-WIFI   TIMER3_Init(72,20000); //超时时间20ms   USART1_Printf("正在初始化WIFI请稍等.\n");   if(ESP8266_Init())   {      USART1_Printf("ESP8266硬件检测错误.\n");     }   else   {      //加密端口      //USART1_Printf("WIFI:%d\n",ESP8266_STA_TCP_Client_Mode("OnePlus5T","1126626497","183.230.40.16",8883,1));            //非加密端口      USART1_Printf("WIFI:%d\n",ESP8266_STA_TCP_Client_Mode("OnePlus5T","1126626497","183.230.40.96",1883,1));     }       //2. MQTT协议初始化     MQTT_Init();     //3. 连接OneNet服务器     while(MQTT_Connect(MQTT_ClientID,MQTT_UserName,MQTT_PassWord))    {        USART1_Printf("OneNet服务器连接失败,正在重试...\n");        delay_ms(500);    }    USART1_Printf("OneNet服务器连接成功.\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,"{\"id\":1,\"dp\":{\"mq2\":[{\"v\":50}]}}");            MQTT_PublishData(POST_TOPIC,mqtt_message,0);            USART1_Printf("发送状态1\r\n");        }        else if(key==3)        {            time_cnt=0;            sprintf(mqtt_message,"{\"id\":1,\"dp\":{\"mq2\":[{\"v\":80}]}}");            MQTT_PublishData(POST_TOPIC,mqtt_message,0);            USART1_Printf("发送状态0\r\n");        }          if(USART3_RX_FLAG)        {            USART3_RX_BUFFER[USART3_RX_CNT]='\0';            for(i=0;i<USART3_RX_CNT;i++)            {                USART1_Printf("%c",USART3_RX_BUFFER[i]);            }            USART3_RX_CNT=0;            USART3_RX_FLAG=0;        }        //定时发送心跳包,保持连接        delay_ms(10);        time_cnt++;        if(time_cnt==500)        {            MQTT_SentHeart();//发送心跳包            time_cnt=0;        }    }}

七、设备登录运行效果

登录成功之后,网页会显示在线状态。

img

按下开发按键上传烟雾数据到服务器效果:

img

img

原网站

版权声明
本文为[华为云]所创,转载请带上原文链接,感谢
https://bbs.huaweicloud.com/blogs/363858