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.
Files changed (31) hide show
  1. {nfcscript-0.0.13 → nfcscript-0.0.15}/PKG-INFO +42 -21
  2. {nfcscript-0.0.13 → nfcscript-0.0.15}/README.md +41 -20
  3. {nfcscript-0.0.13 → nfcscript-0.0.15}/pyproject.toml +2 -2
  4. {nfcscript-0.0.13 → nfcscript-0.0.15}/src/nfc/cli.py +19 -40
  5. nfcscript-0.0.15/src/nfc/hex_util.py +189 -0
  6. {nfcscript-0.0.13 → nfcscript-0.0.15}/src/nfc/reader.py +5 -5
  7. {nfcscript-0.0.13 → nfcscript-0.0.15}/src/nfc/trace.py +51 -29
  8. {nfcscript-0.0.13 → nfcscript-0.0.15}/src/nfcscript.egg-info/PKG-INFO +42 -21
  9. nfcscript-0.0.15/tests/test_hex_util.py +245 -0
  10. nfcscript-0.0.13/src/nfc/hex_util.py +0 -131
  11. nfcscript-0.0.13/tests/test_hex_util.py +0 -168
  12. {nfcscript-0.0.13 → nfcscript-0.0.15}/setup.cfg +0 -0
  13. {nfcscript-0.0.13 → nfcscript-0.0.15}/src/nfc/__init__.py +0 -0
  14. {nfcscript-0.0.13 → nfcscript-0.0.15}/src/nfc/assertions.py +0 -0
  15. {nfcscript-0.0.13 → nfcscript-0.0.15}/src/nfc/bits.py +0 -0
  16. {nfcscript-0.0.13 → nfcscript-0.0.15}/src/nfc/checksum.py +0 -0
  17. {nfcscript-0.0.13 → nfcscript-0.0.15}/src/nfc/delay.py +0 -0
  18. {nfcscript-0.0.13 → nfcscript-0.0.15}/src/nfc/nfc_cli.py +0 -0
  19. {nfcscript-0.0.13 → nfcscript-0.0.15}/src/nfcscript.egg-info/SOURCES.txt +0 -0
  20. {nfcscript-0.0.13 → nfcscript-0.0.15}/src/nfcscript.egg-info/dependency_links.txt +0 -0
  21. {nfcscript-0.0.13 → nfcscript-0.0.15}/src/nfcscript.egg-info/entry_points.txt +0 -0
  22. {nfcscript-0.0.13 → nfcscript-0.0.15}/src/nfcscript.egg-info/requires.txt +0 -0
  23. {nfcscript-0.0.13 → nfcscript-0.0.15}/src/nfcscript.egg-info/top_level.txt +0 -0
  24. {nfcscript-0.0.13 → nfcscript-0.0.15}/tests/test_assertions.py +0 -0
  25. {nfcscript-0.0.13 → nfcscript-0.0.15}/tests/test_bits.py +0 -0
  26. {nfcscript-0.0.13 → nfcscript-0.0.15}/tests/test_checksum.py +0 -0
  27. {nfcscript-0.0.13 → nfcscript-0.0.15}/tests/test_cli.py +0 -0
  28. {nfcscript-0.0.13 → nfcscript-0.0.15}/tests/test_cli_collect.py +0 -0
  29. {nfcscript-0.0.13 → nfcscript-0.0.15}/tests/test_delay.py +0 -0
  30. {nfcscript-0.0.13 → nfcscript-0.0.15}/tests/test_reader.py +0 -0
  31. {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.13
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-driver --trace-protocol
39
+ nfcscript test_card.py --trace=protocol,debug
40
40
 
41
41
  # 设置日志级别
42
- nfcscript test_card.py --trace-level DEBUG
42
+ nfcscript test_card.py --level=trace
43
43
 
44
44
  # 组合使用
45
- nfcscript test_card.py -p COM21 -r pn532 --trace-driver --trace-protocol --trace-level DEBUG
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.set_layer("DRIVER", True)
124
- trace.set_layer("PROTOCOL", True)
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
- print(f"UID: {' '.join(f'{b:02X}' for b in card_info.uid)}")
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
- print(card_info.uid)
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.set_layer(layer, enable)` | 开启/关闭追踪层 |
187
- | `trace.set_level(level)` | 设置日志级别 |
188
- | `trace.set_parse(level)` | 设置解析级别 (0=关闭, 1=hex+摘要) |
189
- | `trace.add_sink(fn)` | 注册结构化 trace 事件回调 |
190
- | `trace.remove_sink(fn)` | 移除已注册的回调 |
191
- | `trace.info(msg)` / `trace.error(msg)` / `trace.warning(msg)` / `trace.success(msg)` / `trace.debug(msg)` | 输出日志 |
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)` | 解析 hex 字符串 |
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
- | `NFC_TRACE_LEVEL` | 日志级别 | `INFO` |
238
- | `NFC_TRACE_DRIVER` | 开启 DRIVER 层追踪 | `false` |
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=DEBUG
255
- NFC_TRACE_DRIVER=true
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-driver --trace-protocol
29
+ nfcscript test_card.py --trace=protocol,debug
30
30
 
31
31
  # 设置日志级别
32
- nfcscript test_card.py --trace-level DEBUG
32
+ nfcscript test_card.py --level=trace
33
33
 
34
34
  # 组合使用
35
- nfcscript test_card.py -p COM21 -r pn532 --trace-driver --trace-protocol --trace-level DEBUG
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.set_layer("DRIVER", True)
114
- trace.set_layer("PROTOCOL", True)
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
- print(f"UID: {' '.join(f'{b:02X}' for b in card_info.uid)}")
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
- print(card_info.uid)
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.set_layer(layer, enable)` | 开启/关闭追踪层 |
177
- | `trace.set_level(level)` | 设置日志级别 |
178
- | `trace.set_parse(level)` | 设置解析级别 (0=关闭, 1=hex+摘要) |
179
- | `trace.add_sink(fn)` | 注册结构化 trace 事件回调 |
180
- | `trace.remove_sink(fn)` | 移除已注册的回调 |
181
- | `trace.info(msg)` / `trace.error(msg)` / `trace.warning(msg)` / `trace.success(msg)` / `trace.debug(msg)` | 输出日志 |
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)` | 解析 hex 字符串 |
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
- | `NFC_TRACE_LEVEL` | 日志级别 | `INFO` |
228
- | `NFC_TRACE_DRIVER` | 开启 DRIVER 层追踪 | `false` |
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=DEBUG
245
- NFC_TRACE_DRIVER=true
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.13"
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.13"
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-driver", action="store_true", help="开启 DRIVER 层追踪")
59
- parser.add_argument("--trace-protocol", action="store_true", help="开启 PROTOCOL 层追踪")
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
- trace_level = os.environ.get("NFC_TRACE_LEVEL", "INFO")
85
- trace_driver = os.environ.get("NFC_TRACE_DRIVER", "").lower() in ("1", "true", "yes")
86
- trace_protocol = os.environ.get("NFC_TRACE_PROTOCOL", "").lower() in ("1", "true", "yes")
87
- trace_parse = os.environ.get("NFC_TRACE_PARSE", "1")
88
- trace_width = os.environ.get("NFC_TRACE_WIDTH")
89
-
90
- if args.trace_level != "INFO":
91
- trace_level = args.trace_level
92
- if args.trace_driver:
93
- trace_driver = True
94
- if args.trace_protocol:
95
- trace_protocol = True
96
- if args.trace_parse is not None:
97
- trace_parse = str(args.trace_parse)
98
-
99
- trace.set_level(trace_level)
100
- trace.set_layer("DRIVER", trace_driver)
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
- print(f"Connected to {reader_type.upper()} on port {port}")
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
- print("Disconnected")
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
- print(f"Warning: CL{cl} BCC 校验失败")
128
+ _trace.log(f"Warning: CL{cl} BCC 校验失败", layer="warning")
129
129
 
130
- print(f"cl{cl}.uid: {FORMAT_HEX(data)}")
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
- print(f"Warning: CL{cl} SAK 选择失败")
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
- 提供日志追踪控制,包装 nfctester.trace 的功能。
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: 层级名称 ("DRIVER" 或 "PROTOCOL")
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: 级别名称 ("INFO", "DEBUG", "ERROR" 等)
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 事件回调。