diff --git a/data/etc/revpipyload/revpipyload.conf b/data/etc/revpipyload/revpipyload.conf index 58ea476..528af45 100644 --- a/data/etc/revpipyload/revpipyload.conf +++ b/data/etc/revpipyload/revpipyload.conf @@ -1,14 +1,17 @@ [DEFAULT] -autoreload=1 -autostart=1 -plcworkdir=/var/lib/revpipyload -plcprogram=program.py +autoreload = 1 +autostart = 1 +plcworkdir = /var/lib/revpipyload +plcprogram = program.py plcarguments= -plcuid=1000 -plcgid=1000 -plcslave=0 -pythonversion=3 -xmlrpc=0 -xmlrpcport=55123 -zeroonerror=0 -zeroonexit=0 +plcuid = 1000 +plcgid = 1000 +plcslave = 0 +plcslaveacl = +plcslaveport = 55234 +pythonversion = 3 +xmlrpc = 0 +xmlrpcacl = +xmlrpcport = 55123 +zeroonerror = 0 +zeroonexit = 0 diff --git a/doc/revpipyload.html b/doc/revpipyload.html index a531907..2bcdc96 100644 --- a/doc/revpipyload.html +++ b/doc/revpipyload.html @@ -32,21 +32,375 @@ begrenzt werden!

Global Attributes

- +
pyloadverion
configrsc
picontrolreset
procimg
pyloadverion
rapcatalog
re_ipacl

Classes

+ + + + + + + + + + + + + + +
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.
RevPiSlaveRevPi PLC-Server.
RevPiSlaveDevKlasse um eine RevPiModIO Verbindung zu verwalten.

Functions

+ + + + + + + + + + +
_ipmatchPrueft IP gegen ACL List und gibt ACL aus.
_zeroprocimgSetzt Prozessabbild auf NULL.
refullmatchre.fullmatch wegen alter python version aus wheezy nachgebaut.
+

+ +

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.
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


RevPiPyLoad

@@ -123,6 +477,12 @@ Methods xml_plcrunning Prueft ob das PLC Programm noch lauft. +xml_plcslavestart +Startet den PLC Slave Server. + +xml_plcslavestop +Stoppt den PLC Slave Server. + xml_plcstart Startet das PLC Programm. @@ -316,6 +676,31 @@ Prueft ob das PLC Programm noch lauft.
True, wenn das PLC Programm noch lauft
+ +

+RevPiPyLoad.xml_plcslavestart

+xml_plcslavestart() +

+Startet den PLC Slave Server. +

+
Returns:
+
+Statuscode: + 0: erfolgreich gestartet + -1: Nicht aktiv in Konfiguration + -2: Laeuft bereits +
+
+

+RevPiPyLoad.xml_plcslavestop

+xml_plcslavestop() +

+Stoppt den PLC Slave Server. +

+
Returns:
+
+True, wenn stop erfolgreich +

RevPiPyLoad.xml_plcstart

@@ -440,5 +825,211 @@ Statuscode:
Up
+

+ +

RevPiSlave

+

+RevPi PLC-Server. +

+ Diese Klasste stellt den RevPi PLC-Server zur verfuegung und akzeptiert + neue Verbindungen. Dieser werden dann als RevPiSlaveDev abgebildet. +

+ Ueber die angegebenen ACLs koennen Zugriffsbeschraenkungen vergeben werden. +

+ +

+

+Derived from

+Thread +

+Class Attributes

+ + +
None
+

+Class Methods

+ + +
None
+

+Methods

+ + + + + + + + + + + + + + +
RevPiSlaveInstantiiert RevPiSlave-Klasse.
newlogfileKonfiguriert die FileHandler auf neue Logdatei.
runStartet Serverkomponente fuer die Annahme neuer Verbindungen.
stopBeendet Slaveausfuehrung.
+

+Static Methods

+ + +
None
+ +

+RevPiSlave (Constructor)

+RevPiSlave(acl, port=55234) +

+Instantiiert RevPiSlave-Klasse. +

+
acl
+
+Stringliste mit Leerstellen getrennt +
port
+
+Listen Port fuer plc Slaveserver +
+
+

+RevPiSlave.newlogfile

+newlogfile() +

+Konfiguriert die FileHandler auf neue Logdatei. +

+

+RevPiSlave.run

+run() +

+Startet Serverkomponente fuer die Annahme neuer Verbindungen. +

+

+RevPiSlave.stop

+stop() +

+Beendet Slaveausfuehrung. +

