python-aaronia 0.7.3__tar.gz → 0.7.5__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 (119) hide show
  1. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/CHANGELOG.md +149 -0
  2. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/Cargo.lock +2 -2
  3. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/Cargo.toml +1 -1
  4. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/PKG-INFO +58 -8
  5. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/docs/APPS.md +13 -5
  6. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/docs/HTTPSPEC.md +242 -9
  7. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/docs/QUICKSTART.md +35 -5
  8. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/docs/SDKSPEC.md +14 -8
  9. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/docs/VERIFICATION.md +1 -0
  10. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/packaging/homebrew/README.md +11 -4
  11. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/packaging/homebrew/soapy-aaronia.rb +1 -1
  12. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/python-aaronia/Cargo.toml +1 -1
  13. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/python-aaronia/README.md +57 -7
  14. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/python-aaronia/aaronia.pyi +11 -0
  15. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/python-aaronia/src/lib.rs +33 -1
  16. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/python-aaronia/test_basic.py +22 -0
  17. python_aaronia-0.7.5/scripts/validate-iq-live.py +386 -0
  18. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/README.md +23 -1
  19. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/Registration.cpp +15 -1
  20. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/src/http_endpoints.rs +15 -1
  21. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/src/seify_impl.rs +6 -1
  22. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/src/unified_source.rs +9 -7
  23. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/src/utils.rs +35 -7
  24. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/.cargo/config.toml +0 -0
  25. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/.gitattributes +0 -0
  26. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/.gitignore +0 -0
  27. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/CONTRIBUTING.md +0 -0
  28. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/DESIGN.md +0 -0
  29. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/LICENSE +0 -0
  30. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/PLUGINS.md +0 -0
  31. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/README.md +0 -0
  32. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/benches/decompress_block.rs +0 -0
  33. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/benches/deinterleave_dual_iq.rs +0 -0
  34. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/benches/parse_int16_packet.rs +0 -0
  35. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/benches/rtsa_open_and_read.rs +0 -0
  36. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/deny.toml +0 -0
  37. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/docs/FILESPEC.md +0 -0
  38. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/docs/USAGE.md +0 -0
  39. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/examples/channel_hopping.rs +0 -0
  40. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/examples/device_control.rs +0 -0
  41. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/examples/dump_metadata.rs +0 -0
  42. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/examples/http_iq_quickstart.rs +0 -0
  43. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/examples/native_sdk_basic.rs +0 -0
  44. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/examples/native_sdk_transmit.rs +0 -0
  45. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/examples/noaa_scanner.rs +0 -0
  46. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/examples/python_arrow_example.py +0 -0
  47. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/examples/read_rtsa_file.rs +0 -0
  48. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/examples/soapy_python_example.py +0 -0
  49. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/include/aaronia.h +0 -0
  50. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/pyproject.toml +0 -0
  51. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/scripts/ci-local.sh +0 -0
  52. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/AaroniaSoapyDevice.cpp +0 -0
  53. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/AaroniaSoapyDevice.hpp +0 -0
  54. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/CMakeLists.txt +0 -0
  55. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeCache.txt +0 -0
  56. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/4.4.2/CMakeCXXCompiler.cmake +0 -0
  57. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/4.4.2/CMakeDetermineCompilerABI_CXX.bin +0 -0
  58. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/4.4.2/CMakeSystem.cmake +0 -0
  59. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/4.4.2/CompilerIdCXX/CMakeCXXCompilerId.cpp +0 -0
  60. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/4.4.2/CompilerIdCXX/a.out +0 -0
  61. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/4.4.2/CompilerIdCXX/apple-sdk.cpp +0 -0
  62. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/CMakeConfigureLog.yaml +0 -0
  63. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/CMakeDirectoryInformation.cmake +0 -0
  64. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/CMakeRuleHashes.txt +0 -0
  65. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/InstallScripts.json +0 -0
  66. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/Makefile.cmake +0 -0
  67. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/Makefile2 +0 -0
  68. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/TargetDirectories.txt +0 -0
  69. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/DependInfo.cmake +0 -0
  70. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/build.make +0 -0
  71. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/cmake_clean.cmake +0 -0
  72. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/compiler_depend.make +0 -0
  73. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/compiler_depend.ts +0 -0
  74. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/depend.make +0 -0
  75. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/flags.make +0 -0
  76. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/link.txt +0 -0
  77. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/progress.make +0 -0
  78. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/cmake.check_cache +0 -0
  79. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/progress.marks +0 -0
  80. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/DependInfo.cmake +0 -0
  81. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/build.make +0 -0
  82. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/cmake_clean.cmake +0 -0
  83. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/compiler_depend.make +0 -0
  84. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/compiler_depend.ts +0 -0
  85. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/progress.make +0 -0
  86. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/build-test/Makefile +0 -0
  87. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/build-test/cmake_install.cmake +0 -0
  88. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/packaging/install.ps1 +0 -0
  89. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/packaging/install.sh +0 -0
  90. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/soapy-aaronia/print.cmake +0 -0
  91. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/src/c_api.rs +0 -0
  92. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/src/decompression.rs +0 -0
  93. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/src/detection.rs +0 -0
  94. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/src/error.rs +0 -0
  95. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/src/file_source.rs +0 -0
  96. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/src/http_sink.rs +0 -0
  97. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/src/http_source.rs +0 -0
  98. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/src/http_streaming.rs +0 -0
  99. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/src/lib.rs +0 -0
  100. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/src/native_sdk.rs +0 -0
  101. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/src/sdk_sink.rs +0 -0
  102. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/src/sdk_source.rs +0 -0
  103. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/src/sdr_source_impl.rs +0 -0
  104. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/src/unified_sink.rs +0 -0
  105. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/test.cmake +0 -0
  106. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/tests/c_api_test.rs +0 -0
  107. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/tests/http_mock_test.rs +0 -0
  108. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/tests/http_resilience_test.rs +0 -0
  109. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/tests/http_sink_test.rs +0 -0
  110. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/tests/integration_test.rs +0 -0
  111. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/tests/live_smoke.rs +0 -0
  112. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/tests/native_sdk_load.rs +0 -0
  113. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/tests/properties.proptest-regressions +0 -0
  114. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/tests/properties.rs +0 -0
  115. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/tests/rtsa_negative_test.rs +0 -0
  116. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/tests/sdr_source_impl_test.rs +0 -0
  117. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/tests/spec_coverage.rs +0 -0
  118. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/tests/test_cw_mag.rs +0 -0
  119. {python_aaronia-0.7.3 → python_aaronia-0.7.5}/tests/test_cw_meta.rs +0 -0
