qac-cli-commands #26
4
poetry.lock
generated
4
poetry.lock
generated
|
|
@ -1,4 +1,4 @@
|
||||||
# This file is automatically @generated by Poetry 2.3.4 and should not be changed by hand.
|
# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand.
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alabaster"
|
name = "alabaster"
|
||||||
|
|
@ -1096,7 +1096,7 @@ files = [
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
attrs = ">=22.2.0"
|
attrs = ">=22.2.0"
|
||||||
jsonschema-specifications = ">=2023.3.6"
|
jsonschema-specifications = ">=2023.03.6"
|
||||||
referencing = ">=0.28.4"
|
referencing = ">=0.28.4"
|
||||||
rpds-py = ">=0.25.0"
|
rpds-py = ">=0.25.0"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -270,9 +270,7 @@ class Streamer:
|
||||||
)
|
)
|
||||||
self._rx = session
|
self._rx = session
|
||||||
await self._send_status("streaming", app_id)
|
await self._send_status("streaming", app_id)
|
||||||
session.task = asyncio.create_task(
|
session.task = asyncio.create_task(self._capture_loop(session), name="ria-streamer-capture")
|
||||||
self._capture_loop(session), name="ria-streamer-capture"
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _handle_rx_stop(self, msg: dict) -> None:
|
async def _handle_rx_stop(self, msg: dict) -> None:
|
||||||
session = self._rx
|
session = self._rx
|
||||||
|
|
@ -310,9 +308,7 @@ class Streamer:
|
||||||
logger.warning("Applying configure failed: %s", exc)
|
logger.warning("Applying configure failed: %s", exc)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
samples = await loop.run_in_executor(
|
samples = await loop.run_in_executor(None, session.sdr.rx, session.buffer_size)
|
||||||
None, session.sdr.rx, session.buffer_size
|
|
||||||
)
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
from ria_toolkit_oss.sdr import SdrDisconnectedError
|
from ria_toolkit_oss.sdr import SdrDisconnectedError
|
||||||
|
|
||||||
|
|
@ -342,7 +338,7 @@ class Streamer:
|
||||||
# ==================================================================
|
# ==================================================================
|
||||||
# TX
|
# TX
|
||||||
|
|
||||||
async def _handle_tx_start(self, msg: dict) -> None:
|
async def _handle_tx_start(self, msg: dict) -> None: # noqa: C901
|
||||||
app_id = msg.get("app_id") or ""
|
app_id = msg.get("app_id") or ""
|
||||||
radio_config = dict(msg.get("radio_config") or {})
|
radio_config = dict(msg.get("radio_config") or {})
|
||||||
|
|
||||||
|
|
@ -383,9 +379,7 @@ class Streamer:
|
||||||
buffer_size = int(radio_config.pop("buffer_size", _DEFAULT_BUFFER_SIZE))
|
buffer_size = int(radio_config.pop("buffer_size", _DEFAULT_BUFFER_SIZE))
|
||||||
underrun_policy = str(radio_config.pop("underrun_policy", "pause"))
|
underrun_policy = str(radio_config.pop("underrun_policy", "pause"))
|
||||||
if underrun_policy not in ("pause", "zero", "repeat"):
|
if underrun_policy not in ("pause", "zero", "repeat"):
|
||||||
await self._send_tx_status(
|
await self._send_tx_status(app_id, "error", f"invalid underrun_policy {underrun_policy!r}")
|
||||||
app_id, "error", f"invalid underrun_policy {underrun_policy!r}"
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
if not device:
|
if not device:
|
||||||
await self._send_tx_status(app_id, "error", "tx_start missing radio_config.device")
|
await self._send_tx_status(app_id, "error", "tx_start missing radio_config.device")
|
||||||
|
|
@ -404,15 +398,10 @@ class Streamer:
|
||||||
# manifest bug and we want it surfaced immediately, not papered
|
# manifest bug and we want it surfaced immediately, not papered
|
||||||
# over with stale radio state.
|
# over with stale radio state.
|
||||||
if hasattr(sdr, "init_tx"):
|
if hasattr(sdr, "init_tx"):
|
||||||
init_args = {
|
init_args = {k: radio_config.get(f"tx_{k}") for k in ("sample_rate", "center_frequency", "gain")}
|
||||||
k: radio_config.get(f"tx_{k}")
|
|
||||||
for k in ("sample_rate", "center_frequency", "gain")
|
|
||||||
}
|
|
||||||
missing = [f"tx_{k}" for k, v in init_args.items() if v is None]
|
missing = [f"tx_{k}" for k, v in init_args.items() if v is None]
|
||||||
if missing:
|
if missing:
|
||||||
raise ValueError(
|
raise ValueError(f"tx_start missing required radio_config keys: {missing}")
|
||||||
f"tx_start missing required radio_config keys: {missing}"
|
|
||||||
)
|
|
||||||
sdr.init_tx(
|
sdr.init_tx(
|
||||||
sample_rate=init_args["sample_rate"],
|
sample_rate=init_args["sample_rate"],
|
||||||
center_frequency=init_args["center_frequency"],
|
center_frequency=init_args["center_frequency"],
|
||||||
|
|
@ -498,9 +487,8 @@ class Streamer:
|
||||||
return _silence(n)
|
return _silence(n)
|
||||||
|
|
||||||
# Max-duration watchdog.
|
# Max-duration watchdog.
|
||||||
if (
|
if session.max_duration_s is not None and (time.monotonic() - session.started_at) >= float(
|
||||||
session.max_duration_s is not None
|
session.max_duration_s
|
||||||
and (time.monotonic() - session.started_at) >= float(session.max_duration_s)
|
|
||||||
):
|
):
|
||||||
session.stop_event.set()
|
session.stop_event.set()
|
||||||
try:
|
try:
|
||||||
|
|
@ -528,7 +516,7 @@ class Streamer:
|
||||||
if arr.size < 2 or arr.size % 2 != 0:
|
if arr.size < 2 or arr.size % 2 != 0:
|
||||||
logger.warning("Malformed TX frame: %d floats (must be non-zero even count)", arr.size)
|
logger.warning("Malformed TX frame: %d floats (must be non-zero even count)", arr.size)
|
||||||
return self._underrun_fill(session, n)
|
return self._underrun_fill(session, n)
|
||||||
samples = (arr[0::2].astype(np.complex64) + 1j * arr[1::2].astype(np.complex64))
|
samples = arr[0::2].astype(np.complex64) + 1j * arr[1::2].astype(np.complex64)
|
||||||
if samples.size < n:
|
if samples.size < n:
|
||||||
out = np.zeros(n, dtype=np.complex64)
|
out = np.zeros(n, dtype=np.complex64)
|
||||||
out[: samples.size] = samples
|
out[: samples.size] = samples
|
||||||
|
|
@ -747,6 +735,7 @@ def _default_sdr_factory(device: str, identifier: str | None):
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Top-level entry
|
# Top-level entry
|
||||||
|
|
||||||
|
|
||||||
async def run_streamer(ws_url: str, token: str, *, cfg: AgentConfig | None = None) -> None:
|
async def run_streamer(ws_url: str, token: str, *, cfg: AgentConfig | None = None) -> None:
|
||||||
"""Connect to *ws_url* and run the streamer loop until cancelled."""
|
"""Connect to *ws_url* and run the streamer loop until cancelled."""
|
||||||
ws = WsClient(ws_url, token)
|
ws = WsClient(ws_url, token)
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,11 @@ import json
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
import paramiko
|
||||||
|
import zmq
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -158,16 +163,21 @@ class RemoteTransmitterController:
|
||||||
"""
|
"""
|
||||||
logger.info(
|
logger.info(
|
||||||
"init_tx: fc=%.3f MHz, fs=%.3f MHz, gain=%.1f dB, ch=%d",
|
"init_tx: fc=%.3f MHz, fs=%.3f MHz, gain=%.1f dB, ch=%d",
|
||||||
center_frequency / 1e6, sample_rate / 1e6, gain, channel,
|
center_frequency / 1e6,
|
||||||
|
sample_rate / 1e6,
|
||||||
|
gain,
|
||||||
|
channel,
|
||||||
)
|
)
|
||||||
self._send({
|
self._send(
|
||||||
|
{
|
||||||
"function_name": "init_tx",
|
"function_name": "init_tx",
|
||||||
"center_frequency": center_frequency,
|
"center_frequency": center_frequency,
|
||||||
"sample_rate": sample_rate,
|
"sample_rate": sample_rate,
|
||||||
"gain": gain,
|
"gain": gain,
|
||||||
"channel": channel,
|
"channel": channel,
|
||||||
"gain_mode": gain_mode,
|
"gain_mode": gain_mode,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
def transmit_async(self, duration_s: float) -> None:
|
def transmit_async(self, duration_s: float) -> None:
|
||||||
"""Start a timed CW transmission in a background thread.
|
"""Start a timed CW transmission in a background thread.
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,6 @@ sys.modules so they run regardless of whether the packages are installed.
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import sys
|
|
||||||
import threading
|
|
||||||
import time
|
import time
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
@ -199,15 +197,11 @@ class TestErrorHandling:
|
||||||
|
|
||||||
def test_missing_paramiko_raises_runtime_error(self):
|
def test_missing_paramiko_raises_runtime_error(self):
|
||||||
"""If paramiko is absent, connecting gives a clear RuntimeError."""
|
"""If paramiko is absent, connecting gives a clear RuntimeError."""
|
||||||
import importlib
|
|
||||||
|
|
||||||
import ria_toolkit_oss.remote_control.remote_transmitter_controller as mod
|
import ria_toolkit_oss.remote_control.remote_transmitter_controller as mod
|
||||||
|
|
||||||
with patch.dict("sys.modules", {"paramiko": None}):
|
with patch.dict("sys.modules", {"paramiko": None}):
|
||||||
with pytest.raises((RuntimeError, ImportError)):
|
with pytest.raises((RuntimeError, ImportError)):
|
||||||
mod.RemoteTransmitterController(
|
mod.RemoteTransmitterController(host="h", ssh_user="u", ssh_key_path="/k")
|
||||||
host="h", ssh_user="u", ssh_key_path="/k"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from unittest.mock import MagicMock, call, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
@ -12,7 +12,6 @@ from ria_toolkit_oss.orchestration.campaign import (
|
||||||
TransmitterConfig,
|
TransmitterConfig,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Helpers
|
# Helpers
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
@ -179,9 +178,7 @@ class TestInitRemoteTxControllers:
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
executor = _make_executor(d)
|
executor = _make_executor(d)
|
||||||
with patch(
|
with patch("ria_toolkit_oss.remote_control.RemoteTransmitterController") as mock_cls:
|
||||||
"ria_toolkit_oss.remote_control.RemoteTransmitterController"
|
|
||||||
) as mock_cls:
|
|
||||||
executor._init_remote_tx_controllers()
|
executor._init_remote_tx_controllers()
|
||||||
mock_cls.assert_not_called()
|
mock_cls.assert_not_called()
|
||||||
assert executor._remote_tx_controllers == {}
|
assert executor._remote_tx_controllers == {}
|
||||||
|
|
@ -264,7 +261,7 @@ class TestStartTransmitterSdrRemote:
|
||||||
tx = executor.config.transmitters[0]
|
tx = executor.config.transmitters[0]
|
||||||
step = CaptureStep(duration=5.0, label="nochan")
|
step = CaptureStep(duration=5.0, label="nochan")
|
||||||
executor._start_transmitter(tx, step)
|
executor._start_transmitter(tx, step)
|
||||||
_, kwargs = mock_ctrl_kwarg = ctrl.init_tx.call_args
|
_, kwargs = ctrl.init_tx.call_args
|
||||||
assert kwargs["channel"] == 0
|
assert kwargs["channel"] == 0
|
||||||
|
|
||||||
def test_missing_controller_raises(self):
|
def test_missing_controller_raises(self):
|
||||||
|
|
@ -381,7 +378,11 @@ class TestRunWithSdrRemote:
|
||||||
),
|
),
|
||||||
patch.object(executor, "_close_sdr"),
|
patch.object(executor, "_close_sdr"),
|
||||||
patch.object(executor, "_close_remote_tx_controllers"),
|
patch.object(executor, "_close_remote_tx_controllers"),
|
||||||
patch.object(executor, "_execute_step", return_value=MagicMock(error=None, qa=MagicMock(flagged=False, snr_db=20.0, duration_s=10.0))),
|
patch.object(
|
||||||
|
executor,
|
||||||
|
"_execute_step",
|
||||||
|
return_value=MagicMock(error=None, qa=MagicMock(flagged=False, snr_db=20.0, duration_s=10.0)),
|
||||||
|
),
|
||||||
):
|
):
|
||||||
executor.run()
|
executor.run()
|
||||||
|
|
||||||
|
|
@ -401,6 +402,7 @@ class TestTransmitBufferAndTimeout:
|
||||||
|
|
||||||
def _executor_with_ctrl(self):
|
def _executor_with_ctrl(self):
|
||||||
from ria_toolkit_oss.orchestration.executor import CampaignExecutor
|
from ria_toolkit_oss.orchestration.executor import CampaignExecutor
|
||||||
|
|
||||||
cfg = CampaignConfig.from_dict(_FULL_CAMPAIGN_DICT)
|
cfg = CampaignConfig.from_dict(_FULL_CAMPAIGN_DICT)
|
||||||
executor = CampaignExecutor(cfg)
|
executor = CampaignExecutor(cfg)
|
||||||
ctrl = MagicMock()
|
ctrl = MagicMock()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user