diff --git a/.hgignore b/.hgignore index 2c9154d..86cdd27 100644 --- a/.hgignore +++ b/.hgignore @@ -1,2 +1,5 @@ syntax: glob *.pyc +deb_dist/* +dist/* +revpipycontrol.egg-info/* diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..f1b5621 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,4 @@ +include MANIFEST.in +recursive-include data * +recursive-include revpipycontrol * +global-exclude *.pyc diff --git a/data/revpipycontrol b/data/revpipycontrol new file mode 100644 index 0000000..6d09afb --- /dev/null +++ b/data/revpipycontrol @@ -0,0 +1,3 @@ +#!/bin/sh + +exec "/usr/share/revpipycontrol/revpipycontrol.py" "$@" diff --git a/data/revpipycontrol.desktop b/data/revpipycontrol.desktop new file mode 100644 index 0000000..de2ffd2 --- /dev/null +++ b/data/revpipycontrol.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Name=RevPi PLC Control +Comment=Controls the Python PLC program on your RevolutionPI +Name[de]=RevPi PLC Steuerung +Comment[de]=Kontrolliert das Python PLC Programm auf dem RevolutionPI +Exec=/usr/bin/revpipycontrol +Icon=revpipycontrol +Terminal=false +Type=Application +Categories=Application; +#StartupNotify=true \ No newline at end of file diff --git a/revpipycontrol/revpicheckclient.py b/revpipycontrol/revpicheckclient.py index 010adb7..1076251 100644 --- a/revpipycontrol/revpicheckclient.py +++ b/revpipycontrol/revpicheckclient.py @@ -1,4 +1,11 @@ -# Thranks to: http://stackoverflow.com/questions/3085696/adding-a-scrollbar-to-a-group-of-widgets-in-tkinter +# +# RevPiPyControl +# +# Webpage: https://revpimodio.org/revpipyplc/ +# (c) Sven Sager, License: LGPLv3 +# +# Thranks to: http://stackoverflow.com/questions/3085696/adding-a- +# scrollbar-to-a-group-of-widgets-in-tkinter import pickle import tkinter @@ -10,28 +17,13 @@ from xmlrpc.client import ServerProxy, Binary, MultiCall class RevPiCheckClient(tkinter.Frame): - def __init__(self, master, xmlcli=None): + def __init__(self, master, xmlcli): """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 - 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 = {} diff --git a/revpipycontrol/revpioption.py b/revpipycontrol/revpioption.py index a175d55..567cbca 100644 --- a/revpipycontrol/revpioption.py +++ b/revpipycontrol/revpioption.py @@ -47,38 +47,51 @@ class RevPiOption(tkinter.Frame): ckb_reload["variable"] = self.var_reload ckb_reload.grid(**cpadw) ckb_zexit = tkinter.Checkbutton(stst, justify="left") - ckb_zexit["text"] = "Prozessabbild auf NULL setzen, wenn " - "Programm\nerfolgreich beendet wird" + ckb_zexit["text"] = "Prozessabbild auf NULL setzen, wenn " \ + "Programm\nerfolgreich beendet wird" ckb_zexit["variable"] = self.var_zexit ckb_zexit.grid(**cpadw) ckb_zerr = tkinter.Checkbutton(stst, justify="left") - ckb_zerr["text"] = "Prozessabbild auf NULL setzen, wenn " - "Programm\ndurch Absturz beendet wird" + ckb_zerr["text"] = "Prozessabbild auf NULL setzen, wenn " \ + "Programm\ndurch Absturz beendet wird" ckb_zerr["variable"] = self.var_zerr ckb_zerr.grid(**cpadw) # Gruppe Programm prog = tkinter.LabelFrame(self) - prog["text"] = "Programm" + prog["text"] = "PLC Programm" prog.grid(columnspan=2, pady=2, sticky="we") + self.var_pythonver = tkinter.IntVar(prog) self.var_startpy = tkinter.StringVar(prog) self.var_slave = tkinter.BooleanVar(prog) + + self.var_pythonver.set(3) + lbl = tkinter.Label(prog) + lbl["text"] = "Python Version" + lbl.grid(columnspan=2, row=0, **cpadw) + rbn = tkinter.Radiobutton(prog) + rbn["text"] = "Python2" + rbn["value"] = 2 + rbn["variable"] = self.var_pythonver + rbn.grid(column=0, row=1, **cpadw) + rbn = tkinter.Radiobutton(prog) + rbn["text"] = "Python3" + rbn["value"] = 3 + rbn["variable"] = self.var_pythonver + rbn.grid(column=1, row=1, **cpadw) lbl = tkinter.Label(prog) lbl["text"] = "Python PLC Programname" - lbl.grid(**cpadw) -# txt_startpy = tkinter.Entry(prog) -# txt_startpy["textvariable"] = self.var_startpy -# txt_startpy.grid(**cpadwe) + lbl.grid(columnspan=2, **cpadw) opt_startpy = tkinter.OptionMenu( prog, self.var_startpy, *self.xmlcli.get_filelist()) - opt_startpy.grid(**cpadwe) + opt_startpy.grid(columnspan=2, **cpadwe) ckb_slave = tkinter.Checkbutton(prog, justify="left") ckb_slave["text"] = "RevPi als PLC-Slave verwenden" ckb_slave["state"] = "disabled" ckb_slave["variable"] = self.var_slave - ckb_slave.grid(**cpadw) + ckb_slave.grid(columnspan=2, **cpadw) # Gruppe XMLRPC xmlrpc = tkinter.LabelFrame(self) @@ -86,6 +99,8 @@ class RevPiOption(tkinter.Frame): xmlrpc.grid(columnspan=2, pady=2, sticky="we") self.var_xmlon = tkinter.BooleanVar(xmlrpc) + self.var_xmlmod2 = tkinter.BooleanVar(xmlrpc) + self.var_xmlmod3 = tkinter.BooleanVar(xmlrpc) self.var_xmlport = tkinter.StringVar(xmlrpc) self.var_xmlport.set("55123") @@ -94,6 +109,17 @@ class RevPiOption(tkinter.Frame): ckb_xmlon["text"] = "XML-RPC Server aktiv auf RevPi" ckb_xmlon["variable"] = self.var_xmlon ckb_xmlon.grid(**cpadw) + self.ckb_xmlmod2 = tkinter.Checkbutton(xmlrpc, justify="left") + self.ckb_xmlmod2["command"] = self.xmlmods + self.ckb_xmlmod2["text"] = \ + "Download von piCtory Konfiguration und\nPLC Programm zulassen" + self.ckb_xmlmod2["variable"] = self.var_xmlmod2 + self.ckb_xmlmod2.grid(**cpadw) + self.ckb_xmlmod3 = tkinter.Checkbutton(xmlrpc, justify="left") + self.ckb_xmlmod3["text"] = \ + "Upload von piCtory Konfiguration und\nPLC Programm zualssen" + self.ckb_xmlmod3["variable"] = self.var_xmlmod3 + self.ckb_xmlmod3.grid(**cpadw) lbl = tkinter.Label(xmlrpc) lbl["text"] = "XML-RPC Serverport" lbl.grid(**cpadw) @@ -123,9 +149,13 @@ class RevPiOption(tkinter.Frame): self.var_zerr.set(dc["zeroonerror"]) self.var_startpy.set(dc["plcprogram"]) + self.var_pythonver.set(dc["pythonversion"]) self.var_slave.set(dc["plcslave"]) - self.var_xmlon.set(dc["xmlrpc"]) + self.var_xmlon.set(dc["xmlrpc"] >= 1) + self.var_xmlmod2.set(dc["xmlrpc"] >= 2) + self.var_xmlmod3.set(dc["xmlrpc"] >= 3) + self.var_xmlport.set(dc["xmlrpcport"]) def _setappdata(self): @@ -136,9 +166,17 @@ class RevPiOption(tkinter.Frame): dc["zeroonerror"] = int(self.var_zerr.get()) dc["plcprogram"] = self.var_startpy.get() + dc["pythonversion"] = self.var_pythonver.get() dc["plcslave"] = int(self.var_slave.get()) - dc["xmlrpc"] = int(self.var_xmlon.get()) + dc["xmlrpc"] = 0 + if self.var_xmlon.get(): + dc["xmlrpc"] += 1 + if self.var_xmlmod2.get(): + dc["xmlrpc"] += 1 + if self.var_xmlmod3.get(): + dc["xmlrpc"] += 1 + dc["xmlrpcport"] = self.var_xmlport.get() ask = tkmsg.askyesnocancel( @@ -148,10 +186,16 @@ class RevPiOption(tkinter.Frame): "laufenden PLC-Programms!", parent=self.master ) if ask is not None: - self.xmlcli.set_config(dc, ask) - tkmsg.showinfo( - "Information", "Einstellungen gespeichert.", parent=self.master - ) + if self.xmlcli.set_config(dc, ask): + tkmsg.showinfo( + "Information", "Einstellungen gespeichert.", parent=self.master + ) + else: + tkmsg.showerror( + "Fehler", "Die Einstellungen konnten nicht gesichert" + "werden. Dies kann passieren, wenn Werte falsch sind!", + parent=self.master + ) def askxmlon(self): if not self.var_xmlon.get(): @@ -162,3 +206,11 @@ class RevPiOption(tkinter.Frame): ) if not ask: self.var_xmlon.set(True) + + self.xmlmods() + + def xmlmods(self): + self.ckb_xmlmod2["state"] = \ + "normal" if self.var_xmlon.get() else "disabled" + self.ckb_xmlmod3["state"] = \ + "normal" if self.var_xmlmod2.get() else "disabled" diff --git a/revpipycontrol/revpiplclist.py b/revpipycontrol/revpiplclist.py index 383bd6c..077fe85 100644 --- a/revpipycontrol/revpiplclist.py +++ b/revpipycontrol/revpiplclist.py @@ -25,7 +25,9 @@ def get_connections(): if os.path.exists(savefile): fh = open(savefile, "rb") connections = pickle.load(fh) - return connections + return connections + else: + return {} class RevPiPlcList(tkinter.Frame): diff --git a/revpipycontrol/revpipycontrol.py b/revpipycontrol/revpipycontrol.py old mode 100644 new mode 100755 index 3290c1a..a639527 --- a/revpipycontrol/revpipycontrol.py +++ b/revpipycontrol/revpipycontrol.py @@ -7,19 +7,36 @@ # (c) Sven Sager, License: LGPLv3 # # -*- coding: utf-8 -*- -import revpicheckclient import revpilogfile import revpioption import revpiplclist import revpiprogram import socket +import sys import tkinter import tkinter.messagebox as tkmsg from functools import partial +from os.path import dirname +from os.path import join as pathjoin from xmlrpc.client import ServerProxy socket.setdefaulttimeout(3) +def addroot(filename): + u"""Hängt root-dir der Anwendung vor Dateinamen. + + Je nach Ausführungsart der Anwendung muss das root-dir über + andere Arten abgerufen werden. + + @param filename: Datei oder Ordnername + @returns: root dir + + """ + if getattr(sys, "frozen", False): + return pathjoin(dirname(sys.executable), filename) + else: + return pathjoin(dirname(__file__), filename) + class RevPiPyControl(tkinter.Frame): @@ -48,6 +65,9 @@ class RevPiPyControl(tkinter.Frame): """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 @@ -115,6 +135,7 @@ class RevPiPyControl(tkinter.Frame): ) def _opt_conn(self, text): + socket.setdefaulttimeout(20) sp = ServerProxy( "http://{}:{}".format( self.dict_conn[text][0], int(self.dict_conn[text][1]) @@ -160,7 +181,6 @@ class RevPiPyControl(tkinter.Frame): self.wait_window(win) def plcprogram(self): - # TODO: Programfenster win = tkinter.Toplevel(self) revpiprogram.RevPiProgram(win, self.cli, self.revpiname) win.focus_set() @@ -178,6 +198,7 @@ class RevPiPyControl(tkinter.Frame): self.cli.plcstart() def servererror(self): + socket.setdefaulttimeout(3) self.cli = None self._btnstate() self.mbar.entryconfig("PLC", state="disabled") diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..1fa2b71 --- /dev/null +++ b/setup.py @@ -0,0 +1,90 @@ +#! /usr/bin/env python3 +# +# (c) Sven Sager, License: LGPLv3 +# +# -*- coding: utf-8 -*- +"""Setupscript fuer RevPiPyLoad.""" +import distutils.command.install_egg_info +from sys import platform +from glob import glob + + +class MyEggInfo(distutils.command.install_egg_info.install_egg_info): + + u"""Disable egg_info installation, seems pointless for a non-library.""" + + def run(self): + u"""just pass egg_info.""" + pass + + +globsetup = { + "author": "Sven Sager", + "author_email": "akira@narux.de", + "url": "https://revpimodio.org/revpipyplc/", + "license": "LGPLv3", + "version": "0.2.1", + + "name": "revpipycontrol", + + "description": "PLC Loader für Python-Projekte auf den RevolutionPi", + "long_description": "" + "Dieses Programm startet beim Systemstart ein angegebenes Python PLC\n" + "Programm. Es überwacht das Programm und startet es im Fehlerfall neu.\n" + "Bei Abstruz kann das gesamte /dev/piControl0 auf 0x00 gesettz werden.\n" + "Außerdem stellt es einen XML-RPC Server bereit, über den die Software\n" + "auf den RevPi geladen werden kann. Das Prozessabbild kann über ein Tool\n" + "zur Laufzeit überwacht werden.", +} + +if platform == "linux": + from setuptools import setup + setup( + maintainer="Sven Sager", + maintainer_email="akira@revpimodio.org", + + scripts=["data/revpipycontrol"], + + data_files=[ + ("share/applications", ["data/revpipycontrol.desktop"]), + ("share/icons/hicolor/32x32/apps", ["data/revpipycontrol.png"]), + ("share/revpipycontrol", glob("revpipycontrol/*.*")), + ], + + install_requires=["tkinter"], + + classifiers=[ + "License :: OSI Approved :: " + "GNU Lesser General Public License v3 (LGPLv3)", + "Operating System :: POSIX :: Linux", + ], + cmdclass={"install_egg_info": MyEggInfo}, + **globsetup + ) + +elif platform == "win32": + import sys + from cx_Freeze import setup, Executable + + sys.path.append("revpipycontrol") + + exe = Executable( + script="revpipycontrol/revpipycontrol.py", + base="Win32GUI", + compress=False, + copyDependentFiles=True, + appendScriptToExe=True, + appendScriptToLibrary=False, + icon="data/revpipycontrol.ico" + ) + + setup( + options={"build_exe": { + "include_files": [ + "revpipycontrol/revpipycontrol.png", + # "m4server/locale" + ] + }}, + executables=[exe], + **globsetup + )