Section 21 MQTT Protocol

Introduction to MQTT protocol

The full name of the MQTT protocol is Message Queuing Telemetry Transport, which translates to the Message Queuing Telemetry Transport Protocol. It is an application layer protocol commonly used in the Internet of Things. It runs in the application layer of TCP/IP and relies on the TCP protocol, so it has very high reliability. At the same time, it is a lightweight protocol based on the <client-server> model of the TCP protocol to publish/subscribe topic messages, and it is also what we often say to send and receive data. Let's take a preliminary look at the names and functions related to mqtt.

MQTT communication model

The MQTT protocol provides one-to-many message publishing, which can reduce the coupling of applications. Users only need to write a small amount of application code to complete one-to-many message publishing and subscription. The protocol is based on the <client-server> model , there are three main identities in the protocol: Publisher, Broker, and Subscriber. Among them, both the publisher and the subscriber of the MQTT message are clients, and the server is just a relay, forwarding the message published by the publisher to all subscribers who subscribe to the topic; the publisher can publish the message within its authority All topics, and message publishers can be subscribers at the same time, realizing the decoupling of producers and consumers, and published messages can be subscribed by multiple subscribers at the same time.

insert image description here
Figure MQTT communication model

Features of the MQTT client:

  1. Publish messages to other related clients.
  2. Subscribe to a topic request to receive related application messages.
  3. Unsubscribe Topic Request to remove receiving application messages.
  4. Terminate the connection from the server.

The MQTT server is often called Broker (message broker), so it is an application program or a device. It is generally a cloud server. For example, some IoT platforms of the BTA three giants often use the MQTT protocol. It is located between the message publisher and Between subscribers, in order to receive messages and send them to subscribers, its functions are:

  1. Accept network connection requests from clients.
  2. Accept application messages published by the client.
  3. Handles subscription and unsubscription requests from clients.
  4. Forward application messages to eligible subscribed clients (including the publisher itself).

Message topics and quality of service

What is a theme? The MQTT server adds a tag to each connected client (subscriber), which matches all subscriptions in the server, and the server will forward the message to each client (subscriber) that matches the tag, and of course subscribe Subscribers also need to have permission to subscribe to the corresponding topic. For example, in Alibaba Cloud, subscribers can only subscribe to topics under the same product, but not cross-product subscriptions. This kind of processing can achieve information security and multiple Subscribers can receive messages in a timely manner. A topic can have multiple levels separated by a slash character, eg /test and /test/test1/test2 are valid topics.

Publishers and subscribers can publish and subscribe to topics in the form of topic names, which are usually encoded in UTF-8 (anyway, it is correct to use English strings). For example, we can directly define a topic named "test". Most MQTT servers support dynamic publishing/subscribing topics, that is, there is no topic in the current server, but the client can directly publish/subscribe messages to the topic, so that the sub-server will create the corresponding topic. Of course, the server generally also Multiple system topics are provided by default, and all connected clients can subscribe to them.

After each client establishes a connection with the server, it is a session. There will be stateful interaction between the client and the server. Subscriptions are based on sessions. Each subscription will contain a topic filter, which is an expression, used To identify one or more topics related to the subscription, the topic filter can use wildcards, so the subscriber needs to specify the topic name and quality of service (QoS) of the subscription, and the subscriber can subscribe to multiple topics and receive multiple publications news published by the author. Similarly, the publisher also needs to establish a session with the server first, and specify the topic name and quality of service to be sent, and it can also send messages to multiple different topics.

So what is quality of service? The quality of service of MQTT provides 3 levels:

  1. QoS0: Send a message at most once. After the message is sent, the receiver will not send a response, and the sender will not resend the message. The message may be delivered once or not at all. This quality of service is often used for unimportant messages. In delivery, because it doesn't matter much if the message gets lost.
  2. QoS1: Send a message at least once (the message needs to be delivered at least once, and can be delivered multiple times). The variable header of the PUBLISH message of QoS1 contains a message identifier, which needs to be confirmed by the PUBACK message. That is, the receiver needs to return a PUBACK response message.
  3. QoS2: This is the highest level of service quality. Message loss and duplication are unacceptable, but there will be additional overhead when using this quality of service level. This level is often used in payment, because payment must be successful once and only once , You can’t fail to give money or give money many times.

MQTT control message

fixed header

