CCS UI Toolkit Introduction

1. Conventions

Note

Disclaimer: Several of the examples exposed in this document are modifications to examples found in the Taurus Developers Guide.

Note

Through this document, the author will refer to widgets by name using the following notation:

TextOnTheWidget kindOfWidget

For example:

Accept button or Open File dialog

2. Installation

The CCS UI Toolkit is intended to be installed in DevEnv machines. The list below is its dependencies. These are all present in DevEnv expect for pint since version 2.2.0:

  • gcc 7

  • qt >= 5.14.0

  • mal >= 1.0.5

  • cii.oldb >= 1.0

  • Python >= 3.5

  • numpy >= .1.1

  • pint >= 0.8

  • future

  • click

  • ply >= 2.3

  • lxml >= 2.1

  • pyside 5.14.1

  • pyqtgraph

The CCS UI Toolkit is installed in a DevEnv machine executing as root the following command:

pip install pint
pip install --index-url https://www.eso.org/~ahoffsta/download/pip/ taurus tauruscii taurus-pyqtgraph

3. Use

We do not intend to cover OLDB setup or usage here, so instead we offer two manner to access to a working CII.OLDB and CSS UI Toolkit environment:

You can either use eltdev21.hq.eso.org:

ssh eeltdev@eltdev21.hq.eso.org

or download the VirtualBox exported VM from winshared shared unit, in ESO intranet. Open a File Explorer and type in the location bar:

\\winshared\shared\tmp\ahoffsta

From there, download CCS_UI_Framework_Prototype_DevEnv_2_1_20.ova

In both cases, the user account to use is eeltdev, with the normal password.

We need to have the following services running:

systemctl start elasticsearch
systemctl start srv-config
systemctl start anaconda-redis5-redis

Note

Elastic Search service takes about 1 minute to start. Keep this in mind if you try to access the OLDB or Configuration CII Services.

4. First steps

Warning

Every time you log into the eeltdev account, you have to load the INTROOT environment. Please execute

source $HOME/CII_COE.sourceme

The OLDB has already been declared, but the process that updates it’s values is not running, please execute:

jrun cii.example.manydatapoints.oldbproducer.Producer

Taurus works through python code, or through a commnand line tool named taurus. To quickly check if Taurus is working, execute:

taurus form 'eval:rand()'
taurus form 'cii.oldb:/root/example/double/sine'

5. MVC

Taurus is an MVC pattern based UI Framework. The MVC pattern aims for reusability of components, and the modularization of development.

  • Reusability: it is achieved by the separation of the Model. The Model should be able to plug into most controller and views, by sharing a common interface.

  • Modularization: Since all views are able to present the model, development can be modularized. A new view can be developed, without loosing functionality of the other view. The Model can be expanded, but since the interface is preset, the view will just show a new widget.

The Model is a section of data from the domain of the application. Responsibilities of this class are:

  • Contain the data

  • Knows how to read from its source

  • Knows how to write it back to its source

  • Translates any metadata into usable Roles

As an example, a model can be, for a motor: the encoder value, brake state, power state, incoming voltage, speed, acceleration. Another example, from a database, the results the query “SELECT * FROM table_students;”. Each column in the result will be a Role, and each row will represent a new item, each with its Roles according to columns.

The View presents to the user the data from the model. Among its responsibilities:

  • Takes a subset of entries in the model, and presents it.

  • Determines the layout of the presentation (list, table, tree, heterogeneous, etc)

  • Each piece of data can use a different Widget, these are called Delegates.

Examples of views: List, ComboBox, Table, Tree, Columns

The Controller takes the inputs from the user, and makes the necessary changes to the model. Responsibilities these classes have:

  • Keeps references to Model and View.

  • Process input from the user, converting it to domain compatible notations.

  • Can also alter the user input.

  • Can manipulate the model, so it changes what is presented.

6. Taurus Model

The Taurus Model is not a complex one, and also, it is not based on QAbstractItemModel Qt class.

