firmwareloop 0.0.13__tar.gz → 0.1.1__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.1}/PKG-INFO +4 -3
  2. {firmwareloop-0.0.13 → firmwareloop-0.1.1}/README.md +3 -2
  3. {firmwareloop-0.0.13 → firmwareloop-0.1.1}/firmwareloop.egg-info/PKG-INFO +4 -3
  4. {firmwareloop-0.0.13 → firmwareloop-0.1.1}/firmwareloop.egg-info/SOURCES.txt +5 -1
  5. {firmwareloop-0.0.13 → firmwareloop-0.1.1}/pyproject.toml +1 -1
  6. {firmwareloop-0.0.13 → firmwareloop-0.1.1}/tools/__init__.py +1 -1
  7. {firmwareloop-0.0.13 → firmwareloop-0.1.1}/tools/fw_mcp_server.py +185 -9
  8. firmwareloop-0.1.1/tools/lib/ndtdbg.py +222 -0
  9. {firmwareloop-0.0.13 → firmwareloop-0.1.1}/tools/logic_capture.ps1 +17 -8
  10. firmwareloop-0.1.1/tools/ndtdbg_bridge.ps1 +232 -0
  11. firmwareloop-0.1.1/tools/ndtdbg_cli.py +193 -0
  12. {firmwareloop-0.0.13 → firmwareloop-0.1.1}/tools/setup-agent-mcp.ps1 +277 -277
  13. firmwareloop-0.1.1/tools/sigrok_csv_to_fwloop.py +41 -0
  14. {firmwareloop-0.0.13 → firmwareloop-0.1.1}/LICENSE +0 -0
  15. {firmwareloop-0.0.13 → firmwareloop-0.1.1}/firmwareloop.egg-info/dependency_links.txt +0 -0
  16. {firmwareloop-0.0.13 → firmwareloop-0.1.1}/firmwareloop.egg-info/entry_points.txt +0 -0
  17. {firmwareloop-0.0.13 → firmwareloop-0.1.1}/firmwareloop.egg-info/requires.txt +0 -0
  18. {firmwareloop-0.0.13 → firmwareloop-0.1.1}/firmwareloop.egg-info/top_level.txt +0 -0
  19. {firmwareloop-0.0.13 → firmwareloop-0.1.1}/setup.cfg +0 -0
  20. {firmwareloop-0.0.13 → firmwareloop-0.1.1}/tools/acceptance-scenario.ps1 +0 -0
  21. {firmwareloop-0.0.13 → firmwareloop-0.1.1}/tools/build.ps1 +0 -0
  22. {firmwareloop-0.0.13 → firmwareloop-0.1.1}/tools/can.ps1 +0 -0
  23. {firmwareloop-0.0.13 → firmwareloop-0.1.1}/tools/check-qoder-mcp.ps1 +0 -0
  24. {firmwareloop-0.0.13 → firmwareloop-0.1.1}/tools/common/build-backends.psm1 +0 -0
  25. {firmwareloop-0.0.13 → firmwareloop-0.1.1}/tools/common/fw.psm1 +0 -0
  26. {firmwareloop-0.0.13 → firmwareloop-0.1.1}/tools/common/uart_probe.py +0 -0
  27. {firmwareloop-0.0.13 → firmwareloop-0.1.1}/tools/doctor.ps1 +0 -0
  28. {firmwareloop-0.0.13 → firmwareloop-0.1.1}/tools/flash.ps1 +0 -0
  29. {firmwareloop-0.0.13 → firmwareloop-0.1.1}/tools/instrument_cli.py +0 -0
  30. {firmwareloop-0.0.13 → firmwareloop-0.1.1}/tools/lib/__init__.py +0 -0
  31. {firmwareloop-0.0.13 → firmwareloop-0.1.1}/tools/lib/instruments.py +0 -0
  32. {firmwareloop-0.0.13 → firmwareloop-0.1.1}/tools/logic_decode.ps1 +0 -0
  33. {firmwareloop-0.0.13 → firmwareloop-0.1.1}/tools/reset.ps1 +0 -0
  34. {firmwareloop-0.0.13 → firmwareloop-0.1.1}/tools/test-backends.ps1 +0 -0
  35. {firmwareloop-0.0.13 → firmwareloop-0.1.1}/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.1
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.1)
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.1)
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.1)
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.1)
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.1
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.1)
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.1)
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.1"
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.1"
@@ -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.1).
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,10 @@ 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)
19
+ - fw_ndtdbg_read_reg: NdtBox IIC register read (ndtdbg_cli.py)
20
+ - fw_ndtdbg_write_reg: NdtBox IIC register write, double-gated (ndtdbg_cli.py)
17
21
  - fw_logic_capture: Logic analyzer protocol capture (logic_capture.ps1)
