MQTT报文解析

MQTT是消息队列遥测传输协议的简称,是一个轻量级的数据传输协议,常见于嵌入式系统与云端的通信。

MQTT的报文格式

MQTT的报文格式为:固定报头 + 可变报头 + 有效载荷

说明:

  1. 固定报头:可理解为帧头+帧长,里面总共包括了控制报文的类型和控制报文类型的标志位,还有剩余长度。

    • 控制报文类型:用来说明该发送的报文是CONNECT、PUBLISH还是SUBSCRIBE,即用来说明发送的报文的类型

    • 控制报文类型标志位:即根据官方文档给出的该报文的序列号,有下面几种:

      在这里插入图片描述

      控制报文类型:

      在这里插入图片描述
      在这里插入图片描述

    • 剩余长度:存储剩余的报文长度,但是不包括自己本身,即不包括用于编码剩余长度的字节本身。

      注意:剩余长度的编码需要UTF-8编码,即用两个字节来作为前缀,然后再加上剩余字节的长度

    剩余长度的计算:

    ​ 剩余长度采用一个变长编码方案,当剩余长度小于128字节时,使用单字节编码,当剩余长度大于128字节时,使用两个字节来对剩余长度进行编码。

    1. 当剩余长度小于128字节时:

      例如:剩余长度为64字节,则编码字节为0x40

    2. 当剩余长度大于128字节时:

      在这里插入图片描述

在剩余字节的计算方法中,官方也给出了伪代码:

do encodedByte = X MOD 128

X = X DIV 128

// if there are more data to encode, set the top bit of this byte if ( X > 0 )

encodedByte = encodedByte OR 128 endif

‘output’ encodedByte while ( X > 0 )

可变报头:可以理解为协议类型 + 协议名

**有效载荷:**可以理解为该报文所携带的信息

CONNECT报文解析

固定报头

官方文档说明如下:

在这里插入图片描述

控制报文类型根据上面查表可知,CONNECT控制报文的类型为1,后面的保留位全为0,所以第一个字节就为0x10

剩余长度的计算可根据官方给的伪代码进行计算,下面会给出代码。

可变报头

在这里插入图片描述
在这里插入图片描述

有上面可知,可变报头的MSB为0,LSB为4,然后后面跟着协议名的UTF-8编码

协议级别:是一个固定值

在这里插入图片描述
在这里插入图片描述
由于连接标志的每个bit位都有含义,这里不细说,可查阅官方文档。

可变报头的最后,是两个字节,用来保存连接时间:

在这里插入图片描述

CONNECT报文的可变报头总共10个字节,从固定报头那里已经说明,注意不要写少了!!!

有效载荷

在这里插入图片描述

有效载荷简单点说就是,在连接标志中设置了什么,在有效载荷中也必须设置!!!

STM32F429IGT6上实现MQTT与巴法云通信

mqtt.h

#ifndef __MQTT_H_
#define __MQTT_H_

#include "stm32f4xx.h"                  // Device header
#include "gpio.h"
#include "wifi.h"
#include "uart.h"
#include "log.h"

#define BYTE0(len)  ( *( (char *)(&len) + 0 ) ) // LSB
#define BYTE1(len)  ( *( (char *)(&len) + 1 ) ) // MSB

void MQTT_Connect_Msg(const char *client_id);

#endif

mqtt.c

#include "mqtt.h"

typedef struct {
    uint8_t mqtt_msg_buf[512]; // mqtt报文消息buffer
    uint8_t mqtt_connect_flag;  // 连接标志
    uint32_t mqtt_msg_cnt; // 消息长度计数器
} MQTT_MSG_T;

// 初始化结构体成员
MQTT_MSG_T mqtt_msg = {
    .mqtt_connect_flag = 0,
    .mqtt_msg_cnt = 0,
};