The MQTT protocol works on top of the TCP protocol, because both the client and the server are at the application layer, so a protocol must be needed to communicate between the two, and then the MQTT control message follows, and the MQTT control message has It consists of 3 parts, namely fixed header (fixed header), variable header (variable header), and payload (data area payload). The fixed header is included in all MQTT control packets, and the variable header and payload are included in some MQTT control packets.

The fixed header occupies two bytes of space, as shown in the figure.

insert image description here

Figure fixed header

The first byte of the fixed header is divided into the type of control message (4bit) and the flag bit of the control message type. There are 14 types of control types, of which 0 and 15 are reserved by the system. For other types, see the table for details.

Form fixed header type

type value illustrate
Reserved 0 system reserved
CONNECT 1 The client requests to connect to the server
CONNACK 2 Connection message confirmation
PUBLISH 3 make an announcement
PUBACK 4 Message publishing receipt confirmation (QoS 1)
PUBREC 5 Publish Received (QoS2)
PUBREL 6 Release release (QoS2)
PUBCOMP 7 Message release completed (QoS2)
SUBSCRIBE 8 client subscription request
SUBACK 9 Subscription Request Message Confirmation
UNSUBSCRIBE 10 Client unsubscribe request
UNSUBACK 11 Unsubscribe Message Confirmation
PINGREQ 12 heartbeat request
PINGRESP 13 heartbeat response
DISCONNECT 14 client disconnected
Reserved 15 system reserved

The bit0-bit3 of the fixed header are flag bits, which have different meanings according to the message type. In fact, except for the PUBLISH type message, the flag bits of other messages are reserved by the system. The first byte bit3 of the PUBLISH message is The duplicate distribution flag (DUP) of the control message, bit1-bit2 is the service quality level, bit0 is the reserved flag of the PUBLISH message, which is used to identify whether the PUBLISH is reserved. When the client sends a PUBLISH message to the server, if the reserved flag is 1 , then the server should retain this message. When a new subscriber subscribes to this topic, the last reserved topic message should be sent to the newly subscribed user.

The second byte of the fixed header starts with the remaining length field, which is used to record the remaining length of the message, indicating the number of bytes remaining in the current message, including the variable header and payload area (if it exists), but the remaining length The number of bytes used to encode the remaining length field itself is not included.

The remaining length field uses a variable-length encoding scheme, and it uses single-byte encoding for values ​​less than 128, while for larger values ​​it is processed in the following way: the lower 7 bits of each byte are used to encode the data length, the highest Bit (bit7) is used to identify whether there are more bytes in the remaining length field, and is encoded according to the big-endian mode, so each byte can encode 128 values ​​and a continuation bit, and the remaining length field can have a maximum of 4 words Festival.

When the remaining length is stored in 1 byte, its value range is 0(0x00)~127(0x7f).

When using 2 bytes, its value range is 128(0x80,0x01)~16383(0Xff,0x7f).

When using 3 bytes, its value range is 16384(0x80,0x80,0x01)~2097151(0xFF,0xFF,0x7F).

When using 4 bytes, its value range is 2097152(0x80,0x80,0x80,0x01)~268435455(0xFF,0xFF,0xFF,0x7F).

In general, the MQTT message can theoretically send a maximum of 256M messages, of course, this situation is very rare.

variable header

Not all MQTT packets have variable headers (for example, PINGREQ heartbeat request and PINGRESP heartbeat response packets do not have variable headers), only certain packets have variable headers, which are included in fixed headers and payloads. Between, the content of the variable header will vary according to the type of the packet, but the packet identifier (Packet Identifier) ​​field of the variable header exists in multiple types of packets, and some packets There is no message identifier field, see the table for details, and see the figure for the message identifier structure.

The form requires a message type for the message identifier field

message type Is the message identifier field required?
CONNECT unnecessary
CONNACK unnecessary
PUBLISH Required (if QoS > 0)
PUBACK need
PUBREC need
PUBREL need
PUBCOMP need
SUBSCRIBE need
SUBACK need
UNSUBSCRIBE need
UNSUBACK need
PINGREQ unnecessary
PINGRESP unnecessary
DISCONNECT unnecessary

insert image description here

Graph Message Identifier

Because the variable headers are different for different packets, the following briefly explains the variable headers of several packets.

CONNECT

