The devscr library for Python

Quick Introduction

The devscr library provides support for named device access, i.e. named access to a device's properties and to a property's fields.

Example: The MX equipment model provides the read property CONSTANT which has the fields minCurrent, maxCurrent, minHysteresis, subtyle, polyBlI, polyIBl, polyUI and polyIU. Using devscr those can be accessed like

>>> import devscr
>>> d = devscr.create("UT1MK1") # UT1MK1 is a MX device
>>> c = d.rconstant() # store read result in variable
>>> print c.data.minCurrent # access a field in the data part of the result
0.0

Device objects

Creation and property inspection

After importing the library one has to create a device object. It provides methods for each property of the specified device. Let us see, which properties a MX device has.

>>> import devscr
>>> d = devscr.create("UT1MK1") # UT1MK1 is a MX device
>>> print d.props # a list of property method names
['cinit', 'creset', 'ractiv', 'rcalc', 'rconndesc', 'rconnect', 'rconstant', 'rcurrenti', 'rcurrents', 'rdevdesc', 'rdevdesc2', 'rdstatus', 'reqmerror', 'rfieldi', 'rfields', 'rinfostat', 'rinverter', 'rmagninfo', 'rmagnsvci', 'rmagnsvcs', 'rmeddatai', 'rmeddatas', 'rmstdatas', 'rnomen', 'rpower', 'rpropdesc', 'rreqdesc', 'rshutup', 'rstatus', 'rversion', 'rvolti', 'rvolts', 'wactiv', 'wconnect', 'wcopyset', 'wcurrents', 'wfields', 'winverter', 'wmagnsvcs', 'wmeddatas', 'wpower', 'wshutup', 'wvolts']

All property method names
  • are lowercase and
  • they are prepended with one of "r", "w" and "c" to indicate whether they are read, write or call properties respectively.
There are more ways to inspect the properties of a device.

>>> print d.props # all supported properties (as above)
>>> print d.dprops # all default properties
>>> print d.cprops # only non-default call properties
>>> print d.rprops # only non-default read properties
>>> print d.wprops # only non-default write properties

Reading properties

To read a property one has to make a call to one of the property method names starting with an "r" (on the device object). Parameterless read accesses are done as above by a simple call like

>>> c = d.rconstant() # d represents a device with a read property CONSTANT

Passing parameters

Parameters are arguments to such a call. They can be passed in several ways.

>>> import devscr
>>> d = devscr.create("ut1mk1")
>>> c = d.rconstant() # parameterless
>>> mdi = d.rmeddatai(0, -1, 6) # parameters as arguments
>>> mdi = d.rmeddatai((0, -1, 6)) # all parameters in one tuple

There is a third variant to provide parameters which is too early to mention here. Read on until this section.

The toplevel structure of property objects

Now that we have a property object mdi, that contains the data that has been read, we surely would like to inspect it. Easy!

>>> print mdi
# stamp
time : <0:0:0:0>
date : <1.1.1970>
evtcode : 0
evtvrtacc : 0
evtmpbits : <0000>
# para
method : 0
select : -1
element : 6
# data
volti : 0
currenti : 0.0
fieldi : 0.0

As you can see the property object consists of three parts
  • the timestamp of the read access (in this case the device seems to be somehow reset or something),
  • the parameters we have passes to the read access (this section is empty for parameterless reads) and
  • the data that was read.
Those three parts are really members of the property object and they can be accessed individually like

>>> print mdi.stamp
time : <0:0:0:0>
date : <1.1.1970>
evtcode : 0
evtvrtacc : 0
evtmpbits : <0000>
>>> print mdi.para
method : 0
select : -1
element : 6
>>> print mdi.data
volti : 0
currenti : 0.0
fieldi : 0.0

Diving deeper into the property object

All three parts, that is the three members stamp, para and data, of the property object are made up exactly the same. They represent some hierarichal structure and the names that you saw before the colons in the last example are really the names of their members (on their individual top levels). To make this point clear let us access some individual data elements.

>>> print mdi.stamp.time
<0:0:0:0>
>>> print mdi.para.select
-1
>>> print mdi.data.currenti
0.0
>>> print mdi.stamp.date.year
1970

The last command demonstrates that mdi.stamp.date (like time and evtmpbits) are not just simple variables but objects with nested members themselves. So what we are really dealing with is a tree (see next section too), that's root is the mdi object.

Note: The decoration "<>" is not standard behaviour and must be explicitly implemented for selected nodes that do not pay off to expand in a quick overview print.

One can inspect the children of a node in this hierarchy using the items member like this:
>>> print mdi.para.items
['method', 'select', 'element']
>>> print mdi.data.items
['volti', 'currenti', 'fieldi']
>>> print mdi.stamp.items
['time', 'date', 'evtcode', 'evtvrtacc', 'evtmpbits']
>>> print mdi.stamp.date.items
['day', 'month', 'year']

Note that that a property object like mdi does not provide the items member. That is because it is intended to be a collection of/namespace for its members stamp, data and para.

Note also that leaf nodes in this tree are really variables which do not (!) provide the member items. So there is no obvious test for "is an item a node in the property tree?".

