firmwareloop 0.0.13__tar.gz → 0.1.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.0.13 → firmwareloop-0.1.0}/PKG-INFO +4 -3
- {firmwareloop-0.0.13 → firmwareloop-0.1.0}/README.md +3 -2
- {firmwareloop-0.0.13 → firmwareloop-0.1.0}/firmwareloop.egg-info/PKG-INFO +4 -3
- {firmwareloop-0.0.13 → firmwareloop-0.1.0}/firmwareloop.egg-info/SOURCES.txt +5 -1
- {firmwareloop-0.0.13 → firmwareloop-0.1.0}/pyproject.toml +1 -1
- {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/__init__.py +1 -1
- {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/fw_mcp_server.py +92 -9
- firmwareloop-0.1.0/tools/lib/ndtdbg.py +222 -0
- {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/logic_capture.ps1 +17 -8
- firmwareloop-0.1.0/tools/ndtdbg_bridge.ps1 +232 -0
- firmwareloop-0.1.0/tools/ndtdbg_cli.py +193 -0
- {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/setup-agent-mcp.ps1 +277 -277
- firmwareloop-0.1.0/tools/sigrok_csv_to_fwloop.py +41 -0
- {firmwareloop-0.0.13 → firmwareloop-0.1.0}/LICENSE +0 -0
- {firmwareloop-0.0.13 → firmwareloop-0.1.0}/firmwareloop.egg-info/dependency_links.txt +0 -0
- {firmwareloop-0.0.13 → firmwareloop-0.1.0}/firmwareloop.egg-info/entry_points.txt +0 -0
- {firmwareloop-0.0.13 → firmwareloop-0.1.0}/firmwareloop.egg-info/requires.txt +0 -0
- {firmwareloop-0.0.13 → firmwareloop-0.1.0}/firmwareloop.egg-info/top_level.txt +0 -0
- {firmwareloop-0.0.13 → firmwareloop-0.1.0}/setup.cfg +0 -0
- {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/acceptance-scenario.ps1 +0 -0
- {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/build.ps1 +0 -0
- {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/can.ps1 +0 -0
- {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/check-qoder-mcp.ps1 +0 -0
- {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/common/build-backends.psm1 +0 -0
- {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/common/fw.psm1 +0 -0
- {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/common/uart_probe.py +0 -0
- {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/doctor.ps1 +0 -0
- {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/flash.ps1 +0 -0
- {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/instrument_cli.py +0 -0
- {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/lib/__init__.py +0 -0
- {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/lib/instruments.py +0 -0
- {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/logic_decode.ps1 +0 -0
- {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/reset.ps1 +0 -0
- {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/test-backends.ps1 +0 -0
- {firmwareloop-0.0.13 → firmwareloop-0.1.0}/tools/test.ps1 +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: firmwareloop
|
|
3
|
-
Version: 0.0
|
|
3
|
+
Version: 0.1.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
|
|
@@ -338,7 +338,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
338
338
|
├── demo-firmware/ 示例固件(CMake;宿主编译模拟 MCU,闭环验证载体)
|
|
339
339
|
├── demo-make/ 示例固件(Make;构建测试载体)
|
|
340
340
|
├── docs/ DEPENDENCY_MATRIX / REUSE_PLAN / V0.0.2_GAP_VERIFICATION
|
|
341
|
-
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.0
|
|
341
|
+
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.1.0)
|
|
342
342
|
├── .mcp.example.json 双层 MCP 配置模板(firmwareloop + agentic-hil)
|
|
343
343
|
├── AGENTS.md 通用智能体规范(Antigravity / Qoder / Cursor 等)
|
|
344
344
|
├── CLAUDE.md Claude Code CLI 指南
|
|
@@ -348,7 +348,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
348
348
|
|
|
349
349
|
---
|
|
350
350
|
|
|
351
|
-
## 能力验证状态(v0.0
|
|
351
|
+
## 能力验证状态(v0.1.0)
|
|
352
352
|
|
|
353
353
|
> 状态定义:`Implemented`(已实现)/ `Simulator Validated`(模拟验证)/
|
|
354
354
|
> `Real Hardware Validated`(真机验证)/ `Experimental` / `Not Implemented`
|
|
@@ -368,6 +368,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
368
368
|
| VISA 仪器(simulator) | Simulator Validated -> Real Hardware Validated 待仪器 |
|
|
369
369
|
| Safety Policy(limits.yaml) | Implemented(simulator 校验)-> 外置权威配置 |
|
|
370
370
|
| 自动修复闭环(M6 载体) | Simulator Validated -> Real Hardware Validated 待 DUT |
|
|
371
|
+
| NdtBox 数据采集/调试(fw_ndtdbg_status / fw_ndtdbg_measure, ChipSea Fxx IIC) | Implemented(无硬件 Fail-Closed 已验证)-> Real Hardware Validated 待 NdtBox |
|
|
371
372
|
|
|
372
373
|
---
|
|
373
374
|
|
|
@@ -310,7 +310,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
310
310
|
├── demo-firmware/ 示例固件(CMake;宿主编译模拟 MCU,闭环验证载体)
|
|
311
311
|
├── demo-make/ 示例固件(Make;构建测试载体)
|
|
312
312
|
├── docs/ DEPENDENCY_MATRIX / REUSE_PLAN / V0.0.2_GAP_VERIFICATION
|
|
313
|
-
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.0
|
|
313
|
+
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.1.0)
|
|
314
314
|
├── .mcp.example.json 双层 MCP 配置模板(firmwareloop + agentic-hil)
|
|
315
315
|
├── AGENTS.md 通用智能体规范(Antigravity / Qoder / Cursor 等)
|
|
316
316
|
├── CLAUDE.md Claude Code CLI 指南
|
|
@@ -320,7 +320,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
320
320
|
|
|
321
321
|
---
|
|
322
322
|
|
|
323
|
-
## 能力验证状态(v0.0
|
|
323
|
+
## 能力验证状态(v0.1.0)
|
|
324
324
|
|
|
325
325
|
> 状态定义:`Implemented`(已实现)/ `Simulator Validated`(模拟验证)/
|
|
326
326
|
> `Real Hardware Validated`(真机验证)/ `Experimental` / `Not Implemented`
|
|
@@ -340,6 +340,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
340
340
|
| VISA 仪器(simulator) | Simulator Validated -> Real Hardware Validated 待仪器 |
|
|
341
341
|
| Safety Policy(limits.yaml) | Implemented(simulator 校验)-> 外置权威配置 |
|
|
342
342
|
| 自动修复闭环(M6 载体) | Simulator Validated -> Real Hardware Validated 待 DUT |
|
|
343
|
+
| NdtBox 数据采集/调试(fw_ndtdbg_status / fw_ndtdbg_measure, ChipSea Fxx IIC) | Implemented(无硬件 Fail-Closed 已验证)-> Real Hardware Validated 待 NdtBox |
|
|
343
344
|
|
|
344
345
|
---
|
|
345
346
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: firmwareloop
|
|
3
|
-
Version: 0.0
|
|
3
|
+
Version: 0.1.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
|
|
@@ -338,7 +338,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
338
338
|
├── demo-firmware/ 示例固件(CMake;宿主编译模拟 MCU,闭环验证载体)
|
|
339
339
|
├── demo-make/ 示例固件(Make;构建测试载体)
|
|
340
340
|
├── docs/ DEPENDENCY_MATRIX / REUSE_PLAN / V0.0.2_GAP_VERIFICATION
|
|
341
|
-
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.0
|
|
341
|
+
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.1.0)
|
|
342
342
|
├── .mcp.example.json 双层 MCP 配置模板(firmwareloop + agentic-hil)
|
|
343
343
|
├── AGENTS.md 通用智能体规范(Antigravity / Qoder / Cursor 等)
|
|
344
344
|
├── CLAUDE.md Claude Code CLI 指南
|
|
@@ -348,7 +348,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
348
348
|
|
|
349
349
|
---
|
|
350
350
|
|
|
351
|
-
## 能力验证状态(v0.0
|
|
351
|
+
## 能力验证状态(v0.1.0)
|
|
352
352
|
|
|
353
353
|
> 状态定义:`Implemented`(已实现)/ `Simulator Validated`(模拟验证)/
|
|
354
354
|
> `Real Hardware Validated`(真机验证)/ `Experimental` / `Not Implemented`
|
|
@@ -368,6 +368,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
368
368
|
| VISA 仪器(simulator) | Simulator Validated -> Real Hardware Validated 待仪器 |
|
|
369
369
|
| Safety Policy(limits.yaml) | Implemented(simulator 校验)-> 外置权威配置 |
|
|
370
370
|
| 自动修复闭环(M6 载体) | Simulator Validated -> Real Hardware Validated 待 DUT |
|
|
371
|
+
| NdtBox 数据采集/调试(fw_ndtdbg_status / fw_ndtdbg_measure, ChipSea Fxx IIC) | Implemented(无硬件 Fail-Closed 已验证)-> Real Hardware Validated 待 NdtBox |
|
|
371
372
|
|
|
372
373
|
---
|
|
373
374
|
|
|
@@ -18,12 +18,16 @@ tools/fw_mcp_server.py
|
|
|
18
18
|
tools/instrument_cli.py
|
|
19
19
|
tools/logic_capture.ps1
|
|
20
20
|
tools/logic_decode.ps1
|
|
21
|
+
tools/ndtdbg_bridge.ps1
|
|
22
|
+
tools/ndtdbg_cli.py
|
|
21
23
|
tools/reset.ps1
|
|
22
24
|
tools/setup-agent-mcp.ps1
|
|
25
|
+
tools/sigrok_csv_to_fwloop.py
|
|
23
26
|
tools/test-backends.ps1
|
|
24
27
|
tools/test.ps1
|
|
25
28
|
tools/common/build-backends.psm1
|
|
26
29
|
tools/common/fw.psm1
|
|
27
30
|
tools/common/uart_probe.py
|
|
28
31
|
tools/lib/__init__.py
|
|
29
|
-
tools/lib/instruments.py
|
|
32
|
+
tools/lib/instruments.py
|
|
33
|
+
tools/lib/ndtdbg.py
|
|
@@ -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.0
|
|
3
|
+
fw_mcp_server.py - FirmwareLoop High-Level Workflow & Device MCP Server (v0.1.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, Cursor) via the Model Context
|
|
@@ -14,6 +14,8 @@ Tools provided:
|
|
|
14
14
|
- fw_run_hil_test: Hardware-in-the-loop pytest suite (test.ps1)
|
|
15
15
|
- fw_acceptance_scenario: End-to-end acceptance scenario (acceptance-scenario.ps1)
|
|
16
16
|
- fw_measure: Controlled PyVISA/SCPI instrument measurement (instrument_cli.py)
|
|
17
|
+
- fw_ndtdbg_status: NdtBox (USB->IIC) connectivity & firmware version probe (ndtdbg_cli.py)
|
|
18
|
+
- fw_ndtdbg_measure: NdtBox pressure-sensor data stream polling over IIC (ndtdbg_cli.py)
|
|
17
19
|
- fw_logic_capture: Logic analyzer protocol capture (logic_capture.ps1)
|
|
18
20
|
- fw_logic_decode: Logic analyzer protocol decoding & assertion (logic_decode.ps1)
|
|
19
21
|
- fw_get_evidence: Read structured audit run evidence (artifacts/runs/)
|
|
@@ -240,6 +242,36 @@ def handle_fw_measure(args: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
240
242
|
return res
|
|
241
243
|
|
|
242
244
|
|
|
245
|
+
def handle_fw_ndtdbg_status(args: Dict[str, Any]) -> Dict[str, Any]:
|
|
246
|
+
cli_script = os.path.join(REPO_ROOT, "tools", "ndtdbg_cli.py")
|
|
247
|
+
cmd = [PYTHON_EXE, cli_script, "status"]
|
|
248
|
+
if args.get("dll_dir"):
|
|
249
|
+
cmd.extend(["--dll-dir", args["dll_dir"]])
|
|
250
|
+
if args.get("slave_addr"):
|
|
251
|
+
cmd.extend(["--slave-addr", str(args["slave_addr"])])
|
|
252
|
+
res = run_process(cmd, timeout=60)
|
|
253
|
+
if res.get("data"):
|
|
254
|
+
return res["data"]
|
|
255
|
+
return res
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def handle_fw_ndtdbg_measure(args: Dict[str, Any]) -> Dict[str, Any]:
|
|
259
|
+
cli_script = os.path.join(REPO_ROOT, "tools", "ndtdbg_cli.py")
|
|
260
|
+
duration = int(args.get("duration_seconds", 2))
|
|
261
|
+
cmd = [PYTHON_EXE, cli_script, "poll", "--duration-ms", str(duration * 1000),
|
|
262
|
+
"--interval-ms", str(args.get("interval_ms", 10))]
|
|
263
|
+
if args.get("full_output"):
|
|
264
|
+
cmd.append("--full")
|
|
265
|
+
if args.get("dll_dir"):
|
|
266
|
+
cmd.extend(["--dll-dir", args["dll_dir"]])
|
|
267
|
+
if args.get("slave_addr"):
|
|
268
|
+
cmd.extend(["--slave-addr", str(args["slave_addr"])])
|
|
269
|
+
res = run_process(cmd, timeout=60 + duration)
|
|
270
|
+
if res.get("data"):
|
|
271
|
+
return res["data"]
|
|
272
|
+
return res
|
|
273
|
+
|
|
274
|
+
|
|
243
275
|
def handle_fw_logic_capture(args: Dict[str, Any]) -> Dict[str, Any]:
|
|
244
276
|
script = os.path.join(REPO_ROOT, "tools", "logic_capture.ps1")
|
|
245
277
|
cmd = [PWSH_EXE, "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", script, "-Json"]
|
|
@@ -253,11 +285,11 @@ def handle_fw_logic_capture(args: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
253
285
|
|
|
254
286
|
duration_ms = args.get("duration_ms")
|
|
255
287
|
if duration_ms:
|
|
256
|
-
cmd.extend(["-
|
|
288
|
+
cmd.extend(["-DurationSec", str(float(duration_ms) / 1000.0)])
|
|
257
289
|
|
|
258
290
|
output_file = args.get("output_file")
|
|
259
291
|
if output_file:
|
|
260
|
-
cmd.extend(["-
|
|
292
|
+
cmd.extend(["-OutDir", output_file])
|
|
261
293
|
|
|
262
294
|
res = run_process(cmd, timeout=30)
|
|
263
295
|
if res.get("data"):
|
|
@@ -1023,6 +1055,57 @@ TOOLS_REGISTRY = {
|
|
|
1023
1055
|
},
|
|
1024
1056
|
"handler": handle_fw_measure,
|
|
1025
1057
|
},
|
|
1058
|
+
"fw_ndtdbg_status": {
|
|
1059
|
+
"description": "Probe NdtBox (USB->IIC debug box, ChipSea Fxx) status: device open, firmware version, module connectivity. Fail-closed (PROBE_NOT_FOUND / TARGET_MISMATCH) when hardware absent or no IIC response.",
|
|
1060
|
+
"inputSchema": {
|
|
1061
|
+
"type": "object",
|
|
1062
|
+
"properties": {
|
|
1063
|
+
"dll_dir": {
|
|
1064
|
+
"type": "string",
|
|
1065
|
+
"description": "NdtDebugTool lib directory containing USBIOX.DLL; default from lab.yaml ndtdbg.dll_dir",
|
|
1066
|
+
},
|
|
1067
|
+
"slave_addr": {
|
|
1068
|
+
"type": "string",
|
|
1069
|
+
"description": "IIC slave address (e.g. 0xA0); default from lab.yaml ndtdbg.slave_addr",
|
|
1070
|
+
},
|
|
1071
|
+
},
|
|
1072
|
+
"additionalProperties": False,
|
|
1073
|
+
},
|
|
1074
|
+
"handler": handle_fw_ndtdbg_status,
|
|
1075
|
+
},
|
|
1076
|
+
"fw_ndtdbg_measure": {
|
|
1077
|
+
"description": "Poll NdtBox IIC debug data stream (ChipSea Fxx) for N seconds: frame count, checksum errors, channel samples. Read-only; firmware upgrade is a separate future capability.",
|
|
1078
|
+
"inputSchema": {
|
|
1079
|
+
"type": "object",
|
|
1080
|
+
"properties": {
|
|
1081
|
+
"duration_seconds": {
|
|
1082
|
+
"type": "integer",
|
|
1083
|
+
"description": "Poll duration in seconds",
|
|
1084
|
+
"default": 2,
|
|
1085
|
+
},
|
|
1086
|
+
"interval_ms": {
|
|
1087
|
+
"type": "integer",
|
|
1088
|
+
"description": "Sampling interval in milliseconds",
|
|
1089
|
+
"default": 10,
|
|
1090
|
+
},
|
|
1091
|
+
"full_output": {
|
|
1092
|
+
"type": "boolean",
|
|
1093
|
+
"description": "Return every frame (may be large); default keeps a frame sample plus totals",
|
|
1094
|
+
"default": False,
|
|
1095
|
+
},
|
|
1096
|
+
"dll_dir": {
|
|
1097
|
+
"type": "string",
|
|
1098
|
+
"description": "NdtDebugTool lib directory containing USBIOX.DLL; default from lab.yaml ndtdbg.dll_dir",
|
|
1099
|
+
},
|
|
1100
|
+
"slave_addr": {
|
|
1101
|
+
"type": "string",
|
|
1102
|
+
"description": "IIC slave address (e.g. 0xA0); default from lab.yaml ndtdbg.slave_addr",
|
|
1103
|
+
},
|
|
1104
|
+
},
|
|
1105
|
+
"additionalProperties": False,
|
|
1106
|
+
},
|
|
1107
|
+
"handler": handle_fw_ndtdbg_measure,
|
|
1108
|
+
},
|
|
1026
1109
|
"fw_logic_capture": {
|
|
1027
1110
|
"description": "Capture digital protocol waveforms (I2C, SPI, UART) via Saleae / sigrok or deterministic simulator.",
|
|
1028
1111
|
"inputSchema": {
|
|
@@ -1160,7 +1243,7 @@ def process_request(req: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
|
1160
1243
|
},
|
|
1161
1244
|
"serverInfo": {
|
|
1162
1245
|
"name": "firmwareloop",
|
|
1163
|
-
"version": "0.0
|
|
1246
|
+
"version": "0.1.0"
|
|
1164
1247
|
}
|
|
1165
1248
|
}
|
|
1166
1249
|
}
|
|
@@ -1252,7 +1335,7 @@ def process_request(req: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
|
1252
1335
|
def handle_cli_update() -> int:
|
|
1253
1336
|
"""Handle `firmwareloop update` command."""
|
|
1254
1337
|
print("============================================================")
|
|
1255
|
-
print(" FirmwareLoop Auto-Updater (v0.0
|
|
1338
|
+
print(" FirmwareLoop Auto-Updater (v0.1.0)")
|
|
1256
1339
|
print("============================================================")
|
|
1257
1340
|
|
|
1258
1341
|
is_git_repo = os.path.exists(os.path.join(REPO_ROOT, ".git"))
|
|
@@ -1395,7 +1478,7 @@ def configure_qoder_user_mcp(user_home: str) -> List[Dict[str, Any]]:
|
|
|
1395
1478
|
def handle_cli_setup() -> int:
|
|
1396
1479
|
"""Handle `firmwareloop setup` command: simultaneously configure Antigravity CLI, Claude Code CLI, and Qoder IDE."""
|
|
1397
1480
|
print("============================================================")
|
|
1398
|
-
print(" FirmwareLoop Multi-Agent Global Setup Helper (v0.0
|
|
1481
|
+
print(" FirmwareLoop Multi-Agent Global Setup Helper (v0.1.0)")
|
|
1399
1482
|
print("============================================================")
|
|
1400
1483
|
print("")
|
|
1401
1484
|
|
|
@@ -1500,7 +1583,7 @@ def handle_cli_setup() -> int:
|
|
|
1500
1583
|
def handle_cli_init() -> int:
|
|
1501
1584
|
"""Handle `firmwareloop init` command."""
|
|
1502
1585
|
print("============================================================")
|
|
1503
|
-
print(" FirmwareLoop Multi-Agent Project Initializer (v0.0
|
|
1586
|
+
print(" FirmwareLoop Multi-Agent Project Initializer (v0.1.0)")
|
|
1504
1587
|
print("============================================================")
|
|
1505
1588
|
cwd = os.getcwd()
|
|
1506
1589
|
print(f"[*] Initializing multi-agent guidelines & bench config in:\n {cwd}\n")
|
|
@@ -1521,7 +1604,7 @@ def handle_cli_init() -> int:
|
|
|
1521
1604
|
|
|
1522
1605
|
|
|
1523
1606
|
def print_cli_help() -> None:
|
|
1524
|
-
print("""FirmwareLoop (fwloop) — AI Agent Firmware Engineering & Lab Automation Platform (v0.0
|
|
1607
|
+
print("""FirmwareLoop (fwloop) — AI Agent Firmware Engineering & Lab Automation Platform (v0.1.0)
|
|
1525
1608
|
|
|
1526
1609
|
Usage:
|
|
1527
1610
|
fwloop [command] (or: firmwareloop [command])
|
|
@@ -1552,7 +1635,7 @@ def main() -> None:
|
|
|
1552
1635
|
elif cmd in ["setup", "register"]:
|
|
1553
1636
|
sys.exit(handle_cli_setup())
|
|
1554
1637
|
elif cmd in ["version", "-v", "--version"]:
|
|
1555
|
-
print("FirmwareLoop v0.0
|
|
1638
|
+
print("FirmwareLoop v0.1.0")
|
|
1556
1639
|
sys.exit(0)
|
|
1557
1640
|
elif cmd in ["help", "-h", "--help"]:
|
|
1558
1641
|
print_cli_help()
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""NdtBox (USB->IIC) 协议层: 芯海 Fxx 芯片的 IIC 寄存器帧与校验。
|
|
2
|
+
|
|
3
|
+
底层 IO 通过 32 位 PowerShell 桥 (tools/ndtdbg_bridge.ps1) 完成——USBIOX.DLL 为 x86-32,
|
|
4
|
+
项目 venv 为 64 位 Python, 无法 ctypes 直调; 桥由 SysWOW64 powershell.exe (32 位) 执行,
|
|
5
|
+
故本模块只做纯协议逻辑与桥返回解析, 全部平台无关、可单测。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import subprocess
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
# 32 位 Windows PowerShell 5.1 (x86), 与 x86 DLL 位宽匹配
|
|
16
|
+
PS32 = r"C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe"
|
|
17
|
+
|
|
18
|
+
_REWRITE_TIMES = 3 # 对齐 ChipSea.cs REWRITE_TIMES
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class NdtBoxError(Exception):
|
|
22
|
+
"""携带项目错误类别白名单之一的异常 (Fail-Closed, 不伪造成功)。"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, error_class: str, message: str, detail: str | None = None):
|
|
25
|
+
super().__init__(message)
|
|
26
|
+
self.error_class = error_class
|
|
27
|
+
self.message = message
|
|
28
|
+
self.detail = detail
|
|
29
|
+
|
|
30
|
+
def to_dict(self) -> dict:
|
|
31
|
+
d = {"ok": False, "error_class": self.error_class, "error": self.message}
|
|
32
|
+
if self.detail:
|
|
33
|
+
d["detail"] = self.detail
|
|
34
|
+
return d
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# ---------------------------------------------------------------------------
|
|
38
|
+
# 纯函数 (协议帧与校验, 平台无关, 直接对应反编译源码)
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
def checksum_two_bytes(buf: bytes) -> int:
|
|
42
|
+
"""CheckSumTwoBytes: 前 length/2 个 LE int16 之和取负 (无符号 16 位)。"""
|
|
43
|
+
total = 0
|
|
44
|
+
for i in range(0, len(buf) - (len(buf) % 2), 2):
|
|
45
|
+
total += int.from_bytes(buf[i:i + 2], "little")
|
|
46
|
+
return (-total) & 0xFFFF
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def build_frame(slave_addr: int, payload: bytes) -> bytes:
|
|
50
|
+
"""IIC 事务帧: [slave_addr] + payload (对应 ChipSea.cs WriteReg/ReadReg 的 array[0]=addr)。"""
|
|
51
|
+
return bytes([slave_addr & 0xFF]) + payload
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def parse_getdata(data: bytes) -> list[int] | None:
|
|
55
|
+
"""解析 0xEE 数据帧: 末 2 字节负和校验, 返回 LE int16 通道数组 (C# (short) 有符号); 校验失败返回 None。"""
|
|
56
|
+
if len(data) < 4 or len(data) % 2 != 0:
|
|
57
|
+
return None
|
|
58
|
+
expected = int.from_bytes(data[-2:], "little")
|
|
59
|
+
if expected != checksum_two_bytes(data[:-2]):
|
|
60
|
+
return None
|
|
61
|
+
channels = []
|
|
62
|
+
for i in range(len(data[:-2]) // 2):
|
|
63
|
+
raw = int.from_bytes(data[i * 2:i * 2 + 2], "little")
|
|
64
|
+
channels.append(raw - 0x10000 if raw >= 0x8000 else raw)
|
|
65
|
+
return channels
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def parse_version(data: bytes) -> str:
|
|
69
|
+
"""0x10 固件版本: 4 字节 -> v%d.%d.%d.%d (ChipSea.cs ReadFwVersion)。"""
|
|
70
|
+
if len(data) < 4:
|
|
71
|
+
raise NdtBoxError("TARGET_MISMATCH", "固件版本响应不足 4 字节", f"len={len(data)}")
|
|
72
|
+
return "v{}.{}.{}.{}".format(*data[:4])
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def parse_module_connect(data: bytes) -> int:
|
|
76
|
+
"""0x29 模组通断: 低 16 位 LE (ChipSea.cs ReadModuleConnect)。"""
|
|
77
|
+
if len(data) < 2:
|
|
78
|
+
raise NdtBoxError("TARGET_MISMATCH", "模组通断响应不足 2 字节", f"len={len(data)}")
|
|
79
|
+
return int.from_bytes(data[:2], "little")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def strip_nfw(file_bytes: bytes, header_len: int = 256) -> bytes:
|
|
83
|
+
""".nfw 固件: 去 256 字节文件头 (ChipSea.cs LoadFile)。"""
|
|
84
|
+
if len(file_bytes) <= header_len:
|
|
85
|
+
raise NdtBoxError("CONFIG_ERROR", ".nfw 文件不足去头长度", f"len={len(file_bytes)}")
|
|
86
|
+
return file_bytes[header_len:]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def pad_bin_128(file_bytes: bytes) -> bytes:
|
|
90
|
+
""".bin 固件: 128 字节对齐, 尾部 0xFF 填充 (ChipSea.cs LoadFile)。"""
|
|
91
|
+
pad = (128 - len(file_bytes) % 128) % 128
|
|
92
|
+
return file_bytes + b"\xFF" * pad
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# ---------------------------------------------------------------------------
|
|
96
|
+
# 桥调用层
|
|
97
|
+
# ---------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
class NdtBoxIo:
|
|
100
|
+
"""通过 32 位 PowerShell 桥访问 NdtBox 的 IIC 通道 (芯片无关原语)。"""
|
|
101
|
+
|
|
102
|
+
def __init__(self, dll_dir: str, slave_addr: int = 0xA0, speed: int = 1,
|
|
103
|
+
bridge_ps1: str | None = None, timeout_ms: int = 60000):
|
|
104
|
+
self.dll_dir = dll_dir
|
|
105
|
+
self.slave_addr = slave_addr & 0xFF
|
|
106
|
+
self.speed = speed
|
|
107
|
+
self.timeout_ms = timeout_ms
|
|
108
|
+
if bridge_ps1 is None:
|
|
109
|
+
bridge_ps1 = os.path.join(os.path.dirname(__file__), "..", "ndtdbg_bridge.ps1")
|
|
110
|
+
self.bridge_ps1 = os.path.abspath(bridge_ps1)
|
|
111
|
+
|
|
112
|
+
def _bridge(self, action: str, **params) -> dict:
|
|
113
|
+
if not os.path.exists(PS32):
|
|
114
|
+
raise NdtBoxError("CONFIG_ERROR", f"32 位 PowerShell 不存在: {PS32}")
|
|
115
|
+
if not os.path.exists(self.bridge_ps1):
|
|
116
|
+
raise NdtBoxError("CONFIG_ERROR", f"桥脚本不存在: {self.bridge_ps1}")
|
|
117
|
+
args = [PS32, "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass",
|
|
118
|
+
"-File", self.bridge_ps1, "-Action", action,
|
|
119
|
+
"-DllDir", self.dll_dir, "-SlaveAddr", str(self.slave_addr),
|
|
120
|
+
"-Speed", str(self.speed)]
|
|
121
|
+
for k, v in params.items():
|
|
122
|
+
args.append(f"-{k}")
|
|
123
|
+
args.append(str(v))
|
|
124
|
+
try:
|
|
125
|
+
proc = subprocess.run(args, capture_output=True, timeout=self.timeout_ms / 1000.0,
|
|
126
|
+
check=False)
|
|
127
|
+
except subprocess.TimeoutExpired:
|
|
128
|
+
raise NdtBoxError("INSTRUMENT_TIMEOUT", f"桥调用超时 >{self.timeout_ms}ms", action)
|
|
129
|
+
out = (proc.stdout or b"").decode("utf-8", errors="replace").strip()
|
|
130
|
+
if not out:
|
|
131
|
+
tail = (proc.stderr or b"").decode("utf-8", errors="replace").strip()[-400:]
|
|
132
|
+
raise NdtBoxError("UNKNOWN_ERROR", f"桥无输出 (exit={proc.returncode})", tail or None)
|
|
133
|
+
try:
|
|
134
|
+
payload = json.loads(out.splitlines()[-1])
|
|
135
|
+
except json.JSONDecodeError as exc:
|
|
136
|
+
raise NdtBoxError("UNKNOWN_ERROR", "桥输出非 JSON", out[-400:]) from exc
|
|
137
|
+
if not payload.get("ok"):
|
|
138
|
+
raise NdtBoxError(payload.get("error_class", "UNKNOWN_ERROR"),
|
|
139
|
+
payload.get("error", "桥返回失败"),
|
|
140
|
+
payload.get("detail"))
|
|
141
|
+
return payload.get("data", {})
|
|
142
|
+
|
|
143
|
+
# -- 芯片无关原语 (对齐 IIC.cs / I2CProxy.cs) --
|
|
144
|
+
|
|
145
|
+
def ping(self) -> dict:
|
|
146
|
+
return self._bridge("open")
|
|
147
|
+
|
|
148
|
+
def read_reg(self, reg: int, length: int) -> bytes:
|
|
149
|
+
if length < 1 or length > 256:
|
|
150
|
+
raise NdtBoxError("CONFIG_ERROR", "读取长度越界 (1..256)", str(length))
|
|
151
|
+
data = self._bridge("read", RegAddr=reg, Len=length)
|
|
152
|
+
return bytes.fromhex(data["hex"].replace(" ", ""))
|
|
153
|
+
|
|
154
|
+
def write_reg(self, reg: int, data: bytes) -> None:
|
|
155
|
+
if len(data) > 128:
|
|
156
|
+
raise NdtBoxError("CONFIG_ERROR", "写入数据过长 (<=128)", str(len(data)))
|
|
157
|
+
self._bridge("write", RegAddr=reg, Data=data.hex())
|
|
158
|
+
|
|
159
|
+
def close(self) -> dict:
|
|
160
|
+
return self._bridge("close")
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class ChipSeaFxx:
|
|
164
|
+
"""芯海 Fxx (0xA0) 协议: 对齐 STDataCollection.IICModule/ChipSea.cs。"""
|
|
165
|
+
|
|
166
|
+
def __init__(self, io: NdtBoxIo, listener_log=None):
|
|
167
|
+
self._io = io
|
|
168
|
+
self._log = listener_log
|
|
169
|
+
|
|
170
|
+
def _logline(self, msg: str) -> None:
|
|
171
|
+
if self._log:
|
|
172
|
+
self._log.append(msg)
|
|
173
|
+
|
|
174
|
+
def _read_reg_retry(self, reg: int, length: int, min_ms_retry: int = 100) -> bytes:
|
|
175
|
+
last = None
|
|
176
|
+
for attempt in range(_REWRITE_TIMES + 1):
|
|
177
|
+
try:
|
|
178
|
+
last = self._io.read_reg(reg, length)
|
|
179
|
+
if any(b != 0 for b in last):
|
|
180
|
+
return last
|
|
181
|
+
except NdtBoxError:
|
|
182
|
+
raise
|
|
183
|
+
import time
|
|
184
|
+
time.sleep(min_ms_retry / 1000.0)
|
|
185
|
+
raise NdtBoxError("TARGET_MISMATCH",
|
|
186
|
+
f"读寄存器 0x{reg:02X} 超时/全 0 (重试{_REWRITE_TIMES}次)", None)
|
|
187
|
+
|
|
188
|
+
def read_fw_version(self) -> str:
|
|
189
|
+
data = self._read_reg_retry(0x10, 4)
|
|
190
|
+
return parse_version(data)
|
|
191
|
+
|
|
192
|
+
def read_module_connect(self) -> int:
|
|
193
|
+
data = self._io.read_reg(0x29, 4)
|
|
194
|
+
return parse_module_connect(data)
|
|
195
|
+
|
|
196
|
+
def read_afe_gain(self) -> bytes:
|
|
197
|
+
return self._io.read_reg(0x16, 2)
|
|
198
|
+
|
|
199
|
+
def read_adc_uv_coef(self) -> int:
|
|
200
|
+
data = self._io.read_reg(0x1B, 3)
|
|
201
|
+
return int.from_bytes(data, "little")
|
|
202
|
+
|
|
203
|
+
def read_dac_mv_coef(self) -> int:
|
|
204
|
+
data = self._io.read_reg(0x1C, 2)
|
|
205
|
+
return int.from_bytes(data, "little")
|
|
206
|
+
|
|
207
|
+
def read_offset(self, ch_num: int) -> list[int]:
|
|
208
|
+
data = self._io.read_reg(0x17, ch_num * 2)
|
|
209
|
+
return [int.from_bytes(data[i * 2:i * 2 + 2], "little") for i in range(ch_num)]
|
|
210
|
+
|
|
211
|
+
# -- 调试数据流 (对齐 ReadModuleConnectDynamic + GetData) --
|
|
212
|
+
|
|
213
|
+
def poll(self, duration_ms: int, interval_ms: int = 10, ready_timeout_ms: int = 2000) -> dict:
|
|
214
|
+
"""连续轮询一帧或多帧, 返回 {frame_count, checksum_errors, ready_timeouts, frames}。
|
|
215
|
+
|
|
216
|
+
frames: [{"t": epoch_ms, "ch": [int16...]}, ...]——由桥在单个 32 位进程内完成循环,
|
|
217
|
+
避免每帧一次进程开销 (GUI 原 100Hz 采样)。
|
|
218
|
+
"""
|
|
219
|
+
if duration_ms <= 0:
|
|
220
|
+
raise NdtBoxError("CONFIG_ERROR", "poll 时长必须 > 0", str(duration_ms))
|
|
221
|
+
return self._io._bridge("poll", DurationMs=int(duration_ms), IntervalMs=int(interval_ms),
|
|
222
|
+
ReadyTimeoutMs=int(ready_timeout_ms))
|
|
@@ -221,24 +221,33 @@ if ($Backend -eq 'simulator') {
|
|
|
221
221
|
|
|
222
222
|
# ---- sigrok backend ------------------------------------------------------------
|
|
223
223
|
else {
|
|
224
|
-
|
|
224
|
+
# sigrok CSV 无时间列且表头为通道名 -> 经 sigrok_csv_to_fwloop.py 转成
|
|
225
|
+
# fwloop 解码器格式 (time_s,D0..D7), 使 capture->decode 全链闭关
|
|
226
|
+
$rawFile = Join-Path $OutDir 'capture.csv'
|
|
227
|
+
$rawSigrok = Join-Path $OutDir 'sigrok-raw.csv'
|
|
225
228
|
$probeArgs = @(
|
|
226
|
-
"--driver=auto",
|
|
227
229
|
"--config", "samplerate=$SampleRateHz",
|
|
228
230
|
"--channels", "0-7",
|
|
229
|
-
"--output-format", "
|
|
230
|
-
"--output-file", $
|
|
231
|
-
"--time", "$([int]([math]::Ceiling($DurationSec * 1000)))ms"
|
|
232
|
-
"$Protocol"
|
|
231
|
+
"--output-format", "csv",
|
|
232
|
+
"--output-file", $rawSigrok,
|
|
233
|
+
"--time", "$([int]([math]::Ceiling($DurationSec * 1000)))ms"
|
|
233
234
|
)
|
|
234
235
|
$res = Invoke-FwProcess -FilePath $sigrok -Arguments $probeArgs -WorkingDirectory $repoRoot -TimeoutMs 120000
|
|
235
236
|
if ($res.timed_out) {
|
|
236
|
-
Exit-WithError -Class 'LOGIC_CAPTURE_ERROR' -Message 'sigrok-cli capture timed out.' -Detail $
|
|
237
|
+
Exit-WithError -Class 'LOGIC_CAPTURE_ERROR' -Message 'sigrok-cli capture timed out.' -Detail $rawSigrok
|
|
237
238
|
}
|
|
238
|
-
if ($res.exit_code -ne 0 -or -not (Test-Path -LiteralPath $
|
|
239
|
+
if ($res.exit_code -ne 0 -or -not (Test-Path -LiteralPath $rawSigrok)) {
|
|
239
240
|
Exit-WithError -Class 'LOGIC_CAPTURE_ERROR' -Message 'sigrok-cli capture failed.' -Detail ($res.stdout + $res.stderr)
|
|
240
241
|
}
|
|
242
|
+
$convScript = Join-Path $repoRoot 'tools\sigrok_csv_to_fwloop.py'
|
|
243
|
+
$py = Resolve-FwPython
|
|
244
|
+
$convRes = Invoke-FwProcess -FilePath $py -Arguments @($convScript, $rawSigrok, $rawFile, $SampleRateHz) `
|
|
245
|
+
-WorkingDirectory $repoRoot -TimeoutMs 120000
|
|
246
|
+
if ($convRes.exit_code -ne 0 -or -not (Test-Path -LiteralPath $rawFile)) {
|
|
247
|
+
Exit-WithError -Class 'LOGIC_CAPTURE_ERROR' -Message 'sigrok CSV conversion failed.' -Detail ($convRes.stdout + $convRes.stderr)
|
|
248
|
+
}
|
|
241
249
|
$meta.files += (Resolve-Path -LiteralPath $rawFile).Path
|
|
250
|
+
$meta.files += (Resolve-Path -LiteralPath $rawSigrok).Path
|
|
242
251
|
}
|
|
243
252
|
|
|
244
253
|
$metaFile = Join-Path $OutDir 'capture.json'
|