diff --git a/protocol.md b/protocol.md index e623af5..70057a0 100644 --- a/protocol.md +++ b/protocol.md @@ -1,4 +1,4 @@ -# MT6 RFID 读卡器 USB HID 私有协议参考文档 +# MT6 RFID 读卡器 USB HID 私有协议参考文档(最终修正版) ## 1. 概述 @@ -12,50 +12,48 @@ 主机发送的命令帧结构如下(从 Report ID 后开始计算偏移)。**SetReq 必须填充至 256 字节**,不足部分用 `0x00` 补齐。 -| 偏移 | 长度 | 字段名 | 说明 | -|:---|:---|:---|:---| -| 0x00 | 1 | Report ID | 固定 `0x01` | -| 0x01 | 4 | Fixed | 固定 `0x00 0x00 0x00 0x00` | -| 0x05 | 2 | Frame Length | 从下一字段(`0x07`)到数据末尾(不含校验和结束符)的总字节数,小端序 | -| 0x07 | 2 | Constant | 固定 `0x00 0x02` | -| 0x09 | 2 | Data Length | 后续数据负载的字节数,小端序 | -| 0x0B | N | Data | 实际数据负载(命令参数) | -| 0x0B+N | 1 | Checksum | 校验字节,计算方法见第 3 节 | -| 0x0C+N | 1 | End Marker | 固定 `0x03` | -| 剩余 | - | Padding | 填充 `0x00` 至 **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) -设备返回的数据帧结构与 SetReq 相同,但 **GetRes 无需填充至 256 字节**,其实际有效长度由 USB 传输层决定。 +设备返回的数据帧结构与 SetReq 相同,但 **GetRes 无需填充至 256 字节**。 -**通用 Data 段格式**: +**Data 段格式**: -| 偏移(相对 Data 段起点) | 长度 | 字段名 | 说明 | +| 偏移 | 长度 | 字段名 | 说明 | |:---|:---|:---|:---| -| 0x00 | 1 | Status | 状态字节。`0x00` 表示成功,非零表示异常 | -| 0x01 | M | Response Data | 实际返回数据(长度由 Data Length - 1 决定) | +| 0x00 | 1 | Status | `0x00` 成功,非零异常 | +| 0x01 | M | Response Data | 实际返回数据 | + ## 3. 校验算法 ### 3.1 外层帧校验 -**计算范围**:`Data Length` 字段的 2 个字节 + `Data` 字段的全部 N 个字节。 -**算法**:逐字节异或(XOR)。 +**算法**:逐字节异或(XOR) +**计算范围**:`Data Length` 两字节+ `Data` 全部字节。 -```python -def calc_outer_checksum(data_len_bytes: bytes, payload: bytes) -> int: - result = 0 - for b in data_len_bytes + payload: - result ^= b - return result -``` +**验证示例**(打开蜂鸣器): +- Data Length: `00 02` +- Data: `cd 01` +- XOR: `00 ^ 02 ^ cd ^ 01 = ce` ✅ ### 3.2 EPC 操作内部校验 #### 3.2.1 Payload Checksum(累加和) -**计算范围**:从 Card Op Command 字段开始,到 Payload 末尾(不含校验字节自身)。 -**算法**:所有字节累加(无符号 8 位溢出,即模 256)。 +**算法**:字节累加模 256 +**计算范围**:从 Card Op Command 字段开始,到 Payload 末尾(不含校验字节自身)。 #### 3.2.2 EPC CRC @@ -64,191 +62,157 @@ def calc_outer_checksum(data_len_bytes: bytes, payload: bytes) -> int: - 多项式:`0x1021` - 初始值:`0xFFFF` - 输出异或:`0xFFFF` -- 输入/输出是否反转:`false` / `false` +- 输入/输出反转:`false` -**计算范围**:EPC Len Indicator(1 字节)+ EPC Status(1 字节)+ EPC Data(N 字节) - -```python -import crcmod - -crc16_genibus = crcmod.mkCrcFun(0x11021, rev=False, initCrc=0xFFFF, xorOut=0xFFFF) - -def calc_epc_crc(epc_len_indicator: int, epc_status: int, epc_data: bytes) -> int: - data = bytes([epc_len_indicator, epc_status]) + epc_data - return crc16_genibus(data) -``` +**计算范围**:`EPC Len Indicator`(1 字节)+ `EPC Status`(1 字节)+ `EPC Data`(N 字节) ## 4. 已知命令集 -### 4.1 读版本号 +以下列出所有已分析命令,仅给出 **Data 段**内容(不含外层帧头),实际发送时需按第 2 节格式包装并填充至 256 字节。 -- **功能**:获取设备固件版本字符串。 -- **SetReq Data**(1 字节):`c0` -- **SetReq 完整数据段**(填充前): - ``` - 01 00 00 00 00 06 00 00 02 01 00 c0 c1 03 - ``` -- **GetRes Data 段**: - ``` - 00 # Status = 成功 - 4d 54 36 5f 52 46 39 31 35 5f 52 57 ... 20 # ASCII 版本字符串 - ``` - **注意**:版本字符串后的 `0x20` 为外层帧校验字节,`0x03` 为帧结束标志。 +### 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` | 参数含义略 | ### 4.2 蜂鸣器控制 -#### 打开蜂鸣器 -- **SetReq Data**(2 字节):`cd 01` -- **SetReq 数据段**: - ``` - 01 00 00 00 00 07 00 00 02 02 00 cd 01 ce 03 - ``` -- **GetRes Data 段**:`00 80` +| 功能 | SetReq Data | GetRes Data | +|:---|:---|:---| +| 打开蜂鸣器 | `cd 01` | `00 80` | +| 关闭蜂鸣器 | `cd 00` | `00 80` | -#### 关闭蜂鸣器 -- **SetReq Data**(2 字节):`cd 00` -- **SetReq 数据段**: - ``` - 01 00 00 00 00 07 00 00 02 02 00 cd 00 cf 03 - ``` -- **GetRes Data 段**:`00 80` +### 4.3 射频电源控制 -### 4.3 设置功率 +| 功能 | SetReq Data | GetRes Data | +|:---|:---|:---| +| 打开射频 | `90 01` | `00 80` | +| 关闭射频 | `90 00` | `00 80` | -- **功能**:设置发射功率等级(取值范围 0~9)。 -- **示例(功率 = 8)SetReq Data**(2 字节):`cc 08` -- **SetReq 数据段**: - ``` - 01 00 00 00 00 07 00 00 02 02 00 cc 08 c6 03 - ``` -- **GetRes Data 段**:`00 80` +### 4.4 工作模式与功率 -### 4.4 设置工作模式 - -- **功能**:切换读卡器工作模式。 -- **SetReq Data**(2 字节):`0f 0X`,其中 `X` 为模式编号。 - - `0f 01`:单标签巡查 - - `0f 02`:被动模式 - - `0f 03`:多标签巡查 -- **示例(被动模式)SetReq 数据段**: - ``` - 01 00 00 00 00 07 00 00 02 02 00 0f 02 0f 03 - ``` -- **GetRes Data 段**:`00 80` +| 功能 | 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 | ### 4.5 EPC 操作 -所有 EPC 相关命令(读、选中、写)的 SetReq Data 段均遵循以下通用结构: +所有 EPC 相关命令(读、选中、写)的 SetReq/GetRes Data 段均遵循以下内部结构: -| 偏移 | 长度 | 字段名 | 说明 | -|:---|:---|:---|:---| -| 0x00 | 1 | Command | 固定 `ce` | -| 0x01 | 1 | Magic | 固定 `bb` | -| 0x02 | 2 | Card Op Command | 操作码,见各子命令 | -| 0x04 | 2 | Internal Length | 后续 Payload 的字节数(小端) | -| 0x06 | N | Payload | 具体操作参数 | -| 0x06+N | 1 | Payload Checksum | 从 Card Op Command 到 Payload 末尾的累加和(模 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 **Card Op Command**:`00 22` -##### SetReq -- Internal Length:`00 00` -- Payload:(无) -- Payload Checksum:`22` -- End Marker:`7e` -- **完整 Data 段**:`ce bb 00 22 00 00 22 7e` +- **SetReq Data**:`ce bb 00 22 00 00 22 7e` + - Internal Length = `00 00` + - Payload Checksum = `22` -##### GetRes -- Data 段结构(有卡): +- **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 | 1 | Status | `00` | 成功 | -| 0x01 | 1 | Magic | `bb` | 固定 | -| 0x02 | 2 | Card Op Resp | `02 22` | 读卡成功标志 | -| 0x04 | 2 | Internal Length | `00 0b` | 后续数据长度 | -| 0x06 | 1 | RSSI | `d3` | 信号强度 | -| 0x07 | 1 | EPC Len Indicator | `18` | EPC 字节数 = 该值 ÷ 4 | -| 0x08 | 1 | EPC Status | `00` | 读取状态 | -| 0x09 | N | EPC Data | `11 22 33 44 55 66` | 实际 EPC | -| 0x09+N | 2 | EPC CRC | `75 ce` | CRC-16/GENIBUS | -| 0x0B+N | 1 | Payload Checksum | `c2` | 累加和 | -| 0x0C+N | 1 | End Marker | `7e` | 固定尾部 | - -- 无卡时 Card Op Resp 为 `01 ff`,Payload 仅含 RSSI 及累加和。 +- **无卡时**:Card Op Resp = `01 ff`,Payload 仅含 RSSI 及累加和,无 EPC 数据。 #### 4.5.2 选中卡 **Card Op Command**:`00 0c` -##### SetReq -- Internal Length:`00 09` -- Payload(9 字节): - - 偏移 0x00~0x05:保留字段(`01 00 00 00 20 10`) - - 偏移 0x06~0x07:EPC Data(如 `11 22`) -- **完整 Data 段示例**:`ce bb 00 0c 00 09 01 00 00 00 20 10 00 11 22 79 7e` +- **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` -##### GetRes -- Data 段: +- **GetRes Data 段**: ``` 00 bb 01 0c 00 01 00 0e 7e ``` - - Card Op Resp:`01 0c` - - Internal Length:`00 01` - - Payload:`00`(选中成功标志) - - Payload Checksum:`0e` + - Card Op Resp = `01 0c` + - Internal Length = `00 01` + - Payload = `00`(选中成功标志) #### 4.5.3 写入 EPC **Card Op Command**:`00 49` -##### SetReq -- Internal Length:`00 0f` -- Payload(15 字节): - | 偏移 | 长度 | 字段名 | 示例值 | 说明 | - |:---|:---|:---|:---|:---| - | 0x00 | 6 | Reserved | `00 00 00 00 01 00` | 保留 | - | 0x06 | 1 | Word Count | `03` | = 2 + 新 EPC 字节数/2 | - | 0x07 | 2 | Old EPC CRC | `ca 9e` | 原卡 EPC 的 CRC-16/GENIBUS | - | 0x09 | 1 | EPC Len Indicator | `08` | 新 EPC 字节数 = 值 ÷ 4 | - | 0x0A | 1 | EPC Status | `00` | 固定 | - | 0x0B | N | New EPC Data | `11 23` | 新 EPC | -- Payload Checksum:`00` -- End Marker:`7e` -- **完整 Data 段示例**: +- **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 字节): + | 偏移 | 字段 | 值示例 | 说明 | + |:---|:---|:---|:---| + | 0x00 | Reserved | `00 00 00 00 01 00 00 00` | 保留 | + | 0x09 | Word Count | `03` | = 2 + 新 EPC 字节数/2 | + | 0x0A | Old EPC CRC | `ca 9e` | 原卡 EPC 的 CRC-16/GENIBUS(大端) | + | 0x0C | EPC Len Indicator | `08` | 新 EPC 字节数 = 值 ÷ 4 | + | 0x0D | EPC Status | `00` | 固定 | + | 0x0E | New EPC Data | `11 23` | 新 EPC | + - Payload Checksum = `00` -##### GetRes -- Data 段: +- **GetRes Data 段**: ``` 00 bb 01 49 00 06 04 08 00 11 22 00 8f 7e ``` - - Card Op Resp:`01 49`(写卡成功) - - Internal Length:`00 06` - - Payload:包含原 EPC `11 22` 等信息,用于确认 + - Card Op Resp = `01 49` + - Payload 偏移0x03中开始原 EPC `11 22` 用于确认 -## 5. 附录:命令速查表 +## 5. 附录:命令速查表(SetReq Data 部分) -| 功能 | SetReq Data(十六进制) | 说明 | -|:---|:---|:---| -| 读版本号 | `c0` | 返回 ASCII 字符串 | -| 打开蜂鸣器 | `cd 01` | | -| 关闭蜂鸣器 | `cd 00` | | -| 设置功率 = 8 | `cc 08` | 取值范围 0~9 | -| 设置单标签巡查 | `0f 01` | | -| 设置被动模式 | `0f 02` | | -| 设置多标签巡查 | `0f 03` | | -| 读取 EPC | `ce bb 00 22 00 00 22 7e` | 返回 EPC 数据 | -| 选中卡(EPC=1122) | `ce bb 00 0c 00 09 01 00 00 00 20 10 00 11 22 79 7e` | Payload Checksum 需根据实际计算 | -| 写 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` | 注意 Word Count 和 Old EPC CRC | +| 功能 | 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` | +| 写 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` | --- -**文档版本**:2.2 -**最后更新**:2026-04-14 +**文档版本**:2.3 +**最后更新**:2026-04-15 **适用设备**:MT6_RF915_RW_WY 及兼容固件 diff --git a/rfid_tester.py b/rfid_tester.py index 5a1156d..03a6a84 100644 --- a/rfid_tester.py +++ b/rfid_tester.py @@ -49,14 +49,14 @@ 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, 'little') - + frame_length_bytes = frame_length.to_bytes(2, 'big') + # Data Length - data_length_bytes = len(data).to_bytes(2, 'little') - + data_length_bytes = len(data).to_bytes(2, 'big') + # Checksum checksum = calc_outer_checksum(data_length_bytes, data) - + # 构建帧 frame = bytearray(REPORT_SIZE) frame[0] = REPORT_ID_SET_REQ @@ -67,33 +67,58 @@ def build_setreq_frame(data: bytes) -> bytes: frame[11:11+len(data)] = data # Data frame[11+len(data)] = checksum # Checksum frame[12+len(data)] = 0x03 # End Marker - + return bytes(frame) def parse_getres_frame(data: bytes) -> tuple: """ - 解析 GetRes 帧 + 解析 GetRes 帧(带Checksum和End Marker验证) :param data: 接收到的数据 - :return: (status, response_data) 或 None + :return: (status, response_data) 或 None,校验失败返回None并打印错误 """ - if len(data) < 12: + if len(data) < 14: # 最小帧长度需要包含Checksum和End Marker + print(f"[DEBUG] 数据长度不足: {len(data)} < 14") return None - + # 检查 Report ID if data[0] != REPORT_ID_GET_RES: + print(f"[DEBUG] Report ID错误: 0x{data[0]:02x} (应为 0x03)") return None - - # 解析帧结构 - frame_length = int.from_bytes(data[5:7], 'little') - data_length = int.from_bytes(data[9:11], 'little') - + + # 解析帧结构(大端序) + 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 + if len(data) < expected_total_len: + print(f"[DEBUG] 数据长度不匹配: 实际{len(data)}, 期望{expected_total_len}") + return None + # 提取 Data 段 response_data = data[11:11+data_length] - + + # 提取 Checksum 和 End Marker + received_checksum = data[11+data_length] + received_end_marker = data[12+data_length] + + # 验证 End Marker(固定为 0x03) + if received_end_marker != 0x03: + print(f"[DEBUG] End Marker错误: 0x{received_end_marker:02x} (应为 0x03)") + return None + + # 计算并验证 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}") + return None + + print(f"[DEBUG] 帧校验通过: Checksum=0x{received_checksum:02x}, EndMarker=0x{received_end_marker:02x}") + if len(response_data) < 1: return None - + status = response_data[0] return (status, response_data[1:]) @@ -105,11 +130,31 @@ def cmd_get_version() -> bytes: return bytes([0xc0]) +def cmd_factory_reset() -> bytes: + """恢复出厂设置命令""" + return bytes([0xcf]) + + +def cmd_read_format() -> bytes: + """读取格式命令""" + return bytes([0x83]) + + +def cmd_set_format() -> bytes: + """设置格式命令""" + 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]) +def cmd_rf_power(on: bool) -> bytes: + """射频电源控制命令""" + return bytes([0x90, 0x01 if on else 0x00]) + + def cmd_set_power(power: int) -> bytes: """设置功率命令 (0-9)""" power = max(0, min(9, power)) @@ -134,39 +179,41 @@ def cmd_select_card(epc_data: bytes) -> bytes: """选中卡命令""" # 固定前缀 + EPC 数据 payload = bytes([0x01, 0x00, 0x00, 0x00, 0x20, 0x10, 0x00]) + epc_data - - # 计算 checksum + + # 计算 checksum(包含 Card Op Command + Internal Length + Payload) card_op_cmd = bytes([0x00, 0x0c]) + internal_len = len(payload).to_bytes(2, 'big') + checksum = 0 - for b in card_op_cmd + payload: + for b in card_op_cmd + internal_len + payload: checksum = (checksum + b) & 0xFF - - internal_len = len(payload).to_bytes(2, 'little') + 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: """写入 EPC 命令""" - epc_len_indicator = (len(new_epc) * 4) // 4 + # EPC Len Indicator: EPC字节数 = 值 ÷ 4,所以值 = EPC字节数 * 4 + epc_len_indicator = len(new_epc) * 4 if epc_len_indicator == 0: - epc_len_indicator = 8 - + epc_len_indicator = 8 # 默认最小值 + word_count = 2 + len(new_epc) // 2 - - payload = bytes([0x00, 0x00, 0x00, 0x00, 0x01, 0x00]) # Reserved - payload += bytes([0x00, 0x00]) # Reserved + + 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 += old_epc_crc # Old EPC CRC (大端序) payload += bytes([epc_len_indicator, 0x00]) # EPC Len Indicator, Status payload += new_epc # New EPC Data - - # 计算 checksum + + # 计算 checksum(包含 Card Op Command + Internal Length + Payload) card_op_cmd = bytes([0x00, 0x49]) + internal_len = len(payload).to_bytes(2, 'big') + checksum = 0 - for b in card_op_cmd + len(payload).to_bytes(2, 'little') + payload: + for b in card_op_cmd + internal_len + payload: checksum = (checksum + b) & 0xFF - - internal_len = len(payload).to_bytes(2, 'little') + return bytes([0xce, 0xbb]) + card_op_cmd + internal_len + payload + bytes([checksum, 0x7e]) @@ -177,7 +224,11 @@ class RFIDDevice: self.device = None self.vendor_id = VENDOR_ID self.product_id = PRODUCT_ID - + self.current_path = None # 当前连接的设备路径 + self.current_interface = -1 # 当前连接的接口号 + self.current_path = None # 当前连接的设备路径 + self.current_interface = -1 # 当前连接的接口号 + def list_devices(self): """列出所有 HID 设备""" devices = [] @@ -195,54 +246,103 @@ class RFIDDevice: except Exception as e: print(f"枚举设备失败: {e}") return devices - + def connect(self, vendor_id=None, product_id=None): - """连接设备""" + """通过VID/PID连接设备(会打开第一个匹配的设备)""" if vendor_id: self.vendor_id = vendor_id if product_id: self.product_id = product_id - + try: self.device = hid.device() self.device.open(self.vendor_id, self.product_id) - return True, f"已连接到设备 (VID: 0x{self.vendor_id:04x}, PID: 0x{self.product_id:04x})" + 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}) - 注意:可能连接到任意接口" except Exception as e: + print(f"[DEBUG] VID/PID连接失败: {e}") return False, f"连接失败: {e}" - + + def connect_by_path(self, path, interface_number=-1): + """通过路径连接设备(可以精确指定接口)""" + try: + self.device = hid.device() + # path可能是bytes类型,需要处理 + if isinstance(path, bytes): + path_str = path + else: + path_str = path.encode('utf-8') if isinstance(path, str) else path + + self.device.open_path(path_str) + self.current_path = path + self.current_interface = interface_number + + # 获取设备信息 + info = self.device.get_manufacturer_string() or "未知" + product = self.device.get_product_string() or "未知" + + print(f"[DEBUG] 通过路径连接成功:") + print(f"[DEBUG] 路径: {path}") + print(f"[DEBUG] 接口号: {interface_number}") + 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:]})" + except Exception as e: + print(f"[DEBUG] 路径连接失败: {e}") + return False, f"连接失败: {e}" + def disconnect(self): """断开连接""" if self.device: try: + print(f"[DEBUG] 断开设备连接 (接口: {self.current_interface})") self.device.close() except: pass self.device = None + self.current_path = None + self.current_interface = -1 return "已断开连接" - + def is_connected(self): """检查是否已连接""" - return self.device is not None - + if self.device: + #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 + } + def send_command(self, cmd_data: bytes): """发送命令并接收响应""" if not self.device: return None, "设备未连接" - + try: # 构建并发送 SetReq frame = build_setreq_frame(cmd_data) self.device.send_feature_report(frame) - + # 接收 GetRes time.sleep(0.1) response = self.device.get_feature_report(REPORT_ID_GET_RES, REPORT_SIZE) - + # 解析响应 result = parse_getres_frame(bytes(response)) if result is None: return None, "响应格式错误" - + status, data = result return data, None except Exception as e: @@ -254,22 +354,27 @@ class RFIDDevice: def create_window(): """创建主窗口""" 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='-ENUM-'), sg.Button('连接', key='-CONNECT-'), - sg.Button('断开', key='-DISCONNECT-')], + 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=(15, 1)), - sg.Button('打开蜂鸣器', key='-BUZZER_ON-', size=(15, 1)), - sg.Button('关闭蜂鸣器', key='-BUZZER_OFF-', size=(15, 1))], + [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('工作模式:'), @@ -278,7 +383,7 @@ def create_window(): sg.Radio('多标签巡查', 'mode', key='-MODE3-'), sg.Button('设置模式', key='-SET_MODE-')] ] - + # EPC 操作区域 epc_frame = [ [sg.Button('读取 EPC', key='-READ_EPC-', size=(15, 1)), @@ -287,12 +392,12 @@ def create_window(): [sg.Text('新 EPC (十六进制):'), sg.Input('', size=(40, 1), key='-NEW_EPC-')], [sg.Button('写入 EPC', key='-WRITE_EPC-', size=(15, 1))] ] - + # 日志输出区域 log_frame = [ [sg.Multiline('', size=(80, 15), key='-LOG-', autoscroll=True, disabled=True)] ] - + layout = [ [sg.Frame('设备连接', device_frame)], [sg.Frame('基本命令', basic_frame)], @@ -300,7 +405,7 @@ def create_window(): [sg.Frame('日志', log_frame)], [sg.Button('清空日志', key='-CLEAR_LOG-'), sg.Button('退出', key='-EXIT-')] ] - + return sg.Window('MT6 RFID 读卡器测试程序', layout, finalize=True) @@ -328,14 +433,14 @@ def main(): window = create_window() rfid = RFIDDevice() device_list = [] - + while True: event, values = window.read() - + if event in (sg.WIN_CLOSED, '-EXIT-'): rfid.disconnect() break - + # 枚举设备 if event == '-ENUM-': device_list = rfid.list_devices() @@ -343,11 +448,17 @@ def main(): 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']}" + f"IF:{d['interface_number']} {d['manufacturer_string']} {d['product_string']} " \ + f"Path:...{path_str[-20:]}" device_strs.append(s) log_message(window, s) - + # 详细日志显示完整路径 + log_message(window, f" -> 完整路径: {path_str}") + # 创建设备选择窗口 select_layout = [ [sg.Listbox(values=device_strs, size=(80, min(10, len(device_strs))), key='-SELECTED-')], @@ -366,12 +477,28 @@ def main(): d = device_list[idx] window['-VID-'].update(f"{d['vendor_id']:04x}") window['-PID-'].update(f"{d['product_id']:04x}") - log_message(window, f"已选择设备: VID=0x{d['vendor_id']:04x}, PID=0x{d['product_id']:04x}, IF={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" 接口号: {interface_number}") + 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}") + else: + window['-STATUS-'].update('连接失败', text_color='red') select_window.close() break else: log_message(window, "未找到 HID 设备") - + # 连接设备 if event == '-CONNECT-': try: @@ -385,13 +512,13 @@ def main(): window['-STATUS-'].update('连接失败', text_color='red') except ValueError: log_message(window, "请输入有效的十六进制 VID/PID") - + # 断开连接 if event == '-DISCONNECT-': msg = rfid.disconnect() log_message(window, msg) window['-STATUS-'].update('未连接', text_color='red') - + # 读取版本号 if event == '-VERSION-': if not rfid.is_connected(): @@ -402,14 +529,72 @@ def main(): if err: log_message(window, f"错误: {err}") else: - log_message(window, f"响应 (hex): ", is_hex=True) log_message(window, data, is_hex=True) try: version = data.decode('ascii', errors='ignore') log_message(window, f"版本: {version}") except: pass - + + # 恢复出厂设置 + if event == '-FACTORY_RESET-': + if not rfid.is_connected(): + log_message(window, "错误: 设备未连接") + else: + log_message(window, "发送命令: 恢复出厂设置") + data, err = rfid.send_command(cmd_factory_reset()) + if err: + log_message(window, f"错误: {err}") + else: + log_message(window, data, is_hex=True) + + # 读取格式 + if event == '-READ_FORMAT-': + if not rfid.is_connected(): + log_message(window, "错误: 设备未连接") + else: + log_message(window, "发送命令: 读取格式") + data, err = rfid.send_command(cmd_read_format()) + if err: + log_message(window, f"错误: {err}") + else: + log_message(window, data, is_hex=True) + + # 设置格式 + if event == '-SET_FORMAT-': + if not rfid.is_connected(): + log_message(window, "错误: 设备未连接") + else: + log_message(window, "发送命令: 设置格式") + data, err = rfid.send_command(cmd_set_format()) + if err: + log_message(window, f"错误: {err}") + else: + log_message(window, data, is_hex=True) + + # 射频电源控制 + if event == '-RF_ON-': + if not rfid.is_connected(): + log_message(window, "错误: 设备未连接") + else: + log_message(window, "发送命令: 打开射频") + data, err = rfid.send_command(cmd_rf_power(True)) + if err: + log_message(window, f"错误: {err}") + else: + log_message(window, data, is_hex=True) + + if event == '-RF_OFF-': + if not rfid.is_connected(): + log_message(window, "错误: 设备未连接") + else: + log_message(window, "发送命令: 关闭射频") + data, err = rfid.send_command(cmd_rf_power(False)) + if err: + log_message(window, f"错误: {err}") + else: + log_message(window, data, is_hex=True) + # 蜂鸣器控制 if event == '-BUZZER_ON-': if not rfid.is_connected(): @@ -421,7 +606,7 @@ def main(): log_message(window, f"错误: {err}") else: log_message(window, f"响应: {data.hex() if data else '无数据'}") - + if event == '-BUZZER_OFF-': if not rfid.is_connected(): log_message(window, "错误: 设备未连接") @@ -432,7 +617,7 @@ def main(): log_message(window, f"错误: {err}") else: log_message(window, f"响应: {data.hex() if data else '无数据'}") - + # 设置功率 if event == '-SET_POWER-': if not rfid.is_connected(): @@ -445,7 +630,7 @@ def main(): log_message(window, f"错误: {err}") else: log_message(window, f"响应: {data.hex() if data else '无数据'}") - + # 设置工作模式 if event == '-SET_MODE-': if not rfid.is_connected(): @@ -463,7 +648,7 @@ def main(): log_message(window, f"错误: {err}") else: log_message(window, f"响应: {data.hex() if data else '无数据'}") - + # 读取 EPC if event == '-READ_EPC-': if not rfid.is_connected(): @@ -477,22 +662,31 @@ def main(): log_message(window, f"响应 (hex): ", is_hex=True) log_message(window, data, is_hex=True) # 解析 EPC + # 注意: parse_getres_frame返回的data是从bb开始的(status已被去除) + # 原始格式: 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: - # 检查 Card Op Resp - if data[2:4] == b'\x02\x22': - # 有卡 - rssi = data[6] if len(data) > 6 else 0 - epc_len_indicator = data[7] if len(data) > 7 else 0 + # 检查 Magic 和 Card Op Resp + if data[0:1] == b'\xbb' and data[1:3] == b'\x02\x22': + # 有卡,解析 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[9:9+epc_len] if len(data) > 9 else b'' + 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"RSSI: 0x{rssi:02x}") + 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[2:4] == b'\x01\xff': - log_message(window, "无卡或读取失败") + elif data[0:1] == b'\xbb' and data[1:3] == b'\x01\xff': + log_message(window, "无卡或读取失败 (Card Op Resp: 01 ff)") else: - log_message(window, f"未知响应: {data[2:4].hex()}") - + 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()}") + # 选中卡 if event == '-SELECT_CARD-': if not rfid.is_connected(): @@ -510,14 +704,18 @@ def main(): log_message(window, f"错误: {err}") else: log_message(window, f"响应 (hex): {data.hex() if data else '无数据'}") - if data and len(data) > 6: - if data[6] == 0x00: + # 注意: parse_getres_frame返回的data是从bb开始的(status已被去除) + # 原始格式: 00 bb 01 0c 00 01 00 0e 7e + # 去掉status后: bb 01 0c 00 01 00 0e 7e + # data[0]=bb, data[1:3]=01 0c(Card Op Resp), data[3:5]=00 01(Internal Length), data[5]=00(Payload-选中成功标志) + if data and len(data) > 5: + if data[5] == 0x00: log_message(window, "选中成功") else: - log_message(window, f"选中失败: 0x{data[6]:02x}") + log_message(window, f"选中失败: 0x{data[5]:02x}") except ValueError: log_message(window, "错误: EPC 数据格式错误,请使用十六进制") - + # 写入 EPC if event == '-WRITE_EPC-': if not rfid.is_connected(): @@ -539,13 +737,13 @@ def main(): log_message(window, f"响应 (hex): {data.hex() if data else '无数据'}") except ValueError: log_message(window, "错误: EPC 数据格式错误,请使用十六进制") - + # 清空日志 if event == '-CLEAR_LOG-': window['-LOG-'].update('') - + window.close() if __name__ == '__main__': - main() \ No newline at end of file + main() diff --git a/test_protocol.py b/test_protocol.py index 0988b65..08c7b8c 100644 --- a/test_protocol.py +++ b/test_protocol.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ -测试 MT6 RFID 读卡器协议函数 +测试 MT6 RFID 读卡器协议函数(大端序版本) """ import sys @@ -16,7 +16,11 @@ from rfid_tester import ( build_setreq_frame, parse_getres_frame, cmd_get_version, + cmd_factory_reset, + cmd_read_format, + cmd_set_format, cmd_buzzer, + cmd_rf_power, cmd_set_power, cmd_set_mode, cmd_read_epc, @@ -29,28 +33,28 @@ def test_calc_outer_checksum(): """测试校验和计算""" print("\n=== 测试校验和计算 ===") - # 根据文档,读版本号命令的校验和应该是 0xc1 - # Data Length: 01 00 (小端序) + # 根据文档,读版本号命令 + # Data Length: 00 01 (大端序) # Data: c0 - data_len = bytes([0x01, 0x00]) + data_len = bytes([0x00, 0x01]) data = bytes([0xc0]) checksum = calc_outer_checksum(data_len, data) print(f"读版本号校验和: 0x{checksum:02x} (期望: 0xc1)") assert checksum == 0xc1, f"校验和计算错误: 期望 0xc1, 实际 0x{checksum:02x}" - # 打开蜂鸣器的校验和应该是 0xce - # Data Length: 02 00 + # 打开蜂鸣器 + # Data Length: 00 02 (大端序) # Data: cd 01 - data_len = bytes([0x02, 0x00]) + data_len = bytes([0x00, 0x02]) data = bytes([0xcd, 0x01]) checksum = calc_outer_checksum(data_len, data) print(f"打开蜂鸣器校验和: 0x{checksum:02x} (期望: 0xce)") assert checksum == 0xce, f"校验和计算错误: 期望 0xce, 实际 0x{checksum:02x}" - # 设置功率=8的校验和应该是 0xc6 - # Data Length: 02 00 + # 设置功率=8 + # Data Length: 00 02 (大端序) # Data: cc 08 - data_len = bytes([0x02, 0x00]) + data_len = bytes([0x00, 0x02]) data = bytes([0xcc, 0x08]) checksum = calc_outer_checksum(data_len, data) print(f"设置功率=8校验和: 0x{checksum:02x} (期望: 0xc6)") @@ -60,14 +64,14 @@ def test_calc_outer_checksum(): def test_build_setreq_frame(): - """测试帧构建""" - print("\n=== 测试帧构建 ===") + """测试帧构建(大端序)""" + print("\n=== 测试帧构建(大端序)===") # 测试读版本号命令 cmd = cmd_get_version() frame = build_setreq_frame(cmd) - # 根据文档,应该是: 01 00 00 00 00 06 00 00 02 01 00 c0 c1 03 - expected = bytes([0x01, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x02, 0x01, 0x00, 0xc0, 0xc1, 0x03]) + # 大端序: 01 00 00 00 00 00 06 00 02 00 01 c0 c1 03 + expected = bytes([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x02, 0x00, 0x01, 0xc0, 0xc1, 0x03]) print(f"读版本号帧 (前14字节): {frame[:14].hex()}") print(f"期望: {expected.hex()}") assert frame[:14] == expected, f"读版本号帧不匹配" @@ -77,8 +81,8 @@ def test_build_setreq_frame(): # 测试打开蜂鸣器命令 cmd = cmd_buzzer(True) frame = build_setreq_frame(cmd) - # 根据文档: 01 00 00 00 00 07 00 00 02 02 00 cd 01 ce 03 - expected = bytes([0x01, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x02, 0x02, 0x00, 0xcd, 0x01, 0xce, 0x03]) + # 大端序: 01 00 00 00 00 00 07 00 02 00 02 cd 01 ce 03 + expected = bytes([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x02, 0x00, 0x02, 0xcd, 0x01, 0xce, 0x03]) print(f"打开蜂鸣器帧 (前15字节): {frame[:15].hex()}") print(f"期望: {expected.hex()}") assert frame[:15] == expected, f"打开蜂鸣器帧不匹配" @@ -87,8 +91,8 @@ def test_build_setreq_frame(): # 测试关闭蜂鸣器命令 cmd = cmd_buzzer(False) frame = build_setreq_frame(cmd) - # 根据文档: 01 00 00 00 00 07 00 00 02 02 00 cd 00 cf 03 - expected = bytes([0x01, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x02, 0x02, 0x00, 0xcd, 0x00, 0xcf, 0x03]) + # 大端序: 01 00 00 00 00 00 07 00 02 00 02 cd 00 cf 03 + expected = bytes([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x02, 0x00, 0x02, 0xcd, 0x00, 0xcf, 0x03]) print(f"关闭蜂鸣器帧 (前15字节): {frame[:15].hex()}") print(f"期望: {expected.hex()}") assert frame[:15] == expected, f"关闭蜂鸣器帧不匹配" @@ -97,8 +101,8 @@ def test_build_setreq_frame(): # 测试设置功率命令 cmd = cmd_set_power(8) frame = build_setreq_frame(cmd) - # 根据文档: 01 00 00 00 00 07 00 00 02 02 00 cc 08 c6 03 - expected = bytes([0x01, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x02, 0x02, 0x00, 0xcc, 0x08, 0xc6, 0x03]) + # 大端序: 01 00 00 00 00 00 07 00 02 00 02 cc 08 c6 03 + expected = bytes([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x02, 0x00, 0x02, 0xcc, 0x08, 0xc6, 0x03]) print(f"设置功率=8帧 (前15字节): {frame[:15].hex()}") print(f"期望: {expected.hex()}") assert frame[:15] == expected, f"设置功率帧不匹配" @@ -107,8 +111,8 @@ def test_build_setreq_frame(): # 测试设置被动模式命令 cmd = cmd_set_mode(2) frame = build_setreq_frame(cmd) - # 根据文档: 01 00 00 00 00 07 00 00 02 02 00 0f 02 0f 03 - expected = bytes([0x01, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x02, 0x02, 0x00, 0x0f, 0x02, 0x0f, 0x03]) + # 大端序: 01 00 00 00 00 00 07 00 02 00 02 0f 02 0f 03 + expected = bytes([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x02, 0x00, 0x02, 0x0f, 0x02, 0x0f, 0x03]) print(f"设置被动模式帧 (前15字节): {frame[:15].hex()}") print(f"期望: {expected.hex()}") assert frame[:15] == expected, f"设置模式帧不匹配" @@ -117,34 +121,49 @@ def test_build_setreq_frame(): # 测试读取 EPC 命令 cmd = cmd_read_epc() frame = build_setreq_frame(cmd) - print(f"读取EPC帧 (前20字节): {frame[:20].hex()}") - # 验证帧结构 - assert frame[0] == 0x01, "Report ID 应为 0x01" - assert frame[1:5] == b'\x00\x00\x00\x00', "固定字段应为 0x00000000" - assert frame[7:9] == b'\x00\x02', "Constant 应为 0x0002" + # Data: ce bb 00 22 00 00 22 7e (8 bytes) + # Frame Length: 00 0d (大端序, 13) + # Data Length: 00 08 (大端序) + # Checksum: 00 ^ 08 ^ ce ^ bb ^ 00 ^ 22 ^ 00 ^ 00 ^ 22 ^ 7e = 03 + expected = bytes([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x02, 0x00, 0x08, + 0xce, 0xbb, 0x00, 0x22, 0x00, 0x00, 0x22, 0x7e, 0x03, 0x03]) + print(f"读取EPC帧 (前21字节): {frame[:21].hex()}") + print(f"期望: {expected.hex()}") + assert frame[:21] == expected, f"读取EPC帧不匹配" print("读取EPC帧测试通过!") + + # 测试打开射频命令 + cmd = cmd_rf_power(True) + frame = build_setreq_frame(cmd) + # Data: 90 01 (2 bytes) + # Data Length: 00 02 + # Frame Length: 00 07 + # Checksum: 00 ^ 02 ^ 90 ^ 01 = 93 + expected = bytes([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x02, 0x00, 0x02, 0x90, 0x01, 0x93, 0x03]) + print(f"打开射频帧 (前15字节): {frame[:15].hex()}") + print(f"期望: {expected.hex()}") + assert frame[:15] == expected, f"打开射频帧不匹配" + print("打开射频帧测试通过!") def test_parse_getres_frame(): - """测试响应帧解析""" - print("\n=== 测试响应帧解析 ===") + """测试响应帧解析(大端序)""" + print("\n=== 测试响应帧解析(大端序)===") # 模拟读版本号成功的响应 - # Report ID: 0x03 - # 响应数据: 00 + 版本字符串 "MT6_RF915_RW" version_str = b"MT6_RF915_RW" response = bytearray() response.append(0x03) # Report ID response.extend([0x00, 0x00, 0x00, 0x00]) # Fixed - frame_len = 2 + 2 + 1 + len(version_str) # constant + data_len + status + version - response.extend(frame_len.to_bytes(2, 'little')) # Frame Length + frame_len = 2 + 2 + 1 + len(version_str) + 1 # constant + data_len + status + version + checksum + response.extend(frame_len.to_bytes(2, 'big')) # Frame Length (大端序) response.extend([0x00, 0x02]) # Constant data_len = 1 + len(version_str) - response.extend(data_len.to_bytes(2, 'little')) # Data Length + response.extend(data_len.to_bytes(2, 'big')) # Data Length (大端序) response.append(0x00) # Status = 成功 response.extend(version_str) # Version string # 计算校验和 - checksum = calc_outer_checksum(data_len.to_bytes(2, 'little'), bytes([0x00]) + version_str) + checksum = calc_outer_checksum(data_len.to_bytes(2, 'big'), bytes([0x00]) + version_str) response.append(checksum) response.append(0x03) # End Marker @@ -167,6 +186,21 @@ def test_command_functions(): print(f"读版本号命令: {cmd.hex()}") assert cmd == bytes([0xc0]), f"读版本号命令应为 c0" + # 测试恢复出厂设置命令 + cmd = cmd_factory_reset() + print(f"恢复出厂设置命令: {cmd.hex()}") + assert cmd == bytes([0xcf]), f"恢复出厂设置命令应为 cf" + + # 测试读取格式命令 + cmd = cmd_read_format() + print(f"读取格式命令: {cmd.hex()}") + assert cmd == bytes([0x83]), f"读取格式命令应为 83" + + # 测试设置格式命令 + cmd = cmd_set_format() + print(f"设置格式命令: {cmd.hex()}") + assert cmd == bytes([0x82, 0x0f, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00]), f"设置格式命令不匹配" + # 测试蜂鸣器命令 cmd = cmd_buzzer(True) print(f"打开蜂鸣器命令: {cmd.hex()}") @@ -176,6 +210,15 @@ def test_command_functions(): print(f"关闭蜂鸣器命令: {cmd.hex()}") assert cmd == bytes([0xcd, 0x00]), f"关闭蜂鸣器命令应为 cd 00" + # 测试射频电源命令 + cmd = cmd_rf_power(True) + print(f"打开射频命令: {cmd.hex()}") + assert cmd == bytes([0x90, 0x01]), f"打开射频命令应为 90 01" + + cmd = cmd_rf_power(False) + print(f"关闭射频命令: {cmd.hex()}") + assert cmd == bytes([0x90, 0x00]), f"关闭射频命令应为 90 00" + # 测试设置功率命令 cmd = cmd_set_power(5) print(f"设置功率=5命令: {cmd.hex()}") @@ -195,13 +238,49 @@ def test_command_functions(): print(f"读取EPC命令: {cmd.hex()}") assert cmd == bytes([0xce, 0xbb, 0x00, 0x22, 0x00, 0x00, 0x22, 0x7e]), f"读取EPC命令不匹配" + # 测试选中卡命令(验证校验和计算包含Internal Length) + epc_data = bytes([0x11, 0x22]) + cmd = cmd_select_card(epc_data) + print(f"选中卡命令 (EPC=1122): {cmd.hex()}") + # 根据协议: ce bb 00 0c 00 09 01 00 00 00 20 10 00 11 22 79 7e + # Checksum = 00+0c+00+09+01+00+00+00+20+10+00+11+22 mod 256 = 79 + expected = bytes([0xce, 0xbb, 0x00, 0x0c, 0x00, 0x09, 0x01, 0x00, 0x00, 0x00, 0x20, 0x10, 0x00, 0x11, 0x22, 0x79, 0x7e]) + assert cmd == expected, f"选中卡命令不匹配: 期望 {expected.hex()}, 实际 {cmd.hex()}" + print("命令生成函数测试通过!") +def test_epc_operations(): + """测试EPC操作命令""" + print("\n=== 测试EPC操作命令 ===") + + # 测试选中卡命令 + epc_data = bytes([0x11, 0x22]) + cmd = cmd_select_card(epc_data) + print(f"选中卡命令: {cmd.hex()}") + # 验证结构: ce bb + Card Op Command + Internal Length(大端) + Payload + Checksum + 7e + assert cmd[0:2] == bytes([0xce, 0xbb]), "Command/Magic不正确" + assert cmd[2:4] == bytes([0x00, 0x0c]), "Card Op Command不正确" + assert cmd[4:6] == bytes([0x00, 0x09]), "Internal Length应为00 09 (大端序)" + assert cmd[-1] == 0x7e, "End Marker应为7e" + print("选中卡命令测试通过!") + + # 测试写入EPC命令 + old_epc_crc = bytes([0xca, 0x9e]) # 大端序 + new_epc = bytes([0x11, 0x23]) + cmd = cmd_write_epc(old_epc_crc, new_epc) + print(f"写入EPC命令: {cmd.hex()}") + # 验证结构 + assert cmd[0:2] == bytes([0xce, 0xbb]), "Command/Magic不正确" + assert cmd[2:4] == bytes([0x00, 0x49]), "Card Op Command不正确" + assert cmd[-1] == 0x7e, "End Marker应为7e" + print("写入EPC命令测试通过!") + + def main(): """主测试函数""" print("=" * 60) - print("MT6 RFID 读卡器协议函数测试") + print("MT6 RFID 读卡器协议函数测试(大端序版本)") print("=" * 60) try: @@ -209,6 +288,7 @@ def main(): test_build_setreq_frame() test_parse_getres_frame() test_command_functions() + test_epc_operations() print("\n" + "=" * 60) print("所有测试通过!")