waverave_hackrf/
lib.rs

1/*!
2
3This is a complete, strongly-asynchronous host crate for the [HackRF][hackrf],
4made using the pure-rust [`nusb`] crate for USB interfacing.
5
6[hackrf]: https://greatscottgadgets.com/hackrf/one/
7
8The standard entry point for this library is [`open_hackrf()`], which will open
9the first available HackRF device.
10
11Getting started is easy: open up a HackRF peripheral, configure it as needed,
12and enter into transmit, receive, or RX sweep mode. Changing the oeprating mode
13also changes the struct used, i.e. it uses the typestate pattern. The different
14states and their corresponding structs are:
15
16- [`HackRf`] - The default, off, state.
17- [`Receive`] - Receiving RF signals.
18- [`Transmit`] - Transmitting RF signals.
19- [`Sweep`] - Running a receive sweep through multiple tuning frequencies.
20
21If a mode change error occurs, the [`HackRf`] struct is returned alongside the
22error, and it can potentially be reset back to the off state by running
23[`HackRf::turn_off`].
24
25As for what using this library looks like in practice, here's an example program
26that configures the system, enters receive mode, and processes samples to
27estimate the average received power relative to full scale:
28
29```no_run
30use anyhow::Result;
31#[tokio::main]
32async fn main() -> Result<()> {
33    let hackrf = waverave_hackrf::open_hackrf()?;
34
35    // Configure: 20MHz sample rate, turn on the RF amp, set IF & BB gains to 16 dB,
36    // and tune to 915 MHz.
37    hackrf.set_sample_rate(20e6).await?;
38    hackrf.set_amp_enable(true).await?;
39    hackrf.set_lna_gain(16).await?;
40    hackrf.set_vga_gain(16).await?;
41    hackrf.set_freq(915_000_000).await?;
42
43    // Start receiving, in bursts of 16384 samples
44    let mut hackrf_rx = hackrf.start_rx(16384).await.map_err(|e| e.err)?;
45
46    // Queue up 64 transfers, retrieve them, and measure average power.
47    for _ in 0..64 {
48        hackrf_rx.submit();
49    }
50    let mut count = 0;
51    let mut pow_sum = 0.0;
52    while hackrf_rx.pending() > 0 {
53        let buf = hackrf_rx.next_complete().await?;
54        for x in buf.samples() {
55            let re = x.re as f64;
56            let im = x.im as f64;
57            pow_sum += re * re + im * im;
58        }
59        count += buf.len();
60    }
61
62    // Stop receiving
63    hackrf_rx.stop().await?;
64
65    // Print out our measurement
66    let average_power = (pow_sum / (count as f64 * 127.0 * 127.0)).log10() * 10.;
67    println!("Average Power = {average_power} dbFS");
68    Ok(())
69}
70
71```
72
73*/
74
75#![warn(missing_docs)]
76
77mod consts;
78pub mod debug;
79mod error;
80pub mod info;
81mod rx;
82mod sweep;
83mod tx;
84use std::ops::Range;
85
86use bytemuck::Pod;
87use core::mem::size_of;
88use nusb::transfer::{ControlIn, ControlOut, ControlType, Recipient};
89use std::sync::mpsc;
90
91use crate::consts::*;
92use crate::debug::Debug;
93use crate::info::Info;
94
95pub use crate::error::{Error, StateChangeError};
96pub use crate::rx::Receive;
97pub use crate::sweep::{Sweep, SweepBuf, SweepMode, SweepParams};
98pub use crate::tx::Transmit;
99
100/// Complex 8-bit signed data, as used by the HackRF.
101pub type ComplexI8 = num_complex::Complex<i8>;
102
103/// Operacake port A1
104pub const PORT_A1: u8 = 0;
105/// Operacake port A2
106pub const PORT_A2: u8 = 1;
107/// Operacake port A3
108pub const PORT_A3: u8 = 2;
109/// Operacake port A4
110pub const PORT_A4: u8 = 3;
111/// Operacake port B1
112pub const PORT_B1: u8 = 4;
113/// Operacake port B2
114pub const PORT_B2: u8 = 5;
115/// Operacake port B3
116pub const PORT_B3: u8 = 6;
117/// Operacake port B4
118pub const PORT_B4: u8 = 7;
119
120/// A Buffer holding HackRF transfer data.
121///
122/// Samples can be directly accessed as slices, and can be extended up to the
123/// length of the fixed-size underlying buffer.
124///
125/// When dropped, this buffer returns to the internal buffer pool it came from.
126/// It can either be backed by an allocation from the system allocator, or by
127/// some platform-specific way of allocating memory for zero-copy USB transfers.
128pub struct Buffer {
129    buf: Vec<u8>,
130    pool: mpsc::Sender<Vec<u8>>,
131}
132
133impl Buffer {
134    pub(crate) fn new(buf: Vec<u8>, pool: mpsc::Sender<Vec<u8>>) -> Self {
135        assert!(buf.len() & 0x1FF == 0);
136        Self { buf, pool }
137    }
138
139    pub(crate) fn into_vec(mut self) -> Vec<u8> {
140        core::mem::take(&mut self.buf)
141    }
142
143    /// Get how many samples this buffer can hold.
144    pub fn capacity(&self) -> usize {
145        // Force down to the nearest 512-byte boundary, which is the transfer
146        // size the HackRF requires.
147        (self.buf.capacity() & !0x1FF) / size_of::<ComplexI8>()
148    }
149
150    /// Clear out the buffer's samples.
151    pub fn clear(&mut self) {
152        self.buf.clear();
153    }
154
155    /// Size of the buffer, in samples.
156    pub fn len(&self) -> usize {
157        self.buf.len() / size_of::<ComplexI8>()
158    }
159
160    /// Returns true if there are no samples in the buffer.
161    pub fn is_empty(&self) -> bool {
162        self.buf.is_empty()
163    }
164
165    /// Remaining capacity in the buffer, in samples.
166    pub fn remaining_capacity(&self) -> usize {
167        self.capacity() - self.len()
168    }
169
170    /// Extend the buffer with some number of samples set to 0, and get a
171    /// mutable slice to the newly initialized samples.
172    ///
173    /// # Panics
174    /// - If there is not enough space left for the added samples.
175    pub fn extend_zeros(&mut self, len: usize) -> &mut [ComplexI8] {
176        assert!(self.remaining_capacity() >= len);
177        let old_len = self.buf.len();
178        let new_len = old_len + len * size_of::<ComplexI8>();
179        self.buf.resize(new_len, 0);
180        let buf: &mut [u8] = &mut self.buf;
181        // SAFETY: We only ever resize according to the size of a ComplexI8,
182        // the buffer always holds ComplexI8 internally, and ComplexI8 has an
183        // alignment of 1.
184        unsafe {
185            core::slice::from_raw_parts_mut(
186                buf.as_mut_ptr().add(old_len) as *mut ComplexI8,
187                len / size_of::<ComplexI8>(),
188            )
189        }
190    }
191
192    /// Extend the buffer with a slice of samples.
193    ///
194    /// # Panics
195    /// - If there is no space left in the buffer for the slice.
196    pub fn extend_from_slice(&mut self, slice: &[ComplexI8]) {
197        assert!(self.remaining_capacity() >= slice.len());
198        // SAFETY: We can always cast a ComplexI8 to bytes, as it meets all the
199        // "plain old data" requirements.
200        let slice = unsafe {
201            core::slice::from_raw_parts(slice.as_ptr() as *const u8, core::mem::size_of_val(slice))
202        };
203        self.buf.extend_from_slice(slice);
204    }
205
206    /// Push a value onto the buffer.
207    ///
208    /// # Panics
209    /// - If there is no space left in the buffer.
210    pub fn push(&mut self, val: ComplexI8) {
211        assert!(self.remaining_capacity() > 0);
212        let slice: &[u8; 2] = unsafe { &*((&val) as *const ComplexI8 as *const [u8; 2]) };
213        self.buf.extend_from_slice(slice);
214    }
215
216    /// Get the sample sequence as a slice of bytes instead of complex values.
217    pub fn bytes(&self) -> &[u8] {
218        &self.buf
219    }
220
221    /// Get the sample sequence as a mutable slice of bytes instead of complex values.
222    pub fn bytes_mut(&mut self) -> &mut [u8] {
223        &mut self.buf
224    }
225
226    /// Get the samples in the buffer.
227    pub fn samples(&self) -> &[ComplexI8] {
228        let buf: &[u8] = &self.buf;
229        // SAFETY: the buffer is aligned because `ComplexI8` has an alignment of
230        // 1, same as a byte buffer, the data is valid, and we truncate to only
231        // valid populated pairs. Also we shouldn't ever have a byte buffer that
232        // isn't an even number of bytes anyway...
233        unsafe {
234            core::slice::from_raw_parts(
235                buf.as_ptr() as *const ComplexI8,
236                self.buf.len() / size_of::<ComplexI8>(),
237            )
238        }
239    }
240
241    /// Mutably get the samples in the buffer.
242    pub fn samples_mut(&mut self) -> &mut [ComplexI8] {
243        let buf: &mut [u8] = &mut self.buf;
244        // SAFETY: the buffer is aligned because `ComplexI8` has an alignment of
245        // 1, same as a byte buffer, the data is valid, and we truncate to only
246        // valid populated pairs. Also we shouldn't ever have a byte buffer that
247        // isn't an even number of bytes anyway...
248        unsafe {
249            core::slice::from_raw_parts_mut(
250                buf.as_mut_ptr() as *mut ComplexI8,
251                self.buf.len() / size_of::<ComplexI8>(),
252            )
253        }
254    }
255}
256
257impl Drop for Buffer {
258    fn drop(&mut self) {
259        let inner = core::mem::take(&mut self.buf);
260        if inner.capacity() > 0 {
261            let _ = self.pool.send(inner);
262        }
263    }
264}
265
266/// Configuration settings for the Bias-T switch.
267///
268/// Used when calling [`HackRf::set_user_bias_t_opts`].
269#[derive(Clone, Debug)]
270pub struct BiasTSetting {
271    /// What mode change to apply when switching to transmit.
272    pub tx: BiasTMode,
273    /// What mode change to apply when switching to receive.
274    pub rx: BiasTMode,
275    /// What mode change to apply when switching off.
276    pub off: BiasTMode,
277}
278
279/// A Bias-T setting change to apply on a mode change.
280///
281/// See [`BiasTSetting`] for where to use this.
282#[allow(missing_docs)]
283#[derive(Clone, Copy, Debug, PartialEq, Eq)]
284pub enum BiasTMode {
285    NoChange,
286    Enable,
287    Disable,
288}
289
290impl BiasTMode {
291    fn as_u16(self) -> u16 {
292        match self {
293            Self::NoChange => 0x0,
294            Self::Disable => 0x2,
295            Self::Enable => 0x3,
296        }
297    }
298}
299
300/// RF Filter Setting Option.
301///
302/// Use when calling [`HackRf::set_freq_explicit`].
303#[repr(u8)]
304#[derive(Clone, Copy, Debug, PartialEq, Eq)]
305pub enum RfPathFilter {
306    /// No filter selected - mixer bypassed.
307    Bypass = 0,
308    /// Low pass filter, `f_c = f_IF - f_LO`
309    LowPass = 1,
310    /// High pass filter, `f_c = f_IF + f_LO`
311    HighPass = 2,
312}
313
314impl std::fmt::Display for RfPathFilter {
315    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
316        match self {
317            Self::Bypass => f.write_str("mixer bypass"),
318            Self::LowPass => f.write_str("low pass filter"),
319            Self::HighPass => f.write_str("high pass filter"),
320        }
321    }
322}
323
324/// Configuration for an Operacake board.
325///
326/// An Operacake board has three different operating modes:
327///
328/// - Manual: the switches are manually set and don't change until the next
329///   configuration operation.
330/// - Frequency: the switches change depending on the center frequency the board
331///   is tuned to.
332/// - Time: the switches change after some number of samples have been
333///   sent/received.
334///
335/// Use when calling [`HackRf::operacake_set_mode`].
336#[derive(Clone, Copy, Debug, PartialEq, Eq)]
337#[repr(u16)]
338#[allow(missing_docs)]
339pub enum OperacakeMode {
340    Manual = 0,
341    Frequency = 1,
342    Time = 2,
343}
344
345/// A Frequency band allocated to a specific port for all Operacakes operating
346/// in frequency mode.
347///
348/// This is used in [`HackRf::operacake_config_freq`].
349///
350/// Ports are zero-indexed, but can also be referred to with the top-level
351/// constants:
352/// - PORT_A1 = 0
353/// - PORT_A2 = 1
354/// - PORT_A3 = 2
355/// - PORT_A4 = 3
356/// - PORT_B1 = 4
357/// - PORT_B2 = 5
358/// - PORT_B3 = 6
359/// - PORT_B4 = 7
360#[derive(Clone, Copy, Debug)]
361pub struct OperacakeFreq {
362    /// Start frequency, in MHz.
363    pub min: u16,
364    /// Stop frequency, in MHz.
365    pub max: u16,
366    /// Port for A0 to use for the range. B0 will use the mirror image.
367    pub port: u8,
368}
369
370/// A dwell time allocated to a specific port for all Operacakes operating in
371/// dwell time mode.
372///
373/// This is used in [`HackRf::operacake_config_time`].
374///
375/// Ports are zero-indexed, but can also be referred to with the top-level
376/// constants:
377/// - PORT_A1 = 0
378/// - PORT_A2 = 1
379/// - PORT_A3 = 2
380/// - PORT_A4 = 3
381/// - PORT_B1 = 4
382/// - PORT_B2 = 5
383/// - PORT_B3 = 6
384/// - PORT_B4 = 7
385#[derive(Clone, Copy, Debug)]
386pub struct OperacakeDwell {
387    /// Dwell time, in number of samples
388    pub dwell: u32,
389    /// Port for A0 to use for the range. B0 will use the mirror image.
390    pub port: u8,
391}
392
393/// A HackRF device descriptor, which can be opened.
394///
395/// These are mostly returned from calling [`list_hackrf_devices`], but can also
396/// be formed by trying to convert a [`nusb::DeviceInfo`] into one.
397pub struct HackRfDescriptor {
398    info: nusb::DeviceInfo,
399}
400
401/// The type of HackRF device that was detected.
402#[allow(missing_docs)]
403#[derive(Clone, Copy, Debug, PartialEq, Eq)]
404pub enum HackRfType {
405    Jawbreaker,
406    One,
407    Rad1o,
408}
409
410impl std::fmt::Display for HackRfType {
411    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
412        match self {
413            Self::Jawbreaker => f.write_str("Jawbreaker"),
414            Self::One => f.write_str("HackRF One"),
415            Self::Rad1o => f.write_str("rad1o"),
416        }
417    }
418}
419
420impl HackRfDescriptor {
421    /// Get the serial number of this HackRF, as a string.
422    pub fn serial(&self) -> Option<&str> {
423        self.info.serial_number()
424    }
425
426    /// Get the [type][HackRfType] of HackRF radio this is.
427    pub fn radio_type(&self) -> HackRfType {
428        match self.info.product_id() {
429            HACKRF_JAWBREAKER_USB_PID => HackRfType::Jawbreaker,
430            HACKRF_ONE_USB_PID => HackRfType::One,
431            RAD1O_USB_PID => HackRfType::Rad1o,
432            _ => panic!("Created a HackRfDescriptor without using a known product ID"),
433        }
434    }
435
436    /// Try and open this HackRf device descriptor.
437    pub fn open(self) -> Result<HackRf, std::io::Error> {
438        let version = self.info.device_version();
439        let ty = self.radio_type();
440        let device = self.info.open()?;
441        #[cfg(not(target_os = "windows"))]
442        {
443            if device.active_configuration()?.configuration_value() != 1 {
444                device.detach_kernel_driver(0)?;
445                device.set_configuration(1)?;
446            }
447        }
448        let interface = device.detach_and_claim_interface(0)?;
449
450        let (buf_pool_send, buf_pool) = mpsc::channel();
451        let tx = TxEndpoint {
452            queue: interface.bulk_out_queue(TX_ENDPOINT_ADDRESS),
453            buf_pool,
454            buf_pool_send,
455        };
456        let (buf_pool_send, buf_pool) = mpsc::channel();
457        let rx = RxEndpoint {
458            queue: interface.bulk_in_queue(RX_ENDPOINT_ADDRESS),
459            buf_pool,
460            buf_pool_send,
461        };
462
463        Ok(HackRf {
464            interface,
465            version,
466            ty,
467            rx,
468            tx,
469        })
470    }
471}
472
473/// Try and turn any [`nusb::DeviceInfo`] descriptor into a HackRF, failing if
474/// the VID and PID don't match any known devices.
475impl TryFrom<nusb::DeviceInfo> for HackRfDescriptor {
476    type Error = &'static str;
477    fn try_from(value: nusb::DeviceInfo) -> Result<Self, Self::Error> {
478        if value.vendor_id() == HACKRF_USB_VID {
479            if matches!(
480                value.product_id(),
481                HACKRF_JAWBREAKER_USB_PID | HACKRF_ONE_USB_PID | RAD1O_USB_PID
482            ) {
483                Ok(HackRfDescriptor { info: value })
484            } else {
485                Err("VID recognized, PID not recognized")
486            }
487        } else {
488            Err("VID doesn't match for HackRF")
489        }
490    }
491}
492
493/// List all available HackRF devices.
494pub fn list_hackrf_devices() -> Result<Vec<HackRfDescriptor>, std::io::Error> {
495    Ok(nusb::list_devices()?
496        .filter(|d| {
497            d.vendor_id() == HACKRF_USB_VID
498                && matches!(
499                    d.product_id(),
500                    HACKRF_JAWBREAKER_USB_PID | HACKRF_ONE_USB_PID | RAD1O_USB_PID
501                )
502        })
503        .map(|d| HackRfDescriptor { info: d })
504        .collect::<Vec<HackRfDescriptor>>())
505}
506
507/// Open the first detected HackRF device in the system.
508///
509/// This is a shortcut for calling [`list_hackrf_devices`] and opening the first one.
510pub fn open_hackrf() -> Result<HackRf, std::io::Error> {
511    list_hackrf_devices()?
512        .into_iter()
513        .next()
514        .ok_or_else(|| std::io::Error::other("No HackRF devices"))?
515        .open()
516}
517
518/// A HackRF device. This is the main struct for talking to the HackRF.
519///
520/// This provides all the settings to actively configure the HackRF while it is
521/// off, as well as the ability to use debug or info fetching operations with
522/// the [`HackRf::info`] and [`HackRf::debug`] functions. Some of these
523/// operations are also exposed while receiving & transmitting, if it makes
524/// sense to do so.
525pub struct HackRf {
526    pub(crate) interface: nusb::Interface,
527    pub(crate) version: u16,
528    pub(crate) ty: HackRfType,
529    pub(crate) rx: RxEndpoint,
530    pub(crate) tx: TxEndpoint,
531}
532
533struct RxEndpoint {
534    queue: nusb::transfer::Queue<nusb::transfer::RequestBuffer>,
535    buf_pool: mpsc::Receiver<Vec<u8>>,
536    buf_pool_send: mpsc::Sender<Vec<u8>>,
537}
538
539struct TxEndpoint {
540    queue: nusb::transfer::Queue<Vec<u8>>,
541    buf_pool: mpsc::Receiver<Vec<u8>>,
542    buf_pool_send: mpsc::Sender<Vec<u8>>,
543}
544
545impl HackRf {
546    fn api_check(&self, needed: u16) -> Result<(), Error> {
547        if self.version < needed {
548            Err(Error::ApiVersion {
549                needed,
550                actual: self.version,
551            })
552        } else {
553            Ok(())
554        }
555    }
556
557    async fn write_u32(&self, req: ControlRequest, val: u32) -> Result<(), Error> {
558        Ok(self
559            .interface
560            .control_out(ControlOut {
561                control_type: ControlType::Vendor,
562                recipient: Recipient::Device,
563                request: req as u8,
564                value: (val & 0xffff) as u16,
565                index: (val >> 16) as u16,
566                data: &[],
567            })
568            .await
569            .status?)
570    }
571
572    async fn write_u16(&self, req: ControlRequest, idx: u16, val: u16) -> Result<(), Error> {
573        Ok(self
574            .interface
575            .control_out(ControlOut {
576                control_type: ControlType::Vendor,
577                recipient: Recipient::Device,
578                request: req as u8,
579                value: val,
580                index: idx,
581                data: &[],
582            })
583            .await
584            .status?)
585    }
586
587    async fn read_u16(&self, req: ControlRequest, idx: u16) -> Result<u16, Error> {
588        let ret = self
589            .interface
590            .control_in(ControlIn {
591                control_type: ControlType::Vendor,
592                recipient: Recipient::Device,
593                request: req as u8,
594                value: 0,
595                index: idx,
596                length: 2,
597            })
598            .await
599            .into_result()?;
600        let ret: [u8; 2] = ret.as_slice().try_into().map_err(|_| Error::ReturnData)?;
601        Ok(u16::from_le_bytes(ret))
602    }
603
604    async fn write_u8(&self, req: ControlRequest, idx: u16, val: u8) -> Result<(), Error> {
605        self.write_u16(req, idx, val as u16).await?;
606        Ok(())
607    }
608
609    async fn read_u8(&self, req: ControlRequest, idx: u16) -> Result<u8, Error> {
610        let ret = self
611            .interface
612            .control_in(ControlIn {
613                control_type: ControlType::Vendor,
614                recipient: Recipient::Device,
615                request: req as u8,
616                value: 0,
617                index: idx,
618                length: 1,
619            })
620            .await
621            .into_result()?;
622        ret.first().copied().ok_or(Error::ReturnData)
623    }
624
625    async fn write_bytes(&self, req: ControlRequest, data: &[u8]) -> Result<(), Error> {
626        self.interface
627            .control_out(ControlOut {
628                control_type: ControlType::Vendor,
629                recipient: Recipient::Device,
630                request: req as u8,
631                value: 0,
632                index: 0,
633                data,
634            })
635            .await
636            .into_result()?;
637        Ok(())
638    }
639
640    async fn read_bytes(&self, req: ControlRequest, len: usize) -> Result<Vec<u8>, Error> {
641        assert!(len < u16::MAX as usize);
642        Ok(self
643            .interface
644            .control_in(ControlIn {
645                control_type: ControlType::Vendor,
646                recipient: Recipient::Device,
647                request: req as u8,
648                value: 0,
649                index: 0,
650                length: len as u16,
651            })
652            .await
653            .into_result()?)
654    }
655
656    async fn read_struct<T>(&self, req: ControlRequest) -> Result<T, Error>
657    where
658        T: Pod,
659    {
660        let size = size_of::<T>();
661        let mut resp = self.read_bytes(req, size).await?;
662        if resp.len() < size {
663            return Err(Error::ReturnData);
664        }
665        resp.truncate(size);
666        Ok(bytemuck::pod_read_unaligned(&resp))
667    }
668
669    async fn set_transceiver_mode(&self, mode: TransceiverMode) -> Result<(), Error> {
670        self.write_u16(ControlRequest::SetTransceiverMode, 0, mode as u16)
671            .await
672    }
673
674    /// Set the baseband filter bandwidth.
675    ///
676    /// The possible settings are: 1.75, 2.5, 3.5, 5, 5.5, 6, 7, 8, 9, 10, 12,
677    /// 14, 15, 20, 24, and 28 MHz. This function will choose the nearest,
678    /// rounded down.
679    ///
680    /// The default is to set this to 3/4 of the sample rate, rounded down to
681    /// the nearest setting.
682    ///
683    /// Setting the sample rate with [`set_sample_rate`][Self::set_sample_rate]
684    /// will modify this setting.
685    pub async fn set_baseband_filter_bandwidth(&self, bandwidth_hz: u32) -> Result<(), Error> {
686        let bandwidth_hz = baseband_filter_bw(bandwidth_hz);
687        self.write_u32(ControlRequest::BasebandFilterBandwidthSet, bandwidth_hz)
688            .await
689    }
690
691    /// Set the transmit underrun limit. This will cause the HackRF to stop
692    /// operation if transmit runs out of samples to send. Set to 0 to disable.
693    ///
694    /// This will also cause all outstanding transmits to stall forever, so some
695    /// timeout will need to be added to the transmit completion futures.
696    pub async fn set_tx_underrun_limit(&self, val: u32) -> Result<(), Error> {
697        self.api_check(0x0106)?;
698        self.write_u32(ControlRequest::SetTxUnderrunLimit, val)
699            .await
700    }
701
702    /// Set the receive overrun limit. This will cause the HackRF to stop
703    /// operation if more than the specified amount of samples get lost. Set to
704    /// 0 to disable.
705    ///
706    /// This will also cause all outstanding receives to stall forever, so some
707    /// timeout will need to be added to the receive completion futures.
708    pub async fn set_rx_overrun_limit(&self, val: u32) -> Result<(), Error> {
709        self.api_check(0x0106)?;
710        self.write_u32(ControlRequest::SetRxOverrunLimit, val).await
711    }
712
713    /// Access the debug/programming commands for the HackRF.
714    pub fn debug(&mut self) -> Debug<'_> {
715        Debug::new(self)
716    }
717
718    /// Access the info commands for the HackRF.
719    pub fn info(&self) -> Info<'_> {
720        Info::new(self)
721    }
722
723    /// Set the operating frequency (recommended method).
724    ///
725    /// This uses the internal frequency tuning code onboard the HackRF, which
726    /// can differ between boards. It automatically sets the LO and IF
727    /// frequencies, as well as the RF path filter.
728    pub async fn set_freq(&self, freq_hz: u64) -> Result<(), Error> {
729        const ONE_MHZ: u64 = 1_000_000;
730        #[repr(C)]
731        #[derive(Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
732        struct FreqParams {
733            mhz: u32,
734            hz: u32,
735        }
736        let mhz = freq_hz / ONE_MHZ;
737        let hz = freq_hz % ONE_MHZ;
738        let params = FreqParams {
739            mhz: (mhz as u32).to_le(),
740            hz: (hz as u32).to_le(),
741        };
742
743        self.write_bytes(ControlRequest::SetFreq, bytemuck::bytes_of(&params))
744            .await
745    }
746
747    /// Set the IF & LO tuning frequencies, and the RF path filter.
748    ///
749    /// You may be looking for [`set_freq`][HackRf::set_freq] instead.
750    ///
751    /// This sets the center frequency to `f_c = f_IF + k * f_LO`, where k is
752    /// -1, 0, or 1 depending on the filter selected.
753    ///
754    /// IF frequency *must* be between 2-3 GHz, and it's strongly recommended to
755    /// be between 2170-2740 MHz.
756    ///
757    /// LO frequency must be between 84.375-5400 MHz. No effect if the filter is
758    /// set to bypass mode.
759    pub async fn set_freq_explicit(
760        &self,
761        if_freq_hz: u64,
762        lo_freq_hz: u64,
763        path: RfPathFilter,
764    ) -> Result<(), Error> {
765        #[repr(C)]
766        #[derive(Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
767        struct FreqParams {
768            if_freq_hz: u64,
769            lo_freq_hz: u64,
770            path: u8,
771            reserved: [u8; 7],
772        }
773
774        const IF_RANGE: Range<u64> = Range {
775            start: 2_000_000_000,
776            end: 3_000_000_001,
777        };
778        const LO_RANGE: Range<u64> = Range {
779            start: 84_375_000,
780            end: 5_400_000_001,
781        };
782
783        if !IF_RANGE.contains(&if_freq_hz) {
784            return Err(Error::TuningRange {
785                range: IF_RANGE,
786                val: if_freq_hz,
787            });
788        }
789        if path != RfPathFilter::Bypass && !LO_RANGE.contains(&lo_freq_hz) {
790            return Err(Error::TuningRange {
791                range: LO_RANGE,
792                val: lo_freq_hz,
793            });
794        }
795
796        let params = FreqParams {
797            if_freq_hz: if_freq_hz.to_le(),
798            lo_freq_hz: lo_freq_hz.to_le(),
799            path: path as u8,
800            reserved: [0u8; 7],
801        };
802
803        self.write_bytes(ControlRequest::SetFreqExplicit, bytemuck::bytes_of(&params))
804            .await
805    }
806
807    /// Set the sample rate using a clock frequency in Hz and a divider value.
808    ///
809    /// The resulting sample rate is `freq_hz/divider`. Divider value can be
810    /// 1-31, and the rate range should be 2-20MHz. Lower & higher values are
811    /// technically possible, but not recommended.
812    ///
813    /// This function will always call
814    /// [`set_baseband_filter_bandwidth`][Self::set_baseband_filter_bandwidth],
815    /// so any changes to the filter should be done *after* this function.
816    ///
817    /// You may want to just use [`set_sample_rate`][Self::set_sample_rate]
818    /// instead.
819    ///
820    pub async fn set_sample_rate_manual(&self, freq_hz: u32, divider: u32) -> Result<(), Error> {
821        #[repr(C)]
822        #[derive(Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
823        struct FracRateParams {
824            freq_hz: u32,
825            divider: u32,
826        }
827
828        const DIV_RANGE: Range<u32> = Range { start: 1, end: 32 };
829        if !DIV_RANGE.contains(&divider) {
830            return Err(Error::ValueRange {
831                range: DIV_RANGE,
832                val: divider,
833            });
834        }
835
836        let params = FracRateParams {
837            freq_hz: freq_hz.to_le(),
838            divider: divider.to_le(),
839        };
840
841        self.write_bytes(ControlRequest::SampleRateSet, bytemuck::bytes_of(&params))
842            .await?;
843
844        let filter_bw = baseband_filter_bw(freq_hz * 3 / (divider * 4));
845        self.set_baseband_filter_bandwidth(filter_bw).await?;
846        Ok(())
847    }
848
849    /// Set the sample rate, which should be between 2-20 MHz.
850    ///
851    /// Lower & higher rates are possible, but not recommended.
852    ///
853    /// This function will always call
854    /// [`set_baseband_filter_bandwidth`][Self::set_baseband_filter_bandwidth],
855    /// so any changes to the filter should be done *after* this function.
856    ///
857    /// This function is a convenience wrapper around
858    /// [`set_sample_rate_manual`][Self::set_sample_rate_manual].
859    ///
860    pub async fn set_sample_rate(&self, freq: f64) -> Result<(), Error> {
861        let freq = freq.clamp(2e6, 20e6);
862
863        let mut freq_hz = 0;
864        let mut divider = 1;
865        let mut diff = f64::MAX;
866
867        // Just blindly check the closest of all possible divider values,
868        // preferring the smaller divider value on ties
869        for i in 1u32..32 {
870            let new_freq_hz = (freq * (i as f64)).round() as u32;
871            let new_diff = ((freq_hz as f64) / (i as f64) - freq).abs();
872            if new_diff < diff {
873                freq_hz = new_freq_hz;
874                divider = i;
875                diff = new_diff;
876            }
877        }
878
879        self.set_sample_rate_manual(freq_hz, divider).await
880    }
881
882    /// Enable/disable the 14dB RF amplifiers.
883    ///
884    /// Enable/disable the RX/TX amplifiers U13/U25 via the controlling switches
885    /// U9 and U14.
886    pub async fn set_amp_enable(&self, enable: bool) -> Result<(), Error> {
887        self.write_u16(ControlRequest::AmpEnable, 0, enable as u16)
888            .await
889    }
890
891    /// Set the LNA gain.
892    ///
893    /// Sets the RF RX gain of the MAX2837 transceiver IC. Must be in the range
894    /// of 0-40 dB, and is forced to 8 dB steps. Intermediate values are rounded
895    /// down.
896    pub async fn set_lna_gain(&self, value: u16) -> Result<(), Error> {
897        if value > 40 {
898            return Err(Error::ValueRange {
899                range: Range { start: 0, end: 41 },
900                val: value as u32,
901            });
902        }
903
904        let ret = self
905            .read_u8(ControlRequest::SetLnaGain, value & (!0x07))
906            .await?;
907        if ret == 0 {
908            return Err(Error::ReturnData);
909        }
910        Ok(())
911    }
912
913    /// Set the VGA gain.
914    ///
915    /// Sets the baseband RX gain of the MAX2837 transceiver IC. Must be in the range
916    /// of 0-62 dB, and is forced to 2 dB steps. Intermediate values are rounded
917    /// down.
918    pub async fn set_vga_gain(&self, value: u16) -> Result<(), Error> {
919        if value > 62 {
920            return Err(Error::ValueRange {
921                range: Range { start: 0, end: 63 },
922                val: value as u32,
923            });
924        }
925
926        let ret = self
927            .read_u8(ControlRequest::SetVgaGain, value & (!0x01))
928            .await?;
929        if ret == 0 {
930            return Err(Error::ReturnData);
931        }
932        Ok(())
933    }
934
935    /// Set the RF TX gain.
936    ///
937    /// Sets the RF TX gain of the MAX2837 transceiver IC. Must be in the range
938    /// of 0-47 dB.
939    pub async fn set_txvga_gain(&self, value: u16) -> Result<(), Error> {
940        if value > 47 {
941            return Err(Error::ValueRange {
942                range: Range { start: 0, end: 48 },
943                val: value as u32,
944            });
945        }
946
947        let ret = self.read_u8(ControlRequest::SetTxvgaGain, value).await?;
948        if ret == 0 {
949            return Err(Error::ReturnData);
950        }
951        Ok(())
952    }
953
954    /// Temporarily enable/disable the bias-tee (antenna port power).
955    ///
956    /// Enable or disable the **3.3v (max 50 mA)** bias-tee. Defaults to
957    /// disabled on power-up.
958    ///
959    /// The firmware auto-disables this after returning to IDLE mode. Consider
960    /// using [`set_user_bias_t_opts`][Self::set_user_bias_t_opts] instead to
961    /// configure the bias to work exactly the way you want it to.
962    pub async fn set_antenna_enable(&self, enable: bool) -> Result<(), Error> {
963        self.write_u16(ControlRequest::AntennaEnable, 0, enable as u16)
964            .await
965    }
966
967    /// Set hardware sync mode (hardware triggering).
968    ///
969    /// See the documentation
970    /// [here](https://hackrf.readthedocs.io/en/latest/hardware_triggering.html).
971    ///
972    /// When enabled, the next operating mode (RX, TX, or Sweep) will not start
973    /// until the input hardware trigger occurs.
974    ///
975    /// Requires API version 0x0102 or higher.
976    pub async fn set_hw_sync_mode(&self, enable: bool) -> Result<(), Error> {
977        self.api_check(0x0102)?;
978        self.write_u16(ControlRequest::SetHwSyncMode, 0, enable as u16)
979            .await
980    }
981
982    /// Get a list of what operacake boards are attached (up to 8).
983    ///
984    /// Requires API version 0x0105 or higher.
985    pub async fn operacake_boards(&self) -> Result<Vec<u8>, Error> {
986        self.api_check(0x0105)?;
987        let mut resp = self
988            .read_bytes(ControlRequest::OperacakeGetBoards, 8)
989            .await?;
990        resp.retain(|&x| x != 0xFF);
991        Ok(resp)
992    }
993
994    /// Set an Operacake board to a specific operating mode.
995    ///
996    /// When set to frequency or dwell time mode, the settings are shared
997    /// between all operacakes in that operating mode.
998    ///
999    /// Requires API version 0x0105 or higher.
1000    pub async fn operacake_set_mode(
1001        &self,
1002        address: u8,
1003        setting: OperacakeMode,
1004    ) -> Result<(), Error> {
1005        self.api_check(0x0105)?;
1006        if address > 7 {
1007            return Err(Error::InvalidParameter("Operacake address is out of range"));
1008        }
1009        self.write_u8(ControlRequest::OperacakeSetMode, setting as u16, address)
1010            .await
1011    }
1012
1013    /// Get the operating mode of an operacake board.
1014    ///
1015    /// Requires API version 0x0105 or higher.
1016    pub async fn operacake_get_mode(&self, address: u8) -> Result<OperacakeMode, Error> {
1017        self.api_check(0x0105)?;
1018        if address > 7 {
1019            return Err(Error::InvalidParameter("Operacake address is out of range"));
1020        }
1021        let ret = self
1022            .interface
1023            .control_in(ControlIn {
1024                control_type: ControlType::Vendor,
1025                recipient: Recipient::Device,
1026                request: ControlRequest::OperacakeGetMode as u8,
1027                value: address as u16,
1028                index: 0,
1029                length: 1,
1030            })
1031            .await
1032            .into_result()?;
1033        let ret = ret.first().ok_or(Error::ReturnData)?;
1034        match ret {
1035            0 => Ok(OperacakeMode::Manual),
1036            1 => Ok(OperacakeMode::Frequency),
1037            2 => Ok(OperacakeMode::Time),
1038            _ => Err(Error::ReturnData),
1039        }
1040    }
1041
1042    /// Set an operacake's switches manually.
1043    ///
1044    /// Should be called after setting manual mode with
1045    /// [`operacake_set_mode`][Self::operacake_set_mode].
1046    ///
1047    /// Requires API version 0x0102 or higher.
1048    pub async fn operacake_config_manual(&self, address: u8, a: u8, b: u8) -> Result<(), Error> {
1049        self.api_check(0x0102)?;
1050        if address > 7 {
1051            return Err(Error::InvalidParameter("Operacake address is out of range"));
1052        }
1053
1054        if a > 7 || b > 7 {
1055            return Err(Error::InvalidParameter(
1056                "One or more port numbers is out of range (0-7)",
1057            ));
1058        }
1059        if (a < 4 && b < 4) || (a >= 4 && b >= 4) {
1060            return Err(Error::InvalidParameter(
1061                "A0 & B0 ports are using same quad of multiplexed ports",
1062            ));
1063        }
1064
1065        let a = a as u16;
1066        let b = b as u16;
1067        self.write_u8(ControlRequest::OperacakeSetPorts, a | (b << 8), address)
1068            .await
1069    }
1070
1071    /// Match frequency bands to operacake ports.
1072    ///
1073    /// These frequency settings are used by any operacake operating in
1074    /// frequency mode.
1075    ///
1076    /// Requires API version 0x0103 or higher.
1077    pub async fn operacake_config_freq(&self, freqs: &[OperacakeFreq]) -> Result<(), Error> {
1078        self.api_check(0x0103)?;
1079        if freqs.len() > 8 {
1080            return Err(Error::InvalidParameter(
1081                "Operacake can only support 8 frequency bands max",
1082            ));
1083        }
1084        let mut data = Vec::with_capacity(5 * freqs.len());
1085        for f in freqs {
1086            if f.port > 7 {
1087                return Err(Error::InvalidParameter(
1088                    "Operacake frequency band port selection is out of range",
1089                ));
1090            }
1091            data.push((f.min >> 8) as u8);
1092            data.push((f.min & 0xFF) as u8);
1093            data.push((f.max >> 8) as u8);
1094            data.push((f.max & 0xFF) as u8);
1095            data.push(f.port);
1096        }
1097
1098        self.write_bytes(ControlRequest::OperacakeSetRanges, &data)
1099            .await
1100    }
1101
1102    /// Match dwell times to operacake ports.
1103    ///
1104    /// These dwell time settings are used by any operacake operating in
1105    /// time mode.
1106    ///
1107    /// Requires API version 0x0105 or higher.
1108    pub async fn operacake_config_time(&self, times: &[OperacakeDwell]) -> Result<(), Error> {
1109        self.api_check(0x0105)?;
1110        if times.len() > 16 {
1111            return Err(Error::InvalidParameter(
1112                "Operacake can only support 16 time slices max",
1113            ));
1114        }
1115        let mut data = Vec::with_capacity(5 * times.len());
1116        for t in times {
1117            if t.port > 7 {
1118                return Err(Error::InvalidParameter(
1119                    "Operacake time slice port selection is out of range",
1120                ));
1121            }
1122            data.extend_from_slice(&t.dwell.to_le_bytes());
1123            data.push(t.port);
1124        }
1125        self.write_bytes(ControlRequest::OperacakeSetDwellTimes, &data)
1126            .await
1127    }
1128
1129    /// Reset the HackRF.
1130    ///
1131    /// Requires API version 0x0102 or higher.
1132    pub async fn reset(&self) -> Result<(), Error> {
1133        self.api_check(0x0102)?;
1134        self.write_u16(ControlRequest::Reset, 0, 0).await
1135    }
1136
1137    /// Turn on the CLKOUT port.
1138    ///
1139    /// Requires API version 0x0103 or higher.
1140    pub async fn clkout_enable(&self, enable: bool) -> Result<(), Error> {
1141        self.api_check(0x0103)?;
1142        self.write_u16(ControlRequest::ClkoutEnable, 0, enable as u16)
1143            .await
1144    }
1145
1146    /// Check the CLKIN port status.
1147    ///
1148    /// Set to true if the CLKIN port is used as the reference clock.
1149    ///
1150    /// Requires API version 0x0106 or higher.
1151    pub async fn clkin_status(&self) -> Result<bool, Error> {
1152        self.api_check(0x0106)?;
1153        Ok(self.read_u8(ControlRequest::GetClkinStatus, 0).await? != 0)
1154    }
1155
1156    /// Perform a GPIO test of an Operacake board.
1157    ///
1158    /// Value 0xFFFF means "GPIO mode disabled" - remove additional add-on
1159    /// boards and retry.
1160    ///
1161    /// Value 0 means all tests passed.
1162    ///
1163    /// In any other values, a 1 bit signals an error. Bits are grouped in
1164    /// groups of 3. Encoding:
1165    ///
1166    /// ```text
1167    /// 0 - u1ctrl - u3ctrl0 - u3ctrl1 - u2ctrl0 - u2ctrl1
1168    /// ```
1169    ///
1170    /// Requires API version 0x0103 or higher.
1171    pub async fn operacake_gpio_test(&self, address: u8) -> Result<u16, Error> {
1172        self.api_check(0x0103)?;
1173        if address > 7 {
1174            return Err(Error::InvalidParameter("Operacake address is out of range"));
1175        }
1176        let ret = self
1177            .interface
1178            .control_in(ControlIn {
1179                control_type: ControlType::Vendor,
1180                recipient: Recipient::Device,
1181                request: ControlRequest::OperacakeGpioTest as u8,
1182                value: address as u16,
1183                index: 0,
1184                length: 2,
1185            })
1186            .await
1187            .into_result()?;
1188        let ret: [u8; 2] = ret.as_slice().try_into().map_err(|_| Error::ReturnData)?;
1189        Ok(u16::from_le_bytes(ret))
1190    }
1191
1192    /// Enable/disable the UI display on devices with one (Rad1o, PortaPack).
1193    ///
1194    /// Requires API version 0x0104 or higher.
1195    pub async fn set_ui_enable(&self, val: u8) -> Result<(), Error> {
1196        self.api_check(0x0104)?;
1197        self.write_u8(ControlRequest::UiEnable, 0, val).await
1198    }
1199
1200    /// Turn the LEDs on or off, overriding the default.
1201    ///
1202    /// There are normally 3 controllable LEDs: USB, RX, and TX. The Rad1o board
1203    /// has 4.  After setting them individually, they may get overridden later
1204    /// by other functions.
1205    ///
1206    /// | Bit | LED  |
1207    /// | --  | --   |
1208    /// | 0   | USB  |
1209    /// | 1   | RX   |
1210    /// | 2   | TX   |
1211    /// | 3   | User |
1212    ///
1213    /// Requires API version 0x0107 or higher.
1214    pub async fn set_leds(&self, state: u8) -> Result<(), Error> {
1215        self.api_check(0x0107)?;
1216        self.write_u8(ControlRequest::SetLeds, 0, state).await
1217    }
1218
1219    /// Set the Bias-Tee behavior.
1220    ///
1221    /// This function will configure what change, if any, to apply to the
1222    /// bias-tee circuit on a mode change. The default is for it to always be
1223    /// off, but with a custom config, it can turn on when switching to RX, TX,
1224    /// or even to always be on. The settings in `opts` are always applied when
1225    /// first changing to that mode, with [`BiasTMode::NoChange`] not changing
1226    /// from whatever it is set to before the transition.
1227    ///
1228    /// Requires API version 0x0108 or higher.
1229    pub async fn set_user_bias_t_opts(&self, opts: BiasTSetting) -> Result<(), Error> {
1230        self.api_check(0x0108)?;
1231        let state: u16 =
1232            0x124 | opts.off.as_u16() | (opts.rx.as_u16() << 3) | (opts.tx.as_u16() << 6);
1233        self.write_u16(ControlRequest::SetUserBiasTOpts, 0, state)
1234            .await
1235    }
1236
1237    /// Switch a HackRF into receive mode, getting `transfer_size` samples at a
1238    /// time. The transfer size is always rounded up to the nearest 256-sample
1239    /// block increment; it's recommended to be 8192 samples but can be smaller
1240    /// or larger as needed. If the same size is used repeatedly with
1241    /// `start_rx`, buffers won't need to be reallocated.
1242    pub async fn start_rx(self, transfer_size: usize) -> Result<Receive, StateChangeError> {
1243        Receive::new(self, transfer_size).await
1244    }
1245
1246    /// Start a RX sweep, which will also set the sample rate and baseband filter.
1247    ///
1248    /// Buffers are reused across sweep operations, provided that
1249    /// [`HackRf::start_rx`] isn't used, or is used with a 8192 sample buffer
1250    /// size.
1251    ///
1252    /// See [`Sweep`] for more details on the RX sweep mode.
1253    pub async fn start_rx_sweep(self, params: &SweepParams) -> Result<Sweep, StateChangeError> {
1254        Sweep::new(self, params).await
1255    }
1256
1257    /// Start a RX sweep, but don't set up the sample rate and baseband filter before starting.
1258    ///
1259    /// Buffers are reused across sweep operations, provided that
1260    /// [`HackRf::start_rx`] isn't used, or is used with a 8192 sample buffer
1261    /// size.
1262    ///
1263    /// See [`Sweep`] for more details on the RX sweep mode.
1264    pub async fn start_rx_sweep_custom_sample_rate(
1265        self,
1266        params: &SweepParams,
1267    ) -> Result<Sweep, StateChangeError> {
1268        Sweep::new_with_custom_sample_rate(self, params).await
1269    }
1270
1271    /// Switch a HackRF into transmit mode, with a set maximum number of samples
1272    /// per buffer block.
1273    ///
1274    /// Buffers are reused across transmit operations, provided that the
1275    /// `max_transfer_size` is always the same.
1276    pub async fn start_tx(self, max_transfer_size: usize) -> Result<Transmit, StateChangeError> {
1277        Transmit::new(self, max_transfer_size).await
1278    }
1279
1280    /// Try and turn the HackRF to the off state, regardless of what mode it is currently in.
1281    pub async fn turn_off(&self) -> Result<(), Error> {
1282        self.set_transceiver_mode(TransceiverMode::Off).await
1283    }
1284}
1285
1286fn baseband_filter_bw(freq: u32) -> u32 {
1287    const MAX2837_FT: &[u32] = &[
1288        1750000, 2500000, 3500000, 5000000, 5500000, 6000000, 7000000, 8000000, 9000000, 10000000,
1289        12000000, 14000000, 15000000, 20000000, 24000000, 28000000,
1290    ];
1291
1292    MAX2837_FT
1293        .iter()
1294        .rev()
1295        .find(|f| freq >= **f)
1296        .copied()
1297        .unwrap_or(MAX2837_FT[0])
1298}
1299
1300#[cfg(test)]
1301mod tests {
1302    use crate::baseband_filter_bw;
1303
1304    #[test]
1305    fn baseband_filter() {
1306        assert_eq!(baseband_filter_bw(1000), 1750000);
1307        assert_eq!(baseband_filter_bw(30_000_000), 28_000_000);
1308        assert_eq!(baseband_filter_bw(3_000_000), 2_500_000);
1309    }
1310}