在Arduino上使用MQTT
一、引言
1.1 MQTT 的概念与基本原理
MQTT(Message Queuing Telemetry Transport)即消息队列遥测传输协议,是一种基于发布 / 订阅模式的轻量级消息传输协议。它由 IBM 在 1999 年为监控输油管道而设计,旨在为资源受限的设备和低带宽、高延迟或不可靠的网络提供高效的消息传输解决方案。
MQTT 的工作模式主要基于客户端 - 代理(Broker)架构。在这个架构中,存在多个客户端(如传感器、执行器、应用程序等设备)和一个中心代理(Broker)。客户端之间并不直接进行通信,而是通过与代理进行交互来实现消息的传递。客户端可以向代理发布(Publish)消息到特定的主题(Topic),也可以订阅(Subscribe)感兴趣的主题。当代理接收到某个主题的消息后,会将该消息转发给所有订阅了此主题的客户端。这种发布 / 订阅模式使得系统的耦合度大大降低,各个客户端可以独立地进行开发和部署。
1.2 MQTT 在物联网领域的应用场景和优势
MQTT 的优势主要体现在以下几个方面。首先,它的协议开销非常小,消息头部只有 2 字节,这使得它在低带宽环境下能够高效运行,减少了数据传输的延迟和成本。其次,MQTT 支持三种不同的服务质量(QoS)等级,分别是 QoS 0(最多一次)、QoS 1(至少一次)和 QoS 2(恰好一次),可以根据不同的应用场景选择合适的 QoS 等级,确保消息传输的可靠性。另外,MQTT 具有良好的扩展性和灵活性,新的设备或应用可以方便地加入或退出系统,而不会对其他部分造成影响。
二、MQTT 协议基础
MQTT 协议采用了简单的二进制编码格式,使得它适用于网络带宽较小、延迟较高、网络不稳定的环境下进行通信。同时,MQTT 提供了 QoS(Quality of Service)服务质量保证机制,支持三种不同的 QoS 等级:
- QoS 0:最多发送一次消息,不提供可靠性保证。
- QoS 1:至少发送一次消息,确保消息能到达接收方,但可能会重复。
- QoS 2:恰好发送一次消息,确保消息能到达接收方且只接收一次。
2.1 MQTT 数据包结构如下:
固定头(Fixed header)
,存在于所有MQTT
数据包中,表示数据包类型及数据包的分组类标识;可变头(Variable header)
,存在于部分MQTT
数据包中,数据包类型决定了可变头是否存在及其具体内容;消息体(Payload)
,存在于部分MQTT
数据包中,表示客户端收到的具体内容;
2.1 MQTT 其他概念:
一、订阅(Subscription)
订阅包含主题筛选器(Topic Filter)和最大服务质量(QoS)。订阅会与一个会话(Session)关联。一个会话可以包含多个订阅。每一个会话中的每个订阅都有一个不同的主题筛选器。
二、会话(Session)
每个客户端与服务器建立连接后就是一个会话,客户端和服务器之间有状态交互。会话存在于一个网络之间,也可能在客户端和服务器之间跨越多个连续的网络连接。
三、主题名(Topic Name)
连接到一个应用程序消息的标签,该标签与服务器的订阅相匹配。服务器会将消息发送给订阅所匹配标签的每个客户端。
四、主题筛选器(Topic Filter)
一个对主题名通配符筛选器,在订阅表达式中使用,表示订阅所匹配到的多个主题。
五、负载(Payload)
消息订阅者所具体接收的内容。
详细内容可以查看MQTT协议文档:MQTT Version 5.0
二、准备工作(安装Mosquitto)
3.1 Mosquitto 简介
Mosquitto 是一款广泛使用的、开源的 MQTT 消息代理软件,它由 Roger Light 开发,遵循 Eclipse Public License 1.0 和 EPL - 2.0 许可证。该软件的设计目标是为物联网设备和应用程序提供一个轻量级、可靠且高效的消息传输平台,能让不同设备之间通过 MQTT 协议顺畅地进行消息通信。
这里我们只需要简单的知道,Mosquitto在MQTT系统里充当Broker 的角色即可。
3.2 Mosquitto 安装(windows)
下载 Mosquitto安装包: https://mosquitto.org/download/
下载好安装包后,点击安装:
安装完成后,会自动生成服务"Mosquitto Broker",需要手动点击“启动”服务后,mosquitto才会正常运行。Mosquitto服务默认占用1883端口。
右击最下方任务栏-->任务管理器-->服务:
打开服务:
找到Mosquitto Broker ,然后启动
3.3 Mosquitto 测试:
使用Mosquitto命令行测试:
找到我们安装Mosquitto的目录,默认是在C:\Program Files\mosquitto
编辑目录下的 mosquitto.conf 文件:添加 allow_anonymous true 并保存(修改第533行),添加 “allow_anonymous true” 这一行配置的目的是允许匿名客户端连接到 Mosquitto 服务器。也就是说,客户端在连接服务器时不需要提供用户名和密码等身份验证信息就可以进行连接,这在一些对安全性要求不高或者测试环境中可能会用到。 (配置完后需要重启服务)
在安装目录下打开两个终端,分别执行 mosquitto_sub 和 mosquitto_pub 命令,是为了验证本地 MQTT 代理服务器的基本功能,展示 MQTT 协议中发布 - 订阅模式的工作流程,如下:
在第一个终端使用:mosquitto_sub -t "test/topic" -h localhost
- 命令解释:
mosquitto_sub
是Mosquitto
提供的用于订阅 MQTT 主题的工具;-t "test/topic"
表示要订阅的主题为test/topic
;-h localhost
表明要连接的MQTT
代理服务器位于本地主机。 - 预期现象:执行该命令后,终端会进入等待状态,等待有消息发布到
test/topic
主题上。此时,终端不会有其他输出,直到有匹配主题的消息到来。
在第二个终端使用: mosquitto_pub -t "test/topic" -m "Hello World" -h localhost
- 命令解释:
mosquitto_pub
是用于向 MQTT 主题发布消息的工具;-t "test/topic"
同样指定了消息要发布到的主题为test/topic
;-m "Hello World"
表示要发布的消息内容是"Hello World"
;-h localhost
同样指定了 MQTT 代理服务器的地址为本地主机。 - 预期现象:执行该命令后,第二个终端可能不会有明显的输出(除非出现错误),但第一个终端(订阅者所在终端)会立即显示接收到的消息 "Hello World"。这是因为发布者将消息发送到了 test/topic 主题,而订阅者已经订阅了该主题,所以能够接收到消息。如下:
使用MQTT Explorer测试:
MQTT Explorer 是一款广受欢迎的 MQTT 客户端桌面应用程序,这里我们只需要知道它是客户端就行。
下载软件: https://mqtt-explorer.com/
安装完成后如下:
我们尝试创建一个新连接,连接到本地服务:
这里的Host我们需要查看一下自己的IP地址,在CMD里使用命令:ipconfig
找到自己的IPV4的地址,填到Host栏目里(端口为1883,其他用户名密码随便填)(记得点保存):
这里如果连接失败,可能是配置文件没有配置可匿名远程访问(需要修改)
连接成功如下图:
四、在 ESP32 上实现 MQTT 通信
使用下面的代码测试MQTT功能:(注意需要下载 PubSubClient 库)
#include <WiFi.h> // 用于WLAN功能的库
#include <PubSubClient.h> // 用于MQTT通信的库
// WLAN访问数据
const char *ssid = "TP-Link_E444"; // WLAN网络名称
const char *password = "21482878"; // WLAN密码
// MQTT代理配置
const char *mqtt_broker = "192.168.0.101"; // MQTT代理的本地IP地址(在笔记本上)
const char *topic = "test"; // 用于一般消息的MQTT主题
const char *topic2 = "Button"; // 用于启动按钮的MQTT主题
const int mqtt_port = 1883; // 标准MQTT端口
// 定义启动按钮引脚(GPIO9)
const int bootButton = 9; // 启动按钮连接到GPIO9
bool buttonPressed = false; // 用于存储按钮状态的变量
// 用于WLAN和MQTT通信的对象
WiFiClient espClient;
PubSubClient client(espClient);
void setup() {
Serial.begin(115200); // 启动串行通信(波特率:115200)
// 将GPIO9配置为带有内部上拉电阻的输入
pinMode(bootButton, INPUT_PULLUP);
// 建立与WLAN的连接
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) { // 只要没有连接,就等待
delay(500);
Serial.println("Connecting to WiFi..");
}
Serial.println("Connected to Wi-Fi"); // 报告连接成功
// 建立与MQTT代理的连接
client.setServer(mqtt_broker, mqtt_port);
while (!client.connected()) {
String client_id = "esp32-client-" + String(WiFi.macAddress()); // 创建唯一的客户端ID
Serial.printf("Connecting to MQTT Broker as %s\n", client_id.c_str());
if (client.connect(client_id.c_str())) { // 尝试连接
Serial.println("Connected to MQTT Broker");
} else {
Serial.print("Connection failed: ");
Serial.println(client.state()); // 输出错误代码
delay(2000); // 等待并再次尝试
}
}
// 向MQTT代理发送初始消息
client.publish(topic, "ESP Client verbunden");
}
void loop() {
// 保持MQTT连接
client.loop();
// 读取启动按钮的状态
if (digitalRead(bootButton) == LOW) { // 按钮被按下(由于上拉电阻为LOW)
if (!buttonPressed) { // 确保只发送一次
buttonPressed = true;
Serial.println("Boot-Knopf gedrückt! Sende neue Nachricht...");
// 通过MQTT代理发送消息
client.publish(topic2, "Button gedrückt");
delay(500); // 短暂延迟以消除抖动
}
} else {
buttonPressed = false; // 当按钮释放时重置状态
}
}
其中WIFI 用户名密码和MQTT代理配置的IP地址,需要更换为自己的。(注意ESP32和电脑需要连接到同一个WIFI,并且电脑端最好关闭对应网络的防火墙)
该代码借助 ESP32 连接指定 WiFi 网络与 IP 为 192.168.X.X 的 MQTT 代理,连接成功后向 “test” 主题发送初始连接消息,随后在循环中持续检测连接到 GPIO9 的启动按钮状态,按下时向 “Button” 主题发送按下消息并消抖。
代码实现很简单,借助AI很快能看明白,这里不再赘述。
五、测试与调试
下面实际测试一下这个代码,将代码上传到 ESP32 开发板,并启动 Mosquitto 代理:
EPS32侧串口打印如下:
MQTT Explorer出现对应消息:
按动开发板上的BOOT键,串口打印如下:
这里按了5次,对应的MQTT Explorer侧也收到5条消息:
至此我们完成了在 ESP32-C6 上实现 MQTT 客户端,该客户端与 MQTT Explorer 连接到 Wi-Fi 网络并与 Mosquito Broker 通信的全过程。