Structural Overview of JISA - OE-FET/JISA GitHub Wiki

Structural Overview of JISA - "How the darn thing works"

This page is intended to give an overview of how the JISA library is structured and how it functions. It is not intended for those who simply wish to use it, create simple extensions or those faint of heart. This exists purely as a reference for those who really want to understand how it works down to a fine level of detail.

The library can generally be broken down into three parts:

  1. Instrument Control
  2. Data Handling
  3. GUI Creation

We shall cover each in its own section.

Instrument Control

JISA provides the means to communicate with instruments over a multitude of different protocols. First and foremost, it is designed to work using a VISA library (for example, National Instruments VISA). However, since most VISA implementations usually have at least one weakness depending operating system (for example GPIB using NI-VISA on linux is normally a fools errand), it is capable of falling back onto more primitive libraries to provide I/O.

Drivers

When connecting to a device, JISA will first try to use VISA. However, in the event that this fails it will try other libraries until it finds one that works. If none work, it will throw an exception. To implement this functionality, JISA has an interface called Driver. All classes that implement Driver are required to provide two methods:

public interface Driver {

    // Attempt to connect to the device at the given address using this driver
    Connection open(InstrumentAddress address) throws VISAException;

    // Scan for any instruments that can be communicated with using this driver
    StrAddress[] search() throws VISAException;

}

The first of these, open(...), should try to establish communications with the specified instrument, throwing an exception upon failure. If it succeeds, it should return a Connection object which represents the now open connection to the instrument, more on this later. This is used by JISA when trying to establish communications with an instrument.

The second method should scan using that driver for any connected instruments, returning an array of their addresses. This is used when VISA.getInstruments() is called (eg when we use GUI.browseVISA()).

Implementations of Driver currently include:

  • VISADriver - VISA Library
  • GPIBDriver - Linux-GPIB Library
  • NIGPIBDriver - National Instruments GPIB Library
  • SerialDriver - Native COM Library
  • RawTCPIPDriver - Native TCP-IP Library

VISA Class

The VISA class is a purely static class that serves as a layer between the various driver objects and instrument objects. Upon initialisation (ie the first time it is accessed), it will try to load up each driver by instantiating each driver object. Each one that successfully loads is added to an array. The result is output to the terminal:

Attempting to load drivers.
Trying VISA driver...                	Success.
Trying Linux GPIB (libgpib) driver...	Nope.
Trying NI-GPIB (ni4882) driver...    	Success.
Trying Serial driver...              	Success.
Trying Raw TCP-IP driver...          	Success.
Successfully loaded 4 drivers.

As we can see, in this example all but the linux-gpib library was successfully loaded (which makes sense considering this was run on a Windows installation with NI-VISA installed). If none load successfully, then the program will terminate in an error state.

The VISA class now provides two important funcionalities:

public class VISA {

    public static Connection openInstrument(InstrumentAddress address) throws VISAException {...}

    public static StrAddress[] getInstruments() throws VISAException {...}

}

The first of these, openInstrument(...), will successively try to open the specified instrument using each driver's open(...) method in the loaded drivers array until it finds one that does not throw an exception. It then returns the Connection object from that driver. If all drivers throw an exception, then so will openInstrument(...), saying that the instrument could not be opened using any available driver.

The second method, getInstruments() successively calls the search() methods of each of the loaded drivers. It then returns a combined array of all unique addresses found.

In essence then, VISA is a static class that acts to combine all the individual drivers into a standard means of opening communications with an instrument.

Connection Objects

JISA also defines an interface called Connection. This is what is returned when opening a connection using a Driver visa VISA. These objects are required to provide:

public interface Connection {

    void write(String toWrite) throws VISAException;

    String read(int bufferSize) throws VISAException;

    void setEOI(boolean set) throws VISAException;

    void setEOS(long character) throws VISAException;

    void setTMO(long duration) throws VISAException;

    void setSerial(int baud, int data, Parity parity, StopBits stop, Flow flow) throws VISAException;

    void close() throws VISAException;

}

write(...) should take a String and send it to the instrument.

read(...) should read from the instrument and return what it has read as a String.

setEOI(...) should set whether to use the EOI (End or Identify, a GPIB standard for indicating that a message is complete) line when using GPIB communications. For other forms of communication this method will do nothing.

setEOS(...) (End of Stream) should set what character should be considered to indicate the end of a message. For example, some instruments will send a carriage-return (CR) character: \r or a line-feed (LF) character: \n or a CRLF: \r\n to indicate that it has finished talking.

setTMO(...) (time-out) should set how long the connection should wait on an operation before timing out. By default this is set to 2 seconds.

setSerial(...) should set the serial parameters used when operating of a serial connection (such as RS-232). For other forms of communication this method will do nothing.

close() simply closes the connection.

In essence then, a Connection object is a handle on the newly-opened connection allowing for data to be sent and received through it.

VISADevice Class

To hide this complexity further, a base class called VISADevice is defined. When instatiated, it takes an address and attempts to acquire and store a Connection object using VISA.openInstrument(...). It then uses its Connection object to provide the methods write(...), read(...) and query(...).

A simplified version of the class is shown below:

public class VISADevice {

    private Connection        connection;
    private InstrumentAddress address;
    private String            terminator     = "";
    private int               readBufferSize = 1024;

    public VISADevice(InstrumentAddress adrress) throws IOException {

        try {
            this.connection = VISA.openInstrument(address);
            this.address    = address;
        } catch (VISAException e) {
            throw new IOException(e.getMessage());
        }

    }

    public void write(String command, Object... args) throws IOException {
        String commandParsed = String.format(command, args).concat(terminator);
        connection.write(commandParsed);
    }

    public String read() throws IOException {
        return connection.read(readBufferSize);
    }

    public String query(String command, Object... args) throws IOException {
        write(command, args);
        return read();
    }

    public double queryDouble(String command, Object... args) throws IOException {
        return Double.valueOf(query(command, args));
    }

}

The result of this is that now a connection can be established by instatiating an object, allowing extensions of VISADevice to be made which further utilise the read/write methods it provides to implement specific device commands like so:

public class MySMU extends VISADevice {

    public MySMU(InstrumentAddress address) throws IOException {
        super(address);
    }

    public double getVoltage() throws IOException {
        return queryDouble("MEASURE:VOLTAGE?");
    }

    public double getCurrent() throws IOException {
       return queryDouble("MEASURE:CURRENT?");
    }

    public void setVoltage(double voltage) throws IOException {
        write("SOURCE:VOLTAGE %f", voltage);
    }

    public void setCurrent(double current) throws IOException {
        write("SOURCE:CURRENT %f", current);
    }

}