Skip to main content

rtlsdr/
lib.rs

1#![warn(clippy::all, clippy::pedantic, clippy::nursery)]
2
3mod sys {
4    #![allow(non_camel_case_types, non_upper_case_globals, unused, non_snake_case)]
5    include!("sys.rs");
6}
7
8macro_rules! rtlsdr_result {
9    ($expr:expr) => {{
10        let result = |ret: i32| -> Result<i32> {
11            if ret < 0 {
12                Err(RtlsdrError::from(ret))
13            } else {
14                Ok(ret)
15            }
16        };
17        result(unsafe { $expr })
18    }};
19}
20
21#[derive(Debug, Copy, Clone, PartialEq, Eq)]
22/// General error type for rtlsdr operations, which can be either a libusb error or
23/// some other unspecified error
24pub enum RtlsdrError {
25    LibusbError(LibusbError),
26    Unspecified(i32),
27}
28
29impl From<i32> for RtlsdrError {
30    fn from(err: i32) -> Self {
31        match LibusbError::try_from(err) {
32            Ok(e) => Self::LibusbError(e),
33            Err(e) => Self::Unspecified(e),
34        }
35    }
36}
37
38pub type Result<T> = std::result::Result<T, RtlsdrError>;
39
40#[repr(i32)]
41#[derive(Debug, Copy, Clone, PartialEq, Eq)]
42pub enum LibusbError {
43    /// Input/output error
44    IoError = -1,
45    /// Invalid parameter
46    InvalidParam = -2,
47    /// Access denied (insufficient permissions)
48    AccessDenied = -3,
49    /// No such device (it may have been disconnected)
50    NoDevice = -4,
51    /// Entity not found
52    NoEntity = -5,
53    /// Resource busy
54    Busy = -6,
55    /// Operation timed out
56    Timeout = -7,
57    /// Overflow
58    Overflow = -8,
59    /// Pipe error
60    Pipe = -9,
61    /// System call interrupted (perhaps due to signal)
62    Interrupted = -10,
63    /// Insufficient memory
64    InsufficientMemory = -11,
65    /// Operation not supported or unimplemented on this platform
66    NotSupported = -12,
67    /// Other error
68    Other = -99,
69}
70
71impl TryFrom<i32> for LibusbError {
72    type Error = i32;
73
74    fn try_from(value: i32) -> std::result::Result<Self, Self::Error> {
75        match value {
76            -1 => Ok(Self::IoError),
77            -2 => Ok(Self::InvalidParam),
78            -3 => Ok(Self::AccessDenied),
79            -4 => Ok(Self::NoDevice),
80            -5 => Ok(Self::NoEntity),
81            -6 => Ok(Self::Busy),
82            -7 => Ok(Self::Timeout),
83            -8 => Ok(Self::Overflow),
84            -9 => Ok(Self::Pipe),
85            -10 => Ok(Self::Interrupted),
86            -11 => Ok(Self::InsufficientMemory),
87            -12 => Ok(Self::NotSupported),
88            -99 => Ok(Self::Other),
89            e => Err(e),
90        }
91    }
92}
93
94#[repr(i32)]
95#[derive(Debug, Copy, Clone, PartialEq, Eq)]
96pub enum TunerType {
97    /// Unknown tuner type
98    Unknown = 0,
99    /// Elonics E4000 tuner
100    E4000 = 1,
101    /// FC0012 tuner
102    FC0012 = 2,
103    /// FC0013 tuner
104    FC0013 = 3,
105    /// FC2580 tuner
106    FC2580 = 4,
107    /// Realtek 820T tuner
108    R820T = 5,
109    /// Realtek 828D tuner
110    R828D = 6,
111}
112
113#[repr(i32)]
114#[derive(Debug, Copy, Clone, PartialEq, Eq)]
115pub enum Sideband {
116    Lower = 0,
117    Upper = 1,
118}
119
120#[repr(u32)]
121#[derive(Debug, Copy, Clone, PartialEq, Eq)]
122pub enum DirectSampling {
123    Disabled = 0,
124    I = 1,
125    Q = 2,
126}
127
128#[repr(u32)]
129#[derive(Debug, Copy, Clone, PartialEq, Eq)]
130pub enum DirectSamplingThreshold {
131    Disabled = 0,
132    I = 1,
133    Q = 2,
134    IBelow = 3,
135    QBelow = 4,
136}
137
138#[derive(Debug, Copy, Clone, PartialEq, Eq)]
139/// Device struct
140pub struct Device {
141    index: u32,
142    dev: *mut sys::rtlsdr_dev,
143}
144
145impl Device {
146    /// Open a device
147    /// # Errors
148    /// This will return an error if the device cannot be opened by librtlsdr (e.g. libusb failure or device is already open)
149    pub fn open(&mut self) -> Result<()> {
150        rtlsdr_result!(sys::rtlsdr_open(&raw mut self.dev, self.index))?;
151
152        Ok(())
153    }
154
155    /// Close device
156    /// # Errors
157    /// This will return an error if the device cannot be closed by librtlsdr (e.g. libusb failure or device is not open)
158    pub fn close(&mut self) -> Result<()> {
159        rtlsdr_result!(sys::rtlsdr_close(self.dev))?;
160
161        self.dev = std::ptr::null_mut();
162
163        Ok(())
164    }
165
166    /// Get crystal oscillator frequencies used for the RTL2832 and the tuner IC
167    /// Usually both ICs use the same clock.
168    /// # Errors
169    /// This will return an error if the frequencies cannot be retrieved by librtlsdr (e.g. libusb failure or device is not open)
170    pub fn get_xtal_freq(&self) -> Result<(u32, u32)> {
171        let mut rtl_freq = 0;
172        let mut tuner_freq = 0;
173
174        rtlsdr_result!(sys::rtlsdr_get_xtal_freq(
175            self.dev,
176            &raw mut rtl_freq,
177            &raw mut tuner_freq
178        ))?;
179
180        Ok((rtl_freq, tuner_freq))
181    }
182
183    /// Set crystal oscillator frequencies used for the RTL2832 and the tuner IC.
184    /// Usually both ICs use the same clock.
185    /// Changing the clock may make sense if you are applying an external clock to the tuner
186    /// or to compensate the frequency (and samplerate) error caused by the original (cheap) crystal.
187    ///
188    /// NOTE: Call this function only if you fully understand the implications.
189    /// # Errors
190    /// This will return an error if the frequencies cannot be set by librtlsdr (e.g. libusb failure or device is not open)
191    pub fn set_xtal_freq(&mut self, rtl_freq: u32, tuner_freq: u32) -> Result<()> {
192        rtlsdr_result!(sys::rtlsdr_set_xtal_freq(self.dev, rtl_freq, tuner_freq))?;
193        Ok(())
194    }
195
196    /// Get USB device strings.
197    /// # Returns
198    /// (manufacturer, product, serial) strings
199    /// # Panics
200    /// This will panic if the retrieved strings from librtlsdr are not valid UTF-8 or if they are not null-terminated.
201    /// # Errors
202    /// This will return an error if the strings cannot be retrieved by librtlsdr (e
203    pub fn get_usb_device_strings(&self) -> Result<(String, String, String)> {
204        let mut manufact = [0u8; 256];
205        let mut product = [0u8; 256];
206        let mut serial = [0u8; 256];
207
208        rtlsdr_result!(sys::rtlsdr_get_usb_strings(
209            self.dev,
210            manufact.as_mut_ptr().cast::<i8>(),
211            product.as_mut_ptr().cast::<i8>(),
212            serial.as_mut_ptr().cast::<i8>()
213        ))?;
214
215        let mps = [&manufact, &product, &serial].map(|s| {
216            std::ffi::CStr::from_bytes_until_nul(s)
217                .expect("usb device string is not null-terminated")
218                .to_str()
219                .expect("Failed to convert usb device string to str")
220                .to_string()
221        });
222
223        Ok(mps.into())
224    }
225
226    /// Read the device EEPROM
227    /// # Errors
228    /// This will return an error if the EEPROM cannot be read by librtlsdr (e.g. libusb failure or device is not open)
229    pub fn read_eeprom(&self, offset: u8, len: u16) -> Result<Box<[u8]>> {
230        let mut buf = vec![0u8; len as usize];
231
232        let n = rtlsdr_result!(sys::rtlsdr_read_eeprom(
233            self.dev,
234            buf.as_mut_ptr(),
235            offset,
236            len
237        ))?;
238
239        #[allow(clippy::cast_sign_loss)]
240        // negative values are already handled by the error case above
241        buf.truncate(n as usize);
242
243        Ok(buf.into_boxed_slice())
244    }
245
246    /// Write the device EEPROM
247    /// # Errors
248    /// This will return an error if the EEPROM cannot be written by librtlsdr (e.g. libusb failure or device is not open)
249    /// # Panics
250    /// This will panic if the buffer length exceeds the maximum EEPROM size (65535 bytes)
251    pub fn write_eeprom(&mut self, offset: u8, buf: &mut [u8]) -> Result<()> {
252        let len = u16::try_from(buf.len()).expect("Buffer length exceeds maximum EEPROM size");
253
254        rtlsdr_result!(sys::rtlsdr_write_eeprom(
255            self.dev,
256            buf.as_mut_ptr(),
257            offset,
258            len
259        ))?;
260
261        Ok(())
262    }
263
264    /// Get actual frequency the device is tuned to in Hz
265    /// # Errors
266    /// This will return an error if the frequency cannot be retrieved by librtlsdr (e.g. libusb failure or device is not open)
267    pub fn get_center_freq(&self) -> Result<u32> {
268        match unsafe { sys::rtlsdr_get_center_freq(self.dev) } {
269            0 => Err(RtlsdrError::Unspecified(0)),
270            freq => Ok(freq),
271        }
272    }
273
274    /// Set the frequency the device is tuned to in Hz
275    /// # Errors
276    /// This will return an error if the frequency cannot be retrieved by librtlsdr (e.g. libusb failure or device is not open)
277    pub fn set_center_freq(&mut self, freq: u32) -> Result<()> {
278        rtlsdr_result!(sys::rtlsdr_set_center_freq(self.dev, freq))?;
279        Ok(())
280    }
281
282    /// Get actual frequency correction value of the device.
283    /// Returns correction value in parts per million (ppm)
284    #[must_use]
285    pub fn get_freq_correction(&self) -> i32 {
286        unsafe { sys::rtlsdr_get_freq_correction(self.dev) }
287    }
288
289    /// Set frequency correction value for the device in parts per million (ppm)
290    /// # Errors
291    /// This will return an error if the frequency cannot be retrieved by librtlsdr (e.g. libusb failure or device is not open)
292    pub fn set_freq_correction(&mut self, ppm: i32) -> Result<()> {
293        rtlsdr_result!(sys::rtlsdr_set_freq_correction(self.dev, ppm))?;
294        Ok(())
295    }
296
297    /// Get the tuner type
298    #[must_use]
299    pub fn get_tuner_type(&self) -> TunerType {
300        let tuner_type = unsafe { sys::rtlsdr_get_tuner_type(self.dev) };
301
302        match tuner_type {
303            1 => TunerType::E4000,
304            2 => TunerType::FC0012,
305            3 => TunerType::FC0013,
306            4 => TunerType::FC2580,
307            5 => TunerType::R820T,
308            6 => TunerType::R828D,
309            _ => TunerType::Unknown,
310        }
311    }
312
313    /// Get a list of gains supported by the tuner.
314    /// Gain values in tenths of a dB, 115 means 11.5 dB
315    /// # Errors
316    /// This will return an error if the frequency cannot be retrieved by librtlsdr (e.g. libusb failure or device is not open)
317    #[allow(clippy::cast_sign_loss)]
318    pub fn get_tuner_gains(&self) -> Result<Vec<i32>> {
319        let n = rtlsdr_result!(sys::rtlsdr_get_tuner_gains(self.dev, std::ptr::null_mut()))?;
320
321        let mut gains = vec![0i32; n as usize];
322
323        let n = rtlsdr_result!(sys::rtlsdr_get_tuner_gains(self.dev, gains.as_mut_ptr()))?;
324        gains.truncate(n as usize);
325
326        Ok(gains)
327    }
328
329    /// Get actual (RF / HF) gain the device is configured to - excluding the IF gain.
330    /// Gain in tenths of a dB, 115 means 11.5 dB.
331    /// # Errors
332    /// This will return an error if the gain cannot be retrieved by librtlsdr (e.g. libusb failure or device is not open)
333    pub fn get_tuner_gain(&self) -> Result<i32> {
334        match unsafe { sys::rtlsdr_get_tuner_gain(self.dev) } {
335            0 => Err(RtlsdrError::Unspecified(0)),
336            gain => Ok(gain),
337        }
338    }
339
340    /// Set the gain for the device.
341    /// Manual gain mode must be enabled for this to work.
342    /// Valid gain values may be queried with [`Device::get_tuner_gains`] function.
343    /// Gain in tenths of a dB, 115 means 11.5 dB
344    /// # Errors
345    /// This will return an error if the gain cannot be set by librtlsdr (e.g. libusb failure or device is not open)
346    pub fn set_tuner_gain(&mut self, gain: i32) -> Result<()> {
347        rtlsdr_result!(sys::rtlsdr_set_tuner_gain(self.dev, gain))?;
348        Ok(())
349    }
350
351    /// Set the bandwidth for the device in Hz. Zero means automatic BW selection.
352    /// # Errors
353    /// This will return an error if the bandwidth cannot be set by librtlsdr (e.g. libusb failure or device is not open)
354    pub fn set_tuner_bandwidth(&mut self, bw: u32) -> Result<()> {
355        rtlsdr_result!(sys::rtlsdr_set_tuner_bandwidth(self.dev, bw))?;
356        Ok(())
357    }
358
359    /// Set the intermediate frequency gain for the device.
360    /// - `stage` intermediate frequency gain stage number (1 to 6 for E4000)
361    /// - `gain` in tenths of a dB, -30 means -3.0 dB.
362    /// # Errors
363    /// This will return an error if the IF gain cannot be set by librtlsdr (e.g. libusb failure or device is not open)
364    pub fn set_tuner_if_gain(&mut self, stage: i32, gain: i32) -> Result<()> {
365        rtlsdr_result!(sys::rtlsdr_set_tuner_if_gain(self.dev, stage, gain))?;
366        Ok(())
367    }
368
369    /// Set the gain mode (automatic/manual) for the device.
370    /// Manual gain mode must be enabled for the gain setter function to work.
371    /// # Errors
372    /// This will return an error if the gain mode cannot be set by librtlsdr (e.g. libusb failure or device is not open)
373    pub fn set_tuner_gain_mode(&mut self, manual: bool) -> Result<()> {
374        rtlsdr_result!(sys::rtlsdr_set_tuner_gain_mode(self.dev, i32::from(manual)))?;
375        Ok(())
376    }
377
378    /// Get actual sample rate the device is configured to in Hz
379    /// # Errors
380    /// This will return an error if the sample rate cannot be retrieved by librtlsdr (e.g. libusb failure or device is not open)
381    pub fn get_sample_rate(&self) -> Result<u32> {
382        match unsafe { sys::rtlsdr_get_sample_rate(self.dev) } {
383            0 => Err(RtlsdrError::Unspecified(0)),
384            rate => Ok(rate),
385        }
386    }
387
388    /// Set the sample rate for the device in Hz
389    /// # Errors
390    /// This will return an error if the sample rate cannot be set by librtlsdr (e.g. libusb failure or device is not open)
391    pub fn set_sample_rate(&mut self, rate: u32) -> Result<()> {
392        rtlsdr_result!(sys::rtlsdr_set_sample_rate(self.dev, rate))?;
393        Ok(())
394    }
395
396    /// Enable test mode that returns an 8 bit counter instead of the samples.
397    /// The counter is generated inside the RTL2832.
398    /// # Errors
399    /// This will return an error if the test mode cannot be set by librtlsdr (e.g. libusb failure or device is not open)
400    pub fn set_test_mode(&mut self, test_mode: bool) -> Result<()> {
401        rtlsdr_result!(sys::rtlsdr_set_testmode(self.dev, i32::from(test_mode)))?;
402        Ok(())
403    }
404
405    /// Enable or disable the internal digital AGC of the RTL2832.
406    /// # Errors
407    /// This will return an error if the AGC mode cannot be set by librtlsdr (e.g. libusb failure or device is not open)
408    pub fn set_agc_mode(&mut self, enabled: bool) -> Result<()> {
409        rtlsdr_result!(sys::rtlsdr_set_agc_mode(self.dev, i32::from(enabled)))?;
410        Ok(())
411    }
412
413    /// Get state of the direct sampling mode
414    /// # Errors
415    /// This will return an error if the direct sampling mode cannot be retrieved by librtlsdr (e.g. libusb failure or device is not open)
416    pub fn get_direct_sampling(&self) -> Result<DirectSampling> {
417        rtlsdr_result!(sys::rtlsdr_get_direct_sampling(self.dev)).map(|mode| match mode {
418            1 => DirectSampling::I,
419            2 => DirectSampling::Q,
420            _ => DirectSampling::Disabled,
421        })
422    }
423
424    /// Enable or disable the direct sampling mode.
425    /// When enabled, the IF mode of the RTL2832 is activated, and [`Device::set_center_freq()`] will control the IF-frequency of the DDC,
426    /// which can be used to tune from 0 to 28.8 MHz (xtal frequency of the RTL2832).
427    /// # Errors
428    /// This will return an error if the direct sampling mode cannot be set by librtlsdr (e.g. libusb failure or device is not open)
429    pub fn set_direct_sampling(&mut self, mode: DirectSampling) -> Result<()> {
430        rtlsdr_result!(sys::rtlsdr_set_direct_sampling(self.dev, mode as i32))?;
431        Ok(())
432    }
433
434    /// Get state of the offset tuning mode
435    /// # Errors
436    /// This will return an error if the offset tuning mode cannot be retrieved by librtlsdr (e.g. libusb failure or device is not open)
437    pub fn get_offset_tuning(&self) -> Result<bool> {
438        rtlsdr_result!(sys::rtlsdr_get_offset_tuning(self.dev)).map(|mode| mode == 1)
439    }
440
441    /// Enable or disable offset tuning for zero-IF tuners, which allows to avoid problems caused by the DC offset of the ADCs and 1/f noise.
442    /// # Errors
443    /// This will return an error if the offset tuning mode cannot be set by librtlsdr (e.g. libusb failure or device is not open)
444    pub fn set_offset_tuning(&mut self, enabled: bool) -> Result<()> {
445        rtlsdr_result!(sys::rtlsdr_set_offset_tuning(self.dev, i32::from(enabled)))?;
446        Ok(())
447    }
448
449    /// Reset buffer in RTL2832
450    /// # Errors
451    /// This will return an error if the buffer cannot be reset by librtlsdr (e.g. libusb failure or device is not open)
452    pub fn reset_buffer(&mut self) -> Result<()> {
453        rtlsdr_result!(sys::rtlsdr_reset_buffer(self.dev))?;
454        Ok(())
455    }
456
457    /// Read data synchronously
458    /// Returns the number of bytes read.
459    /// # Errors
460    /// This will return an error if the samples cannot be read by librtlsdr (e.g. libusb failure or device is not open)
461    /// # Panics
462    /// This will panic if the buffer length exceeds the maximum value of i32 (2^31 - 1) bytes
463    pub fn read(&self, buf: &mut [u8]) -> Result<i32> {
464        let len = i32::try_from(buf.len()).expect("Buffer length exceeds maximum value of u32");
465
466        let mut n_read = 0;
467
468        rtlsdr_result!(sys::rtlsdr_read_sync(
469            self.dev,
470            buf.as_mut_ptr().cast::<std::ffi::c_void>(),
471            len,
472            &raw mut n_read
473        ))?;
474
475        Ok(n_read)
476    }
477
478    /// Read samples from the device asynchronously.
479    /// This will block until the asynchronous reading is canceled.
480    ///
481    /// - `cb`: callback function to return received samples, which should return true to cancel further reading or false to continue reading.
482    /// - `buf_num` optional buffer count, `buf_num` * `buf_len` = overall buffer size set to 0 for default buffer count (15)
483    /// - `buf_len` optional buffer length, must be multiple of 512, should be a multiple of 16384 (URB size),
484    ///   set to 0 for default buffer length (16 * 32 * 512)
485    /// # Errors
486    /// This will return an error if the asynchronous reading cannot be started by librtlsdr (e.g. libusb failure or device is not open)
487    /// # Panics
488    /// This will panic if canceling the asynchronous reading from the callback fails
489    pub fn start_reading<F>(&self, mut cb: F, buf_num: u32, buf_len: u32) -> Result<()>
490    where
491        F: FnMut(Vec<u8>) -> bool,
492    {
493        struct Ctx<'a, F> {
494            callback: &'a mut F,
495            dev: *mut sys::rtlsdr_dev,
496        }
497
498        unsafe extern "C" fn _cb<F>(buf: *mut u8, len: u32, ctx: *mut std::ffi::c_void)
499        where
500            F: FnMut(Vec<u8>) -> bool,
501        {
502            let ctx = &mut *ctx.cast::<Ctx<F>>();
503
504            let mut vec = Vec::with_capacity(len as usize);
505
506            let ptr: *mut u8 = vec.as_mut_ptr();
507            ptr.copy_from_nonoverlapping(buf, len as usize);
508
509            vec.set_len(len as usize);
510
511            let cancel = (*ctx.callback)(vec);
512
513            if cancel {
514                rtlsdr_result!(sys::rtlsdr_cancel_async(ctx.dev))
515                    .expect("Failed to cancel async from callback");
516            }
517        }
518
519        let mut ctx = Ctx {
520            callback: &mut cb,
521            dev: self.dev,
522        };
523
524        rtlsdr_result!(sys::rtlsdr_read_async(
525            self.dev,
526            Some(_cb::<F>),
527            (&raw mut ctx).cast::<std::ffi::c_void>(),
528            buf_num,
529            buf_len
530        ))?;
531
532        Ok(())
533    }
534
535    /// Enable or disable (the bias tee on) GPIO PIN 0 - if not reconfigured.
536    /// This works for rtl-sdr.com v3 dongles, see <http://www.rtl-sdr.com/rtl-sdr-blog-v-3-dongles-user-guide/>
537    /// Note: [`Device::close()`] does not clear GPIO lines, so it leaves the (bias tee) line enabled if a client program
538    /// doesn't explictly disable it.
539    /// # Errors
540    /// This will return an error if the bias tee cannot be set by librtlsdr (e.g. libusb failure or device is not open)
541    pub fn set_bias_tee(&mut self, on: bool) -> Result<()> {
542        rtlsdr_result!(sys::rtlsdr_set_bias_tee(self.dev, i32::from(on)))?;
543        Ok(())
544    }
545
546    /// Enable or disable (the bias tee on) the given GPIO pin.
547    /// Note: [`Device::close()`] does not clear GPIO lines, so it leaves the (bias tee) lines enabled if a client program
548    /// doesn't explictly disable it.
549    /// - `gpio_pin` needs to be in 0 .. 7. BUT pin 4 is connected to Tuner RESET.
550    ///   and for FC0012 is already connected/reserved pin 6 for switching V/U-HF.
551    /// # Errors
552    /// This will return an error if the bias tee cannot be set by librtlsdr (e.g. libusb failure or device is not open)
553    pub fn set_bias_tee_gpio(&mut self, gpio_pin: i32, on: bool) -> Result<()> {
554        rtlsdr_result!(sys::rtlsdr_set_bias_tee_gpio(
555            self.dev,
556            gpio_pin,
557            i32::from(on)
558        ))?;
559        Ok(())
560    }
561}
562
563#[doc = "Get all available devices"]
564#[must_use]
565pub fn get_devices() -> Vec<Device> {
566    let n = unsafe { sys::rtlsdr_get_device_count() };
567
568    (0..n)
569        .map(|index| Device {
570            index,
571            dev: std::ptr::null_mut(),
572        })
573        .collect()
574}