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.
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/.gitignore +6 -8
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/CHANGELOG.md +456 -1
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/CONTRIBUTING.md +1 -1
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/Cargo.lock +11 -11
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/Cargo.toml +1 -1
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/PKG-INFO +2 -2
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/PLUGINS.md +4 -4
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/README.md +29 -2
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/docs/APPS.md +8 -3
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/docs/FILESPEC.md +1 -1
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/docs/HTTPSPEC.md +96 -5
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/docs/QUICKSTART.md +6 -4
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/docs/SDKSPEC.md +1 -1
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/docs/USAGE.md +137 -3
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/include/aaronia.h +58 -1
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/packaging/homebrew/soapy-aaronia.rb +1 -1
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/python-aaronia/Cargo.toml +1 -1
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/python-aaronia/README.md +1 -1
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/python-aaronia/aaronia.pyi +1 -1
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/python-aaronia/src/lib.rs +1 -1
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/soapy-aaronia/AaroniaSoapyDevice.cpp +142 -24
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/soapy-aaronia/AaroniaSoapyDevice.hpp +20 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/soapy-aaronia/CMakeLists.txt +14 -5
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/soapy-aaronia/README.md +82 -12
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/soapy-aaronia/Registration.cpp +35 -7
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/c_api.rs +332 -14
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/decompression.rs +11 -1
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/file_source.rs +96 -20
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/http_endpoints.rs +997 -55
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/http_sink.rs +4 -1
- python_aaronia-0.8.0/src/http_source.rs +3318 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/http_streaming.rs +660 -184
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/lib.rs +12 -0
- python_aaronia-0.8.0/src/link_budget.rs +1524 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/native_sdk.rs +162 -12
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/sdk_source.rs +35 -3
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/sdr_source_impl.rs +9 -7
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/seify_impl.rs +5 -2
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/unified_sink.rs +27 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/unified_source.rs +128 -19
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/utils.rs +167 -14
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/tests/c_api_test.rs +1 -1
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/tests/live_smoke.rs +84 -0
- python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeCache.txt +0 -439
- python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/4.4.2/CMakeCXXCompiler.cmake +0 -103
- python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/4.4.2/CMakeDetermineCompilerABI_CXX.bin +0 -0
- python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/4.4.2/CMakeSystem.cmake +0 -15
- python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/4.4.2/CompilerIdCXX/CMakeCXXCompilerId.cpp +0 -954
- python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/4.4.2/CompilerIdCXX/a.out +0 -0
- python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/4.4.2/CompilerIdCXX/apple-sdk.cpp +0 -1
- python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/CMakeConfigureLog.yaml +0 -1904
- python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/CMakeDirectoryInformation.cmake +0 -16
- python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/CMakeRuleHashes.txt +0 -3
- python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/InstallScripts.json +0 -7
- python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/Makefile.cmake +0 -61
- python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/Makefile2 +0 -157
- python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/TargetDirectories.txt +0 -8
- python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/DependInfo.cmake +0 -24
- python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/build.make +0 -131
- python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/cmake_clean.cmake +0 -13
- python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/compiler_depend.make +0 -2
- python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/compiler_depend.ts +0 -2
- python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/depend.make +0 -2
- python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/flags.make +0 -12
- python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/link.txt +0 -1
- python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/progress.make +0 -4
- python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/cmake.check_cache +0 -1
- python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/progress.marks +0 -1
- python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/DependInfo.cmake +0 -22
- python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/build.make +0 -94
- python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/cmake_clean.cmake +0 -9
- python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/compiler_depend.make +0 -2
- python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/compiler_depend.ts +0 -2
- python_aaronia-0.7.6/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/progress.make +0 -2
- python_aaronia-0.7.6/soapy-aaronia/build-test/Makefile +0 -271
- python_aaronia-0.7.6/soapy-aaronia/build-test/cmake_install.cmake +0 -78
- python_aaronia-0.7.6/src/http_source.rs +0 -1851
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/.cargo/config.toml +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/.gitattributes +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/DESIGN.md +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/LICENSE +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/benches/decompress_block.rs +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/benches/deinterleave_dual_iq.rs +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/benches/parse_int16_packet.rs +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/benches/rtsa_open_and_read.rs +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/deny.toml +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/docs/VERIFICATION.md +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/examples/channel_hopping.rs +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/examples/device_control.rs +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/examples/dump_metadata.rs +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/examples/http_iq_quickstart.rs +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/examples/native_sdk_basic.rs +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/examples/native_sdk_transmit.rs +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/examples/noaa_scanner.rs +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/examples/python_arrow_example.py +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/examples/read_rtsa_file.rs +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/examples/soapy_python_example.py +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/packaging/homebrew/README.md +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/pyproject.toml +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/python-aaronia/test_basic.py +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/scripts/ci-local.sh +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/scripts/validate-iq-live.py +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/soapy-aaronia/packaging/install.ps1 +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/soapy-aaronia/packaging/install.sh +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/soapy-aaronia/print.cmake +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/detection.rs +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/error.rs +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/src/sdk_sink.rs +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/test.cmake +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/tests/http_mock_test.rs +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/tests/http_resilience_test.rs +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/tests/http_sink_test.rs +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/tests/integration_test.rs +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/tests/native_sdk_load.rs +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/tests/properties.proptest-regressions +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/tests/properties.rs +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/tests/rtsa_negative_test.rs +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/tests/sdr_source_impl_test.rs +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/tests/spec_coverage.rs +0 -0
- {python_aaronia-0.7.6 → python_aaronia-0.8.0}/tests/test_cw_mag.rs +0 -0
- {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
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
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
|
-
## [
|
|
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
|
-
|
|
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.
|
|
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.
|
|
1672
|
+
version = "0.4.19"
|
|
1673
1673
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1674
|
-
checksum = "
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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()` |
|
|
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.
|
|
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**:
|
|
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
|
|
143
|
+
# Check for gaps in the stream
|
|
144
144
|
drops = sdr.readSensor("cumulative_drops")
|
|
145
|
-
print(f"
|
|
145
|
+
print(f"Gaps seen: {drops}")
|
|
146
146
|
```
|