diff --git a/doc/index.html b/doc/index.html index 1a15819..71f598e 100644 --- a/doc/index.html +++ b/doc/index.html @@ -30,6 +30,9 @@ Modules revpicheckclient +revpidevelop + + revpiinfo diff --git a/doc/mytools.html b/doc/mytools.html index db6bf59..0511c04 100644 --- a/doc/mytools.html +++ b/doc/mytools.html @@ -12,7 +12,7 @@ Tools-Sammlung.

Global Attributes

- +
savefile_connections
savefile_programpath
savefile_connections
savefile_developer
savefile_programpath

Classes

diff --git a/doc/revpidevelop.html b/doc/revpidevelop.html new file mode 100644 index 0000000..9c74a42 --- /dev/null +++ b/doc/revpidevelop.html @@ -0,0 +1,192 @@ + + +revpidevelop + + + +

+revpidevelop

+ +

+Global Attributes

+ + +
_
+

+Classes

+ + + + + +
RevPiDevelopZeigt Debugfenster an.
+

+Functions

+ + + + + + + + +
_loaddefaultsÜbernimmt für den Pi die letzen Pfade.
_savedefaultsSchreibt fuer den Pi die letzen Pfade.
+

+ +

RevPiDevelop

+

+Zeigt Debugfenster an. +

+

+Derived from

+ttk.Frame +

+Class Attributes

+ + +
app
cli
root
+

+Class Methods

+ + +
None
+

+Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
RevPiDevelopInit RevPiDevelop-Class.
_checkclosePrüft ob Fenster beendet werden soll.
_createwidgetsErstellt alle Widgets.
btn_domyjobHochladen und neu starten.
btn_selectpathLässt dem Benuzter ein Verzeichnis auswählen.
load_pathfilesAktualisiert die Dateiliste.
refresh_statsPasst die Widgets an.
select_pathfilesSetzt state der Buttons.
+

+Static Methods

+ + +
None
+ +

+RevPiDevelop (Constructor)

+RevPiDevelop(master, xmlcli, xmlmode, revpi) +

+Init RevPiDevelop-Class. +

+
Returns:
+
+None +
+
+

+RevPiDevelop._checkclose

+_checkclose(event=None) +

+Prüft ob Fenster beendet werden soll. +

+
event
+
+tkinter-Event +
+
+

+RevPiDevelop._createwidgets

+_createwidgets() +

+Erstellt alle Widgets. +

+

+RevPiDevelop.btn_domyjob

+btn_domyjob() +

+Hochladen und neu starten. +

+

+RevPiDevelop.btn_selectpath

+btn_selectpath() +

+Lässt dem Benuzter ein Verzeichnis auswählen. +

+

+RevPiDevelop.load_pathfiles

+load_pathfiles(silent=False) +

+Aktualisiert die Dateiliste. +

+
silent
+
+Keinen Dialog anzeigen +
+
+

+RevPiDevelop.refresh_stats

+refresh_stats() +

+Passt die Widgets an. +

+

+RevPiDevelop.select_pathfiles

+select_pathfiles(tkevt) +

+Setzt state der Buttons. +

+
Up
+

+ +

_loaddefaults

+_loaddefaults(revpiname=None) +

+Übernimmt für den Pi die letzen Pfade. +

+
revpiname
+
+Einstellungen nur für RevPi laden +
+
+
Returns:
+
+ mit Einstellungen +
+
+
Up
+

+ +

_savedefaults

+_savedefaults(revpiname, settings) +

+Schreibt fuer den Pi die letzen Pfade. +

+
revpiname
+
+Einstellungen sind für diesen RevPi +
settings
+
+ mit Einstellungen +
+
+
Returns:
+
+True, bei erfolgreicher Verarbeitung +
+
+
Up
+
+ \ No newline at end of file diff --git a/doc/revpipycontrol.html b/doc/revpipycontrol.html index 77aa118..2100484 100644 --- a/doc/revpipycontrol.html +++ b/doc/revpipycontrol.html @@ -78,6 +78,9 @@ Methods plcdebug Baut den Debugframe und packt ihn. +plcdevelop +Startet das Developfenster. + plclist Öffnet das Fenster für die Verbindungen. @@ -200,7 +203,13 @@ Baut den Debugframe und packt ihn.
None
- + +

