Skip to content

Driver Guide

rsbrost edited this page Oct 1, 2024 · 42 revisions

Pyscan Driver Guide

What are drivers?

Drivers are classes designed to connect with specific instruments. There are 3 main components of driver classes that enable us to interface with instruments, design experiments, and run them efficiently:

  1. An instrument parameter that is used to connect to the target instrument.
  2. Attributes (or properties) including instrument settings that are controlled or measured as variables in, or outputs of, experiments.
  3. Methods that can be called to perform specific functions.

If a driver doesn't already exist, you can make your own.

Using a Driver

Select the driver you want to use. Is it a PyVISA or a serial driver? Depending on this, they behave differently.

PyVISA drivers use the "Virtual Instrument Software Architecture" package for python, and as such follow a standard format (seen in the Stanford830 driver) and (should) include this line in the Parameters section of their doc string:

instrument : string or pyvisa :class:`pyvisa.Resource`
        visa string or an instantiated instrument

Serial drivers are more nuanced and do not follow as consistent or predictable of a format. They often import their own custom packages common to their name. An example of a serial driver included in pyscan can be found in the helioscamera.py as the HeliosSDK, which imports and uses the from libHeLIC import LibHeLIC package.

These patterns can serve as a quick and easy way to differentiate driver types, but if in doubt feel free to reach out!

Using PyVISA Drivers

To use a PyVISA driver, first import the following packages:

import pyscan as ps
from pyvisa import ResourceManager, VisaIOError

Identifying and Initializing

To identify, connect with, and initialize an instrument, we will use its corresponding driver class; however, we must first identify the instruments address.

This is accomplished by using our built-in get_resources() function. This returns a dictionary of resource addresses paired with their instrument ID strings (if available), which will also print them for you by default. We can call it using the following code:

ps.get_resources()

An example output might look something like this:

ASRL3::INSTR Keithley Instruments Inc.,Model 2260B-80-27,1403238,01.72.20150702

GPIB0::8::INSTR Stanford_Research_Systems,SR830,s/n86813,ver1.07

GPIB0::30::INSTR Agilent Technologies,33522B,MY57802503,4.00-1.19-2.00-58-00


We can then capture and connect to an instrument using its address string as the input parameter for initializing our instance of the driver class. For the Stanford 830 and given the above output we would write:

srs830 = ps.Stanford830('GPIB0::8::INSTR')

To verify that we are connected to the instrument we can query one of its attributes, such as by using srs830.phase and evaluating the response. Additionally, we may now change an instruments property, i.e. srs830.phase = 0 to make sure our instrument is connected and registering changes. Now connected you are ready to start using your instrument!

Note: These tests may fail when attempted with properties that cannot be read or written to, either because they are read or write only properties, or they are interdependent properties currently limited by another setting. If you encounter an issue verifying that you are connected to your instrument, please make sure you are working with an independent, read and write property before assuming your connection failed. Learn more about read and write only settings here.

More about PyVISA drivers

PyVISA drivers automatically inherit from InstrumentDriver, a pyscan class that standardizes how PyVISA drivers are initialized and includes valuable methods for using drivers, extracting data from them, setting up experiments, and ensuring that driver meta data will be saved.

Learn more about instrument driver here: https://pyscan.readthedocs.io/en/latest/api/drivers.html#pyscan.drivers.instrument_driver.InstrumentDriver

Reading, Writing, and Calling Methods

To understand what parameters, properties, and methods the driver currently supports, first reference the doc string by querying the driver with a question mark like so:

srs830?

This can also be done for instrument parameters, attributes, and methods, such as for the attribute phase. To get the doc string for attributes is the same but now referencing the specific property of interest:

srs830.phase?

To understand drivers and their components, doc strings are your best friend.

Parameters:

Parameters are the inputs (required) for initializing the driver. Generally, this will be the pyvisa class, visa string, or an instantiated instrument.

Attributes/Properties:

