10. Instrument drivers

This chapter describes the definition of instrument drivers and instructions for how to create custom drivers. The general driver structure is visualized in Fig. DriverStructure. The Communication part describes the interface and address used for communication, and is normally handled by the Labber Instrument server. The Model and options part provides a way to enable/disable certain features of a driver depending on the instrument model/installed options. Finally, the list of Quantities define all properties and settings available on the instrument.

_images/DriverStructure.png

Structure of an Instrument driver. The Communication part is handled by the Instrument server, while the Model and Options and the Quantities are defined in the driver configuration file.

10.1. Driver definition files

The driver definition file is a file specifying the instrument name, vendor, model and options as well as a list of quantities. For basic instrument drivers, where the value of each quantity can be set or read using a single text command over GPIB, serial, USB or ethernet using the VISA protocol, all information about the instrument and the communication is contained in the definition file. For more advanced drivers, for example network analyzers that capture vector data, the driver definition file needs to be complemented with Python source code for implementing the more advanced instrument operations (see Section PythonDriver). The Python code must be Python 3 compatible.

The driver definition files provided by Labber are located in the “Instrument drivers” folder (see Section PrefsFolder for information on folder locations). The definition files are plain text files using the INI file format, which consists of a number of “sections”, each containing a list of “properties”. The driver file requires implementing sections for General settings, Model and options and VISA Settings, see below for more information about each section. It is recommended to use one of the already present drivers configuration files as a template. For an example of creating an instrument driver from scratch, see Section PythonDriver.

When creating a new driver, the definition file should be placed in the local driver folder (the folder named “Local drivers” in the Preferences window), instead of the global one (“Instrument drivers” in Preferences). This allows the user’s own drivers to be kept separately from the drivers provided by Labber, and it also prevents drivers written by the user from being deleted when updating the Labber program to a newer version. The INI configuration file can be placed directly in the “Drivers” folder, or within a subfolder of that directory. Using a subfolder is the recommended approach, since it gives a natural place to store extra files related to the driver.

Note that even when making additions/changes to an existing driver from the global folder, the best practice is to copy that driver file from the global folder to the local folder, and only make changes to the local version. If drivers with the same names exist in both the local and the global driver folders, Labber will always use the driver in the “Local drivers”-folder.

10.1.1. Signal Generators and Signal Analyzers

Signal Generators and Signal Analyzers are drivers that are used to generate or analyze waveforms. The drivers do not perform any instrument communication, which means that the Model and options and the VISA Settings parts of the INI file do not need to be defined. To define that a driver is a Signal Generators or a Signal Analyzers, set the corresponding item in the General settings-part of the INI file as described below. See Section Signals for more information about how Signal Generators or a Signal Analyzers are used in an experiment.

10.1.2. General settings

The General settings-section define name and version of the driver. Note that it is the name property in this section that sets the driver name, not the name of the driver definition INI file.

name:
The name is shown in all the configuration windows.
version:
The version string should be updated whenever changes are made to this config file.
driver_path:
Name of folder containing the code defining a custom driver. Do not define this item or leave it blank for any standard driver based on the built-in VISA interface.
interface:
Pre-defined communication interface for instrument, default is GPIB.
address:
Pre-defined address for instrument, default is an empty string.
startup:
Pre-defined startup option for instrument, default is Set config.
signal_generator:
Set to True if driver is a Signal Generator. Default is False.
signal_analyzer:
Set to True if driver is a Signal Analyzer. Default is False.
support_hardware_loop:
Set to True if driver supports hardware looping. Default is False.
support_arm:
Set to True if driver supports hardware arming. Default is False.
use_32bit_mode:
Set to True if driver should run in a 32-bit Python environment. Default is False (run in 64-bit). For more information, see Section PythonDist below.

10.1.3. Model and options

The Model and options-section provides a way to enable/disable certain features of a driver depending on the instrument model/installed options.

model_str_1, model_str_2, etc:
List of models supported by the driver.
check_model:
If True, the driver checks the instrument model id at startup (True or False). The model is checked by sending the model_cmd command (see below) over the VISA interface. Default is False.
model_cmd:
Command used to check the instrument model. Default command is *IDN?.
model_id_1, model_id_2, etc:
Model strings expected to be returned by the instrument by the *IDN? call. If not defined, the program assumes model_str_1, model_str_2, etc as default values
option_str_1, option_str_2, etc:
List of available instruments options. The options are shown as checkbox controls in the driver configuration window.
check_options:
If True, the driver checks the installed instrument options at startup (True or False). The option is checked by sending the option_cmd command (defined below). Default is False.
option_cmd:
If check_options is set to True, define command for getting the options from the instrument.
option_id_1, option_id_2, etc:
If check_options is set to True, supply valid id option strings that the instrument returns when sending the option_cmd. The list of option_id should match the elements in the list option_str.

