python-aaronia 0.13.0__tar.gz → 0.13.3__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 (97) hide show
  1. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/CHANGELOG.md +41 -0
  2. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/Cargo.lock +14 -14
  3. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/Cargo.toml +58 -10
  4. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/PKG-INFO +4 -1
  5. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/docs/SDKSPEC.md +8 -0
  6. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/examples/native_sdk_basic.rs +4 -2
  7. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/examples/native_sdk_qualification.rs +4 -2
  8. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/examples/native_sdk_transmit.rs +4 -2
  9. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/include/spectran.h +33 -16
  10. python_aaronia-0.13.3/packaging/homebrew/soapy-aaronia.rb +76 -0
  11. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/pyproject.toml +4 -0
  12. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/python-aaronia/Cargo.toml +1 -1
  13. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/python-aaronia/aaronia.pyi +7 -2
  14. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/python-aaronia/src/lib.rs +309 -159
  15. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/python-aaronia/test_basic.py +31 -0
  16. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/scripts/ci-local.sh +2 -1
  17. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/soapy-aaronia/CMakeLists.txt +12 -4
  18. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/soapy-aaronia/README.md +1 -1
  19. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/soapy-aaronia/Registration.cpp +26 -2
  20. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/soapy-aaronia/SpectranSoapyDevice.cpp +163 -53
  21. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/soapy-aaronia/SpectranSoapyDevice.hpp +5 -0
  22. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/src/c_api.rs +1524 -964
  23. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/src/capabilities.rs +2 -1
  24. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/src/decompression.rs +10 -10
  25. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/src/detection.rs +84 -31
  26. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/src/file_source.rs +162 -36
  27. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/src/http_endpoints.rs +24 -1
  28. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/src/http_sink.rs +45 -37
  29. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/src/http_source.rs +182 -84
  30. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/src/http_streaming.rs +154 -49
  31. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/src/lib.rs +48 -17
  32. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/src/link_budget.rs +1 -0
  33. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/src/native_sdk.rs +635 -208
  34. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/src/native_sdk_lifecycle_tests.rs +149 -15
  35. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/src/sdk_sink.rs +152 -27
  36. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/src/sdk_source.rs +84 -28
  37. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/src/seify_impl.rs +145 -57
  38. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/src/stream_geometry.rs +16 -2
  39. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/src/unified_sink.rs +148 -18
  40. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/src/unified_source.rs +523 -306
  41. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/src/utils.rs +165 -27
  42. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/tests/abi_drift.rs +32 -10
  43. python_aaronia-0.13.3/tests/c_api_test.rs +228 -0
  44. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/tests/http_sink_test.rs +33 -0
  45. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/tests/native_sdk_live.rs +2 -1
  46. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/tests/native_sdk_load.rs +2 -1
  47. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/tests/spec_coverage.rs +12 -0
  48. python_aaronia-0.13.0/packaging/homebrew/soapy-aaronia.rb +0 -47
  49. python_aaronia-0.13.0/tests/c_api_test.rs +0 -107
  50. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/.cargo/config.toml +0 -0
  51. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/.gitattributes +0 -0
  52. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/.gitignore +0 -0
  53. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/CONTRIBUTING.md +0 -0
  54. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/DESIGN.md +0 -0
  55. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/LICENSE +0 -0
  56. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/PLUGINS.md +0 -0
  57. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/README.md +0 -0
  58. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/benches/decompress_block.rs +0 -0
  59. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/benches/deinterleave_dual_iq.rs +0 -0
  60. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/benches/parse_int16_packet.rs +0 -0
  61. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/benches/rtsa_open_and_read.rs +0 -0
  62. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/deny.toml +0 -0
  63. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/docs/APPS.md +0 -0
  64. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/docs/FILESPEC.md +0 -0
  65. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/docs/HTTPSPEC.md +0 -0
  66. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/docs/NATIVE_SDK_COMPATIBILITY.md +0 -0
  67. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/docs/QUICKSTART.md +0 -0
  68. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/docs/SYNC.md +0 -0
  69. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/docs/USAGE.md +0 -0
  70. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/docs/VERIFICATION.md +0 -0
  71. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/examples/device_control.rs +0 -0
  72. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/examples/dump_metadata.rs +0 -0
  73. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/examples/http_iq_quickstart.rs +0 -0
  74. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/examples/noaa_scanner.rs +0 -0
  75. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/examples/python_arrow_example.py +0 -0
  76. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/examples/read_rtsa_file.rs +0 -0
  77. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/examples/soapy_python_example.py +0 -0
  78. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/packaging/homebrew/README.md +0 -0
  79. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/python-aaronia/README.md +0 -0
  80. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/scripts/fake-rtsa-server.py +0 -0
  81. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/scripts/native-sdk-validate.ps1 +0 -0
  82. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/scripts/sdk-container-test.sh +0 -0
  83. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/scripts/validate-iq-live.py +0 -0
  84. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/soapy-aaronia/packaging/install.ps1 +0 -0
  85. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/soapy-aaronia/packaging/install.sh +0 -0
  86. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/src/error.rs +0 -0
  87. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/src/stream_break.rs +0 -0
  88. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/tests/http_mock_test.rs +0 -0
  89. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/tests/http_resilience_test.rs +0 -0
  90. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/tests/integration_test.rs +0 -0
  91. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/tests/live_smoke.rs +0 -0
  92. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/tests/properties.proptest-regressions +0 -0
  93. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/tests/properties.rs +0 -0
  94. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/tests/rtsa_negative_test.rs +0 -0
  95. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/tests/test_cw_mag.rs +0 -0
  96. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/tests/test_cw_meta.rs +0 -0
  97. {python_aaronia-0.13.0 → python_aaronia-0.13.3}/tests/wire_contract.rs +0 -0
