added multidevice support
This commit is contained in:
@@ -53,6 +53,7 @@ def run_config_from_dict(payload: dict[str, Any]) -> RunConfigModel:
|
||||
run_payload.get("locator_server", run_payload.get("locator")),
|
||||
"run.locator_server",
|
||||
)
|
||||
multi_device_payload = _as_dict(radar_payload.get("multi_device"), "radar.multi_device")
|
||||
|
||||
model.radar.model = str(radar_payload.get("model", model.radar.model))
|
||||
model.radar.serial = str(radar_payload.get("serial", model.radar.serial))
|
||||
@@ -66,9 +67,34 @@ def run_config_from_dict(payload: dict[str, Any]) -> RunConfigModel:
|
||||
sweep_payload.get("if_bandwidth_hz", model.radar.sweep.if_bandwidth_hz)
|
||||
)
|
||||
model.radar.sweep.power_dbm = float(sweep_payload.get("stimulus_power_dbm", model.radar.sweep.power_dbm))
|
||||
slave_serials_payload = multi_device_payload.get(
|
||||
"slave_serials",
|
||||
multi_device_payload.get("slave_serial_numbers", model.radar.multi_device.slave_serials),
|
||||
)
|
||||
if isinstance(slave_serials_payload, list):
|
||||
model.radar.multi_device.slave_serials = [str(value).strip() for value in slave_serials_payload if str(value).strip()]
|
||||
elif isinstance(slave_serials_payload, str):
|
||||
model.radar.multi_device.slave_serials = [
|
||||
value.strip()
|
||||
for value in slave_serials_payload.split(",")
|
||||
if value.strip()
|
||||
]
|
||||
model.radar.multi_device.force_external_reference = bool(
|
||||
multi_device_payload.get(
|
||||
"force_external_reference",
|
||||
model.radar.multi_device.force_external_reference,
|
||||
)
|
||||
)
|
||||
model.radar.multi_device.recovery_attempts = int(
|
||||
multi_device_payload.get(
|
||||
"recovery_attempts",
|
||||
model.radar.multi_device.recovery_attempts,
|
||||
)
|
||||
)
|
||||
|
||||
load_switch_payload(port1_payload, model.output_switch)
|
||||
load_switch_payload(port2_payload, model.input_switch)
|
||||
model.apply_device_model_constraints()
|
||||
|
||||
model.runtime.settling_ms = int(run_payload.get("settling_ms", model.runtime.settling_ms))
|
||||
model.runtime.idle_sleep_ms = int(run_payload.get("idle_sleep_ms", model.runtime.idle_sleep_ms))
|
||||
@@ -177,6 +203,7 @@ def run_config_from_dict(payload: dict[str, Any]) -> RunConfigModel:
|
||||
x_m=float(entry_payload.get("x_m", 0.0)),
|
||||
)
|
||||
)
|
||||
model.apply_device_model_constraints()
|
||||
validate_gpr_model(
|
||||
model.gpr,
|
||||
input_switch_positions=model.input_switch.positions,
|
||||
@@ -214,6 +241,11 @@ def run_config_to_dict(model: RunConfigModel) -> dict[str, Any]:
|
||||
"serial": model.radar.serial,
|
||||
"driver_mode": model.radar.driver_mode,
|
||||
"mock_signal_hz": model.radar.mock_signal_hz,
|
||||
"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,
|
||||
},
|
||||
"sweep": {
|
||||
"start_hz": model.radar.sweep.start_hz,
|
||||
"stop_hz": model.radar.sweep.stop_hz,
|
||||
|
||||
@@ -10,6 +10,7 @@ from python_app.models.run_config_schema import (
|
||||
PreprocessAssetModel,
|
||||
PreprocessNotchModel,
|
||||
PreprocessModel,
|
||||
RadarMultiDeviceModel,
|
||||
RadarModel,
|
||||
RadarSweepModel,
|
||||
RingEndpointModel,
|
||||
@@ -36,6 +37,7 @@ __all__ = [
|
||||
"PreprocessAssetModel",
|
||||
"PreprocessNotchModel",
|
||||
"PreprocessModel",
|
||||
"RadarMultiDeviceModel",
|
||||
"RadarModel",
|
||||
"RadarSweepModel",
|
||||
"RingEndpointModel",
|
||||
|
||||
@@ -28,15 +28,25 @@ class RadarSweepModel:
|
||||
power_dbm: float = -30.0
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class RadarMultiDeviceModel:
|
||||
"""Multi-device LibreVNA topology settings."""
|
||||
|
||||
slave_serials: list[str] = field(default_factory=list)
|
||||
force_external_reference: bool = True
|
||||
recovery_attempts: int = 3
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class RadarModel:
|
||||
"""Radar section of run configuration."""
|
||||
|
||||
model: str = ""
|
||||
model: str = "librevna"
|
||||
serial: str = ""
|
||||
driver_mode: str = "mock"
|
||||
mock_signal_hz: float = 1_000_000.0
|
||||
sweep: RadarSweepModel = field(default_factory=RadarSweepModel)
|
||||
multi_device: RadarMultiDeviceModel = field(default_factory=RadarMultiDeviceModel)
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
@@ -190,6 +200,11 @@ class RunConfigModel:
|
||||
gpr: GprModel = field(default_factory=GprModel)
|
||||
combos: list[ComboModel] = field(default_factory=list)
|
||||
|
||||
LIBREVNA_MODEL = "librevna"
|
||||
LIBREVNA_MULTI_MODEL = "librevna_multi"
|
||||
MULTI_DEVICE_INPUT_POSITIONS = 4
|
||||
MULTI_DEVICE_OUTPUT_POSITIONS = 2
|
||||
|
||||
@staticmethod
|
||||
def build_full_combos(input_positions: int, output_positions: int) -> list[ComboModel]:
|
||||
"""Build full Cartesian product of input/output switch positions."""
|
||||
@@ -199,8 +214,45 @@ class RunConfigModel:
|
||||
for input_pos in range(input_positions)
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def build_multi_device_virtual_combos(cls) -> list[ComboModel]:
|
||||
"""Build fixed virtual combo matrix for one master and two slave devices."""
|
||||
return cls.build_full_combos(
|
||||
cls.MULTI_DEVICE_INPUT_POSITIONS,
|
||||
cls.MULTI_DEVICE_OUTPUT_POSITIONS,
|
||||
)
|
||||
|
||||
@property
|
||||
def is_multi_device(self) -> bool:
|
||||
"""Return whether this config targets synchronized multi-device acquisition."""
|
||||
return self.radar.model == self.LIBREVNA_MULTI_MODEL
|
||||
|
||||
def apply_device_model_constraints(self) -> None:
|
||||
"""Apply only required wire-format constraints for the selected device model."""
|
||||
if not self.is_multi_device:
|
||||
return
|
||||
|
||||
self.radar.model = self.LIBREVNA_MULTI_MODEL
|
||||
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 = self.MULTI_DEVICE_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 = self.MULTI_DEVICE_INPUT_POSITIONS
|
||||
self.input_switch.default_position = 0
|
||||
self.combos = self.build_multi_device_virtual_combos()
|
||||
|
||||
def ensure_combos(self) -> None:
|
||||
"""Populate combos with full matrix when no explicit run combos are set."""
|
||||
if self.is_multi_device:
|
||||
self.apply_device_model_constraints()
|
||||
return
|
||||
if self.combos:
|
||||
return
|
||||
self.combos = self.build_full_combos(self.input_switch.positions, self.output_switch.positions)
|
||||
|
||||
Reference in New Issue
Block a user