2026 作業1MQTT基本觀念
作業 參考下面網址
https://alex9ufoexploer.blogspot.com/2025/02/1-mqtt-relay-dht22-mqtt-box-pc-mymqtt.html
https://alex9ufoexploer.blogspot.com/2026/03/mqtt.html
WOKWI Arduino程式
程式碼利用了 ESP32 的雙核心 (Dual-core) 特性與 FreeRTOS 作業系統,將「網路指令接收與顯示」與「數據發送」拆分處理,確保系統運行流暢。
以下是程式碼的逐行詳細說明:
1. 引入庫與硬體定義
#include <WiFi.h> // 用於 ESP32 的 Wi-Fi 連線
#include <PubSubClient.h> // MQTT 通訊協定庫
#include <Adafruit_Sensor.h> // 感測器統一驅動框架
#include <DHT.h> // DHT 感測器庫
#include <DHT_U.h> // DHT 統一介面庫
#include <Wire.h> // I2C 通訊庫
#include <LiquidCrystal_I2C.h> // 1602 LCD I2C 驅動庫
#include <freertos/FreeRTOS.h> // FreeRTOS 核心
#include <freertos/task.h> // FreeRTOS 多工任務管理
這部分載入所有必要的工具套件,包含網路、MQTT、感測器以及多工處理所需的庫。
#define DHTPIN 4 // DHT22 訊號線接到 GPIO 4
#define DHTTYPE DHT22 // 定義感測器型號為 DHT22
#define SDA_PIN 21 // I2C 數據線
#define SCL_PIN 22 // I2C 時脈線
DHT_Unified dht(DHTPIN, DHTTYPE); // 初始化 DHT 物件
LiquidCrystal_I2C lcd(0x27, 16, 2); // 初始化 LCD (位址 0x27, 16欄2列)
const int ledPins[] = {13, 12, 14, 27}; // 定義 4 顆 LED 的腳位
2. MQTT 與全域變數
const char* led_control_topic = "alex9ufo/ledcontrol"; // 訂閱指令的主題
const char* led_status_topic = "alex9ufo/ledstatus"; // 回報 LED 狀態的主題
const char* temp_humi_topic = "alex9ufo/temphumi"; // 發布溫濕度數據的主題
QueueHandle_t mqttPublishQueue; // 定義一個「佇列」,用於在兩個核心之間傳遞要發送的消息
volatile bool dhtReadTriggered = false; // 旗標:通知 Core 1 該發送感測數據了
volatile float lastTemperature = 0.0; // 儲存最新的溫度值
volatile float lastHumidity = 0.0; // 儲存最新的濕度值
Queue (佇列):這是跨核心通訊的橋樑,Core 0 把訊息「丟進去」,Core 1 從「另一頭拿出來」發送。
3. 功能函式 (Helpers)
mqttSubscribeCallback (處理收到的指令)
當 MQTT 經紀人收到發往 ledcontrol 的訊息時會觸發此函式:
解析指令:判斷是
1on,1off... 到alloff。執行硬體動作:使用
digitalWrite切換 LED。準備回饋訊息:將執行結果字串放入
mqttPublishQueue佇列,交由另一個核心發送回雲端。
readDHT22 (讀取感測器與更新 LCD)
讀取數值:獲取溫度與濕度。
更新旗標:設定
dhtReadTriggered = true。LCD 刷新:將數據即時顯示在 1602 螢幕上。
4. FreeRTOS 多工任務 (核心邏輯)
core0Task:運行在 Core 0 (管理員任務)
void core0Task(void * parameter) {
// 1. 設定 MQTT 回調
// 2. 無限迴圈處理:
// - 保持 MQTT 連線 (reconnect)
// - 處理訂閱消息 (client.loop)
// - 每 10 秒自動呼叫 readDHT22()
// - 監聽序列埠,按下 Enter 手動觸發讀取
}
這個任務專門負責「輸入」與「本地顯示」,確保網路反應快速。
core1Task:運行在 Core 1 (通訊員任務)
void core1Task(void * parameter) {
// 無限迴圈處理:
// 1. 檢查佇列 (Queue):若有 LED 狀態訊息,就 publish 出去。
// 2. 檢查旗標 (dhtReadTriggered):若為 true,就把溫濕度發布出去。
}
這個任務專門負責「輸出」,避免頻繁的網路發送動作卡住主程式的硬體反應。
5. 初始化與主循環
void setup() {
Wire.begin(SDA_PIN, SCL_PIN); // 啟動 I2C
lcd.init(); // 初始化 LCD
lcd.backlight(); // 開啟背光
dht.begin(); // 啟動 DHT
setup_wifi(); // 連線 Wi-Fi
// 創建佇列:長度 10,每個元素 50 字節
mqttPublishQueue = xQueueCreate(10, sizeof(char[50]));
// 任務指派:指定 Core 0 執行訂閱與顯示,Core 1 執行數據發送
xTaskCreatePinnedToCore(core0Task, "Core0Task", 10000, NULL, 1, NULL, 0);
xTaskCreatePinnedToCore(core1Task, "Core1Task", 10000, NULL, 1, NULL, 1);
}
void loop() {
vTaskDelete(NULL); // 刪除 loop 任務,因為所有工作都交給了上述兩個 Task
}
總結運作流程:
計時器觸發:Core 0 發現 10 秒到了,讀取 DHT22 並更新 LCD。
跨核通知:Core 0 更新全域變數並舉起「旗標」。
雲端發送:Core 1 偵測到旗標,把溫濕度字串透過 MQTT 發送到
temphumi主題。指令控制:若你從手機發送
1on,Core 0 接收後點亮 LED,並把訊息塞進「佇列」,Core 1 隨後將狀態報回雲端。
Python 程式結合了 Tkinter (圖形化介面) 與 Paho-MQTT (通訊協定),並使用 Threading (多執行緒) 確保介面流暢。以下是逐行詳細說明:
1. 匯入模組
import tkinter as tk # 建立 GUI 視窗的核心庫
from tkinter import ttk, messagebox # 使用更美觀的按鈕元件與對話框
import paho.mqtt.client as mqtt # MQTT 通訊協定庫
import threading # 用於同時執行背景連線與前端介面
2. MQTT 與主題設定
MQTT_BROKER = "mqttgo.io" # MQTT 伺服器網址
MQTT_PORT = 1883 # MQTT 通訊埠 (標準為 1883)
TOPIC_CONTROL = "alex9ufo/ledcontrol" # 發送指令的主題 (Python -> ESP32)
TOPIC_STATUS = "alex9ufo/ledstatus" # 訂閱 LED 狀態的主題 (ESP32 -> Python)
TOPIC_SENSOR = "alex9ufo/temphumi" # 訂閱感測器數據的主題 (ESP32 -> Python)
3. 初始化介面 (__init__)
class MQTTApp:
def __init__(self, root):
self.root = root
self.root.title("ESP32 雙核監控中心")
self.root.geometry("400x500") # 設定視窗大小
self.root.configure(bg="#f0f0f0") # 設定背景顏色
# --- 溫濕度顯示區 ---
# 使用 StringVar 綁定變數,當變數改變時,介面上的文字會自動更新
self.temp_var = tk.StringVar(value="-- °C")
self.humi_var = tk.StringVar(value="-- %")
# (中間省略標籤佈局:使用 grid 將標籤排列在 sensor_frame 中)
# --- LED 控制按鈕 (動態產生) ---
for i in range(1, 5):
# 建立開啟按鈕,使用 lambda 傳入特定參數 (如 1on, 2on)
btn_on = ttk.Button(control_frame, text=f"開啟 LED {i}",
command=lambda idx=i: self.send_command(f"{idx}on"))
# 建立關閉按鈕 (如 1off, 2off)
btn_off = ttk.Button(control_frame, text=f"關閉 LED {i}",
command=lambda idx=i: self.send_command(f"{idx}off"))
4. 多執行緒與連線 (start_mqtt)
# 啟動 MQTT 執行緒
# 因為 loop_forever() 會卡死程式,必須放在 daemon 執行緒中背景執行
mqtt_thread = threading.Thread(target=self.start_mqtt, daemon=True)
mqtt_thread.start()
def start_mqtt(self):
self.client.connect(MQTT_BROKER, MQTT_PORT, 60)
self.client.loop_forever() # 持續監聽訊息
5. MQTT 事件回調
on_connect:連線成功後觸發。此時向伺服器訂閱(Subscribe)TOPIC_STATUS與TOPIC_SENSOR,這樣才能接收到來自 ESP32 的訊息。on_message(核心解析邏輯):Pythondef on_message(self, client, userdata, msg): payload = msg.payload.decode() # 將接收到的二進位數據轉為字串 if msg.topic == TOPIC_SENSOR: # 解析格式 "Temperature: 25.0C, Humidity: 60.0%" parts = payload.split(", ") temp = parts[0].split(": ")[1] # 取得 25.0C humi = parts[1].split(": ")[1] # 取得 60.0% self.temp_var.set(temp) # 更新介面溫度文字 self.humi_var.set(humi) # 更新介面濕度文字
6. 指令發送 (send_command)
def send_command(self, cmd):
# 當按鈕被按下時,將指令 (如 "allon") 發送到 alex9ufo/ledcontrol
self.client.publish(TOPIC_CONTROL, cmd)
程式運作邏輯總結:
背景任務:
start_mqtt在背景不斷檢查是否有新訊息。接收訊息:當 ESP32 每 10 秒發出溫濕度時,
on_message會被觸發,拆解字串後,透過StringVar自動刷新 螢幕上的數據。發送指令:當你點擊介面按鈕,Python 會立刻 Publish 訊息到雲端,ESP32 收到後會切換繼電器並控制 LED。






沒有留言:
張貼留言