1. MQTT 协议核心原理

MQTT(Message Queuing Telemetry Transport)是一种基于发布/订阅模式的轻量级物联网通信协议,专为低带宽、高延迟或不稳定的网络环境设计。其核心组件包括:

  • Broker(代理服务器):负责接收所有客户端消息,并路由转发给订阅者。
  • Topic(主题):消息的标识符,采用层级结构(如 device/001/temp),客户端通过订阅 Topic 接收感兴趣的数据。
  • QoS(服务质量):定义消息传递的可靠性级别,0(最多一次)、1(至少一次)、2(恰好一次)。
  • Last Will(遗嘱):客户端异常断开时,Broker 自动发布预设消息,用于通知其他设备。

MQTT 的优势在于:极低开销(固定头仅 2 字节)、支持异步通信、天然适配传感器数据上报和远程控制场景。ESP32 作为 Wi-Fi/蓝牙双模 MCU,非常适合作为 MQTT 客户端节点。

2. 开发环境准备

2.1 硬件与软件

  • 硬件:ESP32 开发板(如 NodeMCU-32S)、USB 数据线、传感器(可选)。
  • 软件:Arduino IDE(推荐 2.x)或 ESP-IDF(命令行)。本文以 Arduino IDE 为例,因其生态丰富、上手快。

2.2 安装 ESP32 开发板支持

在 Arduino IDE 中,打开“文件”->“首选项”,在“附加开发板管理器网址”中添加:

https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json

然后通过“工具”->“开发板”->“开发板管理器”搜索“ESP32”并安装。

2.3 安装 PubSubClient 库

PubSubClient 是 Arduino 生态中最常用的 MQTT 客户端库,通过“项目”->“加载库”->“管理库”搜索“PubSubClient”并安装。

3. 自建 MQTT Broker(Mosquitto)

3.1 安装与启动

在 Ubuntu 服务器上执行:

sudo apt update
sudo apt install mosquitto mosquitto-clients
sudo systemctl start mosquitto
sudo systemctl enable mosquitto

默认监听 1883 端口(非加密)。为安全起见,建议配置用户名密码:

sudo mosquitto_passwd -c /etc/mosquitto/passwd esp32user
# 输入两次密码

编辑 /etc/mosquitto/mosquitto.conf,添加:

allow_anonymous false
password_file /etc/mosquitto/passwd

重启服务生效。

3.2 测试 Broker

使用命令行客户端测试:

mosquitto_sub -h localhost -t test -u esp32user -P yourpassword &
mosquitto_pub -h localhost -t test -m "hello" -u esp32user -P yourpassword

若收到消息,则 Broker 正常。

4. 阿里云 IoT 平台配置

4.1 创建产品和设备

  1. 登录阿里云 IoT 平台,创建产品(如“ESP32_Demo”),选择“直连设备”。
  2. 在产品下添加设备,获取三元组(ProductKey、DeviceName、DeviceSecret)。
  3. 在“设备”->“Topic 列表”中,默认有 /productKey/deviceName/user/update/productKey/deviceName/user/get 用于上报和下发。

4.2 计算 MQTT 连接参数

阿里云 MQTT 连接地址为 ${productKey}.iot-as-mqtt.cn-shanghai.aliyuncs.com(以华东2为例),端口 1883。用户名和密码由三元组动态生成:

  • ClientID${deviceName}|securemode=3,signmethod=hmacsha1,timestamp=789|
  • 用户名${deviceName}
  • 密码:使用 HMAC-SHA1 对 clientId${deviceName}deviceName${deviceName}productKey${productKey}timestamp789 进行签名,密钥为 DeviceSecret。

为简化,可在阿里云控制台“设备详情”页直接下载 MQTT 连接参数示例。

5. ESP32 代码实现

以下代码兼容自建 Broker 和阿里云,通过宏定义切换。

#include <WiFi.h>
#include <PubSubClient.h>

