firmwareloop 0.1.6__tar.gz → 0.1.8__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 (36) hide show
  1. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/PKG-INFO +6 -4
  2. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/README.md +5 -3
  3. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/firmwareloop.egg-info/PKG-INFO +6 -4
  4. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/pyproject.toml +1 -1
  5. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/tools/__init__.py +1 -1
  6. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/tools/fw_mcp_server.py +61 -7
  7. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/tools/lib/ndtdbg.py +168 -0
  8. firmwareloop-0.1.8/tools/ndtdbg_bridge.ps1 +446 -0
  9. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/tools/ndtdbg_cli.py +24 -1
  10. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/tools/setup-agent-mcp.ps1 +2 -2
  11. firmwareloop-0.1.6/tools/ndtdbg_bridge.ps1 +0 -234
  12. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/LICENSE +0 -0
  13. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/firmwareloop.egg-info/SOURCES.txt +0 -0
  14. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/firmwareloop.egg-info/dependency_links.txt +0 -0
  15. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/firmwareloop.egg-info/entry_points.txt +0 -0
  16. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/firmwareloop.egg-info/requires.txt +0 -0
  17. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/firmwareloop.egg-info/top_level.txt +0 -0
  18. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/setup.cfg +0 -0
  19. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/tools/acceptance-scenario.ps1 +0 -0
  20. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/tools/build.ps1 +0 -0
  21. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/tools/can.ps1 +0 -0
  22. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/tools/check-qoder-mcp.ps1 +0 -0
  23. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/tools/common/build-backends.psm1 +0 -0
  24. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/tools/common/fw.psm1 +0 -0
  25. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/tools/common/uart_probe.py +0 -0
  26. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/tools/doctor.ps1 +0 -0
  27. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/tools/flash.ps1 +0 -0
  28. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/tools/instrument_cli.py +0 -0
  29. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/tools/lib/__init__.py +0 -0
  30. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/tools/lib/instruments.py +0 -0
  31. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/tools/logic_capture.ps1 +0 -0
  32. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/tools/logic_decode.ps1 +0 -0
  33. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/tools/reset.ps1 +0 -0
  34. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/tools/sigrok_csv_to_fwloop.py +0 -0
  35. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/tools/test-backends.ps1 +0 -0
  36. {firmwareloop-0.1.6 → firmwareloop-0.1.8}/tools/test.ps1 +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: firmwareloop
3
- Version: 0.1.6
3
+ Version: 0.1.8
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
@@ -134,7 +134,8 @@ fwloop ndtdebugtool "C:/Users/weilin.zhang/Desktop/zwl/NdtDebugTool-v1.0.19"
134
134
  > 完成以上两步后,Agent 侧即可直接调用 NdtBox 相关工具:
135
135
  > `fw_ndtdbg_status`(版本/连通性/模组通断)、`fw_ndtdbg_measure`(数据轮询采集)、
136
136
  > `fw_ndtdbg_read_reg` / `fw_ndtdbg_write_reg`(任意寄存器读写,写需 `write_allowed` + `confirm` 双门控)、
137
- > `fw_ndtdbg_capture_seq`(寄存器序列 + Saleae 波形同窗口取证)。
137
+ > `fw_ndtdbg_capture_seq`(寄存器序列 + Saleae 波形同窗口取证)、
138
+ > `fw_ndtdbg_flash`(.bin/.nfw 整片擦写烧录,需 `write_allowed` + `confirm` 双门控,默认拒绝)。
138
139
  > 前置硬件要求:NdtBox 已装 CH341 驱动;如需 Saleae 波形证据,另装 sigrok-cli 并配置
139
140
  > `libusb-win32` 驱动(详见 `docs/` 交付说明)。测试时请退出 STDataCollection.exe 上位机(USB 独占)。
140
141
 
@@ -362,7 +363,7 @@ claude mcp remove --scope user agentic-hil
362
363
  ├── demo-firmware/ 示例固件(CMake;宿主编译模拟 MCU,闭环验证载体)
363
364
  ├── demo-make/ 示例固件(Make;构建测试载体)
364
365
  ├── docs/ DEPENDENCY_MATRIX / REUSE_PLAN / V0.0.2_GAP_VERIFICATION
365
- ├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.1.6)
366
+ ├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.1.8)
366
367
  ├── .mcp.example.json 双层 MCP 配置模板(firmwareloop + agentic-hil)
