Add a New Instrument

If there is an instrument you would like to use with Hardware Control but no driver is available yet, you are encouraged to write your own. If you are feeling generous, you can contribute it to Hardware Control for future distributions (see contribution guidelines for help)! Since most of the work is done in the base class, adding a new instrument can be relatively straight forward. This step-by-step guide is designed to help walk you through the process of creating a new driver.

1. Start With a Template

Start by duplicating an existing driver for the same type of instrument (e.g. if you are adding a driver for a new oscilloscope, the oscilloscope driver ‘Keysight_4000X’ for the Keysight 4000X oscilloscope would be a good place to start). This can help you get off on the right foot with regard to common tasks such as writing import statements, configuring logger, and adding parameters and commands.

2. Implement the __init__() Function

The instrument base class (which all instrument drivers typically inherit from) takes care of most things in the init function, but there are still a few details that need to be written for your specific instrument driver. A standard driver takes at least two arguments – an instrument_name and a connection_address – which need to be set or passed to the instrument base class using super().__init__(instrument_name, connection_addr). It is also nice to set self.manufacturer and self.model, but this is not a requirement.

After settings these basic variables, the main task is to define all the possible commands and parameters that are available in the instrument. This is done by using calls to self.add_command and self.add_parameter. self.add_command requires that you define an HC-specific name as a string (for example, TRIGGER) and a command that will be passed to the instrument. self.add_parameter also requires a name (for example, CH1_VOLTAGE) and has the option of initializing two types of commands: a read command and a write command for reading from and writing to an instrument, respectively. A parameter can be initialized with a single read command, a single write command, or both.

When defining these parameters and commands, you can also add custom hooks that act before calling a command, before setting a value, and/or after reading a value.

We also implemented a ‘dummy’ mode for instruments, in which they will return pre-defined data (that can be random), which can be good for debugging while not connected to any instruments. The return values for ‘dummy’ mode can be set in the driver to either a constant value or a python function (to, for example, return a random value). The value can also later be overwritten by the user.

The reason we remap the instrument’s command syntax to new command names in the add_parameter and add_command functions is to ensure that the widgets can base their UIs on generic command/parameter names that can work for all instruments of a similar type. The driver’s purpose is to translate the generic commands to the syntax required for your specific instrument. Because of this, if you plan on using your driver with an existing Hardware-Control GUI, you will need to make sure the name field in the calls to add_parameter and add_command in your driver agrees with the corresponding names in the widget interface that you intend to use.

Note that to get a usable instrument driver, you only need to implement the commands and parameters you want to use. There is no need to implement every command available to the instrument or the widget (although this would be nice).

3. Interfacing with the Instrument through Hardware-Control

To trigger a driver command, use instance.command(<HC name of the command>). You can read parameters with instance[<HC name of parameter>] and set parameters with instance[<HC name of parameter>]=value.