Compare commits

..

No commits in common. "414597e9406c12f38877250ae9b8091884b9075c" and "27049f00eaaa3676794cd98e1f8dcfa13fa44e3b" have entirely different histories.

14 changed files with 408 additions and 2826 deletions

1
.gitignore vendored
View File

@ -52,7 +52,6 @@ tests/sdr/
# Sphinx documentation # Sphinx documentation
docs/build/ docs/build/
docs/_build/
# Jupyter Notebook # Jupyter Notebook
.ipynb_checkpoints .ipynb_checkpoints

File diff suppressed because it is too large Load Diff

View File

@ -1,29 +0,0 @@
/* Change the hex values below to customize heading colours */
.rst-content h1 { color: #2c3e50; }
.rst-content h2,
.rst-content h2 a { color: #ffffff !important; font-size: 22px !important; }
.rst-content h3,
.rst-content h3 a { color: #ffffff !important; font-size: 16px !important; }
.rst-content h3 code { font-size: inherit !important; }
.rst-content .admonition.warning {
background: #1a1a2e !important;
border-left: 4px solid #c0392b !important;
}
.rst-content .admonition.warning .admonition-title {
background: #c0392b !important;
color: #ffffff !important;
}
.rst-content .admonition.warning p {
color: #ffffff !important;
}
.rst-content h4 { color: #404040; }
.highlight * { color: #ffffff !important; }
.ria-cmd { color: #2980b9 !important; }

View File

@ -1,8 +0,0 @@
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('.highlight pre').forEach(function (pre) {
pre.innerHTML = pre.innerHTML.replace(
/((?:^|\n|>))(ria)(?=[ \t]|<)/g,
'$1<span class="ria-cmd">$2</span>'
);
});
});

View File

@ -14,7 +14,7 @@ sys.path.insert(0, os.path.abspath(os.path.join('..', '..')))
project = 'ria-toolkit-oss' project = 'ria-toolkit-oss'
copyright = '2025, Qoherent Inc' copyright = '2025, Qoherent Inc'
author = 'Qoherent Inc.' author = 'Qoherent Inc.'
release = '0.1.5' release = '0.1.4'
# -- General configuration --------------------------------------------------- # -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
@ -73,6 +73,3 @@ def setup(app):
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
html_theme = 'sphinx_rtd_theme' html_theme = 'sphinx_rtd_theme'
html_static_path = ['_static']
html_css_files = ['custom.css']
html_js_files = ['custom.js']

File diff suppressed because it is too large Load Diff

901
poetry.lock generated

File diff suppressed because it is too large Load Diff

2
poetry.toml Normal file
View File

@ -0,0 +1,2 @@
[virtualenvs.options]
system-site-packages = true

View File

@ -1,6 +1,6 @@
[project] [project]
name = "ria-toolkit-oss" name = "ria-toolkit-oss"
version = "0.1.5" version = "0.1.4"
description = "An open-source version of the RIA Toolkit, including the fundamental tools to get started developing, testing, and deploying radio intelligence applications" description = "An open-source version of the RIA Toolkit, including the fundamental tools to get started developing, testing, and deploying radio intelligence applications"
license = { text = "AGPL-3.0-only" } license = { text = "AGPL-3.0-only" }
readme = "README.md" readme = "README.md"
@ -49,8 +49,7 @@ dependencies = [
"pyzmq (>=27.1.0,<28.0.0)", "pyzmq (>=27.1.0,<28.0.0)",
"pyyaml (>=6.0.3,<7.0.0)", "pyyaml (>=6.0.3,<7.0.0)",
"click (>=8.1.0,<9.0.0)", "click (>=8.1.0,<9.0.0)",
"matplotlib (>=3.8.0,<4.0.0)", "matplotlib (>=3.8.0,<4.0.0)"
"paramiko (>=4.0.0)"
] ]
# [project.optional-dependencies] Commented out to prevent Tox tests from failing # [project.optional-dependencies] Commented out to prevent Tox tests from failing
@ -88,7 +87,7 @@ pytest = "^8.0.0"
tox = "^4.19.0" tox = "^4.19.0"
fastapi = ">=0.111,<1.0" fastapi = ">=0.111,<1.0"
uvicorn = {version = ">=0.29,<1.0", extras = ["standard"]} uvicorn = {version = ">=0.29,<1.0", extras = ["standard"]}
onnxruntime = {version = ">=1.17,<2.0", python = ">=3.11"} onnxruntime = ">=1.17,<2.0"
httpx = ">=0.27,<1.0" httpx = ">=0.27,<1.0"
[tool.poetry.group.docs.dependencies] [tool.poetry.group.docs.dependencies]
@ -122,7 +121,7 @@ ria-agent = "ria_toolkit_oss.agent:main"
[tool.poetry.group.server.dependencies] [tool.poetry.group.server.dependencies]
fastapi = ">=0.111,<1.0" fastapi = ">=0.111,<1.0"
uvicorn = {version = ">=0.29,<1.0", extras = ["standard"]} uvicorn = {version = ">=0.29,<1.0", extras = ["standard"]}
onnxruntime = {version = ">=1.17,<2.0", python = ">=3.11"} onnxruntime = ">=1.17,<2.0"
[tool.black] [tool.black]
line-length = 119 line-length = 119

View File

@ -40,19 +40,15 @@ class RemoteTransmitter:
try: try:
if radio_str in ("pluto", "plutosdr"): if radio_str in ("pluto", "plutosdr"):
from ria_toolkit_oss.sdr.pluto import Pluto from ria_toolkit_oss.sdr.pluto import Pluto
self._sdr = Pluto(identifier) self._sdr = Pluto(identifier)
elif radio_str in ("usrp",): elif radio_str in ("usrp",):
from ria_toolkit_oss.sdr.usrp import USRP from ria_toolkit_oss.sdr.usrp import USRP
self._sdr = USRP(identifier) self._sdr = USRP(identifier)
elif radio_str in ("hackrf", "hackrf_one"): elif radio_str in ("hackrf", "hackrf_one"):
from ria_toolkit_oss.sdr.hackrf import HackRF from ria_toolkit_oss.sdr.hackrf import HackRF
self._sdr = HackRF(identifier) self._sdr = HackRF(identifier)
elif radio_str in ("bladerf", "blade"): elif radio_str in ("bladerf", "blade"):
from ria_toolkit_oss.sdr.blade import Blade from ria_toolkit_oss.sdr.blade import Blade
self._sdr = Blade(identifier) self._sdr = Blade(identifier)
else: else:
raise ValueError(f"Unknown SDR type: {radio_str!r}") raise ValueError(f"Unknown SDR type: {radio_str!r}")
@ -81,7 +77,6 @@ class RemoteTransmitter:
if self._sdr is None: if self._sdr is None:
raise RuntimeError("Call set_radio() and init_tx() before transmit()") raise RuntimeError("Call set_radio() and init_tx() before transmit()")
import time import time
# Transmit in a loop until duration has elapsed # Transmit in a loop until duration has elapsed
end = time.monotonic() + duration_s end = time.monotonic() + duration_s
while time.monotonic() < end: while time.monotonic() < end:

View File

@ -14,9 +14,6 @@ import logging
import threading import threading
import time import time
import paramiko
import zmq
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
_STARTUP_WAIT_S = 2.0 # seconds to wait for remote ZMQ server to bind _STARTUP_WAIT_S = 2.0 # seconds to wait for remote ZMQ server to bind
@ -161,21 +158,16 @@ 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, center_frequency / 1e6, sample_rate / 1e6, gain, channel,
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.

View File

@ -12,6 +12,7 @@ import pytest
from ria_toolkit_oss.remote_control.remote_transmitter import RemoteTransmitter from ria_toolkit_oss.remote_control.remote_transmitter import RemoteTransmitter
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Helpers # Helpers
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@ -62,18 +63,14 @@ class TestSetRadio:
def test_hackrf_alias(self): def test_hackrf_alias(self):
tx = RemoteTransmitter() tx = RemoteTransmitter()
mock_sdr = _make_mock_sdr() mock_sdr = _make_mock_sdr()
mock_module = MagicMock() with patch("ria_toolkit_oss.sdr.hackrf.HackRF", return_value=mock_sdr):
mock_module.HackRF = MagicMock(return_value=mock_sdr)
with patch.dict("sys.modules", {"ria_toolkit_oss.sdr.hackrf": mock_module}):
tx.set_radio("hackrf", "") tx.set_radio("hackrf", "")
assert tx._sdr is mock_sdr assert tx._sdr is mock_sdr
def test_hackrf_one_alias(self): def test_hackrf_one_alias(self):
tx = RemoteTransmitter() tx = RemoteTransmitter()
mock_sdr = _make_mock_sdr() mock_sdr = _make_mock_sdr()
mock_module = MagicMock() with patch("ria_toolkit_oss.sdr.hackrf.HackRF", return_value=mock_sdr):
mock_module.HackRF = MagicMock(return_value=mock_sdr)
with patch.dict("sys.modules", {"ria_toolkit_oss.sdr.hackrf": mock_module}):
tx.set_radio("hackrf_one", "") tx.set_radio("hackrf_one", "")
assert tx._sdr is mock_sdr assert tx._sdr is mock_sdr
@ -244,40 +241,34 @@ class TestRunFunction:
def test_init_tx_without_radio_returns_failure(self): def test_init_tx_without_radio_returns_failure(self):
tx = RemoteTransmitter() tx = RemoteTransmitter()
resp = tx.run_function( resp = tx.run_function({
{
"function_name": "init_tx", "function_name": "init_tx",
"center_frequency": 2.4e9, "center_frequency": 2.4e9,
"sample_rate": 20e6, "sample_rate": 20e6,
"gain": 0, "gain": 0,
} })
)
assert resp["status"] is False assert resp["status"] is False
assert resp["error_message"] assert resp["error_message"]
def test_init_tx_with_radio_success(self): def test_init_tx_with_radio_success(self):
tx = self._tx_with_mock_sdr() tx = self._tx_with_mock_sdr()
resp = tx.run_function( resp = tx.run_function({
{
"function_name": "init_tx", "function_name": "init_tx",
"center_frequency": 2.4e9, "center_frequency": 2.4e9,
"sample_rate": 20e6, "sample_rate": 20e6,
"gain": 30, "gain": 30,
} })
)
assert resp["status"] is True assert resp["status"] is True
def test_transmit_runs_for_short_duration(self): def test_transmit_runs_for_short_duration(self):
tx = self._tx_with_mock_sdr() tx = self._tx_with_mock_sdr()
tx._sdr.init_tx = MagicMock() tx._sdr.init_tx = MagicMock()
resp = tx.run_function( resp = tx.run_function({
{
"function_name": "init_tx", "function_name": "init_tx",
"center_frequency": 2.4e9, "center_frequency": 2.4e9,
"sample_rate": 20e6, "sample_rate": 20e6,
"gain": 0, "gain": 0,
} })
)
resp = tx.run_function({"function_name": "transmit", "duration_s": 0.02}) resp = tx.run_function({"function_name": "transmit", "duration_s": 0.02})
assert resp["status"] is True assert resp["status"] is True

View File

@ -7,6 +7,8 @@ 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
@ -197,11 +199,15 @@ 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(host="h", ssh_user="u", ssh_key_path="/k") mod.RemoteTransmitterController(
host="h", ssh_user="u", ssh_key_path="/k"
)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------

View File

@ -2,7 +2,7 @@
from __future__ import annotations from __future__ import annotations
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, call, patch
import pytest import pytest
@ -12,6 +12,7 @@ from ria_toolkit_oss.orchestration.campaign import (
TransmitterConfig, TransmitterConfig,
) )
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Helpers # Helpers
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@ -178,7 +179,9 @@ class TestInitRemoteTxControllers:
} }
] ]
executor = _make_executor(d) executor = _make_executor(d)
with patch("ria_toolkit_oss.remote_control.RemoteTransmitterController") as mock_cls: with patch(
"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 == {}
@ -261,7 +264,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 = ctrl.init_tx.call_args _, kwargs = mock_ctrl_kwarg = 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):
@ -378,11 +381,7 @@ 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( patch.object(executor, "_execute_step", return_value=MagicMock(error=None, qa=MagicMock(flagged=False, snr_db=20.0, duration_s=10.0))),
executor,
"_execute_step",
return_value=MagicMock(error=None, qa=MagicMock(flagged=False, snr_db=20.0, duration_s=10.0)),
),
): ):
executor.run() executor.run()
@ -402,7 +401,6 @@ 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()