STM32之wifi模块与MQTT模块详解

发布于:2025-09-12 ⋅ 阅读:(19) ⋅ 点赞:(0)

1. 物联网组网

  • 设备组网是通过一定技术手段,将设备可以在一定的范围内进行数据传递和操作控制。常见的组网设备
  • WiFi 4G/5G NB-IoT LoRa BlueTooth LwIP (以太网)
  • 联网之后的设备可以将数据上传到云端,同时可以从云端得到相关的数据内容。
    • 远程监控
    • 远程操作
    • 批量多路处理

2. ESP 8266 WiFi 模块

2.1 原理图

  • 802.11 b/g/n
  • Wi-Fi Direct (P2P)、soft-AP
  • 内置 TCP/IP 协议栈
  • 内置 TR 开关、balun、LNA、功率放大器和匹配网络
  • 内置 PLL、稳压器和电源管理组件
  • 802.11b 模式下 + 19.5dBm 的输出功率
  • 支支持天线分集
  • 断电泄露电流小于 10uA
  • 内置低功率 32 位 CPU:可以兼作应用处理器
  • SDIO 2.0、SPI、UART
  • STBC、1x1 MIMO、2x1 MIMO
  • A-MPDU、A-MSDU 的聚合和 0.4μs 的保护间隔
  • 2ms 之内唤醒、连接并传递数据包
  • 待机状态消耗功率小于 1.0mW (DTIM3)

2.2 ESP8266 模块和 STM32 链接

ESP8266 模块对应的电路引脚情况

  • 重点关注 RXD 和 TXD
  • RXD 是用于接收 MCU 提供的 AT 指令
  • TXD 是用于反馈数据到 MCU,包括 AT 指令响应,网络数据响应内容,主要数据形式为【字符串形式】,除了基本字符串格式,包括方便后续解析使用的 JSON 格式数据

当前开发板中,对应的板载链接效果

  • ESP8266 模块的 RXD ==> MCU USART3 TX ==> PB10

  • ESP8266 模块的 TXD ==> MCU USART3 RX ==> PB11

  • 需要通过配置 USART3 数据发送和接收,来进行 ESP8266 WiFi 模块控制,利用 USART3 TX 发送 AT 指令到 WiFi 模块,利用 USART3 RX 接受 WiFi 模块的反馈数据。

  • PB11 ==> RX 浮空输入模式

  • PB10 ==> TX 复用推挽模式

2.3 AT 指令操作 WiFi 模块连接 AP 热点

void ESP8266_Connect(const char *ssid, const char *psk)
{
    printf("ESP8266 WiFi Connect!\n");

    // 1. 发送 AT 指令,判断当前设备是否正常工作
    ESP8266_Send_AT("AT\r\n", "OK", 2000);
    systick_delay_ms(500);

    // 2. 设置当前 ESP8266 工作模式为 STA(Station) 模式
    ESP8266_Send_AT("AT+CWMODE=1\r\n", "OK", 2000);
    systick_delay_ms(500);

    // 3. 扫描当前可以连接的 AP 热点信息,包括 SSID,信号强度
    ESP8266_Send_AT("AT+CWLAP\r\n", "OK", 2000);
    systick_delay_ms(5000);

    // 4. 连接目标热点,提供 SSID 和 PSK,组装字符串时,必须将
    // AT 指令以 \r\n 结尾
    char connectCmd[128] = "";

    sprintf(connectCmd, "AT+CWJAP=\"%s\",\"%s\"\r\n", ssid, psk);
    ESP8266_Send_AT(connectCmd, "OK", 2000);
    systick_delay_ms(5000);

    // 5. 网络连接情况
    ESP8266_Send_AT("AT+CIFSR\r\n", "OK", 2000);
    systick_delay_ms(2000);
}

3. MQTT 协议

