#!/usr/bin/python3 # -*- coding: utf-8 -*- u"""RevPiPyControl main program.""" __author__ = "Sven Sager" __copyright__ = "Copyright (C) 2018 Sven Sager" __license__ = "GPLv3" __version__ = "0.8.0" import revpicheckclient import revpidevelop import revpiinfo import revpilogfile import revpioption import revpilegacy import revpiplclist import revpiprogram import socket import tkinter import tkinter.messagebox as tkmsg import webbrowser from mytools import addroot, gettrans from xmlrpc.client import ServerProxy # Übersetzung laden _ = gettrans() class RevPiPyControl(tkinter.Frame): u"""Baut Hauptprogramm auf.""" def __init__(self, master=None): u"""Init RevPiPyControl-Class. @param master tkinter master""" super().__init__(master) self.master.protocol("WM_DELETE_WINDOW", self._closeapp) self.pack(fill="both", expand=True) self.cli = None self.dict_conn = revpiplclist.get_connections() self.errcount = 0 self.revpiname = None self.revpipyversion = [0, 0, 0] self.xmlfuncs = [] self.xmlmode = 0 # Frames vorbereiten self.debugframe = None self.developframe = None # Globale Fenster self.tkcheckclient = None self.tklogs = None self.tkoptions = None self.tkprogram = None # Fenster aufbauen self._createwidgets() # Daten aktualisieren self.tmr_plcrunning() def _btnstate(self): u"""Setzt den state der Buttons.""" stateval = "disabled" if self.cli is None else "normal" self.btn_plclogs["state"] = stateval self.btn_plcstart["state"] = stateval self.btn_plcstop["state"] = stateval self.btn_plcrestart["state"] = stateval self.btn_debug["state"] = stateval def _closeall(self): u"""Schließt alle Fenster.""" if self.tkcheckclient is not None: self.tkcheckclient.destroy() if self.developframe is not None: self.developframe._checkclose() self.developframe.destroy() self.developframe = None if self.tklogs is not None: self.tklogs.master.destroy() if self.tkoptions is not None: self.tkoptions.destroy() self.tkoptions.master.destroy() if self.tkprogram is not None: self.tkprogram.destroy() if self.debugframe is not None: self.debugframe.destroy() self.debugframe = None try: self.cli.psstop() except Exception: pass def _closeapp(self, event=None): u"""Räumt auf und beendet Programm. @param event tkinter Event""" self._closeall() self.master.destroy() def _createwidgets(self): u"""Erstellt den Fensterinhalt.""" # Hauptfenster self.master.wm_title("RevPi Python PLC Loader") self.master.wm_iconphoto( True, tkinter.PhotoImage(file=addroot("revpipycontrol.png")) ) self.master.wm_resizable(width=False, height=False) # Menü ganz oben self.mbar = tkinter.Menu(self.master) self.master.config(menu=self.mbar) menu1 = tkinter.Menu(self.mbar, tearoff=False) menu1.add_command(label=_("Connections..."), command=self.plclist) menu1.add_separator() menu1.add_command(label=_("Exit"), command=self.master.destroy) self.mbar.add_cascade(label=_("Main"), menu=menu1) self._fillmbar() self._fillconnbar() # Hilfe Menü menu1 = tkinter.Menu(self.mbar, tearoff=False) menu1.add_command( label=_("Visit website..."), command=self.visitwebsite) menu1.add_separator() menu1.add_command(label=_("Info..."), command=self.infowindow) self.mbar.add_cascade(label=_("Help"), menu=menu1) self.main_frame = tkinter.Frame(self) self.main_frame.pack(side="left", fill="y") self.var_conn = tkinter.StringVar(self.main_frame) self.txt_connect = tkinter.Entry( self.main_frame, state="readonly", width=40 ) self.txt_connect["textvariable"] = self.var_conn self.txt_connect.pack(fill="x") self.btn_plcstart = tkinter.Button(self.main_frame) self.btn_plcstart["text"] = _("PLC start") self.btn_plcstart["command"] = self.plcstart self.btn_plcstart.pack(fill="x") self.btn_plcstop = tkinter.Button(self.main_frame) self.btn_plcstop["text"] = _("PLC stop") self.btn_plcstop["command"] = self.plcstop self.btn_plcstop.pack(fill="x") self.btn_plcrestart = tkinter.Button(self.main_frame) self.btn_plcrestart["text"] = _("PLC restart") self.btn_plcrestart["command"] = self.plcrestart self.btn_plcrestart.pack(fill="x") self.btn_plclogs = tkinter.Button(self.main_frame) self.btn_plclogs["text"] = _("PLC logs") self.btn_plclogs["command"] = self.plclogs self.btn_plclogs.pack(fill="x") self.var_status = tkinter.StringVar(self.main_frame) self.txt_status = tkinter.Entry(self.main_frame) self.txt_status["state"] = "readonly" self.txt_status["textvariable"] = self.var_status self.txt_status.pack(fill="x") self.btn_debug = tkinter.Button(self.main_frame) self.btn_debug["text"] = _("PLC watch mode") self.btn_debug["command"] = self.plcdebug self.btn_debug.pack(fill="x") def _fillconnbar(self): u"""Generiert Menüeinträge für Verbindungen.""" self.mconn.delete(0, "end") for con in sorted(self.dict_conn.keys(), key=lambda x: x.lower()): self.mconn.add_command( label=con, command=lambda con=con: self._opt_conn(con) ) def _fillmbar(self): u"""Generiert Menüeinträge.""" # PLC Menü self.mplc = tkinter.Menu(self.mbar, tearoff=False) self.mplc.add_command( label=_("PLC log..."), command=self.plclogs) self.mplc.add_command( label=_("PLC options..."), command=self.plcoptions) self.mplc.add_command( label=_("PLC program..."), command=self.plcprogram) self.mplc.add_command( label=_("PLC developer..."), command=self.plcdevelop) self.mplc.add_separator() self.mplc.add_command( label=_("Disconnect"), command=self.serverdisconnect) self.mbar.add_cascade(label="PLC", menu=self.mplc, state="disabled") # Connection Menü self.mconn = tkinter.Menu(self.mbar, tearoff=False) self.mbar.add_cascade(label=_("Connect"), menu=self.mconn) def _opt_conn(self, text, reconnect=False): u"""Stellt eine neue Verbindung zu RevPiPyLoad her. @param text Verbindungsname @param reconnect Socket Timeout nicht heruntersetzen""" if reconnect: socket.setdefaulttimeout(6) else: socket.setdefaulttimeout(2) sp = ServerProxy( "http://{0}:{1}".format( self.dict_conn[text][0], int(self.dict_conn[text][1]) ) ) # Server prüfen try: self.xmlfuncs = sp.system.listMethods() self.xmlmode = sp.xmlmodus() self.revpipyversion = list(map(int, sp.version().split("."))) except Exception: self.servererror() else: self._closeall() socket.setdefaulttimeout(6) self.cli = ServerProxy( "http://{0}:{1}".format( self.dict_conn[text][0], int(self.dict_conn[text][1]) ) ) self.revpiname = text self.var_conn.set("{0} - {1}:{2}".format( text, self.dict_conn[text][0], int(self.dict_conn[text][1]) )) self.mbar.entryconfig("PLC", state="normal") def infowindow(self): u"""Öffnet das Fenster für die Info.""" win = tkinter.Toplevel(self) win.focus_set() win.grab_set() revpiinfo.RevPiInfo(win, self.cli, __version__) self.wait_window(win) self.dict_conn = revpiplclist.get_connections() self._fillconnbar() def plcdebug(self): u"""Baut den Debugframe und packt ihn. @return None""" self.btn_debug["state"] = "disabled" if "psstart" not in self.xmlfuncs: tkmsg.showwarning( _("Warning"), _("The watch mode ist not supported in version {0} " "of RevPiPyLoad on your RevPi! You need at least version " "0.5.3! Maybe the python3-revpimodio2 module is not " "installed on your RevPi at least version 2.0.0." "").format(self.cli.version()), parent=self.master ) return # FIXME: Bei neuer piCtory Konfig schneller vernichten if self.debugframe is None: try: self.debugframe = revpicheckclient.RevPiCheckClient( self.main_frame, self.cli, self.xmlmode ) except Exception: tkmsg.showwarning( _("Error"), _("Can not load piCtory configuration. \n" "Did you create a hardware configuration? " "Please check this in piCtory!"), parent=self.master ) self.btn_debug["state"] = "normal" return None # Fehler prüfen if self.debugframe.err_workvalues >= self.debugframe.max_errors: self.debugframe = None return None # Show/Hide wechseln if self.debugframe.winfo_viewable(): self.debugframe.hideallwindows() if self.debugframe.autorw.get(): self.debugframe.autorw.set(False) self.debugframe.toggleauto() self.debugframe.dowrite.set(False) self.debugframe.pack_forget() else: self.debugframe.pack(fill="x") self.btn_debug["state"] = "normal" def plcdevelop(self): u"""Startet das Developfenster.""" if self.xmlmode < 3: tkmsg.showwarning( _("Warning"), _("XML-RPC access mode in the RevPiPyLoad " "configuration is too small to access this dialog!"), parent=self.master ) return # Developframe laden if self.developframe is None: self.developframe = revpidevelop.RevPiDevelop( self, self.cli, self.xmlmode, self.revpiname ) if self.developframe.winfo_viewable(): self.developframe._checkclose() self.developframe.pack_forget() else: self.developframe.pack(side="right") # fill="x") def plclist(self): u"""Öffnet das Fenster für die Verbindungen.""" win = tkinter.Toplevel(self) win.focus_set() win.grab_set() revpiplclist.RevPiPlcList(win) self.wait_window(win) self.dict_conn = revpiplclist.get_connections() self._fillconnbar() def plclogs(self): u"""Öffnet das Fenster für Logdateien. @return None""" if "load_plclog" not in self.xmlfuncs: tkmsg.showwarning( _("Warning"), _("This version of Logviewer ist not supported in version {0} " "of RevPiPyLoad on your RevPi! You need at least version " "0.4.1.").format(self.cli.version()), parent=self.master ) return None if self.tklogs is None or len(self.tklogs.children) == 0: win = tkinter.Toplevel(self) self.tklogs = revpilogfile.RevPiLogfile(win, self.cli) else: self.tklogs.focus_set() def plcoptions(self): u"""Startet das Optionsfenster.""" if self.xmlmode < 2: tkmsg.showwarning( _("Warning"), _("XML-RPC access mode in the RevPiPyLoad " "configuration is too small to access this dialog!"), parent=self.master ) else: win = tkinter.Toplevel(self) win.focus_set() win.grab_set() # Gegenstelle prüfen und passende Optionen laden if self.revpipyversion[0] == 0 and self.revpipyversion[1] < 6: self.tkoptions = \ revpilegacy.RevPiOption(win, self.cli) else: self.tkoptions = \ revpioption.RevPiOption(win, self.cli) self.wait_window(win) if self.tkoptions.dc is not None and self.tkoptions.dorestart: # Wenn XML-Modus anders und Dienstneustart if self.xmlmode != self.cli.xmlmodus(): self.serverdisconnect() self._opt_conn(self.revpiname, True) if self.debugframe is not None: self.cli.psstart() def plcprogram(self): u"""Startet das Programmfenster.""" if self.xmlmode < 2: tkmsg.showwarning( _("Warning"), _("XML-RPC access mode in the RevPiPyLoad " "configuration is too small to access this dialog!"), parent=self.master ) else: win = tkinter.Toplevel(self) win.focus_set() win.grab_set() self.tkprogram = revpiprogram.RevPiProgram( win, self.cli, self.xmlmode, self.revpiname) self.wait_window(win) def plcstart(self): u"""Startet das PLC Programm.""" self.cli.plcstart() def plcstop(self): u"""Beendet das PLC Programm.""" self.cli.plcstop() def plcrestart(self): u"""Startet das PLC Programm neu.""" self.cli.plcstop() self.cli.plcstart() def serverdisconnect(self): u"""Trennt eine bestehende Verbindung.""" self._closeall() socket.setdefaulttimeout(2) self.cli = None self._btnstate() self.mbar.entryconfig("PLC", state="disabled") self.var_conn.set("") def servererror(self): u"""Setzt alles zurück für neue Verbindungen.""" self.serverdisconnect() tkmsg.showerror( _("Error"), _("Can not connect to RevPi XML-RPC Service! \n\n" "This could have the following reasons: The RevPi is not " "online, the XML-RPC service is not running or the ACL " "permission is not set for your IP!!!"), parent=self.master ) def tmr_plcrunning(self): u"""Timer der den Status des PLC Programms prüft.""" self._btnstate() if self.cli is None: self.txt_status["readonlybackground"] = "lightblue" self.var_status.set("NOT CONNECTED") else: try: plcec = self.cli.plcexitcode() except Exception: self.errcount += 1 if self.errcount >= 5: self.var_status.set("SERVER ERROR") self.servererror() else: self.errcount = 0 self.txt_status["readonlybackground"] = \ "green" if plcec == -1 else "red" if plcec == -1: plcec = "RUNNING" elif plcec == -2: plcec = "FILE NOT FOUND" elif plcec == -3: plcec = "NOT RUNNING (NO STATUS)" elif plcec == -9: plcec = "PROGRAM KILLED" elif plcec == -15: plcec = "PROGRAM TERMED" elif plcec == 0: plcec = "NOT RUNNING" self.var_status.set(plcec) self.master.after(1000, self.tmr_plcrunning) def visitwebsite(self): u"""Öffnet auf dem System einen Webbrowser zur Projektseite.""" webbrowser.open("https://revpimodio.org") if __name__ == "__main__": root = tkinter.Tk() myapp = RevPiPyControl(root) root.mainloop()