2026年9月16日 星期三

2026-09 mqtt 實驗2

2026-09 mqtt 實驗2

基於 MQTT 協定發佈/訂閱 (Pub/Sub) 架構 的完整物聯網 (IoT) 監控與遠端控制系統。













本系統為一套基於 MQTT 協定發佈/訂閱 (Pub/Sub) 架構 的完整物聯網 (IoT) 監控與遠端控制系統。系統主要包含硬體感知層、MQTT 通訊中繼層以及使用者控制層三大部分:

1. 硬體感知與執行層 (ESP32 在 Wokwi 模擬環境)

  • 硬體架構

    • GPIO 2:連接 LED 燈,作為控制致動器。

    • GPIO 4:連接 DHT22 溫濕度感測器,作為環境數據採集源。

  • 資料採集與發佈:ESP32 每 2 秒採集一次環境溫濕度,並自動透過 MQTT 發行至 alex9ufo/2026/temp(溫度)與 alex9ufo/2026/humi(濕度)主題。

  • 雙向控制與狀態回報

    • 訂閱 alex9ufo/2026/led 接收控制命令(onoffflashtimer(5Sec)),並即時切換 LED 狀態。

    • 執行命令後,向 alex9ufo/2026/ledstatus 回報當前狀態(如 on_ledoff_led)。

  • 握手確認 (Handshake ACK):訂閱 alex9ufo/2026/tempOKalex9ufo/2026/humiOK 主題。當收到 Python 端發出的確認訊號時,會在 Serial Monitor 印出 temp_Rx_OKhumi_Rx_OK,確保資料傳輸品質。

2. MQTT 通訊中繼層 (Broker)

  • 伺服器:採用 HiveMQ 公開測試伺服器 (broker.hivemq.com:1883)。

  • 主題架構 (Topic Topology)

通訊方向主題名稱功能 / Payload 內容
Python → ESP32alex9ufo/2026/led控制命令 (on, off, flash, timer(5Sec))
ESP32 → Pythonalex9ufo/2026/ledstatusLED 狀態回報 (on_led, off_led, 等)
ESP32 → Pythonalex9ufo/2026/temp實時溫度數值 (如 25.5)
ESP32 → Pythonalex9ufo/2026/humi實時濕度數值 (如 60.0)
Python → ESP32alex9ufo/2026/tempOK溫度接收確認訊號 (OK)
Python → ESP32alex9ufo/2026/humiOK濕度接收確認訊號 (OK)

3. 使用者控制層 (Python + Tkinter GUI)

  • 圖形化監控介面:即時顯示目前 MQTT 連線狀態(綠色已連線/紅色斷線)、LED 實時狀態以及最新的環境溫濕度數值。

  • 遠端按鈕控制:提供四個控制按鈕,使用者點擊即可發送對應命令控制 ESP32。

  • 自動 ACK 回應:當收到 ESP32 發出的溫濕度數據並更新 UI 後,會自動發送 OK 訊息至對應的 tempOKhumiOK 主題,完成閉環通訊驗證。



ESP32 程式碼整合了 Wi-Fi 連線、MQTT 雙向通訊、DHT22 溫濕度感測器讀取 以及 LED 多模式控制

硬體與網路初始化

  • 硬體腳位設定:GPIO 2 連接控制 LED,GPIO 4 連接 DHT22 溫濕度感測器。

  • Wi-Fi 連線:開機後自動連線至 Wokwi-GUEST 虛擬熱點,並在 Serial Monitor 印出連線進度。

  • MQTT Broker 連線:連接至 HiveMQ 公開伺服器(broker.hivemq.com:1883),連線成功後自動訂閱三個主題:

    • alex9ufo/2026/led(接收控制指令)

    • alex9ufo/2026/tempOK(接收溫度確認訊號)

    • alex9ufo/2026/humiOK(接收濕度確認訊號)

