2025年12月24日 星期三

WOKWI ESP32 DHT22 溫度濕度 上下限設定 (MQTT 工具)

 WOKWI ESP32  DHT22 溫度濕度 上下限設定









  • 溫度下限值:現在與「繼電器 1」處於 row 2

  • 溫度上限值:現在與「繼電器 3」處於 row 4

  • 濕度下限值:現在與「繼電器 4」處於 row 6

  • 濕度上限值:現在與「繼電器 6」處於 row 8

  • 字體顏色:當繼電器狀態切換為 ON 時,對應的 OFF/ON 字樣會變成紅色


  • MQTT BOX





    MQTTGO.io


    Python+TKinter


    ESP32 結合 MQTT 範例主要是實現物聯網數據的發布 (Publish) 和訂閱 (Subscribe),例如透過 Arduino IDE 寫程式讓 ESP32 連上 WiFi,再連接 EMQX 提供的公開測試 MQTT broker, 範例包括讀取溫濕度感測器 (如 DHT11/DHT22) 將數據發布到特定主題 (Topic),或接收控制指令並控制 LED RELAY 等,關鍵在於使用 PubSubClient 函式庫與 ESP-IDF 的 ESP-MQTT 範例來處理 TCP/SSL/WebSocket 連線和消息收發。 
    核心概念
    • MQTT Broker: 訊息的中介站 (例如 test.mosquitto.org)。
    • Topic: 訊息的分類主題 (例如 home/livingroom/temperature)。
    • Publish (發布者): 將感測器數據發送到 Topic。
    • Subscribe (訂閱者): 訂閱 Topic 接收消息。 






    Python  程式  與  Wokwi Arduino IDE 程式

    import tkinter as tk

    from tkinter import font

    import paho.mqtt.client as mqtt


    # --- MQTT 配置 ---

    BROKER = "mqttgo.io"

    PORT = 1883

    TOPICS = {

        "temp": "alex9ufo/dht22/temp",

        "humi": "alex9ufo/dht22/humi",

    }

    RELAY_TOPICS = [

        "alex9ufo/relay1", "alex9ufo/relay2", "alex9ufo/relay3",

        "alex9ufo/relay4", "alex9ufo/relay5", "alex9ufo/relay6"

    ]


    class MQTTApp:

        def __init__(self, root):

            self.root = root

            self.root.title("ESP32 監控系統")

            self.root.geometry("700x500")

            

            self.label_font = font.Font(size=12)

            self.val_font = font.Font(size=12, weight="bold")


            # 變數初始化

            self.current_temp = tk.StringVar(value="--")

            self.current_humi = tk.StringVar(value="--")

            self.mqtt_status = tk.StringVar(value="斷線")

            self.relay_status = [tk.StringVar(value="OFF") for _ in range(6)]

            self.relay_labels = [] 


            self.setup_ui()


            self.client = mqtt.Client()

            self.client.on_connect = self.on_connect

            self.client.on_message = self.on_message

            

            try:

                self.client.connect(BROKER, PORT, 60)

                self.client.loop_start()

            except:

                self.mqtt_status.set("連線失敗")


        def setup_ui(self):

            # 設定整體網格權重

            for i in range(12): self.root.grid_rowconfigure(i, weight=1)

            for i in range(4): self.root.grid_columnconfigure(i, weight=1)


            # --- 第一行:目前溫濕度 ---

            tk.Label(self.root, text="目前溫度:", font=self.label_font).grid(row=0, column=0, sticky="e")

            tk.Label(self.root, textvariable=self.current_temp, font=self.val_font, fg="blue", relief="sunken", width=12).grid(row=0, column=1, padx=5)

            

            tk.Label(self.root, text="目前濕度:", font=self.label_font).grid(row=0, column=2, sticky="e")

            tk.Label(self.root, textvariable=self.current_humi, font=self.val_font, fg="blue", relief="sunken", width=12).grid(row=0, column=3, padx=5)


            # --- 繼電器與設定值佈局 (依圖片位置) ---

            pins = ["23", "22", "21", "19", "18", "5"]

            

            # 1. 繼電器1 (23) 與 溫度下限值 (Row 2)

            tk.Label(self.root, text=f"繼電器1 ({pins[0]})狀態:", font=self.label_font).grid(row=2, column=0, sticky="e")

            lbl1 = tk.Label(self.root, textvariable=self.relay_status[0], font=self.val_font, relief="sunken", width=12)

            lbl1.grid(row=2, column=1, padx=5); self.relay_labels.append(lbl1)

            

            tk.Label(self.root, text="溫度下限值:", font=self.label_font).grid(row=2, column=2, sticky="e")

            self.ent_t_low = tk.Entry(self.root, width=15); self.ent_t_low.insert(0, "20"); self.ent_t_low.grid(row=2, column=3)


            # 2. 繼電器2 (22) (Row 3)

            tk.Label(self.root, text=f"繼電器2 ({pins[1]})狀態:", font=self.label_font).grid(row=3, column=0, sticky="e")

            lbl2 = tk.Label(self.root, textvariable=self.relay_status[1], font=self.val_font, relief="sunken", width=12)

            lbl2.grid(row=3, column=1, padx=5); self.relay_labels.append(lbl2)


            # 3. 繼電器3 (21) 與 溫度上限值 (Row 4)

            tk.Label(self.root, text=f"繼電器3 ({pins[2]})狀態:", font=self.label_font).grid(row=4, column=0, sticky="e")

            lbl3 = tk.Label(self.root, textvariable=self.relay_status[2], font=self.val_font, relief="sunken", width=12)

            lbl3.grid(row=4, column=1, padx=5); self.relay_labels.append(lbl3)

            

            tk.Label(self.root, text="溫度上限值:", font=self.label_font).grid(row=4, column=2, sticky="e")

            self.ent_t_high = tk.Entry(self.root, width=15); self.ent_t_high.insert(0, "30"); self.ent_t_high.grid(row=4, column=3)


            # --- 中間空行 (Row 5) ---


            # 4. 繼電器4 (19) 與 濕度下限值 (Row 6)

            tk.Label(self.root, text=f"繼電器4 ({pins[3]})狀態:", font=self.label_font).grid(row=6, column=0, sticky="e")

            lbl4 = tk.Label(self.root, textvariable=self.relay_status[3], font=self.val_font, relief="sunken", width=12)

            lbl4.grid(row=6, column=1, padx=5); self.relay_labels.append(lbl4)

            

            tk.Label(self.root, text="濕度下限值:", font=self.label_font).grid(row=6, column=2, sticky="e")

            self.ent_h_low = tk.Entry(self.root, width=15); self.ent_h_low.insert(0, "40"); self.ent_h_low.grid(row=6, column=3)


            # 5. 繼電器5 (18) (Row 7)

            tk.Label(self.root, text=f"繼電器5 ({pins[4]})狀態:", font=self.label_font).grid(row=7, column=0, sticky="e")

            lbl5 = tk.Label(self.root, textvariable=self.relay_status[4], font=self.val_font, relief="sunken", width=12)

            lbl5.grid(row=7, column=1, padx=5); self.relay_labels.append(lbl5)


            # 6. 繼電器6 (5) 與 濕度上限值 (Row 8)

            tk.Label(self.root, text=f"繼電器6 ({pins[5]})狀態:", font=self.label_font).grid(row=8, column=0, sticky="e")

            lbl6 = tk.Label(self.root, textvariable=self.relay_status[5], font=self.val_font, relief="sunken", width=12)

            lbl6.grid(row=8, column=1, padx=5); self.relay_labels.append(lbl6)

            

            tk.Label(self.root, text="濕度上限值:", font=self.label_font).grid(row=8, column=2, sticky="e")

            self.ent_h_high = tk.Entry(self.root, width=15); self.ent_h_high.insert(0, "70"); self.ent_h_high.grid(row=8, column=3)


            # --- 底部:MQTT 連線狀態 ---

            tk.Label(self.root, text="MQTT連線狀態:", font=self.label_font).grid(row=10, column=0, sticky="e")

            tk.Label(self.root, textvariable=self.mqtt_status, font=self.val_font, fg="green", relief="sunken", width=12).grid(row=10, column=1, padx=5)


        def on_connect(self, client, userdata, flags, rc):

            if rc == 0:

                self.mqtt_status.set("已連線")

                client.subscribe(TOPICS["temp"])

                client.subscribe(TOPICS["humi"])


        def on_message(self, client, userdata, msg):

            try:

                val = float(msg.payload.decode())

                if msg.topic == TOPICS["temp"]:

                    self.current_temp.set(f"{val} °C")

                    self.check_logic(val, "temp")

                elif msg.topic == TOPICS["humi"]:

                    self.current_humi.set(f"{val} %")

                    self.check_logic(val, "humi")

            except: pass


        def check_logic(self, current_val, mode):

            try:

                if mode == "temp":

                    low, high = float(self.ent_t_low.get()), float(self.ent_t_high.get())

                    indices = [0, 1, 2]

                else:

                    low, high = float(self.ent_h_low.get()), float(self.ent_h_high.get())

                    indices = [3, 4, 5]


                states = ["OFF", "OFF", "OFF"]

                if current_val < low: states[0] = "ON"

                elif current_val > high: states[2] = "ON"

                else: states[1] = "ON"


                for i, idx in enumerate(indices):

                    self.relay_status[idx].set(states[i])

                    # 狀態為 ON 時顯示紅色

                    self.relay_labels[idx].config(fg="red" if states[i] == "ON" else "black")

                    self.client.publish(RELAY_TOPICS[idx], states[i])

            except: pass


    if __name__ == "__main__":

        root = tk.Tk()

        app = MQTTApp(root)

        root.mainloop()



    WOKWI程式

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

    // --- 配置參數 ---
    const char* ssid = "Wokwi-GUEST";     // Wokwi 專用 WiFi
    const char* password = "";
    const char* mqtt_server = "mqttgo.io";

    // 定義接腳
    const int DHT_PIN = 15;
    const int RELAY_PINS[] = {23, 22, 21, 19, 18, 5};
    const char* RELAY_TOPICS[] = {
      "alex9ufo/relay1", "alex9ufo/relay2", "alex9ufo/relay3",
      "alex9ufo/relay4", "alex9ufo/relay5", "alex9ufo/relay6"
    };

    DHTesp dhtSensor;
    WiFiClient espClient;
    PubSubClient client(espClient);

    unsigned long lastMsg = 0;

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

      WiFi.mode(WIFI_STA);
      WiFi.begin(ssid, password);

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

    // 接收來自 Python (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);

      // 比對 Topic 並切換對應的繼電器接腳
      for (int i = 0; i < 6; i++) {
        if (String(topic) == RELAY_TOPICS[i]) {
          if (message == "ON") {
            digitalWrite(RELAY_PINS[i], HIGH);
          } else {
            digitalWrite(RELAY_PINS[i], LOW);
          }
        }
      }
    }

    void reconnect() {
      while (!client.connected()) {
        Serial.print("Attempting MQTT connection...");
        String clientId = "ESP32Client-" + String(random(0xffff), HEX);
        if (client.connect(clientId.c_str())) {
          Serial.println("connected");
          // 訂閱所有繼電器 Topic
          for (int i = 0; i < 6; i++) {
            client.subscribe(RELAY_TOPICS[i]);
          }
        } else {
          Serial.print("failed, rc=");
          Serial.print(client.state());
          Serial.println(" try again in 5 seconds");
          delay(5000);
        }
      }
    }

    void setup() {
      Serial.begin(115200);
     
      // 初始化繼電器接腳為輸出
      for (int i = 0; i < 6; i++) {
        pinMode(RELAY_PINS[i], OUTPUT);
        digitalWrite(RELAY_PINS[i], LOW); // 預設關閉
      }

      dhtSensor.setup(DHT_PIN, DHTesp::DHT22);
      setup_wifi();
      client.setServer(mqtt_server, 1883);
      client.setCallback(callback);
    }

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

      // 每 2 秒讀取一次溫濕度並上傳
      unsigned long now = millis();
      if (now - lastMsg > 2000) {
        lastMsg = now;
        TempAndHumidity data = dhtSensor.getTempAndHumidity();

        if (isnan(data.temperature) || isnan(data.humidity)) {
          Serial.println("Failed to read from DHT sensor!");
          return;
        }

        // 發布溫度
        client.publish("alex9ufo/dht22/temp", String(data.temperature, 2).c_str());
        // 發布濕度
        client.publish("alex9ufo/dht22/humi", String(data.humidity, 2).c_str());

        Serial.print("Temp: "); Serial.print(data.temperature);
        Serial.print("C, Humi: "); Serial.print(data.humidity); Serial.println("%");
      }
    }

    沒有留言:

    張貼留言

    WOKWI 模擬 MFRC522 RFID Reader + 5個 Tag 發行到MQTT 上

    WOKWI 模擬 MFRC522 RFID Reader + 5個 Tag  發行到MQTT 上 MQTTgo.io # MQTT 設定 MQTT_BROKER = "mqttgo.io" MQTT_PORT = 1883 MQTT_TOPIC = ...