It is located in the taurus.core module, as a set of 5 classes:

  • TaurusModel, the base class for all model elements.

  • TaurusDevice, which represents branches in a tree like structure.

  • TaurusAttribute, which represents leaves in this tree like structure.

  • TaurusAuthority, it represents a database of information about the control system.

  • TaurusFactory, is in charge of preparing an instance of one of the 4 classes above. It will also use NameValidator to figure is a particular URI is well constructed or not, and which kind of class should it return.

_images/taurus_model_01.jpg

Class Diagram of the taurus.core module. TaurusAttribute, TaurusDevice and TaurusAuthority all inherit from the same parent, the TaurusModel class.

TaurusModel base class and TaurusDevice class implements a Composition pattern: Any device can have multiple children, but attributes cannot.

Important

The use of the Composition pattern allows two things: Have a tree like structure of Authority, Devices and Attributes, while at the same moment, being able to access them all in the same way.

The TaurusFactory in the model provides an Abstract Factory pattern, that will provide most of the logic to create the needed entities, but specifics are left to a particular scheme plugin.

from taurus import Attribute
sine = Attribute('cii.oldb:/root/example/double/sine')
sine.rvalue
  1. Using the URI, the TaurusFactory will find out the scheme for the attribute.

  2. Using the matching plugin, it will request instances of the CiiFactory and CiiValidator.

  3. Will check the validity of the uri using the CiiValidator

  4. And the create the attribute using the CiiFactory.

Every instance of a particular URI, is a singleton. So is we were to again request for taurus.Attribute('cii.oldb:/root/example/double/sine'), we would get a reference to the previous object.

Tip

The developer can access taurus.Attribute manually. The models for widgets are expressed as strings of URIs, and is the responsibility of the widget to get an instance to the proper taurus.Attribute class, through the use of taurus.Authority.

This can be helpful while development more complex commands or methods.

7. Taurus Capabilities

Taurus is a UI Framework, intended for Control Systems. It provides access to its functionality through python code, but also through a command line tool: This command line tool is meant as a utility, not for final products.

7.1. Command Line Tool

Every functionality in taurus can be accessed through the taurus command line interface.

To see the complete syntax of taurus command line:

taurus --help

To quickly create a Taurus Form that immediately shows the contents of two datapoints:

taurus form 'cii.oldb:/root/example/double/sine' 'cii.oldb:/root/example/double/cosine'

To execute taurus, with trace level logs:

taurus --log-level Trace form 'cii.oldb:/root/example/double/sine' 'cii.oldb:/root/example/double/cosine'

Start the Demo of all Taurus widgets:

taurus demo

To start the Taurus GUI Wizard:

taurus newgui

Shows every icons available, and its name for QIcon::fromTheme() method:

taurus icons

7.2. Model Plugins

A series of data models allows Taurus access to different backends. In particular, through this document, we will be presenting example that make use of two of them:

  • evaluation is provided by Taurus. This model executes Python code, and translate the return value as best as possible to TaurusAttribute. It is a read-only model.

  • cii.oldb part of the CCS UI Framework, this module allows read/write access to CII OLDB service, as TaurusAttribute.

The different model plugins can be enable or disabled using TaurusCustomSettings.

7.3. URI

Every part of a Taurus model (Attributes, Device, Authority), are identified by a URI. The URI has several parts:

cii.oldb:/root/example/double/sine
  • cii.oldb scheme

  • :/ authority

  • /root/example/double device

  • sine attribute

The scheme normally indicates the transport and message protocol, but in taurus is used also to identify which taurus model plugin should handle this model.

The authority here is mostly empty. CII OLDB only allows one OLDB in the environment. But here, a different protocol could reference a particular server.

The device represents a physical device that is able to measure a physical phenomena, or is able to actuate on a device. In terms of software, is a container for attributes and methods.

The attribute is the representation of a measurement.

7.4. Widgets

All Taurus widget inherit from the same base class, the TaurusBaseWidget. This class inherits at some point from BaseConfigurableClass and Logger.

  • BaseConfigurableClass is in charge of storing and persisting configuration of widgets. This is mostly used by the GUI builder, which uses this functionality to persist position and size of widgets, the models it should present and which roles, amont other things.

  • Logger uses python logging framework to process every log generated by the Taurus framework.

