STM32进阶学习(2)-超详细解释基于ONENET平台的STM32上传温湿度的例程


一、STM32与EPS01联调实现上传数据到onenet云平台

最终的目的是,STM32把ESP01作为wifi模块,测量温湿度数据,设置一个LED灯,通过HTTP或者MQTT协议,连接ONENET云平台,在云平台上显示温湿度数据,并可以远程控制LED亮灭。
这里主要需要创建三个库,分别是:
Onenet
MQTTKit
ESP8266

这里由于KEIL的代码编写我不太喜欢,因此更换为沁恒的MRS,以CH32V307为准,代码补全、查找功能比较强大。这一节我仅分析ONENET平台提供的例程:ESP8266-MQTT-温湿度,分析其运行逻辑。另外,该例程源码在ONENET可以搜索下载,我这个是ONENET-裸机-基础例程-ESP8266-MQTT-TYPE5-温湿度

1.从主程序开始,先看头文件

在这里插入图片描述
显而易见,这里包括了传感器层、网络设备层、协议层、C库和STM32库。

2.main函数里:

在这里插入图片描述
这里定义了无符号的short类型变量和char类型的指针变量,暂时不知道用处,先往后看。
(后面知道,timeCount是计数单位,用于每多少秒发送一次数据;dataPtr是接收数据做处理用的)

3.初始化硬件设备:

在这里插入图片描述
不多说,很简单。值得一提的是串口2的初始化是负责与ESP8266通信的。

4.然后,初始化ESP8266:

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_14;					//GPIOC14-复位
	GPIO_Initure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOC, &GPIO_Initure);
	
	GPIO_WriteBit(GPIOC, GPIO_Pin_14, Bit_RESET);
	DelayXms(250);
	GPIO_WriteBit(GPIOC, GPIO_Pin_14, Bit_SET);
	DelayXms(500);
	
	ESP8266_Clear();
	
	UsartPrintf(USART_DEBUG, "1. AT\r\n");
	while(ESP8266_SendCmd("AT\r\n", "OK"))
		DelayXms(500);
	
	UsartPrintf(USART_DEBUG, "2. CWMODE\r\n");
	while(ESP8266_SendCmd("AT+CWMODE=1\r\n", "OK"))
		DelayXms(500);
	
	UsartPrintf(USART_DEBUG, "3. AT+CWDHCP\r\n");
	while(ESP8266_SendCmd("AT+CWDHCP=1,1\r\n", "OK"))
		DelayXms(500);
	
	UsartPrintf(USART_DEBUG, "4. CWJAP\r\n");
	while(ESP8266_SendCmd(ESP8266_WIFI_INFO, "GOT IP"))
		DelayXms(500);
	
	UsartPrintf(USART_DEBUG, "5. CIPSTART\r\n");
	while(ESP8266_SendCmd(ESP8266_ONENET_INFO, "CONNECT"))
		DelayXms(500);
	
	UsartPrintf(USART_DEBUG, "6. ESP8266 Init OK\r\n");

}

ESP8266初始化的主要流程为:

配置ESP8266复位引脚:使用GPIO_InitTypeDef结构体配置ESP8266复位引脚,将其设置为输出模式,初始电平为低电平。然后将引脚拉高,等待一段时间后再拉低,完成ESP8266模块的复位操作。

为什么要先让它复位再正常工作呢?
因为在使用ESP8266模块时,由于网络环境的不稳定性和ESP8266模块的工作原理等因素,可能会出现ESP8266模块无法正常工作的情况。在这种情况下,需要对ESP8266模块进行复位操作,以恢复其正常工作状态。

调用ESP8266_Clear(); 清空ESP8266缓存:调用ESP8266_Clear()函数清空ESP8266模块的缓存区。

发送AT指令:AT:使用ESP8266_SendCmd()函数发送AT指令,等待ESP8266模块回复“OK”,以确认ESP8266模块正常工作。