3.1 MQTT 概述

  • 广泛用于物联网设备联网通信的传输协议,基于 TCP/IP 实现
  • MQTT 区分客户端和服务器,本地 STM32 + ESP8266 + MQTT 支持,可以认为是 MQTT 的客户端,同时云平台 (ThingsCloud Onenet Aliyun) 物联网平台可以认为是 MQTT 服务器。
  • MQTT 协议中,重点内容是【服务器连接】【订阅主题】【发布消息】
    • 服务器连接:当前 MQTT 客户端链接对应的 MQTT 服务器
    • 订阅主题:MQTT 服务器一旦通过指定主题下发数据,MQTT 客户端可以收到服务器下发的数据内容,可以完成设备控制,例如 LED 亮灭,舵机控制。。。
    • 发布消息:MQTT 客户端按照 MQTT 服务器要求,和云平台自定义内容,将本地数据发送到云平台。可以将传感器数据进行上传。。。
       

3.2 MQTT 相关技术内容

MQTT 技术特征:

  • MQTT 版本有 5.0.0 和 3.1.1
    • 5.0.0 是最新版本,支持功能更多,使用更为方便
    • 3.1.1 是目前主流设备端使用的大面积版本,使用相对复杂。

核心特性:

  1. 轻量高效
    • 协议头最小仅 2 字节,适合资源受限的设备(如传感器、嵌入式设备)。
    • 低功耗设计,适合电池供电设备。
  2. 发布 / 订阅模型
    • 解耦消息生产者(Publisher)和消费者(Subscriber),通过主题(Topic)路由消息。
    • 支持一对多、多对多通信。
  3. 多种服务质量(QoS)
    • QoS 0(最多一次):消息可能丢失。
    • QoS 1(至少一次):消息确保送达,但可能重复。
    • QoS 2(恰好一次):消息确保送达且不重复。
  4. 支持持久会话
    • 客户端可订阅持久化消息,断线重连后接收离线期间的消息(需 Broker 支持)。
  5. 低协议开销
    • 基于 TCP/IP,但也可适配其他传输层(如 WebSocket、MQTT - SN 用于传感器网络)。

3.3 MQTT 控制报文结构

3.3.1 MQTT 报文组成部分
        Fixed header : 确定当前 MQTT 发送的数据目标,例如 连接服务器,发布数据,订阅主题
        Variable header : 根据当前 MQTT Fixed header 目标任务需求,提供不同的报文参数
        Payload :根据当前 MQTT 报文类型和目标数据需求,提供的用户有效载荷内容

3.3.2 Fixed header 固定报头

格式内容

  • 主要有 MQTT 控制报文类型和报文类型指定标志
  • 剩余长度

        在连接和发布报文中控制位置0即可,而订阅报文控制位必须是0x02.

3.4 CONNECT – 连接服务端

3.4.1 CONNECT 主要作用

        客户端到服务端的网络连接建立后,客户端发送给服务端的第一个报文必须是 CONNECT 报文 [MQTT-3.1.0-1]。
       在一个网络连接上,客户端只能发送一次 CONNECT 报文。服务端必须将客户端发送的第二个 CONNECT 报文当作协议违规处理并断开客户端的连接 [MQTT-3.1.0-2]。有关错误处理的信息请查看 4.8 节。

        有效载荷包含一个或多个编码的字段。包括客户端的唯一标识符,Will 主题,Will 消息,用户名和密码。除了客户端标识之外,其它的字段都是可选的,基于标志位来决定可变报头中是否需要包含这些字段。

3.4.2 CONNECT 数据包分析

一、整体结构

MQTT Connect 报文由 Fixed Header(固定头)Variable Header(可变头)Payload(有效载荷) 三部分组成,用数组 MQTT_Connect_Data[256] 存储报文数据。


二、Fixed Header(固定头,2 字节)

字节索引 作用 计算 / 取值逻辑 示例值
0 报文类型 + 标志位 固定为 0x10(二进制 0001 0000,表示 CONNECT 报文,无额外标志位) 0x10
1 剩余长度(可变头 + 有效载荷总长度) 可变头长度(10 字节) + 有效载荷长度(19 字节) = 29 → 二进制 0001 1101 → 0x1D 0x1D

核心逻辑:固定头是报文 “身份标识”,第 0 字节固定 0x10,第 1 字节需计算可变头 + 有效载荷的总长度。


三、Variable Header(可变头,10 字节)

1. 协议名称(2 字节 + 协议内容 “MQTT” 4 字节 → 共 6 字节)

