firmwareloop 0.1.7__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 (35) hide show
  1. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/PKG-INFO +4 -4
  2. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/README.md +3 -3
  3. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/firmwareloop.egg-info/PKG-INFO +4 -4
  4. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/pyproject.toml +1 -1
  5. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/tools/__init__.py +1 -1
  6. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/tools/fw_mcp_server.py +7 -7
  7. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/tools/lib/ndtdbg.py +83 -42
  8. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/tools/ndtdbg_bridge.ps1 +171 -2
  9. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/tools/setup-agent-mcp.ps1 +2 -2
  10. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/LICENSE +0 -0
  11. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/firmwareloop.egg-info/SOURCES.txt +0 -0
  12. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/firmwareloop.egg-info/dependency_links.txt +0 -0
  13. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/firmwareloop.egg-info/entry_points.txt +0 -0
  14. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/firmwareloop.egg-info/requires.txt +0 -0
  15. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/firmwareloop.egg-info/top_level.txt +0 -0
  16. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/setup.cfg +0 -0
  17. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/tools/acceptance-scenario.ps1 +0 -0
  18. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/tools/build.ps1 +0 -0
  19. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/tools/can.ps1 +0 -0
  20. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/tools/check-qoder-mcp.ps1 +0 -0
  21. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/tools/common/build-backends.psm1 +0 -0
  22. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/tools/common/fw.psm1 +0 -0
  23. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/tools/common/uart_probe.py +0 -0
  24. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/tools/doctor.ps1 +0 -0
  25. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/tools/flash.ps1 +0 -0
  26. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/tools/instrument_cli.py +0 -0
  27. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/tools/lib/__init__.py +0 -0
  28. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/tools/lib/instruments.py +0 -0
  29. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/tools/logic_capture.ps1 +0 -0
  30. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/tools/logic_decode.ps1 +0 -0
  31. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/tools/ndtdbg_cli.py +0 -0
  32. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/tools/reset.ps1 +0 -0
  33. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/tools/sigrok_csv_to_fwloop.py +0 -0
  34. {firmwareloop-0.1.7 → firmwareloop-0.1.8}/tools/test-backends.ps1 +0 -0
  35. {firmwareloop-0.1.7 → 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.7
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
@@ -363,7 +363,7 @@ claude mcp remove --scope user agentic-hil
363
363
  ├── demo-firmware/ 示例固件(CMake;宿主编译模拟 MCU,闭环验证载体)
364
364
  ├── demo-make/ 示例固件(Make;构建测试载体)
365
365
  ├── docs/ DEPENDENCY_MATRIX / REUSE_PLAN / V0.0.2_GAP_VERIFICATION
366
- ├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.1.7)
366
+ ├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.1.8)
367
367
  ├── .mcp.example.json 双层 MCP 配置模板(firmwareloop + agentic-hil)
368
368
  ├── AGENTS.md 通用智能体规范(Antigravity / Qoder / Cursor 等)
369
369
  ├── CLAUDE.md Claude Code CLI 指南
@@ -373,7 +373,7 @@ claude mcp remove --scope user agentic-hil
373
373
 
374
374
  ---
375
375
 
376
- ## 能力验证状态(v0.1.7)
376
+ ## 能力验证状态(v0.1.8)
377
377
 
378
378
  > 状态定义:`Implemented`(已实现)/ `Simulator Validated`(模拟验证)/
379
379
  > `Real Hardware Validated`(真机验证)/ `Experimental` / `Not Implemented`
@@ -394,7 +394,7 @@ claude mcp remove --scope user agentic-hil
394
394
  | Safety Policy(limits.yaml) | Implemented(simulator 校验)-> 外置权威配置 |
395
395
  | 自动修复闭环(M6 载体) | Simulator Validated -> Real Hardware Validated 待 DUT |
396
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) | Implemented(时序对齐原厂 UpdateFW, 双闸门 Fail-Closed 已验证)-> Real Hardware 待烧录验收 |
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) |
398
398
 
399
399
  ---
400
400
 
@@ -335,7 +335,7 @@ claude mcp remove --scope user agentic-hil
335
335
  ├── demo-firmware/ 示例固件(CMake;宿主编译模拟 MCU,闭环验证载体)
