#![cfg_attr(docsrs, feature(doc_cfg), feature(doc_auto_cfg))]
#![warn(missing_docs)]
pub use rusb;
use rusb::{Direction, GlobalContext, Recipient, RequestType, UsbContext, Version, request_type};
use std::time::Duration;
#[cfg(feature = "num-complex")]
pub use num_complex;
const HACKRF_USB_VID: u16 = 0x1D50;
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,
}
impl From<Request> for u8 {
fn from(r: Request) -> Self {
r as u8
}
}
#[allow(dead_code)]
#[repr(u8)]
enum TranscieverMode {
Off = 0,
Receive = 1,
Transmit = 2,
Ss = 3,
CpldUpdate = 4,
RxSweep = 5,
}
impl From<TranscieverMode> for u8 {
fn from(tm: TranscieverMode) -> Self {
tm as u8
}
}
impl From<TranscieverMode> for u16 {
fn from(tm: TranscieverMode) -> Self {
tm as u16
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Error {
Usb(rusb::Error),
CtrlTransfer {
dir: Direction,
actual: usize,
expected: usize,
},
Version {
device: Version,
min: Version,
},
Argument,
}
impl From<rusb::Error> for Error {
fn from(e: rusb::Error) -> Self {
Error::Usb(e)
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
}
}
impl std::error::Error for Error {}
#[derive(Debug)]
pub struct RxMode;
#[derive(Debug)]
pub struct UnknownMode;
pub struct HackRfOne<MODE> {
dh: rusb::DeviceHandle<GlobalContext>,
desc: rusb::DeviceDescriptor,
#[allow(dead_code)]
mode: MODE,
to: Duration,
}
impl HackRfOne<UnknownMode> {
pub fn new() -> Option<HackRfOne<UnknownMode>> {
let ctx: GlobalContext = GlobalContext {};
let devices = match ctx.devices() {
Ok(d) => d,
Err(_) => return None,
};
for device in devices.iter() {
let desc = match device.device_descriptor() {
Ok(d) => d,
Err(_) => continue,
};
if desc.vendor_id() == HACKRF_USB_VID && desc.product_id() == HACKRF_ONE_USB_PID {
match device.open() {
Ok(handle) => {
return Some(HackRfOne {
dh: handle,
desc,
mode: UnknownMode,
to: Duration::from_secs(1),
});
}
Err(_) => continue,
}
}
}
None
}
}
impl<MODE> HackRfOne<MODE> {
fn read_control<const N: usize>(
&self,
request: Request,
value: u16,
index: u16,
) -> Result<[u8; N], Error> {
let mut buf: [u8; N] = [0; N];
let n: usize = self.dh.read_control(
request_type(Direction::In, RequestType::Vendor, Recipient::Device),
request.into(),
value,
index,
&mut buf,
self.to,
)?;
if n != buf.len() {
Err(Error::CtrlTransfer {
dir: Direction::In,
actual: n,
expected: buf.len(),
})
} else {
Ok(buf)
}
}
fn write_control(
&mut self,
request: Request,
value: u16,
index: u16,
buf: &[u8],
) -> Result<(), Error> {
let n: usize = self.dh.write_control(
request_type(Direction::Out, RequestType::Vendor, Recipient::Device),
request.into(),
value,
index,
buf,
self.to,
)?;
if n != buf.len() {
Err(Error::CtrlTransfer {
dir: Direction::Out,
actual: n,
expected: buf.len(),
})
} else {
Ok(())
}
}
fn check_api_version(&self, min: Version) -> Result<(), Error> {
fn version_to_u32(v: Version) -> u32 {
((v.major() as u32) << 16) | ((v.minor() as u32) << 8) | (v.sub_minor() as u32)
}
let v: Version = self.device_version();
let v_cmp: u32 = version_to_u32(v);
let min_cmp: u32 = version_to_u32(min);
if v_cmp >= min_cmp {
Ok(())
} else {
Err(Error::Version { device: v, min })
}
}
pub fn device_version(&self) -> Version {
self.desc.device_version()
}
pub fn set_timeout(&mut self, duration: Duration) {
self.to = duration;
}
pub fn board_id(&self) -> Result<u8, Error> {
let data: [u8; 1] = self.read_control(Request::BoardIdRead, 0, 0)?;
Ok(data[0])
}
pub fn version(&self) -> Result<String, Error> {
let mut buf: [u8; 16] = [0; 16];
let n: usize = self.dh.read_control(
request_type(Direction::In, RequestType::Vendor, Recipient::Device),
Request::VersionStringRead.into(),
0,
0,
&mut buf,
self.to,
)?;
Ok(String::from_utf8_lossy(&buf[0..n]).into())
}
pub fn set_freq(&mut self, hz: u64) -> Result<(), Error> {
let buf: [u8; 8] = freq_params(hz);
self.write_control(Request::SetFreq, 0, 0, &buf)
}
pub fn set_amp_enable(&mut self, en: bool) -> Result<(), Error> {
self.write_control(Request::AmpEnable, en.into(), 0, &[])
}
pub fn set_baseband_filter_bandwidth(&mut self, hz: u32) -> Result<(), Error> {
self.write_control(
Request::BasebandFilterBandwidthSet,
(hz & 0xFFFF) as u16,
(hz >> 16) as u16,
&[],
)
}
pub fn set_sample_rate(&mut self, hz: u32, div: u32) -> Result<(), Error> {
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)
}
pub fn set_lna_gain(&mut self, gain: u16) -> Result<(), Error> {
if gain > 40 {
Err(Error::Argument)
} else {
let buf: [u8; 1] = self.read_control(Request::SetLnaGain, 0, gain & !0x07)?;
if buf[0] == 0 {
Err(Error::Argument)
} else {
Ok(())
}
}
}
pub fn set_vga_gain(&mut self, gain: u16) -> Result<(), Error> {
if gain > 62 {
Err(Error::Argument)
} else {
let buf: [u8; 1] = self.read_control(Request::SetVgaGain, 0, gain & !0b1)?;
if buf[0] == 0 {
Err(Error::Argument)
} else {
Ok(())
}
}
}
pub fn set_txvga_gain(&mut self, gain: u16) -> Result<(), Error> {
if gain > 47 {
Err(Error::Argument)
} else {
let buf: [u8; 1] = self.read_control(Request::SetTxvgaGain, 0, gain)?;
if buf[0] == 0 {
Err(Error::Argument)
} else {
Ok(())
}
}
}
pub fn set_antenna_enable(&mut self, value: u8) -> Result<(), Error> {
self.write_control(Request::AntennaEnable, value.into(), 0, &[])
}
pub fn set_clkout_enable(&mut self, en: bool) -> Result<(), Error> {
self.check_api_version(Version::from_bcd(0x0103))?;
self.write_control(Request::ClkoutEnable, en.into(), 0, &[])
}
pub fn reset(mut self) -> Result<HackRfOne<UnknownMode>, Error> {
self.check_api_version(Version::from_bcd(0x0102))?;
self.write_control(Request::Reset, 0, 0, &[])?;
Ok(HackRfOne {
dh: self.dh,
desc: self.desc,
mode: UnknownMode,
to: self.to,
})
}
fn set_transceiver_mode(&mut self, mode: TranscieverMode) -> Result<(), Error> {
self.write_control(Request::SetTransceiverMode, mode.into(), 0, &[])
}
pub fn into_rx_mode(mut self) -> Result<HackRfOne<RxMode>, Error> {
self.set_transceiver_mode(TranscieverMode::Receive)?;
self.dh.claim_interface(0)?;
Ok(HackRfOne {
dh: self.dh,
desc: self.desc,
mode: RxMode,
to: self.to,
})
}
}
impl HackRfOne<RxMode> {
#[cfg_attr(not(feature = "num-complex"), allow(rustdoc::broken_intra_doc_links))]
pub fn rx(&mut self) -> Result<Vec<u8>, Error> {
const ENDPOINT: u8 = 0x81;
const MTU: usize = 128 * 1024;
let mut buf: Vec<u8> = vec![0; MTU];
let n: usize = self.dh.read_bulk(ENDPOINT, &mut buf, self.to)?;
buf.truncate(n);
Ok(buf)
}
pub fn stop_rx(mut self) -> Result<HackRfOne<UnknownMode>, Error> {
self.dh.release_interface(0)?;
self.set_transceiver_mode(TranscieverMode::Off)?;
Ok(HackRfOne {
dh: self.dh,
desc: self.desc,
mode: UnknownMode,
to: self.to,
})
}
}
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 freq_params {
use super::freq_params;
#[test]
fn nominal() {
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]
);
}
#[test]
fn min() {
assert_eq!(freq_params(0), [0; 8]);
}
#[test]
fn max() {
assert_eq!(freq_params(u64::MAX), [0xFF; 8]);
}
}
#[cfg(feature = "num-complex")]
pub fn iq_to_cplx_i8(i: u8, q: u8) -> num_complex::Complex<i8> {
num_complex::Complex::new(i as i8, q as i8)
}
#[cfg(feature = "num-complex")]
pub fn iq_to_cplx_f32(i: u8, q: u8) -> num_complex::Complex<f32> {
num_complex::Complex::new(i as i8 as f32, q as i8 as f32)
}