+
Up
+

+ +

RevPiSlaveDev

+

+Klasse um eine RevPiModIO Verbindung zu verwalten. +

+ Diese Klasste stellt die Funktionen zur Verfuegung um Daten ueber das + Netzwerk mit dem Prozessabbild auszutauschen. +

+ +

+

+Derived from

+Thread +

+Class Attributes

+ + +
None
+

+Class Methods

+ + +
None
+

+Methods

+ + + + + + + + + + + +
RevPiSlaveDevInit RevPiSlaveDev-Class.
runVerarbeitet Anfragen von Remoteteilnehmer.
stopBeendet Verbindungsthread.
+

+Static Methods

+ + +
None
+ +

+RevPiSlaveDev (Constructor)

+RevPiSlaveDev(devcon, acl) +

+Init RevPiSlaveDev-Class. +

+
devcon
+
+Tuple der Verbindung +
deadtime
+
+Timeout der Vararbeitung +
acl
+
+Berechtigungslevel +
+
+

+RevPiSlaveDev.run

+run() +

+Verarbeitet Anfragen von Remoteteilnehmer. +

+

+RevPiSlaveDev.stop

+stop() +

+Beendet Verbindungsthread. +

+
Up
+

+ +

_ipmatch

+_ipmatch(ipaddress, dict_acl) +

+Prueft IP gegen ACL List und gibt ACL aus. +

+
ipaddress
+
+zum pruefen +
dict_acl
+
+ACL Dict gegen die IP zu pruefen ist +
+
+
Returns:
+
+int() ACL Wert oder -1 wenn nicht gefunden +
+
+
Up
+

+ +

_zeroprocimg

+_zeroprocimg() +

+Setzt Prozessabbild auf NULL. +

+
Up
+

+ +

refullmatch

+refullmatch(regex, string) +

+re.fullmatch wegen alter python version aus wheezy nachgebaut. +

+
regex
+
+RegEx Statement +
string
+
+Zeichenfolge gegen die getestet wird +
+
+
Returns:
+
+True, wenn komplett passt sonst False +
+
+
Up

