From 3f85a09382b2f8eaeef8bbc2c0dab67c90696788 Mon Sep 17 00:00:00 2001 From: NaruX Date: Mon, 11 Sep 2017 16:29:44 +0200 Subject: [PATCH 1/7] =?UTF-8?q?Modul=20netio=20eingef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/index-revpimodio2.html | 5 +- doc/revpimodio2.modio.html | 6 +- doc/revpimodio2.netio.html | 543 +++++++++++++++++++++++++++++++++ eric-revpimodio2.api | 31 ++ eric-revpimodio2.bas | 4 + revpimodio2.e4p | 5 +- revpimodio2/__init__.py | 13 +- revpimodio2/io.py | 2 +- revpimodio2/modio.py | 6 +- revpimodio2/netio.py | 598 +++++++++++++++++++++++++++++++++++++ setup.py | 2 +- 11 files changed, 1200 insertions(+), 15 deletions(-) create mode 100644 doc/revpimodio2.netio.html create mode 100644 revpimodio2/netio.py diff --git a/doc/index-revpimodio2.html b/doc/index-revpimodio2.html index 421e8fa..c0634cd 100644 --- a/doc/index-revpimodio2.html +++ b/doc/index-revpimodio2.html @@ -39,7 +39,10 @@ Modules RevPiModIO Modul fuer die Verwaltung der IOs. modio -RevPiModIO Hauptklasse. +RevPiModIO Hauptklasse fuer piControl0 Zugriff. + +netio +RevPiModIO Hauptklasse fuer Netzwerkzugriff. summary Bildet die Summary-Sektion von piCtory ab. diff --git a/doc/revpimodio2.modio.html b/doc/revpimodio2.modio.html index ab63e70..711c979 100644 --- a/doc/revpimodio2.modio.html +++ b/doc/revpimodio2.modio.html @@ -7,7 +7,7 @@

revpimodio2.modio

-RevPiModIO Hauptklasse. +RevPiModIO Hauptklasse fuer piControl0 Zugriff.

Global Attributes

@@ -19,7 +19,7 @@ Classes - + @@ -37,7 +37,7 @@ Functions

RevPiModIO

-Klasse fuer die Verwaltung aller piCtory Informationen. +Klasse fuer die Verwaltung der piCtory Konfiguration.

Diese Klasse uebernimmt die gesamte Konfiguration aus piCtory und bilded die Devices und IOs ab. Sie uebernimmt die exklusive Verwaltung des diff --git a/doc/revpimodio2.netio.html b/doc/revpimodio2.netio.html new file mode 100644 index 0000000..1dc4a30 --- /dev/null +++ b/doc/revpimodio2.netio.html @@ -0,0 +1,543 @@ + + +revpimodio2.netio + + + +

+revpimodio2.netio

+

+RevPiModIO Hauptklasse fuer Netzwerkzugriff. +

+

+Global Attributes

+
RevPiModIOKlasse fuer die Verwaltung aller piCtory Informationen.Klasse fuer die Verwaltung der piCtory Konfiguration.
RevPiModIODriver Klasse um eigene Treiber fuer die virtuellen Devices zu erstellen.
+ +
_sysdeldirty
_sysexit
_sysflush
_syspictory
_syssync
+

+Classes

+ + + + + + + + + + + + + + +
NetFHNetzwerk File Handler fuer das Prozessabbild.
RevPiNetIOKlasse fuer die Verwaltung der piCtory Konfiguration ueber das Netzwerk.
RevPiNetIODriverKlasse um eigene Treiber fuer die virtuellen Devices zu erstellen.
RevPiNetIOSelectedKlasse fuer die Verwaltung einzelner Devices aus piCtory.
+

+Functions

+ + +
None
+

+ +

NetFH

+

+Netzwerk File Handler fuer das Prozessabbild. +

+ Dieses FileObject-like Object verwaltet das Lesen und Schriben des + Prozessabbilds ueber das Netzwerk. Ein entfernter Revolution Pi kann + so gesteuert werden. +

+ +

+

+Derived from

+Thread +

+Class Attributes

+ + +
closed
name
timeout
+

+Class Methods

+ + +
None
+

+Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NetFHInit NetFH-class.
__del__NetworkFileHandler beenden.
_connectStellt die Verbindung zu einem RevPiSlave her.
clear_dirtybytesEntfernt die konfigurierten Dirtybytes vom RevPi Slave.
closeVerbindung trennen.
flushSchreibpuffer senden.
get_closedPruefen ob Verbindung geschlossen ist.
get_nameVerbindugnsnamen zurueckgeben.
get_timeoutGibt aktuellen Timeout zurueck.
readDaten ueber das Netzwerk lesen.
readpictoryRuft die piCtory Konfiguration ab.
runHandler fuer Synchronisierung.
seekSpringt an angegebene Position.
set_dirtybytesKonfiguriert Dirtybytes fuer Prozessabbild bei Verbindungsfehler.
set_timeoutSetzt Timeoutwert fuer Verbindung.
tellGibt aktuelle Position zurueck.
writeDaten ueber das Netzwerk schreiben.
+

+Static Methods

+ + +
None
+ +

+NetFH (Constructor)

+NetFH(address, timeout=500) +

+Init NetFH-class. +

+

+NetFH.__del__

+__del__() +

+NetworkFileHandler beenden. +

+

+NetFH._connect

+_connect() +

+Stellt die Verbindung zu einem RevPiSlave her. +

+

+NetFH.clear_dirtybytes

+clear_dirtybytes(position=None) +

+Entfernt die konfigurierten Dirtybytes vom RevPi Slave. +

+
position
+
+Startposition der Dirtybytes +
+
+

+NetFH.close

+close() +

+Verbindung trennen. +

+

+NetFH.flush

+flush() +

+Schreibpuffer senden. +

+

+NetFH.get_closed

+get_closed() +

+Pruefen ob Verbindung geschlossen ist. +

+
Returns:
+
+True, wenn Verbindung geschlossen ist +
+
+

+NetFH.get_name

+get_name() +

+Verbindugnsnamen zurueckgeben. +

+
Returns:
+
+ IP:PORT +
+
+

+NetFH.get_timeout

+get_timeout() +