Attributes are the properties of the instrument that can be queried and/or written.
Read-only properties can be read by simply calling the property srs830.phase or srs830._phase but cannot be written to.
Write-only properties can be written by declaring the property equal to your target change srs830.phase = 30 but cannot be read.
Read and write properties can be read and written to in the same way, but without the exclusive restriction.

Methods:

Methods can be called in the same manner as parameters, but may include unique inputs. Please reference the doc strings to understand what inputs are required to call each method.

Automating Experiments with Drivers

Please see our demo notebooks to see how you can run your experiments automatically and control multiple devices simultaneously. You would simply replace the devices listed with your own. For example, in the first demo notebook you could replace:

devices.v1 = ps.TestVoltage()

with

devices.srs830 = ps.Stanford830(res)

etc. From there you can control the parameters to your liking for your experiment.

Using Serial Drivers

placeholder

How to Make a Driver

Is the driver you are trying to make a pyvisa driver, or a serial driver? Identify this first and proceed with the corresponding section.

Making a Pyvisa Driver

The overarching driver template for a pysvisa driver is as follows:

# -*- coding: utf-8 -*-
from .instrument_driver import InstrumentDriver
import numpy as np

class YourNewDriver(InstrumentDriver):
    '''
    Class to control <Your Instrument Name>

    Parameters
    ----------
    instrument : string or pyvisa :class:`pyvisa.Resource`
        Visa string or an instantiated instrument

    Attributes
    ----------
    (Properties)
    id : read-only
        Gets the id string of the device.
    prop1 : int
        <prop1 description>
    prop2 : str
        <prop2 description>
    prop3 : <return type>
        <prop3 description>

    Methods
    -------
    method1()
        <method1 description>
    method2()
        <method2 description>
    '''

    def __init__(self, instrument):
        super().__init__(instrument)
        self.debug = False
        self._version = "1.0.0"

        self.initialize_properties()
        self.update_properties()

    def initialize_properties(self):
        '''
        Initialize the properties of the instrument.
        '''
        self.add_device_property({
            'name': 'id',
            'query_string': '*IDN?',
            'read_only': True,
            'return_type': str})

        self.add_device_property({
            'name': 'prop1',
            'write_string': 'PROP1 {}',
            'query_string': 'PROP1?',
            'range': [0, 100],
            'return_type': int})

        self.add_device_property({
            'name': 'prop2',
            'write_string': 'PROP2 {}',
            'query_string': 'PROP2?',
            'indexed_values': ['option1', 'option2'],
            'return_type': str})

        self.add_device_property({
            'name': 'prop3',
            'write_string': 'PROP3 {}',
            'query_string': 'PROP3?',
            'return_type': float})

    def method1(self):
        '''
        <method1 description>

        Parameters
        ----------
        None

        Returns
        -------
        None
        '''
        return self.x + self.y

    def method2(self, param):
        '''
        <method2 description>

        Parameters
        ----------
        param : type
            <param description>

        Returns
        -------
        return_type
            <return description>
        '''
        return self.x * self.y

First Things First

Pyvisa drivers are classes that inherit from the InstrumentDriver class found in pyscan/drivers/instrument_driver.py file, so the first thing to do is to import Instrument driver:
from pyscan.drivers.instrument_driver import InstrumentDriver

Then name and create your new class to inherit from InstrumentDriver:
class yourNewDriver(InstrumentDriver):

Doc Strings

Include a doc string for your driver as the first lines in your new class. The doc string should begin with a description of the new driver class. Doc strings account for all Parameters, Attributes, and Methods.
Each of these sections are to be separated by new lines and follow our standard format as well as syntax closely.

A proper doc string format is as follows:

class yourNewDriver(InstrumentDriver):
    '''
    Class to <describe it here>...
    <more description (optional)> ....

    Parameters
    ----------
    instrument :
        Visa string or an instantiated instrument (return value from
        :func:`.new_instrument`)

    Attributes
    ----------
    (Properties)
    prop1 : int
        <prop1 description>
    prop2 : str
        <prop2 description>
    prop3 : <return type>
        <prop3 description>

    Methods
    -------
    method1()
        <method1 description>
    method2()
        <method2 description>
    '''

    def __init__(self, instrument, debug=False):


