Creating a Custom RTC Widget
This example shows how to implement a custom RTC Widget to be used in the Control and Monitor Tool or any other ELT GUI Application. This example uses the MAL interfaces of the RTC Components to execute Commands and receive information from them.
Provided Example
The provided example implements a RTC Component Widget for the RTC Custom Component Tutorial. It is not intended to be used in an actual implementation, but rather as a template for a new widget.
Source Code Location
The example source code can be found in the following sub-directory of the RTC Toolkit project:
_examples/exampleRtcWidget
Modules
The provided example is composed of only one module.
This module has a special WAF script. Developer may reuse the wscript and toml file.
Dependencies
The provided example component depends on the following modules:
taurusmal - provided by CUT (already included if RTCTK was installed by RPM), it allows Taurus to interact with MAL Servers.
_examples.exampleCustom.interface-python - interface used to distinguish a RTC Custom Example component.
Using the Widget
This example is automatically installed.
You may use it as part of the Control and Monitoring Tool:
# Deploy and start the example applications
$> rtctkExampleCustom.sh provision
$> rtctkExampleCustom.sh deploy
# Start the CtrlMonTool
$> rtctkCtrlMonTool --sde file:$INTROOT/run/exampleCustom/service_disc.yaml gui
Then click on the Custom Commands Panel, and select in the Deployment Overview list the rtc_component_1 entry. This will show the custom widget.
As an alternative, you may use Taurus facilities. Such RTC Component is identified by their MAL RR URI.
# Deploy and start the example applications
$> rtctkExampleCustom.sh provision
$> rtctkExampleCustom.sh deploy
# Start a taurus form
$> taurus form zpb.rr://127.0.0.1:12081/
After you are done with this tutorial, run the undeploy and unprovision commands so that the
application processes are terminated gracefully and the run-directory is cleared again.
# Gracefully terminate the applcations and clean-up
rtctkExampleCustom.sh undeploy
rtctkExampleCustom.sh unprovision
Development Guide
The process of creating a custom widget requires the creation of a custom wscript module, two python files, and a pyproject.toml file.
Item Factory
This factory has its interface defined by Taurus in https://taurus-scada.org/devel/plugins.html#form-factories
The factory is a method that accepts as argument a TaurusDevice object, and return the Class Type of the widget. In case it return None or throws an exception, the factory is skipped.
Taurus may have several factories registered. They will be tried sequentially.
The code below shows an implementation based on the list of interfaces implemented by a MAL Server. In this particular instance, the TaurusDevice that represents a MAL server has a method called “get_interface_fqn”, which returns a list of strings, each string representing an interface.
The factory determines from a dictionary if all the listed interfaces are present in the MAL Server, and if positive, returns the class of the widget RtcCustomWidget.
Listing of rtccustomfactory.py:
from typing import Type
from taurus.core.taurusmodel import TaurusModel
from taurus.qt.qtgui.panel.taurusvalue import TaurusValue
from rtctk.examples.rtc.custom.widget.rtccustomwidget import RtcCustomWidget
def rtc_custom_factory(modelObj: TaurusModel) -> Type[TaurusValue]:
"""
Taurus Value Factory to be registered as a TaurusForm item factory plugin.
This method takes as argument a TaurusModel object. It can use it
to query any specific information.
Then, it return a Widget Class that inherits from TaurusValue.
This factory is intended to be loaded automatically by taurus by using the
entry point "taurus.form.item_factories" in the "taurus" package.
The entry should look like:
rtctk_Form_ItemFactory=rtctk.ctrlmontool.gui.widgets.rtctkfactory:RtctkFactory
<name_of_entry>=<python_module>:<factory_method>
See https://taurus-scada.org/devel/plugins.html#form-factories
See https://packaging.python.org/en/latest/specifications/entry-points/
:param model: taurus model object (Device, Attribute, Authority)
:return: Custom TaurusValue class
"""
fqn_to_widget_map = [
(["::rtctkif::UpdateCmds", "::rtctkexif::CustomCmds", "::stdif::StdCmds"], RtcCustomWidget),
]
klass = None
for entry in fqn_to_widget_map:
# We use the python type "set" to be able to compare the contents of the lists
# and determine if they are contained within another.
device_ifs = modelObj.getDeviceProxy().get_interface_fqn() # pylint: disable=invalid-name
if set(entry[0]).issubset(set(device_ifs)):
klass = entry[1]
break
return klass()
Widget Implementation
The main difference with a normal Composite widget is the use of the TaurusValue class as a base instead of TaurusWidget. These widgets are intended to be contained by a TaurusForm Widget.
The Custom Commands panel is in fact a TaurusForm
widget, and every time a different row is selected in the Deployment Overview
panel, the setModel(uri) method is invoked in the TaurusForm.
The RtcCustomWidget must inherit from TaurusValue. TaurusValue holds up to 5 types of widgets the RtcCustomWidget can provide.
TaurusForm is a grid, where each row is represented by a TaurusValue, and its cells Widgets determined by the TaurusValue.
TaurusForm |
||||
Label |
Read |
Write |
Units |
Extra |
Label |
Read |
Write |
Units |
Extra |
Label |
Read |
Write |
Units |
Extra |
TaurusValue is intended to be instantiated directly. Instead TaurusForm will use the classes defined in LabelWidgetClass, ReadWidgetClass, WriteWidgetClass, UnitsWidgetClass, and ExtraWidgetClass properties.
As a general rule, we only recommend to implement LabelWidgetClass and ReadWidgetClass widgets.
Both RtcCustomTVReadWidget and RtcCustomTVLabelWidget inherit from TaurusWidget as a normal widget. The TaurusForm class will automatically execute setModel(uri) on the widget.
The implementation of RtcCustomTVReadWidget uses a UI file to define its Graphical Design. It also uses TaurusCommandButton to perform Commands on the RTC Component. This means that the definition of the command to execute can be done in the UI file.
More details on this approach for UI design in: https://www.eso.org/~eltmgr/ECS/documents-latest/CUT/sphinx_doc/html/docs/310_beginners_guide.html#qt-and-ui-files
Project Definition and Wscript
This factory is intended to be loaded automatically by Taurus by using the entry point “taurus.form.item_factories” in the “taurus” package.
See https://packaging.python.org/en/latest/specifications/entry-points/
Entry points are a convenient manner of specifying extension points for python applications.
As a developer, you may reuse the provided file. Here is an example listing of the pyproject.toml file:
1[project]
2name = "rtctk.examples.rtc.custom.widget"
3version = "5.0.0"
4dependencies = ["taurus",]
5requires-python = ">=3.8"
6authors = [{name = "Author", email = "author@example.org"},]
7maintainers = [{name = "Author", email = "author@example.org"},]
8description = "RTCTK Example: RTC Widget"
9keywords = ["qt", "taurus", "oldb", "astronomy", "ELT", "RTCTK"]
10classifiers = [
11 "Development Status :: 4 - Beta",
12 "Programming Language :: Python"
13]
14
15[project.optional-dependencies]
16gui = ["PySide2", "PyQt5", "PySide6", "PyQt6"]
17cli = ["click",]
18
19[project.urls]
20Homepage = "https://www.eso.org"
21Documentation = "https://www.eso.org"
22Repository = "https://gitlab.eso.org/rtctk/rtctk"
23"Bug Tracker" = "https://jira.eso.org/secure/Dashboard.jspa"
24Changelog = "https://gitlab.eso.org/rtctk/rtctk/-/blob/master/CHANGELOG.md"
25
26[project.entry-points."taurus.form.item_factories"]
27RtcCustomFactory = "rtctk.examples.rtc.custom.widget.rtccustomfactory:rtc_custom_factory"
Change it as needed, but it is important that these three entries are:
name(line 2): which needs to be the name of the python module installed by wscript.version(line 3): It cannot have pre-release or other suffix. Version handling is done by python pip tool, which is more stricter than the WAF version system.[project.entry-points."taurus.form.item_factories"]section contents (line 27): The section name remains the same, but the line below is to be constructed in a particular manner:RtctkFactory = "rtctk.examples.rtc.custom.widget.rtccustomfactory:rtc_custom_factory"The entry should look like:
<name_of_entry>="<python_module>:<factory_method>"<name_of_entry> can be any string. It is recommended for it to be unique for easier debugging.
<python_module> has to match the
nameentry.<factory_method> method that implements the factory.
A custom wscript is needed to perform the installation of the modules’ metadata. For a new module the wscript only needs two changes:
pkg_name = “rtctk.examples.rtc.custom.widget”: which should match the name entry from pyproject.toml
prj_version = “4.0.0”: which should match the version entry from pyproject.toml.
This wscript will take care of generating the metadata, and installing it to its final location.