nfcscript 0.0.15__tar.gz → 0.0.17__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 (29) hide show
  1. {nfcscript-0.0.15 → nfcscript-0.0.17}/PKG-INFO +17 -17
  2. {nfcscript-0.0.15 → nfcscript-0.0.17}/README.md +16 -16
  3. {nfcscript-0.0.15 → nfcscript-0.0.17}/pyproject.toml +2 -5
  4. {nfcscript-0.0.15 → nfcscript-0.0.17}/src/nfc/cli.py +11 -11
  5. {nfcscript-0.0.15 → nfcscript-0.0.17}/src/nfc/nfc_cli.py +3 -3
  6. {nfcscript-0.0.15 → nfcscript-0.0.17}/src/nfc/reader.py +197 -64
  7. {nfcscript-0.0.15 → nfcscript-0.0.17}/src/nfc/trace.py +1 -24
  8. {nfcscript-0.0.15 → nfcscript-0.0.17}/src/nfcscript.egg-info/PKG-INFO +17 -17
  9. {nfcscript-0.0.15 → nfcscript-0.0.17}/tests/test_cli.py +2 -42
  10. {nfcscript-0.0.15 → nfcscript-0.0.17}/tests/test_reader.py +139 -83
  11. {nfcscript-0.0.15 → nfcscript-0.0.17}/tests/test_trace.py +10 -31
  12. {nfcscript-0.0.15 → nfcscript-0.0.17}/setup.cfg +0 -0
  13. {nfcscript-0.0.15 → nfcscript-0.0.17}/src/nfc/__init__.py +0 -0
  14. {nfcscript-0.0.15 → nfcscript-0.0.17}/src/nfc/assertions.py +0 -0
  15. {nfcscript-0.0.15 → nfcscript-0.0.17}/src/nfc/bits.py +0 -0
  16. {nfcscript-0.0.15 → nfcscript-0.0.17}/src/nfc/checksum.py +0 -0
  17. {nfcscript-0.0.15 → nfcscript-0.0.17}/src/nfc/delay.py +0 -0
  18. {nfcscript-0.0.15 → nfcscript-0.0.17}/src/nfc/hex_util.py +0 -0
  19. {nfcscript-0.0.15 → nfcscript-0.0.17}/src/nfcscript.egg-info/SOURCES.txt +0 -0
  20. {nfcscript-0.0.15 → nfcscript-0.0.17}/src/nfcscript.egg-info/dependency_links.txt +0 -0
  21. {nfcscript-0.0.15 → nfcscript-0.0.17}/src/nfcscript.egg-info/entry_points.txt +0 -0
  22. {nfcscript-0.0.15 → nfcscript-0.0.17}/src/nfcscript.egg-info/requires.txt +0 -0
  23. {nfcscript-0.0.15 → nfcscript-0.0.17}/src/nfcscript.egg-info/top_level.txt +0 -0
  24. {nfcscript-0.0.15 → nfcscript-0.0.17}/tests/test_assertions.py +0 -0
  25. {nfcscript-0.0.15 → nfcscript-0.0.17}/tests/test_bits.py +0 -0
  26. {nfcscript-0.0.15 → nfcscript-0.0.17}/tests/test_checksum.py +0 -0
  27. {nfcscript-0.0.15 → nfcscript-0.0.17}/tests/test_cli_collect.py +0 -0
  28. {nfcscript-0.0.15 → nfcscript-0.0.17}/tests/test_delay.py +0 -0
  29. {nfcscript-0.0.15 → nfcscript-0.0.17}/tests/test_hex_util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nfcscript
3
- Version: 0.0.15
3
+ Version: 0.0.17
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=protocol,debug
39
+ nfcscript test_card.py --trace-layer=protocol,debug
40
40
 
41
41
  # 设置日志级别
42
- nfcscript test_card.py --level=trace
42
+ nfcscript test_card.py --trace-level=driver
43
43
 
44
44
  # 组合使用
