python-aaronia 0.8.1__tar.gz → 0.8.2__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.1 → python_aaronia-0.8.2}/CHANGELOG.md +167 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/CONTRIBUTING.md +14 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/Cargo.lock +80 -2
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/Cargo.toml +13 -2
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/DESIGN.md +1 -1
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/PKG-INFO +19 -1
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/PLUGINS.md +15 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/docs/APPS.md +3 -2
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/docs/FILESPEC.md +9 -16
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/docs/HTTPSPEC.md +148 -46
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/docs/QUICKSTART.md +4 -9
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/docs/SDKSPEC.md +87 -11
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/docs/USAGE.md +1 -1
- python_aaronia-0.8.2/docs/VERIFICATION.md +77 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/python-aaronia/Cargo.toml +8 -1
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/python-aaronia/README.md +18 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/python-aaronia/aaronia.pyi +3 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/python-aaronia/src/lib.rs +47 -9
- python_aaronia-0.8.2/scripts/fake-rtsa-server.py +83 -0
- python_aaronia-0.8.2/scripts/native-sdk-validate.ps1 +139 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/soapy-aaronia/CMakeLists.txt +7 -1
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/src/detection.rs +4 -1
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/src/http_source.rs +8 -5
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/src/http_streaming.rs +27 -25
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/src/native_sdk.rs +314 -36
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/src/sdk_source.rs +12 -9
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/src/unified_source.rs +170 -16
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/tests/live_smoke.rs +32 -0
- python_aaronia-0.8.2/tests/native_sdk_live.rs +648 -0
- python_aaronia-0.8.1/docs/VERIFICATION.md +0 -60
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/.cargo/config.toml +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/.gitattributes +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/.gitignore +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/LICENSE +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/README.md +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/benches/decompress_block.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/benches/deinterleave_dual_iq.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/benches/parse_int16_packet.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/benches/rtsa_open_and_read.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/deny.toml +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/examples/channel_hopping.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/examples/device_control.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/examples/dump_metadata.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/examples/http_iq_quickstart.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/examples/native_sdk_basic.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/examples/native_sdk_transmit.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/examples/noaa_scanner.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/examples/python_arrow_example.py +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/examples/read_rtsa_file.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/examples/soapy_python_example.py +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/include/aaronia.h +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/packaging/homebrew/README.md +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/packaging/homebrew/soapy-aaronia.rb +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/pyproject.toml +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/python-aaronia/test_basic.py +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/scripts/ci-local.sh +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/scripts/sdk-container-test.sh +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/scripts/validate-iq-live.py +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/soapy-aaronia/AaroniaSoapyDevice.cpp +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/soapy-aaronia/AaroniaSoapyDevice.hpp +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/soapy-aaronia/README.md +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/soapy-aaronia/Registration.cpp +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/soapy-aaronia/packaging/install.ps1 +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/soapy-aaronia/packaging/install.sh +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/src/c_api.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/src/decompression.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/src/error.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/src/file_source.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/src/http_endpoints.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/src/http_sink.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/src/lib.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/src/link_budget.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/src/sdk_sink.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/src/sdr_source_impl.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/src/seify_impl.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/src/unified_sink.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/src/utils.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/tests/c_api_test.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/tests/http_mock_test.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/tests/http_resilience_test.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/tests/http_sink_test.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/tests/integration_test.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/tests/native_sdk_load.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/tests/properties.proptest-regressions +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/tests/properties.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/tests/rtsa_negative_test.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/tests/sdr_source_impl_test.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/tests/spec_coverage.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/tests/test_cw_mag.rs +0 -0
- {python_aaronia-0.8.1 → python_aaronia-0.8.2}/tests/test_cw_meta.rs +0 -0
|
@@ -2,6 +2,173 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [Unreleased]
|
|
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
|
+
### Added
|
|
23
|
+
- **`scripts/fake-rtsa-server.py`** serves `/stream` in the real wire
|
|
24
|
+
format at loopback speed, so a decode path can be measured without a
|
|
25
|
+
device or a network. It is how the figures above were taken: over any
|
|
26
|
+
real link the transport dominates and a CPU change is invisible. Its
|
|
27
|
+
docstring carries the caveat that goes with it — over loopback hyper's
|
|
28
|
+
adaptive read buffer never leaves its 8 KiB floor, so kernel time there
|
|
29
|
+
is a property of the harness, not of the crate.
|
|
30
|
+
|
|
31
|
+
### Documentation
|
|
32
|
+
- **`rate_reduction` is time compression, not a sample-rate divider.**
|
|
33
|
+
Five places described it as reducing the rate or optimising bandwidth.
|
|
34
|
+
It thins frames over time — the operation the `waterfall` payload is
|
|
35
|
+
described by — so a continuous IQ stream, having no frames, is
|
|
36
|
+
unaffected: measured at factors of 2, 10 and 64, `sampleFrequency`
|
|
37
|
+
holds at 15,359,988 Hz and the byte rate does not move. That is the
|
|
38
|
+
parameter behaving as specified on a payload it was not meant for.
|
|
39
|
+
`live_stream_rate_reduction_and_scale` had asserted only that packets
|
|
40
|
+
arrived, which is true either way, so nothing caught the wrong
|
|
41
|
+
description; it now pins the IQ behaviour.
|
|
42
|
+
- **The stream can be compressed, up to 6.55x, via `format=rtsa`.**
|
|
43
|
+
Captured from RTSA-Suite's own HTTP Client block:
|
|
44
|
+
`GET /stream?format=rtsa&rate_reduction=8&input=main&compression=5&rate_adaption=0`.
|
|
45
|
+
`format=rtsa` streams the file container and is the only format that
|
|
46
|
+
accepts `compression=N`, which applies the file format's own lossy
|
|
47
|
+
codec. The container carries `float32` (`mSampleType` 11, `DSST_F32N`),
|
|
48
|
+
so level 0 is plain float32 with under 1% of chunk overhead — 123.0
|
|
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.
|
|
81
|
+
|
|
82
|
+
## [v0.8.2] - 2026-09-08
|
|
83
|
+
|
|
84
|
+
The native-SDK backend, validated on a Spectran V6 ECO on Windows 11 for
|
|
85
|
+
the first time. Every item below was found by running against the device;
|
|
86
|
+
the suite that found them ships as `tests/native_sdk_live.rs`.
|
|
87
|
+
|
|
88
|
+
### Fixed
|
|
89
|
+
|
|
90
|
+
- **The SDK library could not load on a stock Windows install.** RTSA-Suite
|
|
91
|
+
PRO puts `AaroniaRTSAAPI.dll` in `sdk\` and its ~23 dependencies (Qt6,
|
|
92
|
+
avcodec, libcrypto, …) in the install root; `LoadLibraryExW` searches
|
|
93
|
+
neither, so detection found the file and the load failed with a bare
|
|
94
|
+
`LoadLibraryExW failed`. The install root is added to the DLL search list
|
|
95
|
+
(`AddDllDirectory`) and the library loaded with the
|
|
96
|
+
`LOAD_LIBRARY_SEARCH_*` flags that consult it — not `SetDllDirectory`,
|
|
97
|
+
which would switch safe search mode off for the whole host process.
|
|
98
|
+
- **A V6 ECO was never found.** `init_native_sdk` enumerated the family
|
|
99
|
+
`spectranv6` only; the ECO answers to `spectranv6eco`, so a machine
|
|
100
|
+
holding one reported "No Spectran V6 devices found" with the device on
|
|
101
|
+
the bus. Each known family is now tried, as `detect_device_family`
|
|
102
|
+
already documented.
|
|
103
|
+
- **ECO IQ came from the spectrum pipeline at 0.4 MS/s.** The ECO's raw-IQ
|
|
104
|
+
mode was mapped to `spectranv6eco/rtsa`, which is its spectrum pipeline
|
|
105
|
+
(`RawSpectrumEco.cpp`); IQ read from it arrived at ~0.4 MS/s whatever
|
|
106
|
+
rate was asked for. `spectranv6eco/iqreceiver` — the mode Aaronia's own
|
|
107
|
+
`IQReceiverEco.cpp` opens — is used instead. `/raw` also opens on an ECO
|
|
108
|
+
but has no `main/spanfreq`, so it cannot honour a requested rate.
|
|
109
|
+
- **The ECO delivered 1.5× the requested rate.** In `iqreceiver` mode
|
|
110
|
+
`main/spanfreq` is a bandwidth and the pipeline streams at 1.5× it —
|
|
111
|
+
every rung exactly, 10 MHz → 15.0 MS/s, 15.36 → 23.04 — up to a
|
|
112
|
+
59.2 MS/s USB ceiling. The request is now translated so the caller gets
|
|
113
|
+
the rate it named on every backend: a 15.36 MS/s request measures
|
|
114
|
+
15.360 MS/s over 153 M samples.
|
|
115
|
+
- **Native-SDK loss was invisible.** Packet flags `WARN_DROPPED` and
|
|
116
|
+
`TIME_DISCONTINUITY` were logged at debug level and nothing else, so
|
|
117
|
+
`cumulative_drops()` read 0 through any amount of loss and
|
|
118
|
+
`last_timestamp_ns()` read 0 always. Both, and `take_overrun()`, now
|
|
119
|
+
report the native source's packets. `WARN_INACCURATE` is deliberately
|
|
120
|
+
not an overrun: the ECO's fractional resampler sets it routinely.
|
|
121
|
+
- **`sample_rate_hz()` echoed the request on native sources.** It now
|
|
122
|
+
reports the rate the device's packets carry once one has been read, as
|
|
123
|
+
the HTTP backend already did.
|
|
124
|
+
- **`read_samples` and `read_samples_dual` returned one packet per call**
|
|
125
|
+
however large `max_samples` was. Both now drain already-queued packets
|
|
126
|
+
until the caller is satisfied, waiting only for the first — and not
|
|
127
|
+
even that when carry-over samples were already handed out, where the
|
|
128
|
+
old code could sleep out the whole poll deadline on a backlog.
|
|
129
|
+
- **A `device_serial` in the second family was never found.** Family
|
|
130
|
+
enumeration stopped at the first family holding any device, so a
|
|
131
|
+
machine with both a V6 and an ECO could not select the ECO by serial.
|
|
132
|
+
Every family is enumerated and the serial resolved across them.
|
|
133
|
+
- **Python could not ask for the SDK.** `python-aaronia` depended on the
|
|
134
|
+
crate's default features, which exclude the backend, and `aaronia.open()`
|
|
135
|
+
with neither `url` nor `file` pinned to localhost HTTP rather than
|
|
136
|
+
auto-detecting. A `native-sdk` feature and `open(sdk=True, serial=…)` /
|
|
137
|
+
`AaroniaConfig.native_sdk` select it explicitly; a missing SDK is then
|
|
138
|
+
an error, never a silent fallback.
|
|
139
|
+
- **A unit test assumed no SDK on the machine.**
|
|
140
|
+
`test_detect_best_source_type_localhost_fallback` asserted the
|
|
141
|
+
localhost-HTTP fallback unconditionally, so `cargo test` failed on any
|
|
142
|
+
machine with RTSA-Suite installed — the one place the backend gets
|
|
143
|
+
tested. It now asserts the actual rule: the SDK when installed, HTTP
|
|
144
|
+
otherwise.
|
|
145
|
+
- **The Soapy plugin's Windows build assumed MSVC.** The Rust static-lib
|
|
146
|
+
name is keyed on the compiler now, not the OS; the windows-gnu toolchain
|
|
147
|
+
emits `libsdr_aaronia_rs.a`.
|
|
148
|
+
|
|
149
|
+
### Added
|
|
150
|
+
|
|
151
|
+
- `tests/native_sdk_live.rs`: thirteen hardware tests — detection,
|
|
152
|
+
identity, ten open/close cycles, a centre-frequency sweep, mid-stream
|
|
153
|
+
retune, a span-ladder characterisation that records the device-reported
|
|
154
|
+
rate per rung, a steady-state rate check, a soak with drop accounting,
|
|
155
|
+
error-message quality, and the Seify and C-ABI paths.
|
|
156
|
+
- `scripts/native-sdk-validate.ps1`: runs the whole matrix on a Windows
|
|
157
|
+
machine with a device.
|
|
158
|
+
- `NativeSdkSource::{observed_sample_rate_hz, cumulative_drops,
|
|
159
|
+
take_overrun, last_timestamp_ns}`.
|
|
160
|
+
|
|
161
|
+
### Measured on a V6 ECO (Windows 11, RTSA-Suite PRO 3.0.3)
|
|
162
|
+
|
|
163
|
+
Every ladder rung from 3.84 to 49.152 MHz delivers exactly the requested
|
|
164
|
+
rate, 3/3 trials each; 61.44 MHz caps at 59.214 MS/s. Steady-state
|
|
165
|
+
delivery is 100.0% of the reported rate. The `iqreceiver` pipeline
|
|
166
|
+
delivers ~40% of rate for the first ~5 s after start, then settles; the
|
|
167
|
+
rate and soak tests warm up past it. The SoapySDR plugin, built with
|
|
168
|
+
MSVC against radioconda's SoapySDR, streams 15.357 MS/s over the SDK
|
|
169
|
+
into Python; see [docs/VERIFICATION.md](docs/VERIFICATION.md) for the
|
|
170
|
+
two limits found on the way.
|
|
171
|
+
|
|
5
172
|
## [v0.8.1] - 2026-09-07
|
|
6
173
|
|
|
7
174
|
### Fixed
|
|
@@ -127,6 +127,20 @@ cargo bench # Run all benchmarks
|
|
|
127
127
|
cargo bench --bench parse_int16_packet # Run a specific harness
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
+
Those measure decode paths in isolation. To measure the whole HTTP path
|
|
131
|
+
end to end without hardware, `scripts/fake-rtsa-server.py` serves
|
|
132
|
+
`/stream` in the real wire format as fast as a client will take it:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
scripts/fake-rtsa-server.py 54999 int16 &
|
|
136
|
+
# then point HttpEndpointsClient::start_stream at http://127.0.0.1:54999
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
It saturates loopback at several GB/s, so the client is always the
|
|
140
|
+
bottleneck. Worth reaching for when a change looks like it should be
|
|
141
|
+
faster but a real link hides the difference — over WiFi or even 2.5GbE
|
|
142
|
+
the network dominates and CPU work does not show up at all.
|
|
143
|
+
|
|
130
144
|
### 8. Mutation Testing
|
|
131
145
|
|
|
132
146
|
`cargo mutants` runs in CI on a weekly cron (Mondays) and on manual dispatch, advisory-only. To run it locally:
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
# It is not intended for manual editing.
|
|
3
3
|
version = 4
|
|
4
4
|
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "adler2"
|
|
7
|
+
version = "2.0.1"
|
|
8
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
9
|
+
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
|
10
|
+
|
|
5
11
|
[[package]]
|
|
6
12
|
name = "ahash"
|
|
7
13
|
version = "0.8.12"
|
|
@@ -356,6 +362,18 @@ dependencies = [
|
|
|
356
362
|
"pin-project-lite",
|
|
357
363
|
]
|
|
358
364
|
|
|
365
|
+
[[package]]
|
|
366
|
+
name = "async-compression"
|
|
367
|
+
version = "0.4.43"
|
|
368
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
369
|
+
checksum = "3976abdc8fe7d1133d43d304afd42abdf5bc3e1319d263d223bde07b5efc4be8"
|
|
370
|
+
dependencies = [
|
|
371
|
+
"compression-codecs",
|
|
372
|
+
"compression-core",
|
|
373
|
+
"pin-project-lite",
|
|
374
|
+
"tokio",
|
|
375
|
+
]
|
|
376
|
+
|
|
359
377
|
[[package]]
|
|
360
378
|
name = "async-executor"
|
|
361
379
|
version = "1.14.0"
|
|
@@ -777,6 +795,23 @@ dependencies = [
|
|
|
777
795
|
"memchr",
|
|
778
796
|
]
|
|
779
797
|
|
|
798
|
+
[[package]]
|
|
799
|
+
name = "compression-codecs"
|
|
800
|
+
version = "0.4.38"
|
|
801
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
802
|
+
checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf"
|
|
803
|
+
dependencies = [
|
|
804
|
+
"compression-core",
|
|
805
|
+
"flate2",
|
|
806
|
+
"memchr",
|
|
807
|
+
]
|
|
808
|
+
|
|
809
|
+
[[package]]
|
|
810
|
+
name = "compression-core"
|
|
811
|
+
version = "0.4.32"
|
|
812
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
813
|
+
checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789"
|
|
814
|
+
|
|
780
815
|
[[package]]
|
|
781
816
|
name = "concurrent-queue"
|
|
782
817
|
version = "2.5.0"
|
|
@@ -930,6 +965,15 @@ dependencies = [
|
|
|
930
965
|
"libc",
|
|
931
966
|
]
|
|
932
967
|
|
|
968
|
+
[[package]]
|
|
969
|
+
name = "crc32fast"
|
|
970
|
+
version = "1.5.1"
|
|
971
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
972
|
+
checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550"
|
|
973
|
+
dependencies = [
|
|
974
|
+
"cfg-if",
|
|
975
|
+
]
|
|
976
|
+
|
|
933
977
|
[[package]]
|
|
934
978
|
name = "criterion"
|
|
935
979
|
version = "0.5.1"
|
|
@@ -1326,6 +1370,17 @@ dependencies = [
|
|
|
1326
1370
|
"rustc_version",
|
|
1327
1371
|
]
|
|
1328
1372
|
|
|
1373
|
+
[[package]]
|
|
1374
|
+
name = "flate2"
|
|
1375
|
+
version = "1.1.10"
|
|
1376
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1377
|
+
checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
|
|
1378
|
+
dependencies = [
|
|
1379
|
+
"crc32fast",
|
|
1380
|
+
"miniz_oxide",
|
|
1381
|
+
"zlib-rs",
|
|
1382
|
+
]
|
|
1383
|
+
|
|
1329
1384
|
[[package]]
|
|
1330
1385
|
name = "fnv"
|
|
1331
1386
|
version = "1.0.7"
|
|
@@ -2401,6 +2456,16 @@ dependencies = [
|
|
|
2401
2456
|
"unicase",
|
|
2402
2457
|
]
|
|
2403
2458
|
|
|
2459
|
+
[[package]]
|
|
2460
|
+
name = "miniz_oxide"
|
|
2461
|
+
version = "0.9.1"
|
|
2462
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2463
|
+
checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c"
|
|
2464
|
+
dependencies = [
|
|
2465
|
+
"adler2",
|
|
2466
|
+
"simd-adler32",
|
|
2467
|
+
]
|
|
2468
|
+
|
|
2404
2469
|
[[package]]
|
|
2405
2470
|
name = "mio"
|
|
2406
2471
|
version = "1.2.2"
|
|
@@ -3027,7 +3092,7 @@ dependencies = [
|
|
|
3027
3092
|
|
|
3028
3093
|
[[package]]
|
|
3029
3094
|
name = "python-aaronia"
|
|
3030
|
-
version = "0.8.
|
|
3095
|
+
version = "0.8.2"
|
|
3031
3096
|
dependencies = [
|
|
3032
3097
|
"arrow",
|
|
3033
3098
|
"num-complex",
|
|
@@ -3556,7 +3621,7 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
|
|
3556
3621
|
|
|
3557
3622
|
[[package]]
|
|
3558
3623
|
name = "sdr-aaronia-rs"
|
|
3559
|
-
version = "0.8.
|
|
3624
|
+
version = "0.8.2"
|
|
3560
3625
|
dependencies = [
|
|
3561
3626
|
"anyhow",
|
|
3562
3627
|
"bitflags 2.13.1",
|
|
@@ -3790,6 +3855,12 @@ version = "2.0.1"
|
|
|
3790
3855
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3791
3856
|
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
|
3792
3857
|
|
|
3858
|
+
[[package]]
|
|
3859
|
+
name = "simd-adler32"
|
|
3860
|
+
version = "0.3.10"
|
|
3861
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3862
|
+
checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
|
|
3863
|
+
|
|
3793
3864
|
[[package]]
|
|
3794
3865
|
name = "simd_cesu8"
|
|
3795
3866
|
version = "1.2.0"
|
|
@@ -4376,6 +4447,7 @@ version = "0.6.11"
|
|
|
4376
4447
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
4377
4448
|
checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
|
|
4378
4449
|
dependencies = [
|
|
4450
|
+
"async-compression",
|
|
4379
4451
|
"bitflags 2.13.1",
|
|
4380
4452
|
"bytes",
|
|
4381
4453
|
"futures-core",
|
|
@@ -5206,6 +5278,12 @@ dependencies = [
|
|
|
5206
5278
|
"syn 2.0.119",
|
|
5207
5279
|
]
|
|
5208
5280
|
|
|
5281
|
+
[[package]]
|
|
5282
|
+
name = "zlib-rs"
|
|
5283
|
+
version = "0.6.7"
|
|
5284
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
5285
|
+
checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12"
|
|
5286
|
+
|
|
5209
5287
|
[[package]]
|
|
5210
5288
|
name = "zmij"
|
|
5211
5289
|
version = "1.0.23"
|
|
@@ -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.8.
|
|
5
|
+
version = "0.8.2"
|
|
6
6
|
edition = "2024"
|
|
7
7
|
repository = "https://github.com/isaacbentley/sdr-aaronia-rs"
|
|
8
8
|
readme = "README.md"
|
|
@@ -18,7 +18,13 @@ rustdoc-args = ["--cfg", "docsrs"]
|
|
|
18
18
|
crate-type = ["cdylib", "staticlib", "rlib"]
|
|
19
19
|
|
|
20
20
|
[dependencies]
|
|
21
|
-
|
|
21
|
+
# `deflate`/`gzip`: the RTSA control plane compresses when asked and we
|
|
22
|
+
# were not asking — /remoteconfig is 17,432 bytes plain against 3,299
|
|
23
|
+
# deflated, /healthstatus 6,100 against 1,436, and both are fetched on
|
|
24
|
+
# every device open. RTSA-Suite's own HTTP Client sends
|
|
25
|
+
# `Accept-Encoding: deflate`. Costs nothing on /stream, which the server
|
|
26
|
+
# does not compress at any level.
|
|
27
|
+
reqwest = { version = "0.13", features = ["blocking", "json", "stream", "deflate", "gzip"], optional = true }
|
|
22
28
|
serde = { version = "1.0", features = ["derive"], optional = true }
|
|
23
29
|
serde_json = { version = "1.0", optional = true }
|
|
24
30
|
thiserror = "2.0"
|
|
@@ -57,6 +63,11 @@ proptest = "1.5"
|
|
|
57
63
|
criterion = { version = "0.5", features = ["html_reports"] }
|
|
58
64
|
wiremock = "0.6" # For HTTP endpoint resiliency testing
|
|
59
65
|
|
|
66
|
+
# The native-SDK live suite uses `#[tokio::test]`, and must build in a
|
|
67
|
+
# `--no-default-features --features native-sdk` configuration where the
|
|
68
|
+
# `http` feature is not pulling `dep:tokio` in.
|
|
69
|
+
tokio = { version = "1.43", features = ["rt-multi-thread", "macros", "time", "sync"] }
|
|
70
|
+
|
|
60
71
|
# Each `[[bench]]` declares one criterion harness binary. Run with
|
|
61
72
|
# `cargo bench --bench <name>`. Coverage:
|
|
62
73
|
# parse_int16_packet — HTTP int16 packet parse throughput
|
|
@@ -81,7 +81,7 @@ Hop dwell deadlines come from `sdr_source::DwellController`; per-hop pacing addi
|
|
|
81
81
|
|
|
82
82
|
The HTTP reader task (spawned by `init_http_source` in `unified_source.rs`) runs a `DropDetector` over each packet's `start_time`/`end_time` metadata. A timestamp gap larger than the tolerance latches `AaroniaSource::pending_overrun`, which `take_overrun()` reads and clears. `single_channel_pump` and `hop_pump` (`sdr_source_impl.rs`) call `take_overrun()` once per emitted `IqPacket`, so a drop detected anywhere since the last read surfaces as `IqPacket::overrun = true` on the next packet. This is a per-call signal, not a precise per-sample one, since drop timing is lost once chunks merge into the flat `sample_buffer`.
|
|
83
83
|
|
|
84
|
-
The native-SDK and file backends do not populate this yet (`overrun` is always `false` for them). Native-SDK overrun detection would need
|
|
84
|
+
The native-SDK and file backends do not populate this yet (`overrun` is always `false` for them). Native-SDK overrun detection would need the overflow/dropped warning bits from the packet `flags` field. The RX path reads and logs those at debug level, but does not yet latch them into `pending_overrun`.
|
|
85
85
|
|
|
86
86
|
The Aaronia capture thread (`AaroniaSdrSource::start`) is wrapped in `catch_unwind`: a panic inside the pump loop is logged rather than silently unwinding the thread.
|
|
87
87
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-aaronia
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.2
|
|
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,6 +40,14 @@ cd python-aaronia
|
|
|
40
40
|
maturin develop --release
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
+
To reach the Aaronia native SDK as well — a Spectran on this machine's
|
|
44
|
+
USB, with RTSA-Suite PRO installed, on Windows or Linux — build with the
|
|
45
|
+
feature; the default wheel speaks only HTTP and files:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
maturin develop --release --features native-sdk
|
|
49
|
+
```
|
|
50
|
+
|
|
43
51
|
Check your setup before writing any code:
|
|
44
52
|
|
|
45
53
|
```bash
|
|
@@ -70,6 +78,15 @@ own schedule, or for Apache Arrow:
|
|
|
70
78
|
|
|
71
79
|
```python
|
|
72
80
|
src = aaronia.open(freq=2.44e9, rate=15.36e6, format="I16")
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`sdk=True` opens the device through the native SDK instead of a server
|
|
84
|
+
(`serial=` picks one of several). It is an error, not a fallback, when
|
|
85
|
+
the SDK is missing — a capture never quietly comes from another backend:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
src = aaronia.open(sdk=True, freq=2.44e9, rate=15.36e6)
|
|
89
|
+
src = aaronia.open(sdk=True, serial="C2-P-03000105", freq=2.44e9)
|
|
73
90
|
samples = src.read_samples_numpy(65536) # numpy complex64 array
|
|
74
91
|
batch = src.read_samples_arrow(65536) # pyarrow FixedSizeListArray of [re, im]
|
|
75
92
|
src.set_center_frequency(2.41e9) # live retune, no teardown
|
|
@@ -157,6 +174,7 @@ Every field is readable and writable.
|
|
|
157
174
|
| `http_base_url` | RTSA-Suite HTTP server URL; pins the HTTP backend |
|
|
158
175
|
| `file_path` | Path to a recorded `.rtsa` file; pins the file backend |
|
|
159
176
|
| `device_serial` | Device selection for the native-SDK backend |
|
|
177
|
+
| `native_sdk` | `True` pins the source to the native SDK; missing SDK is an error |
|
|
160
178
|
| `center_freq` | Center frequency, Hz |
|
|
161
179
|
| `sample_rate` | IQ sample rate, Hz (the Aaronia "span") |
|
|
162
180
|
| `reference_level` | Reference level, dBm |
|
|
@@ -17,6 +17,12 @@ sdr-aaronia-rs = { version = "0.8", features = ["seify"] }
|
|
|
17
17
|
|
|
18
18
|
Instantiate the device with `AaroniaSeifyDevice::from_args` and use it directly (or via `seify::dev::DynDeviceBackend`). The backend is **not** part of seify's built-in enumeration registry — `seify::enumerate()` will not discover it.
|
|
19
19
|
|
|
20
|
+
`url=` selects the HTTP backend and `file=` playback. `sdk=true` (or
|
|
21
|
+
`serial=<device serial>`) selects the Aaronia native SDK; that needs the
|
|
22
|
+
crate built with both features — `features = ["seify", "native-sdk"]` —
|
|
23
|
+
on Windows or Linux with RTSA-Suite PRO installed. Without the feature
|
|
24
|
+
the request is a clean error, never a fallback to HTTP.
|
|
25
|
+
|
|
20
26
|
```rust
|
|
21
27
|
use sdr_aaronia_rs::seify_impl::AaroniaSeifyDevice;
|
|
22
28
|
use seify::{Args, RxDevice, RxStreamer, DeviceInfo};
|
|
@@ -64,6 +70,15 @@ The plugin requires `cmake`, `SoapySDR`, and the compiled `sdr-aaronia-rs` stati
|
|
|
64
70
|
make
|
|
65
71
|
```
|
|
66
72
|
|
|
73
|
+
On Windows, point CMake at the SoapySDR you will load the module into
|
|
74
|
+
— `-DSoapySDR_DIR="C:\Program Files\PothosSDR\cmake"` for PothosSDR,
|
|
75
|
+
or `<radioconda>\Library\cmake` for a GNU Radio (radioconda) install —
|
|
76
|
+
and build with **MSVC**. Both of those runtimes are MSVC-built, and a
|
|
77
|
+
SoapySDR module is C++ (virtual classes, `std::string` across the
|
|
78
|
+
boundary), so a MinGW-built module cannot load into them; the Rust
|
|
79
|
+
side must then be the `x86_64-pc-windows-msvc` toolchain too. Add
|
|
80
|
+
`-DAARONIA_NATIVE_SDK=ON` for the native-SDK backend.
|
|
81
|
+
|
|
67
82
|
3. Ensure SoapySDR can find the plugin. You can install it to your system's Soapy modules directory (e.g. `/usr/local/lib/SoapySDR/modules0.8/`) or set the `SOAPY_SDR_PLUGIN_PATH` environment variable:
|
|
68
83
|
```bash
|
|
69
84
|
export SOAPY_SDR_PLUGIN_PATH=$(pwd)/soapy-aaronia/build
|
|
@@ -54,8 +54,9 @@ device's real ladder rather than round numbers it cannot produce: on a
|
|
|
54
54
|
V6 ECO that is 61.44 MHz halved down to 120 kHz, which the RTSA GUI
|
|
55
55
|
labels Full through 1/512. Nothing in between exists, so a request
|
|
56
56
|
between rungs snaps to the nearest and logs a warning. A full V6 has a
|
|
57
|
-
selectable receiver clock and reaches higher
|
|
58
|
-
|
|
57
|
+
selectable receiver clock and reaches higher. The plugin advertises
|
|
58
|
+
whatever ladder the device declares, falling back to the ECO's only
|
|
59
|
+
when the device cannot be asked.
|
|
59
60
|
|
|
60
61
|
The single gain element is `REF`, the Aaronia reference level in dBm. It
|
|
61
62
|
is not an amplifier gain: raising it reduces sensitivity. Start near
|
|
@@ -195,12 +195,9 @@ Indicates the start of a new stream.
|
|
|
195
195
|
> `capture_start_offset` when the chunk is exactly 48 bytes and the
|
|
196
196
|
> value is plausible (finite, `0 ≤ v < ~10 years`), and the metadata
|
|
197
197
|
> layer anchors `start_time_ns` with it so the reported time span
|
|
198
|
-
> matches the recorded data rather than the stream clock.
|
|
199
|
-
> 40-byte
|
|
200
|
-
>
|
|
201
|
-
> no spec revision, capture, or writer produces that layout, and it
|
|
202
|
-
> misparsed exactly the minimal spec-conformant chunks — it has been
|
|
203
|
-
> removed.
|
|
198
|
+
> matches the recorded data rather than the stream clock. There is no
|
|
199
|
+
> alternate 40-byte "proximity" layout: no spec revision, capture or
|
|
200
|
+
> writer produces one.
|
|
204
201
|
|
|
205
202
|
---
|
|
206
203
|
|
|
@@ -463,9 +460,8 @@ Several chunks use enums or flags to specify data types or features. The most im
|
|
|
463
460
|
> official file-format document (rev. 4). Note the sample-type
|
|
464
461
|
> ordering: each width's *unsigned* variant is immediately followed by
|
|
465
462
|
> its signed sibling (`U8, U16, S16, U32, S32, F32`), rather than all
|
|
466
|
-
> unsigned then all signed — an
|
|
467
|
-
>
|
|
468
|
-
> entirely, so a file using value 9 failed to open. The reader now
|
|
463
|
+
> unsigned then all signed — an ordering easy to transpose, and a
|
|
464
|
+
> transposition silently misreads every affected chunk. The reader
|
|
469
465
|
> maps any value outside the specified range of these three `SAMP`
|
|
470
466
|
> enums to an `Unknown` variant and skips chunks it cannot decode,
|
|
471
467
|
> instead of rejecting the whole file.
|
|
@@ -708,13 +704,10 @@ E0 96 2A ED 3A 1C 15 43 mCreationTime
|
|
|
708
704
|
00 00 00 00 00 00 00 00 mStreamOffset (terminating, no prior stream)
|
|
709
705
|
```
|
|
710
706
|
|
|
711
|
-
> **
|
|
712
|
-
>
|
|
713
|
-
>
|
|
714
|
-
>
|
|
715
|
-
> test captures confirmed the standard layout is the only one — see
|
|
716
|
-
> the layout note in the STRM chunk definition above. The reader now
|
|
717
|
-
> parses every STRM with the standard layout.
|
|
707
|
+
> **Note:** this 40-byte STRM (`mChunkSize = 0x28`) takes the standard
|
|
708
|
+
> layout, like every other size — verified against the vendor PDF and
|
|
709
|
+
> the LFS test captures. See the layout note in the STRM chunk
|
|
710
|
+
> definition above.
|
|
718
711
|
|
|
719
712
|
#### Sample Packet (SAMP)
|
|
720
713
|
```
|