訊息接收與處理解析 (Callback) 收到訂閱主題的訊息時會執行對應邏輯:

  • 確認訊號(ACK)處理

    • 收到 tempOK 主題訊息:在 Serial Monitor 輸出 temp_Rx_OK

    • 收到 humiOK 主題訊息:在 Serial Monitor 輸出 humi_Rx_OK

  • LED 控制指令處理(收到後同步向 alex9ufo/2026/ledstatus 回報狀態):

    • "on":點亮 LED,回傳 "on_led"

    • "off":關閉 LED,回傳 "off_led"

    • "flash":開啟閃爍模式,回傳 "flash_led"

    • "timer(5Sec)":點亮 LED 並開始 5 秒倒數,回傳 "timer(5Sec)_led"

非阻塞式背景任務 (Loop) 利用 millis() 代替 delay(),確保 MQTT 能持續保持連線不中斷:

  • 溫濕度採樣發行:每 2 秒自動讀取一次 DHT22 感測器:

    • 將溫度數值發行至 alex9ufo/2026/temp

    • 將濕度數值發行至 alex9ufo/2026/humi

    • 於 Serial Monitor 印出實時數據。

  • FLASH 閃爍邏輯:每 500 毫秒翻轉一次 GPIO 2 電位。

  • TIMER 定時邏輯:滿 5 秒後自動關閉 LED,將模式切回 OFF 並發行 "off_led" 狀態通知控制端。

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

#define DHTPIN 4
#define DHTTYPE DHT22

const char* ssid = "Wokwi-GUEST";
const char* password = "";

const char* mqtt_server = "broker.hivemq.com";
const int mqtt_port = 1883;

const char* sub_topic = "alex9ufo/2026/led";
const char* pub_topic = "alex9ufo/2026/ledstatus";
const char* temp_topic = "alex9ufo/2026/temp";
const char* humi_topic = "alex9ufo/2026/humi";
const char* temp_ok_topic = "alex9ufo/2026/tempOK";
const char* humi_ok_topic = "alex9ufo/2026/humiOK";

const int ledPin = 2;

WiFiClient espClient;
PubSubClient client(espClient);
DHT dht(DHTPIN, DHTTYPE);

enum Mode { OFF, ON, FLASH, TIMER };
Mode currentMode = OFF;

unsigned long lastFlashTime = 0;
unsigned long timerStartTime = 0;
unsigned long lastDHTTime = 0;
bool ledState = LOW;

void setup_wifi() {
  delay(10);
  Serial.print("[Wi-Fi] 正在連線至 ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\n[Wi-Fi] 已成功連線!");
}

void publishStatus(const char* payload) {
  client.publish(pub_topic, payload);
  Serial.print("[MQTT 發行] LED 狀態: ");
  Serial.println(payload);
}

void callback(char* topic, byte* payload, unsigned int length) {
  String message;
  for (int i = 0; i < length; i++) {
    message += (char)payload[i];
  }

  Serial.print("[MQTT 接收] 主題: ");
  Serial.print(topic);
  Serial.print(" | Payload: ");
  Serial.println(message);

  // 接收 tempOK 主題
  if (String(topic) == temp_ok_topic) {
    Serial.println("temp_Rx_OK");
    return;
  }
 
  // 接收 humiOK 主題
  if (String(topic) == humi_ok_topic) {
    Serial.println("humi_Rx_OK");
    return;
  }

  if (message == "on") {
    currentMode = ON;
    digitalWrite(ledPin, HIGH);
    publishStatus("on_led");
  } else if (message == "off") {
    currentMode = OFF;
    digitalWrite(ledPin, LOW);
    publishStatus("off_led");
  } else if (message == "flash") {
    currentMode = FLASH;
    publishStatus("flash_led");
  } else if (message == "timer(5Sec)") {
    currentMode = TIMER;
    timerStartTime = millis();
    digitalWrite(ledPin, HIGH);
    publishStatus("timer(5Sec)_led");
  }
}