45
- nfcscript test_card.py -p COM21 -r pn532 --trace=protocol,debug --level=trace
45
+ nfcscript test_card.py -p COM21 -r pn532 --trace-layer=protocol,debug --trace-level=driver
46
46
  ```
47
47
 
48
48
  ### nfc-cli - 交互式命令行
@@ -120,8 +120,8 @@ from nfc import *
120
120
  connect()
121
121
 
122
122
  # 开启追踪
123
- trace.driver = True
124
- trace.protocol = True
123
+ trace.filter.driver = True
124
+ trace.filter.protocol = True
125
125
 
126
126
  # 寻卡(自动切换协议解析器)
127
127
  card_info = active()
@@ -195,11 +195,11 @@ with session() as reader:
195
195
 
196
196
  | 属性 | 说明 |
197
197
  |------|------|
198
- | `trace.driver = True` | 启用 driver 层 |
199
- | `trace.protocol = True` | 启用 protocol 层 |
200
- | `trace.debug = False` | 禁用 debug 层 |
201
- | `trace.level = "warning"` | 设置最低日志级别 |
202
- | `trace.level = 30` | 或用数字 |
198
+ | `trace.filter.driver = True` | 启用 driver 层 |
199
+ | `trace.filter.protocol = True` | 启用 protocol 层 |
200
+ | `trace.filter.debug = False` | 禁用 debug 层 |
201
+ | `trace.filter.level = "warning"` | 设置最低日志级别 |
202
+ | `trace.filter.level = 30` | 或用数字 |
203
203
 
204
204
  ```python
205
205
  # 简洁写法
@@ -207,9 +207,9 @@ trace.app("UID: AA BB CC DD")
207
207
  trace.debug("[auth] Rt: 80 81")
208
208
 
209
209
  # 属性控制
210
- trace.driver = True
211
- trace.protocol = True
212
- trace.level = "debug"
210
+ trace.filter.driver = True
211
+ trace.filter.protocol = True
212
+ trace.filter.level = "debug"
213
213
  ```
214
214
 
215
215
  ### assertions.py
@@ -257,7 +257,7 @@ trace.level = "debug"
257
257
  |------|------|--------|
258
258
  | `NFC_PORT` | 串口号 | - |
259
259
  | `NFC_READER` | 读卡器类型 | - |
260
- | `NFC_TRACE` | 启用的层 (逗号分隔) | - |
260
+ | `NFC_TRACE_LAYER` | 启用的层 (逗号分隔) | - |
261
261
  | `NFC_TRACE_LEVEL` | 最低日志级别 | `warning` |
262
262
  | `NFC_CARD_PATH` | 外部卡片模块搜索路径 (分号分隔) | - |
263
263
 
@@ -273,8 +273,8 @@ trace.level = "debug"
273
273
  ```
274
274
  NFC_PORT=COM20
275
275
  NFC_READER=pn532
276
- NFC_TRACE_LEVEL=warning
277
- NFC_TRACE=protocol,debug
276
+ NFC_TRACE_LAYER=protocol,warning,error,app
277
+ NFC_TRACE_LEVEL=protocol
278
278
  ```
279
279
 
280
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=protocol,debug
29
+ nfcscript test_card.py --trace-layer=protocol,debug
30
30
 
31
31
  # 设置日志级别
32
- nfcscript test_card.py --level=trace
32
+ nfcscript test_card.py --trace-level=driver
33
33
 
34
34
  # 组合使用
35
- nfcscript test_card.py -p COM21 -r pn532 --trace=protocol,debug --level=trace
35
+ nfcscript test_card.py -p COM21 -r pn532 --trace-layer=protocol,debug --trace-level=driver
36
36
  ```
37
37
 
38
38
  ### nfc-cli - 交互式命令行
@@ -110,8 +110,8 @@ from nfc import *
110
110
  connect()
111
111
 
112
112
  # 开启追踪
113
- trace.driver = True
114
- trace.protocol = True
113
+ trace.filter.driver = True
114
+ trace.filter.protocol = True
115
115
 
116
116
  # 寻卡(自动切换协议解析器)
