MQTT 物联网通信:100 个传感器的数据怎么传到云端?

MQTT 物联网通信:100 个传感器的数据怎么传到云端?

上一篇 ESP32 固件写好了,但数据的另一端------云端消息中间件怎么搭?这篇从零部署 EMQX,设计 topic 规范和 JSON 格式,加上设备上下线监控和断网重连,一套生产可用的物联网通信就出来了。


为什么是 MQTT + EMQX?

物联网通信有几个硬需求:

  • 低带宽:农田里 4G 信号可能只有两格,包要小
  • 低功耗:设备电池供电,不能维持长连接轮询
  • 双向:不只是上报数据,还要下发指令(开阀、关泵)
  • 大规模:大棚多了,上百个设备要同时在线

MQTT 的发布/订阅模型天生契合:设备 publish 数据到 broker,后端 subscribe 接收存储,控制指令反向 subscribe + publish。EMQX 5.x 开源版免费,单节点能扛 10 万连接,对我们来说完全够用。


EMQX 部署------一条 Docker 命令

bash 复制代码
docker run -d \
  --name emqx \
  --restart unless-stopped \
  -p 1883:1883 \      # MQTT 端口
  -p 8083:8083 \      # WebSocket 端口(小程序用)
  -p 18083:18083 \    # Dashboard 管理界面
  emqx/emqx:5.7.0

访问 http://你的服务器IP:18083,默认账号 admin / public。进去后第一件事:改密码 ,并在「访问控制 → 认证」里创建一个 MQTT 用户(比如 farm_devices / 一个复杂密码),所有 ESP32 用这个用户名密码连接。


Topic 设计------房子的门牌号

MQTT 没有数据库,topic 就是唯一标识。好的 topic 设计让你查问题一目了然,坏的 topic 设计让你半年后对着 Dashboard 一脸懵。

规范

复制代码
{业务域}/{场地}/{设备类型}/{设备ID}/{消息类型}

拆解:

层级 示例值 含义
业务域 farm 农场业务,区别于以后可能的 home factory
场地 greenhouse_01 大棚编号
设备类型 sensor / actuator / camera 设备角色
设备ID esp32_a1b2c3 唯一设备标识(可以用 MAC 地址后 6 位)
消息类型 data / status / cmd / alarm 消息用途

完整示例

复制代码
farm/greenhouse_01/sensor/esp32_a1b2c3/data     ← 设备上报数据(JSON)
farm/greenhouse_01/sensor/esp32_a1b2c3/status    ← 设备状态(在线/电池/信号)
farm/greenhouse_01/actuator/pump_01/cmd          ← 控制指令(平台→设备)
farm/greenhouse_01/sensor/+/data                 ← 通配符:该棚所有传感器数据
farm/+/+/+/alarm                                 ← 通配符:整个农场所有告警

JSON 数据格式------统一方言

每个 sensor 上报的 data JSON 必须统一字段名,不然后端解析要写一堆 if-else。

传感器数据上报(设备 → 平台):

json 复制代码
{
  "dev": "esp32_a1b2c3",
  "ts": 1718000000,
  "ver": "1.0.0",
  "data": {
    "air_temp": 26.5,
    "air_humidity": 68.2,
    "soil_temp": 22.1,
    "soil_moisture": 35.0,
    "light": 42000
  },
  "battery": 3.82,
  "rssi": -65
}

字段说明:

  • dev:设备 ID,必须与 topic 中一致,便于日志追踪
  • ts:Unix 时间戳(秒),ESP32 从 NTP 同步
  • ver:固件版本,OTA 升级时判断
  • data:传感器读数,key 固定
  • battery:电池电压(V),< 3.3V 告警
  • rssi:WiFi 信号强度

控制指令下发(平台 → 设备):

json 复制代码
{
  "cmd": "irrigate",
  "seq": 1823,
  "params": {
    "duration": 300,
    "valve": 1
  }
}

seq 是命令序列号,递增。设备收到后回一个 ACK(带相同 seq),平台就知道这条命令确实送达了。没有 ACK 机制的命令下发就是丢硬币------丢了也不知道。

ACK 回复(设备 → 平台):

json 复制代码
{
  "type": "ack",
  "seq": 1823,
  "result": "ok",
  "msg": ""
}

设备订阅 farm/greenhouse_01/actuator/+/cmd,平台 publish 到具体设备的 cmd topic。设备收到后执行,执行完 publish ACK 到 .../ack


设备上下线管理------谁在线、谁掉线了

MQTT 有个开箱即用的好东西:遗嘱消息(Last Will)

设备连接时声明一个遗嘱 topic,一旦异常断线(心跳超时),broker 自动帮你发布一条遗嘱消息。

ESP32 端(连接时设置):

cpp 复制代码
mqtt.connect(
    MQTT_CLIENT_ID,
    MQTT_USER, MQTT_PASS,
    "farm/greenhouse_01/sensor/esp32_a1b2c3/status",  // 遗嘱 topic
    1,                // QoS
    true,             // retain
    "{\"online\":false,\"ts\":1718000000}"   // 遗嘱 payload
);

设备上线时,自己 publish 一条 {"online":true},并设置 retain=true(新订阅者也能立刻拿到最新状态)。

后端监听(Spring Boot 侧):

后端订阅 farm/+/+/+/status,收到消息后更新设备在线状态到 MySQL,触发告警逻辑:

java 复制代码
@MqttListener(topic = "farm/+/+/+/status")
public void onDeviceStatus(String topic, String payload) {
    DeviceStatus status = JSON.parseObject(payload, DeviceStatus.class);
    deviceService.updateOnlineStatus(status.getDev(), status.isOnline());
    if (!status.isOnline()) {
        alarmService.trigger("设备离线: " + status.getDev());
    }
}

断网重连------农村的真实日常

农村 WiFi 断网三件套:下雨、打雷、光猫被拔(要插电饭锅)。ESP32 的断网重连逻辑必须健壮。

cpp 复制代码
// 带指数退避的重连
void connectMQTT() {
  int retry = 0;
  int delayMs = 1000;
  
  while (!mqtt.connected()) {
    Serial.printf("MQTT 连接中 (第%d次)...", retry + 1);
    
    if (mqtt.connect(MQTT_CLIENT_ID, MQTT_USER, MQTT_PASS,
                     statusTopic, 1, true, willMsg)) {
      Serial.println("成功");
      mqtt.publish(statusTopic, "{\"online\":true}", true);
      mqtt.subscribe(cmdTopic);  // 重新订阅控制指令
      return;
    }
    
    Serial.printf("失败, rc=%d\n", mqtt.state());
    retry++;
    
    if (retry <= 5) {
      delayMs = 1000 * (1 << retry);  // 1s, 2s, 4s, 8s, 16s
    } else {
      delayMs = 60000;  // 5 次后每 60s 重试
    }
    delay(delayMs);
  }
}

加上本地缓存------MQTT 连不上时把数据存到 SPIFFS 文件,网络恢复后补传:

cpp 复制代码
void loop() {
  SensorData data = collectAll();
  
  if (mqtt.connected()) {
    // 先补传本地缓存
    flushLocalCache();
    // 再发当前数据
    publishData(data);
  } else {
    // 存到本地文件
    appendToLocalCache(data);
  }
  
  delay(300000);
}

flushLocalCache()appendToLocalCache() 用 ESP32 的 SPIFFS 或 LittleFS 实现,逻辑不复杂:每条数据一行 JSON,补传时逐行读取 publish,发完清空文件。


带宽和流量估算

一条 data JSON 大约 120 字节,MQTT 协议头约 10 字节,TCP/IP 头约 40 字节,总计约 170 字节/条。5 分钟一条,一天 288 条:约 48KB/天,1.4MB/月。 加上心跳 + ACK,一个设备一个月不超过 3MB。100 个设备也就 300MB/月------即使插个 4G 流量卡,10 块钱的物联网套餐都够用。


快速验证

装好 EMQX 后,开一个 MQTT 客户端工具(MQTTX 好用,免费),连上 broker,订阅 farm/#。然后用 ESP32 烧录上篇代码,你应该立刻在工具里看到数据涌入。

复制代码
farm/greenhouse_01/sensor/esp32_a1b2c3/data  → {"dev":"esp32...","ts":17180...}
farm/greenhouse_01/sensor/esp32_a1b2c3/status → {"online":true}

如果看不到,排查顺序:ESP32 WiFi 连上了吗 → MQTT 密码对了吗 → 防火墙 1883 端口开了吗 → topic 拼写有误吗。


下一篇:《计算机视觉在农业的应用:作物识别 + 病虫害检测实战》------用 YOLOv8 训练一个能认出 10 种蔬菜 + 20 种病害的模型,部署到 RK3588 上。