336
336
  ├── demo-make/ 示例固件(Make;构建测试载体)
337
337
  ├── docs/ DEPENDENCY_MATRIX / REUSE_PLAN / V0.0.2_GAP_VERIFICATION
338
- ├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.1.7)
338
+ ├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.1.8)
339
339
  ├── .mcp.example.json 双层 MCP 配置模板(firmwareloop + agentic-hil)
340
340
  ├── AGENTS.md 通用智能体规范(Antigravity / Qoder / Cursor 等)
341
341
  ├── CLAUDE.md Claude Code CLI 指南
@@ -345,7 +345,7 @@ claude mcp remove --scope user agentic-hil
345
345
 
346
346
  ---
347
347
 
348
- ## 能力验证状态(v0.1.7)
348
+ ## 能力验证状态(v0.1.8)
349
349
 
350
350
  > 状态定义:`Implemented`(已实现)/ `Simulator Validated`(模拟验证)/
351
351
  > `Real Hardware Validated`(真机验证)/ `Experimental` / `Not Implemented`
@@ -366,7 +366,7 @@ claude mcp remove --scope user agentic-hil
366
366
  | Safety Policy(limits.yaml) | Implemented(simulator 校验)-> 外置权威配置 |
367
367
  | 自动修复闭环(M6 载体) | Simulator Validated -> Real Hardware Validated 待 DUT |
368
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) | Implemented(时序对齐原厂 UpdateFW, 双闸门 Fail-Closed 已验证)-> Real Hardware 待烧录验收 |
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) |
370
370
 
371
371
  ---
372
372
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: firmwareloop
3
- Version: 0.1.7
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
@@ -363,7 +363,7 @@ claude mcp remove --scope user agentic-hil
363
363
  ├── demo-firmware/ 示例固件(CMake;宿主编译模拟 MCU,闭环验证载体)
364
364
  ├── demo-make/ 示例固件(Make;构建测试载体)
365
365
  ├── docs/ DEPENDENCY_MATRIX / REUSE_PLAN / V0.0.2_GAP_VERIFICATION
366
- ├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.1.7)
366
+ ├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.1.8)
367
367
  ├── .mcp.example.json 双层 MCP 配置模板(firmwareloop + agentic-hil)
368
368
  ├── AGENTS.md 通用智能体规范(Antigravity / Qoder / Cursor 等)
369
369
  ├── CLAUDE.md Claude Code CLI 指南
@@ -373,7 +373,7 @@ claude mcp remove --scope user agentic-hil
373
373
 
374
374
  ---
375
375
 
376
- ## 能力验证状态(v0.1.7)
376
+ ## 能力验证状态(v0.1.8)
377
377
 
378
378
  > 状态定义:`Implemented`(已实现)/ `Simulator Validated`(模拟验证)/
379
379
  > `Real Hardware Validated`(真机验证)/ `Experimental` / `Not Implemented`
@@ -394,7 +394,7 @@ claude mcp remove --scope user agentic-hil
394
394
  | Safety Policy(limits.yaml) | Implemented(simulator 校验)-> 外置权威配置 |
395
395
  | 自动修复闭环(M6 载体) | Simulator Validated -> Real Hardware Validated 待 DUT |
396
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) | Implemented(时序对齐原厂 UpdateFW, 双闸门 Fail-Closed 已验证)-> Real Hardware 待烧录验收 |
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) |
398
398
 
399
399
  ---
400
400
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "firmwareloop"
7
- version = "0.1.7"
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.7"
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.7).
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
@@ -1487,7 +1487,7 @@ def process_request(req: Dict[str, Any]) -> Optional[Dict[str, Any]]:
1487
1487
  },
1488
1488
  "serverInfo": {
1489
1489
  "name": "firmwareloop",
1490
- "version": "0.1.7"
1490
+ "version": "0.1.8"
1491
1491
  }
1492
1492
  }
1493
1493
  }
@@ -1579,7 +1579,7 @@ def process_request(req: Dict[str, Any]) -> Optional[Dict[str, Any]]:
1579
1579
  def handle_cli_update() -> int:
1580
1580
  """Handle `firmwareloop update` command."""
1581
1581
  print("============================================================")
1582
- print(" FirmwareLoop Auto-Updater (v0.1.7)")
1582
+ print(" FirmwareLoop Auto-Updater (v0.1.8)")
1583
1583
  print("============================================================")
1584
1584
 
1585
1585
  is_git_repo = os.path.exists(os.path.join(REPO_ROOT, ".git"))
@@ -1849,7 +1849,7 @@ def set_ndtdbg_dlldir(dll_path: str) -> str:
1849
1849
  def handle_cli_setup() -> int:
1850
1850
  """Handle `firmwareloop setup` command: simultaneously configure Antigravity CLI, Claude Code CLI, Qoder IDE, and CodeBuddy IDE."""
1851
1851
  print("============================================================")
1852
- print(" FirmwareLoop Multi-Agent Global Setup Helper (v0.1.7)")
1852
+ print(" FirmwareLoop Multi-Agent Global Setup Helper (v0.1.8)")
1853
1853
  print("============================================================")
1854
1854
  print("")
1855
1855
 
@@ -1974,7 +1974,7 @@ def handle_cli_setup() -> int:
1974
1974
  def handle_cli_init() -> int:
1975
1975
  """Handle `firmwareloop init` command."""
1976
1976
  print("============================================================")
1977
- print(" FirmwareLoop Multi-Agent Project Initializer (v0.1.7)")
1977
+ print(" FirmwareLoop Multi-Agent Project Initializer (v0.1.8)")
1978
1978
  print("============================================================")
1979
1979
  cwd = os.getcwd()
1980
1980
  print(f"[*] Initializing multi-agent guidelines & bench config in:\n {cwd}\n")
@@ -1995,7 +1995,7 @@ def handle_cli_init() -> int:
1995
1995
 
1996
1996
 
1997
1997
  def print_cli_help() -> None:
1998
- print("""FirmwareLoop (fwloop) — AI Agent Firmware Engineering & Lab Automation Platform (v0.1.7)
1998
+ print("""FirmwareLoop (fwloop) — AI Agent Firmware Engineering & Lab Automation Platform (v0.1.8)
1999
1999
 
2000
2000
  Usage:
2001
2001
  fwloop [command] (or: firmwareloop [command])
@@ -2035,7 +2035,7 @@ def main() -> None:
2035
2035
  sys.exit(2)
2036
2036
  sys.exit(handle_cli_ndtdebugtool(args[1]))
2037
2037
  elif cmd in ["version", "-v", "--version"]:
2038
- print("FirmwareLoop v0.1.7")
2038
+ print("FirmwareLoop v0.1.8")
2039
2039
  sys.exit(0)
2040
2040
  elif cmd in ["help", "-h", "--help"]:
2041
2041
  print_cli_help()
@@ -217,6 +217,64 @@ class NdtBoxIo:
217
217
  data = self._bridge("raw", WriteHex=write.hex(), ReadLen=read_len)
218
218
  return bytes.fromhex(data["hex"].replace(" ", "")) if read_len > 0 else None
219
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
+
220
278
  def vbrt_reset(self, off_ms: int = 200) -> dict:
221
279
  """模组断电 off_ms 后恢复上电 (usb2io.dll IO10)——复位进入 IIC boot (IOoperation.VBRTOFF)。"""
222
280
  return self._bridge("vbrt", OffMs=off_ms)
@@ -290,60 +348,43 @@ class ChipSeaFxx:
290
348
 
291
349
  def flash_bin(self, file_path: str, erase_ms: int = 2000,
292
350
  progress=None) -> dict:
293
- """经 NdtBox IIC 烧录 .bin/.nfw 固件到芯海 Fxx。
351
+ """经 NdtBox IIC 烧录 .bin/.nfw 固件到芯海 Fxx (单进程 flash action)。
294
352
 
295
- 时序逐行对齐 ChipSea.cs UpdateFW:
296
- LoadFile -> VBRTOFF(200) 断电复位进 boot -> 100ms -> 擦除魔数 -> erase_ms ->
297
- 逐 128B 页写(30ms 间隔) -> 200ms -> 逐 256B 窗回读校验(10ms 间隔) ->
298
- 完成魔数 "PASs" -> VBRTOFF(200) 复位加载新固件。
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)。
299
360
 