To see a representative example of a properly formatted doc string complete and in action please reference the Keithley2260B.

Note:

  1. All lines are indented by 4 spaces to match the contents of the class, and after every parameter, attribute, or method the corresponding contents are indented by 4 more spaces.

  2. Primary headers are followed by a line of dashes equal to the number of letters in the header. In the case of attributes, this is followed by a secondary header (Properties).

Please follow this formatting for your driver to be properly parsed on our read the docs page. Improperly formatted doc strings will fail our auto test driver tests.

Testing a Pyvisa Driver

Pyscan has a built-in automatic testing for pyvisa drivers. That new drivers or changes to old drivers should pass when connected to the instrument. Here's how to implement it.

First check if the driver you want to test already has a test notebook in the drivers_test_notebooks directory.

Please note: you assume responsibility for the potential consequences that can come from testing a faulty driver. By proceeding you agree to read and follow these instructions to ensure no harm comes to you, your instruments, and/or anyone else from improper use of our test unit.

The Driver Already Has a Test Notebook

If the driver already has a test notebook, simply run the test notebook to see if it passes. Test logs storing results and driver test history are stored in pyscan/drivers/testing/driver_test_logs. If the driver fails the tests, the driver test logs are a good resource for evaluating the last time it passed the test cases, which can help narrow down what variables have changed since then that could be causing it to fail.

Making a New Test Notebook

If the driver doesn't already have a test notebook and you want to make one for it, it is important to first understand that by default, the driver's test unit will cycle through all the possible settings included in a driver except for settings that are blacklisted.

Warning

If cycling through a setting on your instrument could cause a safety issue, damage the instrument, or cause unintended consequences with an experiment that is set up before/while testing, be sure to blacklist it in the driver itself as a black_list_for_testing attribute in your drivers __init__.

Furthermore, you will want to check your drivers manual to blacklist interdependent settings as well. Interdependent settings will generally fail the tests because the range of what they can be set to is dependent on another setting.

The automatic tests are safeguarded so that they can't be run on a driver without a black_list_for_testing attribute. This is so experimentalists must acknowledge and consider the importance of a blacklist for testing. If there are no settings to blacklist, simply include it as an empty list to enable testing.

Once you understand the risks of testing and have set up your blacklist attribute, you are ready to create your driver test notebook. Follow this template here, which outlines the steps used in our demo_test_notebook.ipynb (found in the drivers_test_notebooks directory).

First use get resources to identify your connected instruments address.

import pyscan as ps
ps.get_resources()

in our example, the resource captured is the Stanford Research Systems 830 with the resource address "GPIB0::8::INSTR". Now we can create a new instance of our driver class (in this case the class ps.Stanford830) connected to our instrument as so:

srs830 = ps.Stanford830("GPIB0::8::INSTR")

Now we want to check and make sure our device is connected by querying an instrument setting.

print(srs830.phase)

Then we can import and implement the test_driver function by passing it the connected instance of our driver class.

from pyscan.drivers.testing.auto_test_driver import test_driver
test_driver(srs830)

Your Driver Is Passing

Assuming you see Tests passed your driver is off to a good start; however, the auto test driver is not a complete test for your driver. Most drivers include methods that are difficult to test for automatically as they have unique effects or desired functionality. To test your driver thoroughly, now is the time to add tests for the methods used by your driver.

Common issues we've found when testing drivers.

If a device is failing test cases for a particular property, check the error message. When in doubt, try the following solutions first:

  1. Make sure there are no typos, abnormalities, or other mismatches in formatting in the add_device_property section for the given property.

  2. If the instrument's documentation notes that a property has dependencies, add this to the required black_list_for_testing attribute (of type list) in the drivers' __init__. For the Stanford830 it looks like this: self.black_list_for_testing = ['_input_configuration', "_time_constant", "_amplitude", "_power_on_status_clear"]

  3. Check that your driver has a properly formatted doc string that includes all your drivers' attributes.

These are the most common problems we've encountered when testing for the Stanford830.

Making a Serial Driver

placeholder