为什么 MQTT 是物联网的标配?
做 IoT 项目的朋友大概率都听过 MQTT。但你可能也纠结过:HTTP 不是也能传数据吗?WebSocket 也行啊,为什么要专门学一个不太熟悉的协议?
一句话回答:MQTT 是专门为资源受限设备和不可靠网络设计的。
HTTP 每次请求要带上完整的 Header,一条 GET 请求可能就有几百字节。而 MQTT 的最小报文只有 2 字节。在 NB-IoT 或者 2G 网络下,流量就是钱,MQTT 能帮你省下一大笔。
更重要的是 MQTT 的发布/订阅(Pub/Sub)模型。一个传感器发布数据,多个客户端可以同时接收,不需要轮询,不需要长连接。这种解耦设计让系统扩展起来非常轻松。
这篇文章,我会带你从零搭建一套完整的 MQTT 物联网监控系统:用 ESP32 采集温湿度数据,通过 MQTT 协议发送到 Mosquitto 服务器,最后用 Node-RED 做可视化看板。全程实操,代码可以直接跑。
硬件清单
| 硬件 | 型号 | 单价 | 数量 | 备注 |
|---|---|---|---|---|
| 主控板 | ESP32 DevKit V1 | ¥22 | 1 | 带 WiFi/BLE |
| 温湿度传感器 | DHT22 (AM2302) | ¥12 | 1 | 精度优于 DHT11 |
| 面包板 | 830 孔 | ¥8 | 1 | 原型搭建 |
| 杜邦线 | 公对公 | ¥5 | 1 包 | 接线用 |
| USB 数据线 | Micro USB | ¥5 | 1 | 供电和烧录 |
| 合计 | ¥52 |
购买建议:
- DHT22 买带 PCB 板的那种,三根引脚直接插面包板,比裸模块好用
- ESP32 选 ESP32-WROOM-32D 芯片的,兼容性最好
- 如果预算紧张,DHT11 也能用(¥4),但精度差一些(±2°C vs ±0.5°C)
硬件接线
DHT22 和 ESP32 的接线非常简单,只需要三根线:
| DHT22 引脚 | ESP32 引脚 | 说明 |
|---|---|---|
| VCC | 3V3 | 供电 3.3V |
| GND | GND | 接地 |
| DATA | GPIO4 | 数据引脚 |
ESP32 开发板 DHT22 传感器
┌─────────────┐ ┌──────────┐
│ 3V3 ○────┼────┤ VCC │
│ GND ○────┼────┤ GND │
│ GPIO4 ○───┼────┤ DATA │
└─────────────┘ └──────────┘
注意事项:
- 如果你的 DHT22 是裸模块(没有 PCB 板),DATA 和 VCC 之间需要接一个 4.7kΩ-10kΩ 的上拉电阻
- 带 PCB 板的 DHT22 模块通常已经集成了上拉电阻,直接插就行
- 供电必须用 3.3V,接 5V 会损坏传感器
服务器端部署:Mosquitto
先部署 MQTT Broker。我们用 Eclipse Mosquitto,轻量、稳定、开源。
安装 Mosquitto
# Ubuntu/Debian
sudo apt update
sudo apt install -y mosquitto mosquitto-clients
# CentOS/RHEL
sudo dnf install -y mosquitto mosquitto-clients
# macOS
brew install mosquitto
配置 Mosquitto
默认配置下 Mosquitto 只允许本地访问。如果要让 ESP32 从局域网连接,需要修改配置:
sudo nano /etc/mosquitto/mosquitto.conf
添加以下内容:
# 监听默认端口 1883
listener 1883
# 允许匿名连接(测试环境,生产环境建议设置用户名密码)
allow_anonymous true
# 日志级别
log_type all
重启服务:
sudo systemctl restart mosquitto
sudo systemctl enable mosquitto
验证服务器
用命令行工具测试一下:
# 终端 1:订阅主题
mosquitto_sub -h localhost -t "sensor/temperature" -v
# 终端 2:发布消息
mosquitto_pub -h localhost -t "sensor/temperature" -m "25.6"
# 终端 1 应该会收到:sensor/temperature 25.6
如果能看到消息,说明服务器部署成功。
生产环境安全配置(可选)
如果服务器暴露在公网,务必设置认证:
# 设置密码文件
sudo mosquitto_passwd -c /etc/mosquitto/passwd iot_user
# 修改 mosquitto.conf
sudo nano /etc/mosquitto/mosquitto.conf
listener 1883
allow_anonymous false
password_file /etc/mosquitto/passwd
ESP32 端代码
用 Arduino IDE 编写 ESP32 固件。先安装必要的库:
- 打开 Arduino IDE
- 菜单 → 工具 → 管理库
- 搜索并安装 PubSubClient(by Nick O’Leary)
- 搜索并安装 DHT sensor library(by Adafruit)
完整代码
#include
#include
#include
// WiFi 配置
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
// MQTT 配置
const char* mqtt_server = "192.168.1.100"; // 改成你的 Mosquitto 服务器 IP
const int mqtt_port = 1883;
const char* mqtt_user = "iot_user"; // 匿名模式可留空
const char* mqtt_password = "your_password"; // 匿名模式可留空
// DHT22 配置
#define DHTPIN 4
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
// MQTT 客户端
WiFiClient espClient;
PubSubClient client(espClient);
// 发布间隔(毫秒)
const unsigned long PUBLISH_INTERVAL = 5000;
unsigned long lastPublish = 0;
void setup() {
Serial.begin(115200);
dht.begin();
// 连接 WiFi
Serial.print("Connecting to WiFi");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
// 连接 MQTT
client.setServer(mqtt_server, mqtt_port);
connectMQTT();
}
void connectMQTT() {
while (!client.connected()) {
Serial.print("Connecting to MQTT...");
String clientId = "ESP32-Sensor-" + String(random(0xffff), HEX);
if (client.connect(clientId.c_str(), mqtt_user, mqtt_password)) {
Serial.println("connected");
// 订阅控制主题(可选,用于远程指令)
client.subscribe("device/esp32/cmd");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" retrying in 5s");
delay(5000);
}
}
}
void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Received [");
Serial.print(topic);
Serial.print("]: ");
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
}
void loop() {
// 保持 MQTT 连接
if (!client.connected()) {
connectMQTT();
}
client.loop();
// 定时采集并发布数据
unsigned long now = millis();
if (now - lastPublish >= PUBLISH_INTERVAL) {
lastPublish = now;
float temperature = dht.readTemperature();
float humidity = dht.readHumidity();
if (isnan(temperature) || isnan(humidity)) {
Serial.println("读取传感器失败!");
return;
}
// 发布到 MQTT 主题
char tempStr[8];
char humStr[8];
dtostrf(temperature, 4, 1, tempStr);
dtostrf(humidity, 4, 1, humStr);
client.publish("sensor/temperature", tempStr);
client.publish("sensor/humidity", humStr);
Serial.printf("温度: %.1f°C, 湿度: %.1f%%\n", temperature, humidity);
}
}
代码要点说明
主题设计:
sensor/temperature— 温度数据sensor/humidity— 湿度数据device/esp32/cmd— 设备控制指令(订阅)
这种层级命名方式很常见,用 / 分隔主题层级。你也可以用设备 ID 做前缀,比如 home/livingroom/temp,方便扩展多个设备。
连接保持:
client.loop() 必须在 loop 里频繁调用,它负责处理 MQTT 的 keep-alive 心跳和接收消息。如果不调用,服务器会认为客户端掉线。
QoS 级别:
默认 QoS 0(发出去就不管了)。如果数据很重要,可以改为 QoS 1:
client.publish("sensor/temperature", tempStr, false); // QoS 1
可视化:Node-RED 看板
数据发到 MQTT 了,接下来用 Node-RED 做一个实时监控看板。
安装 Node-RED
# 安装 Node.js(如果没有)
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
# 安装 Node-RED
sudo npm install -g --unsafe-perm node-red
# 启动
node-red &
配置流程
在浏览器打开 http://服务器IP:1880,拖拽以下节点连线:
mqtt in → function → debug
↓
ui gauge
-
mqtt in 节点:
- Server:添加你的 Mosquitto 服务器地址
- Topic:
sensor/#(# 是通配符,匹配所有子主题)
-
function 节点(解析数据):
// 解析 MQTT 消息 let topic = msg.topic; let value = parseFloat(msg.payload);
if (topic === "sensor/temperature") {
msg.title = "温度";
msg.value = value;
msg.unit = "°C";
} else if (topic === "sensor/humidity") {
msg.title = "湿度";
msg.value = value;
msg.unit = "%";
}
return msg;
3. **ui gauge 节点**:
- 温度:范围 -10 到 50,颜色绿→黄→红
- 湿度:范围 0 到 100,颜色蓝→绿→黄
4. **ui chart 节点**(可选):
- 添加折线图,记录历史数据趋势
点击 Deploy 后,打开 `http://服务器IP:1880/ui` 就能看到实时数据了。
## 进阶:多设备组网
一台 ESP32 采集数据只是开始。实际项目中你可能有多个传感器节点,MQTT 的优势这时候就体现出来了。
### 设备命名规范
建议用 `位置/设备类型/数据类型` 的格式:
home/livingroom/temp → 客厅温度
home/livingroom/humidity → 客厅湿度
home/bedroom/temp → 卧室温度
office/workshop/co2 → 车间 CO2 浓度
### 用 LWT 检测设备掉线
Last Will and Testament(遗嘱消息)是 MQTT 的一个实用功能。设备意外断线时,Broker 会自动发布一条遗嘱消息:
```cpp
// 在 connect 之前设置遗嘱
client.setWill("device/esp32/status", "offline");
client.connect(clientId.c_str(), mqtt_user, mqtt_password);
// 连接成功后发布上线消息
client.publish("device/esp32/status", "online", true); // retained = true
这样你在 Node-RED 里订阅 device/+/status 就能实时知道哪些设备在线。
常见问题排查
1. ESP32 连不上 WiFi
症状: 串口一直打印 Connecting to WiFi...
排查:
- 检查 SSID 和密码是否正确(注意大小写)
- 确认 ESP32 和路由器距离不要太远(初期调试建议 1 米内)
- 有些路由器开启了 5GHz only,ESP32 只支持 2.4GHz
2. MQTT 连接失败,返回 rc=-2
症状: failed, rc=-2
原因: 无法连接到 Broker 服务器
排查:
- 确认 Mosquitto 服务器 IP 地址正确
- 检查防火墙是否放行了 1883 端口:
sudo ufw allow 1883 - 用
mosquitto_sub在同一网络测试服务器是否正常 - 如果服务器在云服务器上,检查安全组规则
3. 传感器读数为 NaN
症状: 串口打印 读取传感器失败!
排查:
- 检查接线是否松动
- DATA 引脚是否接对(默认 GPIO4)
- 裸模块 DHT22 需要上拉电阻,带 PCB 板的不用
- DHT22 读取间隔不能小于 2 秒,太频繁会失败
4. 数据发布成功但 Node-RED 收不到
排查:
- 确认 Node-RED 的 mqtt in 节点 topic 配置正确
- 用
mosquitto_sub -h 服务器IP -t "sensor/#" -v确认数据确实在发 - 检查 Node-RED 和 Mosquitto 是否在同一网络
5. ESP32 运行一段时间后断线
原因: WiFi 或 MQTT 连接断开后没有自动重连
解决: 代码里已经加了重连逻辑,但如果问题持续,可以:
- 增加看门狗定时器
- 检查电源是否稳定(USB 供电不足会导致 WiFi 模块重启)
- 用外部 5V/1A 电源代替 USB 供电
总结
MQTT 协议的核心就三件事:连接、发布、订阅。但正是这种简单的设计,让它成为了物联网领域最主流的通信协议。
今天我们搭建的系统虽然只有温湿度传感器,但架构已经可以扩展到几十上百个设备。只需要给每个设备分配一个唯一的 Client ID 和主题前缀,数据就能有条不紊地汇聚到服务器。
下一步你可以尝试:
- 添加更多传感器类型(CO2、PM2.5、光照)
- 用 ESP32 的 Deep Sleep 模式实现电池供电(续航几个月)
- 对接 Home Assistant 做智能家居联动
- 用 InfluxDB + Grafana 替代 Node-RED 做更专业的数据可视化
希望这篇博客文章对您有所帮助!