python-aaronia 0.7.6__tar.gz → 0.8.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 (121) hide show
  1. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/.gitignore +6 -8
  2. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/CHANGELOG.md +456 -1
  3. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/CONTRIBUTING.md +1 -1
  4. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/Cargo.lock +11 -11
  5. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/Cargo.toml +1 -1
  6. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/PKG-INFO +2 -2
  7. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/PLUGINS.md +4 -4
  8. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/README.md +29 -2
  9. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/docs/APPS.md +8 -3
  10. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/docs/FILESPEC.md +1 -1
  11. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/docs/HTTPSPEC.md +96 -5
  12. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/docs/QUICKSTART.md +6 -4
  13. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/docs/SDKSPEC.md +1 -1
  14. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/docs/USAGE.md +137 -3
  15. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/include/aaronia.h +58 -1
  16. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/packaging/homebrew/soapy-aaronia.rb +1 -1
  17. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/python-aaronia/Cargo.toml +1 -1
  18. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/python-aaronia/README.md +1 -1
  19. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/python-aaronia/aaronia.pyi +1 -1
  20. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/python-aaronia/src/lib.rs +1 -1
  21. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/soapy-aaronia/AaroniaSoapyDevice.cpp +142 -24
  22. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/soapy-aaronia/AaroniaSoapyDevice.hpp +20 -0
  23. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/soapy-aaronia/CMakeLists.txt +14 -5
  24. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/soapy-aaronia/README.md +82 -12
  25. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/soapy-aaronia/Registration.cpp +35 -7
  26. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/c_api.rs +332 -14
  27. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/decompression.rs +11 -1
  28. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/file_source.rs +96 -20
  29. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/http_endpoints.rs +997 -55
  30. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/http_sink.rs +4 -1
  31. python_aaronia-0.8.0/src/http_source.rs +3318 -0
  32. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/http_streaming.rs +660 -184
  33. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/lib.rs +12 -0
  34. python_aaronia-0.8.0/src/link_budget.rs +1524 -0
  35. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/native_sdk.rs +162 -12
  36. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/sdk_source.rs +35 -3
  37. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/sdr_source_impl.rs +9 -7
  38. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/seify_impl.rs +5 -2
  39. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/unified_sink.rs +27 -0
  40. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/unified_source.rs +128 -19
  41. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/utils.rs +167 -14
  42. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/tests/c_api_test.rs +1 -1
  43. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/tests/live_smoke.rs +84 -0
  44. python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeCache.txt +0 -439
  45. python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/4.4.2/CMakeCXXCompiler.cmake +0 -103
  46. python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/4.4.2/CMakeDetermineCompilerABI_CXX.bin +0 -0
  47. python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/4.4.2/CMakeSystem.cmake +0 -15
  48. python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/4.4.2/CompilerIdCXX/CMakeCXXCompilerId.cpp +0 -954
  49. python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/4.4.2/CompilerIdCXX/a.out +0 -0
  50. python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/4.4.2/CompilerIdCXX/apple-sdk.cpp +0 -1
  51. python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/CMakeConfigureLog.yaml +0 -1904
  52. python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/CMakeDirectoryInformation.cmake +0 -16
  53. python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/CMakeRuleHashes.txt +0 -3
  54. python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/InstallScripts.json +0 -7
  55. python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/Makefile.cmake +0 -61
  56. python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/Makefile2 +0 -157
  57. python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/TargetDirectories.txt +0 -8
  58. python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/DependInfo.cmake +0 -24
  59. python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/build.make +0 -131
  60. python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/cmake_clean.cmake +0 -13
  61. python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/compiler_depend.make +0 -2
  62. python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/compiler_depend.ts +0 -2
  63. python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/depend.make +0 -2
  64. python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/flags.make +0 -12
  65. python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/link.txt +0 -1
  66. python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/progress.make +0 -4
  67. python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/cmake.check_cache +0 -1
  68. python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/progress.marks +0 -1
  69. python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/DependInfo.cmake +0 -22
  70. python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/build.make +0 -94
  71. python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/cmake_clean.cmake +0 -9
  72. python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/compiler_depend.make +0 -2
  73. python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/compiler_depend.ts +0 -2
  74. python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/progress.make +0 -2
  75. python_aaronia-0.7.6/soapy-aaronia/build-test/Makefile +0 -271
  76. python_aaronia-0.7.6/soapy-aaronia/build-test/cmake_install.cmake +0 -78
  77. python_aaronia-0.7.6/src/http_source.rs +0 -1851
  78. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/.cargo/config.toml +0 -0
  79. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/.gitattributes +0 -0
  80. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/DESIGN.md +0 -0
  81. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/LICENSE +0 -0
  82. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/benches/decompress_block.rs +0 -0
  83. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/benches/deinterleave_dual_iq.rs +0 -0
  84. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/benches/parse_int16_packet.rs +0 -0
  85. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/benches/rtsa_open_and_read.rs +0 -0
  86. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/deny.toml +0 -0
  87. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/docs/VERIFICATION.md +0 -0
  88. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/examples/channel_hopping.rs +0 -0
  89. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/examples/device_control.rs +0 -0
  90. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/examples/dump_metadata.rs +0 -0
  91. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/examples/http_iq_quickstart.rs +0 -0
  92. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/examples/native_sdk_basic.rs +0 -0
  93. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/examples/native_sdk_transmit.rs +0 -0
  94. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/examples/noaa_scanner.rs +0 -0
  95. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/examples/python_arrow_example.py +0 -0
  96. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/examples/read_rtsa_file.rs +0 -0
  97. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/examples/soapy_python_example.py +0 -0
  98. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/packaging/homebrew/README.md +0 -0
  99. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/pyproject.toml +0 -0
  100. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/python-aaronia/test_basic.py +0 -0
  101. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/scripts/ci-local.sh +0 -0
  102. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/scripts/validate-iq-live.py +0 -0
  103. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/soapy-aaronia/packaging/install.ps1 +0 -0
  104. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/soapy-aaronia/packaging/install.sh +0 -0
  105. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/soapy-aaronia/print.cmake +0 -0
  106. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/detection.rs +0 -0
  107. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/error.rs +0 -0
  108. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/sdk_sink.rs +0 -0
  109. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/test.cmake +0 -0
  110. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/tests/http_mock_test.rs +0 -0
  111. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/tests/http_resilience_test.rs +0 -0
  112. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/tests/http_sink_test.rs +0 -0
  113. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/tests/integration_test.rs +0 -0
  114. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/tests/native_sdk_load.rs +0 -0
  115. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/tests/properties.proptest-regressions +0 -0
  116. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/tests/properties.rs +0 -0
  117. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/tests/rtsa_negative_test.rs +0 -0
  118. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/tests/sdr_source_impl_test.rs +0 -0
  119. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/tests/spec_coverage.rs +0 -0
  120. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/tests/test_cw_mag.rs +0 -0
  121. {python_aaronia-0.7.6 → python_aaronia-0.8.0}/tests/test_cw_meta.rs +0 -0
