MQTT报文解析
是消息队列遥测传输协议的简称,是一个轻量级的数据传输协议,常见于嵌入式系统与云端的通信。
MQTT报文解析
MQTT是消息队列遥测传输协议的简称,是一个轻量级的数据传输协议,常见于嵌入式系统与云端的通信。
MQTT的报文格式
MQTT的报文格式为:固定报头 + 可变报头 + 有效载荷。
说明:
-
固定报头:可理解为帧头+帧长,里面总共包括了控制报文的类型和控制报文类型的标志位,还有剩余长度。
-
控制报文类型:用来说明该发送的报文是CONNECT、PUBLISH还是SUBSCRIBE,即用来说明发送的报文的类型
-
控制报文类型标志位:即根据官方文档给出的该报文的序列号,有下面几种:

控制报文类型:


-
剩余长度:存储剩余的报文长度,但是不包括自己本身,即不包括用于编码剩余长度的字节本身。
注意:剩余长度的编码需要
UTF-8编码,即用两个字节来作为前缀,然后再加上剩余字节的长度
剩余长度的计算:
剩余长度采用一个变长编码方案,当剩余长度小于128字节时,使用单字节编码,当剩余长度大于128字节时,使用两个字节来对剩余长度进行编码。
-
当剩余长度小于128字节时:
例如:剩余长度为64字节,则编码字节为
0x40 -
当剩余长度大于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进行打印操作
> 以上仅为个人理解,如有误请指出,感谢!!!
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐

所有评论(0)