Rework radar system
This commit is contained in:
@@ -21,7 +21,7 @@
|
||||
## Архитектура
|
||||
|
||||
```
|
||||
[starter] ── TASK_ENABLE ──► плата кратко открыл порт, послал, закрыл
|
||||
[starter] ── TASK_ENABLE ──► плата запускает вариацию
|
||||
│ пишет session.json (target temp1/2, tolerance, variation_type)
|
||||
▼
|
||||
[monitor] ── TRANS_ENABLE ──► плата владеет портом всё время работы
|
||||
@@ -32,19 +32,21 @@
|
||||
сверяет temp1↔target_temp1 и temp2↔target_temp2, |Δ|>tol ─► WARNING в консоль
|
||||
```
|
||||
|
||||
- **Порт лазера эксклюзивен.** `starter` трогает его кратко, затем `monitor`
|
||||
владеет им всё время. `checker` порт не трогает вовсе — читает только файлы.
|
||||
- **Порт лазера эксклюзивен.** При `--start` запуск и мониторинг используют одно
|
||||
соединение. Штатный пайплайн закрывает порт после запуска вариации, и его
|
||||
может открыть отдельный монитор. `checker` читает только файлы.
|
||||
- **Связь через файлы** (JSONL + JSON), а не сокеты, — процессы стартуют,
|
||||
останавливаются и перезапускаются независимо, без рукопожатия.
|
||||
- **Сверяются оба лазера** по внутренним `temp1`/`temp2` (не по внешним
|
||||
термисторам `temp_ext*`), каждый со своим допуском (по умолчанию `0.03 °C`).
|
||||
термисторам `temp_ext*`), с общим допуском (по умолчанию `0.03 °C`).
|
||||
|
||||
## Быстрый старт (CLI, два процесса)
|
||||
|
||||
Терминал 1 — стартовать вариацию и мониторить температуру:
|
||||
Команды запускаются из корня проекта. Терминал 1 — стартовать вариацию и
|
||||
мониторить температуру:
|
||||
|
||||
```bash
|
||||
python -m python_app.scripts.laser_temp_monitor \
|
||||
.venv/bin/python -m python_app.scripts.laser_temp_monitor \
|
||||
--config run_config.json \
|
||||
--start
|
||||
```
|
||||
@@ -52,15 +54,15 @@ python -m python_app.scripts.laser_temp_monitor \
|
||||
Терминал 2 — валидировать температуру и печатать предупреждения:
|
||||
|
||||
```bash
|
||||
python -m python_app.scripts.laser_temp_checker
|
||||
.venv/bin/python -m python_app.scripts.laser_temp_checker
|
||||
```
|
||||
|
||||
Пример вывода чекера при расхождении и возврате в допуск:
|
||||
|
||||
```
|
||||
WARNING laser_temp_checker: Laser 1 temperature off target: measured 28.100 °C,
|
||||
WARNING python_app.hardware_full.laser_control.monitoring.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:
|
||||
INFO python_app.hardware_full.laser_control.monitoring.checker: Laser 1 temperature back within tolerance:
|
||||
28.000 °C (target 28.000, |Δ|=0.000 ≤ 0.030) [seq=2]
|
||||
```
|
||||
|
||||
@@ -69,7 +71,7 @@ INFO laser_temp_checker: Laser 1 temperature back within tolerance:
|
||||
## Конфигурация
|
||||
|
||||
Параметры берутся из `run_config.json`, секция `radar.laser_control`. Мониторинг
|
||||
использует блок `variation` и новое поле `temp_tolerance_c`:
|
||||
использует блок `variation`, включая `temp_tolerance_c`:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -105,7 +107,7 @@ INFO laser_temp_checker: Laser 1 temperature back within tolerance:
|
||||
|
||||
| Поле | Смысл |
|
||||
|---|---|
|
||||
| `port` | Серийный порт лазерной платы (пусто → автоопределение) |
|
||||
| `port` | Серийный порт лазерной платы; CLI-монитор допускает пустое значение для автоопределения, пайплайн требует явный порт |
|
||||
| `static_temp1` / `static_temp2` | Целевые статичные температуры лазеров 1/2, °C |
|
||||
| `min_value` / `max_value` / `step` | Диапазон и шаг свипа тока, мА — из них считается период свипа |
|
||||
| `time_step` / `delay_time` | Тайминги точки (мкс / мс) — тоже входят в период свипа |
|
||||
@@ -126,13 +128,13 @@ INFO laser_temp_checker: Laser 1 temperature back within tolerance:
|
||||
Мониторить уже запущенную из GUI/пайплайна вариацию (без повторного старта):
|
||||
|
||||
```bash
|
||||
python -m python_app.scripts.laser_temp_monitor --config run_config.json
|
||||
.venv/bin/python -m python_app.scripts.laser_temp_monitor --config run_config.json
|
||||
```
|
||||
|
||||
Фиксированный период вместо расчётного (напр. раз в 500 мс):
|
||||
|
||||
```bash
|
||||
python -m python_app.scripts.laser_temp_monitor \
|
||||
.venv/bin/python -m python_app.scripts.laser_temp_monitor \
|
||||
--config run_config.json --strategy interval:500
|
||||
```
|
||||
|
||||
@@ -150,10 +152,10 @@ python -m python_app.scripts.laser_temp_monitor \
|
||||
|
||||
```bash
|
||||
# монитор
|
||||
python -m python_app.scripts.laser_temp_monitor --config cfg.json --start \
|
||||
.venv/bin/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 \
|
||||
.venv/bin/python -m python_app.scripts.laser_temp_checker \
|
||||
--readings /tmp/run7.jsonl --session /tmp/run7.session.json --reminder-every 20
|
||||
```
|
||||
|
||||
@@ -161,9 +163,10 @@ python -m python_app.scripts.laser_temp_checker \
|
||||
|
||||
Когда вариацию стартует штатный пайплайн
|
||||
([`apply_kamil_adc_laser_control`](../../kamil_adc/laser.py)), снимок сессии
|
||||
`session.json` пишется автоматически. Достаточно запустить только чекер
|
||||
(и, при желании, монитор без `--start`, чтобы он опрашивал плату). Так консоль
|
||||
получит предупреждения о рассинхроне температуры прямо во время захвата.
|
||||
`<tmp>/laser_variation_session.json` пишется автоматически. Для предупреждений
|
||||
во время захвата нужны монитор без `--start`, который создаёт показания, и чекер.
|
||||
Если монитор уже работает, достаточно добавить чекер. Он загружает сессию
|
||||
один раз при запуске; после запуска новой вариации чекер нужно перезапустить.
|
||||
|
||||
## Встраивание в свой код (без CLI)
|
||||
|
||||
@@ -187,15 +190,22 @@ period = resolve_period_s("computed", min_value=33.0, max_value=60.0, step=0.05,
|
||||
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()
|
||||
worker = threading.Thread(target=monitor.run, args=(stop,))
|
||||
worker.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)
|
||||
try:
|
||||
while not stop.is_set():
|
||||
for reading in reader.poll():
|
||||
checker.process(reading) # печатает WARNING при |Δ| > tolerance
|
||||
stop.wait(0.2)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
stop.set()
|
||||
worker.join() # завершить опрос до закрытия порта и файла
|
||||
```
|
||||
|
||||
`LaserTemperatureChecker.evaluate(reading)` возвращает список
|
||||
@@ -216,7 +226,7 @@ with LaserController(port="/dev/ttyUSB0") as ctrl, ReadingWriter("readings.jsonl
|
||||
}
|
||||
```
|
||||
|
||||
`readings.jsonl` (по одной строке-объекту на свип):
|
||||
`readings.jsonl` (по одной строке-объекту на успешный опрос):
|
||||
|
||||
```json
|
||||
{"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}
|
||||
@@ -232,14 +242,16 @@ per_point_s = delay_time / 1000 + time_step / 1_000_000
|
||||
sweep_period = num_steps × per_point_s
|
||||
```
|
||||
|
||||
Монитор публикует одно показание за такой период. Если нужен другой темп —
|
||||
Монитор опрашивает плату сразу после запуска, затем ждёт такой период после
|
||||
каждого опроса; это оценка, без синхронизации с границами аппаратных свипов.
|
||||
Если нужен другой темп —
|
||||
`--strategy interval:<ms>`. (Внутренний счётчик `TO6` платы существует, но его
|
||||
семантика не гарантирована, поэтому для тайминга он не используется.)
|
||||
|
||||
## Тесты
|
||||
|
||||
```bash
|
||||
python -m pytest python_app/tests/test_laser_temp_monitoring.py -q
|
||||
.venv/bin/python -m unittest python_app.tests.test_laser_temp_monitoring
|
||||
```
|
||||
|
||||
Покрыто: round-trip сессии, tail JSONL (включая усечённую последнюю строку),
|
||||
|
||||
@@ -23,10 +23,15 @@ from .readings_channel import ReadingWriter, TemperatureReading
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class _Temperatures(Protocol):
|
||||
temp1: float
|
||||
temp2: float
|
||||
|
||||
|
||||
class _MeasurementSource(Protocol):
|
||||
"""Minimal controller surface the monitor depends on (eases testing)."""
|
||||
|
||||
def get_measurements(self) -> object: ...
|
||||
def get_measurements(self) -> _Temperatures | None: ...
|
||||
|
||||
|
||||
def compute_sweep_period_s(
|
||||
@@ -138,5 +143,5 @@ class LaserTemperatureMonitor:
|
||||
logger.info("Temperature monitor stopped after %d readings", seq)
|
||||
|
||||
|
||||
def _opt(value: object) -> Optional[float]:
|
||||
def _opt(value: float | None) -> Optional[float]:
|
||||
return None if value is None else float(value)
|
||||
|
||||
@@ -58,7 +58,7 @@ class TemperatureReading:
|
||||
)
|
||||
|
||||
|
||||
def _opt_float(value: object) -> Optional[float]:
|
||||
def _opt_float(value: str | float | None) -> Optional[float]:
|
||||
return None if value is None else float(value)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user