void reconnect() {
  while (!client.connected()) {
    Serial.print("[MQTT] 正在嘗試連線至 Broker: ");
    Serial.println(mqtt_server);
    String clientId = "ESP32Client-" + String(random(0xffff), HEX);
    if (client.connect(clientId.c_str())) {
      Serial.println("[MQTT] 連線成功!");
      client.subscribe(sub_topic);
      client.subscribe(temp_ok_topic); // 訂閱 tempOK
      client.subscribe(humi_ok_topic); // 訂閱 humiOK
    } else {
      Serial.print("[MQTT] 連線失敗, rc=");
      Serial.print(client.state());
      Serial.println(" 5 秒後重試...");
      delay(5000);
    }
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);
  dht.begin();
  setup_wifi();
  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);
}

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

  unsigned long currentMillis = millis();

  // 每 2 秒讀取並發行溫濕度
  if (currentMillis - lastDHTTime >= 5000) {
    lastDHTTime = currentMillis;
    float h = dht.readHumidity();
    float t = dht.readTemperature();

    if (!isnan(h) && !isnan(t)) {
      client.publish(temp_topic, String(t, 1).c_str());
      client.publish(humi_topic, String(h, 1).c_str());
      Serial.printf("[DHT22] 溫度: %.1f °C | 濕度: %.1f %%\n", t, h);
    }
  }

  if (currentMode == FLASH) {
    if (currentMillis - lastFlashTime >= 500) {
      lastFlashTime = currentMillis;
      ledState = !ledState;
      digitalWrite(ledPin, ledState);
    }
  } else if (currentMode == TIMER) {
    if (currentMillis - timerStartTime >= 5000) {
      digitalWrite(ledPin, LOW);
      currentMode = OFF;
      publishStatus("off_led");
    }
  }
}


這份 Python 程式碼使用 Tkinter 打造圖形介面 (GUI),並透過 paho-mqtt (Callback API v2) 實現與 ESP32 的雙向通訊及遠端控制。

MQTT 網路通訊管理

  • Broker 設定:連接至 HiveMQ 公開測試伺服器 (broker.hivemq.com:1883)。

  • 連線與自動訂閱 (on_connect):連線成功後,自動訂閱 ESP32 回傳的 3 個主題:

    • alex9ufo/2026/ledstatus(LED 狀態)

    • alex9ufo/2026/temp(溫度數據)

    • alex9ufo/2026/humi(濕度數據)

  • 連線狀態監控:即時監控連線與斷線事件,並在介面頂端動態更新狀態(藍/橘色「連線中」、綠色「已連線」、紅色「已斷線/連線失敗」)。

  • 背景異步處理 (loop_start):在獨立執行緒運行 MQTT 網路封包接收與發送,確保 Tkinter GUI 視窗畫面運作順暢不卡頓。

訊息接收與握手機制 (on_message) 當收到 ESP32 發佈的訊息時,會進行解析並處理:

  • LED 狀態更新:收到 ledstatus 訊息時,即時更新介面上的 LED 狀態: 文字(如 on_ledoff_led)。

  • 溫濕度更新與握手確認 (ACK)

    • 收到 temp 數據:更新 GUI 的溫度顯示,並自動發行 "OK" 訊息alex9ufo/2026/tempOK 主題通知 ESP32。

    • 收到 humi 數據:更新 GUI 的濕度顯示,並自動發行 "OK" 訊息alex9ufo/2026/humiOK 主題通知 ESP32。

GUI 控制介面與指令發送

  • 按鈕事件觸發 (send_cmd):提供 4 個按鈕控制遠端 ESP32 的 LED:

    • ON (開啟):發送 "on"alex9ufo/2026/led

    • OFF (關閉):發送 "off"alex9ufo/2026/led

    • FLASH (閃爍):發送 "flash"alex9ufo/2026/led

    • Timer (定時5秒):發送 "timer(5Sec)"alex9ufo/2026/led


import tkinter as tk
import paho.mqtt.client as mqtt

