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