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