配置ESP8266模块工作模式:使用ESP8266_SendCmd()函数发送AT+CWMODE指令,将ESP8266模块的工作模式设置为“Station mode”,以便连接WiFi网络。AT+CWMODE=1是一条AT指令,用于配置ESP8266模块的工作模式。

ESP8266模块支持三种工作模式,分别为“Station mode”、“AP mode”和“AP+Station mode”。其中,“Station mode”是将ESP8266模块作为客户端连接到WiFi网络,可以实现通过WiFi网络连接到互联网的功能。而“AP mode”则是将ESP8266模块作为WiFi热点,其他设备可以连接到它所提供的WiFi网络。最后,“AP+Station mode”则同时支持上述两种功能。
因此,在使用MQTT协议时,需要将ESP8266模块的工作模式设置为“Station mode”,以便连接到WiFi网络,并通过WiFi网络连接到OneNET服务器。

配置ESP8266模块DHCP:使用ESP8266_SendCmd()函数发送AT+CWDHCP=1,1指令,启用ESP8266模块的DHCP功能,自动获取IP地址。

连接WiFi网络:使用ESP8266_SendCmd()函数发送AT+CWJAP指令,连接WiFi网络,并等待ESP8266模块获取到IP地址

连接OneNET服务器:使用ESP8266_SendCmd()函数发送AT+CIPSTART指令,连接OneNET服务器,并等待ESP8266模块建立TCP连接

初始化完成:输出调试信息,表示ESP8266模块初始化完成。

对于初始化ESP8266的函数,内部调用了这个比较重要的函数:

_Bool ESP8266_SendCmd(char *cmd, char *res)

首先把ESP8266定义的各种宏作为一个分析该函数的前提。(这些定义不在一个文件里,只是整合了一下)

//AT+CWJAP指令用于连接WiFi网络,后面的参数中,ONENET表示WiFi网络的名称,而IOT@Chinamobile123则是WiFi网络的密码。
#define ESP8266_WIFI_INFO		"AT+CWJAP=\"ONENET\",\"IOT@Chinamobile123\"\r\n"

//AT+CIPSTART指令用于连接TCP服务器,后面的参数中,TCP表示使用TCP协议连接服务器,183.230.40.39表示OneNET服务器的IP地址,而6002则是OneNET服务器的端口号。
#define ESP8266_ONENET_INFO		"AT+CIPSTART=\"TCP\",\"183.230.40.39\",6002\r\n"

unsigned char esp8266_buf[128];
//cnt是接收计数器
unsigned short esp8266_cnt = 0, esp8266_cntPre = 0;

#define REV_OK		0	//接收完成标志
#define REV_WAIT	1	//接收未完成标志

ESP8266_SendCmd()发送指令函数,看看具体代码:

_Bool ESP8266_SendCmd(char *cmd, char *res)
{
    
    
	unsigned char timeOut = 200;
    Usart_SendString(USART2, (unsigned char *)cmd, strlen((const char *)cmd));
	while(timeOut--)
	{
    
    
		if(ESP8266_WaitRecive() == REV_OK)		//如果收到数据
		{
    
    
	      if(strstr((const char *)esp8266_buf, res) != NULL)//如果检索到关键词
			{
    
    
				ESP8266_Clear();					//清空缓存	
				return 0;
			}
		}
		DelayXms(10);
	}
	return 1;
}

作用是:
向ESP8266模块发送AT指令:使用Usart_SendString函数向ESP8266模块发送AT指令。
等待ESP8266模块返回响应:使用ESP8266_WaitRecive函数等待ESP8266模块返回响应,并将响应保存到esp8266_buf缓存区中。

Bool ESP8266_WaitRecive(void)
{
    
    
	if(esp8266_cnt == 0) 							//如果接收计数为0 则说明没有处于接收数据中,所以直接跳出,结束函数
		return REV_WAIT;
	if(esp8266_cnt == esp8266_cntPre)				//如果上一次的值和这次相同,则说明接收完毕
	{
    
    
		esp8266_cnt = 0;							//清0接收计数
		return REV_OK;								//返回接收完成标志
	}
	esp8266_cntPre = esp8266_cnt;					//置为相同
	return REV_WAIT;								//返回接收未完成标志
}