10.1.4. VISA Settings

This section contains configuration of the VISA protocol. The VISA protocol enables text-based communication with instruments over GPIB, USB, serial and ethernet interfaces.

use_visa:
Enable or disable communication over the VISA protocol (True or False). If False, the driver will not perform any instrument operations (unless there is a custom Python driver, see Section PythonDriver).
reset:
Reset the interface (not the instrument) at startup (True or False). Default is False.
query_instr_errors:
Query instrument errors (True or False). If True, every command sent to the device will be followed by an error query. This is useful when testing instruments, but may degrade performance by slowing down the instrument communication.
error_bit_mask:

If query_instr_errors is True, set bit mask for checking status byte errors (default is 255, include all errors). The bits signal the following errors:

0: Operation

1: Request control

2: Query error

3: Device error

4: Execution error

5: Command error

6: User request

7: Power on

error_cmd:
Command string to be sent to instrument when querying for instrument error messages.
init:
Initialization commands are sent to the instrument when starting the driver. *RST will reset the device, *CLS clears the interface.
final:
Final commands sent to the instrument when closing the driver.
str_true:
String used for sending boolean True to the instrument, default is 1.
str_false:
String used for sending boolean False to the instrument, default is 0.
str_value_out:
Conversion string used for converting value to string to be sent to the instrument. Default is %.9e, which creates 9-digit string using exponential notation. To create strings with floating-point notation, use %.9f instead.
str_value_strip_start:
Number of characters to strip from the beginning of the string returned from the instrument, before trying to convert to a number. Default is 0.
str_value_strip_end:
Number of characters to strip from the end of the string returned from the instrument, before trying to convert to a number. Default is 0.
always_read_after_write:
If True, the program will automatically read the response from the instrument after each write command. Useful for instruments that always reply to all commands. Default is False.

The following entries are optional, they provide detailed settings for the communication interface. Note that the values provided in the INI file will be the default setting for the driver, but the user can always change the settings by going to the Communication settings of the instrument driver user interface and clicking “Show advanced interface settings”.

timeout:
Time (in seconds) before the timing out while waiting for an instrument response. Default is 5 seconds.
term_char:
Termination character used by the instrument, valid values are Auto, None, CR, LF, CR+LF.
send_end_on_write:
Assert end during transfer of last byte of the buffer
suppress_end_on_read:
Suppress end bit termination on read
baud_rate:
Communication speed for serial communication. Default is 9600.
data_bits:
Number of data bits for serial communication. Default is 8.
stop_bits:
Number of stop bits for serial communication. Default is 1, possible values are 1, 1.5 and 2
parity:
Parity used for serial communication, possible values are No parity, Odd parity, Even parity.
gpib_board:
GPIB board number. Default is 0.
gpib_go_to_local:
Make GPIB instrument automatically go to local after closing. Default is False.
tcpip_specify_port:
Use specific TCPIP socket port. Default is False.
tcpip_port:
TCPIP socket port. Only relevant if tcpip_specify_port is True.

10.2. Quantities

All quantities are defined in separate sections, with the name of the quantity given by the section header. The properties of a quantity are defined by a number of keywords, see below for a list the possible options. Only the datatype keyword is mandatory, the other ones are optional.

