python-aaronia 0.8.2__tar.gz → 0.10.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.10.0}/CHANGELOG.md +314 -71
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/Cargo.lock +2 -2
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/Cargo.toml +1 -1
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/DESIGN.md +11 -11
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/PKG-INFO +29 -26
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/PLUGINS.md +105 -6
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/README.md +33 -13
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/docs/FILESPEC.md +7 -12
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/docs/HTTPSPEC.md +29 -34
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/docs/QUICKSTART.md +25 -23
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/docs/SDKSPEC.md +155 -65
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/docs/USAGE.md +62 -54
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/docs/VERIFICATION.md +5 -2
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/examples/channel_hopping.rs +5 -5
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/examples/dump_metadata.rs +9 -9
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/examples/http_iq_quickstart.rs +6 -6
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/examples/native_sdk_basic.rs +10 -5
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/examples/native_sdk_transmit.rs +7 -7
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/examples/noaa_scanner.rs +39 -34
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/examples/python_arrow_example.py +6 -6
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/examples/read_rtsa_file.rs +3 -3
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/examples/soapy_python_example.py +3 -3
- python_aaronia-0.10.0/include/spectran.h +309 -0
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/python-aaronia/Cargo.toml +1 -1
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/python-aaronia/README.md +28 -25
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/python-aaronia/aaronia.pyi +51 -42
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/python-aaronia/src/lib.rs +110 -101
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/python-aaronia/test_basic.py +26 -24
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/scripts/ci-local.sh +20 -0
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/scripts/fake-rtsa-server.py +1 -1
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/scripts/validate-iq-live.py +10 -2
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/soapy-aaronia/CMakeLists.txt +3 -3
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/soapy-aaronia/README.md +4 -3
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/soapy-aaronia/Registration.cpp +85 -53
- python_aaronia-0.8.2/soapy-aaronia/AaroniaSoapyDevice.cpp → python_aaronia-0.10.0/soapy-aaronia/SpectranSoapyDevice.cpp +273 -113
- python_aaronia-0.8.2/soapy-aaronia/AaroniaSoapyDevice.hpp → python_aaronia-0.10.0/soapy-aaronia/SpectranSoapyDevice.hpp +36 -7
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/src/c_api.rs +648 -378
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/src/file_source.rs +91 -69
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/src/http_endpoints.rs +345 -55
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/src/http_sink.rs +19 -19
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/src/http_source.rs +90 -85
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/src/http_streaming.rs +20 -14
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/src/lib.rs +22 -14
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/src/link_budget.rs +65 -63
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/src/native_sdk.rs +337 -130
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/src/sdk_sink.rs +41 -12
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/src/sdk_source.rs +84 -173
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/src/sdr_source_impl.rs +34 -34
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/src/seify_impl.rs +41 -41
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/src/unified_sink.rs +23 -23
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/src/unified_source.rs +390 -303
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/src/utils.rs +185 -14
- python_aaronia-0.10.0/tests/abi_drift.rs +311 -0
- python_aaronia-0.10.0/tests/c_api_test.rs +72 -0
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/tests/http_mock_test.rs +159 -5
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/tests/http_resilience_test.rs +15 -15
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/tests/http_sink_test.rs +2 -2
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/tests/integration_test.rs +6 -6
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/tests/live_smoke.rs +104 -20
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/tests/native_sdk_live.rs +66 -60
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/tests/properties.rs +1 -1
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/tests/sdr_source_impl_test.rs +14 -14
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/tests/spec_coverage.rs +1 -1
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/tests/test_cw_meta.rs +3 -3
- python_aaronia-0.10.0/tests/wire_contract.rs +232 -0
- python_aaronia-0.8.2/include/aaronia.h +0 -256
- python_aaronia-0.8.2/tests/c_api_test.rs +0 -72
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/.cargo/config.toml +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/.gitattributes +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/.gitignore +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/CONTRIBUTING.md +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/LICENSE +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/benches/decompress_block.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/benches/deinterleave_dual_iq.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/benches/parse_int16_packet.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/benches/rtsa_open_and_read.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/deny.toml +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/docs/APPS.md +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/examples/device_control.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/packaging/homebrew/README.md +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/packaging/homebrew/soapy-aaronia.rb +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/pyproject.toml +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/scripts/native-sdk-validate.ps1 +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/scripts/sdk-container-test.sh +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/soapy-aaronia/packaging/install.ps1 +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/soapy-aaronia/packaging/install.sh +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/src/decompression.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/src/detection.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/src/error.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/tests/native_sdk_load.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/tests/properties.proptest-regressions +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/tests/rtsa_negative_test.rs +0 -0
- {python_aaronia-0.8.2 → python_aaronia-0.10.0}/tests/test_cw_mag.rs +0 -0
|
@@ -2,82 +2,247 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
-
## [
|
|
6
|
-
|
|
7
|
-
###
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
5
|
+
## [v0.10.0] - 2026-09-08
|
|
6
|
+
|
|
7
|
+
### Breaking changes
|
|
8
|
+
|
|
9
|
+
**One name per concept, with its unit.** A field, parameter or getter holding a
|
|
10
|
+
bare number now carries its unit (`_hz`, `_dbm`, `_db`, `_s`); a type that
|
|
11
|
+
already carries one, such as `Duration`, does not. `span_frequency` is gone: it
|
|
12
|
+
meant the IQ *sample rate*, while the same word meant alias-free *bandwidth* in
|
|
13
|
+
the link budget. Old names were removed rather than deprecated, since an alias
|
|
14
|
+
that kept working would preserve the ambiguity this release exists to remove.
|
|
15
|
+
The `spanfreq` device key and the `frequencySpan` JSON field are the vendor's
|
|
16
|
+
vocabulary and are unchanged.
|
|
17
|
+
|
|
18
|
+
| Old | New |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| `AaroniaConfig::center_frequency` (field + builder) | `center_frequency_hz` |
|
|
21
|
+
| `AaroniaConfig::span_frequency` (field + builder) | `sample_rate_hz` |
|
|
22
|
+
| `AaroniaConfig::reference_level` (field + builder) | `reference_level_dbm` |
|
|
23
|
+
| `AaroniaSourceBuilder::center_frequency` / `span_frequency` / `reference_level` | `center_frequency_hz` / `sample_rate_hz` / `reference_level_dbm` |
|
|
24
|
+
| `AaroniaSource::set_center_frequency` / `set_span_frequency` / `set_reference_level` | `set_center_frequency_hz` / `set_sample_rate_hz` / `set_reference_level_dbm` |
|
|
25
|
+
| `SourceInfo::center_frequency` / `span_frequency` / `reference_level` | `center_frequency_hz` / `sample_rate_hz` / `reference_level_dbm` |
|
|
26
|
+
| `SourceInfo::sample_rate_hz()` (method) | removed — read the field of the same name |
|
|
27
|
+
| `UnifiedSinkConfig::span_frequency`, `trans_gain` | `sample_rate_hz`, `trans_gain_db` |
|
|
28
|
+
| `ThroughputMeasurement::max_sustainable_span_hz` | `max_sustainable_bandwidth` |
|
|
29
|
+
| `ThroughputMeasurement::stream_sample_rate` | `stream_sample_rate_hz` |
|
|
30
|
+
| `LinkBudgetVerdict::sample_rate`, `fit_span_hz` | `sample_rate_hz`, `fit_bandwidth_hz` |
|
|
31
|
+
| `RtsaMetadata` / `StreamingSdrConfig` bare quantities | unit-suffixed (`_hz`, `_s`) |
|
|
32
|
+
| `HttpSourceBuilder::frequency` / `frequency_str` / `sample_rate` / `reference_level` | `center_frequency_hz` / `center_frequency_str` / `sample_rate_hz` / `reference_level_dbm` |
|
|
33
|
+
| `HttpSinkBuilder::frequency`, `sample_rate` | `center_frequency_hz`, `sample_rate_hz` |
|
|
34
|
+
| `HttpSource::new` / `with_advanced_options`, `HttpSink::new` parameters | `center_frequency_hz` / `sample_rate_hz` / `reference_level_dbm` |
|
|
35
|
+
| `DeviceCapabilities::center_frequency`, `reference_level` | `center_frequency_hz`, `reference_level_dbm` |
|
|
36
|
+
| `PacketMetadata::sample_rate()` | `sample_rate_hz()` |
|
|
37
|
+
|
|
38
|
+
`frequency` became `center_frequency_hz`, not `frequency_hz`: it was always the
|
|
39
|
+
centre frequency. The `_str` variants take a string carrying its own units
|
|
40
|
+
(`"146.52M"`), so they keep no suffix. `DeviceCapabilities` is
|
|
41
|
+
`#[non_exhaustive]`, so callers reach those fields through the API rather than
|
|
42
|
+
by literal.
|
|
43
|
+
|
|
44
|
+
**Rust types name the device: `Aaronia*` becomes `Spectran*`** —
|
|
45
|
+
`SpectranConfig`, `SpectranSource`, `SpectranSourceBuilder`,
|
|
46
|
+
`SpectranSinkBuilder`, `SpectranSeifyDevice`, `SpectranSeifyRxStreamer`,
|
|
47
|
+
`SpectranBackend`, `SpectranSdrSource`.
|
|
48
|
+
|
|
49
|
+
The vendor name stays wherever it is a frozen contract rather than a
|
|
50
|
+
description: the `aaronia_*` C symbols and the `AaroniaSource` /
|
|
51
|
+
`AaroniaFfiError` typedefs, the crate, the PyPI package, the Python module,
|
|
52
|
+
`AaroniaSoapyDevice`, and `driver=aaronia`. That last is the load-bearing one —
|
|
53
|
+
it is typed into GQRX and SDR++ configs already in the world, where breaking it
|
|
54
|
+
reads as "device not found" with nothing pointing at the cause.
|
|
55
|
+
|
|
56
|
+
**Python takes the same vocabulary**, its four exceptions included.
|
|
57
|
+
|
|
58
|
+
| Old Python | New |
|
|
59
|
+
| --- | --- |
|
|
60
|
+
| `aaronia.AaroniaConfig` | `aaronia.SpectranConfig` |
|
|
61
|
+
| `aaronia.AaroniaSource` | `aaronia.SpectranSource` |
|
|
62
|
+
| `aaronia.Aaronia{Connection,Hardware,Timeout}Error`, `AaroniaStreamClosed` | `Spectran…` |
|
|
63
|
+
| `cfg.center_freq` | `cfg.center_frequency_hz` |
|
|
64
|
+
| `cfg.sample_rate` | `cfg.sample_rate_hz` |
|
|
65
|
+
| `cfg.reference_level` | `cfg.reference_level_dbm` |
|
|
66
|
+
| `cfg.read_timeout` | `cfg.read_timeout_s` |
|
|
67
|
+
| `cfg.native_sdk` | `cfg.force_native_sdk` |
|
|
68
|
+
| `src.set_center_frequency()` / `set_sample_rate()` / `set_reference_level()` | `set_center_frequency_hz()` / `set_sample_rate_hz()` / `set_reference_level_dbm()` |
|
|
69
|
+
| `open(freq=, rate=, bandwidth=, ref_level=, read_timeout=)` | `open(center_frequency_hz=, sample_rate_hz=, bandwidth_hz=, reference_level_dbm=, read_timeout_s=)` |
|
|
70
|
+
|
|
71
|
+
`open()`'s keywords changed with them. It is the one-line front door and the
|
|
72
|
+
longer names cost the headline example a wrap, but `open(url, bandwidth=10e6)`
|
|
73
|
+
gives no way to tell Hz from MHz. `sdk=`, `url=`, `file=`, `serial=`, `format=`
|
|
74
|
+
and `scale=` carry no unit and are unchanged.
|
|
75
|
+
|
|
76
|
+
**The whole C ABI moves to `spectran_*`.** Every exported symbol, the header
|
|
77
|
+
(`include/aaronia.h` is now `include/spectran.h`), the opaque typedefs
|
|
78
|
+
(`AaroniaSource` / `AaroniaSourceBuilder` / `AaroniaSink` / `AaroniaSinkBuilder`
|
|
79
|
+
/ `AaroniaFfiError` / `CAaroniaSourceType` become `Spectran*`), and the plugin
|
|
80
|
+
class `AaroniaSoapyDevice` become `SpectranSoapyDevice`. The table below lists
|
|
81
|
+
the symbols that also changed shape; the rest changed prefix only, so
|
|
82
|
+
`aaronia_x` is `spectran_x`.
|
|
83
|
+
|
|
84
|
+
What keeps the vendor name: `driver=aaronia`, because it is typed into GQRX and
|
|
85
|
+
SDR++ configuration files already in the world and breaking it reads as "device
|
|
86
|
+
not found"; the crate, the PyPI package and the Python module, which are
|
|
87
|
+
published names; and `AARTSAAPI_*` / `AaroniaRTSAAPI.dll`, which are the
|
|
88
|
+
vendor's own.
|
|
89
|
+
|
|
90
|
+
**The C ABI is renamed with no forwarders**, so a consumer gets an undefined
|
|
91
|
+
symbol at link time and this table is the migration guide. `FfiSourceInfo`'s
|
|
92
|
+
fields moved with the header in the same commit: C reads them positionally, so
|
|
93
|
+
a stale header would go on reading the right bytes under the wrong name.
|
|
94
|
+
|
|
95
|
+
| Old C symbol | New |
|
|
96
|
+
| --- | --- |
|
|
97
|
+
| `aaronia_source_builder_center_frequency` | `spectran_source_builder_center_frequency_hz` |
|
|
98
|
+
| `aaronia_source_builder_span_frequency` | `spectran_source_builder_sample_rate_hz` |
|
|
99
|
+
| `aaronia_source_builder_reference_level` | `spectran_source_builder_reference_level_dbm` |
|
|
100
|
+
| `aaronia_source_set_center_frequency` | `spectran_source_set_center_frequency_hz` |
|
|
101
|
+
| `aaronia_source_set_span_frequency` | `spectran_source_set_sample_rate_hz` |
|
|
102
|
+
| `aaronia_source_set_reference_level` | `spectran_source_set_reference_level_dbm` |
|
|
103
|
+
| `aaronia_sink_builder_center_frequency` | `spectran_sink_builder_center_frequency_hz` |
|
|
104
|
+
| `aaronia_sink_builder_sample_rate` | `spectran_sink_builder_sample_rate_hz` |
|
|
105
|
+
| `aaronia_sink_builder_trans_gain` | `spectran_sink_builder_trans_gain_db` |
|
|
106
|
+
| `aaronia_source_read_sensors` | `spectran_source_get_sensors` |
|
|
107
|
+
| `aaronia_endpoints_client_read_sensors` | `spectran_endpoints_client_get_sensors` |
|
|
108
|
+
| `FfiSourceInfo::center_frequency` / `span_frequency` / `reference_level` | `center_frequency_hz` / `sample_rate_hz` / `reference_level_dbm` |
|
|
109
|
+
| `aaronia_source_get_gps_time(.., double*)` | `spectran_source_get_gps_time_ns(.., int64_t*)` |
|
|
110
|
+
|
|
111
|
+
The two `read_sensors` are a verb change, not a unit one. `read_` advances a
|
|
112
|
+
stream here (`spectran_source_read_samples`); sensors are a snapshot, so they
|
|
113
|
+
join `spectran_source_get_capabilities`.
|
|
114
|
+
|
|
115
|
+
**GPS time changes type as well as name.** `gps_time_ns() -> Option<i64>`, and
|
|
116
|
+
`int64_t*` in C, matching `last_timestamp_ns`. The conversion moved into the
|
|
117
|
+
library because epoch nanoseconds land near `1.7e18`, where an `f64`'s step is
|
|
118
|
+
256 ns — a single `seconds * 1e9` discards tens of nanoseconds the reading
|
|
119
|
+
still had. The SoapySDR plugin's own whole/fractional split is deleted.
|
|
120
|
+
`GpsState::time` is now `time_s`, and the C function accepts a `NULL`
|
|
121
|
+
out-parameter meaning "is there a fix?".
|
|
122
|
+
|
|
123
|
+
It does **not** improve precision. The device reports `gpstime` as an `f64`,
|
|
124
|
+
whose step at present-day epoch values is about 240 ns, and nothing downstream
|
|
125
|
+
recovers what was never there. `utils::gps_seconds_to_nanos` documents that
|
|
126
|
+
bound and a test pins it against the naive multiply. Across receivers ~240 ns
|
|
127
|
+
is ~70 m of ranging uncertainty, so correlate on the per-packet stream
|
|
128
|
+
timestamps and keep GPS for disciplining and wall-clock labelling.
|
|
129
|
+
|
|
130
|
+
`CaptureControl`'s fields gained units without moving its JSON keys: each is
|
|
131
|
+
now pinned by an explicit `#[serde(rename)]` rather than derived from the Rust
|
|
132
|
+
field name, so a later rename cannot change the wire format by accident.
|
|
133
|
+
`tests/wire_contract.rs` asserts those keys, and asserts the vendor
|
|
134
|
+
`AARTSAAPI_Packet` mirror still matches its C header field for field.
|
|
21
135
|
|
|
22
136
|
### Added
|
|
23
|
-
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
137
|
+
- **The stream clock source can be set, not just read.** `device/sclksource`
|
|
138
|
+
selects what disciplines the receiver's clock; a V6 ECO offers `Consumer`,
|
|
139
|
+
`Oscillator`, `GPS`, `PPS`, `10MHz` and three `... Provider` variants. It was
|
|
140
|
+
readable but read-only, and SoapySDR's `setClockSource` merely logged a
|
|
141
|
+
warning telling the operator to change it in RTSA-Suite.
|
|
142
|
+
`SpectranSource::set_clock_source`, `spectran_source_set_clock_source` and the
|
|
143
|
+
plugin's `setClockSource` now write it on both backends — native SDK via
|
|
144
|
+
`ConfigSetString`, HTTP via a `simpleconfig` PUT that is read back to confirm,
|
|
145
|
+
because a `/remoteconfig` PUT naming a block outside the running mission
|
|
146
|
+
answers 200 and changes nothing. A confirmed mismatch is an **error** naming
|
|
147
|
+
the sources the device does offer: being told you are on a reference you are
|
|
148
|
+
not is worse than a failed call. A read-back yielding nothing is reported as
|
|
149
|
+
unconfirmed rather than failed.
|
|
150
|
+
|
|
151
|
+
This is what makes cross-receiver correlation possible: lock a fleet to one
|
|
152
|
+
10 MHz / PPS / GPS reference and their per-packet timestamps share a timebase.
|
|
153
|
+
What is *not* possible is a commanded synchronous start — the SDK's C API has
|
|
154
|
+
no set-time and no arm-at-time entry point, so multi-device work is a shared
|
|
155
|
+
reference plus post-alignment on timestamps.
|
|
30
156
|
|
|
31
|
-
###
|
|
32
|
-
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
157
|
+
### Fixed
|
|
158
|
+
- **A trailing slash in `device_type` no longer produces a malformed open
|
|
159
|
+
string.** The family/mode split asked whether the string contained a slash, so
|
|
160
|
+
`"spectranv6/"` counted as already mode-qualified and went to
|
|
161
|
+
`AARTSAAPI_OpenDevice` verbatim. The mode is now the text after the *first*
|
|
162
|
+
slash, and an empty one means none was given. The ECO was the sharp edge:
|
|
163
|
+
`"spectranv6eco/"` also slipped past the `spectranv6eco/raw` → `iqreceiver`
|
|
164
|
+
remap, so the device was asked for a pipeline it does not have and answered
|
|
165
|
+
with a result code that said nothing about the typo.
|
|
166
|
+
|
|
167
|
+
A `device_type` naming no family (`""`, `"/"`, `"/raw"`) is no longer
|
|
168
|
+
completed into a family-less `"/raw"`; it comes back untouched and is refused
|
|
169
|
+
at both points where a device type reaches the SDK. Both guards are needed
|
|
170
|
+
because enumeration runs first, and an empty family simply enumerates to
|
|
171
|
+
nothing — the old path reported "no devices found" and never mentioned the
|
|
172
|
+
typo. `device_family` and `device_open_mode` stay infallible: a typo in one
|
|
173
|
+
field is not a reason to make four accessors return `Result`.
|
|
174
|
+
- **An over-wide sample rate no longer reaches the hardware before it is
|
|
175
|
+
refused.** `configure_iq_receiver` checked the IQ-mode constraint as its last
|
|
176
|
+
act, so a rate the receiver clock cannot carry was written to
|
|
177
|
+
`main/centerfreq` and `main/spanfreq` first and rejected afterwards — leaving
|
|
178
|
+
the device holding the misconfiguration the check exists to prevent, and per
|
|
179
|
+
the SDK quietly emitting corrupted samples if anything started the stream.
|
|
180
|
+
The check now runs before the first write.
|
|
181
|
+
|
|
182
|
+
It checks against the clock the call *leaves in place*, which is not always
|
|
183
|
+
the one the device holds on entry. Raw mode writes the clock itself, so that
|
|
184
|
+
write is what counts: a V6 left on its 245.76 MHz clock would otherwise pass a
|
|
185
|
+
150 MS/s request that stops being valid the moment this same call drops the
|
|
186
|
+
clock to 92.16 MHz. Every other mode skips that write, so there the live
|
|
187
|
+
setting is the honest number. The read-back after the writes is kept, and is
|
|
188
|
+
what `receiver_clock_hz()` reports. Native SDK only; the HTTP backend already
|
|
189
|
+
validated at the API boundary.
|
|
190
|
+
|
|
191
|
+
## [v0.9.0] - 2026-09-08
|
|
60
192
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
193
|
+
### Added
|
|
194
|
+
- **SoapySDR: device sensors.** The plugin exposed one sensor
|
|
195
|
+
(`cumulative_drops`); it now surfaces the device's live telemetry from
|
|
196
|
+
`/healthstatus` — FPGA and frontend temperature, ADC headroom (dB below
|
|
197
|
+
full scale, so a client can raise the reference level before it clips),
|
|
198
|
+
USB and DSP buffer fill, error and overflow rates, GPS satellites and
|
|
199
|
+
position. `listSensors` / `readSensor` / `getSensorInfo`, read through a
|
|
200
|
+
dedicated connection and briefly cached, so polling them during a
|
|
201
|
+
capture neither stalls the sample stream nor costs a fetch per key.
|
|
202
|
+
HTTP backend only: the native SDK's own `AARTSAAPI_ConfigHealth` tree
|
|
203
|
+
reads all zeros in raw-SDK mode — the live telemetry is computed and
|
|
204
|
+
populated by RTSA-Suite, the managing application, not by the raw SDK,
|
|
205
|
+
confirmed by dumping the tree live from a V6 ECO — so over `sdk=true`
|
|
206
|
+
the plugin reports only `cumulative_drops`, which is real and
|
|
207
|
+
client-side. Verified live over HTTP on a V6 ECO.
|
|
208
|
+
- **SoapySDR: bandwidth API.** `setBandwidth` / `getBandwidth` /
|
|
209
|
+
`listBandwidths` / `getBandwidthRange`. The device's alias-free
|
|
210
|
+
bandwidth is 0.8x its sample rate; these expose that as SoapySDR's
|
|
211
|
+
separate knob, mapping a requested bandwidth to a sample rate and
|
|
212
|
+
driving the already-verified `setSampleRate` — no new device-write path.
|
|
213
|
+
Verified on a V6 ECO: 15.36 MS/s reports 12.288 MHz, `setBandwidth`
|
|
214
|
+
snaps to a rung.
|
|
215
|
+
- **SoapySDR: the native SDK is discoverable.** `find` advertises a
|
|
216
|
+
second `sdk=true` device beside the HTTP one whenever the SDK is
|
|
217
|
+
installed, and `sdk=true` / `serial=` force the native backend instead
|
|
218
|
+
of silently falling back to localhost HTTP. Verified streaming 15.357
|
|
219
|
+
MS/s over the SDK through the plugin from Python.
|
|
220
|
+
- **C ABI.** `aaronia_source_read_sensors` (fills a value struct, `NaN`
|
|
221
|
+
for absent — no ownership, nothing to free);
|
|
222
|
+
`aaronia_source_builder_force_source_type` and `aaronia_sdk_installed`,
|
|
223
|
+
the C equivalent of `force_native_sdk`, which C had no way to request;
|
|
224
|
+
and the stateless bandwidth helpers `aaronia_usable_bandwidth_hz` /
|
|
225
|
+
`aaronia_iq_sample_rate_for_bandwidth`. `DeviceSensors` is the Rust type
|
|
226
|
+
behind the first.
|
|
68
227
|
|
|
69
|
-
###
|
|
70
|
-
- **
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
228
|
+
### Fixed
|
|
229
|
+
- **File playback reported the decompression time as the capture time.**
|
|
230
|
+
A DSPT_IQ-compressed `.rtsa` is decompressed through RTSAFileTool, which
|
|
231
|
+
writes a fresh header stamped with the moment of conversion, so the
|
|
232
|
+
re-opened file reported *now* — a 2020 capture read as 2026. The
|
|
233
|
+
original header's `creation_time`, parsed before compression was
|
|
234
|
+
detected, is now carried across the decompression, and the derived
|
|
235
|
+
start/end fall-backs follow it. Surfaced the first time the fixture test
|
|
236
|
+
ran on a machine with RTSA-Suite installed.
|
|
237
|
+
- **The SoapySDR plugin could not load the SDK inside a host that carries
|
|
238
|
+
its own Qt6.** The loader was given `LOAD_LIBRARY_SEARCH_DEFAULT_DIRS`,
|
|
239
|
+
which searches the host application's directory first; a GNU Radio
|
|
240
|
+
(radioconda) install holds a different Qt6 build there under the same
|
|
241
|
+
names, and the load failed. It now searches only the DLL's own
|
|
242
|
+
directory, the SDK install root, and System32.
|
|
243
|
+
- **The Seify native-SDK test panicked on teardown.** `AaroniaSeifyDevice`
|
|
244
|
+
owns a tokio runtime, and dropping one inside an async context is a
|
|
245
|
+
tokio panic; the test is synchronous now.
|
|
81
246
|
|
|
82
247
|
## [v0.8.2] - 2026-09-08
|
|
83
248
|
|
|
@@ -169,6 +334,84 @@ MSVC against radioconda's SoapySDR, streams 15.357 MS/s over the SDK
|
|
|
169
334
|
into Python; see [docs/VERIFICATION.md](docs/VERIFICATION.md) for the
|
|
170
335
|
two limits found on the way.
|
|
171
336
|
|
|
337
|
+
|
|
338
|
+
_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):_
|
|
339
|
+
|
|
340
|
+
### Performance
|
|
341
|
+
- **The int16 and float16 IQ decoders are ~23% faster end to end.** Both
|
|
342
|
+
built their output with `Vec::with_capacity` then `push` per sample,
|
|
343
|
+
which carries a capacity check the compiler cannot elide and which was
|
|
344
|
+
blocking vectorisation. Collecting from the slice iterator instead —
|
|
345
|
+
`TrustedLen`, so the vector is sized once — measured 495 to 2433 MS/s
|
|
346
|
+
on the decode loop alone, and 123.5 to 152 MS/s over the whole HTTP
|
|
347
|
+
path including framing and transport. float32 was already a single
|
|
348
|
+
`copy_nonoverlapping` and is unchanged.
|
|
349
|
+
|
|
350
|
+
Worth stating what this does not buy: at 605 MB/s the crate is about
|
|
351
|
+
2.5x the fastest a V6 can produce and 6x a WiFi 6E link, so it was not
|
|
352
|
+
the bottleneck before and is not now. What it buys is CPU left over
|
|
353
|
+
for whatever consumes the samples.
|
|
354
|
+
|
|
355
|
+
### Added
|
|
356
|
+
- **`scripts/fake-rtsa-server.py`** serves `/stream` in the real wire
|
|
357
|
+
format at loopback speed, so a decode path can be measured without a
|
|
358
|
+
device or a network. It is how the figures above were taken: over any
|
|
359
|
+
real link the transport dominates and a CPU change is invisible. Its
|
|
360
|
+
docstring carries the caveat that goes with it — over loopback hyper's
|
|
361
|
+
adaptive read buffer never leaves its 8 KiB floor, so kernel time there
|
|
362
|
+
is a property of the harness, not of the crate.
|
|
363
|
+
|
|
364
|
+
### Documentation
|
|
365
|
+
- **`rate_reduction` is time compression, not a sample-rate divider.**
|
|
366
|
+
Five places described it as reducing the rate or optimising bandwidth.
|
|
367
|
+
It thins frames over time — the operation the `waterfall` payload is
|
|
368
|
+
described by — so a continuous IQ stream, having no frames, is
|
|
369
|
+
unaffected: measured at factors of 2, 10 and 64, `sampleFrequency`
|
|
370
|
+
holds at 15,359,988 Hz and the byte rate does not move. That is the
|
|
371
|
+
parameter behaving as specified on a payload it was not meant for.
|
|
372
|
+
`live_stream_rate_reduction_and_scale` had asserted only that packets
|
|
373
|
+
arrived, which is true either way, so nothing caught the wrong
|
|
374
|
+
description; it now pins the IQ behaviour.
|
|
375
|
+
- **The stream can be compressed, up to 6.55x, via `format=rtsa`.**
|
|
376
|
+
Captured from RTSA-Suite's own HTTP Client block:
|
|
377
|
+
`GET /stream?format=rtsa&rate_reduction=8&input=main&compression=5&rate_adaption=0`.
|
|
378
|
+
`format=rtsa` streams the file container and is the only format that
|
|
379
|
+
accepts `compression=N`, which applies the file format's own lossy
|
|
380
|
+
codec. The container carries `float32` (`mSampleType` 11, `DSST_F32N`),
|
|
381
|
+
so level 0 is plain float32 with under 1% of chunk overhead — 123.0
|
|
382
|
+
MB/s against 122.9 theoretical at 15.36 MS/s. Against that baseline the
|
|
383
|
+
codec buys 2.74x at level 1, 4.43x at level 5 and 13.10x at level 9;
|
|
384
|
+
even level 1 undercuts plain `int16` while carrying float precision.
|
|
385
|
+
Ratios hold at 3.84 MS/s too.
|
|
386
|
+
|
|
387
|
+
HTTPSPEC had documented `format=rtsa` only as the thing a typo falls
|
|
388
|
+
back to — "a completely different wire format rather than an error" —
|
|
389
|
+
and this release had gone on to claim compression was neither offered
|
|
390
|
+
nor useful. Both are corrected. Generic HTTP compression is still not
|
|
391
|
+
available on `/stream` and still would not help (zlib manages 1.07x on
|
|
392
|
+
`float32`); Aaronia's codec wins by being lossy and signal-aware.
|
|
393
|
+
|
|
394
|
+
This crate cannot use it for IQ, tested rather than assumed: a real
|
|
395
|
+
compressed payload handed to `Decompressor::decompress` comes back
|
|
396
|
+
rejected as proprietary `DSPT_IQ`, the same wall that stops compressed
|
|
397
|
+
IQ *files*. `format=rtsa` also defaults to `mCompression=1`, so only
|
|
398
|
+
`compression=0` is decodable and that is 20% larger than plain `int16`.
|
|
399
|
+
Spectra should decode, `DSPT_SPECTRA` being documented, but this
|
|
400
|
+
mission has no spectra input to try.
|
|
401
|
+
|
|
402
|
+
### Performance
|
|
403
|
+
- **The control plane is now requested compressed.** `reqwest` gains the
|
|
404
|
+
`deflate` and `gzip` features, so the client sends
|
|
405
|
+
`Accept-Encoding: gzip,deflate` where it previously sent none. Measured
|
|
406
|
+
against the device: `/remoteconfig` 17,432 bytes to 3,299 deflated,
|
|
407
|
+
`/healthstatus` 6,100 to 1,436. Both are read at every device open, and
|
|
408
|
+
`/healthstatus` again on each stream-gap report. No effect on `/stream`,
|
|
409
|
+
which the server does not compress at any level.
|
|
410
|
+
- The reader channel's size comment claimed ~157 KiB chunks and ~10 MB of
|
|
411
|
+
queue. Measured against a live server it is 64 KiB for 71% of chunks,
|
|
412
|
+
so the queue is ~4 MB, about 45 ms at the 88 MB/s a WiFi 6E path
|
|
413
|
+
delivers.
|
|
414
|
+
|
|
172
415
|
## [v0.8.1] - 2026-09-07
|
|
173
416
|
|
|
174
417
|
### Fixed
|
|
@@ -3092,7 +3092,7 @@ dependencies = [
|
|
|
3092
3092
|
|
|
3093
3093
|
[[package]]
|
|
3094
3094
|
name = "python-aaronia"
|
|
3095
|
-
version = "0.
|
|
3095
|
+
version = "0.10.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.10.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.10.0"
|
|
6
6
|
edition = "2024"
|
|
7
7
|
repository = "https://github.com/isaacbentley/sdr-aaronia-rs"
|
|
8
8
|
readme = "README.md"
|
|
@@ -4,7 +4,7 @@ This document describes the architecture of the `sdr-aaronia-rs` crate, which pr
|
|
|
4
4
|
|
|
5
5
|
## 1. Overview
|
|
6
6
|
|
|
7
|
-
The Aaronia ecosystem offers three ways to access Spectran hardware: the native RTSA-Suite PRO SDK, the HTTP streaming API, and recorded RTSA files. This crate wraps all three behind a single deterministic facade, `
|
|
7
|
+
The Aaronia ecosystem offers three ways to access Spectran hardware: the native RTSA-Suite PRO SDK, the HTTP streaming API, and recorded RTSA files. This crate wraps all three behind a single deterministic facade, `SpectranSource`, so callers can work with Aaronia hardware without depending on the underlying transport.
|
|
8
8
|
|
|
9
9
|
## 2. Architecture
|
|
10
10
|
|
|
@@ -13,7 +13,7 @@ The crate is built around an automatic source-detection router that instantiates
|
|
|
13
13
|
```mermaid
|
|
14
14
|
graph TB
|
|
15
15
|
subgraph "sdr-aaronia-rs Unified API"
|
|
16
|
-
API[
|
|
16
|
+
API[SpectranSourceBuilder]
|
|
17
17
|
Detection[Deterministic Auto-Detection]
|
|
18
18
|
end
|
|
19
19
|
|
|
@@ -32,13 +32,13 @@ graph TB
|
|
|
32
32
|
|
|
33
33
|
## 3. Source Selection
|
|
34
34
|
|
|
35
|
-
`
|
|
35
|
+
`SpectranSource::new(config)` selects a backend using strict, deterministic rules:
|
|
36
36
|
|
|
37
37
|
| Configuration | Backend | Fallback | Platforms |
|
|
38
38
|
|---|---|---|---|
|
|
39
|
-
| `
|
|
40
|
-
| `
|
|
41
|
-
| `
|
|
39
|
+
| `SpectranConfig::from_file("recording.rtsa")` | File | None — fails if the file is missing | All |
|
|
40
|
+
| `SpectranConfig::from_http("http://192.168.1.100")` | HTTP | None — fails if unreachable | All |
|
|
41
|
+
| `SpectranConfig::default()` (nothing specified) | Native SDK, when the `native-sdk` feature is enabled and the SDK library is found | Connects to the default HTTP endpoint (`http://localhost:54664`) otherwise. Since `native-sdk` is not a default feature, a stock build always resolves to HTTP. | All (SDK itself is Windows/Linux only) |
|
|
42
42
|
| Force option, e.g. `config.force_native_sdk()` | Forced type | None — fails if unavailable | Depends on forced type |
|
|
43
43
|
|
|
44
44
|
## 4. Backend Implementations
|
|
@@ -58,7 +58,7 @@ Available on Windows and Linux only, behind the non-default `native-sdk` feature
|
|
|
58
58
|
|
|
59
59
|
- Implements the V9 RTSA HTTP specification.
|
|
60
60
|
- Supports Basic Auth and token-based authentication.
|
|
61
|
-
- **Formats**: supports the `Json`, `Int16`, `Float16`, and `Float32` wire formats, maintaining a persistent chunked-transfer buffer across packet boundaries. The default is `Float32` (binary, lossless); `Int16` is opt-in via `
|
|
61
|
+
- **Formats**: supports the `Json`, `Int16`, `Float16`, and `Float32` wire formats, maintaining a persistent chunked-transfer buffer across packet boundaries. The default is `Float32` (binary, lossless); `Int16` is opt-in via `SpectranConfig::stream_format()` / `stream_scale()`.
|
|
62
62
|
- Beyond streaming, `HttpEndpointsClient` exposes device control and health telemetry (as a generic configuration tree) within a headless Rust process.
|
|
63
63
|
|
|
64
64
|
### 4.3 RTSA Files
|
|
@@ -69,21 +69,21 @@ Available on Windows and Linux only, behind the non-default `native-sdk` feature
|
|
|
69
69
|
|
|
70
70
|
## 5. Channel Hopping and Dwell Control
|
|
71
71
|
|
|
72
|
-
Under the `sdr-source` feature, `
|
|
72
|
+
Under the `sdr-source` feature, `SpectranSdrSource` (in `sdr_source_impl.rs`) implements the `SdrSource` trait from the external `orecchiette-sdr-source-rs` crate, which the crate re-exports as `sdr_source`. Automatic mid-stream retuning via `SourceConfig.channels_hz` is supported per backend:
|
|
73
73
|
|
|
74
74
|
- **Native SDK**: re-issues `configure_iq_receiver` with the new center frequency, applying the change to the open device handle via the `main/centerfreq` configuration key. No stream restart is required.
|
|
75
|
-
- **HTTP**: `
|
|
75
|
+
- **HTTP**: `SpectranSource::set_center_frequency_hz` wraps `HttpEndpointsClient::configure_capture` as a `PUT` to the license-free `/control` endpoint, always sending the complete capture tuple (center, span, reference level) with unchanged values filled from the cached config. The full tuple is required: RTSA servers silently ignore a capture `PUT` carrying only one of the two frequency fields (it returns `{"success":true}` but the device stays put — live-verified). Hopping deliberately avoids `/remoteconfig` and is not gated on the license probe. (Whether that path needs the "Remote Config" license at all is unconfirmed: a system without the license accepted `/remoteconfig` writes in testing. See docs/HTTPSPEC.md.)
|
|
76
76
|
- **File**: not supported.
|
|
77
77
|
|
|
78
78
|
Hop dwell deadlines come from `sdr_source::DwellController`; per-hop pacing additionally inserts a ~75 ms settle after every retune (`RETUNE_SETTLE` in `sdr_source_impl.rs`) to ride out the RTSA's apply-config latency and flush stale samples from the pipeline.
|
|
79
79
|
|
|
80
80
|
## 6. Overrun Detection and Fault Handling
|
|
81
81
|
|
|
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 `
|
|
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 `SpectranSource::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
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
|
-
The Aaronia capture thread (`
|
|
86
|
+
The Aaronia capture thread (`SpectranSdrSource::start`) is wrapped in `catch_unwind`: a panic inside the pump loop is logged rather than silently unwinding the thread.
|
|
87
87
|
|
|
88
88
|
## 7. FutureSDR Integration
|
|
89
89
|
|