Files
radar_system/python_app/hardware_full/matrix_radar_service.py
T
AyzenandClaude Opus 5 6693ece0aa per-point stimulus switching: host side
Матрица 4x4 за одну развёртку: каждый порт мастера измеряется на каждой частоте
дважды, с двумя положениями выходного ключа. Ключ дёргает контроллер в
триггерной петле, поэтому расбери переключателями больше не управляет —
SwitchedMatrixRadarService уходит из тракта, все 16 комбинаций снимаются в
пределах одной точки вместо разброса между тремя развёртками.

Выходы нумеруются позиция-мажорно, стадии порт-мажорно (старшие биты счётчика
стадий выбирают порт, младший — положение ключа). Из этого следует, что
положение ключа = младший бит номера стадии, а период 2 нацело делит 4 стадии —
поэтому контроллеру достаточно инвертировать ногу на каждый цикл триггера, без
абсолютного счётчика. Выходы 0 и 1 при этом сохраняют прежний смысл.

Приёмники слейвов пронумерованы от числа выходов + 1, а не от фиксированного 3:
иначе при 4 выходах прямой член «приёмник 3 <- выход 3» назывался бы так же, как
отражение выхода 3. При двух выходах даёт прежние порты 3..6.

Слейвам порт-стадии выставляются в 7: стоковый приём «одна за последней стадией»
перестаёт работать, когда порт выбирается старшими битами.

Всё за флагом multi_device.stimulus_switch_positions (1 = прежнее поведение,
байт в байт). Спецификация правок FPGA/STM в device_firmware/per_point_switching.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 21:26:56 +03:00

89 lines
3.6 KiB
Python

"""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,
switch_substage_count=config.matrix_stimulus_switch_positions,
)
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=config.matrix_virtual_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}")