diff --git a/.hgignore b/.hgignore index 9e0632f..a10df60 100644 --- a/.hgignore +++ b/.hgignore @@ -3,6 +3,7 @@ syntax: glob deb_dist/* dist/* revpipycontrol.egg-info/* -doc/* deb/* .eric6project/* +*.directory +*.mo diff --git a/doc/index.html b/doc/index.html new file mode 100644 index 0000000..621e3bb --- /dev/null +++ b/doc/index.html @@ -0,0 +1,41 @@ + + +Table of contents + + + +

+Table of contents

+ + + +

+Modules

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
mytools
revpicheckclient
revpiinfo
revpilogfile
revpioption
revpiplclist
revpiprogram
revpipycontrol
+ \ No newline at end of file diff --git a/doc/mytools.html b/doc/mytools.html new file mode 100644 index 0000000..c8a2a96 --- /dev/null +++ b/doc/mytools.html @@ -0,0 +1,59 @@ + + +mytools + + + +

+mytools

+ +

+Global Attributes

+ + +
None
+

+Classes

+ + +
None
+

+Functions

+ + + + + + + + +
addrootHängt root-dir der Anwendung vor Dateinamen.
gettrans
+

+ +

addroot

+addroot(filename) +

+Hängt root-dir der Anwendung vor Dateinamen. +

+ Je nach Ausführungsart der Anwendung muss das root-dir über + andere Arten abgerufen werden. +

+
filename
+
+Datei oder Ordnername +
+
+
Returns:
+
+root dir +
+
+
Up
+

+ +

gettrans

+gettrans(proglang=None) + +
Up
+
+ \ No newline at end of file diff --git a/doc/revpicheckclient.html b/doc/revpicheckclient.html new file mode 100644 index 0000000..7b5ca6f --- /dev/null +++ b/doc/revpicheckclient.html @@ -0,0 +1,294 @@ + + +revpicheckclient + + + +

+revpicheckclient

+ +

+Global Attributes

+ + +
_
+

+Classes

+ + + + + +
RevPiCheckClient
+

+Functions

+ + +
None
+

+ +

RevPiCheckClient

+ +

+Derived from

+tkinter.Frame +

+Class Attributes

+ + +
None
+

+Class Methods

+ + +
None
+

+Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RevPiCheckClientInstantiiert MyApp-Klasse.
__chvalSchreibt neuen Output Wert auf den RevPi.
__hidewinVerbergt übergebenes Fenster.
__saveoldvalueSpeichert bei Keypress aktuellen Wert für wiederherstellung.
__showwinZeigt oder verbergt übergebenes Fenster.
__spinboxkeyPrüft die Eingabe auf plausibilität.
_createiogroupErstellt IO-Gruppen.
_createwidgetsErstellt den Fensterinhalt.
_onfrmconfErstellt Fenster in einem Canvas.
_warnwriteWarnung für Benutzer über Schreibfunktion einmal fragen.
_workvaluesAlle Werte der Inputs und Outputs abrufen.
hideallwindowsVersteckt alle Fenster.
readvaluesRuft nur Input Werte von RevPi ab und aktualisiert Fenster.
refreshvaluesRuft alle IO Werte von RevPi ab und aktualisiert Fenster.
tmr_workvaluesTimer für zyklische Abfrage.
toggleautoSchaltet zwischen Autorefresh um und aktualisiert Widgets.
togglewriteSchaltet zwischen DoWrite um und aktiviert Schreibfunktion.
validatereturnÜberprüft die Rückgaben der setvalue Funktion.
writevaluesSchreibt geänderte Outputs auf den RevPi.
+

+Static Methods

+ + +
None
+ +

+RevPiCheckClient (Constructor)

+RevPiCheckClient(master, xmlcli, xmlmode=0) +

+Instantiiert MyApp-Klasse. +

+

+RevPiCheckClient.__chval

+__chval(device, io, event=None) +

+Schreibt neuen Output Wert auf den RevPi. +

+

+RevPiCheckClient.__hidewin

+__hidewin(win, event=None) +

+Verbergt übergebenes Fenster. +

+
win
+
+Fenster zum verbergen +
event
+
+Tkinter Event +
+
+

+RevPiCheckClient.__saveoldvalue

+__saveoldvalue(event, tkvar) +

+Speichert bei Keypress aktuellen Wert für wiederherstellung. +

+

+RevPiCheckClient.__showwin

+__showwin(win) +

+Zeigt oder verbergt übergebenes Fenster. +

+
win
+
+Fenster zum anzeigen/verbergen +
+
+

+RevPiCheckClient.__spinboxkey

+__spinboxkey(device, io, event=None) +

+Prüft die Eingabe auf plausibilität. +

+
event
+
+tkinter Event +
io
+
+IO Liste mit tkinter Variable +
+
+

+RevPiCheckClient._createiogroup

+_createiogroup(device, frame, iotype) +

+Erstellt IO-Gruppen. +

+
device
+
+Deviceposition +
frame
+
+tkinter Frame +
iotype
+
+'inp' oder 'out' als str() +
+
+

+RevPiCheckClient._createwidgets

+_createwidgets() +

+Erstellt den Fensterinhalt. +

+

+RevPiCheckClient._onfrmconf

+_onfrmconf(canvas) +

+Erstellt Fenster in einem Canvas. +

+
canvas
+
+Canvas in dem Objekte erstellt werden sollen +
+
+

+RevPiCheckClient._warnwrite

+_warnwrite() +

+Warnung für Benutzer über Schreibfunktion einmal fragen. +

+
Returns:
+
+True, wenn Warnung einmal mit OK bestätigt wurde +
+
+

+RevPiCheckClient._workvalues

+_workvalues(io_dicts=None, writeout=False) +

+Alle Werte der Inputs und Outputs abrufen. +

+
io_dicts
+
+Arbeit nur für dieses Dict() +
writeout
+
+Änderungen auf RevPi schreiben +
+
+
Returns:
+
+None +
+
+

+RevPiCheckClient.hideallwindows

+hideallwindows() +

+Versteckt alle Fenster. +

+

+RevPiCheckClient.readvalues

+readvalues() +

+Ruft nur Input Werte von RevPi ab und aktualisiert Fenster. +

+

+RevPiCheckClient.refreshvalues

+refreshvalues() +

+Ruft alle IO Werte von RevPi ab und aktualisiert Fenster. +

+

+RevPiCheckClient.tmr_workvalues

+tmr_workvalues() +

+Timer für zyklische Abfrage. +

+
Returns:
+
+None +
+
+

+RevPiCheckClient.toggleauto

+toggleauto() +

+Schaltet zwischen Autorefresh um und aktualisiert Widgets. +

+

+RevPiCheckClient.togglewrite

+togglewrite() +

+Schaltet zwischen DoWrite um und aktiviert Schreibfunktion. +

+

+RevPiCheckClient.validatereturn

+validatereturn(returnlist) +

+Überprüft die Rückgaben der setvalue Funktion. +

+
returnlist
+
+list() der xml Rückgabe +
+
+

+RevPiCheckClient.writevalues

+writevalues() +

+Schreibt geänderte Outputs auf den RevPi. +

+
Up
+
+ \ No newline at end of file diff --git a/doc/revpiinfo.html b/doc/revpiinfo.html new file mode 100644 index 0000000..ebc2484 --- /dev/null +++ b/doc/revpiinfo.html @@ -0,0 +1,99 @@ + + +revpiinfo + + + +

+revpiinfo

+ +

+Global Attributes

+ + +
_
+

+Classes

+ + + + + +
RevPiInfo
+

+Functions

+ + +
None
+

+ +

RevPiInfo

+ +

+Derived from

+tkinter.Frame +

+Class Attributes

+ + +
None
+

+Class Methods

+ + +
None
+

+Methods

+ + + + + + + + + + + + + + +
RevPiInfoInit RevPiLogfile-Class.
_checkclosePrüft ob Fenster beendet werden soll.
_createwidgetsErstellt alle Widgets.
visitwebsiteÖffnet auf dem System einen Webbrowser zur Projektseite.
+

+Static Methods

+ + +
None
+ +

+RevPiInfo (Constructor)

+RevPiInfo(master, xmlcli, version) +

+Init RevPiLogfile-Class. +

+

+RevPiInfo._checkclose

+_checkclose(event=None) +

+Prüft ob Fenster beendet werden soll. +

+
event
+
+tkinter-Event +
+
+

+RevPiInfo._createwidgets

+_createwidgets(extended=False) +

+Erstellt alle Widgets. +

+

+RevPiInfo.visitwebsite

+visitwebsite(event=None) +

+Öffnet auf dem System einen Webbrowser zur Projektseite. +

+
Up
+
+ \ No newline at end of file diff --git a/doc/revpilogfile.html b/doc/revpilogfile.html new file mode 100644 index 0000000..3e0242b --- /dev/null +++ b/doc/revpilogfile.html @@ -0,0 +1,164 @@ + + +revpilogfile + + + +

+revpilogfile

+ +

+Global Attributes

+ + +
_
+

+Classes

+ + + + + +
RevPiLogfile
+

+Functions

+ + +
None
+

+ +

RevPiLogfile

+ +

+Derived from

+tkinter.Frame +

+Class Attributes

+ + +
None
+

+Class Methods

+ + +
None
+

+Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
RevPiLogfileInit RevPiLogfile-Class.
_checkclosePrüft ob Fenster beendet werden soll.
_createwidgetsErstellt alle Widgets.
_load_logLäd die angegebenen Logfiles herunter.
btn_clearappLeert die Logliste der App.
btn_clearplcLeert die Logliste des PLC.
get_applogRuft App Logbuch ab.
get_plclogRuft PLC Logbuch ab.
+

+Static Methods

+ + +
None
+ +

+RevPiLogfile (Constructor)

+RevPiLogfile(master, xmlcli) +

+Init RevPiLogfile-Class. +

+

+RevPiLogfile._checkclose

+_checkclose(event=None) +

+Prüft ob Fenster beendet werden soll. +

+
event
+
+tkinter-Event +
+
+

+RevPiLogfile._createwidgets

+_createwidgets() +

+Erstellt alle Widgets. +

+

+RevPiLogfile._load_log

+_load_log(textwidget, xmlcall, startposition, full) +

+Läd die angegebenen Logfiles herunter. +

+
textwidget
+
+Widget in das Logs eingefügt werden sollen +
xmlcall
+
+xmlrpc Funktion zum Abrufen der Logdaten +
startposition
+
+Startposition ab der Logdaten kommen sollen +
full
+
+Komplettes Logbuch laden +
+
+
Returns:
+
+Ende der Datei (neue Startposition) +
+
+

+RevPiLogfile.btn_clearapp

+btn_clearapp() +

+Leert die Logliste der App. +

+

+RevPiLogfile.btn_clearplc

+btn_clearplc() +

+Leert die Logliste des PLC. +

+

+RevPiLogfile.get_applog

+get_applog(full=False) +

+Ruft App Logbuch ab. +

+
full
+
+Ganzes Logbuch laden +
+
+

+RevPiLogfile.get_plclog

+get_plclog(full=False) +

+Ruft PLC Logbuch ab. +

+
full
+
+Ganzes Logbuch laden +
+
+
Up
+
+ \ No newline at end of file diff --git a/doc/revpioption.html b/doc/revpioption.html new file mode 100644 index 0000000..1c42b8d --- /dev/null +++ b/doc/revpioption.html @@ -0,0 +1,164 @@ + + +revpioption + + + +

+revpioption

+ +

+Global Attributes

+ + +
_
+

+Classes

+ + + + + +
RevPiOption
+

+Functions

+ + +
None
+

+ +

RevPiOption

+ +

+Derived from

+tkinter.Frame +

+Class Attributes

+ + +
None
+

+Class Methods

+ + +
None
+

+Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RevPiOptionInit RevPiOption-Class.
_changesdonePrüft ob sich die Einstellungen geändert haben.
_checkclosePrüft ob Fenster beendet werden soll.
_createwidgetsErstellt Widgets.
_loadappdataLäd aktuelle Einstellungen vom RevPi.
_setappdataSpeichert geänderte Einstellungen auf RevPi.
askxmlonFragt Nuter, ob wirklicht abgeschaltet werden soll.
xmlmod2_tailPasst XML-Optionszugriff an.
xmlmod_tailPasst XML-Optionszugriff an.
+

+Static Methods

+ + +
None
+ +

+RevPiOption (Constructor)

+RevPiOption(master, xmlcli) +

+Init RevPiOption-Class. +

+
Returns:
+
+None +
+
+

+RevPiOption._changesdone

+_changesdone() +

+Prüft ob sich die Einstellungen geändert haben. +

+
Returns:
+
+True, wenn min. eine Einstellung geändert wurde +
+
+

+RevPiOption._checkclose

+_checkclose(event=None) +

+Prüft ob Fenster beendet werden soll. +

+
event
+
+tkinter-Event +
+
+

+RevPiOption._createwidgets

+_createwidgets() +

+Erstellt Widgets. +

+

+RevPiOption._loadappdata

+_loadappdata(refresh=False) +

+Läd aktuelle Einstellungen vom RevPi. +

+
refresh
+
+Wenn True, werden Einstellungen heruntergeladen. +
+
+

+RevPiOption._setappdata

+_setappdata() +

+Speichert geänderte Einstellungen auf RevPi. +

+
Returns:
+
+None +
+
+

+RevPiOption.askxmlon

+askxmlon() +

+Fragt Nuter, ob wirklicht abgeschaltet werden soll. +

+

+RevPiOption.xmlmod2_tail

+xmlmod2_tail() +

+Passt XML-Optionszugriff an. +

+

+RevPiOption.xmlmod_tail

+xmlmod_tail() +

+Passt XML-Optionszugriff an. +

+
Up
+
+ \ No newline at end of file diff --git a/doc/revpiplclist.html b/doc/revpiplclist.html new file mode 100644 index 0000000..d2f8095 --- /dev/null +++ b/doc/revpiplclist.html @@ -0,0 +1,188 @@ + + +revpiplclist + + + +

+revpiplclist

+ +

+Global Attributes

+ + +
_
savefile
+

+Classes

+ + + + + +
RevPiPlcList
+

+Functions

+ + + + + +
get_connectionsVerbindungen aus Datei laden.
+

+ +

RevPiPlcList

+ +

+Derived from

+tkinter.Frame +

+Class Attributes

+ + +
myapp
root
+

+Class Methods

+ + +
None
+

+Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RevPiPlcListInit RevPiPlcList-class.
_checkclosePrüft ob Fenster beendet werden soll.
_createwidgetsErstellt alle Widgets.
_saveappdataSpeichert Verbindungen im home Dir.
build_listconnFüllt Verbindungsliste.
evt_btnaddVerbindungseinstellungen übernehmen.
evt_btnnewNeue Verbindung erstellen.
evt_btnremoveVerbindung löschen.
evt_btnsaveAlle Verbindungen speichern.
evt_keypressPasst bei Tastendruck den Status der Buttons an.
evt_listconnÜbernimmt Einstellungen in Eingabefelder.
+

+Static Methods

+ + +
None
+ +

+RevPiPlcList (Constructor)

+RevPiPlcList(master) +

+Init RevPiPlcList-class. +

+
master
+
+tkinter master +
+
+

+RevPiPlcList._checkclose

+_checkclose(event=None) +

+Prüft ob Fenster beendet werden soll. +

+
event
+
+tkinter-Event +
+
+

+RevPiPlcList._createwidgets

+_createwidgets() +

+Erstellt alle Widgets. +

+

+RevPiPlcList._saveappdata

+_saveappdata() +

+Speichert Verbindungen im home Dir. +

+
Returns:
+
+True, bei erfolgreicher Verarbeitung +
+
+

+RevPiPlcList.build_listconn

+build_listconn() +

+Füllt Verbindungsliste. +

+

+RevPiPlcList.evt_btnadd

+evt_btnadd() +

+Verbindungseinstellungen übernehmen. +

+

+RevPiPlcList.evt_btnnew

+evt_btnnew() +

+Neue Verbindung erstellen. +

+

+RevPiPlcList.evt_btnremove

+evt_btnremove() +

+Verbindung löschen. +

+

+RevPiPlcList.evt_btnsave

+evt_btnsave() +

+Alle Verbindungen speichern. +

+

+RevPiPlcList.evt_keypress

+evt_keypress(evt=None) +

+Passt bei Tastendruck den Status der Buttons an. +

+

+RevPiPlcList.evt_listconn

+evt_listconn(evt=None) +

+Übernimmt Einstellungen in Eingabefelder. +

+
Up
+

+ +

get_connections

+get_connections() +

+Verbindungen aus Datei laden. +

+
Returns:
+
+dict() mit Verbindungen +
+
+
Up
+
+ \ No newline at end of file diff --git a/doc/revpiprogram.html b/doc/revpiprogram.html new file mode 100644 index 0000000..4709afd --- /dev/null +++ b/doc/revpiprogram.html @@ -0,0 +1,246 @@ + + +revpiprogram + + + +

+revpiprogram

+ +

+Global Attributes

+ + +
_
savefile
+

+Classes

+ + + + + +
RevPiProgram
+

+Functions

+ + +
None
+

+ +

RevPiProgram

+ +

+Derived from

+tkinter.Frame +

+Class Attributes

+ + +
None
+

+Class Methods

+ + +
None
+

+Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RevPiProgramInit RevPiProgram-Class.
_checkclosePrüft ob Fenster beendet werden soll.
_createwidgetsErstellt alle Widgets.
_evt_optdownPasst je nach gewählter Option den Status der Widgets an.
_evt_optupPasst je nach gewählter Option den Status der Widgets an.
_loaddefaultÜbernimmt für den Pi die letzen Pfade.
_savedefaultsSchreibt fuer den Pi die letzen Pfade.
check_replacedirGibt das rootdir von einem entpackten Verzeichnis zurueck.
create_filelistErstellt eine Dateiliste von einem Verzeichnis.
getpictoryrscLäd die piCtory Konfiguration herunter.
getprocimgLäd das aktuelle Prozessabbild herunter.
picontrolresetFürt ein Reset der piBridge durch.
plcdownloadLäd das aktuelle Projekt herunter.
plcuploadLädt das angegebene Projekt auf den RevPi.
setpictoryrscÜberträgt die angegebene piCtory-Konfiguration.
+

+Static Methods

+ + +
None
+ +

+RevPiProgram (Constructor)

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

+Init RevPiProgram-Class. +

+
Returns:
+
+None +
+
+

+RevPiProgram._checkclose

+_checkclose(event=None) +

+Prüft ob Fenster beendet werden soll. +

+
event
+
+tkinter-Event +
+
+

+RevPiProgram._createwidgets

+_createwidgets() +

+Erstellt alle Widgets. +

+

+RevPiProgram._evt_optdown

+_evt_optdown(text="") +

+Passt je nach gewählter Option den Status der Widgets an. +

+

+RevPiProgram._evt_optup

+_evt_optup(text="") +

+Passt je nach gewählter Option den Status der Widgets an. +

+

+RevPiProgram._loaddefault

+_loaddefault(full=False) +

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

+
full
+
+Einstellungen für alle Verbindungen laden +
+
+
Returns:
+
+dict() mit Einstellungen +
+
+

+RevPiProgram._savedefaults

+_savedefaults() +

+Schreibt fuer den Pi die letzen Pfade. +

+
Returns:
+
+True, bei erfolgreicher Verarbeitung +
+
+

+RevPiProgram.check_replacedir

+check_replacedir(rootdir) +

+Gibt das rootdir von einem entpackten Verzeichnis zurueck. +

+ Dabei wird geprueft, ob es sich um einen einzelnen Ordner handelt + und ob es eine piCtory Konfiguration im rootdir gibt. +

+
rootdir
+
+Verzeichnis fuer Pruefung +
+
+
Returns:
+
+Abgeaendertes rootdir +
+
+

+RevPiProgram.create_filelist

+create_filelist(rootdir) +

+Erstellt eine Dateiliste von einem Verzeichnis. +

+
rootdir
+
+Verzeichnis fuer das eine Liste erstellt werden soll +
+
+
Returns:
+
+Dateiliste +
+
+

+RevPiProgram.getpictoryrsc

+getpictoryrsc() +

+Läd die piCtory Konfiguration herunter. +

+

+RevPiProgram.getprocimg

+getprocimg() +

+Läd das aktuelle Prozessabbild herunter. +

+

+RevPiProgram.picontrolreset

+picontrolreset() +

+Fürt ein Reset der piBridge durch. +

+

+RevPiProgram.plcdownload

+plcdownload() +

+Läd das aktuelle Projekt herunter. +

+

+RevPiProgram.plcupload

+plcupload() +

+Lädt das angegebene Projekt auf den RevPi. +

+
Returns:
+
+True, bei erfolgreicher Verarbeitung +
+
+

+RevPiProgram.setpictoryrsc

+setpictoryrsc(filename=None) +

+Überträgt die angegebene piCtory-Konfiguration. +

+
Up
+
+ \ No newline at end of file diff --git a/doc/revpipycontrol.html b/doc/revpipycontrol.html new file mode 100644 index 0000000..25d825a --- /dev/null +++ b/doc/revpipycontrol.html @@ -0,0 +1,270 @@ + + +revpipycontrol + + + +

+revpipycontrol

+ +

+Global Attributes

+ + +
_
pycontrolversion
+

+Classes

+ + + + + +
RevPiPyControl
+

+Functions

+ + +
None
+

+ +

RevPiPyControl

+ +

+Derived from

+tkinter.Frame +

+Class Attributes

+ + +
myapp
root
+

+Class Methods

+ + +
None
+

+Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RevPiPyControlInit RevPiPyControl-Class.
_btnstateSetzt den state der Buttons.
_closeallSchließt alle Fenster.
_closeappRäumt auf und beendet Programm.
_createwidgetsErstellt den Fensterinhalt.
_fillconnbarGeneriert Menüeinträge für Verbindungen.
_fillmbarGeneriert Menüeinträge.
_opt_connStellt eine neue Verbindung zu RevPiPyLoad her.
infowindowÖffnet das Fenster für die Info.
plcdebugBaut den Debugframe und packt ihn.
plclistÖffnet das Fenster für die Verbindungen.
plclogsÖffnet das Fenster für Logdateien.
plcoptionsStartet das Optionsfenster.
plcprogramStartet das Programmfenster.
plcrestartStartet das PLC Programm neu.
plcstartStartet das PLC Programm.
plcstopBeendet das PLC Programm.
serverdisconnectTrennt eine bestehende Verbindung.
servererrorSetzt alles zurück für neue Verbindungen.
tmr_plcrunningTimer der den Status des PLC Programms prüft.
visitwebsiteÖffnet auf dem System einen Webbrowser zur Projektseite.
+

+Static Methods

+ + +
None
+ +

+RevPiPyControl (Constructor)

+RevPiPyControl(master=None) +

+Init RevPiPyControl-Class. +

+
master
+
+tkinter master +
+
+

+RevPiPyControl._btnstate

+_btnstate() +

+Setzt den state der Buttons. +

+

+RevPiPyControl._closeall

+_closeall() +

+Schließt alle Fenster. +

+

+RevPiPyControl._closeapp

+_closeapp(event=None) +

+Räumt auf und beendet Programm. +

+
event
+
+tkinter Event +
+
+

+RevPiPyControl._createwidgets

+_createwidgets() +

+Erstellt den Fensterinhalt. +

+

+RevPiPyControl._fillconnbar

+_fillconnbar() +

+Generiert Menüeinträge für Verbindungen. +

+

+RevPiPyControl._fillmbar

+_fillmbar() +

+Generiert Menüeinträge. +

+

+RevPiPyControl._opt_conn

+_opt_conn(text, reconnect=False) +

+Stellt eine neue Verbindung zu RevPiPyLoad her. +

+
text
+
+Verbindungsname +
reconnect
+
+Socket Timeout nicht heruntersetzen +
+
+

+RevPiPyControl.infowindow

+infowindow() +

+Öffnet das Fenster für die Info. +

+

+RevPiPyControl.plcdebug

+plcdebug() +

+Baut den Debugframe und packt ihn. +

+

+RevPiPyControl.plclist

+plclist() +

+Öffnet das Fenster für die Verbindungen. +

+

+RevPiPyControl.plclogs

+plclogs() +

+Öffnet das Fenster für Logdateien. +

+
Returns:
+
+None +
+
+

+RevPiPyControl.plcoptions

+plcoptions() +

+Startet das Optionsfenster. +

+

+RevPiPyControl.plcprogram

+plcprogram() +

+Startet das Programmfenster. +

+

+RevPiPyControl.plcrestart

+plcrestart() +

+Startet das PLC Programm neu. +

+

+RevPiPyControl.plcstart

+plcstart() +

+Startet das PLC Programm. +

+

+RevPiPyControl.plcstop

+plcstop() +

+Beendet das PLC Programm. +

+

+RevPiPyControl.serverdisconnect

+serverdisconnect() +

+Trennt eine bestehende Verbindung. +

+

+RevPiPyControl.servererror

+servererror() +

+Setzt alles zurück für neue Verbindungen. +

+

+RevPiPyControl.tmr_plcrunning

+tmr_plcrunning() +

+Timer der den Status des PLC Programms prüft. +

+

+RevPiPyControl.visitwebsite

+visitwebsite() +

+Öffnet auf dem System einen Webbrowser zur Projektseite. +

+
Up
+
+ \ No newline at end of file diff --git a/revpipycontrol.api b/revpipycontrol.api index 41dff9d..0fcb54c 100644 --- a/revpipycontrol.api +++ b/revpipycontrol.api @@ -1,35 +1,54 @@ -revpicheckclient.RevPiCheckClient._autorw?5() +mytools.addroot?4(filename) +mytools.gettrans?4(proglang=None) +revpicheckclient.RevPiCheckClient.__chval?6(device, io, event=None) +revpicheckclient.RevPiCheckClient.__hidewin?6(win, event=None) +revpicheckclient.RevPiCheckClient.__saveoldvalue?6(event, tkvar) +revpicheckclient.RevPiCheckClient.__showwin?6(win) +revpicheckclient.RevPiCheckClient.__spinboxkey?6(device, io, event=None) revpicheckclient.RevPiCheckClient._createiogroup?5(device, frame, iotype) revpicheckclient.RevPiCheckClient._createwidgets?5() -revpicheckclient.RevPiCheckClient._readvaluesdev?5(device, iotype) -revpicheckclient.RevPiCheckClient._writevaluesdev?5(device) -revpicheckclient.RevPiCheckClient.myapp?7 -revpicheckclient.RevPiCheckClient.onfrmconf?4(canvas) +revpicheckclient.RevPiCheckClient._onfrmconf?5(canvas) +revpicheckclient.RevPiCheckClient._warnwrite?5() +revpicheckclient.RevPiCheckClient._workvalues?5(io_dicts=None, writeout=False) +revpicheckclient.RevPiCheckClient.hideallwindows?4() revpicheckclient.RevPiCheckClient.readvalues?4() -revpicheckclient.RevPiCheckClient.root?7 +revpicheckclient.RevPiCheckClient.refreshvalues?4() +revpicheckclient.RevPiCheckClient.tmr_workvalues?4() revpicheckclient.RevPiCheckClient.toggleauto?4() +revpicheckclient.RevPiCheckClient.togglewrite?4() +revpicheckclient.RevPiCheckClient.validatereturn?4(returnlist) revpicheckclient.RevPiCheckClient.writevalues?4() -revpicheckclient.RevPiCheckClient?1(master, xmlcli) +revpicheckclient.RevPiCheckClient?1(master, xmlcli, xmlmode=0) +revpicheckclient._?8 +revpiinfo.RevPiInfo._checkclose?5(event=None) +revpiinfo.RevPiInfo._createwidgets?5(extended=False) +revpiinfo.RevPiInfo.visitwebsite?4(event=None) +revpiinfo.RevPiInfo?1(master, xmlcli, version) +revpiinfo._?8 +revpilogfile.RevPiLogfile._checkclose?5(event=None) revpilogfile.RevPiLogfile._createwidgets?5() +revpilogfile.RevPiLogfile._load_log?5(textwidget, xmlcall, startposition, full) revpilogfile.RevPiLogfile.btn_clearapp?4() revpilogfile.RevPiLogfile.btn_clearplc?4() -revpilogfile.RevPiLogfile.get_applines?4() -revpilogfile.RevPiLogfile.get_applog?4() -revpilogfile.RevPiLogfile.get_plclines?4() -revpilogfile.RevPiLogfile.get_plclog?4() +revpilogfile.RevPiLogfile.get_applog?4(full=False) +revpilogfile.RevPiLogfile.get_plclog?4(full=False) revpilogfile.RevPiLogfile?1(master, xmlcli) +revpilogfile._?8 +revpioption.RevPiOption._changesdone?5() +revpioption.RevPiOption._checkclose?5(event=None) revpioption.RevPiOption._createwidgets?5() -revpioption.RevPiOption._loadappdata?5() +revpioption.RevPiOption._loadappdata?5(refresh=False) revpioption.RevPiOption._setappdata?5() revpioption.RevPiOption.askxmlon?4() -revpioption.RevPiOption.xmlmods?4() -revpioption.RevPiOption?1(master, xmlcli, xmlmode) +revpioption.RevPiOption.xmlmod2_tail?4() +revpioption.RevPiOption.xmlmod_tail?4() +revpioption.RevPiOption?1(master, xmlcli) +revpioption._?8 +revpiplclist.RevPiPlcList._checkclose?5(event=None) revpiplclist.RevPiPlcList._createwidgets?5() -revpiplclist.RevPiPlcList._loadappdata?5() revpiplclist.RevPiPlcList._saveappdata?5() revpiplclist.RevPiPlcList.build_listconn?4() revpiplclist.RevPiPlcList.evt_btnadd?4() -revpiplclist.RevPiPlcList.evt_btnclose?4() revpiplclist.RevPiPlcList.evt_btnnew?4() revpiplclist.RevPiPlcList.evt_btnremove?4() revpiplclist.RevPiPlcList.evt_btnsave?4() @@ -38,8 +57,10 @@ revpiplclist.RevPiPlcList.evt_listconn?4(evt=None) revpiplclist.RevPiPlcList.myapp?7 revpiplclist.RevPiPlcList.root?7 revpiplclist.RevPiPlcList?1(master) +revpiplclist._?8 revpiplclist.get_connections?4() revpiplclist.savefile?7 +revpiprogram.RevPiProgram._checkclose?5(event=None) revpiprogram.RevPiProgram._createwidgets?5() revpiprogram.RevPiProgram._evt_optdown?5(text="") revpiprogram.RevPiProgram._evt_optup?5(text="") @@ -49,31 +70,35 @@ revpiprogram.RevPiProgram.check_replacedir?4(rootdir) revpiprogram.RevPiProgram.create_filelist?4(rootdir) revpiprogram.RevPiProgram.getpictoryrsc?4() revpiprogram.RevPiProgram.getprocimg?4() -revpiprogram.RevPiProgram.myapp?7 revpiprogram.RevPiProgram.picontrolreset?4() revpiprogram.RevPiProgram.plcdownload?4() revpiprogram.RevPiProgram.plcupload?4() -revpiprogram.RevPiProgram.root?7 revpiprogram.RevPiProgram.setpictoryrsc?4(filename=None) revpiprogram.RevPiProgram?1(master, xmlcli, xmlmode, revpi) +revpiprogram._?8 revpiprogram.savefile?7 revpipycontrol.RevPiPyControl._btnstate?5() revpipycontrol.RevPiPyControl._closeall?5() +revpipycontrol.RevPiPyControl._closeapp?5(event=None) revpipycontrol.RevPiPyControl._createwidgets?5() revpipycontrol.RevPiPyControl._fillconnbar?5() revpipycontrol.RevPiPyControl._fillmbar?5() -revpipycontrol.RevPiPyControl._opt_conn?5(text) +revpipycontrol.RevPiPyControl._opt_conn?5(text, reconnect=False) +revpipycontrol.RevPiPyControl.infowindow?4() revpipycontrol.RevPiPyControl.myapp?7 +revpipycontrol.RevPiPyControl.plcdebug?4() revpipycontrol.RevPiPyControl.plclist?4() revpipycontrol.RevPiPyControl.plclogs?4() -revpipycontrol.RevPiPyControl.plcmonitor?4() revpipycontrol.RevPiPyControl.plcoptions?4() revpipycontrol.RevPiPyControl.plcprogram?4() revpipycontrol.RevPiPyControl.plcrestart?4() revpipycontrol.RevPiPyControl.plcstart?4() revpipycontrol.RevPiPyControl.plcstop?4() revpipycontrol.RevPiPyControl.root?7 +revpipycontrol.RevPiPyControl.serverdisconnect?4() revpipycontrol.RevPiPyControl.servererror?4() revpipycontrol.RevPiPyControl.tmr_plcrunning?4() +revpipycontrol.RevPiPyControl.visitwebsite?4() revpipycontrol.RevPiPyControl?1(master=None) -revpipycontrol.addroot?4(filename) +revpipycontrol._?8 +revpipycontrol.pycontrolversion?7 diff --git a/revpipycontrol.bas b/revpipycontrol.bas index c02ecc3..32a817a 100644 --- a/revpipycontrol.bas +++ b/revpipycontrol.bas @@ -1,4 +1,5 @@ RevPiCheckClient tkinter.Frame +RevPiInfo tkinter.Frame RevPiLogfile tkinter.Frame RevPiOption tkinter.Frame RevPiPlcList tkinter.Frame diff --git a/revpipycontrol.e4p b/revpipycontrol.e4p index 68fd5fa..38de6f1 100644 --- a/revpipycontrol.e4p +++ b/revpipycontrol.e4p @@ -1,7 +1,7 @@ - + en_US @@ -9,7 +9,7 @@ Python3 Console - 0.2.12 + 0.4.1 Sven Sager akira@narux.de @@ -21,6 +21,8 @@ revpipycontrol/revpilogfile.py revpipycontrol/revpioption.py revpipycontrol/revpiprogram.py + revpipycontrol/mytools.py + revpipycontrol/revpiinfo.py @@ -30,6 +32,7 @@ data doc revpipycontrol.api + stdeb.cfg revpipycontrol/revpipycontrol.py @@ -224,12 +227,6 @@ setup.py - - noindex - - - True - outputDirectory diff --git a/revpipycontrol/locale/de/LC_MESSAGES/revpipycontrol.po b/revpipycontrol/locale/de/LC_MESSAGES/revpipycontrol.po new file mode 100644 index 0000000..5b858a2 --- /dev/null +++ b/revpipycontrol/locale/de/LC_MESSAGES/revpipycontrol.po @@ -0,0 +1,722 @@ +msgid "" +msgstr "" +"Project-Id-Version: RevPiPyControl 0.4.0\n" +"POT-Creation-Date: 2017-07-03 10:36+0200\n" +"PO-Revision-Date: 2017-07-03 10:36+0200\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 1.8.7.1\n" +"X-Poedit-Basepath: ../../..\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Poedit-SourceCharset: UTF-8\n" +"X-Poedit-SearchPath-0: .\n" + +#: revpicheckclient.py:107 revpicheckclient.py:432 revpioption.py:309 +#: revpiplclist.py:205 revpiprogram.py:315 revpiprogram.py:346 +#: revpiprogram.py:410 revpiprogram.py:416 revpiprogram.py:422 +#: revpiprogram.py:430 revpiprogram.py:436 revpiprogram.py:469 +#: revpiprogram.py:553 revpiprogram.py:626 revpiprogram.py:657 +#: revpiprogram.py:670 revpiprogram.py:725 revpiprogram.py:745 +#: revpiprogram.py:753 revpipycontrol.py:363 +msgid "Error" +msgstr "Fehler" + +#: revpicheckclient.py:108 +msgid "" +"Given value for Output '{}' is not valid! \n" +"Reset to '{}'" +msgstr "" +"Angegebener Wert für Output '{}' ist nicht gültig! \n" +"Setze auf '{}' zurück" + +#: revpicheckclient.py:207 +msgid "Devices of RevPi" +msgstr "Devices vom RevPi" + +#: revpicheckclient.py:239 +msgid "Control" +msgstr "Kontrolle" + +#: revpicheckclient.py:243 +msgid "Read all IOs" +msgstr "Lese alle IOs" + +#: revpicheckclient.py:248 +msgid "Read just Inputs" +msgstr "Nur Inputs lesen" + +#: revpicheckclient.py:255 +msgid "Write Outputs" +msgstr "Outputs schreiben" + +#: revpicheckclient.py:261 +msgid "Autorefresh values" +msgstr "Aktualisiere Werte automatisch" + +#: revpicheckclient.py:269 +msgid "Write values to RevPi" +msgstr "Schreibe Werte auf RevPi" + +#: revpicheckclient.py:283 revpiprogram.py:443 revpipycontrol.py:235 +#: revpipycontrol.py:280 revpipycontrol.py:298 revpipycontrol.py:324 +msgid "Warning" +msgstr "Warnung" + +#: revpicheckclient.py:284 +msgid "" +"You want to set outputs on the RevPi! Note that these are set " +"IMMEDIATELY!!! \n" +"If another control program is running on the RevPi, it could interfere and " +"reset the outputs." +msgstr "" +"Sie wollen Outputs auf dem RevPi setzen! Beachten Sie, dass diese SOFORT " +"gesetzt werden!!! \n" +"Wenn auf dem RevPi ein anderes Programm zur Steuerung läuft, könnte dies " +"gestört werden und die Ausgänge wieder zurücksetzen." + +#: revpicheckclient.py:429 +msgid "Error set value of device '{}' Output '{}': {} \n" +msgstr "Fehler beim Setzen der Werte auf Device '{}' bei Output '{}': {}\n" + +#: revpiinfo.py:38 +msgid "RevPi Python PLC info" +msgstr "RevPi Python PLC Information" + +#: revpiinfo.py:51 +msgid "RevPi Python PLC - Control" +msgstr "RevPi Python PLC - Kontrollcenter" + +#: revpiinfo.py:55 +msgid "Version: {}" +msgstr "Version: {}" + +#: revpiinfo.py:78 +msgid "RevPiPyLoad version on RevPi:" +msgstr "RevPiPyLoad Version auf RevPi:" + +#: revpiinfo.py:83 +msgid "not conn." +msgstr "nicht verb." + +#: revpiinfo.py:91 +msgid "" +"\n" +"RevPiModIO, RevPiPyLoad and RevPiPyControl\n" +"are community driven projects. They are all\n" +"free and open source software.\n" +"All of them comes with ABSOLUTELY NO\n" +"WARRANTY, to the extent permitted by \n" +"applicable law.\n" +"\n" +"\n" +"(c) Sven Sager, License: LGPLv3" +msgstr "" +"\n" +"RevPiModIO, RevPiPyLoad und RevPiPyControl\n" +"sind gemeinschaftsorientierte Projekte. Sie sind\n" +"alle frei und Open Source Software.\n" +"Alle kommen mit ABSOLUT KEINER\n" +"GARANTIE, soweit gesetzlich zulässig.\n" +"\n" +"\n" +"(c) Sven Sager, Lizenz: LGPLv3" + +#: revpiinfo.py:138 revpioption.py:232 revpiplclist.py:134 +msgid "Close" +msgstr "Schließen" + +#: revpilogfile.py:41 +msgid "RevPi Python PLC Logs" +msgstr "RevPi Python PLC Logdaten" + +#: revpilogfile.py:54 +msgid "RevPiPyLoad - Logfile" +msgstr "RevPiPyLoad - Logdatei" + +#: revpilogfile.py:58 revpilogfile.py:73 +msgid "Clear screen" +msgstr "Leere Ansicht" + +#: revpilogfile.py:69 +msgid "Python PLC program - Logfile" +msgstr "Python PLC Programm - Logdatei" + +#: revpilogfile.py:157 +msgid "Can not access log file on the RevPi" +msgstr "Auf die Logdatei des RevPi kann nicht zugegriffen werden" + +#: revpioption.py:66 revpioption.py:272 revpioption.py:319 revpiplclist.py:63 +#: revpiplclist.py:181 revpiprogram.py:379 revpiprogram.py:454 +msgid "Question" +msgstr "Frage" + +#: revpioption.py:67 revpiplclist.py:64 +msgid "" +"Do you really want to quit? \n" +"Unsaved changes will be lost" +msgstr "" +"Wollen Sie wirklich beenden? \n" +"Nicht gespeicherte Änderungen gehen verloren" + +#: revpioption.py:77 +msgid "RevPi Python PLC Options" +msgstr "RevPi Python PLC Einstellungen" + +#: revpioption.py:87 +msgid "Start / Stop behavior" +msgstr "Start / Stop Verhalten" + +#: revpioption.py:96 +msgid "Start program automatically" +msgstr "Starte Programm automatisch" + +#: revpioption.py:102 +msgid "Restart program after exit" +msgstr "Starte Programm nach Beenden neu" + +#: revpioption.py:110 +msgid "" +"Set process image to NULL if program\n" +"terminates successfully" +msgstr "" +"Prozessabbild auf NULL setzen, wenn Programm\n" +"erfolgreich beendet wird" + +#: revpioption.py:118 +msgid "" +"Set process image to NULL if program\n" +"terminates with errors" +msgstr "" +"Prozessabbild auf NULL setzen, wenn Programm\n" +"fehlerhaft beendet wird" + +#: revpioption.py:125 +msgid "PLC program" +msgstr "PLC Programm" + +#: revpioption.py:136 +msgid "Python version" +msgstr "Python Version" + +#: revpioption.py:153 +msgid "Python PLC program name" +msgstr "Python PLC Programmname" + +#: revpioption.py:165 +msgid "Program arguments" +msgstr "Programmargumente" + +#: revpioption.py:174 +msgid "Use RevPi as PLC-Slave" +msgstr "RevPi als PLC-Slave verwenden" + +#: revpioption.py:181 +msgid "XML-RPC server" +msgstr "XML-RPC Server" + +#: revpioption.py:193 +msgid "Activate XML-RPC server on RevPi" +msgstr "Aktiviere XML-RPC Server auf RevPi" + +#: revpioption.py:201 +msgid "" +"Allow download of piCtory configuration and\n" +"PLC programm" +msgstr "" +"Download von piCtroy Konfiguration und\n" +"PLC Programm zulassen" + +#: revpioption.py:208 +msgid "" +"Allow upload of piCtory configuration and\n" +"PLC programm" +msgstr "" +"Hochladen von piCtroy Konfiguration und\n" +"PLC Programm zulassen" + +#: revpioption.py:213 +msgid "XML-RPC server port" +msgstr "XML-RPC Serverport" + +#: revpioption.py:227 revpiplclist.py:131 +msgid "Save" +msgstr "Speichern" + +#: revpioption.py:265 revpioption.py:301 revpiplclist.py:197 revpiprogram.py:68 +msgid "Information" +msgstr "Information" + +#: revpioption.py:266 +msgid "You have not made any changes to save." +msgstr "Sie haben keine Änderungen zum Speichern vorgenommen." + +#: revpioption.py:273 +msgid "" +"The settings are now saved on the Revolution Pi. \n" +"\n" +"Should the new settings take effect immediately? \n" +"This means a restart of the service and the PLC program!" +msgstr "" +"Die Einstellungen werden jetzt auf dem Revolution Pi gespeichert.\n" +"\n" +"Sollen die neuen Einstellungen sofort in Kraft treten?\n" +"Dies bedeutet einen Neustart des Dienstes und des laufenden PLC-Programms!" + +#: revpioption.py:302 +msgid "Settings saved" +msgstr "Einstellungen gespeichert" + +#: revpioption.py:310 +msgid "The settings could not be saved. This can happen if values are wrong!" +msgstr "" +"Die Einstellungen konnten nicht gesichert werden. Dies kann passieren, wenn " +"Werte falsch sind!" + +#: revpioption.py:320 +msgid "" +"Are you sure you want to deactivate the XML-RPC server? You will NOT be able " +"to access the Revolution Pi with this program." +msgstr "" +"Soll der XML-RPC Server wirklich beendet werden? Sie können dann NICHT mehr " +"mit diesem Programm auf den Revolution Pi zugreifen." + +#: revpiplclist.py:74 +msgid "RevPi Python PLC connections" +msgstr "RevPi Python PLC Verbindungen" + +#: revpiplclist.py:94 +msgid "Name" +msgstr "Name" + +#: revpiplclist.py:101 +msgid "IP address" +msgstr "IP Adresse" + +#: revpiplclist.py:109 +msgid "Port" +msgstr "Port" + +#: revpiplclist.py:118 +msgid "New" +msgstr "Neu" + +#: revpiplclist.py:121 +msgid "Apply" +msgstr "Übernehmen" + +#: revpiplclist.py:125 +msgid "Remove" +msgstr "Entfernen" + +#: revpiplclist.py:182 +msgid "Do you really want to delete the selected connection '{}'?" +msgstr "Wollen Sie die ausgewählte Verbindung '{}' wirklich löschen?" + +#: revpiplclist.py:198 +msgid "" +"Successfully saved. \n" +"Do you want to close this window?" +msgstr "" +"Verbindungen erfolgreich gespeichert. \n" +"Möchten Sie dieses Fenster jetzt schließen?" + +#: revpiplclist.py:206 +msgid "Failed to save connections" +msgstr "Verbindungen konnten nicht gespeichert werden" + +#: revpiprogram.py:69 +msgid "" +"A PLC program has been uploaded. Please check the PLC options to see if the " +"correct program is specified as the start program." +msgstr "" +"Ein PLC Programm wurde hochgeladen. Bitte prüfen Sie die PLC Optionen, ob " +"dort das richtige Programm als Startprogramm angegeben ist." + +#: revpiprogram.py:78 +msgid "RevPi Python PLC program" +msgstr "RevPi Python PLC Programm" + +#: revpiprogram.py:92 +msgid "PLC python program" +msgstr "PLC Python Programm" + +#: revpiprogram.py:102 revpiprogram.py:104 revpiprogram.py:112 +#: revpiprogram.py:116 +msgid "Files" +msgstr "Dateien" + +#: revpiprogram.py:102 revpiprogram.py:104 revpiprogram.py:501 +#: revpiprogram.py:610 +msgid "Zip archive" +msgstr "Zip Archiv" + +#: revpiprogram.py:102 revpiprogram.py:104 revpiprogram.py:514 +#: revpiprogram.py:640 +msgid "TGZ archive" +msgstr "TGZ Archiv" + +#: revpiprogram.py:104 +msgid "Folder" +msgstr "Verzeichnis" + +#: revpiprogram.py:121 +msgid "Download PLC program as:" +msgstr "PLC Programm herunterladen als:" + +#: revpiprogram.py:131 +msgid "include piCtory configuration" +msgstr "inkl. piCtory Konfiguration" + +#: revpiprogram.py:136 revpiprogram.py:180 revpiprogram.py:201 +msgid "Download" +msgstr "Herunterladen" + +#: revpiprogram.py:141 +msgid "Upload PLC program as:" +msgstr "PLC Programm hochladen als:" + +#: revpiprogram.py:153 +msgid "clean upload folder before upload" +msgstr "Uploadverzeichnis vor dem Upload leeren" + +#: revpiprogram.py:160 +msgid "includes piCtory configuration" +msgstr "enthält piCtory Konfiguration" + +#: revpiprogram.py:166 revpiprogram.py:188 +msgid "Upload" +msgstr "Hochladen" + +#: revpiprogram.py:172 +msgid "piCtory configuration" +msgstr "piCtory Konfiguration" + +#: revpiprogram.py:176 +msgid "Download piCtory configuration" +msgstr "piCtory Konfiguration herunterladen" + +#: revpiprogram.py:183 +msgid "Upload piCtory configuration" +msgstr "piCtory Konfiguration hochladen" + +#: revpiprogram.py:194 +msgid "piControl0 process image" +msgstr "piControl0 Prozessabbild" + +#: revpiprogram.py:197 +msgid "Download process image dump" +msgstr "Prozessabbild Dump herunterladen" + +#: revpiprogram.py:207 +msgid "Reset piControl" +msgstr "piControl zurücksetzen" + +#: revpiprogram.py:210 +msgid "Execute piControlReset" +msgstr "piControlReset ausführen" + +#: revpiprogram.py:214 +msgid "execute" +msgstr "ausführen" + +#: revpiprogram.py:220 revpipycontrol.py:107 +msgid "Exit" +msgstr "Beenden" + +#: revpiprogram.py:305 revpiprogram.py:336 revpiprogram.py:497 +#: revpiprogram.py:510 +msgid "Save as..." +msgstr "Speichern unter..." + +#: revpiprogram.py:308 revpiprogram.py:371 +msgid "piCtory config" +msgstr "piCtory Konfiguration" + +#: revpiprogram.py:308 revpiprogram.py:339 revpiprogram.py:371 +#: revpiprogram.py:501 revpiprogram.py:514 revpiprogram.py:585 +#: revpiprogram.py:610 revpiprogram.py:640 +msgid "All files" +msgstr "Alle Dateien" + +#: revpiprogram.py:316 revpiprogram.py:347 revpiprogram.py:554 +msgid "Could not load and save file!" +msgstr "Datei konnte nicht geladen und gespeichert werden!" + +#: revpiprogram.py:321 revpiprogram.py:352 revpiprogram.py:391 +#: revpiprogram.py:399 revpiprogram.py:463 revpiprogram.py:559 +#: revpiprogram.py:715 +msgid "Success" +msgstr "Erfolgreich" + +#: revpiprogram.py:322 revpiprogram.py:353 revpiprogram.py:560 +msgid "File successfully loaded and saved." +msgstr "Dateien erfolgreich übertragen und gespeichert." + +#: revpiprogram.py:339 +msgid "Imagefiles" +msgstr "Image Dateien" + +#: revpiprogram.py:367 +msgid "Open piCtory file..." +msgstr "piCtory Datei öffnen..." + +#: revpiprogram.py:380 +msgid "" +"Should the piControl driver be reset after uploading the piCtory " +"configuration?" +msgstr "" +"Soll nach dem Hochladen der piCtory Konfiguration ein Reset am piControl " +"Treiber durchgeführt werden?" + +#: revpiprogram.py:392 +msgid "" +"The transfer of the piCtory configuration and the reset of piControl have " +"been successfully executed." +msgstr "" +"Die Übertragung der piCtory Konfiguration und der Reset von piControl wurden " +"erfolgreich ausgeführt." + +#: revpiprogram.py:400 +msgid "The piCtory configuration was successfully transferred." +msgstr "" +"Die Übertragung der piCtory Konfiguration wurde erfolgreich ausgeführt." + +#: revpiprogram.py:411 +msgid "Can not process the transferred file." +msgstr "Kann die übertragene Datei nicht verarbeiten." + +#: revpiprogram.py:417 +msgid "Can not find main elements in piCtory file." +msgstr "Kann Hauptelemente in piCtory datei nicht finden." + +#: revpiprogram.py:423 +msgid "" +"Contained devices could not be found on Revolution Pi. The configuration may " +"be from a newer piCtory version!" +msgstr "" +"Enthaltene Geräte konnten auf dem Revolution Pi nicht gefunden werden. " +"Möglicherweise stammt die Konfiguration von einer neueren piCtory Version!" + +#: revpiprogram.py:431 +msgid "Could not load RAP catalog on Revolution Pi." +msgstr "Konnte RAP Katalog auf dem Revolution Pi nicht laden." + +#: revpiprogram.py:437 +msgid "The piCtory configuration could not be written on the Revolution Pi." +msgstr "" +"Die piCtory Konfiguration konnte auf dem Revolution Pi nicht geschrieben " +"werden." + +#: revpiprogram.py:444 +msgid "" +"The piCtroy configuration has been saved successfully. \n" +"An error occurred on piControl reset!" +msgstr "" +"Die piCtroy Konfiguration wurde erfolgreich gespeichert. \n" +"Beim piControl Reset trat allerdings ein Fehler auf!" + +#: revpiprogram.py:455 +msgid "" +"Are you sure to reset piControl? \n" +"The process image and the piBridge are interrupted !!!" +msgstr "" +"Soll piControlReset wirklich durchgeführt werden? \n" +"Das Prozessabbild und die piBridge werden dann unterbrochen!!!" + +#: revpiprogram.py:464 +msgid "piControl reset executed successfully" +msgstr "piControl reset wurde erfolgreich ausgeführt" + +#: revpiprogram.py:470 +msgid "piControl reset could not be executed successfully" +msgstr "piControl Reset konnte nicht erfolgreich durchgeführt werden" + +#: revpiprogram.py:484 +msgid "Directory to save" +msgstr "Verzeichnis zum speichern" + +#: revpiprogram.py:595 +msgid "Folder to upload" +msgstr "Verzeichnis zum Hochladen" + +#: revpiprogram.py:606 +msgid "Upload Zip archive..." +msgstr "Zip Archiv hochladen..." + +#: revpiprogram.py:627 +msgid "The specified file is not a ZIP archive." +msgstr "Die angegebene Datei ist kein ZIP Archiv." + +#: revpiprogram.py:636 +msgid "Upload TarGz archiv..." +msgstr "TarGz Archiv hochladen..." + +#: revpiprogram.py:658 +msgid "The specified file is not a TAR archive." +msgstr "Die angegebene Datei ist kein TAR Archiv." + +#: revpiprogram.py:671 +msgid "There was an error deleting the files on the Revolution Pi." +msgstr "" +"Beim Löschen der Dateien auf dem Revolution Pi ist ein Fehler aufgetreten." + +#: revpiprogram.py:716 +msgid "The PLC program was transferred successfully." +msgstr "Das PLC Programm wurde erfolgreich übertragen." + +#: revpiprogram.py:726 +msgid "There is no piCtory configuration in this archive." +msgstr "Es wurde keine piCtory Konfiguration im Archiv gefunden." + +#: revpiprogram.py:746 +msgid "The Revolution Pi could not process some parts of the transmission." +msgstr "Der Revoluton Pi konnte Teile der Übertragung nicht verarbeiten." + +#: revpiprogram.py:754 +msgid "Errors occurred during transmission" +msgstr "Bei der Übertragung traten Fehler auf" + +#: revpipycontrol.py:105 +msgid "Connections..." +msgstr "Verbindungen..." + +#: revpipycontrol.py:108 +msgid "Main" +msgstr "Datei" + +#: revpipycontrol.py:116 +msgid "Visit website..." +msgstr "Webseite besuchen..." + +#: revpipycontrol.py:118 +msgid "Info..." +msgstr "Info..." + +#: revpipycontrol.py:119 +msgid "Help" +msgstr "Hilfe" + +#: revpipycontrol.py:127 +msgid "PLC start" +msgstr "PLC Start" + +#: revpipycontrol.py:132 +msgid "PLC stop" +msgstr "PLC Stopp" + +#: revpipycontrol.py:137 +msgid "PLC restart" +msgstr "PLC Neustart" + +#: revpipycontrol.py:142 +msgid "PLC logs" +msgstr "PLC Logs" + +#: revpipycontrol.py:153 +msgid "PLC watch mode" +msgstr "PLC watch Modus" + +#: revpipycontrol.py:170 +msgid "PLC log..." +msgstr "PLC Log..." + +#: revpipycontrol.py:172 +msgid "PLC options..." +msgstr "PLC Optionen..." + +#: revpipycontrol.py:174 +msgid "PLC program..." +msgstr "PLC Programm..." + +#: revpipycontrol.py:178 +msgid "Disconnect" +msgstr "Trennen" + +#: revpipycontrol.py:183 +msgid "Connect" +msgstr "Verbinden" + +#: revpipycontrol.py:236 +msgid "" +"The watch mode ist not supported in version {} of RevPiPyLoad on your RevPi! " +"You need at least version 0.4.0. Or the python3-revpimodio module is not " +"installton your RevPi at least version 0.15.0." +msgstr "" +"Der Watch-Modus wird von der RevPiPyLoad Version {} auf dem RevPi nicht " +"unterstützt! Sie benötigen mindestens Version 0.4.0. Oder das python3-" +"revpimodio Modul ist auf dem RevPi nicht in min. Version 0.15.0 installiert." + +#: revpipycontrol.py:281 +msgid "" +"This version of Logviewer ist not supported in version {} of RevPiPyLoad on " +"your RevPi! You need at least version 0.4.1." +msgstr "" +"Diese Version vom Logbetrachter wird in der RevPiPyLoad Version {} auf Ihrem " +"RevPi nicht unterstützt! Sie benötigen mindestens Version 0.4.1." + +#: revpipycontrol.py:299 revpipycontrol.py:325 +msgid "" +"XML-RPC access mode in the RevPiPyLoad configuration is too small to access " +"this dialog!" +msgstr "" +"Der XML-RPC Modus ist beim RevPiPyLoad nicht hoch genug eingestellt, um " +"diesen Dialog zu verwenden!" + +#: revpipycontrol.py:364 +msgid "Can not reach server!" +msgstr "Server ist nicht erreichbar!" + +#~ msgid "" +#~ "The watch mode ist not supported in version {} of RevPiPyLoad on your " +#~ "RevPi! You need at least version 0.4.0." +#~ msgstr "" +#~ "Der Watch-Modus wird von der RevPiPyLoad Version {} auf dem RevPi nicht " +#~ "unterstützt! Sie benötigen mindestens Version 0.4.0." + +#~ msgid "" +#~ "XML-RPC access mode in the RevPiPyLoad configuration is too small to " +#~ "access this dialog" +#~ msgstr "" +#~ "Der XML-RPC Modus ist beim RevPiPyLoad nicht hoch genug eingestellt, um " +#~ "diesen Dialog zu verwenden!" + +#~ msgid "Do you really want to delete the selected connection '{}'" +#~ msgstr "Wollen Sie die ausgewählte Verbindung '{}' wirklich löschen?" + +#~ msgid "piControl0 prozess image" +#~ msgstr "piControl0 Prozessabbild" + +#~ msgid "piControlReset executed successfully" +#~ msgstr "piControl Reset war erfolgreich" + +#~ msgid "" +#~ "XML-RPC access mode in the RevPiPyLoad configuration is to small to " +#~ "access this dialog" +#~ msgstr "" +#~ "Der XML-RPC Modus ist beim RevPiPyLoad nicht hoch genug eingestellt, um " +#~ "diesen Dialog zu verwenden!" + +#~ msgid "Quit" +#~ msgstr "Beenden" + +#~ msgid "The transfer was successful." +#~ msgstr "Die Übertragung war erfolgreich." + +#~ msgid "" +#~ "The transfer of the piCtory configuration and the reset of piControl have " +#~ "been successfully executed" +#~ msgstr "" +#~ "Die Übertragung der piCtory Konfiguration und der Reset von piControl " +#~ "wurden erfolgreich ausgeführt" + +#~ msgid "The piCtory configuration was successfully transferred" +#~ msgstr "" +#~ "Die Übertragung der piCtory Konfiguration wurde erfolgreich ausgeführt" + +#~ msgid "Given value for Output '{}' is not valid! Reset to '{}'" +#~ msgstr "" +#~ "Angegebener Wert bei Output '{}' ist nicht gültig! Setze auf '{}' zurück" diff --git a/revpipycontrol/mytools.py b/revpipycontrol/mytools.py new file mode 100644 index 0000000..fb90979 --- /dev/null +++ b/revpipycontrol/mytools.py @@ -0,0 +1,46 @@ +# +# RevPiPyControl +# +# Webpage: https://revpimodio.org/revpipyplc/ +# (c) Sven Sager, License: LGPLv3 +# +# -*- coding: utf-8 -*- +import gettext +import locale +import sys +from os.path import dirname +from os.path import join as pathjoin + + +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 + @return root dir + + """ + if getattr(sys, "frozen", False): + return pathjoin(dirname(sys.executable), filename) + else: + return pathjoin(dirname(__file__), filename) + + +def gettrans(proglang=None): + + # Sprache auswählen + if proglang is None: + # Autodetect Language or switch to static + # proglang = "en" + proglang = locale.getdefaultlocale()[0].split('_')[0] + + # Übersetzungen laden + trans = gettext.translation( + "revpipycontrol", + addroot("locale"), + languages=[proglang], + fallback=True + ) + return trans.gettext diff --git a/revpipycontrol/revpicheckclient.py b/revpipycontrol/revpicheckclient.py index 1076251..9226f2e 100644 --- a/revpipycontrol/revpicheckclient.py +++ b/revpipycontrol/revpicheckclient.py @@ -9,216 +9,429 @@ import pickle import tkinter -from argparse import ArgumentParser -from concurrent.futures import ThreadPoolExecutor -from time import sleep -from xmlrpc.client import ServerProxy, Binary, MultiCall +import tkinter.messagebox as tkmsg +from mytools import gettrans +from threading import Lock +from xmlrpc.client import MultiCall + +# Übersetzung laden +_ = gettrans() class RevPiCheckClient(tkinter.Frame): - def __init__(self, master, xmlcli): + def __init__(self, master, xmlcli, xmlmode=0): """Instantiiert MyApp-Klasse.""" super().__init__(master) - self.pack(fill="both", expand=True) + # XML-Daten abrufen + self.xmlmode = xmlmode self.cli = xmlcli + self.cli.psstart() + self.lst_devices = self.cli.ps_devices() + self.dict_devices = {v[0]: v[1] for v in self.lst_devices} + self.lst_devices = [d[0] for d in self.lst_devices] + self.dict_inps = pickle.loads(self.cli.ps_inps().data) + self.dict_outs = pickle.loads(self.cli.ps_outs().data) + self.err_workvalues = 0 + self.max_errors = 25 - self.lst_devices = self.cli.get_devicenames() - self.lst_group = [] - self.dict_inpvar = {} - self.dict_outvar = {} + self.lk = Lock() + self.dict_wins = {} + self.__checkwrite = True + self.__lockedvar = None + self.__oldvalue = None self.autorw = tkinter.BooleanVar() - self.fut_autorw = None + self.dowrite = tkinter.BooleanVar() # Fenster aufbauen self._createwidgets() # Aktuelle Werte einlesen - self.readvalues() + self.refreshvalues() - def _autorw(self): - dict_inp = {} - dict_out = {} + def __chval(self, device, io, event=None): + u"""Schreibt neuen Output Wert auf den RevPi.""" + if self.dowrite.get() and self._warnwrite(): + with self.lk: + self.validatereturn( + self.cli.ps_setvalue(device, io[0], io[5].get()) + ) - 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)) + # Alles neu einlesen wenn nicht AutoRW aktiv ist + if not self.autorw.get(): + self.refreshvalues() - dict_inp = self.cli.refreshvalues( - Binary(pickle.dumps(dict_out, 3)) + self.__lockedvar = None + + def __hidewin(self, win, event=None): + u"""Verbergt übergebenes Fenster. + @param win Fenster zum verbergen + @param event Tkinter Event""" + win.withdraw() + + def __saveoldvalue(self, event, tkvar): + u"""Speichert bei Keypress aktuellen Wert für wiederherstellung.""" + if self.__lockedvar is None: + self.__lockedvar = tkvar + try: + self.__oldvalue = tkvar.get() + except Exception: + pass + + def __showwin(self, win): + u"""Zeigt oder verbergt übergebenes Fenster. + @param win Fenster zum anzeigen/verbergen""" + if win.winfo_viewable(): + win.withdraw() + else: + win.deiconify() + + def __spinboxkey(self, device, io, event=None): + u"""Prüft die Eingabe auf plausibilität. + @param event tkinter Event + @param io IO Liste mit tkinter Variable""" + # io = [name,bytelen,byteaddr,bmk,bitaddress,(tkinter_var)] + try: + newvalue = io[5].get() + # Wertebereich prüfen + if newvalue < 0 or newvalue > 256 ** io[1] - 1: + raise ValueError("too big") + + self.__chval(device, io) + + except Exception: + io[5].set(self.__oldvalue) + tkmsg.showerror( + _("Error"), + _("Given value for Output '{}' is not valid! \nReset to ""'{}'" + "").format(self.dict_devices[device], self.__oldvalue), + parent=self.dict_wins[device] ) - 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 onfrmconf(self, canvas): - canvas.configure(scrollregion=canvas.bbox("all")) + # Focus zurücksetzen + event.widget.focus_set() def _createiogroup(self, device, frame, iotype): - """Erstellt IO-Gruppen.""" - # IOs generieren - canvas = tkinter.Canvas(frame, borderwidth=0, width=180, heigh=800) + u"""Erstellt IO-Gruppen. + + @param device Deviceposition + @param frame tkinter Frame + @param iotype 'inp' oder 'out' als str() + + """ + # IO-Typen festlegen + if iotype == "inp": + lst_io = self.dict_inps[device] + else: + lst_io = self.dict_outs[device] + + # Fensterinhalt aufbauen + calc_heigh = len(lst_io) * 21 + canvas = tkinter.Canvas( + frame, + borderwidth=0, + width=180, + heigh=calc_heigh if calc_heigh <= 600 else 600 + ) s_frame = tkinter.Frame(canvas) vsb = tkinter.Scrollbar(frame, orient="vertical", command=canvas.yview) canvas.configure(yscrollcommand=vsb.set) + # Scrollrad Linux + canvas.bind( + "", + lambda x: canvas.yview_scroll(-1, "units") + ) + canvas.bind( + "", + lambda x: canvas.yview_scroll(1, "units") + ) + 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.onfrmconf(canvas) + "", lambda event, canvas=canvas: self._onfrmconf(canvas) ) + # IOs generieren rowcount = 0 - for io in self.cli.get_iolist(device, iotype): - # io = [name,default,anzbits,adressbyte,export,adressid,bmk,bitaddress,tkinter_var] + for io in lst_io: + # io = [name,bytelen,byteaddr,bmk,bitaddress,(tkinter_var)] tkinter.Label(s_frame, text=io[0]).grid( column=0, row=rowcount, sticky="w" ) - if io[7] >= 0: + if io[4] >= 0: var = tkinter.BooleanVar() check = tkinter.Checkbutton(s_frame) + check["command"] = \ + lambda device=device, io=io: self.__chval(device, io) check["state"] = "disabled" if iotype == "inp" else "normal" check["text"] = "" check["variable"] = var check.grid(column=1, row=rowcount) else: var = tkinter.IntVar() - txt = tkinter.Spinbox(s_frame, to=256) + txt = tkinter.Spinbox(s_frame, to=256 ** io[1] - 1) + txt.bind( + "", + lambda event, tkvar=var: self.__saveoldvalue(event, tkvar) + ) + txt.bind( + "", + lambda event, device=device, io=io: + self.__spinboxkey(device, io, event) + ) + txt["command"] = \ + lambda device=device, io=io: self.__chval(device, io) txt["state"] = "disabled" if iotype == "inp" else "normal" - txt["width"] = 4 + txt["width"] = 5 txt["textvariable"] = var txt.grid(column=1, row=rowcount) - # Steuerelementvariable in IO übernehmen + # Steuerelementvariable in IO übernehmen (mutabel) 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") + cFxPxy53 = {"fill": "x", "padx": 5, "pady": 3} + + devgrp = tkinter.LabelFrame(self) + devgrp["text"] = _("Devices of RevPi") + devgrp.pack(expand=True, fill="both", side="left") for dev in self.lst_devices: - # Variablen vorbereiten - self.dict_inpvar[dev] = [] - self.dict_outvar[dev] = [] + win = tkinter.Toplevel(self) + win.wm_title(self.dict_devices[dev]) + win.protocol( + "WM_DELETE_WINDOW", + lambda win=win: self.__hidewin(win) + ) + win.resizable(False, True) + win.withdraw() + self.dict_wins[dev] = win # Devicegruppe erstellen - group = tkinter.LabelFrame(self) - group["text"] = dev + group = tkinter.LabelFrame(win) + group["text"] = self.dict_devices[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") + # Button erstellen + btn = tkinter.Button(devgrp) + btn["command"] = lambda win=win: self.__showwin(win) + btn["text"] = self.dict_devices[dev] + btn.pack(**cFxPxy53) - 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") + # Steuerungsfunktionen + cntgrp = tkinter.LabelFrame(self) + cntgrp["text"] = _("Control") + cntgrp.pack(expand=True, fill="both", side="right") - self.btn_read = tkinter.Button(self) - self.btn_read["text"] = "LESEN" + self.btn_refresh = tkinter.Button(cntgrp) + self.btn_refresh["text"] = _("Read all IOs") + self.btn_refresh["command"] = self.refreshvalues + self.btn_refresh.pack(**cFxPxy53) + + self.btn_read = tkinter.Button(cntgrp) + self.btn_read["text"] = _("Read just Inputs") self.btn_read["command"] = self.readvalues - self.btn_read.pack(side="bottom", fill="x") + self.btn_read.pack(**cFxPxy53) - check = tkinter.Checkbutton(self) - check["command"] = self.toggleauto - check["text"] = "autoupdate" - check["variable"] = self.autorw - check.pack(side="bottom") + self.btn_write = tkinter.Button(cntgrp) + self.btn_write["state"] = "normal" if self.xmlmode >= 3 \ + else "disabled" + self.btn_write["text"] = _("Write Outputs") + self.btn_write["command"] = self.writevalues + self.btn_write.pack(**cFxPxy53) - def _readvaluesdev(self, device, iotype): - """Ruft alle aktuellen Werte fuer das Device ab.""" - # Multicall vorbereiten - mc_values = MultiCall(self.cli) + self.chk_auto = tkinter.Checkbutton(cntgrp) + self.chk_auto["command"] = self.toggleauto + self.chk_auto["text"] = _("Autorefresh values") + self.chk_auto["variable"] = self.autorw + self.chk_auto.pack(anchor="w") - if iotype == "inp": - lst_ios = self.dict_inpvar[device] - elif iotype == "out": - lst_ios = self.dict_outvar[device] + self.chk_dowrite = tkinter.Checkbutton(cntgrp) + self.chk_dowrite["command"] = self.togglewrite + self.chk_dowrite["state"] = "normal" if self.xmlmode >= 3 \ + and self.autorw.get() else "disabled" + self.chk_dowrite["text"] = _("Write values to RevPi") + self.chk_dowrite["variable"] = self.dowrite + self.chk_dowrite.pack(anchor="w") - for io in lst_ios: - mc_values.get_iovalue(device, io[0]) + def _onfrmconf(self, canvas): + u"""Erstellt Fenster in einem Canvas. + @param canvas Canvas in dem Objekte erstellt werden sollen""" + canvas.configure(scrollregion=canvas.bbox("all")) - i = 0 - for value in mc_values(): - value = pickle.loads(value.data) - if type(value) == bytes: - value = int.from_bytes(value, byteorder="little") + def _warnwrite(self): + u"""Warnung für Benutzer über Schreibfunktion einmal fragen. + @return True, wenn Warnung einmal mit OK bestätigt wurde""" + if self.__checkwrite: + self.__checkwrite = not tkmsg.askokcancel( + _("Warning"), + _("You want to set outputs on the RevPi! Note that these are " + "set IMMEDIATELY!!! \nIf another control program is " + "running on the RevPi, it could interfere and reset the " + "outputs."), + icon=tkmsg.WARNING, + parent=self.master + ) + return not self.__checkwrite - lst_ios[i][8].set(value) - i += 1 + def _workvalues(self, io_dicts=None, writeout=False): + u"""Alle Werte der Inputs und Outputs abrufen. - 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] + @param io_dicts Arbeit nur für dieses Dict() + @param writeout Änderungen auf RevPi schreiben + @return None - for io in lst_ios: - mc_values.set_iovalue(device, io[0], pickle.dumps(io[8].get(), 3)) + """ + # Abfragelisten vorbereiten + if io_dicts is None: + io_dicts = [self.dict_inps, self.dict_outs] - # Multicall ausführen - mc_values() + # Werte abrufen + with self.lk: + try: + ba_values = bytearray(self.cli.ps_values().data) + self.err_workvalues = 0 + except: + if self.autorw.get(): + self.err_workvalues += 1 + else: + self.err_workvalues = self.max_errors + + if self.err_workvalues >= self.max_errors: + self.hideallwindows() + self.pack_forget() + + return None + + # Multicall zum Schreiben vorbereiten + if writeout: + xmlmc = MultiCall(self.cli) + + for dev in self.dict_devices: + # io = [name,bytelen,byteaddr,bmk,bitaddress,(tkinter_var)] + + # IO Typ verarbeiten + for iotype in io_dicts: + # ios verarbeiten + for io in iotype[dev]: + + # Gesperrte Variable überspringen + if io[5] == self.__lockedvar: + continue + + # Bytes umwandeln + int_byte = int.from_bytes( + ba_values[io[2]:io[2] + io[1]], byteorder="little" + ) + if io[4] >= 0: + # Bit-IO + new_val = bool(int_byte & 1 << io[4]) + if writeout and new_val != io[5].get(): + xmlmc.ps_setvalue(dev, io[0], io[5].get()) + else: + io[5].set(new_val) + else: + # Byte-IO + if writeout and int_byte != io[5].get(): + xmlmc.ps_setvalue(dev, io[0], io[5].get()) + else: + io[5].set(int_byte) + + # Werte per Multicall schreiben + if writeout: + with self.lk: + self.validatereturn(xmlmc()) + + def hideallwindows(self): + u"""Versteckt alle Fenster.""" + for win in self.dict_wins: + self.dict_wins[win].withdraw() def readvalues(self): - """Alle Werte der Inputs und Outputs abrufen.""" - # Werte aus Prozessabbild einlesen - self.cli.readprocimg() + u"""Ruft nur Input Werte von RevPi ab und aktualisiert Fenster.""" + if not self.autorw.get(): + self._workvalues([self.dict_inps]) - for dev in self.lst_devices: - self._readvaluesdev(dev, "inp") - self._readvaluesdev(dev, "out") + def refreshvalues(self): + u"""Ruft alle IO Werte von RevPi ab und aktualisiert Fenster.""" + if not self.autorw.get(): + self._workvalues() + + def tmr_workvalues(self): + u"""Timer für zyklische Abfrage. + @return None""" + # Verbleibener Timer könnte schon ungültig sein + if not self.autorw.get(): + try: + self.chk_auto["state"] = "normal" + except: + pass + return None + + self._workvalues() + + self.master.after(200, self.tmr_workvalues) 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) + u"""Schaltet zwischen Autorefresh um und aktualisiert Widgets.""" + stateval = "disabled" if self.autorw.get() else "normal" + self.btn_refresh["state"] = stateval + self.btn_read["state"] = stateval + self.btn_write["state"] = stateval if self.xmlmode >= 3 \ + else "disabled" + + self.chk_dowrite["state"] = "normal" if self.xmlmode >= 3 \ + and self.autorw.get() else "disabled" + + if self.autorw.get(): + self.tmr_workvalues() + else: + self.chk_auto["state"] = "disabled" + self.dowrite.set(False) + + def togglewrite(self): + u"""Schaltet zwischen DoWrite um und aktiviert Schreibfunktion.""" + if self._warnwrite(): + self.refreshvalues() + else: + self.dowrite.set(False) + + def validatereturn(self, returnlist): + u"""Überprüft die Rückgaben der setvalue Funktion. + @param returnlist list() der xml Rückgabe""" + if type(returnlist[0]) != list: + returnlist = [returnlist] + + str_errmsg = "" + for lst_result in returnlist: + # [device, io, status, msg] + + if not lst_result[2]: + # Fehlermeldungen erstellen + devicename = self.dict_devices[lst_result[0]] + str_errmsg += _( + "Error set value of device '{}' Output '{}': {} \n" + ).format(devicename, lst_result[1], lst_result[3]) + if str_errmsg != "": + tkmsg.showerror(_("Error"), str_errmsg) 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() + u"""Schreibt geänderte Outputs auf den RevPi.""" + if self._warnwrite() and not self.autorw.get(): + self._workvalues([self.dict_outs], True) diff --git a/revpipycontrol/revpiinfo.py b/revpipycontrol/revpiinfo.py new file mode 100644 index 0000000..775ba9b --- /dev/null +++ b/revpipycontrol/revpiinfo.py @@ -0,0 +1,143 @@ +# +# RevPiPyControl +# +# Webpage: https://revpimodio.org/revpipyplc/ +# (c) Sven Sager, License: LGPLv3 +# +# -*- coding: utf-8 -*- +import tkinter +import tkinter.font as tkf +import webbrowser +from mytools import gettrans + +# Übersetzung laden +_ = gettrans() + + +class RevPiInfo(tkinter.Frame): + + def __init__(self, master, xmlcli, version): + u"""Init RevPiLogfile-Class.""" + self.master = master + self.xmlcli = xmlcli + + # Systemvariablen + self.version = version + + # Fenster bauen + self._createwidgets() + + def _checkclose(self, event=None): + u"""Prüft ob Fenster beendet werden soll. + @param event tkinter-Event""" + self.master.destroy() + + def _createwidgets(self, extended=False): + u"""Erstellt alle Widgets.""" + super().__init__(self.master) + self.master.wm_title(_("RevPi Python PLC info")) + self.master.bind("", self._checkclose) + self.master.resizable(False, False) + self.pack(fill="both", expand=True) + + # Fonts laden + fntlarge = tkf.Font(size=20, weight="bold") + fntmid = tkf.Font(size=15) + fntbold = tkf.Font(size=10, weight="bold") + + # Kopfdaten + lbl = tkinter.Label(self) + lbl["font"] = fntlarge + lbl["text"] = _("RevPi Python PLC - Control") + lbl.pack(pady=5) + lbl = tkinter.Label(self) + lbl["font"] = fntmid + lbl["text"] = _("Version: {}").format(self.version) + lbl.bind( + "", + lambda event: self._createwidgets(extended=not extended) + ) + lbl.pack(pady=5) + + # Mittelframe geteilt (links/rechts) --------------------------------- + frame_main = tkinter.Frame(self) + frame_main.pack(anchor="nw", fill="x", pady=5) + + # Rows konfigurieren + frame_main.rowconfigure(0, weight=0) + frame_main.rowconfigure(1, weight=1) + frame_main.rowconfigure(2, weight=1) + + int_row = 0 + cpadnw = {"padx": 4, "pady": 2, "sticky": "nw"} + cpadsw = {"padx": 4, "pady": 2, "sticky": "sw"} + + # Linke Seite Mittelframe ---------------- + lbl = tkinter.Label(frame_main) + lbl["font"] = fntbold + lbl["text"] = _("RevPiPyLoad version on RevPi:") + lbl.grid(column=0, row=int_row, **cpadnw) + + lbl = tkinter.Label(frame_main) + lbl["font"] = fntbold + lbl["text"] = _("not conn.") if self.xmlcli is None \ + else self.xmlcli.version() + lbl.grid(column=1, row=int_row, **cpadnw) + + int_row += 1 # 1 + lbl = tkinter.Label(frame_main) + lbl["justify"] = "left" + lbl["text"] = _( + "\nRevPiModIO, RevPiPyLoad and RevPiPyControl\n" + "are community driven projects. They are all\n" + "free and open source software.\n" + "All of them comes with ABSOLUTELY NO\n" + "WARRANTY, to the extent permitted by \n" + "applicable law.\n" + "\n" + "\n" + "(c) Sven Sager, License: LGPLv3" + ) + lbl.grid(column=0, row=int_row, columnspan=2, **cpadnw) + + int_row += 1 # 2 + lbl = tkinter.Label(frame_main) + lbl.bind("", self.visitwebsite) + lbl["fg"] = "blue" + lbl["text"] = "https://revpimodio.org/" + lbl.grid(column=0, row=int_row, columnspan=2, **cpadsw) + + # int_row += 1 # 3 + + # Rechte Seite Mittelframe --------------- + + # Funktionen der Gegenstelle + if self.xmlcli is not None: + frame_func = tkinter.Frame(frame_main) + txt_xmlfunc = tkinter.Text(frame_func, width=30, height=15) + scr_xmlfunc = tkinter.Scrollbar(frame_func) + if extended: + txt_xmlfunc.insert(tkinter.END, "\n".join( + self.xmlcli.system.listMethods() + )) + elif "get_filelist" in self.xmlcli.system.listMethods(): + txt_xmlfunc.insert(tkinter.END, "\n".join( + self.xmlcli.get_filelist() + )) + txt_xmlfunc["yscrollcommand"] = scr_xmlfunc.set + txt_xmlfunc["state"] = "disabled" + scr_xmlfunc["command"] = txt_xmlfunc.yview + txt_xmlfunc.pack(side="left") + scr_xmlfunc.pack(fill="y", side="right") + if txt_xmlfunc.get(1.0) != "\n": + frame_func.grid(column=3, row=0, rowspan=int_row + 1, **cpadnw) + + # Unten Beenden-Button ----------------------------------------------- + self.btnapplog = tkinter.Button(self) + self.btnapplog["command"] = self._checkclose + self.btnapplog["text"] = _("Close") + self.btnapplog.pack(fill="x", padx=100) + + def visitwebsite(self, event=None): + u"""Öffnet auf dem System einen Webbrowser zur Projektseite.""" + webbrowser.open("https://revpimodio.org") diff --git a/revpipycontrol/revpilogfile.py b/revpipycontrol/revpilogfile.py index 0de9088..5595303 100644 --- a/revpipycontrol/revpilogfile.py +++ b/revpipycontrol/revpilogfile.py @@ -5,22 +5,40 @@ # (c) Sven Sager, License: LGPLv3 # # -*- coding: utf-8 -*- -import pickle import tkinter +from mytools import gettrans + +# Übersetzung laden +_ = gettrans() class RevPiLogfile(tkinter.Frame): def __init__(self, master, xmlcli): + u"""Init RevPiLogfile-Class.""" super().__init__(master) + self.master.bind("", self._checkclose) self.pack(fill="both", expand=True) self.xmlcli = xmlcli + # Systemvariablen + self.loadblock = 16384 + self.errapp = 0 + self.errplc = 0 + self.mrkapp = 0 + self.mrkplc = 0 + # Fenster bauen self._createwidgets() + def _checkclose(self, event=None): + u"""Prüft ob Fenster beendet werden soll. + @param event tkinter-Event""" + self.master.destroy() + def _createwidgets(self): - self.master.wm_title("RevPi Python PLC Logs") + u"""Erstellt alle Widgets.""" + self.master.wm_title(_("RevPi Python PLC Logs")) self.rowconfigure(0, weight=0) self.rowconfigure(1, weight=1) @@ -33,11 +51,11 @@ class RevPiLogfile(tkinter.Frame): # PLC Log self.lblapplog = tkinter.Label(self) - self.lblapplog["text"] = "RevPyPyLoad - Logfile" + self.lblapplog["text"] = _("RevPiPyLoad - Logfile") self.lblapplog.grid(column=0, row=0, sticky="w") self.btnapplog = tkinter.Button(self) self.btnapplog["command"] = self.btn_clearplc - self.btnapplog["text"] = "Clear screen" + self.btnapplog["text"] = _("Clear screen") self.btnapplog.grid(column=1, row=0, sticky="e") self.plclog = tkinter.Text(self) self.plcscr = tkinter.Scrollbar(self) @@ -48,11 +66,11 @@ class RevPiLogfile(tkinter.Frame): # APP Log self.lblapplog = tkinter.Label(self) - self.lblapplog["text"] = "Python PLC program - Logfile" + self.lblapplog["text"] = _("Python PLC program - Logfile") self.lblapplog.grid(column=3, row=0, sticky="w") self.btnapplog = tkinter.Button(self) self.btnapplog["command"] = self.btn_clearapp - self.btnapplog["text"] = "Clear screen" + self.btnapplog["text"] = _("Clear screen") self.btnapplog.grid(column=4, row=0, sticky="e") self.applog = tkinter.Text(self) self.appscr = tkinter.Scrollbar(self) @@ -61,47 +79,92 @@ class RevPiLogfile(tkinter.Frame): self.applog["yscrollcommand"] = self.appscr.set self.appscr["command"] = self.applog.yview - self.get_applog() - self.get_plclog() - - # Timer zum nachladen aktivieren - self.master.after(1000, self.get_applines) - self.master.after(1000, self.get_plclines) + # Logtimer zum Laden starten + self.get_applog(full=True) + self.get_plclog(full=True) def btn_clearapp(self): + u"""Leert die Logliste der App.""" self.applog.delete(1.0, tkinter.END) def btn_clearplc(self): + u"""Leert die Logliste des PLC.""" self.plclog.delete(1.0, tkinter.END) - def get_applines(self): - roll = self.applog.yview()[1] == 1.0 + def get_applog(self, full=False): + u"""Ruft App Logbuch ab. + @param full Ganzes Logbuch laden""" + + # Logs abrufen und letzte Position merken try: - for line in pickle.loads(self.xmlcli.get_applines().data): - self.applog.insert(tkinter.END, line) + self.mrkapp = self._load_log( + self.applog, self.xmlcli.load_applog, self.mrkapp, full + ) + self.errapp = 0 except: - pass - if roll: - self.applog.see(tkinter.END) - self.master.after(1000, self.get_applines) + self.errapp += 1 - def get_applog(self): - self.applog.delete(1.0, tkinter.END) - self.applog.insert(1.0, pickle.loads(self.xmlcli.get_applog().data)) - self.applog.see(tkinter.END) + # Timer neu starten + self.master.after(1000, self.get_applog) - def get_plclines(self): - roll = self.plclog.yview()[1] == 1.0 + def get_plclog(self, full=False): + u"""Ruft PLC Logbuch ab. + @param full Ganzes Logbuch laden""" + + # Logs abrufen und letzte Position merken try: - for line in pickle.loads(self.xmlcli.get_plclines().data): - self.plclog.insert(tkinter.END, line) + self.mrkplc = self._load_log( + self.plclog, self.xmlcli.load_plclog, self.mrkplc, full + ) + self.errplc = 0 except: - pass - if roll: - self.plclog.see(tkinter.END) - self.master.after(1000, self.get_plclines) + self.errplc += 1 - def get_plclog(self): - self.plclog.delete(1.0, tkinter.END) - self.plclog.insert(1.0, pickle.loads(self.xmlcli.get_plclog().data)) - self.plclog.see(tkinter.END) + # Timer neu starten + self.master.after(1000, self.get_plclog) + + def _load_log(self, textwidget, xmlcall, startposition, full): + u"""Läd die angegebenen Logfiles herunter. + + @param textwidget Widget in das Logs eingefügt werden sollen + @param xmlcall xmlrpc Funktion zum Abrufen der Logdaten + @param startposition Startposition ab der Logdaten kommen sollen + @param full Komplettes Logbuch laden + @return Ende der Datei (neue Startposition) + + """ + roll = textwidget.yview()[1] == 1.0 + startposition = 0 if full else startposition + logbytes = b'' + while True: + # Datenblock vom XML-RPC Server holen + bytebuff = xmlcall(startposition, self.loadblock).data + + logbytes += bytebuff + startposition += len(bytebuff) + + # Prüfen ob alle Daten übertragen wurden + if len(bytebuff) < self.loadblock: + break + + if full: + textwidget.delete(1.0, tkinter.END) + + if bytebuff == b'\x16': #  + # Kein Zugriff auf Logdatei + textwidget.delete(1.0, tkinter.END) + textwidget.insert( + tkinter.END, _("Can not access log file on the RevPi") + ) + elif bytebuff == b'\x19': #  + # Logdatei neu begonnen + startposition = 0 + else: + # Text in Widget übernehmen + textwidget.insert(tkinter.END, logbytes.decode("utf-8")) + + # Automatisch ans Ende rollen + if roll or full: + textwidget.see(tkinter.END) + + return startposition diff --git a/revpipycontrol/revpioption.py b/revpipycontrol/revpioption.py index 68dce00..c7bfd1d 100644 --- a/revpipycontrol/revpioption.py +++ b/revpipycontrol/revpioption.py @@ -7,35 +7,84 @@ # -*- coding: utf-8 -*- import tkinter import tkinter.messagebox as tkmsg +from mytools import gettrans + +# Übersetzung laden +_ = gettrans() class RevPiOption(tkinter.Frame): - def __init__(self, master, xmlcli, xmlmode): - if xmlmode < 2: + def __init__(self, master, xmlcli): + u"""Init RevPiOption-Class. + @return None""" + try: + self.dc = xmlcli.get_config() + except: + self.dc = None return None super().__init__(master) + self.master.bind("", self._checkclose) + self.master.protocol("WM_DELETE_WINDOW", self._checkclose) self.pack(expand=True, fill="both") self.xmlcli = xmlcli - self.xmlmode = xmlmode - self.xmlstate = "normal" if xmlmode == 3 else "disabled" + self.mrk_var_xmlmod2 = False + self.mrk_var_xmlmod3 = False + self.mrk_xmlmodask = False + self.dorestart = False # Fenster bauen self._createwidgets() self._loadappdata() + def _changesdone(self): + u"""Prüft ob sich die Einstellungen geändert haben. + @return True, wenn min. eine Einstellung geändert wurde""" + return ( + self.var_start.get() != self.dc.get("autostart", "1") + or self.var_reload.get() != self.dc.get("autoreload", "1") + or self.var_zexit.get() != self.dc.get("zeroonexit", "0") + or self.var_zerr.get() != self.dc.get("zeroonerror", "0") + or self.var_startpy.get() != self.dc.get("plcprogram", "none.py") + or self.var_startargs.get() != self.dc.get("plcarguments", "") + or self.var_pythonver.get() != self.dc.get("pythonversion", "3") + or self.var_slave.get() != self.dc.get("plcslave", "0") + or self.var_xmlon.get() != (self.dc.get("xmlrpc", 0) >= 1) + or self.var_xmlmod2.get() != (self.dc.get("xmlrpc", 0) >= 2) + or self.var_xmlmod3.get() != (self.dc.get("xmlrpc", 0) >= 3) + or self.var_xmlport.get() != self.dc.get("xmlrpcport", "55123") + ) + + def _checkclose(self, event=None): + u"""Prüft ob Fenster beendet werden soll. + @param event tkinter-Event""" + ask = True + if self._changesdone(): + ask = tkmsg.askyesno( + _("Question"), + _("Do you really want to quit? \nUnsaved changes will " + "be lost"), + parent=self.master, default="no" + ) + + if ask: + self.master.destroy() + def _createwidgets(self): - self.master.wm_title("RevPi Python PLC Options") + u"""Erstellt Widgets.""" + self.master.wm_title(_("RevPi Python PLC Options")) self.master.wm_resizable(width=False, height=False) + xmlstate = "normal" if self.dc["xmlrpc"] >= 3 else "disabled" + cpadw = {"padx": 4, "pady": 2, "sticky": "w"} cpadwe = {"padx": 4, "pady": 2, "sticky": "we"} # Gruppe Start/Stop stst = tkinter.LabelFrame(self) - stst["text"] = "Start / Stopp Verhalten" + stst["text"] = _("Start / Stop behavior") stst.grid(columnspan=2, pady=2, sticky="we") self.var_start = tkinter.BooleanVar(stst) @@ -44,34 +93,36 @@ class RevPiOption(tkinter.Frame): self.var_zerr = tkinter.BooleanVar(stst) ckb_start = tkinter.Checkbutton(stst) - ckb_start["text"] = "Programm automatisch starten" - ckb_start["state"] = self.xmlstate + ckb_start["text"] = _("Start program automatically") + ckb_start["state"] = xmlstate ckb_start["variable"] = self.var_start ckb_start.grid(**cpadw) ckb_reload = tkinter.Checkbutton(stst) - ckb_reload["text"] = "Programm nach Beenden neu starten" - ckb_reload["state"] = self.xmlstate + ckb_reload["text"] = _("Restart program after exit") + ckb_reload["state"] = xmlstate ckb_reload["variable"] = self.var_reload ckb_reload.grid(**cpadw) ckb_zexit = tkinter.Checkbutton(stst, justify="left") - ckb_zexit["state"] = self.xmlstate - ckb_zexit["text"] = "Prozessabbild auf NULL setzen, wenn " \ - "Programm\nerfolgreich beendet wird" + ckb_zexit["state"] = xmlstate + ckb_zexit["text"] = _( + "Set process image to NULL if program\n" + "terminates successfully") ckb_zexit["variable"] = self.var_zexit ckb_zexit.grid(**cpadw) ckb_zerr = tkinter.Checkbutton(stst, justify="left") - ckb_zerr["state"] = self.xmlstate - ckb_zerr["text"] = "Prozessabbild auf NULL setzen, wenn " \ - "Programm\ndurch Absturz beendet wird" + ckb_zerr["state"] = xmlstate + ckb_zerr["text"] = _( + "Set process image to NULL if program\n" + "terminates with errors") ckb_zerr["variable"] = self.var_zerr ckb_zerr.grid(**cpadw) # Gruppe Programm prog = tkinter.LabelFrame(self) - prog["text"] = "PLC Programm" + prog["text"] = _("PLC program") prog.grid(columnspan=2, pady=2, sticky="we") self.var_pythonver = tkinter.IntVar(prog) @@ -82,24 +133,24 @@ class RevPiOption(tkinter.Frame): self.var_pythonver.set(3) lbl = tkinter.Label(prog) - lbl["text"] = "Python Version" + lbl["text"] = _("Python version") lbl.grid(columnspan=2, row=0, **cpadw) rbn = tkinter.Radiobutton(prog) - rbn["state"] = self.xmlstate + rbn["state"] = xmlstate rbn["text"] = "Python2" rbn["value"] = 2 rbn["variable"] = self.var_pythonver rbn.grid(column=0, row=1, **cpadw) rbn = tkinter.Radiobutton(prog) - rbn["state"] = self.xmlstate + rbn["state"] = xmlstate 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["text"] = _("Python PLC program name") lbl.grid(columnspan=2, **cpadw) lst = self.xmlcli.get_filelist() @@ -107,11 +158,11 @@ class RevPiOption(tkinter.Frame): lst.append("none") opt_startpy = tkinter.OptionMenu( prog, self.var_startpy, *lst) - opt_startpy["state"] = self.xmlstate + opt_startpy["state"] = xmlstate opt_startpy.grid(columnspan=2, **cpadwe) lbl = tkinter.Label(prog) - lbl["text"] = "Programm Argumente" + lbl["text"] = _("Program arguments") lbl.grid(columnspan=2, **cpadw) txt = tkinter.Entry(prog) @@ -119,15 +170,15 @@ class RevPiOption(tkinter.Frame): txt.grid(columnspan=2, **cpadw) ckb_slave = tkinter.Checkbutton(prog, justify="left") - ckb_slave["state"] = self.xmlstate - ckb_slave["text"] = "RevPi als PLC-Slave verwenden" + ckb_slave["state"] = xmlstate + ckb_slave["text"] = _("Use RevPi as PLC-Slave") ckb_slave["state"] = "disabled" ckb_slave["variable"] = self.var_slave ckb_slave.grid(columnspan=2, **cpadw) # Gruppe XMLRPC xmlrpc = tkinter.LabelFrame(self) - xmlrpc["text"] = "XML-RPC Server" + xmlrpc["text"] = _("XML-RPC server") xmlrpc.grid(columnspan=2, pady=2, sticky="we") self.var_xmlon = tkinter.BooleanVar(xmlrpc) @@ -138,124 +189,161 @@ class RevPiOption(tkinter.Frame): ckb_xmlon = tkinter.Checkbutton(xmlrpc) ckb_xmlon["command"] = self.askxmlon - ckb_xmlon["state"] = self.xmlstate - ckb_xmlon["text"] = "XML-RPC Server aktiv auf RevPi" + ckb_xmlon["state"] = xmlstate + ckb_xmlon["text"] = _("Activate XML-RPC server on 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["state"] = self.xmlstate + self.ckb_xmlmod2["command"] = self.xmlmod2_tail + self.ckb_xmlmod2["state"] = xmlstate self.ckb_xmlmod2["text"] = \ - "Download von piCtory Konfiguration und\nPLC Programm zulassen" + _("Allow download of piCtory configuration and\nPLC programm") self.ckb_xmlmod2["variable"] = self.var_xmlmod2 self.ckb_xmlmod2.grid(**cpadw) self.ckb_xmlmod3 = tkinter.Checkbutton(xmlrpc, justify="left") - self.ckb_xmlmod3["state"] = self.xmlstate + self.ckb_xmlmod3["state"] = xmlstate self.ckb_xmlmod3["text"] = \ - "Upload von piCtory Konfiguration und\nPLC Programm zualssen" + _("Allow upload of piCtory configuration and\nPLC programm") self.ckb_xmlmod3["variable"] = self.var_xmlmod3 self.ckb_xmlmod3.grid(**cpadw) lbl = tkinter.Label(xmlrpc) - lbl["text"] = "XML-RPC Serverport" + lbl["text"] = _("XML-RPC server port") lbl.grid(**cpadw) spb_xmlport = tkinter.Spinbox(xmlrpc) spb_xmlport["to"] = 65535 spb_xmlport["from"] = 1024 - spb_xmlport["state"] = self.xmlstate + spb_xmlport["state"] = xmlstate spb_xmlport["textvariable"] = self.var_xmlport spb_xmlport.grid(**cpadwe) # Buttons btn_save = tkinter.Button(self) btn_save["command"] = self._setappdata - btn_save["state"] = self.xmlstate - btn_save["text"] = "Speichern" + btn_save["state"] = xmlstate + btn_save["text"] = _("Save") btn_save.grid(column=0, row=3) btn_close = tkinter.Button(self) - btn_close["command"] = self.master.destroy - btn_close["text"] = "Schließen" + btn_close["command"] = self._checkclose + btn_close["text"] = _("Close") btn_close.grid(column=1, row=3) - def _loadappdata(self): - dc = self.xmlcli.get_config() + def _loadappdata(self, refresh=False): + u"""Läd aktuelle Einstellungen vom RevPi. + @param refresh Wenn True, werden Einstellungen heruntergeladen.""" + if refresh: + self.dc = self.xmlcli.get_config() - self.var_start.set(dc.get("autostart", "1")) - self.var_reload.set(dc.get("autoreload", "1")) - self.var_zexit.set(dc.get("zeroonexit", "0")) - self.var_zerr.set(dc.get("zeroonerror", "0")) + self.var_start.set(self.dc.get("autostart", "1")) + self.var_reload.set(self.dc.get("autoreload", "1")) + self.var_zexit.set(self.dc.get("zeroonexit", "0")) + self.var_zerr.set(self.dc.get("zeroonerror", "0")) - self.var_startpy.set(dc.get("plcprogram", "none.py")) - self.var_startargs.set(dc.get("plcarguments", "")) - self.var_pythonver.set(dc.get("pythonversion", "3")) - self.var_slave.set(dc.get("plcslave", "0")) + self.var_startpy.set(self.dc.get("plcprogram", "none.py")) + self.var_startargs.set(self.dc.get("plcarguments", "")) + self.var_pythonver.set(self.dc.get("pythonversion", "3")) + self.var_slave.set(self.dc.get("plcslave", "0")) - self.var_xmlon.set(dc.get("xmlrpc", 0) >= 1) - self.var_xmlmod2.set(dc.get("xmlrpc", 0) >= 2) - self.var_xmlmod3.set(dc.get("xmlrpc", 0) >= 3) + self.var_xmlon.set(self.dc.get("xmlrpc", 0) >= 1) + self.var_xmlmod2.set(self.dc.get("xmlrpc", 0) >= 2) + self.mrk_var_xmlmod2 = self.var_xmlmod2.get() + self.var_xmlmod3.set(self.dc.get("xmlrpc", 0) >= 3) + self.mrk_var_xmlmod3 = self.var_xmlmod3.get() - self.var_xmlport.set(dc.get("xmlrpcport", "55123")) + self.var_xmlport.set(self.dc.get("xmlrpcport", "55123")) def _setappdata(self): - dc = {} - dc["autostart"] = int(self.var_start.get()) - dc["autoreload"] = int(self.var_reload.get()) - dc["zeroonexit"] = int(self.var_zexit.get()) - dc["zeroonerror"] = int(self.var_zerr.get()) + u"""Speichert geänderte Einstellungen auf RevPi. + @return None""" - dc["plcprogram"] = self.var_startpy.get() - dc["plcarguments"] = self.var_startargs.get() - dc["pythonversion"] = self.var_pythonver.get() - dc["plcslave"] = int(self.var_slave.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() - self.xmlmode = dc["xmlrpc"] + if not self._changesdone(): + tkmsg.showinfo( + _("Information"), + _("You have not made any changes to save."), + ) + self._checkclose() + return None ask = tkmsg.askyesnocancel( - "Frage", "Die Einstellungen werden jetzt auf dem Revolution Pi " - "gespeichert. \n\nSollen die neuen Einstellungen sofort in Kraft " - "treten? \nDies bedeutet einen Neustart des Dienstes und des ggf. " - "laufenden PLC-Programms!", parent=self.master + _("Question"), + _("The settings are now saved on the Revolution Pi. \n\n" + "Should the new settings take effect immediately? \nThis " + "means a restart of the service and the PLC program!"), + parent=self.master ) if ask is not None: - if self.xmlcli.set_config(dc, ask): + self.dc["autostart"] = int(self.var_start.get()) + self.dc["autoreload"] = int(self.var_reload.get()) + self.dc["zeroonexit"] = int(self.var_zexit.get()) + self.dc["zeroonerror"] = int(self.var_zerr.get()) + + self.dc["plcprogram"] = self.var_startpy.get() + self.dc["plcarguments"] = self.var_startargs.get() + self.dc["pythonversion"] = self.var_pythonver.get() + self.dc["plcslave"] = int(self.var_slave.get()) + + self.dc["xmlrpc"] = 0 + if self.var_xmlon.get(): + self.dc["xmlrpc"] += 1 + if self.var_xmlmod2.get(): + self.dc["xmlrpc"] += 1 + if self.var_xmlmod3.get(): + self.dc["xmlrpc"] += 1 + + self.dc["xmlrpcport"] = self.var_xmlport.get() + + if self.xmlcli.set_config(self.dc, ask): tkmsg.showinfo( - "Information", "Einstellungen gespeichert.", + _("Information"), + _("Settings saved"), parent=self.master ) + self.dorestart = ask + self._checkclose() else: tkmsg.showerror( - "Fehler", "Die Einstellungen konnten nicht gesichert" - "werden. Dies kann passieren, wenn Werte falsch sind!", + _("Error"), + _("The settings could not be saved. This can happen if " + "values are wrong!"), parent=self.master ) def askxmlon(self): - if not self.var_xmlon.get(): - ask = tkmsg.askyesno( - "Frage", "Soll der XML-RPC Server wirklich beendet werden? " - "Sie können dann NICHT mehr mit diesem Programm auf den " - "Revolution Pi zugreifen.", parent=self.master + u"""Fragt Nuter, ob wirklicht abgeschaltet werden soll.""" + if not (self.var_xmlon.get() or self.mrk_xmlmodask): + self.mrk_xmlmodask = tkmsg.askyesno( + _("Question"), + _("Are you sure you want to deactivate the XML-RPC server? " + "You will NOT be able to access the Revolution Pi with " + "this program."), + parent=self.master ) - if not ask: + if not self.mrk_xmlmodask: self.var_xmlon.set(True) - self.xmlmods() + self.xmlmod_tail() - 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" + def xmlmod_tail(self): + u"""Passt XML-Optionszugriff an.""" + if self.var_xmlon.get(): + self.var_xmlmod2.set(self.mrk_var_xmlmod2) + self.ckb_xmlmod2["state"] = "normal" + else: + self.mrk_var_xmlmod2 = self.var_xmlmod2.get() + self.var_xmlmod2.set(False) + self.ckb_xmlmod2["state"] = "disabled" + self.xmlmod2_tail() + + def xmlmod2_tail(self): + u"""Passt XML-Optionszugriff an.""" + if self.var_xmlmod2.get(): + self.var_xmlmod3.set(self.mrk_var_xmlmod3) + self.ckb_xmlmod3["state"] = "normal" + else: + self.mrk_var_xmlmod3 = self.var_xmlmod3.get() + self.var_xmlmod3.set(False) + self.ckb_xmlmod3["state"] = "disabled" diff --git a/revpipycontrol/revpiplclist.py b/revpipycontrol/revpiplclist.py index db7a039..8c59b6a 100644 --- a/revpipycontrol/revpiplclist.py +++ b/revpipycontrol/revpiplclist.py @@ -9,10 +9,14 @@ import os.path import pickle import tkinter import tkinter.messagebox as tkmsg +from mytools import gettrans from os import environ from os import makedirs from sys import platform +# Übersetzungen laden +_ = gettrans() + # Systemwerte if platform == "linux": homedir = environ["HOME"] @@ -21,8 +25,9 @@ else: savefile = os.path.join(homedir, ".revpipyplc", "connections.dat") -# Für andere Module zum Laden der Connections def get_connections(): + u"""Verbindungen aus Datei laden. + @return dict() mit Verbindungen""" if os.path.exists(savefile): fh = open(savefile, "rb") connections = pickle.load(fh) @@ -34,21 +39,41 @@ def get_connections(): class RevPiPlcList(tkinter.Frame): def __init__(self, master): + u"""Init RevPiPlcList-class. + @param master tkinter master""" super().__init__(master) + self.master.bind("", self._checkclose) self.pack() self.changes = False # Daten laden - self._connections = {} + self._connections = get_connections() # Fenster bauen self._createwidgets() - self._loadappdata() + self.build_listconn() + + def _checkclose(self, event=None): + u"""Prüft ob Fenster beendet werden soll. + @param event tkinter-Event""" + ask = True + if self.changes: + ask = tkmsg.askyesno( + _("Question"), + _("Do you really want to quit? \nUnsaved changes will " + "be lost"), + parent=self.master + ) + + if ask: + self.master.destroy() def _createwidgets(self): - self.master.wm_title("RevPi Python PLC Connections") + u"""Erstellt alle Widgets.""" + self.master.wm_title(_("RevPi Python PLC connections")) self.master.wm_resizable(width=False, height=False) + self.master.protocol("WM_DELETE_WINDOW", self._checkclose) # Listbox mit vorhandenen Verbindungen self.scr_conn = tkinter.Scrollbar(self) @@ -66,14 +91,14 @@ class RevPiPlcList(tkinter.Frame): self.var_port.set("55123") # Eingabefelder für Adresse und Namen - tkinter.Label(self, text="Name").grid( + tkinter.Label(self, text=_("Name")).grid( column=2, row=0, sticky="wn", padx=5, pady=5) self.txt_name = tkinter.Entry(self, textvariable=self.var_name) self.txt_name.bind("", self.evt_keypress) self.txt_name.grid( column=3, row=0, columnspan=3, sticky="n", padx=5, pady=5) - tkinter.Label(self, text="IP-Adresse").grid( + tkinter.Label(self, text=_("IP address")).grid( column=2, row=1, sticky="wn", padx=5, pady=5 ) self.txt_address = tkinter.Entry(self, textvariable=self.var_address) @@ -81,7 +106,7 @@ class RevPiPlcList(tkinter.Frame): self.txt_address.grid( column=3, row=1, columnspan=3, sticky="n", padx=5, pady=5) - tkinter.Label(self, text="Port").grid( + tkinter.Label(self, text=_("Port")).grid( column=2, row=2, sticky="wn", padx=5, pady=5) self.txt_port = tkinter.Entry(self, textvariable=self.var_port) self.txt_port.bind("", self.evt_keypress) @@ -90,32 +115,28 @@ class RevPiPlcList(tkinter.Frame): # Listenbutton self.btn_new = tkinter.Button( - self, text="Neu", command=self.evt_btnnew) + self, text=_("New"), command=self.evt_btnnew) self.btn_new.grid(column=2, row=3, sticky="s") self.btn_add = tkinter.Button( - self, text="Übernehmen", command=self.evt_btnadd, + self, text=_("Apply"), command=self.evt_btnadd, state="disabled") self.btn_add.grid(column=3, row=3, sticky="s") self.btn_remove = tkinter.Button( - self, text="Entfernen", command=self.evt_btnremove, + self, text=_("Remove"), command=self.evt_btnremove, state="disabled") self.btn_remove.grid(column=4, row=3, sticky="s") # Fensterbuttons self.btn_save = tkinter.Button( - self, text="Speichern", command=self.evt_btnsave) + self, text=_("Save"), command=self.evt_btnsave) self.btn_save.grid(column=3, row=9, sticky="se") self.btn_close = tkinter.Button( - self, text="Schließen", command=self.evt_btnclose) + self, text=_("Close"), command=self._checkclose) self.btn_close.grid(column=4, row=9, sticky="se") - def _loadappdata(self): - if os.path.exists(savefile): - fh = open(savefile, "rb") - self._connections = pickle.load(fh) - self.build_listconn() - def _saveappdata(self): + u"""Speichert Verbindungen im home Dir. + @return True, bei erfolgreicher Verarbeitung""" try: makedirs(os.path.dirname(savefile), exist_ok=True) fh = open(savefile, "wb") @@ -126,11 +147,13 @@ class RevPiPlcList(tkinter.Frame): return True def build_listconn(self): + u"""Füllt Verbindungsliste.""" self.list_conn.delete(0, "end") lst_conns = sorted(self._connections.keys(), key=lambda x: x.lower()) self.list_conn.insert("end", *lst_conns) def evt_btnadd(self): + u"""Verbindungseinstellungen übernehmen.""" # TODO: Daten prüfen self._connections[self.var_name.get()] = \ (self.var_address.get(), self.var_port.get()) @@ -139,20 +162,8 @@ class RevPiPlcList(tkinter.Frame): self.evt_btnnew() self.changes = True - def evt_btnclose(self): - if self.changes: - ask = tkmsg.askyesno( - parent=self.master, title="Frage...", - message="Wollen Sie wirklich beenden?\n" - "Nicht gespeicherte Änderungen gehen verloren", - ) - else: - ask = True - - if ask: - self.master.destroy() - def evt_btnnew(self): + u"""Neue Verbindung erstellen.""" self.list_conn.select_clear(0, "end") self.evt_listconn() @@ -162,13 +173,14 @@ class RevPiPlcList(tkinter.Frame): self.var_port.set("55123") def evt_btnremove(self): + u"""Verbindung löschen.""" item_index = self.list_conn.curselection() if len(item_index) == 1: item = self.list_conn.get(item_index[0]) ask = tkmsg.askyesno( - "Frage", - "Wollen Sie die Ausgewählte Verbindung '{}' wirklich " - "löschen?".format(item), + _("Question"), + _("Do you really want to delete the selected connection '{}'?" + "").format(item), parent=self.master ) if ask: @@ -179,21 +191,24 @@ class RevPiPlcList(tkinter.Frame): self.changes = True def evt_btnsave(self): + u"""Alle Verbindungen speichern.""" if self._saveappdata(): ask = tkmsg.askyesno( - "Information", "Verbindungen erfolgreich gespeichert.\n" - "Möchten Sie dieses Fenster jetzt schließen?", + _("Information"), + _("Successfully saved. \nDo you want to close this window?"), parent=self.master ) if ask: self.master.destroy() else: tkmsg.showerror( - "Fehler", "Verbindungen konnten nicht gespeichert werden", + _("Error"), + _("Failed to save connections"), parent=self.master ) def evt_listconn(self, evt=None): + u"""Übernimmt Einstellungen in Eingabefelder.""" item_index = self.list_conn.curselection() if len(item_index) == 1: @@ -211,6 +226,7 @@ class RevPiPlcList(tkinter.Frame): self.btn_remove["state"] = "disabled" def evt_keypress(self, evt=None): + u"""Passt bei Tastendruck den Status der Buttons an.""" okvalue = "normal" if ( self.var_address.get() != "" and self.var_name.get() != "" diff --git a/revpipycontrol/revpiprogram.py b/revpipycontrol/revpiprogram.py index 01abff2..4eea90e 100644 --- a/revpipycontrol/revpiprogram.py +++ b/revpipycontrol/revpiprogram.py @@ -13,13 +13,16 @@ import tkinter import tkinter.filedialog as tkfd import tkinter.messagebox as tkmsg import zipfile +from mytools import gettrans from os import environ from os import makedirs from shutil import rmtree from sys import platform -from tempfile import mktemp, mkdtemp +from tempfile import mkstemp, mkdtemp from xmlrpc.client import Binary +# Übersetzung laden +_ = gettrans() # Systemwerte if platform == "linux": @@ -32,11 +35,14 @@ savefile = os.path.join(homedir, ".revpipyplc", "programpath.dat") class RevPiProgram(tkinter.Frame): def __init__(self, master, xmlcli, xmlmode, revpi): + u"""Init RevPiProgram-Class. + @return None""" if xmlmode < 2: return None super().__init__(master) -# master.protocol("WM_DELETE_WINDOW", self._checkclose) + self.master.protocol("WM_DELETE_WINDOW", self._checkclose) + self.master.bind("", self._checkclose) self.pack(expand=True, fill="both") self.uploaded = False @@ -54,15 +60,22 @@ class RevPiProgram(tkinter.Frame): self._evt_optdown() self._evt_optup() -# def _checkclose(self): -# if self.uploaded: -# tkmsg.showinfo("Ein PLC Programm wurde hochgeladen. " -# "Bitte die PLC options prüfen ob dort das neue Programm" -# "eingestellt werden muss.") -# self.master.destroy() + def _checkclose(self, event=None): + u"""Prüft ob Fenster beendet werden soll. + @param event tkinter-Event""" + if self.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 + ) + self.master.destroy() def _createwidgets(self): - self.master.wm_title("RevPi Python PLC Programm") + u"""Erstellt alle Widgets.""" + self.master.wm_title(_("RevPi Python PLC program")) self.master.wm_resizable(width=False, height=False) self.rowconfigure(0, weight=1) @@ -76,7 +89,7 @@ class RevPiProgram(tkinter.Frame): # Gruppe Programm prog = tkinter.LabelFrame(self) prog.columnconfigure(0, weight=1) - prog["text"] = "PLC Python programm" + prog["text"] = _("PLC python program") prog.grid(columnspan=2, pady=2, sticky="we") # Variablen vorbereiten @@ -86,16 +99,26 @@ class RevPiProgram(tkinter.Frame): self.var_typedown = tkinter.StringVar(prog) self.var_typeup = tkinter.StringVar(prog) - self.lst_typedown = ["Dateien", "Zip Archiv", "TGZ Archiv"] - self.lst_typeup = ["Dateien", "Ordner", "Zip Archiv", "TGZ Archiv"] + self.lst_typedown = [_("Files"), _("Zip archive"), _("TGZ archive")] + self.lst_typeup = [ + _("Files"), _("Folder"), _("Zip archive"), _("TGZ archive") + ] self.var_picdown.set(self.opt.get("picdown", False)) self.var_picup.set(self.opt.get("picup", False)) - self.var_typedown.set(self.opt.get("typedown", self.lst_typedown[0])) - self.var_typeup.set(self.opt.get("typeup", self.lst_typeup[0])) + + # Gespeicherte Werte übernehmen + saved_val = self.opt.get("typedown", self.lst_typedown[0]) + self.var_typedown.set( + saved_val if saved_val in self.lst_typedown else _("Files") + ) + saved_val = self.opt.get("typeup", self.lst_typeup[0]) + self.var_typeup.set( + saved_val if saved_val in self.lst_typeup else _("Files") + ) r = 0 lbl = tkinter.Label(prog) - lbl["text"] = "PLC Programm herunterladen als:" + lbl["text"] = _("Download PLC program as:") lbl.grid(column=0, row=r, **cpadw) opt = tkinter.OptionMenu( prog, self.var_typedown, *self.lst_typedown, @@ -105,17 +128,17 @@ class RevPiProgram(tkinter.Frame): r = 1 self.ckb_picdown = tkinter.Checkbutton(prog) - self.ckb_picdown["text"] = "inkl. piCtory Konfiguration" + self.ckb_picdown["text"] = _("include piCtory configuration") self.ckb_picdown["variable"] = self.var_picdown self.ckb_picdown.grid(column=0, row=r, **cpadw) btn = tkinter.Button(prog) btn["command"] = self.plcdownload - btn["text"] = "Download" + btn["text"] = _("Download") btn.grid(column=1, row=r, **cpad) r = 2 lbl = tkinter.Label(prog) - lbl["text"] = "PLC Programm hochladen als:" + lbl["text"] = _("Upload PLC program as:") lbl.grid(column=0, row=r, **cpadw) opt = tkinter.OptionMenu( prog, self.var_typeup, *self.lst_typeup, @@ -127,77 +150,78 @@ class RevPiProgram(tkinter.Frame): r = 3 ckb = tkinter.Checkbutton(prog) ckb["state"] = self.xmlstate - ckb["text"] = "vorher alles im Uploadverzeichnis löschen" + ckb["text"] = _("clean upload folder before upload") ckb["variable"] = self.var_cleanup ckb.grid(column=0, row=r, columnspan=2, **cpadw) r = 4 self.ckb_picup = tkinter.Checkbutton(prog) self.ckb_picup["state"] = self.xmlstate - self.ckb_picup["text"] = "enthält piCtory Konfiguration" + self.ckb_picup["text"] = _("includes piCtory configuration") self.ckb_picup["variable"] = self.var_picup self.ckb_picup.grid(column=0, row=r, **cpadw) btn = tkinter.Button(prog) btn["command"] = self.plcupload btn["state"] = self.xmlstate - btn["text"] = "Upload" + btn["text"] = _("Upload") btn.grid(column=1, row=r, **cpad) # Gruppe piCtory picto = tkinter.LabelFrame(self) picto.columnconfigure(0, weight=1) - picto["text"] = "piCtory Konfiguration" + picto["text"] = _("piCtory configuration") picto.grid(columnspan=2, pady=2, sticky="we") lbl = tkinter.Label(picto) - lbl["text"] = "piCtory Konfiguration herunterladen" + lbl["text"] = _("Download piCtory configuration") lbl.grid(column=0, row=0, **cpadw) btn = tkinter.Button(picto) btn["command"] = self.getpictoryrsc - btn["text"] = "Download" + btn["text"] = _("Download") btn.grid(column=1, row=0, **cpad) lbl = tkinter.Label(picto) - lbl["text"] = "piCtory Konfiguration hochladen" + lbl["text"] = _("Upload piCtory configuration") lbl.grid(column=0, row=1, **cpadw) btn = tkinter.Button(picto) btn["command"] = self.setpictoryrsc btn["state"] = self.xmlstate - btn["text"] = "Upload" + btn["text"] = _("Upload") btn.grid(column=1, row=1, **cpad) # Gruppe ProcImg proc = tkinter.LabelFrame(self) proc.columnconfigure(0, weight=1) - proc["text"] = "piControl0 Prozessabbild" + proc["text"] = _("piControl0 process image") proc.grid(columnspan=2, pady=2, sticky="we") lbl = tkinter.Label(proc) - lbl["text"] = "Prozessabbild-Dump herunterladen" + lbl["text"] = _("Download process image dump") lbl.grid(column=0, row=0, **cpadw) btn = tkinter.Button(proc) btn["command"] = self.getprocimg - btn["text"] = "Download" + btn["text"] = _("Download") btn.grid(column=1, row=0, **cpad) # Gruppe piControlReset picon = tkinter.LabelFrame(self) picon.columnconfigure(0, weight=1) - picon["text"] = "piControl Reset" + picon["text"] = _("Reset piControl") picon.grid(columnspan=2, pady=2, sticky="we") lbl = tkinter.Label(picon) - lbl["text"] = "piControlReset ausführen" + lbl["text"] = _("Execute piControlReset") lbl.grid(column=0, row=0, **cpadw) btn = tkinter.Button(picon) btn["command"] = self.picontrolreset - btn["text"] = "ausführen" + btn["text"] = _("execute") btn.grid(column=1, row=0, **cpad) # Beendenbutton btn = tkinter.Button(self) - btn["command"] = self.master.destroy - btn["text"] = "Beenden" + btn["command"] = self._checkclose + btn["text"] = _("Exit") btn.grid() def _evt_optdown(self, text=""): + u"""Passt je nach gewählter Option den Status der Widgets an.""" if self.lst_typedown.index(self.var_typedown.get()) == 0: self.var_picdown.set(False) self.ckb_picdown["state"] = "disable" @@ -205,6 +229,7 @@ class RevPiProgram(tkinter.Frame): self.ckb_picdown["state"] = "normal" def _evt_optup(self, text=""): + u"""Passt je nach gewählter Option den Status der Widgets an.""" if self.lst_typeup.index(self.var_typeup.get()) <= 1: self.var_picup.set(False) self.ckb_picup["state"] = "disable" @@ -212,7 +237,9 @@ class RevPiProgram(tkinter.Frame): self.ckb_picup["state"] = "normal" def _loaddefault(self, full=False): - """Uebernimmt fuer den Pi die letzen Pfade.""" + u"""Übernimmt für den Pi die letzen Pfade. + @param full Einstellungen für alle Verbindungen laden + @return dict() mit Einstellungen""" if os.path.exists(savefile): fh = open(savefile, "rb") dict_all = pickle.load(fh) @@ -223,7 +250,8 @@ class RevPiProgram(tkinter.Frame): return {} def _savedefaults(self): - """Schreibt fuer den Pi die letzen Pfade.""" + u"""Schreibt fuer den Pi die letzen Pfade. + @return True, bei erfolgreicher Verarbeitung""" try: makedirs(os.path.dirname(savefile), exist_ok=True) dict_all = self._loaddefault(full=True) @@ -236,9 +264,9 @@ class RevPiProgram(tkinter.Frame): return True def create_filelist(self, rootdir): - """Erstellt eine Dateiliste von einem Verzeichnis. - @param rootdir: Verzeichnis fuer das eine Liste erstellt werden soll - @returns: Dateiliste""" + u"""Erstellt eine Dateiliste von einem Verzeichnis. + @param rootdir Verzeichnis fuer das eine Liste erstellt werden soll + @return Dateiliste""" filelist = [] for tup_dir in os.walk(rootdir): for fname in tup_dir[2]: @@ -249,9 +277,9 @@ class RevPiProgram(tkinter.Frame): """Gibt das rootdir von einem entpackten Verzeichnis zurueck. Dabei wird geprueft, ob es sich um einen einzelnen Ordner handelt - und ob es eine piCtory Konfiguraiton im rootdir gibt. - @param rootdir: Verzeichnis fuer Pruefung - @returns: Abgeaendertes rootdir + und ob es eine piCtory Konfiguration im rootdir gibt. + @param rootdir Verzeichnis fuer Pruefung + @return Abgeaendertes rootdir """ lst_dir = os.listdir(rootdir) @@ -270,28 +298,29 @@ class RevPiProgram(tkinter.Frame): return (rootdir, None) def getpictoryrsc(self): + u"""Läd die piCtory Konfiguration herunter.""" fh = tkfd.asksaveasfile( mode="wb", parent=self.master, confirmoverwrite=True, - title="Speichern als...", + title=_("Save as..."), initialdir=self.opt.get("getpictoryrsc_dir", ""), initialfile=self.revpi + ".rsc", - filetypes=(("piCtory Config", "*.rsc"), ("All Files", "*.*")) + filetypes=((_("piCtory config"), "*.rsc"), (_("All files"), "*.*")) ) if fh is not None: try: fh.write(self.xmlcli.get_pictoryrsc().data) except: tkmsg.showerror( - parent=self.master, title="Fehler", - message="Datei konnte nicht geladen und gespeichert " - "werden!" + _("Error"), + _("Could not load and save file!"), + parent=self.master, ) else: tkmsg.showinfo( - parent=self.master, title="Erfolgreich", - message="Datei erfolgreich vom Revolution Pi geladen " - "und gespeichert.", + _("Success"), + _("File successfully loaded and saved."), + parent=self.master ) # Einstellungen speichern self.opt["getpictoryrsc_dir"] = os.path.dirname(fh.name) @@ -300,28 +329,29 @@ class RevPiProgram(tkinter.Frame): fh.close() def getprocimg(self): + u"""Läd das aktuelle Prozessabbild herunter.""" fh = tkfd.asksaveasfile( mode="wb", parent=self.master, confirmoverwrite=True, - title="Speichern als...", + title=_("Save as..."), initialdir=self.opt.get("getprocimg_dir", ""), initialfile=self.revpi + ".img", - filetypes=(("Imagefiles", "*.img"), ("All Files", "*.*")) + filetypes=((_("Imagefiles"), "*.img"), (_("All files"), "*.*")) ) if fh is not None: try: fh.write(self.xmlcli.get_procimg().data) except: tkmsg.showerror( - parent=self.master, title="Fehler", - message="Datei konnte nicht geladen und gespeichert" - "werden!" + _("Error"), + _("Could not load and save file!"), + parent=self.master ) else: tkmsg.showinfo( - parent=self.master, title="Erfolgreich", - message="Datei erfolgreich vom Revolution Pi geladen " - "und gespeichert.", + _("Success"), + _("File successfully loaded and saved."), + parent=self.master ) # Einstellungen speichern self.opt["getprocimg_dir"] = os.path.dirname(fh.name) @@ -330,77 +360,118 @@ class RevPiProgram(tkinter.Frame): fh.close() def setpictoryrsc(self, filename=None): + u"""Überträgt die angegebene piCtory-Konfiguration.""" if filename is None: fh = tkfd.askopenfile( mode="rb", parent=self.master, - title="piCtory Datei öffnen...", + title=_("Open piCtory file..."), initialdir=self.opt.get("setpictoryrsc_dir", ""), initialfile=self.revpi + ".rsc", - filetypes=(("piCtory Config", "*.rsc"), ("All Files", "*.*")) + filetypes=( + (_("piCtory config"), "*.rsc"), (_("All files"), "*.*") + ) ) else: fh = open(filename, "rb") if fh is not None: ask = tkmsg.askyesno( - parent=self.master, title="Frage", - message="Soll nach dem Hochladen der piCtory Konfiguration " - "ein Reset am piControl Treiber durchgeführt werden?" + _("Question"), + _("Should the piControl driver be reset after " + "uploading the piCtory configuration?"), + parent=self.master ) ec = self.xmlcli.set_pictoryrsc(Binary(fh.read()), ask) - print(ec) + if ec == 0: if ask: tkmsg.showinfo( - parent=self.master, title="Erfolgreich", - message="Die Übertragung der piCtory Konfiguration " - "und der Reset von piControl wurden erfolgreich " - "ausgeführt") + _("Success"), + _("The transfer of the piCtory configuration " + "and the reset of piControl have been " + "successfully executed."), + parent=self.master + ) else: tkmsg.showinfo( - parent=self.master, title="Erfolgreich", - message="Die Übertragung der piCtory Konfiguration " - "wurde erfolgreich ausgeführt") + _("Success"), + _("The piCtory configuration was " + "successfully transferred."), + parent=self.master + ) - #Einstellungen speichern + # Einstellungen speichern self.opt["setpictoryrsc_dir"] = os.path.dirname(fh.name) self._savedefaults() + elif ec == -1: + tkmsg.showerror( + _("Error"), + _("Can not process the transferred file."), + parent=self.master + ) + elif ec == -2: + tkmsg.showerror( + _("Error"), + _("Can not find main elements in piCtory file."), + parent=self.master + ) + elif ec == -4: + tkmsg.showerror( + _("Error"), + _("Contained devices could not be found on Revolution " + "Pi. The configuration may be from a newer piCtory " + "version!"), + parent=self.master + ) + elif ec == -5: + tkmsg.showerror( + _("Error"), + _("Could not load RAP catalog on Revolution Pi."), + parent=self.master + ) elif ec < 0: tkmsg.showerror( - parent=self.master, title="Fehler", - message="Die piCtory Konfiguration konnte auf dem " - "Revolution Pi nicht geschrieben werden.") + _("Error"), + _("The piCtory configuration could not be " + "written on the Revolution Pi."), + parent=self.master + ) elif ec > 0: tkmsg.showwarning( - parent=self.master, title="Warnung", - message="Die piCtroy Konfiguration wurde erfolgreich " - "gespeichert. \nBeim piControl Reset trat allerdings ein " - "Fehler auf!") + _("Warning"), + _("The piCtroy configuration has been saved successfully." + " \nAn error occurred on piControl reset!"), + parent=self.master + ) fh.close() def picontrolreset(self): + u"""Fürt ein Reset der piBridge durch.""" ask = tkmsg.askyesno( - parent=self.master, title="Frage...", - message="Soll piControlReset wirklich durchgeführt werden? \n" - "Das Prozessabbild und die Steuerung werden dann unterbrochen!!!" + _("Question"), + _("Are you sure to reset piControl? \nThe process image " + "and the piBridge are interrupted !!!"), + parent=self.master ) if ask: ec = self.xmlcli.resetpicontrol() if ec == 0: tkmsg.showinfo( - parent=self.master, title="Erfolgreich", - message="piControlReset erfolgreich durchgeführt" + _("Success"), + _("piControl reset executed successfully"), + parent=self.master ) else: tkmsg.showerror( - parten=self.master, title="Fehler", - message="piControlReset konnte nicht erfolgreich " - "durchgeführt werden" + _("Error"), + _("piControl reset could not be executed successfully"), + parten=self.master ) def plcdownload(self): + u"""Läd das aktuelle Projekt herunter.""" tdown = self.lst_typedown.index(self.var_typedown.get()) fh = None dirselect = "" @@ -409,23 +480,25 @@ class RevPiProgram(tkinter.Frame): # Ordner dirselect = tkfd.askdirectory( parent=self.master, - title="Verzeichnis zum Ablegen", + title=_("Directory to save"), mustexist=False, initialdir=self.opt.get("plcdownload_dir", self.revpi) ) if type(dirselect) == str and dirselect != "": - fh = open(mktemp(), "wb") + fh = open(mkstemp()[1], "wb") elif tdown == 1: # Zip fh = tkfd.asksaveasfile( mode="wb", parent=self.master, confirmoverwrite=True, - title="Speichern als...", + title=_("Save as..."), initialdir=self.opt.get("plcdownload_file", ""), initialfile=self.revpi + ".zip", - filetypes=(("Zip Archiv", "*.zip"), ("All Files", "*.*")) + filetypes=( + (_("Zip archive"), "*.zip"), (_("All files"), "*.*") + ) ) elif tdown == 2: @@ -433,19 +506,23 @@ class RevPiProgram(tkinter.Frame): fh = tkfd.asksaveasfile( mode="wb", parent=self.master, confirmoverwrite=True, - title="Speichern als...", + title=_("Save as..."), initialdir=self.opt.get("plcdownload_file", ""), initialfile=self.revpi + ".tar.gz", - filetypes=(("Tar Archiv", "*.tar.gz"), ("All Files", "*.*")) + filetypes=( + (_("TGZ archive"), "*.tar.gz"), (_("All files"), "*.*") + ) ) if fh is not None: if tdown == 1: plcfile = self.xmlcli.plcdownload( - "zip", self.var_picdown.get()) + "zip", self.var_picdown.get() + ) else: plcfile = self.xmlcli.plcdownload( - "tar", self.var_picdown.get()) + "tar", self.var_picdown.get() + ) try: fh.write(plcfile.data) @@ -456,9 +533,7 @@ class RevPiProgram(tkinter.Frame): fh_pack = tarfile.open(fh.name) # Unterverzeichnis streichen - rootname = "" for taritem in fh_pack.getmembers(): - print(rootname) if not taritem.name == "revpipyload": taritem.name = \ taritem.name.replace("revpipyload/", "") @@ -474,15 +549,15 @@ class RevPiProgram(tkinter.Frame): except: raise tkmsg.showerror( - parent=self.master, title="Fehler", - message="Datei konnte nicht geladen und gespeichert " - "werden!" + _("Error"), + _("Could not load and save file!"), + parent=self.master ) else: tkmsg.showinfo( - parent=self.master, title="Erfolgreich", - message="Datei erfolgreich vom Revolution Pi geladen " - "und gespeichert.", + _("Success"), + _("File successfully loaded and saved."), + parent=self.master ) # Einstellungen speichern @@ -491,6 +566,8 @@ class RevPiProgram(tkinter.Frame): fh.close() def plcupload(self): + u"""Lädt das angegebene Projekt auf den RevPi. + @return True, bei erfolgreicher Verarbeitung""" tup = self.lst_typeup.index(self.var_typeup.get()) dirselect = "" dirtmp = None @@ -502,9 +579,9 @@ class RevPiProgram(tkinter.Frame): # Datei fileselect = tkfd.askopenfilenames( parent=self.master, - title="Python Programm übertragen...", + title="Upload Python program...", initialdir=self.opt.get("plcupload_dir", ""), - filetypes=(("Python", "*.py"), ("All Files", "*.*")) + filetypes=(("Python", "*.py"), (_("All files"), "*.*")) ) if type(fileselect) == tuple and len(fileselect) > 0: for file in fileselect: @@ -514,7 +591,7 @@ class RevPiProgram(tkinter.Frame): # Ordner dirselect = tkfd.askdirectory( parent=self.master, - title="Verzeichnis zum Hochladen", + title=_("Folder to upload"), mustexist=True, initialdir=self.opt.get("plcupload_dir", self.revpi) ) @@ -525,10 +602,12 @@ class RevPiProgram(tkinter.Frame): # Zip fileselect = tkfd.askopenfilename( parent=self.master, - title="Zip-Archive übertragen...", + title=_("Upload Zip archive..."), initialdir=self.opt.get("plcupload_file", ""), initialfile=self.revpi + ".zip", - filetypes=(("Zip Archiv", "*.zip"), ("All Files", "*.*")) + filetypes=( + (_("Zip archive"), "*.zip"), (_("All files"), "*.*") + ) ) if type(fileselect) == str and fileselect != "": # Zipdatei prüfen @@ -543,18 +622,22 @@ class RevPiProgram(tkinter.Frame): else: tkmsg.showerror( - parent=self.master, title="Fehler", - message="Die angegebene Datei ist kein ZIP-Archiv.") + _("Error"), + _("The specified file is not a ZIP archive."), + parent=self.master + ) return False elif tup == 3: # TarGz fileselect = tkfd.askopenfilename( parent=self.master, - title="TarGz-Archiv übertragen...", + title=_("Upload TarGz archiv..."), initialdir=self.opt.get("plcupload_file", ""), initialfile=self.revpi + ".tar.gz", - filetypes=(("Tar Archiv", "*.tar.gz"), ("All Files", "*.*")) + filetypes=( + (_("TGZ archive"), "*.tar.gz"), (_("All files"), "*.*") + ) ) if type(fileselect) == str and fileselect != "": @@ -570,8 +653,10 @@ class RevPiProgram(tkinter.Frame): else: tkmsg.showerror( - parent=self.master, title="Fehler", - message="Die angegebene Datei ist kein TAR-Archiv.") + _("Error"), + _("The specified file is not a TAR archive."), + parent=self.master + ) return False # Wenn keine Dateien gewählt @@ -581,12 +666,16 @@ class RevPiProgram(tkinter.Frame): # Vor Übertragung aufräumen wenn ausgewählt if self.var_cleanup.get() and not self.xmlcli.plcuploadclean(): tkmsg.showerror( - parent=self.masger, title="Fehler", - message="Beim Löschen der Dateien auf dem Revolution Pi ist " - "ein Fehler aufgetreten.") + _("Error"), + _("There was an error deleting the files on the " + "Revolution Pi."), + parent=self.master + ) return False - # Flag setzen, weil ab hier Veränderungen existieren + # Aktuell konfiguriertes Programm lesen (für uploaded Flag) + opt_program = self.xmlcli.get_config() + opt_program = opt_program.get("plcprogram", "none.py") self.uploaded = True ec = 0 @@ -595,7 +684,7 @@ class RevPiProgram(tkinter.Frame): if fname == rscfile: continue - # TODO: Fehlerabfang bei Dateilesen + # FIXME: Fehlerabfang bei Dateilesen with open(fname, "rb") as fh: # Dateinamen ermitteln @@ -604,6 +693,10 @@ class RevPiProgram(tkinter.Frame): else: sendname = fname.replace(dirselect, "")[1:] + # Prüfen ob Dateiname bereits als Startprogramm angegeben ist + if sendname == opt_program: + self.uploaded = False + # Datei übertragen try: ustatus = self.xmlcli.plcupload( @@ -618,17 +711,21 @@ class RevPiProgram(tkinter.Frame): if ec == 0: tkmsg.showinfo( - parent=self.master, title="Erfolgreich", - message="Die Übertragung war erfolgreich.") + _("Success"), + _("The PLC program was transferred successfully."), + parent=self.master + ) if self.var_picup.get(): if rscfile is not None: self.setpictoryrsc(rscfile) else: tkmsg.showerror( - parent=self.master, title="Fehler", - message="Es wurde im Archiv keine piCtory " - "Konfiguration gefunden") + _("Error"), + _("There is no piCtory configuration in this " + "archive."), + parent=self.master + ) # Einstellungen speichern if tup == 0: @@ -644,21 +741,21 @@ class RevPiProgram(tkinter.Frame): elif ec == -1: tkmsg.showerror( - parent=self.master, title="Fehler", - message="Der Revoluton Pi konnte Teile der Übertragung nicht " - "verarbeiten.") + _("Error"), + _("The Revolution Pi could not process some parts of the " + "transmission."), + parent=self.master + ) elif ec == -2: tkmsg.showerror( - parent=self.master, title="Fehler", - message="Bei der Übertragung traten Fehler auf") + _("Error"), + _("Errors occurred during transmission"), + parent=self.master + ) # Temp-Dir aufräumen if dirtmp is not None: rmtree(dirtmp) - -if __name__ == "__main__": - root = tkinter.Tk() - myapp = RevPiProgram(root, None, "test") - root.mainloop() + return True diff --git a/revpipycontrol/revpipycontrol.py b/revpipycontrol/revpipycontrol.py index 4f518d6..0dd4773 100755 --- a/revpipycontrol/revpipycontrol.py +++ b/revpipycontrol/revpipycontrol.py @@ -1,55 +1,52 @@ #!/usr/bin/python3 # # RevPiPyControl -# Version: 0.2.12 +# Version: see global var pycontrolverion # # Webpage: https://revpimodio.org/revpipyplc/ # (c) Sven Sager, License: LGPLv3 # # -*- coding: utf-8 -*- +import revpicheckclient +import revpiinfo 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 +import webbrowser +from mytools import addroot, gettrans from xmlrpc.client import ServerProxy +# Übersetzung laden +_ = gettrans() -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) +pycontrolversion = "0.4.1" class RevPiPyControl(tkinter.Frame): 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.xmlfuncs = [] self.xmlmode = 0 + # Debugger vorbereiten + self.debugframe = None + # Globale Fenster + self.tkcheckclient = None self.tklogs = None self.tkoptions = None self.tkprogram = None @@ -61,14 +58,38 @@ class RevPiPyControl(tkinter.Frame): 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.tklogs is not None: + self.tklogs.master.destroy() + if self.tkoptions is not None: + self.tkoptions.destroy() + if self.tkprogram is not None: + self.tkprogram.destroy() + if self.debugframe is not None: + self.plcdebug() + self.debugframe.destroy() + self.cli.psstop() + self.debugframe = None + + def _closeapp(self, event=None): + u"""Räumt auf und beendet Programm. + @param event tkinter Event""" + self._closeall() + self.master.destroy() def _createwidgets(self): - """Erstellt den Fensterinhalt.""" + u"""Erstellt den Fensterinhalt.""" # Hauptfenster self.master.wm_title("RevPi Python PLC Loader") self.master.wm_iconphoto( @@ -81,36 +102,44 @@ class RevPiPyControl(tkinter.Frame): self.master.config(menu=self.mbar) menu1 = tkinter.Menu(self.mbar, tearoff=False) - menu1.add_command(label="Connections...", command=self.plclist) + 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) + 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.var_conn = tkinter.StringVar(self) - self.txt_connect = tkinter.Entry( - self, textvariable=self.var_conn, state="readonly", width=30) + self.txt_connect = tkinter.Entry(self, state="readonly", width=40) + self.txt_connect["textvariable"] = self.var_conn self.txt_connect.pack(fill="x") self.btn_plcstart = tkinter.Button(self) - self.btn_plcstart["text"] = "PLC Start" + 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["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["text"] = _("PLC restart") self.btn_plcrestart["command"] = self.plcrestart self.btn_plcrestart.pack(fill="x") self.btn_plclogs = tkinter.Button(self) - self.btn_plclogs["text"] = "PLC Logs" + self.btn_plclogs["text"] = _("PLC logs") self.btn_plclogs["command"] = self.plclogs self.btn_plclogs.pack(fill="x") @@ -120,28 +149,48 @@ class RevPiPyControl(tkinter.Frame): self.txt_status["textvariable"] = self.var_status self.txt_status.pack(fill="x") + self.btn_debug = tkinter.Button(self) + 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 monitor...", command=self.plcmonitor) - 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 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_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) + self.mbar.add_cascade(label=_("Connect"), menu=self.mconn) - def _fillconnbar(self): - 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=partial(self._opt_conn, con) - ) + 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(15) + else: + socket.setdefaulttimeout(2) - def _opt_conn(self, text): - socket.setdefaulttimeout(2) sp = ServerProxy( "http://{}:{}".format( self.dict_conn[text][0], int(self.dict_conn[text][1]) @@ -149,6 +198,7 @@ class RevPiPyControl(tkinter.Frame): ) # Server prüfen try: + self.xmlfuncs = sp.system.listMethods() self.xmlmode = sp.xmlmodus() except: self.servererror() @@ -166,86 +216,157 @@ class RevPiPyControl(tkinter.Frame): )) self.mbar.entryconfig("PLC", state="normal") - def _closeall(self): - if self.tklogs is not None: - self.tklogs.master.destroy() - if self.tkoptions is not None: - self.tkoptions.destroy() - if self.tkprogram is not None: - self.tkprogram.destroy() - - def plclist(self): + def infowindow(self): + u"""Öffnet das Fenster für die Info.""" win = tkinter.Toplevel(self) - revpiplclist.RevPiPlcList(win) win.focus_set() win.grab_set() + revpiinfo.RevPiInfo(win, self.cli, pycontrolversion) + self.wait_window(win) + self.dict_conn = revpiplclist.get_connections() + self._fillconnbar() + + def plcdebug(self): + u"""Baut den Debugframe und packt ihn.""" + self.btn_debug["state"] = "disabled" + + if "psstart" not in self.xmlfuncs: + tkmsg.showwarning( + _("Warning"), + _("The watch mode ist not supported in version {} " + "of RevPiPyLoad on your RevPi! You need at least version " + "0.4.0. Or the python3-revpimodio module is not installt" + "on your RevPi at least version 0.15.0." + "").format(self.cli.version()), + parent=self.master + ) + else: + # Debugfenster laden + if self.debugframe is None \ + or self.debugframe.err_workvalues >= \ + self.debugframe.max_errors: + self.debugframe = revpicheckclient.RevPiCheckClient( + self, self.cli, self.xmlmode + ) + + # 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 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 {} " + "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 plcmonitor(self): - # TODO: Monitorfenster - pass - def plcoptions(self): + u"""Startet das Optionsfenster.""" if self.xmlmode < 2: tkmsg.showwarning( - parent=self.master, title="Warnung", - message="Der XML-RPC Modus ist beim RevPiPyLoad nicht hoch " - "genug eingestellt, um diesen Dialog zu verwenden!" + _("Warning"), + _("XML-RPC access mode in the RevPiPyLoad " + "configuration is too small to access this dialog!"), + parent=self.master ) else: win = tkinter.Toplevel(self) - self.tkoptions = \ - revpioption.RevPiOption(win, self.cli, self.xmlmode) win.focus_set() win.grab_set() + self.tkoptions = \ + revpioption.RevPiOption(win, self.cli) self.wait_window(win) - self.xmlmode = self.tkoptions.xmlmode + if self.tkoptions.dc is not None and self.tkoptions.dorestart: + + # Wenn XML-Modus anders und Dienstneustart + if self.xmlmode != self.tkoptions.dc["xmlrpc"]: + 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( - parent=self.master, title="Warnung", - message="Der XML-RPC Modus ist beim RevPiPyLoad nicht hoch " - "genug eingestellt, um diesen Dialog zu verwenden!" + _("Warning"), + _("XML-RPC access mode in the RevPiPyLoad " + "configuration is too small to access this dialog!"), + parent=self.master ) else: win = tkinter.Toplevel(self) - self.tkprogram = revpiprogram.RevPiProgram( - win, self.cli, self.xmlmode, self.revpiname) 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 servererror(self): - """Setzt alles auf NULL.""" + 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("") - self._closeall() - tkmsg.showerror("Fehler", "Server ist nicht erreichbar!") + + def servererror(self): + u"""Setzt alles zurück für neue Verbindungen.""" + self.serverdisconnect() + tkmsg.showerror( + _("Error"), + _("Can not reach server!"), + 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" @@ -267,16 +388,22 @@ class RevPiPyControl(tkinter.Frame): 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 = "PROGRAMS TERMED" + 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() diff --git a/setup.py b/setup.py index 0af49cd..65c5ec7 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ globsetup = { "author_email": "akira@narux.de", "url": "https://revpimodio.org/revpipyplc/", "license": "LGPLv3", - "version": "0.2.12", + "version": "0.4.1", "name": "revpipycontrol", @@ -82,7 +82,7 @@ elif platform == "win32": options={"build_exe": { "include_files": [ "revpipycontrol/revpipycontrol.png", - # "m4server/locale" + "revpipycontrol/locale" ] }}, executables=[exe],