In a session, the client can only send a CONNECT message once. It is a message used by the client to request a connection to the server. It is often called a connection message. If the client sends multiple connection messages, the server must Treat the second CONNECT packet sent by the client as a protocol violation and disconnect the client.

The variable header of the CONNECT message contains four fields: Protocol Name, Protocol Level, Connect Flags, and Keep Alive fields.

The protocol name is a UTF-8 encoded string of MQTT, which also contains two-byte fields MSB and LSB used to record the length of the protocol name.

After the protocol name is the protocol level. The MQTT protocol uses an 8-bit unsigned value to indicate the revision of the protocol. For the MQTT3.1 version of the protocol, the value of the protocol level field is 3 (0x03), while for the MQTT3.1.1 version protocol, the value of the protocol level field is 4 (0x04). If the server finds that the protocol level field in the connection message is an unsupported protocol level, the server must send a CONNACK response connection message with the return code 0x01 (unsupported protocol level), and then terminate the client's connection request.

The connection flag field involves a lot of content. It is represented by one byte after the protocol level, but it is divided into many flag bits, as shown in the figure.

insert image description here

The bit0 of the connection flag field in the figure
is a flag bit reserved by MQTT. During the connection process, the server will check whether the bit0 of the connection flag is 0. If it is not 0, the server task this connection message is invalid and the connection request will be terminated.

bit1 is to clear the session flag Clean Session. Generally speaking, the client always sets the clear session flag to 0 or 1 when requesting to connect to the server. After the session connection is established, this value is fixed. Of course, the choice of this value depends on For specific applications, if the clear session flag is set to 1, the client will not receive old application messages, and needs to re-subscribe to related topics after each successful connection. A client with the clear session flag set to 0 will, after reconnecting, receive all QoS1 and QoS2 messages published while it was disconnected (by other publishers). Therefore, to ensure that messages are not lost during disconnection, use QoS1 or QoS2 levels with the clear session flag set to 0.

bit2 is the Will Flag. If this bit is set to 1, it means that if the client establishes a session with the server, the will message (Will Message) must be stored in the server. When the client disconnects, the will The message will be sent to all subscribers subscribed to this conversation topic, this message is very useful, we can know the status of this device, whether it has been offline, in case of starting a backup plan, of course, want to not send will message It is also possible, just let the server side delete the will message when it receives the DISCONNECT message.

bit3-bit4 is used to specify the quality of service level used when publishing the will message, which is the same as the service quality of other messages. The value of will QoS can be equal to 0 (0x00), 1 (0x01), 2 (0x02), of course, use The premise of the will message is that the will flag bit is 1.

bit5 indicates the Will Retain flag bit. When the client disconnects unexpectedly, if Will Retain is set to 1, the server must publish the will message as a retained message, otherwise it does not need to be retained.

bit6 is the password flag bit Password Flag, if the password flag is set to 0, the payload cannot contain the password field, otherwise it must contain the password field.

bit6 is the user name flag bit User Name Flag, if the user name flag is set to 0, the user name field cannot be included in the payload, otherwise the user name field must be included.

The keep-alive field is a time interval in seconds. It uses two bytes to record the maximum idle time interval allowed by the client. Simply put, the client must communicate with the server during this time to let the server Knowing that the client is still connected and not disconnected, of course, if there is no other control message to send, the client must also send a PINGREQ message to inform the server that it is still connected.

In general, the content of the variable header of the entire CONNECT message is as follows, see the figure for details.

insert image description here
Figure CONNECT message variable header

CONNACK

Let's explain the variable header part of the CONNACK message again. In fact, with the previous experience, this part is very simple for everyone. It consists of the connection confirmation flag field (Connect Acknowledge Flags) and the connection return code field ( Connect Return code), each occupying 1 byte.

Its first byte is the connection confirmation flag field, bit1-bit7 are reserved bits and must be set to 0, bit0 is the current session (SessionPresent) flag bit.

Its second byte is the return code field. If the server receives a CONNECT message but cannot process it for some reason, the server will return a CONNACK message containing the return code. If the server returns a CONNACK message with a non-zero return code field, it must close the network connection. See the table for the return code description.

Form return code value and corresponding description

return code value describe
0x00 The connection has been accepted by the server
0x01 Connection refused, the server does not support the MQTT protocol level requested by the client
0x02 Connection refused, server identifier is in correct UTF-8 encoding, but is not allowed
0x03 Connection refused, network connection established but MQTT service unavailable
0x04 Connection refused, invalid data format for username or password
0x05 Connection refused, client is not authorized to connect to this server
0x06~0xFF reserved for unused

