# !/Library/Frameworks/Python.framework/Versions/3.3/bin/python3 # !/usr/bin/env python3 #Copyright 2013 Embedded Data Systems, LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os import xml.etree.ElementTree as ET #from xml.etree.ElementTree import XML,fromstring, tostring import tkinter as tk import datetime import threading #import cProfile import queue from tkinter import * from tkinter import filedialog from tkinter import messagebox import urllib.request def CreateMainWin(master,dftURL,dftEUI,dftTag): # Create GUI global outputDisp,eURL,eEUI,eTag,eTag2,eTag3,eLF,oUnits,bBegin,bLF,omUnits bgnd = '#E0E0E0' frm = tk.Frame(master, borderwidth=10, bg=bgnd) tk.Label(frm, text='Read and parse XML data from an '\ 'OW Server or MeshNet Controller', background=bgnd).grid(row=0, columnspan=5, sticky=W) tk.Button(frm, text='Help', command=Help, background=bgnd, highlightbackground=bgnd).grid(row=0, column=5) tk.Label(frm, text='URL:', background=bgnd).grid(row=1, sticky=W) eURL = tk.Entry(frm, highlightbackground=bgnd) eURL.grid(row=1, column=1, columnspan=5, sticky=EW) eURL.insert(0,dftURL) tk.Label(frm, text='EUI:', background=bgnd).grid(row=2, columnspan=6, sticky=W) eEUI = tk.Entry(frm, highlightbackground=bgnd) eEUI.grid(row=2, column=1, columnspan=5, sticky=EW) eEUI.insert(0,dftEUI) tk.Label(frm, text='Tag1:', background=bgnd).grid(row=3, columnspan=6, sticky=W) eTag = tk.Entry(frm, highlightbackground=bgnd) eTag.grid(row=3, column=1, columnspan=5, sticky=EW) eTag.insert(0,dftTag) tk.Label(frm, text='Tag2:', background=bgnd).grid(row=4, columnspan=6, sticky=W) eTag2 = tk.Entry(frm, highlightbackground=bgnd) eTag2.grid(row=4, column=1, columnspan=5, sticky=EW) tk.Label(frm, text='Tag3:', background=bgnd).grid(row=5, columnspan=6, sticky=W) eTag3 = tk.Entry(frm, highlightbackground=bgnd) eTag3.grid(row=5, column=1, columnspan=5, sticky=EW) tk.Label(frm, text='Log File:', background=bgnd).grid(row=6, columnspan=6, sticky=W) eLF = tk.Entry(frm, highlightbackground=bgnd) eLF.grid(row=6, column=1, columnspan=5, sticky=EW) bLF = tk.Button(frm, text='Select Log File', command=GetFN, background=bgnd, highlightbackground=bgnd) bLF.grid(row=7, column=0, columnspan=2, sticky=W) oUnits = tk.StringVar(frm) oUnits.set("English") omUnits = tk.OptionMenu(frm, oUnits, "English", "Metric") omUnits.grid(row=7, column=2) omUnits.config(bg=bgnd, highlightbackground=bgnd) bBegin = tk.Button(frm, text='Start', command=BeginReading, background=bgnd, highlightbackground=bgnd) bBegin.grid(row=7, column=4, sticky=E) tk.Button(frm, text='Quit', command=lambda master=master:End(master), background=bgnd, highlightbackground=bgnd).grid(row=7, column=5) outputDisp = tk.Text(frm, height=30, background='white', highlightbackground='grey', highlightthickness=1) outputDisp.grid(row=8, column=0, columnspan=6, sticky=N+S+E+W) sb = tk.Scrollbar(frm) sb.grid(row=8, column=6, sticky=N+S) sb.config(command=outputDisp.yview) outputDisp.config(yscrollcommand=sb.set) frm.pack(fill=BOTH, expand=1) master.configure(background='#F0F0F0') master.title("EDS XML Reader - v1.00") master.resizable(0,0) def Help(): # Display help message helpMsg = 'This program reads the XML file from a OW Server or MeshNet controller and displays the information in one of two formats:\n\ A) Using just URL and Tag1, it retrieves and displays 1 field from all the devices in the XML file\n\ B) Using URL, EUI and Tags 1-3, it retrieves and displays from 1 to 3 fields from a single device in the XML file\n\n\ To operate in mode A:\n\ 1) Enter the URL in the URL field (eg. http://192.168.1.61/details.xml)\n\ 2) Enter the Tag in the Tag1 field (eg. Temperature)\n\ 3) Make certain the EUI field has no characters, including spaces\n\ 4) Click the Start button\n\n\ See the readme.pdf file for more information' tk.messagebox.showinfo("Help", helpMsg) def GetFN(): # Get log file name by displaying file dialog box fn = tk.filedialog.asksaveasfilename() if fn != '': eLF.delete(0,END) eLF.insert(0,fn) def WrTextLine(inclTimeStamp, msg): # Write text to the output queue and optionally, if enabled, the log file # Optionally include a timestamp global filenm if inclTimeStamp == True: now = datetime.datetime.now() ts = now.strftime("%Y.%m.%d_%H:%M:%S") os = ts + ',' + msg + '\n' else: os = msg + '\n' wQueue.put(os) # outputDisp.insert(END, os) # outputDisp.yview(END) # # Delete lines when display fills up # while True: # i = outputDisp.index('end') # if float(i) > 200: # outputDisp.delete(3.0, 4.0) # else: # break # Write filename if enabled try: if filenm != '': f=open(filenm, 'a') f.write(os) f.close except: filenm = '' def ConvUnits(val, name): # Convert units between metric and english (imperial) global dunits if dunits == 'Metric': return val elif name == 'Temperature': return (val * 9/5) + 32 elif name == 'BarometricPressure': return (val * 0.0295301) else: return val def End(rt): # End the program global sysQuit sysQuit = True rt.destroy() def ReadDevice(): # Repeatedly read sensors and display information global sysQuit if sysQuit == False: try: XMLData = webGetXMLData(url) doc=ET.fromstring(XMLData) # Get up to 3 different tag values from 1 sensor using its EUI if sysMode > 0: myDevice = xmlGetDeviceByEUI(doc, eui) # Write data from sensors if it exists if myDevice != None: outstr = doc.findtext("./UpTime") if outstr == None: outstr = doc.findtext("./PollCount") outstr = outstr + ',' + str(ConvUnits(float(myDevice.findtext("./" + tag)), tag)) if sysMode > 1: outstr = outstr + ',' + str(ConvUnits(float(myDevice.findtext("./" + tag2)), tag2)) if sysMode > 2: outstr = outstr + ',' + str(ConvUnits(float(myDevice.findtext("./" + tag3)), tag3)) else: # Write empty fields in output file if sensor disappears outstr = ',' if sysMode > 1: outstr = outstr + ',' if sysMode > 2: outstr = outstr + ',' # Get 1 tag value from multiple sensors # read the sensors in the EUI order we saved when first started else: outstr = doc.findtext("./UpTime") if outstr == None: outstr = doc.findtext("./PollCount") for curEUI in EUIList: outstr = outstr + ',' myDevice = xmlGetDeviceByEUI(doc, curEUI) if myDevice != None: myVal = myDevice.findtext("./" + tag) # Print an empty field if tag is not at this EUI if myVal != None: outstr = outstr + str(ConvUnits(float(myVal), tag)) WrTextLine(True, outstr) tmr = threading.Timer(1, ReadDevice) tmr.start() except: WrTextLine(True, " ERROR: Cannot read information") BeginReading() def BeginReading(): # Begin reading. Determine reading type by validity of EUI field, type can be: # 1) Read from 1 to 3 tags from one sensor (requires EUI and at least first tag field to be valid) # 2) Read 1 tag from multiple sensors (requires first tag field to be valid and EUI to be invalid or empty) # If called when running, return to idle state global sysQuit,dunits,tag,tag2,tag3,eui,url,filenm,sysMode,EUIList try: # If we are running, stop and return to idle state if bBegin['text'] == 'Stop': sysQuit = True # Set GUI to idle mode bBegin.configure(text='Start') bLF.configure(state=NORMAL) omUnits.configure(state=NORMAL) eTag.configure(state=NORMAL) eTag2.configure(state=NORMAL) eTag3.configure(state=NORMAL) eLF.configure(state=NORMAL) eEUI.configure(state=NORMAL) eURL.configure(state=NORMAL) return # Save user information in local fields tag = eTag.get() tag2 = eTag2.get() tag3 = eTag3.get() eui = eEUI.get() url = eURL.get() dunits = oUnits.get() filenm = eLF.get() # Verify log file can be opened, on error empty the string so we don't use it try: if filenm != '': with open(filenm, 'a'): pass except: WrTextLine(False, "WARNING: Log file cannot be opened, logging is disabled") filenm = '' # Check if we can get the XML data from the URL try: XMLData = webGetXMLData(url) except: WrTextLine(False, "ERROR: URL cannot be found") raise # Try to read the first tag from the EUI, if it fails try to read the first tag from anything try: tagstr = tag root=ET.fromstring(XMLData) myDevice = xmlGetDeviceByEUI(root, eui) # Display error if the eui field had chars but could not be found in the XML file if myDevice == None and eui != '': raise # If the EUI the user entered gave us data, try reading 1 or more tags from it if myDevice != None: result = myDevice.findtext("./" + tag) if result == []: WrTextLine(False, "ERROR: Tag1 cannot be found") raise sysMode = 1 if len(tag2): tagstr = tagstr + ', ' + tag2 result = myDevice.findtext("./" + tag2) if result == None or (len(result) == 0): WrTextLine(False, "ERROR: Tag2 cannot be found") raise sysMode += 1 if len(tag2) and len(tag3): tagstr = tagstr + ', ' + tag3 result = myDevice.findtext("./" + tag3) if result == None or (len(result) == 0): WrTextLine(False, "ERROR: Tag3 cannot be found") raise sysMode += 1 # The EUI did not give us data, try reading any data that matches the first tag else: result = xmlGetTextByTag(root, tag) if result == []: raise # Get EUI or ROMId list (MeshNet controller or OW Server) result = xmlGetTextByTag(root, "EUI") if result == []: result = xmlGetTextByTag(root, "ROMId") if result == []: raise # Store only EUI's that have the tag of interest EUIList = list() for curEUI in result: myDevice = xmlGetDeviceByEUI(root, curEUI.text) myVal = myDevice.findtext("./" + tag) if myVal != None: EUIList.append(curEUI.text) if len(EUIList) == 0: raise sysMode = 0 except: WrTextLine(False, "ERROR: EUI and/or Tag(s) cannot be found") raise if result is None: WrTextLine(False, "ERROR: Tag cannot be found") else: sysQuit = False if sysMode > 0: # Reading 1 or more tags from one sensor, display header WrTextLine(False, 'Reading ' + eui + ' for ' + tagstr) WrTextLine(False, 'Date/Time, UpTime, ' + tagstr) else: # Reading one tag from 1 or more sensors, display header WrTextLine(False, 'Reading multiple sensors for ' + tagstr) headerStr = 'Date/Time, UpTime' for hs in EUIList: headerStr = headerStr + ', ' + hs WrTextLine(False, headerStr) # Set GUI to run mode bBegin.configure(text='Stop') bLF.configure(state=DISABLED) omUnits.configure(state=DISABLED) eTag.configure(state=DISABLED) eTag2.configure(state=DISABLED) eTag3.configure(state=DISABLED) eLF.configure(state=DISABLED) eEUI.configure(state=DISABLED) eURL.configure(state=DISABLED) ReadDevice() except: pass def ReadQueue(): # Read the output queue in the GUI loop to avoid threading problems while wQueue.empty() == FALSE: os = wQueue.get() outputDisp.insert(END, os) outputDisp.yview(END) # Delete lines when display fills up # while True: # i = outputDisp.index('end') # if float(i) > 200: # outputDisp.delete(3.0, 4.0) # else: # break rt.after(500, ReadQueue) def webGetXMLData(url): "Get the XML data from web appliance" usock = urllib.request.urlopen(url, timeout=3) XMLD = usock.read().decode('utf-8') usock.close() #strip schema information to make code more readable return '' + XMLD[XMLD.find('">')+2:] def xmlGetDeviceByEUI( xmlData, searchEUI): "Get a device XML data with specified EUI or ROMId" for devices in xmlData.findall("./*/[EUI]"): if devices.findtext("./EUI") == searchEUI: return devices for devices in xmlData.findall("./*/[ROMId]"): if devices.findtext("./ROMId") == searchEUI: return devices def xmlGetTextByTag( xmlData, tagName): "Get a list of tags by name in the entire XML doc" return xmlData.findall(".//" + tagName) def main(): # Program starts here global rt,wQueue,url,eui,tag,dunits,filenm # Defaults url = 'http://192.168.1.17/details.xml' eui = '' tag = 'Temperature' wQueue = queue.Queue() rt = tk.Tk() CreateMainWin(rt,url,eui,tag) rt.after(100,ReadQueue) rt.mainloop() sysQuit = True sys.exit(0) if __name__ == "__main__": main() # cProfile.run('main()')