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.
- {nfcscript-0.0.15 → nfcscript-0.0.17}/PKG-INFO +17 -17
- {nfcscript-0.0.15 → nfcscript-0.0.17}/README.md +16 -16
- {nfcscript-0.0.15 → nfcscript-0.0.17}/pyproject.toml +2 -5
- {nfcscript-0.0.15 → nfcscript-0.0.17}/src/nfc/cli.py +11 -11
- {nfcscript-0.0.15 → nfcscript-0.0.17}/src/nfc/nfc_cli.py +3 -3
- {nfcscript-0.0.15 → nfcscript-0.0.17}/src/nfc/reader.py +197 -64
- {nfcscript-0.0.15 → nfcscript-0.0.17}/src/nfc/trace.py +1 -24
- {nfcscript-0.0.15 → nfcscript-0.0.17}/src/nfcscript.egg-info/PKG-INFO +17 -17
- {nfcscript-0.0.15 → nfcscript-0.0.17}/tests/test_cli.py +2 -42
- {nfcscript-0.0.15 → nfcscript-0.0.17}/tests/test_reader.py +139 -83
- {nfcscript-0.0.15 → nfcscript-0.0.17}/tests/test_trace.py +10 -31
- {nfcscript-0.0.15 → nfcscript-0.0.17}/setup.cfg +0 -0
- {nfcscript-0.0.15 → nfcscript-0.0.17}/src/nfc/__init__.py +0 -0
- {nfcscript-0.0.15 → nfcscript-0.0.17}/src/nfc/assertions.py +0 -0
- {nfcscript-0.0.15 → nfcscript-0.0.17}/src/nfc/bits.py +0 -0
- {nfcscript-0.0.15 → nfcscript-0.0.17}/src/nfc/checksum.py +0 -0
- {nfcscript-0.0.15 → nfcscript-0.0.17}/src/nfc/delay.py +0 -0
- {nfcscript-0.0.15 → nfcscript-0.0.17}/src/nfc/hex_util.py +0 -0
- {nfcscript-0.0.15 → nfcscript-0.0.17}/src/nfcscript.egg-info/SOURCES.txt +0 -0
- {nfcscript-0.0.15 → nfcscript-0.0.17}/src/nfcscript.egg-info/dependency_links.txt +0 -0
- {nfcscript-0.0.15 → nfcscript-0.0.17}/src/nfcscript.egg-info/entry_points.txt +0 -0
- {nfcscript-0.0.15 → nfcscript-0.0.17}/src/nfcscript.egg-info/requires.txt +0 -0
- {nfcscript-0.0.15 → nfcscript-0.0.17}/src/nfcscript.egg-info/top_level.txt +0 -0
- {nfcscript-0.0.15 → nfcscript-0.0.17}/tests/test_assertions.py +0 -0
- {nfcscript-0.0.15 → nfcscript-0.0.17}/tests/test_bits.py +0 -0
- {nfcscript-0.0.15 → nfcscript-0.0.17}/tests/test_checksum.py +0 -0
- {nfcscript-0.0.15 → nfcscript-0.0.17}/tests/test_cli_collect.py +0 -0
- {nfcscript-0.0.15 → nfcscript-0.0.17}/tests/test_delay.py +0 -0
- {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.
|
|
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=
|
|
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=
|
|
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
|
-
| `
|
|
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
|
-
|
|
277
|
-
|
|
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=
|
|
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=
|
|
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
|
-
| `
|
|
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
|
-
|
|
267
|
-
|
|
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.
|
|
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.
|
|
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="最低日志级别 (
|
|
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("
|
|
69
|
-
trace_arg = args.
|
|
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.
|
|
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.
|
|
76
|
+
setattr(trace.filter, layer, True)
|
|
77
77
|
elif name:
|
|
78
|
-
trace.
|
|
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.
|
|
83
|
-
trace.
|
|
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=
|
|
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
|
|
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
|
-
|
|
71
|
+
@property
|
|
72
|
+
def rf_field(self) -> bool:
|
|
73
|
+
"""获取射频场状态 (rf 别名)。"""
|
|
74
|
+
return self.rf
|
|
15
75
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
|
25
|
-
|
|
82
|
+
def mf_crypto(self) -> bool:
|
|
83
|
+
"""获取 Mifare 硬件加密状态。"""
|
|
84
|
+
return getattr(get_reader(), "mf_crypto", False)
|
|
26
85
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
|
41
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
165
|
+
获取当前连接的读卡器实例,若未连接则尝试从环境变量自动连接。
|
|
72
166
|
|
|
73
167
|
Returns:
|
|
74
168
|
reader: 当前连接的读卡器实例。
|
|
75
169
|
"""
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
98
|
-
reader = _state.reader
|
|
198
|
+
rdr = get_reader()
|
|
99
199
|
|
|
100
200
|
if not low_layer:
|
|
101
|
-
return
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
res =
|
|
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(
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
res =
|
|
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
|
-
|
|
384
|
+
开启读卡器的 RF 场。
|
|
279
385
|
|
|
280
386
|
Raises:
|
|
281
387
|
AssertionError: 未连接读卡器时抛出。
|
|
282
388
|
"""
|
|
283
|
-
|
|
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
|
-
|
|
394
|
+
关闭读卡器的 RF 场。
|
|
290
395
|
|
|
291
396
|
Raises:
|
|
292
397
|
AssertionError: 未连接读卡器时抛出。
|
|
293
398
|
"""
|
|
294
|
-
|
|
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
|
|
312
|
-
card_info =
|
|
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
|
-
|
|
419
|
+
connect(port, reader_type)
|
|
323
420
|
try:
|
|
324
|
-
yield
|
|
421
|
+
yield reader
|
|
325
422
|
finally:
|
|
326
|
-
|
|
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
|
设置解析级别。
|