[FreeRTOS] Porting MQTT of LWIP 2.1.2 based on STM32

related articles

1. "MQTT Protocol Analysis Summary (1)"
2. "MQTT Protocol Analysis Summary (2)"
3. "[IoT] How to Connect to Baidu IoT Cloud"
4. "[FreeRTOS] MQTT based on STM32 transplantation of LWIP 2.1.2 》

1 Introduction

MQTT protocolWorking on TCP , the end and the agent exchange predefined control messages to complete the communication. Because MQTT is an application layer protocol, it needs to run on the LwIP protocol , so we use the Socket API for porting.

IoT Hub is a fully managed cloud service for developers in the Internet of Things field. Through the mainstream Internet of Things protocol MQTT communication, a secure two-way connection can be established between smart devices and the cloud to quickly implement Internet of Things projects.

2. Download the MQTT source code

MQTT source code download address: https://github.com/eclipse/paho.mqtt.embedded-c
Insert picture description here

This repository contains the source code of the Eclipse Paho MQTT C/c++ client library for embedded platforms. There are three sub-projects:

  • MQTTPacket: Simple deserialization/serialization of MQTT packets
  • MQTTClient: Advanced C++ client
  • MQTTClient-C: Advanced C client (almost a clone of C++ client)

We are here for the STM32 platform, and we chose the MQTTPacket project code.

3. Porting the MQTT protocol

  • Create a MQTTfolder and then MQTTPacket\srcadd files to the project directory under the directory MQTTfolder.
    Insert picture description here
  • Then the MQTTPacket\samplesdirectory under transport.c, transport.hadded to this folder.
    Insert picture description here
  • Add MQTT header file path
    Insert picture description here

4. Modify the transport.c file

transport.cThe file is the API of the MQTT protocol to call the underlying socket interface. Basically, you can use the sample code transport.c. Here are some fine-tunings. as follows:

#include "transport.h"
#include "lwip/opt.h"
#include "lwip/arch.h"
#include "lwip/api.h"
#include "lwip/inet.h"
#include "lwip/sockets.h"
#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);
	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);
	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);
	//连接服务器 
	ret = connect(*sock,(struct sockaddr*)&addr,sizeof(addr));
	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);
	return rc;
}

5. Add the mqttclient.c file

mqttclient.cThe file mainly created 2 threads: Receive Thread and Send Thread . It is responsible for receiving and parsing messages sent by the MQTT server and sending messages to the MQTT server.

  • The
    main flow chart of Receive Thread Receive Thread is as follows:
    Insert picture description here
    Receive Thread code is as follows:

    void Client_Connect(void)
    {
          
          
        char* host_ip;
      
    #if  LWIP_DNS
        ip4_addr_t dns_ip;
        netconn_gethostbyname(HOST_NAME, &dns_ip);
        host_ip = ip_ntoa(&dns_ip);
        printf("host name : %s , host_ip : %s\r\n",HOST_NAME,host_ip);
    #else
        host_ip = HOST_NAME;
    #endif  
    MQTT_START: 
      
    		//创建网络连接
    		printf("1.开始连接对应云平台的服务器...\r\n");
        printf("服务器IP地址:%s,端口号:%0d!\r\n",host_ip,HOST_PORT);
    		while(1)
    		{
          
          
    				//连接服务器
    				MQTT_Socket = transport_open((int8_t*)host_ip,HOST_PORT);
    				//如果连接服务器成功
    				if(MQTT_Socket >= 0)
    				{
          
          
    						printf("连接云平台服务器成功!\r\n");
    						break;
    				}
    				printf("连接云平台服务器失败,等待3秒再尝试重新连接!\r\n");
    				//等待3秒
    				vTaskDelay(3000);
    		}
        
        printf("2.MQTT用户名与密钥验证登录...\r\n");
        //MQTT用户名与密钥验证登录
        if(MQTT_Connect() != Connect_OK)
        {
          
          
             //重连服务器
             printf("MQTT用户名与密钥验证登录失败...\r\n");
              //关闭链接
             transport_close();
             goto MQTT_START;	 
        }
        
    		//订阅消息
    		printf("3.开始订阅消息...\r\n");
        //订阅消息
        if(MQTTSubscribe(MQTT_Socket,(char *)TOPIC,QOS1) < 0)
        {
          
          
             //重连服务器
             printf("客户端订阅消息失败...\r\n");
              //关闭链接
             transport_close();
             goto MQTT_START;	   
        }	
    
    		//无限循环
    		printf("4.开始循环接收订阅的消息...\r\n");
    
    }
    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)
    			{
          
          
    				//重连服务器
    				printf("发送保持活性ping失败....\r\n");
    				goto CLOSE;	 
    			}
    
    			//心跳成功
    			printf("发送保持活性ping作为心跳成功....\r\n");
    			//表明有数据交换
    			no_mqtt_msg_exchange = 0;
    		}	
    	}
    
    CLOSE:
    	//关闭链接
    	transport_close();
    	//重新链接服务器
    	goto MQTT_START;   
    }
    
  • The
    main flowchart of Send Thread Send Thread is as follows:
    Insert picture description here

  • Send Thread
    Send Thread code is as follows:

    void mqtt_send_thread(void *pvParameters)
    {
          
          
        uint8_t res;
        /* 定义一个创建信息返回值,默认为pdTRUE */
        BaseType_t xReturn = pdTRUE;
        /* 定义一个接收消息的变量 */
        Stu_Sys_Data_TypeDef* recv_data;
        //初始化json数据
        cJSON* cJSON_Data = NULL;
        cJSON_Data = cJSON_Data_Init();
        while(1)
        {
          
          
            
        xReturn = xQueueReceive( MQTT_Data_Queue,    /* 消息队列的句柄 */
                                 &recv_data,      /* 发送的消息内容 */
                                 portMAX_DELAY); /* 等待时间 3000ms */
          if(xReturn == pdTRUE)
          {
          
          
            //更新数据      
    		res = cJSON_Update(cJSON_Data, STUDENT_ID, &recv_data->sid);
    		res = cJSON_Update(cJSON_Data, HEIGHT_NUM, &recv_data->heightNum);
    		res = cJSON_Update(cJSON_Data, WEIGHT_NUM, &recv_data->weightNum);
    		
            if(UPDATE_SUCCESS == res)
            {
          
          
                //更新数据成功,
                char* p = cJSON_Print(cJSON_Data);
                //发布消息
                MQTTMsgPublish(MQTT_Socket,(char*)TOPIC,QOS0,(uint8_t*)p);
    			
                vPortFree(p);
                p = NULL;
            }
            else
              printf("update fail\r\n");
          }
        }
    }
    

6. Verification test

Finally, the verification test is successful, as follows:
Insert picture description here

7. Data download address

The complete code download address for successful transplantation is as follows:
https://download.csdn.net/download/ZHONGCAI0901/14023611

Guess you like

Origin blog.csdn.net/ZHONGCAI0901/article/details/111874877