"""Standalone laser temperature checker process. Reads the target setpoints frozen at variation start and tails the JSONL readings channel produced by the monitor. For every reading it validates both lasers and prints a console warning whenever a measured temperature drifts from its target by more than the tolerance (default 0.03 °C). Never touches the serial port, so it is fully independent of the monitor and can be started/stopped at any time. Example:: python -m python_app.scripts.laser_temp_checker python -m python_app.scripts.laser_temp_checker --session s.json --readings r.jsonl """ from __future__ import annotations import argparse import logging import signal import threading from pathlib import Path from python_app.hardware_full.laser_control.monitoring import ( DEFAULT_READINGS_PATH, DEFAULT_SESSION_PATH, LaserTemperatureChecker, LaserVariationSession, ReadingReader, ) logger = logging.getLogger("laser_temp_checker") _POLL_INTERVAL_S = 0.2 def main() -> int: parser = argparse.ArgumentParser(description="Validate laser temperature against setpoints") parser.add_argument("--session", type=Path, default=DEFAULT_SESSION_PATH, help="Session snapshot with target setpoints + tolerance") parser.add_argument("--readings", type=Path, default=DEFAULT_READINGS_PATH, help="JSONL readings channel to tail") parser.add_argument("--tolerance", type=float, default=None, help="Override tolerance in °C (default: from session)") parser.add_argument("--reminder-every", type=int, default=0, help="Repeat a warning every N readings while off target (0=off)") parser.add_argument("--from-start", action="store_true", help="Validate the whole readings file, not just new lines") args = parser.parse_args() logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s: %(message)s") session = LaserVariationSession.load(args.session) if args.tolerance is not None: session.tolerance_c = args.tolerance checker = LaserTemperatureChecker.from_session(session, reminder_every=args.reminder_every) logger.info( "Checking against T1=%.3f T2=%.3f °C, tolerance ±%.3f °C (%s)", session.target_temp1, session.target_temp2, session.tolerance_c, session.variation_type, ) reader = ReadingReader(args.readings, from_start=args.from_start) stop_event = threading.Event() def request_stop(_signum: int, _frame: object) -> None: stop_event.set() signal.signal(signal.SIGINT, request_stop) signal.signal(signal.SIGTERM, request_stop) while not stop_event.is_set(): for reading in reader.poll(): checker.process(reading) stop_event.wait(_POLL_INTERVAL_S) return 0 if __name__ == "__main__": raise SystemExit(main())