seify_hackrfone/
lib.rs

1#![deny(unsafe_code)]
2
3//! # HackRF One API
4//!
5//! This crate provides a Rust interface to the [HackRF One](https://greatscottgadgets.com/hackrf/one/),
6//! a popular software-defined radio (SDR) peripheral. It allows for transmitting and receiving
7//! radio signals using the HackRF One device in pure Rust.
8//!
9//! ## Example
10//!
11//! ```rust,no_run
12//! use anyhow::Result;
13//! use seify_hackrfone::{Config, HackRf};
14//!
15//! fn main() -> Result<()> {
16//!     let radio = HackRf::open_first()?;
17//!
18//!     radio.start_rx(&Config {
19//!         vga_db: 0,
20//!         txvga_db: 0,
21//!         lna_db: 0,
22//!         amp_enable: false,
23//!         antenna_enable: false,
24//!         frequency_hz: 915_000_000,
25//!         sample_rate_hz: 2_000_000,
26//!         sample_rate_div: 1,
27//!     })?;
28//!
29//!     let mut buf = vec![0u8; 32 * 1024];
30//!     loop {
31//!         radio.read(&mut buf)?;
32//!         // Process samples...
33//!     }
34//! }
35//! ```
36//!
37//! ## License
38//!
39//! This crate is licensed under the MIT License.
40
41#![cfg_attr(docsrs, feature(doc_cfg), feature(doc_auto_cfg))]
42// TODO(tjn): re-enable
43#![warn(missing_docs)]
44
45mod types;
46pub use types::*;
47
48use std::sync::atomic::Ordering;
49
50use futures_lite::future::block_on;
51use nusb::{
52    transfer::{ControlIn, ControlOut, ControlType, Queue, Recipient, RequestBuffer},
53    DeviceInfo,
54};
55
56/// HackRF USB vendor ID.
57const HACKRF_USB_VID: u16 = 0x1D50;
58/// HackRF One USB product ID.
59const HACKRF_ONE_USB_PID: u16 = 0x6089;
60
61/// HackRF One software defined radio.
62pub struct HackRf {
63    interface: nusb::Interface,
64    version: UsbVersion,
65    mode: AtomicMode,
66}
67
68impl HackRf {
69    /// Opens `info` based on the result of a `nusb` scan.
70    pub fn open(info: DeviceInfo) -> Result<Self> {
71        let device = info.open()?;
72
73        let interface = device
74            .detach_and_claim_interface(0)
75            .expect("claim interface");
76
77        Ok(HackRf {
78            interface,
79            version: UsbVersion::from_bcd(info.device_version()),
80            mode: AtomicMode::new(Mode::Off),
81        })
82    }
83
84    /// Wraps a HackRf One exposed through an existing file descriptor.
85    ///
86    /// Useful on platforms like Android where [`UsbManager`](https://developer.android.com/reference/android/hardware/usb/UsbManager#openAccessory(android.hardware.usb.UsbAccessory)) permits access to an fd.
87    #[cfg(any(target_os = "android", target_os = "linux"))]
88    pub fn from_fd(fd: std::os::fd::OwnedFd) -> Result<Self> {
89        use std::os::fd::AsRawFd;
90        log::info!("Wrapping hackrf fd={}", fd.as_raw_fd());
91        let device = nusb::Device::from_fd(fd)?;
92
93        let interface = device
94            .detach_and_claim_interface(0)
95            .expect("claim interface");
96
97        Ok(HackRf {
98            interface,
99            // TODO: Actually read version, dont assume latest
100            version: UsbVersion::from_bcd(0x0102),
101            mode: AtomicMode::new(Mode::Off),
102        })
103    }
104
105    /// Opens the first Hackrf One found via USB.
106    pub fn open_first() -> Result<HackRf> {
107        for device in nusb::list_devices()? {
108            if device.vendor_id() == HACKRF_USB_VID && device.product_id() == HACKRF_ONE_USB_PID {
109                match Self::open(device) {
110                    Ok(dev) => return Ok(dev),
111                    Err(_) => continue,
112                }
113            }
114        }
115
116        Err(Error::NotFound)
117    }
118
119    /// Scans the usb bus for hackrf devices, returning the pair of (bus_num, bus_addr) for each
120    /// device.
121    // TODO: add equivalent macos function that uses location_id
122    #[cfg(any(target_os = "linux", target_os = "android"))]
123    pub fn scan() -> Result<Vec<(u8, u8)>> {
124        let mut res = vec![];
125        for device in nusb::list_devices()? {
126            if device.vendor_id() == HACKRF_USB_VID && device.product_id() == HACKRF_ONE_USB_PID {
127                res.push((device.bus_number(), device.device_address()));
128            }
129        }
130        Ok(res)
131    }
132
133    /// Opens a hackrf with usb address `<bus_number>:<address>`
134    // TODO: add equivalent macos function that uses location_id
135    #[cfg(any(target_os = "linux", target_os = "android"))]
136    pub fn open_bus(bus_number: u8, address: u8) -> Result<HackRf> {
137        for device in nusb::list_devices()? {
138            match device.vendor_id() == HACKRF_USB_VID
139                && device.product_id() == HACKRF_ONE_USB_PID
140                && device.bus_number() == bus_number
141                && device.device_address() == address
142            {
143                true => return Self::open(device),
144                false => (),
145            }
146        }
147
148        Err(Error::NotFound)
149    }
150
151    /// Resets the HackRf.
152    pub fn reset(self) -> Result<()> {
153        self.check_api_version(UsbVersion::from_bcd(0x0102))?;
154        self.write_control(Request::Reset, 0, 0, &[])?;
155
156        Ok(())
157    }
158
159    /// Returns the USB version of the device.
160    pub fn device_version(&self) -> UsbVersion {
161        self.version
162    }
163
164    /// Reads the board ID of the HackRF One device.
165    pub fn board_id(&self) -> Result<u8> {
166        let data: [u8; 1] = self.read_control(Request::BoardIdRead, 0, 0)?;
167        Ok(data[0])
168    }
169
170    /// Read the firmware version.
171    pub fn version(&self) -> Result<String> {
172        let buf = block_on(self.interface.control_in(ControlIn {
173            control_type: ControlType::Vendor,
174            recipient: Recipient::Device,
175            request: Request::VersionStringRead as u8,
176            value: 0x0,
177            index: 0x0,
178            length: 64,
179        }))
180        .into_result()?;
181
182        Ok(String::from_utf8_lossy(&buf).into())
183    }
184
185    fn apply_config(&self, config: &Config) -> Result<()> {
186        self.set_lna_gain(config.lna_db)?;
187        self.set_vga_gain(config.vga_db)?;
188        self.set_txvga_gain(config.txvga_db)?;
189        self.set_freq(config.frequency_hz)?;
190        self.set_amp_enable(config.amp_enable)?;
191        self.set_antenna_enable(config.antenna_enable)?;
192        self.set_sample_rate(config.sample_rate_hz, config.sample_rate_div)?;
193
194        Ok(())
195    }
196
197    /// Transitions the radio into transmit mode.
198    /// Call this function before calling [`Self::write`].
199    ///
200    /// Previous state set via `set_xxx` functions will be overridden with the parameters set in `config`.
201    ///
202    /// # Errors
203    /// This function will return an error if a tx or rx operation is already in progress or if an
204    /// I/O error occurs
205    pub fn start_tx(&self, config: &Config) -> Result<()> {
206        // NOTE: perform atomic exchange first so that we only change the transceiver mode once if
207        // other threads are racing to change with us
208        if let Err(actual) = self.mode.compare_exchange(
209            Mode::Off,
210            Mode::Transmit,
211            Ordering::AcqRel,
212            Ordering::Relaxed,
213        ) {
214            return Err(Error::WrongMode {
215                required: Mode::Off,
216                actual,
217            });
218        }
219
220        self.apply_config(config)?;
221
222        self.write_control(Request::SetTransceiverMode, Mode::Transmit as u16, 0, &[])?;
223
224        Ok(())
225    }
226
227    /// Transitions the radio into receive mode.
228    /// Call this function before calling [`Self::read`].
229    ///
230    /// Previous state set via `set_xxx` functions will be overridden with the parameters set in `config`.
231    ///
232    /// # Errors
233    /// This function will return an error if a tx or rx operation is already in progress or if an
234    /// I/O error occurs
235    pub fn start_rx(&self, config: &Config) -> Result<()> {
236        // NOTE: perform atomic exchange first so that we only change the transceiver mode once if
237        // other threads are racing to change with us
238        if let Err(actual) = self.mode.compare_exchange(
239            Mode::Off,
240            Mode::Receive,
241            Ordering::AcqRel,
242            Ordering::Relaxed,
243        ) {
244            return Err(Error::WrongMode {
245                required: Mode::Off,
246                actual,
247            });
248        }
249
250        self.apply_config(config)?;
251
252        self.write_control(Request::SetTransceiverMode, Mode::Receive as u16, 0, &[])?;
253
254        Ok(())
255    }
256
257    /// Stops the transmit operation and transitions the radio into off mode.
258    ///
259    /// # Errors
260    /// This function will return an error if the device is not in transmit mode or if an
261    /// I/O error occurs.
262    pub fn stop_tx(&self) -> Result<()> {
263        // NOTE:  perform atomic exchange last so that we prevent other threads from racing to
264        // start tx/rx with the delivery of our TransceiverMode::Off request
265        //
266        // This means if multiple threads call stop_tx/stop_rx concurrently the hackrf may receive
267        // multiple TransceiverMode::Off requests, but will always end up in a valid state with the
268        // transceiver disabled.
269        //
270        // Adding something like Mode::IdlePending would solve this,
271        // however quickly this begins to look like a manually implemented mutex.
272        //
273        // To keep this crate low-level and low-overhead, this solution is fine and we expect
274        // consumers to wrap our type in an Arc and be smart enough to not enable / disable tx / rx
275        // from multiple threads at the same time on a single duplex radio.
276
277        self.write_control(Request::SetTransceiverMode, Mode::Off as u16, 0, &[])?;
278
279        if let Err(actual) = self.mode.compare_exchange(
280            Mode::Transmit,
281            Mode::Off,
282            Ordering::AcqRel,
283            Ordering::Relaxed,
284        ) {
285            return Err(Error::WrongMode {
286                required: Mode::Transmit,
287                actual,
288            });
289        }
290
291        Ok(())
292    }
293
294    /// Stops the receive operation and transitions the radio into off mode.
295    ///
296    /// # Errors
297    /// This function will return an error if the device is not in receive mode or if an
298    /// I/O error occurs.
299    pub fn stop_rx(&self) -> Result<()> {
300        // NOTE: same as above - perform atomic exchange last
301
302        self.write_control(Request::SetTransceiverMode, Mode::Off as u16, 0, &[])?;
303
304        if let Err(actual) = self.mode.compare_exchange(
305            Mode::Receive,
306            Mode::Off,
307            Ordering::AcqRel,
308            Ordering::Relaxed,
309        ) {
310            return Err(Error::WrongMode {
311                required: Mode::Receive,
312                actual,
313            });
314        }
315
316        Ok(())
317    }
318
319    /// Read samples from the radio.
320    ///
321    /// # Panics
322    /// This function panics if samples is not a multiple of 512
323    pub fn read(&self, samples: &mut [u8]) -> Result<usize> {
324        self.ensure_mode(Mode::Receive)?;
325
326        if samples.len() % 512 != 0 {
327            panic!("samples must be a multiple of 512");
328        }
329
330        const ENDPOINT: u8 = 0x81;
331        let buf = block_on(
332            self.interface
333                .bulk_in(ENDPOINT, RequestBuffer::new(samples.len())),
334        )
335        .into_result()?;
336        samples[..buf.len()].copy_from_slice(&buf);
337
338        Ok(buf.len())
339    }
340
341    /// Writes samples to the radio.
342    ///
343    /// # Panics
344    /// This function panics if samples is not a multiple of 512
345    pub fn write(&self, samples: &[u8]) -> Result<usize> {
346        self.ensure_mode(Mode::Transmit)?;
347
348        if samples.len() % 512 != 0 {
349            panic!("samples must be a multiple of 512");
350        }
351
352        const ENDPOINT: u8 = 0x02;
353        let buf = Vec::from(samples);
354        // TODO: dont allocate
355        let n = block_on(self.interface.bulk_out(ENDPOINT, buf)).into_result()?;
356
357        Ok(n.actual_length())
358    }
359
360    /// Setup the device to stream samples.
361    pub fn start_rx_stream(&self, transfer_size: usize) -> Result<RxStream> {
362        if transfer_size % 512 != 0 {
363            panic!("transfer_size must be a multiple of 512");
364        }
365
366        const ENDPOINT: u8 = 0x81;
367        Ok(RxStream {
368            queue: self.interface.bulk_in_queue(ENDPOINT),
369            in_flight_transfers: 3,
370            transfer_size,
371            buf_pos: transfer_size,
372            buf: vec![0u8; transfer_size],
373        })
374    }
375}
376
377/// Represents an asynchronous receive stream from the HackRF device.
378///
379/// Use this to read samples from the device in a streaming fashion.
380pub struct RxStream {
381    queue: Queue<RequestBuffer>,
382    in_flight_transfers: usize,
383    transfer_size: usize,
384    buf_pos: usize,
385    buf: Vec<u8>,
386}
387
388impl RxStream {
389    /// Read samples from the device, blocking until more are available.
390    pub fn read_sync(&mut self, count: usize) -> Result<&[u8]> {
391        let buffered_remaining = self.buf.len() - self.buf_pos;
392        if buffered_remaining > 0 {
393            let to_consume = std::cmp::min(count, buffered_remaining);
394            let ret = &self.buf[self.buf_pos..self.buf_pos + to_consume];
395            self.buf_pos += ret.len();
396            return Ok(ret);
397        }
398
399        while self.queue.pending() < self.in_flight_transfers {
400            self.queue.submit(RequestBuffer::new(self.transfer_size));
401        }
402        let completion = block_on(self.queue.next_complete());
403
404        if let Err(e) = completion.status {
405            return Err(e.into());
406        }
407
408        let reuse = std::mem::replace(&mut self.buf, completion.data);
409        self.buf_pos = 0;
410
411        self.queue
412            .submit(RequestBuffer::reuse(reuse, self.transfer_size));
413
414        // bytes are now buffered, use tail recursion for code above to return subslice
415        self.read_sync(count)
416    }
417}
418
419impl HackRf {
420    fn ensure_mode(&self, expected: Mode) -> Result<()> {
421        let actual = self.mode.load(Ordering::Acquire);
422        if actual != expected {
423            return Err(Error::WrongMode {
424                required: expected,
425                actual,
426            });
427        }
428        Ok(())
429    }
430
431    fn read_control<const N: usize>(
432        &self,
433        request: Request,
434        value: u16,
435        index: u16,
436    ) -> Result<[u8; N]> {
437        let mut res: [u8; N] = [0; N];
438        let buf = block_on(self.interface.control_in(ControlIn {
439            control_type: ControlType::Vendor,
440            recipient: Recipient::Device,
441            request: request as u8,
442            value,
443            index,
444            length: N as u16,
445        }))
446        .into_result()?;
447
448        if buf.len() != N {
449            return Err(Error::TransferTruncated {
450                actual: buf.len(),
451                expected: N,
452            });
453        }
454
455        res.copy_from_slice(&buf);
456        Ok(res)
457    }
458
459    fn write_control(&self, request: Request, value: u16, index: u16, buf: &[u8]) -> Result<()> {
460        let out = block_on(self.interface.control_out(ControlOut {
461            control_type: ControlType::Vendor,
462            recipient: Recipient::Device,
463            request: request as u8,
464            value,
465            index,
466            data: buf,
467        }))
468        .into_result()?;
469
470        if out.actual_length() != buf.len() {
471            Err(Error::TransferTruncated {
472                actual: out.actual_length(),
473                expected: buf.len(),
474            })
475        } else {
476            Ok(())
477        }
478    }
479
480    fn check_api_version(&self, min: UsbVersion) -> Result<()> {
481        fn version_to_u32(v: UsbVersion) -> u32 {
482            ((v.major() as u32) << 16) | ((v.minor() as u32) << 8) | (v.sub_minor() as u32)
483        }
484
485        if version_to_u32(self.version) >= version_to_u32(min) {
486            Ok(())
487        } else {
488            Err(Error::NoApi {
489                device: self.version,
490                min,
491            })
492        }
493    }
494
495    /// Set the center frequency.
496    pub fn set_freq(&self, hz: u64) -> Result<()> {
497        let buf: [u8; 8] = freq_params(hz);
498        self.write_control(Request::SetFreq, 0, 0, &buf)
499    }
500
501    /// Enable the RX/TX RF amplifier.
502    ///
503    /// In GNU radio this is used as the RF gain, where a value of 0 dB is off,
504    /// and a value of 14 dB is on.
505    pub fn set_amp_enable(&self, enable: bool) -> Result<()> {
506        self.write_control(Request::AmpEnable, enable.into(), 0, &[])
507    }
508
509    /// Set the baseband filter bandwidth.
510    ///
511    /// This is automatically set when the sample rate is changed with
512    /// [`Self::set_sample_rate`].
513    pub fn set_baseband_filter_bandwidth(&self, hz: u32) -> Result<()> {
514        self.write_control(
515            Request::BasebandFilterBandwidthSet,
516            (hz & 0xFFFF) as u16,
517            (hz >> 16) as u16,
518            &[],
519        )
520    }
521
522    /// Set the sample rate.
523    ///
524    /// For anti-aliasing, the baseband filter bandwidth is automatically set to
525    /// the widest available setting that is no more than 75% of the sample rate.
526    /// This happens every time the sample rate is set.
527    /// If you want to override the baseband filter selection, you must do so
528    /// after setting the sample rate.
529    ///
530    /// Limits are 8MHz - 20MHz.
531    /// Preferred rates are 8, 10, 12.5, 16, 20MHz due to less jitter.
532    pub fn set_sample_rate(&self, hz: u32, div: u32) -> Result<()> {
533        let hz: u32 = hz.to_le();
534        let div: u32 = div.to_le();
535        let buf: [u8; 8] = [
536            (hz & 0xFF) as u8,
537            ((hz >> 8) & 0xFF) as u8,
538            ((hz >> 16) & 0xFF) as u8,
539            ((hz >> 24) & 0xFF) as u8,
540            (div & 0xFF) as u8,
541            ((div >> 8) & 0xFF) as u8,
542            ((div >> 16) & 0xFF) as u8,
543            ((div >> 24) & 0xFF) as u8,
544        ];
545        self.write_control(Request::SampleRateSet, 0, 0, &buf)?;
546        self.set_baseband_filter_bandwidth((0.75 * (hz as f32) / (div as f32)) as u32)
547    }
548
549    /// Set the LNA (low noise amplifier) gain.
550    ///
551    /// Range 0 to 40dB in 8dB steps.
552    ///
553    /// This is also known as the IF gain.
554    pub fn set_lna_gain(&self, gain: u16) -> Result<()> {
555        if gain > 40 {
556            Err(Error::Argument("lna gain must be less than 40"))
557        } else {
558            let buf: [u8; 1] = self.read_control(Request::SetLnaGain, 0, gain & !0x07)?;
559            if buf[0] == 0 {
560                panic!("Unexpected return value from read_control(SetLnaGain)");
561            } else {
562                Ok(())
563            }
564        }
565    }
566
567    /// Set the VGA (variable gain amplifier) gain.
568    ///
569    /// Range 0 to 62dB in 2dB steps.
570    ///
571    /// This is also known as the baseband (BB) gain.
572    pub fn set_vga_gain(&self, gain: u16) -> Result<()> {
573        if gain > 62 || gain % 2 == 1 {
574            Err(Error::Argument(
575                "gain parameter out of range. must be even and less than or equal to 62",
576            ))
577        } else {
578            let buf: [u8; 1] = self.read_control(Request::SetVgaGain, 0, gain & !0b1)?;
579            if buf[0] == 0 {
580                panic!("What is this return value?")
581            } else {
582                Ok(())
583            }
584        }
585    }
586
587    /// Set the transmit VGA gain.
588    ///
589    /// Range 0 to 47dB in 1db steps.
590    pub fn set_txvga_gain(&self, gain: u16) -> Result<()> {
591        if gain > 47 {
592            Err(Error::Argument("gain parameter out of range. max is 47"))
593        } else {
594            let buf: [u8; 1] = self.read_control(Request::SetTxvgaGain, 0, gain)?;
595            if buf[0] == 0 {
596                panic!("What is this return value?")
597            } else {
598                Ok(())
599            }
600        }
601    }
602
603    /// Antenna power port control. Dhruv's guess: is this DC bias?
604    ///
605    /// The source docs are a little lacking in terms of explanations here.
606    pub fn set_antenna_enable(&self, value: bool) -> Result<()> {
607        let value = if value { 1 } else { 0 };
608        self.write_control(Request::AntennaEnable, value, 0, &[])
609    }
610}
611
612// Helper for set_freq
613fn freq_params(hz: u64) -> [u8; 8] {
614    const MHZ: u64 = 1_000_000;
615
616    let l_freq_mhz: u32 = u32::try_from(hz / MHZ).unwrap_or(u32::MAX).to_le();
617    let l_freq_hz: u32 = u32::try_from(hz - u64::from(l_freq_mhz) * MHZ)
618        .unwrap_or(u32::MAX)
619        .to_le();
620
621    [
622        (l_freq_mhz & 0xFF) as u8,
623        ((l_freq_mhz >> 8) & 0xFF) as u8,
624        ((l_freq_mhz >> 16) & 0xFF) as u8,
625        ((l_freq_mhz >> 24) & 0xFF) as u8,
626        (l_freq_hz & 0xFF) as u8,
627        ((l_freq_hz >> 8) & 0xFF) as u8,
628        ((l_freq_hz >> 16) & 0xFF) as u8,
629        ((l_freq_hz >> 24) & 0xFF) as u8,
630    ]
631}
632
633#[cfg(test)]
634mod test {
635    use std::time::Duration;
636
637    use super::*;
638
639    #[test]
640    fn test_freq_params() {
641        assert_eq!(freq_params(915_000_000), [0x93, 0x03, 0, 0, 0, 0, 0, 0]);
642        assert_eq!(freq_params(915_000_001), [0x93, 0x03, 0, 0, 1, 0, 0, 0]);
643        assert_eq!(
644            freq_params(123456789),
645            [0x7B, 0, 0, 0, 0x55, 0xF8, 0x06, 0x00]
646        );
647
648        assert_eq!(freq_params(0), [0; 8]);
649
650        assert_eq!(freq_params(u64::MAX), [0xFF; 8]);
651    }
652
653    // NOTE: make sure you can transmit on the frequency below and that you have the correct
654    // antenna / attenuation before enabling!
655    // #[test]
656    #[allow(dead_code)]
657    fn device_states() {
658        let radio = HackRf::open_first().expect("Failed to open hackrf");
659
660        radio
661            .start_tx(&Config {
662                vga_db: 0,
663                txvga_db: 0,
664                lna_db: 0,
665                amp_enable: false,
666                antenna_enable: false,
667                frequency_hz: 915_000_000,
668                sample_rate_hz: 2_000_000,
669                sample_rate_div: 1,
670            })
671            .unwrap();
672        std::thread::sleep(Duration::from_millis(50));
673
674        radio.stop_tx().unwrap();
675        assert!(radio.stop_tx().is_err());
676        assert!(radio.stop_tx().is_err());
677        assert!(radio.stop_rx().is_err());
678        assert!(radio.stop_rx().is_err());
679
680        std::thread::sleep(Duration::from_millis(50));
681
682        radio
683            .start_rx(&Config {
684                vga_db: 0,
685                txvga_db: 0,
686                lna_db: 0,
687                amp_enable: false,
688                antenna_enable: false,
689                frequency_hz: 915_000_000,
690                sample_rate_hz: 2_000_000,
691                sample_rate_div: 1,
692            })
693            .unwrap();
694        std::thread::sleep(Duration::from_millis(50));
695
696        radio.stop_rx().unwrap();
697        assert!(radio.stop_rx().is_err());
698        assert!(radio.stop_rx().is_err());
699        assert!(radio.stop_tx().is_err());
700        assert!(radio.stop_tx().is_err());
701    }
702}