117
117
  card_info = active()
@@ -185,11 +185,11 @@ with session() as reader:
185
185
 
186
186
  | 属性 | 说明 |
187
187
  |------|------|
188
- | `trace.driver = True` | 启用 driver 层 |
189
- | `trace.protocol = True` | 启用 protocol 层 |
190
- | `trace.debug = False` | 禁用 debug 层 |
191
- | `trace.level = "warning"` | 设置最低日志级别 |
192
- | `trace.level = 30` | 或用数字 |
188
+ | `trace.filter.driver = True` | 启用 driver 层 |
189
+ | `trace.filter.protocol = True` | 启用 protocol 层 |
190
+ | `trace.filter.debug = False` | 禁用 debug 层 |
191
+ | `trace.filter.level = "warning"` | 设置最低日志级别 |
192
+ | `trace.filter.level = 30` | 或用数字 |
193
193
 
194
194
  ```python
195
195
  # 简洁写法
@@ -197,9 +197,9 @@ trace.app("UID: AA BB CC DD")
197
197
  trace.debug("[auth] Rt: 80 81")
198
198
 
199
199
  # 属性控制
200
- trace.driver = True
201
- trace.protocol = True
202
- trace.level = "debug"
200
+ trace.filter.driver = True
201
+ trace.filter.protocol = True
202
+ trace.filter.level = "debug"
203
203
  ```
204
204
 
205
205
  ### assertions.py
@@ -247,7 +247,7 @@ trace.level = "debug"
247
247
  |------|------|--------|
248
248
  | `NFC_PORT` | 串口号 | - |
249
249
  | `NFC_READER` | 读卡器类型 | - |
250
- | `NFC_TRACE` | 启用的层 (逗号分隔) | - |
250
+ | `NFC_TRACE_LAYER` | 启用的层 (逗号分隔) | - |
251
251
  | `NFC_TRACE_LEVEL` | 最低日志级别 | `warning` |
252
252
  | `NFC_CARD_PATH` | 外部卡片模块搜索路径 (分号分隔) | - |
253
253
 
@@ -263,8 +263,8 @@ trace.level = "debug"
263
263
  ```
264
264
  NFC_PORT=COM20
265
265
  NFC_READER=pn532
266
- NFC_TRACE_LEVEL=warning
267
- NFC_TRACE=protocol,debug
266
+ NFC_TRACE_LAYER=protocol,warning,error,app
267
+ NFC_TRACE_LEVEL=protocol
268
268
  ```
269
269
 
270
270
  ## AI 编写脚本
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nfcscript"
3
- version = "0.0.15"
3
+ version = "0.0.17"
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"
@@ -24,9 +24,6 @@ testpaths = ["tests"]
24
24
  [tool.uv]
25
25
  package = true
26
26
 
27
- [tool.uv.sources]
28
- nfctester = { workspace = true }
29
-
30
27
  [dependency-groups]
31
28
  dev = [
32
29
  "pytest",
@@ -34,7 +31,7 @@ dev = [
34
31
  ]
35
32
 
36
33
  [tool.bumpversion]
37
- current_version = "0.0.15"
34
+ current_version = "0.0.17"
38
35
  commit = true
39
36
  tag = true
40
37
  allow_dirty = true
@@ -41,8 +41,8 @@ def main():
41
41
  parser.add_argument("script", help="脚本文件路径")
42
42
  parser.add_argument("-p", "--port", default=None, help="串口号 (默认: NFC_PORT 环境变量或 COM20)")
43
43
  parser.add_argument("-r", "--reader", default=None, help="读卡器类型 (默认: NFC_READER 环境变量或 pn532)")
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)")
44
+ parser.add_argument("--trace-layer", default=None, help="启用的层 (逗号分隔: driver,debug,protocol,warning,error,app,all)")
45
+ parser.add_argument("--trace-level", default=None, help="最低日志级别 (driver/debug/protocol/warning/error/app, 默认: warning)")
46
46
 
