python-aaronia 0.8.2__tar.gz → 0.9.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.8.2 → python_aaronia-0.9.0}/CHANGELOG.md +129 -72
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/Cargo.lock +2 -2
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/Cargo.toml +1 -1
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/PKG-INFO +1 -1
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/PLUGINS.md +67 -1
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/docs/VERIFICATION.md +3 -1
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/include/aaronia.h +47 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/python-aaronia/Cargo.toml +1 -1
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/soapy-aaronia/AaroniaSoapyDevice.cpp +173 -16
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/soapy-aaronia/AaroniaSoapyDevice.hpp +29 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/soapy-aaronia/Registration.cpp +43 -11
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/src/c_api.rs +190 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/src/file_source.rs +23 -1
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/src/http_endpoints.rs +209 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/src/lib.rs +10 -7
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/src/native_sdk.rs +37 -21
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/src/unified_source.rs +38 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/tests/live_smoke.rs +68 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/tests/native_sdk_live.rs +11 -9
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/.cargo/config.toml +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/.gitattributes +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/.gitignore +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/CONTRIBUTING.md +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/DESIGN.md +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/LICENSE +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/README.md +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/benches/decompress_block.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/benches/deinterleave_dual_iq.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/benches/parse_int16_packet.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/benches/rtsa_open_and_read.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/deny.toml +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/docs/APPS.md +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/docs/FILESPEC.md +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/docs/HTTPSPEC.md +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/docs/QUICKSTART.md +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/docs/SDKSPEC.md +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/docs/USAGE.md +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/examples/channel_hopping.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/examples/device_control.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/examples/dump_metadata.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/examples/http_iq_quickstart.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/examples/native_sdk_basic.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/examples/native_sdk_transmit.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/examples/noaa_scanner.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/examples/python_arrow_example.py +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/examples/read_rtsa_file.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/examples/soapy_python_example.py +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/packaging/homebrew/README.md +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/packaging/homebrew/soapy-aaronia.rb +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/pyproject.toml +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/python-aaronia/README.md +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/python-aaronia/aaronia.pyi +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/python-aaronia/src/lib.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/python-aaronia/test_basic.py +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/scripts/ci-local.sh +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/scripts/fake-rtsa-server.py +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/scripts/native-sdk-validate.ps1 +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/scripts/sdk-container-test.sh +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/scripts/validate-iq-live.py +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/soapy-aaronia/CMakeLists.txt +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/soapy-aaronia/README.md +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/soapy-aaronia/packaging/install.ps1 +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/soapy-aaronia/packaging/install.sh +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/src/decompression.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/src/detection.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/src/error.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/src/http_sink.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/src/http_source.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/src/http_streaming.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/src/link_budget.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/src/sdk_sink.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/src/sdk_source.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/src/sdr_source_impl.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/src/seify_impl.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/src/unified_sink.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/src/utils.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/tests/c_api_test.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/tests/http_mock_test.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/tests/http_resilience_test.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/tests/http_sink_test.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/tests/integration_test.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/tests/native_sdk_load.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/tests/properties.proptest-regressions +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/tests/properties.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/tests/rtsa_negative_test.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/tests/sdr_source_impl_test.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/tests/spec_coverage.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/tests/test_cw_mag.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.9.0}/tests/test_cw_meta.rs +0 -0
|
@@ -4,80 +4,59 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
-
### Performance
|
|
8
|
-
- **The int16 and float16 IQ decoders are ~23% faster end to end.** Both
|
|
9
|
-
built their output with `Vec::with_capacity` then `push` per sample,
|
|
10
|
-
which carries a capacity check the compiler cannot elide and which was
|
|
11
|
-
blocking vectorisation. Collecting from the slice iterator instead —
|
|
12
|
-
`TrustedLen`, so the vector is sized once — measured 495 to 2433 MS/s
|
|
13
|
-
on the decode loop alone, and 123.5 to 152 MS/s over the whole HTTP
|
|
14
|
-
path including framing and transport. float32 was already a single
|
|
15
|
-
`copy_nonoverlapping` and is unchanged.
|
|
16
|
-
|
|
17
|
-
Worth stating what this does not buy: at 605 MB/s the crate is about
|
|
18
|
-
2.5x the fastest a V6 can produce and 6x a WiFi 6E link, so it was not
|
|
19
|
-
the bottleneck before and is not now. What it buys is CPU left over
|
|
20
|
-
for whatever consumes the samples.
|
|
21
|
-
|
|
22
7
|
### Added
|
|
23
|
-
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
8
|
+
- **SoapySDR: device sensors.** The plugin exposed one sensor
|
|
9
|
+
(`cumulative_drops`); it now surfaces the device's live telemetry from
|
|
10
|
+
`/healthstatus` — FPGA and frontend temperature, ADC headroom (dB below
|
|
11
|
+
full scale, so a client can raise the reference level before it clips),
|
|
12
|
+
USB and DSP buffer fill, error and overflow rates, GPS satellites and
|
|
13
|
+
position. `listSensors` / `readSensor` / `getSensorInfo`, read through a
|
|
14
|
+
dedicated connection and briefly cached, so polling them during a
|
|
15
|
+
capture neither stalls the sample stream nor costs a fetch per key.
|
|
16
|
+
HTTP backend only: the native SDK's own `AARTSAAPI_ConfigHealth` tree
|
|
17
|
+
reads all zeros in raw-SDK mode — the live telemetry is computed and
|
|
18
|
+
populated by RTSA-Suite, the managing application, not by the raw SDK,
|
|
19
|
+
confirmed by dumping the tree live from a V6 ECO — so over `sdk=true`
|
|
20
|
+
the plugin reports only `cumulative_drops`, which is real and
|
|
21
|
+
client-side. Verified live over HTTP on a V6 ECO.
|
|
22
|
+
- **SoapySDR: bandwidth API.** `setBandwidth` / `getBandwidth` /
|
|
23
|
+
`listBandwidths` / `getBandwidthRange`. The device's alias-free
|
|
24
|
+
bandwidth is 0.8x its sample rate; these expose that as SoapySDR's
|
|
25
|
+
separate knob, mapping a requested bandwidth to a sample rate and
|
|
26
|
+
driving the already-verified `setSampleRate` — no new device-write path.
|
|
27
|
+
Verified on a V6 ECO: 15.36 MS/s reports 12.288 MHz, `setBandwidth`
|
|
28
|
+
snaps to a rung.
|
|
29
|
+
- **SoapySDR: the native SDK is discoverable.** `find` advertises a
|
|
30
|
+
second `sdk=true` device beside the HTTP one whenever the SDK is
|
|
31
|
+
installed, and `sdk=true` / `serial=` force the native backend instead
|
|
32
|
+
of silently falling back to localhost HTTP. Verified streaming 15.357
|
|
33
|
+
MS/s over the SDK through the plugin from Python.
|
|
34
|
+
- **C ABI.** `aaronia_source_read_sensors` (fills a value struct, `NaN`
|
|
35
|
+
for absent — no ownership, nothing to free);
|
|
36
|
+
`aaronia_source_builder_force_source_type` and `aaronia_sdk_installed`,
|
|
37
|
+
the C equivalent of `force_native_sdk`, which C had no way to request;
|
|
38
|
+
and the stateless bandwidth helpers `aaronia_usable_bandwidth_hz` /
|
|
39
|
+
`aaronia_iq_sample_rate_for_bandwidth`. `DeviceSensors` is the Rust type
|
|
40
|
+
behind the first.
|
|
30
41
|
|
|
31
|
-
###
|
|
32
|
-
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
MB/s against 122.9 theoretical at 15.36 MS/s. Against that baseline the
|
|
50
|
-
codec buys 2.74x at level 1, 4.43x at level 5 and 13.10x at level 9;
|
|
51
|
-
even level 1 undercuts plain `int16` while carrying float precision.
|
|
52
|
-
Ratios hold at 3.84 MS/s too.
|
|
53
|
-
|
|
54
|
-
HTTPSPEC had documented `format=rtsa` only as the thing a typo falls
|
|
55
|
-
back to — "a completely different wire format rather than an error" —
|
|
56
|
-
and this release had gone on to claim compression was neither offered
|
|
57
|
-
nor useful. Both are corrected. Generic HTTP compression is still not
|
|
58
|
-
available on `/stream` and still would not help (zlib manages 1.07x on
|
|
59
|
-
`float32`); Aaronia's codec wins by being lossy and signal-aware.
|
|
60
|
-
|
|
61
|
-
This crate cannot use it for IQ, tested rather than assumed: a real
|
|
62
|
-
compressed payload handed to `Decompressor::decompress` comes back
|
|
63
|
-
rejected as proprietary `DSPT_IQ`, the same wall that stops compressed
|
|
64
|
-
IQ *files*. `format=rtsa` also defaults to `mCompression=1`, so only
|
|
65
|
-
`compression=0` is decodable and that is 20% larger than plain `int16`.
|
|
66
|
-
Spectra should decode, `DSPT_SPECTRA` being documented, but this
|
|
67
|
-
mission has no spectra input to try.
|
|
68
|
-
|
|
69
|
-
### Performance
|
|
70
|
-
- **The control plane is now requested compressed.** `reqwest` gains the
|
|
71
|
-
`deflate` and `gzip` features, so the client sends
|
|
72
|
-
`Accept-Encoding: gzip,deflate` where it previously sent none. Measured
|
|
73
|
-
against the device: `/remoteconfig` 17,432 bytes to 3,299 deflated,
|
|
74
|
-
`/healthstatus` 6,100 to 1,436. Both are read at every device open, and
|
|
75
|
-
`/healthstatus` again on each stream-gap report. No effect on `/stream`,
|
|
76
|
-
which the server does not compress at any level.
|
|
77
|
-
- The reader channel's size comment claimed ~157 KiB chunks and ~10 MB of
|
|
78
|
-
queue. Measured against a live server it is 64 KiB for 71% of chunks,
|
|
79
|
-
so the queue is ~4 MB, about 45 ms at the 88 MB/s a WiFi 6E path
|
|
80
|
-
delivers.
|
|
42
|
+
### Fixed
|
|
43
|
+
- **File playback reported the decompression time as the capture time.**
|
|
44
|
+
A DSPT_IQ-compressed `.rtsa` is decompressed through RTSAFileTool, which
|
|
45
|
+
writes a fresh header stamped with the moment of conversion, so the
|
|
46
|
+
re-opened file reported *now* — a 2020 capture read as 2026. The
|
|
47
|
+
original header's `creation_time`, parsed before compression was
|
|
48
|
+
detected, is now carried across the decompression, and the derived
|
|
49
|
+
start/end fall-backs follow it. Surfaced the first time the fixture test
|
|
50
|
+
ran on a machine with RTSA-Suite installed.
|
|
51
|
+
- **The SoapySDR plugin could not load the SDK inside a host that carries
|
|
52
|
+
its own Qt6.** The loader was given `LOAD_LIBRARY_SEARCH_DEFAULT_DIRS`,
|
|
53
|
+
which searches the host application's directory first; a GNU Radio
|
|
54
|
+
(radioconda) install holds a different Qt6 build there under the same
|
|
55
|
+
names, and the load failed. It now searches only the DLL's own
|
|
56
|
+
directory, the SDK install root, and System32.
|
|
57
|
+
- **The Seify native-SDK test panicked on teardown.** `AaroniaSeifyDevice`
|
|
58
|
+
owns a tokio runtime, and dropping one inside an async context is a
|
|
59
|
+
tokio panic; the test is synchronous now.
|
|
81
60
|
|
|
82
61
|
## [v0.8.2] - 2026-09-08
|
|
83
62
|
|
|
@@ -169,6 +148,84 @@ MSVC against radioconda's SoapySDR, streams 15.357 MS/s over the SDK
|
|
|
169
148
|
into Python; see [docs/VERIFICATION.md](docs/VERIFICATION.md) for the
|
|
170
149
|
two limits found on the way.
|
|
171
150
|
|
|
151
|
+
|
|
152
|
+
_Also in 0.8.2 (recorded here on the 0.9.0 pass; these landed across the 0.8.x docs/perf commits and were never restamped):_
|
|
153
|
+
|
|
154
|
+
### Performance
|
|
155
|
+
- **The int16 and float16 IQ decoders are ~23% faster end to end.** Both
|
|
156
|
+
built their output with `Vec::with_capacity` then `push` per sample,
|
|
157
|
+
which carries a capacity check the compiler cannot elide and which was
|
|
158
|
+
blocking vectorisation. Collecting from the slice iterator instead —
|
|
159
|
+
`TrustedLen`, so the vector is sized once — measured 495 to 2433 MS/s
|
|
160
|
+
on the decode loop alone, and 123.5 to 152 MS/s over the whole HTTP
|
|
161
|
+
path including framing and transport. float32 was already a single
|
|
162
|
+
`copy_nonoverlapping` and is unchanged.
|
|
163
|
+
|
|
164
|
+
Worth stating what this does not buy: at 605 MB/s the crate is about
|
|
165
|
+
2.5x the fastest a V6 can produce and 6x a WiFi 6E link, so it was not
|
|
166
|
+
the bottleneck before and is not now. What it buys is CPU left over
|
|
167
|
+
for whatever consumes the samples.
|
|
168
|
+
|
|
169
|
+
### Added
|
|
170
|
+
- **`scripts/fake-rtsa-server.py`** serves `/stream` in the real wire
|
|
171
|
+
format at loopback speed, so a decode path can be measured without a
|
|
172
|
+
device or a network. It is how the figures above were taken: over any
|
|
173
|
+
real link the transport dominates and a CPU change is invisible. Its
|
|
174
|
+
docstring carries the caveat that goes with it — over loopback hyper's
|
|
175
|
+
adaptive read buffer never leaves its 8 KiB floor, so kernel time there
|
|
176
|
+
is a property of the harness, not of the crate.
|
|
177
|
+
|
|
178
|
+
### Documentation
|
|
179
|
+
- **`rate_reduction` is time compression, not a sample-rate divider.**
|
|
180
|
+
Five places described it as reducing the rate or optimising bandwidth.
|
|
181
|
+
It thins frames over time — the operation the `waterfall` payload is
|
|
182
|
+
described by — so a continuous IQ stream, having no frames, is
|
|
183
|
+
unaffected: measured at factors of 2, 10 and 64, `sampleFrequency`
|
|
184
|
+
holds at 15,359,988 Hz and the byte rate does not move. That is the
|
|
185
|
+
parameter behaving as specified on a payload it was not meant for.
|
|
186
|
+
`live_stream_rate_reduction_and_scale` had asserted only that packets
|
|
187
|
+
arrived, which is true either way, so nothing caught the wrong
|
|
188
|
+
description; it now pins the IQ behaviour.
|
|
189
|
+
- **The stream can be compressed, up to 6.55x, via `format=rtsa`.**
|
|
190
|
+
Captured from RTSA-Suite's own HTTP Client block:
|
|
191
|
+
`GET /stream?format=rtsa&rate_reduction=8&input=main&compression=5&rate_adaption=0`.
|
|
192
|
+
`format=rtsa` streams the file container and is the only format that
|
|
193
|
+
accepts `compression=N`, which applies the file format's own lossy
|
|
194
|
+
codec. The container carries `float32` (`mSampleType` 11, `DSST_F32N`),
|
|
195
|
+
so level 0 is plain float32 with under 1% of chunk overhead — 123.0
|
|
196
|
+
MB/s against 122.9 theoretical at 15.36 MS/s. Against that baseline the
|
|
197
|
+
codec buys 2.74x at level 1, 4.43x at level 5 and 13.10x at level 9;
|
|
198
|
+
even level 1 undercuts plain `int16` while carrying float precision.
|
|
199
|
+
Ratios hold at 3.84 MS/s too.
|
|
200
|
+
|
|
201
|
+
HTTPSPEC had documented `format=rtsa` only as the thing a typo falls
|
|
202
|
+
back to — "a completely different wire format rather than an error" —
|
|
203
|
+
and this release had gone on to claim compression was neither offered
|
|
204
|
+
nor useful. Both are corrected. Generic HTTP compression is still not
|
|
205
|
+
available on `/stream` and still would not help (zlib manages 1.07x on
|
|
206
|
+
`float32`); Aaronia's codec wins by being lossy and signal-aware.
|
|
207
|
+
|
|
208
|
+
This crate cannot use it for IQ, tested rather than assumed: a real
|
|
209
|
+
compressed payload handed to `Decompressor::decompress` comes back
|
|
210
|
+
rejected as proprietary `DSPT_IQ`, the same wall that stops compressed
|
|
211
|
+
IQ *files*. `format=rtsa` also defaults to `mCompression=1`, so only
|
|
212
|
+
`compression=0` is decodable and that is 20% larger than plain `int16`.
|
|
213
|
+
Spectra should decode, `DSPT_SPECTRA` being documented, but this
|
|
214
|
+
mission has no spectra input to try.
|
|
215
|
+
|
|
216
|
+
### Performance
|
|
217
|
+
- **The control plane is now requested compressed.** `reqwest` gains the
|
|
218
|
+
`deflate` and `gzip` features, so the client sends
|
|
219
|
+
`Accept-Encoding: gzip,deflate` where it previously sent none. Measured
|
|
220
|
+
against the device: `/remoteconfig` 17,432 bytes to 3,299 deflated,
|
|
221
|
+
`/healthstatus` 6,100 to 1,436. Both are read at every device open, and
|
|
222
|
+
`/healthstatus` again on each stream-gap report. No effect on `/stream`,
|
|
223
|
+
which the server does not compress at any level.
|
|
224
|
+
- The reader channel's size comment claimed ~157 KiB chunks and ~10 MB of
|
|
225
|
+
queue. Measured against a live server it is 64 KiB for 71% of chunks,
|
|
226
|
+
so the queue is ~4 MB, about 45 ms at the 88 MB/s a WiFi 6E path
|
|
227
|
+
delivers.
|
|
228
|
+
|
|
172
229
|
## [v0.8.1] - 2026-09-07
|
|
173
230
|
|
|
174
231
|
### Fixed
|
|
@@ -3092,7 +3092,7 @@ dependencies = [
|
|
|
3092
3092
|
|
|
3093
3093
|
[[package]]
|
|
3094
3094
|
name = "python-aaronia"
|
|
3095
|
-
version = "0.
|
|
3095
|
+
version = "0.9.0"
|
|
3096
3096
|
dependencies = [
|
|
3097
3097
|
"arrow",
|
|
3098
3098
|
"num-complex",
|
|
@@ -3621,7 +3621,7 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
|
|
3621
3621
|
|
|
3622
3622
|
[[package]]
|
|
3623
3623
|
name = "sdr-aaronia-rs"
|
|
3624
|
-
version = "0.
|
|
3624
|
+
version = "0.9.0"
|
|
3625
3625
|
dependencies = [
|
|
3626
3626
|
"anyhow",
|
|
3627
3627
|
"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.9.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.9.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+)
|
|
@@ -23,6 +23,11 @@ crate built with both features — `features = ["seify", "native-sdk"]` —
|
|
|
23
23
|
on Windows or Linux with RTSA-Suite PRO installed. Without the feature
|
|
24
24
|
the request is a clean error, never a fallback to HTTP.
|
|
25
25
|
|
|
26
|
+
`AaroniaSeifyDevice` owns a tokio runtime. Drop it from synchronous
|
|
27
|
+
code: dropping it inside an `async` context (a `#[tokio::test]`, a task)
|
|
28
|
+
is a tokio panic, "Cannot drop a runtime in a context where blocking is
|
|
29
|
+
not allowed".
|
|
30
|
+
|
|
26
31
|
```rust
|
|
27
32
|
use sdr_aaronia_rs::seify_impl::AaroniaSeifyDevice;
|
|
28
33
|
use seify::{Args, RxDevice, RxStreamer, DeviceInfo};
|
|
@@ -95,13 +100,29 @@ SoapySDRUtil --info
|
|
|
95
100
|
|
|
96
101
|
### Usage
|
|
97
102
|
|
|
98
|
-
|
|
103
|
+
`url=` connects to an RTSA-Suite HTTP server block, `file=` plays back a
|
|
104
|
+
recording, and `sdk=true` (or `serial=<device serial>`) opens the device
|
|
105
|
+
through the Aaronia native SDK — a build with `-DAARONIA_NATIVE_SDK=ON`
|
|
106
|
+
on a machine with RTSA-Suite PRO installed. A bare `driver=aaronia` keeps
|
|
107
|
+
its old meaning, the HTTP server on localhost; when the SDK is installed,
|
|
108
|
+
`SoapySDRUtil --find` lists a second, `sdk=true` entry beside it.
|
|
99
109
|
|
|
100
110
|
```bash
|
|
101
111
|
# Example testing with SoapySDRUtil
|
|
102
112
|
SoapySDRUtil --probe="driver=aaronia,url=http://localhost:54664"
|
|
113
|
+
SoapySDRUtil --probe="driver=aaronia,sdk=true"
|
|
103
114
|
```
|
|
104
115
|
|
|
116
|
+
Two things learned running the plugin on Windows inside a GNU Radio
|
|
117
|
+
(radioconda) install: that build's Python binding rejects the `dict`
|
|
118
|
+
form for every driver — `SoapySDR.Device("driver=aaronia,sdk=true")`
|
|
119
|
+
works where `SoapySDR.Device(dict(driver="aaronia", sdk="true"))` raises
|
|
120
|
+
"no match" — and a host process that has already loaded its own Qt6
|
|
121
|
+
(GNU Radio Companion itself) cannot also load the Aaronia SDK, which
|
|
122
|
+
brings a different Qt6; use `url=` from inside GRC. Loading the SDK from
|
|
123
|
+
a host whose *directory* merely contains Qt6 copies, such as
|
|
124
|
+
`SoapySDRUtil` in radioconda's `Library\bin`, works since 0.8.3.
|
|
125
|
+
|
|
105
126
|
In Python (using `SoapySDR` python bindings):
|
|
106
127
|
```python
|
|
107
128
|
import SoapySDR
|
|
@@ -161,3 +182,48 @@ In SoapySDR, you can access these metrics via the `readSensor()` API:
|
|
|
161
182
|
drops = sdr.readSensor("cumulative_drops")
|
|
162
183
|
print(f"Gaps seen: {drops}")
|
|
163
184
|
```
|
|
185
|
+
|
|
186
|
+
### Sensors
|
|
187
|
+
|
|
188
|
+
Over the **HTTP backend** the plugin also surfaces the device's live
|
|
189
|
+
telemetry from `/healthstatus` as SoapySDR sensors. `listSensors()`
|
|
190
|
+
returns only those the device is currently reporting:
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
for name in sdr.listSensors():
|
|
194
|
+
info = sdr.getSensorInfo(name)
|
|
195
|
+
print(f"{name} = {sdr.readSensor(name)} {info.units}")
|
|
196
|
+
# fpga_temp = 54.3 C
|
|
197
|
+
# frontend_temp = 65.1 C
|
|
198
|
+
# adc_range = 29.4 dB # headroom below full scale; near 0 is close to clipping
|
|
199
|
+
# usb_buffer = 0.0625 # transfer-buffer fill, fraction 0-1
|
|
200
|
+
# dsp_buffer = 0
|
|
201
|
+
# gps_satellites = 0
|
|
202
|
+
# ...plus cumulative_drops
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
`adc_range` is the one to watch when setting the reference level, and a
|
|
206
|
+
climbing `usb_buffer` is the first sign the host is not draining the
|
|
207
|
+
stream fast enough. The reads are briefly cached, so a probe's burst of
|
|
208
|
+
`readSensor` calls costs one HTTP fetch, and they go through a separate
|
|
209
|
+
connection from the sample stream — polling sensors during a capture does
|
|
210
|
+
not stall sample delivery. Over the **native SDK** backend only
|
|
211
|
+
`cumulative_drops` is reported: the raw SDK's own `AARTSAAPI_ConfigHealth`
|
|
212
|
+
tree reads all zeros (the live telemetry is populated by RTSA-Suite, the
|
|
213
|
+
managing application, not by the raw SDK), so exposing it there would
|
|
214
|
+
report a device permanently at 0 °C.
|
|
215
|
+
|
|
216
|
+
### Bandwidth
|
|
217
|
+
|
|
218
|
+
SoapySDR keeps sample rate and analog/usable bandwidth as separate knobs.
|
|
219
|
+
The RTSA's alias-free bandwidth is ~0.8x its sample rate, exposed here:
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
sdr.setBandwidth(SoapySDR.SOAPY_SDR_RX, 0, 10e6) # asks for ~10 MHz usable
|
|
223
|
+
print(sdr.getBandwidth(SoapySDR.SOAPY_SDR_RX, 0)) # the bandwidth actually delivered
|
|
224
|
+
print(sdr.listBandwidths(SoapySDR.SOAPY_SDR_RX, 0))
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
`setBandwidth` maps the request to the nearest sample-rate rung and
|
|
228
|
+
drives `setSampleRate`; the two stay consistent, so setting either
|
|
229
|
+
updates the other.
|
|
@@ -38,7 +38,9 @@ full V6 are marked unverified.
|
|
|
38
38
|
| Native SDK from Python (`aaronia.open(sdk=True)`) | Native SDK | **Verified manually** on the same machine against radioconda's Python 3.12: 15.363 MS/s of a 15.36 MS/s request into NumPy, retune, timestamps and drop counters populated |
|
|
39
39
|
| Native SDK from the C ABI | Native SDK | **Live-verified**: a builder with neither URL nor file auto-detects the SDK; there is still no explicit selector over C |
|
|
40
40
|
| Native SDK from Seify (`sdk=true`) | Native SDK | Compiled with `--features seify,native-sdk`; the live test exists but was not run on this pass |
|
|
41
|
-
| SoapySDR plugin over the native SDK | Native SDK | **Verified manually** on Windows 11: built with MSVC (VS 2022 Build Tools) against radioconda's SoapySDR 0.8.1, loads into it, and streams 15.357 MS/s of a 15.36 MS/s request into Python
|
|
41
|
+
| SoapySDR plugin over the native SDK | Native SDK | **Verified manually** on Windows 11: built with MSVC (VS 2022 Build Tools) against radioconda's SoapySDR 0.8.1, loads into it, and streams 15.357 MS/s of a 15.36 MS/s request into Python. `find` advertises a `sdk=true` device when the SDK is installed, and `sdk=true`/`serial=` open it; `SoapySDR.Device("driver=aaronia,sdk=true")` works (that build's binding rejects the `dict` form for every driver). The C++-host DLL-load failure — the loader reaching radioconda's own Qt6 first — is fixed in 0.9.0 (search restricted to the DLL dir, SDK root, System32) |
|
|
42
|
+
| SoapySDR bandwidth API (`setBandwidth`, `getBandwidth`, `listBandwidths`, `getBandwidthRange`) | Both | **Verified manually** on a V6 ECO over the native SDK: `getBandwidth` at 15.36 MS/s reports 12.288 MHz (0.8x), the ladder lists one usable bandwidth per rung, and `setBandwidth` snaps to a rung through the live-verified `setSampleRate`. Pure reparametrization, so the HTTP path follows |
|
|
43
|
+
| SoapySDR sensors (temperatures, ADC range, buffer fill, GPS) | HTTP | **Live-verified** on a V6 ECO: the crate's `live_device_sensors` reads real values over HTTP (FPGA temp, ADC range), and the plugin's `readSensor`/`listSensors` return the full set through `SoapySDRUtil` / Python. Parser also unit-tested against a `/healthstatus`-shaped tree. Over the native SDK the plugin reports only `cumulative_drops`: the raw SDK's `AARTSAAPI_ConfigHealth` tree reads all zeros (dumped live), the live telemetry being RTSA-Suite's, not the raw SDK's — so rich sensors are HTTP-only by design |
|
|
42
44
|
| HTTP TX push (`/sample`) | HTTP | Endpoint exercised live; RF output not measured |
|
|
43
45
|
|
|
44
46
|
"Live-verified" means an `#[ignore]`d test in
|
|
@@ -96,6 +96,24 @@ typedef struct FfiDeviceCapabilities {
|
|
|
96
96
|
const char* rx_antenna; // e.g. "RX1"; NULL if the mode names none
|
|
97
97
|
} FfiDeviceCapabilities;
|
|
98
98
|
|
|
99
|
+
/* Live sensor readings. A plain value struct the caller allocates; every
|
|
100
|
+
* field is a reading or NaN for "not reported". No ownership, nothing to
|
|
101
|
+
* free. Fill it with aaronia_source_read_sensors. */
|
|
102
|
+
typedef struct FfiDeviceSensors {
|
|
103
|
+
double fpga_temp_c;
|
|
104
|
+
double frontend_temp_c;
|
|
105
|
+
double board_power_w;
|
|
106
|
+
double adc_range_db; // ADC headroom below full scale, dB
|
|
107
|
+
double usb_buffer_fill; // fraction 0.0-1.0
|
|
108
|
+
double dsp_buffer_fill; // fraction 0.0-1.0
|
|
109
|
+
double errors_per_second;
|
|
110
|
+
double usb_overflows_per_second;
|
|
111
|
+
double dsp_overflows_per_second;
|
|
112
|
+
double gps_satellites;
|
|
113
|
+
double gps_latitude; // degrees; 0 with no fix
|
|
114
|
+
double gps_longitude; // degrees; 0 with no fix
|
|
115
|
+
} FfiDeviceSensors;
|
|
116
|
+
|
|
99
117
|
// Opaque pointers
|
|
100
118
|
typedef struct AaroniaSourceBuilder AaroniaSourceBuilder;
|
|
101
119
|
typedef struct AaroniaSource AaroniaSource;
|
|
@@ -116,6 +134,24 @@ void aaronia_source_builder_reference_level(AaroniaSourceBuilder* builder, doubl
|
|
|
116
134
|
void aaronia_source_builder_http_source(AaroniaSourceBuilder* builder, const char* base_url);
|
|
117
135
|
void aaronia_source_builder_file_source(AaroniaSourceBuilder* builder, const char* file_path);
|
|
118
136
|
void aaronia_source_builder_device_serial(AaroniaSourceBuilder* builder, const char* serial);
|
|
137
|
+
|
|
138
|
+
/* Pin the source to one backend instead of auto-detecting. Forcing
|
|
139
|
+
* NativeSdk makes a missing SDK a build error rather than a silent
|
|
140
|
+
* fallback to localhost HTTP. */
|
|
141
|
+
void aaronia_source_builder_force_source_type(AaroniaSourceBuilder *builder,
|
|
142
|
+
CAaroniaSourceType source_type);
|
|
143
|
+
|
|
144
|
+
/* Whether the native SDK library is present, by the search the builder
|
|
145
|
+
* uses; does not load it. */
|
|
146
|
+
bool aaronia_sdk_installed(void);
|
|
147
|
+
|
|
148
|
+
/* Alias-free bandwidth (Hz) delivered at an IQ sample rate; smaller than
|
|
149
|
+
* the rate. Stateless. */
|
|
150
|
+
double aaronia_usable_bandwidth_hz(double sample_rate_hz);
|
|
151
|
+
|
|
152
|
+
/* The IQ sample rate (Hz) whose alias-free bandwidth covers bandwidth_hz;
|
|
153
|
+
* the inverse of aaronia_usable_bandwidth_hz. Stateless. */
|
|
154
|
+
double aaronia_iq_sample_rate_for_bandwidth(double bandwidth_hz);
|
|
119
155
|
// RX channel selection (native-SDK backend): 0 = Rx1 (default),
|
|
120
156
|
// 1 = Rx2, 2 = Rx1+Rx2 dual capture (read with
|
|
121
157
|
// aaronia_source_read_samples_dual). Other values ignored.
|
|
@@ -172,6 +208,12 @@ void aaronia_source_info_free(FfiSourceInfo* info);
|
|
|
172
208
|
FfiDeviceCapabilities* aaronia_source_get_capabilities(AaroniaSource* source);
|
|
173
209
|
void aaronia_source_capabilities_free(FfiDeviceCapabilities* caps);
|
|
174
210
|
|
|
211
|
+
/* Fill *out with the device's live sensors. Returns true when the read
|
|
212
|
+
* completed (fields may be NaN where unreported; file/native-SDK backends
|
|
213
|
+
* complete all-NaN), false (out untouched) for a null pointer or a
|
|
214
|
+
* current-thread runtime. Blocking: one /healthstatus GET on HTTP. */
|
|
215
|
+
bool aaronia_source_read_sensors(AaroniaSource* source, FfiDeviceSensors* out);
|
|
216
|
+
|
|
175
217
|
// --- Sink FFI --- //
|
|
176
218
|
//
|
|
177
219
|
// WARNING: the whole TX path is hardware-unverified (driven per the
|
|
@@ -233,6 +275,11 @@ AaroniaFfiError aaronia_sink_write_samples(
|
|
|
233
275
|
HttpEndpointsClient* aaronia_endpoints_client_new(const char* base_url);
|
|
234
276
|
void aaronia_endpoints_client_free(HttpEndpointsClient* client);
|
|
235
277
|
FfiServerInfo* aaronia_endpoints_client_get_info(HttpEndpointsClient* client);
|
|
278
|
+
|
|
279
|
+
/* Fill *out with the device's live sensors through a standalone client,
|
|
280
|
+
* off the streaming source's read lock. Same reading and return contract
|
|
281
|
+
* as aaronia_source_read_sensors. */
|
|
282
|
+
bool aaronia_endpoints_client_read_sensors(HttpEndpointsClient* client, FfiDeviceSensors* out);
|
|
236
283
|
void aaronia_server_info_free(FfiServerInfo* info);
|
|
237
284
|
AaroniaFfiError aaronia_endpoints_client_control_streaming(HttpEndpointsClient* client, bool start);
|
|
238
285
|
AaroniaFfiError aaronia_endpoints_client_control_recording(HttpEndpointsClient* client, bool start, const char* name);
|