STM32 advanced learning (2) - super detailed explanation of the routine of uploading temperature and humidity of STM32 based on ONENET platform


1. Joint debugging of STM32 and EPS01 to upload data to the onenet cloud platform

The ultimate goal is that STM32 uses ESP01 as a wifi module to measure temperature and humidity data, set up an LED light, connect to ONENET cloud platform through HTTP or MQTT protocol, display temperature and humidity data on the cloud platform, and remotely control LED on and off.
There are mainly three libraries that need to be created here, namely:
Onenet
MQTTKit
​​ESP8266

Here, I don’t like the code writing of KEIL, so I replaced it with Qinheng’s MRS, based on CH32V307, which has powerful code completion and search functions. In this section, I only analyze the routine provided by the ONENET platform: ESP8266-MQTT-temperature and humidity, and analyze its operation logic. In addition, the source code of this routine can be searched and downloaded in ONENET, mine is ONENET-bare metal-basic routine-ESP8266-MQTT-TYPE5-temperature and humidity .

1. Starting from the main program, first look at the header file

insert image description here
Obviously, this includes the sensor layer, network device layer, protocol layer, C library and STM32 library.

2. In the main function:

insert image description here
Here, unsigned short type variables and char type pointer variables are defined. I don’t know the use for the time being, so let’s look back first.
(As you will know later, timeCount is a counting unit, which is used to send data every few seconds; dataPtr is used to receive data for processing)

3. Initialize the hardware device:

insert image description here
Not much to say, it's very simple. It is worth mentioning that the initialization of serial port 2 is responsible for communicating with ESP8266.

4. Then, initialize the ESP8266:

ESP8266_Init();					//初始化ESP8266

Jump in:

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");

}

The main process of ESP8266 initialization is:

Configure the ESP8266 reset pin : Use the GPIO_InitTypeDef structure to configure the ESP8266 reset pin, set it to output mode, and the initial level is low. Then pull the pin high, wait for a while and then pull it low to complete the reset operation of the ESP8266 module.

Why do you need to reset it first and then work normally?
Because when using the ESP8266 module, due to factors such as the instability of the network environment and the working principle of the ESP8266 module, the ESP8266 module may not work properly. In this case, it is necessary to reset the ESP8266 module to restore its normal working state.

②Call ESP8266_Clear(); Empty the ESP8266 cache : call the ESP8266_Clear() function to clear the cache of the ESP8266 module.

③Send AT commands: AT : use the ESP8266_SendCmd() function to send AT commands, and wait for the ESP8266 module to reply "OK" to confirm that the ESP8266 module is working normally.

Configure the working mode of the ESP8266 module: use the ESP8266_SendCmd() function to send the AT+CWMODE command, and set the working mode of the ESP8266 module to "Station mode" to connect to the WiFi network. AT+CWMODE=1 is an AT command, which is used to configure the working mode of the ESP8266 module.

The ESP8266 module supports three working modes, which are "Station mode", "AP mode" and "AP+Station mode". Among them, "Station mode" is to connect the ESP8266 module as a client to the WiFi network, which can realize the function of connecting to the Internet through the WiFi network. The "AP mode" is to use the ESP8266 module as a WiFi hotspot, and other devices can connect to the WiFi network it provides. Finally, "AP+Station mode" supports the above two functions at the same time.
Therefore, when using the MQTT protocol, it is necessary to set the working mode of the ESP8266 module to "Station mode" in order to connect to the WiFi network and connect to the OneNET server through the WiFi network.

⑤Configure ESP8266 module DHCP: use ESP8266_SendCmd() function to send AT+CWDHCP=1,1 command , enable the DHCP function of ESP8266 module, and automatically obtain an IP address.

⑥Connect to the WiFi network: use the ESP8266_SendCmd() function to send the AT+CWJAP command, connect to the WiFi network, and wait for the ESP8266 module to obtain the IP address .

⑦Connect to OneNET server: use ESP8266_SendCmd() function to send AT+CIPSTART command, connect to OneNET server, and wait for the ESP8266 module to establish a TCP connection .

⑧Initialization completed: Output debugging information, indicating that the initialization of the ESP8266 module is completed.