47
47
  args, extra = parser.parse_known_args()
48
48
 
@@ -64,23 +64,23 @@ def main():
64
64
 
65
65
  from nfc import trace
66
66
 
67
- # 解析 --trace: 启用指定层
68
- trace_env = os.environ.get("NFC_TRACE", "")
69
- trace_arg = args.trace or trace_env
67
+ # 解析 --trace-layer: 启用指定层 (调试用,默认已开启 protocol/warning/error/app)
68
+ trace_env = os.environ.get("NFC_TRACE_LAYER", "")
69
+ trace_arg = args.trace_layer or trace_env
70
70
  if trace_arg:
71
- trace.set_level("trace") # 允许所有层可见
71
+ trace.filter.level = "driver" # 允许所有层可见
72
72
  for name in trace_arg.split(","):
73
73
  name = name.strip().lower()
74
74
  if name == "all":
75
75
  for layer in ["driver", "debug", "protocol", "warning", "error", "app"]:
76
- trace.set_layer(layer, True)
76
+ setattr(trace.filter, layer, True)
77
77
  elif name:
78
- trace.set_layer(name, True)
78
+ setattr(trace.filter, name, True)
79
79
 
80
- # 解析 --level: 最低级别过滤
80
+ # 解析 --trace-level: 最低级别过滤
81
81
  level_env = os.environ.get("NFC_TRACE_LEVEL", "warning")
82
- level_arg = args.level or level_env
83
- trace.set_level(level_arg)
82
+ level_arg = args.trace_level or level_env
83
+ trace.filter.level = level_arg
84
84
 
85
85
  run_script(args.script)
86
86
 
@@ -172,13 +172,13 @@ def main():
172
172
  parser.error("需要指定卡片类型 (-c/--card) 或使用 --list-cards 查看可用类型")
173
173
 
174
174
  try:
175
- connect(port=args.port, reader_type=args.reader)
175
+ reader.connect(port=args.port, reader_type=args.reader)
176
176
  except Exception as e:
177
177
  print(f"连接读卡器失败: {e}")
178
178
  sys.exit(1)
179
179
 
180
180
  try:
181
- card = CardRegistry.create(args.card, reader=get_reader())
181
+ card = CardRegistry.create(args.card, reader=reader.target)
182
182
  methods = _get_methods(card)
183
183
 
184
184
  print(f"已连接卡片: {args.card}")
@@ -220,7 +220,7 @@ def main():
220
220
  print(f"错误: {e}")
221
221
 
222
222
  finally:
223
- close()
223
+ reader.close()
224
224
  print("已断开连接")
225
225
 
226
226
 
@@ -1,5 +1,10 @@
1
+ import atexit
1
2
  import os
3
+ import sys
2
4
  from contextlib import contextmanager
5
+ from typing import Any
6
+
7
+ from dotenv import load_dotenv
3
8
  from nfctester.drivers.card_reader import CardInfo, TransceiveBits
4
9
  from nfctester.registry import CardReaderRegistry
5
10
  from nfctester.trace import trace as _trace
@@ -7,44 +12,104 @@ from .assertions import ASSERT_EQUAL, ASSERT_IS_NOT_NONE, ASSERT_LEN
7
12
  from .checksum import GET_BCC
8
13
  from .hex_util import FORMAT_HEX
9
14
 
15
+ _DOTENV_LOADED = False
16
+
17
+
18
+ def _find_dotenv_all(start_dir: str) -> list[str]:
19
+ """从起始目录逐级向上搜索所有 .env 文件"""
20
+ chain = []
21
+ path = start_dir
22
+ while True:
23
+ env_path = os.path.join(path, ".env")
24
+ if os.path.exists(env_path):
25
+ chain.append(env_path)
26
+ parent = os.path.dirname(path)
27
+ if parent == path:
28
+ break
29
+ path = parent
30
+ chain.reverse()
31
+ return chain
32
+
33
+
34
+ def _load_env_configs() -> None:
35
+ """按需加载当前目录及上级目录的 .env 文件"""
36
+ global _DOTENV_LOADED
37
+ if not _DOTENV_LOADED:
38
+ try:
39
+ cwd = os.getcwd()
40
+ for env_path in _find_dotenv_all(cwd):
41
+ load_dotenv(env_path, override=False)
42
+ except Exception:
43
+ pass
44
+ _DOTENV_LOADED = True
45
+
46
+
47
+ # ─── 轻量读卡器门面单例 ───────────────────────────────────────────────────
10
48
 
