Files
FilmForge/test_run.py
T
hulk 2f43efb94b Tools: PID debug toolchain from V0.1.x bring-up
test_run.py: judged closed-loop test over COM5 (fixed sequence, J1~J4
2026-10-15 20:55:29 +08:00

112 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""FilmForge 温控闭环自动测试(COM5,RS485)。
固定测试序列:M999(版本) -> M2S500(0.5s 上报) -> M4S50(目标) -> M3S1(加热)
-> 采样 DURATION 秒 -> M3S0/M2S0 收尾。
不设温度上限(用户明确:真机靠物理温度开关保护,测试环境上限由用户控制)。
判定标准(脚本自动输出,不掺人工解释):
J1 上报流:>2 帧/s 连续; J2 稳态窗(最后 300s):|均值-目标|<=0.5C
J3 稳态窗极值:[目标-0.5, 目标+0.5](用户规格 ±0.5C)
J4 无 OPEN/SHRT 故障值
输出:test_data.csv(t,temp 全序列)+ test_events.log(全部收发)+ 终端摘要。
"""
import sys
import time
sys.path.insert(0, "GUI")
import filmforge_rs485 as ff
PORT = "COM5"
TARGET = 50.0
DURATION = float(sys.argv[1]) if len(sys.argv) > 1 else 600.0
rows = [] # (t, temp or None)
events = []
faults = []
version = None
t0 = time.time()
def on_event(line):
events.append((time.time() - t0, line))
with open("test_events.log", "a", encoding="utf-8") as f:
f.write(f"{time.time():.3f} {line}\n")
def on_status(msg):
on_event(f"[STATUS] {msg}")
def on_frame(frame, t_rx):
payload = frame.payload if isinstance(frame.payload, str) else \
frame.payload.decode("ascii", errors="replace")
try:
val = float(payload)
except ValueError:
val = None
if val is not None and val > -100.0: # 正常温度
rows.append((time.time() - t0, val))
elif val is not None: # OPEN=-273.15 / SHORT=-274.15
faults.append((time.time() - t0, val))
on_event(f"[FAULT] {payload}")
else:
if payload.startswith("v"):
global version
version = payload
on_event(f"[FRAME] {payload!r}")
link = ff.FF485(on_event=on_event, on_status=on_status, on_frame=on_frame)
link.open(PORT)
time.sleep(0.5)
if not link.is_connected():
print("OPEN COM5 FAILED")
sys.exit(1)
# ---- 序列 ----
link.send("M999")
time.sleep(1.0)
link.send("M2S500")
time.sleep(1.0)
link.send("M4S50")
time.sleep(1.0)
link.send("M3S1")
on_event("[SEQ] heat on, target 50")
t_end = time.time() + DURATION
while time.time() < t_end:
time.sleep(1.0)
link.send("M3S0")
time.sleep(0.5)
link.send("M2S0")
time.sleep(1.0)
link.close()
# ---- 判定输出 ----
with open("test_data.csv", "w", encoding="utf-8") as f:
f.write("t,temp\n")
for t, v in rows:
f.write(f"{t:.2f},{v:.2f}\n")
print(f"version={version} frames={len(rows)} faults={len(faults)}")
if rows:
temps = [v for _, v in rows]
print(f"info global_max={max(temps):.2f} global_min={min(temps):.2f}")
settle = [v for t, v in rows if t >= rows[-1][0] - 300.0] or temps
mean = sum(settle) / len(settle)
print(f"J2 settle_mean={mean:.2f} (|{-TARGET + mean:.2f}|<=0.5? {'PASS' if abs(mean - TARGET) <= 0.5 else 'FAIL'})")
ok3 = max(settle) <= TARGET + 0.5 and min(settle) >= TARGET - 0.5
print(f"J3 settle_min={min(settle):.2f} settle_max={max(settle):.2f} "
f"p2p={max(settle) - min(settle):.2f} ([{TARGET - 0.5:.1f},{TARGET + 0.5:.1f}]? {'PASS' if ok3 else 'FAIL'})")
cross = next((t for t, v in rows if abs(v - TARGET) <= 0.3), None)
if cross:
post = [(t, v) for t, v in rows if t >= cross]
print(f"reach={cross:.0f}s post_max={max(v for _, v in post):.2f} "
f"(overshoot {max(v for _, v in post) - TARGET:+.2f})")
print(f"J4 faults={'PASS' if not faults else 'FAIL(' + str(len(faults)) + ')'}")
print(f"J1 report_rate={'PASS' if len(rows) >= DURATION * 1.6 else 'FAIL'}")