@@ -4,6 +4,155 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [v0.7.5] - 2026-08-13
8
+
9
+ ### Added
10
+ - **`scale` on the Python config and `aaronia.open()`**, the integer
11
+ encode multiplier for the `I16` wire format. Rust, the C API and the
12
+ SoapySDR plugin all had it; Python did not, so a Python program
13
+ choosing `I16` for bandwidth had no way out of the trap below.
14
+ - **`scripts/validate-iq-live.py`**, an end-to-end check that the
15
+ samples an application receives are the ones the device sent, rather
16
+ than that bytes arrived. Every wire format must decode to the same
17
+ spectrum, the Python and SoapySDR paths must agree, and a known
18
+ transmitter must land where it should. Run against a live server it
19
+ places a NOAA weather-radio carrier within 312 Hz of 162.400 MHz and
20
+ on the correct side of zero — the one check that catches transposed
21
+ I and Q, which nothing comparing the radio against itself can see.
22
+
23
+ ### Fixed
24
+ - **`format="I16"` silently discards weak signals at the default
25
+ scale.** The server sends `round(value * scale)`, so the step is
26
+ `1 / scale`, and the default of 16384 gives 6.1e-5 — coarser than a
27
+ quiet band's noise floor. Measured against a live server, **68% of
28
+ int16 samples came back exactly zero** where float32 had none. At
29
+ `scale=1e6` the zero fraction was 0.0% and the amplitude matched
30
+ float32. Documented, with the measurements, in the Python README.
31
+
32
+ ### Changed
33
+ - **The Homebrew formula is no longer a release asset.** It is uploaded
34
+ as the `homebrew-formula` workflow artifact instead. Homebrew 4 and
35
+ later cannot install from a formula URL, so on the release page it
36
+ was a file no user could act on, listed among ones they can. Its only
37
+ reader is whoever updates the tap, and the checksums are still
38
+ rendered against the published archives so none are computed by hand.
39
+
40
+ ### Documentation
41
+ - **Wire format is a throughput decision, and the default is the
42
+ expensive one.** At 15.36 MS/s over a LAN, float32 needs 123 MB/s:
43
+ measured, it delivered 6.5 MS/s with 290 drops, while float16 and
44
+ int16 both delivered 15.1 MS/s. The Python README now carries the
45
+ numbers rather than a general warning.
46
+ - **Measured what the 80% usable-bandwidth figure actually is, and
47
+ explained it in one sentence.** Sample rate and RF bandwidth were
48
+ described as related by a ratio without saying what the ratio was or
49
+ where it came from. RTSA declares exactly 0.8 x Fs as the packet's
50
+ frequency range at every rate — a fixed rule, checked at 61.44,
51
+ 15.36, 7.68 and 3.84 MHz — and every sample still arrives, so an FFT
52
+ spans the whole rate while only that 80% is flat and calibrated.
53
+ Sweeping the receiver's own noise floor on a V6 ECO confirms it: the
54
+ response is flat within 0.5 dB across 0.80 of the rate at 15.36 MHz
55
+ sampling and 0.89 at 7.68 MHz, and at full span the analog filter is
56
+ about 1 dB down by the declared edge — which is where Aaronia's
57
+ 44 MHz data-sheet figure comes from, against the 49.152 MHz the
58
+ device itself declares. The guidance readers need is one line: to see
59
+ N Hz of spectrum, sample at N / 0.8.
60
+ - **The READMEs stated the V6 ECO's sample-rate ladder as if it were
61
+ every device's.** "The device runs a fixed ladder of rates: 61.44 MHz
62
+ halved down to 120 kHz" is measured and true for an ECO, but a full
63
+ V6 selects its receiver clock and starts higher — by how much is the
64
+ open question the specs already record, and the user-facing pages did
65
+ not carry it. The Python, SoapySDR, quickstart and applications docs
66
+ now say whose ladder it is and point at the note, and
67
+ `iq_sample_rates` says so in its own documentation.
68
+ - The SoapySDR README gained a Sample rates section: why
69
+ `listSampleRates` reports the real ladder, that `setSampleRate` snaps
70
+ and logs, and that `getSampleRate` while streaming is the number to
71
+ trust on hardware the advertised ladder does not describe.
72
+ - SDKSPEC still gave the eco family's clock as 61.44 MHz in a second
73
+ place, contradicting the correction made elsewhere in the same
74
+ document. It is 92.16 MHz; 61.44 MHz is the top IQ rate.
75
+ - The same sweep caught the claim in `unified_source`'s own
76
+ documentation, in the `/control` span note in HTTPSPEC, and in the
77
+ 0.8 usable-bandwidth ratio, all of which read as universal and are
78
+ measurements from one device. `seify_impl`'s sample-rate range is
79
+ capped at 61.44 MHz for the same reason and now says so: seify has no
80
+ device handle at that point to ask for something better.
81
+
82
+ ## [v0.7.4] - 2026-08-12
83
+
84
+ ### Fixed
85
+ - **The SoapySDR plugin ignored an unrecognised `format=` silently.**
86
+ A device string carrying `format=int16` — the wire name rather than
87
+ the plugin's `I16` — streamed the default format while claiming
88
+ otherwise. It now warns and continues. The server behaves the same
89
+ way and worse: an unrecognised `format=` on `/stream` serves the
90
+ RTSA file format with HTTP 200 rather than an error, so a typo
91
+ changes the wire format entirely. `raw16`, which Aaronia's own Qt
92
+ reference client sends, is a working alias for `int16`.
93
+
94
+ ### Documentation
95
+ - Checked Aaronia's V6 remote control notes (rev 4, May 2026) against
96
+ the hardware. `/remoteconfig` enum fields take an index as well as a
97
+ label; one `simpleconfig` PUT can carry several groups, and groups
98
+ other than `main` work; and a PUT naming a block that is not in the
99
+ mission returns 200 and changes nothing, which
100
+ `simple_remote_config` now warns about since it reports `Ok(())` for
101
+ a write that did not happen. In the config-tree form the receiver
102
+ name is ignored altogether — the write is routed by `config.name`.
103
+ - Documented loading a mission over `/control`, and that every
104
+ `/control` payload needs its `type` or the server answers `400`.
105
+ Loading a mission is deliberately not exposed by the crate: swapping
106
+ the mission under a running capture should be a caller's decision,
107
+ not a side effect.
108
+ - Noted that RTSA-Suite has no status endpoint. Aaronia's own liveness
109
+ check reads the `404` from `/api/status` as proof the server is up.
110
+ - **What "Full" means on a full V6 is unresolved.** A V6 ECO follows the
111
+ SDK's `spanfreq <= receiverclock / 1.5`, measured. Aaronia's Remote
112
+ Config screenshots show a full V6 at a 92 MHz clock delivering
113
+ 92.16 MHz of IQ samples per second at span "Full" — the clock itself.
114
+ `iq_sample_rates_for_clock` may therefore understate the top of the
115
+ ladder by 1.5x for a full V6 at a non-default clock; it says so now.
116
+ Settling it needs a full V6.
117
+ - HTTPSPEC contradicted itself on the Remote Config licence, asserting
118
+ in one section that writes need it and in another that a live
119
+ unlicensed system accepts them. The second is what the hardware does,
120
+ re-confirmed for centre frequency, decimation, reference level and
121
+ the preamplifier.
122
+ - SDKSPEC still gave the V6 ECO's receiver clock as 61.44 MHz, which
123
+ 0.6.2 corrected in code to 92.16 MHz. 61.44 MHz is the ECO's top IQ
124
+ rate, that clock over 1.5; the document had the two confused.
125
+ - **A marker stream is not a categories packet**, which the previous
126
+ draft of this entry got wrong. Aaronia's example declares
127
+ `payload: "spectra"`, and spectra samples are a 2D array, so its
128
+ nesting is correct for what it says it is. Its three frequency fields
129
+ are all zero, so the category names and ranges are the only
130
+ description of what the numbers mean.
131
+ - Aaronia's endpoint specification (rev 11) settles several things this
132
+ document had only inferred, and their support answers go further.
133
+ `/control` takes PUT only, and a command reaches every block that
134
+ understands it unless `receiverUUID` or `receiverName` scopes it —
135
+ the specification says such commands cannot be addressed to a block,
136
+ which their support corrected in 2024. The per-type settings are now
137
+ listed in full, including `deviceconnect` and `camera`, which this
138
+ crate does not model. Zones cannot be configured remotely at all.
139
+ The server starts dropping data once its outbound TCP buffer passes
140
+ 8 MB, which is the mechanism behind most unexplained gaps.
141
+ `/healthstatus` is organised as `info`, `status`, `health`,
142
+ `settings` and `components`, the last being how satellites attached
143
+ over HTTP appear in a local tree.
144
+ - **`status/iqsamples` is the native rate, not the delivered one.** It
145
+ held at 61.44 MHz while the same device delivered 15.36, then 7.68,
146
+ then 61.44 MS/s. It looks like a sample rate and is not the one your
147
+ stream is running at; read `sampleFrequency` from packet metadata.
148
+ Documented the other fields a V6 ECO reports alongside it.
149
+ - **One HTTP Server and one HTTP Client connection are free**;
150
+ additional instances and connections are licensed separately, as are
151
+ Stream Merger and Stream Splitter. Running this crate and a second
152
+ client against one server at the same time is a second connection —
153
+ the licence limit most likely to be met in practice, and unrelated to
154
+ Remote Config.
155
+
7
156
  ## [v0.7.3] - 2026-08-12
