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 18:30:04 +0000 (Fri, 06 Nov 2009) $ |
---|
17 | # $Author: jemian $ |
---|
18 | # $Revision: 83 $ |
---|
19 | # $URL$ |
---|
20 | # $Id: epics2xml.py 83 2009-11-06 18:30:04Z 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 | |
---|
27 | from xml.dom import minidom |
---|
28 | import datetime |
---|
29 | #import copy |
---|
30 | #import pvConnect |
---|
31 | |
---|
32 | |
---|
33 | class 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 | |
---|
62 | def ReadConfigFile(self): |
---|
63 | '''read the configuration from an XML file into an internal dictionary (self.db) |
---|
64 | |
---|
65 | @note: this method uses xml.dom.minidom (built into all Pythons) |
---|
66 | @see: http://docs.python.org/library/xml.dom.minidom.html |
---|
67 | ''' |
---|
68 | try: |
---|
69 | doc = minidom.parse(self.configFile) # parse an XML file by name |
---|
70 | assert doc.documentElement.tagName == self.rootElementConfig |
---|
71 | except IOError: |
---|
72 | return 'Could not read the XML file: ' + self.configFile |
---|
73 | except AssertionError: |
---|
74 | return 'XML root element is not ' + self.rootElementConfig |
---|
75 | #... read all attributes from the root element |
---|
76 | docElem = doc.documentElement |
---|
77 | self.Clear() |
---|
78 | version = self._get_attribute(docElem, "version", "not-specified") |
---|
79 | try: |
---|
80 | # only handle v1.0 resource configuration files |
---|
81 | assert version == u'1.0' |
---|
82 | except AssertionError: |
---|
83 | doc.unlink() |
---|
84 | return 'Cannot handle file version:', version |
---|
85 | # file verified now |
---|
86 | self.db['title'] = self._getFirstChildData(docElem, "title", "default title") |
---|
87 | tfNode = docElem.getElementsByTagName("targetFile")[0] |
---|
88 | tf_db = {} |
---|
89 | tf_db['name'] = self._get_attribute(tfNode, "name", "").strip() |
---|
90 | tf_db['refreshMinimum'] = self._getFirstChildData(tfNode, "refreshMinimum", "60") |
---|
91 | tf_db['stylesheet'] = self._getFirstChildData(tfNode, "stylesheet", "basic-table.xsl") |
---|
92 | # refreshMinimum |
---|
93 | self.db['targetFile'] = tf_db |
---|
94 | pvlist_db = [] |
---|
95 | for elementNode in docElem.getElementsByTagName("pv"): |
---|
96 | pv_db = {} |
---|
97 | pv_db['name'] = self._get_attribute(elementNode, "name", "").strip() |
---|
98 | pv_db['rate'] = self._get_attribute(elementNode, "rate", "monitor").strip() |
---|
99 | pv_db['description'] = self._getFirstChildData(elementNode, "description", "") |
---|
100 | pvlist_db.append(pv_db) |
---|
101 | self.db['pvList'] = pvlist_db |
---|
102 | |
---|
103 | def _getFirstChildData(self, parent, tag, default): |
---|
104 | '''get the full content of the node or return the default |
---|
105 | @param parent: XML Node object |
---|
106 | @param tag: [string] name of element to find |
---|
107 | @param default: [string] default value to return''' |
---|
108 | str = default |
---|
109 | # need some error checking here |
---|
110 | nodeList = parent.getElementsByTagName(tag) |
---|
111 | node = nodeList[0] |
---|
112 | child = node.firstChild |
---|
113 | data = child.data |
---|
114 | str = data.strip() |
---|
115 | return str |
---|
116 | |
---|
117 | def _get_attribute(self, node, key, default): |
---|
118 | '''get a specific attribute or return the default |
---|
119 | @param node: XML Node object |
---|
120 | @param key: [string] name of attribute to find |
---|
121 | @param default: [string] default value to return''' |
---|
122 | value = default |
---|
123 | if node.attributes.has_key(key): |
---|
124 | value = node.attributes[key].value |
---|
125 | return value |
---|
126 | |
---|
127 | def SaveXmlFile(self): |
---|
128 | '''save the internal data (self.__repr__()) to the target XML file''' |
---|
129 | out = open(self.db['targetFile']['name'], 'w') |
---|
130 | out.write(repr(self)) |
---|
131 | out.close() |
---|
132 | return 'Saved settings to ' + self.configFile |
---|
133 | |
---|
134 | def _SetAttr(self, node, attribute, value): |
---|
135 | '''add attributes that are not empty (but do not strip the whitespace) |
---|
136 | @param node: XML Node object |
---|
137 | @param attribute: [string] name of attribute |
---|
138 | @param value: [string] value of attribute''' |
---|
139 | if len(value) > 0: |
---|
140 | node.setAttribute(attribute, value) |
---|
141 | |
---|
142 | def _makeTextNode(self, doc, tag, value): |
---|
143 | '''create a text node for the XML file |
---|
144 | @param doc: [xml.dom.minidom documentElement object] |
---|
145 | @param tag: [string] element name |
---|
146 | @param value: [string] element text''' |
---|
147 | node = doc.createElement(tag) |
---|
148 | text = doc.createTextNode(value) |
---|
149 | node.appendChild(text) |
---|
150 | return node |
---|
151 | |
---|
152 | def _getDateTime(self): |
---|
153 | '''return current date and time as a tuple |
---|
154 | @return: yyyy-mm-dd hh:mm:ss |
---|
155 | ''' |
---|
156 | t = datetime.datetime.now() |
---|
157 | yyyymmdd = t.strftime("%Y-%m-%d") |
---|
158 | hhmmss = t.strftime("%H:%M:%S") |
---|
159 | return yyyymmdd, hhmmss |
---|
160 | |
---|
161 | def getPvKey(self, name, key, default): |
---|
162 | '''get the internal database representation of the PV key |
---|
163 | or the default |
---|
164 | @param name: EPICS PV name |
---|
165 | @param key: value | date | time |
---|
166 | @param default: use if key not defined''' |
---|
167 | if self.pv.has_key(name): |
---|
168 | pv = self.pv[name] |
---|
169 | if pv.has_key(key): |
---|
170 | content = pv[key] |
---|
171 | else: |
---|
172 | content = default |
---|
173 | else: |
---|
174 | content = default |
---|
175 | return content |
---|
176 | |
---|
177 | def setPvKey(self, name, key, value): |
---|
178 | '''set the internal database representation of the PV key |
---|
179 | @param name: EPICS PV name |
---|
180 | @param key: value | date | time |
---|
181 | @param value: new content from this EPICS PV |
---|
182 | ''' |
---|
183 | if not self.pv.has_key(name): |
---|
184 | self.pv[name] = {} |
---|
185 | pv = self.pv[name] |
---|
186 | pv[key] = value |
---|
187 | |
---|
188 | def setPv(self, name, value): |
---|
189 | '''save this PV now |
---|
190 | @param name: EPICS PV name |
---|
191 | @param value: new content from this EPICS PV |
---|
192 | ''' |
---|
193 | yyyymmdd, hhmmss = self._getDateTime() |
---|
194 | self.setPvKey(name, 'value', value) |
---|
195 | self.setPvKey(name, 'date', yyyymmdd) |
---|
196 | self.setPvKey(name, 'time', hhmmss) |
---|
197 | |
---|
198 | def __repr__(self): |
---|
199 | '''default representation of this structure is XML |
---|
200 | @return: XML representation of internal database (db) |
---|
201 | @note: What about a default stylesheet? |
---|
202 | ''' |
---|
203 | # Create the minidom document |
---|
204 | doc = minidom.Document() |
---|
205 | |
---|
206 | # Use a stylesheet |
---|
207 | target = "xml-stylesheet" |
---|
208 | instructions = 'type="text/xsl" href="%s" ' % (self.db['targetFile']['stylesheet']) |
---|
209 | # |
---|
210 | xsltNode = doc.createProcessingInstruction(target, instructions) |
---|
211 | doc.appendChild(xsltNode) |
---|
212 | |
---|
213 | # Create the root element |
---|
214 | root = doc.createElement(self.rootElementPvdata) |
---|
215 | self._SetAttr(root, "version", "1.0") |
---|
216 | yyyymmdd, hhmmss = self._getDateTime() |
---|
217 | self._SetAttr(root, "date", yyyymmdd) |
---|
218 | self._SetAttr(root, "time", hhmmss) |
---|
219 | doc.appendChild(root) |
---|
220 | titleNode = self._makeTextNode(doc, "title", self.db['title']) |
---|
221 | root.appendChild(titleNode) |
---|
222 | for pv in self.db['pvList']: |
---|
223 | pvNode = doc.createElement('pv') |
---|
224 | name = pv['name'] |
---|
225 | pv_value = self.getPvKey(name, "value", "") |
---|
226 | pv_date = self.getPvKey(name, "date", "") |
---|
227 | pv_time = self.getPvKey(name, "time", "") |
---|
228 | self._SetAttr(pvNode, "name", pv['name']) |
---|
229 | self._SetAttr(pvNode, "rate", pv['rate']) |
---|
230 | self._SetAttr(pvNode, "date", pv_date) |
---|
231 | self._SetAttr(pvNode, "time", pv_time) |
---|
232 | pvNode.appendChild(self._makeTextNode(doc, "description", pv['description'])) |
---|
233 | pvNode.appendChild(self._makeTextNode(doc, "value", pv_value)) |
---|
234 | root.appendChild(pvNode) |
---|
235 | return doc.toprettyxml(indent=" ") |
---|
236 | |
---|
237 | def receiver(self, epics_args, user_args): |
---|
238 | '''Example response to an EPICS monitor on the channel |
---|
239 | @param value: str(epics_args['pv_value'])''' |
---|
240 | value = epics_args['pv_value'] |
---|
241 | print 'receiver', 'updated value:', str(value) |
---|
242 | |
---|
243 | |
---|
244 | if __name__ == '__main__': |
---|
245 | cf = Epics2Xml("config.xml") |
---|
246 | cf.ReadConfigFile() |
---|
247 | # connect with EPICS here and start waiting for events |
---|
248 | # How to save periodically? |
---|
249 | # How to throttle the output file update rate? |
---|
250 | |
---|
251 | #---------------------------- |
---|
252 | # load some artificial data to test |
---|
253 | cf.setPv("S:SRcurrent:AI", '99.9853') |
---|
254 | cf.setPv("APS:BarometricPressure:MBR", '992.083') |
---|
255 | print cf.__repr__() |
---|
256 | #---------------------------- |
---|