Skip to main content

A generic template for creating python object-based drivers for SCPI hardware devices which communicate via VISA. Compatible with ARTIQ installed with [artiq] modifier

Project description

PyPI - Python Version

A generic driver generator for devices controlled via (virtual) COM ports using SCPI commands. Creates a python class for controlling your device.

This class is compatible with the ARTIQ experimental control system and, if desired, a network ARTIQ controller is also generated.

Installation

Install the package and its dependancies with:

pip install generic-scpi-driver

If you’d like to use the ARTIQ network controller generation, instead install with:

pip install generic-scpi-driver[artiq]

Usage

Basics

To make a driver, simply inherit from the GenericDriver class. To define commands, call _register_query on the class (not on objects of the class). For example:

from generic_scpi_driver import GenericDriver

class SimpleDriver(GenericDriver):
    '''A driver for my simple SCPI device'''

SimpleDriver._register_query("get_identity", "*IDN")

This creates a class called SimpleDriver which has a constuctor __init__, a close() method and a new method called get_identity() which takes no parameters and returns a string. You could open a connection to your device like this:

dev = SimpleDriver(
    id="COM10",
    baud_rate=57600
)

# This sends the command "*IDN\n" to the device and returns the response
identity = dev.get_identity()

Parameters

For more complex commands, you can specify parameters for the command:

SimpleDriver._register_query(
    "set_voltage",
    "VOLT",
    args=[
        GenericDriver.Arg(name="channel"),
        GenericDriver.Arg(name="voltage"),
    ]
)

This would allow you to call:

# Using positional arguments
dev.set_voltage(0, 5.4)

# ...or keyword arguments
dev.set_voltage(channel=0, voltage=5.4)

Parameters can be validated by passing a custom function (which may accept any single parameter and must return a string to be sent to the device, or throw an error):

def check_voltage_in_limits(v):
    voltage = float(v)
    if voltage > 10:
        raise ValueError("Voltage too high")
    return str(voltage)

SimpleDriver._register_query(
    "set_voltage",
    "VOLT",
    args=[
        GenericDriver.Arg(name="channel", validator=lambda: str(int(x))),
        GenericDriver.Arg(name="voltage", validator=check_voltage_in_limits, default=0.0,
    ]
)

Return values

Return values are, by default, the string returned by the SCPI device in response to your command. If you’d prefer to process these, you can pass a response_parser function:

SimpleDriver._register_query(
    "count_foobars",
    "COUN",
    response_parser=int,
)

SimpleDriver._register_query(
    "list_foobars",
    "LIST",
    response_parser=lambda x: x.split(","),
)

If your device doesn’t give any response at all, you can set response_parser=None and the driver won’t attempt to listen for a respose.

Error checking

You can also add error checking to your commands. Pass a function as response_validator and it will be called with the output from the device (not the parsed output of response_parser) as its input. The response_validator’s return value will be ignored: it’s only job is to raise an exception if needed. E.g.

def check_for_error(s):
    if "error" in s.lower():
        raise RuntimeError("Error returned by device: {}".format(s))

SimpleDriver._register_query(
    "do_something",
    "DOOO",
    response_validator=check_for_error,
)

Asyncronous operation

By default, all methods are syncronous. If you’d prefer async operation, pass coroutine=True to _register_query. This creates a new thread for the serial call and returns an asyncio coroutine. Note that you have to call these using an async loop which is a whole topic of python programming. This is particularly useful for ARTIQ drivers, since ARTIQ handles coroutines automatically.

Custom methods

The method generation is intended to be quite flexible, but if you really need custom logic there’s nothing to stop you writing your own methods. You can use self.instr to access the pyvisa.Resource for your device. Use the wrappers with_handler to cause the driver to issue a VISA .flush() if an error occurs and with_lock to ensure that only one method access the device at a time (only relevant in multi-threaded applications).

from generic_scpi_driver import GenericDriver, with_lock, with_handler

class SimpleDriver(GenericDriver):
    '''A driver for my simple SCPI device'''

    @with_handler
    @with_lock
    def do_complex_thing(self):
        '''Do something complex'''
        response = self.instr.query("COMP 1 2 3")
        return int(response) + 5

Startup checking

It can be useful to check on startup if communicatio with a device has been established successfully. To do this, define a method in the class called check_connection. Return value is ignored, but this method will be called when the object is constucted and has the chance to raise an exception. Example:

from generic_scpi_driver import GenericDriver, with_lock, with_handler

class SimpleDriver(GenericDriver):
    '''A driver for my simple SCPI device'''

    def check_connection(self):
        idn = self.get_identity()
        if idn != "My device":
            raise RuntimeError(f"Bad device identity: got '{idn}'")

# Note that it's fine to define functions later which get used in methods
# defined previously
SimpleDriver._register_query("get_identity", "*IDN")

Simulation mode

The constuctor accepts a keyword parameter simulation=True to return a simulation device, for running offline unit tests. This won’t work unless you also register a simulator device with a method query which takes a string and returns a string. For example:

class Simulator:
    def query(s):
        if s == "*IDN":
            return "Simulator device"
        else:
            return "ERROR"

class SimpleDriver(GenericDriver):
    pass

SimpleDriver._register_simulator(Simulator)
SimpleDriver._register_query("get_identity", "*IDN")

dev = SimpleDriver(id="fake", simulation=True)

dev.get_identity()  # returns "Simulator device"

ARTIQ Controllers

To get a network controller for use by the ARTIQ controller manager, just make a python module like:

from generic_scpi_driver import get_controller_func

from .my_driver import SimpleDriver

# Makes a controller called "SimpleDriver" which listens to port 3300 by default
main = get_controller_func("SimpleDriver", 3300, SimpleDriver)


if __name__ == "__main__":
    main()

Register this main function in your setup.py like so:

setup(
    ...
    entry_points={
        "console_scripts": [
            "artiq_simple_device=my_driver_package.my_driver_controller:main",
        ]
    },
)

After installing your package using pip install -e . as normal, you should be able to call artiq_simple_device on the command line to launch a controller for your device.

Development

For developing the package, you’ll need a few more packages. Install with:

pip install -e .[dev,artiq]

Authors

generic_scpi_driver was written by Charles Baynham.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

generic_scpi_driver-1.6.tar.gz (13.5 kB view details)

Uploaded Source

Built Distribution

generic_scpi_driver-1.6-py3-none-any.whl (13.5 kB view details)

Uploaded Python 3

File details

Details for the file generic_scpi_driver-1.6.tar.gz.

File metadata

  • Download URL: generic_scpi_driver-1.6.tar.gz
  • Upload date:
  • Size: 13.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.8.20

File hashes

Hashes for generic_scpi_driver-1.6.tar.gz
Algorithm Hash digest
SHA256 dd96c14ecc3f2eac70866b08d0705f3ab91bfe0ea41785f33b765e2210517997
MD5 7061759f1065dc2aea3c12e213350355
BLAKE2b-256 d7be00daf1caba69cdd37a7bba18e7cad07cbea7642dbefb7531a6ace758f85f

See more details on using hashes here.

File details

Details for the file generic_scpi_driver-1.6-py3-none-any.whl.

File metadata

File hashes

Hashes for generic_scpi_driver-1.6-py3-none-any.whl
Algorithm Hash digest
SHA256 d3b9f843c27c765f141ec8c4bfe884d1dfe6c481708ab82321c1aa1d745e2e3f
MD5 a4d3d35ef7b8250779b0ad21b887a463
BLAKE2b-256 f57c5179c1e354efc61cc853d64b0c47e69903f374ed7ef8fe417d3505d21199

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page