datatype:
The data type should be one of DOUBLE, BOOLEAN, COMBO, STRING, COMPLEX, VECTOR, VECTOR_COMPLEX, PATH or BUTTON. Only DOUBLE, BOOLEAN and COMBO datatypes can be stepped in a measurement. The BUTTON datatype does not have an associated value, and can therefore not be controlled from the Measurement program. It is typically used to manually force an instrument to perform a certain task.
label:
Label shown next to control in user interface. If not specified, the label defaults to the name of the quantity.
unit:
Unit for the quantity.
def_value:
Default value.
tooltip:
Tool tip shown when hovering the mouse over the control in the driver GUI.
low_lim:
Lowest allowable value. Defaults to -INF.
high_lim:
Highest allowable values. Defaults to +INF.
x_name:
X-axis label for a vector data. Only valid if datatype is VECTOR or VECTOR_COMPLEX.
x_unit:
X-axis unit for a vector data. Only valid if datatype is VECTOR or VECTOR_COMPLEX.
combo_def_1, combo_def_2, ...:
Options for a pull-down combo box. Only used when datatype is COMBO.
group:
Name of the group where the control belongs.
section:
Name of the section where the control belongs.
state_quant:
Quantity that determines this control’s visibility.
state_value_1,state_value_2, ...:
Values of state_quant for which the control is visible.
model_value_1, model_value_2, ...:
Values of model for which the control is visible. The value must match one of the models defined in the Model and Options-section described above.
option_value_1, option_value_2, ...:
Values of option for which the control is visible. The value must match one of the options defined in the Model and Options-section described above.
permission:
Sets read/writability, options are BOTH, READ, WRITE or NONE. Default is BOTH.
show_in_measurement_dlg
This setting is optional. If True, the quantity will be automatically shown when adding the instrument to a Measurement configuration. This is useful for instrument that contain a lot of quantities, but where most are not likely to be stepped in a measurement.
set_cmd:
Command used to send data to the instrument. Put “<*>” where the value should appear. If “<*>” does not occur in the string, the value will be added after the command.
get_cmd:
Command used to get the data from the instrument. Default is set_cmd?.
cmd_def_1, cmd_def_2, ...:
List of strings that define what is sent to/read from an instrument for a quantity that is defined as a a list of multiple options. Only used when datatype is COMBO.

See Section SweepDriver for a list of extra properties that need to be defined for instruments that support sweeping.

The Instrument Server uses the list of quantities to create the controls in the driver dialog window, as shown in Fig. DriverDef.

_images/DriverDefinition.png

An Instrument driver dialog, shown together with the corresponding instrument definition file.

10.3. Custom drivers - Python code

Custom drivers are required when single-line command strings get_cmd and set_cmd as defined in the instrument definition file are too simple to read or write a value to an instrument. This is often the case for instruments like network analyzers or oscilloscopes, which contained vector-valued quantities that depend in complicated ways on other settings of the instrument.

The process of creating a custom driver is best described by an example. We are going to create a driver that generates a sinusoid, but without doing any actual instrument communication (the driver will be a Signal Generator, as describe in Section Signals). For an example involving instrument communication, see the drivers for one of the network analyzers or oscilloscopes in the Instrument Drivers folder.

10.3.1. Creating the driver definition file

Every driver, even the custom ones, require a definition file. We start with the General settings-section:

[General settings]

# The name is shown in all the configuration windows
name: Simple Signal Generator

# The version string
version: 1.0

# Name of folder containing the code defining a custom driver
driver_path: SimpleSignalGenerator

# Define that the driver is a Signal Generator
signal_generator: True

Note that we define the driver_path: this signals that there is a custom driver available for this instrument. When starting the driver, the Instrument Server will look for the Python file “SimpleSignalGenerator/SimpleSignalGenerator.py” in the Instrument Drivers folder, or for the file “SimpleSignalGenerator.py” in the folder where the INI configuration file is located. See Section PythonCode below for more information on how to implement the code for custom drivers.

This particular instrument driver does not do any instrument communication and therefore does not have any model or option definitions, so we can skip the Model and options-section and the VISA settings-section.

Next, we need to define the quantities of the driver. For this example, we want to be able to define the amplitude, frequency and phase of the signal to be generated. In addition, we want to add the option of adding white noise to the signal.

[Frequency]
datatype: DOUBLE
unit: Hz
def_value: 10.0

[Amplitude]
datatype: DOUBLE
unit: V
def_value: 1.0

[Phase]
datatype: DOUBLE
unit: deg
def_value: 0.0

[Add noise]
datatype: BOOLEAN
def_value: False

[Noise amplitude]
datatype: DOUBLE
unit: V
def_value: 0.1
state_quant: Add noise
state_value_1: True

[Signal]
datatype: VECTOR
permission: READ
x_name: Time
x_unit: s

Note that the Noise amplitude quantity will only be visible if Add noise is True. The last quantity (“Signal”) represent the signal we want to generate in the Python code. The permission of this quantity is set to READ, to indicate that this quantity can only be read, not written.

For your convenience, this example driver INI definition file and the corresponding Python code are available under Examples in the Instrument Drivers folder.

10.3.2. Implementing the Python code

