python-aaronia 0.12.0__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.
Files changed (95) hide show
  1. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/CHANGELOG.md +54 -0
  2. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/Cargo.lock +12 -12
  3. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/Cargo.toml +5 -1
  4. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/PKG-INFO +33 -30
  5. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/README.md +36 -2
  6. python_aaronia-0.13.0/docs/NATIVE_SDK_COMPATIBILITY.md +99 -0
  7. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/docs/SDKSPEC.md +29 -43
  8. python_aaronia-0.13.0/examples/native_sdk_qualification.rs +94 -0
  9. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/include/spectran.h +7 -0
  10. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/python-aaronia/Cargo.toml +1 -1
  11. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/python-aaronia/README.md +32 -29
  12. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/python-aaronia/aaronia.pyi +16 -0
  13. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/python-aaronia/src/lib.rs +86 -0
  14. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/python-aaronia/test_basic.py +26 -0
  15. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/scripts/ci-local.sh +1 -0
  16. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/soapy-aaronia/README.md +27 -23
  17. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/soapy-aaronia/Registration.cpp +15 -1
  18. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/soapy-aaronia/SpectranSoapyDevice.cpp +32 -8
  19. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/src/c_api.rs +133 -0
  20. python_aaronia-0.13.0/src/capabilities.rs +206 -0
  21. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/src/error.rs +12 -0
  22. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/src/http_endpoints.rs +12 -97
  23. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/src/http_streaming.rs +7 -1
  24. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/src/lib.rs +2 -0
  25. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/src/native_sdk.rs +449 -109
  26. python_aaronia-0.13.0/src/native_sdk_lifecycle_tests.rs +374 -0
  27. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/src/sdk_source.rs +5 -12
  28. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/src/seify_impl.rs +32 -12
  29. python_aaronia-0.13.0/src/stream_geometry.rs +125 -0
  30. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/src/unified_source.rs +256 -29
  31. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/tests/spec_coverage.rs +73 -14
  32. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/.cargo/config.toml +0 -0
  33. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/.gitattributes +0 -0
  34. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/.gitignore +0 -0
  35. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/CONTRIBUTING.md +0 -0
  36. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/DESIGN.md +0 -0
  37. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/LICENSE +0 -0
  38. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/PLUGINS.md +0 -0
  39. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/benches/decompress_block.rs +0 -0
  40. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/benches/deinterleave_dual_iq.rs +0 -0
  41. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/benches/parse_int16_packet.rs +0 -0
  42. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/benches/rtsa_open_and_read.rs +0 -0
  43. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/deny.toml +0 -0
  44. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/docs/APPS.md +0 -0
  45. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/docs/FILESPEC.md +0 -0
  46. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/docs/HTTPSPEC.md +0 -0
  47. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/docs/QUICKSTART.md +0 -0
  48. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/docs/SYNC.md +0 -0
  49. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/docs/USAGE.md +0 -0
  50. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/docs/VERIFICATION.md +0 -0
  51. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/examples/device_control.rs +0 -0
  52. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/examples/dump_metadata.rs +0 -0
  53. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/examples/http_iq_quickstart.rs +0 -0
  54. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/examples/native_sdk_basic.rs +0 -0
  55. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/examples/native_sdk_transmit.rs +0 -0
  56. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/examples/noaa_scanner.rs +0 -0
  57. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/examples/python_arrow_example.py +0 -0
  58. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/examples/read_rtsa_file.rs +0 -0
  59. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/examples/soapy_python_example.py +0 -0
  60. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/packaging/homebrew/README.md +0 -0
  61. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/packaging/homebrew/soapy-aaronia.rb +0 -0
  62. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/pyproject.toml +0 -0
  63. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/scripts/fake-rtsa-server.py +0 -0
  64. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/scripts/native-sdk-validate.ps1 +0 -0
  65. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/scripts/sdk-container-test.sh +0 -0
  66. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/scripts/validate-iq-live.py +0 -0
  67. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/soapy-aaronia/CMakeLists.txt +0 -0
  68. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/soapy-aaronia/SpectranSoapyDevice.hpp +0 -0
  69. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/soapy-aaronia/packaging/install.ps1 +0 -0
  70. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/soapy-aaronia/packaging/install.sh +0 -0
  71. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/src/decompression.rs +0 -0
  72. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/src/detection.rs +0 -0
  73. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/src/file_source.rs +0 -0
  74. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/src/http_sink.rs +0 -0
  75. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/src/http_source.rs +0 -0
  76. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/src/link_budget.rs +0 -0
  77. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/src/sdk_sink.rs +0 -0
  78. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/src/stream_break.rs +0 -0
  79. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/src/unified_sink.rs +0 -0
  80. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/src/utils.rs +0 -0
  81. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/tests/abi_drift.rs +0 -0
  82. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/tests/c_api_test.rs +0 -0
  83. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/tests/http_mock_test.rs +0 -0
  84. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/tests/http_resilience_test.rs +0 -0
  85. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/tests/http_sink_test.rs +0 -0
  86. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/tests/integration_test.rs +0 -0
  87. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/tests/live_smoke.rs +0 -0
  88. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/tests/native_sdk_live.rs +0 -0
  89. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/tests/native_sdk_load.rs +0 -0
  90. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/tests/properties.proptest-regressions +0 -0
  91. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/tests/properties.rs +0 -0
  92. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/tests/rtsa_negative_test.rs +0 -0
  93. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/tests/test_cw_mag.rs +0 -0
  94. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/tests/test_cw_meta.rs +0 -0
  95. {python_aaronia-0.12.0 → python_aaronia-0.13.0}/tests/wire_contract.rs +0 -0
