Files
2026-10-06 12:32:26 +03:00

88 lines
3.4 KiB
Python

"""VNA high-level controller for direct protocol packets."""
from __future__ import annotations
from dataclasses import replace
import logging
from ..enums import PacketType, SyncMode
from ..exceptions import ParseError
from ..models import Packet, SweepResult, VNADatapointPacket, VNASweepSettings
from ..session import LibreVNASession
from ..sweep.assembler import assemble_vna_sweep
logger = logging.getLogger(__name__)
class VNAController:
"""VNA operations built on direct packet protocol."""
def __init__(self, session: LibreVNASession) -> None:
"""Bind VNA controller to active session."""
self._session = session
self._settings: VNASweepSettings | None = None
def configure(self, settings: VNASweepSettings) -> None:
"""Send `SweepSettings` packet to configure VNA operation."""
effective = settings
if effective.sync_mode is None:
effective = replace(effective, sync_mode=SyncMode.DISABLED)
self._settings = effective
self._session.clear_queue(PacketType.VNA_DATAPOINT)
self._session.send(Packet(PacketType.SWEEP_SETTINGS, effective), require_ack=True)
logger.info(
"VNA configured: kind=%s start=%.3fHz stop=%.3fHz points=%d ifbw=%.3fHz",
effective.kind.value,
effective.f_start_hz,
effective.f_stop_hz,
effective.points,
effective.if_bandwidth_hz,
)
def acquire(self, *, expected_points: int | None = None, timeout_s: float | None = None) -> SweepResult:
"""Acquire one complete sweep and return assembled complex traces."""
if self._settings is None:
raise ParseError("VNA is not configured. Call vna.configure() before acquire().")
settings = self._settings
points_target = expected_points if expected_points is not None else settings.points
if points_target <= 0:
raise ValueError("expected_points must be > 0")
# Firmware integrates for 1/IFBW after dwell/PLL settling at every point.
# Allow a discarded free-running tail and USB/host overhead, but detect
# a stalled instrument independently of this whole-sweep budget.
point_seconds = 1.0 / settings.if_bandwidth_hz + settings.dwell_s + 0.001
if timeout_s is None:
timeout_s = max(10.0, 2.0 * points_target * point_seconds + 2.0)
logger.info(
"VNA acquire start: expected_points=%d timeout=%.2fs standby=%s",
points_target,
timeout_s,
settings.standby,
)
self._session.clear_queue(PacketType.VNA_DATAPOINT)
if settings.standby:
self._session.send(Packet(PacketType.INITIATE_SWEEP), require_ack=True)
ordered = self._session.collect_indexed_payloads(
packet_type=PacketType.VNA_DATAPOINT,
expected_points=points_target,
timeout_s=timeout_s,
progress_timeout_s=max(1.5, 3.0 * point_seconds),
payload_type=VNADatapointPacket,
payload_error="Expected decoded VNADatapointPacket payload",
)
device_info = self._session.get_device_info()
result = assemble_vna_sweep(
ordered,
settings,
num_ports=device_info.num_ports,
expected_points=points_target,
)
logger.info("VNA acquire complete: received_points=%d", len(result.x))
return result