EPICS Access from Python Geoff Savage DØ Workshop Thursday June 22, 2000
6/22/00DZero Workshop2 Game Plan Discuss EPICS channel access (CA) u C library u High level discussion Provide examples from CaChannel u Python class that wraps CA library u Wrap = provide python interface u Delve further into CA functionality
6/22/00DZero Workshop3 EPICS Channel Access Role in EPICS architecture Architecture Services Library functionality
6/22/00DZero Workshop4 Role in EPICS Architecture IOC Host IOC Network Transfer information between host and IOC
6/22/00DZero Workshop5 CA Architecture Client-server architecture u IOC = server u Host application = client Client makes requests to server Channel = client-server connection u Connection between client and PV u All channel creation requests made by name Host and IOC must be on network
6/22/00DZero Workshop6 CA Services Dynamic channel creation Automatic reconnect Read/Write Monitoring u Events u Access control u Connection Data type conversions Composite data structures
6/22/00DZero Workshop7 Channel Creation IOC Host Who has channel ‘XXXX’? (broadcast) IOC Host I have channel ‘XXXX’. IOC Host Negotiate connection. (point-to-point)
6/22/00DZero Workshop8 CA Library Messages Connection Synchronous Asynchronous Group
6/22/00DZero Workshop9 CA Messages All CA requests are buffered in host u Connect, get, put, … u Increased efficiency Buffer is sent when full or upon user request PV get/put requests can’t be in same message as connection request
6/22/00DZero Workshop10 Connection (CA Lib) “Establish and maintain a virtual circuit between application and PV in a CA server” Connection must be made before accessing PV Network transparency (by name) Callback on connection status change
6/22/00DZero Workshop11 Synchronous (CA Lib) Make requests Flush request buffer into message Wait until all requests in message have been completed or timeout occurs Return control to application
6/22/00DZero Workshop12 Asynchronous (CA Lib) Make requests and specify callback Flush request buffer into message Return control to application You are notified of completion through execution of callback at a later time Synchronous and Asynchronous requests can be in the same message
6/22/00DZero Workshop13 Group (CA Lib) “Guarantee that a set of CA requests have completed” Synchronous (no callback) Create Get/put Block waiting for completion (timeout) Test for completion Remove uncompleted requests
6/22/00DZero Workshop14 EPICS CA Summary High level discussion Role = transfer information Architecture = client-server Services - Covered in more detail during CaChannel discussion Library functionality u Buffering for efficiency, Connections, Synchronous, Asynchronous, Group
6/22/00DZero Workshop15 CaChannel Architecture Simple examples Data types Synchronous I/O Asynchronous I/O Monitoring Use with Tkinter
6/22/00DZero Workshop16 CaChannel Architecture EPICS channel access C library Collection of C functions Distributed with EPICS caPython Python wrapper around the C library Collection of python functions CaChannel Python class Calls caPython functions Move from C function to Python class interface.
6/22/00DZero Workshop17 setup d0python python Python (#6, May , 21:44:32) [C] on osf1V4 Copyright Stichting Mathematisch Centrum, Amsterdam >>> from CaChannel import * >>> ch = CaChannel() >>> ch.searchw('catest') >>> ch.putw( ) >>> ch.getw() Simple Interpreter Example Each method performs one synchronous request. connect write read
6/22/00DZero Workshop18 Simple Script Example #! /bin/env python # from CaChannel import * def main(): try: catest = CaChannel() catest.searchw('catest') catest.putw( ) print catest.getw() except CaChannelException, status: print ca.message(status) main()
6/22/00DZero Workshop19 Data Types … Each PV has a native type Different data types can be requested Requests using native type most efficient >>> ch.getw() >>> ch.getw(ca.DBR_INT) 123 >>> ch.getw(ca.DBR_STRING) '123' >>> ch.field_type() 6 >>> ca.dbr_text(ch.field_type()) 'DBR_DOUBLE‘ >>> Not all caPython functions implemented as methods.
6/22/00DZero Workshop20 … Data Types … >>> scan = CaChannel() >>> scan.searchw('catest.SCAN') >>> scan.getw() 6 >>> scan.getw(ca.DBR_STRING) '1 second' >>> scan.putw(5) >>> scan.getw(ca.DBR_STRING) '2 second‘ >>> ca.dbr_text(scan.field_type()) 'DBR_ENUM' >>>
6/22/00DZero Workshop21 … Data Types >>> scan.putw('1 second') Traceback (innermost last): File " ", line 1, in ? File "/d0usr/products/d0python/OSF1/v02_03/lib/CaChannel.py", line 383, in putw count, pval = self.__setup_put(value, req_type) File "/d0usr/products/d0python/OSF1/v02_03/lib/CaChannel.py", line 131, in __setup_put CaChannel.dbr_d[req_type]['convert'](value), ValueError: invalid literal for int(): 1 second >>> scan.putw('1 second', ca.DBR_STRING) >>> scan.getw(ca.DBR_STRING) '1 second'
6/22/00DZero Workshop22 Request Data Types Request typePython typeComments DBR_STRINGString DBR_ENUMIntegerEnumerated DBR_CHARInteger8 bits DBR_INTInteger16 bits DBR_LONGInteger32 bits DBR_FLOATFloat DBR_DOUBLEFloat Array of DBRList of type
6/22/00DZero Workshop23 Synchronous I/O … pend_io = flush request buffer and wait for CA requests to complete pend_io affects requests from all CaChannel instances Only one request buffer for all CaChannel instances
6/22/00DZero Workshop24 … Synchronous I/O … Multiple requests can be issued before each pend_io All connections must be made before get or put On a get a value is not valid until pend_io has returned with no errors >>> >>> ch.search('catest') >>> ch.pend_io() >>> >>> ch.array_put( ) >>> ch.pend_io() >>> >>> ch.array_get() >>> ch.pend_io() >>> ch.getValue()
6/22/00DZero Workshop25 … Synchronous I/O catest.array_get() cawave.array_get() scan.array_get() ca.pend_io(1.0) print catest.getValue() print cawave.getValue() print scan.getValue() except CaChannelException,status: print ca.message(status) try: catest = CaChannel() cawave = CaChannel() scan = CaChannel() catest.search('catest') cawave.search('cawave') scan.search('catest.SCAN') catest.pend_io() t=(0,1,2,3,4,5,6,7,8,9,10, 11,12,13,14,15,16,17,18,19) catest.array_put( ) cawave.array_put(t) scan.array_put("1 second“, ca.DBR_STRING) cawave.pend_io()
6/22/00DZero Workshop26 Asynchronous I/O … No waiting for CA requests to complete A user specified callback function is executed when the request has completed ca_pend_event = flush the request buffer and wait for timeout seconds or until all CA background activity is processed ca_flush_io = flush the request buffer
6/22/00DZero Workshop27 … Asynchronous I/O … Asynchronous callbacks do not preempt the main thread Instead ca_pend_io, ca_pend_event, or ca_poll must be called at least every 15 seconds to allow background activity to process ca_poll = ca_pend_event with short (0.001 second) timeout
6/22/00DZero Workshop28 … Asynchronous I/O … try: chan = CaChannel() chan.search_and_connect('catest', connectCb) chan.flush_io() for i in range(20): chan.pend_event() chan.array_put_callback(3.3, None, None, putCb) chan.flush_io() for i in range(20): chan.pend_event() chan.array_get_callback(None, None, getCb1) chan.flush_io() for i in range(20): chan.pend_event() chan.array_get_callback(ca.dbf_type_to_DBR_STS( chan.field_type()), None,getCb2) chan.flush_io() for i in range(20): chan.pend_event() except CaChannelException, status: print ca.message(status)
6/22/00DZero Workshop29 Compound Data Types Only supported in asynchronous mode Extra information with the value u Status – alarm values u Time – status + timestamp u Graphics – status + alarm limits + display limits u Control – graphics + control limits Routines are provided to convert DBR type to compound type
6/22/00DZero Workshop30 Connection Callback epics_args = 2 element tuple t[1] = channel identifier, used to get the channels name t[2] = connection state u CA_OP_CONN_UP u CA_OP_CONN_DOWN user_args = tuple containing all python objects specified after the callback in the method def connectCb(epics_args, user_args): print epics_args print user_args
6/22/00DZero Workshop31 Put Callback chid = channel identifier type = request type (DBR_XXXX) count = element count status = CA status code from server def putCb(epics_args, user_args): print ca.name(epics_args['chid']) print ca.dbr_text(epics_args['type']) print epics_args['count'] print ca.message(epics_args['status']) print user_args
6/22/00DZero Workshop32 Get Callback value(s) = data returned by the server. Multiple data elements are returned in a tuple. def getCb1(epics_args, user_args): print "pvName = ", ca.name(epics_args['chid']) print "type = ", ca.dbr_text(epics_args['type']) print "count = ", epics_args['count'] print "status = ", ca.message(epics_args['status']) print "user args = ", user_args print "value(s) = ", epics_args['pv_value']
6/22/00DZero Workshop33 Get Callback with Status pv_severity = alarm severity pv_status = alarm status def getCb2(epics_args, user_args): print "pvName = ", ca.name(epics_args['chid']) print "type = ", ca.dbr_text(epics_args['type']) print "count = ", epics_args['count'] print "status = ", ca.message(epics_args['status']) print "user args = ", user_args print "value(s) = ", epics_args['pv_value'] print ca.alarmSeverityString(epics_args['pv_severity']) print ca.alarmStatusString(epics_args['pv_status'])
6/22/00DZero Workshop34 Asynchronous I/O Usage Tip To obtain the CaChannel instance to which the callback corresponds use the name Create a dictionary whose keys are names and contents are the CaChannel instances
6/22/00DZero Workshop35 … Asynchronous I/O Callbacks implemented for u Time u Graphics u Control
6/22/00DZero Workshop36 Monitoring … Access rights Connection For technical reasons neither of these are in the CaChannel interface They can be implemented as a C function
6/22/00DZero Workshop37 … Monitoring … Asynchronous I/O originated at the server Client requests notification from the server when u the channel’s value changes by more than the value dead band or alarm dead band u the channel’s alarm state changes Callbacks match those described under asynchronous I/O
6/22/00DZero Workshop38 … Monitoring … def eventCb(epics_args, user_args): print ca.message(epics_args['status']) print "new value = ", epics_args['pv_value'] print ca.alarmSeverityString(epics_args['pv_severity']) print ca.alarmStatusString(epics_args['pv_status']) try: chan = CaChannel() chan.searchw('catest') chan.add_masked_array_event( ca.dbf_type_to_DBR_STS(chan.field_type()), None, ca.DBE_VALUE | ca.DBE_ALARM, eventCb) except CaChannelException, status: print ca.message(status)
6/22/00DZero Workshop39 … Monitoring Monitor maskNotification condition ca.DBE_VALUE when the channel’s value changes by more than MDEL ca.DBE_LOG when the channel’s value changes by more than MDEL ca.DBE_ALARM when the channel’s alarm state changes
6/22/00DZero Workshop40 Tkinter and CaChannel EPICS CA is not thread safe All CaChannel activity must be performed in the main thread Use the Tkinter after method to interrupt the mainloop at regular intervals to allow CA backgroud activity to execute Execute CaChannel calls from the update function called by after
6/22/00DZero Workshop41 CaChannel Summary Architecture = Python wrapper around C library Data types = native types most efficient, extended types Synchronous I/O = interpreter or scripts, easy to use Asynchronous I/O and monitoring u Callbacks u Background activity
6/22/00DZero Workshop42 Documentation In the doc directory of the d0python package CaChannel Guide caPython Guide Working on getting up to date u Time, graphic, and control return types u Timeouts (pend_io, pend_event)
6/22/00DZero Workshop43 CaChannel Internals Constructor searchw() – connect putw() - write getw() - read
6/22/00DZero Workshop44 CaChannel Constructor def __init__(self): # Un-initialized channel id structure self.__chid = ca.new_chid() # Monitor event id self.__evid = None self.__timeout = None # override class timeout
6/22/00DZero Workshop45 searchw() def searchw(self, pvName): status = ca.search(pvName, self.__chid) if (ca.ECA_NORMAL != status): raise CaChannelException, status if self.__timeout is None: timeout = CaChannel.ca_timeout else: timeout = self.__timeout status = ca.pend_io(timeout) if (ca.ECA_NORMAL != status): raise CaChannelException, status
6/22/00DZero Workshop46 putw() def putw(self, value, req_type=None): if(None == req_type): req_type = self.field_type() count, pval = self.__setup_put(value, req_type) status = ca.array_put(req_type, count, self.__chid, pval) if (ca.ECA_NORMAL != status): ca.ptrfree(pval) raise CaChannelException, status if self.__timeout is None: timeout = CaChannel.ca_timeout else: timeout = self.__timeout status = ca.pend_io(timeout) if (ca.ECA_NORMAL != status): ca.ptrfree(pval) raise CaChannelException, status ca.ptrfree(pval)
6/22/00DZero Workshop47 getw() … def getw(self, req_type=None): if(None == req_type): req_type = ca.field_type(self.__chid) count, pval = self.__setup_get(req_type) status = ca.array_get(req_type, count, self.__chid, pval) if (ca.ECA_NORMAL != status): ca.ptrfree(pval) raise CaChannelException, status if self.__timeout is None: timeout = CaChannel.ca_timeout else: timeout = self.__timeout status = ca.pend_io(timeout)
6/22/00DZero Workshop48 … getw() if (ca.ECA_NORMAL != status): ca.ptrfree(pval) raise CaChannelException, status if(1 == count): value = ca.ptrvalue(pval) else: value = self.__build_list(pval, count) ca.ptrfree(pval) return value
6/22/00DZero Workshop49 Simple C Example … /*caExample.c*/ #include main(int argc,char **argv) { struct dbr_ctrl_enum data; int ndx; int status; chid mychid;
6/22/00DZero Workshop50 … Simple C Example SEVCHK(ca_task_initialize(),"ca_task_initialize"); SEVCHK(ca_search(argv[1],&mychid),"ca_search failure"); SEVCHK(ca_pend_io(5.0),"ca_pend_io failure"); SEVCHK(ca_get(DBR_CTRL_ENUM,mychid,(void *)&data),"ca_get failure"); SEVCHK(ca_pend_io(5.0),"ca_pend_io failure"); printf("status: %d, severity: %d, count: %d\n", data.status, data.severity, data.no_str); for (ndx = 0; ndx < data.no_str; ndx += 1) printf(" %2d %s\n", ndx, data.strs[ndx]); return(0); }