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.
113 lines
3.5 KiB
Python
113 lines
3.5 KiB
Python
"""High-level switch service selecting native/mock backend driver."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import TYPE_CHECKING
|
|
|
|
from python_app.hardware_full.switch_drivers import (
|
|
H7992Driver,
|
|
HMC349ADriver,
|
|
MockSwitchDriver,
|
|
SwitchDriverProtocol,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from python_app.models.run_config_schema import SwitchModel
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class SwitchService:
|
|
"""Facade around concrete switch drivers."""
|
|
|
|
name: str
|
|
positions: int
|
|
mode: str = "mock"
|
|
driver: str = "h7992"
|
|
gpio_chip: str = "/dev/gpiochip0"
|
|
pin_a: int = 17
|
|
pin_b: int = 27
|
|
invert_logic: bool = False
|
|
default_position: int = 0
|
|
_driver: SwitchDriverProtocol = field(init=False, repr=False)
|
|
|
|
def __post_init__(self) -> None:
|
|
"""Create underlying driver based on configured mode and type."""
|
|
self._driver = self._build_driver()
|
|
|
|
@classmethod
|
|
def from_model(cls, model: SwitchModel) -> SwitchService:
|
|
"""Build a switch service from a ``SwitchModel`` config section.
|
|
|
|
The single construction path shared by every acquisition producer and
|
|
capture workflow, so all devices drive switches identically.
|
|
"""
|
|
return cls(
|
|
name=model.name,
|
|
positions=model.positions,
|
|
mode=model.driver_mode,
|
|
driver=model.driver,
|
|
gpio_chip=model.gpio_chip,
|
|
pin_a=model.pin_a,
|
|
pin_b=model.pin_b,
|
|
invert_logic=model.invert_logic,
|
|
default_position=model.default_position,
|
|
)
|
|
|
|
def open(self) -> None:
|
|
"""Open underlying switch driver resources."""
|
|
self._driver.open()
|
|
|
|
def close(self) -> None:
|
|
"""Close underlying switch driver resources."""
|
|
self._driver.close()
|
|
|
|
def switch_to(self, position: int) -> None:
|
|
"""Switch to requested position."""
|
|
self._driver.switch_to(position)
|
|
|
|
def position_count(self) -> int:
|
|
"""Return number of positions supported by the backend driver."""
|
|
return self._driver.position_count()
|
|
|
|
@property
|
|
def current_position(self) -> int:
|
|
"""Return current switch position reported by backend driver."""
|
|
return self._driver.current_position
|
|
|
|
def _build_driver(self) -> SwitchDriverProtocol:
|
|
"""Instantiate concrete driver according to configured mode and driver kind."""
|
|
mode = self.mode.strip().lower()
|
|
driver_kind = self.driver.strip().lower()
|
|
|
|
if mode == "mock":
|
|
return MockSwitchDriver(
|
|
name=self.name,
|
|
positions=self.positions,
|
|
default_position=self.default_position,
|
|
)
|
|
|
|
if mode != "native":
|
|
raise RuntimeError(f"Unsupported switch mode: {self.mode}")
|
|
|
|
if driver_kind == "h7992":
|
|
return H7992Driver(
|
|
name=self.name,
|
|
positions=self.positions,
|
|
default_position=self.default_position,
|
|
gpio_chip=self.gpio_chip,
|
|
pin_a=self.pin_a,
|
|
pin_b=self.pin_b,
|
|
)
|
|
if driver_kind == "hmc349a":
|
|
return HMC349ADriver(
|
|
name=self.name,
|
|
positions=self.positions,
|
|
default_position=self.default_position,
|
|
gpio_chip=self.gpio_chip,
|
|
pin_a=self.pin_a,
|
|
invert_logic=self.invert_logic,
|
|
)
|
|
|
|
raise RuntimeError(f"Unsupported switch driver: {self.driver}")
|