WOKWI 模擬RFID UID輸出到MQTT , Python 程式訂閱 MQTT主題 註冊與比對UID碼
新的 UID 生成函數:
generateSpecificRangeUID()- 定義範圍:
將十六進位數值直接定義為C++unsigned long start_val = 0x8EED7100; unsigned long end_val = 0x8EED71FF;unsigned long類型,因為它們超出了int的範圍。 - 生成亂數:
C++
unsigned long random_val = random(start_val, end_val + 1);random(min, max)函數生成min(包含)到max(不包含)之間的亂數。因此,為了包含end_val,我們需要將max設定為end_val + 1。 - 轉換為十六進位字符串:
這裡使用了C++char hex_uid[9]; // 8 characters + null terminator sprintf(hex_uid, "%08lX", random_val); // %08lX for 8 uppercase hex digits of a long return String(hex_uid);sprintf()函數,這是一個強大的 C 語言格式化輸出函數。%08lX:%: 格式說明符的開始。0: 如果數字不足指定寬度,用零填充。8: 總寬度為 8 個字元。l: 表示對long類型進行操作。X: 將數字轉換為大寫的十六進位表示。
- 將
char陣列轉換為String物件返回。
- 定義範圍:
-
rfidSimTask中調用新的函數:- 將
String uid = generateRandomUID();更改為String uid = generateSpecificRangeUID();,以確保產生的是指定範圍內的 UID。
- 將
-
Serial 監控器提示訊息更新:
setup()函數中的Serial.println訊息已更新,以準確反映新亂數產生器的功能和範圍。rfidSimTask中的提示也已更新,指導用戶如何觸發 RFID 模擬。
-
處理
Serial.available()的改進:- 在
rfidSimTask中,增加了對\r(回車符) 的檢查,並在讀取\n或\r後,額外檢查並消耗緩衝區中可能存在的其他換行符號(例如在 Windows 上,Enter 會發送\r\n),這使得輸入響應更穩定。
- 在
如何使用這個程式碼
- 複製程式碼: 將上述程式碼複製到 Arduino IDE 或 PlatformIO 項目中。
- 配置 WiFi: 將
ssid和password替換為您的實際 Wi-Fi 憑證。如果使用 Wokwi 模擬器,Wokwi-GUEST 通常不需要密碼。 - 編譯並上傳: 將程式碼編譯並上傳到您的 ESP32 開發板。
- 打開 Serial 監控器: 設置 Baud rate 為
115200。 - 模擬 RFID 掃描: 在 Serial 監控器的輸入框中輸入任何字元,然後按下 Enter 鍵。
- 每次按下 Enter,程式都會生成一個介於
8EED7100和8EED71FF之間的新十六進位 UID。 - 這個 UID 會透過 MQTT 發布到
broker.mqttgo.io的alex9ufo/rfidUID主題。 - 成功發布後,板載 LED(通常是 GPIO2)會短暫亮起 500 毫秒。
- 每次按下 Enter,程式都會生成一個介於
現在,您的 ESP32 模擬器將會產生符合特定範圍的 UID,並透過 MQTT 發送,完美地與您之前修正的 Python
應用程式互動。
import tkinter as tk from tkinter import messagebox import sqlite3 import random # 雖然不再產生亂數,但保留以防未來擴充 from datetime import datetime import threading import time import paho.mqtt.client as mqtt # 導入 MQTT 客戶端庫 # --- 全域變數 --- mqtt_client = None # MQTT 客戶端實例 stop_event = threading.Event() # 用於控制線程停止 display_window = None # 用於追蹤顯示數據的視窗 result_label = None # 用於顯示比對結果的標籤 led_canvas = None # 用於繪製LED燈的畫布 random_number_display = None # 用於顯示接收到的 UID current_mode = "register" # 預設模式:註冊 "register" 或 比對 "match" mqtt_received_uid_queue = [] # 用於暫存從 MQTT 接收到的 UID,確保線程安全 queue_lock = threading.Lock() # 用於保護隊列的鎖 # --- MQTT 相關設定 --- MQTT_BROKER = "broker.mqttgo.io" MQTT_PORT = 1883 MQTT_TOPIC = "alex9ufo/rfidUID" # --- MQTT 回調函數 --- def on_connect(client, userdata, flags, rc): """當客戶端連接到 MQTT Broker 時呼叫。""" if rc == 0: print("已成功連接到 MQTT Broker") client.subscribe(MQTT_TOPIC) print(f"已訂閱主題: {MQTT_TOPIC}") # 連接成功後,如果處於自動模式,則確保線程在運行 if start_button['state'] == tk.DISABLED: # 如果啟動按鈕是禁用的,表示正在自動運行 # 如果線程沒有啟動,這裡可以考慮再次啟動,但通常是由start_auto_action負責 pass else: print(f"連接到 MQTT Broker 失敗,返回碼: {rc}") messagebox.showerror("MQTT 連接錯誤", f"無法連接到 Broker,返回碼: {rc}") def on_message(client, userdata, msg): """當從訂閱的主題接收到訊息時呼叫。""" try: received_uid = msg.payload.decode("utf-8").strip() print(f"從主題 {msg.topic} 接收到 UID: {received_uid}") # 將接收到的 UID 放入佇列,並通知主線程處理 with queue_lock: mqtt_received_uid_queue.append(received_uid) # 通知 Tkinter 主線程處理佇列中的 UID root.event_generate("<<MQTT_UID_Received>>") except Exception as e: print(f"處理 MQTT 訊息時發生錯誤: {e}") # --- 資料庫操作 (不變) --- def create_table(): """建立資料庫表格,如果不存在則建立。""" conn = sqlite3.connect('random_numbers.db') cursor = conn.cursor() cursor.execute(''' CREATE TABLE IF NOT EXISTS random_data ( ID INTEGER PRIMARY KEY AUTOINCREMENT, UID TEXT NOT NULL UNIQUE, -- UID設定為唯一,避免重複註冊 Date TEXT NOT NULL, Time TEXT NOT NULL ) ''') # 檢查ID是否從1001開始,如果不是則更新序列 cursor.execute("SELECT seq FROM sqlite_sequence WHERE name='random_data'") result = cursor.fetchone() if result is None or result[0] < 1000: # 如果seq不存在或小於1000(對應ID 1001) cursor.execute("INSERT OR IGNORE INTO random_data (ID, UID, Date, Time) VALUES (1000, '', '', '')") cursor.execute("DELETE FROM random_data WHERE ID = 1000") # 插入後刪除,只為設定序列 conn.commit() conn.close() # 不再需要 generate_random_uid(),因為 UID 來自 MQTT def register_uid_to_db(uid_to_register): """將UID儲存到資料庫 (註冊)。""" try: now = datetime.now() current_date = now.strftime("%Y-%m-%d") current_time = now.strftime("%H:%M:%S") conn = sqlite3.connect('random_numbers.db') cursor = conn.cursor() # 獲取下一個ID,並確保從1001開始 cursor.execute("SELECT MAX(ID) FROM random_data") max_id = cursor.fetchone()[0] next_id = 1001 if max_id is None else max(max_id + 1, 1001) cursor.execute("INSERT INTO random_data (ID, UID, Date, Time) VALUES (?, ?, ?, ?)", (next_id, uid_to_register, current_date, current_time)) conn.commit() conn.close() return True except sqlite3.IntegrityError: return False # UID已存在 except Exception as e: messagebox.showerror("錯誤", f"註冊時發生錯誤: {e}") return False def check_uid_in_db(uid_to_check): """檢查UID是否存在於資料庫中 (比對)。 如果存在,返回 (True, ID),否則返回 (False, None)。 """ conn = sqlite3.connect('random_numbers.db') cursor = conn.cursor() cursor.execute("SELECT ID FROM random_data WHERE UID = ?", (uid_to_check,)) result = cursor.fetchone() conn.close() if result: return (True, result[0]) # 找到,返回True和ID else: return (False, None) # 未找到 def get_all_data(): """從資料庫獲取所有數據。""" conn = sqlite3.connect('random_numbers.db') cursor = conn.cursor() cursor.execute("SELECT ID, UID, Date, Time FROM random_data ORDER BY ID ASC") rows = cursor.fetchall() conn.close() return rows # --- GUI 相關函數 (略有修改) --- def update_display_data_text(text_widget): """更新顯示數據的Text widget內容。""" rows = get_all_data() text_widget.config(state="normal") # 允許編輯 text_widget.delete(1.0, tk.END) # 清空內容 text_widget.insert(tk.END, "ID UID Date Time\n") text_widget.insert(tk.END, "="*50 + "\n") for row in rows: text_widget.insert(tk.END, f"{str(row[0]).ljust(10)}{str(row[1]).ljust(15)}{str(row[2]).ljust(12)}{row[3]}\n") text_widget.config(state="disabled") # 設為只讀 text_widget.see(tk.END) # 滾動到最新數據 def on_display_window_close(): """處理顯示數據視窗關閉事件。""" global display_window if display_window: display_window.destroy() display_window = None def display_data_in_window(): """在獨立視窗中顯示資料庫數據,並支援即時更新。""" global display_window if display_window is None or not display_window.winfo_exists(): display_window = tk.Toplevel(root) display_window.title("所有儲存的數據 (即時更新)") display_window.geometry("600x400") text_area = tk.Text(display_window, wrap="word", width=70, height=20, font=("Consolas", 10)) text_area.pack(expand=True, fill="both", padx=10, pady=10) scrollbar = tk.Scrollbar(text_area, command=text_area.yview) scrollbar.pack(side="right", fill="y") text_area.config(yscrollcommand=scrollbar.set) display_window.protocol("WM_DELETE_WINDOW", on_display_window_close) update_display_data_text(display_window.children['!text']) def update_result_display(is_matched, matched_id=None): """更新比對結果和LED燈狀態,並顯示ID號碼。""" global result_label, led_canvas if is_matched: result_text = f"通過 ID={matched_id}" if matched_id is not None else "通過" result_label.config(text=result_text, fg="green") led_canvas.itemconfig("led", fill="green") else: result_label.config(text="禁止", fg="red") led_canvas.itemconfig("led", fill="red") def update_random_number_display(uid): """更新主視窗中的亂數顯示 (現在是接收到的 UID)。""" global random_number_display if random_number_display: random_number_display.config(text=f"最新 UID: {uid}") # 標籤文字修改 # --- 模式切換與執行邏輯 --- def set_mode(mode): """設定當前操作模式 (註冊或比對)。""" global current_mode current_mode = mode mode_label.config(text=f"當前模式: {'註冊' if current_mode == 'register' else '比對'}") # 確保在切換模式時,停止任何正在進行的自動操作 stop_auto_action_internal() if current_mode == 'register': start_button.config(text="開始自動註冊 (MQTT)", command=start_auto_action, state=tk.NORMAL) stop_button.config(text="停止自動註冊", command=stop_auto_action, state=tk.DISABLED) manual_action_button.config(text="手動處理 (從MQTT)", command=manual_action) # 隱藏比對結果顯示 if result_label: result_label.pack_forget() if led_canvas: led_canvas.pack_forget() display_data_in_window() # 註冊模式下自動開啟數據顯示 else: # match mode start_button.config(text="開始自動比對 (MQTT)", command=start_auto_action, state=tk.NORMAL) stop_button.config(text="停止自動比對", command=stop_auto_action, state=tk.DISABLED) manual_action_button.config(text="手動處理 (從MQTT)", command=manual_action) # 顯示比對結果 if result_label: result_label.pack(pady=10) if led_canvas: led_canvas.pack(pady=5) update_result_display(False) # 初始化為紅色 if display_window and display_window.winfo_exists(): on_display_window_close() # 比對模式下關閉數據顯示,避免混淆 def mqtt_processing_thread(): """處理從 MQTT 佇列中取出 UID 並執行相應操作的線程。""" global stop_event # 每次啟動線程前,重置事件,確保它是False狀態 stop_event.clear() while not stop_event.is_set(): # 只要停止事件未被設定,就繼續運行 uid = None with queue_lock: if mqtt_received_uid_queue: uid = mqtt_received_uid_queue.pop(0) # 從佇列中取出一個 UID if uid: # 如果有收到 UID # 在主線程更新GUI (顯示最新 UID) root.after(10, lambda uid_val=uid: update_random_number_display(uid_val)) if current_mode == "register": registered = register_uid_to_db(uid) if registered: print(f"註冊成功: {uid}") else: print(f"UID {uid} 已存在,跳過註冊。") # 在主線程更新GUI (數據顯示) if display_window and display_window.winfo_exists(): root.after(10, lambda: update_display_data_text(display_window.children['!text'])) elif current_mode == "match": is_matched, matched_id = check_uid_in_db(uid) # 接收ID資訊 print(f"比對UID: {uid} -> {'通過' if is_matched else '禁止'}") # 在主線程更新GUI (結果和LED) root.after(10, lambda: update_result_display(is_matched, matched_id)) # 傳遞ID給更新函數 # 使用 wait() 方法,即使沒有 UID 也要檢查停止事件 # 如果沒有 UID,可以稍微等待一下,避免空轉 if not uid: # 如果沒有處理到 UID,則稍微等待,避免CPU佔用過高 if stop_event.wait(0.1): # 等待0.1秒,如果事件被設定則立即返回True break # 事件被設定,跳出迴圈,線程終止 else: # 如果處理了 UID,等待一小段時間以限制處理速度,或立即進入下一次檢查 if stop_event.wait(0.01): # 處理完一個後稍作等待,防止瞬間處理太多(可調整) break def start_auto_action(): """啟動自動執行 (根據當前模式)。""" global mqtt_client # 每次啟動前,確保舊的線程已停止,並且重置 stop_event stop_auto_action_internal() # 啟動 MQTT 客戶端 if mqtt_client is None: mqtt_client = mqtt.Client() mqtt_client.on_connect = on_connect mqtt_client.on_message = on_message try: mqtt_client.connect(MQTT_BROKER, MQTT_PORT, 60) mqtt_client.loop_start() # 在後台啟動 MQTT 網路循環 except Exception as e: messagebox.showerror("MQTT 連接失敗", f"無法連接到 Broker: {e}") return # 連接失敗則不啟動處理線程 # 啟動處理線程 thread = threading.Thread(target=mqtt_processing_thread) thread.daemon = True thread.start() start_button.config(state=tk.DISABLED) stop_button.config(state=tk.NORMAL) messagebox.showinfo("提示", f"已開始自動處理 MQTT 訊息 (模式: {'註冊' if current_mode == 'register' else '比對'})!") def stop_auto_action_internal(): """內部函數:發送信號以停止自動操作線程和 MQTT 客戶端。""" global stop_event, mqtt_client if not stop_event.is_set(): # 如果事件還沒被設定,就設定它 stop_event.set() # 設定事件,通知處理線程停止 # 停止 MQTT 客戶端循環 if mqtt_client: mqtt_client.loop_stop() # 停止 MQTT 網路循環 mqtt_client.disconnect() # 斷開連接 mqtt_client = None # 重置客戶端實例 # 禁用停止按鈕,啟用啟動按鈕 stop_button.config(state=tk.DISABLED) start_button.config(state=tk.NORMAL) def stop_auto_action(): """停止自動執行 (外部呼叫)。""" stop_auto_action_internal() messagebox.showinfo("提示", f"已停止自動處理 MQTT 訊息!") def start_auto_register(): set_mode("register") # 切換模式時會自動停止 start_auto_action() def stop_auto_register(): stop_auto_action() def start_auto_match(): set_mode("match") # 切換模式時會自動停止 start_auto_action() def stop_auto_match(): stop_auto_action() def manual_action(event=None): # manual_action 可以接受 event 參數 """手動單次執行 (從 MQTT 佇列中取 UID 處理)。""" uid = None with queue_lock: if mqtt_received_uid_queue: uid = mqtt_received_uid_queue.pop(0) # 從佇列中取出一個 UID if uid: # 如果有取到 UID update_random_number_display(uid) # 顯示本次處理的 UID if current_mode == "register": if register_uid_to_db(uid): messagebox.showinfo("註冊成功", f"成功註冊UID: {uid}") else: messagebox.showwarning("註冊失敗", f"UID: {uid} 已存在,無法重複註冊。") if display_window and display_window.winfo_exists(): update_display_data_text(display_window.children['!text']) elif current_mode == "match": is_matched, matched_id = check_uid_in_db(uid) # 接收ID資訊 messagebox.showinfo("比對結果", f"UID: {uid}\n結果: {'通過' if is_matched else '禁止'}{f' ID={matched_id}' if is_matched else ''}") update_result_display(is_matched, matched_id) # 傳遞ID給更新函數 else: messagebox.showinfo("無新 UID", "MQTT 佇列中暫無新的 UID 可供處理。請確保 MQTT Broker 正在發送訊息。") # --- Tkinter 接收 MQTT 事件綁定 --- def handle_mqtt_uid_received(event): """處理從 MQTT 接收到 UID 的自定義事件。""" # 當收到此事件時,如果處於自動模式,無需額外操作,因為 mqtt_processing_thread 會自動處理佇列 # 如果是手動模式,此事件只是提示有新數據,但不會自動處理 pass # --- 主Tkinter視窗設定 --- root = tk.Tk() root.title("MQTT UID 註冊/比對系統") # 標題更改 root.geometry("450x600") create_table() # 啟動時建立資料庫表格 # 綁定自定義事件處理器 root.bind("<<MQTT_UID_Received>>", handle_mqtt_uid_received) # --- 模式選擇框架 --- mode_frame = tk.LabelFrame(root, text="選擇模式", padx=10, pady=10) mode_frame.pack(pady=10) register_mode_button = tk.Button(mode_frame, text="註冊模式", command=lambda: set_mode("register")) register_mode_button.pack(side=tk.LEFT, padx=5) match_mode_button = tk.Button(mode_frame, text="比對模式", command=lambda: set_mode("match")) match_mode_button.pack(side=tk.LEFT, padx=5) mode_label = tk.Label(root, text="當前模式: 註冊", font=("Arial", 12, "bold")) mode_label.pack(pady=5) # --- 最新 UID 顯示區 --- random_number_display = tk.Label(root, text="最新 UID: (等待MQTT)", font=("Consolas", 14, "bold"), fg="blue") random_number_display.pack(pady=10) # --- 操作按鈕 --- start_button = tk.Button(root, text="開始自動註冊 (MQTT)", command=start_auto_action, state=tk.NORMAL, width=25, height=2) # 文字修改 start_button.pack(pady=10) stop_button = tk.Button(root, text="停止自動註冊", command=stop_auto_action, state=tk.DISABLED, width=25, height=2) # 文字修改 stop_button.pack(pady=5) manual_action_button = tk.Button(root, text="手動處理 (從MQTT)", command=manual_action, width=25, height=2) # 文字修改 manual_action_button.pack(pady=5) view_data_button = tk.Button(root, text="開啟/更新數據顯示", command=display_data_in_window, width=25, height=2) view_data_button.pack(pady=5) # --- 比對結果顯示 (預設隱藏) --- result_label = tk.Label(root, text="", font=("Arial", 28, "bold")) # result_label.pack_forget() # 初始隱藏,在比對模式下才顯示 led_canvas = tk.Canvas(root, width=50, height=50, bg="white") led_canvas.create_oval(10, 10, 40, 40, fill="gray", outline="black", tags="led") # led_canvas.pack_forget() # 初始隱藏,在比對模式下才顯示 # --- 初始化為註冊模式 --- set_mode("register") root.mainloop() # 程式結束時確保 MQTT 客戶端斷開連接 if mqtt_client: mqtt_client.loop_stop() mqtt_client.disconnect()
主要修改與新功能
-
導入
paho.mqtt.client:import paho.mqtt.client as mqtt導入 MQTT 客戶端庫。
-
MQTT 全域變數和設定:
mqtt_client = None:儲存 MQTT 客戶端實例。MQTT_BROKER,MQTT_PORT,MQTT_TOPIC:定義 MQTT 連接和訂閱的常量。mqtt_received_uid_queue = []:新增一個列表作為佇列,用於暫存從 MQTT 接收到的 UID。MQTT 訊息處理通常在單獨的線程中進行,將 UID 放入佇列可以確保數據在多線程環境中的安全傳輸。queue_lock = threading.Lock():用於保護mqtt_received_uid_queue,避免多線程同時讀寫導致數據損壞。
-
MQTT 回調函數:
on_connect(client, userdata, flags, rc): 當成功連接到 Broker 後,會自動訂閱MQTT_TOPIC。on_message(client, userdata, msg): 當接收到來自訂閱主題的訊息時觸發。- 它會將
msg.payload解碼為 UTF-8 字符串,並去除空白。 - 將 UID 放入佇列: 使用
with queue_lock:確保線程安全地將接收到的uid加入mqtt_received_uid_queue。 - 通知 Tkinter 主線程:
root.event_generate("<<MQTT_UID_Received>>")創建一個自定義的 Tkinter 事件。這是一種推薦的跨線程更新 GUI 的方法,避免直接在 MQTT 回調線程中操作 Tkinter 元件。
- 它會將
-
MQTT 處理線程 (
mqtt_processing_thread):- 這個線程現在取代了原有的
auto_mode_thread。 - 它不再自己生成亂數,而是從
mqtt_received_uid_queue中取出 UID 進行處理。 with queue_lock::安全地從佇列中pop(0)取出最老的 UID。- 優化等待邏輯:
- 如果佇列為空 (
if not uid:),線程會使用stop_event.wait(0.1)等待 0.1 秒,避免空轉佔用 CPU,同時仍能響應停止信號。 - 如果處理了 UID,則
stop_event.wait(0.01)稍微等待一下,控制處理速度。
- 如果佇列為空 (
- 這個線程現在取代了原有的
-
啟動/停止邏輯修改:
start_auto_action():- 初始化 MQTT 客戶端: 如果
mqtt_client尚未初始化,則創建實例,設定on_connect和on_message回調,然後嘗試連接到 Broker 並啟動mqtt_client.loop_start()(這會讓 MQTT 客戶端在後台運行一個網路循環,負責連接、訂閱和接收訊息)。 - 然後才啟動
mqtt_processing_thread。
- 初始化 MQTT 客戶端: 如果
stop_auto_action_internal():- 除了設定
stop_event來停止mqtt_processing_thread外,還會呼叫mqtt_client.loop_stop()和mqtt_client.disconnect()來妥善關閉 MQTT 連接。
- 除了設定
-
manual_action()函數修改:- 「手動處理」現在是從
mqtt_received_uid_queue中取一個 UID 來處理,而不是自己生成。 - 如果佇列為空,會彈出提示。
- 「手動處理」現在是從
-
Tkinter 事件綁定 (
handle_mqtt_uid_received):root.bind("<<MQTT_UID_Received>>", handle_mqtt_uid_received):將自定義事件綁定到一個空函數。雖然這個例子中handle_mqtt_uid_received自身沒有做太多事情,但這種模式是標準且安全的,表示 Tkinter 已經收到了來自其他線程的通知,可以觸發 UI 更新。實際的 UID 處理邏輯已經移到mqtt_processing_thread了。
-
GUI 文字修改:
- 按鈕和顯示 UID 的標籤文字已更新,以反映其 MQTT 來源。
- 視窗標題也已更改。
如何運行
- 確保安裝
paho-mqtt:Bashpip install paho-mqtt - 將上述修正後的程式碼儲存為一個
.py檔案(例如mqtt_uid_system.py)。 - 打開命令提示字元或終端機。
- 導航到儲存該檔案的目錄。
- 運行命令:
python mqtt_uid_system.py
操作步驟
- 啟動程式: 程式啟動後,會看到「MQTT UID 註冊/比對系統」視窗。
- 啟動 MQTT 連接: 點擊 「開始自動註冊 (MQTT)」 或 「開始自動比對 (MQTT)」 按鈕。
- 程式將嘗試連接到
broker.mqttgo.io。如果連接成功,你應該會在控制台看到「已成功連接到 MQTT Broker」和「已訂閱主題: alex9ufo/rfidUID」的訊息。 - 同時,一個後台處理線程也會啟動。
- 程式將嘗試連接到
- 發送 MQTT 訊息:
- 你需要使用一個 MQTT 客戶端(例如 MQTTX、MQTT Explorer 或其他程式碼)向
broker.mqttgo.io的alex9ufo/rfidUID主題發送測試訊息。 - 訊息內容就是你希望處理的 UID 碼,例如:
8EED71A5或8EED71CC。
- 你需要使用一個 MQTT 客戶端(例如 MQTTX、MQTT Explorer 或其他程式碼)向
- 觀察程式行為:
- 當有新的 UID 訊息發送過來時:
- 主視窗的 「最新 UID: 」 標籤會更新顯示接收到的 UID。
- 如果處於「註冊模式」:
- 程式會嘗試將該 UID 儲存到資料庫中。
- 資料庫顯示視窗會即時更新。
- 如果處於「比對模式」:
- 程式會將該 UID 與資料庫中的記錄進行比對。
- 比對結果(「通過 ID=XXX」或「禁止」)和 LED 燈會即時更新。
- 當有新的 UID 訊息發送過來時:
- 停止功能:
- 點擊 「停止自動註冊」 或 「停止自動比對」 按鈕,程式將會停止 MQTT 客戶端的後台循環,斷開連接,並停止處理線程。
- 手動處理:
- 即使沒有啟動自動模式,只要 MQTT 客戶端連接並接收到訊息,UID 會被放入佇列。
- 你可以點擊 「手動處理 (從MQTT)」 按鈕,從佇列中取出一個最新的 UID 進行一次註冊或比對操作。





沒有留言:
張貼留言