ESP8266_WaitRecive()函数:
判断是否处于接收数据状态:如果当前的接收计数为0,说明没有处于接收数据状态,直接返回接收等待标志(REV_WAIT)。
判断是否接收完成:如果当前的接收计数与上一次的相同,说明数据已经接收完毕,清空接收计数并返回接收完成标志(REV_OK)。
返回接收等待标志:如果数据还未接收完毕,返回接收等待标志。

判断是否收到期望的响应:使用strstr函数在esp8266_buf缓存区中检索关键词,如果检索到了期望的响应,则清空缓存并返回0;否则继续等待响应。因为我发送每个指令,sendCMD函数也定义了res=OK,本身ESP8266接收到AT指令后就会返回一个OK,只要与res=OK了,说明返回成功,这个检索关键词就是OK
返回函数执行结果:如果在规定时间内未收到期望的响应,则返回1。这就是发送指令函数。

所以,ESP8266初始化里,每次发送AT指令都通过了该函数,比如:
ESP8266_SendCmd(“AT+CWDHCP=1,1\r\n”, “OK”);
ESP8266_SendCmd(ESP8266_WIFI_INFO, “GOT IP”);
ESP8266_WIFI_INFO用宏定义,这样便于随时修改WIFI具体信息。
这个时候ESP8266就已经在AT指令的要求下通过WIFI接入ONENET云平台了。

5.接入ONENET

ESP8266设备方配置好后,就要进行网络协议方面的配置了。首先还是把相关的宏定义整合在一起方便说明。

//产品ID(PROID)是OneNET平台上创建的产品的唯一标识符,用于区分不同的产品;
#define PROID		"77247"
//鉴权信息(AUTH_INFO)是用于验证设备身份的密钥,用于确保只有经过授权的设备可以连接到OneNET平台
#define AUTH_INFO	"test"
//设备ID(DEVID)是OneNET平台上创建的设备的唯一标识符,用于区分不同的设备。
#define DEVID		"5616839"

extern unsigned char esp8266_buf[128];

转到主程序main.c里继续往下看:

while(OneNet_DevLink())			//接入OneNET
		DelayXms(500);
	
	Beep_Set(BEEP_ON);				//鸣叫提示接入成功
	DelayXms(250);
	Beep_Set(BEEP_OFF);

主要就是这个OneNet_DevLink()函数,着重分析这个函数:

_Bool OneNet_DevLink(void)
{
    
    
	
	MQTT_PACKET_STRUCTURE mqttPacket = {
    
    NULL, 0, 0, 0};					//协议包

	unsigned char *dataPtr;
	
	_Bool status = 1;
	
	UsartPrintf(USART_DEBUG, "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);									//等待平台响应
		if(dataPtr != NULL)
		{
    
    
			if(MQTT_UnPacketRecv(dataPtr) == MQTT_PKT_CONNACK)
			{
    
    
				switch(MQTT_UnPacketConnectAck(dataPtr))
				{
    
    
					case 0:UsartPrintf(USART_DEBUG, "Tips:	连接成功\r\n");status = 0;break;
					
					case 1:UsartPrintf(USART_DEBUG, "WARN:	连接失败:协议错误\r\n");break;
					case 2:UsartPrintf(USART_DEBUG, "WARN:	连接失败:非法的clientid\r\n");break;
					case 3:UsartPrintf(USART_DEBUG, "WARN:	连接失败:服务器失败\r\n");break;
					case 4:UsartPrintf(USART_DEBUG, "WARN:	连接失败:用户名或密码错误\r\n");break;
					case 5:UsartPrintf(USART_DEBUG, "WARN:	连接失败:非法链接(比如token非法)\r\n");break;
					
					default:UsartPrintf(USART_DEBUG, "ERR:	连接失败:未知错误\r\n");break;
				}
			}
		}
		
		MQTT_DeleteBuffer(&mqttPacket);								//删包
	}
	else
		UsartPrintf(USART_DEBUG, "WARN:	MQTT_PacketConnect Failed\r\n");
	
	return status;
	
}

