2025年11月7日 星期五

RFID 讀寫功能是通過模擬卡片記憶體和認證機制

 RFID 讀寫功能是通過模擬卡片記憶體和認證機制

 使用 Python tkinter 框架模擬 MIFARE 1K RFID 卡片讀寫個人數據的應用程式。

由於在單一的 Tkinter 程式中無法直接控制 MFRC522 硬體(這需要特定的硬體庫和底層通信),此範例的 RFID 讀寫功能是通過模擬卡片記憶體和認證機制來實現的,但其流程和邏輯與您提供的 C++ 範例保持一致。

模擬流程與 C++ 程式碼的對應關係:

C++ 程式碼動作Tkinter 模擬動作邏輯說明
key.keyByte[i] = 0xFF;self.CORRECT_KEY = "FFFFFFFFFFFF"模擬工廠預設的 Key A/B 密鑰。
mfrc522.PICC_IsNewCardPresent()點擊 "檢查卡片 (Check Card)" 按鈕模擬卡片進入讀卡器範圍。
mfrc522.PCD_Authenticate(...)點擊 "執行認證 (Authenticate)"檢查使用者輸入的 Key 是否與模擬密鑰相符。
mfrc522.MIFARE_Write(...)點擊 "寫入數據 (Write Data)"檢查認證狀態,如果成功,將數據寫入模擬記憶體。
mfrc522.MIFARE_Read(...)點擊 "讀取數據 (Read Data)"檢查認證狀態,如果成功,從模擬記憶體中讀取數據。

 

卡片資料結構

Mifare Classic 1k 卡片總共有 1024 Bytes;卡片總共被分成 16 個 Sectors,每個 Sector 有 64 Bytes;每個 Sector 又被分成 4 個 Blocks,每個 Block 有 16 Bytes。

每個 Sector 的 0-2 個 Block 是資料區塊 (Data block),可以用來存放資料,每個 Sector 的第 3 個 Block 是控制區塊 (Control block),用來存放存取控制資料,裡面有 Sector Key A、Sector Key B 及存取權限。

Data block 還可以被設定成特殊的 Value block,可以用來存放 32 位元的有號整數,值區域會對整數進行備份,避免資料發生錯誤,並且會有額外四個指令:遞增、遞減、備份、還原可以執行。

存取權限的部分在修改前,通常會建議直接透過一些工具直接計算權限(搜尋 MIFARE Classic 1K Access Bits Calculator 就能找到),避免不小心設定錯誤導致整個 Block 資料被鎖死(俗稱變磚)。

第 0 個 Sector 的第 0 個 Block 是特殊的 Block,被規劃成製造商區塊 (Manufacturer block),這個區塊會在製造時寫入一個唯讀的資料,通常用來存放卡片的 UID 及製造商資料。



這是一個使用 Python tkinter 框架模擬 MIFARE 1K RFID 卡片讀寫個人數據的應用程式。

由於在單一的 Tkinter 程式中無法直接控制 MFRC522 硬體(這需要特定的硬體庫和底層通信),此範例的 RFID 讀寫功能是通過模擬卡片記憶體和認證機制來實現的,但其流程和邏輯與您提供的 C++ 範例保持一致。

模擬流程與 C++ 程式碼的對應關係:

C++ 程式碼動作Tkinter 模擬動作邏輯說明
key.keyByte[i] = 0xFF;self.CORRECT_KEY = "FFFFFFFFFFFF"模擬工廠預設的 Key A/B 密鑰。
mfrc522.PICC_IsNewCardPresent()點擊 "檢查卡片 (Check Card)" 按鈕模擬卡片進入讀卡器範圍。
mfrc522.PCD_Authenticate(...)點擊 "執行認證 (Authenticate)"檢查使用者輸入的 Key 是否與模擬密鑰相符。
mfrc522.MIFARE_Write(...)點擊 "寫入數據 (Write Data)"檢查認證狀態,如果成功,將數據寫入模擬記憶體。
mfrc522.MIFARE_Read(...)點擊 "讀取數據 (Read Data)"檢查認證狀態,如果成功,從模擬記憶體中讀取數據。

