source: epics2www/pvConnect.py @ 75

Last change on this file since 75 was 75, checked in by jemian, 14 years ago

initial commit

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Rev Date Author Id Url
File size: 11.1 KB
Line 
1#!/usr/bin/env python
2
3#*************************************************************************
4# Copyright (c) 2009 The University of Chicago, as Operator of Argonne
5#     National Laboratory.
6# Copyright (c) 2009 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: 2009-11-06 15:49:21 +0000 (Fri, 06 Nov 2009) $
51# $Author: jemian $
52# $Revision: 75 $
53# $URL$
54# $Id: pvConnect.py 75 2009-11-06 15:49:21Z 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_ID = 100
107            self.timer = wx.PyTimer(self.poll)
108            self.timer.Start(self.TIMER_ID)
109
110    def start(self):
111        '''start polling'''
112        if IMPORTED_WX:     # only if wx was imported
113            self.running = True
114            self.timer.Start(self.TIMER_ID)
115
116    def stop(self):
117        '''stop polling'''
118        if IMPORTED_WX:     # only if wx was imported
119            self.running = False
120            self.timer.Stop()
121
122    def poll(self):
123        '''Poll for changes in Channel'''
124        if IMPORTED_WX:     # only if wx was imported
125            CaChannel.ca.poll()
126
127    def GetInterval(self):
128        '''return the current interval between calls to CaChannel.ca.poll()'''
129        if IMPORTED_WX:     # only if wx was imported
130            return self.interval_s
131
132    def SetInterval(self, interval_s):
133        '''set the next interval between calls to CaChannel.ca.poll()'''
134        if IMPORTED_WX:     # only if wx was imported
135            self.interval_s = interval_s
136
137
138class EpicsPv:
139    '''manage a connection with an EPICS PV'''
140
141    def __init__(self, name):
142        '''initialize the class and set default values
143           @param name: [string] EPICS PV to connect'''
144        self.pv = name
145        self.chan = None
146        self.value = None
147        self.user_callback = None
148        self.epics_args = None
149        self.user_args = None
150        self.mask = None
151        if IMPORTED_CACHANNEL:
152            self.mask = CaChannel.ca.DBE_VALUE
153
154    def callback(self, epics_args, user_args):
155        '''receive an EPICS callback, copy epics_args and user_args, then call user'''
156        self.epics_args = epics_args
157        self.user_args = user_args
158        self.value = epics_args['pv_value']
159        if self.user_callback != None:
160            self.user_callback(epics_args, user_args)
161
162    def connect(self):
163        '''initiate the connection with EPICS'''
164        if IMPORTED_CACHANNEL:
165            if len(self.pv) > 0:
166                self.chan = CaChannel.CaChannel()
167                self.chan.search(str(self.pv))
168
169    def connectw(self):
170        '''initiate the connection with EPICS, standard wait for the connection'''
171        if IMPORTED_CACHANNEL:
172            if len(self.pv) > 0:
173                self.chan = CaChannel.CaChannel()
174                self.chan.searchw(str(self.pv))
175
176    def release(self):
177        '''release the connection with EPICS
178            @note: Release ALL channels before calling on_exit()'''
179        if self.chan != None:
180            del self.chan
181            self.chan = None
182
183    def monitor(self):
184        '''Initiate a monitor on the EPICS channel, delivering the
185            CaChannel callback to the supplied function.
186           
187            @note: Example:
188                ch = EpicsPv(test_pv)
189                ch.connectw()
190                uargs = test_pv, widget.SetLabel
191                ch.SetUserArgs(uargs)
192                ch.SetUserCallback(myCallback)
193                ch.monitor()
194           
195            @warning: At this time, there is not an easy way to turn off monitors.
196                Instead, ch.release() the channel (which will set self.chan = None),
197                To re-start a monitor after a ch.release(), connect as usual and start
198                the monitor again, as the first time.
199        '''
200        if IMPORTED_CACHANNEL and self.chan != None:
201            type = CaChannel.ca.dbf_type_to_DBR_GR(self.chan.field_type())
202            # call supplied callback routine with default argument list
203            #       self.user_callback(epics_args, user_args)
204            self.chan.add_masked_array_event(type, 
205                None, self.mask, self.callback, self.user_args)
206
207    def GetPv(self):
208        '''@return: PV name'''
209        return self.pv
210
211    def GetValue(self):
212        '''@return: value from EPICS from the most recent monitor event'''
213        return self.value
214
215    def SetPv(self, name):
216        '''redefine the PV name only if there is no connection
217            @param name: valid EPICS PV name'''
218        if self.chan == None:
219            self.pv = name
220
221    def GetChan(self):
222        '''@return: CaChannel channel'''
223        return self.chan
224
225    def GetEpicsArgs(self):
226        '''@return: epics_args from the most recent monitor event'''
227        return self.epics_args
228
229    def GetUserArgs(self):
230        '''@return: user_args from the most recent monitor event'''
231        return self.user_args
232
233    def SetUserArgs(self, user_args):
234        '''define the user_args tuple to use when monitoring
235            @param user_args: tuple of user data (for use in user_callback function)'''
236        self.user_args = user_args
237
238    def SetMask(self, mask):
239        '''Define the mask used when applying a channel monitor.
240            The default is: self.mask = CaChannel.ca.DBE_VALUE
241            @param mask: as defined in the CaChannel manual'''
242        self.pv = mask
243
244    def GetUserCallback(self):
245        '''return the callback function supplied by the caller
246            values will be set by self.user_callback(value)
247            @return: function object'''
248        return self.user_callback
249
250    def SetUserCallback(self, user_callback):
251        '''Set the callback function supplied by the caller
252            values will be set by self.user_callback(value)
253            @param user_callback: function object'''
254        self.user_callback = user_callback
255
256
257def on_exit(timer = None):
258    '''Exit handler to stop the ca.poll()
259       @param timer: CaPollWx object
260
261       Call this to cleanup when program is exiting.
262       ONLY call this function during a program's exit handling.
263       If ca.task_exit() is not called, then expect to see the errors:
264            FATAL: exception not rethrown
265            Abort
266    '''
267    if IMPORTED_CACHANNEL:     # only if ca binding was loaded
268        CaChannel.ca.task_exit()
269    try:        # fail no matter what
270        if timer != None:
271            timer.stop()
272    except:
273        pass
274
275
276def CaPoll():
277    '''Use in non-GUI scripts to call ca.poll() in the background'''
278    if IMPORTED_CACHANNEL:
279        CaChannel.ca.poll()
280
281
282def GetRTYP(pv):
283    '''Returns the record type of "pv"
284       @param pv:[string]
285       @return: [string] EPICS record type or None if cannot connect'''
286    if not IMPORTED_CACHANNEL:
287        return None
288    if len(pv) == 0:
289        return None
290    base = pv.split('.')[0]
291    if testConnect(base):
292        rtyp_pv = base + '.RTYP'
293        ch = EpicsPv(rtyp_pv)
294        ch.connectw()
295        rtyp = ch.chan.getw()
296        del ch
297        return rtyp
298    else:
299        return None
300
301
302def testConnect(pv):
303    '''Tests if a CaChannel connection can be established to "pv"
304       @param pv:[string]
305       @return: True if can connect, otherwise False'''
306    result = False
307    if not IMPORTED_CACHANNEL:
308        return result
309    try:
310        #print 'testConnect:', type(pv), pv
311        chan = CaChannel.CaChannel()
312        chan.searchw(str(pv))
313        val = chan.getw()
314        del chan
315        result = True
316    except (TypeError, CaChannel.CaChannelException), status:
317        # python3: (TypeError, CaChannel.CaChannelException) as status
318        #print 'testConnect:', status
319        pass
320    return result
321
322
323def receiver(epics_args, user_args):
324    '''Example response to an EPICS monitor on the channel
325       @param value: str(epics_args['pv_value'])'''
326    value = epics_args['pv_value']
327    print 'receiver', 'updated value:', str(value)
328
329
330if __name__ == '__main__':
331    if IMPORTED_CACHANNEL:
332        test_pv = 'S:SRcurrentAI'
333        if testConnect(test_pv):
334            print "recordType(%s) = %s" % (test_pv, GetRTYP(test_pv))
335            ch = EpicsPv(test_pv)
336            ch.connectw()
337            ch.SetUserCallback(receiver)
338            ch.monitor()
339            ch.chan.pend_event()
340            import time
341            count = 5
342            for seconds in range(count):
343                time.sleep(1)
344                ch.chan.pend_event()
345                print count - seconds - 1, ch.GetPv(), '=', ch.GetValue()
346            ch.release()
347            on_exit()
348    else:
349        print "CaChannel is missing, cannot run"
Note: See TracBrowser for help on using the repository browser.