跳转到内容

MQTT客户端设置

本节介绍如何使用PubSubClient库在ESP32上设置MQTT客户端。通过本节学习,你将能够:

  • 为ESP32安装和配置PubSubClient库
  • 将ESP32连接到MQTT Broker
  • 实现带Wi-Fi依赖管理的连接重试逻辑
  • 配置MQTT服务器地址、端口和认证
  • ESP32具有可用的Wi-Fi连接(01-09)
  • 正在运行的MQTT Broker(同一网络上的Mosquitto,默认端口1883)
  • 已安装PubSubClient库(通过库管理器或lib_deps)

Nick O’Leary(Node-RED的创建者)开发的PubSubClient库是Arduino兼容开发板的事实标准MQTT客户端。它处理MQTT协议细节,同时提供简单的API。

关键特性

  • 轻量级(~15 KB闪存,~2 KB RAM)
  • 支持QoS 0和QoS 1
  • 支持MQTT 3.1.1
  • 无内置TLS支持(需要WiFiClientSecure实现TLS——见01-16)
  • 单线程,必须在 loop() 中轮询

核心函数

函数描述
PubSubClient(wifiClient)带Wi-Fi客户端的构造函数
setServer(broker, port)设置MQTT Broker地址和端口
setCallback(callback)注册消息处理函数
connect(clientId)连接到Broker
connect(clientId, user, pass)带认证连接
publish(topic, payload)发布消息
subscribe(topic)订阅主题
loop()维护连接,处理消息
connected()检查MQTT连接状态

每个MQTT客户端必须有一个唯一的客户端标识符(client ID)。如果两个客户端使用相同ID连接,Broker会断开第一个。惯例是使用设备特定的名称:

const char* clientId = "esp32-sensor-01";
// 或动态生成:
String clientId = "esp32-" + String(WiFi.macAddress());

服务器配置需要:

  • Broker IP/主机名:例如 "192.168.1.100""mqtt.local"(mDNS)
  • 端口:1883(未加密)或8883(TLS)
  • 凭证(可选):用户名和密码

此示例在Wi-Fi连接后建立MQTT连接:

#include <WiFi.h>
#include <PubSubClient.h>
// Wi-Fi凭证
const char* ssid = "YourWiFiSSID";
const char* password = "YourWiFiPassword";
// MQTT Broker配置
const char* mqttServer = "192.168.1.100"; // 你的Broker IP
const int mqttPort = 1883;
const char* mqttUser = ""; // 无认证时留空
const char* mqttPassword = "";
const char* clientId = "esp32-client";
WiFiClient wifiClient;
PubSubClient client(wifiClient);
void setup() {
Serial.begin(115200);
delay(1000);
// 连接Wi-Fi
connectToWiFi();
// 配置MQTT
client.setServer(mqttServer, mqttPort);
client.setCallback(mqttCallback);
// 连接到MQTT Broker
connectToMQTT();
}
void loop() {
// 维护MQTT连接(必须定期调用)
if (!client.connected()) {
connectToMQTT();
}
client.loop();
// 你的其他代码在这里
delay(100);
}
void connectToWiFi() {
Serial.print("Connecting to Wi-Fi");
WiFi.begin(ssid, password);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(500);
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nWi-Fi connected!");
Serial.print("IP: ");
Serial.println(WiFi.localIP());
} else {
Serial.println("\nWi-Fi failed! Restarting...");
ESP.restart();
}
}
void connectToMQTT() {
// 循环直到连接成功
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
if (client.connect(clientId, mqttUser, mqttPassword)) {
Serial.println("connected!");
// 连接成功后订阅主题
client.subscribe("esp32/commands");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" retrying in 5 seconds");
delay(5000);
}
}
}
void mqttCallback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived on topic: ");
Serial.println(topic);
String message;
for (unsigned int i = 0; i < length; i++) {
message += (char)payload[i];
}
Serial.print("Message: ");
Serial.println(message);
}

第二步:带遗嘱消息(LWT)的MQTT连接

Section titled “第二步:带遗嘱消息(LWT)的MQTT连接”

LWT是一个功能,当客户端意外断开时发送预定义消息:

