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"

TOPIC_SUB = "alex9ufo/ledstatus"


# MQTT v2 Callback: 處理連線狀態

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)

    else:

        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):

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


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

def on_message(client, userdata, msg):

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

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


def send_cmd(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:

    client.connect_async(BROKER, PORT, 60)

    client.loop_start()

except Exception as 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);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }
}

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

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

void reconnect() {
  while (!client.connected()) {
    String clientId = "ESP32Client-" + String(random(0xffff), HEX);
    if (client.connect(clientId.c_str())) {
      client.subscribe(sub_topic);
    } else {
      delay(5000);
    }
  }
}

void setup() {
  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;
      client.publish(pub_topic, "off_led");
    }
  }
}

沒有留言:

張貼留言

2026-09 mqtt 實驗1

 2026-09 mqtt 實驗1 MQTT (Message Queuing Telemetry Transport) 是一種輕量級的 發佈/訂閱 (Publish/Subscribe) 訊息傳輸協定,專為網路頻寬有限、硬體資源受限或網路不穩定的物聯網 (IoT) 裝置與 ...