提示:如果服务端收到清理会话(CleanSession)标志为1 的连接,除了将CONNACK 报文中的返回码设置为0 之外,还必须将CONNACK 报文中的当前会话设置(Session Present)标志为0。

那么总的来说,CONNACK 报文的可变报头部分内容具体见图。

insert image description here

图 CONNACK 可变报头

在此,就不再对MQTT 报文的可变报头部分过多赘述,大家可以参考MQTT 协议手册,里面有很详细的描述。

有效载荷

有效载荷也是存在与某些报文中,不同的报文有效载荷也是不一样的,比如:

CONNECT 报文的有效载荷(payload)包含一个或多个以长度为前缀的字段,可变报头中的标志决定是否包含这些字段。如果包含的话,必须按这个顺序出现:客户端标识符,遗嘱主题,遗嘱消息,用户名,密码。

SUBSCRIBE 报文的有效载荷包含了一个主题过滤器列表,它们标识着客户端想要订阅的主题,每一个过滤器后面跟着一个字节,这个字节被叫做服务质量要求(Requested QoS),它给出了服务端向客户端发送应用消息所允许的最大QoS 等级。

这里只是讲述了一小部分内容,关于具体的有效载荷部分也可以去看MQTT 手册,此处就不再赘述。

移植MQTT 协议

初步了解了MQTT 协议,那么我们怎么在开发板上运行它呢?首先,LwIP 协议肯定是要的,因为MQTT 是应用层协议,基于TCP 协议至少,首先我们就需要把LwIP 协议跑通,我们就使用Socket API 来进行移植。

首先下载MQTT 的库:https://github.com/eclipse/paho.mqtt.embedded-c。

然后创建一个MQTT 文件夹,再将MQTTPacketsrc 目录下的文件添加到工程目录MQTT 文件夹,再将MQTTPacketsamples 目录下的transport.c、transport.h 添加到这个文件夹下,添加完成后文件夹内容具体见图。

insert image description here

图MQTT 文件夹下的内容

我们把这些文件加入我们的工程之中,并且指定头文件路径,然后实现transport.c 文件的移植层接口,其内容具体见代码清单

代码清单transport.c 文件内容

#include "transport.h"
#include "lwip/opt.h"
#include "lwip/arch.h"
#include "lwip/api.h"
#include "lwip/inet.h"
#include "lwip/sockets.h" (1)
#include "string.h"
static int mysock;
/************************************************************************
** 函数名称: transport_sendPacketBuffer
** 函数功能: 以TCP 方式发送数据
** 入口参数: unsigned char* buf:数据缓冲区
** int buflen:数据长度
** 出口参数: <0 发送数据失败
************************************************************************/
int32_t transport_sendPacketBuffer( uint8_t* buf, int32_t buflen)
{
    
    
	int32_t rc;
	rc = write(mysock, buf, buflen); (2)
	return rc;
}
/************************************************************************
** 函数名称: transport_getdata
** 函数功能: 接收TCP 数据
** 入口参数: unsigned char* buf:数据缓冲区
** int count:数据长度
** 出口参数: <=0 接收数据失败
************************************************************************/
int32_t transport_getdata(uint8_t* buf, int32_t count)
{
    
    
	int32_t rc;
	
	rc = recv(mysock, buf, count, 0); (3)
	return rc;
}