@@ -4,6 +4,47 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [v0.13.3] - 2026-11-10
8
+
9
+ ### Added
10
+ - `SpectranSource::new_with_options`, `SpectranSourceBuilder::listen_only`, `spectran_source_builder_listen_only`, SoapySDR `listen_only` device arg, and Python `SpectranSource.listen_only` to connect to HTTP streams without issuing `/remoteconfig` or `/control` tuning writes.
11
+ - `SdkSink` and `SpectranSink` serial selection (`device_serial` / `spectran_sink_builder_device_serial`) plus C ABI TX controls (`spectran_sink_set_center_frequency_hz`, `spectran_sink_set_sample_rate_hz`, `spectran_sink_set_trans_gain_db`, `spectran_sink_get_master_stream_time_ns`) and full SoapySDR TX frequency, sample-rate, gain, serial, and burst-timestamp support.
12
+ - `AARONIA_NATIVE_SDK=ON` enabled by default on 64-bit Linux and Windows SoapySDR builds (including CI and release artifacts), and crate-root re-exports completed across `sdr-aaronia-rs`.
13
+
14
+ ### Fixed
15
+ - Wrapped all 48 `extern "C" spectran_*` entry points in `catch_unwind`, made `FFI_RUNTIME` initialization fallible and `LocalSet`-safe, saturated `Instant` read deadlines, and tightened `SpectranSource *` pointer typing and alignment checks in `include/spectran.h` and `src/c_api.rs`.
16
+ - Serialized concurrent `&self` C++ config-tree queries on `NativeSdk` via `device_query_lock`, bounded `num * stride` and validated `fp32` alignment and negative `packet.size` in `read_samples_within`, guarded null config nodes and NUL-terminated device serials, preserved SDK refcounts and intentionally leaked `libloading::Library` on failed teardown, rolled back partial device/packet state on errors, supported `iqtransceiver` RX mode, and reset carry buffers on `set_rf_span_hz`.
17
+ - Replaced `PySpectranSource` `RefCell` state with a `Mutex` acquired exclusively inside `py.detach(...)` and detached the GIL with a bounded `shutdown_timeout` in `Drop`, eliminating re-entrant borrow panics, `pyo3-log` ABBA GIL deadlocks, and runtime teardown hangs; declared `numpy` and optional `pyarrow` dependencies in `python-aaronia/pyproject.toml`.
18
+ - Eliminated the `StreamParser::skip_bytes` black-hole DoS when binary packet headers exceed `MAX_BINARY_PAYLOAD` by resynchronizing via `find_next_header`, sanitized `NaN`/`±Inf` binary floats to `0.0`, trimmed trailing URL slashes, validated `http`/`https` schemes, skipped redundant `/remoteconfig` writes + 500 ms sleeps on HTTP reconnect, dynamically discovered `Block_*` nodes while tolerating unlicensed `/remoteconfig`, and supported wideband V6 HTTP sample rates.
19
+ - Flushed queued HTTP TX batches before `FutureSDR` sink teardown instead of aborting in-flight frames, and fixed `SdkSinkBlock` zero-burst livelocks, monotonic burst timestamp scheduling, and `io.call_again` signaling.
20
+ - Added single-wavelet-block decompression support for compressed `.rtsa` `Spectra` chunks, pinned default multi-substream file cursor reads/seeks to the primary sub-stream, skipped non-IQ `Spectra` chunks in `SpectranSource` file reads, and stepped up out of `<install>/sdk` when locating RTSA XML configs and `RTSAFileTool`.
21
+ - Mounted `.dmg` disk images via `hdiutil` in `packaging/homebrew/soapy-aaronia.rb` with architecture guards and dynamic SoapySDR module ABI detection.
22
+ - Updated `rustls` (`0.23.46`), `rustls-webpki` (`0.103.16`), `tokio` (`1.54.1`), `webpki-root-certs` (`1.0.12`), and `async-executor` (`1.14.1`) in `Cargo.lock` to resolve RustSec advisories and yanked crate entries.
23
+
24
+ ## [v0.13.2] - 2026-10-15
25
+
26
+ ### Fixed
27
+ - `HttpEndpointsClient` now configures `.no_proxy()` on `reqwest::Client`, preventing corporate/system `HTTP_PROXY` environment variables from hijacking localhost or LAN RTSA-Suite connections.
28
+ - `HttpSource` (`FutureSDR` block) wraps its async `init`/`work` bodies in a poll-scoped Tokio context guard (`WithTokioHandle`) instead of calling `Handle::block_on`, preventing nested runtime panics when driven inside a Tokio worker thread.
29
+ - `HttpSource::apply_initial_tune` now falls back to `/control` (`configure_capture`) when `/remoteconfig` returns HTTP 200 without applying `center_frequency_hz` or `decimation_index` (unlicensed `/remoteconfig`; see https://aaronia.com/en/software-licence-remote-config) as well as on transport errors.
30
+ - Guarded HTTP streaming metadata and sample payloads (`float16`, `float32`, spectrum, and JSON paths) against non-finite (`NaN`/`±Inf`) values so corrupt headers or sample words cannot poison downstream DSP state or observed stream geometry.
31
+ - `NativeSdkSource` now clears carry-over sample buffers on `configure_iq_receiver_with_options`, uses precision-preserving `epoch_seconds_to_nanos` for `last_timestamp_ns`, populates `device_mode` in `device_capabilities`, and prevents empty or invalid-stride packets from prematurely latching `got_packet` during deadline reads.
32
+ - `SpectranSource::read_samples` now honours `config.read_timeout` on the `NativeSdk` backend via `read_samples_deadline`, validates finite tuning inputs across all backends, and clears stale `rf_span_hz`/`decimation_factor` overrides when `set_sample_rate_hz` is called.
33
+ - Hardened RTSA chunk parsers (`STRM`, `SAMP`, `SSTR`, `ANTA`, `MDTT`) with minimum header size checks to reject truncated chunk headers before seeking.
34
+ - `SpectranSeifyDevice` now uses `tokio::task::block_in_place` when called from a multi-thread Tokio runtime, recovers poisoned mutex locks, and reports observed file/stream frequencies in `frequency()`.
35
+ - `python-aaronia` now clears `force_source_type` when `http_base_url` or `file_path` is reset to `None`, and stops any active stream on `start_streaming` re-entry or `Drop`.
36
+ - macOS SoapySDR release artifacts are now packaged as `.dmg` disk images instead of `.tar.gz`, and macOS Python wheels build cleanly without the Linux/Windows-only `native-sdk` feature.
37
+
38
+ ## [v0.13.1] - 2026-10-08
39
+
40
+ ### Added
41
+ - `NativeSdkSource::configure_sweepsa_with_reference_level` applies an optional
42
+ reference level to native spectrum sweeps, backported from Specola's vendor
43
+ update. Explicit levels require valid SDK bounds and are checked before any
44
+ configuration write; unsupported/missing settings fail instead of being ignored.
45
+ The existing `SweepsaConfig` layout and `configure_sweepsa` API remain compatible.
46
+
47
+
7
48
  ## [v0.13.0] - 2026-10-08
8
49
 
9
50
  ### Added
@@ -376,9 +376,9 @@ dependencies = [
376
376
 
377
377
  [[package]]
378
378
  name = "async-executor"
379
- version = "1.14.0"
379
+ version = "1.14.1"
380
380
  source = "registry+https://github.com/rust-lang/crates.io-index"
381
- checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a"
381
+ checksum = "f426a31126c735126297bc5ea7a521263c9faff4f63ca415267ee625651c4f37"
382
382
  dependencies = [
383
383
  "async-task",
384
384
  "concurrent-queue",
@@ -703,9 +703,9 @@ checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
703
703
 
704
704
  [[package]]
705
705
  name = "chacha20"
706
- version = "0.10.1"
706
+ version = "0.10.2"
707
707
  source = "registry+https://github.com/rust-lang/crates.io-index"
708
- checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"
708
+ checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06"
709
709
  dependencies = [
710
710
  "cfg-if",
711
711
  "cpufeatures 0.3.0",
@@ -3049,7 +3049,7 @@ dependencies = [
3049
3049
 
3050
3050
  [[package]]
3051
3051
  name = "python-aaronia"
3052
- version = "0.13.0"
3052
+ version = "0.13.3"
3053
3053
  dependencies = [
3054
3054
  "arrow",
3055
3055
  "num-complex",
@@ -3431,9 +3431,9 @@ dependencies = [
3431
3431
 
3432
3432
  [[package]]
3433
3433
  name = "rustls"
3434
- version = "0.23.45"
3434
+ version = "0.23.46"
3435
3435
  source = "registry+https://github.com/rust-lang/crates.io-index"
3436
- checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634"
3436
+ checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf"
3437
3437
  dependencies = [
3438
3438
  "aws-lc-rs",
3439
3439
  "once_cell",
@@ -3494,9 +3494,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
3494
3494
 
3495
3495
  [[package]]
3496
3496
  name = "rustls-webpki"
3497
- version = "0.103.14"
3497
+ version = "0.103.16"
3498
3498
  source = "registry+https://github.com/rust-lang/crates.io-index"
3499
- checksum = "0527518605e68109d875e248ea259b6758801cf165e4b2c2733ae3b51f12535a"
3499
+ checksum = "4443b1b6d39d7c663139942cc4558262562d2c84123bb6282ef177009ca8b281"
3500
3500
  dependencies = [
3501
3501
  "aws-lc-rs",
3502
3502
  "ring",
@@ -3578,7 +3578,7 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
3578
3578
 
3579
3579
  [[package]]
3580
3580
  name = "sdr-aaronia-rs"
3581
- version = "0.13.0"
3581
+ version = "0.13.3"
3582
3582
  dependencies = [
3583
3583
  "anyhow",
3584
3584
  "bitflags 2.13.1",
@@ -4289,9 +4289,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
4289
4289
 
4290
4290
  [[package]]
4291
4291
  name = "tokio"
4292
- version = "1.53.1"
4292
+ version = "1.54.1"
4293
4293
  source = "registry+https://github.com/rust-lang/crates.io-index"
4294
- checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
4294
+ checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3"
4295
4295
  dependencies = [
4296
4296
  "bytes",
4297
4297
  "libc",
@@ -4784,9 +4784,9 @@ dependencies = [
4784
4784
 
4785
4785
  [[package]]
4786
4786
  name = "webpki-root-certs"
4787
- version = "1.0.9"
4787
+ version = "1.0.12"
4788
4788
  source = "registry+https://github.com/rust-lang/crates.io-index"
4789
- checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b"
4789
+ checksum = "293198dcebfb821155b5092ab94b5a82ff180eddd7241df3bd6e66a4ffd1a585"
4790
4790
  dependencies = [
4791
4791
  "rustls-pki-types",
4792
4792
  ]
@@ -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.13.0"
5
+ version = "0.13.3"
6
6
  edition = "2024"
7
7
  repository = "https://github.com/isaacbentley/sdr-aaronia-rs"
8
8
  readme = "README.md"
@@ -75,6 +75,7 @@ tokio = { version = "1.43", features = ["rt-multi-thread", "macros", "time", "sy
75
75
  [[bench]]
76
76
  name = "parse_int16_packet"
77
77
  harness = false
78
+ required-features = ["http"]
78
79
 
79
80
  [[bench]]
80
81
  name = "deinterleave_dual_iq"
@@ -83,21 +84,64 @@ harness = false
83
84
  [[bench]]
84
85
  name = "decompress_block"
85
86
  harness = false
87
+ required-features = ["file"]
86
88
 
87
89
  [[bench]]
88
90
  name = "rtsa_open_and_read"
89
91
  harness = false
92
+ required-features = ["file"]
93
+
94
+ # Feature guards for integration tests so `cargo check/test --all-targets`
95
+ # succeeds under every isolated `--no-default-features --features <feat>`
96
+ # permutation.
97
+ [[test]]
98
+ name = "c_api_test"
99
+ required-features = ["ffi"]
100
+
101
+ [[test]]
102
+ name = "http_mock_test"
103
+ required-features = ["http"]
104
+
105
+ [[test]]
106
+ name = "http_resilience_test"
107
+ required-features = ["http", "file"]
90
108
 
91
- # `http_sink_test` drives a FutureSDR flowgraph and imports
92
- # `sdr_aaronia_rs::http_sink`, both of which only exist under the
93
- # `futuresdr` feature. Without this guard `cargo clippy --all-targets` and
94
- # `cargo test --all-targets` fail to compile on a default checkout with
95
- # `cannot find module or crate futuresdr`. (`tests/native_sdk_load.rs`
96
- # needs no entry — it guards itself with an inner `#![cfg(...)]`.)
97
109
  [[test]]
98
110
  name = "http_sink_test"
99
111
  required-features = ["futuresdr"]
100
112
 
113
+ [[test]]
114
+ name = "integration_test"
115
+ required-features = ["http", "file"]
116
+
117
+ [[test]]
118
+ name = "live_smoke"
119
+ required-features = ["http", "file"]
120
+
121
+ [[test]]
122
+ name = "native_sdk_live"
123
+ required-features = ["native-sdk", "http", "file", "ffi"]
124
+
125
+ [[test]]
126
+ name = "properties"
127
+ required-features = ["http", "file"]
128
+
129
+ [[test]]
130
+ name = "rtsa_negative_test"
131
+ required-features = ["file"]
132
+
133
+ [[test]]
134
+ name = "test_cw_mag"
135
+ required-features = ["file"]
136
+
137
+ [[test]]
138
+ name = "test_cw_meta"
139
+ required-features = ["file"]
140
+
141
+ [[test]]
142
+ name = "wire_contract"
143
+ required-features = ["http"]
144
+
101
145
  [features]
102
146
  default = ["http", "file", "ffi"]
103
147
  http = ["dep:reqwest", "dep:tokio", "dep:serde", "dep:serde_json",
@@ -110,12 +154,16 @@ native-sdk = ["dep:libloading", "dep:widestring"]
110
154
 
111
155
  [[example]]
112
156
  name = "noaa_scanner"
113
- required-features = ["futuresdr", "http"]
157
+ required-features = ["futuresdr", "http", "file"]
114
158
 
115
159
  [[example]]
116
160
  name = "native_sdk_basic"
117
161
  required-features = ["native-sdk"]
118
162
 
163
+ [[example]]
164
+ name = "native_sdk_transmit"
165
+ required-features = ["native-sdk"]
166
+
119
167
  [[example]]
120
168
  name = "dump_metadata"
121
169
  required-features = ["file"]
@@ -126,11 +174,11 @@ required-features = ["http"]
126
174
 
127
175
  [[example]]
128
176
  name = "http_iq_quickstart"
129
- required-features = ["http"]
177
+ required-features = ["http", "file"]
130
178
 
131
179
  [[example]]
132
180
  name = "read_rtsa_file"
133
- required-features = ["file"]
181
+ required-features = ["http", "file"]
134
182
 
135
183
 
136
184
  [target.'cfg(any(target_os = "windows", target_os = "linux"))'.dependencies]
@@ -1,11 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-aaronia
3
- Version: 0.13.0
3
+ Version: 0.13.3
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+)
7
7
  Classifier: Topic :: Communications :: Ham Radio
8
8
  Classifier: Topic :: Scientific/Engineering
9
+ Requires-Dist: numpy>=1.24
10
+ Requires-Dist: pyarrow>=14.0 ; extra == 'arrow'
11
+ Provides-Extra: arrow
9
12
  Summary: Python bindings for sdr-aaronia-rs (Aaronia SPECTRAN V6 SDR source)
10
13
  License: GPL-3.0-or-later
11
14
  Requires-Python: >=3.9
@@ -6,6 +6,14 @@ 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
+
9
17
  ## Current driver behavior (0.13)
10
18
 
11
19
  See [Native SDK compatibility and qualification](NATIVE_SDK_COMPATIBILITY.md)
@@ -15,7 +15,8 @@
15
15
 
16
16
  #[cfg(all(
17
17
  feature = "native-sdk",
18
- any(target_os = "windows", target_os = "linux")
18
+ any(target_os = "windows", target_os = "linux"),
19
+ target_pointer_width = "64"
19
20
  ))]
20
21
  fn main() -> anyhow::Result<()> {
21
22
  use sdr_aaronia_rs::native_sdk::NativeSdkSource;
@@ -129,7 +130,8 @@ fn main() -> anyhow::Result<()> {
129
130
 
130
131
  #[cfg(not(all(
131
132
  feature = "native-sdk",
132
- any(target_os = "windows", target_os = "linux")
133
+ any(target_os = "windows", target_os = "linux"),
134
+ target_pointer_width = "64"
133
135
  )))]
134
136
  fn main() {
135
137
  println!("This example requires the 'native-sdk' feature and Windows or Linux.");
@@ -2,7 +2,8 @@
2
2
  //! Run only with the radio released by other apps; no TX is opened.
3
3
  #[cfg(all(
4
4
  feature = "native-sdk",
5
- any(target_os = "windows", target_os = "linux")
5
+ any(target_os = "windows", target_os = "linux"),
6
+ target_pointer_width = "64"
6
7
  ))]
7
8
  #[tokio::main(flavor = "current_thread")]
8
9
  async fn main() -> anyhow::Result<()> {
@@ -87,7 +88,8 @@ async fn main() -> anyhow::Result<()> {
87
88
  }
88
89
  #[cfg(not(all(
89
90
  feature = "native-sdk",
90
- any(target_os = "windows", target_os = "linux")
91
+ any(target_os = "windows", target_os = "linux"),
92
+ target_pointer_width = "64"
91
93
  )))]
92
94
  fn main() {
93
95
  eprintln!("Requires native-sdk on Windows or Linux");
@@ -11,7 +11,8 @@
11
11
 
12
12
  #[cfg(all(
13
13
  feature = "native-sdk",
14
- any(target_os = "windows", target_os = "linux")
14
+ any(target_os = "windows", target_os = "linux"),
15
+ target_pointer_width = "64"
15
16
  ))]
16
17
  #[tokio::main]
17
18
  async fn main() -> anyhow::Result<()> {
@@ -139,7 +140,8 @@ async fn main() -> anyhow::Result<()> {
139
140
 
140
141
  #[cfg(not(all(
141
142
  feature = "native-sdk",
142
- any(target_os = "windows", target_os = "linux")
143
+ any(target_os = "windows", target_os = "linux"),
144
+ target_pointer_width = "64"
143
145
  )))]
