2026年7月3日 星期五

rfid nxp iso14443 的 編碼方式

 rfid nxp iso14443 的 編碼方式





https://ww1.microchip.com/downloads/en/devicedoc/doc2056.pdf


import tkinter as tk

from tkinter import ttk, messagebox


# 建立主視窗

root = tk.Tk()

root.title("ISO 14443 Type A 規範與訊號模擬教學")

root.geometry("800x700")

root.configure(bg="#f5f5f5")


# 設定樣式

style = ttk.Style()

style.configure("TLabel", background="#f5f5f5", font=("Microsoft JhengHei", 12))

style.configure("Header.TLabel", font=("Microsoft JhengHei", 16, "bold"), foreground="#003366")

style.configure("Action.TButton", font=("Microsoft JhengHei", 11, "bold"))


# ==================== 上半部:保留原先的規範說明 ====================


title_label = ttk.Label(root, text="RFID / NFC - ISO 14443 Type A 標準與訊號模擬", style="Header.TLabel")

title_label.pack(pady=15)


intro_text = "• Type A 是 NXP 最具代表性的標準(廣泛應用於 MIFARE Classic, Ultralight 等)。"

intro_label = ttk.Label(root, text=intro_text, wraplength=700, justify="left", foreground="#333333")

intro_label.pack(pady=5, padx=20, anchor="w")


ttk.Separator(root, orient='horizontal').pack(fill='x', padx=20, pady=10)


display_frame = tk.LabelFrame(root, text="靜態規範說明(點選按鈕切換)", font=("Microsoft JhengHei", 11, "bold"), bg="#ffffff", fg="#333333", padx=15, pady=10)

display_frame.pack(fill="x", padx=20, pady=5)


detail_title = tk.Label(display_frame, text="請選擇通訊方向...", font=("Microsoft JhengHei", 13, "bold"), bg="#ffffff", anchor="w")

detail_title.pack(fill="x", pady=(0, 5))


detail_content = tk.Label(display_frame, text="點選下方按鈕查看讀寫器或標籤的調變與編碼規範。", font=("Microsoft JhengHei", 11), bg="#ffffff", justify="left", anchor="nw")

detail_content.pack(fill="x")


def show_pcd_to_picc():

    detail_title.config(text="讀寫器 到 標籤 (Reader to Card, PCD -> PICC)", fg="#d9534f")

    content = "【調變方式】100% ASK (振幅鍵移調變) —— 訊號完全中斷來代表資料邏輯。\n【編碼方式】修正密勒編碼 (Modified Miller Coding) —— 於特定位置加入脈衝,維持能量。"

    detail_content.config(text=content)


def show_picc_to_pcd():

    detail_title.config(text="標籤 到 讀寫器 (Card to Reader, PICC -> PCD)", fg="#0275d8")

    content = "【調變方式】負載調變 (Load Modulation) —— 改變天線阻抗影響磁場。\n【編碼與副載波】曼徹斯特編碼 (Manchester Coding) / 副載波 847.5 kHz —— 抗干擾能力強。\n【通信速率】標準速率為 106 kbit/s"

    detail_content.config(text=content)


btn_frame = ttk.Frame(root)

btn_frame.pack(pady=5)

btn_pcd = ttk.Button(btn_frame, text="1. 讀寫器 -> 標籤 (PCD->PICC)", command=show_pcd_to_picc, style="Action.TButton")

btn_pcd.pack(side="left", padx=15)

btn_picc = ttk.Button(btn_frame, text="2. 標籤 -> 讀寫器 (PICC->PCD)", command=show_picc_to_pcd, style="Action.TButton")

btn_picc.pack(side="left", padx=15)


ttk.Separator(root, orient='horizontal').pack(fill='x', padx=20, pady=10)


# ==================== 下半部:延伸設計 - 訊號波形模擬區 ====================


simulation_frame = tk.LabelFrame(root, text="延伸設計:互動式訊號波形模擬器", font=("Microsoft JhengHei", 11, "bold"), bg="#fcfcfc", fg="#006633", padx=15, pady=10)

simulation_frame.pack(fill="both", expand=True, padx=20, pady=5)


# 輸入區域

input_frame = ttk.Frame(simulation_frame)

input_frame.pack(fill="x", pady=5)


input_label = ttk.Label(input_frame, text="請輸入二進位信號 (例如 01011011):", font=("Microsoft JhengHei", 11))

input_label.pack(side="left", padx=5)


signal_entry = ttk.Entry(input_frame, font=("Consolas", 12), width=20)

signal_entry.insert(0, "01011011")  # 預設值

signal_entry.pack(side="left", padx=5)


# 畫布(用於繪製文字與偽波形)

canvas = tk.Canvas(simulation_frame, bg="#1e1e1e", height=320)

canvas.pack(fill="both", expand=True, pady=5)


