From ed23ce2b48957b767c11c8d4043990dc6caf80f2 Mon Sep 17 00:00:00 2001 From: NaruX Date: Sun, 26 Feb 2017 18:36:34 +0100 Subject: [PATCH] simple log viewer --- revpipycontrol.e4p | 108 +++++++++++++- revpipycontrol/revpicheckclient.py | 230 +++++++++++++++++++++++++++++ revpipycontrol/revpipycontrol.py | 80 ++++++++-- 3 files changed, 406 insertions(+), 12 deletions(-) create mode 100644 revpipycontrol/revpicheckclient.py diff --git a/revpipycontrol.e4p b/revpipycontrol.e4p index 0820acc..b20eb50 100644 --- a/revpipycontrol.e4p +++ b/revpipycontrol.e4p @@ -1,7 +1,7 @@ - + en_US @@ -16,6 +16,7 @@ revpipycontrol/__init__.py revpipycontrol/revpipycontrol.py + revpipycontrol/revpicheckclient.py @@ -23,7 +24,110 @@ - None + Mercurial + + + + add + + + + + + + + checkout + + + + + + + + commit + + + + + + + + diff + + + + + + + + export + + + + + + + + global + + + + + + + + history + + + + + + + + log + + + + + + + + remove + + + + + + + + status + + + + + + + + tag + + + + + + + + update + + + + + + + + + + + diff --git a/revpipycontrol/revpicheckclient.py b/revpipycontrol/revpicheckclient.py new file mode 100644 index 0000000..3076f1d --- /dev/null +++ b/revpipycontrol/revpicheckclient.py @@ -0,0 +1,230 @@ +# Thranks to: http://stackoverflow.com/questions/3085696/adding-a-scrollbar-to-a-group-of-widgets-in-tkinter + +import pickle +import tkinter +from argparse import ArgumentParser +from concurrent.futures import ThreadPoolExecutor +from time import sleep +from xmlrpc.client import ServerProxy, Binary, MultiCall + + +class RevPiCheckClient(tkinter.Frame): + + def __init__(self, master, xmlcli=None): + """Instantiiert MyApp-Klasse.""" + super().__init__(master) + self.pack(fill="both", expand=True) + + # Command arguments + parser = ArgumentParser( + description="Revolution Pi IO-Client" + ) + parser.add_argument( + "-a", "--address", dest="address", default="127.0.0.1", + help="Server address (Default: 127.0.0.1)" + ) + parser.add_argument( + "-p", "--port", dest="port", type=int, default=55074, + help="Use port to connect to server (Default: 55074)" + ) + self.pargs = parser.parse_args() + + self.cli = xmlcli if xmlcli is not None else ServerProxy( + "http://{}:{}".format(self.pargs.address, self.pargs.port) + ) + self.lst_devices = self.cli.get_devicenames() + self.lst_group = [] + self.dict_inpvar = {} + self.dict_outvar = {} + + self.autorw = tkinter.BooleanVar() + self.fut_autorw = None + + # Fenster aufbauen + self._createwidgets() + + # Aktuelle Werte einlesen + self.readvalues() + + def _autorw(self): + dict_inp = {} + dict_out = {} + + while self.autorw.get(): + for dev in self.lst_devices: + try: + dict_out[dev] = [ + value[8].get() for value in self.dict_outvar[dev] + ] + except: + print("lasse {} aus".format(dev)) + + dict_inp = self.cli.refreshvalues( + Binary(pickle.dumps(dict_out, 3)) + ) + dict_inp = pickle.loads(dict_inp.data) + + for dev in dict_inp: + for io in self.dict_inpvar[dev]: + try: + io[8].set(dict_inp[dev].pop(0)) + except: + print("lasse {} aus".format(io[0])) + + sleep(0.1) + + def onFrameConfigure(self, canvas): + canvas.configure(scrollregion=canvas.bbox("all")) + + def _createiogroup(self, device, frame, iotype): + """Erstellt IO-Gruppen.""" + # IOs generieren + canvas = tkinter.Canvas(frame, borderwidth=0, width=180, heigh=800) + s_frame = tkinter.Frame(canvas) + vsb = tkinter.Scrollbar(frame, orient="vertical", command=canvas.yview) + canvas.configure(yscrollcommand=vsb.set) + + vsb.pack(side="right", fill="y") + canvas.pack(side="left", fill="both", expand=True) + + canvas.create_window((4, 4), window=s_frame, anchor="nw") + s_frame.bind("", lambda event, canvas=canvas: self.onFrameConfigure(canvas)) + + rowcount = 0 + for io in self.cli.get_iolist(device, iotype): + # io = [name,default,anzbits,adressbyte,export,adressid,bmk,bitaddress,tkinter_var] + + tkinter.Label(s_frame, text=io[0]).grid(column=0, row=rowcount, sticky="w") + + if io[7] >= 0: + var = tkinter.BooleanVar() + check = tkinter.Checkbutton(s_frame) + check["state"] = "disabled" if iotype == "inp" else "normal" + check["text"] = "" + check["variable"] = var + check.grid(column=1, row=rowcount) + #check.pack(anchor="e", side="right") + else: + var = tkinter.IntVar() + #txt = tkinter.Spinbox(fra_group, to=256) + txt = tkinter.Spinbox(s_frame, to=256) + txt["state"] = "disabled" if iotype == "inp" else "normal" + txt["width"] = 4 + txt["textvariable"] = var + txt.grid(column=1, row=rowcount) + #txt.pack(anchor="e", side="right") + + # Steuerelementvariable in IO übernehmen + io.append(var) + if iotype == "inp": + self.dict_inpvar[device].append(io) + elif iotype == "out": + self.dict_outvar[device].append(io) + + rowcount += 1 + + def _createwidgets(self): + """Erstellt den Fensterinhalt.""" + # Hauptfenster + self.master.wm_title("RevPi Onlineview") + + for dev in self.lst_devices: + # Variablen vorbereiten + self.dict_inpvar[dev] = [] + self.dict_outvar[dev] = [] + + # Devicegruppe erstellen + group = tkinter.LabelFrame(self) + group["text"] = dev + group.pack(side="left", fill="both", expand=True) + self.lst_group.append(group) + + for iotype in ["inp", "out"]: + frame = tkinter.Frame(group) + frame.pack(side="left", fill="both", expand=True) + self._createiogroup(dev, frame, iotype) + +# self.btn_update = tkinter.Button(self) +# self.btn_update["text"] = "UPDATE" +# self.btn_update["command"] = self._autorw +# self.btn_update.pack(anchor="s", side="bottom", fill="x") + + self.btn_write = tkinter.Button(self) + self.btn_write["text"] = "SCHREIBEN" + self.btn_write["command"] = self.writevalues + self.btn_write.pack(side="bottom", fill="x") + + self.btn_read = tkinter.Button(self) + self.btn_read["text"] = "LESEN" + self.btn_read["command"] = self.readvalues + self.btn_read.pack(side="bottom", fill="x") + + check = tkinter.Checkbutton(self) + check["command"] = self.toggleauto + check["text"] = "autoupdate" + check["variable"] = self.autorw + check.pack(side="bottom") + + def _readvaluesdev(self, device, iotype): + """Ruft alle aktuellen Werte fuer das Device ab.""" + # Multicall vorbereiten + mc_values = MultiCall(self.cli) + + if iotype == "inp": + lst_ios = self.dict_inpvar[device] + elif iotype == "out": + lst_ios = self.dict_outvar[device] + + for io in lst_ios: + mc_values.get_iovalue(device, io[0]) + + i = 0 + for value in mc_values(): + value = pickle.loads(value.data) + if type(value) == bytes: + value = int.from_bytes(value, byteorder="little") + + lst_ios[i][8].set(value) + i += 1 + + def _writevaluesdev(self, device): + """Sendet Werte der Outputs fuer ein Device.""" + # Multicall vorbereiten + mc_values = MultiCall(self.cli) + lst_ios = lst_ios = self.dict_outvar[device] + + for io in lst_ios: + mc_values.set_iovalue(device, io[0], pickle.dumps(io[8].get(), 3)) + + # Multicall ausführen + mc_values() + + def readvalues(self): + """Alle Werte der Inputs und Outputs abrufen.""" + # Werte aus Prozessabbild einlesen + self.cli.readprocimg() + + for dev in self.lst_devices: + self._readvaluesdev(dev, "inp") + self._readvaluesdev(dev, "out") + + def toggleauto(self): + self.btn_read["state"] = "disabled" if self.autorw.get() else "normal" + self.btn_write["state"] = "disabled" if self.autorw.get() else "normal" + if self.autorw.get() and (self.fut_autorw is None or self.fut_autorw.done()): + e = ThreadPoolExecutor(max_workers=1) + self.fut_autorw = e.submit(self._autorw) + + def writevalues(self): + """Alle Outputs senden.""" + pass + #for dev in self.lst_devices: + #self._writevaluesdev(dev) + + # Werte in Prozessabbild schreiben + #self.cli.writeprocimg() + +if __name__ == "__main__": + root = tkinter.Tk() + myapp = RevPiCheckClient(root) + myapp.mainloop() diff --git a/revpipycontrol/revpipycontrol.py b/revpipycontrol/revpipycontrol.py index f8a7d8c..34dd221 100644 --- a/revpipycontrol/revpipycontrol.py +++ b/revpipycontrol/revpipycontrol.py @@ -3,11 +3,53 @@ # (c) Sven Sager, License: GPLv3 # # -*- coding: utf-8 -*- +import revpicheckclient import tkinter from argparse import ArgumentParser from concurrent.futures import ThreadPoolExecutor from xmlrpc.client import ServerProxy, Binary + +class RevPiPyLogs(tkinter.Frame): + + def __init__(self, master, xmlcli): + super().__init__(master) + self.pack(fill="both", expand=True) + self.xmlcli = xmlcli + self._createwidgets() + + def _createwidgets(self): + self.master.wm_title("RevPi Python PLC Logs") + + # PLC Log + self.plclog = tkinter.Text(self) + self.plcscr = tkinter.Scrollbar(self) + self.plclog.pack(side="left", expand=True, fill="both") + self.plcscr.pack(side="left", fill="y") + self.plclog["yscrollcommand"] = self.plcscr.set + self.plcscr["command"] = self.plclog.yview + + # APP Log + self.applog = tkinter.Text(self) + self.appscr = tkinter.Scrollbar(self) + self.appscr.pack(side="right", fill="y") + self.applog.pack(side="right", expand=True, fill="both") + self.applog["yscrollcommand"] = self.appscr.set + self.appscr["command"] = self.applog.yview + + self.get_applog() + self.get_plclog() + + def get_applog(self): + self.applog.delete(1.0, tkinter.END) + self.applog.insert(1.0, self.xmlcli.get_applog()) + self.applog.see(tkinter.END) + + def get_plclog(self): + self.plclog.delete(1.0, tkinter.END) + self.plclog.insert(1.0, self.xmlcli.get_plclog()) + self.plclog.see(tkinter.END) + class RevPiPyControl(tkinter.Frame): def __init__(self, master=None): @@ -34,7 +76,7 @@ class RevPiPyControl(tkinter.Frame): # Fenster aufbauen self._createwidgets() - + # Daten aktualisieren self.plcrunning() @@ -42,44 +84,62 @@ class RevPiPyControl(tkinter.Frame): """Erstellt den Fensterinhalt.""" # Hauptfenster self.master.wm_title("RevPi Python PLC Loader") - + self.var_status = tkinter.StringVar() self.txt_status = tkinter.Entry() self.txt_status["textvariable"] = self.var_status self.txt_status.pack(fill="x") - + self.btn_plcrunning = tkinter.Button(self) self.btn_plcrunning["text"] = "PLC Status" self.btn_plcrunning["command"] = self.plcrunning self.btn_plcrunning.pack(fill="x") - + self.btn_plcstart = tkinter.Button(self) self.btn_plcstart["text"] = "PLC Start" self.btn_plcstart["command"] = self.plcstart self.btn_plcstart.pack(fill="x") - + self.btn_plcstop = tkinter.Button(self) self.btn_plcstop["text"] = "PLC Stop" self.btn_plcstop["command"] = self.plcstop self.btn_plcstop.pack(fill="x") - + self.btn_plcrestart = tkinter.Button(self) self.btn_plcrestart["text"] = "PLC Restart" self.btn_plcrestart["command"] = self.plcrestart self.btn_plcrestart.pack(fill="x") - def plcstart(self): +# self.btn_plcrestart = tkinter.Button(self) +# self.btn_plcrestart["text"] = "PLC Monitor" +# self.btn_plcrestart["command"] = self.plcmonitor +# self.btn_plcrestart.pack(fill="x") + + self.btn_plcrestart = tkinter.Button(self) + self.btn_plcrestart["text"] = "PLC Logs" + self.btn_plcrestart["command"] = self.plclogs + self.btn_plcrestart.pack(fill="x") + + def plclogs(self): + root = tkinter.Tk() + self.tklogs = RevPiPyLogs(root, self.cli) + + def plcmonitor(self): + root = tkinter.Tk() + self.tkmonitor = revpicheckclient.RevPiCheckClient(root, self.cli) + + def plcstart (self): self.cli.plcstart() self.plcrunning() - + def plcstop(self): self.cli.plcstop() self.plcrunning() - + def plcrestart(self): self.cli.plcrestart() self.plcrunning() - + def plcrunning(self): if self.cli.plcrunning(): self.btn_plcrunning["activebackground"] = "green"