8
157
 
9
158
  ### Fixed
@@ -3027,7 +3027,7 @@ dependencies = [
3027
3027
 
3028
3028
  [[package]]
3029
3029
  name = "python-aaronia"
3030
- version = "0.7.3"
3030
+ version = "0.7.5"
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.7.3"
3559
+ version = "0.7.5"
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.7.3"
5
+ version = "0.7.5"
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.7.3
3
+ Version: 0.7.5
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+)
@@ -88,17 +88,66 @@ above depends on.
88
88
 
89
89
  ## Sample rates
90
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.
91
+ The device runs a ladder of rates rather than a continuous range: each
92
+ rung is half the one above it. Ask for anything else and it quietly
93
+ uses the nearest rung, leaving your program computing against a rate
94
+ that is not in use.
94
95
 
95
96
  ```python
96
97
  aaronia.sample_rates() # every rate, highest first
97
98
  aaronia.sample_rate_for_bandwidth(8e6) # 15.36e6: the lowest rate covering 8 MHz
98
99
  ```
99
100
 
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.
101
+ **Sample rate is not RF bandwidth.** You get every sample, so an FFT of
102
+ them spans the full rate but only the middle 80% is flat and
103
+ calibrated. That is not an approximation: RTSA reports exactly 0.8 x Fs
104
+ as the packet's frequency range at every rate. Outside it, data still
105
+ arrives, attenuated and uncalibrated.
106
+
107
+ So **to see N Hz of spectrum, sample at N / 0.8**, which is what
108
+ `sample_rate_for_bandwidth()` computes. Aaronia's data sheet quotes a
109
+ more conservative figure still — 44 MHz for the ECO against the
110
+ 49.152 MHz it declares at full span — because the analog filter is
111
+ already about 1 dB down at that edge. The
112
+ [quickstart](https://github.com/isaacbentley/sdr-aaronia-rs/blob/main/docs/QUICKSTART.md#4-troubleshooting)
113
+ has the measurements.
114
+
115
+ `sample_rates()` returns the ladder for a SPECTRAN V6 ECO — 61.44 MHz
116
+ down to 120 kHz — which is measured, rung by rung. A full V6 has a
117
+ selectable receiver clock and can go higher, and exactly how much
118
+ higher is not settled; see
119
+ [the note in HTTPSPEC](https://github.com/isaacbentley/sdr-aaronia-rs/blob/main/docs/HTTPSPEC.md#unresolved-the-full-v6s-top-rate).
120
+ On that hardware, take the rate the device reports over the computed
121
+ ladder: it arrives in the stream metadata, and `diagnose()` prints it.
122
+
123
+ ## Choosing a wire format
124
+
125
+ `format` decides what crosses the network, and it matters more than it
126
+ looks. Measured against a live server at 15.36 MS/s over a LAN:
127
+
128
+ | format | bytes/sample | delivered | drops |
129
+ | --- | --- | --- | --- |
130
+ | `F32` (default) | 8 | 6.5 MS/s | 290 |
131
+ | `F16` | 4 | 15.1 MS/s | 9 |
132
+ | `I16` | 4 | 15.1 MS/s | 12 |
133
+
134
+ `F32` needs 123 MB/s at that rate and the link could not carry it, so
135
+ most of the capture was dropped. Either half-width format fits.
136
+
137
+ `I16` has one trap: the server sends `round(value * scale)`, so the
138
+ quantisation step is `1 / scale`, and the default of 16384 gives a step
139
+ of 6.1e-5. A quiet band's noise floor is smaller than that — on the
140
+ same server, **68% of `I16` samples came back exactly zero** while
141
+ `F32` had none. Pass `scale=`, or lower `reference_level` for more
142
+ gain:
143
+
144
+ ```python
145
+ aaronia.open(url, freq=2.44e9, rate=15.36e6, format="I16", scale=1e6)
146
+ ```
147
+
148
+ At `scale=1e6` the zero fraction measured 0.0% and the amplitude
149
+ matched `F32`. `F16` needs no such tuning, which makes it the simpler
150
+ choice when the link is the constraint.
102
151
 
103
152
  ## Configuration (`AaroniaConfig`)
104
153
 
@@ -113,6 +162,7 @@ Every field is readable and writable.
113
162
  | `sample_rate` | IQ sample rate, Hz (the Aaronia "span") |
114
163
  | `reference_level` | Reference level, dBm |
115
164
  | `format` | HTTP wire format: `"F32"`, `"F16"` or `"I16"`. `I16` is the low-bandwidth network mode |
165
+ | `scale` | Integer encode multiplier for `I16` (see below). None uses the server default |
116
166
  | `receiver_channel` | `"Rx1"` (default), `"Rx2"`, or `"Rx1And2"` (native SDK, full V6) |
117
167
  | `read_timeout` | Seconds a blocking read waits before `AaroniaTimeoutError` (default `30.0`) |
118
168
  | `auto_reconnect` | Reconnect the HTTP stream after a drop (default `True`) |
@@ -169,8 +219,8 @@ silently defaulting.
169
219
 
170
220
  | Function | Purpose |
171
221
  | --- | --- |
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 |
222
+ | `open(url=None, *, freq, rate, bandwidth, ref_level, file, format, scale, read_timeout)` | Configure, connect and start streaming in one call |
223
+ | `sample_rates()` | The V6 ECO's sample rates, highest first (see [Sample rates](#sample-rates)) |
174
224
  | `sample_rate_for_bandwidth(hz)` | Lowest rate covering that much spectrum |
175
225
  | `diagnose(url)` | `(ok, message, fix)` for each setup check; what `aaronia-doctor` prints |
176
226
 
@@ -50,9 +50,12 @@ driver=aaronia,url=http://localhost:54664
50
50
  ```
51
51
 
52
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.
53
+ device's real ladder rather than round numbers it cannot produce: on a
54
+ V6 ECO that is 61.44 MHz halved down to 120 kHz, which the RTSA GUI
55
+ labels Full through 1/512. Nothing in between exists, so a request
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.
56
59
 
57
60
  The single gain element is `REF`, the Aaronia reference level in dBm. It
58
61
  is not an amplifier gain: raising it reduces sensitivity. Start near
@@ -101,8 +104,13 @@ directly and skip the SoapySDR layer.
101
104
 
102
105
  ## General notes
103
106
 
104
- - Sample rate is the Aaronia "span", not the usable RF bandwidth. The
105
- alias-free span is smaller, roughly 49 MHz within a 61.44 MHz capture.
107
+ - Sample rate is the Aaronia "span", not the usable RF bandwidth. Every
108
+ sample reaches the application, so the display spans the full rate,
109
+ but only the middle 80% is flat and calibrated — RTSA reports exactly
110
+ 0.8 x Fs as the packet's frequency range. Set the rate whose 80%
111
+ covers the span you want to see: 61.44 MHz of sampling gives roughly
112
+ 49 MHz of calibrated spectrum. The edges are real data, just rolled
113
+ off.
106
114
  - `readStream` honours `timeoutUs` and returns partial reads within the
107
115
  deadline, per the SoapySDR contract.
108
116
  - Retuning mid-stream is safe and requires no Aaronia licence.
@@ -161,6 +161,43 @@ http://localhost:54664/stream?format=int16&scale=1000000
161
161
  http://localhost:54664/stream?rate_reduction=10
162
162
  ```
163
163
 
164
+ ### An unknown `format` is not an error
165
+
166
+ `format=` accepts `json`, `int16`, `float16` and `float32`. `raw16` is
167
+ an accepted alias for `int16` — Aaronia's own Qt reference client
168
+ defaults to it — and produces byte-identical framing.
169
+
170
+ Anything the server does not recognise serves **the RTSA file format**
171
+ instead: a `DSFH` header followed by `STRM`/`ANTA` chunks, with HTTP
172
+ 200 and no warning. A typo in `format=` therefore yields a completely
173
+ different wire format rather than an error, and a parser expecting
174
+ JSON-plus-binary will fail somewhere well past the point that would
175
+ have identified the cause. Verified against a live server: `format=`
176
+ with a nonsense value returned `DSFH`.
177
+
178
+ This crate builds the string from an enum, so it cannot typo, and the
179
+ SoapySDR plugin now warns on an unrecognised `format=` device argument
180
+ rather than silently falling back to the default.
181
+
182
+ ### When the server drops data
183
+
184
+ The HTTP server block starts dropping data once its outbound TCP
185
+ buffer passes **8 MB**. Nothing announces it; the loss shows up as a
186
+ gap between the timestamps of two adjacent packets, which is what
187
+ `DropDetector` watches for. A slow consumer, a slow link, or a rate
188
+ the network cannot carry all end here, so reducing the wire format
189
+ (`format=int16`) or the rate (`rate_reduction=n`) is the fix rather
190
+ than a larger client-side buffer.
191
+
192
+ ### Liveness
193
+
194
+ There is no status endpoint. Aaronia's own remote control notes probe
195
+ with `curl -v http://127.0.0.1:54664/api/status` and treat the `404 Not
196
+ Found` as the answer: a reply of any kind proves the port is open, the
197
+ mission is loaded and the HTTP Server block is running. A connection
198
+ refused or a timeout means it is not. `/info` serves the same purpose
199
+ and returns something useful, which is what this crate uses.
200
+
164
201
  ### Application Process Control
165
202
  **URL**: `/app/process`
166
203
  **Method**: PUT
@@ -250,6 +287,42 @@ packet-metadata object, so `payload`, `minPower`, `maxPower`, and
250
287
  `stepFrequency` (the sample rate) is optional; `push: true` marks the packet for immediate transmission.
251
288
 
252
289
  ### Control Endpoint
290
+
291
+ `/control` accepts **PUT only**. A GET — which is what a browser does
292
+ with the URL — is not supported.
293
+
294
+ **A command reaches every block that understands it, unless you scope
295
+ it.** Aaronia's 2022 specification says control commands "are not
296
+ addressed to a specific RTSA block and will be processed from all RTSA
297
+ blocks in the block graph", which is true as a default. Their support
298
+ staff refined it in 2024: `receiverUUID` and `receiverName` "can be
299
+ used to limit the requested setting to individual blocks", and "in some
300
+ cases it may be necessary to specify them". Read the names or UUIDs
301
+ from `/remoteconfig` first if you need to target one block.
302
+
303
+ These are one-off requests. Aaronia's own words: if they cannot be
304
+ executed, or conflict with the local configuration, the behaviour is
305
+ undefined — nothing reports the conflict.
306
+
307
+ **The settings each `type` accepts**, per Aaronia support (January
308
+ 2024), which goes well beyond the published specification:
309
+
310
+ | `type` | Settings |
311
+ | :--- | :--- |
312
+ | `capture` | `receiverUUID`, `receiverName`, `frequencyCenter`, `frequencyStart`, `frequencyBins`, `referenceLevel`, `start` — plus `frequencySpan` and `frequencyEnd`, which the specification documents and this crate uses |
313
+ | `deviceconnect` | `receiverUUID`, `receiverName`, `start` — connect or disconnect a device |
314
+ | `streaming` | `receiverUUID`, `receiverName`, `filename`, `start` |
315
+ | `recording` | `receiverUUID`, `receiverName`, `filename`, `start` |
316
+ | `mission` | `save`, `reload`, `load` (with the path in `file`) |
317
+ | `antenna` | `receiverUUID`, `receiverName`, `latitude`+`longitude` (both or neither), `azimuth`+`declination`, `rotate` |
318
+ | `camera` | `receiverUUID`, `receiverName`, `latitude`+`longitude`, `azimuth`+`declination`, `aperture`, `channel`, `altitude`, `start` |
319
+
320
+ `start`, `rotate`, `save`, `load` and `reload` are booleans. A
321
+ `filename` must be an absolute path **on the machine running
322
+ RTSA-Suite**, with forward slashes.
323
+
324
+ Zones cannot be configured this way; Aaronia state that remote
325
+ configuration of zones is not supported.
253
326
  **URL**: `/control`
254
327
  **Method**: PUT
255
328
  **Description**: Send commands to the RTSA suite
@@ -284,7 +357,17 @@ packet-metadata object, so `payload`, `minPower`, `maxPower`, and
284
357
  ```
285
358
 
286
359
  > **`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
360
+ > The device halves its top rate down a ladder 61.44 MHz on the V6
361
+ > ECO this was measured against. The usable RF bandwidth it declares in
362
+ > `startFrequency..endFrequency` is exactly 0.8 x Fs at every rate,
363
+ > checked at 61.44, 15.36, 7.68 and 3.84 MHz; every sample still
364
+ > arrives, so an FFT spans the whole rate, but only that 80% is flat
365
+ > and calibrated. Sweeping the receiver's own noise floor puts the real
366
+ > filter at or beyond the declared edge at decimated rates (flat within
367
+ > 0.5 dB across 0.80 x Fs at 15.36 MHz, 0.89 at 7.68 MHz) and just
368
+ > inside it at full span (about 1 dB down at the declared edge, 3 dB at
369
+ > 0.84 x Fs), which is where Aaronia's 44 MHz data-sheet figure for the
370
+ > ECO comes from. Given a span
288
371
  > it cannot produce, it selects the rate whose alias-free bandwidth
289
372
  > (0.8 x Fs) is nearest the request: 2.5 MHz yields Fs = 3.84 MHz,
290
373
  > 1.3 MHz yields 1.92 MHz, and 10 MHz yields 15.36 MHz. A value that is
@@ -320,7 +403,7 @@ packet-metadata object, so `payload`, `minPower`, `maxPower`, and
320
403
  }
321
404
  ```
322
405
 
323
- #### Save/Reload Mission
406
+ #### Save/Reload/Load Mission
324
407
  ```json
325
408
  {
326
409
  "save": true,
@@ -328,6 +411,28 @@ packet-metadata object, so `payload`, `minPower`, `maxPower`, and
328
411
  }
329
412
  ```
330
413
 
414
+ A mission can also be swapped outright, which is how a headless station
415
+ changes what it is running. `load` takes an absolute path to an `.rmix`
416
+ on the machine running RTSA-Suite, not on the client:
417
+ ```json
418
+ {
419
+ "type": "mission",
420
+ "load": true,
421
+ "file": "C:/Aaronia/Recordings/HTTP streaming/http_test.rmix"
422
+ }
423
+ ```
424
+ A mission carrying an HTTP Server block must already be running, or
425
+ there is nothing listening to accept the request. Aaronia's remote
426
+ control notes (rev 4, May 2026) give this as a `curl` invocation from
427
+ an elevated prompt. This crate does not expose it: swapping the mission
428
+ out from under a running capture is the caller's decision to make
429
+ deliberately, not a side effect of a library call.
430
+
431
+ **Every `/control` payload needs its `type`.** A body without one is
432
+ rejected with `400` and the text `Type argument missing`, so a
433
+ hand-built payload that omits it fails loudly rather than silently —
434
+ unlike a partial capture request, which is accepted and ignored.
435
+
331
436
  ### Configuration Endpoints
332
437
 
333
438
  #### Server Info (`/info`)
@@ -430,8 +535,18 @@ Notes verified against a live V6 ECO:
430
535
  and `frequencySpan` are both present.
431
536
  - **The `receiverName` key is case-tolerant.** `receivername` was accepted
432
537
  identically.
433
- - Enum-valued settings are written as their label string, for example
434
- `decimation` as `"1 / 128"`, not as an index.
538
+ - **Enum-valued settings take either the label string or the index.**
539
+ Aaronia's example writes `decimation` as `"1 / 128"`; writing
540
+ `decimation0` as the integer `3` was accepted and applied identically
541
+ (verified against `/sample`: index 3 gave 7.68 MHz, index 2 gave
542
+ 15.36 MHz). Indices are positions in the `values` list that
543
+ `GET /remoteconfig` reports for that item.
544
+ - **Groups other than `main` work**, and several can go in one PUT:
545
+ `{"main": {...}, "calibration": {"preamp": 1}}` applied both.
546
+ - **An unknown block name is a silent no-op.** A `simpleconfig` PUT
547
+ naming a block that is not in the mission returns HTTP 200 and changes
548
+ nothing. There is no error to catch, so read the value back if it
549
+ matters.
435
550
  - Other keys the example writes: `run` (bool), `preamp` (`"Auto"`),
436
551
  `reflevel` (float), and `filerecord` (bool) plus a filename template on
437
552
  a FileWriter block, which is how it starts and stops recording.
@@ -445,7 +560,15 @@ Notes verified against a live V6 ECO:
445
560
  }
446
561
  }
447
562
  ```
448
- `receiverName` must match a block in the running mission; `HttpEndpointsClient::find_iq_demodulator_block_name` auto-discovers it.
563
+ `receiverName` must match a block in the running mission;
564
+ `HttpEndpointsClient::find_iq_demodulator_block_name` auto-discovers it.
565
+ A name that matches nothing still returns 200 (see above).
566
+
567
+ In the full `{request, config}` form the receiver name is ignored
568
+ altogether: the write is routed by `config.name`. Sending
569
+ `receivername`, `receiverName` or no name at all applied the same
570
+ change. Nothing depends on getting that key right, and nothing warns
571
+ when it is wrong.
449
572
 
450
573
  **Configuration Item Fields**:
451
574
  | Field | Description |
@@ -469,6 +592,28 @@ configuration tree (`HealthStatus` is an alias for `ConfigItem`) rather
469
592
  than the typed shape below, which describes the upstream block-health
470
593
  fields.
471
594
 
595
+ **Subgroups** per health-aware block, per Aaronia's specification:
596
+ `info`, `status`, `health`, `settings`, and `components` — the last a
597
+ recursive list of sub-blocks when satellites are attached over HTTP,
598
+ which is how a remote station's devices show up in a local tree.
599
+
600
+ **What a V6 ECO actually reports** under `status` and `health`,
601
+ alongside the `info` fields below:
602
+
603
+ | Item | Example | Meaning |
604
+ | :--- | :--- | :--- |
605
+ | `status/iqsamples` | 61439323 | **Native** IQ rate in Hz, not the delivered one. Measured constant at 61.44 MHz while the same device delivered 15.36, then 7.68, then 61.44 MS/s — it does not follow the decimation setting |
606
+ | `status/usbbuffer` | 0.0625 | USB buffer fill, fraction |
607
+ | `status/adcrange` | 17.16 | ADC headroom in dB |
608
+ | `status/strmtimedist` | 5.96e-06 | Stream time distance in seconds |
609
+ | `health/fronttemp` | 64.7 | Frontend temperature, °C |
610
+ | `health/fpgatemp` | 52.5 | FPGA temperature, °C |
611
+ | `health/boardpower` | 8.975 | Board power draw, W |
612
+
613
+ `iqsamples` is the one to be careful with: it looks like a sample rate
614
+ and is not the one your stream is running at. Read `sampleFrequency`
615
+ from packet metadata for that.
616
+
472
617
  **Health Status Fields**:
473
618
  | Field | Description |
474
619
  | :--- | :--- |
@@ -577,7 +722,35 @@ The samples in a category ordered packet have one measurement per category.
577
722
  }
