Code Monkey home page Code Monkey logo

rust-industrial-io's Introduction

Rust Industrial I/O for Linux

Crates.io

Rust library crate for using the Linux Industrial I/O (IIO) subsystem, primarily used for the input and output of analog data from a Linux system in user space. See the IIO Wiki.

The current version is a wrapper around the user-space C library, libiio. Subsequent versions may access the interface the kernel ABI directly.

To use in an application, add this to Cargo.toml:

[dependencies]
industrial-io = "0.5"

Pre-release Note

This is a pre-release version of the crate. The API is stabilizing, but is still under active development and may change before a final release.

This initial development work wrappers a specific version (v0.21) of libiio. It assumes that the library is pre-installed on the build system and the target system.

Contributing

Contributions to this project are gladly welcomed. Just keep a few things in mind:

  • Please make all Pull Requests against the develop branch of the repository. We prefer to keep the master branch relatively stable between releases and to do integration and testing in the develop branch.
  • Please keep individual Pull Requests to a single topic.
  • Please do not reformat code with other updates. Any code reformatting should be in a separate commit or PR. The formatting specification is in .rustfmt.toml and currently requires the nightly release.

Contributions are particularly welcome for any adjustments or feedback pertaining to different IIO device. If you test, work, or have any trouble with specific IIO hardware or drivers, let us know.

New examples for different hardware are also requested.

Latest News

Overall, an effort is underway to get this crate to production quality. It includes:

  • Full coverage of the libiio API - or as much as makes sense.
  • A complete set of working examples.
  • Unit tests and CI

To keep up with the latest announcements for this project, follow:

Twitter: @fmpagliughi

New in Version 0.5.2

  • PR #26 - Added 'utilities' feature to be able to turn off build of binary applications (i.e. only build the library).
  • #21 - Update nix dependency to avoid linking vulnerable version
  • Updated dependencies for clap and ctrlc crates.

New in Version 0.5.1

  • iio_info_rs utility now supports network and URI contexts.
  • PR #19 macOS build makes a distinction for Intel and non-Intel builds when searching for Homebrew Frameworks (libiio library).
  • PR #20 Fix some clippy suggestions. Particularly cleaner casting of raw pointers, etc.

The Basics

The C libiio library provides a user-space interface to the Linux Industrial I/O subsystem for interfacing to (possibly high-speed) analog hardware, such as A/D's, D/A's, accelerometers, gyroscopes, etc. This crate provides a fairly thin, safe, wrapper around the C library.

To access any physical devices you must first create a Context which can use one of several back-ends, the most common of which are:

  • "local" - To access hardware on the local machine
  • "network" - To access hardware on a remote machine that is running the IIO network daemon, iiod

There are also backends for USB and serial devices.

The default context will use the local machine, but can be overridden with the IIOD_REMOTE environment variable which, when set, gives the host name for a network context.

A given context, once created, can then be queried for information about the devices it contains and the channels provided by each device. The context, devices and their channels contain attributes that read and set data and the parameters for collecting it.

Data sampling and/or output can be started by trigger devices which can be hardware or software timers to collect periodic data, or triggers based on external events like GPIO inputs. Note, too, that some devices can self-trigger.

The library can also use the in-kernel ring buffers to collect data at higher speeds with less jitter.

There are a number of applications in the examples/ directory.

Hardware and Driver Peculiarities

The Linux IIO subsystem and libiio abstract a large number of different types of hardware with considerably different feature sets. Between the different capabilities of the hardware and the drivers written for them, applications can often see weird and unexpected results when starting out with a new device. The example applications are not guaranteed to work out-of-the box with all different types of hardware. But they provide a decent template for the most common usage. Some modifications and experimentation are often required when working with new devices.

Implementation Details

The Rust Industrial I/O library is a fairly thin wrapper around the C libiio, with some features thrown in to give it a more Rust-y feel.

Library Wrapper

To do anything with libiio, the application must first create a Contextto either manipulate the hardware on the local device (i.e. a local context), or to communicate with hardware on a remote device such as over a network connection. Creating a local context is a fairly heavyweight operation compared to other library operations in that it will scan the hardware and build up a local representation in memory.

The context is thus a snapshot of the hardware at the time at which it was created. Any hardware that is added outside of the context - such as another process creating a new hrtimer, will not be reflected in it. A new context would need to be created to re-scan the hardware.

But then, finding hardware is very efficient in that it just searches through the data structures in the context. A call like ctx.find_device('adc0') just looks for a string match in the list of hardware devices, and the pointer returned by the underlying library call is juts a reference to an existing context data structure.