// WiFi 配置
const char* ssid = "your_ssid";
const char* password = "your_password";

// MQTT 配置
// 自建 Broker 示例
const char* mqtt_server = "192.168.1.100";
const int mqtt_port = 1883;
const char* mqtt_user = "esp32user";
const char* mqtt_pass = "yourpassword";

// 阿里云配置(注释掉自建,启用以下)
// const char* mqtt_server = "yourProductKey.iot-as-mqtt.cn-shanghai.aliyuncs.com";
// const int mqtt_port = 1883;
// const char* mqtt_user = "yourDeviceName";
// const char* mqtt_pass = "yourHmacSha1Password";
// const char* clientId = "yourDeviceName|securemode=3,signmethod=hmacsha1,timestamp=789|";

WiFiClient espClient;
PubSubClient client(espClient);

// 传感器数据模拟
float readTemperature() {
  return random(20, 30) + random(0, 10) / 10.0;
}

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();
  // 处理下行命令,如控制 LED
}

void reconnect() {
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // 自建 Broker 使用 client.connect("ESP32Client", mqtt_user, mqtt_pass)
    // 阿里云需指定 clientId
    if (client.connect("ESP32Client", mqtt_user, mqtt_pass)) {
      Serial.println("connected");
      client.subscribe("device/001/control"); // 订阅控制主题
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5s");
      delay(5000);
    }
  }
}

void setup() {
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi connected");
  
  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);
}

void loop() {
  if (!client.connected()) {
    reconnect();
  }
  client.loop();

  // 每 5 秒上报一次温度
  static unsigned long lastMsg = 0;
  if (millis() - lastMsg > 5000) {
    lastMsg = millis();
    float temp = readTemperature();
    char msg[50];
    snprintf(msg, 50, "{\"temp\":%.1f}", temp);
    // 自建 Broker 主题:device/001/data;阿里云:/productKey/deviceName/user/update
    client.publish("device/001/data", msg);
    Serial.print("Publish: ");
    Serial.println(msg);
  }
}

代码说明

  • 使用 WiFiClient 建立 TCP 连接,PubSubClient 负责 MQTT 协议交互。
  • reconnect() 函数实现断线重连,并订阅控制主题。
  • 每 5 秒发布一次 JSON 格式的温度数据,实际项目可替换为真实传感器读取。

6. 测试与验证

  • 自建 Broker:在服务器上运行 mosquitto_sub -t device/001/data -u esp32user -P yourpassword,观察 ESP32 上报的数据。
  • 阿里云:在控制台“日志服务”中查看设备上行消息,或使用“在线调试”功能发送下行指令。

7. 注意事项与优化建议

  • 网络稳定性:ESP32 的 Wi-Fi 连接可能因信号弱而断开,建议在 loop() 中增加 Wi-Fi 状态检测,断线时自动重连。
  • QoS 选择:对于传感器数据上报,QoS 0 足够;对于控制指令,建议使用 QoS 1 确保送达。
  • 安全加固:生产环境应启用 TLS/SSL 加密(端口 8883),并定期更换密码。
  • 资源管理:避免在回调函数中执行耗时操作,否则会阻塞 MQTT 消息处理。
  • 主题设计:采用层级化命名(如 project/deviceID/sensor),便于权限管理和数据路由。
  • 调试技巧:使用 client.state() 返回码判断连接失败原因,常见错误码:-2 网络失败、-4 连接超时、-5 连接拒绝。

8. 总结

本文详细介绍了 ESP32 通过 MQTT 协议接入自建 Broker 和阿里云 IoT 平台的完整流程。从协议原理到代码实现,开发者可以快速搭建起稳定的数据上云通道。实际项目中,建议结合传感器驱动、数据缓存和 OTA 升级等功能,构建完整的物联网节点。MQTT 的灵活性和 ESP32 的高性价比,使其成为物联网原型开发和产品落地的理想组合。