Merge branch 'develop'

This commit is contained in:
2021-01-14 19:02:59 +01:00
10 changed files with 506 additions and 322 deletions

2
.idea/misc.xml generated
View File

@@ -3,7 +3,7 @@
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6" project-jdk-type="Python SDK" />
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.8" project-jdk-type="Python SDK" />
<component name="PythonCompatibilityInspectionAdvertiser">
<option name="version" value="3" />
</component>

2
.idea/revpimodio2.iml generated
View File

@@ -2,7 +2,7 @@
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Python 3.6" jdkType="Python SDK" />
<orderEntry type="jdk" jdkName="Python 3.8" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TestRunnerService">

View File

@@ -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 <akira@revpimodio.org>"
__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

View File

@@ -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."""

View File

@@ -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,10 +534,10 @@ 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:
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 \
@@ -544,8 +546,6 @@ class ProcimgWriter(Thread):
else:
# Inputs in Puffer, Outputs in Prozessabbild
for dev in self._modio._lst_refresh:
with dev._filelock:
dev._ba_devdata[dev._slc_inp] = \
bytesbuff[dev._slc_inpoff]
if self.__eventwork \
@@ -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()

View File

@@ -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,9 +667,7 @@ class IOBase(object):
)
except Exception as e:
self._parentdevice._modio._gotioerror("file_ioset", e)
else:
# Gepuffertes Schreiben der Outputs
return
# Für Bitoperationen sperren
self._parentdevice._filelock.acquire()
@@ -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,7 +715,8 @@ class IOBase(object):
self._parentdevice._modio._myfh.flush()
except IOError as e:
self._parentdevice._modio._gotioerror("ioset", e)
else:
return
self._parentdevice._ba_devdata[self._slc_address] = value
def unreg_event(self, func=None, edge=None) -> None:

View File

@@ -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):
if not self._imgwriter.is_alive():
self.exit(full=False)
if self._imgwriter.is_alive():
e = RuntimeError("no new io data in cycle loop")
else:
e = RuntimeError("autorefresh thread not running")
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()
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)

View File

@@ -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:
# b CM ii ii 00000000 b = 16
if self.__int_buff == 0:
return
try:
self._slavesock.sendall(pack(
# b CM ii ii 00000000 b = 16
buff = self._direct_sr(pack(
"=c2sHH8xc",
HEADER_START, b'FD', self.__int_buff, len(self.__by_buff), HEADER_STOP
) + self.__by_buff)
) + self.__by_buff, 1)
except Exception:
self.__flusherr = True
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':
if buff != b'\x1e':
# ACL prüfen und ggf Fehler werfen
self.__check_acl(blockok)
self.__check_acl(buff)
self.__flusherr = True
self.__sockerr.set()
raise IOError("flush error on network")
else:
self.__flusherr = False
def get_closed(self) -> bool:
"""
@@ -416,18 +415,14 @@ class NetFH(Thread):
if not (isinstance(arg, bytes) and len(arg) <= 1024):
raise TypeError("arg must be <class 'bytes'>")
with self.__socklock:
# b CM xx ii iiii0000 b = 16
self._slavesock.sendall(pack(
buff = self._direct_sr(pack(
"=c2s2xHI4xc",
HEADER_START, b'IC', len(arg), request, HEADER_STOP
) + arg)
# Rückmeldebyte auswerten
blockok = self._slavesock.recv(1)
if blockok != b'\x1e':
) + arg, 1)
if buff != b'\x1e':
# ACL prüfen und ggf Fehler werfen
self.__check_acl(blockok)
self.__check_acl(buff)
self.__sockerr.set()
raise IOError("ioctl error on network")
@@ -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(
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)
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(
buff = self._direct_sr(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
), 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<ipn>(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

View File

@@ -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

View File

@@ -17,7 +17,7 @@ setup(
license="LGPLv3",
name="revpimodio2",
version="2.5.3",
version="2.5.3g",
packages=["revpimodio2"],
python_requires="~=3.2",