367
368
  ├── AGENTS.md 通用智能体规范(Antigravity / Qoder / Cursor 等)
368
369
  ├── CLAUDE.md Claude Code CLI 指南
@@ -372,7 +373,7 @@ claude mcp remove --scope user agentic-hil
372
373
 
373
374
  ---
374
375
 
375
- ## 能力验证状态(v0.1.6
376
+ ## 能力验证状态(v0.1.8
376
377
 
377
378
  > 状态定义:`Implemented`(已实现)/ `Simulator Validated`(模拟验证)/
378
379
  > `Real Hardware Validated`(真机验证)/ `Experimental` / `Not Implemented`
@@ -393,6 +394,7 @@ claude mcp remove --scope user agentic-hil
393
394
  | Safety Policy(limits.yaml) | Implemented(simulator 校验)-> 外置权威配置 |
394
395
  | 自动修复闭环(M6 载体) | Simulator Validated -> Real Hardware Validated 待 DUT |
395
396
  | NdtBox 数据采集/调试(fw_ndtdbg_status / fw_ndtdbg_measure, ChipSea Fxx IIC) | Implemented(无硬件 Fail-Closed 已验证)-> Real Hardware Validated 待 NdtBox |
397
+ | NdtBox IIC 固件烧录(fw_ndtdbg_flash, .bin/.nfw 整片擦写, 芯海 Fxx) | Real Hardware Validated(84.87.1.1 / 84.87.1.2 真机整片擦写 verify pass, ~22s;双闸门 Fail-Closed) |
396
398
 
397
399
  ---
398
400
 
@@ -106,7 +106,8 @@ fwloop ndtdebugtool "C:/Users/weilin.zhang/Desktop/zwl/NdtDebugTool-v1.0.19"
106
106
  > 完成以上两步后,Agent 侧即可直接调用 NdtBox 相关工具:
107
107
  > `fw_ndtdbg_status`(版本/连通性/模组通断)、`fw_ndtdbg_measure`(数据轮询采集)、
108
108
  > `fw_ndtdbg_read_reg` / `fw_ndtdbg_write_reg`(任意寄存器读写,写需 `write_allowed` + `confirm` 双门控)、
109
- > `fw_ndtdbg_capture_seq`(寄存器序列 + Saleae 波形同窗口取证)。
109
+ > `fw_ndtdbg_capture_seq`(寄存器序列 + Saleae 波形同窗口取证)、
110
+ > `fw_ndtdbg_flash`(.bin/.nfw 整片擦写烧录,需 `write_allowed` + `confirm` 双门控,默认拒绝)。
110
111
  > 前置硬件要求:NdtBox 已装 CH341 驱动;如需 Saleae 波形证据,另装 sigrok-cli 并配置
111
112
  > `libusb-win32` 驱动(详见 `docs/` 交付说明)。测试时请退出 STDataCollection.exe 上位机(USB 独占)。
112
113
 
@@ -334,7 +335,7 @@ claude mcp remove --scope user agentic-hil
334
335
  ├── demo-firmware/ 示例固件(CMake;宿主编译模拟 MCU,闭环验证载体)
335
336
  ├── demo-make/ 示例固件(Make;构建测试载体)
336
337
  ├── docs/ DEPENDENCY_MATRIX / REUSE_PLAN / V0.0.2_GAP_VERIFICATION
337
- ├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.1.6)
338
+ ├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.1.8)
338
339
  ├── .mcp.example.json 双层 MCP 配置模板(firmwareloop + agentic-hil)
339
340
  ├── AGENTS.md 通用智能体规范(Antigravity / Qoder / Cursor 等)
340
341
  ├── CLAUDE.md Claude Code CLI 指南
@@ -344,7 +345,7 @@ claude mcp remove --scope user agentic-hil
344
345
 
345
346
  ---
346
347
 
347
- ## 能力验证状态(v0.1.6
348
+ ## 能力验证状态(v0.1.8
348
349
 
349
350
  > 状态定义:`Implemented`(已实现)/ `Simulator Validated`(模拟验证)/
350
351
  > `Real Hardware Validated`(真机验证)/ `Experimental` / `Not Implemented`
@@ -365,6 +366,7 @@ claude mcp remove --scope user agentic-hil
365
366
  | Safety Policy(limits.yaml) | Implemented(simulator 校验)-> 外置权威配置 |
