Add RFG UDP source to rph_radar

This commit is contained in:
Ayzen
2026-10-06 19:36:00 +03:00
parent a185e61495
commit cfe81f782b
29 changed files with 817 additions and 279 deletions
@@ -4,11 +4,14 @@ from __future__ import annotations
from collections.abc import Iterable
from dataclasses import asdict
import ipaddress
import logging
import math
import re
from typing import Any
from python_app.hardware_full.laser_control.conversions import variation_step_count
from python_app.hardware_full.laser_control.validators import validate_variation
from python_app.models.json_fields import (
finite_number,
read_bool as _require_bool, read_int as _require_int, read_str as _require_str,
@@ -138,6 +141,8 @@ def validate_acquisition_config(config: RunConfigModel) -> None:
if config.is_rph_radar:
validate_rph_radar_model(config.radar.rph_radar)
if config.radar.rph_radar.source == "rfg_udp":
validate_rfg_source(config)
for switch, physical_positions in (
(config.input_switch, config.matrix_input_switch_positions),
@@ -170,8 +175,40 @@ def validate_acquisition_config(config: RunConfigModel) -> None:
raise ValueError(f"rings.{name}.slot_size_bytes must be at least {minimum} for this sweep")
def parse_udp_bind(text: str) -> tuple[str, int]:
"""``HOST:PORT`` (empty host = all interfaces) -> socket address."""
host, colon, port = text.rpartition(":")
try:
ipaddress.IPv4Address(host or "0.0.0.0")
except ValueError:
colon = ""
if not colon or not port.isdigit() or not 1 <= int(port) <= 65535:
raise ValueError(f"radar.rph_radar.udp_bind must be IPV4:PORT, got {text!r}")
return host or "0.0.0.0", int(port)
def validate_rfg_source(config: RunConfigModel) -> None:
"""The RFG board numbers steps of the laser current sweep; its exact length must be known."""
laser = config.radar.laser_control
if laser.mode.strip().lower() != "variation":
raise ValueError("radar.rph_radar.source rfg_udp requires radar.laser_control.mode 'variation'")
variation = laser.variation
validate_variation(variation.min_value, variation.max_value, variation.step,
variation.time_step, variation.delay_time)
if variation_step_count(variation.min_value, variation.max_value, variation.step) < 2:
raise ValueError("The laser current sweep must have at least two steps")
# Without a source-side marker a sweep after a switch cannot be proven fresh, so every
# capture path (run combos and the full calibration matrix) must see a single pair.
if config.input_switch.positions != 1 or config.output_switch.positions != 1:
raise ValueError("radar.rph_radar.source rfg_udp supports one antenna pair: both switches need 1 position")
def validate_rph_radar_model(adc: RphRadarModel) -> None:
"""Reject phase/grid errors before stopping a running pipeline."""
if adc.source not in ("e502", "rfg_udp"):
raise ValueError("radar.rph_radar.source must be e502 or rfg_udp")
if adc.source == "rfg_udp":
parse_udp_bind(adc.udp_bind)
phase, band = adc.phase_calibration, adc.band
for name, value in {**asdict(phase), **asdict(band)}.items():
if isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(value):