11
- class _NFCState:
12
- """单例状态管理,封装读卡器生命周期"""
49
+ class _ReaderFacade:
50
+ """
51
+ 读卡器单例门面对象 (`reader`)。
52
+
53
+ 统一维护读卡器连接状态,提供 `reader.rf = True / False` 属性控制,
54
+ 并将所有属性与方法调用优先路由到当前模块顶层函数,或透传到底层 CardReader 实例。
55
+ """
56
+
57
+ def __init__(self) -> None:
58
+ self._target: Any = None
59
+ self._connected: bool = False
60
+
61
+ @property
62
+ def rf(self) -> bool:
63
+ """获取射频场状态。"""
64
+ return getattr(get_reader(), "rf_field", False)
65
+
66
+ @rf.setter
67
+ def rf(self, value: bool) -> None:
68
+ """设置射频场开关 (True / False)。"""
69
+ get_reader().rf_field = bool(value)
13
70
 
14
- _instance = None
71
+ @property
72
+ def rf_field(self) -> bool:
73
+ """获取射频场状态 (rf 别名)。"""
74
+ return self.rf
15
75
 
16
- def __new__(cls):
17
- if cls._instance is None:
18
- cls._instance = super().__new__(cls)
19
- cls._instance._reader = None
20
- cls._instance._connected = False
21
- return cls._instance
76
+ @rf_field.setter
77
+ def rf_field(self, value: bool) -> None:
78
+ """设置射频场开关 (rf 别名)。"""
79
+ self.rf = value
22
80
 
23
81
  @property
24
- def reader(self):
25
- return self._reader
82
+ def mf_crypto(self) -> bool:
83
+ """获取 Mifare 硬件加密状态。"""
84
+ return getattr(get_reader(), "mf_crypto", False)
26
85
 
27
- def connect(self, port: str = "COM20", reader_type: str = "pn532"):
28
- self._reader = CardReaderRegistry.create(reader_type, transport="serial", port=port)
29
- self._reader.open()
30
- self._connected = True
31
- _trace.log(f"Connected to {reader_type.upper()} on port {port}", layer="app")
86
+ @property
87
+ def target(self) -> Any:
88
+ """获取当前连接的底层 CardReader 实例。"""
89
+ return get_reader()
32
90
 
33
- def disconnect(self):
34
- if self._connected and self._reader:
35
- self._reader.close()
36
- self._connected = False
37
- self._reader = None
38
- _trace.log("Disconnected", layer="app")
91
+ def __getattr__(self, name: str) -> Any:
92
+ # 1. 优先路由到当前模块的顶层函数(如 reader.active -> active)
93
+ mod = sys.modules[__name__]
94
+ if hasattr(mod, name) and not name.startswith("_"):
95
+ return getattr(mod, name)
96
+ # 2. 其次透传给底层 CardReader 驱动实例(如 reader.mf_auth -> _target.mf_auth)
97
+ return getattr(self.target, name)
39
98
 
40
- def ensure_connected(self):
41
- ASSERT_IS_NOT_NONE(self._reader, msg="NFC driver not connected. Call connect() first.")
99
+ def __enter__(self):
100
+ get_reader()
101
+ return self
42
102
 
103
+ def __exit__(self, exc_type, exc_val, exc_tb):
104
+ pass
43
105
 
44
- _state = _NFCState()
45
106
 
107
+ reader = _ReaderFacade()
46
108
 
