python-aaronia 0.6.1__tar.gz → 0.7.0__tar.gz

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