As an example, here is the inheritance tree of the TaurusLabel widget:

_images/taurus_label_inheritance_tree.png

Taurus Label inheritance tree, from Taurus documentation.

7.4.1. Display Widgets

One main class of widgets that Taurus offers, is the display widgets. All of them are located in the taurus.qt.qtgui.display python module. All of them are read-only, as they are intended for the presentation of information.

_images/display_widgets_01.png

Display widgets, from top to bottom: TaurusLabel, TaurusLCD and TaurusLed

In the same module, there are basic Qt widgets, that are then used by the Taurus widgets.

Taurus widgets do not implement logic, nor formatting. Taurus widgets only add three properties, and one method, which are used then by its Controller.

  • model a URI stored in a string. The widgets are not in charge of getting its model, only to contain the string.

  • fgRole indicates which piece of data from the model, will be used as text.

  • bgRole indicates which piece of data from the model will be used as background color.

  • handleEvent() used to inform widgets of Taurus events. They are mostly related to model changes. These events are forwarded to the controller.

An interesting concept of Taurus is the fragments. Fragments are properties in the model item that we can query. Fragments are accessed by a URI, prepending #fragname_name. For example, we can a short name (label) for the datapoint using:

#!/opt/anaconda3/bin/python
import sys
from taurus.external.qt import Qt
from taurus.qt.qtgui.application import TaurusApplication
from taurus.qt.qtgui.display import TaurusLabel

if __name__ == "__main__":
    app = TaurusApplication(sys.argv, cmd_line_parser=None,)
    panel = Qt.QWidget()
    layout = Qt.QHBoxLayout()
    panel.setLayout(layout)

    w1, w2 = TaurusLabel(), TaurusLabel()
    layout.addWidget(w1)
    layout.addWidget(w2)

    w1.model, w1.bgRole = 'cii.oldb:/root/example/double/tan#label', ''
    w2.model = 'cii.oldb:/root/example/double/tan'
    panel.show()
    sys.exit(app.exec_())

The application looks like:

_images/roles_01.png

Taurus Labels using bgRole to change the information presented

Normal fragments included in a Taurus model are:

  • label

  • rvalue.quality

  • rvalue.magnitude

  • rvalue.units

  • rvalue.timestamp

  • range

  • alarm

  • warning

If a model needs, it can add fragments. Fragments are python properties.

7.4.2. Input Widgets

These are widgets that allow the user to modify a presented value. There are located in the taurus.qt.qtgui.input module. All of them inherit from TaurusBaseWritableWidget Pressing enter on them will trigger a write operation to the model backend.

All of them present the following graphical design pattern: when the value in the widget differs from the latest read value, the widgets is highlighted in blue. This indicates that there is still a change to be commited back into the control system.

As an examples, the following code can show how the widgets would look like:

#!/opt/anaconda3/bin/python
import sys
from taurus.external.qt import Qt
from taurus.qt.qtgui.application import TaurusApplication
from taurus.qt.qtgui.input import TaurusValueLineEdit, TaurusValueSpinBox, TaurusWheelEdit

if __name__ == "__main__":
    app = TaurusApplication(sys.argv, cmd_line_parser=None,)
    panel = Qt.QWidget()
    layout = Qt.QVBoxLayout()
    panel.setLayout(layout)

    w1, w2, w3 = TaurusValueLineEdit(), TaurusValueSpinBox(), TaurusWheelEdit()
    layout.addWidget(w1)
    layout.addWidget(w2)
    layout.addWidget(w3)

    w1.model = 'cii.oldb:/root/example/string/timestamp'
    w2.model = 'cii.oldb:/root/example/double/smallincrement'
    w3.model = 'cii.oldb:/root/example/double/sine'
    panel.show()
    sys.exit(app.exec_())
_images/input_widgets_01.png

Inputs widgets, from top to bottom, TaurusValueLineEdit, TaurusValueSpinBox, TaurusWheelEdit.

7.5. Taurus Form

Taurus offers a way to abstract yourself from all the programming of these widgets, by using the Taurus form. This utility is a CLI command (taurus form), and also a class (TaurusForm). allows to quickly bring up a UI with the specified _widgets_ in it.

(base) [eeltdev@eltdev showcase]$ taurus form --help
Usage: taurus form [OPTIONS] [MODELS]...

  Shows a Taurus form populated with the given model names

Options:
  --window-name TEXT  Name of the window
  --config FILENAME   configuration file for initialization
  --help              Show this message and exit.

It will use the TaurusForm class, which takes a string list as model, and creates a QGridLayout of 5 columns by n rows, n being the number of items in the string list.

Label

Read Widget

Write Widget

Units

Extra

For example:

taurus form 'eval:Q("12.5m")' 'cii.oldb:/root/example/string/timestamp'
_images/taurus_form_01.png

Taurus Form allows to quickly access values, using a list of strings.

In this case, the first row has the eval:Q("12.5m") Attribute, which has a Label, Read Widget and Units entries. The second row has the cii.oldb:/root/example/string/timestamp which has a Label, Read Widget and Write Widget. Note that none of them has the Extra widget, as is feature intended for manual configuration.

_images/taurus_form_02.png

Taurus Form highlighting in blue items that have been updated.

The Reset button will restore the value in the Write widgets to the last know value read from the backend. The Apply button will send the values in the Write widget to the backend for store. When the value from the Write widget is different from the one in the backend, then the widget turns blue, and the label is highlighted in blue as well.

Another interesting feature of the TaurusForm, is that is allows to change the Read and Write widgets on runtime. Right clicking on the label allows to access this, and other kind of functions.

If you have two Taurus Form windows opened, or two of them in the same application, you can drag and drop model items from one to another.

Here is how to use TaurusForm in code:

#!/opt/anaconda3/bin/python
import sys
from taurus.external.qt import Qt
from taurus.qt.qtgui.application import TaurusApplication
from taurus.qt.qtgui.panel import TaurusForm

if __name__ == "__main__":
    app = TaurusApplication(sys.argv, cmd_line_parser=None,)
    panel = TaurusForm()
    model = [ 'eval:Q("12.5m")',
              'cii.oldb:/root/example/string/timestamp' ]
    panel.setModel(model)
    panel.show()
    sys.exit(app.exec_())

Tip

Nowhere in the command line or the code, the developer indicated which kind of widget we wanted to use for each item in the model. This is automatically determined by the Taurus Form, according to the datatype, and this can be altered by the use of CustomMappings.

Tip

At this point, the developer may notice a pattern. The URI is used to autodetermine the kind of Model element we need. The datatype and roles are used to automatically determine the widgets we need.

In run time, you can right click a label, and change its Read or Write widget. In code, you can do it like this:

from taurus.external.qt import Qt
from taurus.qt.qtgui.application import TaurusApplication
from taurus.qt.qtgui.panel import TaurusForm
from taurus.qt.qtgui.display import TaurusLabel
from taurus.qt.qtgui.input import TaurusWheelEdit

if __name__ == "__main__":
    app = TaurusApplication(sys.argv, cmd_line_parser=None,)
    panel = TaurusForm()
    props = [ 'sine','cosine','sinequality','cosinequality','bigincrement','smalldecrement']
    model = [ 'cii.oldb:/root/example/double/%s' % p for p in props ]
    panel.setModel(model)
    panel[0].readWidgetClass = TaurusLabel         # you can provide an arbitrary class...
    panel[2].writeWidgetClass = 'TaurusWheelEdit'  # ...or, if it is a Taurus class you can just give its name
    panel.show()
    sys.exit(app.exec_())

The output of this program looks like this:

_images/taurus_form_03.png

Taurus form with manually set widgets.

7.5.1. Plotting Widgets

Taurus provides two set of plotting widgets, based on different libraries. One of them is based on PyQwt, and the other in PyQtGraph.

In terms of licensing, PyQwt is out of specs, as it only has a commercial licence to further develop widgets based on them. Based on just its name, PyQtGraph, the developer may think it is solely based on PyQt bindinds, but it turns out pyqtgraph support PyQt4, PyQt5, PySide and PySide2 bindinds and it is MIT licensed.

This library is somewhat new to the Taurus framework, so not full plotting capabitilies are present yet. This is certainly an area were we could improve the framework.

The widgets taurus_pyqtgraph offers:

  • TaurusTrend Plots the evolution over time of a scalar attribute.

  • TaurusPlot Plots a curve based on an Array attribute.

In the following example, the sine scalar attribute is use as model for a TaurusTrend widget:

#!/opt/anaconda3/bin/python
import sys
from taurus.external.qt import Qt
from taurus.qt.qtgui.application import TaurusApplication
from taurus.qt.qtgui.plot import TaurusTrend, TaurusPlot

if __name__ == "__main__":
    app = TaurusApplication(sys.argv, cmd_line_parser=None,)

    panel = TaurusTrend()
    model = ['cii.oldb:/root/example/double/sine']
    panel.setModel(model)
    panel.show()
    sys.exit(app.exec_())
_images/taurus_trend_01.png

TaurusTrend widget from the program above.

This example is a bit more complex. Here we used a layout manager, to put side by side two widgets, one for plotting, the other one for trending. TaurusPlot is used to plot an Array attribute, gotten from the evaluation scheme, while the TaurusTrend widgets trends a sine and cosine scalar attributes:

#!/opt/anaconda3/bin/python
import sys
from taurus.external.qt import Qt
from taurus.qt.qtgui.application import TaurusApplication
from taurus.qt.qtgui.plot import TaurusTrend, TaurusPlot

if __name__ == "__main__":
    app = TaurusApplication(sys.argv, cmd_line_parser=None,)
    panel = Qt.QWidget()
    layout = Qt.QVBoxLayout()
    panel.setLayout(layout)
    plot = TaurusPlot()
    plot_model = ['eval:rand(256)']
    plot.setModel(plot_model)
    trend = TaurusTrend()
    trend_model = ['cii.oldb:/root/example/double/sine','cii.oldb:/root/example/double/cosine']
    trend.setModel(trend_model)
    layout.addWidget(plot)
    layout.addWidget(trend)
    panel.show()
    sys.exit(app.exec_())
_images/taurus_plot_trend_01.png

TaurusPlot and TaurusTrend widgets from the program above.

7.6. Taurus GUI

Taurus offers a GUI generator. This is a wizards based GUI creator, which is invoked by taurus newgui command line.

To start a saved GUI, used the command taurus gui:

Usage: taurus gui [OPTIONS] CONFNAME

  Launch a TaurusGUI using the given CONF

Options:
  --safe-mode    launch in safe mode (it prevents potentially problematic
                 configs from being loaded)
  --ini INIFILE  settings file (.ini) to be loaded (defaults to
                 <user_config_dir>/<appname>.ini)
  --help         Show this message and exit.

You can also install this as a system application using:

pip install /home/eeltdev/showcase/newgui_03

You can then execute it using:

newgui_03

8. Contents of the example OLDB

You can use any of the datapoints in this list as part of taurus models:

  • /root/example/double/bigincrement

  • /root/example/double/smallincrement

  • /root/example/double/bigdecrement

  • /root/example/double/smalldecrement

  • /root/example/double/cosine

  • /root/example/double/tan

  • /root/example/double/sinelimits The OLDB has set an upper limit of 0.7 and a lower limit of -0.7

  • /root/example/double/cosinelimits The OLDB has set an upper limit of 0.7 and a lower limit of -0.7

  • /root/example/double/sinequality The quality is bad when greater than 0.9, suspect when greater than 0.8, and ok in the rest of the range. In the negatives it repeats.

  • /root/example/double/cosinequality The quality is bad when greater than 0.9, suspect when greater than 0.8, and ok in the rest of the range. In the negatives it repeats.

  • /root/example/string/timestamp This reports the current timestamp.

  • /root/example/boolean/switch This boolean changes every 3 seconds.

Remember to use well-formed URIs:

'cii.oldb:/root/example/double/bigincrement'

9. Advanced

9.1. Taurus Data Model revisited

As we said before, Taurus data model is not based on the QAbstractItemModel Qt class. But it does offer classes that are this kind of models. There are located in the taurus.qt.qtcore.model module. There are compatible with the QAbstractItemModel, and thus can be used as model for any QAbstractItemView of Qt.

All these classes inherit from the TaurusBaseModel, which in turns, inherits from QAbstractItemModel.

9.2. Device to Widget Mapping

The Form Custom Widget Map entry in the TaurusCustomSettings can be used to indicate custom mapping between devices, and wigets. This can be very useful when development new widgets.

T_FORM_CUSTOM_WIDGET_MAP = \
    {'SimuMotor': ('sardana.taurus.qt.qtgui.extra_pool.PoolMotorTV', (), {}),
     'Motor': ('sardana.taurus.qt.qtgui.extra_pool.PoolMotorTV', (), {})
    }

10. Extending Taurus

10.1. How-to developed a Taurus Model Plugin

Note

This guide is based on Carlos Pascual instructions, and what the author has learned while writing the plugin.

Your first task will be to determine how you will map the elements in your underlying control system (or backend), to taurus model elements. You have to determine which concepts will represent an attribute, a device, and the authority.

Then, the developer needs to determine a URI notation for this. URI has to be able to identify in a unique manner an element in the control system.

Once you have this two tasks completed, create test cases based in URIs for valid and invalid cases. Be generous, and try to make it fail. This will save you later a lot of time.

The NameValidator is the first set of classes used in the Taurus Model. Once it has been determined that the plugin is capable of processing a particular scheme, then the NameValidator is used to determine the correctness of the URI.

Implement the NameValidator module first. It has several classes in it, each for model element. Each one of them are partial regular expressions. One regular expression for each element of the model. For example,

  • TaurusCiiAuthorityNameValidator will check for the scheme + authority.

  • TaurusCiiDeviceNameValidator will check for scheme + authority + device

  • TaurusCiiAttributeNameValidator will check for scheme + authority + device + attribute

As an example, here are the regular expression used for the TaurusCiiAttributeNameValidator. You can see how the authority is defered to the TaurusCiiAuthorityNameValidator. The path regular expression makes use of variable storage. This means that devname is going to contain the result of the regular expression between the next rounded parenthesis ?P<devname>():

scheme = 'cii.oldb'
authority = TaurusCiiAuthorityNameValidator.authority
path = r'/root/((?P<devname>([A-Za-z0-9]?[A-Za-z0-9/]+[A-Za-z0-9]?)?)/(?P<attrname>([A-Za-z0-9]+)))'

The h5file and tauruscii make use of test-driven-development to make sure that every piece of the URI is correctly parsed. It is highly recommended to use it as well, as concatenating regex can be tricky. See test_h5filevalidator or test_taurusciivalidator for details.

After the regular expression are ready, the developer needs to implement the getNames() methods for each NameValidator. Testing this should be easy, but do not skip this over. Regular expression matching is difficult, so some cases could be miss. Again, use test driven development, to see if the output of the getNames() methods matches what was intended from the URI.

Once the model name validators are working, implementing the model classes themselves should not be too difficult if you already have some reasonable python API for accessing your control system. It is basically a matter of implementing the encode() / decode() methods to map the types expected/provided by your control system into those used in Taurus. Here looking at the epics scheme, tango scheme, or cii.oldb scheme will probably be helpful.

The read() / write() / poll() methods are usually trivial.

Tip

Start small. Implement read-only. When ready, go for write support.

Implement the decode() and read() methods.

While implementing the decode() and read() method, the developer should take care with thread synchronization. There should be only one attribute which is a critical section of code. This is the redifinition of the rvalue attribute.

After the testing your classes you can use tauruscustomsettings to include the scheme plugin into Taurus. Just add it to this line:

EXTRA_SCHEME_MODULE = ['tauruscii']

Tip

In general, looking at the h5file scheme is really helpful as a learning tool, as it is a smaller (LOC) model, it is readonly and poll based. We recommend to start looking here.

Once the basic functionality is there, start looking into the epics scheme, and tango scheme. You can see there how the subscription (events) is done, and handled.

