Driver Programming: Difference between revisions
No edit summary |
|||
Line 26: | Line 26: | ||
All device classes that are available via the [[version manager]] are also examples. Download them and see the source code, e.g. using the "Open/Modify" button that each module has. Copy device classes to the public folder "CustomDevices" using the version manager and start using them as a template for your own device classes. | All device classes that are available via the [[version manager]] are also examples. Download them and see the source code, e.g. using the "Open/Modify" button that each module has. Copy device classes to the public folder "CustomDevices" using the version manager and start using them as a template for your own device classes. | ||
=== Instantiation & destruction === | |||
A instance of the Device class is instantiated and destroyed very often: | |||
* for each measurement run | |||
* whenever the Module needs to know the 'variables', 'units' or the shortname | |||
Thus, the __init__ should be a light-weight function as it is called often. It also means that you cannot store parameters in a device class to use them later again. Every parameter of the device class instance exists only as long the device class lives and after a run, for example, the device class is destroyed. This further means that dll files or other files should not be loaded in the __init__ function, but rather during [[connect]] or [[initialize]]. | |||
=== Importing python packages === | === Importing python packages === | ||
All packages which come along with SweepMe! can be imported as usual at the beginning of the file. | All packages which come along with SweepMe! can be imported as usual at the beginning of the file. | ||
If you need to import packages | If you need to import packages that do not come with the SweepMe! installation, you can use the [[LibraryBuilder]] to ship an extra python package with your DeviceClass. | ||
=== __init__ === | |||
This function should be part of any device class, and according to the minimal working example the function "__init__" of the base class "EmptyDevice" must be called first. Then, you can define variables, units, plottype, and savetype as described above. Furthermore, you can set the variable self.shortname to a string that will be shown in the sequencer to help the user to quickly identify which instrument is used. Besides that, you can use the __init__ function to define important variables that are frequently needed in all other functions. | |||
=== Defining return variables === | === Defining return variables === | ||
Line 54: | Line 67: | ||
In order to return your measured data to SweepMe! use the [[call]] function and return as many values as you have defined variables. | In order to return your measured data to SweepMe! use the [[call]] function and return as many values as you have defined variables. | ||
=== GUI interaction === | === GUI interaction === |
Revision as of 18:37, 1 July 2020
Device Classes are small python based code snippets that act as drivers to tell SweepMe! how to interface with a certain instrument.
Basic idea
SweepMe! creates a Device object based on a selected Device Class. For this Device object, several semantic functions are called that are the identical across all modules and instruments. For example, these pre-defined functions are named "connect", "initialize", "configure", "start", "measure", "call", "unconfigure", "deinitialize", "disconnect". There are many more such functions and you only need to add those to your Device Class that you need. When these functions are called during the program run is described here: Sequencer procedure. As a Device Class programmer, you have to make sure that your instrument is doing right things at these functions, e.g. after "initialize" is called, your instrument should be initialized, but it remains your choice what is exactly done. Once, these semantic functions are perfectly adjusted, you can use your Device Class like a driver that can be used in combination with all other SweepMe! modules and drivers.
In SweepMe!'s devices classes you have no direct access to the main program or the modules, but rather SweepMe! calls the device class functions and sets a number of variables to control the program flow.
Minimal working example
A Device Class is a main.py file in which a python class-Object is inherited from a parent class called EmptyDeviceClass.
The following four lines of codes are always needed:
from EmptyDeviceClass import EmptyDevice # Loading the EmptyDevice Class
class Device(EmptyDevice): # Creating a new Device Class by inheriting from EmptyDevice
def __init__(self): # The python class object need to be initialized
EmptyDevice.__init__(self) # Finally, the initialization of EmptyDevice has to be done
Copying these four files into an empty main.py should result in a working Device Class that is basically doing nothing. You can then add further commands as needed.
Examples
All device classes that are available via the version manager are also examples. Download them and see the source code, e.g. using the "Open/Modify" button that each module has. Copy device classes to the public folder "CustomDevices" using the version manager and start using them as a template for your own device classes.
Instantiation & destruction
A instance of the Device class is instantiated and destroyed very often:
- for each measurement run
- whenever the Module needs to know the 'variables', 'units' or the shortname
Thus, the __init__ should be a light-weight function as it is called often. It also means that you cannot store parameters in a device class to use them later again. Every parameter of the device class instance exists only as long the device class lives and after a run, for example, the device class is destroyed. This further means that dll files or other files should not be loaded in the __init__ function, but rather during connect or initialize.
Importing python packages
All packages which come along with SweepMe! can be imported as usual at the beginning of the file. If you need to import packages that do not come with the SweepMe! installation, you can use the LibraryBuilder to ship an extra python package with your DeviceClass.
__init__
This function should be part of any device class, and according to the minimal working example the function "__init__" of the base class "EmptyDevice" must be called first. Then, you can define variables, units, plottype, and savetype as described above. Furthermore, you can set the variable self.shortname to a string that will be shown in the sequencer to help the user to quickly identify which instrument is used. Besides that, you can use the __init__ function to define important variables that are frequently needed in all other functions.
Defining return variables
Each Device Class can return an arbitrary number of variables that are subsequently available for plotting, displaying them in a monitor widget, or saving them to the measurement data file.
In the function "__init__" of your Device class you have to define following objects:
self.variables = ["Variable1", "Variable2", "Variable3"]
self.units = ["s", "m", ""]
In this example, three variables are defined, but you can also add more. Please make sure that you have the same number of units which are defined as seconds, meters, and no unit (empty string).
self.plottype = [True, True, False]
self.savetype = [True, False, False]
Additionally, you can define 'self.plottype' and 'self.savetype' which again need to have the same length as 'self.variables'. If you do not define them, they will be always True for each variable. If the plottype of a variable is True, you can select this variable in the plot. If the savetype of a variable is True, the data of this variable is saved to the measurement file.
In order to return your measured data to SweepMe! use the call function and return as many values as you have defined variables.
GUI interaction
The interaction with grahical user interface (GUI) is realized with the two function get_GUIparameter and set_GUIparameter. The function get_GUIparameter receives a dictionary with all parameters of the GUI. The function set_GUIparameter has to return a dictionary that tells SweepMe! which GUI elements should be enabled (active) and which options or default values should be displayed.
Getting GUI parameter
In order to figure out which parameters are selected by the user, the function 'get_GUIparameter' has to be used. It has one argument 'parameter' that is used to handover a dictionary that consists of keys that are related the GUI elements of the Module and the selected values. Overwrite this function in your device class to make use of it. For example:
def get_GUIparameter(self, parameter):
print(parameter)
Here, the print command can be used to see the dictionary in the debug widget and learn which keys are accessible. These keys of the dictionary can vary between each Module, but there are some which are common for all, like "Label", "Device", "Port".
In order to load a single parameter, for example the Sweep mode or the selected port, use:
def get_GUIparameter(self, parameter):
self.sweepmode = str(parameter["SweepMode"])
self.port_string = str(parameter["Port"])
The variables 'self.sweepmode' and 'self.port_string' contain 'self.' which makes them an attribute of the entire device class so that you can use them in all other functions of your device class that have 'self' as first argument.
It is good practice to immediately change the data type to whatever you need for further processing. Should, the type of the data change, your code will not break.
Typical functions:
- int(): change to an integer number
- float(): change to a float number
- str(): change to a string
Setting GUI parameter
GUI elements of the Module for which you implement your Device Class must be activated. For that reason, put the following function into your DeviceClass
def set_GUIparameter(self):
GUIparameter = {}
return GUIparameter
Now, you can fill the dictionary GUIparameter with keys and values. Each key represents a certain GUI item of the Module.
GUIparameter = {
"SweepMode" : ["Current in A", "Voltage in V"], # define a list
}
Here, "SweepMode" represents the ComboBox of the Module that presents the possible modes to vary set values. To get all possible keys that can be used with set_GUIparameter, you can use the function get_GUIparameter.
Creating individual parameters
The Modules Logger and Switch provide the possibility to generate GUI items dynamically. Just add your own keys to the dictionary and based on the type of the default value, the corresponding GUI element will be created for you.
GUIparameter = {
"MyOwnKeyForInteger" : 1, # define a GUI element to enter an integer
"MyOwnKeyForFloat" : 1.23, # define a GUI element to enter a float
"MyOwnKeyForString" : "SomeText", # define a GUI element to enter a string
"MyOwnKeyForSelection" : ["Choice1", "Choice2"], # define a GUI element to select from a ComboBox
"MyOwnKeyForBool" : True, # define a GUI element to use CheckBox
"MyOwnKeyForSeparator" : None, # define a GUI element that just displays the key with bold font
"" : None, # an empty line
"MyOwnKeyForFolder" : pathlib.Path(<folder>), # define a GUI element that displays a button to select a folder
"MyOwnKeyForFile" : pathlib.Path(<file>), # define a GUI element that displays a button to select a file
" " : None, # another empty line with a different key from the first empty line
}
The keys you use will be returned then by get_GUIparameter with the selected values of the user. Please make sure that you do not use keys that are provided by the Module. See the description of get_GUIparameter to see which keys already exist.
Pre-defined GUI keys
There are some keys that should not be used to define your your own GUI keys when creating device classes for Logger and Switch.
- "Label": The field where the user can change the label of the module
- "Device": The field where the user selects the device class
- "Port": the field where the user selects the port
- "Calibration": the field where the user selects the calibration
- "Channel": the field where the user selects the channel
- "Description": the field where the user can read an info about the device class
- "SweepMode": the field where the user selects the sweep mode
- "SweepValue": the field where the user selects the sweep value
Description
Device Classes that are programmed for the modules Logger or Switch can have a description that is displayed in the description box when the corresponding Device Class is selected.
To enable this feature, add a static variable 'description' to your class, e.g.
class Device(EmptyDevice):
description = "here you describe how your Device Class should be used" # a static variable
def __init__(self):
...
The string will be interpreted like html, so that headings, enumerations, etc. are possible.
Ports
If you use standardized communications interfaches like GPIB, COM, or USBTMC, you make use of SweepMe!'s port manager that manages everything and you can use write and read function to communicate with your instrument. Otherwise, you have to handle the creation and destruction of a port object yourself using the functions connect and disconnect.
Finding & selecting ports
If your Device Class makes use of the port manager, available ports will be automatically added to the field "Port". Otherwise you can add the function "find_Ports" and return a list of strings that identify possible ports. In both cases, you can retrieve the port selected by the user via the function "get_GUIparameter" using the key "Port".
Multichannel support
deprecated from 1.5.5: Use the key "Channel" with the functions "set_GUIparameter" and "get_GUIparameter" to list available ports in an extra ComboBox.
Some measurement equipment has two channels but only one communications port, e.g. some Source-Measuring-Units or Paramter Analyzers have multiple channels to independently source voltages or currents, but everything is controlled via one port.
Of course, one could implement a Device Class for each channel of the device but in case of changes multiple files have to be revised. In order to unify the device handling, you can define multiple channels in your Device Class by adding a static variable:
class Device(EmptyDevice):
multichannel = ["CH1", "CH2", "CH3", "CH4"] # a static variable
def __init__(self):
...
In that case, the user will see four Device Classes in the Device list of the Module with the string defined above at the end.
To figure out which channel is chosen by the user, use:
def get_GUIparameter(self, parameter):
self.device = self.parameter["Device"]
self.channel = int(self.device[-1])
The above example will first read out the name of the chosen Device Class. Assuming that the last character of the string is the number of the selected channel. Of course, you can modify the above example to your needs.
Stop a measurement
The measurement stops whenever a standard function such as 'connect', 'initialize', ... , return with False. So just add "return False" to your code whenever something goes wrong and you like to abort the measurement. The measurement also stops if the variable self.stopMeasurement is no empty string anymore. So you can use
self.stopMeasurement = "text-to-be-displayed-in-a-message-box-to-inform-user"
If SweepMe! detects a non-empty string it will stop the measurement after the current function returns. Use "return False" to let the current function return immediately.
Alternatively, you can use the following function
self.stop_Measurement("text-to-be-displayed-in-a-message-box-to-inform-user")
The error message will be displayed in a message box to the user.
Communication with the user
If you like to display a message in the info box of the "Measurement" tab, you can use:
self.message_Info("text-to-be-displayed-in-the-info-box")
If you like to inform the user with a message box, use
self.message_Box("text-to-be-displayed-in-the-message-box")
Please note that the message box is non-blocking and the measurement will continue, even if the the message is not confirmed by the user.
Log messages to a file
You can easily write messages to a file using
self.write_Log("message-to-be-saved")
The message will be saved in the file temp_logbook.txt within the temp-folder. When saving data the leading "temp" of the file will be automatically renamed by the given file name.
Inter device class communication
Sometimes multiple instances of a device class are created for a measurement and they need to share some information, e.g. a certain object to handle the communication. For this purpose a dictionary called "self.device_communication" is available in all device classes that is exactly the same for all device classes. You can simply add a key-value pair and all other device classes can access the entries as well and vice versa. The dictionary is available starting with a measurement and can be accessed during all functions that are called during the measurement. To avoid interference, we recommend to use very unique key-strings that e.g. include the name of the device class. For example, a Device Class which opens a dll-file to communicate with a spectrometer will have exclusive access to that port. In order to allow multiple Spectrometer module and their device classes to access the same port object it must be made available to all.
def connect(self):
if "my_spectrometer_port" in self.device_communication:
# it means that another instance of this device class already opened the port object
self.port = self.device_communication["my_spectrometer_port"]
else:
# the port object is not available yet, so we have to create it and make it available to other instances of the same device class
self.port = create_some_port_object()
self.device_communication["my_spectrometer_port"] = self.port
The same can be done, when disconnecting and closing the port object:
def disconnect(self):
if "my_spectrometer_port" in self.device_communication:
# if the key is still available, the first instance of the device class will close the port and remove the key
self.port.close() # close your port or disconnect
del self.device_communication["my_spectrometer_port"] # remove the key-value pair from the dictionary
Module specific functions
Several modules provide additional functionality, e.g. the latest version of the monochromator module has a button to tell the instrument to go to a home position. These module specific functions are described on the page of each Module.