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}