MQTT客户端设置
MQTT客户端设置
Section titled “MQTT客户端设置”本节介绍如何使用PubSubClient库在ESP32上设置MQTT客户端。通过本节学习,你将能够:
- 为ESP32安装和配置PubSubClient库
- 将ESP32连接到MQTT Broker
- 实现带Wi-Fi依赖管理的连接重试逻辑
- 配置MQTT服务器地址、端口和认证
- ESP32具有可用的Wi-Fi连接(01-09)
- 正在运行的MQTT Broker(同一网络上的Mosquitto,默认端口1883)
- 已安装PubSubClient库(通过库管理器或lib_deps)
PubSubClient库
Section titled “PubSubClient库”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客户端标识符
Section titled “MQTT客户端标识符”每个MQTT客户端必须有一个唯一的客户端标识符(client ID)。如果两个客户端使用相同ID连接,Broker会断开第一个。惯例是使用设备特定的名称:
const char* clientId = "esp32-sensor-01";// 或动态生成:String clientId = "esp32-" + String(WiFi.macAddress());MQTT服务器配置
Section titled “MQTT服务器配置”服务器配置需要:
- Broker IP/主机名:例如
"192.168.1.100"或"mqtt.local"(mDNS) - 端口:1883(未加密)或8883(TLS)
- 凭证(可选):用户名和密码
第一步:基础MQTT连接
Section titled “第一步:基础MQTT连接”此示例在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 IPconst 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); } }}第三步:带认证的MQTT连接
Section titled “第三步:带认证的MQTT连接”如果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); } }}第四步:检查连接返回码
Section titled “第四步:检查连接返回码”client.state() 函数返回连接状态。常见值:
| 代码 | 常量 | 含义 |
|---|---|---|
| -4 | MQTT_CONNECTION_TIMEOUT | 连接超时 |
| -3 | MQTT_CONNECTION_LOST | 连接丢失 |
| -2 | MQTT_CONNECT_FAILED | 连接失败 |
| -1 | MQTT_DISCONNECTED | 客户端已断开 |
| 0 | MQTT_CONNECTED | 成功连接 |
| 1 | MQTT_CONNECT_BAD_PROTOCOL | 协议版本错误 |
| 2 | MQTT_CONNECT_BAD_CLIENT_ID | 客户端ID被拒绝 |
| 3 | MQTT_CONNECT_UNAVAILABLE | 服务器不可用 |
| 4 | MQTT_CONNECT_BAD_CREDENTIALS | 用户名/密码错误 |
| 5 | MQTT_CONNECT_UNAUTHORIZED | 未授权 |
- ESP32在Wi-Fi连接后连接到MQTT Broker
- 串行监视器显示”MQTT connected”消息
- 从其他客户端订阅
esp32/status显示”online”消息 - 断开ESP32会通过LWT发送”offline”
- 认证MQTT连接成功(如适用)
MQTT连接反复失败
Section titled “MQTT连接反复失败”常见返回码及解决方案:
- rc=4:凭证错误——检查MQTT用户名和密码
- rc=5:未授权——检查Broker ACL设置
- rc=-2:网络不可达——验证Broker IP和Wi-Fi连接
- rc=-4:超时——Broker可能在不同网络或防火墙阻止了1883端口
通用解决方案:
- 验证Broker正在运行:在电脑上执行
mosquitto_sub -h 192.168.1.100 -t "test" - 检查防火墙:ESP32和Broker之间的1883端口必须开放
- 从ESP32 ping Broker(如果可用)
- 检查客户端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(); }}未收到MQTT消息
Section titled “未收到MQTT消息”原因:
client.loop()调用不够频繁- 未订阅正确的主题
- 发布者和订阅者之间的QoS不匹配
解决方案:
- 确保至少每100ms调用一次
client.loop() - 验证订阅主题与发布主题完全匹配
- 尝试发布者和订阅者都使用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秒
- PubSubClient是ESP32的标准MQTT库,通过
setServer()和setCallback()配置 - MQTT连接需要活动的Wi-Fi连接——始终先检查Wi-Fi状态
- 每个客户端需要唯一的ID;重复会导致断开连接
- LWT(遗嘱消息)支持离线检测
client.loop()必须频繁调用以维护连接和处理消息- 连接返回码有助于诊断认证和网络问题