python-aaronia 0.12.0__tar.gz → 0.13.1__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.12.0 → python_aaronia-0.13.1}/CHANGELOG.md +64 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/Cargo.lock +12 -12
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/Cargo.toml +5 -1
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/PKG-INFO +33 -30
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/README.md +36 -2
- python_aaronia-0.13.1/docs/NATIVE_SDK_COMPATIBILITY.md +99 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/docs/SDKSPEC.md +37 -43
- python_aaronia-0.13.1/examples/native_sdk_qualification.rs +94 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/include/spectran.h +7 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/python-aaronia/Cargo.toml +1 -1
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/python-aaronia/README.md +32 -29
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/python-aaronia/aaronia.pyi +16 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/python-aaronia/src/lib.rs +86 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/python-aaronia/test_basic.py +26 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/scripts/ci-local.sh +1 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/soapy-aaronia/README.md +27 -23
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/soapy-aaronia/Registration.cpp +15 -1
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/soapy-aaronia/SpectranSoapyDevice.cpp +32 -8
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/src/c_api.rs +133 -0
- python_aaronia-0.13.1/src/capabilities.rs +206 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/src/error.rs +12 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/src/http_endpoints.rs +12 -97
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/src/http_streaming.rs +7 -1
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/src/lib.rs +2 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/src/native_sdk.rs +491 -109
- python_aaronia-0.13.1/src/native_sdk_lifecycle_tests.rs +439 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/src/sdk_source.rs +5 -12
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/src/seify_impl.rs +32 -12
- python_aaronia-0.13.1/src/stream_geometry.rs +125 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/src/unified_source.rs +256 -29
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/tests/spec_coverage.rs +85 -14
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/.cargo/config.toml +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/.gitattributes +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/.gitignore +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/CONTRIBUTING.md +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/DESIGN.md +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/LICENSE +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/PLUGINS.md +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/benches/decompress_block.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/benches/deinterleave_dual_iq.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/benches/parse_int16_packet.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/benches/rtsa_open_and_read.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/deny.toml +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/docs/APPS.md +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/docs/FILESPEC.md +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/docs/HTTPSPEC.md +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/docs/QUICKSTART.md +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/docs/SYNC.md +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/docs/USAGE.md +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/docs/VERIFICATION.md +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/examples/device_control.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/examples/dump_metadata.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/examples/http_iq_quickstart.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/examples/native_sdk_basic.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/examples/native_sdk_transmit.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/examples/noaa_scanner.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/examples/python_arrow_example.py +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/examples/read_rtsa_file.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/examples/soapy_python_example.py +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/packaging/homebrew/README.md +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/packaging/homebrew/soapy-aaronia.rb +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/pyproject.toml +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/scripts/fake-rtsa-server.py +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/scripts/native-sdk-validate.ps1 +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/scripts/sdk-container-test.sh +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/scripts/validate-iq-live.py +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/soapy-aaronia/CMakeLists.txt +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/soapy-aaronia/SpectranSoapyDevice.hpp +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/soapy-aaronia/packaging/install.ps1 +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/soapy-aaronia/packaging/install.sh +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/src/decompression.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/src/detection.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/src/file_source.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/src/http_sink.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/src/http_source.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/src/link_budget.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/src/sdk_sink.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/src/stream_break.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/src/unified_sink.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/src/utils.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/tests/abi_drift.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/tests/c_api_test.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/tests/http_mock_test.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/tests/http_resilience_test.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/tests/http_sink_test.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/tests/integration_test.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/tests/live_smoke.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/tests/native_sdk_live.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/tests/native_sdk_load.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/tests/properties.proptest-regressions +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/tests/properties.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/tests/rtsa_negative_test.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/tests/test_cw_mag.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/tests/test_cw_meta.rs +0 -0
- {python_aaronia-0.12.0 → python_aaronia-0.13.1}/tests/wire_contract.rs +0 -0
|
@@ -4,6 +4,70 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [v0.13.1] - 2026-10-08
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- `NativeSdkSource::configure_sweepsa_with_reference_level` applies an optional
|
|
11
|
+
reference level to native spectrum sweeps, backported from Specola's vendor
|
|
12
|
+
update. Explicit levels require valid SDK bounds and are checked before any
|
|
13
|
+
configuration write; unsupported/missing settings fail instead of being ignored.
|
|
14
|
+
The existing `SweepsaConfig` layout and `configure_sweepsa` API remain compatible.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## [v0.13.0] - 2026-10-08
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- Native IQ packet RF span and centre metadata, independent of the actual IQ
|
|
21
|
+
sample clock. Mono and dual reads preserve that geometry with carried samples.
|
|
22
|
+
- Direct SDK capability queries: configuration bounds, clock/channel/decimation
|
|
23
|
+
options with their original enum indices and disabled flags, selected mode,
|
|
24
|
+
and SDK version. Missing metadata stays unknown; no RTSA app is required.
|
|
25
|
+
- Receive-only `NativeReceiverOptions`: explicit family/mode, IQ receiver RF
|
|
26
|
+
span, or enabled raw clock/decimation selection. SDK configuration bounds are
|
|
27
|
+
checked before writes. They are not a hardware qualification certificate.
|
|
28
|
+
- Versioned capability JSON and RF-span controls in C and Python; device-derived
|
|
29
|
+
ranges and observed RF bandwidth in seify and SoapySDR. Windows/Linux Python
|
|
30
|
+
release wheels now enable `native-sdk` (the proprietary SDK remains separate).
|
|
31
|
+
- An opt-in, serial-selected `native_sdk_qualification` example for receive-only
|
|
32
|
+
span changes and repeated device stop/reopen checks.
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
- Failed SDK opens close any partially allocated device. Connect/start failures
|
|
36
|
+
attempt stop, disconnect and close while preserving the original error.
|
|
37
|
+
- Stop attempts all cleanup steps, returns the first failure, clears carried IQ
|
|
38
|
+
and observed metadata, and retains a failed-close handle for a Drop retry.
|
|
39
|
+
Repeated start/stop calls are idempotent; unified restart reopens the same
|
|
40
|
+
selected serial and settings, without silently switching devices or backend.
|
|
41
|
+
- SDK global initialization/shutdown reference counting is serialized across the
|
|
42
|
+
initialized-state transition.
|
|
43
|
+
- IQ receiver span validation uses the opened mode's `main/spanfreq` bounds,
|
|
44
|
+
rather than the legacy V6 raw-clock constraint. Runtime native setters use the
|
|
45
|
+
same configuration path as construction.
|
|
46
|
+
- Reads stop before a queued packet changes centre, IQ rate or RF span, and a
|
|
47
|
+
carry-only read does not wait for another SDK packet.
|
|
48
|
+
- Native capabilities and sensors no longer come from an unrelated HTTP server.
|
|
49
|
+
- Unknown SDK modes cannot reinterpret spectra as IQ or IQ as spectra.
|
|
50
|
+
- SoapySDR tolerates an unknown discrete sample-rate ladder and reports native
|
|
51
|
+
bandwidth from packet metadata. An RX open no longer attempts TX setup.
|
|
52
|
+
- Release publication is gated on full CI, with semver checked against the
|
|
53
|
+
previous reachable release instead of a fixed historical tag.
|
|
54
|
+
|
|
55
|
+
### Migration and qualification
|
|
56
|
+
- This 0.x minor release adds public config fields and SDK mode enum variants.
|
|
57
|
+
Build `SpectranConfig` with `..Default::default()` and review exhaustive mode
|
|
58
|
+
matches. Existing C struct layouts and functions are retained.
|
|
59
|
+
- Explicit `spectranv6eco/raw` is preserved; automatic ECO selection remains
|
|
60
|
+
`iqreceiver`. Explicit raw clock/decimation controls replace the legacy rate
|
|
61
|
+
request; the actual delivered rate comes from IQ packets.
|
|
62
|
+
- Soapy TX setup requires explicit `tx=true`, a native backend and compatible
|
|
63
|
+
licensed hardware. TX and dual-RX remain hardware-unverified here.
|
|
64
|
+
- Windows ECO mono qualification passed five stop/reopen cycles at RF spans
|
|
65
|
+
44, 20, 10, 5 and 44 MHz. The 44 MHz span carried about 59.214 MS/s; the lower
|
|
66
|
+
spans carried 30, 15 and 7.5 MS/s. One initial discontinuity was reported, with
|
|
67
|
+
no additional discontinuities during this short trial. Signals was restored.
|
|
68
|
+
This is not an endurance, amplitude-calibration or all-model certification.
|
|
69
|
+
See [native SDK compatibility](docs/NATIVE_SDK_COMPATIBILITY.md).
|
|
70
|
+
|
|
7
71
|
## [v0.12.0] - 2026-09-29
|
|
8
72
|
|
|
9
73
|
### Removed
|
|
@@ -1288,7 +1288,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
1288
1288
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
|
1289
1289
|
dependencies = [
|
|
1290
1290
|
"libc",
|
|
1291
|
-
"windows-sys 0.
|
|
1291
|
+
"windows-sys 0.61.2",
|
|
1292
1292
|
]
|
|
1293
1293
|
|
|
1294
1294
|
[[package]]
|
|
@@ -2065,7 +2065,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
|
|
|
2065
2065
|
dependencies = [
|
|
2066
2066
|
"hermit-abi",
|
|
2067
2067
|
"libc",
|
|
2068
|
-
"windows-sys 0.
|
|
2068
|
+
"windows-sys 0.61.2",
|
|
2069
2069
|
]
|
|
2070
2070
|
|
|
2071
2071
|
[[package]]
|
|
@@ -3049,7 +3049,7 @@ dependencies = [
|
|
|
3049
3049
|
|
|
3050
3050
|
[[package]]
|
|
3051
3051
|
name = "python-aaronia"
|
|
3052
|
-
version = "0.
|
|
3052
|
+
version = "0.13.1"
|
|
3053
3053
|
dependencies = [
|
|
3054
3054
|
"arrow",
|
|
3055
3055
|
"num-complex",
|
|
@@ -3120,7 +3120,7 @@ dependencies = [
|
|
|
3120
3120
|
"once_cell",
|
|
3121
3121
|
"socket2",
|
|
3122
3122
|
"tracing",
|
|
3123
|
-
"windows-sys 0.
|
|
3123
|
+
"windows-sys 0.61.2",
|
|
3124
3124
|
]
|
|
3125
3125
|
|
|
3126
3126
|
[[package]]
|
|
@@ -3426,14 +3426,14 @@ dependencies = [
|
|
|
3426
3426
|
"errno",
|
|
3427
3427
|
"libc",
|
|
3428
3428
|
"linux-raw-sys",
|
|
3429
|
-
"windows-sys 0.
|
|
3429
|
+
"windows-sys 0.61.2",
|
|
3430
3430
|
]
|
|
3431
3431
|
|
|
3432
3432
|
[[package]]
|
|
3433
3433
|
name = "rustls"
|
|
3434
|
-
version = "0.23.
|
|
3434
|
+
version = "0.23.45"
|
|
3435
3435
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3436
|
-
checksum = "
|
|
3436
|
+
checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634"
|
|
3437
3437
|
dependencies = [
|
|
3438
3438
|
"aws-lc-rs",
|
|
3439
3439
|
"once_cell",
|
|
@@ -3483,7 +3483,7 @@ dependencies = [
|
|
|
3483
3483
|
"security-framework",
|
|
3484
3484
|
"security-framework-sys",
|
|
3485
3485
|
"webpki-root-certs",
|
|
3486
|
-
"windows-sys 0.
|
|
3486
|
+
"windows-sys 0.61.2",
|
|
3487
3487
|
]
|
|
3488
3488
|
|
|
3489
3489
|
[[package]]
|
|
@@ -3578,7 +3578,7 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
|
|
3578
3578
|
|
|
3579
3579
|
[[package]]
|
|
3580
3580
|
name = "sdr-aaronia-rs"
|
|
3581
|
-
version = "0.
|
|
3581
|
+
version = "0.13.1"
|
|
3582
3582
|
dependencies = [
|
|
3583
3583
|
"anyhow",
|
|
3584
3584
|
"bitflags 2.13.1",
|
|
@@ -4158,10 +4158,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
4158
4158
|
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
|
4159
4159
|
dependencies = [
|
|
4160
4160
|
"fastrand",
|
|
4161
|
-
"getrandom 0.4
|
|
4161
|
+
"getrandom 0.3.4",
|
|
4162
4162
|
"once_cell",
|
|
4163
4163
|
"rustix",
|
|
4164
|
-
"windows-sys 0.
|
|
4164
|
+
"windows-sys 0.61.2",
|
|
4165
4165
|
]
|
|
4166
4166
|
|
|
4167
4167
|
[[package]]
|
|
@@ -4819,7 +4819,7 @@ version = "0.1.11"
|
|
|
4819
4819
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
4820
4820
|
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
|
4821
4821
|
dependencies = [
|
|
4822
|
-
"windows-sys 0.
|
|
4822
|
+
"windows-sys 0.61.2",
|
|
4823
4823
|
]
|
|
4824
4824
|
|
|
4825
4825
|
[[package]]
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name = "sdr-aaronia-rs"
|
|
3
3
|
description = "Unified Rust interface for Aaronia Spectran Spectrum Analyzers / SDRs, featuring Python bindings, a SoapySDR plugin, HTTP streaming, and native SDK support."
|
|
4
4
|
license = "GPL-3.0-or-later"
|
|
5
|
-
version = "0.
|
|
5
|
+
version = "0.13.1"
|
|
6
6
|
edition = "2024"
|
|
7
7
|
repository = "https://github.com/isaacbentley/sdr-aaronia-rs"
|
|
8
8
|
readme = "README.md"
|
|
@@ -139,3 +139,7 @@ widestring = { version = "1.1", optional = true }
|
|
|
139
139
|
|
|
140
140
|
[workspace]
|
|
141
141
|
members = ["python-aaronia"]
|
|
142
|
+
|
|
143
|
+
[[example]]
|
|
144
|
+
name = "native_sdk_qualification"
|
|
145
|
+
required-features = ["native-sdk", "http", "file"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-aaronia
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.1
|
|
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+)
|
|
@@ -103,41 +103,39 @@ common fields.
|
|
|
103
103
|
|
|
104
104
|
The
|
|
105
105
|
[quickstart](https://github.com/isaacbentley/sdr-aaronia-rs/blob/main/docs/QUICKSTART.md)
|
|
106
|
-
covers configuring the RTSA-Suite HTTP Server block
|
|
107
|
-
above depends on.
|
|
106
|
+
covers configuring the RTSA-Suite HTTP Server block for HTTP sources. Native SDK sources do not require that server.
|
|
108
107
|
|
|
109
|
-
## Sample
|
|
108
|
+
## Sample rate and RF span
|
|
110
109
|
|
|
111
|
-
The
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
that is not in use.
|
|
110
|
+
The module-level `sample_rates()` and `sample_rate_for_bandwidth()` functions
|
|
111
|
+
remain legacy HTTP/ECO helpers. They are not capabilities of every native device.
|
|
112
|
+
For a native IQ receiver, request RF span separately and inspect packet geometry:
|
|
115
113
|
|
|
116
114
|
```python
|
|
117
|
-
aaronia.
|
|
118
|
-
|
|
115
|
+
cfg = aaronia.SpectranConfig()
|
|
116
|
+
cfg.force_native_sdk = True
|
|
117
|
+
cfg.device_serial = "YOUR_DEVICE_SERIAL"
|
|
118
|
+
cfg.native_mode = "iqreceiver"
|
|
119
|
+
cfg.rf_span_hz = 44e6
|
|
120
|
+
cfg.center_frequency_hz = 280e6
|
|
121
|
+
cfg.reference_level_dbm = -50
|
|
122
|
+
src = aaronia.SpectranSource()
|
|
123
|
+
src.start_streaming(cfg)
|
|
124
|
+
print(src.capabilities()) # schema_version + capabilities
|
|
125
|
+
samples = src.read_samples_numpy(65536)
|
|
126
|
+
print(src.source_info()) # observed rate and RF span
|
|
127
|
+
src.set_rf_span_hz(20e6)
|
|
128
|
+
src.stop_streaming()
|
|
119
129
|
```
|
|
120
130
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
`
|
|
129
|
-
conservative still — 44 MHz for the ECO against the 49.152 MHz it
|
|
130
|
-
declares at full span — because the analog filter is ~1 dB down by that
|
|
131
|
-
edge. The
|
|
132
|
-
[quickstart](https://github.com/isaacbentley/sdr-aaronia-rs/blob/main/docs/QUICKSTART.md#4-troubleshooting)
|
|
133
|
-
has the measurements.
|
|
134
|
-
|
|
135
|
-
`sample_rates()` returns the V6 ECO's ladder, 61.44 MHz down to 120 kHz.
|
|
136
|
-
A full V6 selects its receiver clock and goes higher, by how much is
|
|
137
|
-
unsettled; see
|
|
138
|
-
[the note in HTTPSPEC](https://github.com/isaacbentley/sdr-aaronia-rs/blob/main/docs/HTTPSPEC.md#unresolved-the-full-v6s-top-rate).
|
|
139
|
-
There, trust the rate the device reports in stream metadata, which
|
|
140
|
-
`diagnose()` prints.
|
|
131
|
+
IQ sample clock and valid RF span are distinct. The tested ECO delivered 44 MHz
|
|
132
|
+
RF span at about 59.214 MS/s, and 20 MHz at 30 MS/s. Native configuration bounds
|
|
133
|
+
may exceed the model's rated bandwidth. Neither those bounds nor RF span certifies
|
|
134
|
+
amplitude calibration. Unknown metadata stays unknown. See the
|
|
135
|
+
[qualification matrix](../docs/NATIVE_SDK_COMPATIBILITY.md).
|
|
136
|
+
|
|
137
|
+
For HTTP only, the legacy helper uses the measured 0.8×Fs filter width and ECO
|
|
138
|
+
rate ladder. Prefer `source_info()` for what the stream actually delivered.
|
|
141
139
|
|
|
142
140
|
## Choosing a wire format
|
|
143
141
|
|
|
@@ -178,6 +176,11 @@ Every field is readable and writable.
|
|
|
178
176
|
| `file_path` | Path to a recorded `.rtsa` file; pins the file backend |
|
|
179
177
|
| `device_serial` | Device selection for the native-SDK backend |
|
|
180
178
|
| `force_native_sdk` | `True` pins the source to the native SDK; missing SDK is an error |
|
|
179
|
+
| `native_family` | Optional bare SDK family, e.g. `"spectranv6eco"` |
|
|
180
|
+
| `native_mode` | Optional receive-only `"iqreceiver"` or `"raw"` |
|
|
181
|
+
| `rf_span_hz` | Optional requested native IQ receiver RF span, Hz |
|
|
182
|
+
| `receiver_clock` | Optional enabled raw-mode SDK clock label |
|
|
183
|
+
| `decimation_factor` | Optional raw-mode power-of-two factor |
|
|
181
184
|
| `center_frequency_hz` | Center frequency, Hz |
|
|
182
185
|
| `sample_rate_hz` | IQ sample rate (Fs), Hz |
|
|
183
186
|
| `reference_level_dbm` | Reference level, dBm |
|
|
@@ -240,11 +240,11 @@ Add the following to your `Cargo.toml`:
|
|
|
240
240
|
```toml
|
|
241
241
|
[dependencies]
|
|
242
242
|
# By default, includes HTTP, File and C FFI backend support
|
|
243
|
-
sdr-aaronia-rs = "0.
|
|
243
|
+
sdr-aaronia-rs = "0.13"
|
|
244
244
|
tokio = { version = "1.43", features = ["rt-multi-thread", "macros"] }
|
|
245
245
|
|
|
246
246
|
# To enable additional backends, opt into their features (e.g. native-sdk, futuresdr)
|
|
247
|
-
# sdr-aaronia-rs = { version = "0.
|
|
247
|
+
# sdr-aaronia-rs = { version = "0.13", features = ["native-sdk", "futuresdr"] }
|
|
248
248
|
```
|
|
249
249
|
|
|
250
250
|
HTTP reads use one timeout budget for the entire requested block and return partial data at a timeout, frequency change, sample-rate change, or detected gap. `capture_frequency_hz()` and `capture_sample_rate_hz()` describe the returned samples even when newer packets are queued. File tuning setters preserve the recording's metadata.
|
|
@@ -330,6 +330,40 @@ building from source and the wire-format trade-offs are in
|
|
|
330
330
|
[PLUGINS.md](PLUGINS.md); pass `format=I16` to halve network bandwidth,
|
|
331
331
|
which is a real wire-format change rather than a client-side conversion.
|
|
332
332
|
|
|
333
|
+
### Native SDK geometry and lifecycle
|
|
334
|
+
|
|
335
|
+
Enable `native-sdk` on Windows or Linux and install Aaronia's SDK separately.
|
|
336
|
+
RTSA-Suite does not need to be running. `NativeReceiverOptions` selects a
|
|
337
|
+
receive-only mode and an RF span independently of `sample_rate_hz`:
|
|
338
|
+
|
|
339
|
+
```rust,no_run
|
|
340
|
+
# #[cfg(all(feature = "http", feature = "file", feature = "native-sdk", any(target_os = "windows", target_os = "linux")))]
|
|
341
|
+
# async fn example() -> sdr_aaronia_rs::Result<()> {
|
|
342
|
+
use sdr_aaronia_rs::{SpectranSourceBuilder, SourceType};
|
|
343
|
+
use sdr_aaronia_rs::capabilities::NativeReceiverOptions;
|
|
344
|
+
let mut builder = SpectranSourceBuilder::new();
|
|
345
|
+
builder.force_source_type(SourceType::NativeSdk);
|
|
346
|
+
builder.device_serial("YOUR_DEVICE_SERIAL".to_owned());
|
|
347
|
+
builder.native_options(NativeReceiverOptions {
|
|
348
|
+
mode: Some("iqreceiver".into()),
|
|
349
|
+
rf_span_hz: Some(44e6),
|
|
350
|
+
..Default::default()
|
|
351
|
+
});
|
|
352
|
+
let mut source = builder.build().await?;
|
|
353
|
+
let capabilities = source.device_capabilities().await;
|
|
354
|
+
source.start_streaming().await?;
|
|
355
|
+
// After reading IQ, capture_sample_rate_hz / capture_bandwidth_hz describe
|
|
356
|
+
// the delivered samples. Configuration bounds do not certify RF performance.
|
|
357
|
+
source.stop_streaming().await?;
|
|
358
|
+
# Ok(())
|
|
359
|
+
# }
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Stop releases the device. Restart on the same unified source reopens the selected
|
|
363
|
+
serial and reapplies settings. Unknown metadata stays unknown; there is no
|
|
364
|
+
universal ECO/V6 bandwidth ratio. See the
|
|
365
|
+
[mode and qualification matrix](docs/NATIVE_SDK_COMPATIBILITY.md) for tested scope.
|
|
366
|
+
|
|
333
367
|
### seify (Rust-native)
|
|
334
368
|
|
|
335
369
|
Enable the `seify` feature and construct the device with
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Native SDK configuration and qualification (0.13)
|
|
2
|
+
|
|
3
|
+
The driver queries the opened device and mode rather than imposing one ECO's
|
|
4
|
+
rate ladder on every SPECTRAN. `DeviceCapabilities` reports SDK configuration
|
|
5
|
+
metadata; it does not certify every model, hardware option or licensed mode.
|
|
6
|
+
Aaronia's [current compact USB range](https://aaronia.com/en/shop/spectrum-analyzer/real-time/compact-usb)
|
|
7
|
+
has different bandwidths and receiver/transmitter options. Consult the specific
|
|
8
|
+
model's rating as well as the SDK's enabled options.
|
|
9
|
+
|
|
10
|
+
## Supported receive paths
|
|
11
|
+
|
|
12
|
+
| Family / mode | Configuration path | Validation here |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
| `spectranv6eco/iqreceiver` (automatic ECO default) | RF span via `main/spanfreq`; actual rate/span from IQ packets | Windows ECO mono live trial |
|
|
15
|
+
| `spectranv6/iqreceiver` | RF span via `main/spanfreq`; actual rate/span from IQ packets | Mode/geometry tests; no hardware trial |
|
|
16
|
+
| `spectranv6/raw`, `spectranrsa/raw` | Raw clock, channel and decimation options from ConfigInfo | Configuration/parser tests; no hardware trial |
|
|
17
|
+
| `spectranv6eco/raw` (explicit only) | Enabled raw clock/channel/decimation options when exposed | Mode tests; no dual-ECO hardware trial |
|
|
18
|
+
| ECO `rtsa`, V6/ECO `sweepsa` | Low-level spectra API only; refused by unified IQ configuration | Payload guards |
|
|
19
|
+
| Other family/mode strings | Enumeration may accept an explicit family; unknown IQ payload modes are refused | No hardware claims |
|
|
20
|
+
|
|
21
|
+
Automatic discovery tries the known family names. `DeviceNotFound` and
|
|
22
|
+
`DeviceNotReady` are typed errors; SDK ownership errors retain `SdkApi` codes.
|
|
23
|
+
The application chooses its retry deadline. No retry silently changes device,
|
|
24
|
+
starts RTSA, or takes over a device owned by another process.
|
|
25
|
+
|
|
26
|
+
## Three different bandwidth values
|
|
27
|
+
|
|
28
|
+
1. `NativeReceiverOptions.rf_span_hz` / `set_rf_span_hz`: requested RF span in an
|
|
29
|
+
IQ receiver mode. The setter returns SDK read-back while open, or the queued
|
|
30
|
+
request while stopped; restart revalidates deferred settings.
|
|
31
|
+
2. `DeviceCapabilities.rf_span_hz`: SDK ConfigInfo setting bounds. The tested ECO
|
|
32
|
+
SDK returned 1 Hz–200 MHz, exceeding that model's 44 MHz rated width. These
|
|
33
|
+
bounds alone must not be presented as a calibrated hardware bandwidth.
|
|
34
|
+
3. `capture_bandwidth_hz()` / `SourceInfo.bandwidth_hz`: valid RF span declared by
|
|
35
|
+
the IQ packets actually returned. `capture_sample_rate_hz()` is the separate
|
|
36
|
+
IQ sample clock. Unavailable capture bandwidth is zero (unknown).
|
|
37
|
+
|
|
38
|
+
RF span does not establish dBm amplitude calibration. The legacy HTTP 0.8×Fs
|
|
39
|
+
helper and the IQ receiver's legacy Fs/1.5 request translation are compatibility
|
|
40
|
+
helpers, not universal hardware laws. Use explicit RF span and observed packet
|
|
41
|
+
geometry when the recording or display depends on exact coverage.
|
|
42
|
+
|
|
43
|
+
## Explicit raw configuration
|
|
44
|
+
|
|
45
|
+
Select `mode=raw`, then use an enabled `receiver_clock` label and/or a power-of-two
|
|
46
|
+
`decimation_factor`. The option's original index is retained despite disabled or
|
|
47
|
+
empty entries. An unavailable/disabled request fails before configuration writes.
|
|
48
|
+
Explicit raw clock/decimation selects the pipeline instead of the legacy
|
|
49
|
+
`sample_rate_hz` request. The delivered packet rate remains authoritative.
|
|
50
|
+
Receiver channels are validated against the opened raw mode. A mono ECO trial
|
|
51
|
+
cannot establish dual-RX support, interleaving, or the licensed options of a
|
|
52
|
+
200-series ECO, PLUS or XPR device.
|
|
53
|
+
|
|
54
|
+
## Opening, stopping and reopening
|
|
55
|
+
|
|
56
|
+
Partial open/connect/start failures attempt all applicable cleanup. Stop attempts
|
|
57
|
+
stop, disconnect and close, returning the first error after attempting the rest.
|
|
58
|
+
A failed close remains owned for a Drop retry. Repeated start/stop calls are
|
|
59
|
+
idempotent; successful stop clears stale carry/geometry. Unified restart reopens
|
|
60
|
+
the same serial and mode, reapplying settings. Settings queued while stopped are
|
|
61
|
+
checked against fresh metadata before writes on reopen. SDK calls themselves
|
|
62
|
+
remain vendor operations without a driver-imposed cancellation deadline.
|
|
63
|
+
|
|
64
|
+
## Binding parity
|
|
65
|
+
|
|
66
|
+
Rust exposes typed capabilities/options. C adds capability JSON (schema version
|
|
67
|
+
1), RF span range/setter and builder options without changing existing C struct
|
|
68
|
+
layouts; free JSON with `spectran_string_free`. Python exposes config fields,
|
|
69
|
+
`capabilities()`, `source_info()` and `set_rf_span_hz()`. Seify and Soapy consume
|
|
70
|
+
reported bounds; an unknown native discrete rate ladder is empty rather than a
|
|
71
|
+
fabricated ECO ladder. Soapy's native bandwidth is observed RF span, not Fs×0.8.
|
|
72
|
+
Soapy TX setup now requires explicit `tx=true` and compatible licensed hardware.
|
|
73
|
+
|
|
74
|
+
## Windows live result, 2026-10-08
|
|
75
|
+
|
|
76
|
+
An explicitly selected SPECTRAN V6 ECO on Windows, SDK API 1.0.4, was tested with
|
|
77
|
+
RTSA closed, reference level −50 dBm, 65,536-sample reads and five eight-second
|
|
78
|
+
cycles. Each cycle called start twice, read IQ, then called stop twice. Frequency
|
|
79
|
+
and reference settings were queued while closed; restart reopened the same serial.
|
|
80
|
+
|
|
81
|
+
| Cycle | Requested / observed RF span | Observed IQ rate | Centre | Returned samples |
|
|
82
|
+
| --- | --- | --- | --- | --- |
|
|
83
|
+
| 1 | 44 MHz | 59.2137035 MS/s | 280 MHz | 268,500,992 |
|
|
84
|
+
| 2 | 20 MHz | 30 MS/s | 281 MHz | 196,476,928 |
|
|
85
|
+
| 3 | 10 MHz | 15 MS/s | 282 MHz | 98,435,072 |
|
|
86
|
+
| 4 | 5 MHz | 7.5 MS/s | 283 MHz | 49,119,232 |
|
|
87
|
+
| 5 | 44 MHz | 59.2137035 MS/s | 284 MHz | 388,038,656 |
|
|
88
|
+
|
|
89
|
+
The program exited successfully and printed its completion marker. The cumulative
|
|
90
|
+
discontinuity count was one from the first cycle and did not increase. Signals
|
|
91
|
+
was restored afterward with fresh samples and its previous tune/reference level.
|
|
92
|
+
This short mono trial does not establish endurance, hot-unplug recovery, Linux
|
|
93
|
+
hardware operation, wider raw clocks, dual-RX, TX or all-model compatibility.
|
|
94
|
+
|
|
95
|
+
Reproduce only on a device available for exclusive receive use:
|
|
96
|
+
|
|
97
|
+
```sh
|
|
98
|
+
cargo run --example native_sdk_qualification --features native-sdk -- YOUR_DEVICE_SERIAL 8
|
|
99
|
+
```
|
|
@@ -6,6 +6,24 @@ The Aaronia Real-Time Spectrum Analyzer (RTSA) Vendor Software Development Kit (
|
|
|
6
6
|
|
|
7
7
|
> **Status & attribution.** This document is a *community-compiled* reference, **not** an official Aaronia specification. It is assembled from Aaronia's official open-source sample code, public posts on the Aaronia V6 forum, vendor SDK headers, and empirical analysis. Where these disagree, the vendor's own materials are authoritative. See [Sources and Attribution](#sources-and-attribution) for the upstream, vendor-published references.
|
|
8
8
|
|
|
9
|
+
## Sweep reference level (0.13.1)
|
|
10
|
+
|
|
11
|
+
Use `configure_sweepsa_with_reference_level(&config, Some(-50.0))` to apply a
|
|
12
|
+
reference level in dBm. `None` preserves the previous setting, as the existing
|
|
13
|
+
`configure_sweepsa` call does. Explicit levels are validated against SDK metadata
|
|
14
|
+
before any sweep geometry is changed. This additive method carries Specola's
|
|
15
|
+
vendor functionality without adding a field to the public `SweepsaConfig` struct.
|
|
16
|
+
|
|
17
|
+
## Current driver behavior (0.13)
|
|
18
|
+
|
|
19
|
+
See [Native SDK compatibility and qualification](NATIVE_SDK_COMPATIBILITY.md)
|
|
20
|
+
for the current configuration, lifecycle and binding contract. Older empirical
|
|
21
|
+
measurements below describe their particular mode/device, not every V6 variant.
|
|
22
|
+
IQ `stepFrequency` is the actual sample clock; `spanFrequency` is valid RF span;
|
|
23
|
+
centre is `startFrequency + spanFrequency / 2`. The driver carries these values
|
|
24
|
+
with the returned IQ, never replacing RF span with a universal clock ratio.
|
|
25
|
+
SDK ConfigInfo bounds can exceed a model's rated bandwidth.
|
|
26
|
+
|
|
9
27
|
## Table of Contents
|
|
10
28
|
|
|
11
29
|
- [Features and Purpose](#features-and-purpose)
|
|
@@ -53,7 +71,7 @@ The Aaronia RTSA Vendor SDK provides low-level, high-performance access to Aaron
|
|
|
53
71
|
* **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)
|
|
54
72
|
* **Clock Rates** (V6 ECO): fixed at 92.16 MHz. Its top IQ rate is 61.44 MHz, which is that clock over 1.5 — easy to confuse, and confusing them records the rate as the clock
|
|
55
73
|
* **Decimation**: Full, 1/2, 1/4, 1/8, 1/16, 1/32, 1/64, 1/128, 1/256, 1/512
|
|
56
|
-
* **IQ
|
|
74
|
+
* **IQ receiver sample rate**: reported by packet `stepFrequency`, separately from `spanfreq` / RF span. Lower ECO spans measured 1.5× span, with an independent ceiling at wider spans; this is not a raw-mode constraint
|
|
57
75
|
* **Health Monitoring**: Real-time device status including temperatures, sample rates, power levels, USB statistics, GPS data
|
|
58
76
|
|
|
59
77
|
## Verified Architecture
|
|
@@ -396,7 +414,7 @@ a `/2ⁿ` ladder: a 10 MHz request streams at 15.0 MS/s, 15.36 at 23.04,
|
|
|
396
414
|
Every rung of a request→delivered sweep was ×1.5 to the third decimal.
|
|
397
415
|
This crate's `sample_rate_hz` is the sample rate (the HTTP backend
|
|
398
416
|
delivers exactly it), so `configure_iq_receiver` writes `rate / 1.5` on
|
|
399
|
-
this mode
|
|
417
|
+
this mode as a compatibility request, subject to the observed ceiling; a
|
|
400
418
|
15.36 MS/s request now measures 15.360 MS/s, and `sample_rate_hz` reports
|
|
401
419
|
what the packets carry. Two more measured facts: the pipeline delivers
|
|
402
420
|
~40% of rate for ~5 s after start and flags one `TIME_DISCONTINUITY` as
|
|
@@ -405,47 +423,23 @@ it settles, then holds 100.0% with no further flags; and it sets
|
|
|
405
423
|
which is why that flag is not treated as an overrun. `spectranv6/raw` on
|
|
406
424
|
a full V6 has not been measured and is left untranslated.
|
|
407
425
|
|
|
408
|
-
###
|
|
409
|
-
|
|
410
|
-
`
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
the device keeps what it has and the check reads it live. An eco
|
|
426
|
-
exposes no such key and falls back to the 92.16 MHz default; a full V6
|
|
427
|
-
in a non-raw IQ mode may sit on a faster clock, and assuming 92.16
|
|
428
|
-
there would refuse rates it can genuinely reach.
|
|
429
|
-
|
|
430
|
-
The second call runs after the writes, against the clock read back from
|
|
431
|
-
`device/receiverclock` via `AARTSAAPI_ConfigGetString`. Its real job is
|
|
432
|
-
to make `receiver_clock_hz()` report the device's own number rather than
|
|
433
|
-
the requested one. As a check it is deliberately weak in one direction:
|
|
434
|
-
the rate already fits the intended clock, so a device sitting on a
|
|
435
|
-
*faster* clock than intended still passes — an ignored clock write is
|
|
436
|
-
invisible here, and harmless, because a rate that fits 92.16 MHz also
|
|
437
|
-
fits 245.76. What it catches is a device on a clock too slow for the
|
|
438
|
-
rate, which the pre-write check had no way to know about. That error
|
|
439
|
-
says the device is already configured, since unlike the pre-write refusal
|
|
440
|
-
there is no prior state to restore.
|
|
441
|
-
|
|
442
|
-
Both enforce `rate * 1.5 <= clock`; the pre-write one returns
|
|
443
|
-
`Error::Config` from `validate_iq_mode`, the post-write one wraps it in
|
|
444
|
-
an `Error::Sdk`. `utils::the_92mhz_label_is_the_default_receiver_clock`
|
|
445
|
-
pins the label↔rate equality the raw-mode arm depends on.
|
|
446
|
-
|
|
447
|
-
The ConfigItem labels the SDK exposes are *rounded* — `"92MHz"` is
|
|
448
|
-
actually 92.16 MHz, etc. Use `receiver_clock_for_label` to convert.
|
|
426
|
+
### Mode-specific validation (0.13)
|
|
427
|
+
|
|
428
|
+
IQ receiver requests validate `main/spanfreq` against the opened mode's ConfigInfo
|
|
429
|
+
before writes. They do not use a default 92 MHz raw clock as a ceiling. Explicit
|
|
430
|
+
RF span avoids the legacy Fs/1.5 translation; packet geometry reports what was
|
|
431
|
+
actually delivered, including the independent USB/IQ ceiling.
|
|
432
|
+
|
|
433
|
+
Raw modes retain the legacy default clock/rate validation for callers that do not
|
|
434
|
+
request explicit raw controls. `NativeReceiverOptions.receiver_clock` and
|
|
435
|
+
`decimation_factor` instead select enabled device-provided enum options; they do
|
|
436
|
+
not apply the legacy 1.5× check to a wider raw pipeline. Actual rate comes from
|
|
437
|
+
packets. A decimation-only request preserves the currently reported clock when
|
|
438
|
+
available. Unknown capabilities remain unknown; the legacy `receiver_clock_hz()`
|
|
439
|
+
fallback is not proof of the installed hardware's clock.
|
|
440
|
+
|
|
441
|
+
The ConfigItem labels are rounded (`"92MHz"` is 92.16 MHz). The legacy conversion
|
|
442
|
+
table is retained below; explicit labels are validated against the device options.
|
|
449
443
|
|
|
450
444
|
| Label | Actual rate | Source |
|
|
451
445
|
|----------|----------------|--------------------|
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
//! Receive-only ECO qualification: explicit serial, span, carry and stop/reopen.
|
|
2
|
+
//! Run only with the radio released by other apps; no TX is opened.
|
|
3
|
+
#[cfg(all(
|
|
4
|
+
feature = "native-sdk",
|
|
5
|
+
any(target_os = "windows", target_os = "linux")
|
|
6
|
+
))]
|
|
7
|
+
#[tokio::main(flavor = "current_thread")]
|
|
8
|
+
async fn main() -> anyhow::Result<()> {
|
|
9
|
+
use sdr_aaronia_rs::{Error, SourceType, SpectranSourceBuilder};
|
|
10
|
+
use std::time::{Duration, Instant};
|
|
11
|
+
let serial = std::env::args()
|
|
12
|
+
.nth(1)
|
|
13
|
+
.ok_or_else(|| anyhow::anyhow!("Pass the explicit USB serial"))?;
|
|
14
|
+
let seconds: u64 = std::env::args()
|
|
15
|
+
.nth(2)
|
|
16
|
+
.map(|s| s.parse())
|
|
17
|
+
.transpose()?
|
|
18
|
+
.unwrap_or(8);
|
|
19
|
+
anyhow::ensure!(
|
|
20
|
+
(2..=30).contains(&seconds),
|
|
21
|
+
"cycle duration must be 2–30 seconds"
|
|
22
|
+
);
|
|
23
|
+
let mut builder = SpectranSourceBuilder::new();
|
|
24
|
+
builder
|
|
25
|
+
.force_source_type(SourceType::NativeSdk)
|
|
26
|
+
.device_serial(serial)
|
|
27
|
+
.center_frequency_hz(280e6)
|
|
28
|
+
.reference_level_dbm(-50.0)
|
|
29
|
+
.rf_span_hz(44e6);
|
|
30
|
+
let until = Instant::now() + Duration::from_secs(15);
|
|
31
|
+
let mut source = loop {
|
|
32
|
+
match builder.build().await {
|
|
33
|
+
Ok(source) => break source,
|
|
34
|
+
Err(Error::DeviceNotReady { .. } | Error::DeviceNotFound { .. })
|
|
35
|
+
if Instant::now() < until =>
|
|
36
|
+
{
|
|
37
|
+
tokio::time::sleep(Duration::from_millis(500)).await
|
|
38
|
+
}
|
|
39
|
+
Err(error) => return Err(error.into()),
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
println!("{}", source.device_capabilities().await.to_json()?);
|
|
43
|
+
let mut buffer = Vec::new();
|
|
44
|
+
for (index, span) in [44e6, 20e6, 10e6, 5e6, 44e6].into_iter().enumerate() {
|
|
45
|
+
if index > 0 {
|
|
46
|
+
source.set_rf_span_hz(span).await?;
|
|
47
|
+
}
|
|
48
|
+
source.start_streaming().await?;
|
|
49
|
+
source.start_streaming().await?; // Must be idempotent.
|
|
50
|
+
let until = Instant::now() + Duration::from_secs(seconds);
|
|
51
|
+
let mut samples = 0usize;
|
|
52
|
+
let mut reads = 0;
|
|
53
|
+
while Instant::now() < until {
|
|
54
|
+
match source.read_samples(&mut buffer, 65536).await {
|
|
55
|
+
Ok(n) => {
|
|
56
|
+
samples += n;
|
|
57
|
+
reads += 1;
|
|
58
|
+
}
|
|
59
|
+
Err(Error::Io(error)) if error.kind() == std::io::ErrorKind::TimedOut => continue,
|
|
60
|
+
Err(error) => return Err(error.into()),
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
let info = source.source_info();
|
|
64
|
+
println!(
|
|
65
|
+
"{{\"cycle\":{index},\"requested_span_hz\":{span},\"sample_rate_hz\":{},\"rf_span_hz\":{},\"center_hz\":{},\"samples\":{samples},\"reads\":{reads},\"drops\":{}}}",
|
|
66
|
+
info.sample_rate_hz,
|
|
67
|
+
info.bandwidth_hz,
|
|
68
|
+
info.center_frequency_hz,
|
|
69
|
+
source.cumulative_drops()
|
|
70
|
+
);
|
|
71
|
+
anyhow::ensure!(samples > 0, "no IQ in cycle {index}");
|
|
72
|
+
anyhow::ensure!(
|
|
73
|
+
(info.bandwidth_hz - span).abs() < 1.0,
|
|
74
|
+
"RF width differs from request"
|
|
75
|
+
);
|
|
76
|
+
source.stop_streaming().await?;
|
|
77
|
+
source.stop_streaming().await?; // Must not close twice.
|
|
78
|
+
// Configuration while closed is deferred; start reopens the same serial.
|
|
79
|
+
source
|
|
80
|
+
.set_center_frequency_hz(280e6 + (index + 1) as f64 * 1e6)
|
|
81
|
+
.await?;
|
|
82
|
+
source.set_reference_level_dbm(-50.0 + index as f64).await?;
|
|
83
|
+
}
|
|
84
|
+
source.stop_streaming().await?;
|
|
85
|
+
println!("{{\"qualification_complete\":true}}");
|
|
86
|
+
Ok(())
|
|
87
|
+
}
|
|
88
|
+
#[cfg(not(all(
|
|
89
|
+
feature = "native-sdk",
|
|
90
|
+
any(target_os = "windows", target_os = "linux")
|
|
91
|
+
)))]
|
|
92
|
+
fn main() {
|
|
93
|
+
eprintln!("Requires native-sdk on Windows or Linux");
|
|
94
|
+
}
|
|
@@ -305,6 +305,13 @@ SpectranFfiError spectran_endpoints_client_control_recording(HttpEndpointsClient
|
|
|
305
305
|
|
|
306
306
|
// --- General FFI Utilities --- //
|
|
307
307
|
|
|
308
|
+
/* Additive native SDK APIs; existing struct layouts remain unchanged. */
|
|
309
|
+
char *spectran_source_capabilities_json(void *source);
|
|
310
|
+
SpectranFfiError spectran_source_rf_span_range(void *source, double *min_hz, double *max_hz);
|
|
311
|
+
SpectranFfiError spectran_source_set_rf_span_hz(void *source, double hz);
|
|
312
|
+
SpectranFfiError spectran_source_builder_native_options(SpectranSourceBuilder *builder,
|
|
313
|
+
const char *family, const char *mode, double rf_span_hz, const char *clock, uint32_t decimation);
|
|
314
|
+
|
|
308
315
|
void spectran_string_free(char* s);
|
|
309
316
|
// Takes the code as an int so any value is safe to pass; unknown codes
|
|
310
317
|
// yield "Unknown error code".
|