diff --git a/debian/revpipyload.init b/debian/revpipyload.init index 5d19d9c..fa2f8e0 100755 --- a/debian/revpipyload.init +++ b/debian/revpipyload.init @@ -5,8 +5,8 @@ # Required-Stop: $remote_fs $syslog $piControl # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 -# Short-Description: Start RevPiPyLoad to execute python plc program -# Description: This file starts the RevPiPyLoad on system +# Short-Description: RevPiPyLoad to execute python plc program +# Description: This file manages the RevPiPyLoad on system # boot. The Loader starts your python plc program and # check whether it is running. ### END INIT INFO diff --git a/doc/index.html b/doc/index.html new file mode 100644 index 0000000..418bfe4 --- /dev/null +++ b/doc/index.html @@ -0,0 +1,26 @@ + + +Table of contents + + + +

+Table of contents

+ + + +

+Modules

+ + + + + + + + + + + +
procimgserverStellt Funktionen bereit um das Prozessabbild zu ueberwachen.
proginitMain functions of our program.
revpipyloadRevolution Pi Python PLC Loader.
+ \ No newline at end of file diff --git a/doc/procimgserver.html b/doc/procimgserver.html new file mode 100644 index 0000000..12a44e9 --- /dev/null +++ b/doc/procimgserver.html @@ -0,0 +1,207 @@ + + +procimgserver + + + +

+procimgserver

+

+Stellt Funktionen bereit um das Prozessabbild zu ueberwachen. +

+Bei ausreichend Rechten koennen Ausgaenge auch gesetzt werden um einen +IO-Check bei Inbetriebname durchzufuehren. +

+

+Global Attributes

+ + +
None
+

+Classes

+ + + + + +
ProcimgServerServerkomponente fuer zusaetzliche XML-RPC Funktionen.
+

+Functions

+ + +
None
+

+ +

ProcimgServer

+

+Serverkomponente fuer zusaetzliche XML-RPC Funktionen. +

+ Diese Klasse registriert zusaetzliche Funktionen an einem besthenden + XML-RPC-Server. Der Funktionsumfang wird erweitert um zyklisch das + Prozessabbild zu empfangen und bei ausreichend Rechten Ausgaenge zu + setzen. +

+ +

+

+Derived from

+None +

+Class Attributes

+ + +
None
+

+Class Methods

+ + +
None
+

+Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
ProcimgServerInstantiiert RevPiCheckServer()-Klasse.
devicesGeneriert Deviceliste mit Position und Namen.
iosGeneriert ein dict() der Devices und IOs.
loadrevpimodioInstantiiert das RevPiModIO Modul.
setvalueSetzt einen Wert auf dem RevPi.
startRegistriert XML Funktionen.
stopEntfernt XML-Funktionen.
valuesLiefert Prozessabbild an Client.
+

+Static Methods

+ + +
None
+ +

+ProcimgServer (Constructor)

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

+Instantiiert RevPiCheckServer()-Klasse. +

+
xmlserver
+
+XML-RPC Server +
procimg
+
+Pfad zum Prozessabbild +
configrsc
+
+Pfad zur piCtory Konfigurationsdatei +
logger
+
+Loggerinstanz +
aclmode
+
+Zugriffsrechte +
+
+

+ProcimgServer.devices

+devices() +

+Generiert Deviceliste mit Position und Namen. +

+
Returns:
+
+list() mit Tuple (pos, name) +
+
+

+ProcimgServer.ios

+ios(type) +

+Generiert ein dict() der Devices und IOs. +

+
type
+
+IO Typ inp/out +
+
+
Returns:
+
+pickled dict() +
+
+

+ProcimgServer.loadrevpimodio

+loadrevpimodio() +

+Instantiiert das RevPiModIO Modul. +

+
Returns:
+
+True, wenn erfolgreich, sonst False +
+
+

+ProcimgServer.setvalue

+setvalue(device, io, value) +

+Setzt einen Wert auf dem RevPi. +

+
device
+
+Device Position oder Name +
io
+
+IO Name fuer neuen Wert +
value
+
+Neuer Wert +
+
+
Returns:
+
+list() [device, io, status, msg] +
+
+

+ProcimgServer.start

+start() +

+Registriert XML Funktionen. +

+
Returns:
+
+True, wenn erfolgreich +
+
+

+ProcimgServer.stop

+stop() +

+Entfernt XML-Funktionen. +

+

+ProcimgServer.values

+values() +

+Liefert Prozessabbild an Client. +

+
Returns:
+
+Binary() bytes or None +
+
+
Up
+
+ \ No newline at end of file diff --git a/doc/revpipyload.html b/doc/revpipyload.html index 16b3a4e..8edc9dc 100644 --- a/doc/revpipyload.html +++ b/doc/revpipyload.html @@ -32,7 +32,7 @@ begrenzt werden!

Global Attributes

- +
configrsc
picontrolreset
procimg
pyloadverion
configrsc
picontrolreset
procimg
pyloadverion
rapcatalog

Classes

@@ -64,8 +64,6 @@ Ermoeglicht den Zugriff auf die Logdateien.

Beinhaltet Funktionen fuer den Abruf der gesamten Logdatei fuer das RevPiPyLoad-System und die Logdatei der PLC-Anwendung. - Ausserdem koennen nur neue Zeilen abgerufen werden, um eine dynamische - Logansicht zu ermoeglichen.

@@ -92,17 +90,11 @@ Methods closeall Fuehrt close auf File Handler durch. -get_applines -Gibt neue Zeilen ab letzen Aufruf zurueck. +load_applog +Uebertraegt Logdaten des PLC Programms Binaer. -get_applog -Gibt die gesamte Logdatei zurueck. - -get_plclines -Gibt neue Zeilen ab letzen Aufruf zurueck. - -get_plclog -Gibt die gesamte Logdatei zurueck. +load_plclog +Uebertraegt Logdaten des Loaders Binaer.