看上去十分庞杂,实际上确实很复杂。一句一句来看:

(1)初始化协议包结构体成员变量

MQTT_PACKET_STRUCTURE mqttPacket = {
    
    NULL, 0, 0, 0};					//协议包

这段代码主要是定义了一个MQTT_PACKET_STRUCTURE结构体类型的变量mqttPacket,并初始化了它的成员变量

MQTT_PACKET_STRUCTURE结构体类型是用来存储MQTT协议包的,其中包括以下成员变量:

typedef struct Buffer
{
    
    
	uint8	*_data;		//协议数据
	uint32	_len;		//写入的数据长度
	uint32	_size;		//缓存总大小
	uint8	_memFlag;	//内存使用的方案:0-未分配	1-使用的动态分配		2-使用的固定内存
} MQTT_PACKET_STRUCTURE;

在这段代码中,变量mqttPacket被初始化为一个空的协议包,data成员变量被赋值为NULL,表示没有协议包数据;len成员变量被赋值为0,表示协议包长度为0;size和memflag都被赋值为0,表示缓存大小为0,内存未分配。

(2)进入MQTT_PacketConnect()函数

下面的两行是定义了标识符,暂时不清楚用处,往后面看:

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);									//等待平台响应
		if(dataPtr != NULL)
		{
    
    
			if(MQTT_UnPacketRecv(dataPtr) == MQTT_PKT_CONNACK)
			{
    
    
				switch(MQTT_UnPacketConnectAck(dataPtr))
				{
    
    
					case 0:UsartPrintf(USART_DEBUG, "Tips:	连接成功\r\n");status = 0;break;
					
					case 1:UsartPrintf(USART_DEBUG, "WARN:	连接失败:协议错误\r\n");break;
					case 2:UsartPrintf(USART_DEBUG, "WARN:	连接失败:非法的clientid\r\n");break;
					case 3:UsartPrintf(USART_DEBUG, "WARN:	连接失败:服务器失败\r\n");break;
					case 4:UsartPrintf(USART_DEBUG, "WARN:	连接失败:用户名或密码错误\r\n");break;
					case 5:UsartPrintf(USART_DEBUG, "WARN:	连接失败:非法链接(比如token非法)\r\n");break;
					
					default:UsartPrintf(USART_DEBUG, "ERR:	连接失败:未知错误\r\n");break;
				}
			}
		}
		
		MQTT_DeleteBuffer(&mqttPacket);								//删包
	}
	else
		UsartPrintf(USART_DEBUG, "WARN:	MQTT_PacketConnect Failed\r\n");

如果成立,则往下执行:

MQTT_PacketConnect(PROID, AUTH_INFO, DEVID, 256, 0, MQTT_QOS_LEVEL0, NULL, NULL, 0, &mqttPacket) == 0

进入MQTT_PacketConnect函数:

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;
	}
	
	//异常掉线情况下,服务器发布的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(发布态)消息的,保证消息传递的次数-----------------------------
	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(发布态)的消息,表示服务器要保留这次推送的信息,如果有新的订阅者出现,就把这消息推送给它。如果不设那么推送至当前订阅的就释放了
	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;
	
	//分配内存-----------------------------------------------------------------------------
	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;
	
/*************************************可变头部***********************************************/
	
	//可变头部----------------------协议名长度 和 协议名--------------------------------------
	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;
	
	//可变头部----------------------连接标志(该函数开头处理的数据)-----------------------------
    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;

}

这个函数更加复杂,看名字就知道是客户端向服务器端发送CONNECT报文的函数,关于CONNECT报文,在第一节里写说明了。
先分析形参和实参:

形参: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

