# -*- 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'}")