@@ -4,6 +4,60 @@ 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
+
7
61
  ## [v0.12.0] - 2026-09-29
8
62
 
9
63
  ### 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.52.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.52.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.12.0"
3052
+ version = "0.13.0"
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.52.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.52.0",
3429
+ "windows-sys 0.61.2",
3430
3430
  ]
3431
3431
 
3432
3432
  [[package]]
3433
3433
  name = "rustls"
3434
- version = "0.23.43"
3434
+ version = "0.23.45"
3435
3435
  source = "registry+https://github.com/rust-lang/crates.io-index"
3436
- checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06"
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.52.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.12.0"
3581
+ version = "0.13.0"
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.3",
4161
+ "getrandom 0.3.4",
4162
4162
  "once_cell",
4163
4163
  "rustix",
4164
- "windows-sys 0.52.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.52.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.12.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"
@@ -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.12.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, which everything
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 rates
108
+ ## Sample rate and RF span
110
109
 
111
- The device runs a ladder of rates rather than a continuous range: each
112
- rung is half the one above it. Ask for anything else and it quietly
113
- uses the nearest rung, leaving your program computing against a rate
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.sample_rates() # every rate, highest first
118
- aaronia.sample_rate_for_bandwidth(8e6) # 15.36e6: the lowest rate covering 8 MHz
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
- **Sample rate is not RF bandwidth.** You get every sample, so an FFT of
122
- them spans the full rate — but only the middle 80% is flat and
123
- calibrated. That is not an approximation: RTSA reports exactly 0.8 x Fs
124
- as the packet's frequency range at every rate. Outside it, data still
125
- arrives, attenuated and uncalibrated.
126
-
127
- So **to see N Hz of spectrum, sample at N / 0.8**, which is what
128
- `sample_rate_for_bandwidth()` computes. Aaronia's data sheet is more
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.12"
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.12", features = ["native-sdk", "futuresdr"] }
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,16 @@ 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
+ ## Current driver behavior (0.13)
10
+
11
+ See [Native SDK compatibility and qualification](NATIVE_SDK_COMPATIBILITY.md)
12
+ for the current configuration, lifecycle and binding contract. Older empirical
13
+ measurements below describe their particular mode/device, not every V6 variant.
14
+ IQ `stepFrequency` is the actual sample clock; `spanFrequency` is valid RF span;
15
+ centre is `startFrequency + spanFrequency / 2`. The driver carries these values
16
+ with the returned IQ, never replacing RF span with a universal clock ratio.
17
+ SDK ConfigInfo bounds can exceed a model's rated bandwidth.
18
+
9
19
  ## Table of Contents
10
20
 
