在 RFID 領域中,Reader-driven Polling(輪詢協議)是最基礎且直覺的存取方法。
Polling 是由讀取器(Reader)依序點名每一個已知的標籤(Tag)。
在業界常見的場景中,這通常用於讀取器已經擁有「標籤白名單」的情況,或者是在確定性要求極高的工業自動化環境中。
RFID Polling Protocol 模擬器實作
這個程式模擬讀取器按照列表順序發送「查詢指令」,標籤收到後給予「應答(ACK)」。
Polling 模式的核心原理
無碰撞(No Collision): 與 ALOHA 不同,Polling 由讀取器嚴格控制「誰該說話」。在模擬器中,你可以看到讀取器一次只會與一條虛線(連線)互動,這消除了標籤同時發送訊號導致的衝突。
確定性(Deterministic): 讀取器知道它要讀取哪些標籤。這在自動化倉儲(例如輸送帶上的五個固定位置)非常有用,因為讀取器可以精確計算掃描完所有標籤所需的時間。
主要缺點: 如果環境中有「新出現」的標籤(不在名單內),Polling 模式將無法發現它。這就是為什麼現代 RFID 協議(如 EPC Gen2)通常結合了 Slotted ALOHA 來發現新標籤,再用 Polling/Query 來讀取詳細資料。
import tkinter as tk
from tkinter import ttk
import time
from threading import Thread
class RFIDPollingSimulator:
def __init__(self, root):
self.root = root
self.root.title("RFID Reader-Driven Polling 模擬器")
# 預設標籤庫 (模擬 Reader 擁有的清單)
self.tag_inventory = {
"TAG-001": {"status": "Waiting", "data": "Sensor_Data_A"},
"TAG-002": {"status": "Waiting", "data": "Sensor_Data_B"},
"TAG-003": {"status": "Waiting", "data": "Sensor_Data_C"},
"TAG-004": {"status": "Waiting", "data": "Sensor_Data_D"}
}
self.is_running = False
self.current_polling_idx = -1
self.step_mode = tk.BooleanVar(value=True)
self.step_trigger = False
self.setup_ui()
def setup_ui(self):
# 頂部控制區
ctrl_frame = tk.Frame(self.root, padx=10, pady=10)
ctrl_frame.pack(fill="x")
self.btn_start = tk.Button(ctrl_frame, text="開始輪詢 (Start Polling)", command=self.start_polling, bg="#e3f2fd")
self.btn_start.pack(side=tk.LEFT, padx=5)
tk.Checkbutton(ctrl_frame, text="手動單步執行", variable=self.step_mode).pack(side=tk.LEFT, padx=10)
self.btn_next = tk.Button(ctrl_frame, text="下一個標籤 >>", command=self.next_step, state=tk.DISABLED)
self.btn_next.pack(side=tk.LEFT, padx=5)
# 中間:視覺化展示區
self.canvas = tk.Canvas(self.root, width=600, height=250, bg="white", highlightthickness=1, highlightbackground="#ddd")
self.canvas.pack(pady=10, padx=10)
# 繪製 Reader
self.reader_rect = self.canvas.create_rectangle(220, 20, 380, 60, fill="#1976d2", outline="#0d47a1")
self.canvas.create_text(300, 40, text="RFID READER", fill="white", font=("Arial", 10, "bold"))
# 繪製標籤與連線
self.tag_shapes = {}
self.tag_lines = {}
tags_keys = list(self.tag_inventory.keys())
for i, tid in enumerate(tags_keys):
x = 80 + (i * 140)
line = self.canvas.create_line(300, 60, x, 160, dash=(4, 4), state=tk.HIDDEN, fill="#aaa")
tag = self.canvas.create_oval(x-35, 160, x+35, 200, fill="#f5f5f5", outline="#9e9e9e", width=2)
self.canvas.create_text(x, 180, text=tid, font=("Arial", 9))
self.tag_shapes[tid] = tag
self.tag_lines[tid] = line
# 底部:狀態表格
self.tree = ttk.Treeview(self.root, columns=("ID", "Status", "Content"), show="headings", height=5)
self.tree.heading("ID", text="標籤 ID")
self.tree.heading("Status", text="通訊狀態")
self.tree.heading("Content", text="回傳資料")
self.tree.column("ID", width=100)
self.tree.column("Status", width=150)
self.tree.pack(fill="x", padx=10, pady=10)
for tid in self.tag_inventory:
self.tree.insert("", tk.END, iid=tid, values=(tid, "等待中...", "---"))
def next_step(self):
self.step_trigger = True
def start_polling(self):
if self.is_running: return
self.is_running = True
self.btn_start.config(state=tk.DISABLED)
self.btn_next.config(state=tk.NORMAL if self.step_mode.get() else tk.DISABLED)
# 重設 UI
for tid in self.tag_inventory:
self.canvas.itemconfig(self.tag_shapes[tid], fill="#f5f5f5")
self.canvas.itemconfig(self.tag_lines[tid], state=tk.HIDDEN)
self.tree.item(tid, values=(tid, "等待中...", "---"))
Thread(target=self.run_polling_logic, daemon=True).start()
def run_polling_logic(self):
tags_keys = list(self.tag_inventory.keys())
for tid in tags_keys:
if not self.is_running: break
# 1. 準備查詢
self.update_ui_node(tid, "querying")
# 手動模式等待
if self.step_mode.get():
self.step_trigger = False
while not self.step_trigger and self.is_running:
time.sleep(0.1)
else:
time.sleep(1.2)
# 2. 模擬通訊
self.update_ui_node(tid, "active")
time.sleep(0.8)
# 3. 獲取資料
data = self.tag_inventory[tid]["data"]
self.update_ui_node(tid, "completed", data)
time.sleep(0.5)
self.is_running = False
self.root.after(0, self.reset_buttons)
def update_ui_node(self, tid, state, data=None):
def update():
if state == "querying":
self.canvas.itemconfig(self.tag_shapes[tid], fill="#fff9c4") # 黃色:詢問中
self.tree.item(tid, values=(tid, "正在發送查詢...", "---"))
elif state == "active":
self.canvas.itemconfig(self.tag_shapes[tid], fill="#bbdefb") # 藍色:連線中
self.canvas.itemconfig(self.tag_lines[tid], state=tk.NORMAL, fill="#1976d2")
self.tree.item(tid, values=(tid, "標籤應答中 (ACK)", "---"))
elif state == "completed":
self.canvas.itemconfig(self.tag_shapes[tid], fill="#c8e6c9") # 綠色:完成
self.canvas.itemconfig(self.tag_lines[tid], fill="#4caf50")
self.tree.item(tid, values=(tid, "讀取成功", data))
self.root.after(0, update)
def reset_buttons(self):
self.btn_start.config(state=tk.NORMAL)
self.btn_next.config(state=tk.DISABLED)
if __name__ == "__main__":
root = tk.Tk()
app = RFIDPollingSimulator(root)
root.mainloop()

沒有留言:
張貼留言