@@ -10,15 +10,13 @@ tests/sdk/
10
10
  # Local agent configuration/memory
11
11
  .agents/
12
12
 
13
- # CMake build directory for SoapySDR plugin
14
- soapy-aaronia/build/
15
- python-aaronia/__pycache__/test_basic.cpython-314-pytest-9.1.1.pyc
16
- python-aaronia/err.txt
17
- python-aaronia/check_errors.txt
18
- branch_diff.txt
13
+ # Local CMake build trees for the SoapySDR plugin. Globbed, not a single
14
+ # `build/`: a second tree under any other name (build-test, build-e2e)
15
+ # was committed once already — 33 files of CMakeCache and compiler probe
16
+ # binaries carrying absolute paths from the machine that made them.
17
+ soapy-aaronia/build*/
19
18
 
20
- # Python bindings dev environment and local plugin builds
19
+ # Python bindings dev environment and build output
21
20
  python-aaronia/venv/
22
21
  python-aaronia/**/__pycache__/
23
- soapy-aaronia/build/
24
22
  dist/
@@ -2,7 +2,462 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
- ## [Unreleased]
5
+ ## [v0.8.0] - 2026-09-07
6
+
7
+ **Breaking:** `StreamStats` gained a `device_health` field, so an
8
+ exhaustive struct literal over it no longer compiles. It is
9
+ `#[non_exhaustive]` now, along with the new `DeviceCapabilities` and
10
+ `DeviceHealthSummary`: these are reports a consumer reads rather than
11
+ builds, and the attribute makes every future field addition free. Only
12
+ the `futuresdr` feature exposes `StreamStats`; the default feature set is
13
+ unaffected.
14
+
15
+ ### Added
16
+ - **The SoapySDR probe now reports what the device says about itself.**
17
+ `SoapySDRUtil --probe` published constants compiled in for one model:
18
+ `hardware=Spectran V6` whatever was attached, a 10 Hz–6 GHz frequency
19
+ range, and a −100…+10 dB gain range. A SPECTRAN V6 ECO declares
20
+ 5.5 MHz–8 GHz and −55…+23 dBm — too low at one end for any tune to
21
+ succeed, two whole GHz short at the other, and a gain slider spanning
22
+ values the device clamps. The plugin now reads `/remoteconfig` and
23
+ `/healthstatus` once at construction and publishes the model, serial,
24
+ firmware version, both ranges with their declared steps, and the
25
+ sample-rate ladder.
26
+
27
+ The ladder is the part that needed care: `status/iqsamples` is the
28
+ device's native undecimated rate but it is a *measurement*
29
+ (61 411 246 Hz for a nominal 61 440 000), so it is snapped to the exact
30
+ `receiver_clock / 1.5` rung it names — new `utils::snap_to_ladder_top`
31
+ — before being halved once per rung `decimation0` offers. A rate
32
+ advertised to an application has to be one the device can be set to.
33
+ `getSampleRateRange` is now taken from the ends of that same ladder, so
34
+ it can no longer disagree with `listSampleRates`; its old 10 kHz floor
35
+ sat below the slowest rung the hardware has.
36
+
37
+ New API: `http_endpoints::DeviceCapabilities` and `ValueRange`,
38
+ `HttpEndpointsClient::get_device_capabilities()`,
39
+ `AaroniaSource::device_capabilities()`, and the C entry points
40
+ `aaronia_source_get_capabilities` / `aaronia_source_capabilities_free`.
41
+ Every field is optional and independently so: a device answering about
42
+ frequency but not gain still gets its frequency range published, and
43
+ the file and native-SDK backends — which have no equivalent surface to
44
+ ask — fall back throughout. Nothing here is a guess; a field that
45
+ cannot be read stays absent.
46
+ - **A stream gap now says whether the device caused it.** `HttpSource`
47
+ reads `/healthstatus` on each gap report and checks the device block's
48
+ own loss counters — `status/errors`, `status/usboverflows`,
49
+ `status/dsboverflows`, all per-second rates, so the reading describes
50
+ the moment of the gap rather than the run so far. Nonzero and the loss
51
+ starts at the device, where no amount of network headroom will recover
52
+ it; zero and the samples went missing downstream, in the server's 8 MB
53
+ outbound buffer or on the wire. The read is detached rather than
54
+ awaited: the control-plane timeout is 30 s, and stalling `work()` that
55
+ long would back the chunk channel up into the very loss being
56
+ diagnosed. `HttpEndpointsClient::get_device_health()` exposes the same
57
+ reduction of the health tree, and each reading is published as
58
+ `StreamStats::device_health`.
59
+
60
+ What it cannot settle is whether another client is on the same server
61
+ block. Nothing in the HTTP surface counts connections, so clearing the
62
+ device narrows a gap to a set of causes that includes contention
63
+ without singling it out — which is why the warning names it rather
64
+ than asserting it.
65
+
66
+ ### Fixed
67
+ - **The SoapySDR plugin reported the clock source as `Internal`, a name
68
+ the device does not use, on hardware running off an external 10 MHz
69
+ reference.** `device/sclksource` offers `Consumer`, `Oscillator`,
70
+ `GPS`, `PPS`, `10MHz` and three `… Provider` variants; the measured V6
71
+ ECO is on `10MHz`. An operator who wired a house reference up for
72
+ frequency accuracy was told the device was free-running.
73
+ `listClockSources` and `getClockSource` now answer from the device.
74
+ `setClockSource` still only reads: it accepts the current source as
75
+ the no-op it is, and warns for anything else naming what the device is
76
+ actually on. `listAntennas` likewise takes its name from
77
+ `device/devicemode` — `RX1` on this device, and correct rather than
78
+ coincidental on a V6 running an RX2 mode.
79
+ - **The SoapySDR plugin could link a stale Rust static library.** The
80
+ CMake rule produced `libsdr_aaronia_rs.a` through an
81
+ `add_custom_command(OUTPUT …)` with no `DEPENDS`, so CMake treated the
82
+ archive as up to date the moment it existed and skipped cargo
83
+ entirely. Editing Rust sources and rebuilding relinked the module
84
+ against the old archive — silently, producing a module that looked
85
+ fine and did not contain the change. It is a custom *target* now,
86
+ whose command runs every build and which cargo no-ops when nothing
87
+ moved, which is what the comment there always claimed happened.
88
+ - **`SoapySDRUtil --probe` reported `Timestamps: NO` on a device that
89
+ timestamps every buffer.** `hasHardwareTime("")` answered with the last
90
+ stream timestamp, so it read as "no capability" until a packet had
91
+ arrived — and a probe never streams. It is a capability query now:
92
+ every RTSA packet header carries a start time, `readStream` has always
93
+ returned `SOAPY_SDR_HAS_TIME` with correct epoch nanoseconds, and an
94
+ application deciding at setup whether to record timestamps no longer
95
+ reads the probe as "cannot". `hasHardwareTime("GPS")` is unchanged and
96
+ still reports a value, since a GPS fix may genuinely not exist.
97
+ - **The SoapySDR plugin advertised a TX channel on every device,
98
+ including receivers that cannot transmit at all.** `SoapySDRUtil
99
+ --probe` on a V6 ECO over HTTP reported `1 Tx`, a full TX channel
100
+ section and `Full-duplex: YES`; an application that believed it failed
101
+ on the first write. `getNumChannels` did guard on holding a sink, but
102
+ `aaronia_sink_build` allocates unconditionally — it succeeds on a build
103
+ carrying no TX code at all, and only `aaronia_sink_initialize` fails —
104
+ so the guard was never false. A sink is now built only when the new
105
+ `aaronia_sink_supported()` reports this binary carries the native-SDK
106
+ TX path *and* the source is that backend, which means a `serial=` open
107
+ with no `url=`/`file=` overriding it. The probe now reads `0 Tx` and
108
+ `Full-duplex: NO`.
109
+
110
+ Still not a device capability check: a native-SDK build opened by
111
+ serial against a V6 ECO would advertise TX, since the ECO has no
112
+ transmitter and nothing here asks the SDK.
113
+ - **Restored a clean CI run.** Three clippy lints had been failing
114
+ `cargo clippy --workspace --all-features --all-targets` on Linux since
115
+ v0.7.7 — a collapsible `if let` in `UnifiedSink::initialize`, a needless
116
+ borrow in `native_sdk`, and a `field_reassign_with_default` in a
117
+ `sdk_source` unit test. All three sit in code gated to Windows and
118
+ Linux, so a macOS `cargo clippy` compiles none of it and reports
119
+ success; only CI's `--all-features` run sees them.
120
+
121
+ ### Documentation
122
+ - **What several clients on one HTTP Server block cost, measured.** The
123
+ block accepts any number of concurrent `/stream` clients and serves
124
+ each one a full copy, so *n* clients cost the server *n* times the
125
+ egress. Two clients at 15.36 MS/s ran contiguous at 61.8 MB/s each;
126
+ five saturated the 2.5GbE path at 293.9 MB/s — the same ceiling a
127
+ single fast stream hits — and the loss fell on an arbitrary two of the
128
+ five, moving to a different pair on a repeat run. Two consequences a
129
+ client cannot escape: a clean stream is not evidence of being alone,
130
+ and a gap is not evidence of company. `/info`, `/healthstatus`,
131
+ `/remoteconfig` and the `/stream` response headers were all checked and
132
+ none counts connections.
133
+ - **Corrected the free-licence claim in `docs/HTTPSPEC.md`.** It read
134
+ that running this crate alongside a second client would meet the
135
+ one-connection limit. Five simultaneous clients were served on a system
136
+ holding one HTTP Server block licence, with no error and no refusal:
137
+ the limit is on block instances in the mission graph, not on
138
+ connections to one block.
139
+ - **The link a device actually needs, measured.** `link_budget`'s module
140
+ docs gain a 2.5GbE table beside the gigabit one, and the README a
141
+ requirements section. A 44 MHz real-time-bandwidth device — an ECO 100 —
142
+ has to run the 61.44 MS/s rung, and that rung costs 245.8 MB/s at 4 bytes
143
+ a sample, which gigabit cannot carry; 2.5 Gbps Ethernet is the floor for
144
+ those devices, and the wire format has to stay at 4 bytes a sample
145
+ because 8 asks 491.5 MB/s and loses 41 % of the stream. Two controls came
146
+ out of the same measurement: the path saturates at 292 MB/s, 93 % of
147
+ 2.5GbE line rate, so the wire is the limit and not the server; and two
148
+ configurations asking the same 245.8 MB/s by different routes — 8 bytes a
149
+ sample at 30.72 MS/s, 4 bytes at 61.44 — deliver the same 244 MB/s,
150
+ confirming that only the byte rate matters.
151
+
152
+ ## [v0.7.7] - 2026-09-06
153
+
154
+ ### Added
155
+ - **`utils::IQ_RATE_CLOCK_RATIO` and `utils::iq_ladder_from_top`.** The
156
+ 1.5 receiver-clock-cycles-per-sample rule was a bare literal in three
157
+ places; it has a name now, and the ladder's shape is available
158
+ without the clock rule for a caller that already holds the top rung
159
+ — the link budget's device-anchored remedy, which used to reconstruct
160
+ a clock only to divide it back out (not exact in floating point).
161
+ `PacketMetadata::sample_rate()` likewise names the rate a header
162
+ reports (`sampleFrequency`, else `samples / duration`), shared by
163
+ `StreamingSdrConfig::from_metadata` and the probe's sniff.
164
+ - **A link budget, so a span that cannot fit is caught before the
165
+ capture instead of after it.** The span picks a rate off the
166
+ decimation ladder and at 4 bytes a sample that rate is a byte rate the
167
+ path has to sustain: `--span 10M` is 15.36 MS/s and 61.4 MB/s, which
168
+ measured contiguous over gigabit, while `--span 20M` is 30.72 MS/s and
169
+ 122.9 MB/s, which measured 1024 skips and 1.84 s lost of 35 s. The new
170
+ `link_budget` module does the arithmetic in both directions —
171
+ `required_byte_rate` for what a span costs, `max_sustainable_span` for
172
+ the widest rung a measured rate affords, inverted through the existing
173
+ ladder rather than a second copy of it — and `measure_link_throughput`
174
+ measures the path end to end by counting bytes off `/stream`.
175
+ Deliberately end to end: the bottleneck may be the server, a switch,
176
+ the air or this host's own ingest, and the NIC's advertised link speed
177
+ sees none of them.
178
+
179
+ The probe discards a 500 ms settle window before counting
180
+ (`LINK_PROBE_SETTLE`). Without it a probe *lies*: the server hands over
181
+ its pre-connect backlog faster than real time — measured at ~0.27–0.35 s
182
+ of signal, all inside a 345 ms window at connect — so counting it reads
183
+ above the true link rate and waves through a span that cannot fit. Two
184
+ tests pin this, one driving the settle logic off synthetic instants
185
+ with a 200 MB/s burst ahead of a 10 MB/s stream, and one showing the
186
+ same trace measuring several times too fast with the settle window
187
+ removed.
188
+ An unreachable server, an idle mission or a stream that stops mid-window
189
+ is an error and never a rate, because "0 MB/s" would condemn every span
190
+ on the ladder.
191
+ - **`HttpSource` runs that check passively and warns once**, on the
192
+ stream it is already reading — no second connection, no cost beyond
193
+ adding up chunk lengths. It compares the bytes arriving against what
194
+ the device's own reported rate needs and, if the path is short, names
195
+ the requested span, the rate it needs, what was measured, and the
196
+ widest span that would have fitted. Complementary to `DropDetector`,
197
+ which says the server *did* drop data after the fact; when both fire
198
+ the gap warning now says so, rather than reading as a second unrelated
199
+ fault. Silence is the answer whenever the measurement did not happen:
200
+ a warning never fires on a failed one.
201
+ - **`StreamFormat::iq_bytes_per_sample`**, the one definition of the
202
+ constant the whole budget turns on. `calculate_binary_size` now reads
203
+ it instead of carrying its own copy of the same match.
204
+
205
+ ### Fixed
206
+ - **The passive link-budget check now judges against the rate the device
207
+ reports, not the rate the caller asked for.** `current_sample_rate`
208
+ adopts a new rate only past a 10% hysteresis band (deliberately, to
209
+ keep the tuning target stable), so the check compared against a
210
+ *requested* rate the device merely came close to — under the builder's
211
+ own 1 MS/s default (served by the 0.96 MS/s rung, a 4% gap against a
212
+ 2% tolerance) it warned "this path cannot carry the configured span"
213
+ on every healthy start. The device-reported rate is now tracked
214
+ separately, without hysteresis; no rate reported means no verdict. An
215
+ external mid-measurement retune (RTSA GUI/API, no stream restart)
216
+ restarts the meter, so mixed-rate bytes are never judged against one
217
+ rate's requirement.
218
+ - **The passive meter counts decoded IQ payload, once per sweep,
219
+ instead of raw chunk bytes per chunk.** Per-chunk counting stamped a
220
+ whole drained batch at one instant, so the first post-settle sweep
221
+ counted the queued connect backlog at zero elapsed width — inflating
222
+ the measurement in exactly the case the settle window exists for, and
223
+ enough to wave through a genuinely short path. Raw bytes also counted
224
+ JSON headers and any spectra/histogram packets sharing the stream,
225
+ which under-warned on mixed missions. One payload observation per
226
+ sweep discards the backlog sweep whole (it becomes the meter's mark)
227
+ and compares like against like.
228
+ - **A stall no longer dilutes the measurement, and the window's close is
229
+ exclusive.** The counting window used to run to whenever the next
230
+ observation happened to arrive, so an 8 s consumer stall averaged dead
231
+ time into the "sustained" rate and produced a spurious shortfall
232
+ warning; the observation that crosses the boundary is now excluded in
233
+ both directions. `ThroughputMeter::finish` also refuses to answer
234
+ before the window closes, instead of leaving that check as a
235
+ convention each caller must remember, and the reported settle interval
236
+ is the one actually discarded rather than the one configured.
237
+ - **A configuration restart re-arms the check; a verdict survives
238
+ reconnects.** The `restart_pending` retune path never reset the
239
+ check's once-per-source flags, so a widened span was never measured
240
+ (the case the check exists for) and gap warnings kept citing a stale
241
+ verdict about a span no longer configured. The check's state is now a
242
+ single `LinkCheck` enum — unmeasured / measuring / done — reset to
243
+ unmeasured on configuration restarts only. A parse error in the sweep
244
+ that closes the window no longer discards the finished measurement
245
+ either: the verdict is reported before the error propagates into the
246
+ reconnect path.
247
+ - **The shortfall remedy is computed from the device's own ladder and
248
+ format.** The "widest span that fits" now halves down from the
249
+ device-reported rate, so it is a rung the device actually has —
250
+ inverting through the default-clock ladder named wrong rungs for a
251
+ full V6 on a faster receiver clock — and the no-rung-fits fallback
252
+ computes its figures from the stream format instead of hardcoding
253
+ int16's "120 kS/s, 0.5 MB/s", which was self-contradictory for
254
+ float32. Float32 streams are also told that int16 would halve the
255
+ requirement.
256
+ - **The probe validates its window and probes the capture's stream.**
257
+ `measure_link_throughput` refuses windows under a new
258
+ `MIN_PROBE_WINDOW` (100 ms): two adjacent socket-buffer reads
259
+ microseconds apart would otherwise "measure" gigabytes a second over
260
+ a link that cannot sustain one. `measure_link_throughput_with` now
261
+ takes the capture's `StreamParams` (format *and* input,
262
+ rate-reduction, scale — it used to probe the server's default input
263
+ at full rate whatever the capture would open) plus an explicit settle
264
+ window for servers whose connect backlog outlasts the default, and
265
+ its header sniff parses packet headers only — skipping degenerate
266
+ zero-rate status headers, logging when it gives up — instead of
267
+ running the full sample decoder on payloads it then threw away.
268
+ - **Rate tracking and the meter listen only to IQ packets.** A
269
+ spectra/histogram header carries no `sampleFrequency`, so the parser
270
+ derives a *frame* rate orders of magnitude below the IQ rate; on a
271
+ mixed mission that ping-ponged the device-rate tracker (restarting the
272
+ check on every interleaved packet, so it never finished) and handed it
273
+ a nonsense yardstick. The meter also counted those packets' scalars at
274
+ IQ byte width — half their wire cost — inflating the measured rate.
275
+ Both trackers and the byte count now key on the packet's payload type,
276
+ and a sweep that decodes no IQ does not observe at all. The probe's
277
+ header sniff follows the same rule: `stream_sample_rate` is read from
278
+ an IQ header, never from a spectra frame rate that happened to come
279
+ first.
280
+ - **A failed measurement re-arms the check instead of retiring it.** A
281
+ window that closed without a usable count, or before the device ever
282
+ reported a rate, used to become "checked, no verdict" — permanently,
283
+ on no evidence, so the configuration streamed unjudged forever. It now
284
+ starts a fresh window, reserving the no-verdict terminal state for
285
+ configurations that genuinely cannot be measured. A device retune
286
+ arriving *after* the verdict re-arms too (the verdict described the
287
+ old rate's budget), and `ThroughputMeter::finish` refuses a window
288
+ that actually observed less than half its configured span — a couple
289
+ of packets plus a stray late closing observation say nothing about
290
+ what the path sustains.
291
+ - **`rate_reduction(0)` is rejected at the boundary.** Both builders
292
+ accepted it and sent `?rate_reduction=0` to the server, while the
293
+ link check's measurable gate read `n <= 1` as "no decimation" and
294
+ computed a full-rate requirement against a wire the server was
295
+ decimating by whatever it made of zero. `HttpSourceBuilder::build`,
296
+ `HttpEndpointsClient::start_stream` and the probe now refuse it with
297
+ `Error::Config` (new `StreamParams::validate`), and the gate accepts
298
+ exactly 1.
299
+ - **The shared client builder no longer forces HTTP/1.1.** The
300
+ consolidation carried the endpoints client's `.http1_only()` (which
301
+ the probe had copied) into `rtsa_client_builder`, where it also
302
+ pinned `HttpSource`'s streaming client — which had never had it, and
303
+ streamed through TLS-terminating proxies whose ALPN offers only h2.
304
+ Over plain `http://` reqwest speaks HTTP/1.1 regardless, so the pin
305
+ protected nothing there; over `https://` ALPN now negotiates for
306
+ every client, control plane included, with HTTP/2's adaptive
307
+ flow-control window enabled so a negotiated h2 stream is not capped
308
+ at hyper's default 2 MiB window per round trip — a ceiling the link
309
+ check would otherwise measure and blame on the path.
310
+ - **One framing implementation, shared by the parser and the probe's
311
+ header sniff.** The probe's rate sniff re-scanned its whole
312
+ accumulation on every chunk and resynced a failed header parse past
313
+ the separator where the stream parser resyncs one byte past the `{`
314
+ — binary payloads contain separator bytes, so the two could disagree
315
+ about which header speaks first. `StreamParser` and the sniff now
316
+ frame packets through the same `scan_packet_header`, which finds each
317
+ region's terminator once rather than once per `{` candidate (the old
318
+ loop was quadratic in brace-dense garbage: 256 KiB of `{` took 20 s
319
+ of a tokio worker), honours the hardware-verified LF+RS two-byte
320
+ separator, and reports rejected candidates so the parser's
321
+ `parse_errors` counter is unchanged. The sniff drops bytes as they
322
+ are ruled out and retains at most one candidate awaiting its
323
+ terminator.
324
+ - **An HTTP source starts again after `stop_streaming`.** The reader
325
+ task is spawned at construction and stopping it left nothing to
326
+ restart, so a SoapySDR deactivate/activate cycle (and seify's
327
+ `deactivate_at`/`activate_at`) failed with "HTTP streaming not
328
+ properly initialized". `start_streaming` now reconnects.
329
+ - **A packet whose body cannot be decoded no longer wedges the stream.**
330
+ The parser returned the error but left the packet in its buffer, so
331
+ every later chunk failed on the same bytes while the buffer grew
332
+ without bound. The packet is now skipped. `HttpSource` also resets the
333
+ parser and the drop detector on every reconnect, so a retune no longer
334
+ decodes the new connection's first bytes as the old packet's payload.
335
+ - **`read_samples` returns a partial block on timeout instead of losing
336
+ it.** Samples already moved into the caller's buffer went out with the
337
+ error, an unflagged gap through the C and Python APIs.
338
+ - **`IqPacket.sample_rate_hz` reports the rate the device streams.** The
339
+ `sdr-source` adapter captured the requested rate before the first
340
+ packet and never looked again, so a rate the device snapped to another
341
+ ladder rung was mis-stamped for the whole session.
342
+ - **RTSA files: `mSampleSize` counts values per sample, not bytes.** The
343
+ captures under `tests/` carry 2 for float32 IQ and 1024 for spectra.
344
+ Seeking within a chunk used it as a byte stride, so a second partial
345
+ read of a chunk landed mid-sample, and a spectra read returned one
346
+ scalar per spectrum. Both now use the sample type's width.
347
+ - **The native SDK is shut down by the last client, not the first.**
348
+ `AARTSAAPI_Shutdown` is process-wide, but each source and sink called
349
+ it from its own `Drop`, so re-initialising a source, or pairing a
350
+ source with a sink, tore the SDK down under the survivor.
351
+ - **A bare `spectranv6eco` opens `spectranv6eco/rtsa`.** The ECO has no
352
+ `/raw` mode; `SdkConfig` appended one anyway. SDKSPEC's mode list said
353
+ the same and now agrees with its own V6-vs-ECO table.
354
+ - **Clippy passes on Rust 1.98.** The new `chunks_exact_to_as_chunks`
355
+ lint failed the workspace check at eight decode sites.
356
+ - **The remote-config licence probe can report `Active`.** It looked for
357
+ a bare `reflevel` under a group named `main`; a V6 exposes `reflevel0`
358
+ in its receiver block, so the probe always answered `NotLicensed`. It
359
+ now writes the discovered block, as the retune path does.
360
+ - **`HttpSource` built outside a Tokio runtime errors at build.** It used
361
+ to panic inside `init()` when it spawned its reader.
362
+ - **`HttpSink` with `buffer_size(0)` no longer spins forever**; the size
363
+ is clamped to one.
364
+ - **A quiet dwell in hop mode is an empty read**, not a read error
365
+ counted toward the source-dead bailout.
366
+ - **The first packet after a failed initial connection is not flagged as
367
+ an overrun**; nothing was lost.
368
+ - **seify: `sample_rate()` reports the observed rate**, and the range
369
+ starts at the ladder's 120 kS/s floor instead of 10 kHz.
370
+ - **C API: `aaronia_get_error_message` takes an `int`** and answers
371
+ unknown codes, where an out-of-range enum was undefined behaviour. C
372
+ callers are unaffected (an enum converts to `int`); a Rust caller of
373
+ the `ffi` module's function passes `code as i32`.
374
+ - **Native SDK:** `aaronia_source_read_samples_timeout` honours its
375
+ deadline (it polled for up to 500 ms regardless); spectra reads honour
376
+ the packet stride; an SDK call that returns no object is an error
377
+ rather than a null pointer; opening a second device on a live source
378
+ is refused; the device is closed on drop.
379
+ - **RTSA files:** a negative DSFT stream offset no longer overflows the
380
+ header search, and `seek_to_sample` works on reverse-order files.
381
+ - **Spectrum decompression caps the output size** taken from packet
382
+ metadata.
383
+
384
+ ### Performance
385
+ - The stream parser drops an over-cap payload as it arrives instead of
386
+ buffering it; uncompressed spectra packets decode without a copy;
387
+ spectra file reads decode in place; the C API reserves the caller's
388
+ length before a read; `sample_rate_hz` is an atomic load, so stamping
389
+ it on every packet takes no lock.
390
+
391
+ ### Changed
392
+ - **Byte-rate helpers answer `Option<f64>`, never a `0.0` sentinel.**
393
+ `required_byte_rate`, `max_sustainable_span` and friends returned
394
+ `0.0` for "unknown / nothing fits", a convention every comparison site
395
+ had to remember — `required <= measured` on a garbage rate reads
396
+ "fits". `None` now makes the compiler enforce it, and
397
+ `StreamFormat::iq_bytes_per_sample` returns `Option<usize>` (`None`
398
+ for JSON) for the same reason. New `max_sustainable_sample_rate_below`
399
+ answers remedy queries anchored to the device's reported rate.
400
+ - **The link-budget verdict is data, not just a log line.** New
401
+ `LinkBudgetVerdict` (measured rate, requirement, fit span, shortness)
402
+ is published as `StreamStats::link_budget`, so a GUI or orchestrator
403
+ can auto-narrow the span without scraping logs; the stream-gap warning
404
+ restates the verdict's actual figures from it. `StreamFormat` gained
405
+ `CAPTURE_DEFAULT`, the one definition behind `HttpSourceBuilder`'s
406
+ default, `StreamStats::default` and `DEFAULT_LINK_FORMAT`, which were
407
+ three separately written `Int16` literals.
408
+ - **One copy each of the RTSA client plumbing.** The probe re-used
409
+ hand-rolled copies of the endpoints client's construction, auth
410
+ application, base-URL validation, `/stream` query serialization and
411
+ status-to-error mapping — five drift risks (the three reqwest clients
412
+ had already drifted to three different setting subsets). All are
413
+ shared now: `AuthMethod::apply_to` owns the `RToken` header,
414
+ `rtsa_client_builder` owns the client settings (the streaming client
415
+ gains the keepalive/nodelay/HTTP-1.1 compatibility settings the
416
+ endpoints client always had), and `StreamParams::stream_url`
417
+ serializes every `/stream` URL, including `HttpSource`'s.
418
+ - **`LinkBudgetVerdict::judge` is the verdict's one producer.**
419
+ Requirement, shortness-against-tolerance and the remedy rung were
420
+ assembled by hand at each caller; the constructor now owns that
421
+ arithmetic (so the struct's invariants — fit fields populated only on
422
+ a shortfall, span matching the fit rate — hold by construction), the
423
+ verdict carries `bytes_per_sample` for report text, and
424
+ `ThroughputMeasurement::max_sustainable_span_hz` takes the stream
425
+ format instead of silently assuming int16. The no-rung-fits warning
426
+ cites the device ladder's actual last rung rather than a hardcoded
427
+ 1/512.
428
+ - **`HttpSource`'s stream-open failure is the typed error the rest of
429
+ the crate returns.** A non-success status on `/stream` is now
430
+ `Error::Http { status, context }` — the same variant the endpoints
431
+ client and the probe produce, with `context` naming the stream URL —
432
+ instead of `Error::Protocol("Stream endpoint returned error: …")`.
433
+ Anything matching on that text will need the new form.
434
+ - **`work()`'s output copy is two bulk `copy_from_slice` calls** over the
435
+ deque's contiguous halves instead of a `pop_front` per sample, whose
436
+ per-element wrap-around check the compiler cannot lift. Factored into
437
+ `copy_out` and unit-tested, including the wrapped-deque case.
438
+
439
+ ### Documentation
440
+ - **Measured the HTTP transport on WiFi 7 and recorded where the
441
+ ceiling actually is.** `curl` on `/stream` sustains ~75 MB/s
442
+ (0.6 Gbit/s) station-to-station at a 2.4 Gbps PHY — both ends on air
443
+ halves the medium — and the figure is the same for all three wire
444
+ formats, so the encoding is not the limit. Two parallel connections
445
+ measured less in aggregate (63.9 vs 74 MB/s), so a single connection
446
+ is optimal and nothing smarter is available to the client. Against
447
+ that, the crate's full framing-plus-decode path measures ~3 GB/s and
448
+ the individual decoders 0.8–10 GS/s: the parser has fortyfold
449
+ headroom and is never the bottleneck. What the link buys at 4 bytes a
450
+ sample is ~19 MS/s — the 15.36 MS/s rung fits, 30.72 does not, and
451
+ full span (246 MB/s) needs a wired path to the RTSA host. Two ignored
452
+ throughput-meter tests (`decode_throughput_meter`,
453
+ `framing_throughput_meter`) keep these numbers re-measurable in one
454
+ command.
455
+ - README and PLUGINS pinned `sdr-aaronia-rs = "0.6"`, a major behind
456
+ the crate, so nothing they describe resolved. Now `"0.7"`.
457
+ - `cumulative_drops` counts client-detected timestamp gaps; four docs
458
+ called them server-reported drops. Bandwidth figures now use the
459
+ 61.44 MS/s top rate rather than the 92 MHz clock. The examples table
460
+ and the QUICKSTART verification link were corrected.
6
461
 
