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 name entry.

    • <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.