nfcscript 0.0.1__tar.gz → 0.0.6__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.
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nfcscript
3
- Version: 0.0.1
4
- Summary: NFC test script
3
+ Version: 0.0.6
4
+ Summary: A plugin-based core engine for DSL-style NFC testing automation.
5
5
  Author: crthu
6
6
  Requires-Python: >=3.14
7
7
  Description-Content-Type: text/markdown
8
- Requires-Dist: CarrotRFIDTester
8
+ Requires-Dist: nfctester
9
9
 
10
10
  # nfcscript
11
11
 
@@ -1,19 +1,19 @@
1
1
  [project]
2
2
  name = "nfcscript"
3
- version = "0.0.1"
4
- description = "NFC test script"
3
+ version = "0.0.6"
4
+ description = "A plugin-based core engine for DSL-style NFC testing automation."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.14"
7
7
  authors = [{name = "crthu"}]
8
8
  dependencies = [
9
- "CarrotRFIDTester",
9
+ "nfctester",
10
10
  ]
11
11
 
12
12
  [tool.setuptools.packages.find]
13
13
  where = ["src"]
14
14
 
15
15
  [project.scripts]
16
- nfcscript = "nfcscript.main:main"
16
+ nfcscript = "nfc.cli:main"
17
17
 
18
18
  [tool.pytest.ini_options]
19
19
  pythonpath = ["src"]
@@ -29,12 +29,14 @@ dev = [
29
29
  ]
30
30
 
31
31
  [tool.bumpversion]
32
- current_version = "0.0.1"
32
+ current_version = "0.0.6"
33
+ commit = true
34
+ tag = true
33
35
  allow_dirty = true
36
+ tag_name = "v{new_version}"
37
+ message = "chore(release): bump version {current_version} → {new_version}"
34
38
  parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
35
39
  serialize = ["{major}.{minor}.{patch}"]
36
- search = 'version = "{current_version}"'
37
- replace = 'version = "{new_version}"'
38
40
 
39
41
  [[tool.bumpversion.files]]
40
42
  filename = "pyproject.toml"
@@ -0,0 +1,6 @@
1
+ from .reader import *
2
+ from .delay import *
3
+ from .assertions import *
4
+ from .bits import *
5
+ from .hex_util import *
6
+ from .checksum import *
@@ -0,0 +1,68 @@
1
+ import inspect
2
+ from typing import Any, TypeGuard, TypeVar
3
+
4
+ from nfc.hex_util import FORMAT_HEX
5
+
6
+ T = TypeVar("T")
7
+
8
+ def _get_stack_trace() -> str:
9
+ """获取格式化的调用堆栈"""
10
+ # inspect.stack()[0] 是 _get_stack_trace
11
+ # inspect.stack()[1] 是 ASSERT_... 函数
12
+ # inspect.stack()[2:] 是实际的业务调用代码
13
+ stack = inspect.stack()[2:]
14
+ chain = []
15
+ for frame in stack:
16
+ # 简化路径,仅显示文件名
17
+ filename = frame.filename.split('\\')[-1].split('/')[-1]
18
+ chain.append(f" at {filename}:{frame.lineno} in {frame.function}")
19
+ return "\n".join(chain)
20
+
21
+ def ASSERT_LEN(data: Any, expected_len: int, msg: str | None = None) -> None:
22
+ actual_len = len(data)
23
+ if actual_len != expected_len:
24
+ error_msg = (
25
+ f"\nAssertion Failed: {msg}\n" if msg else ""
26
+ ) + (
27
+ f"Assertion Failed: Length mismatch\n"
28
+ f" Expected: {expected_len}\n"
29
+ f" Actual : {actual_len}\n"
30
+ f" Data : {data}\n"
31
+ f"Stack Trace:\n{_get_stack_trace()}"
32
+ )
33
+ raise AssertionError(error_msg)
34
+
35
+ def ASSERT_EQUAL(expected: Any, actual: Any, msg: str | None = None) -> None:
36
+ if actual != expected:
37
+ error_msg = (
38
+ f"\nAssertion Failed: {msg}\n" if msg else ""
39
+ ) + (
40
+ f"Assertion Failed: Value mismatch\n"
41
+ f" Expected: {FORMAT_HEX(expected)} ({type(expected).__name__})\n"
42
+ f" Actual : {FORMAT_HEX(actual)} ({type(actual).__name__})\n"
43
+ f"Stack Trace:\n{_get_stack_trace()}"
44
+ )
45
+ raise AssertionError(error_msg)
46
+
47
+ def ASSERT_IS_NONE(value: Any, msg: str | None = None) -> None:
48
+ if value is not None:
49
+ error_msg = (
50
+ f"\nAssertion Failed: {msg}\n" if msg else ""
51
+ ) + (
52
+ f"Assertion Failed: Expected None\n"
53
+ f" Actual : {value} ({type(value).__name__})\n"
54
+ f"Stack Trace:\n{_get_stack_trace()}"
55
+ )
56
+ raise AssertionError(error_msg)
57
+
58
+ def ASSERT_IS_NOT_NONE(value: T | None, msg: str | None = None) -> TypeGuard[T]:
59
+ if value is None:
60
+ error_msg = (
61
+ f"\nAssertion Failed: {msg}\n" if msg else ""
62
+ ) + (
63
+ f"Assertion Failed: Expected Not None\n"
64
+ f" Actual : {value}\n"
65
+ f"Stack Trace:\n{_get_stack_trace()}"
66
+ )
67
+ raise AssertionError(error_msg)
68
+ return True
@@ -1,4 +1,6 @@
1
- def bits_update(val: int, mask: int, data: int) -> int:
1
+ from .hex_util import FORMAT_HEX
2
+
3
+ def BITS_UPDATE(val: int, mask: int, data: int) -> int:
2
4
  """
3
5
  更新寄存器中的特定位域 (Read-Modify-Write)
4
6
  :param val: 当前寄存器的原始值
@@ -6,10 +8,10 @@ def bits_update(val: int, mask: int, data: int) -> int:
6
8
  :param data: 已移位对齐的新数值 (例如: 0x20, 即 1 << 5)
7
9
  :return: 更新后的寄存器值
8
10
  """
9
- assert (data & ~mask) == 0, f"Data {hex(data)} contains bits outside mask {hex(mask)}"
11
+ assert (data & ~mask) == 0, f"Data {FORMAT_HEX(data)} contains bits outside mask {FORMAT_HEX(mask)}"
10
12
  return (val & ~mask) | (data & mask)
11
13
 
12
- def bits_set(val: int, mask: int) -> int:
14
+ def BITS_SET(val: int, mask: int) -> int:
13
15
  """
14
16
  将寄存器中的指定位强制置 1 (OR 操作)
15
17
 
@@ -17,7 +19,7 @@ def bits_set(val: int, mask: int) -> int:
17
19
  """
18
20
  return val | mask
19
21
 
20
- def bits_reset(val: int, mask: int) -> int:
22
+ def BITS_RESET(val: int, mask: int) -> int:
21
23
  """
22
24
  将寄存器中的指定位强制清 0 (AND-NOT 操作)
23
25
 
@@ -0,0 +1,10 @@
1
+ from .assertions import ASSERT_LEN
2
+ from .hex_util import FORMAT_HEX
3
+
4
+ def GET_BCC(data: list[int]) -> int:
5
+ """
6
+ 计算 BCC: data[0]^data[1]^data[2]^data[3]
7
+ :param data: 4字节的UID数据
8
+ """
9
+ ASSERT_LEN(data, 4, msg=f"计算BCC时输入数据长度应为4: {FORMAT_HEX(data)}")
10
+ return data[0] ^ data[1] ^ data[2] ^ data[3]
@@ -0,0 +1,27 @@
1
+ import sys
2
+ import argparse
3
+ import os
4
+ import runpy
5
+
6
+ def run_script(script_path):
7
+ # Add the script's directory to sys.path so it can find its dependencies
8
+ script_dir = os.path.dirname(os.path.abspath(script_path))
9
+ sys.path.insert(0, script_dir)
10
+
11
+ # Run the script as __main__ so __name__ == "__main__" works inside the script
12
+ runpy.run_path(script_path, run_name="__main__")
13
+
14
+ def main():
15
+ print("Hello from nfcscript!")
16
+ parser = argparse.ArgumentParser(description="NFC Script Runner")
17
+ parser.add_argument("script", help="Path to the script to run")
18
+ args = parser.parse_args()
19
+
20
+ if not os.path.exists(args.script):
21
+ print(f"Error: Script not found: {args.script}")
22
+ sys.exit(1)
23
+
24
+ run_script(args.script)
25
+
26
+ if __name__ == "__main__":
27
+ main()
@@ -0,0 +1,4 @@
1
+ import time
2
+
3
+ def DELAY_MS(ms: int) -> None:
4
+ time.sleep(ms / 1000.0)
@@ -0,0 +1,99 @@
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
+ def PARSE_HEX(text: str) -> tuple[str, int]:
7
+ """
8
+ 解析文本 -> (纯十六进制字符串, 最后字节有效位数)
9
+ 支持: "AA BB 7'h03" -> ("AABB03", 7)
10
+ """
11
+ last_bits = 0
12
+ cleaned = text.strip()
13
+
14
+ # 查找所有位标记
15
+ matches = list(BIT_PATTERN.finditer(cleaned))
16
+ if matches:
17
+ last_match = matches[-1]
18
+ last_bits = int(last_match.group(1))
19
+ hex_val = last_match.group(2)
20
+
21
+ # 替换位标记为纯 hex
22
+ cleaned = cleaned[:last_match.start()] + hex_val + cleaned[last_match.end():]
23
+
24
+ # 去空格得到纯 hex
25
+ clean_hex = cleaned.replace(" ", "")
26
+
27
+ # 简单的合法性检查
28
+ if clean_hex:
29
+ try:
30
+ bytes.fromhex(clean_hex)
31
+ except ValueError:
32
+ raise ValueError(f"不合法的十六进制数据: {clean_hex}")
33
+
34
+ return clean_hex, last_bits
35
+
36
+ def FORMAT_HEX(data: int | bytes | list[int] | str, last_bits: int = 0) -> str:
37
+ """
38
+ 格式化数据为带位标记的可视化格式
39
+ 支持输入类型: int, bytes, list[int], str (纯 hex)
40
+ 示例:
41
+ FORMAT_HEX(0xAABB03, 7) -> "AA BB 7'h03"
42
+ FORMAT_HEX("AABB03", 7) -> "AA BB 7'h03"
43
+ FORMAT_HEX(b'\xaa\xbb\x03', 7) -> "AA BB 7'h03"
44
+ FORMAT_HEX([0xAA, 0xBB, 0x03], 7) -> "AA BB 7'h03"
45
+ """
46
+ # 1. 统一转换为纯十六进制字符串
47
+ if isinstance(data, int):
48
+ hex_str = hex(data)[2:].upper()
49
+ elif isinstance(data, bytes):
50
+ hex_str = data.hex().upper()
51
+ elif isinstance(data, list):
52
+ hex_str = bytes(data).hex().upper()
53
+ elif isinstance(data, str):
54
+ hex_str = data.replace(" ", "").upper()
55
+ else:
56
+ raise TypeError(f"不支持的输入类型: {type(data)}")
57
+
58
+ if not hex_str:
59
+ return ""
60
+
61
+ # 2. 如果是整字节 (无 last_bits)
62
+ if last_bits == 0:
63
+ return ' '.join(hex_str[i:i+2] for i in range(0, len(hex_str), 2))
64
+
65
+ # 3. 有 last_bits,处理最后一个字节
66
+ if len(hex_str) <= 2:
67
+ return f"{last_bits}'h{hex_str}"
68
+
69
+ prefix = hex_str[:-2]
70
+ last_byte = hex_str[-2:]
71
+
72
+ formatted_prefix = ' '.join(prefix[i:i+2] for i in range(0, len(prefix), 2))
73
+ return f"{formatted_prefix} {last_bits}'h{last_byte}"
74
+
75
+ if __name__ == "__main__":
76
+ # --- 测试例程 ---
77
+ print("=== Hex 工具测试 ===")
78
+
79
+ # 测试用例: (data, last_bits, 类型名)
80
+ test_cases = [
81
+ ("AABB03", 7, "str"),
82
+ (0xAABB03, 7, "int"),
83
+ (b'\xAA\xBB\x03', 7, "bytes"),
84
+ ([0xAA, 0xBB, 0x03], 7, "list"),
85
+ ("FF", 4, "str"),
86
+ (0xFF, 4, "int"),
87
+ ("AABBCC", 0, "str"),
88
+ (0xAABBCC, 0, "int"),
89
+ (b'\xAA\xBB\xCC', 0, "bytes"),
90
+ ]
91
+
92
+ for data, b, typename in test_cases:
93
+ formatted = FORMAT_HEX(data, b)
94
+ print(f"输入: ({data}, {b}, {typename}) -> 格式化: {formatted}")
95
+
96
+ # 反向解析验证
97
+ parsed_h, parsed_b = PARSE_HEX(formatted)
98
+ print(f"解析: {formatted} -> Hex: {parsed_h}, Bits: {parsed_b}")
99
+ print("-" * 20)
@@ -0,0 +1,197 @@
1
+ from typing import Any
2
+
3
+ from .assertions import ASSERT_EQUAL, ASSERT_IS_NOT_NONE, ASSERT_LEN
4
+ from .checksum import GET_BCC
5
+ from .hex_util import FORMAT_HEX
6
+ from nfctester.hardware.serial_transport import SerialTransport
7
+ from nfctester.drivers.pn532_hsu import PN532_HSU
8
+
9
+ # Global state
10
+ _transport: SerialTransport | None = None
11
+ _driver: PN532_HSU | None = None
12
+ _is_connected = False
13
+
14
+ def connect(port="COM20"):
15
+ """
16
+ 连接并初始化 PN532 读卡器。
17
+
18
+ Args:
19
+ port: 串口号,默认为 "COM20"。
20
+ """
21
+ global _transport, _driver, _is_connected
22
+ _transport = SerialTransport(port=port)
23
+ _driver = PN532_HSU(_transport)
24
+ _driver.connect()
25
+ _is_connected = True
26
+ print(f"Connected to PN532 on {_transport.ser.port}")
27
+
28
+ def active(ll: bool = False, ignore_error: bool = False) -> dict | None:
29
+ """
30
+ 寻卡操作,检测并激活目标卡片。
31
+
32
+ Args:
33
+ ll: 如果为 True,则执行底层抗冲突流程以兼容非标卡;
34
+ 如果为 False,则使用 PN532 原生 find()。
35
+ ignore_error: 如果为 True,当 BCC 或 SAK 校验失败时,记录警告而不停止执行。
36
+
37
+ Returns:
38
+ dict: 包含卡片信息的字典,失败时返回 None。
39
+ Key 说明:
40
+ - 'uid' (bytes): 卡片的 UID。
41
+ - 'atq' (bytes): 卡片的 ATQ (Answer To Request)。
42
+ - 'sak' (int): 卡片的 SAK (Select Acknowledge)。
43
+ - 'raw' (bytes): PN532 返回的原始响应数据。
44
+ """
45
+ ASSERT_IS_NOT_NONE(_driver, msg="NFC driver not connected. Call connect() first.")
46
+
47
+ if not ll:
48
+ return _driver.find()
49
+ else:
50
+ # 手动底层寻卡流程
51
+ # 1. REQA
52
+ res_reqa = reqa()
53
+ if not res_reqa:
54
+ return None
55
+ atq = bytes(res_reqa[0])
56
+
57
+ # 2. 抗冲突与选择
58
+ full_uid = []
59
+ sak = 0
60
+ for cl in [1, 2, 3]:
61
+ res = anticoll(cl_level=cl, nvb=0x20)
62
+ if not res or not res[0]:
63
+ return None
64
+
65
+ data = res[0]
66
+ # 只有当有数据时才进行长度校验
67
+ if len(data) > 0:
68
+ ASSERT_LEN(data, 5, msg=f"CL{cl} 抗冲突返回数据长度不符: {FORMAT_HEX(data)}")
69
+
70
+ # 计算期望的 BCC
71
+ expected_bcc = GET_BCC(data[0:4])
72
+
73
+ if not ignore_error:
74
+ ASSERT_EQUAL(expected_bcc, data[4], msg=f"CL{cl} BCC 校验失败: data={FORMAT_HEX(data)}")
75
+ elif data[4] != expected_bcc:
76
+ print(f"Warning: CL{cl} BCC 校验失败")
77
+
78
+ has_next = (data[0] == 0x88)
79
+ uid_to_select = data[0:5]
80
+ sak_res = select(cl_level=cl, uid=uid_to_select)
81
+
82
+ # SAK 校验
83
+ if not sak_res:
84
+ if not ignore_error: ASSERT_IS_NOT_NONE(sak_res, msg=f"CL{cl} SAK 选择失败")
85
+ else: print(f"Warning: CL{cl} SAK 选择失败")
86
+
87
+ if has_next:
88
+ full_uid.extend(data[1:4])
89
+ else:
90
+ full_uid.extend(data[0:4])
91
+ # 记录最后一次 SELECT 的 SAK
92
+ sak = sak_res[0] if sak_res else 0
93
+ break
94
+
95
+ # 返回兼容的字典格式
96
+ return {
97
+ 'uid': bytes(full_uid),
98
+ 'atq': atq,
99
+ 'sak': sak,
100
+ 'raw': bytes(full_uid) # 兼容原有的 raw 字段
101
+ }
102
+
103
+ def transceive(data: list[int], tx_crc: bool = True, rx_crc: bool = True) -> list[int]:
104
+ """
105
+ 使用底层帧交互方式发送数据。
106
+
107
+ Args:
108
+ data: 要发送的字节列表。
109
+ tx_crc: 发送时是否自动添加 CRC。
110
+ rx_crc: 接收时是否自动校验 CRC。
111
+
112
+ Returns:
113
+ list[int]: 接收到的数据字节列表。
114
+ """
115
+ ASSERT_IS_NOT_NONE(_driver, msg="NFC driver not connected.")
116
+ _driver.set_crc(tx_crc, rx_crc)
117
+ res = _driver.transceive(bytes(data))
118
+ # print(f"transceive: {FORMAT_HEX(data)}")
119
+ return list(res) if res is not None else []
120
+
121
+ def transceive_bits(data: list[int], last_tx_bits: int = 0, tx_crc: bool = True, rx_crc: bool = True) -> tuple[list[int], int]:
122
+ """
123
+ 使用底层帧交互方式发送数据,并支持对最后一个字节进行位控制。
124
+
125
+ Args:
126
+ data: 要发送的字节列表。
127
+ last_tx_bits: 最后一个字节实际发送的有效位数 (1-7),
128
+ 0 表示发送完整字节。
129
+ tx_crc: 发送时是否自动添加 CRC。
130
+ rx_crc: 接收时是否自动校验 CRC。
131
+
132
+ Returns:
133
+ tuple[list[int], int]: (接收到的数据字节列表, 最后一个字节的有效位数)。
134
+ 最后一个字节的有效位数 0 表示完整字节。
135
+ """
136
+ ASSERT_IS_NOT_NONE(_driver, msg="NFC driver not connected.")
137
+ _driver.set_crc(tx_crc, rx_crc)
138
+ res = _driver.transceive(bytes(data), last_tx_bits=last_tx_bits)
139
+ # print(f"transceive: {FORMAT_HEX(data, last_tx_bits)}")
140
+ return (list(res) if res is not None else []), _driver.last_rx_bits
141
+
142
+ def reqa() -> tuple[list[int], int] | None:
143
+ """ISO14443-A REQA (7 bits)"""
144
+ # REQA: 0x26,短帧,仅发送 7 bits,响应无 CRC
145
+ return transceive_bits([0x26], last_tx_bits=7, tx_crc=False, rx_crc=False)
146
+
147
+ def wupa() -> tuple[list[int], int] | None:
148
+ """ISO14443-A WUPA (7 bits)"""
149
+ # WUPA: 0x52,短帧,仅发送 7 bits,响应无 CRC
150
+ return transceive_bits([0x52], last_tx_bits=7, tx_crc=False, rx_crc=False)
151
+
152
+ def halt() -> list[int] | None:
153
+ """ISO14443-A HALT (Standard Frame)"""
154
+ # HALT: 0x50 0x00 + CRC
155
+ return transceive([0x50, 0x00], tx_crc=True, rx_crc=True)
156
+
157
+ def anticoll(cl_level: int, nvb: int = 0x20, uid_prefix: list[int] = []) -> tuple[list[int], int] | None:
158
+ """
159
+ ISO14443-A ANTICOLL (Anti-collision)
160
+ :param cl_level: 1 (0x93) or 2 (0x95)
161
+ :param nvb: Number of Valid Bits,定义了包含 SEL 和 NVB 在内的有效位数
162
+ :param uid_prefix: 已知的部分 UID (0-4 字节)
163
+ :return: (响应数据, last_rx_bits)
164
+ """
165
+ cmd = [0x93 if cl_level == 1 else 0x95, nvb] + uid_prefix
166
+ # 抗冲突响应无 CRC
167
+ return transceive_bits(cmd, last_tx_bits=0, tx_crc=False, rx_crc=False)
168
+
169
+ def select(cl_level: int, uid: list[int]) -> list[int] | None:
170
+ """
171
+ ISO14443-A SELECT (Standard Frame)
172
+ :param cl_level: 1 (0x93) or 2 (0x95)
173
+ :param uid: 完整 5 字节 UID (包含 BCC)
174
+ """
175
+ cmd = [0x93 if cl_level == 1 else 0x95, 0x70] + uid
176
+ # 选择帧需带 CRC
177
+ return transceive(cmd, tx_crc=True, rx_crc=True)
178
+
179
+ def field_on():
180
+ """开启 PN532 的 RF 场。"""
181
+ ASSERT_IS_NOT_NONE(_driver, msg="NFC driver not connected.")
182
+ _driver.set_rf_field(True)
183
+
184
+ def field_off():
185
+ """关闭 PN532 的 RF 场。"""
186
+ ASSERT_IS_NOT_NONE(_driver, msg="NFC driver not connected.")
187
+ _driver.set_rf_field(False)
188
+
189
+ def close():
190
+ """
191
+ 断开与 PN532 读卡器的连接。
192
+ """
193
+ global _is_connected
194
+ if _is_connected and _driver:
195
+ _driver.disconnect()
196
+ _is_connected = False
197
+ print("Disconnected")
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nfcscript
3
- Version: 0.0.1
4
- Summary: NFC test script
3
+ Version: 0.0.6
4
+ Summary: A plugin-based core engine for DSL-style NFC testing automation.
5
5
  Author: crthu
6
6
  Requires-Python: >=3.14
7
7
  Description-Content-Type: text/markdown
8
- Requires-Dist: CarrotRFIDTester
8
+ Requires-Dist: nfctester
9
9
 
10
10
  # nfcscript
11
11
 
@@ -1,9 +1,13 @@
1
1
  README.md
2
2
  pyproject.toml
3
- src/nfcscript/__init__.py
4
- src/nfcscript/assertions.py
5
- src/nfcscript/bits.py
6
- src/nfcscript/main.py
3
+ src/nfc/__init__.py
4
+ src/nfc/assertions.py
5
+ src/nfc/bits.py
6
+ src/nfc/checksum.py
7
+ src/nfc/cli.py
8
+ src/nfc/delay.py
9
+ src/nfc/hex_util.py
10
+ src/nfc/reader.py
7
11
  src/nfcscript.egg-info/PKG-INFO
8
12
  src/nfcscript.egg-info/SOURCES.txt
9
13
  src/nfcscript.egg-info/dependency_links.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ nfcscript = nfc.cli:main
@@ -0,0 +1 @@
1
+ nfctester
File without changes
@@ -1,37 +0,0 @@
1
- import inspect
2
- from typing import Any
3
-
4
- def _get_stack_trace() -> str:
5
- """获取格式化的调用堆栈"""
6
- # inspect.stack()[0] 是 _get_stack_trace
7
- # inspect.stack()[1] 是 ASSERT_... 函数
8
- # inspect.stack()[2:] 是实际的业务调用代码
9
- stack = inspect.stack()[2:]
10
- chain = []
11
- for frame in stack:
12
- # 简化路径,仅显示文件名
13
- filename = frame.filename.split('\\')[-1].split('/')[-1]
14
- chain.append(f" at {filename}:{frame.lineno} in {frame.function}")
15
- return "\n".join(chain)
16
-
17
- def ASSERT_LEN(data: Any, expected_len: int) -> None:
18
- actual_len = len(data)
19
- if actual_len != expected_len:
20
- error_msg = (
21
- f"\nAssertion Failed: Length mismatch\n"
22
- f" Expected: {expected_len}\n"
23
- f" Actual : {actual_len}\n"
24
- f" Data : {data}\n"
25
- f"Stack Trace:\n{_get_stack_trace()}"
26
- )
27
- raise AssertionError(error_msg)
28
-
29
- def ASSERT_EQUAL(actual: Any, expected: Any) -> None:
30
- if actual != expected:
31
- error_msg = (
32
- f"\nAssertion Failed: Value mismatch\n"
33
- f" Expected: {expected} ({type(expected).__name__})\n"
34
- f" Actual : {actual} ({type(actual).__name__})\n"
35
- f"Stack Trace:\n{_get_stack_trace()}"
36
- )
37
- raise AssertionError(error_msg)
@@ -1,5 +0,0 @@
1
- def main():
2
- print("Hello from nfcscript!")
3
-
4
- if __name__ == "__main__":
5
- main()
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- nfcscript = nfcscript.main:main
@@ -1 +0,0 @@
1
- CarrotRFIDTester
@@ -1 +0,0 @@
1
- nfcscript
File without changes
File without changes
File without changes