Nothing is created or destroyed when new Rust hardware structures are declared, such as Device or Channel. Therefore the Rust structures can easily be cloned by copying the pointer to the library structure.

The Rust Context object is just a reference-counted smart pointer to an InnerContext. This makes it easy to share the C context between different objects (devices, channels, etc), and to manage its lifetime. The InnerContext actually wraps the C context. When it goes out of scope, typically when the last reference disappears, the C context is destroyed. Cloning the InnerContext creates a full copy of the C library's context.

This creates some confusion around "cloning" a Context. Since a Context is just a thread-safe, reference counted smart pointer to that inner context, cloning it just creates a new, shared pointer to the existing inner/C context. This makes it easy to share the context and guarantee it's lifetime between multiple Device objects created from it. Cloning a Context and sending the clone to another thread will actually then share the InnerContext (and thus C context) between the two threads.

Often, however, when using separate threads to manage each device, it can be more efficient to create a fully separate C context for each thread. To do this, a "deep" clone of the Context is necessary. This is simply a clone of the InnerContext, and creating new smart pointers around that inner context. So it is a clone of the InnerContext which actually makes a copy of the C library context. See the next section for details.

Thread Safety

Early versions of this library (v0.4.x and before) were written with the mistaken belief that the underling libiio was not thread-safe. Some public information about the library was a little misleading, but with input from a maintainers of the library and additional published information, thread restrictions are slowly being lifted from this library.

Starting in v0.5, the following is now possible:

  • InnerContext, which actually wraps the C library context, is now Sync in addition to being Send. It can be shared between threads.
  • Context is now implemented with an Arc to point to its InnerContext. So these references to the inner context can be sent to different threads and those threads can share the same context.
  • The Device objects, which hold a Context reference, are now Send. They can be moved to a different thread than the one that created the context.
  • For now, the Channel and Buffer objects are still !Send and !Sync, and need to live in the same thread with the Device, but these restrictions may be loosened as we figure out which specific operations are not thread safe.
  • The Buffer::refill() function now take a mutable reference to self, &mut self, in preparation of loosening thread restrictions on the buffer. The buffer definitely can not be filled by two different threads at the same time.

Even with these new thread capabilities, when the physical devices described by an IIO context can be manipulated by different threads, it is often still desirable to use a separate Context instance for each thread. There are two ways to do this:

  1. Simply create a new Context object in each thread using the same URI, etc. These might not be exactly the same if some new hardware was added or removed between the time any two contexts were created. But this is perhaps rare.
  2. Create a context and then make clones of its InnerContext object and send those to other threads. Each one can then be used to create a new Context instance in that thread.

This second option is considerably more efficient and can be done in several ways.

One way is to do a "deep" clone of a context and send it to the other thread:

let ctx = Context::new()?;
let thr_ctx = ctx.try_deep_clone()?;

thread::spawn(move || {
    let dev = thr_ctx.find_device("somedevice")?
    // ...
});

This makes a copy of the inner context which clones the C library context. It then sends the one-and-only reference to the other thread, giving it exclusive access to that C context.

Alternately, to be explicit about cloning the inner context, this can be done:

let ctx = Context::new()?;
let cti = ctx.try_clone_inner()?;

thread::spawn(move || {
    let thr_ctx = Context::from(cti);
    let dev = thr_ctx.find_device("somedevice")?
    // ...
});

Here the inner context is cloned to the cti object which is moved into the thread and consumed to create a new context object, thr_ctx. This procedure was required in the earlier versions of library, prior to v0.5.0.

An alternate way to share devices across threads and processes is to run the IIO network daemon on the local machine and allow it to control the local context. Then multiple client applications can access it from localhost using a network context. The daemon will serialize access to the device and let multiple clients share it. Each thread in the client would still need a separate network context.

The thing to keep in mind is that although Rust can enforce thread safety within a single process, the overall IIO subsystem is exposed to the other processes. Different devices and drivers might expose access differently. It is up to the system designer to insure that processes using IIO don't interfere with each other.

Testing the Crate

A great thing about the user-space IIO libraries is that, if you're developing on a fairly recent Linux host, you can start experimenting without having to do development on a board. You can run the IIO server daemon on an embedded board, and then use this crate to communicate with it over a network connection. When your application is working, you can then compile it for the target board, test it natively, and deploy.

Alternately, you can test with a mock, "dummy" context on a development host. This is a kernel module that simulates several devices. It can be used from a local context on a host machine to do some initial development and test of the library. See below for details on loading it into the kernel.

BeagleBone