Once the INI file has been created, we need to implement the Python code that generates the signal. The code should define a subclass of either the InstrumentDriver.InstrumentWorker or the VISA_Driver class, depending on if the driver will use the VISA protocol for communication or not. The VISA_Driver class is a subclass of InstrumentDriver.InstrumentWorker, and details for how to subclass the VISA_Driver is described in Section SubClassVISA below. Labber is running Python 3 for all instrument drivers, make sure that all code is Python 3 compatible. See Section PythonDist below for more information about the Python distribution.

The new class should re-implement the four functions performOpen, performClose, performSetValue and performGetValue, which are called when an instrument is started, stopped, and called for setting or getting an instrument value, respectively. To describe the procedure, we create a Python class for the Simple Signal Generator-example shown above:

import InstrumentDriver
import numpy as np

class Driver(InstrumentDriver.InstrumentWorker):
    """ This class implements a simple signal generator driver"""

    def performOpen(self, options={}):
        """Perform the operation of opening the instrument connection"""
        pass

    def performClose(self, bError=False, options={}):
        """Perform the close instrument connection operation"""
        pass

As described in the previous section, the Simple Signal Generator is only for demonstration purposes and will not involve any actual instrument communication, so we subclass InstrumentDriver.InstrumentWorker instead of VISA_Driver. In this example, the functions performOpen and performClose don’t do anything.

The code for the more interesting functions performSetValue and performGetValue follow below:

def performSetValue(self, quant, value, sweepRate=0.0, options={}):
    """Perform the Set Value instrument operation. This function should
    return the actual value set by the instrument"""
    # just return the value
    return value

def performGetValue(self, quant, options={}):
    """Perform the Get Value instrument operation"""
    # proceed depending on quantity
    if quant.name == 'Signal':
        # if asking for signal, start with getting values of other controls
        amp = self.getValue('Amplitude')
        freq = self.getValue('Frequency')
        phase = self.getValue('Phase')
        add_noise = self.getValue('Add noise')
        # calculate time vector from 0 to 1 with 1000 elements
        time = np.linspace(0,1,1000)
        signal = amp * np.sin(freq*time*2*np.pi + phase*np.pi/180.0)
        # add noise
        if add_noise:
            noise_amp = self.getValue('Noise amplitude')
            signal += noise_amp * np.random.randn(len(signal))
        # create trace object that contains timing info
        trace = quant.getTraceDict(signal, t0=0.0, dt=time[1]-time[0])
        # finally, return the trace object
        return trace
    else:
        # for other quantities, just return current value of control
        return quant.getValue()

The functions performSetValue and performGetValue take a quant object as a first parameter. The object represents the quantity to be read/set, and all properties of the quantity (as defined in the INI configuration file) can be accessed from the object’s data members. This is used in the performGetValue-function, where the object variable quant.name is accessed to find out which quantity to read. See Section quantObj below for more info about the quant objects.

The options variable present in both performSetValue and performGetValue-definitions is a Python dictionary that contains additional options for setting/getting a value. It is used to provide a way to determine if a driver is called multiple times within a single step of a Measurement (see functions isFirstCall and isFinalCall in the list of driver helper functions in Section driverObj below).

10.3.3. Helper functions for quant objects

The quant object represents an instrument quantity, and it provides a few helper functions that are useful when writing drivers:

quant.getValue():
The function returns the current value of the quantity. Note that it will just return the local value stored the driver, no instrument communications is performed when calling this function.
quant.getValueIndex(value=None):
The function returns the value as an index number, only useful for quantities with datatype=COMBO. If value=None, the function will return the local value stored the driver. Note that no instrument communications is performed when calling this function.
quant.setValue(value, rate=None):
The function sets the current value of the quantity. Note that it will just update the local value stored in the driver, no instrument communications is performed when calling this function.
quant.getTraceDict(y, x0=0.0, dx=1.0, x1=None, x=None, logX=False):
Returns a python dictionary containing the numpy array y, together with additional x-scale info. The x-scale information can be supplied either as start value and step size (x0, dx), as start and stop values (x0, x1), or as a full vector (input parameter x, must have same length as y). If using the start/stop notation (x0, x1), it is possible to set logX to True to create a trace with logarithmic interpolation between the start/stop values. These dictionaries are used to pass waveform data between drivers with vector-valued quantities, like Signal Generator and Signal Analyzers.
quant.getCmdStringFromValue(value=None)
Convert the input value to a string formatted for sending to the instrument. If the input parameter value is None, the current value is used.
quant.getValueFromCmdString(sValue)
Inspect the input string sValue coming from the instrument and return a numerical value.

10.3.4. Helper functions for driver objects

