From 3944ec2801b7335d301283c7910ac37e89909f5b Mon Sep 17 00:00:00 2001 From: NaruX Date: Tue, 12 Sep 2017 16:17:03 +0200 Subject: [PATCH] =?UTF-8?q?Umstellung=20auf=20proginit=20als=20globale=20D?= =?UTF-8?q?atenquelle=20Aufteilung=20der=20Funktionen=20in=20mehrere=20Mod?= =?UTF-8?q?ule=20FileHandler=20von=20stdout=20schlie=C3=9Fen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .hgignore | 1 + doc/index.html | 6 + doc/logsystem.html | 248 ++++++++++++++++++ doc/plcsystem.html | 147 +++++++++++ doc/procimgserver.html | 11 +- doc/proginit.html | 13 +- doc/revpipyload.html | 350 +------------------------- eric-revpipyload.api | 46 ++-- revpipyload.e4p | 8 +- revpipyload/logsystem.py | 186 ++++++++++++++ revpipyload/plcsystem.py | 224 +++++++++++++++++ revpipyload/procimgserver.py | 36 ++- revpipyload/proginit.py | 85 +++++-- revpipyload/revpipyload.py | 476 +++-------------------------------- setup.py | 2 +- 15 files changed, 973 insertions(+), 866 deletions(-) create mode 100644 doc/logsystem.html create mode 100644 doc/plcsystem.html create mode 100644 revpipyload/logsystem.py create mode 100644 revpipyload/plcsystem.py diff --git a/.hgignore b/.hgignore index f1479c8..c3f38eb 100644 --- a/.hgignore +++ b/.hgignore @@ -5,3 +5,4 @@ test/* build/* *.pyc deb/* +demo/* diff --git a/doc/index.html b/doc/index.html index 418bfe4..cd2c3d2 100644 --- a/doc/index.html +++ b/doc/index.html @@ -13,6 +13,12 @@ Table of contents Modules + + + + + + diff --git a/doc/logsystem.html b/doc/logsystem.html new file mode 100644 index 0000000..43eb55a --- /dev/null +++ b/doc/logsystem.html @@ -0,0 +1,248 @@ + + +logsystem + + + +

+logsystem

+

+Modul fuer die Verwaltung der Logdateien. +

+

+Global Attributes

+
logsystemModul fuer die Verwaltung der Logdateien.
plcsystemModul fuer die Verwaltung der PLC Funktionen.
procimgserver Stellt Funktionen bereit um das Prozessabbild zu ueberwachen.
+ +
None
+

+Classes

+ + + + + + + + +
LogReaderErmoeglicht den Zugriff auf die Logdateien.
PipeLogwriterFile PIPE fuer das Schreiben des APP Log.
+

+Functions

+ + +
None
+

+ +

LogReader

+

+Ermoeglicht den Zugriff auf die Logdateien. +

+ Beinhaltet Funktionen fuer den Abruf der gesamten Logdatei fuer das + RevPiPyLoad-System und die Logdatei der PLC-Anwendung. +

+ +

+

+Derived from

+None +

+Class Attributes

+ + +
None
+

+Class Methods

+ + +
None
+

+Methods

+ + + + + + + + + + + + + + +
LogReaderInstantiiert LogReader-Klasse.
closeallFuehrt close auf File Handler durch.
load_applogUebertraegt Logdaten des PLC Programms Binaer.
load_plclogUebertraegt Logdaten des Loaders Binaer.
+

+Static Methods

+ + +
None
+ +

+LogReader (Constructor)

+LogReader() +

+Instantiiert LogReader-Klasse. +

+

+LogReader.closeall

+closeall() +

+Fuehrt close auf File Handler durch. +

+

+LogReader.load_applog

+load_applog(start, count) +

+Uebertraegt Logdaten des PLC Programms Binaer. +

+
start
+
+Startbyte +
count
+
+Max. Byteanzahl zum uebertragen +
+
+
Returns:
+
+Binary() der Logdatei +
+
+

+LogReader.load_plclog

+load_plclog(start, count) +

+Uebertraegt Logdaten des Loaders Binaer. +

+
start
+
+Startbyte +
count
+
+Max. Byteanzahl zum uebertragen +
+
+
Returns:
+
+Binary() der Logdatei +
+
+
Up
+

+ +

PipeLogwriter

+

+File PIPE fuer das Schreiben des APP Log. +

+ Spezieller LogFile-Handler fuer die Ausgabe des subprocess fuer das Python + PLC Programm. Die Ausgabe kann nicht auf einen neuen FileHandler + umgeschrieben werden. Dadurch waere es nicht moeglich nach einem logrotate + die neue Datei zu verwenden. Ueber die PIPE wird dies umgangen. +

+ +

+

+Derived from

+Thread +

+Class Attributes

+ + +
None
+

+Class Methods

+ + +
None
+

+Methods

+ + + + + + + + + + + + + + + + + + + + + + + +
PipeLogwriterInstantiiert PipeLogwriter-Klasse.
__del__Close der FileHandler.
_configurefhKonfiguriert den FileHandler fuer Ausgaben der PLCAPP.
loglineSchreibt eine Zeile in die Logdatei oder stdout.
newlogfileKonfiguriert den FileHandler auf eine neue Logdatei.
runPrueft auf neue Logzeilen und schreibt diese.
stopBeendetden Thread und die FileHandler werden geschlossen.
+

+Static Methods

+ + +
None
+ +

+PipeLogwriter (Constructor)

+PipeLogwriter(logfilename) +

+Instantiiert PipeLogwriter-Klasse. +

+
logfilename
+
+Dateiname fuer Logdatei +
+
+

+PipeLogwriter.__del__

+__del__() +

+Close der FileHandler. +

+

+PipeLogwriter._configurefh

+_configurefh() +

+Konfiguriert den FileHandler fuer Ausgaben der PLCAPP. +

+
Returns:
+
+FileHandler-Objekt +
+
+

+PipeLogwriter.logline

+logline(message) +

+Schreibt eine Zeile in die Logdatei oder stdout. +

+
message
+
+Logzeile zum Schreiben +
+
+

+PipeLogwriter.newlogfile

+newlogfile() +

+Konfiguriert den FileHandler auf eine neue Logdatei. +

+

+PipeLogwriter.run

+run() +

+Prueft auf neue Logzeilen und schreibt diese. +

+

+PipeLogwriter.stop

+stop() +

+Beendetden Thread und die FileHandler werden geschlossen. +

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

+plcsystem

+

+Modul fuer die Verwaltung der PLC Funktionen. +

+

+Global Attributes

+ + +
None
+

+Classes

+ + + + + +
RevPiPlcVerwaltet das PLC Python Programm.
+

+Functions

+ + +
None
+

+ +

RevPiPlc

+

+Verwaltet das PLC Python Programm. +

+ Dieser Thread startet das PLC Python Programm und ueberwacht es. Sollte es + abstuerzen kann es automatisch neu gestartet werden. Die Ausgaben des + Programms werden in eine Logdatei umgeleitet, damit der Entwickler sein + Programm analysieren und debuggen kann. +

+ +

+

+Derived from

+Thread +

+Class Attributes

+ + +
None
+

+Class Methods

+ + +
None
+

+Methods

+ + + + + + + + + + + + + + + + + + + + + + + +
RevPiPlcInstantiiert RevPiPlc-Klasse.
_configureplwKonfiguriert den PipeLogwriter fuer Ausgaben der PLCAPP.
_setuppopenSetzt UID und GID fuer das PLC Programm.
_spopenStartet das PLC Programm.
newlogfileKonfiguriert die FileHandler auf neue Logdatei.
runFuehrt PLC-Programm aus und ueberwacht es.
stopBeendet PLC-Programm.
+

+Static Methods

+ + +
None
+ +

+RevPiPlc (Constructor)

+RevPiPlc(program, arguments, pversion) +

+Instantiiert RevPiPlc-Klasse. +

+

+RevPiPlc._configureplw

+_configureplw() +

+Konfiguriert den PipeLogwriter fuer Ausgaben der PLCAPP. +

+
Returns:
+
+PipeLogwriter() +
+
+

+RevPiPlc._setuppopen

+_setuppopen() +

+Setzt UID und GID fuer das PLC Programm. +

+

+RevPiPlc._spopen

+_spopen(lst_proc) +

+Startet das PLC Programm. +

+
lst_proc
+
+Prozessliste +
+
+
Returns:
+
+subprocess +
+
+

+RevPiPlc.newlogfile

+newlogfile() +

+Konfiguriert die FileHandler auf neue Logdatei. +

+

+RevPiPlc.run

+run() +

+Fuehrt PLC-Programm aus und ueberwacht es. +

+

+RevPiPlc.stop

+stop() +

+Beendet PLC-Programm. +

+
Up
+
+ \ No newline at end of file diff --git a/doc/procimgserver.html b/doc/procimgserver.html index 12a44e9..9a77e8c 100644 --- a/doc/procimgserver.html +++ b/doc/procimgserver.html @@ -93,22 +93,13 @@ Static Methods

ProcimgServer (Constructor)

-ProcimgServer(logger, xmlserver, configrsc, procimg, aclmode) +ProcimgServer(xmlserver, aclmode)

Instantiiert RevPiCheckServer()-Klasse.

xmlserver
XML-RPC Server -
procimg
-
-Pfad zum Prozessabbild -
configrsc
-
-Pfad zur piCtory Konfigurationsdatei -
logger
-
-Loggerinstanz
aclmode
Zugriffsrechte diff --git a/doc/proginit.html b/doc/proginit.html index d9f6e46..162e9f9 100644 --- a/doc/proginit.html +++ b/doc/proginit.html @@ -12,7 +12,7 @@ Main functions of our program.

Global Attributes

- +
forked
globalconffile
logapp
logger
logplc
pargs
startdir
forked
globalconffile
logapp
logger
logplc
pargs
picontrolreset
rapcatalog
startdir

Classes

@@ -23,6 +23,9 @@ Classes Functions + + + @@ -31,6 +34,14 @@ Functions
_zeroprocimgSetzt Prozessabbild auf NULL.
cleanup Clean up program.


+ +

_zeroprocimg

+_zeroprocimg(self) +

+Setzt Prozessabbild auf NULL. +

+
Up
+

cleanup

cleanup() diff --git a/doc/revpipyload.html b/doc/revpipyload.html index 8edc9dc..a531907 100644 --- a/doc/revpipyload.html +++ b/doc/revpipyload.html @@ -32,21 +32,12 @@ begrenzt werden!

Global Attributes

- +
configrsc
picontrolreset
procimg
pyloadverion
rapcatalog
pyloadverion

Classes

- - - - - - - - - @@ -57,345 +48,6 @@ Functions
LogReaderErmoeglicht den Zugriff auf die Logdateien.
PipeLogwriterFile PIPE fuer das Schreiben des APP Log.
RevPiPlcVerwaltet das PLC Python Programm.
RevPiPyLoad Hauptklasse, die alle Funktionen zur Verfuegung stellt.
None


- -

LogReader

-

-Ermoeglicht den Zugriff auf die Logdateien. -

- Beinhaltet Funktionen fuer den Abruf der gesamten Logdatei fuer das - RevPiPyLoad-System und die Logdatei der PLC-Anwendung. -

- -

-

-Derived from

-None -

-Class Attributes

- - -
None
-

-Class Methods

- - -
None
-

-Methods

- - - - - - - - - - - - - - -
LogReaderInstantiiert LogReader-Klasse.
closeallFuehrt close auf File Handler durch.
load_applogUebertraegt Logdaten des PLC Programms Binaer.
load_plclogUebertraegt Logdaten des Loaders Binaer.
-

-Static Methods

- - -
None
- -

-LogReader (Constructor)

-LogReader() -

-Instantiiert LogReader-Klasse. -

-

-LogReader.closeall

-closeall() -

-Fuehrt close auf File Handler durch. -

-

-LogReader.load_applog

-load_applog(start, count) -

-Uebertraegt Logdaten des PLC Programms Binaer. -

-
start
-
-Startbyte -
count
-
-Max. Byteanzahl zum uebertragen -
-
-
Returns:
-
-Binary() der Logdatei -
-
-

-LogReader.load_plclog

-load_plclog(start, count) -

-Uebertraegt Logdaten des Loaders Binaer. -

-
start
-
-Startbyte -
count
-
-Max. Byteanzahl zum uebertragen -
-
-
Returns:
-
-Binary() der Logdatei -
-
-
Up
-

- -

PipeLogwriter

-

-File PIPE fuer das Schreiben des APP Log. -

- Spezieller LogFile-Handler fuer die Ausgabe des subprocess fuer das Python - PLC Programm. Die Ausgabe kann nicht auf einen neuen FileHandler - umgeschrieben werden. Dadurch waere es nicht moeglich nach einem logrotate - die neue Datei zu verwenden. Ueber die PIPE wird dies umgangen. -

- -

-

-Derived from

-Thread -

-Class Attributes

- - -
None
-

-Class Methods

- - -
None
-

-Methods

- - - - - - - - - - - - - - - - - - - - - - - -
PipeLogwriterInstantiiert PipeLogwriter-Klasse.
__del__Close file handler.
_configurefhKonfiguriert den FileHandler fuer Ausgaben der PLCAPP.
loglineSchreibt eine Zeile in die Logdatei oder stdout.
newlogfileKonfiguriert den FileHandler auf eine neue Logdatei.
runPrueft auf neue Logzeilen und schreibt diese.
stopBeendetden Thread und die FileHandler werden geschlossen.
-

-Static Methods

- - -
None
- -

-PipeLogwriter (Constructor)

-PipeLogwriter(logfilename) -

-Instantiiert PipeLogwriter-Klasse. -

-
logfilename
-
-Dateiname fuer Logdatei -
-
-

-PipeLogwriter.__del__

-__del__() -

-Close file handler. -

-

-PipeLogwriter._configurefh

-_configurefh() -

-Konfiguriert den FileHandler fuer Ausgaben der PLCAPP. -

-
Returns:
-
-FileHandler-Objekt -
-
-

-PipeLogwriter.logline

-logline(message) -

-Schreibt eine Zeile in die Logdatei oder stdout. -

-
message
-
-Logzeile zum Schreiben -
-
-

-PipeLogwriter.newlogfile

-newlogfile() -

-Konfiguriert den FileHandler auf eine neue Logdatei. -

-

-PipeLogwriter.run

-run() -

-Prueft auf neue Logzeilen und schreibt diese. -

-

-PipeLogwriter.stop

-stop() -

-Beendetden Thread und die FileHandler werden geschlossen. -

-
Up
-

- -

RevPiPlc

-

-Verwaltet das PLC Python Programm. -

- Dieser Thread startet das PLC Python Programm und ueberwacht es. Sollte es - abstuerzen kann es automatisch neu gestartet werden. Die Ausgaben des - Programms werden in eine Logdatei umgeleitet, damit der Entwickler sein - Programm analysieren und debuggen kann. -

- -

-

-Derived from

-Thread -

-Class Attributes

- - -
None
-

-Class Methods

- - -
None
-

-Methods

- - - - - - - - - - - - - - - - - - - - - - - - - - -
RevPiPlcInstantiiert RevPiPlc-Klasse.
_configureplwKonfiguriert den PipeLogwriter fuer Ausgaben der PLCAPP.
_setuppopenSetzt UID und GID fuer das PLC Programm.
_spopenStartet das PLC Programm.
_zeroprocimgSetzt Prozessabbild auf NULL.
newlogfileKonfiguriert die FileHandler auf neue Logdatei.
runFuehrt PLC-Programm aus und ueberwacht es.
stopBeendet PLC-Programm.
-

-Static Methods

- - -
None
- -

-RevPiPlc (Constructor)

-RevPiPlc(program, arguments, pversion) -

-Instantiiert RevPiPlc-Klasse. -

-

-RevPiPlc._configureplw

-_configureplw() -

-Konfiguriert den PipeLogwriter fuer Ausgaben der PLCAPP. -

-
Returns:
-
-PipeLogwriter() -
-
-

-RevPiPlc._setuppopen

-_setuppopen() -

-Setzt UID und GID fuer das PLC Programm. -

-

-RevPiPlc._spopen

-_spopen(lst_proc) -

-Startet das PLC Programm. -

-
lst_proc
-
-Prozessliste -
-
-
Returns:
-
-subprocess -
-
-

-RevPiPlc._zeroprocimg

-_zeroprocimg() -

-Setzt Prozessabbild auf NULL. -

-

-RevPiPlc.newlogfile

-newlogfile() -

-Konfiguriert die FileHandler auf neue Logdatei. -

-

-RevPiPlc.run

-run() -

-Fuehrt PLC-Programm aus und ueberwacht es. -

-

-RevPiPlc.stop

-stop() -

-Beendet PLC-Programm. -

-
Up
-

RevPiPyLoad

diff --git a/eric-revpipyload.api b/eric-revpipyload.api index bdcbe29..d5c2abb 100644 --- a/eric-revpipyload.api +++ b/eric-revpipyload.api @@ -1,3 +1,21 @@ +logsystem.LogReader.closeall?4() +logsystem.LogReader.load_applog?4(start, count) +logsystem.LogReader.load_plclog?4(start, count) +logsystem.LogReader?1() +logsystem.PipeLogwriter.__del__?6() +logsystem.PipeLogwriter._configurefh?5() +logsystem.PipeLogwriter.logline?4(message) +logsystem.PipeLogwriter.newlogfile?4() +logsystem.PipeLogwriter.run?4() +logsystem.PipeLogwriter.stop?4() +logsystem.PipeLogwriter?1(logfilename) +plcsystem.RevPiPlc._configureplw?5() +plcsystem.RevPiPlc._setuppopen?5() +plcsystem.RevPiPlc._spopen?5(lst_proc) +plcsystem.RevPiPlc.newlogfile?4() +plcsystem.RevPiPlc.run?4() +plcsystem.RevPiPlc.stop?4() +plcsystem.RevPiPlc?1(program, arguments, pversion) procimgserver.ProcimgServer.devices?4() procimgserver.ProcimgServer.ios?4(type) procimgserver.ProcimgServer.loadrevpimodio?4() @@ -5,7 +23,8 @@ procimgserver.ProcimgServer.setvalue?4(device, io, value) procimgserver.ProcimgServer.start?4() procimgserver.ProcimgServer.stop?4() procimgserver.ProcimgServer.values?4() -procimgserver.ProcimgServer?1(logger, xmlserver, configrsc, procimg, aclmode) +procimgserver.ProcimgServer?1(xmlserver, aclmode) +proginit._zeroprocimg?5(self) proginit.cleanup?4() proginit.configure?4() proginit.forked?7 @@ -14,26 +33,9 @@ proginit.logapp?7 proginit.logger?7 proginit.logplc?7 proginit.pargs?7 +proginit.picontrolreset?7 +proginit.rapcatalog?7 proginit.startdir?7 -revpipyload.LogReader.closeall?4() -revpipyload.LogReader.load_applog?4(start, count) -revpipyload.LogReader.load_plclog?4(start, count) -revpipyload.LogReader?1() -revpipyload.PipeLogwriter.__del__?6() -revpipyload.PipeLogwriter._configurefh?5() -revpipyload.PipeLogwriter.logline?4(message) -revpipyload.PipeLogwriter.newlogfile?4() -revpipyload.PipeLogwriter.run?4() -revpipyload.PipeLogwriter.stop?4() -revpipyload.PipeLogwriter?1(logfilename) -revpipyload.RevPiPlc._configureplw?5() -revpipyload.RevPiPlc._setuppopen?5() -revpipyload.RevPiPlc._spopen?5(lst_proc) -revpipyload.RevPiPlc._zeroprocimg?5() -revpipyload.RevPiPlc.newlogfile?4() -revpipyload.RevPiPlc.run?4() -revpipyload.RevPiPlc.stop?4() -revpipyload.RevPiPlc?1(program, arguments, pversion) revpipyload.RevPiPyLoad._loadconfig?5() revpipyload.RevPiPyLoad._plcthread?5() revpipyload.RevPiPyLoad._sigexit?5(signum, frame) @@ -60,8 +62,4 @@ revpipyload.RevPiPyLoad.xml_reload?4() revpipyload.RevPiPyLoad.xml_setconfig?4(dc, loadnow=False) revpipyload.RevPiPyLoad.xml_setpictoryrsc?4(filebytes, reset=False) revpipyload.RevPiPyLoad?1() -revpipyload.configrsc?7 -revpipyload.picontrolreset?7 -revpipyload.procimg?7 revpipyload.pyloadverion?7 -revpipyload.rapcatalog?7 diff --git a/revpipyload.e4p b/revpipyload.e4p index eff7f78..b25c5a4 100644 --- a/revpipyload.e4p +++ b/revpipyload.e4p @@ -1,7 +1,7 @@ - + en_US @@ -9,15 +9,17 @@ Python3 Console Dieser Loader wird über das Init-System geladen und führt das angegebene Pythonprogramm aus. Es ist für den RevolutionPi gedacht um automatisch das SPS-Programm zu starten. - 0.4.2 + 0.4.3 Sven Sager akira@narux.de - + revpipyload/proginit.py setup.py revpipyload/revpipyload.py revpipyload/procimgserver.py + revpipyload/logsystem.py + revpipyload/plcsystem.py diff --git a/revpipyload/logsystem.py b/revpipyload/logsystem.py new file mode 100644 index 0000000..370b918 --- /dev/null +++ b/revpipyload/logsystem.py @@ -0,0 +1,186 @@ +# -*- coding: utf-8 -*- +# +# RevPiPyLoad +# +# Webpage: https://revpimodio.org/revpipyplc/ +# (c) Sven Sager, License: LGPLv3 +# +"""Modul fuer die Verwaltung der Logdateien.""" +import os +import proginit +from threading import Event, Lock, Thread +from xmlrpc.client import Binary + + +class LogReader(): + + """Ermoeglicht den Zugriff auf die Logdateien. + + Beinhaltet Funktionen fuer den Abruf der gesamten Logdatei fuer das + RevPiPyLoad-System und die Logdatei der PLC-Anwendung. + + """ + + def __init__(self): + """Instantiiert LogReader-Klasse.""" + self.fhapp = None + self.fhapplk = Lock() + self.fhplc = None + self.fhplclk = Lock() + + def closeall(self): + """Fuehrt close auf File Handler durch.""" + if self.fhapp is not None: + self.fhapp.close() + if self.fhplc is not None: + self.fhplc.close() + + def load_applog(self, start, count): + """Uebertraegt Logdaten des PLC Programms Binaer. + + @param start Startbyte + @param count Max. Byteanzahl zum uebertragen + @return Binary() der Logdatei + + """ + if not os.access(proginit.logapp, os.R_OK): + return Binary(b'\x16') #  + elif start > os.path.getsize(proginit.logapp): + return Binary(b'\x19') #  + else: + with self.fhapplk: + if self.fhapp is None or self.fhapp.closed: + self.fhapp = open(proginit.logapp, "rb") + + self.fhapp.seek(start) + return Binary(self.fhapp.read(count)) + + def load_plclog(self, start, count): + """Uebertraegt Logdaten des Loaders Binaer. + + @param start Startbyte + @param count Max. Byteanzahl zum uebertragen + @return Binary() der Logdatei + + """ + if not os.access(proginit.logplc, os.R_OK): + return Binary(b'\x16') #  + elif start > os.path.getsize(proginit.logplc): + return Binary(b'\x19') #  + else: + with self.fhplclk: + if self.fhplc is None or self.fhplc.closed: + self.fhplc = open(proginit.logplc, "rb") + + self.fhplc.seek(start) + return Binary(self.fhplc.read(count)) + + +class PipeLogwriter(Thread): + + """File PIPE fuer das Schreiben des APP Log. + + Spezieller LogFile-Handler fuer die Ausgabe des subprocess fuer das Python + PLC Programm. Die Ausgabe kann nicht auf einen neuen FileHandler + umgeschrieben werden. Dadurch waere es nicht moeglich nach einem logrotate + die neue Datei zu verwenden. Ueber die PIPE wird dies umgangen. + + """ + + def __init__(self, logfilename): + """Instantiiert PipeLogwriter-Klasse. + @param logfilename Dateiname fuer Logdatei""" + super().__init__() + self._exit = Event() + self._lckfh = Lock() + self.logfile = logfilename + + # Logdatei öffnen + self._fh = self._configurefh() + + # Pipes öffnen + self._fdr, self.fdw = os.pipe() + proginit.logger.debug("pipe fd read: {} / write: {}".format( + self._fdr, self.fdw + )) + + def __del__(self): + """Close der FileHandler.""" + # FileHandler schließen + if self._fh is not None: + self._fh.close() + + def _configurefh(self): + """Konfiguriert den FileHandler fuer Ausgaben der PLCAPP. + @return FileHandler-Objekt""" + proginit.logger.debug("enter PipeLogwriter._configurefh()") + + logfile = None + dirname = os.path.dirname(self.logfile) + + if os.access(dirname, os.R_OK | os.W_OK): + logfile = open(self.logfile, "a") + else: + raise RuntimeError("can not open logfile {}".format(self.logfile)) + + proginit.logger.debug("leave PipeLogwriter._configurefh()") + return logfile + + def logline(self, message): + """Schreibt eine Zeile in die Logdatei oder stdout. + @param message Logzeile zum Schreiben""" + with self._lckfh: + self._fh.write("{}\n".format(message)) + self._fh.flush() + + def newlogfile(self): + """Konfiguriert den FileHandler auf eine neue Logdatei.""" + proginit.logger.debug("enter RevPiPlc.newlogfile()") + with self._lckfh: + self._fh.close() + self._fh = self._configurefh() + proginit.logger.debug("leave RevPiPlc.newlogfile()") + + def run(self): + """Prueft auf neue Logzeilen und schreibt diese.""" + proginit.logger.debug("enter PipeLogwriter.run()") + + fhread = os.fdopen(self._fdr) + while not self._exit.is_set(): + line = fhread.readline() + self._lckfh.acquire() + try: + self._fh.write(line) + self._fh.flush() + except: + proginit.logger.exception("PipeLogwriter in write log line") + finally: + self._lckfh.release() + proginit.logger.debug("leave logreader pipe loop") + + proginit.logger.debug("close all pipes") + fhread.close() + os.close(self.fdw) + proginit.logger.debug("closed all pipes") + + # FileHandler schließen + if self._fh is not None: + self._fh.close() + + proginit.logger.debug("leave PipeLogwriter.run()") + + def stop(self): + """Beendetden Thread und die FileHandler werden geschlossen.""" + proginit.logger.debug("enter PipeLogwriter.stop()") + self._exit.set() + + self._lckfh.acquire() + # Letzten Log in Pipe schreiben zum befreien + try: + os.write(self.fdw, b"\n") + except: + pass + finally: + self._lckfh.release() + + proginit.logger.debug("leave PipeLogwriter.stop()") diff --git a/revpipyload/plcsystem.py b/revpipyload/plcsystem.py new file mode 100644 index 0000000..bc3ae2c --- /dev/null +++ b/revpipyload/plcsystem.py @@ -0,0 +1,224 @@ +# -*- coding: utf-8 -*- +# +# RevPiPyLoad +# +# Webpage: https://revpimodio.org/revpipyplc/ +# (c) Sven Sager, License: LGPLv3 +# +"""Modul fuer die Verwaltung der PLC Funktionen.""" +import os +import proginit +import shlex +import subprocess +from logsystem import PipeLogwriter +from sys import stdout as sysstdout +from threading import Event, Thread +from time import sleep, asctime + + +class RevPiPlc(Thread): + + """Verwaltet das PLC Python Programm. + + Dieser Thread startet das PLC Python Programm und ueberwacht es. Sollte es + abstuerzen kann es automatisch neu gestartet werden. Die Ausgaben des + Programms werden in eine Logdatei umgeleitet, damit der Entwickler sein + Programm analysieren und debuggen kann. + + """ + + def __init__(self, program, arguments, pversion): + """Instantiiert RevPiPlc-Klasse.""" + super().__init__() + + self._arguments = arguments + self._evt_exit = Event() + self._plw = self._configureplw() + self._program = program + self._procplc = None + self._pversion = pversion + self.autoreload = False + self.exitcode = None + self.gid = 65534 + self.uid = 65534 + self.zeroonerror = False + self.zeroonexit = False + + def _configureplw(self): + """Konfiguriert den PipeLogwriter fuer Ausgaben der PLCAPP. + @return PipeLogwriter()""" + proginit.logger.debug("enter RevPiPlc._configureplw()") + + logfile = None + if proginit.pargs.daemon: + if os.access(os.path.dirname(proginit.logapp), os.R_OK | os.W_OK): + logfile = proginit.logapp + elif proginit.pargs.logfile is not None: + logfile = proginit.pargs.logfile + + if logfile is not None: + logfile = PipeLogwriter(logfile) + + proginit.logger.debug("leave RevPiPlc._configureplw()") + return logfile + + def _setuppopen(self): + """Setzt UID und GID fuer das PLC Programm.""" + proginit.logger.info( + "set uid {} and gid {} for plc program".format( + self.uid, self.gid) + ) + os.setgid(self.gid) + os.setuid(self.uid) + + def _spopen(self, lst_proc): + """Startet das PLC Programm. + @param lst_proc Prozessliste + @return subprocess""" + proginit.logger.debug("enter RevPiPlc._spopen({})".format(lst_proc)) + + sp = subprocess.Popen( + lst_proc, + preexec_fn=self._setuppopen, + cwd=os.path.dirname(self._program), + bufsize=1, + stdout=sysstdout if self._plw is None else self._plw.fdw, + stderr=subprocess.STDOUT + ) + proginit.logger.debug("leave RevPiPlc._spopen()") + return sp + + def newlogfile(self): + """Konfiguriert die FileHandler auf neue Logdatei.""" + proginit.logger.debug("enter RevPiPlc.newlogfile()") + + if self._plw is not None: + self._plw.newlogfile() + self._plw.logline("-" * 55) + self._plw.logline("start new logfile: {}".format(asctime())) + + proginit.logger.debug("leave RevPiPlc.newlogfile()") + + def run(self): + """Fuehrt PLC-Programm aus und ueberwacht es.""" + proginit.logger.debug("enter RevPiPlc.run()") + + if self._pversion == 2: + lst_proc = shlex.split("/usr/bin/env python2 -u {} {}".format( + self._program, self._arguments + )) + else: + lst_proc = shlex.split("/usr/bin/env python3 -u {} {}".format( + self._program, self._arguments + )) + + # Prozess erstellen + proginit.logger.info("start plc program {}".format(self._program)) + self._procplc = self._spopen(lst_proc) + + # LogWriter starten und Logausgaben schreiben + if self._plw is not None: + self._plw.logline("-" * 55) + self._plw.logline("plc: {} started: {}".format( + os.path.basename(self._program), asctime() + )) + self._plw.start() + + while not self._evt_exit.is_set(): + + # Auswerten + self.exitcode = self._procplc.poll() + + if self.exitcode is not None: + if self.exitcode > 0: + # PLC Python Programm abgestürzt + proginit.logger.error( + "plc program crashed - exitcode: {}".format( + self.exitcode + ) + ) + if self.zeroonerror: + proginit._zeroprocimg() + proginit.logger.warning( + "set piControl0 to ZERO after PLC program error") + + else: + # PLC Python Programm sauber beendet + proginit.logger.info("plc program did a clean exit") + if self.zeroonexit: + proginit._zeroprocimg() + proginit.logger.info( + "set piControl0 to ZERO after PLC program returns " + "clean exitcode") + + if not self._evt_exit.is_set() and self.autoreload: + # Prozess neu starten + self._procplc = self._spopen(lst_proc) + if self.exitcode == 0: + proginit.logger.warning( + "restart plc program after clean exit" + ) + else: + proginit.logger.warning( + "restart plc program after crash" + ) + else: + break + + self._evt_exit.wait(1) + + if self._plw is not None: + self._plw.logline("-" * 55) + self._plw.logline("plc: {} stopped: {}".format( + os.path.basename(self._program), asctime() + )) + + proginit.logger.debug("leave RevPiPlc.run()") + + def stop(self): + """Beendet PLC-Programm.""" + proginit.logger.debug("enter RevPiPlc.stop()") + + proginit.logger.info("stop revpiplc thread") + self._evt_exit.set() + + # Prüfen ob es einen subprocess gibt + if self._procplc is None: + if self._plw is not None: + self._plw.stop() + self._plw.join() + proginit.logger.debug("log pipes successfully closed") + + proginit.logger.debug("leave RevPiPlc.stop()") + return + + # Prozess beenden + count = 0 + proginit.logger.info("term plc program {}".format(self._program)) + self._procplc.terminate() + + while self._procplc.poll() is None and count < 10: + count += 1 + proginit.logger.info( + "wait term plc program {} seconds".format(count * 0.5) + ) + sleep(0.5) + if self._procplc.poll() is None: + proginit.logger.warning( + "can not term plc program {}".format(self._program) + ) + self._procplc.kill() + proginit.logger.warning("killed plc program") + + # Exitcode auswerten + self.exitcode = self._procplc.poll() + if self.zeroonexit and self.exitcode == 0 \ + or self.zeroonerror and self.exitcode != 0: + proginit._zeroprocimg() + + if self._plw is not None: + self._plw.stop() + self._plw.join() + proginit.logger.debug("log pipes successfully closed") + + proginit.logger.debug("leave RevPiPlc.stop()") diff --git a/revpipyload/procimgserver.py b/revpipyload/procimgserver.py index c135843..627c801 100644 --- a/revpipyload/procimgserver.py +++ b/revpipyload/procimgserver.py @@ -1,10 +1,10 @@ +# -*- coding: utf-8 -*- # # RevPiPyLoad # # Webpage: https://revpimodio.org/revpipyplc/ # (c) Sven Sager, License: LGPLv3 # -# -*- coding: utf-8 -*- """Stellt Funktionen bereit um das Prozessabbild zu ueberwachen. Bei ausreichend Rechten koennen Ausgaenge auch gesetzt werden um einen @@ -12,6 +12,7 @@ IO-Check bei Inbetriebname durchzufuehren. """ import pickle +import proginit import revpimodio from xmlrpc.client import Binary @@ -27,22 +28,17 @@ class ProcimgServer(): """ - def __init__(self, logger, xmlserver, configrsc, procimg, aclmode): + def __init__(self, xmlserver, aclmode): """Instantiiert RevPiCheckServer()-Klasse. @param xmlserver XML-RPC Server - @param procimg Pfad zum Prozessabbild - @param configrsc Pfad zur piCtory Konfigurationsdatei - @param logger Loggerinstanz @param aclmode Zugriffsrechte """ # Logger übernehmen - self.logger = logger - self.logger.debug("enter ProcimgServer.__init__()") + proginit.logger.debug("enter ProcimgServer.__init__()") + self.acl = aclmode - self.configrsc = configrsc - self.procimg = procimg self.rpi = None # XML-Server übernehmen @@ -60,7 +56,7 @@ class ProcimgServer(): self.loadrevpimodio() - self.logger.debug("leave ProcimgServer.__init__()") + proginit.logger.debug("leave ProcimgServer.__init__()") def devices(self): """Generiert Deviceliste mit Position und Namen. @@ -104,19 +100,19 @@ class ProcimgServer(): if self.rpi is not None: self.rpi.cleanup() - self.logger.debug("create revpimodio class") + proginit.logger.debug("create revpimodio class") try: self.rpi = revpimodio.RevPiModIO( - configrsc=self.configrsc, - procimg=self.procimg, + configrsc=proginit.pargs.configrsc, + procimg=proginit.pargs.procimg ) except: self.rpi = None - self.logger.error("piCtory configuration not loadable") + proginit.logger.error("piCtory configuration not loadable") return False self.rpi.devices.syncoutputs(device=0) - self.logger.debug("created revpimodio class") + proginit.logger.debug("created revpimodio class") return True def setvalue(self, device, io, value): @@ -171,9 +167,9 @@ class ProcimgServer(): def start(self): """Registriert XML Funktionen. @return True, wenn erfolgreich""" - self.logger.debug("enter ProcimgServer.start()") - ec = False + proginit.logger.debug("enter ProcimgServer.start()") + ec = False if self.rpi is not None: # Registriere Funktionen @@ -188,12 +184,12 @@ class ProcimgServer(): ) ec = True - self.logger.debug("leave ProcimgServer.start()") + proginit.logger.debug("leave ProcimgServer.start()") return ec def stop(self): """Entfernt XML-Funktionen.""" - self.logger.debug("enter ProcimgServer.stop()") + proginit.logger.debug("enter ProcimgServer.stop()") # Entferne Funktionen for xmlfunc in self.xmlreadfuncs: @@ -204,4 +200,4 @@ class ProcimgServer(): if xmlfunc in self.xmlsrv.funcs: del self.xmlsrv.funcs[xmlfunc] - self.logger.debug("leave ProcimgServer.stop()") + proginit.logger.debug("leave ProcimgServer.stop()") diff --git a/revpipyload/proginit.py b/revpipyload/proginit.py index 3aee849..0f737ab 100644 --- a/revpipyload/proginit.py +++ b/revpipyload/proginit.py @@ -1,17 +1,15 @@ +# -*- coding: utf-8 -*- # # RevPiPyLoad # # Webpage: https://revpimodio.org/revpipyplc/ # (c) Sven Sager, License: LGPLv3 # -# -*- coding: utf-8 -*- """Main functions of our program.""" import logging -import os.path +import os import sys from argparse import ArgumentParser -from os import fork as osfork - forked = False globalconffile = None @@ -19,21 +17,43 @@ logapp = "revpipyloadapp.log" logplc = "revpipyload.log" logger = None pargs = None +picontrolreset = "/opt/KUNBUS/piControlReset" +rapcatalog = None startdir = None +def _zeroprocimg(self): + """Setzt Prozessabbild auf NULL.""" + procimg = "/dev/piControl0" if pargs is None else pargs.procimg + if os.access(procimg, os.W_OK): + with open(procimg, "w+b", 0) as f: + f.write(bytes(4096)) + else: + if logger is not None: + logger.error("zeroprocimg can not write to piControl device") + + def cleanup(): """Clean up program.""" + # NOTE: Pidfile wirklich löschen? + if pargs is not None and pargs.daemon: + if os.path.exists("/var/run/revpipyload.pid"): + os.remove("/var/run/revpipyload.pid") + # Logging beenden logging.shutdown() + # Dateihandler schließen + if pargs.daemon: + sys.stdout.close() + def configure(): """Initialize general program functions.""" # Command arguments parser = ArgumentParser( - description="RevolutionPi Python3 Loader" + description="RevolutionPi Python Loader" ) parser.add_argument( "-d", "--daemon", action="store_true", dest="daemon", @@ -50,6 +70,7 @@ def configure(): ) parser.add_argument( "--procimg", dest="procimg", + default="/dev/piControl0", help="Path to process image" ) parser.add_argument( @@ -63,15 +84,6 @@ def configure(): global pargs pargs = parser.parse_args() - # Pfade absolut umschreiben - global startdir - if startdir is None: - startdir = os.path.abspath(".") - if pargs.conffile is not None and os.path.dirname(pargs.conffile) == "": - pargs.conffile = os.path.join(startdir, pargs.conffile) - if pargs.logfile is not None and os.path.dirname(pargs.logfile) == "": - pargs.logfile = os.path.join(startdir, pargs.logfile) - # Prüfen ob als Daemon ausgeführt werden soll global forked pidfile = "/var/run/revpipyload.pid" @@ -84,7 +96,7 @@ def configure(): ) # Zum daemon machen - pid = osfork() + pid = os.fork() if pid > 0: with open(pidfile, "w") as f: f.write(str(pid)) @@ -92,15 +104,58 @@ def configure(): else: forked = True + # piCtory Konfiguration prüfen + if pargs.configrsc is None: + lst_rsc = ["/etc/revpi/config.rsc", "/opt/KUNBUS/config.rsc"] + for rscfile in lst_rsc: + if os.access(rscfile, os.F_OK | os.R_OK): + pargs.configrsc = rscfile + break + elif not os.access(pargs.configrsc, os.F_OK | os.R_OK): + pargs.configrsc = None + if pargs.configrsc is None: + raise RuntimeError( + "can not find known pictory configurations at {}" + "".format(", ".join(lst_rsc)) + ) + + # piControlReset suchen + global picontrolreset + if not os.access(picontrolreset, os.F_OK | os.X_OK): + picontrolreset = "/usr/bin/piTest -x" + + # rap Katalog an bekannten Stellen prüfen und laden + global rapcatalog + lst_rap = [ + "/opt/KUNBUS/pictory/resources/data/rap", + "/var/www/pictory/resources/data/rap" + ] + for rapfolder in lst_rap: + if os.path.isdir(rapfolder): + rapcatalog = os.listdir(rapfolder) + + # Pfade absolut umschreiben + global startdir + if startdir is None: + startdir = os.path.abspath(".") + if pargs.conffile is not None and os.path.dirname(pargs.conffile) == "": + pargs.conffile = os.path.join(startdir, pargs.conffile) + if pargs.logfile is not None and os.path.dirname(pargs.logfile) == "": + pargs.logfile = os.path.join(startdir, pargs.logfile) + global logapp global logplc if pargs.daemon: + # Ausgage vor Umhängen schließen + sys.stdout.close() + # Ausgaben umhängen in Logfile logapp = "/var/log/revpipyloadapp" logplc = "/var/log/revpipyload" pargs.conffile = "/etc/revpipyload/revpipyload.conf" sys.stdout = open(logplc, "a") sys.stderr = sys.stdout + elif pargs.logfile is not None: logplc = pargs.logfile diff --git a/revpipyload/revpipyload.py b/revpipyload/revpipyload.py index ab722bf..d60b5ad 100755 --- a/revpipyload/revpipyload.py +++ b/revpipyload/revpipyload.py @@ -1,4 +1,5 @@ #!/usr/bin/python3 +# -*- coding: utf-8 -*- # # RevPiPyLoad # Version: see global var pyloadverion @@ -6,7 +7,6 @@ # Webpage: https://revpimodio.org/revpipyplc/ # (c) Sven Sager, License: LGPLv3 # -# -*- coding: utf-8 -*- """Revolution Pi Python PLC Loader. Stellt das RevPiPyLoad Programm bereit. Dieses Programm lauft als Daemon auf @@ -31,11 +31,11 @@ begrenzt werden! """ import gzip +import logsystem +import plcsystem import proginit import os -import shlex import signal -import subprocess import tarfile import zipfile from concurrent import futures @@ -43,394 +43,13 @@ from configparser import ConfigParser from json import loads as jloads from re import match as rematch from shutil import rmtree -from sys import stdout as sysstdout from tempfile import mkstemp -from threading import Thread, Event, Lock -from time import sleep, asctime +from threading import Event +from time import asctime from xmlrpc.client import Binary from xmlrpc.server import SimpleXMLRPCServer -configrsc = None -picontrolreset = "/opt/KUNBUS/piControlReset" -procimg = "/dev/piControl0" -pyloadverion = "0.4.2" -rapcatalog = None - - -class LogReader(): - - """Ermoeglicht den Zugriff auf die Logdateien. - - Beinhaltet Funktionen fuer den Abruf der gesamten Logdatei fuer das - RevPiPyLoad-System und die Logdatei der PLC-Anwendung. - - """ - - def __init__(self): - """Instantiiert LogReader-Klasse.""" - self.fhapp = None - self.fhapplk = Lock() - self.fhplc = None - self.fhplclk = Lock() - - def closeall(self): - """Fuehrt close auf File Handler durch.""" - if self.fhapp is not None: - self.fhapp.close() - if self.fhplc is not None: - self.fhplc.close() - - def load_applog(self, start, count): - """Uebertraegt Logdaten des PLC Programms Binaer. - - @param start Startbyte - @param count Max. Byteanzahl zum uebertragen - @return Binary() der Logdatei - - """ - if not os.access(proginit.logapp, os.R_OK): - return Binary(b'\x16') #  - elif start > os.path.getsize(proginit.logapp): - return Binary(b'\x19') #  - else: - with self.fhapplk: - if self.fhapp is None or self.fhapp.closed: - self.fhapp = open(proginit.logapp, "rb") - - self.fhapp.seek(start) - return Binary(self.fhapp.read(count)) - - def load_plclog(self, start, count): - """Uebertraegt Logdaten des Loaders Binaer. - - @param start Startbyte - @param count Max. Byteanzahl zum uebertragen - @return Binary() der Logdatei - - """ - if not os.access(proginit.logplc, os.R_OK): - return Binary(b'\x16') #  - elif start > os.path.getsize(proginit.logplc): - return Binary(b'\x19') #  - else: - with self.fhplclk: - if self.fhplc is None or self.fhplc.closed: - self.fhplc = open(proginit.logplc, "rb") - - self.fhplc.seek(start) - return Binary(self.fhplc.read(count)) - - -class PipeLogwriter(Thread): - - """File PIPE fuer das Schreiben des APP Log. - - Spezieller LogFile-Handler fuer die Ausgabe des subprocess fuer das Python - PLC Programm. Die Ausgabe kann nicht auf einen neuen FileHandler - umgeschrieben werden. Dadurch waere es nicht moeglich nach einem logrotate - die neue Datei zu verwenden. Ueber die PIPE wird dies umgangen. - - """ - - def __init__(self, logfilename): - """Instantiiert PipeLogwriter-Klasse. - @param logfilename Dateiname fuer Logdatei""" - super().__init__() - self._exit = Event() - self._fh = None - self._lckfh = Lock() - self.logfile = logfilename - - # Logdatei öffnen - self._fh = self._configurefh() - - # Pipes öffnen - self._fdr, self.fdw = os.pipe() - proginit.logger.debug("pipe fd read: {} / write: {}".format( - self._fdr, self.fdw - )) - - def __del__(self): - """Close file handler.""" - if self._fh is not None: - self._fh.close() - - def _configurefh(self): - """Konfiguriert den FileHandler fuer Ausgaben der PLCAPP. - @return FileHandler-Objekt""" - proginit.logger.debug("enter PipeLogwriter._configurefh()") - - dirname = os.path.dirname(self.logfile) - proginit.logger.debug("dirname = {}".format(os.path.abspath(dirname))) - - if os.access(dirname, os.R_OK | os.W_OK): - logfile = open(self.logfile, "a") - else: - raise RuntimeError("can not open logfile {}".format(self.logfile)) - - proginit.logger.debug("leave PipeLogwriter._configurefh()") - return logfile - - def logline(self, message): - """Schreibt eine Zeile in die Logdatei oder stdout. - @param message Logzeile zum Schreiben""" - with self._lckfh: - self._fh.write("{}\n".format(message)) - self._fh.flush() - - def newlogfile(self): - """Konfiguriert den FileHandler auf eine neue Logdatei.""" - proginit.logger.debug("enter RevPiPlc.newlogfile()") - with self._lckfh: - self._fh.close() - self._fh = self._configurefh() - proginit.logger.debug("leave RevPiPlc.newlogfile()") - - def run(self): - """Prueft auf neue Logzeilen und schreibt diese.""" - proginit.logger.debug("enter PipeLogwriter.run()") - - fhread = os.fdopen(self._fdr) - while not self._exit.is_set(): - line = fhread.readline() - self._lckfh.acquire() - try: - self._fh.write(line) - self._fh.flush() - except: - proginit.logger.exception("PipeLogwriter in write log line") - finally: - self._lckfh.release() - proginit.logger.debug("leave logreader pipe loop") - - proginit.logger.debug("close all pipes") - os.close(self._fdr) - os.close(self.fdw) - proginit.logger.debug("closed all pipes") - - proginit.logger.debug("leave PipeLogwriter.run()") - - def stop(self): - """Beendetden Thread und die FileHandler werden geschlossen.""" - proginit.logger.debug("enter PipeLogwriter.stop()") - self._exit.set() - self._lckfh.acquire() - try: - os.write(self.fdw, b"\n") - except: - pass - finally: - self._lckfh.release() - - proginit.logger.debug("leave PipeLogwriter.stop()") - - -class RevPiPlc(Thread): - - """Verwaltet das PLC Python Programm. - - Dieser Thread startet das PLC Python Programm und ueberwacht es. Sollte es - abstuerzen kann es automatisch neu gestartet werden. Die Ausgaben des - Programms werden in eine Logdatei umgeleitet, damit der Entwickler sein - Programm analysieren und debuggen kann. - - """ - - def __init__(self, program, arguments, pversion): - """Instantiiert RevPiPlc-Klasse.""" - super().__init__() - self.autoreload = False - self._arguments = arguments - self._evt_exit = Event() - self.exitcode = None - self.gid = 65534 - self._plw = self._configureplw() - self._program = program - self._procplc = None - self._pversion = pversion - self.uid = 65534 - self.zeroonerror = False - self.zeroonexit = False - - def _configureplw(self): - """Konfiguriert den PipeLogwriter fuer Ausgaben der PLCAPP. - @return PipeLogwriter()""" - proginit.logger.debug("enter RevPiPlc._configureplw()") - logfile = None - if proginit.pargs.daemon: - if os.access(os.path.dirname(proginit.logapp), os.R_OK | os.W_OK): - logfile = proginit.logapp - elif proginit.pargs.logfile is not None: - logfile = proginit.pargs.logfile - - if logfile is not None: - logfile = PipeLogwriter(logfile) - - proginit.logger.debug("leave RevPiPlc._configureplw()") - return logfile - - def _setuppopen(self): - """Setzt UID und GID fuer das PLC Programm.""" - proginit.logger.info( - "set uid {} and gid {} for plc program".format( - self.uid, self.gid) - ) - os.setgid(self.gid) - os.setuid(self.uid) - - def _spopen(self, lst_proc): - """Startet das PLC Programm. - @param lst_proc Prozessliste - @return subprocess""" - proginit.logger.debug("enter RevPiPlc._spopen({})".format(lst_proc)) - sp = subprocess.Popen( - lst_proc, - preexec_fn=self._setuppopen, - cwd=os.path.dirname(self._program), - bufsize=1, - stdout=sysstdout if self._plw is None else self._plw.fdw, - stderr=subprocess.STDOUT - ) - proginit.logger.debug("leave RevPiPlc._spopen()") - return sp - - def _zeroprocimg(self): - """Setzt Prozessabbild auf NULL.""" - if os.path.exists("/dev/piControl0"): - with open("/dev/piControl0", "w+b", 0) as f: - f.write(bytes(4096)) - - def newlogfile(self): - """Konfiguriert die FileHandler auf neue Logdatei.""" - proginit.logger.debug("enter RevPiPlc.newlogfile()") - if self._plw is not None: - self._plw.newlogfile() - self._plw.logline("-" * 55) - self._plw.logline("start new logfile: {}".format(asctime())) - - proginit.logger.debug("leave RevPiPlc.newlogfile()") - - def run(self): - """Fuehrt PLC-Programm aus und ueberwacht es.""" - proginit.logger.debug("enter RevPiPlc.run()") - if self._pversion == 2: - lst_proc = shlex.split("/usr/bin/env python2 -u {} {}".format( - self._program, self._arguments - )) - else: - lst_proc = shlex.split("/usr/bin/env python3 -u {} {}".format( - self._program, self._arguments - )) - - # Prozess erstellen - proginit.logger.info("start plc program {}".format(self._program)) - self._procplc = self._spopen(lst_proc) - - # LogWriter starten und Logausgaben schreiben - if self._plw is not None: - self._plw.logline("-" * 55) - self._plw.logline("plc: {} started: {}".format( - os.path.basename(self._program), asctime() - )) - self._plw.start() - - while not self._evt_exit.is_set(): - - # Auswerten - self.exitcode = self._procplc.poll() - - if self.exitcode is not None: - if self.exitcode > 0: - # PLC Python Programm abgestürzt - proginit.logger.error( - "plc program crashed - exitcode: {}".format( - self.exitcode - ) - ) - if self.zeroonerror: - self._zeroprocimg() - proginit.logger.warning( - "set piControl0 to ZERO after PLC program error") - - else: - # PLC Python Programm sauber beendet - proginit.logger.info("plc program did a clean exit") - if self.zeroonexit: - self._zeroprocimg() - proginit.logger.info( - "set piControl0 to ZERO after PLC program returns " - "clean exitcode") - - if not self._evt_exit.is_set() and self.autoreload: - # Prozess neu starten - self._procplc = self._spopen(lst_proc) - if self.exitcode == 0: - proginit.logger.warning( - "restart plc program after clean exit" - ) - else: - proginit.logger.warning( - "restart plc program after crash" - ) - else: - break - - self._evt_exit.wait(1) - - if self._plw is not None: - self._plw.logline("-" * 55) - self._plw.logline("plc: {} stopped: {}".format( - os.path.basename(self._program), asctime() - )) - - proginit.logger.debug("leave RevPiPlc.run()") - - def stop(self): - """Beendet PLC-Programm.""" - proginit.logger.debug("enter RevPiPlc.stop()") - proginit.logger.info("stop revpiplc thread") - self._evt_exit.set() - - # Prüfen ob es einen subprocess gibt - if self._procplc is None: - if self._plw is not None: - self._plw.stop() - self._plw.join() - proginit.logger.debug("log pipes successfully closed") - - proginit.logger.debug("leave RevPiPlc.stop()") - return - - # Prozess beenden - count = 0 - proginit.logger.info("term plc program {}".format(self._program)) - self._procplc.terminate() - - while self._procplc.poll() is None and count < 10: - count += 1 - proginit.logger.info( - "wait term plc program {} seconds".format(count * 0.5) - ) - sleep(0.5) - if self._procplc.poll() is None: - proginit.logger.warning( - "can not term plc program {}".format(self._program) - ) - self._procplc.kill() - proginit.logger.warning("killed plc program") - - # Exitcode auswerten - self.exitcode = self._procplc.poll() - if self.zeroonexit and self.exitcode == 0 \ - or self.zeroonerror and self.exitcode != 0: - self._zeroprocimg() - - if self._plw is not None: - self._plw.stop() - self._plw.join() - proginit.logger.debug("log pipes successfully closed") - - proginit.logger.debug("leave RevPiPlc.stop()") +pyloadverion = "0.4.3" class RevPiPyLoad(): @@ -444,56 +63,21 @@ class RevPiPyLoad(): def __init__(self): """Instantiiert RevPiPyLoad-Klasse.""" - proginit.configure() proginit.logger.debug("enter RevPiPyLoad.__init__()") - # piCtory Konfiguration an bekannten Stellen prüfen - global configrsc - configrsc = proginit.pargs.configrsc - lst_rsc = ["/etc/revpi/config.rsc", "/opt/KUNBUS/config.rsc"] - for rscfile in lst_rsc: - if os.access(rscfile, os.F_OK | os.R_OK): - configrsc = rscfile - break - if configrsc is None: - raise RuntimeError( - "can not find known pictory configurations at {}" - "".format(", ".join(lst_rsc)) - ) - - # Alternatives Processabbild verwenden - if proginit.pargs.procimg is not None: - global procimg - procimg = proginit.pargs.procimg - - # rap Katalog an bekannten Stellen prüfen und laden - global rapcatalog - lst_rap = [ - "/opt/KUNBUS/pictory/resources/data/rap", - "/var/www/pictory/resources/data/rap" - ] - for rapfolder in lst_rap: - if os.path.isdir(rapfolder): - rapcatalog = os.listdir(rapfolder) - - # piControlReset suchen - global picontrolreset - if not os.access(picontrolreset, os.F_OK | os.X_OK): - picontrolreset = "/usr/bin/piTest -x" - # Klassenattribute self._exit = True - self.pictorymtime = os.path.getmtime(configrsc) + self.pictorymtime = os.path.getmtime(proginit.pargs.configrsc) self.evt_loadconfig = Event() self.globalconfig = ConfigParser() - self.logr = LogReader() + self.logr = logsystem.LogReader() self.plc = None self.tfile = {} self.tpe = None self.xsrv = None self.xml_ps = None - # Load config + # Konfiguration laden self._loadconfig() # Signal events @@ -579,7 +163,7 @@ class RevPiPyLoad(): try: import procimgserver self.xml_ps = procimgserver.ProcimgServer( - proginit.logger, self.xsrv, configrsc, procimg, self.xmlrpc + self.xsrv, self.xmlrpc ) self.xsrv.register_function(self.xml_psstart, "psstart") self.xsrv.register_function(self.xml_psstop, "psstop") @@ -612,7 +196,8 @@ class RevPiPyLoad(): self.xsrv.register_function( self.xml_plcuploadclean, "plcuploadclean") self.xsrv.register_function( - lambda: os.system(picontrolreset), "resetpicontrol") + lambda: os.system(proginit.picontrolreset), + "resetpicontrol") self.xsrv.register_function( self.xml_setconfig, "set_config") self.xsrv.register_function( @@ -643,7 +228,7 @@ class RevPiPyLoad(): return None proginit.logger.debug("create PLC watcher") - th_plc = RevPiPlc( + th_plc = plcsystem.RevPiPlc( os.path.join(self.plcworkdir, self.plcprog), self.plcarguments, self.pythonver @@ -660,11 +245,7 @@ class RevPiPyLoad(): def _sigexit(self, signum, frame): """Signal handler to clean and exit program.""" proginit.logger.debug("enter RevPiPyLoad._sigexit()") - - # Programm stoppen und aufräumen self.stop() - proginit.cleanup() - proginit.logger.debug("leave RevPiPyLoad._sigexit()") def _sigloadconfig(self, signum, frame): @@ -718,7 +299,9 @@ class RevPiPyLoad(): os.path.join(tup_dir[0], file), arcname=arcname ) if pictory: - fh_pack.write(configrsc, arcname="config.rsc") + fh_pack.write( + proginit.pargs.configrsc, arcname="config.rsc" + ) except: filename = "" finally: @@ -730,7 +313,7 @@ class RevPiPyLoad(): try: fh_pack.add(".", arcname=os.path.basename(self.plcworkdir)) if pictory: - fh_pack.add(configrsc, arcname="config.rsc") + fh_pack.add(proginit.pargs.configrsc, arcname="config.rsc") except: filename = "" finally: @@ -760,9 +343,9 @@ class RevPiPyLoad(): and not self.evt_loadconfig.is_set(): # piCtory auf Veränderung prüfen - if self.pictorymtime != os.path.getmtime(configrsc): + if self.pictorymtime != os.path.getmtime(proginit.pargs.configrsc): proginit.logger.warning("piCtory configuration was changed") - self.pictorymtime = os.path.getmtime(configrsc) + self.pictorymtime = os.path.getmtime(proginit.pargs.configrsc) if self.xml_ps is not None: self.xml_psstop() @@ -831,7 +414,7 @@ class RevPiPyLoad(): """Gibt die config.rsc Datei von piCotry zurueck. @return xmlrpc.client.Binary()""" proginit.logger.debug("xmlrpc call getpictoryrsc") - with open(configrsc, "rb") as fh: + with open(proginit.pargs.configrsc, "rb") as fh: buff = fh.read() return Binary(buff) @@ -839,7 +422,7 @@ class RevPiPyLoad(): """Gibt die Rohdaten aus piControl0 zurueck. @return xmlrpc.client.Binary()""" proginit.logger.debug("xmlrpc call getprocimg") - with open(procimg, "rb") as fh: + with open(proginit.pargs.procimg, "rb") as fh: buff = fh.read() return Binary(buff) @@ -872,7 +455,7 @@ class RevPiPyLoad(): -3 Lief nie """ - proginit.logger.debug("xmlrpc call plcexitcode") + # NOTE: proginit.logger.debug("xmlrpc call plcexitcode") if self.plc is None: return -2 elif self.plc.is_alive(): @@ -1043,7 +626,7 @@ class RevPiPyLoad(): return -2 # Prüfen ob Modulkatalog vorhanden ist - if rapcatalog is None: + if proginit.rapcatalog is None: return -5 else: @@ -1051,7 +634,7 @@ class RevPiPyLoad(): for picdev in jconfigrsc["Devices"]: found = False picdev = picdev["id"][7:-4] - for rapdev in rapcatalog: + for rapdev in proginit.rapcatalog: if rapdev.find(picdev) >= 0: found = True @@ -1060,13 +643,13 @@ class RevPiPyLoad(): return -4 try: - with open(configrsc, "wb") as fh: + with open(proginit.pargs.configrsc, "wb") as fh: fh.write(filebytes.data) except: return -3 else: if reset: - return os.system(picontrolreset) + return os.system(proginit.picontrolreset) else: return 0 @@ -1089,5 +672,12 @@ class RevPiPyLoad(): if __name__ == "__main__": + # Programmeinstellungen konfigurieren + proginit.configure() + + # Programm starten root = RevPiPyLoad() root.start() + + # Aufräumen + proginit.cleanup() diff --git a/setup.py b/setup.py index 64cce8f..1d194ca 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ setup( license="LGPLv3", name="revpipyload", - version="0.4.2", + version="0.4.3", scripts=["data/revpipyload"],