GrailClient
Introduction
GrailClient is a Python module that eases the task of interfacing to the Grail server. Though using SOAPpy directly to talk to Grail is easy enought, there are some aspects that are inconvenient or difficult:
- Sending arrays or complex structures to Grail
- Receiving and decoding entire parameter or sampler structures
- Implementing callbacks.
GrailClient does the heavy lifting for these tasks.
Note that GrailClient is not required to interface to Grail. It is a convenience.
Finding GrailClient
GrailClient.py
can be found in the sparrow git repository, in
sparrow/gbt/api/ygor/src
.
Installing GrailClient
GrailClient is installed as part of the Sparrow installation.
Some simple GrailClient examples:
>>> from gbt.ygor import GrailClient
>>> cl = GrailClient("toe", 18000) # for simulator Grail
Imports GrailClient and creates an instance.
Now you can find out what GrailClient methods and attributes are available. This is one area that GrailClient can help over a bare SOAPProxy to Grail. Methods and attributes are visible:
>>> dir(cl)
Many methods are straight through calls to Grail through SOAPpy. These behave exactly the same in GrailClient:
>>> cl.show_managers()
>>> cl.on("Undulator")
>>> # etc.
Where GrailClient can help is that it provides class instances of managers. This can save some typing, as well as helping clarify program structure.
>>> tempMon = cl.create_manager("TemperatureMonitor")
>>> tempMon.on()
>>> for i in tempMon.show_samplers():
... print i
...
{'name': 'StructureTemp', 'description': 'Temperature Sensor readings'}
>>>
Here we asked Grail to list the TemperatureMonitor samplers. Now, the good part. First, we will start a Monitor for the sampler on Grail. Next, we will obtain the latest value for that sampler, and finally we will print it using the pretty printer from the
pprint
module:
>>> tempMon.reg_sampler('StructureTemp')
'OK'
>>> s = tempMon.get_sampler('StructureTemp')
>>> pprint(s)
{'StructureTemp': {'StructureTemp': {'gbtts1_2001': {u'description': 'sensor name',
u'name': 'gbtts1_2001',
u'type': 'float',
u'units': 'Celsius',
u'value': '2.4'},
'gbtts1_2003': {u'description': 'sensor name',
u'name': 'gbtts1_2003',
u'type': 'float',
u'units': 'Celsius',
u'value': '1.93'},
'gbtts1_2005': {u'description': 'sensor name',
u'name': 'gbtts1_2005',
u'type': 'float',
u'units': 'Celsius',
u'value': '3.54'},
'gbtts2_2001': {u'description': 'sensor name',
u'name': 'gbtts2_2001',
u'type': 'float',
u'units': 'Celsius',
u'value': '4.09'},
'gbtts2_2002': {u'description': 'sensor name',
u'name': 'gbtts2_2002',
u'type': 'float',
u'units': 'Celsius',
u'value': '4.16'},
'gbtts2_2004': {u'description': 'sensor name',
u'name': 'gbtts2_2004',
u'type': 'float',
u'units': 'Celsius',
u'value': '3.21'},
'gbtts3_2001': {u'description': 'sensor name',
u'name': 'gbtts3_2001',
u'type': 'float',
u'units': 'Celsius',
u'value': '3.26'},
'gbtts3_2002': {u'description': 'sensor name',
u'name': 'gbtts3_2002',
u'type': 'float',
u'units': 'Celsius',
u'value': '4.04'},
'gbtts3_2003': {u'description': 'sensor name',
u'name': 'gbtts3_2003',
u'type': 'float',
u'units': 'Celsius',
u'value': '3.71'},
'gbtts3_2004': {u'description': 'sensor name',
u'name': 'gbtts3_2004',
u'type': 'float',
u'units': 'Celsius',
u'value': '4.43'},
'gbtts4_2001': {u'description': 'sensor name',
u'name': 'gbtts4_2001',
u'type': 'float',
u'units': 'Celsius',
u'value': '3.6'},
'gbtts4_2002': {u'description': 'sensor name',
u'name': 'gbtts4_2002',
u'type': 'float',
u'units': 'Celsius',
u'value': '2.1'},
'gbtts4_2003': {u'description': 'sensor name',
u'name': 'gbtts4_2003',
u'type': 'float',
u'units': 'Celsius',
u'value': '3.21'},
'gbtts4_2004': {u'description': 'sensor name',
u'name': 'gbtts4_2004',
u'type': 'float',
u'units': 'Celsius',
u'value': '3.86'},
'gbtts4_2005': {u'description': 'sensor name',
u'name': 'gbtts4_2005',
u'type': 'float',
u'units': 'Celsius',
u'value': '14.17'},
'gbtts4_2006': {u'description': 'sensor name',
u'name': 'gbtts4_2006',
u'type': 'float',
u'units': 'Celsius',
u'value': '4.07'},
'gbtts5_2001': {u'description': 'sensor name',
u'name': 'gbtts5_2001',
u'type': 'float',
u'units': 'Celsius',
u'value': '2.91'},
'gbtts5_2002': {u'description': 'sensor name',
u'name': 'gbtts5_2002',
u'type': 'float',
u'units': 'Celsius',
u'value': '3.99'},
'gbtts5_2003': {u'description': 'sensor name',
u'name': 'gbtts5_2003',
u'type': 'float',
u'units': 'Celsius',
u'value': '2.73'},
'gbtts5_2004': {u'description': 'sensor name',
u'name': 'gbtts5_2004',
u'type': 'float',
u'units': 'Celsius',
u'value': '3.83'},
'gbtts5_2006': {u'description': 'sensor name',
u'name': 'gbtts5_2006',
u'type': 'float',
u'units': 'Celsius',
u'value': 'NaN'}}},
'description': 'Temperature Sensor readings',
'id': '1409286144',
'name': 'StructureTemp'}
>>>
As can be seen, GrailClient converts the sampler structure into a straight-forward Python dictionary, which
pprint
then renders into a highly readable output. Parameters are obtained exactly the same way:
>>> s = tempMon.get_parameter('state')
>>> pprint(s)
{'description': 'current operational state of Manager',
'id': '1409286155',
'name': 'state',
'state': {'state': {u'description': 'current operational state of Manager',
u'name': 'state',
u'type': 'enum',
u'units': 'None',
u'value': 'Ready'}}}
>>>
One of the bothers with the straight
SOAPProxy
interface is the use of the Grail
set_values_array()
method. This method takes an array of elements, each consisting of 3 fields: device, parameter field path, and parameter field value. It was necessary to set up a
SOAPpy.structType
for each element, and feed it into a
SOAPpy.arrayType
, and finally call
set_values_array()
. With GrailClient, all that is needed is a list whose elements are each 3 element lists or tuples (or 2, if using a Manager class; device can then be ommitted):
>>> a = [["Undulator", "levelHW", 5], ["Undulator", "struct_dynamic_array,2,b", 66]]
>>> cl.send_values_array(a)
Or:
>>> und = cl.create_manager("Undulator")
>>> a = [["levelHW", 5], ["struct_dynamic_array,2,b", 66]]
>>> und.send_values_array(a) # values must be only for Undulator!
Parameter and Sampler callbacks
The primary reason GrailClient exists is to support callbacks for parameters and samplers. GrailClient handles these by creating a SOAP Server in a separate thread, ready to receive notification from Grail when any registered parameters or samplers changed. GrailClient users can then register a parameter or sampler along with a callback function:
>>> def cb(device, param, val):
... print "Received parameter", param, "from device", device
... pprint(val)
...
>>> cl.reg_param("Undulator", "levelHW", cb) # or und.reg_param("levelHW", cb)
Note the signature of the callback function. The first and second parameters,
device
and
param
, are strings. The third,
val
, is a dictionary whose keys match the ygor parameter field names, as described above in the examples section.
To unregister a callback, simply use the
unreg_param()
methods with the same keys:
>>> cl.unreg_param("Undulator", "levelHW", cb) # or und.unreg_param("levelHW", cb)
Make sure that the same keys are used to unregister! This means the strings must say the same things, and the callback function object must be the same object used to register the callback. I may add an
unreg_all()
function if there is a need for it.
Samplers are handled in the same way, except using the
reg_sampler()
and
unreg_sampler()
methods.
Note: reg_sampler()
and
unreg_sampler()
replace the older
start_sampler()
and
stop_sampler()
methods. If you wish to start a sampler but have no desire to register a callback for that sampler (for example, you wish to poll it every few minutes with
get_sampler()
), simply use
reg_sampler()
and
unreg_sampler()
without the callback function parameter. For example:
>>> undulator = cl.create_manager("Undulator") # GrailClient reg_sampler method similar; must provide device.
>>> undulator.reg_sampler("MySampler") # note no callback provided!
>>> MS = undulator.get_sampler("MySampler")
>>> undulator.unreg_sampler("MySampler")
Debugging SOAP messages
Sometimes it is useful to see the SOAP XML traffic between the client and server. One easy way to do this is in an interactive python shell, do the following:
In [13]: from gbt.ygor import GrailClient
In [14]: gc = GrailClient("toe", 18000)
In [15]: gc.cl.config.debug = 1 # <<<<<<<<<<<<< THIS CAUSES XML DEBUG OUTPUT!
In [16]: sc = gc.create_manager("ScanCoordinator")
In [17]: sc.get_value('state')
In build.
In dump. obj= ScanCoordinator
In gentag.
In dump_string.
In dumper.
In dump. obj= state
In gentag.
In dump_string.
In dumper.
*** Outgoing HTTP headers **********************************************
POST / HTTP/1.0
Host: toe:18000
User-agent: SOAPpy 0.12.5 (http://pywebsvcs.sf.net)
Content-type: text/xml; charset=UTF-8
Content-length: 565
SOAPAction: "get_value"
************************************************************************
*** Outgoing SOAP ******************************************************
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/1999/XMLSchema"
>
<SOAP-ENV:Body>
<ns1:get_value xmlns:ns1="urn:grail" SOAP-ENC:root="1">
<v1 xsi:type="xsd:string">ScanCoordinator</v1>
<v2 xsi:type="xsd:string">state</v2>
</ns1:get_value>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
************************************************************************
code= 200
msg= OK
headers= Server: gSOAP/2.3
Content-Type: text/xml; charset=utf-8
Content-Length: 542
Connection: close
content-type= text/xml; charset=utf-8
data= <?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:xsd="http://www.w3.org/1999/XMLSchema" xmlns:ns1="urn:grail" xmlns:ns2="urn:grail-client"><SOAP-ENV:Body SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" id="_0"><ns1:get-valueResponse><result xsi:type="xsd:string">Ready</result></ns1:get-valueResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>
*** Incoming HTTP headers **********************************************
HTTP/1.? 200 OK
Server: gSOAP/2.3
Content-Type: text/xml; charset=utf-8
Content-Length: 542
Connection: close
************************************************************************
*** Incoming SOAP ******************************************************
<?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:xsd="http://www.w3.org/1999/XMLSchema" xmlns:ns1="urn:grail" xmlns:ns2="urn:grail-client"><SOAP-ENV:Body SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" id="_0"><ns1:get-valueResponse><result xsi:type="xsd:string">Ready</result></ns1:get-valueResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>
************************************************************************
Out[17]: 'Ready'
--
RamonCreager - 14 Nov 2003