The base driver object InstrumentDriver.InstrumentWorker provides the following helper functions. The options variable present in the functions isFirstCall and isFinalCall is a Python dictionary with additional options that is passed to the performSetValue and performGetValue-functions when calling the driver from outside.

getName():
Return name of instrument, as defined in the user-interface dialog.
getInterface():
Return instrument interface, as defined in the dialog. The interface type is one of GPIB, TCPIP, USB, Serial, VISA, Other, None.
getAddress():
Return address of instrument, as defined in the user-interface dialog. This is function can be used to determine when opening communication to an instrument.
getCommunicationCfg():
Return communication configuration as a dictionary, with the following keys: Timeout, Term. character, Send end on write, Suppress end bit termination on read, Baud rate, Data bits, Stop bits and Parity. The configuration items are described in Section CommunicationCfg above.
getValue(quant_name):
The function is used to access the current local value of any quantity of the driver. The function is used repeatedly in the example above for getting the amplitude, frequency and phase when creating the sinusoid.
getValueArray(quant_name):
Same as above, but will return current value as a numpy array instead if the quantity is vector-valued. Otherwise, it’ll return an empty numpy array.
getValueIndex(quant_name):
Get value of quantity as numerical index. Only useful for quantities with datatype=COMBO.
getCmdStringFromValue(quant_name):
Get command string for current value of quantity with name quant_name. See quant.getCmdStringFromValue in section above for more info.
setValue(quant_name, value, sweepRate=None):
The function is used to set the local value of any quantity of the driver. No hardware communication will take place; to actual set the instrument value, use the function sendValueToOther defined below.
readValueFromOther(quant_name, options={}):
The function will read the value of another quantity from the instrument. In contrast to the getValue mentioned above, this function will perform actual hardware communication to retrieve the current value from the instrument. The function will return the updated value.
sendValueToOther(quant_name, value, sweep_rate=0.0, options={}):
The function will communicate with the hardware to update the value to another quantity of the instrument. The function will return the updated value.
getModel():
Get model string.
setModel(model_name):
Set model string.
getOptions():
Get list of strings describing installed options.
setOptions(list_of_options):
Set list of strings describing installed options.
isConfigUpdated(bReset=True):
Returns true if any non-read-only quantity of the instrument has been updated since the last call to this function where bReset was True.
isFirstCall(options):
If a driver is used in a Measurement and there are multiple quantities of that driver that will be updated within a single step, it can be advantageous to delay outputting data to an instrument until all local driver quantities have been updated. This function returns True if the current call is the first one within the current measurement step.
isFinalCall(options):
Same as above, but returns True if the current call is the last one.
isStopped():
Return True if the user stopped the measurement. If the instrument communication is expected to take a long time, it’s recommended to periodically call this function to ensure that the driver remains responsive to user interaction.
isHardwareTrig(options):
Return True if the caller is in hardware trig mode.
isHardwareLoop(options):
Return True if the caller is in hardware loop mode.
getHardwareLoopIndex(options):
Get the current hardware loop index. The function returns a tuple (index, n_pts), where index is the index for the current call, and n_pts is the total number of points of the hardware loop.
log(message, level=20):
Log a message to the instrument logger. The log level is an integer ranging from 30 (warning, always shown) to 10 (debug, only shown in debug mode).
wait(wait_time=0.05):
Pause execution and put the process to sleep for the given time (in seconds).
getValueFromUserDialog(value=None, text=’Enter value:’, title=’User input’):
Show user interface dialog to ask the user for an input value. The function returns the value entered by the user.
reportStatus(message):
Report status update to the Instrument Server and connected clients. The argument message should be a string.
reportProgress(quant, progress):
Report progress update when setting/getting the value of the quantity quant to the Instrument Server and connected clients. The argument progress should be a floating point value between 0.0 and 1.0. The function is used to provide feedback to the user when performing slow instrument operations, for example when sweeping a magnetic field.
reportCurrentValue(quant, value):
Report current value of the quantity quant to the Instrument Server and connected clients. The function is used to provide feedback to the user when performing slow instrument operations, for example when sweeping a magnetic field.

10.3.5. Testing the driver

As stated previously, this example driver INI definition file and the corresponding Python code are available under Examples in the Instrument Drivers folder. To test the driver, move the INI file and the folder with the Python code to reside directly in the Instrument Drivers folder. Next, start the Instrument Server and add a new instrument. If the driver is defined properly, the Simple Signal Generator should show up in the instrument driver list. Select the new driver and although the driver doesn’t perform any instrument communication, we still need to provide an address. Select “Other” under “Interface” and type any string in the “Address” text box. This to ensure that every instrument has a unique address so that the Instrument Server can access multiple instances of the Simple Signal Generator, if needed. Finally, click “OK” to close the dialog.