578
723
  ```
579
724
 
580
- As with histograms, category samples are a flat number array (one value per category).
725
+ As with histograms, category samples are a flat number array (one value
726
+ per category) — that is what a live server produced.
727
+
728
+ **A marker stream is not a categories packet.** Aaronia's remote
729
+ control notes show the Spectrum block's `Marker` output wired to an
730
+ HTTP Server, and the result looks like this:
731
+
732
+ ```json
733
+ {
734
+ "payload": "spectra",
735
+ "startFrequency": 0, "endFrequency": 0, "sampleFrequency": 0,
736
+ "sampleSize": 4, "sampleDepth": 1,
737
+ "categories": [
738
+ { "name": "Slow Sine(R)", "startFrequency": 2403000000, "endFrequency": 2405000000 },
739
+ { "name": "Marker", "startFrequency": 2412852325.3, "endFrequency": 2412852335.3 }
740
+ ],
741
+ "samples": [[-78.6, -57.26, -47.28, -114.25]]
742
+ }
743
+ ```
744
+
745
+ It carries a `categories` array but declares `payload: "spectra"`, and
746
+ spectra samples are a 2D array — one row per frame — so the nesting is
747
+ correct for what it says it is, not a contradiction of the flat
748
+ categories form above. `sampleSize` is the number of values in a row,
749
+ here one per category.
750
+
751
+ What is worth knowing before parsing one: all three frequency fields
752
+ are `0`, so nothing can be derived from them, and the category names
753
+ and ranges are the only description of what the numbers mean.
581
754
 
582
755
  ### Antenna Data
583
756
  Data captured using antennas with location or directional information.
@@ -614,7 +787,44 @@ and `Location and Time`; `sclksource` offers `Consumer`, `Oscillator`,
614
787
 
615
788
  `decimation0` takes the same labels as the SDK's `main/decimation`,
616
789
  `"Full"` through `"1 / 512"`, verified by writing one over HTTP. Both
617
- transports drive the same divider.
790
+ transports drive the same divider. Over HTTP the index works as well as
791
+ the label: writing `3` and reading `/sample` back gave 7.68 MHz, and `2`
792
+ gave 15.36 MHz, which is 61.44 MHz halved that many times on a V6 ECO.
793
+
794
+ #### Unresolved: the full V6's top rate
795
+
796
+ The SDK constrains `spanfreq * 1.5 <= receiverclock`, and a V6 ECO
797
+ follows it: its top IQ rate measures 61.44 MHz against the 92.16 MHz
798
+ clock the crate assumes for it. `iq_sample_rates_for_clock` takes that
799
+ ratio as the rule, so it puts a full V6 on the `245MHz` clock at
800
+ 163.84 MHz.
801
+
802
+ Aaronia's own figures do not agree. Their endpoint specification closes
803
+ with a note about achieving "the full 250M samples of IQ data", their
804
+ block documentation advertises "a real-time bandwidth of up to 245MHz
805
+ each" for the V6's two inputs, and both round to the 245.76 MHz that
806
+ the `245MHz` clock label denotes — the clock itself, not two thirds of
807
+ it.
808
+
809
+ There is a reading that reconciles them. The clock list runs past
810
+ `245MHz` to `492MHz` (491.52 MHz), and 245.76 MHz of span satisfies the
811
+ 1.5 rule against that clock with room to spare. On that reading 245 MHz
812
+ of real-time bandwidth is real but needs the fastest clock, and the
813
+ `245MHz` label tops out at 163.84 MHz exactly as the crate computes.
814
+
815
+ What does not fit either reading is the Remote Config panel Aaronia
816
+ published in 2021 and again in 2022: a full V6 with the clock set to
817
+ `92MHz` reporting roughly 92.16 MHz of IQ samples per second. That
818
+ counter is not the delivered rate — measured on an ECO, `iqsamples`
819
+ held at 61.44 MHz while the device was actually delivering 15.36 and
820
+ then 7.68 MHz, so it reports the native undecimated rate. Taken at face
821
+ value it says a full V6 at a 92 MHz clock has a native rate of
822
+ 92.16 MHz, which the 1.5 rule forbids.
823
+
824
+ No full V6 has been available to measure, so this stays open. The
825
+ practical guidance is unchanged: the ECO path is verified rung by rung,
826
+ and for anything else read the rate the device reports in its stream
827
+ metadata rather than trusting a computed ladder.
618
828
 
619
829
  ### Two things that do not carry over
620
830
 
@@ -638,6 +848,20 @@ transports drive the same divider.
638
848
  - `/info` - Device information
639
849
  - `/healthstatus` - Device health monitoring
640
850
 
851
+ **HTTP Server and Client blocks are themselves licensed, and the free
852
+ tier is one of each.** Aaronia's staff put it plainly in the HTTP
853
+ Server thread: only one HTTP Server instance is included in the free
854
+ RTSA-Suite PRO licence and additional instances must be licensed
855
+ separately, the same going for the HTTP Client, where only one
856
+ connection is free. Stream Merger and Stream Splitter, the blocks that
857
+ would otherwise let several streams share one connection, are not in
858
+ the free licence either.
859
+
860
+ This is the licence limit most likely to be met in practice: running
861
+ this crate and a second client — a SoapySDR application, say — against
862
+ one server at the same time is a second connection. It has nothing to
863
+ do with the Remote Config licence discussed below.
864
+
641
865
  **Remote Configuration** (`/remoteconfig`):
642
866
  - Device parameter configuration.
643
867
  - Aaronia sells a "Remote Config" license, and this document previously
@@ -655,10 +879,17 @@ transports drive the same divider.
655
879
  and may be what actually matters.
656
880
  - This crate does not depend on the answer: it tunes through `/control`,
657
881
  which needs no license.
882
+ - Re-confirmed 2026-08-12 against RTSA-Suite PRO and a V6 ECO on the
883
+ same unlicensed system, in both payload forms, for `centerfreq0`,
884
+ `decimation0`, `reflevel0` and `calibration/preamp`.
658
885
 
659
886
  **License Detection Methods**:
660
887
 
661
- **Important**: Read access to `/remoteconfig` is available WITHOUT license, but write operations require the license. Because reads are license-free, a read-only check cannot distinguish "licensed" from "unlicensed" — only a write test can.
888
+ **Important**: reads of `/remoteconfig` are license-free, so a read-only
889
+ check cannot prove write capability either way — only a write test can.
890
+ It cannot prove the *license* either: writes succeeded on an unlicensed
891
+ system (see above), so a successful probe means "this server accepts
892
+ writes", not "this server is licensed".
662
893
 
663
894
  The client therefore exposes two methods:
664
895
 
@@ -793,8 +1024,10 @@ fn parse_iq_int16(data: &[u8], metadata_scale: f32) -> Vec<Complex32> {
793
1024
  |---------|------|---------|
794
1025
  | 1.0 | 2025-01-11 | Initial HTTP specification from original documentation |
795
1026
  | 2.0 | 2025-01-11 | Enhanced with comprehensive streaming protocol specification and implementation guidelines |
1027
+ | 2.4 | 2026-08-12 | Added Aaronia support's full `/control` settings list (`deviceconnect`, `camera`, per-type fields, `receiverUUID`/`receiverName` scoping), which supersedes the specification's claim that commands cannot be addressed to a block; documented that an unrecognised `format=` silently serves the RTSA file format and that `raw16` aliases `int16`, both verified live |
1028
+ | 2.3 | 2026-08-12 | Folded in Aaronia's endpoint specification (rev 11) and the block forum threads: `/control` broadcasts to every block and is PUT-only, the server drops data past an 8 MB outbound buffer, `/healthstatus` subgroups and the fields a V6 ECO reports, and the one-server/one-client free-licence limit. Measured that `status/iqsamples` is the native rate, not the delivered one. Corrected the marker-stream entry: it declares `payload: "spectra"`, so its nested samples are the spectra form and not a counter-example to flat categories |
1029
+ | 2.2 | 2026-08-12 | Verified Aaronia's V6 remote control notes (rev 4) against hardware: enum writes by index, multi-group and non-`main` `simpleconfig` PUTs, the silent no-op on an unknown block name, the ignored receiver name in the config-tree form; documented mission loading and the `type` requirement on `/control`, the absence of a status endpoint, and the unresolved conflict over what "Full" means on a full V6; resolved a contradiction over what the Remote Config licence gates |
796
1030
  | 2.1 | 2026-08-06 | Live-hardware corrections folded in (two-byte separator, spectra frame counting, `scale` inversion); documented `/samples`, the `/sample` TX push, and the `simpleconfig` PUT form; corrected `limit` semantics, field types, and flat histogram/categories sample arrays; removed decorative icons |
797
-
798
1031
  ---
799
1032
 
800
1033
  ## Sources and Attribution