Add laser temperature monitoring for current-variation runs

Independent monitor + checker for CHANGE_CURRENT_LD1 variation, where the
board sweeps LD1 current autonomously while both laser temperatures must stay
on their static setpoints. Nothing previously verified that the setpoints were
actually reached, so a stale temperature silently corrupted measurements.

New package python_app/hardware_full/laser_control/monitoring/:
- session.py: LaserVariationSession snapshot (targets + tolerance frozen at start)
- readings_channel.py: JSONL append/tail channel for per-sweep readings
- monitor.py: LaserTemperatureMonitor polls the board once per sweep, publishes
- checker.py: LaserTemperatureChecker validates temp1/temp2 vs targets (0.03 C),
  warns to console per laser with anti-spam state

CLI entry points scripts/laser_temp_monitor.py and scripts/laser_temp_checker.py
run as independent processes communicating via IPC files.

Also: add temp_tolerance_c to LaserVariationModeModel (schema + codec round-trip)
and write the session snapshot from apply_kamil_adc_laser_control's variation path
so the checker works with pipeline-started runs too.

Tests in tests/test_laser_temp_monitoring.py (17 cases); existing laser-control
protocol and config-codec suites remain green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
awe
2026-07-27 21:41:14 +03:00
co-authored by Claude Opus 4.8
parent 52c1218bf7
commit e219f6ec02
+8 -1
View File
@@ -26,6 +26,7 @@ from python_app.hardware_full.laser_control.controller import (
DEVICE_MAIN_MESSAGE_ID, DEVICE_MAIN_MESSAGE_ID,
LaserController, LaserController,
) )
from python_app.hardware_full.laser_control.exceptions import PortBusyError
from python_app.hardware_full.laser_control.models import VariationType from python_app.hardware_full.laser_control.models import VariationType
from python_app.hardware_full.laser_control.monitoring import ( from python_app.hardware_full.laser_control.monitoring import (
DEFAULT_READINGS_PATH, DEFAULT_READINGS_PATH,
@@ -114,7 +115,13 @@ def main() -> int:
pi_coeff2_p=laser.pi_coeff2_p, pi_coeff2_p=laser.pi_coeff2_p,
pi_coeff2_i=laser.pi_coeff2_i, pi_coeff2_i=laser.pi_coeff2_i,
) )
controller.connect() try:
controller.connect()
except PortBusyError as exc:
# Expected, benign conflict: the manual-control UI (or another monitor)
# already owns the port. Exit cleanly with guidance, not a traceback.
logger.error("%s", exc)
return 2
try: try:
if args.start: if args.start:
_start_variation(controller, variation) _start_variation(controller, variation)