2026年3月2日 星期一

MQTT 基本觀念與實驗(1)

MQTT 基本觀念與實驗 (1)


一個基於 ESP32 的物聯網系統架構,描述了硬體端、雲端代理伺服器(Broker)以及多個客戶端(Clients)之間的互動關係

以下是根據圖片內容對該系統關係的詳細說明:

1. 硬體終端 (CLIENT 3 / ESP32)

這是系統的實體感測與執行層

  • 感測器數據採集:透過 DHT22 感測器讀取環境的溫度與濕度數據

  • 在地顯示:將溫濕度數值即時顯示於 I2C 16x2 LCD 螢幕上(例如:Temp: 23.7 C, Humi: 76.5%)

  • 訊息發布 (Publish):將讀取的數據上傳至 MQTT Broker

    • 溫度主題alex9ufo/mqtt/ex1/Temp

    • 濕度主題alex9ufo/mqtt/ex1/Humi

  • 執行器控制:訂閱主題 alex9ufo/mqtt/ex1/led 以接收控制指令,操作 LED 的狀態(如亮、滅、閃爍或計時)

2. MQTT 雲端代理伺服器 (SERVER / BROKER)

該系統使用 MQTTGO (NMking Technology) 作為核心通訊中轉站

  • 訊息路由:負責接收來自 ESP32 的溫濕度訊息,並將其轉發給所有訂閱該主題的客戶端(如 Python 介面)

  • 指令轉發:接收來自控制端(Client 1 或 2)的 LED 指令,並發送給 ESP32

3. 監控與控制端 (CLIENT 1 & 2)

這些客戶端負責與使用者互動:

  • Python GUI 介面 (Client 2)

    • 數據監測:訂閱溫濕度主題,並以圓餅圖或文字形式顯示「當前溫度: 23.7°C」與「當前濕度: 76.5%」

    • 連線狀態:顯示 MQTT 的連線指示燈

    • 遠端控制:提供按鈕發送指令至 led 主題,包含 ON(亮)、OFF(滅)、Flash(閃爍)及 Timer: 5s(計時 5 秒)

  • MQTTBOX (Client 1)

    • 作為通訊測試工具,用於監看或手動發布主題訊息,確保系統通訊正常

總結關係流程

  1. 數據流:[ESP32] → 發布溫濕度 → [MQTT Broker] → 轉送訊息 → [Python 介面]

  2. 指令流:[Python 介面] → 發布 LED 指令 → [MQTT Broker] → 轉送指令 → [ESP32] → 控制 LED 動作


在 MQTT(Message Queuing Telemetry Transport)架構中,核心概念是**「發行(Publish)」「訂閱(Subscribe)」**,這是一種「非同步」的通訊方式,所有訊息都透過一個中間人(Broker)來傳遞。

以下根據你的接線圖 與架構圖 ,詳細說明這套系統的運作邏輯:

1. 角色定義

  • 發行者 (Publisher):產生訊息並發送到特定「主題 (Topic)」的客戶端。在你的案例中,ESP32 與 Python 程式都同時扮演這個角色。

  • 訂閱者 (Subscriber):向 Broker 登記想要接收某個「主題」訊息的客戶端。

  • 代理伺服器 (Broker):即 mqttgo.io 。它負責接收所有發行者的訊息,並將訊息精準地轉發給所有有訂閱該主題的客戶端。


2. 數據流:從感測器到監控介面 (上行)