实参:PROID, AUTH_INFO, DEVID, 256, 0, MQTT_QOS_LEVEL0, NULL, NULL, 0, &mqttPacket

函数参数包括:
user:连接的用户名,类型为int8指针(即字符型指针),产品ID(PROID);
password:连接的密码,类型为int8指针,鉴权密钥;
devid:设备ID,类型为int8指针,设备ID;
cTime:连接超时时间,类型为uint16(无符号16位整型),设置为256ms;
clean_session:是否清除会话,类型为uint1(无符号1位整型),0即为false,也就是说客户端要求我保存没有收到的报文;
qos:服务质量等级,类型为uint1,这里是0,最多一次(At most once),消息发布者只发送一次消息,不保证消息能够被接收端接收到;
will_topic:遗嘱消息的主题,类型为int8指针,这里是空;
will_msg:遗嘱消息的内容,类型为int8指针,为空;
will_retain:遗嘱消息是否保留,类型为int32(有符号32位整型);
mqttPacket:MQTT协议包结构体,类型为MQTT_PACKET_STRUCTURE指针。
该函数的作用是构建MQTT协议的连接包,并将连接包的数据存储在mqttPacket指向的结构体中。函数执行成功返回0,否则返回错误码。

简单的说,这个函数:
首先定义了一些变量,包括标志位flags、遗嘱消息主题长度will_topic_len、总长度total_len等等。然后根据参数构建连接包的各个字段,计算连接包总长度total_len。
在这里插入图片描述
分别根据标志位和qos等级设置连接包的不同字段,如断线后是否清理离线消息、遗嘱消息的qos等级、遗嘱消息是否保留等等。
在这里插入图片描述

如果用户名或密码为空,则返回错误码。
在这里插入图片描述

动态分配内存:
在这里插入图片描述

构建连接包的固定头部、可变头部和消息体
在这里插入图片描述
在这里插入图片描述
最终将连接包的数据存储在mqttPacket指向的结构体中。函数执行成功返回0,否则返回错误码。这个函数我目前看不懂,只能拿来用,写不出来也说不明白,总之向张继瑞大佬respect!

(3)当MQTT_PacketConnect函数成功后,调用ESP8266_SendData(mqttPacket._data, mqttPacket._len)

首先,转到ESP8266_SendData()函数去,看看这两个形参是干嘛的:

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, ">"))				//收到‘>’时可以发送数据
	{
    
    
		Usart_SendString(USART2, data, len);		//发送设备连接请求数据
	}
}

这是一个函数实现,其作用是通过ESP8266模块向远程服务器发送数据。下面是实现过程的简要说明:
首先定义一个字符数组cmdBuf,用于存储发送命令。
调用ESP8266_Clear函数清空ESP8266模块的接收缓存,以便后续的数据交换。
使用sprintf函数将发送命令格式化为AT+CIPSEND=len\r\n的形式,其中len表示待发送数据的长度。
调用ESP8266_SendCmd函数向ESP8266模块发送命令,并等待模块返回">“符号,表示可以发送数据了。如果收到”>"符号,就调用Usart_SendString函数将数据发送出去,这个函数是将数据通过USART串口发送出去。
所以,这个函数实际上mqttPacket._data中存储的MQTT连接包数据发送给远程服务器

(4)等待平台响应

dataPtr = ESP8266_GetIPD(250);									//等待平台响应
		if(dataPtr != NULL)
		{
    
    
			if(MQTT_UnPacketRecv(dataPtr) == MQTT_PKT_CONNACK)
			{
    
    
				switch(MQTT_UnPacketConnectAck(dataPtr))
				{
    
    
					case 0:UsartPrintf(USART_DEBUG, "Tips:	连接成功\r\n");status = 0;break;
					
					case 1:UsartPrintf(USART_DEBUG, "WARN:	连接失败:协议错误\r\n");break;
					case 2:UsartPrintf(USART_DEBUG, "WARN:	连接失败:非法的clientid\r\n");break;
					case 3:UsartPrintf(USART_DEBUG, "WARN:	连接失败:服务器失败\r\n");break;
					case 4:UsartPrintf(USART_DEBUG, "WARN:	连接失败:用户名或密码错误\r\n");break;
					case 5:UsartPrintf(USART_DEBUG, "WARN:	连接失败:非法链接(比如token非法)\r\n");break;
					
					default:UsartPrintf(USART_DEBUG, "ERR:	连接失败:未知错误\r\n");break;
				}
			}
		}

