industrial_io/
context.rs

1// libiio-sys/src/context.rs
2//
3// Copyright (c) 2018-2025, 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
11//! Industrial I/O Contexts.
12//!
13
14use crate::{cstring_opt, ffi, sys_result, Device, Error, Result, Version};
15use nix::errno::Errno;
16use std::{
17    ffi::{CStr, CString},
18    os::raw::{c_char, c_uint},
19    ptr, slice, str,
20    sync::Arc,
21    time::Duration,
22};
23
24/////////////////////////////////////////////////////////////////////////////
25
26/// An Industrial I/O Context
27///
28/// This object maintains a reference counted pointer to the context object
29/// of the underlying library's `iio_context` object. Once all references to
30/// the Context object have been dropped, the underlying `iio_context` will be
31/// destroyed. This is done to make creation and use of a single Device more
32/// ergonomic by removing the need to manage the lifetime of the Context.
33#[derive(Debug, Clone)]
34pub struct Context {
35    inner: Arc<InnerContext>,
36}
37
38/// Backends for I/O Contexts.
39///
40/// An I/O [`Context`] relies on a backend that provides sensor data.
41#[derive(Debug)]
42pub enum Backend<'a> {
43    /// Use the default backend. This will create a network context if the
44    /// IIOD_REMOTE environment variable is set to the hostname where the
45    /// IIOD server runs. If set to an empty string, the server will be
46    /// discovered using ZeroConf. If the environment variable is not set,
47    /// a local context will be created instead.
48    Default,
49    /// XML Backend, creates a Context from an XML file. Here the string is
50    /// the name of the file.
51    /// Example Parameter:
52    /// "/home/user/file.xml"
53    Xml(&'a str),
54    /// XML Backend, creates a Context from an in-memory XML string. Here
55    /// the string _is_ the XML description.
56    XmlMem(&'a str),
57    /// Network Backend, creates a Context through a network connection.
58    /// Requires a hostname, IPv4 or IPv6 address to connect to another host
59    /// that is running the [IIO Daemon]. If an empty string is provided,
60    /// automatic discovery through ZeroConf is performed (if available in IIO).
61    /// Example Parameter:
62    ///
63    /// - "192.168.2.1" to connect to given IPv4 host, **or**
64    /// - "localhost" to connect to localhost running IIOD, **or**
65    /// - "plutosdr.local" to connect to host with given hostname, **or**
66    /// - "" for automatic discovery
67    ///
68    /// [IIO Daemon]: https://github.com/analogdevicesinc/libiio/tree/master/iiod
69    Network(&'a str),
70    /// USB Backend, creates a context through a USB connection.
71    /// If only a single USB device is attached, provide an empty String ("")
72    /// to use that. When more than one usb device is attached, requires bus,
73    /// address, and interface parts separated with a dot.
74    /// Example Parameter: "3.32.5"
75    Usb(&'a str),
76    /// Serial Backend, creates a context through a serial connection.
77    /// Requires (Values in parentheses show examples):
78    ///
79    /// - a port (/dev/ttyUSB0),
80    /// - baud_rate (default 115200)
81    /// - serial port configuration
82    ///     - data bits (5 6 7 8 9)
83    ///     - parity ('n' none, 'o' odd, 'e' even, 'm' mark, 's' space)
84    ///     - stop bits (1 2)
85    ///     - flow control ('\0' none, 'x' Xon Xoff, 'r' RTSCTS, 'd' DTRDSR)
86    ///
87    /// Example Parameters:
88    ///
89    /// - "/dev/ttyUSB0,115200", **or**
90    /// - "/dev/ttyUSB0,115200,8n1"
91    Serial(&'a str),
92    /// "Guess" the backend to use from the URI that's supplied. This merely
93    /// provides compatibility with [`iio_create_context_from_uri`] from the
94    /// underlying IIO C-library. Refer to the IIO docs for information on how
95    /// to format this parameter.
96    ///
97    /// [`iio_create_context_from_uri`]: https://analogdevicesinc.github.io/libiio/master/libiio/group__Context.html#gafdcee40508700fa395370b6c636e16fe
98    Uri(&'a str),
99    /// Local Backend, only available on Linux hosts. Sensors to work with are
100    /// part of the system and accessible in sysfs (under `/sys/...`).
101    #[cfg(target_os = "linux")]
102    Local,
103}
104
105/// This holds a pointer to the library context.
106/// When it is dropped, the library context is destroyed.
107#[derive(Debug)]
108pub struct InnerContext {
109    /// Pointer to a libiio Context object
110    pub(crate) ctx: *mut ffi::iio_context,
111}
112
113impl InnerContext {
114    /// Tries to create the inner context from a C context pointer.
115    ///
116    /// This should be called _right after_ creating the C context as it
117    /// will use the last error on failure.
118    fn new(ctx: *mut ffi::iio_context) -> Result<Self> {
119        if ctx.is_null() {
120            Err(Error::from(Errno::last()))
121        }
122        else {
123            Ok(Self { ctx })
124        }
125    }
126
127    /// Tries to create a full, deep, copy of the underlying context.
128    ///
129    /// This creates a full copy of the actual context held in the underlying
130    /// C library. This is useful if you want to give a separate copy to each
131    /// thread in an application, which could help performance.
132    pub fn try_clone(&self) -> Result<Self> {
133        Self::new(unsafe { ffi::iio_context_clone(self.ctx) })
134    }
135}
136
137impl Drop for InnerContext {
138    /// Dropping destroys the underlying C context.
139    ///
140    /// When held by [`Context`] references, this should happen when the last
141    /// context referring to it goes out of scope.
142    fn drop(&mut self) {
143        unsafe { ffi::iio_context_destroy(self.ctx) };
144    }
145}
146
147// The inner context can be sent to another thread.
148unsafe impl Send for InnerContext {}
149
150// The inner context can be shared with another thread.
151unsafe impl Sync for InnerContext {}
152
153impl Context {
154    /// Creates a default context from a local or remote IIO device.
155    ///
156    /// # Notes
157    ///
158    /// This will create a network context if the `IIOD_REMOTE`
159    /// environment variable is set to the hostname where the IIOD server
160    /// runs. If set to an empty string, the server will be discovered using
161    /// `ZeroConf`. If the environment variable is not set, a local context
162    /// will be created instead.
163    pub fn new() -> Result<Self> {
164        Self::from_ptr(unsafe { ffi::iio_create_default_context() })
165    }
166
167    /// Create an IIO Context.
168    ///
169    /// A context contains one or more devices (i.e. sensors) that can provide
170    /// data. Note that any device can always only be associated with one
171    /// context!
172    ///
173    /// Contexts rely on [`Backend`]s to discover available sensors. Multiple
174    /// [`Backend`]s are supported.
175    ///
176    /// # Examples
177    ///
178    /// Create a context to work with sensors on the local system
179    /// (Only supported for Linux hosts):
180    ///
181    /// ```no_run
182    /// use industrial_io as iio;
183    ///
184    /// let ctx = iio::Context::with_backend(iio::Backend::Local);
185    /// ```
186    ///
187    /// Create a context that works with senors on some network host:
188    ///
189    /// ```no_run
190    /// use industrial_io as iio;
191    ///
192    /// let ctx_ip = iio::Context::with_backend(iio::Backend::Network("192.168.2.1"));
193    /// let ctx_host = iio::Context::with_backend(iio::Backend::Network("runs-iiod.local"));
194    /// let ctx_auto = iio::Context::with_backend(iio::Backend::Network(""));
195    /// ```
196    ///
197    /// Creates a Context using some arbitrary URI (like it is used in the
198    /// underlying IIO C-library):
199    ///
200    /// ```no_run
201    /// use industrial_io as iio;
202    ///
203    /// let ctx = iio::Context::with_backend(iio::Backend::Uri("ip:192.168.2.1"));
204    /// ```
205    pub fn with_backend(be: Backend) -> Result<Self> {
206        Self::from_ptr(unsafe {
207            match be {
208                Backend::Default => ffi::iio_create_default_context(),
209                Backend::Xml(name) => {
210                    let name = CString::new(name)?;
211                    ffi::iio_create_xml_context(name.as_ptr())
212                }
213                Backend::XmlMem(xml) => {
214                    let n = xml.len();
215                    let xml = CString::new(xml)?;
216                    ffi::iio_create_xml_context_mem(xml.as_ptr(), n)
217                }
218                Backend::Network(host) => {
219                    let host = CString::new(host)?;
220                    ffi::iio_create_network_context(host.as_ptr())
221                }
222                Backend::Usb(device) => {
223                    let uri = CString::new(format!("usb:{}", device))?;
224                    ffi::iio_create_context_from_uri(uri.as_ptr())
225                }
226                Backend::Serial(tty) => {
227                    let uri = CString::new(format!("serial:{}", tty))?;
228                    ffi::iio_create_context_from_uri(uri.as_ptr())
229                }
230                Backend::Uri(uri) => {
231                    let uri = CString::new(uri)?;
232                    ffi::iio_create_context_from_uri(uri.as_ptr())
233                }
234                #[cfg(target_os = "linux")]
235                Backend::Local => ffi::iio_create_local_context(),
236            }
237        })
238    }
239
240    /// Creates a context specified by the `uri`.
241    pub fn from_uri(uri: &str) -> Result<Self> {
242        Self::with_backend(Backend::Uri(uri))
243    }
244
245    /// Creates a network backend on the specified host.
246    ///
247    /// This is a convenience function to create a context with the network
248    /// back end.
249    pub fn from_network(hostname: &str) -> Result<Self> {
250        Self::with_backend(Backend::Network(hostname))
251    }
252
253    /// Creates a context from an existing "inner" object.
254    pub fn from_inner(inner: InnerContext) -> Self {
255        Self::from(inner)
256    }
257
258    /// Creates a Rust Context object from a C context pointer.
259    fn from_ptr(ctx: *mut ffi::iio_context) -> Result<Self> {
260        let inner = InnerContext::new(ctx)?;
261        Ok(Self::from_inner(inner))
262    }
263
264    /// Try to create a clone of the inner underlying context.
265    ///
266    /// The inner context wraps the C library context. Cloning it makes
267    /// a full copy of the C context.
268    pub fn try_clone_inner(&self) -> Result<InnerContext> {
269        self.inner.try_clone()
270    }
271
272    /// Tries to release the inner context.
273    ///
274    /// This attempts to release and return the [`InnerContext`], which
275    /// succeeds if this is the only [`Context`] referring to it. If there are
276    /// other references, an error is returned with a [`Context`].
277    pub fn try_release_inner(self) -> std::result::Result<InnerContext, Self> {
278        match Arc::try_unwrap(self.inner) {
279            Ok(inner) => Ok(inner),
280            Err(inner_ptr) => Err(Self { inner: inner_ptr }),
281        }
282    }
283
284    /// Make a new context based on a full copy of underlying C context.
285    pub fn try_deep_clone(&self) -> Result<Self> {
286        let inner = self.inner.try_clone()?;
287        Ok(Self {
288            inner: Arc::new(inner),
289        })
290    }
291
292    /// Get the name of the context.
293    /// This should be "local", "xml", or "network" depending on how the context was created.
294    pub fn name(&self) -> String {
295        let pstr = unsafe { ffi::iio_context_get_name(self.inner.ctx) };
296        cstring_opt(pstr).unwrap_or_default()
297    }
298
299    /// Get a description of the context
300    pub fn description(&self) -> String {
301        let pstr = unsafe { ffi::iio_context_get_description(self.inner.ctx) };
302        cstring_opt(pstr).unwrap_or_default()
303    }
304
305    /// Get the version of the backend in use
306    pub fn version(&self) -> Version {
307        let mut major: c_uint = 0;
308        let mut minor: c_uint = 0;
309
310        const BUF_SZ: usize = 8;
311        let mut buf = vec![b' ' as c_char; BUF_SZ];
312        let pbuf = buf.as_mut_ptr();
313
314        unsafe { ffi::iio_context_get_version(self.inner.ctx, &mut major, &mut minor, pbuf) };
315
316        let sgit = unsafe {
317            if buf.contains(&0) {
318                CStr::from_ptr(pbuf).to_owned()
319            }
320            else {
321                let slc = str::from_utf8(slice::from_raw_parts(pbuf.cast(), BUF_SZ)).unwrap();
322                CString::new(slc).unwrap()
323            }
324        };
325        Version {
326            major: major as u32,
327            minor: minor as u32,
328            git_tag: sgit.to_string_lossy().into_owned(),
329        }
330    }
331
332    /// Obtain the XML representation of the context.
333    pub fn xml(&self) -> String {
334        let pstr = unsafe { ffi::iio_context_get_xml(self.inner.ctx) };
335        cstring_opt(pstr).unwrap_or_default()
336    }
337
338    /// Determines if the context has any attributes
339    pub fn has_attrs(&self) -> bool {
340        unsafe { ffi::iio_context_get_attrs_count(self.inner.ctx) > 0 }
341    }
342
343    /// Gets the number of context-specific attributes
344    pub fn num_attrs(&self) -> usize {
345        unsafe { ffi::iio_context_get_attrs_count(self.inner.ctx) as usize }
346    }
347
348    /// Gets the name and value of the context-specific attributes.
349    /// Note that this is different than the same function for other IIO
350    /// types, in that this retrieves both the name and value of the
351    /// attributes in a single call.
352    pub fn get_attr(&self, idx: usize) -> Result<(String, String)> {
353        let mut pname: *const c_char = ptr::null();
354        let mut pval: *const c_char = ptr::null();
355
356        sys_result(
357            unsafe {
358                ffi::iio_context_get_attr(self.inner.ctx, idx as c_uint, &mut pname, &mut pval)
359            },
360            (),
361        )?;
362        let name = cstring_opt(pname);
363        let val = cstring_opt(pval);
364        if name.is_none() || val.is_none() {
365            return Err(Error::StringConversionError.into());
366        }
367        Ok((name.unwrap(), val.unwrap()))
368    }
369
370    /// Gets an iterator for the attributes in the context
371    pub fn attributes(&self) -> AttrIterator<'_> {
372        AttrIterator { ctx: self, idx: 0 }
373    }
374
375    /// Sets the timeout for I/O operations
376    ///
377    /// `timeout` The timeout. A value of zero specifies that no timeout
378    ///     should be used.
379    pub fn set_timeout(&self, timeout: Duration) -> Result<()> {
380        let ms: u64 = 1000 * timeout.as_secs() + u64::from(timeout.subsec_millis());
381        self.set_timeout_ms(ms)
382    }
383
384    /// Sets the timeout for I/O operations, in milliseconds
385    ///
386    /// `timeout` The timeout, in ms. A value of zero specifies that no
387    ///     timeout should be used.
388    pub fn set_timeout_ms(&self, ms: u64) -> Result<()> {
389        let ret = unsafe { ffi::iio_context_set_timeout(self.inner.ctx, ms as c_uint) };
390        sys_result(ret, ())
391    }
392
393    /// Get the number of devices in the context
394    pub fn num_devices(&self) -> usize {
395        unsafe { ffi::iio_context_get_devices_count(self.inner.ctx) as usize }
396    }
397
398    /// Gets a device by index
399    pub fn get_device(&self, idx: usize) -> Result<Device> {
400        let dev = unsafe { ffi::iio_context_get_device(self.inner.ctx, idx as c_uint) };
401        if dev.is_null() {
402            return Err(Error::InvalidIndex);
403        }
404        Ok(Device {
405            dev,
406            ctx: self.clone(),
407        })
408    }
409
410    /// Try to find a device by name or ID
411    /// `name` The name or ID of the device to find. For versions that
412    /// support a label, it can also be used to look up a device.
413    pub fn find_device(&self, name: &str) -> Option<Device> {
414        let name = CString::new(name).unwrap();
415        let dev = unsafe { ffi::iio_context_find_device(self.inner.ctx, name.as_ptr()) };
416        if dev.is_null() {
417            None
418        }
419        else {
420            Some(Device {
421                dev,
422                ctx: self.clone(),
423            })
424        }
425    }
426
427    /// Gets an iterator for all the devices in the context.
428    pub fn devices(&self) -> DeviceIterator<'_> {
429        DeviceIterator { ctx: self, idx: 0 }
430    }
431
432    /// Destroy the context
433    ///
434    /// This consumes the context to destroy the instance.
435    pub fn destroy(self) {}
436}
437
438impl PartialEq for Context {
439    /// Two contexts are the same if they refer to the same underlying
440    /// object in the library.
441    fn eq(&self, other: &Self) -> bool {
442        self.inner.ctx == other.inner.ctx
443    }
444}
445
446impl From<InnerContext> for Context {
447    /// Makes a new [`Context`] from the [`InnerContext`]
448    fn from(inner: InnerContext) -> Self {
449        Self {
450            inner: Arc::new(inner),
451        }
452    }
453}
454
455/// Iterator over the Devices in a Context
456#[derive(Debug)]
457pub struct DeviceIterator<'a> {
458    /// Reference to the IIO context containing the Device
459    ctx: &'a Context,
460    /// The current Device index for the iterator
461    idx: usize,
462}
463
464impl Iterator for DeviceIterator<'_> {
465    type Item = Device;
466
467    /// Gets the next Device from the iterator.
468    fn next(&mut self) -> Option<Self::Item> {
469        match self.ctx.get_device(self.idx) {
470            Ok(dev) => {
471                self.idx += 1;
472                Some(dev)
473            }
474            Err(_) => None,
475        }
476    }
477}
478
479/// Iterator over the attributes in a Context
480#[derive(Debug)]
481pub struct AttrIterator<'a> {
482    /// Reference to the IIO context containing the Device
483    ctx: &'a Context,
484    /// Index for the next Context attribute from the iterator
485    idx: usize,
486}
487
488impl Iterator for AttrIterator<'_> {
489    type Item = (String, String);
490
491    /// Gets the next Device attribute from the iterator.
492    fn next(&mut self) -> Option<Self::Item> {
493        match self.ctx.get_attr(self.idx) {
494            Ok(name_val) => {
495                self.idx += 1;
496                Some(name_val)
497            }
498            Err(_) => None,
499        }
500    }
501}
502
503// --------------------------------------------------------------------------
504//                              Unit Tests
505// --------------------------------------------------------------------------
506
507// Note: These tests assume that the IIO Dummy kernel module is loaded
508// locally with a device created. See the `load_dummy.sh` script.
509
510#[cfg(test)]
511mod tests {
512    use super::*;
513    use std::thread;
514
515    // See that we get the default context.
516    #[test]
517    fn default_context() {
518        let res = Context::new();
519        assert!(res.is_ok());
520
521        let res = Context::from_ptr(ptr::null_mut());
522        assert!(res.is_err());
523    }
524
525    // Clone a context and make sure it's reported as same one.
526    #[test]
527    fn clone_context() {
528        let ctx = Context::new().unwrap();
529        let ctx2 = ctx.clone();
530        assert_eq!(ctx, ctx2);
531    }
532
533    // Clone the inner context and send to another thread.
534    #[test]
535    fn multi_thread() {
536        let ctx = Context::new().unwrap();
537        let cti = ctx.try_clone_inner().unwrap();
538
539        let thr = thread::spawn(move || {
540            let _thr_ctx = Context::from_inner(cti);
541        });
542        thr.join().unwrap();
543    }
544
545    // See that device iterator gets the correct number of devices.
546    #[test]
547    fn dev_iterator_count() {
548        let ctx = Context::new().unwrap();
549        let ndev = ctx.num_devices();
550        assert!(ndev != 0);
551        assert!(ctx.devices().count() == ndev);
552    }
553
554    // See that the description gives back something.
555    #[test]
556    fn name() {
557        let ctx = Context::new().unwrap();
558        let name = ctx.name();
559        println!("Context name: {}", name);
560        assert!(name == "local" || name == "network");
561    }
562
563    // See that the description gives back something.
564    #[test]
565    fn description() {
566        let ctx = Context::new().unwrap();
567        let desc = ctx.description();
568        println!("Context description: {}", desc);
569        assert!(!desc.is_empty());
570    }
571}