python-aaronia 0.6.0__tar.gz → 0.6.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.
Files changed (80) hide show
  1. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/CHANGELOG.md +86 -3
  2. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/Cargo.lock +2 -2
  3. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/Cargo.toml +1 -1
  4. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/DESIGN.md +2 -2
  5. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/PKG-INFO +1 -1
  6. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/PLUGINS.md +9 -1
  7. python_aaronia-0.6.2/README.md +212 -0
  8. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/docs/APPS.md +1 -1
  9. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/docs/HTTPSPEC.md +105 -5
  10. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/docs/QUICKSTART.md +32 -13
  11. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/docs/SDKSPEC.md +58 -0
  12. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/docs/USAGE.md +28 -2
  13. python_aaronia-0.6.2/docs/VERIFICATION.md +59 -0
  14. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/python-aaronia/Cargo.toml +1 -1
  15. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/soapy-aaronia/AaroniaSoapyDevice.cpp +48 -7
  16. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/src/http_endpoints.rs +21 -7
  17. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/src/lib.rs +3 -2
  18. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/src/native_sdk.rs +244 -12
  19. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/src/unified_sink.rs +5 -0
  20. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/src/unified_source.rs +80 -4
  21. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/src/utils.rs +196 -7
  22. python_aaronia-0.6.0/README.md +0 -291
  23. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/.cargo/config.toml +0 -0
  24. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/.gitattributes +0 -0
  25. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/.gitignore +0 -0
  26. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/CONTRIBUTING.md +0 -0
  27. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/LICENSE +0 -0
  28. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/benches/decompress_block.rs +0 -0
  29. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/benches/deinterleave_dual_iq.rs +0 -0
  30. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/benches/parse_int16_packet.rs +0 -0
  31. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/benches/rtsa_open_and_read.rs +0 -0
  32. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/deny.toml +0 -0
  33. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/docs/FILESPEC.md +0 -0
  34. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/examples/channel_hopping.rs +0 -0
  35. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/examples/device_control.rs +0 -0
  36. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/examples/dump_metadata.rs +0 -0
  37. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/examples/http_iq_quickstart.rs +0 -0
  38. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/examples/native_sdk_basic.rs +0 -0
  39. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/examples/native_sdk_transmit.rs +0 -0
  40. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/examples/noaa_scanner.rs +0 -0
  41. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/examples/python_arrow_example.py +0 -0
  42. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/examples/read_rtsa_file.rs +0 -0
  43. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/examples/soapy_python_example.py +0 -0
  44. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/include/aaronia.h +0 -0
  45. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/pyproject.toml +0 -0
  46. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/python-aaronia/README.md +0 -0
  47. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/python-aaronia/aaronia.pyi +0 -0
  48. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/python-aaronia/src/lib.rs +0 -0
  49. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/python-aaronia/test_basic.py +0 -0
  50. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/scripts/ci-local.sh +0 -0
  51. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/soapy-aaronia/AaroniaSoapyDevice.hpp +0 -0
  52. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/soapy-aaronia/CMakeLists.txt +0 -0
  53. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/soapy-aaronia/README.md +0 -0
  54. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/soapy-aaronia/Registration.cpp +0 -0
  55. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/src/c_api.rs +0 -0
  56. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/src/decompression.rs +0 -0
  57. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/src/detection.rs +0 -0
  58. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/src/error.rs +0 -0
  59. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/src/file_source.rs +0 -0
  60. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/src/http_sink.rs +0 -0
  61. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/src/http_source.rs +0 -0
  62. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/src/http_streaming.rs +0 -0
  63. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/src/sdk_sink.rs +0 -0
  64. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/src/sdk_source.rs +0 -0
  65. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/src/sdr_source_impl.rs +0 -0
  66. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/src/seify_impl.rs +0 -0
  67. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/tests/c_api_test.rs +0 -0
  68. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/tests/http_mock_test.rs +0 -0
  69. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/tests/http_resilience_test.rs +0 -0
  70. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/tests/http_sink_test.rs +0 -0
  71. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/tests/integration_test.rs +0 -0
  72. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/tests/live_smoke.rs +0 -0
  73. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/tests/native_sdk_load.rs +0 -0
  74. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/tests/properties.proptest-regressions +0 -0
  75. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/tests/properties.rs +0 -0
  76. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/tests/rtsa_negative_test.rs +0 -0
  77. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/tests/sdr_source_impl_test.rs +0 -0
  78. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/tests/spec_coverage.rs +0 -0
  79. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/tests/test_cw_mag.rs +0 -0
  80. {python_aaronia-0.6.0 → python_aaronia-0.6.2}/tests/test_cw_meta.rs +0 -0
