Lập trình ESP32 – Bài 19: Kết nối MQTT, Publish dữ liệu cảm biến & Subscribe điều khiển thiết bị

Hướng dẫn dùng MQTT với ESP32: publish dữ liệu cảm biến (DHT11) lên topic và subscribe lệnh điều khiển LED từ server. Có ví dụ mã đầy đủ, cấu trúc topic, JSON payload, và cách tự reconnect.

Mục lục

  1. Giới thiệu
  2. MQTT hoạt động thế nào?
  3. Phần cứng cần chuẩn bị
  4. Sơ đồ kết nối
  5. Cài thư viện & cấu hình
  6. Mã nguồn ví dụ hoàn chỉnh
  7. Giải thích mã
  8. Test nhanh bằng MQTT client
  9. Lỗi thường gặp & cách khắc phục
  10. Gợi ý mở rộng

1) Giới thiệu

MQTT là giao thức nhẹ cho IoT, tối ưu băng thông và độ trễ thấp. Với ESP32, bạn có thể:

  • Publish dữ liệu cảm biến lên topic (telemetry).
  • Subscribe lệnh từ server để điều khiển LED/relay.
  • Tự reconnect khi WiFi/MQTT bị gián đoạn.

2) MQTT hoạt động thế nào?

  • Broker (ví dụ: Mosquitto, EMQX, HiveMQ) làm “trung tâm”.
  • Thiết bị publish vào một topic (vd: iot/orgA/dev01/telemetry).
  • Ứng dụng khác subscribe topic đó để nhận dữ liệu.
  • Có QoS 0/1/2, retained message, Last Will để báo mất kết nối.

Cấu trúc topic khuyến nghị:

iot/<org>/<deviceId>/telemetry

iot/<org>/<deviceId>/cmd/led

Payload JSON mẫu (telemetry):

{"ts": 1731148800000, "temp": 29.5, "hum": 62.1, "device": "dev01"}

3) Phần cứng cần chuẩn bị

Linh kiệnSố lượngGhi chú
ESP32 DevKit V11
Cảm biến DHT111DATA → GPIO4 (từ Bài 4)
LED + điện trở 220Ω1LED trạng thái, GPIO2
Dây nối, breadboardVài sợi

4) Sơ đồ kết nối (Connection Diagram)

Tóm tắt dây:

  • DHT11: VCC → 3V3, GND → GND, DATA → GPIO4 (+ 10kΩ kéo lên 3V3 nếu cảm biến rời).
  • LED: Anode → GPIO2 qua 220Ω, Cathode → GND.

5) Cài thư viện & cấu hình

  • PubSubClient (by Nick O’Leary) – Tools → Manage Libraries…
  • DHT sensor library + Adafruit Unified Sensor (nếu chưa cài từ Bài 4)

Khai báo broker:

  • Nếu có broker riêng: mqtt_host = “mqtt.yourdomain.com” (TLS khuyến nghị).
  • Nếu test nhanh: dùng broker public (không bảo mật – chỉ dùng thử), ví dụ broker.hivemq.com.

6) Mã nguồn ví dụ hoàn chỉnh

#include <WiFi.h>

#include <PubSubClient.h>

#include <DHT.h>

// ==== WiFi & MQTT ====

const char* ssid       = "YourWiFiName";

const char* password   = "YourPassword";

// Dùng broker riêng nếu có (khuyến nghị). Public chỉ để demo.

const char* mqtt_host  = "broker.hivemq.com";

const uint16_t mqtt_port = 1883; // TLS thường 8883 (cần thêm WiFiClientSecure)

const char* org       = "orgA";

const char* deviceId  = "dev01";

String topicTelemetry = String("iot/") + org + "/" + deviceId + "/telemetry";

String topicCmdLed    = String("iot/") + org + "/" + deviceId + "/cmd/led";

String lastWillTopic  = String("iot/") + org + "/" + deviceId + "/status";

// ==== DHT & LED ====

#define DHTPIN 4

#define DHTTYPE DHT11

#define LED_PIN 2

DHT dht(DHTPIN, DHTTYPE);

WiFiClient espClient;

PubSubClient mqtt(espClient);

// Forward declare

void ensureMqtt();

void onMqttMessage(char* topic, byte* payload, unsigned int length);

void setup() {

  Serial.begin(115200);

  delay(500);

  pinMode(LED_PIN, OUTPUT);

  digitalWrite(LED_PIN, LOW);

  // WiFi

  Serial.print("Connecting WiFi");

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }

  Serial.println("\n✅ WiFi connected: " + WiFi.localIP().toString());

  // MQTT

  mqtt.setServer(mqtt_host, mqtt_port);

  mqtt.setCallback(onMqttMessage);

  dht.begin();

}

unsigned long lastPub = 0;