我將在一個文件中生成完整的 Tkinter 應用程式。

RFID 數據讀寫模擬器
11月8日 下午12:09

程式碼執行說明:

  1. 運行環境: 執行上述 Python 腳本,它將啟動一個 GUI 窗口。

  2. 模擬步驟:

    • Step 1: 檢查卡片:點擊 "檢查卡片 (Check Card)" 按鈕,模擬 PCD 偵測到一張新卡片。

    • Step 2: 認證:保持密鑰為預設的 FFFFFFFFFFFF,點擊 "執行認證 (Authenticate)"。如果密鑰正確,認證狀態會變為綠色,並且紀錄上次認證的區塊地址。

    • Step 3: 寫入數據:在寫入欄位中輸入 32 個十六進制字元(或保持預設值),點擊 "寫入數據 (Write Data)"。這將更新模擬記憶體。

    • Step 4: 讀取數據:點擊 "讀取數據 (Read Data)"。模擬器將從記憶體中取出數據,並同時顯示其十六進制和 ASCII 轉換結果。

注意: 您必須先點擊 "檢查卡片",然後對目標 "區塊地址" 執行 "認證" 成功後,才能進行讀寫操作,這嚴格遵循了 C++ 範例中 PICC_IsNewCardPresent()PCD_Authenticate() 的邏輯順序。


import tkinter as tk

from tkinter import ttk, messagebox

import binascii


