#!/usr/bin/env python3 # -*- coding: utf-8 -*- """从串口日志采集 meas: 读数并填入执行卡 CALDATA 表。 用法: cal_collect.py --doc <执行卡路径> --sem ua --point 1 [--samples N] [--log <日志路径>] 自动读取 serial.log 最近 N 拍(默认 15),取中位数,写入 CALDATA 表对应行。 重复运行同一 --point 会覆盖旧值。 """ import argparse import re import statistics import sys import time from pathlib import Path DEFAULT_LOG = ".dsh/skills/serial-assistant/scripts/state/serial.log" SEM_FIELD = {"ua": "ua_mv", "un": "un_mv", "ia": "ia_mv"} def read_median(log_path, sem, n_samples=15, min_val=None): field = SEM_FIELD.get(sem) if not field: print("ERROR: unknown sem %r (use ua/un/ia)" % sem) return None text = Path(log_path).read_text(encoding="utf-8", errors="replace") lines = text.strip().splitlines() meas = [l for l in lines if "meas:" in l and field + "=" in l] recent = meas[-n_samples * 2:] if len(meas) >= n_samples * 2 else meas vals = [] for l in recent: m = re.search(field + r"=(\d+\.?\d*)", l) if m: v = float(m.group(1)) if min_val is not None and v < min_val: continue vals.append(v) if not vals: return None return round(statistics.median(vals)) def update_caldata(doc_path, sem, point, value): text = Path(doc_path).read_text(encoding="utf-8") m = re.search(r"(.*?)", text, re.S) if not m: print("ERROR: no CALDATA markers in %s" % doc_path) return False block = m.group(1) pattern = r"(\|\s*%s\s*\|\s*%d\s*\|\s*)(\S*|)(\s*\|.*)" % (re.escape(sem), point) new_block, count = re.subn(pattern, r"\g<1>%d\3" % value, block) if count != 1: print("ERROR: expected 1 match for %s point %d, got %d" % (sem, point, count)) return False text = text.replace(block, new_block) Path(doc_path).write_text(text, encoding="utf-8", newline="\n") return True def main(): ap = argparse.ArgumentParser(description="采集串口读数填入 CALDATA 表") ap.add_argument("--doc", required=True, help="执行卡文档路径") ap.add_argument("--sem", required=True, choices=["ua", "un", "ia"], help="通道") ap.add_argument("--point", required=True, type=int, help="点序号 (1-5)") ap.add_argument("--samples", type=int, default=15, help="采样拍数 (默认 15)") ap.add_argument("--log", default=DEFAULT_LOG, help="串口日志路径") ap.add_argument("--min-val", type=float, default=None, help="过滤低于此值的读数(切换瞬态)") ap.add_argument("--wait", type=float, default=3.0, help="等待新数据到达的秒数 (默认 3)") a = ap.parse_args() if a.point < 1 or a.point > 8: print("ERROR: point must be 1-8") return 1 time.sleep(a.wait) med = read_median(a.log, a.sem, a.samples, a.min_val) if med is None: print("ERROR: no valid %s data in last %d samples" % (a.sem, a.samples)) return 1 print("%s point %d: %d mV/mA (median of %d samples)" % (a.sem, a.point, med, a.samples)) if not update_caldata(a.doc, a.sem, a.point, med): return 1 print("OK: updated CALDATA table in %s" % a.doc) return 0 if __name__ == "__main__": sys.exit(main())