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
- zero-initialize property objects,
- the para and data parts of which can be set and
- 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.
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