python-aaronia 0.6.2__tar.gz → 0.7.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/CHANGELOG.md +67 -0
  2. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/Cargo.lock +2 -2
  3. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/Cargo.toml +1 -1
  4. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/PKG-INFO +56 -13
  5. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/README.md +6 -10
  6. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/docs/APPS.md +13 -8
  7. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/docs/QUICKSTART.md +20 -11
  8. python_aaronia-0.7.1/packaging/homebrew/README.md +43 -0
  9. python_aaronia-0.7.1/packaging/homebrew/soapy-aaronia.rb +47 -0
  10. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/pyproject.toml +5 -0
  11. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/python-aaronia/Cargo.toml +1 -1
  12. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/python-aaronia/README.md +55 -12
  13. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/python-aaronia/aaronia.pyi +69 -1
  14. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/python-aaronia/src/lib.rs +377 -0
  15. python_aaronia-0.7.1/python-aaronia/test_basic.py +143 -0
  16. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/soapy-aaronia/CMakeLists.txt +28 -0
  17. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/soapy-aaronia/README.md +20 -13
  18. python_aaronia-0.7.1/soapy-aaronia/build-test/CMakeCache.txt +439 -0
  19. python_aaronia-0.7.1/soapy-aaronia/build-test/CMakeFiles/4.4.2/CMakeCXXCompiler.cmake +103 -0
  20. python_aaronia-0.7.1/soapy-aaronia/build-test/CMakeFiles/4.4.2/CMakeDetermineCompilerABI_CXX.bin +0 -0
  21. python_aaronia-0.7.1/soapy-aaronia/build-test/CMakeFiles/4.4.2/CMakeSystem.cmake +15 -0
  22. python_aaronia-0.7.1/soapy-aaronia/build-test/CMakeFiles/4.4.2/CompilerIdCXX/CMakeCXXCompilerId.cpp +954 -0
  23. python_aaronia-0.7.1/soapy-aaronia/build-test/CMakeFiles/4.4.2/CompilerIdCXX/a.out +0 -0
  24. python_aaronia-0.7.1/soapy-aaronia/build-test/CMakeFiles/4.4.2/CompilerIdCXX/apple-sdk.cpp +1 -0
  25. python_aaronia-0.7.1/soapy-aaronia/build-test/CMakeFiles/CMakeConfigureLog.yaml +1904 -0
  26. python_aaronia-0.7.1/soapy-aaronia/build-test/CMakeFiles/CMakeDirectoryInformation.cmake +16 -0
  27. python_aaronia-0.7.1/soapy-aaronia/build-test/CMakeFiles/CMakeRuleHashes.txt +3 -0
  28. python_aaronia-0.7.1/soapy-aaronia/build-test/CMakeFiles/InstallScripts.json +7 -0
  29. python_aaronia-0.7.1/soapy-aaronia/build-test/CMakeFiles/Makefile.cmake +61 -0
  30. python_aaronia-0.7.1/soapy-aaronia/build-test/CMakeFiles/Makefile2 +157 -0
  31. python_aaronia-0.7.1/soapy-aaronia/build-test/CMakeFiles/TargetDirectories.txt +8 -0
  32. python_aaronia-0.7.1/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/DependInfo.cmake +24 -0
  33. python_aaronia-0.7.1/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/build.make +131 -0
  34. python_aaronia-0.7.1/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/cmake_clean.cmake +13 -0
  35. python_aaronia-0.7.1/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/compiler_depend.make +2 -0
  36. python_aaronia-0.7.1/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/compiler_depend.ts +2 -0
  37. python_aaronia-0.7.1/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/depend.make +2 -0
  38. python_aaronia-0.7.1/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/flags.make +12 -0
  39. python_aaronia-0.7.1/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/link.txt +1 -0
  40. python_aaronia-0.7.1/soapy-aaronia/build-test/CMakeFiles/aaroniaSupport.dir/progress.make +4 -0
  41. python_aaronia-0.7.1/soapy-aaronia/build-test/CMakeFiles/cmake.check_cache +1 -0
  42. python_aaronia-0.7.1/soapy-aaronia/build-test/CMakeFiles/progress.marks +1 -0
  43. python_aaronia-0.7.1/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/DependInfo.cmake +22 -0
  44. python_aaronia-0.7.1/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/build.make +94 -0
  45. python_aaronia-0.7.1/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/cmake_clean.cmake +9 -0
  46. python_aaronia-0.7.1/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/compiler_depend.make +2 -0
  47. python_aaronia-0.7.1/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/compiler_depend.ts +2 -0
  48. python_aaronia-0.7.1/soapy-aaronia/build-test/CMakeFiles/sdr_aaronia_rs_static.dir/progress.make +2 -0
  49. python_aaronia-0.7.1/soapy-aaronia/build-test/Makefile +271 -0
  50. python_aaronia-0.7.1/soapy-aaronia/build-test/cmake_install.cmake +78 -0
  51. python_aaronia-0.7.1/soapy-aaronia/packaging/install.ps1 +60 -0
  52. python_aaronia-0.7.1/soapy-aaronia/packaging/install.sh +74 -0
  53. python_aaronia-0.7.1/soapy-aaronia/print.cmake +1 -0
  54. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/src/error.rs +10 -0
  55. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/src/http_source.rs +6 -2
  56. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/src/unified_source.rs +2 -2
  57. python_aaronia-0.7.1/test.cmake +1 -0
  58. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/tests/http_resilience_test.rs +3 -3
  59. python_aaronia-0.6.2/python-aaronia/test_basic.py +0 -54
  60. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/.cargo/config.toml +0 -0
  61. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/.gitattributes +0 -0
  62. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/.gitignore +0 -0
  63. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/CONTRIBUTING.md +0 -0
  64. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/DESIGN.md +0 -0
  65. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/LICENSE +0 -0
  66. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/PLUGINS.md +0 -0
  67. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/benches/decompress_block.rs +0 -0
  68. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/benches/deinterleave_dual_iq.rs +0 -0
  69. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/benches/parse_int16_packet.rs +0 -0
  70. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/benches/rtsa_open_and_read.rs +0 -0
  71. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/deny.toml +0 -0
  72. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/docs/FILESPEC.md +0 -0
  73. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/docs/HTTPSPEC.md +0 -0
  74. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/docs/SDKSPEC.md +0 -0
  75. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/docs/USAGE.md +0 -0
  76. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/docs/VERIFICATION.md +0 -0
  77. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/examples/channel_hopping.rs +0 -0
  78. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/examples/device_control.rs +0 -0
  79. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/examples/dump_metadata.rs +0 -0
  80. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/examples/http_iq_quickstart.rs +0 -0
  81. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/examples/native_sdk_basic.rs +0 -0
  82. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/examples/native_sdk_transmit.rs +0 -0
  83. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/examples/noaa_scanner.rs +0 -0
  84. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/examples/python_arrow_example.py +0 -0
  85. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/examples/read_rtsa_file.rs +0 -0
  86. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/examples/soapy_python_example.py +0 -0
  87. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/include/aaronia.h +0 -0
  88. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/scripts/ci-local.sh +0 -0
  89. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/soapy-aaronia/AaroniaSoapyDevice.cpp +0 -0
  90. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/soapy-aaronia/AaroniaSoapyDevice.hpp +0 -0
  91. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/soapy-aaronia/Registration.cpp +0 -0
  92. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/src/c_api.rs +0 -0
  93. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/src/decompression.rs +0 -0
  94. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/src/detection.rs +0 -0
  95. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/src/file_source.rs +0 -0
  96. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/src/http_endpoints.rs +0 -0
  97. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/src/http_sink.rs +0 -0
  98. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/src/http_streaming.rs +0 -0
  99. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/src/lib.rs +0 -0
  100. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/src/native_sdk.rs +0 -0
  101. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/src/sdk_sink.rs +0 -0
  102. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/src/sdk_source.rs +0 -0
  103. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/src/sdr_source_impl.rs +0 -0
  104. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/src/seify_impl.rs +0 -0
  105. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/src/unified_sink.rs +0 -0
  106. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/src/utils.rs +0 -0
  107. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/tests/c_api_test.rs +0 -0
  108. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/tests/http_mock_test.rs +0 -0
  109. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/tests/http_sink_test.rs +0 -0
  110. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/tests/integration_test.rs +0 -0
  111. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/tests/live_smoke.rs +0 -0
  112. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/tests/native_sdk_load.rs +0 -0
  113. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/tests/properties.proptest-regressions +0 -0
  114. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/tests/properties.rs +0 -0
  115. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/tests/rtsa_negative_test.rs +0 -0
  116. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/tests/sdr_source_impl_test.rs +0 -0
  117. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/tests/spec_coverage.rs +0 -0
  118. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/tests/test_cw_mag.rs +0 -0
  119. {python_aaronia-0.6.2 → python_aaronia-0.7.1}/tests/test_cw_meta.rs +0 -0
