The plugins are written in python 3.4. For the development we recommend to use one of these tools:
The name of a plugin has to be unique, in the following we will use <unique-plugin-name> as a placeholder.
The plugin directory <unique-plugin-name> has to contain a plugin python file <unique-plugin-name>.py, a plugin description file <unique-plugin-name>.yapsy-plugin. Additionally it is possible and recommended to add a documentation as a plugin documentation file <unique-plugin-name>.rst and a small icon as 16x16 png with the name box.png. Files, which are used in the rst-file, should be stored in the sub folder _static, otherwise these files wont be recognized during the documentation build process.
The whole plugin folder must then be stored under one of the provided categories, which can be found in papi/plugin/.
A sample folder structure for a plugin is given here:
papi/
plugin/
display/
/LCDDisplay/
_static/
LCDDisplay.png
LCDDisplay.py
LCDDisplay.rst
LCDDisplay.yapsy-plugin
box.png
It is also possible to create an own category by creating the following folder structure:
papi/
plugin/
<unique-category-name>/
info.ini
The info.ini is optional and contains the category name as it is used in PaPI.
[Config]
Name = My Foo Bar Category
The plugin must be categorized by storing the plugin directory in one of the sub folders, which can be found here: papi/plugin/
It is recommended to use a given template for the plugin python file, which enables an ease entry point for the development of an own plugin. The template files can be found in papi/plugin/templates.
The plugin description file has to provide the following information as an ini file and must be stored as <unique-plugin-name>.yapsy-plugin.
[Core]
Name = <unique-plugin-name>
Module = <unique-plugin-name>
[Documentation]
Author = Foo Bar
Version = 1.0
Description = This FooBar plugin is used by PaPI.
Icon = License: <URL>, Provided by: <URL>
By using rst for the documentation, the plugin documentation file <unique-plugin-name>.rst will be recognized during the documentation build process .
Blocks are used to collect all signals created by the same source. An entire block and or a single signal can be subscribed by other plugins e.g. a plot. We recommend to read Signals/Blocks.
In the following we gonna create a Block with the name Source. In the next step a signal named Step is created and added to the previous created Block. At the end the PaPI-backend will be informed and the Block can be used by other plugins. Previous sent blocks will be deleted.
1 2 3 4 5 6 | def cb_initialize_plugin(self):
self.block = self.pl_create_DBlock('Source')
signal = pl_create_DSignal('Step')
self.block.add_signal(signal)
self.pl_send_new_block_list([block])
|
Here we assume an IOPlugin which cb_execute function is called in a loop. In the first step the plugin must be initialized.
1 2 3 4 5 6 7 8 9 10 | def cb_initialize_plugin(self):
self.block = self.pl_create_DBlock('Source')
self.step_signal = self.pl_create_DSignal('Step')
self.block.add_signal(self.step_signal)
self.pl_send_new_block_list([self.block])
self.delta_t = 0.1 #[s]
self.t = 0
self.signal_value = 0
|
In the next step we implement the ability to provide a step at time 10.
Warning
This is just an easy example. PaPI doesn’t provide soft real-time.
1 2 3 4 5 6 7 8 9 | def cb_execute(self, Data=None, block_name = None, plugin_uname = None):
if self.t > 10:
self.signal_value = 1
self.pl_send_new_data('Source', [self.t], {'Step' : vec[self.signal_value]} )
self.t += self.delta_t
time.sleep(self.delta_t)
|
An alternative call of pl_send_new_data is given here:
self.pl_send_new_data(self.block.get_name(), [self.t], {self.step_signal.get_name() : vec[self.signal_value]} )
Parameters are used to enable an external control of a running plugin. We recommend to read Parameters.
At first three parameters are created and the PaPI-backend gets informed by using the function pl_send_new_parameter_list. To limit possible user entries in the frontend, a regex was defined for the
1 2 3 4 5 6 7 | def cb_initialize_plugin(self):
self.para_foo = self.pl_create_DParameter('foo',default=0)
self.para_bar = self.pl_create_DParameter('bar',default=0)
self.para_baz = self.pl_create_DParameter('baz',default=1, Regex='[0-9]+')
self.pl_send_new_parameter_list(para_list)
|
Events are used to change parameters of other plugins. We recommend to read Events.
A new event is defined by the following code and at the end the PaPI-backend will be informed and the event can be used to change parameters of other plugins. In the code above a button was also created which is used to trigger the change event by a simple user interaction (clicking the button).
1 2 3 4 5 6 7 8 | def cb_initialize_plugin(self):
self.event_start = self.pl_create_DEvent('Start')
self.pl_send_new_event_list([self.event_start])
self.button = QPushButton(self.name)
self.button.clicked.connect(self.clicked_start_button)
self.button.setText('Click')
|
The event can be emitted as following, here as the result of clicking a button in the GUI.
1 2 | def clicked_start_button(self):
self.pl_emit_event('1', self.event_start)
|
It is possible to set a default configuration for every plugin which can be modified by the user during the creation process.
The callback function cb_get_plugin_configuration is used to inform the PaPI backend about the configuration.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | def cb_get_plugin_configuration(self):
config = {
'flag': {
'value': "0",
'regex': '^(1|0)$',
'type': 'bool',
'display_text': 'Flag',
'tooltip' : 'Checkable checkbox'
},
'color': {
'value': "(123,123,123)",
'regex': '^\(\d+\s*,\s*\d+\s*,\s*\d+\)$',
'type': 'color',
'advanced': '1',
'display_text': 'Color'
},
'file': {
'value': "",
'advanced': '1',
'type' : 'file',
'display_text': 'Needed File',
'tooltip' : 'File needed by the plugin'
},
'text': {
'value': 'Wert',
'advanced': '1',
'display_text': 'Erweiterter Wert'
}
}
return config
|
As you can see it is possible to describe a single configuration attribute in a detailed way but only the key value is mandatory.
The other keys are used to provided a comfortable change of the attribute during the creation process. The following table provides an overview over all supported keys for describing an arbitrary attribute.
Key | Description |
---|---|
value | Mandatory key !! |
regex | used to limit the possible user entries in a textfield by the use of a regular expression. |
type | enables additional gui elements in the creation dialog. |
advanced | is used display this attribute on an additional tab in the creation dialog. |
display_text | sets the name of the attribute as it is seen by the user. |
tooltip | used to define a tooltip triggered by a mouse hover. |
PaPI supports currently only the following types.
Type | Description | Value |
---|---|---|
color | Opens a color picker | (r,g,b) |
file | Opens a file dialog | ~/config/File.xml |
bool | Checkbox | (0|1) |
In case of an unknown type or none type was defined a simple textfield is used.
File dialog. |
Color picker. |
The plugin configuration can be changed by the user during the creation process of the plugin and leads to a modified configuration which will be used as startup configuration. This configuration can be accessed in three different ways:
Note
We recommend to use this function.
1 2 3 | def cb_initialize_plugin(self):
self.color = self.pl_get_config_element('color')
self.color_regex = self.pl_get_config_element('color','regex')
|
1 2 3 4 | def cb_initialize_plugin(self):
self.config = self.pl_get_current_config()
self.color = self.config['color']['value']
self.color_regex = self.config['color']['regex']
|
Warning
Use this function only if you know what you do ! Take a look at the documentation for this function.
1 2 3 4 5 6 | def cb_initialize_plugin(self):
self.config = self.pl_get_current_config_ref()
self.color = self.config['color']['value']
self.color_regex = self.config['color']['regex']
self.config['text']['value'] = 'FooBar'
|
If you only like to change one single value in the startup configuration we recommend the function pl_set_config_element:
1 2 | def cb_initailize_plugin():
self.pl_set_config_element('color', '(10,20,30)')
|
The following description is only valid for plugins which are based on visual_template.py or rather are a subclass of vip_base
Creating a widget is very simple, all we need is to import the following modules:
from PyQt5 import QtWidgets
and to create a widget in the cb_initialize_plugin function and to inform the PaPI backend about the widget by using pl_set_widget_for_internal_usage
def cb_initialize_plugin(self):
self.LcdWidget = QtWidgets.QLCDNumber()
self.pl_set_widget_for_internal_usage(self.LcdWidget)
The function cb_execute is called by the PaPI backend with a currently received data set. Data is a dictionary with an entry CORE_TIME_SIGNAL, a constant defined in papi.constants., which contains the time vector. The other entries are data vectors of subscribed signals. To determine the data source the corresponding block_name and plugin_name is given for a single cb_execute step.
1 2 3 4 5 6 7 8 | import papi.constants as pc
def cb_execute(self, Data=None, block_name = None, plugin_uname = None):
time = Data[pc.CORE_TIME_SIGNAL]
for signal_name in Data:
if signal_name != pc.CORE_TIME_SIGNAL:
data = Data[signal_name]
|
The set_parameter is always called when a parameter is changed. To determine the modified parameter the parameter’s name is given as name, of course the new value is also given as value. The value is always from type string that means it may be necessary to cast the string as float, or int.
1 2 3 4 5 6 7 8 9 10 11 12 13 | def cb_set_parameter(self, name, value):
if name == 'ParameterName1':
print(name + " --> " + str(value));
if name == 'ParameterName2':
new_int = int(float(value))
print(name + " --> " + str(new_int))
if name == 'ParameterName3':
if int(float(value)) == int('1'):
print(name + " --> " + " True ")
else:
print(name + " --> " + " False ")
|
The PaPI framework executes this functions
def cb_pause(self):
"""
Function pause
:return:
"""
pass
This enables the developer to handle a users wish to break the plugin. PaPI will also stop to call the cb_execute(Data, block_name, plugin_uname) function.
The PaPI framework executes this functions
def cb_resume(self):
"""
Function resume
:return:
"""
pass
This enables the developer to handle a users wish to resume the plugin. PaPI will start again to call the cb_execute(Data, block_name, plugin_uname) function if necessary.
The PaPI framework executes this functions when this function was executed PaPI will stop and remove the plugin.
This function must be implemented because the plugin developer should be aware of the fact that this function exists. Quiting a plugin without stopping it in a proper could have bad effects on other running plugins.
def cb_quit(self):
"""
Function quit
:return:
"""
pass