nfcscript 0.0.7__tar.gz → 0.0.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nfcscript-0.0.7 → nfcscript-0.0.9}/PKG-INFO +12 -2
- {nfcscript-0.0.7 → nfcscript-0.0.9}/README.md +11 -1
- {nfcscript-0.0.7 → nfcscript-0.0.9}/pyproject.toml +2 -2
- {nfcscript-0.0.7 → nfcscript-0.0.9}/src/nfc/reader.py +18 -38
- {nfcscript-0.0.7 → nfcscript-0.0.9}/src/nfcscript.egg-info/PKG-INFO +12 -2
- {nfcscript-0.0.7 → nfcscript-0.0.9}/setup.cfg +0 -0
- {nfcscript-0.0.7 → nfcscript-0.0.9}/src/nfc/__init__.py +0 -0
- {nfcscript-0.0.7 → nfcscript-0.0.9}/src/nfc/assertions.py +0 -0
- {nfcscript-0.0.7 → nfcscript-0.0.9}/src/nfc/bits.py +0 -0
- {nfcscript-0.0.7 → nfcscript-0.0.9}/src/nfc/checksum.py +0 -0
- {nfcscript-0.0.7 → nfcscript-0.0.9}/src/nfc/cli.py +0 -0
- {nfcscript-0.0.7 → nfcscript-0.0.9}/src/nfc/delay.py +0 -0
- {nfcscript-0.0.7 → nfcscript-0.0.9}/src/nfc/hex_util.py +0 -0
- {nfcscript-0.0.7 → nfcscript-0.0.9}/src/nfc/nfc_cli.py +0 -0
- {nfcscript-0.0.7 → nfcscript-0.0.9}/src/nfc/trace.py +0 -0
- {nfcscript-0.0.7 → nfcscript-0.0.9}/src/nfcscript.egg-info/SOURCES.txt +0 -0
- {nfcscript-0.0.7 → nfcscript-0.0.9}/src/nfcscript.egg-info/dependency_links.txt +0 -0
- {nfcscript-0.0.7 → nfcscript-0.0.9}/src/nfcscript.egg-info/entry_points.txt +0 -0
- {nfcscript-0.0.7 → nfcscript-0.0.9}/src/nfcscript.egg-info/requires.txt +0 -0
- {nfcscript-0.0.7 → nfcscript-0.0.9}/src/nfcscript.egg-info/top_level.txt +0 -0
- {nfcscript-0.0.7 → nfcscript-0.0.9}/tests/test_main.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.9
|
|
4
4
|
Summary: A plugin-based core engine for DSL-style NFC testing automation.
|
|
5
5
|
Author: crthu
|
|
6
6
|
Requires-Python: >=3.14
|
|
@@ -191,7 +191,7 @@ with session() as reader:
|
|
|
191
191
|
|
|
192
192
|
| 函数 | 说明 |
|
|
193
193
|
|------|------|
|
|
194
|
-
| `ASSERT_EQUAL(
|
|
194
|
+
| `ASSERT_EQUAL(actual, expected, msg)` | 相等断言 |
|
|
195
195
|
| `ASSERT_LEN(data, expected_len, msg)` | 长度断言 |
|
|
196
196
|
| `ASSERT_IS_NOT_NONE(value, msg)` | 非空断言 |
|
|
197
197
|
| `ASSERT_IS_NONE(value, msg)` | 空值断言 |
|
|
@@ -253,6 +253,16 @@ NFC_TRACE_DRIVER=true
|
|
|
253
253
|
NFC_TRACE_PROTOCOL=true
|
|
254
254
|
```
|
|
255
255
|
|
|
256
|
+
## AI 编写脚本
|
|
257
|
+
|
|
258
|
+
本目录下 `SKILL.md` 提供了 DSL 的完整 API 参考和 few-shot 示例,可直接用于 AI Agent 生成 NFC 脚本。
|
|
259
|
+
|
|
260
|
+
**使用方式**:在 AI 对话中引用该文件,例如:
|
|
261
|
+
```
|
|
262
|
+
@nfcscript/SKILL.md
|
|
263
|
+
```
|
|
264
|
+
Agent 会基于 SKILL.md 中的 API 签名、few-shot 示例和约定生成正确的脚本代码。
|
|
265
|
+
|
|
256
266
|
## 开发
|
|
257
267
|
|
|
258
268
|
```bash
|
|
@@ -181,7 +181,7 @@ with session() as reader:
|
|
|
181
181
|
|
|
182
182
|
| 函数 | 说明 |
|
|
183
183
|
|------|------|
|
|
184
|
-
| `ASSERT_EQUAL(
|
|
184
|
+
| `ASSERT_EQUAL(actual, expected, msg)` | 相等断言 |
|
|
185
185
|
| `ASSERT_LEN(data, expected_len, msg)` | 长度断言 |
|
|
186
186
|
| `ASSERT_IS_NOT_NONE(value, msg)` | 非空断言 |
|
|
187
187
|
| `ASSERT_IS_NONE(value, msg)` | 空值断言 |
|
|
@@ -243,6 +243,16 @@ NFC_TRACE_DRIVER=true
|
|
|
243
243
|
NFC_TRACE_PROTOCOL=true
|
|
244
244
|
```
|
|
245
245
|
|
|
246
|
+
## AI 编写脚本
|
|
247
|
+
|
|
248
|
+
本目录下 `SKILL.md` 提供了 DSL 的完整 API 参考和 few-shot 示例,可直接用于 AI Agent 生成 NFC 脚本。
|
|
249
|
+
|
|
250
|
+
**使用方式**:在 AI 对话中引用该文件,例如:
|
|
251
|
+
```
|
|
252
|
+
@nfcscript/SKILL.md
|
|
253
|
+
```
|
|
254
|
+
Agent 会基于 SKILL.md 中的 API 签名、few-shot 示例和约定生成正确的脚本代码。
|
|
255
|
+
|
|
246
256
|
## 开发
|
|
247
257
|
|
|
248
258
|
```bash
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "nfcscript"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.9"
|
|
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.9"
|
|
38
38
|
commit = true
|
|
39
39
|
tag = true
|
|
40
40
|
allow_dirty = true
|
|
@@ -1,20 +1,12 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from contextlib import contextmanager
|
|
3
|
-
from
|
|
3
|
+
from nfctester.drivers.card_reader import CardInfo, TransceiveResult
|
|
4
4
|
from nfctester.registry import CardReaderRegistry
|
|
5
5
|
from .assertions import ASSERT_EQUAL, ASSERT_IS_NOT_NONE, ASSERT_LEN
|
|
6
6
|
from .checksum import GET_BCC
|
|
7
7
|
from .hex_util import FORMAT_HEX
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
class TransceiveResult(NamedTuple):
|
|
11
|
-
"""transceive_bits 返回结果"""
|
|
12
|
-
data: list[int]
|
|
13
|
-
"""接收到的数据字节列表"""
|
|
14
|
-
bits: int
|
|
15
|
-
"""最后一个字节的有效位数 (0 = 完整字节)"""
|
|
16
|
-
|
|
17
|
-
|
|
18
10
|
class _NFCState:
|
|
19
11
|
"""单例状态管理,封装读卡器生命周期"""
|
|
20
12
|
|
|
@@ -33,13 +25,13 @@ class _NFCState:
|
|
|
33
25
|
|
|
34
26
|
def connect(self, port: str = "COM20", reader_type: str = "pn532"):
|
|
35
27
|
self._reader = CardReaderRegistry.create(reader_type, transport="serial", port=port)
|
|
36
|
-
self._reader.
|
|
28
|
+
self._reader.open()
|
|
37
29
|
self._connected = True
|
|
38
30
|
print(f"Connected to {reader_type.upper()} on port {port}")
|
|
39
31
|
|
|
40
32
|
def disconnect(self):
|
|
41
33
|
if self._connected and self._reader:
|
|
42
|
-
self._reader.
|
|
34
|
+
self._reader.close()
|
|
43
35
|
self._connected = False
|
|
44
36
|
self._reader = None
|
|
45
37
|
print("Disconnected")
|
|
@@ -84,30 +76,29 @@ def get_reader():
|
|
|
84
76
|
return _state.reader
|
|
85
77
|
|
|
86
78
|
|
|
87
|
-
def active(ll: bool = False, ignore_error: bool = False, reqa_cmd: int = 0x26) ->
|
|
79
|
+
def active(ll: bool = False, ignore_error: bool = False, reqa_cmd: int = 0x26) -> CardInfo | None:
|
|
88
80
|
"""
|
|
89
81
|
寻卡操作,检测并激活目标卡片。
|
|
90
82
|
|
|
91
83
|
Args:
|
|
92
84
|
ll: 如果为 True,则执行底层抗冲突流程以兼容非标卡;
|
|
93
|
-
如果为 False
|
|
85
|
+
如果为 False,则使用读卡器原生 active()。
|
|
94
86
|
ignore_error: 如果为 True,当 BCC 或 SAK 校验失败时,记录警告而不停止执行。
|
|
95
87
|
reqa_cmd: 底层寻卡时使用的 REQA 命令字节,默认 0x26。
|
|
96
88
|
仅在 ll=True 时生效。
|
|
97
89
|
|
|
98
90
|
Returns:
|
|
99
|
-
|
|
100
|
-
|
|
91
|
+
CardInfo: 包含卡片信息的数据类,失败时返回 None。
|
|
92
|
+
属性说明:
|
|
101
93
|
- 'uid' (bytes): 卡片的 UID。
|
|
102
94
|
- 'atq' (bytes): 卡片的 ATQ (Answer To Request)。
|
|
103
95
|
- 'sak' (int): 卡片的 SAK (Select Acknowledge)。
|
|
104
|
-
- 'raw' (bytes): PN532 返回的原始响应数据。
|
|
105
96
|
"""
|
|
106
97
|
_state.ensure_connected()
|
|
107
98
|
reader = _state.reader
|
|
108
99
|
|
|
109
100
|
if not ll:
|
|
110
|
-
return reader.
|
|
101
|
+
return reader.active()
|
|
111
102
|
|
|
112
103
|
# 手动底层寻卡流程
|
|
113
104
|
# 1. REQA
|
|
@@ -138,7 +129,7 @@ def active(ll: bool = False, ignore_error: bool = False, reqa_cmd: int = 0x26) -
|
|
|
138
129
|
|
|
139
130
|
print(f"cl{cl}.uid: {FORMAT_HEX(data)}")
|
|
140
131
|
has_next = (data[0] == 0x88)
|
|
141
|
-
uid_to_select = data[0:5]
|
|
132
|
+
uid_to_select = list(data[0:5])
|
|
142
133
|
sak_res = select(cl_level=cl, uid=uid_to_select)
|
|
143
134
|
|
|
144
135
|
# SAK 校验
|
|
@@ -156,13 +147,7 @@ def active(ll: bool = False, ignore_error: bool = False, reqa_cmd: int = 0x26) -
|
|
|
156
147
|
sak = sak_res[0] if sak_res else 0
|
|
157
148
|
break
|
|
158
149
|
|
|
159
|
-
|
|
160
|
-
return {
|
|
161
|
-
'uid': bytes(full_uid),
|
|
162
|
-
'atq': bytes(atq),
|
|
163
|
-
'sak': sak,
|
|
164
|
-
'raw': bytes(full_uid), # 兼容原有的 raw 字段
|
|
165
|
-
}
|
|
150
|
+
return CardInfo(uid=bytes(full_uid), atq=bytes(atq), sak=sak)
|
|
166
151
|
|
|
167
152
|
|
|
168
153
|
def transceive(data: list[int], tx_crc: bool = True, rx_crc: bool = True) -> list[int]:
|
|
@@ -179,9 +164,8 @@ def transceive(data: list[int], tx_crc: bool = True, rx_crc: bool = True) -> lis
|
|
|
179
164
|
"""
|
|
180
165
|
_state.ensure_connected()
|
|
181
166
|
reader = _state.reader
|
|
182
|
-
reader.
|
|
183
|
-
res
|
|
184
|
-
return list(res) if res is not None else []
|
|
167
|
+
res = reader.transceive(bytes(data), tx_crc=tx_crc, rx_crc=rx_crc)
|
|
168
|
+
return list(res.data) if res and res.data else []
|
|
185
169
|
|
|
186
170
|
|
|
187
171
|
def transceive_bits(data: list[int], last_tx_bits: int = 0, tx_crc: bool = True, rx_crc: bool = True) -> TransceiveResult | None:
|
|
@@ -196,15 +180,11 @@ def transceive_bits(data: list[int], last_tx_bits: int = 0, tx_crc: bool = True,
|
|
|
196
180
|
rx_crc: 接收时是否自动校验 CRC。
|
|
197
181
|
|
|
198
182
|
Returns:
|
|
199
|
-
TransceiveResult | None: 包含 data 和
|
|
183
|
+
TransceiveResult | None: 包含 data 和 rx_bits 属性,失败返回 None。
|
|
200
184
|
"""
|
|
201
185
|
_state.ensure_connected()
|
|
202
186
|
reader = _state.reader
|
|
203
|
-
reader.
|
|
204
|
-
res = reader.transceive(bytes(data), last_tx_bits=last_tx_bits)
|
|
205
|
-
if res is None:
|
|
206
|
-
return None
|
|
207
|
-
return TransceiveResult(data=list(res), bits=reader.last_rx_bits)
|
|
187
|
+
return reader.transceive_bits(bytes(data), last_tx_bits=last_tx_bits, tx_crc=tx_crc, rx_crc=rx_crc)
|
|
208
188
|
|
|
209
189
|
|
|
210
190
|
def reqa(cmd: int = 0x26) -> TransceiveResult | None:
|
|
@@ -239,7 +219,7 @@ def anticoll(cl_level: int, nvb: int = 0x20, uid_prefix: list[int] = []) -> Tran
|
|
|
239
219
|
:param cl_level: 1 (0x93), 2 (0x95), or 3 (0x97)
|
|
240
220
|
:param nvb: Number of Valid Bits,定义了包含 SEL 和 NVB 在内的有效位数
|
|
241
221
|
:param uid_prefix: 已知的部分 UID (0-4 字节)
|
|
242
|
-
:return: TransceiveResult 包含 data 和
|
|
222
|
+
:return: TransceiveResult 包含 data 和 rx_bits 属性
|
|
243
223
|
"""
|
|
244
224
|
cmd = [_get_sel_cmd(cl_level), nvb] + uid_prefix
|
|
245
225
|
# 抗冲突响应无 CRC
|
|
@@ -260,13 +240,13 @@ def select(cl_level: int, uid: list[int]) -> list[int] | None:
|
|
|
260
240
|
def field_on():
|
|
261
241
|
"""开启 PN532 的 RF 场。"""
|
|
262
242
|
_state.ensure_connected()
|
|
263
|
-
_state.reader.
|
|
243
|
+
_state.reader.rf_field = True
|
|
264
244
|
|
|
265
245
|
|
|
266
246
|
def field_off():
|
|
267
247
|
"""关闭 PN532 的 RF 场。"""
|
|
268
248
|
_state.ensure_connected()
|
|
269
|
-
_state.reader.
|
|
249
|
+
_state.reader.rf_field = False
|
|
270
250
|
|
|
271
251
|
|
|
272
252
|
def close():
|
|
@@ -283,7 +263,7 @@ def session(port: str = None, reader_type: str = None):
|
|
|
283
263
|
|
|
284
264
|
用法:
|
|
285
265
|
with session("COM20") as reader:
|
|
286
|
-
card_info = reader.
|
|
266
|
+
card_info = reader.active()
|
|
287
267
|
"""
|
|
288
268
|
if port is None:
|
|
289
269
|
port = os.environ.get("NFC_PORT")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nfcscript
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.9
|
|
4
4
|
Summary: A plugin-based core engine for DSL-style NFC testing automation.
|
|
5
5
|
Author: crthu
|
|
6
6
|
Requires-Python: >=3.14
|
|
@@ -191,7 +191,7 @@ with session() as reader:
|
|
|
191
191
|
|
|
192
192
|
| 函数 | 说明 |
|
|
193
193
|
|------|------|
|
|
194
|
-
| `ASSERT_EQUAL(
|
|
194
|
+
| `ASSERT_EQUAL(actual, expected, msg)` | 相等断言 |
|
|
195
195
|
| `ASSERT_LEN(data, expected_len, msg)` | 长度断言 |
|
|
196
196
|
| `ASSERT_IS_NOT_NONE(value, msg)` | 非空断言 |
|
|
197
197
|
| `ASSERT_IS_NONE(value, msg)` | 空值断言 |
|
|
@@ -253,6 +253,16 @@ NFC_TRACE_DRIVER=true
|
|
|
253
253
|
NFC_TRACE_PROTOCOL=true
|
|
254
254
|
```
|
|
255
255
|
|
|
256
|
+
## AI 编写脚本
|
|
257
|
+
|
|
258
|
+
本目录下 `SKILL.md` 提供了 DSL 的完整 API 参考和 few-shot 示例,可直接用于 AI Agent 生成 NFC 脚本。
|
|
259
|
+
|
|
260
|
+
**使用方式**:在 AI 对话中引用该文件,例如:
|
|
261
|
+
```
|
|
262
|
+
@nfcscript/SKILL.md
|
|
263
|
+
```
|
|
264
|
+
Agent 会基于 SKILL.md 中的 API 签名、few-shot 示例和约定生成正确的脚本代码。
|
|
265
|
+
|
|
256
266
|
## 开发
|
|
257
267
|
|
|
258
268
|
```bash
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|