Merge pull request 'updated tags for new release' (#25) from clifix into main
All checks were successful
Test with tox / Test with tox (3.11) (push) Successful in 6m10s
Build Project / Build Project (3.10) (push) Successful in 15m3s
Build Sphinx Docs Set / Build Docs (push) Successful in 20m10s
Test with tox / Test with tox (3.10) (push) Successful in 20m10s
Build Project / Build Project (3.11) (push) Successful in 21m38s
Build Project / Build Project (3.12) (push) Successful in 21m40s
Test with tox / Test with tox (3.12) (push) Successful in 15m26s

Reviewed-on: #25
Reviewed-by: madrigal <madrigal@qoherent.ai>
This commit is contained in:
M madrigal 2026-04-20 14:36:59 -04:00
commit 414597e940
14 changed files with 2826 additions and 408 deletions

1
.gitignore vendored
View File

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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,29 @@
/* 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

@ -0,0 +1,8 @@
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'
copyright = '2025, Qoherent Inc'
author = 'Qoherent Inc.'
release = '0.1.4'
release = '0.1.5'
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
@ -73,3 +73,6 @@ def setup(app):
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
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

View File

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

View File

@ -1,6 +1,6 @@
[project]
name = "ria-toolkit-oss"
version = "0.1.4"
version = "0.1.5"
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" }
readme = "README.md"
@ -49,7 +49,8 @@ dependencies = [
"pyzmq (>=27.1.0,<28.0.0)",
"pyyaml (>=6.0.3,<7.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
@ -87,7 +88,7 @@ pytest = "^8.0.0"
tox = "^4.19.0"
fastapi = ">=0.111,<1.0"
uvicorn = {version = ">=0.29,<1.0", extras = ["standard"]}
onnxruntime = ">=1.17,<2.0"
onnxruntime = {version = ">=1.17,<2.0", python = ">=3.11"}
httpx = ">=0.27,<1.0"
[tool.poetry.group.docs.dependencies]
@ -121,7 +122,7 @@ ria-agent = "ria_toolkit_oss.agent:main"
[tool.poetry.group.server.dependencies]
fastapi = ">=0.111,<1.0"
uvicorn = {version = ">=0.29,<1.0", extras = ["standard"]}
onnxruntime = ">=1.17,<2.0"
onnxruntime = {version = ">=1.17,<2.0", python = ">=3.11"}
[tool.black]
line-length = 119

View File

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

View File

@ -14,6 +14,9 @@ import logging
import threading
import time
import paramiko
import zmq
logger = logging.getLogger(__name__)
_STARTUP_WAIT_S = 2.0 # seconds to wait for remote ZMQ server to bind
@ -158,16 +161,21 @@ class RemoteTransmitterController:
"""
logger.info(
"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",
"center_frequency": center_frequency,
"sample_rate": sample_rate,
"gain": gain,
"channel": channel,
"gain_mode": gain_mode,
})
}
)
def transmit_async(self, duration_s: float) -> None:
"""Start a timed CW transmission in a background thread.

View File

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

View File

@ -7,8 +7,6 @@ sys.modules so they run regardless of whether the packages are installed.
from __future__ import annotations
import json
import sys
import threading
import time
from types import ModuleType
from unittest.mock import MagicMock, patch
@ -199,15 +197,11 @@ class TestErrorHandling:
def test_missing_paramiko_raises_runtime_error(self):
"""If paramiko is absent, connecting gives a clear RuntimeError."""
import importlib
import ria_toolkit_oss.remote_control.remote_transmitter_controller as mod
with patch.dict("sys.modules", {"paramiko": None}):
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 unittest.mock import MagicMock, call, patch
from unittest.mock import MagicMock, patch
import pytest
@ -12,7 +12,6 @@ from ria_toolkit_oss.orchestration.campaign import (
TransmitterConfig,
)
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
@ -179,9 +178,7 @@ class TestInitRemoteTxControllers:
}
]
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()
mock_cls.assert_not_called()
assert executor._remote_tx_controllers == {}
@ -264,7 +261,7 @@ class TestStartTransmitterSdrRemote:
tx = executor.config.transmitters[0]
step = CaptureStep(duration=5.0, label="nochan")
executor._start_transmitter(tx, step)
_, kwargs = mock_ctrl_kwarg = ctrl.init_tx.call_args
_, kwargs = ctrl.init_tx.call_args
assert kwargs["channel"] == 0
def test_missing_controller_raises(self):
@ -381,7 +378,11 @@ class TestRunWithSdrRemote:
),
patch.object(executor, "_close_sdr"),
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()
@ -401,6 +402,7 @@ class TestTransmitBufferAndTimeout:
def _executor_with_ctrl(self):
from ria_toolkit_oss.orchestration.executor import CampaignExecutor
cfg = CampaignConfig.from_dict(_FULL_CAMPAIGN_DICT)
executor = CampaignExecutor(cfg)
ctrl = MagicMock()