The hierarchy of the data

So the structure of mdi is really like

            _______mdi______
           /       |        \
      ___para*   data*       stamp*____________________
     /   |  |   / |  \       |     \      \  \         \
 method  |  |   | | fieldi   time*  |      \  evtvtacc evtmpbits*
    select  |   | currenti  /| | \  date*  |          / | | \
      element   volti      / | | |  | | \  evtcode    | | |  bit3
                       hours | | | day| |             | | bit2
                          mins | | month|             | bit1
                            secs |    year            bit0
                            musecs

where each *ed element provides the member items that is a list of names of its children/members (exactly those strings that you see in the picture).

Now that the basic structure of a property object is clear, let us access the data that is returned by the CONSTANT property which is a bit more complex.
>>> print c.data.items
['minCurrent', 'maxCurrent', 'minHysteresis', 'subtype', 'polyBlI', 'polyIBl', 'polyUI', 'polyIU']
>>> print c.data.maxCurrent
460.0
>>> print c.data.polyBlI # this is an array of 3 polynonials
_0 : start : 0.0
   : end   : 100.0
   : a0    : -0.000424639001722
   : a1    : -0.00150850997306
   : a2    : -1.4004500315e-07
   : a3    : 3.93821003586e-10
_1 : start : 100.0
   : end   : 250.0
   : a0    : 0.000199411995709
   : a1    : -0.00152507005259
   : a2    : 3.98358013243e-09
   : a3    : -1.42950998261e-11
_2 : start : 250.0
   : end   : 500.0
   : a0    : 0.00164985004812
   : a1    : -0.00153368001338
   : a2    : 3.19129989101e-09
   : a3    : 3.37035989395e-11
>>> print len(c.data.polyBlI) # python's array-length function
3
>>> print c.data.polyBli[1]
start : 100.0
end   : 250.0
a0    : 0.000199411995709
a1    : -0.00152507005259
a2    : 3.98358013243e-09
a3    : -1.42950998261e-11
>>> print c.data.polyBli[1].start
100.0

As you can see the data part of the CONSTANT property is quite structured with arrays of complex types. A path in this tree can be traversed using the data element names from the XML equipment model files, the usual array indexing notation and the dot path separator.

As before nodes that are not leafs in this tree provide the member items that you can inspect to see what the tree looks like.
>>> print c.data.polyBlI[1].items
['start', 'end', 'a0', 'a1', 'a2', 'a3']

Writing properties

This is done as you would expect, analogously to passing parameters for reading:

>>> k=ds.create("berttg1") # a KGB test device
>>> print k.rcurrenti().data # what is set up now?
currenti : 99.99634552
>>> stat = k.wcurrents(10) # parameters as arguments (only one here)
>>> print k.rcurrenti().data
currenti : 9.99542236328
>>> stat = k.wcurrents((50)) # all parameters in a tuple
>>> print k.rcurrenti().data
currenti : 50.005191803

Instead of returning data write actions return a status object that contains two numbers that tell what happened during the action. Those status objects can be printed themselves and this way they tell what happened in a textual form. (In the folllowing example stat is an object of type DevScrStat.)

>>> print stat.primStat()
204312481
>>> print stat.secStat()
204312481
>>> print stat
PrimStat: KGB-S-OK, Auftrag erfolgreich ausgeführt
SecStat : KGB-S-OK, Auftrag erfolgreich ausgeführt

Writing parameterized properties

This is where the two variants of passing data and parameters become in(!)-interchangeable. When passing data and parameters to a write property you want to distinguish between them. This is done by passing two tuples, the one containing the data first:

>>> f = ds.create("some_fictional_nomenclature")
>>> d = (10, 2.6, -1) # the data to be passed
>>> p = (0, 6) # the parameters to be passed
>>> stat = d.wfictionalprop(d, p)
>>> print stat
PrimStat: FICT-S-OK, Auftrag erfolgreich ausgeführt
SecStat : FICT-S-OK, Auftrag erfolgreich ausgeführt

Accessing call properties

Works like reading properties but returns status object as for write properties.

Other ways to provide write data and parameters

There are situations where handling write data and parameters in tuples is unhandy. Consider the need to read a (heavily) parameterized property over a range of one of those parameters' domain, say parameter p with domain {0,1,...,9}. The code would look something like
>>> para = computeParaToIterate() # some given helper function
>>> print para
[0, -5.4, 8, None, -1] # None-initialized 'para-to-iterate'
>>> for i in range(10):
...     para[3] = i
...     print d.rtheprop(tuple(para)).data.fieldOfInterest # d is a device object
...
9
8.7
[...]

The code is of course much more telling if one uses named variables. Therefore devscr provides means to
  1. zero-initialize property objects,
  2. the para and data parts of which can be set and
  3. which can then be passed to property calls, too
In terms of devscr this is called "building skeletons". Have a look:
>>> def computeParaToIterate(s): # s is a skelleton
...   # somehow set the constant parameters here like
...   # s.para.fields = 10.0
...   return s
...
[...]
>>> skel = d.skel("rtheprop") # the device object d provides the read property THEPROP
>>> print skel.para
fields   : 0
method   : 0
someprop : 0
0        : 0
lastdata : 0
>>> s = computeParaToIterate(skel)
>>> for i in range(10):
...     s.para.p = i
...     print d.rtheprop(s).data.fieldOfInterest
9
8.7
[...]

