firmwareloop 0.7.0__tar.gz → 0.7.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.
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/PKG-INFO +7 -7
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/README.md +5 -5
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/firmwareloop.egg-info/PKG-INFO +7 -7
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/firmwareloop.egg-info/requires.txt +1 -1
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/pyproject.toml +2 -2
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/tools/__init__.py +1 -1
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/tools/fw_mcp_server.py +17 -10
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/tools/lib/ndtdbg.py +115 -35
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/tools/ndtdbg_cli.py +15 -5
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/tools/setup-agent-mcp.ps1 +2 -2
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/LICENSE +0 -0
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/firmwareloop.egg-info/SOURCES.txt +0 -0
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/firmwareloop.egg-info/dependency_links.txt +0 -0
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/firmwareloop.egg-info/entry_points.txt +0 -0
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/firmwareloop.egg-info/top_level.txt +0 -0
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/setup.cfg +0 -0
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/tools/acceptance-scenario.ps1 +0 -0
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/tools/build.ps1 +0 -0
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/tools/can.ps1 +0 -0
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/tools/check-qoder-mcp.ps1 +0 -0
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/tools/common/build-backends.psm1 +0 -0
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/tools/common/fw.psm1 +0 -0
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/tools/common/uart_probe.py +0 -0
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/tools/doctor.ps1 +0 -0
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/tools/flash.ps1 +0 -0
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/tools/instrument_cli.py +0 -0
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/tools/lib/__init__.py +0 -0
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/tools/lib/instruments.py +0 -0
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/tools/lib/serial_assistant.py +0 -0
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/tools/logic_capture.ps1 +0 -0
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/tools/logic_decode.ps1 +0 -0
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/tools/ndtdbg_bridge.ps1 +0 -0
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/tools/reset.ps1 +0 -0
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/tools/serial_cli.py +0 -0
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/tools/sigrok_csv_to_fwloop.py +0 -0
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/tools/test-backends.ps1 +0 -0
- {firmwareloop-0.7.0 → firmwareloop-0.7.1}/tools/test.ps1 +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: firmwareloop
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.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
|
|
@@ -18,7 +18,7 @@ Requires-Python: >=3.10
|
|
|
18
18
|
Description-Content-Type: text/markdown
|
|
19
19
|
License-File: LICENSE
|
|
20
20
|
Requires-Dist: pyvisa>=1.14.0
|
|
21
|
-
Requires-Dist: pyvisa-py>=0.7.
|
|
21
|
+
Requires-Dist: pyvisa-py>=0.7.1
|
|
22
22
|
Requires-Dist: pyserial>=3.5
|
|
23
23
|
Requires-Dist: pytest>=8.0.0
|
|
24
24
|
Requires-Dist: pyocd>=0.36.0
|
|
@@ -383,7 +383,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
383
383
|
├── demo-firmware/ 示例固件(CMake;宿主编译模拟 MCU,闭环验证载体)
|
|
384
384
|
├── demo-make/ 示例固件(Make;构建测试载体)
|
|
385
385
|
├── docs/ DEPENDENCY_MATRIX / REUSE_PLAN / V0.0.2_GAP_VERIFICATION
|
|
386
|
-
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.7.
|
|
386
|
+
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.7.1)
|
|
387
387
|
├── .mcp.example.json 双层 MCP 配置模板(firmwareloop + agentic-hil)
|
|
388
388
|
├── AGENTS.md 通用智能体规范(Antigravity / Qoder / Cursor 等)
|
|
389
389
|
├── CLAUDE.md Claude Code CLI 指南
|
|
@@ -393,7 +393,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
393
393
|
|
|
394
394
|
---
|
|
395
395
|
|
|
396
|
-
## 能力验证状态(v0.7.
|
|
396
|
+
## 能力验证状态(v0.7.1)
|
|
397
397
|
|
|
398
398
|
> 状态定义:`Implemented`(已实现)/ `Simulator Validated`(模拟验证)/
|
|
399
399
|
> `Real Hardware Validated`(真机验证)/ `Experimental` / `Not Implemented`
|
|
@@ -416,7 +416,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
416
416
|
| 自动修复闭环(M6 载体) | Simulator Validated -> Real Hardware Validated 待 DUT |
|
|
417
417
|
| NdtBox 数据采集/调试(fw_ndtdbg_status / fw_ndtdbg_measure, ChipSea Fxx IIC) | Implemented(无硬件 Fail-Closed 已验证)-> Real Hardware Validated 待 NdtBox |
|
|
418
418
|
| NdtBox 目标断电复位(fw_ndtdbg_power_cycle, 仅 io10 断电复位, 无写闸门) | Implemented(单测覆盖时序/探针缺失指引)-> Real Hardware Validated 待 NdtBox |
|
|
419
|
-
| NdtBox IIC 固件烧录(fw_ndtdbg_flash, .bin/.nfw 整片擦写, 芯海 Fxx) | Real Hardware Validated(84.87.1.1 / 84.87.1.2 真机整片擦写 verify pass;双闸门 Fail-Closed)+ v0.7.
|
|
419
|
+
| NdtBox IIC 固件烧录(fw_ndtdbg_flash, .bin/.nfw 整片擦写, 芯海 Fxx) | Real Hardware Validated(84.87.1.1 / 84.87.1.2 真机整片擦写 verify pass;双闸门 Fail-Closed)+ v0.7.1 **差分 boot 判据**(真机实测: 擦除后读 0x0000 = 00 00 00 00, 旧"全 FF"判据在该 ROM 上永久误判 → 已改为"仅同一条非平凡应答才熔断", 并提供 `boot_judge: off` 关闭开关) |
|
|
420
420
|
| 抓开机日志(fw_serial_capture + power_cycle_off_ms, 窗口内断电复位) | Implemented(simulator 验证窗口与复位串联)-> Real Hardware Validated 待 NdtBox + DUT |
|
|
421
421
|
|
|
422
422
|
---
|
|
@@ -430,8 +430,8 @@ claude mcp remove --scope user agentic-hil
|
|
|
430
430
|
| 写操作 `PERMISSION_DENIED` | 全局 `lab.yaml` 的 `ndtdbg.write_allowed` 为 `false` → 临时改 `true` 并重启 Agent(测试完建议改回 `false`) |
|
|
431
431
|
| sigrok 报 `Failed to receive reply to EP1 command 0x7d` | Saleae 驱动不是 libusb-win32 → 用 Zadig 重装;或设备被 Logic 软件占用(先退出) |
|
|
432
432
|
| `fw_logic_decode` 空帧 | 抓取窗口内无总线事务 → 改用 `fw_ndtdbg_capture_seq`(抓取与寄存器序列同窗口进行),不要串行先抓后写 |
|
|
433
|
-
| 烧录报 `BOOT_NOT_ENTERED
|
|
434
|
-
| `fw_ndtdbg_flash` 报 `TARGET_MISMATCH: 烧录校验失败 @0x....` |
|
|
433
|
+
| 烧录报 `BOOT_NOT_ENTERED`(擦除魔数前后 0x0000 读回**完全相同**的非平凡数据) | 差分判据认定应答者仍是运行态 App(擦除被忽略)→ 仅对目标板断电重启(勿动 BOX)后重试;反复出现时确认 `off_ms`(默认 500ms,独立看门狗芯片需真 POR)与 io10 供电复位接线。若你确认目标 ROM 的 0x0000 语义特殊,用 `boot_judge: off`(lab.yaml/CLI `--boot-judge off`/MCP `boot_judge`)关闭判据,让逐窗校验做唯一判据 |
|
|
434
|
+
| `fw_ndtdbg_flash` 报 `TARGET_MISMATCH: 烧录校验失败 @0x....` | 校验是**权威判据**(不是探测启发式)。信封带 `stage_timings`/`probe_before_hex`/`probe_hex`/`boot_evidence`/`first_mismatch_*`:`boot_evidence=erase_effective` 说明擦除生效 → 看失配偏移是否恒定(恒定=页写未落地);`inconclusive` 说明探测无区分度(擦除后读回全 00/全 FF)→ 需人工确认是否真进了 boot;整片重写次数由 `rewrite_times` 控制(默认 3) |
|
|
435
435
|
| 串口 `SERIAL_PORT_ERROR: ... PermissionError(13)` | 端口被 GUI 上位机独占(NDT Debug Tool / STDataCollection.exe、串口助手)→ 关闭占用方后 `fw_serial_open`;残留会话用 `fw_serial_close`(已自动等待/强杀进程,端口释放可验证) |
|
|
436
436
|
| `fw_serial_status` 的 `error_class` 为 `UART_BUSY` | 会话未打开或已被占用 → 先 `fw_serial_open`;残留会话 `fw_serial_close` 清理 |
|
|
437
437
|
|
|
@@ -355,7 +355,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
355
355
|
├── demo-firmware/ 示例固件(CMake;宿主编译模拟 MCU,闭环验证载体)
|
|
356
356
|
├── demo-make/ 示例固件(Make;构建测试载体)
|
|
357
357
|
├── docs/ DEPENDENCY_MATRIX / REUSE_PLAN / V0.0.2_GAP_VERIFICATION
|
|
358
|
-
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.7.
|
|
358
|
+
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.7.1)
|
|
359
359
|
├── .mcp.example.json 双层 MCP 配置模板(firmwareloop + agentic-hil)
|
|
360
360
|
├── AGENTS.md 通用智能体规范(Antigravity / Qoder / Cursor 等)
|
|
361
361
|
├── CLAUDE.md Claude Code CLI 指南
|
|
@@ -365,7 +365,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
365
365
|
|
|
366
366
|
---
|
|
367
367
|
|
|
368
|
-
## 能力验证状态(v0.7.
|
|
368
|
+
## 能力验证状态(v0.7.1)
|
|
369
369
|
|
|
370
370
|
> 状态定义:`Implemented`(已实现)/ `Simulator Validated`(模拟验证)/
|
|
371
371
|
> `Real Hardware Validated`(真机验证)/ `Experimental` / `Not Implemented`
|
|
@@ -388,7 +388,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
388
388
|
| 自动修复闭环(M6 载体) | Simulator Validated -> Real Hardware Validated 待 DUT |
|
|
389
389
|
| NdtBox 数据采集/调试(fw_ndtdbg_status / fw_ndtdbg_measure, ChipSea Fxx IIC) | Implemented(无硬件 Fail-Closed 已验证)-> Real Hardware Validated 待 NdtBox |
|
|
390
390
|
| NdtBox 目标断电复位(fw_ndtdbg_power_cycle, 仅 io10 断电复位, 无写闸门) | Implemented(单测覆盖时序/探针缺失指引)-> Real Hardware Validated 待 NdtBox |
|
|
391
|
-
| NdtBox IIC 固件烧录(fw_ndtdbg_flash, .bin/.nfw 整片擦写, 芯海 Fxx) | Real Hardware Validated(84.87.1.1 / 84.87.1.2 真机整片擦写 verify pass;双闸门 Fail-Closed)+ v0.7.
|
|
391
|
+
| NdtBox IIC 固件烧录(fw_ndtdbg_flash, .bin/.nfw 整片擦写, 芯海 Fxx) | Real Hardware Validated(84.87.1.1 / 84.87.1.2 真机整片擦写 verify pass;双闸门 Fail-Closed)+ v0.7.1 **差分 boot 判据**(真机实测: 擦除后读 0x0000 = 00 00 00 00, 旧"全 FF"判据在该 ROM 上永久误判 → 已改为"仅同一条非平凡应答才熔断", 并提供 `boot_judge: off` 关闭开关) |
|
|
392
392
|
| 抓开机日志(fw_serial_capture + power_cycle_off_ms, 窗口内断电复位) | Implemented(simulator 验证窗口与复位串联)-> Real Hardware Validated 待 NdtBox + DUT |
|
|
393
393
|
|
|
394
394
|
---
|
|
@@ -402,8 +402,8 @@ claude mcp remove --scope user agentic-hil
|
|
|
402
402
|
| 写操作 `PERMISSION_DENIED` | 全局 `lab.yaml` 的 `ndtdbg.write_allowed` 为 `false` → 临时改 `true` 并重启 Agent(测试完建议改回 `false`) |
|
|
403
403
|
| sigrok 报 `Failed to receive reply to EP1 command 0x7d` | Saleae 驱动不是 libusb-win32 → 用 Zadig 重装;或设备被 Logic 软件占用(先退出) |
|
|
404
404
|
| `fw_logic_decode` 空帧 | 抓取窗口内无总线事务 → 改用 `fw_ndtdbg_capture_seq`(抓取与寄存器序列同窗口进行),不要串行先抓后写 |
|
|
405
|
-
| 烧录报 `BOOT_NOT_ENTERED
|
|
406
|
-
| `fw_ndtdbg_flash` 报 `TARGET_MISMATCH: 烧录校验失败 @0x....` |
|
|
405
|
+
| 烧录报 `BOOT_NOT_ENTERED`(擦除魔数前后 0x0000 读回**完全相同**的非平凡数据) | 差分判据认定应答者仍是运行态 App(擦除被忽略)→ 仅对目标板断电重启(勿动 BOX)后重试;反复出现时确认 `off_ms`(默认 500ms,独立看门狗芯片需真 POR)与 io10 供电复位接线。若你确认目标 ROM 的 0x0000 语义特殊,用 `boot_judge: off`(lab.yaml/CLI `--boot-judge off`/MCP `boot_judge`)关闭判据,让逐窗校验做唯一判据 |
|
|
406
|
+
| `fw_ndtdbg_flash` 报 `TARGET_MISMATCH: 烧录校验失败 @0x....` | 校验是**权威判据**(不是探测启发式)。信封带 `stage_timings`/`probe_before_hex`/`probe_hex`/`boot_evidence`/`first_mismatch_*`:`boot_evidence=erase_effective` 说明擦除生效 → 看失配偏移是否恒定(恒定=页写未落地);`inconclusive` 说明探测无区分度(擦除后读回全 00/全 FF)→ 需人工确认是否真进了 boot;整片重写次数由 `rewrite_times` 控制(默认 3) |
|
|
407
407
|
| 串口 `SERIAL_PORT_ERROR: ... PermissionError(13)` | 端口被 GUI 上位机独占(NDT Debug Tool / STDataCollection.exe、串口助手)→ 关闭占用方后 `fw_serial_open`;残留会话用 `fw_serial_close`(已自动等待/强杀进程,端口释放可验证) |
|
|
408
408
|
| `fw_serial_status` 的 `error_class` 为 `UART_BUSY` | 会话未打开或已被占用 → 先 `fw_serial_open`;残留会话 `fw_serial_close` 清理 |
|
|
409
409
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: firmwareloop
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.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
|
|
@@ -18,7 +18,7 @@ Requires-Python: >=3.10
|
|
|
18
18
|
Description-Content-Type: text/markdown
|
|
19
19
|
License-File: LICENSE
|
|
20
20
|
Requires-Dist: pyvisa>=1.14.0
|
|
21
|
-
Requires-Dist: pyvisa-py>=0.7.
|
|
21
|
+
Requires-Dist: pyvisa-py>=0.7.1
|
|
22
22
|
Requires-Dist: pyserial>=3.5
|
|
23
23
|
Requires-Dist: pytest>=8.0.0
|
|
24
24
|
Requires-Dist: pyocd>=0.36.0
|
|
@@ -383,7 +383,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
383
383
|
├── demo-firmware/ 示例固件(CMake;宿主编译模拟 MCU,闭环验证载体)
|
|
384
384
|
├── demo-make/ 示例固件(Make;构建测试载体)
|
|
385
385
|
├── docs/ DEPENDENCY_MATRIX / REUSE_PLAN / V0.0.2_GAP_VERIFICATION
|
|
386
|
-
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.7.
|
|
386
|
+
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.7.1)
|
|
387
387
|
├── .mcp.example.json 双层 MCP 配置模板(firmwareloop + agentic-hil)
|
|
388
388
|
├── AGENTS.md 通用智能体规范(Antigravity / Qoder / Cursor 等)
|
|
389
389
|
├── CLAUDE.md Claude Code CLI 指南
|
|
@@ -393,7 +393,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
393
393
|
|
|
394
394
|
---
|
|
395
395
|
|
|
396
|
-
## 能力验证状态(v0.7.
|
|
396
|
+
## 能力验证状态(v0.7.1)
|
|
397
397
|
|
|
398
398
|
> 状态定义:`Implemented`(已实现)/ `Simulator Validated`(模拟验证)/
|
|
399
399
|
> `Real Hardware Validated`(真机验证)/ `Experimental` / `Not Implemented`
|
|
@@ -416,7 +416,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
416
416
|
| 自动修复闭环(M6 载体) | Simulator Validated -> Real Hardware Validated 待 DUT |
|
|
417
417
|
| NdtBox 数据采集/调试(fw_ndtdbg_status / fw_ndtdbg_measure, ChipSea Fxx IIC) | Implemented(无硬件 Fail-Closed 已验证)-> Real Hardware Validated 待 NdtBox |
|
|
418
418
|
| NdtBox 目标断电复位(fw_ndtdbg_power_cycle, 仅 io10 断电复位, 无写闸门) | Implemented(单测覆盖时序/探针缺失指引)-> Real Hardware Validated 待 NdtBox |
|
|
419
|
-
| NdtBox IIC 固件烧录(fw_ndtdbg_flash, .bin/.nfw 整片擦写, 芯海 Fxx) | Real Hardware Validated(84.87.1.1 / 84.87.1.2 真机整片擦写 verify pass;双闸门 Fail-Closed)+ v0.7.
|
|
419
|
+
| NdtBox IIC 固件烧录(fw_ndtdbg_flash, .bin/.nfw 整片擦写, 芯海 Fxx) | Real Hardware Validated(84.87.1.1 / 84.87.1.2 真机整片擦写 verify pass;双闸门 Fail-Closed)+ v0.7.1 **差分 boot 判据**(真机实测: 擦除后读 0x0000 = 00 00 00 00, 旧"全 FF"判据在该 ROM 上永久误判 → 已改为"仅同一条非平凡应答才熔断", 并提供 `boot_judge: off` 关闭开关) |
|
|
420
420
|
| 抓开机日志(fw_serial_capture + power_cycle_off_ms, 窗口内断电复位) | Implemented(simulator 验证窗口与复位串联)-> Real Hardware Validated 待 NdtBox + DUT |
|
|
421
421
|
|
|
422
422
|
---
|
|
@@ -430,8 +430,8 @@ claude mcp remove --scope user agentic-hil
|
|
|
430
430
|
| 写操作 `PERMISSION_DENIED` | 全局 `lab.yaml` 的 `ndtdbg.write_allowed` 为 `false` → 临时改 `true` 并重启 Agent(测试完建议改回 `false`) |
|
|
431
431
|
| sigrok 报 `Failed to receive reply to EP1 command 0x7d` | Saleae 驱动不是 libusb-win32 → 用 Zadig 重装;或设备被 Logic 软件占用(先退出) |
|
|
432
432
|
| `fw_logic_decode` 空帧 | 抓取窗口内无总线事务 → 改用 `fw_ndtdbg_capture_seq`(抓取与寄存器序列同窗口进行),不要串行先抓后写 |
|
|
433
|
-
| 烧录报 `BOOT_NOT_ENTERED
|
|
434
|
-
| `fw_ndtdbg_flash` 报 `TARGET_MISMATCH: 烧录校验失败 @0x....` |
|
|
433
|
+
| 烧录报 `BOOT_NOT_ENTERED`(擦除魔数前后 0x0000 读回**完全相同**的非平凡数据) | 差分判据认定应答者仍是运行态 App(擦除被忽略)→ 仅对目标板断电重启(勿动 BOX)后重试;反复出现时确认 `off_ms`(默认 500ms,独立看门狗芯片需真 POR)与 io10 供电复位接线。若你确认目标 ROM 的 0x0000 语义特殊,用 `boot_judge: off`(lab.yaml/CLI `--boot-judge off`/MCP `boot_judge`)关闭判据,让逐窗校验做唯一判据 |
|
|
434
|
+
| `fw_ndtdbg_flash` 报 `TARGET_MISMATCH: 烧录校验失败 @0x....` | 校验是**权威判据**(不是探测启发式)。信封带 `stage_timings`/`probe_before_hex`/`probe_hex`/`boot_evidence`/`first_mismatch_*`:`boot_evidence=erase_effective` 说明擦除生效 → 看失配偏移是否恒定(恒定=页写未落地);`inconclusive` 说明探测无区分度(擦除后读回全 00/全 FF)→ 需人工确认是否真进了 boot;整片重写次数由 `rewrite_times` 控制(默认 3) |
|
|
435
435
|
| 串口 `SERIAL_PORT_ERROR: ... PermissionError(13)` | 端口被 GUI 上位机独占(NDT Debug Tool / STDataCollection.exe、串口助手)→ 关闭占用方后 `fw_serial_open`;残留会话用 `fw_serial_close`(已自动等待/强杀进程,端口释放可验证) |
|
|
436
436
|
| `fw_serial_status` 的 `error_class` 为 `UART_BUSY` | 会话未打开或已被占用 → 先 `fw_serial_open`;残留会话 `fw_serial_close` 清理 |
|
|
437
437
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "firmwareloop"
|
|
7
|
-
version = "0.7.
|
|
7
|
+
version = "0.7.1"
|
|
8
8
|
description = "AI Agent Firmware Development and Lab Automation Platform (Dual-Tier MCP Server)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
@@ -14,7 +14,7 @@ license = { text = "MIT" }
|
|
|
14
14
|
requires-python = ">=3.10"
|
|
15
15
|
dependencies = [
|
|
16
16
|
"pyvisa>=1.14.0",
|
|
17
|
-
"pyvisa-py>=0.7.
|
|
17
|
+
"pyvisa-py>=0.7.1",
|
|
18
18
|
"pyserial>=3.5",
|
|
19
19
|
"pytest>=8.0.0",
|
|
20
20
|
"pyocd>=0.36.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.7.
|
|
3
|
+
fw_mcp_server.py - FirmwareLoop High-Level Workflow & Device MCP Server (v0.7.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, CodeBuddy IDE, Cursor) via the Model Context
|
|
@@ -332,7 +332,7 @@ def handle_fw_ndtdbg_write_reg(args: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
332
332
|
def handle_fw_ndtdbg_flash(args: Dict[str, Any]) -> Dict[str, Any]:
|
|
333
333
|
"""Erase + flash a .bin/.nfw image onto the NdtBox target chip (ChipSea Fxx) over IIC.
|
|
334
334
|
|
|
335
|
-
P1-3 (0.7.
|
|
335
|
+
P1-3 (0.7.1): 去掉按签名重试 —— "TARGET_MISMATCH @0x0000" 正是"根本没进 boot"的
|
|
336
336
|
特征签名 (旧 boot 判据假阳的同一条错误路径), 盲目重试只会重复同一失败路径。
|
|
337
337
|
整片重写 (<= rewrite_times) 现由 flash_exec 内部完成, 结果带 rewrite_count;
|
|
338
338
|
BOOT_NOT_ENTERED 直接返回指引 (需断电重启, 重试无意义)。
|
|
@@ -344,6 +344,8 @@ def handle_fw_ndtdbg_flash(args: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
344
344
|
cmd.extend(["--off-ms", str(int(args["off_ms"]))])
|
|
345
345
|
if args.get("rewrite_times") is not None:
|
|
346
346
|
cmd.extend(["--rewrite-times", str(max(0, int(args["rewrite_times"])))])
|
|
347
|
+
if args.get("boot_judge"):
|
|
348
|
+
cmd.extend(["--boot-judge", str(args["boot_judge"])])
|
|
347
349
|
if args.get("confirm"):
|
|
348
350
|
cmd.append("--confirm")
|
|
349
351
|
_ndtdbg_reg_args(args, cmd)
|
|
@@ -1770,7 +1772,7 @@ TOOLS_REGISTRY = {
|
|
|
1770
1772
|
"handler": handle_fw_ndtdbg_write_reg,
|
|
1771
1773
|
},
|
|
1772
1774
|
"fw_ndtdbg_flash": {
|
|
1773
|
-
"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 -> BOOT JUDGEMENT: 0x0000
|
|
1775
|
+
"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 -> differential BOOT JUDGEMENT: 0x0000 is sampled once BEFORE the power cycle and once AFTER the erase; only an identical NON-TRIVIAL reading (the same App answering before and after a command only the boot ROM executes) raises BOOT_NOT_ENTERED - all-00/all-FF/no-ACK are inconclusive and proceed, because a real ROM may answer 0x0000 with 00 00 00 00 after erase -> 128B page writes -> 256B read-back verify = the authoritative criterion, whole-chip rewrite on mismatch -> finish 'PASs'). DESTRUCTIVE full-chip erase: requires lab.yaml ndtdbg.write_allowed: true AND confirm: true, otherwise PERMISSION_DENIED.",
|
|
1774
1776
|
"inputSchema": {
|
|
1775
1777
|
"type": "object",
|
|
1776
1778
|
"properties": {
|
|
@@ -1799,7 +1801,12 @@ TOOLS_REGISTRY = {
|
|
|
1799
1801
|
},
|
|
1800
1802
|
"rewrite_times": {
|
|
1801
1803
|
"type": "integer",
|
|
1802
|
-
"description": "Whole-chip rewrite budget when read-back verify mismatches (aligned with ChipSea.cs REWRITE_TIMES). Default 3; 0 disables rewriting. The result carries rewrite_count/attempts, and a failed flash reports stage_timings/probe_hex/first_mismatch_* for post-mortem",
|
|
1804
|
+
"description": "Whole-chip rewrite budget when read-back verify mismatches (aligned with ChipSea.cs REWRITE_TIMES). Default 3; 0 disables rewriting. The result carries rewrite_count/attempts, and a failed flash reports stage_timings/probe_before_hex/probe_hex/boot_evidence/first_mismatch_* for post-mortem",
|
|
1805
|
+
},
|
|
1806
|
+
"boot_judge": {
|
|
1807
|
+
"type": "string",
|
|
1808
|
+
"enum": ["diff", "off"],
|
|
1809
|
+
"description": "Boot-probe criterion. 'diff' (default): compare 0x0000 before the power cycle vs after the erase, and raise BOOT_NOT_ENTERED only when both reads are identical NON-TRIVIAL data (proof the same running App answered the erase magic). 'off': only record probe evidence (probe_before_hex/probe_hex/boot_evidence) and never block - the 256B read-back verify stays the sole criterion, i.e. exactly the bridge/GUI behaviour. Use 'off' if a target ROM reports an unexpected 0x0000 signature",
|
|
1803
1810
|
},
|
|
1804
1811
|
"dll_dir": {
|
|
1805
1812
|
"type": "string",
|
|
@@ -2300,7 +2307,7 @@ def process_request(req: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
|
2300
2307
|
},
|
|
2301
2308
|
"serverInfo": {
|
|
2302
2309
|
"name": "firmwareloop",
|
|
2303
|
-
"version": "0.7.
|
|
2310
|
+
"version": "0.7.1"
|
|
2304
2311
|
}
|
|
2305
2312
|
}
|
|
2306
2313
|
}
|
|
@@ -2435,7 +2442,7 @@ def _settle_stashed_entrypoints(stashed) -> None:
|
|
|
2435
2442
|
def handle_cli_update() -> int:
|
|
2436
2443
|
"""Handle `firmwareloop update` command."""
|
|
2437
2444
|
print("============================================================")
|
|
2438
|
-
print(" FirmwareLoop Auto-Updater (v0.7.
|
|
2445
|
+
print(" FirmwareLoop Auto-Updater (v0.7.1)")
|
|
2439
2446
|
print("============================================================")
|
|
2440
2447
|
|
|
2441
2448
|
is_git_repo = os.path.exists(os.path.join(REPO_ROOT, ".git"))
|
|
@@ -2866,7 +2873,7 @@ def set_ndtdbg_dlldir(dll_path: str) -> str:
|
|
|
2866
2873
|
def handle_cli_setup() -> int:
|
|
2867
2874
|
"""Handle `firmwareloop setup` command: simultaneously configure Antigravity CLI, Claude Code CLI, Qoder IDE, and CodeBuddy IDE."""
|
|
2868
2875
|
print("============================================================")
|
|
2869
|
-
print(" FirmwareLoop Multi-Agent Global Setup Helper (v0.7.
|
|
2876
|
+
print(" FirmwareLoop Multi-Agent Global Setup Helper (v0.7.1)")
|
|
2870
2877
|
print("============================================================")
|
|
2871
2878
|
print("")
|
|
2872
2879
|
|
|
@@ -2991,7 +2998,7 @@ def handle_cli_setup() -> int:
|
|
|
2991
2998
|
def handle_cli_init(chip: Optional[str] = None, upgrade: bool = False) -> int:
|
|
2992
2999
|
"""Handle `firmwareloop init [target_chip]` command."""
|
|
2993
3000
|
print("============================================================")
|
|
2994
|
-
print(" FirmwareLoop Multi-Agent Project Initializer (v0.7.
|
|
3001
|
+
print(" FirmwareLoop Multi-Agent Project Initializer (v0.7.1)")
|
|
2995
3002
|
print("============================================================")
|
|
2996
3003
|
cwd = os.getcwd()
|
|
2997
3004
|
print(f"[*] Initializing multi-agent guidelines & bench config in:\n {cwd}\n")
|
|
@@ -3024,7 +3031,7 @@ def handle_cli_init(chip: Optional[str] = None, upgrade: bool = False) -> int:
|
|
|
3024
3031
|
|
|
3025
3032
|
|
|
3026
3033
|
def print_cli_help() -> None:
|
|
3027
|
-
print("""FirmwareLoop (fwloop) — AI Agent Firmware Engineering & Lab Automation Platform (v0.7.
|
|
3034
|
+
print("""FirmwareLoop (fwloop) — AI Agent Firmware Engineering & Lab Automation Platform (v0.7.1)
|
|
3028
3035
|
|
|
3029
3036
|
Usage:
|
|
3030
3037
|
fwloop [command] (or: firmwareloop [command])
|
|
@@ -3070,7 +3077,7 @@ def main() -> None:
|
|
|
3070
3077
|
sys.exit(2)
|
|
3071
3078
|
sys.exit(handle_cli_ndtdebugtool(args[1]))
|
|
3072
3079
|
elif cmd in ["version", "-v", "--version"]:
|
|
3073
|
-
print("FirmwareLoop v0.7.
|
|
3080
|
+
print("FirmwareLoop v0.7.1")
|
|
3074
3081
|
sys.exit(0)
|
|
3075
3082
|
elif cmd in ["help", "-h", "--help"]:
|
|
3076
3083
|
print_cli_help()
|
|
@@ -262,9 +262,9 @@ PAGE_READ_GAP_MS = 10 # 回读窗口间隔 (ReadFwAndCheck Thread.Sleep(10)
|
|
|
262
262
|
def _flash_envelope(timings: dict | None = None, **extra) -> dict:
|
|
263
263
|
"""P1-2: 烧录成功/失败共用同一组诊断字段。
|
|
264
264
|
|
|
265
|
-
分阶段耗时 (stage_timings) + boot 探测原始数据 (probe_hex) +
|
|
266
|
-
(first_mismatch_offset/expect/got) +
|
|
267
|
-
能当场区分
|
|
265
|
+
分阶段耗时 (stage_timings) + boot 探测原始数据 (probe_before_hex/probe_hex) +
|
|
266
|
+
判据结论 (boot_evidence) + 首个失配点 (first_mismatch_offset/expect/got) +
|
|
267
|
+
页数与重写次数——失败不再是"只有一句话", 能当场区分"根本没进 boot"与"页写没落地"。
|
|
268
268
|
"""
|
|
269
269
|
out: dict = {"stage_timings": dict(timings or {})}
|
|
270
270
|
for key, val in extra.items():
|
|
@@ -273,6 +273,38 @@ def _flash_envelope(timings: dict | None = None, **extra) -> dict:
|
|
|
273
273
|
return out
|
|
274
274
|
|
|
275
275
|
|
|
276
|
+
BOOT_JUDGE_MODES = ("diff", "off")
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def classify_boot_evidence(before: bytes | None, after: bytes | None) -> str:
|
|
280
|
+
"""烧录 boot 判据 (0.7.1 差分判据, 取代被实战证伪的"擦除后必须全 FF")。
|
|
281
|
+
|
|
282
|
+
返回: 'app_still_answering' (唯一熔断情形) | 'erase_effective' | 'inconclusive' | 'not_evaluated'
|
|
283
|
+
|
|
284
|
+
实战铁证 (2026-09-11, CSA37F72): 擦除后读 0x0000 = `00 00 00 00` 而非 FF ——
|
|
285
|
+
桥内单体 flash (无判据、时序同 GUI) 对同一批文件全部 verify pass ⇒ "全 FF" 只是
|
|
286
|
+
该 ROM 的**未验证假设**, 一旦当硬判据就会永久误判 BOOT_NOT_ENTERED。
|
|
287
|
+
|
|
288
|
+
现判据只在"有正面证据表明应答者仍是运行态固件"时熔断:
|
|
289
|
+
* 两次读数不同 (含"有应答 -> 无应答") ⇒ 擦除生效 ⇒ 继续;
|
|
290
|
+
* 任一侧无应答 (None) ⇒ 无法比对 ⇒ 不确定, 继续;
|
|
291
|
+
* 两次相同但内容是全 00/全 FF ⇒ 无法区分 (本 ROM 擦除后正是全 00, 而某些固件
|
|
292
|
+
的静态寄存器也可能全 0) ⇒ 不确定, 继续;
|
|
293
|
+
* 两次相同且是非平凡数据 (如 `25 01 18 01`) ⇒ 擦除魔数 (boot ROM 专属命令)
|
|
294
|
+
前后是同一条应答 ⇒ 魔数被忽略, 应答者仍是 App ⇒ 熔断。
|
|
295
|
+
一切不确定情形都放行, 由页写后的逐窗回读校验做权威兜底 (fail-closed)。
|
|
296
|
+
"""
|
|
297
|
+
if before is None and after is None:
|
|
298
|
+
return "inconclusive"
|
|
299
|
+
if before is None or after is None:
|
|
300
|
+
return "erase_effective" # 一侧有内容: 相较之下状态变了
|
|
301
|
+
if before != after:
|
|
302
|
+
return "erase_effective"
|
|
303
|
+
if all(b in (0x00, 0xFF) for b in after):
|
|
304
|
+
return "inconclusive" # 全 00/全 FF 无区分度
|
|
305
|
+
return "app_still_answering"
|
|
306
|
+
|
|
307
|
+
|
|
276
308
|
def load_firmware(file_path: str) -> tuple[str, bytes]:
|
|
277
309
|
"""读取固件: .nfw 去 256 头 / .bin 128 对齐 0xFF 补齐 (ChipSea.cs LoadFile)。返回 (类型, 载荷)。"""
|
|
278
310
|
if not os.path.isfile(file_path):
|
|
@@ -615,25 +647,38 @@ class NdtBoxIo:
|
|
|
615
647
|
"duration_actual_ms": int((time.monotonic() - started) * 1000),
|
|
616
648
|
}
|
|
617
649
|
|
|
650
|
+
def _probe_0000(self) -> bytes | None:
|
|
651
|
+
"""读 0x0000 4 字节 (boot 判据探测): 有应答返回字节, 无应答 (TARGET_MISMATCH) 返回 None。
|
|
652
|
+
|
|
653
|
+
BUS_TIMEOUT (clock-stretch 挂死) / PROTOCOL_ERROR (会话错位) 上抛——这两种状态下
|
|
654
|
+
任何继续操作都不可信 (fail-closed)。
|
|
655
|
+
"""
|
|
656
|
+
try:
|
|
657
|
+
return self.raw_write_read(b"\x00\x00", 4)
|
|
658
|
+
except NdtBoxError as exc:
|
|
659
|
+
if exc.error_class == "TARGET_MISMATCH":
|
|
660
|
+
return None
|
|
661
|
+
raise
|
|
662
|
+
|
|
618
663
|
def flash_exec(self, plan: dict, timeout_ms: int | None = None) -> dict:
|
|
619
664
|
"""Python 驱动整条 UpdateFW 序列 (0.4.0 serve 化): usb2io 断电复位进 boot +
|
|
620
665
|
擦除 -> 页写 -> 回读校验 -> 完成魔数, 全部 I2C 事务经 serve 会话 (同进程同
|
|
621
666
|
会话连续, 复刻原厂 GUI 进程模型)。校验 (逐窗 vs expect_hex) 在本模块完成,
|
|
622
|
-
失败 Fail-Closed 抛 TARGET_MISMATCH
|
|
667
|
+
并是 burn 成功与否的**权威判据**; 失败 Fail-Closed 抛 TARGET_MISMATCH
|
|
668
|
+
(信封带 stage_timings/probe_*/boot_evidence/first_mismatch_*)。
|
|
623
669
|
校验不一致时按 rewrite_times 整片重写 (对齐 ChipSea.cs REWRITE_TIMES)。
|
|
624
670
|
|
|
625
671
|
plan: {"off_ms":500,"erase_ms":2000,"page_gap_ms":30,"read_gap_ms":10,
|
|
626
|
-
"rewrite_times":3,
|
|
672
|
+
"rewrite_times":3,"boot_judge":"diff",
|
|
627
673
|
"writes":["<2B 地址+128B 数据帧 hex>",...],
|
|
628
674
|
"windows":[{"off":0,"expect_hex":"<256B hex>"},...]}
|
|
629
675
|
|
|
630
|
-
boot
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
时序随之与 GUI/桥内 flash 等价: vbrt -> 100ms(boot ready) -> 擦除魔数。
|
|
676
|
+
boot 判据 (0.7.1 差分判据; 详见 classify_boot_evidence):
|
|
677
|
+
断电复位**前**读一次 0x0000 作为基线 (在关键窗口之外, 不影响"断电->擦除"间隔,
|
|
678
|
+
时序仍与 GUI/桥内 flash 等价: vbrt -> 100ms(boot ready) -> 擦除魔数), 擦除
|
|
679
|
+
+ erase_ms 后再读一次, 只在"两次是同一条非平凡应答"时判 BOOT_NOT_ENTERED;
|
|
680
|
+
全 00/全 FF/任一侧无应答一律放行, 由页写后的逐窗校验兜底。boot_judge="off"
|
|
681
|
+
完全关闭该判据 (仅记录证据, 不熔断)。
|
|
637
682
|
"""
|
|
638
683
|
import time
|
|
639
684
|
off_ms = int(plan.get("off_ms", DEFAULT_OFF_MS))
|
|
@@ -641,6 +686,10 @@ class NdtBoxIo:
|
|
|
641
686
|
page_gap = int(plan.get("page_gap_ms", PAGE_WRITE_GAP_MS))
|
|
642
687
|
read_gap = int(plan.get("read_gap_ms", PAGE_READ_GAP_MS))
|
|
643
688
|
rewrite_times = max(0, int(plan.get("rewrite_times", _REWRITE_TIMES)))
|
|
689
|
+
boot_judge = str(plan.get("boot_judge", "diff") or "diff").lower()
|
|
690
|
+
if boot_judge not in BOOT_JUDGE_MODES:
|
|
691
|
+
raise NdtBoxError("CONFIG_ERROR",
|
|
692
|
+
f"boot_judge 非法 (可选 {'/'.join(BOOT_JUDGE_MODES)})", boot_judge)
|
|
644
693
|
pages = plan.get("writes", [])
|
|
645
694
|
windows = plan.get("windows", [])
|
|
646
695
|
t_all = time.monotonic()
|
|
@@ -648,9 +697,20 @@ class NdtBoxIo:
|
|
|
648
697
|
timings = {"power_cycle_ms": 0, "boot_probe_ms": 0, "erase_ms": 0,
|
|
649
698
|
"write_ms": 0, "verify_ms": 0}
|
|
650
699
|
probe_hex: str | None = None
|
|
700
|
+
probe_before_hex: str | None = None
|
|
701
|
+
boot_evidence = "not_evaluated" if boot_judge == "off" else "inconclusive"
|
|
651
702
|
rewrite_count = 0
|
|
652
703
|
with self.session():
|
|
653
704
|
for attempt in range(rewrite_times + 1):
|
|
705
|
+
# --- 0) 基線探测 (仅在首试判据开启时): 断电复位前采样 0x0000 ---
|
|
706
|
+
# 放在 vbrt 之前 => "断电 -> 擦除魔数" 窗口内不多任何事务 (与桥内
|
|
707
|
+
# 单体 flash/GUI 时序一致), 同时拿到"运行态固件应答长什么样"的基线。
|
|
708
|
+
if boot_judge == "diff" and attempt == 0:
|
|
709
|
+
t_stage = time.monotonic()
|
|
710
|
+
probe_before = self._probe_0000()
|
|
711
|
+
if probe_before:
|
|
712
|
+
probe_before_hex = probe_before.hex().upper()
|
|
713
|
+
timings["boot_probe_ms"] += round((time.monotonic() - t_stage) * 1000)
|
|
654
714
|
# --- 1) 断电复位进 boot (IOoperation.VBRTOFF) ---
|
|
655
715
|
t_stage = time.monotonic()
|
|
656
716
|
self.vbrt_reset(off_ms)
|
|
@@ -668,6 +728,8 @@ class NdtBoxIo:
|
|
|
668
728
|
"擦除命令超时无应答 (目标未进 boot 或总线挂死)。请仅对目标板断电重启 "
|
|
669
729
|
"(勿动 BOX), 然后重新调用 fw_ndtdbg_flash", exc.detail,
|
|
670
730
|
_flash_envelope(timings, probe_hex=probe_hex,
|
|
731
|
+
probe_before_hex=probe_before_hex,
|
|
732
|
+
boot_evidence=boot_evidence,
|
|
671
733
|
rewrite_count=rewrite_count)) from exc
|
|
672
734
|
raise NdtBoxError(
|
|
673
735
|
"BOOT_NOT_ENTERED",
|
|
@@ -677,34 +739,38 @@ class NdtBoxIo:
|
|
|
677
739
|
"独立看门狗芯片需要真断电)",
|
|
678
740
|
exc.detail,
|
|
679
741
|
_flash_envelope(timings, probe_hex=probe_hex,
|
|
742
|
+
probe_before_hex=probe_before_hex,
|
|
743
|
+
boot_evidence=boot_evidence,
|
|
680
744
|
rewrite_count=rewrite_count)) from exc
|
|
681
745
|
time.sleep(erase_ms / 1000.0)
|
|
682
746
|
timings["erase_ms"] += round((time.monotonic() - t_stage) * 1000)
|
|
683
|
-
# --- 3) boot 判据 (
|
|
747
|
+
# --- 3) boot 判据 (差分: 只有"魔数前后同一条非平凡应答"才熔断) ---
|
|
684
748
|
t_stage = time.monotonic()
|
|
685
|
-
probe
|
|
686
|
-
try:
|
|
687
|
-
probe = self.raw_write_read(b"\x00\x00", 4)
|
|
688
|
-
except NdtBoxError as exc:
|
|
689
|
-
if exc.error_class == "BUS_TIMEOUT":
|
|
690
|
-
raise # clock-stretch 挂死: 上抛 (信封已带断电指引)
|
|
691
|
-
probe = None # 无应答: ROM 在 boot 窗口内的读协议不确定, 交给后续校验兜底
|
|
749
|
+
probe = self._probe_0000()
|
|
692
750
|
timings["boot_probe_ms"] += round((time.monotonic() - t_stage) * 1000)
|
|
693
751
|
if probe:
|
|
694
752
|
probe_hex = probe.hex().upper()
|
|
695
|
-
if
|
|
753
|
+
if boot_judge == "diff" and attempt == 0:
|
|
754
|
+
boot_evidence = classify_boot_evidence(
|
|
755
|
+
bytes.fromhex(probe_before_hex) if probe_before_hex else None, probe)
|
|
756
|
+
elif boot_judge == "off":
|
|
757
|
+
boot_evidence = "not_evaluated"
|
|
758
|
+
# 重写轮次不再判据 (基线已过期), 证据沿用首试结论
|
|
759
|
+
if boot_evidence == "app_still_answering":
|
|
696
760
|
raise NdtBoxError(
|
|
697
761
|
"BOOT_NOT_ENTERED",
|
|
698
|
-
f"
|
|
699
|
-
"
|
|
700
|
-
"工具读协议的寄存器, 实测 iic_test[8] @0x00 = 25 01 18 01 02 05 00 01)。"
|
|
762
|
+
f"擦除魔数前后 0x0000 读回完全相同 ({probe_hex}): 擦除被忽略 ⇒ 目标未进入 "
|
|
763
|
+
"boot ROM (该命令只有 boot ROM 执行, 同一条应答说明应答者仍是运行态固件)。"
|
|
701
764
|
"请仅对目标板断电重启 (勿动 BOX) 后重试; 若反复出现: ①确认 off_ms 足以构成"
|
|
702
765
|
"真 POR (默认 500ms, 独立看门狗芯片只有上电/掉电/复位脚能清零); ②检查 "
|
|
703
766
|
"usb2io IO10 供电复位接线; ③确认烧录的是本芯片 boot ROM 支持的协议; "
|
|
704
767
|
"④若目标固件由你 (agent) 改动过, 先确认它确实使能了 I2C 从机 (从机未 init "
|
|
705
|
-
"或提前 return 时该地址不应答, 现象会变成读回全 FF, 与掉电不可区分)"
|
|
768
|
+
"或提前 return 时该地址不应答, 现象会变成读回全 FF, 与掉电不可区分)。"
|
|
769
|
+
"如仍要强制烧录: lab.yaml ndtdbg.flash.boot_judge: off / CLI --boot-judge off",
|
|
706
770
|
None,
|
|
707
771
|
_flash_envelope(timings, probe_hex=probe_hex,
|
|
772
|
+
probe_before_hex=probe_before_hex,
|
|
773
|
+
boot_evidence=boot_evidence,
|
|
708
774
|
pages_written=0, rewrite_count=rewrite_count))
|
|
709
775
|
# --- 4) 逐页写 (WriteFw) ---
|
|
710
776
|
t_stage = time.monotonic()
|
|
@@ -739,11 +805,17 @@ class NdtBoxIo:
|
|
|
739
805
|
if attempt < rewrite_times:
|
|
740
806
|
rewrite_count += 1 # 整片重写 (ChipSea.cs REWRITE_TIMES 语义)
|
|
741
807
|
continue
|
|
808
|
+
hint = ("校验是烧录成功与否的权威判据 (非探测启发式): 若 probe 前后一致且为"
|
|
809
|
+
"全 00/全 FF, 说明本次未进 boot 与'擦除生效'无法区分——请检查目标是否"
|
|
810
|
+
"真的断电复位 (off_ms/IO10 接线)、以及目标固件是否使能了 I2C 从机; "
|
|
811
|
+
"必要时可 --boot-judge off 关闭探测判据 (不改变校验)")
|
|
742
812
|
raise NdtBoxError(
|
|
743
813
|
"TARGET_MISMATCH",
|
|
744
814
|
f"烧录校验失败 @0x{mismatch['offset']:04X} (整片重写 {rewrite_count} 次后仍不一致)",
|
|
745
|
-
f"expect={mismatch['expect']} got={mismatch['got']}",
|
|
815
|
+
f"expect={mismatch['expect']} got={mismatch['got']}; {hint}",
|
|
746
816
|
_flash_envelope(timings, probe_hex=probe_hex,
|
|
817
|
+
probe_before_hex=probe_before_hex,
|
|
818
|
+
boot_evidence=boot_evidence,
|
|
747
819
|
pages_written=len(pages),
|
|
748
820
|
pages_written_total=len(pages) * (rewrite_count + 1),
|
|
749
821
|
rewrite_count=rewrite_count,
|
|
@@ -759,7 +831,9 @@ class NdtBoxIo:
|
|
|
759
831
|
timings["total_ms"] = round((time.monotonic() - t_all) * 1000)
|
|
760
832
|
out = {"verify": "pass", "pages_written": len(pages),
|
|
761
833
|
"read_windows": len(windows), "rewrite_count": rewrite_count,
|
|
762
|
-
"attempts": rewrite_count + 1,
|
|
834
|
+
"attempts": rewrite_count + 1,
|
|
835
|
+
"probe_hex": probe_hex, "probe_before_hex": probe_before_hex,
|
|
836
|
+
"boot_evidence": boot_evidence, "boot_judge": boot_judge,
|
|
763
837
|
"off_ms": off_ms, "stage_timings": timings}
|
|
764
838
|
# 兼容 0.6.x 的扁平键 (P3 分阶段耗时): 由 stage_timings 派生, 单一来源
|
|
765
839
|
out.update({"reset_ms": timings["power_cycle_ms"],
|
|
@@ -1118,21 +1192,22 @@ class ChipSeaFxx:
|
|
|
1118
1192
|
|
|
1119
1193
|
def flash_bin(self, file_path: str, erase_ms: int = 2000,
|
|
1120
1194
|
off_ms: int | None = None, rewrite_times: int | None = None,
|
|
1121
|
-
progress=None) -> dict:
|
|
1195
|
+
boot_judge: str | None = None, progress=None) -> dict:
|
|
1122
1196
|
"""经 NdtBox IIC 烧录 .bin/.nfw 固件到芯海 Fxx (UpdateFW 语义)。
|
|
1123
1197
|
|
|
1124
1198
|
时序逐行对齐 ChipSea.cs UpdateFW, 进程模型对齐原厂 STDataCollection GUI:
|
|
1125
|
-
VBRTOFF(off_ms) 断电复位进 boot
|
|
1126
|
-
|
|
1127
|
-
逐
|
|
1128
|
-
完成魔数 "PASs" -> VBRTOFF
|
|
1199
|
+
[判据基线探测 0x0000 (在窗口之外)] -> VBRTOFF(off_ms) 断电复位进 boot
|
|
1200
|
+
-> 100ms -> 擦除魔数 -> erase_ms -> 差分 boot 判据 ->
|
|
1201
|
+
逐 128B 页写(30ms) -> 200ms -> 逐 256B 窗回读校验(10ms, 权威判据)
|
|
1202
|
+
-> 不一致整片重写 (<= rewrite_times) -> 完成魔数 "PASs" -> VBRTOFF 复位加载。
|
|
1129
1203
|
全程经同一 serve 会话连续执行 (同进程同 USB 会话), ~15s 量级。
|
|
1130
1204
|
帧组装在本层 (可单测); 校验失败 Fail-Closed 抛 NdtBoxError, 信封带
|
|
1131
|
-
stage_timings/probe_hex/first_mismatch_* (P1-2)。
|
|
1205
|
+
stage_timings/probe_before_hex/probe_hex/boot_evidence/first_mismatch_* (P1-2)。
|
|
1132
1206
|
|
|
1133
1207
|
off_ms 默认 DEFAULT_OFF_MS=500 (独立看门狗芯片需真 POR); rewrite_times
|
|
1134
|
-
默认 _REWRITE_TIMES=3 (对齐 ChipSea.cs)
|
|
1135
|
-
|
|
1208
|
+
默认 _REWRITE_TIMES=3 (对齐 ChipSea.cs); boot_judge 默认 "diff" (差分判据,
|
|
1209
|
+
只在有正面证据表明应答者仍是运行态固件时熔断; "off" = 完全关闭, 仅记录证据)。
|
|
1210
|
+
progress(phase, done, total) 在 verify 阶段末回调。
|
|
1136
1211
|
"""
|
|
1137
1212
|
import time
|
|
1138
1213
|
fw_type, fw = load_firmware(file_path)
|
|
@@ -1145,6 +1220,7 @@ class ChipSeaFxx:
|
|
|
1145
1220
|
"page_gap_ms": PAGE_WRITE_GAP_MS,
|
|
1146
1221
|
"read_gap_ms": PAGE_READ_GAP_MS,
|
|
1147
1222
|
"rewrite_times": _REWRITE_TIMES if rewrite_times is None else max(0, int(rewrite_times)),
|
|
1223
|
+
"boot_judge": str(boot_judge or "diff").lower(),
|
|
1148
1224
|
"writes": [build_page_write_frame(off, fw[off:off + FLASH_WRITE_PAGE]).hex()
|
|
1149
1225
|
for off in pages],
|
|
1150
1226
|
"windows": [{"off": off, "expect_hex": expected_read_window(fw, off).hex()}
|
|
@@ -1162,6 +1238,10 @@ class ChipSeaFxx:
|
|
|
1162
1238
|
"erase_ms": int(erase_ms),
|
|
1163
1239
|
"off_ms": plan["off_ms"],
|
|
1164
1240
|
"rewrite_times": plan["rewrite_times"],
|
|
1241
|
+
"boot_judge": plan["boot_judge"],
|
|
1242
|
+
"boot_evidence": res.get("boot_evidence"),
|
|
1243
|
+
"probe_hex": res.get("probe_hex"),
|
|
1244
|
+
"probe_before_hex": res.get("probe_before_hex"),
|
|
1165
1245
|
"rewrite_count": res.get("rewrite_count", 0),
|
|
1166
1246
|
"attempts": res.get("attempts", 1),
|
|
1167
1247
|
"stage_timings": res.get("stage_timings"),
|
|
@@ -504,7 +504,8 @@ def cmd_stream(io: NdtBoxIo, config: dict, specs: list, interval_ms, duration_s:
|
|
|
504
504
|
|
|
505
505
|
|
|
506
506
|
def cmd_flash_bin(io: NdtBoxIo, config: dict, file_path: str, erase_ms: int, confirm: bool,
|
|
507
|
-
off_ms: int | None = None, rewrite_times: int | None = None
|
|
507
|
+
off_ms: int | None = None, rewrite_times: int | None = None,
|
|
508
|
+
boot_judge: str | None = None) -> None:
|
|
508
509
|
# 烧录 = 整片擦除 + 页写, 破坏性高于 write-reg, 同款双闸门
|
|
509
510
|
ndt_cfg = config.get("ndtdbg") or {}
|
|
510
511
|
allowed = ndt_cfg.get("write_allowed", False)
|
|
@@ -516,22 +517,27 @@ def cmd_flash_bin(io: NdtBoxIo, config: dict, file_path: str, erase_ms: int, con
|
|
|
516
517
|
f"生效配置: {effective_config_desc()}"))
|
|
517
518
|
# P1-1: off_ms 可配置 (CLI --off-ms > lab.yaml ndtdbg.flash.off_ms > 内置 500ms)。
|
|
518
519
|
# 独立看门狗芯片 (如 CSA37F72 IWDG_S) 只有真 POR 能清零, 200ms 可能只是深度掉压。
|
|
520
|
+
flash_cfg = ndt_cfg.get("flash") or {}
|
|
519
521
|
if off_ms is None:
|
|
520
|
-
flash_cfg = ndt_cfg.get("flash") or {}
|
|
521
522
|
off_ms = flash_cfg.get("off_ms")
|
|
523
|
+
# 0.7.1: boot 判据可整体关闭 (差分判据仍是启发式; 现场遇到与本 ROM 行为不符的
|
|
524
|
+
# 目标时, 用 off 让页写后的逐窗校验做唯一判据 —— 即桥内单体 flash 的语义)
|
|
525
|
+
if boot_judge is None:
|
|
526
|
+
boot_judge = flash_cfg.get("boot_judge")
|
|
522
527
|
|
|
523
528
|
def progress(phase: str, done: int, total: int) -> None:
|
|
524
529
|
print(f"[flash-bin] {phase} {done}/{total}", file=sys.stderr, flush=True)
|
|
525
530
|
|
|
526
531
|
result = ChipSeaFxx(io).flash_bin(file_path, erase_ms=erase_ms, off_ms=off_ms,
|
|
527
|
-
rewrite_times=rewrite_times,
|
|
532
|
+
rewrite_times=rewrite_times, boot_judge=boot_judge,
|
|
533
|
+
progress=progress)
|
|
528
534
|
result["authorization"] = {"write_allowed": True,
|
|
529
535
|
"granted_by": write_allowed_source() or "(default false)",
|
|
530
536
|
"chain": list(_CONFIG_FILES)}
|
|
531
537
|
ok("flash-bin", result,
|
|
532
538
|
log=f"UpdateFW({file_path}): {result['fw_len']}B, {result['pages_written']} pages, "
|
|
533
539
|
f"off_ms={result.get('off_ms')}, rewrite={result.get('rewrite_count', 0)}, "
|
|
534
|
-
f"verify={result['verify']}")
|
|
540
|
+
f"boot_judge={result.get('boot_judge')}, verify={result['verify']}")
|
|
535
541
|
|
|
536
542
|
|
|
537
543
|
# --------------------------------------------------------------------------- main
|
|
@@ -582,6 +588,9 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
582
588
|
"独立看门狗芯片需要真 POR)")
|
|
583
589
|
f.add_argument("--rewrite-times", type=int, default=None,
|
|
584
590
|
help="校验不一致时整片重写次数上限 (默认对齐 ChipSea.cs 的 3; 0=不重写)")
|
|
591
|
+
f.add_argument("--boot-judge", choices=["diff", "off"], default=None,
|
|
592
|
+
help="boot 判据 (默认 diff: 只有'擦除魔数前后同一条非平凡应答'才熔断; "
|
|
593
|
+
"off: 完全关闭探测判据, 仅记录证据, 由逐窗校验做唯一判据)")
|
|
585
594
|
|
|
586
595
|
st = sub.add_parser("stream", parents=[common],
|
|
587
596
|
help="高速连续采集寄存器组到 CSV (单会话长驻, 只读)")
|
|
@@ -641,7 +650,8 @@ def main(argv=None) -> None:
|
|
|
641
650
|
cmd_poll(io, args.duration_ms, args.interval_ms, args.max_frames, args.full)
|
|
642
651
|
elif args.cmd == "flash-bin":
|
|
643
652
|
cmd_flash_bin(io, config, args.file, args.erase_ms, args.confirm,
|
|
644
|
-
off_ms=args.off_ms, rewrite_times=args.rewrite_times
|
|
653
|
+
off_ms=args.off_ms, rewrite_times=args.rewrite_times,
|
|
654
|
+
boot_judge=args.boot_judge)
|
|
645
655
|
elif args.cmd == "stream":
|
|
646
656
|
specs = [parse_reg_spec(s) for s in args.regs]
|
|
647
657
|
cmd_stream(io, config, specs, args.interval_ms, args.duration_s,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<#
|
|
2
2
|
.SYNOPSIS
|
|
3
|
-
Dual-Tier MCP setup and registration helper (v0.7.
|
|
3
|
+
Dual-Tier MCP setup and registration helper (v0.7.1).
|
|
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.7.
|
|
245
|
+
Write-Host " FirmwareLoop Dual-Tier MCP & Agent Setup Helper (v0.7.1)" -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|