@@ -4,6 +4,73 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [v0.7.1] - 2026-08-12
8
+
9
+ ### Fixed
10
+ - **The published macOS SoapySDR module would not load, on any Mac.**
11
+ `dlopen` failed with "symbol not found in flat namespace" for a Rust
12
+ vtable entry. CMake links module targets on macOS with
13
+ `-undefined dynamic_lookup`, and the Apple linker emitted a
14
+ flat-namespace bind for a symbol it had already localised into the
15
+ same image. The module now resolves its symbols at link time, so this
16
+ is a build failure rather than something a user meets on first run.
17
+ Every 0.5.x, 0.6.x and 0.7.0 macOS archive is affected; the Linux and
18
+ Windows modules are not.
19
+ - **The packaged module's load check ran on Linux only**, which is why
20
+ the above shipped. It now runs on all three platforms at release
21
+ time, and CI builds and checks the plugin on macOS as well as Linux.
22
+
23
+ ### Documentation
24
+ - The Linux module needs glibc 2.38 or later, so it does not load on
25
+ Ubuntu 22.04 or Debian 12. Said so in the plugin README.
26
+
27
+ ## [v0.7.0] - 2026-08-12
28
+
29
+ ### Added
30
+ - **`aaronia.open()`, block iteration and context-manager support in
31
+ Python.** The shortest working program is now three lines. `open()`
32
+ takes the URL, frequency and either an exact `rate` or the
33
+ `bandwidth` you want covered, connects, and starts streaming;
34
+ `for block in src.blocks(65536)` ends when the source runs out
35
+ instead of raising; and `with` stops the stream even when the body
36
+ fails, raising a failed teardown only if the body itself succeeded.
37
+ The old config-object path is unchanged and still the way to reach
38
+ every option.
39
+ - **`aaronia-doctor`, a command that checks an RTSA server.** It reports
40
+ whether the server answers, whether the mission has an input carrying
41
+ IQ, and what rate the device is running, and prints the fix for each
42
+ failure. The same checks are available as `aaronia.diagnose(url)`,
43
+ which returns `(ok, message, fix)` tuples, bounded at 20 seconds so a
44
+ stalled server cannot leave it waiting. Every failure it names is one
45
+ that otherwise shows up as a timeout with no explanation.
46
+ - **`aaronia.sample_rates()` and `aaronia.sample_rate_for_bandwidth()`**,
47
+ exposing the crate's rate ladder to Python so a program can ask for a
48
+ rate the hardware will actually run.
49
+ - **`Error::StreamClosed`**, separating "the stream ended" from "a read
50
+ failed". Both used to arrive as `Error::Protocol`, so a consumer
51
+ could not tell a capture that finished from one that was cut short.
52
+ Rust code matching on `Error::Protocol` for the closed-stream case
53
+ needs the new variant instead; the enum is `#[non_exhaustive]`, so
54
+ existing wildcard arms keep compiling. In Python the matching
55
+ exception is `AaroniaStreamClosed`, a subclass of
56
+ `AaroniaConnectionError`, so existing handlers are unaffected. It is
57
+ what lets `blocks()` end a loop on a finished stream while still
58
+ raising on a timeout or a transport failure, which would otherwise
59
+ make a truncated capture look like one that simply ran out.
60
+ - **An installer in every SoapySDR release archive.** `install.sh`
61
+ (`install.ps1` on Windows) finds SoapySDR's module directory, clears
62
+ the macOS quarantine flag, copies the module in, and confirms it
63
+ loads. It prints instructions rather than guessing when SoapySDR is
64
+ missing.
65
+ - **A Homebrew formula for the SoapySDR plugin**, in
66
+ `packaging/homebrew`. The release workflow renders it against the
67
+ published archives, checksums included, and attaches it to the
68
+ release, so updating a tap is a copy.
69
+
70
+ ### Fixed
71
+ - The SoapySDR application guide still listed the old invented sample
72
+ rates for GQRX. It now describes the real ladder.
73
+
7
74
  ## [v0.6.2] - 2026-08-12
