Python Module cmwrda

Module implements an Interface for RDA device access, this means provides access to FESA-devices.

state: can be considered as operational

Required Set-Up

Usage of the Python RDA device access requires the set-up for accelerator controls usage and selection of the controls domain to be used.

Extension of the library search path LD_LIBRARY_PATH is no longer needed (from October 2019) for usage of the module cmwrda. The path to resolve library dependencies is now included in the C++ implementation of the module.

The module is intended for Python 2.7, but should also work with Python 3 .

CSCO-Setup

To be able to use the control system tools, the control system setup should be executed:

source /common/usr/cscofe/scripts/accdefs

Don't forget the leading 'source' to establish the definitions in the running shell.

Further the location of the module has to be added into the Python-path:

export PYTHONPATH=/common/usr/cscofe/python

An easy way to ensure the setup is to insert the above given commands into the shell configuration file .bashrc, located in the home directory.

RDA Domain-Selection

RDA remote access uses address information which is hold in so called 'directory servers'. At GSI three domains are established, each using a dedicated directory server:
  • Production: Devices and software in GSIs accelerator facility
  • Integration: Devices (generally mock-devices) and software which is used for integration tests
  • Development: Devices and software under development

Before RDA can be used, the respective directory server has to be selected. This is done by setting the environment variable CMW_DIRECTORY_CLIENT_SERVERLIST (export CMW_DIRECTORY_CLIENT_SERVERLIST=cmwdev00a.acc.gsi.de:5021 etc).

Setting of the environment variable CMW_DIRECTORY_CLIENT_SERVERLIST is the only way for defining the RDA domain. This setting MUST be done before importing the module cmwrda .

For setting of the environment variable CMW_DIRECTORY_CLIENT_SERVERLIST before using Python with the module cmwrda two special comand can be used:
rdapython
or
rdaipython

Both commands will select the required domain by defining the mentioned environment variable, and then call the standard interpreter, python or ipython. Selection of the domain is by comand-line options:
  • --dev: RDA access in the development domain
  • --pro or --prod: RDA access in the production domain
  • --int: RDA access in the integration domain
  • no option: Default is the development domain
$ rdapython --dev # will operate in the development domain

$ rdapython --pro # will operate in the production domain

Environment variable RDAPY_SYSTEM_ENVIRONMENT may be used to set the default environment for rdapython/rdaipython:
  • export RDAPY_SYSTEM_ENVIRONMENT=dev will set the development environment as default
  • export RDAPY_SYSTEM_ENVIRONMENT=pro will set the production environment as default
Explicit usage of options --pro / --prod, --dev, or --int will ovveride the default setting.

Usage

>>> import cmwrda

Basic help is provided:
>>> help(cmwrda)

Concepts

Communication is with properties of devices. A device/property combination establishes a communication endpoint. For each combination of device and property a separate endpoint has to be created to provide access to the properties.

For each such comunication endpoint, data can be read (get), write (set), or subscribed..

In the read and write actions, including subscriptions, data is transported in data-containers. The data sets contain data-elements, called value. Each data-container may contain multiple value data-elements.

Each value is labeled by a name, called the tag (on the FESA-Level: value-item), and contains one element of the data types which are supported by RDA. Such elements can be single data (scalars) or multiple data. Presently the Python binding supports
  • Scalar: single values of basic data types (BOOL, BYTE, SHORT, INT, LONG, FLOAT, DOUBLE, STRING)
  • Array: arrays of the basic data types (BOOL_ARRAY, SHORT_ARRAY, ..., DOUBLE_ARRAY, STRING_ARRAY)
  • Array2D: two-dimensional arrays of the basic data types (BOOL_ARRAY_2D, SHORT_ARRAY_2D, ..., STRING_ARRAY_2D)
An identifier for the RDA data type is also contained in the value-elements.

Properties may require filters, which then specify the access in detail. Handling of filters is explained below.

Classes and Types

Python-Classes
  • Rda: Endpoint for communication, providing read/write access and subscriptions
  • Data: Data-container (ValueScalar/ValueArray/ValueArray2D elements), for data exchange with front-ends
  • ValueScalar: Value-element for single data
  • ValueArray: Value-element for arrays
  • ValueArray2D: Value-element for two dimensional arrays
  • Context: Context (cycle name, stamps, ...) of aquisition data
  • AsyncReadCallback: Base class for asynchronous read callbacks
  • AsyncWriteCallback: Base class for asynchronous write callbacks
  • RdaListener: Base class for subscriptions callbacks
  • RequestHandle: Description of an asynchronous read or write call
  • Subscription: Descripion of an established subscription
  • RdaException: Exceptions returned by module cmwrda
  • RdaSubscriptionHandle: Set up subscription to a property - depricated, use class Rda

Constants:
  • RdaType: RDA data types
    • values RdaType.BOOL, RdaType.BYTE, ..., RdaType.BOOL_ARRAY, ..., RdaType.BOOL_ARRAY_2D, ...
    • RdaType ['DOUBLE'] will provide the enumeration value for the type double (6)
    • RdaType [6] will provide the mnemonic string ('DOUBLE')

Handling

Establish Communication

Commuication with FESA devices is with endpoints (access points). A communication endpoint is defined by the combination of a device and a property.

Define communication endpoint for FESA-device (XYRT1LT43) and property (Setting):
>>> conn = cmwrda.Rda('XYRT1LT43', 'Setting')

or, with explicit defining a device server (does not depend on database-entries for name resolution),
>>> conn = cmwrda.Rda('XYRT1LT43', 'Setting', 'ElectroStatQuadDU.vml200d')

For such a communication endpoint data can be read, data can be written to, or data can be subscribed.

Data Container

Data exchange with the front-end layer is by data containers.

Data : Class to transport data to and from front-ends

Data container in general contain several value-items. A value-item, or in short value, is a labled collection of data of identical type.

Values

A value-item, or in short value, is the smallest grouping of data of identical type. The data elements of a value can be single data, or arrays of one or multiple dimension. The cmwrda module suports scalars (single data) and one and two dimensional arrays.

Value-elements are represented by four classes:

ValueScalar : scalar (single) data
ValueArray : array (one dimension)
ValueArray2D : 2-dim array
Value : general class, for data returned from the front-end

The value-items a characterised by
a name, the tag
Rda data type, the data type identifier, e.g. RdaType.BOOL, RdaType.BYTE, ..., RdaType.STRING, RdaType.BOOL_ARRAY, ..., RdaType.BOOL_ARRAY_2D, RdaType.STRING_ARRAY_2D,
the data elements, which can be numerical, boolean or strings, according to the data type identifier

Value-items must be created usind the specific classes ValueScalar, ValueArray and ValueArray2D, which defines the dimension of the data elements. All three items tag name, Rda data type and the data elements, must be provided for creation. Scalar data elements can be given as single element or a list with one element. Array data elements have to be provided as list. Two dimensional array elements have to be provided as list too. To define the two dimensions, the row count and the column count have to provided additionally in the constructor.

Data from the front-end is returned in the general class Value, which can contain a scalar, a one or a two dimensional array.

From the Value-elements the data, as well as the tag-name and the RDA-type can be extracted:
>>> v = value.data() # get the data from the value-element
>>> tag = value.tag() # get the tag-name
>>> rdaType = value.type() # get the RDA data type (BOOL, ..., INT, ..., DOUBLE_ARRAY, ...)

Value-element data always are returend as list, even for scalars and two-dimensional arrays.

To reconstruct two-dimensinal arrays, number of rows and columns is provided as well as a method to extract a row from the two dimensional array:
>>> r = value.rowCnt() # number of rows
>>> c = value.columnCnt() # number of columns
>>> row = value.getRow(row_index) # extract row

Creating Data Containers

First, a new (empty) data container has to be created:
>>> data = cmwrda.Data()

Then value-elements have to be inserted into the container:
Create Value-elements, either as single value (ValueScalar), Array (ValueArray) or as two-dimensional Array (Array2D), by providing name of tag/value-item, data-type, and data:
>>> v1 = cmwrda.ValueScalar('curents', cmwrda.RdaType.DOUBLE, 17.04)
then add Value-element to the data container:
>>> data.add(v1)
repeat for other Value-elements
v2=cmwrda.ValueArray ( 'voltageValuesToDemonstrate', cmwrda.RdaType.DOUBLE_ARRAY, [17.4, 47.11, 0.815] )
v3=cmwrda.ValueScalar ( 'voltageValuesValidation', cmwrda.RdaType.LONG, 17 )
. . .
until Data Container is filled with desired value-elements

Evaluating Data Containers

Data-objects can be printed directly:
>>> print data # will print contents of data

Contents of Data-object can be extracted at once:
>>> allValues = data.getValues()

will give a dictionary, containing all Value-objects, indexed by their tag-name. To obtain the tag names, use member function keys() or iterate over the dictionary:
>>> print allValues.keys()
>>> for tag in allValues: print tag

Single Value-elements can be extracted from the dictionary, indexed by the tag-name (e.g. 'current')
>>> value = allValues['current']

Value elements can be extracted from the Data-object directly, giving the tag-name (e.g. value-item 'current'):
>>> value = data.getValue('current')

Context-Information

Selector for Beam-Process, Sequence, ...

Access to multiplexed data (reading and writing) requires to provide a data selector, which in general means the beam-process number.

The selector has to be provided as parameter in read or write operations. The selector parameter can be omitted for non-multiplexed data.

FESA devices expect the selector as a string in the form 'FAIR.SELECTOR.P=<nn>'. In the cmwrda-module the selector can be given
  • in the full form: 'FAIR.SELECTOR.P=17', 'FAIR.SELECTOR.S=3', ...
  • abbreviated: 'P=17', 'S=3'
  • or, for beam-processes, as integer number only: 17
Context-Information in Read-Data

Data-Containers, read from devices, provide in addition some context-information:
  • acquisition stamp
  • cycle stamp
  • cycle name
Such context-Information is provided as objects of class Context. Context elements can be extracted from a data-container data by:
ctx = data.getContext()

The elements of the context can be extracted by particular methods, e.g. getCycleName(), getAcqStamp() (in nano seconds) or getAcqStampSec() (full seconds) with getAcqStampNanoSec() (nano sec fraction from full second).

Reading Data:

To access a device, define a connection device / property combination, as in above section "Establish Communication".

From such a connection, data can be read. For multipexed properties, a selector must be given, which can be omitted for non-multiplexed properties.
>>> data = conn.read() # non-multiplexed: no data selector needed
>>> data = conn.read('P=17') # multiplexed: selector needed, here: beam process 17 :

return an element of class Data , containing returned property data as objects of subclasses of class Value (objects of classes !ValueScalar , !ValueArray , !ValueArray2D ).

Access to properties with filters are supported, see below.

The data container can be evaluated as described in the section 'Evaluating Data Container' above.

Writing Data

Data for write access has to be provided in a data container, as described above.

It's also possible to use data containers, as returned from a read operqtion, for write operations.

To access a device, define a connection device / property combination, as in above section "Establish Communication".

To such a connection, data can be send. For multipexed properties, a selector must be given, which can be omitted for non-multiplexed properties.
>>> conn.write (data) # no data selector needed
>>> conn.write(data, 'P=17') # selector needed for multiplexed data, here: beam process 17

Access to properties with filters are supported, see below.

Asynchronous Read/Write

The class Rda provides methods for asynchronous read and write operations:

: readAsync : asynchronous read : writeAsync : asynchronous write

Both methods return a description of the call in a RequestHandle object.

The call finishes without waiting for completion. The result of the operation is returned via a callback object which has to be provided in the asynchronous operation. The callback must be an object of a subclass of classes AsyncReadCallback respectively AsyncWriteCallback.

For asynchronous read requests, the result of the operation, a description of the request in a RequestHandle object and especially the returned data in a Data object (see above, is returned in the method recieved. For aynchronous write requests RequestHandle object is returned in the method completion. For both asynchronous read and write in case of an error the method onError is called, providing besides a RequestHandle object, an RdaException object desribing the error.

Users should subclass the callback classes which are provided by cmwrda, providing their own implementation of the receive, completion and onError methods.

Calls to properties with filters are supported, see below.

Subscriptions

Subscriptions are set up by the method subscribe of the class Rda . Objects of the class Rda are created, given nomenclature and property, and optionally a server, as described above. The former class RdaSubscriptionHandle can still be used, but is considered as deprecated.

The method subscribe will switch on the subscription. To receive data from a subscription, a listener (a callback) has to be provided and, for multiplexed properties, a selector must be given:

>>> subscr = conn.subscribe(listener) # no data selector needed
>>> subscr = conn.subscribe(listener, 'P=17') # selector for multiplexed data, here: beam process 17

An empty selector (an empty string, "" , or a None ), which is the default, specifies subscription to all executions of the property (not specific to a beam process or sequence).

Subscriptions to properties with filters are supported, see below.

The method subscribe returns a Subscription object, containing information of the subscription. The Subscription object is used to end a subscription, by its method unsubscribe :

>>> subscr.unsubscribe()

Alternatively, the method unsubscribe of the class Rda may be used to end the last subscription for the access point.

The listener (the callback) must be an object of a class which inherits from cmwrda.RdaListener . It has to implement a method receive and should implement a method !onError ,

>>> class MyListener(cmwrda.RdaListener):
>>> def __init__(self):
>>> cmwrda.RdaListener.__init__(self)
>>> def receive(self, data, update, subscription):
>>> ...
>>> def onError(self, exception, update, subscription):
>>> ...

The receive method will be called on notifications and its onError when rda reports an error. The data that is passed to the receive method is a cmwrda.Data container (see above, just as the data you get from the read call (see above).

The meaning of the parameters is
  • data: Data object - the returned data
  • update: integer - execution of the subscription, 0 = NORMAL, 1 = FIRST_UPDATE, 2 = IMMEDIATE_UPDATE
  • subscription: Subscription object - descriptive data for the subscription
  • exception: RdaException object - the occured error

For an example on how to inherit from RdaListener see https://git.acc.gsi.de/fesa-drivers/feSupport/src/branch/master/tools/rdapy/test/rdapy_test.py . A simple python script for a subscription is (non multiplexed property, default callback):
class MyListener(cmwrda.RdaListener):
    def receive(self, data, update, subscription):
        print 'subscription data received'
        print 'update:', update
        print 'subscribed:', subscription.getNomen(), subscription.getProperty(), subscription.getSelector()
        print 'data:'
        print data  
    def onError(self, exception, update, subscription):
        print 'subscription error received'
        print 'update', update
        print 'exeption:, exception
        print 'subscribed:', subscription.getNomen(), subscription.getProperty(), subscription.getSelector()

nomen = "GTK1MU1"
prop = "Status"
ap = cmwrda.Rda(nomen, prop)
listener = MyListener()
subs = ap.subscribe(listener) # selector may be provided as additional parameter
for i in range(10):
   time.sleep(2)
   print("waiting for updates")
subs.unsubscribe()

Note that the RdaListener is only a base class which should be subclassed by the user. It prints a message on the screen when a notification arrives, and displays the data in generic form. To do anything useful you need to inherit and override its receive.

Filters

FESA properties may require filters. Filter information is transported in container of type Data , filled the same way as a Data container in a write access (see provide data container). The filter container is passed as additional parameter in the access method. In case of filters, a selector must be provided (which, of cause, then can be empty).

Filters can be provided in read , write , readAsync , writeAsync or subscribe methods:

>>> filter = cmwrda.Data()
. . . # fill filter object

>>> data = conn.read('P=17', filter)
>>> conn.write(data, 'P=17', filter)
>>> subs.subscribe(listener, "", filter)

Exception handling

Exceptions coming from the cmwrda lib are encapsulated for better usability. They are hierarchically structured as follows

cmwrda.RdaException                  : type for all exceptions thrown explicitly in cmwrda
   cmwrda.RdaInterfaceException      : all exceptions coming from the CERN interface
      cmwrda.RdaUnspecificException  : pyrda3._rda3_bindings.RdaException 
      cmwrda.RdaNameServiceException : pyrda3._rda3_bindings.NameServiceException
      cmwrda.RdaDataException        : pyrda3._rda3_bindings.DataException
      cmwrda.RdaUnknownException     : remaining exception types

Upgrade and Compatibility

The cmwrda modul was upgraded in April and May 2021. While the main modification is inside, by exchanging the former SWIG interface to a pybind11 based interface, provided by CERN, some modifications and extensions are provided to the users:
  • handling of subscriptions now is implemented in class Rda
    • compatibility: the formerly required class RdaSubscriptionHandle is deprecated, but will continue working
  • parameters lists of methods receive and onError are extended by two additional parameters update and subscription
    • compatibility: the new additional parameters of receive and onError can be omitted, methods without the last or both of these parameters will work
  • the former getErrorState method is no longer provided (it was a specific from the SWIG-interface), errors now are signaled by throwing RdaExceptions
  • asynchronous read and write calls are supported
  • constants for the Rda data types are provided (like RdaType.STRING, RdaType.DOUBLE_ARRAY, RdaType.LONG_ARRAY_2D)
  • while primarily developed for Python 2.7, the module is compatible to Python 3 too

Online Help

Online help is provided, it may give additional information on the interface. Try Python-help:
>>> help(cmwrda)
>>> help(cmwrda.Rda)
>>> help(cmwrda.Rda.read)
>>> help(cmwrda.RdaType)
. . .

This topic: FESA > WebHome > Fesa3_300 > FESA3ClientSoftware300 > PythonCmwRda
Topic revision: 01 Dec 2022, SolveighMatthies
This site is powered by FoswikiCopyright © by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding Foswiki? Send feedback