+Gibt aktuellen Timeout zurueck. +

+
Returns:
+
+ in Millisekunden +
+
+

+NetFH.read

+read(length) +

+Daten ueber das Netzwerk lesen. +

+
length
+
+Anzahl der Bytes +
+
+
Returns:
+
+Gelesene +
+
+

+NetFH.readpictory

+readpictory() +

+Ruft die piCtory Konfiguration ab. +

+
Returns:
+
+ piCtory Datei +
+
+

+NetFH.run

+run() +

+Handler fuer Synchronisierung. +

+

+NetFH.seek

+seek(position) +

+Springt an angegebene Position. +

+
position
+
+An diese Position springen +
+
+

+NetFH.set_dirtybytes

+set_dirtybytes(position, dirtybytes) +

+Konfiguriert Dirtybytes fuer Prozessabbild bei Verbindungsfehler. +

+
positon
+
+Startposition zum Schreiben +
dirtybytes
+
+ die geschrieben werden sollen +
+
+

+NetFH.set_timeout

+set_timeout(value) +

+Setzt Timeoutwert fuer Verbindung. +

+
value
+
+Timeout in Millisekunden +
+
+

+NetFH.tell

+tell() +

+Gibt aktuelle Position zurueck. +

+
Returns:
+
+int aktuelle Position +
+
+

+NetFH.write

+write(bytebuff) +

+Daten ueber das Netzwerk schreiben. +

+
bytebuff
+
+Bytes zum schreiben +
+
+
Returns:
+
+ Anzahl geschriebener bytes +
+
+
Up
+

+ +

RevPiNetIO

+

+Klasse fuer die Verwaltung der piCtory Konfiguration ueber das Netzwerk. +

+ Diese Klasse uebernimmt die gesamte Konfiguration aus piCtory und bilded + die Devices und IOs ab. Sie uebernimmt die exklusive Verwaltung des + Prozessabbilds und stellt sicher, dass die Daten synchron sind. + Sollten nur einzelne Devices gesteuert werden, verwendet man + RevPiModIOSelected() und uebergibt bei Instantiierung eine Liste mit + Device Positionen oder Device Namen. +

+ +

+

+Derived from

+_RevPiModIO +

+Class Attributes

+ + +
None
+

+Class Methods

+ + +
None
+

+Methods

+ + + + + + + + + + + + + + + + + +
RevPiNetIOInstantiiert die Grundfunktionen.
_create_myfhErstellt NetworkFileObject.
get_jconfigrscLaed die piCotry Konfiguration und erstellt ein .
net_cleardefaultvaluesLoescht Defaultwerte vom PLC Slave.
net_setdefaultvaluesKonfiguriert den PLC Slave mit den piCtory Defaultwerten.
+

+Static Methods

+ + +
None
+ +

+RevPiNetIO (Constructor)

+RevPiNetIO(address, autorefresh=False, monitoring=False, syncoutputs=True, simulator=False) +

+Instantiiert die Grundfunktionen. +

+
address:
+
+IP-Adresse / (IP, Port) +
autorefresh
+
+Wenn True, alle Devices zu autorefresh hinzufuegen +
monitoring
+
+In- und Outputs werden gelesen, niemals geschrieben +
syncoutputs
+
+Aktuell gesetzte Outputs vom Prozessabbild einlesen +
simulator
+
+Laed das Modul als Simulator und vertauscht IOs +
+
+

+RevPiNetIO._create_myfh

+_create_myfh() +

+Erstellt NetworkFileObject. + return FileObject +

+

+RevPiNetIO.get_jconfigrsc

+get_jconfigrsc() +

+Laed die piCotry Konfiguration und erstellt ein . +

+
Returns:
+
+ der piCtory Konfiguration +
+
+

+RevPiNetIO.net_cleardefaultvalues

+net_cleardefaultvalues(device=None) +

+Loescht Defaultwerte vom PLC Slave. +

+
device
+
+nur auf einzelnes Device anwenden, sonst auf Alle +
+
+

+RevPiNetIO.net_setdefaultvalues

+net_setdefaultvalues(device=None) +

+Konfiguriert den PLC Slave mit den piCtory Defaultwerten. +

+
device
+
+nur auf einzelnes Device anwenden, sonst auf Alle +
+
+
Up
+

+ +

RevPiNetIODriver

+

+Klasse um eigene Treiber fuer die virtuellen Devices zu erstellen. +

+ Mit dieser Klasse werden nur angegebene Virtuelle Devices mit RevPiModIO + verwaltet. Bei Instantiierung werden automatisch die Inputs und Outputs + verdreht, um das Schreiben der Inputs zu ermoeglichen. Die Daten koennen + dann ueber logiCAD an den Devices abgerufen werden. +

+ +

+

+Derived from

+RevPiNetIOSelected +

+Class Attributes

+ + +
None
+

+Class Methods

+ + +
None
+

+Methods

+ + + + + +
RevPiNetIODriverInstantiiert die Grundfunktionen.
+

+Static Methods

+ + +
None
+ +

+RevPiNetIODriver (Constructor)

+RevPiNetIODriver(address, virtdev, autorefresh=False, monitoring=False, syncoutputs=True) +

+Instantiiert die Grundfunktionen. +

+ Parameter 'monitoring' und 'simulator' stehen hier nicht zur + Verfuegung, da diese automatisch gesetzt werden. +

+
address:
+
+IP-Adresse / (IP, Port) +
virtdev
+
+Virtuelles Device oder mehrere als +
+
+
See Also:
+
+RevPiModIO.__init__(...) +
+
+
Up
+

+ +

RevPiNetIOSelected

+

+Klasse fuer die Verwaltung einzelner Devices aus piCtory. +

+ Diese Klasse uebernimmt nur angegebene Devices der piCtory Konfiguration + und bilded sie inkl. IOs ab. Sie uebernimmt die exklusive Verwaltung des + Adressbereichs im Prozessabbild an dem sich die angegebenen Devices + befinden und stellt sicher, dass die Daten synchron sind. +

+ +

+

+Derived from

+RevPiNetIO +

+Class Attributes

+ + +
None
+

+Class Methods

+ + +
None
+

+Methods

