IoT 智能控制面板 (Python Tkinter)
customtkinter
為了在 tkinter 中達到如同網頁般的圓角與平滑陰影視覺效果,最頂級且優雅的作法是使用 CustomTkinter 庫。
安裝 Python MQTT 套件
paho-mqtt 與 customtkinter
Python 與 Wokwi 的主題變數必須一字不差:
LED 控制:
alex9ufo/home/led溫度:
alex9ufo/home/temperature濕度:
alex9ufo/home/humidity
Python程式
import customtkinter as ctk
import paho.mqtt.client as mqtt
import threading
import json
# --- 基礎視覺風格設定 ---
ctk.set_appearance_mode("Light")
ctk.set_default_color_theme("green")
# --- MQTT 參數設定(必須與 Wokwi 完全一致) ---
MQTT_BROKER = "broker.emqx.io"
MQTT_PORT = 1883
TOPIC_LED = "alex9ufo/home/led"
TOPIC_TEMP = "alex9ufo/home/temperature"
TOPIC_HUM = "alex9ufo/home/humidity"
class EnvironmentDashboard(ctk.CTk):
def __init__(self):
super().__init__()
# 視窗設定
self.title("IoT 智能控制面板 (Python MQTT 連動版)")
self.geometry("540x420")
self.configure(fg_color="#F5F5F7")
# 建立 UI 介面
self.setup_ui()
# 啟動後台 MQTT 連線
self.start_mqtt()
def setup_ui(self):
# 1. 頂部標題欄與狀態 (Header)
self.header_frame = ctk.CTkFrame(self, fg_color="transparent")
self.header_frame.pack(fill="x", padx=40, pady=(40, 20))
self.title_label = ctk.CTkLabel(
self.header_frame, text="環境主控台",
font=ctk.CTkFont(family="Arial", size=26, weight="bold"), text_color="#1D1D1F"
)
self.title_label.pack(side="left")
self.status_frame = ctk.CTkFrame(self.header_frame, fg_color="transparent")
self.status_frame.pack(side="right")
self.status_dot = ctk.CTkLabel(self.status_frame, text="●", font=ctk.CTkFont(size=14), text_color="#FF3B30") # 預設紅色斷線
self.status_dot.pack(side="left", padx=(0, 6))
self.status_text = ctk.CTkLabel(self.status_frame, text="連線中...", font=ctk.CTkFont(family="Arial", size=14, weight="bold"), text_color="#86868B")
self.status_text.pack(side="left")
# 2. 數據卡片區域 (Grid Layout)
self.grid_frame = ctk.CTkFrame(self, fg_color="transparent")
self.grid_frame.pack(fill="x", padx=40, pady=10)
self.grid_frame.grid_columnconfigure(0, weight=1)
self.grid_frame.grid_columnconfigure(1, weight=1)
# 🌡️ 溫度卡片
self.temp_card = ctk.CTkFrame(self.grid_frame, fg_color="#FFFFFF", corner_radius=16)
self.temp_card.grid(row=0, column=0, padx=(0, 10), sticky="nsew")
self.temp_title = ctk.CTkLabel(self.temp_card, text="🌡️ 當前溫度", font=ctk.CTkFont(family="Arial", size=14, weight="bold"), text_color="#86868B")
self.temp_title.pack(anchor="w", padx=20, pady=(20, 5))
self.temp_value = ctk.CTkLabel(self.temp_card, text="--.- °C", font=ctk.CTkFont(family="Arial", size=28, weight="bold"), text_color="#1D1D1F")
self.temp_value.pack(anchor="w", padx=20, pady=(0, 20))
# 💧 濕度卡片
self.hum_card = ctk.CTkFrame(self.grid_frame, fg_color="#FFFFFF", corner_radius=16)
self.hum_card.grid(row=0, column=1, padx=(10, 0), sticky="nsew")
self.hum_title = ctk.CTkLabel(self.hum_card, text="💧 當前濕度", font=ctk.CTkFont(family="Arial", size=14, weight="bold"), text_color="#86868B")
self.hum_title.pack(anchor="w", padx=20, pady=(20, 5))
self.hum_value = ctk.CTkLabel(self.hum_card, text="--.- %", font=ctk.CTkFont(family="Arial", size=28, weight="bold"), text_color="#1D1D1F")
self.hum_value.pack(anchor="w", padx=20, pady=(0, 20))
# 3. LED 控制卡片區域
self.control_card = ctk.CTkFrame(self, fg_color="#FFFFFF", corner_radius=16)
self.control_card.pack(fill="x", padx=40, pady=20)
self.control_info_frame = ctk.CTkFrame(self.control_card, fg_color="transparent")
self.control_info_frame.pack(side="left", padx=24, pady=20)
self.control_title = ctk.CTkLabel(self.control_info_frame, text="遠端 LED 節點", font=ctk.CTkFont(family="Arial", size=18, weight="bold"), text_color="#1D1D1F")
self.control_title.pack(anchor="w")
self.control_status = ctk.CTkLabel(self.control_info_frame, text="目前狀態:關閉", font=ctk.CTkFont(family="Arial", size=13), text_color="#86868B")
self.control_status.pack(anchor="w", pady=(2, 0))
self.led_switch = ctk.CTkSwitch(
self.control_card, text="", command=self.on_switch_toggle,
onvalue="ON", offvalue="OFF", progress_color="#34C759"
)
self.led_switch.pack(side="right", padx=24)
self.led_switch.configure(state="disabled") # 連線前禁用開關
# --- MQTT 核心邏輯 ---
def start_mqtt(self):
# 初始化 paho-mqtt 客戶端
self.client = mqtt.Client()
self.client.on_connect = self.on_mqtt_connect
self.client.on_message = self.on_mqtt_message
self.client.on_disconnect = self.on_mqtt_disconnect
# 使用獨立線程執行 MQTT 循環,避免造成 Tkinter 視窗凍結卡死
mqtt_thread = threading.Thread(target=self.mqtt_loop_runner, daemon=True)
mqtt_thread.start()
def mqtt_loop_runner(self):
try:
self.client.connect(MQTT_BROKER, MQTT_PORT, 60)
self.client.loop_forever() # 持續監聽
except Exception as e:
print(f"MQTT 連線失敗: {e}")
def on_mqtt_connect(self, client, userdata, flags, rc):
if rc == 0:
print("Python 成功連線至 MQTT Broker!")
# 切換 UI 狀態為已連線 (綠燈)
self.status_dot.configure(text_color="#34C759")
self.status_text.configure(text="已連線")
self.led_switch.configure(state="normal")
# 訂閱 Wokwi 的溫濕度主題
self.client.subscribe(TOPIC_TEMP)
self.client.subscribe(TOPIC_HUM)
else:
print(f"連線錯誤,錯誤碼: {rc}")
def on_mqtt_disconnect(self, client, userdata, rc):
print("與 MQTT Broker 斷開連線")
self.status_dot.configure(text_color="#FF3B30")
self.status_text.configure(text="已斷線")
self.led_switch.configure(state="disabled")
def on_mqtt_message(self, client, userdata, msg):
topic = msg.topic
payload = msg.payload.decode("utf-8")
print(f"收到 Wokwi 數據 -> 主題: {topic} | 內容: {payload}")
# 即時更新 Tkinter 介面上的數字
if topic == TOPIC_TEMP:
self.temp_value.configure(text=f"{float(payload):.1f} °C")
elif topic == TOPIC_HUM:
self.hum_value.configure(text=f"{float(payload):.1f} %")
def on_switch_toggle(self):
# 取得目前開關狀態 (ON / OFF)
command = self.led_switch.get()
self.control_status.configure(text=f"目前狀態:{'開啟' if command == 'ON' else '關閉'}")
# 發送指令給 Wokwi
self.client.publish(TOPIC_LED, command)
print(f"已發送控制指令給 Wokwi -> {command}")
if __name__ == "__main__":
app = EnvironmentDashboard()
app.mainloop()




沒有留言:
張貼留言