source: wxmtxy/trunk/pvConnect.py @ 178

Last change on this file since 178 was 178, checked in by jemian, 15 years ago

working to make code more robust against failed PV connections, pydoc call must be improved

  • Property svn:executable set to *
  • Property svn:keywords set to Date Revision Author HeadURL Id
File size: 11.1 KB
Line 
1#!/usr/bin/env python
2
3#*************************************************************************
4# Copyright (c) 2009-2010 The University of Chicago, as Operator of Argonne
5#     National Laboratory.
6# Copyright (c) 2009-2010 The Regents of the University of California, as
7#     Operator of Los Alamos National Laboratory.
8# This file is distributed subject to a Software License Agreement found
9# in the file LICENSE that is included with this distribution.
10#*************************************************************************
11
12'''
13simplified connections to an EPICS PV using CaChannel
14
15Provides these classes:
16
17    CaPollWx
18        Use in WX-based GUIs to call ca.poll() in the background
19        @param interval_s: [float] interval between calls to ca.poll()
20
21    EpicsPv
22        manage a CaChannel connection with an EPICS PV
23        @param name: [string] EPICS PV to connect
24
25Provides these utility routines:
26
27    on_exit(timer)
28        Exit handler to stop the ca.poll()
29        @param timer: CaPollWx object
30
31    CaPoll()
32        Use in non-GUI scripts to call ca.poll() in the background
33
34    GetRTYP(pv)
35        Returns the record type of "pv"
36        @param pv:[string]
37        @return: [string] EPICS record type or None if cannot connect
38
39    testConnect(pv)
40        Tests if a CaChannel connection can be established to "pv"
41        @param pv:[string]
42        @return: True if can connect, otherwise False
43
44    receiver(value)
45        Example response to an EPICS monitor on the channel
46        @param value: str(epics_args['pv_value'])
47
48@version:
49########### SVN repository information ###################
50# $Date: 2010-06-02 23:14:09 +0000 (Wed, 02 Jun 2010) $
51# $Author: jemian $
52# $Revision: 178 $
53# $URL: wxmtxy/trunk/pvConnect.py $
54# $Id: pvConnect.py 178 2010-06-02 23:14:09Z jemian $
55########### SVN repository information ###################
56'''
57
58
59import sys
60import time
61
62
63try:
64    # CaChannel provides access to the EPICS PVs
65    import CaChannel
66    IMPORTED_CACHANNEL = True
67except:
68    IMPORTED_CACHANNEL = False
69
70
71try:
72    # wx is needed for the timer to call CaChannel.ca.poll()
73    # only use this with a wx-based GUI
74    import wx
75    IMPORTED_WX = True
76except:
77    IMPORTED_WX = False
78
79
80class CaPollWx:
81    '''Use in WX-based GUIs to call ca.poll() in the background
82       
83        Set up a separate thread to trigger periodic calls to the
84        EPICS CaChannel.ca.poll() connection.  Awaiting (a.k.a.,
85        outstanding or pending) channel access background
86        activity executes during the poll.  Calls pend_event()
87        with a timeout short enough to poll. 
88
89        The default polling interval is 0.1 second.
90   
91        @note: The code will silently do nothing if wx was not imported.
92        This routine use the wx.PyTimer() to call ca.poll() frequently
93        during the main WX event loop.
94
95        @warning: Only use this in a routine that has already called
96        wx.App() or an exception will occur. 
97        Command line code will need to call ca.poll() using a different
98        method (such as CaPoll() below).
99    '''
100
101    def __init__(self, interval_s = 0.1):
102        '''@param interval_s: [float] interval between calls to ca.poll()'''
103        if IMPORTED_WX:     # only if wx was imported
104            self.running = False
105            self.interval_s = interval_s
106            self.timer = wx.PyTimer(self.poll)
107            self.timer.Start(int(self.interval_s*1000))
108
109    def start(self):
110        '''start polling'''
111        if IMPORTED_WX:     # only if wx was imported
112            self.running = True
113            self.timer.Start(int(self.interval_s*1000))
114
115    def stop(self):
116        '''stop polling'''
117        if IMPORTED_WX:     # only if wx was imported
118            self.running = False
119            self.timer.Stop()
120
121    def poll(self):
122        '''Poll for changes in Channel'''
123        if IMPORTED_WX:     # only if wx was imported
124            CaChannel.ca.poll()
125
126    def GetInterval(self):
127        '''return the current interval between calls to CaChannel.ca.poll()'''
128        if IMPORTED_WX:     # only if wx was imported
129            return self.interval_s
130
131    def SetInterval(self, interval_s):
132        '''set the next interval between calls to CaChannel.ca.poll()'''
133        if IMPORTED_WX:     # only if wx was imported
134            self.interval_s = interval_s
135
136
137class EpicsPv:
138    '''manage a connection with an EPICS PV'''
139
140    def __init__(self, name):
141        '''initialize the class and set default values
142           @param name: [string] EPICS PV to connect'''
143        self.pv = name
144        self.chan = None
145        self.value = None
146        self.user_callback = None
147        self.epics_args = None
148        self.user_args = None
149        self.mask = None
150        if IMPORTED_CACHANNEL:
151            self.mask = CaChannel.ca.DBE_VALUE
152
153    def callback(self, epics_args, user_args):
154        '''receive an EPICS callback, copy epics_args and user_args, then call user'''
155        self.epics_args = epics_args
156        self.user_args = user_args
157        self.value = epics_args['pv_value']
158        if self.user_callback != None:
159            self.user_callback(epics_args, user_args)
160
161    def connect(self):
162        '''initiate the connection with EPICS'''
163        if IMPORTED_CACHANNEL:
164            if len(self.pv) > 0:
165                self.chan = CaChannel.CaChannel()
166                self.chan.search(str(self.pv))
167
168    def connectw(self):
169        '''initiate the connection with EPICS, standard wait for the connection'''
170        if IMPORTED_CACHANNEL:
171            if len(self.pv) > 0:
172                self.chan = CaChannel.CaChannel()
173                self.chan.searchw(str(self.pv))
174
175    def release(self):
176        '''release the connection with EPICS
177            @note: Release ALL channels before calling on_exit()'''
178        if self.chan != None:
179            del self.chan
180            self.chan = None
181
182    def monitor(self):
183        '''Initiate a monitor on the EPICS channel, delivering the
184            CaChannel callback to the supplied function.
185           
186            @note: Example:
187                ch = EpicsPv(test_pv)
188                ch.connectw()
189                uargs = test_pv, widget.SetLabel
190                ch.SetUserArgs(uargs)
191                ch.SetUserCallback(myCallback)
192                ch.monitor()
193           
194            @warning: At this time, there is not an easy way to turn off monitors.
195                Instead, ch.release() the channel (which will set self.chan = None),
196                To re-start a monitor after a ch.release(), connect as usual and start
197                the monitor again, as the first time.
198        '''
199        if IMPORTED_CACHANNEL and self.chan != None:
200            type = CaChannel.ca.dbf_type_to_DBR_GR(self.chan.field_type())
201            # call supplied callback routine with default argument list
202            #       self.user_callback(epics_args, user_args)
203            self.chan.add_masked_array_event(type, 
204                None, self.mask, self.callback, self.user_args)
205
206    def GetPv(self):
207        '''@return: PV name'''
208        return self.pv
209
210    def GetValue(self):
211        '''@return: value from EPICS from the most recent monitor event'''
212        return self.value
213
214    def SetPv(self, name):
215        '''redefine the PV name only if there is no connection
216            @param name: valid EPICS PV name'''
217        if self.chan == None:
218            self.pv = name
219
220    def GetChan(self):
221        '''@return: CaChannel channel'''
222        return self.chan
223
224    def GetEpicsArgs(self):
225        '''@return: epics_args from the most recent monitor event'''
226        return self.epics_args
227
228    def GetUserArgs(self):
229        '''@return: user_args from the most recent monitor event'''
230        return self.user_args
231
232    def SetUserArgs(self, user_args):
233        '''define the user_args tuple to use when monitoring
234            @param user_args: tuple of user data (for use in user_callback function)'''
235        self.user_args = user_args
236
237    def SetMask(self, mask):
238        '''Define the mask used when applying a channel monitor.
239            The default is: self.mask = CaChannel.ca.DBE_VALUE
240            @param mask: as defined in the CaChannel manual'''
241        self.pv = mask
242
243    def GetUserCallback(self):
244        '''return the callback function supplied by the caller
245            values will be set by self.user_callback(value)
246            @return: function object'''
247        return self.user_callback
248
249    def SetUserCallback(self, user_callback):
250        '''Set the callback function supplied by the caller
251            values will be set by self.user_callback(value)
252            @param user_callback: function object'''
253        self.user_callback = user_callback
254
255
256def on_exit(timer = None):
257    '''Exit handler to stop the ca.poll()
258       @param timer: CaPollWx object
259
260       Call this to cleanup when program is exiting.
261       ONLY call this function during a program's exit handling.
262       If ca.task_exit() is not called, then expect to see the errors:
263            FATAL: exception not rethrown
264            Abort
265    '''
266    if IMPORTED_CACHANNEL:     # only if ca binding was loaded
267        CaChannel.ca.task_exit()
268    try:        # fail no matter what
269        if timer != None:
270            timer.stop()
271    except:
272        pass
273
274
275def CaPoll():
276    '''Use in non-GUI scripts to call ca.poll() in the background'''
277    if IMPORTED_CACHANNEL:
278        CaChannel.ca.poll()
279
280
281def GetRTYP(pv):
282    '''Returns the record type of "pv"
283       @param pv:[string]
284       @return: [string] EPICS record type or None if cannot connect'''
285    if not IMPORTED_CACHANNEL:
286        return None
287    if len(pv) == 0:
288        return None
289    base = pv.split('.')[0]
290    if testConnect(base):
291        rtyp_pv = base + '.RTYP'
292        ch = EpicsPv(rtyp_pv)
293        ch.connectw()
294        rtyp = ch.chan.getw()
295        del ch
296        return rtyp
297    else:
298        return None
299
300
301def testConnect(pv):
302    '''Tests if a CaChannel connection can be established to "pv"
303       @param pv:[string]
304       @return: True if can connect, otherwise False'''
305    result = False
306    if not IMPORTED_CACHANNEL:
307        return result
308    try:
309        #print 'testConnect:', type(pv), pv
310        chan = CaChannel.CaChannel()
311        chan.searchw(str(pv))
312        val = chan.getw()
313        del chan
314        result = True
315    except (TypeError, CaChannel.CaChannelException), status:
316        # python3: (TypeError, CaChannel.CaChannelException) as status
317        #print 'testConnect:', status
318        pass
319    return result
320
321
322def receiver(epics_args, user_args):
323    '''Example response to an EPICS monitor on the channel
324       @param value: str(epics_args['pv_value'])'''
325    value = epics_args['pv_value']
326    print 'receiver', 'updated value:', str(value)
327
328
329if __name__ == '__main__':
330    if IMPORTED_CACHANNEL:
331        test_pv = 'S:SRcurrentAI'
332        if testConnect(test_pv):
333            print "recordType(%s) = %s" % (test_pv, GetRTYP(test_pv))
334            ch = EpicsPv(test_pv)
335            ch.connectw()
336            ch.SetUserCallback(receiver)
337            ch.monitor()
338            ch.chan.pend_event()
339            import time
340            count = 5
341            for seconds in range(count):
342                time.sleep(1)
343                ch.chan.pend_event()
344                print count - seconds - 1, ch.GetPv(), '=', ch.GetValue()
345            ch.release()
346            on_exit()
347    else:
348        print "CaChannel is missing, cannot run"
Note: See TracBrowser for help on using the repository browser.