diff --git a/doc/revpimodio2.netio.html b/doc/revpimodio2.netio.html index 48b8224..f92c724 100644 --- a/doc/revpimodio2.netio.html +++ b/doc/revpimodio2.netio.html @@ -18,6 +18,9 @@ Global Attributes Classes + + + @@ -37,6 +40,37 @@ Functions
AclExceptionProbleme mit Berechtigungen.
NetFH Netzwerk File Handler fuer das Prozessabbild.
None


+ +

AclException

+

+Probleme mit Berechtigungen. +

+

+Derived from

+Exception +

+Class Attributes

+ + +
None
+

+Class Methods

+ + +
None
+

+Methods

+ + +
None
+

+Static Methods

+ + +
None
+ +
Up
+

NetFH

@@ -54,7 +88,7 @@ Thread

Class Attributes

- +
__slots__
closed
name
timeout
__slots__
closed
name
reconnecting
timeout

Class Methods

@@ -98,6 +132,9 @@ Methods get_name Verbindugnsnamen zurueckgeben. +get_reconnecting +Interner reconnect aktiv wegen Netzwerkfehlern. + get_timeout Gibt aktuellen Timeout zurueck. @@ -241,6 +278,17 @@ Verbindugnsnamen zurueckgeben.
IP:PORT
+ +

+NetFH.get_reconnecting

+get_reconnecting() +

+Interner reconnect aktiv wegen Netzwerkfehlern. +

+
Returns:
+
+True, wenn reconnect aktiv +

NetFH.get_timeout

@@ -385,7 +433,7 @@ _RevPiModIO

Class Attributes

- +
__slots__
__slots__
reconnecting

Class Methods

@@ -408,6 +456,9 @@ Methods get_jconfigrsc Laedt die piCotry Konfiguration und erstellt ein . +get_reconnecting +Interner reconnect aktiv wegen Netzwerkfehlern. + net_cleardefaultvalues Loescht Defaultwerte vom PLC Slave. @@ -476,6 +527,17 @@ Laedt die piCotry Konfiguration und erstellt ein .
der piCtory Konfiguration
+ +

+RevPiNetIO.get_reconnecting

+get_reconnecting() +

+Interner reconnect aktiv wegen Netzwerkfehlern. +

+
Returns:
+
+True, wenn reconnect aktiv +

RevPiNetIO.net_cleardefaultvalues

