nfcscript 0.0.13__tar.gz → 0.0.15__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.13 → nfcscript-0.0.15}/PKG-INFO +42 -21
- {nfcscript-0.0.13 → nfcscript-0.0.15}/README.md +41 -20
- {nfcscript-0.0.13 → nfcscript-0.0.15}/pyproject.toml +2 -2
- {nfcscript-0.0.13 → nfcscript-0.0.15}/src/nfc/cli.py +19 -40
- nfcscript-0.0.15/src/nfc/hex_util.py +189 -0
- {nfcscript-0.0.13 → nfcscript-0.0.15}/src/nfc/reader.py +5 -5
- {nfcscript-0.0.13 → nfcscript-0.0.15}/src/nfc/trace.py +51 -29
- {nfcscript-0.0.13 → nfcscript-0.0.15}/src/nfcscript.egg-info/PKG-INFO +42 -21
- nfcscript-0.0.15/tests/test_hex_util.py +245 -0
- nfcscript-0.0.13/src/nfc/hex_util.py +0 -131
- nfcscript-0.0.13/tests/test_hex_util.py +0 -168
- {nfcscript-0.0.13 → nfcscript-0.0.15}/setup.cfg +0 -0
- {nfcscript-0.0.13 → nfcscript-0.0.15}/src/nfc/__init__.py +0 -0
- {nfcscript-0.0.13 → nfcscript-0.0.15}/src/nfc/assertions.py +0 -0
- {nfcscript-0.0.13 → nfcscript-0.0.15}/src/nfc/bits.py +0 -0
- {nfcscript-0.0.13 → nfcscript-0.0.15}/src/nfc/checksum.py +0 -0
- {nfcscript-0.0.13 → nfcscript-0.0.15}/src/nfc/delay.py +0 -0
- {nfcscript-0.0.13 → nfcscript-0.0.15}/src/nfc/nfc_cli.py +0 -0
- {nfcscript-0.0.13 → nfcscript-0.0.15}/src/nfcscript.egg-info/SOURCES.txt +0 -0
- {nfcscript-0.0.13 → nfcscript-0.0.15}/src/nfcscript.egg-info/dependency_links.txt +0 -0
- {nfcscript-0.0.13 → nfcscript-0.0.15}/src/nfcscript.egg-info/entry_points.txt +0 -0
- {nfcscript-0.0.13 → nfcscript-0.0.15}/src/nfcscript.egg-info/requires.txt +0 -0
- {nfcscript-0.0.13 → nfcscript-0.0.15}/src/nfcscript.egg-info/top_level.txt +0 -0
- {nfcscript-0.0.13 → nfcscript-0.0.15}/tests/test_assertions.py +0 -0
- {nfcscript-0.0.13 → nfcscript-0.0.15}/tests/test_bits.py +0 -0
- {nfcscript-0.0.13 → nfcscript-0.0.15}/tests/test_checksum.py +0 -0
- {nfcscript-0.0.13 → nfcscript-0.0.15}/tests/test_cli.py +0 -0
- {nfcscript-0.0.13 → nfcscript-0.0.15}/tests/test_cli_collect.py +0 -0
- {nfcscript-0.0.13 → nfcscript-0.0.15}/tests/test_delay.py +0 -0
- {nfcscript-0.0.13 → nfcscript-0.0.15}/tests/test_reader.py +0 -0
- {nfcscript-0.0.13 → nfcscript-0.0.15}/tests/test_trace.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nfcscript
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.15
|
|
4
4
|
Summary: A plugin-based core engine for DSL-style NFC testing automation.
|
|
5
5
|
Author: crthu
|
|
6
6
|
Requires-Python: >=3.14
|
|
@@ -36,13 +36,13 @@ nfcscript test_card.py -p COM21
|
|
|
36
36
|
nfcscript test_card.py -r acr122u
|
|
37
37
|
|
|
38
38
|
# 开启追踪层
|
|
39
|
-
nfcscript test_card.py --trace
|
|
39
|
+
nfcscript test_card.py --trace=protocol,debug
|
|
40
40
|
|
|
41
41
|
# 设置日志级别
|
|
42
|
-
nfcscript test_card.py --trace
|
|
42
|
+
nfcscript test_card.py --level=trace
|
|
43
43
|
|
|
44
44
|
# 组合使用
|
|
45
|
-
nfcscript test_card.py -p COM21 -r pn532 --trace
|
|
45
|
+
nfcscript test_card.py -p COM21 -r pn532 --trace=protocol,debug --level=trace
|
|
46
46
|
```
|
|
47
47
|
|
|
48
48
|
### nfc-cli - 交互式命令行
|
|
@@ -120,13 +120,13 @@ from nfc import *
|
|
|
120
120
|
connect()
|
|
121
121
|
|
|
122
122
|
# 开启追踪
|
|
123
|
-
trace.
|
|
124
|
-
trace.
|
|
123
|
+
trace.driver = True
|
|
124
|
+
trace.protocol = True
|
|
125
125
|
|
|
126
126
|
# 寻卡(自动切换协议解析器)
|
|
127
127
|
card_info = active()
|
|
128
128
|
ASSERT_IS_NOT_NONE(card_info, "未扫描到卡片")
|
|
129
|
-
|
|
129
|
+
trace.app(f"UID: {' '.join(f'{b:02X}' for b in card_info.uid)}")
|
|
130
130
|
|
|
131
131
|
# 创建卡片实例
|
|
132
132
|
from nfctester.registry import CardRegistry
|
|
@@ -143,7 +143,7 @@ from nfc import *
|
|
|
143
143
|
|
|
144
144
|
with session() as reader:
|
|
145
145
|
card_info = reader.active()
|
|
146
|
-
|
|
146
|
+
trace.app(f"UID: {card_info.uid}")
|
|
147
147
|
# 自动断开连接
|
|
148
148
|
```
|
|
149
149
|
|
|
@@ -181,14 +181,36 @@ with session() as reader:
|
|
|
181
181
|
|
|
182
182
|
### trace.py
|
|
183
183
|
|
|
184
|
+
**简洁写法(推荐):**
|
|
185
|
+
|
|
184
186
|
| 函数 | 说明 |
|
|
185
187
|
|------|------|
|
|
186
|
-
| `trace.
|
|
187
|
-
| `trace.
|
|
188
|
-
| `trace.
|
|
189
|
-
| `trace.
|
|
190
|
-
| `trace.
|
|
191
|
-
|
|
188
|
+
| `trace.app(msg)` | 输出到 app 层 (level=50) |
|
|
189
|
+
| `trace.debug(msg)` | 输出到 debug 层 (level=10) |
|
|
190
|
+
| `trace.warning(msg)` | 输出到 warning 层 (level=30) |
|
|
191
|
+
| `trace.error(msg)` | 输出到 error 层 (level=40) |
|
|
192
|
+
| `trace.log(msg, layer="app")` | 通用文本日志,指定层 |
|
|
193
|
+
|
|
194
|
+
**属性控制(推荐):**
|
|
195
|
+
|
|
196
|
+
| 属性 | 说明 |
|
|
197
|
+
|------|------|
|
|
198
|
+
| `trace.driver = True` | 启用 driver 层 |
|
|
199
|
+
| `trace.protocol = True` | 启用 protocol 层 |
|
|
200
|
+
| `trace.debug = False` | 禁用 debug 层 |
|
|
201
|
+
| `trace.level = "warning"` | 设置最低日志级别 |
|
|
202
|
+
| `trace.level = 30` | 或用数字 |
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
# 简洁写法
|
|
206
|
+
trace.app("UID: AA BB CC DD")
|
|
207
|
+
trace.debug("[auth] Rt: 80 81")
|
|
208
|
+
|
|
209
|
+
# 属性控制
|
|
210
|
+
trace.driver = True
|
|
211
|
+
trace.protocol = True
|
|
212
|
+
trace.level = "debug"
|
|
213
|
+
```
|
|
192
214
|
|
|
193
215
|
### assertions.py
|
|
194
216
|
|
|
@@ -211,7 +233,8 @@ with session() as reader:
|
|
|
211
233
|
|
|
212
234
|
| 函数 | 说明 |
|
|
213
235
|
|------|------|
|
|
214
|
-
| `PARSE_HEX(text)` |
|
|
236
|
+
| `PARSE_HEX(text)` | 解析纯 hex,返回 `list[int]` |
|
|
237
|
+
| `PARSE_HEX_BITS(text, last_bits=None)` | 解析 hex + Verilog 位标记,返回 `tuple[list[int], int]` |
|
|
215
238
|
| `FORMAT_HEX(data, last_bits)` | 格式化为可视化 hex |
|
|
216
239
|
|
|
217
240
|
### checksum.py
|
|
@@ -234,9 +257,8 @@ with session() as reader:
|
|
|
234
257
|
|------|------|--------|
|
|
235
258
|
| `NFC_PORT` | 串口号 | - |
|
|
236
259
|
| `NFC_READER` | 读卡器类型 | - |
|
|
237
|
-
| `
|
|
238
|
-
| `
|
|
239
|
-
| `NFC_TRACE_PROTOCOL` | 开启 PROTOCOL 层追踪 | `false` |
|
|
260
|
+
| `NFC_TRACE` | 启用的层 (逗号分隔) | - |
|
|
261
|
+
| `NFC_TRACE_LEVEL` | 最低日志级别 | `warning` |
|
|
240
262
|
| `NFC_CARD_PATH` | 外部卡片模块搜索路径 (分号分隔) | - |
|
|
241
263
|
|
|
242
264
|
支持多层 `.env` 加载:从脚本目录向上搜索所有 `.env` 文件,按从外到内的顺序依次加载(内层覆盖外层同名变量)。
|
|
@@ -251,9 +273,8 @@ with session() as reader:
|
|
|
251
273
|
```
|
|
252
274
|
NFC_PORT=COM20
|
|
253
275
|
NFC_READER=pn532
|
|
254
|
-
NFC_TRACE_LEVEL=
|
|
255
|
-
|
|
256
|
-
NFC_TRACE_PROTOCOL=true
|
|
276
|
+
NFC_TRACE_LEVEL=warning
|
|
277
|
+
NFC_TRACE=protocol,debug
|
|
257
278
|
```
|
|
258
279
|
|
|
259
280
|
## AI 编写脚本
|
|
@@ -26,13 +26,13 @@ nfcscript test_card.py -p COM21
|
|
|
26
26
|
nfcscript test_card.py -r acr122u
|
|
27
27
|
|
|
28
28
|
# 开启追踪层
|
|
29
|
-
nfcscript test_card.py --trace
|
|
29
|
+
nfcscript test_card.py --trace=protocol,debug
|
|
30
30
|
|
|
31
31
|
# 设置日志级别
|
|
32
|
-
nfcscript test_card.py --trace
|
|
32
|
+
nfcscript test_card.py --level=trace
|
|
33
33
|
|
|
34
34
|
# 组合使用
|
|
35
|
-
nfcscript test_card.py -p COM21 -r pn532 --trace
|
|
35
|
+
nfcscript test_card.py -p COM21 -r pn532 --trace=protocol,debug --level=trace
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
### nfc-cli - 交互式命令行
|
|
@@ -110,13 +110,13 @@ from nfc import *
|
|
|
110
110
|
connect()
|
|
111
111
|
|
|
112
112
|
# 开启追踪
|
|
113
|
-
trace.
|
|
114
|
-
trace.
|
|
113
|
+
trace.driver = True
|
|
114
|
+
trace.protocol = True
|
|
115
115
|
|
|
116
116
|
# 寻卡(自动切换协议解析器)
|
|
117
117
|
card_info = active()
|
|
118
118
|
ASSERT_IS_NOT_NONE(card_info, "未扫描到卡片")
|
|
119
|
-
|
|
119
|
+
trace.app(f"UID: {' '.join(f'{b:02X}' for b in card_info.uid)}")
|
|
120
120
|
|
|
121
121
|
# 创建卡片实例
|
|
122
122
|
from nfctester.registry import CardRegistry
|
|
@@ -133,7 +133,7 @@ from nfc import *
|
|
|
133
133
|
|
|
134
134
|
with session() as reader:
|
|
135
135
|
card_info = reader.active()
|
|
136
|
-
|
|
136
|
+
trace.app(f"UID: {card_info.uid}")
|
|
137
137
|
# 自动断开连接
|
|
138
138
|
```
|
|
139
139
|
|
|
@@ -171,14 +171,36 @@ with session() as reader:
|
|
|
171
171
|
|
|
172
172
|
### trace.py
|
|
173
173
|
|
|
174
|
+
**简洁写法(推荐):**
|
|
175
|
+
|
|
174
176
|
| 函数 | 说明 |
|
|
175
177
|
|------|------|
|
|
176
|
-
| `trace.
|
|
177
|
-
| `trace.
|
|
178
|
-
| `trace.
|
|
179
|
-
| `trace.
|
|
180
|
-
| `trace.
|
|
181
|
-
|
|
178
|
+
| `trace.app(msg)` | 输出到 app 层 (level=50) |
|
|
179
|
+
| `trace.debug(msg)` | 输出到 debug 层 (level=10) |
|
|
180
|
+
| `trace.warning(msg)` | 输出到 warning 层 (level=30) |
|
|
181
|
+
| `trace.error(msg)` | 输出到 error 层 (level=40) |
|
|
182
|
+
| `trace.log(msg, layer="app")` | 通用文本日志,指定层 |
|
|
183
|
+
|
|
184
|
+
**属性控制(推荐):**
|
|
185
|
+
|
|
186
|
+
| 属性 | 说明 |
|
|
187
|
+
|------|------|
|
|
188
|
+
| `trace.driver = True` | 启用 driver 层 |
|
|
189
|
+
| `trace.protocol = True` | 启用 protocol 层 |
|
|
190
|
+
| `trace.debug = False` | 禁用 debug 层 |
|
|
191
|
+
| `trace.level = "warning"` | 设置最低日志级别 |
|
|
192
|
+
| `trace.level = 30` | 或用数字 |
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
# 简洁写法
|
|
196
|
+
trace.app("UID: AA BB CC DD")
|
|
197
|
+
trace.debug("[auth] Rt: 80 81")
|
|
198
|
+
|
|
199
|
+
# 属性控制
|
|
200
|
+
trace.driver = True
|
|
201
|
+
trace.protocol = True
|
|
202
|
+
trace.level = "debug"
|
|
203
|
+
```
|
|
182
204
|
|
|
183
205
|
### assertions.py
|
|
184
206
|
|
|
@@ -201,7 +223,8 @@ with session() as reader:
|
|
|
201
223
|
|
|
202
224
|
| 函数 | 说明 |
|
|
203
225
|
|------|------|
|
|
204
|
-
| `PARSE_HEX(text)` |
|
|
226
|
+
| `PARSE_HEX(text)` | 解析纯 hex,返回 `list[int]` |
|
|
227
|
+
| `PARSE_HEX_BITS(text, last_bits=None)` | 解析 hex + Verilog 位标记,返回 `tuple[list[int], int]` |
|
|
205
228
|
| `FORMAT_HEX(data, last_bits)` | 格式化为可视化 hex |
|
|
206
229
|
|
|
207
230
|
### checksum.py
|
|
@@ -224,9 +247,8 @@ with session() as reader:
|
|
|
224
247
|
|------|------|--------|
|
|
225
248
|
| `NFC_PORT` | 串口号 | - |
|
|
226
249
|
| `NFC_READER` | 读卡器类型 | - |
|
|
227
|
-
| `
|
|
228
|
-
| `
|
|
229
|
-
| `NFC_TRACE_PROTOCOL` | 开启 PROTOCOL 层追踪 | `false` |
|
|
250
|
+
| `NFC_TRACE` | 启用的层 (逗号分隔) | - |
|
|
251
|
+
| `NFC_TRACE_LEVEL` | 最低日志级别 | `warning` |
|
|
230
252
|
| `NFC_CARD_PATH` | 外部卡片模块搜索路径 (分号分隔) | - |
|
|
231
253
|
|
|
232
254
|
支持多层 `.env` 加载:从脚本目录向上搜索所有 `.env` 文件,按从外到内的顺序依次加载(内层覆盖外层同名变量)。
|
|
@@ -241,9 +263,8 @@ with session() as reader:
|
|
|
241
263
|
```
|
|
242
264
|
NFC_PORT=COM20
|
|
243
265
|
NFC_READER=pn532
|
|
244
|
-
NFC_TRACE_LEVEL=
|
|
245
|
-
|
|
246
|
-
NFC_TRACE_PROTOCOL=true
|
|
266
|
+
NFC_TRACE_LEVEL=warning
|
|
267
|
+
NFC_TRACE=protocol,debug
|
|
247
268
|
```
|
|
248
269
|
|
|
249
270
|
## AI 编写脚本
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "nfcscript"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.15"
|
|
4
4
|
description = "A plugin-based core engine for DSL-style NFC testing automation."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.14"
|
|
@@ -34,7 +34,7 @@ dev = [
|
|
|
34
34
|
]
|
|
35
35
|
|
|
36
36
|
[tool.bumpversion]
|
|
37
|
-
current_version = "0.0.
|
|
37
|
+
current_version = "0.0.15"
|
|
38
38
|
commit = true
|
|
39
39
|
tag = true
|
|
40
40
|
allow_dirty = true
|
|
@@ -19,20 +19,6 @@ def _find_dotenv_all(start_dir):
|
|
|
19
19
|
chain.reverse()
|
|
20
20
|
return chain
|
|
21
21
|
|
|
22
|
-
def _parse_level(value: str) -> int:
|
|
23
|
-
"""将字符串解析为解析级别,支持数字和别名"""
|
|
24
|
-
val = value.lower().strip()
|
|
25
|
-
if val in ("0", "false", "no", "off"):
|
|
26
|
-
return 0
|
|
27
|
-
if val in ("1", "simple", "summary"):
|
|
28
|
-
return 1
|
|
29
|
-
if val in ("2", "full", "tree"):
|
|
30
|
-
return 2
|
|
31
|
-
try:
|
|
32
|
-
return int(value)
|
|
33
|
-
except ValueError:
|
|
34
|
-
return 1
|
|
35
|
-
|
|
36
22
|
def run_script(script_path):
|
|
37
23
|
script_dir = os.path.dirname(os.path.abspath(script_path))
|
|
38
24
|
cwd = os.getcwd()
|
|
@@ -55,11 +41,8 @@ def main():
|
|
|
55
41
|
parser.add_argument("script", help="脚本文件路径")
|
|
56
42
|
parser.add_argument("-p", "--port", default=None, help="串口号 (默认: NFC_PORT 环境变量或 COM20)")
|
|
57
43
|
parser.add_argument("-r", "--reader", default=None, help="读卡器类型 (默认: NFC_READER 环境变量或 pn532)")
|
|
58
|
-
parser.add_argument("--trace
|
|
59
|
-
parser.add_argument("--
|
|
60
|
-
parser.add_argument("--trace-parse", type=int, default=None, choices=[0, 1, 2],
|
|
61
|
-
help="解析级别: 0=关闭 1=简单 2=复杂 (默认: NFC_TRACE_PARSE 或 1)")
|
|
62
|
-
parser.add_argument("--trace-level", default="INFO", help="日志级别 (默认: INFO)")
|
|
44
|
+
parser.add_argument("--trace", default=None, help="启用的层 (逗号分隔: driver,debug,protocol,warning,error,app,all)")
|
|
45
|
+
parser.add_argument("--level", default=None, help="最低日志级别 (trace/debug/warning/error/app, 默认: warning)")
|
|
63
46
|
|
|
64
47
|
args, extra = parser.parse_known_args()
|
|
65
48
|
|
|
@@ -81,27 +64,23 @@ def main():
|
|
|
81
64
|
|
|
82
65
|
from nfc import trace
|
|
83
66
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
trace.
|
|
101
|
-
trace.set_layer("PROTOCOL", trace_protocol)
|
|
102
|
-
trace.set_parse(_parse_level(trace_parse))
|
|
103
|
-
if trace_width is not None:
|
|
104
|
-
os.environ["CRFT_TRACE_WIDTH"] = trace_width
|
|
67
|
+
# 解析 --trace: 启用指定层
|
|
68
|
+
trace_env = os.environ.get("NFC_TRACE", "")
|
|
69
|
+
trace_arg = args.trace or trace_env
|
|
70
|
+
if trace_arg:
|
|
71
|
+
trace.set_level("trace") # 允许所有层可见
|
|
72
|
+
for name in trace_arg.split(","):
|
|
73
|
+
name = name.strip().lower()
|
|
74
|
+
if name == "all":
|
|
75
|
+
for layer in ["driver", "debug", "protocol", "warning", "error", "app"]:
|
|
76
|
+
trace.set_layer(layer, True)
|
|
77
|
+
elif name:
|
|
78
|
+
trace.set_layer(name, True)
|
|
79
|
+
|
|
80
|
+
# 解析 --level: 最低级别过滤
|
|
81
|
+
level_env = os.environ.get("NFC_TRACE_LEVEL", "warning")
|
|
82
|
+
level_arg = args.level or level_env
|
|
83
|
+
trace.set_level(level_arg)
|
|
105
84
|
|
|
106
85
|
run_script(args.script)
|
|
107
86
|
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
# 位标记正则:匹配 N'hXX 格式 (如 7'h26, 4'hC)
|
|
4
|
+
BIT_PATTERN = re.compile(r"(\d+)\s*\'h\s*([0-9a-fA-F]+)")
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def PARSE_HEX(text: str) -> list[int]:
|
|
8
|
+
"""
|
|
9
|
+
解析纯 hex 字符串为 list[int]。
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
text: 十六进制文本,支持空格分隔 (如 "AA BB CC" 或 "AABBCC")。
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
list[int]: 解析后的字节列表。
|
|
16
|
+
|
|
17
|
+
Raises:
|
|
18
|
+
ValueError: 不合法的十六进制数据时抛出。
|
|
19
|
+
|
|
20
|
+
Examples:
|
|
21
|
+
>>> PARSE_HEX("AA BB CC")
|
|
22
|
+
[0xAA, 0xBB, 0xCC]
|
|
23
|
+
>>> PARSE_HEX("0011")
|
|
24
|
+
[0x00, 0x11]
|
|
25
|
+
"""
|
|
26
|
+
# 去空格得到纯 hex
|
|
27
|
+
clean_hex = text.replace(" ", "").upper()
|
|
28
|
+
|
|
29
|
+
# 简单的合法性检查
|
|
30
|
+
if clean_hex:
|
|
31
|
+
try:
|
|
32
|
+
return list(bytes.fromhex(clean_hex))
|
|
33
|
+
except ValueError:
|
|
34
|
+
raise ValueError(f"不合法的十六进制数据: {clean_hex}")
|
|
35
|
+
return []
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def PARSE_HEX_BITS(text: str, last_bits: int | None = None) -> tuple[list[int], int]:
|
|
39
|
+
"""
|
|
40
|
+
解析 hex 字符串为 tuple[list[int], int],支持 Verilog 位标记。
|
|
41
|
+
|
|
42
|
+
支持格式:
|
|
43
|
+
- 纯 hex: "AA BB CC" → ([0xAA, 0xBB, 0xCC], 0)
|
|
44
|
+
- Verilog 标记: "AA 4'hBB" → ([0xAA, 0x0B], 4)
|
|
45
|
+
- 混合: "AA BB 7'h03" → ([0xAA, 0xBB, 0x03], 7)
|
|
46
|
+
- 手动指定: "AA BB", last_bits=4 → ([0xAA, 0xBB], 4)
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
text: 十六进制文本,支持空格分隔和 N'hXX 位标记。
|
|
50
|
+
last_bits: 可选的最后字节有效位数,与 Verilog 标记冲突时报错。
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
tuple[list[int], int]: (字节列表, 最后字节有效位数)。
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
ValueError: 不合法的十六进制数据、位标记冲突或多标记时报错。
|
|
57
|
+
|
|
58
|
+
Examples:
|
|
59
|
+
>>> PARSE_HEX_BITS("AA BB CC")
|
|
60
|
+
([0xAA, 0xBB, 0xCC], 0)
|
|
61
|
+
>>> PARSE_HEX_BITS("AA 4'hBB")
|
|
62
|
+
([0xAA, 0x0B], 4)
|
|
63
|
+
>>> PARSE_HEX_BITS("0011", last_bits=4)
|
|
64
|
+
([0x00, 0x01], 4)
|
|
65
|
+
>>> PARSE_HEX_BITS("AA 4'hBB", last_bits=4)
|
|
66
|
+
ValueError: 冲突
|
|
67
|
+
"""
|
|
68
|
+
# 冲突检测:只允许一个 Verilog 标记
|
|
69
|
+
matches = list(BIT_PATTERN.finditer(text))
|
|
70
|
+
if len(matches) > 1:
|
|
71
|
+
raise ValueError("只允许一个 Verilog 位标记")
|
|
72
|
+
if matches and last_bits is not None:
|
|
73
|
+
raise ValueError("last_bits 参数与 Verilog 位标记冲突")
|
|
74
|
+
|
|
75
|
+
bits = last_bits if last_bits is not None else 0
|
|
76
|
+
|
|
77
|
+
# 处理 Verilog 位标记
|
|
78
|
+
if matches:
|
|
79
|
+
m = matches[0]
|
|
80
|
+
n_bits = int(m.group(1))
|
|
81
|
+
bits = n_bits
|
|
82
|
+
# 按 bits 截断: 4'hCA → 0x0A
|
|
83
|
+
val = int(m.group(2), 16) & ((1 << n_bits) - 1)
|
|
84
|
+
hex_val = format(val, 'X')
|
|
85
|
+
if len(hex_val) % 2 != 0:
|
|
86
|
+
hex_val = '0' + hex_val
|
|
87
|
+
text = text[:m.start()] + hex_val + text[m.end():]
|
|
88
|
+
|
|
89
|
+
# 去空格得到纯 hex
|
|
90
|
+
clean_hex = text.replace(" ", "").upper()
|
|
91
|
+
|
|
92
|
+
# 解析 hex 数据
|
|
93
|
+
if clean_hex:
|
|
94
|
+
try:
|
|
95
|
+
data = list(bytes.fromhex(clean_hex))
|
|
96
|
+
except ValueError:
|
|
97
|
+
raise ValueError(f"不合法的十六进制数据: {clean_hex}")
|
|
98
|
+
else:
|
|
99
|
+
data = []
|
|
100
|
+
|
|
101
|
+
# last_bits 截断最后一个字节
|
|
102
|
+
if bits and data:
|
|
103
|
+
data[-1] = data[-1] & ((1 << bits) - 1)
|
|
104
|
+
|
|
105
|
+
return data, bits
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def FORMAT_HEX(data: int | bytes | list[int] | str, last_bits: int = 0) -> str:
|
|
109
|
+
"""
|
|
110
|
+
格式化数据为带位标记的可视化格式。
|
|
111
|
+
|
|
112
|
+
支持输入类型: int, bytes, list[int], str (纯 hex)。
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
data: 待格式化的数据。
|
|
116
|
+
last_bits: 最后一个字节的有效位数 (1-7),0 表示整字节。
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
str: 格式化后的字符串。
|
|
120
|
+
|
|
121
|
+
Raises:
|
|
122
|
+
TypeError: 不支持的输入类型时抛出。
|
|
123
|
+
|
|
124
|
+
Examples:
|
|
125
|
+
>>> FORMAT_HEX(0xAABB03, 7)
|
|
126
|
+
"AA BB 7'h03"
|
|
127
|
+
>>> FORMAT_HEX([0xAA, 0xBB, 0x03])
|
|
128
|
+
'AA BB 03'
|
|
129
|
+
>>> FORMAT_HEX("AABB", 4)
|
|
130
|
+
"AA 4'hBB"
|
|
131
|
+
"""
|
|
132
|
+
# 1. 统一转换为纯十六进制字符串
|
|
133
|
+
if isinstance(data, int):
|
|
134
|
+
hex_str = data.to_bytes(max(1, (data.bit_length() + 7) // 8)).hex().upper()
|
|
135
|
+
elif isinstance(data, bytes):
|
|
136
|
+
hex_str = data.hex().upper()
|
|
137
|
+
elif isinstance(data, list):
|
|
138
|
+
hex_str = bytes(data).hex().upper()
|
|
139
|
+
elif isinstance(data, str):
|
|
140
|
+
hex_str = data.replace(" ", "").upper()
|
|
141
|
+
else:
|
|
142
|
+
raise TypeError(f"不支持的输入类型: {type(data)}")
|
|
143
|
+
|
|
144
|
+
if not hex_str:
|
|
145
|
+
return ""
|
|
146
|
+
|
|
147
|
+
# 2. 如果是整字节 (无 last_bits)
|
|
148
|
+
if last_bits == 0:
|
|
149
|
+
return ' '.join(hex_str[i:i+2] for i in range(0, len(hex_str), 2))
|
|
150
|
+
|
|
151
|
+
# 3. 有 last_bits,处理最后一个字节
|
|
152
|
+
if len(hex_str) <= 2:
|
|
153
|
+
return f"{last_bits}'h{hex_str}"
|
|
154
|
+
|
|
155
|
+
prefix = hex_str[:-2]
|
|
156
|
+
last_byte = hex_str[-2:]
|
|
157
|
+
formatted_prefix = ' '.join(prefix[i:i+2] for i in range(0, len(prefix), 2))
|
|
158
|
+
return f"{formatted_prefix} {last_bits}'h{last_byte}"
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
if __name__ == "__main__":
|
|
162
|
+
# --- 测试例程 ---
|
|
163
|
+
print("=== Hex 工具测试 ===")
|
|
164
|
+
|
|
165
|
+
# 测试用例: (data, last_bits, 类型名)
|
|
166
|
+
test_cases = [
|
|
167
|
+
("AABB03", 7, "str"),
|
|
168
|
+
(0xAABB03, 7, "int"),
|
|
169
|
+
(b'\xAA\xBB\x03', 7, "bytes"),
|
|
170
|
+
([0xAA, 0xBB, 0x03], 7, "list"),
|
|
171
|
+
("FF", 4, "str"),
|
|
172
|
+
(0xFF, 4, "int"),
|
|
173
|
+
("AABBCC", 0, "str"),
|
|
174
|
+
(0xAABBCC, 0, "int"),
|
|
175
|
+
(b'\xAA\xBB\xCC', 0, "bytes"),
|
|
176
|
+
]
|
|
177
|
+
|
|
178
|
+
for data, b, typename in test_cases:
|
|
179
|
+
formatted = FORMAT_HEX(data, b)
|
|
180
|
+
print(f"输入: ({data}, {b}, {typename}) -> 格式化: {formatted}")
|
|
181
|
+
|
|
182
|
+
# 反向解析验证
|
|
183
|
+
if b:
|
|
184
|
+
parsed, bits = PARSE_HEX_BITS(formatted)
|
|
185
|
+
print(f"解析: {formatted} -> Hex: {parsed}, Bits: {bits}")
|
|
186
|
+
else:
|
|
187
|
+
parsed = PARSE_HEX(formatted)
|
|
188
|
+
print(f"解析: {formatted} -> Hex: {parsed}")
|
|
189
|
+
print("-" * 20)
|
|
@@ -28,14 +28,14 @@ class _NFCState:
|
|
|
28
28
|
self._reader = CardReaderRegistry.create(reader_type, transport="serial", port=port)
|
|
29
29
|
self._reader.open()
|
|
30
30
|
self._connected = True
|
|
31
|
-
|
|
31
|
+
_trace.log(f"Connected to {reader_type.upper()} on port {port}", layer="app")
|
|
32
32
|
|
|
33
33
|
def disconnect(self):
|
|
34
34
|
if self._connected and self._reader:
|
|
35
35
|
self._reader.close()
|
|
36
36
|
self._connected = False
|
|
37
37
|
self._reader = None
|
|
38
|
-
|
|
38
|
+
_trace.log("Disconnected", layer="app")
|
|
39
39
|
|
|
40
40
|
def ensure_connected(self):
|
|
41
41
|
ASSERT_IS_NOT_NONE(self._reader, msg="NFC driver not connected. Call connect() first.")
|
|
@@ -125,9 +125,9 @@ def active(low_layer: bool = False, ignore_error: bool = False, reqa_cmd: int =
|
|
|
125
125
|
if not ignore_error:
|
|
126
126
|
ASSERT_EQUAL(expected_bcc, data[4], msg=f"CL{cl} BCC 校验失败: data={FORMAT_HEX(data)}")
|
|
127
127
|
elif data[4] != expected_bcc:
|
|
128
|
-
|
|
128
|
+
_trace.log(f"Warning: CL{cl} BCC 校验失败", layer="warning")
|
|
129
129
|
|
|
130
|
-
|
|
130
|
+
_trace.log(f"cl{cl}.uid: {FORMAT_HEX(data)}", layer="app")
|
|
131
131
|
has_next = (data[0] == 0x88)
|
|
132
132
|
uid_to_select = data[0:5]
|
|
133
133
|
sak_res = select(cl_level=cl, uid=uid_to_select)
|
|
@@ -137,7 +137,7 @@ def active(low_layer: bool = False, ignore_error: bool = False, reqa_cmd: int =
|
|
|
137
137
|
if not ignore_error:
|
|
138
138
|
ASSERT_IS_NOT_NONE(sak_res, msg=f"CL{cl} SAK 选择失败")
|
|
139
139
|
else:
|
|
140
|
-
|
|
140
|
+
_trace.log(f"Warning: CL{cl} SAK 选择失败", layer="warning")
|
|
141
141
|
|
|
142
142
|
if has_next:
|
|
143
143
|
full_uid.extend(data[1:4])
|
|
@@ -1,19 +1,66 @@
|
|
|
1
1
|
"""
|
|
2
2
|
NFC Script Trace 模块
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
统一日志 API,包装 nfctester.trace 的 6 层日志系统。
|
|
5
|
+
层: driver, debug, protocol, warning, error, app
|
|
6
|
+
|
|
7
|
+
支持两种控制方式:
|
|
8
|
+
1. 属性控制: trace.driver = True, trace.level = "warning"
|
|
9
|
+
2. 方法控制: trace.set_layer("driver", True), trace.set_level("warning")
|
|
5
10
|
"""
|
|
6
11
|
|
|
7
12
|
from typing import Callable
|
|
8
13
|
from nfctester.trace import TraceEvent, trace as _trace
|
|
9
14
|
|
|
10
15
|
|
|
16
|
+
def log(msg: str, layer: str = "app"):
|
|
17
|
+
"""通用文本日志,指定层"""
|
|
18
|
+
_trace.log(msg, layer)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def app(msg: str):
|
|
22
|
+
"""输出到 app 层 (level=50)"""
|
|
23
|
+
_trace.app(msg)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def debug(msg: str):
|
|
27
|
+
"""输出到 debug 层 (level=10)"""
|
|
28
|
+
_trace.debug(msg)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def warning(msg: str):
|
|
32
|
+
"""输出到 warning 层 (level=30)"""
|
|
33
|
+
_trace.warning(msg)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def error(msg: str):
|
|
37
|
+
"""输出到 error 层 (level=40)"""
|
|
38
|
+
_trace.error(msg)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def info(msg: str):
|
|
42
|
+
"""输出 INFO 级别日志 (无层标签)"""
|
|
43
|
+
_trace.info(msg)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# --- 属性控制 ---
|
|
47
|
+
|
|
48
|
+
def __setattr__(name, value):
|
|
49
|
+
"""属性控制: trace.driver = True"""
|
|
50
|
+
setattr(_trace, name, value)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def __getattr__(name):
|
|
54
|
+
"""属性获取: trace.driver"""
|
|
55
|
+
return getattr(_trace, name)
|
|
56
|
+
|
|
57
|
+
|
|
11
58
|
def set_layer(layer: str, enable: bool = True):
|
|
12
59
|
"""
|
|
13
60
|
开启或关闭追踪层。
|
|
14
61
|
|
|
15
62
|
Args:
|
|
16
|
-
layer: 层级名称 ("
|
|
63
|
+
layer: 层级名称 ("driver", "debug", "protocol", "warning", "error", "app")
|
|
17
64
|
enable: True 开启,False 关闭
|
|
18
65
|
"""
|
|
19
66
|
_trace.set_layer(layer, enable)
|
|
@@ -21,10 +68,10 @@ def set_layer(layer: str, enable: bool = True):
|
|
|
21
68
|
|
|
22
69
|
def set_level(level: str):
|
|
23
70
|
"""
|
|
24
|
-
|
|
71
|
+
设置最低日志级别。
|
|
25
72
|
|
|
26
73
|
Args:
|
|
27
|
-
level: 级别名称 ("
|
|
74
|
+
level: 级别名称 ("trace", "debug", "warning", "error", "app")
|
|
28
75
|
"""
|
|
29
76
|
_trace.set_level(level)
|
|
30
77
|
|
|
@@ -39,31 +86,6 @@ def set_parse(level: int = 1):
|
|
|
39
86
|
_trace.set_parse(level)
|
|
40
87
|
|
|
41
88
|
|
|
42
|
-
def info(msg: str):
|
|
43
|
-
"""输出 INFO 级别日志"""
|
|
44
|
-
_trace.info(msg)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def error(msg: str):
|
|
48
|
-
"""输出 ERROR 级别日志"""
|
|
49
|
-
_trace.error(msg)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def warning(msg: str):
|
|
53
|
-
"""输出 WARNING 级别日志"""
|
|
54
|
-
_trace.warning(msg)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def success(msg: str):
|
|
58
|
-
"""输出 SUCCESS 级别日志"""
|
|
59
|
-
_trace.success(msg)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def debug(msg: str):
|
|
63
|
-
"""输出 DEBUG 级别日志"""
|
|
64
|
-
_trace.debug(msg)
|
|
65
|
-
|
|
66
|
-
|
|
67
89
|
def add_sink(fn: Callable[[TraceEvent], None]):
|
|
68
90
|
"""
|
|
69
91
|
注册结构化 trace 事件回调。
|