industrial_io/
device.rs

1// libiio-sys/src/device.rs
2//
3// Copyright (c) 2018-2021, Frank Pagliughi
4//
5// Licensed under the MIT license:
6//   <LICENSE or http://opensource.org/licenses/MIT>
7// This file may not be copied, modified, or distributed except according
8// to those terms.
9//
10//! Industrial I/O Devices
11//!
12
13use super::*;
14use crate::{ffi, Direction, ATTR_BUF_SIZE};
15use nix::errno::Errno;
16use std::{
17    collections::HashMap,
18    ffi::CString,
19    os::raw::{c_char, c_longlong, c_uint},
20    ptr,
21};
22
23/// An Industrial I/O Device
24///
25/// This can not be created directly. It is obtained from a context.
26#[derive(Debug, Clone)]
27pub struct Device {
28    /// Pointer to the underlying device object.
29    pub(crate) dev: *mut ffi::iio_device,
30    /// The IIO context containing the device.
31    pub(crate) ctx: Context,
32}
33
34impl Device {
35    /// Gets the context to which the device belongs
36    pub fn context(&self) -> Context {
37        self.ctx.clone()
38    }
39
40    /// Gets the device ID (e.g. <b><i>iio:device0</i></b>)
41    pub fn id(&self) -> Option<String> {
42        let pstr = unsafe { ffi::iio_device_get_id(self.dev) };
43        cstring_opt(pstr)
44    }
45
46    /// Gets the name of the device
47    pub fn name(&self) -> Option<String> {
48        let pstr = unsafe { ffi::iio_device_get_name(self.dev) };
49        cstring_opt(pstr)
50    }
51
52    /// Gets the label of the device, if any.
53    #[cfg(not(any(feature = "libiio_v0_19", feature = "libiio_v0_21")))]
54    pub fn label(&self) -> Option<String> {
55        let pstr = unsafe { ffi::iio_device_get_label(self.dev) };
56        cstring_opt(pstr)
57    }
58
59    /// Determines if the device is capable of buffered I/O.
60    /// This is true if any of the channels are scan elements.
61    pub fn is_buffer_capable(&self) -> bool {
62        // This "trick" is from C lib 'iio_info.c'
63        for chan in self.channels() {
64            if chan.is_scan_element() {
65                return true;
66            }
67        }
68        false
69    }
70
71    /// Determines whether the device is a trigger
72    pub fn is_trigger(&self) -> bool {
73        unsafe { ffi::iio_device_is_trigger(self.dev) }
74    }
75
76    /// Associate a trigger for this device.
77    /// `trigger` The device to be used as a trigger.
78    pub fn set_trigger(&self, trigger: &Self) -> Result<()> {
79        let ret = unsafe { ffi::iio_device_set_trigger(self.dev, trigger.dev) };
80        sys_result(ret, ())
81    }
82
83    /// Removes the trigger from the device.
84    pub fn remove_trigger(&self) -> Result<()> {
85        let ret = unsafe { ffi::iio_device_set_trigger(self.dev, ptr::null()) };
86        sys_result(ret, ())
87    }
88
89    /// Set the number of kernel buffers for the device.
90    pub fn set_num_kernel_buffers(&self, n: u32) -> Result<()> {
91        let ret = unsafe { ffi::iio_device_set_kernel_buffers_count(self.dev, n as c_uint) };
92        sys_result(ret, ())
93    }
94
95    // ----- Attributes -----
96
97    /// Determines if the device has any attributes
98    pub fn has_attrs(&self) -> bool {
99        unsafe { ffi::iio_device_get_attrs_count(self.dev) > 0 }
100    }
101
102    /// Gets the number of device-specific attributes
103    pub fn num_attrs(&self) -> usize {
104        unsafe { ffi::iio_device_get_attrs_count(self.dev) as usize }
105    }
106
107    /// Gets the name of the device-specific attribute at the index
108    pub fn get_attr(&self, idx: usize) -> Result<String> {
109        let pstr = unsafe { ffi::iio_device_get_attr(self.dev, idx as c_uint) };
110        cstring_opt(pstr).ok_or(Error::InvalidIndex)
111    }
112
113    /// Try to find a device-specific attribute by its name
114    pub fn find_attr(&self, name: &str) -> Option<String> {
115        let cname = cstring_or_bail!(name);
116        let pstr = unsafe { ffi::iio_device_find_attr(self.dev, cname.as_ptr()) };
117        cstring_opt(pstr)
118    }
119
120    /// Determines if a buffer-specific attribute exists
121    pub fn has_attr(&self, name: &str) -> bool {
122        let cname = cstring_or_bail_false!(name);
123        let pstr = unsafe { ffi::iio_device_find_attr(self.dev, cname.as_ptr()) };
124        !pstr.is_null()
125    }
126
127    /// Reads a device-specific attribute
128    ///
129    /// `attr` The name of the attribute
130    pub fn attr_read<T: FromAttribute>(&self, attr: &str) -> Result<T> {
131        let sval = self.attr_read_str(attr)?;
132        T::from_attr(&sval)
133    }
134
135    /// Reads a device-specific attribute as a string
136    ///
137    /// `attr` The name of the attribute
138    pub fn attr_read_str(&self, attr: &str) -> Result<String> {
139        let mut buf = vec![0 as c_char; ATTR_BUF_SIZE];
140        let attr = CString::new(attr)?;
141        let ret = unsafe {
142            ffi::iio_device_attr_read(self.dev, attr.as_ptr(), buf.as_mut_ptr(), buf.len())
143        };
144        sys_result(ret as i32, ())?;
145        let s = unsafe {
146            CStr::from_ptr(buf.as_ptr())
147                .to_str()
148                .map_err(|_| Error::StringConversionError)?
149        };
150        Ok(s.into())
151    }
152
153    /// Reads a device-specific attribute as a boolean
154    ///
155    /// `attr` The name of the attribute
156    pub fn attr_read_bool(&self, attr: &str) -> Result<bool> {
157        let mut val: bool = false;
158        let attr = CString::new(attr)?;
159        let ret = unsafe { ffi::iio_device_attr_read_bool(self.dev, attr.as_ptr(), &mut val) };
160        sys_result(ret, val)
161    }
162
163    /// Reads a device-specific attribute as an integer (i64)
164    ///
165    /// `attr` The name of the attribute
166    pub fn attr_read_int(&self, attr: &str) -> Result<i64> {
167        let mut val: c_longlong = 0;
168        let attr = CString::new(attr)?;
169        let ret = unsafe { ffi::iio_device_attr_read_longlong(self.dev, attr.as_ptr(), &mut val) };
170        sys_result(ret, val as i64)
171    }
172
173    /// Reads a device-specific attribute as a floating-point (f64) number
174    ///
175    /// `attr` The name of the attribute
176    pub fn attr_read_float(&self, attr: &str) -> Result<f64> {
177        let mut val: f64 = 0.0;
178        let attr = CString::new(attr)?;
179        let ret = unsafe { ffi::iio_device_attr_read_double(self.dev, attr.as_ptr(), &mut val) };
180        sys_result(ret, val)
181    }
182
183    /// Reads all the device-specific attributes.
184    /// This is especially useful when using the network backend to
185    /// retrieve all the attributes with a single call.
186    pub fn attr_read_all(&self) -> Result<HashMap<String, String>> {
187        let mut map = HashMap::new();
188        let pmap = (&mut map as *mut HashMap<_, _>).cast();
189        let ret = unsafe { ffi::iio_device_attr_read_all(self.dev, Some(attr_read_all_cb), pmap) };
190        sys_result(ret, map)
191    }
192
193    /// Writes a device-specific attribute
194    ///
195    /// `attr` The name of the attribute
196    /// `val` The value to write
197    pub fn attr_write<T: ToAttribute>(&self, attr: &str, val: T) -> Result<()> {
198        let sval = T::to_attr(&val)?;
199        self.attr_write_str(attr, &sval)
200    }
201
202    /// Writes a device-specific attribute as a string
203    ///
204    /// `attr` The name of the attribute
205    /// `val` The value to write
206    pub fn attr_write_str(&self, attr: &str, val: &str) -> Result<()> {
207        let attr = CString::new(attr)?;
208        let val = CString::new(val)?;
209        let ret = unsafe { ffi::iio_device_attr_write(self.dev, attr.as_ptr(), val.as_ptr()) };
210        sys_result(ret as i32, ())
211    }
212
213    /// Writes a device-specific attribute as a boolean
214    ///
215    /// `attr` The name of the attribute
216    /// `val` The value to write
217    pub fn attr_write_bool(&self, attr: &str, val: bool) -> Result<()> {
218        let attr = CString::new(attr)?;
219        let ret = unsafe { ffi::iio_device_attr_write_bool(self.dev, attr.as_ptr(), val) };
220        sys_result(ret, ())
221    }
222
223    /// Writes a device-specific attribute as an integer (i64)
224    ///
225    /// `attr` The name of the attribute
226    /// `val` The value to write
227    pub fn attr_write_int(&self, attr: &str, val: i64) -> Result<()> {
228        let attr = CString::new(attr)?;
229        let ret = unsafe { ffi::iio_device_attr_write_longlong(self.dev, attr.as_ptr(), val) };
230        sys_result(ret, ())
231    }
232
233    /// Writes a device-specific attribute as a floating-point (f64) number
234    ///
235    /// `attr` The name of the attribute
236    /// `val` The value to write
237    pub fn attr_write_float(&self, attr: &str, val: f64) -> Result<()> {
238        let attr = CString::new(attr)?;
239        let ret = unsafe { ffi::iio_device_attr_write_double(self.dev, attr.as_ptr(), val) };
240        sys_result(ret, ())
241    }
242
243    /// Gets an iterator for the attributes in the device
244    pub fn attributes(&self) -> AttrIterator<'_> {
245        AttrIterator { dev: self, idx: 0 }
246    }
247
248    // ----- Channels -----
249
250    /// Gets the number of channels on the device
251    pub fn num_channels(&self) -> usize {
252        unsafe { ffi::iio_device_get_channels_count(self.dev) as usize }
253    }
254
255    /// Gets a channel by index
256    pub fn get_channel(&self, idx: usize) -> Result<Channel> {
257        let chan = unsafe { ffi::iio_device_get_channel(self.dev, idx as c_uint) };
258        if chan.is_null() {
259            Err(Error::InvalidIndex)
260        }
261        else {
262            Ok(Channel {
263                chan,
264                ctx: self.context(),
265            })
266        }
267    }
268
269    /// Try to find a channel by its name or ID
270    pub fn find_channel(&self, name: &str, dir: Direction) -> Option<Channel> {
271        let is_output = dir == Direction::Output;
272        let cname = cstring_or_bail!(name);
273        let chan = unsafe { ffi::iio_device_find_channel(self.dev, cname.as_ptr(), is_output) };
274
275        if chan.is_null() {
276            None
277        }
278        else {
279            Some(Channel {
280                chan,
281                ctx: self.context(),
282            })
283        }
284    }
285
286    /// Try to find an input channel by its name or ID
287    #[inline]
288    pub fn find_input_channel(&self, name: &str) -> Option<Channel> {
289        self.find_channel(name, Direction::Input)
290    }
291
292    /// Try to find an input channel by its name or ID
293    #[inline]
294    pub fn find_output_channel(&self, name: &str) -> Option<Channel> {
295        self.find_channel(name, Direction::Output)
296    }
297
298    /// Gets an iterator for the channels in the device
299    pub fn channels(&self) -> ChannelIterator<'_> {
300        ChannelIterator { dev: self, idx: 0 }
301    }
302
303    // ----- Buffer Functions -----
304
305    /// Creates a buffer for the device.
306    ///
307    /// `sample_count` The number of samples the buffer should hold
308    /// `cyclic` Whether to enable cyclic mode.
309    pub fn create_buffer(&self, sample_count: usize, cyclic: bool) -> Result<Buffer> {
310        let buf = unsafe { ffi::iio_device_create_buffer(self.dev, sample_count, cyclic) };
311        if buf.is_null() {
312            return Err(Errno::last().into());
313        }
314        Ok(Buffer {
315            buf,
316            cap: sample_count,
317            dev: self.clone(),
318        })
319    }
320
321    // ----- Low-level & Debug functions -----
322
323    /// Gets the current sample size, in bytes.
324    /// This gets the number of bytes requires to store the samples,
325    /// based on the the channels that are currently enabled.
326    pub fn sample_size(&self) -> Result<usize> {
327        let ret = unsafe { ffi::iio_device_get_sample_size(self.dev) };
328        sys_result(ret as i32, ret as usize)
329    }
330
331    /// Gets the value of a hardware register
332    pub fn reg_read(&self, addr: u32) -> Result<u32> {
333        let mut val: u32 = 0;
334        let ret = unsafe { ffi::iio_device_reg_read(self.dev, addr, &mut val) };
335        sys_result(ret as i32, val)
336    }
337
338    /// Sets the value of a hardware register
339    pub fn reg_write(&self, addr: u32, val: u32) -> Result<()> {
340        let ret = unsafe { ffi::iio_device_reg_write(self.dev, addr, val) };
341        sys_result(ret as i32, ())
342    }
343}
344
345// The Device can be sent to another thread.
346unsafe impl Send for Device {}
347
348impl PartialEq for Device {
349    /// Two devices are the same if they refer to the same underlying
350    /// object in the library.
351    fn eq(&self, other: &Self) -> bool {
352        self.dev == other.dev
353    }
354}
355
356/// Iterator over the Channels in a Device
357#[derive(Debug)]
358pub struct ChannelIterator<'a> {
359    /// Reference to the Device that we're scanning for Channels
360    dev: &'a Device,
361    /// Index for the next Channel from the iterator.
362    idx: usize,
363}
364
365impl Iterator for ChannelIterator<'_> {
366    type Item = Channel;
367
368    fn next(&mut self) -> Option<Self::Item> {
369        match self.dev.get_channel(self.idx) {
370            Ok(chan) => {
371                self.idx += 1;
372                Some(chan)
373            }
374            Err(_) => None,
375        }
376    }
377}
378
379/// Iterator over the attributes in a Device
380#[derive(Debug)]
381pub struct AttrIterator<'a> {
382    /// Reference to the Device that we're scanning for attributes
383    dev: &'a Device,
384    /// Index for the next Device attribute from the Iterator.
385    idx: usize,
386}
387
388impl Iterator for AttrIterator<'_> {
389    type Item = String;
390
391    /// Gets the next Device attribute from the iterator
392    fn next(&mut self) -> Option<Self::Item> {
393        match self.dev.get_attr(self.idx) {
394            Ok(name) => {
395                self.idx += 1;
396                Some(name)
397            }
398            Err(_) => None,
399        }
400    }
401}
402
403// --------------------------------------------------------------------------
404//                              Unit Tests
405// --------------------------------------------------------------------------
406
407// Note: These tests assume that the IIO Dummy kernel module is loaded
408// locally with a device created. See the `load_dummy.sh` script.
409
410#[cfg(test)]
411mod tests {
412    use super::*;
413
414    const DEV_ID: &str = "iio:device0";
415    const DEV_NAME: &str = "dummydev";
416
417    // Make sure we get a device
418    #[test]
419    fn get_device() {
420        let ctx = Context::new().unwrap();
421
422        let id_dev = ctx.find_device(DEV_ID).unwrap();
423        assert_eq!(id_dev.id(), Some(DEV_ID.to_string()));
424
425        let name_dev = ctx.find_device(DEV_NAME).unwrap();
426        assert_eq!(name_dev.name(), Some(DEV_NAME.to_string()));
427
428        // Find by name or ID should both work and give the same device.
429        let id = name_dev.id().unwrap();
430        let id_dev = ctx.find_device(&id).unwrap();
431        assert_eq!(name_dev.name(), Some(DEV_NAME.to_string()));
432        assert_eq!(name_dev, id_dev);
433    }
434
435    // See that attr iterator gets the correct number of attributes
436    #[test]
437    fn attr_iterator_count() {
438        let ctx = Context::new().unwrap();
439        let dev = ctx.find_device(DEV_ID).unwrap();
440
441        let n = dev.num_attrs();
442        assert!(n != 0);
443        assert!(dev.attributes().count() == n);
444    }
445
446    // Just the fact that this compiles is probably sufficient.
447    #[test]
448    fn test_device_send() {
449        use std::thread;
450
451        let ctx = Context::new().unwrap();
452        let dev = ctx.find_device("timer0").unwrap();
453
454        // Looks like this requires root access
455        //const FREQ: i64 = 1000;
456        //dev.attr_write_int("sampling_frequency", FREQ).unwrap();
457
458        let thr = thread::spawn(move || {
459            //let freq = dev.attr_read_int("sampling_frequency").unwrap();
460            //assert_eq!(FREQ, freq);
461
462            let name = dev.name().unwrap();
463            assert_eq!(name, "timer0");
464        });
465        let _ = thr.join();
466    }
467}