_images/SignalGenerator.png

The example instrument driver Simple Signal Generator.

The new instrument should appear in the main Instrument Server list. Double-click the instrument name will bring up the driver configuration window, as shown in Fig. SignalGen. To test the code, start the driver with the “Start” button and make sure the “Trace”-checkbox is checked to view the sinusoid. To control parameters while the driver is running, either just update one of the controls, or go to the server window, expand the “Simple Signal Generator”-item in the instrument list and use the “Set Value”-button to set a new value. If the “Update continuously”-control in the driver dialog is checked, you will see the trace change in real time as parameters are modified.

10.4. Subclassing the VISA driver

The previous example was a little bit unusual, since no actual instrument communication was performed in the driver. A more common situation would be where most communication can be handled with simple text-based commands as defined in the driver INI configuration file, but where a few advanced quantities need special functionality. For these cases, the easiest way to proceed is to subclass the VISA_Driver and only re-implement code for the special cases. The code below shows an example of what such a driver would look like:

from VISA_Driver import VISA_Driver

class Driver(VISA_Driver):
    """ This class re-implements the VISA driver"""

    def performOpen(self, options={}):
        """Perform the operation of opening the instrument connection"""
        # calling the generic VISA open to make sure we have a connection
        VISA_Driver.performOpen(self, options=options)
        # do additional initialization code here...
        pass

    def performClose(self, bError=False, options={}):
        """Perform the close instrument connection operation"""
        # calling the generic VISA class to close communication
        VISA_Driver.performClose(self, bError, options=options)
        # do additional cleaning up code here...
        pass

    def performSetValue(self, quant, value, sweepRate=0.0, options={}):
        """Perform the Set Value instrument operation. This function should
        return the actual value set by the instrument"""
        # check quantity name
        if quant.name == 'Some_Special_Operation':
            # special case, perform special code to set value
            pass
        else:
            # otherwise, call standard VISA case
            value = VISA_Driver.performSetValue(self, quant, value, sweepRate, options)
        return value

    def performGetValue(self, quant, options={}):
        """Perform the Get Value instrument operation"""
        # check quantity name
        if quant.name == 'Some_Special_Operation':
            # special case, perform special code to get value
            value = 0.0
        else:
            # for all other cases, call generic VISA driver
            value = VISA_Driver.performGetValue(self, quant, options)
        return value

Note that both the performOpen and performClose functions have to call the generic VISA class, to make sure that the communication is properly initiated.

10.4.1. Helper functions for drivers subclassing the VISA driver

In addition to the helper functions provided by the generic driver object (described in Section driverObj above), the VISA_Driver provides the following helper function:

writeAndLog(sCmd, bCheckError=True):
The function will send the command string sCmd to the instrument. If bCheckError is True, an error check is performed after the command has been sent.
write(sCmd, bCheckError=True):
Same as above, but no entry will be created in the Instrument Log, regardless of the log level. See Section logs for more information about the logging feature.
reply = askAndLog(sCmd, bCheckError=True):
The function will send the command string sCmd to the instrument and wait for a reply. The reply is returned as a text string. If bCheckError is True, an error check is performed after the command has been sent and data has been received.
reply = ask(sCmd, bCheckError=True):
Same as above, but no entry will be created in the Instrument Log, regardless of the log level. See Section logs for more information about the logging feature.
reply = read(n_bytes=None, ignore_termination=False):
Read a total of n_bytes from the device, ignoring any termination characters. If n_bytes is None, the complete buffer is read. If ignore_termination is set to True, the program will not check for or remove termination characters.
queryErrors():
Check for instrument errors by checking the event status register (*ESR?). An exception is raised if the instrument reports an error. The check only takes place if the item query_instr_errors in the VISA settings of the INI file is set to True.

10.5. Support for sweeping

Some quantities, for example the B-field of a magnet, require the output to be changed with a well-defined sweep rate whenever the value is updated. Labber provides supports for swept quantities, but such drivers require a few extra configuration settings compared to standard drivers. The extra setting are described in the subsection below. For more information on how swept experiments are implemented in the Measurement Setup dialog, see Section SweepModeSetup.

10.5.1. Sweeping - Driver definition file