這是將環境數據傳送到雲端與電腦的過程:

  • 動作:發行 (Publish)

    • 發行者:ESP32

    • 主題與內容

      1. 主題:alex9ufo/mqtt/ex1/Temp,內容:例如 23.7

      2. 主題:alex9ufo/mqtt/ex1/Humi,內容:例如 76.5

    • 頻率:每 5 秒發送一次。 

    • DHT22 的感測極限(溫度  -40  C ~ +80 C ,濕度 0% ~ 100%

  • 動作:訂閱 (Subscribe)

    • 訂閱者:Python Tkinter 程式 (Client 2) 與 MQTTBOX (Client 1)

    • 結果:當 Broker 收到 ESP32 的溫濕度時,會立刻推播給 Python 程式,進而更新介面上的圓餅圖


3. 指令流:從電腦控制硬體 (下行)

這是遠端控制 LED 燈光的過程:

  • 動作:發行 (Publish)

    • 發行者:Python Tkinter 程式

    • 主題alex9ufo/mqtt/ex1/led

    • 指令內容:根據按下不同的按鈕,發送 onoffflashtimer

  • 動作:訂閱 (Subscribe)

    • 訂閱者:ESP32 (Client 3)

    • 行為邏輯

      • ESP32 一直在監聽 .../led 這個主題

      • 一旦收到 on,ESP32 的程式碼會將 GPIO 2 輸出高電位,點亮 LED

      • 一旦收到 timer,ESP32 啟動內部計時器,5 秒後自動熄滅 LED


4. 運作特性總結

  1. 解耦性:ESP32 並不需要知道是誰在控制它(可能是 Python 程式,也可能是 MQTTBOX),它只需要訂閱正確的主題並對指令做出反應

  2. 多對多:如果同時有五台電腦執行 Python 程式並訂閱了溫濕度主題,這五台電腦都會同時看到最新的數據

  3. 即時性:MQTT 是一種極輕量化的協定,非常適合像 ESP32 這種記憶體有限的嵌入式設備,能達成毫秒等級的指令反應

// --- 硬體接線設定 ---
#define DHTPIN 13
#define DHTTYPE DHT22
#define LED_PIN 2
#define SDA_PIN 21
#define SCL_PIN 22

// --- WiFi 與 MQTT 設定 ---
const char* ssid = "Wokwi-GUEST";
const char* password = "";
const char* mqtt_server = "mqttgo.io";
const char* topicTemp = "alex9ufo/mqtt/ex1/Temp";
const char* topicHumi = "alex9ufo/mqtt/ex1/Humi";
const char* topicLED = "alex9ufo/mqtt/ex1/led";

在物聯網開發中,選擇一個穩定且免費的 MQTT Broker 進行測試是非常重要的。

根據你的架構圖,你目前使用的是 MQTTGO (NMking Technology) ,這是一個非常適合在地化測試的選擇。

除了 MQTTGO 之外,以下是全球開發者社群中最常用且穩定的免費公共 MQTT Broker:


1. HiveMQ Public Broker

這是目前最受歡迎的公共測試伺服器之一,擁有非常友善的線上查看工具。

  • 伺服器位址: broker.hivemq.com

  • 連接埠: 1883 (TCP), 8000 (WebSockets)

  • 優點: 提供線上網頁客戶端,不需要安裝軟體就能檢查訊息是否發行成功。

2. Mosquitto (test.mosquitto.org)

由 Eclipse 基金會維護,是 MQTT 協定的官方參考實作。

  • 伺服器位址: test.mosquitto.org

  • 連接埠: 1883 (不加密), 8883 (SSL 加密)

  • 優點: 支援多種安全連線模式(TLS/SSL),適合需要測試加密連線的專案。

3. EMQX Public Broker

EMQX 是目前效能極高的分散式 MQTT Broker。

  • 伺服器位址: broker.emqx.io

  • 連接埠: 1883, 8083 (WebSockets)

  • 優點: 延遲極低,全球擁有多個節點,穩定性高。

4. Shiftr.io

這是一個非常特殊的視覺化 Broker。

  • 特性: 它可以將你所有連接的設備與主題(Topic)以互動式地圖的方式呈現,非常直觀。

  • 免費額度: 提供一定數量的免費連接數,適合教學與展示。


公共 Broker 使用注意事項 (重要)

在使用這些免費伺服器時,請務必遵守以下規範:

  1. 隱私風險: 公共 Broker 是完全公開的。任何人只要訂閱 # (通配符),就能看到你發送的所有數據 。請勿傳送機密資訊(如真實密碼、個人住址)。

  2. 主題唯一性: 為了避免跟別人的專案衝突,建議你的主題名稱要包含獨特的 ID,例如像你現在使用的 alex9ufo/...

  3. 不保證持久性: 免費伺服器隨時可能重啟或清除數據,不建議用於商業或需要 24/7 運作的正式環境。

總結建議

如果你在台灣開發,MQTTGO 的連線速度通常很理想。如果你需要更多的診斷工具或 Web 連線測試,HiveMQEMQX 是首選。


















WOKWI程式

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

// --- 硬體接線設定 ---
#define DHTPIN 13
#define DHTTYPE DHT22
#define LED_PIN 2
#define SDA_PIN 21
#define SCL_PIN 22

// --- WiFi 與 MQTT 設定 ---
const char* ssid = "Wokwi-GUEST";
const char* password = "";
const char* mqtt_server = "mqttgo.io";
const char* topicTemp = "alex9ufo/mqtt/ex1/Temp";
const char* topicHumi = "alex9ufo/mqtt/ex1/Humi";
const char* topicLED = "alex9ufo/mqtt/ex1/led";


// --- 全域變數 ---
String ledMode = "off";
unsigned long lastMsg = 0;
unsigned long myTimerStart = 0; // 修正後的變數名稱,避開系統關鍵字
bool timerActive = false;
unsigned long lastFlash = 0;
bool flashState = false;

DHT dht(DHTPIN, DHTTYPE);
LiquidCrystal_I2C lcd(0x27, 16, 2); // 若沒畫面,請嘗試 0x3F 0x27
WiFiClient espClient;
PubSubClient client(espClient);

// --- 函數定義 ---

void setup_wifi() {
  delay(10);
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

// 接收 MQTT 訊息的回傳函數
void callback(char* topic, byte* payload, unsigned int length) {
  String message = "";
  for (int i = 0; i < length; i++) {
    message += (char)payload[i];
  }
 
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  Serial.println(message);

  if (String(topic) == topicLED) {
    ledMode = message;
    timerActive = false; // 重置計時器狀態
   
    if (ledMode == "on") {
      digitalWrite(LED_PIN, HIGH);
    } else if (ledMode == "off") {
      digitalWrite(LED_PIN, LOW);
    } else if (ledMode == "timer") {
      digitalWrite(LED_PIN, HIGH);
      myTimerStart = millis(); // 記錄啟動時間
      timerActive = true;
    } else if (ledMode == "flash") {
      // flash 邏輯會在 loop 中處理
    }
  }
}

void reconnect() {
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // 建立隨機 Client ID
    String clientId = "ESP32Client-";
    clientId += String(random(0xffff), HEX);
   
    if (client.connect(clientId.c_str())) {
      Serial.println("connected");
      client.subscribe(topicLED); // 訂閱 LED 控制主題
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      delay(5000);
    }
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);
 
  // 初始化 I2C 與 LCD
  Wire.begin(SDA_PIN, SCL_PIN);
  lcd.init();
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print("MQTT Loading...");

  dht.begin();
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
}

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

  // --- LED 模式處理邏輯 ---
  if (ledMode == "flash") {
    // 每 0.5 秒 (500ms) 切換一次狀態
    if (millis() - lastFlash >= 500) {
      lastFlash = millis();
      flashState = !flashState;
      digitalWrite(LED_PIN, flashState);
    }
  }
  else if (ledMode == "timer" && timerActive) {
    // 亮 5 秒後自動熄滅
    if (millis() - myTimerStart >= 5000) {
      digitalWrite(LED_PIN, LOW);
      timerActive = false;
      ledMode = "off";
      Serial.println("Timer finished: LED OFF");
    }
  }

  // --- 溫濕度讀取與發布 (每 5 秒更新一次) ---
  unsigned long now = millis();
  if (now - lastMsg > 5000) {
    lastMsg = now;
    float h = dht.readHumidity();
    float t = dht.readTemperature();

    if (isnan(h) || isnan(t)) {
      Serial.println("Failed to read from DHT sensor!");
      lcd.setCursor(0, 0);
      lcd.print("Sensor Error   ");
      return;
    }

    // 發布到 MQTT Broker
    client.publish(topicTemp, String(t).c_str());
    client.publish(topicHumi, String(h).c_str());

    // 顯示於 LCD 1602
    lcd.setCursor(0, 0);
    lcd.print("Temp: ");
    lcd.print(t, 1);
    lcd.print(" C   "); // 多空格用於清除舊殘影
   
    lcd.setCursor(0, 1);
    lcd.print("Humi: ");
    lcd.print(h, 1);
    lcd.print(" %   ");

    Serial.printf("Published - T: %.1f, H: %.1f\n", t, h);
  }
}

