Implementing a measurment interface in Rust (VISA)

After moving around in different languages and projects I want to focus on a specific project.

There is a software from National Instruments called VISA (Virtual instrument software architecture). It is closed source and it seems there is no more Linux support.

There is also a software called Keysight IO Library Suite. It also seems to be closed source. Both are only available for x86, at least I haven't found a version for ARM. And both can be wrapped with PyVISA.

There is also a pure VISA implementation in Python that doesn't need the proprietary libraries.

I want to implement this in Rust. It should run on Windows, Linux and Mac and also on x86 and ARM (maybe even on bare metal with no-std) if possible.

I want to be able to talk to measurement devices and power supplies over RS232, USB, Ethernet and GPIB and I want to focus on SCPI protocol first.

I haven't found much projects in Rust on this topic. There is RusticLab which seems to be only a wrapper for NI-VISA and a SCPI parser which seems to be more a library for a measurement device itself.

Please let me know if I missed a related project.

Isn't SCADA the same class software?

It sounds like you are trying to write a library which communicates with peripheral devices. Generally you'll do this by

  1. Look up the data sheet for the device in question and find out how it communicates with the PC
  2. Find a library which talks that particular communication protocol (e.g. the spidev crate for devices communicating over SPI)
  3. Using the data sheet, figure out which messages get sent back and forth and how they are interpreted on either end
  4. Wrap all of this up into a library which has user-friendly methods for extracting the various bits of data
  5. Repeat steps 1-4 for the next device

Often you can separate the data being passed back and forth (messages) from the mechanism used to transmit this data (SPI, SCSI, etc.). This is good for making a cross-platform library because only the communication mechanism will be specific to a platform, which means you can reuse the same messaging and business logic while passing in the platform-specific bits (e.g. like how you pass the reader into hound's WavReader::new() constructor instead of it being hard-coded to only use files) or papering over them with conditional compilation.

Sounds similar, but has a different goal. It seems to connect wit PLCs and similar devices. I want to communicate with things like a multimeter, e.g. the Keithley 2701 or a power supply from Agilent. Typical interfaces are RS232, Ethernet or GPIB and a common protocol in SCPI.

Kind of. They don't have SPI interfaces (see comment above). But in general you are right. I want to write different layers. A communication layer (Ethernet, RS232, GPIB over USB, GPIB over PCI and so on). On top of that a protocol layer (SCPI for now) and on top of that implementations for different devices.

You see this pattern quite often in the embedded world.

As a quick example, there is an aht20 crate for interfacing with AHT20 temperature/humidity sensors. It exposes a Aht20 type which accepts some I2C object which can read and write bytes. You would then pair this with something like I2Cdev from the linux-embedded-hal crate which provides access to the Linux I2C subsystem. The embedded_hal crate then ties all of the components together by providing common abstractions.

The annoying part is you don't have any commonly accepted tools like grpc or swagger for declaring how to interact with a particular device, so you often need to write all the code by hand instead of letting a program generate it for you. I think that'll be a big reason why the only libraries you can find are proprietary... companies have sunk a lot of engineering hours into writing these interfaces and it contains a lot of valuable IP.

Sure, but this isn't an embedded project. I don't want to interface temperature or humidity sensors via I2C or SPI. I'm talking about professional equipment like the previously mentioned Keithley 2701 with Ethernet (it's a bit older, but I'm still using it at work).

In your original post you said you want to implement something like VISA or the Keysight IO Library Suite in Rust. That makes it sound like you want to make some sort of toolkit which works with a large variety of devices on a large variety of platforms.

I doubt you'll find pre-made Rust libraries for the exact equipment you are targeting, simply because the manufacturer will normally provide you with a C library and some header files and call it a day (if that). That means you'll be writing a lot of code yourself and big picture things like architecture and layers are where you'll gain the most value (hence the examples from analogous projects in the embedded world). On the other hand, creating an interface for a specific device is "just" a case of looking at the data sheet and doing the grunt work.

That said, here are some concrete crates you may be able to use:

  • pnet for low level networking primitives (e.g. sending/receiving ethernet packages)
  • scpi for working with the SCPI protocol
  • binread for parsing custom binary protocols in a declarative way

That's the point. A HAL might be a good description for that. Most devices share similar functions and I want to create a trait for them.

For example the Keithley 2701. Create a new object for this device, set the communication interface (Ethernet, RS232 or GPIB over USB and so on) and than you can call functions like "get_voltage" or similar. It should be easily possible to replace the Keithley with another device with the same features.

The trait translates the function to the SCPI commands for a specific device and sends it over the specified communication interface.

The scpi crate seems to implement only the client side of the protocol, if I'm not totally wrong, and the SCPI protocol is pure ASCII. No need for binary.

I was able to write something similar in Python, but only for the Keithley. I want something more generic and in Rust.