init commit
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
"""Public controller classes."""
|
||||
|
||||
from .config import ConfigController
|
||||
from .vna import VNAController
|
||||
|
||||
__all__ = [
|
||||
"ConfigController",
|
||||
"VNAController",
|
||||
]
|
||||
@@ -0,0 +1,54 @@
|
||||
"""Device configuration controller."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from ..enums import PacketType
|
||||
from ..exceptions import ParseError
|
||||
from ..models import DeviceConfigVariant, Packet
|
||||
from ..protocol import parse_device_config
|
||||
from ..session import LibreVNASession
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ConfigController:
|
||||
"""Device configuration read/write/reset operations."""
|
||||
|
||||
def __init__(self, session: LibreVNASession) -> None:
|
||||
"""Bind controller to an active session."""
|
||||
self._session = session
|
||||
|
||||
def get(self, *, timeout_s: float = 1.0) -> DeviceConfigVariant:
|
||||
"""Read configuration block for active hardware family."""
|
||||
packet = self._session.request(
|
||||
PacketType.REQUEST_DEVICE_CONFIGURATION,
|
||||
PacketType.DEVICE_CONFIGURATION,
|
||||
timeout_s=timeout_s,
|
||||
)
|
||||
if not isinstance(packet.payload, (bytes, bytearray, memoryview)):
|
||||
raise ParseError("DeviceConfiguration payload has unexpected type")
|
||||
cfg = parse_device_config(bytes(packet.payload), self._session.hardware_family)
|
||||
logger.info(
|
||||
"Loaded device configuration for family=%s fields=%d",
|
||||
cfg.family.name,
|
||||
len(cfg.values),
|
||||
)
|
||||
return cfg
|
||||
|
||||
def set(self, cfg: DeviceConfigVariant, *, timeout_s: float = 1.0) -> None:
|
||||
"""Write configuration block for active hardware family."""
|
||||
if cfg.family != self._session.hardware_family:
|
||||
raise ParseError("DeviceConfigVariant family does not match connected hardware family")
|
||||
logger.info(
|
||||
"Writing device configuration for family=%s fields=%d",
|
||||
cfg.family.name,
|
||||
len(cfg.values),
|
||||
)
|
||||
self._session.send(Packet(PacketType.DEVICE_CONFIGURATION, cfg), require_ack=True, timeout_s=timeout_s)
|
||||
|
||||
def reset(self, *, timeout_s: float = 1.0) -> None:
|
||||
"""Reset device configuration to firmware defaults."""
|
||||
logger.warning("Resetting device configuration to firmware defaults")
|
||||
self._session.send(Packet(PacketType.RESET_DEVICE_CONFIGURATION), require_ack=True, timeout_s=timeout_s)
|
||||
@@ -0,0 +1,99 @@
|
||||
"""VNA high-level controller for direct protocol packets."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import replace
|
||||
import logging
|
||||
from typing import Callable
|
||||
|
||||
from ..enums import PacketType
|
||||
from ..exceptions import ParseError
|
||||
from ..models import Packet, StreamHandle, SweepResult, VNADatapointPacket, VNARawPoint, VNASweepSettings
|
||||
from ..session import LibreVNASession
|
||||
from ..sweep.assembler import assemble_vna_sweep, datapoint_to_raw_point
|
||||
|
||||
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=self._session.default_sync_mode)
|
||||
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 ports=%s",
|
||||
effective.kind.value,
|
||||
effective.f_start_hz,
|
||||
effective.f_stop_hz,
|
||||
effective.points,
|
||||
effective.if_bandwidth_hz,
|
||||
effective.excited_ports,
|
||||
)
|
||||
|
||||
def acquire(self, *, expected_points: int | None = None, timeout_s: float = 10.0) -> 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")
|
||||
|
||||
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,
|
||||
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
|
||||
|
||||
def stream(self, callback: Callable[[VNARawPoint], None]) -> StreamHandle:
|
||||
"""Subscribe callback for every incoming VNA datapoint packet."""
|
||||
if self._settings is None:
|
||||
raise ParseError("VNA is not configured. Call vna.configure() before stream().")
|
||||
|
||||
settings = self._settings
|
||||
num_ports = self._session.get_device_info().num_ports
|
||||
|
||||
def _on_packet(packet: Packet) -> None:
|
||||
"""Decode datapoint packet and forward mapped raw point to callback."""
|
||||
payload = packet.payload
|
||||
if not isinstance(payload, VNADatapointPacket):
|
||||
raise ParseError("Expected decoded VNADatapointPacket payload")
|
||||
callback(datapoint_to_raw_point(payload, settings, num_ports=num_ports))
|
||||
|
||||
logger.info("VNA stream subscription started")
|
||||
return self._session.subscribe(PacketType.VNA_DATAPOINT, _on_packet)
|
||||
Reference in New Issue
Block a user