8
75
 
9
76
  ## [v0.6.1] - 2026-08-12
@@ -3027,7 +3027,7 @@ dependencies = [
3027
3027
 
3028
3028
  [[package]]
3029
3029
  name = "python-aaronia"
3030
- version = "0.6.2"
3030
+ version = "0.7.1"
3031
3031
  dependencies = [
3032
3032
  "arrow",
3033
3033
  "num-complex",
@@ -3556,7 +3556,7 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
3556
3556
 
3557
3557
  [[package]]
3558
3558
  name = "sdr-aaronia-rs"
3559
- version = "0.6.2"
3559
+ version = "0.7.1"
3560
3560
  dependencies = [
3561
3561
  "anyhow",
3562
3562
  "bitflags 2.13.1",
@@ -2,7 +2,7 @@
2
2
  name = "sdr-aaronia-rs"
3
3
  description = "Unified Rust interface for Aaronia Spectran Spectrum Analyzers / SDRs, featuring Python bindings, a SoapySDR plugin, HTTP streaming, and native SDK support."
4
4
  license = "GPL-3.0-or-later"
5
- version = "0.6.2"
5
+ version = "0.7.1"
6
6
  edition = "2024"
7
7
  repository = "https://github.com/isaacbentley/sdr-aaronia-rs"
8
8
  readme = "README.md"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-aaronia
3
- Version: 0.6.2
3
+ Version: 0.7.1
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
@@ -40,37 +40,66 @@ cd python-aaronia
40
40
  maturin develop --release
41
41
  ```
42
42
 
43
+ Check your setup before writing any code:
44
+
45
+ ```bash
46
+ aaronia-doctor http://localhost:54664
47
+ ```
48
+
49
+ It reports whether the server is reachable, whether the mission has an
50
+ input carrying IQ, and what rate the device is running, and names the
51
+ fix for each failure.
52
+
43
53
  ## Quickstart
44
54
 
45
55
  ```python
46
56
  import aaronia
47
57
 
48
- cfg = aaronia.AaroniaConfig()
49
- cfg.http_base_url = "http://localhost:54664" # RTSA-Suite HTTP server block
50
- cfg.center_freq = 2.44e9 # Hz
51
- cfg.sample_rate = 15.36e6 # Hz
52
- cfg.format = "F32" # wire format: F32, F16, or I16
58
+ with aaronia.open("http://localhost:54664", freq=2.44e9, bandwidth=10e6) as src:
59
+ for block in src.blocks(65536): # numpy complex64 arrays
60
+ process(block)
61
+ ```
62
+
63
+ `aaronia.open()` connects and starts streaming in one call. `bandwidth`
64
+ asks for that much usable spectrum and picks a sample rate the hardware
65
+ can actually run; pass `rate=` instead to name one exactly. Use
66
+ `file="capture.rtsa"` in place of the URL to play back a recording.
53
67
 
54
- src = aaronia.AaroniaSource()
55
- src.start_streaming(cfg)
68
+ Iterating with `blocks()` ends when the stream closes. To read on your
69
+ own schedule, or for Apache Arrow:
56
70
 
71
+ ```python
72
+ src = aaronia.open(freq=2.44e9, rate=15.36e6, format="I16")
57
73
  samples = src.read_samples_numpy(65536) # numpy complex64 array
58
74
  batch = src.read_samples_arrow(65536) # pyarrow FixedSizeListArray of [re, im]
59
-
60
75
  src.set_center_frequency(2.41e9) # live retune, no teardown
61
-
62
76
  print(src.cumulative_drops(), src.take_overrun(), src.last_timestamp_ns())
63
77
  src.stop_streaming()
64
78
  ```
65
79
 
66
- File playback: set `cfg.file_path = "capture.rtsa"` instead of
67
- `http_base_url`.
80
+ For full control, build an `AaroniaConfig` and pass it to
81
+ `AaroniaSource.start_streaming()`; `open()` is a shorthand for the
82
+ common fields.
68
83
 
69
84
  The
70
85
  [quickstart](https://github.com/isaacbentley/sdr-aaronia-rs/blob/main/docs/QUICKSTART.md)
71
86
  covers configuring the RTSA-Suite HTTP Server block, which everything
72
87
  above depends on.
73
88
 
89
+ ## Sample rates
90
+
91
+ The device runs a fixed ladder of rates: 61.44 MHz halved down to
92
+ 120 kHz. Ask for anything else and it quietly uses the nearest rung,
93
+ leaving your program computing against a rate that is not in use.
94
+
95
+ ```python
96
+ aaronia.sample_rates() # every rate, highest first
97
+ aaronia.sample_rate_for_bandwidth(8e6) # 15.36e6: the lowest rate covering 8 MHz
98
+ ```
99
+
100
+ A rate carries only 80% of itself as alias-free bandwidth, which is why
101
+ 8 MHz of spectrum needs 15.36 MHz of sampling.
102
+
74
103
  ## Configuration (`AaroniaConfig`)
75
104
 
76
105
  Every field is readable and writable.
@@ -107,11 +136,14 @@ silently defaulting.
107
136
  enabled, which is the default. The reader reopens the stream,
108
137
  re-applies the current tuning, and flags the first read after the gap
109
138
  through `take_overrun()`. After five failed attempts the stream ends
110
- and reads raise `AaroniaConnectionError`.
139
+ and reads raise `AaroniaStreamClosed`.
111
140
  - **Typed exceptions.** `AaroniaConnectionError` (unreachable endpoint),
112
141
  `AaroniaTimeoutError`, `AaroniaHardwareError` (device and SDK errors)
113
142
  and `ValueError` (invalid configuration), mapped from the Rust error
114
143
  enum with the full cause chain in the message.
144
+ `AaroniaStreamClosed` subclasses `AaroniaConnectionError` and means
145
+ the stream finished rather than failed; `blocks()` ends on it, while
146
+ a timeout or transport failure still raises.
115
147
  - **Dual-channel** reads (`receiver_channel = "Rx1And2"` with
116
148
  `read_samples_dual_numpy(count)`, returning two time-aligned arrays)
117
149
  require the native-SDK backend: Windows or Linux with the Aaronia SDK
@@ -123,6 +155,8 @@ silently defaulting.
123
155
  | Method | Purpose |
124
156
  | --- | --- |
125
157
  | `start_streaming(cfg)` / `stop_streaming()` | Session lifecycle |
158
+ | `with src: ...` | Stops streaming on the way out, including after an exception |
159
+ | `blocks(count)` | Iterate `count`-sample arrays until the stream closes |
126
160
  | `read_samples_numpy(count)` | NumPy `complex64` array |
127
161
  | `read_samples_arrow(count)` | PyArrow `FixedSizeListArray` of `[re, im]` float32 pairs |
128
162
  | `read_samples_dual_numpy(count)` | `(rx1, rx2)` NumPy arrays (dual-channel captures) |
@@ -131,3 +165,12 @@ silently defaulting.
131
165
  | `take_overrun()` | True once per detected receive-side overrun |
132
166
  | `last_timestamp_ns()` | Epoch-ns timestamp of the last received block (HTTP backend; 0 otherwise) |
133
167
 
168
+ ## Module functions
169
+
170
+ | Function | Purpose |
171
+ | --- | --- |
172
+ | `open(url=None, *, freq, rate, bandwidth, ref_level, file, format, read_timeout)` | Configure, connect and start streaming in one call |
173
+ | `sample_rates()` | Every sample rate the hardware can run |
174
+ | `sample_rate_for_bandwidth(hz)` | Lowest rate covering that much spectrum |
175
+ | `diagnose(url)` | `(ok, message, fix)` for each setup check; what `aaronia-doctor` prints |
176
+
@@ -102,21 +102,17 @@ pip install python-aaronia
102
102
  ```python
103
103
  import aaronia
104
104
 
105
- cfg = aaronia.AaroniaConfig()
106
- cfg.http_base_url = "http://localhost:54664"
107
- cfg.center_freq = 2.44e9
108
- cfg.sample_rate = 15.36e6
109
-
110
- src = aaronia.AaroniaSource()
111
- src.start_streaming(cfg)
112
- samples = src.read_samples_numpy(65536) # numpy complex64
113
- src.stop_streaming()
105
+ with aaronia.open("http://localhost:54664", freq=2.44e9, bandwidth=10e6) as src:
106
+ for block in src.blocks(65536): # numpy complex64 arrays
107
+ process(block)
114
108
  ```
115
109
 
116
110
  Reads land in NumPy or PyArrow with one copy out of the receive buffer.
117
111
  Blocking calls release the GIL, errors arrive as typed exceptions, and
118
112
  the package ships type stubs. Wheels are abi3 for CPython 3.9 and
119
- later. Full reference: [python-aaronia/README.md](python-aaronia/README.md).
113
+ later. The bundled `aaronia-doctor` command checks a server and names
114
+ the fix for whatever is wrong. Full reference:
115
+ [python-aaronia/README.md](python-aaronia/README.md).
120
116
 
121
117
  ### SoapySDR: GQRX, SDR++, GNU Radio and others
122
118
 
@@ -1,14 +1,20 @@
1
1
  # Using a SPECTRAN from existing SDR applications
2
2
 
3
3
  Any SoapySDR-capable application can drive a SPECTRAN through the
4
- [`soapy-aaronia`](../soapy-aaronia/README.md) plugin. Install the plugin
5
- first. Prebuilt binaries are attached to each release. Confirm it is
6
- visible:
4
+ [`soapy-aaronia`](../soapy-aaronia/README.md) plugin. Install it first:
5
+ download the archive for your platform from a
6
+ [release](https://github.com/isaacbentley/sdr-aaronia-rs/releases),
7
+ unpack it, and run the bundled installer (`./install.sh`, or
8
+ `.\install.ps1` in PowerShell on Windows). It finds SoapySDR's module
9
+ directory, copies the plugin in, and confirms it loads:
7
10
 
8
11
  ```bash
9
12
  SoapySDRUtil --check=aaronia
10
13
  ```
11
14
 
15
+ The [plugin README](../soapy-aaronia/README.md#install-a-prebuilt-module)
16
+ covers installing by hand and building from source.
17
+
12
18
  Every application below uses the same device string. In most cases the
13
19
  RTSA-Suite HTTP server URL is the only argument needed:
14
20
 
@@ -43,11 +49,10 @@ GQRX accepts a device string directly in its configuration dialog:
43
49
  driver=aaronia,url=http://localhost:54664
44
50
  ```
45
51
 
46
- Select the sample rate from GQRX's list. The plugin advertises 250 kHz,
47
- 500 kHz, 1, 2, 5, 10, 15.36, 20, 30.72 and 61.44 MHz for applications
48
- that build dropdowns from `listSampleRates`. Rates within the reported
49
- range also work over HTTP, so a rate absent from the list is not
50
- necessarily unsupported.
52
+ Select the sample rate from GQRX's list. The plugin advertises the
53
+ device's real ladder: 61.44 MHz halved down to 120 kHz, which the RTSA
54
+ GUI labels Full through 1/512. The hardware produces no other rate, so a
55
+ request in between snaps to the nearest rung and logs a warning.
51
56
 
52
57
  The single gain element is `REF`, the Aaronia reference level in dBm. It
53
58
  is not an amplifier gain: raising it reduces sensitivity. Start near
@@ -86,30 +86,39 @@ cargo run --example http_iq_quickstart --features http -- 2440e6 15.36e6 http://
86
86
  pip install python-aaronia
87
87
  ```
88
88
 
89
+ Check the server first. The command ships with the package and reports
90
+ what is wrong, and how to fix it:
91
+
92
+ ```bash
93
+ aaronia-doctor http://localhost:54664
94
+ ```
95
+
89
96
  ```python
90
97
  import aaronia
91
98
 
92
- cfg = aaronia.AaroniaConfig()
93
- cfg.http_base_url = "http://localhost:54664"
94
- cfg.center_freq = 2.44e9
95
- cfg.sample_rate = 15.36e6
96
-
97
- src = aaronia.AaroniaSource()
98
- src.start_streaming(cfg)
99
- samples = src.read_samples_numpy(65536) # numpy complex64
100
- print(samples[:4], src.cumulative_drops())
101
- src.stop_streaming()
99
+ with aaronia.open("http://localhost:54664", freq=2.44e9, bandwidth=10e6) as src:
100
+ samples = src.read_samples_numpy(65536) # numpy complex64
101
+ print(samples[:4], src.cumulative_drops())
102
102
  ```
103
103
 
104
+ `bandwidth` picks a sample rate the hardware can run. Pass `rate=` to
105
+ name one exactly, or build an `AaroniaConfig` for the full set of
106
+ options.
107
+
104
108
  ### SoapySDR
105
109
 
110
+ Download the plugin archive for your platform from a
111
+ [release](https://github.com/isaacbentley/sdr-aaronia-rs/releases),
112
+ unpack it, and run the bundled installer:
113
+
106
114
  ```bash
115
+ ./install.sh
107
116
  SoapySDRUtil --probe="driver=aaronia,url=http://localhost:54664"
108
117
  ```
109
118
 
110
119
  [APPS.md](APPS.md) covers GQRX, SDR++ and GNU Radio.
111
120
  [../soapy-aaronia/README.md](../soapy-aaronia/README.md) covers
112
- installing the plugin.
121
+ installing by hand and building from source.
113
122
 
114
123
  ## 4. Troubleshooting
115
124
 
@@ -0,0 +1,43 @@
1
+ # Homebrew tap
2
+
3
+ `soapy-aaronia.rb` installs the prebuilt SoapySDR plugin from a GitHub
4
+ release. It is kept here so it is versioned alongside the code it
5
+ installs, and published from a tap repository.
6
+
7
+ ## One-time setup
8
+
9
+ Create a public repository named `homebrew-tap` under the same owner as
10
+ this one. The name is fixed: Homebrew maps `owner/tap` to
11
+ `github.com/owner/homebrew-tap`. Add the formula as
12
+ `Formula/soapy-aaronia.rb`.
13
+
14
+ Users then install with:
15
+
16
+ ```bash
17
+ brew install isaacbentley/tap/soapy-aaronia
18
+ ```
19
+
20
+ Homebrew pulls in SoapySDR, drops the module into
21
+ `lib/SoapySDR/modules0.8`, and its `brew test` step confirms
22
+ `SoapySDRUtil --check=aaronia` reports the driver as present.
23
+
24
+ ## Each release
25
+
26
+ The release workflow renders this file against the published archives
27
+ and attaches the result to the release as `soapy-aaronia.rb`, with the
28
+ version and both SHA-256 checksums filled in. Copy that file into the
29
+ tap repository as `Formula/soapy-aaronia.rb` and commit. Nothing needs
30
+ hashing by hand.
31
+
32
+ Before pushing, `brew audit --strict --online soapy-aaronia` checks the
33
+ formula, and `brew install --build-from-source` proves it works.
34
+
35
+ ## Coverage
36
+
37
+ The formula covers macOS on Apple silicon and Linux on x86-64, matching
38
+ the platforms the release builds. Elsewhere, `brew` has nothing to
39
+ install and the plugin has to be built from source; see
40
+ [soapy-aaronia/README.md](../../soapy-aaronia/README.md).
41
+
42
+ The module directory is pinned to `modules0.8`, which is SoapySDR's ABI
43
+ 0.8 path. A future SoapySDR ABI bump requires changing that line.
@@ -0,0 +1,47 @@
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.7.1"
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
+ on_arm do
22
+ url "https://github.com/isaacbentley/sdr-aaronia-rs/releases/download/v#{version}/SoapyAaronia-#{version}-macos-arm64.tar.gz"
23
+ sha256 "REPLACE_WITH_SHA256_OF_MACOS_ARM64_ARCHIVE"
24
+ end
25
+ end
26
+
27
+ on_linux do
28
+ on_intel do
29
+ url "https://github.com/isaacbentley/sdr-aaronia-rs/releases/download/v#{version}/SoapyAaronia-#{version}-linux-x86_64.tar.gz"
30
+ sha256 "REPLACE_WITH_SHA256_OF_LINUX_X86_64_ARCHIVE"
31
+ end
32
+ end
33
+
34
+ depends_on "soapysdr"
35
+
36
+ def install
37
+ # SoapySDR loads modules from a versioned directory under its own
38
+ # prefix; Homebrew links this into place for us.
39
+ (lib/"SoapySDR/modules0.8").install "libaaroniaSupport.so"
40
+ doc.install "INSTALL.md"
41
+ end
42
+
43
+ test do
44
+ # Proves the module loads into SoapySDR rather than merely existing.
45
+ assert_match "aaronia", shell_output("#{Formula["soapysdr"].bin}/SoapySDRUtil --check=aaronia")
46
+ end
47
+ end
@@ -18,6 +18,11 @@ classifiers = [
18
18
  # Version comes from python-aaronia/Cargo.toml (single source of truth).
19
19
  dynamic = ["version"]
20
20
 
21
+ # A real command, so checking a setup needs no code:
22
+ # aaronia-doctor http://localhost:54664
23
+ [project.scripts]
24
+ aaronia-doctor = "aaronia:doctor_cli"
25
+
21
26
  [project.urls]
22
27
  Repository = "https://github.com/isaacbentley/sdr-aaronia-rs"
23
28
  Changelog = "https://github.com/isaacbentley/sdr-aaronia-rs/blob/main/CHANGELOG.md"
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "python-aaronia"
3
- version = "0.6.2"
3
+ version = "0.7.1"
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"
@@ -25,37 +25,66 @@ cd python-aaronia
25
25
  maturin develop --release
26
26
  ```
27
27
 
28
+ Check your setup before writing any code:
29
+
30
+ ```bash
31
+ aaronia-doctor http://localhost:54664
32
+ ```
33
+
34
+ It reports whether the server is reachable, whether the mission has an
35
+ input carrying IQ, and what rate the device is running, and names the
36
+ fix for each failure.
37
+
28
38
  ## Quickstart
29
39
 
30
40
  ```python
31
41
  import aaronia
32
42
 
33
- cfg = aaronia.AaroniaConfig()
34
- cfg.http_base_url = "http://localhost:54664" # RTSA-Suite HTTP server block
35
- cfg.center_freq = 2.44e9 # Hz
36
- cfg.sample_rate = 15.36e6 # Hz
37
- cfg.format = "F32" # wire format: F32, F16, or I16
43
+ with aaronia.open("http://localhost:54664", freq=2.44e9, bandwidth=10e6) as src:
44
+ for block in src.blocks(65536): # numpy complex64 arrays
45
+ process(block)
46
+ ```
47
+
48
+ `aaronia.open()` connects and starts streaming in one call. `bandwidth`
49
+ asks for that much usable spectrum and picks a sample rate the hardware
50
+ can actually run; pass `rate=` instead to name one exactly. Use
51
+ `file="capture.rtsa"` in place of the URL to play back a recording.
38
52
 
39
- src = aaronia.AaroniaSource()
40
- src.start_streaming(cfg)
53
+ Iterating with `blocks()` ends when the stream closes. To read on your
54
+ own schedule, or for Apache Arrow:
41
55
 
56
+ ```python
57
+ src = aaronia.open(freq=2.44e9, rate=15.36e6, format="I16")
42
58
  samples = src.read_samples_numpy(65536) # numpy complex64 array
43
59
  batch = src.read_samples_arrow(65536) # pyarrow FixedSizeListArray of [re, im]
44
-
45
60
  src.set_center_frequency(2.41e9) # live retune, no teardown
46
-
47
61
  print(src.cumulative_drops(), src.take_overrun(), src.last_timestamp_ns())
48
62
  src.stop_streaming()
49
63
  ```
50
64
 
51
- File playback: set `cfg.file_path = "capture.rtsa"` instead of
52
- `http_base_url`.
65
+ For full control, build an `AaroniaConfig` and pass it to
66
+ `AaroniaSource.start_streaming()`; `open()` is a shorthand for the
67
+ common fields.
53
68
 
54
69
  The
55
70
  [quickstart](https://github.com/isaacbentley/sdr-aaronia-rs/blob/main/docs/QUICKSTART.md)
56
71
  covers configuring the RTSA-Suite HTTP Server block, which everything
57
72
  above depends on.
58
73
 
74
+ ## Sample rates
75
+
76
+ The device runs a fixed ladder of rates: 61.44 MHz halved down to
77
+ 120 kHz. Ask for anything else and it quietly uses the nearest rung,
78
+ leaving your program computing against a rate that is not in use.
79
+
80
+ ```python
81
+ aaronia.sample_rates() # every rate, highest first
82
+ aaronia.sample_rate_for_bandwidth(8e6) # 15.36e6: the lowest rate covering 8 MHz
83
+ ```
84
+
85
+ A rate carries only 80% of itself as alias-free bandwidth, which is why
86
+ 8 MHz of spectrum needs 15.36 MHz of sampling.
87
+
59
88
  ## Configuration (`AaroniaConfig`)
60
89
 
61
90
  Every field is readable and writable.
@@ -92,11 +121,14 @@ silently defaulting.
92
121
  enabled, which is the default. The reader reopens the stream,
93
122
  re-applies the current tuning, and flags the first read after the gap
94
123
  through `take_overrun()`. After five failed attempts the stream ends
95
- and reads raise `AaroniaConnectionError`.
124
+ and reads raise `AaroniaStreamClosed`.
96
125
  - **Typed exceptions.** `AaroniaConnectionError` (unreachable endpoint),
97
126
  `AaroniaTimeoutError`, `AaroniaHardwareError` (device and SDK errors)
98
127
  and `ValueError` (invalid configuration), mapped from the Rust error
99
128
  enum with the full cause chain in the message.
129
+ `AaroniaStreamClosed` subclasses `AaroniaConnectionError` and means
130
+ the stream finished rather than failed; `blocks()` ends on it, while
131
+ a timeout or transport failure still raises.
100
132
  - **Dual-channel** reads (`receiver_channel = "Rx1And2"` with
101
133
  `read_samples_dual_numpy(count)`, returning two time-aligned arrays)
102
134
  require the native-SDK backend: Windows or Linux with the Aaronia SDK
@@ -108,6 +140,8 @@ silently defaulting.
108
140
  | Method | Purpose |
109
141
  | --- | --- |
110
142
  | `start_streaming(cfg)` / `stop_streaming()` | Session lifecycle |
143
+ | `with src: ...` | Stops streaming on the way out, including after an exception |
144
+ | `blocks(count)` | Iterate `count`-sample arrays until the stream closes |
111
145
  | `read_samples_numpy(count)` | NumPy `complex64` array |
112
146
  | `read_samples_arrow(count)` | PyArrow `FixedSizeListArray` of `[re, im]` float32 pairs |
113
147
  | `read_samples_dual_numpy(count)` | `(rx1, rx2)` NumPy arrays (dual-channel captures) |
@@ -115,3 +149,12 @@ silently defaulting.
115
149
  | `cumulative_drops()` | Total server-reported dropped samples |
116
150
  | `take_overrun()` | True once per detected receive-side overrun |
117
151
  | `last_timestamp_ns()` | Epoch-ns timestamp of the last received block (HTTP backend; 0 otherwise) |
152
+
153
+ ## Module functions
154
+
155
+ | Function | Purpose |
156
+ | --- | --- |
157
+ | `open(url=None, *, freq, rate, bandwidth, ref_level, file, format, read_timeout)` | Configure, connect and start streaming in one call |
158
+ | `sample_rates()` | Every sample rate the hardware can run |
159
+ | `sample_rate_for_bandwidth(hz)` | Lowest rate covering that much spectrum |
160
+ | `diagnose(url)` | `(ok, message, fix)` for each setup check; what `aaronia-doctor` prints |