soapysdr/
device.rs

1use soapysdr_sys::*;
2pub use soapysdr_sys::SoapySDRRange as Range;
3use std::slice;
4use std::ptr;
5use std::sync::Arc;
6use std::ffi::{ CStr, CString };
7use std::os::raw::{c_int, c_char};
8use std::os::raw::c_void;
9use std::marker::PhantomData;
10
11use super::{ Args, ArgInfo, StreamSample, Format };
12use crate::arginfo::arg_info_from_c;
13
14/// An error code from SoapySDR
15#[repr(i32)]
16#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
17#[non_exhaustive]
18pub enum ErrorCode {
19    /// Returned when read has a timeout.
20    Timeout = -1,
21
22    /// Returned for non-specific stream errors.
23    StreamError = -2,
24
25    /// Returned when read has data corruption.
26    /// For example, the driver saw a malformed packet.
27    Corruption = -3,
28
29    /// Returned when read has an overflow condition.
30    /// For example, and internal buffer has filled.
31    Overflow = -4,
32
33    /// Returned when a requested operation or flag setting
34    /// is not supported by the underlying implementation.
35    NotSupported = -5,
36
37    /// Returned when a the device encountered a stream time
38    /// which was expired (late) or too early to process.
39    TimeError = -6,
40
41    /// Returned when write caused an underflow condition.
42    /// For example, a continuous stream was interrupted.
43    Underflow = -7,
44
45    /// Error without a specific code, see error string
46    Other = 0,
47}
48
49impl ErrorCode {
50    fn from_c(code: c_int) -> ErrorCode {
51        match code {
52            SOAPY_SDR_TIMEOUT       =>  ErrorCode::Timeout,
53            SOAPY_SDR_STREAM_ERROR  =>  ErrorCode::StreamError,
54            SOAPY_SDR_CORRUPTION    =>  ErrorCode::Corruption,
55            SOAPY_SDR_OVERFLOW      =>  ErrorCode::Overflow,
56            SOAPY_SDR_NOT_SUPPORTED =>  ErrorCode::NotSupported,
57            SOAPY_SDR_TIME_ERROR    =>  ErrorCode::TimeError,
58            SOAPY_SDR_UNDERFLOW     =>  ErrorCode::Underflow,
59            _                       =>  ErrorCode::Other,
60        }
61    }
62}
63
64/// An error type combining an error code and a string message
65#[derive(Clone, Debug, Hash)]
66pub struct Error {
67    pub code: ErrorCode,
68    pub message: String,
69}
70
71impl ::std::fmt::Display for Error {
72    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
73        write!(f, "{:?}: {}", self.code, self.message)
74    }
75}
76
77impl ::std::error::Error for Error {
78    fn description(&self) -> &str {
79        &self.message[..]
80    }
81}
82
83/// Transmit or Receive
84#[repr(u32)]
85#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
86pub enum Direction {
87    /// Transmit direction
88    Tx = SOAPY_SDR_TX,
89
90    /// Receive direction
91    Rx = SOAPY_SDR_RX
92}
93
94impl From<Direction> for c_int {
95    fn from(f: Direction) -> c_int {
96        f as c_int
97    }
98}
99
100struct DeviceInner {
101    ptr: *mut SoapySDRDevice,
102}
103
104/// Device method implementations are required to be thread safe
105unsafe impl Send for DeviceInner {}
106unsafe impl Sync for DeviceInner {}
107
108impl Drop for DeviceInner {
109    fn drop(&mut self) {
110        unsafe {
111            SoapySDRDevice_unmake(self.ptr);
112        }
113    }
114}
115
116/// An opened SDR hardware device.
117#[derive(Clone)]
118pub struct Device {
119    inner: Arc<DeviceInner>,
120}
121
122impl Device {
123    fn from_ptr(ptr: *mut SoapySDRDevice) -> Device {
124        Device {
125            inner: Arc::new(DeviceInner {
126                ptr,
127            }),
128        }
129    }
130}
131
132fn last_error_str() -> String {
133    unsafe {
134        // Capture error string from thread local storage
135        CStr::from_ptr(SoapySDRDevice_lastError()).to_string_lossy().into()
136    }
137}
138
139fn check_error<T>(r: T) -> Result<T, Error> {
140    unsafe {
141        if SoapySDRDevice_lastStatus() == 0 {
142            Ok(r)
143        } else {
144            Err(Error {
145                code: ErrorCode::Other,
146                message: last_error_str(),
147            })
148        }
149    }
150}
151
152fn check_ret_error(r: c_int) -> Result<(), Error> {
153    if r == 0 {
154        Ok(())
155    } else {
156        Err(Error {
157            code: ErrorCode::from_c(r),
158            message: last_error_str(),
159        })
160    }
161}
162
163fn len_result(ret: c_int) -> Result<c_int, Error> {
164    if ret >= 0 {
165        Ok(ret)
166    } else {
167        Err(Error {
168            code: ErrorCode::from_c(ret),
169            message: last_error_str(),
170        })
171    }
172}
173
174unsafe fn string_result(r: *mut c_char) -> Result<String, Error> {
175    let ptr: *mut c_char = check_error(r)?;
176    let ret = CStr::from_ptr(ptr).to_string_lossy().into();
177    SoapySDR_free(ptr as *mut c_void);
178    Ok(ret)
179}
180
181unsafe fn string_list_result<F: FnOnce(*mut usize) -> *mut *mut c_char>(f: F) -> Result<Vec<String>, Error> {
182    let mut len: usize = 0;
183    let mut ptr = check_error(f(&mut len as *mut _))?;
184    let ret = slice::from_raw_parts(ptr, len).iter()
185        .map(|&p| CStr::from_ptr(p).to_string_lossy().into())
186        .collect();
187    SoapySDRStrings_clear(&mut ptr as *mut _, len);
188    Ok(ret)
189}
190
191unsafe fn arg_info_result<F: FnOnce(*mut usize) -> *mut SoapySDRArgInfo>(f: F) -> Result<Vec<ArgInfo>, Error> {
192    let mut len: usize = 0;
193    let ptr = check_error(f(&mut len as *mut _))?;
194    let r = slice::from_raw_parts(ptr, len).iter().map(|x| arg_info_from_c(x)).collect();
195    SoapySDRArgInfoList_clear(ptr, len);
196    Ok(r)
197}
198
199unsafe fn list_result<T: Copy, F: FnOnce(*mut usize) -> *mut T>(f: F) -> Result<Vec<T>, Error> {
200    let mut len: usize = 0;
201    let ptr = check_error(f(&mut len as *mut _))?;
202    let ret = slice::from_raw_parts(ptr, len).to_owned();
203    SoapySDR_free(ptr as *mut c_void);
204    Ok(ret)
205}
206
207fn optional_string_arg<S: AsRef<str>>(optstr: Option<S>) -> CString {
208    match optstr {
209        Some(s) => CString::new(s.as_ref()).expect("Optional arg string contains null"),
210        None => CString::new("").unwrap(),
211    }
212}
213
214/// Enumerate a list of available devices on the system.
215///
216/// `args`: a set of arguments to filter the devices returned.
217///
218/// # Example (list all devices)
219/// ```
220/// for dev in soapysdr::enumerate("").unwrap() {
221///     println!("{}", dev);
222/// }
223/// ```
224///
225/// This function returns a list of argument lists that can be passed to `Device::new()` to
226/// open the device.
227pub fn enumerate<A: Into<Args>>(args: A) -> Result<Vec<Args>, Error> {
228    unsafe {
229        let mut len: usize = 0;
230        let devs = check_error(SoapySDRDevice_enumerate(args.into().as_raw_const(), &mut len as *mut _))?;
231        let args = slice::from_raw_parts(devs, len).iter().map(|&arg| Args::from_raw(arg)).collect();
232        SoapySDR_free(devs as *mut c_void);
233        Ok(args)
234    }
235}
236
237impl Device {
238    /// Find and open a device matching a set of filters.
239    ///
240    /// # Example
241    /// ```
242    /// let mut d = soapysdr::Device::new("type=null").unwrap();
243    /// ```
244    pub fn new<A: Into<Args>>(args: A) -> Result<Device, Error> {
245        unsafe {
246            let d = check_error(SoapySDRDevice_make(args.into().as_raw_const()))?;
247            Ok(Device::from_ptr(d))
248        }
249    }
250
251    #[doc(hidden)]
252    pub fn null_device() -> Device {
253        Device::new("type=null").unwrap()
254    }
255
256    /// A key that uniquely identifies the device driver.
257    ///
258    /// This key identifies the underlying implementation.
259    /// Several variants of a product may share a driver.
260    pub fn driver_key(&self) -> Result<String, Error> {
261        unsafe {
262            string_result(SoapySDRDevice_getDriverKey(self.inner.ptr))
263        }
264    }
265
266    /// A key that uniquely identifies the hardware.
267    ///
268    /// This key should be meaningful to the user to optimize for the underlying hardware.
269    pub fn hardware_key(&self) -> Result<String, Error> {
270        unsafe {
271            string_result(SoapySDRDevice_getDriverKey(self.inner.ptr))
272        }
273    }
274
275    /// Query a dictionary of available device information.
276    ///
277    /// This dictionary can any number of values like
278    /// vendor name, product name, revisions, serials...
279    ///
280    /// This information can be displayed to the user
281    /// to help identify the instantiated device.
282    pub fn hardware_info(&self) -> Result<Args, Error> {
283        unsafe {
284            check_error(SoapySDRDevice_getHardwareInfo(self.inner.ptr)).map(|x| Args::from_raw(x))
285        }
286    }
287
288    /// Get the mapping configuration string.
289    pub fn frontend_mapping(&self, direction: Direction) -> Result<String, Error> {
290        unsafe {
291            string_result(SoapySDRDevice_getFrontendMapping(self.inner.ptr, direction.into()))
292        }
293    }
294
295    /// List the device's sensors.
296    pub fn list_sensors(&self) -> Result<Vec<String>, Error> {
297        unsafe {
298            string_list_result(|len_ptr| SoapySDRDevice_listSensors(self.inner.ptr, len_ptr))
299        }
300    }
301
302    /// Read sensor value.
303    pub fn read_sensor(&self, key: &str) -> Result<String, Error> {
304        let key_c = CString::new(key).expect("key contains null byte");
305        unsafe {
306            string_result(SoapySDRDevice_readSensor(self.inner.ptr, key_c.as_ptr() as *const i8))
307        }
308    }
309
310    /// Get channel sensor info.
311    pub fn get_channel_sensor_info(&self, dir: Direction, channel: usize, key: &str) -> Result<ArgInfo, Error> {
312        let key_c = CString::new(key).expect("key contains null byte");
313        Ok(unsafe {
314               arg_info_from_c(&SoapySDRDevice_getChannelSensorInfo(
315                       self.inner.ptr, dir.into(), channel, key_c.as_ptr() as *const i8))
316        })
317    }
318
319    /// List the channel's sensors.
320    pub fn list_channel_sensors(&self, dir: Direction, channel: usize) -> Result<Vec<String>, Error> {
321        unsafe {
322            string_list_result(|len_ptr| SoapySDRDevice_listChannelSensors(self.inner.ptr, dir.into(), channel, len_ptr))
323        }
324    }
325
326    /// Read channel sensor value.
327    pub fn read_channel_sensor(&self, dir: Direction, channel: usize, key: &str) -> Result<String, Error> {
328        let key_c = CString::new(key).expect("key contains null byte");
329        unsafe {
330            string_result(SoapySDRDevice_readChannelSensor(self.inner.ptr, dir.into(), channel, key_c.as_ptr() as *const i8))
331        }
332    }
333
334    /// Get sensor info.
335    pub fn get_sensor_info(&self, key: &str) -> Result<ArgInfo, Error> {
336        let key_c = CString::new(key).expect("key contains null byte");
337        Ok(unsafe {
338               arg_info_from_c(&SoapySDRDevice_getSensorInfo(
339                       self.inner.ptr, key_c.as_ptr() as *const i8))
340        })
341    }
342
343    /// Set the frontend mapping of available DSP units to RF frontends.
344    ///
345    /// This controls channel mapping and channel availability.
346    pub fn set_frontend_mapping<S: Into<Vec<u8>>>(&self, direction: Direction, mapping: S) -> Result<(), Error> {
347        unsafe {
348            let mapping_c = CString::new(mapping).expect("Mapping contains null byte");
349            SoapySDRDevice_setFrontendMapping(self.inner.ptr, direction.into(), mapping_c.as_ptr());
350            check_error(())
351        }
352    }
353
354    /// Get a number of channels given the streaming direction
355    pub fn num_channels(&self, direction: Direction) -> Result<usize, Error> {
356        unsafe {
357            check_error(SoapySDRDevice_getNumChannels(self.inner.ptr, direction.into()))
358        }
359    }
360
361    /// Get channel info given the streaming direction
362    pub fn channel_info(&self, direction: Direction, channel: usize) -> Result<Args, Error> {
363        unsafe {
364            check_error(SoapySDRDevice_getChannelInfo(self.inner.ptr, direction.into(), channel)).map(|x| Args::from_raw(x))
365        }
366    }
367
368    /// Find out if the specified channel is full or half duplex.
369    ///
370    /// Returns `true` for full duplex, `false` for half duplex.
371    pub fn full_duplex(&self, direction: Direction, channel: usize) -> Result<bool, Error> {
372        unsafe {
373            check_error(SoapySDRDevice_getFullDuplex(self.inner.ptr, direction.into(), channel))
374        }
375    }
376
377    /// Query a list of the available stream formats.
378    pub fn stream_formats(&self, direction: Direction, channel: usize) -> Result<Vec<Format>, Error> {
379        unsafe {
380            let mut len: usize = 0;
381            let mut ptr = check_error(SoapySDRDevice_getStreamFormats(self.inner.ptr, direction.into(), channel, &mut len as *mut _))?;
382            let ret = slice::from_raw_parts(ptr, len).iter()
383                .flat_map(|&p| CStr::from_ptr(p).to_str().ok())
384                .flat_map(|s| s.parse().ok())
385                .collect();
386            SoapySDRStrings_clear(&mut ptr as *mut _, len);
387            Ok(ret)
388        }
389    }
390
391    /// Get the hardware's native stream format and full-scale value for this channel.
392    ///
393    /// This is the format used by the underlying transport layer,
394    /// and the direct buffer access API calls (when available).
395    pub fn native_stream_format(&self, direction: Direction, channel: usize) -> Result<(Format, f64), Error> {
396        unsafe {
397            let mut fullscale: f64 = 0.0;
398            let ptr = check_error(SoapySDRDevice_getNativeStreamFormat(self.inner.ptr, direction.into(), channel, &mut fullscale as *mut _))?;
399
400            let format = CStr::from_ptr(ptr).to_str().ok()
401                .and_then(|s| s.parse().ok())
402                .ok_or_else(|| Error { code: ErrorCode::Other, message: "Invalid stream format returned by SoapySDR".into()})?;
403
404            Ok((format, fullscale))
405        }
406    }
407
408    /// Query the argument info description for stream args.
409    pub fn stream_args_info(&self, direction: Direction, channel: usize) -> Result<Vec<ArgInfo>, Error> {
410        unsafe {
411            arg_info_result(|len_ptr| SoapySDRDevice_getStreamArgsInfo(self.inner.ptr, direction.into(), channel, len_ptr))
412        }
413    }
414
415    ///  Initialize an RX stream given a list of channels
416    pub fn rx_stream<E: StreamSample>(&self, channels: &[usize]) -> Result<RxStream<E>, Error> {
417        self.rx_stream_args(channels, ())
418    }
419
420    ///  Initialize an RX stream given a list of channels and stream arguments.
421    pub fn rx_stream_args<E: StreamSample, A: Into<Args>>(&self, channels: &[usize], args: A) -> Result<RxStream<E>, Error> {
422        unsafe {
423            let mut stream: *mut SoapySDRStream = ptr::null_mut();
424            check_error(SoapySDRDevice_setupStream(self.inner.ptr,
425                &mut stream as *mut _,
426                Direction::Rx.into(),
427                E::STREAM_FORMAT.as_ptr(),
428                channels.as_ptr(), channels.len(),
429                args.into().as_raw_const()
430            )).map(|_| RxStream {
431                device: self.clone(),
432                handle: stream,
433                nchannels: channels.len(),
434                flags: 0,
435                time_ns: 0,
436                active: false,
437                phantom: PhantomData,
438            })
439        }
440    }
441
442    /// Initialize a TX stream given a list of channels and stream arguments.
443    pub fn tx_stream<E: StreamSample>(&self, channels: &[usize]) -> Result<TxStream<E>, Error> {
444        self.tx_stream_args(channels, ())
445    }
446
447    /// Initialize a TX stream given a list of channels and stream arguments.
448    pub fn tx_stream_args<E: StreamSample, A: Into<Args>>(&self, channels: &[usize], args: A) -> Result<TxStream<E>, Error> {
449        unsafe {
450            let mut stream: *mut SoapySDRStream = ptr::null_mut();
451            check_error(SoapySDRDevice_setupStream(self.inner.ptr,
452                &mut stream as *mut _,
453                Direction::Tx.into(),
454                E::STREAM_FORMAT.as_ptr(),
455                channels.as_ptr(), channels.len(),
456                args.into().as_raw_const()
457            )).map(|_| TxStream {
458                device: self.clone(),
459                handle: stream,
460                nchannels: channels.len(),
461                active: false,
462                phantom: PhantomData,
463            })
464        }
465    }
466
467    /// Get a list of available antennas to select on a given chain.
468    pub fn antennas(&self, direction: Direction, channel: usize) -> Result<Vec<String>, Error> {
469        unsafe {
470            string_list_result(|len_ptr| SoapySDRDevice_listAntennas(self.inner.ptr, direction.into(), channel, len_ptr))
471        }
472    }
473
474    /// Set the selected antenna on a chain.
475    pub fn set_antenna<S: Into<Vec<u8>>>(&self, direction: Direction, channel: usize, name: S) -> Result<(), Error> {
476        unsafe {
477            let name_c = CString::new(name).expect("Antenna name contains null byte");
478            SoapySDRDevice_setAntenna(self.inner.ptr, direction.into(), channel, name_c.as_ptr());
479            check_error(())
480        }
481    }
482
483    /// Get the selected antenna on a chain.
484    pub fn antenna(&self, direction: Direction, channel: usize) -> Result<String, Error> {
485        unsafe {
486            string_result(SoapySDRDevice_getAntenna(self.inner.ptr, direction.into(), channel))
487        }
488    }
489
490    /// Does the device support automatic DC offset corrections?
491    ///
492    /// Returns true if automatic corrections are supported
493    pub fn has_dc_offset_mode(&self, direction: Direction, channel: usize) -> Result<bool, Error> {
494        unsafe {
495            check_error(SoapySDRDevice_hasDCOffsetMode(self.inner.ptr, direction.into(), channel))
496        }
497    }
498
499    /// Enable or disable automatic DC offset corrections mode.
500    pub fn set_dc_offset_mode(&self, direction: Direction, channel: usize, automatic: bool) -> Result<(), Error> {
501        unsafe {
502            SoapySDRDevice_setDCOffsetMode(self.inner.ptr, direction.into(), channel, automatic);
503            check_error(())
504        }
505    }
506
507    /// Returns true if automatic DC offset mode is enabled
508    pub fn dc_offset_mode(&self, direction: Direction, channel: usize) -> Result<bool, Error> {
509        unsafe {
510            check_error(SoapySDRDevice_getDCOffsetMode(self.inner.ptr, direction.into(), channel))
511        }
512    }
513
514    /// Does the device support frontend DC offset corrections?
515    ///
516    /// Returns true if manual corrections are supported
517    pub fn has_dc_offset(&self, direction: Direction, channel: usize) -> Result<bool, Error> {
518        unsafe {
519            check_error(SoapySDRDevice_hasDCOffset(self.inner.ptr, direction.into(), channel))
520        }
521    }
522
523    /// Set the frontend DC offset correction.
524    ///
525    /// The offsets are configured for each of the I and Q components (1.0 max)
526    pub fn set_dc_offset(&self, direction: Direction, channel: usize, offset_i: f64, offset_q: f64) -> Result<(), Error> {
527        unsafe {
528            SoapySDRDevice_setDCOffset(self.inner.ptr, direction.into(), channel, offset_i, offset_q);
529            check_error(())
530        }
531    }
532
533    /// Get the frontend DC offset correction for (I, Q), 1.0 max
534    pub fn dc_offset(&self, direction: Direction, channel: usize) -> Result<(f64, f64), Error> {
535        unsafe {
536            let mut i: f64 = 0.0;
537            let mut q: f64 = 0.0;
538            SoapySDRDevice_getDCOffset(self.inner.ptr, direction.into(), channel, &mut i as *mut _, &mut q as *mut _);
539            check_error((i, q))
540        }
541    }
542
543    /// Does the device support frontend IQ balance correction?
544    ///
545    /// Returns true if IQ balance corrections are supported.
546    pub fn has_iq_balance(&self, direction: Direction, channel: usize) -> Result<bool, Error> {
547        unsafe {
548            check_error(SoapySDRDevice_hasIQBalance(self.inner.ptr, direction.into(), channel))
549        }
550    }
551
552    /// Set the frontend IQ balance correction
553    ///
554    /// The correction is configured for each of the I and Q components (1.0 max)
555    pub fn set_iq_balance(&self, direction: Direction, channel: usize, balance_i: f64, balance_q: f64) -> Result<(), Error> {
556        unsafe {
557            SoapySDRDevice_setIQBalance(self.inner.ptr, direction.into(), channel, balance_i, balance_q);
558            check_error(())
559        }
560    }
561
562    /// Get the frontend IQ balance correction for (I, Q), 1.0 max
563    pub fn iq_balance(&self, direction: Direction, channel: usize) -> Result<(f64, f64), Error> {
564        unsafe {
565            let mut i: f64 = 0.0;
566            let mut q: f64 = 0.0;
567            SoapySDRDevice_getIQBalance(self.inner.ptr, direction.into(), channel, &mut i as *mut _, &mut q as *mut _);
568            check_error((i, q))
569        }
570    }
571
572    /// List available amplification elements.
573    ///
574    /// Elements should be in order RF to baseband.
575    pub fn list_gains(&self, direction: Direction, channel: usize) -> Result<Vec<String>, Error> {
576        unsafe {
577            string_list_result(|len_ptr| SoapySDRDevice_listGains(self.inner.ptr, direction.into(), channel, len_ptr))
578        }
579    }
580
581    /// Does the device support automatic gain control?
582    pub fn has_gain_mode(&self, direction: Direction, channel: usize) -> Result<bool, Error> {
583        unsafe {
584            check_error(SoapySDRDevice_hasGainMode(self.inner.ptr, direction.into(), channel))
585        }
586    }
587
588    /// Enable or disable automatic gain control.
589    pub fn set_gain_mode(&self, direction: Direction, channel: usize, automatic: bool) -> Result<(), Error> {
590        unsafe {
591            SoapySDRDevice_setGainMode(self.inner.ptr, direction.into(), channel, automatic);
592            check_error(())
593        }
594    }
595
596    /// Returns true if automatic gain control is enabled
597    pub fn gain_mode(&self, direction: Direction, channel: usize) -> Result<bool, Error> {
598        unsafe {
599            check_error(SoapySDRDevice_getGainMode(self.inner.ptr, direction.into(), channel))
600        }
601    }
602
603    /// Set the overall amplification in a chain.
604    ///
605    /// The gain will be distributed automatically across available elements.
606    ///
607    /// `gain`: the new amplification value in dB
608    pub fn set_gain(&self, direction: Direction, channel: usize, gain: f64) -> Result<(), Error> {
609        unsafe {
610            SoapySDRDevice_setGain(self.inner.ptr, direction.into(), channel, gain);
611            check_error(())
612        }
613    }
614
615    /// Get the overall value of the gain elements in a chain in dB.
616    pub fn gain(&self, direction: Direction, channel: usize) -> Result<f64, Error> {
617        unsafe {
618            check_error(SoapySDRDevice_getGain(self.inner.ptr, direction.into(), channel))
619        }
620    }
621
622    /// Get the overall range of possible gain values.
623    pub fn gain_range(&self, direction: Direction, channel: usize) -> Result<Range, Error> {
624        unsafe {
625            check_error(SoapySDRDevice_getGainRange(self.inner.ptr, direction.into(), channel))
626        }
627    }
628
629    /// Set the value of a amplification element in a chain.
630    ///
631    /// # Arguments
632    /// * `name`: the name of an amplification element from `Device::list_gains`
633    /// * `gain`: the new amplification value in dB
634    pub fn set_gain_element<S: Into<Vec<u8>>>(&self, direction: Direction, channel: usize, name: S, gain: f64) -> Result<(), Error> {
635        unsafe {
636            let name_c = CString::new(name).expect("Gain name contains null byte");
637            SoapySDRDevice_setGainElement(self.inner.ptr, direction.into(), channel, name_c.as_ptr(), gain);
638            check_error(())
639        }
640    }
641
642    /// Get the value of an individual amplification element in a chain in dB.
643    pub fn gain_element<S: Into<Vec<u8>>>(&self, direction: Direction, channel: usize, name: S) -> Result<f64, Error> {
644        unsafe {
645            let name_c = CString::new(name).expect("Gain name contains null byte");
646            check_error(SoapySDRDevice_getGainElement(self.inner.ptr, direction.into(), channel, name_c.as_ptr()))
647        }
648    }
649
650    /// Get the range of possible gain values for a specific element.
651    pub fn gain_element_range<S: Into<Vec<u8>>>(&self, direction: Direction, channel: usize, name: S) -> Result<Range, Error> {
652        unsafe {
653            let name_c = CString::new(name).expect("Gain name contains null byte");
654            check_error(SoapySDRDevice_getGainElementRange(self.inner.ptr, direction.into(), channel, name_c.as_ptr()))
655        }
656    }
657
658    /// Get the ranges of overall frequency values.
659    pub fn frequency_range(&self, direction: Direction, channel: usize) -> Result<Vec<Range>, Error> {
660        unsafe {
661            list_result(|len_ptr| SoapySDRDevice_getFrequencyRange(self.inner.ptr, direction.into(), channel, len_ptr))
662        }
663    }
664
665    /// Get the overall center frequency of the chain.
666    ///
667    ///   - For RX, this specifies the down-conversion frequency.
668    ///   - For TX, this specifies the up-conversion frequency.
669    ///
670    /// Returns the center frequency in Hz.
671    pub fn frequency(&self, direction: Direction, channel: usize) -> Result<f64, Error> {
672        unsafe {
673            check_error(SoapySDRDevice_getFrequency(self.inner.ptr, direction.into(), channel))
674        }
675    }
676
677    /// Set the center frequency of the chain.
678    ///
679    ///   - For RX, this specifies the down-conversion frequency.
680    ///   - For TX, this specifies the up-conversion frequency.
681    ///
682    /// The default implementation of `set_frequency` will tune the "RF"
683    /// component as close as possible to the requested center frequency in Hz.
684    /// Tuning inaccuracies will be compensated for with the "BB" component.
685    ///
686    /// The `args` can be used to augment the tuning algorithm.
687    ///
688    ///   - Use `"OFFSET"` to specify an "RF" tuning offset,
689    ///     usually with the intention of moving the LO out of the passband.
690    ///     The offset will be compensated for using the "BB" component.
691    ///   - Use the name of a component for the key and a frequency in Hz
692    ///     as the value (any format) to enforce a specific frequency.
693    ///     The other components will be tuned with compensation
694    ///     to achieve the specified overall frequency.
695    ///   - Use the name of a component for the key and the value `"IGNORE"`
696    ///     so that the tuning algorithm will avoid altering the component.
697    ///   - Vendor specific implementations can also use the same args to augment
698    ///     tuning in other ways such as specifying fractional vs integer N tuning.
699    ///
700    pub fn set_frequency<A: Into<Args>>(&self, direction: Direction, channel: usize, frequency: f64, args: A) -> Result<(), Error> {
701        unsafe {
702            SoapySDRDevice_setFrequency(self.inner.ptr, direction.into(), channel, frequency, args.into().as_raw_const());
703            check_error(())
704        }
705    }
706
707    /// List available tunable elements in the chain.
708    ///
709    /// Elements should be in order RF to baseband.
710    pub fn list_frequencies(&self, direction: Direction, channel: usize) -> Result<Vec<String>, Error> {
711        unsafe {
712            string_list_result(|len_ptr| SoapySDRDevice_listFrequencies(self.inner.ptr, direction.into(), channel, len_ptr))
713        }
714    }
715
716    /// Get the range of tunable values for the specified element.
717    pub fn component_frequency_range<S: Into<Vec<u8>>>(&self, direction: Direction, channel: usize, name: S) -> Result<Vec<Range>, Error> {
718        unsafe {
719            let name_c = CString::new(name).expect("Component name contains null byte");
720            list_result(|len_ptr| SoapySDRDevice_getFrequencyRangeComponent(self.inner.ptr, direction.into(), channel, name_c.as_ptr(), len_ptr))
721        }
722    }
723
724    /// Get the frequency of a tunable element in the chain.
725    pub fn component_frequency<S: Into<Vec<u8>>>(&self, direction: Direction, channel: usize, name: S) -> Result<f64, Error> {
726        unsafe {
727            let name_c = CString::new(name).expect("Component name contains null byte");
728            check_error(SoapySDRDevice_getFrequencyComponent(self.inner.ptr, direction.into(), channel, name_c.as_ptr()))
729        }
730    }
731
732    /// Tune the center frequency of the specified element.
733    ///
734    ///   - For RX, this specifies the down-conversion frequency.
735    ///   - For TX, this specifies the up-conversion frequency.
736    ///
737    /// Recommended names used to represent tunable components:
738    ///
739    ///   - "CORR" - freq error correction in PPM
740    ///   - "RF" - frequency of the RF frontend
741    ///   - "BB" - frequency of the baseband DSP
742    ///
743    pub fn set_component_frequency<S: Into<Vec<u8>>, A: Into<Args>>(&self, direction: Direction, channel: usize, name: S, frequency: f64, args: A) -> Result<(), Error> {
744        unsafe {
745            let name_c = CString::new(name).expect("Component name contains null byte");
746            SoapySDRDevice_setFrequencyComponent(self.inner.ptr, direction.into(), channel, name_c.as_ptr(), frequency, args.into().as_raw_const());
747            check_error(())
748        }
749    }
750
751    /// Query the argument info description for tune args.
752    pub fn frequency_args_info(&self, direction: Direction, channel: usize) -> Result<Vec<ArgInfo>, Error> {
753        unsafe {
754            arg_info_result(|len_ptr| SoapySDRDevice_getFrequencyArgsInfo(self.inner.ptr, direction.into(), channel, len_ptr))
755        }
756    }
757
758    /// Get the baseband sample rate of the chain in samples per second.
759    pub fn sample_rate(&self, direction: Direction, channel: usize) -> Result<f64, Error> {
760        unsafe {
761            check_error(SoapySDRDevice_getSampleRate(self.inner.ptr, direction.into(), channel))
762        }
763    }
764
765    /// Set the baseband sample rate of the chain in samples per second.
766    pub fn set_sample_rate(&self, direction: Direction, channel: usize, rate: f64) -> Result<(), Error> {
767        unsafe {
768            SoapySDRDevice_setSampleRate(self.inner.ptr, direction.into(), channel, rate);
769            check_error(())
770        }
771    }
772
773    /// Get the range of possible baseband sample rates.
774    pub fn get_sample_rate_range(&self, direction: Direction, channel: usize) -> Result<Vec<Range>, Error> {
775        unsafe {
776            list_result(|len_ptr| SoapySDRDevice_getSampleRateRange(self.inner.ptr, direction.into(), channel, len_ptr))
777        }
778    }
779
780    /// Get the baseband filter width of the chain in Hz
781    pub fn bandwidth(&self, direction: Direction, channel: usize) -> Result<f64, Error> {
782        unsafe {
783            check_error(SoapySDRDevice_getBandwidth(self.inner.ptr, direction.into(), channel))
784        }
785    }
786
787    /// Set the baseband filter width of the chain in Hz
788    pub fn set_bandwidth(&self, direction: Direction, channel: usize, bandwidth: f64) -> Result<(), Error> {
789        unsafe {
790            SoapySDRDevice_setBandwidth(self.inner.ptr, direction.into(), channel, bandwidth);
791            check_error(())
792        }
793    }
794
795    /// Get the ranges of possible baseband filter widths.
796    pub fn bandwidth_range(&self, direction: Direction, channel: usize) -> Result<Vec<Range>, Error> {
797        unsafe {
798            list_result(|len_ptr| SoapySDRDevice_getBandwidthRange(self.inner.ptr, direction.into(), channel, len_ptr))
799        }
800    }
801
802    /// List time sources
803    pub fn list_time_sources(&self) -> Result<Vec<String>, Error> {
804        unsafe { string_list_result(|len_ptr| SoapySDRDevice_listTimeSources(self.inner.ptr, len_ptr)) }
805    }
806
807    /// Get the current time source
808    pub fn get_time_source(&self) -> Result<String, Error> {
809        unsafe { string_result(SoapySDRDevice_getTimeSource(self.inner.ptr)) }
810    }
811
812    /// Set the current time source
813    pub fn set_time_source<S: Into<Vec<u8>>>(&self, time_source: S) -> Result<(), Error> {
814        let time_source = CString::new(time_source).expect("Time source contained null");
815        unsafe {
816            SoapySDRDevice_setTimeSource(self.inner.ptr, time_source.as_ptr());
817            check_error(())
818        }
819    }
820
821    /// Check whether there is a given hardware time source.
822    /// Hardware time sources are not the same as time sources (at least for UHD Devices)
823    /// UHD supported hw time sources: "PPS" or "" (i.e. None)
824    pub fn has_hardware_time(
825        &self,
826        hw_time_source: Option<&str>,
827    ) -> Result<bool, Error> {
828        let hw_time_source = optional_string_arg(hw_time_source);
829        unsafe {
830            let has_hw_time = SoapySDRDevice_hasHardwareTime(self.inner.ptr, hw_time_source.as_ptr());
831            check_error(has_hw_time)
832        }
833    }
834
835    /// Get the current timestamp in ns
836    pub fn get_hardware_time(
837        &self,
838        hw_time_source: Option<&str>,
839    ) -> Result<i64, Error> {
840        let hw_time_source = optional_string_arg(hw_time_source);
841        unsafe {
842            let tstamp = SoapySDRDevice_getHardwareTime(self.inner.ptr, hw_time_source.as_ptr());
843            check_error(tstamp)
844        }
845    }
846
847    /// Set the current hardware timestmap for the given source
848    /// UHD supported hardware times: "CMD","PPS","UNKNOWN_PPS"
849    pub fn set_hardware_time(
850        &self,
851        hw_time_source: Option<&str>,
852        new_time_ns: i64,
853    ) -> Result<(), Error> {
854        let hw_time_source = optional_string_arg(hw_time_source);
855        unsafe {
856            SoapySDRDevice_setHardwareTime(self.inner.ptr, new_time_ns, hw_time_source.as_ptr());
857            check_error(())
858        }
859    }
860
861    /// List clock sources
862    pub fn list_clock_sources(&self) -> Result<Vec<String>, Error> {
863        unsafe { string_list_result(|len_ptr| SoapySDRDevice_listClockSources(self.inner.ptr, len_ptr)) }
864    }
865
866    /// Get the current clock source
867    pub fn get_clock_source(&self) -> Result<String, Error> {
868        unsafe { string_result(SoapySDRDevice_getClockSource(self.inner.ptr)) }
869    }
870
871    /// Set the current clock source
872    pub fn set_clock_source<S: Into<Vec<u8>>>(&self, clock_source: S) -> Result<(), Error> {
873        let clock_source = CString::new(clock_source).expect("clock source contained null");
874        unsafe {
875            SoapySDRDevice_setClockSource(self.inner.ptr, clock_source.as_ptr());
876            check_error(())
877        }
878    }
879
880    // TODO: sensors
881
882    // TODO: registers
883
884    /// Write a setting
885    pub fn write_setting<S: Into<Vec<u8>>>(&self, key: S, value: S) -> Result<(), Error> {
886        let key = CString::new(key).expect("key must not contain null byte");
887        let value = CString::new(value).expect("value must not contain null byte");
888        unsafe {
889            check_ret_error(SoapySDRDevice_writeSetting(self.inner.ptr, key.as_ptr(), value.as_ptr()))?;
890            Ok(())
891        }
892    }
893
894    /// Read a setting
895    pub fn read_setting<S: Into<Vec<u8>>>(&self, key: S) -> Result<String, Error> {
896        let key = CString::new(key).expect("key must not contain null byte");
897        unsafe {
898            string_result(SoapySDRDevice_readSetting(self.inner.ptr, key.as_ptr()))
899        }
900    }
901
902    // TODO: gpio
903
904    // TODO: I2C
905
906    // TODO: SPI
907
908    // TODO: UART
909
910}
911
912/// A stream open for receiving.
913///
914/// To obtain a RxStream, call [Device::rx_stream]. The type parameter `E` represents the type
915/// of this stream's samples.
916///
917/// Streams may involve multiple channels.
918pub struct RxStream<E: StreamSample> {
919    device: Device,
920    handle: *mut SoapySDRStream,
921    nchannels: usize,
922    flags: i32,
923    time_ns: i64,
924    active: bool,
925    phantom: PhantomData<fn(&mut[E])>,
926}
927
928/// Streams may only be used on one thread at a time but may be sent between threads
929unsafe impl<E: StreamSample> Send for RxStream<E> {}
930
931impl<E: StreamSample> Drop for RxStream<E> {
932    fn drop(&mut self) {
933        unsafe {
934            if self.active {
935                self.deactivate(None).ok();
936            }
937            SoapySDRDevice_closeStream(self.device.inner.ptr, self.handle);
938        }
939    }
940}
941
942impl<E: StreamSample> RxStream<E> {
943    /// Get the stream's maximum transmission unit (MTU) in number of elements.
944    ///
945    /// The MTU specifies the maximum payload transfer in a stream operation.
946    /// This value can be used as a stream buffer allocation size that can
947    /// best optimize throughput given the underlying stream implementation.
948    pub fn mtu(&self) -> Result<usize, Error> {
949        unsafe {
950            check_error(SoapySDRDevice_getStreamMTU(self.device.inner.ptr, self.handle))
951        }
952    }
953
954    /// Activate a stream.
955    ///
956    /// Call `activate` to enable a stream before using `read()`
957    ///
958    /// # Arguments:
959    ///   * `time_ns` -- optional activation time in nanoseconds
960    pub fn activate(&mut self, time_ns: Option<i64>) -> Result<(), Error> {
961        if self.active { return Err(Error { code: ErrorCode::Other, message: "Stream is already active".into() }); }
962        unsafe {
963            let flags = if time_ns.is_some() { SOAPY_SDR_HAS_TIME as i32 } else { 0 };
964            check_ret_error(SoapySDRDevice_activateStream(self.device.inner.ptr, self.handle, flags, time_ns.unwrap_or(0), 0))?;
965            self.active = true;
966            Ok(())
967        }
968    }
969
970    /// Fetch the active state of the stream.
971    pub fn active(&self) -> bool {
972        self.active
973    }
974
975    // TODO: activate_burst()
976
977    /// Deactivate a stream.
978    /// The implementation will control switches or halt data flow.
979    ///
980    /// # Arguments:
981    ///   * `time_ns` -- optional deactivation time in nanoseconds
982    pub fn deactivate(&mut self, time_ns: Option<i64>) -> Result<(), Error> {
983        if !self.active { return Err(Error { code: ErrorCode::Other, message: "Stream is not active".into() }); }
984        unsafe {
985            let flags = if time_ns.is_some() { SOAPY_SDR_HAS_TIME as i32 } else { 0 };
986            check_ret_error(SoapySDRDevice_deactivateStream(self.device.inner.ptr, self.handle, flags, time_ns.unwrap_or(0)))?;
987            self.active = false;
988            Ok(())
989        }
990    }
991
992    /// Read samples from the stream into the provided buffers.
993    ///
994    /// `buffers` contains one destination slice for each channel of this stream.
995    ///
996    /// Returns the number of samples read, which may be smaller than the size of the passed arrays.
997    ///
998    /// # Panics
999    ///  * If `buffers` is not the same length as the `channels` array passed to `Device::rx_stream`.
1000    pub fn read(&mut self, buffers: &mut [&mut [E]], timeout_us: i64) -> Result<usize, Error> {
1001        unsafe {
1002            assert!(buffers.len() == self.nchannels);
1003
1004            let num_samples = buffers.iter().map(|b| b.len()).min().unwrap_or(0);
1005
1006            //TODO: avoid this allocation
1007            let buf_ptrs = buffers.iter().map(|b| b.as_ptr()).collect::<Vec<_>>();
1008
1009            self.flags = 0;
1010            let len = len_result(SoapySDRDevice_readStream(
1011                self.device.inner.ptr,
1012                self.handle,
1013                buf_ptrs.as_ptr() as *const *mut _,
1014                num_samples,
1015                &mut self.flags as *mut _,
1016                &mut self.time_ns as *mut _,
1017                timeout_us as _
1018            ))?;
1019
1020            Ok(len as usize)
1021        }
1022    }
1023
1024    /// Return timestamp of the last successful `read()` operation.
1025    pub fn time_ns(&self) -> i64 {
1026        self.time_ns
1027    }
1028}
1029
1030/// A stream open for transmitting.
1031///
1032/// To obtain a TxStream, call [Device::tx_stream]. The type parameter `E` represents the type
1033/// of this stream's samples.
1034///
1035/// Streams may involve multiple channels.
1036pub struct TxStream<E: StreamSample> {
1037    device: Device,
1038    handle: *mut SoapySDRStream,
1039    nchannels: usize,
1040    active: bool,
1041    phantom: PhantomData<fn(&[E])>,
1042}
1043
1044/// Streams may only be used on one thread at a time but may be sent between threads
1045unsafe impl<E: StreamSample> Send for TxStream<E> {}
1046
1047impl<E: StreamSample> Drop for TxStream<E> {
1048    fn drop(&mut self) {
1049        unsafe {
1050            if self.active {
1051                self.deactivate(None).ok();
1052            }
1053            SoapySDRDevice_closeStream(self.device.inner.ptr, self.handle);
1054        }
1055    }
1056}
1057
1058impl<E: StreamSample> TxStream<E> {
1059    /// Get the stream's maximum transmission unit (MTU) in number of elements.
1060    ///
1061    /// The MTU specifies the maximum payload transfer in a stream operation.
1062    /// This value can be used as a stream buffer allocation size that can
1063    /// best optimize throughput given the underlying stream implementation.
1064    pub fn mtu(&self) -> Result<usize, Error> {
1065        unsafe {
1066            check_error(SoapySDRDevice_getStreamMTU(self.device.inner.ptr, self.handle))
1067        }
1068    }
1069
1070    /// Activate a stream.
1071    ///
1072    /// Call `activate` to enable a stream before using `write()`
1073    ///
1074    /// # Arguments:
1075    ///   * `time_ns` -- optional activation time in nanoseconds
1076    pub fn activate(&mut self, time_ns: Option<i64>) -> Result<(), Error> {
1077        if self.active { return Err(Error { code: ErrorCode::Other, message: "Stream is already active".into() }); }
1078        unsafe {
1079            let flags = if time_ns.is_some() { SOAPY_SDR_HAS_TIME as i32 } else { 0 };
1080            check_ret_error(SoapySDRDevice_activateStream(self.device.inner.ptr, self.handle, flags, time_ns.unwrap_or(0), 0))?;
1081            self.active = true;
1082            Ok(())
1083        }
1084    }
1085
1086    /// Fetch the active state of the stream.
1087    pub fn active(&self) -> bool {
1088        self.active
1089    }
1090
1091    /// Deactivate a stream.
1092    /// The implementation will control switches or halt data flow.
1093    ///
1094    /// # Arguments:
1095    ///   * `time_ns` -- optional deactivation time in nanoseconds
1096    pub fn deactivate(&mut self, time_ns: Option<i64>) -> Result<(), Error> {
1097        if !self.active { return Err(Error { code: ErrorCode::Other, message: "Stream is not active".into() }); }
1098        unsafe {
1099            let flags = if time_ns.is_some() { SOAPY_SDR_HAS_TIME as i32 } else { 0 };
1100            check_ret_error(SoapySDRDevice_deactivateStream(self.device.inner.ptr, self.handle, flags, time_ns.unwrap_or(0)))?;
1101            self.active = false;
1102            Ok(())
1103        }
1104    }
1105
1106    /// Attempt to write samples to the device from the provided buffer.
1107    ///
1108    /// The stream must first be [activated](TxStream::activate).
1109    ///
1110    /// `buffers` contains one source slice for each channel of the stream.
1111    ///
1112    /// `at_ns` is an optional nanosecond precision device timestamp at which
1113    /// the device is to begin the transmission (c.f. [get_hardware_time](Device::get_hardware_time)).
1114    ///
1115    /// `end_burst` indicates when this packet ends a burst transmission.
1116    ///
1117    /// Returns the number of samples written, which may be smaller than the size of the passed arrays.
1118    ///
1119    /// # Panics
1120    ///  * If `buffers` is not the same length as the `channels` array passed to `Device::tx_stream`.
1121    ///  * If all the buffers in `buffers` are not the same length.
1122    pub fn write(&mut self, buffers: &[&[E]], at_ns: Option<i64>, end_burst: bool, timeout_us: i64) -> Result<usize, Error> {
1123        unsafe {
1124            assert!(buffers.len() == self.nchannels, "Number of buffers must equal number of channels on stream");
1125
1126            let mut buf_ptrs = Vec::with_capacity(self.nchannels);
1127            let num_elems = buffers.get(0).map_or(0, |x| x.len());
1128            for buf in buffers {
1129                assert_eq!(buf.len(), num_elems, "All buffers must be the same length");
1130                buf_ptrs.push(buf.as_ptr());
1131            }
1132
1133            let mut flags = 0;
1134
1135            if at_ns.is_some() {
1136                flags |= SOAPY_SDR_HAS_TIME as i32;
1137            }
1138
1139            if end_burst {
1140                flags |= SOAPY_SDR_END_BURST as i32;
1141            }
1142
1143            let len = len_result(SoapySDRDevice_writeStream(
1144                self.device.inner.ptr,
1145                self.handle,
1146                buf_ptrs.as_ptr() as *const *const _,
1147                num_elems,
1148                &mut flags as *mut _,
1149                at_ns.unwrap_or(0),
1150                timeout_us as _
1151            ))?;
1152
1153            Ok(len as usize)
1154        }
1155    }
1156
1157    /// Write all samples to the device.
1158    ///
1159    /// This method repeatedly calls [write](TxStream::write) until the entire provided buffer has
1160    /// been written.
1161    ///
1162    /// The stream must first be [activated](TxStream::activate).
1163    ///
1164    /// `buffers` contains one source slice for each channel of the stream.
1165    ///
1166    /// `at_ns` is an optional nanosecond precision device timestamp at which
1167    /// the device is to begin the transmission (c.f. [get_hardware_time](Device::get_hardware_time)).
1168    ///
1169    /// `end_burst` indicates when this packet ends a burst transmission.
1170    ///
1171    /// # Panics
1172    ///  * If `buffers` is not the same length as the `channels` array passed to `Device::rx_stream`.
1173    ///  * If all the buffers in `buffers` are not the same length.
1174    pub fn write_all(&mut self, buffers: &[&[E]], at_ns: Option<i64>, end_burst: bool, timeout_us: i64) -> Result<(), Error> {
1175        let mut buffers = buffers.to_owned();
1176        let mut at_ns = at_ns;
1177
1178        while buffers.get(0).map_or(0, |x| x.len()) > 0 {
1179            // The timestamp is only sent on the first write.
1180            let written = self.write(&buffers, at_ns.take(), end_burst, timeout_us)?;
1181
1182            // Advance the buffer pointers
1183            for buf in &mut buffers {
1184                *buf = &buf[written..];
1185            }
1186        }
1187
1188        Ok(())
1189    }
1190
1191    /// Read the status of the stream.
1192    /// 
1193    /// This is required to detect underflows and such as they are not reported by 
1194    /// [write](TxStream::write).
1195    /// 
1196    /// `chan_mask``, `flags``, and `time_ns`` are output parameters and _may_ be 
1197    /// set depending on the type of status result.
1198    /// 
1199    /// Returns the status `Result`, usually this will be an [Error], as would be 
1200    /// returned from a stream read/write.
1201    /// 
1202    /// [ErrorCode::Timeout] should generally be ignored.
1203    /// 
1204    /// Note that `timeout_us` is only `i32` on Windows and panics if the value is 
1205    /// too large for `i32` on that platform.
1206    pub fn read_status(&mut self, chan_mask: &mut usize, flags: &mut i32, time_ns: &mut i64, timeout_us: i64) -> Result<usize, Error> {
1207        // Conversion needed for Windows, which takes an i32 here for some reason.
1208        #[allow(clippy::useless_conversion)]
1209        let timeout_us = timeout_us.try_into().unwrap();
1210        unsafe {
1211            let status = len_result(SoapySDRDevice_readStreamStatus(
1212                self.device.inner.ptr,
1213                self.handle,
1214                chan_mask,
1215                flags,
1216                time_ns,
1217                timeout_us
1218            ))?;
1219
1220            Ok(status as usize)
1221        }
1222    }
1223
1224    // TODO: DMA
1225
1226}