/************************************************************************
** 函数名称: transport_open
** 函数功能: 打开一个接口,并且和服务器建立连接
** 入口参数: char* servip: 服务器域名
** int port: 端口号
** 出口参数: <0 打开连接失败
************************************************************************/
int32_t transport_open(int8_t* servip, int32_t port)
{
    
    
	int32_t *sock = &mysock;
	int32_t ret;
// int32_t opt;
	struct sockaddr_in addr;
	
	//初始化服务器信息
	memset(&addr,0,sizeof(addr));
	addr.sin_len = sizeof(addr);
	addr.sin_family = AF_INET;
	//填写服务器端口号
	addr.sin_port = PP_HTONS(port);
	//填写服务器IP 地址
	addr.sin_addr.s_addr = inet_addr((const char*)servip);
	
	//创建SOCK
	*sock = socket(AF_INET,SOCK_STREAM,0); (4)
	//连接服务器
	ret = connect(*sock,(struct sockaddr*)&addr,sizeof(addr)); (5)
	if (ret != 0)
	{
    
    
		//关闭链接
		close(*sock);
		//连接失败
		return -1;
}
//连接成功, 设置超时时间1000ms
// opt = 1000;
// setsockopt(*sock,SOL_SOCKET,SO_RCVTIMEO,&opt,sizeof(int));
//返回套接字
return *sock;
}
/************************************************************************
** 函数名称: transport_close
** 函数功能: 关闭套接字
** 入口参数: unsigned char* buf:数据缓冲区
** int buflen:数据长度
** 出口参数: <0 发送数据失败
************************************************************************/
int transport_close(void)
{
    
    
	int rc;
	// rc = close(mysock);
	rc = shutdown(mysock, SHUT_WR);
	rc = recv(mysock, NULL, (size_t)0, 0);
	rc = close(mysock); (6)
	return rc;
}
  • 代码清单(1):添加头文件,我们使用Socket API 就添加LwIP 中对应的头文件。
  • 代码清单(2):transport_sendPacketBuffer() 函数是MQTT 发送数据函数,这个函数必须以TCP协议发送数据,参数buf 指定数据缓冲区,buflen 指定了数据长度,调用write() 函数进行发送数据,并且返回发送状态。
  • 代码清单(3):transport_getdata() 函数是MQTT 接收数据的函数,需要我们用Socket API 获取接收到的数据,参数buf 指定数据缓冲区,count 指定了获取数据长度,我们只要调用recv() 将数据获取回来即可。
  • 代码清单(4)(5):transport_open() 函数用于打开一个连接接口,并且让客户端和服务器建立连接,这个函数是实现MQTT 的前提,必须产生TCP 连接才能进入下一步操作,因此我们在函数中需要根据配置信息连接到服务器中,socket() 用于创建一个套接字,并且调用connect() 函数连接到服务器上,如果连接失败则关闭套接字,返回-1。
  • 代码清单(6):transport_close() 是MQTT 与服务器断开的时候会调用的函数,它用来关闭一个套接字的。
    然后我们在工程中实现两个线程,一个是MQTT 发送线程,另一个是MQTT 接收线程,这样子的话,我们的MQTT 协议就在开发板中跑起来了,我们提供了完整的MQTT 客户端连接到服务器demo,下面简单实现两个线程的处理,更多的代码请参考我们的工程,我们首先在USER 目录下创建一个mqttclient.c 文件,然后加入代码清单 所示代码。

代码清单mqttclient.c 文件内容(部分)

