From 0ea4139f20707c351c34da68233601c5974d938b Mon Sep 17 00:00:00 2001 From: kicer Date: Wed, 15 Apr 2026 12:06:20 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E5=8A=9F=E8=83=BD=E5=AE=8C?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- protocol.md | 163 +++++++++------- requirements.txt | 1 + rfid_tester.py | 493 ++++++++++++++++++++++++++++++++--------------- 3 files changed, 428 insertions(+), 229 deletions(-) create mode 100644 requirements.txt diff --git a/protocol.md b/protocol.md index 70057a0..cac8c24 100644 --- a/protocol.md +++ b/protocol.md @@ -12,17 +12,17 @@ 主机发送的命令帧结构如下(从 Report ID 后开始计算偏移)。**SetReq 必须填充至 256 字节**,不足部分用 `0x00` 补齐。 -| 偏移 | 长度 | 字段名 | 字节序 | 示例(打开蜂鸣器) | 说明 | -|:---|:---|:---|:---|:---|:---| -| 0x00 | 1 | Report ID | - | `01` | 固定 `0x01` | -| 0x01 | 4 | Fixed | - | `00 00 00 00` | 固定 | -| 0x05 | 2 | Frame Length | **大端** | `00 07` | 从Constant到Data的总字节数 | -| 0x07 | 2 | Constant | - | `00 02` | 固定 `0x00 0x02` | -| 0x09 | 2 | Data Length | **大端** | `00 02` | 后续数据负载的字节数 | -| 0x0B | N | Data | - | `cd 01` | 实际数据负载 | -| 0x0B+N | 1 | Checksum | - | `ce` | 校验字节,算法见第 3 节 | -| 0x0C+N | 1 | End Marker | - | `03` | 固定 `0x03` | -| 剩余 | - | Padding | - | `00 ...` | 填充至 **256 字节** | +| 偏移 | 长度 | 字段名 | 字节序 | 示例(打开蜂鸣器) | 说明 | +| :----- | :--- | :----------- | :------- | :----------------- | :------------------------- | +| 0x00 | 1 | Report ID | - | `01` | 固定 `0x01` | +| 0x01 | 4 | Fixed | - | `00 00 00 00` | 固定 | +| 0x05 | 2 | Frame Length | **大端** | `00 07` | 从Constant到Data的总字节数 | +| 0x07 | 2 | Constant | - | `00 02` | 固定 `0x00 0x02` | +| 0x09 | 2 | Data Length | **大端** | `00 02` | 后续数据负载的字节数 | +| 0x0B | N | Data | - | `cd 01` | 实际数据负载 | +| 0x0B+N | 1 | Checksum | - | `ce` | 校验字节,算法见第 3 节 | +| 0x0C+N | 1 | End Marker | - | `03` | 固定 `0x03` | +| 剩余 | - | Padding | - | `00 ...` | 填充至 **256 字节** | ### 2.2 GetRes(设备 → 主机,Report ID = 0x03) @@ -30,11 +30,10 @@ **Data 段格式**: -| 偏移 | 长度 | 字段名 | 说明 | -|:---|:---|:---|:---| -| 0x00 | 1 | Status | `0x00` 成功,非零异常 | -| 0x01 | M | Response Data | 实际返回数据 | - +| 偏移 | 长度 | 字段名 | 说明 | +| :--- | :--- | :------------ | :-------------------- | +| 0x00 | 1 | Status | `0x00` 成功,非零异常 | +| 0x01 | M | Response Data | 实际返回数据 | ## 3. 校验算法 @@ -44,6 +43,7 @@ **计算范围**:`Data Length` 两字节+ `Data` 全部字节。 **验证示例**(打开蜂鸣器): + - Data Length: `00 02` - Data: `cd 01` - XOR: `00 ^ 02 ^ cd ^ 01 = ce` ✅ @@ -59,6 +59,7 @@ **算法**:CRC-16/GENIBUS **参数**: + - 多项式:`0x1021` - 初始值:`0xFFFF` - 输出异或:`0xFFFF` @@ -72,49 +73,51 @@ ### 4.1 系统与配置类命令 -| 功能 | SetReq Data | GetRes Data | 备注 | -|:---|:---|:---|:---| -| 读版本号 | `c0` | `00` + ASCII 字符串 | | -| 恢复出厂设置 | `cf` | `00 80` | | -| 读取格式 | `83` | `00 01 00 00 00 01 01 00 00`(示例) | 返回当前格式参数 | -| 设置格式 | `82 0f 00 00 00 01 01 01 00` | `00 80` | 参数含义略 | +| 功能 | SetReq Data | GetRes Data | 备注 | +| :----------- | :--------------------------- | :----------------------------------- | :--------------- | +| 读版本号 | `c0` | `00` + ASCII 字符串 | | +| 恢复出厂设置 | `cf` | `00 80` | | +| 读取格式 | `83` | `00 01 00 00 00 01 01 00 00`(示例) | 返回当前格式参数 | +| 设置格式 | `82 0f 00 00 00 01 01 01 00` | `00 80` | 参数含义略 | ### 4.2 蜂鸣器控制 -| 功能 | SetReq Data | GetRes Data | -|:---|:---|:---| -| 打开蜂鸣器 | `cd 01` | `00 80` | -| 关闭蜂鸣器 | `cd 00` | `00 80` | +| 功能 | SetReq Data | GetRes Data | +| :--------- | :---------- | :---------- | +| 打开蜂鸣器 | `cd 01` | `00 80` | +| 关闭蜂鸣器 | `cd 00` | `00 80` | ### 4.3 射频电源控制 -| 功能 | SetReq Data | GetRes Data | -|:---|:---|:---| -| 打开射频 | `90 01` | `00 80` | -| 关闭射频 | `90 00` | `00 80` | +| 功能 | SetReq Data | GetRes Data | +| :------- | :---------- | :---------- | +| 打开射频 | `90 01` | `00 80` | +| 关闭射频 | `90 00` | `00 80` | ### 4.4 工作模式与功率 -| 功能 | SetReq Data | GetRes Data | 参数说明 | -|:---|:---|:---|:---| -| 设置单标签巡查 | `0f 01` | `00 80` | 模式 1(主动读卡) | -| 设置被动模式 | `0f 02` | `00 80` | 模式 2(被动读卡)| -| 设置多标签巡查 | `0f 03` | `00 80` | 模式 3 | -| 设置功率 = N | `cc N` | `00 80` | N 范围 0~9 | +| 功能 | SetReq Data | GetRes Data | 参数说明 | +| :------------- | :---------- | :---------- | :----------- | +| 主动读卡 | `0f 01` | `00 80` | 触发单次寻卡 | +| 被动读卡 | `0f 02` | `00 80` | 持续等待标签 | +| 设置单标签巡查 | `0f 01` | `00 80` | 模式 1 | +| 设置被动模式 | `0f 02` | `00 80` | 模式 2 | +| 设置多标签巡查 | `0f 03` | `00 80` | 模式 3 | +| 设置功率 = N | `cc N` | `00 80` | N 范围 0~9 | ### 4.5 EPC 操作 所有 EPC 相关命令(读、选中、写)的 SetReq/GetRes Data 段均遵循以下内部结构: -| 偏移 | 长度 | 字段名 | 字节序 | 说明 | -|:---|:---|:---|:---|:---| -| 0x00 | 1 | Command/Status | - | 固定 SetReq=`ce`,GetRes=`00`(成功) | -| 0x01 | 1 | Magic | - | 固定 `bb` | -| 0x02 | 2 | Card Op Command | - | 操作码(见各子命令) | -| 0x04 | 2 | Internal Length | **大端** | 后续 Payload 的字节数 | -| 0x06 | N | Payload | - | 具体操作参数 | -| 0x06+N | 1 | Payload Checksum | - | 累加和(模 256) | -| 0x07+N | 1 | End Marker | - | 固定 `7e` | +| 偏移 | 长度 | 字段名 | 字节序 | 说明 | +| :----- | :--- | :--------------- | :------- | :---------------------------------- | +| 0x00 | 1 | Command/Status | - | 固定 SetReq=`ce`,GetRes=`00`(成功) | +| 0x01 | 1 | Magic | - | 固定 `bb` | +| 0x02 | 2 | Card Op Command | - | 操作码(见各子命令) | +| 0x04 | 2 | Internal Length | **大端** | 后续 Payload 的字节数 | +| 0x06 | N | Payload | - | 具体操作参数 | +| 0x06+N | 1 | Payload Checksum | - | 累加和(模 256) | +| 0x07+N | 1 | End Marker | - | 固定 `7e` | #### 4.5.1 读取 EPC @@ -125,22 +128,24 @@ - Payload Checksum = `22` - **GetRes Data 段(有卡)**: + ``` 00 bb 02 22 00 0b d3 18 00 11 22 33 44 55 66 75 ce c2 7e ``` - | 偏移 | 字段 | 值示例 | 说明 | - |:---|:---|:---|:---| - | 0x00 | Status | `00` | 成功 | - | 0x01 | Magic | `bb` | 固定 | - | 0x02 | Card Op Resp | `02 22` | 读卡成功标志 | - | 0x04 | Internal Length | `00 0b` | 后续数据长度(大端) | - | 0x06 | RSSI | `d3` | 信号强度 | - | 0x07 | EPC Len Indicator | `18` | EPC 字节数 = 值 ÷ 4 | - | 0x08 | EPC Status | `00` | 成功 | - | 0x09 | EPC Data | `11 22 33 44 55 66` | 实际 EPC | - | 0x0F | EPC CRC | `75 ce` | CRC-16/GENIBUS | - | 0x11 | Payload Checksum | `c2` | 累加和 | - | 0x12 | End Marker | `7e` | 固定尾部 | + + | 偏移 | 字段 | 值示例 | 说明 | + | :--- | :---------------- | :------------------ | :------------------- | + | 0x00 | Status | `00` | 成功 | + | 0x01 | Magic | `bb` | 固定 | + | 0x02 | Card Op Resp | `02 22` | 读卡成功标志 | + | 0x04 | Internal Length | `00 0b` | 后续数据长度(大端) | + | 0x06 | RSSI | `d3` | 信号强度 | + | 0x07 | EPC Len Indicator | `18` | EPC 字节数 = 值 ÷ 4 | + | 0x08 | EPC Status | `00` | 成功 | + | 0x09 | EPC Data | `11 22 33 44 55 66` | 实际 EPC | + | 0x0F | EPC CRC | `75 ce` | CRC-16/GENIBUS | + | 0x11 | Payload Checksum | `c2` | 累加和 | + | 0x12 | End Marker | `7e` | 固定尾部 | - **无卡时**:Card Op Resp = `01 ff`,Payload 仅含 RSSI 及累加和,无 EPC 数据。 @@ -149,9 +154,11 @@ **Card Op Command**:`00 0c` - **SetReq Data 示例**(选中 EPC `11 22`): + ``` ce bb 00 0c 00 09 01 00 00 00 20 10 00 11 22 79 7e ``` + - Internal Length = `00 09` - Payload(7+N 字节):7字节保留字段 + EPC Data - Payload Checksum = `79` @@ -160,6 +167,7 @@ ``` 00 bb 01 0c 00 01 00 0e 7e ``` + - Card Op Resp = `01 0c` - Internal Length = `00 01` - Payload = `00`(选中成功标志) @@ -169,9 +177,11 @@ **Card Op Command**:`00 49` - **SetReq Data 示例**(将 EPC `11 22` 改为 `11 23`): + ``` ce bb 00 49 00 0f 00 00 00 00 01 00 00 00 03 ca 9e 08 00 11 23 00 7e ``` + - Internal Length = `00 0f` - Payload(15 字节): | 偏移 | 字段 | 值示例 | 说明 | @@ -188,27 +198,30 @@ ``` 00 bb 01 49 00 06 04 08 00 11 22 00 8f 7e ``` + - Card Op Resp = `01 49` - - Payload 偏移0x03中开始原 EPC `11 22` 用于确认 + - Payload 偏移0x03中开始原 EPC `11 22` 用于确认不重复 ## 5. 附录:命令速查表(SetReq Data 部分) -| 功能 | Data(十六进制) | -|:---|:---| -| 读版本号 | `c0` | -| 恢复出厂设置 | `cf` | -| 读取格式 | `83` | -| 设置格式 | `82 0f 00 00 00 01 01 01 00` | -| 打开蜂鸣器 | `cd 01` | -| 关闭蜂鸣器 | `cd 00` | -| 打开射频 | `90 01` | -| 关闭射频 | `90 00` | -| 设置单标签巡查 | `0f 01` | -| 设置被动模式 | `0f 02` | -| 设置多标签巡查 | `0f 03` | -| 设置功率 = 8 | `cc 08` | -| 读取 EPC | `ce bb 00 22 00 00 22 7e` | -| 选中卡(EPC=1122) | `ce bb 00 0c 00 09 01 00 00 00 20 10 00 11 22 79 7e` | +| 功能 | Data(十六进制) | +| :------------------ | :--------------------------------------------------------------------- | +| 读版本号 | `c0` | +| 恢复出厂设置 | `cf` | +| 读取格式 | `83` | +| 设置格式 | `82 0f 00 00 00 01 01 01 00` | +| 打开蜂鸣器 | `cd 01` | +| 关闭蜂鸣器 | `cd 00` | +| 打开射频 | `90 01` | +| 关闭射频 | `90 00` | +| 主动读卡 | `0f 01` | +| 被动读卡 | `0f 02` | +| 设置单标签巡查 | `0f 01` | +| 设置被动模式 | `0f 02` | +| 设置多标签巡查 | `0f 03` | +| 设置功率 = 8 | `cc 08` | +| 读取 EPC | `ce bb 00 22 00 00 22 7e` | +| 选中卡(EPC=1122) | `ce bb 00 0c 00 09 01 00 00 00 20 10 00 11 22 79 7e` | | 写 EPC(1122→1123) | `ce bb 00 49 00 0f 00 00 00 00 01 00 00 00 03 ca 9e 08 00 11 23 00 7e` | --- diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..daccb3d --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +hidapi diff --git a/rfid_tester.py b/rfid_tester.py index 03a6a84..a314cae 100644 --- a/rfid_tester.py +++ b/rfid_tester.py @@ -5,19 +5,21 @@ MT6 RFID 读卡器测试程序 基于 USB HID 私有协议 """ -import sys import os +import sys import time # 添加 PySimpleGUI 目录到路径 -sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'PySimpleGUI')) +sys.path.insert( + 0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "PySimpleGUI") +) import PySimpleGUI as sg try: import hid except ImportError: - sg.popup_error('请安装 hidapi 库:\npip install hidapi') + sg.popup_error("请安装 hidapi 库:\npip install hidapi") sys.exit(1) @@ -32,6 +34,7 @@ PRODUCT_ID = 0x0035 # 默认PID # ==================== 协议函数 ==================== + def calc_outer_checksum(data_len_bytes: bytes, payload: bytes) -> int: """计算外层帧校验(XOR)""" result = 0 @@ -40,6 +43,20 @@ def calc_outer_checksum(data_len_bytes: bytes, payload: bytes) -> int: return result +def calc_epc_crc(epc_len_indicator: int, epc_status: int, epc_data: bytes) -> int: + data = bytes([epc_len_indicator, epc_status]) + epc_data + crc = 0xFFFF + for byte in data: + crc ^= byte << 8 + for _ in range(8): + if crc & 0x8000: + crc = (crc << 1) ^ 0x1021 + else: + crc = crc << 1 + crc &= 0xFFFF + return crc ^ 0xFFFF + + def build_setreq_frame(data: bytes) -> bytes: """ 构建 SetReq 帧 @@ -49,10 +66,10 @@ def build_setreq_frame(data: bytes) -> bytes: # Frame Length: 从 0x07 到 Checksum(包含校验和,不含结束符) # = 2 (constant) + 2 (data length) + len(data) + 1 (checksum) frame_length = 5 + len(data) - frame_length_bytes = frame_length.to_bytes(2, 'big') + frame_length_bytes = frame_length.to_bytes(2, "big") # Data Length - data_length_bytes = len(data).to_bytes(2, 'big') + data_length_bytes = len(data).to_bytes(2, "big") # Checksum checksum = calc_outer_checksum(data_length_bytes, data) @@ -60,13 +77,13 @@ def build_setreq_frame(data: bytes) -> bytes: # 构建帧 frame = bytearray(REPORT_SIZE) frame[0] = REPORT_ID_SET_REQ - frame[1:5] = b'\x00\x00\x00\x00' # Fixed + frame[1:5] = b"\x00\x00\x00\x00" # Fixed frame[5:7] = frame_length_bytes # Frame Length - frame[7:9] = b'\x00\x02' # Constant + frame[7:9] = b"\x00\x02" # Constant frame[9:11] = data_length_bytes # Data Length - frame[11:11+len(data)] = data # Data - frame[11+len(data)] = checksum # Checksum - frame[12+len(data)] = 0x03 # End Marker + frame[11 : 11 + len(data)] = data # Data + frame[11 + len(data)] = checksum # Checksum + frame[12 + len(data)] = 0x03 # End Marker return bytes(frame) @@ -87,8 +104,8 @@ def parse_getres_frame(data: bytes) -> tuple: return None # 解析帧结构(大端序) - frame_length = int.from_bytes(data[5:7], 'big') - data_length = int.from_bytes(data[9:11], 'big') + frame_length = int.from_bytes(data[5:7], "big") + data_length = int.from_bytes(data[9:11], "big") # 检查数据长度是否合理 expected_total_len = 11 + data_length + 2 # header + data + checksum + end_marker @@ -97,11 +114,11 @@ def parse_getres_frame(data: bytes) -> tuple: return None # 提取 Data 段 - response_data = data[11:11+data_length] + response_data = data[11 : 11 + data_length] # 提取 Checksum 和 End Marker - received_checksum = data[11+data_length] - received_end_marker = data[12+data_length] + received_checksum = data[11 + data_length] + received_end_marker = data[12 + data_length] # 验证 End Marker(固定为 0x03) if received_end_marker != 0x03: @@ -111,10 +128,12 @@ def parse_getres_frame(data: bytes) -> tuple: # 计算并验证 Checksum(XOR算法:Data Length两字节 + Data全部字节) calculated_checksum = calc_outer_checksum(data[9:11], response_data) if calculated_checksum != received_checksum: - print(f"[DEBUG] Checksum错误: 计算=0x{calculated_checksum:02x}, 接收=0x{received_checksum:02x}") + print( + f"[DEBUG] Checksum错误: 计算=0x{calculated_checksum:02x}, 接收=0x{received_checksum:02x}" + ) return None - print(f"[DEBUG] 帧校验通过: Checksum=0x{received_checksum:02x}, EndMarker=0x{received_end_marker:02x}") + # print(f"[DEBUG] 帧校验通过: Checksum=0x{received_checksum:02x}, EndMarker=0x{received_end_marker:02x}") if len(response_data) < 1: return None @@ -125,14 +144,15 @@ def parse_getres_frame(data: bytes) -> tuple: # ==================== 命令函数 ==================== + def cmd_get_version() -> bytes: """读版本号命令""" - return bytes([0xc0]) + return bytes([0xC0]) def cmd_factory_reset() -> bytes: """恢复出厂设置命令""" - return bytes([0xcf]) + return bytes([0xCF]) def cmd_read_format() -> bytes: @@ -142,12 +162,12 @@ def cmd_read_format() -> bytes: def cmd_set_format() -> bytes: """设置格式命令""" - return bytes([0x82, 0x0f, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00]) + return bytes([0x82, 0x0F, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00]) def cmd_buzzer(on: bool) -> bytes: """蜂鸣器控制命令""" - return bytes([0xcd, 0x01 if on else 0x00]) + return bytes([0xCD, 0x01 if on else 0x00]) def cmd_rf_power(on: bool) -> bytes: @@ -158,7 +178,7 @@ def cmd_rf_power(on: bool) -> bytes: def cmd_set_power(power: int) -> bytes: """设置功率命令 (0-9)""" power = max(0, min(9, power)) - return bytes([0xcc, power]) + return bytes([0xCC, power]) def cmd_set_mode(mode: int) -> bytes: @@ -167,12 +187,12 @@ def cmd_set_mode(mode: int) -> bytes: 2: 被动模式 3: 多标签巡查 """ - return bytes([0x0f, mode]) + return bytes([0x0F, mode]) def cmd_read_epc() -> bytes: """读取 EPC 命令""" - return bytes([0xce, 0xbb, 0x00, 0x22, 0x00, 0x00, 0x22, 0x7e]) + return bytes([0xCE, 0xBB, 0x00, 0x22, 0x00, 0x00, 0x22, 0x7E]) def cmd_select_card(epc_data: bytes) -> bytes: @@ -181,14 +201,20 @@ def cmd_select_card(epc_data: bytes) -> bytes: payload = bytes([0x01, 0x00, 0x00, 0x00, 0x20, 0x10, 0x00]) + epc_data # 计算 checksum(包含 Card Op Command + Internal Length + Payload) - card_op_cmd = bytes([0x00, 0x0c]) - internal_len = len(payload).to_bytes(2, 'big') + card_op_cmd = bytes([0x00, 0x0C]) + internal_len = len(payload).to_bytes(2, "big") checksum = 0 for b in card_op_cmd + internal_len + payload: checksum = (checksum + b) & 0xFF - return bytes([0xce, 0xbb]) + card_op_cmd + internal_len + payload + bytes([checksum, 0x7e]) + return ( + bytes([0xCE, 0xBB]) + + card_op_cmd + + internal_len + + payload + + bytes([checksum, 0x7E]) + ) def cmd_write_epc(old_epc_crc: bytes, new_epc: bytes) -> bytes: @@ -200,7 +226,9 @@ def cmd_write_epc(old_epc_crc: bytes, new_epc: bytes) -> bytes: word_count = 2 + len(new_epc) // 2 - payload = bytes([0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]) # Reserved (8 bytes) + payload = bytes( + [0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00] + ) # Reserved (8 bytes) payload += bytes([word_count]) # Word Count payload += old_epc_crc # Old EPC CRC (大端序) payload += bytes([epc_len_indicator, 0x00]) # EPC Len Indicator, Status @@ -208,17 +236,24 @@ def cmd_write_epc(old_epc_crc: bytes, new_epc: bytes) -> bytes: # 计算 checksum(包含 Card Op Command + Internal Length + Payload) card_op_cmd = bytes([0x00, 0x49]) - internal_len = len(payload).to_bytes(2, 'big') + internal_len = len(payload).to_bytes(2, "big") checksum = 0 for b in card_op_cmd + internal_len + payload: checksum = (checksum + b) & 0xFF - return bytes([0xce, 0xbb]) + card_op_cmd + internal_len + payload + bytes([checksum, 0x7e]) + return ( + bytes([0xCE, 0xBB]) + + card_op_cmd + + internal_len + + payload + + bytes([checksum, 0x7E]) + ) # ==================== 设备管理类 ==================== + class RFIDDevice: def __init__(self): self.device = None @@ -234,15 +269,17 @@ class RFIDDevice: devices = [] try: for d in hid.enumerate(): - devices.append({ - 'vendor_id': d['vendor_id'], - 'product_id': d['product_id'], - 'manufacturer_string': d.get('manufacturer_string', ''), - 'product_string': d.get('product_string', ''), - 'serial_number': d.get('serial_number', ''), - 'interface_number': d.get('interface_number', -1), - 'path': d['path'] - }) + devices.append( + { + "vendor_id": d["vendor_id"], + "product_id": d["product_id"], + "manufacturer_string": d.get("manufacturer_string", ""), + "product_string": d.get("product_string", ""), + "serial_number": d.get("serial_number", ""), + "interface_number": d.get("interface_number", -1), + "path": d["path"], + } + ) except Exception as e: print(f"枚举设备失败: {e}") return devices @@ -259,8 +296,13 @@ class RFIDDevice: self.device.open(self.vendor_id, self.product_id) self.current_path = None self.current_interface = -1 - print(f"[DEBUG] 通过VID/PID连接: VID=0x{self.vendor_id:04x}, PID=0x{self.product_id:04x}") - return True, f"已连接到设备 (VID: 0x{self.vendor_id:04x}, PID: 0x{self.product_id:04x}) - 注意:可能连接到任意接口" + print( + f"[DEBUG] 通过VID/PID连接: VID=0x{self.vendor_id:04x}, PID=0x{self.product_id:04x}" + ) + return ( + True, + f"已连接到设备 (VID: 0x{self.vendor_id:04x}, PID: 0x{self.product_id:04x}) - 注意:可能连接到任意接口", + ) except Exception as e: print(f"[DEBUG] VID/PID连接失败: {e}") return False, f"连接失败: {e}" @@ -273,7 +315,7 @@ class RFIDDevice: if isinstance(path, bytes): path_str = path else: - path_str = path.encode('utf-8') if isinstance(path, str) else path + path_str = path.encode("utf-8") if isinstance(path, str) else path self.device.open_path(path_str) self.current_path = path @@ -289,8 +331,15 @@ class RFIDDevice: print(f"[DEBUG] 制造商: {info}") print(f"[DEBUG] 产品: {product}") - path_display = path.decode('utf-8', errors='ignore') if isinstance(path, bytes) else str(path) - return True, f"已连接到设备 (IF={interface_number}, 路径: ...{path_display[-30:]})" + path_display = ( + path.decode("utf-8", errors="ignore") + if isinstance(path, bytes) + else str(path) + ) + return ( + True, + f"已连接到设备 (IF={interface_number}, 路径: ...{path_display[-30:]})", + ) except Exception as e: print(f"[DEBUG] 路径连接失败: {e}") return False, f"连接失败: {e}" @@ -311,17 +360,17 @@ class RFIDDevice: def is_connected(self): """检查是否已连接""" if self.device: - #print(f"[DEBUG] 设备已连接 - 接口: {self.current_interface}, 路径: {self.current_path}") + # print(f"[DEBUG] 设备已连接 - 接口: {self.current_interface}, 路径: {self.current_path}") return True return False def get_connection_info(self): """获取当前连接信息""" return { - 'interface': self.current_interface, - 'path': self.current_path, - 'vendor_id': self.vendor_id, - 'product_id': self.product_id + "interface": self.current_interface, + "path": self.current_path, + "vendor_id": self.vendor_id, + "product_id": self.product_id, } def send_command(self, cmd_data: bytes): @@ -351,80 +400,111 @@ class RFIDDevice: # ==================== GUI 界面 ==================== + def create_window(): """创建主窗口""" - sg.theme('LightBlue2') + sg.theme("LightBlue2") # 设备连接区域 device_frame = [ - [sg.Text('VID (十六进制):'), sg.Input('FFFF', size=(8, 1), key='-VID-'), - sg.Text('PID (十六进制):'), sg.Input('0035', size=(8, 1), key='-PID-'), - sg.Button('连接', key='-CONNECT-'), - sg.Button('断开', key='-DISCONNECT-'), - sg.Button('枚举设备', key='-ENUM-')], - [sg.Text('状态: ', size=(12, 1)), sg.Text('未连接', key='-STATUS-', text_color='red')] + [ + sg.Text("VID (十六进制):"), + sg.Input("FFFF", size=(8, 1), key="-VID-"), + sg.Text("PID (十六进制):"), + sg.Input("0035", size=(8, 1), key="-PID-"), + sg.Button("连接", key="-CONNECT-"), + sg.Button("断开", key="-DISCONNECT-"), + sg.Button("枚举设备", key="-ENUM-"), + ], + [ + sg.Text("状态: ", size=(12, 1)), + sg.Text("未连接", key="-STATUS-", text_color="red"), + ], ] # 基本命令区域 basic_frame = [ - [sg.Button('读取版本号', key='-VERSION-', size=(12, 1)), - sg.Button('恢复出厂', key='-FACTORY_RESET-', size=(12, 1)), - sg.Button('读取格式', key='-READ_FORMAT-', size=(12, 1)), - sg.Button('设置格式', key='-SET_FORMAT-', size=(12, 1))], - [sg.Button('打开蜂鸣器', key='-BUZZER_ON-', size=(12, 1)), - sg.Button('关闭蜂鸣器', key='-BUZZER_OFF-', size=(12, 1)), - sg.Button('打开射频', key='-RF_ON-', size=(12, 1)), - sg.Button('关闭射频', key='-RF_OFF-', size=(12, 1))], - [sg.Text('功率 (0-9):'), sg.Slider(range=(0, 9), default_value=8, orientation='h', size=(20, 15), key='-POWER-'), - sg.Button('设置功率', key='-SET_POWER-')], - [sg.Text('工作模式:'), - sg.Radio('单标签巡查', 'mode', key='-MODE1-', default=True), - sg.Radio('被动模式', 'mode', key='-MODE2-'), - sg.Radio('多标签巡查', 'mode', key='-MODE3-'), - sg.Button('设置模式', key='-SET_MODE-')] + [ + sg.Button("读取版本号", key="-VERSION-", size=(12, 1)), + sg.Button("恢复出厂", key="-FACTORY_RESET-", size=(12, 1)), + ], + [ + sg.Button("打开蜂鸣器", key="-BUZZER_ON-", size=(12, 1)), + sg.Button("关闭蜂鸣器", key="-BUZZER_OFF-", size=(12, 1)), + ], + [ + sg.Text("功率 (0-9):"), + sg.Slider( + range=(0, 9), + default_value=3, + orientation="h", + size=(20, 15), + key="-POWER-", + ), + sg.Button("设置功率", key="-SET_POWER-"), + ], + [ + sg.Text("工作模式:"), + sg.Radio("单标签巡查", "mode", key="-MODE1-", default=True), + sg.Radio("被动模式", "mode", key="-MODE2-"), + sg.Radio("多标签巡查", "mode", key="-MODE3-"), + sg.Button("设置模式", key="-SET_MODE-"), + ], ] # EPC 操作区域 epc_frame = [ - [sg.Button('读取 EPC', key='-READ_EPC-', size=(15, 1)), - sg.Button('选中卡', key='-SELECT_CARD-', size=(15, 1))], - [sg.Text('EPC 数据 (十六进制):'), sg.Input('', size=(40, 1), key='-EPC_DATA-')], - [sg.Text('新 EPC (十六进制):'), sg.Input('', size=(40, 1), key='-NEW_EPC-')], - [sg.Button('写入 EPC', key='-WRITE_EPC-', size=(15, 1))] + [ + sg.Button("读取 EPC", key="-READ_EPC-", size=(15, 1)), + sg.Text("", key="-READ_EPC_STATUS-", size=(40, 1), text_color="black"), + ], # 选中状态提示 + [ + sg.Text("原 EPC 数据(十六进制):"), + sg.Input("", size=(40, 1), key="-EPC_DATA-"), + sg.Button("选中卡", key="-SELECT_CARD-", size=(15, 1)), + ], + [ + sg.Text("新 EPC 数据(十六进制):"), + sg.Input("", size=(40, 1), key="-NEW_EPC-"), + ], + [ + sg.Button("写入 EPC", key="-WRITE_EPC-", size=(15, 1)), + sg.Text("", key="-WRITE_EPC_STATUS-", size=(40, 1), text_color="black"), + ], # 写入状态提示 ] # 日志输出区域 log_frame = [ - [sg.Multiline('', size=(80, 15), key='-LOG-', autoscroll=True, disabled=True)] + [sg.Multiline("", size=(80, 15), key="-LOG-", autoscroll=True, disabled=True)] ] layout = [ - [sg.Frame('设备连接', device_frame)], - [sg.Frame('基本命令', basic_frame)], - [sg.Frame('EPC 操作', epc_frame)], - [sg.Frame('日志', log_frame)], - [sg.Button('清空日志', key='-CLEAR_LOG-'), sg.Button('退出', key='-EXIT-')] + [sg.Frame("设备连接", device_frame)], + [sg.Frame("基本命令", basic_frame)], + [sg.Frame("EPC 操作", epc_frame)], + [sg.Frame("日志", log_frame)], + [sg.Button("清空日志", key="-CLEAR_LOG-"), sg.Button("退出", key="-EXIT-")], ] - return sg.Window('MT6 RFID 读卡器测试程序', layout, finalize=True) + return sg.Window("MT6 RFID 读卡器测试程序", layout, finalize=True) def log_message(window, message, is_hex=False): """输出日志消息""" - timestamp = time.strftime('%H:%M:%S') + timestamp = time.strftime("%H:%M:%S") if is_hex: if isinstance(message, bytes): - hex_str = ' '.join(f'{b:02x}' for b in message) - window['-LOG-'].print(f'[{timestamp}] {hex_str}') + hex_str = " ".join(f"{b:02x}" for b in message) + window["-LOG-"].print(f"[{timestamp}] {hex_str}") else: - window['-LOG-'].print(f'[{timestamp}] {message}') + window["-LOG-"].print(f"[{timestamp}] {message}") else: - window['-LOG-'].print(f'[{timestamp}] {message}') + window["-LOG-"].print(f"[{timestamp}] {message}") def hex_to_bytes(hex_str: str) -> bytes: """十六进制字符串转字节""" - hex_str = hex_str.replace(' ', '').replace(',', '') + hex_str = hex_str.replace(" ", "").replace(",", "") return bytes.fromhex(hex_str) @@ -437,23 +517,29 @@ def main(): while True: event, values = window.read() - if event in (sg.WIN_CLOSED, '-EXIT-'): + if event in (sg.WIN_CLOSED, "-EXIT-"): rfid.disconnect() break # 枚举设备 - if event == '-ENUM-': + if event == "-ENUM-": device_list = rfid.list_devices() if device_list: log_message(window, f"找到 {len(device_list)} 个 HID 设备") device_strs = [] for i, d in enumerate(device_list): # 显示路径信息,帮助区分相同VID/PID的设备 - path = d['path'] - path_str = path.decode('utf-8', errors='ignore') if isinstance(path, bytes) else str(path) - s = f"[{i}] VID:0x{d['vendor_id']:04x} PID:0x{d['product_id']:04x} " \ - f"IF:{d['interface_number']} {d['manufacturer_string']} {d['product_string']} " \ + path = d["path"] + path_str = ( + path.decode("utf-8", errors="ignore") + if isinstance(path, bytes) + else str(path) + ) + s = ( + f"[{i}] VID:0x{d['vendor_id']:04x} PID:0x{d['product_id']:04x} " + f"IF:{d['interface_number']} {d['manufacturer_string']} {d['product_string']} " f"Path:...{path_str[-20:]}" + ) device_strs.append(s) log_message(window, s) # 详细日志显示完整路径 @@ -461,66 +547,81 @@ def main(): # 创建设备选择窗口 select_layout = [ - [sg.Listbox(values=device_strs, size=(80, min(10, len(device_strs))), key='-SELECTED-')], - [sg.Button('使用选中设备'), sg.Button('取消')] + [ + sg.Listbox( + values=device_strs, + size=(80, min(10, len(device_strs))), + key="-SELECTED-", + ) + ], + [sg.Button("使用选中设备"), sg.Button("取消")], ] - select_window = sg.Window('选择设备', select_layout, modal=True) + select_window = sg.Window("选择设备", select_layout, modal=True) while True: sel_event, sel_values = select_window.read() - if sel_event in (sg.WIN_CLOSED, '取消'): + if sel_event in (sg.WIN_CLOSED, "取消"): select_window.close() break - if sel_event == '使用选中设备': - selected_idx = sel_values.get('-SELECTED-', []) + if sel_event == "使用选中设备": + selected_idx = sel_values.get("-SELECTED-", []) if selected_idx: idx = device_strs.index(selected_idx[0]) d = device_list[idx] - window['-VID-'].update(f"{d['vendor_id']:04x}") - window['-PID-'].update(f"{d['product_id']:04x}") + window["-VID-"].update(f"{d['vendor_id']:04x}") + window["-PID-"].update(f"{d['product_id']:04x}") # 使用路径连接设备 - path = d['path'] - interface_number = d['interface_number'] + path = d["path"] + interface_number = d["interface_number"] log_message(window, f"正在连接设备...") - log_message(window, f" VID: 0x{d['vendor_id']:04x}, PID: 0x{d['product_id']:04x}") + log_message( + window, + f" VID: 0x{d['vendor_id']:04x}, PID: 0x{d['product_id']:04x}", + ) log_message(window, f" 接口号: {interface_number}") - path_str = path.decode('utf-8', errors='ignore') if isinstance(path, bytes) else str(path) + path_str = ( + path.decode("utf-8", errors="ignore") + if isinstance(path, bytes) + else str(path) + ) log_message(window, f" 路径: {path_str}") success, msg = rfid.connect_by_path(path, interface_number) log_message(window, msg) if success: - window['-STATUS-'].update('已连接', text_color='green') - log_message(window, f"当前使用接口: IF{rfid.current_interface}") + window["-STATUS-"].update("已连接", text_color="green") + log_message( + window, f"当前使用接口: IF{rfid.current_interface}" + ) else: - window['-STATUS-'].update('连接失败', text_color='red') + window["-STATUS-"].update("连接失败", text_color="red") select_window.close() break else: log_message(window, "未找到 HID 设备") # 连接设备 - if event == '-CONNECT-': + if event == "-CONNECT-": try: - vid = int(values['-VID-'], 16) - pid = int(values['-PID-'], 16) + vid = int(values["-VID-"], 16) + pid = int(values["-PID-"], 16) success, msg = rfid.connect(vid, pid) log_message(window, msg) if success: - window['-STATUS-'].update('已连接', text_color='green') + window["-STATUS-"].update("已连接", text_color="green") else: - window['-STATUS-'].update('连接失败', text_color='red') + window["-STATUS-"].update("连接失败", text_color="red") except ValueError: log_message(window, "请输入有效的十六进制 VID/PID") # 断开连接 - if event == '-DISCONNECT-': + if event == "-DISCONNECT-": msg = rfid.disconnect() log_message(window, msg) - window['-STATUS-'].update('未连接', text_color='red') + window["-STATUS-"].update("未连接", text_color="red") # 读取版本号 - if event == '-VERSION-': + if event == "-VERSION-": if not rfid.is_connected(): log_message(window, "错误: 设备未连接") else: @@ -531,13 +632,13 @@ def main(): else: log_message(window, data, is_hex=True) try: - version = data.decode('ascii', errors='ignore') + version = data.decode("ascii", errors="ignore") log_message(window, f"版本: {version}") except: pass # 恢复出厂设置 - if event == '-FACTORY_RESET-': + if event == "-FACTORY_RESET-": if not rfid.is_connected(): log_message(window, "错误: 设备未连接") else: @@ -549,7 +650,7 @@ def main(): log_message(window, data, is_hex=True) # 读取格式 - if event == '-READ_FORMAT-': + if event == "-READ_FORMAT-": if not rfid.is_connected(): log_message(window, "错误: 设备未连接") else: @@ -561,7 +662,7 @@ def main(): log_message(window, data, is_hex=True) # 设置格式 - if event == '-SET_FORMAT-': + if event == "-SET_FORMAT-": if not rfid.is_connected(): log_message(window, "错误: 设备未连接") else: @@ -573,7 +674,7 @@ def main(): log_message(window, data, is_hex=True) # 射频电源控制 - if event == '-RF_ON-': + if event == "-RF_ON-": if not rfid.is_connected(): log_message(window, "错误: 设备未连接") else: @@ -584,7 +685,7 @@ def main(): else: log_message(window, data, is_hex=True) - if event == '-RF_OFF-': + if event == "-RF_OFF-": if not rfid.is_connected(): log_message(window, "错误: 设备未连接") else: @@ -596,7 +697,7 @@ def main(): log_message(window, data, is_hex=True) # 蜂鸣器控制 - if event == '-BUZZER_ON-': + if event == "-BUZZER_ON-": if not rfid.is_connected(): log_message(window, "错误: 设备未连接") else: @@ -607,7 +708,7 @@ def main(): else: log_message(window, f"响应: {data.hex() if data else '无数据'}") - if event == '-BUZZER_OFF-': + if event == "-BUZZER_OFF-": if not rfid.is_connected(): log_message(window, "错误: 设备未连接") else: @@ -619,11 +720,11 @@ def main(): log_message(window, f"响应: {data.hex() if data else '无数据'}") # 设置功率 - if event == '-SET_POWER-': + if event == "-SET_POWER-": if not rfid.is_connected(): log_message(window, "错误: 设备未连接") else: - power = int(values['-POWER-']) + power = int(values["-POWER-"]) log_message(window, f"发送命令: 设置功率 = {power}") data, err = rfid.send_command(cmd_set_power(power)) if err: @@ -632,17 +733,19 @@ def main(): log_message(window, f"响应: {data.hex() if data else '无数据'}") # 设置工作模式 - if event == '-SET_MODE-': + if event == "-SET_MODE-": if not rfid.is_connected(): log_message(window, "错误: 设备未连接") else: mode = 1 - if values['-MODE2-']: + if values["-MODE2-"]: mode = 2 - elif values['-MODE3-']: + elif values["-MODE3-"]: mode = 3 - mode_names = {1: '单标签巡查', 2: '被动模式', 3: '多标签巡查'} - log_message(window, f"发送命令: 设置模式 = {mode_names.get(mode, mode)}") + mode_names = {1: "单标签巡查", 2: "被动模式", 3: "多标签巡查"} + log_message( + window, f"发送命令: 设置模式 = {mode_names.get(mode, mode)}" + ) data, err = rfid.send_command(cmd_set_mode(mode)) if err: log_message(window, f"错误: {err}") @@ -650,14 +753,18 @@ def main(): log_message(window, f"响应: {data.hex() if data else '无数据'}") # 读取 EPC - if event == '-READ_EPC-': + if event == "-READ_EPC-": + # 清空状态提示 + window["-READ_EPC_STATUS-"].update("", text_color="black") if not rfid.is_connected(): log_message(window, "错误: 设备未连接") + window["-READ_EPC_STATUS-"].update("✗ 设备未连接", text_color="red") else: log_message(window, "发送命令: 读取 EPC") data, err = rfid.send_command(cmd_read_epc()) if err: log_message(window, f"错误: {err}") + window["-READ_EPC_STATUS-"].update(f"✗ {err}", text_color="red") else: log_message(window, f"响应 (hex): ", is_hex=True) log_message(window, data, is_hex=True) @@ -666,33 +773,57 @@ def main(): # 原始格式: 00 bb 02 22 00 0b d3 18 00 [EPC] ... # 去掉status后: bb 02 22 00 0b d3 18 00 [EPC] ... # 所以: data[0]=bb, data[1:3]=Card Op Resp, data[5]=RSSI, data[6]=EPC Len, data[8:]=EPC - if data and len(data) > 10: + if data and len(data) >= 8: # 检查 Magic 和 Card Op Resp - if data[0:1] == b'\xbb' and data[1:3] == b'\x02\x22': + if ( + data[0:1] == b"\xbb" + and data[1:3] == b"\x02\x22" + and len(data) >= 12 + ): # 有卡,解析 EPC rssi = data[5] if len(data) > 5 else 0 epc_len_indicator = data[6] if len(data) > 6 else 0 - epc_len = epc_len_indicator // 4 if epc_len_indicator > 0 else 0 - epc_data = data[8:8+epc_len] if len(data) > 8 else b'' + epc_len = ( + epc_len_indicator // 4 if epc_len_indicator > 0 else 0 + ) + epc_data = data[8 : 8 + epc_len] if len(data) > 8 else b"" log_message(window, f"Magic: 0x{data[0]:02x} (应为 bb)") - log_message(window, f"Card Op Resp: {data[1:3].hex()} (02 22=有卡)") + log_message( + window, f"Card Op Resp: {data[1:3].hex()} (02 22=有卡)" + ) log_message(window, f"RSSI: 0x{rssi:02x}") - log_message(window, f"EPC Len Indicator: 0x{epc_len_indicator:02x} (字节数={epc_len})") + log_message( + window, + f"EPC Len Indicator: 0x{epc_len_indicator:02x} (字节数={epc_len})", + ) log_message(window, f"EPC: {epc_data.hex()}") - window['-EPC_DATA-'].update(epc_data.hex()) - elif data[0:1] == b'\xbb' and data[1:3] == b'\x01\xff': + window["-EPC_DATA-"].update(epc_data.hex()) + # 成功状态提示 + window["-READ_EPC_STATUS-"].update( + f"✓ 读取成功: {epc_data.hex()}", text_color="green" + ) + elif data[0:1] == b"\xbb" and data[1:3] == b"\x01\xff": log_message(window, "无卡或读取失败 (Card Op Resp: 01 ff)") + # 无卡状态提示 + window["-READ_EPC_STATUS-"].update( + "✗ 读取失败: 无卡", text_color="red" + ) else: log_message(window, f"未知响应格式:") log_message(window, f" Magic: {data[0:1].hex()} (应为 bb)") log_message(window, f" Card Op Resp: {data[1:3].hex()}") + # 未知响应状态提示 + window["-READ_EPC_STATUS-"].update( + "✗ 未知响应格式", text_color="red" + ) # 选中卡 - if event == '-SELECT_CARD-': + if event == "-SELECT_CARD-": + # 清空状态提示 if not rfid.is_connected(): log_message(window, "错误: 设备未连接") else: - epc_hex = values['-EPC_DATA-'].strip() + epc_hex = values["-EPC_DATA-"].strip() if not epc_hex: log_message(window, "错误: 请输入 EPC 数据") else: @@ -703,7 +834,10 @@ def main(): if err: log_message(window, f"错误: {err}") else: - log_message(window, f"响应 (hex): {data.hex() if data else '无数据'}") + log_message( + window, + f"响应 (hex): {data.hex() if data else '无数据'}", + ) # 注意: parse_getres_frame返回的data是从bb开始的(status已被去除) # 原始格式: 00 bb 01 0c 00 01 00 0e 7e # 去掉status后: bb 01 0c 00 01 00 0e 7e @@ -711,39 +845,90 @@ def main(): if data and len(data) > 5: if data[5] == 0x00: log_message(window, "选中成功") + window["-READ_EPC_STATUS-"].update( + f"✓ 选中当前卡", text_color="green" + ) else: log_message(window, f"选中失败: 0x{data[5]:02x}") except ValueError: log_message(window, "错误: EPC 数据格式错误,请使用十六进制") # 写入 EPC - if event == '-WRITE_EPC-': + if event == "-WRITE_EPC-": + # 清空状态提示 + window["-WRITE_EPC_STATUS-"].update("", text_color="black") if not rfid.is_connected(): log_message(window, "错误: 设备未连接") + window["-WRITE_EPC_STATUS-"].update("✗ 设备未连接", text_color="red") else: - new_epc_hex = values['-NEW_EPC-'].strip() + new_epc_hex = values["-NEW_EPC-"].strip() + old_epc_hex = values["-EPC_DATA-"].strip() if not new_epc_hex: log_message(window, "错误: 请输入新的 EPC 数据") + window["-WRITE_EPC_STATUS-"].update( + "✗ 请输入新EPC", text_color="red" + ) + elif not old_epc_hex: + log_message(window, "错误: 请先读取 EPC 数据") + window["-WRITE_EPC_STATUS-"].update("✗ 请先读卡", text_color="red") else: try: new_epc = hex_to_bytes(new_epc_hex) - # 这里需要一个旧的 EPC CRC,暂时使用示例值 - old_epc_crc = bytes([0xca, 0x9e]) # 需要根据实际 EPC 计算 + old_epc = hex_to_bytes(old_epc_hex) + # 需要根据实际 EPC 计算 + old_epc_crc = calc_epc_crc( + len(old_epc) * 4, 0, old_epc + ).to_bytes(2, "big") log_message(window, f"发送命令: 写入 EPC={new_epc.hex()}") - data, err = rfid.send_command(cmd_write_epc(old_epc_crc, new_epc)) + data, err = rfid.send_command( + cmd_write_epc(old_epc_crc, new_epc) + ) if err: log_message(window, f"错误: {err}") + window["-WRITE_EPC_STATUS-"].update( + f"✗ {err}", text_color="red" + ) else: - log_message(window, f"响应 (hex): {data.hex() if data else '无数据'}") + log_message( + window, + f"响应 (hex): {data.hex() if data else '无数据'}", + ) + # 验证写入是否成功 + if data and len(data) >= 3: + # 检查 Magic 和 Card Op Resp + if data[0] == 0xBB and data[1:3] == b"\x01\x49": + # 成功状态提示 + window["-WRITE_EPC_STATUS-"].update( + f"✓ 写入成功: {new_epc.hex()}", + text_color="green", + ) + else: + # 写入失败 + log_message( + window, + f"写入失败: Card Op Resp = {data[1:3].hex()}", + ) + window["-WRITE_EPC_STATUS-"].update( + "✗ 写入失败", text_color="red" + ) + else: + # 无效响应 + log_message(window, f"无效响应: 数据长度不足") + window["-WRITE_EPC_STATUS-"].update( + "✗ 无效响应", text_color="red" + ) except ValueError: log_message(window, "错误: EPC 数据格式错误,请使用十六进制") + window["-WRITE_EPC_STATUS-"].update( + "✗ EPC格式错误", text_color="red" + ) # 清空日志 - if event == '-CLEAR_LOG-': - window['-LOG-'].update('') + if event == "-CLEAR_LOG-": + window["-LOG-"].update("") window.close() -if __name__ == '__main__': +if __name__ == "__main__": main()