+ + + + + +
RevPiNetIOSelectedInstantiiert nur fuer angegebene Devices die Grundfunktionen.
+

+Static Methods

+ + +
None
+ +

+RevPiNetIOSelected (Constructor)

+RevPiNetIOSelected(address, deviceselection, autorefresh=False, monitoring=False, syncoutputs=True, simulator=False) +

+Instantiiert nur fuer angegebene Devices die Grundfunktionen. +

+ Der Parameter deviceselection kann eine einzelne + Device Position / einzelner Device Name sein oder eine Liste mit + mehreren Positionen / Namen +

+
address:
+
+IP-Adresse / (IP, Port) +
deviceselection
+
+Positionsnummer oder Devicename +
+
+
See Also:
+
+RevPiNetIO.__init__(...) +
+
+
Up
+
+ \ No newline at end of file diff --git a/eric-revpimodio2.api b/eric-revpimodio2.api index 106916d..bd13522 100644 --- a/eric-revpimodio2.api +++ b/eric-revpimodio2.api @@ -161,4 +161,35 @@ revpimodio2.modio.RevPiModIO.writeprocimg?4(device=None) revpimodio2.modio.RevPiModIO?1(autorefresh=False, monitoring=False, syncoutputs=True, procimg=None, configrsc=None, simulator=False) revpimodio2.modio.RevPiModIODriver?1(virtdev, autorefresh=False, monitoring=False, syncoutputs=True, procimg=None, configrsc=None) revpimodio2.modio.RevPiModIOSelected?1(deviceselection, autorefresh=False, monitoring=False, syncoutputs=True, procimg=None, configrsc=None, simulator=False) +revpimodio2.netio.NetFH._connect?5() +revpimodio2.netio.NetFH.clear_dirtybytes?4(position=None) +revpimodio2.netio.NetFH.close?4() +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_timeout?4() +revpimodio2.netio.NetFH.name?7 +revpimodio2.netio.NetFH.read?4(length) +revpimodio2.netio.NetFH.readpictory?4() +revpimodio2.netio.NetFH.run?4() +revpimodio2.netio.NetFH.seek?4(position) +revpimodio2.netio.NetFH.set_dirtybytes?4(position, dirtybytes) +revpimodio2.netio.NetFH.set_timeout?4(value) +revpimodio2.netio.NetFH.tell?4() +revpimodio2.netio.NetFH.timeout?7 +revpimodio2.netio.NetFH.write?4(bytebuff) +revpimodio2.netio.NetFH?1(address, timeout=500) +revpimodio2.netio.RevPiNetIO._create_myfh?5() +revpimodio2.netio.RevPiNetIO.get_jconfigrsc?4() +revpimodio2.netio.RevPiNetIO.net_cleardefaultvalues?4(device=None) +revpimodio2.netio.RevPiNetIO.net_setdefaultvalues?4(device=None) +revpimodio2.netio.RevPiNetIO?1(address, autorefresh=False, monitoring=False, syncoutputs=True, simulator=False) +revpimodio2.netio.RevPiNetIODriver?1(address, virtdev, autorefresh=False, monitoring=False, syncoutputs=True) +revpimodio2.netio.RevPiNetIOSelected?1(address, deviceselection, autorefresh=False, monitoring=False, syncoutputs=True, simulator=False) +revpimodio2.netio._sysdeldirty?8 +revpimodio2.netio._sysexit?8 +revpimodio2.netio._sysflush?8 +revpimodio2.netio._syspictory?8 +revpimodio2.netio._syssync?8 revpimodio2.summary.Summary?1(summary) diff --git a/eric-revpimodio2.bas b/eric-revpimodio2.bas index 641f05f..0edb3bc 100644 --- a/eric-revpimodio2.bas +++ b/eric-revpimodio2.bas @@ -2,8 +2,12 @@ Core Device EventCallback Thread Gateway Device IntIO IOBase +NetFH Thread ProcimgWriter Thread RevPiModIODriver RevPiModIOSelected RevPiModIOSelected RevPiModIO +RevPiNetIO _RevPiModIO +RevPiNetIODriver RevPiNetIOSelected +RevPiNetIOSelected RevPiNetIO StructIO IOBase Virtual Gateway diff --git a/revpimodio2.e4p b/revpimodio2.e4p index 2ac062a..277149d 100644 --- a/revpimodio2.e4p +++ b/revpimodio2.e4p @@ -1,7 +1,7 @@ - + en_US @@ -9,7 +9,7 @@ Python3 Console Das Modul stellt alle Devices und IOs aus der piCtory Konfiguration in Python3 zur Verfügung. Es ermöglicht den direkten Zugriff auf die Werte über deren vergebenen Namen. Lese- und Schreibaktionen mit dem Prozessabbild werden von dem Modul selbst verwaltet, ohne dass sich der Programmierer um Offsets und Adressen kümmern muss. Für die Gatewaymodule wie ModbusTCP oder Profinet sind eigene 'Inputs' und 'Outputs' über einen bestimmten Adressbereich definierbar. Auf diese IOs kann mit Python3 über den Namen direkt auf die Werte zugegriffen werden. - 2.0.4 + 2.1.0 Sven Sager akira@narux.de @@ -31,6 +31,7 @@ test/web_virtdevdriver.py test/web_benniesrun.py test/web_benniesrunxxl.py + revpimodio2/netio.py diff --git a/revpimodio2/__init__.py b/revpimodio2/__init__.py index 406f651..db8d0d3 100644 --- a/revpimodio2/__init__.py +++ b/revpimodio2/__init__.py @@ -18,13 +18,14 @@ fuehrt das Modul bei Datenaenderung aus. """ import warnings -from .modio import * - -__all__ = ["RevPiModIO", "RevPiModIOSelected", "RevPiModIODriver"] +__all__ = [ + "RevPiModIO", "RevPiModIOSelected", "RevPiModIODriver", + "RevPiNetIO", "RevPiNetIOSelected", "RevPiNetIODriver" +] __author__ = "Sven Sager " __name__ = "revpimodio2" __package__ = "revpimodio2" -__version__ = "2.0.4" +__version__ = "2.1.0" # Global package values OFF = 0 @@ -60,3 +61,7 @@ def consttostr(value): return "BOTH" else: return "" + +# Benötigte Klassen importieren +from .modio import RevPiModIO, RevPiModIOSelected, RevPiModIODriver +from .netio import RevPiNetIO, RevPiNetIOSelected, RevPiNetIODriver diff --git a/revpimodio2/io.py b/revpimodio2/io.py index 64547a1..51c9ac5 100644 --- a/revpimodio2/io.py +++ b/revpimodio2/io.py @@ -8,7 +8,7 @@ """RevPiModIO Modul fuer die Verwaltung der IOs.""" import struct from threading import Event -from .__init__ import RISING, FALLING, BOTH, consttostr +from revpimodio2 import RISING, FALLING, BOTH, consttostr class Type(object): diff --git a/revpimodio2/modio.py b/revpimodio2/modio.py index 6d09679..782a22a 100644 --- a/revpimodio2/modio.py +++ b/revpimodio2/modio.py @@ -5,7 +5,7 @@ # Webpage: https://revpimodio.org/ # (c) Sven Sager, License: LGPLv3 # -"""RevPiModIO Hauptklasse.""" +"""RevPiModIO Hauptklasse fuer piControl0 Zugriff.""" import warnings from json import load as jload from math import ceil @@ -18,12 +18,12 @@ from . import device as devicemodule from . import helper as helpermodule from . import summary as summarymodule from .io import IOList -from .__init__ import RISING, FALLING, BOTH +from revpimodio2 import RISING, FALLING, BOTH class RevPiModIO(object): - """Klasse fuer die Verwaltung aller piCtory Informationen. + """Klasse fuer die Verwaltung der piCtory Konfiguration. Diese Klasse uebernimmt die gesamte Konfiguration aus piCtory und bilded die Devices und IOs ab. Sie uebernimmt die exklusive Verwaltung des diff --git a/revpimodio2/netio.py b/revpimodio2/netio.py new file mode 100644 index 0000000..4b40faa --- /dev/null +++ b/revpimodio2/netio.py @@ -0,0 +1,598 @@ +# -*- coding: utf-8 -*- +# +# python3-RevPiModIO +# +# Webpage: https://revpimodio.org/ +# (c) Sven Sager, License: LGPLv3 +# +"""RevPiModIO Hauptklasse fuer Netzwerkzugriff.""" +import socket +from json import loads as jloads +from threading import Thread, Event, Lock + +from .device import Device +from .modio import RevPiModIO as _RevPiModIO + +# Synchronisierungsbefehl +_syssync = b'\x01\x06\x16\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17' +# Disconnectbefehl +_sysexit = b'\x01EX\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17' +# DirtyBytes von Server entfernen +_sysdeldirty = b'\x01EY\x00\x00\x00\x00\xFF\x00\x00\x00\x00\x00\x00\x00\x17' +# piCtory Konfiguration laden +_syspictory = b'\x01PI\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17' +# Übertragene Bytes schreiben +_sysflush = b'\x01SD\x00\x00\x00\x00\x1c\x00\x00\x00\x00\x00\x00\x00\x17' + + +class NetFH(Thread): + + """Netzwerk File Handler fuer das Prozessabbild. + + Dieses FileObject-like Object verwaltet das Lesen und Schriben des + Prozessabbilds ueber das Netzwerk. Ein entfernter Revolution Pi kann + so gesteuert werden. + + """ + + def __init__(self, address, timeout=500): + """Init NetFH-class.""" + super().__init__() + self.daemon = True + + self.__by_buff = b'' + self.__int_buff = 0 + self.__dictdirty = {} + self.__flusherr = False + self.__sockact = False + self.__sockerr = Event() + self.__sockend = False + self.__socklock = Lock() + self.__timeout = timeout / 1000 + self.__trigger = False + self.__waitsync = self.__timeout / 2 + + socket.setdefaulttimeout(self.__timeout) + + # Verbindung herstellen + self._address = address + self._slavesock = None + self._connect() + + if self._slavesock is None: + raise FileNotFoundError("can not connect to revpi slave") + + # NetFH konfigurieren + self.__position = 0 + self.start() + + def __del__(self): + """NetworkFileHandler beenden.""" + self.close() + + def _connect(self): + """Stellt die Verbindung zu einem RevPiSlave her.""" + # Neuen Socket aufbauen + so = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + so.connect(self._address) + except: + pass + else: + # Alten Socket trennen + with self.__socklock: + if self._slavesock is not None: + self._slavesock.close() + + self._slavesock = so + self.__sockerr.clear() + + # Timeout setzen + self.set_timeout(int(self.__timeout * 1000)) + + # DirtyBytes übertragen + for pos in self.__dictdirty: + self.set_dirtybytes(pos, self.__dictdirty[pos]) + + def clear_dirtybytes(self, position=None): + """Entfernt die konfigurierten Dirtybytes vom RevPi Slave. + @param position Startposition der Dirtybytes""" + if self.__sockend: + raise ValueError("I/O operation on closed file") + + with self.__socklock: + if position is None: + self._slavesock.sendall(_sysdeldirty) + else: + self._slavesock.sendall( + b'\x01EY' + + position.to_bytes(length=2, byteorder="little") + + b'\x00\x00\xFF\x00\x00\x00\x00\x00\x00\x00\x17' + ) + + check = self._slavesock.recv(1) + if check != b'\x1e': + self.__sockerr.set() + raise IOError("clear dirtybytes error on network") + + # Daten bei Erfolg übernehmen + if position is None: + self.__dictdirty = {} + else: + del self.__dictdirty[position] + + self.__trigger = True + + def close(self): + """Verbindung trennen.""" + self.__sockend = True + self.__sockerr.set() + + def flush(self): + """Schreibpuffer senden.""" + if self.__sockend: + raise ValueError("flush of closed file") + + with self.__socklock: + self._slavesock.sendall( + self.__by_buff + _sysflush + ) + + # Rückmeldebyte auswerten + blockok = self._slavesock.recv(1) + if blockok != b'\x1e': + self.__sockerr.set() + self.__flusherr = True + raise IOError("flush error on network") + else: + self.__flusherr = False + + # Puffer leeren + self.__int_buff = 0 + self.__by_buff = b'' + + self.__trigger = True + + def get_closed(self): + """Pruefen ob Verbindung geschlossen ist. + @return True, wenn Verbindung geschlossen ist""" + return self.__sockend + + def get_name(self): + """Verbindugnsnamen zurueckgeben. + @return IP:PORT""" + return "{}:{}".format(*self._address) + + def get_timeout(self): + """Gibt aktuellen Timeout zurueck. + @return in Millisekunden""" + return int(self.__timeout * 1000) + + def read(self, length): + """Daten ueber das Netzwerk lesen. + @param length Anzahl der Bytes + @return Gelesene """ + if self.__sockend: + raise ValueError("read of closed file") + + with self.__socklock: + self._slavesock.send( + b'\x01DA' + + self.__position.to_bytes(length=2, byteorder="little") + + length.to_bytes(length=2, byteorder="little") + + b'\x00\x00\x00\x00\x00\x00\x00\x00\x17' + ) + + bytesbuff = bytearray() + while not self.__sockend and len(bytesbuff) < length: + rbytes = self._slavesock.recv(1024) + + if rbytes == b'': + self.__sockerr.set() + raise IOError("read error on network") + bytesbuff += rbytes + + self.__position += length + self.__trigger = True + + return bytes(bytesbuff) + + def readpictory(self): + """Ruft die piCtory Konfiguration ab. + @return piCtory Datei""" + if self.__sockend: + 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) + + byte_buff += data + if data.find(b'\x04') >= 0: + 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: + + # Auf Fehlermeldung warten + if self.__sockerr.wait(self.__waitsync): + if not self.__sockend: + # Im Fehlerfall neu verbinden + self._connect() + + elif not self.__sockend: + # Kein Fehler aufgetreten, sync durchführen wenn socket frei + if not self.__trigger and \ + self.__socklock.acquire(blocking=False): + try: + self._slavesock.send(_syssync) + data = self._slavesock.recv(2) + except IOError: + print("ioerror in run()") + self.__sockerr.set() + else: + if data != b'\x06\x16': + print("data error in run():", str(data)) + self.__sockerr.set() + self.__socklock.release() + + self.__trigger = False + + # Vom Socket trennen + with self.__socklock: + try: + if self.__sockend: + self._slavesock.send(_sysexit) + else: + self._slavesock.shutdown(socket.SHUT_RDWR) + except: + pass + self._slavesock.close() + + def seek(self, position): + """Springt an angegebene Position. + @param position An diese Position springen""" + if self.__sockend: + raise ValueError("seek of closed file") + self.__position = int(position) + + def set_dirtybytes(self, position, dirtybytes): + """Konfiguriert Dirtybytes fuer Prozessabbild bei Verbindungsfehler. + @param positon Startposition zum Schreiben + @param dirtybytes die geschrieben werden sollen""" + if self.__sockend: + raise ValueError("I/O operation on closed file") + + with self.__socklock: + self._slavesock.sendall( + b'\x01EY' + + position.to_bytes(length=2, byteorder="little") + + len(dirtybytes).to_bytes(length=2, byteorder="little") + + b'\x00\x00\x00\x00\x00\x00\x00\x00\x17' + + dirtybytes + ) + + check = self._slavesock.recv(1) + if check != b'\x1e': + self.__sockerr.set() + raise IOError("set dirtybytes error on network") + + # Daten erfolgreich übernehmen + self.__dictdirty[position] = dirtybytes + + self.__trigger = True + + def set_timeout(self, value): + """Setzt Timeoutwert fuer Verbindung. + @param value Timeout in Millisekunden""" + if self.__sockend: + raise ValueError("I/O operation on closed file") + + if type(value) == int and (0 < value <= 65535): + self.__timeout = value / 1000 + self.__waitsync = self.__timeout / 2 - 0.05 + + # Timeouts in Sockets übernehmen + socket.setdefaulttimeout(self.__timeout) + self._slavesock.settimeout(self.__timeout) + + with self.__socklock: + self._slavesock.send( + b'\x01CF' + + value.to_bytes(length=2, byteorder="little") + + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17' + ) + check = self._slavesock.recv(1) + if check != b'\x1e': + self.__sockerr.set() + raise IOError("set timeout error on network") + + self.__trigger = True + + else: + raise ValueError("value must between 0 and 65535 milliseconds") + + def tell(self): + """Gibt aktuelle Position zurueck. + @return int aktuelle Position""" + if self.__sockend: + raise ValueError("I/O operation on closed file") + return self.__position + + def write(self, bytebuff): + """Daten ueber das Netzwerk schreiben. + @param bytebuff Bytes zum schreiben + @return Anzahl geschriebener bytes""" + if self.__sockend: + raise ValueError("write to closed file") + + if self.__flusherr: + raise IOError("I/O error since last flush") + + with self.__socklock: + self.__int_buff += 1 + + # Datenblöcke mit Group Seperator in Puffer ablegen + self.__by_buff += b'\x01SD' + \ + self.__position.to_bytes(length=2, byteorder="little") + \ + len(bytebuff).to_bytes(length=2, byteorder="little") + \ + b'\x1d\x00\x00\x00\x00\x00\x00\x00\x17' + \ + bytebuff + + # TODO: Bufferlänge und dann flushen? + + return len(bytebuff) + + closed = property(get_closed) + name = property(get_name) + timeout = property(get_timeout, set_timeout) + + +class RevPiNetIO(_RevPiModIO): + + """Klasse fuer die Verwaltung der piCtory Konfiguration ueber das Netzwerk. + + Diese Klasse uebernimmt die gesamte Konfiguration aus piCtory und bilded + die Devices und IOs ab. Sie uebernimmt die exklusive Verwaltung des + Prozessabbilds und stellt sicher, dass die Daten synchron sind. + Sollten nur einzelne Devices gesteuert werden, verwendet man + RevPiModIOSelected() und uebergibt bei Instantiierung eine Liste mit + Device Positionen oder Device Namen. + + """ + + def __init__( + self, address, autorefresh=False, monitoring=False, + syncoutputs=True, simulator=False): + """Instantiiert die Grundfunktionen. + + @param address: IP-Adresse / (IP, Port) + @param autorefresh Wenn True, alle Devices zu autorefresh hinzufuegen + @param monitoring In- und Outputs werden gelesen, niemals geschrieben + @param syncoutputs Aktuell gesetzte Outputs vom Prozessabbild einlesen + @param simulator Laed das Modul als Simulator und vertauscht IOs + + """ + + # Adresse verarbeiten + if type(address) == str: + # TODO: IP-Adresse prüfen + self._address = (address, 55234) + + elif type(address) == tuple: + if len(address) == 2 \ + and type(address[0]) == str \ + and type(address[1]) == int: + + # Werte prüfen + # TODO: IP-Adresse prüfen + if 0 < address[1] <= 65535: + raise ValueError("port number out of range 1 - 65535") + + self._address = address + + else: + raise ValueError( + "address tuple must be (, )" + ) + + # Vererben + super().__init__( + autorefresh, + monitoring, + syncoutputs, + "{}:{}".format(*self._address), + None, + simulator + ) + + # Netzwerkfilehandler anlegen + self._myfh = self._create_myfh() + + # Modul konfigurieren + self._configure(self.get_jconfigrsc()) + + def _create_myfh(self): + """Erstellt NetworkFileObject. + return FileObject""" + self._buffedwrite = True + return NetFH(self._address) + + def get_jconfigrsc(self): + """Laed die piCotry Konfiguration und erstellt ein . + @return der piCtory Konfiguration""" + mynh = NetFH(self._address) + byte_buff = mynh.readpictory() + mynh.close() + return jloads(byte_buff.decode("utf-8")) + + def net_cleardefaultvalues(self, device=None): + """Loescht Defaultwerte vom PLC Slave. + @param device nur auf einzelnes Device anwenden, sonst auf Alle""" + + if self.monitoring: + raise RuntimeError( + "can not send default values, while system is in " + "monitoring mode" + ) + + if device is None: + mylist = self.device + else: + dev = device if issubclass(type(device), Device) \ + else self.device.__getitem__(device) + mylist = [dev] + + for dev in mylist: + self._myfh.clear_dirtybytes(dev._offset + dev._slc_out.start) + + def net_setdefaultvalues(self, device=None): + """Konfiguriert den PLC Slave mit den piCtory Defaultwerten. + @param device nur auf einzelnes Device anwenden, sonst auf Alle""" + + if self.monitoring: + raise RuntimeError( + "can not send default values, while system is in " + "monitoring mode" + ) + + if device is None: + mylist = self.device + else: + dev = device if issubclass(type(device), Device) \ + else self.device.__getitem__(device) + mylist = [dev] + + for dev in mylist: + dirtybytes = bytearray() + for lst_io in self.io[dev._slc_outoff]: + listlen = len(lst_io) + + if listlen == 1: + # Byteorientierte Outputs direkt übernehmen + dirtybytes += lst_io[0]._defaultvalue + + elif listlen > 1: + # Bitorientierte Outputs in ein Byte zusammenfassen + int_byte = 0 + lstbyte = lst_io.copy() + lstbyte.reverse() + + for bitio in lstbyte: + # Von hinten die bits nach vorne schieben + int_byte <<= 1 + if bitio is not None: + int_byte += 1 if bitio._defaultvalue else 0 + + # Errechneten Int-Wert in ein Byte umwandeln + dirtybytes += \ + int_byte.to_bytes(length=1, byteorder="little") + + # Dirtybytes an PLC Slave senden + self._myfh.set_dirtybytes( + dev._offset + dev._slc_out.start, dirtybytes + ) + + +class RevPiNetIOSelected(RevPiNetIO): + + """Klasse fuer die Verwaltung einzelner Devices aus piCtory. + + Diese Klasse uebernimmt nur angegebene Devices der piCtory Konfiguration + und bilded sie inkl. IOs ab. Sie uebernimmt die exklusive Verwaltung des + Adressbereichs im Prozessabbild an dem sich die angegebenen Devices + befinden und stellt sicher, dass die Daten synchron sind. + + """ + + def __init__( + self, address, deviceselection, autorefresh=False, + monitoring=False, syncoutputs=True, simulator=False): + """Instantiiert nur fuer angegebene Devices die Grundfunktionen. + + Der Parameter deviceselection kann eine einzelne + Device Position / einzelner Device Name sein oder eine Liste mit + mehreren Positionen / Namen + + @param address: IP-Adresse / (IP, Port) + @param deviceselection Positionsnummer oder Devicename + @see #RevPiNetIO.__init__ RevPiNetIO.__init__(...) + + """ + super().__init__( + address, autorefresh, monitoring, syncoutputs, simulator + ) + + # Device liste erstellen + if type(deviceselection) == list: + for dev in deviceselection: + self._lst_devselect.append(dev) + else: + self._lst_devselect.append(deviceselection) + + for vdev in self._lst_devselect: + if type(vdev) != int and type(vdev) != str: + raise ValueError( + "need device position as or device name as " + "" + ) + + self._configure(self.get_jconfigrsc()) + + if len(self.device) == 0: + if type(self) == RevPiNetIODriver: + raise RuntimeError( + "could not find any given VIRTUAL devices in config" + ) + else: + raise RuntimeError( + "could not find any given devices in config" + ) + elif len(self.device) != len(self._lst_devselect): + if type(self) == RevPiNetIODriver: + raise RuntimeError( + "could not find all given VIRTUAL devices in config" + ) + else: + raise RuntimeError( + "could not find all given devices in config" + ) + + +class RevPiNetIODriver(RevPiNetIOSelected): + + """Klasse um eigene Treiber fuer die virtuellen Devices zu erstellen. + + Mit dieser Klasse werden nur angegebene Virtuelle Devices mit RevPiModIO + verwaltet. Bei Instantiierung werden automatisch die Inputs und Outputs + verdreht, um das Schreiben der Inputs zu ermoeglichen. Die Daten koennen + dann ueber logiCAD an den Devices abgerufen werden. + + """ + + def __init__( + self, address, virtdev, autorefresh=False, monitoring=False, + syncoutputs=True): + """Instantiiert die Grundfunktionen. + + Parameter 'monitoring' und 'simulator' stehen hier nicht zur + Verfuegung, da diese automatisch gesetzt werden. + + @param address: IP-Adresse / (IP, Port) + @param virtdev Virtuelles Device oder mehrere als + @see #RevPiModIO.__init__ RevPiModIO.__init__(...) + + """ + # Parent mit monitoring=False und simulator=True laden + super().__init__( + address, virtdev, autorefresh, False, syncoutputs, True + ) diff --git a/setup.py b/setup.py index aac7cc2..2d9043a 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( license="LGPLv3", name="revpimodio2", - version="2.0.4", + version="2.1.0", packages=["revpimodio2"], From 09b2859db01100a77557ca372026449be55f9f9b Mon Sep 17 00:00:00 2001 From: NaruX Date: Sun, 17 Sep 2017 15:06:18 +0200 Subject: [PATCH 3/7] =?UTF-8?q?RevPiNetIO=20nur=20.=5Fconfigure=20wenn=20n?= =?UTF-8?q?icht=20vererbt=20Bugfix:=20pr=C3=BCfen=20auf=20self.=5Fmyfh=20i?= =?UTF-8?q?s=20not=20None=20statt=20hasattr=20Socket=20in=20.close()=20bee?= =?UTF-8?q?nden=20-=20nicht=20am=20Ende=20von=20.run()=20da=20als=20daemon?= =?UTF-8?q?=3DTrue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- revpimodio2.e4p | 9 ++++++--- revpimodio2/__init__.py | 2 +- revpimodio2/modio.py | 9 +++++++-- revpimodio2/netio.py | 27 ++++++++++++++------------- setup.py | 2 +- 5 files changed, 29 insertions(+), 20 deletions(-) diff --git a/revpimodio2.e4p b/revpimodio2.e4p index 277149d..91961e3 100644 --- a/revpimodio2.e4p +++ b/revpimodio2.e4p @@ -1,7 +1,7 @@ - + en_US @@ -9,7 +9,7 @@ Python3 Console Das Modul stellt alle Devices und IOs aus der piCtory Konfiguration in Python3 zur Verfügung. Es ermöglicht den direkten Zugriff auf die Werte über deren vergebenen Namen. Lese- und Schreibaktionen mit dem Prozessabbild werden von dem Modul selbst verwaltet, ohne dass sich der Programmierer um Offsets und Adressen kümmern muss. Für die Gatewaymodule wie ModbusTCP oder Profinet sind eigene 'Inputs' und 'Outputs' über einen bestimmten Adressbereich definierbar. Auf diese IOs kann mit Python3 über den Namen direkt auf die Werte zugegriffen werden. - 2.1.0 + 2.1.1 Sven Sager akira@narux.de @@ -32,6 +32,7 @@ test/web_benniesrun.py test/web_benniesrunxxl.py revpimodio2/netio.py + test_trace.py @@ -181,6 +182,7 @@ setup.py + test_trace.py @@ -227,6 +229,7 @@ setup.py + test_trace.py @@ -278,7 +281,7 @@ ExcludeFiles - + */test_trace.py ExcludeMessages diff --git a/revpimodio2/__init__.py b/revpimodio2/__init__.py index db8d0d3..68e90d1 100644 --- a/revpimodio2/__init__.py +++ b/revpimodio2/__init__.py @@ -25,7 +25,7 @@ __all__ = [ __author__ = "Sven Sager " __name__ = "revpimodio2" __package__ = "revpimodio2" -__version__ = "2.1.0" +__version__ = "2.1.1" # Global package values OFF = 0 diff --git a/revpimodio2/modio.py b/revpimodio2/modio.py index 782a22a..e47121e 100644 --- a/revpimodio2/modio.py +++ b/revpimodio2/modio.py @@ -87,7 +87,7 @@ class RevPiModIO(object): def __del__(self): """Zerstoert alle Klassen um aufzuraeumen.""" self.exit(full=True) - if hasattr(self, "_myfh"): + if self._myfh is not None: self._myfh.close() def __evt_exit(self, signum, sigframe): @@ -116,7 +116,8 @@ class RevPiModIO(object): if len(self._lst_devselect) > 0: lst_found = [] - if type(self) == RevPiModIODriver: + if type(self) == RevPiModIODriver \ + or type(self) == RevPiNetIODriver: _searchtype = "VIRTUAL" else: _searchtype = None @@ -907,3 +908,7 @@ class RevPiModIODriver(RevPiModIOSelected): super().__init__( virtdev, autorefresh, False, syncoutputs, procimg, configrsc, True ) + + +# Nachträglicher Import +from .netio import RevPiNetIODriver diff --git a/revpimodio2/netio.py b/revpimodio2/netio.py index 4b40faa..e4a2837 100644 --- a/revpimodio2/netio.py +++ b/revpimodio2/netio.py @@ -128,6 +128,17 @@ class NetFH(Thread): self.__sockend = True self.__sockerr.set() + # Vom Socket sauber trennen + with self.__socklock: + try: + if self.__sockend: + self._slavesock.send(_sysexit) + else: + self._slavesock.shutdown(socket.SHUT_RDWR) + except: + pass + self._slavesock.close() + def flush(self): """Schreibpuffer senden.""" if self.__sockend: @@ -247,17 +258,6 @@ class NetFH(Thread): self.__trigger = False - # Vom Socket trennen - with self.__socklock: - try: - if self.__sockend: - self._slavesock.send(_sysexit) - else: - self._slavesock.shutdown(socket.SHUT_RDWR) - except: - pass - self._slavesock.close() - def seek(self, position): """Springt an angegebene Position. @param position An diese Position springen""" @@ -418,8 +418,9 @@ class RevPiNetIO(_RevPiModIO): # Netzwerkfilehandler anlegen self._myfh = self._create_myfh() - # Modul konfigurieren - self._configure(self.get_jconfigrsc()) + # Nur Konfigurieren, wenn nicht vererbt + if type(self) == RevPiNetIO: + self._configure(self.get_jconfigrsc()) def _create_myfh(self): """Erstellt NetworkFileObject. diff --git a/setup.py b/setup.py index 2d9043a..2c4deef 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( license="LGPLv3", name="revpimodio2", - version="2.1.0", + version="2.1.1", packages=["revpimodio2"], From d34d37f1096949426e74dd9b816a58956a1edd3f Mon Sep 17 00:00:00 2001 From: NaruX Date: Thu, 21 Sep 2017 12:59:25 +0200 Subject: [PATCH 5/7] =?UTF-8?q?NetFH.close()=20mehrfach=20aufrufbar=20ohne?= =?UTF-8?q?=20Fehler=20Socket=20schlie=C3=9Fen,=20wenn=20Fehler=20bei=20Ve?= =?UTF-8?q?rbindung?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- revpimodio2/netio.py | 19 ++++++++++--------- setup.py | 4 ++-- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/revpimodio2/netio.py b/revpimodio2/netio.py index e4a2837..947ee18 100644 --- a/revpimodio2/netio.py +++ b/revpimodio2/netio.py @@ -77,7 +77,7 @@ class NetFH(Thread): try: so.connect(self._address) except: - pass + so.close() else: # Alten Socket trennen with self.__socklock: @@ -129,14 +129,15 @@ class NetFH(Thread): self.__sockerr.set() # Vom Socket sauber trennen - with self.__socklock: - try: - if self.__sockend: - self._slavesock.send(_sysexit) - else: - self._slavesock.shutdown(socket.SHUT_RDWR) - except: - pass + 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: + pass self._slavesock.close() def flush(self): diff --git a/setup.py b/setup.py index 784ee18..9b0f497 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( license="LGPLv3", name="revpimodio2", - version="2.1.1", + version="2.1.1b2", packages=["revpimodio2"], python_requires="~=3.2", @@ -35,7 +35,7 @@ setup( "zugegriffen werden.", classifiers=[ - "Development Status :: 5 - Production/Stable", + "Development Status :: 4 - Beta", "Environment :: Console", "Intended Audience :: Developers", "License :: OSI Approved :: " From 7da1906c96db3f16ad4372a1d09a37814cb138a0 Mon Sep 17 00:00:00 2001 From: NaruX Date: Mon, 6 Nov 2017 12:56:04 +0100 Subject: [PATCH 7/7] RevPiNetIO.net_cleardefaultvalues() funktionierte nicht mit Server Bugfix: Bei Instantiierungsfehler traten weitere Fehler auf --- revpimodio2.e4p | 6 +++++- revpimodio2/netio.py | 34 ++++++++++++++++++++++------------ setup.py | 2 +- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/revpimodio2.e4p b/revpimodio2.e4p index 1bfe61b..2067fda 100644 --- a/revpimodio2.e4p +++ b/revpimodio2.e4p @@ -1,7 +1,7 @@ - + en_US @@ -31,6 +31,8 @@ test/web_virtdevdriver.py test/web_benniesrun.py test/web_benniesrunxxl.py + revpimodio2/netio.py + test_unitnet.py test_unit.py @@ -183,6 +185,7 @@ setup.py test_unit.py + test_unitnet.py @@ -230,6 +233,7 @@ setup.py test_unit.py + test_unitnet.py diff --git a/revpimodio2/netio.py b/revpimodio2/netio.py index 947ee18..3e84e57 100644 --- a/revpimodio2/netio.py +++ b/revpimodio2/netio.py @@ -102,12 +102,14 @@ class NetFH(Thread): with self.__socklock: if position is None: + # Alle Dirtybytes löschen self._slavesock.sendall(_sysdeldirty) else: + # Nur bestimmte Dirtybytes löschen self._slavesock.sendall( b'\x01EY' + position.to_bytes(length=2, byteorder="little") + - b'\x00\x00\xFF\x00\x00\x00\x00\x00\x00\x00\x17' + b'\x00\x00\xFE\x00\x00\x00\x00\x00\x00\x00\x17' ) check = self._slavesock.recv(1) @@ -115,13 +117,13 @@ class NetFH(Thread): self.__sockerr.set() raise IOError("clear dirtybytes error on network") - # Daten bei Erfolg übernehmen - if position is None: - self.__dictdirty = {} - else: - del self.__dictdirty[position] + # Daten bei Erfolg übernehmen + if position is None: + self.__dictdirty = {} + elif position in self.__dictdirty: + del self.__dictdirty[position] - self.__trigger = True + self.__trigger = True def close(self): """Verbindung trennen.""" @@ -224,6 +226,7 @@ class NetFH(Thread): byte_buff += data if data.find(b'\x04') >= 0: + # NOTE: Nur suchen oder Ende prüfen? return byte_buff[:-1] self.__sockerr.set() @@ -320,7 +323,7 @@ class NetFH(Thread): self.__trigger = True else: - raise ValueError("value must between 0 and 65535 milliseconds") + raise ValueError("value must between 1 and 65535 milliseconds") def tell(self): """Gibt aktuelle Position zurueck. @@ -384,6 +387,13 @@ class RevPiNetIO(_RevPiModIO): """ + # Objekte die auch schon bei Fehler benötigt werden + self._exit = Event() + self._imgwriter = None + self._lst_refresh = [] + self._myfh = None + self._waitexit = Event() + # Adresse verarbeiten if type(address) == str: # TODO: IP-Adresse prüfen @@ -396,7 +406,7 @@ class RevPiNetIO(_RevPiModIO): # Werte prüfen # TODO: IP-Adresse prüfen - if 0 < address[1] <= 65535: + if not 0 < address[1] <= 65535: raise ValueError("port number out of range 1 - 65535") self._address = address @@ -448,14 +458,14 @@ class RevPiNetIO(_RevPiModIO): ) if device is None: - mylist = self.device + self._myfh.clear_dirtybytes() else: dev = device if issubclass(type(device), Device) \ else self.device.__getitem__(device) mylist = [dev] - for dev in mylist: - self._myfh.clear_dirtybytes(dev._offset + dev._slc_out.start) + for dev in mylist: + self._myfh.clear_dirtybytes(dev._offset + dev._slc_out.start) def net_setdefaultvalues(self, device=None): """Konfiguriert den PLC Slave mit den piCtory Defaultwerten. diff --git a/setup.py b/setup.py index 9b0f497..8a23776 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( license="LGPLv3", name="revpimodio2", - version="2.1.1b2", + version="2.1.1b3", packages=["revpimodio2"], python_requires="~=3.2",