字节索引 作用 计算 / 取值逻辑 示例值
2 协议长度高位(MSB) 协议内容 “MQTT” 长度是 4 → 4 / 256 = 0 → 二进制 0000 0000 → 0x00 0x00
3 协议长度低位(LSB) 4 % 256 = 4 → 二进制 0000 0100 → 0x04 0x04
4-7 协议内容 “MQTT” 字符转 16 进制:M(0x4D)、Q(0x51)、T(0x54)、T(0x54) 0x4D 0x51 0x54 0x54
 

核心逻辑:用 2 + N 模式(2 字节存长度 + N 字节存内容),这里 “MQTT” 长度是 4,所以先存 0x00 0x04,再存字符对应的 16 进制。

2. 协议版本(1 字节)

字节索引 作用 计算 / 取值逻辑 示例值
8 MQTT 版本(3.1.1) 固定用 0x04 表示 MQTT 3.1.1 0x04

核心逻辑:协议规定 MQTT 3.1.1 对应 0x04,直接填固定值。

3. 连接标志(Connect Flags,1 字节)

字节索引 作用 计算 / 取值逻辑 示例值
9 标志位组合
组成部分是告知当前 MQTT 协议后续的载荷内容,每一个标志位告示后续的数据中,
是否包含对应的数据内容
Username ( 1 ) Password ( 1 ) Will Retain ( 1 ) Will QoS ( 2 ) Will
Flag ( 1 ) Clean Session ( 1 ) 保留 ( 1 )
根据 ThingsCloud 分析,需要提供用户名和密码,同时按照常规内容,不需要返回, QoS = 0
flag 不需要, Clean Session 需要
MQTT_Connect_Data [ 9 ] ==> 1100 0010 ==> 0xC2
0xC2
 

核心逻辑:每一位代表不同功能(如是否用用户名密码、是否清除会话等),按需求组合后转 16 进制。

4. 保持连接时间(Keep Alive,2 字节)

字节索引 作用 计算 / 取值逻辑 示例值
10 时间高位(MSB) 假设 Keep Alive = 60秒 → 60 / 256 = 0 → 二进制 0000 0000 → 0x00 0x00
11 时间低位(LSB) 60 % 256 = 60 → 二进制 0011 1100 → 0x3C(注意:原示例计算可能有误,60 转 16 进制是 0x3C,非 0x3CD ,这里以正确逻辑为准 ) 0x3C
 

核心逻辑:用 2 字节存时长(秒),高位存商、低位存余数,超过范围会触发重连。


四、Payload(有效载荷,19 字节)

按 2 + N 模式存储 客户端标识、用户名、密码,总长度 = 2(客户端标识长度头) + 9(客户端标识内容) + 2(用户名长度头) + 2(用户名内容) + 2(密码长度头) + 2(密码内容) = 19 字节。

1. 客户端标识(示例:Client_GL ,9 字节内容 + 2 字节长度头 → 共 11 字节)

字节索引 作用 计算 / 取值逻辑 示例值
12 长度高位(MSB) 客户端标识长度 9 → 9 / 256 = 0 → 0x00 0x00
13 长度低位(LSB) 9 % 256 = 9 → 0x09 0x09
14-22 客户端标识 “Client_GL” 字符转 16 进制:C(0x43)、l(0x6C)、i(0x69)、e(0x65)、n(0x6E)、t(0x74)、_(0x5F)、G(0x47)、L(0x4C) 对应字节依次为 0x43 0x6C 0x69 0x65 0x6E 0x74 0x5F 0x47 0x4C