18
22
  - fw_logic_decode: Logic analyzer protocol decoding & assertion (logic_decode.ps1)
19
23
  - fw_get_evidence: Read structured audit run evidence (artifacts/runs/)
@@ -240,6 +244,67 @@ def handle_fw_measure(args: Dict[str, Any]) -> Dict[str, Any]:
240
244
  return res
241
245
 
242
246
 
247
+ def handle_fw_ndtdbg_status(args: Dict[str, Any]) -> Dict[str, Any]:
248
+ cli_script = os.path.join(REPO_ROOT, "tools", "ndtdbg_cli.py")
249
+ cmd = [PYTHON_EXE, cli_script, "status"]
250
+ if args.get("dll_dir"):
251
+ cmd.extend(["--dll-dir", args["dll_dir"]])
252
+ if args.get("slave_addr"):
253
+ cmd.extend(["--slave-addr", str(args["slave_addr"])])
254
+ res = run_process(cmd, timeout=60)
255
+ if res.get("data"):
256
+ return res["data"]
257
+ return res
258
+
259
+
260
+ def handle_fw_ndtdbg_measure(args: Dict[str, Any]) -> Dict[str, Any]:
261
+ cli_script = os.path.join(REPO_ROOT, "tools", "ndtdbg_cli.py")
262
+ duration = int(args.get("duration_seconds", 2))
263
+ cmd = [PYTHON_EXE, cli_script, "poll", "--duration-ms", str(duration * 1000),
264
+ "--interval-ms", str(args.get("interval_ms", 10))]
265
+ if args.get("full_output"):
266
+ cmd.append("--full")
267
+ if args.get("dll_dir"):
268
+ cmd.extend(["--dll-dir", args["dll_dir"]])
269
+ if args.get("slave_addr"):
270
+ cmd.extend(["--slave-addr", str(args["slave_addr"])])
271
+ res = run_process(cmd, timeout=60 + duration)
272
+ if res.get("data"):
273
+ return res["data"]
274
+ return res
275
+
276
+
277
+ def _ndtdbg_reg_args(args: Dict[str, Any], cmd: List[str]) -> None:
278
+ if args.get("dll_dir"):
279
+ cmd.extend(["--dll-dir", args["dll_dir"]])
280
+ if args.get("slave_addr"):
281
+ cmd.extend(["--slave-addr", str(args["slave_addr"])])
282
+
283
+
284
+ def handle_fw_ndtdbg_read_reg(args: Dict[str, Any]) -> Dict[str, Any]:
285
+ cli_script = os.path.join(REPO_ROOT, "tools", "ndtdbg_cli.py")
286
+ cmd = [PYTHON_EXE, cli_script, "read-reg", "--addr", str(args.get("reg", "0x10")),
287
+ "--len", str(args.get("len", 1))]
288
+ _ndtdbg_reg_args(args, cmd)
289
+ res = run_process(cmd, timeout=60)
290
+ if res.get("data"):
291
+ return res["data"]
292
+ return res
293
+
294
+
295
+ def handle_fw_ndtdbg_write_reg(args: Dict[str, Any]) -> Dict[str, Any]:
296
+ cli_script = os.path.join(REPO_ROOT, "tools", "ndtdbg_cli.py")
297
+ cmd = [PYTHON_EXE, cli_script, "write-reg", "--addr", str(args.get("reg")),
298
+ "--data", str(args.get("data", ""))]
299
+ if args.get("confirm"):
300
+ cmd.append("--confirm")
301
+ _ndtdbg_reg_args(args, cmd)
302
+ res = run_process(cmd, timeout=60)
303
+ if res.get("data"):
304
+ return res["data"]
305
+ return res
306
+
307
+
243
308
  def handle_fw_logic_capture(args: Dict[str, Any]) -> Dict[str, Any]:
244
309
  script = os.path.join(REPO_ROOT, "tools", "logic_capture.ps1")