47
- def connect(port: str = None, reader_type: str = None):
109
+
110
+ # ─── 模块级顶层函数(单一事实来源) ──────────────────────────────────────────
111
+
112
+ def connect(port: str = None, reader_type: str = None) -> None:
48
113
  """
49
114
  连接并初始化读卡器。
50
115
 
@@ -55,6 +120,7 @@ def connect(port: str = None, reader_type: str = None):
55
120
  Raises:
56
121
  ValueError: 未提供 port 或 reader_type 且环境变量未设置。
57
122
  """
123
+ _load_env_configs()
58
124
  if port is None:
59
125
  port = os.environ.get("NFC_PORT")
60
126
  if reader_type is None:
@@ -63,18 +129,53 @@ def connect(port: str = None, reader_type: str = None):
63
129
  raise ValueError("未指定串口,请通过 -p 参数、connect(port=...) 或 NFC_PORT 环境变量配置")
64
130
  if reader_type is None:
65
131
  raise ValueError("未指定读卡器类型,请通过 -r 参数、connect(reader_type=...) 或 NFC_READER 环境变量配置")
66
- _state.connect(port, reader_type)
132
+
133
+ if reader._connected and reader._target is not None:
134
+ disconnect()
135
+
136
+ reader._target = CardReaderRegistry.create(reader_type, transport="serial", port=port)
137
+ reader._target.open()
138
+ reader._connected = True
139
+ _trace.log(f"Connected to {reader_type.upper()} on port {port}", layer="app")
140
+
141
+
142
+ def disconnect() -> None:
143
+ """断开与读卡器的连接并释放资源。"""
144
+ if reader._connected and reader._target is not None:
145
+ try:
146
+ reader._target.rf_field = False
147
+ except Exception:
148
+ pass
149
+ try:
150
+ reader._target.close()
151
+ except Exception:
152
+ pass
153
+ reader._connected = False
154
+ reader._target = None
155
+ _trace.log("Disconnected", layer="app")
156
+
157
+
158
+ def close() -> None:
159
+ """断开与读卡器的连接 (disconnect 别名)。"""
160
+ disconnect()
67
161
 
68
162
 
69
- def get_reader():
163
+ def get_reader() -> Any:
70
164
  """
71
- 获取当前连接的读卡器实例,供 Card 类使用。
165
+ 获取当前连接的读卡器实例,若未连接则尝试从环境变量自动连接。
72
166
 
73
167
  Returns:
74
168
  reader: 当前连接的读卡器实例。
75
169
  """
76
- _state.ensure_connected()
77
- return _state.reader
170
+ if not reader._connected or reader._target is None:
171
+ _load_env_configs()
172
+ port = os.environ.get("NFC_PORT")
173
+ reader_type = os.environ.get("NFC_READER")
174
+ if port and reader_type:
175
+ connect(port, reader_type)
176
+ else:
177
+ ASSERT_IS_NOT_NONE(reader._target, msg="NFC driver not connected. Call connect() first.")
178
+ return reader._target
78
179
 
79
180
 
80
181
  def active(low_layer: bool = False, ignore_error: bool = False, reqa_cmd: int = 0x26) -> CardInfo | None:
