test: stabilize HIL suites — inter-suite reset, async tm1617 inject, ACK-first
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
eb1e5cbe84
commit
6da7d4d627
|
|
@ -537,7 +537,8 @@ uint8_t app_log_try_command(const char *cmd)
|
|||
SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, line);
|
||||
return 1U;
|
||||
}
|
||||
/* 测试注入:tm1617 key N(0~3=MAIN_D/C/B/A 段落或导航键,4=释放) */
|
||||
/* 测试注入:tm1617 key N(0~3=MAIN_D/C/B/A 段落或导航键,4=释放)
|
||||
* ACK 先于 Handle:0~3 会投递触摸消息,可能挤占 RTT 下行导致 ACK 丢失 */
|
||||
if (strncmp(cmd, "tm1617 key ", 11) == 0 && cmd[11] != '\0') {
|
||||
unsigned key = (unsigned)strtoul(cmd + 11, NULL, 10);
|
||||
char line[48];
|
||||
|
|
@ -545,9 +546,9 @@ uint8_t app_log_try_command(const char *cmd)
|
|||
SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, "TM1617_KEY_BAD\n");
|
||||
return 1U;
|
||||
}
|
||||
app_tm1617_inject_key((uint8_t)key);
|
||||
snprintf(line, sizeof(line), "TM1617_KEY_OK key=%u\n", key);
|
||||
SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, line);
|
||||
app_tm1617_inject_key((uint8_t)key);
|
||||
return 1U;
|
||||
}
|
||||
/* 测试注入:adc key N on|off(0~3;1=独立尾奏键,与万能第4键同路径) */
|
||||
|
|
@ -559,9 +560,9 @@ uint8_t app_log_try_command(const char *cmd)
|
|||
SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, "ADC_KEY_BAD\n");
|
||||
return 1U;
|
||||
}
|
||||
ADC_IN1_KEY_Handle((uint8_t)idx, on ? true : false);
|
||||
snprintf(line, sizeof(line), "ADC_KEY_OK idx=%u on=%d\n", idx, on);
|
||||
SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, line);
|
||||
ADC_IN1_KEY_Handle((uint8_t)idx, on ? true : false);
|
||||
return 1U;
|
||||
}
|
||||
/* 测试注入:chord xpose N(0~11,C=0) */
|
||||
|
|
|
|||
|
|
@ -91,5 +91,6 @@ void app_tm1617_inject_key(uint8_t key)
|
|||
key = KEY_NULL;
|
||||
key_last = key;
|
||||
LOG_I("KEY", "tm1617 inject key=%u", (unsigned)key);
|
||||
TM1617_Handle(key);
|
||||
/* ???????? RTT poll ?????? UI/?????????? */
|
||||
MainTask_Sendmsg(MSG_ID_KEY_1617, key, 0, 0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
@echo off
|
||||
REM Launch K1 HIL harness from repo root or any cwd.
|
||||
REM Usage: run_k1_harness.bat selftest
|
||||
REM run_k1_harness.bat run --suite all --profile nightly
|
||||
setlocal
|
||||
cd /d "%~dp0tools"
|
||||
python -m k1_harness %*
|
||||
exit /b %ERRORLEVEL%
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Repo-root launcher: python run_k1_harness.py ... (same as cd tools && python -m k1_harness ...)"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||
TOOLS = os.path.join(ROOT, "tools")
|
||||
if TOOLS not in sys.path:
|
||||
sys.path.insert(0, TOOLS)
|
||||
os.chdir(TOOLS)
|
||||
|
||||
from k1_harness.cli import main # noqa: E402
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -18,29 +18,41 @@
|
|||
|
||||
## 命令
|
||||
|
||||
在仓库根目录(推荐):
|
||||
|
||||
```bash
|
||||
python run_k1_harness.py selftest
|
||||
python run_k1_harness.py run --suite all --profile nightly
|
||||
# 或
|
||||
run_k1_harness.bat run --suite all --profile nightly
|
||||
```
|
||||
|
||||
也可进入 `tools` 后:
|
||||
|
||||
```bash
|
||||
cd tools
|
||||
|
||||
# 无硬件自检
|
||||
python -m k1_harness selftest
|
||||
python -m k1_harness run --suite all --profile nightly
|
||||
```
|
||||
|
||||
> 注意:不要在仓库根目录直接执行 `python -m k1_harness`(会报 `No module named k1_harness`)。
|
||||
> 包位于 `tools/k1_harness/`。
|
||||
|
||||
```bash
|
||||
# App 协议(BLE 通道,默认)
|
||||
python -m k1_harness run --suite app --transport ble
|
||||
python run_k1_harness.py run --suite app --transport ble
|
||||
|
||||
# App 协议(串口通道)
|
||||
python -m k1_harness run --suite app --transport com --port COM5
|
||||
python run_k1_harness.py run --suite app --transport com --port COM5
|
||||
|
||||
# BLE 链路 smoke
|
||||
python -m k1_harness run --suite ble
|
||||
python run_k1_harness.py run --suite ble
|
||||
|
||||
# 按键 / 资源包 / UI(J-Link RTT)
|
||||
python -m k1_harness run --suite keys,keys1617,packs,ui
|
||||
|
||||
# 全量(nightly)
|
||||
python -m k1_harness run --suite all --profile nightly
|
||||
python run_k1_harness.py run --suite keys,keys1617,packs,ui
|
||||
|
||||
# 含关机用例(破坏性,跑完设备软关机)
|
||||
python -m k1_harness run --suite all --allow-poweroff
|
||||
python run_k1_harness.py run --suite all --allow-poweroff
|
||||
```
|
||||
|
||||
报告默认写到 `Doc/reports/k1_hil_<时间戳>.md`,可用 `--report` 指定。
|
||||
|
|
|
|||
|
|
@ -31,6 +31,23 @@ class Ctx:
|
|||
dump_png: str = ""
|
||||
|
||||
|
||||
def _recover_device(device: str) -> None:
|
||||
"""RTT 套件之间硬件复位,避免前序注入饿死后续命令。"""
|
||||
import subprocess
|
||||
from .paths import TOOLS_DIR
|
||||
|
||||
script = os.path.join(TOOLS_DIR, "rtt_reset.py")
|
||||
print(f"\n----- 套件间复位 ({device}) -----", flush=True)
|
||||
try:
|
||||
subprocess.run(
|
||||
[sys.executable, script, "--device", device, "--wait-boot", "5"],
|
||||
timeout=60,
|
||||
check=False,
|
||||
)
|
||||
except Exception as exc:
|
||||
print(f"复位失败(继续): {exc}", flush=True)
|
||||
|
||||
|
||||
def _run_selftest() -> int:
|
||||
report = HarnessReport(title="K1 harness selftest(无硬件)")
|
||||
|
||||
|
|
@ -95,8 +112,12 @@ def _run_suites(args) -> int:
|
|||
report.notes.append(f"套件: {', '.join(names)}")
|
||||
report.notes.append(f"transport={ctx.transport} device={ctx.device} profile={args.profile}")
|
||||
|
||||
prev_rtt = False
|
||||
for name in names:
|
||||
mod, needs = SUITES[name]
|
||||
is_rtt = "rtt" in needs
|
||||
if is_rtt and prev_rtt:
|
||||
_recover_device(ctx.device)
|
||||
if "transport" in needs and ctx.transport == "com" and not ctx.port:
|
||||
report.add(name, "前置条件", "SKIP", "transport=com 需 --port;已跳过该套件")
|
||||
continue
|
||||
|
|
@ -107,6 +128,7 @@ def _run_suites(args) -> int:
|
|||
report.add(name, "套件执行", "FAIL", f"SystemExit: {exc}")
|
||||
except Exception as exc: # 单套件异常不拖垮整体
|
||||
report.add(name, "套件执行", "FAIL", f"{type(exc).__name__}: {exc}")
|
||||
prev_rtt = is_rtt
|
||||
|
||||
report.print_summary()
|
||||
ts = report.started.strftime("%Y%m%d_%H%M%S")
|
||||
|
|
|
|||
|
|
@ -23,4 +23,4 @@ SUITES = {
|
|||
"ui": (ui_nav, {"rtt"}),
|
||||
}
|
||||
|
||||
ALL_ORDER = ("app", "ble", "keys", "keys1617", "packs", "ui")
|
||||
ALL_ORDER = ("app", "ble", "keys", "packs", "ui", "keys1617")
|
||||
|
|
|
|||
|
|
@ -1,34 +1,99 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""keys1617 套件:TM1617 段落/导航键注入(RTT `tm1617 key N`).
|
||||
|
||||
键值:0=KEY_MAIN_D(SEG5) 1=KEY_MAIN_C(SEG6) 2=KEY_MAIN_B(SEG7) 3=KEY_MAIN_A(SEG8) 4=释放(KEY_NULL)
|
||||
需要固件含 app_tm1617_inject_key 钩子;无 ACK 时记 SKIP。
|
||||
键值:0=KEY_MAIN_D 1=KEY_MAIN_C 2=KEY_MAIN_B 3=KEY_MAIN_A 4=释放(KEY_NULL)
|
||||
每个键单独开 RTT 会话,失败时硬件复位后重试一次,避免触摸洪泛饿死后续命令。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
from ..paths import TOOLS_DIR
|
||||
from ..transports.rtt_session import open_session
|
||||
|
||||
SUITE = "keys1617"
|
||||
KEY_NAMES = {0: "MAIN_D", 1: "MAIN_C", 2: "MAIN_B", 3: "MAIN_A"}
|
||||
|
||||
|
||||
def run(report, ctx) -> None:
|
||||
with open_session(ctx.device) as sess:
|
||||
if not sess.cmd_ack("tm1617 key 4", "TM1617_KEY_OK", timeout_s=2.0, retries=3):
|
||||
report.add(SUITE, "TM1617 注入命令探测", "SKIP",
|
||||
"无 TM1617_KEY_OK:固件缺少 'tm1617 key' 命令(需 app_tm1617_inject_key 钩子)")
|
||||
def _hw_reset(device: str) -> None:
|
||||
script = os.path.join(TOOLS_DIR, "rtt_reset.py")
|
||||
subprocess.run(
|
||||
[sys.executable, script, "--device", device, "--wait-boot", "5"],
|
||||
timeout=60,
|
||||
check=False,
|
||||
)
|
||||
|
||||
|
||||
def _inject_once(sess, key: int) -> tuple[bool, str]:
|
||||
tag = f"key={key}"
|
||||
before = len(sess.lines)
|
||||
ok = sess.cmd_ack(f"tm1617 key {key}", "TM1617_KEY_OK", timeout_s=2.5, retries=3)
|
||||
if ok:
|
||||
return True, "TM1617_KEY_OK"
|
||||
recent = sess.lines[before:]
|
||||
if any("tm1617 inject" in l and tag in l for l in recent):
|
||||
return True, "inject log"
|
||||
if any("TM1617_KEY_OK" in l and tag in l for l in recent):
|
||||
return True, "TM1617_KEY_OK (late)"
|
||||
return False, "无 ACK/inject 日志"
|
||||
|
||||
|
||||
def _inject_with_retry(report, device: str, key: int, name: str) -> None:
|
||||
try:
|
||||
with open_session(device) as sess:
|
||||
ok, detail = _inject_once(sess, key)
|
||||
if ok:
|
||||
# 尽快释放,减轻触摸洪泛
|
||||
if key != 4:
|
||||
sess.cmd_ack("tm1617 key 4", "TM1617_KEY_OK", timeout_s=1.5, retries=2)
|
||||
time.sleep(0.3)
|
||||
report.add(SUITE, name, "PASS", detail)
|
||||
return
|
||||
except SystemExit as exc:
|
||||
detail = f"SystemExit: {exc}"
|
||||
except Exception as exc:
|
||||
detail = f"{type(exc).__name__}: {exc}"
|
||||
|
||||
print(f"!! {name} 首次失败,复位后重试…", flush=True)
|
||||
_hw_reset(device)
|
||||
try:
|
||||
with open_session(device) as sess:
|
||||
ok, detail = _inject_once(sess, key)
|
||||
if ok:
|
||||
if key != 4:
|
||||
sess.cmd_ack("tm1617 key 4", "TM1617_KEY_OK", timeout_s=1.5, retries=2)
|
||||
report.add(SUITE, name, "PASS", detail + " (retry)")
|
||||
return
|
||||
report.add(SUITE, name, "FAIL", detail)
|
||||
except SystemExit as exc:
|
||||
report.add(SUITE, name, "FAIL", f"retry SystemExit: {exc}")
|
||||
except Exception as exc:
|
||||
report.add(SUITE, name, "FAIL", f"retry {type(exc).__name__}: {exc}")
|
||||
|
||||
|
||||
def run(report, ctx) -> None:
|
||||
# 探测命令是否存在
|
||||
try:
|
||||
with open_session(ctx.device) as sess:
|
||||
ok, detail = _inject_once(sess, 4)
|
||||
except SystemExit as exc:
|
||||
report.add(SUITE, "TM1617 注入命令探测", "FAIL", f"SystemExit: {exc}")
|
||||
return
|
||||
except Exception as exc:
|
||||
report.add(SUITE, "TM1617 注入命令探测", "FAIL", f"{type(exc).__name__}: {exc}")
|
||||
return
|
||||
report.add(SUITE, "TM1617 注入命令探测", "PASS", "TM1617_KEY_OK")
|
||||
|
||||
for key, name in KEY_NAMES.items():
|
||||
ok = sess.cmd_ack(f"tm1617 key {key}", "TM1617_KEY_OK", timeout_s=2.0, retries=3)
|
||||
if not ok:
|
||||
report.add(SUITE, f"tm1617 {name}", "FAIL", "无 ACK")
|
||||
continue
|
||||
seen = any(f"tm1617 key={key}" in line for line in sess.lines[-30:])
|
||||
report.add(SUITE, f"tm1617 {name}", "PASS",
|
||||
"" if seen else "ACK 有;[KEY] 日志未捕获")
|
||||
sess.pump(0.1)
|
||||
report.add(SUITE, "TM1617 注入命令探测", "SKIP",
|
||||
"无 TM1617_KEY_OK:固件缺少 'tm1617 key'(需含钩子固件)")
|
||||
return
|
||||
report.add(SUITE, "TM1617 注入命令探测", "PASS", detail)
|
||||
|
||||
ok = sess.cmd_ack("tm1617 key 4", "TM1617_KEY_OK", timeout_s=2.0, retries=3)
|
||||
report.add(SUITE, "tm1617 释放(KEY_NULL)", "PASS" if ok else "FAIL", "" if ok else "无 ACK")
|
||||
for key, label in KEY_NAMES.items():
|
||||
_inject_with_retry(report, ctx.device, key, f"tm1617 {label}")
|
||||
_hw_reset(ctx.device) # 每键后复位,保证下一键 RTT 干净
|
||||
|
||||
_inject_with_retry(report, ctx.device, 4, "tm1617 释放(KEY_NULL)")
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""keys 套件:TM1629 和弦垫全键注入(RTT `chord key N`,0 释放 / 1~21 和弦 / 22 拍速 / 23 停止).
|
||||
"""keys 套件:TM1629 和弦垫全键注入(RTT `chord key N`).
|
||||
|
||||
判定:固件回 CHORD_KEY_OK 且 [KEY] 日志出现对应键值。
|
||||
若完全无 CHORD_KEY_OK → 记 SKIP(固件过旧,需重烧含 app_log 注入命令的固件)。
|
||||
判定:CHORD_KEY_OK,或 [KEY] inject 日志(RTT 丢 ACK 时兜底)。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
|
|
@ -11,32 +10,35 @@ from ..transports.rtt_session import open_session
|
|||
SUITE = "keys"
|
||||
|
||||
|
||||
def _chord_ok(sess, key: int) -> bool:
|
||||
if sess.cmd_ack(f"chord key {key}", "CHORD_KEY_OK", timeout_s=2.5, retries=5):
|
||||
return True
|
||||
tag = f"inject key={key}"
|
||||
return any(tag in line for line in sess.lines[-40:])
|
||||
|
||||
|
||||
def run(report, ctx) -> None:
|
||||
with open_session(ctx.device) as sess:
|
||||
sess.cmd("log clear", 0.2)
|
||||
|
||||
# 探测固件是否支持注入命令
|
||||
if not sess.cmd_ack("chord key 0", "CHORD_KEY_OK", timeout_s=2.0, retries=3):
|
||||
if not _chord_ok(sess, 0):
|
||||
report.add(SUITE, "TM1629 注入命令探测", "SKIP",
|
||||
"无 CHORD_KEY_OK:固件缺少 'chord key' RTT 命令,请重烧新固件")
|
||||
return
|
||||
report.add(SUITE, "TM1629 注入命令探测", "PASS", "CHORD_KEY_OK")
|
||||
|
||||
for key in range(1, 24):
|
||||
ok = sess.cmd_ack(f"chord key {key}", "CHORD_KEY_OK", timeout_s=2.0, retries=3)
|
||||
if not ok:
|
||||
report.add(SUITE, f"chord key {key}", "FAIL", "无 ACK")
|
||||
continue
|
||||
seen = any(f"key={key}" in line and "[KEY" in line for line in sess.lines[-30:])
|
||||
report.add(SUITE, f"chord key {key}", "PASS" if seen else "PASS",
|
||||
"" if seen else "ACK 有;[KEY] 日志未捕获(可能级别/时序)")
|
||||
ok = _chord_ok(sess, key)
|
||||
report.add(SUITE, f"chord key {key}", "PASS" if ok else "FAIL",
|
||||
"" if ok else "无 ACK")
|
||||
sess.pump(0.05)
|
||||
|
||||
ok = sess.cmd_ack("chord key 0", "CHORD_KEY_OK", timeout_s=2.0, retries=3)
|
||||
ok = _chord_ok(sess, 0)
|
||||
report.add(SUITE, "chord key 0 释放", "PASS" if ok else "FAIL", "" if ok else "无 ACK")
|
||||
|
||||
# 移调注入 0~11 抽查边界
|
||||
for xp in (0, 6, 11):
|
||||
ok = sess.cmd_ack(f"chord xpose {xp}", "CHORD_XPOSE_OK", timeout_s=2.0, retries=3)
|
||||
ok = sess.cmd_ack(f"chord xpose {xp}", "CHORD_XPOSE_OK", timeout_s=2.5, retries=5)
|
||||
if not ok:
|
||||
ok = any(f"xpose set {xp}" in l or f"xp={xp}" in l for l in sess.lines[-30:])
|
||||
report.add(SUITE, f"chord xpose {xp}", "PASS" if ok else "FAIL", "" if ok else "无 ACK")
|
||||
sess.cmd_ack("chord xpose 0", "CHORD_XPOSE_OK", timeout_s=2.0, retries=3) # 恢复
|
||||
sess.cmd_ack("chord xpose 0", "CHORD_XPOSE_OK", timeout_s=2.0, retries=3)
|
||||
|
|
|
|||
|
|
@ -17,45 +17,41 @@ SUITE = "packs"
|
|||
BIN3_PRESETS = (0, 1, 2) # 1.1645 / 2.1564 / 3.1364
|
||||
|
||||
|
||||
def _load_and_check(report, sess, cmd: str, tag: str, name: str) -> bool:
|
||||
"""发加载命令,校验 <tag> 行 ret=0 且 OK。返回是否可用(False=命令缺失→SKIP)。"""
|
||||
sess.pump(0.15)
|
||||
sess.cmd(cmd, settle=0.1)
|
||||
deadline_lines = sess.pump(2.5)
|
||||
hit = [l for l in deadline_lines if tag in l]
|
||||
if not hit:
|
||||
def _load_and_check(report, sess, cmd: str, tag: str, name: str, *, missing: str = "FAIL") -> bool:
|
||||
"""发加载命令,校验 <tag> 行。True=有响应;False=无响应(按 missing 记结果)."""
|
||||
sess.pump(0.2)
|
||||
got = sess.cmd_ack(cmd, tag, timeout_s=4.0, retries=4)
|
||||
if not got:
|
||||
report.add(SUITE, name, missing, f"无 {tag} 响应")
|
||||
return False
|
||||
line = hit[-1]
|
||||
hit = [l for l in sess.lines[-20:] if tag in l]
|
||||
line = hit[-1] if hit else tag
|
||||
ok = "ret=0" in line and "OK" in line and "MISMATCH" not in line
|
||||
if "ret=" not in line and tag in line:
|
||||
ok = "MISMATCH" not in line and "ERR" not in line
|
||||
report.add(SUITE, name, "PASS" if ok else "FAIL", line.strip())
|
||||
return True
|
||||
|
||||
|
||||
def run(report, ctx) -> None:
|
||||
with open_session(ctx.device) as sess:
|
||||
sess.cmd("log clear", 0.2)
|
||||
if not sess.cmd_ack("log status", "LOG_STATUS", timeout_s=3.0, retries=4):
|
||||
report.add(SUITE, "RTT 下行探测", "FAIL", "log status 无响应(J-Link/固件忙?)")
|
||||
return
|
||||
sess.cmd("log clear", 0.3)
|
||||
|
||||
# ---- 加载校验 ----
|
||||
_load_and_check(report, sess, "tone bin1 0", "TONE_BIN1", "1.bin 节奏加载 idx0")
|
||||
|
||||
if not _load_and_check(report, sess, "tone bin2 0", "TONE_BIN2", "2.bin 本地曲目加载 idx0"):
|
||||
# 老固件没有 tone bin2:退回 tone local 做基本校验
|
||||
sess.cmd("tone local", settle=0.1)
|
||||
lines = sess.pump(2.0)
|
||||
hit = [l for l in lines if "TONE_LOCAL" in l]
|
||||
if hit:
|
||||
line = hit[-1]
|
||||
ok = "ret=0" in line and "OK" in line
|
||||
report.add(SUITE, "2.bin 本地曲目加载(tone local)", "PASS" if ok else "FAIL", line.strip())
|
||||
else:
|
||||
report.add(SUITE, "2.bin 本地曲目加载", "SKIP", "无 TONE_BIN2/TONE_LOCAL 响应")
|
||||
if not _load_and_check(report, sess, "tone bin2 0", "TONE_BIN2",
|
||||
"2.bin 本地曲目加载 idx0", missing="SKIP"):
|
||||
_load_and_check(report, sess, "tone local", "TONE_LOCAL",
|
||||
"2.bin 本地曲目加载(tone local)", missing="SKIP")
|
||||
|
||||
bin3_ok = True
|
||||
for idx in BIN3_PRESETS:
|
||||
if not _load_and_check(report, sess, f"tone bin3 {idx}", "TONE_BIN3",
|
||||
f"3.bin 万能加载 idx{idx}"):
|
||||
bin3_ok = False
|
||||
report.add(SUITE, f"3.bin 万能加载 idx{idx}", "FAIL", "无 TONE_BIN3 响应")
|
||||
|
||||
if not bin3_ok:
|
||||
report.add(SUITE, "拨片起奏/尾奏", "SKIP", "3.bin 加载失败,级联跳过")
|
||||
|
|
@ -63,26 +59,33 @@ def run(report, ctx) -> None:
|
|||
|
||||
# ---- 拨片起奏(万能 idx0)----
|
||||
sess.cmd("log clear", 0.2)
|
||||
sess.cmd("tone bin3 0", settle=0.8)
|
||||
sess.cmd_ack("tone bin3 0", "TONE_BIN3", timeout_s=4.0, retries=3)
|
||||
base = len(sess.lines)
|
||||
sess.cmd("tone start", settle=0.2)
|
||||
sess.cmd_ack("tone start", "TONE_START_DONE", timeout_s=3.0, retries=3)
|
||||
sess.pump(3.0)
|
||||
pick_lines = sess.lines[base:]
|
||||
pitch_on = [l for l in pick_lines if "[PITCH]" in l and " on " in l]
|
||||
report.add(SUITE, "3.bin 拨片起奏有声", "PASS" if pitch_on else "FAIL",
|
||||
f"{len(pitch_on)} 条 PITCH on" if pitch_on else "起奏后无 PITCH on")
|
||||
|
||||
# ---- 尾奏回归(adc key 1 = 独立尾奏键,与万能第4键同路径)----
|
||||
if not sess.cmd_ack("adc key 1 on", "ADC_KEY_OK", timeout_s=2.0, retries=3):
|
||||
# ---- 尾奏回归 ----
|
||||
if not sess.cmd_ack("adc key 1 on", "ADC_KEY_OK", timeout_s=2.5, retries=4):
|
||||
# 兜底:万能第 4 键(固件已映射到 AutoBand_StartOutro)
|
||||
if sess.cmd_ack("chord key 4", "CHORD_KEY_OK", timeout_s=2.0, retries=3):
|
||||
report.add(SUITE, "3.bin 尾奏触发", "PASS", "chord key 4(adc key 不可用)")
|
||||
else:
|
||||
report.add(SUITE, "3.bin 尾奏(adc key 1)", "SKIP",
|
||||
"固件缺少 'adc key' 命令;请用含 ADC 注入钩子的固件")
|
||||
"固件缺少 'adc key';chord key 4 也无 ACK")
|
||||
sess.cmd_ack("chord key 23", "CHORD_KEY_OK", timeout_s=2.0, retries=3)
|
||||
return
|
||||
else:
|
||||
report.add(SUITE, "3.bin 尾奏触发", "PASS", "ADC_KEY_OK")
|
||||
|
||||
out_base = len(sess.lines)
|
||||
sess.pump(4.0)
|
||||
outro_lines = sess.lines[out_base:]
|
||||
outro_pitch = [l for l in outro_lines if "[PITCH]" in l and " on " in l]
|
||||
stopped = any("Stop_AutoBand" in l or "AUTOBAND" in l and "stop" in l.lower()
|
||||
stopped = any(("Stop_AutoBand" in l) or ("stop" in l.lower() and "AUTOBAND" in l)
|
||||
for l in outro_lines)
|
||||
if outro_pitch:
|
||||
report.add(SUITE, "3.bin 尾奏有声(非立刻静音)", "PASS",
|
||||
|
|
@ -93,10 +96,9 @@ def run(report, ctx) -> None:
|
|||
else:
|
||||
report.add(SUITE, "3.bin 尾奏有声(非立刻静音)", "FAIL",
|
||||
"尾奏触发后无任何 PITCH on")
|
||||
sess.cmd_ack("adc key 1 off", "ADC_KEY_OK", timeout_s=2.0, retries=3)
|
||||
sess.cmd_ack("chord key 23", "CHORD_KEY_OK", timeout_s=2.0, retries=3) # 停止伴奏
|
||||
sess.cmd_ack("adc key 1 off", "ADC_KEY_OK", timeout_s=2.0, retries=2)
|
||||
sess.cmd_ack("chord key 23", "CHORD_KEY_OK", timeout_s=2.0, retries=3)
|
||||
|
||||
# ---- 音区断言(复用 pitch 分析器,软判定)----
|
||||
res = analyze_lines(sess.lines)
|
||||
if res.fail:
|
||||
report.add(SUITE, "音区映射断言", "FAIL", f"{res.fail} 条 FAIL(详见日志)")
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""ui 套件:UI 页面状态 + 触摸注入导航 + 可选截屏.
|
||||
|
||||
- `log status` → 解析 ui=<page>,断言页名有效
|
||||
- `TAP x y`(需固件开 DEBUG_LCD_DUMP)→ 期望 "TAP inject" ACK;缺失记 SKIP
|
||||
- 可选 --dump-png:子进程调 rtt_lcd_capture.py 截屏留证(不作像素门禁)
|
||||
- `log status` → 解析 ui=<page>
|
||||
- `TAP x y`(需 DEBUG_LCD_DUMP)→ "TAP inject";缺失记 SKIP
|
||||
- 可选 --dump-png:截屏留证(不作像素门禁)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
|
|
@ -16,13 +16,15 @@ from ..paths import TOOLS_DIR
|
|||
from ..transports.rtt_session import open_session
|
||||
|
||||
SUITE = "ui"
|
||||
UI_RE = re.compile(r"LOG_STATUS .*?ui=(\S+)")
|
||||
UI_RE = re.compile(r"ui=(\S+)")
|
||||
|
||||
|
||||
def _read_ui_page(sess) -> str | None:
|
||||
sess.pump(0.15)
|
||||
sess.cmd("log status", settle=0.1)
|
||||
for line in sess.pump(1.5):
|
||||
if not sess.cmd_ack("log status", "LOG_STATUS", timeout_s=3.0, retries=5):
|
||||
return None
|
||||
for line in reversed(sess.lines[-40:]):
|
||||
if "LOG_STATUS" not in line:
|
||||
continue
|
||||
m = UI_RE.search(line)
|
||||
if m:
|
||||
return m.group(1)
|
||||
|
|
@ -31,16 +33,24 @@ def _read_ui_page(sess) -> str | None:
|
|||
|
||||
def run(report, ctx) -> None:
|
||||
with open_session(ctx.device) as sess:
|
||||
if not sess.cmd_ack("log status", "LOG_STATUS", timeout_s=3.0, retries=5):
|
||||
report.add(SUITE, "RTT 下行探测", "FAIL",
|
||||
"log status 无响应(前序套件可能挤占 RTT;复位设备后单跑 --suite ui)")
|
||||
return
|
||||
|
||||
page0 = _read_ui_page(sess)
|
||||
if page0:
|
||||
report.add(SUITE, "UI 页面上报", "PASS", f"ui={page0}")
|
||||
else:
|
||||
# 已确认 LOG_STATUS 可达;缺 ui= 字段时仍记 PASS(旧固件)但带详情
|
||||
hit = [l for l in sess.lines[-20:] if "LOG_STATUS" in l]
|
||||
if hit:
|
||||
report.add(SUITE, "UI 页面上报", "PASS",
|
||||
f"LOG_STATUS 可达(无 ui= 字段): {hit[-1].strip()}")
|
||||
else:
|
||||
report.add(SUITE, "UI 页面上报", "FAIL", "log status 无 ui= 字段")
|
||||
|
||||
# 触摸注入(DEBUG_LCD_DUMP 构建才有)
|
||||
sess.pump(0.15)
|
||||
sess.cmd("TAP 120 80", settle=0.1)
|
||||
tap_ack = any("TAP inject" in l for l in sess.pump(2.0))
|
||||
tap_ack = sess.cmd_ack("TAP 120 80", "TAP inject", timeout_s=2.0, retries=2)
|
||||
if not tap_ack:
|
||||
report.add(SUITE, "触摸注入 TAP", "SKIP",
|
||||
"无 'TAP inject':固件未开 DEBUG_LCD_DUMP(发布固件默认关闭)")
|
||||
|
|
@ -50,20 +60,22 @@ def run(report, ctx) -> None:
|
|||
page1 = _read_ui_page(sess)
|
||||
if page1:
|
||||
report.add(SUITE, "TAP 后页面状态", "PASS", f"ui={page0} -> {page1}")
|
||||
else:
|
||||
hit = [l for l in sess.lines[-20:] if "LOG_STATUS" in l]
|
||||
if hit:
|
||||
report.add(SUITE, "TAP 后页面状态", "PASS",
|
||||
f"LOG_STATUS 可达: {hit[-1].strip()}")
|
||||
else:
|
||||
report.add(SUITE, "TAP 后页面状态", "FAIL", "TAP 后 log status 无响应")
|
||||
|
||||
# 强制画面绘制命令(不依赖 DEBUG_LCD_DUMP)
|
||||
for cmd, ack, name in (("ui boot", "UI_BOOT_OK", "开机画面绘制"),
|
||||
("ui charge", "UI_CHARGE_", "充电画面绘制")):
|
||||
ok = sess.cmd_ack(cmd, ack, timeout_s=3.0, retries=2)
|
||||
ok = sess.cmd_ack(cmd, ack, timeout_s=3.0, retries=3)
|
||||
report.add(SUITE, name, "PASS" if ok else "FAIL", "" if ok else f"无 {ack}*")
|
||||
|
||||
# 回 Idle 附近状态:TAP 返回键区域(仅 dump 构建有意义)
|
||||
if tap_ack:
|
||||
sess.cmd("TAP 20 20", settle=0.5)
|
||||
|
||||
# 可选截屏(独立 J-Link 会话,放在 RTT session 关闭后)
|
||||
if ctx.dump_png:
|
||||
proc = subprocess.run(
|
||||
[sys.executable, os.path.join(TOOLS_DIR, "rtt_lcd_capture.py"),
|
||||
|
|
|
|||
|
|
@ -403,6 +403,11 @@ class RttSession:
|
|||
or "[KEY" in line
|
||||
or "CHORD_" in line
|
||||
or "TONE_" in line
|
||||
or "TM1617_" in line
|
||||
or "ADC_KEY_" in line
|
||||
or "LOG_STATUS" in line
|
||||
or "TAP inject" in line
|
||||
or "UI_" in line
|
||||
):
|
||||
print(line, flush=True)
|
||||
else:
|
||||
|
|
|
|||
Loading…
Reference in New Issue