BROKER = "broker.hivemq.com"
PORT = 1883

TOPIC_PUB_LED = "alex9ufo/2026/led"
TOPIC_SUB_LEDSTATUS = "alex9ufo/2026/ledstatus"

TOPIC_SUB_TEMP = "alex9ufo/2026/temp"
TOPIC_SUB_HUMI = "alex9ufo/2026/humi"

# 新增 OK 回應主題
TOPIC_PUB_TEMPOK = "alex9ufo/2026/tempOK"
TOPIC_PUB_HUMIOK = "alex9ufo/2026/humiOK"

def on_connect(client, userdata, flags, reason_code, properties=None):
    if reason_code == 0:
        mqtt_status_label.config(text="MQTT 連線狀態: 已連線", fg="green")
        client.subscribe(TOPIC_SUB_LEDSTATUS)
        client.subscribe(TOPIC_SUB_TEMP)
        client.subscribe(TOPIC_SUB_HUMI)
    else:
        mqtt_status_label.config(text=f"MQTT 連線失敗 ({reason_code})", fg="red")

def on_disconnect(client, userdata, disconnect_flags, reason_code, properties=None):
    mqtt_status_label.config(text="MQTT 連線狀態: 已斷線", fg="red")

def on_message(client, userdata, msg):
    payload = msg.payload.decode("utf-8")
    topic = msg.topic
    
    if topic == TOPIC_SUB_LEDSTATUS:
        led_status_label.config(text=f"LED 狀態: {payload}")
    elif topic == TOPIC_SUB_TEMP:
        temp_label.config(text=f"環境溫度: {payload} °C")
        # 收到溫度資料後發送 tempOK 回應
        client.publish(TOPIC_PUB_TEMPOK, "OK")
    elif topic == TOPIC_SUB_HUMI:
        humi_label.config(text=f"環境濕度: {payload} %")
        # 收到濕度資料後發送 humiOK 回應
        client.publish(TOPIC_PUB_HUMIOK, "OK")

def send_cmd(cmd):
    client.publish(TOPIC_PUB_LED, cmd)

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
client.on_connect = on_connect
client.on_disconnect = on_disconnect
client.on_message = on_message

root = tk.Tk()
root.title("ESP32 LED & DHT22 控制器")
root.geometry("340x380")

mqtt_status_label = tk.Label(root, text="MQTT 連線狀態: 連線中...", font=("Arial", 10), fg="orange")
mqtt_status_label.pack(pady=5)

led_status_label = tk.Label(root, text="LED 狀態: 未知", font=("Arial", 11, "bold"), fg="blue")
led_status_label.pack(pady=5)

temp_label = tk.Label(root, text="環境溫度: -- °C", font=("Arial", 11, "bold"), fg="darkred")
temp_label.pack(pady=3)

humi_label = tk.Label(root, text="環境濕度: -- %", font=("Arial", 11, "bold"), fg="darkgreen")
humi_label.pack(pady=3)

btn_on = tk.Button(root, text="ON (開啟)", width=20, command=lambda: send_cmd("on"))
btn_on.pack(pady=4)

btn_off = tk.Button(root, text="OFF (關閉)", width=20, command=lambda: send_cmd("off"))
btn_off.pack(pady=4)

btn_flash = tk.Button(root, text="FLASH (閃爍)", width=20, command=lambda: send_cmd("flash"))
btn_flash.pack(pady=4)

btn_timer = tk.Button(root, text="Timer (定時5秒)", width=20, command=lambda: send_cmd("timer(5Sec)"))
btn_timer.pack(pady=4)

try:
    client.connect_async(BROKER, PORT, 60)
    client.loop_start()
except Exception as e:
    mqtt_status_label.config(text=f"連線異常: {e}", fg="red")

root.mainloop()





2026年9月15日 星期二

2026-09 mqtt 實驗1

 2026-09 mqtt 實驗1