void loop() {

  if (WiFi.status() != WL_CONNECTED) {

    Serial.println("⚠️ WiFi lost. Reconnecting...");

    WiFi.reconnect();

    delay(1000);

  }

  ensureMqtt();

  mqtt.loop();

  // Publish telemetry mỗi 5 giây

  unsigned long now = millis();

  if (now - lastPub > 5000) {

    lastPub = now;

    float h = dht.readHumidity();

    float t = dht.readTemperature();

    if (isnan(h) || isnan(t)) {

      Serial.println("❌ DHT read failed");

      return;

    }

    // JSON đơn giản (có thể dùng ArduinoJson cho payload lớn)

    String payload = String("{\"ts\":") + String((unsigned long)(millis())) +

                     ",\"temp\":" + String(t, 2) +

                     ",\"hum\":"  + String(h, 2) +

                     ",\"device\":\"" + deviceId + "\"}";

    boolean ok = mqtt.publish(topicTelemetry.c_str(), payload.c_str(), true); // retained

    Serial.print(ok ? "✅ Published: " : "❌ Publish failed: ");

    Serial.println(payload);

  }

}

// Đảm bảo MQTT luôn kết nối, có Last Will & subscribe lệnh

void ensureMqtt() {

  while (!mqtt.connected()) {

    Serial.print("Connecting MQTT...");

    String clientId = "esp32-" + String(deviceId) + "-" + String(random(0xffff), HEX);

    // Last Will: báo "offline" khi mất kết nối đột ngột

    if (mqtt.connect(clientId.c_str(), NULL, NULL,

                     lastWillTopic.c_str(), 0, true, "offline")) {

      Serial.println(" connected.");

      // Đánh dấu online

      mqtt.publish(lastWillTopic.c_str(), "online", true);

      // Nhận lệnh LED

      mqtt.subscribe(topicCmdLed.c_str());

      Serial.print("Subscribed: "); Serial.println(topicCmdLed);

    } else {

      Serial.print(" failed, rc="); Serial.print(mqtt.state());

      Serial.println(" | retry in 2s");

      delay(2000);

    }

  }

}

// Xử lý lệnh: iot/<org>/<deviceId>/cmd/led  -> payload: "ON" / "OFF" / "TOGGLE"

void onMqttMessage(char* topic, byte* payload, unsigned int length) {

  String msg; msg.reserve(length);

  for (unsigned int i = 0; i < length; i++) msg += (char)payload[i];

  Serial.print("? ["); Serial.print(topic); Serial.print("] "); Serial.println(msg);

  if (String(topic) == topicCmdLed) {

    if (msg == "ON")       digitalWrite(LED_PIN, HIGH);

    else if (msg == "OFF") digitalWrite(LED_PIN, LOW);

    else if (msg == "TOGGLE") digitalWrite(LED_PIN, !digitalRead(LED_PIN));

  }

}

7) Giải thích mã

  • PubSubClient xử lý MQTT; setServer() chỉ định broker; setCallback() nhận tin.
  • Last Will: lastWillTopic mang trạng thái “offline” khi thiết bị rớt kết nối đột ngột.
  • Retained: publish retained giúp client mới subscribe sẽ nhận trạng thái mới nhất.
  • ensureMqtt(): tự kết nối lại & subscribe topic lệnh.
  • onMqttMessage(): nhận lệnh “ON” | “OFF” | “TOGGLE” để điều khiển LED.

8) Test nhanh bằng MQTT client

Bạn có thể dùng:

  • MQTTX (GUI)
  • MQTT Explorer
  • Hoặc mosquitto_pub/sub (CLI)

Ví dụ (CLI):

# Subscribe telemetry

mosquitto_sub -h broker.hivemq.com -t "iot/orgA/dev01/telemetry" -v

# Gửi lệnh bật LED

mosquitto_pub -h broker.hivemq.com -t "iot/orgA/dev01/cmd/led" -m "ON" -q 1

9) Lỗi thường gặp & cách khắc phục

  • Không kết nối được broker → Kiểm tra host/port, tường lửa router, hoặc đổi mạng.
  • Mất kết nối liên tục → Tăng keepAlive, kiểm tra nguồn ESP32, bắt WiFi yếu.
  • DHT đọc lỗi → Tăng delay giữa các lần đọc; kiểm tra điện trở kéo lên cho DATA.
  • Topic sai chính tả → Dùng biến topicTelemetry, topicCmdLed thống nhất.

10) Gợi ý mở rộng

  • Dùng TLS (8883) với WiFiClientSecure + chứng chỉ CA.
  • Thêm QoS 1 cho gói lệnh quan trọng.
  • Lưu local buffer (SPIFFS/LittleFS) để retry khi mất mạng.
  • Gửi batched JSON (nhiều mẫu/gói) để tối ưu băng thông.
  • Tạo dashboard bằng Node-RED / Grafana / Home Assistant.

Comments

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *