firmwareloop 0.0.13__tar.gz → 0.1.0__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 (35) hide show
  1. {firmwareloop-0.0.13 → firmwareloop-0.1.0}/PKG-INFO +4 -3
  2. {firmwareloop-0.0.13 → firmwareloop-0.1.0}/README.md +3 -2
  3. {firmwareloop-0.0.13 → firmwareloop-0.1.0}/firmwareloop.egg-info/PKG-INFO +4 -3
  4. {firmwareloop-0.0.13 → firmwareloop-0.1.0}/firmwareloop.egg-info/SOURCES.txt +5 -1
  5. {firmwareloop-0.0.13 → firmwareloop-0.1.0}/pyproject.toml +1 -1
  6. {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/__init__.py +1 -1
  7. {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/fw_mcp_server.py +92 -9
  8. firmwareloop-0.1.0/tools/lib/ndtdbg.py +222 -0
  9. {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/logic_capture.ps1 +17 -8
  10. firmwareloop-0.1.0/tools/ndtdbg_bridge.ps1 +232 -0
  11. firmwareloop-0.1.0/tools/ndtdbg_cli.py +193 -0
  12. {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/setup-agent-mcp.ps1 +277 -277
  13. firmwareloop-0.1.0/tools/sigrok_csv_to_fwloop.py +41 -0
  14. {firmwareloop-0.0.13 → firmwareloop-0.1.0}/LICENSE +0 -0
  15. {firmwareloop-0.0.13 → firmwareloop-0.1.0}/firmwareloop.egg-info/dependency_links.txt +0 -0
  16. {firmwareloop-0.0.13 → firmwareloop-0.1.0}/firmwareloop.egg-info/entry_points.txt +0 -0
  17. {firmwareloop-0.0.13 → firmwareloop-0.1.0}/firmwareloop.egg-info/requires.txt +0 -0
  18. {firmwareloop-0.0.13 → firmwareloop-0.1.0}/firmwareloop.egg-info/top_level.txt +0 -0
  19. {firmwareloop-0.0.13 → firmwareloop-0.1.0}/setup.cfg +0 -0
  20. {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/acceptance-scenario.ps1 +0 -0
  21. {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/build.ps1 +0 -0
  22. {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/can.ps1 +0 -0
  23. {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/check-qoder-mcp.ps1 +0 -0
  24. {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/common/build-backends.psm1 +0 -0
  25. {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/common/fw.psm1 +0 -0
  26. {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/common/uart_probe.py +0 -0
  27. {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/doctor.ps1 +0 -0
  28. {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/flash.ps1 +0 -0
  29. {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/instrument_cli.py +0 -0
  30. {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/lib/__init__.py +0 -0
  31. {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/lib/instruments.py +0 -0
  32. {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/logic_decode.ps1 +0 -0
  33. {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/reset.ps1 +0 -0
  34. {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/test-backends.ps1 +0 -0
  35. {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/test.ps1 +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: firmwareloop
3
- Version: 0.0.13
3
+ Version: 0.1.0
4
4
  Summary: AI Agent Firmware Development and Lab Automation Platform (Dual-Tier MCP Server)
5
5
  Author-email: WeilinZhang <19523330249@163.com>
6
6
  License: MIT
@@ -338,7 +338,7 @@ claude mcp remove --scope user agentic-hil
338
338
  ├── demo-firmware/ 示例固件(CMake;宿主编译模拟 MCU,闭环验证载体)
339
339
  ├── demo-make/ 示例固件(Make;构建测试载体)
340
340
  ├── docs/ DEPENDENCY_MATRIX / REUSE_PLAN / V0.0.2_GAP_VERIFICATION
341
- ├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.0.13)
341
+ ├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.1.0)
342
342
  ├── .mcp.example.json 双层 MCP 配置模板(firmwareloop + agentic-hil)
343
343
  ├── AGENTS.md 通用智能体规范(Antigravity / Qoder / Cursor 等)
344
344
  ├── CLAUDE.md Claude Code CLI 指南
@@ -348,7 +348,7 @@ claude mcp remove --scope user agentic-hil
348
348
 
349
349
  ---
350
350
 
351
- ## 能力验证状态(v0.0.13)
351
+ ## 能力验证状态(v0.1.0)
352
352
 
353
353
  > 状态定义:`Implemented`(已实现)/ `Simulator Validated`(模拟验证)/
354
354
  > `Real Hardware Validated`(真机验证)/ `Experimental` / `Not Implemented`
@@ -368,6 +368,7 @@ claude mcp remove --scope user agentic-hil
368
368
  | VISA 仪器(simulator) | Simulator Validated -> Real Hardware Validated 待仪器 |
369
369
  | Safety Policy(limits.yaml) | Implemented(simulator 校验)-> 外置权威配置 |
370
370
  | 自动修复闭环(M6 载体) | Simulator Validated -> Real Hardware Validated 待 DUT |
371
+ | NdtBox 数据采集/调试(fw_ndtdbg_status / fw_ndtdbg_measure, ChipSea Fxx IIC) | Implemented(无硬件 Fail-Closed 已验证)-> Real Hardware Validated 待 NdtBox |
371
372
 
372
373
  ---
373
374
 
@@ -310,7 +310,7 @@ claude mcp remove --scope user agentic-hil
310
310
  ├── demo-firmware/ 示例固件(CMake;宿主编译模拟 MCU,闭环验证载体)
311
311
  ├── demo-make/ 示例固件(Make;构建测试载体)
312
312
  ├── docs/ DEPENDENCY_MATRIX / REUSE_PLAN / V0.0.2_GAP_VERIFICATION
313
- ├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.0.13)
313
+ ├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.1.0)
314
314
  ├── .mcp.example.json 双层 MCP 配置模板(firmwareloop + agentic-hil)
315
315
  ├── AGENTS.md 通用智能体规范(Antigravity / Qoder / Cursor 等)
316
316
  ├── CLAUDE.md Claude Code CLI 指南
@@ -320,7 +320,7 @@ claude mcp remove --scope user agentic-hil
320
320
 
321
321
  ---
322
322
 
323
- ## 能力验证状态(v0.0.13)
323
+ ## 能力验证状态(v0.1.0)
324
324
 
325
325
  > 状态定义:`Implemented`(已实现)/ `Simulator Validated`(模拟验证)/
326
326
  > `Real Hardware Validated`(真机验证)/ `Experimental` / `Not Implemented`
@@ -340,6 +340,7 @@ claude mcp remove --scope user agentic-hil
340
340
  | VISA 仪器(simulator) | Simulator Validated -> Real Hardware Validated 待仪器 |
341
341
  | Safety Policy(limits.yaml) | Implemented(simulator 校验)-> 外置权威配置 |
342
342
  | 自动修复闭环(M6 载体) | Simulator Validated -> Real Hardware Validated 待 DUT |
343
+ | NdtBox 数据采集/调试(fw_ndtdbg_status / fw_ndtdbg_measure, ChipSea Fxx IIC) | Implemented(无硬件 Fail-Closed 已验证)-> Real Hardware Validated 待 NdtBox |
343
344
 
344
345
  ---
345
346
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: firmwareloop
3
- Version: 0.0.13
3
+ Version: 0.1.0
4
4
  Summary: AI Agent Firmware Development and Lab Automation Platform (Dual-Tier MCP Server)
5
5
  Author-email: WeilinZhang <19523330249@163.com>
6
6
  License: MIT
@@ -338,7 +338,7 @@ claude mcp remove --scope user agentic-hil
338
338
  ├── demo-firmware/ 示例固件(CMake;宿主编译模拟 MCU,闭环验证载体)
339
339
  ├── demo-make/ 示例固件(Make;构建测试载体)
340
340
  ├── docs/ DEPENDENCY_MATRIX / REUSE_PLAN / V0.0.2_GAP_VERIFICATION
341
- ├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.0.13)
341
+ ├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.1.0)
342
342
  ├── .mcp.example.json 双层 MCP 配置模板(firmwareloop + agentic-hil)
343
343
  ├── AGENTS.md 通用智能体规范(Antigravity / Qoder / Cursor 等)
344
344
  ├── CLAUDE.md Claude Code CLI 指南
@@ -348,7 +348,7 @@ claude mcp remove --scope user agentic-hil
348
348
 
349
349
  ---
350
350
 
351
- ## 能力验证状态(v0.0.13)
351
+ ## 能力验证状态(v0.1.0)
352
352
 
353
353
  > 状态定义:`Implemented`(已实现)/ `Simulator Validated`(模拟验证)/
354
354
  > `Real Hardware Validated`(真机验证)/ `Experimental` / `Not Implemented`
@@ -368,6 +368,7 @@ claude mcp remove --scope user agentic-hil
368
368
  | VISA 仪器(simulator) | Simulator Validated -> Real Hardware Validated 待仪器 |
369
369
  | Safety Policy(limits.yaml) | Implemented(simulator 校验)-> 外置权威配置 |
370
370
  | 自动修复闭环(M6 载体) | Simulator Validated -> Real Hardware Validated 待 DUT |
371
+ | NdtBox 数据采集/调试(fw_ndtdbg_status / fw_ndtdbg_measure, ChipSea Fxx IIC) | Implemented(无硬件 Fail-Closed 已验证)-> Real Hardware Validated 待 NdtBox |
371
372
 
372
373
  ---
373
374
 
@@ -18,12 +18,16 @@ tools/fw_mcp_server.py
18
18
  tools/instrument_cli.py
19
19
  tools/logic_capture.ps1
20
20
  tools/logic_decode.ps1
21
+ tools/ndtdbg_bridge.ps1
22
+ tools/ndtdbg_cli.py
21
23
  tools/reset.ps1
22
24
  tools/setup-agent-mcp.ps1
25
+ tools/sigrok_csv_to_fwloop.py
23
26
  tools/test-backends.ps1
24
27
  tools/test.ps1
25
28
  tools/common/build-backends.psm1
26
29
  tools/common/fw.psm1
27
30
  tools/common/uart_probe.py
28
31
  tools/lib/__init__.py
29
- tools/lib/instruments.py
32
+ tools/lib/instruments.py
33
+ tools/lib/ndtdbg.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "firmwareloop"
7
- version = "0.0.13"
7
+ version = "0.1.0"
8
8
  description = "AI Agent Firmware Development and Lab Automation Platform (Dual-Tier MCP Server)"
9
9
  readme = "README.md"
10
10
  authors = [
@@ -2,4 +2,4 @@
2
2
  FirmwareLoop - AI Agent Firmware Development and Lab Automation Platform.
3
3
  """
4
4
 
5
- __version__ = "0.0.13"
5
+ __version__ = "0.1.0"
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- fw_mcp_server.py - FirmwareLoop High-Level Workflow & Device MCP Server (v0.0.13).
3
+ fw_mcp_server.py - FirmwareLoop High-Level Workflow & Device MCP Server (v0.1.0).
4
4
 
5
5
  Exposes high-level firmware engineering and hardware management tools to AI Coding
6
6
  Agents (Antigravity CLI, Claude Code CLI, Qoder IDE, Cursor) via the Model Context
@@ -14,6 +14,8 @@ Tools provided:
14
14
  - fw_run_hil_test: Hardware-in-the-loop pytest suite (test.ps1)
15
15
  - fw_acceptance_scenario: End-to-end acceptance scenario (acceptance-scenario.ps1)
16
16
  - fw_measure: Controlled PyVISA/SCPI instrument measurement (instrument_cli.py)
17
+ - fw_ndtdbg_status: NdtBox (USB->IIC) connectivity & firmware version probe (ndtdbg_cli.py)
18
+ - fw_ndtdbg_measure: NdtBox pressure-sensor data stream polling over IIC (ndtdbg_cli.py)
17
19
  - fw_logic_capture: Logic analyzer protocol capture (logic_capture.ps1)
18
20
  - fw_logic_decode: Logic analyzer protocol decoding & assertion (logic_decode.ps1)
19
21
  - fw_get_evidence: Read structured audit run evidence (artifacts/runs/)
@@ -240,6 +242,36 @@ def handle_fw_measure(args: Dict[str, Any]) -> Dict[str, Any]:
240
242
  return res
241
243
 
242
244
 
245
+ def handle_fw_ndtdbg_status(args: Dict[str, Any]) -> Dict[str, Any]:
246
+ cli_script = os.path.join(REPO_ROOT, "tools", "ndtdbg_cli.py")
247
+ cmd = [PYTHON_EXE, cli_script, "status"]
248
+ if args.get("dll_dir"):
249
+ cmd.extend(["--dll-dir", args["dll_dir"]])
250
+ if args.get("slave_addr"):
251
+ cmd.extend(["--slave-addr", str(args["slave_addr"])])
252
+ res = run_process(cmd, timeout=60)
253
+ if res.get("data"):
254
+ return res["data"]
255
+ return res
256
+
257
+
258
+ def handle_fw_ndtdbg_measure(args: Dict[str, Any]) -> Dict[str, Any]:
259
+ cli_script = os.path.join(REPO_ROOT, "tools", "ndtdbg_cli.py")
260
+ duration = int(args.get("duration_seconds", 2))
261
+ cmd = [PYTHON_EXE, cli_script, "poll", "--duration-ms", str(duration * 1000),
262
+ "--interval-ms", str(args.get("interval_ms", 10))]
263
+ if args.get("full_output"):
264
+ cmd.append("--full")
265
+ if args.get("dll_dir"):
266
+ cmd.extend(["--dll-dir", args["dll_dir"]])
267
+ if args.get("slave_addr"):
268
+ cmd.extend(["--slave-addr", str(args["slave_addr"])])
269
+ res = run_process(cmd, timeout=60 + duration)
270
+ if res.get("data"):
271
+ return res["data"]
272
+ return res
273
+
274
+
243
275
  def handle_fw_logic_capture(args: Dict[str, Any]) -> Dict[str, Any]:
244
276
  script = os.path.join(REPO_ROOT, "tools", "logic_capture.ps1")
245
277
  cmd = [PWSH_EXE, "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", script, "-Json"]
@@ -253,11 +285,11 @@ def handle_fw_logic_capture(args: Dict[str, Any]) -> Dict[str, Any]:
253
285
 
254
286
  duration_ms = args.get("duration_ms")
255
287
  if duration_ms:
256
- cmd.extend(["-DurationMs", str(duration_ms)])
288
+ cmd.extend(["-DurationSec", str(float(duration_ms) / 1000.0)])
257
289
 
258
290
  output_file = args.get("output_file")
259
291
  if output_file:
260
- cmd.extend(["-OutputFile", output_file])
292
+ cmd.extend(["-OutDir", output_file])
261
293
 
262
294
  res = run_process(cmd, timeout=30)
263
295
  if res.get("data"):
@@ -1023,6 +1055,57 @@ TOOLS_REGISTRY = {
1023
1055
  },
1024
1056
  "handler": handle_fw_measure,
1025
1057
  },
1058
+ "fw_ndtdbg_status": {
1059
+ "description": "Probe NdtBox (USB->IIC debug box, ChipSea Fxx) status: device open, firmware version, module connectivity. Fail-closed (PROBE_NOT_FOUND / TARGET_MISMATCH) when hardware absent or no IIC response.",
1060
+ "inputSchema": {
1061
+ "type": "object",
1062
+ "properties": {
1063
+ "dll_dir": {
1064
+ "type": "string",
1065
+ "description": "NdtDebugTool lib directory containing USBIOX.DLL; default from lab.yaml ndtdbg.dll_dir",
1066
+ },
1067
+ "slave_addr": {
1068
+ "type": "string",
1069
+ "description": "IIC slave address (e.g. 0xA0); default from lab.yaml ndtdbg.slave_addr",
1070
+ },
1071
+ },
1072
+ "additionalProperties": False,
1073
+ },
1074
+ "handler": handle_fw_ndtdbg_status,
1075
+ },
1076
+ "fw_ndtdbg_measure": {
1077
+ "description": "Poll NdtBox IIC debug data stream (ChipSea Fxx) for N seconds: frame count, checksum errors, channel samples. Read-only; firmware upgrade is a separate future capability.",
1078
+ "inputSchema": {
1079
+ "type": "object",
1080
+ "properties": {
1081
+ "duration_seconds": {
1082
+ "type": "integer",
1083
+ "description": "Poll duration in seconds",
1084
+ "default": 2,
1085
+ },
1086
+ "interval_ms": {
1087
+ "type": "integer",
1088
+ "description": "Sampling interval in milliseconds",
1089
+ "default": 10,
1090
+ },
1091
+ "full_output": {
1092
+ "type": "boolean",
1093
+ "description": "Return every frame (may be large); default keeps a frame sample plus totals",
1094
+ "default": False,
1095
+ },
1096
+ "dll_dir": {
1097
+ "type": "string",
1098
+ "description": "NdtDebugTool lib directory containing USBIOX.DLL; default from lab.yaml ndtdbg.dll_dir",
1099
+ },
1100
+ "slave_addr": {
1101
+ "type": "string",
1102
+ "description": "IIC slave address (e.g. 0xA0); default from lab.yaml ndtdbg.slave_addr",
1103
+ },
1104
+ },
1105
+ "additionalProperties": False,
1106
+ },
1107
+ "handler": handle_fw_ndtdbg_measure,
1108
+ },
1026
1109
  "fw_logic_capture": {
1027
1110
  "description": "Capture digital protocol waveforms (I2C, SPI, UART) via Saleae / sigrok or deterministic simulator.",
1028
1111
  "inputSchema": {
@@ -1160,7 +1243,7 @@ def process_request(req: Dict[str, Any]) -> Optional[Dict[str, Any]]:
1160
1243
  },
1161
1244
  "serverInfo": {
1162
1245
  "name": "firmwareloop",
1163
- "version": "0.0.13"
1246
+ "version": "0.1.0"
1164
1247
  }
1165
1248
  }
1166
1249
  }
@@ -1252,7 +1335,7 @@ def process_request(req: Dict[str, Any]) -> Optional[Dict[str, Any]]:
1252
1335
  def handle_cli_update() -> int:
1253
1336
  """Handle `firmwareloop update` command."""
1254
1337
  print("============================================================")
1255
- print(" FirmwareLoop Auto-Updater (v0.0.13)")
1338
+ print(" FirmwareLoop Auto-Updater (v0.1.0)")
1256
1339
  print("============================================================")
1257
1340
 
1258
1341
  is_git_repo = os.path.exists(os.path.join(REPO_ROOT, ".git"))
@@ -1395,7 +1478,7 @@ def configure_qoder_user_mcp(user_home: str) -> List[Dict[str, Any]]:
1395
1478
  def handle_cli_setup() -> int:
1396
1479
  """Handle `firmwareloop setup` command: simultaneously configure Antigravity CLI, Claude Code CLI, and Qoder IDE."""
1397
1480
  print("============================================================")
1398
- print(" FirmwareLoop Multi-Agent Global Setup Helper (v0.0.13)")
1481
+ print(" FirmwareLoop Multi-Agent Global Setup Helper (v0.1.0)")
1399
1482
  print("============================================================")
1400
1483
  print("")
1401
1484
 
@@ -1500,7 +1583,7 @@ def handle_cli_setup() -> int:
1500
1583
  def handle_cli_init() -> int:
1501
1584
  """Handle `firmwareloop init` command."""
1502
1585
  print("============================================================")
1503
- print(" FirmwareLoop Multi-Agent Project Initializer (v0.0.13)")
1586
+ print(" FirmwareLoop Multi-Agent Project Initializer (v0.1.0)")
1504
1587
  print("============================================================")
1505
1588
  cwd = os.getcwd()
1506
1589
  print(f"[*] Initializing multi-agent guidelines & bench config in:\n {cwd}\n")
@@ -1521,7 +1604,7 @@ def handle_cli_init() -> int:
1521
1604
 
1522
1605
 
1523
1606
  def print_cli_help() -> None:
1524
- print("""FirmwareLoop (fwloop) — AI Agent Firmware Engineering & Lab Automation Platform (v0.0.13)
1607
+ print("""FirmwareLoop (fwloop) — AI Agent Firmware Engineering & Lab Automation Platform (v0.1.0)
1525
1608
 
1526
1609
  Usage:
1527
1610
  fwloop [command] (or: firmwareloop [command])
@@ -1552,7 +1635,7 @@ def main() -> None:
1552
1635
  elif cmd in ["setup", "register"]:
1553
1636
  sys.exit(handle_cli_setup())
1554
1637
  elif cmd in ["version", "-v", "--version"]:
1555
- print("FirmwareLoop v0.0.13")
1638
+ print("FirmwareLoop v0.1.0")
1556
1639
  sys.exit(0)
1557
1640
  elif cmd in ["help", "-h", "--help"]:
1558
1641
  print_cli_help()
@@ -0,0 +1,222 @@
1
+ """NdtBox (USB->IIC) 协议层: 芯海 Fxx 芯片的 IIC 寄存器帧与校验。
2
+
3
+ 底层 IO 通过 32 位 PowerShell 桥 (tools/ndtdbg_bridge.ps1) 完成——USBIOX.DLL 为 x86-32,
4
+ 项目 venv 为 64 位 Python, 无法 ctypes 直调; 桥由 SysWOW64 powershell.exe (32 位) 执行,
5
+ 故本模块只做纯协议逻辑与桥返回解析, 全部平台无关、可单测。
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import os
12
+ import subprocess
13
+ import sys
14
+
15
+ # 32 位 Windows PowerShell 5.1 (x86), 与 x86 DLL 位宽匹配
16
+ PS32 = r"C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe"
17
+
18
+ _REWRITE_TIMES = 3 # 对齐 ChipSea.cs REWRITE_TIMES
19
+
20
+
21
+ class NdtBoxError(Exception):
22
+ """携带项目错误类别白名单之一的异常 (Fail-Closed, 不伪造成功)。"""
23
+
24
+ def __init__(self, error_class: str, message: str, detail: str | None = None):
25
+ super().__init__(message)
26
+ self.error_class = error_class
27
+ self.message = message
28
+ self.detail = detail
29
+
30
+ def to_dict(self) -> dict:
31
+ d = {"ok": False, "error_class": self.error_class, "error": self.message}
32
+ if self.detail:
33
+ d["detail"] = self.detail
34
+ return d
35
+
36
+
37
+ # ---------------------------------------------------------------------------
38
+ # 纯函数 (协议帧与校验, 平台无关, 直接对应反编译源码)
39
+ # ---------------------------------------------------------------------------
40
+
41
+ def checksum_two_bytes(buf: bytes) -> int:
42
+ """CheckSumTwoBytes: 前 length/2 个 LE int16 之和取负 (无符号 16 位)。"""
43
+ total = 0
44
+ for i in range(0, len(buf) - (len(buf) % 2), 2):
45
+ total += int.from_bytes(buf[i:i + 2], "little")
46
+ return (-total) & 0xFFFF
47
+
48
+
49
+ def build_frame(slave_addr: int, payload: bytes) -> bytes:
50
+ """IIC 事务帧: [slave_addr] + payload (对应 ChipSea.cs WriteReg/ReadReg 的 array[0]=addr)。"""
51
+ return bytes([slave_addr & 0xFF]) + payload
52
+
53
+
54
+ def parse_getdata(data: bytes) -> list[int] | None:
55
+ """解析 0xEE 数据帧: 末 2 字节负和校验, 返回 LE int16 通道数组 (C# (short) 有符号); 校验失败返回 None。"""
56
+ if len(data) < 4 or len(data) % 2 != 0:
57
+ return None
58
+ expected = int.from_bytes(data[-2:], "little")
59
+ if expected != checksum_two_bytes(data[:-2]):
60
+ return None
61
+ channels = []
62
+ for i in range(len(data[:-2]) // 2):
63
+ raw = int.from_bytes(data[i * 2:i * 2 + 2], "little")
64
+ channels.append(raw - 0x10000 if raw >= 0x8000 else raw)
65
+ return channels
66
+
67
+
68
+ def parse_version(data: bytes) -> str:
69
+ """0x10 固件版本: 4 字节 -> v%d.%d.%d.%d (ChipSea.cs ReadFwVersion)。"""
70
+ if len(data) < 4:
71
+ raise NdtBoxError("TARGET_MISMATCH", "固件版本响应不足 4 字节", f"len={len(data)}")
72
+ return "v{}.{}.{}.{}".format(*data[:4])
73
+
74
+
75
+ def parse_module_connect(data: bytes) -> int:
76
+ """0x29 模组通断: 低 16 位 LE (ChipSea.cs ReadModuleConnect)。"""
77
+ if len(data) < 2:
78
+ raise NdtBoxError("TARGET_MISMATCH", "模组通断响应不足 2 字节", f"len={len(data)}")
79
+ return int.from_bytes(data[:2], "little")
80
+
81
+
82
+ def strip_nfw(file_bytes: bytes, header_len: int = 256) -> bytes:
83
+ """.nfw 固件: 去 256 字节文件头 (ChipSea.cs LoadFile)。"""
84
+ if len(file_bytes) <= header_len:
85
+ raise NdtBoxError("CONFIG_ERROR", ".nfw 文件不足去头长度", f"len={len(file_bytes)}")
86
+ return file_bytes[header_len:]
87
+
88
+
89
+ def pad_bin_128(file_bytes: bytes) -> bytes:
90
+ """.bin 固件: 128 字节对齐, 尾部 0xFF 填充 (ChipSea.cs LoadFile)。"""
91
+ pad = (128 - len(file_bytes) % 128) % 128
92
+ return file_bytes + b"\xFF" * pad
93
+
94
+
95
+ # ---------------------------------------------------------------------------
96
+ # 桥调用层
97
+ # ---------------------------------------------------------------------------
98
+
99
+ class NdtBoxIo:
100
+ """通过 32 位 PowerShell 桥访问 NdtBox 的 IIC 通道 (芯片无关原语)。"""
101
+
102
+ def __init__(self, dll_dir: str, slave_addr: int = 0xA0, speed: int = 1,
103
+ bridge_ps1: str | None = None, timeout_ms: int = 60000):
104
+ self.dll_dir = dll_dir
105
+ self.slave_addr = slave_addr & 0xFF
106
+ self.speed = speed
107
+ self.timeout_ms = timeout_ms
108
+ if bridge_ps1 is None:
109
+ bridge_ps1 = os.path.join(os.path.dirname(__file__), "..", "ndtdbg_bridge.ps1")
110
+ self.bridge_ps1 = os.path.abspath(bridge_ps1)
111
+
112
+ def _bridge(self, action: str, **params) -> dict:
113
+ if not os.path.exists(PS32):
114
+ raise NdtBoxError("CONFIG_ERROR", f"32 位 PowerShell 不存在: {PS32}")
115
+ if not os.path.exists(self.bridge_ps1):
116
+ raise NdtBoxError("CONFIG_ERROR", f"桥脚本不存在: {self.bridge_ps1}")
117
+ args = [PS32, "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass",
118
+ "-File", self.bridge_ps1, "-Action", action,
119
+ "-DllDir", self.dll_dir, "-SlaveAddr", str(self.slave_addr),
120
+ "-Speed", str(self.speed)]
121
+ for k, v in params.items():
122
+ args.append(f"-{k}")
123
+ args.append(str(v))
124
+ try:
125
+ proc = subprocess.run(args, capture_output=True, timeout=self.timeout_ms / 1000.0,
126
+ check=False)
127
+ except subprocess.TimeoutExpired:
128
+ raise NdtBoxError("INSTRUMENT_TIMEOUT", f"桥调用超时 >{self.timeout_ms}ms", action)
129
+ out = (proc.stdout or b"").decode("utf-8", errors="replace").strip()
130
+ if not out:
131
+ tail = (proc.stderr or b"").decode("utf-8", errors="replace").strip()[-400:]
132
+ raise NdtBoxError("UNKNOWN_ERROR", f"桥无输出 (exit={proc.returncode})", tail or None)
133
+ try:
134
+ payload = json.loads(out.splitlines()[-1])
135
+ except json.JSONDecodeError as exc:
136
+ raise NdtBoxError("UNKNOWN_ERROR", "桥输出非 JSON", out[-400:]) from exc
137
+ if not payload.get("ok"):
138
+ raise NdtBoxError(payload.get("error_class", "UNKNOWN_ERROR"),
139
+ payload.get("error", "桥返回失败"),
140
+ payload.get("detail"))
141
+ return payload.get("data", {})
142
+
143
+ # -- 芯片无关原语 (对齐 IIC.cs / I2CProxy.cs) --
144
+
145
+ def ping(self) -> dict:
146
+ return self._bridge("open")
147
+
148
+ def read_reg(self, reg: int, length: int) -> bytes:
149
+ if length < 1 or length > 256:
150
+ raise NdtBoxError("CONFIG_ERROR", "读取长度越界 (1..256)", str(length))
151
+ data = self._bridge("read", RegAddr=reg, Len=length)
152
+ return bytes.fromhex(data["hex"].replace(" ", ""))
153
+
154
+ def write_reg(self, reg: int, data: bytes) -> None:
155
+ if len(data) > 128:
156
+ raise NdtBoxError("CONFIG_ERROR", "写入数据过长 (<=128)", str(len(data)))
157
+ self._bridge("write", RegAddr=reg, Data=data.hex())
158
+
159
+ def close(self) -> dict:
160
+ return self._bridge("close")
161
+
162
+
163
+ class ChipSeaFxx:
164
+ """芯海 Fxx (0xA0) 协议: 对齐 STDataCollection.IICModule/ChipSea.cs。"""
165
+
166
+ def __init__(self, io: NdtBoxIo, listener_log=None):
167
+ self._io = io
168
+ self._log = listener_log
169
+
170
+ def _logline(self, msg: str) -> None:
171
+ if self._log:
172
+ self._log.append(msg)
173
+
174
+ def _read_reg_retry(self, reg: int, length: int, min_ms_retry: int = 100) -> bytes:
175
+ last = None
176
+ for attempt in range(_REWRITE_TIMES + 1):
177
+ try:
178
+ last = self._io.read_reg(reg, length)
179
+ if any(b != 0 for b in last):
180
+ return last
181
+ except NdtBoxError:
182
+ raise
183
+ import time
184
+ time.sleep(min_ms_retry / 1000.0)
185
+ raise NdtBoxError("TARGET_MISMATCH",
186
+ f"读寄存器 0x{reg:02X} 超时/全 0 (重试{_REWRITE_TIMES}次)", None)
187
+
188
+ def read_fw_version(self) -> str:
189
+ data = self._read_reg_retry(0x10, 4)
190
+ return parse_version(data)
191
+
192
+ def read_module_connect(self) -> int:
193
+ data = self._io.read_reg(0x29, 4)
194
+ return parse_module_connect(data)
195
+
196
+ def read_afe_gain(self) -> bytes:
197
+ return self._io.read_reg(0x16, 2)
198
+
199
+ def read_adc_uv_coef(self) -> int:
200
+ data = self._io.read_reg(0x1B, 3)
201
+ return int.from_bytes(data, "little")
202
+
203
+ def read_dac_mv_coef(self) -> int:
204
+ data = self._io.read_reg(0x1C, 2)
205
+ return int.from_bytes(data, "little")
206
+
207
+ def read_offset(self, ch_num: int) -> list[int]:
208
+ data = self._io.read_reg(0x17, ch_num * 2)
209
+ return [int.from_bytes(data[i * 2:i * 2 + 2], "little") for i in range(ch_num)]
210
+
211
+ # -- 调试数据流 (对齐 ReadModuleConnectDynamic + GetData) --
212
+
213
+ def poll(self, duration_ms: int, interval_ms: int = 10, ready_timeout_ms: int = 2000) -> dict:
214
+ """连续轮询一帧或多帧, 返回 {frame_count, checksum_errors, ready_timeouts, frames}。
215
+
216
+ frames: [{"t": epoch_ms, "ch": [int16...]}, ...]——由桥在单个 32 位进程内完成循环,
217
+ 避免每帧一次进程开销 (GUI 原 100Hz 采样)。
218
+ """
219
+ if duration_ms <= 0:
220
+ raise NdtBoxError("CONFIG_ERROR", "poll 时长必须 > 0", str(duration_ms))
221
+ return self._io._bridge("poll", DurationMs=int(duration_ms), IntervalMs=int(interval_ms),
222
+ ReadyTimeoutMs=int(ready_timeout_ms))
@@ -221,24 +221,33 @@ if ($Backend -eq 'simulator') {
221
221
 
222
222
  # ---- sigrok backend ------------------------------------------------------------
223
223
  else {
224
- $rawFile = Join-Path $OutDir 'capture.sr'
224
+ # sigrok CSV 无时间列且表头为通道名 -> 经 sigrok_csv_to_fwloop.py 转成
225
+ # fwloop 解码器格式 (time_s,D0..D7), 使 capture->decode 全链闭关
226
+ $rawFile = Join-Path $OutDir 'capture.csv'
227
+ $rawSigrok = Join-Path $OutDir 'sigrok-raw.csv'
225
228
  $probeArgs = @(
226
- "--driver=auto",
227
229
  "--config", "samplerate=$SampleRateHz",
228
230
  "--channels", "0-7",
229
- "--output-format", "binary",
230
- "--output-file", $rawFile,
231
- "--time", "$([int]([math]::Ceiling($DurationSec * 1000)))ms",
232
- "$Protocol"
231
+ "--output-format", "csv",
232
+ "--output-file", $rawSigrok,
233
+ "--time", "$([int]([math]::Ceiling($DurationSec * 1000)))ms"
233
234
  )
234
235
  $res = Invoke-FwProcess -FilePath $sigrok -Arguments $probeArgs -WorkingDirectory $repoRoot -TimeoutMs 120000
235
236
  if ($res.timed_out) {
236
- Exit-WithError -Class 'LOGIC_CAPTURE_ERROR' -Message 'sigrok-cli capture timed out.' -Detail $rawFile
237
+ Exit-WithError -Class 'LOGIC_CAPTURE_ERROR' -Message 'sigrok-cli capture timed out.' -Detail $rawSigrok
237
238
  }
238
- if ($res.exit_code -ne 0 -or -not (Test-Path -LiteralPath $rawFile)) {
239
+ if ($res.exit_code -ne 0 -or -not (Test-Path -LiteralPath $rawSigrok)) {
239
240
  Exit-WithError -Class 'LOGIC_CAPTURE_ERROR' -Message 'sigrok-cli capture failed.' -Detail ($res.stdout + $res.stderr)
240
241
  }
242
+ $convScript = Join-Path $repoRoot 'tools\sigrok_csv_to_fwloop.py'
243
+ $py = Resolve-FwPython
244
+ $convRes = Invoke-FwProcess -FilePath $py -Arguments @($convScript, $rawSigrok, $rawFile, $SampleRateHz) `
245
+ -WorkingDirectory $repoRoot -TimeoutMs 120000
246
+ if ($convRes.exit_code -ne 0 -or -not (Test-Path -LiteralPath $rawFile)) {
247
+ Exit-WithError -Class 'LOGIC_CAPTURE_ERROR' -Message 'sigrok CSV conversion failed.' -Detail ($convRes.stdout + $convRes.stderr)
248
+ }
241
249
  $meta.files += (Resolve-Path -LiteralPath $rawFile).Path
250
+ $meta.files += (Resolve-Path -LiteralPath $rawSigrok).Path
242
251
  }
243
252
 
244
253
  $metaFile = Join-Path $OutDir 'capture.json'