Aprendizaje avanzado STM32 (2): explicación súper detallada de la rutina de carga de temperatura y humedad de STM32 basada en la plataforma ONENET


1. Depuración conjunta de STM32 y EPS01 para cargar datos en la plataforma en la nube onenet

El objetivo final es que STM32 use ESP01 como módulo wifi para medir datos de temperatura y humedad, configurar una luz LED, conectarse a la plataforma en la nube ONENET a través del protocolo HTTP o MQTT, mostrar datos de temperatura y humedad en la plataforma en la nube y controlar LED de forma remota encendido y apagado.
Hay principalmente tres bibliotecas que deben crearse aquí, a saber:
Onenet
MQTTKit
​​​​ESP8266

Aquí, no me gusta la escritura de código de KEIL, así que lo reemplacé con MRS de Qinheng, basado en CH32V307, que tiene una finalización de código poderosa y funciones de busqueda En esta sección solo analizo la rutina proporcionada por la plataforma ONENET: ESP8266-MQTT-temperatura y humedad, y analizo su lógica de funcionamiento. Además, el código fuente de esta rutina se puede buscar y descargar en ONENET, el mío es ONENET-bare metal-basic rutina-ESP8266-MQTT-TYPE5-temperatura y humedad .

1. Comenzando desde el programa principal, primero mire el archivo de encabezado

inserte la descripción de la imagen aquí
Obviamente, esto incluye la capa de sensor, la capa de dispositivo de red, la capa de protocolo, la biblioteca C y la biblioteca STM32.

2. En la función principal:

inserte la descripción de la imagen aquí
Aquí, se definen las variables de tipo corto sin signo y las variables de puntero de tipo char. No sé el uso por el momento, así que echemos un vistazo primero.
(Como sabrá más adelante, timeCount es una unidad de conteo, que se usa para enviar datos cada pocos segundos; dataPtr se usa para recibir datos para su procesamiento)

3. Inicialice el dispositivo de hardware:

inserte la descripción de la imagen aquí
No hay mucho que decir, es muy simple. Cabe mencionar que la inicialización del puerto serie 2 se encarga de comunicar con ESP8266.

4. Luego, inicialice el ESP8266:

ESP8266_Init();					//初始化ESP8266

Saltar:

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

}

El proceso principal de inicialización de ESP8266 es:

Configure el pin de reinicio ESP8266 : use la estructura GPIO_InitTypeDef para configurar el pin de reinicio ESP8266, configúrelo en modo de salida y el nivel inicial es bajo. Luego tire del pin hacia arriba, espere un momento y luego tire hacia abajo para completar la operación de reinicio del módulo ESP8266.

¿Por qué necesita reiniciarlo primero y luego trabajar normalmente?
Porque al usar el módulo ESP8266, debido a factores como la inestabilidad del entorno de red y el principio de funcionamiento del módulo ESP8266, es posible que el módulo ESP8266 no funcione normalmente. En este caso, es necesario restablecer el módulo ESP8266 para restaurar su estado de funcionamiento normal.

②Llamar a ESP8266_Clear(); Vacíe el caché de ESP8266 : llame a la función ESP8266_Clear() para borrar el caché del módulo ESP8266.

③Enviar comandos AT: AT : utilice la función ESP8266_SendCmd() para enviar comandos AT y espere a que el módulo ESP8266 responda "OK" para confirmar que el módulo ESP8266 funciona normalmente.

Configure el modo de trabajo del módulo ESP8266: use la función ESP8266_SendCmd () para enviar el comando AT + CWMODE y configure el modo de trabajo del módulo ESP8266 en "Modo de estación" para conectarse a la red WiFi. AT+CWMODE=1 es un comando AT, que se utiliza para configurar el modo de trabajo del módulo ESP8266.

El módulo ESP8266 admite tres modos de trabajo, que son "Modo de estación", "Modo AP" y "Modo AP+Estación". Entre ellos, el "Modo de estación" es conectar el módulo ESP8266 como cliente a la red WiFi, que puede realizar la función de conectarse a Internet a través de la red WiFi. El "modo AP" es usar el módulo ESP8266 como un punto de acceso WiFi, y otros dispositivos pueden conectarse a la red WiFi que proporciona. Finalmente, el "modo AP+Estación" admite las dos funciones anteriores al mismo tiempo.
Por lo tanto, al usar el protocolo MQTT, es necesario configurar el modo de trabajo del módulo ESP8266 en "Modo de estación" para conectarse a la red WiFi y conectarse al servidor OneNET a través de la red WiFi.