\ No newline at end of file diff --git a/eric-revpipyload.api b/eric-revpipyload.api index d5c2abb..abc1126 100644 --- a/eric-revpipyload.api +++ b/eric-revpipyload.api @@ -1,21 +1,3 @@ -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() @@ -23,8 +5,7 @@ procimgserver.ProcimgServer.setvalue?4(device, io, value) procimgserver.ProcimgServer.start?4() procimgserver.ProcimgServer.stop?4() procimgserver.ProcimgServer.values?4() -procimgserver.ProcimgServer?1(xmlserver, aclmode) -proginit._zeroprocimg?5(self) +procimgserver.ProcimgServer?1(logger, xmlserver, configrsc, procimg, aclmode) proginit.cleanup?4() proginit.configure?4() proginit.forked?7 @@ -33,9 +14,25 @@ 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.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) @@ -52,6 +49,8 @@ revpipyload.RevPiPyLoad.xml_getprocimg?4() revpipyload.RevPiPyLoad.xml_plcdownload?4(mode="tar", pictory=False) revpipyload.RevPiPyLoad.xml_plcexitcode?4() revpipyload.RevPiPyLoad.xml_plcrunning?4() +revpipyload.RevPiPyLoad.xml_plcslavestart?4() +revpipyload.RevPiPyLoad.xml_plcslavestop?4() revpipyload.RevPiPyLoad.xml_plcstart?4() revpipyload.RevPiPyLoad.xml_plcstop?4() revpipyload.RevPiPyLoad.xml_plcupload?4(filedata, filename) @@ -62,4 +61,19 @@ 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.RevPiSlave.newlogfile?4() +revpipyload.RevPiSlave.run?4() +revpipyload.RevPiSlave.stop?4() +revpipyload.RevPiSlave?1(acl, port=55234) +revpipyload.RevPiSlaveDev.run?4() +revpipyload.RevPiSlaveDev.stop?4() +revpipyload.RevPiSlaveDev?1(devcon, acl) +revpipyload._ipmatch?5(ipaddress, dict_acl) +revpipyload._zeroprocimg?5() +revpipyload.configrsc?7 +revpipyload.picontrolreset?7 +revpipyload.procimg?7 revpipyload.pyloadverion?7 +revpipyload.rapcatalog?7 +revpipyload.re_ipacl?7 +revpipyload.refullmatch?4(regex, string) diff --git a/eric-revpipyload.bas b/eric-revpipyload.bas index 9aaa29e..ca3e92c 100644 --- a/eric-revpipyload.bas +++ b/eric-revpipyload.bas @@ -1,2 +1,4 @@ PipeLogwriter Thread RevPiPlc Thread +RevPiSlave Thread +RevPiSlaveDev Thread diff --git a/revpipyload/revpipyload.py b/revpipyload/revpipyload.py index d60b5ad..11bc795 100755 --- a/revpipyload/revpipyload.py +++ b/revpipyload/revpipyload.py @@ -36,6 +36,7 @@ import plcsystem import proginit import os import signal +import socket import tarfile import zipfile from concurrent import futures @@ -46,10 +47,396 @@ from shutil import rmtree from tempfile import mkstemp from threading import Event from time import asctime +from timeit import default_timer from xmlrpc.client import Binary from xmlrpc.server import SimpleXMLRPCServer pyloadverion = "0.4.3" +pyloadverion = "0.5.0" +re_ipacl = "(([\\d\\*]{1,3}\\.){3}[\\d\\*]{1,3},[0-1] ?)*" + + +def _ipmatch(ipaddress, dict_acl): + """Prueft IP gegen ACL List und gibt ACL aus. + + @param ipaddress zum pruefen + @param dict_acl ACL Dict gegen die IP zu pruefen ist + @return int() ACL Wert oder -1 wenn nicht gefunden + + """ + for aclip in sorted(dict_acl, reverse=True): + regex = aclip.replace(".", "\\.").replace("*", "\\d{1,3}") + if refullmatch(regex, ipaddress): + return dict_acl[aclip] + return -1 + + +def refullmatch(regex, string): + """re.fullmatch wegen alter python version aus wheezy nachgebaut. + + @param regex RegEx Statement + @param string Zeichenfolge gegen die getestet wird + @return True, wenn komplett passt sonst False + + """ + m = rematch(regex, string) + return m is not None and m.end() == len(string) + + +def _zeroprocimg(): + """Setzt Prozessabbild auf NULL.""" + if os.path.exists(procimg): + f = open(procimg, "w+b", 0) + f.write(bytes(4096)) + + + +class RevPiSlave(Thread): + + """RevPi PLC-Server. + + Diese Klasste stellt den RevPi PLC-Server zur verfuegung und akzeptiert + neue Verbindungen. Dieser werden dann als RevPiSlaveDev abgebildet. + + Ueber die angegebenen ACLs koennen Zugriffsbeschraenkungen vergeben werden. + + """ + + def __init__(self, acl, port=55234): + """Instantiiert RevPiSlave-Klasse. + @param acl Stringliste mit Leerstellen getrennt + @param port Listen Port fuer plc Slaveserver""" + super().__init__() + self._evt_exit = Event() + self.exitcode = None + self._port = port + self.so = None + self._th_dev = [] + self.zeroonerror = False + self.zeroonexit = False + + # ACLs aufbereiten + self.dict_acl = {} + for host in acl.split(): + aclsplit = host.split(",") + self.dict_acl[aclsplit[0]] = \ + 0 if len(aclsplit) == 1 else int(aclsplit[1]) + + def newlogfile(self): + """Konfiguriert die FileHandler auf neue Logdatei.""" + pass + + def run(self): + """Startet Serverkomponente fuer die Annahme neuer Verbindungen.""" + proginit.logger.debug("enter RevPiSlave.run()") + + # Socket öffnen und konfigurieren + self.so = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + while not self._evt_exit.is_set(): + try: + self.so.bind(("", self._port)) + except: + proginit.logger.warning("can not bind socket - retry") + self._evt_exit.wait(1) + else: + break + self.so.listen(15) + + # Mit Socket arbeiten + while not self._evt_exit.is_set(): + self.exitcode = -1 + + # Verbindung annehmen + proginit.logger.debug("accept new connection") + try: + tup_sock = self.so.accept() + except: + if not self._evt_exit.is_set(): + proginit.logger.exception("accept exception") + continue + + # ACL prüfen + aclstatus = _ipmatch(tup_sock[1][0], self.dict_acl) + if aclstatus == -1: + tup_sock[0].close() + proginit.logger.warning( + "host ip '{}' does not match revpiacl - disconnect" + "".format(tup_sock[1][0]) + ) + else: + # Thread starten + th = RevPiSlaveDev(tup_sock, aclstatus) + th.start() + self._th_dev.append(th) + + # Liste von toten threads befreien + self._th_dev = [ + th_check for th_check in self._th_dev if th_check.is_alive() + ] + + # Alle Threads beenden + for th in self._th_dev: + th.stop() + + # Socket schließen + self.so.close() + self.so = None + + self.exitcode = 0 + + proginit.logger.debug("leave RevPiSlave.run()") + + def stop(self): + """Beendet Slaveausfuehrung.""" + proginit.logger.debug("enter RevPiSlave.stop()") + + self._evt_exit.set() + if self.so is not None: + try: + self.so.shutdown(socket.SHUT_RDWR) + except: + pass + + proginit.logger.debug("leave RevPiSlave.stop()") + + +class RevPiSlaveDev(Thread): + + """Klasse um eine RevPiModIO Verbindung zu verwalten. + + Diese Klasste stellt die Funktionen zur Verfuegung um Daten ueber das + Netzwerk mit dem Prozessabbild auszutauschen. + + """ + + def __init__(self, devcon, acl): + """Init RevPiSlaveDev-Class. + + @param devcon Tuple der Verbindung + @param deadtime Timeout der Vararbeitung + @param acl Berechtigungslevel + + """ + super().__init__() + self._acl = acl + self.daemon = True + self._deadtime = None + self._devcon, self._addr = devcon + self._evt_exit = Event() + self._writeerror = False + + # Sicherheitsbytes + self.ey_dict = {} + + def run(self): + """Verarbeitet Anfragen von Remoteteilnehmer.""" + proginit.logger.debug("enter RevPiSlaveDev.run()") + + proginit.logger.info( + "got new connection from host {} with acl {}".format( + self._addr, self._acl) + ) + + # Prozessabbild öffnen + fh_proc = open(procimg, "r+b", 0) + + dirty = True + while not self._evt_exit.is_set(): + # Laufzeitberechnung starten + ot = default_timer() + + # Meldung erhalten + try: + netcmd = self._devcon.recv(16) + except: + break + + # Wenn Meldung ungültig ist aussteigen + if netcmd[0:1] != b'\x01' or netcmd[-1:] != b'\x17': + if netcmd != b'': + proginit.logger.error( + "net cmd not valid {}".format(netcmd) + ) + break + + cmd = netcmd[1:3] + if cmd == b'DA': + # Processabbild übertragen + # bCMiiii00000000b = 16 + + position = int.from_bytes(netcmd[3:5], byteorder="little") + length = int.from_bytes(netcmd[5:7], byteorder="little") + + fh_proc.seek(position) + try: + self._devcon.sendall(fh_proc.read(length)) + except: + proginit.logger.error("error while send read data") + break + + elif cmd == b'SD' and self._acl == 1: + # Ausgänge empfangen, wenn acl es erlaubt + # bCMiiiic0000000b = 16 + + position = int.from_bytes(netcmd[3:5], byteorder="little") + length = int.from_bytes(netcmd[5:7], byteorder="little") + control = netcmd[7:8] + + if control == b'\x1d' and length > 0: + try: + block = self._devcon.recv(length) + except: + proginit.logger.error("error while recv data to write") + self._writeerror = True + break + fh_proc.seek(position) + + # Länge der Daten prüfen + if len(block) == length: + fh_proc.write(block) + else: + proginit.logger.error("got wrong length to write") + break + + # Record seperator character + if control == b'\x1c': + if self._writeerror: + self._devcon.send(b'\xff') + else: + self._devcon.send(b'\x1e') + self._writeerror = False + + elif cmd == b'\x06\x16': + # Just sync + self._devcon.send(b'\x06\x16') + + elif cmd == b'CF': + # Socket konfigurieren + # bCMii0000000000b = 16 + + timeoutms = int.from_bytes(netcmd[3:5], byteorder="little") + + self._deadtime = timeoutms / 1000 + self._devcon.settimeout(self._deadtime) + + # Record seperator character + self._devcon.send(b'\x1e') + + elif cmd == b'EY': + # Bytes bei Verbindungsabbruch schreiben + # bCMiiiix0000000b = 16 + + position = int.from_bytes( + netcmd[3:5], byteorder="little" + ) + length = int.from_bytes( + netcmd[5:7], byteorder="little" + ) + if netcmd[7:8] == b'\xFF': + # Dirtybytes löschen + if position in self.ey_dict: + del self.ey_dict[position] + + # Record seperator character + self._devcon.send(b'\x1e') + proginit.logger.info( + "cleared dirty bytes on position {}" + "".format(position) + ) + + else: + # Dirtybytes hinzufügen + bytesbuff = bytearray() + try: + while not self._evt_exit.is_set() \ + and len(bytesbuff) < length: + block = self._devcon.recv(1024) + bytesbuff += block + if block == b'': + break + + except: + proginit.logger.error("error while recv dirty bytes") + break + + # Länge der Daten prüfen + if len(bytesbuff) == length: + self.ey_dict[position] = bytesbuff + else: + proginit.logger.error("got wrong length to write") + break + + # Record seperator character + self._devcon.send(b'\x1e') + proginit.logger.info( + "got dirty bytes to write on error on position {}" + "".format(position) + ) + + elif cmd == b'PI': + # piCtory Konfiguration senden + proginit.logger.debug( + "transfair pictory configuration: {}".format(configrsc) + ) + fh_pic = open(configrsc, "rb") + while True: + data = fh_pic.read(1024) + if data: + # FIXME: Fehler fangen + self._devcon.send(data) + else: + fh_pic.close() + break + + # End-of-Transmission character + self._devcon.send(b'\x04') + continue + + elif cmd == b'EX': + # Sauber Verbindung verlassen + dirty = False + self._evt_exit.set() + continue + + else: + # Kein gültiges CMD gefunden, abbruch! + break + + # Verarbeitungszeit prüfen + if self._deadtime is not None: + comtime = default_timer() - ot + if comtime > self._deadtime: + proginit.logger.warning( + "runtime more than {} ms: {}!".format( + int(self._deadtime * 1000), comtime + ) + ) + # TODO: Soll ein Fehler ausgelöst werden? + + # Dirty verlassen + if dirty: + for pos in self.ey_dict: + fh_proc.seek(pos) + fh_proc.write(self.ey_dict[pos]) + + proginit.logger.error("dirty shutdown of connection") + + fh_proc.close() + self._devcon.close() + self._devcon = None + + proginit.logger.info("disconnected from {}".format(self._addr)) + proginit.logger.debug("leave RevPiSlaveDev.run()") + + def stop(self): + """Beendet Verbindungsthread.""" + proginit.logger.debug("enter RevPiSlaveDev.stop()") + + self._evt_exit.set() + if self._devcon is not None: + self._devcon.shutdown(socket.SHUT_RDWR) + + proginit.logger.debug("leave RevPiSlaveDev.stop()") class RevPiPyLoad(): @@ -121,10 +508,33 @@ class RevPiPyLoad(): self.globalconfig["DEFAULT"].get("plcworkdir", ".") self.plcslave = \ int(self.globalconfig["DEFAULT"].get("plcslave", 0)) + + # PLC Slave ACL laden und prüfen + plcslaveacl = \ + self.globalconfig["DEFAULT"].get("plcslaveacl", "") + if len(plcslaveacl) > 0 and not refullmatch(re_ipacl, plcslaveacl): + self.plcslaveacl = "" + proginit.logger.warning("can not load plcslaveacl - wrong format") + else: + self.plcslaveacl = plcslaveacl + + self.plcslaveport = \ + int(self.globalconfig["DEFAULT"].get("plcslaveport", 55234)) self.pythonver = \ int(self.globalconfig["DEFAULT"].get("pythonversion", 3)) self.xmlrpc = \ int(self.globalconfig["DEFAULT"].get("xmlrpc", 0)) + + # XML ACL laden und prüfen + # TODO: xmlrpcacl auswerten + xmlrpcacl = \ + self.globalconfig["DEFAULT"].get("xmlrpcacl", "") + if len(xmlrpcacl) > 0 and not refullmatch(re_ipacl, xmlrpcacl): + self.xmlrpcacl = "" + proginit.logger.warning("can not load xmlrpcacl - wrong format") + else: + self.xmlrpcacl = xmlrpcacl + self.zeroonerror = \ int(self.globalconfig["DEFAULT"].get("zeroonerror", 1)) self.zeroonexit = \ @@ -135,6 +545,10 @@ class RevPiPyLoad(): # PLC Thread konfigurieren self.plc = self._plcthread() + if self.plcslave: + self.th_plcslave = RevPiSlave(self.plcslaveacl, self.plcslaveport) + else: + self.th_plcslave = None # XMLRPC-Server Instantiieren und konfigurieren if self.xmlrpc >= 1: @@ -198,6 +612,9 @@ class RevPiPyLoad(): self.xsrv.register_function( lambda: os.system(proginit.picontrolreset), "resetpicontrol") + self.xml_plcslavestop, "plcslavestop") + self.xsrv.register_function( + lambda: os.system(picontrolreset), "resetpicontrol") self.xsrv.register_function( self.xml_setconfig, "set_config") self.xsrv.register_function( @@ -219,6 +636,7 @@ class RevPiPyLoad(): """Konfiguriert den PLC-Thread fuer die Ausfuehrung. @return PLC-Thread Object or None""" proginit.logger.debug("enter RevPiPyLoad._plcthread()") + th_plc = None # Prüfen ob Programm existiert if not os.path.exists(os.path.join(self.plcworkdir, self.plcprog)): @@ -334,6 +752,10 @@ class RevPiPyLoad(): self.tpe = futures.ThreadPoolExecutor(max_workers=1) self.tpe.submit(self.xsrv.serve_forever) + if self.plcslave: + # Slaveausfuehrung übergeben + self.th_plcslave.start() + if self.autostart: proginit.logger.debug("starting revpiplc-thread") if self.plc is not None: @@ -342,6 +764,8 @@ class RevPiPyLoad(): while not self._exit \ and not self.evt_loadconfig.is_set(): + # TODO: Soll hier der PLC Server Thread geprüft werden? + # piCtory auf Veränderung prüfen if self.pictorymtime != os.path.getmtime(proginit.pargs.configrsc): proginit.logger.warning("piCtory configuration was changed") @@ -366,8 +790,14 @@ class RevPiPyLoad(): proginit.logger.info("stopping revpipyload") self._exit = True + if self.th_plcslave is not None and self.th_plcslave.is_alive(): + proginit.logger.debug("stopping revpi slave thread") + self.th_plcslave.stop() + self.th_plcslave.join() + proginit.logger.debug("revpi slave thread successfully closed") + if self.plc is not None and self.plc.is_alive(): - proginit.logger.debug("stopping revpiplc-thread") + proginit.logger.debug("stopping revpiplc thread") self.plc.stop() self.plc.join() proginit.logger.debug("revpiplc thread successfully closed") @@ -391,8 +821,11 @@ class RevPiPyLoad(): dc["plcprogram"] = self.plcprog dc["plcarguments"] = self.plcarguments dc["plcslave"] = self.plcslave + dc["plcslaveacl"] = self.plcslaveacl + dc["plcslaveport"] = self.plcslaveport dc["pythonversion"] = self.pythonver dc["xmlrpc"] = self.xmlrpc + dc["xmlrpcacl"] = self.xmlrpcacl dc["xmlrpcport"] = \ self.globalconfig["DEFAULT"].get("xmlrpcport", 55123) dc["zeroonerror"] = self.zeroonerror @@ -564,8 +997,11 @@ class RevPiPyLoad(): "plcprogram": ".+", "plcarguments": ".*", "plcslave": "[01]", + "plcslaveacl": re_ipacl, + "plcslaveport": "[0-9]{,5}", "pythonversion": "[23]", "xmlrpc": "[0-3]", + "xmlrpcacl": re_ipacl, "xmlrpcport": "[0-9]{,5}", "zeroonerror": "[01]", "zeroonexit": "[01]" @@ -574,7 +1010,7 @@ class RevPiPyLoad(): # Werte übernehmen for key in keys: if key in dc: - if rematch(keys[key], str(dc[key])) is None: + if not refullmatch(keys[key], str(dc[key])): proginit.logger.error( "got wrong setting '{}' with value '{}'".format( key, dc[key] @@ -670,6 +1106,36 @@ class RevPiPyLoad(): else: return False + def xml_plcslavestart(self): + """Startet den PLC Slave Server. + + @return Statuscode: + 0: erfolgreich gestartet + -1: Nicht aktiv in Konfiguration + -2: Laeuft bereits + + """ + if self.plcslave: + if self.th_plcslave is not None and self.th_plcslave.is_alive(): + return -2 + else: + self.th_plcslave = RevPiSlave( + self.plcslaveacl, self.plcslaveport + ) + self.th_plcslave.start() + return 0 + else: + return -1 + + def xml_plcslavestop(self): + """Stoppt den PLC Slave Server. + @return True, wenn stop erfolgreich""" + if self.th_plcslave is not None: + self.th_plcslave.stop() + return True + else: + return False + if __name__ == "__main__": # Programmeinstellungen konfigurieren diff --git a/setup.py b/setup.py index 1d194ca..2a9f059 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ setup( license="LGPLv3", name="revpipyload", - version="0.4.3", + version="0.5.0", scripts=["data/revpipyload"],