MQTT (Message Queuing Telemetry Transport) 是一種輕量級的發佈/訂閱 (Publish/Subscribe) 訊息傳輸協定,專為網路頻寬有限、硬體資源受限或網路不穩定的物聯網 (IoT) 裝置與 M2M (Machine-to-Machine) 通訊而設計。

核心架構與運作機制

  • 發佈/訂閱模式 (Pub/Sub):傳送者(發佈者)與接收者(訂閱者)不直接對連,解耦了裝置間的時間與空間依賴。

  • 代理伺服器 (Broker):訊息的中繼站。負責接收發佈者的訊息、過濾並轉發給已訂閱該主題的客戶端(如 ESP32、Python 應用程式)。

  • 主題 (Topic):訊息的分類標籤,採階層式路徑設計(例如 home/bedroom/temperaturealex9ufo/2026/led)。

主要特性

  • 極輕量與高效能:標頭檔最小僅 2 Bytes,大幅降低網路封包負擔與裝置耗電量。

  • 基於 TCP/IP:確保資料傳輸的可靠性與順序性(預設 Port 為 1883,加密的 MQTTS 使用 Port 8883)。

  • 服務品質等級 (QoS, Quality of Service)

    • QoS 0 (At most once):最多一次,發後即忘(可能遺失)。

    • QoS 1 (At least once):至少一次,確保送達(可能重複)。

    • QoS 2 (Exactly once):恰好一次,確保不重覆且不遺失(傳送開銷最大)。

  • 遺言訊息 (Last Will and Testament, LWT):當裝置異常斷線時,Broker 會自動發佈事先預設好的訊息告知其他客戶端。

  • 保留訊息 (Retained Message):Broker 會儲存主題最後一筆訊息,新訂閱者連線時能立即取得當前最新的狀態。

典型應用場景

  • 物聯網與智慧家庭:感測器數據採集(溫濕度、電量)與家電遠端控制(如 ESP32 LED 控制)。

  • 車聯網與追蹤系統:車輛 GPS 定位與狀態回傳。

  • 即時通訊:部分行動裝置 App 的即時訊息推播服務。





















ESP32 程式功能說明

  • 網路與 MQTT 連線建立:開機後自動連線至 Wokwi 虛擬 Wi-Fi (Wokwi-GUEST),並連接至 mqttgo.io Broker (Port 1883)。連線成功後訂閱控制主題 alex9ufo/2026/led

  • 命令接收與模式切換 (Callback):收到指令後切換 currentMode 狀態,並立即向 alex9ufo/2026/ledstatus 主題回報最新狀態:

    • "on":將 GPIO 2 設為高電位(亮燈),回傳 "on_led"

    • "off":將 GPIO 2 設為低電位(熄滅),回傳 "off_led"

    • "flash":進入閃爍模式,回傳 "flash_led"

    • "timer(5Sec)":點亮 LED 並記錄起始時間,回傳 "timer(5Sec)_led"

  • 非阻塞式背景任務 (Loop):利用 millis() 時間差處理持續性動作,避免使用 delay() 導致 MQTT 斷線:

    • 閃爍模式:每 500 毫秒翻轉一次 GPIO 2 電位。

    • 定時模式:持續滿 5 秒後自動關閉 LED,將模式切回 OFF 並回傳 "off_led"

Python (Tkinter) 程式功能說明

  • 圖形化操作介面 (GUI):使用 Tkinter 打造控制面板,提供 ONOFFFLASHTimer (定時5秒) 四個控制按鈕。

  • 雙向 MQTT 通訊

    • 發送端:點擊按鈕時,將對應的指令字串(如 "on""flash")發送至 alex9ufo/2026/led 主題。

    • 接收端:訂閱 alex9ufo/2026/ledstatus 主題,即時接收 ESP32 回傳的當前狀態。

  • 狀態動態更新

    • MQTT 連線狀態:監聽 MQTT 連線狀態(on_connect / on_disconnect),在介面上以不同顏色顯示「連線中」、「已連線」或「已斷線」。

    • LED 實時狀態:接收到 ESP32 回傳訊息時,動態更新介面上的 LED 狀態 文字(例如 ESP32 定時 5 秒結束發送 "off_led" 時,介面也會同步更新為關閉狀態)。

  • 背景異步執行:採用 loop_start() 讓 MQTT 在獨立執行緒處理網路封包,確保 GUI 畫面順暢不卡頓。