For the function of initializing ESP8266, this more important function is called internally:

_Bool ESP8266_SendCmd(char *cmd, char *res)

Firstly, various macros defined by ESP8266 are taken as a prerequisite for analyzing the function. (These definitions are not in one file, just integrated)

//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() sends command function , see the specific code:

_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;
}

The function is:
send AT command to ESP8266 module: use Usart_SendString function to send AT command to ESP8266 module.
Wait for the ESP8266 module to return a response: use the ESP8266_WaitRecive function to wait for the ESP8266 module to return a response, and save the response to the esp8266_buf buffer.

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() function:
determine whether it is in the state of receiving data: if the current receiving count is 0, it means that it is not in the state of receiving data, and directly returns the receiving waiting flag (REV_WAIT).
Judging whether the reception is complete: If the current reception count is the same as the last time, it means that the data has been received, clear the reception count and return the reception completion flag (REV_OK).
Return receiving waiting flag: If the data has not been received yet, return receiving waiting flag.

Determine whether the expected response is received: use the strstr function to retrieve keywords in the esp8266_buf buffer, if the expected response is retrieved, clear the cache and return 0; otherwise continue to wait for the response. Because I send each command, the sendCMD function also defines res=OK. ESP8266 itself will return an OK after receiving the AT command. As long as it matches res=OK, it means the return is successful, and the search keyword is OK .
Return function execution result: If the expected response is not received within the specified time, return 1. This is the send command function.

Therefore, in the ESP8266 initialization, every time the AT command is sent, this function is passed, for example:
ESP8266_SendCmd(“AT+CWDHCP=1,1\r\n”, “OK”);
ESP8266_SendCmd(ESP8266_WIFI_INFO, “GOT IP”);
ESP8266_WIFI_INFO is defined with a macro, which makes it easy to modify WIFI specific information at any time.
At this time, ESP8266 has already connected to ONENET cloud platform through WIFI under the requirement of AT command.

5. Access ONENET

After the ESP8266 device side is configured, it is necessary to configure the network protocol. First of all, it is necessary to integrate the relevant macro definitions together for easy explanation.

//产品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];

Go to the main program main.c and continue to look down:

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

The main thing is this OneNet_DevLink() function , focusing on analyzing this function:

_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;
	
}

It looks complicated, but it is actually very complicated. Sentence by sentence:

(1) Initialize the member variables of the protocol packet structure

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

This code mainly defines a variable mqttPacket of the MQTT_PACKET_STRUCTURE structure type, and initializes its member variables .

The MQTT_PACKET_STRUCTURE structure type is used to store MQTT protocol packets, including the following member variables:

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

In this code, the variable mqttPacket is initialized as an empty protocol packet, the data member variable is assigned a value of NULL, indicating that there is no protocol packet data; the len member variable is assigned a value of 0, indicating that the protocol packet length is 0; size and memflag are both It is assigned a value of 0, indicating that the cache size is 0 and the memory is not allocated.

(2) Enter the MQTT_PacketConnect() function

The following two lines define identifiers. I don’t know their usefulness for the time being. Let’s look back:

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");

If it is established, execute it as follows:

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

Enter the MQTT_PacketConnect function:

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;

}

This function is more complicated. You can tell from the name that it is the function for the client to send a CONNECT message to the server. About the CONNECT message, write an explanation in the first section.
First analyze the formal parameters and actual parameters:

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

PUBLIC: PROID, AUTH_INFO, DEVID, 256, 0, MQTT_QOS_LEVEL0, NULL, NULL, 0, &mqttPacket

Function parameters include:
user: user name of the connection, the type is an int8 pointer (that is, a character pointer), product ID (PROID); password: the
password of the connection, the type is an int8 pointer, and the authentication key;
devid: device ID, the type Int8 pointer, device ID;
cTime: connection timeout time, the type is uint16 (unsigned 16-bit integer), set to 256ms;
clean_session: Whether to clear the session, the type is uint1 (unsigned 1-bit integer), 0 is false, that is to say, the client requires me to save unreceived messages;
qos: quality of service level, the type is uint1, here is 0, at most once (At most once), the message publisher only sends the message once, and there is no guarantee of the message Can be received by the receiving end;
will_topic: the topic of the will message, the type is an int8 pointer, here is empty;
will_msg: the content of the will message, the type is an int8 pointer, which is empty;
will_retain: whether the will message is retained, the type is int32 (with signed 32-bit integer);
mqttPacket: MQTT protocol packet structure, the type is MQTT_PACKET_STRUCTURE pointer.
The function of this function is to construct the connection packet of the MQTT protocol, and store the data of the connection packet in the structure pointed to by mqttPacket. If the function executes successfully, it returns 0, otherwise it returns an error code.