+RevPiPyControl.plcdevelop

+plcdevelop() +

+Startet das Developfenster. +

RevPiPyControl.plclist

plclist() diff --git a/eric-revpipycontrol.api b/eric-revpipycontrol.api index 35d2dd1..e48d393 100644 --- a/eric-revpipycontrol.api +++ b/eric-revpipycontrol.api @@ -21,6 +21,7 @@ aclmanager._?8 mytools.addroot?4(filename) mytools.gettrans?4(proglang=None) mytools.savefile_connections?7 +mytools.savefile_developer?7 mytools.savefile_programpath?7 revpicheckclient.RevPiCheckClient.__chval?6(device, io, event=None) revpicheckclient.RevPiCheckClient.__hidewin?6(win, event=None) @@ -43,6 +44,20 @@ revpicheckclient.RevPiCheckClient.validatereturn?4(returnlist) revpicheckclient.RevPiCheckClient.writevalues?4() revpicheckclient.RevPiCheckClient?1(master, xmlcli, xmlmode=0) revpicheckclient._?8 +revpidevelop.RevPiDevelop._checkclose?5(event=None) +revpidevelop.RevPiDevelop._createwidgets?5() +revpidevelop.RevPiDevelop.app?7 +revpidevelop.RevPiDevelop.btn_domyjob?4() +revpidevelop.RevPiDevelop.btn_selectpath?4() +revpidevelop.RevPiDevelop.cli?7 +revpidevelop.RevPiDevelop.load_pathfiles?4(silent=False) +revpidevelop.RevPiDevelop.refresh_stats?4() +revpidevelop.RevPiDevelop.root?7 +revpidevelop.RevPiDevelop.select_pathfiles?4(tkevt) +revpidevelop.RevPiDevelop?1(master, xmlcli, xmlmode, revpi) +revpidevelop._?8 +revpidevelop._loaddefaults?5(revpiname=None) +revpidevelop._savedefaults?5(revpiname, settings) revpiinfo.RevPiInfo._checkclose?5(event=None) revpiinfo.RevPiInfo._createwidgets?5(extended=False) revpiinfo.RevPiInfo.visitwebsite?4(event=None) @@ -119,6 +134,7 @@ revpipycontrol.RevPiPyControl._opt_conn?5(text, reconnect=False) revpipycontrol.RevPiPyControl.infowindow?4() revpipycontrol.RevPiPyControl.myapp?7 revpipycontrol.RevPiPyControl.plcdebug?4() +revpipycontrol.RevPiPyControl.plcdevelop?4() revpipycontrol.RevPiPyControl.plclist?4() revpipycontrol.RevPiPyControl.plclogs?4() revpipycontrol.RevPiPyControl.plcoptions?4() diff --git a/eric-revpipycontrol.bas b/eric-revpipycontrol.bas index 7e6201d..b0573bb 100644 --- a/eric-revpipycontrol.bas +++ b/eric-revpipycontrol.bas @@ -1,5 +1,6 @@ AclManager ttk.Frame RevPiCheckClient tkinter.Frame +RevPiDevelop ttk.Frame RevPiInfo tkinter.Frame RevPiLogfile tkinter.Frame RevPiOption tkinter.Frame diff --git a/revpipycontrol.e4p b/revpipycontrol.e4p index f7fd027..40f271f 100644 --- a/revpipycontrol.e4p +++ b/revpipycontrol.e4p @@ -1,7 +1,7 @@ - + en_US @@ -9,7 +9,7 @@ Python3 Console - 0.6.2 + 0.7.0 Sven Sager akira@narux.de @@ -27,6 +27,7 @@ revpipycontrol/revpilegacy.py revpipycontrol/shared/ipaclmanager.py revpipycontrol/shared/__init__.py + revpipycontrol/revpidevelop.py diff --git a/revpipycontrol/mytools.py b/revpipycontrol/mytools.py index 8cd4ab9..4356578 100644 --- a/revpipycontrol/mytools.py +++ b/revpipycontrol/mytools.py @@ -19,8 +19,11 @@ if platform == "linux": homedir = environ["HOME"] else: homedir = environ["APPDATA"] + savefile_connections = pathjoin( homedir, ".revpipyplc", "connections.dat") +savefile_developer = pathjoin( + homedir, ".revpipyplc", "developer.dat") savefile_programpath = pathjoin( homedir, ".revpipyplc", "programpath.dat") diff --git a/revpipycontrol/revpidevelop.py b/revpipycontrol/revpidevelop.py new file mode 100644 index 0000000..3bc0c59 --- /dev/null +++ b/revpipycontrol/revpidevelop.py @@ -0,0 +1,339 @@ +# -*- coding: utf-8 -*- +# +# RevPiPyControl +# +# Webpage: https://revpimodio.org/revpipyplc/ +# (c) Sven Sager, License: LGPLv3 +# +u"""PLC Programm und Konfig hoch und runterladen.""" +import gzip +import os +import pickle +import tkinter +import tkinter.filedialog as tkfd +import tkinter.messagebox as tkmsg +from mytools import homedir +from mytools import gettrans +from mytools import savefile_developer as savefile +from revpilogfile import RevPiLogfile +from tkinter import ttk +from xmlrpc.client import Binary + +# Übersetzung laden +_ = gettrans() + + +def _loaddefaults(revpiname=None): + u"""Übernimmt für den Pi die letzen Pfade. + @param revpiname Einstellungen nur für RevPi laden + @return mit Einstellungen""" + if os.path.exists(savefile): + with open(savefile, "rb") as fh: + dict_all = pickle.load(fh) + if revpiname is None: + return dict_all + else: + return dict_all.get(revpiname, {}) + return {} + + +def _savedefaults(revpiname, settings): + u"""Schreibt fuer den Pi die letzen Pfade. + + @param revpiname Einstellungen sind für diesen RevPi + @param settings mit Einstellungen + @return True, bei erfolgreicher Verarbeitung + + """ + try: + os.makedirs(os.path.dirname(savefile), exist_ok=True) + if revpiname is None: + dict_all = settings + else: + dict_all = _loaddefaults() + dict_all[revpiname] = settings + with open(savefile, "wb") as fh: + pickle.dump(dict_all, fh) + except: + return False + return True + + +class RevPiDevelop(ttk.Frame): + + u"""Zeigt Debugfenster an.""" + + def __init__(self, master, xmlcli, xmlmode, revpi): + u"""Init RevPiDevelop-Class. + @return None""" + if xmlmode < 3: + return None + + super().__init__(master) + self.master.protocol("WM_DELETE_WINDOW", self._checkclose) + self.master.bind("", self._checkclose) + self.pack(expand=True, fill="both") + + self.revpi = revpi + self.xmlcli = xmlcli + + # Letzte Einstellungen übernehmen + self.opt = _loaddefaults(revpi) + + # Einstellungen + self.pathselected = self.opt.get("pathselected", False) + self.watchpath = self.opt.get("watchpath", homedir) + self.watchfiles = self.opt.get("watchfiles", []) + + # Fenster bauen + self._createwidgets() + + # Alte Einstellungen anwenden + if self.pathselected: + self.load_pathfiles(silent=True) + + self.refresh_stats() + + # Logfenster öffnen + self.tklog = RevPiLogfile(master, self.xmlcli) + + def _checkclose(self, event=None): + u"""Prüft ob Fenster beendet werden soll. + @param event tkinter-Event""" + + # Einstellungen speichern + self.opt["pathselected"] = self.pathselected + self.opt["watchpath"] = self.watchpath + self.opt["watchfiles"] = self.watchfiles + _savedefaults(self.revpi, self.opt) + + # Logfile und eigenes fenster schließen + self.tklog.destroy() + self.master.destroy() + + def _createwidgets(self): + u"""Erstellt alle Widgets.""" + self.master.wm_title(_("RevPi Python PLC program")) + + self.rowconfigure(0, weight=1) + self.columnconfigure(0, weight=1) + + cpad = {"padx": 4, "pady": 2} + # cpade = {"padx": 4, "pady": 2, "sticky": "e"} + cpadw = {"padx": 4, "pady": 2, "sticky": "w"} + cpadwe = {"padx": 4, "pady": 2, "sticky": "we"} + + # Gruppe Develop + devel = ttk.LabelFrame(self) + devel.columnconfigure(0, weight=1) + devel["text"] = _("File watcher for PLC development") + devel.grid(**cpadwe) + + r = 0 + lbl = ttk.Label(devel) + lbl["text"] = _("Path to list files:") + lbl.grid(row=r, **cpadw) + + r += 1 + self.lbl_path = ttk.Label(devel) + self.lbl_path["text"] = self.watchpath + self.lbl_path.grid(row=r, column=0, columnspan=2, **cpadw) + + btn = ttk.Button(devel) + btn["command"] = self.btn_selectpath + btn["text"] = _("Select path") + btn.grid(row=r, column=1, **cpadw) + + # Listbox + r += 1 + trv = ttk.Frame(devel) + trv.columnconfigure(0, weight=1) + trv.grid(row=r, columnspan=2, sticky="we") + scb_files = ttk.Scrollbar(trv) + self.trv_files = ttk.Treeview(trv) + self.trv_files.bind("<>", self.select_pathfiles) + self.trv_files["height"] = 15 + self.trv_files["yscrollcommand"] = scb_files.set + self.trv_files.grid(row=0, column=0, sticky="we") + scb_files["command"] = self.trv_files.yview + scb_files.grid(row=0, column=1, sticky="ns") + + # Uploadbutton + r += 1 + btnlist = ttk.Frame(devel) + btnlist.columnconfigure(1, weight=1) + btnlist.grid(row=r, columnspan=2, sticky="we") + + btn = ttk.Button(btnlist) + btn["command"] = lambda: self.xmlcli.plcstop() + btn["text"] = _("Just stop PLC") + btn.grid(row=0, column=0, **cpadwe) + + self.btn_jobs = ttk.Button(btnlist) + self.btn_jobs["command"] = self.btn_domyjob + self.btn_jobs["text"] = _("Stop / Upload / Start") + self.btn_jobs.grid(row=0, column=1, **cpadwe) + + btn = ttk.Button(btnlist) + btn["command"] = lambda: self.xmlcli.plcstart() + btn["text"] = _("Just start PLC") + btn.grid(row=0, column=2, **cpadwe) + + # Beendenbutton + btn = ttk.Button(self) + btn["command"] = self._checkclose + btn["text"] = _("Exit") + btn.grid(**cpad) + + def btn_domyjob(self): + u"""Hochladen und neu starten.""" + + # PLC Programm anhalten + self.xmlcli.plcstop() + + # Aktuell konfiguriertes Programm lesen (für uploaded Flag) + opt_program = self.xmlcli.get_config() + opt_program = opt_program.get("plcprogram", "none.py") + uploaded = True + ec = 0 + + for fname in self.watchfiles: + + # FIXME: Fehlerabfang bei Dateilesen + with open(fname, "rb") as fh: + + # Ordnernamen vom System entfernen + sendname = fname.replace(self.watchpath, "")[1:] + + # Prüfen ob Dateiname bereits als Startprogramm angegeben ist + if sendname == opt_program: + uploaded = False + + # Datei übertragen + try: + ustatus = self.xmlcli.plcupload( + Binary(gzip.compress(fh.read())), sendname + ) + except: + ec = -2 + break + + if not ustatus: + ec = -1 + break + + if ec == 0: + # Wenn eines der Dateien nicht das Hauptprogram ist, info + if uploaded: + tkmsg.showinfo( + _("Information"), + _("A PLC program has been uploaded. Please check the " + "PLC options to see if the correct program is " + "specified as the start program."), + parent=self.master + ) + + elif ec == -1: + tkmsg.showerror( + _("Error"), + _("The Revolution Pi could not process some parts of the " + "transmission."), + parent=self.master + ) + + elif ec == -2: + tkmsg.showerror( + _("Error"), + _("Errors occurred during transmission"), + parent=self.master + ) + + # PLC Programm starten + self.xmlcli.plcstart() + + def btn_selectpath(self): + u"""Lässt dem Benuzter ein Verzeichnis auswählen.""" + dirselect = tkfd.askdirectory( + parent=self.master, + title=_("Directory to watch"), + mustexist=False, + initialdir=self.watchpath + ) + if not dirselect: + return + + # Neuen Pfad übernehmen + if os.path.exists(dirselect): + self.pathselected = True + self.watchpath = dirselect + self.load_pathfiles() + + else: + tkmsg.showerror( + _("Error"), + _("Can not open the selected folder."), + parent=self.master + ) + + self.refresh_stats() + + def load_pathfiles(self, silent=False): + u"""Aktualisiert die Dateiliste. + @param silent Keinen Dialog anzeigen""" + # Liste leeren + self.trv_files.delete(*self.trv_files.get_children()) + + # Dateiliste erstellen + filecount = 0 + for tup_walk in os.walk(self.watchpath): + for filename in tup_walk[2]: + fullname = os.path.join(tup_walk[0], filename) + self.trv_files.insert( + "", "end", fullname, + text=fullname.replace(self.watchpath, "")[1:], + values=fullname + ) + + # Dateiobergrenze + filecount += 1 + if filecount >= 1000: + break + + if filecount >= 1000: + if not silent: + tkmsg.showwarning( + _("Warning"), + _("Found more than 1000 files! Only 1000 files can be " + "shown in this dialog, all other will be ignored." + ""), + parent=self.master + ) + break + + # Alle Elemente für Selection prüfen und anwenden + for watchfile in self.watchfiles.copy(): + try: + self.trv_files.item(watchfile) + except: + self.watchfiles.remove(watchfile) + self.trv_files.selection_set(self.watchfiles) + + def select_pathfiles(self, tkevt): + u"""Setzt state der Buttons.""" + self.watchfiles = list(self.trv_files.selection()) + self.refresh_stats() + + def refresh_stats(self): + u"""Passt die Widgets an.""" + self.btn_jobs["state"] = "normal" if len(self.watchfiles) > 0 \ + else "disabled" + self.lbl_path["text"] = self.watchpath + + +# Debugging +if __name__ == "__main__": + from xmlrpc.client import ServerProxy + cli = ServerProxy("http://localhost:55123") + root = tkinter.Tk() + app = RevPiDevelop(root, cli, 3, "debugging") + app.mainloop() diff --git a/revpipycontrol/revpiplclist.py b/revpipycontrol/revpiplclist.py index 29900fd..acb384e 100644 --- a/revpipycontrol/revpiplclist.py +++ b/revpipycontrol/revpiplclist.py @@ -12,6 +12,8 @@ import tkinter import tkinter.messagebox as tkmsg from mytools import gettrans from mytools import savefile_connections as savefile +from revpidevelop import _loaddefaults as developloaddefaults +from revpidevelop import _savedefaults as developsavedefaults from revpiprogram import _loaddefaults as programloaddefaults from revpiprogram import _savedefaults as programsavedefaults from os import makedirs @@ -154,6 +156,11 @@ class RevPiPlcList(tkinter.Frame): return False # Andere Einstellungen aufräumen + dict = developloaddefaults() + for revpi in tuple(dict.keys()): + if revpi not in self._connections: + del dict[revpi] + developsavedefaults(None, dict) dict = programloaddefaults() for revpi in tuple(dict.keys()): if revpi not in self._connections: diff --git a/revpipycontrol/revpiprogram.py b/revpipycontrol/revpiprogram.py index 7d3a1ee..d9e00e8 100644 --- a/revpipycontrol/revpiprogram.py +++ b/revpipycontrol/revpiprogram.py @@ -15,8 +15,8 @@ import tkinter.filedialog as tkfd import tkinter.messagebox as tkmsg import zipfile from mytools import gettrans +from mytools import homedir from mytools import savefile_programpath as savefile -from os import makedirs from shutil import rmtree from tempfile import mkstemp, mkdtemp from xmlrpc.client import Binary @@ -48,7 +48,7 @@ def _savedefaults(revpiname, settings): """ try: - makedirs(os.path.dirname(savefile), exist_ok=True) + os.makedirs(os.path.dirname(savefile), exist_ok=True) if revpiname is None: dict_all = settings else: @@ -588,6 +588,7 @@ class RevPiProgram(tkinter.Frame): dirtmp = None filelist = [] fileselect = None + foldername = "" rscfile = None if tup == 0: @@ -595,7 +596,7 @@ class RevPiProgram(tkinter.Frame): fileselect = tkfd.askopenfilenames( parent=self.master, title="Upload Python program...", - initialdir=self.opt.get("plcupload_dir", ""), + initialdir=self.opt.get("plcupload_dir", homedir), filetypes=(("Python", "*.py"), (_("All files"), "*.*")) ) if type(fileselect) == tuple and len(fileselect) > 0: @@ -608,8 +609,12 @@ class RevPiProgram(tkinter.Frame): parent=self.master, title=_("Folder to upload"), mustexist=True, - initialdir=self.opt.get("plcupload_dir", self.revpi) + initialdir=self.opt.get("plcupload_dir", homedir) ) + + # Ordnernamen merken um diesen auf RevPi anzulegen + foldername = os.path.basename(dirselect) + if type(dirselect) == str and dirselect != "": filelist = self.create_filelist(dirselect) @@ -706,7 +711,11 @@ class RevPiProgram(tkinter.Frame): if dirselect == "": sendname = os.path.basename(fname) else: - sendname = fname.replace(dirselect, "")[1:] + # Ordnernamen in Dateipfad für RevPi übernehmen + sendname = os.path.join( + foldername, + fname.replace(dirselect, "")[1:] + ) # Prüfen ob Dateiname bereits als Startprogramm angegeben ist if sendname == opt_program: diff --git a/revpipycontrol/revpipycontrol.py b/revpipycontrol/revpipycontrol.py index 3c424eb..7736aa9 100755 --- a/revpipycontrol/revpipycontrol.py +++ b/revpipycontrol/revpipycontrol.py @@ -9,6 +9,7 @@ # u"""Hauptprogramm.""" import revpicheckclient +import revpidevelop import revpiinfo import revpilogfile import revpioption @@ -25,7 +26,7 @@ from xmlrpc.client import ServerProxy # Übersetzung laden _ = gettrans() -pycontrolversion = "0.6.2" +pycontrolversion = "0.7.0" class RevPiPyControl(tkinter.Frame): @@ -52,6 +53,7 @@ class RevPiPyControl(tkinter.Frame): # Globale Fenster self.tkcheckclient = None + self.tkdevelop = None self.tklogs = None self.tkoptions = None self.tkprogram = None @@ -75,6 +77,8 @@ class RevPiPyControl(tkinter.Frame): u"""Schließt alle Fenster.""" if self.tkcheckclient is not None: self.tkcheckclient.destroy() + if self.tkdevelop is not None: + self.tkdevelop.destroy() if self.tklogs is not None: self.tklogs.master.destroy() if self.tkoptions is not None: @@ -179,6 +183,8 @@ class RevPiPyControl(tkinter.Frame): 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( @@ -282,6 +288,27 @@ class RevPiPyControl(tkinter.Frame): 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 + ) + else: + # Logfenster schließen + if self.tklogs is not None: + self.tklogs.master.destroy() + + win = tkinter.Toplevel(self) + win.focus_set() + win.grab_set() + self.tkdevelop = revpidevelop.RevPiDevelop( + win, self.cli, self.xmlmode, self.revpiname) + self.wait_window(win) + def plclist(self): u"""Öffnet das Fenster für die Verbindungen.""" win = tkinter.Toplevel(self) diff --git a/setup.py b/setup.py index 742dc68..cbed974 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ globsetup = { "author_email": "akira@narux.de", "url": "https://revpimodio.org/revpipyplc/", "license": "LGPLv3", - "version": "0.6.2", + "version": "0.7.0", "name": "revpipycontrol",