Telegram + PythonTkinter
GUI 介面:
你會看到一個 Tkinter 視窗,顯示一個圓形 LED 燈(初始為紅色,
off)。你可以輸入模擬的溫度和濕度值。
點擊「輸出資料到 Telegram」按鈕,會將目前的 LED 狀態和溫濕度數據發送到你的 Telegram 聊天中。
Telegram Bot 交互:
/on: LED 變為綠色。/off: LED 變為紅色。/flash: LED 每 0.5 秒在綠色和紅色之間交替閃爍。/Timer: LED 變為綠色,持續 10 秒。10 秒後,它會自動變為紅色(off),並在 Telegram 中收到通知。/status: Bot 會回覆目前的 LED 狀態(on/off/flash/timer)以及 GUI 中輸入的溫度和濕度數據。初始化: 程式啟動後,會自動向你的聊天 ID 發送一條初始化狀態訊息。
# ⚠️ 請替換成你的實際 Token 和 Chat ID
#TELEGRAM_TOKEN = "YOUR_TELEGRAM_BOT_TOKEN"
#TARGET_CHAT_ID = "YOUR_CHAT_ID"
import tkinter as tk
from tkinter import messagebox
import threading
import time
import asyncio
from telegram import Update, Bot
from telegram.ext import Application, CommandHandler, ContextTypes
# --- 1. 配置與全域狀態 ---
# ⚠️ 請替換成你的實際 Token 和 Chat ID
#TELEGRAM_TOKEN = "YOUR_TELEGRAM_BOT_TOKEN"
#TARGET_CHAT_ID = "YOUR_CHAT_ID"
TELEGRAM_TOKEN = "77138940254:AAHbrWu9ovb1BKPQy2WsbNSjNxfCGfCrEWU-o"
TARGET_CHAT_ID = "719265218469"
# LED 狀態變數:'off', 'on', 'flash', 'timer'
led_state = 'off'
# Timer 持續時間 (秒)
TIMER_DURATION = 10
# 全域變數來保存 Tkinter App 的引用,用於 Bot 執行緒存取
app_instance = None
# --- 2. Tkinter GUI 應用程式類別 ---
class LEDControllerApp:
def __init__(self, master, bot_application):
self.master = master
master.title("LED & 感測器控制器")
self.bot_application = bot_application
self.led_status_str = tk.StringVar(value="off")
# 繪製 LED 的 Canvas
self.canvas = tk.Canvas(master, width=100, height=100)
self.canvas.pack(pady=10)
# 初始狀態:紅色 (off)
self.led_circle = self.canvas.create_oval(10, 10, 90, 90, fill="red", outline="black")
# 狀態標籤
tk.Label(master, text="LED 狀態:").pack()
self.status_label = tk.Label(master, textvariable=self.led_status_str)
self.status_label.pack()
# 模擬溫度和濕度的輸入框
tk.Label(master, text="溫度 (°C):").pack()
self.temp_entry = tk.Entry(master)
self.temp_entry.insert(0, "25.0")
self.temp_entry.pack()
tk.Label(master, text="濕度 (%):").pack()
self.humidity_entry = tk.Entry(master)
self.humidity_entry.insert(0, "60.0")
self.humidity_entry.pack()
# 輸出按鈕:將數據送給 Telegram
self.send_button = tk.Button(master, text="輸出資料到 Telegram", command=self.send_status_to_telegram_from_gui)
self.send_button.pack(pady=10)
# Timer 和 Flash 相關狀態
self.flash_toggle = False
self.timer_end_time = None
# 啟動 LED 顯示更新迴圈
self.update_led_display()
# 程式初始化後將狀態發送給 Telegram
master.after(100, self.initial_status_send)
def initial_status_send(self):
"""程式初始化後發送狀態給 Telegram"""
current_status_msg = self.get_current_status_message()
# 使用 run_coroutine_threadsafe 將非同步任務提交給 Bot 執行緒的事件迴圈
asyncio.run_coroutine_threadsafe(
self.bot_application.bot.send_message(chat_id=TARGET_CHAT_ID, text=f"程式已啟動。\n目前狀態:{current_status_msg}"),
self.bot_application.loop
)
def get_current_status_message(self):
"""獲取目前 LED 和感測器數據的狀態資訊"""
try:
temp = self.temp_entry.get()
humidity = self.humidity_entry.get()
# 檢查 Timer 剩餘時間
timer_info = ""
if led_state == 'timer' and self.timer_end_time:
remaining = int(self.timer_end_time - time.time())
if remaining > 0:
timer_info = f" (剩餘 {remaining} 秒)"
else:
timer_info = f" (即將結束)"
status_msg = (
f"LED 狀態: {led_state.upper()}{timer_info}\n"
f"溫度: {temp} °C\n"
f"濕度: {humidity} %"
)
return status_msg
except Exception:
return f"LED 狀態: {led_state.upper()}\n溫度/濕度資料讀取錯誤。"
def send_status_to_telegram_from_gui(self):
"""從 GUI 按鈕觸發發送狀態"""
status_msg = self.get_current_status_message()
# 將發送操作排程到 Bot 執行緒的事件迴圈中
asyncio.run_coroutine_threadsafe(
self.bot_application.bot.send_message(chat_id=TARGET_CHAT_ID, text=f"手動報告:\n{status_msg}"),
self.bot_application.loop
)
messagebox.showinfo("發送成功", "狀態已發送到 Telegram。")
def update_led_display(self):
"""根據 led_state 更新 LED 顯示和狀態標籤"""
global led_state
self.led_status_str.set(led_state)
if led_state == 'on':
# 綠色圓形 LED
self.canvas.itemconfig(self.led_circle, fill="green")
self.master.after(500, self.update_led_display) # 繼續循環
elif led_state == 'off':
# 紅色圓形 LED
self.canvas.itemconfig(self.led_circle, fill="red")
self.master.after(500, self.update_led_display) # 繼續循環
elif led_state == 'flash':
# 綠色 (0.5s) / 紅色 (0.5s) 交替閃爍
if self.flash_toggle:
self.canvas.itemconfig(self.led_circle, fill="green")
else:
self.canvas.itemconfig(self.led_circle, fill="red")
self.flash_toggle = not self.flash_toggle
self.master.after(500, self.update_led_display) # 每 0.5 秒更新一次
elif led_state == 'timer':
current_time = time.time()
if self.timer_end_time and current_time < self.timer_end_time:
# 倒計時期間保持綠色
self.canvas.itemconfig(self.led_circle, fill="green")
self.master.after(500, self.update_led_display)
else:
# 倒計時結束,切換到 off (紅色)
led_state = 'off'
self.canvas.itemconfig(self.led_circle, fill="red")
self.timer_end_time = None
# 發送通知給 Telegram
asyncio.run_coroutine_threadsafe(
self.bot_application.bot.send_message(chat_id=TARGET_CHAT_ID, text="Timer 結束,LED 已切換到 OFF (紅色)。"),
self.bot_application.loop
)
self.master.after(500, self.update_led_display)
else:
# 預設或未知狀態 (灰色)
self.canvas.itemconfig(self.led_circle, fill="gray")
self.master.after(500, self.update_led_display)
def set_led_state_from_telegram(self, new_state):
"""供 Telegram 處理器調用的方法,安全地更新狀態"""
global led_state
led_state = new_state
if new_state == 'timer':
self.timer_end_time = time.time() + TIMER_DURATION
# ⚠️ 確保在 Tkinter 主執行緒中更新顯示
self.master.after_idle(self.update_led_display)
# --- 3. Telegram Bot 處理函式 ---
async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""處理 /start 命令"""
await update.message.reply_text('歡迎使用 LED 控制 Bot。\n請使用 /on, /off, /flash, /Timer, 或 /status 命令。')
async def on_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""處理 /on 命令:綠色"""
if app_instance:
app_instance.set_led_state_from_telegram('on')
await update.message.reply_text('LED 已開啟 (綠色)。')
async def off_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""處理 /off 命令:紅色"""
if app_instance:
app_instance.set_led_state_from_telegram('off')
await update.message.reply_text('LED 已關閉 (紅色)。')
async def flash_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""處理 /flash 命令:綠/紅交替"""
if app_instance:
app_instance.set_led_state_from_telegram('flash')
await update.message.reply_text('LED 正在閃爍 (綠/紅交替,每 0.5 秒)。')
async def timer_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""處理 /Timer 命令:10 秒綠色後轉紅色"""
if app_instance:
app_instance.set_led_state_from_telegram('timer')
await update.message.reply_text(f'LED 已開啟 Timer,將持續 {TIMER_DURATION} 秒 (綠色),隨後轉為 OFF (紅色)。')
async def status_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""處理 /status 命令:回傳目前 LED 狀態和溫濕度"""
if app_instance:
status_msg = app_instance.get_current_status_message()
await update.message.reply_text(f"目前狀態:\n{status_msg}")
else:
await update.message.reply_text("GUI 應用程式未完全初始化,無法獲取狀態。")
def start_telegram_bot(application: Application):
"""在單獨的執行緒中啟動 Telegram Bot 輪詢"""
# 建立一個新的事件迴圈並設定給此執行緒
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
application.loop = loop # 儲存 loop,以便在 Tkinter 執行緒中使用 run_coroutine_threadsafe
# 添加命令處理器
application.add_handler(CommandHandler("start", start_command))
application.add_handler(CommandHandler("on", on_command))
application.add_handler(CommandHandler("off", off_command))
application.add_handler(CommandHandler("flash", flash_command))
application.add_handler(CommandHandler("Timer", timer_command))
application.add_handler(CommandHandler("status", status_command))
# 啟動 Bot
print("Telegram Bot 正在啟動...")
# 使用 run_polling 進行阻塞式輪詢
application.run_polling(poll_interval=1)
print("Telegram Bot 停止。")
# --- 4. 主程式入口 ---
if __name__ == '__main__':
# 檢查是否替換了 Token 和 Chat ID
if TELEGRAM_TOKEN == "YOUR_TELEGRAM_BOT_TOKEN" or TARGET_CHAT_ID == "YOUR_CHAT_ID":
print("⚠️ 錯誤:請務必在程式碼開頭替換你的 TELEGRAM_TOKEN 和 TARGET_CHAT_ID!")
exit()
# 初始化 Telegram Bot 應用
bot_application = Application.builder().token(TELEGRAM_TOKEN).build()
# 啟動 Telegram Bot 執行緒
# 必須在單獨的執行緒中運行,以免阻塞 Tkinter 的 mainloop
bot_thread = threading.Thread(target=start_telegram_bot, args=(bot_application,))
bot_thread.daemon = True # 設定為守護執行緒,主程式退出時它也退出
bot_thread.start()
# 啟動 Tkinter 主程式
root = tk.Tk()
app_instance = LEDControllerApp(root, bot_application)
# 運行 Tkinter 主迴圈 (阻塞式)
root.mainloop()
# Tkinter 退出後,停止 Telegram Bot
print("Tkinter 視窗關閉。正在停止 Telegram Bot...")
# 異步停止 Bot
try:
# 將停止操作提交給 Bot 執行緒的事件迴圈
asyncio.run_coroutine_threadsafe(bot_application.updater.stop(), bot_application.loop).result(timeout=5)
except Exception as e:
print(f"停止 Bot 發生錯誤: {e}")
bot_thread.join(timeout=5)
print("程式退出。")





沒有留言:
張貼留言