From cf0f136c07b159eaf88ce8aec469b92b57dfec2e Mon Sep 17 00:00:00 2001 From: NaruX Date: Wed, 13 Sep 2017 10:00:46 +0200 Subject: [PATCH] =?UTF-8?q?Modul=20picontrolserver=20angelegt=20und=20PLCS?= =?UTF-8?q?lave-Funktionen=20verschoben=20Kleine=20Anpassungen=20durch=20?= =?UTF-8?q?=C3=9Cbernehme=20von=20default-Zweig?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/index.html | 3 + doc/picontrolserver.html | 190 ++++++++++++ doc/revpipyload.html | 514 +-------------------------------- eric-revpipyload.api | 62 ++-- revpipyload.e4p | 3 +- revpipyload/picontrolserver.py | 362 +++++++++++++++++++++++ revpipyload/revpipyload.py | 376 +----------------------- 7 files changed, 599 insertions(+), 911 deletions(-) create mode 100644 doc/picontrolserver.html create mode 100644 revpipyload/picontrolserver.py diff --git a/doc/index.html b/doc/index.html index cd2c3d2..51a6d4c 100644 --- a/doc/index.html +++ b/doc/index.html @@ -16,6 +16,9 @@ Modules logsystem Modul fuer die Verwaltung der Logdateien. +picontrolserver +Modul fuer die Verwaltung der PLC-Slave Funktionen. + plcsystem Modul fuer die Verwaltung der PLC Funktionen. diff --git a/doc/picontrolserver.html b/doc/picontrolserver.html new file mode 100644 index 0000000..d72e85c --- /dev/null +++ b/doc/picontrolserver.html @@ -0,0 +1,190 @@ + + +picontrolserver + + + +

+picontrolserver

+

+Modul fuer die Verwaltung der PLC-Slave Funktionen. +

+

+Global Attributes

+ + +
None
+

+Classes

+ + + + + + + + +
RevPiSlaveRevPi PLC-Server.
RevPiSlaveDevKlasse um eine RevPiModIO Verbindung zu verwalten.
+

+Functions

+ + +
None
+

+ +

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
+
+ \ No newline at end of file diff --git a/doc/revpipyload.html b/doc/revpipyload.html index 2bcdc96..23d8a8d 100644 --- a/doc/revpipyload.html +++ b/doc/revpipyload.html @@ -32,29 +32,14 @@ begrenzt werden!

Global Attributes

- +
configrsc
picontrolreset
procimg
pyloadverion
rapcatalog
re_ipacl
pyloadversion
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.

@@ -64,344 +49,11 @@ Functions

_ipmatch Prueft IP gegen ACL List und gibt ACL aus. -_zeroprocimg -Setzt Prozessabbild auf NULL. - refullmatch re.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

@@ -826,162 +478,6 @@ 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) @@ -1003,14 +499,6 @@ int() ACL Wert oder -1 wenn nicht gefunden
Up


- -

_zeroprocimg

-_zeroprocimg() -

-Setzt Prozessabbild auf NULL. -

-
Up
-

refullmatch

refullmatch(regex, string) diff --git a/eric-revpipyload.api b/eric-revpipyload.api index abc1126..b406d34 100644 --- a/eric-revpipyload.api +++ b/eric-revpipyload.api @@ -1,3 +1,28 @@ +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) +picontrolserver.RevPiSlave.newlogfile?4() +picontrolserver.RevPiSlave.run?4() +picontrolserver.RevPiSlave.stop?4() +picontrolserver.RevPiSlave?1(acl, port=55234) +picontrolserver.RevPiSlaveDev.run?4() +picontrolserver.RevPiSlaveDev.stop?4() +picontrolserver.RevPiSlaveDev?1(devcon, acl) +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 +30,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,25 +40,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.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) @@ -61,19 +71,7 @@ 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.pyloadversion?7 revpipyload.re_ipacl?7 revpipyload.refullmatch?4(regex, string) diff --git a/revpipyload.e4p b/revpipyload.e4p index b25c5a4..d890648 100644 --- a/revpipyload.e4p +++ b/revpipyload.e4p @@ -1,7 +1,7 @@ - + en_US @@ -20,6 +20,7 @@ revpipyload/procimgserver.py revpipyload/logsystem.py revpipyload/plcsystem.py + revpipyload/picontrolserver.py diff --git a/revpipyload/picontrolserver.py b/revpipyload/picontrolserver.py new file mode 100644 index 0000000..13965dd --- /dev/null +++ b/revpipyload/picontrolserver.py @@ -0,0 +1,362 @@ +# -*- coding: utf-8 -*- +# +# RevPiPyLoad +# +# Webpage: https://revpimodio.org/revpipyplc/ +# (c) Sven Sager, License: LGPLv3 +# +"""Modul fuer die Verwaltung der PLC-Slave Funktionen.""" +import proginit +import socket +from threading import Event, Thread +from timeit import default_timer +from revpipyload import _ipmatch + + +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(proginit.pargs.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(proginit.pargs.configrsc) + ) + fh_pic = open(proginit.pargs.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()") diff --git a/revpipyload/revpipyload.py b/revpipyload/revpipyload.py index 11bc795..5775e7e 100755 --- a/revpipyload/revpipyload.py +++ b/revpipyload/revpipyload.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # # RevPiPyLoad -# Version: see global var pyloadverion +# Version: see global var pyloadversion # # Webpage: https://revpimodio.org/revpipyplc/ # (c) Sven Sager, License: LGPLv3 @@ -32,11 +32,11 @@ begrenzt werden! """ import gzip import logsystem +import picontrolserver import plcsystem import proginit import os import signal -import socket import tarfile import zipfile from concurrent import futures @@ -47,12 +47,10 @@ 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" +pyloadversion = "0.5.0" re_ipacl = "(([\\d\\*]{1,3}\\.){3}[\\d\\*]{1,3},[0-1] ?)*" @@ -83,362 +81,6 @@ def refullmatch(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(): """Hauptklasse, die alle Funktionen zur Verfuegung stellt. @@ -546,7 +188,9 @@ class RevPiPyLoad(): # PLC Thread konfigurieren self.plc = self._plcthread() if self.plcslave: - self.th_plcslave = RevPiSlave(self.plcslaveacl, self.plcslaveport) + self.th_plcslave = picontrolserver.RevPiSlave( + self.plcslaveacl, self.plcslaveport + ) else: self.th_plcslave = None @@ -612,15 +256,17 @@ class RevPiPyLoad(): self.xsrv.register_function( lambda: os.system(proginit.picontrolreset), "resetpicontrol") + self.xsrv.register_function( self.xml_plcslavestop, "plcslavestop") 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( self.xml_setpictoryrsc, "set_pictoryrsc") - self.xsrv.register_function(lambda: pyloadverion, "version") + self.xsrv.register_function(lambda: pyloadversion, "version") self.xsrv.register_function(lambda: self.xmlrpc, "xmlmodus") proginit.logger.debug("created xmlrpc server") @@ -1119,7 +765,7 @@ class RevPiPyLoad(): if self.th_plcslave is not None and self.th_plcslave.is_alive(): return -2 else: - self.th_plcslave = RevPiSlave( + self.th_plcslave = picontrolserver.RevPiSlave( self.plcslaveacl, self.plcslaveport ) self.th_plcslave.start()