STM32通过ESP8266(MQTT)连接新版ONENET(2024/4/23)(保姆级教程)附运行结果

STM32通过ESP8266(MQTT)连接新版ONENET(2024/4/23)(保姆级教程)附运行结果

码农世界 2024-05-28 前端 83 次浏览 0个评论

⏩ 大家好哇!我是小光,想要成为系统架构师的嵌入式爱好者。

⏩在各种嵌入式系统中我们经常会使用上位机去做显示,本文对STM32通过ESP8266连接最新版的ONENET做一个详细教程。

⏩感谢你的阅读,不对的地方欢迎指正。

STM32通过ESP8266连接新版ONENET代码(更新时间:2024/4/10)

加入小光嵌入式交流群(qq群号:737327353)免费获取博主所有资料哦!


ONENET

  • 引言
  • 实验环境
    • 硬件环境
    • 软件环境
    • ONENET配置
      • 创建产品、设备、物模型
      • 计算token
      • STM32 ESP8266驱动代码编写
        • ESP8266串口驱动
        • esp8266驱动
        • 连接onenet驱动代码
        • 成果展示
        • 总结

          引言

          由于ONENET的更新,新版与旧版不互通,在使用WIFI连接新版ONENET时,需要在旧版上更改部分代码,在查找了网上很多资料的时候发现都没有讲的很清楚,本篇文章对STM32通过ESP8266(MQTT协议)连接最新版的ONENET做一个详细教程。

          实验环境

          硬件环境

          开发板:STM32F103ZET6

          WIFI模块:正点原子esp8266WIFI模块

          传感器:DHT11温湿度模块

          软件环境

          ONENET物联网开放平台

          ONENET数据可视化view3.0

          ONENET配置

          创建产品、设备、物模型

          1.进入ONENET开发者中心

          2.创建产品

          产品种类根据你的项目填写(随便选也没事),智能化方式一定要选设备接入

          除了我框住的地方其他随便填,如果你选择了标准方案,他会给你提前配置好物模型(数据模板),我们这里自己配,所以选择自定义方案。

          3.创建设备

          4.配置物模型(数据模型),和上传数据的格式有关

          一定要读写

          到这一步我们的数据格式就配好了

          5.查看上传数据

          在设备管理里面就可以打开我们创建好的设备了

          下面是我们需要记住的信息,我们在使用WIFI连接时需要使用。

          在属性中我们就可以查看上传数据了

          计算token

          这是官方文档,可以下载token:

          token生成工具

          把配置ONENET时第五步的信息填入token:

          res:products/产品ID/devices/设备ID

          STM32 ESP8266驱动代码编写

          ESP8266串口驱动

          这里我接的是串口三,RST接PA5

          void uart3_init()
          {
            		GPIO_InitTypeDef GPIO_InitStructure;
          		USART_InitTypeDef USART_InitStructure;
          		NVIC_InitTypeDef NVIC_InitStructure;
          		RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);	
          		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
          		//USART3_TX   GPIOB.10
          		GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //PB.10
          		GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
          		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	
          		GPIO_Init(GPIOB, &GPIO_InitStructure);
          		
          		//USART3_RX	  GPIOB.11
          		GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;//PB11
          		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
          		GPIO_Init(GPIOB, &GPIO_InitStructure);
          		//Usart3 NVIC 
          		NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
          		NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=5 ;
          		NVIC_InitStructure.NVIC_IRQChannelSubPriority = 5;		
          		NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			
          		NVIC_Init(&NVIC_InitStructure);	
          	
          		//USART 
          		USART_InitStructure.USART_BaudRate = USART3_bound;
          		USART_InitStructure.USART_WordLength = USART_WordLength_8b;
          		USART_InitStructure.USART_StopBits = USART_StopBits_1;
          		USART_InitStructure.USART_Parity = USART_Parity_No;
          		USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
          		USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	
          		USART_Init(USART3, &USART_InitStructure); 
          		USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);
          		USART_Cmd(USART3, ENABLE);  
          		TIM4_Init(999,7199);		//10ms中断
          		USART3_RX_STA=0;		//清零
          		TIM4_Set(0);			//关闭定时器4
          }
          

          中断服务函数:

          //通过判断接收连续2个字符之间的时间差不大于10ms来决定是不是一次连续的数据.
          //如果2个字符接收间隔超过10ms,则认为不是1次连续数据.也就是超过10ms没有接收到
          //任何数据,则表示此次接收完毕.
          //接收到的数据状态
          //[15]:0,没有接收到数据;1,接收到了一批数据.
          //[14:0]:接收到的数据长度  	 
          void USART3_IRQHandler(void)
          {
          	u8 res;	    
          	if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)//接收到数据
          	{	 
           
          		res =USART_ReceiveData(USART3);		
          		if(USART3_RX_STA
          			TIM_SetCounter(TIM4,0);//计数器清空        				 
          			if(USART3_RX_STA==0)TIM4_Set(1);	 	//使能定时器4的中断 
          			USART3_RX_BUF[USART3_RX_STA++]=res;		//记录接收到的值	 
          		}else 
          		{
          			USART3_RX_STA|=1<<15;					//强制标记接收完成
          		} 
          	}  											 
          }   
          

          esp8266驱动

          先配置连接的WIFI和新版ONENET物联网平台的网址,需要把WIFI名称和密码改成自己的,我这边是自己配置的路由器,使用手机开热点还是电脑开热点都可以。

          #define ESP8266_WIFI_INFO		"AT+CWJAP=\"WIFI名称\",\"WIFI密码\"\r\n"
          #define ESP8266_ONENET_INFO		"AT+CIPSTART=\"TCP\",\"mqtts.heclouds.com\",1883\r\n"
          #define REV_OK		0	//接收完成标志
          #define REV_WAIT	1	//接收未完成标志
          // esp8266 使用的是串口三
          //尽量使用sendstring#define esp8266_printf u3_printf  
          #define esp8266_RX_STA USART3_RX_STA
          #define esp8266_RX_BUF USART3_RX_BUF
          #define esp8266_uart  USART3
          

          ESP8266相关函数:

          //==========================================================
          //	函数名称:	ESP8266_Clear
          //	函数功能:	清空缓存
          //	入口参数:	无
          //	返回参数:	无
          //	说明:		
          //==========================================================
          void ESP8266_Clear(void)
          {
          	memset(esp8266_RX_BUF, 0, sizeof(esp8266_RX_BUF));
          	esp8266_RX_STA = 0;
          }
          //==========================================================
          //	函数名称:	ESP8266_WaitRecive
          //	函数功能:	等待接收完成
          //	入口参数:	无
          //	返回参数:	REV_OK-接收完成		REV_WAIT-接收超时未完成
          //	说明:		循环调用检测是否接收完成
          //==========================================================
          _Bool ESP8266_WaitRecive(void)
          {
          	if(esp8266_RX_STA&0x8000){
          		esp8266_RX_STA = 0;
          		return REV_OK;
          	}
          	return REV_WAIT;								//返回接收未完成标志
          }
          //==========================================================
          //	函数名称:	ESP8266_SendCmd
          //	函数功能:	发送命令
          //	入口参数:	cmd:命令
          //				res:需要检查的返回指令
          //	返回参数:	0-成功	1-失败
          //	说明:		
          //==========================================================
          _Bool ESP8266_SendCmd(char *cmd, char *res)
          {
          	
          	unsigned char timeOut = 200;
          	
          	Usart_SendString(esp8266_uart, (unsigned char *)cmd, strlen((const char *)cmd));
          	
          	while(timeOut--)
          	{
          		if(ESP8266_WaitRecive() == REV_OK)							//如果收到数据
          		{
          			if(strstr((const char *)esp8266_RX_BUF, res) != NULL)		//如果检索到关键词
          			{
          				ESP8266_Clear();									//清空缓存
          				return 0;
          			}
          		}
          		
          		delay_ms(10);
          	}
          	
          	return 1;
          }
          //==========================================================
          //	函数名称:	ESP8266_SendData
          //	函数功能:	发送数据
          //	入口参数:	data:数据
          //				len:长度
          //	返回参数:	无
          //	说明:		
          //==========================================================
          void ESP8266_SendData(unsigned char *data, unsigned short len)
          {
          	char cmdBuf[32];
          	
          	ESP8266_Clear();								//清空接收缓存
          	
          	//先发送要发送数据的指令做准备
          	sprintf(cmdBuf, "AT+CIPSEND=%d\r\n", len);		//发送命令
          	if(!ESP8266_SendCmd(cmdBuf, ">"))				//收到‘>’时可以发送数据
          	{
          		//既然准备完毕即可开始发送数据
          		//esp8266_printf("%s",data);
          		Usart_SendString(esp8266_uart, data, len);		//发送设备连接请求数据
          		//Usart_SendString(USART3, data, len);		//发送设备连接请求数据
          	}
          }
          //==========================================================
          //	函数名称:	ESP8266_GetIPD
          //	函数功能:	获取平台返回的数据
          //	入口参数:	等待的时间(乘以10ms)
          //	返回参数:	平台返回的原始数据
          //	说明:		不同网络设备返回的格式不同,需要去调试
          //				如ESP8266的返回格式为	"+IPD,x:yyy"	x代表数据长度,yyy是数据内容
          //==========================================================
          unsigned char *ESP8266_GetIPD(unsigned short timeOut)
          {
          	char *ptrIPD = NULL;
          	
          	do
          	{
          		if(ESP8266_WaitRecive() == REV_OK)								//如果接收完成
          		{
          			ptrIPD = strstr((char *)esp8266_RX_BUF, "IPD,");				//搜索“IPD”头
          			if(ptrIPD == NULL)											//如果没找到,可能是IPD头的延迟,还是需要等待一会,但不会超过设定的时间
          			{
          				//UsartPrintf(USART_DEBUG, "\"IPD\" not found\r\n");
          			}
          			else
          			{
          				ptrIPD = strchr(ptrIPD, ':');							//找到':'
          				if(ptrIPD != NULL)
          				{
          					ptrIPD++;
          					return (unsigned char *)(ptrIPD);
          				}
          				else
          					return NULL;
          				
          			}
          		}
          		
          		delay_ms(5);													//延时等待
          	} while(timeOut--);
          	
          	return NULL;														//超时还未找到,返回空指针
          }
          //==========================================================
          //	函数名称:	ESP8266_Init
          //	函数功能:	初始化ESP8266
          //	入口参数:	无
          //	返回参数:	无
          //	说明:		
          //==========================================================
          void ESP8266_Init(void)
          {
          	GPIO_InitTypeDef GPIO_Initure;
          	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
          	//ESP8266复位引脚
          	GPIO_Initure.GPIO_Mode = GPIO_Mode_Out_PP;
          	GPIO_Initure.GPIO_Pin = GPIO_Pin_5;					//GPIOA5-复位
          	GPIO_Initure.GPIO_Speed = GPIO_Speed_50MHz;
          	GPIO_Init(GPIOA, &GPIO_Initure);
          	GPIO_WriteBit(GPIOA, GPIO_Pin_5, Bit_RESET);
          	delay_ms(250);
          	GPIO_WriteBit(GPIOA, GPIO_Pin_5, Bit_SET);
          	delay_ms(500);
          	ESP8266_Clear();
          	printf("AT\r\n");
          	while(ESP8266_SendCmd("AT\r\n", "OK"))
          	delay_ms(500);
          	
          	printf("AT+CWMODE=1\r\n");
          	while(ESP8266_SendCmd("AT+CWMODE=1\r\n", "OK"))
          	delay_ms(500);
          	
          	printf("AT+CWDHCP=1,1\r\n");
          	while(ESP8266_SendCmd("AT+CWDHCP=1,1\r\n", "OK"))
          	delay_ms(500);
          	
          	printf("%s\r\n",ESP8266_WIFI_INFO);
          	while(ESP8266_SendCmd(ESP8266_WIFI_INFO, "GOT IP"))
          	delay_ms(500);
          	
          	printf("%s\r\n",ESP8266_ONENET_INFO);
          	while(ESP8266_SendCmd(ESP8266_ONENET_INFO, "CONNECT"))
          	delay_ms(500);
          	
          	printf("ESP8266 Init OK\r\n");
          	delay_ms(500);
          }
          

          连接onenet驱动代码

          需要将产品ID、设备ID、token改成自己的

          #define PROID		"dz64yYgxk0"  //产品ID
          #define AUTH_INFO	"token"  //鉴权信息token
          #define DEVID		"weather"   //设备名称
          

          与ONENET平台建立连接:

          //==========================================================
          //	函数名称:	OneNet_DevLink
          //
          //	函数功能:	与onenet创建连接
          //
          //	入口参数:	无
          //
          //	返回参数:	0-成功	!0-失败
          //
          //	说明:		与onenet平台建立连接
          //==========================================================
          _Bool OneNet_DevLink(void)
          {
          	
          	MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0};					//协议包
          	unsigned char *dataPtr;
          	
          	_Bool status = 1;
          	if(MQTT_PacketConnect(PROID, AUTH_INFO, DEVID, 256, 1, MQTT_QOS_LEVEL0, NULL, NULL, 0, &mqttPacket) == 0) //修改clean_session=1
          	{
          		ESP8266_SendData(mqttPacket._data, mqttPacket._len);			//上传平台
          		
          		dataPtr = ESP8266_GetIPD(250);									//等待平台响应
          		if(dataPtr != NULL)//如果平台返回数据不为空则
          		{
          			if(MQTT_UnPacketRecv(dataPtr) == MQTT_PKT_CONNACK)//	MQTT数据接收类型判断(connack报文)
          			{
          				switch(MQTT_UnPacketConnectAck(dataPtr))//打印是否连接成功及连接失败的原因
          				{
          					case 0:
          							printf( "Tips:	连接成功\r\n");
          							status = 0;
          							break;
          					
          					case 1:		
          							printf(  "WARN:	连接失败:协议错误\r\n");
          							break;
          					case 2:
          									
          							printf(  "WARN:	连接失败:非法的clientid\r\n");
          							break;
          					case 3:
          							printf(  "WARN:	连接失败:服务器失败\r\n");
          							break;
          					case 4:
          							printf(  "WARN:	连接失败:用户名或密码错误\r\n");
          							break;
          					case 5:
          							printf(  "WARN:	连接失败:非法链接(比如token非法)\r\n");
          							break;
          					
          					default:
          							printf(  "ERR:	连接失败:未知错误\r\n");
          							break;
          				}
          			}
          		}
          		
          		MQTT_DeleteBuffer(&mqttPacket);								//删包
          	}
          	else{
          		printf( "WARN:	MQTT_PacketConnect Failed\r\n");
          	}
          	delay_ms(500);
          	return status;
          	
          }
          

          数据包封装与发送:

          extern DHT11_Data_TypeDef DHT11_Data;
          unsigned char OneNet_FillBuf(char *buf)
          {
          	
          	char text[48];
          	
          	strcpy(buf,"{\"id\":\"123\",\"params\":{");
          	//温度
          	memset(text,0,sizeof(text));
          	sprintf(text,"\"temp\":{\"value\":%d},",DHT11_Data.temp_int);
          	strcat(buf,text);
          	//湿度
          	memset(text,0,sizeof(text));
          	sprintf(text,"\"humi\":{\"value\":%d}",DHT11_Data.humi_int);
          	strcat(buf,text);
          	
          	strcat(buf,"}}");
          	return strlen(buf);
          }
          //==========================================================
          //	函数名称:	OneNet_SendData
          //
          //	函数功能:	上传数据到平台
          //
          //	入口参数:	type:发送数据的格式
          //
          //	返回参数:	无
          //
          //	说明:		
          //==========================================================
          void OneNet_SendData(void)
          {
          	
          	MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0};												//协议包
          	
          	char buf[128];
          	
          	short body_len = 0, i = 0;
          	memset(buf, 0, sizeof(buf));//清空数组内容
          	
          	body_len = OneNet_FillBuf(buf);	//获取当前需要发送的数据流的总长度
          	printf("%s\r\n",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);									//上传数据到平台
          			MQTT_DeleteBuffer(&mqttPacket);															//删包
          		}
          		else;
          //			printf(  "WARN:EDP_NewBuffer Failed\r\n");
          	}
          	
          }
          

          发布与订阅消息:

          //==========================================================
          //	函数名称:	OneNET_Publish
          //
          //	函数功能:	发布消息
          //
          //	入口参数:	topic:发布的主题
          //				msg:消息内容
          //
          //	返回参数:	无
          //
          //	说明:		
          //==========================================================
          void OneNET_Publish(const char *topic, const char *msg)
          {
          	MQTT_PACKET_STRUCTURE mqtt_packet = {NULL, 0, 0, 0};						//协议包
          	
          	//UsartPrintf(USART_DEBUG, "Publish Topic: %s, Msg: %s\r\n", topic, msg);
          	
          	if(MQTT_PacketPublish(MQTT_PUBLISH_ID, topic, msg, strlen(msg), MQTT_QOS_LEVEL0, 0, 1, &mqtt_packet) == 0)
          	{
          		ESP8266_SendData(mqtt_packet._data, mqtt_packet._len);					//向平台发送订阅请求
          		
          		MQTT_DeleteBuffer(&mqtt_packet);										//删包
          	}
          }
          //==========================================================
          //	函数名称:	OneNET_Subscribe
          //
          //	函数功能:	订阅
          //
          //	入口参数:	无
          //
          //	返回参数:	无
          //
          //	说明:		
          //==========================================================
          void OneNET_Subscribe(void)
          {
          	
          	MQTT_PACKET_STRUCTURE mqtt_packet = {NULL, 0, 0, 0};						//协议包
          	
          	char topic_buf[56];
          	const char *topic = topic_buf;
          	
          	snprintf(topic_buf, sizeof(topic_buf), "$sys/%s/%s/thing/property/set", PROID, DEVID);
          	
          	//UsartPrintf(USART_DEBUG, "Subscribe Topic: %s\r\n", topic_buf);
          	
          	if(MQTT_PacketSubscribe(MQTT_SUBSCRIBE_ID, MQTT_QOS_LEVEL0, &topic, 1, &mqtt_packet) == 0)
          	{
          		ESP8266_SendData(mqtt_packet._data, mqtt_packet._len);					//向平台发送订阅请求
          		
          		MQTT_DeleteBuffer(&mqtt_packet);										//删包
          	}
          }
          

          同时在MQTT的驱动文件中需要把产品ID和设备ID修改自己的

          这个驱动文件太大有需要的话在文章开头进群免费下载

          main.c

          #include "led.h"
          #include "delay.h"
          #include "sys.h"
          #include "usart.h"
           #include "dht11.h"//dht11
           #include "onenet.h"
          #include "esp8266.h"
          //本程序只供学习使用,未经作者许可,不得用于其它任何用途
          //STM32F103最小系统板
          //MQ传感器驱动代码	   
          //技术交流群:737327353
          //修改日期:2024/4/21
          //版本:V1.0
          //版权所有,盗版必究。
          //Copyright(C) CSDN 小光学嵌入式					  
          /
          /******************引脚接口**************************
          DHT11(3.3V):
          	 DATA PB13 
          ESP8266(5V):
          	 TXD PB11
          	 RXD PB10
               RST PA5
          ************************************************/
           DHT11_Data_TypeDef DHT11_Data;
           
           
           int main(void)
           {		
          	u16 times=0;
          	delay_init();	    	 //延时函数初始化	  
          	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
          	uart_init();	 //串口初始化为115200
           	LED_Init();			     //LED端口初始化
          	
          	ESP8266_Init();
          	while(OneNet_DevLink());//接入OneNET
          	OneNET_Subscribe();
          	
           	while(1)
          	{
          		if(times++>100){
          			Read_DHT11(&DHT11_Data);//读取温湿度
          			OneNet_SendData();
          			printf("OneNET Msg Success\r\n");
          			ESP8266_Clear();//切记清除esp8266否则接收不到订阅
          			times = 0;
          		}
          		delay_ms(10);
          	}
           }
          

          通过上面的配置我们的驱动代码就写完啦!

          成果展示

          上电之后,成功连接ONENET之后设备会显示在线:

          同时属性中会有数据上传:

          再使用view3.0做一个可视化:

          总结

          本篇文章对STM32通过ESP8266(MQTT协议)连接最新版的ONENET做一个非常详细的教程。下一篇文章对view3.0可视化做一个详细教程。

          加入小光嵌入式交流群(qq群号:737327353)免费下载全部源码哦!

转载请注明来自码农世界,本文标题:《STM32通过ESP8266(MQTT)连接新版ONENET(2024/4/23)(保姆级教程)附运行结果》

百度分享代码,如果开启HTTPS请参考李洋个人博客
每一天,每一秒,你所做的决定都会改变你的人生!

发表评论

快捷回复:

评论列表 (暂无评论,83人围观)参与讨论

还没有评论,来说两句吧...

Top