@@ -122,49 +114,43 @@ LogReader.closeall

closeall()

Fuehrt close auf File Handler durch. -

+

-LogReader.get_applines

-get_applines() +LogReader.load_applog +load_applog(start, count)

-Gibt neue Zeilen ab letzen Aufruf zurueck. +Uebertraegt Logdaten des PLC Programms Binaer.

-
Returns:
+
start
-list() mit neuen Zeilen +Startbyte +
count
+
+Max. Byteanzahl zum uebertragen
-
-

-LogReader.get_applog

-get_applog() -

-Gibt die gesamte Logdatei zurueck. -

+
Returns:
-str() mit Logdaten +Binary() der Logdatei
-
+

-LogReader.get_plclines

-get_plclines() +LogReader.load_plclog +load_plclog(start, count)

-Gibt neue Zeilen ab letzen Aufruf zurueck. +Uebertraegt Logdaten des Loaders Binaer.

-
Returns:
+
start
-list() mit neuen Zeilen +Startbyte +
count
+
+Max. Byteanzahl zum uebertragen
-
-

-LogReader.get_plclog

-get_plclog() -

-Gibt die gesamte Logdatei zurueck. -

+
Returns:
-str() mit Logdaten +Binary() der Logdatei
Up
@@ -232,7 +218,7 @@ PipeLogwriter (Constructor)

Instantiiert PipeLogwriter-Klasse.

-
logfilename:
+
logfilename
Dateiname fuer Logdatei
@@ -260,7 +246,7 @@ PipeLogwriter.logline

Schreibt eine Zeile in die Logdatei oder stdout.

-
message:
+
message
Logzeile zum Schreiben
@@ -374,7 +360,7 @@ RevPiPlc._spopen

Startet das PLC Programm.

-
lst_proc:
+
lst_proc
Prozessliste
@@ -456,13 +442,13 @@ Methods Signal handler to start new logfile. packapp -Erzeugt aus dem PLC-Programm ein TAR-File. +Erzeugt aus dem PLC-Programm ein TAR/Zip-File. start -Start plcload and PLC python program. +Start revpipyload. stop -Stop PLC python program and plcload. +Stop revpipyload. xml_getconfig Uebertraegt die RevPiPyLoad Konfiguration. @@ -492,11 +478,17 @@ Methods Stoppt das PLC Programm. xml_plcupload -Empfaengt Dateien fuer das PLC Programm. +Empfaengt Dateien fuer das PLC Programm einzeln. xml_plcuploadclean Loescht das gesamte plcworkdir Verzeichnis. +xml_psstart +Startet den Prozessabbildserver. + +xml_psstop +Stoppt den Prozessabbildserver. + xml_reload Startet RevPiPyLoad neu und verwendet neue Konfiguraiton. @@ -558,12 +550,12 @@ Signal handler to start new logfile. RevPiPyLoad.packapp packapp(mode="tar", pictory=False)

-Erzeugt aus dem PLC-Programm ein TAR-File. +Erzeugt aus dem PLC-Programm ein TAR/Zip-File.

-
mode:
+
mode
Packart 'tar' oder 'zip' -
pictory:
+
pictory
piCtory Konfiguration mit einpacken
@@ -577,13 +569,13 @@ Dateinamen des Archivs RevPiPyLoad.start start()

-Start plcload and PLC python program. +Start revpipyload.

RevPiPyLoad.stop

stop()

-Stop PLC python program and plcload. +Stop revpipyload.

RevPiPyLoad.xml_getconfig

@@ -635,10 +627,10 @@ RevPiPyLoad.xml_plcdownload

Uebertraegt ein Archiv vom plcworkdir.

-
mode:
+
mode
Archivart 'tar' 'zip' -
pictory:
+
pictory
piCtory Konfiguraiton mit einpacken
@@ -682,6 +674,7 @@ Startet das PLC Programm.
Returns:
int() Status: + -0 Erfolgreich -1 Programm lauft noch -2 Datei nicht gefunden
@@ -695,6 +688,7 @@ Stoppt das PLC Programm.
Returns:
int() Exitcode vom PLC Programm + -0 Erfolgreich -1 PLC Programm lief nicht
@@ -702,12 +696,12 @@ int() Exitcode vom PLC Programm RevPiPyLoad.xml_plcupload xml_plcupload(filedata, filename)

-Empfaengt Dateien fuer das PLC Programm. +Empfaengt Dateien fuer das PLC Programm einzeln.

-
filedata:
+
filedata
GZIP Binary data der datei -
filename:
+
filename
Name inkl. Unterverzeichnis der Datei
@@ -727,6 +721,28 @@ Loescht das gesamte plcworkdir Verzeichnis.
True, wenn erfolgreich
+
+

+RevPiPyLoad.xml_psstart

+xml_psstart() +

+Startet den Prozessabbildserver. +

+
Returns:
+
+True, wenn start erfolgreich +
+
+

+RevPiPyLoad.xml_psstop

+xml_psstop() +

+Stoppt den Prozessabbildserver. +

+
Returns:
+
+True, wenn stop erfolgreich +

RevPiPyLoad.xml_reload

@@ -751,10 +767,10 @@ RevPiPyLoad.xml_setpictoryrsc

Schreibt die config.rsc Datei von piCotry.

