├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── args.rs ├── probe.rs ├── rx_generic.rs ├── rx_threaded.rs └── rx_typed.rs └── src ├── args.rs ├── device.rs ├── impls ├── aaronia.rs ├── aaronia_http.rs ├── dummy.rs ├── hackrfone.rs ├── mod.rs ├── rtlsdr.rs └── soapy.rs ├── lib.rs ├── range.rs └── streamer.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: CI 4 | 5 | jobs: 6 | lints: 7 | name: Linux 8 | runs-on: ubuntu-latest 9 | env: 10 | RUST_BACKTRACE: full 11 | steps: 12 | - uses: actions/checkout@v4 13 | with: 14 | submodules: recursive 15 | 16 | - uses: dtolnay/rust-toolchain@nightly 17 | with: 18 | components: rustfmt, clippy 19 | 20 | - name: Apt Update 21 | run: sudo apt-get -y update 22 | 23 | - name: Install Soapy 24 | run: sudo apt-get -y install libsoapysdr-dev 25 | 26 | - name: Fmt 27 | run: cargo fmt --all -- --check 28 | 29 | - name: Clippy 30 | run: cargo clippy --all-targets --workspace --features=rtlsdr,aaronia_http,soapy -- -D warnings 31 | 32 | - name: Test 33 | run: cargo test --all-targets --features=aaronia_http,rtlsdr,soapy 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | .idea/ 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "crates/rtl-sdr-rs"] 2 | path = crates/rtl-sdr-rs 3 | url = https://github.com/bastibl/rtl-sdr-rs.git 4 | [submodule "crates/seify-hackrfone"] 5 | path = crates/seify-hackrfone 6 | url = https://github.com/MerchGuardian/seify-hackrfone.git 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "seify" 3 | version = "0.16.0" 4 | description = "Shiny Samples from your Rusty SDR" 5 | documentation = "https://docs.rs/seify/" 6 | edition = "2021" 7 | homepage = "https://www.futuresdr.org" 8 | license = "Apache-2.0" 9 | repository = "https://github.com/FutureSDR/seify" 10 | 11 | [features] 12 | default = ["soapy", "dummy"] 13 | aaronia = ["dep:aaronia-rtsa"] 14 | aaronia_http = ["dep:ureq"] 15 | dummy = [] 16 | hackrfone = ["dep:seify-hackrfone"] 17 | rtlsdr = ["dep:seify-rtlsdr"] 18 | soapy = ["dep:soapysdr"] 19 | 20 | [[example]] 21 | name = "rx_typed" 22 | required-features = ["rtlsdr"] 23 | 24 | [dependencies] 25 | futures = "0.3" 26 | log = "0.4" 27 | nom = "7.1" 28 | num-complex = "0.4" 29 | serde = { version = "1.0", features = ["derive"] } 30 | serde_json = "1.0" 31 | serde_with = "3.11" 32 | thiserror = "2.0" 33 | 34 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 35 | once_cell = "1.20" 36 | seify-rtlsdr = { path = "crates/rtl-sdr-rs", version = "0.0.3", optional = true } 37 | seify-hackrfone = { path = "crates/seify-hackrfone", version = "0.1.0", optional = true } 38 | soapysdr = { version = "0.4", optional = true } 39 | ureq = { version = "2.10", features = ["json"], optional = true } 40 | 41 | [target.'cfg(any(target_os = "linux", target_os= "windows"))'.dependencies] 42 | aaronia-rtsa = { version = "0.0.6", optional = true } 43 | 44 | [dev-dependencies] 45 | clap = { version = "4.5", features = ["derive"] } 46 | ctrlc = "3.4" 47 | env_logger = "0.11" 48 | gnuplot = "0.0.43" 49 | rustfft = "6.2" 50 | vmcircbuffer = "0.0.10" 51 | 52 | [package.metadata.docs.rs] 53 | no-default-features = true 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Seify! A Rusty SDR Hardware Abstraction Library 2 | 3 | ## Goals 4 | 5 | A clear path towards a great Rust SDR driver ecosystem. 6 | 7 | - Seify has an implementation for Soapy and, therefore, supports basically all available SDR frontends. 8 | - Seify supports both typed and generic devices with dynamic dispatch. There is no or minimal overhead for the typed version, i.e., there should be no reason not to use Seify. 9 | - Once more native Rust drivers become available, they can be added to Seify and gradually move from Soapy to pure-Rust drivers. 10 | - A clear path towards a proper async and WASM WebUSB. 11 | - Zero-installation: Rust drivers need no libraries from the base system. Either they are network/http-based or they use `rusb`, which vendors `libusb`. 12 | - Proper driver integration for Rust drivers (e.g., no threads in the core library). 13 | - Rust drivers are added with crate features per binary and do not rely on system-wide libraries. 14 | - Provide a framework for Rust SDR drivers, to avoid diverging concepts of driver implementations in the ecosystem. 15 | 16 | ## Hardware Drivers 17 | 18 | To add a new SDR driver, add a struct, implementing the `DeviceTrait` in the `src/impls` folder and add feature-gated logic for the driver to the probing/enumeration logic in `src/device.rs`. 19 | 20 | At the moment, Seify is designed to commit the driver implementations upstream, i.e., there is no plugin system. 21 | This will probably be added but is no priority at the moment. 22 | While this concentrates maintenance efforts on Seify, it simplifies things for the user, who just add Seify to the project and enables feature flags for their SDR. 23 | 24 | ## Example 25 | 26 | ```rust 27 | use num_complex::Complex32; 28 | use seify::Device; 29 | 30 | pub fn main() -> Result<(), Box> { 31 | let dev = Device::new()?; 32 | let mut samps = [Complex32::new(0.0, 0.0); 1024]; 33 | let mut rx = dev.rx_streamer(&[0])?; 34 | rx.activate()?; 35 | let n = rx.read(&mut [&mut samps], 200000)?; 36 | println!("read {n} samples"); 37 | 38 | Ok(()) 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /examples/args.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use serde::Deserialize; 3 | use serde_with::serde_as; 4 | use serde_with::DisplayFromStr; 5 | use std::error::Error; 6 | 7 | use seify::Args; 8 | 9 | #[serde_as] 10 | #[derive(Debug, Deserialize)] 11 | struct Config { 12 | driver: String, 13 | #[serde_as(as = "DisplayFromStr")] 14 | id: u32, 15 | } 16 | 17 | fn main() -> Result<(), Box> { 18 | // create Args from string 19 | let mut args = Args::from("driver=\"the driver\", id=123, not=interesting")?; 20 | // set value manually 21 | args.set("bar", "baz"); 22 | // merge with other args 23 | args.merge(Args::from("foo = bar")?); 24 | println!("args: {args:?}"); 25 | 26 | // get a value, parsing it from the value string 27 | println!("id {}", args.get::("id").unwrap()); 28 | 29 | // deserialize a struct from the arguments 30 | let c: Config = args.deserialize().unwrap(); 31 | println!("config {c:#?}"); 32 | 33 | Ok(()) 34 | } 35 | -------------------------------------------------------------------------------- /examples/probe.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use seify::enumerate_with_args; 3 | use seify::Device; 4 | use seify::Direction::Rx; 5 | 6 | #[derive(Parser, Debug)] 7 | #[clap(version)] 8 | struct Args { 9 | /// Device Filters 10 | #[clap(short, long, default_value = "")] 11 | args: String, 12 | } 13 | 14 | pub fn main() -> Result<(), Box> { 15 | env_logger::init(); 16 | let cli = Args::parse(); 17 | 18 | let devs = enumerate_with_args(cli.args)?; 19 | println!("Devices"); 20 | println!("========================================="); 21 | println!("devs: {devs:?}"); 22 | 23 | for d in devs { 24 | let dev = Device::from_args(d)?; 25 | 26 | println!(); 27 | println!("Device ({:?} - {:?}), ", dev.driver(), dev.id()?); 28 | println!("========================================="); 29 | 30 | println!("driver: {:?}", dev.driver()); 31 | println!("id: {:?}", dev.id()?); 32 | println!("info: {:?}", dev.info()?); 33 | println!("sample rate: {:?}", dev.sample_rate(Rx, 0)?); 34 | println!("frequency: {:?}", dev.frequency(Rx, 0)?); 35 | println!("gain: {:?}", dev.gain(Rx, 0)?); 36 | println!("gain range: {:?}", dev.gain_range(Rx, 0)?); 37 | println!("sample rate range: {:?}", dev.get_sample_rate_range(Rx, 0)?); 38 | } 39 | 40 | Ok(()) 41 | } 42 | -------------------------------------------------------------------------------- /examples/rx_generic.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use num_complex::Complex32; 3 | 4 | use seify::Device; 5 | use seify::Direction::Rx; 6 | use seify::RxStreamer; 7 | 8 | #[derive(Parser, Debug)] 9 | #[clap(version)] 10 | struct Args { 11 | /// Device Filters 12 | #[clap(short, long, default_value = "")] 13 | args: String, 14 | } 15 | 16 | pub fn main() -> Result<(), Box> { 17 | env_logger::init(); 18 | let cli = Args::parse(); 19 | 20 | let dev = Device::from_args(cli.args)?; 21 | // Get typed reference to device impl 22 | // let r: &seify::impls::RtlSdr = dev.impl_ref().unwrap(); 23 | 24 | // HackRf doesnt support agc 25 | if dev.supports_agc(Rx, 0)? { 26 | dev.enable_agc(Rx, 0, true)?; 27 | } 28 | dev.set_frequency(Rx, 0, 927e6)?; 29 | dev.set_sample_rate(Rx, 0, 3.2e6)?; 30 | 31 | println!("driver: {:?}", dev.driver()); 32 | println!("id: {:?}", dev.id()?); 33 | println!("info: {:?}", dev.info()?); 34 | println!("sample rate: {:?}", dev.sample_rate(Rx, 0)?); 35 | println!("frequency: {:?}", dev.frequency(Rx, 0)?); 36 | println!("gain: {:?}", dev.gain(Rx, 0)?); 37 | 38 | let mut samps = [Complex32::new(0.0, 0.0); 8192]; 39 | let mut rx = dev.rx_streamer(&[0])?; 40 | rx.activate()?; 41 | let n = rx.read(&mut [&mut samps], 200000)?; 42 | 43 | plot(&mut samps[..n]); 44 | 45 | Ok(()) 46 | } 47 | 48 | fn plot(s: &mut [num_complex::Complex32]) { 49 | use gnuplot::*; 50 | 51 | let mut planner = rustfft::FftPlanner::new(); 52 | planner.plan_fft_forward(s.len()).process(s); 53 | 54 | let abs: Vec = s.iter().map(|s| s.norm_sqr().log10()).collect(); 55 | 56 | let mut fg = Figure::new(); 57 | fg.axes2d().set_title("Spectrum", &[]).lines( 58 | 0..s.len(), 59 | abs, 60 | &[LineWidth(3.0), Color("blue"), LineStyle(DotDash)], 61 | ); 62 | fg.show().unwrap(); 63 | } 64 | -------------------------------------------------------------------------------- /examples/rx_threaded.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use num_complex::Complex32; 3 | use std::error::Error; 4 | use std::sync::atomic::AtomicBool; 5 | use std::sync::atomic::Ordering; 6 | use std::sync::Arc; 7 | use vmcircbuffer::sync; 8 | 9 | use seify::Device; 10 | use seify::Direction::Rx; 11 | use seify::RxStreamer; 12 | 13 | #[derive(Parser, Debug)] 14 | #[clap(version)] 15 | struct Args { 16 | /// Device Filter 17 | #[clap(short, long, default_value = "")] 18 | args: String, 19 | } 20 | 21 | pub fn main() -> Result<(), Box> { 22 | env_logger::init(); 23 | let cli = Args::parse(); 24 | 25 | let dev = Device::from_args(cli.args)?; 26 | 27 | println!("driver: {:?}", dev.driver()); 28 | println!("id: {:?}", dev.id()?); 29 | println!("info: {:?}", dev.info()?); 30 | println!("sample rate: {:?}", dev.sample_rate(Rx, 0)?); 31 | println!("frequency: {:?}", dev.frequency(Rx, 0)?); 32 | println!("gain: {:?}", dev.gain(Rx, 0)?); 33 | 34 | let mut w = sync::Circular::with_capacity::(8192)?; 35 | let mut r = w.add_reader(); 36 | 37 | // producer thread 38 | let terminate = Arc::new(AtomicBool::new(false)); 39 | let rx_thread = std::thread::spawn({ 40 | let terminate = terminate.clone(); 41 | move || -> Result<(), Box> { 42 | let mut rx = dev.rx_streamer(&[0])?; 43 | let mtu = rx.mtu()?; 44 | rx.activate()?; 45 | 46 | loop { 47 | if terminate.load(Ordering::Relaxed) { 48 | break Ok(()); 49 | } 50 | let w_buff = w.slice(); 51 | let n = std::cmp::min(w_buff.len(), mtu); 52 | let n = rx.read(&mut [&mut w_buff[0..n]], 200000)?; 53 | w.produce(n); 54 | } 55 | } 56 | }); 57 | 58 | ctrlc::set_handler({ 59 | let terminate = terminate.clone(); 60 | move || { 61 | println!("terminating..."); 62 | terminate.store(true, Ordering::Relaxed); 63 | } 64 | }) 65 | .expect("Error setting Ctrl-C handler"); 66 | 67 | // consumer 68 | loop { 69 | if terminate.load(Ordering::Relaxed) { 70 | break; 71 | } 72 | let r_buff = r.slice().unwrap(); 73 | let l = r_buff.len(); 74 | println!("received {l} samples"); 75 | r.consume(l); 76 | } 77 | 78 | if let Err(e) = rx_thread.join() { 79 | std::panic::resume_unwind(e); 80 | } 81 | Ok(()) 82 | } 83 | -------------------------------------------------------------------------------- /examples/rx_typed.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use num_complex::Complex32; 3 | 4 | use seify::impls::rtlsdr; 5 | use seify::Device; 6 | use seify::Direction::Rx; 7 | use seify::RxStreamer; 8 | 9 | #[derive(Parser, Debug)] 10 | #[clap(version)] 11 | struct Args { 12 | /// Device Filters 13 | #[clap(short, long, default_value = "")] 14 | args: String, 15 | } 16 | 17 | pub fn main() -> Result<(), Box> { 18 | env_logger::init(); 19 | let cli = Args::parse(); 20 | 21 | let rtl = rtlsdr::RtlSdr::open(cli.args)?; 22 | let dev = Device::from_impl(rtl); 23 | // Get typed reference to device impl 24 | // let _r : &seify::impls::RtlSdr = dev.impl_ref().unwrap(); 25 | 26 | dev.enable_agc(Rx, 0, true)?; 27 | dev.set_frequency(Rx, 0, 101e6)?; 28 | dev.set_sample_rate(Rx, 0, 3.2e6)?; 29 | 30 | println!("driver: {:?}", dev.driver()); 31 | println!("id: {:?}", dev.id()?); 32 | println!("info: {:?}", dev.info()?); 33 | println!("sample rate: {:?}", dev.sample_rate(Rx, 0)?); 34 | println!("frequency: {:?}", dev.frequency(Rx, 0)?); 35 | println!("gain: {:?}", dev.gain(Rx, 0)?); 36 | 37 | let mut samps = [Complex32::new(0.0, 0.0); 8192]; 38 | let mut rx = dev.rx_streamer(&[0])?; 39 | rx.activate()?; 40 | let n = rx.read(&mut [&mut samps], 2000)?; 41 | 42 | plot(&mut samps[..n]); 43 | 44 | Ok(()) 45 | } 46 | 47 | fn plot(s: &mut [num_complex::Complex32]) { 48 | use gnuplot::*; 49 | 50 | let mut planner = rustfft::FftPlanner::new(); 51 | planner.plan_fft_forward(s.len()).process(s); 52 | 53 | let abs = s.iter().map(|s| s.norm_sqr().log10()); 54 | 55 | let mut fg = Figure::new(); 56 | fg.axes2d().set_title("Spectrum", &[]).lines( 57 | 0..s.len(), 58 | abs, 59 | &[LineWidth(3.0), Color("blue"), LineStyle(DotDash)], 60 | ); 61 | fg.show().unwrap(); 62 | } 63 | -------------------------------------------------------------------------------- /src/args.rs: -------------------------------------------------------------------------------- 1 | use nom::branch::alt; 2 | use nom::bytes::complete::escaped; 3 | use nom::bytes::complete::tag; 4 | use nom::bytes::complete::take_while1; 5 | use nom::character::complete::multispace0; 6 | use nom::character::complete::none_of; 7 | use nom::error::{FromExternalError, ParseError}; 8 | use nom::multi::separated_list0; 9 | use nom::sequence::delimited; 10 | use nom::sequence::separated_pair; 11 | use nom::IResult; 12 | use serde::Deserialize; 13 | use serde::Serialize; 14 | use serde_with::serde_as; 15 | use std::collections::HashMap; 16 | use std::str::FromStr; 17 | 18 | use crate::Error; 19 | 20 | /// Arbitrary arguments and parameters. 21 | #[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] 22 | #[serde(transparent)] 23 | #[serde_as] 24 | pub struct Args { 25 | #[serde_with(as = "BTreeMap<_, Vec>")] 26 | map: HashMap, 27 | } 28 | 29 | impl Args { 30 | /// Create new, empty [Args]. 31 | pub fn new() -> Self { 32 | Self { 33 | map: HashMap::new(), 34 | } 35 | } 36 | /// Create new, [Args].from string 37 | pub fn from>(s: S) -> Result { 38 | s.as_ref().parse() 39 | } 40 | /// Try to get a value of type `V` that is tried to be parsed from the string mapped by the 41 | /// `key`. 42 | pub fn get>( 43 | &self, 44 | key: impl AsRef, 45 | ) -> Result { 46 | self.map 47 | .get(key.as_ref()) 48 | .ok_or(Error::NotFound) 49 | .and_then(|v| v.parse().or(Err(Error::ValueError))) 50 | } 51 | /// Map the `key` the stringified `value`. 52 | pub fn set, V: Into>(&mut self, key: K, value: V) -> Option { 53 | self.map.insert(key.into(), value.into()) 54 | } 55 | /// Remove the `key` together with its associated value. 56 | pub fn remove>(&mut self, key: K) -> Option { 57 | self.map.remove(key.as_ref()) 58 | } 59 | /// Iterate over key-value pairs of the [`Args`]. 60 | pub fn iter(&self) -> std::collections::hash_map::Iter<'_, String, String> { 61 | self.map.iter() 62 | } 63 | /// Iterate mutably over key-value pairs of the [`Args`]. 64 | pub fn iter_mut(&mut self) -> std::collections::hash_map::IterMut<'_, String, String> { 65 | self.map.iter_mut() 66 | } 67 | /// Get a reference to the underlying [HashMap](std::collections::HashMap). 68 | pub fn map(&self) -> &HashMap { 69 | &self.map 70 | } 71 | /// Merge with another Args struct, consuming it. 72 | pub fn merge(&mut self, mut other: Self) { 73 | for (k, v) in other.map.drain() { 74 | self.set(k, v); 75 | } 76 | } 77 | /// Try to [`Deserialize`] a value of type `D` from the JSON-serialized [`Args`]. 78 | pub fn deserialize Deserialize<'a>>(&self) -> Option { 79 | let s = serde_json::to_string(&self).ok()?; 80 | serde_json::from_str(&s).ok() 81 | } 82 | } 83 | 84 | impl std::fmt::Debug for Args { 85 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 86 | self.map.fmt(f) 87 | } 88 | } 89 | 90 | impl std::fmt::Display for Args { 91 | fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { 92 | let mut i = self.iter(); 93 | if let Some((k, v)) = i.next() { 94 | write!(fmt, "{k}={v}")?; 95 | for (k, v) in i { 96 | write!(fmt, ", {k}={v}")?; 97 | } 98 | } 99 | Ok(()) 100 | } 101 | } 102 | 103 | fn parse_string<'a, E>(input: &'a str) -> IResult<&'a str, &'a str, E> 104 | where 105 | E: ParseError<&'a str> + FromExternalError<&'a str, std::num::ParseIntError> + std::fmt::Debug, 106 | { 107 | let esc_single = escaped(none_of("\\\'"), '\\', tag("'")); 108 | let esc_or_empty_single = alt((esc_single, tag(""))); 109 | let esc_double = escaped(none_of("\\\""), '\\', tag("\"")); 110 | let esc_or_empty_double = alt((esc_double, tag(""))); 111 | let filter = |c: char| c != ',' && c != '=' && !c.is_whitespace(); 112 | 113 | delimited( 114 | multispace0, 115 | alt(( 116 | delimited(tag("'"), esc_or_empty_single, tag("'")), 117 | delimited(tag("\""), esc_or_empty_double, tag("\"")), 118 | take_while1(filter), 119 | )), 120 | multispace0, 121 | )(input) 122 | } 123 | 124 | impl FromStr for Args { 125 | type Err = Error; 126 | 127 | fn from_str(s: &str) -> Result { 128 | let v = separated_list0( 129 | delimited(multispace0, tag(","), multispace0), 130 | separated_pair( 131 | parse_string::>, 132 | delimited(multispace0, tag("="), multispace0), 133 | parse_string, 134 | ), 135 | )(s) 136 | .or(Err(Error::ValueError))?; 137 | Ok(Args { 138 | map: HashMap::from_iter(v.1.iter().cloned().map(|(a, b)| (a.into(), b.into()))), 139 | }) 140 | } 141 | } 142 | 143 | // impl for T: AsRef once this issue is resolved 144 | // https://github.com/rust-lang/rust/issues/50133#issuecomment-64690839 145 | impl TryInto for &str { 146 | type Error = Error; 147 | 148 | fn try_into(self) -> Result { 149 | self.parse() 150 | } 151 | } 152 | 153 | impl TryInto for String { 154 | type Error = Error; 155 | 156 | fn try_into(self) -> Result { 157 | self.parse() 158 | } 159 | } 160 | 161 | impl TryInto for &String { 162 | type Error = Error; 163 | 164 | fn try_into(self) -> Result { 165 | self.parse() 166 | } 167 | } 168 | 169 | impl TryInto for Option { 170 | type Error = Error; 171 | 172 | fn try_into(self) -> Result { 173 | match self { 174 | Some(s) => s.parse(), 175 | None => Ok(Args::new()), 176 | } 177 | } 178 | } 179 | 180 | impl From<&Args> for Args { 181 | fn from(value: &Args) -> Self { 182 | value.clone() 183 | } 184 | } 185 | 186 | impl From<()> for Args { 187 | fn from(_value: ()) -> Self { 188 | Args::new() 189 | } 190 | } 191 | 192 | impl Default for Args { 193 | fn default() -> Self { 194 | Self::new() 195 | } 196 | } 197 | 198 | #[cfg(test)] 199 | mod tests { 200 | use super::*; 201 | 202 | #[test] 203 | fn deserialize_empty() { 204 | let c: Args = "".parse().unwrap(); 205 | assert_eq!(c.map.len(), 0); 206 | } 207 | #[test] 208 | fn deserialize_single() { 209 | let c: Args = "foo=bar".parse().unwrap(); 210 | assert_eq!(c.get::("foo").unwrap(), "bar"); 211 | assert_eq!(c.map.len(), 1); 212 | } 213 | #[test] 214 | fn deserialize_more() { 215 | let c: Args = "foo=bar,fo=ba".parse().unwrap(); 216 | assert_eq!(c.get::("foo").unwrap(), "bar"); 217 | assert_eq!(c.get::("fo").unwrap(), "ba"); 218 | assert_eq!(c.map.len(), 2); 219 | } 220 | #[test] 221 | fn deserialize_whitespace() { 222 | let c: Args = " foo = bar , fo=ba ".parse().unwrap(); 223 | assert_eq!(c.get::("foo").unwrap(), "bar"); 224 | assert_eq!(c.get::("fo").unwrap(), "ba"); 225 | assert_eq!(c.map.len(), 2); 226 | } 227 | #[test] 228 | fn deserialize_nonascii() { 229 | let c: Args = " f-oo = b_ar".parse().unwrap(); 230 | assert_eq!(c.get::("f-oo").unwrap(), "b_ar"); 231 | assert_eq!(c.map.len(), 1); 232 | } 233 | #[test] 234 | fn deserialize_dquoted() { 235 | let c: Args = "foo=bar,fo=\"ba ,\"".parse().unwrap(); 236 | assert_eq!(c.get::("foo").unwrap(), "bar"); 237 | assert_eq!(c.get::("fo").unwrap(), "ba ,"); 238 | assert_eq!(c.map.len(), 2); 239 | } 240 | #[test] 241 | fn deserialize_squoted() { 242 | let c: Args = "foo=bar,fo='ba ,\"', hello ='a s d f '".parse().unwrap(); 243 | assert_eq!(c.get::("foo").unwrap(), "bar"); 244 | assert_eq!(c.get::("fo").unwrap(), "ba ,\""); 245 | assert_eq!(c.get::("hello").unwrap(), "a s d f "); 246 | assert_eq!(c.map.len(), 3); 247 | } 248 | #[test] 249 | fn config_get() { 250 | let c: Args = "foo=123,bar=lol".parse().unwrap(); 251 | assert_eq!(c.map.len(), 2); 252 | assert_eq!(c.get::("foo").unwrap(), 123); 253 | assert_eq!(c.get::("foo").unwrap(), "123"); 254 | assert!(matches!(c.get::("fooo"), Err(Error::NotFound))); 255 | assert_eq!(c.get::("bar").unwrap(), "lol"); 256 | assert!(matches!(c.get::("bar"), Err(Error::ValueError))); 257 | } 258 | #[test] 259 | fn serde() { 260 | use serde::Deserialize; 261 | use serde_with::serde_as; 262 | use serde_with::DisplayFromStr; 263 | 264 | #[serde_as] 265 | #[derive(Deserialize)] 266 | struct Foo { 267 | #[serde_as(as = "DisplayFromStr")] 268 | bar: u32, 269 | } 270 | 271 | let c: Args = "bar=123,hello=world".parse().unwrap(); 272 | let s = serde_json::to_string(&c).unwrap(); 273 | let f: Foo = serde_json::from_str(&s).unwrap(); 274 | assert_eq!(f.bar, 123); 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /src/device.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(unused_variables)] 3 | use std::any::Any; 4 | use std::sync::Arc; 5 | 6 | use crate::Args; 7 | use crate::Direction; 8 | use crate::Driver; 9 | use crate::Error; 10 | use crate::Range; 11 | use crate::RxStreamer; 12 | use crate::TxStreamer; 13 | 14 | /// Central trait, implemented by hardware drivers. 15 | pub trait DeviceTrait: Any + Send { 16 | /// Associated RX streamer 17 | type RxStreamer: RxStreamer; 18 | /// Associated TX streamer 19 | type TxStreamer: TxStreamer; 20 | 21 | /// Cast to Any for downcasting. 22 | fn as_any(&self) -> &dyn Any; 23 | /// Cast to Any for downcasting to a mutable reference. 24 | fn as_any_mut(&mut self) -> &mut dyn Any; 25 | 26 | /// SDR [driver](Driver) 27 | fn driver(&self) -> Driver; 28 | /// Identifier for the device, e.g., its serial. 29 | fn id(&self) -> Result; 30 | /// Device info that can be displayed to the user. 31 | fn info(&self) -> Result; 32 | /// Number of supported Channels. 33 | fn num_channels(&self, direction: Direction) -> Result; 34 | /// Full Duplex support. 35 | fn full_duplex(&self, direction: Direction, channel: usize) -> Result; 36 | 37 | //================================ STREAMER ============================================ 38 | /// Create an RX streamer. 39 | fn rx_streamer(&self, channels: &[usize], args: Args) -> Result; 40 | /// Create a TX streamer. 41 | fn tx_streamer(&self, channels: &[usize], args: Args) -> Result; 42 | 43 | //================================ ANTENNA ============================================ 44 | /// List of available antenna ports. 45 | fn antennas(&self, direction: Direction, channel: usize) -> Result, Error>; 46 | /// Currently used antenna port. 47 | fn antenna(&self, direction: Direction, channel: usize) -> Result; 48 | /// Set antenna port. 49 | fn set_antenna(&self, direction: Direction, channel: usize, name: &str) -> Result<(), Error>; 50 | 51 | //================================ AGC ============================================ 52 | /// Does the device support automatic gain control? 53 | fn supports_agc(&self, direction: Direction, channel: usize) -> Result; 54 | 55 | /// Enable or disable automatic gain control. 56 | fn enable_agc(&self, direction: Direction, channel: usize, agc: bool) -> Result<(), Error>; 57 | 58 | /// Returns true, if automatic gain control is enabled 59 | fn agc(&self, direction: Direction, channel: usize) -> Result; 60 | 61 | //================================ GAIN ============================================ 62 | /// List of available gain elements. 63 | /// 64 | /// Elements should be in order RF to baseband. 65 | fn gain_elements(&self, direction: Direction, channel: usize) -> Result, Error>; 66 | /// Set the overall amplification in a chain. 67 | /// 68 | /// The gain will be distributed automatically across available elements. 69 | /// 70 | /// `gain`: the new amplification value in dB 71 | fn set_gain(&self, direction: Direction, channel: usize, gain: f64) -> Result<(), Error>; 72 | 73 | /// Get the overall value of the gain elements in a chain in dB. 74 | fn gain(&self, direction: Direction, channel: usize) -> Result, Error>; 75 | 76 | /// Get the overall [`Range`] of possible gain values. 77 | fn gain_range(&self, direction: Direction, channel: usize) -> Result; 78 | 79 | /// Set the value of a amplification element in a chain. 80 | /// 81 | /// ## Arguments 82 | /// * `name`: the name of an amplification element from `Device::list_gains` 83 | /// * `gain`: the new amplification value in dB 84 | fn set_gain_element( 85 | &self, 86 | direction: Direction, 87 | channel: usize, 88 | name: &str, 89 | gain: f64, 90 | ) -> Result<(), Error>; 91 | 92 | /// Get the value of an individual amplification element in a chain in dB. 93 | fn gain_element( 94 | &self, 95 | direction: Direction, 96 | channel: usize, 97 | name: &str, 98 | ) -> Result, Error>; 99 | 100 | /// Get the range of possible gain values for a specific element. 101 | fn gain_element_range( 102 | &self, 103 | direction: Direction, 104 | channel: usize, 105 | name: &str, 106 | ) -> Result; 107 | 108 | //================================ FREQUENCY ============================================ 109 | 110 | /// Get the ranges of overall frequency values. 111 | fn frequency_range(&self, direction: Direction, channel: usize) -> Result; 112 | 113 | /// Get the overall center frequency of the chain. 114 | /// 115 | /// - For RX, this specifies the down-conversion frequency. 116 | /// - For TX, this specifies the up-conversion frequency. 117 | /// 118 | /// Returns the center frequency in Hz. 119 | fn frequency(&self, direction: Direction, channel: usize) -> Result; 120 | 121 | /// Set the center frequency of the chain. 122 | /// 123 | /// - For RX, this specifies the down-conversion frequency. 124 | /// - For TX, this specifies the up-conversion frequency. 125 | /// 126 | /// The default implementation of `set_frequency` will tune the "RF" 127 | /// component as close as possible to the requested center frequency in Hz. 128 | /// Tuning inaccuracies will be compensated for with the "BB" component. 129 | /// 130 | /// The `args` can be used to augment the tuning algorithm. 131 | /// 132 | /// - Use `"OFFSET"` to specify an "RF" tuning offset, 133 | /// usually with the intention of moving the LO out of the passband. 134 | /// The offset will be compensated for using the "BB" component. 135 | /// - Use the name of a component for the key and a frequency in Hz 136 | /// as the value (any format) to enforce a specific frequency. 137 | /// The other components will be tuned with compensation 138 | /// to achieve the specified overall frequency. 139 | /// - Use the name of a component for the key and the value `"IGNORE"` 140 | /// so that the tuning algorithm will avoid altering the component. 141 | /// - Vendor specific implementations can also use the same args to augment 142 | /// tuning in other ways such as specifying fractional vs integer N tuning. 143 | /// 144 | fn set_frequency( 145 | &self, 146 | direction: Direction, 147 | channel: usize, 148 | frequency: f64, 149 | args: Args, 150 | ) -> Result<(), Error>; 151 | 152 | /// List available tunable elements in the chain. 153 | /// 154 | /// Elements should be in order RF to baseband. 155 | fn frequency_components( 156 | &self, 157 | direction: Direction, 158 | channel: usize, 159 | ) -> Result, Error>; 160 | 161 | /// Get the range of tunable values for the specified element. 162 | fn component_frequency_range( 163 | &self, 164 | direction: Direction, 165 | channel: usize, 166 | name: &str, 167 | ) -> Result; 168 | 169 | /// Get the frequency of a tunable element in the chain. 170 | fn component_frequency( 171 | &self, 172 | direction: Direction, 173 | channel: usize, 174 | name: &str, 175 | ) -> Result; 176 | 177 | /// Tune the center frequency of the specified element. 178 | /// 179 | /// - For RX, this specifies the down-conversion frequency. 180 | /// - For TX, this specifies the up-conversion frequency. 181 | fn set_component_frequency( 182 | &self, 183 | direction: Direction, 184 | channel: usize, 185 | name: &str, 186 | frequency: f64, 187 | ) -> Result<(), Error>; 188 | 189 | //================================ SAMPLE RATE ============================================ 190 | 191 | /// Get the baseband sample rate of the chain in samples per second. 192 | fn sample_rate(&self, direction: Direction, channel: usize) -> Result; 193 | 194 | /// Set the baseband sample rate of the chain in samples per second. 195 | fn set_sample_rate(&self, direction: Direction, channel: usize, rate: f64) 196 | -> Result<(), Error>; 197 | 198 | /// Get the range of possible baseband sample rates. 199 | fn get_sample_rate_range(&self, direction: Direction, channel: usize) -> Result; 200 | 201 | //================================ BANDWIDTH ============================================ 202 | 203 | /// Get the hardware bandwidth filter, if available. 204 | /// 205 | /// Returns `Err(Error::NotSupported)` if unsupported in underlying driver. 206 | fn bandwidth(&self, direction: Direction, channel: usize) -> Result; 207 | 208 | /// Set the hardware bandwidth filter, if available. 209 | /// 210 | /// Returns `Err(Error::NotSupported)` if unsupported in underlying driver. 211 | fn set_bandwidth(&self, direction: Direction, channel: usize, bw: f64) -> Result<(), Error>; 212 | 213 | /// Get the range of possible bandwidth filter values, if available. 214 | /// 215 | /// Returns `Err(Error::NotSupported)` if unsupported in underlying driver. 216 | fn get_bandwidth_range(&self, direction: Direction, channel: usize) -> Result; 217 | 218 | //========================= AUTOMATIC DC OFFSET CORRECTIONS =============================== 219 | 220 | /// Returns true if automatic corrections are supported 221 | fn has_dc_offset_mode(&self, direction: Direction, channel: usize) -> Result; 222 | 223 | /// Enable or disable automatic DC offset corrections mode. 224 | fn set_dc_offset_mode( 225 | &self, 226 | direction: Direction, 227 | channel: usize, 228 | automatic: bool, 229 | ) -> Result<(), Error>; 230 | 231 | /// Returns true if automatic DC offset mode is enabled 232 | fn dc_offset_mode(&self, direction: Direction, channel: usize) -> Result; 233 | } 234 | 235 | /// Wrapps a driver, implementing the [DeviceTrait]. 236 | /// 237 | /// Implements a more ergonomic version of the [`DeviceTrait`], e.g., using `Into`, which 238 | /// would not be possible in traits. 239 | #[derive(Clone)] 240 | pub struct Device { 241 | dev: T, 242 | } 243 | 244 | impl Device { 245 | /// Creates a [`GenericDevice`] opening the first device discovered through 246 | /// [`enumerate`](crate::enumerate). 247 | pub fn new() -> Result { 248 | let mut devs = crate::enumerate()?; 249 | if devs.is_empty() { 250 | return Err(Error::NotFound); 251 | } 252 | Self::from_args(devs.remove(0)) 253 | } 254 | 255 | /// Creates a [`GenericDevice`] opening the first device with a given `driver`, specified in 256 | /// the `args` or the first device discovered through [`enumerate`](crate::enumerate) that 257 | /// matches the args. 258 | pub fn from_args>(args: A) -> Result { 259 | let args = args.try_into().map_err(|_| Error::ValueError)?; 260 | let driver = match args.get::("driver") { 261 | Ok(d) => Some(d), 262 | Err(Error::NotFound) => None, 263 | Err(e) => return Err(e), 264 | }; 265 | #[cfg(all(feature = "aaronia", any(target_os = "linux", target_os = "windows")))] 266 | { 267 | if driver.is_none() || matches!(driver, Some(Driver::Aaronia)) { 268 | match crate::impls::Aaronia::open(&args) { 269 | Ok(d) => { 270 | return Ok(Device { 271 | dev: Arc::new(DeviceWrapper { dev: d }), 272 | }) 273 | } 274 | Err(Error::NotFound) => { 275 | if driver.is_some() { 276 | return Err(Error::NotFound); 277 | } 278 | } 279 | Err(e) => return Err(e), 280 | } 281 | } 282 | } 283 | #[cfg(all(feature = "aaronia_http", not(target_arch = "wasm32")))] 284 | { 285 | if driver.is_none() || matches!(driver, Some(Driver::AaroniaHttp)) { 286 | match crate::impls::AaroniaHttp::open(&args) { 287 | Ok(d) => { 288 | return Ok(Device { 289 | dev: Arc::new(DeviceWrapper { dev: d }), 290 | }) 291 | } 292 | Err(Error::NotFound) => { 293 | if driver.is_some() { 294 | return Err(Error::NotFound); 295 | } 296 | } 297 | Err(e) => return Err(e), 298 | } 299 | } 300 | } 301 | #[cfg(all(feature = "rtlsdr", not(target_arch = "wasm32")))] 302 | { 303 | if driver.is_none() || matches!(driver, Some(Driver::RtlSdr)) { 304 | match crate::impls::RtlSdr::open(&args) { 305 | Ok(d) => { 306 | return Ok(Device { 307 | dev: Arc::new(DeviceWrapper { dev: d }), 308 | }) 309 | } 310 | Err(Error::NotFound) => { 311 | if driver.is_some() { 312 | return Err(Error::NotFound); 313 | } 314 | } 315 | Err(e) => return Err(e), 316 | } 317 | } 318 | } 319 | #[cfg(all(feature = "soapy", not(target_arch = "wasm32")))] 320 | { 321 | if driver.is_none() || matches!(driver, Some(Driver::Soapy)) { 322 | match crate::impls::Soapy::open(&args) { 323 | Ok(d) => { 324 | return Ok(Device { 325 | dev: Arc::new(DeviceWrapper { dev: d }), 326 | }) 327 | } 328 | Err(Error::NotFound) => { 329 | if driver.is_some() { 330 | return Err(Error::NotFound); 331 | } 332 | } 333 | Err(e) => return Err(e), 334 | } 335 | } 336 | } 337 | #[cfg(all(feature = "hackrfone", not(target_arch = "wasm32")))] 338 | { 339 | if driver.is_none() || matches!(driver, Some(Driver::HackRf)) { 340 | match crate::impls::HackRfOne::open(&args) { 341 | Ok(d) => { 342 | return Ok(Device { 343 | dev: Arc::new(DeviceWrapper { dev: d }), 344 | }) 345 | } 346 | Err(Error::NotFound) => { 347 | if driver.is_some() { 348 | return Err(Error::NotFound); 349 | } 350 | } 351 | Err(e) => return Err(e), 352 | } 353 | } 354 | } 355 | #[cfg(feature = "dummy")] 356 | { 357 | if driver.is_none() || matches!(driver, Some(Driver::Dummy)) { 358 | match crate::impls::Dummy::open(&args) { 359 | Ok(d) => { 360 | return Ok(Device { 361 | dev: Arc::new(DeviceWrapper { dev: d }), 362 | }) 363 | } 364 | Err(Error::NotFound) => { 365 | if driver.is_some() { 366 | return Err(Error::NotFound); 367 | } 368 | } 369 | Err(e) => return Err(e), 370 | } 371 | } 372 | } 373 | 374 | Err(Error::NotFound) 375 | } 376 | } 377 | 378 | /// Type for a generic/wrapped hardware driver, implementing the [`DeviceTrait`]. 379 | /// 380 | /// This is usually used to create a hardware-independent `Device`, for example, 381 | /// through [`Device::new`], which doesn't know a priori which implementation will be used. 382 | /// The type abstracts over the `DeviceTrait` implementation as well as the associated 383 | /// streamer implementations. 384 | pub type GenericDevice = 385 | Arc, TxStreamer = Box> + Sync>; 386 | 387 | impl Device { 388 | /// Create a device from the device implementation. 389 | pub fn from_impl(dev: T) -> Self { 390 | Self { dev } 391 | } 392 | /// Try to downcast to a given device implementation `D`, either directly (from `Device`) 393 | /// or indirectly (from a `Device` that wraps a `D`). 394 | pub fn impl_ref(&self) -> Result<&D, Error> { 395 | if let Some(d) = self.dev.as_any().downcast_ref::() { 396 | return Ok(d); 397 | } 398 | 399 | let d = self 400 | .dev 401 | .as_any() 402 | .downcast_ref::, 405 | TxStreamer = Box<(dyn TxStreamer + 'static)>, 406 | > + Sync 407 | + 'static), 408 | >>() 409 | .ok_or(Error::ValueError)?; 410 | 411 | let d = (**d) 412 | .as_any() 413 | .downcast_ref::>() 414 | .ok_or(Error::ValueError)?; 415 | Ok(&d.dev) 416 | } 417 | /// Try to downcast mutably to a given device implementation `D`, either directly 418 | /// (from `Device`) or indirectly (from a `Device` that wraps a `D`). 419 | pub fn impl_mut(&mut self) -> Result<&mut D, Error> { 420 | // work around borrow checker limitation 421 | if let Some(d) = self.dev.as_any().downcast_ref::() { 422 | Ok(self.dev.as_any_mut().downcast_mut::().unwrap()) 423 | } else { 424 | let d = self 425 | .dev 426 | .as_any_mut() 427 | .downcast_mut::, 430 | TxStreamer = Box<(dyn TxStreamer + 'static)>, 431 | > + 'static), 432 | >>() 433 | .ok_or(Error::ValueError)?; 434 | 435 | let d = (**d) 436 | .as_any_mut() 437 | .downcast_mut::>() 438 | .ok_or(Error::ValueError)?; 439 | Ok(&mut d.dev) 440 | } 441 | } 442 | } 443 | 444 | struct DeviceWrapper { 445 | dev: D, 446 | } 447 | 448 | impl< 449 | R: RxStreamer + 'static, 450 | T: TxStreamer + 'static, 451 | D: DeviceTrait, 452 | > DeviceTrait for DeviceWrapper 453 | { 454 | type RxStreamer = Box; 455 | type TxStreamer = Box; 456 | 457 | fn as_any(&self) -> &dyn Any { 458 | self 459 | } 460 | fn as_any_mut(&mut self) -> &mut dyn Any { 461 | self 462 | } 463 | 464 | fn driver(&self) -> Driver { 465 | self.dev.driver() 466 | } 467 | fn id(&self) -> Result { 468 | self.dev.id() 469 | } 470 | fn info(&self) -> Result { 471 | self.dev.info() 472 | } 473 | fn num_channels(&self, direction: Direction) -> Result { 474 | self.dev.num_channels(direction) 475 | } 476 | fn full_duplex(&self, direction: Direction, channel: usize) -> Result { 477 | self.dev.full_duplex(direction, channel) 478 | } 479 | 480 | fn rx_streamer(&self, channels: &[usize], args: Args) -> Result { 481 | Ok(Box::new(self.dev.rx_streamer(channels, args)?)) 482 | } 483 | fn tx_streamer(&self, channels: &[usize], args: Args) -> Result { 484 | Ok(Box::new(self.dev.tx_streamer(channels, args)?)) 485 | } 486 | 487 | fn antennas(&self, direction: Direction, channel: usize) -> Result, Error> { 488 | self.dev.antennas(direction, channel) 489 | } 490 | 491 | fn antenna(&self, direction: Direction, channel: usize) -> Result { 492 | self.dev.antenna(direction, channel) 493 | } 494 | 495 | fn set_antenna(&self, direction: Direction, channel: usize, name: &str) -> Result<(), Error> { 496 | self.dev.set_antenna(direction, channel, name) 497 | } 498 | 499 | fn gain_elements(&self, direction: Direction, channel: usize) -> Result, Error> { 500 | self.dev.gain_elements(direction, channel) 501 | } 502 | 503 | fn supports_agc(&self, direction: Direction, channel: usize) -> Result { 504 | self.dev.supports_agc(direction, channel) 505 | } 506 | 507 | fn enable_agc(&self, direction: Direction, channel: usize, agc: bool) -> Result<(), Error> { 508 | self.dev.enable_agc(direction, channel, agc) 509 | } 510 | 511 | fn agc(&self, direction: Direction, channel: usize) -> Result { 512 | self.dev.agc(direction, channel) 513 | } 514 | 515 | fn set_gain(&self, direction: Direction, channel: usize, gain: f64) -> Result<(), Error> { 516 | self.dev.set_gain(direction, channel, gain) 517 | } 518 | 519 | fn gain(&self, direction: Direction, channel: usize) -> Result, Error> { 520 | self.dev.gain(direction, channel) 521 | } 522 | 523 | fn gain_range(&self, direction: Direction, channel: usize) -> Result { 524 | self.dev.gain_range(direction, channel) 525 | } 526 | 527 | fn set_gain_element( 528 | &self, 529 | direction: Direction, 530 | channel: usize, 531 | name: &str, 532 | gain: f64, 533 | ) -> Result<(), Error> { 534 | self.dev.set_gain_element(direction, channel, name, gain) 535 | } 536 | 537 | fn gain_element( 538 | &self, 539 | direction: Direction, 540 | channel: usize, 541 | name: &str, 542 | ) -> Result, Error> { 543 | self.dev.gain_element(direction, channel, name) 544 | } 545 | 546 | fn gain_element_range( 547 | &self, 548 | direction: Direction, 549 | channel: usize, 550 | name: &str, 551 | ) -> Result { 552 | self.dev.gain_element_range(direction, channel, name) 553 | } 554 | 555 | fn frequency_range(&self, direction: Direction, channel: usize) -> Result { 556 | self.dev.frequency_range(direction, channel) 557 | } 558 | 559 | fn frequency(&self, direction: Direction, channel: usize) -> Result { 560 | self.dev.frequency(direction, channel) 561 | } 562 | 563 | fn set_frequency( 564 | &self, 565 | direction: Direction, 566 | channel: usize, 567 | frequency: f64, 568 | args: Args, 569 | ) -> Result<(), Error> { 570 | self.dev.set_frequency(direction, channel, frequency, args) 571 | } 572 | 573 | fn frequency_components( 574 | &self, 575 | direction: Direction, 576 | channel: usize, 577 | ) -> Result, Error> { 578 | self.dev.frequency_components(direction, channel) 579 | } 580 | 581 | fn component_frequency_range( 582 | &self, 583 | direction: Direction, 584 | channel: usize, 585 | name: &str, 586 | ) -> Result { 587 | self.dev.component_frequency_range(direction, channel, name) 588 | } 589 | 590 | fn component_frequency( 591 | &self, 592 | direction: Direction, 593 | channel: usize, 594 | name: &str, 595 | ) -> Result { 596 | self.dev.component_frequency(direction, channel, name) 597 | } 598 | 599 | fn set_component_frequency( 600 | &self, 601 | direction: Direction, 602 | channel: usize, 603 | name: &str, 604 | frequency: f64, 605 | ) -> Result<(), Error> { 606 | self.dev 607 | .set_component_frequency(direction, channel, name, frequency) 608 | } 609 | 610 | fn sample_rate(&self, direction: Direction, channel: usize) -> Result { 611 | self.dev.sample_rate(direction, channel) 612 | } 613 | 614 | fn set_sample_rate( 615 | &self, 616 | direction: Direction, 617 | channel: usize, 618 | rate: f64, 619 | ) -> Result<(), Error> { 620 | self.dev.set_sample_rate(direction, channel, rate) 621 | } 622 | 623 | fn get_sample_rate_range(&self, direction: Direction, channel: usize) -> Result { 624 | self.dev.get_sample_rate_range(direction, channel) 625 | } 626 | 627 | fn bandwidth(&self, direction: Direction, channel: usize) -> Result { 628 | self.dev.bandwidth(direction, channel) 629 | } 630 | 631 | fn set_bandwidth(&self, direction: Direction, channel: usize, bw: f64) -> Result<(), Error> { 632 | self.dev.set_bandwidth(direction, channel, bw) 633 | } 634 | 635 | fn get_bandwidth_range(&self, direction: Direction, channel: usize) -> Result { 636 | self.dev.get_bandwidth_range(direction, channel) 637 | } 638 | 639 | fn has_dc_offset_mode(&self, direction: Direction, channel: usize) -> Result { 640 | self.dev.has_dc_offset_mode(direction, channel) 641 | } 642 | 643 | fn set_dc_offset_mode( 644 | &self, 645 | direction: Direction, 646 | channel: usize, 647 | automatic: bool, 648 | ) -> Result<(), Error> { 649 | self.dev.set_dc_offset_mode(direction, channel, automatic) 650 | } 651 | 652 | fn dc_offset_mode(&self, direction: Direction, channel: usize) -> Result { 653 | self.dev.dc_offset_mode(direction, channel) 654 | } 655 | } 656 | 657 | #[doc(hidden)] 658 | impl DeviceTrait for GenericDevice { 659 | type RxStreamer = Box; 660 | type TxStreamer = Box; 661 | 662 | fn as_any(&self) -> &dyn Any { 663 | self 664 | } 665 | 666 | fn as_any_mut(&mut self) -> &mut dyn Any { 667 | self 668 | } 669 | 670 | fn driver(&self) -> Driver { 671 | self.as_ref().driver() 672 | } 673 | fn id(&self) -> Result { 674 | self.as_ref().id() 675 | } 676 | fn info(&self) -> Result { 677 | self.as_ref().info() 678 | } 679 | fn num_channels(&self, direction: Direction) -> Result { 680 | self.as_ref().num_channels(direction) 681 | } 682 | fn full_duplex(&self, direction: Direction, channel: usize) -> Result { 683 | self.as_ref().full_duplex(direction, channel) 684 | } 685 | 686 | fn rx_streamer(&self, channels: &[usize], args: Args) -> Result { 687 | Ok(Box::new(self.as_ref().rx_streamer(channels, args)?)) 688 | } 689 | 690 | fn tx_streamer(&self, channels: &[usize], args: Args) -> Result { 691 | Ok(Box::new(self.as_ref().tx_streamer(channels, args)?)) 692 | } 693 | 694 | fn antennas(&self, direction: Direction, channel: usize) -> Result, Error> { 695 | self.as_ref().antennas(direction, channel) 696 | } 697 | 698 | fn antenna(&self, direction: Direction, channel: usize) -> Result { 699 | self.as_ref().antenna(direction, channel) 700 | } 701 | 702 | fn set_antenna(&self, direction: Direction, channel: usize, name: &str) -> Result<(), Error> { 703 | self.as_ref().set_antenna(direction, channel, name) 704 | } 705 | 706 | fn gain_elements(&self, direction: Direction, channel: usize) -> Result, Error> { 707 | self.as_ref().gain_elements(direction, channel) 708 | } 709 | 710 | fn supports_agc(&self, direction: Direction, channel: usize) -> Result { 711 | self.as_ref().supports_agc(direction, channel) 712 | } 713 | 714 | fn enable_agc(&self, direction: Direction, channel: usize, agc: bool) -> Result<(), Error> { 715 | self.as_ref().enable_agc(direction, channel, agc) 716 | } 717 | 718 | fn agc(&self, direction: Direction, channel: usize) -> Result { 719 | self.as_ref().agc(direction, channel) 720 | } 721 | 722 | fn set_gain(&self, direction: Direction, channel: usize, gain: f64) -> Result<(), Error> { 723 | self.as_ref().set_gain(direction, channel, gain) 724 | } 725 | 726 | fn gain(&self, direction: Direction, channel: usize) -> Result, Error> { 727 | self.as_ref().gain(direction, channel) 728 | } 729 | 730 | fn gain_range(&self, direction: Direction, channel: usize) -> Result { 731 | self.as_ref().gain_range(direction, channel) 732 | } 733 | 734 | fn set_gain_element( 735 | &self, 736 | direction: Direction, 737 | channel: usize, 738 | name: &str, 739 | gain: f64, 740 | ) -> Result<(), Error> { 741 | self.as_ref() 742 | .set_gain_element(direction, channel, name, gain) 743 | } 744 | 745 | fn gain_element( 746 | &self, 747 | direction: Direction, 748 | channel: usize, 749 | name: &str, 750 | ) -> Result, Error> { 751 | self.as_ref().gain_element(direction, channel, name) 752 | } 753 | 754 | fn gain_element_range( 755 | &self, 756 | direction: Direction, 757 | channel: usize, 758 | name: &str, 759 | ) -> Result { 760 | self.as_ref().gain_element_range(direction, channel, name) 761 | } 762 | 763 | fn frequency_range(&self, direction: Direction, channel: usize) -> Result { 764 | self.as_ref().frequency_range(direction, channel) 765 | } 766 | 767 | fn frequency(&self, direction: Direction, channel: usize) -> Result { 768 | self.as_ref().frequency(direction, channel) 769 | } 770 | 771 | fn set_frequency( 772 | &self, 773 | direction: Direction, 774 | channel: usize, 775 | frequency: f64, 776 | args: Args, 777 | ) -> Result<(), Error> { 778 | self.as_ref() 779 | .set_frequency(direction, channel, frequency, args) 780 | } 781 | 782 | fn frequency_components( 783 | &self, 784 | direction: Direction, 785 | channel: usize, 786 | ) -> Result, Error> { 787 | self.as_ref().frequency_components(direction, channel) 788 | } 789 | 790 | fn component_frequency_range( 791 | &self, 792 | direction: Direction, 793 | channel: usize, 794 | name: &str, 795 | ) -> Result { 796 | self.as_ref() 797 | .component_frequency_range(direction, channel, name) 798 | } 799 | 800 | fn component_frequency( 801 | &self, 802 | direction: Direction, 803 | channel: usize, 804 | name: &str, 805 | ) -> Result { 806 | self.as_ref().component_frequency(direction, channel, name) 807 | } 808 | 809 | fn set_component_frequency( 810 | &self, 811 | direction: Direction, 812 | channel: usize, 813 | name: &str, 814 | frequency: f64, 815 | ) -> Result<(), Error> { 816 | self.as_ref() 817 | .set_component_frequency(direction, channel, name, frequency) 818 | } 819 | 820 | fn sample_rate(&self, direction: Direction, channel: usize) -> Result { 821 | self.as_ref().sample_rate(direction, channel) 822 | } 823 | 824 | fn set_sample_rate( 825 | &self, 826 | direction: Direction, 827 | channel: usize, 828 | rate: f64, 829 | ) -> Result<(), Error> { 830 | self.as_ref().set_sample_rate(direction, channel, rate) 831 | } 832 | 833 | fn get_sample_rate_range(&self, direction: Direction, channel: usize) -> Result { 834 | self.as_ref().get_sample_rate_range(direction, channel) 835 | } 836 | 837 | fn bandwidth(&self, direction: Direction, channel: usize) -> Result { 838 | self.as_ref().bandwidth(direction, channel) 839 | } 840 | 841 | fn set_bandwidth(&self, direction: Direction, channel: usize, bw: f64) -> Result<(), Error> { 842 | self.as_ref().set_bandwidth(direction, channel, bw) 843 | } 844 | 845 | fn get_bandwidth_range(&self, direction: Direction, channel: usize) -> Result { 846 | self.as_ref().get_bandwidth_range(direction, channel) 847 | } 848 | 849 | fn has_dc_offset_mode(&self, direction: Direction, channel: usize) -> Result { 850 | self.as_ref().has_dc_offset_mode(direction, channel) 851 | } 852 | 853 | fn set_dc_offset_mode( 854 | &self, 855 | direction: Direction, 856 | channel: usize, 857 | automatic: bool, 858 | ) -> Result<(), Error> { 859 | self.as_ref() 860 | .set_dc_offset_mode(direction, channel, automatic) 861 | } 862 | 863 | fn dc_offset_mode(&self, direction: Direction, channel: usize) -> Result { 864 | self.as_ref().dc_offset_mode(direction, channel) 865 | } 866 | } 867 | 868 | impl< 869 | R: RxStreamer + 'static, 870 | T: TxStreamer + 'static, 871 | D: DeviceTrait + Clone + 'static, 872 | > Device 873 | { 874 | /// SDR [driver](Driver) 875 | pub fn driver(&self) -> Driver { 876 | self.dev.driver() 877 | } 878 | /// Identifier for the device, e.g., its serial. 879 | pub fn id(&self) -> Result { 880 | self.dev.id() 881 | } 882 | /// Device info that can be displayed to the user. 883 | pub fn info(&self) -> Result { 884 | self.dev.info() 885 | } 886 | /// Number of supported Channels. 887 | pub fn num_channels(&self, direction: Direction) -> Result { 888 | self.dev.num_channels(direction) 889 | } 890 | /// Full Duplex support. 891 | pub fn full_duplex(&self, direction: Direction, channel: usize) -> Result { 892 | self.dev.full_duplex(direction, channel) 893 | } 894 | 895 | //================================ STREAMER ============================================ 896 | /// Create an RX streamer. 897 | pub fn rx_streamer(&self, channels: &[usize]) -> Result { 898 | self.dev.rx_streamer(channels, Args::new()) 899 | } 900 | /// Create an RX streamer, using `args`. 901 | pub fn rx_streamer_with_args(&self, channels: &[usize], args: Args) -> Result { 902 | self.dev.rx_streamer(channels, args) 903 | } 904 | /// Create a TX Streamer. 905 | pub fn tx_streamer(&self, channels: &[usize]) -> Result { 906 | self.dev.tx_streamer(channels, Args::new()) 907 | } 908 | /// Create a TX Streamer, using `args`. 909 | pub fn tx_streamer_with_args(&self, channels: &[usize], args: Args) -> Result { 910 | self.dev.tx_streamer(channels, args) 911 | } 912 | 913 | //================================ ANTENNA ============================================ 914 | /// List of available antenna ports. 915 | pub fn antennas(&self, direction: Direction, channel: usize) -> Result, Error> { 916 | self.dev.antennas(direction, channel) 917 | } 918 | /// Currently used antenna port. 919 | pub fn antenna(&self, direction: Direction, channel: usize) -> Result { 920 | self.dev.antenna(direction, channel) 921 | } 922 | /// Set antenna port. 923 | pub fn set_antenna( 924 | &self, 925 | direction: Direction, 926 | channel: usize, 927 | name: &str, 928 | ) -> Result<(), Error> { 929 | self.dev.set_antenna(direction, channel, name) 930 | } 931 | 932 | //================================ AGC ============================================ 933 | /// Does the device support automatic gain control? 934 | pub fn supports_agc(&self, direction: Direction, channel: usize) -> Result { 935 | self.dev.supports_agc(direction, channel) 936 | } 937 | /// Enable or disable automatic gain control. 938 | pub fn enable_agc(&self, direction: Direction, channel: usize, agc: bool) -> Result<(), Error> { 939 | self.dev.enable_agc(direction, channel, agc) 940 | } 941 | /// Returns true, if automatic gain control is enabled 942 | pub fn agc(&self, direction: Direction, channel: usize) -> Result { 943 | self.dev.agc(direction, channel) 944 | } 945 | 946 | //================================ GAIN ============================================ 947 | /// List of available gain elements. 948 | /// 949 | /// Elements should be in order RF to baseband. 950 | pub fn gain_elements( 951 | &self, 952 | direction: Direction, 953 | channel: usize, 954 | ) -> Result, Error> { 955 | self.dev.gain_elements(direction, channel) 956 | } 957 | 958 | /// Set the overall amplification in a chain. 959 | /// 960 | /// The gain will be distributed automatically across available elements. 961 | /// 962 | /// `gain`: the new amplification value in dB 963 | pub fn set_gain(&self, direction: Direction, channel: usize, gain: f64) -> Result<(), Error> { 964 | self.dev.set_gain(direction, channel, gain) 965 | } 966 | 967 | /// Get the overall value of the gain elements in a chain in dB. 968 | pub fn gain(&self, direction: Direction, channel: usize) -> Result, Error> { 969 | self.dev.gain(direction, channel) 970 | } 971 | 972 | /// Get the overall [`Range`] of possible gain values. 973 | pub fn gain_range(&self, direction: Direction, channel: usize) -> Result { 974 | self.dev.gain_range(direction, channel) 975 | } 976 | 977 | /// Set the value of a amplification element in a chain. 978 | /// 979 | /// ## Arguments 980 | /// * `name`: the name of an amplification element from `Device::list_gains` 981 | /// * `gain`: the new amplification value in dB 982 | pub fn set_gain_element( 983 | &self, 984 | direction: Direction, 985 | channel: usize, 986 | name: &str, 987 | gain: f64, 988 | ) -> Result<(), Error> { 989 | self.dev.set_gain_element(direction, channel, name, gain) 990 | } 991 | 992 | /// Get the value of an individual amplification element in a chain in dB. 993 | pub fn gain_element( 994 | &self, 995 | direction: Direction, 996 | channel: usize, 997 | name: &str, 998 | ) -> Result, Error> { 999 | self.dev.gain_element(direction, channel, name) 1000 | } 1001 | 1002 | /// Get the range of possible gain values for a specific element. 1003 | pub fn gain_element_range( 1004 | &self, 1005 | direction: Direction, 1006 | channel: usize, 1007 | name: &str, 1008 | ) -> Result { 1009 | self.dev.gain_element_range(direction, channel, name) 1010 | } 1011 | 1012 | //================================ FREQUENCY ============================================ 1013 | 1014 | /// Get the ranges of overall frequency values. 1015 | pub fn frequency_range(&self, direction: Direction, channel: usize) -> Result { 1016 | self.dev.frequency_range(direction, channel) 1017 | } 1018 | 1019 | /// Get the overall center frequency of the chain. 1020 | /// 1021 | /// - For RX, this specifies the down-conversion frequency. 1022 | /// - For TX, this specifies the up-conversion frequency. 1023 | /// 1024 | /// Returns the center frequency in Hz. 1025 | pub fn frequency(&self, direction: Direction, channel: usize) -> Result { 1026 | self.dev.frequency(direction, channel) 1027 | } 1028 | 1029 | /// Set the center frequency of the chain. 1030 | /// 1031 | /// - For RX, this specifies the down-conversion frequency. 1032 | /// - For TX, this specifies the up-conversion frequency. 1033 | /// 1034 | /// The default implementation of `set_frequency` will tune the "RF" 1035 | /// component as close as possible to the requested center frequency in Hz. 1036 | /// Tuning inaccuracies will be compensated for with the "BB" component. 1037 | /// 1038 | pub fn set_frequency( 1039 | &self, 1040 | direction: Direction, 1041 | channel: usize, 1042 | frequency: f64, 1043 | ) -> Result<(), Error> { 1044 | self.dev 1045 | .set_frequency(direction, channel, frequency, Args::new()) 1046 | } 1047 | 1048 | /// Like [`set_frequency`](Self::set_frequency) but using `args` to augment the tuning algorithm. 1049 | /// 1050 | /// - Use `"OFFSET"` to specify an "RF" tuning offset, 1051 | /// usually with the intention of moving the LO out of the passband. 1052 | /// The offset will be compensated for using the "BB" component. 1053 | /// - Use the name of a component for the key and a frequency in Hz 1054 | /// as the value (any format) to enforce a specific frequency. 1055 | /// The other components will be tuned with compensation 1056 | /// to achieve the specified overall frequency. 1057 | /// - Use the name of a component for the key and the value `"IGNORE"` 1058 | /// so that the tuning algorithm will avoid altering the component. 1059 | /// - Vendor specific implementations can also use the same args to augment 1060 | /// tuning in other ways such as specifying fractional vs integer N tuning. 1061 | /// 1062 | pub fn set_frequency_with_args( 1063 | &self, 1064 | direction: Direction, 1065 | channel: usize, 1066 | frequency: f64, 1067 | args: Args, 1068 | ) -> Result<(), Error> { 1069 | self.dev.set_frequency(direction, channel, frequency, args) 1070 | } 1071 | 1072 | /// List available tunable elements in the chain. 1073 | /// 1074 | /// Elements should be in order RF to baseband. 1075 | pub fn frequency_components( 1076 | &self, 1077 | direction: Direction, 1078 | channel: usize, 1079 | ) -> Result, Error> { 1080 | self.dev.frequency_components(direction, channel) 1081 | } 1082 | 1083 | /// Get the range of tunable values for the specified element. 1084 | pub fn component_frequency_range( 1085 | &self, 1086 | direction: Direction, 1087 | channel: usize, 1088 | name: &str, 1089 | ) -> Result { 1090 | self.dev.component_frequency_range(direction, channel, name) 1091 | } 1092 | 1093 | /// Get the frequency of a tunable element in the chain. 1094 | pub fn component_frequency( 1095 | &self, 1096 | direction: Direction, 1097 | channel: usize, 1098 | name: &str, 1099 | ) -> Result { 1100 | self.dev.component_frequency(direction, channel, name) 1101 | } 1102 | 1103 | /// Tune the center frequency of the specified element. 1104 | /// 1105 | /// - For RX, this specifies the down-conversion frequency. 1106 | /// - For TX, this specifies the up-conversion frequency. 1107 | pub fn set_component_frequency( 1108 | &self, 1109 | direction: Direction, 1110 | channel: usize, 1111 | name: &str, 1112 | frequency: f64, 1113 | ) -> Result<(), Error> { 1114 | self.dev 1115 | .set_component_frequency(direction, channel, name, frequency) 1116 | } 1117 | 1118 | //================================ SAMPLE RATE ============================================ 1119 | 1120 | /// Get the baseband sample rate of the chain in samples per second. 1121 | pub fn sample_rate(&self, direction: Direction, channel: usize) -> Result { 1122 | self.dev.sample_rate(direction, channel) 1123 | } 1124 | 1125 | /// Set the baseband sample rate of the chain in samples per second. 1126 | pub fn set_sample_rate( 1127 | &self, 1128 | direction: Direction, 1129 | channel: usize, 1130 | rate: f64, 1131 | ) -> Result<(), Error> { 1132 | self.dev.set_sample_rate(direction, channel, rate) 1133 | } 1134 | 1135 | /// Get the range of possible baseband sample rates. 1136 | pub fn get_sample_rate_range( 1137 | &self, 1138 | direction: Direction, 1139 | channel: usize, 1140 | ) -> Result { 1141 | self.dev.get_sample_rate_range(direction, channel) 1142 | } 1143 | 1144 | //================================ BANDWIDTH ============================================ 1145 | 1146 | /// Get the hardware bandwidth filter, if available. 1147 | /// 1148 | /// Returns `Err(Error::NotSupported)` if unsupported in underlying driver. 1149 | pub fn bandwidth(&self, direction: Direction, channel: usize) -> Result { 1150 | self.dev.bandwidth(direction, channel) 1151 | } 1152 | 1153 | /// Set the hardware bandwidth filter, if available. 1154 | /// 1155 | /// Returns `Err(Error::NotSupported)` if unsupported in underlying driver. 1156 | pub fn set_bandwidth( 1157 | &self, 1158 | direction: Direction, 1159 | channel: usize, 1160 | bw: f64, 1161 | ) -> Result<(), Error> { 1162 | self.dev.set_bandwidth(direction, channel, bw) 1163 | } 1164 | 1165 | /// Get the range of possible bandwidth filter values, if available. 1166 | /// 1167 | /// Returns `Err(Error::NotSupported)` if unsupported in underlying driver. 1168 | pub fn get_bandwidth_range( 1169 | &self, 1170 | direction: Direction, 1171 | channel: usize, 1172 | ) -> Result { 1173 | self.dev.get_bandwidth_range(direction, channel) 1174 | } 1175 | } 1176 | -------------------------------------------------------------------------------- /src/impls/aaronia.rs: -------------------------------------------------------------------------------- 1 | //! Aaronia Spectran SDK Library 2 | #![allow(unused_variables)] 3 | #![allow(unused_imports)] 4 | #![allow(dead_code)] 5 | use std::any::Any; 6 | use std::sync::Arc; 7 | use std::sync::Mutex; 8 | 9 | use aaronia_rtsa::ApiHandle; 10 | use aaronia_rtsa::ConfigItem; 11 | use aaronia_rtsa::Device as Sdr; 12 | use aaronia_rtsa::DeviceInfo; 13 | use aaronia_rtsa::Packet; 14 | 15 | use crate::Args; 16 | use crate::DeviceTrait; 17 | use crate::Direction; 18 | use crate::Direction::*; 19 | use crate::Driver; 20 | use crate::Error; 21 | use crate::Range; 22 | use crate::RangeItem; 23 | 24 | /// Aaronia SpectranV6 driver, using the native SDK 25 | #[derive(Debug)] 26 | pub struct Aaronia { 27 | dev: Arc>, 28 | index: usize, 29 | } 30 | 31 | unsafe impl Send for Aaronia {} 32 | unsafe impl Sync for Aaronia {} 33 | 34 | /// Aaronia SpectranV6 RX Streamer 35 | pub struct RxStreamer { 36 | dev: Arc>, 37 | packet: Option<(Packet, usize)>, 38 | } 39 | 40 | unsafe impl Send for RxStreamer {} 41 | 42 | impl RxStreamer { 43 | fn new(dev: Arc>) -> Self { 44 | Self { dev, packet: None } 45 | } 46 | } 47 | 48 | /// Aaronia SpectranV6 TX Streamer 49 | pub struct TxStreamer { 50 | dev: Arc>, 51 | } 52 | 53 | unsafe impl Send for TxStreamer {} 54 | 55 | impl TxStreamer { 56 | fn new(dev: Arc>) -> Self { 57 | Self { dev } 58 | } 59 | } 60 | 61 | impl Aaronia { 62 | /// Get a list of detected Aaronia SpectranV6 devices 63 | /// 64 | /// The returned [`Args`] specify the device, i.e., passing them to [`Aaronia::open`] will open 65 | /// this particular device. At the moment, this just uses the index in the list of devices 66 | /// returned by the driver. 67 | pub fn probe(_args: &Args) -> Result, Error> { 68 | let mut api = ApiHandle::new().or(Err(Error::DeviceError))?; 69 | api.rescan_devices().or(Err(Error::DeviceError))?; 70 | let devs = api.devices().or(Err(Error::DeviceError))?; 71 | Ok(devs 72 | .iter() 73 | .enumerate() 74 | .map(|(i, d)| format!("index={i}, driver=aaronia").parse().unwrap()) 75 | .collect()) 76 | } 77 | 78 | /// Create an Aaronia SpectranV6 Device 79 | /// 80 | /// At the moment, only an `index` argument is considered, which defines the index of the 81 | /// devices in the list returned by [`ApiHandle::devices`](aaronia_rtsa::ApiHandle::devices). 82 | /// If the devices were already [`scanned`](aaronia_rtsa::ApiHandle::rescan_devices) in a call 83 | /// to [`probe`](Self::probe), they are not rescanned to avoid changing the `index` identifier. 84 | pub fn open>(args: A) -> Result { 85 | let mut api = ApiHandle::new().or(Err(Error::DeviceError))?; 86 | api.rescan_devices().or(Err(Error::DeviceError))?; 87 | let devs = api.devices().or(Err(Error::DeviceError))?; 88 | 89 | if devs.is_empty() { 90 | return Err(Error::NotFound); 91 | } 92 | 93 | let args = args.try_into().or(Err(Error::ValueError))?; 94 | let index = args.get::("index").unwrap_or(0); 95 | 96 | let mut dev = api 97 | .get_this_device(&devs[index]) 98 | .or(Err(Error::DeviceError))?; 99 | dev.open().or(Err(Error::DeviceError))?; 100 | #[allow(clippy::arc_with_non_send_sync)] 101 | Ok(Aaronia { 102 | dev: Arc::new(Mutex::new(dev)), 103 | index, 104 | }) 105 | } 106 | } 107 | 108 | impl DeviceTrait for Aaronia { 109 | type RxStreamer = RxStreamer; 110 | type TxStreamer = TxStreamer; 111 | 112 | fn as_any(&self) -> &dyn Any { 113 | self 114 | } 115 | 116 | fn as_any_mut(&mut self) -> &mut dyn Any { 117 | self 118 | } 119 | 120 | fn driver(&self) -> crate::Driver { 121 | Driver::Aaronia 122 | } 123 | 124 | fn id(&self) -> Result { 125 | Ok(format!("{}", self.index)) 126 | } 127 | 128 | fn info(&self) -> Result { 129 | format!("driver=aaronia, index={}", self.index).try_into() 130 | } 131 | 132 | fn num_channels(&self, direction: Direction) -> Result { 133 | match direction { 134 | Rx => Ok(2), 135 | Tx => Ok(1), 136 | } 137 | } 138 | 139 | fn full_duplex(&self, direction: Direction, channel: usize) -> Result { 140 | match (direction, channel) { 141 | (Rx, 0 | 1) => Ok(true), 142 | (Tx, 0) => Ok(true), 143 | _ => Err(Error::ValueError), 144 | } 145 | } 146 | 147 | fn rx_streamer( 148 | &self, 149 | channels: &[usize], 150 | args: crate::Args, 151 | ) -> Result { 152 | if channels == [0] { 153 | Ok(RxStreamer::new(self.dev.clone())) 154 | } else { 155 | Err(Error::ValueError) 156 | } 157 | } 158 | 159 | fn tx_streamer( 160 | &self, 161 | channels: &[usize], 162 | args: crate::Args, 163 | ) -> Result { 164 | if channels == [0] { 165 | Ok(TxStreamer::new(self.dev.clone())) 166 | } else { 167 | Err(Error::ValueError) 168 | } 169 | } 170 | 171 | fn antennas(&self, direction: Direction, channel: usize) -> Result, Error> { 172 | match (direction, channel) { 173 | (Rx, 0) => Ok(vec!["RX1".to_string()]), 174 | (Rx, 1) => Ok(vec!["RX2".to_string()]), 175 | (Tx, 0) => Ok(vec!["TX1".to_string()]), 176 | _ => Err(Error::ValueError), 177 | } 178 | } 179 | 180 | fn antenna(&self, direction: Direction, channel: usize) -> Result { 181 | match (direction, channel) { 182 | (Rx, 0) => Ok("RX1".to_string()), 183 | (Rx, 1) => Ok("RX2".to_string()), 184 | (Tx, 0) => Ok("TX1".to_string()), 185 | _ => Err(Error::ValueError), 186 | } 187 | } 188 | 189 | fn set_antenna(&self, direction: Direction, channel: usize, name: &str) -> Result<(), Error> { 190 | match (direction, channel, name) { 191 | (Rx, 0, "RX1") => Ok(()), 192 | (Rx, 1, "RX2") => Ok(()), 193 | (Tx, 0, "TX1") => Ok(()), 194 | _ => Err(Error::ValueError), 195 | } 196 | } 197 | 198 | fn gain_elements(&self, direction: Direction, channel: usize) -> Result, Error> { 199 | match (direction, channel) { 200 | (Rx, 0 | 1) => Ok(vec!["TUNER".to_string()]), 201 | (Tx, 0) => Ok(vec!["TUNER".to_string()]), 202 | _ => Err(Error::ValueError), 203 | } 204 | } 205 | 206 | fn supports_agc(&self, direction: Direction, channel: usize) -> Result { 207 | match (direction, channel) { 208 | (Rx, 0 | 1) => Ok(true), 209 | _ => Err(Error::ValueError), 210 | } 211 | } 212 | 213 | fn enable_agc(&self, direction: Direction, channel: usize, agc: bool) -> Result<(), Error> { 214 | let mut dev = self.dev.lock().unwrap(); 215 | match (direction, channel) { 216 | (Rx, 0 | 1) => { 217 | if agc { 218 | dev.set("device/gaincontrol", "power") 219 | .or(Err(Error::DeviceError)) 220 | } else { 221 | dev.set("device/gaincontrol", "manual") 222 | .or(Err(Error::DeviceError)) 223 | } 224 | } 225 | _ => Err(Error::ValueError), 226 | } 227 | } 228 | 229 | fn agc(&self, direction: Direction, channel: usize) -> Result { 230 | let mut dev = self.dev.lock().unwrap(); 231 | match (direction, channel) { 232 | (Rx, 0 | 1) => match dev.get("device/gaincontrol").or(Err(Error::DeviceError))? { 233 | ConfigItem::Enum(0, _) => Ok(false), 234 | _ => Ok(true), 235 | }, 236 | _ => Err(Error::ValueError), 237 | } 238 | } 239 | 240 | fn set_gain(&self, direction: Direction, channel: usize, gain: f64) -> Result<(), Error> { 241 | self.set_gain_element(direction, channel, "TUNER", gain) 242 | } 243 | 244 | fn gain(&self, direction: Direction, channel: usize) -> Result, Error> { 245 | self.gain_element(direction, channel, "TUNER") 246 | } 247 | 248 | fn gain_range(&self, direction: Direction, channel: usize) -> Result { 249 | self.gain_element_range(direction, channel, "TUNER") 250 | } 251 | 252 | fn set_gain_element( 253 | &self, 254 | direction: Direction, 255 | channel: usize, 256 | name: &str, 257 | gain: f64, 258 | ) -> Result<(), Error> { 259 | let mut dev = self.dev.lock().unwrap(); 260 | match (direction, channel, name) { 261 | (Rx, 0 | 1, "TUNER") | (Tx, 0, "TUNER") => { 262 | if (0.0..=30.0).contains(&gain) { 263 | dev.set("main/reflevel", format!("{}", -8.0 - gain)) 264 | .or(Err(Error::DeviceError)) 265 | } else { 266 | Err(Error::ValueError) 267 | } 268 | } 269 | _ => Err(Error::DeviceError), 270 | } 271 | } 272 | 273 | fn gain_element( 274 | &self, 275 | direction: Direction, 276 | channel: usize, 277 | name: &str, 278 | ) -> Result, Error> { 279 | match (direction, channel) { 280 | (Rx, 0) => Ok(None), 281 | (Rx, 1) => Ok(None), 282 | (Tx, 0) => todo!(), 283 | _ => Err(Error::ValueError), 284 | } 285 | } 286 | 287 | fn gain_element_range( 288 | &self, 289 | direction: Direction, 290 | channel: usize, 291 | name: &str, 292 | ) -> Result { 293 | match (direction, channel, name) { 294 | (Rx, 0 | 1, "TUNER") => Ok(Range::new(vec![RangeItem::Interval(0.0, 30.0)])), 295 | (Tx, 0, "TUNER") => Ok(Range::new(vec![RangeItem::Interval(-100.0, 10.0)])), 296 | _ => Err(Error::ValueError), 297 | } 298 | } 299 | 300 | fn frequency_range(&self, direction: Direction, channel: usize) -> Result { 301 | self.component_frequency_range(direction, channel, "TUNER") 302 | } 303 | 304 | fn frequency(&self, direction: Direction, channel: usize) -> Result { 305 | self.component_frequency(direction, channel, "TUNER") 306 | } 307 | 308 | fn set_frequency( 309 | &self, 310 | direction: Direction, 311 | channel: usize, 312 | frequency: f64, 313 | _args: Args, 314 | ) -> Result<(), Error> { 315 | self.set_component_frequency(direction, channel, "TUNER", frequency) 316 | } 317 | 318 | fn frequency_components( 319 | &self, 320 | direction: Direction, 321 | channel: usize, 322 | ) -> Result, Error> { 323 | match (direction, channel) { 324 | (Rx, 0 | 1) | (Tx, 0) => Ok(vec!["TUNER".to_string()]), 325 | _ => Err(Error::ValueError), 326 | } 327 | } 328 | 329 | fn component_frequency_range( 330 | &self, 331 | direction: Direction, 332 | channel: usize, 333 | name: &str, 334 | ) -> Result { 335 | match (direction, channel, name) { 336 | (Rx, 0 | 1, "TUNER") | (Tx, 0, "TUNER") => { 337 | Ok(Range::new(vec![RangeItem::Interval(193e6, 6e9)])) 338 | } 339 | _ => Err(Error::ValueError), 340 | } 341 | } 342 | 343 | fn component_frequency( 344 | &self, 345 | direction: Direction, 346 | channel: usize, 347 | name: &str, 348 | ) -> Result { 349 | match (direction, channel, name) { 350 | (Rx, 0 | 1, "TUNER") => { 351 | let mut dev = self.dev.lock().unwrap(); 352 | let s = dev.get("main/centerfreq").or(Err(Error::DeviceError))?; 353 | match s { 354 | ConfigItem::Number(f) => Ok(f), 355 | _ => Err(Error::ValueError), 356 | } 357 | } 358 | _ => Err(Error::ValueError), 359 | } 360 | } 361 | 362 | fn set_component_frequency( 363 | &self, 364 | _direction: Direction, 365 | channel: usize, 366 | name: &str, 367 | frequency: f64, 368 | ) -> Result<(), Error> { 369 | let mut dev = self.dev.lock().unwrap(); 370 | match (channel, name) { 371 | (0 | 1, "TUNER") => dev 372 | .set("main/centerfreq", format!("{frequency}")) 373 | .or(Err(Error::DeviceError)), 374 | _ => Err(Error::ValueError), 375 | } 376 | } 377 | 378 | fn sample_rate(&self, direction: Direction, channel: usize) -> Result { 379 | match (direction, channel) { 380 | (Rx, 0 | 1) => { 381 | let mut dev = self.dev.lock().unwrap(); 382 | let s = dev 383 | .get("device/receiverclock") 384 | .or(Err(Error::DeviceError))?; 385 | let rate = match s { 386 | ConfigItem::Enum(0, _) => 92e6, 387 | ConfigItem::Enum(1, _) => 122e6, 388 | ConfigItem::Enum(2, _) => 184e6, 389 | ConfigItem::Enum(3, _) => 245e6, 390 | _ => return Err(Error::ValueError), 391 | }; 392 | let s = dev.get("main/decimation").or(Err(Error::DeviceError))?; 393 | let s = match s { 394 | ConfigItem::Enum(0, _) => 1.0, 395 | ConfigItem::Enum(1, _) => 2.0, 396 | ConfigItem::Enum(2, _) => 4.0, 397 | ConfigItem::Enum(3, _) => 8.0, 398 | ConfigItem::Enum(4, _) => 16.0, 399 | ConfigItem::Enum(5, _) => 32.0, 400 | ConfigItem::Enum(6, _) => 64.0, 401 | ConfigItem::Enum(7, _) => 128.0, 402 | ConfigItem::Enum(8, _) => 256.0, 403 | ConfigItem::Enum(9, _) => 512.0, 404 | _ => return Err(Error::ValueError), 405 | }; 406 | 407 | Ok(rate / s) 408 | } 409 | _ => Err(Error::ValueError), 410 | } 411 | } 412 | 413 | fn set_sample_rate( 414 | &self, 415 | direction: Direction, 416 | channel: usize, 417 | rate: f64, 418 | ) -> Result<(), Error> { 419 | let mut dev = self.dev.lock().unwrap(); 420 | match (direction, channel) { 421 | (Rx, 0 | 1) => { 422 | let dec = vec![1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0, 256.0, 512.0]; 423 | for (i, d) in dec.into_iter().enumerate() { 424 | if (rate - 92e6 / d).abs() < 0.00001 { 425 | dev.set("device/receiverclock", "92MHz") 426 | .or(Err(Error::DeviceError))?; 427 | return dev 428 | .set_int("main/decimation", i as i64) 429 | .or(Err(Error::DeviceError)); 430 | } 431 | } 432 | Err(Error::ValueError) 433 | } 434 | (Tx, 0) => todo!(), 435 | _ => Err(Error::ValueError), 436 | } 437 | } 438 | 439 | fn get_sample_rate_range(&self, direction: Direction, channel: usize) -> Result { 440 | match (direction, channel) { 441 | (Rx, 0 | 1) => Ok(Range::new( 442 | vec![1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0, 256.0, 512.0] 443 | .into_iter() 444 | .map(|v| RangeItem::Value(92e6 / v)) 445 | .collect(), 446 | )), 447 | (Tx, 0) => todo!(), 448 | _ => Err(Error::ValueError), 449 | } 450 | } 451 | } 452 | 453 | impl crate::RxStreamer for RxStreamer { 454 | fn mtu(&self) -> Result { 455 | Ok(1024) 456 | } 457 | 458 | fn activate_at(&mut self, time_ns: Option) -> Result<(), Error> { 459 | let mut dev = self.dev.lock().unwrap(); 460 | dev.connect().or(Err(Error::DeviceError))?; 461 | dev.start().or(Err(Error::DeviceError)) 462 | } 463 | 464 | fn deactivate_at(&mut self, time_ns: Option) -> Result<(), Error> { 465 | let mut dev = self.dev.lock().unwrap(); 466 | dev.stop().or(Err(Error::DeviceError))?; 467 | dev.disconnect().or(Err(Error::DeviceError)) 468 | } 469 | 470 | fn read( 471 | &mut self, 472 | buffers: &mut [&mut [num_complex::Complex32]], 473 | _timeout_us: i64, 474 | ) -> Result { 475 | let mut dev = self.dev.lock().unwrap(); 476 | debug_assert_eq!(buffers.len(), 1); 477 | 478 | let mut i = 0; 479 | let len = buffers[0].len(); 480 | while i < len { 481 | match self.packet.take() { 482 | None => { 483 | let p = dev.packet(0).or(Err(Error::DeviceError))?; 484 | let cur = p.samples(); 485 | let n = std::cmp::min(len - i, cur.len()); 486 | buffers[0][i..i + n].copy_from_slice(&cur[0..n]); 487 | i += n; 488 | if n == cur.len() { 489 | dev.consume(0).or(Err(Error::DeviceError))?; 490 | } else { 491 | self.packet = Some((p, n)); 492 | } 493 | } 494 | Some((p, offset)) => { 495 | let cur = p.samples(); 496 | let n = std::cmp::min(len - i, cur.len() - offset); 497 | buffers[0][i..i + n].copy_from_slice(&cur[offset..offset + n]); 498 | i += n; 499 | if offset + n == cur.len() { 500 | dev.consume(0).or(Err(Error::DeviceError))?; 501 | } else { 502 | self.packet = Some((p, offset + n)); 503 | } 504 | } 505 | } 506 | } 507 | 508 | Ok(len) 509 | } 510 | } 511 | 512 | impl crate::TxStreamer for TxStreamer { 513 | fn mtu(&self) -> Result { 514 | Ok(1024) 515 | } 516 | 517 | fn activate_at(&mut self, time_ns: Option) -> Result<(), Error> { 518 | todo!() 519 | } 520 | 521 | fn deactivate_at(&mut self, time_ns: Option) -> Result<(), Error> { 522 | todo!() 523 | } 524 | 525 | fn write( 526 | &mut self, 527 | buffers: &[&[num_complex::Complex32]], 528 | at_ns: Option, 529 | end_burst: bool, 530 | timeout_us: i64, 531 | ) -> Result { 532 | todo!() 533 | } 534 | 535 | fn write_all( 536 | &mut self, 537 | buffers: &[&[num_complex::Complex32]], 538 | at_ns: Option, 539 | end_burst: bool, 540 | timeout_us: i64, 541 | ) -> Result<(), Error> { 542 | todo!() 543 | } 544 | } 545 | -------------------------------------------------------------------------------- /src/impls/aaronia_http.rs: -------------------------------------------------------------------------------- 1 | //! Aaronia Spectran HTTP Client 2 | use num_complex::Complex32; 3 | use std::io::BufRead; 4 | use std::io::BufReader; 5 | use std::io::Read; 6 | use std::sync::atomic::AtomicU64; 7 | use std::sync::atomic::Ordering; 8 | use std::sync::Arc; 9 | use std::time::SystemTime; 10 | use ureq::serde_json::json; 11 | use ureq::serde_json::Value; 12 | use ureq::Agent; 13 | 14 | use crate::Args; 15 | use crate::DeviceTrait; 16 | use crate::Direction; 17 | use crate::Direction::*; 18 | use crate::Driver; 19 | use crate::Error; 20 | use crate::Range; 21 | use crate::RangeItem; 22 | 23 | /// Aaronia SpectranV6 driver, using the HTTP interface 24 | #[derive(Clone)] 25 | pub struct AaroniaHttp { 26 | url: String, 27 | tx_url: String, 28 | agent: Agent, 29 | f_offset: f64, 30 | tx_frequency: Arc, 31 | tx_sample_rate: Arc, 32 | } 33 | 34 | /// Aaronia SpectranV6 HTTP RX Streamer 35 | pub struct RxStreamer { 36 | agent: Agent, 37 | url: String, 38 | items_left: usize, 39 | reader: Option>>, 40 | } 41 | 42 | /// expected maximum delay for the transfer of samples between host and rf hardware, used to set the transmit start time to an achievalble but close value; in seconds 43 | const STREAMING_DELAY: f64 = 0.01; // 0.2 is too much, 0.001 too little 44 | 45 | /// Aaronia SpectranV6 HTTP TX Streamer 46 | pub struct TxStreamer { 47 | agent: Agent, 48 | url: String, 49 | frequency: Arc, 50 | sample_rate: Arc, 51 | last_transmission_end_time: f64, 52 | } 53 | 54 | impl AaroniaHttp { 55 | /// Try to connect to an Aaronia HTTP server interface 56 | /// 57 | /// Looks for a `url` argument or tries `http://localhost:54664` as the default. 58 | pub fn probe(args: &Args) -> Result, Error> { 59 | let url = args 60 | .get::("url") 61 | .unwrap_or_else(|_| String::from("http://localhost:54664")); 62 | let test_path = format!("{url}/info"); 63 | 64 | let agent = Agent::new(); 65 | let resp = match agent.get(&test_path).call() { 66 | Ok(r) => r, 67 | Err(e) => { 68 | if e.kind() == ureq::ErrorKind::ConnectionFailed 69 | && args.get::("driver").is_ok() 70 | { 71 | return Err(e.into()); 72 | } else { 73 | return Ok(Vec::new()); 74 | } 75 | } 76 | }; 77 | if resp.status() == 200 { 78 | let mut args = args.clone(); 79 | args.merge(format!("driver=aaronia_http, url={url}").try_into()?); 80 | Ok(vec![args]) 81 | } else { 82 | Ok(Vec::new()) 83 | } 84 | } 85 | 86 | /// Create an Aaronia SpectranV6 HTTP Device 87 | /// 88 | /// Looks for a `url` argument or tries `http://localhost:54664` as the default. 89 | pub fn open>(args: A) -> Result { 90 | let mut v = Self::probe(&args.try_into().or(Err(Error::ValueError))?)?; 91 | if v.is_empty() { 92 | Err(Error::NotFound) 93 | } else { 94 | let a = v.remove(0); 95 | 96 | let f_offset = a.get::("f_offset").unwrap_or(20e6); 97 | let url = a.get::("url")?; 98 | let tx_url = a.get::("tx_url").unwrap_or_else(|_| url.clone()); 99 | 100 | Ok(Self { 101 | agent: Agent::new(), 102 | url, 103 | tx_url, 104 | f_offset, 105 | tx_frequency: Arc::new(AtomicU64::new(2_450_000_000)), 106 | tx_sample_rate: Arc::new(AtomicU64::new(1_000_000)), 107 | }) 108 | } 109 | } 110 | } 111 | 112 | impl AaroniaHttp { 113 | fn config(&self) -> Result { 114 | let url = format!("{}/remoteconfig", self.url); 115 | let s = self.agent.get(&url).call()?.into_string()?; 116 | Ok(ureq::serde_json::from_str(&s)?) 117 | } 118 | 119 | fn get_element(&self, path: Vec<&str>) -> Result { 120 | let config = self.config()?; 121 | let mut element = &config["config"]; 122 | for p in path { 123 | for i in element["items"].as_array().unwrap() { 124 | if i["name"].as_str().unwrap() == p { 125 | element = i; 126 | } 127 | } 128 | } 129 | Ok(element.clone()) 130 | } 131 | 132 | fn get_enum(&self, path: Vec<&str>) -> Result<(u64, String), Error> { 133 | let element = self.get_element(path)?; 134 | let i = element["value"].as_u64().unwrap(); 135 | let v: Vec<&str> = element["values"].as_str().unwrap().split(',').collect(); 136 | Ok((i, v[i as usize].to_string())) 137 | } 138 | 139 | fn get_f64(&self, path: Vec<&str>) -> Result { 140 | let element = self.get_element(path)?; 141 | Ok(element["value"].as_f64().unwrap()) 142 | } 143 | fn send_json(&self, json: Value) -> Result<(), Error> { 144 | self.agent 145 | .put(&format!("{}/remoteconfig", self.url)) 146 | .send_json(json)?; 147 | 148 | Ok(()) 149 | } 150 | } 151 | 152 | impl DeviceTrait for AaroniaHttp { 153 | type RxStreamer = RxStreamer; 154 | type TxStreamer = TxStreamer; 155 | 156 | fn as_any(&self) -> &dyn std::any::Any { 157 | self 158 | } 159 | 160 | fn as_any_mut(&mut self) -> &mut dyn std::any::Any { 161 | self 162 | } 163 | 164 | fn driver(&self) -> Driver { 165 | Driver::AaroniaHttp 166 | } 167 | 168 | fn id(&self) -> Result { 169 | Ok(format!("driver=aarnia_http, url={}", self.url)) 170 | } 171 | 172 | fn info(&self) -> Result { 173 | format!("driver=aarnia_http, url={}", self.url).try_into() 174 | } 175 | 176 | fn num_channels(&self, direction: Direction) -> Result { 177 | match direction { 178 | Rx => Ok(2), 179 | Tx => Ok(1), 180 | } 181 | } 182 | 183 | fn full_duplex(&self, direction: Direction, channel: usize) -> Result { 184 | match (direction, channel) { 185 | (Rx, 0 | 1) => Ok(true), 186 | (Tx, 0) => Ok(true), 187 | _ => Err(Error::ValueError), 188 | } 189 | } 190 | 191 | fn rx_streamer(&self, channels: &[usize], _args: Args) -> Result { 192 | if channels == [0] { 193 | Ok(RxStreamer { 194 | url: self.url.clone(), 195 | agent: self.agent.clone(), 196 | items_left: 0, 197 | reader: None, 198 | }) 199 | } else { 200 | Err(Error::ValueError) 201 | } 202 | } 203 | 204 | fn tx_streamer(&self, channels: &[usize], _args: Args) -> Result { 205 | if channels == [0] { 206 | Ok(TxStreamer { 207 | url: self.tx_url.clone(), 208 | agent: self.agent.clone(), 209 | frequency: self.tx_frequency.clone(), 210 | sample_rate: self.tx_sample_rate.clone(), 211 | last_transmission_end_time: SystemTime::now() 212 | .duration_since(SystemTime::UNIX_EPOCH) 213 | .unwrap() 214 | .as_secs_f64(), 215 | }) 216 | } else { 217 | Err(Error::ValueError) 218 | } 219 | } 220 | 221 | fn antennas(&self, direction: Direction, channel: usize) -> Result, Error> { 222 | match (direction, channel) { 223 | (Rx, 0) => Ok(vec!["RX1".to_string()]), 224 | (Rx, 1) => Ok(vec!["RX2".to_string()]), 225 | (Tx, 0) => Ok(vec!["TX1".to_string()]), 226 | _ => Err(Error::ValueError), 227 | } 228 | } 229 | 230 | fn antenna(&self, direction: Direction, channel: usize) -> Result { 231 | match (direction, channel) { 232 | (Rx, 0) => Ok("RX1".to_string()), 233 | (Rx, 1) => Ok("RX2".to_string()), 234 | (Tx, 0) => Ok("TX1".to_string()), 235 | _ => Err(Error::ValueError), 236 | } 237 | } 238 | 239 | fn set_antenna(&self, direction: Direction, channel: usize, name: &str) -> Result<(), Error> { 240 | match (direction, channel, name) { 241 | (Rx, 0, "RX1") => Ok(()), 242 | (Rx, 1, "RX2") => Ok(()), 243 | (Tx, 0, "TX1") => Ok(()), 244 | _ => Err(Error::ValueError), 245 | } 246 | } 247 | 248 | fn gain_elements(&self, direction: Direction, channel: usize) -> Result, Error> { 249 | match (direction, channel) { 250 | (Rx, 0 | 1) => Ok(vec!["TUNER".to_string()]), 251 | (Tx, 0) => Ok(vec!["TUNER".to_string()]), 252 | _ => Err(Error::ValueError), 253 | } 254 | } 255 | 256 | fn supports_agc(&self, direction: Direction, channel: usize) -> Result { 257 | match (direction, channel) { 258 | (Rx, 0 | 1) => Ok(true), 259 | _ => Err(Error::ValueError), 260 | } 261 | } 262 | 263 | fn enable_agc(&self, direction: Direction, channel: usize, agc: bool) -> Result<(), Error> { 264 | match (direction, channel) { 265 | (Rx, 0 | 1) => { 266 | let json = json!({ 267 | "receiverName": "Block_Spectran_V6B_0", 268 | "simpleconfig": { 269 | "device": { 270 | "gaincontrol": if agc { "peak" } else { "manual" } 271 | } 272 | } 273 | }); 274 | self.send_json(json) 275 | } 276 | _ => Err(Error::ValueError), 277 | } 278 | } 279 | 280 | fn agc(&self, _direction: Direction, _channel: usize) -> Result { 281 | let (_, s) = self.get_enum(vec![ 282 | "Block_Spectran_V6B_0", 283 | "config", 284 | "device", 285 | "gaincontrol", 286 | ])?; 287 | if s == "manual" { 288 | Ok(false) 289 | } else { 290 | Ok(true) 291 | } 292 | } 293 | 294 | fn set_gain(&self, direction: Direction, channel: usize, gain: f64) -> Result<(), Error> { 295 | match (direction, channel) { 296 | (Rx, 0 | 1) => { 297 | let lvl = -gain - 8.0; 298 | let json = json!({ 299 | "receiverName": "Block_Spectran_V6B_0", 300 | "simpleconfig": { 301 | "main": { 302 | "reflevel": lvl 303 | } 304 | } 305 | }); 306 | self.send_json(json) 307 | } 308 | (Tx, 0) => { 309 | let range = Range::new(vec![RangeItem::Interval(-100.0, 10.0)]); 310 | if !range.contains(gain) { 311 | log::warn!("aaronia_http: gain out of range"); 312 | return Err(Error::OutOfRange(range, gain)); 313 | } 314 | let json = json!({ 315 | "receiverName": "Block_Spectran_V6B_0", 316 | "simpleconfig": { 317 | "main": { 318 | "transattn": gain 319 | } 320 | } 321 | }); 322 | self.send_json(json) 323 | } 324 | _ => Err(Error::ValueError), 325 | } 326 | } 327 | 328 | fn gain(&self, direction: Direction, channel: usize) -> Result, Error> { 329 | match (direction, channel) { 330 | (Rx, 0 | 1) => { 331 | let lvl = 332 | self.get_f64(vec!["Block_Spectran_V6B_0", "config", "main", "reflevel"])?; 333 | Ok(Some(-lvl - 8.0)) 334 | } 335 | _ => { 336 | todo!() 337 | } 338 | } 339 | } 340 | 341 | fn gain_range(&self, direction: Direction, channel: usize) -> Result { 342 | match (direction, channel) { 343 | (Rx, 0 | 1) => Ok(Range::new(vec![RangeItem::Interval(0.0, 30.0)])), 344 | _ => todo!(), 345 | } 346 | } 347 | 348 | fn set_gain_element( 349 | &self, 350 | _direction: Direction, 351 | _channel: usize, 352 | _name: &str, 353 | _gain: f64, 354 | ) -> Result<(), Error> { 355 | todo!() 356 | } 357 | 358 | fn gain_element( 359 | &self, 360 | _direction: Direction, 361 | _channel: usize, 362 | _name: &str, 363 | ) -> Result, Error> { 364 | todo!() 365 | } 366 | 367 | fn gain_element_range( 368 | &self, 369 | _direction: Direction, 370 | _channel: usize, 371 | _name: &str, 372 | ) -> Result { 373 | todo!() 374 | } 375 | 376 | fn frequency_range(&self, _direction: Direction, _channel: usize) -> Result { 377 | todo!() 378 | } 379 | 380 | fn frequency(&self, direction: Direction, channel: usize) -> Result { 381 | match (direction, channel) { 382 | (Rx, 0 | 1) => self.get_f64(vec![ 383 | "Block_IQDemodulator_0", 384 | "config", 385 | "main", 386 | "centerfreq", 387 | ]), 388 | (Tx, 0) => Ok(self.tx_frequency.load(Ordering::SeqCst) as f64), 389 | _ => Err(Error::ValueError), 390 | } 391 | } 392 | 393 | fn set_frequency( 394 | &self, 395 | direction: Direction, 396 | channel: usize, 397 | frequency: f64, 398 | _args: Args, 399 | ) -> Result<(), Error> { 400 | match (direction, channel) { 401 | (Rx, 0 | 1) => { 402 | let f = (frequency - self.f_offset).max(0.0); 403 | self.set_component_frequency(direction, channel, "RF", f)?; 404 | self.set_component_frequency(direction, channel, "DEMOD", self.f_offset) 405 | } 406 | (Tx, 0) => self.set_component_frequency(direction, channel, "RF", frequency), 407 | _ => Err(Error::ValueError), 408 | } 409 | } 410 | 411 | fn frequency_components( 412 | &self, 413 | direction: Direction, 414 | channel: usize, 415 | ) -> Result, Error> { 416 | match (direction, channel) { 417 | (Rx, 0 | 1) => Ok(vec!["RF".to_string(), "DEMOD".to_string()]), 418 | _ => todo!(), 419 | } 420 | } 421 | 422 | fn component_frequency_range( 423 | &self, 424 | _direction: Direction, 425 | _channel: usize, 426 | _name: &str, 427 | ) -> Result { 428 | todo!() 429 | } 430 | 431 | fn component_frequency( 432 | &self, 433 | direction: Direction, 434 | channel: usize, 435 | name: &str, 436 | ) -> Result { 437 | match (direction, channel, name) { 438 | (Rx, 0 | 1, "DEMOD") => { 439 | let rf = 440 | self.get_f64(vec!["Block_Spectran_V6B_0", "config", "main", "centerfreq"])?; 441 | let demod = self.get_f64(vec![ 442 | "Block_IQDemodulator_0", 443 | "config", 444 | "main", 445 | "centerfreq", 446 | ])?; 447 | Ok(demod - rf) 448 | } 449 | (Rx, 0 | 1, "RF") => { 450 | self.get_f64(vec!["Block_Spectran_V6B_0", "config", "main", "centerfreq"]) 451 | } 452 | _ => todo!(), 453 | } 454 | } 455 | 456 | fn set_component_frequency( 457 | &self, 458 | direction: Direction, 459 | channel: usize, 460 | name: &str, 461 | frequency: f64, 462 | ) -> Result<(), Error> { 463 | match (direction, channel, name) { 464 | (Rx, 0 | 1, "RF") => { 465 | let json = json!({ 466 | "receiverName": "Block_Spectran_V6B_0", 467 | "simpleconfig": { 468 | "main": { 469 | "centerfreq": frequency 470 | } 471 | } 472 | }); 473 | self.send_json(json) 474 | } 475 | (Rx, 0 | 1, "DEMOD") => { 476 | let rf = 477 | self.get_f64(vec!["Block_Spectran_V6B_0", "config", "main", "centerfreq"])?; 478 | let json = json!({ 479 | "receiverName": "Block_IQDemodulator_0", 480 | "simpleconfig": { 481 | "main": { 482 | "centerfreq": frequency + rf 483 | } 484 | } 485 | }); 486 | self.send_json(json) 487 | } 488 | (Tx, 0, "RF") => { 489 | self.tx_frequency.store(frequency as u64, Ordering::SeqCst); 490 | Ok(()) 491 | } 492 | _ => Err(Error::ValueError), 493 | } 494 | } 495 | 496 | fn sample_rate(&self, direction: Direction, channel: usize) -> Result { 497 | match (direction, channel) { 498 | (Rx, 0 | 1) => self.get_f64(vec![ 499 | "Block_IQDemodulator_0", 500 | "config", 501 | "main", 502 | "samplerate", 503 | ]), 504 | (Tx, 0) => Ok(self.tx_sample_rate.load(Ordering::SeqCst) as f64), 505 | _ => Err(Error::ValueError), 506 | } 507 | } 508 | 509 | fn set_sample_rate( 510 | &self, 511 | direction: Direction, 512 | channel: usize, 513 | rate: f64, 514 | ) -> Result<(), Error> { 515 | match (direction, channel) { 516 | (Rx, 0 | 1) => { 517 | let json = json!({ 518 | "receiverName": "Block_IQDemodulator_0", 519 | "simpleconfig": { 520 | "main": { 521 | "samplerate": rate, 522 | "spanfreq": rate 523 | } 524 | } 525 | }); 526 | self.send_json(json) 527 | } 528 | (Tx, 0) => { 529 | self.tx_sample_rate.store(rate as u64, Ordering::SeqCst); 530 | Ok(()) 531 | } 532 | _ => Err(Error::ValueError), 533 | } 534 | } 535 | 536 | fn get_sample_rate_range(&self, direction: Direction, channel: usize) -> Result { 537 | match (direction, channel) { 538 | (Rx, 0 | 1) => Ok(Range::new(vec![RangeItem::Interval(0.0, 92.16e6)])), 539 | (Tx, 0) => todo!(), 540 | _ => Err(Error::ValueError), 541 | } 542 | } 543 | 544 | fn bandwidth(&self, _direction: Direction, _channel: usize) -> Result { 545 | Err(Error::NotSupported) 546 | } 547 | 548 | fn set_bandwidth(&self, _direction: Direction, _channel: usize, _bw: f64) -> Result<(), Error> { 549 | Err(Error::NotSupported) 550 | } 551 | 552 | fn get_bandwidth_range(&self, _direction: Direction, _channel: usize) -> Result { 553 | Err(Error::NotSupported) 554 | } 555 | 556 | fn has_dc_offset_mode(&self, _direction: Direction, _channel: usize) -> Result { 557 | Err(Error::NotSupported) 558 | } 559 | 560 | fn set_dc_offset_mode( 561 | &self, 562 | _direction: Direction, 563 | _channel: usize, 564 | _automatic: bool, 565 | ) -> Result<(), Error> { 566 | Err(Error::NotSupported) 567 | } 568 | 569 | fn dc_offset_mode(&self, _direction: Direction, _channel: usize) -> Result { 570 | Err(Error::NotSupported) 571 | } 572 | } 573 | 574 | impl RxStreamer { 575 | fn parse_header(&mut self) -> Result<(), Error> { 576 | let mut buf = Vec::with_capacity(512); 577 | self.reader.as_mut().unwrap().read_until(10, &mut buf)?; 578 | let header: Value = serde_json::from_str(&String::from_utf8_lossy(&buf))?; 579 | self.reader.as_mut().unwrap().consume(1); 580 | 581 | let i = header 582 | .get("samples") 583 | .and_then(|x| x.to_string().parse::().ok()) 584 | .ok_or(Error::Misc( 585 | "Parsing Samples from JSON Header failed".to_string(), 586 | ))?; 587 | 588 | self.items_left = i; 589 | Ok(()) 590 | } 591 | } 592 | 593 | impl crate::RxStreamer for RxStreamer { 594 | fn mtu(&self) -> Result { 595 | Ok(65536) 596 | } 597 | 598 | fn activate_at(&mut self, _time_ns: Option) -> Result<(), Error> { 599 | let r = self 600 | .agent 601 | .get(&format!("{}/stream?format=float32", self.url)) 602 | .call()? 603 | .into_reader(); 604 | self.reader = Some(BufReader::new(r)); 605 | Ok(()) 606 | } 607 | 608 | fn deactivate_at(&mut self, _time_ns: Option) -> Result<(), Error> { 609 | self.reader = None; 610 | Ok(()) 611 | } 612 | 613 | fn read( 614 | &mut self, 615 | buffers: &mut [&mut [num_complex::Complex32]], 616 | _timeout_us: i64, 617 | ) -> Result { 618 | if self.items_left == 0 { 619 | self.parse_header()?; 620 | } 621 | 622 | let is = std::mem::size_of::(); 623 | let n = std::cmp::min(self.items_left, buffers[0].len()); 624 | 625 | let out = 626 | unsafe { std::slice::from_raw_parts_mut(buffers[0].as_mut_ptr() as *mut u8, n * is) }; 627 | self.reader 628 | .as_mut() 629 | .unwrap() 630 | .read_exact(&mut out[0..n * is])?; 631 | 632 | self.items_left -= n; 633 | 634 | Ok(n) 635 | } 636 | } 637 | 638 | impl crate::TxStreamer for TxStreamer { 639 | fn mtu(&self) -> Result { 640 | Ok(65536 * 8) 641 | } 642 | 643 | fn activate_at(&mut self, _time_ns: Option) -> Result<(), Error> { 644 | Ok(()) 645 | } 646 | 647 | fn deactivate_at(&mut self, _time_ns: Option) -> Result<(), Error> { 648 | Ok(()) 649 | } 650 | 651 | fn write( 652 | &mut self, 653 | buffers: &[&[num_complex::Complex32]], 654 | at_ns: Option, 655 | end_burst: bool, 656 | _timeout_us: i64, 657 | ) -> Result { 658 | debug_assert_eq!(buffers.len(), 1); 659 | debug_assert_eq!(at_ns, None); 660 | 661 | let frequency = self.frequency.load(Ordering::SeqCst) as f64; 662 | let sample_rate = self.sample_rate.load(Ordering::SeqCst) as f64; 663 | let len: usize = buffers[0].len(); 664 | 665 | let start = SystemTime::now() 666 | .duration_since(SystemTime::UNIX_EPOCH) 667 | .unwrap() 668 | .as_secs_f64() 669 | + STREAMING_DELAY; 670 | let num_streamable_samples = if start < self.last_transmission_end_time { 671 | // log::debug!("WARNING: cannot send immediately, expecting {}s delay.", self.last_transmission_end_time - (start - STREAMING_DELAY)); 672 | let time_remaining_in_tx_queue = 1.0_f64 - (self.last_transmission_end_time - start); 673 | let num_streamable_samples_tmp = time_remaining_in_tx_queue * sample_rate; 674 | if num_streamable_samples_tmp <= 0.0 { 675 | // log::debug!("WARNING: stream start time lies more than one second in the future due to backed up TX queue."); 676 | // tx queue fully backed up 677 | return Ok(0); 678 | } else if end_burst && (num_streamable_samples_tmp as usize) < len { 679 | // not enough space in tx queue to send burst in one go -> return and retry later 680 | // log::debug!("WARNING: cannot send burst while assuring less than 1s streaming delay."); 681 | assert!(len <= (1.0_f64 / sample_rate) as usize); // assure that the burst can be sent at all if tx queue is empty 682 | return Ok(0); 683 | } else if (num_streamable_samples_tmp as usize) < len { 684 | // log::debug!("WARNING: tx queue running full, sending only a subset of samples ({}/{}).", num_streamable_samples_tmp, len); 685 | num_streamable_samples_tmp as usize 686 | } else { 687 | // log::debug!("WARNING: tx queue starting to run full."); 688 | len 689 | } 690 | } else { 691 | len 692 | }; 693 | let start = start.max(self.last_transmission_end_time); 694 | let stop = start + num_streamable_samples as f64 / sample_rate; 695 | self.last_transmission_end_time = stop + 1.0_f64 / sample_rate; // use one sample spacing between queued requests 696 | 697 | let samples = unsafe { 698 | std::slice::from_raw_parts( 699 | buffers[0].as_ptr() as *const f32, 700 | num_streamable_samples * 2, 701 | ) 702 | }; 703 | 704 | // log::debug!( 705 | // "sending {}{} samples with delay of {}s", 706 | // if end_burst { "burst of " } else { "" }, 707 | // num_streamable_samples, 708 | // start 709 | // - SystemTime::now() 710 | // .duration_since(SystemTime::UNIX_EPOCH) 711 | // .unwrap() 712 | // .as_secs_f64() 713 | // ); 714 | 715 | let j = json!({ 716 | "startTime": start, 717 | "endTime": stop, 718 | "startFrequency": frequency - sample_rate / 2.0, 719 | "endFrequency": frequency + sample_rate / 2.0, 720 | // parameter "stepFrequency": sample_rate, not required for upload/tx, used for subsampling in rx 721 | "minPower": -2, 722 | "maxPower": 2, 723 | "sampleSize": 2, 724 | "sampleDepth": 1, 725 | "unit": "volt", 726 | "payload": "iq", 727 | // do not set "flush": true, else it will drop all preceding samples still in the queue 728 | "push": true, 729 | // parameter "format": "json" or "f32" not necessary for upload/tx, used to request specific format in rx 730 | "samples": samples, 731 | }); 732 | 733 | self.agent 734 | .post(&format!("{}/sample", self.url)) 735 | .send_json(j)?; 736 | 737 | Ok(num_streamable_samples) 738 | } 739 | 740 | fn write_all( 741 | &mut self, 742 | _buffers: &[&[num_complex::Complex32]], 743 | _at_ns: Option, 744 | _end_burst: bool, 745 | _timeout_us: i64, 746 | ) -> Result<(), Error> { 747 | unimplemented!() 748 | } 749 | } 750 | -------------------------------------------------------------------------------- /src/impls/dummy.rs: -------------------------------------------------------------------------------- 1 | //! Dummy SDR for CI 2 | use std::sync::Arc; 3 | use std::sync::Mutex; 4 | 5 | use crate::Args; 6 | use crate::DeviceTrait; 7 | use crate::Direction; 8 | use crate::Direction::Rx; 9 | use crate::Direction::Tx; 10 | use crate::Driver; 11 | use crate::Error; 12 | use crate::Range; 13 | use crate::RangeItem; 14 | 15 | /// Dummy Device 16 | #[derive(Clone)] 17 | pub struct Dummy { 18 | rx_agc: Arc>, 19 | rx_bw: Arc>, 20 | rx_freq: Arc>, 21 | rx_gain: Arc>, 22 | rx_rate: Arc>, 23 | tx_agc: Arc>, 24 | tx_bw: Arc>, 25 | tx_freq: Arc>, 26 | tx_gain: Arc>, 27 | tx_rate: Arc>, 28 | } 29 | 30 | /// Dummy RX Streamer 31 | pub struct RxStreamer; 32 | 33 | /// Dummy TX Streamer 34 | pub struct TxStreamer; 35 | 36 | impl Dummy { 37 | /// Get a list of Devices 38 | /// 39 | /// Will only return exactly one device, if `dummy` is set as driver. 40 | pub fn probe(args: &Args) -> Result, Error> { 41 | match args.get::("driver").as_deref() { 42 | Ok("dummy") => { 43 | let mut a = Args::new(); 44 | a.set("driver", "dummy"); 45 | Ok(vec![a]) 46 | } 47 | _ => Ok(Vec::new()), 48 | } 49 | } 50 | /// Create a Dummy Device 51 | pub fn open>(_args: A) -> Result { 52 | Ok(Self { 53 | rx_agc: Arc::new(Mutex::new(false)), 54 | rx_gain: Arc::new(Mutex::new(0.0)), 55 | rx_freq: Arc::new(Mutex::new(0.0)), 56 | rx_rate: Arc::new(Mutex::new(0.0)), 57 | rx_bw: Arc::new(Mutex::new(0.0)), 58 | tx_agc: Arc::new(Mutex::new(false)), 59 | tx_gain: Arc::new(Mutex::new(0.0)), 60 | tx_freq: Arc::new(Mutex::new(0.0)), 61 | tx_rate: Arc::new(Mutex::new(0.0)), 62 | tx_bw: Arc::new(Mutex::new(0.0)), 63 | }) 64 | } 65 | } 66 | 67 | impl DeviceTrait for Dummy { 68 | type RxStreamer = RxStreamer; 69 | type TxStreamer = TxStreamer; 70 | 71 | fn as_any(&self) -> &dyn std::any::Any { 72 | self 73 | } 74 | 75 | fn as_any_mut(&mut self) -> &mut dyn std::any::Any { 76 | self 77 | } 78 | 79 | fn driver(&self) -> Driver { 80 | Driver::Dummy 81 | } 82 | 83 | fn id(&self) -> Result { 84 | Ok("dummy".to_string()) 85 | } 86 | 87 | fn info(&self) -> Result { 88 | let mut a = Args::new(); 89 | a.set("driver", "dummy"); 90 | Ok(a) 91 | } 92 | 93 | fn num_channels(&self, _direction: Direction) -> Result { 94 | Ok(1) 95 | } 96 | 97 | fn full_duplex(&self, _direction: Direction, _channel: usize) -> Result { 98 | Ok(true) 99 | } 100 | 101 | fn rx_streamer(&self, channels: &[usize], _args: Args) -> Result { 102 | match channels { 103 | &[0] => Ok(RxStreamer), 104 | _ => Err(Error::ValueError), 105 | } 106 | } 107 | 108 | fn tx_streamer(&self, channels: &[usize], _args: Args) -> Result { 109 | match channels { 110 | &[0] => Ok(TxStreamer), 111 | _ => Err(Error::ValueError), 112 | } 113 | } 114 | 115 | fn antennas(&self, _direction: Direction, channel: usize) -> Result, Error> { 116 | if channel == 0 { 117 | Ok(vec!["A".to_string()]) 118 | } else { 119 | Err(Error::ValueError) 120 | } 121 | } 122 | 123 | fn antenna(&self, _direction: Direction, channel: usize) -> Result { 124 | if channel == 0 { 125 | Ok("A".to_string()) 126 | } else { 127 | Err(Error::ValueError) 128 | } 129 | } 130 | 131 | fn set_antenna(&self, _direction: Direction, channel: usize, name: &str) -> Result<(), Error> { 132 | match (channel, name) { 133 | (0, "A") => Ok(()), 134 | _ => Err(Error::ValueError), 135 | } 136 | } 137 | 138 | fn gain_elements(&self, _direction: Direction, channel: usize) -> Result, Error> { 139 | if channel == 0 { 140 | Ok(vec!["RF".to_string()]) 141 | } else { 142 | Err(Error::ValueError) 143 | } 144 | } 145 | 146 | fn supports_agc(&self, _direction: Direction, channel: usize) -> Result { 147 | if channel == 0 { 148 | Ok(true) 149 | } else { 150 | Err(Error::ValueError) 151 | } 152 | } 153 | 154 | fn enable_agc(&self, direction: Direction, channel: usize, agc: bool) -> Result<(), Error> { 155 | match (channel, direction) { 156 | (0, Rx) => { 157 | *self.rx_agc.lock().unwrap() = agc; 158 | Ok(()) 159 | } 160 | (0, Tx) => { 161 | *self.tx_agc.lock().unwrap() = agc; 162 | Ok(()) 163 | } 164 | _ => Err(Error::ValueError), 165 | } 166 | } 167 | 168 | fn agc(&self, direction: Direction, channel: usize) -> Result { 169 | match (channel, direction) { 170 | (0, Rx) => Ok(*self.rx_agc.lock().unwrap()), 171 | (0, Tx) => Ok(*self.tx_agc.lock().unwrap()), 172 | _ => Err(Error::ValueError), 173 | } 174 | } 175 | 176 | fn set_gain(&self, direction: Direction, channel: usize, gain: f64) -> Result<(), Error> { 177 | if channel == 0 && gain >= 0.0 { 178 | match direction { 179 | Rx => *self.rx_gain.lock().unwrap() = gain, 180 | Tx => *self.tx_gain.lock().unwrap() = gain, 181 | } 182 | Ok(()) 183 | } else { 184 | Err(Error::ValueError) 185 | } 186 | } 187 | 188 | fn gain(&self, direction: Direction, channel: usize) -> Result, Error> { 189 | match (channel, direction) { 190 | (0, Rx) => { 191 | if *self.rx_agc.lock().unwrap() { 192 | Ok(None) 193 | } else { 194 | Ok(Some(*self.rx_gain.lock().unwrap())) 195 | } 196 | } 197 | (0, Tx) => { 198 | if *self.tx_agc.lock().unwrap() { 199 | Ok(None) 200 | } else { 201 | Ok(Some(*self.tx_gain.lock().unwrap())) 202 | } 203 | } 204 | _ => Err(Error::ValueError), 205 | } 206 | } 207 | 208 | fn gain_range(&self, _direction: Direction, channel: usize) -> Result { 209 | if channel == 0 { 210 | Ok(Range::new(vec![RangeItem::Interval(0.0, f64::MAX)])) 211 | } else { 212 | Err(Error::ValueError) 213 | } 214 | } 215 | 216 | fn set_gain_element( 217 | &self, 218 | direction: Direction, 219 | channel: usize, 220 | name: &str, 221 | gain: f64, 222 | ) -> Result<(), Error> { 223 | if channel == 0 && name == "RF" && gain >= 0.0 { 224 | match direction { 225 | Rx => *self.rx_gain.lock().unwrap() = gain, 226 | Tx => *self.tx_gain.lock().unwrap() = gain, 227 | } 228 | Ok(()) 229 | } else { 230 | Err(Error::ValueError) 231 | } 232 | } 233 | 234 | fn gain_element( 235 | &self, 236 | direction: Direction, 237 | channel: usize, 238 | name: &str, 239 | ) -> Result, Error> { 240 | match (channel, direction, name) { 241 | (0, Direction::Rx, "RF") => { 242 | if *self.rx_agc.lock().unwrap() { 243 | Ok(None) 244 | } else { 245 | Ok(Some(*self.rx_gain.lock().unwrap())) 246 | } 247 | } 248 | (0, Direction::Tx, "RF") => { 249 | if *self.tx_agc.lock().unwrap() { 250 | Ok(None) 251 | } else { 252 | Ok(Some(*self.tx_gain.lock().unwrap())) 253 | } 254 | } 255 | _ => Err(Error::ValueError), 256 | } 257 | } 258 | 259 | fn gain_element_range( 260 | &self, 261 | _direction: Direction, 262 | channel: usize, 263 | name: &str, 264 | ) -> Result { 265 | if channel == 0 && name == "RF" { 266 | Ok(Range::new(vec![RangeItem::Interval(0.0, f64::MAX)])) 267 | } else { 268 | Err(Error::ValueError) 269 | } 270 | } 271 | 272 | fn frequency_range(&self, _direction: Direction, channel: usize) -> Result { 273 | if channel == 0 { 274 | Ok(Range::new(vec![RangeItem::Interval(0.0, f64::MAX)])) 275 | } else { 276 | Err(Error::ValueError) 277 | } 278 | } 279 | 280 | fn frequency(&self, direction: Direction, channel: usize) -> Result { 281 | match (channel, direction) { 282 | (0, Rx) => Ok(*self.rx_freq.lock().unwrap()), 283 | (0, Tx) => Ok(*self.tx_freq.lock().unwrap()), 284 | _ => Err(Error::ValueError), 285 | } 286 | } 287 | 288 | fn set_frequency( 289 | &self, 290 | direction: Direction, 291 | channel: usize, 292 | frequency: f64, 293 | _args: Args, 294 | ) -> Result<(), Error> { 295 | if channel == 0 && frequency >= 0.0 { 296 | match direction { 297 | Rx => *self.rx_freq.lock().unwrap() = frequency, 298 | Tx => *self.tx_freq.lock().unwrap() = frequency, 299 | } 300 | Ok(()) 301 | } else { 302 | Err(Error::ValueError) 303 | } 304 | } 305 | 306 | fn frequency_components( 307 | &self, 308 | _direction: Direction, 309 | channel: usize, 310 | ) -> Result, Error> { 311 | if channel == 0 { 312 | Ok(vec!["freq".to_string()]) 313 | } else { 314 | Err(Error::ValueError) 315 | } 316 | } 317 | 318 | fn component_frequency_range( 319 | &self, 320 | _direction: Direction, 321 | channel: usize, 322 | name: &str, 323 | ) -> Result { 324 | if channel == 0 && name == "freq" { 325 | Ok(Range::new(vec![RangeItem::Interval(0.0, f64::MAX)])) 326 | } else { 327 | Err(Error::ValueError) 328 | } 329 | } 330 | 331 | fn component_frequency( 332 | &self, 333 | direction: Direction, 334 | channel: usize, 335 | name: &str, 336 | ) -> Result { 337 | if channel == 0 && name == "freq" { 338 | match direction { 339 | Rx => Ok(*self.rx_freq.lock().unwrap()), 340 | Tx => Ok(*self.tx_freq.lock().unwrap()), 341 | } 342 | } else { 343 | Err(Error::ValueError) 344 | } 345 | } 346 | 347 | fn set_component_frequency( 348 | &self, 349 | direction: Direction, 350 | channel: usize, 351 | name: &str, 352 | frequency: f64, 353 | ) -> Result<(), Error> { 354 | if channel == 0 && name == "freq" && frequency >= 0.0 { 355 | match direction { 356 | Rx => { 357 | *self.rx_freq.lock().unwrap() = frequency; 358 | Ok(()) 359 | } 360 | Tx => { 361 | *self.tx_freq.lock().unwrap() = frequency; 362 | Ok(()) 363 | } 364 | } 365 | } else { 366 | Err(Error::ValueError) 367 | } 368 | } 369 | 370 | fn sample_rate(&self, direction: Direction, channel: usize) -> Result { 371 | match (channel, direction) { 372 | (0, Rx) => Ok(*self.rx_rate.lock().unwrap()), 373 | (0, Tx) => Ok(*self.tx_rate.lock().unwrap()), 374 | _ => Err(Error::ValueError), 375 | } 376 | } 377 | 378 | fn set_sample_rate( 379 | &self, 380 | direction: Direction, 381 | channel: usize, 382 | rate: f64, 383 | ) -> Result<(), Error> { 384 | if channel == 0 && rate >= 0.0 { 385 | match direction { 386 | Rx => *self.rx_rate.lock().unwrap() = rate, 387 | Tx => *self.tx_rate.lock().unwrap() = rate, 388 | } 389 | Ok(()) 390 | } else { 391 | Err(Error::ValueError) 392 | } 393 | } 394 | 395 | fn get_sample_rate_range(&self, _direction: Direction, channel: usize) -> Result { 396 | if channel == 0 { 397 | Ok(Range::new(vec![RangeItem::Interval(0.0, f64::MAX)])) 398 | } else { 399 | Err(Error::ValueError) 400 | } 401 | } 402 | 403 | fn bandwidth(&self, direction: Direction, channel: usize) -> Result { 404 | match (channel, direction) { 405 | (0, Rx) => Ok(*self.rx_bw.lock().unwrap()), 406 | (0, Tx) => Ok(*self.tx_bw.lock().unwrap()), 407 | _ => Err(Error::ValueError), 408 | } 409 | } 410 | 411 | fn set_bandwidth(&self, direction: Direction, channel: usize, bw: f64) -> Result<(), Error> { 412 | if channel == 0 && bw >= 0.0 { 413 | match direction { 414 | Rx => *self.rx_bw.lock().unwrap() = bw, 415 | Tx => *self.tx_bw.lock().unwrap() = bw, 416 | } 417 | Ok(()) 418 | } else { 419 | Err(Error::ValueError) 420 | } 421 | } 422 | 423 | fn get_bandwidth_range(&self, _direction: Direction, channel: usize) -> Result { 424 | if channel == 0 { 425 | Ok(Range::new(vec![RangeItem::Interval(0.0, f64::MAX)])) 426 | } else { 427 | Err(Error::ValueError) 428 | } 429 | } 430 | 431 | fn has_dc_offset_mode(&self, _direction: Direction, _channel: usize) -> Result { 432 | Err(Error::NotSupported) 433 | } 434 | 435 | fn set_dc_offset_mode( 436 | &self, 437 | _direction: Direction, 438 | _channel: usize, 439 | _automatic: bool, 440 | ) -> Result<(), Error> { 441 | Err(Error::NotSupported) 442 | } 443 | 444 | fn dc_offset_mode(&self, _direction: Direction, _channel: usize) -> Result { 445 | Err(Error::NotSupported) 446 | } 447 | } 448 | 449 | impl crate::RxStreamer for RxStreamer { 450 | fn mtu(&self) -> Result { 451 | Ok(1500) 452 | } 453 | 454 | fn activate_at(&mut self, _time_ns: Option) -> Result<(), Error> { 455 | Ok(()) 456 | } 457 | 458 | fn deactivate_at(&mut self, _time_ns: Option) -> Result<(), Error> { 459 | Ok(()) 460 | } 461 | 462 | fn read( 463 | &mut self, 464 | buffers: &mut [&mut [num_complex::Complex32]], 465 | _timeout_us: i64, 466 | ) -> Result { 467 | for b in buffers.iter_mut() { 468 | b.fill(num_complex::Complex32::new(0.0, 0.0)) 469 | } 470 | Ok(buffers[0].len()) 471 | } 472 | } 473 | 474 | impl crate::TxStreamer for TxStreamer { 475 | fn mtu(&self) -> Result { 476 | Ok(1500) 477 | } 478 | 479 | fn activate_at(&mut self, _time_ns: Option) -> Result<(), Error> { 480 | Ok(()) 481 | } 482 | 483 | fn deactivate_at(&mut self, _time_ns: Option) -> Result<(), Error> { 484 | Ok(()) 485 | } 486 | 487 | fn write( 488 | &mut self, 489 | buffers: &[&[num_complex::Complex32]], 490 | _at_ns: Option, 491 | _end_burst: bool, 492 | _timeout_us: i64, 493 | ) -> Result { 494 | Ok(buffers[0].len()) 495 | } 496 | 497 | fn write_all( 498 | &mut self, 499 | _buffers: &[&[num_complex::Complex32]], 500 | _at_ns: Option, 501 | _end_burst: bool, 502 | _timeout_us: i64, 503 | ) -> Result<(), Error> { 504 | Ok(()) 505 | } 506 | } 507 | -------------------------------------------------------------------------------- /src/impls/hackrfone.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex}; 2 | 3 | use num_complex::Complex32; 4 | use seify_hackrfone::Config; 5 | 6 | use crate::{Args, Direction, Error, Range, RangeItem}; 7 | 8 | pub struct HackRfOne { 9 | inner: Arc, 10 | } 11 | 12 | const MTU: usize = 64 * 1024; 13 | 14 | impl HackRfOne { 15 | pub fn probe(_args: &Args) -> Result, Error> { 16 | let mut devs = vec![]; 17 | for (bus_number, address) in seify_hackrfone::HackRf::scan()? { 18 | log::debug!("probing {bus_number}:{address}"); 19 | devs.push( 20 | format!( 21 | "driver=hackrfone, bus_number={}, address={}", 22 | bus_number, address 23 | ) 24 | .try_into()?, 25 | ); 26 | } 27 | Ok(devs) 28 | } 29 | 30 | /// Create a Hackrf One devices 31 | pub fn open>(args: A) -> Result { 32 | let args: Args = args.try_into().or(Err(Error::ValueError))?; 33 | 34 | // TODO(troy): 35 | // re-enable once new version of nusb is published: https://github.com/kevinmehall/nusb/issues/84 36 | /* 37 | if let Ok(fd) = args.get::("fd") { 38 | let fd = unsafe { OwnedFd::from_raw_fd(fd) }; 39 | 40 | return Ok(Self { 41 | inner: Arc::new(HackRfInner { 42 | dev: seify_hackrfone::HackRf::from_fd(fd)?, 43 | tx_config: Mutex::new(Config::tx_default()), 44 | rx_config: Mutex::new(Config::rx_default()), 45 | }), 46 | }); 47 | } 48 | */ 49 | 50 | let bus_number = args.get("bus_number"); 51 | let address = args.get("address"); 52 | let dev = match (bus_number, address) { 53 | (Ok(bus_number), Ok(address)) => { 54 | seify_hackrfone::HackRf::open_bus(bus_number, address)? 55 | } 56 | (Err(Error::NotFound), Err(Error::NotFound)) => { 57 | log::debug!("Opening first hackrf device"); 58 | seify_hackrfone::HackRf::open_first()? 59 | } 60 | (bus_number, address) => { 61 | log::warn!("HackRfOne::open received invalid args: bus_number: {bus_number:?}, address: {address:?}"); 62 | return Err(Error::ValueError); 63 | } 64 | }; 65 | 66 | Ok(Self { 67 | inner: Arc::new(HackRfInner { 68 | dev, 69 | tx_config: Mutex::new(Config::tx_default()), 70 | rx_config: Mutex::new(Config::rx_default()), 71 | }), 72 | }) 73 | } 74 | 75 | pub fn with_config(&self, direction: Direction, f: F) -> R 76 | where 77 | F: FnOnce(&mut Config) -> R, 78 | { 79 | let config = match direction { 80 | Direction::Tx => self.inner.tx_config.lock(), 81 | Direction::Rx => self.inner.rx_config.lock(), 82 | }; 83 | f(&mut config.unwrap()) 84 | } 85 | } 86 | 87 | struct HackRfInner { 88 | dev: seify_hackrfone::HackRf, 89 | tx_config: Mutex, 90 | rx_config: Mutex, 91 | } 92 | 93 | pub struct RxStreamer { 94 | inner: Arc, 95 | stream: Option, 96 | } 97 | 98 | impl RxStreamer { 99 | fn new(inner: Arc) -> Self { 100 | Self { 101 | inner, 102 | stream: None, 103 | } 104 | } 105 | } 106 | 107 | impl crate::RxStreamer for RxStreamer { 108 | fn mtu(&self) -> Result { 109 | Ok(MTU) 110 | } 111 | 112 | fn activate_at(&mut self, _time_ns: Option) -> Result<(), Error> { 113 | // TODO: sleep precisely for `time_ns` 114 | let config = self.inner.rx_config.lock().unwrap(); 115 | self.inner.dev.start_rx(&config)?; 116 | 117 | self.stream = Some(self.inner.dev.start_rx_stream(MTU)?); 118 | 119 | Ok(()) 120 | } 121 | 122 | fn deactivate_at(&mut self, _time_ns: Option) -> Result<(), Error> { 123 | // TODO: sleep precisely for `time_ns` 124 | 125 | let _ = self.stream.take().unwrap(); 126 | self.inner.dev.stop_rx()?; 127 | Ok(()) 128 | } 129 | 130 | fn read( 131 | &mut self, 132 | buffers: &mut [&mut [num_complex::Complex32]], 133 | _timeout_us: i64, 134 | ) -> Result { 135 | debug_assert_eq!(buffers.len(), 1); 136 | 137 | if buffers[0].is_empty() { 138 | return Ok(0); 139 | } 140 | let buf = self.stream.as_mut().unwrap().read_sync(buffers[0].len())?; 141 | 142 | let samples = buf.len() / 2; 143 | for i in 0..samples { 144 | buffers[0][i] = Complex32::new( 145 | (buf[i * 2] as f32 - 127.0) / 128.0, 146 | (buf[i * 2 + 1] as f32 - 127.0) / 128.0, 147 | ); 148 | } 149 | Ok(samples) 150 | } 151 | } 152 | 153 | pub struct TxStreamer { 154 | inner: Arc, 155 | } 156 | 157 | impl TxStreamer { 158 | fn new(inner: Arc) -> Self { 159 | Self { inner } 160 | } 161 | } 162 | 163 | impl crate::TxStreamer for TxStreamer { 164 | fn mtu(&self) -> Result { 165 | Ok(MTU) 166 | } 167 | 168 | fn activate_at(&mut self, _time_ns: Option) -> Result<(), Error> { 169 | // TODO: sleep precisely for `time_ns` 170 | 171 | let config = self.inner.tx_config.lock().unwrap(); 172 | self.inner.dev.start_rx(&config)?; 173 | 174 | Ok(()) 175 | } 176 | 177 | fn deactivate_at(&mut self, _time_ns: Option) -> Result<(), Error> { 178 | // TODO: sleep precisely for `time_ns` 179 | 180 | self.inner.dev.stop_tx()?; 181 | Ok(()) 182 | } 183 | 184 | fn write( 185 | &mut self, 186 | buffers: &[&[num_complex::Complex32]], 187 | _at_ns: Option, 188 | _end_burst: bool, 189 | _timeout_us: i64, 190 | ) -> Result { 191 | debug_assert_eq!(buffers.len(), 1); 192 | todo!(); 193 | 194 | // self.inner.dev.write(samples) 195 | } 196 | 197 | fn write_all( 198 | &mut self, 199 | buffers: &[&[num_complex::Complex32]], 200 | _at_ns: Option, 201 | _end_burst: bool, 202 | _timeout_us: i64, 203 | ) -> Result<(), Error> { 204 | debug_assert_eq!(buffers.len(), 1); 205 | 206 | let mut n = 0; 207 | while n < buffers[0].len() { 208 | let buf = &buffers[0][n..]; 209 | n += self.write(&[buf], None, false, 0)?; 210 | } 211 | 212 | Ok(()) 213 | } 214 | } 215 | 216 | impl crate::DeviceTrait for HackRfOne { 217 | type RxStreamer = RxStreamer; 218 | 219 | type TxStreamer = TxStreamer; 220 | 221 | fn as_any(&self) -> &dyn std::any::Any { 222 | self 223 | } 224 | 225 | fn as_any_mut(&mut self) -> &mut dyn std::any::Any { 226 | self 227 | } 228 | 229 | fn driver(&self) -> crate::Driver { 230 | crate::Driver::HackRf 231 | } 232 | 233 | fn id(&self) -> Result { 234 | Ok(self.inner.dev.board_id()?.to_string()) 235 | } 236 | 237 | fn info(&self) -> Result { 238 | let mut args = crate::Args::default(); 239 | args.set("firmware version", self.inner.dev.version()?); 240 | Ok(args) 241 | } 242 | 243 | fn num_channels(&self, _: crate::Direction) -> Result { 244 | Ok(1) 245 | } 246 | 247 | fn full_duplex(&self, _direction: Direction, _channel: usize) -> Result { 248 | Ok(false) 249 | } 250 | 251 | fn rx_streamer(&self, channels: &[usize], _args: Args) -> Result { 252 | if channels != [0] { 253 | Err(Error::ValueError) 254 | } else { 255 | Ok(RxStreamer::new(Arc::clone(&self.inner))) 256 | } 257 | } 258 | 259 | fn tx_streamer(&self, channels: &[usize], _args: Args) -> Result { 260 | if channels != [0] { 261 | Err(Error::ValueError) 262 | } else { 263 | Ok(TxStreamer::new(Arc::clone(&self.inner))) 264 | } 265 | } 266 | 267 | fn antennas(&self, direction: Direction, channel: usize) -> Result, Error> { 268 | self.antenna(direction, channel).map(|a| vec![a]) 269 | } 270 | 271 | fn antenna(&self, direction: Direction, channel: usize) -> Result { 272 | if channel == 0 { 273 | Ok(match direction { 274 | Direction::Rx => "RX".to_string(), 275 | Direction::Tx => "TX".to_string(), 276 | }) 277 | } else { 278 | Err(Error::ValueError) 279 | } 280 | } 281 | 282 | fn set_antenna(&self, direction: Direction, channel: usize, name: &str) -> Result<(), Error> { 283 | if channel == 0 { 284 | if direction == Direction::Rx && name == "RX" 285 | || direction == Direction::Tx && name == "TX" 286 | { 287 | Ok(()) 288 | } else { 289 | Err(Error::NotSupported) 290 | } 291 | } else { 292 | Err(Error::ValueError) 293 | } 294 | } 295 | 296 | fn gain_elements(&self, direction: Direction, channel: usize) -> Result, Error> { 297 | if channel == 0 { 298 | // TODO: add support for other gains (RF and baseband) 299 | // See: https://hackrf.readthedocs.io/en/latest/faq.html#what-gain-controls-are-provided-by-hackrf 300 | match direction { 301 | Direction::Tx => Ok(vec!["IF".into()]), 302 | // TODO: add rest 303 | Direction::Rx => Ok(vec!["IF".into()]), 304 | } 305 | } else { 306 | Err(Error::ValueError) 307 | } 308 | } 309 | 310 | fn supports_agc(&self, _direction: Direction, channel: usize) -> Result { 311 | if channel == 0 { 312 | Ok(false) 313 | } else { 314 | Err(Error::ValueError) 315 | } 316 | } 317 | 318 | fn enable_agc(&self, _direction: Direction, channel: usize, _agc: bool) -> Result<(), Error> { 319 | if channel == 0 { 320 | Err(Error::NotSupported) 321 | } else { 322 | Err(Error::ValueError) 323 | } 324 | } 325 | 326 | fn agc(&self, _direction: Direction, channel: usize) -> Result { 327 | if channel == 0 { 328 | Err(Error::NotSupported) 329 | } else { 330 | Err(Error::ValueError) 331 | } 332 | } 333 | 334 | fn set_gain(&self, direction: Direction, channel: usize, gain: f64) -> Result<(), Error> { 335 | self.set_gain_element(direction, channel, "IF", gain) 336 | } 337 | 338 | fn gain(&self, direction: Direction, channel: usize) -> Result, Error> { 339 | self.gain_element(direction, channel, "IF") 340 | } 341 | 342 | fn gain_range(&self, direction: Direction, channel: usize) -> Result { 343 | self.gain_element_range(direction, channel, "IF") 344 | } 345 | 346 | fn set_gain_element( 347 | &self, 348 | direction: Direction, 349 | channel: usize, 350 | name: &str, 351 | gain: f64, 352 | ) -> Result<(), Error> { 353 | let r = self.gain_range(direction, channel)?; 354 | if r.contains(gain) && name == "IF" { 355 | match direction { 356 | Direction::Tx => todo!(), 357 | Direction::Rx => { 358 | let mut config = self.inner.rx_config.lock().unwrap(); 359 | config.lna_db = gain as u16; 360 | Ok(()) 361 | } 362 | } 363 | } else { 364 | log::warn!("Gain out of range"); 365 | Err(Error::OutOfRange(r, gain)) 366 | } 367 | } 368 | 369 | fn gain_element( 370 | &self, 371 | direction: Direction, 372 | channel: usize, 373 | name: &str, 374 | ) -> Result, Error> { 375 | if channel == 0 && name == "IF" { 376 | match direction { 377 | Direction::Tx => todo!(), 378 | Direction::Rx => { 379 | let config = self.inner.rx_config.lock().unwrap(); 380 | Ok(Some(config.lna_db as f64)) 381 | } 382 | } 383 | } else { 384 | Err(Error::ValueError) 385 | } 386 | } 387 | 388 | fn gain_element_range( 389 | &self, 390 | direction: Direction, 391 | channel: usize, 392 | name: &str, 393 | ) -> Result { 394 | // TODO: add support for other gains 395 | if channel == 0 && name == "IF" { 396 | match direction { 397 | Direction::Tx => Ok(Range::new(vec![RangeItem::Step(0.0, 47.0, 1.0)])), 398 | Direction::Rx => Ok(Range::new(vec![RangeItem::Step(0.0, 40.0, 8.0)])), 399 | } 400 | } else { 401 | Err(Error::ValueError) 402 | } 403 | } 404 | 405 | fn frequency_range(&self, direction: Direction, channel: usize) -> Result { 406 | self.component_frequency_range(direction, channel, "TUNER") 407 | } 408 | 409 | fn frequency(&self, direction: Direction, channel: usize) -> Result { 410 | self.component_frequency(direction, channel, "TUNER") 411 | } 412 | 413 | fn set_frequency( 414 | &self, 415 | direction: Direction, 416 | channel: usize, 417 | frequency: f64, 418 | _args: Args, 419 | ) -> Result<(), Error> { 420 | self.set_component_frequency(direction, channel, "TUNER", frequency) 421 | } 422 | 423 | fn frequency_components( 424 | &self, 425 | _direction: Direction, 426 | channel: usize, 427 | ) -> Result, Error> { 428 | if channel == 0 { 429 | Ok(vec!["TUNER".to_string()]) 430 | } else { 431 | Err(Error::ValueError) 432 | } 433 | } 434 | 435 | fn component_frequency_range( 436 | &self, 437 | _direction: Direction, 438 | channel: usize, 439 | name: &str, 440 | ) -> Result { 441 | if channel == 0 && name == "TUNER" { 442 | // up to 7.25GHz 443 | Ok(Range::new(vec![RangeItem::Interval(0.0, 7_270_000_000.0)])) 444 | } else { 445 | Err(Error::ValueError) 446 | } 447 | } 448 | 449 | fn component_frequency( 450 | &self, 451 | direction: Direction, 452 | channel: usize, 453 | name: &str, 454 | ) -> Result { 455 | if channel == 0 && name == "TUNER" { 456 | self.with_config(direction, |config| Ok(config.frequency_hz as f64)) 457 | } else { 458 | Err(Error::ValueError) 459 | } 460 | } 461 | 462 | fn set_component_frequency( 463 | &self, 464 | direction: Direction, 465 | channel: usize, 466 | name: &str, 467 | frequency: f64, 468 | ) -> Result<(), Error> { 469 | if channel == 0 470 | && self 471 | .frequency_range(direction, channel)? 472 | .contains(frequency) 473 | && name == "TUNER" 474 | { 475 | self.with_config(direction, |config| { 476 | config.frequency_hz = frequency as u64; 477 | self.inner.dev.set_freq(frequency as u64)?; 478 | Ok(()) 479 | }) 480 | } else { 481 | Err(Error::ValueError) 482 | } 483 | } 484 | 485 | fn sample_rate(&self, direction: Direction, channel: usize) -> Result { 486 | // NOTE: same state for both "directions" lets hope future sdr doesnt assume there are two 487 | // values here, should be fine since we told it we're not full duplex 488 | if channel == 0 { 489 | self.with_config(direction, |config| Ok(config.sample_rate_hz as f64)) 490 | } else { 491 | Err(Error::ValueError) 492 | } 493 | } 494 | 495 | fn set_sample_rate( 496 | &self, 497 | direction: Direction, 498 | channel: usize, 499 | rate: f64, 500 | ) -> Result<(), Error> { 501 | if channel == 0 502 | && self 503 | .get_sample_rate_range(direction, channel)? 504 | .contains(rate) 505 | { 506 | self.with_config(direction, |config| { 507 | // TODO: use sample rate div to enable lower effective sampling rate 508 | config.sample_rate_hz = rate as u32; 509 | config.sample_rate_div = 1; 510 | }); 511 | Ok(()) 512 | } else { 513 | Err(Error::ValueError) 514 | } 515 | } 516 | 517 | fn get_sample_rate_range(&self, _direction: Direction, channel: usize) -> Result { 518 | if channel == 0 { 519 | Ok(Range::new(vec![RangeItem::Interval( 520 | 1_000_000.0, 521 | 20_000_000.0, 522 | )])) 523 | } else { 524 | Err(Error::ValueError) 525 | } 526 | } 527 | 528 | fn bandwidth(&self, _direction: Direction, _channel: usize) -> Result { 529 | Err(Error::NotSupported) 530 | } 531 | 532 | fn set_bandwidth(&self, _direction: Direction, _channel: usize, bw: f64) -> Result<(), Error> { 533 | Ok(self.inner.dev.set_baseband_filter_bandwidth(bw as _)?) 534 | } 535 | 536 | fn get_bandwidth_range(&self, _direction: Direction, _channel: usize) -> Result { 537 | Err(Error::NotSupported) 538 | } 539 | } 540 | -------------------------------------------------------------------------------- /src/impls/mod.rs: -------------------------------------------------------------------------------- 1 | //! Hardware drivers, implementing the [`DeviceTrait`](crate::DeviceTrait). 2 | #[cfg(all(feature = "aaronia", any(target_os = "linux", target_os = "windows")))] 3 | pub mod aaronia; 4 | #[cfg(all(feature = "aaronia", any(target_os = "linux", target_os = "windows")))] 5 | pub use aaronia::Aaronia; 6 | 7 | #[cfg(all(feature = "aaronia_http", not(target_arch = "wasm32")))] 8 | pub mod aaronia_http; 9 | #[cfg(all(feature = "aaronia_http", not(target_arch = "wasm32")))] 10 | pub use aaronia_http::AaroniaHttp; 11 | 12 | #[cfg(feature = "dummy")] 13 | pub mod dummy; 14 | #[cfg(feature = "dummy")] 15 | pub use dummy::Dummy; 16 | 17 | #[cfg(all(feature = "rtlsdr", not(target_arch = "wasm32")))] 18 | pub mod rtlsdr; 19 | #[cfg(all(feature = "rtlsdr", not(target_arch = "wasm32")))] 20 | pub use rtlsdr::RtlSdr; 21 | 22 | #[cfg(all(feature = "soapy", not(target_arch = "wasm32")))] 23 | pub mod soapy; 24 | #[cfg(all(feature = "soapy", not(target_arch = "wasm32")))] 25 | pub use soapy::Soapy; 26 | 27 | #[cfg(all(feature = "hackrfone", not(target_arch = "wasm32")))] 28 | pub mod hackrfone; 29 | #[cfg(all(feature = "hackrfone", not(target_arch = "wasm32")))] 30 | pub use hackrfone::HackRfOne; 31 | -------------------------------------------------------------------------------- /src/impls/rtlsdr.rs: -------------------------------------------------------------------------------- 1 | //! RTL SDR 2 | use num_complex::Complex32; 3 | use seify_rtlsdr::enumerate; 4 | use seify_rtlsdr::RtlSdr as Sdr; 5 | use seify_rtlsdr::TunerGain; 6 | use std::any::Any; 7 | use std::sync::Arc; 8 | use std::sync::Mutex; 9 | 10 | use crate::Args; 11 | use crate::DeviceTrait; 12 | use crate::Direction; 13 | use crate::Direction::*; 14 | use crate::Driver; 15 | use crate::Error; 16 | use crate::Range; 17 | use crate::RangeItem; 18 | 19 | const MTU: usize = 4 * 16384; 20 | 21 | /// Rusty RTL-SDR driver 22 | #[derive(Clone)] 23 | pub struct RtlSdr { 24 | dev: Arc, 25 | index: usize, 26 | i: Arc>, 27 | } 28 | unsafe impl Send for RtlSdr {} 29 | unsafe impl Sync for RtlSdr {} 30 | 31 | struct Inner { 32 | gain: TunerGain, 33 | } 34 | 35 | /// Rusty RTL-SDR RX streamer 36 | pub struct RxStreamer { 37 | dev: Arc, 38 | buf: [u8; MTU], 39 | } 40 | 41 | unsafe impl Send for RxStreamer {} 42 | 43 | impl RxStreamer { 44 | fn new(dev: Arc) -> Self { 45 | Self { dev, buf: [0; MTU] } 46 | } 47 | } 48 | 49 | /// Rusty RTL-SDR TX dummy streamer 50 | pub struct TxDummy; 51 | unsafe impl Send for TxDummy {} 52 | 53 | impl RtlSdr { 54 | /// Get a list of detected RTL-SDR devices 55 | /// 56 | /// The returned [`Args`] specify the device, i.e., passing them to [`RtlSdr::open`] will open 57 | /// this particular device. At the moment, this just uses the index in the list of devices 58 | /// returned by the driver. 59 | pub fn probe(_args: &Args) -> Result, Error> { 60 | let rtls = enumerate().or(Err(Error::DeviceError))?; 61 | let mut devs = Vec::new(); 62 | for r in rtls { 63 | devs.push(format!("driver=rtlsdr, index={}", r.index).try_into()?); 64 | } 65 | Ok(devs) 66 | } 67 | /// Create an RTL-SDR device 68 | /// 69 | /// At the moment, only an `index` argument is considered, which defines the index of the 70 | /// devices in the list returned by the driver. 71 | pub fn open>(args: A) -> Result { 72 | let args = args.try_into().or(Err(Error::ValueError))?; 73 | let index = args.get::("index").unwrap_or(0); 74 | let rtls = enumerate().or(Err(Error::DeviceError))?; 75 | if index >= rtls.len() { 76 | return Err(Error::NotFound); 77 | } 78 | #[allow(clippy::arc_with_non_send_sync)] 79 | let dev = Arc::new(Sdr::open(index)?); 80 | dev.set_tuner_gain(TunerGain::Auto)?; 81 | dev.set_bias_tee(false)?; 82 | let dev = RtlSdr { 83 | dev, 84 | index, 85 | i: Arc::new(Mutex::new(Inner { 86 | gain: TunerGain::Auto, 87 | })), 88 | }; 89 | Ok(dev) 90 | } 91 | } 92 | 93 | impl DeviceTrait for RtlSdr { 94 | type RxStreamer = RxStreamer; 95 | type TxStreamer = TxDummy; 96 | 97 | fn as_any(&self) -> &dyn Any { 98 | self 99 | } 100 | 101 | fn as_any_mut(&mut self) -> &mut dyn Any { 102 | self 103 | } 104 | 105 | fn driver(&self) -> crate::Driver { 106 | Driver::RtlSdr 107 | } 108 | 109 | fn id(&self) -> Result { 110 | Ok(format!("{}", self.index)) 111 | } 112 | 113 | fn info(&self) -> Result { 114 | format!("driver=rtlsdr, index={}", self.index).try_into() 115 | } 116 | 117 | fn num_channels(&self, direction: Direction) -> Result { 118 | match direction { 119 | Rx => Ok(1), 120 | Tx => Ok(0), 121 | } 122 | } 123 | 124 | fn full_duplex(&self, _direction: Direction, _channel: usize) -> Result { 125 | Ok(false) 126 | } 127 | 128 | fn rx_streamer(&self, channels: &[usize], _args: Args) -> Result { 129 | if channels != [0] { 130 | Err(Error::ValueError) 131 | } else { 132 | Ok(RxStreamer::new(self.dev.clone())) 133 | } 134 | } 135 | 136 | fn tx_streamer(&self, _channels: &[usize], _args: Args) -> Result { 137 | Err(Error::NotSupported) 138 | } 139 | 140 | fn antennas(&self, direction: Direction, channel: usize) -> Result, Error> { 141 | self.antenna(direction, channel).map(|a| vec![a]) 142 | } 143 | 144 | fn antenna(&self, direction: Direction, channel: usize) -> Result { 145 | if matches!(direction, Rx) && channel == 0 { 146 | Ok("RX".to_string()) 147 | } else if matches!(direction, Rx) { 148 | Err(Error::ValueError) 149 | } else { 150 | Err(Error::NotSupported) 151 | } 152 | } 153 | 154 | fn set_antenna(&self, direction: Direction, channel: usize, name: &str) -> Result<(), Error> { 155 | if matches!(direction, Rx) && channel == 0 && name == "RX" { 156 | Ok(()) 157 | } else if matches!(direction, Rx) { 158 | Err(Error::ValueError) 159 | } else { 160 | Err(Error::NotSupported) 161 | } 162 | } 163 | 164 | fn gain_elements(&self, direction: Direction, channel: usize) -> Result, Error> { 165 | if matches!(direction, Rx) && channel == 0 { 166 | Ok(vec!["TUNER".to_string()]) 167 | } else if matches!(direction, Rx) { 168 | Err(Error::ValueError) 169 | } else { 170 | Err(Error::NotSupported) 171 | } 172 | } 173 | 174 | fn supports_agc(&self, direction: Direction, channel: usize) -> Result { 175 | if matches!(direction, Rx) && channel == 0 { 176 | Ok(true) 177 | } else if matches!(direction, Rx) { 178 | Err(Error::ValueError) 179 | } else { 180 | Err(Error::NotSupported) 181 | } 182 | } 183 | 184 | fn enable_agc(&self, direction: Direction, channel: usize, agc: bool) -> Result<(), Error> { 185 | let gains = self.dev.get_tuner_gains().or(Err(Error::DeviceError))?; 186 | if matches!(direction, Rx) && channel == 0 { 187 | let mut inner = self.i.lock().unwrap(); 188 | if agc { 189 | inner.gain = TunerGain::Auto; 190 | Ok(self.dev.set_tuner_gain(inner.gain.clone())?) 191 | } else { 192 | inner.gain = TunerGain::Manual(gains[gains.len() / 2]); 193 | Ok(self.dev.set_tuner_gain(inner.gain.clone())?) 194 | } 195 | } else if matches!(direction, Rx) { 196 | Err(Error::ValueError) 197 | } else { 198 | Err(Error::NotSupported) 199 | } 200 | } 201 | 202 | fn agc(&self, direction: Direction, channel: usize) -> Result { 203 | if matches!(direction, Rx) && channel == 0 { 204 | let inner = self.i.lock().unwrap(); 205 | Ok(matches!(inner.gain, TunerGain::Auto)) 206 | } else if matches!(direction, Rx) { 207 | Err(Error::ValueError) 208 | } else { 209 | Err(Error::NotSupported) 210 | } 211 | } 212 | 213 | fn set_gain(&self, direction: Direction, channel: usize, gain: f64) -> Result<(), Error> { 214 | self.set_gain_element(direction, channel, "TUNER", gain) 215 | } 216 | 217 | fn gain(&self, direction: Direction, channel: usize) -> Result, Error> { 218 | self.gain_element(direction, channel, "TUNER") 219 | } 220 | 221 | fn gain_range(&self, direction: Direction, channel: usize) -> Result { 222 | self.gain_element_range(direction, channel, "TUNER") 223 | } 224 | 225 | fn set_gain_element( 226 | &self, 227 | direction: Direction, 228 | channel: usize, 229 | name: &str, 230 | gain: f64, 231 | ) -> Result<(), Error> { 232 | let r = self.gain_range(direction, channel)?; 233 | if r.contains(gain) && name == "TUNER" { 234 | let mut inner = self.i.lock().unwrap(); 235 | inner.gain = TunerGain::Manual((gain * 10.0) as i32); 236 | Ok(self.dev.set_tuner_gain(inner.gain.clone())?) 237 | } else { 238 | log::warn!("Gain out of range"); 239 | Err(Error::OutOfRange(r, gain)) 240 | } 241 | } 242 | 243 | fn gain_element( 244 | &self, 245 | direction: Direction, 246 | channel: usize, 247 | name: &str, 248 | ) -> Result, Error> { 249 | if matches!(direction, Rx) && channel == 0 && name == "TUNER" { 250 | let inner = self.i.lock().unwrap(); 251 | match inner.gain { 252 | TunerGain::Auto => Ok(None), 253 | TunerGain::Manual(i) => Ok(Some(i as f64)), 254 | } 255 | } else if matches!(direction, Rx) { 256 | Err(Error::ValueError) 257 | } else { 258 | Err(Error::NotSupported) 259 | } 260 | } 261 | 262 | fn gain_element_range( 263 | &self, 264 | direction: Direction, 265 | channel: usize, 266 | name: &str, 267 | ) -> Result { 268 | if matches!(direction, Rx) && channel == 0 && name == "TUNER" { 269 | Ok(Range::new(vec![RangeItem::Interval(0.0, 50.0)])) 270 | } else if matches!(direction, Rx) { 271 | Err(Error::ValueError) 272 | } else { 273 | Err(Error::NotSupported) 274 | } 275 | } 276 | 277 | fn frequency_range(&self, direction: Direction, channel: usize) -> Result { 278 | self.component_frequency_range(direction, channel, "TUNER") 279 | } 280 | 281 | fn frequency(&self, direction: Direction, channel: usize) -> Result { 282 | self.component_frequency(direction, channel, "TUNER") 283 | } 284 | 285 | fn set_frequency( 286 | &self, 287 | direction: Direction, 288 | channel: usize, 289 | frequency: f64, 290 | _args: Args, 291 | ) -> Result<(), Error> { 292 | self.set_component_frequency(direction, channel, "TUNER", frequency) 293 | } 294 | 295 | fn frequency_components( 296 | &self, 297 | direction: Direction, 298 | channel: usize, 299 | ) -> Result, Error> { 300 | if matches!(direction, Rx) && channel == 0 { 301 | Ok(vec!["TUNER".to_string()]) 302 | } else if matches!(direction, Rx) { 303 | Err(Error::ValueError) 304 | } else { 305 | Err(Error::NotSupported) 306 | } 307 | } 308 | 309 | fn component_frequency_range( 310 | &self, 311 | direction: Direction, 312 | channel: usize, 313 | name: &str, 314 | ) -> Result { 315 | if matches!(direction, Rx) && channel == 0 && name == "TUNER" { 316 | Ok(Range::new(vec![RangeItem::Interval(0.0, 2e9)])) 317 | } else if matches!(direction, Rx) { 318 | Err(Error::ValueError) 319 | } else { 320 | Err(Error::NotSupported) 321 | } 322 | } 323 | 324 | fn component_frequency( 325 | &self, 326 | direction: Direction, 327 | channel: usize, 328 | name: &str, 329 | ) -> Result { 330 | if matches!(direction, Rx) && channel == 0 && name == "TUNER" { 331 | Ok(self.dev.get_center_freq() as f64) 332 | } else if matches!(direction, Rx) { 333 | Err(Error::ValueError) 334 | } else { 335 | Err(Error::NotSupported) 336 | } 337 | } 338 | 339 | fn set_component_frequency( 340 | &self, 341 | direction: Direction, 342 | channel: usize, 343 | name: &str, 344 | frequency: f64, 345 | ) -> Result<(), Error> { 346 | if matches!(direction, Rx) 347 | && channel == 0 348 | && self 349 | .frequency_range(direction, channel)? 350 | .contains(frequency) 351 | && name == "TUNER" 352 | { 353 | self.dev.set_center_freq(frequency as u32)?; 354 | Ok(self.dev.reset_buffer()?) 355 | } else if matches!(direction, Rx) { 356 | Err(Error::ValueError) 357 | } else { 358 | Err(Error::NotSupported) 359 | } 360 | } 361 | 362 | fn sample_rate(&self, direction: Direction, channel: usize) -> Result { 363 | if matches!(direction, Rx) && channel == 0 { 364 | Ok(self.dev.get_sample_rate() as f64) 365 | } else if matches!(direction, Rx) { 366 | Err(Error::ValueError) 367 | } else { 368 | Err(Error::NotSupported) 369 | } 370 | } 371 | 372 | fn set_sample_rate( 373 | &self, 374 | direction: Direction, 375 | channel: usize, 376 | rate: f64, 377 | ) -> Result<(), Error> { 378 | if matches!(direction, Rx) 379 | && channel == 0 380 | && self 381 | .get_sample_rate_range(direction, channel)? 382 | .contains(rate) 383 | { 384 | self.dev.set_tuner_bandwidth(rate as u32)?; 385 | Ok(self.dev.set_sample_rate(rate as u32)?) 386 | } else if matches!(direction, Rx) { 387 | Err(Error::ValueError) 388 | } else { 389 | Err(Error::NotSupported) 390 | } 391 | } 392 | 393 | fn get_sample_rate_range(&self, direction: Direction, channel: usize) -> Result { 394 | if matches!(direction, Rx) && channel == 0 { 395 | Ok(Range::new(vec![ 396 | RangeItem::Interval(225_001.0, 300_000.0), 397 | RangeItem::Interval(900_001.0, 3_200_000.0), 398 | ])) 399 | } else if matches!(direction, Rx) { 400 | Err(Error::ValueError) 401 | } else { 402 | Err(Error::NotSupported) 403 | } 404 | } 405 | 406 | fn bandwidth(&self, _direction: Direction, _channel: usize) -> Result { 407 | Err(Error::NotSupported) 408 | } 409 | 410 | fn set_bandwidth(&self, _direction: Direction, _channel: usize, bw: f64) -> Result<(), Error> { 411 | Ok(self.dev.set_tuner_bandwidth(bw as _)?) 412 | } 413 | 414 | fn get_bandwidth_range(&self, _direction: Direction, _channel: usize) -> Result { 415 | Err(Error::NotSupported) 416 | } 417 | 418 | fn has_dc_offset_mode(&self, _direction: Direction, _channel: usize) -> Result { 419 | Err(Error::NotSupported) 420 | } 421 | 422 | fn set_dc_offset_mode( 423 | &self, 424 | _direction: Direction, 425 | _channel: usize, 426 | _automatic: bool, 427 | ) -> Result<(), Error> { 428 | Err(Error::NotSupported) 429 | } 430 | 431 | fn dc_offset_mode(&self, _direction: Direction, _channel: usize) -> Result { 432 | Err(Error::NotSupported) 433 | } 434 | } 435 | 436 | impl crate::RxStreamer for RxStreamer { 437 | fn mtu(&self) -> Result { 438 | Ok(MTU) 439 | } 440 | fn activate_at(&mut self, _time_ns: Option) -> Result<(), Error> { 441 | self.dev.reset_buffer().or(Err(Error::DeviceError)) 442 | } 443 | fn deactivate_at(&mut self, _time_ns: Option) -> Result<(), Error> { 444 | Ok(()) 445 | } 446 | fn read(&mut self, buffers: &mut [&mut [Complex32]], _timeout_us: i64) -> Result { 447 | debug_assert_eq!(buffers.len(), 1); 448 | // make len multiple of 256 to make u multiple of 512 449 | let len = std::cmp::min(buffers[0].len(), MTU / 2); 450 | let len = len & !0xff; 451 | if len == 0 { 452 | return Ok(0); 453 | } 454 | let n = self.dev.read_sync(&mut self.buf[0..len * 2])?; 455 | debug_assert_eq!(n % 2, 0); 456 | 457 | for i in 0..n / 2 { 458 | buffers[0][i] = Complex32::new( 459 | (self.buf[i * 2] as f32 - 127.0) / 128.0, 460 | (self.buf[i * 2 + 1] as f32 - 127.0) / 128.0, 461 | ); 462 | } 463 | Ok(n / 2) 464 | } 465 | } 466 | 467 | impl crate::TxStreamer for TxDummy { 468 | fn mtu(&self) -> Result { 469 | unreachable!() 470 | } 471 | fn activate_at(&mut self, _time_ns: Option) -> Result<(), Error> { 472 | unreachable!() 473 | } 474 | fn deactivate_at(&mut self, _time_ns: Option) -> Result<(), Error> { 475 | unreachable!() 476 | } 477 | fn write( 478 | &mut self, 479 | _buffers: &[&[Complex32]], 480 | _at_ns: Option, 481 | _end_burst: bool, 482 | _timeout_us: i64, 483 | ) -> Result { 484 | unreachable!() 485 | } 486 | fn write_all( 487 | &mut self, 488 | _buffers: &[&[Complex32]], 489 | _at_ns: Option, 490 | _end_burst: bool, 491 | _timeout_us: i64, 492 | ) -> Result<(), Error> { 493 | unreachable!() 494 | } 495 | } 496 | -------------------------------------------------------------------------------- /src/impls/soapy.rs: -------------------------------------------------------------------------------- 1 | //! Soapy SDR 2 | use num_complex::Complex32; 3 | use std::sync::OnceLock; 4 | 5 | use crate::Args; 6 | use crate::DeviceTrait; 7 | use crate::Direction; 8 | use crate::Driver; 9 | use crate::Error; 10 | use crate::Range; 11 | use crate::RangeItem; 12 | 13 | /// Soapy Device 14 | #[derive(Clone)] 15 | pub struct Soapy { 16 | dev: soapysdr::Device, 17 | args: Args, 18 | index: usize, 19 | } 20 | 21 | /// Soapy RX Streamer 22 | pub struct RxStreamer { 23 | streamer: soapysdr::RxStream, 24 | } 25 | 26 | /// Soapy TX Streamer 27 | pub struct TxStreamer { 28 | streamer: soapysdr::TxStream, 29 | } 30 | 31 | /// Configures SoapySDR logging to route through the `log` crate. 32 | /// 33 | /// This function is idempotent and will only configure logging once. 34 | fn init_soapy_logging() { 35 | static INIT: OnceLock<()> = OnceLock::new(); 36 | INIT.get_or_init(|| { 37 | soapysdr::configure_logging(); 38 | }); 39 | } 40 | 41 | impl Soapy { 42 | /// Get a list of detected devices, supported by Soapy 43 | /// 44 | /// The returned [`Args`] specify the device, i.e., passing them to [`Soapy::open`] will open 45 | /// this particular device. Using the `soapy_driver` argument it is possible to specify the 46 | /// `driver` argument for Soapy. 47 | pub fn probe(args: &Args) -> Result, Error> { 48 | init_soapy_logging(); 49 | let v = soapysdr::enumerate(soapysdr::Args::try_from(args.clone())?)?; 50 | let v: Vec = v.into_iter().map(Into::into).collect(); 51 | Ok(v.into_iter() 52 | .map(|mut a| { 53 | match a.get::("driver") { 54 | Ok(d) => { 55 | a.set("soapy_driver", d); 56 | a.set("driver", "soapy") 57 | } 58 | Err(_) => a.set("driver", "soapy"), 59 | }; 60 | a 61 | }) 62 | .collect()) 63 | } 64 | /// Create a Soapy Device 65 | /// 66 | /// It is possible to specify the Soapy `driver` argument by passing the `soapy_driver` argument 67 | /// to this function. 68 | pub fn open>(args: A) -> Result { 69 | init_soapy_logging(); 70 | let mut args: Args = args.try_into().or(Err(Error::ValueError))?; 71 | let index = args.get("index").unwrap_or(0); 72 | 73 | let orig_args = args.clone(); 74 | if let Ok(d) = args.get::("soapy_driver") { 75 | args.set("driver", d); 76 | } else { 77 | args.remove("driver"); 78 | } 79 | 80 | Ok(Self { 81 | dev: soapysdr::Device::new(soapysdr::Args::try_from(args)?)?, 82 | args: orig_args, 83 | index, 84 | }) 85 | } 86 | } 87 | 88 | impl DeviceTrait for Soapy { 89 | type RxStreamer = RxStreamer; 90 | type TxStreamer = TxStreamer; 91 | 92 | fn as_any(&self) -> &dyn std::any::Any { 93 | self 94 | } 95 | 96 | fn as_any_mut(&mut self) -> &mut dyn std::any::Any { 97 | self 98 | } 99 | 100 | fn driver(&self) -> Driver { 101 | Driver::Soapy 102 | } 103 | 104 | fn id(&self) -> Result { 105 | Ok(format!("{}", self.index)) 106 | } 107 | 108 | fn info(&self) -> Result { 109 | Ok(self.args.clone()) 110 | } 111 | 112 | fn num_channels(&self, direction: Direction) -> Result { 113 | Ok(self.dev.num_channels(direction.into())?) 114 | } 115 | 116 | fn full_duplex(&self, direction: Direction, channel: usize) -> Result { 117 | Ok(self.dev.full_duplex(direction.into(), channel)?) 118 | } 119 | 120 | fn rx_streamer(&self, channels: &[usize], args: Args) -> Result { 121 | Ok(RxStreamer { 122 | streamer: self 123 | .dev 124 | .rx_stream_args(channels, soapysdr::Args::try_from(args)?)?, 125 | }) 126 | } 127 | 128 | fn tx_streamer(&self, channels: &[usize], args: Args) -> Result { 129 | Ok(TxStreamer { 130 | streamer: self 131 | .dev 132 | .tx_stream_args(channels, soapysdr::Args::try_from(args)?)?, 133 | }) 134 | } 135 | 136 | fn antennas(&self, direction: Direction, channel: usize) -> Result, Error> { 137 | Ok(self.dev.antennas(direction.into(), channel)?) 138 | } 139 | 140 | fn antenna(&self, direction: Direction, channel: usize) -> Result { 141 | Ok(self.dev.antenna(direction.into(), channel)?) 142 | } 143 | 144 | fn set_antenna(&self, direction: Direction, channel: usize, name: &str) -> Result<(), Error> { 145 | Ok(self.dev.set_antenna(direction.into(), channel, name)?) 146 | } 147 | 148 | fn gain_elements(&self, direction: Direction, channel: usize) -> Result, Error> { 149 | Ok(self.dev.list_gains(direction.into(), channel)?) 150 | } 151 | 152 | fn supports_agc(&self, direction: Direction, channel: usize) -> Result { 153 | Ok(self.dev.has_gain_mode(direction.into(), channel)?) 154 | } 155 | 156 | fn enable_agc(&self, direction: Direction, channel: usize, agc: bool) -> Result<(), Error> { 157 | Ok(self.dev.set_gain_mode(direction.into(), channel, agc)?) 158 | } 159 | 160 | fn agc(&self, direction: Direction, channel: usize) -> Result { 161 | Ok(self.dev.gain_mode(direction.into(), channel)?) 162 | } 163 | 164 | fn set_gain(&self, direction: Direction, channel: usize, gain: f64) -> Result<(), Error> { 165 | Ok(self.dev.set_gain(direction.into(), channel, gain)?) 166 | } 167 | 168 | fn gain(&self, direction: Direction, channel: usize) -> Result, Error> { 169 | if self.agc(direction, channel)? { 170 | Ok(None) 171 | } else { 172 | Ok(Some(self.dev.gain(direction.into(), channel)?)) 173 | } 174 | } 175 | 176 | fn gain_range(&self, direction: Direction, channel: usize) -> Result { 177 | let range = self.dev.gain_range(direction.into(), channel)?; 178 | Ok(range.into()) 179 | } 180 | 181 | fn set_gain_element( 182 | &self, 183 | direction: Direction, 184 | channel: usize, 185 | name: &str, 186 | gain: f64, 187 | ) -> Result<(), Error> { 188 | Ok(self 189 | .dev 190 | .set_gain_element(direction.into(), channel, name, gain)?) 191 | } 192 | 193 | fn gain_element( 194 | &self, 195 | direction: Direction, 196 | channel: usize, 197 | name: &str, 198 | ) -> Result, Error> { 199 | if self.agc(direction, channel)? { 200 | Ok(None) 201 | } else { 202 | Ok(Some(self.dev.gain_element( 203 | direction.into(), 204 | channel, 205 | name, 206 | )?)) 207 | } 208 | } 209 | 210 | fn gain_element_range( 211 | &self, 212 | direction: Direction, 213 | channel: usize, 214 | name: &str, 215 | ) -> Result { 216 | let range = self 217 | .dev 218 | .gain_element_range(direction.into(), channel, name)?; 219 | Ok(range.into()) 220 | } 221 | 222 | fn frequency_range(&self, direction: Direction, channel: usize) -> Result { 223 | let range = self.dev.frequency_range(direction.into(), channel)?; 224 | Ok(range.into()) 225 | } 226 | 227 | fn frequency(&self, direction: Direction, channel: usize) -> Result { 228 | Ok(self.dev.frequency(direction.into(), channel)?) 229 | } 230 | 231 | fn set_frequency( 232 | &self, 233 | direction: Direction, 234 | channel: usize, 235 | frequency: f64, 236 | args: Args, 237 | ) -> Result<(), Error> { 238 | Ok(self.dev.set_frequency( 239 | direction.into(), 240 | channel, 241 | frequency, 242 | soapysdr::Args::try_from(args)?, 243 | )?) 244 | } 245 | 246 | fn frequency_components( 247 | &self, 248 | direction: Direction, 249 | channel: usize, 250 | ) -> Result, Error> { 251 | Ok(self.dev.list_frequencies(direction.into(), channel)?) 252 | } 253 | 254 | fn component_frequency_range( 255 | &self, 256 | direction: Direction, 257 | channel: usize, 258 | name: &str, 259 | ) -> Result { 260 | let range = self 261 | .dev 262 | .component_frequency_range(direction.into(), channel, name)?; 263 | Ok(range.into()) 264 | } 265 | 266 | fn component_frequency( 267 | &self, 268 | direction: Direction, 269 | channel: usize, 270 | name: &str, 271 | ) -> Result { 272 | Ok(self 273 | .dev 274 | .component_frequency(direction.into(), channel, name)?) 275 | } 276 | 277 | fn set_component_frequency( 278 | &self, 279 | direction: Direction, 280 | channel: usize, 281 | name: &str, 282 | frequency: f64, 283 | ) -> Result<(), Error> { 284 | Ok(self.dev.set_component_frequency( 285 | direction.into(), 286 | channel, 287 | name, 288 | frequency, 289 | soapysdr::Args::new(), 290 | )?) 291 | } 292 | 293 | fn sample_rate(&self, direction: Direction, channel: usize) -> Result { 294 | Ok(self.dev.sample_rate(direction.into(), channel)?) 295 | } 296 | 297 | fn set_sample_rate( 298 | &self, 299 | direction: Direction, 300 | channel: usize, 301 | rate: f64, 302 | ) -> Result<(), Error> { 303 | Ok(self.dev.set_sample_rate(direction.into(), channel, rate)?) 304 | } 305 | 306 | fn get_sample_rate_range(&self, direction: Direction, channel: usize) -> Result { 307 | let range = self.dev.get_sample_rate_range(direction.into(), channel)?; 308 | Ok(range.into()) 309 | } 310 | 311 | fn bandwidth(&self, direction: Direction, channel: usize) -> Result { 312 | Ok(self.dev.bandwidth(direction.into(), channel)?) 313 | } 314 | 315 | fn set_bandwidth(&self, direction: Direction, channel: usize, bw: f64) -> Result<(), Error> { 316 | Ok(self.dev.set_bandwidth(direction.into(), channel, bw)?) 317 | } 318 | 319 | fn get_bandwidth_range(&self, direction: Direction, channel: usize) -> Result { 320 | let range = self.dev.bandwidth_range(direction.into(), channel)?; 321 | Ok(range.into()) 322 | } 323 | 324 | fn has_dc_offset_mode(&self, direction: Direction, channel: usize) -> Result { 325 | Ok(self.dev.has_dc_offset_mode(direction.into(), channel)?) 326 | } 327 | 328 | fn set_dc_offset_mode( 329 | &self, 330 | direction: Direction, 331 | channel: usize, 332 | automatic: bool, 333 | ) -> Result<(), Error> { 334 | Ok(self 335 | .dev 336 | .set_dc_offset_mode(direction.into(), channel, automatic)?) 337 | } 338 | 339 | fn dc_offset_mode(&self, direction: Direction, channel: usize) -> Result { 340 | Ok(self.dev.dc_offset_mode(direction.into(), channel)?) 341 | } 342 | } 343 | 344 | impl crate::RxStreamer for RxStreamer { 345 | fn mtu(&self) -> Result { 346 | Ok(self.streamer.mtu()?) 347 | } 348 | 349 | fn activate_at(&mut self, time_ns: Option) -> Result<(), Error> { 350 | Ok(self.streamer.activate(time_ns)?) 351 | } 352 | 353 | fn deactivate_at(&mut self, time_ns: Option) -> Result<(), Error> { 354 | Ok(self.streamer.deactivate(time_ns)?) 355 | } 356 | 357 | fn read( 358 | &mut self, 359 | buffers: &mut [&mut [num_complex::Complex32]], 360 | timeout_us: i64, 361 | ) -> Result { 362 | Ok(self.streamer.read(buffers, timeout_us)?) 363 | } 364 | } 365 | 366 | impl crate::TxStreamer for TxStreamer { 367 | fn mtu(&self) -> Result { 368 | Ok(self.streamer.mtu()?) 369 | } 370 | 371 | fn activate_at(&mut self, time_ns: Option) -> Result<(), Error> { 372 | Ok(self.streamer.activate(time_ns)?) 373 | } 374 | 375 | fn deactivate_at(&mut self, time_ns: Option) -> Result<(), Error> { 376 | Ok(self.streamer.deactivate(time_ns)?) 377 | } 378 | 379 | fn write( 380 | &mut self, 381 | buffers: &[&[num_complex::Complex32]], 382 | at_ns: Option, 383 | end_burst: bool, 384 | timeout_us: i64, 385 | ) -> Result { 386 | Ok(self.streamer.write(buffers, at_ns, end_burst, timeout_us)?) 387 | } 388 | 389 | fn write_all( 390 | &mut self, 391 | buffers: &[&[num_complex::Complex32]], 392 | at_ns: Option, 393 | end_burst: bool, 394 | timeout_us: i64, 395 | ) -> Result<(), Error> { 396 | Ok(self 397 | .streamer 398 | .write_all(buffers, at_ns, end_burst, timeout_us)?) 399 | } 400 | } 401 | 402 | impl From for Error { 403 | fn from(value: soapysdr::Error) -> Self { 404 | if value.code == soapysdr::ErrorCode::Overflow { 405 | Error::Overflow 406 | } else { 407 | Error::Soapy(value) 408 | } 409 | } 410 | } 411 | 412 | impl From for soapysdr::Direction { 413 | fn from(value: crate::Direction) -> Self { 414 | match value { 415 | crate::Direction::Rx => soapysdr::Direction::Rx, 416 | crate::Direction::Tx => soapysdr::Direction::Tx, 417 | } 418 | } 419 | } 420 | 421 | impl From for Range { 422 | fn from(range: soapysdr::Range) -> Self { 423 | let mut r = vec![]; 424 | if range.step == 0.0 && range.minimum == range.maximum { 425 | r.push(RangeItem::Value(range.minimum)); 426 | } else if range.step == 0.0 { 427 | r.push(RangeItem::Interval(range.minimum, range.maximum)); 428 | } else { 429 | r.push(RangeItem::Step(range.minimum, range.maximum, range.step)); 430 | } 431 | Range::new(r) 432 | } 433 | } 434 | 435 | impl From> for Range { 436 | fn from(value: Vec) -> Self { 437 | let mut range = Range::new(vec![]); 438 | for v in value.into_iter() { 439 | range.merge(v.into()); 440 | } 441 | range 442 | } 443 | } 444 | 445 | impl TryFrom for soapysdr::Args { 446 | type Error = Error; 447 | 448 | fn try_from(args: Args) -> Result { 449 | let s = format!("{args}"); 450 | Ok(s.as_str().into()) 451 | } 452 | } 453 | 454 | impl From for Args { 455 | fn from(value: soapysdr::Args) -> Self { 456 | let mut a = Self::new(); 457 | for (key, value) in value.iter() { 458 | a.set(key, value); 459 | } 460 | a 461 | } 462 | } 463 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod args; 2 | pub use args::Args; 3 | 4 | mod device; 5 | pub use device::Device; 6 | pub use device::DeviceTrait; 7 | pub use device::GenericDevice; 8 | 9 | pub mod impls; 10 | 11 | mod range; 12 | pub use range::Range; 13 | pub use range::RangeItem; 14 | 15 | mod streamer; 16 | pub use streamer::RxStreamer; 17 | pub use streamer::TxStreamer; 18 | 19 | use serde::{Deserialize, Serialize}; 20 | 21 | use std::str::FromStr; 22 | use thiserror::Error; 23 | 24 | /// Seify Error 25 | #[derive(Debug, Error)] 26 | pub enum Error { 27 | #[error("DeviceError")] 28 | DeviceError, 29 | #[error("Value ({1}) out of range ({0:?})")] 30 | OutOfRange(Range, f64), 31 | #[error("Value Error")] 32 | ValueError, 33 | #[error("Not Found")] 34 | NotFound, 35 | #[error("corresponding feature not enabled")] 36 | FeatureNotEnabled, 37 | #[error("Not Supported")] 38 | NotSupported, 39 | #[error("Overflow")] 40 | Overflow, 41 | #[error("Inactive")] 42 | Inactive, 43 | #[error("Json ({0})")] 44 | Json(#[from] serde_json::Error), 45 | #[error("Misc")] 46 | Misc(String), 47 | #[error("Io ({0})")] 48 | Io(#[from] std::io::Error), 49 | #[cfg(all(feature = "soapy", not(target_arch = "wasm32")))] 50 | #[error("Soapy ({0})")] 51 | Soapy(soapysdr::Error), 52 | #[cfg(all(feature = "aaronia_http", not(target_arch = "wasm32")))] 53 | #[error("Ureq ({0})")] 54 | Ureq(Box), 55 | #[cfg(all(feature = "rtlsdr", not(target_arch = "wasm32")))] 56 | #[error("RtlSdr ({0})")] 57 | RtlSdr(#[from] seify_rtlsdr::error::RtlsdrError), 58 | #[cfg(all(feature = "hackrfone", not(target_arch = "wasm32")))] 59 | #[error("Hackrf ({0})")] 60 | HackRfOne(#[from] seify_hackrfone::Error), 61 | } 62 | 63 | #[cfg(all(feature = "aaronia_http", not(target_arch = "wasm32")))] 64 | impl From for Error { 65 | fn from(value: ureq::Error) -> Self { 66 | Error::Ureq(Box::new(value)) 67 | } 68 | } 69 | 70 | /// Supported hardware drivers. 71 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 72 | #[non_exhaustive] 73 | pub enum Driver { 74 | Aaronia, 75 | AaroniaHttp, 76 | Dummy, 77 | HackRf, 78 | RtlSdr, 79 | Soapy, 80 | } 81 | 82 | impl FromStr for Driver { 83 | type Err = Error; 84 | 85 | fn from_str(s: &str) -> Result { 86 | let s = s.to_lowercase(); 87 | if s == "aaronia" { 88 | return Ok(Driver::Aaronia); 89 | } 90 | if s == "aaronia_http" || s == "aaronia-http" || s == "aaroniahttp" { 91 | return Ok(Driver::AaroniaHttp); 92 | } 93 | if s == "rtlsdr" || s == "rtl-sdr" || s == "rtl" { 94 | return Ok(Driver::RtlSdr); 95 | } 96 | if s == "soapy" || s == "soapysdr" { 97 | return Ok(Driver::Soapy); 98 | } 99 | if s == "hackrf" || s == "hackrfone" { 100 | return Ok(Driver::HackRf); 101 | } 102 | if s == "dummy" || s == "Dummy" { 103 | return Ok(Driver::Dummy); 104 | } 105 | Err(Error::ValueError) 106 | } 107 | } 108 | 109 | /// Direction (Rx/TX) 110 | #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] 111 | pub enum Direction { 112 | Rx, 113 | Tx, 114 | } 115 | 116 | /// Enumerate devices. 117 | /// 118 | /// ## Returns 119 | /// 120 | /// A vector or [`Args`] that provide information about the device and can be used to identify it 121 | /// uniquely, i.e., passing the [`Args`] to [`Device::from_args`](crate::Device::from_args) will 122 | /// open this particular device. 123 | pub fn enumerate() -> Result, Error> { 124 | enumerate_with_args(Args::new()) 125 | } 126 | 127 | /// Enumerate devices with given [`Args`]. 128 | /// 129 | /// ## Returns 130 | /// 131 | /// A vector or [`Args`] that provide information about the device and can be used to identify it 132 | /// uniquely, i.e., passing the [`Args`] to [`Device::from_args`](crate::Device::from_args) will 133 | /// open this particular device. 134 | pub fn enumerate_with_args>(a: A) -> Result, Error> { 135 | let args: Args = a.try_into().or(Err(Error::ValueError))?; 136 | let mut devs = Vec::new(); 137 | let driver = match args.get::("driver") { 138 | Ok(s) => Some(s.parse::()?), 139 | Err(_) => None, 140 | }; 141 | 142 | #[cfg(all(feature = "aaronia", any(target_os = "linux", target_os = "windows")))] 143 | { 144 | if driver.is_none() || matches!(driver, Some(Driver::Aaronia)) { 145 | devs.append(&mut impls::Aaronia::probe(&args)?) 146 | } 147 | } 148 | #[cfg(not(all(feature = "aaronia", any(target_os = "linux", target_os = "windows"))))] 149 | { 150 | if matches!(driver, Some(Driver::Aaronia)) { 151 | return Err(Error::FeatureNotEnabled); 152 | } 153 | } 154 | 155 | #[cfg(all(feature = "aaronia_http", not(target_arch = "wasm32")))] 156 | { 157 | if driver.is_none() || matches!(driver, Some(Driver::AaroniaHttp)) { 158 | devs.append(&mut impls::AaroniaHttp::probe(&args)?) 159 | } 160 | } 161 | #[cfg(not(all(feature = "aaronia_http", not(target_arch = "wasm32"))))] 162 | { 163 | if matches!(driver, Some(Driver::AaroniaHttp)) { 164 | return Err(Error::FeatureNotEnabled); 165 | } 166 | } 167 | 168 | #[cfg(all(feature = "rtlsdr", not(target_arch = "wasm32")))] 169 | { 170 | if driver.is_none() || matches!(driver, Some(Driver::RtlSdr)) { 171 | devs.append(&mut impls::RtlSdr::probe(&args)?) 172 | } 173 | } 174 | #[cfg(not(all(feature = "rtlsdr", not(target_arch = "wasm32"))))] 175 | { 176 | if matches!(driver, Some(Driver::RtlSdr)) { 177 | return Err(Error::FeatureNotEnabled); 178 | } 179 | } 180 | 181 | #[cfg(all(feature = "soapy", not(target_arch = "wasm32")))] 182 | { 183 | if driver.is_none() || matches!(driver, Some(Driver::Soapy)) { 184 | devs.append(&mut impls::Soapy::probe(&args)?) 185 | } 186 | } 187 | #[cfg(not(all(feature = "soapy", not(target_arch = "wasm32"))))] 188 | { 189 | if matches!(driver, Some(Driver::Soapy)) { 190 | return Err(Error::FeatureNotEnabled); 191 | } 192 | } 193 | 194 | #[cfg(all(feature = "hackrfone", not(target_arch = "wasm32")))] 195 | { 196 | if driver.is_none() || matches!(driver, Some(Driver::HackRf)) { 197 | devs.append(&mut impls::HackRfOne::probe(&args)?) 198 | } 199 | } 200 | #[cfg(not(all(feature = "hackrfone", not(target_arch = "wasm32"))))] 201 | { 202 | if matches!(driver, Some(Driver::HackRf)) { 203 | return Err(Error::FeatureNotEnabled); 204 | } 205 | } 206 | #[cfg(feature = "dummy")] 207 | { 208 | if driver.is_none() || matches!(driver, Some(Driver::Dummy)) { 209 | devs.append(&mut impls::Dummy::probe(&args)?) 210 | } 211 | } 212 | #[cfg(not(feature = "dummy"))] 213 | { 214 | if matches!(driver, Some(Driver::Dummy)) { 215 | return Err(Error::FeatureNotEnabled); 216 | } 217 | } 218 | 219 | let _ = &mut devs; 220 | Ok(devs) 221 | } 222 | -------------------------------------------------------------------------------- /src/range.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | use serde::Serialize; 3 | 4 | /// Component of a [Range]. 5 | /// 6 | /// A [RangeItem] can be an interval, a fixed value, or a step interval. 7 | #[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)] 8 | pub enum RangeItem { 9 | /// Min/max interval (inclusive). 10 | Interval(f64, f64), 11 | /// Exact, fixed value. 12 | Value(f64), 13 | /// Min/max/skip intervals. 14 | Step(f64, f64, f64), 15 | } 16 | 17 | /// Range of possible values, comprised of individual values and/or intervals. 18 | #[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)] 19 | pub struct Range { 20 | pub items: Vec, 21 | } 22 | 23 | impl Range { 24 | /// Create a [`Range`] from [`RangeItems`](RangeItem). 25 | pub fn new(items: Vec) -> Self { 26 | Self { items } 27 | } 28 | /// Check if the [`Range`] contains the `value`. 29 | pub fn contains(&self, value: f64) -> bool { 30 | for item in &self.items { 31 | match *item { 32 | RangeItem::Interval(a, b) => { 33 | if a <= value && value <= b { 34 | return true; 35 | } 36 | } 37 | RangeItem::Value(v) => { 38 | if (v - value).abs() <= f64::EPSILON { 39 | return true; 40 | } 41 | } 42 | RangeItem::Step(min, max, step) => { 43 | if value < min { 44 | continue; 45 | } 46 | let mut v = min + ((value - min) / step).floor() * step; 47 | while v <= max && v <= value { 48 | if (v - value).abs() <= f64::EPSILON { 49 | return true; 50 | } 51 | v += step; 52 | } 53 | } 54 | } 55 | } 56 | false 57 | } 58 | /// Returns the value in [`Range`] that is closest to the given `value` or `None`, if the 59 | /// [`Range`] is empty. 60 | pub fn closest(&self, value: f64) -> Option { 61 | fn closer(target: f64, closest: Option, current: f64) -> f64 { 62 | match closest { 63 | Some(c) => { 64 | if (target - current).abs() < (c - target).abs() { 65 | current 66 | } else { 67 | c 68 | } 69 | } 70 | None => current, 71 | } 72 | } 73 | 74 | if self.contains(value) { 75 | Some(value) 76 | } else { 77 | let mut close = None; 78 | for i in self.items.iter() { 79 | match i { 80 | RangeItem::Interval(a, b) => { 81 | close = Some(closer(value, close, *a)); 82 | close = Some(closer(value, close, *b)); 83 | } 84 | RangeItem::Value(a) => { 85 | close = Some(closer(value, close, *a)); 86 | } 87 | RangeItem::Step(min, max, step) => { 88 | if value <= *min { 89 | close = Some(closer(value, close, *min)); 90 | continue; 91 | } 92 | if value >= *max { 93 | close = Some(closer(value, close, *max)); 94 | continue; 95 | } 96 | let mut v = min + ((value - min) / step).floor() * step; 97 | while v <= *max && v <= value + step { 98 | close = Some(closer(value, close, v)); 99 | v += step; 100 | } 101 | } 102 | } 103 | } 104 | close 105 | } 106 | } 107 | /// Returns the smallest value in [`Range`] that is as big as the given `value` or bigger. 108 | /// Returns `None`, if the [`Range`] is empty or if all values are smaller than the given value. 109 | pub fn at_least(&self, value: f64) -> Option { 110 | fn closer_at_least(target: f64, closest: Option, current: f64) -> Option { 111 | match closest { 112 | Some(c) => { 113 | if (target - current).abs() < (c - target).abs() && current >= target { 114 | Some(current) 115 | } else { 116 | closest 117 | } 118 | } 119 | None => { 120 | if current >= target { 121 | Some(current) 122 | } else { 123 | None 124 | } 125 | } 126 | } 127 | } 128 | 129 | if self.contains(value) { 130 | Some(value) 131 | } else { 132 | let mut close = None; 133 | for i in self.items.iter() { 134 | match i { 135 | RangeItem::Interval(a, b) => { 136 | close = closer_at_least(value, close, *a); 137 | close = closer_at_least(value, close, *b); 138 | } 139 | RangeItem::Value(a) => { 140 | close = closer_at_least(value, close, *a); 141 | } 142 | RangeItem::Step(min, max, step) => { 143 | if value <= *min { 144 | close = closer_at_least(value, close, *min); 145 | continue; 146 | } 147 | if value >= *max { 148 | close = closer_at_least(value, close, *max); 149 | continue; 150 | } 151 | let mut v = min + ((value - min) / step).floor() * step; 152 | while v <= *max && v <= value + step { 153 | close = closer_at_least(value, close, v); 154 | v += step; 155 | } 156 | } 157 | } 158 | } 159 | close 160 | } 161 | } 162 | /// Returns the largest value in [`Range`] that is as big as the given `value` or smaller. 163 | /// Returns `None`, if the [`Range`] is empty or if all values are bigger than the given `value`. 164 | pub fn at_max(&self, value: f64) -> Option { 165 | fn closer_at_max(target: f64, closest: Option, current: f64) -> Option { 166 | match closest { 167 | Some(c) => { 168 | if (target - current).abs() < (c - target).abs() && current <= target { 169 | Some(current) 170 | } else { 171 | closest 172 | } 173 | } 174 | None => { 175 | if current <= target { 176 | Some(current) 177 | } else { 178 | None 179 | } 180 | } 181 | } 182 | } 183 | 184 | if self.contains(value) { 185 | Some(value) 186 | } else { 187 | let mut close = None; 188 | for i in self.items.iter() { 189 | match i { 190 | RangeItem::Interval(a, b) => { 191 | close = closer_at_max(value, close, *a); 192 | close = closer_at_max(value, close, *b); 193 | } 194 | RangeItem::Value(a) => { 195 | close = closer_at_max(value, close, *a); 196 | } 197 | RangeItem::Step(min, max, step) => { 198 | if value <= *min { 199 | close = closer_at_max(value, close, *min); 200 | continue; 201 | } 202 | if value >= *max { 203 | close = closer_at_max(value, close, *max); 204 | continue; 205 | } 206 | let mut v = min + ((value - min) / step).floor() * step; 207 | while v <= *max && v <= value + step { 208 | close = closer_at_max(value, close, v); 209 | v += step; 210 | } 211 | } 212 | } 213 | } 214 | close 215 | } 216 | } 217 | /// Merges two [`Ranges`](Range). 218 | pub fn merge(&mut self, mut r: Range) { 219 | self.items.append(&mut r.items) 220 | } 221 | } 222 | 223 | #[cfg(test)] 224 | mod tests { 225 | use super::*; 226 | 227 | #[test] 228 | fn contains_empty() { 229 | let r = Range::new(Vec::new()); 230 | assert!(!r.contains(123.0)); 231 | } 232 | #[test] 233 | fn contains() { 234 | let r = Range::new(vec![ 235 | RangeItem::Value(123.0), 236 | RangeItem::Interval(23.0, 42.0), 237 | RangeItem::Step(100.0, 110.0, 1.0), 238 | ]); 239 | assert!(r.contains(123.0)); 240 | assert!(r.contains(23.0)); 241 | assert!(r.contains(42.0)); 242 | assert!(r.contains(40.0)); 243 | assert!(r.contains(100.0)); 244 | assert!(r.contains(107.0)); 245 | assert!(r.contains(110.0)); 246 | assert!(!r.contains(19.0)); 247 | } 248 | #[test] 249 | fn closest() { 250 | let r = Range::new(vec![ 251 | RangeItem::Value(123.0), 252 | RangeItem::Interval(23.0, 42.0), 253 | RangeItem::Step(100.0, 110.0, 1.0), 254 | ]); 255 | assert_eq!(r.closest(122.0), Some(123.0)); 256 | assert_eq!(r.closest(1000.0), Some(123.0)); 257 | assert_eq!(r.closest(30.0), Some(30.0)); 258 | assert_eq!(r.closest(20.0), Some(23.0)); 259 | assert_eq!(r.closest(50.0), Some(42.0)); 260 | assert_eq!(r.closest(99.5), Some(100.0)); 261 | assert_eq!(r.closest(105.3), Some(105.0)); 262 | assert_eq!(r.closest(105.8), Some(106.0)); 263 | assert_eq!(r.closest(109.8), Some(110.0)); 264 | assert_eq!(r.closest(113.8), Some(110.0)); 265 | } 266 | #[test] 267 | fn at_least() { 268 | let r = Range::new(vec![ 269 | RangeItem::Value(123.0), 270 | RangeItem::Interval(23.0, 42.0), 271 | RangeItem::Step(100.0, 110.0, 1.0), 272 | ]); 273 | assert_eq!(r.at_least(120.0), Some(123.0)); 274 | assert_eq!(r.at_least(1000.0), None); 275 | assert_eq!(r.at_least(30.0), Some(30.0)); 276 | assert_eq!(r.at_least(10.0), Some(23.0)); 277 | assert_eq!(r.at_least(99.0), Some(100.0)); 278 | assert_eq!(r.at_least(105.5), Some(106.0)); 279 | } 280 | #[test] 281 | fn at_max() { 282 | let r = Range::new(vec![ 283 | RangeItem::Value(123.0), 284 | RangeItem::Interval(23.0, 42.0), 285 | RangeItem::Step(100.0, 110.0, 1.0), 286 | ]); 287 | assert_eq!(r.at_max(90.0), Some(42.0)); 288 | assert_eq!(r.at_max(10.0), None); 289 | assert_eq!(r.at_max(30.0), Some(30.0)); 290 | assert_eq!(r.at_max(50.0), Some(42.0)); 291 | assert_eq!(r.at_max(101.0), Some(101.0)); 292 | assert_eq!(r.at_max(100.3), Some(100.0)); 293 | assert_eq!(r.at_max(111.3), Some(110.0)); 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /src/streamer.rs: -------------------------------------------------------------------------------- 1 | use num_complex::Complex32; 2 | 3 | use crate::Error; 4 | 5 | /// Receive samples from a [Device](crate::Device) through one or multiple channels. 6 | pub trait RxStreamer: Send { 7 | /// Get the stream's maximum transmission unit (MTU) in number of elements. 8 | /// 9 | /// The MTU specifies the maximum payload transfer in a stream operation. 10 | /// This value can be used as a stream buffer allocation size that can 11 | /// best optimize throughput given the underlying stream implementation. 12 | fn mtu(&self) -> Result; 13 | 14 | /// Activate a stream. 15 | /// 16 | /// Call `activate` to enable a stream before using `read()` 17 | fn activate(&mut self) -> Result<(), Error> { 18 | self.activate_at(None) 19 | } 20 | 21 | /// Activate a stream. 22 | /// 23 | /// Call `activate` to enable a stream before using `read()` 24 | /// 25 | /// # Arguments: 26 | /// * `time_ns` -- optional activation time in nanoseconds from the time the function is 27 | /// called. 28 | fn activate_at(&mut self, time_ns: Option) -> Result<(), Error>; 29 | 30 | /// Deactivate a stream. 31 | /// The implementation will control switches or halt data flow. 32 | fn deactivate(&mut self) -> Result<(), Error> { 33 | self.deactivate_at(None) 34 | } 35 | 36 | /// Deactivate a stream. 37 | /// The implementation will control switches or halt data flow. 38 | /// 39 | /// # Arguments: 40 | /// * `time_ns` -- optional deactivation time in nanoseconds from the time the function is 41 | /// called. 42 | fn deactivate_at(&mut self, time_ns: Option) -> Result<(), Error>; 43 | 44 | /// Read samples from the stream into the provided buffers. 45 | /// 46 | /// `buffers` contains one destination slice for each channel of this stream. 47 | /// 48 | /// Returns the number of samples read, which may be smaller than the size of the passed arrays. 49 | /// 50 | /// # Panics 51 | /// * If `buffers` is not the same length as the `channels` array passed to 52 | /// [`Device::rx_streamer`](crate::Device::rx_streamer) that created the streamer. 53 | fn read(&mut self, buffers: &mut [&mut [Complex32]], timeout_us: i64) -> Result; 54 | } 55 | 56 | #[doc(hidden)] 57 | impl RxStreamer for Box { 58 | fn mtu(&self) -> Result { 59 | self.as_ref().mtu() 60 | } 61 | fn activate_at(&mut self, time_ns: Option) -> Result<(), Error> { 62 | self.as_mut().activate_at(time_ns) 63 | } 64 | fn deactivate_at(&mut self, time_ns: Option) -> Result<(), Error> { 65 | self.as_mut().deactivate_at(time_ns) 66 | } 67 | fn read(&mut self, buffers: &mut [&mut [Complex32]], timeout_us: i64) -> Result { 68 | self.as_mut().read(buffers, timeout_us) 69 | } 70 | } 71 | 72 | /// Transmit samples with a [Device](crate::Device) through one or multiple channels. 73 | pub trait TxStreamer: Send { 74 | /// Get the stream's maximum transmission unit (MTU) in number of elements. 75 | /// 76 | /// The MTU specifies the maximum payload transfer in a stream operation. 77 | /// This value can be used as a stream buffer allocation size that can 78 | /// best optimize throughput given the underlying stream implementation. 79 | fn mtu(&self) -> Result; 80 | 81 | /// Activate a stream. 82 | /// 83 | /// Call `activate` to enable a stream before using `write()` 84 | fn activate(&mut self) -> Result<(), Error> { 85 | self.activate_at(None) 86 | } 87 | 88 | /// Activate a stream. 89 | /// 90 | /// Call `activate` to enable a stream before using `write()` 91 | /// 92 | /// # Arguments: 93 | /// * `time_ns` -- optional activation time in nanoseconds from the time the function is 94 | /// called. 95 | fn activate_at(&mut self, time_ns: Option) -> Result<(), Error>; 96 | 97 | /// Deactivate a stream. 98 | /// The implementation will control switches or halt data flow. 99 | fn deactivate(&mut self) -> Result<(), Error> { 100 | self.deactivate_at(None) 101 | } 102 | 103 | /// Deactivate a stream. 104 | /// The implementation will control switches or halt data flow. 105 | /// 106 | /// # Arguments: 107 | /// * `time_ns` -- optional deactivation time in nanoseconds from the time the function is 108 | /// called 109 | fn deactivate_at(&mut self, time_ns: Option) -> Result<(), Error>; 110 | 111 | /// Attempt to write samples to the device from the provided buffer. 112 | /// 113 | /// The stream must first be [activated](TxStreamer::activate). 114 | /// 115 | /// `buffers` contains one source slice for each channel of the stream. 116 | /// 117 | /// `at_ns` is an optional nanosecond precision device timestamp relative to the time the 118 | /// function is called at which the device will begin the transmission. 119 | /// 120 | /// `end_burst` indicates the end of a burst transmission. 121 | /// 122 | /// Returns the number of samples written, which may be smaller than the size of the passed arrays. 123 | /// 124 | /// # Panics 125 | /// * If `buffers` are not the same length as the `channels` array passed to [`Device::tx_streamer`](crate::Device::tx_streamer). 126 | /// * If the buffers in `buffers` are not the same length. 127 | fn write( 128 | &mut self, 129 | buffers: &[&[Complex32]], 130 | at_ns: Option, 131 | end_burst: bool, 132 | timeout_us: i64, 133 | ) -> Result; 134 | 135 | /// Write all samples to the device. 136 | /// 137 | /// This method repeatedly calls [write](TxStreamer::write) until the entire provided buffer has 138 | /// been written. 139 | /// 140 | /// The stream must first be [activated](TxStreamer::activate). 141 | /// 142 | /// `buffers` contains one source slice for each channel of the stream. 143 | /// 144 | /// `at_ns` is an optional nanosecond precision device timestamp relative to the time the 145 | /// function is called at which the device will begin the transmission. 146 | /// 147 | /// `end_burst` indicates the end of a burst transmission. 148 | /// 149 | /// # Panics 150 | /// * If `buffers` are not the same length as the `channels` array passed to [`Device::tx_streamer`](crate::Device::tx_streamer). 151 | /// * If the buffers in `buffers` are not the same length. 152 | fn write_all( 153 | &mut self, 154 | buffers: &[&[Complex32]], 155 | at_ns: Option, 156 | end_burst: bool, 157 | timeout_us: i64, 158 | ) -> Result<(), Error>; 159 | } 160 | 161 | #[doc(hidden)] 162 | impl TxStreamer for Box { 163 | fn mtu(&self) -> Result { 164 | self.as_ref().mtu() 165 | } 166 | fn activate_at(&mut self, time_ns: Option) -> Result<(), Error> { 167 | self.as_mut().activate_at(time_ns) 168 | } 169 | fn deactivate_at(&mut self, time_ns: Option) -> Result<(), Error> { 170 | self.as_mut().deactivate_at(time_ns) 171 | } 172 | fn write( 173 | &mut self, 174 | buffers: &[&[Complex32]], 175 | at_ns: Option, 176 | end_burst: bool, 177 | timeout_us: i64, 178 | ) -> Result { 179 | self.as_mut().write(buffers, at_ns, end_burst, timeout_us) 180 | } 181 | fn write_all( 182 | &mut self, 183 | buffers: &[&[Complex32]], 184 | at_ns: Option, 185 | end_burst: bool, 186 | timeout_us: i64, 187 | ) -> Result<(), Error> { 188 | self.as_mut() 189 | .write_all(buffers, at_ns, end_burst, timeout_us) 190 | } 191 | } 192 | --------------------------------------------------------------------------------