程式碼是一個完整的 IoT 智慧溫濕度監控系統。它結合了硬體感測(DHT22)、在地顯示(LCD 1602)以及遠端雲端控制(MQTT)。

以下我為你將這段程式碼拆解為四大核心模組進行詳細說明:


1. 網路連線模組 (WiFi & MQTT)

這部分負責讓 ESP32 能夠「上網」並與 mqttgo.io 伺服器溝通。

  • setup_wifi(): 這是標準的 WiFi 連線流程。它會不斷檢查狀態直到連上為止。在 Wokwi 模擬器中,Wokwi-GUEST 是免密碼的。

  • reconnect(): 這是 MQTT 的生命線。如果網路斷掉或伺服器失聯,它會自動嘗試重新連線,並在成功後重新訂閱 LED 控制的主題 (alex9ufo/mqtt/ex1/led)。

  • clientId: 程式碼中使用了隨機生成的 ID,這是為了避免多人同時使用同一個伺服器時產生踢人斷線的問題。

2. 感測與發布模組 (DHT22 & LCD)

這部分負責「採集數據」並「對外展示」。

  • 每 5 秒更新一次: 透過 millis() - lastMsg > 5000 判斷時間,這比使用 delay(5000) 好,因為它不會讓系統卡死,讓 MQTT 能持續接收指令。

  • 數據發布: 讀取到的溫度與濕度會分別轉換成字串,推送到 .../Temp.../Humi 兩個主題。

  • 在地顯示: lcd.print 會同步更新資訊到 16x2 的螢幕上。

