nfcscript 0.0.1__tar.gz → 0.0.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nfcscript-0.0.7/PKG-INFO +264 -0
- nfcscript-0.0.7/README.md +254 -0
- {nfcscript-0.0.1 → nfcscript-0.0.7}/pyproject.toml +14 -7
- nfcscript-0.0.7/src/nfc/__init__.py +7 -0
- nfcscript-0.0.7/src/nfc/assertions.py +68 -0
- {nfcscript-0.0.1/src/nfcscript → nfcscript-0.0.7/src/nfc}/bits.py +6 -4
- nfcscript-0.0.7/src/nfc/checksum.py +10 -0
- nfcscript-0.0.7/src/nfc/cli.py +116 -0
- nfcscript-0.0.7/src/nfc/delay.py +4 -0
- nfcscript-0.0.7/src/nfc/hex_util.py +99 -0
- nfcscript-0.0.7/src/nfc/nfc_cli.py +202 -0
- nfcscript-0.0.7/src/nfc/reader.py +300 -0
- nfcscript-0.0.7/src/nfc/trace.py +73 -0
- nfcscript-0.0.7/src/nfcscript.egg-info/PKG-INFO +264 -0
- {nfcscript-0.0.1 → nfcscript-0.0.7}/src/nfcscript.egg-info/SOURCES.txt +10 -4
- nfcscript-0.0.7/src/nfcscript.egg-info/entry_points.txt +3 -0
- nfcscript-0.0.7/src/nfcscript.egg-info/requires.txt +2 -0
- nfcscript-0.0.7/src/nfcscript.egg-info/top_level.txt +1 -0
- nfcscript-0.0.1/PKG-INFO +0 -12
- nfcscript-0.0.1/README.md +0 -3
- nfcscript-0.0.1/src/nfcscript/__init__.py +0 -0
- nfcscript-0.0.1/src/nfcscript/assertions.py +0 -37
- nfcscript-0.0.1/src/nfcscript/main.py +0 -5
- nfcscript-0.0.1/src/nfcscript.egg-info/PKG-INFO +0 -12
- nfcscript-0.0.1/src/nfcscript.egg-info/entry_points.txt +0 -2
- nfcscript-0.0.1/src/nfcscript.egg-info/requires.txt +0 -1
- nfcscript-0.0.1/src/nfcscript.egg-info/top_level.txt +0 -1
- {nfcscript-0.0.1 → nfcscript-0.0.7}/setup.cfg +0 -0
- {nfcscript-0.0.1 → nfcscript-0.0.7}/src/nfcscript.egg-info/dependency_links.txt +0 -0
- {nfcscript-0.0.1 → nfcscript-0.0.7}/tests/test_main.py +0 -0
nfcscript-0.0.7/PKG-INFO
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nfcscript
|
|
3
|
+
Version: 0.0.7
|
|
4
|
+
Summary: A plugin-based core engine for DSL-style NFC testing automation.
|
|
5
|
+
Author: crthu
|
|
6
|
+
Requires-Python: >=3.14
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: nfctester
|
|
9
|
+
Requires-Dist: python-dotenv
|
|
10
|
+
|
|
11
|
+
# nfcscript
|
|
12
|
+
|
|
13
|
+
NFC 测试脚本 DSL 执行环境,基于 `nfctester` 插件化架构。
|
|
14
|
+
|
|
15
|
+
## 安装
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
uv sync
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 使用
|
|
22
|
+
|
|
23
|
+
### nfcscript - 脚本执行
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
nfcscript test_card.py
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
#### 常用参数
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# 指定串口
|
|
33
|
+
nfcscript test_card.py -p COM21
|
|
34
|
+
|
|
35
|
+
# 指定读卡器类型
|
|
36
|
+
nfcscript test_card.py -r acr122u
|
|
37
|
+
|
|
38
|
+
# 开启追踪层
|
|
39
|
+
nfcscript test_card.py --trace-driver --trace-protocol
|
|
40
|
+
|
|
41
|
+
# 设置日志级别
|
|
42
|
+
nfcscript test_card.py --trace-level DEBUG
|
|
43
|
+
|
|
44
|
+
# 组合使用
|
|
45
|
+
nfcscript test_card.py -p COM21 -r pn532 --trace-driver --trace-protocol --trace-level DEBUG
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### nfc-cli - 交互式命令行
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# 列出所有可用卡片类型
|
|
52
|
+
nfc-cli --list-cards
|
|
53
|
+
|
|
54
|
+
# 连接卡片并进入交互模式
|
|
55
|
+
nfc-cli -c ntag224 -p COM21
|
|
56
|
+
|
|
57
|
+
# 指定读卡器类型
|
|
58
|
+
nfc-cli -c mifare_classic -p COM21 -r pn532
|
|
59
|
+
|
|
60
|
+
# 通过环境变量自动发现外部卡片
|
|
61
|
+
export NFC_CARD_PATH="../scripts/lib"
|
|
62
|
+
nfc-cli -c sm7 -p COM20
|
|
63
|
+
|
|
64
|
+
# 或手动指定
|
|
65
|
+
nfc-cli -c sm7 -p COM20 -i ../scripts/lib
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
#### 交互命令
|
|
69
|
+
|
|
70
|
+
进入交互模式后,可以输入命令操作卡片:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
> help # 显示可用命令
|
|
74
|
+
> auth 7 FFFFFFFFFFFF # 认证块 7
|
|
75
|
+
> read 10 # 读取页 10
|
|
76
|
+
> write 20 AABBCCDD... # 写入页 20
|
|
77
|
+
> quit # 退出
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
支持的值格式:
|
|
81
|
+
- 十六进制: `0xFF`, `0xAABBCCDD`
|
|
82
|
+
- 十进制: `7`, `255`
|
|
83
|
+
- 字节串: `b'\xff\x00'`
|
|
84
|
+
|
|
85
|
+
#### 导入外部卡片
|
|
86
|
+
|
|
87
|
+
使用 `-i` 参数导入外部卡片模块,支持单个 `.py` 文件或包含 `__init__.py` 的目录:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# 导入单个文件
|
|
91
|
+
nfc-cli -c my_card -p COM20 -i /path/to/my_card.py
|
|
92
|
+
|
|
93
|
+
# 导入目录(需要有 __init__.py)
|
|
94
|
+
nfc-cli -c sm7 -p COM20 -i /path/to/lib
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
外部卡片模块需要使用 `@CardRegistry.register("name")` 装饰器注册。
|
|
98
|
+
|
|
99
|
+
#### 示例
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
# NTAG224 认证并读取
|
|
103
|
+
nfc-cli -c ntag224 -p COM21
|
|
104
|
+
> auth 1 AABBCCDD112233445566778899001122
|
|
105
|
+
> read 4
|
|
106
|
+
|
|
107
|
+
# Mifare Classic 操作
|
|
108
|
+
nfc-cli -c mifare_classic -p COM21
|
|
109
|
+
> authenticate 7 FFFFFFFFFFFF
|
|
110
|
+
> read_block 8
|
|
111
|
+
> write_block 9 AABBCCDD112233445566778899001122
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 脚本示例
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from nfc import *
|
|
118
|
+
|
|
119
|
+
# 连接读卡器 (默认使用环境变量 NFC_PORT 或 COM20)
|
|
120
|
+
connect()
|
|
121
|
+
|
|
122
|
+
# 开启追踪
|
|
123
|
+
trace.set_layer("DRIVER", True)
|
|
124
|
+
trace.set_layer("PROTOCOL", True)
|
|
125
|
+
|
|
126
|
+
# 寻卡
|
|
127
|
+
card_info = active()
|
|
128
|
+
ASSERT_IS_NOT_NONE(card_info, "未扫描到卡片")
|
|
129
|
+
print(f"UID: {card_info['uid'].hex().upper()}")
|
|
130
|
+
|
|
131
|
+
# 创建卡片实例
|
|
132
|
+
from nfctester.registry import CardRegistry
|
|
133
|
+
card = CardRegistry.create("mifare", reader=get_reader())
|
|
134
|
+
|
|
135
|
+
# 关闭连接
|
|
136
|
+
close()
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### 上下文管理器
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
from nfc import *
|
|
143
|
+
|
|
144
|
+
with session() as reader:
|
|
145
|
+
card_info = reader.find()
|
|
146
|
+
print(card_info)
|
|
147
|
+
# 自动断开连接
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## 模块说明
|
|
151
|
+
|
|
152
|
+
| 模块 | 说明 |
|
|
153
|
+
|------|------|
|
|
154
|
+
| `reader.py` | 读卡器连接与通信 |
|
|
155
|
+
| `trace.py` | 日志追踪控制 |
|
|
156
|
+
| `assertions.py` | 测试断言 |
|
|
157
|
+
| `bits.py` | 位操作工具 |
|
|
158
|
+
| `hex_util.py` | 十六进制工具 |
|
|
159
|
+
| `checksum.py` | 校验工具 |
|
|
160
|
+
| `delay.py` | 延时工具 |
|
|
161
|
+
|
|
162
|
+
## API 参考
|
|
163
|
+
|
|
164
|
+
### reader.py
|
|
165
|
+
|
|
166
|
+
| 函数 | 说明 |
|
|
167
|
+
|------|------|
|
|
168
|
+
| `connect(port, reader_type)` | 连接读卡器 |
|
|
169
|
+
| `get_reader()` | 获取当前读卡器实例 |
|
|
170
|
+
| `active(ll, ignore_error)` | 寻卡,返回 uid/atq/sak |
|
|
171
|
+
| `transceive(data, tx_crc, rx_crc)` | 底层帧交互 |
|
|
172
|
+
| `transceive_bits(data, last_tx_bits, tx_crc, rx_crc)` | 位控制帧交互 |
|
|
173
|
+
| `reqa()` | ISO14443-A REQA |
|
|
174
|
+
| `wupa()` | ISO14443-A WUPA |
|
|
175
|
+
| `halt()` | ISO14443-A HALT |
|
|
176
|
+
| `select(cl_level, uid)` | ISO14443-A SELECT |
|
|
177
|
+
| `anticoll(cl_level, nvb, uid_prefix)` | ISO14443-A ANTICOLL |
|
|
178
|
+
| `field_on()` / `field_off()` | RF 场控制 |
|
|
179
|
+
| `session(port, reader_type)` | 上下文管理器 |
|
|
180
|
+
| `close()` | 断开连接 |
|
|
181
|
+
|
|
182
|
+
### trace.py
|
|
183
|
+
|
|
184
|
+
| 函数 | 说明 |
|
|
185
|
+
|------|------|
|
|
186
|
+
| `trace.set_layer(layer, enable)` | 开启/关闭追踪层 |
|
|
187
|
+
| `trace.set_level(level)` | 设置日志级别 |
|
|
188
|
+
| `trace.info(msg)` / `trace.error(msg)` / `trace.warning(msg)` / `trace.success(msg)` / `trace.debug(msg)` | 输出日志 |
|
|
189
|
+
|
|
190
|
+
### assertions.py
|
|
191
|
+
|
|
192
|
+
| 函数 | 说明 |
|
|
193
|
+
|------|------|
|
|
194
|
+
| `ASSERT_EQUAL(expected, actual, msg)` | 相等断言 |
|
|
195
|
+
| `ASSERT_LEN(data, expected_len, msg)` | 长度断言 |
|
|
196
|
+
| `ASSERT_IS_NOT_NONE(value, msg)` | 非空断言 |
|
|
197
|
+
| `ASSERT_IS_NONE(value, msg)` | 空值断言 |
|
|
198
|
+
|
|
199
|
+
### bits.py
|
|
200
|
+
|
|
201
|
+
| 函数 | 说明 |
|
|
202
|
+
|------|------|
|
|
203
|
+
| `BITS_UPDATE(val, mask, data)` | Read-Modify-Write |
|
|
204
|
+
| `BITS_SET(val, mask)` | 置位 |
|
|
205
|
+
| `BITS_RESET(val, mask)` | 清位 |
|
|
206
|
+
|
|
207
|
+
### hex_util.py
|
|
208
|
+
|
|
209
|
+
| 函数 | 说明 |
|
|
210
|
+
|------|------|
|
|
211
|
+
| `PARSE_HEX(text)` | 解析 hex 字符串 |
|
|
212
|
+
| `FORMAT_HEX(data, last_bits)` | 格式化为可视化 hex |
|
|
213
|
+
|
|
214
|
+
### checksum.py
|
|
215
|
+
|
|
216
|
+
| 函数 | 说明 |
|
|
217
|
+
|------|------|
|
|
218
|
+
| `GET_BCC(data)` | 计算异或校验 |
|
|
219
|
+
|
|
220
|
+
### delay.py
|
|
221
|
+
|
|
222
|
+
| 函数 | 说明 |
|
|
223
|
+
|------|------|
|
|
224
|
+
| `DELAY_MS(ms)` | 毫秒延时 |
|
|
225
|
+
|
|
226
|
+
## 环境变量
|
|
227
|
+
|
|
228
|
+
优先级: CLI 参数 > 内层 `.env` > 外层 `.env` > 系统环境变量 (必须配置,否则报错)
|
|
229
|
+
|
|
230
|
+
| 变量 | 说明 | 默认值 |
|
|
231
|
+
|------|------|--------|
|
|
232
|
+
| `NFC_PORT` | 串口号 | - |
|
|
233
|
+
| `NFC_READER` | 读卡器类型 | - |
|
|
234
|
+
| `NFC_TRACE_LEVEL` | 日志级别 | `INFO` |
|
|
235
|
+
| `NFC_TRACE_DRIVER` | 开启 DRIVER 层追踪 | `false` |
|
|
236
|
+
| `NFC_TRACE_PROTOCOL` | 开启 PROTOCOL 层追踪 | `false` |
|
|
237
|
+
| `NFC_CARD_PATH` | 外部卡片模块搜索路径 (分号分隔) | - |
|
|
238
|
+
|
|
239
|
+
支持多层 `.env` 加载:从脚本目录向上搜索所有 `.env` 文件,按从外到内的顺序依次加载(内层覆盖外层同名变量)。
|
|
240
|
+
|
|
241
|
+
例如,脚本位于 `/project/tests/run.py`,沿途有 `/project/.env` 和 `/project/tests/.env`,加载顺序为:
|
|
242
|
+
|
|
243
|
+
1. `/project/.env` (基础配置)
|
|
244
|
+
2. `/project/tests/.env` (覆盖基础配置中的同名变量)
|
|
245
|
+
|
|
246
|
+
在项目根目录或脚本所在目录创建 `.env` 文件进行持久化配置:
|
|
247
|
+
|
|
248
|
+
```
|
|
249
|
+
NFC_PORT=COM20
|
|
250
|
+
NFC_READER=pn532
|
|
251
|
+
NFC_TRACE_LEVEL=DEBUG
|
|
252
|
+
NFC_TRACE_DRIVER=true
|
|
253
|
+
NFC_TRACE_PROTOCOL=true
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## 开发
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
# 运行测试
|
|
260
|
+
uv run pytest
|
|
261
|
+
|
|
262
|
+
# 运行特定测试
|
|
263
|
+
uv run pytest tests/test_xxx.py
|
|
264
|
+
```
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# nfcscript
|
|
2
|
+
|
|
3
|
+
NFC 测试脚本 DSL 执行环境,基于 `nfctester` 插件化架构。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
uv sync
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 使用
|
|
12
|
+
|
|
13
|
+
### nfcscript - 脚本执行
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
nfcscript test_card.py
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
#### 常用参数
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# 指定串口
|
|
23
|
+
nfcscript test_card.py -p COM21
|
|
24
|
+
|
|
25
|
+
# 指定读卡器类型
|
|
26
|
+
nfcscript test_card.py -r acr122u
|
|
27
|
+
|
|
28
|
+
# 开启追踪层
|
|
29
|
+
nfcscript test_card.py --trace-driver --trace-protocol
|
|
30
|
+
|
|
31
|
+
# 设置日志级别
|
|
32
|
+
nfcscript test_card.py --trace-level DEBUG
|
|
33
|
+
|
|
34
|
+
# 组合使用
|
|
35
|
+
nfcscript test_card.py -p COM21 -r pn532 --trace-driver --trace-protocol --trace-level DEBUG
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### nfc-cli - 交互式命令行
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# 列出所有可用卡片类型
|
|
42
|
+
nfc-cli --list-cards
|
|
43
|
+
|
|
44
|
+
# 连接卡片并进入交互模式
|
|
45
|
+
nfc-cli -c ntag224 -p COM21
|
|
46
|
+
|
|
47
|
+
# 指定读卡器类型
|
|
48
|
+
nfc-cli -c mifare_classic -p COM21 -r pn532
|
|
49
|
+
|
|
50
|
+
# 通过环境变量自动发现外部卡片
|
|
51
|
+
export NFC_CARD_PATH="../scripts/lib"
|
|
52
|
+
nfc-cli -c sm7 -p COM20
|
|
53
|
+
|
|
54
|
+
# 或手动指定
|
|
55
|
+
nfc-cli -c sm7 -p COM20 -i ../scripts/lib
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
#### 交互命令
|
|
59
|
+
|
|
60
|
+
进入交互模式后,可以输入命令操作卡片:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
> help # 显示可用命令
|
|
64
|
+
> auth 7 FFFFFFFFFFFF # 认证块 7
|
|
65
|
+
> read 10 # 读取页 10
|
|
66
|
+
> write 20 AABBCCDD... # 写入页 20
|
|
67
|
+
> quit # 退出
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
支持的值格式:
|
|
71
|
+
- 十六进制: `0xFF`, `0xAABBCCDD`
|
|
72
|
+
- 十进制: `7`, `255`
|
|
73
|
+
- 字节串: `b'\xff\x00'`
|
|
74
|
+
|
|
75
|
+
#### 导入外部卡片
|
|
76
|
+
|
|
77
|
+
使用 `-i` 参数导入外部卡片模块,支持单个 `.py` 文件或包含 `__init__.py` 的目录:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# 导入单个文件
|
|
81
|
+
nfc-cli -c my_card -p COM20 -i /path/to/my_card.py
|
|
82
|
+
|
|
83
|
+
# 导入目录(需要有 __init__.py)
|
|
84
|
+
nfc-cli -c sm7 -p COM20 -i /path/to/lib
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
外部卡片模块需要使用 `@CardRegistry.register("name")` 装饰器注册。
|
|
88
|
+
|
|
89
|
+
#### 示例
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# NTAG224 认证并读取
|
|
93
|
+
nfc-cli -c ntag224 -p COM21
|
|
94
|
+
> auth 1 AABBCCDD112233445566778899001122
|
|
95
|
+
> read 4
|
|
96
|
+
|
|
97
|
+
# Mifare Classic 操作
|
|
98
|
+
nfc-cli -c mifare_classic -p COM21
|
|
99
|
+
> authenticate 7 FFFFFFFFFFFF
|
|
100
|
+
> read_block 8
|
|
101
|
+
> write_block 9 AABBCCDD112233445566778899001122
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 脚本示例
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from nfc import *
|
|
108
|
+
|
|
109
|
+
# 连接读卡器 (默认使用环境变量 NFC_PORT 或 COM20)
|
|
110
|
+
connect()
|
|
111
|
+
|
|
112
|
+
# 开启追踪
|
|
113
|
+
trace.set_layer("DRIVER", True)
|
|
114
|
+
trace.set_layer("PROTOCOL", True)
|
|
115
|
+
|
|
116
|
+
# 寻卡
|
|
117
|
+
card_info = active()
|
|
118
|
+
ASSERT_IS_NOT_NONE(card_info, "未扫描到卡片")
|
|
119
|
+
print(f"UID: {card_info['uid'].hex().upper()}")
|
|
120
|
+
|
|
121
|
+
# 创建卡片实例
|
|
122
|
+
from nfctester.registry import CardRegistry
|
|
123
|
+
card = CardRegistry.create("mifare", reader=get_reader())
|
|
124
|
+
|
|
125
|
+
# 关闭连接
|
|
126
|
+
close()
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 上下文管理器
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from nfc import *
|
|
133
|
+
|
|
134
|
+
with session() as reader:
|
|
135
|
+
card_info = reader.find()
|
|
136
|
+
print(card_info)
|
|
137
|
+
# 自动断开连接
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## 模块说明
|
|
141
|
+
|
|
142
|
+
| 模块 | 说明 |
|
|
143
|
+
|------|------|
|
|
144
|
+
| `reader.py` | 读卡器连接与通信 |
|
|
145
|
+
| `trace.py` | 日志追踪控制 |
|
|
146
|
+
| `assertions.py` | 测试断言 |
|
|
147
|
+
| `bits.py` | 位操作工具 |
|
|
148
|
+
| `hex_util.py` | 十六进制工具 |
|
|
149
|
+
| `checksum.py` | 校验工具 |
|
|
150
|
+
| `delay.py` | 延时工具 |
|
|
151
|
+
|
|
152
|
+
## API 参考
|
|
153
|
+
|
|
154
|
+
### reader.py
|
|
155
|
+
|
|
156
|
+
| 函数 | 说明 |
|
|
157
|
+
|------|------|
|
|
158
|
+
| `connect(port, reader_type)` | 连接读卡器 |
|
|
159
|
+
| `get_reader()` | 获取当前读卡器实例 |
|
|
160
|
+
| `active(ll, ignore_error)` | 寻卡,返回 uid/atq/sak |
|
|
161
|
+
| `transceive(data, tx_crc, rx_crc)` | 底层帧交互 |
|
|
162
|
+
| `transceive_bits(data, last_tx_bits, tx_crc, rx_crc)` | 位控制帧交互 |
|
|
163
|
+
| `reqa()` | ISO14443-A REQA |
|
|
164
|
+
| `wupa()` | ISO14443-A WUPA |
|
|
165
|
+
| `halt()` | ISO14443-A HALT |
|
|
166
|
+
| `select(cl_level, uid)` | ISO14443-A SELECT |
|
|
167
|
+
| `anticoll(cl_level, nvb, uid_prefix)` | ISO14443-A ANTICOLL |
|
|
168
|
+
| `field_on()` / `field_off()` | RF 场控制 |
|
|
169
|
+
| `session(port, reader_type)` | 上下文管理器 |
|
|
170
|
+
| `close()` | 断开连接 |
|
|
171
|
+
|
|
172
|
+
### trace.py
|
|
173
|
+
|
|
174
|
+
| 函数 | 说明 |
|
|
175
|
+
|------|------|
|
|
176
|
+
| `trace.set_layer(layer, enable)` | 开启/关闭追踪层 |
|
|
177
|
+
| `trace.set_level(level)` | 设置日志级别 |
|
|
178
|
+
| `trace.info(msg)` / `trace.error(msg)` / `trace.warning(msg)` / `trace.success(msg)` / `trace.debug(msg)` | 输出日志 |
|
|
179
|
+
|
|
180
|
+
### assertions.py
|
|
181
|
+
|
|
182
|
+
| 函数 | 说明 |
|
|
183
|
+
|------|------|
|
|
184
|
+
| `ASSERT_EQUAL(expected, actual, msg)` | 相等断言 |
|
|
185
|
+
| `ASSERT_LEN(data, expected_len, msg)` | 长度断言 |
|
|
186
|
+
| `ASSERT_IS_NOT_NONE(value, msg)` | 非空断言 |
|
|
187
|
+
| `ASSERT_IS_NONE(value, msg)` | 空值断言 |
|
|
188
|
+
|
|
189
|
+
### bits.py
|
|
190
|
+
|
|
191
|
+
| 函数 | 说明 |
|
|
192
|
+
|------|------|
|
|
193
|
+
| `BITS_UPDATE(val, mask, data)` | Read-Modify-Write |
|
|
194
|
+
| `BITS_SET(val, mask)` | 置位 |
|
|
195
|
+
| `BITS_RESET(val, mask)` | 清位 |
|
|
196
|
+
|
|
197
|
+
### hex_util.py
|
|
198
|
+
|
|
199
|
+
| 函数 | 说明 |
|
|
200
|
+
|------|------|
|
|
201
|
+
| `PARSE_HEX(text)` | 解析 hex 字符串 |
|
|
202
|
+
| `FORMAT_HEX(data, last_bits)` | 格式化为可视化 hex |
|
|
203
|
+
|
|
204
|
+
### checksum.py
|
|
205
|
+
|
|
206
|
+
| 函数 | 说明 |
|
|
207
|
+
|------|------|
|
|
208
|
+
| `GET_BCC(data)` | 计算异或校验 |
|
|
209
|
+
|
|
210
|
+
### delay.py
|
|
211
|
+
|
|
212
|
+
| 函数 | 说明 |
|
|
213
|
+
|------|------|
|
|
214
|
+
| `DELAY_MS(ms)` | 毫秒延时 |
|
|
215
|
+
|
|
216
|
+
## 环境变量
|
|
217
|
+
|
|
218
|
+
优先级: CLI 参数 > 内层 `.env` > 外层 `.env` > 系统环境变量 (必须配置,否则报错)
|
|
219
|
+
|
|
220
|
+
| 变量 | 说明 | 默认值 |
|
|
221
|
+
|------|------|--------|
|
|
222
|
+
| `NFC_PORT` | 串口号 | - |
|
|
223
|
+
| `NFC_READER` | 读卡器类型 | - |
|
|
224
|
+
| `NFC_TRACE_LEVEL` | 日志级别 | `INFO` |
|
|
225
|
+
| `NFC_TRACE_DRIVER` | 开启 DRIVER 层追踪 | `false` |
|
|
226
|
+
| `NFC_TRACE_PROTOCOL` | 开启 PROTOCOL 层追踪 | `false` |
|
|
227
|
+
| `NFC_CARD_PATH` | 外部卡片模块搜索路径 (分号分隔) | - |
|
|
228
|
+
|
|
229
|
+
支持多层 `.env` 加载:从脚本目录向上搜索所有 `.env` 文件,按从外到内的顺序依次加载(内层覆盖外层同名变量)。
|
|
230
|
+
|
|
231
|
+
例如,脚本位于 `/project/tests/run.py`,沿途有 `/project/.env` 和 `/project/tests/.env`,加载顺序为:
|
|
232
|
+
|
|
233
|
+
1. `/project/.env` (基础配置)
|
|
234
|
+
2. `/project/tests/.env` (覆盖基础配置中的同名变量)
|
|
235
|
+
|
|
236
|
+
在项目根目录或脚本所在目录创建 `.env` 文件进行持久化配置:
|
|
237
|
+
|
|
238
|
+
```
|
|
239
|
+
NFC_PORT=COM20
|
|
240
|
+
NFC_READER=pn532
|
|
241
|
+
NFC_TRACE_LEVEL=DEBUG
|
|
242
|
+
NFC_TRACE_DRIVER=true
|
|
243
|
+
NFC_TRACE_PROTOCOL=true
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## 开发
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
# 运行测试
|
|
250
|
+
uv run pytest
|
|
251
|
+
|
|
252
|
+
# 运行特定测试
|
|
253
|
+
uv run pytest tests/test_xxx.py
|
|
254
|
+
```
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "nfcscript"
|
|
3
|
-
version = "0.0.
|
|
4
|
-
description = "NFC
|
|
3
|
+
version = "0.0.7"
|
|
4
|
+
description = "A plugin-based core engine for DSL-style NFC testing automation."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.14"
|
|
7
7
|
authors = [{name = "crthu"}]
|
|
8
8
|
dependencies = [
|
|
9
|
-
"
|
|
9
|
+
"nfctester",
|
|
10
|
+
"python-dotenv",
|
|
10
11
|
]
|
|
11
12
|
|
|
12
13
|
[tool.setuptools.packages.find]
|
|
13
14
|
where = ["src"]
|
|
14
15
|
|
|
15
16
|
[project.scripts]
|
|
16
|
-
nfcscript = "
|
|
17
|
+
nfcscript = "nfc.cli:main"
|
|
18
|
+
nfc-cli = "nfc.nfc_cli:main"
|
|
17
19
|
|
|
18
20
|
[tool.pytest.ini_options]
|
|
19
21
|
pythonpath = ["src"]
|
|
@@ -22,6 +24,9 @@ testpaths = ["tests"]
|
|
|
22
24
|
[tool.uv]
|
|
23
25
|
package = true
|
|
24
26
|
|
|
27
|
+
[tool.uv.sources]
|
|
28
|
+
nfctester = { workspace = true }
|
|
29
|
+
|
|
25
30
|
[dependency-groups]
|
|
26
31
|
dev = [
|
|
27
32
|
"pytest",
|
|
@@ -29,12 +34,14 @@ dev = [
|
|
|
29
34
|
]
|
|
30
35
|
|
|
31
36
|
[tool.bumpversion]
|
|
32
|
-
current_version = "0.0.
|
|
37
|
+
current_version = "0.0.7"
|
|
38
|
+
commit = true
|
|
39
|
+
tag = true
|
|
33
40
|
allow_dirty = true
|
|
41
|
+
tag_name = "v{new_version}"
|
|
42
|
+
message = "chore(release): bump version {current_version} → {new_version}"
|
|
34
43
|
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
|
|
35
44
|
serialize = ["{major}.{minor}.{patch}"]
|
|
36
|
-
search = 'version = "{current_version}"'
|
|
37
|
-
replace = 'version = "{new_version}"'
|
|
38
45
|
|
|
39
46
|
[[tool.bumpversion.files]]
|
|
40
47
|
filename = "pyproject.toml"
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
from typing import Any, TypeGuard, TypeVar
|
|
3
|
+
|
|
4
|
+
from nfc.hex_util import FORMAT_HEX
|
|
5
|
+
|
|
6
|
+
T = TypeVar("T")
|
|
7
|
+
|
|
8
|
+
def _get_stack_trace() -> str:
|
|
9
|
+
"""获取格式化的调用堆栈"""
|
|
10
|
+
# inspect.stack()[0] 是 _get_stack_trace
|
|
11
|
+
# inspect.stack()[1] 是 ASSERT_... 函数
|
|
12
|
+
# inspect.stack()[2:] 是实际的业务调用代码
|
|
13
|
+
stack = inspect.stack()[2:]
|
|
14
|
+
chain = []
|
|
15
|
+
for frame in stack:
|
|
16
|
+
# 简化路径,仅显示文件名
|
|
17
|
+
filename = frame.filename.split('\\')[-1].split('/')[-1]
|
|
18
|
+
chain.append(f" at {filename}:{frame.lineno} in {frame.function}")
|
|
19
|
+
return "\n".join(chain)
|
|
20
|
+
|
|
21
|
+
def ASSERT_LEN(data: Any, expected_len: int, msg: str | None = None) -> None:
|
|
22
|
+
actual_len = len(data)
|
|
23
|
+
if actual_len != expected_len:
|
|
24
|
+
error_msg = (
|
|
25
|
+
f"\nAssertion Failed: {msg}\n" if msg else ""
|
|
26
|
+
) + (
|
|
27
|
+
f"Assertion Failed: Length mismatch\n"
|
|
28
|
+
f" Expected: {expected_len}\n"
|
|
29
|
+
f" Actual : {actual_len}\n"
|
|
30
|
+
f" Data : {data}\n"
|
|
31
|
+
f"Stack Trace:\n{_get_stack_trace()}"
|
|
32
|
+
)
|
|
33
|
+
raise AssertionError(error_msg)
|
|
34
|
+
|
|
35
|
+
def ASSERT_EQUAL(expected: Any, actual: Any, msg: str | None = None) -> None:
|
|
36
|
+
if actual != expected:
|
|
37
|
+
error_msg = (
|
|
38
|
+
f"\nAssertion Failed: {msg}\n" if msg else ""
|
|
39
|
+
) + (
|
|
40
|
+
f"Assertion Failed: Value mismatch\n"
|
|
41
|
+
f" Expected: {FORMAT_HEX(expected)} ({type(expected).__name__})\n"
|
|
42
|
+
f" Actual : {FORMAT_HEX(actual)} ({type(actual).__name__})\n"
|
|
43
|
+
f"Stack Trace:\n{_get_stack_trace()}"
|
|
44
|
+
)
|
|
45
|
+
raise AssertionError(error_msg)
|
|
46
|
+
|
|
47
|
+
def ASSERT_IS_NONE(value: Any, msg: str | None = None) -> None:
|
|
48
|
+
if value is not None:
|
|
49
|
+
error_msg = (
|
|
50
|
+
f"\nAssertion Failed: {msg}\n" if msg else ""
|
|
51
|
+
) + (
|
|
52
|
+
f"Assertion Failed: Expected None\n"
|
|
53
|
+
f" Actual : {value} ({type(value).__name__})\n"
|
|
54
|
+
f"Stack Trace:\n{_get_stack_trace()}"
|
|
55
|
+
)
|
|
56
|
+
raise AssertionError(error_msg)
|
|
57
|
+
|
|
58
|
+
def ASSERT_IS_NOT_NONE(value: T | None, msg: str | None = None) -> TypeGuard[T]:
|
|
59
|
+
if value is None:
|
|
60
|
+
error_msg = (
|
|
61
|
+
f"\nAssertion Failed: {msg}\n" if msg else ""
|
|
62
|
+
) + (
|
|
63
|
+
f"Assertion Failed: Expected Not None\n"
|
|
64
|
+
f" Actual : {value}\n"
|
|
65
|
+
f"Stack Trace:\n{_get_stack_trace()}"
|
|
66
|
+
)
|
|
67
|
+
raise AssertionError(error_msg)
|
|
68
|
+
return True
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
from .hex_util import FORMAT_HEX
|
|
2
|
+
|
|
3
|
+
def BITS_UPDATE(val: int, mask: int, data: int) -> int:
|
|
2
4
|
"""
|
|
3
5
|
更新寄存器中的特定位域 (Read-Modify-Write)
|
|
4
6
|
:param val: 当前寄存器的原始值
|
|
@@ -6,10 +8,10 @@ def bits_update(val: int, mask: int, data: int) -> int:
|
|
|
6
8
|
:param data: 已移位对齐的新数值 (例如: 0x20, 即 1 << 5)
|
|
7
9
|
:return: 更新后的寄存器值
|
|
8
10
|
"""
|
|
9
|
-
assert (data & ~mask) == 0, f"Data {
|
|
11
|
+
assert (data & ~mask) == 0, f"Data {FORMAT_HEX(data)} contains bits outside mask {FORMAT_HEX(mask)}"
|
|
10
12
|
return (val & ~mask) | (data & mask)
|
|
11
13
|
|
|
12
|
-
def
|
|
14
|
+
def BITS_SET(val: int, mask: int) -> int:
|
|
13
15
|
"""
|
|
14
16
|
将寄存器中的指定位强制置 1 (OR 操作)
|
|
15
17
|
|
|
@@ -17,7 +19,7 @@ def bits_set(val: int, mask: int) -> int:
|
|
|
17
19
|
"""
|
|
18
20
|
return val | mask
|
|
19
21
|
|
|
20
|
-
def
|
|
22
|
+
def BITS_RESET(val: int, mask: int) -> int:
|
|
21
23
|
"""
|
|
22
24
|
将寄存器中的指定位强制清 0 (AND-NOT 操作)
|
|
23
25
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from .assertions import ASSERT_LEN
|
|
2
|
+
from .hex_util import FORMAT_HEX
|
|
3
|
+
|
|
4
|
+
def GET_BCC(data: list[int]) -> int:
|
|
5
|
+
"""
|
|
6
|
+
计算 BCC: data[0]^data[1]^data[2]^data[3]
|
|
7
|
+
:param data: 4字节的UID数据
|
|
8
|
+
"""
|
|
9
|
+
ASSERT_LEN(data, 4, msg=f"计算BCC时输入数据长度应为4: {FORMAT_HEX(data)}")
|
|
10
|
+
return data[0] ^ data[1] ^ data[2] ^ data[3]
|