In addition to the properties listed in in Section Quantities, the driver INI-file of an instrument that supports sweeping needs to define the following properties for a sweepable quantity:

sweep_cmd:
Command used to sweep data. Use “<sr>” for sweep rate or “<st>” for sweep time, and “<*>” for the value. Note that sweep rate will be defined in terms of change per second or change per minute, as set by the sweep_minute-setting defined below. If the instrument does not have a built-in command for sweeping, a similar effect can be achieved by repeatedly using the set_cmd to incrementally change the instrument value. To enable this feature, set sweep_cmd to ***REPEAT SET***, followed by the time interval between setting values (in seconds). If no time interval is defined, default is 0.1 seconds.
sweep_check_cmd:
Command used to check if the instrument is currently in sweep mode. The instrument should return True or 1 if the instrument is sweeping towards a value. If sweep_check_cmd is not defined, the program will determine if an instrument is in sweep mode by continuously reading the current value and comparing it against the target value with resolution sweep_res, as defined below.
sweep_res:
Attainable resolution when sweeping an instrument, in absolute units. Default value is 10^{-10}, to avoid float rounding errors. This parameter is not used if the sweep_check_cmd is defined.
stop_cmd:
Command used to stop a sweep.
sweep_rate:
Default sweep rate, in rate per second or rate per minute (as set by the sweep_minute parameter defined below). If this value is non-zero, sweeping will be turned on automatically for this quantity. Default value is 0.
sweep_minute:
If True, sweep rates are defined in terms of value rate per minute, otherwise in rate per second. Default is False (rate per second).
sweep_rate_low:
Minimal sweep rate. Default is 0.
sweep_rate_high:
Maximal sweep rate. Default is +Inf

Note that the existence of the sweep_cmd-parameter defines whether a quantity is sweepable or not. If a quantity is sweepable, the Instrument Server, Instrument Driver and the Measurement Setup configuration dialogs will contain a few extra options for controlling the sweep rates. If the sweep_cmd-parameter is defined but the set_cmd-parameter is not, the driver will not allow direct setting of output values. This is useful for instruments like magnets, whose output currents must always be swept at a certain rate.

10.5.2. Sweeping - Python code

If the sweeping functionality of a driver cannot be implemented using the built-in functionality based on the parameters in the driver definition file listed above, it is possible to write custom Python code for carrying out the sweeping. To begin with, the performSetValue-function for setting an instrument value (described in Section PythonCode and Section SubClassVISA above) needs to be implemented to support sweeping:

def performSetValue(self, quant, value, sweepRate=0.0, options={}):
When re-implementing the performSetValue-function for a swept quantity, it is important that the code inspects the sweepRate parameter to see if the user wants to set the value directly (sweepRate=0.0), or perform sweeping (sweepRate>0.0). Note that in sweep mode (sweepRate>0.0), the function should not wait for the sweep to finish, since the sweep checking/waiting is handled by the Instrument Server. The sweepRate parameter is defined in terms of change per second or change per minute, as set by the sweep_minute configuration parameter defined in the section above.

In addition to the four standard functions performOpen, performClose, performSetValue and performGetValue described in Section PythonCode, drivers that support sweeping may also re-implement the following functions:

def checkIfSweeping(self, quant, options={}):
The function should return True if the instrument is currently sweeping to the target value. The standard implementation will either send the sweep_check_cmd to the instrument or continuously read the current value and compare to the target, as described in Section SweepDriver above.
def performStopSweep(self, quant, options={}):
This function should stop the current sweep. The default implementation will send the stop_cmd to the instrument, as described in Section SweepDriver above.

10.6. Hardware arming and triggering

In hardware trigger mode, log instrument will be armed to wait for a hardware trigger before starting to acquire data. The function isHardwareTrig(options) can be used by both instruments outputting and instruments reading values to check if the measurement is in hardware trig mode. Instruments that supports hardware arming need to define the support_arm parameter in the General settings of the driver definition file (see Section DriverINIGeneral above), and implement the following function:

def performArm(quant_names, options={}):
The function should arm the instrument, to make it ready to acquire values for the list of quantities defined by quant_names.

The function performArm is called before issuing the trigger starting the measurement. See Section HardwareTrig for more information how hardware triggering is configured in the Measurement program.

10.7. Hardware looping

Some instruments can perform looping of values within the instrument hardware. This allows for implementing more efficient looping than with a computer, since there will be no need for the computer to send new values to the instrument at each step value. For a more detailed description of how hardware looping works and how it is configured in the Measurement program, see Section HardwareLoop.