这段代码的作用是等待ESP8266模块返回平台响应,并根据响应解析出MQTT连接响应包的内容。具体过程如下:

调用ESP8266_GetIPD函数等待ESP8266模块返回平台响应,并等待250毫秒,如果在规定时间内收到了响应,就返回响应数据的指针dataPtr,否则返回NULL。

如果dataPtr不为NULL,则调用MQTT_UnPacketRecv函数对响应数据进行解包,判断响应数据的类型是否为MQTT_PKT_CONNACK,如果是,则表示收到了MQTT连接响应包。

接着调用MQTT_UnPacketConnectAck函数解析MQTT连接响应包的内容,并根据返回值判断连接是否成功。如果返回值为0,则表示连接成功,将status变量设置为0;如果返回值为其他值,则表示连接失败,根据返回值输出相应的错误提示。

需要注意的是,MQTT连接响应包的格式和内容与MQTT连接包是不同的,因此在解析响应包的时候需要使用MQTT_UnPacketConnectAck函数进行解析,而不能直接使用MQTT_UnPacketConnect函数。
另外,由于ESP8266_GetIPD函数返回的dataPtr指向的是ESP8266模块的接收缓存区,因此需要在使用完dataPtr后及时释放缓存区,否则可能会导致接收缓存区溢出。

(5) MQTT_DeleteBuffer(&mqttPacket)删包

删除MQTT数据包缓存区,释放内存空间。最后,如果发送失败,串口打印发送失败。到了这里,OneNet_DevLink()函数就结束了。再跳回主程序里。

6.进入while(1)循环

(1)把温湿度数据封装成MQTT数据包发送

if(++timeCount >= 500)									//发送间隔5s
		{
    
    
			SHT20_GetValue();
			
			UsartPrintf(USART_DEBUG, "OneNet_SendData\r\n");
			OneNet_SendData();									//发送数据
			
			timeCount = 0;
			ESP8266_Clear();
		}

判断timeCount是否大于等于500,如果是,则调用SHT20_GetValue函数获取温湿度传感器的当前数值,封装成MQTT数据包并通过ESP8266模块发送到OneNet平台。同时,将timeCount变量重置为0,以便进行下一次计数。调用ESP8266_Clear函数清空ESP8266模块的接收缓存区,避免影响下一次数据发送操作。
这里具体看看OneNet_SendData()函数

void OneNet_SendData(void)
{
    
    
	MQTT_PACKET_STRUCTURE mqttPacket = {
    
    NULL, 0, 0, 0};												//协议包
	char buf[128];
	short body_len = 0, i = 0;
	UsartPrintf(USART_DEBUG, "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);									//上传数据到平台
			UsartPrintf(USART_DEBUG, "Send %d Bytes\r\n", mqttPacket._len);
			
			MQTT_DeleteBuffer(&mqttPacket);															//删包
		}
		else
			UsartPrintf(USART_DEBUG, "WARN:	EDP_NewBuffer Failed\r\n");
	}
	
}

这段代码的作用是将温湿度传感器的数据封装成MQTT数据包,通过ESP8266模块上传到OneNet平台。具体过程如下:

定义一个MQTT_PACKET_STRUCTURE类型的结构体mqttPacket,用于封装MQTT数据包。
定义一个buf数组,用于存放需要发送的温湿度数据。同时,调用memset函数将buf数组清零,避免出现未初始化的数据。
调用OneNet_FillBuf函数填充buf数组,获取当前需要发送的温湿度数据流的总长度。
如果body_len大于0,则表示有数据需要发送,可以进行MQTT数据包的封装和发送操作。调用MQTT_PacketSaveData函数封装MQTT数据包,并将封装好的数据存储在mqttPacket结构体中。其中,DEVID表示设备ID,5表示QoS等级为1。
将温湿度数据流逐个字节复制到mqttPacket._data中,同时将mqttPacket._len加上数据长度,用于记录数据包长度。
调用ESP8266_SendData函数将mqttPacket._data中的数据通过ESP8266模块上传到OneNet平台。
打印上传的数据长度。
调用MQTT_DeleteBuffer函数删除mqttPacket结构体中存储的数据,释放内存空间。
如果body_len等于0,则表示没有需要发送的数据,不进行任何操作。
这里是核心:OneNet_FillBuf函数

unsigned char OneNet_FillBuf(char *buf)
{
    
    
	
	char text[32];
	
	memset(text, 0, sizeof(text));
	
	strcpy(buf, ",;");
	
	memset(text, 0, sizeof(text));
	sprintf(text, "Tempreture,%f;", sht20_info.tempreture);
	strcat(buf, text);
	
	memset(text, 0, sizeof(text));
	sprintf(text, "Humidity,%f;", sht20_info.humidity);
	strcat(buf, text);
	
	return strlen(buf);

}

这段代码的作用是将温湿度传感器的数据封装成一个字符串流,用于上传到OneNet平台。具体过程如下:
定义一个text数组,用于存储每个数据点的名称和数值。
调用memset函数将text数组清零,避免出现未初始化的数据。
在buf数组中添加",;"字符串,表示数据流的开始。
调用sprintf函数将温度数据的名称和数值存储到text数组中。
调用strcat函数将text数组中存储的温度数据添加到buf数组中。
调用memset函数将text数组清零,避免出现未初始化的数据

重复步骤4-6,将湿度数据的名称和数值存储到text数组中,并添加到buf数组中。
返回buf数组的长度,即为需要上传的数据流的总长度。

也就是说,前面的所有函数和代码都是关于ESP8266和ONENET、MQTT初始化和创建连接用的,属于我们可以不用修改的地方,真正要修改的,是这个函数。sht20_info.tempreture和sht20_info.humidity是温湿度数据,因此,后面不管是测量温湿度、还是距离、光照强度、空气质量,都可以在这个函数中进行修改,原理也很简单,就是调用sprintf函数将温度数据的名称和数值存储到text数组中。调用strcat函数将text数组中存储的温度数据添加到buf数组中,调用memset函数将text数组清零,避免出现未初始化的数据。要传输多少个测量量,就重复这个步骤多少次。

(2)获取接收数据并处理

		dataPtr = ESP8266_GetIPD(0);
		if(dataPtr != NULL)
			OneNet_RevPro(dataPtr);
		
		DelayXms(10);

这段代码的作用是获取ESP8266模块接收到的数据,如果有数据则调用OneNet_RevPro函数进行处理和解析。具体过程如下:

调用ESP8266_GetIPD函数获取ESP8266模块接收到的数据。其中,0表示接收单元的ID,如果有多个接收单元,可以通过不同的ID来区分。

如果dataPtr不为NULL,则表示有数据需要进行处理和解析,调用OneNet_RevPro函数对数据进行处理。具体处理过程根据具体的需求和应用场景而定。
调用DelayXms函数实现10毫秒的延时,避免程序过于占用CPU资源。

到此,全部的过程就结束了。可以看到,STM32通过ESP8266和ONENET上传温湿度的例程十分复杂,都是,实际应用起来,也不是说晦涩难懂。我写这篇博客,是因为不知道要入门MQTT和ONENET该怎么办,只好从源码看起。下一节,我将记录自己修改该例程代码,并实现自己的云平台应用。

猜你喜欢

转载自blog.csdn.net/qq_53092944/article/details/131148158