-
filebytes:
+
filebytes
xmlrpc.client.Binary()-Objekt -
reset:
+
reset
Reset piControl Device
@@ -762,10 +778,12 @@ Reset piControl Device
Returns:
Statuscode: - 0 Alles erfolgreich + -0 Alles erfolgreich -1 Kann JSON-Datei nicht laden -2 piCtory Elemente in JSON-Datei nicht gefunden -3 Konnte Konfiguraiton nicht schreiben + -4 Module in Konfiguration enthalten, die es nicht gibt + -5 Kein RAP Katalog zur Ueberpruefung gefunden Positive Zahl ist exitcode von piControlReset
diff --git a/eric-revpipyload.api b/eric-revpipyload.api index 080cf22..bdcbe29 100644 --- a/eric-revpipyload.api +++ b/eric-revpipyload.api @@ -1,3 +1,11 @@ +procimgserver.ProcimgServer.devices?4() +procimgserver.ProcimgServer.ios?4(type) +procimgserver.ProcimgServer.loadrevpimodio?4() +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) proginit.cleanup?4() proginit.configure?4() proginit.forked?7 @@ -8,10 +16,8 @@ proginit.logplc?7 proginit.pargs?7 proginit.startdir?7 revpipyload.LogReader.closeall?4() -revpipyload.LogReader.get_applines?4() -revpipyload.LogReader.get_applog?4() -revpipyload.LogReader.get_plclines?4() -revpipyload.LogReader.get_plclog?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() @@ -48,6 +54,8 @@ revpipyload.RevPiPyLoad.xml_plcstart?4() revpipyload.RevPiPyLoad.xml_plcstop?4() revpipyload.RevPiPyLoad.xml_plcupload?4(filedata, filename) revpipyload.RevPiPyLoad.xml_plcuploadclean?4() +revpipyload.RevPiPyLoad.xml_psstart?4() +revpipyload.RevPiPyLoad.xml_psstop?4() revpipyload.RevPiPyLoad.xml_reload?4() revpipyload.RevPiPyLoad.xml_setconfig?4(dc, loadnow=False) revpipyload.RevPiPyLoad.xml_setpictoryrsc?4(filebytes, reset=False) @@ -56,3 +64,4 @@ revpipyload.configrsc?7 revpipyload.picontrolreset?7 revpipyload.procimg?7 revpipyload.pyloadverion?7 +revpipyload.rapcatalog?7 diff --git a/revpipyload.e4p b/revpipyload.e4p index 279b131..eff7f78 100644 --- a/revpipyload.e4p +++ b/revpipyload.e4p @@ -1,7 +1,7 @@ - + en_US @@ -9,7 +9,7 @@ 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.3.0 + 0.4.2 Sven Sager akira@narux.de @@ -17,6 +17,7 @@ revpipyload/proginit.py setup.py revpipyload/revpipyload.py + revpipyload/procimgserver.py @@ -28,7 +29,9 @@ doc debian eric-revpipyload.api + stdeb.cfg + revpipyload/revpipyload.py Mercurial @@ -209,12 +212,6 @@ setup.py - - noindex - - - True - outputDirectory diff --git a/revpipyload/procimgserver.py b/revpipyload/procimgserver.py new file mode 100644 index 0000000..c135843 --- /dev/null +++ b/revpipyload/procimgserver.py @@ -0,0 +1,207 @@ +# +# 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 +IO-Check bei Inbetriebname durchzufuehren. + +""" +import pickle +import revpimodio +from xmlrpc.client import Binary + + +class ProcimgServer(): + + """Serverkomponente fuer zusaetzliche XML-RPC Funktionen. + + Diese Klasse registriert zusaetzliche Funktionen an einem besthenden + XML-RPC-Server. Der Funktionsumfang wird erweitert um zyklisch das + Prozessabbild zu empfangen und bei ausreichend Rechten Ausgaenge zu + setzen. + + """ + + def __init__(self, logger, xmlserver, configrsc, procimg, 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__()") + self.acl = aclmode + self.configrsc = configrsc + self.procimg = procimg + self.rpi = None + + # XML-Server übernehmen + self.xmlsrv = xmlserver + self.xmlreadfuncs = { + "ps_devices": self.devices, + "ps_inps": lambda: self.ios("inp"), + "ps_outs": lambda: self.ios("out"), + "ps_mems": lambda: self.ios("mem"), + "ps_values": self.values, + } + self.xmlwritefuncs = { + "ps_setvalue": self.setvalue, + } + + self.loadrevpimodio() + + self.logger.debug("leave ProcimgServer.__init__()") + + def devices(self): + """Generiert Deviceliste mit Position und Namen. + @return list() mit Tuple (pos, name)""" + return [ + (dev.position, dev.name) for dev in self.rpi.devices + ] + + def ios(self, type): + """Generiert ein dict() der Devices und IOs. + @param type IO Typ inp/out + @return pickled dict()""" + dict_ios = {} + for dev in self.rpi.devices: + dict_ios[dev.position] = [] + + # IO Typen auswerten + if type == "inp": + lst_io = dev.get_inps() + elif type == "out": + lst_io = dev.get_outs() + elif type == "mem": + lst_io = dev.get_mems() + else: + lst_io = [] + + for io in lst_io: + dict_ios[dev.position].append([ + io.name, + 1 if io._bitlength == 1 else int(io._bitlength / 8), + io.slc_address.start + dev.offset, + io.bmk, + io._bitaddress, + ]) + return Binary(pickle.dumps(dict_ios)) + + def loadrevpimodio(self): + """Instantiiert das RevPiModIO Modul. + @return True, wenn erfolgreich, sonst False""" + # RevPiModIO-Modul Instantiieren + if self.rpi is not None: + self.rpi.cleanup() + + self.logger.debug("create revpimodio class") + try: + self.rpi = revpimodio.RevPiModIO( + configrsc=self.configrsc, + procimg=self.procimg, + ) + except: + self.rpi = None + self.logger.error("piCtory configuration not loadable") + return False + + self.rpi.devices.syncoutputs(device=0) + self.logger.debug("created revpimodio class") + return True + + def setvalue(self, device, io, value): + """Setzt einen Wert auf dem RevPi. + + @param device Device Position oder Name + @param io IO Name fuer neuen Wert + @param value Neuer Wert + @return list() [device, io, status, msg] + + """ + # Zugriffsrechte prüfen + if self.acl < 3: + return [ + device, io, False, + "not allowed in XML-RPC permission mode {}".format(self.acl) + ] + + # Binary() in bytes() umwandeln + if type(value) == Binary: + value = value.data + + self.rpi.devices.syncoutputs(device=device) + + try: + # Neuen Wert übernehmen + if type(value) == bytes or type(value) == bool: + self.rpi.devices[device][io].set_value(value) + else: + self.rpi.devices[device][io].set_value( + value.to_bytes( + self.rpi.devices[device][io].length, byteorder="little" + ) + ) + except Exception as e: + return [device, io, False, str(e)] + + self.rpi.devices.writeprocimg(device=device) + return [device, io, True, ""] + + def values(self): + """Liefert Prozessabbild an Client. + @return Binary() bytes or None""" + if self.rpi.devices.readprocimg() and self.rpi.devices.syncoutputs(): + bytebuff = b'' + for dev in self.rpi.devices: + bytebuff += bytes(dev) + return Binary(bytebuff) + else: + return None + + def start(self): + """Registriert XML Funktionen. + @return True, wenn erfolgreich""" + self.logger.debug("enter ProcimgServer.start()") + ec = False + + if self.rpi is not None: + + # Registriere Funktionen + for xmlfunc in self.xmlreadfuncs: + self.xmlsrv.register_function( + self.xmlreadfuncs[xmlfunc], xmlfunc + ) + if self.acl >= 3: + for xmlfunc in self.xmlwritefuncs: + self.xmlsrv.register_function( + self.xmlwritefuncs[xmlfunc], xmlfunc + ) + ec = True + + self.logger.debug("leave ProcimgServer.start()") + return ec + + def stop(self): + """Entfernt XML-Funktionen.""" + self.logger.debug("enter ProcimgServer.stop()") + + # Entferne Funktionen + for xmlfunc in self.xmlreadfuncs: + if xmlfunc in self.xmlsrv.funcs: + del self.xmlsrv.funcs[xmlfunc] + if self.acl >= 3: + for xmlfunc in self.xmlwritefuncs: + if xmlfunc in self.xmlsrv.funcs: + del self.xmlsrv.funcs[xmlfunc] + + self.logger.debug("leave ProcimgServer.stop()") diff --git a/revpipyload/proginit.py b/revpipyload/proginit.py index 6340bd6..3bc0e98 100644 --- a/revpipyload/proginit.py +++ b/revpipyload/proginit.py @@ -102,7 +102,8 @@ def configure(): # Program logger global logger - logger = logging.getLogger() + if logger is None: + logger = logging.getLogger() # Alle handler entfernen for lhandler in logger.handlers: diff --git a/revpipyload/revpipyload.py b/revpipyload/revpipyload.py index 2540acd..964d841 100755 --- a/revpipyload/revpipyload.py +++ b/revpipyload/revpipyload.py @@ -33,7 +33,6 @@ begrenzt werden! import gzip import proginit import os -import pickle import shlex import signal import socket @@ -47,7 +46,7 @@ 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 mktemp +from tempfile import mkstemp from threading import Thread, Event, Lock from time import sleep, asctime from timeit import default_timer @@ -57,7 +56,8 @@ from xmlrpc.server import SimpleXMLRPCServer configrsc = None picontrolreset = "/opt/KUNBUS/piControlReset" procimg = "/dev/piControl0" -pyloadverion = "0.3.0" +pyloadverion = "0.4.2" +rapcatalog = None def _zeroprocimg(): @@ -73,17 +73,15 @@ class LogReader(): Beinhaltet Funktionen fuer den Abruf der gesamten Logdatei fuer das RevPiPyLoad-System und die Logdatei der PLC-Anwendung. - Ausserdem koennen nur neue Zeilen abgerufen werden, um eine dynamische - Logansicht zu ermoeglichen. """ def __init__(self): """Instantiiert LogReader-Klasse.""" self.fhapp = None - self.posapp = 0 + self.fhapplk = Lock() self.fhplc = None - self.posplc = 0 + self.fhplclk = Lock() def closeall(self): """Fuehrt close auf File Handler durch.""" @@ -92,84 +90,45 @@ class LogReader(): if self.fhplc is not None: self.fhplc.close() - def get_applines(self): - """Gibt neue Zeilen ab letzen Aufruf zurueck. - @returns: list() mit neuen Zeilen""" + 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(pickle.dumps([])) + return Binary(b'\x16') #  + elif start > os.path.getsize(proginit.logapp): + return Binary(b'\x19') #  else: - if self.fhapp is None or self.fhapp.closed: - self.fhapp = open(proginit.logapp) + with self.fhapplk: + if self.fhapp is None or self.fhapp.closed: + self.fhapp = open(proginit.logapp, "rb") - lst_new = [] - while True: - self.posapp = self.fhapp.tell() - line = self.fhapp.readline() - if line: - lst_new.append(line) - else: - self.fhapp.seek(self.posapp) - break + self.fhapp.seek(start) + return Binary(self.fhapp.read(count)) - proginit.logger.debug( - "got {} new app log lines".format(len(lst_new)) - ) - return Binary(pickle.dumps(lst_new)) + def load_plclog(self, start, count): + """Uebertraegt Logdaten des Loaders Binaer. - def get_applog(self): - """Gibt die gesamte Logdatei zurueck. - @returns: str() mit Logdaten""" - if not os.access(proginit.logapp, os.R_OK): - proginit.logger.error( - "can not access logfile {}".format(proginit.logapp) - ) - return Binary(pickle.dumps("")) - else: - if self.fhapp is None or self.fhapp.closed: - self.fhapp = open(proginit.logapp) - self.fhapp.seek(0) - return Binary(pickle.dumps(self.fhapp.read())) + @param start Startbyte + @param count Max. Byteanzahl zum uebertragen + @return Binary() der Logdatei - def get_plclines(self): - """Gibt neue Zeilen ab letzen Aufruf zurueck. - @returns: list() mit neuen Zeilen""" + """ if not os.access(proginit.logplc, os.R_OK): - proginit.logger.error( - "can not access logfile {}".format(proginit.logplc) - ) - return Binary(pickle.dumps([])) + return Binary(b'\x16') #  + elif start > os.path.getsize(proginit.logplc): + return Binary(b'\x19') #  else: - if self.fhplc is None or self.fhplc.closed: - self.fhplc = open(proginit.logplc) + with self.fhplclk: + if self.fhplc is None or self.fhplc.closed: + self.fhplc = open(proginit.logplc, "rb") - lst_new = [] - while True: - self.posplc = self.fhplc.tell() - line = self.fhplc.readline() - if line: - lst_new.append(line) - else: - self.fhplc.seek(self.posplc) - break - - proginit.logger.debug( - "got {} new pyloader log lines".format(len(lst_new)) - ) - return Binary(pickle.dumps(lst_new)) - - def get_plclog(self): - """Gibt die gesamte Logdatei zurueck. - @returns: str() mit Logdaten""" - if not os.access(proginit.logplc, os.R_OK): - proginit.logger.error( - "can not access logfile {}".format(proginit.logplc) - ) - return Binary(pickle.dumps("")) - else: - if self.fhplc is None or self.fhplc.closed: - self.fhplc = open(proginit.logplc) - self.fhplc.seek(0) - return Binary(pickle.dumps(self.fhplc.read())) + self.fhplc.seek(start) + return Binary(self.fhplc.read(count)) class PipeLogwriter(Thread): @@ -185,7 +144,7 @@ class PipeLogwriter(Thread): def __init__(self, logfilename): """Instantiiert PipeLogwriter-Klasse. - @param logfilename: Dateiname fuer Logdatei""" + @param logfilename Dateiname fuer Logdatei""" super().__init__() self._exit = Event() self._fh = None @@ -208,11 +167,10 @@ class PipeLogwriter(Thread): def _configurefh(self): """Konfiguriert den FileHandler fuer Ausgaben der PLCAPP. - @returns: FileHandler-Objekt""" + @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): @@ -225,7 +183,7 @@ class PipeLogwriter(Thread): def logline(self, message): """Schreibt eine Zeile in die Logdatei oder stdout. - @param message: Logzeile zum Schreiben""" + @param message Logzeile zum Schreiben""" with self._lckfh: self._fh.write("{}\n".format(message)) self._fh.flush() @@ -243,7 +201,6 @@ class PipeLogwriter(Thread): proginit.logger.debug("enter PipeLogwriter.run()") fhread = os.fdopen(self._fdr) - proginit.logger.debug("enter logreader pipe loop") while not self._exit.is_set(): line = fhread.readline() self._lckfh.acquire() @@ -251,7 +208,7 @@ class PipeLogwriter(Thread): self._fh.write(line) self._fh.flush() except: - proginit.logger.exception("PipeLogwriter write log line") + proginit.logger.exception("PipeLogwriter in write log line") finally: self._lckfh.release() proginit.logger.debug("leave logreader pipe loop") @@ -307,7 +264,7 @@ class RevPiPlc(Thread): def _configureplw(self): """Konfiguriert den PipeLogwriter fuer Ausgaben der PLCAPP. - @returns: PipeLogwriter()""" + @return PipeLogwriter()""" proginit.logger.debug("enter RevPiPlc._configureplw()") logfile = None if proginit.pargs.daemon: @@ -324,15 +281,17 @@ class RevPiPlc(Thread): def _setuppopen(self): """Setzt UID und GID fuer das PLC Programm.""" - proginit.logger.debug( - "set uid {} and gid {}".format(self.uid, self.gid)) + 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 - @returns: subprocess""" + @param lst_proc Prozessliste + @return subprocess""" proginit.logger.debug("enter RevPiPlc._spopen({})".format(lst_proc)) sp = subprocess.Popen( lst_proc, @@ -388,7 +347,7 @@ class RevPiPlc(Thread): if self.exitcode > 0: # PLC Python Programm abgestürzt proginit.logger.error( - "plc program chrashed - exitcode: {}".format( + "plc program crashed - exitcode: {}".format( self.exitcode ) ) @@ -422,6 +381,12 @@ class RevPiPlc(Thread): 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): @@ -434,9 +399,8 @@ class RevPiPlc(Thread): if self._procplc is None: if self._plw is not None: self._plw.stop() - proginit.logger.debug("join after NONE pipe thread") self._plw.join() - proginit.logger.debug("joined after NONE pipe thread") + proginit.logger.debug("log pipes successfully closed") proginit.logger.debug("leave RevPiPlc.stop()") return @@ -467,9 +431,8 @@ class RevPiPlc(Thread): if self._plw is not None: self._plw.stop() - proginit.logger.debug("join pipe thread") self._plw.join() - proginit.logger.debug("joined pipe thread") + proginit.logger.debug("log pipes successfully closed") proginit.logger.debug("leave RevPiPlc.stop()") @@ -675,12 +638,10 @@ class RevPiPyLoad(): def __init__(self): """Instantiiert RevPiPyLoad-Klasse.""" proginit.configure() - - # Globale Werte anpassen - global configrsc - global picontrolreset + proginit.logger.debug("enter RevPiPyLoad.__init__()") # piCtory Konfiguration an bekannten Stellen prüfen + global 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): @@ -688,16 +649,28 @@ class RevPiPyLoad(): break if configrsc is None: raise RuntimeError( - "can not access known pictory configurations at {}" + "can not find known pictory configurations at {}" "".format(", ".join(lst_rsc)) ) + # 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.evt_loadconfig = Event() self.globalconfig = ConfigParser() self.logr = LogReader() @@ -705,6 +678,7 @@ class RevPiPyLoad(): self.tfile = {} self.tpe = None self.xsrv = None + self.xml_ps = None # Load config self._loadconfig() @@ -715,8 +689,12 @@ class RevPiPyLoad(): signal.signal(signal.SIGHUP, self._sigloadconfig) signal.signal(signal.SIGUSR1, self._signewlogfile) + proginit.logger.debug("leave RevPiPyLoad.__init__()") + def _loadconfig(self): """Load configuration file and setup modul.""" + proginit.logger.debug("enter RevPiPyLoad._loadconfig()") + self.evt_loadconfig.clear() pauseproc = False @@ -773,18 +751,34 @@ class RevPiPyLoad(): allow_none=True ) self.xsrv.register_introspection_functions() + self.xsrv.register_multicall_functions() # XML Modus 1 Nur Logs lesen und PLC Programm neu starten - self.xsrv.register_function(self.logr.get_applines, "get_applines") - self.xsrv.register_function(self.logr.get_applog, "get_applog") - self.xsrv.register_function(self.logr.get_plclines, "get_plclines") - self.xsrv.register_function(self.logr.get_plclog, "get_plclog") + self.xsrv.register_function(self.logr.load_applog, "load_applog") + self.xsrv.register_function(self.logr.load_plclog, "load_plclog") self.xsrv.register_function(self.xml_plcexitcode, "plcexitcode") self.xsrv.register_function(self.xml_plcrunning, "plcrunning") self.xsrv.register_function(self.xml_plcstart, "plcstart") self.xsrv.register_function(self.xml_plcstop, "plcstop") self.xsrv.register_function(self.xml_reload, "reload") + # Erweiterte Funktionen anmelden + try: + import procimgserver + self.xml_ps = procimgserver.ProcimgServer( + proginit.logger, self.xsrv, configrsc, procimg, self.xmlrpc + ) + self.xsrv.register_function(self.xml_psstart, "psstart") + self.xsrv.register_function(self.xml_psstop, "psstop") + except: + self.xml_ps = None + proginit.logger.warning( + "can not load revpimodio module. maybe its not installed " + "or an old version (required at least 0.15.0). if you " + "like to use the process monitor feature, update/install " + "revpimodio: 'apt-get install python3-revpimodio'" + ) + # XML Modus 2 Einstellungen lesen und Programm herunterladen if self.xmlrpc >= 2: self.xsrv.register_function( @@ -821,9 +815,11 @@ class RevPiPyLoad(): ) self.start() + proginit.logger.debug("leave RevPiPyLoad._loadconfig()") + def _plcthread(self): """Konfiguriert den PLC-Thread fuer die Ausfuehrung. - @returns: PLC-Thread Object or None""" + @return PLC-Thread Object or None""" th_plc = None if self.plcslave: @@ -851,28 +847,29 @@ class RevPiPyLoad(): th_plc.uid = int(self.globalconfig["DEFAULT"].get("plcuid", 65534)) th_plc.zeroonerror = self.zeroonerror th_plc.zeroonexit = self.zeroonexit - proginit.logger.debug("created PLC watcher") + proginit.logger.debug("leave RevPiPyLoad._plcthread()") return th_plc def _sigexit(self, signum, frame): """Signal handler to clean and exit program.""" - proginit.logger.debug("got exit signal") - self.stop() + proginit.logger.debug("enter RevPiPyLoad._sigexit()") - # Programm aufräumen + # Programm stoppen und aufräumen + self.stop() proginit.cleanup() - proginit.logger.debug("end revpipyload program") + proginit.logger.debug("leave RevPiPyLoad._sigexit()") def _sigloadconfig(self, signum, frame): """Signal handler to load configuration.""" - proginit.logger.debug("got reload config signal") + proginit.logger.debug("enter RevPiPyLoad._sigloadconfig()") self.evt_loadconfig.set() + proginit.logger.debug("leave RevPiPyLoad._sigloadconfig()") def _signewlogfile(self, signum, frame): """Signal handler to start new logfile.""" - proginit.logger.debug("got new logfile signal") + proginit.logger.debug("enter RevPiPyLoad._signewlogfile()") # Logger neu konfigurieren proginit.configure() @@ -885,15 +882,20 @@ class RevPiPyLoad(): # Logreader schließen self.logr.closeall() - def packapp(self, mode="tar", pictory=False): - """Erzeugt aus dem PLC-Programm ein TAR-File. + proginit.logger.debug("leave RevPiPyLoad._signewlogfile()") - @param mode: Packart 'tar' oder 'zip' - @param pictory: piCtory Konfiguration mit einpacken - @returns: Dateinamen des Archivs + def packapp(self, mode="tar", pictory=False): + """Erzeugt aus dem PLC-Programm ein TAR/Zip-File. + + @param mode Packart 'tar' oder 'zip' + @param pictory piCtory Konfiguration mit einpacken + @return Dateinamen des Archivs """ - filename = mktemp(suffix=".packed", prefix="plc") + proginit.logger.debug("enter RevPiPyLoad.packapp()") + + tup_file = mkstemp(suffix="_packed", prefix="plc_") + filename = tup_file[1] if mode == "zip": fh_pack = zipfile.ZipFile(filename, mode="w") @@ -928,10 +930,13 @@ class RevPiPyLoad(): finally: fh_pack.close() + proginit.logger.debug("leave RevPiPyLoad.packapp()") return filename def start(self): - """Start plcload and PLC python program.""" + """Start revpipyload.""" + proginit.logger.debug("enter RevPiPyLoad.start()") + proginit.logger.info("starting revpipyload") self._exit = False @@ -947,14 +952,28 @@ class RevPiPyLoad(): while not self._exit \ and not self.evt_loadconfig.is_set(): + + # piCtory auf Veränderung prüfen + if self.pictorymtime != os.path.getmtime(configrsc): + proginit.logger.warning("piCtory configuration was changed") + self.pictorymtime = os.path.getmtime(configrsc) + + if self.xml_ps is not None: + self.xml_psstop() + self.xml_ps.loadrevpimodio() + self.evt_loadconfig.wait(1) if not self._exit: proginit.logger.info("exit python plc program to reload config") self._loadconfig() + proginit.logger.debug("leave RevPiPyLoad.start()") + def stop(self): - """Stop PLC python program and plcload.""" + """Stop revpipyload.""" + proginit.logger.debug("enter RevPiPyLoad.stop()") + proginit.logger.info("stopping revpipyload") self._exit = True @@ -962,6 +981,7 @@ class RevPiPyLoad(): proginit.logger.debug("stopping revpiplc-thread") self.plc.stop() self.plc.join() + proginit.logger.debug("revpiplc thread successfully closed") if self.xmlrpc >= 1: proginit.logger.info("shutting down xmlrpc-server") @@ -969,9 +989,11 @@ class RevPiPyLoad(): self.tpe.shutdown() self.xsrv.server_close() + proginit.logger.debug("leave RevPiPyLoad.stop()") + def xml_getconfig(self): """Uebertraegt die RevPiPyLoad Konfiguration. - @returns: dict() der Konfiguration""" + @return dict() der Konfiguration""" proginit.logger.debug("xmlrpc call getconfig") dc = {} dc["autoreload"] = self.autoreload @@ -990,7 +1012,7 @@ class RevPiPyLoad(): def xml_getfilelist(self): """Uebertraegt die Dateiliste vom plcworkdir. - @returns: list() mit Dateinamen""" + @return list() mit Dateinamen""" proginit.logger.debug("xmlrpc call getfilelist") lst_file = [] wd = os.walk("./") @@ -1001,7 +1023,7 @@ class RevPiPyLoad(): def xml_getpictoryrsc(self): """Gibt die config.rsc Datei von piCotry zurueck. - @returns: xmlrpc.client.Binary()""" + @return xmlrpc.client.Binary()""" proginit.logger.debug("xmlrpc call getpictoryrsc") with open(configrsc, "rb") as fh: buff = fh.read() @@ -1009,7 +1031,7 @@ class RevPiPyLoad(): def xml_getprocimg(self): """Gibt die Rohdaten aus piControl0 zurueck. - @returns: xmlrpc.client.Binary()""" + @return xmlrpc.client.Binary()""" proginit.logger.debug("xmlrpc call getprocimg") with open(procimg, "rb") as fh: buff = fh.read() @@ -1018,9 +1040,9 @@ class RevPiPyLoad(): def xml_plcdownload(self, mode="tar", pictory=False): """Uebertraegt ein Archiv vom plcworkdir. - @param mode: Archivart 'tar' 'zip' - @param pictory: piCtory Konfiguraiton mit einpacken - @returns: Binary() mit Archivdatei + @param mode Archivart 'tar' 'zip' + @param pictory piCtory Konfiguraiton mit einpacken + @return Binary() mit Archivdatei """ proginit.logger.debug("xmlrpc call plcdownload") @@ -1039,7 +1061,7 @@ class RevPiPyLoad(): def xml_plcexitcode(self): """Gibt den aktuellen exitcode vom PLC Programm zurueck. - @returns: int() exitcode oder: + @return int() exitcode oder: -1 laeuft noch -2 Datei nicht gefunden -3 Lief nie @@ -1055,14 +1077,15 @@ class RevPiPyLoad(): def xml_plcrunning(self): """Prueft ob das PLC Programm noch lauft. - @returns: True, wenn das PLC Programm noch lauft""" + @return True, wenn das PLC Programm noch lauft""" proginit.logger.debug("xmlrpc call plcrunning") return False if self.plc is None else self.plc.is_alive() def xml_plcstart(self): """Startet das PLC Programm. - @returns: int() Status: + @return int() Status: + -0 Erfolgreich -1 Programm lauft noch -2 Datei nicht gefunden @@ -1081,7 +1104,8 @@ class RevPiPyLoad(): def xml_plcstop(self): """Stoppt das PLC Programm. - @returns: int() Exitcode vom PLC Programm + @return int() Exitcode vom PLC Programm + -0 Erfolgreich -1 PLC Programm lief nicht """ @@ -1089,16 +1113,17 @@ class RevPiPyLoad(): if self.plc is not None and self.plc.is_alive(): self.plc.stop() self.plc.join() + proginit.logger.debug("revpiplc thread successfully closed") return self.plc.exitcode else: return -1 def xml_plcupload(self, filedata, filename): - """Empfaengt Dateien fuer das PLC Programm. + """Empfaengt Dateien fuer das PLC Programm einzeln. - @param filedata: GZIP Binary data der datei - @param filename: Name inkl. Unterverzeichnis der Datei - @returns: Ture, wenn Datei erfolgreich gespeichert wurde + @param filedata GZIP Binary data der datei + @param filename Name inkl. Unterverzeichnis der Datei + @return Ture, wenn Datei erfolgreich gespeichert wurde """ proginit.logger.debug("xmlrpc call plcupload") @@ -1128,7 +1153,7 @@ class RevPiPyLoad(): def xml_plcuploadclean(self): """Loescht das gesamte plcworkdir Verzeichnis. - @returns: True, wenn erfolgreich""" + @return True, wenn erfolgreich""" proginit.logger.debug("xmlrpc call plcuploadclean") try: rmtree(".", ignore_errors=True) @@ -1143,7 +1168,7 @@ class RevPiPyLoad(): def xml_setconfig(self, dc, loadnow=False): """Empfaengt die RevPiPyLoad Konfiguration. - @returns: True, wenn erfolgreich angewendet""" + @return True, wenn erfolgreich angewendet""" proginit.logger.debug("xmlrpc call setconfig") keys = { "autoreload": "[01]", @@ -1186,13 +1211,15 @@ class RevPiPyLoad(): def xml_setpictoryrsc(self, filebytes, reset=False): """Schreibt die config.rsc Datei von piCotry. - @param filebytes: xmlrpc.client.Binary()-Objekt - @param reset: Reset piControl Device - @returns: Statuscode: - 0 Alles erfolgreich + @param filebytes xmlrpc.client.Binary()-Objekt + @param reset Reset piControl Device + @return Statuscode: + -0 Alles erfolgreich -1 Kann JSON-Datei nicht laden -2 piCtory Elemente in JSON-Datei nicht gefunden -3 Konnte Konfiguraiton nicht schreiben + -4 Module in Konfiguration enthalten, die es nicht gibt + -5 Kein RAP Katalog zur Ueberpruefung gefunden Positive Zahl ist exitcode von piControlReset """ @@ -1210,6 +1237,23 @@ class RevPiPyLoad(): if chk not in jconfigrsc: return -2 + # Prüfen ob Modulkatalog vorhanden ist + if rapcatalog is None: + return -5 + else: + + # piCtory Device in Katalog suchen + for picdev in jconfigrsc["Devices"]: + found = False + picdev = picdev["id"][7:-4] + for rapdev in rapcatalog: + if rapdev.find(picdev) >= 0: + found = True + + # Device im Katalog nicht gefunden + if not found: + return -4 + try: with open(configrsc, "wb") as fh: fh.write(filebytes.data) @@ -1221,6 +1265,23 @@ class RevPiPyLoad(): else: return 0 + def xml_psstart(self): + """Startet den Prozessabbildserver. + @return True, wenn start erfolgreich""" + if self.xml_ps is not None: + return self.xml_ps.start() + else: + return False + + def xml_psstop(self): + """Stoppt den Prozessabbildserver. + @return True, wenn stop erfolgreich""" + if self.xml_ps is not None: + self.xml_ps.stop() + return True + else: + return False + if __name__ == "__main__": root = RevPiPyLoad() diff --git a/setup.py b/setup.py index ae17d66..64cce8f 100644 --- a/setup.py +++ b/setup.py @@ -27,10 +27,12 @@ setup( license="LGPLv3", name="revpipyload", - version="0.3.0", + version="0.4.2", scripts=["data/revpipyload"], + install_requires=["revpimodio"], + data_files=[ ("/etc/default", ["data/etc/default/revpipyload"]), ("/etc/revpipyload", ["data/etc/revpipyload/revpipyload.conf"]), @@ -40,12 +42,12 @@ setup( description="PLC Loader für Python-Projekte auf den RevolutionPi", long_description="" - "Dieses Programm startet beim Systemstart ein angegebenes Python PLC\n" - "Programm. Es überwacht das Programm und startet es im Fehlerfall neu.\n" - "Bei Absturz kann das gesamte /dev/piControl0 auf 0x00 gesetzt werden.\n" - "Außerdem stellt es einen XML-RPC Server bereit, über den die Software\n" - "auf den RevPi geladen werden kann. Das Prozessabbild kann über ein Tool\n" - "zur Laufzeit überwacht werden.", + "Dieses Programm startet beim Systemstart ein angegebenes Python PLC \n" + "Programm. Es überwacht das Programm und startet es im Fehlerfall neu. \n" + "Bei Absturz kann das gesamte /dev/piControl0 auf 0x00 gesetzt werden. \n" + "Außerdem stellt es einen XML-RPC Server bereit, über den die Software \n" + "auf den RevPi geladen werden kann. Das Prozessabbild kann über ein \n" + "Tool zur Laufzeit überwacht werden.", classifiers=[ "License :: OSI Approved :: " diff --git a/stdeb.cfg b/stdeb.cfg index 2d9a383..b6c3754 100644 --- a/stdeb.cfg +++ b/stdeb.cfg @@ -1,4 +1,6 @@ [DEFAULT] -X-Python3-Version: 3.2- +Debian-Version: 1 +Depends3: python3-revpimodio (>= 0.11.0) Package: revpipyload -Suite: stable \ No newline at end of file +Suite: stable +X-Python3-Version: >=3.2