MQTT 协议实战:用 ESP32 搭建实时物联网监控系统

为什么 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 固件。先安装必要的库:

  1. 打开 Arduino IDE
  2. 菜单 → 工具 → 管理库
  3. 搜索并安装 PubSubClient(by Nick O’Leary)
  4. 搜索并安装 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
  1. mqtt in 节点

    • Server:添加你的 Mosquitto 服务器地址
    • Topic:sensor/#(# 是通配符,匹配所有子主题)
  2. 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 做更专业的数据可视化

希望这篇博客文章对您有所帮助!