A device's skel method takes the name of a property method and creates an object that has exactly the same structure as if the property was really read and all fields are set to zero. We set the constant para fields by name and iterated over p 's range, each time setting p by name.

When passing a property object to a property access method its data and para parts are extracted as needed for the access. That works for write and call accesses, too. As an example parameterized write accesses extract the data and para parts of the property object.

Note: It is also possible to pass only the data or para part of a readily initialized skelleton to the property methods as in
>>> import devscr
>>> ut1mk1 = devscr.create("ut1mk1")
>>> mdi = ut1mk1.skel("rmeddatai")
>>> mdi.para.method, mdi.para.select, mdi.para.element = 0, -1, 6
>>> print ut1mk1.rmeddatai(mdi.para)
# stamp
strftime  : 01.Jan.1970  00:00:00 + 0ms UTC
strfevent : PZ = TIF (0), VrtAcc = 0, Event = PZ_CHANEND (0)
time      : <0:0:0:0>
date      : <1.1.1970>
evtcode   : 0
evtvrtacc : 0
evtmpbits : <0000>
# para
method  : 0
select  : -1
element : 6
# data
volti    : 0
currenti : 0.0
fieldi   : 0.0
Of course this works too with write data and parameterized write properties.

Note: As you might have observed, it is not quite consistent to obtain zero-initialized skeletons from a device object. The above approach makes special use of the more general implementation of that functionality in devscr. In fact skel is a classmethod of a device object's class, which, in turn, you can obtain using the eqMod method in the devscr module (instead of the create method) and calling skel on it. Now one has to provide an equipment model name of course.
>>> import devscr
>>> mx = devscr.eqMod("mx")
>>> mdiSkel = mx.skel("rmeddatai")
>>> print mdi.para
method  : 0
select  : 0
element : 0
>>> # now you can set those parameters ...
...
>>> mdiSkel.para.method=0
>>> mdiSkel.para.select=-1
>>> mdiSkel.para.element=6
>>> # ... and use it in a call like before
...
>>> d=devscr.create("ut1mk1")
>>> print d.rmeddatai(mdiSkel).data # extracts para part here
volti    : 0
currenti : 0.0
fieldi   : 0.0

Error Handling

devscr may raise a DevScrError during one of the following steps:
  • Creating a device using devscr.create, if the device does not exist.
  • A read/write/call access failed due to some error (device disconnected after creation but before call, timeout, server down, etc.).
  • Trying to access an inexistent array, that is if you try indexing a data item that is not an array either for reading or writing or if you try to determine its length using len.
  • Trying to build a skelleton using skel for a name that is not a property name. This applies to these skel implementations:
    • EqMod.skel (and thus XXXEqMod.skel for each XXX)
    • and each device object's skel method
  • Trying to retrieve meta information for a name that is not a property name using EqMod.meta (and thus XXXEqMod.meta for each XXX).
Note: Because no one can stop you from using the features of the Python language in a wrong way (e.g. accidentally deleting a property method) other exceptions may be raised anywhere, anyhow and anytime.

Retriving Meta Data

Types

You can retrieve meta data from devscr in the following fashion. For each data item you can determine the type using pythons built-in type function.

>>> import devscr
>>> d = devscr.create("ut1mk1")
>>> c = d.rconstant()
>>> print type(d.data.polybli[1].start)
<type 'float'>

Description strings

If you want to get the desciption strings provided to devscr from the equipment model XMLs, you first have to install this information on a per equipment model basis like

>>> devscr.installMeta("mx")

Now you can access the meta information of data items when giving their name to the meta() method of their parent node in the tree. Omitting the field name results in the meta information for that parent node (for example a complex type).

>>> # assuming d as a mx device as above
...
>>> print d.data.polybli[0].meta("start")
Start of valid polynomial range.
>>> print d.data.polybli[0]
Structure of one polynomial of the device constants.
>>> print d.data.polybli # an array has no meta in the XMLs
Some data object, possibly an array.
>>> print d.data.meta("subtype")
Device subtype [...]

The description of a property can be accessed through a property object, either a real one or a skelleton. Using an equipment model class' skel method works too.

>>> # assuming d as a mx device as above
...
>>> print d.skel("rconstant").meta()
Polynomials are \f$y = a_3 x^3 + a_2 x^2 [...]
>>> print d.rconstant().meta()
Polynomials are \f$y = a_3 x^3 + a_2 x^2 [...]
>>> print devscr.eqMod("mx").meta("rconstant")
Polynomials are \f$y = a_3 x^3 + a_2 x^2 [...]

Preconditions

devscr should be usable when the system variables
  • PYTHONPATH contains $PROOT/python/current
  • LD_LIBRARY_PATH contains $libasl64 and libmsg64

See also

Customizing Devscr

Slides 1-16 of devscrIntoAndLittleBackground.pdf
Topic revision: r27 - 10 Sep 2018, LudwigHechler
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