6.4.4. Plugin cookbook

Most of the work of a plugin is the communication with the instrument, and it depends on how the instrument is reached: a text protocol, a serial line, VISA, a DLL, a .NET assembly… The package pymodaq_plugins_mockexamples holds a cookbook: short, tested plugins, one for each of these ways, to copy from when writing the plugin of your instrument.

Each recipe runs without hardware: it talks to a simulated device (a mock) written for it, so you can try it in the Dashboard, read it, and run its tests. Where a recipe has a port or an address, leave it to MOCK to use the simulated device; give the real one to talk to your instrument.

How to use a recipe:

  1. Choose the recipe in the list below and read the header of its files: it tells what to copy, what to adapt (the commands and the parameters of your instrument) and what to keep (the parts that PyMoDAQ needs and that are easy to get wrong: threads, master and slave, units…).

  2. Put the communication with the instrument in a class of the hardware folder of your package, as the recipes do, and keep the plugin files thin (see Instrument Plugins).

  3. Test your plugin as the recipes do, against a simulated device and the tools of pymodaq.utils.plugin_testing (see Testing the behaviour of your plugin without hardware). A test on a simulated device checks your side of the contract with PyMoDAQ, not the device: say what was tried on the real instrument.

The files of the recipes are in src/pymodaq_plugins_mockexamples/ of the repository. The README of the package has a table of them (cookbook of device patterns).

6.4.4.1. The recipes

An instrument with a text protocol (commands such as *IDN? ): pymeasure (recommended)

daq_move_CookbookPymeasure, daq_0Dviewer_CookbookPymeasure and hardware/pymeasure_source.py. The instrument is described in a few declarative lines with pymeasure (the commands to read and set a value, the validators, the checking of errors). Its tests send the exact commands of the manual to a protocol adapter, and also run on a simulated device.

A serial line (RS-232, USB-serial)

daq_move_CookbookSerial, daq_0Dviewer_CookbookSerial and hardware/serial_stage.py. A driver written directly on pyserial: the terminations of the commands, the timeouts treated as errors, one lock because the driver is shared by the actuator and the detector, the error queue of the device and a homing that blocks until it is over and then calls move_done.

VISA / SCPI instruments

daq_move_CookbookVisa, daq_0Dviewer_CookbookVisa and hardware/visa_meter.py. The same on a pyvisa resource: explicit terminations and timeout, query versus write.

A DLL from the manufacturer (C library)

daq_move_CookbookDll (three axes), daq_0Dviewer_CookbookDll and hardware/dll_controller.py. A ctypes driver: the argtypes and restype of each function, the opaque handle, byref, a structure filled by the library, error codes turned into exceptions and a callback that the library calls from one of its threads (it is kept alive, and it only emits a Qt signal). The library is replaced by an object used like the loaded CDLL, so the code of the driver is the real one. hardware/native_library.py loads a library (names per platform, path from a setting or an environment variable, an error that lists what was tried) and hardware/system_library_demo.py shows the techniques on the C library of the system.

A .NET assembly from the manufacturer

daq_move_CookbookDotNet (three axes), daq_0Dviewer_CookbookDotNet, hardware/dotnet_controller.py and hardware/dotnet_runtime.py. A pythonnet driver: the out parameters that come back in a tuple, the events given a Python function, the exception type of the assembly, IDisposable. The assembly is replaced by an object used like its namespace. hardware/system_dotnet_demo.py shows the techniques on the .NET libraries of the runtime. pythonnet needs a .NET runtime only when it talks to an assembly.

A Python package from the manufacturer

There is no recipe: put it behind a class of the hardware folder, created by ini_stage or ini_detector, as the drivers above, so that a simulated version of this class can replace it in the tests.

A camera

daq_2Dviewer_CookbookCamera and hardware/mock_camera.py: the recipe to copy for a real camera. The plugin is a CameraBase of pymodaq_plugins_utils (acquisition in a thread of its own, live and snap, averaging, the ROI drawn in the viewer, binning, FPS) and the driver follows the CameraDriverBase interface of the same package, with the method names of pylablib. daq_2Dviewer_BSCamera and hardware/beam_steering.py are the camera of the beam steering demonstrations, a plain plugin on purpose: what a camera driver gives its plugin, a region of interest and a binning applied by the camera (the image is smaller), an exposure time, and axes calibrated in µm that follow them. daq_2Dviewer_RoiStuff is the other way to select a region: it is drawn in the viewer, received by the plugin in roi_select and applied to the data after the grab.

A spectrometer

daq_1Dviewer_CookbookSpectrometer, daq_move_CookbookSpectrometer and hardware/spectrometer.py. A detector whose axis (the wavelength in nm) changes because another module, here the actuator that turns the grating, moves it: the axis is asked to the instrument at every grab and sent with the data. The actuator is a slave of the detector and shares its instrument. The detector has the methods (get_spectro_wl, set_laser_wl…) that the spectrometer extension calls.

6.4.4.2. What the recipes share

  • The simulated device is a plain Python class in the hardware folder, with the same public methods as the driver.

  • An actuator and a detector of the same instrument get the same Controller ID in the Dashboard: the first initialized is the master and creates the driver, the other is the slave and receives it.

  • A callback or an event raised by a thread of the driver never calls move_done or touches a widget: it emits a Qt signal, received in the thread of the plugin.

  • Every actuator value and every axis has a unit.