Rework radar system

This commit is contained in:
Ayzen
2026-10-06 12:32:26 +03:00
parent 552cdc688a
commit 5bb86b7165
299 changed files with 13537 additions and 27763 deletions
+28 -31
View File
@@ -9,6 +9,8 @@ import logging
from pathlib import Path
from typing import Any
from python_app.file_io import sanitize_path_component
logger = logging.getLogger(__name__)
MAX_COMBOS = 4096
@@ -186,14 +188,12 @@ class SwitchModel:
name: str
driver_mode: str = "mock"
driver: str = ""
radar_port: int = 0
positions: int = 1
default_position: int = 0
gpio_chip: str = ""
pin_a: int = -1
pin_b: int = -1
invert_logic: bool = False
@dataclass(slots=True)
@@ -278,23 +278,6 @@ class S21PreprocessModel:
object_calibration: PreprocessAssetModel = field(default_factory=PreprocessAssetModel)
@dataclass(slots=True)
class S11CalibrationModel:
"""One-port S11 OSL calibration assets."""
open: PreprocessAssetModel = field(default_factory=PreprocessAssetModel)
short: PreprocessAssetModel = field(default_factory=PreprocessAssetModel)
load: PreprocessAssetModel = field(default_factory=PreprocessAssetModel)
@dataclass(slots=True)
class S11PreprocessModel:
"""One-port S11 preprocessing assets."""
calibration: S11CalibrationModel = field(default_factory=S11CalibrationModel)
reference: PreprocessAssetModel = field(default_factory=PreprocessAssetModel)
@dataclass(slots=True)
class PreprocessNotchModel:
"""Optional frequency-domain notch filter applied after calibration and reference subtraction."""
@@ -310,7 +293,6 @@ class PreprocessModel:
"""Selected preprocessing artifacts for live acquisition."""
s21: S21PreprocessModel = field(default_factory=S21PreprocessModel)
s11: S11PreprocessModel = field(default_factory=S11PreprocessModel)
notch: PreprocessNotchModel = field(default_factory=PreprocessNotchModel)
@@ -402,11 +384,6 @@ class RunConfigModel:
"""Return whether this config targets synchronized multi-device acquisition."""
return self.radar.model == self.LIBREVNA_MULTI_MODEL
@property
def is_matrix_radar(self) -> bool:
"""Return whether this config acquires the full virtual switch matrix per sweep."""
return self.is_multi_device
@property
def matrix_output_switch_positions(self) -> int:
"""Physical positions of the real switch on the master stimulus path."""
@@ -449,7 +426,21 @@ class RunConfigModel:
"""Return whether this config targets the external Kamil ADC acquisition path."""
return self.radar.model == self.KAMIL_ADC_MODEL
def radar_key_extra_parts(self) -> list[str]:
def radar_key(self) -> str:
"""Stable hardware/sweep identity used to find saved preprocess sets."""
radar, sweep = self.radar, self.radar.sweep
serials = [radar.serial or "no_serial"]
serials.extend(value.strip() or "no_serial" for value in self._radar_key_extra_parts())
serial = "_".join(sanitize_path_component(value) for value in serials)
points = "adc" if self.is_kamil_adc else str(int(sweep.points))
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)}"
)
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)
@@ -497,7 +488,7 @@ class RunConfigModel:
def apply_device_model_constraints(self) -> None:
"""Apply only required wire-format constraints for the selected device model."""
if not self.is_matrix_radar:
if not self.is_multi_device:
return
self._apply_matrix_virtual_switches()
self.combos = self.build_runtime_combos()
@@ -509,21 +500,19 @@ class RunConfigModel:
self.output_switch.name = self.output_switch.name or "virtual_output"
self.output_switch.driver_mode = "mock"
self.output_switch.driver = self.output_switch.driver or "h7992"
self.output_switch.radar_port = 1
self.output_switch.positions = out_physical * self.matrix_virtual_output_positions
self.output_switch.default_position = 0
self.input_switch.name = self.input_switch.name or "virtual_input"
self.input_switch.driver_mode = "mock"
self.input_switch.driver = self.input_switch.driver or "h7992"
self.input_switch.radar_port = 2
self.input_switch.positions = in_physical * self.MULTI_DEVICE_INPUT_POSITIONS
self.input_switch.default_position = 0
def ensure_combos(self) -> None:
"""Populate combos with full matrix when no explicit run combos are set."""
if self.is_matrix_radar:
if self.is_multi_device:
self.apply_device_model_constraints()
return
if self.combos:
@@ -531,7 +520,7 @@ class RunConfigModel:
self.combos = self.build_full_combos(self.input_switch.positions, self.output_switch.positions)
@classmethod
def from_dict(cls, payload: dict[str, Any]) -> RunConfigModel:
def from_dict(cls, payload: object) -> RunConfigModel:
"""Build model from JSON-like payload using codec layer."""
from python_app.models.run_config_codec import run_config_from_dict
@@ -558,3 +547,11 @@ class RunConfigModel:
from python_app.models.run_config_codec import run_config_to_dict
return run_config_to_dict(self)
def _format_float_for_key(value: float) -> str:
"""Format float compactly for deterministic key serialization."""
integer = int(round(value))
if abs(value - float(integer)) < 1e-6:
return str(integer)
return f"{value:.6f}".rstrip("0").rstrip(".")