移除 OpenCode 配置与命令,技能与串口单一配置源迁移至 .dsh/skills 与 .serialtest,同步更新全部文档引用

This commit is contained in:
2026-10-10 14:05:36 +08:00
parent e89fe54a97
commit aac5f99fe9
44 changed files with 3868 additions and 180 deletions
@@ -0,0 +1,184 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""stlink_flash.py - ST-LINK 命令行烧录/复位/回读校验(纯标准库,可移植 skill 版)。
用法(**项目根目录**下运行,任意 python3):
python <skill>/scripts/stlink_flash.py list
python <skill>/scripts/stlink_flash.py flash [镜像.hex|.bin] # 烧录 + -Rst -Run
python <skill>/scripts/stlink_flash.py cmp [镜像.hex|.bin] [--base 0x08000000]
python <skill>/scripts/stlink_flash.py reset # 只复位运行
python <skill>/scripts/stlink_flash.py halt | run
镜像缺省取项目档案 "hex"(解析顺序同下)。
ST-LINK_CLI 解析(命令行 > 档案 > 环境 > PATH):
--cli > 档案 "stlink_cli" > $DSH_STLINK_CLI > $DTU_STLINK_CLI > PATH
(环境变量**显式设了但路径无效 = 立即报错,不静默回退**)
铁律(实测教训,脚本已内置):
- 烧录必带 -Rst -Run:裸 -P 在目标核死循环时报 Complete 但内容不落地
- flash 成功判据 = rc==0 且输出含 "Programming Complete"
- cmp 成功判据 = 某行以 "No difference found" 开头(原样透传 CLI 输出,
上层 run_boot_test 的 readback_ok 默认值直接可用)
- cmp 连接时会**复位目标**(板子重打开机日志)→ 自动闭环里必须排在日志采集之后
退出码: 0=OK 1=探针/目标错误 2=CLI 未找到/无效 3=用法错误
"""
import argparse
import json
import os
import shutil
import subprocess
import sys
import time
from pathlib import Path
def find_config_file():
env = os.environ.get("DSH_SERIAL_CONFIG")
if env:
return Path(env)
cwd = Path.cwd()
candidates = [cwd / ".serialtest" / "config.json",
cwd / ".opencode" / "serial_config.json"]
for c in candidates:
if c.is_file():
return c
return candidates[0]
def load_config():
f = find_config_file()
if not f.is_file():
return {}, ""
try:
raw = json.loads(f.read_text(encoding="utf-8-sig"))
except (OSError, ValueError) as e:
return {}, "cannot parse {}: {}".format(f, e)
if not isinstance(raw, dict):
return {}, "config root is not an object: {}".format(f)
return raw, ""
def find_cli(cli_arg, cfg):
for label, val in (("--cli", cli_arg),
("config 'stlink_cli'", cfg.get("stlink_cli")),
("$DSH_STLINK_CLI", os.environ.get("DSH_STLINK_CLI")),
("$DTU_STLINK_CLI", os.environ.get("DTU_STLINK_CLI"))):
if val:
if Path(val).is_file():
return Path(val), label
raise SystemExit("ENV_ERROR: {} is set but not a file: {} "
"(fix the value; no silent fallback)".format(label, val))
w = shutil.which("ST-LINK_CLI.exe") or shutil.which("ST-LINK_CLI")
if w:
return Path(w), "PATH"
raise SystemExit("ENV_ERROR: ST-LINK_CLI.exe not found "
"(--cli / config 'stlink_cli' / $DSH_STLINK_CLI / $DTU_STLINK_CLI / PATH)")
def run_cli(cli, args, timeout):
t0 = time.monotonic()
try:
proc = subprocess.run([str(cli)] + args, capture_output=True, timeout=timeout)
rc = proc.returncode
raw = (proc.stdout or b"") + (proc.stderr or b"")
except subprocess.TimeoutExpired as e:
rc = -1
raw = (e.stdout or b"") + (e.stderr or b"")
text = raw.decode("utf-8", errors="replace")
for line in text.strip().splitlines():
print(line)
print("RC: {} elapsed_ms={}".format(rc, int((time.monotonic() - t0) * 1000)))
return rc, text
def main(argv=None):
for stream in (sys.stdout, sys.stderr):
try:
stream.reconfigure(encoding="utf-8", errors="replace")
except Exception:
pass
ap = argparse.ArgumentParser(description="ST-LINK CLI flash/reset/readback (pure stdlib)")
ap.add_argument("command", choices=["list", "flash", "cmp", "reset", "halt", "run"])
ap.add_argument("image", nargs="?", default=None,
help="image file (.hex/.bin; default: config 'hex')")
ap.add_argument("--cli", default=None, help="ST-LINK_CLI.exe path")
ap.add_argument("--base", default="0x08000000", help="cmp base address")
ap.add_argument("--timeout", type=int, default=None,
help="subprocess timeout (default: flash 300 / cmp 180 / other 60)")
args = ap.parse_args(argv)
cfg, err = load_config()
if err:
print("ENV_ERROR: {}".format(err))
return 3
cli, src = find_cli(args.cli, cfg)
print("CLI: {} ({})".format(cli, src))
if args.command in ("list", "reset", "halt", "run"):
if args.image:
print("NOTE: image argument ignored for '{}'".format(args.command))
timeout = args.timeout or 60
if args.command == "list":
rc, _ = run_cli(cli, ["-List"], timeout)
print("STLINK_RC={}".format(rc))
return 0 if rc == 0 else 1
if args.command == "reset":
rc, _ = run_cli(cli, ["-c", "SWD", "-Rst", "-Run", "-Q"], timeout)
print("STLINK_RC={}".format(rc))
return 0 if rc == 0 else 1
if args.command == "halt":
rc, _ = run_cli(cli, ["-c", "SWD", "-Rst", "-Halt"], timeout)
print("STLINK_RC={}".format(rc))
return 0 if rc == 0 else 1
rc, _ = run_cli(cli, ["-c", "SWD", "-Run"], timeout)
print("STLINK_RC={}".format(rc))
return 0 if rc == 0 else 1
# flash / cmp 需要镜像
img = args.image or cfg.get("hex") or ""
if not img:
print("STLINK_ERROR: no image given and no config 'hex'")
return 3
img = Path(img)
if not img.is_file():
print("STLINK_ERROR: image not found: {}".format(img))
return 3
if args.command == "flash":
print("FLASH: {} then reset and run".format(img))
rc, text = run_cli(cli, ["-c", "SWD", "-P", str(img), "-Rst", "-Run", "-Q"],
args.timeout or 300)
if rc == 0 and "programming complete" in text.lower():
print("FLASH: OK")
print("STLINK_RC=0")
return 0
if rc == 0:
print("STLINK_ERROR: rc=0 but no 'Programming Complete' in output "
"(treat as failure - possible forwarded/hung run)")
print("STLINK_RC=1")
return 1
print("STLINK_RC=1")
return 1
# cmp:原样透传输出("No difference found" 独占行首 = 成功判据)
print("CMP: {} vs device flash @ {} (NOTE: connecting resets the target)".format(
img, args.base))
rc, text = run_cli(cli, ["-c", "SWD", "-CmpFile", str(img), args.base],
args.timeout or 180)
ok = any(ln.strip().lower().startswith("no difference found")
for ln in text.splitlines())
if ok:
print("CMP: OK (No difference found)")
print("STLINK_RC=0")
return 0
print("STLINK_ERROR: readback mismatch or compare failed")
print("STLINK_RC=1")
return 1
if __name__ == "__main__":
sys.exit(main())