Simply put, this function:
first defines some variables, including flags, will_topic_len, total_len, and so on. Then construct each field of the connection packet according to the parameters, and calculate the total length of the connection packet total_len.
insert image description here
Set different fields of the connection packet according to the flag bit and qos level, such as whether to clean up offline messages after disconnection, the qos level of will messages, whether to keep will messages, etc.
insert image description here

If the username or password is empty, an error code is returned.
insert image description here

Dynamically allocate memory:
insert image description here

Construct the fixed header, variable header and message body of the connection packet,
insert image description here
insert image description here
and finally store the data of the connection packet in the structure pointed to by mqttPacket. If the function executes successfully, it returns 0, otherwise it returns an error code. I can't understand this function at the moment, I can only use it, I can't write it out, and I can't explain it. In short, I would like to respect Zhang Jirui!

(3) When the MQTT_PacketConnect function succeeds, call ESP8266_SendData(mqttPacket._data, mqttPacket._len)

First, go to the ESP8266_SendData() function to see what these two parameters are for:

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);		//发送设备连接请求数据
	}
}

This is a function implementation, its role is to send data to the remote server through the ESP8266 module. The following is a brief description of the implementation process:
First, define a character array cmdBuf for storing sending commands.
Call the ESP8266_Clear function to clear the receive buffer of the ESP8266 module for subsequent data exchange.
Use the sprintf function to format the send command in the form of AT+CIPSEND=len\r\n, where len represents the length of the data to be sent.
Call the ESP8266_SendCmd function to send commands to the ESP8266 module, and wait for the module to return the ">" symbol, indicating that data can be sent. If you receive the ">" symbol, call the Usart_SendString function to send the data. This function sends the data through the USART serial port.
Therefore, this function actually sends the MQTT connection packet data stored in mqttPacket._data to the remote server .

(4) Wait for the platform to respond

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;
				}
			}
		}

The function of this code is to wait for the ESP8266 module to return the platform response, and analyze the content of the MQTT connection response packet according to the response. The specific process is as follows:

Call the ESP8266_GetIPD function to wait for the ESP8266 module to return the platform response, and wait for 250 milliseconds. If the response is received within the specified time, it will return the pointer dataPtr of the response data, otherwise it will return NULL.

If dataPtr is not NULL, call the MQTT_UnPacketRecv function to unpack the response data, and judge whether the type of the response data is MQTT_PKT_CONNACK, if yes, it means that the MQTT connection response packet has been received.

Then call the MQTT_UnPacketConnectAck function to parse the content of the MQTT connection response packet, and judge whether the connection is successful according to the return value. If the return value is 0, it means that the connection is successful, and the status variable is set to 0; if the return value is other values, it means that the connection failed, and the corresponding error prompt is output according to the return value.

It should be noted that the format and content of the MQTT connection response packet are different from the MQTT connection packet, so when parsing the response packet, the MQTT_UnPacketConnectAck function needs to be used instead of the MQTT_UnPacketConnect function directly.
In addition, since the dataPtr returned by the ESP8266_GetIPD function points to the receiving buffer of the ESP8266 module, it is necessary to release the buffer in time after using the dataPtr, otherwise the receiving buffer may overflow.

(5) MQTT_DeleteBuffer(&mqttPacket)删包

Delete the MQTT packet buffer to release memory space. Finally, if the sending fails, the serial print fails to send. At this point, the OneNet_DevLink() function is over. Then jump back to the main program.

6. Enter the while(1) loop

(1) Encapsulate the temperature and humidity data into MQTT packets and send them

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

