From 963e173dc261b6d5e18c47dc5bd79eeb77cc46ac Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Sun, 30 Aug 2020 10:41:52 +0200 Subject: [PATCH 1/8] Redesign netio.py to prevent errors and provide clean source code --- revpimodio2/__init__.py | 2 +- revpimodio2/netio.py | 323 ++++++++++++++-------------------------- setup.py | 2 +- 3 files changed, 116 insertions(+), 211 deletions(-) diff --git a/revpimodio2/__init__.py b/revpimodio2/__init__.py index 1135477..31a4c4f 100644 --- a/revpimodio2/__init__.py +++ b/revpimodio2/__init__.py @@ -22,7 +22,7 @@ __author__ = "Sven Sager " __copyright__ = "Copyright (C) 2020 Sven Sager" __license__ = "LGPLv3" __name__ = "revpimodio2" -__version__ = "2.5.3" +__version__ = "2.5.3a" # Global package values OFF = 0 diff --git a/revpimodio2/netio.py b/revpimodio2/netio.py index 8b823e1..4189ec5 100644 --- a/revpimodio2/netio.py +++ b/revpimodio2/netio.py @@ -82,7 +82,6 @@ class NetFH(Thread): self.__config_changed = False self.__int_buff = 0 self.__dictdirty = {} - self.__flusherr = False self.__replace_ios_h = b'' self.__pictory_h = b'' self.__sockerr = Event() @@ -210,7 +209,6 @@ class NetFH(Thread): self._slavesock = so self.__sockerr.clear() - self.__flusherr = False # Timeout setzen self.set_timeout(int(self.__timeout * 1000)) @@ -219,18 +217,25 @@ class NetFH(Thread): for pos in self.__dictdirty: self.set_dirtybytes(pos, self.__dictdirty[pos]) - def _direct_send(self, send_bytes: bytes, recv_len: int) -> bytes: + def _direct_sr(self, send_bytes: bytes, recv_len: int) -> bytes: """ - Sicherer Zugriff auf send / recv Schnittstelle mit Laengenpruefung. + Secure send and receive function for network handler. - :param send_bytes: Bytes, die gesendet werden sollen - :param recv_len: Anzahl der die empfangen werden sollen - :return: Empfangende Bytes + Will raise exception on closed network handler or network errors and + set the sockerr flag. + + :param send_bytes: Bytes to send or empty + :param recv_len: Amount of bytes to receive + :return: Received bytes """ if self.__sockend.is_set(): raise ValueError("I/O operation on closed file") + if self.__sockerr.is_set(): + raise IOError("not allowed while reconnect") + + try: + self.__socklock.acquire() - with self.__socklock: counter = 0 send_len = len(send_bytes) while counter < send_len: @@ -238,7 +243,7 @@ class NetFH(Thread): sent = self._slavesock.send(send_bytes[counter:]) if sent == 0: self.__sockerr.set() - raise IOError("lost network connection") + raise IOError("lost network connection while send") counter += sent self.__buff_recv.clear() @@ -247,12 +252,20 @@ class NetFH(Thread): self.__buff_block, min(recv_len, self.__buff_size) ) if count == 0: - self.__sockerr.set() - raise IOError("lost network connection") + raise IOError("lost network connection while receive") self.__buff_recv += self.__buff_block[:count] recv_len -= count - return bytes(self.__buff_recv) + # Create copy in socklock environment + return_buffer = bytes(self.__buff_recv) + except Exception: + self.__sockerr.set() + raise + + finally: + self.__socklock.release() + + return return_buffer def clear_dirtybytes(self, position=None) -> None: """ @@ -268,42 +281,32 @@ class NetFH(Thread): if self.__sockend.is_set(): raise ValueError("I/O operation on closed file") - error = False - try: - self.__socklock.acquire() - - if position is None: - # Alle Dirtybytes löschen - self._slavesock.sendall(_sysdeldirty) - else: - # Nur bestimmte Dirtybytes löschen - # b CM ii xx c0000000 b = 16 - self._slavesock.sendall(pack( - "=c2sH2xc7xc", - HEADER_START, b'EY', position, b'\xfe', HEADER_STOP - )) - - check = self._slavesock.recv(1) - if check != b'\x1e': - # ACL prüfen und ggf Fehler werfen - self.__check_acl(check) - - raise IOError("clear dirtybytes error on network") - except AclException: - raise - except Exception: - error = True - finally: - self.__socklock.release() - # Daten immer übernehmen if position is None: - self.__dictdirty = {} + self.__dictdirty.clear() elif position in self.__dictdirty: del self.__dictdirty[position] - if error: - # Fehler nach übernahme der Daten auslösen um diese zu setzen + try: + if position is None: + # Alle Dirtybytes löschen + buff = self._direct_sr(_sysdeldirty, 1) + else: + # Nur bestimmte Dirtybytes löschen + # b CM ii xx c0000000 b = 16 + buff = self._direct_sr(pack( + "=c2sH2xc7xc", + HEADER_START, b'EY', position, b'\xfe', HEADER_STOP + ), 1) + if buff != b'\x1e': + # ACL prüfen und ggf Fehler werfen + self.__check_acl(buff) + + raise IOError("clear dirtybytes error on network") + except AclException: + self.__dictdirty.clear() + raise + except Exception: self.__sockerr.set() def close(self) -> None: @@ -334,32 +337,25 @@ class NetFH(Thread): if self.__sockend.is_set(): raise ValueError("flush of closed file") - with self.__socklock: + try: # b CM ii ii 00000000 b = 16 - try: - self._slavesock.sendall(pack( - "=c2sHH8xc", - HEADER_START, b'FD', self.__int_buff, len(self.__by_buff), HEADER_STOP - ) + self.__by_buff) - except Exception: - self.__flusherr = True - raise - finally: - # Puffer immer leeren - self.__int_buff = 0 - self.__by_buff.clear() + buff = self._direct_sr(pack( + "=c2sHH8xc", + HEADER_START, b'FD', self.__int_buff, len(self.__by_buff), HEADER_STOP + ) + self.__by_buff, 1) + except Exception: + raise + finally: + # Puffer immer leeren + self.__int_buff = 0 + self.__by_buff.clear() - # Rückmeldebyte auswerten - blockok = self._slavesock.recv(1) - if blockok != b'\x1e': - # ACL prüfen und ggf Fehler werfen - self.__check_acl(blockok) + if buff != b'\x1e': + # ACL prüfen und ggf Fehler werfen + self.__check_acl(buff) - self.__flusherr = True - self.__sockerr.set() - raise IOError("flush error on network") - else: - self.__flusherr = False + self.__sockerr.set() + raise IOError("flush error on network") def get_closed(self) -> bool: """ @@ -416,21 +412,17 @@ class NetFH(Thread): if not (isinstance(arg, bytes) and len(arg) <= 1024): raise TypeError("arg must be ") - with self.__socklock: - # b CM xx ii iiii0000 b = 16 - self._slavesock.sendall(pack( - "=c2s2xHI4xc", - HEADER_START, b'IC', len(arg), request, HEADER_STOP - ) + arg) + # b CM xx ii iiii0000 b = 16 + buff = self._direct_sr(pack( + "=c2s2xHI4xc", + HEADER_START, b'IC', len(arg), request, HEADER_STOP + ) + arg, 1) + if buff != b'\x1e': + # ACL prüfen und ggf Fehler werfen + self.__check_acl(buff) - # Rückmeldebyte auswerten - blockok = self._slavesock.recv(1) - if blockok != b'\x1e': - # ACL prüfen und ggf Fehler werfen - self.__check_acl(blockok) - - self.__sockerr.set() - raise IOError("ioctl error on network") + self.__sockerr.set() + raise IOError("ioctl error on network") def read(self, length: int) -> bytes: """ @@ -444,28 +436,14 @@ class NetFH(Thread): if self.__sockend.is_set(): raise ValueError("read of closed file") - with self.__socklock: - # b CM ii ii 00000000 b = 16 - self._slavesock.sendall(pack( - "=c2sHH8xc", - HEADER_START, b'DA', self.__position, length, HEADER_STOP - )) + # b CM ii ii 00000000 b = 16 + buff = self._direct_sr(pack( + "=c2sHH8xc", + HEADER_START, b'DA', self.__position, length, HEADER_STOP + ), length) - self.__position += length - - self.__buff_recv.clear() - while length > 0: - count = self._slavesock.recv_into( - self.__buff_block, - min(length, self.__buff_size), - ) - if count == 0: - self.__sockerr.set() - raise IOError("read error on network") - self.__buff_recv += self.__buff_block[:count] - length -= count - - return bytes(self.__buff_recv) + self.__position += length + return buff def readinto(self, buffer: bytearray) -> int: """ @@ -480,29 +458,14 @@ class NetFH(Thread): raise ValueError("read of closed file") length = len(buffer) - with self.__socklock: - # b CM ii ii 00000000 b = 16 - self._slavesock.sendall(pack( - "=c2sHH8xc", - HEADER_START, b'DA', self.__position, length, HEADER_STOP - )) - net_buffer = b'' - while length > 0: - count = self._slavesock.recv_into( - self.__buff_block, - min(length, self.__buff_size), - ) - if count == 0: - self.__sockerr.set() - raise IOError("read error on network") - net_buffer += self.__buff_block[:count] - length -= count - - # We received the correct amount of bytes here - self.__position += length - buffer[:] = net_buffer + # b CM ii ii 00000000 b = 16 + buff = self._direct_sr(pack( + "=c2sHH8xc", + HEADER_START, b'DA', self.__position, length, HEADER_STOP + ), length) + buffer[:] = buff return len(buffer) def readpictory(self) -> bytes: @@ -519,31 +482,9 @@ class NetFH(Thread): "could not read/parse piCtory configuration over network" ) - with self.__socklock: - self._slavesock.sendall(_syspictory) - - self.__buff_recv.clear() - recv_lenght = 4 - while recv_lenght > 0: - count = self._slavesock.recv_into( - self.__buff_block, recv_lenght - ) - if count == 0: - raise IOError("readpictory length error on network") - self.__buff_recv += self.__buff_block[:count] - recv_lenght -= count - - recv_lenght, = unpack("=I", self.__buff_recv) - while recv_lenght > 0: - count = self._slavesock.recv_into( - self.__buff_block, min(recv_lenght, self.__buff_size) - ) - if count == 0: - raise IOError("readpictory file error on network") - self.__buff_recv += self.__buff_block[:count] - recv_lenght -= count - - return bytes(self.__buff_recv[4:]) + buff = self._direct_sr(_syspictory, 4) + recv_length, = unpack("=I", buff) + return self._direct_sr(b'', recv_length) def readreplaceio(self) -> bytes: """ @@ -559,31 +500,9 @@ class NetFH(Thread): "replace_io_file: could not read/parse over network" ) - with self.__socklock: - self._slavesock.sendall(_sysreplaceio) - - self.__buff_recv.clear() - recv_lenght = 4 - while recv_lenght > 0: - count = self._slavesock.recv_into( - self.__buff_block, recv_lenght - ) - if count == 0: - raise IOError("readreplaceio length error on network") - self.__buff_recv += self.__buff_block[:count] - recv_lenght -= count - - recv_lenght, = unpack("=I", self.__buff_recv) - while recv_lenght > 0: - count = self._slavesock.recv_into( - self.__buff_block, min(recv_lenght, self.__buff_size) - ) - if count == 0: - raise IOError("readreplaceio file error on network") - self.__buff_recv += self.__buff_block[:count] - recv_lenght -= count - - return bytes(self.__buff_recv[4:]) + buff = self._direct_sr(_sysreplaceio, 4) + recv_length, = unpack("=I", buff) + return self._direct_sr(b'', recv_length) def run(self) -> None: """Handler fuer Synchronisierung.""" @@ -622,7 +541,7 @@ class NetFH(Thread): self.__buff_block, recv_lenght ) if count == 0: - raise IOError("lost network connection") + raise IOError("lost network connection on sync") self.__buff_recv += self.__buff_block[:count] recv_lenght -= count @@ -666,34 +585,26 @@ class NetFH(Thread): if self.__sockend.is_set(): raise ValueError("I/O operation on closed file") - error = False - try: - self.__socklock.acquire() - - # b CM ii ii 00000000 b = 16 - self._slavesock.sendall(pack( - "=c2sHH8xc", - HEADER_START, b'EY', position, len(dirtybytes), HEADER_STOP - ) + dirtybytes) - - check = self._slavesock.recv(1) - if check != b'\x1e': - # ACL prüfen und ggf Fehler werfen - self.__check_acl(check) - - raise IOError("set dirtybytes error on network") - except AclException: - raise - except Exception: - error = True - finally: - self.__socklock.release() - # Daten immer übernehmen self.__dictdirty[position] = dirtybytes - if error: - # Fehler nach übernahme der Daten auslösen um diese zu setzen + try: + # b CM ii ii 00000000 b = 16 + buff = self._direct_sr(pack( + "=c2sHH8xc", + HEADER_START, b'EY', position, len(dirtybytes), HEADER_STOP + ) + dirtybytes, 1) + + if buff != b'\x1e': + # ACL prüfen und ggf Fehler werfen + self.__check_acl(buff) + + raise IOError("set dirtybytes error on network") + except AclException: + # Not allowed, clear for reconnect + self.__dictdirty.clear() + raise + except Exception: self.__sockerr.set() def set_timeout(self, value: int) -> None: @@ -709,20 +620,15 @@ class NetFH(Thread): self.__set_systimeout(value) try: - self.__socklock.acquire() - # b CM ii xx 00000000 b = 16 - self._slavesock.sendall(pack( + buff = self._direct_sr(pack( "=c2sH10xc", HEADER_START, b'CF', value, HEADER_STOP - )) - check = self._slavesock.recv(1) - if check != b'\x1e': + ), 1) + if buff != b'\x1e': raise IOError("set timeout error on network") except Exception: self.__sockerr.set() - finally: - self.__socklock.release() def tell(self) -> int: """ @@ -747,9 +653,8 @@ class NetFH(Thread): raise ConfigChanged("configuration on revolution pi was changed") if self.__sockend.is_set(): raise ValueError("write to closed file") - - if self.__flusherr: - raise IOError("I/O error since last flush") + if self.__sockerr.is_set(): + raise IOError("not allowed while reconnect") with self.__socklock: self.__int_buff += 1 diff --git a/setup.py b/setup.py index 2fd9026..57af0c4 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ setup( license="LGPLv3", name="revpimodio2", - version="2.5.3", + version="2.5.3a", packages=["revpimodio2"], python_requires="~=3.2", From 6f5415a56754261b68446e65edbd4b45048ff635 Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Tue, 8 Sep 2020 07:26:44 +0200 Subject: [PATCH 2/8] Add piCtory value mappings for RevPi Compact memories in pictory.py. --- revpimodio2/pictory.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/revpimodio2/pictory.py b/revpimodio2/pictory.py index d94cbde..1ec623f 100644 --- a/revpimodio2/pictory.py +++ b/revpimodio2/pictory.py @@ -15,9 +15,8 @@ __license__ = "LGPLv3" # - RevPiGateCANopen_20161102_1_0.rap -# Can be used for : -# RevPi AIO 1.0 (RevPiAIO_20170301_1_0.rap) class AIO: + """Memory value mappings for RevPi AIO 1.0 (RevPiAIO_20170301_1_0.rap).""" OUT_RANGE_OFF = 0 # Off OUT_RANGE_0_5V = 1 # 0 - 5V OUT_RANGE_0_10V = 2 # 0 - 10V @@ -89,9 +88,8 @@ class AIO: RTD_4_WIRE = 1 # 4-wire -# Can be used for : -# RevPi DI 1.0 (RevPiDI_20160818_1_0.rap) class DI: + """Memory value mappings for RevPi DI 1.0 (RevPiDI_20160818_1_0.rap).""" IN_MODE_DIRECT = 0 # Direct IN_MODE_COUNT_RISING = 1 # Counter, rising edge IN_MODE_COUNT_FALLING = 2 # Counter, falling edge @@ -103,9 +101,8 @@ class DI: IN_DEBOUNCE_3MS = 3 # 3ms -# Can be used for : -# RevPi DO 1.0 (RevPiDO_20160818_1_0.rap) class DO: + """Memory value mappings for RevPi DO 1.0 (RevPiDO_20160818_1_0.rap).""" OUT_PWM_FREQ_40HZ = 1 # 40Hz 1% OUT_PWM_FREQ_80HZ = 2 # 80Hz 2% OUT_PWM_FREQ_160HZ = 4 # 160Hz 4% @@ -113,7 +110,19 @@ class DO: OUT_PWM_FREQ_400HZ = 10 # 400Hz 10% -# Can be used for : -# RevPi DIO 1.0 (RevPiDIO_20160818_1_0.rap) class DIO(DI, DO): + """Memory value mappings for RevPi DIO 1.0 (RevPiDIO_20160818_1_0.rap).""" pass + + +class COMPACT: + """Memory value mappings for RevPi Compact 1.0 (RevPiCompact_20171023_1_0.rap).""" + DIN_DEBOUNCE_OFF = 0 # Off + DIN_DEBOUNCE_25US = 1 # 25us + DIN_DEBOUNCE_750US = 2 # 750us + DIN_DEBOUNCE_3MS = 3 # 3ms + + AIN_MODE_OFF = 0 # Off + AIN_MODE_0_10V = 1 # 0 - 10V + AIN_MODE_PT100 = 3 # PT100 + AIN_MODE_PT1000 = 7 # PT1000 From 9460c4f106da76108b4532af42a11ea0dcd49d83 Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Wed, 9 Sep 2020 09:47:55 +0200 Subject: [PATCH 3/8] Cycleloop does not raise Exception, run_plc accept debug flag. User has to use maxioerrors = n, if the program should crash on process image errors. If the ProcimgWriter helper is running but get no new data for 2500 ms, the cycleloop will print just a warning. Cycle function is paused for that time. Most interesting for RevPi NET classes. --- revpimodio2/__init__.py | 6 +++--- revpimodio2/modio.py | 36 +++++++++++++++++++++++++++--------- setup.py | 2 +- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/revpimodio2/__init__.py b/revpimodio2/__init__.py index 31a4c4f..05047c6 100644 --- a/revpimodio2/__init__.py +++ b/revpimodio2/__init__.py @@ -16,13 +16,13 @@ __all__ = [ "RevPiModIO", "RevPiModIODriver", "RevPiModIOSelected", "run_plc", "RevPiNetIO", "RevPiNetIODriver", "RevPiNetIOSelected", "Cycletools", "EventCallback", - "AIO", "DI", "DO", "DIO", + "AIO", "COMPACT", "DI", "DO", "DIO", ] __author__ = "Sven Sager " __copyright__ = "Copyright (C) 2020 Sven Sager" __license__ = "LGPLv3" __name__ = "revpimodio2" -__version__ = "2.5.3a" +__version__ = "2.5.3b" # Global package values OFF = 0 @@ -96,7 +96,7 @@ def consttostr(value) -> str: # Benötigte Klassen importieren -from .pictory import AIO, DI, DO, DIO +from .pictory import AIO, COMPACT, DI, DO, DIO from .helper import Cycletools, EventCallback from .modio import RevPiModIO, RevPiModIODriver, RevPiModIOSelected, run_plc from .netio import RevPiNetIO, RevPiNetIODriver, RevPiNetIOSelected diff --git a/revpimodio2/modio.py b/revpimodio2/modio.py index 492fd8e..e7eb312 100644 --- a/revpimodio2/modio.py +++ b/revpimodio2/modio.py @@ -741,12 +741,18 @@ class RevPiModIO(object): while ec is None and not cycleinfo.last: # Auf neue Daten warten und nur ausführen wenn set() if not self._imgwriter.newdata.wait(2.5): - self.exit(full=False) - if self._imgwriter.is_alive(): - e = RuntimeError("no new io data in cycle loop") - else: + if not self._imgwriter.is_alive(): + self.exit(full=False) e = RuntimeError("autorefresh thread not running") - break + break + + # Just warn, user has to use maxioerrors to kill program + warnings.warn(RuntimeWarning( + "no new io data in cycle loop for 2500 milliseconds" + )) + cycleinfo.last = self._exit.is_set() + continue + self._imgwriter.newdata.clear() # Vor Aufruf der Funktion autorefresh sperren @@ -1336,7 +1342,9 @@ class RevPiModIODriver(RevPiModIOSelected): ) -def run_plc(func, cycletime=50, replace_io_file=None): +def run_plc( + func, cycletime=50, replace_io_file=None, debug=True, + procimg=None, configrsc=None): """ Run Revoluton Pi as real plc with cycle loop and exclusive IO access. @@ -1344,17 +1352,27 @@ def run_plc(func, cycletime=50, replace_io_file=None): handle the program exit signal. You will access the .io, .core, .device via the cycletools in your cycle function. - Shortcut vor this source code: - rpi = RevPiModIO(autorefresh=True, replace_io_file=replace_io_file) + Shortcut for this source code: + rpi = RevPiModIO(autorefresh=True, replace_io_file=..., debug=...) rpi.handlesignalend() return rpi.cycleloop(func, cycletime) :param func: Function to run every set milliseconds :param cycletime: Cycle time in milliseconds :param replace_io_file: Load replace IO configuration from file + :param debug: Print all warnings and detailed error messages + :param procimg: Use different process image + :param configrsc: Use different piCtory configuration + :return: None or the return value of the cycle function """ - rpi = RevPiModIO(autorefresh=True, replace_io_file=replace_io_file) + rpi = RevPiModIO( + autorefresh=True, + replace_io_file=replace_io_file, + debug=debug, + procimg=procimg, + configrsc=configrsc, + ) rpi.handlesignalend() return rpi.cycleloop(func, cycletime) diff --git a/setup.py b/setup.py index 57af0c4..5300286 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ setup( license="LGPLv3", name="revpimodio2", - version="2.5.3a", + version="2.5.3b", packages=["revpimodio2"], python_requires="~=3.2", From beb7a36cc7efc8526e5532f54bbf47a797b79d41 Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Wed, 30 Sep 2020 16:04:29 +0200 Subject: [PATCH 4/8] Set values in process image AND buffer with shared_procimg=True --- revpimodio2/__init__.py | 2 +- revpimodio2/io.py | 39 ++++++++++++++++++++------------------- setup.py | 2 +- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/revpimodio2/__init__.py b/revpimodio2/__init__.py index 05047c6..747e8fc 100644 --- a/revpimodio2/__init__.py +++ b/revpimodio2/__init__.py @@ -22,7 +22,7 @@ __author__ = "Sven Sager " __copyright__ = "Copyright (C) 2020 Sven Sager" __license__ = "LGPLv3" __name__ = "revpimodio2" -__version__ = "2.5.3b" +__version__ = "2.5.3c" # Global package values OFF = 0 diff --git a/revpimodio2/io.py b/revpimodio2/io.py index 7ab7f98..ec63c5c 100644 --- a/revpimodio2/io.py +++ b/revpimodio2/io.py @@ -630,6 +630,7 @@ class IOBase(object): ) except Exception as e: self._parentdevice._modio._gotioerror("ioset", e) + return elif hasattr(self._parentdevice._modio._myfh, "ioctl"): # IOCTL über Netzwerk @@ -643,6 +644,7 @@ class IOBase(object): except Exception as e: self._parentdevice._modio._gotioerror( "net_ioset", e) + return else: # IOCTL in Datei simulieren @@ -655,28 +657,26 @@ class IOBase(object): ) except Exception as e: self._parentdevice._modio._gotioerror("file_ioset", e) + return - else: - # Gepuffertes Schreiben der Outputs + # Für Bitoperationen sperren + self._parentdevice._filelock.acquire() - # Für Bitoperationen sperren - self._parentdevice._filelock.acquire() + # Hier gibt es immer nur ein byte, als int holen + int_byte = self._parentdevice._ba_devdata[self._slc_address.start] - # Hier gibt es immer nur ein byte, als int holen - int_byte = self._parentdevice._ba_devdata[self._slc_address.start] + # Aktuellen Wert vergleichen und ggf. setzen + if not bool(int_byte & self._bitshift) == value: + if value: + int_byte += self._bitshift + else: + int_byte -= self._bitshift - # Aktuellen Wert vergleichen und ggf. setzen - if not bool(int_byte & self._bitshift) == value: - if value: - int_byte += self._bitshift - else: - int_byte -= self._bitshift + # Zurückschreiben wenn verändert + self._parentdevice._ba_devdata[self._slc_address.start] = \ + int_byte - # Zurückschreiben wenn verändert - self._parentdevice._ba_devdata[self._slc_address.start] = \ - int_byte - - self._parentdevice._filelock.release() + self._parentdevice._filelock.release() else: if type(value) != bytes: @@ -705,8 +705,9 @@ class IOBase(object): self._parentdevice._modio._myfh.flush() except IOError as e: self._parentdevice._modio._gotioerror("ioset", e) - else: - self._parentdevice._ba_devdata[self._slc_address] = value + return + + self._parentdevice._ba_devdata[self._slc_address] = value def unreg_event(self, func=None, edge=None) -> None: """ diff --git a/setup.py b/setup.py index 5300286..f9a2c5f 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ setup( license="LGPLv3", name="revpimodio2", - version="2.5.3b", + version="2.5.3c", packages=["revpimodio2"], python_requires="~=3.2", From 79164bd41b08179e11161f1c6da98bf3523f9c0a Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Sat, 10 Oct 2020 14:14:57 +0200 Subject: [PATCH 5/8] Add support for RevPi Flat --- revpimodio2/__init__.py | 2 +- revpimodio2/device.py | 278 +++++++++++++++++++++++++++++++++++++++- revpimodio2/modio.py | 6 + setup.py | 2 +- 4 files changed, 282 insertions(+), 6 deletions(-) diff --git a/revpimodio2/__init__.py b/revpimodio2/__init__.py index 747e8fc..e65841c 100644 --- a/revpimodio2/__init__.py +++ b/revpimodio2/__init__.py @@ -22,7 +22,7 @@ __author__ = "Sven Sager " __copyright__ = "Copyright (C) 2020 Sven Sager" __license__ = "LGPLv3" __name__ = "revpimodio2" -__version__ = "2.5.3c" +__version__ = "2.5.3d" # Global package values OFF = 0 diff --git a/revpimodio2/device.py b/revpimodio2/device.py index 3426de9..11fb408 100644 --- a/revpimodio2/device.py +++ b/revpimodio2/device.py @@ -593,11 +593,13 @@ class Core(Base): exp_a1red = lst_led[1].export exp_a2green = lst_led[2].export exp_a2red = lst_led[3].export + # exp_wd = lst_led[7].export else: exp_a1green = lst_led[0].export exp_a1red = exp_a1green exp_a2green = exp_a1green exp_a2red = exp_a1green + # exp_wd = exp_a1green # Echte IOs erzeugen self.a1green = IOBase(self, [ @@ -907,12 +909,10 @@ class Connect(Core): exp_a3green = lst_led[4].export exp_a3red = lst_led[5].export exp_x2out = lst_led[6].export - exp_wd = lst_led[7].export else: exp_a3green = lst_led[0].export exp_a3red = exp_a3green exp_x2out = exp_a3green - exp_wd = exp_a3green lst_status = lst_myios[self._slc_statusbyte.start] if len(lst_status) == 8: exp_x2in = lst_status[6].export @@ -1013,7 +1013,7 @@ class Connect(Core): class Compact(Base): """ - Klasse fuer den RevPi Connect. + Klasse fuer den RevPi Compact. Stellt Funktionen fuer die LEDs zur Verfuegung. Auf IOs wird ueber das .io Objekt zugegriffen. @@ -1025,7 +1025,7 @@ class Compact(Base): def __setattr__(self, key, value): """Verhindert Ueberschreibung der LEDs.""" if hasattr(self, key) and key in ( - "a1green", "a1red", "a2green", "a2red"): + "a1green", "a1red", "a2green", "a2red", "wd"): raise AttributeError( "direct assignment is not supported - use .value Attribute" ) @@ -1160,6 +1160,276 @@ class Compact(Base): ) * 10 +class Flat(Base): + """ + Klasse fuer den RevPi Flat. + + Stellt Funktionen fuer die LEDs zur Verfuegung. Auf IOs wird ueber das .io + Objekt zugegriffen. + """ + + __slots__ = "_slc_temperature", "_slc_frequency", "_slc_led", \ + "a1green", "a1red", "a2green", "a2red", \ + "a3green", "a3red", "a4green", "a4red", \ + "a5green", "a5red", "wd" + + def __setattr__(self, key, value): + """Verhindert Ueberschreibung der LEDs.""" + if hasattr(self, key) and key in ( + "a1green", "a1red", "a2green", "a2red", + "a3green", "a3red", "a4green", "a4red", + "a5green", "a5red", "wd"): + raise AttributeError( + "direct assignment is not supported - use .value Attribute" + ) + else: + object.__setattr__(self, key, value) + + def _devconfigure(self) -> None: + """Core-Klasse vorbereiten.""" + + # Statische IO Verknüpfungen des Compacts + self._slc_led = slice(6, 8) + self._slc_temperature = slice(4, 5) + self._slc_frequency = slice(5, 6) + + # Exportflags prüfen (Byte oder Bit) + lst_led = self._modio.io[self._slc_devoff][self._slc_led.start] + if len(lst_led) == 8: + exp_a1green = lst_led[0].export + exp_a1red = lst_led[1].export + exp_a2green = lst_led[2].export + exp_a2red = lst_led[3].export + exp_a3green = lst_led[4].export + exp_a3red = lst_led[5].export + exp_a4green = lst_led[6].export + exp_a4red = lst_led[7].export + + # Next byte + lst_led = self._modio.io[self._slc_devoff][self._slc_led.start + 1] + exp_a5green = lst_led[0].export + exp_a5red = lst_led[1].export + else: + exp_a1green = lst_led[0].export + exp_a1red = exp_a1green + exp_a2green = exp_a1green + exp_a2red = exp_a1green + exp_a3green = exp_a1green + exp_a3red = exp_a1green + exp_a4green = exp_a1green + exp_a4red = exp_a1green + exp_a5green = exp_a1green + exp_a5red = exp_a1green + + # Echte IOs erzeugen + self.a1green = IOBase(self, [ + "core.a1green", 0, 1, self._slc_led.start, + exp_a1green, None, "LED_A1_GREEN", "0" + ], OUT, "little", False) + self.a1red = IOBase(self, [ + "core.a1red", 0, 1, self._slc_led.start, + exp_a1red, None, "LED_A1_RED", "1" + ], OUT, "little", False) + self.a2green = IOBase(self, [ + "core.a2green", 0, 1, self._slc_led.start, + exp_a2green, None, "LED_A2_GREEN", "2" + ], OUT, "little", False) + self.a2red = IOBase(self, [ + "core.a2red", 0, 1, self._slc_led.start, + exp_a2red, None, "LED_A2_RED", "3" + ], OUT, "little", False) + self.a3green = IOBase(self, [ + "core.a3green", 0, 1, self._slc_led.start, + exp_a3green, None, "LED_A3_GREEN", "4" + ], OUT, "little", False) + self.a3red = IOBase(self, [ + "core.a3red", 0, 1, self._slc_led.start, + exp_a3red, None, "LED_A3_RED", "5" + ], OUT, "little", False) + self.a4green = IOBase(self, [ + "core.a4green", 0, 1, self._slc_led.start, + exp_a4green, None, "LED_A4_GREEN", "6" + ], OUT, "little", False) + self.a4red = IOBase(self, [ + "core.a4red", 0, 1, self._slc_led.start, + exp_a4red, None, "LED_A4_RED", "7" + ], OUT, "little", False) + self.a5green = IOBase(self, [ + "core.a5green", 0, 1, self._slc_led.start, + exp_a5green, None, "LED_A5_GREEN", "8" + ], OUT, "little", False) + self.a5red = IOBase(self, [ + "core.a5red", 0, 1, self._slc_led.start, + exp_a5red, None, "LED_A5_RED", "9" + ], OUT, "little", False) + + # Software watchdog einrichten + self.wd = IOBase(self, [ + "core.wd", 0, 1, self._slc_led.start, + False, None, "WatchDog", "15" + ], OUT, "little", False) + + def _get_leda1(self) -> int: + """ + Get value of LED A1 from RevPi Flat device. + + :return: 0=off, 1=green, 2=red + """ + return self._ba_devdata[self._slc_led.start] & 0b11 + + def _get_leda2(self) -> int: + """ + Get value of LED A2 from RevPi Flat device. + + :return: 0=off, 1=green, 2=red + """ + return (self._ba_devdata[self._slc_led.start] & 0b1100) >> 2 + + def _get_leda3(self) -> int: + """ + Get value of LED A3 from RevPi Flat device. + + :return: 0=off, 1=green, 2=red + """ + return (self._ba_devdata[self._slc_led.start] & 0b110000) >> 4 + + def _get_leda4(self) -> int: + """ + Get value of LED A4 from RevPi Flat device. + + :return: 0=off, 1=green, 2=red + """ + return (self._ba_devdata[self._slc_led.start] & 0b11000000) >> 6 + + def _get_leda5(self) -> int: + """ + Get value of LED A5 from RevPi Flat device. + + :return: 0=off, 1=green, 2=red + """ + return self._ba_devdata[self._slc_led.start + 1] & 0b11 + + def _set_leda1(self, value: int) -> None: + """ + Set LED A1 on RevPi Flat device. + + :param value: 0=off, 1=green, 2=red + """ + if 0 <= value <= 3: + proc_value = self._ba_devdata[self._slc_led.start] + proc_value_calc = proc_value & 0b11 + if proc_value_calc == value: + return + # Set new value + self._ba_devdata[self._slc_led.start] = \ + proc_value - proc_value_calc + value + else: + raise ValueError("led status must be between 0 and 3") + + def _set_leda2(self, value: int) -> None: + """ + Set LED A2 on RevPi Flat device. + + :param value: 0=off, 1=green, 2=red + """ + if 0 <= value <= 3: + value <<= 2 + proc_value = self._ba_devdata[self._slc_led.start] + proc_value_calc = proc_value & 0b1100 + if proc_value_calc == value: + return + # Set new value + self._ba_devdata[self._slc_led.start] = \ + proc_value - proc_value_calc + value + else: + raise ValueError("led status must be between 0 and 3") + + def _set_leda3(self, value: int) -> None: + """ + Set LED A3 on RevPi Flat device. + + :param value: 0=off, 1=green, 2=red + """ + if 0 <= value <= 3: + value <<= 4 + proc_value = self._ba_devdata[self._slc_led.start] + proc_value_calc = proc_value & 0b110000 + if proc_value_calc == value: + return + # Set new value + self._ba_devdata[self._slc_led.start] = \ + proc_value - proc_value_calc + value + else: + raise ValueError("led status must be between 0 and 3") + + def _set_leda4(self, value: int) -> None: + """ + Set LED A4 on RevPi Flat device. + + :param value: 0=off, 1=green, 2=red + """ + if 0 <= value <= 3: + value <<= 6 + proc_value = self._ba_devdata[self._slc_led.start] + proc_value_calc = proc_value & 0b11000000 + if proc_value_calc == value: + return + # Set new value + self._ba_devdata[self._slc_led.start] = \ + proc_value - proc_value_calc + value + else: + raise ValueError("led status must be between 0 and 3") + + def _set_leda5(self, value: int) -> None: + """ + Set LED A5 on RevPi Flat device. + + :param value: 0=off, 1=green, 2=red + """ + if 0 <= value <= 3: + proc_value = self._ba_devdata[self._slc_led.start + 1] + proc_value_calc = proc_value & 0b11 + if proc_value_calc == value: + return + # Set new value + self._ba_devdata[self._slc_led.start + 1] = \ + proc_value - proc_value_calc + value + else: + raise ValueError("led status must be between 0 and 3") + + def wd_toggle(self): + """Toggle watchdog bit to prevent a timeout.""" + self.wd.value = not self.wd.value + + A1 = property(_get_leda1, _set_leda1) + A2 = property(_get_leda2, _set_leda2) + A3 = property(_get_leda3, _set_leda3) + A4 = property(_get_leda4, _set_leda4) + A5 = property(_get_leda5, _set_leda5) + + @property + def temperature(self) -> int: + """ + Gibt CPU-Temperatur zurueck. + + :return: CPU-Temperatur in Celsius (-273 wenn nich verfuegbar) + """ + return -273 if self._slc_temperature is None else int.from_bytes( + self._ba_devdata[self._slc_temperature], byteorder="little" + ) + + @property + def frequency(self) -> int: + """ + Gibt CPU Taktfrequenz zurueck. + + :return: CPU Taktfrequenz in MHz (-1 wenn nicht verfuegbar) + """ + return -1 if self._slc_frequency is None else int.from_bytes( + self._ba_devdata[self._slc_frequency], byteorder="little" + ) * 10 + + class DioModule(Device): """Stellt ein DIO / DI / DO Modul dar.""" diff --git a/revpimodio2/modio.py b/revpimodio2/modio.py index e7eb312..ed1756d 100644 --- a/revpimodio2/modio.py +++ b/revpimodio2/modio.py @@ -254,6 +254,12 @@ class RevPiModIO(object): self, device, simulator=self._simulator ) self.core = dev_new + elif pt == 135: + # RevPi Flat + dev_new = devicemodule.Flat( + self, device, simulator=self._simulator + ) + self.core = dev_new else: # Base immer als Fallback verwenden dev_new = devicemodule.Base( diff --git a/setup.py b/setup.py index f9a2c5f..77ae37e 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ setup( license="LGPLv3", name="revpimodio2", - version="2.5.3c", + version="2.5.3d", packages=["revpimodio2"], python_requires="~=3.2", From 4ac1283c5b5c4fb8f4492398cfef096f44d79507 Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Mon, 14 Dec 2020 19:41:56 +0100 Subject: [PATCH 6/8] Bugfix: Set A1 - A5 LED did not work with shared_procimg=True --- .idea/misc.xml | 2 +- .idea/revpimodio2.iml | 2 +- revpimodio2/device.py | 98 ++++++++++--------------------------------- setup.py | 2 +- 4 files changed, 25 insertions(+), 79 deletions(-) diff --git a/.idea/misc.xml b/.idea/misc.xml index bc8d735..7694c31 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,7 +3,7 @@ - + diff --git a/.idea/revpimodio2.iml b/.idea/revpimodio2.iml index c8efcbb..d7db3b1 100644 --- a/.idea/revpimodio2.iml +++ b/.idea/revpimodio2.iml @@ -2,7 +2,7 @@ - + diff --git a/revpimodio2/device.py b/revpimodio2/device.py index 11fb408..3608889 100644 --- a/revpimodio2/device.py +++ b/revpimodio2/device.py @@ -675,13 +675,8 @@ class Core(Base): :param value: 0=aus, 1=gruen, 2=rot """ if 0 <= value <= 3: - proc_value = self._ba_devdata[self._slc_led.start] - proc_value_calc = proc_value & 3 - if proc_value_calc == value: - return - # Set new value - self._ba_devdata[self._slc_led.start] = \ - proc_value - proc_value_calc + value + self.a1green(bool(value & 1)) + self.a1red(bool(value & 2)) else: raise ValueError("led status must be between 0 and 3") @@ -692,14 +687,8 @@ class Core(Base): :param value: 0=aus, 1=gruen, 2=rot """ if 0 <= value <= 3: - value <<= 2 - proc_value = self._ba_devdata[self._slc_led.start] - proc_value_calc = proc_value & 12 - if proc_value_calc == value: - return - # Set new value - self._ba_devdata[self._slc_led.start] = \ - proc_value - proc_value_calc + value + self.a2green(bool(value & 1)) + self.a2red(bool(value & 2)) else: raise ValueError("led status must be between 0 and 3") @@ -963,14 +952,8 @@ class Connect(Core): :param: value 0=aus, 1=gruen, 2=rot """ if 0 <= value <= 3: - value <<= 4 - proc_value = self._ba_devdata[self._slc_led.start] - proc_value_calc = proc_value & 48 - if proc_value_calc == value: - return - # Set new value - self._ba_devdata[self._slc_led.start] = \ - proc_value - proc_value_calc + value + self.a3green(bool(value & 1)) + self.a3red(bool(value & 2)) else: raise ValueError("led status must be between 0 and 3") @@ -1102,13 +1085,8 @@ class Compact(Base): :param value: 0=aus, 1=gruen, 2=rot """ if 0 <= value <= 3: - proc_value = self._ba_devdata[self._slc_led.start] - proc_value_calc = proc_value & 3 - if proc_value_calc == value: - return - # Set new value - self._ba_devdata[self._slc_led.start] = \ - proc_value - proc_value_calc + value + self.a1green(bool(value & 1)) + self.a1red(bool(value & 2)) else: raise ValueError("led status must be between 0 and 3") @@ -1119,14 +1097,8 @@ class Compact(Base): :param value: 0=aus, 1=gruen, 2=rot """ if 0 <= value <= 3: - value <<= 2 - proc_value = self._ba_devdata[self._slc_led.start] - proc_value_calc = proc_value & 12 - if proc_value_calc == value: - return - # Set new value - self._ba_devdata[self._slc_led.start] = \ - proc_value - proc_value_calc + value + self.a2green(bool(value & 1)) + self.a2red(bool(value & 2)) else: raise ValueError("led status must be between 0 and 3") @@ -1263,6 +1235,8 @@ class Flat(Base): exp_a5red, None, "LED_A5_RED", "9" ], OUT, "little", False) + # todo: Add internal switch and relay, like Connect + # Software watchdog einrichten self.wd = IOBase(self, [ "core.wd", 0, 1, self._slc_led.start, @@ -1316,13 +1290,8 @@ class Flat(Base): :param value: 0=off, 1=green, 2=red """ if 0 <= value <= 3: - proc_value = self._ba_devdata[self._slc_led.start] - proc_value_calc = proc_value & 0b11 - if proc_value_calc == value: - return - # Set new value - self._ba_devdata[self._slc_led.start] = \ - proc_value - proc_value_calc + value + self.a1green(bool(value & 1)) + self.a1red(bool(value & 2)) else: raise ValueError("led status must be between 0 and 3") @@ -1333,14 +1302,8 @@ class Flat(Base): :param value: 0=off, 1=green, 2=red """ if 0 <= value <= 3: - value <<= 2 - proc_value = self._ba_devdata[self._slc_led.start] - proc_value_calc = proc_value & 0b1100 - if proc_value_calc == value: - return - # Set new value - self._ba_devdata[self._slc_led.start] = \ - proc_value - proc_value_calc + value + self.a2green(bool(value & 1)) + self.a2red(bool(value & 2)) else: raise ValueError("led status must be between 0 and 3") @@ -1351,14 +1314,8 @@ class Flat(Base): :param value: 0=off, 1=green, 2=red """ if 0 <= value <= 3: - value <<= 4 - proc_value = self._ba_devdata[self._slc_led.start] - proc_value_calc = proc_value & 0b110000 - if proc_value_calc == value: - return - # Set new value - self._ba_devdata[self._slc_led.start] = \ - proc_value - proc_value_calc + value + self.a3green(bool(value & 1)) + self.a3red(bool(value & 2)) else: raise ValueError("led status must be between 0 and 3") @@ -1369,14 +1326,8 @@ class Flat(Base): :param value: 0=off, 1=green, 2=red """ if 0 <= value <= 3: - value <<= 6 - proc_value = self._ba_devdata[self._slc_led.start] - proc_value_calc = proc_value & 0b11000000 - if proc_value_calc == value: - return - # Set new value - self._ba_devdata[self._slc_led.start] = \ - proc_value - proc_value_calc + value + self.a4green(bool(value & 1)) + self.a4red(bool(value & 2)) else: raise ValueError("led status must be between 0 and 3") @@ -1387,13 +1338,8 @@ class Flat(Base): :param value: 0=off, 1=green, 2=red """ if 0 <= value <= 3: - proc_value = self._ba_devdata[self._slc_led.start + 1] - proc_value_calc = proc_value & 0b11 - if proc_value_calc == value: - return - # Set new value - self._ba_devdata[self._slc_led.start + 1] = \ - proc_value - proc_value_calc + value + self.a5green(bool(value & 1)) + self.a5red(bool(value & 2)) else: raise ValueError("led status must be between 0 and 3") diff --git a/setup.py b/setup.py index 77ae37e..a5e55c9 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ setup( license="LGPLv3", name="revpimodio2", - version="2.5.3d", + version="2.5.3e", packages=["revpimodio2"], python_requires="~=3.2", From 6beb05577e8bcf0c21906caeb7d6a37a2f7e1586 Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Mon, 14 Dec 2020 19:50:48 +0100 Subject: [PATCH 7/8] Add blocking parameter to .cycleloop If you want to use a cycleloop function and your own tasks, you can set blocking=False in .cycleloop(...) call. Same as .mainloop(...) call. --- revpimodio2/helper.py | 5 +++-- revpimodio2/modio.py | 17 +++++++++++++++-- setup.py | 2 +- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/revpimodio2/helper.py b/revpimodio2/helper.py index c0980c4..b256ff3 100644 --- a/revpimodio2/helper.py +++ b/revpimodio2/helper.py @@ -4,6 +4,7 @@ import queue import warnings from math import ceil from threading import Event, Lock, Thread +from time import sleep from timeit import default_timer from revpimodio2 import BOTH, FALLING, RISING @@ -602,8 +603,8 @@ class ProcimgWriter(Thread): self._eventq.put(tup_fire, False) del self.__dict_delay[tup_fire] - # Refresh abwarten - self._work.wait(self._adjwait) + # Sleep and not .wait (.wait uses system clock) + sleep(self._adjwait) # Wartezeit anpassen um echte self._refresh zu erreichen mrk_dt = default_timer() diff --git a/revpimodio2/modio.py b/revpimodio2/modio.py index ed1756d..31f93ba 100644 --- a/revpimodio2/modio.py +++ b/revpimodio2/modio.py @@ -678,7 +678,7 @@ class RevPiModIO(object): self._exit_level |= 2 self.exit(full=True) - def cycleloop(self, func, cycletime=50): + def cycleloop(self, func, cycletime=50, blocking=True): """ Startet den Cycleloop. @@ -705,6 +705,7 @@ class RevPiModIO(object): :param func: Funktion, die ausgefuehrt werden soll :param cycletime: Zykluszeit in Millisekunden - Standardwert 50 ms + :param blocking: Wenn False, blockiert das Programm hier NICHT :return: None or the return value of the cycle function """ # Prüfen ob ein Loop bereits läuft @@ -726,6 +727,16 @@ class RevPiModIO(object): "registered function '{0}' ist not callable".format(func) ) + # Thread erstellen, wenn nicht blockieren soll + if not blocking: + self._th_mainloop = Thread( + target=self.cycleloop, + args=(func,), + kwargs={"cycletime": cycletime, "blocking": True} + ) + self._th_mainloop.start() + return + # Zykluszeit übernehmen old_cycletime = self._imgwriter.refresh if not cycletime == self._imgwriter.refresh: @@ -777,11 +788,13 @@ class RevPiModIO(object): except Exception as ex: if self._imgwriter.lck_refresh.locked(): self._imgwriter.lck_refresh.release() - self.exit(full=False) + if self._th_mainloop is None: + self.exit(full=False) e = ex finally: # Cycleloop beenden self._looprunning = False + self._th_mainloop = None # Alte autorefresh Zeit setzen self._imgwriter.refresh = old_cycletime diff --git a/setup.py b/setup.py index a5e55c9..1b7c037 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ setup( license="LGPLv3", name="revpimodio2", - version="2.5.3e", + version="2.5.3f", packages=["revpimodio2"], python_requires="~=3.2", From dc731afafdf727ca98be382eb61c51094001ee36 Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Wed, 16 Dec 2020 18:46:45 +0100 Subject: [PATCH 8/8] Parameter shared_procimg can be set per device The shared_procimg Parameter is still a global parameter, but can now be set per device as well with .shared_procimg(...) of all devices. --- revpimodio2/device.py | 21 +++++++++++++++++---- revpimodio2/helper.py | 19 +++++++++---------- revpimodio2/io.py | 4 ++-- revpimodio2/modio.py | 14 +++++++------- revpimodio2/netio.py | 7 +++++-- setup.py | 2 +- 6 files changed, 41 insertions(+), 26 deletions(-) diff --git a/revpimodio2/device.py b/revpimodio2/device.py index 3608889..bc4898a 100644 --- a/revpimodio2/device.py +++ b/revpimodio2/device.py @@ -125,10 +125,11 @@ class Device(object): """ __slots__ = "__my_io_list", "_ba_devdata", "_ba_datacp", \ - "_dict_events", "_filelock", "_length", "_modio", "_name", "_offset", \ - "_position", "_producttype", "_selfupdate", "_slc_devoff", \ - "_slc_inp", "_slc_inpoff", "_slc_mem", "_slc_memoff", \ - "_slc_out", "_slc_outoff", "bmk", "catalognr", "comment", "extend", \ + "_dict_events", "_filelock", "_length", "_modio", "_name", \ + "_offset", "_position", "_producttype", "_selfupdate", \ + "_slc_devoff", "_slc_inp", "_slc_inpoff", "_slc_mem", \ + "_slc_memoff", "_slc_out", "_slc_outoff", "_shared_procimg", \ + "bmk", "catalognr", "comment", "extend", \ "guid", "id", "inpvariant", "outvariant", "type" def __init__(self, parentmodio, dict_device, simulator=False): @@ -146,6 +147,7 @@ class Device(object): self._length = 0 self.__my_io_list = [] self._selfupdate = False + self._shared_procimg = parentmodio._shared_procimg # Wertzuweisung aus dict_device self._name = dict_device.get("name") @@ -500,6 +502,17 @@ class Device(object): """ self._modio.setdefaultvalues(self) + def shared_procimg(self, activate: bool) -> None: + """ + Activate sharing of process image just for this device. + + WARNING: All outputs will set immediately in process image on value + change. That is also inside the cycle loop! + + :param activate: Set True to activate process image sharing + """ + self._shared_procimg = True if activate else False + def syncoutputs(self) -> bool: """ Lesen aller Outputs im Prozessabbild fuer dieses Device. diff --git a/revpimodio2/helper.py b/revpimodio2/helper.py index b256ff3..3a49896 100644 --- a/revpimodio2/helper.py +++ b/revpimodio2/helper.py @@ -464,6 +464,7 @@ class ProcimgWriter(Thread): tup_fireth[0].func, tup_fireth[1], tup_fireth[2] ) th.start() + self._eventqth.task_done() except queue.Empty: pass @@ -533,20 +534,18 @@ class ProcimgWriter(Thread): fh.seek(0) fh.readinto(bytesbuff) - if self._modio._monitoring or self._modio._direct_output: - # Inputs und Outputs in Puffer - for dev in self._modio._lst_refresh: - with dev._filelock: + for dev in self._modio._lst_refresh: + with dev._filelock: + if self._modio._monitoring or dev._shared_procimg: + # Inputs und Outputs in Puffer dev._ba_devdata[:] = bytesbuff[dev._slc_devoff] if self.__eventwork \ and len(dev._dict_events) > 0 \ and dev._ba_datacp != dev._ba_devdata: self.__check_change(dev) - else: - # Inputs in Puffer, Outputs in Prozessabbild - for dev in self._modio._lst_refresh: - with dev._filelock: + else: + # Inputs in Puffer, Outputs in Prozessabbild dev._ba_devdata[dev._slc_inp] = \ bytesbuff[dev._slc_inpoff] if self.__eventwork \ @@ -557,8 +556,8 @@ class ProcimgWriter(Thread): fh.seek(dev._slc_outoff.start) fh.write(dev._ba_devdata[dev._slc_out]) - if self._modio._buffedwrite: - fh.flush() + if self._modio._buffedwrite: + fh.flush() except IOError as e: self._modio._gotioerror("autorefresh", e, mrk_warn) diff --git a/revpimodio2/io.py b/revpimodio2/io.py index ec63c5c..4d03407 100644 --- a/revpimodio2/io.py +++ b/revpimodio2/io.py @@ -614,7 +614,7 @@ class IOBase(object): # Versuchen egal welchen Typ in Bool zu konvertieren value = bool(value) - if self._parentdevice._modio._direct_output: + if self._parentdevice._shared_procimg: # Direktes Schreiben der Outputs if self._parentdevice._modio._run_on_pi: @@ -694,7 +694,7 @@ class IOBase(object): ) ) - if self._parentdevice._modio._direct_output: + if self._parentdevice._shared_procimg: with self._parentdevice._modio._myfh_lck: try: self._parentdevice._modio._myfh.seek( diff --git a/revpimodio2/modio.py b/revpimodio2/modio.py index 31f93ba..b11c675 100644 --- a/revpimodio2/modio.py +++ b/revpimodio2/modio.py @@ -32,7 +32,7 @@ class RevPiModIO(object): """ __slots__ = "__cleanupfunc", "_autorefresh", "_buffedwrite", "_exit_level", \ - "_configrsc", "_direct_output", "_exit", "_imgwriter", "_ioerror", \ + "_configrsc", "_shared_procimg", "_exit", "_imgwriter", "_ioerror", \ "_length", "_looprunning", "_lst_devselect", "_lst_refresh", \ "_maxioerrors", "_myfh", "_myfh_lck", "_monitoring", "_procimg", \ "_simulator", "_syncoutputs", "_th_mainloop", "_waitexit", \ @@ -76,10 +76,10 @@ class RevPiModIO(object): self._autorefresh = autorefresh self._configrsc = configrsc - self._direct_output = shared_procimg or direct_output self._monitoring = monitoring self._procimg = "/dev/piControl0" if procimg is None else procimg self._simulator = simulator + self._shared_procimg = shared_procimg or direct_output self._syncoutputs = syncoutputs # TODO: bei simulator und procimg prüfen ob datei existiert / anlegen? @@ -1036,6 +1036,7 @@ class RevPiModIO(object): # Direct callen da Prüfung in io.IOBase.reg_event ist tup_fire[0].func(tup_fire[1], tup_fire[2]) + self._imgwriter._eventq.task_done() # Laufzeitprüfung if runtime != -1 and \ @@ -1107,7 +1108,7 @@ class RevPiModIO(object): # FileHandler sperren dev._filelock.acquire() - if self._monitoring or self._direct_output: + if self._monitoring or dev._shared_procimg: # Alles vom Bus einlesen dev._ba_devdata[:] = bytesbuff[dev._slc_devoff] else: @@ -1194,9 +1195,6 @@ class RevPiModIO(object): :param device: nur auf einzelnes Device anwenden :return: True, wenn Arbeiten an allen Devices erfolgreich waren """ - if self._direct_output: - return True - if self._monitoring: raise RuntimeError( "can not write process image, while system is in monitoring " @@ -1219,7 +1217,9 @@ class RevPiModIO(object): global_ex = None workokay = True for dev in mylist: - if not dev._selfupdate: + if dev._shared_procimg: + continue + elif not dev._selfupdate: dev._filelock.acquire() # Outpus auf Bus schreiben diff --git a/revpimodio2/netio.py b/revpimodio2/netio.py index 4189ec5..8b8c913 100644 --- a/revpimodio2/netio.py +++ b/revpimodio2/netio.py @@ -337,6 +337,9 @@ class NetFH(Thread): if self.__sockend.is_set(): raise ValueError("flush of closed file") + if self.__int_buff == 0: + return + try: # b CM ii ii 00000000 b = 16 buff = self._direct_sr(pack( @@ -659,7 +662,7 @@ class NetFH(Thread): with self.__socklock: self.__int_buff += 1 - # Datenblöcke mit Group Seperator in Puffer ablegen + # Datenblock mit Position und Länge in Puffer ablegen self.__by_buff += self.__position.to_bytes(length=2, byteorder="little") + \ len(bytebuff).to_bytes(length=2, byteorder="little") + \ bytebuff @@ -707,7 +710,7 @@ class RevPiNetIO(_RevPiModIO): :param direct_output: Deprecated, use shared_procimg """ check_ip = compile( - r"^(?P(25[0-5]|(2[0-4]|[01]?\d|)\d))(\.(?P=ipn)){3}$" + r"^(25[0-5]|(2[0-4]|[01]?\d|)\d)(\.(25[0-5]|(2[0-4]|[01]?\d|)\d)){3}$" ) # Adresse verarbeiten diff --git a/setup.py b/setup.py index 1b7c037..899d939 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ setup( license="LGPLv3", name="revpimodio2", - version="2.5.3f", + version="2.5.3g", packages=["revpimodio2"], python_requires="~=3.2",