⑤Configure el DHCP del módulo ESP8266: utilice la función ESP8266_SendCmd() para enviar el comando AT+CWDHCP=1,1 , habilite la función DHCP del módulo ESP8266 y obtenga automáticamente una dirección IP.

⑥Conéctese a la red WiFi: utilice la función ESP8266_SendCmd() para enviar el comando AT+CWJAP, conéctese a la red WiFi y espere a que el módulo ESP8266 obtenga la dirección IP .

⑦Conéctese al servidor OneNET: utilice la función ESP8266_SendCmd() para enviar el comando AT+CIPSTART, conéctese al servidor OneNET y espere a que el módulo ESP8266 establezca una conexión TCP .

⑧ Inicialización completada: información de depuración de salida, lo que indica que se completó la inicialización del módulo ESP8266.

Para la función de inicializar ESP8266, esta función más importante se llama internamente:

_Bool ESP8266_SendCmd(char *cmd, char *res)

En primer lugar, varias macros definidas por ESP8266 se toman como requisito previo para analizar la función. (Estas definiciones no están en un archivo, solo integradas)

//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() envía la función de comando , consulte el código específico:

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

La función es:
enviar comando AT al módulo ESP8266: use la función Usart_SendString para enviar comando AT al módulo ESP8266.
Espere a que el módulo ESP8266 devuelva una respuesta: use la función ESP8266_WaitRecive para esperar a que el módulo ESP8266 devuelva una respuesta y guarde la respuesta en el búfer 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;								//返回接收未完成标志
}

Función ESP8266_WaitRecive():
determina si está en el estado de recepción de datos: si el recuento de recepción actual es 0, significa que no está en el estado de recepción de datos y devuelve directamente el indicador de espera de recepción (REV_WAIT).
Determinar si la recepción está completa: si el recuento de recepción actual es el mismo que la última vez, significa que se han recibido los datos, borre el recuento de recepción y devuelva el indicador de finalización de recepción (REV_OK).
Indicador de espera de recepción de devolución: si los datos aún no se han recibido, devuelva el indicador de espera de recepción.

Determine si se recibe la respuesta esperada: use la función strstr para recuperar palabras clave en el búfer esp8266_buf, si se recupera la respuesta esperada, borre el caché y devuelva 0; de lo contrario, continúe esperando la respuesta. Debido a que envío cada comando, la función sendCMD también define res=OK. El propio ESP8266 devolverá un OK después de recibir el comando AT. Siempre que coincida con res=OK, significa que la devolución es exitosa y la palabra clave de búsqueda está bien .
Devuelve el resultado de la ejecución de la función: si la respuesta esperada no se recibe dentro del tiempo especificado, devuelve 1. Esta es la función de comando de envío.

Por lo tanto, en la inicialización del ESP8266, cada vez que se envía el comando AT se pasa esta función, por ejemplo:
ESP8266_SendCmd(“AT+CWDHCP=1,1\r\n”, “OK”);
ESP8266_SendCmd(ESP8266_WIFI_INFO, “GOT IP”);
ESP8266_WIFI_INFO se define con una macro, lo que facilita la modificación de la información específica de WIFI en cualquier momento.
En este momento, ESP8266 ya se ha conectado a la plataforma en la nube ONENET a través de WIFI bajo el requisito del comando AT.

5. Accede a ONENET

Después de configurar el lado del dispositivo ESP8266, es necesario configurar el protocolo de red. En primer lugar, es necesario integrar las definiciones de macro relevantes para facilitar la explicación.

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

Vaya al programa principal main.c y continúe mirando hacia abajo:

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

Lo principal es esta función OneNet_DevLink() , centrándose en analizar esta función:

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

Parece complicado, pero en realidad es muy complicado. Oración por oración:

(1) Inicializar las variables miembro de la estructura del paquete de protocolo

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

Este código define principalmente una variable mqttPacket del tipo de estructura MQTT_PACKET_STRUCTURE e inicializa sus variables miembro .

El tipo de estructura MQTT_PACKET_STRUCTURE se utiliza para almacenar paquetes de protocolo MQTT, incluidas las siguientes variables miembro:

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

En este código, la variable mqttPacket se inicializa como un paquete de protocolo vacío, a la variable de miembro de datos se le asigna un valor de NULL, lo que indica que no hay datos de paquetes de protocolo; a la variable de miembro len se le asigna un valor de 0, lo que indica que el protocolo la longitud del paquete es 0; el tamaño y la bandera mem son ambos Se le asigna un valor de 0, lo que indica que el tamaño de la memoria caché es 0 y la memoria no está asignada.

(2) Introduzca la función MQTT_PacketConnect()

Las siguientes dos líneas definen identificadores, no sé su utilidad por el momento, echemos un vistazo atrás:

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

Si está establecido, ejecútelo de la siguiente manera:

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

Ingrese la función 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;

}

Esta función es más complicada.Puedes decir por el nombre que es la función para que el cliente envíe un mensaje de CONEXIÓN al servidor.Sobre el mensaje de CONEXIÓN, escriba una explicación en la primera sección.
Primero analice los parámetros formales y los parámetros reales:

形参:const int8 *usuario, const int8 *contraseña, 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

PÚBLICO: PROID, AUTH_INFO, DEVID, 256, 0, MQTT_QOS_LEVEL0, NULL, NULL, 0, &mqttPacket

Los parámetros de la función incluyen:
usuario: nombre de usuario de la conexión, el tipo es un puntero int8 (es decir, un puntero de carácter), ID del producto (PROID); contraseña: la contraseña
de la conexión, el tipo es un puntero int8 y el clave de autenticación;
devid: ID del dispositivo, el puntero de tipo Int8, ID del dispositivo;
cTime: tiempo de espera de la conexión, el tipo es uint16 (entero de 16 bits sin signo), establecido en 256 ms;
clean_session: ya sea para borrar la sesión, el tipo es uint1 (entero de 1 bit sin signo), 0 es falso, es decir, el cliente me pide que guarde los mensajes no recibidos; qos
: nivel de calidad de servicio, el tipo es uint1, aquí es 0, como máximo una vez (A lo sumo una vez) , el editor del mensaje solo envía el mensaje una vez y no hay garantía de que el mensaje pueda ser recibido por el extremo receptor;
will_topic: el tema del mensaje will, el tipo es un puntero int8, aquí está vacío;
will_msg: el contenido del mensaje de testamento, el tipo es un puntero int8, que está vacío;
will_retain: si se retiene el mensaje de testamento, el tipo es int32 (con un entero de 32 bits con signo);
mqttPacket: estructura de paquete de protocolo MQTT, el tipo es un puntero MQTT_PACKET_STRUCTURE .
La función de esta función es construir el paquete de conexión del protocolo MQTT y almacenar los datos del paquete de conexión en la estructura a la que apunta mqttPacket. Si la función se ejecuta con éxito, devuelve 0, de lo contrario, devuelve un código de error.

En pocas palabras, esta función:
primero define algunas variables, incluidas banderas, will_topic_len, total_len, etc. Luego construya cada campo del paquete de conexión de acuerdo con los parámetros y calcule la longitud total del paquete de conexión total_len.
inserte la descripción de la imagen aquí
Establezca diferentes campos del paquete de conexión de acuerdo con el bit de bandera y el nivel de calidad de servicio, por ejemplo, si limpiar los mensajes fuera de línea después de la desconexión, el nivel de calidad de servicio de los mensajes de voluntad, si desea conservar los mensajes de voluntad, etc.
inserte la descripción de la imagen aquí

Si el nombre de usuario o la contraseña están vacíos, se devuelve un código de error.
inserte la descripción de la imagen aquí

Asignar memoria dinámicamente:
inserte la descripción de la imagen aquí

Construya el encabezado fijo, el encabezado variable y el cuerpo del mensaje del paquete de conexión
inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí
y, finalmente, almacene los datos del paquete de conexión en la estructura señalada por mqttPacket. Si la función se ejecuta con éxito, devuelve 0, de lo contrario, devuelve un código de error. No puedo entender esta función en este momento, solo puedo usarla, no puedo escribirla y no puedo explicarla. En resumen, ¡me gustaría respetar a Zhang Jirui!

(3) Cuando la función MQTT_PacketConnect tenga éxito, llame a ESP8266_SendData(mqttPacket._data, mqttPacket._len)

Primero, vaya a la función ESP8266_SendData() para ver para qué sirven estos dos parámetros:

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

