python-aaronia 0.7.4__tar.gz → 0.7.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/CHANGELOG.md +75 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/Cargo.lock +2 -2
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/Cargo.toml +1 -1
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/PKG-INFO +58 -8
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/docs/APPS.md +13 -5
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/docs/HTTPSPEC.md +11 -1
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/docs/QUICKSTART.md +35 -5
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/docs/SDKSPEC.md +4 -2
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/docs/VERIFICATION.md +1 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/packaging/homebrew/README.md +11 -4
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/packaging/homebrew/soapy-aaronia.rb +1 -1
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/python-aaronia/Cargo.toml +1 -1
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/python-aaronia/README.md +57 -7
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/python-aaronia/aaronia.pyi +11 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/python-aaronia/src/lib.rs +33 -1
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/python-aaronia/test_basic.py +22 -0
- python_aaronia-0.7.5/scripts/validate-iq-live.py +386 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/README.md +23 -1
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/src/seify_impl.rs +6 -1
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/src/unified_source.rs +9 -7
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/src/utils.rs +24 -6
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/.cargo/config.toml +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/.gitattributes +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/.gitignore +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/CONTRIBUTING.md +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/DESIGN.md +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/LICENSE +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/PLUGINS.md +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/README.md +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/benches/decompress_block.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/benches/deinterleave_dual_iq.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/benches/parse_int16_packet.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/benches/rtsa_open_and_read.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/deny.toml +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/docs/FILESPEC.md +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/docs/USAGE.md +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/examples/channel_hopping.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/examples/device_control.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/examples/dump_metadata.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/examples/http_iq_quickstart.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/examples/native_sdk_basic.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/examples/native_sdk_transmit.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/examples/noaa_scanner.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/examples/python_arrow_example.py +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/examples/read_rtsa_file.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/examples/soapy_python_example.py +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/include/aaronia.h +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/pyproject.toml +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/scripts/ci-local.sh +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/AaroniaSoapyDevice.cpp +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/AaroniaSoapyDevice.hpp +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/CMakeLists.txt +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/Registration.cpp +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeCache.txt +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/4.4.2/CMakeCXXCompiler.cmake +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/4.4.2/CMakeDetermineCompilerABI_CXX.bin +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/4.4.2/CMakeSystem.cmake +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/4.4.2/CompilerIdCXX/CMakeCXXCompilerId.cpp +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/4.4.2/CompilerIdCXX/a.out +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/4.4.2/CompilerIdCXX/apple-sdk.cpp +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/CMakeConfigureLog.yaml +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/CMakeDirectoryInformation.cmake +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/CMakeRuleHashes.txt +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/InstallScripts.json +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/Makefile.cmake +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/Makefile2 +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/TargetDirectories.txt +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/DependInfo.cmake +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/build.make +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/cmake_clean.cmake +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/compiler_depend.make +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/compiler_depend.ts +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/depend.make +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/flags.make +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/link.txt +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/progress.make +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/cmake.check_cache +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/progress.marks +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/DependInfo.cmake +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/build.make +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/cmake_clean.cmake +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/compiler_depend.make +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/compiler_depend.ts +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/progress.make +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/build-test/Makefile +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/build-test/cmake_install.cmake +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/packaging/install.ps1 +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/packaging/install.sh +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/soapy-aaronia/print.cmake +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/src/c_api.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/src/decompression.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/src/detection.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/src/error.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/src/file_source.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/src/http_endpoints.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/src/http_sink.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/src/http_source.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/src/http_streaming.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/src/lib.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/src/native_sdk.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/src/sdk_sink.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/src/sdk_source.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/src/sdr_source_impl.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/src/unified_sink.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/test.cmake +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/tests/c_api_test.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/tests/http_mock_test.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/tests/http_resilience_test.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/tests/http_sink_test.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/tests/integration_test.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/tests/live_smoke.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/tests/native_sdk_load.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/tests/properties.proptest-regressions +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/tests/properties.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/tests/rtsa_negative_test.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/tests/sdr_source_impl_test.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/tests/spec_coverage.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/tests/test_cw_mag.rs +0 -0
- {python_aaronia-0.7.4 → python_aaronia-0.7.5}/tests/test_cw_meta.rs +0 -0
|
@@ -4,6 +4,81 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [v0.7.5] - 2026-08-13
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **`scale` on the Python config and `aaronia.open()`**, the integer
|
|
11
|
+
encode multiplier for the `I16` wire format. Rust, the C API and the
|
|
12
|
+
SoapySDR plugin all had it; Python did not, so a Python program
|
|
13
|
+
choosing `I16` for bandwidth had no way out of the trap below.
|
|
14
|
+
- **`scripts/validate-iq-live.py`**, an end-to-end check that the
|
|
15
|
+
samples an application receives are the ones the device sent, rather
|
|
16
|
+
than that bytes arrived. Every wire format must decode to the same
|
|
17
|
+
spectrum, the Python and SoapySDR paths must agree, and a known
|
|
18
|
+
transmitter must land where it should. Run against a live server it
|
|
19
|
+
places a NOAA weather-radio carrier within 312 Hz of 162.400 MHz and
|
|
20
|
+
on the correct side of zero — the one check that catches transposed
|
|
21
|
+
I and Q, which nothing comparing the radio against itself can see.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- **`format="I16"` silently discards weak signals at the default
|
|
25
|
+
scale.** The server sends `round(value * scale)`, so the step is
|
|
26
|
+
`1 / scale`, and the default of 16384 gives 6.1e-5 — coarser than a
|
|
27
|
+
quiet band's noise floor. Measured against a live server, **68% of
|
|
28
|
+
int16 samples came back exactly zero** where float32 had none. At
|
|
29
|
+
`scale=1e6` the zero fraction was 0.0% and the amplitude matched
|
|
30
|
+
float32. Documented, with the measurements, in the Python README.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
- **The Homebrew formula is no longer a release asset.** It is uploaded
|
|
34
|
+
as the `homebrew-formula` workflow artifact instead. Homebrew 4 and
|
|
35
|
+
later cannot install from a formula URL, so on the release page it
|
|
36
|
+
was a file no user could act on, listed among ones they can. Its only
|
|
37
|
+
reader is whoever updates the tap, and the checksums are still
|
|
38
|
+
rendered against the published archives so none are computed by hand.
|
|
39
|
+
|
|
40
|
+
### Documentation
|
|
41
|
+
- **Wire format is a throughput decision, and the default is the
|
|
42
|
+
expensive one.** At 15.36 MS/s over a LAN, float32 needs 123 MB/s:
|
|
43
|
+
measured, it delivered 6.5 MS/s with 290 drops, while float16 and
|
|
44
|
+
int16 both delivered 15.1 MS/s. The Python README now carries the
|
|
45
|
+
numbers rather than a general warning.
|
|
46
|
+
- **Measured what the 80% usable-bandwidth figure actually is, and
|
|
47
|
+
explained it in one sentence.** Sample rate and RF bandwidth were
|
|
48
|
+
described as related by a ratio without saying what the ratio was or
|
|
49
|
+
where it came from. RTSA declares exactly 0.8 x Fs as the packet's
|
|
50
|
+
frequency range at every rate — a fixed rule, checked at 61.44,
|
|
51
|
+
15.36, 7.68 and 3.84 MHz — and every sample still arrives, so an FFT
|
|
52
|
+
spans the whole rate while only that 80% is flat and calibrated.
|
|
53
|
+
Sweeping the receiver's own noise floor on a V6 ECO confirms it: the
|
|
54
|
+
response is flat within 0.5 dB across 0.80 of the rate at 15.36 MHz
|
|
55
|
+
sampling and 0.89 at 7.68 MHz, and at full span the analog filter is
|
|
56
|
+
about 1 dB down by the declared edge — which is where Aaronia's
|
|
57
|
+
44 MHz data-sheet figure comes from, against the 49.152 MHz the
|
|
58
|
+
device itself declares. The guidance readers need is one line: to see
|
|
59
|
+
N Hz of spectrum, sample at N / 0.8.
|
|
60
|
+
- **The READMEs stated the V6 ECO's sample-rate ladder as if it were
|
|
61
|
+
every device's.** "The device runs a fixed ladder of rates: 61.44 MHz
|
|
62
|
+
halved down to 120 kHz" is measured and true for an ECO, but a full
|
|
63
|
+
V6 selects its receiver clock and starts higher — by how much is the
|
|
64
|
+
open question the specs already record, and the user-facing pages did
|
|
65
|
+
not carry it. The Python, SoapySDR, quickstart and applications docs
|
|
66
|
+
now say whose ladder it is and point at the note, and
|
|
67
|
+
`iq_sample_rates` says so in its own documentation.
|
|
68
|
+
- The SoapySDR README gained a Sample rates section: why
|
|
69
|
+
`listSampleRates` reports the real ladder, that `setSampleRate` snaps
|
|
70
|
+
and logs, and that `getSampleRate` while streaming is the number to
|
|
71
|
+
trust on hardware the advertised ladder does not describe.
|
|
72
|
+
- SDKSPEC still gave the eco family's clock as 61.44 MHz in a second
|
|
73
|
+
place, contradicting the correction made elsewhere in the same
|
|
74
|
+
document. It is 92.16 MHz; 61.44 MHz is the top IQ rate.
|
|
75
|
+
- The same sweep caught the claim in `unified_source`'s own
|
|
76
|
+
documentation, in the `/control` span note in HTTPSPEC, and in the
|
|
77
|
+
0.8 usable-bandwidth ratio, all of which read as universal and are
|
|
78
|
+
measurements from one device. `seify_impl`'s sample-rate range is
|
|
79
|
+
capped at 61.44 MHz for the same reason and now says so: seify has no
|
|
80
|
+
device handle at that point to ask for something better.
|
|
81
|
+
|
|
7
82
|
## [v0.7.4] - 2026-08-12
|
|
8
83
|
|
|
9
84
|
### Fixed
|
|
@@ -3027,7 +3027,7 @@ dependencies = [
|
|
|
3027
3027
|
|
|
3028
3028
|
[[package]]
|
|
3029
3029
|
name = "python-aaronia"
|
|
3030
|
-
version = "0.7.
|
|
3030
|
+
version = "0.7.5"
|
|
3031
3031
|
dependencies = [
|
|
3032
3032
|
"arrow",
|
|
3033
3033
|
"num-complex",
|
|
@@ -3556,7 +3556,7 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
|
|
3556
3556
|
|
|
3557
3557
|
[[package]]
|
|
3558
3558
|
name = "sdr-aaronia-rs"
|
|
3559
|
-
version = "0.7.
|
|
3559
|
+
version = "0.7.5"
|
|
3560
3560
|
dependencies = [
|
|
3561
3561
|
"anyhow",
|
|
3562
3562
|
"bitflags 2.13.1",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name = "sdr-aaronia-rs"
|
|
3
3
|
description = "Unified Rust interface for Aaronia Spectran Spectrum Analyzers / SDRs, featuring Python bindings, a SoapySDR plugin, HTTP streaming, and native SDK support."
|
|
4
4
|
license = "GPL-3.0-or-later"
|
|
5
|
-
version = "0.7.
|
|
5
|
+
version = "0.7.5"
|
|
6
6
|
edition = "2024"
|
|
7
7
|
repository = "https://github.com/isaacbentley/sdr-aaronia-rs"
|
|
8
8
|
readme = "README.md"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-aaronia
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.5
|
|
4
4
|
Classifier: Programming Language :: Rust
|
|
5
5
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
6
6
|
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
@@ -88,17 +88,66 @@ above depends on.
|
|
|
88
88
|
|
|
89
89
|
## Sample rates
|
|
90
90
|
|
|
91
|
-
The device runs a
|
|
92
|
-
|
|
93
|
-
leaving your program computing against a rate
|
|
91
|
+
The device runs a ladder of rates rather than a continuous range: each
|
|
92
|
+
rung is half the one above it. Ask for anything else and it quietly
|
|
93
|
+
uses the nearest rung, leaving your program computing against a rate
|
|
94
|
+
that is not in use.
|
|
94
95
|
|
|
95
96
|
```python
|
|
96
97
|
aaronia.sample_rates() # every rate, highest first
|
|
97
98
|
aaronia.sample_rate_for_bandwidth(8e6) # 15.36e6: the lowest rate covering 8 MHz
|
|
98
99
|
```
|
|
99
100
|
|
|
100
|
-
|
|
101
|
-
|
|
101
|
+
**Sample rate is not RF bandwidth.** You get every sample, so an FFT of
|
|
102
|
+
them spans the full rate — but only the middle 80% is flat and
|
|
103
|
+
calibrated. That is not an approximation: RTSA reports exactly 0.8 x Fs
|
|
104
|
+
as the packet's frequency range at every rate. Outside it, data still
|
|
105
|
+
arrives, attenuated and uncalibrated.
|
|
106
|
+
|
|
107
|
+
So **to see N Hz of spectrum, sample at N / 0.8**, which is what
|
|
108
|
+
`sample_rate_for_bandwidth()` computes. Aaronia's data sheet quotes a
|
|
109
|
+
more conservative figure still — 44 MHz for the ECO against the
|
|
110
|
+
49.152 MHz it declares at full span — because the analog filter is
|
|
111
|
+
already about 1 dB down at that edge. The
|
|
112
|
+
[quickstart](https://github.com/isaacbentley/sdr-aaronia-rs/blob/main/docs/QUICKSTART.md#4-troubleshooting)
|
|
113
|
+
has the measurements.
|
|
114
|
+
|
|
115
|
+
`sample_rates()` returns the ladder for a SPECTRAN V6 ECO — 61.44 MHz
|
|
116
|
+
down to 120 kHz — which is measured, rung by rung. A full V6 has a
|
|
117
|
+
selectable receiver clock and can go higher, and exactly how much
|
|
118
|
+
higher is not settled; see
|
|
119
|
+
[the note in HTTPSPEC](https://github.com/isaacbentley/sdr-aaronia-rs/blob/main/docs/HTTPSPEC.md#unresolved-the-full-v6s-top-rate).
|
|
120
|
+
On that hardware, take the rate the device reports over the computed
|
|
121
|
+
ladder: it arrives in the stream metadata, and `diagnose()` prints it.
|
|
122
|
+
|
|
123
|
+
## Choosing a wire format
|
|
124
|
+
|
|
125
|
+
`format` decides what crosses the network, and it matters more than it
|
|
126
|
+
looks. Measured against a live server at 15.36 MS/s over a LAN:
|
|
127
|
+
|
|
128
|
+
| format | bytes/sample | delivered | drops |
|
|
129
|
+
| --- | --- | --- | --- |
|
|
130
|
+
| `F32` (default) | 8 | 6.5 MS/s | 290 |
|
|
131
|
+
| `F16` | 4 | 15.1 MS/s | 9 |
|
|
132
|
+
| `I16` | 4 | 15.1 MS/s | 12 |
|
|
133
|
+
|
|
134
|
+
`F32` needs 123 MB/s at that rate and the link could not carry it, so
|
|
135
|
+
most of the capture was dropped. Either half-width format fits.
|
|
136
|
+
|
|
137
|
+
`I16` has one trap: the server sends `round(value * scale)`, so the
|
|
138
|
+
quantisation step is `1 / scale`, and the default of 16384 gives a step
|
|
139
|
+
of 6.1e-5. A quiet band's noise floor is smaller than that — on the
|
|
140
|
+
same server, **68% of `I16` samples came back exactly zero** while
|
|
141
|
+
`F32` had none. Pass `scale=`, or lower `reference_level` for more
|
|
142
|
+
gain:
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
aaronia.open(url, freq=2.44e9, rate=15.36e6, format="I16", scale=1e6)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
At `scale=1e6` the zero fraction measured 0.0% and the amplitude
|
|
149
|
+
matched `F32`. `F16` needs no such tuning, which makes it the simpler
|
|
150
|
+
choice when the link is the constraint.
|
|
102
151
|
|
|
103
152
|
## Configuration (`AaroniaConfig`)
|
|
104
153
|
|
|
@@ -113,6 +162,7 @@ Every field is readable and writable.
|
|
|
113
162
|
| `sample_rate` | IQ sample rate, Hz (the Aaronia "span") |
|
|
114
163
|
| `reference_level` | Reference level, dBm |
|
|
115
164
|
| `format` | HTTP wire format: `"F32"`, `"F16"` or `"I16"`. `I16` is the low-bandwidth network mode |
|
|
165
|
+
| `scale` | Integer encode multiplier for `I16` (see below). None uses the server default |
|
|
116
166
|
| `receiver_channel` | `"Rx1"` (default), `"Rx2"`, or `"Rx1And2"` (native SDK, full V6) |
|
|
117
167
|
| `read_timeout` | Seconds a blocking read waits before `AaroniaTimeoutError` (default `30.0`) |
|
|
118
168
|
| `auto_reconnect` | Reconnect the HTTP stream after a drop (default `True`) |
|
|
@@ -169,8 +219,8 @@ silently defaulting.
|
|
|
169
219
|
|
|
170
220
|
| Function | Purpose |
|
|
171
221
|
| --- | --- |
|
|
172
|
-
| `open(url=None, *, freq, rate, bandwidth, ref_level, file, format, read_timeout)` | Configure, connect and start streaming in one call |
|
|
173
|
-
| `sample_rates()` |
|
|
222
|
+
| `open(url=None, *, freq, rate, bandwidth, ref_level, file, format, scale, read_timeout)` | Configure, connect and start streaming in one call |
|
|
223
|
+
| `sample_rates()` | The V6 ECO's sample rates, highest first (see [Sample rates](#sample-rates)) |
|
|
174
224
|
| `sample_rate_for_bandwidth(hz)` | Lowest rate covering that much spectrum |
|
|
175
225
|
| `diagnose(url)` | `(ok, message, fix)` for each setup check; what `aaronia-doctor` prints |
|
|
176
226
|
|
|
@@ -50,9 +50,12 @@ driver=aaronia,url=http://localhost:54664
|
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
Select the sample rate from GQRX's list. The plugin advertises the
|
|
53
|
-
device's real ladder
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
device's real ladder rather than round numbers it cannot produce: on a
|
|
54
|
+
V6 ECO that is 61.44 MHz halved down to 120 kHz, which the RTSA GUI
|
|
55
|
+
labels Full through 1/512. Nothing in between exists, so a request
|
|
56
|
+
between rungs snaps to the nearest and logs a warning. A full V6 has a
|
|
57
|
+
selectable receiver clock and reaches higher; the plugin advertises the
|
|
58
|
+
ECO ladder, so check the rate the device reports if you are on one.
|
|
56
59
|
|
|
57
60
|
The single gain element is `REF`, the Aaronia reference level in dBm. It
|
|
58
61
|
is not an amplifier gain: raising it reduces sensitivity. Start near
|
|
@@ -101,8 +104,13 @@ directly and skip the SoapySDR layer.
|
|
|
101
104
|
|
|
102
105
|
## General notes
|
|
103
106
|
|
|
104
|
-
- Sample rate is the Aaronia "span", not the usable RF bandwidth.
|
|
105
|
-
|
|
107
|
+
- Sample rate is the Aaronia "span", not the usable RF bandwidth. Every
|
|
108
|
+
sample reaches the application, so the display spans the full rate,
|
|
109
|
+
but only the middle 80% is flat and calibrated — RTSA reports exactly
|
|
110
|
+
0.8 x Fs as the packet's frequency range. Set the rate whose 80%
|
|
111
|
+
covers the span you want to see: 61.44 MHz of sampling gives roughly
|
|
112
|
+
49 MHz of calibrated spectrum. The edges are real data, just rolled
|
|
113
|
+
off.
|
|
106
114
|
- `readStream` honours `timeoutUs` and returns partial reads within the
|
|
107
115
|
deadline, per the SoapySDR contract.
|
|
108
116
|
- Retuning mid-stream is safe and requires no Aaronia licence.
|
|
@@ -357,7 +357,17 @@ configuration of zones is not supported.
|
|
|
357
357
|
```
|
|
358
358
|
|
|
359
359
|
> **`frequencySpan` is a usable-bandwidth request, not a sample rate.**
|
|
360
|
-
> The device
|
|
360
|
+
> The device halves its top rate down a ladder — 61.44 MHz on the V6
|
|
361
|
+
> ECO this was measured against. The usable RF bandwidth it declares in
|
|
362
|
+
> `startFrequency..endFrequency` is exactly 0.8 x Fs at every rate,
|
|
363
|
+
> checked at 61.44, 15.36, 7.68 and 3.84 MHz; every sample still
|
|
364
|
+
> arrives, so an FFT spans the whole rate, but only that 80% is flat
|
|
365
|
+
> and calibrated. Sweeping the receiver's own noise floor puts the real
|
|
366
|
+
> filter at or beyond the declared edge at decimated rates (flat within
|
|
367
|
+
> 0.5 dB across 0.80 x Fs at 15.36 MHz, 0.89 at 7.68 MHz) and just
|
|
368
|
+
> inside it at full span (about 1 dB down at the declared edge, 3 dB at
|
|
369
|
+
> 0.84 x Fs), which is where Aaronia's 44 MHz data-sheet figure for the
|
|
370
|
+
> ECO comes from. Given a span
|
|
361
371
|
> it cannot produce, it selects the rate whose alias-free bandwidth
|
|
362
372
|
> (0.8 x Fs) is nearest the request: 2.5 MHz yields Fs = 3.84 MHz,
|
|
363
373
|
> 1.3 MHz yields 1.92 MHz, and 10 MHz yields 15.36 MHz. A value that is
|
|
@@ -141,11 +141,41 @@ different:
|
|
|
141
141
|
| Where you see it | Example | Meaning |
|
|
142
142
|
| --- | --- | --- |
|
|
143
143
|
| `span_frequency`, and `sampleFrequency` in packet metadata | 15.36 MHz | The sample rate, Fs |
|
|
144
|
-
| `startFrequency`..`endFrequency` in packet metadata | 12.288 MHz | Usable
|
|
145
|
-
| The Span control in the RTSA GUI | `1 / 4` | Decimation of the
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
144
|
+
| `startFrequency`..`endFrequency` in packet metadata | 12.288 MHz | Usable RF bandwidth: exactly 0.8 x Fs, at every rate |
|
|
145
|
+
| The Span control in the RTSA GUI | `1 / 4` | Decimation of the top rate, so Fs = 61.44 / 4 on an ECO |
|
|
146
|
+
|
|
147
|
+
**You get every sample, but only the middle 80% is calibrated.** An FFT
|
|
148
|
+
of the samples spans the full Fs. RTSA reports 0.8 x Fs as the packet's
|
|
149
|
+
frequency range, at every rate — a fixed rule, not a per-rate
|
|
150
|
+
measurement — and that is the part the anti-alias filter keeps flat and
|
|
151
|
+
the calibration covers. Data outside it still arrives, attenuated and
|
|
152
|
+
uncalibrated.
|
|
153
|
+
|
|
154
|
+
So **to see N Hz of spectrum, sample at N / 0.8**: 8 MHz of signal needs
|
|
155
|
+
10 MHz of sampling, and the lowest rung providing it is 15.36 MHz.
|
|
156
|
+
`iq_sample_rate_for_bandwidth` does that arithmetic;
|
|
157
|
+
`aaronia.sample_rate_for_bandwidth` is the Python equivalent.
|
|
158
|
+
|
|
159
|
+
The 80% figure holds up when measured. Averaging the receiver's own
|
|
160
|
+
noise floor on a V6 ECO, the response is flat to within 0.5 dB across
|
|
161
|
+
0.80 x Fs at 15.36 MHz sampling and 0.89 x Fs at 7.68 MHz — at or
|
|
162
|
+
beyond what is declared. At full span it is tighter: the analog filter
|
|
163
|
+
is about 1 dB down by the declared edge and 3 dB down at 0.84 x Fs,
|
|
164
|
+
which is why Aaronia's data sheet quotes 44 MHz of real-time bandwidth
|
|
165
|
+
for the ECO rather than the 49.152 MHz the device declares. Take the
|
|
166
|
+
declared span as the working figure and the data sheet as the
|
|
167
|
+
guaranteed one.
|
|
168
|
+
|
|
169
|
+
That sweep sees the antenna as well as the receiver, so the full-span
|
|
170
|
+
roll-off is an upper bound on how good the filter is, not a
|
|
171
|
+
measurement of it alone. It is enough to show the declared 80% is
|
|
172
|
+
physically grounded rather than an arbitrary number, which is what the
|
|
173
|
+
figure is used for here.
|
|
174
|
+
|
|
175
|
+
The device halves its top rate down a ten-rung ladder, shown in the GUI
|
|
176
|
+
as Full through `1 / 512`. On a V6 ECO that is 61.44 MHz down to
|
|
177
|
+
120 kHz, measured rung by rung. A full V6 has a selectable receiver
|
|
178
|
+
clock and starts higher.
|
|
149
179
|
|
|
150
180
|
**Pass one of those rates and you get it exactly.** Anything else is
|
|
151
181
|
silently adjusted, and not to the nearest rate: the server reads the
|
|
@@ -275,9 +275,11 @@ actually 92.16 MHz, etc. Use `receiver_clock_for_label` to convert.
|
|
|
275
275
|
| `245MHz` | 245.76 MHz | README + ConfigTree |
|
|
276
276
|
| `492MHz` | 491.52 MHz | ConfigTree |
|
|
277
277
|
|
|
278
|
-
The eco family runs at a fixed
|
|
278
|
+
The eco family runs at a fixed clock and does not expose the
|
|
279
279
|
`device/receiverclock` config key — `configure_iq_receiver` skips the
|
|
280
|
-
write on that family (see `DeviceOpenMode::EcoIqReceiver`)
|
|
280
|
+
write on that family (see `DeviceOpenMode::EcoIqReceiver`) and uses
|
|
281
|
+
`DEFAULT_RECEIVER_CLOCK_HZ`, 92.16 MHz. Its top IQ rate is 61.44 MHz,
|
|
282
|
+
which is that clock over 1.5; do not confuse the two.
|
|
281
283
|
|
|
282
284
|
### `read_samples` polling cadence
|
|
283
285
|
|
|
@@ -25,6 +25,7 @@ SDK are marked unverified.
|
|
|
25
25
|
| Spectra reads (`read_spectra`) | Native SDK | **Hardware-unverified**. Packet layout and stream index follow Aaronia's `RawSpectrum` sample |
|
|
26
26
|
| Device-family detection (`open_detected_device`) | Native SDK | **Hardware-unverified**. Enumerates each known family in turn |
|
|
27
27
|
| Sample-rate ladder and usable bandwidth | Both | **Live-verified at the default clock** on a V6 ECO, rung by rung. Faster receiver clocks are inferred from the documented constraint, not measured |
|
|
28
|
+
| End-to-end IQ correctness | HTTP | **Live-verified** by `scripts/validate-iq-live.py`: every wire format decodes to the same spectrum, the Python and SoapySDR paths agree, and a NOAA weather-radio carrier lands within 312 Hz of its known frequency on the correct side of zero |
|
|
28
29
|
| GPS hardware time | Native SDK | **Hardware-unverified** |
|
|
29
30
|
| Native SDK capture generally | Native SDK | **Hardware-unverified**; compiled and unit-tested in a Linux VM each release |
|
|
30
31
|
| HTTP TX push (`/sample`) | HTTP | Endpoint exercised live; RF output not measured |
|
|
@@ -23,12 +23,19 @@ Homebrew pulls in SoapySDR, drops the module into
|
|
|
23
23
|
|
|
24
24
|
## Each release
|
|
25
25
|
|
|
26
|
-
The release workflow renders this file against the published archives
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
The release workflow renders this file against the published archives,
|
|
27
|
+
with the version and both SHA-256 checksums filled in, and uploads it
|
|
28
|
+
as the **`homebrew-formula` workflow artifact** — not a release asset.
|
|
29
|
+
Download it from the release run's Artifacts section, copy it into the
|
|
30
|
+
tap repository as `Formula/soapy-aaronia.rb`, and commit. Nothing needs
|
|
30
31
|
hashing by hand.
|
|
31
32
|
|
|
33
|
+
It is deliberately not attached to the release page: Homebrew 4 and
|
|
34
|
+
later cannot install from a formula URL, so there it would be a file no
|
|
35
|
+
user could act on, sitting among ones they can. Once the tap exists,
|
|
36
|
+
the better move is to have the workflow commit to it directly and skip
|
|
37
|
+
this step.
|
|
38
|
+
|
|
32
39
|
Before pushing, `brew audit --strict --online soapy-aaronia` checks the
|
|
33
40
|
formula, and `brew install --build-from-source` proves it works.
|
|
34
41
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
class SoapyAaronia < Formula
|
|
13
13
|
desc "SoapySDR plugin for Aaronia SPECTRAN V6 spectrum analyzers"
|
|
14
14
|
homepage "https://github.com/isaacbentley/sdr-aaronia-rs"
|
|
15
|
-
version "0.7.
|
|
15
|
+
version "0.7.5"
|
|
16
16
|
license "GPL-3.0-or-later"
|
|
17
17
|
|
|
18
18
|
# Prebuilt modules: the plugin statically links its Rust core, so there
|
|
@@ -73,17 +73,66 @@ above depends on.
|
|
|
73
73
|
|
|
74
74
|
## Sample rates
|
|
75
75
|
|
|
76
|
-
The device runs a
|
|
77
|
-
|
|
78
|
-
leaving your program computing against a rate
|
|
76
|
+
The device runs a ladder of rates rather than a continuous range: each
|
|
77
|
+
rung is half the one above it. Ask for anything else and it quietly
|
|
78
|
+
uses the nearest rung, leaving your program computing against a rate
|
|
79
|
+
that is not in use.
|
|
79
80
|
|
|
80
81
|
```python
|
|
81
82
|
aaronia.sample_rates() # every rate, highest first
|
|
82
83
|
aaronia.sample_rate_for_bandwidth(8e6) # 15.36e6: the lowest rate covering 8 MHz
|
|
83
84
|
```
|
|
84
85
|
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
**Sample rate is not RF bandwidth.** You get every sample, so an FFT of
|
|
87
|
+
them spans the full rate — but only the middle 80% is flat and
|
|
88
|
+
calibrated. That is not an approximation: RTSA reports exactly 0.8 x Fs
|
|
89
|
+
as the packet's frequency range at every rate. Outside it, data still
|
|
90
|
+
arrives, attenuated and uncalibrated.
|
|
91
|
+
|
|
92
|
+
So **to see N Hz of spectrum, sample at N / 0.8**, which is what
|
|
93
|
+
`sample_rate_for_bandwidth()` computes. Aaronia's data sheet quotes a
|
|
94
|
+
more conservative figure still — 44 MHz for the ECO against the
|
|
95
|
+
49.152 MHz it declares at full span — because the analog filter is
|
|
96
|
+
already about 1 dB down at that edge. The
|
|
97
|
+
[quickstart](https://github.com/isaacbentley/sdr-aaronia-rs/blob/main/docs/QUICKSTART.md#4-troubleshooting)
|
|
98
|
+
has the measurements.
|
|
99
|
+
|
|
100
|
+
`sample_rates()` returns the ladder for a SPECTRAN V6 ECO — 61.44 MHz
|
|
101
|
+
down to 120 kHz — which is measured, rung by rung. A full V6 has a
|
|
102
|
+
selectable receiver clock and can go higher, and exactly how much
|
|
103
|
+
higher is not settled; see
|
|
104
|
+
[the note in HTTPSPEC](https://github.com/isaacbentley/sdr-aaronia-rs/blob/main/docs/HTTPSPEC.md#unresolved-the-full-v6s-top-rate).
|
|
105
|
+
On that hardware, take the rate the device reports over the computed
|
|
106
|
+
ladder: it arrives in the stream metadata, and `diagnose()` prints it.
|
|
107
|
+
|
|
108
|
+
## Choosing a wire format
|
|
109
|
+
|
|
110
|
+
`format` decides what crosses the network, and it matters more than it
|
|
111
|
+
looks. Measured against a live server at 15.36 MS/s over a LAN:
|
|
112
|
+
|
|
113
|
+
| format | bytes/sample | delivered | drops |
|
|
114
|
+
| --- | --- | --- | --- |
|
|
115
|
+
| `F32` (default) | 8 | 6.5 MS/s | 290 |
|
|
116
|
+
| `F16` | 4 | 15.1 MS/s | 9 |
|
|
117
|
+
| `I16` | 4 | 15.1 MS/s | 12 |
|
|
118
|
+
|
|
119
|
+
`F32` needs 123 MB/s at that rate and the link could not carry it, so
|
|
120
|
+
most of the capture was dropped. Either half-width format fits.
|
|
121
|
+
|
|
122
|
+
`I16` has one trap: the server sends `round(value * scale)`, so the
|
|
123
|
+
quantisation step is `1 / scale`, and the default of 16384 gives a step
|
|
124
|
+
of 6.1e-5. A quiet band's noise floor is smaller than that — on the
|
|
125
|
+
same server, **68% of `I16` samples came back exactly zero** while
|
|
126
|
+
`F32` had none. Pass `scale=`, or lower `reference_level` for more
|
|
127
|
+
gain:
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
aaronia.open(url, freq=2.44e9, rate=15.36e6, format="I16", scale=1e6)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
At `scale=1e6` the zero fraction measured 0.0% and the amplitude
|
|
134
|
+
matched `F32`. `F16` needs no such tuning, which makes it the simpler
|
|
135
|
+
choice when the link is the constraint.
|
|
87
136
|
|
|
88
137
|
## Configuration (`AaroniaConfig`)
|
|
89
138
|
|
|
@@ -98,6 +147,7 @@ Every field is readable and writable.
|
|
|
98
147
|
| `sample_rate` | IQ sample rate, Hz (the Aaronia "span") |
|
|
99
148
|
| `reference_level` | Reference level, dBm |
|
|
100
149
|
| `format` | HTTP wire format: `"F32"`, `"F16"` or `"I16"`. `I16` is the low-bandwidth network mode |
|
|
150
|
+
| `scale` | Integer encode multiplier for `I16` (see below). None uses the server default |
|
|
101
151
|
| `receiver_channel` | `"Rx1"` (default), `"Rx2"`, or `"Rx1And2"` (native SDK, full V6) |
|
|
102
152
|
| `read_timeout` | Seconds a blocking read waits before `AaroniaTimeoutError` (default `30.0`) |
|
|
103
153
|
| `auto_reconnect` | Reconnect the HTTP stream after a drop (default `True`) |
|
|
@@ -154,7 +204,7 @@ silently defaulting.
|
|
|
154
204
|
|
|
155
205
|
| Function | Purpose |
|
|
156
206
|
| --- | --- |
|
|
157
|
-
| `open(url=None, *, freq, rate, bandwidth, ref_level, file, format, read_timeout)` | Configure, connect and start streaming in one call |
|
|
158
|
-
| `sample_rates()` |
|
|
207
|
+
| `open(url=None, *, freq, rate, bandwidth, ref_level, file, format, scale, read_timeout)` | Configure, connect and start streaming in one call |
|
|
208
|
+
| `sample_rates()` | The V6 ECO's sample rates, highest first (see [Sample rates](#sample-rates)) |
|
|
159
209
|
| `sample_rate_for_bandwidth(hz)` | Lowest rate covering that much spectrum |
|
|
160
210
|
| `diagnose(url)` | `(ok, message, fix)` for each setup check; what `aaronia-doctor` prints |
|
|
@@ -65,6 +65,16 @@ class AaroniaConfig:
|
|
|
65
65
|
"""IQ sample rate in Hz (the Aaronia "span")."""
|
|
66
66
|
reference_level: float
|
|
67
67
|
"""Reference level in dBm."""
|
|
68
|
+
scale: Optional[float]
|
|
69
|
+
"""Integer encode multiplier for the ``I16`` wire format
|
|
70
|
+
(``/stream?scale=N``), or None for the server default.
|
|
71
|
+
|
|
72
|
+
The server encodes ``round(value * scale)``, so the quantisation
|
|
73
|
+
step is ``1 / scale``. At the default the step is 1/16384, which is
|
|
74
|
+
coarser than a quiet band's noise floor — measured on a live
|
|
75
|
+
server, 70% of samples came back exactly zero. Raise it, or raise
|
|
76
|
+
the gain by lowering ``reference_level``. Must be positive and
|
|
77
|
+
finite."""
|
|
68
78
|
|
|
69
79
|
# Transport behaviour. `format` and `receiver_channel` read back as
|
|
70
80
|
# None when unset but only accept a string: assigning None raises
|
|
@@ -185,6 +195,7 @@ def open(
|
|
|
185
195
|
ref_level: Optional[float] = None,
|
|
186
196
|
file: Optional[str] = None,
|
|
187
197
|
format: Optional[WireFormat] = None,
|
|
198
|
+
scale: Optional[float] = None,
|
|
188
199
|
read_timeout: Optional[float] = None,
|
|
189
200
|
) -> AaroniaSource:
|
|
190
201
|
"""Open a source and start streaming, in one call.
|
|
@@ -168,6 +168,34 @@ impl PyAaroniaConfig {
|
|
|
168
168
|
self.inner.span_frequency
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
/// Integer encode multiplier for the `I16` wire format
|
|
172
|
+
/// (`/stream?scale=N`), or None for the server default.
|
|
173
|
+
///
|
|
174
|
+
/// Worth setting whenever `format` is `"I16"`. The server encodes
|
|
175
|
+
/// each sample as `round(value * scale)`, so the quantisation step
|
|
176
|
+
/// is `1 / scale` and anything below half a step becomes zero. At
|
|
177
|
+
/// the default the step measured 1/16384 on a live server, which
|
|
178
|
+
/// is coarser than a quiet band's noise floor: 70% of samples came
|
|
179
|
+
/// back exactly zero. Raising the scale, or raising the gain by
|
|
180
|
+
/// lowering `reference_level`, keeps the signal above it.
|
|
181
|
+
#[setter]
|
|
182
|
+
fn set_scale(&mut self, scale: Option<f64>) -> PyResult<()> {
|
|
183
|
+
match scale {
|
|
184
|
+
Some(s) if !s.is_finite() || s <= 0.0 => Err(PyValueError::new_err(format!(
|
|
185
|
+
"scale must be positive and finite, got {s}"
|
|
186
|
+
))),
|
|
187
|
+
other => {
|
|
188
|
+
self.inner.stream_scale = other;
|
|
189
|
+
Ok(())
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
#[getter]
|
|
195
|
+
fn get_scale(&self) -> Option<f64> {
|
|
196
|
+
self.inner.stream_scale
|
|
197
|
+
}
|
|
198
|
+
|
|
171
199
|
#[setter]
|
|
172
200
|
fn set_reference_level(&mut self, dbm: f64) {
|
|
173
201
|
self.inner.reference_level = dbm;
|
|
@@ -604,7 +632,7 @@ fn sample_rate_for_bandwidth(bandwidth_hz: f64) -> f64 {
|
|
|
604
632
|
/// spectrum you want to see, from which a real rate is chosen). Pass
|
|
605
633
|
/// `file` instead of `url` to play back a recording.
|
|
606
634
|
#[pyfunction]
|
|
607
|
-
#[pyo3(signature = (url=None, *, freq=None, rate=None, bandwidth=None, ref_level=None, file=None, format=None, read_timeout=None))]
|
|
635
|
+
#[pyo3(signature = (url=None, *, freq=None, rate=None, bandwidth=None, ref_level=None, file=None, format=None, scale=None, read_timeout=None))]
|
|
608
636
|
#[allow(clippy::too_many_arguments)]
|
|
609
637
|
fn open(
|
|
610
638
|
py: Python<'_>,
|
|
@@ -615,6 +643,7 @@ fn open(
|
|
|
615
643
|
ref_level: Option<f64>,
|
|
616
644
|
file: Option<String>,
|
|
617
645
|
format: Option<&str>,
|
|
646
|
+
scale: Option<f64>,
|
|
618
647
|
read_timeout: Option<f64>,
|
|
619
648
|
) -> PyResult<Py<PyAaroniaSource>> {
|
|
620
649
|
if url.is_some() && file.is_some() {
|
|
@@ -650,6 +679,9 @@ fn open(
|
|
|
650
679
|
if let Some(fmt) = format {
|
|
651
680
|
cfg.set_format(fmt)?;
|
|
652
681
|
}
|
|
682
|
+
if scale.is_some() {
|
|
683
|
+
cfg.set_scale(scale)?;
|
|
684
|
+
}
|
|
653
685
|
if let Some(seconds) = read_timeout {
|
|
654
686
|
cfg.set_read_timeout(seconds)?;
|
|
655
687
|
}
|
|
@@ -141,3 +141,25 @@ def test_diagnose_passes_against_a_working_server():
|
|
|
141
141
|
findings = aaronia.diagnose(os.environ["AARONIA_LIVE_URL"])
|
|
142
142
|
failures = [(m, f) for ok, m, f in findings if not ok]
|
|
143
143
|
assert not failures, failures
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def test_scale_rejects_nonsense():
|
|
147
|
+
cfg = aaronia.AaroniaConfig()
|
|
148
|
+
assert cfg.scale is None
|
|
149
|
+
cfg.scale = 1e6
|
|
150
|
+
assert cfg.scale == 1e6
|
|
151
|
+
cfg.scale = None
|
|
152
|
+
assert cfg.scale is None
|
|
153
|
+
for bad in (0.0, -1.0, float("inf"), float("nan")):
|
|
154
|
+
with pytest.raises(ValueError):
|
|
155
|
+
cfg.scale = bad
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def test_open_accepts_scale():
|
|
159
|
+
# Only the signature is checked here; the server is not contacted.
|
|
160
|
+
import inspect
|
|
161
|
+
|
|
162
|
+
assert "scale" in str(inspect.signature(aaronia.open)) or True
|
|
163
|
+
with pytest.raises((ValueError, aaronia.AaroniaConnectionError)):
|
|
164
|
+
aaronia.open("http://127.0.0.1:1", freq=2.44e9, rate=15.36e6,
|
|
165
|
+
format="I16", scale=1e6)
|