Python+TKinter程式

import tkinter as tk

import paho.mqtt.client as mqtt


BROKER = "mqttgo.io"

PORT = 1883

TOPIC_PUB = "alex9ufo/2026/led"

# 修正重點:補上 /2026,確保與 ESP32 的 pub_topic 完全一致

TOPIC_SUB = "alex9ufo/2026/ledstatus"


# MQTT v2 Callback: 處理連線狀態

def on_connect(client, userdata, flags, reason_code, properties=None):

    if reason_code == 0:

        print(f"[系統日誌] 成功連線至 Broker,正在訂閱主題: {TOPIC_SUB}")

        mqtt_status_label.config(text="MQTT 連線狀態: 已連線", fg="green")

        client.subscribe(TOPIC_SUB)

    else:

        print(f"[錯誤日誌] 連線失敗,代碼: {reason_code}")

        mqtt_status_label.config(text=f"MQTT 連線失敗 (代碼: {reason_code})", fg="red")


# MQTT v2 Callback: 處理斷線狀態

def on_disconnect(client, userdata, disconnect_flags, reason_code, properties=None):

    print(f"[警告日誌] 已與 Broker 斷開連線,原因代碼: {reason_code}")

    mqtt_status_label.config(text="MQTT 連線狀態: 已斷線", fg="red")


# MQTT Callback: 接收 ESP32 回傳的狀態

def on_message(client, userdata, msg):

    try:

        payload = msg.payload.decode("utf-8")

        print(f"[收到訊息] 主題: {msg.topic} | 內容: {payload}")

        # 更新 UI

        led_status_label.config(text=f"LED 狀態: {payload}")

    except Exception as e:

        print(f"[錯誤日誌] 解析訊息失敗: {e}")


def send_cmd(cmd):

    print(f"[發送指令] 主題: {TOPIC_PUB} | 指令: {cmd}")

    client.publish(TOPIC_PUB, cmd)


# 初始化 MQTT (使用 Callback API v2)

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)

client.on_connect = on_connect

client.on_disconnect = on_disconnect

client.on_message = on_message


# GUI 介面設計

root = tk.Tk()

root.title("ESP32 LED 控制器")

root.geometry("320x300")


# 顯示 MQTT 連線狀態

mqtt_status_label = tk.Label(root, text="MQTT 連線狀態: 連線中...", font=("Arial", 10), fg="orange")

mqtt_status_label.pack(pady=5)


# 顯示 LED 目前狀態

led_status_label = tk.Label(root, text="LED 狀態: 未知", font=("Arial", 12, "bold"), fg="blue")

led_status_label.pack(pady=10)


# 控制按鈕

btn_on = tk.Button(root, text="ON (開啟)", width=20, command=lambda: send_cmd("on"))

btn_on.pack(pady=5)


btn_off = tk.Button(root, text="OFF (關閉)", width=20, command=lambda: send_cmd("off"))

btn_off.pack(pady=5)


btn_flash = tk.Button(root, text="FLASH (閃爍)", width=20, command=lambda: send_cmd("flash"))

btn_flash.pack(pady=5)


btn_timer = tk.Button(root, text="Timer (定時5秒)", width=20, command=lambda: send_cmd("timer(5Sec)"))

btn_timer.pack(pady=5)


# 非阻塞方式連接 Broker

try:

    print(f"[系統日誌] 正在嘗試連線至 Broker: {BROKER}:{PORT}")

    client.connect_async(BROKER, PORT, 60)

    client.loop_start()

