python-aaronia 0.8.1__tar.gz → 0.8.2__tar.gz

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