def draw_wave():

    # 驗證輸入

    bit_string = signal_entry.get().strip()

    if not bit_string or not all(b in '01' for b in bit_string):

        messagebox.showerror("輸入錯誤", "請輸入有效的二進位字串(僅包含 0 與 1)!")

        return

    

    if len(bit_string) > 12:

        messagebox.showwarning("字串過長", "為了最佳顯示效果,請輸入 12 位元以內的訊號。")

        bit_string = bit_string[:12]

    

    canvas.delete("all")  # 清空畫布

    

    # 畫布參數

    start_x = 180

    bit_width = 45

    

    # 繪製時間軸與格線

    for i in range(len(bit_string) + 1):

        x = start_x + i * bit_width

        canvas.create_line(x, 20, x, 300, fill="#333333", dash=(2, 2))

    

    # 寫出上方原始資料位元

    for i, bit in enumerate(bit_string):

        x = start_x + i * bit_width + (bit_width / 2)

        canvas.create_text(x, 15, text=bit, fill="#ffffff", font=("Consolas", 12, "bold"))

    

    # --- 1) 100% ASK ---

    canvas.create_text(85, 50, text="1) 100% ASK", fill="#ff9999", font=("Microsoft JhengHei", 11, "bold"), anchor="w")

    # 示意圖:用密集波形代表有能量,完全平線代表100%中斷

    x_curr = start_x

    for bit in bit_string:

        if bit == '1': # 假設 1 有訊號,0 由於 Modified Miller 配合會全斷或局部斷,此處簡化示意 ASK 本身特性

            canvas.create_line(x_curr, 50, x_curr + bit_width, 50, fill="#ff4d4d", width=3)

        else:

            canvas.create_line(x_curr, 65, x_curr + bit_width, 65, fill="#ff4d4d", width=1, dash=(4,4))

        x_curr += bit_width

    canvas.create_text(start_x, 80, text="說明: 邏輯由載波的「有/無(100%斷開)」決定,斷開時卡片完全接收不到能量。", fill="#aaaaaa", font=("Microsoft JhengHei", 9), anchor="w")


    # --- 2) Modified Miller ---

    canvas.create_text(85, 120, text="2) Modified Miller", fill="#ffcc99", font=("Microsoft JhengHei", 11, "bold"), anchor="w")

    x_curr = start_x

    last_bit = '1'

    for bit in bit_string:

        # 修正密勒編碼:

        # '1' -> 位元中間有一個下降脈衝下降

        # '0' -> 若前一個是'1'則整段高電平;若前一個是'0'則在位元起始點有下降脈衝

        y_high, y_low = 120, 135

        if bit == '1':

            canvas.create_line(x_curr, y_high, x_curr + bit_width/2, y_high, fill="#ffa64d", width=2)

            canvas.create_line(x_curr + bit_width/2, y_high, x_curr + bit_width/2, y_low, fill="#ffa64d", width=2)

            canvas.create_line(x_curr + bit_width/2, y_low, x_curr + bit_width, y_low, fill="#ffa64d", width=2)

        else: # bit == '0'

            if last_bit == '1':

                canvas.create_line(x_curr, y_high, x_curr + bit_width, y_high, fill="#ffa64d", width=2)

            else:

                canvas.create_line(x_curr, y_low, x_curr, y_high, fill="#ffa64d", width=2)

                canvas.create_line(x_curr, y_high, x_curr + bit_width, y_high, fill="#ffa64d", width=2)

        x_curr += bit_width

        last_bit = bit

    canvas.create_text(start_x, 150, text="說明: 脈衝位置取決於目前與前一個位元。特點是省電且能保持同步。", fill="#aaaaaa", font=("Microsoft JhengHei", 9), anchor="w")


    # --- 3) Load Modulation ---

    canvas.create_text(85, 190, text="3) Load Modulation", fill="#99ccff", font=("Microsoft JhengHei", 11, "bold"), anchor="w")

    x_curr = start_x

    for bit in bit_string:

        # 用振幅大小變化來展示阻抗切換帶來的負載調變

        y_max, y_min = 185, 195

        if bit == '1':

            canvas.create_line(x_curr, y_max, x_curr + bit_width, y_max, fill="#3399ff", width=4)

        else:

            canvas.create_line(x_curr, y_min, x_curr + bit_width, y_min, fill="#3399ff", width=2)

        x_curr += bit_width

    canvas.create_text(start_x, 220, text="說明: 卡片主動切換內部負載阻抗,使讀寫器天線端感應到微弱的電壓振幅起伏。", fill="#aaaaaa", font=("Microsoft JhengHei", 9), anchor="w")


    # --- 4) Manchester Coding ---

    canvas.create_text(85, 260, text="4) Manchester", fill="#99ff99", font=("Microsoft JhengHei", 11, "bold"), anchor="w")

    x_curr = start_x

    for bit in bit_string:

        y_high, y_low = 255, 270

        # Type A 採用的 Manchester:

        # 邏輯 1 = 位元前半段為低,後半段為高 (自低往高跳變)

        # 邏輯 0 = 位元前半段為高,後半段為低 (自高往低跳變)

        if bit == '1':

            canvas.create_line(x_curr, y_low, x_curr + bit_width/2, y_low, fill="#5cd65c", width=2)

            canvas.create_line(x_curr + bit_width/2, y_low, x_curr + bit_width/2, y_high, fill="#5cd65c", width=2)

            canvas.create_line(x_curr + bit_width/2, y_high, x_curr + bit_width, y_high, fill="#5cd65c", width=2)

        else:

            canvas.create_line(x_curr, y_high, x_curr + bit_width/2, y_high, fill="#5cd65c", width=2)

            canvas.create_line(x_curr + bit_width/2, y_high, x_curr + bit_width/2, y_low, fill="#5cd65c", width=2)

            canvas.create_line(x_curr + bit_width/2, y_low, x_curr + bit_width, y_low, fill="#5cd65c", width=2)

        

        # 連接相鄰位元的轉折線

        if x_curr > start_x:

            canvas.create_line(x_curr, 255, x_curr, 270, fill="#225522", width=1)

        x_curr += bit_width

    canvas.create_text(start_x, 292, text="說明: 以位元正中央的「跳變方向」決定邏輯。1為上升沿,0為下降沿,抗噪性極佳。", fill="#aaaaaa", font=("Microsoft JhengHei", 9), anchor="w")