Determine whether the timeCount is greater than or equal to 500, if so, call the SHT20_GetValue function to obtain the current value of the temperature and humidity sensor, encapsulate it into an MQTT packet and send it to the OneNet platform through the ESP8266 module. At the same time, reset the timeCount variable to 0 for the next count. Call the ESP8266_Clear function to clear the receiving buffer of the ESP8266 module to avoid affecting the next data sending operation.
Here is a specific look at the OneNet_SendData() function :

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");
	}
	
}

The function of this code is to encapsulate the data of the temperature and humidity sensor into an MQTT packet and upload it to the OneNet platform through the ESP8266 module. The specific process is as follows:

Define a structure mqttPacket of type MQTT_PACKET_STRUCTURE for encapsulating MQTT packets.
Define a buf array to store the temperature and humidity data to be sent. At the same time, call the memset function to clear the buf array to avoid uninitialized data.
Call the OneNet_FillBuf function to fill the buf array to obtain the total length of the current temperature and humidity data stream that needs to be sent.
If body_len is greater than 0, it means that there is data to be sent, and the encapsulation and sending operations of MQTT data packets can be performed. Call the MQTT_PacketSaveData function to encapsulate the MQTT data packet, and store the encapsulated data in the mqttPacket structure. Among them, DEVID indicates the device ID, and 5 indicates that the QoS level is 1.
Copy the temperature and humidity data stream byte by byte to mqttPacket._data, and add the data length to mqttPacket._len to record the packet length.
Call the ESP8266_SendData function to upload the data in mqttPacket._data to the OneNet platform through the ESP8266 module.
Print the uploaded data length.
Call the MQTT_DeleteBuffer function to delete the data stored in the mqttPacket structure and release the memory space.
If body_len is equal to 0, it means that there is no data to be sent, and no operation is performed.
Here is the core: OneNet_FillBuf function

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);

}

The function of this code is to encapsulate the data of the temperature and humidity sensor into a string stream for uploading to the OneNet platform. The specific process is as follows:
Define a text array for storing the name and value of each data point.
Call the memset function to clear the text array to avoid uninitialized data.
Add a ",;" string to the buf array to indicate the start of the data stream.
Call the sprintf function to store the name and value of the temperature data into the text array.
Call the strcat function to add the temperature data stored in the text array to the buf array.
Call the memset function to clear the text array to avoid uninitialized data
.
Repeat steps 4-6 to store the name and value of the humidity data in the text array and add them to the buf array.
Returns the length of the buf array, which is the total length of the data stream to be uploaded.

In other words, all the previous functions and codes are related to ESP8266 and ONENET, MQTT initialization and connection creation, which belong to the place where we don’t need to modify. What we really need to modify is this function. sht20_info.tempreture and sht20_info.humidity are temperature and humidity data. Therefore, whether it is measuring temperature and humidity, distance, light intensity, or air quality, it can be modified in this function. The principle is also very simple, that is, call the sprintf function to convert the temperature The name and value of the data are stored in the text array. Call the strcat function to add the temperature data stored in the text array to the buf array, and call the memset function to clear the text array to avoid uninitialized data . This step is repeated as many times as the measured quantities to be transferred.

(2) Obtain and process received data

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

The function of this code is to obtain the data received by the ESP8266 module, and if there is data, call the OneNet_RevPro function to process and analyze it. The specific process is as follows:

Call the ESP8266_GetIPD function to get the data received by the ESP8266 module. Among them, 0 represents the ID of the receiving unit, and if there are multiple receiving units, they can be distinguished by different IDs.

If dataPtr is not NULL, it means that there is data that needs to be processed and analyzed, and the OneNet_RevPro function is called to process the data. The specific processing process depends on specific requirements and application scenarios.
Call the DelayXms function to achieve a delay of 10 milliseconds to prevent the program from occupying too much CPU resources.

At this point, the whole process is over. It can be seen that the routines for STM32 to upload temperature and humidity through ESP8266 and ONENET are very complicated, both of which are difficult to understand in practical application. I wrote this blog because I didn't know what to do to get started with MQTT and ONENET, so I had to start from the source code. In the next section, I will record myself modifying the routine code and implementing my own cloud platform application.

Guess you like

Origin blog.csdn.net/qq_53092944/article/details/131148158