@@ -4,6 +4,89 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [v0.6.2] - 2026-08-12
8
+
9
+ ## [v0.6.1] - 2026-08-12
10
+
11
+ ### Fixed
12
+ - **The SoapySDR plugin advertised sample rates the hardware cannot
13
+ produce.** Seven of the ten rates it listed, 1, 2, 5, 10 and 20 MHz
14
+ among them, do not exist on the device, which runs at 61.44 MHz
15
+ divided by a power of two. Applications build their rate dropdowns
16
+ from that list, so choosing 10 MHz ran the device at a different rate
17
+ while the application went on displaying 10. The list is now the real
18
+ ladder, 61.44 MHz down to 120 kHz, and `setSampleRate` snaps to the
19
+ nearest one and logs when it has to.
20
+ - **The crate reported the requested sample rate rather than the one in
21
+ use.** The device adjusts a rate it cannot produce, so
22
+ `get_source_info()` described a capture that was not happening. HTTP
23
+ sources now report the rate, centre frequency and usable bandwidth
24
+ from the stream's own metadata once packets arrive.
25
+
26
+ ### Added
27
+ - `iq_sample_rates`, `usable_bandwidth_hz`, `nearest_iq_sample_rate` and
28
+ `iq_sample_rate_for_bandwidth` in `utils`, with the constants
29
+ `IQ_CLOCK_HZ` and `USABLE_BANDWIDTH_RATIO`. The device samples at
30
+ 61.44 MHz divided by a power of two and delivers 0.8 of that as
31
+ alias-free bandwidth. Callers were deriving their own rates from
32
+ guesses, so the relationships now live in one tested place. Wanting
33
+ 8 MHz of spectrum needs 10 MHz of sampling, and
34
+ `iq_sample_rate_for_bandwidth` returns the 15.36 MHz that provides it.
35
+ `iq_sample_rates_for_clock` covers devices whose receiver clock is not
36
+ the default: a V6 ECO has a fixed clock and gives the measured ladder,
37
+ while a full V6 can select a faster one and reach further. Aaronia's
38
+ samples set `device/receiverclock` to "92MHz" or "245MHz"; only the
39
+ default has been checked against hardware.
40
+
41
+ ### Added (native SDK)
42
+ - **Device-family auto-detection.** `detect_device_family` and
43
+ `open_detected_device` try each known family in turn, so an ECO owner
44
+ no longer has to know that the default `spectranv6` will not find
45
+ their device and that `spectranv6eco` is the string they needed.
46
+ - **`read_spectra`**, with the stream index taken from the open mode
47
+ rather than assumed. `spectranv6/raw` carries spectra on stream 2 and
48
+ IQ on stream 0; every other mode uses stream 0. Hardware-unverified.
49
+ - **`receiver_clock_hz`** on the native source, and
50
+ `spectranv6eco/rtsa` added to the known open modes. The clock sets the
51
+ rate ladder's ceiling, so callers that need to know which rates exist
52
+ can now ask instead of assuming.
53
+
54
+ ### Fixed (native SDK)
55
+ - **The V6 ECO's fixed receiver clock was recorded as 61.44 MHz.** It is
56
+ 92.16 MHz: an ECO streams at 61.44 MHz sampling, measured against real
57
+ hardware, and the constraint checked at configuration time is
58
+ `span * 1.5 <= clock`. The old value rejected every span above
59
+ 40.96 MHz, including the device's own maximum.
60
+ - **Dual-channel capture selected the wrong mode and would have
61
+ returned corrupted samples.** `RxChannel::Rx1And2` wrote
62
+ `device/receiverchannel = "Rx1+Rx2"`, which delivers the two inputs as
63
+ two independent streams at indices 0 and 1. This crate reads a single
64
+ stream and deinterleaves it, which is the contract of the other mode,
65
+ `"Rx12"`. On a two-input V6 the result would have been Rx1's samples
66
+ split into two bogus channels, with no error anywhere. It now writes
67
+ `"Rx12"`. Aaronia's `RawIQ2RX` and `RawIQ2RXInterleave` samples
68
+ demonstrate one mode each. Still hardware-unverified.
69
+ - **Sweep mode set the wrong resolution-bandwidth key.** It sent
70
+ `main/rbw`, which no Aaronia sample uses; the key is `main/rbwfreq`.
71
+ Checked against Aaronia's published `SweepSpectrumEco` sample, which
72
+ also confirms `main/startfreq`, `main/stopfreq`, `main/reflevel` and
73
+ the `spectranv6eco/sweepsa` open string that this crate already used.
74
+ The sweep path remains hardware-unverified.
75
+
76
+ ### Documentation
77
+ - **GPS time needs GPS switched on, and the crate does not do it.**
78
+ Devices ship with `device/gpsmode` disabled, so `get_gps_time` would
79
+ return `None` indefinitely and appear broken. Aaronia's `GPSTime`
80
+ sample sets `device/gpsmode` to "Location and Time" and
81
+ `device/sclksource` to "GPS Provider" before starting the device;
82
+ `get_gps_time` now says so.
83
+ - Documented what `/control`'s `frequencySpan` actually means. It is a
84
+ request for usable bandwidth, not a sample rate: the device picks the
85
+ rate whose alias-free span is nearest, so 2.5 MHz yields 3.84 MHz and
86
+ 10 MHz yields 15.36 MHz. Values on the rate ladder round-trip exactly,
87
+ which is why the field looks like a sample rate in ordinary use.
88
+ Verified across nine requests on a V6 ECO.
89
+
7
90
  ## [v0.6.0] - 2026-08-11
8
91
 
9
92
  Reliability and documentation release. The HTTP backend now handles a
@@ -80,9 +163,9 @@ hardware and which have not.
80
163
  ### Release
81
164
  - Release archives now carry the module, install instructions and the
82
165
  licence, and their names state the version, OS and architecture.
