Rename kamil_adc to rph_radar and fix its data path
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import asdict
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
@@ -105,8 +106,10 @@ def run_config_from_dict(payload: object) -> RunConfigModel:
|
||||
run_payload.get("locator_server", run_payload.get("locator")),
|
||||
"run.locator_server",
|
||||
)
|
||||
if radar_payload.get("model") == "kamil_adc":
|
||||
raise ValueError("radar.model kamil_adc was renamed: use rph_radar and move radar.kamil_adc to radar.rph_radar")
|
||||
multi_device_payload = _as_dict(radar_payload.get("multi_device"), "radar.multi_device")
|
||||
kamil_adc_payload = _as_dict(radar_payload.get("kamil_adc"), "radar.kamil_adc")
|
||||
rph_radar_payload = _as_dict(radar_payload.get("rph_radar"), "radar.rph_radar")
|
||||
laser_control_payload = _as_dict(radar_payload.get("laser_control"), "radar.laser_control")
|
||||
|
||||
model.radar.model = _read_str(radar_payload, "model", model.radar.model)
|
||||
@@ -162,36 +165,36 @@ def run_config_from_dict(payload: object) -> RunConfigModel:
|
||||
"stimulus_switch_positions",
|
||||
model.radar.multi_device.stimulus_switch_positions,
|
||||
)
|
||||
model.radar.kamil_adc.project_dir = _read_str(
|
||||
kamil_adc_payload, "project_dir", model.radar.kamil_adc.project_dir
|
||||
model.radar.rph_radar.project_dir = _read_str(
|
||||
rph_radar_payload, "project_dir", model.radar.rph_radar.project_dir
|
||||
)
|
||||
model.radar.kamil_adc.executable_path = _read_str(
|
||||
kamil_adc_payload, "executable_path", model.radar.kamil_adc.executable_path
|
||||
model.radar.rph_radar.executable_path = _read_str(
|
||||
rph_radar_payload, "executable_path", model.radar.rph_radar.executable_path
|
||||
)
|
||||
model.radar.kamil_adc.tty_path = _read_str(
|
||||
kamil_adc_payload, "tty_path", model.radar.kamil_adc.tty_path
|
||||
model.radar.rph_radar.tty_path = _read_str(
|
||||
rph_radar_payload, "tty_path", model.radar.rph_radar.tty_path
|
||||
)
|
||||
model.radar.kamil_adc.args = _load_string_list(kamil_adc_payload, "args", "radar.kamil_adc")
|
||||
model.radar.kamil_adc.env = _load_string_dict(kamil_adc_payload, "env", "radar.kamil_adc")
|
||||
model.radar.kamil_adc.startup_timeout_s = _read_float(
|
||||
kamil_adc_payload, "startup_timeout_s", model.radar.kamil_adc.startup_timeout_s
|
||||
model.radar.rph_radar.args = _load_string_list(rph_radar_payload, "args", "radar.rph_radar")
|
||||
model.radar.rph_radar.env = _load_string_dict(rph_radar_payload, "env", "radar.rph_radar")
|
||||
model.radar.rph_radar.startup_timeout_s = _read_float(
|
||||
rph_radar_payload, "startup_timeout_s", model.radar.rph_radar.startup_timeout_s
|
||||
)
|
||||
model.radar.kamil_adc.sweep_timeout_s = _read_float(
|
||||
kamil_adc_payload, "sweep_timeout_s", model.radar.kamil_adc.sweep_timeout_s
|
||||
model.radar.rph_radar.sweep_timeout_s = _read_float(
|
||||
rph_radar_payload, "sweep_timeout_s", model.radar.rph_radar.sweep_timeout_s
|
||||
)
|
||||
model.radar.kamil_adc.stop_timeout_s = _read_float(
|
||||
kamil_adc_payload, "stop_timeout_s", model.radar.kamil_adc.stop_timeout_s
|
||||
model.radar.rph_radar.stop_timeout_s = _read_float(
|
||||
rph_radar_payload, "stop_timeout_s", model.radar.rph_radar.stop_timeout_s
|
||||
)
|
||||
phase_calibration_payload = _as_dict(
|
||||
kamil_adc_payload.get("phase_calibration"), "radar.kamil_adc.phase_calibration"
|
||||
rph_radar_payload.get("phase_calibration"), "radar.rph_radar.phase_calibration"
|
||||
)
|
||||
calibration = model.radar.kamil_adc.phase_calibration
|
||||
calibration = model.radar.rph_radar.phase_calibration
|
||||
calibration.phase0_rad = _read_float(phase_calibration_payload, "phase0_rad", calibration.phase0_rad)
|
||||
calibration.freq0_hz = _read_float(phase_calibration_payload, "freq0_hz", calibration.freq0_hz)
|
||||
calibration.phase1_rad = _read_float(phase_calibration_payload, "phase1_rad", calibration.phase1_rad)
|
||||
calibration.freq1_hz = _read_float(phase_calibration_payload, "freq1_hz", calibration.freq1_hz)
|
||||
band_payload = _as_dict(kamil_adc_payload.get("band"), "radar.kamil_adc.band")
|
||||
band = model.radar.kamil_adc.band
|
||||
band_payload = _as_dict(rph_radar_payload.get("band"), "radar.rph_radar.band")
|
||||
band = model.radar.rph_radar.band
|
||||
band.start_hz = _read_float(band_payload, "start_hz", band.start_hz)
|
||||
band.stop_hz = _read_float(band_payload, "stop_hz", band.stop_hz)
|
||||
band.points = _read_int(band_payload, "points", band.points)
|
||||
@@ -274,11 +277,6 @@ def run_config_from_dict(payload: object) -> RunConfigModel:
|
||||
model.radar.laser_control.variation.delay_time = _read_int(
|
||||
laser_variation_payload, "delay_time", model.radar.laser_control.variation.delay_time
|
||||
)
|
||||
model.radar.laser_control.variation.temp_tolerance_c = _read_float(
|
||||
laser_variation_payload,
|
||||
"temp_tolerance_c",
|
||||
model.radar.laser_control.variation.temp_tolerance_c,
|
||||
)
|
||||
|
||||
load_switch_payload(port1_payload, model.output_switch)
|
||||
load_switch_payload(port2_payload, model.input_switch)
|
||||
@@ -410,200 +408,22 @@ def run_config_from_dict(payload: object) -> RunConfigModel:
|
||||
|
||||
|
||||
def run_config_to_dict(model: RunConfigModel) -> dict[str, Any]:
|
||||
"""Encode :class:`RunConfigModel` to C++ pipeline-compatible JSON structure."""
|
||||
"""Encode the model, spelling only fields whose wire names differ."""
|
||||
model.ensure_combos()
|
||||
sweep_payload = {
|
||||
"start_hz": model.radar.sweep.start_hz,
|
||||
"stop_hz": model.radar.sweep.stop_hz,
|
||||
"if_bandwidth_hz": model.radar.sweep.if_bandwidth_hz,
|
||||
"stimulus_power_dbm": model.radar.sweep.power_dbm,
|
||||
"dwell_us": model.radar.sweep.dwell_us,
|
||||
}
|
||||
if not model.is_kamil_adc:
|
||||
sweep_payload["points"] = model.radar.sweep.points
|
||||
|
||||
radar = asdict(model.radar)
|
||||
sweep = radar["sweep"]
|
||||
sweep["stimulus_power_dbm"] = sweep.pop("power_dbm")
|
||||
if model.is_rph_radar:
|
||||
sweep.pop("points")
|
||||
preprocess = asdict(model.preprocess)
|
||||
preprocess["notch"]["bands_hz"] = [list(band) for band in model.preprocess.notch.bands_hz]
|
||||
return {
|
||||
"radar": {
|
||||
"model": model.radar.model,
|
||||
"serial": model.radar.serial,
|
||||
"driver_mode": model.radar.driver_mode,
|
||||
"multi_device": {
|
||||
"slave_serials": list(model.radar.multi_device.slave_serials),
|
||||
"force_external_reference": model.radar.multi_device.force_external_reference,
|
||||
"recovery_attempts": model.radar.multi_device.recovery_attempts,
|
||||
"output_switch_positions": model.radar.multi_device.output_switch_positions,
|
||||
"input_switch_positions": model.radar.multi_device.input_switch_positions,
|
||||
"stimulus_switch_positions": model.radar.multi_device.stimulus_switch_positions
|
||||
},
|
||||
"kamil_adc": {
|
||||
"project_dir": model.radar.kamil_adc.project_dir,
|
||||
"executable_path": model.radar.kamil_adc.executable_path,
|
||||
"tty_path": model.radar.kamil_adc.tty_path,
|
||||
"args": list(model.radar.kamil_adc.args),
|
||||
"env": dict(model.radar.kamil_adc.env),
|
||||
"startup_timeout_s": model.radar.kamil_adc.startup_timeout_s,
|
||||
"sweep_timeout_s": model.radar.kamil_adc.sweep_timeout_s,
|
||||
"stop_timeout_s": model.radar.kamil_adc.stop_timeout_s,
|
||||
"phase_calibration": {
|
||||
"phase0_rad": model.radar.kamil_adc.phase_calibration.phase0_rad,
|
||||
"freq0_hz": model.radar.kamil_adc.phase_calibration.freq0_hz,
|
||||
"phase1_rad": model.radar.kamil_adc.phase_calibration.phase1_rad,
|
||||
"freq1_hz": model.radar.kamil_adc.phase_calibration.freq1_hz,
|
||||
},
|
||||
"band": {
|
||||
"start_hz": model.radar.kamil_adc.band.start_hz,
|
||||
"stop_hz": model.radar.kamil_adc.band.stop_hz,
|
||||
"points": model.radar.kamil_adc.band.points,
|
||||
},
|
||||
},
|
||||
"laser_control": {
|
||||
"enabled": model.radar.laser_control.enabled,
|
||||
"port": model.radar.laser_control.port,
|
||||
"mode": model.radar.laser_control.mode,
|
||||
"pi_coeff1_p": model.radar.laser_control.pi_coeff1_p,
|
||||
"pi_coeff1_i": model.radar.laser_control.pi_coeff1_i,
|
||||
"pi_coeff2_p": model.radar.laser_control.pi_coeff2_p,
|
||||
"pi_coeff2_i": model.radar.laser_control.pi_coeff2_i,
|
||||
"manual": {
|
||||
"temp1": model.radar.laser_control.manual.temp1,
|
||||
"temp2": model.radar.laser_control.manual.temp2,
|
||||
"current1": model.radar.laser_control.manual.current1,
|
||||
"current2": model.radar.laser_control.manual.current2,
|
||||
},
|
||||
"variation": {
|
||||
"variation_type": model.radar.laser_control.variation.variation_type,
|
||||
"static_temp1": model.radar.laser_control.variation.static_temp1,
|
||||
"static_temp2": model.radar.laser_control.variation.static_temp2,
|
||||
"static_current1": model.radar.laser_control.variation.static_current1,
|
||||
"static_current2": model.radar.laser_control.variation.static_current2,
|
||||
"min_value": model.radar.laser_control.variation.min_value,
|
||||
"max_value": model.radar.laser_control.variation.max_value,
|
||||
"step": model.radar.laser_control.variation.step,
|
||||
"time_step": model.radar.laser_control.variation.time_step,
|
||||
"delay_time": model.radar.laser_control.variation.delay_time,
|
||||
"temp_tolerance_c": model.radar.laser_control.variation.temp_tolerance_c,
|
||||
},
|
||||
},
|
||||
"sweep": sweep_payload,
|
||||
},
|
||||
"switches": {
|
||||
"port1": {
|
||||
"name": model.output_switch.name,
|
||||
"driver_mode": model.output_switch.driver_mode,
|
||||
"radar_port": model.output_switch.radar_port,
|
||||
"positions": model.output_switch.positions,
|
||||
"default_position": model.output_switch.default_position,
|
||||
"gpio_chip": model.output_switch.gpio_chip,
|
||||
"pin_a": model.output_switch.pin_a,
|
||||
"pin_b": model.output_switch.pin_b,
|
||||
},
|
||||
"port2": {
|
||||
"name": model.input_switch.name,
|
||||
"driver_mode": model.input_switch.driver_mode,
|
||||
"radar_port": model.input_switch.radar_port,
|
||||
"positions": model.input_switch.positions,
|
||||
"default_position": model.input_switch.default_position,
|
||||
"gpio_chip": model.input_switch.gpio_chip,
|
||||
"pin_a": model.input_switch.pin_a,
|
||||
"pin_b": model.input_switch.pin_b,
|
||||
},
|
||||
},
|
||||
"control_button": {
|
||||
"enabled": model.control_button.enabled,
|
||||
"gpio_chip": model.control_button.gpio_chip,
|
||||
"pin": model.control_button.pin,
|
||||
"active_low": model.control_button.active_low,
|
||||
"bias": model.control_button.bias,
|
||||
"debounce_ms": model.control_button.debounce_ms,
|
||||
"action": model.control_button.action,
|
||||
},
|
||||
"logging": {
|
||||
"level": model.logging.level,
|
||||
},
|
||||
"run": {
|
||||
"settling_ms": model.runtime.settling_ms,
|
||||
"idle_sleep_ms": model.runtime.idle_sleep_ms,
|
||||
"continuous": model.runtime.continuous,
|
||||
"processing_live_config_path": model.runtime.processing_live_config_path,
|
||||
"locator_server": {
|
||||
"device_id": model.runtime.locator_server.device_id,
|
||||
"protocol_version": model.runtime.locator_server.protocol_version,
|
||||
"host": model.runtime.locator_server.host,
|
||||
"port": model.runtime.locator_server.port,
|
||||
"max_payload_bytes": model.runtime.locator_server.max_payload_bytes,
|
||||
"client_queue_size": model.runtime.locator_server.client_queue_size,
|
||||
"logger_name": model.runtime.locator_server.logger_name,
|
||||
},
|
||||
"combos": [{"input": combo.input, "output": combo.output} for combo in model.combos],
|
||||
},
|
||||
"preprocess": {
|
||||
"s21": {
|
||||
"calibration": {
|
||||
"set_name": model.preprocess.s21.calibration.set_name,
|
||||
"bundle_path": model.preprocess.s21.calibration.bundle_path,
|
||||
},
|
||||
"reference": {
|
||||
"set_name": model.preprocess.s21.reference.set_name,
|
||||
"bundle_path": model.preprocess.s21.reference.bundle_path,
|
||||
},
|
||||
"object_calibration": {
|
||||
"set_name": model.preprocess.s21.object_calibration.set_name,
|
||||
"bundle_path": model.preprocess.s21.object_calibration.bundle_path,
|
||||
},
|
||||
},
|
||||
"notch": {
|
||||
"enabled": model.preprocess.notch.enabled,
|
||||
"bands_hz": [[low_hz, high_hz] for low_hz, high_hz in model.preprocess.notch.bands_hz],
|
||||
"taper_width_hz": model.preprocess.notch.taper_width_hz,
|
||||
"taper_type": model.preprocess.notch.taper_type,
|
||||
},
|
||||
},
|
||||
"gpr": {
|
||||
"relative_permittivity": model.gpr.relative_permittivity,
|
||||
"tx_geometry": [
|
||||
{
|
||||
"output_pos": entry.output_pos,
|
||||
"x_m": entry.x_m,
|
||||
"y_m": entry.y_m,
|
||||
"z_m": entry.z_m,
|
||||
}
|
||||
for entry in model.gpr.tx_geometry
|
||||
],
|
||||
"rx_geometry": [
|
||||
{
|
||||
"input_pos": entry.input_pos,
|
||||
"x_m": entry.x_m,
|
||||
"y_m": entry.y_m,
|
||||
"z_m": entry.z_m,
|
||||
}
|
||||
for entry in model.gpr.rx_geometry
|
||||
],
|
||||
},
|
||||
"rings": {
|
||||
"raw": {
|
||||
"name": model.rings.raw.name,
|
||||
"capacity": model.rings.raw.capacity,
|
||||
"slot_size_bytes": model.rings.raw.slot_size_bytes,
|
||||
},
|
||||
"raw_tap": {
|
||||
"name": model.rings.raw_tap.name,
|
||||
"capacity": model.rings.raw_tap.capacity,
|
||||
"slot_size_bytes": model.rings.raw_tap.slot_size_bytes,
|
||||
},
|
||||
"preprocessed": {
|
||||
"name": model.rings.preprocessed.name,
|
||||
"capacity": model.rings.preprocessed.capacity,
|
||||
"slot_size_bytes": model.rings.preprocessed.slot_size_bytes,
|
||||
},
|
||||
"preprocessed_tap": {
|
||||
"name": model.rings.preprocessed_tap.name,
|
||||
"capacity": model.rings.preprocessed_tap.capacity,
|
||||
"slot_size_bytes": model.rings.preprocessed_tap.slot_size_bytes,
|
||||
},
|
||||
"results": {
|
||||
"name": model.rings.results.name,
|
||||
"capacity": model.rings.results.capacity,
|
||||
"slot_size_bytes": model.rings.results.slot_size_bytes,
|
||||
},
|
||||
},
|
||||
"radar": radar,
|
||||
"switches": {"port1": asdict(model.output_switch), "port2": asdict(model.input_switch)},
|
||||
"control_button": asdict(model.control_button),
|
||||
"logging": asdict(model.logging),
|
||||
"run": {**asdict(model.runtime), "combos": [asdict(combo) for combo in model.combos]},
|
||||
"preprocess": preprocess,
|
||||
"gpr": asdict(model.gpr),
|
||||
"rings": asdict(model.rings),
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import asdict, dataclass, field
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
@@ -45,7 +45,7 @@ class RadarMultiDeviceModel:
|
||||
"""Multi-device LibreVNA topology settings."""
|
||||
|
||||
slave_serials: list[str] = field(default_factory=list)
|
||||
force_external_reference: bool = True
|
||||
force_external_reference: bool = False
|
||||
recovery_attempts: int = 3
|
||||
output_switch_positions: int = 1 # 1 = свитча нет
|
||||
input_switch_positions: int = 1 # 1 = свитча нет
|
||||
@@ -56,7 +56,7 @@ class RadarMultiDeviceModel:
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class KamilAdcPhaseCalibrationModel:
|
||||
class RphRadarPhaseCalibrationModel:
|
||||
"""Affine law mapping the reference signal's unwrapped phase to frequency.
|
||||
|
||||
Two fixed anchor points ``(phase0_rad, freq0_hz)`` and ``(phase1_rad, freq1_hz)``
|
||||
@@ -73,7 +73,7 @@ class KamilAdcPhaseCalibrationModel:
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class KamilAdcBandModel:
|
||||
class RphRadarBandModel:
|
||||
"""Fixed frequency window every sweep is cropped to and resampled onto.
|
||||
|
||||
Each sweep is resampled onto ``linspace(start_hz, stop_hz, points)`` so all
|
||||
@@ -88,8 +88,8 @@ class KamilAdcBandModel:
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class KamilAdcModel:
|
||||
"""External Kamil ADC acquisition process settings."""
|
||||
class RphRadarModel:
|
||||
"""External RadioPhotonic radar (RPH) acquisition process settings."""
|
||||
|
||||
project_dir: str = ""
|
||||
executable_path: str = ""
|
||||
@@ -99,10 +99,10 @@ class KamilAdcModel:
|
||||
startup_timeout_s: float = 5.0
|
||||
sweep_timeout_s: float = 5.0
|
||||
stop_timeout_s: float = 2.0
|
||||
phase_calibration: KamilAdcPhaseCalibrationModel = field(
|
||||
default_factory=KamilAdcPhaseCalibrationModel
|
||||
phase_calibration: RphRadarPhaseCalibrationModel = field(
|
||||
default_factory=RphRadarPhaseCalibrationModel
|
||||
)
|
||||
band: KamilAdcBandModel = field(default_factory=KamilAdcBandModel)
|
||||
band: RphRadarBandModel = field(default_factory=RphRadarBandModel)
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
@@ -129,14 +129,11 @@ class LaserVariationModeModel:
|
||||
step: float = 0.1
|
||||
time_step: int = 20
|
||||
delay_time: int = 3
|
||||
# Max allowed |measured - target| laser temperature before the temperature
|
||||
# checker warns, °C. Applied independently to both lasers (temp1/temp2).
|
||||
temp_tolerance_c: float = 0.03
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class LaserControlModel:
|
||||
"""Laser-control board settings applied before Kamil ADC acquisition."""
|
||||
"""Laser-control board settings applied before RadioPhotonic radar (RPH) acquisition."""
|
||||
|
||||
enabled: bool = False
|
||||
port: str = ""
|
||||
@@ -158,7 +155,7 @@ class RadarModel:
|
||||
driver_mode: str = "mock"
|
||||
sweep: RadarSweepModel = field(default_factory=RadarSweepModel)
|
||||
multi_device: RadarMultiDeviceModel = field(default_factory=RadarMultiDeviceModel)
|
||||
kamil_adc: KamilAdcModel = field(default_factory=KamilAdcModel)
|
||||
rph_radar: RphRadarModel = field(default_factory=RphRadarModel)
|
||||
laser_control: LaserControlModel = field(default_factory=LaserControlModel)
|
||||
|
||||
|
||||
@@ -337,7 +334,7 @@ class RunConfigModel:
|
||||
|
||||
LIBREVNA_MODEL = "librevna"
|
||||
LIBREVNA_MULTI_MODEL = "librevna_multi"
|
||||
KAMIL_ADC_MODEL = "kamil_adc"
|
||||
RPH_RADAR_MODEL = "rph_radar"
|
||||
MULTI_DEVICE_INPUT_POSITIONS = 4
|
||||
MULTI_DEVICE_OUTPUT_POSITIONS = 2
|
||||
# Stimulus ports the master itself owns. The virtual output axis is this times the
|
||||
@@ -400,72 +397,40 @@ class RunConfigModel:
|
||||
def build_runtime_combos(self) -> list[ComboModel]:
|
||||
"""Build the combo matrix from the effective switch axis sizes."""
|
||||
return self.build_full_combos(self.input_switch.positions, self.output_switch.positions)
|
||||
|
||||
|
||||
@property
|
||||
def is_kamil_adc(self) -> bool:
|
||||
"""Return whether this config targets the external Kamil ADC acquisition path."""
|
||||
return self.radar.model == self.KAMIL_ADC_MODEL
|
||||
def is_rph_radar(self) -> bool:
|
||||
"""Return whether this config targets the external RadioPhotonic radar (RPH) acquisition path."""
|
||||
return self.radar.model == self.RPH_RADAR_MODEL
|
||||
|
||||
def radar_key(self) -> str:
|
||||
"""Stable hardware/sweep identity used to find saved preprocess sets."""
|
||||
radar, sweep = self.radar, self.radar.sweep
|
||||
if self.is_rph_radar:
|
||||
laser = radar.laser_control
|
||||
mode = laser.mode.strip().lower()
|
||||
optical = asdict(laser.variation if mode == "variation" else laser.manual)
|
||||
physical = (asdict(radar.rph_radar.band), asdict(radar.rph_radar.phase_calibration),
|
||||
radar.rph_radar.args, (self.input_switch.positions, self.output_switch.positions),
|
||||
(mode, optical, laser.pi_coeff1_p, laser.pi_coeff1_i,
|
||||
laser.pi_coeff2_p, laser.pi_coeff2_i) if laser.enabled else None)
|
||||
digest = hashlib.sha256(json.dumps(physical, sort_keys=True).encode()).hexdigest()[:16]
|
||||
return f"{radar.model}_{digest}"
|
||||
serials = [radar.serial or "no_serial"]
|
||||
serials.extend(value.strip() or "no_serial" for value in self._radar_key_extra_parts())
|
||||
if self.is_multi_device:
|
||||
serials.extend(value.strip() or "no_serial" for value in radar.multi_device.slave_serials)
|
||||
serial = "_".join(sanitize_path_component(value) for value in serials)
|
||||
points = "adc" if self.is_kamil_adc else str(int(sweep.points))
|
||||
points = str(int(sweep.points))
|
||||
# Switch positions decide what each combo index means, so sets from another matrix never match.
|
||||
matrix = (f"_out{self.matrix_output_switch_positions}_in{self.matrix_input_switch_positions}"
|
||||
f"_stim{self.matrix_stimulus_switch_positions}") if self.is_multi_device else ""
|
||||
return (
|
||||
f"{radar.model}_{serial}"
|
||||
f"_st{_format_float_for_key(sweep.start_hz)}_sp{_format_float_for_key(sweep.stop_hz)}"
|
||||
f"_p{points}_if{_format_float_for_key(sweep.if_bandwidth_hz)}"
|
||||
f"_pw{_format_float_for_key(sweep.power_dbm)}"
|
||||
f"_pw{_format_float_for_key(sweep.power_dbm)}{matrix}"
|
||||
)
|
||||
|
||||
def _radar_key_extra_parts(self) -> list[str]:
|
||||
"""Return model-specific identity parts that affect captured data."""
|
||||
if self.is_multi_device:
|
||||
return list(self.radar.multi_device.slave_serials)
|
||||
if self.is_kamil_adc:
|
||||
payload = {
|
||||
"kamil_adc": {
|
||||
"project_dir": self.radar.kamil_adc.project_dir,
|
||||
"executable_path": self.radar.kamil_adc.executable_path,
|
||||
"tty_path": self.radar.kamil_adc.tty_path,
|
||||
"args": list(self.radar.kamil_adc.args),
|
||||
"env": dict(sorted(self.radar.kamil_adc.env.items())),
|
||||
},
|
||||
"laser_control": {
|
||||
"enabled": self.radar.laser_control.enabled,
|
||||
"port": self.radar.laser_control.port,
|
||||
"mode": self.radar.laser_control.mode,
|
||||
"pi_coeff1_p": self.radar.laser_control.pi_coeff1_p,
|
||||
"pi_coeff1_i": self.radar.laser_control.pi_coeff1_i,
|
||||
"pi_coeff2_p": self.radar.laser_control.pi_coeff2_p,
|
||||
"pi_coeff2_i": self.radar.laser_control.pi_coeff2_i,
|
||||
"manual": {
|
||||
"temp1": self.radar.laser_control.manual.temp1,
|
||||
"temp2": self.radar.laser_control.manual.temp2,
|
||||
"current1": self.radar.laser_control.manual.current1,
|
||||
"current2": self.radar.laser_control.manual.current2,
|
||||
},
|
||||
"variation": {
|
||||
"variation_type": self.radar.laser_control.variation.variation_type,
|
||||
"static_temp1": self.radar.laser_control.variation.static_temp1,
|
||||
"static_temp2": self.radar.laser_control.variation.static_temp2,
|
||||
"static_current1": self.radar.laser_control.variation.static_current1,
|
||||
"static_current2": self.radar.laser_control.variation.static_current2,
|
||||
"min_value": self.radar.laser_control.variation.min_value,
|
||||
"max_value": self.radar.laser_control.variation.max_value,
|
||||
"step": self.radar.laser_control.variation.step,
|
||||
"time_step": self.radar.laser_control.variation.time_step,
|
||||
"delay_time": self.radar.laser_control.variation.delay_time,
|
||||
},
|
||||
},
|
||||
}
|
||||
encoded = json.dumps(payload, sort_keys=True, separators=(",", ":"))
|
||||
digest = hashlib.sha256(encoded.encode("utf-8")).hexdigest()[:16]
|
||||
return [f"kamil_{digest}"]
|
||||
return []
|
||||
|
||||
def apply_device_model_constraints(self) -> None:
|
||||
"""Apply only required wire-format constraints for the selected device model."""
|
||||
if not self.is_multi_device:
|
||||
|
||||
@@ -20,6 +20,7 @@ from python_app.models.run_config_schema import (
|
||||
GprModel,
|
||||
GprRxGeometryModel,
|
||||
GprTxGeometryModel,
|
||||
RphRadarModel,
|
||||
RadarSweepModel,
|
||||
RingEndpointModel,
|
||||
RunConfigModel,
|
||||
@@ -129,12 +130,15 @@ def validate_acquisition_config(config: RunConfigModel) -> None:
|
||||
if mode not in ("mock", "native"):
|
||||
raise ValueError("radar.driver_mode must be mock or native")
|
||||
if config.radar.model not in (
|
||||
config.LIBREVNA_MODEL, config.LIBREVNA_MULTI_MODEL, config.KAMIL_ADC_MODEL,
|
||||
config.LIBREVNA_MODEL, config.LIBREVNA_MULTI_MODEL, config.RPH_RADAR_MODEL,
|
||||
):
|
||||
raise ValueError(f"Unsupported radar model: {config.radar.model}")
|
||||
if mode != "native" and config.is_kamil_adc:
|
||||
if mode != "native" and config.is_rph_radar:
|
||||
raise ValueError(f"{config.radar.model} requires native driver mode")
|
||||
|
||||
if config.is_rph_radar:
|
||||
validate_rph_radar_model(config.radar.rph_radar)
|
||||
|
||||
for switch, physical_positions in (
|
||||
(config.input_switch, config.matrix_input_switch_positions),
|
||||
(config.output_switch, config.matrix_output_switch_positions),
|
||||
@@ -155,7 +159,7 @@ def validate_acquisition_config(config: RunConfigModel) -> None:
|
||||
if not switch.gpio_chip or switch.pin_a < 0 or switch.pin_b < 0 or switch.pin_b == switch.pin_a:
|
||||
raise ValueError(f"{context}: GPIO chip and distinct nonnegative control pins are required")
|
||||
|
||||
points = config.radar.kamil_adc.band.points if config.is_kamil_adc else config.radar.sweep.points
|
||||
points = config.radar.rph_radar.band.points if config.is_rph_radar else config.radar.sweep.points
|
||||
# RAW3/PRP3 header, frequency/S21 arrays and capture windows.
|
||||
minimum = 44 + len(config.combos) * (28 + 12 * points)
|
||||
for name in ("raw", "raw_tap", "preprocessed", "preprocessed_tap"):
|
||||
@@ -166,6 +170,24 @@ def validate_acquisition_config(config: RunConfigModel) -> None:
|
||||
raise ValueError(f"rings.{name}.slot_size_bytes must be at least {minimum} for this sweep")
|
||||
|
||||
|
||||
def validate_rph_radar_model(adc: RphRadarModel) -> None:
|
||||
"""Reject phase/grid errors before stopping a running pipeline."""
|
||||
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):
|
||||
raise ValueError(f"radar.rph_radar.{name} must be finite")
|
||||
if phase.phase0_rad == phase.phase1_rad or phase.freq0_hz == phase.freq1_hz:
|
||||
raise ValueError("radar.rph_radar.phase_calibration requires distinct phases and frequencies")
|
||||
if not 0 <= band.start_hz < band.stop_hz <= 3.4e38:
|
||||
raise ValueError("radar.rph_radar.band requires 0 <= start_hz < stop_hz within float32")
|
||||
if not isinstance(band.points, int) or not 2 <= band.points <= _UINT32_MAX:
|
||||
raise ValueError("radar.rph_radar.band.points must be an integer in [2, uint32 max]")
|
||||
for name in ("startup_timeout_s", "sweep_timeout_s", "stop_timeout_s"):
|
||||
value = getattr(adc, name)
|
||||
if not math.isfinite(value) or value <= 0:
|
||||
raise ValueError(f"radar.rph_radar.{name} must be finite and positive")
|
||||
|
||||
|
||||
def validate_sweep_model(sweep: RadarSweepModel) -> None:
|
||||
"""Validate radar sweep bounds (point count and frequency span).
|
||||
|
||||
|
||||
Reference in New Issue
Block a user