Add real switch support for multi-device matrix radar

MultiDeviceLibreVnaService only exposes the 2x4 virtual combo matrix on
its own USB transport. When a physical GPIO switch sits on the master
stimulus and/or slave receiver path, the effective matrix is wider than
that. SwitchedMatrixRadarService wraps the inner service and drives the
extra switch(es) between acquire_collection calls, widening the combo
matrix by the physical position counts (matrix_output_switch_positions /
matrix_input_switch_positions in RunConfigModel).

Combo-matrix construction was centralized into
RunConfigModel.build_runtime_combos() so the GUI, workflows, and codec
all derive the same widened matrix instead of each computing its own
version of the virtual 2x4 layout.
This commit is contained in:
2026-07-29 17:44:28 +03:00
parent f967da7f53
commit 68bec25f17
11 changed files with 341 additions and 32 deletions
+12
View File
@@ -219,6 +219,16 @@ def run_config_from_dict(payload: dict[str, Any]) -> RunConfigModel:
"recovery_attempts",
model.radar.multi_device.recovery_attempts,
)
model.radar.multi_device.output_switch_positions = _read_int(
multi_device_payload,
"output_switch_positions",
model.radar.multi_device.output_switch_positions,
)
model.radar.multi_device.input_switch_positions = _read_int(
multi_device_payload,
"input_switch_positions",
model.radar.multi_device.input_switch_positions,
)
model.radar.kamil_adc.project_dir = _read_str(
kamil_adc_payload, "project_dir", model.radar.kamil_adc.project_dir
)
@@ -493,6 +503,8 @@ def run_config_to_dict(model: RunConfigModel) -> dict[str, Any]:
"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
},
"kamil_adc": {
"project_dir": model.radar.kamil_adc.project_dir,
+27 -4
View File
@@ -40,6 +40,8 @@ class RadarMultiDeviceModel:
slave_serials: list[str] = field(default_factory=list)
force_external_reference: bool = True
recovery_attempts: int = 3
output_switch_positions: int = 1 # 1 = свитча нет
input_switch_positions: int = 1 # 1 = свитча нет
@dataclass(slots=True)
@@ -387,6 +389,24 @@ class RunConfigModel:
"""Return whether this config acquires the full virtual switch matrix per sweep."""
return self.is_multi_device or self.is_sn9000
@property
def matrix_output_switch_positions(self) -> int:
"""Physical positions of the real switch on the master stimulus path."""
if not self.is_multi_device:
return 1
return max(1, int(self.radar.multi_device.output_switch_positions))
@property
def matrix_input_switch_positions(self) -> int:
"""Physical positions of the real switch on the slave receiver path."""
if not self.is_multi_device:
return 1
return max(1, int(self.radar.multi_device.input_switch_positions))
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."""
@@ -443,22 +463,25 @@ class RunConfigModel:
if not self.is_matrix_radar:
return
self._apply_matrix_virtual_switches()
self.combos = self.build_matrix_radar_virtual_combos()
self.combos = self.build_runtime_combos()
def _apply_matrix_virtual_switches(self) -> None:
"""Pin the canonical 2x4 virtual switch matrix used by all matrix-mode radars."""
"""Pin the virtual switch matrix, widened by any real switch on the path."""
out_physical = self.matrix_output_switch_positions
in_physical = self.matrix_input_switch_positions
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.positions = out_physical * 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.positions = in_physical * self.MULTI_DEVICE_INPUT_POSITIONS
self.input_switch.default_position = 0
def ensure_combos(self) -> None: