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/__init__.py b/revpimodio2/__init__.py
index 1135477..e65841c 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.3"
+__version__ = "2.5.3d"
# 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/device.py b/revpimodio2/device.py
index 3426de9..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.
@@ -593,11 +606,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, [
@@ -673,13 +688,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")
@@ -690,14 +700,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")
@@ -907,12 +911,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
@@ -963,14 +965,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")
@@ -1013,7 +1009,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 +1021,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"
)
@@ -1102,13 +1098,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 +1110,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")
@@ -1160,6 +1145,250 @@ 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)
+
+ # todo: Add internal switch and relay, like Connect
+
+ # 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:
+ self.a1green(bool(value & 1))
+ self.a1red(bool(value & 2))
+ 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:
+ self.a2green(bool(value & 1))
+ self.a2red(bool(value & 2))
+ 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:
+ self.a3green(bool(value & 1))
+ self.a3red(bool(value & 2))
+ 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:
+ self.a4green(bool(value & 1))
+ self.a4red(bool(value & 2))
+ 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:
+ self.a5green(bool(value & 1))
+ self.a5red(bool(value & 2))
+ 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/helper.py b/revpimodio2/helper.py
index c0980c4..3a49896 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
@@ -463,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
@@ -532,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 \
@@ -556,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)
@@ -602,8 +602,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/io.py b/revpimodio2/io.py
index 96fca1d..b9ac368 100644
--- a/revpimodio2/io.py
+++ b/revpimodio2/io.py
@@ -624,7 +624,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:
@@ -640,6 +640,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
@@ -653,6 +654,7 @@ class IOBase(object):
except Exception as e:
self._parentdevice._modio._gotioerror(
"net_ioset", e)
+ return
else:
# IOCTL in Datei simulieren
@@ -665,28 +667,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:
@@ -704,7 +704,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(
@@ -715,8 +715,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/revpimodio2/modio.py b/revpimodio2/modio.py
index c189347..9dc87f4 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?
@@ -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(
@@ -682,7 +688,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.
@@ -709,6 +715,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
@@ -730,6 +737,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:
@@ -751,12 +768,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
@@ -775,11 +798,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
@@ -1023,6 +1048,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 \
@@ -1094,7 +1120,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:
@@ -1181,9 +1207,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 "
@@ -1206,7 +1229,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
@@ -1348,7 +1373,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.
@@ -1356,17 +1383,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/revpimodio2/netio.py b/revpimodio2/netio.py
index 8b823e1..8b8c913 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,28 @@ class NetFH(Thread):
if self.__sockend.is_set():
raise ValueError("flush of closed file")
- with self.__socklock:
+ if self.__int_buff == 0:
+ return
+
+ 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 +415,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 +439,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 +461,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 +485,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 +503,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 +544,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 +588,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 +623,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,14 +656,13 @@ 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
- # 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
@@ -802,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/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
diff --git a/setup.py b/setup.py
index 2fd9026..899d939 100644
--- a/setup.py
+++ b/setup.py
@@ -17,7 +17,7 @@ setup(
license="LGPLv3",
name="revpimodio2",
- version="2.5.3",
+ version="2.5.3g",
packages=["revpimodio2"],
python_requires="~=3.2",