Refactor radar system

This commit is contained in:
Ayzen
2026-10-05 16:46:28 +03:00
parent 515049d2ed
commit 8f097dfa2e
179 changed files with 8212 additions and 6676 deletions
@@ -28,6 +28,7 @@ from .protocol import (
from .transport import USBTransport
logger = logging.getLogger(__name__)
_PACKET_QUEUE_MAX = 32768
class _IndexedPayload(Protocol):
@@ -50,7 +51,9 @@ class LibreVNASession:
on_disconnect=self._on_transport_disconnect,
)
self._incoming: dict[PacketType, deque[Packet]] = defaultdict(deque)
# The sweep free-runs between acquisitions, and subscriptions do not
# consume request queues. Retain a bounded tail of stale datapoints.
self._incoming: dict[PacketType, deque[Packet]] = defaultdict(lambda: deque(maxlen=_PACKET_QUEUE_MAX))
self._subscribers: dict[PacketType, set[Callable[[Packet], None]]] = defaultdict(set)
self._incoming_cv = threading.Condition()
@@ -159,8 +162,8 @@ class LibreVNASession:
def disconnect(self) -> None:
"""Disconnect USB transport and clear session state."""
logger.debug("Closing USB transport and clearing session queues")
self._transport.disconnect()
with self._incoming_cv:
self._fatal_error = DeviceDisconnectedError("LibreVNA session is disconnected")
self._incoming.clear()
self._subscribers.clear()
self._incoming_cv.notify_all()
@@ -168,6 +171,7 @@ class LibreVNASession:
self._awaiting_ack = False
self._ack_result = None
self._ack_cv.notify_all()
self._transport.disconnect()
def send(self, packet: Packet, *, require_ack: bool = True, timeout_s: float = 0.5) -> None:
"""Send one protocol packet and optionally wait for ACK/NACK."""
@@ -188,26 +192,26 @@ class LibreVNASession:
self._awaiting_ack = True
self._ack_result = None
self._transport.write(frame, timeout_s=timeout_s)
if require_ack:
deadline = time.monotonic() + timeout_s
with self._ack_cv:
while self._ack_result is None:
self._raise_if_fatal_locked()
remaining = deadline - time.monotonic()
if remaining <= 0:
self._awaiting_ack = False
raise TimeoutError(f"Timeout waiting for ACK for packet {packet.type.name}")
self._ack_cv.wait(timeout=remaining)
result = self._ack_result
self._awaiting_ack = False
self._ack_result = None
if result == PacketType.NACK:
raise NackError(f"Received NACK for packet {packet.type.name}")
logger.debug("ACK received for packet %s", packet.type.name)
try:
self._transport.write(frame, timeout_s=timeout_s)
if require_ack:
deadline = time.monotonic() + timeout_s
with self._ack_cv:
while self._ack_result is None:
self._raise_if_fatal_locked()
remaining = deadline - time.monotonic()
if remaining <= 0:
raise TimeoutError(f"Timeout waiting for ACK for packet {packet.type.name}")
self._ack_cv.wait(timeout=remaining)
result = self._ack_result
if result == PacketType.NACK:
raise NackError(f"Received NACK for packet {packet.type.name}")
logger.debug("ACK received for packet %s", packet.type.name)
finally:
if require_ack:
with self._ack_cv:
self._awaiting_ack = False
self._ack_result = None
def request(
self,
@@ -238,9 +242,10 @@ class LibreVNASession:
payload_type: type[TPacketPayload],
payload_error: str,
) -> list[TPacketPayload]:
"""Collect indexed payloads by `point_number` into ascending order.
"""Collect one sweep from point zero, returning points in ascending order.
Missing points are omitted; callers decide whether this is acceptable.
Ignore the tail before the first point zero. A new point zero discards
an incomplete earlier cycle instead of filling its gaps from a later one.
"""
if expected_points <= 0:
raise ValueError("expected_points must be > 0")
@@ -265,6 +270,11 @@ class LibreVNASession:
f"Received out-of-range point index {point_number}, expected 0..{expected_points - 1}"
)
if point_number == 0:
collected = [None] * expected_points
received = 0
elif collected[0] is None:
continue
if collected[point_number] is None:
received += 1
collected[point_number] = payload
@@ -391,8 +401,12 @@ class LibreVNASession:
callbacks: list[Callable[[Packet], None]] = []
with self._incoming_cv:
self._incoming[packet.type].append(packet)
callbacks = list(self._subscribers.get(packet.type, set()))
queue = self._incoming[packet.type]
if len(queue) == queue.maxlen and not callbacks and packet.type != PacketType.VNA_DATAPOINT:
self._set_fatal_error(ParseError(f"Packet queue overflow for {packet.type.name}"))
return
queue.append(packet)
self._incoming_cv.notify_all()
for callback in callbacks: