Контроль температуры при вариации тока лазера
Набор из трёх развязанных компонентов для автоматизации измерений в режиме
вариации тока лазера 1 (CHANGE_CURRENT_LD1). Пока плата гоняет свип тока,
температуры лазеров должны оставаться на заданных статичных уставках. Эти модули
раз в свип считывают реальную температуру и предупреждают, если она разошлась с
целью.
Зачем это нужно
При запуске вариации тока из GUI изменённые значения температуры могут фактически не дойти до цели — реальная температура остаётся прежней, и измерение становится некорректным. Плата после старта задачи гоняет свип автономно и никак не сигнализирует, что уставка не достигнута. Эти модули закрывают пробел: независимо опрашивают плату и валидируют температуру относительно уставок, зафиксированных в момент старта вариации.
⚠️ В прошивке реализована только вариация тока (
CHANGE_CURRENT_LD1). Вариация температуры не поддерживается и в этот API не заложена.
Архитектура
[starter] ── TASK_ENABLE ──► плата кратко открыл порт, послал, закрыл
│ пишет session.json (target temp1/2, tolerance, variation_type)
▼
[monitor] ── TRANS_ENABLE ──► плата владеет портом всё время работы
│ раз в свип: get_measurements()
│ дописывает строку в readings.jsonl (seq, temp1, temp2, temp_ext, I1, I2)
▼
[checker] читает session.json + tail readings.jsonl
сверяет temp1↔target_temp1 и temp2↔target_temp2, |Δ|>tol ─► WARNING в консоль
- Порт лазера эксклюзивен.
starterтрогает его кратко, затемmonitorвладеет им всё время.checkerпорт не трогает вовсе — читает только файлы. - Связь через файлы (JSONL + JSON), а не сокеты, — процессы стартуют, останавливаются и перезапускаются независимо, без рукопожатия.
- Сверяются оба лазера по внутренним
temp1/temp2(не по внешним термисторамtemp_ext*), каждый со своим допуском (по умолчанию0.03 °C).
Быстрый старт (CLI, два процесса)
Терминал 1 — стартовать вариацию и мониторить температуру:
python -m python_app.scripts.laser_temp_monitor \
--config run_config.json \
--start
Терминал 2 — валидировать температуру и печатать предупреждения:
python -m python_app.scripts.laser_temp_checker
Пример вывода чекера при расхождении и возврате в допуск:
WARNING laser_temp_checker: Laser 1 temperature off target: measured 28.100 °C,
target 28.000 °C, Δ=+0.100 °C exceeds tolerance ±0.030 °C [seq=1]
INFO laser_temp_checker: Laser 1 temperature back within tolerance:
28.000 °C (target 28.000, |Δ|=0.000 ≤ 0.030) [seq=2]
Остановка — Ctrl+C (SIGINT) в любом из процессов.
Конфигурация
Параметры берутся из run_config.json, секция radar.laser_control. Мониторинг
использует блок variation и новое поле temp_tolerance_c:
{
"radar": {
"model": "kamil_adc",
"laser_control": {
"enabled": true,
"port": "/dev/ttyUSB0",
"mode": "variation",
"pi_coeff1_p": 2560,
"pi_coeff1_i": 128,
"pi_coeff2_p": 2560,
"pi_coeff2_i": 128,
"variation": {
"variation_type": "CHANGE_CURRENT_LD1",
"static_temp1": 28.0,
"static_temp2": 28.9,
"static_current1": 33.0,
"static_current2": 35.0,
"min_value": 33.0,
"max_value": 60.0,
"step": 0.05,
"time_step": 50,
"delay_time": 10,
"temp_tolerance_c": 0.03
}
}
}
}
Ключевые поля для мониторинга:
| Поле | Смысл |
|---|---|
port |
Серийный порт лазерной платы (пусто → автоопределение) |
static_temp1 / static_temp2 |
Целевые статичные температуры лазеров 1/2, °C |
min_value / max_value / step |
Диапазон и шаг свипа тока, мА — из них считается период свипа |
time_step / delay_time |
Тайминги точки (мкс / мс) — тоже входят в период свипа |
temp_tolerance_c |
Допуск сверки, °C (по умолчанию 0.03) |
Опции CLI
laser_temp_monitor
| Аргумент | По умолчанию | Назначение |
|---|---|---|
--config |
— (обязателен) | Путь к run_config.json |
--start |
выкл. | Послать CHANGE_CURRENT_LD1 перед мониторингом и записать сессию |
--strategy |
computed |
computed (раз в свип) или interval:<ms> (фикс. период) |
--readings |
<tmp>/laser_temp_readings.jsonl |
Куда дописывать показания |
--session |
<tmp>/laser_variation_session.json |
Куда писать снимок сессии (с --start) |
Мониторить уже запущенную из GUI/пайплайна вариацию (без повторного старта):
python -m python_app.scripts.laser_temp_monitor --config run_config.json
Фиксированный период вместо расчётного (напр. раз в 500 мс):
python -m python_app.scripts.laser_temp_monitor \
--config run_config.json --strategy interval:500
laser_temp_checker
| Аргумент | По умолчанию | Назначение |
|---|---|---|
--session |
<tmp>/laser_variation_session.json |
Снимок с целями и допуском |
--readings |
<tmp>/laser_temp_readings.jsonl |
Какой канал показаний тайлить |
--tolerance |
из сессии | Переопределить допуск, °C |
--reminder-every |
0 (выкл.) |
Повторять предупреждение каждые N показаний, пока вне допуска |
--from-start |
выкл. | Проверить весь файл показаний, а не только новые строки |
Разные пути для нескольких одновременных прогонов:
# монитор
python -m python_app.scripts.laser_temp_monitor --config cfg.json --start \
--readings /tmp/run7.jsonl --session /tmp/run7.session.json
# чекер
python -m python_app.scripts.laser_temp_checker \
--readings /tmp/run7.jsonl --session /tmp/run7.session.json --reminder-every 20
Интеграция с пайплайном Kamil ADC
Когда вариацию стартует штатный пайплайн
(apply_kamil_adc_laser_control), снимок сессии
session.json пишется автоматически. Достаточно запустить только чекер
(и, при желании, монитор без --start, чтобы он опрашивал плату). Так консоль
получит предупреждения о рассинхроне температуры прямо во время захвата.
Встраивание в свой код (без CLI)
import threading
from python_app.hardware_full.laser_control.controller import LaserController
from python_app.hardware_full.laser_control.monitoring import (
LaserTemperatureMonitor, LaserTemperatureChecker, LaserVariationSession,
ReadingWriter, ReadingReader, resolve_period_s,
)
# 1. Зафиксировать цели при старте вариации
session = LaserVariationSession(
variation_type="CHANGE_CURRENT_LD1",
target_temp1=28.0, target_temp2=28.9, tolerance_c=0.03,
)
# 2. Монитор (в проде controller — реальный LaserController)
period = resolve_period_s("computed", min_value=33.0, max_value=60.0, step=0.05,
time_step_us=50, delay_time_ms=10)
stop = threading.Event()
with LaserController(port="/dev/ttyUSB0") as ctrl, ReadingWriter("readings.jsonl") as w:
monitor = LaserTemperatureMonitor(ctrl, w, period_s=period)
threading.Thread(target=monitor.run, args=(stop,), daemon=True).start()
# 3. Чекер: тайлить показания и валидировать оба лазера
checker = LaserTemperatureChecker.from_session(session)
reader = ReadingReader("readings.jsonl")
while not stop.is_set():
for reading in reader.poll():
checker.process(reading) # печатает WARNING при |Δ| > tolerance
stop.wait(0.2)
LaserTemperatureChecker.evaluate(reading) возвращает список
LaserDeviation (по лазеру: измеренное, цель, Δ, в допуске ли) без логирования —
удобно для собственной обработки/накопления статистики.
Формат IPC-файлов
session.json:
{
"variation_type": "CHANGE_CURRENT_LD1",
"target_temp1": 28.0,
"target_temp2": 28.9,
"tolerance_c": 0.03,
"started_at_iso": "2026-07-27T12:00:00"
}
readings.jsonl (по одной строке-объекту на свип):
{"seq":0,"mono_ns":123456789,"temp1":28.0,"temp2":28.9,"temp_ext1":22.0,"temp_ext2":23.0,"current1":33.0,"current2":35.0}
Как определяется «раз в свип»
Плата не отдаёт явную границу свипа, поэтому период оценивается из параметров:
num_steps = round(|max_value - min_value| / step) + 1
per_point_s = delay_time / 1000 + time_step / 1_000_000
sweep_period = num_steps × per_point_s
Монитор публикует одно показание за такой период. Если нужен другой темп —
--strategy interval:<ms>. (Внутренний счётчик TO6 платы существует, но его
семантика не гарантирована, поэтому для тайминга он не используется.)
Тесты
python -m pytest python_app/tests/test_laser_temp_monitoring.py -q
Покрыто: round-trip сессии, tail JSONL (включая усечённую последнюю строку), расчёт периода свипа, маппинг измерений монитором, и валидация чекера по каждому лазеру отдельно (порог, граница допуска, повторные предупреждения, восстановление).