11
21
  - [Features and Purpose](#features-and-purpose)
@@ -53,7 +63,7 @@ The Aaronia RTSA Vendor SDK provides low-level, high-performance access to Aaron
53
63
  * **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
64
  * **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
65
  * **Decimation**: Full, 1/2, 1/4, 1/8, 1/16, 1/32, 1/64, 1/128, 1/256, 1/512
56
- * **IQ Mode Sample Rate**: equal to `spanfreq` (constraint: `spanfreq ≤ receiverclock / 1.5`). Measured on a V6 ECO, untested on a full V6, and not obviously consistent with Aaronia's own 245 MHz / 250 Msample figures for the V6 — see [HTTPSPEC](HTTPSPEC.md#unresolved-the-full-v6s-top-rate)
66
+ * **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
67
  * **Health Monitoring**: Real-time device status including temperatures, sample rates, power levels, USB statistics, GPS data
58
68
 
59
69
  ## Verified Architecture
@@ -396,7 +406,7 @@ a `/2ⁿ` ladder: a 10 MHz request streams at 15.0 MS/s, 15.36 at 23.04,
396
406
  Every rung of a request→delivered sweep was ×1.5 to the third decimal.
397
407
  This crate's `sample_rate_hz` is the sample rate (the HTTP backend
398
408
  delivers exactly it), so `configure_iq_receiver` writes `rate / 1.5` on
399
- this mode and the caller gets the rate it named on every backend; a
409
+ this mode as a compatibility request, subject to the observed ceiling; a
400
410
  15.36 MS/s request now measures 15.360 MS/s, and `sample_rate_hz` reports
401
411
  what the packets carry. Two more measured facts: the pipeline delivers
402
412
  ~40% of rate for ~5 s after start and flags one `TIME_DISCONTINUITY` as
@@ -405,47 +415,23 @@ it settles, then holds 100.0% with no further flags; and it sets
405
415
  which is why that flag is not treated as an overrun. `spectranv6/raw` on
406
416
  a full V6 has not been measured and is left untranslated.
407
417
 
408
- ### IQ-mode receiver clock constraint
409
-
410
- `NativeSdkSource::configure_iq_receiver` calls
411
- `utils::validate_iq_mode(rate, clock)` twice, and the order matters.
412
-
413
- The gate runs **before the first config write**, against the clock the
414
- call *leaves in place* — which is not always the one the device holds on
415
- entry:
416
-
417
- - On `spectranv6/raw`, the function writes `device/receiverclock` itself
418
- (`DEFAULT_RECEIVER_CLOCK_LABEL`, `"92MHz"`, = 92.16 MHz), so that write
419
- decides the clock and the check uses it. Checking the *current* setting
420
- here would pass a rate the incoming clock cannot carry: a V6 left on
421
- 245.76 MHz would accept 150 MS/s and then have the clock pulled down to
422
- 92.16 MHz underneath it.
423
- - On every other open mode the clock write is skipped
424
- (`DeviceOpenMode::supports_raw_only_keys` is true only for `Raw`), so
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.
418
+ ### Mode-specific validation (0.13)
419
+
420
+ IQ receiver requests validate `main/spanfreq` against the opened mode's ConfigInfo
421
+ before writes. They do not use a default 92 MHz raw clock as a ceiling. Explicit
422
+ RF span avoids the legacy Fs/1.5 translation; packet geometry reports what was
423
+ actually delivered, including the independent USB/IQ ceiling.
424
+
425
+ Raw modes retain the legacy default clock/rate validation for callers that do not
426
+ request explicit raw controls. `NativeReceiverOptions.receiver_clock` and
427
+ `decimation_factor` instead select enabled device-provided enum options; they do
428
+ not apply the legacy 1.5× check to a wider raw pipeline. Actual rate comes from
429
+ packets. A decimation-only request preserves the currently reported clock when
430
+ available. Unknown capabilities remain unknown; the legacy `receiver_clock_hz()`
431
+ fallback is not proof of the installed hardware's clock.
432
+
433
+ The ConfigItem labels are rounded (`"92MHz"` is 92.16 MHz). The legacy conversion
434
+ table is retained below; explicit labels are validated against the device options.
449
435
 
450
436
  | Label | Actual rate | Source |
451
437
  |----------|----------------|--------------------|
@@ -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".
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "python-aaronia"
3
- version = "0.12.0"
3
+ version = "0.13.0"
4
4
  edition = "2024"
5
5
  description = "Python bindings for sdr-aaronia-rs (Aaronia SPECTRAN V6 SDR source)"
6
6
  license = "GPL-3.0-or-later"