3. 遠端指令處理 (MQTT Callback)

當你從手機或電腦發送訊息到 alex9ufo/mqtt/ex1/led 時,callback() 函數會被觸發。

  • 它會根據接收到的內容 (on, off, timer, flash) 來改變 ledMode 變數。

  • 這就像是 ESP32 的耳目,隨時監聽遠端的命令。

4. LED 行為邏輯 (核心控制)

這是在 loop() 中根據 ledMode 執行的具體動作: | 模式 | 行為邏輯 | | :--- | :--- | | on | 恆亮(在 callback 中直接設定)。 | | off | 恆滅(在 callback 中直接設定)。 | | flash | 透過 millis() 計算,每 500ms 切換一次電位,達成閃爍效果。 | | timer | 收到指令時亮起,並記錄當下時間,滿 5 秒後程式會自動將其設為 off。 |


Python 程式
import tkinter as tk import paho.mqtt.client as mqtt import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg import matplotlib # --- 解決中文字型問題 --- # 設定 Matplotlib 支援中文 (微軟正黑體) matplotlib.rcParams['font.family'] = ['Microsoft JhengHei', 'sans-serif'] matplotlib.rcParams['axes.unicode_minus'] = False # 解決負號顯示問題 # --- 設定參數 --- MQTT_SERVER = "mqttgo.io" TOPIC_TEMP = "alex9ufo/mqtt/ex1/Temp" TOPIC_HUMI = "alex9ufo/mqtt/ex1/Humi" TOPIC_LED = "alex9ufo/mqtt/ex1/led" class MQTTApp: def __init__(self, root): self.root = root self.root.title("ESP32 MQTT 控制面板 (DHT22 全量程版)") self.root.geometry("600x650") self.temp_val = 0.0 self.humi_val = 0.0 self.setup_ui() self.setup_mqtt() def setup_ui(self): # --- 連線狀態指示燈 --- status_frame = tk.Frame(self.root) status_frame.pack(pady=10) tk.Label(status_frame, text="連線狀態: ").pack(side=tk.LEFT) self.canvas_status = tk.Canvas(status_frame, width=20, height=20) self.canvas_status.pack(side=tk.LEFT) self.status_light = self.canvas_status.create_oval(2, 2, 18, 18, fill="red") # --- LED 控制按鈕 --- ctrl_frame = tk.LabelFrame(self.root, text="LED 遠端控制", padx=10, pady=10) ctrl_frame.pack(pady=10, fill="x", padx=20) btns = [ ("亮燈 (ON)", "on"), ("滅燈 (OFF)", "off"), ("閃爍 (Flash)", "flash"), ("計時 (Timer 5s)", "timer") ] for text, cmd in btns: btn = tk.Button(ctrl_frame, text=text, width=15, command=lambda c=cmd: self.publish_led(c)) btn.pack(side=tk.LEFT, padx=5, expand=True) # --- 圖表區 --- self.fig, (self.ax1, self.ax2) = plt.subplots(1, 2, figsize=(6, 3)) self.canvas_plt = FigureCanvasTkAgg(self.fig, master=self.root) self.canvas_plt.get_tk_widget().pack(pady=20) self.update_charts() def setup_mqtt(self): self.client = mqtt.Client(callback_api_version=mqtt.CallbackAPIVersion.VERSION2) self.client.on_connect = self.on_connect self.client.on_message = self.on_message self.client.on_disconnect = self.on_disconnect try: self.client.connect(MQTT_SERVER, 1883, 60) self.client.loop_start() except Exception as e: print(f"無法連接 MQTT: {e}") def on_connect(self, client, userdata, flags, rc, properties=None): if rc == 0: print("MQTT 連線成功") self.canvas_status.itemconfig(self.status_light, fill="green") self.client.subscribe([(TOPIC_TEMP, 0), (TOPIC_HUMI, 0)]) else: self.canvas_status.itemconfig(self.status_light, fill="red") def on_disconnect(self, client, userdata, disconnect_flags, rc, properties=None): print("MQTT 斷開連線") self.canvas_status.itemconfig(self.status_light, fill="red") def on_message(self, client, userdata, msg): try: payload = msg.payload.decode() if msg.topic == TOPIC_TEMP: self.temp_val = float(payload) elif msg.topic == TOPIC_HUMI: self.humi_val = float(payload) self.root.after(0, self.update_charts) except Exception as e: print(f"處理訊息錯誤: {e}") def publish_led(self, command): self.client.publish(TOPIC_LED, command) print(f"已發送指令: {command}") def update_charts(self): self.ax1.clear() self.ax2.clear() # --- 1. 溫度圓餅圖 (-40 到 +80) --- # 總範圍 = 80 - (-40) = 120 # 將數值平移:若溫度是 -40,平移後為 0;若溫度是 80,平移後為 120 temp_offset = self.temp_val + 40 temp_clipped = max(0, min(120, temp_offset)) # 限制在 0~120 避免繪圖錯誤 # 繪製圓餅圖 (數值, 剩餘空間) self.ax1.pie([temp_clipped, 120 - temp_clipped], labels=["溫度", ""], colors=["#ff9999", "#eeeeee"], startangle=90, counterclock=False) self.ax1.set_title(f"溫度範圍: -40~+80\n當前: {self.temp_val}°C") # --- 2. 濕度圓餅圖 (0 到 100) --- humi_clipped = max(0, min(100, self.humi_val)) # 限制在 0~100 之間 self.ax2.pie([humi_clipped, 100 - humi_clipped], labels=["濕度", ""], colors=["#66b3ff", "#eeeeee"], startangle=90, counterclock=False) self.ax2.set_title(f"濕度範圍: 0~100\n當前: {self.humi_val}%") self.canvas_plt.draw() if __name__ == "__main__": root = tk.Tk() app = MQTTApp(root) root.mainloop()




