firmwareloop 0.8.0__tar.gz → 0.9.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/PKG-INFO +4 -4
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/README.md +3 -3
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/firmwareloop.egg-info/PKG-INFO +4 -4
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/pyproject.toml +1 -1
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/tools/__init__.py +1 -1
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/tools/fw_mcp_server.py +92 -58
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/tools/lib/ndtdbg.py +63 -29
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/tools/ndtdbg_cli.py +33 -8
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/tools/setup-agent-mcp.ps1 +2 -2
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/LICENSE +0 -0
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/firmwareloop.egg-info/SOURCES.txt +0 -0
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/firmwareloop.egg-info/dependency_links.txt +0 -0
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/firmwareloop.egg-info/entry_points.txt +0 -0
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/firmwareloop.egg-info/requires.txt +0 -0
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/firmwareloop.egg-info/top_level.txt +0 -0
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/setup.cfg +0 -0
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/tools/acceptance-scenario.ps1 +0 -0
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/tools/build.ps1 +0 -0
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/tools/can.ps1 +0 -0
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/tools/check-qoder-mcp.ps1 +0 -0
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/tools/common/build-backends.psm1 +0 -0
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/tools/common/fw.psm1 +0 -0
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/tools/common/uart_probe.py +0 -0
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/tools/doctor.ps1 +0 -0
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/tools/flash.ps1 +0 -0
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/tools/instrument_cli.py +0 -0
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/tools/lib/__init__.py +0 -0
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/tools/lib/instruments.py +0 -0
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/tools/lib/serial_assistant.py +0 -0
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/tools/logic_capture.ps1 +0 -0
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/tools/logic_decode.ps1 +0 -0
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/tools/ndtdbg_bridge.ps1 +0 -0
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/tools/reset.ps1 +0 -0
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/tools/serial_cli.py +0 -0
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/tools/sigrok_csv_to_fwloop.py +0 -0
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/tools/test-backends.ps1 +0 -0
- {firmwareloop-0.8.0 → firmwareloop-0.9.0}/tools/test.ps1 +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: firmwareloop
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: AI Agent Firmware Development and Lab Automation Platform (Dual-Tier MCP Server)
|
|
5
5
|
Author-email: WeilinZhang <19523330249@163.com>
|
|
6
6
|
License: MIT
|
|
@@ -385,7 +385,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
385
385
|
├── demo-firmware/ 示例固件(CMake;宿主编译模拟 MCU,闭环验证载体)
|
|
386
386
|
├── demo-make/ 示例固件(Make;构建测试载体)
|
|
387
387
|
├── docs/ DEPENDENCY_MATRIX / REUSE_PLAN / V0.0.2_GAP_VERIFICATION
|
|
388
|
-
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.
|
|
388
|
+
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.9.0)
|
|
389
389
|
├── .mcp.example.json 双层 MCP 配置模板(firmwareloop + agentic-hil)
|
|
390
390
|
├── AGENTS.md 通用智能体规范(Antigravity / Qoder / Cursor 等)
|
|
391
391
|
├── CLAUDE.md Claude Code CLI 指南
|
|
@@ -395,7 +395,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
395
395
|
|
|
396
396
|
---
|
|
397
397
|
|
|
398
|
-
## 能力验证状态(v0.
|
|
398
|
+
## 能力验证状态(v0.9.0)
|
|
399
399
|
|
|
400
400
|
> 状态定义:`Implemented`(已实现)/ `Simulator Validated`(模拟验证)/
|
|
401
401
|
> `Real Hardware Validated`(真机验证)/ `Experimental` / `Not Implemented`
|
|
@@ -419,7 +419,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
419
419
|
| 自动修复闭环(M6 载体) | Simulator Validated -> Real Hardware Validated 待 DUT |
|
|
420
420
|
| NdtBox 数据采集/调试(fw_ndtdbg_status / fw_ndtdbg_measure, ChipSea Fxx IIC) | Implemented(无硬件 Fail-Closed 已验证)-> Real Hardware Validated 待 NdtBox |
|
|
421
421
|
| NdtBox 目标断电复位(fw_ndtdbg_power_cycle, 仅 io10 断电复位, 无写闸门) | Implemented(单测覆盖时序/探针缺失指引)-> Real Hardware Validated 待 NdtBox |
|
|
422
|
-
| NdtBox IIC 固件烧录(fw_ndtdbg_flash, .bin/.nfw 整片擦写, 芯海 Fxx) | Real Hardware Validated
|
|
422
|
+
| NdtBox IIC 固件烧录(fw_ndtdbg_flash, .bin/.nfw 整片擦写, 芯海 Fxx) | Real Hardware Validated(两批真实固件镜像真机整片擦写 verify pass; 2026-09-11 同批文件桥内单体 flash 6/6 pass;双闸门 Fail-Closed)。**v0.7.2 起默认引擎 = 桥内单体 flash**(逐笔 serve 路径现场实测"擦除生效但页写不落地",保留 `flash_engine: serve` 供 A/B);差分 boot 判据 + `boot_judge: off` 逃生开关见 0.7.1;**待验收声明:默认引擎路径(Python 组 plan → Popen 桥 → 解析信封)尚待真机验收** |
|
|
423
423
|
| 烧后冷启动冒烟(fw_ndtdbg_flash 默认开, `cold_boot_check`) | Implemented(单测覆盖通过/全 FF/期望不符/关闭;`COLD_BOOT_VERIFY_FAILED` 错误类)-> Real Hardware Validated 待真机 |
|
|
424
424
|
| 串口占用取证(`port_probe` 试开硬结论 + `holder_candidates` 候选名单) | Implemented(试开结论可复现;候选按 cmdline/name 标注置信度) |
|
|
425
425
|
| 抓开机日志(fw_serial_capture + power_cycle_off_ms, 窗口内断电复位) | Implemented(simulator 验证窗口与复位串联)-> Real Hardware Validated 待 NdtBox + DUT |
|
|
@@ -357,7 +357,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
357
357
|
├── demo-firmware/ 示例固件(CMake;宿主编译模拟 MCU,闭环验证载体)
|
|
358
358
|
├── demo-make/ 示例固件(Make;构建测试载体)
|
|
359
359
|
├── docs/ DEPENDENCY_MATRIX / REUSE_PLAN / V0.0.2_GAP_VERIFICATION
|
|
360
|
-
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.
|
|
360
|
+
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.9.0)
|
|
361
361
|
├── .mcp.example.json 双层 MCP 配置模板(firmwareloop + agentic-hil)
|
|
362
362
|
├── AGENTS.md 通用智能体规范(Antigravity / Qoder / Cursor 等)
|
|
363
363
|
├── CLAUDE.md Claude Code CLI 指南
|
|
@@ -367,7 +367,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
367
367
|
|
|
368
368
|
---
|
|
369
369
|
|
|
370
|
-
## 能力验证状态(v0.
|
|
370
|
+
## 能力验证状态(v0.9.0)
|
|
371
371
|
|
|
372
372
|
> 状态定义:`Implemented`(已实现)/ `Simulator Validated`(模拟验证)/
|
|
373
373
|
> `Real Hardware Validated`(真机验证)/ `Experimental` / `Not Implemented`
|
|
@@ -391,7 +391,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
391
391
|
| 自动修复闭环(M6 载体) | Simulator Validated -> Real Hardware Validated 待 DUT |
|
|
392
392
|
| NdtBox 数据采集/调试(fw_ndtdbg_status / fw_ndtdbg_measure, ChipSea Fxx IIC) | Implemented(无硬件 Fail-Closed 已验证)-> Real Hardware Validated 待 NdtBox |
|
|
393
393
|
| NdtBox 目标断电复位(fw_ndtdbg_power_cycle, 仅 io10 断电复位, 无写闸门) | Implemented(单测覆盖时序/探针缺失指引)-> Real Hardware Validated 待 NdtBox |
|
|
394
|
-
| NdtBox IIC 固件烧录(fw_ndtdbg_flash, .bin/.nfw 整片擦写, 芯海 Fxx) | Real Hardware Validated
|
|
394
|
+
| NdtBox IIC 固件烧录(fw_ndtdbg_flash, .bin/.nfw 整片擦写, 芯海 Fxx) | Real Hardware Validated(两批真实固件镜像真机整片擦写 verify pass; 2026-09-11 同批文件桥内单体 flash 6/6 pass;双闸门 Fail-Closed)。**v0.7.2 起默认引擎 = 桥内单体 flash**(逐笔 serve 路径现场实测"擦除生效但页写不落地",保留 `flash_engine: serve` 供 A/B);差分 boot 判据 + `boot_judge: off` 逃生开关见 0.7.1;**待验收声明:默认引擎路径(Python 组 plan → Popen 桥 → 解析信封)尚待真机验收** |
|
|
395
395
|
| 烧后冷启动冒烟(fw_ndtdbg_flash 默认开, `cold_boot_check`) | Implemented(单测覆盖通过/全 FF/期望不符/关闭;`COLD_BOOT_VERIFY_FAILED` 错误类)-> Real Hardware Validated 待真机 |
|
|
396
396
|
| 串口占用取证(`port_probe` 试开硬结论 + `holder_candidates` 候选名单) | Implemented(试开结论可复现;候选按 cmdline/name 标注置信度) |
|
|
397
397
|
| 抓开机日志(fw_serial_capture + power_cycle_off_ms, 窗口内断电复位) | Implemented(simulator 验证窗口与复位串联)-> Real Hardware Validated 待 NdtBox + DUT |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: firmwareloop
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: AI Agent Firmware Development and Lab Automation Platform (Dual-Tier MCP Server)
|
|
5
5
|
Author-email: WeilinZhang <19523330249@163.com>
|
|
6
6
|
License: MIT
|
|
@@ -385,7 +385,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
385
385
|
├── demo-firmware/ 示例固件(CMake;宿主编译模拟 MCU,闭环验证载体)
|
|
386
386
|
├── demo-make/ 示例固件(Make;构建测试载体)
|
|
387
387
|
├── docs/ DEPENDENCY_MATRIX / REUSE_PLAN / V0.0.2_GAP_VERIFICATION
|
|
388
|
-
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.
|
|
388
|
+
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.9.0)
|
|
389
389
|
├── .mcp.example.json 双层 MCP 配置模板(firmwareloop + agentic-hil)
|
|
390
390
|
├── AGENTS.md 通用智能体规范(Antigravity / Qoder / Cursor 等)
|
|
391
391
|
├── CLAUDE.md Claude Code CLI 指南
|
|
@@ -395,7 +395,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
395
395
|
|
|
396
396
|
---
|
|
397
397
|
|
|
398
|
-
## 能力验证状态(v0.
|
|
398
|
+
## 能力验证状态(v0.9.0)
|
|
399
399
|
|
|
400
400
|
> 状态定义:`Implemented`(已实现)/ `Simulator Validated`(模拟验证)/
|
|
401
401
|
> `Real Hardware Validated`(真机验证)/ `Experimental` / `Not Implemented`
|
|
@@ -419,7 +419,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
419
419
|
| 自动修复闭环(M6 载体) | Simulator Validated -> Real Hardware Validated 待 DUT |
|
|
420
420
|
| NdtBox 数据采集/调试(fw_ndtdbg_status / fw_ndtdbg_measure, ChipSea Fxx IIC) | Implemented(无硬件 Fail-Closed 已验证)-> Real Hardware Validated 待 NdtBox |
|
|
421
421
|
| NdtBox 目标断电复位(fw_ndtdbg_power_cycle, 仅 io10 断电复位, 无写闸门) | Implemented(单测覆盖时序/探针缺失指引)-> Real Hardware Validated 待 NdtBox |
|
|
422
|
-
| NdtBox IIC 固件烧录(fw_ndtdbg_flash, .bin/.nfw 整片擦写, 芯海 Fxx) | Real Hardware Validated
|
|
422
|
+
| NdtBox IIC 固件烧录(fw_ndtdbg_flash, .bin/.nfw 整片擦写, 芯海 Fxx) | Real Hardware Validated(两批真实固件镜像真机整片擦写 verify pass; 2026-09-11 同批文件桥内单体 flash 6/6 pass;双闸门 Fail-Closed)。**v0.7.2 起默认引擎 = 桥内单体 flash**(逐笔 serve 路径现场实测"擦除生效但页写不落地",保留 `flash_engine: serve` 供 A/B);差分 boot 判据 + `boot_judge: off` 逃生开关见 0.7.1;**待验收声明:默认引擎路径(Python 组 plan → Popen 桥 → 解析信封)尚待真机验收** |
|
|
423
423
|
| 烧后冷启动冒烟(fw_ndtdbg_flash 默认开, `cold_boot_check`) | Implemented(单测覆盖通过/全 FF/期望不符/关闭;`COLD_BOOT_VERIFY_FAILED` 错误类)-> Real Hardware Validated 待真机 |
|
|
424
424
|
| 串口占用取证(`port_probe` 试开硬结论 + `holder_candidates` 候选名单) | Implemented(试开结论可复现;候选按 cmdline/name 标注置信度) |
|
|
425
425
|
| 抓开机日志(fw_serial_capture + power_cycle_off_ms, 窗口内断电复位) | Implemented(simulator 验证窗口与复位串联)-> Real Hardware Validated 待 NdtBox + DUT |
|
|
@@ -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.
|
|
3
|
+
fw_mcp_server.py - FirmwareLoop High-Level Workflow & Device MCP Server (v0.9.0).
|
|
4
4
|
|
|
5
5
|
Exposes high-level firmware engineering and hardware management tools to AI Coding
|
|
6
6
|
Agents (Antigravity CLI, Claude Code CLI, Qoder IDE, CodeBuddy IDE, DeepSeek Harness,
|
|
@@ -384,13 +384,16 @@ def handle_fw_ndtdbg_power_cycle(args: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
384
384
|
def _stream_cli_cmd(args: Dict[str, Any], out_path: str, duration_cap: float) -> Optional[list]:
|
|
385
385
|
"""构造 stream CLI 命令 (同步/job 共用); 返回 None 表示参数非法。"""
|
|
386
386
|
cli_script = os.path.join(REPO_ROOT, "tools", "ndtdbg_cli.py")
|
|
387
|
-
regs = args.get("regs") or ["0xEE:8"]
|
|
388
387
|
duration_s = min(max(float(args.get("duration_s", 10)), 0.0), duration_cap)
|
|
389
388
|
max_frames = int(args.get("max_frames", 0))
|
|
390
389
|
if duration_s <= 0 and max_frames <= 0:
|
|
391
390
|
return None
|
|
392
|
-
cmd = [sys.executable, cli_script, "stream", "--
|
|
393
|
-
|
|
391
|
+
cmd = [sys.executable, cli_script, "stream", "--out", out_path]
|
|
392
|
+
regs = args.get("regs")
|
|
393
|
+
if regs:
|
|
394
|
+
if isinstance(regs, str):
|
|
395
|
+
regs = [regs]
|
|
396
|
+
cmd.extend(["--regs", *[str(r) for r in regs]])
|
|
394
397
|
if args.get("interval_ms") is not None:
|
|
395
398
|
cmd.extend(["--interval-ms", str(int(args["interval_ms"]))])
|
|
396
399
|
if duration_s > 0:
|
|
@@ -567,10 +570,13 @@ def handle_fw_ndtdbg_capture_seq(args: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
567
570
|
cli_script = os.path.join(REPO_ROOT, "tools", "ndtdbg_cli.py")
|
|
568
571
|
cap_script = os.path.join(REPO_ROOT, "tools", "logic_capture.ps1")
|
|
569
572
|
duration_ms = int(args.get("duration_ms", 8000))
|
|
570
|
-
writes = args.get("writes"
|
|
571
|
-
|
|
573
|
+
writes = args.get("writes")
|
|
574
|
+
if not writes:
|
|
575
|
+
return {"ok": False, "error_class": "CONFIG_ERROR",
|
|
576
|
+
"error": "fw_ndtdbg_capture_seq 必须传入非空 writes 序列 (如 [{'reg': '0xBE', 'data': '01'}]), 无默认序列; 参见目标固件协议文档"}
|
|
577
|
+
poll_reg = args.get("poll_reg")
|
|
572
578
|
poll_attempts = int(args.get("poll_attempts", 5))
|
|
573
|
-
readback_reg = args.get("readback_reg"
|
|
579
|
+
readback_reg = args.get("readback_reg")
|
|
574
580
|
poll_expect = " ".join(str(args.get("poll_expect", "00 00")).split()).upper()
|
|
575
581
|
confirm = bool(args.get("confirm", True))
|
|
576
582
|
|
|
@@ -902,7 +908,7 @@ def handle_fw_init_project(args: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
902
908
|
return {
|
|
903
909
|
"ok": False,
|
|
904
910
|
"error_class": "CONFIG_ERROR",
|
|
905
|
-
"error": "target_chip 必填 (如 STM32F103C8 /
|
|
911
|
+
"error": "target_chip 必填 (如 STM32F103C8 / STM32F407ZG)",
|
|
906
912
|
"detail": "fwloop init <target_chip> 或 MCP fw_init_project(target_chip=...)",
|
|
907
913
|
}
|
|
908
914
|
|
|
@@ -1936,8 +1942,7 @@ TOOLS_REGISTRY = {
|
|
|
1936
1942
|
"regs": {
|
|
1937
1943
|
"type": "array",
|
|
1938
1944
|
"items": {"type": "string"},
|
|
1939
|
-
"description": "Register specs 'addr:len[:name]', e.g. ['0xEE:8:ch'
|
|
1940
|
-
"default": ["0xEE:8"],
|
|
1945
|
+
"description": "Register specs 'addr:len[:name]', e.g. ['0xEE:8:ch'] (EXAMPLE). Required unless configured in lab.yaml ndtdbg.stream.regs or ndtdbg.target_profile.stream_regs.",
|
|
1941
1946
|
},
|
|
1942
1947
|
"interval_ms": {
|
|
1943
1948
|
"type": "integer",
|
|
@@ -1961,7 +1966,7 @@ TOOLS_REGISTRY = {
|
|
|
1961
1966
|
"decode": {
|
|
1962
1967
|
"type": "string",
|
|
1963
1968
|
"enum": ["hex", "int16le", "uint16le", "int8", "uint8"],
|
|
1964
|
-
"description": "Optional decoded columns besides raw hex (int16le for
|
|
1969
|
+
"description": "Optional decoded columns besides raw hex (e.g. int16le for 4-channel little-endian sensor frames).",
|
|
1965
1970
|
"default": "hex",
|
|
1966
1971
|
},
|
|
1967
1972
|
"retry": {
|
|
@@ -1987,7 +1992,7 @@ TOOLS_REGISTRY = {
|
|
|
1987
1992
|
"interleave_writes": {
|
|
1988
1993
|
"type": "array",
|
|
1989
1994
|
"items": {"type": "string"},
|
|
1990
|
-
"description": "Interleave-write stress profile (P1-1): 'addr:cycle:v1,v2,...:every=N' (rotate values every N frames) or 'addr:once:v:frame=M' (single write at frame M). Written in-frame gaps over the SAME USB session (no dual-open). Requires write_allowed: true AND confirm: true. e.g. ['0x56:cycle:00,01,02,03:every=8'] reproduces
|
|
1995
|
+
"description": "Interleave-write stress profile (P1-1): 'addr:cycle:v1,v2,...:every=N' (rotate values every N frames) or 'addr:once:v:frame=M' (single write at frame M). Written in-frame gaps over the SAME USB session (no dual-open). Requires write_allowed: true AND confirm: true. e.g. ['0x56:cycle:00,01,02,03:every=8'] reproduces schedule-race style concurrent-write stress profiles (example from a real product debugging session).",
|
|
1991
1996
|
},
|
|
1992
1997
|
"confirm": {
|
|
1993
1998
|
"type": "boolean",
|
|
@@ -1997,7 +2002,6 @@ TOOLS_REGISTRY = {
|
|
|
1997
2002
|
"dll_dir": {"type": "string", "description": "NdtDebugTool lib dir; default from lab.yaml ndtdbg.dll_dir"},
|
|
1998
2003
|
"slave_addr": {"type": "string", "description": "IIC slave address, e.g. 0xA0; default from lab.yaml"},
|
|
1999
2004
|
},
|
|
2000
|
-
"required": ["regs"],
|
|
2001
2005
|
"additionalProperties": False,
|
|
2002
2006
|
},
|
|
2003
2007
|
"handler": handle_fw_ndtdbg_stream,
|
|
@@ -2010,8 +2014,7 @@ TOOLS_REGISTRY = {
|
|
|
2010
2014
|
"regs": {
|
|
2011
2015
|
"type": "array",
|
|
2012
2016
|
"items": {"type": "string"},
|
|
2013
|
-
"description": "Register specs 'addr:len[:name]'.
|
|
2014
|
-
"default": ["0xEE:8"],
|
|
2017
|
+
"description": "Register specs 'addr:len[:name]', e.g. ['0xEE:8:ch'] (EXAMPLE). Required unless configured in lab.yaml ndtdbg.stream.regs or ndtdbg.target_profile.stream_regs.",
|
|
2015
2018
|
},
|
|
2016
2019
|
"interval_ms": {"type": "integer", "description": "Target sampling interval in ms.", "default": 100},
|
|
2017
2020
|
"duration_s": {"type": "number", "description": "Total capture duration in seconds (cap 3600).", "default": 10},
|
|
@@ -2056,7 +2059,7 @@ TOOLS_REGISTRY = {
|
|
|
2056
2059
|
"handler": handle_fw_ndtdbg_stream_stop,
|
|
2057
2060
|
},
|
|
2058
2061
|
"fw_ndtdbg_capture_seq": {
|
|
2059
|
-
"description": "One-call protocol evidence: concurrently runs a
|
|
2062
|
+
"description": "One-call protocol evidence: concurrently runs a target-specific register write sequence and a sigrok I2C capture, returning the capture path for fw_logic_decode. REQUIRED: pass your target firmware's real register protocol via writes (e.g. [{'reg': '0xBE', 'data': '01'}]). Optional poll_reg/poll_expect/readback_reg for handshake & verification. Writes are double-gated (confirm: true + lab.yaml ndtdbg.write_allowed); degrades to sequence-only execution (capture_skipped) when sigrok-cli is missing.",
|
|
2060
2063
|
"inputSchema": {
|
|
2061
2064
|
"type": "object",
|
|
2062
2065
|
"properties": {
|
|
@@ -2074,20 +2077,21 @@ TOOLS_REGISTRY = {
|
|
|
2074
2077
|
"data": {"type": "string"},
|
|
2075
2078
|
},
|
|
2076
2079
|
},
|
|
2077
|
-
"description": "
|
|
2080
|
+
"description": "Target-specific register write sequence (e.g. [{'reg': '0xBE', 'data': '01'}]). Required, no hardcoded default.",
|
|
2078
2081
|
},
|
|
2079
2082
|
"confirm": {
|
|
2080
2083
|
"type": "boolean",
|
|
2081
2084
|
"description": "Acknowledge the register write intent; handler auto-passes --confirm to the CLI (double-gated with lab.yaml ndtdbg.write_allowed)",
|
|
2082
2085
|
"default": True,
|
|
2083
2086
|
},
|
|
2084
|
-
"poll_reg": {"type": "string", "description": "
|
|
2087
|
+
"poll_reg": {"type": "string", "description": "Optional poll register until it matches poll_expect (no default; skipped if omitted)"},
|
|
2085
2088
|
"poll_expect": {"type": "string", "description": "Expected poll register value (hex) that ends the poll loop; default '00 00' - set to match your firmware protocol", "default": "00 00"},
|
|
2086
2089
|
"poll_attempts": {"type": "integer", "description": "Max poll attempts", "default": 5},
|
|
2087
|
-
"readback_reg": {"type": "string", "description": "
|
|
2090
|
+
"readback_reg": {"type": "string", "description": "Optional read-back verification register (no default; skipped if omitted)"},
|
|
2088
2091
|
"dll_dir": {"type": "string", "description": "NdtDebugTool lib directory; default from lab.yaml"},
|
|
2089
2092
|
"slave_addr": {"type": "string", "description": "IIC slave address (e.g. 0xA0)"},
|
|
2090
2093
|
},
|
|
2094
|
+
"required": ["writes"],
|
|
2091
2095
|
"additionalProperties": False,
|
|
2092
2096
|
},
|
|
2093
2097
|
"handler": handle_fw_ndtdbg_capture_seq,
|
|
@@ -2174,7 +2178,7 @@ TOOLS_REGISTRY = {
|
|
|
2174
2178
|
},
|
|
2175
2179
|
"target_chip": {
|
|
2176
2180
|
"type": "string",
|
|
2177
|
-
"description": "Target MCU chip model (e.g. STM32F103C8,
|
|
2181
|
+
"description": "Target MCU chip model (e.g. STM32F103C8, STM32F407ZG) - REQUIRED, no hardcoded default",
|
|
2178
2182
|
},
|
|
2179
2183
|
"build_backend": {
|
|
2180
2184
|
"type": "string",
|
|
@@ -2387,7 +2391,7 @@ def process_request(req: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
|
2387
2391
|
},
|
|
2388
2392
|
"serverInfo": {
|
|
2389
2393
|
"name": "firmwareloop",
|
|
2390
|
-
"version": "0.
|
|
2394
|
+
"version": "0.9.0"
|
|
2391
2395
|
}
|
|
2392
2396
|
}
|
|
2393
2397
|
}
|
|
@@ -2522,7 +2526,7 @@ def _settle_stashed_entrypoints(stashed) -> None:
|
|
|
2522
2526
|
def handle_cli_update() -> int:
|
|
2523
2527
|
"""Handle `firmwareloop update` command."""
|
|
2524
2528
|
print("============================================================")
|
|
2525
|
-
print(" FirmwareLoop Auto-Updater (v0.
|
|
2529
|
+
print(" FirmwareLoop Auto-Updater (v0.9.0)")
|
|
2526
2530
|
print("============================================================")
|
|
2527
2531
|
|
|
2528
2532
|
is_git_repo = os.path.exists(os.path.join(REPO_ROOT, ".git"))
|
|
@@ -2659,6 +2663,18 @@ def handle_cli_selftest(target: str = "ndtdbg") -> int:
|
|
|
2659
2663
|
def report(name, status, detail=""):
|
|
2660
2664
|
results.append((name, status, detail))
|
|
2661
2665
|
|
|
2666
|
+
# P2-2: 从生效配置加载产品协议 profile (避免硬编码 0xEE 与 0xBE)
|
|
2667
|
+
try:
|
|
2668
|
+
from tools import ndtdbg_cli
|
|
2669
|
+
_cfg = ndtdbg_cli.load_config(None)
|
|
2670
|
+
except Exception:
|
|
2671
|
+
_cfg = {}
|
|
2672
|
+
ndt_cfg = (_cfg.get("ndtdbg") or {}) if isinstance(_cfg, dict) else {}
|
|
2673
|
+
target_profile = ndt_cfg.get("target_profile") or {}
|
|
2674
|
+
stream_cfg = ndt_cfg.get("stream") or {}
|
|
2675
|
+
stream_regs = target_profile.get("stream_regs") or stream_cfg.get("regs")
|
|
2676
|
+
loop_cfg = target_profile.get("write_loop") or target_profile.get("selftest_sequence")
|
|
2677
|
+
|
|
2662
2678
|
# 1. status 分层探测
|
|
2663
2679
|
print("\n[1/4] status 探测 (Box / IIC bus / target chip) ...")
|
|
2664
2680
|
rc, body = run_cli("status", timeout=30)
|
|
@@ -2675,40 +2691,58 @@ def handle_cli_selftest(target: str = "ndtdbg") -> int:
|
|
|
2675
2691
|
report("protocol.desync", _st, _detail)
|
|
2676
2692
|
|
|
2677
2693
|
# 2. stream 冒烟 (100ms x 10s)
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
report("write.restore", "PASS", "0xBE=00 已恢复")
|
|
2694
|
+
if stream_regs:
|
|
2695
|
+
if isinstance(stream_regs, str):
|
|
2696
|
+
stream_regs = [stream_regs]
|
|
2697
|
+
stream_cmd = ["stream", "--interval-ms", "100", "--duration-s", "10",
|
|
2698
|
+
"--regs", *[str(r) for r in stream_regs]]
|
|
2699
|
+
frame_cfg = target_profile.get("measure_frame") or {}
|
|
2700
|
+
dec = frame_cfg.get("decode")
|
|
2701
|
+
if dec and dec != "raw":
|
|
2702
|
+
stream_cmd.extend(["--decode", dec])
|
|
2703
|
+
print(f"[2/4] stream 冒烟 (100ms x 10s, regs={stream_regs}) ...")
|
|
2704
|
+
rc, body = run_cli(*stream_cmd, timeout=60)
|
|
2705
|
+
stream_data = (body or {}).get("data") if isinstance(body, dict) else None
|
|
2706
|
+
if rc == 0 and stream_data:
|
|
2707
|
+
total = stream_data.get("frames_total", 0)
|
|
2708
|
+
conserved = (stream_data.get("frames_ok", 0) + stream_data.get("frames_ff_gap", 0)
|
|
2709
|
+
+ stream_data.get("frames_io_fail", 0)) == total
|
|
2710
|
+
avg = stream_data.get("interval_actual_avg_ms")
|
|
2711
|
+
report("stream.frames_conserved", "PASS" if conserved else "FAIL",
|
|
2712
|
+
f"total={total} (ok={stream_data.get('frames_ok')}, "
|
|
2713
|
+
f"ff_gap={stream_data.get('frames_ff_gap')}, io_fail={stream_data.get('frames_io_fail')})")
|
|
2714
|
+
report("stream.interval_perf", "PASS" if (avg is not None and avg <= 110) else "FAIL",
|
|
2715
|
+
f"avg={avg}ms (target 100ms, 验收线 <=110ms)")
|
|
2716
|
+
report("stream.io_overhead", "PASS" if stream_data.get("io_overhead_avg_ms") is not None else "FAIL",
|
|
2717
|
+
f"io_overhead_avg_ms={stream_data.get('io_overhead_avg_ms')}")
|
|
2718
|
+
report("stream.gaps", "PASS", f"gaps_n={stream_data.get('gaps_n', 0)} (仅 >2x target 计数)")
|
|
2719
|
+
else:
|
|
2720
|
+
report("stream", "FAIL", (body or {}).get("error_class", f"exit {rc}"))
|
|
2706
2721
|
else:
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2722
|
+
print("[2/4] stream 冒烟 (未配置 target_profile.stream_regs, 跳过) ...")
|
|
2723
|
+
report("stream", "SKIP", "未配置产品协议 profile (target_profile.stream_regs),寄存器级自验跳过")
|
|
2724
|
+
|
|
2725
|
+
# 3. 写读回环 (write_allowed 双门内; 未授权或未配 profile 时 SKIP, 不算 FAIL)
|
|
2726
|
+
if loop_cfg:
|
|
2727
|
+
loop_addr = str(loop_cfg.get("addr", "0xBE"))
|
|
2728
|
+
w_data = str(loop_cfg.get("write_data", "01"))
|
|
2729
|
+
r_data = str(loop_cfg.get("restore_data", "00"))
|
|
2730
|
+
print(f"[3/4] 写读回环 ({loop_addr}={w_data}->{r_data}, 需 lab.yaml write_allowed) ...")
|
|
2731
|
+
rc, body = run_cli("write-reg", "--addr", loop_addr, "--data", w_data, "--confirm")
|
|
2732
|
+
if rc == 0 and body and body.get("ok"):
|
|
2733
|
+
auth = (body.get("data") or {}).get("authorization") or {}
|
|
2734
|
+
report("write.readback", "PASS", f"{loop_addr}={w_data} 写入成功 (授权来源: {auth.get('granted_by', '?')})")
|
|
2735
|
+
run_cli("write-reg", "--addr", loop_addr, "--data", r_data, "--confirm")
|
|
2736
|
+
report("write.restore", "PASS", f"{loop_addr}={r_data} 已恢复")
|
|
2710
2737
|
else:
|
|
2711
|
-
|
|
2738
|
+
err_class = (body or {}).get("error_class", f"exit {rc}")
|
|
2739
|
+
if err_class == "PERMISSION_DENIED":
|
|
2740
|
+
report("write.readback", "SKIP", "write_allowed 未开启 (双门保护), 跳过写回环")
|
|
2741
|
+
else:
|
|
2742
|
+
report("write.readback", "FAIL", err_class)
|
|
2743
|
+
else:
|
|
2744
|
+
print("[3/4] 写读回环 (未配置 target_profile.write_loop, 跳过) ...")
|
|
2745
|
+
report("write.readback", "SKIP", "未配置产品协议 profile (target_profile.write_loop),写回环自验跳过")
|
|
2712
2746
|
|
|
2713
2747
|
# 3.5 write_allowed 卫生检查 (P1-2): 任何层级为 true 即 WARNING (会话级临时态)
|
|
2714
2748
|
print("[3.5/4] write_allowed 卫生检查 ...")
|
|
@@ -3139,7 +3173,7 @@ def set_ndtdbg_dlldir(dll_path: str) -> str:
|
|
|
3139
3173
|
def handle_cli_setup() -> int:
|
|
3140
3174
|
"""Handle `firmwareloop setup` command: simultaneously configure Antigravity CLI, Claude Code CLI, Qoder IDE, CodeBuddy IDE, DeepSeek Harness (DSH), and ZCode IDE."""
|
|
3141
3175
|
print("============================================================")
|
|
3142
|
-
print(" FirmwareLoop Multi-Agent Global Setup Helper (v0.
|
|
3176
|
+
print(" FirmwareLoop Multi-Agent Global Setup Helper (v0.9.0)")
|
|
3143
3177
|
print("============================================================")
|
|
3144
3178
|
print("")
|
|
3145
3179
|
|
|
@@ -3294,7 +3328,7 @@ def handle_cli_setup() -> int:
|
|
|
3294
3328
|
def handle_cli_init(chip: Optional[str] = None, upgrade: bool = False) -> int:
|
|
3295
3329
|
"""Handle `firmwareloop init [target_chip]` command."""
|
|
3296
3330
|
print("============================================================")
|
|
3297
|
-
print(" FirmwareLoop Multi-Agent Project Initializer (v0.
|
|
3331
|
+
print(" FirmwareLoop Multi-Agent Project Initializer (v0.9.0)")
|
|
3298
3332
|
print("============================================================")
|
|
3299
3333
|
cwd = os.getcwd()
|
|
3300
3334
|
print(f"[*] Initializing multi-agent guidelines & bench config in:\n {cwd}\n")
|
|
@@ -3327,7 +3361,7 @@ def handle_cli_init(chip: Optional[str] = None, upgrade: bool = False) -> int:
|
|
|
3327
3361
|
|
|
3328
3362
|
|
|
3329
3363
|
def print_cli_help() -> None:
|
|
3330
|
-
print("""FirmwareLoop (fwloop) — AI Agent Firmware Engineering & Lab Automation Platform (v0.
|
|
3364
|
+
print("""FirmwareLoop (fwloop) — AI Agent Firmware Engineering & Lab Automation Platform (v0.9.0)
|
|
3331
3365
|
|
|
3332
3366
|
Usage:
|
|
3333
3367
|
fwloop [command] (or: firmwareloop [command])
|
|
@@ -3373,7 +3407,7 @@ def main() -> None:
|
|
|
3373
3407
|
sys.exit(2)
|
|
3374
3408
|
sys.exit(handle_cli_ndtdebugtool(args[1]))
|
|
3375
3409
|
elif cmd in ["version", "-v", "--version"]:
|
|
3376
|
-
print("FirmwareLoop v0.
|
|
3410
|
+
print("FirmwareLoop v0.9.0")
|
|
3377
3411
|
sys.exit(0)
|
|
3378
3412
|
elif cmd in ["help", "-h", "--help"]:
|
|
3379
3413
|
print_cli_help()
|
|
@@ -64,18 +64,53 @@ def build_frame(slave_addr: int, payload: bytes) -> bytes:
|
|
|
64
64
|
return bytes([slave_addr & 0xFF]) + payload
|
|
65
65
|
|
|
66
66
|
|
|
67
|
-
def parse_getdata(data: bytes) -> list[int] | None:
|
|
68
|
-
"""
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
def parse_getdata(data: bytes, profile: dict | None = None) -> list[int] | None:
|
|
68
|
+
"""按 profile 参数化解析数据帧 (P1-1):
|
|
69
|
+
|
|
70
|
+
profile:
|
|
71
|
+
checksum: "negated-sum-le16" (默认) | "none"
|
|
72
|
+
decode: "int16le" (默认) | "uint16le" | "int8" | "uint8" | "raw"
|
|
73
|
+
min_len: 最短字节数 (默认: negated-sum-le16 为 4, 其余为 1)
|
|
74
|
+
校验失败、长度不足或格式不合法返回 None。
|
|
75
|
+
"""
|
|
76
|
+
cfg = profile or {}
|
|
77
|
+
checksum_algo = cfg.get("checksum", "negated-sum-le16")
|
|
78
|
+
decode_mode = cfg.get("decode", "int16le")
|
|
79
|
+
min_len = int(cfg.get("min_len", 4 if checksum_algo == "negated-sum-le16" else 1))
|
|
80
|
+
|
|
81
|
+
if checksum_algo not in ("negated-sum-le16", "none", None):
|
|
82
|
+
raise NdtBoxError("CONFIG_ERROR", f"不支持的 measure_frame 校验算法: {checksum_algo}")
|
|
83
|
+
if decode_mode not in ("int16le", "uint16le", "int8", "uint8", "raw"):
|
|
84
|
+
raise NdtBoxError("CONFIG_ERROR", f"不支持的 measure_frame 解码模式: {decode_mode}")
|
|
85
|
+
|
|
86
|
+
if len(data) < min_len:
|
|
73
87
|
return None
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
88
|
+
|
|
89
|
+
if checksum_algo == "negated-sum-le16":
|
|
90
|
+
if len(data) < 2 or len(data) % 2 != 0:
|
|
91
|
+
return None
|
|
92
|
+
payload = data[:-2]
|
|
93
|
+
expected = int.from_bytes(data[-2:], "little")
|
|
94
|
+
if expected != checksum_two_bytes(payload):
|
|
95
|
+
return None
|
|
96
|
+
else:
|
|
97
|
+
payload = data
|
|
98
|
+
|
|
99
|
+
if decode_mode == "int16le":
|
|
100
|
+
channels = []
|
|
101
|
+
for i in range(0, len(payload) - len(payload) % 2, 2):
|
|
102
|
+
raw = int.from_bytes(payload[i:i + 2], "little")
|
|
103
|
+
channels.append(raw - 0x10000 if raw >= 0x8000 else raw)
|
|
104
|
+
return channels
|
|
105
|
+
elif decode_mode == "uint16le":
|
|
106
|
+
return [int.from_bytes(payload[i:i + 2], "little")
|
|
107
|
+
for i in range(0, len(payload) - len(payload) % 2, 2)]
|
|
108
|
+
elif decode_mode == "int8":
|
|
109
|
+
return [b - 256 if b >= 0x80 else b for b in payload]
|
|
110
|
+
elif decode_mode in ("uint8", "raw"):
|
|
111
|
+
return list(payload)
|
|
112
|
+
else:
|
|
113
|
+
raise NdtBoxError("CONFIG_ERROR", f"不支持的 measure_frame 解码模式: {decode_mode}")
|
|
79
114
|
|
|
80
115
|
|
|
81
116
|
def parse_version(data: bytes) -> str:
|
|
@@ -1364,15 +1399,23 @@ class ChipSeaFxx:
|
|
|
1364
1399
|
|
|
1365
1400
|
# -- 调试数据流 (对齐 ReadModuleConnectDynamic + GetData) --
|
|
1366
1401
|
|
|
1367
|
-
def poll(self, duration_ms: int, interval_ms: int = 10, ready_timeout_ms: int = 2000
|
|
1402
|
+
def poll(self, duration_ms: int, interval_ms: int = 10, ready_timeout_ms: int = 2000,
|
|
1403
|
+
frame_profile: dict | None = None) -> dict:
|
|
1368
1404
|
"""连续轮询一帧或多帧, 返回 {frame_count, checksum_errors, ready_timeouts, frames}。
|
|
1369
1405
|
|
|
1370
|
-
frames: [{"t": epoch_ms, "ch": [
|
|
1406
|
+
frames: [{"t": epoch_ms, "ch": [...]}, ...]——0.4.0 Python 驱动: serve
|
|
1371
1407
|
会话长驻, 序列对齐 ChipSea.cs ReadModuleConnectDynamic + GetData
|
|
1372
1408
|
(SetDebugMode(0) -> ClearDataReady -> SetDebugMode(0xA4) -> 读 ready -> 读数据)。
|
|
1409
|
+
P1-1: 数据寄存器与帧解析由 frame_profile (lab.yaml target_profile.measure_frame) 参数化。
|
|
1373
1410
|
"""
|
|
1374
1411
|
if duration_ms <= 0:
|
|
1375
1412
|
raise NdtBoxError("CONFIG_ERROR", "poll 时长必须 > 0", str(duration_ms))
|
|
1413
|
+
cfg = frame_profile or {}
|
|
1414
|
+
data_reg = int(str(cfg.get("data_reg", 0xEE)), 0) if "data_reg" in cfg else 0xEE
|
|
1415
|
+
checksum_algo = cfg.get("checksum", "negated-sum-le16")
|
|
1416
|
+
decode_mode = cfg.get("decode", "int16le")
|
|
1417
|
+
needs_even = (checksum_algo == "negated-sum-le16" or decode_mode in ("int16le", "uint16le"))
|
|
1418
|
+
|
|
1376
1419
|
import time
|
|
1377
1420
|
frames: list[dict] = []
|
|
1378
1421
|
checksum_errors = 0
|
|
@@ -1397,26 +1440,17 @@ class ChipSeaFxx:
|
|
|
1397
1440
|
time.sleep(interval_ms / 1000.0)
|
|
1398
1441
|
continue
|
|
1399
1442
|
length = ready
|
|
1400
|
-
if length % 2 != 0:
|
|
1443
|
+
if needs_even and length % 2 != 0:
|
|
1401
1444
|
checksum_errors += 1
|
|
1402
1445
|
time.sleep(interval_ms / 1000.0)
|
|
1403
1446
|
continue
|
|
1404
|
-
rb = self._io.read_reg(
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
if expected != checksum_two_bytes(rb[:-2]):
|
|
1408
|
-
checksum_errors += 1
|
|
1409
|
-
else:
|
|
1410
|
-
samples = []
|
|
1411
|
-
for i in range(length // 2 - 1):
|
|
1412
|
-
v = int.from_bytes(rb[i * 2:i * 2 + 2], "little")
|
|
1413
|
-
if v > 32767:
|
|
1414
|
-
v -= 65536 # C# (short) 有符号语义
|
|
1415
|
-
samples.append(v)
|
|
1416
|
-
if samples:
|
|
1417
|
-
frames.append({"t": time.time() * 1000, "ch": samples})
|
|
1418
|
-
else:
|
|
1447
|
+
rb = self._io.read_reg(data_reg, length)
|
|
1448
|
+
samples = parse_getdata(rb, cfg)
|
|
1449
|
+
if samples is None:
|
|
1419
1450
|
checksum_errors += 1
|
|
1451
|
+
else:
|
|
1452
|
+
if samples:
|
|
1453
|
+
frames.append({"t": time.time() * 1000, "ch": samples})
|
|
1420
1454
|
time.sleep(interval_ms / 1000.0)
|
|
1421
1455
|
return {"frame_count": len(frames), "checksum_errors": checksum_errors,
|
|
1422
1456
|
"ready_timeouts": dead_frames, "interval_ms": interval_ms,
|
|
@@ -350,10 +350,19 @@ def cmd_write_reg(io: NdtBoxIo, config: dict, reg: int, data_hex: str, confirm:
|
|
|
350
350
|
}, log=f"WriteReg(0x{reg:02X})")
|
|
351
351
|
|
|
352
352
|
|
|
353
|
-
def cmd_poll(io: NdtBoxIo, duration_ms: int, interval_ms: int, max_frames: int, full: bool) -> None:
|
|
353
|
+
def cmd_poll(io: NdtBoxIo, config: dict, duration_ms: int, interval_ms: int, max_frames: int, full: bool) -> None:
|
|
354
354
|
if duration_ms <= 0:
|
|
355
355
|
fail("CONFIG_ERROR", "poll 时长必须 > 0", str(duration_ms))
|
|
356
|
-
|
|
356
|
+
ndtdbg_cfg = config.get("ndtdbg") or {}
|
|
357
|
+
target_profile = ndtdbg_cfg.get("target_profile") or {}
|
|
358
|
+
frame_profile = target_profile.get("measure_frame") or ndtdbg_cfg.get("measure_frame")
|
|
359
|
+
if not frame_profile:
|
|
360
|
+
fail("CONFIG_ERROR",
|
|
361
|
+
"measure/poll 缺失目标产品帧格式配置 (target_profile.measure_frame)",
|
|
362
|
+
detail=(f"生效配置: {effective_config_desc()}; "
|
|
363
|
+
"请在 lab.yaml ndtdbg.target_profile.measure_frame 配置帧格式 "
|
|
364
|
+
"(如 data_reg: '0xEE', checksum: 'negated-sum-le16', decode: 'int16le', min_len: 4)"))
|
|
365
|
+
result = ChipSeaFxx(io).poll(duration_ms, interval_ms=interval_ms, frame_profile=frame_profile)
|
|
357
366
|
frames = result.get("frames", [])
|
|
358
367
|
if not full and len(frames) > max_frames:
|
|
359
368
|
kept = frames[:max_frames // 2 + 1] + frames[-(max_frames // 2 + 1):]
|
|
@@ -373,6 +382,8 @@ def cmd_stream(io: NdtBoxIo, config: dict, specs: list, interval_ms, duration_s:
|
|
|
373
382
|
IO_FAIL (重试后仍无应答, 连续计数达 fail_threshold 熔断)。Ctrl+C 优雅收尾,
|
|
374
383
|
CSV 已采数据保留, summary 标记 aborted。
|
|
375
384
|
"""
|
|
385
|
+
if not specs:
|
|
386
|
+
fail("CONFIG_ERROR", "stream 缺失目标寄存器表 (specs 不能为空)")
|
|
376
387
|
# 默认值: lab.yaml ndtdbg.stream 段可覆盖内置默认 (均有默认值, 非必填)
|
|
377
388
|
stream_cfg = (config.get("ndtdbg") or {}).get("stream") or {}
|
|
378
389
|
if interval_ms is None:
|
|
@@ -463,7 +474,7 @@ def cmd_stream(io: NdtBoxIo, config: dict, specs: list, interval_ms, duration_s:
|
|
|
463
474
|
fh.close()
|
|
464
475
|
hints = {
|
|
465
476
|
"PROBE_NOT_FOUND": "检查 NdtBox USB 连接与 CH341 驱动; 退出 STDataCollection.exe (USB 独占)",
|
|
466
|
-
"TARGET_MISMATCH": "检查 IIC 接线/上拉/供电/从机地址; 请求-响应式通道需先 write_reg 开启 debug_mode (
|
|
477
|
+
"TARGET_MISMATCH": "检查 IIC 接线/上拉/供电/从机地址; 请求-响应式通道需先 write_reg 开启 debug_mode (参见目标固件协议文档)",
|
|
467
478
|
}
|
|
468
479
|
hint = hints.get(exc.error_class)
|
|
469
480
|
detail = exc.detail or ""
|
|
@@ -601,7 +612,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
601
612
|
w.add_argument("--addr", type=lambda x: int(x, 0), required=True)
|
|
602
613
|
w.add_argument("--data", required=True, help="十六进制数据, 如 'A5 01 02'")
|
|
603
614
|
|
|
604
|
-
p_poll = sub.add_parser("poll", parents=[common], help="轮询采集 (
|
|
615
|
+
p_poll = sub.add_parser("poll", parents=[common], help="轮询采集 (调试数据流, 依 lab.yaml target_profile.measure_frame 解析)")
|
|
605
616
|
p_poll.add_argument("--duration-ms", type=int, default=2000)
|
|
606
617
|
p_poll.add_argument("--interval-ms", type=int, default=10)
|
|
607
618
|
p_poll.add_argument("--max-frames", type=int, default=4, help="非 --full 时保留的帧样本数")
|
|
@@ -636,8 +647,8 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
636
647
|
|
|
637
648
|
st = sub.add_parser("stream", parents=[common],
|
|
638
649
|
help="高速连续采集寄存器组到 CSV (单会话长驻, 只读)")
|
|
639
|
-
st.add_argument("--regs", nargs="+", default=
|
|
640
|
-
help="寄存器 spec addr:len[:name], 如 0xEE:8:ch
|
|
650
|
+
st.add_argument("--regs", nargs="+", default=None,
|
|
651
|
+
help="寄存器 spec addr:len[:name], 如 0xEE:8:ch (默认读 lab.yaml ndtdbg.stream.regs 或 target_profile.stream_regs)")
|
|
641
652
|
st.add_argument("--interval-ms", type=int, default=None,
|
|
642
653
|
help="目标采样间隔 ms (默认 lab.yaml ndtdbg.stream.default_interval_ms 或 100)")
|
|
643
654
|
st.add_argument("--duration-s", type=float, default=0,
|
|
@@ -689,7 +700,7 @@ def main(argv=None) -> None:
|
|
|
689
700
|
fail("CONFIG_ERROR", "write-reg 参数非法")
|
|
690
701
|
cmd_write_reg(io, config, args.addr, args.data, args.confirm)
|
|
691
702
|
elif args.cmd == "poll":
|
|
692
|
-
cmd_poll(io, args.duration_ms, args.interval_ms, args.max_frames, args.full)
|
|
703
|
+
cmd_poll(io, config, args.duration_ms, args.interval_ms, args.max_frames, args.full)
|
|
693
704
|
elif args.cmd == "flash-bin":
|
|
694
705
|
cmd_flash_bin(io, config, args.file, args.erase_ms, args.confirm,
|
|
695
706
|
off_ms=args.off_ms, rewrite_times=args.rewrite_times,
|
|
@@ -698,7 +709,21 @@ def main(argv=None) -> None:
|
|
|
698
709
|
cold_boot_reg=args.cold_boot_reg,
|
|
699
710
|
cold_boot_expect=args.cold_boot_expect)
|
|
700
711
|
elif args.cmd == "stream":
|
|
701
|
-
|
|
712
|
+
raw_regs = args.regs
|
|
713
|
+
if not raw_regs:
|
|
714
|
+
ndtdbg_cfg = config.get("ndtdbg") or {}
|
|
715
|
+
stream_cfg = ndtdbg_cfg.get("stream") or {}
|
|
716
|
+
target_profile = ndtdbg_cfg.get("target_profile") or {}
|
|
717
|
+
raw_regs = stream_cfg.get("regs") or target_profile.get("stream_regs")
|
|
718
|
+
if not raw_regs:
|
|
719
|
+
fail("CONFIG_ERROR",
|
|
720
|
+
"stream 缺失目标寄存器表 (regs 必填且未配置兜底)",
|
|
721
|
+
detail=(f"生效配置: {effective_config_desc()}; "
|
|
722
|
+
"请传 --regs 参数 (如 --regs 0xEE:8:ch), "
|
|
723
|
+
"或在 lab.yaml ndtdbg.stream.regs / ndtdbg.target_profile.stream_regs 配置"))
|
|
724
|
+
if isinstance(raw_regs, str):
|
|
725
|
+
raw_regs = [raw_regs]
|
|
726
|
+
specs = [parse_reg_spec(s) for s in raw_regs]
|
|
702
727
|
cmd_stream(io, config, specs, args.interval_ms, args.duration_s,
|
|
703
728
|
args.max_frames, args.out, args.decode, args.retry,
|
|
704
729
|
args.fail_threshold, args.require_target, args.pre_write,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<#
|
|
2
2
|
.SYNOPSIS
|
|
3
|
-
Dual-Tier MCP setup and registration helper (v0.
|
|
3
|
+
Dual-Tier MCP setup and registration helper (v0.9.0).
|
|
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.
|
|
245
|
+
Write-Host " FirmwareLoop Dual-Tier MCP & Agent Setup Helper (v0.9.0)" -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
|
|
File without changes
|