
前言:為什麼 IoT 專案最後都會選 MQTT
如果你做過「把感測器資料丟上網頁」的專案,大概都經歷過同一段掙扎:要嘛讓 ESP32 開一個網頁(但你不在同一個網段就看不到),要嘛用 HTTP 定時打 API(但每次都要重新連線,耗電又慢)。
MQTT 解決的正是這件事:它讓裝置「講話」而不是「被問話」。資料一有變化就發布出去,有興趣的人自然會收到。
MQTT 跟 HTTP 差在哪

關鍵差別在「解耦」:發布者和訂閱者完全不需要知道彼此的存在,甚至不需要同時在線。這對電池供電、偶爾上線的裝置來說是決定性的優勢。
三個角色,各管一件事


在這篇的專案裡,三個角色分別是:ESP32(Publisher)、Mosquitto(Broker)、Node-RED(Subscriber)。三方唯一的約定就是 Topic 名稱 —— 這裡用的是 sensor/environment。
整體資料流

整條鏈路是單向的:感測器讀取 → ESP32 打包成 JSON → 發布到 Broker → Node-RED 訂閱並顯示。而且全部軟體都是開源免費的,硬體成本不到台幣 500 元。
先把 Broker 架起來
Mosquitto 是最輕量也最常見的開源 MQTT Broker,在 Linux 上一行指令就裝好:
# Ubuntu / Debian
sudo apt install mosquitto mosquitto-clients
# 啟動服務
sudo systemctl enable mosquitto
sudo systemctl start mosquitto
# 測試是否正常
mosquitto_sub -h localhost -t "test" &
mosquitto_pub -h localhost -t "test" -m "Hello MQTT"
預設 Port 是 1883(未加密);如果要用 TLS,則是 8883。本機測試用 1883 就好,要對外開放就一定要看完後面的安全性章節。
接線:ESP32 與 DHT22
| DHT22 接腳 | ESP32 GPIO |
|---|---|
| VCC(Pin 1) | 3.3V |
| DATA(Pin 2) | GPIO4 |
| NC(Pin 3) | 不使用 |
| GND(Pin 4) | GND |

那一顆上拉電阻(4.7 kΩ~10 kΩ)是整個專案最容易被忽略、卻最常導致失敗的零件。如果你買的是藍色封裝的 DHT22 模組,通常已經內建,可以直接接。
ESP32 程式碼
需要三個程式庫:DHT sensor library(Adafruit)、PubSubClient(MQTT)、ArduinoJson(JSON 封裝)。完整程式如下:
#include <WiFi.h>
#include <PubSubClient.h>
#include <DHT.h>
#include <ArduinoJson.h>
// WiFi 設定
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASS";
// MQTT 設定
const char* mqtt_server = "192.168.1.100"; // Broker IP
const int mqtt_port = 1883;
const char* mqtt_topic = "sensor/environment";
// DHT 設定
#define DHTPIN 4
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
WiFiClient espClient;
PubSubClient client(espClient);
void setup_wifi() {
delay(10);
Serial.println();
Serial.print("連線到 WiFi: ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.print("已連線,IP: ");
Serial.println(WiFi.localIP());
}
void reconnect() {
while (!client.connected()) {
Serial.print("MQTT 連線中...");
if (client.connect("ESP32_DHT22")) {
Serial.println("已連線");
} else {
Serial.print("失敗,rc=");
Serial.print(client.state());
Serial.println(" 5 秒後重試");
delay(5000);
}
}
}
void setup() {
Serial.begin(115200);
dht.begin();
setup_wifi();
client.setServer(mqtt_server, mqtt_port);
}
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
// 每 10 秒讀取一次
static unsigned long lastMsg = 0;
if (millis() - lastMsg > 10000) {
lastMsg = millis();
float h = dht.readHumidity();
float t = dht.readTemperature();
if (isnan(h) || isnan(t)) {
Serial.println("DHT22 讀取失敗");
return;
}
// 封裝 JSON
JsonDocument doc;
doc["temperature"] = t;
doc["humidity"] = h;
doc["device"] = "esp32-lobby";
char buffer[256];
size_t n = serializeJson(doc, buffer);
client.publish(mqtt_topic, buffer, n);
Serial.printf("已發布: %.1f°C, %.1f%%\r\n", t, h);
}
}
這段程式有一個值得學的細節:用 millis() 而不是 delay() 來控制上報間隔。這樣在等待的期間,ESP32 仍然能處理 WiFi 與 MQTT 的維護工作,斷線重連邏輯才有機會執行。
Node-RED 儀表板
Node-RED 是視覺化的物聯網編程工具,用來接 MQTT 資料並畫成圖表非常直觀。安裝:
# 安裝 Node-RED
sudo npm install -g node-red
# 安裝儀表板套件
npm install node-red-dashboard
# 啟動
node-red
裝好之後,整個儀表板其實只有三個節點:

拉線、Deploy,打開 /ui 就能看到溫濕度即時更新的指針圖表。
安全性設定:對外開放前一定要做
如果你的 Broker 會暴露在網際網路上(例如架在雲端 VPS),沒有密碼保護等於把感測器資料公開展示。先建立密碼檔:
# 建立密碼檔案
sudo mosquitto_passwd -c /etc/mosquitto/passwd esp32_client
# 輸入密碼:your_password
# 編輯設定檔 /etc/mosquitto/mosquitto.conf
echo "allow_anonymous false" >> /etc/mosquitto/mosquitto.conf
echo "password_file /etc/mosquitto/passwd" >> /etc/mosquitto/mosquitto.conf
# 重啟服務
sudo systemctl restart mosquitto
ESP32 端也要跟著加上帳號密碼:
// 在 connect 時加入帳密
client.connect("ESP32_DHT22", "esp32_client", "your_password");
如果要把這套系統推進到工業環境,原文的建議是:加上 TLS 加密、使用 QoS 1 確保資料不遺失,並考慮用 EMQX 取代 Mosquitto。
四個一定會遇到的問題

其中兩個最值得先記住:DHT22 回傳 NaN 有 99% 是上拉電阻;而用 delay() 控制上報間隔會讓 WiFi 與 MQTT 的維護工作停擺,這是新手最常犯的錯,也是斷線問題的常見元凶。
總結
這個專案把「感測器 → 雲端 → 儀表板」的完整鏈路走了一遍,四個重點:
- 發布/訂閱取代請求/回應:裝置主動講話,不需要被問
- 三方只靠 Topic 約定:Publisher、Broker、Subscriber 完全解耦
- Broker 是唯一要維運的元件:1883 未加密、8883 走 TLS
- 硬體的坑都在電阻上:DHT22 的上拉電阻決定成敗
下一步很自然:把資料存進資料庫、加上告警規則,或是換成支援 QoS 與叢集的 EMQX。這些都只是同一套架構往外延伸而已。
文章評論