void mqtt_recv_thread(void *pvParameters)
{
    
    
	uint32_t curtick;
	uint8_t no_mqtt_msg_exchange = 1;
	uint8_t buf[MSG_MAX_LEN];
	int32_t buflen = sizeof(buf);
	int32_t type;
	fd_set readfd;
	struct timeval tv; //等待时间
	tv.tv_sec = 0;
	tv.tv_usec = 10;
	
MQTT_START:
	//开始连接
	Client_Connect();
	//获取当前滴答,作为心跳包起始时间
	curtick = xTaskGetTickCount();
	while (1)
	{
    
    
		//表明无数据交换
		no_mqtt_msg_exchange = 1;
		
		FD_ZERO(&readfd);
		FD_SET(MQTT_Socket,&readfd);
		
		//等待可读事件
		select(MQTT_Socket+1,&readfd,NULL,NULL,&tv);
		//判断MQTT 服务器是否有数据
		if (FD_ISSET(MQTT_Socket,&readfd) != 0)
		{
    
    
			//读取数据包--注意这里参数为0,不阻塞
			type = ReadPacketTimeout(MQTT_Socket,buf,buflen,0);
			if (type != -1)
			{
    
    
				mqtt_pktype_ctl(type,buf,buflen);
				//表明有数据交换
				no_mqtt_msg_exchange = 0;
				//获取当前滴答,作为心跳包起始时间
				curtick = xTaskGetTickCount();
			}
		}
		
	//这里主要目的是定时向服务器发送PING 保活命令
	if ((xTaskGetTickCount() - curtick) >(KEEPLIVE_TIME/2*1000))
	{
    
    
		curtick = xTaskGetTickCount();
		//判断是否有数据交换
		if (no_mqtt_msg_exchange == 0)
		{
    
    
			//如果有数据交换,这次就不需要发送PING 消息
			continue;
		}
		
		if (MQTT_PingReq(MQTT_Socket) < 0)
		{
    
    
			//重连服务器
			PRINT_DEBUG("发送保持活性ping 失败....\n");
			goto CLOSE;
		}
		
		//心跳成功
		PRINT_DEBUG("发送保持活性ping 作为心跳成功....\n");
		//表明有数据交换
		no_mqtt_msg_exchange = 0;
		}
	}
	
CLOSE:
	//关闭链接
	transport_close();
	//重新链接服务器
	goto MQTT_START;
}
void mqtt_send_thread(void *pvParameters)
{
    
    
	int32_t ret;
	uint8_t no_mqtt_msg_exchange = 1;
	uint32_t curtick;
uint8_t res;
/* 定义一个创建信息返回值,默认为pdTRUE */
BaseType_t xReturn = pdTRUE;
/* 定义一个接收消息的变量*/
// uint32_t* r_data;
DHT11_Data_TypeDef* recv_data;
//初始化json 数据
cJSON* cJSON_Data = NULL;
cJSON_Data = cJSON_Data_Init();
double a,b;
MQTT_SEND_START:
while (1)
{
    
    
xReturn = xQueueReceive( MQTT_Data_Queue, /* 消息队列的句柄*/
&recv_data, /* 发送的消息内容*/
3000); /* 等待时间3000ms */
if (xReturn == pdTRUE)
{
    
    
a = recv_data->temperature;
b = recv_data->humidity;
printf("a = %f,b = %f\n",a,b);
			//更新数据
			res = cJSON_Update(cJSON_Data,TEMP_NUM,&a);
			res = cJSON_Update(cJSON_Data,HUM_NUM,&b);
			
			if (UPDATE_SUCCESS == res)
			{
    
    
				//更新数据成功,
				char* p = cJSON_Print(cJSON_Data);
				//发布消息
				ret = MQTTMsgPublish(MQTT_Socket,(char*)TOPIC,QOS0,(uint8_t*)p);
				
				if (ret >= 0)
				{
    
    
					//表明有数据交换
					no_mqtt_msg_exchange = 0;
					//获取当前滴答,作为心跳包起始时间
					curtick = xTaskGetTickCount();
				}
				vPortFree(p);
				p = NULL;
			}
			else
				PRINT_DEBUG("update fail\n");
		}
		//这里主要目的是定时向服务器发送PING 保活命令
		if ((xTaskGetTickCount() - curtick) >(KEEPLIVE_TIME/2*1000))
		{
    
    
			curtick = xTaskGetTickCount();
			//判断是否有数据交换
			if (no_mqtt_msg_exchange == 0)
			{
    
    
				//如果有数据交换,这次就不需要发送PING 消息
				continue;
			}

			if (MQTT_PingReq(MQTT_Socket) < 0)
			{
    
    
				//重连服务器
				PRINT_DEBUG("发送保持活性ping 失败....\n");
				goto MQTT_SEND_CLOSE;
			}

			//心跳成功
			PRINT_DEBUG("发送保持活性ping 作为心跳成功....\n");
			//表明有数据交换
			no_mqtt_msg_exchange = 0;
		}
	}
MQTT_SEND_CLOSE:
	//关闭链接
	transport_close();
	//开始连接
	Client_Connect();
	goto MQTT_SEND_START;
}
void
mqtt_thread_init(void)
{
    
    
	sys_thread_new("mqtt_recv_thread", mqtt_recv_thread, NULL, 2048, 6);
	sys_thread_new("mqtt_send_thread", mqtt_send_thread, NULL, 2048, 7);
}

cJSON 移植

In fact, we use cJSON content in the mqttclient.c file, and often use JSON format to send and receive data when connecting to major cloud platforms, so we must also port cJSON to our project. cJSON is a C language library for parsing JSON packets. The library files are cJSON.c and cJSON.h. All implementations are in these two files.

The porting of cJSON is very simple. First, we first download the source file of cJSON: https://github.com/DaveGamble/cJSON.

