firmwareloop 0.2.2__tar.gz → 0.3.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.2.2 → firmwareloop-0.3.0}/PKG-INFO +3 -3
- {firmwareloop-0.2.2 → firmwareloop-0.3.0}/README.md +2 -2
- {firmwareloop-0.2.2 → firmwareloop-0.3.0}/firmwareloop.egg-info/PKG-INFO +3 -3
- {firmwareloop-0.2.2 → firmwareloop-0.3.0}/pyproject.toml +1 -1
- {firmwareloop-0.2.2 → firmwareloop-0.3.0}/tools/__init__.py +1 -1
- {firmwareloop-0.2.2 → firmwareloop-0.3.0}/tools/fw_mcp_server.py +15 -8
- {firmwareloop-0.2.2 → firmwareloop-0.3.0}/tools/lib/ndtdbg.py +34 -4
- {firmwareloop-0.2.2 → firmwareloop-0.3.0}/tools/ndtdbg_bridge.ps1 +72 -31
- {firmwareloop-0.2.2 → firmwareloop-0.3.0}/tools/ndtdbg_cli.py +28 -4
- {firmwareloop-0.2.2 → firmwareloop-0.3.0}/tools/setup-agent-mcp.ps1 +2 -2
- {firmwareloop-0.2.2 → firmwareloop-0.3.0}/LICENSE +0 -0
- {firmwareloop-0.2.2 → firmwareloop-0.3.0}/firmwareloop.egg-info/SOURCES.txt +0 -0
- {firmwareloop-0.2.2 → firmwareloop-0.3.0}/firmwareloop.egg-info/dependency_links.txt +0 -0
- {firmwareloop-0.2.2 → firmwareloop-0.3.0}/firmwareloop.egg-info/entry_points.txt +0 -0
- {firmwareloop-0.2.2 → firmwareloop-0.3.0}/firmwareloop.egg-info/requires.txt +0 -0
- {firmwareloop-0.2.2 → firmwareloop-0.3.0}/firmwareloop.egg-info/top_level.txt +0 -0
- {firmwareloop-0.2.2 → firmwareloop-0.3.0}/setup.cfg +0 -0
- {firmwareloop-0.2.2 → firmwareloop-0.3.0}/tools/acceptance-scenario.ps1 +0 -0
- {firmwareloop-0.2.2 → firmwareloop-0.3.0}/tools/build.ps1 +0 -0
- {firmwareloop-0.2.2 → firmwareloop-0.3.0}/tools/can.ps1 +0 -0
- {firmwareloop-0.2.2 → firmwareloop-0.3.0}/tools/check-qoder-mcp.ps1 +0 -0
- {firmwareloop-0.2.2 → firmwareloop-0.3.0}/tools/common/build-backends.psm1 +0 -0
- {firmwareloop-0.2.2 → firmwareloop-0.3.0}/tools/common/fw.psm1 +0 -0
- {firmwareloop-0.2.2 → firmwareloop-0.3.0}/tools/common/uart_probe.py +0 -0
- {firmwareloop-0.2.2 → firmwareloop-0.3.0}/tools/doctor.ps1 +0 -0
- {firmwareloop-0.2.2 → firmwareloop-0.3.0}/tools/flash.ps1 +0 -0
- {firmwareloop-0.2.2 → firmwareloop-0.3.0}/tools/instrument_cli.py +0 -0
- {firmwareloop-0.2.2 → firmwareloop-0.3.0}/tools/lib/__init__.py +0 -0
- {firmwareloop-0.2.2 → firmwareloop-0.3.0}/tools/lib/instruments.py +0 -0
- {firmwareloop-0.2.2 → firmwareloop-0.3.0}/tools/logic_capture.ps1 +0 -0
- {firmwareloop-0.2.2 → firmwareloop-0.3.0}/tools/logic_decode.ps1 +0 -0
- {firmwareloop-0.2.2 → firmwareloop-0.3.0}/tools/reset.ps1 +0 -0
- {firmwareloop-0.2.2 → firmwareloop-0.3.0}/tools/sigrok_csv_to_fwloop.py +0 -0
- {firmwareloop-0.2.2 → firmwareloop-0.3.0}/tools/test-backends.ps1 +0 -0
- {firmwareloop-0.2.2 → firmwareloop-0.3.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.3.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
|
|
@@ -381,7 +381,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
381
381
|
├── demo-firmware/ 示例固件(CMake;宿主编译模拟 MCU,闭环验证载体)
|
|
382
382
|
├── demo-make/ 示例固件(Make;构建测试载体)
|
|
383
383
|
├── docs/ DEPENDENCY_MATRIX / REUSE_PLAN / V0.0.2_GAP_VERIFICATION
|
|
384
|
-
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.
|
|
384
|
+
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.3.0)
|
|
385
385
|
├── .mcp.example.json 双层 MCP 配置模板(firmwareloop + agentic-hil)
|
|
386
386
|
├── AGENTS.md 通用智能体规范(Antigravity / Qoder / Cursor 等)
|
|
387
387
|
├── CLAUDE.md Claude Code CLI 指南
|
|
@@ -391,7 +391,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
391
391
|
|
|
392
392
|
---
|
|
393
393
|
|
|
394
|
-
## 能力验证状态(v0.
|
|
394
|
+
## 能力验证状态(v0.3.0)
|
|
395
395
|
|
|
396
396
|
> 状态定义:`Implemented`(已实现)/ `Simulator Validated`(模拟验证)/
|
|
397
397
|
> `Real Hardware Validated`(真机验证)/ `Experimental` / `Not Implemented`
|
|
@@ -353,7 +353,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
353
353
|
├── demo-firmware/ 示例固件(CMake;宿主编译模拟 MCU,闭环验证载体)
|
|
354
354
|
├── demo-make/ 示例固件(Make;构建测试载体)
|
|
355
355
|
├── docs/ DEPENDENCY_MATRIX / REUSE_PLAN / V0.0.2_GAP_VERIFICATION
|
|
356
|
-
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.
|
|
356
|
+
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.3.0)
|
|
357
357
|
├── .mcp.example.json 双层 MCP 配置模板(firmwareloop + agentic-hil)
|
|
358
358
|
├── AGENTS.md 通用智能体规范(Antigravity / Qoder / Cursor 等)
|
|
359
359
|
├── CLAUDE.md Claude Code CLI 指南
|
|
@@ -363,7 +363,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
363
363
|
|
|
364
364
|
---
|
|
365
365
|
|
|
366
|
-
## 能力验证状态(v0.
|
|
366
|
+
## 能力验证状态(v0.3.0)
|
|
367
367
|
|
|
368
368
|
> 状态定义:`Implemented`(已实现)/ `Simulator Validated`(模拟验证)/
|
|
369
369
|
> `Real Hardware Validated`(真机验证)/ `Experimental` / `Not Implemented`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: firmwareloop
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.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
|
|
@@ -381,7 +381,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
381
381
|
├── demo-firmware/ 示例固件(CMake;宿主编译模拟 MCU,闭环验证载体)
|
|
382
382
|
├── demo-make/ 示例固件(Make;构建测试载体)
|
|
383
383
|
├── docs/ DEPENDENCY_MATRIX / REUSE_PLAN / V0.0.2_GAP_VERIFICATION
|
|
384
|
-
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.
|
|
384
|
+
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.3.0)
|
|
385
385
|
├── .mcp.example.json 双层 MCP 配置模板(firmwareloop + agentic-hil)
|
|
386
386
|
├── AGENTS.md 通用智能体规范(Antigravity / Qoder / Cursor 等)
|
|
387
387
|
├── CLAUDE.md Claude Code CLI 指南
|
|
@@ -391,7 +391,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
391
391
|
|
|
392
392
|
---
|
|
393
393
|
|
|
394
|
-
## 能力验证状态(v0.
|
|
394
|
+
## 能力验证状态(v0.3.0)
|
|
395
395
|
|
|
396
396
|
> 状态定义:`Implemented`(已实现)/ `Simulator Validated`(模拟验证)/
|
|
397
397
|
> `Real Hardware Validated`(真机验证)/ `Experimental` / `Not Implemented`
|
|
@@ -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.3.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, Cursor) via the Model Context
|
|
@@ -360,6 +360,8 @@ def handle_fw_ndtdbg_stream(args: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
360
360
|
cmd.extend(["--fail-threshold", str(int(args["fail_threshold"]))])
|
|
361
361
|
if args.get("require_target"):
|
|
362
362
|
cmd.append("--require-target")
|
|
363
|
+
if args.get("pre_write"):
|
|
364
|
+
cmd.extend(["--pre-write", *[str(p) for p in args["pre_write"]]])
|
|
363
365
|
_ndtdbg_reg_args(args, cmd)
|
|
364
366
|
return _ndtdbg_run(cmd, timeout=int(duration_s) + 120)
|
|
365
367
|
|
|
@@ -1387,7 +1389,7 @@ TOOLS_REGISTRY = {
|
|
|
1387
1389
|
"handler": handle_fw_ndtdbg_flash,
|
|
1388
1390
|
},
|
|
1389
1391
|
"fw_ndtdbg_stream": {
|
|
1390
|
-
"description": "Stream-read a set of IIC registers from the NdtBox target chip (ChipSea Fxx) at a fixed interval via one long-lived USB session. Read-only. Writes CSV with timestamps; returns JSON summary (frames, FF_GAP/IO_FAIL counts, interval stats).
|
|
1392
|
+
"description": "Stream-read a set of IIC registers from the NdtBox target chip (ChipSea Fxx) at a fixed interval via one long-lived USB session. Read-only. Writes CSV with timestamps; returns JSON summary (frames, FF_GAP/IO_FAIL counts, interval stats, io_overhead). Supports two protocol profiles: continuous (pure-read polling) and request-response (pre_write command written before every frame, e.g. PT488 0x10/0x14 channels). Per-frame IO overhead is reported separately in io_overhead_avg_ms.",
|
|
1391
1393
|
"inputSchema": {
|
|
1392
1394
|
"type": "object",
|
|
1393
1395
|
"properties": {
|
|
@@ -1437,6 +1439,11 @@ TOOLS_REGISTRY = {
|
|
|
1437
1439
|
"description": "Probe reg 0x10 before capture; abort with TARGET_MISMATCH when the chip does not answer.",
|
|
1438
1440
|
"default": False,
|
|
1439
1441
|
},
|
|
1442
|
+
"pre_write": {
|
|
1443
|
+
"type": "array",
|
|
1444
|
+
"items": {"type": "string"},
|
|
1445
|
+
"description": "Request-response protocol profile: commands 'addr:data' written before EVERY frame (e.g. ['0xEC:10']) so request-response channels refresh each read. Omit for continuous (pure-read) streams. Defaults from lab.yaml ndtdbg.stream.pre_write when omitted.",
|
|
1446
|
+
},
|
|
1440
1447
|
"dll_dir": {"type": "string", "description": "NdtDebugTool lib dir; default from lab.yaml ndtdbg.dll_dir"},
|
|
1441
1448
|
"slave_addr": {"type": "string", "description": "IIC slave address, e.g. 0xA0; default from lab.yaml"},
|
|
1442
1449
|
},
|
|
@@ -1619,7 +1626,7 @@ def process_request(req: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
|
1619
1626
|
},
|
|
1620
1627
|
"serverInfo": {
|
|
1621
1628
|
"name": "firmwareloop",
|
|
1622
|
-
"version": "0.
|
|
1629
|
+
"version": "0.3.0"
|
|
1623
1630
|
}
|
|
1624
1631
|
}
|
|
1625
1632
|
}
|
|
@@ -1754,7 +1761,7 @@ def _settle_stashed_entrypoints(stashed) -> None:
|
|
|
1754
1761
|
def handle_cli_update() -> int:
|
|
1755
1762
|
"""Handle `firmwareloop update` command."""
|
|
1756
1763
|
print("============================================================")
|
|
1757
|
-
print(" FirmwareLoop Auto-Updater (v0.
|
|
1764
|
+
print(" FirmwareLoop Auto-Updater (v0.3.0)")
|
|
1758
1765
|
print("============================================================")
|
|
1759
1766
|
|
|
1760
1767
|
is_git_repo = os.path.exists(os.path.join(REPO_ROOT, ".git"))
|
|
@@ -2029,7 +2036,7 @@ def set_ndtdbg_dlldir(dll_path: str) -> str:
|
|
|
2029
2036
|
def handle_cli_setup() -> int:
|
|
2030
2037
|
"""Handle `firmwareloop setup` command: simultaneously configure Antigravity CLI, Claude Code CLI, Qoder IDE, and CodeBuddy IDE."""
|
|
2031
2038
|
print("============================================================")
|
|
2032
|
-
print(" FirmwareLoop Multi-Agent Global Setup Helper (v0.
|
|
2039
|
+
print(" FirmwareLoop Multi-Agent Global Setup Helper (v0.3.0)")
|
|
2033
2040
|
print("============================================================")
|
|
2034
2041
|
print("")
|
|
2035
2042
|
|
|
@@ -2154,7 +2161,7 @@ def handle_cli_setup() -> int:
|
|
|
2154
2161
|
def handle_cli_init() -> int:
|
|
2155
2162
|
"""Handle `firmwareloop init` command."""
|
|
2156
2163
|
print("============================================================")
|
|
2157
|
-
print(" FirmwareLoop Multi-Agent Project Initializer (v0.
|
|
2164
|
+
print(" FirmwareLoop Multi-Agent Project Initializer (v0.3.0)")
|
|
2158
2165
|
print("============================================================")
|
|
2159
2166
|
cwd = os.getcwd()
|
|
2160
2167
|
print(f"[*] Initializing multi-agent guidelines & bench config in:\n {cwd}\n")
|
|
@@ -2175,7 +2182,7 @@ def handle_cli_init() -> int:
|
|
|
2175
2182
|
|
|
2176
2183
|
|
|
2177
2184
|
def print_cli_help() -> None:
|
|
2178
|
-
print("""FirmwareLoop (fwloop) — AI Agent Firmware Engineering & Lab Automation Platform (v0.
|
|
2185
|
+
print("""FirmwareLoop (fwloop) — AI Agent Firmware Engineering & Lab Automation Platform (v0.3.0)
|
|
2179
2186
|
|
|
2180
2187
|
Usage:
|
|
2181
2188
|
fwloop [command] (or: firmwareloop [command])
|
|
@@ -2215,7 +2222,7 @@ def main() -> None:
|
|
|
2215
2222
|
sys.exit(2)
|
|
2216
2223
|
sys.exit(handle_cli_ndtdebugtool(args[1]))
|
|
2217
2224
|
elif cmd in ["version", "-v", "--version"]:
|
|
2218
|
-
print("FirmwareLoop v0.
|
|
2225
|
+
print("FirmwareLoop v0.3.0")
|
|
2219
2226
|
sys.exit(0)
|
|
2220
2227
|
elif cmd in ["help", "-h", "--help"]:
|
|
2221
2228
|
print_cli_help()
|
|
@@ -106,6 +106,30 @@ def parse_reg_spec(spec: str) -> dict:
|
|
|
106
106
|
return {"addr": addr, "len": length, "name": name or f"reg_{addr:02X}"}
|
|
107
107
|
|
|
108
108
|
|
|
109
|
+
def parse_pre_write_spec(spec: str) -> dict:
|
|
110
|
+
"""解析 pre-write spec 'addr:data' -> {'addr', 'data'} (请求-响应式协议每帧先写的命令)。
|
|
111
|
+
|
|
112
|
+
data 为 hex 载荷 (可含空格)。Fail-Closed: 格式/越界抛 CONFIG_ERROR。
|
|
113
|
+
"""
|
|
114
|
+
parts = str(spec).split(":", 1)
|
|
115
|
+
if len(parts) != 2 or not parts[0] or not parts[1].strip():
|
|
116
|
+
raise NdtBoxError("CONFIG_ERROR", "pre-write spec 格式非法 (addr:data)", str(spec))
|
|
117
|
+
try:
|
|
118
|
+
addr = int(parts[0], 0)
|
|
119
|
+
except ValueError as exc:
|
|
120
|
+
raise NdtBoxError("CONFIG_ERROR", "pre-write spec addr 不是合法整数", str(spec)) from exc
|
|
121
|
+
if not 0 <= addr <= 0xFF:
|
|
122
|
+
raise NdtBoxError("CONFIG_ERROR", "pre-write spec addr 越界 (0..0xFF)", hex(addr))
|
|
123
|
+
data = parts[1].strip()
|
|
124
|
+
if len(data.replace(" ", "")) % 2 != 0:
|
|
125
|
+
raise NdtBoxError("CONFIG_ERROR", "pre-write spec data 不是偶数长度 hex", data)
|
|
126
|
+
try:
|
|
127
|
+
bytes.fromhex(data.replace(" ", ""))
|
|
128
|
+
except ValueError as exc:
|
|
129
|
+
raise NdtBoxError("CONFIG_ERROR", "pre-write spec data 含非法 hex 字符", data) from exc
|
|
130
|
+
return {"addr": addr, "data": data}
|
|
131
|
+
|
|
132
|
+
|
|
109
133
|
def decode_payload(hex_str: str | None, mode: str) -> str | None:
|
|
110
134
|
"""hex 载荷按模式解码为 '|' 分隔数值串; mode=hex 或空载荷返回 None。
|
|
111
135
|
|
|
@@ -132,17 +156,23 @@ def decode_payload(hex_str: str | None, mode: str) -> str | None:
|
|
|
132
156
|
return "|".join(str(v) for v in vals) if vals else None
|
|
133
157
|
|
|
134
158
|
|
|
135
|
-
def interval_stats(frame_times_ms: list[float]) -> dict:
|
|
136
|
-
"""
|
|
159
|
+
def interval_stats(frame_times_ms: list[float], target_ms: float | None = None) -> dict:
|
|
160
|
+
"""帧开始时间序列 -> 实际间隔统计; 帧数 < 2 时返回空 dict。
|
|
161
|
+
|
|
162
|
+
gaps_n 语义 (实战修正): 仅统计间隔 > 2x 目标间隔的帧数 (真实缺帧/卡顿);
|
|
163
|
+
正常 IO 开销 (~10ms/帧) 不计入, 否则该指标永远接近 100% 失真。
|
|
164
|
+
"""
|
|
137
165
|
gaps = [round(b - a, 1) for a, b in zip(frame_times_ms, frame_times_ms[1:])]
|
|
138
166
|
if not gaps:
|
|
139
167
|
return {}
|
|
140
168
|
ordered = sorted(gaps)
|
|
141
|
-
|
|
142
|
-
"gaps_n": len(gaps),
|
|
169
|
+
out = {
|
|
143
170
|
"interval_actual_avg_ms": round(sum(gaps) / len(gaps), 1),
|
|
144
171
|
"interval_actual_p95_ms": ordered[min(len(ordered) - 1, int(len(ordered) * 0.95))],
|
|
145
172
|
}
|
|
173
|
+
if target_ms is not None:
|
|
174
|
+
out["gaps_n"] = sum(1 for g in gaps if g > 2 * target_ms)
|
|
175
|
+
return out
|
|
146
176
|
|
|
147
177
|
|
|
148
178
|
def strip_nfw(file_bytes: bytes, header_len: int = 256) -> bytes:
|
|
@@ -463,11 +463,17 @@ try {
|
|
|
463
463
|
$sRetry = if ($null -ne $cfg.retry) { [int]$cfg.retry } else { 2 }
|
|
464
464
|
$sFailThr = if ($null -ne $cfg.fail_threshold) { [int]$cfg.fail_threshold } else { 50 }
|
|
465
465
|
$sRequireTarget = [bool]$cfg.require_target
|
|
466
|
+
$sPreWrites = if ($cfg.pre_writes) { @($cfg.pre_writes) } else { @() }
|
|
466
467
|
if ($sDurationS -le 0 -and $sMaxFrames -le 0) { Fail "CONFIG_ERROR" "stream 需要 duration_s 或 max_frames 之一 > 0" $null }
|
|
467
468
|
if ($sInterval -lt 1) { Fail "CONFIG_ERROR" "stream interval_ms 必须 >= 1" $sInterval }
|
|
468
469
|
foreach ($r in $sRegs) {
|
|
469
470
|
if (([int]$r.len) -lt 1 -or ([int]$r.len) -gt 256) { Fail "CONFIG_ERROR" "stream 读长度越界 (1..256)" $r.len }
|
|
470
471
|
}
|
|
472
|
+
foreach ($pw in $sPreWrites) {
|
|
473
|
+
if ($null -eq $pw.addr -or -not $pw.data) { Fail "CONFIG_ERROR" "pre_writes 项需要 {addr, data}" $null }
|
|
474
|
+
if (([int]$pw.addr) -lt 0 -or ([int]$pw.addr) -gt 0xFF) { Fail "CONFIG_ERROR" "pre_writes addr 越界 (0..0xFF)" $pw.addr }
|
|
475
|
+
if (((Parse-Hex ([string]$pw.data)).Count + 2) -gt 256) { Fail "CONFIG_ERROR" "pre_writes 写事务过长 (含从机地址+寄存器地址须 <=256)" $pw.data }
|
|
476
|
+
}
|
|
471
477
|
|
|
472
478
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
|
473
479
|
$sw = [System.Diagnostics.Stopwatch]::StartNew()
|
|
@@ -495,50 +501,83 @@ try {
|
|
|
495
501
|
$framesOk = 0; $framesGap = 0; $framesIoFail = 0
|
|
496
502
|
$frameNo = 0
|
|
497
503
|
$consecIoFail = 0
|
|
504
|
+
$frameIoMsSum = 0.0 # 帧内 IO 耗时累计 (帧完成 - 帧开始), 供 io_overhead_avg_ms
|
|
498
505
|
$aborted = $false; $abortReason = ""
|
|
499
506
|
$durationMsTotal = [int]($sDurationS * 1000)
|
|
500
507
|
while ($true) {
|
|
501
508
|
$frameNo++
|
|
502
509
|
$frameStartMs = $sw.Elapsed.TotalMilliseconds
|
|
503
510
|
$hasIoFail = $false; $hasGap = $false
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
511
|
+
|
|
512
|
+
# --- pre-write (请求-响应式协议): 每帧先写请求寄存器再读数据 ---
|
|
513
|
+
# 写失败 -> 记 IO_FAIL 行并跳过本帧读 (请求未送达, 读到的是旧快照)
|
|
514
|
+
$preOk = $true
|
|
515
|
+
foreach ($pw in $sPreWrites) {
|
|
516
|
+
$pAddr = [int]$pw.addr
|
|
517
|
+
$pData = Parse-Hex ([string]$pw.data)
|
|
518
|
+
$pwOk = $false
|
|
507
519
|
try {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
if (Invoke-WriteRead $h $tx
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
520
|
+
$tx = New-Object byte[] (2 + $pData.Count)
|
|
521
|
+
$tx[0] = [byte]$SlaveAddr; $tx[1] = [byte]$pAddr
|
|
522
|
+
[Array]::Copy($pData, 0, $tx, 2, $pData.Count)
|
|
523
|
+
for ($pwA = 0; $pwA -le $sRetry; $pwA++) {
|
|
524
|
+
if (Invoke-WriteRead $h $tx (New-Object byte[] 0)) { $pwOk = $true; break }
|
|
525
|
+
if ($pwA -lt $sRetry) { Start-Sleep -Milliseconds 5 }
|
|
526
|
+
}
|
|
527
|
+
} catch {
|
|
528
|
+
$pwOk = $false
|
|
529
|
+
}
|
|
530
|
+
if (-not $pwOk) {
|
|
531
|
+
$preOk = $false
|
|
532
|
+
$tRel = [math]::Round($frameStartMs, 1)
|
|
533
|
+
[Console]::WriteLine(("#F`t{0}`t{1}`t0x{2:X2}`t-`tIO_FAIL" -f $frameNo, $tRel, $pAddr))
|
|
534
|
+
[Console]::Out.Flush()
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if ($preOk) {
|
|
539
|
+
foreach ($r in $sRegs) {
|
|
540
|
+
$reg = [int]$r.addr; $rlen = [int]$r.len
|
|
541
|
+
$hexOut = "-"; $tag = "OK"
|
|
542
|
+
try {
|
|
543
|
+
for ($attempt = 0; $attempt -le $sRetry; $attempt++) {
|
|
544
|
+
$tx = New-Object byte[] 2
|
|
545
|
+
$tx[0] = [byte]$SlaveAddr; $tx[1] = [byte]$reg
|
|
546
|
+
$rb = New-Object byte[] $rlen
|
|
547
|
+
if (Invoke-WriteRead $h $tx $rb) {
|
|
548
|
+
# ForEach-Object 将管道元素绑定到 $_——scriptblock 用 param()
|
|
549
|
+
# 形式收参恒为 null (Null 方法调用), 必须内联 $_
|
|
550
|
+
$hexStr = (($rb | ForEach-Object { $_.ToString("X2") }) -join " ")
|
|
551
|
+
$allFF = $true
|
|
552
|
+
foreach ($b in $rb) { if ($b -ne 0xFF) { $allFF = $false; break } }
|
|
553
|
+
if ($allFF) {
|
|
554
|
+
$tag = "FF_GAP"; $hexOut = $hexStr # 总线高阻特征: 记录原值, 重试
|
|
555
|
+
if ($attempt -lt $sRetry) { Start-Sleep -Milliseconds 5 }
|
|
556
|
+
} else {
|
|
557
|
+
$tag = "OK"; $hexOut = $hexStr
|
|
558
|
+
break
|
|
559
|
+
}
|
|
521
560
|
} else {
|
|
522
|
-
$tag = "
|
|
523
|
-
|
|
561
|
+
$tag = "IO_FAIL"; $hexOut = "-"
|
|
562
|
+
if ($attempt -lt $sRetry) { Start-Sleep -Milliseconds 5 }
|
|
524
563
|
}
|
|
525
|
-
} else {
|
|
526
|
-
$tag = "IO_FAIL"; $hexOut = "-"
|
|
527
|
-
if ($attempt -lt $sRetry) { Start-Sleep -Milliseconds 5 }
|
|
528
564
|
}
|
|
565
|
+
} catch {
|
|
566
|
+
# 单寄存器异常降级为 IO_FAIL 行继续采集, 不作废整个 stream (fail-closed 韧性)
|
|
567
|
+
$tag = "IO_FAIL"; $hexOut = "-"
|
|
529
568
|
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
569
|
+
if ($tag -eq "IO_FAIL") { $hasIoFail = $true }
|
|
570
|
+
elseif ($tag -eq "FF_GAP") { $hasGap = $true }
|
|
571
|
+
# t_rel 取帧开始时刻: 帧间隔统计不含 IO 开销 (否则 p95 永远略超 target);
|
|
572
|
+
# 帧内 IO 耗时由 summary 的 io_overhead_avg_ms 单独暴露
|
|
573
|
+
$tRel = [math]::Round($frameStartMs, 1)
|
|
574
|
+
# 方法调用参数列表里逗号是参数分隔符, 必须括号把整个 -f 表达式包成单参数,
|
|
575
|
+
# 否则 -f 只拿到 1 个参数, {1} 起索引越界 (FormatException)
|
|
576
|
+
[Console]::WriteLine(("#F`t{0}`t{1}`t0x{2:X2}`t{3}`t{4}" -f $frameNo, $tRel, $reg, $hexOut, $tag))
|
|
577
|
+
[Console]::Out.Flush()
|
|
533
578
|
}
|
|
534
|
-
if ($tag -eq "IO_FAIL") { $hasIoFail = $true }
|
|
535
|
-
elseif ($tag -eq "FF_GAP") { $hasGap = $true }
|
|
536
|
-
$tRel = [math]::Round($sw.Elapsed.TotalMilliseconds, 1)
|
|
537
|
-
# 方法调用参数列表里逗号是参数分隔符, 必须括号把整个 -f 表达式包成单参数,
|
|
538
|
-
# 否则 -f 只拿到 1 个参数, {1} 起索引越界 (FormatException)
|
|
539
|
-
[Console]::WriteLine(("#F`t{0}`t{1}`t0x{2:X2}`t{3}`t{4}" -f $frameNo, $tRel, $reg, $hexOut, $tag))
|
|
540
|
-
[Console]::Out.Flush()
|
|
541
579
|
}
|
|
580
|
+
$frameIoMsSum += ($sw.Elapsed.TotalMilliseconds - $frameStartMs)
|
|
542
581
|
if ($hasIoFail) { $framesIoFail++; $consecIoFail++ }
|
|
543
582
|
else {
|
|
544
583
|
if ($hasGap) { $framesGap++ } else { $framesOk++ }
|
|
@@ -554,9 +593,11 @@ try {
|
|
|
554
593
|
}
|
|
555
594
|
}
|
|
556
595
|
[STDataCollection.Icb]::USBIO_CloseDevice([uint32]0) | Out-Null
|
|
596
|
+
$ioOverhead = if ($frameNo -gt 0) { [math]::Round($frameIoMsSum / $frameNo, 1) } else { 0 }
|
|
557
597
|
$sum = @{ ok = $true; action = "stream"; data = @{
|
|
558
598
|
frames_ok = $framesOk; frames_ff_gap = $framesGap; frames_io_fail = $framesIoFail;
|
|
559
599
|
frames_total = $frameNo; aborted = $aborted; abort_reason = $abortReason;
|
|
600
|
+
io_overhead_avg_ms = $ioOverhead;
|
|
560
601
|
duration_actual_ms = [int]$sw.Elapsed.TotalMilliseconds } }
|
|
561
602
|
[Console]::WriteLine(($sum | ConvertTo-Json -Compress -Depth 5))
|
|
562
603
|
[Console]::Out.Flush()
|
|
@@ -13,7 +13,7 @@ from datetime import datetime
|
|
|
13
13
|
|
|
14
14
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
15
15
|
from lib.ndtdbg import (ChipSeaFxx, NdtBoxError, NdtBoxIo, decode_payload, # noqa: E402
|
|
16
|
-
interval_stats, parse_reg_spec)
|
|
16
|
+
interval_stats, parse_pre_write_spec, parse_reg_spec)
|
|
17
17
|
|
|
18
18
|
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
19
19
|
|
|
@@ -199,7 +199,7 @@ def cmd_poll(io: NdtBoxIo, duration_ms: int, interval_ms: int, max_frames: int,
|
|
|
199
199
|
|
|
200
200
|
def cmd_stream(io: NdtBoxIo, config: dict, specs: list, interval_ms, duration_s: float,
|
|
201
201
|
max_frames: int, out_path, decode: str, retry: int, fail_threshold,
|
|
202
|
-
require_target: bool) -> None:
|
|
202
|
+
require_target: bool, pre_write_specs=None) -> None:
|
|
203
203
|
"""高速连续采集 (只读): 桥进程内长驻 USB 会话, 单帧开销 = 一次 I2C 事务。
|
|
204
204
|
|
|
205
205
|
帧语义: OK / FF_GAP (读成功但全 FF, 总线高阻特征, 只统计不熔断) /
|
|
@@ -227,8 +227,15 @@ def cmd_stream(io: NdtBoxIo, config: dict, specs: list, interval_ms, duration_s:
|
|
|
227
227
|
if out_dir:
|
|
228
228
|
os.makedirs(out_dir, exist_ok=True)
|
|
229
229
|
|
|
230
|
+
# pre-write (请求-响应式协议): CLI --pre-write 优先, 未传时回落 lab.yaml
|
|
231
|
+
# ndtdbg.stream.pre_write (产品协议 profile 沉淀); 两处均未配置 = 纯读 continuous
|
|
232
|
+
if pre_write_specs is None:
|
|
233
|
+
pre_write_specs = stream_cfg.get("pre_write") or []
|
|
234
|
+
pre_writes = [parse_pre_write_spec(s) for s in pre_write_specs]
|
|
235
|
+
|
|
230
236
|
spec = {
|
|
231
237
|
"regs": [{"addr": s["addr"], "len": s["len"], "name": s["name"]} for s in specs],
|
|
238
|
+
"pre_writes": [{"addr": p["addr"], "data": p["data"]} for p in pre_writes],
|
|
232
239
|
"interval_ms": interval_ms, "duration_s": float(duration_s), "max_frames": max_frames,
|
|
233
240
|
"retry": retry, "fail_threshold": fail_threshold, "require_target": require_target,
|
|
234
241
|
}
|
|
@@ -266,6 +273,17 @@ def cmd_stream(io: NdtBoxIo, config: dict, specs: list, interval_ms, duration_s:
|
|
|
266
273
|
data = io.stream_run(spec, on_row=on_row)
|
|
267
274
|
except KeyboardInterrupt:
|
|
268
275
|
data = None # 桥被终止; 行级计数近似, CSV 已写数据保留
|
|
276
|
+
except NdtBoxError as exc:
|
|
277
|
+
fh.close()
|
|
278
|
+
hints = {
|
|
279
|
+
"PROBE_NOT_FOUND": "检查 NdtBox USB 连接与 CH341 驱动; 退出 STDataCollection.exe (USB 独占)",
|
|
280
|
+
"TARGET_MISMATCH": "检查 IIC 接线/上拉/供电/从机地址; 请求-响应式通道需先 write_reg 开启 debug_mode (如 0xBE=01)",
|
|
281
|
+
}
|
|
282
|
+
hint = hints.get(exc.error_class)
|
|
283
|
+
detail = exc.detail or ""
|
|
284
|
+
if hint:
|
|
285
|
+
detail = f"{detail}; 建议: {hint}" if detail else f"建议: {hint}"
|
|
286
|
+
fail(exc.error_class, exc.message, detail=detail or None)
|
|
269
287
|
finally:
|
|
270
288
|
fh.close()
|
|
271
289
|
|
|
@@ -279,14 +297,17 @@ def cmd_stream(io: NdtBoxIo, config: dict, specs: list, interval_ms, duration_s:
|
|
|
279
297
|
"frames_io_fail": data.get("frames_io_fail", rows["IO_FAIL"]) if data else rows["IO_FAIL"],
|
|
280
298
|
"frames_total": total if total else sum(rows.values()),
|
|
281
299
|
"interval_target_ms": interval_ms,
|
|
300
|
+
"io_overhead_avg_ms": data.get("io_overhead_avg_ms") if data else None,
|
|
282
301
|
"duration_actual_s": round((data.get("duration_actual_ms") or 0) / 1000.0, 2) if data
|
|
283
302
|
else round((state["t"] or 0) / 1000.0, 2),
|
|
284
303
|
"aborted": bool(data.get("aborted")) if data else True,
|
|
285
304
|
"abort_reason": data.get("abort_reason") or ("interrupted" if data is None else ""),
|
|
286
305
|
}
|
|
287
|
-
summary.update(interval_stats(frame_times))
|
|
306
|
+
summary.update(interval_stats(frame_times, target_ms=interval_ms))
|
|
288
307
|
if decode != "hex":
|
|
289
308
|
summary["decode"] = decode
|
|
309
|
+
if pre_writes:
|
|
310
|
+
summary["pre_write"] = [f"{p['addr']:#x}:{p['data']}" for p in pre_writes]
|
|
290
311
|
|
|
291
312
|
if summary["aborted"] and summary["abort_reason"] == "fail_threshold":
|
|
292
313
|
# 熔断: Fail-Closed 错误信封, 但 data 携带已采数据 summary
|
|
@@ -377,6 +398,9 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
377
398
|
help="连续 IO_FAIL 熔断阈值 (默认 lab.yaml ndtdbg.stream.fail_threshold 或 50; 0=不熔断)")
|
|
378
399
|
st.add_argument("--require-target", action="store_true",
|
|
379
400
|
help="采集前探测 0x10, 无有效芯片应答则 TARGET_MISMATCH")
|
|
401
|
+
st.add_argument("--pre-write", nargs="+", default=None,
|
|
402
|
+
help="请求-响应式协议: 每帧先写的命令 spec addr:data (如 0xEC:10); "
|
|
403
|
+
"默认读 lab.yaml ndtdbg.stream.pre_write, 均未配置 = 纯读 continuous")
|
|
380
404
|
return p
|
|
381
405
|
|
|
382
406
|
|
|
@@ -405,7 +429,7 @@ def main(argv=None) -> None:
|
|
|
405
429
|
specs = [parse_reg_spec(s) for s in args.regs]
|
|
406
430
|
cmd_stream(io, config, specs, args.interval_ms, args.duration_s,
|
|
407
431
|
args.max_frames, args.out, args.decode, args.retry,
|
|
408
|
-
args.fail_threshold, args.require_target)
|
|
432
|
+
args.fail_threshold, args.require_target, args.pre_write)
|
|
409
433
|
else: # pragma: no cover
|
|
410
434
|
fail("CONFIG_ERROR", f"unknown cmd: {args.cmd}")
|
|
411
435
|
except NdtBoxError as exc:
|
|
@@ -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.3.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.3.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
|