void MQTT_Connect_Msg(const char *client_id)
{
    memset(mqtt_msg.mqtt_msg_buf, 0, sizeof(mqtt_msg.mqtt_msg_buf));
    mqtt_msg.mqtt_msg_cnt = 0;
    mqtt_msg.mqtt_connect_flag = 0;
    uint32_t encodedByte;
    uint32_t cnt = 3000; // 等待时间计数器

    uint32_t id_len = strlen(client_id);  // id长度
    uint32_t remain_len = 10 + (2 + id_len);  // utf-8编码,需要两个字节作为前缀

    /*固定报头*/
    mqtt_msg.mqtt_msg_buf[mqtt_msg.mqtt_msg_cnt++] = 0x10;
    // 剩余长度  根据官方给的伪代码进行计算
    do{
        encodedByte = remain_len % 128;
        remain_len = remain_len / 128;
        // if there are more data to encode, set the top bit of this byte
        if ( remain_len > 0 )
            encodedByte = encodedByte | 128;
        else
            mqtt_msg.mqtt_msg_buf[mqtt_msg.mqtt_msg_cnt++] = encodedByte;
    } while (remain_len > 0);
    
    /*可变报头*/
    mqtt_msg.mqtt_msg_buf[mqtt_msg.mqtt_msg_cnt++] = 0x00; // 长度MSB
    mqtt_msg.mqtt_msg_buf[mqtt_msg.mqtt_msg_cnt++] = 0x04; // 长度LSB
    mqtt_msg.mqtt_msg_buf[mqtt_msg.mqtt_msg_cnt++] = 'M';  // 协议名
    mqtt_msg.mqtt_msg_buf[mqtt_msg.mqtt_msg_cnt++] = 'Q';
    mqtt_msg.mqtt_msg_buf[mqtt_msg.mqtt_msg_cnt++] = 'T';
    mqtt_msg.mqtt_msg_buf[mqtt_msg.mqtt_msg_cnt++] = 'T';
    mqtt_msg.mqtt_msg_buf[mqtt_msg.mqtt_msg_cnt++] = 0x04; // 协议级别
    mqtt_msg.mqtt_msg_buf[mqtt_msg.mqtt_msg_cnt++] = 0x02; // 连接标志 此处仅设置清理会话
    mqtt_msg.mqtt_msg_buf[mqtt_msg.mqtt_msg_cnt++] = 0x00; // 保持连接MSB
    mqtt_msg.mqtt_msg_buf[mqtt_msg.mqtt_msg_cnt++] = 0x78; // 保持连接LSB,总共保持120s

    /*有效载荷*/
    mqtt_msg.mqtt_msg_buf[mqtt_msg.mqtt_msg_cnt++] = BYTE1(id_len); // 客户端标识符的utf-8编码前缀 MSB
    mqtt_msg.mqtt_msg_buf[mqtt_msg.mqtt_msg_cnt++] = BYTE0(id_len);
    memcpy(&mqtt_msg.mqtt_msg_buf[mqtt_msg.mqtt_msg_cnt], client_id, id_len); // 拷贝有效载荷内容
    mqtt_msg.mqtt_msg_cnt += id_len; // 位置便宜

    // 连接服务器
    UART_SendBuffer(USART6, mqtt_msg.mqtt_msg_buf, mqtt_msg.mqtt_msg_cnt);
    while(cnt--){
        delay_ms(4); // 注意等待的时间,不够长可能连接不上

        // 根据响应报文进行解析
        if(wifi_uart.wifi_recv_buf[0] == 0x20 && wifi_uart.wifi_recv_buf[1] == 0x02){ // 返回的固定报头
            switch (wifi_uart.wifi_recv_buf[3])
            {
            case 0x00:
                mqtt_msg.mqtt_connect_flag = 1;
                LOG(INFO, "连接已接受!\n");
                break;
            case 0x01:
                mqtt_msg.mqtt_connect_flag = 0;
                LOG(LERROR, "连接已拒绝,不支持的协议版本!\n");
                break;
            case 0x02:
                mqtt_msg.mqtt_connect_flag = 0;
                LOG(LERROR, "连接已拒绝,不合格的客户端标识符!\n");
                break;
            case 0x03:
                mqtt_msg.mqtt_connect_flag = 0;
                LOG(LERROR, "连接已拒绝,服务端不可用!\n");
                break;
            case 0x04:
                mqtt_msg.mqtt_connect_flag = 0;
                LOG(LERROR, "连接已拒绝,无效的用户名或密码!\n");
                break;
            case 0x05:
                mqtt_msg.mqtt_connect_flag = 0;
                LOG(LERROR, "连接已拒绝,未授权!\n");
                break;
            default:
                break;
            }

            break;
        }
    }
    wifi_clear_buf();  // 清除信息结构体
}

main.c


#include "main.h"

void sys_init(void)
{
	led_init();
	UART_Init(USART6, 115200);
	UART_Init(USART1, 9600);
}

int main(void)
{
	sys_init();
	
	MQTT_Connect_Msg("your private key");
	
	while (1)
	{
		
	}
}

void USART6_IRQHandler(void)
{
	u8 data = 0;
	
	 if(USART_GetITStatus(USART6, USART_IT_RXNE) == SET){
        data = USART_ReceiveData(USART6);
        
        if(wifi_uart.uart_recvcnt < wifi_uart.wifi_recv_len){
            wifi_uart.wifi_recv_buf[wifi_uart.uart_recvcnt++] = data;
        }
		
		send_data_check(data);
        
        USART_ClearITPendingBit(USART6, USART_IT_RXNE);
	}
}

上面的LOG其实就是用其他uart进行打印操作

    if(wifi_uart.uart_recvcnt < wifi_uart.wifi_recv_len){
        wifi_uart.wifi_recv_buf[wifi_uart.uart_recvcnt++] = data;
    }
	
	send_data_check(data);
    
    USART_ClearITPendingBit(USART6, USART_IT_RXNE);
}

}


> 上面的LOG其实就是用其他uart进行打印操作

> 以上仅为个人理解,如有误请指出,感谢!!!
Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