diff --git a/.gitmodules b/.gitmodules index a219b25..549cf26 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "crates/rtl-sdr-rs"] path = crates/rtl-sdr-rs url = https://github.com/bastibl/rtl-sdr-rs.git +[submodule "crates/seify-hackrfone"] + path = crates/seify-hackrfone + url = https://github.com/MerchGuardian/seify-hackrfone.git diff --git a/Cargo.toml b/Cargo.toml index fe93251..13acac9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,9 +9,10 @@ license = "Apache-2.0" repository = "https://github.com/FutureSDR/seify" [features] -default = ["soapy"] +# default = ["soapy"] +default = ["hackrfone"] rtlsdr = ["dep:seify-rtlsdr"] -hackrfone = ["dep:seify-hackrfone", "dep:nusb"] +hackrfone = ["dep:seify-hackrfone"] aaronia = ["dep:aaronia-rtsa"] aaronia_http = ["dep:ureq"] soapy = ["dep:soapysdr"] @@ -25,7 +26,6 @@ futures = "0.3" log = "0.4" nom = "7.1" num-complex = "0.4" -nusb = { git = "https://github.com/kevinmehall/nusb.git", optional = true } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_with = "3.6" @@ -34,7 +34,7 @@ thiserror = "1.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] once_cell = "1.19" seify-rtlsdr = { path = "crates/rtl-sdr-rs", version = "0.0.3", optional = true } -seify-hackrfone = { path = "crates/hackrf-one-rs", optional = true } +seify-hackrfone = { path = "crates/seify-hackrfone", version = "0.1.0", optional = true } soapysdr = { version = "0.4", optional = true } ureq = { version = "2.9", features = ["json"], optional = true } diff --git a/crates/hackrf-one-rs/.gitignore b/crates/hackrf-one-rs/.gitignore deleted file mode 100644 index ea8c4bf..0000000 --- a/crates/hackrf-one-rs/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target diff --git a/crates/hackrf-one-rs/Cargo.lock b/crates/hackrf-one-rs/Cargo.lock deleted file mode 100644 index bc32bd0..0000000 --- a/crates/hackrf-one-rs/Cargo.lock +++ /dev/null @@ -1,446 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "anyhow" -version = "1.0.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "atomic_enum" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e1aca718ea7b89985790c94aad72d77533063fe00bc497bb79a7c2dae6a661" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "autocfg" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" - -[[package]] -name = "bitflags" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "errno" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "fastrand" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" - -[[package]] -name = "futures-core" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" - -[[package]] -name = "futures-io" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" - -[[package]] -name = "futures-lite" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - -[[package]] -name = "io-kit-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b" -dependencies = [ - "core-foundation-sys", - "mach2", -] - -[[package]] -name = "libc" -version = "0.2.158" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" - -[[package]] -name = "linux-raw-sys" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" - -[[package]] -name = "log" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" - -[[package]] -name = "mach2" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" -dependencies = [ - "libc", -] - -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "nusb" -version = "0.1.10" -source = "git+https://github.com/kevinmehall/nusb.git#809228038f43de2d7a636825d17439b445f612b7" -dependencies = [ - "atomic-waker", - "core-foundation", - "core-foundation-sys", - "futures-core", - "io-kit-sys", - "libc", - "log", - "once_cell", - "rustix", - "slab", - "windows-sys 0.48.0", -] - -[[package]] -name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "parking" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" - -[[package]] -name = "pin-project-lite" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" - -[[package]] -name = "proc-macro2" -version = "1.0.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rustix" -version = "0.38.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.52.0", -] - -[[package]] -name = "seify-hackrfone" -version = "0.1.0" -dependencies = [ - "anyhow", - "atomic_enum", - "futures-lite", - "num-complex", - "nusb", - "thiserror", - "tracing", -] - -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - -[[package]] -name = "syn" -version = "2.0.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "thiserror" -version = "1.0.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing" -version = "0.1.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" -dependencies = [ - "once_cell", -] - -[[package]] -name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/crates/hackrf-one-rs/Cargo.toml b/crates/hackrf-one-rs/Cargo.toml deleted file mode 100644 index 31fe579..0000000 --- a/crates/hackrf-one-rs/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "seify-hackrfone" -version = "0.1.0" -edition = "2021" -authors = ["Troy Neubauer "] - -[dependencies] -num-complex = "0.4" -thiserror = "1.0.63" -atomic_enum = "0.3.0" -nusb = { git = "https://github.com/kevinmehall/nusb.git" } -futures-lite = "2.3.0" -tracing = "0.1.40" - -[dev-dependencies] -anyhow = "1.0.86" diff --git a/crates/hackrf-one-rs/examples/info.rs b/crates/hackrf-one-rs/examples/info.rs deleted file mode 100644 index 03e300a..0000000 --- a/crates/hackrf-one-rs/examples/info.rs +++ /dev/null @@ -1,11 +0,0 @@ -use anyhow::{Context, Result}; -use seify_hackrfone::HackRf; - -fn main() -> Result<()> { - let radio = HackRf::open_first().context("Failed to open Hackrf")?; - - println!("Board ID: {:?}", radio.board_id()); - println!("Version: {:?}", radio.version()); - println!("Device version: {:?}", radio.device_version()); - Ok(()) -} diff --git a/crates/hackrf-one-rs/examples/rx.rs b/crates/hackrf-one-rs/examples/rx.rs deleted file mode 100644 index 9efdf97..0000000 --- a/crates/hackrf-one-rs/examples/rx.rs +++ /dev/null @@ -1,57 +0,0 @@ -use anyhow::{Context, Result}; -use num_complex::Complex32; -use seify_hackrfone::{Config, HackRf}; -use std::time::Instant; - -fn main() -> Result<()> { - let radio = HackRf::open_first().context("Failed to open Hackrf")?; - - println!("Board ID: {:?}", radio.board_id()); - println!("Version: {:?}", radio.version()); - println!("Device version: {:?}", radio.device_version()); - - radio - .start_rx(&Config { - vga_db: 0, - txvga_db: 0, - lna_db: 0, - power_port_enable: false, - antenna_enable: false, - frequency_hz: 915_000_000, - sample_rate_hz: 2_000_000, - sample_rate_div: 0, - }) - .context("Failed to receive on hackrf")?; - - const MTU: usize = 128 * 1024; - let mut buf = vec![0u8; MTU]; - let mut samples = vec![]; - - let collect_count = 100_000_000; - let mut last_print = Instant::now(); - - while samples.len() < collect_count { - let n = radio.read(&mut buf).context("Failed to receive samples")?; - assert_eq!(n, buf.len()); - for iq in buf.chunks_exact(2) { - samples.push(Complex32::new( - (iq[0] as f32 - 127.0) / 128.0, - (iq[1] as f32 - 127.0) / 128.0, - )); - } - - if last_print.elapsed().as_millis() > 500 { - println!( - " read {} samples ({:.1}%)", - samples.len(), - samples.len() as f64 / collect_count as f64 * 100.0 - ); - last_print = Instant::now(); - } - } - println!("Collected {} samples", samples.len()); - - println!("First 100 {:#?} samples", &samples[..100]); - - Ok(()) -} diff --git a/crates/hackrf-one-rs/src/lib.rs b/crates/hackrf-one-rs/src/lib.rs deleted file mode 100644 index e4a80d1..0000000 --- a/crates/hackrf-one-rs/src/lib.rs +++ /dev/null @@ -1,863 +0,0 @@ -#![deny(unsafe_code)] - -//! HackRF One API. -//! -//! To get started take a look at [`HackRf::open_first`]. -#![cfg_attr(docsrs, feature(doc_cfg), feature(doc_auto_cfg))] -// TODO(tjn): re-enable -// #![warn(missing_docs)] - -use std::{ - sync::atomic::{AtomicU64, Ordering}, - time::Duration, -}; - -use futures_lite::future::block_on; -use nusb::{ - transfer::{ControlIn, ControlOut, ControlType, Queue, Recipient, RequestBuffer}, - DeviceInfo, -}; - -/// HackRF USB vendor ID. -const HACKRF_USB_VID: u16 = 0x1D50; -/// HackRF One USB product ID. -const HACKRF_ONE_USB_PID: u16 = 0x6089; - -#[allow(dead_code)] -#[repr(u8)] -enum Request { - SetTransceiverMode = 1, - Max2837Write = 2, - Max2837Read = 3, - Si5351CWrite = 4, - Si5351CRead = 5, - SampleRateSet = 6, - BasebandFilterBandwidthSet = 7, - Rffc5071Write = 8, - Rffc5071Read = 9, - SpiflashErase = 10, - SpiflashWrite = 11, - SpiflashRead = 12, - BoardIdRead = 14, - VersionStringRead = 15, - SetFreq = 16, - AmpEnable = 17, - BoardPartidSerialnoRead = 18, - SetLnaGain = 19, - SetVgaGain = 20, - SetTxvgaGain = 21, - AntennaEnable = 23, - SetFreqExplicit = 24, - UsbWcidVendorReq = 25, - InitSweep = 26, - OperacakeGetBoards = 27, - OperacakeSetPorts = 28, - SetHwSyncMode = 29, - Reset = 30, - OperacakeSetRanges = 31, - ClkoutEnable = 32, - SpiflashStatus = 33, - SpiflashClearStatus = 34, - OperacakeGpioTest = 35, - CpldChecksum = 36, - UiEnable = 37, -} - -#[allow(dead_code)] -#[repr(u16)] -enum TransceiverMode { - Off = 0, - Receive = 1, - Transmit = 2, - Ss = 3, - CpldUpdate = 4, - RxSweep = 5, -} - -#[atomic_enum::atomic_enum] -#[derive(PartialEq)] -pub enum Mode { - Idle = 0, - Receive, - Transmit, -} - -/// Configurable parameters on the hackrf -#[derive(Debug)] -pub struct Config { - /// Baseband gain, 0-62dB in 2dB increments (rx only) - pub vga_db: u16, - /// 0 - 47 dB in 1dB increments (tx only) - pub txvga_db: u16, - - /// Low-noise amplifier gain, in 0-40dB in 8dB increments (rx only) - // Pre baseband receive - pub lna_db: u16, - /// RF amplifier (on/off) - pub amp_enable: bool, - - /// Antenna power port control - // Power enable on antenna - pub antenna_enable: bool, - /// Frequency in hz - pub frequency_hz: u64, - pub sample_rate_hz: u32, - // TODO: provide helpers for setting this up - pub sample_rate_div: u32, -} - -impl Config { - pub fn tx_default() -> Self { - Self { - vga_db: 0, - lna_db: 0, - txvga_db: 40, - amp_enable: false, - antenna_enable: false, - frequency_hz: 908_000_000, - sample_rate_hz: 2_500_000, - sample_rate_div: 1, - } - } - - pub fn rx_default() -> Self { - Self { - vga_db: 24, - lna_db: 0, - txvga_db: 0, - amp_enable: false, - antenna_enable: false, - frequency_hz: 908_000_000, - sample_rate_hz: 2_500_000, - sample_rate_div: 1, - } - } -} - -/// HackRF One errors. -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("io")] - Io(#[from] std::io::Error), - #[error("transfer")] - Transfer(#[from] nusb::transfer::TransferError), - #[error("transfer truncated")] - TransferTruncated { - /// Actual amount of bytes transferred. - actual: usize, - /// Excepted number of bytes transferred. - expected: usize, - }, - /// An API call is not supported by your hardware. - /// - /// Try updating the firmware on your device. - #[error("no api")] - NoApi { - /// Current device version. - device: UsbVersion, - /// Minimum version required. - min: UsbVersion, - }, - #[error("{0}")] - Argument(&'static str), - #[error("Hackrf in invalid mode. Required: {required:?}, actual: {actual:?}")] - WrongMode { required: Mode, actual: Mode }, - #[error("Device not found")] - NotFound, -} - -pub type Result = std::result::Result; - -/// A three-part version consisting of major, minor, and sub minor components. -/// -/// The intended use case of `Version` is to extract meaning from the version fields in USB -/// descriptors, such as `bcdUSB` and `bcdDevice` in device descriptors. -#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)] -// Taken from rusb::Version: https://github.com/a1ien/rusb/blob/8f8c3c6bff6a494a140da4d93dd946bf1e564d66/src/fields.rs#L142-L203 -// nusb doesnt currently have this -pub struct UsbVersion(pub u8, pub u8, pub u8); - -impl UsbVersion { - /// Extracts a version from a binary coded decimal (BCD) field. BCD fields exist in USB - /// descriptors as 16-bit integers encoding a version as `0xJJMN`, where `JJ` is the major - /// version, `M` is the minor version, and `N` is the sub minor version. For example, 2.0 is - /// encoded as `0x0200` and 1.1 is encoded as `0x0110`. - pub fn from_bcd(mut raw: u16) -> Self { - let sub_minor: u8 = (raw & 0x000F) as u8; - raw >>= 4; - - let minor: u8 = (raw & 0x000F) as u8; - raw >>= 4; - - let mut major: u8 = (raw & 0x000F) as u8; - raw >>= 4; - - major += (10 * raw) as u8; - - UsbVersion(major, minor, sub_minor) - } - - /// Returns the major version. - pub fn major(self) -> u8 { - let UsbVersion(major, _, _) = self; - major - } - - /// Returns the minor version. - pub fn minor(self) -> u8 { - let UsbVersion(_, minor, _) = self; - minor - } - - /// Returns the sub minor version. - pub fn sub_minor(self) -> u8 { - let UsbVersion(_, _, sub_minor) = self; - sub_minor - } -} - -impl std::fmt::Display for UsbVersion { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}.{}.{}", self.major(), self.minor(), self.sub_minor()) - } -} - -/// HackRF One software defined radio. -pub struct HackRf { - interface: nusb::Interface, - version: UsbVersion, - mode: AtomicMode, - timeout_nanos: AtomicU64, -} - -impl HackRf { - /// Wraps an existing nusb::Device. - /// Useful on platform like Android where you are forced to use [`nusb::Device::from_fd`]. - pub fn wrap(device: nusb::Device) -> Result { - let interface = device.claim_interface(0)?; - - return Ok(HackRf { - interface, - // TODO: Actually read version, dont assume latest - version: UsbVersion::from_bcd(0x0102), - timeout_nanos: AtomicU64::new(Duration::from_millis(500).as_nanos() as u64), - mode: AtomicMode::new(Mode::Idle), - }); - } - - /// Opens `info` based on the result of a `nusb` scan. - fn open(info: DeviceInfo) -> Result { - let device = info.open()?; - let interface = device.claim_interface(0)?; - - return Ok(HackRf { - interface, - version: UsbVersion::from_bcd(info.device_version()), - timeout_nanos: AtomicU64::new(Duration::from_millis(500).as_nanos() as u64), - mode: AtomicMode::new(Mode::Idle), - }); - } - - /// Opens the first Hackrf One found via USB. - pub fn open_first() -> Result { - for device in nusb::list_devices()? { - if device.vendor_id() == HACKRF_USB_VID && device.product_id() == HACKRF_ONE_USB_PID { - match Self::open(device) { - Ok(dev) => return Ok(dev), - Err(_) => continue, - } - } - } - - Err(Error::NotFound) - } - - /// Scans the usb bus for hackrf devices, returning the pair of (bus_num, bus_addr) for each - /// device. - pub fn scan() -> Result> { - let mut res = vec![]; - for device in nusb::list_devices()? { - if device.vendor_id() == HACKRF_USB_VID && device.product_id() == HACKRF_ONE_USB_PID { - res.push((device.busnum(), device.device_address())); - } - } - Ok(res) - } - - /// Opens a hackrf with usb address `:
` - pub fn open_bus(bus_number: u8, address: u8) -> Result { - for device in nusb::list_devices()? { - if device.vendor_id() == HACKRF_USB_VID - && device.product_id() == HACKRF_ONE_USB_PID - && device.busnum() == bus_number - && device.device_address() == address - { - return Self::open(device); - } - } - - Err(Error::NotFound) - } - - /// Resets the HackRf. - pub fn reset(self) -> Result<()> { - self.check_api_version(UsbVersion::from_bcd(0x0102))?; - self.write_control(Request::Reset, 0, 0, &[])?; - - Ok(()) - } - - pub fn device_version(&self) -> UsbVersion { - self.version - } - - pub fn board_id(&self) -> Result { - let data: [u8; 1] = self.read_control(Request::BoardIdRead, 0, 0)?; - Ok(data[0]) - } - - /// Read the firmware version. - pub fn version(&self) -> Result { - let buf = block_on(self.interface.control_in(ControlIn { - control_type: ControlType::Vendor, - recipient: Recipient::Device, - request: Request::VersionStringRead as u8, - value: 0x0, - index: 0x0, - length: 64, - })) - .into_result()?; - - Ok(String::from_utf8_lossy(&buf).into()) - } - - /// Transitions the radio into transmit mode. - /// Call this function before calling [`Self::write`]. - /// - /// Previous state set via `set_xxx` functions will be overriden with the parameters set in `config`. - /// - /// # Errors - /// This function will return an error if a tx or rx operation is already in progress or if an - /// I/O error occurs - pub fn start_tx(&self, config: &Config) -> Result<()> { - tracing::info!("Starting tx: {config:?}"); - - // NOTE: perform atomic exchange first so that we only change the transceiver mode once if - // other threads are racing to change with us - if let Err(actual) = self.mode.compare_exchange( - Mode::Idle, - Mode::Transmit, - Ordering::AcqRel, - Ordering::Relaxed, - ) { - return Err(Error::WrongMode { - required: Mode::Idle, - actual, - }); - } - - self.apply_config(config)?; - - self.write_control( - Request::SetTransceiverMode, - TransceiverMode::Transmit as u16, - 0, - &[], - )?; - - Ok(()) - } - - /// Transitions the radio into receive mode. - /// Call this function before calling [`Self::read`]. - /// - /// Previous state set via `set_xxx` functions will be overriden with the parameters set in `config`. - /// - /// # Errors - /// This function will return an error if a tx or rx operation is already in progress or if an - /// I/O error occurs - pub fn start_rx(&self, config: &Config) -> Result<()> { - // NOTE: perform atomic exchange first so that we only change the transceiver mode once if - // other threads are racing to change with us - if let Err(actual) = self.mode.compare_exchange( - Mode::Idle, - Mode::Receive, - Ordering::AcqRel, - Ordering::Relaxed, - ) { - return Err(Error::WrongMode { - required: Mode::Idle, - actual, - }); - } - - self.apply_config(config)?; - - self.write_control( - Request::SetTransceiverMode, - TransceiverMode::Receive as u16, - 0, - &[], - )?; - - Ok(()) - } - - pub fn stop_tx(&self) -> Result<()> { - // NOTE: perform atomic exchange last so that we prevent other threads from racing to - // start tx/rx with the delivery of our TransceiverMode::Off request - // - // This means if multiple threads call stop_tx/stop_rx concurrently the hackrf may receive - // multiple TransceiverMode::Off requests, but will always end up in a valid state with the - // transceiver disabled. - // - // Adding something like Mode::IdlePending would solve this, - // however quickly this begins to look like a manually implemented mutex. - // - // To keep this crate low-level and low-overhead, this solution is fine and we expect - // consumers to wrap our type in an Arc and be smart enough to not enable / disable tx / rx - // from multiple threads at the same time on a single duplex radio. - - self.write_control( - Request::SetTransceiverMode, - TransceiverMode::Off as u16, - 0, - &[], - )?; - - if let Err(actual) = self.mode.compare_exchange( - Mode::Transmit, - Mode::Idle, - Ordering::AcqRel, - Ordering::Relaxed, - ) { - return Err(Error::WrongMode { - required: Mode::Transmit, - actual, - }); - } - - Ok(()) - } - - pub fn stop_rx(&self) -> Result<()> { - // NOTE: same as above - perform atomic exchange last - - self.write_control( - Request::SetTransceiverMode, - TransceiverMode::Off as u16, - 0, - &[], - )?; - - if let Err(actual) = self.mode.compare_exchange( - Mode::Receive, - Mode::Idle, - Ordering::AcqRel, - Ordering::Relaxed, - ) { - return Err(Error::WrongMode { - required: Mode::Receive, - actual, - }); - } - - Ok(()) - } - - /// Read samples from the radio. - /// - /// # Panics - /// This function panics if samples is not a multiple of 512 - pub fn read(&self, samples: &mut [u8]) -> Result { - self.ensure_mode(Mode::Receive)?; - - if samples.len() % 512 != 0 { - panic!("samples must be a multiple of 512"); - } - - const ENDPOINT: u8 = 0x81; - let buf = block_on( - self.interface - .bulk_in(ENDPOINT, RequestBuffer::new(samples.len())), - ) - .into_result()?; - samples[..buf.len()].copy_from_slice(&buf); - - Ok(buf.len()) - } - - /// Writes samples to the radio. - /// - /// # Panics - /// This function panics if samples is not a multiple of 512 - pub fn write(&self, samples: &[u8]) -> Result { - self.ensure_mode(Mode::Transmit)?; - - if samples.len() % 512 != 0 { - panic!("samples must be a multiple of 512"); - } - - const ENDPOINT: u8 = 0x02; - let buf = Vec::from(samples); - // TODO: dont allocate - let n = block_on(self.interface.bulk_out(ENDPOINT, buf)).into_result()?; - - Ok(n.actual_length()) - } - - /// Setup the device to stream samples. - pub fn start_rx_stream(&self, transfer_size: usize) -> Result { - if transfer_size % 512 != 0 { - panic!("transfer_size must be a multiple of 512"); - } - - const ENDPOINT: u8 = 0x81; - Ok(RxStream { - queue: self.interface.bulk_in_queue(ENDPOINT), - in_flight_transfers: 3, - transfer_size, - buf_pos: transfer_size, - buf: vec![0u8; transfer_size], - }) - } -} - -pub struct RxStream { - queue: Queue, - in_flight_transfers: usize, - transfer_size: usize, - buf_pos: usize, - buf: Vec, -} - -impl RxStream { - /// Read samples from the device, blocking until more are available. - pub fn read_sync(&mut self, count: usize) -> Result<&[u8]> { - let buffered_remaining = self.buf.len() - self.buf_pos; - if buffered_remaining > 0 { - let to_consume = std::cmp::min(count, buffered_remaining); - let ret = &self.buf[self.buf_pos..self.buf_pos + to_consume]; - self.buf_pos += ret.len(); - return Ok(ret); - } - - while self.queue.pending() < self.in_flight_transfers { - self.queue.submit(RequestBuffer::new(self.transfer_size)); - } - let completion = block_on(self.queue.next_complete()); - - if let Err(e) = completion.status { - return Err(e.into()); - } - - let reuse = std::mem::replace(&mut self.buf, completion.data); - self.buf_pos = 0; - - self.queue - .submit(RequestBuffer::reuse(reuse, self.transfer_size)); - - // bytes are now buffered, use tail recursion for code above to return subslice - self.read_sync(count) - } -} - -impl HackRf { - fn apply_config(&self, config: &Config) -> Result<()> { - self.set_lna_gain(config.lna_db)?; - self.set_vga_gain(config.vga_db)?; - self.set_txvga_gain(config.txvga_db)?; - self.set_freq(config.frequency_hz)?; - self.set_amp_enable(config.amp_enable)?; - self.set_antenna_enable(config.antenna_enable)?; - self.set_sample_rate(config.sample_rate_hz, config.sample_rate_div)?; - - Ok(()) - } - - fn ensure_mode(&self, expected: Mode) -> Result<()> { - let actual = self.mode.load(Ordering::Acquire); - if actual != expected { - return Err(Error::WrongMode { - required: expected, - actual, - }); - } - Ok(()) - } - - pub fn timeout(&self) -> Duration { - Duration::from_nanos(self.timeout_nanos.load(Ordering::Acquire)) - } - - fn read_control( - &self, - request: Request, - value: u16, - index: u16, - ) -> Result<[u8; N]> { - let mut res: [u8; N] = [0; N]; - let buf = block_on(self.interface.control_in(ControlIn { - control_type: ControlType::Vendor, - recipient: Recipient::Device, - request: request as u8, - value, - index, - length: N as u16, - })) - .into_result()?; - - if buf.len() != N { - return Err(Error::TransferTruncated { - actual: buf.len(), - expected: N, - }); - } - - res.copy_from_slice(&buf); - Ok(res) - } - - fn write_control(&self, request: Request, value: u16, index: u16, buf: &[u8]) -> Result<()> { - let out = block_on(self.interface.control_out(ControlOut { - control_type: ControlType::Vendor, - recipient: Recipient::Device, - request: request as u8, - value, - index, - data: buf, - })) - .into_result()?; - - if out.actual_length() != buf.len() { - Err(Error::TransferTruncated { - actual: out.actual_length(), - expected: buf.len(), - }) - } else { - Ok(()) - } - } - - fn check_api_version(&self, min: UsbVersion) -> Result<()> { - fn version_to_u32(v: UsbVersion) -> u32 { - ((v.major() as u32) << 16) | ((v.minor() as u32) << 8) | (v.sub_minor() as u32) - } - - if version_to_u32(self.version) >= version_to_u32(min) { - Ok(()) - } else { - Err(Error::NoApi { - device: self.version, - min, - }) - } - } - - /// Set the center frequency. - pub fn set_freq(&self, hz: u64) -> Result<()> { - let buf: [u8; 8] = freq_params(hz); - self.write_control(Request::SetFreq, 0, 0, &buf) - } - - /// Enable the RX/TX RF amplifier. - /// - /// In GNU radio this is used as the RF gain, where a value of 0 dB is off, - /// and a value of 14 dB is on. - pub fn set_amp_enable(&self, enable: bool) -> Result<()> { - self.write_control(Request::AmpEnable, enable.into(), 0, &[]) - } - - /// Set the baseband filter bandwidth. - /// - /// This is automatically set when the sample rate is changed with - /// [`Self::set_sample_rate`]. - pub fn set_baseband_filter_bandwidth(&self, hz: u32) -> Result<()> { - self.write_control( - Request::BasebandFilterBandwidthSet, - (hz & 0xFFFF) as u16, - (hz >> 16) as u16, - &[], - ) - } - - /// Set the sample rate. - /// - /// For anti-aliasing, the baseband filter bandwidth is automatically set to - /// the widest available setting that is no more than 75% of the sample rate. - /// This happens every time the sample rate is set. - /// If you want to override the baseband filter selection, you must do so - /// after setting the sample rate. - /// - /// Limits are 8MHz - 20MHz. - /// Preferred rates are 8, 10, 12.5, 16, 20MHz due to less jitter. - pub fn set_sample_rate(&self, hz: u32, div: u32) -> Result<()> { - let hz: u32 = hz.to_le(); - let div: u32 = div.to_le(); - let buf: [u8; 8] = [ - (hz & 0xFF) as u8, - ((hz >> 8) & 0xFF) as u8, - ((hz >> 16) & 0xFF) as u8, - ((hz >> 24) & 0xFF) as u8, - (div & 0xFF) as u8, - ((div >> 8) & 0xFF) as u8, - ((div >> 16) & 0xFF) as u8, - ((div >> 24) & 0xFF) as u8, - ]; - self.write_control(Request::SampleRateSet, 0, 0, &buf)?; - self.set_baseband_filter_bandwidth((0.75 * (hz as f32) / (div as f32)) as u32) - } - - /// Set the LNA (low noise amplifier) gain. - /// - /// Range 0 to 40dB in 8dB steps. - /// - /// This is also known as the IF gain. - pub fn set_lna_gain(&self, gain: u16) -> Result<()> { - if gain > 40 { - Err(Error::Argument("lna gain must be less than 40")) - } else { - let buf: [u8; 1] = self.read_control(Request::SetLnaGain, 0, gain & !0x07)?; - if buf[0] == 0 { - panic!("Unexpected return value from read_control(SetLnaGain)"); - } else { - Ok(()) - } - } - } - - /// Set the VGA (variable gain amplifier) gain. - /// - /// Range 0 to 62dB in 2dB steps. - /// - /// This is also known as the baseband (BB) gain. - pub fn set_vga_gain(&self, gain: u16) -> Result<()> { - if gain > 62 || gain % 2 == 1 { - Err(Error::Argument( - "gain parameter out of range. must be even and less than or equal to 62", - )) - } else { - let buf: [u8; 1] = self.read_control(Request::SetVgaGain, 0, gain & !0b1)?; - if buf[0] == 0 { - panic!("What is this return value?") - } else { - Ok(()) - } - } - } - - /// Set the transmit VGA gain. - /// - /// Range 0 to 47dB in 1db steps. - pub fn set_txvga_gain(&self, gain: u16) -> Result<()> { - if gain > 47 { - Err(Error::Argument("gain parameter out of range. max is 47")) - } else { - let buf: [u8; 1] = self.read_control(Request::SetTxvgaGain, 0, gain)?; - if buf[0] == 0 { - panic!("What is this return value?") - } else { - Ok(()) - } - } - } - - /// Antenna power port control. Dhruv's guess: is this DC bias? - /// - /// The source docs are a little lacking in terms of explanations here. - pub fn set_antenna_enable(&self, value: bool) -> Result<()> { - let value = if value { 1 } else { 0 }; - self.write_control(Request::AntennaEnable, value, 0, &[]) - } -} - -// Helper for set_freq -fn freq_params(hz: u64) -> [u8; 8] { - const MHZ: u64 = 1_000_000; - - let l_freq_mhz: u32 = u32::try_from(hz / MHZ).unwrap_or(u32::MAX).to_le(); - let l_freq_hz: u32 = u32::try_from(hz - u64::from(l_freq_mhz) * MHZ) - .unwrap_or(u32::MAX) - .to_le(); - - [ - (l_freq_mhz & 0xFF) as u8, - ((l_freq_mhz >> 8) & 0xFF) as u8, - ((l_freq_mhz >> 16) & 0xFF) as u8, - ((l_freq_mhz >> 24) & 0xFF) as u8, - (l_freq_hz & 0xFF) as u8, - ((l_freq_hz >> 8) & 0xFF) as u8, - ((l_freq_hz >> 16) & 0xFF) as u8, - ((l_freq_hz >> 24) & 0xFF) as u8, - ] -} - -#[cfg(test)] -mod test { - use std::time::Duration; - - use super::*; - - #[test] - fn test_freq_params() { - assert_eq!(freq_params(915_000_000), [0x93, 0x03, 0, 0, 0, 0, 0, 0]); - assert_eq!(freq_params(915_000_001), [0x93, 0x03, 0, 0, 1, 0, 0, 0]); - assert_eq!( - freq_params(123456789), - [0x7B, 0, 0, 0, 0x55, 0xF8, 0x06, 0x00] - ); - - assert_eq!(freq_params(0), [0; 8]); - - assert_eq!(freq_params(u64::MAX), [0xFF; 8]); - } - - // NOTE: make sure you can transmit on the frequency below before enabling! - // #[test] - #[allow(dead_code)] - fn device_states() { - let radio = HackRf::open_first().expect("Failed to open hackrf"); - - radio - .start_tx(&Config { - vga_db: 0, - txvga_db: 0, - lna_db: 0, - amp_enable: false, - antenna_enable: false, - frequency_hz: 915_000_000, - sample_rate_hz: 2_000_000, - sample_rate_div: 1, - }) - .unwrap(); - std::thread::sleep(Duration::from_millis(50)); - - radio.stop_tx().unwrap(); - assert!(radio.stop_tx().is_err()); - assert!(radio.stop_tx().is_err()); - assert!(radio.stop_rx().is_err()); - assert!(radio.stop_rx().is_err()); - - std::thread::sleep(Duration::from_millis(50)); - - radio - .start_rx(&Config { - vga_db: 0, - txvga_db: 0, - lna_db: 0, - amp_enable: false, - antenna_enable: false, - frequency_hz: 915_000_000, - sample_rate_hz: 2_000_000, - sample_rate_div: 1, - }) - .unwrap(); - std::thread::sleep(Duration::from_millis(50)); - - radio.stop_rx().unwrap(); - assert!(radio.stop_rx().is_err()); - assert!(radio.stop_rx().is_err()); - assert!(radio.stop_tx().is_err()); - assert!(radio.stop_tx().is_err()); - } -} diff --git a/crates/seify-hackrfone b/crates/seify-hackrfone new file mode 160000 index 0000000..9f08ce5 --- /dev/null +++ b/crates/seify-hackrfone @@ -0,0 +1 @@ +Subproject commit 9f08ce5cf59492f484eedb02ae1fcc1af354f196 diff --git a/examples/rx_generic.rs b/examples/rx_generic.rs index a16dd78..7caddf6 100644 --- a/examples/rx_generic.rs +++ b/examples/rx_generic.rs @@ -21,7 +21,10 @@ pub fn main() -> Result<(), Box> { // Get typed reference to device impl // let r: &seify::impls::RtlSdr = dev.impl_ref().unwrap(); - dev.enable_agc(Rx, 0, true)?; + // HackRf doesnt support agc + if dev.supports_agc(Rx, 0)? { + dev.enable_agc(Rx, 0, true)?; + } dev.set_frequency(Rx, 0, 927e6)?; dev.set_sample_rate(Rx, 0, 3.2e6)?; diff --git a/src/impls/hackrfone.rs b/src/impls/hackrfone.rs index 993c278..d9d50a0 100644 --- a/src/impls/hackrfone.rs +++ b/src/impls/hackrfone.rs @@ -35,14 +35,11 @@ impl HackRfOne { let args: Args = args.try_into().or(Err(Error::ValueError))?; if let Ok(fd) = args.get::("fd") { - log::info!("Wrapping hackrf fd={fd}"); - // SAFETY: the caller intends to pass ownership to us let fd = unsafe { OwnedFd::from_raw_fd(fd) }; - let dev = nusb::Device::from_fd(fd)?; return Ok(Self { inner: Arc::new(HackRfInner { - dev: seify_hackrfone::HackRf::wrap(dev)?, + dev: seify_hackrfone::HackRf::from_fd(fd)?, tx_config: Mutex::new(Config::tx_default()), rx_config: Mutex::new(Config::rx_default()), }), @@ -60,7 +57,7 @@ impl HackRfOne { seify_hackrfone::HackRf::open_first()? } (bus_number, address) => { - info::warn!("HackRfOne::open received invalid args: bus_number: {bus_number:?}, address: {address:?}"); + log::warn!("HackRfOne::open received invalid args: bus_number: {bus_number:?}, address: {address:?}"); return Err(Error::ValueError); } };