| EpicsCA.PV = class PV | ||
| == pv: The Process Variable A pv encapsulates an Epics Process Variable (aka a 'channel'). The primary interface methods for a pv are to get() and put() its value: >>>p = PV(pv_name) # create a pv object given a pv name >>>p.get() # get pv value >>>p.put(val) # set pv to specified value. Additional important attributes include: >>>p.pvname # name of pv >>>p.value # pv value (can be set or get) >>>p.char_value # string representation of pv value >>>p.count # number of elements in array pvs >>>p.type # EPICS data type: 'string','double','enum','long',.... A pv uses Channel Access monitors to improve efficiency and minimize network traffic, so that calls to get() fetches the cached value, which is automatically updated. Note that it is important to periodically call poll() or pend_event() to make sure that the pv values are up to date. == Initialization, Connection, time-outs: On creating a pv [pv = EpicsCA.PV()], the following optional arguments can be given: pvname name of pv [None] callback user-specified callback function (see below) [None] connect indicate whether or not to connect [False] use_numpy use numpy arrays for array data [True] use_control use full Control Epics Data Type [False] connect_time maximum time (in sec) for connection [1.0] use_char_changes run user-defined callbacks only when the 'char_value' changes, ignoring trivial changes [True] A PV can be created without a pvname and assigned a pvname later. In order to communicate with the corresponding channel on the IOC, a PV needs to "connect". This creates a dedicated connection to the IOC on which the PV lives, and creates resources for the PV. A Python PV object cannot actually do anything to the PV on the IOC until it is connected. Connection is a two-step process. First a local PV is "created" in local memory. This happens very quickly, and happens automatically when a PV is initialized (and has a pvname). Second, connection is completed with network communication to the IOC. This is necessary to determine the PV "type" (that is, integer, double, string, enum, etc) and "count" (that is, whether it holds an array of values) that are needed to allocate resources on the client machine. Again, this connection must happen before you can do anything useful with the PV. When done as a single step, the creation and connection process takes 30msec. This is a feature of Channel Access. In normal use this is fast enough, but for cases where you know that you're going to use many PVs, this connection time can add up. In this case, it turns out (again, a Channel Access feature) that it is much faster to create many PVs without connecting to them, and then connect to all of them. Because of this, a Python PV is created with "connect=False" by default. This delays complete connection until it is needed. That is, doing a .get() or .put() will automatically connect to the PV. Generally this is good, as it reduces average connection time, but there is an important caveat: an unconnected PV will not respond to changes. That means, you cannot create a bunch of PVs with x = PV(pvname) and then poll() for changes -- you won't see any changes until the PVs are connected. A convienent way to deal with this is to run EpicsCA.connect_all() prior to polling for changes. This function will complet the connection of all unconnected PVs. The 'connected' attribute reflects wether the pv is successfully connected. >>> p = PV() >>> p.connected False >>> p <pv: unnamed> >>> p.pvname = 'XXX:m1.VAL' >>> p.connected True >>> p <pv: 'XXX:m1.VAL', count=1, type=double, access=read/write> If a pv cannot connect in the connect_time, an EpicsCAError will be raised. == Information about a pv In addition to 'value', 'char_value', and 'connected' discussed above, a pv has the following attributes: count element count: 1 for scalars, number of array elements for arrays. precision precision (number of values past decimal point) for floating point values host name of host providing pv access string describing read/write access ctype integer containing EPICS type (int, long, enum, etc) type string describing EPICS type enum_strings list of strings for Enumeration States for enum variables status integer status (1 means OK, apparantly) severity EPICS severity (0 means OK, apparantly) info text paragraph of 'status report' listing most of all attributes. The following additional 'Control Type' information can also be obtained for pvs: units string containing units, if applicable (else None) hlim control high limit llim control low limit display_llim display high limit display_hlim display low limit To get this information, you must create the PV with the optional 'use_control=True' argument to PV(): >>> p = PV('XXX:m1.VAL', use_control=True) >>> print p.units 'mm' PV representation: At the python command prompt, typing the name of the PV object will print a short description of the PVs connection status and type. Some examples: >>> p = EpicsCA.PV() >>> p <PV: unnamed> >>> p = EpicsCA.PV('xxx',connect=False) >>> p, p.connected (<PV: 'xxx': unconnected>, False) >>> p.get() >>> p, p.connected (<PV: 'xxx', count=1, type=double, access=read/write>, False) == PV Values, String representations The 'value' attribute holds the PVs value and can be gotten or set either with .get()/.set() methods or simply by accessing/assigning to the 'value': >>> print p.value 1.000 >>> p.value = 2.0 # (PV processes, motor moves, etc) Or, with methods: >>> print p.get() 2.000 >>> p.put(1.0) The 'char_value' attribute holds a string representation of the value. For floating point numbers and integers, this is simply an appropriately formatted string (using the precision). For ENUM variables, this contains the 'enum string' name. The char_value attribute cannot be set. == Getting and Putting PVs get() can retrieve either the 'raw' value or a string representation -- the 'char_value'. That is, pv.get() and pv.value will return the 'raw' value, which may be of several data types (int, float, array, string). To get the string representation for a PV, use pv.get(use_char=True) or pv.char_value. What the 'char_value' holds depends on the data type, but it will always be a python string. For string PV data, the char_value is the same as the raw value. For integers, it is pretty straightforward. For floats/doubles, the char_value is formatted according to the PVs precision. For enum PVs, the char_value hold the value description. For arrays, the char_value will be '<array(size=N, type=I)>' where N is the length of the array, and the type will tell the data type of each array member. With put(), you simply provide the value (the raw value, not the char_value) that you want to set the value to. These are equivalent. >>> p.put(1.0) >>> p.value = 1.0 It is often desirable to know when the put has completes (eg, when a motor is done moving, when a counter is done counting, or more generally when a record and its forward links have fully processed). There are two ways to do this: First, you can use the the 'wait=True' option in put(): >>> put(1.0, wait=True, timeout=60.0) This will block until the put is complete or until the timeout (in seconds, and which has a default value of 3600.0) has expired. An alternative is to not block, but to poll from python until the PVs 'put_complete' attribute is set to True. >>> p.put(1.0,user_wait=True) >>> while not p.put_complete: >>> pend_event() >>> print 'still moving' This approach can be a bit more convenient for interactive codes. == User Callbacks (monitors): Under normal operation of a program communicating with Epics, pend_event(), poll(), or pend_io() should be called periodically. When a PVs value on the host machine has changed, a pend_event() or poll() is the only way for the local application to know that the change has occurred. When a variable changes and one of the polling routines is called, the internal value and attributes are automatically updated to the latest values. In addition, you can define one or more 'callback' routines to be run every time a PVs state is seen to change. The callback routine(s) can be used to update GUI widgets, for example. A callback can be specified at PV creation: >>>def my_callback(PV=None, **kws): print 'my callback ', PV.pvname, PV.value >>>p = EpicsCA.PV('XXX', callback=my_callback) See the closure class (in this module) for a convenient way to set a callback with arguments and keywords. ********** Important Note: ********** callback functions will be called with the first argument 'PV' holding the PV object. You *must* have a 'PV' argument in your callback routine!! ********** Important Note: ********** Additional callbacks can be added and removed at any time. To do this, a dictionary of callbacks is maintained, each having an 'index' as a key. If not specified, an index key will be created. add_callback(self,callback=None,index=None,*args,**kws): set a user defined callback, returns the index key for this callback. remove_callback(index=None): remove a callback given its index name clear_callbacks(): clear all user-defined callbacks list_callbacks(): returns a list of existing callbacks and their indices set_callback(callback=None,*args,**kws): set a callback, erasing all current callbacks. This is included for backward compatibility -- normally, add_callback() should be used. ********** Recommendataion: ********** Because the user-defined callbacks are really run from within a "pend_event()", it is not very useful to set other PVs and rely on them being changed (as the action may not be noticable until later), or to run very extensive calculations. My recommendation is to use callbacks for doing little more than noting that a PV have changed, and then acting on that change after pend_event(). This can be especially true for GUI code, and explains much of the code in lib/wx. A useful approach is to create a store of "changes", have pend_event() simply put information about the changes in that store, then have another routine which acts on the changes: changes = [] def onEvent(pv=None, **kw): if pv is not None: changes.append((pv.pvname, pv.char_value, time.ctime()) for pv in PVlist: pv.set_callback(callback=onEvent) def showChanges(): while changes: name,cvalue,ctime = changes.pop() print 'PV %s = %s at %s' % (name,cvalue,ctime) # main loop while True: pend_event() # note PV changes showChanges() # act on PV changes | ||
Methods defined here:
Data and other attributes defined here:
| ||