366
367
  | 自动修复闭环(M6 载体) | Simulator Validated -> Real Hardware Validated 待 DUT |
367
368
  | NdtBox 数据采集/调试(fw_ndtdbg_status / fw_ndtdbg_measure, ChipSea Fxx IIC) | Implemented(无硬件 Fail-Closed 已验证)-> Real Hardware Validated 待 NdtBox |
369
+ | NdtBox IIC 固件烧录(fw_ndtdbg_flash, .bin/.nfw 整片擦写, 芯海 Fxx) | Real Hardware Validated(84.87.1.1 / 84.87.1.2 真机整片擦写 verify pass, ~22s;双闸门 Fail-Closed) |
368
370
 
369
371
  ---
370
372
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: firmwareloop
3
- Version: 0.1.6
3
+ Version: 0.1.8
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
@@ -134,7 +134,8 @@ fwloop ndtdebugtool "C:/Users/weilin.zhang/Desktop/zwl/NdtDebugTool-v1.0.19"
134
134
  > 完成以上两步后,Agent 侧即可直接调用 NdtBox 相关工具:
135
135
  > `fw_ndtdbg_status`(版本/连通性/模组通断)、`fw_ndtdbg_measure`(数据轮询采集)、
136
136
  > `fw_ndtdbg_read_reg` / `fw_ndtdbg_write_reg`(任意寄存器读写,写需 `write_allowed` + `confirm` 双门控)、
137
- > `fw_ndtdbg_capture_seq`(寄存器序列 + Saleae 波形同窗口取证)。
137
+ > `fw_ndtdbg_capture_seq`(寄存器序列 + Saleae 波形同窗口取证)、
138
+ > `fw_ndtdbg_flash`(.bin/.nfw 整片擦写烧录,需 `write_allowed` + `confirm` 双门控,默认拒绝)。
138
139
  > 前置硬件要求:NdtBox 已装 CH341 驱动;如需 Saleae 波形证据,另装 sigrok-cli 并配置
139
140
  > `libusb-win32` 驱动(详见 `docs/` 交付说明)。测试时请退出 STDataCollection.exe 上位机(USB 独占)。
140
141
 
@@ -362,7 +363,7 @@ claude mcp remove --scope user agentic-hil
362
363
  ├── demo-firmware/ 示例固件(CMake;宿主编译模拟 MCU,闭环验证载体)
363
364
  ├── demo-make/ 示例固件(Make;构建测试载体)
364
365
  ├── docs/ DEPENDENCY_MATRIX / REUSE_PLAN / V0.0.2_GAP_VERIFICATION
365
- ├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.1.6)
366
+ ├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.1.8)
366
367
  ├── .mcp.example.json 双层 MCP 配置模板(firmwareloop + agentic-hil)
367
368
  ├── AGENTS.md 通用智能体规范(Antigravity / Qoder / Cursor 等)
368
369
  ├── CLAUDE.md Claude Code CLI 指南
@@ -372,7 +373,7 @@ claude mcp remove --scope user agentic-hil
372
373
 
373
374
  ---
374
375
 
375
- ## 能力验证状态(v0.1.6
376
+ ## 能力验证状态(v0.1.8
376
377
 
377
378
  > 状态定义:`Implemented`(已实现)/ `Simulator Validated`(模拟验证)/
378
379
  > `Real Hardware Validated`(真机验证)/ `Experimental` / `Not Implemented`
@@ -393,6 +394,7 @@ claude mcp remove --scope user agentic-hil
393
394
  | Safety Policy(limits.yaml) | Implemented(simulator 校验)-> 外置权威配置 |
394
395
  | 自动修复闭环(M6 载体) | Simulator Validated -> Real Hardware Validated 待 DUT |
395
396
  | NdtBox 数据采集/调试(fw_ndtdbg_status / fw_ndtdbg_measure, ChipSea Fxx IIC) | Implemented(无硬件 Fail-Closed 已验证)-> Real Hardware Validated 待 NdtBox |
397
+ | NdtBox IIC 固件烧录(fw_ndtdbg_flash, .bin/.nfw 整片擦写, 芯海 Fxx) | Real Hardware Validated(84.87.1.1 / 84.87.1.2 真机整片擦写 verify pass, ~22s;双闸门 Fail-Closed) |
396
398
 
397
399
  ---
