"""Factory for matrix-mode (multi-port) radar acquisition services. Matrix radars acquire the full virtual switch matrix per call via ``acquire_collection`` instead of one combo per ``acquire`` like single-radar services. The supported models are LibreVNA in synchronized multi-device mode and the PLANAR SN9000 multi-port analyzer. Service implementations are imported lazily so a config that targets one model does not pull in transport dependencies (libusb, pyvisa, etc.) needed only by the other model. """ from __future__ import annotations from typing import Protocol from python_app.models.dataset_model import SweepCollection from python_app.models.run_config_model import RadarSweepModel, RunConfigModel class MatrixRadarService(Protocol): """Common API for radars that emit a full combo matrix per acquisition.""" def open(self) -> None: """Open hardware connections.""" def close(self) -> None: """Close hardware connections.""" def configure(self, sweep: RadarSweepModel) -> None: """Apply sweep settings to the radar.""" def acquire_collection(self, collection_id: int = 1) -> SweepCollection: """Acquire one complete virtual switch matrix in canonical combo order.""" def recover(self) -> None: """Reconnect to the radar after a transient acquisition failure.""" def create_matrix_radar_service(config: RunConfigModel) -> MatrixRadarService: """Create the matrix radar service implementation for the active config.""" if not config.is_matrix_radar: raise RuntimeError("matrix radar service factory requires a matrix-mode radar.model") model = config.radar.model if model == RunConfigModel.LIBREVNA_MULTI_MODEL: from python_app.hardware_full.multi_device_service import MultiDeviceLibreVnaService inner = MultiDeviceLibreVnaService( master_serial=config.radar.serial, slave_serials=list(config.radar.multi_device.slave_serials), force_external_reference=config.radar.multi_device.force_external_reference, recovery_attempts=config.radar.multi_device.recovery_attempts, backend_mode=config.radar.driver_mode, ) out_physical = config.matrix_output_switch_positions in_physical = config.matrix_input_switch_positions if out_physical <= 1 and in_physical <= 1: return inner from python_app.hardware_full.switched_matrix_radar_service import ( SwitchedMatrixRadarService, build_physical_switch, ) return SwitchedMatrixRadarService( inner=inner, output_switch=build_physical_switch(config.output_switch, out_physical, config.radar.driver_mode), input_switch=build_physical_switch(config.input_switch, in_physical, config.radar.driver_mode), inner_output_positions=RunConfigModel.MULTI_DEVICE_OUTPUT_POSITIONS, inner_input_positions=RunConfigModel.MULTI_DEVICE_INPUT_POSITIONS, settling_ms=config.runtime.settling_ms, ) if model == RunConfigModel.SN9000_MODEL: if config.radar.driver_mode != "native": raise RuntimeError("SN9000 requires radar.driver_mode='native'") from python_app.hardware_full.sn9000_service import Sn9000Service visa_library = config.radar.visa_library or "@ivi" return Sn9000Service( host=config.radar.remote_host, port=config.radar.remote_port, visa_library=visa_library, ) raise RuntimeError(f"Unsupported matrix radar model: {model}")