From 31571b54efa67f56f039b3a14c1a6529c3560957 Mon Sep 17 00:00:00 2001 From: agent Date: Wed, 29 Jul 2026 05:19:41 +0000 Subject: [PATCH] =?UTF-8?q?feat(tools):=20flash=5Frun.py=20=E5=85=A8?= =?UTF-8?q?=E9=87=8F=E7=83=A7=E5=BD=95=E6=89=A7=E8=A1=8C=E5=99=A8=EF=BC=88?= =?UTF-8?q?=E5=85=A8=E7=A8=8B=20JSONL=20=E7=95=99=E8=AF=81=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/flash_run.py | 96 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 tools/flash_run.py diff --git a/tools/flash_run.py b/tools/flash_run.py new file mode 100644 index 0000000..f462987 --- /dev/null +++ b/tools/flash_run.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +"""SUOTA 全量烧录执行器(用户授权 2026-07-29,范围见 DECISIONS.md)。 +全程事件流写 docs/suota-flash-log.jsonl;结束打印统计与事后 DIS 验证。 +用法:.venv/bin/python tools/flash_run.py [image] [gpio_hex] +""" +import asyncio +import datetime +import json +import sys +import time +from pathlib import Path + +sys.path.insert(0, "src") +from ppclock.bridge import BridgeTransport # noqa: E402 + +HOST, PORT = "192.168.61.35", 8971 +DEVICE = "18:BC:5A:5D:BF:28" +IMAGE = sys.argv[1] if len(sys.argv) > 1 else "analysis/firmware/fw_tst.img" +GPIO = sys.argv[2] if len(sys.argv) > 2 else "05060040" # BE 序 C(gpio_probe 实证) +LOG = Path("docs/suota-flash-log.jsonl") + + +def log(entry): + entry["ts"] = datetime.datetime.now().isoformat(timespec="milliseconds") + with LOG.open("a", encoding="utf-8") as f: + f.write(json.dumps(entry, ensure_ascii=False) + "\n") + + +async def ota_with_gpio(host, port, image, gpio_hex, prog): + """带 gpio_map 参数的 run_ota(BridgeTransport.run_ota 固定默认,临时直连 RPC)。""" + reader, writer = await asyncio.open_connection(host, port) + events = [] + + async def read_loop(): + async for line in reader: + msg = json.loads(line) + events.append(msg) + prog(msg) + + task = asyncio.create_task(read_loop()) + try: + writer.write(json.dumps({"id": 1, "op": "ota", "image": image.hex(), + "gpio_map": gpio_hex}).encode() + b"\n") + await writer.drain() + while True: + done, _ = await asyncio.wait({task}, timeout=1800) + if done: + break + # 检查是否已收到最终响应 + finals = [e for e in events if "id" in e] + if finals: + break + if task.done(): + break + finals = [e for e in events if "id" in e] + return finals[-1] if finals else {"ok": False, "error": "no final response"} + finally: + task.cancel() + writer.close() + + +async def main(): + LOG.unlink(missing_ok=True) + log({"kind": "run_start", "image": IMAGE, "gpio": GPIO, + "authorization": "用户授权 2026-07-29(DECISIONS.md)"}) + image = Path(IMAGE).read_bytes() + t0 = time.time() + stats = {} + + def prog(msg): + el = time.time() - t0 + entry = {"el": round(el, 2), "msg": msg} + log({"kind": "event", **entry}) + e = msg.get("event") + if e == "ota_stage": + for c in msg.get("codes", []): + stats[c] = stats.get(c, 0) + 1 + print(f"[{el:6.1f}s] 阶段: {msg['stage']}" + + (f" codes={msg['codes']}" if msg.get("codes") else ""), flush=True) + elif e == "ota_progress": + stats[2] = stats.get(2, 0) + 1 + if msg["block"] % 20 == 0 or msg["block"] == msg["total"]: + print(f"[{el:6.1f}s] 块确认(2) {msg['block']}/{msg['total']}", flush=True) + + async with BridgeTransport(address=DEVICE, timeout=45, subscribe=False, + host=HOST, port=PORT) as t: + print("已连接(净连接),开始全量烧录...", flush=True) + log({"kind": "connected", "device": DEVICE}) + # BridgeTransport.run_ota 不支持 gpio_map 参数,直接发 RPC + result = await ota_with_gpio(HOST, PORT, image, GPIO, prog) + print(f"[{time.time() - t0:6.1f}s] 最终结果: {result}", flush=True) + log({"kind": "run_end", "result": result, "stats": stats}) + print(f"=== 状态统计: {stats} ===", flush=True) + + +asyncio.run(main())