Esta es una implementación de función, su función es enviar datos al servidor remoto a través del módulo ESP8266. La siguiente es una breve descripción del proceso de implementación:
Primero, defina una matriz de caracteres cmdBuf para almacenar comandos de envío.
Llame a la función ESP8266_Clear para borrar el búfer de recepción del módulo ESP8266 para el intercambio de datos posterior.
Utilice la función sprintf para formatear el comando de envío en forma de AT+CIPSEND=len\r\n, donde len representa la longitud de los datos que se enviarán.
Llame a la función ESP8266_SendCmd para enviar comandos al módulo ESP8266 y espere a que el módulo devuelva el símbolo ">", lo que indica que se pueden enviar datos. Si recibe el símbolo ">", llame a la función Usart_SendString para enviar los datos. Esta función envía los datos a través del puerto serie USART.
Por lo tanto, esta función en realidad envía los datos del paquete de conexión MQTT almacenados en mqttPacket._data al servidor remoto .

(4) Espere a que la plataforma responda

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

La función de este código es esperar a que el módulo ESP8266 devuelva la respuesta de la plataforma y analizar el contenido del paquete de respuesta de conexión MQTT de acuerdo con la respuesta. El proceso específico es el siguiente:

Llame a la función ESP8266_GetIPD para esperar a que el módulo ESP8266 devuelva la respuesta de la plataforma y espere milisegundos 250. Si la respuesta se recibe dentro del tiempo especificado, devolverá el puntero dataPtr de los datos de respuesta, de lo contrario, devolverá NULL.

Si dataPtr no es NULL, llame a la función MQTT_UnPacketRecv para desempaquetar los datos de respuesta y determine si el tipo de datos de respuesta es MQTT_PKT_CONNACK; en caso afirmativo, significa que se recibió el paquete de respuesta de conexión MQTT.

Luego llame a la función MQTT_UnPacketConnectAck para analizar el contenido del paquete de respuesta de la conexión MQTT y determine si la conexión es exitosa de acuerdo con el valor de retorno. Si el valor de retorno es 0, significa que la conexión es exitosa y la variable de estado se establece en 0; si el valor de retorno es otro valor, significa que la conexión falló y se emite el mensaje de error correspondiente de acuerdo con el retorno valor.

Cabe señalar que el formato y el contenido del paquete de respuesta de la conexión MQTT son diferentes del paquete de conexión MQTT, por lo que al analizar el paquete de respuesta, se debe usar la función MQTT_UnPacketConnectAck en lugar de la función MQTT_UnPacketConnect directamente.
Además, dado que el dataPtr devuelto por la función ESP8266_GetIPD apunta al búfer de recepción del módulo ESP8266, es necesario liberar el búfer a tiempo después de usar el dataPtr, de lo contrario, el búfer de recepción puede desbordarse.

(5) MQTT_DeleteBuffer(&mqttPacket) incluido

Elimine el búfer de paquetes MQTT para liberar espacio en la memoria. Finalmente, si el envío falla, la impresión en serie no se envía. En este punto, la función OneNet_DevLink() ha terminado. Luego vuelve al programa principal.

6. Ingrese al ciclo while(1)

(1) Encapsule los datos de temperatura y humedad en paquetes MQTT y envíelos

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

Determina si el timeCount es mayor o igual a 500, de ser así llama a la función SHT20_GetValue para obtener el valor actual del sensor de temperatura y humedad, encapsúlalo en un paquete MQTT y envíalo a la plataforma OneNet a través del módulo ESP8266. Al mismo tiempo, restablezca la variable timeCount a 0 para el siguiente conteo. Llame a la función ESP8266_Clear para borrar el búfer de recepción del módulo ESP8266 para evitar afectar la siguiente operación de envío de datos.
Aquí hay una mirada específica a la función 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");
	}
	
}

La función de este código es encapsular los datos del sensor de temperatura y humedad en un paquete MQTT y subirlo a la plataforma OneNet a través del módulo ESP8266. El proceso específico es el siguiente:

Defina una estructura mqttPacket de tipo MQTT_PACKET_STRUCTURE para encapsular paquetes MQTT.
Defina una matriz buf para almacenar los datos de temperatura y humedad que se enviarán. Al mismo tiempo, llame a la función memset para borrar la matriz buf y evitar datos no inicializados.
Llame a la función OneNet_FillBuf para llenar la matriz buf y obtener la longitud total del flujo de datos de temperatura y humedad actual que debe enviarse.
Si body_len es mayor que 0, significa que hay datos para enviar y se pueden realizar las operaciones de encapsulación y envío de paquetes de datos MQTT. Llame a la función MQTT_PacketSaveData para encapsular el paquete de datos MQTT y almacene los datos encapsulados en la estructura mqttPacket. Entre ellos, DEVID indica el ID del dispositivo y 5 indica que el nivel de QoS es 1.
Copie el flujo de datos de temperatura y humedad byte a byte en mqttPacket._data y agregue la longitud de los datos a mqttPacket._len para registrar la longitud del paquete.
Llame a la función ESP8266_SendData para cargar los datos en mqttPacket._data a la plataforma OneNet a través del módulo ESP8266.
Imprime la longitud de los datos cargados.
Llame a la función MQTT_DeleteBuffer para eliminar los datos almacenados en la estructura mqttPacket y liberar espacio en la memoria.
Si body_len es igual a 0, significa que no hay datos para enviar y no se realiza ninguna operación.
Aquí está el núcleo: función 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);

}

La función de este código es encapsular los datos del sensor de temperatura y humedad en un flujo de cadena para cargarlos en la plataforma OneNet. El proceso específico es el siguiente:
Defina una matriz de texto para almacenar el nombre y el valor de cada punto de datos.
Llame a la función memset para borrar la matriz de texto para evitar datos no inicializados.
Agregue una cadena "," a la matriz buf para indicar el inicio del flujo de datos.
Llame a la función sprintf para almacenar el nombre y el valor de los datos de temperatura en la matriz de texto.
Llame a la función strcat para agregar los datos de temperatura almacenados en la matriz de texto a la matriz buf.
Llame a la función memset para borrar la matriz de texto y evitar datos no inicializados
.
Repita los pasos 4 a 6 para almacenar el nombre y el valor de los datos de humedad en la matriz de texto y agréguelos a la matriz buf.
Devuelve la longitud de la matriz buf, que es la longitud total del flujo de datos que se cargará.

En otras palabras, todas las funciones y códigos anteriores están relacionados con ESP8266 y ONENET, la inicialización de MQTT y la creación de conexiones, que pertenecen al lugar donde no necesitamos modificar, lo que realmente necesitamos modificar es esta función. sht20_info.tempreture y sht20_info.humidity son datos de temperatura y humedad. Por lo tanto, ya sea que se mida la temperatura y la humedad, la distancia, la intensidad de la luz o la calidad del aire, se puede modificar en esta función. El principio también es muy simple, es decir, llame a la función sprintf para convertir la temperatura. El nombre y el valor de los datos se almacenan en la matriz de texto. Llame a la función strcat para agregar los datos de temperatura almacenados en la matriz de texto a la matriz buf y llame a la función memset para borrar la matriz de texto y evitar datos no inicializados . Este paso se repite tantas veces como cantidades medidas se van a transferir.

(2) Obtener y procesar los datos recibidos

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

La función de este código es obtener los datos recibidos por el módulo ESP8266, y si hay datos llamar a la función OneNet_RevPro para procesarlos y analizarlos. El proceso específico es el siguiente:

Llame a la función ESP8266_GetIPD para obtener los datos recibidos por el módulo ESP8266. Entre ellos, 0 representa el ID de la unidad receptora y, si hay varias unidades receptoras, se pueden distinguir por diferentes ID.

Si dataPtr no es NULL, significa que hay datos que deben procesarse y analizarse, y se llama a la función OneNet_RevPro para procesar los datos. El proceso de procesamiento específico depende de requisitos específicos y escenarios de aplicación.
Llame a la función DelayXms para lograr un retraso de 10 milisegundos para evitar que el programa ocupe demasiados recursos de la CPU.

En este punto, todo el proceso ha terminado. Se puede ver que las rutinas para que STM32 cargue la temperatura y la humedad a través de ESP8266 y ONENET son muy complicadas, las cuales son difíciles de entender en la aplicación práctica. Escribí este blog porque no sabía qué hacer para comenzar con MQTT y ONENET, así que tuve que comenzar desde el código fuente. En la siguiente sección, me grabaré modificando el código de rutina e implementando mi propia aplicación de plataforma en la nube.

Supongo que te gusta

Origin blog.csdn.net/qq_53092944/article/details/131148158
Recomendado
Clasificación