Python 程式碼是一個結合了 GUI(Tkinter)、數據視覺化(Matplotlib)與物聯網通訊(MQTT)的完整客戶端。

以下是逐行詳細說明:


1. 匯入庫與環境設定

Python
import tkinter as tk                             # 建立視窗介面的標準庫
import paho.mqtt.client as mqtt                  # MQTT 通訊協定庫
import matplotlib.pyplot as plt                 # 繪圖工具
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg # 將圖表嵌入 Tkinter 的工具
import matplotlib
  • 這部分載入了所有必要的工具,特別是 FigureCanvasTkAgg,它是連接「圖表」與「視窗視窗」的橋樑。

2. 中文字型補丁

Python
matplotlib.rcParams['font.family'] = ['Microsoft JhengHei', 'sans-serif']
matplotlib.rcParams['axes.unicode_minus'] = False 
  • 第 1 行:指定 Matplotlib 使用「微軟正黑體」,否則圖表中的中文會變「豆腐塊」。

  • 第 2 行:確保座標軸上的負號 (-) 能正常顯示。

3. 常數設定

Python
MQTT_SERVER = "mqttgo.io"
TOPIC_TEMP = "alex9ufo/mqtt/ex1/Temp"
TOPIC_HUMI = "alex9ufo/mqtt/ex1/Humi"
TOPIC_LED = "alex9ufo/mqtt/ex1/led"
  • 定義了 MQTT 伺服器位址與三個通訊主題(訂閱溫濕度、發布 LED 指令)。

4. 類別初始化 __init__

Python
class MQTTApp:
    def __init__(self, root):
        self.root = root
        self.root.title("ESP32 MQTT 控制面板 (2026 版)")
        self.root.geometry("600x650")

        self.temp_val = 0.0  # 儲存目前的溫度值
        self.humi_val = 0.0  # 儲存目前的濕度值

        self.setup_ui()    # 初始化介面佈局
        self.setup_mqtt()  # 初始化通訊連線
  • 這是程式的起點,設定視窗大小並準備存放數據的變數。

