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 是一個完全在雲端(瀏覽器)執行的微處理器模擬器,它的組譯與編譯(Compile)工作都是在後端的雲端伺服器上即時處理。
原則上,Wokwi 的伺服器架構相當彈性,隨時都可以使用。但如果從伺服器負載與網路延遲的角度來看,以下幾個時間點通常反應最快、最不容易因為伺服器忙碌而出現超時(Timeout)或組譯失敗:
1. 避開歐美日間尖峰(台灣時間:深夜至清晨)
Wokwi 的使用者遍布全球,特別是歐美的學校、創客與工程師。
最佳時間: 台灣時間 上午 06:00 到 中午 12:00,或者 深夜 23:00 以後。
原因: 這段時間剛好是美國的深夜/清晨,以及歐洲的凌晨,全球整體的伺服器使用量處於低谷,系統資源最充裕。
2. 避開台灣與亞洲的教學熱門時段
許多大專院校和高職的電子、資訊科系(例如物聯網、微處理器專題課程)會使用 Wokwi 進行線上教學與實作。
建議避開: 週一至週五的 上午 09:00 - 12:00、下午 13:30 - 16:30。
原因: 當多個班級同時上課、幾百名學生一起按下「執行/編譯」時,短時間內會造成伺服器排隊現象。
💡 排除組譯失敗的工程實用建議
如果您在編譯 ESP32 或 Arduino 專案時經常遇到失敗,通常不一定是時間問題,更常見的是以下幾個隱性原因。可以透過以下方式大幅提升組譯成功率:
檢查第三方函式庫(Libraries): Wokwi 載入過多或不相容的函式庫有時會導致編譯器卡死。建議檢查
diagram.json或pip相關設定,確認函式庫版本是否衝突。避免過大的全域陣列: 如果在程式碼中宣告了過大的靜態陣列(特別是記憶體較小的晶片),容易觸發編譯器的記憶體配置錯誤。
瀏覽器快取殘留: 有時是前端瀏覽器與後端 WebSocket 連線斷開。如果發現一直卡在 "Compiling...",直接 重新整理網頁(F5) 或複製程式碼開一個新的 Wokwi 專案視窗,通常就能立刻解決。
// --- MQTT 設定 ---const int mqtt_port = 1883;const char* mqtt_broker = "mqttgo.io";const char* led_control_topic = "alex9ufo/ledcontrol";const char* temp_humi_topic = "alex9ufo/temphumi";const char* led_status_topic = "alex9ufo/ledstatus";
Thonny 是一個專為初學者設計的 Python IDE。它本身並不是一個單一的程式庫(Library),但它內建了 Python 標準函式庫,並提供了一個直覺的圖形介面來安裝和管理第三方套件。🛠️ 如何在 Thonny 中安裝程式庫 (套件)Thonny 提供了一個稱為「管理套件」的簡單工具,讓您無需輸入繁瑣的終端機指令即可安裝庫:
點擊選單列的 工具 (Tools)。
- math: 提供數學運算(如
sqrt(),sin(),cos())。 - random: 用於產生隨機數。
- time / datetime: 處理時間與日期。
- tkinter: 用於開發簡易的圖形介面。
- turtle: 常見於教學,用於繪製圖形。
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 隨後將狀態報回雲端。
安裝 Thonny 非常簡單,它是一款專為 Python 初學者設計的整合開發環境(IDE),最大的優點是內建了 Python 環境,所以你不需要另外安裝 Python 就能直接開始寫程式。
以下是針對不同作業系統的安裝步驟:
## 1. 下載安裝檔
首先,請前往 Thonny 的官方網站:
官網地址:
thonny.org 在頁面右上角的 Windows、macOS 或 Linux 圖示上,點擊適合你系統的連結下載。
## 2. 各作業系統安裝流程
Windows 用戶
執行下載的
.exe安裝檔。選擇 "Install for me only" (僅為我安裝) 或 "Install for all users"(通常選第一個即可)。
持續點擊 Next (下一步)。
建議勾選 "Create desktop icon",方便之後從桌面開啟。
最後點擊 Install,完成後按 Finish。
Gemini 說了
在 Thonny 中管理與安裝 Python 套件(例如 requests, pandas 或 matplotlib)非常直覺,主要透過內建的圖形化介面完成。
以下是詳細的步驟:
在 Thonny 中管理與安裝 Python 套件(例如 requests, pandas 或 matplotlib)非常直覺,主要透過內建的圖形化介面完成。
以下是詳細的步驟:
## 1. 開啟套件管理員
啟動 Thonny 軟體。
在上方功能表點選 「工具」(Tools)」。
從下拉選單中選擇 「管理套件... (Manage packages...)」。
啟動 Thonny 軟體。
在上方功能表點選 「工具」(Tools)」。
從下拉選單中選擇 「管理套件... (Manage packages...)」。
## 2. 搜尋並安裝套件
在彈出的視窗中,你會看到一個搜尋框。
輸入套件名稱: 例如輸入 requests。
點擊 「從 PyPI 搜尋 (Search on PyPI)」 按鈕。
在搜尋結果中點選正確的套件名稱。
點擊右下角的 「安裝 (Install)」 按鈕。
等待進度條跑完,安裝成功後,該套件就會出現在左側的已安裝列表。
在彈出的視窗中,你會看到一個搜尋框。
輸入套件名稱: 例如輸入
requests。點擊 「從 PyPI 搜尋 (Search on PyPI)」 按鈕。
在搜尋結果中點選正確的套件名稱。
點擊右下角的 「安裝 (Install)」 按鈕。
等待進度條跑完,安裝成功後,該套件就會出現在左側的已安裝列表。
## 3. 如何確認安裝成功?
安裝完成後,你可以直接在 Thonny 下方的 「指令 shell (Shell)」 視窗輸入以下程式碼測試:
Pythonimport requests
print("安裝成功!")
如果沒有出現紅色錯誤訊息(如 ModuleNotFoundError),就代表套件已經可以使用了。
安裝完成後,你可以直接在 Thonny 下方的 「指令 shell (Shell)」 視窗輸入以下程式碼測試:
import requests
print("安裝成功!")
如果沒有出現紅色錯誤訊息(如 ModuleNotFoundError),就代表套件已經可以使用了。
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。








沒有留言:
張貼留言