300
- progress(phase, done, total) 在 write/verify 阶段回调。
301
- 校验不匹配抛 TARGET_MISMATCH (Fail-Closed, 不伪造成功)。
361
+ progress(phase, done, total) 在 verify 阶段末回调 (逐帧 #progress 由桥转发到 stderr)。
302
362
  """
303
363
  import time
304
-
305
364
  fw_type, fw = load_firmware(file_path)
306
365
  started = time.monotonic()
307
- pages = [fw[i:i + FLASH_WRITE_PAGE] for i in range(0, len(fw), FLASH_WRITE_PAGE)]
366
+ pages = list(range(0, len(fw), FLASH_WRITE_PAGE))
308
367
  windows = list(range(0, len(fw), FLASH_READ_WINDOW))
309
-
310
- self._io.vbrt_reset(200)
311
- time.sleep(BOOT_READY_MS / 1000.0)
312
- self._io.raw_write_read(b"\x00\x00" + ERASE_MAGIC, 0)
313
- time.sleep(0.010)
314
- time.sleep(erase_ms / 1000.0)
315
-
316
- for i, page in enumerate(pages):
317
- self._io.raw_write_read(build_page_write_frame(i * FLASH_WRITE_PAGE, page), 0)
318
- time.sleep(PAGE_WRITE_GAP_MS / 1000.0)
319
- if progress:
320
- progress("write", i + 1, len(pages))
321
-
322
- time.sleep(0.200)
323
- for i, off in enumerate(windows):
324
- expect = expected_read_window(fw, off)
325
- got = self._io.raw_write_read(off.to_bytes(2, "big"), FLASH_READ_WINDOW)
326
- if got != expect:
327
- diff = next(k for k in range(FLASH_READ_WINDOW) if got[k] != expect[k])
328
- raise NdtBoxError(
329
- "TARGET_MISMATCH",
330
- f"烧录校验失败 @0x{off + diff:04X}",
331
- f"expect={expect[diff:diff + 8].hex()} got={got[diff:diff + 8].hex()}")
332
- time.sleep(PAGE_READ_GAP_MS / 1000.0)
333
- if progress:
334
- progress("verify", i + 1, len(windows))
335
-
336
- self._io.raw_write_read(b"\x00\x00" + FINISH_MAGIC, 0)
337
- time.sleep(0.010)
338
- self._io.vbrt_reset(200)
339
-
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))
340
381
  return {
341
382
  "file": file_path,
342
383
  "file_type": fw_type,
343
384
  "fw_len": len(fw),
344
385
  "pages_written": len(pages),
345
386
  "read_windows": len(windows),
346
- "erase_ms": erase_ms,
347
- "verify": "pass",
387
+ "erase_ms": int(erase_ms),
388
+ "verify": res.get("verify", "pass"),
348
389
  "duration_ms": int((time.monotonic() - started) * 1000),
349
390
  }
@@ -4,7 +4,7 @@
4
4
  # 输出: stdout 单行 JSON {ok, action, data} 或 {ok:false, error_class, error, detail}。
5
5
  param(
6
6
  [Parameter(Mandatory = $true)]
7
- [ValidateSet("open", "read", "write", "version", "connect", "poll", "close", "raw", "vbrt")]
7
+ [ValidateSet("open", "read", "write", "version", "connect", "poll", "close", "raw", "vbrt", "run", "flash")]
8
8
  [string]$Action,
9
9
 
10
10
  [string]$DllDir,
@@ -245,13 +245,18 @@ try {
245
245
  }
246
246
  "raw" {
247
247
  # 对齐 ChipSea.cs 的多字节地址事务: 写 [slave, wBuf...] 再读 ReadLen 字节 (IIC.WriteRead 语义)
248
+ # 注意: $wb 不含从机地址, 必须前置 $SlaveAddr 为事务首字节 (USBIO_StreamI2C 以 wBuffer[0] 为从机地址)。
249
+ # 历史上曾遗漏该前置, 导致首字节(页地址)被当作从机地址, 事务从未到达芯片, 校验读回全 FF。
248
250
  $wb = Parse-Hex $WriteHex
249
251
  if ($wb.Count -eq 0) { Fail "CONFIG_ERROR" "raw 需要 -WriteHex (不含从机地址的写事务字节)" $null }
250
252
  if (($wb.Count + 1) -gt 256) { Fail "CONFIG_ERROR" "raw 写事务过长 (含从机地址须 <=256)" $wb.Count }
251
253
  if ($ReadLen -lt 0 -or $ReadLen -gt 256) { Fail "CONFIG_ERROR" "raw -ReadLen 越界 (0..256)" $ReadLen }
254
+ $tx = New-Object byte[] ($wb.Count + 1)
255
+ $tx[0] = [byte]$SlaveAddr
256
+ [Array]::Copy($wb, 0, $tx, 1, $wb.Count)
252
257
  $h = Open-Device 0
253
258
  $rb = New-Object byte[] $ReadLen
254
- if (-not (Invoke-WriteRead $h $wb $rb)) { Fail "TARGET_MISMATCH" ("IIC raw 事务无应答 (w={0}B r={1}B)" -f $wb.Count, $ReadLen) (($wb | ForEach-Object { $_.ToString("X2") }) -join " ") }
259
+ if (-not (Invoke-WriteRead $h $tx $rb)) { Fail "TARGET_MISMATCH" ("IIC raw 事务无应答 (w={0}B r={1}B)" -f $tx.Count, $ReadLen) (($tx | ForEach-Object { $_.ToString("X2") }) -join " ") }
255
260
  [STDataCollection.Icb]::USBIO_CloseDevice([uint32]0) | Out-Null
256
261
  $dataOut = @{ written = $wb.Count; read = $ReadLen }
257
262
  if ($ReadLen -gt 0) { $dataOut.hex = (($rb | ForEach-Object { $_.ToString("X2") }) -join " ") }
@@ -271,6 +276,170 @@ try {
271
276
  Write-Output (@{ ok = $true; action = "vbrt"; data = @{ off_ms = $OffMs; powered_on = $true } } | ConvertTo-Json -Compress -Depth 4)
272
277
  exit 0
273
278
  }
279
+ "run" {
280
+ # 批量执行 ops (stdin JSON 数组), 单进程内完成——避免烧录等高频路径逐帧 spawn 32 位 PS 的进程开销。
281
+ # ops item: {"op":"sleep","ms":N} | {"op":"w","hex":"..."} | {"op":"r","hex":"<addr>","len":N}
282
+ # w/r 均前置 $SlaveAddr (同 raw); r 的读回按序收集进 read_hexes (无空格 hex)。
283
+ # 进度行以 "#progress " 前缀写 stdout (非 JSON, Python 侧转发到 stderr), 最终结果为末行 JSON。
284
+ $opsJson = [Console]::In.ReadToEnd()
285
+ $ops = $opsJson | ConvertFrom-Json
286
+ if (-not $ops) { Fail "CONFIG_ERROR" "run 需要 stdin 传入 ops JSON 数组" $null }
287
+ $h = Open-Device 0
288
+ $readBacks = New-Object System.Collections.ArrayList
289
+ $wTotal = 0; $rTotal = 0
290
+ foreach ($op in $ops) { if ($op.op -eq "w") { $wTotal++ } elseif ($op.op -eq "r") { $rTotal++ } }
291
+ $wIdx = 0; $rIdx = 0
292
+ foreach ($op in $ops) {
293
+ if ($op.op -eq "sleep") {
294
+ Start-Sleep -Milliseconds ([int]$op.ms)
295
+ continue
296
+ }
297
+ if ($op.op -eq "w") {
298
+ $wb = Parse-Hex ([string]$op.hex)
299
+ if ($wb.Count -eq 0) { Fail "CONFIG_ERROR" "run 写帧为空 (w #$($wIdx + 1))" $null }
300
+ if (($wb.Count + 1) -gt 256) { Fail "CONFIG_ERROR" "run 写帧过长 (w #$($wIdx + 1))" $wb.Count }
301
+ $tx = New-Object byte[] ($wb.Count + 1)
302
+ $tx[0] = [byte]$SlaveAddr
303
+ [Array]::Copy($wb, 0, $tx, 1, $wb.Count)
304
+ if (-not (Invoke-WriteRead $h $tx (New-Object byte[] 0))) {
305
+ Fail "TARGET_MISMATCH" ("IIC run 写无应答 (w {0}/{1})" -f ($wIdx + 1), $wTotal) (($tx | ForEach-Object { $_.ToString("X2") }) -join " ")
306
+ }
307
+ $wIdx++
308
+ Write-Output ("#progress write {0}/{1}" -f $wIdx, $wTotal)
309
+ continue
310
+ }
311
+ if ($op.op -eq "r") {
312
+ $ab = Parse-Hex ([string]$op.hex)
313
+ $len = [int]$op.len
314
+ if ($ab.Count -eq 0) { Fail "CONFIG_ERROR" "run 读地址为空 (r #$($rIdx + 1))" $null }
315
+ if ($len -lt 1 -or $len -gt 256) { Fail "CONFIG_ERROR" "run 读长度越界 (r #$($rIdx + 1))" $len }
316
+ $tx = New-Object byte[] ($ab.Count + 1)
317
+ $tx[0] = [byte]$SlaveAddr
318
+ [Array]::Copy($ab, 0, $tx, 1, $ab.Count)
319
+ $rb = New-Object byte[] $len
320
+ if (-not (Invoke-WriteRead $h $tx $rb)) {
321
+ Fail "TARGET_MISMATCH" ("IIC run 读无应答 (r {0}/{1})" -f ($rIdx + 1), $rTotal) (($tx | ForEach-Object { $_.ToString("X2") }) -join " ")
322
+ }
323
+ [void]$readBacks.Add((($rb | ForEach-Object { $_.ToString("X2") }) -join ""))
324
+ $rIdx++
325
+ Write-Output ("#progress read {0}/{1}" -f $rIdx, $rTotal)
326
+ continue
327
+ }
328
+ Fail "CONFIG_ERROR" ("run 未知 op: {0}" -f $op.op) $null
329
+ }
330
+ [STDataCollection.Icb]::USBIO_CloseDevice([uint32]0) | Out-Null
331
+ Write-Output (@{ ok = $true; action = "run"; data = @{ writes = $wIdx; reads = $rIdx; read_hexes = @($readBacks) } } | ConvertTo-Json -Compress -Depth 5)
332
+ exit 0
333
+ }
334
+ "flash" {
335
+ # 单进程完整固件烧录 (UpdateFW 语义, 复刻 STDataCollection GUI 进程模型):
336
+ # usb2io(IO10) 断电复位进 boot -> 100ms -> 擦除魔数 -> erase_ms ->
337
+ # stdin 页写帧(128B/页, page_gap) -> 200ms -> stdin 期望窗(256B) 逐窗比对 ->
338
+ # 完成魔数 -> usb2io 断电复位加载。
339
+ # usb2io 与 USBIOX 句柄在本进程内同时持有且全程连续——GUI 常驻进程模型,
340
+ # 消除跨进程的"断电->擦除"间隔 (须落在 app 升级服务的等待窗口内)。
341
+ # stdin JSON: {"off_ms":200,"erase_ms":2000,"page_gap_ms":30,"read_gap_ms":10,
342
+ # "writes":["<帧 hex: 2B 大端地址+128B 数据>",...],
343
+ # "windows":[{"off":0,"expect_hex":"<256B hex>"},...]}
344
+ $planJson = [Console]::In.ReadToEnd()
345
+ $plan = $planJson | ConvertFrom-Json
346
+ if (-not $plan) { Fail "CONFIG_ERROR" "flash 需要 stdin 传入 plan JSON" $null }
347
+ $offMs = [int]$plan.off_ms
348
+ $eraseMs = [int]$plan.erase_ms
349
+ $pageGap = [int]$plan.page_gap_ms
350
+ $readGap = [int]$plan.read_gap_ms
351
+ $h = Open-Device 0
352
+ $u = [STDataCollection.U2io]::USB2IO_Open(1)
353
+ if ([int]$u -eq -1) {
354
+ [STDataCollection.Icb]::USBIO_CloseDevice([uint32]0) | Out-Null
355
+ Fail "PROBE_NOT_FOUND" "USB2IO 打开失败 (设备 1); 确认 DllDir 含 usb2io.dll 且 NdtBox 已连接" $null
356
+ }
357
+ $wTotal = 0; $rTotal = 0
358
+ try {
359
+ # --- 断电复位进 boot (IOoperation.VBRTOFF) ---
360
+ [STDataCollection.U2io]::USB2IO_SetIoCfg($u, 10, 3) | Out-Null
361
+ [STDataCollection.U2io]::USB2IO_SetIoOut($u, 10, 1) | Out-Null
362
+ Start-Sleep -Milliseconds $offMs
363
+ [STDataCollection.U2io]::USB2IO_SetIoOut($u, 10, 0) | Out-Null
364
+ Start-Sleep -Milliseconds 50
365
+ Start-Sleep -Milliseconds 100 # boot ready (UpdateFW Thread.Sleep(100))
366
+
367
+ # --- 整片擦除 (EreaseChipCmd) ---
368
+ $magic = [byte[]](0xAA, 0x55, 0xA5, 0x5A)
369
+ $eb = New-Object byte[] 7
370
+ $eb[0] = [byte]$SlaveAddr
371
+ $eb[1] = 0; $eb[2] = 0
372
+ [Array]::Copy($magic, 0, $eb, 3, 4)
373
+ if (-not (Invoke-WriteRead $h $eb (New-Object byte[] 0))) {
374
+ Fail "TARGET_MISMATCH" "擦除命令无应答 (StreamI2C=false)" (($eb | ForEach-Object { $_.ToString("X2") }) -join " ")
375
+ }
376
+ Start-Sleep -Milliseconds $eraseMs
377
+
378
+ # --- 逐页写 (WriteFw) ---
379
+ $writes = @($plan.writes)
380
+ $wTotal = $writes.Count
381
+ for ($wi = 0; $wi -lt $wTotal; $wi++) {
382
+ $wb = Parse-Hex ([string]$writes[$wi])
383
+ if (($wb.Count + 1) -gt 256) { Fail "CONFIG_ERROR" "flash 页写帧过长 (页 $wi)" $wb.Count }
384
+ $tx = New-Object byte[] ($wb.Count + 1)
385
+ $tx[0] = [byte]$SlaveAddr
386
+ [Array]::Copy($wb, 0, $tx, 1, $wb.Count)
387
+ if (-not (Invoke-WriteRead $h $tx (New-Object byte[] 0))) {
388
+ Fail "TARGET_MISMATCH" ("页写无应答 (write {0}/{1})" -f ($wi + 1), $wTotal) (($tx | ForEach-Object { $_.ToString("X2") }) -join " ")
389
+ }
390
+ if ($pageGap -gt 0) { Start-Sleep -Milliseconds $pageGap }
391
+ Write-Output ("#progress write {0}/{1}" -f ($wi + 1), $wTotal)
392
+ }
393
+ Start-Sleep -Milliseconds 200 # WriteFw 后至校验前 (Thread.Sleep(200))
394
+
395
+ # --- 逐窗回读校验 (ReadFwAndCheck) ---
396
+ $windows = @($plan.windows)
397
+ $rTotal = $windows.Count
398
+ for ($ri = 0; $ri -lt $rTotal; $ri++) {
399
+ $op = $windows[$ri]
400
+ $off = [int]$op.off
401
+ $rb = New-Object byte[] 256
402
+ $tx = New-Object byte[] 3
403
+ $tx[0] = [byte]$SlaveAddr
404
+ $tx[1] = [byte](($off -shr 8) -band 0xFF)
405
+ $tx[2] = [byte]($off -band 0xFF)
406
+ if (-not (Invoke-WriteRead $h $tx $rb)) {
407
+ Fail "TARGET_MISMATCH" ("回读无应答 (read {0}/{1} @0x{2:X4})" -f ($ri + 1), $rTotal, $off) $null
408
+ }
409
+ $gotHex = (($rb | ForEach-Object { $_.ToString("X2") }) -join "").ToLower()
410
+ $expHex = [string]$op.expect_hex
411
+ if ($gotHex -cne $expHex) {
412
+ $k = 0
413
+ while ($k -lt $gotHex.Length -and $gotHex.Substring($k, 2) -eq $expHex.Substring($k, 2)) { $k += 2 }
414
+ Fail "TARGET_MISMATCH" ("烧录校验失败 @0x{0:X4}" -f ($off + ($k / 2))) ("expect={0} got={1}" -f $expHex.Substring($k, 16), $gotHex.Substring($k, 16))
415
+ }
416
+ if ($readGap -gt 0) { Start-Sleep -Milliseconds $readGap }
417
+ Write-Output ("#progress read {0}/{1}" -f ($ri + 1), $rTotal)
418
+ }
419
+
420
+ # --- 完成魔数 (UpgradeFinishCmd "PASs") ---
421
+ $fmagic = [byte[]](0x50, 0x41, 0x53, 0x73)
422
+ $fb = New-Object byte[] 7
423
+ $fb[0] = [byte]$SlaveAddr
424
+ $fb[1] = 0; $fb[2] = 0
425
+ [Array]::Copy($fmagic, 0, $fb, 3, 4)
426
+ if (-not (Invoke-WriteRead $h $fb (New-Object byte[] 0))) {
427
+ Fail "TARGET_MISMATCH" "完成魔数无应答 (StreamI2C=false)" (($fb | ForEach-Object { $_.ToString("X2") }) -join " ")
428
+ }
429
+ Start-Sleep -Milliseconds 10
430
+
431
+ # --- 结束断电复位加载新固件 ---
432
+ [STDataCollection.U2io]::USB2IO_SetIoOut($u, 10, 1) | Out-Null
433
+ Start-Sleep -Milliseconds $offMs
434
+ [STDataCollection.U2io]::USB2IO_SetIoOut($u, 10, 0) | Out-Null
435
+ Start-Sleep -Milliseconds 50
436
+ } finally {
437
+ [STDataCollection.U2io]::USB2IO_Close($u) | Out-Null
438
+ [STDataCollection.Icb]::USBIO_CloseDevice([uint32]0) | Out-Null
439
+ }
440
+ Write-Output (@{ ok = $true; action = "flash"; data = @{ verify = "pass"; pages_written = $wTotal; read_windows = $rTotal } } | ConvertTo-Json -Compress -Depth 4)
441
+ exit 0
442
+ }
274
443
  }
275
444
  } catch {
276
445
  Fail "UNKNOWN_ERROR" "bridge 异常: $($_.Exception.Message)" $_.ScriptStackTrace
@@ -1,6 +1,6 @@
1
1
  <#
2
2
  .SYNOPSIS
3
- Dual-Tier MCP setup and registration helper (v0.1.7).
3
+ Dual-Tier MCP setup and registration helper (v0.1.8).
4
4
  Inspects installed AI coding agents (Qoder, Claude Code, Antigravity, Cursor)
5
5
  and configures global MCP / skills or prints exact registration commands.
6
6
 
@@ -242,7 +242,7 @@ if ($Json) {
242
242
  Write-FwJson ([pscustomobject]$report)
243
243
  } else {
244
244
  Write-Host "============================================================" -ForegroundColor Cyan
245
- Write-Host " FirmwareLoop Dual-Tier MCP & Agent Setup Helper (v0.1.7)" -ForegroundColor Green
245
+ Write-Host " FirmwareLoop Dual-Tier MCP & Agent Setup Helper (v0.1.8)" -ForegroundColor Green
246
246
  Write-Host "============================================================" -ForegroundColor Cyan
247
247
  Write-Host ""
248
248
  Write-Host "[1] Google Antigravity / Gemini CLI:" -ForegroundColor Yellow
File without changes
File without changes
File without changes