class MifareDataApp:

    def __init__(self, master):

        self.master = master

        master.title("MIFARE 1K 讀寫流程模擬 (Tkinter)")


        # --- 模擬卡片記憶體與密鑰 ---

        # 模擬 64 個區塊 (Blocks),每個區塊 16 個位元組,初始化為 '00'

        # 數據以 Hex String 儲存 (32 個字元)

        self.simulated_card_memory = ["00" * 16] * 64

        # 模擬預設密鑰 (Key A/B),與 C++ 範例的 FFFFFFFFFFFF 對應

        self.CORRECT_KEY = "FFFFFFFFFFFF"

        

        # 狀態追蹤

        self.is_card_present = False

        self.is_authenticated = False

        self.auth_block = -1 # 紀錄上次認證成功的區塊

        

        self.create_widgets()

        self.update_status()


    def create_widgets(self):

        # 設定樣式

        style = ttk.Style()

        style.configure('TButton', font=('Arial', 10), padding=5)

        style.configure('TLabel', font=('Arial', 10), padding=2)

        style.configure('Title.TLabel', font=('Arial', 12, 'bold'))

        

        main_frame = ttk.Frame(self.master, padding="10 10 10 10")

        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))


        # 1. 狀態區

        status_frame = ttk.LabelFrame(main_frame, text="卡片狀態", padding="10")

        status_frame.grid(row=0, column=0, columnspan=2, sticky='ew', pady=10)


        self.card_status_var = tk.StringVar(value="未連接")

        self.auth_status_var = tk.StringVar(value="未認證")

        

        ttk.Label(status_frame, text="卡片存在狀態:").grid(row=0, column=0, sticky='w')

        ttk.Label(status_frame, textvariable=self.card_status_var, foreground='red').grid(row=0, column=1, sticky='w')


        ttk.Label(status_frame, text="認證狀態:").grid(row=1, column=0, sticky='w')

        ttk.Label(status_frame, textvariable=self.auth_status_var, foreground='red').grid(row=1, column=1, sticky='w')

        

        ttk.Button(status_frame, text="檢查卡片 (Check Card)", command=self.check_card).grid(row=0, column=2, rowspan=2, padx=20)



        # 2. 參數輸入區

        param_frame = ttk.LabelFrame(main_frame, text="操作參數", padding="10")

        param_frame.grid(row=1, column=0, sticky='ew', padx=5, pady=5)

        

        # 區塊地址

        ttk.Label(param_frame, text="區塊地址 (0-63):").grid(row=0, column=0, sticky='w', padx=5, pady=5)

        self.block_address_var = tk.StringVar(value="2") # 預設 Block 2

        ttk.Entry(param_frame, textvariable=self.block_address_var, width=5).grid(row=0, column=1, sticky='w', padx=5, pady=5)

        

        # 密鑰

        ttk.Label(param_frame, text="密鑰 Key (12 Hex):").grid(row=1, column=0, sticky='w', padx=5, pady=5)

        self.key_var = tk.StringVar(value=self.CORRECT_KEY) # 預設正確密鑰

        ttk.Entry(param_frame, textvariable=self.key_var, width=15).grid(row=1, column=1, sticky='w', padx=5, pady=5)


        # 3. 寫入數據區

        write_frame = ttk.LabelFrame(main_frame, text="寫入數據 (16 Bytes / 32 Hex Characters)", padding="10")

        write_frame.grid(row=2, column=0, sticky='ew', padx=5, pady=5)

        

        self.data_to_write_var = tk.StringVar(value=self.string_to_hex("Rui Santos - RNT", 16))

        

        ttk.Entry(write_frame, textvariable=self.data_to_write_var, width=35).grid(row=0, column=0, columnspan=2, pady=5)

        

        ttk.Button(write_frame, text="執行認證 (Authenticate)", command=self.authenticate).grid(row=1, column=0, pady=5, sticky='ew')

        ttk.Button(write_frame, text="寫入數據 (Write Data)", command=self.write_data).grid(row=1, column=1, pady=5, sticky='ew')


        # 4. 讀取數據區

        read_frame = ttk.LabelFrame(main_frame, text="讀取數據", padding="10")

        read_frame.grid(row=1, column=1, rowspan=2, sticky='nsew', padx=5, pady=5)

        

        self.read_data_var = tk.StringVar(value="")

        self.read_ascii_var = tk.StringVar(value="")


        ttk.Button(read_frame, text="讀取數據 (Read Data)", command=self.read_data).grid(row=0, column=0, sticky='ew', pady=5)

        

        ttk.Label(read_frame, text="Hex (32 Chars):", font=('Courier', 10)).grid(row=1, column=0, sticky='w', pady=(10, 2))

        ttk.Label(read_frame, textvariable=self.read_data_var, wraplength=200, foreground='green', font=('Courier', 10)).grid(row=2, column=0, sticky='w', padx=5)


        ttk.Label(read_frame, text="ASCII 轉換:", font=('Courier', 10)).grid(row=3, column=0, sticky='w', pady=(10, 2))

        ttk.Label(read_frame, textvariable=self.read_ascii_var, wraplength=200, foreground='green', font=('Courier', 10)).grid(row=4, column=0, sticky='w', padx=5)


        # 5. 訊息日誌

        self.log_text = tk.StringVar(value="等待操作...")

        ttk.Label(main_frame, textvariable=self.log_text, wraplength=480, justify=tk.LEFT, foreground='black', background='#f0f0f0').grid(row=3, column=0, columnspan=2, sticky='ew', pady=10, padx=5)

        

        # 讓列寬度自適應

        main_frame.grid_columnconfigure(0, weight=1)

        main_frame.grid_columnconfigure(1, weight=1)


    def update_status(self):

        # 更新卡片和認證狀態顯示

        self.card_status_var.set("存在" if self.is_card_present else "未連接")

        self.auth_status_var.set(f"成功 ({self.auth_block})" if self.is_authenticated else "未認證")

        

        # 設置顏色

        card_color = 'green' if self.is_card_present else 'red'

        auth_color = 'green' if self.is_authenticated else 'red'

        

        self.master.winfo_children()[0].winfo_children()[0].winfo_children()[1].config(foreground=card_color)

        self.master.winfo_children()[0].winfo_children()[0].winfo_children()[3].config(foreground=auth_color)


    def log_message(self, message):

        self.log_text.set(message)

        

    def string_to_hex(self, s, length):

        # 將字串轉換為 Hex,並用 '00' 填充至指定長度

        hex_str = binascii.hexlify(s.encode('ascii')).decode('utf-8').upper()

        # 確保長度為 16 bytes (32 hex chars)

        return hex_str.ljust(length * 2, '0')


    def hex_to_string(self, hex_str):

        # 將 Hex 轉換為字串,並移除末尾的空位元組 (0x00)

        try:

            bytes_data = binascii.unhexlify(hex_str.encode('utf-8'))

            return bytes_data.decode('ascii').rstrip('\x00')

        except binascii.Error:

            return "數據錯誤"


    def validate_inputs(self, check_auth=True):

        # 檢查區塊地址

        try:

            block = int(self.block_address_var.get())

            if not (0 <= block <= 63):

                self.log_message("錯誤:區塊地址必須在 0 到 63 之間。")

                return None, None

        except ValueError:

            self.log_message("錯誤:區塊地址無效。")

            return None, None

            

        # 檢查密鑰長度

        key = self.key_var.get().upper()

        if len(key) != 12 or not all(c in '0123456789ABCDEF' for c in key):

            self.log_message("錯誤:密鑰必須是 12 個十六進制字元。")

            return None, None

            

        # 檢查卡片和認證狀態

        if not self.is_card_present:

            self.log_message("錯誤:請先檢查卡片是否在場 (PICC_IsNewCardPresent)。")

            return None, None

            

        if check_auth and (not self.is_authenticated or self.auth_block != block):

            self.log_message(f"錯誤:請先使用 Key A/B 認證區塊 {block} (PCD_Authenticate)。")

            return None, None

            

        return block, key


    # --- 模擬 MFRC522 API 函式 ---


    def check_card(self):

        # 模擬 PICC_IsNewCardPresent() 和 PICC_ReadCardSerial()

        self.is_card_present = True

        self.is_authenticated = False # 卡片重新連線,認證失效

        self.auth_block = -1

        self.update_status()

        self.log_message("偵測到新的 MIFARE 1K 卡片。UID: F1AABBCC (模擬)。請執行認證。")



    def authenticate(self):

        # 模擬 PCD_Authenticate(0x60, blockAddress, &key, &(mfrc522.uid))

        block, key = self.validate_inputs(check_auth=False)

        if block is None:

            return


        if key == self.CORRECT_KEY:

            self.is_authenticated = True

            self.auth_block = block

            self.update_status()

            self.log_message(f"認證成功!區塊 {block} 已成功認證,可以使用讀寫操作。")

        else:

            self.is_authenticated = False

            self.auth_block = -1

            self.update_status()

            self.log_message("認證失敗 (Authentication failed)。密鑰不正確。")



    def write_data(self):

        # 模擬 MIFARE_Write(blockAddress, newBlockData, 16)

        block, key = self.validate_inputs(check_auth=True)

        if block is None:

            return

            

        # 檢查數據長度

        data_hex = self.data_to_write_var.get().upper()

        if len(data_hex) != 32 or not all(c in '0123456789ABCDEF' for c in data_hex):

            self.log_message("錯誤:寫入數據必須是 32 個十六進制字元 (16 Bytes)。")

            return


        # 執行寫入

        self.simulated_card_memory[block] = data_hex

        self.log_message(f"數據寫入成功 (Data written successfully)!區塊 {block} 的數據已更新。")



    def read_data(self):

        # 模擬 MIFARE_Read(blockAddress, blockDataRead, &bufferblocksize)

        block, key = self.validate_inputs(check_auth=True)

        if block is None:

            return


        # 執行讀取

        read_hex = self.simulated_card_memory[block]

        read_ascii = self.hex_to_string(read_hex)


        self.read_data_var.set(read_hex)

        self.read_ascii_var.set(read_ascii)


        self.log_message(f"讀取成功 (Read successfully)!已從區塊 {block} 讀取 16 Bytes 數據。")



if __name__ == "__main__":

    root = tk.Tk()

    app = MifareDataApp(root)

    root.mainloop()


沒有留言:

張貼留言

ESP32 (ESP-IDF in VS Code) MFRC522 + MQTT + PYTHON TKinter +SQLite

 ESP32 (ESP-IDF in VS Code) MFRC522 + MQTT + PYTHON TKinter +SQLite  ESP32 VS Code 程式 ; PlatformIO Project Configuration File ; ;   Build op...