@@ -94,11 +195,10 @@ def active(low_layer: bool = False, ignore_error: bool = False, reqa_cmd: int =
94
195
  - 'atq' (list[int]): 卡片的 ATQ (Answer To Request)。
95
196
  - 'sak' (int): 卡片的 SAK (Select Acknowledge)。
96
197
  """
97
- _state.ensure_connected()
98
- reader = _state.reader
198
+ rdr = get_reader()
99
199
 
100
200
  if not low_layer:
101
- return reader.active()
201
+ return rdr.active()
102
202
 
103
203
  # 手动底层寻卡流程
104
204
  # 1. REQA
@@ -109,6 +209,7 @@ def active(low_layer: bool = False, ignore_error: bool = False, reqa_cmd: int =
109
209
 
110
210
  # 2. 抗冲突与选择
111
211
  full_uid = []
212
+ sak = 0
112
213
  for cl in [1, 2, 3]:
113
214
  res = anticoll(cl_level=cl, nvb=0x20)
114
215
  if not res.data:
@@ -153,30 +254,35 @@ def active(low_layer: bool = False, ignore_error: bool = False, reqa_cmd: int =
153
254
  return info
154
255
 
155
256
 
156
- def transceive(data: list[int], tx_crc: bool = True, rx_crc: bool = True) -> list[int]:
257
+ def transceive(data: list[int] | bytes, tx_crc: bool = True, rx_crc: bool = True) -> list[int]:
157
258
  """
158
259
  使用底层帧交互方式发送数据。
159
260
 
160
261
  Args:
161
- data: 要发送的字节列表。
262
+ data: 要发送的字节列表或 bytes。
162
263
  tx_crc: 发送时是否自动添加 CRC。False 则原样发送。
163
264
  rx_crc: 接收时是否自动校验 CRC。False 则原样接收不校验。
164
265
 
165
266
  Returns:
166
267
  list[int]: 接收到的数据字节列表。
167
268
  """
168
- _state.ensure_connected()
169
- reader = _state.reader
170
- res = reader.transceive(data, tx_crc=tx_crc, rx_crc=rx_crc)
269
+ rdr = get_reader()
270
+ raw_list = list(data) if isinstance(data, (bytes, bytearray)) else data
271
+ res = rdr.transceive(raw_list, tx_crc=tx_crc, rx_crc=rx_crc)
171
272
  return res.data if res and res.data else []
172
273
 
173
274
 
174
- def transceive_bits(data: list[int], last_tx_bits: int = 0, tx_crc: bool = True, rx_crc: bool = True) -> TransceiveBits:
275
+ def transceive_bits(
276
+ data: list[int] | bytes,
277
+ last_tx_bits: int = 0,
278
+ tx_crc: bool = True,
279
+ rx_crc: bool = True,
280
+ ) -> TransceiveBits:
175
281
  """
176
282
  使用底层帧交互方式发送数据,并支持对最后一个字节进行位控制。
177
283
 
178
284
  Args:
179
- data: 要发送的字节列表。
285
+ data: 要发送的字节列表或 bytes。
180
286
  last_tx_bits: 最后一个字节实际发送的有效位数 (1-7),0 表示发送完整字节。
181
287
  tx_crc: 发送时是否自动添加 CRC。False 则原样发送。
182
288
  rx_crc: 接收时是否自动校验 CRC。False 则原样接收不校验。
@@ -184,9 +290,9 @@ def transceive_bits(data: list[int], last_tx_bits: int = 0, tx_crc: bool = True,
184
290
  Returns:
185
291
  TransceiveBits: 包含 data 和 bits 属性,失败返回空的 TransceiveBits(data=[], bits=0)。
186
292
  """
187
- _state.ensure_connected()
188
- reader = _state.reader
189
- res = reader.transceive(data, last_tx_bits=last_tx_bits, tx_crc=tx_crc, rx_crc=rx_crc)
293
+ rdr = get_reader()
294
+ raw_list = list(data) if isinstance(data, (bytes, bytearray)) else data
295
+ res = rdr.transceive(raw_list, last_tx_bits=last_tx_bits, tx_crc=tx_crc, rx_crc=rx_crc)
190
296
  return res if res else TransceiveBits(data=[], bits=0)
191
297
 
192
298
 
@@ -273,33 +379,24 @@ def select(cl_level: int, uid: list[int]) -> list[int]:
273
379
  return transceive(cmd, tx_crc=True, rx_crc=True)
274
380
 
275
381
 
276
- def field_on():
382
+ def field_on() -> None:
277
383
  """
278
- 开启 PN532 的 RF 场。
384
+ 开启读卡器的 RF 场。
279
385
 
280
386
  Raises:
281
387
  AssertionError: 未连接读卡器时抛出。
282
388
  """
283
- _state.ensure_connected()
284
- _state.reader.rf_field = True
389
+ get_reader().rf_field = True
285
390
 
286
391
 
287
- def field_off():
392
+ def field_off() -> None:
288
393
  """
289
- 关闭 PN532 的 RF 场。
394
+ 关闭读卡器的 RF 场。
290
395
 
291
396
  Raises:
292
397
  AssertionError: 未连接读卡器时抛出。
293
398
  """
294
- _state.ensure_connected()
295
- _state.reader.rf_field = False
296
-
297
-
298
- def close():
299
- """
300
- 断开与 PN532 读卡器的连接。
301
- """
302
- _state.disconnect()
399
+ get_reader().rf_field = False
303
400
 
304
401
 
305
402
  @contextmanager
@@ -308,8 +405,8 @@ def session(port: str = None, reader_type: str = None):
308
405
  上下文管理器,自动管理连接生命周期。
309
406
 
310
407
  用法:
311
- with session("COM20") as reader:
312
- card_info = reader.active()
408
+ with session("COM20") as r:
409
+ card_info = r.active()
313
410
  """
314
411
  if port is None:
315
412
  port = os.environ.get("NFC_PORT")
@@ -319,8 +416,44 @@ def session(port: str = None, reader_type: str = None):
319
416
  raise ValueError("未指定串口,请通过 session(port=...) 或 NFC_PORT 环境变量配置")
320
417
  if reader_type is None:
321
418
  raise ValueError("未指定读卡器类型,请通过 session(reader_type=...) 或 NFC_READER 环境变量配置")
322
- _state.connect(port, reader_type)
419
+ connect(port, reader_type)
323
420
  try:
324
- yield _state.reader
421
+ yield reader
325
422
  finally:
326
- _state.disconnect()
423
+ disconnect()
424
+
425
+
426
+ def _atexit_cleanup() -> None:
427
+ """进程退出时的 atexit 安全清理钩子:先关闭物理 RF 场,再关闭串口。"""
428
+ if reader._connected and reader._target is not None:
429
+ try:
430
+ reader._target.rf_field = False
431
+ except Exception:
432
+ pass
433
+ try:
434
+ reader._target.close()
435
+ except Exception:
436
+ pass
437
+
438
+
439
+ atexit.register(_atexit_cleanup)
440
+
441
+
442
+ __all__ = [
443
+ "reader",
444
+ "connect",
445
+ "disconnect",
446
+ "get_reader",
447
+ "active",
448
+ "transceive",
449
+ "transceive_bits",
450
+ "reqa",
451
+ "wupa",
452
+ "halt",
453
+ "anticoll",
454
+ "select",
455
+ "field_on",
456
+ "field_off",
457
+ "close",
458
+ "session",
459
+ ]
@@ -4,9 +4,7 @@ NFC Script Trace 模块
4
4
  统一日志 API,包装 nfctester.trace 的 6 层日志系统。
5
5
  层: driver, debug, protocol, warning, error, app
6
6
 
7
- 支持两种控制方式:
8
- 1. 属性控制: trace.driver = True, trace.level = "warning"
9
- 2. 方法控制: trace.set_layer("driver", True), trace.set_level("warning")
7
+ 过滤器控制: trace.filter.driver = True, trace.filter.level = "warning"
10
8
  """
11
9
 
12
10
  from typing import Callable
@@ -55,27 +53,6 @@ def __getattr__(name):
55
53
  return getattr(_trace, name)
56
54
 
57
55
 
58
- def set_layer(layer: str, enable: bool = True):
59
- """
60
- 开启或关闭追踪层。
61
-
62
- Args:
63
- layer: 层级名称 ("driver", "debug", "protocol", "warning", "error", "app")
64
- enable: True 开启,False 关闭
65
- """
66
- _trace.set_layer(layer, enable)
67
-
68
-
69
- def set_level(level: str):
70
- """
71
- 设置最低日志级别。
72
-
73
- Args:
74
- level: 级别名称 ("trace", "debug", "warning", "error", "app")
75
- """
76
- _trace.set_level(level)
77
-
78
-
79
56
  def set_parse(level: int = 1):
80
57
  """
81
58
  设置解析级别。