Independent monitor + checker for CHANGE_CURRENT_LD1 variation
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
"""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())
|
||||
@@ -0,0 +1,142 @@
|
||||
"""Standalone laser temperature monitor process.
|
||||
|
||||
Owns the laser serial port, (optionally) starts a current-variation task, then
|
||||
polls the board once per sweep and appends each reading to a JSONL channel that
|
||||
the temperature checker tails. Runs until SIGINT/SIGTERM.
|
||||
|
||||
Examples::
|
||||
|
||||
# Start LD1 current variation from a run config, then monitor:
|
||||
python -m python_app.scripts.laser_temp_monitor --config run_config.json --start
|
||||
|
||||
# Monitor a variation that is already running:
|
||||
python -m python_app.scripts.laser_temp_monitor --config run_config.json
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import signal
|
||||
import threading
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from python_app.hardware_full.laser_control.controller import (
|
||||
DEVICE_MAIN_MESSAGE_ID,
|
||||
LaserController,
|
||||
)
|
||||
from python_app.hardware_full.laser_control.models import VariationType
|
||||
from python_app.hardware_full.laser_control.monitoring import (
|
||||
DEFAULT_READINGS_PATH,
|
||||
DEFAULT_SESSION_PATH,
|
||||
LaserTemperatureMonitor,
|
||||
LaserVariationSession,
|
||||
ReadingWriter,
|
||||
resolve_period_s,
|
||||
)
|
||||
from python_app.models.run_config_model import RunConfigModel
|
||||
|
||||
logger = logging.getLogger("laser_temp_monitor")
|
||||
|
||||
|
||||
def _start_variation(controller: LaserController, variation) -> None:
|
||||
"""Send the CHANGE_CURRENT_LD1 task and freeze the session snapshot."""
|
||||
controller.reset()
|
||||
controller.set_manual_mode(
|
||||
temp1=variation.static_temp1,
|
||||
temp2=variation.static_temp2,
|
||||
current1=variation.static_current1,
|
||||
current2=variation.static_current2,
|
||||
message_id=DEVICE_MAIN_MESSAGE_ID,
|
||||
)
|
||||
controller.start_variation(
|
||||
variation_type=VariationType[variation.variation_type],
|
||||
params={
|
||||
"static_temp1": variation.static_temp1,
|
||||
"static_temp2": variation.static_temp2,
|
||||
"static_current1": variation.static_current1,
|
||||
"static_current2": variation.static_current2,
|
||||
"min_value": variation.min_value,
|
||||
"max_value": variation.max_value,
|
||||
"step": variation.step,
|
||||
"time_step": variation.time_step,
|
||||
"delay_time": variation.delay_time,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Poll laser temperature once per sweep")
|
||||
parser.add_argument("--config", required=True, type=Path, help="Path to run_config.json")
|
||||
parser.add_argument("--readings", type=Path, default=DEFAULT_READINGS_PATH,
|
||||
help="JSONL readings channel to append to")
|
||||
parser.add_argument("--session", type=Path, default=DEFAULT_SESSION_PATH,
|
||||
help="Session snapshot path (written with --start)")
|
||||
parser.add_argument("--strategy", default="computed",
|
||||
help="'computed' (per sweep) or 'interval:<ms>'")
|
||||
parser.add_argument("--start", action="store_true",
|
||||
help="Send CHANGE_CURRENT_LD1 before monitoring")
|
||||
args = parser.parse_args()
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s: %(message)s")
|
||||
|
||||
config = RunConfigModel.load_from_path(args.config)
|
||||
laser = config.radar.laser_control
|
||||
variation = laser.variation
|
||||
if variation.variation_type != "CHANGE_CURRENT_LD1":
|
||||
logger.warning(
|
||||
"Only CHANGE_CURRENT_LD1 is supported by firmware; got %s",
|
||||
variation.variation_type,
|
||||
)
|
||||
|
||||
period_s = resolve_period_s(
|
||||
args.strategy,
|
||||
min_value=variation.min_value,
|
||||
max_value=variation.max_value,
|
||||
step=variation.step,
|
||||
time_step_us=variation.time_step,
|
||||
delay_time_ms=variation.delay_time,
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
controller = LaserController(
|
||||
port=laser.port or None,
|
||||
pi_coeff1_p=laser.pi_coeff1_p,
|
||||
pi_coeff1_i=laser.pi_coeff1_i,
|
||||
pi_coeff2_p=laser.pi_coeff2_p,
|
||||
pi_coeff2_i=laser.pi_coeff2_i,
|
||||
)
|
||||
controller.connect()
|
||||
try:
|
||||
if args.start:
|
||||
_start_variation(controller, variation)
|
||||
LaserVariationSession(
|
||||
variation_type=variation.variation_type,
|
||||
target_temp1=variation.static_temp1,
|
||||
target_temp2=variation.static_temp2,
|
||||
tolerance_c=variation.temp_tolerance_c,
|
||||
started_at_iso=datetime.now().isoformat(timespec="seconds"),
|
||||
).save(args.session)
|
||||
logger.info("Started CHANGE_CURRENT_LD1 and wrote session %s", args.session)
|
||||
|
||||
with ReadingWriter(args.readings) as writer:
|
||||
monitor = LaserTemperatureMonitor(
|
||||
controller=controller, writer=writer, period_s=period_s
|
||||
)
|
||||
logger.info("Monitoring to %s (period=%.3fs)", args.readings, period_s)
|
||||
monitor.run(stop_event)
|
||||
finally:
|
||||
controller.disconnect()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user