Several maker boards can be used to try out the Industrial I/O subsystem pretty easily. The BeagleBone Black and Green have the on-board AM335X A/D, and the BeagleBone AI has an STM touchscreen chip that can be used for analog input.

The IIO library for the BeagleBones support individual and buffered sampling, though without external trigger support. The recent Debian 9.x IoT distributions for the board have IIO compiled into the kernel which can be used out of the box - although the user-space library, libiio, should be upgraded (see below).

Linux Development Host

Several modern Linux distributions, such as Ubuntu 18.04, have IIO modules compiled for the kernel, such as the dummy context. These can be loaded into the kernel, like:

$ sudo modprobe iio_dummy
$ sudo modprobe iio_trig_hrtimer

Once loaded, the configfs can be used to create devices and triggers. The load_dummy.sh script included in this repository can be used to load the modules and configure a device, suitable for basic experiments or running the unit tests.

$ sudo ./load_dummy.sh

macOS

The crate is also compatible with macOS, though only the network contexts are available. The libiio framework can be built from source or installed from a community homebrew formula:

brew install tfcollins/homebrew-formulae/libiio

Installing the C Library

Install libiio v0.21 on the target board. If you're developing on a Linux host, install the same version of the library there so that you can do some development on the host,

Check the version on the target

If you're using a BeagleBone Black, an old library may have shipped with the distro. Install the latest distribution for the board. (This was tested with Debian 9.5 2018-10-07 4GB SD IoT).

Log onto the board and check the version:

$ iiod --version
0.21

If this is less than 0.21, remove the Debian packages and install from sources.

First, get rid of the existing library and utilities:

$ sudo apt-get purge libiio-utils libiio0

Build from sources

Install the pre-requisites for the build:

$ sudo apt-get install cmake flex bison libxml2-dev libserialport-dev

And then download the library sources and build:

$ cd /tmp
$ wget https://github.com/analogdevicesinc/libiio/archive/v0.21.tar.gz
$ tar -xf v0.21.tar.gz 
$ cd libiio-0.21/
$ mkdir build ; cd build
$ cmake .. && make && sudo make install

Then check that the version installed properly:

$ iiod --version
0.21

Build the Rust Crate

This is a fairly standard Rust wrapper project around a C library. It contains an unsafe "-sys" sub-crate to wrap the C library API, and a higher-level, safe, Rust library in the main crate. The crate also contains utilities that interface with IIO devices via the library. To build the library and utilities:

$ cargo build

The library can be built with fewer dependencies. To build just the library:

$ cargo build --lib --no-default-features

There are also a number of example applications. They can all be built with:

$ cargo build --examples

rust-industrial-io's People

Contributors

fpagliughi avatar funky185540 avatar hellow554 avatar knightshrub avatar skrap avatar tomstokes avatar yarlb avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

rust-industrial-io's Issues

Tests for the library

Currently there aren't any tests yet. I'd really like to add some, but I'm uncertain about how to test an embedded library that's as close to the metal as this one... Do you happen to have any suggestions? Or have you made some thoughts on it that you may want to share?

My best guess was to use the XML backend, although I have no clue how that works (yet). It seems that one can't rely on the iio_dummy kernel-module to be available, at least I couldn't find it anywhere on my Fedora host... But then again XML would possibly allow for the tests to be run in a CI environment?

Just throwing some thoughts in here. If you have a pointer for me, I'll happily go invest that!

Missing function: create_context_from_uri

Hello, the library currently misses the create_context_from_uri function.
I need this in order to work with AD's ADALM-PLUTO SDR.
I don't have a lot of experience using unsafe rust or doing ffi in rust, but would this be an acceptable implementation?

/// Tries to create a context from the specified URI
pub fn from_uri(uri: &str) -> Result<Context> {
    let uri = CString::new(uri).expect("CString::new(uri) failed");
    let ctx = unsafe { 
        ffi::iio_create_context_from_uri(uri.as_ptr()) 
     };
     if ctx.is_null() { bail!(SysError(Errno::last())); }
     Ok(Context{ ctx, })
}

EACCESS: Permission denied when running examples

Hi,

I'm currently teaching myself how to program in Rust, and as I have a university project that requires me to read sensors from an embedded system I though why not do it in Rust and learn something along the way (Besides I personally find that the C-interface to libiio is extremely awkward to use). So first up: Thanks for making the effort!

I decided to start with the examples to see how everything works out. And unfortunately I can't seem to properly access the sensors on my system. See below:

$ sudo ./riio_tsbuf -d "mpu6050" -c "accel_z" -f 100 -n 128 -t "mpu6050-dev2"
Can't set sampling rate to 100Hz: EACCES: Permission denied
WARNING: High-speed mode not enabled
Capturing a buffer...
10:03:27.369253: 57402
10:03:27.389241: 53306
10:03:27.409228: 4667
10:03:27.429215: 58426
10:03:27.449202: 49722
10:03:27.468002: 60986
[ ... ]

As can be estimated from the timestamps, the sample rate is set to 50Hz.
However, when I attempt to update the samplerate via CLI, it works:

$ echo "100" | sudo tee /sys/bus/iio/devices/iio\:device2/sampling_frequency
100
$ cat /sys/bus/iio/devices/iio\:device2/sampling_frequency
100

Is this somehow related to libiio, or is my installation to blame?

And as a sidenote: Do the weird sensor readings originate from the sensor/driver acting up, or could this be related to the way that the samples are collected? The device was resting on the table while performing the measurements, and the measurements are rather inconsistent imo... :(

Thanks in advance!

Update Rust support to Edition 2018

As a (trivial) first step to possibly supporting async/await, update the library to use Rust Edition 2018, and test with some recent compiler versions (v1.46, etc)

Is this project being maintained?

Hi,

I've been working on some Rust IIO applications and noticed that this library - while still usable today - hasn't been updated in a while. I've been doing some work to bring it up to speed with the things I need to do, but I'm wondering if you still have time to review and accept contributions or if it's best that I continue on my own.

If you'd like to discuss things privately for any reason, I can be reached at [email protected].

Thanks!

Doesn't build on all versions of macOS

The build.rs file is hard-wired to search for the libiio files under /opt/homebrew/Frameworks on a Mac. This isn't the standard location for homebrew files on all machines or versions, and the default may even be a function of the processor hardware.

It seems that on Intel machines, homebrew installs under /usr/local and the Frameworks directory lives at the top-level, not under the homebrew directory (meaning /usr/local/Frameworks)

We need to figure out how to find this directory during the macOS build. The brew app configurations might give a hint, like:

% brew config | grep HOMEBREW
HOMEBREW_VERSION: 3.3.12
HOMEBREW_PREFIX: /usr/local
...

Thread safety

The readme states that "the contexts and devices in the underlying libiio are not thread safe."

They actually are, though. You can't obviously access some resources at the same time (e.g. trying to refill a buffer in two different threads at the same time), but you can read/write attributes in one thread and manipulate the buffer in another thread, it works just fine.

Feature request: add support for device labels.

The libiiohas an iio_device_get_label() function that is useful to find a device that is linked to a particular use.

Basically, you add a label attribute to the DTS node (see biding here) and you can use this label to lookup the right device. This can be useful to esly map the virtual device to the real one in the application, to have a unique reference that does not change (in contract to the ID, if you add another device).

It would be nice to have this functionality available from Rust too! I think this should not be too complicated to implement. It just need to mimic the behavior of method industrial_io::device::Device::name().

Update nix dependency to avoid linking vulnerable version

The nix crate has a vulnerability in the 0.16 version which is a dependency of this crate.

https://rustsec.org/advisories/RUSTSEC-2021-0119

Since the crate dependency is specified as "0.16" without an operator, cargo treats that as a carat requirement, which means that only 0.16.X will be acceptable. (An update is allowed if the new version number does not modify the left-most non-zero digit in the major, minor, patch grouping, per the carat requirement cargo docs.)

My request is that you switch to an explicit >= operator, like nix = ">=0.16". I think this will allow current patched versions of nix to be used with this crate.

Thanks!

Discussion: Streamline "attr_read" and "attr_write" methods?

Hello,

I'm currently seeking to document the 'channel' module, and I wonder whether it might be possible to streamline all "attr_read_" and "attr_write_" calls into one of "attr_read/write" respectively?

I'm just throwing in thoughts here: Upon creating a Channel instance, the library could get all attributes and determine which datatype they have. As channels are always bound to exactly one device, the channel could carry around a HashMap of all attribute entries, along with their data types. Then we could take some T from the user, and cast it to the type needed to set the attributes (or the other way round for reading).

As an added bonus we could check whether an attribute the user attempts to set/get really exists (if it has a matching key in the HashMap), and return a descriptive error if it doesn't, or if the argument can't be converted to the requested attributes type.

I'm aware that it's very debatable (and probably naive!), because we would need to gather this information at channel creation, but I thought I'd put it up for discussion nonetheless.

Or, seeing that libiio exposes 15 functions merely to work with attributes, would it be worthwhile to make structs for the attributes, too? Then each channel could carry around a slice of attributes, where each instance holds it's name (as string), it's data type, and possibly it's current value...

Thoughts? Would this even be possible?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.