python-aaronia 0.11.2__tar.gz → 0.13.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/CHANGELOG.md +216 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/CONTRIBUTING.md +1 -1
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/Cargo.lock +12 -57
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/Cargo.toml +7 -9
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/DESIGN.md +3 -5
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/PKG-INFO +33 -30
- python_aaronia-0.13.0/README.md +442 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/docs/HTTPSPEC.md +13 -1
- python_aaronia-0.13.0/docs/NATIVE_SDK_COMPATIBILITY.md +99 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/docs/SDKSPEC.md +29 -43
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/docs/USAGE.md +0 -1
- python_aaronia-0.13.0/examples/native_sdk_qualification.rs +94 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/include/spectran.h +13 -5
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/python-aaronia/Cargo.toml +1 -1
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/python-aaronia/README.md +32 -29
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/python-aaronia/aaronia.pyi +16 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/python-aaronia/src/lib.rs +86 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/python-aaronia/test_basic.py +26 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/scripts/ci-local.sh +1 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/soapy-aaronia/README.md +27 -23
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/soapy-aaronia/Registration.cpp +15 -1
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/soapy-aaronia/SpectranSoapyDevice.cpp +32 -8
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/src/c_api.rs +204 -4
- python_aaronia-0.13.0/src/capabilities.rs +206 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/src/error.rs +12 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/src/file_source.rs +277 -15
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/src/http_endpoints.rs +12 -97
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/src/http_source.rs +1893 -219
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/src/http_streaming.rs +917 -81
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/src/lib.rs +7 -14
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/src/link_budget.rs +50 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/src/native_sdk.rs +468 -111
- python_aaronia-0.13.0/src/native_sdk_lifecycle_tests.rs +374 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/src/sdk_source.rs +5 -12
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/src/seify_impl.rs +32 -12
- python_aaronia-0.13.0/src/stream_break.rs +206 -0
- python_aaronia-0.13.0/src/stream_geometry.rs +125 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/src/unified_source.rs +297 -31
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/tests/spec_coverage.rs +73 -14
- python_aaronia-0.11.2/README.md +0 -255
- python_aaronia-0.11.2/examples/channel_hopping.rs +0 -82
- python_aaronia-0.11.2/src/sdr_source_impl.rs +0 -674
- python_aaronia-0.11.2/tests/sdr_source_impl_test.rs +0 -426
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/.cargo/config.toml +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/.gitattributes +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/.gitignore +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/LICENSE +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/PLUGINS.md +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/benches/decompress_block.rs +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/benches/deinterleave_dual_iq.rs +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/benches/parse_int16_packet.rs +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/benches/rtsa_open_and_read.rs +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/deny.toml +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/docs/APPS.md +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/docs/FILESPEC.md +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/docs/QUICKSTART.md +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/docs/SYNC.md +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/docs/VERIFICATION.md +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/examples/device_control.rs +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/examples/dump_metadata.rs +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/examples/http_iq_quickstart.rs +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/examples/native_sdk_basic.rs +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/examples/native_sdk_transmit.rs +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/examples/noaa_scanner.rs +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/examples/python_arrow_example.py +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/examples/read_rtsa_file.rs +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/examples/soapy_python_example.py +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/packaging/homebrew/README.md +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/packaging/homebrew/soapy-aaronia.rb +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/pyproject.toml +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/scripts/fake-rtsa-server.py +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/scripts/native-sdk-validate.ps1 +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/scripts/sdk-container-test.sh +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/scripts/validate-iq-live.py +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/soapy-aaronia/CMakeLists.txt +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/soapy-aaronia/SpectranSoapyDevice.hpp +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/soapy-aaronia/packaging/install.ps1 +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/soapy-aaronia/packaging/install.sh +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/src/decompression.rs +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/src/detection.rs +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/src/http_sink.rs +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/src/sdk_sink.rs +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/src/unified_sink.rs +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/src/utils.rs +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/tests/abi_drift.rs +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/tests/c_api_test.rs +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/tests/http_mock_test.rs +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/tests/http_resilience_test.rs +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/tests/http_sink_test.rs +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/tests/integration_test.rs +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/tests/live_smoke.rs +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/tests/native_sdk_live.rs +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/tests/native_sdk_load.rs +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/tests/properties.proptest-regressions +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/tests/properties.rs +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/tests/rtsa_negative_test.rs +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/tests/test_cw_mag.rs +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/tests/test_cw_meta.rs +0 -0
- {python_aaronia-0.11.2 → python_aaronia-0.13.0}/tests/wire_contract.rs +0 -0
|
@@ -4,6 +4,222 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [v0.13.0] - 2026-10-08
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Native IQ packet RF span and centre metadata, independent of the actual IQ
|
|
11
|
+
sample clock. Mono and dual reads preserve that geometry with carried samples.
|
|
12
|
+
- Direct SDK capability queries: configuration bounds, clock/channel/decimation
|
|
13
|
+
options with their original enum indices and disabled flags, selected mode,
|
|
14
|
+
and SDK version. Missing metadata stays unknown; no RTSA app is required.
|
|
15
|
+
- Receive-only `NativeReceiverOptions`: explicit family/mode, IQ receiver RF
|
|
16
|
+
span, or enabled raw clock/decimation selection. SDK configuration bounds are
|
|
17
|
+
checked before writes. They are not a hardware qualification certificate.
|
|
18
|
+
- Versioned capability JSON and RF-span controls in C and Python; device-derived
|
|
19
|
+
ranges and observed RF bandwidth in seify and SoapySDR. Windows/Linux Python
|
|
20
|
+
release wheels now enable `native-sdk` (the proprietary SDK remains separate).
|
|
21
|
+
- An opt-in, serial-selected `native_sdk_qualification` example for receive-only
|
|
22
|
+
span changes and repeated device stop/reopen checks.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
- Failed SDK opens close any partially allocated device. Connect/start failures
|
|
26
|
+
attempt stop, disconnect and close while preserving the original error.
|
|
27
|
+
- Stop attempts all cleanup steps, returns the first failure, clears carried IQ
|
|
28
|
+
and observed metadata, and retains a failed-close handle for a Drop retry.
|
|
29
|
+
Repeated start/stop calls are idempotent; unified restart reopens the same
|
|
30
|
+
selected serial and settings, without silently switching devices or backend.
|
|
31
|
+
- SDK global initialization/shutdown reference counting is serialized across the
|
|
32
|
+
initialized-state transition.
|
|
33
|
+
- IQ receiver span validation uses the opened mode's `main/spanfreq` bounds,
|
|
34
|
+
rather than the legacy V6 raw-clock constraint. Runtime native setters use the
|
|
35
|
+
same configuration path as construction.
|
|
36
|
+
- Reads stop before a queued packet changes centre, IQ rate or RF span, and a
|
|
37
|
+
carry-only read does not wait for another SDK packet.
|
|
38
|
+
- Native capabilities and sensors no longer come from an unrelated HTTP server.
|
|
39
|
+
- Unknown SDK modes cannot reinterpret spectra as IQ or IQ as spectra.
|
|
40
|
+
- SoapySDR tolerates an unknown discrete sample-rate ladder and reports native
|
|
41
|
+
bandwidth from packet metadata. An RX open no longer attempts TX setup.
|
|
42
|
+
- Release publication is gated on full CI, with semver checked against the
|
|
43
|
+
previous reachable release instead of a fixed historical tag.
|
|
44
|
+
|
|
45
|
+
### Migration and qualification
|
|
46
|
+
- This 0.x minor release adds public config fields and SDK mode enum variants.
|
|
47
|
+
Build `SpectranConfig` with `..Default::default()` and review exhaustive mode
|
|
48
|
+
matches. Existing C struct layouts and functions are retained.
|
|
49
|
+
- Explicit `spectranv6eco/raw` is preserved; automatic ECO selection remains
|
|
50
|
+
`iqreceiver`. Explicit raw clock/decimation controls replace the legacy rate
|
|
51
|
+
request; the actual delivered rate comes from IQ packets.
|
|
52
|
+
- Soapy TX setup requires explicit `tx=true`, a native backend and compatible
|
|
53
|
+
licensed hardware. TX and dual-RX remain hardware-unverified here.
|
|
54
|
+
- Windows ECO mono qualification passed five stop/reopen cycles at RF spans
|
|
55
|
+
44, 20, 10, 5 and 44 MHz. The 44 MHz span carried about 59.214 MS/s; the lower
|
|
56
|
+
spans carried 30, 15 and 7.5 MS/s. One initial discontinuity was reported, with
|
|
57
|
+
no additional discontinuities during this short trial. Signals was restored.
|
|
58
|
+
This is not an endurance, amplitude-calibration or all-model certification.
|
|
59
|
+
See [native SDK compatibility](docs/NATIVE_SDK_COMPATIBILITY.md).
|
|
60
|
+
|
|
61
|
+
## [v0.12.0] - 2026-09-29
|
|
62
|
+
|
|
63
|
+
### Removed
|
|
64
|
+
- **The `sdr-source` feature, and with it the dependency on `orecchiette-sdr-source-rs`.**
|
|
65
|
+
`SpectranSdrSource` and `SpectranBackend` (the `SdrSource`-trait facade over the async
|
|
66
|
+
source), the `sdr_source` re-export module and the `channel_hopping` example were one
|
|
67
|
+
consumer's adapter, and they live beside that consumer now (Specola's `sdr-aaronia-source`
|
|
68
|
+
crate), so this crate stands on its own: `default` is `http`, `file` and `ffi`, and
|
|
69
|
+
`crossbeam-channel` goes with the adapter. A caller that used the facade builds the same
|
|
70
|
+
thing from `SpectranSourceBuilder` and its own trait; the code is 670 lines and GPL, take it.
|
|
71
|
+
|
|
72
|
+
### Added
|
|
73
|
+
- **`HttpSource` reports where the stream broke**, not just how often.
|
|
74
|
+
`HttpSourceBuilder::with_stream_breaks` takes a `StreamBreakSink` (see the
|
|
75
|
+
new `stream_break` module) and the source calls it with the **absolute index
|
|
76
|
+
of the first sample after each discontinuity**, in its own output stream:
|
|
77
|
+
a server gap the drop detector saw, a capacity trim, a reconnect, and a
|
|
78
|
+
centre or rate change the device declared. Optional; a caller that does not
|
|
79
|
+
ask pays nothing.
|
|
80
|
+
|
|
81
|
+
The counters that were there before cannot be acted on. A consumer carrying
|
|
82
|
+
state across deliveries — symbol timing, carrier tracking, burst framing, a
|
|
83
|
+
recording's contiguity — needs to know *which* samples belong to the old
|
|
84
|
+
epoch, and "the server skipped 3 times" does not say. Breaks are held against
|
|
85
|
+
the samples still queued and emitted only as those samples are produced, so a
|
|
86
|
+
later trim cannot move an index already reported; samples the source discards
|
|
87
|
+
never occupy an index at all.
|
|
88
|
+
|
|
89
|
+
- `StreamParser::process_data_recovering` and `ParsedItem`: a malformed
|
|
90
|
+
packet becomes a `ParsedItem::Lost` in stream order instead of an `Err` that
|
|
91
|
+
discards the rest of the chunk. `process_data` keeps its contract.
|
|
92
|
+
- `StreamStats::non_iq_packets_skipped` and
|
|
93
|
+
`StreamStats::packets_lost_to_parse_errors`.
|
|
94
|
+
|
|
95
|
+
### Fixed
|
|
96
|
+
- **A catch-up after a stall no longer trims what it just fetched.** One
|
|
97
|
+
`fetch_samples` sweep drained every chunk the reader task had queued — up to
|
|
98
|
+
64, about a million int16 samples — into a buffer that trimmed a few packets
|
|
99
|
+
deep, so any stall of the consumer longer than a few milliseconds discarded
|
|
100
|
+
most of what arrived during it. Measured live from bigear against a
|
|
101
|
+
Spectran V6 at 15.36 MS/s, where the trim sat at 147 456 samples (~9.6 ms):
|
|
102
|
+
with one flowgraph executor every 45 s run lost samples, 2 to 10 times; with
|
|
103
|
+
two, two runs in five. The trim now sits one sweep above the refill target
|
|
104
|
+
(`catch_up_headroom`: `CHUNK_CHANNEL_DEPTH` chunks of the largest size seen,
|
|
105
|
+
in samples, plus a carried packet), and a sweep takes at most
|
|
106
|
+
`CHUNK_CHANNEL_DEPTH` chunks, so a catch-up always fits. A consumer that falls
|
|
107
|
+
behind now backs up the chunk channel and the socket instead of being
|
|
108
|
+
trimmed, and any loss moves upstream, where the packet timestamps report it
|
|
109
|
+
as a server gap. Re-measured live, same span: one executor 1 gap in 5 runs,
|
|
110
|
+
two executors 0 in 3. The overflow warning no longer names "the first
|
|
111
|
+
moments of a stream" as the expected cause.
|
|
112
|
+
|
|
113
|
+
What changes with it, so nothing reads as a regression later:
|
|
114
|
+
`StreamStats::buffer_capacity` still reports the enforced capacity, which now
|
|
115
|
+
includes the headroom and jumps once the first chunk sizes it (about eight
|
|
116
|
+
times the old figure at 15.36 MS/s int16), so a fill ratio reads lower. A
|
|
117
|
+
consumer that stalls now reads its backlog rather than a gap: up to the buffer
|
|
118
|
+
plus the channel, about 150 ms at 15.36 MS/s, which a pipeline faster than
|
|
119
|
+
real time then catches up — including a retune that arrived behind it. The
|
|
120
|
+
buffer can grow to about 10 MB (int16) or 20 MB (float32) during a stall, on
|
|
121
|
+
top of the channel's 4 MB. And because a slow consumer now throttles the
|
|
122
|
+
reads the link-budget meter counts, a window in which a sweep found the chunk
|
|
123
|
+
channel full is marked (`ThroughputMeter::note_consumer_limited`) and yields
|
|
124
|
+
no verdict, rather than one blaming the path for the consumer.
|
|
125
|
+
- **A lost packet at a high sample rate is now reported.** `DropDetector`
|
|
126
|
+
flagged a gap only past a fixed 1 ms, and at 61.44 MS/s a 16 384-sample
|
|
127
|
+
packet lasts 267 µs, so a whole lost packet was spliced over silently. The
|
|
128
|
+
tolerance is now relative to the stream: half the packet's own duration, or
|
|
129
|
+
four times the residual observed between contiguous packets (at most three
|
|
130
|
+
quarters of a packet), floored at the timestamps' microsecond resolution
|
|
131
|
+
and still capped at 1 ms, so low rates behave as before.
|
|
132
|
+
`DropDetector::observed_jitter_seconds` exposes the calibration. Not yet
|
|
133
|
+
verified live against a real RTSA stream at a high rate.
|
|
134
|
+
- **A compressed spectra or histogram packet no longer swallows the IQ behind
|
|
135
|
+
it.** It was framed at its *uncompressed* size, but a compressed payload is
|
|
136
|
+
a bitstream whose length the header does not carry. It is now reported as
|
|
137
|
+
`ParsedItem::Skipped` where it sat and the parser resynchronises on the next
|
|
138
|
+
header.
|
|
139
|
+
- **A skipped non-IQ packet is no longer reported as a gap in the IQ.** Any
|
|
140
|
+
packet whose header was read but whose non-IQ payload could not be decoded
|
|
141
|
+
was a `ParsedItem::Lost`, which `HttpSource` recorded as a `Gap` and counted
|
|
142
|
+
in `packets_lost_to_parse_errors` — a hole claimed in an IQ stream that had
|
|
143
|
+
none. The new `ParsedItem::Skipped { payload, reason }` carries the declared
|
|
144
|
+
payload type; `HttpSource` counts it in `non_iq_packets_skipped` and records
|
|
145
|
+
no break. An undecodable IQ packet, or one whose extent could not be trusted,
|
|
146
|
+
is still `Lost`. (Breaking for exhaustive matches on `ParsedItem`.)
|
|
147
|
+
- **A timestamp that steps backwards is a break, not continuity.**
|
|
148
|
+
`DropDetector::observe` returned `Continuous` for a packet starting before
|
|
149
|
+
the previous one ended, however far, and fed the step into the jitter
|
|
150
|
+
estimate, holding the tolerance at its ceiling afterwards. A backward step
|
|
151
|
+
past the tolerance is now `DropResult::Backward { overlap_seconds }`, counted
|
|
152
|
+
in `DropDetector::backward_steps` and kept out of the jitter estimate and
|
|
153
|
+
the drop totals; `HttpSource` records a `Gap` there and `UnifiedSource`
|
|
154
|
+
flags the buffer. (Breaking for exhaustive matches on `DropResult`.)
|
|
155
|
+
- **`DropDetector::resync` keeps the jitter calibration.** It was cleared on
|
|
156
|
+
every lost packet and reconnect, and since the estimate grows only from
|
|
157
|
+
residuals already judged contiguous, a server whose residuals sit above
|
|
158
|
+
half a packet raised false drops until it was relearned. `reset` still
|
|
159
|
+
clears it.
|
|
160
|
+
- **Compressed spectra decode on a frames × bins grid.** The wavelet grid was
|
|
161
|
+
sized from the square root of one frame's bins, ignoring the frame count
|
|
162
|
+
and dropping bins. Shapes whose layout is undocumented — more than one
|
|
163
|
+
16-spectrum block, depth planes, histograms — are refused instead of
|
|
164
|
+
decoded into a wrong shape.
|
|
165
|
+
- **`HttpSource` no longer outputs spectra, histogram or category scalars as
|
|
166
|
+
IQ.** They were queued with the IQ, fed the drop detector (whose timeline
|
|
167
|
+
they are not on) and overwrote `current_frequency` (a marker declares 0 Hz).
|
|
168
|
+
They are now counted and skipped.
|
|
169
|
+
- **One undecodable packet no longer costs the connection.** `HttpSource`
|
|
170
|
+
skips it, keeps the packets either side, and records a gap where it sat;
|
|
171
|
+
only an unrecoverable framing error still reconnects.
|
|
172
|
+
- **The README's stream-break example is doctested only where it compiles.**
|
|
173
|
+
It uses `HttpSourceBuilder`, which needs the `futuresdr` feature, so
|
|
174
|
+
`cargo test` with default features failed on it; the body is now gated on
|
|
175
|
+
that feature.
|
|
176
|
+
- **A stalled `/stream` reconnects.** The reader waited forever on a socket
|
|
177
|
+
that stayed open and sent nothing; it now gives up after 5 s and the source
|
|
178
|
+
takes its ordinary reconnect path.
|
|
179
|
+
- **A retune swallowed by a restart is announced after it.** The restart
|
|
180
|
+
discarded the queued `Retune` but kept the baseline it had advanced, so the
|
|
181
|
+
new band was never reported. The baseline now rewinds to the last geometry
|
|
182
|
+
reported.
|
|
183
|
+
- **A trim before the first sample publishes one `Initial`**, carrying the
|
|
184
|
+
geometry the first sample arrived under, instead of `Initial` for a
|
|
185
|
+
discarded band followed by `Retune` at the same index.
|
|
186
|
+
- **The first-start tune runs even when the connect/run write fails**, takes
|
|
187
|
+
its target from the builder rather than from packet-tracked fields, and is
|
|
188
|
+
marked done once attempted. The connect/run failure is logged at warn.
|
|
189
|
+
- **`UnifiedSource` HTTP reads no longer split on a derived-rate wobble.** The
|
|
190
|
+
read boundary compared rates with `!=`; it uses the crate's 10% band.
|
|
191
|
+
- **RTSA files:** resync over a corrupt region scans in 64 KiB blocks instead
|
|
192
|
+
of one `seek` per byte (16 MiB of zeroes: 34 s → well under a second); an IQ
|
|
193
|
+
`SAMP` chunk declaring other than one I/Q pair per sample is refused rather
|
|
194
|
+
than read with a mismatched stride; `seek_to_sample` indexes chunks per
|
|
195
|
+
sub-stream, as reads do; and `validate_structure` no longer overflows on an
|
|
196
|
+
absurd sample count.
|
|
197
|
+
- **C ABI:** `spectran_source_builder_force_source_type` takes the source type
|
|
198
|
+
as `int` and ignores an unknown value. Taking the `#[repr(C)]` enum by value
|
|
199
|
+
made an out-of-range value from C undefined behaviour. Source-compatible for
|
|
200
|
+
C and C++ callers passing an enumerator.
|
|
201
|
+
- **Native SDK:** a packet consumed without being delivered (bad stride,
|
|
202
|
+
oversized, undemuxable) now counts in `cumulative_drops` and sets
|
|
203
|
+
`take_overrun`, like a packet the device itself flagged as dropped.
|
|
204
|
+
|
|
205
|
+
### Changed
|
|
206
|
+
- **Gap detection and sample queuing share one pass over each batch.** They
|
|
207
|
+
were two loops, so every packet's gap was measured against the buffer length
|
|
208
|
+
*before* any of the batch had been appended — the right answer only for the
|
|
209
|
+
first packet in it. No change to what is reported, only to where.
|
|
210
|
+
- **Float16 IQ payloads convert in bulk.** The per-sample `f16::to_f32` loop is
|
|
211
|
+
replaced by `half`'s slice conversion, which selects SIMD at runtime on
|
|
212
|
+
AArch64 FP16 and x86 F16C and keeps a portable fallback. Aligned
|
|
213
|
+
little-endian payloads are borrowed in place; an odd-aligned payload — a JSON
|
|
214
|
+
header can leave one at any address — passes through a 1 KiB stack buffer
|
|
215
|
+
rather than a second packet-sized allocation. Big-endian targets keep the
|
|
216
|
+
scalar path. Measured on macOS ARM64 in release, against the scalar
|
|
217
|
+
implementation over three alternating A/B rounds: 36% less time for an
|
|
218
|
+
aligned payload, 10% for an odd-aligned one, at 49 152 and 65 535 IQ pairs.
|
|
219
|
+
Every half-float bit pattern — subnormals, signed zeros and NaN payloads
|
|
220
|
+
included — is compared against the previous implementation at both
|
|
221
|
+
alignments and with odd IQ counts.
|
|
222
|
+
|
|
7
223
|
## [v0.11.2] - 2026-09-11
|
|
8
224
|
|
|
9
225
|
### Added
|
|
@@ -78,7 +78,7 @@ git lfs pull
|
|
|
78
78
|
cargo test --test integration_test
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
Beyond the tiers named here, `tests/` also contains focused suites for the HTTP mock server (`http_mock_test.rs`), the HTTP sink (`http_sink_test.rs`, requires `futuresdr`), the C API (`c_api_test.rs`),
|
|
81
|
+
Beyond the tiers named here, `tests/` also contains focused suites for the HTTP mock server (`http_mock_test.rs`), the HTTP sink (`http_sink_test.rs`, requires `futuresdr`), the C API (`c_api_test.rs`), RTSA negative cases (`rtsa_negative_test.rs`), CW fixtures (`test_cw_mag.rs`, `test_cw_meta.rs`), SDK library loading (`native_sdk_load.rs`), and opt-in live-hardware smoke tests (`live_smoke.rs`). All of these run as part of `cargo test` where their features and environment allow.
|
|
82
82
|
|
|
83
83
|
### 3. Property Tests (`tests/properties.rs`)
|
|
84
84
|
|
|
@@ -1010,28 +1010,6 @@ dependencies = [
|
|
|
1010
1010
|
"itertools",
|
|
1011
1011
|
]
|
|
1012
1012
|
|
|
1013
|
-
[[package]]
|
|
1014
|
-
name = "crossbeam"
|
|
1015
|
-
version = "0.8.4"
|
|
1016
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1017
|
-
checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8"
|
|
1018
|
-
dependencies = [
|
|
1019
|
-
"crossbeam-channel",
|
|
1020
|
-
"crossbeam-deque",
|
|
1021
|
-
"crossbeam-epoch",
|
|
1022
|
-
"crossbeam-queue",
|
|
1023
|
-
"crossbeam-utils",
|
|
1024
|
-
]
|
|
1025
|
-
|
|
1026
|
-
[[package]]
|
|
1027
|
-
name = "crossbeam-channel"
|
|
1028
|
-
version = "0.5.16"
|
|
1029
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1030
|
-
checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e"
|
|
1031
|
-
dependencies = [
|
|
1032
|
-
"crossbeam-utils",
|
|
1033
|
-
]
|
|
1034
|
-
|
|
1035
1013
|
[[package]]
|
|
1036
1014
|
name = "crossbeam-deque"
|
|
1037
1015
|
version = "0.8.7"
|
|
@@ -1051,15 +1029,6 @@ dependencies = [
|
|
|
1051
1029
|
"crossbeam-utils",
|
|
1052
1030
|
]
|
|
1053
1031
|
|
|
1054
|
-
[[package]]
|
|
1055
|
-
name = "crossbeam-queue"
|
|
1056
|
-
version = "0.3.13"
|
|
1057
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1058
|
-
checksum = "803d13fb3b09d88be9f4dbc29062c66b19bf7170867ceb746d2a8689bf6c7a26"
|
|
1059
|
-
dependencies = [
|
|
1060
|
-
"crossbeam-utils",
|
|
1061
|
-
]
|
|
1062
|
-
|
|
1063
1032
|
[[package]]
|
|
1064
1033
|
name = "crossbeam-utils"
|
|
1065
1034
|
version = "0.8.22"
|
|
@@ -1319,7 +1288,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
1319
1288
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
|
1320
1289
|
dependencies = [
|
|
1321
1290
|
"libc",
|
|
1322
|
-
"windows-sys 0.
|
|
1291
|
+
"windows-sys 0.61.2",
|
|
1323
1292
|
]
|
|
1324
1293
|
|
|
1325
1294
|
[[package]]
|
|
@@ -2096,7 +2065,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
|
|
|
2096
2065
|
dependencies = [
|
|
2097
2066
|
"hermit-abi",
|
|
2098
2067
|
"libc",
|
|
2099
|
-
"windows-sys 0.
|
|
2068
|
+
"windows-sys 0.61.2",
|
|
2100
2069
|
]
|
|
2101
2070
|
|
|
2102
2071
|
[[package]]
|
|
@@ -2770,18 +2739,6 @@ dependencies = [
|
|
|
2770
2739
|
"hashbrown 0.14.5",
|
|
2771
2740
|
]
|
|
2772
2741
|
|
|
2773
|
-
[[package]]
|
|
2774
|
-
name = "orecchiette-sdr-source-rs"
|
|
2775
|
-
version = "0.1.3"
|
|
2776
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2777
|
-
checksum = "9a940ff866bdff8ad17ba9b9881c73789c61824ab083d1db56b6b4944fce216b"
|
|
2778
|
-
dependencies = [
|
|
2779
|
-
"anyhow",
|
|
2780
|
-
"crossbeam",
|
|
2781
|
-
"num-complex",
|
|
2782
|
-
"thiserror 2.0.20",
|
|
2783
|
-
]
|
|
2784
|
-
|
|
2785
2742
|
[[package]]
|
|
2786
2743
|
name = "parking"
|
|
2787
2744
|
version = "2.2.1"
|
|
@@ -3092,7 +3049,7 @@ dependencies = [
|
|
|
3092
3049
|
|
|
3093
3050
|
[[package]]
|
|
3094
3051
|
name = "python-aaronia"
|
|
3095
|
-
version = "0.
|
|
3052
|
+
version = "0.13.0"
|
|
3096
3053
|
dependencies = [
|
|
3097
3054
|
"arrow",
|
|
3098
3055
|
"num-complex",
|
|
@@ -3163,7 +3120,7 @@ dependencies = [
|
|
|
3163
3120
|
"once_cell",
|
|
3164
3121
|
"socket2",
|
|
3165
3122
|
"tracing",
|
|
3166
|
-
"windows-sys 0.
|
|
3123
|
+
"windows-sys 0.61.2",
|
|
3167
3124
|
]
|
|
3168
3125
|
|
|
3169
3126
|
[[package]]
|
|
@@ -3469,14 +3426,14 @@ dependencies = [
|
|
|
3469
3426
|
"errno",
|
|
3470
3427
|
"libc",
|
|
3471
3428
|
"linux-raw-sys",
|
|
3472
|
-
"windows-sys 0.
|
|
3429
|
+
"windows-sys 0.61.2",
|
|
3473
3430
|
]
|
|
3474
3431
|
|
|
3475
3432
|
[[package]]
|
|
3476
3433
|
name = "rustls"
|
|
3477
|
-
version = "0.23.
|
|
3434
|
+
version = "0.23.45"
|
|
3478
3435
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3479
|
-
checksum = "
|
|
3436
|
+
checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634"
|
|
3480
3437
|
dependencies = [
|
|
3481
3438
|
"aws-lc-rs",
|
|
3482
3439
|
"once_cell",
|
|
@@ -3526,7 +3483,7 @@ dependencies = [
|
|
|
3526
3483
|
"security-framework",
|
|
3527
3484
|
"security-framework-sys",
|
|
3528
3485
|
"webpki-root-certs",
|
|
3529
|
-
"windows-sys 0.
|
|
3486
|
+
"windows-sys 0.61.2",
|
|
3530
3487
|
]
|
|
3531
3488
|
|
|
3532
3489
|
[[package]]
|
|
@@ -3621,21 +3578,19 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
|
|
3621
3578
|
|
|
3622
3579
|
[[package]]
|
|
3623
3580
|
name = "sdr-aaronia-rs"
|
|
3624
|
-
version = "0.
|
|
3581
|
+
version = "0.13.0"
|
|
3625
3582
|
dependencies = [
|
|
3626
3583
|
"anyhow",
|
|
3627
3584
|
"bitflags 2.13.1",
|
|
3628
3585
|
"byteorder",
|
|
3629
3586
|
"bytes",
|
|
3630
3587
|
"criterion",
|
|
3631
|
-
"crossbeam-channel",
|
|
3632
3588
|
"futures",
|
|
3633
3589
|
"futures-timer",
|
|
3634
3590
|
"futuresdr",
|
|
3635
3591
|
"half",
|
|
3636
3592
|
"libloading",
|
|
3637
3593
|
"num-complex",
|
|
3638
|
-
"orecchiette-sdr-source-rs",
|
|
3639
3594
|
"proptest",
|
|
3640
3595
|
"reqwest",
|
|
3641
3596
|
"seify",
|
|
@@ -4203,10 +4158,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
4203
4158
|
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
|
4204
4159
|
dependencies = [
|
|
4205
4160
|
"fastrand",
|
|
4206
|
-
"getrandom 0.4
|
|
4161
|
+
"getrandom 0.3.4",
|
|
4207
4162
|
"once_cell",
|
|
4208
4163
|
"rustix",
|
|
4209
|
-
"windows-sys 0.
|
|
4164
|
+
"windows-sys 0.61.2",
|
|
4210
4165
|
]
|
|
4211
4166
|
|
|
4212
4167
|
[[package]]
|
|
@@ -4864,7 +4819,7 @@ version = "0.1.11"
|
|
|
4864
4819
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
4865
4820
|
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
|
4866
4821
|
dependencies = [
|
|
4867
|
-
"windows-sys 0.
|
|
4822
|
+
"windows-sys 0.61.2",
|
|
4868
4823
|
]
|
|
4869
4824
|
|
|
4870
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.0"
|
|
6
6
|
edition = "2024"
|
|
7
7
|
repository = "https://github.com/isaacbentley/sdr-aaronia-rs"
|
|
8
8
|
readme = "README.md"
|
|
@@ -11,7 +11,7 @@ categories = ["hardware-support", "api-bindings", "science"]
|
|
|
11
11
|
exclude = ["tests/*.rtsa", "tests/sdk/", "tests/asan/", ".github/"]
|
|
12
12
|
|
|
13
13
|
[package.metadata.docs.rs]
|
|
14
|
-
features = ["http", "file", "
|
|
14
|
+
features = ["http", "file", "ffi", "native-sdk"]
|
|
15
15
|
rustdoc-args = ["--cfg", "docsrs"]
|
|
16
16
|
|
|
17
17
|
[lib]
|
|
@@ -42,8 +42,6 @@ futures-timer = { version = "3.0", optional = true }
|
|
|
42
42
|
num-complex = "0.4"
|
|
43
43
|
tempfile = { version = "3.8", optional = true }
|
|
44
44
|
anyhow = { version = "1.0", optional = true }
|
|
45
|
-
crossbeam-channel = { version = "0.5", optional = true }
|
|
46
|
-
orecchiette-sdr-source-rs = { version = "0.1.3", optional = true }
|
|
47
45
|
# default-features = false: seify's defaults include its own driver
|
|
48
46
|
# backends (soapy -> soapysdr-sys, which needs system SoapySDR +
|
|
49
47
|
# libclang at build time and is BSL-1.0-licensed). We only need the
|
|
@@ -101,12 +99,11 @@ name = "http_sink_test"
|
|
|
101
99
|
required-features = ["futuresdr"]
|
|
102
100
|
|
|
103
101
|
[features]
|
|
104
|
-
default = ["http", "file", "
|
|
102
|
+
default = ["http", "file", "ffi"]
|
|
105
103
|
http = ["dep:reqwest", "dep:tokio", "dep:serde", "dep:serde_json",
|
|
106
104
|
"dep:url", "dep:bytes", "dep:futures", "dep:half"]
|
|
107
105
|
file = ["dep:byteorder", "dep:bitflags", "dep:tempfile"]
|
|
108
106
|
futuresdr = ["http", "dep:futuresdr", "dep:futures-timer", "dep:anyhow"]
|
|
109
|
-
sdr-source = ["http", "file", "dep:crossbeam-channel", "dep:anyhow", "dep:orecchiette-sdr-source-rs"]
|
|
110
107
|
seify = ["http", "file", "dep:seify"]
|
|
111
108
|
ffi = ["http", "file"]
|
|
112
109
|
native-sdk = ["dep:libloading", "dep:widestring"]
|
|
@@ -135,9 +132,6 @@ required-features = ["http"]
|
|
|
135
132
|
name = "read_rtsa_file"
|
|
136
133
|
required-features = ["file"]
|
|
137
134
|
|
|
138
|
-
[[example]]
|
|
139
|
-
name = "channel_hopping"
|
|
140
|
-
required-features = ["sdr-source"]
|
|
141
135
|
|
|
142
136
|
[target.'cfg(any(target_os = "windows", target_os = "linux"))'.dependencies]
|
|
143
137
|
libloading = { version = "0.7", optional = true }
|
|
@@ -145,3 +139,7 @@ widestring = { version = "1.1", optional = true }
|
|
|
145
139
|
|
|
146
140
|
[workspace]
|
|
147
141
|
members = ["python-aaronia"]
|
|
142
|
+
|
|
143
|
+
[[example]]
|
|
144
|
+
name = "native_sdk_qualification"
|
|
145
|
+
required-features = ["native-sdk", "http", "file"]
|
|
@@ -69,22 +69,20 @@ Available on Windows and Linux only, behind the non-default `native-sdk` feature
|
|
|
69
69
|
|
|
70
70
|
## 5. Channel Hopping and Dwell Control
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
Retuning mid-stream is `SpectranSource::set_center_frequency_hz`, per backend:
|
|
73
73
|
|
|
74
74
|
- **Native SDK**: re-issues `configure_iq_receiver` with the new center frequency, applying the change to the open device handle via the `main/centerfreq` configuration key. No stream restart is required.
|
|
75
75
|
- **HTTP**: `SpectranSource::set_center_frequency_hz` wraps `HttpEndpointsClient::configure_capture` as a `PUT` to the license-free `/control` endpoint, always sending the complete capture tuple (center, span, reference level) with unchanged values filled from the cached config. The full tuple is required: RTSA servers silently ignore a capture `PUT` carrying only one of the two frequency fields (it returns `{"success":true}` but the device stays put — live-verified). Hopping deliberately avoids `/remoteconfig` and is not gated on the license probe. (Whether that path needs the "Remote Config" license at all is unconfirmed: a system without the license accepted `/remoteconfig` writes in testing. See docs/HTTPSPEC.md.)
|
|
76
76
|
- **File**: not supported.
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
The hop loop itself — which channel next, how long to dwell there, and the ~75 ms settle after a retune that rides out the RTSA's apply-config latency and flushes stale samples — is the consumer's. Until 0.12.0 this crate carried one behind the `sdr-source` feature (`SpectranSdrSource`, an implementation of `orecchiette-sdr-source-rs`'s `SdrSource` trait); it lives beside its consumer now (Specola's `sdr-aaronia-source`), and a caller with its own trait builds the same thing from `SpectranSourceBuilder`.
|
|
79
79
|
|
|
80
80
|
## 6. Overrun Detection and Fault Handling
|
|
81
81
|
|
|
82
|
-
The HTTP reader task (spawned by `init_http_source` in `unified_source.rs`) runs a `DropDetector` over each packet's `start_time`/`end_time` metadata. A timestamp gap larger than the tolerance latches `SpectranSource::pending_overrun`, which `take_overrun()` reads and clears.
|
|
82
|
+
The HTTP reader task (spawned by `init_http_source` in `unified_source.rs`) runs a `DropDetector` over each packet's `start_time`/`end_time` metadata. A timestamp gap larger than the tolerance latches `SpectranSource::pending_overrun`, which `take_overrun()` reads and clears. A consumer's read loop calls `take_overrun()` once per block it hands on, so a drop detected anywhere since the last read surfaces on the next block (Specola's adapter sets its `IqPacket::overrun` from it). This is a per-call signal, not a precise per-sample one, since drop timing is lost once chunks merge into the flat `sample_buffer`.
|
|
83
83
|
|
|
84
84
|
The native-SDK and file backends do not populate this yet (`overrun` is always `false` for them). Native-SDK overrun detection would need the overflow/dropped warning bits from the packet `flags` field. The RX path reads and logs those at debug level, but does not yet latch them into `pending_overrun`.
|
|
85
85
|
|
|
86
|
-
The Aaronia capture thread (`SpectranSdrSource::start`) is wrapped in `catch_unwind`: a panic inside the pump loop is logged rather than silently unwinding the thread.
|
|
87
|
-
|
|
88
86
|
## 7. FutureSDR Integration
|
|
89
87
|
|
|
90
88
|
FutureSDR integration is available under the `futuresdr` feature gate. The crate exposes lower-level builder variants (e.g. `HttpSourceBuilder`) so `HttpSource` can be used as a source block in FutureSDR flowgraphs:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-aaronia
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.0
|
|
4
4
|
Classifier: Programming Language :: Rust
|
|
5
5
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
6
6
|
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
@@ -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 |
|