Hardware looping requires that both the instrument outputting and the instrument reading values support hardware looping, and that the instrument reading values supports hardware arming, as defined by the support_hardware_loop and support_arm parameters in the General settings of the driver definition file (see Section DriverINIGeneral above).

10.7.1. Hardware looping - outputting values

In hardware looping mode, the performSetValue function will be called n times for a step sequence containing n points. At the final call, the instrument should be configured to start outputting values when a trigger is issued. The function isHardwareLoop(options) can be used to check if the measurement is in hardware loop mode, and the function (index, n_pts) = getHardwareLoopIndex(options) can be used to get the current hardware loop index and the total number of point n_pts.

10.7.2. Hardware looping - reading values

In addition to defining the support_hardware_loop and the support_arm parameters in the driver definition file, the driver Python file needs to implement the performArm function for arming the instrument to acquire multiple values. The number of values to expect is given by the output n_pts of the function (index, n_pts) = getHardwareLoopIndex(options). After the instrument has been armed and a trigger has been sent, the function performGetValue will be called multiple times to acquire the results.

In the same way as for instruments outputting values, the functions isHardwareLoop(options) and (index, n_pts) = getHardwareLoopIndex(options) can be used to check if the measurement is in hardware loop mode, and to get the current hardware loop index and the total number of point n_pts, respectively.

10.8. Python distribution

When starting an instrument driver, Labber will launch a dedicated driver process and execute its Python code in the new process. By default, Labber will use the default, built-in Python distribution, which currently is a 64-bit version of Python 3.5. However, this may change to a newer Python version in a future release of Labber.

10.8.1. Python distribution, 32-bit version

For compatibility reasons, Labber is also shipped with a 32-bit version of the same Python distribution (Windows only), to allow control of older instruments for which only 32-bit Windows DLLs/drivers are available.

To activate the 32-bit Python version for a specific driver, open the driver’s configuration window in the Instrument Server, go to the “Communication”-section, click “Show advanced interface settings”, and check the “Run in 32-bit mode”-box prior to starting the instrument driver. Note that each instance of an instrument driver is running in its own, separate process, which makes it is possible to have some drivers run in 32-bit mode, while others are running in 64-bit mode. To set an instrument’s default setting to run in 32-bit mode, use the use_32bit_mode-flag described in Section DriverINIGeneral above.

10.8.2. External Python distribution

Sometimes it is convenient to use an external Python distribution instead of Labber’s built-in one. For example, a driver may be relying on a number of external Python packages , and it is convenient to install/update those packages using a Python package manager instead of manually copying them into the folder location of the Labber driver.

To use an external Python distribution, open Labber’s Preferences dialog, go to the “Advanced”-section and point the “Python distribution” control to the file representing the python executable for a given Python environment. The Python environment must be running Python 3.5 or later, and it is recommended to use the Anaconda/miniconda package manager for configuring the environment. For Anaconda/miniconda distributions, the python executable is located directly in the root of each environment folder (pythonw.exe, Windows), or under bin/python (MacOS version). Note that the external Python distribution will be used for all instrument drivers, except for the ones running in 32-bit mode (Windows only).

The external Python distribution must contain the following packages:

  • numpy
  • scipy
  • h5py
  • pycrypto
  • future
  • pyvisa

To set up a valid environment using Anaconda/miniconda, run the following command from the command line:

conda install pip numpy scipy h5py pycrypto future
pip install pyvisa

For more information about the Anaconda/miniconda Python distributions, see https://www.continuum.io/.

10.8.3. Troubleshooting, external Python distribution

If a driver process terminates immediately upon starting or if a dialog pops up with a “Broken pipe”-message, there are most likely missing packages in the external Python distribution. To find out which package is missing, quit the Instrument Server and restart it from a terminal window to get access to the standard error output.

  • For Windows, open a terminal window, cd to the location of the Instrument Server application, the run the application InstrumentServer-Console.exe from the command line. In addition, the “Python distribution” variable mentioned in Section PythonDistExternal above should to point to the file python.exe instead of pythonw.exe.

  • For macOS, open a terminal window, cd into the location
    /Applications/Labber/InstrumentServer.app/Contents/MacOS, then run the Instrument Server by typing ./InstrumentServer in the terminal window.
  • On Windows, there have been reports of incompatibilities with certain versions of Anaconda. Anaconda3 v.4.4.0 is the most recent working version to have been tested to work.