7
462
  ## [v0.7.6] - 2026-08-15
8
463
 
@@ -120,7 +120,7 @@ open target/llvm-cov/html/index.html # xdg-open on Linux, start on Windows
120
120
 
121
121
  ### 7. Criterion Benchmarks (`benches/`)
122
122
 
123
- Three benchmark harnesses track the hot paths across releases. Benchmarks are run on demand to investigate performance implications of a PR.
123
+ Four benchmark harnesses track the hot paths across releases. Benchmarks are run on demand to investigate performance implications of a PR.
124
124
 
125
125
  ```bash
126
126
  cargo bench # Run all benchmarks
@@ -1275,7 +1275,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1275
1275
  checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
1276
1276
  dependencies = [
1277
1277
  "libc",
1278
- "windows-sys 0.61.2",
1278
+ "windows-sys 0.52.0",
1279
1279
  ]
1280
1280
 
1281
1281
  [[package]]
@@ -1669,9 +1669,9 @@ dependencies = [
1669
1669
 
1670
1670
  [[package]]
1671
1671
  name = "h2"
1672
- version = "0.4.15"
1672
+ version = "0.4.19"
1673
1673
  source = "registry+https://github.com/rust-lang/crates.io-index"
1674
- checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155"
1674
+ checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16"
1675
1675
  dependencies = [
1676
1676
  "atomic-waker",
1677
1677
  "bytes",
@@ -2041,7 +2041,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
2041
2041
  dependencies = [
2042
2042
  "hermit-abi",
2043
2043
  "libc",
2044
- "windows-sys 0.61.2",
2044
+ "windows-sys 0.52.0",
2045
2045
  ]
2046
2046
 
2047
2047
  [[package]]
@@ -3027,7 +3027,7 @@ dependencies = [
3027
3027
 
3028
3028
  [[package]]
3029
3029
  name = "python-aaronia"
3030
- version = "0.7.6"
3030
+ version = "0.8.0"
3031
3031
  dependencies = [
3032
3032
  "arrow",
3033
3033
  "num-complex",
@@ -3098,7 +3098,7 @@ dependencies = [
3098
3098
  "once_cell",
3099
3099
  "socket2",
3100
3100
  "tracing",
3101
- "windows-sys 0.61.2",
3101
+ "windows-sys 0.52.0",
3102
3102
  ]
3103
3103
 
3104
3104
  [[package]]
@@ -3404,7 +3404,7 @@ dependencies = [
3404
3404
  "errno",
3405
3405
  "libc",
3406
3406
  "linux-raw-sys",
3407
- "windows-sys 0.61.2",
3407
+ "windows-sys 0.52.0",
3408
3408
  ]
3409
3409
 
3410
3410
  [[package]]
@@ -3461,7 +3461,7 @@ dependencies = [
3461
3461
  "security-framework",
3462
3462
  "security-framework-sys",
3463
3463
  "webpki-root-certs",
3464
- "windows-sys 0.61.2",
3464
+ "windows-sys 0.52.0",
3465
3465
  ]
3466
3466
 
3467
3467
  [[package]]
@@ -3556,7 +3556,7 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
3556
3556
 
3557
3557
  [[package]]
3558
3558
  name = "sdr-aaronia-rs"
3559
- version = "0.7.6"
3559
+ version = "0.8.0"
3560
3560
  dependencies = [
3561
3561
  "anyhow",
3562
3562
  "bitflags 2.13.1",
@@ -4135,7 +4135,7 @@ dependencies = [
4135
4135
  "getrandom 0.4.3",
4136
4136
  "once_cell",
4137
4137
  "rustix",
4138
- "windows-sys 0.61.2",
4138
+ "windows-sys 0.52.0",
4139
4139
  ]
4140
4140
 
4141
4141
  [[package]]
@@ -4792,7 +4792,7 @@ version = "0.1.11"
4792
4792
  source = "registry+https://github.com/rust-lang/crates.io-index"
4793
4793
  checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
4794
4794
  dependencies = [
4795
- "windows-sys 0.61.2",
4795
+ "windows-sys 0.52.0",
4796
4796
  ]
4797
4797
 
4798
4798
  [[package]]
@@ -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.6"
5
+ version = "0.8.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.7.6
3
+ Version: 0.8.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+)
@@ -211,7 +211,7 @@ silently defaulting.
211
211
  | `read_samples_arrow(count)` | PyArrow `FixedSizeListArray` of `[re, im]` float32 pairs |
212
212
  | `read_samples_dual_numpy(count)` | `(rx1, rx2)` NumPy arrays (dual-channel captures) |
213
213
  | `set_center_frequency(hz)` / `set_sample_rate(hz)` / `set_reference_level(dbm)` | Live retuning |
214
- | `cumulative_drops()` | Total server-reported dropped samples |
214
+ | `cumulative_drops()` | Timestamp gaps detected in the stream so far (gap events, not samples) |
215
215
  | `take_overrun()` | True once per detected receive-side overrun |
216
216
  | `last_timestamp_ns()` | Epoch-ns timestamp of the last received block (HTTP backend; 0 otherwise) |
217
217
 
@@ -12,7 +12,7 @@ To use the Seify plugin, enable the `seify` feature in your `Cargo.toml`:
12
12
 
13
13
  ```toml