83
- - The Linux module ships stripped of debug symbols, down from about
84
- 19 MB. The release job checks the stripped module still loads before
85
- publishing it.
166
+ - The Linux module ships stripped of debug symbols, 19.1 MB down to
167
+ 15.4 MB. The rest is statically linked Rust, not symbols. The release
168
+ job checks the stripped module still loads before publishing it.
86
169
  - GitHub releases now use this file's entry for the tag as their
87
170
  description, with the generated commit list below it.
88
171
 
@@ -3027,7 +3027,7 @@ dependencies = [
3027
3027
 
3028
3028
  [[package]]
3029
3029
  name = "python-aaronia"
3030
- version = "0.6.0"
3030
+ version = "0.6.2"
3031
3031
  dependencies = [
3032
3032
  "arrow",
3033
3033
  "num-complex",
@@ -3556,7 +3556,7 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
3556
3556
 
3557
3557
  [[package]]
3558
3558
  name = "sdr-aaronia-rs"
3559
- version = "0.6.0"
3559
+ version = "0.6.2"
3560
3560
  dependencies = [
3561
3561
  "anyhow",
3562
3562
  "bitflags 2.13.1",
@@ -2,7 +2,7 @@
2
2
  name = "sdr-aaronia-rs"
3
3
  description = "Unified Rust interface for Aaronia Spectran Spectrum Analyzers / SDRs, featuring Python bindings, a SoapySDR plugin, HTTP streaming, and native SDK support."
4
4
  license = "GPL-3.0-or-later"
5
- version = "0.6.0"
5
+ version = "0.6.2"
6
6
  edition = "2024"
7
7
  repository = "https://github.com/isaacbentley/sdr-aaronia-rs"
8
8
  readme = "README.md"
@@ -51,7 +51,7 @@ Available on Windows and Linux only, behind the non-default `native-sdk` feature
51
51
  - **IQ mode constraint**: enforces `span * 1.5 <= receiverclock` by reading the live device clock before streaming.
52
52
  - **Data path (RX)**: polls `GetPacket` on the C++ side every 5 ms. Where the packet stride allows, the raw sample buffer is reinterpreted in place as `Complex32` (no per-sample conversion) before being copied into the caller's buffer.
53
53
  - **Data path (TX)**: provides an experimental `TxStream` API built on `SendPacket`. This path is hardware-unverified pending full V6 testing, as the ECO model lacks TX.
54
- - **Sweep-spectra mode**: configures non-IQ sweeping via `SweepsaConfig` by modifying `"main/startfreq"`, `"main/stopfreq"`, `"main/rbw"`, and related keys (these config paths are inferred from the SDK naming convention and are hardware-unverified). Supports the `"spectranv6/sweepsa"` and `"spectranv6eco/sweepsa"` open strings.
54
+ - **Sweep-spectra mode**: configures non-IQ sweeping via `SweepsaConfig` by modifying `"main/startfreq"`, `"main/stopfreq"`, `"main/rbwfreq"` and `"main/reflevel"`. These key names match Aaronia's own `SweepSpectrumEco` sample; the code previously sent `"main/rbw"`, which no sample uses. Supports the `"spectranv6/sweepsa"` and `"spectranv6eco/sweepsa"` open strings, both of which the samples confirm. The path itself remains hardware-unverified.
55
55
  - **Health and GPS telemetry**: exposes `HealthState` and `GpsState` structs populated by a recursive configuration tree-walker (`walk_health_tree`) that fetches device diagnostics dynamically. Not yet exercised against live hardware.
56
56
 
57
57
  ### 4.2 HTTP Streaming
@@ -72,7 +72,7 @@ Available on Windows and Linux only, behind the non-default `native-sdk` feature
72
72
  Under the `sdr-source` feature, `AaroniaSdrSource` (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**: `AaroniaSource::set_center_frequency` 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). The RTSA-Suite "Remote Config" license gates the separate `/remoteconfig` write path, which the hopping code deliberately avoids hopping is not gated on the license probe.
75
+ - **HTTP**: `AaroniaSource::set_center_frequency` 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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-aaronia
3
- Version: 0.6.0
3
+ Version: 0.6.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+)
@@ -122,7 +122,15 @@ Two different knobs, easy to confuse:
122
122
  sdr = SoapySDR.Device("driver=aaronia,url=http://atc.local:54664,format=I16")
123
123
  ```
124
124
 
125
- ## 3. Metrics and Error Handling
125
+ ## 3. Transmit
126
+
127
+ TX is available through the SoapySDR plugin only when it is built
128
+ against the native SDK on Windows or Linux. Elsewhere `setupStream` for
129
+ TX fails with a descriptive error. Bursts are sent for immediate
130
+ transmission; timed TX is not supported. The whole TX path is
131
+ hardware-unverified. See [docs/VERIFICATION.md](docs/VERIFICATION.md).
132
+
133
+ ## 4. Metrics and Error Handling
126
134
 
127
135
  Both the native Rust API and the Python/C++ bindings expose critical metrics to monitor the health of your RTSA stream:
128
136
 
@@ -0,0 +1,212 @@
1
+ # sdr-aaronia-rs
2
+
3
+ [![Crates.io](https://img.shields.io/crates/v/sdr-aaronia-rs.svg)](https://crates.io/crates/sdr-aaronia-rs)
4
+ [![Docs.rs](https://docs.rs/sdr-aaronia-rs/badge.svg)](https://docs.rs/sdr-aaronia-rs)
5
+ [![CI](https://github.com/isaacbentley/sdr-aaronia-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/isaacbentley/sdr-aaronia-rs/actions/workflows/ci.yml)
6
+ [![License: GPL-3.0-or-later](https://img.shields.io/github/license/isaacbentley/sdr-aaronia-rs.svg)](https://choosealicense.com/licenses/gpl-3.0/)
7
+
8
+ One API for Aaronia SPECTRAN analyzers and SDRs, whether the samples
9
+ come from the native SDK, an RTSA-Suite HTTP server, or a recorded file.
10
+ Python bindings and a SoapySDR plugin come from the same engine.
11
+
12
+ *Disclaimer: This project is not affiliated with Aaronia AG. Aaronia, SPECTRAN, and RTSA-Suite PRO are trademarks of Aaronia AG.*
13
+
14
+ Working with a SPECTRAN usually means choosing a transport first and
15
+ then writing against whatever API that transport exposes. `AaroniaSource`
16
+ removes the choice: point it at a file, a URL, or nothing at all, and it
17
+ selects a backend and presents the same interface either way.
18
+
19
+ | What you configure | What it uses |
20
+ | --- | --- |
21
+ | `file_path` | Buffered playback of an RTSA capture file |
22
+ | `http_base_url` | HTTP streaming from an RTSA-Suite server block |
23
+ | Neither | The native SDK, falling back to `localhost:54664` |
24
+ | `force_source_type` | Exactly the backend you name |
25
+
26
+ ## What it does
27
+
28
+ - **Streams IQ and spectra over HTTP** in JSON, Int16, Float16 or
29
+ Float32, with Basic and token authentication. Retuning mid-stream
30
+ needs no Aaronia licence, and dropped streams reconnect on their own.
31
+ - **Reads `.rtsa` capture files** through buffered I/O, with metadata
32
+ extraction and multi-stream support.
33
+ - **Talks to the hardware directly** through the Aaronia SDK on Windows
34
+ and Linux, including transmit. The SDK is not available for macOS.
35
+ - **Controls and monitors the device**: streaming parameters, health,
36
+ input enumeration and the configuration tree.
37
+ - **Plugs into FutureSDR** with `HttpSource` and `HttpSink` flowgraph
38
+ blocks.
39
+
40
+ ## Installation
41
+
42
+ Add the following to your `Cargo.toml`:
43
+
44
+ ```toml
45
+ [dependencies]
46
+ # By default, includes HTTP, File, native sdr-source trait, and C FFI backend support
47
+ sdr-aaronia-rs = "0.6"
48
+ tokio = { version = "1.43", features = ["rt-multi-thread", "macros"] }
49
+
50
+ # To enable additional backends, opt into their features (e.g. native-sdk, futuresdr)
51
+ # sdr-aaronia-rs = { version = "0.6", features = ["native-sdk", "futuresdr"] }
52
+ ```
53
+
54
+ ## Quickstart
55
+
56
+ Set the RF parameters and read:
57
+
58
+ ```rust,no_run
59
+ use sdr_aaronia_rs::{AaroniaSource, AaroniaConfig};
60
+ use anyhow::Result;
61
+
62
+ #[tokio::main]
63
+ async fn main() -> Result<()> {
64
+ let config = AaroniaConfig::default()
65
+ .center_frequency(446.0e6) // 446 MHz
66
+ .span_frequency(10.0e6) // 10 MHz span
67
+ .reference_level(-30.0); // -30 dBm
68
+
69
+ let mut source = AaroniaSource::new(config).await?;
70
+
71
+ let mut buffer = Vec::with_capacity(1024);
72
+ let n = source.read_samples(&mut buffer, 1024).await?;
73
+ println!("Received {} IQ samples", n);
74
+
75
+ Ok(())
76
+ }
77
+ ```
78
+
79
+ ## Usage
80
+
81
+ [docs/USAGE.md](docs/USAGE.md) has worked examples for everything the
82
+ quickstart leaves out: the builder pattern, explicit backend selection,
83
+ wire formats and network bandwidth, configuration profiles, device
84
+ control, FutureSDR integration, authentication and low-level stream
85
+ access. Its Rust snippets are compiled as doctests, and it indexes the
86
+ runnable programs in [`examples/`](examples/).
87
+
88
+ [docs/QUICKSTART.md](docs/QUICKSTART.md) covers configuring the
89
+ RTSA-Suite HTTP Server block, which all of the above depends on.
90
+
91
+ ## Using it from other tools
92
+
93
+ The Rust crate is the engine. The same code drives three other
94
+ surfaces, so an Aaronia device works in the tools people already use.
95
+
96
+ ### Python
97
+
98
+ ```bash
99
+ pip install python-aaronia
100
+ ```
101
+
102
+ ```python
103
+ import aaronia
104
+
105
+ cfg = aaronia.AaroniaConfig()
106
+ cfg.http_base_url = "http://localhost:54664"
107
+ cfg.center_freq = 2.44e9
108
+ cfg.sample_rate = 15.36e6
109
+
110
+ src = aaronia.AaroniaSource()
111
+ src.start_streaming(cfg)
112
+ samples = src.read_samples_numpy(65536) # numpy complex64
113
+ src.stop_streaming()
114
+ ```
115
+
116
+ Reads land in NumPy or PyArrow with one copy out of the receive buffer.
117
+ Blocking calls release the GIL, errors arrive as typed exceptions, and
118
+ the package ships type stubs. Wheels are abi3 for CPython 3.9 and
119
+ later. Full reference: [python-aaronia/README.md](python-aaronia/README.md).
120
+
121
+ ### SoapySDR: GQRX, SDR++, GNU Radio and others
122
+
123
+ ```bash
124
+ SoapySDRUtil --probe="driver=aaronia,url=http://localhost:54664"
125
+ ```
126
+
127
+ Every release attaches a prebuilt plugin for Linux, macOS and Windows,
128
+ so no toolchain is needed. The plugin streams CF32 and CS16, honours
129
+ `timeoutUs` with partial reads, stays safe to retune while streaming,
130
+ and reports timestamps and dropped-block counts.
131
+
132
+ Per-application setup is in [docs/APPS.md](docs/APPS.md). Installation,
133
+ building from source and the wire-format trade-offs are in
134
+ [PLUGINS.md](PLUGINS.md); pass `format=I16` to halve network bandwidth,
135
+ which is a real wire-format change rather than a client-side conversion.
136
+
137
+ ### seify (Rust-native)
138
+
139
+ Enable the `seify` feature and construct the device with
140
+ `AaroniaSeifyDevice::from_args`. It is not part of seify's built-in
141
+ enumeration, so it will not appear in `seify::enumerate()`. See
142
+ [PLUGINS.md](PLUGINS.md).
143
+
144
+ ### What has been tested
145
+
146
+ Not every path has run against hardware. Transmit, dual-channel capture
147
+ and the native-SDK backend have not.
148
+ [docs/VERIFICATION.md](docs/VERIFICATION.md) gives the status of each
149
+ feature and how it was checked.
150
+
151
+ ## Connection Resilience
152
+
153
+ Connecting (the `/info` probe and initial tuning PUT) retries transient
154
+ failures up to 4 times with exponential backoff, bounded by a 10 second
155
+ total budget. Refused connections, unresolved DNS and 5xx/408/429
156
+ responses are retried; 4xx and configuration errors fail on the first
157
+ attempt. This matters for `*.local` hostnames, which refuse the first
158
+ connection from a cold process while mDNS resolves.
159
+
160
+ `AaroniaConfig::read_timeout` (default 30 s) bounds `read_samples`.
161
+ `read_samples_deadline`, and therefore the SoapySDR and seify paths, uses
162
+ its caller's per-call deadline instead.
163
+
164
+ A dropped HTTP stream, from an RTSA restart or a network interruption,
165
+ reconnects automatically. This is `AaroniaConfig::auto_reconnect`,
166
+ enabled by default. The reader reopens the stream, re-applies the
167
+ current tuning (a restarted server returns to its mission's frequency),
168
+ and flags the first packet after the gap as an overrun so callers know
169
+ samples were missed. After 5 attempts, roughly 8 seconds of backoff, it
170
+ stops and reads report a closed stream, matching the behaviour of
171
+ `auto_reconnect(false)`.
172
+
173
+ ## Feature Flags
174
+
175
+ Functionality is grouped behind Cargo features so unused dependencies stay out of your build:
176
+
177
+ | Feature | Description | Default |
178
+ |---|---|---|
179
+ | `http` | HTTP streaming via `reqwest` and `tokio`. | **Yes** |
180
+ | `file` | Buffered RTSA file parsing. | **Yes** |
181
+ | `native-sdk` | Links the proprietary Aaronia C++ SDK. Windows/Linux only. | No |
182
+ | `futuresdr` | Enables the FutureSDR block API: `HttpSource`, `HttpSink`, and their builders. Implies `http`. | No |
183
+ | `sdr-source` | Integrates `AaroniaSdrSource` implementing the native `SdrSource` traits. | **Yes** |
184
+ | `ffi` | Builds the C-API export layer. | **Yes** |
185
+
186
+ ## Testing & Contributing
187
+
188
+ The test suite consists of unit tests, integration tests against LFS captures, and property tests enforcing specification invariants.
189
+
190
+ Please see [CONTRIBUTING.md](CONTRIBUTING.md) for detailed instructions on running the test suite, generating coverage reports, and formatting your code before submitting a Pull Request.
191
+
192
+ ## Documentation
193
+
194
+ Start here:
195
+
196
+ - [Quickstart](docs/QUICKSTART.md) — configuring an RTSA-Suite mission, first samples in Rust, Python and SoapySDR, and troubleshooting for common setup failures.
197
+ - [Usage](docs/USAGE.md) — worked examples for each part of the API, plus the `AARONIA_SDK_PATH` and `AARONIA_USER_AGENT` environment variables.
198
+ - [Using existing SDR apps](docs/APPS.md) — SDR++, GQRX, GNU Radio, SoapySDR from Python.
199
+
200
+ Reference:
201
+
202
+ - [Verification status](docs/VERIFICATION.md) — which features have been tested against real hardware.
203
+ - [SDR plugins](PLUGINS.md) — SoapySDR and seify setup, wire-format trade-offs, metrics.
204
+ - [Architecture & Design](DESIGN.md) — Internal architecture and execution flow.
205
+ - [RTSA File Format Specification](docs/FILESPEC.md) — On-disk `.rtsa` capture-file format and how this crate parses it.
206
+ - [HTTP API Specification](docs/HTTPSPEC.md) — The RTSA HTTP streaming and control API, including Remote Config licence detection.
207
+ - [Native SDK Specification](docs/SDKSPEC.md) — The Aaronia RTSA-Suite PRO SDK surface and the Rust binding notes.
208
+ - [Changelog](CHANGELOG.md) — Release history.
209
+
210
+ ## License
211
+
212
+ This project is licensed under the GNU General Public License v3.0 or later (GPL-3.0-or-later) - see the LICENSE file for details.
@@ -73,7 +73,7 @@ from SoapySDR import SOAPY_SDR_RX, SOAPY_SDR_CF32
73
73
  import numpy as np
74
74
 
75
75
  sdr = SoapySDR.Device("driver=aaronia,url=http://localhost:54664")
76
- sdr.setSampleRate(SOAPY_SDR_RX, 0, 12.288e6)
76
+ sdr.setSampleRate(SOAPY_SDR_RX, 0, 15.36e6)
77
77
  sdr.setFrequency(SOAPY_SDR_RX, 0, 2.44e9)
78
78
 
79
79
  rx = sdr.setupStream(SOAPY_SDR_RX, SOAPY_SDR_CF32)
@@ -283,6 +283,16 @@ packet-metadata object, so `payload`, `minPower`, `maxPower`, and
283
283
  }
284
284
  ```
285
285
 
286
+ > **`frequencySpan` is a usable-bandwidth request, not a sample rate.**
287
+ > The device runs at 61.44 MHz divided by a power of two. Given a span
288
+ > it cannot produce, it selects the rate whose alias-free bandwidth
289
+ > (0.8 x Fs) is nearest the request: 2.5 MHz yields Fs = 3.84 MHz,
290
+ > 1.3 MHz yields 1.92 MHz, and 10 MHz yields 15.36 MHz. A value that is
291
+ > itself on the rate ladder round-trips exactly, which is why the field
292
+ > behaves like a sample rate in ordinary use. `sampleFrequency` in the
293
+ > packet metadata reports the rate in force; `startFrequency` and
294
+ > `endFrequency` bound the usable span. Verified on a V6 ECO.
295
+
286
296
  > **Both frequency fields are required for a retune to apply.** Live
287
297
  > testing against RTSA-Suite PRO (HTTP server block fed by a SPECTRAN
288
298
  > V6 ECO) shows the server returns `{"success":true}` for a capture
@@ -388,6 +398,44 @@ packet-metadata object, so `payload`, `minPower`, `maxPower`, and
388
398
  }
389
399
  ```
390
400
 
401
+ **Full PUT form** (config tree). Aaronia's own automation example uses this
402
+ shape for every setting it changes:
403
+
404
+ ```json
405
+ {
406
+ "request": 11,
407
+ "receiverName": "Block_Spectran_V6B_0",
408
+ "config": {
409
+ "type": "group",
410
+ "items": [
411
+ {
412
+ "type": "group",
413
+ "name": "main",
414
+ "items": [
415
+ { "type": "float", "name": "centerfreq", "value": 2440000000 }
416
+ ]
417
+ }
418
+ ]
419
+ }
420
+ }
421
+ ```
422
+
423
+ Notes verified against a live V6 ECO:
424
+
425
+ - **Field names are model-specific.** The V6B example writes `centerfreq`;
426
+ a V6 ECO exposes `centerfreq0` and `centerfreq1` for its two channels.
427
+ Read `GET /remoteconfig` and use the names that device reports.
428
+ - **A frequency change needs only the frequency field.** This differs from
429
+ `/control`, where a capture request is ignored unless `frequencyCenter`
430
+ and `frequencySpan` are both present.
431
+ - **The `receiverName` key is case-tolerant.** `receivername` was accepted
432
+ identically.
433
+ - Enum-valued settings are written as their label string, for example
434
+ `decimation` as `"1 / 128"`, not as an index.
435
+ - Other keys the example writes: `run` (bool), `preamp` (`"Auto"`),
436
+ `reflevel` (float), and `filerecord` (bool) plus a filename template on
437
+ a FileWriter block, which is how it starts and stops recording.
438
+
391
439
  **Simplified PUT form**: in addition to the full `{request, config}` shape, the server accepts a shorter per-block write (used by the SDR++ and SDRangel Aaronia plugins), exposed via `HttpEndpointsClient::simple_remote_config`:
392
440
  ```json
393
441
  {
@@ -542,6 +590,44 @@ Data captured using antennas with location or directional information.
542
590
  | `azimuth` | Azimuth of directional antenna |
543
591
  | `declination` | Declination of directional antenna |
544
592
 
593
+ ## What the native SDK samples tell us about this API
594
+
595
+ Aaronia's [C++ SDK samples](https://github.com/Aaronia-Open-source/RTSA-API-Samples)
596
+ drive the same hardware through a different transport, so some of what
597
+ they establish applies here and some does not.
598
+
599
+ ### GPS needs enabling, over HTTP too
600
+
601
+ The `device/gpsmode` and `device/sclksource` keys the `GPSTime` sample
602
+ writes are the same keys this config tree exposes, so the prerequisite
603
+ is identical. A device ships with `gpsmode` set to `Disabled` and takes
604
+ its stream clock from whatever `sclksource` names, which on the system
605
+ tested here was `10MHz` rather than GPS. Timestamps are not
606
+ GPS-disciplined until both change, and nothing reports an error
607
+ meanwhile.
608
+
609
+ Observed on a V6 ECO: `gpsmode` offers `Disabled`, `Location`, `Time`
610
+ and `Location and Time`; `sclksource` offers `Consumer`, `Oscillator`,
611
+ `GPS`, `PPS`, `10MHz` and three `... Provider` variants.
612
+
613
+ ### The rate ladder carries over
614
+
615
+ `decimation0` takes the same labels as the SDK's `main/decimation`,
616
+ `"Full"` through `"1 / 512"`, verified by writing one over HTTP. Both
617
+ transports drive the same divider.
618
+
619
+ ### Two things that do not carry over
620
+
621
+ - **Receiver-channel selection.** There is no `device/receiverchannel`
622
+ here. The tree exposes per-channel settings instead — `centerfreq0`
623
+ and `centerfreq1`, `decimation0` and `decimation1`, `rfchsource0` and
624
+ `rfchsource1` — and which channels reach the stream is a property of
625
+ the RTSA mission graph. The SDK's `Rx12` versus `Rx1+Rx2` distinction
626
+ has no equivalent.
627
+ - **Stream indices.** The SDK separates IQ from spectra by packet stream
628
+ index. HTTP selects data with the `input=` parameter and reports the
629
+ kind in each packet's `payload` field.
630
+
545
631
  ## Authorization & Licensing
546
632
 
547
633
  ### HTTP Streaming vs Remote Configuration
@@ -552,10 +638,23 @@ Data captured using antennas with location or directional information.
552
638
  - `/info` - Device information
553
639
  - `/healthstatus` - Device health monitoring
554
640
 
555
- **Remote Configuration** (Requires separate license):
556
- - `/remoteconfig` - Device parameter configuration
557
- - **License Required**: "Remote Config" license from Aaronia
558
- - **Alternative**: Use Native SDK for configuration without HTTP licensing restrictions
641
+ **Remote Configuration** (`/remoteconfig`):
642
+ - Device parameter configuration.
643
+ - Aaronia sells a "Remote Config" license, and this document previously
644
+ stated that `/remoteconfig` writes fail without it. **That is not what
645
+ a live system does.** On RTSA-Suite PRO driving a SPECTRAN V6 ECO whose
646
+ license list contains no Remote Config entry, `PUT /remoteconfig`
647
+ retuned the device repeatedly, in both the config-tree and simplified
648
+ forms, returning HTTP 200 with the change applied.
649
+ - What that license actually gates is therefore unconfirmed. It may
650
+ cover a different feature, or a different edition or version, or
651
+ parameters other than the ones tested. Treat any claim that
652
+ `/remoteconfig` writes require it as unverified.
653
+ - The system tested did hold "Block: HTTP Server" and "Block: HTTP
654
+ Client" licenses, so the HTTP surface itself is licensed separately
655
+ and may be what actually matters.
656
+ - This crate does not depend on the answer: it tunes through `/control`,
657
+ which needs no license.
559
658
 
560
659
  **License Detection Methods**:
561
660
 
@@ -564,7 +663,7 @@ Data captured using antennas with location or directional information.
564
663
  The client therefore exposes two methods:
565
664
 
566
665
  - `detect_remote_config_license()` — **read-only**. Never touches device state. Classifies 401/403 responses; on read success it returns `Unknown` (write capability unproven).
567
- - `probe_remote_config_write_license()` — **active probe**. Performs a read-modify-restore cycle on `reflevel` (+1 dB, restored best-effort) to positively verify write capability. Use only when you genuinely need proof of `/remoteconfig` write access. Frequency hopping does **not** need it — retuning goes through the license-free `/control` endpoint (see the capture-control note above; the silent-ignore behavior once attributed to licensing was traced to partial capture payloads).
666
+ - `probe_remote_config_write_license()` — **active probe**. Performs a read-modify-restore cycle on `reflevel` (+1 dB, restored best-effort) to positively verify write capability. Use only when you genuinely need proof of `/remoteconfig` write access. `AaroniaSource::probe_remote_config_license()` delegates to it for HTTP sources, and reports `Active` for the file and native-SDK backends, which do not need the licence. Frequency hopping does **not** need it — retuning goes through the license-free `/control` endpoint (see the capture-control note above; the silent-ignore behavior once attributed to licensing was traced to partial capture payloads).
568
667
 
569
668
  **Practical Detection (Active Probe)**:
570
669
  ```rust
@@ -707,6 +806,7 @@ consult:
707
806
 
708
807
  - **RTSA-Suite PRO HTTP streaming** (Aaronia V6 forum) — [v6-forum.aaronia.de/forum/topic/rtsa-suite-pro-http-streaming](https://v6-forum.aaronia.de/forum/topic/rtsa-suite-pro-http-streaming/)
709
808
  - Aaronia RTSA-Suite PRO product documentation — <https://rtsa-manual.aaronia.com/>
809
+ - **Aaronia's own automation example** — [Aaronia-Open-source/python_RTSA_HTTP_API_Sequence_Example](https://github.com/Aaronia-Open-source/python_RTSA_HTTP_API_Sequence_Example). A Python script that runs a measurement sequence and records to disk, driving the device entirely through `PUT /remoteconfig`. It is the source of the config-tree write example below.
710
810
 
711
811
  The content here is derived from the above plus empirical analysis of live
712
812
  RTSA-Suite PRO streams. "Aaronia", "RTSA", and "Spectran" are the property of
@@ -60,7 +60,7 @@ use sdr_aaronia_rs::{AaroniaConfig, AaroniaSource};
60
60
  async fn main() -> anyhow::Result<()> {
61
61
  let config = AaroniaConfig::from_http("http://localhost:54664")
62
62
  .center_frequency(2.44e9)
63
- .span_frequency(12.288e6) // sample rate, not RF bandwidth
63
+ .span_frequency(15.36e6) // sample rate (Fs), not RF bandwidth
64
64
  .reference_level(-20.0);
65
65
 
66
66
  let mut source = AaroniaSource::new(config).await?;
@@ -77,7 +77,7 @@ async fn main() -> anyhow::Result<()> {
77
77
 
78
78
  ```bash
79
79
  # args: <center-hz> <sample-rate-hz> <url>
80
- cargo run --example http_iq_quickstart --features http -- 2440e6 12.288e6 http://localhost:54664
80
+ cargo run --example http_iq_quickstart --features http -- 2440e6 15.36e6 http://localhost:54664
81
81
  ```
82
82
 
83
83
  ### Python
@@ -92,7 +92,7 @@ import aaronia
92
92
  cfg = aaronia.AaroniaConfig()
93
93
  cfg.http_base_url = "http://localhost:54664"
94
94
  cfg.center_freq = 2.44e9
95
- cfg.sample_rate = 12.288e6
95
+ cfg.sample_rate = 15.36e6
96
96
 
97
97
  src = aaronia.AaroniaSource()
98
98
  src.start_streaming(cfg)
@@ -124,22 +124,41 @@ Usually the missing connection from step 4: the device block's output is
124
124
  not wired to the HTTP Server block. `curl /sample` shows whether data is
125
125
  present.
126
126
 
127
- **Span and sample rate.**
127
+ **Span, sample rate and the "1 / 4" in the GUI.**
128
128
  `span_frequency` is the IQ sample rate (Fs). The name comes from the
129
- Aaronia API. It is not the usable RF bandwidth, which is smaller because
130
- of the anti-alias filter (roughly 49 MHz usable within a 61.44 MHz Fs
131
- capture). The device derives its decimation steps from a 61.44 MHz
132
- clock. Over HTTP the RTSA-Suite accepts rates across its supported
133
- range, and the crate rejects a rate that would violate the IQ-mode
134
- constraint before streaming starts.
129
+ Aaronia API. Three numbers describe the same capture and they are all
130
+ different:
131
+
132
+ | Where you see it | Example | Meaning |
133
+ | --- | --- | --- |
134
+ | `span_frequency`, and `sampleFrequency` in packet metadata | 15.36 MHz | The sample rate, Fs |
135
+ | `startFrequency`..`endFrequency` in packet metadata | 12.288 MHz | Usable alias-free bandwidth, which measures 0.8 x Fs |
136
+ | The Span control in the RTSA GUI | `1 / 4` | Decimation of the 61.44 MHz clock, so Fs = 61.44 / 4 |
137
+
138
+ The device runs at 61.44 MHz divided by a power of two, ten rates from
139
+ 61.44 MHz down to 120 kHz, shown in the GUI as Full through `1 / 512`.
140
+
141
+ **Pass one of those rates and you get it exactly.** Anything else is
142
+ silently adjusted, and not to the nearest rate: the server reads the
143
+ requested span as the *usable bandwidth* you want and picks the rate
144
+ whose usable span is closest. Asking for 2.5 MHz gives Fs = 3.84 MHz,
145
+ whose usable span is 3.07 MHz, rather than the numerically closer
146
+ 1.92 MHz. Verified across nine requests on a V6 ECO.
147
+
148
+ `AaroniaSource::get_source_info()` reports the rate the server is
149
+ actually sending once packets are flowing, so read it back rather than
150
+ assuming.
151
+
152
+ The crate rejects a rate that would violate the IQ-mode constraint
153
+ before streaming starts, but it does not second-guess the ladder.
135
154
 
136
155
  **Retuning has no effect.**
137
156
  The `/control` endpoint applies a frequency change only when
138
157
  `frequencyCenter` and `frequencySpan` are both present. A request
139
158
  carrying one of them returns `{"success":true}` and is ignored. This
140
159
  crate always sends the complete tuple, so use `set_center_frequency`
141
- instead of issuing PUTs directly. No Aaronia licence is involved; the
142
- licence gates `/remoteconfig`, which the crate does not use for tuning.
160
+ instead of issuing PUTs directly. No Aaronia licence is involved: the
161
+ crate tunes through `/control`, which needs none.
143
162
 
144
163
  **Network saturation at high sample rates.**
145
164
  Float32 requires roughly 740 MB/s at 92 MSPS. On anything other than
@@ -165,7 +184,7 @@ use sdr_aaronia_rs::{AaroniaConfig, AaroniaSource};
165
184
  let config = AaroniaConfig::default()
166
185
  .force_native_sdk()
167
186
  .center_frequency(2.44e9)
168
- .span_frequency(12.288e6);
187
+ .span_frequency(15.36e6);
169
188
  let mut source = AaroniaSource::new(config).await?;
170
189
  # Ok(())
171
190
  # }