Then find cJSON.c and cJSON.h in the file directory, copy them to the cJSON folder in our project directory (create it if not), then add it to the project, and specify the header file path, Because we are using the FreeRTOS operating system, the dynamic memory allocation and release functions in cJSON need to cooperate with the dynamic memory allocation and release functions of the operating system. Just modify the code shown in the code list in the cJSON.c file. Of course Also need to pay attention to including FreeRTOS related header files.

Code list cJSON.c file modified content

static void * CJSON_CDECL internal_malloc(size_t size)
{
    
    
	// return malloc(size);
	return pvPortMalloc(size);
}
static void CJSON_CDECL internal_free(void *pointer)
{
    
    
	// free(pointer);
	vPortFree(pointer);
}
static void * CJSON_CDECL internal_realloc(void *pointer,
		size_t size)
{
    
    
	// return realloc(pointer, size);
	return NULL;
}
#else
#define internal_malloc pvPortMalloc
#define internal_free vPortFree
#define internal_realloc
#endif

In order to make better use of the functions provided by cJSON to process our programs, we simply encapsulate cJSON, including initialization, update, parsing, etc. of cJSON format data. Of course, you can also encapsulate and use it yourself. We create a cJSON_Process.c file, And add the following code, see the code list for details.

Code list cJSON_Process.c file content

#include "cJSON_Process.h"
#include "main.h"
cJSON* cJSON_Data_Init(void)
{
    
    
	cJSON* cJSON_Root = NULL; //json 根节点
	
	cJSON_Root = cJSON_CreateObject(); /* 创建项目*/
	if (NULL == cJSON_Root)
	{
    
    
		return NULL;
	}/* 添加元素键值对*/
	cJSON_AddStringToObject(cJSON_Root, NAME, DEFAULT_NAME);
	cJSON_AddNumberToObject(cJSON_Root, TEMP_NUM, DEFAULT_TEMP_NUM);
	cJSON_AddNumberToObject(cJSON_Root, HUM_NUM, DEFAULT_HUM_NUM);
	
	char* p = cJSON_Print(cJSON_Root); /*p 指向的字符串是json 格式的*/

// PRINT_DEBUG("%s\n",p);

	vPortFree(p);
	p = NULL;
	
	return cJSON_Root;

}
uint8_t cJSON_Update(const cJSON * const object,const char * const string,void *d)
{
    
    
	cJSON* node = NULL; //json 根节点
	node = cJSON_GetObjectItem(object,string);
	if (node == NULL)
		return NULL;
	if (cJSON_IsBool(node))
	{
    
    
		int *b = (int*)d;
// printf ("d = %d",*b);
		cJSON_GetObjectItem(object,string)->type = *b ? cJSON_True : cJSON_False;
// char* p = cJSON_Print(object); /*p 指向的字符串是json 格式的*/
		return 1;
	}
	else if (cJSON_IsString(node))
	{
    
    
		cJSON_GetObjectItem(object,string)->valuestring = (char*)d;
// char* p = cJSON_Print(object); /*p 指向的字符串是json 格式的*/
		return 1;
	}
	else if (cJSON_IsNumber(node))
	{
    
    
		double *num = (double*)d;
// printf ("num = %f",*num);
// cJSON_GetObjectItem(object,string)->valueint = (double)*num;
		cJSON_GetObjectItem(object,string)->valuedouble = (double)*num;
// char* p = cJSON_Print(object); /*p 指向的字符串是json 格式的*/
		return 1;
	}
	else
		return 1;
}

void Proscess(void* data)
{
    
    
	PRINT_DEBUG("开始解析JSON 数据");
	cJSON *root,*json_name,*json_temp_num,*json_hum_num;
	root = cJSON_Parse((char*)data); //解析成json 形式

	json_name = cJSON_GetObjectItem( root , NAME); //获取键值内容
	json_temp_num = cJSON_GetObjectItem( root , TEMP_NUM );
	json_hum_num = cJSON_GetObjectItem( root , HUM_NUM );

	PRINT_DEBUG("name:%s\n temp_num:%f\n hum_num:%f\n",
				json_name->valuestring,
				json_temp_num->valuedouble,
				json_hum_num->valuedouble);

	cJSON_Delete(root); //释放内存
}

At this point, our entire MQTT program framework is basically completed, and we will start using the MQTT program below.


Reference: LwIP Application Development Practical Guide - Based on Wildfire STM32

Guess you like

Origin blog.csdn.net/picassocao/article/details/129278260