source: epics2xml/epics2xml.py @ 1443

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

Fixes #2: initial version ready for testing, probably needs better error handling

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Rev Date Author Id Url
File size: 10.5 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'''@note: watch EPICS process variables and write them to an XML file
13
14@version:
15########### SVN repository information ###################
16# $Date: 2009-11-06 19:15:10 +0000 (Fri, 06 Nov 2009) $
17# $Author: jemian $
18# $Revision: 85 $
19# $URL$
20# $Id: epics2xml.py 85 2009-11-06 19:15:10Z jemian $
21########### SVN repository information ###################
22
23@note: for help with xml.dom, see http://docs.python.org/library/xml.dom.html
24'''
25
26
27from xml.dom import minidom
28import datetime
29#import copy
30import pvConnect
31
32
33class Epics2Xml:
34    '''watch PVs and write to XMl'''
35
36    def __init__(self, configFile=None):
37        '''prepare the settings file
38            @param configFile: [string] name of XML file with settings'''
39        self.rootElementConfig = 'epics2xml'
40        self.rootElementPvdata = 'pvdata'
41        self.Clear()
42        self.SetConfigFile(configFile)
43
44    def GetDb(self):
45        '''@return: database'''
46        return self.db
47
48    def GetConfigFile(self):
49        '''@return: name of XML configuration file'''
50        return self.configFile
51
52    def SetConfigFile(self, thefile):
53        '''set the name of XML configuration file
54            @param thefile: [string] name of XML file with the configuration'''
55        self.configFile = thefile
56
57    def Clear(self):
58        '''reset the internal data representations (db, pv) to empty'''
59        self.db = {}
60        self.pv = {}
61        self.newData = False
62
63    def ReadConfigFile(self):
64        '''read the configuration from an XML file into an internal dictionary (self.db)
65
66            @note: this method uses xml.dom.minidom (built into all Pythons)
67            @see: http://docs.python.org/library/xml.dom.minidom.html
68        '''
69        try:
70            doc = minidom.parse(self.configFile) # parse an XML file by name
71            assert doc.documentElement.tagName == self.rootElementConfig
72        except IOError:
73            return 'Could not read the XML file: ' + self.configFile
74        except AssertionError:
75            return 'XML root element is not ' + self.rootElementConfig
76        #... read all attributes from the root element
77        docElem = doc.documentElement
78        self.Clear()
79        version = self._get_attribute(docElem, "version", "not-specified")
80        try:
81            # only handle v1.0 resource configuration files
82            assert version == u'1.0'
83        except AssertionError:
84            doc.unlink()
85            return 'Cannot handle file version:', version
86        # file verified now
87        self.db['title'] = self._getFirstChildData(docElem, "title", "default title")
88        tfNode = docElem.getElementsByTagName("targetFile")[0]
89        tf_db = {}
90        tf_db['name'] = self._get_attribute(tfNode, "name", "").strip()
91        tf_db['refreshMinimum'] = self._getFirstChildData(tfNode, "refreshMinimum", "60")
92        tf_db['stylesheet'] = self._getFirstChildData(tfNode, "stylesheet", "basic-table.xsl")
93        # refreshMinimum
94        self.db['targetFile'] = tf_db
95        pvlist_db = []
96        for elementNode in docElem.getElementsByTagName("pv"):
97            pv_db = {}
98            pv_db['name'] = self._get_attribute(elementNode, "name", "").strip()
99            pv_db['rate'] = self._get_attribute(elementNode, "rate", "monitor").strip()
100            pv_db['description'] = self._getFirstChildData(elementNode, "description", "")
101            pvlist_db.append(pv_db)
102        self.db['pvList'] = pvlist_db
103
104    def _getFirstChildData(self, parent, tag, default):
105        '''get the full content of the node or return the default
106            @param parent: XML Node object
107            @param tag: [string] name of element to find
108            @param default: [string] default value to return'''
109        str = default
110        # need some error checking here
111        nodeList = parent.getElementsByTagName(tag)
112        node = nodeList[0]
113        child = node.firstChild
114        data = child.data
115        str = data.strip()
116        return str
117
118    def _get_attribute(self, node, key, default):
119        '''get a specific attribute or return the default
120            @param node: XML Node object
121            @param key: [string] name of attribute to find
122            @param default: [string] default value to return'''
123        value = default
124        if node.attributes.has_key(key):
125            value = node.attributes[key].value
126        return value
127
128    def SaveXmlFile(self):
129        '''save the internal data (self.__repr__()) to the target XML file'''
130        out = open(self.db['targetFile']['name'], 'w')
131        out.write(repr(self))
132        out.close()
133        return 'Saved settings to ' + self.configFile
134
135    def _SetAttr(self, node, attribute, value):
136        '''add attributes that are not empty (but do not strip the whitespace)
137            @param node: XML Node object
138            @param attribute: [string] name of attribute
139            @param value: [string] value of attribute'''
140        if len(value) > 0:
141            node.setAttribute(attribute, value)
142
143    def _makeTextNode(self, doc, tag, value):
144        '''create a text node for the XML file
145            @param doc: [xml.dom.minidom documentElement object]
146            @param tag: [string] element name
147            @param value: [string] element text'''
148        node = doc.createElement(tag)
149        text = doc.createTextNode(value)
150        node.appendChild(text)
151        return node
152
153    def _getDateTime(self):
154        '''return current date and time as a tuple
155            @return: yyyy-mm-dd hh:mm:ss
156        '''
157        t = datetime.datetime.now()
158        yyyymmdd = t.strftime("%Y-%m-%d")
159        hhmmss = t.strftime("%H:%M:%S")
160        return yyyymmdd, hhmmss
161
162    def getPvKey(self, name, key, default):
163        '''get the internal database representation of the PV key
164            or the default
165            @param name: EPICS PV name
166            @param key: value | date | time
167            @param default: use if key not defined'''
168        if self.pv.has_key(name):
169            pv = self.pv[name]
170            if pv.has_key(key):
171                content = pv[key]
172            else:
173                content = default
174        else:
175            content = default
176        return content
177
178    def setPvKey(self, name, key, value):
179        '''set the internal database representation of the PV key
180            @param name: EPICS PV name
181            @param key: value | date | time
182            @param value: new content from this EPICS PV
183        '''
184        if not self.pv.has_key(name):
185            self.pv[name] = {}
186        pv = self.pv[name]
187        pv[key] = value
188        self.newData = True
189
190    def setPv(self, name, value):
191        '''save this PV now
192            @param name: EPICS PV name
193            @param value: new content from this EPICS PV
194        '''
195        yyyymmdd, hhmmss = self._getDateTime()
196        self.setPvKey(name, 'value', value)
197        self.setPvKey(name, 'date', yyyymmdd)
198        self.setPvKey(name, 'time', hhmmss)
199
200    def __repr__(self):
201        '''default representation of this structure is XML
202            @return: XML representation of internal database (db)
203            @note: What about a default stylesheet?
204        '''
205        # Create the minidom document
206        doc = minidom.Document()
207
208        # Use a stylesheet
209        target = "xml-stylesheet"
210        instructions = 'type="text/xsl" href="%s" ' % (self.db['targetFile']['stylesheet'])
211        #
212        xsltNode = doc.createProcessingInstruction(target, instructions)
213        doc.appendChild(xsltNode)
214
215        # Create the root element
216        root = doc.createElement(self.rootElementPvdata)
217        self._SetAttr(root, "version", "1.0")
218        yyyymmdd, hhmmss = self._getDateTime()
219        self._SetAttr(root, "date", yyyymmdd)
220        self._SetAttr(root, "time", hhmmss)
221        doc.appendChild(root)
222        titleNode = self._makeTextNode(doc, "title", self.db['title'])
223        root.appendChild(titleNode)
224        for pv in self.db['pvList']:
225            pvNode = doc.createElement('pv')
226            name = pv['name']
227            pv_value = self.getPvKey(name, "value", "")
228            pv_date = self.getPvKey(name, "date", "")
229            pv_time = self.getPvKey(name, "time", "")
230            self._SetAttr(pvNode, "name", pv['name'])
231            self._SetAttr(pvNode, "rate", pv['rate'])
232            self._SetAttr(pvNode, "date", pv_date)
233            self._SetAttr(pvNode, "time", pv_time)
234            pvNode.appendChild(self._makeTextNode(doc, "description", pv['description']))
235            pvNode.appendChild(self._makeTextNode(doc, "value", pv_value))
236            root.appendChild(pvNode)
237        return doc.toprettyxml(indent="  ")
238
239    def receiver(self, epics_args, user_args):
240        '''Response to an EPICS monitor on the channel.  Expects to have the PV name as the first user_args.
241           @param value: str(epics_args['pv_value'])'''
242        value = epics_args['pv_value']
243        pv = user_args[0]
244        self.setPv(pv, str(value))
245        #print 'receiver: ', pv, ' = ', str(value)
246
247
248if __name__ == '__main__':
249    cf = Epics2Xml("config.xml")
250    cf.ReadConfigFile()
251    # connect with EPICS here and start waiting for events
252    if pvConnect.IMPORTED_CACHANNEL:
253        for item in cf.db['pvList']:
254            pv = item['name']
255            if pvConnect.testConnect(pv):
256                #print pv
257                ch = pvConnect.EpicsPv(pv)
258                ch.SetUserArgs(pv)
259                ch.connectw()
260                ch.SetUserCallback(cf.receiver)
261                ch.monitor()
262                ch.chan.pend_event()
263        import time
264        while True:
265            # wait for the minimum refresh period
266            refreshSeconds = int(cf.db['targetFile'] ['refreshMinimum'])
267            for seconds in range(refreshSeconds):
268                time.sleep(1)
269                ch.chan.pend_event()
270            # write only if there is new data
271            if cf.newData:
272                #print cf.__repr__()
273                cf.SaveXmlFile()
274        # not likely to get here
275        pvConnect.on_exit()
276    else:
277        #----------------------------
278        # load some artificial data to test
279        cf.setPv("S:SRcurrent:AI", '99.9853')
280        cf.setPv("APS:BarometricPressure:MBR", '992.083')
281        print cf.__repr__()
282    #----------------------------
Note: See TracBrowser for help on using the repository browser.