[75] | 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 |
---|
[79] | 29 | #import copy |
---|
| 30 | #import pvConnect |
---|
[75] | 31 | |
---|
| 32 | |
---|
[78] | 33 | class Epics2Xml: |
---|
[75] | 34 | '''watch PVs and write to XMl''' |
---|
| 35 | |
---|
[78] | 36 | def __init__(self, configFile=None): |
---|
[75] | 37 | '''prepare the settings file |
---|
[78] | 38 | @param configFile: [string] name of XML file with settings''' |
---|
[81] | 39 | self.rootElementConfig = 'epics2xml' |
---|
| 40 | self.rootElementPvdata = 'pvdata' |
---|
[75] | 41 | self.Clear() |
---|
[78] | 42 | self.SetConfigFile(configFile) |
---|
[75] | 43 | |
---|
| 44 | def GetDb(self): |
---|
| 45 | '''@return: database''' |
---|
| 46 | return self.db |
---|
| 47 | |
---|
[78] | 48 | def GetConfigFile(self): |
---|
[79] | 49 | '''@return: name of XML configuration file''' |
---|
| 50 | return self.configFile |
---|
[75] | 51 | |
---|
[78] | 52 | def SetConfigFile(self, thefile): |
---|
| 53 | '''set the name of XML configuration file |
---|
| 54 | @param thefile: [string] name of XML file with the configuration''' |
---|
[79] | 55 | self.configFile = thefile |
---|
[75] | 56 | |
---|
| 57 | def Clear(self): |
---|
[81] | 58 | '''reset the internal data representations (db, pv) to empty''' |
---|
[75] | 59 | self.db = {} |
---|
[81] | 60 | self.pv = {} |
---|
[75] | 61 | |
---|
[79] | 62 | def ReadConfigFile(self): |
---|
[81] | 63 | '''read the configuration from an XML file into an internal dictionary (self.db) |
---|
[79] | 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 |
---|
[81] | 70 | assert doc.documentElement.tagName == self.rootElementConfig |
---|
[79] | 71 | except IOError: |
---|
| 72 | return 'Could not read the XML file: ' + self.configFile |
---|
| 73 | except AssertionError: |
---|
[81] | 74 | return 'XML root element is not ' + self.rootElementConfig |
---|
[79] | 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 |
---|
[81] | 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 |
---|
[79] | 102 | |
---|
[81] | 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 |
---|
[75] | 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): |
---|
[81] | 128 | '''save the internal data (self.__repr__()) to the target XML file''' |
---|
| 129 | out = open(self.db['targetFile']['name'], 'w') |
---|
[75] | 130 | out.write(repr(self)) |
---|
| 131 | out.close() |
---|
[79] | 132 | return 'Saved settings to ' + self.configFile |
---|
[75] | 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 | |
---|
[81] | 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 | |
---|
[75] | 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 | |
---|
[81] | 206 | # Use a stylesheet |
---|
| 207 | target = "xml-stylesheet" |
---|
[82] | 208 | instructions = 'type="text/xsl" href="%s" ' % (self.db['targetFile']['stylesheet']) |
---|
[81] | 209 | # |
---|
| 210 | xsltNode = doc.createProcessingInstruction(target, instructions) |
---|
| 211 | doc.appendChild(xsltNode) |
---|
| 212 | |
---|
[75] | 213 | # Create the root element |
---|
[81] | 214 | root = doc.createElement(self.rootElementPvdata) |
---|
[75] | 215 | self._SetAttr(root, "version", "1.0") |
---|
[81] | 216 | yyyymmdd, hhmmss = self._getDateTime() |
---|
[75] | 217 | self._SetAttr(root, "date", yyyymmdd) |
---|
| 218 | self._SetAttr(root, "time", hhmmss) |
---|
| 219 | doc.appendChild(root) |
---|
[81] | 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) |
---|
[75] | 235 | return doc.toprettyxml(indent=" ") |
---|
| 236 | |
---|
[83] | 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) |
---|
[75] | 242 | |
---|
[83] | 243 | |
---|
[75] | 244 | if __name__ == '__main__': |
---|
[79] | 245 | cf = Epics2Xml("config.xml") |
---|
| 246 | cf.ReadConfigFile() |
---|
[83] | 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 | #---------------------------- |
---|
[81] | 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__() |
---|
[83] | 256 | #---------------------------- |
---|