# 模擬按鈕

btn_sim = ttk.Button(input_frame, text="產生模擬波形", command=draw_wave, style="Action.TButton")

btn_sim.pack(side="left", padx=10)


# 初始化繪圖

draw_wave()


# 啟動主程式迴圈

root.mainloop()


2026年6月14日 星期日

Node-Red --> MQTT --> Fuxa 開源碼網頁式圖控平台

Node-Red --> MQTT --> Fuxa  




  






FUXA(一個開源的 Web HMI / SCADA 自動化監控軟體)的專案設定檔

這份設定檔完整定義了 HMI 監控畫面的後端通訊(MQTT 連線、點位標籤)前端網頁圖形介面(SVG 畫布、視窗排版、開關元件)

以下為您詳細說明這支程式(專案)的核心結構與運作原理:

一、 後端通訊與標籤設定 (Devices & Tags)

FUXA 透過這個區塊與外界的物聯網設備或 Node-RED 進行資料交換:

  1. 通訊客戶端 (MQTT Client)

    • 專案中配置了一個名為 mqtt1 的通訊介面,連接到公共的 MQTT 代理伺服器(Broker):mqtt://mqttgo.io:1883

    • 系統內建了一個系統標籤 mqtt1 Connection Status,用來監測此 MQTT 連線是否正常。

  2. 點位標籤 (Tags Mapping): 專案中定義了 4 個與前端元件連動的按鈕標籤(Tags),其對應的 MQTT 主題 (Topic) 分別為:

    • button1 ➔ 訂閱與發送主題:alex9ufo/btn/1/set

    • button2 ➔ 訂閱與發送主題:alex9ufo/btn/2/set

    • button3 ➔ 訂閱與發送主題:alex9ufo/btn/3/set

    • button4 ➔ 訂閱與發送主題:alex9ufo/btn/4/set

    當前端畫面上的開關被切換,或是遠端設備透過這些主題發送數據時,FUXA 都會進行雙向的同步更新。

二、 前端圖形視覺介面 (Views & UI Components)