14
14
  [dependencies]
15
- sdr-aaronia-rs = { version = "0.6", features = ["seify"] }
15
+ sdr-aaronia-rs = { version = "0.7", features = ["seify"] }
16
16
  ```
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.
@@ -136,11 +136,11 @@ Both the native Rust API and the Python/C++ bindings expose critical metrics to
136
136
 
137
137
  - **Hardware Timestamps**: You can retrieve the precise hardware timestamp (in nanoseconds) of the last received block using `last_timestamp_ns`.
138
138
  - **Overruns**: The `take_overrun()` function checks if the internal buffer has overflown since the last check, allowing you to react to drops on the client side.
139
- - **Cumulative Drops**: The `cumulative_drops()` function reports the total number of blocks dropped by the Aaronia server itself due to network backpressure.
139
+ - **Cumulative Drops**: `cumulative_drops()` counts the timestamp gaps the client's drop detector has seen in the stream. Each is one gap event, however many samples it spanned.
140
140
 
141
141
  In SoapySDR, you can access these metrics via the `readSensor()` API:
142
142
  ```python
143
- # Check for server-side drops
143
+ # Check for gaps in the stream
144
144
  drops = sdr.readSensor("cumulative_drops")
145
- print(f"Dropped blocks: {drops}")
145
+ print(f"Gaps seen: {drops}")
146
146
  ```