398
400
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "firmwareloop"
7
- version = "0.1.6"
7
+ version = "0.1.8"
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.1.6"
5
+ __version__ = "0.1.8"
@@ -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.1.6).
3
+ fw_mcp_server.py - FirmwareLoop High-Level Workflow & Device MCP Server (v0.1.8).
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, CodeBuddy IDE, Cursor) via the Model Context
@@ -19,6 +19,7 @@ Tools provided:
19
19
  - fw_ndtdbg_read_reg: NdtBox IIC register read (ndtdbg_cli.py)
20
20
  - fw_ndtdbg_write_reg: NdtBox IIC register write, double-gated (ndtdbg_cli.py)
21
21
  - fw_ndtdbg_capture_seq: NdtBox register sequence + concurrent I2C capture for waveform evidence (ndtdbg_cli.py)
22
+ - fw_ndtdbg_flash: NdtBox IIC firmware flash (.bin/.nfw, full-chip erase, double-gated) (ndtdbg_cli.py)
22
23
  - fw_logic_capture: Logic analyzer protocol capture (logic_capture.ps1)
23
24
  - fw_logic_decode: Logic analyzer protocol decoding & assertion (logic_decode.ps1)
24
25
  - fw_get_evidence: Read structured audit run evidence (artifacts/runs/)
@@ -306,6 +307,18 @@ def handle_fw_ndtdbg_write_reg(args: Dict[str, Any]) -> Dict[str, Any]:
306
307
  return res
307
308
 
308
309
 
310
+ def handle_fw_ndtdbg_flash(args: Dict[str, Any]) -> Dict[str, Any]:
311
+ """Erase + flash a .bin/.nfw image onto the NdtBox target chip (ChipSea Fxx) over IIC."""
312
+ cli_script = os.path.join(REPO_ROOT, "tools", "ndtdbg_cli.py")
313
+ cmd = [PYTHON_EXE, cli_script, "flash-bin", "--file", str(args.get("file_path")),
314
+ "--erase-ms", str(int(args.get("erase_ms", 2000)))]
315
+ if args.get("confirm"):
316
+ cmd.append("--confirm")
317
+ _ndtdbg_reg_args(args, cmd)
318
+ timeout_s = min(max(int(args.get("timeout_seconds", 300)), 30), 900)
319
+ return run_process(cmd, timeout=timeout_s)
320
+
321
+
309
322
  def handle_fw_ndtdbg_capture_seq(args: Dict[str, Any]) -> Dict[str, Any]:
