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

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¥221带 WiFi/BLE
温湿度传感器DHT22 (AM2302)¥121精度优于 DHT11
面包板830 孔¥81原型搭建
杜邦线公对公¥51 包接线用
USB 数据线Micro USB¥51供电和烧录
合计¥52

购买建议:

  • DHT22 买带 PCB 板的那种,三根引脚直接插面包板,比裸模块好用

  • ESP32 选 ESP32-WROOM-32D 芯片的,兼容性最好

  • 如果预算紧张,DHT11 也能用(¥4),但精度差一些(±2°C vs ±0.5°C)

硬件接线

DHT22 和 ESP32 的接线非常简单,只需要三根线:

DHT22 引脚ESP32 引脚说明
VCC3V3供电 3.3V
GNDGND接地
DATAGPIO4数据引脚
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. $1

  2. $1

  3. $1

  4. $1

完整代码

#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 = 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. $1

  2. $1

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 做更专业的数据可视化

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