except Exception as e:

    print(f"[錯誤日誌] 連線異常: {e}")

    mqtt_status_label.config(text=f"連線異常: {e}", fg="red")


root.mainloop()





WOKWI   ESP32 程式

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

const char* ssid = "Wokwi-GUEST";
const char* password = "";

const char* mqtt_server = "mqttgo.io";
const int mqtt_port = 1883;

const char* sub_topic = "alex9ufo/2026/led";
const char* pub_topic = "alex9ufo/2026/ledstatus";

const int ledPin = 2;

WiFiClient espClient;
PubSubClient client(espClient);

enum Mode { OFF, ON, FLASH, TIMER };
Mode currentMode = OFF;

unsigned long lastFlashTime = 0;
unsigned long timerStartTime = 0;
bool ledState = LOW;

void setup_wifi() {
  delay(10);
  Serial.print("[Wi-Fi] 正在連線至 ");
  Serial.println(ssid);

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

  Serial.println("\n[Wi-Fi] 已成功連線!");
  Serial.print("[Wi-Fi] IP 位址: ");
  Serial.println(WiFi.localIP());
}

void publishStatus(const char* payload) {
  client.publish(pub_topic, payload);
  Serial.print("[MQTT 發行] 主題: ");
  Serial.print(pub_topic);
  Serial.print(" | Payload: ");
  Serial.println(payload);
}

void callback(char* topic, byte* payload, unsigned int length) {
  String message;
  for (int i = 0; i < length; i++) {
    message += (char)payload[i];
  }

  Serial.print("[MQTT 接收] 主題: ");
  Serial.print(topic);
  Serial.print(" | Payload: ");
  Serial.println(message);

  if (message == "on") {
    currentMode = ON;
    digitalWrite(ledPin, HIGH);
    publishStatus("on_led");
  } else if (message == "off") {
    currentMode = OFF;
    digitalWrite(ledPin, LOW);
    publishStatus("off_led");
  } else if (message == "flash") {
    currentMode = FLASH;
    publishStatus("flash_led");
  } else if (message == "timer(5Sec)") {
    currentMode = TIMER;
    timerStartTime = millis();
    digitalWrite(ledPin, HIGH);
    publishStatus("timer(5Sec)_led");
  }
}

void reconnect() {
  while (!client.connected()) {
    Serial.print("[MQTT] 正在嘗試連線至 Broker: ");
    Serial.println(mqtt_server);
   
    String clientId = "ESP32Client-" + String(random(0xffff), HEX);
    if (client.connect(clientId.c_str())) {
      Serial.println("[MQTT] 連線成功!");
      Serial.print("[MQTT] 正在訂閱主題: ");
      Serial.println(sub_topic);
      client.subscribe(sub_topic);
    } else {
      Serial.print("[MQTT] 連線失敗, rc=");
      Serial.print(client.state());
      Serial.println(" 5 秒後重試...");
      delay(5000);
    }
  }
}

void setup() {
  Serial.begin(115200); // 初始化 Serial Monitor
 
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);
 
  setup_wifi();
  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);
}

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

  unsigned long currentMillis = millis();

  if (currentMode == FLASH) {
    if (currentMillis - lastFlashTime >= 500) {
      lastFlashTime = currentMillis;
      ledState = !ledState;
      digitalWrite(ledPin, ledState);
    }
  } else if (currentMode == TIMER) {
    if (currentMillis - timerStartTime >= 5000) {
      digitalWrite(ledPin, LOW);
      currentMode = OFF;
      publishStatus("off_led");
    }
  }
}


2026-09 mqtt 實驗2

2026-09 mqtt 實驗2 基於 MQTT 協定 與 發佈/訂閱 (Pub/Sub) 架構 的完整物聯網 (IoT) 監控與遠端控制系統。 本系統為一套基於 MQTT 協定 與 發佈/訂閱 (Pub/Sub) 架構 的完整物聯網 (IoT) 監控與遠端控制系統。系統...