5. 建立 UI 介面 setup_ui

Python
    def setup_ui(self):
        # --- 連線狀態指示燈 ---
        status_frame = tk.Frame(self.root)
        status_frame.pack(pady=10)
        tk.Label(status_frame, text="連線狀態: ").pack(side=tk.LEFT)
        self.canvas_status = tk.Canvas(status_frame, width=20, height=20)
        self.canvas_status.pack(side=tk.LEFT)
        self.status_light = self.canvas_status.create_oval(2, 2, 18, 18, fill="red")
  • 建立一個圓形指示燈(預設紅色),代表 MQTT 是否連線成功。

Python
        # --- LED 控制按鈕 ---
        ctrl_frame = tk.LabelFrame(self.root, text="LED 遠端控制", padx=10, pady=10)
        ctrl_frame.pack(pady=10, fill="x", padx=20)

        btns = [ ("亮燈 (ON)", "on"), ("滅燈 (OFF)", "off"), ... ]

        for text, cmd in btns:
            btn = tk.Button(ctrl_frame, text=text, ..., 
                            command=lambda c=cmd: self.publish_led(c))
            btn.pack(side=tk.LEFT, padx=5, expand=True)
  • 利用迴圈產生四個按鈕。這裡用了 lambda 技巧,讓不同按鈕被按下時,會帶入不同的指令字串(如 "on" 或 "flash")到發送函數。

Python
        # --- 圖表區 ---
        self.fig, (self.ax1, self.ax2) = plt.subplots(1, 2, figsize=(6, 3))
        self.canvas_plt = FigureCanvasTkAgg(self.fig, master=self.root)
        self.canvas_plt.get_tk_widget().pack(pady=20)
  • 建立一個包含左右兩個子圖的畫布,並將其置於視窗中心。

6. MQTT 連線邏輯 setup_mqtt

Python
    def setup_mqtt(self):
        self.client = mqtt.Client(callback_api_version=mqtt.CallbackAPIVersion.VERSION2)
        self.client.on_connect = self.on_connect
        self.client.on_message = self.on_message
        self.client.on_disconnect = self.on_disconnect

        self.client.connect(MQTT_SERVER, 1883, 60)
        self.client.loop_start() # 開啟背景線程持續監聽
  • 設定回呼函數(Callback)。loop_start() 非常關鍵,它讓 MQTT 在背景運作,不會卡住視窗的滑鼠點擊動作。

7. 事件處理函數

  • on_connect: 當連上伺服器時,指示燈轉為綠色,並訂閱溫濕度主題。

  • on_message: 當 ESP32 回傳數據時,解析數值,並呼叫 root.after(0, self.update_charts)

    注意:這裡使用 after 是為了確保繪圖動作是在視窗的主執行緒中完成,避免程式崩潰。

  • publish_led: 呼叫 client.publish() 將按鈕對應的字串送往 MQTT Broker。

8. 繪圖更新 update_charts

Python
    def update_charts(self):
        self.ax1.clear() # 清除舊圖
        # ... 繪製 Pie 圖 ...
        self.ax1.pie([self.temp_val, max(0.1, 50-self.temp_val)], ...)
        # ...
        self.canvas_plt.draw() # 刷新畫布顯示
  • 這是視覺化的核心。我們用 50-當前溫度100-當前濕度 來算出圓餅圖的剩餘比例。max(0.1, ...) 是為了防止數據超過範圍導致繪圖錯誤。


9. 主程式進入點

Python
if __name__ == "__main__":
    root = tk.Tk()      # 初始化視窗
    app = MQTTApp(root) # 啟動我們的程式邏輯
    root.mainloop()     # 保持視窗運行

這份程式展示了現代 IoT 應用的標準架構:硬體採集 (ESP32) -> 雲端中轉 (MQTT) -> 桌面監控 (Python)

沒有留言:

張貼留言

MQTT 基本觀念與實驗(1)

MQTT 基本觀念與實驗 (1) 一個基於 ESP32 的物聯網系統架構,描述了硬體端、雲端代理伺服器(Broker)以及多個客戶端(Clients)之間的互動關係 。 以下是根據圖片內容對該系統關係的詳細說明: 1. 硬體終端 (CLIENT 3 / ESP32) 這是系統的實...