這部分定義了操作員在瀏覽器畫面上看見的 SCADA / HMI 控制面板:

  1. 畫布與主視覺設定 (Views)

    • 建立了一個名為 v_67ba4a88 的中大型監控畫面(寬 1520 像素、高 850 像素)。

    • 背景顏色設定為帶有科技感的深灰色(#2A2A2A),非常符合工業自動化控制台的暗色系風格。

  2. 前端圖形元件 (SVG Elements): 畫面上配置了數個基於 SVG 向量圖形的動態控制元件,包含:

    • 文字與排版:包含了標題文字,並使用特定字型(如 Arial)與色彩配置。

    • 互動式切換開關 (NGX-SWITCH):畫面上擺放了對應按鈕的網頁開關切換元件。

      • 當開關處於關閉狀態時,畫面上會渲染出紅色、並顯示 "OFF" 字樣的圓形按鈕。

      • 這些開關元件在 HTML/SVG 結構中被賦予了特定的 id,並直接與後端的 button1 ~ button4 標籤進行綁定(Binding)。

三、 專案整體運作邏輯

這份設定檔在 FUXA 系統中啟動後,會實現以下物聯網(IoT)雙向控制情境:

  • 下達控制指令(發送端): 當操作員在瀏覽器打開這個 FUXA HMI 網頁,手動點擊畫面上的開關由 OFF 變更為 ON 時,FUXA 會立刻透過 mqttgo.io 伺服器,向 alex9ufo/btn/x/set 主題發送變更訊號,藉此操控遠端的實體設備(如 PLC 或 ESP32 開發板)。

  • 接收設備狀態(接收端): 如果遠端實體設備的狀態改變(例如由現場按鈕觸發),設備會發送最新的狀態到相同的 MQTT 主題,FUXA 收到通知後,網頁畫面上的紅色 "OFF" 開關就會自動跳轉為 ON 的狀態,達到即時監控的效果。

總結

這是一個非常標準的 Web-based HMI/SCADA(網頁型人機介面)專案設定。它利用 FUXA 的視覺化優勢,結合 MQTT 輕量化通訊協定,為管理員提供了一個跨平台、直覺且具備動態反饋的遠端設備控制面板。




Node-RED 的流程設定檔(Flows)。它的核心功能是建立一個網頁圖形介面(Dashboard),讓使用者可以用來遠端控制 4 個虛擬按鈕,並將控制指令透過 MQTT 物聯網通訊協定發送出去,用以控制現場的實體設備(如 HMI 系統 FUXA、PLC 或 LED 燈)。

以下為您詳細拆解這段程式的運作邏輯與各個節點的功能:

1. 核心運作邏輯(運作流程)

這段程式主要分為兩大邏輯區塊

  • 【發送控制端】網頁開關 ➔ 發送 MQTT 指令: 當使用者在 Node-RED 網頁上切換「遠端控制按鈕」時,程式會根據開關狀態(開啟/關閉),透過 MQTT 發送數字 10 到雲端 MQTT 伺服器,用來設定遠端設備。

  • 【接收監聽端】監聽 MQTT ➔ 偵錯輸出: 程式另外拉出了一條線,專門監聽控制按鈕 1 的主題(Topic)。只要該主題有任何資料變動,就會在 Node-RED 的後台偵錯視窗(Debug)印出數值,方便開發者確認指令是否有成功送出。

2. 節點詳細功能說明

⓵ 使用者介面(UI Dashboard 節點)

  • ui_tab (工業物聯網控制台) & ui_group (現場設備控制 (4按鈕)): 在網頁上建立一個名為「工業物聯網控制台」的頁面,並在裡面規劃了一個寬度為 5 的區塊,專門擺放這 4 個按鈕。

  • ui_switch (UI 控制按鈕 1 ~ 4): 這是網頁上的 4 個切換開關(Switch)。

    • 當切換到 ON (開啟):會送出數值 1 (onvalue": "1")。

    • 當切換到 OFF (關閉):會送出數值 0 (offvalue": "0")。

    • 每個按鈕都有自己獨立的 topic(分別為 "1", "2", "3", "4"),代表它們的編號。

⓶ 雲端通訊(MQTT 節點)

  • mqtt-broker (mqttgo): 這是通訊的基礎設定。這段程式使用了一個免費公開的 MQTT 伺服器,網址為 mqttgo.io,通訊連接埠為 1883

  • mqtt out (發送控制指令至 FUXA): 當網頁按鈕被觸發時,這四個節點會負責把 10 打向雲端伺服器,它們發送的 MQTT 主題 (Topic) 分別為:

    • 按鈕 1 ➔ alex9ufo/btn/1/set

    • 按鈕 2 ➔ alex9ufo/btn/2/set

    • 按鈕 3 ➔ alex9ufo/btn/3/set

    • 按鈕 4 ➔ alex9ufo/btn/4/set (註:名稱中的 FUXA 代表這些指令預期是要給一個叫做 FUXA 的 Web HMI/SCADA 自動化監控軟體接收並執行的。)

⓷ 監聽與除錯(MQTT In & Debug 節點)

  • mqtt in 節點: 獨立監聽了 alex9ufo/btn/1/set(按鈕 1)的主題。當任何地方(不論是網頁端或外部實體設備)更改了按鈕 1 的數值,它都會接收到。

  • debug (debug 392) 節點: 接收到 mqtt in 的資料後,直接顯示在 Node-RED 介面右側的 Debug 欄位中,用來即時觀測按鈕 1 的狀態變化。

總結

這是一段非常標準的 IoT 設備雙向通訊/控制雛形。使用者只要啟動這個 Node-RED 流程,開啟網頁瀏覽器,就可以直接點擊畫面上的 4 個開關,透過雲端的 mqttgo.io 伺服器,對遠端的工業設備或微控制器(如 ESP32、PLC)進行開關(0與1)控制。


[{"id":"ui.switch.btn1","type":"ui_switch","z":"f8a1b2c3.flow1","name":"UI 控制按鈕 1","label":"🔘 遠端控制按鈕 1","tooltip":"","group":"ui.group.btn","order":1,"width":0,"height":0,"passthru":true,"decouple":"false","topic":"1","topicType":"str","style":"","onvalue":"1","onvalueType":"num","onicon":"","oncolor":"","offvalue":"0","offvalueType":"num","officon":"","offcolor":"","animate":true,"className":"","x":140,"y":220,"wires":[["mqtt.out.btn"]]},{"id":"ui.switch.btn2","type":"ui_switch","z":"f8a1b2c3.flow1","name":"UI 控制按鈕 2","label":"🔘 遠端控制按鈕 2","tooltip":"","group":"ui.group.btn","order":2,"width":0,"height":0,"passthru":true,"decouple":"false","topic":"2","topicType":"str","style":"","onvalue":"1","onvalueType":"num","onicon":"","oncolor":"","offvalue":"0","offvalueType":"num","officon":"","offcolor":"","animate":true,"className":"","x":140,"y":260,"wires":[["ad806271d836e060"]]},{"id":"ui.switch.btn3","type":"ui_switch","z":"f8a1b2c3.flow1","name":"UI 控制按鈕 3","label":"🔘 遠端控制按鈕 3","tooltip":"","group":"ui.group.btn","order":3,"width":0,"height":0,"passthru":true,"decouple":"false","topic":"3","topicType":"str","style":"","onvalue":"1","onvalueType":"num","onicon":"","oncolor":"","offvalue":"0","offvalueType":"num","officon":"","offcolor":"","animate":true,"className":"","x":140,"y":300,"wires":[["dc6f8581d5294e65"]]},{"id":"ui.switch.btn4","type":"ui_switch","z":"f8a1b2c3.flow1","name":"UI 控制按鈕 4","label":"🔘 遠端控制按鈕 4","tooltip":"","group":"ui.group.btn","order":4,"width":0,"height":0,"passthru":true,"decouple":"false","topic":"4","topicType":"str","style":"","onvalue":"1","onvalueType":"num","onicon":"","oncolor":"","offvalue":"0","offvalueType":"num","officon":"","offcolor":"","animate":true,"className":"","x":140,"y":340,"wires":[["62cf2b25030192df"]]},{"id":"mqtt.out.btn","type":"mqtt out","z":"f8a1b2c3.flow1","name":"發送控制指令至 FUXA (SET1)","topic":"alex9ufo/btn/1/set","qos":"1","retain":"false","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"192c2b20bef1e71a","x":450,"y":220,"wires":[]},{"id":"ad806271d836e060","type":"mqtt out","z":"f8a1b2c3.flow1","name":"發送控制指令至 FUXA (SET2)","topic":"alex9ufo/btn/2/set","qos":"1","retain":"false","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"192c2b20bef1e71a","x":450,"y":260,"wires":[]},{"id":"dc6f8581d5294e65","type":"mqtt out","z":"f8a1b2c3.flow1","name":"發送控制指令至 FUXA (SET3)","topic":"alex9ufo/btn/3/set","qos":"1","retain":"false","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"192c2b20bef1e71a","x":450,"y":300,"wires":[]},{"id":"62cf2b25030192df","type":"mqtt out","z":"f8a1b2c3.flow1","name":"發送控制指令至 FUXA (SET4)","topic":"alex9ufo/btn/4/set","qos":"1","retain":"false","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"192c2b20bef1e71a","x":450,"y":340,"wires":[]},{"id":"5067b3d71bbb541e","type":"debug","z":"f8a1b2c3.flow1","name":"debug 392","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":390,"y":400,"wires":[]},{"id":"19327f627d40f5c3","type":"mqtt in","z":"f8a1b2c3.flow1","name":"","topic":"alex9ufo/btn/1/set","qos":"1","datatype":"auto-detect","broker":"192c2b20bef1e71a","nl":false,"rap":true,"rh":0,"inputs":0,"x":150,"y":400,"wires":[["5067b3d71bbb541e"]]},{"id":"ui.group.btn","type":"ui_group","name":"現場設備控制 (4按鈕)","tab":"ui.tab.main","order":1,"disp":true,"width":"5","collapse":false,"className":""},{"id":"192c2b20bef1e71a","type":"mqtt-broker","name":"mqttgo","broker":"mqttgo.io","port":"1883","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"autoUnsubscribe":true,"birthTopic":"","birthQos":"0","birthRetain":"false","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closeRetain":"false","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willRetain":"false","willPayload":"","willMsg":{},"userProps":"","sessionExpiry":""},{"id":"ui.tab.main","type":"ui_tab","name":"工業物聯網控制台","icon":"dashboard","order":1,"disabled":false,"hidden":false}]

2026年6月10日 星期三

 










[{"id":"ui.text.led1","type":"ui_text","z":"f8a1b2c3.flow1","group":"ui.group.led","order":1,"width":0,"height":0,"name":"","label":"LED 狀態總覽11","format":"{{msg.topic}}: {{msg.payload ? '🟢 亮燈 (ON)' : '🔴 熄滅 (OFF)'}}","layout":"row-spread","className":"","style":false,"font":"","fontSize":"","color":"#000000","x":400,"y":40,"wires":[]},{"id":"ui.switch.btn1","type":"ui_switch","z":"f8a1b2c3.flow1","name":"UI 控制按鈕 1","label":"🔘 遠端控制按鈕 1","tooltip":"","group":"ui.group.btn","order":1,"width":0,"height":0,"passthru":true,"decouple":"false","topic":"1","topicType":"str","style":"","onvalue":"1","onvalueType":"num","onicon":"","oncolor":"","offvalue":"0","offvalueType":"num","officon":"","offcolor":"","animate":true,"className":"","x":140,"y":220,"wires":[["mqtt.out.btn"]]},{"id":"ui.switch.btn2","type":"ui_switch","z":"f8a1b2c3.flow1","name":"UI 控制按鈕 2","label":"🔘 遠端控制按鈕 2","group":"ui.group.btn","order":2,"width":0,"height":0,"passthru":true,"decouple":false,"topic":"2","topicType":"str","style":"","onvalue":"1","onvalueType":"num","onicon":"","oncolor":"","offvalue":"0","offvalueType":"num","officon":"","offcolor":"","className":"","x":140,"y":260,"wires":[["ad806271d836e060"]]},{"id":"ui.switch.btn3","type":"ui_switch","z":"f8a1b2c3.flow1","name":"UI 控制按鈕 3","label":"🔘 遠端控制按鈕 3","group":"ui.group.btn","order":3,"width":0,"height":0,"passthru":true,"decouple":false,"topic":"3","topicType":"str","style":"","onvalue":"1","onvalueType":"num","onicon":"","oncolor":"","offvalue":"0","offvalueType":"num","officon":"","offcolor":"","className":"","x":140,"y":300,"wires":[["dc6f8581d5294e65"]]},{"id":"ui.switch.btn4","type":"ui_switch","z":"f8a1b2c3.flow1","name":"UI 控制按鈕 4","label":"🔘 遠端控制按鈕 4","group":"ui.group.btn","order":4,"width":0,"height":0,"passthru":true,"decouple":false,"topic":"4","topicType":"str","style":"","onvalue":"1","onvalueType":"num","onicon":"","oncolor":"","offvalue":"0","offvalueType":"num","officon":"","offcolor":"","className":"","x":140,"y":340,"wires":[["62cf2b25030192df"]]},{"id":"mqtt.out.btn","type":"mqtt out","z":"f8a1b2c3.flow1","name":"發送控制指令至 FUXA (SET1)","topic":"factory/btn/1/set","qos":"1","retain":"false","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"192c2b20bef1e71a","x":450,"y":220,"wires":[]},{"id":"22f1fdcee37e088c","type":"mqtt in","z":"f8a1b2c3.flow1","name":"","topic":"factory/led/2/status","qos":"1","datatype":"auto-detect","broker":"192c2b20bef1e71a","nl":false,"rap":true,"rh":0,"inputs":0,"x":150,"y":80,"wires":[["6b00c77afedbfd4c"]]},{"id":"65d8fc7ba6d0c61d","type":"mqtt in","z":"f8a1b2c3.flow1","name":"","topic":"factory/led/3/status","qos":"1","datatype":"auto-detect","broker":"192c2b20bef1e71a","nl":false,"rap":true,"rh":0,"inputs":0,"x":150,"y":120,"wires":[["8a031dbe30adccba"]]},{"id":"a22011250799c62f","type":"mqtt in","z":"f8a1b2c3.flow1","name":"","topic":"factory/led/1/status","qos":"1","datatype":"auto-detect","broker":"192c2b20bef1e71a","nl":false,"rap":true,"rh":0,"inputs":0,"x":150,"y":40,"wires":[["ui.text.led1"]]},{"id":"1ebf0dac14b1ad85","type":"mqtt in","z":"f8a1b2c3.flow1","name":"","topic":"factory/led/4/status","qos":"1","datatype":"auto-detect","broker":"192c2b20bef1e71a","nl":false,"rap":true,"rh":0,"inputs":0,"x":150,"y":160,"wires":[["0da3deeac7dbd5d8"]]},{"id":"ad806271d836e060","type":"mqtt out","z":"f8a1b2c3.flow1","name":"發送控制指令至 FUXA (SET2)","topic":"factory/btn/2/set","qos":"1","retain":"false","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"192c2b20bef1e71a","x":450,"y":260,"wires":[]},{"id":"dc6f8581d5294e65","type":"mqtt out","z":"f8a1b2c3.flow1","name":"發送控制指令至 FUXA (SET3)","topic":"factory/btn/3/set","qos":"1","retain":"false","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"192c2b20bef1e71a","x":450,"y":300,"wires":[]},{"id":"62cf2b25030192df","type":"mqtt out","z":"f8a1b2c3.flow1","name":"發送控制指令至 FUXA (SET4)","topic":"factory/btn/4/set","qos":"1","retain":"false","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"192c2b20bef1e71a","x":450,"y":340,"wires":[]},{"id":"6b00c77afedbfd4c","type":"ui_text","z":"f8a1b2c3.flow1","group":"ui.group.led","order":2,"width":0,"height":0,"name":"","label":"LED 狀態總覽12","format":"{{msg.topic}}: {{msg.payload ? '🟢 亮燈 (ON)' : '🔴 熄滅 (OFF)'}}","layout":"row-spread","className":"","style":false,"font":"","fontSize":"","color":"#000000","x":400,"y":80,"wires":[]},{"id":"8a031dbe30adccba","type":"ui_text","z":"f8a1b2c3.flow1","group":"ui.group.led","order":3,"width":0,"height":0,"name":"","label":"LED 狀態總覽13","format":"{{msg.topic}}: {{msg.payload ? '🟢 亮燈 (ON)' : '🔴 熄滅 (OFF)'}}","layout":"row-spread","className":"","style":false,"font":"","fontSize":"","color":"#000000","x":400,"y":120,"wires":[]},{"id":"0da3deeac7dbd5d8","type":"ui_text","z":"f8a1b2c3.flow1","group":"ui.group.led","order":4,"width":0,"height":0,"name":"","label":"LED 狀態總覽14","format":"{{msg.topic}}: {{msg.payload ? '🟢 亮燈 (ON)' : '🔴 熄滅 (OFF)'}}","layout":"row-spread","className":"","style":false,"font":"","fontSize":"","color":"#000000","x":400,"y":160,"wires":[]},{"id":"ui.group.led","type":"ui_group","name":"現場狀態監視 (4 LED)","tab":"ui.tab.main","order":2,"disp":true,"width":"8","collapse":false,"className":""},{"id":"ui.group.btn","type":"ui_group","name":"現場設備控制 (4按鈕)","tab":"ui.tab.main","order":1,"disp":true,"width":"5","collapse":false,"className":""},{"id":"192c2b20bef1e71a","type":"mqtt-broker","name":"mqttgo","broker":"mqttgo.io","port":"1883","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"autoUnsubscribe":true,"birthTopic":"","birthQos":"0","birthRetain":"false","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closeRetain":"false","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willRetain":"false","willPayload":"","willMsg":{},"userProps":"","sessionExpiry":""},{"id":"ui.tab.main","type":"ui_tab","name":"工業物聯網控制台","icon":"dashboard","order":1,"disabled":false,"hidden":false}]

2026年6月9日 星期二

ModBus + Fuxa開源碼網頁式圖控平台 + MQTT

 ModBus + Fuxa 

 + MQTT 







這是一個非常實用且經典的工業物聯網(IIoT)與數據可視化架構。在這個架構中,Modbus Poll 負責模擬底層工業設備(如 PLC、感測器),MQTT Broker 擔任資料交換的中台,而 FUXA 則是一套開源的 Web-based SCADA 系統,負責將資料抓回並進行動態網頁監控。

為了讓這個通訊範例能夠順利運作,我們將架構設計如下:

Modbus Poll (設備端) ──[Modbus TCP]──> FUXA (SCADA中心) ──[MQTT]──> MQTT Broker (雲端/中台)

以下為您規劃完整的環境建置與通訊設計步驟:

1. 環境準備與軟體設定

請確保您的電腦上已安裝或準備好以下軟體:

  1. Modbus Poll:用於模擬 Modbus TCP Slave(伺服器)或 Master。在本範例中,我們將其設定為 Modbus TCP Slave(模擬一台 PLC)。

  2. MQTT Broker:可以使用公用的 Broker(例如 broker.hivemq.com),或在本地端用 Docker 運行 Mosquitto

  3. FUXA:確認已啟動 FUXA 服務(通常透過 Node.js 或 Docker 執行,瀏覽器輸入 http://localhost:1881 進入畫面)。

2. 第一階段:Modbus Poll 設定 (模擬工業設備)

我們首先讓 Modbus Poll 模擬一個內含溫度與濕度資料的智能感測器。

步驟 A:建立暫存器

  1. 打開 Modbus Poll,點選選單的 Setup -> Read/Write Definition

  2. 設定如下:

    • Slave ID: 1

    • Function: 03 Read Holding Registers (4x)

    • Address: 0 (從 0 開始)

    • Quantity: 10 (讀取 10 個暫存器)

  3. 點擊 OK

步驟 B:啟動 Modbus TCP 伺服器

  1. 點選選單的 Connection -> Connect...

  2. 在 Connection 下拉選單選擇 Modbus TCP/IP Server

  3. TCP Server Port: 預設 502(如果權限衝突,可改為 5020)。

  4. 點擊 OK 啟動監聽。


ModbusTCPTool

Modbus TCP test tool, support server and client mode, support 4 types of registers, client support regular reading data, data support hexadecimal, binary and other





步驟 C:模擬數據輸入

在編號 40001 (Address 0) 與 40002 (Address 1) 的格子點兩下,分別手動輸入數值:

  • 40001 (Address 0): 輸入 25 (模擬溫度 $25^\circ\text{C}$)

  • 40002 (Address 1): 輸入 60 (模擬濕度 $60\%$)

3. 第二階段:FUXA 設定 (SCADA 核心)

FUXA 在這裡扮演雙重角色:它既要透過 Modbus TCP 向 Modbus Poll 讀取資料,又要透過 MQTT 將資料 發布(Publish) 出去。

步驟 A:新增 Modbus TCP 驅動連線

  1. 打開 FUXA 網頁控制台,切換到 Configuration (設定) 頁面。

  2. Devices 區塊點擊 + Add device

  3. 設定參數:

    • Name: Modbus_PLC

    • Type: Modbus TCP

    • Host: 127.0.0.1 (若在同一台電腦)

    • Port: 502 (需與 Modbus Poll 設定一致)

    • Slave ID: 1

  4. 點擊儲存,確認連線狀態顯示為綠色(Connected)。

步驟 B:在 FUXA 中建立標籤 (Tags)

在剛剛建立的 Modbus_PLC 設備下新增兩個 Tags:

  1. 溫度標籤 (Temperature)

    • Name: Temperature

    • Address: 40001 (或根據 FUXA 格式輸入 F40001 或數值類型,通常選 Int16)

  2. 濕度標籤 (Humidity)

    • Name: Humidity

    • Address: 40002

此時在 FUXA 的 Tag 列表中,應該就能即時看到從 Modbus Poll 傳過來的 2560

步驟 C:新增 MQTT 連線 (轉發數據)

  1. 在 FUXA 的 DevicesConnectivity 區塊(依版本不同),點擊 + Add device/client 並選擇 MQTT

  2. 設定參數:

    • Name: MQTT_Broker

    • URL: mqtt://broker.hivemq.com:1883 (此處以公共測試 Broker 為例)

    • Client ID: 可自訂(如 FUXA_Gateway_01

  3. 儲存並確認連線成功。

步驟 D:設定 Tag 的 MQTT 發布功能

  1. 編輯剛剛建立的 Temperature 標籤。

  2. 找到 PublishMQTT Export 相關設定。

  3. 啟用發布,並指定 Topic:

    • Topic: factory/sensor01/temperature

  4. 同理,將 Humidity 標籤設定發布至:

    • Topic: factory/sensor01/humidity

4. 第三階段:MQTT 軟體驗證 (接收端)

為了驗證數據是否成功經由 FUXA 轉發至 MQTT 網路,我們可以使用任意 MQTT 客戶端軟體(例如 MQTTXMQTT ExplorerAdvanced REST Client)來進行訂閱(Subscribe)。

  1. 打開您的 MQTT 軟體,連線至與 FUXA 相同的 Broker:

    • Host: broker.hivemq.com

    • Port: 1883

  2. 建立連線後,新增一個訂閱主題(Subscription):

    • Topic: factory/sensor01/# (使用萬用字元 # 可以同時接收該路徑下的所有資料)

  3. 驗證結果

    您將會在 MQTT 軟體的接收視窗中,看見定時推送過來的 JSON 格式或純文字訊息,例如:

    JSON
    { "value": 25, "timestamp": 1780000000000 }




















5. 通訊測試與連動驗證

當整體架構打通後,您可以進行以下測試來驗證通訊的即時性:

  1. 回到 Modbus Poll 軟體,將 40001 的數值從 25 修改為 28

  2. 查看 FUXA 的畫面,確認其畫面上顯示的 Temperature 標籤數值已同步變更為 28

  3. 查看 MQTT 軟體,確認收到一筆新的 Payload,其數值已更新為 28

透過這個範例,您成功實作了工業現場總線(Modbus)物聯網雲端協定(MQTT)的數據整合,這也是當前工業 4.0 智慧工廠最核心的通訊架構之一。

rfid nxp iso14443 的 編碼方式

 rfid nxp iso14443 的 編碼方式 https://ww1.microchip.com/downloads/en/devicedoc/doc2056.pdf import tkinter as tk from tkinter import ttk, message...