245
310
  cmd = [PWSH_EXE, "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", script, "-Json"]
@@ -253,11 +318,11 @@ def handle_fw_logic_capture(args: Dict[str, Any]) -> Dict[str, Any]:
253
318
 
254
319
  duration_ms = args.get("duration_ms")
255
320
  if duration_ms:
256
- cmd.extend(["-DurationMs", str(duration_ms)])
321
+ cmd.extend(["-DurationSec", str(float(duration_ms) / 1000.0)])
257
322
 
258
323
  output_file = args.get("output_file")
259
324
  if output_file:
260
- cmd.extend(["-OutputFile", output_file])
325
+ cmd.extend(["-OutDir", output_file])
261
326
 
262
327
  res = run_process(cmd, timeout=30)
263
328
  if res.get("data"):
@@ -1023,6 +1088,117 @@ TOOLS_REGISTRY = {
1023
1088
  },
1024
1089
  "handler": handle_fw_measure,
1025
1090
  },
1091
+ "fw_ndtdbg_status": {
1092
+ "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.",
1093
+ "inputSchema": {
1094
+ "type": "object",
1095
+ "properties": {
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_status,
1108
+ },
1109
+ "fw_ndtdbg_measure": {
1110
+ "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.",
1111
+ "inputSchema": {
1112
+ "type": "object",
1113
+ "properties": {
1114
+ "duration_seconds": {
1115
+ "type": "integer",
1116
+ "description": "Poll duration in seconds",
1117
+ "default": 2,
1118
+ },
1119
+ "interval_ms": {
1120
+ "type": "integer",
1121
+ "description": "Sampling interval in milliseconds",
1122
+ "default": 10,
1123
+ },
1124
+ "full_output": {
1125
+ "type": "boolean",
1126
+ "description": "Return every frame (may be large); default keeps a frame sample plus totals",
1127
+ "default": False,
1128
+ },
1129
+ "dll_dir": {
1130
+ "type": "string",
1131
+ "description": "NdtDebugTool lib directory containing USBIOX.DLL; default from lab.yaml ndtdbg.dll_dir",
1132
+ },
1133
+ "slave_addr": {
1134
+ "type": "string",
1135
+ "description": "IIC slave address (e.g. 0xA0); default from lab.yaml ndtdbg.slave_addr",
1136
+ },
1137
+ },
1138
+ "additionalProperties": False,
1139
+ },
1140
+ "handler": handle_fw_ndtdbg_measure,
1141
+ },
1142
+ "fw_ndtdbg_read_reg": {
1143
+ "description": "Read IIC register bytes from the NdtBox target chip (ChipSea Fxx). Read-only, hardware-agnostic register access.",
1144
+ "inputSchema": {
1145
+ "type": "object",
1146
+ "properties": {
1147
+ "reg": {
1148
+ "type": "string",
1149
+ "description": "Register address (0x prefix or decimal), e.g. 0xBE",
1150
+ "default": "0x10",
1151
+ },
1152
+ "len": {
1153
+ "type": "integer",
1154
+ "description": "Bytes to read (1..256)",
1155
+ "default": 1,
1156
+ },
1157
+ "dll_dir": {
1158
+ "type": "string",
1159
+ "description": "NdtDebugTool lib directory; default from lab.yaml ndtdbg.dll_dir",
1160
+ },
1161
+ "slave_addr": {
1162
+ "type": "string",
1163
+ "description": "IIC slave address (e.g. 0xA0)",
1164
+ },
1165
+ },
1166
+ "additionalProperties": False,
1167
+ },
1168
+ "handler": handle_fw_ndtdbg_read_reg,
1169
+ },
1170
+ "fw_ndtdbg_write_reg": {
1171
+ "description": "Write bytes to an IIC register of the NdtBox target chip (ChipSea Fxx). Defended: requires lab.yaml ndtdbg.write_allowed: true AND the confirm: true argument; otherwise returns PERMISSION_DENIED.",
1172
+ "inputSchema": {
1173
+ "type": "object",
1174
+ "properties": {
1175
+ "reg": {
1176
+ "type": "string",
1177
+ "description": "Register address (0x prefix or decimal), e.g. 0xBE",
1178
+ },
1179
+ "data": {
1180
+ "type": "string",
1181
+ "description": "Hex payload, e.g. 01 or BE 42",
1182
+ },
1183
+ "confirm": {
1184
+ "type": "boolean",
1185
+ "description": "Acknowledge the write intent (double-gated with lab.yaml write_allowed)",
1186
+ "default": False,
1187
+ },
1188
+ "dll_dir": {
1189
+ "type": "string",
1190
+ "description": "NdtDebugTool lib directory; default from lab.yaml ndtdbg.dll_dir",
1191
+ },
1192
+ "slave_addr": {
1193
+ "type": "string",
1194
+ "description": "IIC slave address (e.g. 0xA0)",
1195
+ },
1196
+ },
1197
+ "required": ["reg", "data"],
1198
+ "additionalProperties": False,
1199
+ },
1200
+ "handler": handle_fw_ndtdbg_write_reg,
1201
+ },
1026
1202
  "fw_logic_capture": {
1027
1203
  "description": "Capture digital protocol waveforms (I2C, SPI, UART) via Saleae / sigrok or deterministic simulator.",
1028
1204
  "inputSchema": {
@@ -1160,7 +1336,7 @@ def process_request(req: Dict[str, Any]) -> Optional[Dict[str, Any]]:
1160
1336
  },
1161
1337
  "serverInfo": {
1162
1338
  "name": "firmwareloop",
1163
- "version": "0.0.13"
1339
+ "version": "0.1.1"
1164
1340
  }
1165
1341
  }
1166
1342
  }
@@ -1252,7 +1428,7 @@ def process_request(req: Dict[str, Any]) -> Optional[Dict[str, Any]]:
1252
1428
  def handle_cli_update() -> int:
1253
1429
  """Handle `firmwareloop update` command."""
1254
1430
  print("============================================================")
1255
- print(" FirmwareLoop Auto-Updater (v0.0.13)")
1431
+ print(" FirmwareLoop Auto-Updater (v0.1.1)")
1256
1432
  print("============================================================")
1257
1433
 
1258
1434
  is_git_repo = os.path.exists(os.path.join(REPO_ROOT, ".git"))
@@ -1395,7 +1571,7 @@ def configure_qoder_user_mcp(user_home: str) -> List[Dict[str, Any]]:
1395
1571
  def handle_cli_setup() -> int:
1396
1572
  """Handle `firmwareloop setup` command: simultaneously configure Antigravity CLI, Claude Code CLI, and Qoder IDE."""
1397
1573
  print("============================================================")
1398
- print(" FirmwareLoop Multi-Agent Global Setup Helper (v0.0.13)")
1574
+ print(" FirmwareLoop Multi-Agent Global Setup Helper (v0.1.1)")
1399
1575
  print("============================================================")
1400
1576
  print("")
1401
1577
 
@@ -1500,7 +1676,7 @@ def handle_cli_setup() -> int:
1500
1676
  def handle_cli_init() -> int:
1501
1677
  """Handle `firmwareloop init` command."""
1502
1678
  print("============================================================")
1503
- print(" FirmwareLoop Multi-Agent Project Initializer (v0.0.13)")
1679
+ print(" FirmwareLoop Multi-Agent Project Initializer (v0.1.1)")
1504
1680
  print("============================================================")
1505
1681
  cwd = os.getcwd()
1506
1682
  print(f"[*] Initializing multi-agent guidelines & bench config in:\n {cwd}\n")
@@ -1521,7 +1697,7 @@ def handle_cli_init() -> int:
1521
1697
 
1522
1698
 
1523
1699
  def print_cli_help() -> None:
1524
- print("""FirmwareLoop (fwloop) — AI Agent Firmware Engineering & Lab Automation Platform (v0.0.13)
1700
+ print("""FirmwareLoop (fwloop) — AI Agent Firmware Engineering & Lab Automation Platform (v0.1.1)
1525
1701
 
1526
1702
  Usage:
1527
1703
  fwloop [command] (or: firmwareloop [command])
@@ -1552,7 +1728,7 @@ def main() -> None:
1552
1728
  elif cmd in ["setup", "register"]:
1553
1729
  sys.exit(handle_cli_setup())
1554
1730
  elif cmd in ["version", "-v", "--version"]:
1555
- print("FirmwareLoop v0.0.13")
1731
+ print("FirmwareLoop v0.1.1")
1556
1732
  sys.exit(0)
1557
1733
  elif cmd in ["help", "-h", "--help"]:
1558
1734
  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'