Skip to main content

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