python-aaronia 0.7.5__tar.gz → 0.7.7__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.5 → python_aaronia-0.7.7}/CHANGELOG.md +395 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/CONTRIBUTING.md +1 -1
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/Cargo.lock +11 -11
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/Cargo.toml +1 -1
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/PKG-INFO +2 -2
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/PLUGINS.md +4 -4
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/README.md +5 -2
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/docs/APPS.md +1 -1
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/docs/FILESPEC.md +1 -1
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/docs/HTTPSPEC.md +6 -1
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/docs/QUICKSTART.md +6 -4
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/docs/SDKSPEC.md +1 -1
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/docs/USAGE.md +68 -3
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/include/aaronia.h +3 -1
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/packaging/homebrew/soapy-aaronia.rb +1 -1
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/python-aaronia/Cargo.toml +1 -1
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/python-aaronia/README.md +1 -1
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/python-aaronia/aaronia.pyi +1 -1
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/python-aaronia/src/lib.rs +1 -1
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/README.md +2 -1
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/src/c_api.rs +39 -14
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/src/decompression.rs +11 -1
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/src/file_source.rs +159 -30
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/src/http_endpoints.rs +577 -55
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/src/http_sink.rs +4 -1
- python_aaronia-0.7.7/src/http_source.rs +3061 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/src/http_streaming.rs +660 -184
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/src/lib.rs +15 -2
- python_aaronia-0.7.7/src/link_budget.rs +1490 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/src/native_sdk.rs +162 -12
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/src/sdk_source.rs +33 -3
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/src/sdr_source_impl.rs +9 -7
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/src/seify_impl.rs +5 -2
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/src/unified_sink.rs +7 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/src/unified_source.rs +95 -19
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/src/utils.rs +125 -14
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/tests/c_api_test.rs +1 -1
- python_aaronia-0.7.5/src/http_source.rs +0 -1485
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/.cargo/config.toml +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/.gitattributes +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/.gitignore +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/DESIGN.md +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/LICENSE +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/benches/decompress_block.rs +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/benches/deinterleave_dual_iq.rs +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/benches/parse_int16_packet.rs +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/benches/rtsa_open_and_read.rs +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/deny.toml +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/docs/VERIFICATION.md +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/examples/channel_hopping.rs +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/examples/device_control.rs +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/examples/dump_metadata.rs +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/examples/http_iq_quickstart.rs +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/examples/native_sdk_basic.rs +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/examples/native_sdk_transmit.rs +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/examples/noaa_scanner.rs +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/examples/python_arrow_example.py +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/examples/read_rtsa_file.rs +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/examples/soapy_python_example.py +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/packaging/homebrew/README.md +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/pyproject.toml +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/python-aaronia/test_basic.py +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/scripts/ci-local.sh +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/scripts/validate-iq-live.py +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/AaroniaSoapyDevice.cpp +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/AaroniaSoapyDevice.hpp +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/CMakeLists.txt +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/Registration.cpp +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/build-test/CMakeCache.txt +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/build-test/CMakeFiles/4.4.2/CMakeCXXCompiler.cmake +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/build-test/CMakeFiles/4.4.2/CMakeDetermineCompilerABI_CXX.bin +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/build-test/CMakeFiles/4.4.2/CMakeSystem.cmake +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/build-test/CMakeFiles/4.4.2/CompilerIdCXX/CMakeCXXCompilerId.cpp +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/build-test/CMakeFiles/4.4.2/CompilerIdCXX/a.out +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/build-test/CMakeFiles/4.4.2/CompilerIdCXX/apple-sdk.cpp +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/build-test/CMakeFiles/CMakeConfigureLog.yaml +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/build-test/CMakeFiles/CMakeDirectoryInformation.cmake +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/build-test/CMakeFiles/CMakeRuleHashes.txt +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/build-test/CMakeFiles/InstallScripts.json +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/build-test/CMakeFiles/Makefile.cmake +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/build-test/CMakeFiles/Makefile2 +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/build-test/CMakeFiles/TargetDirectories.txt +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/DependInfo.cmake +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/build.make +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/cmake_clean.cmake +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/compiler_depend.make +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/compiler_depend.ts +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/depend.make +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/flags.make +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/link.txt +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/progress.make +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/build-test/CMakeFiles/cmake.check_cache +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/build-test/CMakeFiles/progress.marks +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/DependInfo.cmake +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/build.make +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/cmake_clean.cmake +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/compiler_depend.make +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/compiler_depend.ts +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/progress.make +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/build-test/Makefile +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/build-test/cmake_install.cmake +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/packaging/install.ps1 +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/packaging/install.sh +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/soapy-aaronia/print.cmake +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/src/detection.rs +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/src/error.rs +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/src/sdk_sink.rs +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/test.cmake +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/tests/http_mock_test.rs +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/tests/http_resilience_test.rs +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/tests/http_sink_test.rs +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/tests/integration_test.rs +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/tests/live_smoke.rs +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/tests/native_sdk_load.rs +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/tests/properties.proptest-regressions +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/tests/properties.rs +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/tests/rtsa_negative_test.rs +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/tests/sdr_source_impl_test.rs +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/tests/spec_coverage.rs +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/tests/test_cw_mag.rs +0 -0
- {python_aaronia-0.7.5 → python_aaronia-0.7.7}/tests/test_cw_meta.rs +0 -0
|
@@ -4,6 +4,401 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [v0.7.7] - 2026-09-06
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **`utils::IQ_RATE_CLOCK_RATIO` and `utils::iq_ladder_from_top`.** The
|
|
11
|
+
1.5 receiver-clock-cycles-per-sample rule was a bare literal in three
|
|
12
|
+
places; it has a name now, and the ladder's shape is available
|
|
13
|
+
without the clock rule for a caller that already holds the top rung
|
|
14
|
+
— the link budget's device-anchored remedy, which used to reconstruct
|
|
15
|
+
a clock only to divide it back out (not exact in floating point).
|
|
16
|
+
`PacketMetadata::sample_rate()` likewise names the rate a header
|
|
17
|
+
reports (`sampleFrequency`, else `samples / duration`), shared by
|
|
18
|
+
`StreamingSdrConfig::from_metadata` and the probe's sniff.
|
|
19
|
+
- **A link budget, so a span that cannot fit is caught before the
|
|
20
|
+
capture instead of after it.** The span picks a rate off the
|
|
21
|
+
decimation ladder and at 4 bytes a sample that rate is a byte rate the
|
|
22
|
+
path has to sustain: `--span 10M` is 15.36 MS/s and 61.4 MB/s, which
|
|
23
|
+
measured contiguous over gigabit, while `--span 20M` is 30.72 MS/s and
|
|
24
|
+
122.9 MB/s, which measured 1024 skips and 1.84 s lost of 35 s. The new
|
|
25
|
+
`link_budget` module does the arithmetic in both directions —
|
|
26
|
+
`required_byte_rate` for what a span costs, `max_sustainable_span` for
|
|
27
|
+
the widest rung a measured rate affords, inverted through the existing
|
|
28
|
+
ladder rather than a second copy of it — and `measure_link_throughput`
|
|
29
|
+
measures the path end to end by counting bytes off `/stream`.
|
|
30
|
+
Deliberately end to end: the bottleneck may be the server, a switch,
|
|
31
|
+
the air or this host's own ingest, and the NIC's advertised link speed
|
|
32
|
+
sees none of them.
|
|
33
|
+
|
|
34
|
+
The probe discards a 500 ms settle window before counting
|
|
35
|
+
(`LINK_PROBE_SETTLE`). Without it a probe *lies*: the server hands over
|
|
36
|
+
its pre-connect backlog faster than real time — measured at ~0.27–0.35 s
|
|
37
|
+
of signal, all inside a 345 ms window at connect — so counting it reads
|
|
38
|
+
above the true link rate and waves through a span that cannot fit. Two
|
|
39
|
+
tests pin this, one driving the settle logic off synthetic instants
|
|
40
|
+
with a 200 MB/s burst ahead of a 10 MB/s stream, and one showing the
|
|
41
|
+
same trace measuring several times too fast with the settle window
|
|
42
|
+
removed.
|
|
43
|
+
An unreachable server, an idle mission or a stream that stops mid-window
|
|
44
|
+
is an error and never a rate, because "0 MB/s" would condemn every span
|
|
45
|
+
on the ladder.
|
|
46
|
+
- **`HttpSource` runs that check passively and warns once**, on the
|
|
47
|
+
stream it is already reading — no second connection, no cost beyond
|
|
48
|
+
adding up chunk lengths. It compares the bytes arriving against what
|
|
49
|
+
the device's own reported rate needs and, if the path is short, names
|
|
50
|
+
the requested span, the rate it needs, what was measured, and the
|
|
51
|
+
widest span that would have fitted. Complementary to `DropDetector`,
|
|
52
|
+
which says the server *did* drop data after the fact; when both fire
|
|
53
|
+
the gap warning now says so, rather than reading as a second unrelated
|
|
54
|
+
fault. Silence is the answer whenever the measurement did not happen:
|
|
55
|
+
a warning never fires on a failed one.
|
|
56
|
+
- **`StreamFormat::iq_bytes_per_sample`**, the one definition of the
|
|
57
|
+
constant the whole budget turns on. `calculate_binary_size` now reads
|
|
58
|
+
it instead of carrying its own copy of the same match.
|
|
59
|
+
|
|
60
|
+
### Fixed
|
|
61
|
+
- **The passive link-budget check now judges against the rate the device
|
|
62
|
+
reports, not the rate the caller asked for.** `current_sample_rate`
|
|
63
|
+
adopts a new rate only past a 10% hysteresis band (deliberately, to
|
|
64
|
+
keep the tuning target stable), so the check compared against a
|
|
65
|
+
*requested* rate the device merely came close to — under the builder's
|
|
66
|
+
own 1 MS/s default (served by the 0.96 MS/s rung, a 4% gap against a
|
|
67
|
+
2% tolerance) it warned "this path cannot carry the configured span"
|
|
68
|
+
on every healthy start. The device-reported rate is now tracked
|
|
69
|
+
separately, without hysteresis; no rate reported means no verdict. An
|
|
70
|
+
external mid-measurement retune (RTSA GUI/API, no stream restart)
|
|
71
|
+
restarts the meter, so mixed-rate bytes are never judged against one
|
|
72
|
+
rate's requirement.
|
|
73
|
+
- **The passive meter counts decoded IQ payload, once per sweep,
|
|
74
|
+
instead of raw chunk bytes per chunk.** Per-chunk counting stamped a
|
|
75
|
+
whole drained batch at one instant, so the first post-settle sweep
|
|
76
|
+
counted the queued connect backlog at zero elapsed width — inflating
|
|
77
|
+
the measurement in exactly the case the settle window exists for, and
|
|
78
|
+
enough to wave through a genuinely short path. Raw bytes also counted
|
|
79
|
+
JSON headers and any spectra/histogram packets sharing the stream,
|
|
80
|
+
which under-warned on mixed missions. One payload observation per
|
|
81
|
+
sweep discards the backlog sweep whole (it becomes the meter's mark)
|
|
82
|
+
and compares like against like.
|
|
83
|
+
- **A stall no longer dilutes the measurement, and the window's close is
|
|
84
|
+
exclusive.** The counting window used to run to whenever the next
|
|
85
|
+
observation happened to arrive, so an 8 s consumer stall averaged dead
|
|
86
|
+
time into the "sustained" rate and produced a spurious shortfall
|
|
87
|
+
warning; the observation that crosses the boundary is now excluded in
|
|
88
|
+
both directions. `ThroughputMeter::finish` also refuses to answer
|
|
89
|
+
before the window closes, instead of leaving that check as a
|
|
90
|
+
convention each caller must remember, and the reported settle interval
|
|
91
|
+
is the one actually discarded rather than the one configured.
|
|
92
|
+
- **A configuration restart re-arms the check; a verdict survives
|
|
93
|
+
reconnects.** The `restart_pending` retune path never reset the
|
|
94
|
+
check's once-per-source flags, so a widened span was never measured
|
|
95
|
+
(the case the check exists for) and gap warnings kept citing a stale
|
|
96
|
+
verdict about a span no longer configured. The check's state is now a
|
|
97
|
+
single `LinkCheck` enum — unmeasured / measuring / done — reset to
|
|
98
|
+
unmeasured on configuration restarts only. A parse error in the sweep
|
|
99
|
+
that closes the window no longer discards the finished measurement
|
|
100
|
+
either: the verdict is reported before the error propagates into the
|
|
101
|
+
reconnect path.
|
|
102
|
+
- **The shortfall remedy is computed from the device's own ladder and
|
|
103
|
+
format.** The "widest span that fits" now halves down from the
|
|
104
|
+
device-reported rate, so it is a rung the device actually has —
|
|
105
|
+
inverting through the default-clock ladder named wrong rungs for a
|
|
106
|
+
full V6 on a faster receiver clock — and the no-rung-fits fallback
|
|
107
|
+
computes its figures from the stream format instead of hardcoding
|
|
108
|
+
int16's "120 kS/s, 0.5 MB/s", which was self-contradictory for
|
|
109
|
+
float32. Float32 streams are also told that int16 would halve the
|
|
110
|
+
requirement.
|
|
111
|
+
- **The probe validates its window and probes the capture's stream.**
|
|
112
|
+
`measure_link_throughput` refuses windows under a new
|
|
113
|
+
`MIN_PROBE_WINDOW` (100 ms): two adjacent socket-buffer reads
|
|
114
|
+
microseconds apart would otherwise "measure" gigabytes a second over
|
|
115
|
+
a link that cannot sustain one. `measure_link_throughput_with` now
|
|
116
|
+
takes the capture's `StreamParams` (format *and* input,
|
|
117
|
+
rate-reduction, scale — it used to probe the server's default input
|
|
118
|
+
at full rate whatever the capture would open) plus an explicit settle
|
|
119
|
+
window for servers whose connect backlog outlasts the default, and
|
|
120
|
+
its header sniff parses packet headers only — skipping degenerate
|
|
121
|
+
zero-rate status headers, logging when it gives up — instead of
|
|
122
|
+
running the full sample decoder on payloads it then threw away.
|
|
123
|
+
- **Rate tracking and the meter listen only to IQ packets.** A
|
|
124
|
+
spectra/histogram header carries no `sampleFrequency`, so the parser
|
|
125
|
+
derives a *frame* rate orders of magnitude below the IQ rate; on a
|
|
126
|
+
mixed mission that ping-ponged the device-rate tracker (restarting the
|
|
127
|
+
check on every interleaved packet, so it never finished) and handed it
|
|
128
|
+
a nonsense yardstick. The meter also counted those packets' scalars at
|
|
129
|
+
IQ byte width — half their wire cost — inflating the measured rate.
|
|
130
|
+
Both trackers and the byte count now key on the packet's payload type,
|
|
131
|
+
and a sweep that decodes no IQ does not observe at all. The probe's
|
|
132
|
+
header sniff follows the same rule: `stream_sample_rate` is read from
|
|
133
|
+
an IQ header, never from a spectra frame rate that happened to come
|
|
134
|
+
first.
|
|
135
|
+
- **A failed measurement re-arms the check instead of retiring it.** A
|
|
136
|
+
window that closed without a usable count, or before the device ever
|
|
137
|
+
reported a rate, used to become "checked, no verdict" — permanently,
|
|
138
|
+
on no evidence, so the configuration streamed unjudged forever. It now
|
|
139
|
+
starts a fresh window, reserving the no-verdict terminal state for
|
|
140
|
+
configurations that genuinely cannot be measured. A device retune
|
|
141
|
+
arriving *after* the verdict re-arms too (the verdict described the
|
|
142
|
+
old rate's budget), and `ThroughputMeter::finish` refuses a window
|
|
143
|
+
that actually observed less than half its configured span — a couple
|
|
144
|
+
of packets plus a stray late closing observation say nothing about
|
|
145
|
+
what the path sustains.
|
|
146
|
+
- **`rate_reduction(0)` is rejected at the boundary.** Both builders
|
|
147
|
+
accepted it and sent `?rate_reduction=0` to the server, while the
|
|
148
|
+
link check's measurable gate read `n <= 1` as "no decimation" and
|
|
149
|
+
computed a full-rate requirement against a wire the server was
|
|
150
|
+
decimating by whatever it made of zero. `HttpSourceBuilder::build`,
|
|
151
|
+
`HttpEndpointsClient::start_stream` and the probe now refuse it with
|
|
152
|
+
`Error::Config` (new `StreamParams::validate`), and the gate accepts
|
|
153
|
+
exactly 1.
|
|
154
|
+
- **The shared client builder no longer forces HTTP/1.1.** The
|
|
155
|
+
consolidation carried the endpoints client's `.http1_only()` (which
|
|
156
|
+
the probe had copied) into `rtsa_client_builder`, where it also
|
|
157
|
+
pinned `HttpSource`'s streaming client — which had never had it, and
|
|
158
|
+
streamed through TLS-terminating proxies whose ALPN offers only h2.
|
|
159
|
+
Over plain `http://` reqwest speaks HTTP/1.1 regardless, so the pin
|
|
160
|
+
protected nothing there; over `https://` ALPN now negotiates for
|
|
161
|
+
every client, control plane included, with HTTP/2's adaptive
|
|
162
|
+
flow-control window enabled so a negotiated h2 stream is not capped
|
|
163
|
+
at hyper's default 2 MiB window per round trip — a ceiling the link
|
|
164
|
+
check would otherwise measure and blame on the path.
|
|
165
|
+
- **One framing implementation, shared by the parser and the probe's
|
|
166
|
+
header sniff.** The probe's rate sniff re-scanned its whole
|
|
167
|
+
accumulation on every chunk and resynced a failed header parse past
|
|
168
|
+
the separator where the stream parser resyncs one byte past the `{`
|
|
169
|
+
— binary payloads contain separator bytes, so the two could disagree
|
|
170
|
+
about which header speaks first. `StreamParser` and the sniff now
|
|
171
|
+
frame packets through the same `scan_packet_header`, which finds each
|
|
172
|
+
region's terminator once rather than once per `{` candidate (the old
|
|
173
|
+
loop was quadratic in brace-dense garbage: 256 KiB of `{` took 20 s
|
|
174
|
+
of a tokio worker), honours the hardware-verified LF+RS two-byte
|
|
175
|
+
separator, and reports rejected candidates so the parser's
|
|
176
|
+
`parse_errors` counter is unchanged. The sniff drops bytes as they
|
|
177
|
+
are ruled out and retains at most one candidate awaiting its
|
|
178
|
+
terminator.
|
|
179
|
+
- **An HTTP source starts again after `stop_streaming`.** The reader
|
|
180
|
+
task is spawned at construction and stopping it left nothing to
|
|
181
|
+
restart, so a SoapySDR deactivate/activate cycle (and seify's
|
|
182
|
+
`deactivate_at`/`activate_at`) failed with "HTTP streaming not
|
|
183
|
+
properly initialized". `start_streaming` now reconnects.
|
|
184
|
+
- **A packet whose body cannot be decoded no longer wedges the stream.**
|
|
185
|
+
The parser returned the error but left the packet in its buffer, so
|
|
186
|
+
every later chunk failed on the same bytes while the buffer grew
|
|
187
|
+
without bound. The packet is now skipped. `HttpSource` also resets the
|
|
188
|
+
parser and the drop detector on every reconnect, so a retune no longer
|
|
189
|
+
decodes the new connection's first bytes as the old packet's payload.
|
|
190
|
+
- **`read_samples` returns a partial block on timeout instead of losing
|
|
191
|
+
it.** Samples already moved into the caller's buffer went out with the
|
|
192
|
+
error, an unflagged gap through the C and Python APIs.
|
|
193
|
+
- **`IqPacket.sample_rate_hz` reports the rate the device streams.** The
|
|
194
|
+
`sdr-source` adapter captured the requested rate before the first
|
|
195
|
+
packet and never looked again, so a rate the device snapped to another
|
|
196
|
+
ladder rung was mis-stamped for the whole session.
|
|
197
|
+
- **RTSA files: `mSampleSize` counts values per sample, not bytes.** The
|
|
198
|
+
captures under `tests/` carry 2 for float32 IQ and 1024 for spectra.
|
|
199
|
+
Seeking within a chunk used it as a byte stride, so a second partial
|
|
200
|
+
read of a chunk landed mid-sample, and a spectra read returned one
|
|
201
|
+
scalar per spectrum. Both now use the sample type's width.
|
|
202
|
+
- **The native SDK is shut down by the last client, not the first.**
|
|
203
|
+
`AARTSAAPI_Shutdown` is process-wide, but each source and sink called
|
|
204
|
+
it from its own `Drop`, so re-initialising a source, or pairing a
|
|
205
|
+
source with a sink, tore the SDK down under the survivor.
|
|
206
|
+
- **A bare `spectranv6eco` opens `spectranv6eco/rtsa`.** The ECO has no
|
|
207
|
+
`/raw` mode; `SdkConfig` appended one anyway. SDKSPEC's mode list said
|
|
208
|
+
the same and now agrees with its own V6-vs-ECO table.
|
|
209
|
+
- **Clippy passes on Rust 1.98.** The new `chunks_exact_to_as_chunks`
|
|
210
|
+
lint failed the workspace check at eight decode sites.
|
|
211
|
+
- **The remote-config licence probe can report `Active`.** It looked for
|
|
212
|
+
a bare `reflevel` under a group named `main`; a V6 exposes `reflevel0`
|
|
213
|
+
in its receiver block, so the probe always answered `NotLicensed`. It
|
|
214
|
+
now writes the discovered block, as the retune path does.
|
|
215
|
+
- **`HttpSource` built outside a Tokio runtime errors at build.** It used
|
|
216
|
+
to panic inside `init()` when it spawned its reader.
|
|
217
|
+
- **`HttpSink` with `buffer_size(0)` no longer spins forever**; the size
|
|
218
|
+
is clamped to one.
|
|
219
|
+
- **A quiet dwell in hop mode is an empty read**, not a read error
|
|
220
|
+
counted toward the source-dead bailout.
|
|
221
|
+
- **The first packet after a failed initial connection is not flagged as
|
|
222
|
+
an overrun**; nothing was lost.
|
|
223
|
+
- **seify: `sample_rate()` reports the observed rate**, and the range
|
|
224
|
+
starts at the ladder's 120 kS/s floor instead of 10 kHz.
|
|
225
|
+
- **C API: `aaronia_get_error_message` takes an `int`** and answers
|
|
226
|
+
unknown codes, where an out-of-range enum was undefined behaviour. C
|
|
227
|
+
callers are unaffected (an enum converts to `int`); a Rust caller of
|
|
228
|
+
the `ffi` module's function passes `code as i32`.
|
|
229
|
+
- **Native SDK:** `aaronia_source_read_samples_timeout` honours its
|
|
230
|
+
deadline (it polled for up to 500 ms regardless); spectra reads honour
|
|
231
|
+
the packet stride; an SDK call that returns no object is an error
|
|
232
|
+
rather than a null pointer; opening a second device on a live source
|
|
233
|
+
is refused; the device is closed on drop.
|
|
234
|
+
- **RTSA files:** a negative DSFT stream offset no longer overflows the
|
|
235
|
+
header search, and `seek_to_sample` works on reverse-order files.
|
|
236
|
+
- **Spectrum decompression caps the output size** taken from packet
|
|
237
|
+
metadata.
|
|
238
|
+
|
|
239
|
+
### Performance
|
|
240
|
+
- The stream parser drops an over-cap payload as it arrives instead of
|
|
241
|
+
buffering it; uncompressed spectra packets decode without a copy;
|
|
242
|
+
spectra file reads decode in place; the C API reserves the caller's
|
|
243
|
+
length before a read; `sample_rate_hz` is an atomic load, so stamping
|
|
244
|
+
it on every packet takes no lock.
|
|
245
|
+
|
|
246
|
+
### Changed
|
|
247
|
+
- **Byte-rate helpers answer `Option<f64>`, never a `0.0` sentinel.**
|
|
248
|
+
`required_byte_rate`, `max_sustainable_span` and friends returned
|
|
249
|
+
`0.0` for "unknown / nothing fits", a convention every comparison site
|
|
250
|
+
had to remember — `required <= measured` on a garbage rate reads
|
|
251
|
+
"fits". `None` now makes the compiler enforce it, and
|
|
252
|
+
`StreamFormat::iq_bytes_per_sample` returns `Option<usize>` (`None`
|
|
253
|
+
for JSON) for the same reason. New `max_sustainable_sample_rate_below`
|
|
254
|
+
answers remedy queries anchored to the device's reported rate.
|
|
255
|
+
- **The link-budget verdict is data, not just a log line.** New
|
|
256
|
+
`LinkBudgetVerdict` (measured rate, requirement, fit span, shortness)
|
|
257
|
+
is published as `StreamStats::link_budget`, so a GUI or orchestrator
|
|
258
|
+
can auto-narrow the span without scraping logs; the stream-gap warning
|
|
259
|
+
restates the verdict's actual figures from it. `StreamFormat` gained
|
|
260
|
+
`CAPTURE_DEFAULT`, the one definition behind `HttpSourceBuilder`'s
|
|
261
|
+
default, `StreamStats::default` and `DEFAULT_LINK_FORMAT`, which were
|
|
262
|
+
three separately written `Int16` literals.
|
|
263
|
+
- **One copy each of the RTSA client plumbing.** The probe re-used
|
|
264
|
+
hand-rolled copies of the endpoints client's construction, auth
|
|
265
|
+
application, base-URL validation, `/stream` query serialization and
|
|
266
|
+
status-to-error mapping — five drift risks (the three reqwest clients
|
|
267
|
+
had already drifted to three different setting subsets). All are
|
|
268
|
+
shared now: `AuthMethod::apply_to` owns the `RToken` header,
|
|
269
|
+
`rtsa_client_builder` owns the client settings (the streaming client
|
|
270
|
+
gains the keepalive/nodelay/HTTP-1.1 compatibility settings the
|
|
271
|
+
endpoints client always had), and `StreamParams::stream_url`
|
|
272
|
+
serializes every `/stream` URL, including `HttpSource`'s.
|
|
273
|
+
- **`LinkBudgetVerdict::judge` is the verdict's one producer.**
|
|
274
|
+
Requirement, shortness-against-tolerance and the remedy rung were
|
|
275
|
+
assembled by hand at each caller; the constructor now owns that
|
|
276
|
+
arithmetic (so the struct's invariants — fit fields populated only on
|
|
277
|
+
a shortfall, span matching the fit rate — hold by construction), the
|
|
278
|
+
verdict carries `bytes_per_sample` for report text, and
|
|
279
|
+
`ThroughputMeasurement::max_sustainable_span_hz` takes the stream
|
|
280
|
+
format instead of silently assuming int16. The no-rung-fits warning
|
|
281
|
+
cites the device ladder's actual last rung rather than a hardcoded
|
|
282
|
+
1/512.
|
|
283
|
+
- **`HttpSource`'s stream-open failure is the typed error the rest of
|
|
284
|
+
the crate returns.** A non-success status on `/stream` is now
|
|
285
|
+
`Error::Http { status, context }` — the same variant the endpoints
|
|
286
|
+
client and the probe produce, with `context` naming the stream URL —
|
|
287
|
+
instead of `Error::Protocol("Stream endpoint returned error: …")`.
|
|
288
|
+
Anything matching on that text will need the new form.
|
|
289
|
+
- **`work()`'s output copy is two bulk `copy_from_slice` calls** over the
|
|
290
|
+
deque's contiguous halves instead of a `pop_front` per sample, whose
|
|
291
|
+
per-element wrap-around check the compiler cannot lift. Factored into
|
|
292
|
+
`copy_out` and unit-tested, including the wrapped-deque case.
|
|
293
|
+
|
|
294
|
+
### Documentation
|
|
295
|
+
- **Measured the HTTP transport on WiFi 7 and recorded where the
|
|
296
|
+
ceiling actually is.** `curl` on `/stream` sustains ~75 MB/s
|
|
297
|
+
(0.6 Gbit/s) station-to-station at a 2.4 Gbps PHY — both ends on air
|
|
298
|
+
halves the medium — and the figure is the same for all three wire
|
|
299
|
+
formats, so the encoding is not the limit. Two parallel connections
|
|
300
|
+
measured less in aggregate (63.9 vs 74 MB/s), so a single connection
|
|
301
|
+
is optimal and nothing smarter is available to the client. Against
|
|
302
|
+
that, the crate's full framing-plus-decode path measures ~3 GB/s and
|
|
303
|
+
the individual decoders 0.8–10 GS/s: the parser has fortyfold
|
|
304
|
+
headroom and is never the bottleneck. What the link buys at 4 bytes a
|
|
305
|
+
sample is ~19 MS/s — the 15.36 MS/s rung fits, 30.72 does not, and
|
|
306
|
+
full span (246 MB/s) needs a wired path to the RTSA host. Two ignored
|
|
307
|
+
throughput-meter tests (`decode_throughput_meter`,
|
|
308
|
+
`framing_throughput_meter`) keep these numbers re-measurable in one
|
|
309
|
+
command.
|
|
310
|
+
- README and PLUGINS pinned `sdr-aaronia-rs = "0.6"`, a major behind
|
|
311
|
+
the crate, so nothing they describe resolved. Now `"0.7"`.
|
|
312
|
+
- `cumulative_drops` counts client-detected timestamp gaps; four docs
|
|
313
|
+
called them server-reported drops. Bandwidth figures now use the
|
|
314
|
+
61.44 MS/s top rate rather than the 92 MHz clock. The examples table
|
|
315
|
+
and the QUICKSTART verification link were corrected.
|
|
316
|
+
|
|
317
|
+
## [v0.7.6] - 2026-08-15
|
|
318
|
+
|
|
319
|
+
All of this is about the FutureSDR `HttpSource` block (the `futuresdr`
|
|
320
|
+
feature), whose streaming path turned out to be losing most of the
|
|
321
|
+
stream. Found and measured against a live SPECTRAN V6 ECO running a
|
|
322
|
+
49 MHz survey for a trunking decoder.
|
|
323
|
+
|
|
324
|
+
### Fixed
|
|
325
|
+
- **The sample buffer guillotined every packet bigger than itself.**
|
|
326
|
+
The capacity trim runs as each packet lands, and with capacity fixed
|
|
327
|
+
at `buffer_size * 2` a device sending 49k-sample packets into a
|
|
328
|
+
16384-sample capacity lost ~75% of every packet before the consumer
|
|
329
|
+
was offered any of it. Measured live: 61.4 MS/s at the device,
|
|
330
|
+
0.33 MS/s reaching the pipeline, and digital decode unable to hold
|
|
331
|
+
frame sync. The capacity now floors at four times the largest packet
|
|
332
|
+
observed, learned from the stream; the floor only ever raises the
|
|
333
|
+
configured value.
|
|
334
|
+
- **The initial tune could claim success while the device never
|
|
335
|
+
moved.** `/control` answers `success=true` whether or not any block
|
|
336
|
+
applies the command — the same device has been measured both
|
|
337
|
+
honouring and ignoring the identical full-tuple payload in different
|
|
338
|
+
mission states. The block's start-up tune now writes the real field
|
|
339
|
+
names (`centerfreq0`, `decimation0`, `reflevel0`) via `/remoteconfig`
|
|
340
|
+
to the block discovered by walking the config tree, then reads them
|
|
341
|
+
back and warns about anything that did not take. Where no such block
|
|
342
|
+
exists (an IQ-demodulator mission exposes different fields), it falls
|
|
343
|
+
back to the old `/control` capture command and says the result is
|
|
344
|
+
unverified. The tune is one-shot: stream restarts after an external
|
|
345
|
+
retune no longer re-push the source's stale target over it.
|
|
346
|
+
- **Launching an app no longer overwrites the operator's gain.** The
|
|
347
|
+
builder's reference level was a bare number pushed on every start, so
|
|
348
|
+
merely starting a flowgraph reset the device to the builder default.
|
|
349
|
+
It is now optional and left untouched unless the caller set one.
|
|
350
|
+
- **`work()` spun.** The runtime ran it whenever the output port had
|
|
351
|
+
any room — measured at 106,000–390,000 calls a second averaging 14
|
|
352
|
+
free samples each — burning ~60% of a core and starving the
|
|
353
|
+
downstream block. The output port now requires a worthwhile block of
|
|
354
|
+
room before `work()` runs; call rate dropped to ~14/s.
|
|
355
|
+
|
|
356
|
+
### Changed
|
|
357
|
+
- **The HTTP socket is drained by a dedicated task**, not inside
|
|
358
|
+
`work()`. The socket used to be read only while the scheduler happened
|
|
359
|
+
to run the block, which capped throughput at a tenth of what `curl`
|
|
360
|
+
pulls from the same endpoint. A background task now pushes chunks
|
|
361
|
+
into a bounded channel (~10 MB), whose fill is the backpressure point:
|
|
362
|
+
consumer falls behind → channel fills → reader blocks → TCP flow
|
|
363
|
+
control stops the server. Measured at a 49 MHz span: 1.8 → ~7–9 MS/s.
|
|
364
|
+
The honest ceiling is the link (~57 MB/s ≈ 14 MS/s as float32);
|
|
365
|
+
61.44 MS/s over this transport is 2 Gbit/s and not reachable.
|
|
366
|
+
- Buffer-overflow drops are now counted and logged geometrically
|
|
367
|
+
rather than once per occurrence, which at wide span was 5040 log
|
|
368
|
+
lines in 25 seconds for one piece of information.
|
|
369
|
+
- File playback decodes little-endian cf32 with a zero-copy read
|
|
370
|
+
straight into the sample buffer; the explicit per-sample decode
|
|
371
|
+
remains for big-endian hosts, and a round-trip test pins the two to
|
|
372
|
+
identical output.
|
|
373
|
+
|
|
374
|
+
### Fixed (in review, before this release shipped)
|
|
375
|
+
- The refill loop kept iterating through its 50 ms idle sleeps when the
|
|
376
|
+
channel was empty, holding samples already in the buffer for up to
|
|
377
|
+
800 ms before producing them. It now flushes immediately and sleeps
|
|
378
|
+
only when there is nothing to flush.
|
|
379
|
+
- A parse error mid-stream reconnected without reaping the reader task,
|
|
380
|
+
which on a stalled socket keeps holding a server connection — on the
|
|
381
|
+
free licence, the one connection the reconnect needs.
|
|
382
|
+
- Two comments overstated the `/control` finding as "this endpoint does
|
|
383
|
+
not carry these fields on a Spectran V6". Measured today, the same
|
|
384
|
+
device honours the identical full-tuple payload — the accurate claim
|
|
385
|
+
is that `success=true` proves nothing either way, which is the reason
|
|
386
|
+
the verified `/remoteconfig` path exists. Where no `centerfreq0`
|
|
387
|
+
block is found, the initial tune now falls back to `/control` rather
|
|
388
|
+
than silently not tuning (an IQ-demodulator mission exposes
|
|
389
|
+
`centerfreq`, not `centerfreq0`).
|
|
390
|
+
|
|
391
|
+
### Added
|
|
392
|
+
- `iq_sample_rate_for_decimation_index`, `decimation_index_for_rate`
|
|
393
|
+
and `decimation_index_for_bandwidth` in `utils`: the RTSA "Span"
|
|
394
|
+
enum (`Full` … `1 / 512`) mapped onto the sample-rate ladder and
|
|
395
|
+
back, so a requested span becomes the enum index the device actually
|
|
396
|
+
takes.
|
|
397
|
+
- `HttpEndpointsClient::apply_capture_config` — retune via
|
|
398
|
+
`/remoteconfig` with read-back confirmation — plus
|
|
399
|
+
`find_block_name_with_field`, which discovers the receiver block by
|
|
400
|
+
the field the write will target instead of assuming its category.
|
|
401
|
+
|
|
7
402
|
## [v0.7.5] - 2026-08-13
|
|
8
403
|
|
|
9
404
|
### Added
|
|
@@ -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.7.
|
|
3030
|
+
version = "0.7.7"
|
|
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.7.
|
|
3559
|
+
version = "0.7.7"
|
|
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.7.
|
|
5
|
+
version = "0.7.7"
|
|
6
6
|
edition = "2024"
|
|
7
7
|
repository = "https://github.com/isaacbentley/sdr-aaronia-rs"
|
|
8
8
|
readme = "README.md"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-aaronia
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.7
|
|
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
|
```
|
|
@@ -36,6 +36,9 @@ selects a backend and presents the same interface either way.
|
|
|
36
36
|
input enumeration and the configuration tree.
|
|
37
37
|
- **Plugs into FutureSDR** with `HttpSource` and `HttpSink` flowgraph
|
|
38
38
|
blocks.
|
|
39
|
+
- **Says whether the link can carry the span** before a capture is
|
|
40
|
+
wasted: `link_budget` measures the path end to end and names the
|
|
41
|
+
widest span on the device's decimation ladder that fits it.
|
|
39
42
|
|
|
40
43
|
## Installation
|
|
41
44
|
|
|
@@ -44,11 +47,11 @@ Add the following to your `Cargo.toml`:
|
|
|
44
47
|
```toml
|
|
45
48
|
[dependencies]
|
|
46
49
|
# By default, includes HTTP, File, native sdr-source trait, and C FFI backend support
|
|
47
|
-
sdr-aaronia-rs = "0.
|
|
50
|
+
sdr-aaronia-rs = "0.7"
|
|
48
51
|
tokio = { version = "1.43", features = ["rt-multi-thread", "macros"] }
|
|
49
52
|
|
|
50
53
|
# To enable additional backends, opt into their features (e.g. native-sdk, futuresdr)
|
|
51
|
-
# sdr-aaronia-rs = { version = "0.
|
|
54
|
+
# sdr-aaronia-rs = { version = "0.7", features = ["native-sdk", "futuresdr"] }
|
|
52
55
|
```
|
|
53
56
|
|
|
54
57
|
## Quickstart
|
|
@@ -114,7 +114,7 @@ directly and skip the SoapySDR layer.
|
|
|
114
114
|
- `readStream` honours `timeoutUs` and returns partial reads within the
|
|
115
115
|
deadline, per the SoapySDR contract.
|
|
116
116
|
- Retuning mid-stream is safe and requires no Aaronia licence.
|
|
117
|
-
-
|
|
117
|
+
- Gaps detected in the stream are counted by `readSensor("cumulative_drops")`.
|
|
118
118
|
An overrun sets `SOAPY_SDR_END_ABRUPT` on the affected read.
|
|
119
119
|
- TX is hardware-unverified and exists only when the plugin is built
|
|
120
120
|
against the native SDK on Windows or Linux.
|
|
@@ -375,7 +375,7 @@ Contains the actual measurement data.
|
|
|
375
375
|
| `mPacketStartTime` | `double` | Start time of this chunk relative to stream start. |
|
|
376
376
|
| `mPacketEndTime` | `double` | End time of this chunk relative to stream start. |
|
|
377
377
|
| `mPacketFlags` | `quint32` | Packet flags (`DSPPF_*`). |
|
|
378
|
-
| `mSampleSize` | `quint32` |
|
|
378
|
+
| `mSampleSize` | `quint32` | Values per sample, not bytes: 2 for an IQ pair, the bin count for a spectrum. The captures under `tests/` carry 2 (float32 IQ) and 1024 (spectra), the same meaning as the HTTP stream's `sampleSize`. The byte stride of a sample is this count times the width of `mSampleType`. |
|
|
379
379
|
| `mSampleDepth` | `quint32` | Depth of a sample. |
|
|
380
380
|
| `mNumSamples` | `quint32` | Number of samples in this packet. |
|
|
381
381
|
| **Payload** | `binary` | The payload contains the actual sample data, formatted as specified by the header fields. |
|
|
@@ -302,7 +302,12 @@ from `/remoteconfig` first if you need to target one block.
|
|
|
302
302
|
|
|
303
303
|
These are one-off requests. Aaronia's own words: if they cannot be
|
|
304
304
|
executed, or conflict with the local configuration, the behaviour is
|
|
305
|
-
undefined — nothing reports the conflict.
|
|
305
|
+
undefined — nothing reports the conflict. That is not theoretical: the
|
|
306
|
+
same device has been measured both honouring and ignoring an identical
|
|
307
|
+
full-tuple capture command in different mission states, answering
|
|
308
|
+
`success=true` both times. A retune that must be provable goes through
|
|
309
|
+
`/remoteconfig` and reads the value back
|
|
310
|
+
(`HttpEndpointsClient::apply_capture_config` does exactly this).
|
|
306
311
|
|
|
307
312
|
**The settings each `type` accepts**, per Aaronia support (January
|
|
308
313
|
2024), which goes well beyond the published specification:
|
|
@@ -200,14 +200,16 @@ instead of issuing PUTs directly. No Aaronia licence is involved: the
|
|
|
200
200
|
crate tunes through `/control`, which needs none.
|
|
201
201
|
|
|
202
202
|
**Network saturation at high sample rates.**
|
|
203
|
-
Float32
|
|
203
|
+
Float32 needs roughly 490 MB/s at the 61.44 MS/s top rate. On anything other than
|
|
204
204
|
localhost, use the `I16` wire format via
|
|
205
205
|
`AaroniaConfig::low_bandwidth_mode()` or the `format=I16` SoapySDR
|
|
206
206
|
device argument to halve that. This changes the wire format itself,
|
|
207
207
|
unlike a client-side `CS16` conversion.
|
|
208
208
|
|
|
209
209
|
**Dropped samples.**
|
|
210
|
-
`cumulative_drops()`
|
|
210
|
+
`cumulative_drops()` counts the timestamp gaps the client has detected
|
|
211
|
+
in the stream (one per gap, however many samples it spanned), and
|
|
212
|
+
`take_overrun()` flags the read that follows one.
|
|
211
213
|
Steady growth means the link or the consumer cannot keep pace. Reduce
|
|
212
214
|
the sample rate, switch to `I16`, or read in larger blocks.
|
|
213
215
|
|
|
@@ -231,5 +233,5 @@ let mut source = AaroniaSource::new(config).await?;
|
|
|
231
233
|
|
|
232
234
|
Build with `--features native-sdk`. Aaronia does not ship a macOS SDK,
|
|
233
235
|
so this path is unavailable there. The native-SDK paths are
|
|
234
|
-
hardware-unverified in this project; see the verification table in
|
|
235
|
-
[
|
|
236
|
+
hardware-unverified in this project; see the verification table in
|
|
237
|
+
[VERIFICATION.md](VERIFICATION.md).
|
|
@@ -49,7 +49,7 @@ The Aaronia RTSA Vendor SDK provides low-level, high-performance access to Aaron
|
|
|
49
49
|
* **Real-time Data Access**: Packet-based streaming of IQ samples and spectrum data with configurable decimation
|
|
50
50
|
* **Device Modes**: Verified support for multiple operational modes:
|
|
51
51
|
* **SpectranV6**: `spectranv6/raw`, `spectranv6/iqreceiver`, `spectranv6/iqtransceiver`, `spectranv6/iqtransmitter`, `spectranv6/sweepsa`
|
|
52
|
-
* **SpectranV6 ECO**: `spectranv6eco/raw
|
|
52
|
+
* **SpectranV6 ECO**: `spectranv6eco/rtsa` (its raw pipeline; there is no `spectranv6eco/raw`), `spectranv6eco/iqreceiver`, `spectranv6eco/iqtransceiver`, `spectranv6eco/iqtransmitter`, `spectranv6eco/sweepsa`
|
|
53
53
|
* **Configuration Tree**: Hierarchical configuration system with verified parameters:
|
|
54
54
|
* **Clock Rates** (V6): 46MHz (46.08), 61MHz (61.44), 76MHz (76.80), 92MHz (92.16), 122MHz (122.88), 184MHz (184.32), 245MHz (245.76), 492MHz (491.52) — see the full label table under [Rust Binding Notes](#rust-binding-notes)
|
|
55
55
|
* **Clock Rates** (V6 ECO): fixed at 92.16 MHz. Its top IQ rate is 61.44 MHz, which is that clock over 1.5 — the two are easy to confuse and this document once recorded the rate as the clock
|