144
146
  fn main() {
145
147
  println!("This example requires the 'native-sdk' feature and Windows or Linux.");
@@ -142,8 +142,8 @@ void spectran_source_builder_device_serial(SpectranSourceBuilder* builder, const
142
142
  void spectran_source_builder_force_source_type(SpectranSourceBuilder *builder,
143
143
  int source_type);
144
144
 
145
- /* Whether the native SDK library is present, by the search the builder
146
- * uses; does not load it. */
145
+ /* Whether the native SDK backend is compiled into this binary and the
146
+ * vendor SDK library is present on disk; does not load it. */
147
147
  bool spectran_sdk_installed(void);
148
148
 
149
149
  /* Alias-free bandwidth (Hz) delivered at an IQ sample rate; smaller than
@@ -169,10 +169,19 @@ void spectran_source_builder_read_timeout_us(SpectranSourceBuilder* builder, uin
169
169
  // Automatic reconnection of the HTTP sample stream (default true). When
170
170
  // false, a dropped stream ends the session and later reads error out.
171
171
  void spectran_source_builder_auto_reconnect(SpectranSourceBuilder* builder, bool enabled);
172
+ // Passive HTTP mode (default false). When true, never writes to /control or
173
+ // /remoteconfig, allowing read-only attachment to shared or unlicensed servers.
174
+ void spectran_source_builder_listen_only(SpectranSourceBuilder* builder, bool enabled);
172
175
  SpectranSource* spectran_source_build(SpectranSourceBuilder* builder);
173
176
 
174
177
  // --- SpectranSource FFI --- //
175
178
  //
179
+ // Thread-safety contract: a `SpectranSource*` handle is NOT internally
180
+ // synchronized. All calls targeting the same `SpectranSource*` (including
181
+ // reads, retunes, capability/sensor queries, and free) MUST be serialized
182
+ // by the caller (single thread at a time). Separate `SpectranSource*` or
183
+ // `HttpEndpointsClient*` handles may be used concurrently on separate threads.
184
+ //
176
185
  // Read return codes: >= 0 samples read; -1 generic error (details via
177
186
  // spectran_last_error); -3 timeout — a private convention of this API,
178
187
  // chosen so SoapySDR wrappers can map it 1:1 onto SOAPY_SDR_TIMEOUT.
@@ -218,12 +227,11 @@ SpectranFfiError spectran_source_set_clock_source(SpectranSource* source, const
218
227
  FfiSourceInfo* spectran_source_get_source_info(SpectranSource* source);
219
228
  void spectran_source_info_free(FfiSourceInfo* info);
220
229
 
221
- // Read the device's declared capabilities. BLOCKING: two control-plane
222
- // GETs, so call it once and cache. Returns NULL only for a null source
223
- // or when called from a current-thread tokio runtime; a device that
224
- // cannot be asked yields a struct with every field absent. Answers for
225
- // the HTTP backend — file and native-SDK sources report nothing, having
226
- // no equivalent surface to ask.
230
+ // Read the device's declared capabilities. BLOCKING: control-plane or
231
+ // SDK queries, so call it once and cache. Returns NULL only for a null
232
+ // source or when called from a current-thread tokio runtime; a device
233
+ // that cannot be asked yields a struct with every field absent. Answers
234
+ // for both the HTTP and native-SDK backends; file sources report nothing.
227
235
  FfiDeviceCapabilities* spectran_source_get_capabilities(SpectranSource* source);
228
236
  void spectran_source_capabilities_free(FfiDeviceCapabilities* caps);
229
237
 
@@ -269,16 +277,25 @@ void spectran_sink_builder_free(SpectranSinkBuilder* builder);
269
277
  void spectran_sink_builder_center_frequency_hz(SpectranSinkBuilder* builder, double hz);
270
278
  void spectran_sink_builder_sample_rate_hz(SpectranSinkBuilder* builder, double hz);
271
279
  void spectran_sink_builder_trans_gain_db(SpectranSinkBuilder* builder, double db);
280
+ void spectran_sink_builder_device_serial(SpectranSinkBuilder* builder, const char* serial);
272
281
  SpectranSink* spectran_sink_build(SpectranSinkBuilder* builder);
273
282
  void spectran_sink_free(SpectranSink* sink);
274
- // Loads the native SDK, opens the first matching device, configures
283
+ // Thread-safety contract: like SpectranSource*, a SpectranSink* handle has no
284
+ // internal lock and must be accessed by at most one thread at a time.
285
+ //
286
+ // Loads the native SDK, opens the matching device, configures
275
287
  // the IQ transmitter from the builder settings, and starts the TX
276
288
  // stream. Blocking.
277
289
  SpectranFfiError spectran_sink_initialize(SpectranSink* sink);
278
290
  SpectranFfiError spectran_sink_stop_streaming(SpectranSink* sink);
291
+ SpectranFfiError spectran_sink_set_center_frequency_hz(SpectranSink* sink, double hz);
292
+ SpectranFfiError spectran_sink_set_sample_rate_hz(SpectranSink* sink, double hz);
293
+ SpectranFfiError spectran_sink_set_trans_gain_db(SpectranSink* sink, double db);
294
+ bool spectran_sink_get_master_stream_time_ns(SpectranSink* sink, int64_t* out_time_ns);
279
295
  // start_time_s / end_time_s are in device MASTER STREAM TIME seconds,
280
296
  // not wall-clock epoch time. Samples use the same FfiComplex layout as
281
- // the read path.
297
+ // the read path. Pass samples == NULL with num_samples == 0 for a 0-sample
298
+ // startup/shutdown sync packet.
282
299
  SpectranFfiError spectran_sink_write_samples(
283
300
  SpectranSink* sink,
284
301
  int32_t channel,
@@ -295,9 +312,9 @@ HttpEndpointsClient* spectran_endpoints_client_new(const char* base_url);
295
312
  void spectran_endpoints_client_free(HttpEndpointsClient* client);
296
313
  FfiServerInfo* spectran_endpoints_client_get_info(HttpEndpointsClient* client);
297
314
 
298
- /* Fill *out with the device's live sensors through a standalone client,
299
- * off the streaming source's read lock. Same reading and return contract
300
- * as spectran_source_get_sensors. */
315
+ /* Fill *out with the device's live sensors through a standalone client
316
+ * (safe to call concurrently while another thread owns a SpectranSource*).
317
+ * Same reading and return contract as spectran_source_get_sensors. */
301
318
  bool spectran_endpoints_client_get_sensors(HttpEndpointsClient* client, FfiDeviceSensors* out);
302
319
  void spectran_server_info_free(FfiServerInfo* info);
303
320
  SpectranFfiError spectran_endpoints_client_control_streaming(HttpEndpointsClient* client, bool start);
@@ -306,9 +323,9 @@ SpectranFfiError spectran_endpoints_client_control_recording(HttpEndpointsClient
306
323
  // --- General FFI Utilities --- //
307
324
 
308
325
  /* 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);
326
+ char *spectran_source_capabilities_json(SpectranSource *source);
327
+ SpectranFfiError spectran_source_rf_span_range(SpectranSource *source, double *min_hz, double *max_hz);
328
+ SpectranFfiError spectran_source_set_rf_span_hz(SpectranSource *source, double hz);
312
329
  SpectranFfiError spectran_source_builder_native_options(SpectranSourceBuilder *builder,
313
330
  const char *family, const char *mode, double rf_span_hz, const char *clock, uint32_t decimation);
314
331
 
@@ -0,0 +1,76 @@
1
+ # Homebrew formula for the Aaronia SoapySDR plugin.
2
+ #
3
+ # Lives here so it is versioned with the code it installs. To publish it,
4
+ # copy this file into a tap repository named homebrew-tap, as
5
+ # Formula/soapy-aaronia.rb, and users get:
6
+ #
7
+ # brew install <owner>/tap/soapy-aaronia
8
+ #
9
+ # At each release, update `version`, both `sha256` values (from the
10
+ # release archives) and nothing else. `brew audit --strict soapy-aaronia`
11
+ # checks the formula before publishing.
12
+ class SoapyAaronia < Formula
13
+ desc "SoapySDR plugin for Aaronia SPECTRAN V6 spectrum analyzers"
14
+ homepage "https://github.com/isaacbentley/sdr-aaronia-rs"
15
+ version "0.13.3"
16
+ license "GPL-3.0-or-later"
17
+
18
+ # Prebuilt modules: the plugin statically links its Rust core, so there
19
+ # is nothing to build here and no Rust toolchain to depend on.
20
+ on_macos do
21
+ depends_on arch: :arm64
22
+
23
+ on_arm do
24
+ url "https://github.com/isaacbentley/sdr-aaronia-rs/releases/download/v#{version}/SoapyAaronia-#{version}-macos-arm64.dmg",
25
+ using: :nounzip
26
+ sha256 "REPLACE_WITH_SHA256_OF_MACOS_ARM64_ARCHIVE"
27
+ end
28
+ end
29
+
30
+ on_linux do
31
+ depends_on arch: :x86_64
32
+
33
+ on_intel do
34
+ url "https://github.com/isaacbentley/sdr-aaronia-rs/releases/download/v#{version}/SoapyAaronia-#{version}-linux-x86_64.tar.gz"
35
+ sha256 "REPLACE_WITH_SHA256_OF_LINUX_X86_64_ARCHIVE"
36
+ end
37
+ end
38
+
39
+ depends_on "soapysdr"
40
+
41
+ def install
42
+ # Discover SoapySDR's module ABI version dynamically rather than
43
+ # hardcoding "0.8" across future SoapySDR releases.
44
+ info = Utils.safe_popen_read(Formula["soapysdr"].opt_bin/"SoapySDRUtil", "--info")
45
+ abi = info[/ABI Version:\s*v?([0-9]+\.[0-9]+)/i, 1] || "0.8"
46
+ mod_dir = lib/"SoapySDR/modules#{abi}"
47
+
48
+ if OS.mac?
49
+ dmg = Dir["*.dmg"].first
50
+ odie "No .dmg archive found in buildpath" unless dmg
51
+ mountpoint = buildpath/"dmg_mount"
52
+ mountpoint.mkpath
53
+ system "hdiutil", "attach", "-nobrowse", "-readonly", "-mountpoint", mountpoint, dmg
54
+ begin
55
+ so_file = Pathname.glob(mountpoint/"**/libaaroniaSupport.so").first
56
+ odie "libaaroniaSupport.so not found inside mounted DMG" unless so_file
57
+ mod_dir.install so_file
58
+ install_doc = Pathname.glob(mountpoint/"**/INSTALL.md").first
59
+ doc.install install_doc if install_doc
60
+ ensure
61
+ system "hdiutil", "detach", mountpoint, "-force"
62
+ end
63
+ else
64
+ so_file = Pathname.glob("**/libaaroniaSupport.so").first
65
+ odie "libaaroniaSupport.so not found in archive" unless so_file
66
+ mod_dir.install so_file
67
+ install_doc = Pathname.glob("**/INSTALL.md").first
68
+ doc.install install_doc if install_doc
69
+ end
70
+ end
71
+
72
+ test do
73
+ # Proves the module loads into SoapySDR rather than merely existing.
74
+ assert_match "aaronia", shell_output("#{Formula["soapysdr"].opt_bin}/SoapySDRUtil --check=aaronia")
75
+ end
76
+ end
@@ -17,6 +17,10 @@ classifiers = [
17
17
  ]
18
18
  # Version comes from python-aaronia/Cargo.toml (single source of truth).
19
19
  dynamic = ["version"]
20
+ dependencies = ["numpy>=1.24"]
21
+
22
+ [project.optional-dependencies]
23
+ arrow = ["pyarrow>=14.0"]
20
24
 
21
25
  # A real command, so checking a setup needs no code:
22
26
  # aaronia-doctor http://localhost:54664
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "python-aaronia"
3
- version = "0.13.0"
3
+ version = "0.13.3"
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"
@@ -108,6 +108,9 @@ class SpectranConfig:
108
108
  (default 30.0). Must be positive and finite."""
109
109
  auto_reconnect: bool
110
110
  """Reconnect the HTTP stream after a drop (default ``True``)."""
111
+ listen_only: bool
112
+ """Stream from an HTTP server without issuing ``/control`` or
113
+ ``/remoteconfig`` writes (default ``False``)."""
111
114
 
112
115
  class SpectranSource:
113
116
  """A streaming IQ source.
@@ -136,8 +139,9 @@ class SpectranSource:
136
139
  a truncated capture is not mistaken for a finished one.
137
140
  """
138
141
 
139
- def start_streaming(self, config: SpectranConfig) -> None:
140
- """Connect to the backend selected by ``config`` and start streaming.
142
+ def start_streaming(self, config: Optional[SpectranConfig] = None) -> None:
143
+ """Connect to the backend selected by ``config`` (or the previous
144
+ configuration when omitted) and start streaming.
141
145
 
142
146
  Raises ``SpectranConnectionError`` if the endpoint is
143
147
  unreachable, ``ValueError`` for invalid configuration, and
@@ -234,6 +238,7 @@ def open(
234
238
  format: Optional[WireFormat] = None,
235
239
  scale: Optional[float] = None,
236
240
  read_timeout_s: Optional[float] = None,
241
+ listen_only: bool = False,
237
242
  ) -> SpectranSource:
238
243
  """Open a source and start streaming, in one call.
239
244