2. 用户名(示例:CG ,2 字节内容 + 2 字节长度头 → 共 4 字节

字节索引 作用 计算 / 取值逻辑 示例值
23 长度高位(MSB) 用户名长度 2 → 2 / 256 = 0 → 0x00 0x00
24 长度低位(LSB) 2 % 256 = 2 → 0x02 0x02
25-26 用户名 “CG” 字符转 16 进制:C(0x43)、G(0x47) 0x43 0x47

3. 密码(示例:HL ,2 字节内容 + 2 字节长度头 → 共 4 字节)

字节索引 作用 计算 / 取值逻辑 示例值
27 长度高位(MSB) 密码长度 2 → 2 / 256 = 0 → 0x00 0x00
28 长度低位(LSB) 2 % 256 = 2 → 0x02 0x02
29-30 密码 “HL” 字符转 16 进制:H(0x48)、L(0x4C) 0x48 0x4C
 

核心逻辑:所有字段都用 2 + N 模式(2 字节存长度 + N 字节存内容),按实际字符长度计算存储。


五、总结流程(快速梳理)

  1. 固定头:填 0x10(报文类型) + 计算 “可变头 + 有效载荷长度” 填 0x1D
  2. 可变头
    • 协议名称:存 0x00 0x04 + “MQTT”(0x4D 0x51 0x54 0x54)。
    • 协议版本:填 0x04
    • 连接标志:按需求填 0xC2(含用户名密码、清除会话等)。
    • 保持连接:填 0x00(高位) + 0x3C(低位,Keep Alive=60 时)。
  3. 有效载荷
    • 客户端标识:存 0x00 0x09 + “Client_GL” 字符对应的 16 进制。
    • 用户名:存 0x00 0x02 + “CG” 字符对应的 16 进制。
    • 密码:存 0x00 0x02 + “HL” 字符对应的 16 进制。
3.4.3 STM32 利用 ESP8266 连接 MQTT 流程
  • ESP8266 联网
  • ESP8266 利用 TCP 协议和 MQTT 云平台建立连接
  • ESP8266 开启【透传】模式 + 【数据直发】方式
  • 将 Connect 操作数据包发送到 MQTT 云平台,建立和项目的连接。

3.5 PUBLISH – 发布消息

3.5.1 发布消息 Topic

        MQTT 客户端到服务器或者服务器到客户端,都需要通过发布主题进行明确当前提交的信息内容是什么内容。
        ThingsCloud 支持的发布主题 Topic。针对于 ThingsCloud 云端设备,选择 public_topic 对应 attributes。提交数据格式为 JSON 格式。
【注意】发布消息是在 MQTT 服务器连接之后

3.5.2 发布数据包分析

一、整体结构

MQTT PUBLISH 报文用于 客户端向服务器发布消息,由 Fixed Header(固定头)Variable Header(可变头)Payload(有效载荷) 三部分组成,用数组 MQTT_Publish_Data[256] 存储。


二、Fixed Header(固定头,2 字节)

字节索引 作用 计算 / 取值逻辑 示例值
0 报文类型 + 标志位 固定为 0x30(二进制 0011 0000,表示 PUBLISH 报文,QoS 0 时标志位为 0000 0x30
1 剩余长度(可变头 + 有效载荷总长度) 可变头长度(2 + strlen("attributes")) + 有效载荷长度(JSON 字符串长度) 需计算

核心逻辑

 
  • 第 0 字节固定 0x30(PUBLISH 报文类型)。
  • 第 1 字节需动态计算:可变头长度 + 有效载荷长度,具体值取决于主题名和 JSON 内容长度。

三、Variable Header(可变头,2 + N 字节,N 是主题名长度)

作用:存储 发布主题(Topic),格式为 2 + N(2 字节存长度 + N 字节存主题名)。

以 ThingsCloud 要求的主题 attributes 为例:

字节索引 作用 计算 / 取值逻辑 示例值
2 主题名长度高位(MSB) strlen("attributes") / 256 → 主题名长度是 10 → 10 / 256 = 0 → 0x00 0x00
3 主题名长度低位(LSB) strlen("attributes") % 256 → 10 % 256 = 10 → 0x0A 0x0A
4-13 主题名字符串 “attributes” 字符转 16 进制:a(0x61)、t(0x74)、t(0x74)、r(0x72)、i(0x69)、b(0x62)、u(0x75)、t(0x74)、e(0x65)、s(0x73) 对应字节依次为 0x61 0x74 0x74 0x72 0x69 0x62 0x75 0x74 0x65 0x73

核心逻辑
用 2 + N 模式存储主题名,先存长度(高位 + 低位),再存主题名字符对应的 16 进制。


四、Payload(有效载荷,JSON 字符串长度字节)

作用:存储 发布的实际数据,格式为 JSON 字符串(如传感器数据)。

以示例 JSON {"Temp":25.3,"Hum":55} 为例:

字节索引 作用 计算 / 取值逻辑 示例值
14-... JSON 字符串内容 直接拼接在可变头之后,字符转 16 进制存储 如 0x7B 0x22 0x54 0x65 0x6D 0x70 0x22 0x3A 0x32 0x53 0x2E 0x33 0x2C 0x22 0x48 0x75 0x6D 0x22 0x3A 0x35 0x35 0x7D

核心逻辑
有效载荷是 JSON 格式的业务数据,直接拼接在可变头之后,按字符转 16 进制存储。


五、代码实现流程(快速梳理)

  1. 初始化数组
    u8 MQTT_Publish_Data[256] = "";(预留足够空间存报文)

  2. 固定头

    • 第 0 字节:MQTT_Publish_Data[0] = 0x30;(PUBLISH 报文类型)
    • 第 1 字节:需计算 可变头长度 + 有效载荷长度,动态填充。
  3. 可变头(主题名)

    • 计算主题名长度:len = strlen("attributes")(结果为 10)
    • 存长度高位:MQTT_Publish_Data[2] = len / 256;(即 0x00
    • 存长度低位:MQTT_Publish_Data[3] = len % 256;(即 0x0A
    • 存主题名字符:memcpy(&MQTT_Publish_Data[4], "attributes", len);
  4. 有效载荷(JSON 数据)

    • 准备 JSON 字符串:{"Temp":25.3,"Hum":55}
    • 拼接数据:memcpy(&MQTT_Publish_Data[4 + len], json_str, strlen(json_str));
  5. 发送报文
    调用 ESP8266_SendBuffer(...) 发送完整数组。

3.6 SUBSCRIBE - 订阅主题

3.6.1 订阅主题 Topic
3.5.2 订阅主题数据包分析

一、整体结构

MQTT SUBSCRIBE 报文用于 客户端向服务器订阅主题,由 Fixed Header(固定头)Variable Header(可变头)Payload(有效载荷) 三部分组成,用数组 MQTT_Subscribe_Data[256] 存储。


二、Fixed Header(固定头,2 字节)

字节索引 作用 计算 / 取值逻辑 示例值
0 报文类型 + 标志位 固定为 0x80(二进制 1000 0000,表示 SUBSCRIBE 报文,QoS 0 时标志位为 0000 0x80
1 剩余长度(可变头 + 有效载荷总长度) 可变头长度(2 字节) + 有效载荷长度(2 + strlen("attributes/push") + 1 需计算

核心逻辑

 
  • 第 0 字节固定 0x80(SUBSCRIBE 报文类型)。
  • 第 1 字节需动态计算:可变头长度 + 有效载荷长度,具体值取决于主题名长度。

三、Variable Header(可变头,2 字节)

作用:存储 订阅数据包 ID(QoS 0 时可自定义,无实际校验逻辑)。

字节索引 作用 计算 / 取值逻辑 示例值
2 数据包 ID 高位 自定义为 0x00 0x00
3 数据包 ID 低位 自定义为 0x08 0x08

核心逻辑
QoS 0 时,ID 仅用于占位,可随意填(如 0x00 0x08),不影响功能。


四、Payload(有效载荷,2 + N + 1 字节,N 是主题名长度)

作用:存储 订阅主题(Topic) 和 QoS 等级,格式为 2(主题名长度) + N(主题名字符) + 1(QoS 等级)

以订阅主题 attributes/push 为例:

字节索引 作用 计算 / 取值逻辑 示例值
4 主题名长度高位(MSB) strlen("attributes/push") / 256 → 主题名长度是 14 → 14 / 256 = 0 → 0x00 0x00
5 主题名长度低位(LSB) strlen("attributes/push") % 256 → 14 % 256 = 14 → 0x0E 0x0E
6-19 主题名字符串 “attributes/push” 字符转 16 进制:a(0x61)、t(0x74)、t(0x74)、r(0x72)、i(0x69)、b(0x62)、u(0x75)、t(0x74)、e(0x65)、s(0x73)、/(0x2F)、p(0x70)、u(0x75)、s(0x73)、h(0x68) 对应字节依次为 0x61 0x74 0x74 0x72 0x69 0x62 0x75 0x74 0x65 0x73 0x2F 0x70 0x75 0x73 0x68
20 QoS 等级 QoS 0 对应 0x00(二进制 0000 0000 0x00

核心逻辑
用 2 + N + 1 模式存储订阅信息:

 
  • 先存主题名长度(高位 + 低位),
  • 再存主题名字符对应的 16 进制,
  • 最后存 QoS 等级(QoS 0 填 0x00)。

五、代码实现流程(快速梳理)

  1. 初始化数组
    u8 MQTT_Subscribe_Data[256] = "";(预留足够空间存报文)

  2. 固定头

    • 第 0 字节:MQTT_Subscribe_Data[0] = 0x80;(SUBSCRIBE 报文类型)
    • 第 1 字节:需计算 可变头长度(2) + 有效载荷长度(2 + strlen("attributes/push") + 1),动态填充。
  3. 可变头(数据包 ID)

    • 第 2 字节:MQTT_Subscribe_Data[2] = 0x00;(ID 高位,自定义)
    • 第 3 字节:MQTT_Subscribe_Data[3] = 0x08;(ID 低位,自定义)
  4. 有效载荷(订阅主题 + QoS)

    • 计算主题名长度:len = strlen("attributes/push")(结果为 14)
    • 存长度高位:MQTT_Subscribe_Data[4] = len / 256;(即 0x00
    • 存长度低位:MQTT_Subscribe_Data[5] = len % 256;(即 0x0E
    • 存主题名字符:memcpy(&MQTT_Subscribe_Data[6], "attributes/push", len);
    • 存 QoS 等级:MQTT_Subscribe_Data[6 + len] = 0x00;(QoS 0)
  5. 发送报文
    调用 ESP8266_SendBuffer(...) 发送完整数组。

3.7 代码示例

mqtt.h:
#ifndef _MQTT_H
#define _MQTT_H

#include "stdio.h"
#include "string.h"
#include "stdlib.h"

#include "stm32f10x.h"
#include "ESP8266.h"
#include "systick.h"

//替换自己的
#define CLIENT_ID ""
#define USARNAME ""
#define PASSWORD ""
#define KEEP_ALIVE (60)

#define MQTT_SERVER_ADDR "bj-2-mqtt.iot-api.com"
#define MQTT_SERVER_PORT (1883)

#define PUBLISH_TOPIC "attributes"
#define SUBSCRIBE_TOPIC "attributes/push"

void MQTT_Config(void);
u8 MQTT_Connect_ThingsCloud(const char *protocol, const char *mqtt_addr, u16 mqtt_port);

void MQTT_Send_Connect_Package(void);
void MQTT_Send_Publish_Package(const char *topic, const char *data, u32 len);
void MQTT_Send_Subscribe_Package(const char *topic);

void MQTT_Data_Handler(u8 *buffer, u16 length);


#endif

mqtt.c:

#include "mqtt.h"

//固定头字节数
u32 fixed_header_len = 0;
//可变头子结束吧
u32 variable_header_len = 0;
//有效载荷字节数
u32 payload_len = 0;

void MQTT_Config(void){
	// TCP连接云平台
	MQTT_Connect_ThingsCloud("TCP",MQTT_SERVER_ADDR,MQTT_SERVER_PORT);
	SysTick_Delay_ms(1000);  // 延时1秒,等待连接稳定
	
	//开启ESP8266透传模式,数据不解析
	ESP8266_OpenTransparent();
	SysTick_Delay_ms(1000);
	
	//开启数据直发模式,起点是wifi模块,终点直发云服务器
	ESP8266_SendMode();
	SysTick_Delay_ms(1000);
	
	//告知MQTT平台,当前连接的用户项目是哪一个
	MQTT_Send_Connect_Package();
	SysTick_Delay_ms(1000);
	
	//发送订阅包,订阅指定的主题
	MQTT_Send_Subscribe_Package(SUBSCRIBE_TOPIC);
}

/**
 * @brief 通过TCP连接到MQTT云平台
 * @param protocol 协议类型(如"TCP")
 * @param mqtt_addr MQTT服务器地址
 * @param mqtt_port MQTT服务器端口
 * @return 连接结果(1表示成功,0表示失败)
 */
u8 MQTT_Connect_ThingsCloud(const char *protocol,const char *mqtt_addr,u16 mqtt_port){
	// 1.定义存储AT指令的缓冲区
	char cmd_buffer[128] = "";
	
	// 2.格式化AT指令,用于建立TCP连接
	// AT+CIPSTART指令用于ESP8266建立网络连接
    sprintf(cmd_buffer, "AT+CIPSTART=\"%s\",\"%s\",%d\r\n", protocol, mqtt_addr, mqtt_port);
	//发送AT指令
	ESP8266_SendString(cmd_buffer);

	 SysTick_Delay_ms(500);  // 延时500ms,等待指令响应
    
    // 3. 检查ESP8266的响应是否包含"OK",返回检查结果
    return ESP8266_AckCheck("OK");
}

/**
 * @brief 发送MQTT连接包(CONNECT报文)
 * @note 用于客户端与MQTT服务器建立连接,包含客户端标识、用户名、密码等信息
 */
void MQTT_Send_Connect_Package(void){
	/*
     1. 数据准备
     */
    u8 MQTT_Connect_Data[256] = "";  // 存储MQTT连接报文的缓冲区
	
		//计算客户端ID,用户名。密码的长度
		u32 client_id_len = strlen(CLIENT_ID);
		u32 username_len = strlen(USARNAME);    // 用户名长度(注:变量名可能应为USERNAME)
    u32 password_len = strlen(PASSWORD);    // 密码长度
	
		//计算各部分的长度
		fixed_header_len = 2;  //固定头部长度就是2
		variable_header_len = 10;
		//有效载荷长度
		payload_len = 2 + 2 + 2 + client_id_len + username_len + password_len;
	
		// 2. 【处理固定头】
		MQTT_Connect_Data[0] = 0x10;  // 报文类型:CONNECT(0x10),无标志位
		// 剩余长度 = 可变头长度 + 有效载荷长度
		MQTT_Connect_Data[1] = variable_header_len + payload_len;
	
		// 3. 【处理可变头】
    // 3.1 协议名(MQTT)及其长度
    // 协议名长度为2字节(高位在前,低位在后)
		MQTT_Connect_Data[2] = strlen("MQTT") / 256;   //高字节
		MQTT_Connect_Data[3] = strlen("MQTT") % 256;    //低字节
		
		// 拷贝协议名字符串("MQTT")到缓冲区,四个字节
    memcpy(&MQTT_Connect_Data[4], "MQTT", strlen("MQTT"));
		
		MQTT_Connect_Data[8] = 0x04;  // 协议版本:0x04表示MQTT 3.1.1
		
		// 3.2 连接标志(Connect Flags)
    // 0xC2 = 11000010,含义:
    // - 第7位:用户名标志(1,表示有效载荷包含用户名)
    // - 第6位:密码标志(1,表示有效载荷包含密码)
    // - 第1位:清除会话标志(1,表示清除之前的会话)
    MQTT_Connect_Data[9] = 0xC2;
		
		// 3.3 保持连接时间(Keep Alive),单位为秒
    // 这里设置为60秒,分为高低两个字节存储
    MQTT_Connect_Data[10] = KEEP_ALIVE / 256;  // 高位字节
    MQTT_Connect_Data[11] = KEEP_ALIVE % 256;  // 低位字节
		
		// 4. Payload 有效载荷内容
    /* Client_ID - 客户端标识 */
    // 客户端ID长度(2字节)
		MQTT_Connect_Data[12] = client_id_len / 256;  // 高位
    MQTT_Connect_Data[13] = client_id_len % 256;  // 低位
    // 拷贝客户端ID到缓冲区
    memcpy(&MQTT_Connect_Data[14], CLIENT_ID, client_id_len);
		
		 /* Username - 用户名 */
    // 计算用户名在缓冲区中的起始位置
    u16 username_index = 14 + client_id_len;
    // 用户名长度(2字节)
    MQTT_Connect_Data[username_index] = username_len / 256;    // 高位
    MQTT_Connect_Data[username_index + 1] = username_len % 256;  // 低位
    // 拷贝用户名到缓冲区
    memcpy(&MQTT_Connect_Data[username_index + 2], USARNAME, username_len);
		
		/* Password - 密码 */
    // 计算密码在缓冲区中的起始位置
    u16 password_index = username_index + 2 + username_len;
    // 密码长度(2字节)
    MQTT_Connect_Data[password_index] = password_len / 256;    // 高位
    MQTT_Connect_Data[password_index + 1] = password_len % 256;  // 低位
    // 拷贝密码到缓冲区
    memcpy(&MQTT_Connect_Data[password_index + 2], PASSWORD, password_len);
		
		// 发送完整的MQTT连接包(固定头+可变头+有效载荷)
    ESP8266_SendBuffer(MQTT_Connect_Data, fixed_header_len + variable_header_len + payload_len);
}

/**
 * @brief 发送MQTT订阅包(SUBSCRIBE报文)
 * @param topic 要订阅的主题
 * @note 用于客户端订阅指定的主题,以接收该主题的消息
 */
void MQTT_Send_Subscribe_Package(const char *topic){
	// 1.数据准备
	u8 MQTT_Subscribe_Data[128] = "";
	u32 topic_len = strlen(topic);
	
	//计算各部分的长度
	fixed_header_len = 2;
	variable_header_len = 2;
	//有效载荷长度,主题长度 + 主题 + qos0 1个字节  
	payload_len = 2 + topic_len + 1;   
	
	// 1.固定头内容
	MQTT_Subscribe_Data[0] = 0x82;  // 报文类型:SUBSCRIBE(0x80),标志位0x02
	//剩余长度 = 可变头长度 + 有效
	 MQTT_Subscribe_Data[1] = variable_header_len + payload_len;
	
		// 2. 可变头 - 消息ID(用于匹配订阅确认)由于是qos0,这两个字节随便填
    MQTT_Subscribe_Data[2] = 0x00;  // 消息ID高位
    MQTT_Subscribe_Data[3] = 0x08;  // 消息ID低位(此处固定为0x0008)
	
	// 3. 有效载荷
    // 主题长度(2字节)
    MQTT_Subscribe_Data[4] = topic_len / 256;  // 高位
    MQTT_Subscribe_Data[5] = topic_len % 256;  // 低位
    // 拷贝主题到缓冲区
    memcpy(&MQTT_Subscribe_Data[6], topic, topic_len);
    // QoS等级(0x00表示QoS 0,最多一次交付)
    MQTT_Subscribe_Data[6 + topic_len] = 0x00;
		
		
		// 发送完整的订阅包
    ESP8266_SendBuffer(MQTT_Subscribe_Data, fixed_header_len + variable_header_len + payload_len);
}

/**
 * @brief 发送MQTT发布包(PUBLISH报文)
 * @param topic 发布消息的主题
 * @param data 要发布的数据内容
 * @param len 数据内容的长度
 * @note 用于客户端向指定主题发布消息
 */
void MQTT_Send_Publish_Package(const char *topic, const char *data, u32 len)
{
    // 1. 数据准备
    u8 MQTT_Publish_Data[128] = "";  // 存储发布报文的缓冲区
    u32 topic_len = strlen(topic);   // 发布主题的长度
    
    // 计算各部分长度
    fixed_header_len = 2;  // 固定头长度为2字节
    variable_header_len = 2 + topic_len;  // 可变头长度 = 主题长度(2字节) + 主题内容长度
    payload_len = len;  // 有效载荷长度为数据长度
    
    // 2. 固定头内容
    MQTT_Publish_Data[0] = 0x30;  // 报文类型:PUBLISH(0x30),标志位0x00(QoS 0)
    // 剩余长度 = 可变头长度 + 有效载荷长度
    MQTT_Publish_Data[1] = variable_header_len + payload_len;
    
    // 3. 可变头 - 主题相关信息
    // 主题长度(2字节)
    MQTT_Publish_Data[2] = topic_len / 256;  // 高位
    MQTT_Publish_Data[3] = topic_len % 256;  // 低位
    // 拷贝主题到缓冲区
    memcpy(&MQTT_Publish_Data[4], topic, topic_len);
    
    // 4. 有效载荷 - 发布的数据内容
    memcpy(&MQTT_Publish_Data[4 + topic_len], data, len);
    
    // 发送完整的发布包
    ESP8266_SendBuffer(MQTT_Publish_Data, fixed_header_len + variable_header_len + payload_len);
}


网站公告

今日签到

点亮在社区的每一天
去签到