diff --git a/eric-revpimodio2.api b/eric-revpimodio2.api index 6572765..6316765 100644 --- a/eric-revpimodio2.api +++ b/eric-revpimodio2.api @@ -192,11 +192,13 @@ revpimodio2.netio.NetFH.closed?7 revpimodio2.netio.NetFH.flush?4() revpimodio2.netio.NetFH.get_closed?4() revpimodio2.netio.NetFH.get_name?4() +revpimodio2.netio.NetFH.get_reconnecting?4() revpimodio2.netio.NetFH.get_timeout?4() revpimodio2.netio.NetFH.ioctl?4(request, arg=b'') revpimodio2.netio.NetFH.name?7 revpimodio2.netio.NetFH.read?4(length) revpimodio2.netio.NetFH.readpictory?4() +revpimodio2.netio.NetFH.reconnecting?7 revpimodio2.netio.NetFH.run?4() revpimodio2.netio.NetFH.seek?4(position) revpimodio2.netio.NetFH.set_dirtybytes?4(position, dirtybytes) @@ -208,8 +210,10 @@ revpimodio2.netio.NetFH?1(address, timeout=500) revpimodio2.netio.RevPiNetIO._create_myfh?5() revpimodio2.netio.RevPiNetIO.disconnect?4() revpimodio2.netio.RevPiNetIO.get_jconfigrsc?4() +revpimodio2.netio.RevPiNetIO.get_reconnecting?4() revpimodio2.netio.RevPiNetIO.net_cleardefaultvalues?4(device=None) revpimodio2.netio.RevPiNetIO.net_setdefaultvalues?4(device=None) +revpimodio2.netio.RevPiNetIO.reconnecting?7 revpimodio2.netio.RevPiNetIO?1(address, autorefresh=False, monitoring=False, syncoutputs=True, simulator=False, debug=False, replace_io_file=None, direct_output=False) revpimodio2.netio.RevPiNetIODriver?1(address, virtdev, autorefresh=False, monitoring=False, syncoutputs=True, debug=False, replace_io_file=None, direct_output=False) revpimodio2.netio.RevPiNetIOSelected?1(address, deviceselection, autorefresh=False, monitoring=False, syncoutputs=True, simulator=False, debug=False, replace_io_file=None, direct_output=False) diff --git a/eric-revpimodio2.bas b/eric-revpimodio2.bas index 3eda7c3..dea047d 100644 --- a/eric-revpimodio2.bas +++ b/eric-revpimodio2.bas @@ -1,3 +1,4 @@ +AclException Exception Base Device Connect Core Core Base diff --git a/revpimodio2/netio.py b/revpimodio2/netio.py index c2da6a4..e0a9363 100644 --- a/revpimodio2/netio.py +++ b/revpimodio2/netio.py @@ -25,6 +25,13 @@ _syspictory = b'\x01PI\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17' _sysflush = b'\x01SD\x00\x00\x00\x00\x1c\x00\x00\x00\x00\x00\x00\x00\x17' +class AclException(Exception): + + """Probleme mit Berechtigungen.""" + + pass + + class NetFH(Thread): """Netzwerk File Handler fuer das Prozessabbild. @@ -54,7 +61,7 @@ class NetFH(Thread): self.__flusherr = False self.__sockact = False self.__sockerr = Event() - self.__sockend = False + self.__sockend = Event() self.__socklock = Lock() self.__timeout = None self.__trigger = False @@ -90,10 +97,10 @@ class NetFH(Thread): if bytecode == b'\x18': # Alles beenden, wenn nicht erlaubt - self.__sockend = True + self.__sockend.set() self.__sockerr.set() self._slavesock.close() - raise RuntimeError( + raise AclException( "write access to the process image is not permitted - use " "monitoring=True or check aclplcslave.conf on RevPi and " "reload revpipyload!" @@ -151,11 +158,12 @@ class NetFH(Thread): @returns Empfangende Bytes """ - if self.__sockend: + if self.__sockend.is_set(): raise ValueError("I/O operation on closed file") with self.__socklock: self._slavesock.sendall(send_bytes) + # FIXME: Schleife bis Daten empfangen sind einbauen recv = self._slavesock.recv(recv_count) self.__trigger = True return recv @@ -163,10 +171,13 @@ class NetFH(Thread): def clear_dirtybytes(self, position=None): """Entfernt die konfigurierten Dirtybytes vom RevPi Slave. @param position Startposition der Dirtybytes""" - if self.__sockend: + if self.__sockend.is_set(): raise ValueError("I/O operation on closed file") - with self.__socklock: + error = False + try: + self.__socklock.acquire() + if position is None: # Alle Dirtybytes löschen self._slavesock.sendall(_sysdeldirty) @@ -184,40 +195,52 @@ class NetFH(Thread): # ACL prüfen und ggf Fehler werfen self.__check_acl(check) - self.__sockerr.set() raise IOError("clear dirtybytes error on network") + except AclException: + raise + except Exception: + error = True + finally: + self.__socklock.release() - # Daten bei Erfolg übernehmen + # Daten immer übernehmen if position is None: self.__dictdirty = {} elif position in self.__dictdirty: del self.__dictdirty[position] + if error: + # Fehler nach übernahme der Daten auslösen um diese zu setzen + self.__sockerr.set() + self.__trigger = True def close(self): """Verbindung trennen.""" - if self.__sockend: + if self.__sockend.is_set(): return - self.__sockend = True + self.__sockend.set() self.__sockerr.set() # Vom Socket sauber trennen if self._slavesock is not None: - with self.__socklock: - try: - if self.__sockend: - self._slavesock.send(_sysexit) - else: - self._slavesock.shutdown(socket.SHUT_RDWR) - except Exception: - pass + try: + self.__socklock.acquire() + self._slavesock.send(_sysexit) + + # NOTE: Wird das benötigt? + self._slavesock.shutdown(socket.SHUT_RDWR) + except Exception: + pass + finally: + self.__socklock.release() + self._slavesock.close() def flush(self): """Schreibpuffer senden.""" - if self.__sockend: + if self.__sockend.is_set(): raise ValueError("flush of closed file") with self.__socklock: @@ -248,13 +271,18 @@ class NetFH(Thread): def get_closed(self): """Pruefen ob Verbindung geschlossen ist. @return True, wenn Verbindung geschlossen ist""" - return self.__sockend + return self.__sockend.is_set() def get_name(self): """Verbindugnsnamen zurueckgeben. @return IP:PORT""" return "{0}:{1}".format(*self._address) + def get_reconnecting(self): + """Interner reconnect aktiv wegen Netzwerkfehlern. + @return True, wenn reconnect aktiv""" + return self.__sockerr.is_set() + def get_timeout(self): """Gibt aktuellen Timeout zurueck. @return in Millisekunden""" @@ -264,7 +292,7 @@ class NetFH(Thread): """IOCTL Befehle ueber das Netzwerk senden. @param request Request as @param arg Argument as """ - if self.__sockend: + if self.__sockend.is_set(): raise ValueError("read of closed file") if not (isinstance(arg, bytes) and len(arg) <= 1024): @@ -295,7 +323,7 @@ class NetFH(Thread): """Daten ueber das Netzwerk lesen. @param length Anzahl der Bytes @return Gelesene """ - if self.__sockend: + if self.__sockend.is_set(): raise ValueError("read of closed file") with self.__socklock: @@ -307,8 +335,8 @@ class NetFH(Thread): ) bytesbuff = bytearray() - while not self.__sockend and len(bytesbuff) < length: - rbytes = self._slavesock.recv(1024) + while not self.__sockend.is_set() and len(bytesbuff) < length: + rbytes = self._slavesock.recv(256) if rbytes == b'': self.__sockerr.set() @@ -323,33 +351,36 @@ class NetFH(Thread): def readpictory(self): """Ruft die piCtory Konfiguration ab. @return piCtory Datei""" - if self.__sockend: + if self.__sockend.is_set(): raise ValueError("read of closed file") with self.__socklock: self._slavesock.send(_syspictory) byte_buff = bytearray() - while not self.__sockend: - data = self._slavesock.recv(1024) + while not self.__sockend.is_set(): + data = self._slavesock.recv(256) byte_buff += data if data.find(b'\x04') >= 0: + self.__trigger = True + # NOTE: Nur suchen oder Ende prüfen? return byte_buff[:-1] self.__sockerr.set() raise IOError("readpictory error on network") - self.__trigger = True - def run(self): """Handler fuer Synchronisierung.""" - while not self.__sockend: + while not self.__sockend.is_set(): # Bei Fehlermeldung neu verbinden if self.__sockerr.is_set(): self._connect() + if self.__sockerr.is_set(): + # Verhindert bei Scheitern 100% CPU last + self.__sockend.wait(self.__waitsync) else: # Kein Fehler aufgetreten, sync durchführen wenn socket frei @@ -380,7 +411,7 @@ class NetFH(Thread): def seek(self, position): """Springt an angegebene Position. @param position An diese Position springen""" - if self.__sockend: + if self.__sockend.is_set(): raise ValueError("seek of closed file") self.__position = int(position) @@ -388,10 +419,13 @@ class NetFH(Thread): """Konfiguriert Dirtybytes fuer Prozessabbild bei Verbindungsfehler. @param positon Startposition zum Schreiben @param dirtybytes die geschrieben werden sollen""" - if self.__sockend: + if self.__sockend.is_set(): raise ValueError("I/O operation on closed file") - with self.__socklock: + error = False + try: + self.__socklock.acquire() + self._slavesock.sendall( b'\x01EY' + position.to_bytes(length=2, byteorder="little") + @@ -406,24 +440,35 @@ class NetFH(Thread): # ACL prüfen und ggf Fehler werfen self.__check_acl(check) - self.__sockerr.set() raise IOError("set dirtybytes error on network") + except AclException: + raise + except Exception: + error = True + finally: + self.__socklock.release() - # Daten erfolgreich übernehmen - self.__dictdirty[position] = dirtybytes + # Daten immer übernehmen + self.__dictdirty[position] = dirtybytes - self.__trigger = True + if error: + # Fehler nach übernahme der Daten auslösen um diese zu setzen + self.__sockerr.set() + + self.__trigger = True def set_timeout(self, value): """Setzt Timeoutwert fuer Verbindung. @param value Timeout in Millisekunden""" - if self.__sockend: + if self.__sockend.is_set(): raise ValueError("I/O operation on closed file") # Timeoutwert verarbeiten (könnte Exception auslösen) self.__set_systimeout(value) - with self.__socklock: + try: + self.__socklock.acquire() + self._slavesock.send( b'\x01CF' + value.to_bytes(length=2, byteorder="little") + @@ -431,15 +476,18 @@ class NetFH(Thread): ) check = self._slavesock.recv(1) if check != b'\x1e': - self.__sockerr.set() raise IOError("set timeout error on network") + except Exception: + self.__sockerr.set() + finally: + self.__socklock.release() - self.__trigger = True + self.__trigger = True def tell(self): """Gibt aktuelle Position zurueck. @return int aktuelle Position""" - if self.__sockend: + if self.__sockend.is_set(): raise ValueError("I/O operation on closed file") return self.__position @@ -447,7 +495,7 @@ class NetFH(Thread): """Daten ueber das Netzwerk schreiben. @param bytebuff Bytes zum schreiben @return Anzahl geschriebener bytes""" - if self.__sockend: + if self.__sockend.is_set(): raise ValueError("write to closed file") if self.__flusherr: @@ -469,6 +517,7 @@ class NetFH(Thread): closed = property(get_closed) name = property(get_name) + reconnecting = property(get_reconnecting) timeout = property(get_timeout, set_timeout) @@ -579,6 +628,11 @@ class RevPiNetIO(_RevPiModIO): mynh.close() return jloads(byte_buff.decode("utf-8")) + def get_reconnecting(self): + """Interner reconnect aktiv wegen Netzwerkfehlern. + @return True, wenn reconnect aktiv""" + return self._myfh.reconnecting + def net_cleardefaultvalues(self, device=None): """Loescht Defaultwerte vom PLC Slave. @param device nur auf einzelnes Device anwenden, sonst auf Alle""" @@ -646,6 +700,8 @@ class RevPiNetIO(_RevPiModIO): dev._offset + dev._slc_out.start, dirtybytes ) + reconnecting = property(get_reconnecting) + class RevPiNetIOSelected(RevPiNetIO):