// LWT配置
const char* willTopic = "esp32/status";
const char* willMessage = "offline";
int willQos = 1;
bool willRetain = true;
void connectToMQTT() {
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
// 带LWT连接:(clientId, user, pass, willTopic, willQos, willRetain, willMessage)
if (client.connect(clientId, mqttUser, mqttPassword,
willTopic, willQos, willRetain, willMessage)) {
Serial.println("connected!");
// 发布在线状态
client.publish("esp32/status", "online", true);
// 订阅
client.subscribe("esp32/commands");
} else {
Serial.print("failed, rc=");
Serial.println(client.state());
delay(5000);
}
}
}

如果Broker需要认证,传递用户名和密码:

const char* mqttUser = "iot-device";
const char* mqttPassword = "secure-password-123";
void connectToMQTT() {
while (!client.connected()) {
if (client.connect(clientId, mqttUser, mqttPassword)) {
Serial.println("MQTT connected with authentication");
client.subscribe("esp32/commands");
} else {
Serial.print("MQTT auth failed, state: ");
Serial.println(client.state());
delay(5000);
}
}
}

client.state() 函数返回连接状态。常见值:

代码常量含义
-4MQTT_CONNECTION_TIMEOUT连接超时
-3MQTT_CONNECTION_LOST连接丢失
-2MQTT_CONNECT_FAILED连接失败
-1MQTT_DISCONNECTED客户端已断开
0MQTT_CONNECTED成功连接
1MQTT_CONNECT_BAD_PROTOCOL协议版本错误
2MQTT_CONNECT_BAD_CLIENT_ID客户端ID被拒绝
3MQTT_CONNECT_UNAVAILABLE服务器不可用
4MQTT_CONNECT_BAD_CREDENTIALS用户名/密码错误
5MQTT_CONNECT_UNAUTHORIZED未授权
  • ESP32在Wi-Fi连接后连接到MQTT Broker
  • 串行监视器显示”MQTT connected”消息
  • 从其他客户端订阅 esp32/status 显示”online”消息
  • 断开ESP32会通过LWT发送”offline”
  • 认证MQTT连接成功(如适用)

常见返回码及解决方案

  • rc=4:凭证错误——检查MQTT用户名和密码
  • rc=5:未授权——检查Broker ACL设置
  • rc=-2:网络不可达——验证Broker IP和Wi-Fi连接
  • rc=-4:超时——Broker可能在不同网络或防火墙阻止了1883端口

通用解决方案

  1. 验证Broker正在运行:在电脑上执行 mosquitto_sub -h 192.168.1.100 -t "test"
  2. 检查防火墙:ESP32和Broker之间的1883端口必须开放
  3. 从ESP32 ping Broker(如果可用)
  4. 检查客户端ID唯一性:没有其他设备使用相同的 clientId

MQTT中出现”WiFiClient not connected”错误

Section titled “MQTT中出现”WiFiClient not connected”错误”

原因:在尝试MQTT重连之前Wi-Fi已断开。

解决方案:在进行MQTT操作前始终验证Wi-Fi状态:

void loop() {
if (WiFi.status() == WL_CONNECTED) {
if (!client.connected()) {
connectToMQTT();
}
client.loop();
} else {
// 先重连Wi-Fi
connectToWiFi();
}
}

原因

  • client.loop() 调用不够频繁
  • 未订阅正确的主题
  • 发布者和订阅者之间的QoS不匹配

解决方案

  1. 确保至少每100ms调用一次 client.loop()
  2. 验证订阅主题与发布主题完全匹配
  3. 尝试发布者和订阅者都使用QoS 0
  • 结合Wi-Fi和MQTT重连:在尝试MQTT连接前始终检查Wi-Fi状态
  • 使用唯一的客户端ID:在客户端ID后追加MAC地址以避免冲突
  • 启用LWT:遗嘱消息帮助Broker(和Node-RED)检测离线设备
  • 保持 client.loop() 频繁调用:在每次 loop() 迭代中调用,而不是在长时间延迟内部
  • 记录连接状态:使用 client.state() 有助于诊断连接问题
  • 设置较短的保活间隔:默认15秒;对于不稳定网络,通过 client.setKeepAlive(10) 减少到10秒
  1. PubSubClient是ESP32的标准MQTT库,通过 setServer()setCallback() 配置
  2. MQTT连接需要活动的Wi-Fi连接——始终先检查Wi-Fi状态
  3. 每个客户端需要唯一的ID;重复会导致断开连接
  4. LWT(遗嘱消息)支持离线检测
  5. client.loop() 必须频繁调用以维护连接和处理消息
  6. 连接返回码有助于诊断认证和网络问题