python-aaronia 0.6.1__tar.gz → 0.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/CHANGELOG.md +81 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/Cargo.lock +2 -2
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/Cargo.toml +1 -1
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/PKG-INFO +56 -13
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/README.md +6 -10
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/docs/APPS.md +13 -8
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/docs/HTTPSPEC.md +38 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/docs/QUICKSTART.md +20 -11
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/docs/SDKSPEC.md +58 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/docs/VERIFICATION.md +13 -1
- python_aaronia-0.7.0/packaging/homebrew/README.md +43 -0
- python_aaronia-0.7.0/packaging/homebrew/soapy-aaronia.rb +47 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/pyproject.toml +5 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/python-aaronia/Cargo.toml +1 -1
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/python-aaronia/README.md +55 -12
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/python-aaronia/aaronia.pyi +69 -1
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/python-aaronia/src/lib.rs +377 -0
- python_aaronia-0.7.0/python-aaronia/test_basic.py +143 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/soapy-aaronia/README.md +17 -14
- python_aaronia-0.7.0/soapy-aaronia/packaging/install.ps1 +60 -0
- python_aaronia-0.7.0/soapy-aaronia/packaging/install.sh +74 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/src/error.rs +10 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/src/http_source.rs +6 -2
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/src/lib.rs +2 -2
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/src/native_sdk.rs +236 -10
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/src/unified_sink.rs +5 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/src/unified_source.rs +2 -2
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/src/utils.rs +64 -9
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/tests/http_resilience_test.rs +3 -3
- python_aaronia-0.6.1/python-aaronia/test_basic.py +0 -54
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/.cargo/config.toml +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/.gitattributes +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/.gitignore +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/CONTRIBUTING.md +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/DESIGN.md +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/LICENSE +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/PLUGINS.md +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/benches/decompress_block.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/benches/deinterleave_dual_iq.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/benches/parse_int16_packet.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/benches/rtsa_open_and_read.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/deny.toml +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/docs/FILESPEC.md +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/docs/USAGE.md +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/examples/channel_hopping.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/examples/device_control.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/examples/dump_metadata.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/examples/http_iq_quickstart.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/examples/native_sdk_basic.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/examples/native_sdk_transmit.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/examples/noaa_scanner.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/examples/python_arrow_example.py +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/examples/read_rtsa_file.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/examples/soapy_python_example.py +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/include/aaronia.h +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/scripts/ci-local.sh +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/soapy-aaronia/AaroniaSoapyDevice.cpp +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/soapy-aaronia/AaroniaSoapyDevice.hpp +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/soapy-aaronia/CMakeLists.txt +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/soapy-aaronia/Registration.cpp +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/src/c_api.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/src/decompression.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/src/detection.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/src/file_source.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/src/http_endpoints.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/src/http_sink.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/src/http_streaming.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/src/sdk_sink.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/src/sdk_source.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/src/sdr_source_impl.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/src/seify_impl.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/tests/c_api_test.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/tests/http_mock_test.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/tests/http_sink_test.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/tests/integration_test.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/tests/live_smoke.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/tests/native_sdk_load.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/tests/properties.proptest-regressions +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/tests/properties.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/tests/rtsa_negative_test.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/tests/sdr_source_impl_test.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/tests/spec_coverage.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/tests/test_cw_mag.rs +0 -0
- {python_aaronia-0.6.1 → python_aaronia-0.7.0}/tests/test_cw_meta.rs +0 -0
|
@@ -4,6 +4,55 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [v0.7.0] - 2026-08-12
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **`aaronia.open()`, block iteration and context-manager support in
|
|
11
|
+
Python.** The shortest working program is now three lines. `open()`
|
|
12
|
+
takes the URL, frequency and either an exact `rate` or the
|
|
13
|
+
`bandwidth` you want covered, connects, and starts streaming;
|
|
14
|
+
`for block in src.blocks(65536)` ends when the source runs out
|
|
15
|
+
instead of raising; and `with` stops the stream even when the body
|
|
16
|
+
fails, raising a failed teardown only if the body itself succeeded.
|
|
17
|
+
The old config-object path is unchanged and still the way to reach
|
|
18
|
+
every option.
|
|
19
|
+
- **`aaronia-doctor`, a command that checks an RTSA server.** It reports
|
|
20
|
+
whether the server answers, whether the mission has an input carrying
|
|
21
|
+
IQ, and what rate the device is running, and prints the fix for each
|
|
22
|
+
failure. The same checks are available as `aaronia.diagnose(url)`,
|
|
23
|
+
which returns `(ok, message, fix)` tuples, bounded at 20 seconds so a
|
|
24
|
+
stalled server cannot leave it waiting. Every failure it names is one
|
|
25
|
+
that otherwise shows up as a timeout with no explanation.
|
|
26
|
+
- **`aaronia.sample_rates()` and `aaronia.sample_rate_for_bandwidth()`**,
|
|
27
|
+
exposing the crate's rate ladder to Python so a program can ask for a
|
|
28
|
+
rate the hardware will actually run.
|
|
29
|
+
- **`Error::StreamClosed`**, separating "the stream ended" from "a read
|
|
30
|
+
failed". Both used to arrive as `Error::Protocol`, so a consumer
|
|
31
|
+
could not tell a capture that finished from one that was cut short.
|
|
32
|
+
Rust code matching on `Error::Protocol` for the closed-stream case
|
|
33
|
+
needs the new variant instead; the enum is `#[non_exhaustive]`, so
|
|
34
|
+
existing wildcard arms keep compiling. In Python the matching
|
|
35
|
+
exception is `AaroniaStreamClosed`, a subclass of
|
|
36
|
+
`AaroniaConnectionError`, so existing handlers are unaffected. It is
|
|
37
|
+
what lets `blocks()` end a loop on a finished stream while still
|
|
38
|
+
raising on a timeout or a transport failure, which would otherwise
|
|
39
|
+
make a truncated capture look like one that simply ran out.
|
|
40
|
+
- **An installer in every SoapySDR release archive.** `install.sh`
|
|
41
|
+
(`install.ps1` on Windows) finds SoapySDR's module directory, clears
|
|
42
|
+
the macOS quarantine flag, copies the module in, and confirms it
|
|
43
|
+
loads. It prints instructions rather than guessing when SoapySDR is
|
|
44
|
+
missing.
|
|
45
|
+
- **A Homebrew formula for the SoapySDR plugin**, in
|
|
46
|
+
`packaging/homebrew`. The release workflow renders it against the
|
|
47
|
+
published archives, checksums included, and attaches it to the
|
|
48
|
+
release, so updating a tap is a copy.
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
- The SoapySDR application guide still listed the old invented sample
|
|
52
|
+
rates for GQRX. It now describes the real ladder.
|
|
53
|
+
|
|
54
|
+
## [v0.6.2] - 2026-08-12
|
|
55
|
+
|
|
7
56
|
## [v0.6.1] - 2026-08-12
|
|
8
57
|
|
|
9
58
|
### Fixed
|
|
@@ -30,8 +79,40 @@ All notable changes to this project will be documented in this file.
|
|
|
30
79
|
guesses, so the relationships now live in one tested place. Wanting
|
|
31
80
|
8 MHz of spectrum needs 10 MHz of sampling, and
|
|
32
81
|
`iq_sample_rate_for_bandwidth` returns the 15.36 MHz that provides it.
|
|
82
|
+
`iq_sample_rates_for_clock` covers devices whose receiver clock is not
|
|
83
|
+
the default: a V6 ECO has a fixed clock and gives the measured ladder,
|
|
84
|
+
while a full V6 can select a faster one and reach further. Aaronia's
|
|
85
|
+
samples set `device/receiverclock` to "92MHz" or "245MHz"; only the
|
|
86
|
+
default has been checked against hardware.
|
|
87
|
+
|
|
88
|
+
### Added (native SDK)
|
|
89
|
+
- **Device-family auto-detection.** `detect_device_family` and
|
|
90
|
+
`open_detected_device` try each known family in turn, so an ECO owner
|
|
91
|
+
no longer has to know that the default `spectranv6` will not find
|
|
92
|
+
their device and that `spectranv6eco` is the string they needed.
|
|
93
|
+
- **`read_spectra`**, with the stream index taken from the open mode
|
|
94
|
+
rather than assumed. `spectranv6/raw` carries spectra on stream 2 and
|
|
95
|
+
IQ on stream 0; every other mode uses stream 0. Hardware-unverified.
|
|
96
|
+
- **`receiver_clock_hz`** on the native source, and
|
|
97
|
+
`spectranv6eco/rtsa` added to the known open modes. The clock sets the
|
|
98
|
+
rate ladder's ceiling, so callers that need to know which rates exist
|
|
99
|
+
can now ask instead of assuming.
|
|
33
100
|
|
|
34
101
|
### Fixed (native SDK)
|
|
102
|
+
- **The V6 ECO's fixed receiver clock was recorded as 61.44 MHz.** It is
|
|
103
|
+
92.16 MHz: an ECO streams at 61.44 MHz sampling, measured against real
|
|
104
|
+
hardware, and the constraint checked at configuration time is
|
|
105
|
+
`span * 1.5 <= clock`. The old value rejected every span above
|
|
106
|
+
40.96 MHz, including the device's own maximum.
|
|
107
|
+
- **Dual-channel capture selected the wrong mode and would have
|
|
108
|
+
returned corrupted samples.** `RxChannel::Rx1And2` wrote
|
|
109
|
+
`device/receiverchannel = "Rx1+Rx2"`, which delivers the two inputs as
|
|
110
|
+
two independent streams at indices 0 and 1. This crate reads a single
|
|
111
|
+
stream and deinterleaves it, which is the contract of the other mode,
|
|
112
|
+
`"Rx12"`. On a two-input V6 the result would have been Rx1's samples
|
|
113
|
+
split into two bogus channels, with no error anywhere. It now writes
|
|
114
|
+
`"Rx12"`. Aaronia's `RawIQ2RX` and `RawIQ2RXInterleave` samples
|
|
115
|
+
demonstrate one mode each. Still hardware-unverified.
|
|
35
116
|
- **Sweep mode set the wrong resolution-bandwidth key.** It sent
|
|
36
117
|
`main/rbw`, which no Aaronia sample uses; the key is `main/rbwfreq`.
|
|
37
118
|
Checked against Aaronia's published `SweepSpectrumEco` sample, which
|
|
@@ -3027,7 +3027,7 @@ dependencies = [
|
|
|
3027
3027
|
|
|
3028
3028
|
[[package]]
|
|
3029
3029
|
name = "python-aaronia"
|
|
3030
|
-
version = "0.
|
|
3030
|
+
version = "0.7.0"
|
|
3031
3031
|
dependencies = [
|
|
3032
3032
|
"arrow",
|
|
3033
3033
|
"num-complex",
|
|
@@ -3556,7 +3556,7 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
|
|
3556
3556
|
|
|
3557
3557
|
[[package]]
|
|
3558
3558
|
name = "sdr-aaronia-rs"
|
|
3559
|
-
version = "0.
|
|
3559
|
+
version = "0.7.0"
|
|
3560
3560
|
dependencies = [
|
|
3561
3561
|
"anyhow",
|
|
3562
3562
|
"bitflags 2.13.1",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name = "sdr-aaronia-rs"
|
|
3
3
|
description = "Unified Rust interface for Aaronia Spectran Spectrum Analyzers / SDRs, featuring Python bindings, a SoapySDR plugin, HTTP streaming, and native SDK support."
|
|
4
4
|
license = "GPL-3.0-or-later"
|
|
5
|
-
version = "0.
|
|
5
|
+
version = "0.7.0"
|
|
6
6
|
edition = "2024"
|
|
7
7
|
repository = "https://github.com/isaacbentley/sdr-aaronia-rs"
|
|
8
8
|
readme = "README.md"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-aaronia
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Classifier: Programming Language :: Rust
|
|
5
5
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
6
6
|
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
@@ -40,37 +40,66 @@ cd python-aaronia
|
|
|
40
40
|
maturin develop --release
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
+
Check your setup before writing any code:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
aaronia-doctor http://localhost:54664
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
It reports whether the server is reachable, whether the mission has an
|
|
50
|
+
input carrying IQ, and what rate the device is running, and names the
|
|
51
|
+
fix for each failure.
|
|
52
|
+
|
|
43
53
|
## Quickstart
|
|
44
54
|
|
|
45
55
|
```python
|
|
46
56
|
import aaronia
|
|
47
57
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
58
|
+
with aaronia.open("http://localhost:54664", freq=2.44e9, bandwidth=10e6) as src:
|
|
59
|
+
for block in src.blocks(65536): # numpy complex64 arrays
|
|
60
|
+
process(block)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`aaronia.open()` connects and starts streaming in one call. `bandwidth`
|
|
64
|
+
asks for that much usable spectrum and picks a sample rate the hardware
|
|
65
|
+
can actually run; pass `rate=` instead to name one exactly. Use
|
|
66
|
+
`file="capture.rtsa"` in place of the URL to play back a recording.
|
|
53
67
|
|
|
54
|
-
|
|
55
|
-
|
|
68
|
+
Iterating with `blocks()` ends when the stream closes. To read on your
|
|
69
|
+
own schedule, or for Apache Arrow:
|
|
56
70
|
|
|
71
|
+
```python
|
|
72
|
+
src = aaronia.open(freq=2.44e9, rate=15.36e6, format="I16")
|
|
57
73
|
samples = src.read_samples_numpy(65536) # numpy complex64 array
|
|
58
74
|
batch = src.read_samples_arrow(65536) # pyarrow FixedSizeListArray of [re, im]
|
|
59
|
-
|
|
60
75
|
src.set_center_frequency(2.41e9) # live retune, no teardown
|
|
61
|
-
|
|
62
76
|
print(src.cumulative_drops(), src.take_overrun(), src.last_timestamp_ns())
|
|
63
77
|
src.stop_streaming()
|
|
64
78
|
```
|
|
65
79
|
|
|
66
|
-
|
|
67
|
-
`
|
|
80
|
+
For full control, build an `AaroniaConfig` and pass it to
|
|
81
|
+
`AaroniaSource.start_streaming()`; `open()` is a shorthand for the
|
|
82
|
+
common fields.
|
|
68
83
|
|
|
69
84
|
The
|
|
70
85
|
[quickstart](https://github.com/isaacbentley/sdr-aaronia-rs/blob/main/docs/QUICKSTART.md)
|
|
71
86
|
covers configuring the RTSA-Suite HTTP Server block, which everything
|
|
72
87
|
above depends on.
|
|
73
88
|
|
|
89
|
+
## Sample rates
|
|
90
|
+
|
|
91
|
+
The device runs a fixed ladder of rates: 61.44 MHz halved down to
|
|
92
|
+
120 kHz. Ask for anything else and it quietly uses the nearest rung,
|
|
93
|
+
leaving your program computing against a rate that is not in use.
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
aaronia.sample_rates() # every rate, highest first
|
|
97
|
+
aaronia.sample_rate_for_bandwidth(8e6) # 15.36e6: the lowest rate covering 8 MHz
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
A rate carries only 80% of itself as alias-free bandwidth, which is why
|
|
101
|
+
8 MHz of spectrum needs 15.36 MHz of sampling.
|
|
102
|
+
|
|
74
103
|
## Configuration (`AaroniaConfig`)
|
|
75
104
|
|
|
76
105
|
Every field is readable and writable.
|
|
@@ -107,11 +136,14 @@ silently defaulting.
|
|
|
107
136
|
enabled, which is the default. The reader reopens the stream,
|
|
108
137
|
re-applies the current tuning, and flags the first read after the gap
|
|
109
138
|
through `take_overrun()`. After five failed attempts the stream ends
|
|
110
|
-
and reads raise `
|
|
139
|
+
and reads raise `AaroniaStreamClosed`.
|
|
111
140
|
- **Typed exceptions.** `AaroniaConnectionError` (unreachable endpoint),
|
|
112
141
|
`AaroniaTimeoutError`, `AaroniaHardwareError` (device and SDK errors)
|
|
113
142
|
and `ValueError` (invalid configuration), mapped from the Rust error
|
|
114
143
|
enum with the full cause chain in the message.
|
|
144
|
+
`AaroniaStreamClosed` subclasses `AaroniaConnectionError` and means
|
|
145
|
+
the stream finished rather than failed; `blocks()` ends on it, while
|
|
146
|
+
a timeout or transport failure still raises.
|
|
115
147
|
- **Dual-channel** reads (`receiver_channel = "Rx1And2"` with
|
|
116
148
|
`read_samples_dual_numpy(count)`, returning two time-aligned arrays)
|
|
117
149
|
require the native-SDK backend: Windows or Linux with the Aaronia SDK
|
|
@@ -123,6 +155,8 @@ silently defaulting.
|
|
|
123
155
|
| Method | Purpose |
|
|
124
156
|
| --- | --- |
|
|
125
157
|
| `start_streaming(cfg)` / `stop_streaming()` | Session lifecycle |
|
|
158
|
+
| `with src: ...` | Stops streaming on the way out, including after an exception |
|
|
159
|
+
| `blocks(count)` | Iterate `count`-sample arrays until the stream closes |
|
|
126
160
|
| `read_samples_numpy(count)` | NumPy `complex64` array |
|
|
127
161
|
| `read_samples_arrow(count)` | PyArrow `FixedSizeListArray` of `[re, im]` float32 pairs |
|
|
128
162
|
| `read_samples_dual_numpy(count)` | `(rx1, rx2)` NumPy arrays (dual-channel captures) |
|
|
@@ -131,3 +165,12 @@ silently defaulting.
|
|
|
131
165
|
| `take_overrun()` | True once per detected receive-side overrun |
|
|
132
166
|
| `last_timestamp_ns()` | Epoch-ns timestamp of the last received block (HTTP backend; 0 otherwise) |
|
|
133
167
|
|
|
168
|
+
## Module functions
|
|
169
|
+
|
|
170
|
+
| Function | Purpose |
|
|
171
|
+
| --- | --- |
|
|
172
|
+
| `open(url=None, *, freq, rate, bandwidth, ref_level, file, format, read_timeout)` | Configure, connect and start streaming in one call |
|
|
173
|
+
| `sample_rates()` | Every sample rate the hardware can run |
|
|
174
|
+
| `sample_rate_for_bandwidth(hz)` | Lowest rate covering that much spectrum |
|
|
175
|
+
| `diagnose(url)` | `(ok, message, fix)` for each setup check; what `aaronia-doctor` prints |
|
|
176
|
+
|
|
@@ -102,21 +102,17 @@ pip install python-aaronia
|
|
|
102
102
|
```python
|
|
103
103
|
import aaronia
|
|
104
104
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
cfg.sample_rate = 15.36e6
|
|
109
|
-
|
|
110
|
-
src = aaronia.AaroniaSource()
|
|
111
|
-
src.start_streaming(cfg)
|
|
112
|
-
samples = src.read_samples_numpy(65536) # numpy complex64
|
|
113
|
-
src.stop_streaming()
|
|
105
|
+
with aaronia.open("http://localhost:54664", freq=2.44e9, bandwidth=10e6) as src:
|
|
106
|
+
for block in src.blocks(65536): # numpy complex64 arrays
|
|
107
|
+
process(block)
|
|
114
108
|
```
|
|
115
109
|
|
|
116
110
|
Reads land in NumPy or PyArrow with one copy out of the receive buffer.
|
|
117
111
|
Blocking calls release the GIL, errors arrive as typed exceptions, and
|
|
118
112
|
the package ships type stubs. Wheels are abi3 for CPython 3.9 and
|
|
119
|
-
later.
|
|
113
|
+
later. The bundled `aaronia-doctor` command checks a server and names
|
|
114
|
+
the fix for whatever is wrong. Full reference:
|
|
115
|
+
[python-aaronia/README.md](python-aaronia/README.md).
|
|
120
116
|
|
|
121
117
|
### SoapySDR: GQRX, SDR++, GNU Radio and others
|
|
122
118
|
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
# Using a SPECTRAN from existing SDR applications
|
|
2
2
|
|
|
3
3
|
Any SoapySDR-capable application can drive a SPECTRAN through the
|
|
4
|
-
[`soapy-aaronia`](../soapy-aaronia/README.md) plugin. Install
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
[`soapy-aaronia`](../soapy-aaronia/README.md) plugin. Install it first:
|
|
5
|
+
download the archive for your platform from a
|
|
6
|
+
[release](https://github.com/isaacbentley/sdr-aaronia-rs/releases),
|
|
7
|
+
unpack it, and run the bundled installer (`./install.sh`, or
|
|
8
|
+
`.\install.ps1` in PowerShell on Windows). It finds SoapySDR's module
|
|
9
|
+
directory, copies the plugin in, and confirms it loads:
|
|
7
10
|
|
|
8
11
|
```bash
|
|
9
12
|
SoapySDRUtil --check=aaronia
|
|
10
13
|
```
|
|
11
14
|
|
|
15
|
+
The [plugin README](../soapy-aaronia/README.md#install-a-prebuilt-module)
|
|
16
|
+
covers installing by hand and building from source.
|
|
17
|
+
|
|
12
18
|
Every application below uses the same device string. In most cases the
|
|
13
19
|
RTSA-Suite HTTP server URL is the only argument needed:
|
|
14
20
|
|
|
@@ -43,11 +49,10 @@ GQRX accepts a device string directly in its configuration dialog:
|
|
|
43
49
|
driver=aaronia,url=http://localhost:54664
|
|
44
50
|
```
|
|
45
51
|
|
|
46
|
-
Select the sample rate from GQRX's list. The plugin advertises
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
necessarily unsupported.
|
|
52
|
+
Select the sample rate from GQRX's list. The plugin advertises the
|
|
53
|
+
device's real ladder: 61.44 MHz halved down to 120 kHz, which the RTSA
|
|
54
|
+
GUI labels Full through 1/512. The hardware produces no other rate, so a
|
|
55
|
+
request in between snaps to the nearest rung and logs a warning.
|
|
51
56
|
|
|
52
57
|
The single gain element is `REF`, the Aaronia reference level in dBm. It
|
|
53
58
|
is not an amplifier gain: raising it reduces sensitivity. Start near
|
|
@@ -590,6 +590,44 @@ Data captured using antennas with location or directional information.
|
|
|
590
590
|
| `azimuth` | Azimuth of directional antenna |
|
|
591
591
|
| `declination` | Declination of directional antenna |
|
|
592
592
|
|
|
593
|
+
## What the native SDK samples tell us about this API
|
|
594
|
+
|
|
595
|
+
Aaronia's [C++ SDK samples](https://github.com/Aaronia-Open-source/RTSA-API-Samples)
|
|
596
|
+
drive the same hardware through a different transport, so some of what
|
|
597
|
+
they establish applies here and some does not.
|
|
598
|
+
|
|
599
|
+
### GPS needs enabling, over HTTP too
|
|
600
|
+
|
|
601
|
+
The `device/gpsmode` and `device/sclksource` keys the `GPSTime` sample
|
|
602
|
+
writes are the same keys this config tree exposes, so the prerequisite
|
|
603
|
+
is identical. A device ships with `gpsmode` set to `Disabled` and takes
|
|
604
|
+
its stream clock from whatever `sclksource` names, which on the system
|
|
605
|
+
tested here was `10MHz` rather than GPS. Timestamps are not
|
|
606
|
+
GPS-disciplined until both change, and nothing reports an error
|
|
607
|
+
meanwhile.
|
|
608
|
+
|
|
609
|
+
Observed on a V6 ECO: `gpsmode` offers `Disabled`, `Location`, `Time`
|
|
610
|
+
and `Location and Time`; `sclksource` offers `Consumer`, `Oscillator`,
|
|
611
|
+
`GPS`, `PPS`, `10MHz` and three `... Provider` variants.
|
|
612
|
+
|
|
613
|
+
### The rate ladder carries over
|
|
614
|
+
|
|
615
|
+
`decimation0` takes the same labels as the SDK's `main/decimation`,
|
|
616
|
+
`"Full"` through `"1 / 512"`, verified by writing one over HTTP. Both
|
|
617
|
+
transports drive the same divider.
|
|
618
|
+
|
|
619
|
+
### Two things that do not carry over
|
|
620
|
+
|
|
621
|
+
- **Receiver-channel selection.** There is no `device/receiverchannel`
|
|
622
|
+
here. The tree exposes per-channel settings instead — `centerfreq0`
|
|
623
|
+
and `centerfreq1`, `decimation0` and `decimation1`, `rfchsource0` and
|
|
624
|
+
`rfchsource1` — and which channels reach the stream is a property of
|
|
625
|
+
the RTSA mission graph. The SDK's `Rx12` versus `Rx1+Rx2` distinction
|
|
626
|
+
has no equivalent.
|
|
627
|
+
- **Stream indices.** The SDK separates IQ from spectra by packet stream
|
|
628
|
+
index. HTTP selects data with the `input=` parameter and reports the
|
|
629
|
+
kind in each packet's `payload` field.
|
|
630
|
+
|
|
593
631
|
## Authorization & Licensing
|
|
594
632
|
|
|
595
633
|
### HTTP Streaming vs Remote Configuration
|
|
@@ -86,30 +86,39 @@ cargo run --example http_iq_quickstart --features http -- 2440e6 15.36e6 http://
|
|
|
86
86
|
pip install python-aaronia
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
+
Check the server first. The command ships with the package and reports
|
|
90
|
+
what is wrong, and how to fix it:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
aaronia-doctor http://localhost:54664
|
|
94
|
+
```
|
|
95
|
+
|
|
89
96
|
```python
|
|
90
97
|
import aaronia
|
|
91
98
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
cfg.sample_rate = 15.36e6
|
|
96
|
-
|
|
97
|
-
src = aaronia.AaroniaSource()
|
|
98
|
-
src.start_streaming(cfg)
|
|
99
|
-
samples = src.read_samples_numpy(65536) # numpy complex64
|
|
100
|
-
print(samples[:4], src.cumulative_drops())
|
|
101
|
-
src.stop_streaming()
|
|
99
|
+
with aaronia.open("http://localhost:54664", freq=2.44e9, bandwidth=10e6) as src:
|
|
100
|
+
samples = src.read_samples_numpy(65536) # numpy complex64
|
|
101
|
+
print(samples[:4], src.cumulative_drops())
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
+
`bandwidth` picks a sample rate the hardware can run. Pass `rate=` to
|
|
105
|
+
name one exactly, or build an `AaroniaConfig` for the full set of
|
|
106
|
+
options.
|
|
107
|
+
|
|
104
108
|
### SoapySDR
|
|
105
109
|
|
|
110
|
+
Download the plugin archive for your platform from a
|
|
111
|
+
[release](https://github.com/isaacbentley/sdr-aaronia-rs/releases),
|
|
112
|
+
unpack it, and run the bundled installer:
|
|
113
|
+
|
|
106
114
|
```bash
|
|
115
|
+
./install.sh
|
|
107
116
|
SoapySDRUtil --probe="driver=aaronia,url=http://localhost:54664"
|
|
108
117
|
```
|
|
109
118
|
|
|
110
119
|
[APPS.md](APPS.md) covers GQRX, SDR++ and GNU Radio.
|
|
111
120
|
[../soapy-aaronia/README.md](../soapy-aaronia/README.md) covers
|
|
112
|
-
installing
|
|
121
|
+
installing by hand and building from source.
|
|
113
122
|
|
|
114
123
|
## 4. Troubleshooting
|
|
115
124
|
|
|
@@ -876,3 +876,61 @@ the Aaronia Software License Agreement governs use of the SDK itself (see
|
|
|
876
876
|
[Licensing Considerations](#licensing-considerations)); this community document
|
|
877
877
|
describes only the public interface. "Aaronia", "RTSA", and "Spectran" are the
|
|
878
878
|
property of Aaronia AG.
|
|
879
|
+
|
|
880
|
+
## Facts taken from Aaronia's published samples
|
|
881
|
+
|
|
882
|
+
The following come from
|
|
883
|
+
[Aaronia-Open-source/RTSA-API-Samples](https://github.com/Aaronia-Open-source/RTSA-API-Samples),
|
|
884
|
+
read in full. They are vendor code rather than vendor documentation, but
|
|
885
|
+
they are the closest thing to an authoritative statement of how the API
|
|
886
|
+
is meant to be driven, and this crate's native-SDK paths cannot be
|
|
887
|
+
tested here.
|
|
888
|
+
|
|
889
|
+
### Both receivers, two different modes
|
|
890
|
+
|
|
891
|
+
`device/receiverchannel` takes four values, and the last two are not
|
|
892
|
+
interchangeable:
|
|
893
|
+
|
|
894
|
+
| Value | Delivery |
|
|
895
|
+
| --- | --- |
|
|
896
|
+
| `Rx1`, `Rx2` | One input, one stream |
|
|
897
|
+
| `Rx12` | Both inputs interleaved into **one** stream: four floats per sample, `[I0, Q0, I1, Q1]`, read from stream 0 |
|
|
898
|
+
| `Rx1+Rx2` | Both inputs as **two independent streams**, fetched and consumed separately at indices 0 and 1 |
|
|
899
|
+
|
|
900
|
+
`RawIQ2RXInterleave` uses the first, `RawIQ2RX` the second. This crate
|
|
901
|
+
reads a single stream and deinterleaves it, so it writes `Rx12`.
|
|
902
|
+
|
|
903
|
+
### V6 against V6 ECO
|
|
904
|
+
|
|
905
|
+
| | Full V6 | V6 ECO |
|
|
906
|
+
| --- | --- | --- |
|
|
907
|
+
| Family string | `spectranv6` | `spectranv6eco` |
|
|
908
|
+
| `device/receiverchannel` | Set explicitly | Never set: one receiver |
|
|
909
|
+
| `device/receiverclock` | Set, `"92MHz"` or `"245MHz"` | Never set: fixed |
|
|
910
|
+
| Spectrum packets | Stream index 2 | Stream index 0 |
|
|
911
|
+
| Raw-mode open string | `spectranv6/raw` | `spectranv6eco/rtsa` |
|
|
912
|
+
|
|
913
|
+
The clock matters beyond configuration: with `span * 1.5 <=
|
|
914
|
+
receiverclock`, a V6 on the fast clock reaches roughly 163 MHz of span
|
|
915
|
+
where the ECO's fixed clock allows 61.44 MHz.
|
|
916
|
+
|
|
917
|
+
### Sample rates
|
|
918
|
+
|
|
919
|
+
`main/decimation` accepts either the label (`"1 / 64"`) or the index
|
|
920
|
+
(`6`). Combined with the clock, that gives the rate ladder: the top rate
|
|
921
|
+
is `receiverclock / 1.5` and each step halves it.
|
|
922
|
+
|
|
923
|
+
### Transmitting
|
|
924
|
+
|
|
925
|
+
The samples flag the first packet `SEGMENT_START | STREAM_START`, the
|
|
926
|
+
last `SEGMENT_END | STREAM_END`, and everything between `0`. The
|
|
927
|
+
transceiver samples additionally send a zero-length packet carrying only
|
|
928
|
+
`STREAM_START`, timestamped at the master stream clock, before real
|
|
929
|
+
data, to improve startup synchronisation.
|
|
930
|
+
|
|
931
|
+
### Keys this crate does not use
|
|
932
|
+
|
|
933
|
+
`device/outputformat` (`"iq"` or `"spectra"`), `main/demodcenterfreq`
|
|
934
|
+
and `main/demodspanfreq`, `main/centerfreqtx` and `main/centerfreqrx`
|
|
935
|
+
for independent transceiver tuning, `calibration/preamp`, and the
|
|
936
|
+
read-only `boostusbbytessecond` throughput reading.
|
|
@@ -21,7 +21,10 @@ SDK are marked unverified.
|
|
|
21
21
|
| SoapySDR plugin RX | HTTP | Verified manually (~9.7 Msps via `SoapySDRUtil`); no automated live test, as a `soapysdr` dev-dependency would make `cargo test` unbuildable without system SoapySDR |
|
|
22
22
|
| Python bindings RX | HTTP | Verified manually (NumPy and Arrow); no automated live test |
|
|
23
23
|
| TX (`UnifiedSink`, `aaronia_sink_*`, SoapySDR TX) | Native SDK | **Hardware-unverified**. No TX-licensed device available |
|
|
24
|
-
| Dual-channel RX (`Rx1And2`, `read_samples_dual`) | Native SDK | **Hardware-unverified**. Requires a full V6 |
|
|
24
|
+
| Dual-channel RX (`Rx1And2`, `read_samples_dual`) | Native SDK | **Hardware-unverified**. Requires a full V6. Selects `Rx12`, the interleaved single-stream mode, matching how this crate reads |
|
|
25
|
+
| Spectra reads (`read_spectra`) | Native SDK | **Hardware-unverified**. Packet layout and stream index follow Aaronia's `RawSpectrum` sample |
|
|
26
|
+
| Device-family detection (`open_detected_device`) | Native SDK | **Hardware-unverified**. Enumerates each known family in turn |
|
|
27
|
+
| Sample-rate ladder and usable bandwidth | Both | **Live-verified at the default clock** on a V6 ECO, rung by rung. Faster receiver clocks are inferred from the documented constraint, not measured |
|
|
25
28
|
| GPS hardware time | Native SDK | **Hardware-unverified** |
|
|
26
29
|
| Native SDK capture generally | Native SDK | **Hardware-unverified**; compiled and unit-tested in a Linux VM each release |
|
|
27
30
|
| HTTP TX push (`/sample`) | HTTP | Endpoint exercised live; RF output not measured |
|
|
@@ -40,6 +43,15 @@ cargo test --all-features --test live_smoke -- --ignored --nocapture
|
|
|
40
43
|
Contributions that convert an unverified row, particularly from users
|
|
41
44
|
with a full V6 or a TX licence, are welcome.
|
|
42
45
|
|
|
46
|
+
Native-SDK entries carry a second qualifier worth stating plainly: they
|
|
47
|
+
are not merely untested, they run on a platform this project cannot
|
|
48
|
+
build on locally. They are compile-checked for Linux and Windows and
|
|
49
|
+
their logic is drawn from Aaronia's published samples, which caught
|
|
50
|
+
three real defects, but nothing has executed them against a device.
|
|
51
|
+
|
|
52
|
+
If you have a full V6 or a TX licence, closing one of these rows is the
|
|
53
|
+
single most valuable contribution you can make to this crate.
|
|
54
|
+
|
|
43
55
|
## Related
|
|
44
56
|
|
|
45
57
|
- [QUICKSTART.md](QUICKSTART.md) — setting up an RTSA-Suite mission.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Homebrew tap
|
|
2
|
+
|
|
3
|
+
`soapy-aaronia.rb` installs the prebuilt SoapySDR plugin from a GitHub
|
|
4
|
+
release. It is kept here so it is versioned alongside the code it
|
|
5
|
+
installs, and published from a tap repository.
|
|
6
|
+
|
|
7
|
+
## One-time setup
|
|
8
|
+
|
|
9
|
+
Create a public repository named `homebrew-tap` under the same owner as
|
|
10
|
+
this one. The name is fixed: Homebrew maps `owner/tap` to
|
|
11
|
+
`github.com/owner/homebrew-tap`. Add the formula as
|
|
12
|
+
`Formula/soapy-aaronia.rb`.
|
|
13
|
+
|
|
14
|
+
Users then install with:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
brew install isaacbentley/tap/soapy-aaronia
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Homebrew pulls in SoapySDR, drops the module into
|
|
21
|
+
`lib/SoapySDR/modules0.8`, and its `brew test` step confirms
|
|
22
|
+
`SoapySDRUtil --check=aaronia` reports the driver as present.
|
|
23
|
+
|
|
24
|
+
## Each release
|
|
25
|
+
|
|
26
|
+
The release workflow renders this file against the published archives
|
|
27
|
+
and attaches the result to the release as `soapy-aaronia.rb`, with the
|
|
28
|
+
version and both SHA-256 checksums filled in. Copy that file into the
|
|
29
|
+
tap repository as `Formula/soapy-aaronia.rb` and commit. Nothing needs
|
|
30
|
+
hashing by hand.
|
|
31
|
+
|
|
32
|
+
Before pushing, `brew audit --strict --online soapy-aaronia` checks the
|
|
33
|
+
formula, and `brew install --build-from-source` proves it works.
|
|
34
|
+
|
|
35
|
+
## Coverage
|
|
36
|
+
|
|
37
|
+
The formula covers macOS on Apple silicon and Linux on x86-64, matching
|
|
38
|
+
the platforms the release builds. Elsewhere, `brew` has nothing to
|
|
39
|
+
install and the plugin has to be built from source; see
|
|
40
|
+
[soapy-aaronia/README.md](../../soapy-aaronia/README.md).
|
|
41
|
+
|
|
42
|
+
The module directory is pinned to `modules0.8`, which is SoapySDR's ABI
|
|
43
|
+
0.8 path. A future SoapySDR ABI bump requires changing that line.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Homebrew formula for the Aaronia SoapySDR plugin.
|
|
2
|
+
#
|
|
3
|
+
# Lives here so it is versioned with the code it installs. To publish it,
|
|
4
|
+
# copy this file into a tap repository named homebrew-tap, as
|
|
5
|
+
# Formula/soapy-aaronia.rb, and users get:
|
|
6
|
+
#
|
|
7
|
+
# brew install <owner>/tap/soapy-aaronia
|
|
8
|
+
#
|
|
9
|
+
# At each release, update `version`, both `sha256` values (from the
|
|
10
|
+
# release archives) and nothing else. `brew audit --strict soapy-aaronia`
|
|
11
|
+
# checks the formula before publishing.
|
|
12
|
+
class SoapyAaronia < Formula
|
|
13
|
+
desc "SoapySDR plugin for Aaronia SPECTRAN V6 spectrum analyzers"
|
|
14
|
+
homepage "https://github.com/isaacbentley/sdr-aaronia-rs"
|
|
15
|
+
version "0.7.0"
|
|
16
|
+
license "GPL-3.0-or-later"
|
|
17
|
+
|
|
18
|
+
# Prebuilt modules: the plugin statically links its Rust core, so there
|
|
19
|
+
# is nothing to build here and no Rust toolchain to depend on.
|
|
20
|
+
on_macos do
|
|
21
|
+
on_arm do
|
|
22
|
+
url "https://github.com/isaacbentley/sdr-aaronia-rs/releases/download/v#{version}/SoapyAaronia-#{version}-macos-arm64.tar.gz"
|
|
23
|
+
sha256 "REPLACE_WITH_SHA256_OF_MACOS_ARM64_ARCHIVE"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
on_linux do
|
|
28
|
+
on_intel do
|
|
29
|
+
url "https://github.com/isaacbentley/sdr-aaronia-rs/releases/download/v#{version}/SoapyAaronia-#{version}-linux-x86_64.tar.gz"
|
|
30
|
+
sha256 "REPLACE_WITH_SHA256_OF_LINUX_X86_64_ARCHIVE"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
depends_on "soapysdr"
|
|
35
|
+
|
|
36
|
+
def install
|
|
37
|
+
# SoapySDR loads modules from a versioned directory under its own
|
|
38
|
+
# prefix; Homebrew links this into place for us.
|
|
39
|
+
(lib/"SoapySDR/modules0.8").install "libaaroniaSupport.so"
|
|
40
|
+
doc.install "INSTALL.md"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
test do
|
|
44
|
+
# Proves the module loads into SoapySDR rather than merely existing.
|
|
45
|
+
assert_match "aaronia", shell_output("#{Formula["soapysdr"].bin}/SoapySDRUtil --check=aaronia")
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -18,6 +18,11 @@ classifiers = [
|
|
|
18
18
|
# Version comes from python-aaronia/Cargo.toml (single source of truth).
|
|
19
19
|
dynamic = ["version"]
|
|
20
20
|
|
|
21
|
+
# A real command, so checking a setup needs no code:
|
|
22
|
+
# aaronia-doctor http://localhost:54664
|
|
23
|
+
[project.scripts]
|
|
24
|
+
aaronia-doctor = "aaronia:doctor_cli"
|
|
25
|
+
|
|
21
26
|
[project.urls]
|
|
22
27
|
Repository = "https://github.com/isaacbentley/sdr-aaronia-rs"
|
|
23
28
|
Changelog = "https://github.com/isaacbentley/sdr-aaronia-rs/blob/main/CHANGELOG.md"
|