10.2. Composite Widgets

Since the read write access to the control system has already been covered by the control system and special representation of values is done by the Display widgets or Input widgets, the creation of composite widgets can be simple.

Start the Qt designer. Please open it using this command line, as this will allow the Qt Designer to load the python widgets from Taurus:

taurus designer

Create a new UI form for a Widget.

In the widget drop first a TaurusGroupBox. Reset the layout of the whole widget, to HBoxLayout.

Add now a TaurusLabel to the TaurusGroupBox, and a TaurusLineEdit. Reset the layout of the TaurusGroupBox to HBoxLayout

So, we have something like this:

The TaurusGroupBox does not offer much functionality, except for the model property. All Taurus Widget, aside from the model property, they also have the boolean userParentModel property. This property allows to set the model of a child widget to TaurusGroupBox in a partial manner.

Let’s say the model of TaurusGroupBox is 'cii.oldb:/root/example/double'. Then the developer can set in TaurusLabel, the useParentModel to true, and the model property to '/sine'.

An example of how this can be done can be found in Sardana’s TaurusMotorH.

10.3. New Taurus Widgets

These widgets are a bit more complex to develop, as they have to translate the values from the underlying control system, into string, lists, or colors for the Roles. All this is done in the Controller.

Each Taurus widgets comes with a Controller. They all inherit from the same base class TaurusBaseController. The base class provides event handling, and get/setter methods for updates into widget presentation (fgRole and bgRole handling).

Making a parallel with Qt; whenever Qt detect an event that requires a widget to be redrawn, for example the sizising or the application window, or interacting with the widget itself, Qt will call the widgets update method. Likely so, when Taurus has an event that requires a widget to change its presentation, it will call the update method on the controller. The Controllers update method in turn, will call methods to update: connections, foreground, background and the tooltip:

def update(self):
    widget = self.widget()
    self._updateConnections(widget)
    self._updateForeground(widget)
    self._updateBackground(widget)
    self._updateToolTip(widget)

It is important to note that the _updateForeground and _updateBackground method are for the developer of a widget to implement:

def _updateForeground(self, widget):
    pass

def _updateBackground(self, widget):
    pass

As an example, the TaurusLabelController is in charge of preparing values of string datatype from the model: the reason is because QLabel only presents text. We can alter also the color and the size of the text, but at the end, it just presents a string.

TaurusLabelController does this in the _updateForeground() method. This method uses the suffix (units), prefix and numeric value and concatenate them together into a string. This is then assigned to the TaurusLabel using setText() method.

11. Debugging

11.1. Taurus Log Level

Any taurus command can change its logging output level using the option:

--log-level [Critical|Error|Warning|Info|Debug|Trace]
                                Show only logs with priority LEVEL or above
                                [default: Info]

A good first step to debug a TaurusWidget or Scheme Plugin is to enable trace level logs and see what is going on.

11.2. OLDB GUI

Sometimes is can be good to compare the values presented in Taurus with the one in the database. You can do this using the oldb-gui. Its will also present you the metadata of the datapoint, which is used by cii.oldb plugin to fill more details into the TaurusAttribute object.

_images/oldb-gui_01.png

OLDB GUI. In the left there is a browsable tree view of the database contents, and the user can select a datapoint. On the right hand side are the details of the selected datapoint.

The OLDB GUI also allows you to subscribe to datapoints changes.

11.3. Python faulthandler

In case needed, you can get a more complete error or exception output using this:

python -q -X faulthandle [script_name]

You can use it in the interactive script console, or while executing a python file.

11.4. Python debuger

11.5. Profiler

We recommend to use Plop. Plop comes in two modules, one needed for collection of data, and the other one use to present the data. Here is how to use it:

pip install plop
python -m plop.collector <script.py>
python -m plop.viewer --datadir=profiles

The output of Plop is a very nicely constructure graph view of the calls. The size of the bubble indicate the ammount of time spend of the method, and the arrows indicate the backtrace to it.

_images/profiler_01.png

Plop viewer in action. It presents the information in a dynamic and interactive graph plot.