310
323
  """单次调用内并发: 逻辑分析仪抓取与 NdtBox 寄存器序列同时进行。
311
324
 
@@ -1265,6 +1278,47 @@ TOOLS_REGISTRY = {
1265
1278
  },
1266
1279
  "handler": handle_fw_ndtdbg_write_reg,
1267
1280
  },
1281
+ "fw_ndtdbg_flash": {
1282
+ "description": "Erase and flash a .bin/.nfw firmware image onto the NdtBox target chip (ChipSea Fxx) over IIC via NdtDebugTool (power-cycle reset into boot -> erase magic AA 55 A5 5A -> 128B page writes -> 256B read-back verify -> finish 'PASs'). DESTRUCTIVE full-chip erase: requires lab.yaml ndtdbg.write_allowed: true AND confirm: true, otherwise PERMISSION_DENIED.",
1283
+ "inputSchema": {
1284
+ "type": "object",
1285
+ "properties": {
1286
+ "file_path": {
1287
+ "type": "string",
1288
+ "description": "Firmware image path (.bin auto-padded to 128-byte alignment with 0xFF; .nfw stripped of its 256-byte header)",
1289
+ },
1290
+ "confirm": {
1291
+ "type": "boolean",
1292
+ "description": "Acknowledge the destructive full-chip erase (double-gated with lab.yaml ndtdbg.write_allowed)",
1293
+ "default": False,
1294
+ },
1295
+ "erase_ms": {
1296
+ "type": "integer",
1297
+ "description": "Erase wait in ms (align with factory UpdateFW: 2000)",
1298
+ "default": 2000,
1299
+ },
1300
+ "timeout_seconds": {
1301
+ "type": "integer",
1302
+ "description": "CLI process timeout; budget ~30s per 64KB image (page writes paced at 30ms)",
1303
+ "default": 300,
1304
+ },
1305
+ "dll_dir": {
1306
+ "type": "string",
1307
+ "description": "NdtDebugTool lib directory containing USBIOX.DLL and usb2io.dll; default from lab.yaml ndtdbg.dll_dir",
1308
+ },
1309
+ "slave_addr": {
1310
+ "type": "string",
1311
+ "description": "IIC slave address (e.g. 0xA0); default from lab.yaml ndtdbg.slave_addr",
1312
+ },
1313
+ "speed": {
1314
+ "type": "integer",
1315
+ "description": "IIC speed 0=20kHz 1=100kHz; default from lab.yaml ndtdbg.iic_speed",
1316
+ },
1317
+ },
1318
+ "required": ["file_path"],
1319
+ },
1320
+ "handler": handle_fw_ndtdbg_flash,
1321
+ },
1268
1322
  "fw_ndtdbg_capture_seq": {
1269
1323
  "description": "One-call protocol evidence: concurrently runs a NdtBox register write sequence (default 0xBE=01, 0x01=BE 42, poll 0x01, readback 0xBE) and a 3s Saleae/sigrok I2C capture, returning the capture path for fw_logic_decode.",
1270
1324
  "inputSchema": {
@@ -1433,7 +1487,7 @@ def process_request(req: Dict[str, Any]) -> Optional[Dict[str, Any]]:
1433
1487
  },
1434
1488
  "serverInfo": {
1435
1489
  "name": "firmwareloop",
1436
- "version": "0.1.6"
1490
+ "version": "0.1.8"
1437
1491
  }
1438
1492
  }
1439
1493
  }
@@ -1525,7 +1579,7 @@ def process_request(req: Dict[str, Any]) -> Optional[Dict[str, Any]]:
1525
1579
  def handle_cli_update() -> int:
1526
1580
  """Handle `firmwareloop update` command."""
1527
1581
  print("============================================================")
1528
- print(" FirmwareLoop Auto-Updater (v0.1.6)")
1582
+ print(" FirmwareLoop Auto-Updater (v0.1.8)")
1529
1583
  print("============================================================")
1530
1584
 
1531
1585
  is_git_repo = os.path.exists(os.path.join(REPO_ROOT, ".git"))
@@ -1795,7 +1849,7 @@ def set_ndtdbg_dlldir(dll_path: str) -> str:
1795
1849
  def handle_cli_setup() -> int:
1796
1850
  """Handle `firmwareloop setup` command: simultaneously configure Antigravity CLI, Claude Code CLI, Qoder IDE, and CodeBuddy IDE."""
1797
1851
  print("============================================================")
1798
- print(" FirmwareLoop Multi-Agent Global Setup Helper (v0.1.6)")
1852
+ print(" FirmwareLoop Multi-Agent Global Setup Helper (v0.1.8)")
1799
1853
  print("============================================================")
1800
1854
  print("")
1801
1855
 
@@ -1920,7 +1974,7 @@ def handle_cli_setup() -> int:
1920
1974
  def handle_cli_init() -> int:
1921
1975
  """Handle `firmwareloop init` command."""
1922
1976
  print("============================================================")
1923
- print(" FirmwareLoop Multi-Agent Project Initializer (v0.1.6)")
1977
+ print(" FirmwareLoop Multi-Agent Project Initializer (v0.1.8)")
1924
1978
  print("============================================================")
1925
1979
  cwd = os.getcwd()
1926
1980
  print(f"[*] Initializing multi-agent guidelines & bench config in:\n {cwd}\n")
@@ -1941,7 +1995,7 @@ def handle_cli_init() -> int:
1941
1995
 
1942
1996
 
1943
1997
  def print_cli_help() -> None:
1944
- print("""FirmwareLoop (fwloop) — AI Agent Firmware Engineering & Lab Automation Platform (v0.1.6)
1998
+ print("""FirmwareLoop (fwloop) — AI Agent Firmware Engineering & Lab Automation Platform (v0.1.8)
1945
1999
 
1946
2000
  Usage:
1947
2001
  fwloop [command] (or: firmwareloop [command])
@@ -1981,7 +2035,7 @@ def main() -> None:
1981
2035
  sys.exit(2)
1982
2036
  sys.exit(handle_cli_ndtdebugtool(args[1]))
1983
2037
  elif cmd in ["version", "-v", "--version"]:
1984
- print("FirmwareLoop v0.1.6")
2038
+ print("FirmwareLoop v0.1.8")
1985
2039
  sys.exit(0)
1986
2040
  elif cmd in ["help", "-h", "--help"]:
1987
2041
  print_cli_help()
@@ -92,6 +92,53 @@ def pad_bin_128(file_bytes: bytes) -> bytes:
92
92
  return file_bytes + b"\xFF" * pad
93
93
 
94
94
 
95
+ # ---------------------------------------------------------------------------
96
+ # 固件烧录 (对齐 ChipSea.cs UpdateFW: 断电复位 -> 整片擦除 -> 页写 -> 回读校验 -> 完成魔数)
97
+ # ---------------------------------------------------------------------------
98
+
99
+ FLASH_WRITE_PAGE = 128 # WriteFw 每事务 128 字节负载 (IIC 写共 130B = 2B 页地址 + 负载)
100
+ FLASH_READ_WINDOW = 256 # ReadFwAndCheck 每窗回读 256 字节
101
+ ERASE_MAGIC = b"\xAA\x55\xA5\x5A" # EreaseChipCmd 写 @0x0000
102
+ FINISH_MAGIC = b"\x50\x41\x53\x73" # UpgradeFinishCmd "PASs" 写 @0x0000
103
+ BOOT_READY_MS = 100 # VBRTOFF 上电后到发擦除命令的等待 (UpdateFW Thread.Sleep(100))
104
+ PAGE_WRITE_GAP_MS = 30 # 页写间隔 (WriteFw Thread.Sleep(30))
105
+ PAGE_READ_GAP_MS = 10 # 回读窗口间隔 (ReadFwAndCheck Thread.Sleep(10))
106
+
107
+
108
+ def load_firmware(file_path: str) -> tuple[str, bytes]:
109
+ """读取固件: .nfw 去 256 头 / .bin 128 对齐 0xFF 补齐 (ChipSea.cs LoadFile)。返回 (类型, 载荷)。"""
110
+ if not os.path.isfile(file_path):
111
+ raise NdtBoxError("CONFIG_ERROR", "固件文件不存在", file_path)
112
+ ext = os.path.splitext(file_path)[1].lower()
113
+ try:
114
+ with open(file_path, "rb") as fh:
115
+ raw = fh.read()
116
+ except OSError as exc:
117
+ raise NdtBoxError("CONFIG_ERROR", "固件文件读取失败", str(exc)) from exc
118
+ if ext == ".nfw":
119
+ return "nfw", strip_nfw(raw)
120
+ if ext == ".bin":
121
+ if not raw:
122
+ raise NdtBoxError("CONFIG_ERROR", ".bin 固件为空", file_path)
123
+ return "bin", pad_bin_128(raw)
124
+ raise NdtBoxError("CONFIG_ERROR", "仅支持 .bin / .nfw 固件文件", ext or "(无后缀)")
125
+
126
+
127
+ def build_page_write_frame(offset: int, page: bytes) -> bytes:
128
+ """页写帧: 页地址大端 2 字节 + <=128 字节负载 (ChipSea.cs WriteFw 逐页自 0x0000 递增)。"""
129
+ if len(page) > FLASH_WRITE_PAGE:
130
+ raise NdtBoxError("CONFIG_ERROR", "页负载超过 128 字节", str(len(page)))
131
+ if not 0 <= offset <= 0xFFFF:
132
+ raise NdtBoxError("CONFIG_ERROR", "页地址越界 (0..0xFFFF)", hex(offset))
133
+ return offset.to_bytes(2, "big") + page
134
+
135
+
136
+ def expected_read_window(fw: bytes, offset: int) -> bytes:
137
+ """回读期望窗口: 256 字节, 末窗不足部分 0xFF 补齐 (ChipSea.cs ReadFwAndCheck)。"""
138
+ window = fw[offset:offset + FLASH_READ_WINDOW]
139
+ return window + b"\xFF" * (FLASH_READ_WINDOW - len(window))
140
+
141
+
95
142
  # ---------------------------------------------------------------------------
96
143
  # 桥调用层
97
144
  # ---------------------------------------------------------------------------
@@ -156,6 +203,82 @@ class NdtBoxIo:
156
203
  raise NdtBoxError("CONFIG_ERROR", "写入数据过长 (<=128)", str(len(data)))
157
204
  self._bridge("write", RegAddr=reg, Data=data.hex())
158
205
 
206
+ def raw_write_read(self, write: bytes, read_len: int = 0) -> bytes | None:
207
+ """原始 IIC 事务 (对齐 IIC.WriteRead): 写 write 字节(不含从机地址) 再读 read_len 字节。
208
+
209
+ 覆盖 ChipSea 烧录的多字节地址形态 (页写 130B / 页读 [hi,lo]+256)。
210
+ """
211
+ if not write:
212
+ raise NdtBoxError("CONFIG_ERROR", "raw 写字节为空", None)
213
+ if 1 + len(write) > 256:
214
+ raise NdtBoxError("CONFIG_ERROR", "raw 写事务过长 (含从机地址须 <=256)", str(len(write)))
215
+ if not 0 <= read_len <= 256:
216
+ raise NdtBoxError("CONFIG_ERROR", "raw 读长度越界 (0..256)", str(read_len))
217
+ data = self._bridge("raw", WriteHex=write.hex(), ReadLen=read_len)
218
+ return bytes.fromhex(data["hex"].replace(" ", "")) if read_len > 0 else None
219
+
220
+ def _bridge_pipe(self, action: str, payload_text: str, timeout_ms: int | None) -> dict:
221
+ """把 JSON 文本经 stdin 送入桥的批量 action, 单进程执行。
222
+
223
+ #progress 进度行由桥写 stdout (非 JSON), 本方法转发到本进程 stderr。
224
+ 末行为 JSON: {ok:false,...} -> 转 NdtBoxError (Fail-Closed); 成功返回 data。
225
+ """
226
+ import sys
227
+ args = [PS32, "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass",
228
+ "-File", self.bridge_ps1, "-Action", action,
229
+ "-DllDir", self.dll_dir, "-SlaveAddr", str(self.slave_addr),
230
+ "-Speed", str(self.speed)]
231
+ to = self.timeout_ms if timeout_ms is None else timeout_ms
232
+ try:
233
+ proc = subprocess.run(args, input=payload_text.encode("utf-8"), capture_output=True,
234
+ timeout=to / 1000.0, check=False)
235
+ except subprocess.TimeoutExpired:
236
+ raise NdtBoxError("INSTRUMENT_TIMEOUT", f"桥 {action} 执行超时 >{to}ms", None)
237
+ out = (proc.stdout or b"").decode("utf-8", errors="replace")
238
+ lines = [ln for ln in out.splitlines() if ln]
239
+ for ln in lines[:-1]:
240
+ if ln.startswith("#progress"):
241
+ print(ln, file=sys.stderr, flush=True)
242
+ if not lines:
243
+ tail = (proc.stderr or b"").decode("utf-8", errors="replace").strip()[-400:]
244
+ raise NdtBoxError("UNKNOWN_ERROR", f"桥 {action} 无输出 (exit={proc.returncode})", tail or None)
245
+ try:
246
+ result = json.loads(lines[-1])
247
+ except json.JSONDecodeError as exc:
248
+ raise NdtBoxError("UNKNOWN_ERROR", f"桥 {action} 输出非 JSON", out[-400:]) from exc
249
+ if not result.get("ok"):
250
+ raise NdtBoxError(result.get("error_class", "UNKNOWN_ERROR"),
251
+ result.get("error", f"桥 {action} 返回失败"),
252
+ result.get("detail"))
253
+ return result.get("data", {})
254
+
255
+ def run_ops(self, ops: list[dict], timeout_ms: int | None = None) -> dict:
256
+ """在单个 32 位 PS 进程内顺序执行 ops——批量路径避免逐帧 spawn 进程开销。
257
+
258
+ ops item: {"op":"sleep","ms":N} | {"op":"w","hex":"<帧 hex>"} |
259
+ {"op":"r","hex":"<2B 地址 hex>","len":N}
260
+ 桥自动为 w/r 前置从机地址; r 的读回按序进入返回 read_hexes (无空格 hex, 每窗一条)。
261
+ """
262
+ if not ops:
263
+ raise NdtBoxError("CONFIG_ERROR", "run_ops 空序列", None)
264
+ return self._bridge_pipe("run", json.dumps(ops, separators=(",", ":")), timeout_ms)
265
+
266
+ def flash_exec(self, plan: dict, timeout_ms: int | None = None) -> dict:
267
+ """单进程完整固件烧录 (bridge "flash"), 复刻原厂 GUI 进程模型:
268
+ usb2io(IO10) 断电复位进 boot + USBIOX IIC 全序列在**同一进程同一会话**内连续执行,
269
+ 消除跨进程的"断电->擦除"间隔。校验 (逐窗 vs expect_hex) 在桥内完成, 失败即
270
+ {ok:false, TARGET_MISMATCH} -> 本方法转 NdtBoxError (Fail-Closed)。
271
+
272
+ plan: {"off_ms":200,"erase_ms":2000,"page_gap_ms":30,"read_gap_ms":10,
273
+ "writes":["<2B 地址+128B 数据帧 hex>",...],
274
+ "windows":[{"off":0,"expect_hex":"<256B hex>"},...]}
275
+ """
276
+ return self._bridge_pipe("flash", json.dumps(plan, separators=(",", ":")), timeout_ms)
277
+
278
+ def vbrt_reset(self, off_ms: int = 200) -> dict:
279
+ """模组断电 off_ms 后恢复上电 (usb2io.dll IO10)——复位进入 IIC boot (IOoperation.VBRTOFF)。"""
280
+ return self._bridge("vbrt", OffMs=off_ms)
281
+
159
282
  def close(self) -> dict:
160
283
  return self._bridge("close")
161
284
 
@@ -220,3 +343,48 @@ class ChipSeaFxx:
220
343
  raise NdtBoxError("CONFIG_ERROR", "poll 时长必须 > 0", str(duration_ms))
221
344
  return self._io._bridge("poll", DurationMs=int(duration_ms), IntervalMs=int(interval_ms),
222
345
  ReadyTimeoutMs=int(ready_timeout_ms))
346
+
347
+ # -- 固件烧录 (对齐 ChipSea.cs UpdateFW: 仅芯海 Fxx, 整片擦写) --
348
+
349
+ def flash_bin(self, file_path: str, erase_ms: int = 2000,
350
+ progress=None) -> dict:
351
+ """经 NdtBox IIC 烧录 .bin/.nfw 固件到芯海 Fxx (单进程 flash action)。
352
+
353
+ 时序逐行对齐 ChipSea.cs UpdateFW, 进程模型对齐原厂 STDataCollection GUI:
354
+ VBRTOFF(200) 断电复位进 boot -> 100ms -> 擦除魔数 -> erase_ms ->
355
+ 逐 128B 页写(30ms) -> 200ms -> 逐 256B 窗回读校验(10ms, 桥内逐窗比对) ->
356
+ 完成魔数 "PASs" -> VBRTOFF(200) 复位加载。
357
+ 整条序列在**一个 32 位 PS 进程**内连续执行 (usb2io + USBIOX 句柄同持),
358
+ 消除跨进程"断电->擦除"间隔, ~15s 量级。帧组装在本层 (可单测),
359
+ 校验失败由桥 {ok:false, TARGET_MISMATCH} -> flash_exec 抛 NdtBoxError (Fail-Closed)。
360
+
361
+ progress(phase, done, total) 在 verify 阶段末回调 (逐帧 #progress 由桥转发到 stderr)。
362
+ """
363
+ import time
364
+ fw_type, fw = load_firmware(file_path)
365
+ started = time.monotonic()
366
+ pages = list(range(0, len(fw), FLASH_WRITE_PAGE))
367
+ windows = list(range(0, len(fw), FLASH_READ_WINDOW))
368
+ plan = {
369
+ "off_ms": 200,
370
+ "erase_ms": int(erase_ms),
371
+ "page_gap_ms": PAGE_WRITE_GAP_MS,
372
+ "read_gap_ms": PAGE_READ_GAP_MS,
373
+ "writes": [build_page_write_frame(off, fw[off:off + FLASH_WRITE_PAGE]).hex()
374
+ for off in pages],
375
+ "windows": [{"off": off, "expect_hex": expected_read_window(fw, off).hex()}
376
+ for off in windows],
377
+ }
378
+ res = self._io.flash_exec(plan)
379
+ if progress:
380
+ progress("verify", len(windows), len(windows))
381
+ return {
382
+ "file": file_path,
383
+ "file_type": fw_type,
384
+ "fw_len": len(fw),
385
+ "pages_written": len(pages),
386
+ "read_windows": len(windows),
387
+ "erase_ms": int(erase_ms),
388
+ "verify": res.get("verify", "pass"),
389
+ "duration_ms": int((time.monotonic() - started) * 1000),
390
+ }