Docstrings optimieren 2 - Code-Cleanup

This commit is contained in:
2019-10-20 23:51:57 +02:00
parent 10af9a01d5
commit 9d85c7bdc0
4 changed files with 424 additions and 305 deletions

View File

@@ -12,7 +12,7 @@ class App(object):
__slots__ = "name", "version", "language", "layout", "savets"
def __init__(self, app):
def __init__(self, app: dict):
"""
Instantiiert die App-Klasse.

View File

@@ -1,26 +1,27 @@
# -*- coding: utf-8 -*-
"""RevPiModIO Hauptklasse fuer piControl0 Zugriff."""
__author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2018 Sven Sager"
__license__ = "LGPLv3"
import warnings
from configparser import ConfigParser
from json import load as jload
from multiprocessing import cpu_count
from os import access, F_OK, R_OK
from os import F_OK, R_OK, access
from os import stat as osstat
from queue import Empty
from revpimodio2 import acheck, DeviceNotFoundError, BOTH, RISING, FALLING
from signal import signal, SIG_DFL, SIGINT, SIGTERM
from signal import SIGINT, SIGTERM, SIG_DFL, signal
from stat import S_ISCHR
from threading import Thread, Event, Lock
from threading import Event, Lock, Thread
from timeit import default_timer
from revpimodio2 import BOTH, DeviceNotFoundError, FALLING, RISING, acheck
__author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2018 Sven Sager"
__license__ = "LGPLv3"
class RevPiModIO(object):
"""Klasse fuer die Verwaltung der piCtory Konfiguration.
"""
Klasse fuer die Verwaltung der piCtory Konfiguration.
Diese Klasse uebernimmt die gesamte Konfiguration aus piCtory und bilded
die Devices und IOs ab. Sie uebernimmt die exklusive Verwaltung des
@@ -28,33 +29,32 @@ class RevPiModIO(object):
Sollten nur einzelne Devices gesteuert werden, verwendet man
RevPiModIOSelected() und uebergibt bei Instantiierung eine Liste mit
Device Positionen oder Device Namen.
"""
__slots__ = "__cleanupfunc", "_autorefresh", "_buffedwrite", \
"_configrsc", "_direct_output", "_exit", "_imgwriter", "_ioerror", \
"_length", "_looprunning", "_lst_devselect", "_lst_refresh", \
"_maxioerrors", "_myfh", "_myfh_lck", "_monitoring", "_procimg", \
"_simulator", "_syncoutputs", "_th_mainloop", "_waitexit", \
"core", "app", "device", "exitsignal", "io", "summary", "_debug", \
"_replace_io_file", "_run_on_pi"
"_configrsc", "_direct_output", "_exit", "_imgwriter", "_ioerror", \
"_length", "_looprunning", "_lst_devselect", "_lst_refresh", \
"_maxioerrors", "_myfh", "_myfh_lck", "_monitoring", "_procimg", \
"_simulator", "_syncoutputs", "_th_mainloop", "_waitexit", \
"core", "app", "device", "exitsignal", "io", "summary", "_debug", \
"_replace_io_file", "_run_on_pi"
def __init__(
self, autorefresh=False, monitoring=False, syncoutputs=True,
procimg=None, configrsc=None, simulator=False, debug=True,
replace_io_file=None, direct_output=False):
"""Instantiiert die Grundfunktionen.
@param autorefresh Wenn True, alle Devices zu autorefresh hinzufuegen
@param monitoring In- und Outputs werden gelesen, niemals geschrieben
@param syncoutputs Aktuell gesetzte Outputs vom Prozessabbild einlesen
@param procimg Abweichender Pfad zum Prozessabbild
@param configrsc Abweichender Pfad zur piCtory Konfigurationsdatei
@param simulator Laedt das Modul als Simulator und vertauscht IOs
@param debug Gibt alle Warnungen inkl. Zyklusprobleme aus
@param replace_io_file Replace IO Konfiguration aus Datei laden
@param direct_output Write outputs immediately to process image (slow)
"""
Instantiiert die Grundfunktionen.
:param autorefresh: Wenn True, alle Devices zu autorefresh hinzufuegen
:param monitoring: In- und Outputs werden gelesen, niemals geschrieben
:param syncoutputs: Aktuell gesetzte Outputs vom Prozessabbild einlesen
:param procimg: Abweichender Pfad zum Prozessabbild
:param configrsc: Abweichender Pfad zur piCtory Konfigurationsdatei
:param simulator: Laedt das Modul als Simulator und vertauscht IOs
:param debug: Gibt alle Warnungen inkl. Zyklusprobleme aus
:param replace_io_file: Replace IO Konfiguration aus Datei laden
:param direct_output: Write outputs immediately to process image (slow)
"""
# Parameterprüfung
acheck(
@@ -127,10 +127,13 @@ class RevPiModIO(object):
if self._myfh is not None:
self._myfh.close()
def __evt_exit(self, signum, sigframe):
"""Eventhandler fuer Programmende.
@param signum Signalnummer
@param sigframe Signalframe"""
def __evt_exit(self, signum, sigframe) -> None:
"""
Eventhandler fuer Programmende.
:param signum: Signalnummer
:param sigframe: Signalframe
"""
signal(SIGINT, SIG_DFL)
signal(SIGTERM, SIG_DFL)
self.exit(full=True)
@@ -140,10 +143,12 @@ class RevPiModIO(object):
if not self._monitoring:
self.writeprocimg()
def _configure(self, jconfigrsc):
"""Verarbeitet die piCtory Konfigurationsdatei.
@param jconfigrsc: Data to build IOs as <class 'dict'> of JSON"""
def _configure(self, jconfigrsc: dict) -> None:
"""
Verarbeitet die piCtory Konfigurationsdatei.
:param jconfigrsc: Data to build IOs as <class 'dict'> of JSON
"""
# Filehandler konfigurieren, wenn er noch nicht existiert
if self._myfh is None:
self._myfh = self._create_myfh()
@@ -283,12 +288,12 @@ class RevPiModIO(object):
if self.core._slc_errorlimit1 is not None:
io = self.io[
self.core.offset + self.core._slc_errorlimit1.start
][0]
][0]
io.set_value(io._defaultvalue)
if self.core._slc_errorlimit2 is not None:
io = self.io[
self.core.offset + self.core._slc_errorlimit2.start
][0]
][0]
io.set_value(io._defaultvalue)
# RS485 errors schreiben
@@ -301,15 +306,15 @@ class RevPiModIO(object):
# Summary Klasse instantiieren
self.summary = summarymodule.Summary(jconfigrsc["Summary"])
def _configure_replace_io(self, creplaceio):
"""Importiert ersetzte IOs in diese Instanz.
def _configure_replace_io(self, creplaceio: ConfigParser) -> None:
"""
Importiert ersetzte IOs in diese Instanz.
Importiert ersetzte IOs, welche vorher mit .export_replaced_ios(...)
in eine Datei exportiert worden sind. Diese IOs werden in dieser
Instanz wiederhergestellt.
@param ireplaceio: Data to replace ios as <class 'ConfigParser'>
:param creplaceio: Data to replace ios as <class 'ConfigParser'>
"""
for io in creplaceio:
if io == "DEFAULT":
@@ -371,19 +376,28 @@ class RevPiModIO(object):
)
def _create_myfh(self):
"""Erstellt FileObject mit Pfad zum procimg.
return FileObject"""
"""
Erstellt FileObject mit Pfad zum procimg.
:return: FileObject
"""
self._buffedwrite = False
return open(self._procimg, "r+b", 0)
def _get_configrsc(self):
"""Getter function.
@return Pfad der verwendeten piCtory Konfiguration"""
def _get_configrsc(self) -> str:
"""
Getter function.
:return: Pfad der verwendeten piCtory Konfiguration
"""
return self._configrsc
def _get_cpreplaceio(self):
"""Laed die replace_io_file Konfiguration und verarbeitet sie.
@return <class 'ConfigParser'> der replace io daten"""
def _get_cpreplaceio(self) -> ConfigParser:
"""
Laed die replace_io_file Konfiguration und verarbeitet sie.
:return: <class 'ConfigParser'> der replace io daten
"""
cp = ConfigParser()
# TODO: verfeinern!
@@ -400,58 +414,85 @@ class RevPiModIO(object):
return cp
def _get_cycletime(self):
"""Gibt Aktualisierungsrate in ms der Prozessabbildsynchronisierung aus.
@return Millisekunden"""
def _get_cycletime(self) -> int:
"""
Gibt Aktualisierungsrate in ms der Prozessabbildsynchronisierung aus.
:return: Millisekunden
"""
return self._imgwriter.refresh
def _get_debug(self):
"""Gibt Status des Debugflags zurueck.
@return Status des Debugflags"""
def _get_debug(self) -> bool:
"""
Gibt Status des Debugflags zurueck.
:return: Status des Debugflags
"""
return self._debug == 1
def _get_ioerrors(self):
"""Getter function.
@return Aktuelle Anzahl gezaehlter Fehler"""
def _get_ioerrors(self) -> int:
"""
Getter function.
:return: Aktuelle Anzahl gezaehlter Fehler
"""
return self._ioerror
def _get_length(self):
"""Getter function.
@return Laenge in Bytes der Devices"""
def _get_length(self) -> int:
"""
Getter function.
:return: Laenge in Bytes der Devices
"""
return self._length
def _get_maxioerrors(self):
"""Getter function.
@return Anzahl erlaubte Fehler"""
def _get_maxioerrors(self) -> int:
"""
Getter function.
:return: Anzahl erlaubte Fehler
"""
return self._maxioerrors
def _get_monitoring(self):
"""Getter function.
@return True, wenn als Monitoring gestartet"""
def _get_monitoring(self) -> bool:
"""
Getter function.
:return: True, wenn als Monitoring gestartet
"""
return self._monitoring
def _get_procimg(self):
"""Getter function.
@return Pfad des verwendeten Prozessabbilds"""
def _get_procimg(self) -> str:
"""
Getter function.
:return: Pfad des verwendeten Prozessabbilds
"""
return self._procimg
def _get_replace_io_file(self):
"""Gibt Pfad zur verwendeten replace IO Datei aus.
@return Pfad zur replace IO Datei"""
def _get_replace_io_file(self) -> str:
"""
Gibt Pfad zur verwendeten replace IO Datei aus.
:return: Pfad zur replace IO Datei
"""
return self._replace_io_file
def _get_simulator(self):
"""Getter function.
@return True, wenn als Simulator gestartet"""
def _get_simulator(self) -> bool:
"""
Getter function.
:return: True, wenn als Simulator gestartet
"""
return self._simulator
def _gotioerror(self, action, e=None, show_warn=True):
"""IOError Verwaltung fuer Prozessabbildzugriff.
@param action Zusatzinformationen zum loggen
@param e Exception to log if debug is enabled
@param show_warn Warnung anzeigen
def _gotioerror(self, action: str, e=None, show_warn=True) -> None:
"""
IOError Verwaltung fuer Prozessabbildzugriff.
:param action: Zusatzinformationen zum loggen
:param e: Exception to log if debug is enabled
:param show_warn: Warnung anzeigen
"""
self._ioerror += 1
if self._maxioerrors != 0 and self._ioerror >= self._maxioerrors:
@@ -475,9 +516,12 @@ class RevPiModIO(object):
RuntimeWarning
)
def _set_cycletime(self, milliseconds):
"""Setzt Aktualisierungsrate der Prozessabbild-Synchronisierung.
@param milliseconds <class 'int'> in Millisekunden"""
def _set_cycletime(self, milliseconds: int) -> None:
"""
Setzt Aktualisierungsrate der Prozessabbild-Synchronisierung.
:param milliseconds: <class 'int'> in Millisekunden
"""
if self._looprunning:
raise RuntimeError(
"can not change cycletime when cycleloop or mainloop is "
@@ -486,9 +530,12 @@ class RevPiModIO(object):
else:
self._imgwriter.refresh = milliseconds
def _set_debug(self, value):
"""Setzt debugging Status um mehr Meldungen zu erhalten oder nicht.
@param value Wenn True, werden umfangreiche Medungen angezeigt"""
def _set_debug(self, value: bool) -> None:
"""
Setzt debugging Status um mehr Meldungen zu erhalten oder nicht.
:param value: Wenn True, werden umfangreiche Medungen angezeigt
"""
if type(value) == bool:
value = int(value)
if not type(value) == int:
@@ -506,18 +553,24 @@ class RevPiModIO(object):
else:
warnings.filterwarnings("always", module="revpimodio2")
def _set_maxioerrors(self, value):
"""Setzt Anzahl der maximal erlaubten Fehler bei Prozessabbildzugriff.
@param value Anzahl erlaubte Fehler"""
def _set_maxioerrors(self, value: int) -> None:
"""
Setzt Anzahl der maximal erlaubten Fehler bei Prozessabbildzugriff.
:param value: Anzahl erlaubte Fehler
"""
if type(value) == int and value >= 0:
self._maxioerrors = value
else:
raise ValueError("value must be 0 or a positive integer")
def _simulate_ioctl(self, request, arg=b''):
"""Simuliert IOCTL Funktionen auf procimg Datei.
@param request IO Request
@param arg: Request argument"""
def _simulate_ioctl(self, request: int, arg=b'') -> None:
"""
Simuliert IOCTL Funktionen auf procimg Datei.
:param request: IO Request
:param arg: Request argument
"""
if request == 19216:
# Einzelnes Bit setzen
byte_address = int.from_bytes(arg[:2], byteorder="little")
@@ -552,7 +605,7 @@ class RevPiModIO(object):
for i in range(16):
if bool(bit_field & 1 << i):
io_byte = self.device[dev_position].offset \
+ int(self.device[dev_position]._lst_counter[i])
+ int(self.device[dev_position]._lst_counter[i])
break
if io_byte == -1:
@@ -564,12 +617,12 @@ class RevPiModIO(object):
if self._buffedwrite:
self._myfh.flush()
def autorefresh_all(self):
def autorefresh_all(self) -> None:
"""Setzt alle Devices in autorefresh Funktion."""
for dev in self.device:
dev.autorefresh()
def cleanup(self):
def cleanup(self) -> None:
"""Beendet autorefresh und alle Threads."""
self.exit(full=True)
self._myfh.close()
@@ -580,7 +633,8 @@ class RevPiModIO(object):
self.summary = None
def cycleloop(self, func, cycletime=50):
"""Startet den Cycleloop.
"""
Startet den Cycleloop.
Der aktuelle Programmthread wird hier bis Aufruf von
.exit() "gefangen". Er fuehrt nach jeder Aktualisierung
@@ -603,10 +657,9 @@ class RevPiModIO(object):
50 Millisekunden, in denen das Prozessabild eingelesen, die uebergebene
Funktion ausgefuert und das Prozessabbild geschrieben wird.
@param func Funktion, die ausgefuehrt werden soll
@param cycletime Zykluszeit in Millisekunden - Standardwert 50 ms
@return None or the return value of the cycle function
:param func: Funktion, die ausgefuehrt werden soll
:param cycletime: Zykluszeit in Millisekunden - Standardwert 50 ms
:return: None or the return value of the cycle function
"""
# Prüfen ob ein Loop bereits läuft
if self._looprunning:
@@ -679,8 +732,9 @@ class RevPiModIO(object):
return ec
def exit(self, full=True):
"""Beendet mainloop() und optional autorefresh.
def exit(self, full=True) -> None:
"""
Beendet mainloop() und optional autorefresh.
Wenn sich das Programm im mainloop() befindet, wird durch Aufruf
von exit() die Kontrolle wieder an das Hauptprogramm zurueckgegeben.
@@ -689,8 +743,8 @@ class RevPiModIO(object):
dem autorefresh. Der Thread fuer die Prozessabbildsynchronisierung
wird dann gestoppt und das Programm kann sauber beendet werden.
@param full Entfernt auch alle Devices aus autorefresh"""
:param full: Entfernt auch alle Devices aus autorefresh
"""
# Benutzerevent
self.exitsignal.set()
@@ -714,15 +768,17 @@ class RevPiModIO(object):
if not self._monitoring:
self.writeprocimg(dev)
def export_replaced_ios(self, filename="replace_ios.conf"):
"""Exportiert ersetzte IOs dieser Instanz.
def export_replaced_ios(self, filename="replace_ios.conf") -> None:
"""
Exportiert ersetzte IOs dieser Instanz.
Exportiert alle ersetzten IOs, welche mit .replace_io(...) angelegt
wurden. Die Datei kann z.B. fuer RevPiPyLoad verwndet werden um Daten
in den neuen Formaten per MQTT zu uebertragen oder mit RevPiPyControl
anzusehen.
@param filename Dateiname fuer Exportdatei"""
@param filename Dateiname fuer Exportdatei
"""
acheck(str, filename=filename)
cp = ConfigParser()
@@ -753,9 +809,12 @@ class RevPiModIO(object):
"".format(filename, e)
)
def get_jconfigrsc(self):
"""Laedt die piCtory Konfiguration und erstellt ein <class 'dict'>.
@return <class 'dict'> der piCtory Konfiguration"""
def get_jconfigrsc(self) -> dict:
"""
Laedt die piCtory Konfiguration und erstellt ein <class 'dict'>.
:return: <class 'dict'> der piCtory Konfiguration
"""
# piCtory Konfiguration prüfen
if self._configrsc is not None:
if not access(self._configrsc, F_OK | R_OK):
@@ -786,8 +845,9 @@ class RevPiModIO(object):
)
return jdata
def handlesignalend(self, cleanupfunc=None):
"""Signalhandler fuer Programmende verwalten.
def handlesignalend(self, cleanupfunc=None) -> None:
"""
Signalhandler fuer Programmende verwalten.
Wird diese Funktion aufgerufen, uebernimmt RevPiModIO die SignalHandler
fuer SIGINT und SIGTERM. Diese werden Empfangen, wenn das
@@ -804,9 +864,7 @@ class RevPiModIO(object):
RevPiModIO Thrads / Funktionen werden die SignalHandler wieder
freigegeben.
@param cleanupfunc Funktion wird nach dem letzten Lesen der Inputs
ausgefuehrt, gefolgt vom letzten Schreiben der Outputs
:param cleanupfunc: Funktion wird nach dem Beenden ausgefuehrt
"""
# Prüfen ob Funktion callable ist
if not (cleanupfunc is None or callable(cleanupfunc)):
@@ -818,8 +876,9 @@ class RevPiModIO(object):
signal(SIGINT, self.__evt_exit)
signal(SIGTERM, self.__evt_exit)
def mainloop(self, blocking=True):
"""Startet den Mainloop mit Eventueberwachung.
def mainloop(self, blocking=True) -> None:
"""
Startet den Mainloop mit Eventueberwachung.
Der aktuelle Programmthread wird hier bis Aufruf von
RevPiDevicelist.exit() "gefangen" (es sei denn blocking=False). Er
@@ -833,9 +892,7 @@ class RevPiModIO(object):
Events vom RevPi benoetigt werden, aber das Programm weiter ausgefuehrt
werden soll.
@param blocking Wenn False, blockiert das Programm hier NICHT
@return None
:param blocking: Wenn False, blockiert das Programm hier NICHT
"""
# Prüfen ob ein Loop bereits läuft
if self._looprunning:
@@ -935,14 +992,14 @@ class RevPiModIO(object):
if e is not None:
raise e
def readprocimg(self, device=None):
"""Einlesen aller Inputs aller/eines Devices vom Prozessabbild.
def readprocimg(self, device=None) -> bool:
"""
Einlesen aller Inputs aller/eines Devices vom Prozessabbild.
Devices mit aktiverem autorefresh werden ausgenommen!
@param device nur auf einzelnes Device anwenden
@return True, wenn Arbeiten an allen Devices erfolgreich waren
:param device: nur auf einzelnes Device anwenden
:return: True, wenn Arbeiten an allen Devices erfolgreich waren
"""
if device is None:
mylist = self.device
@@ -988,13 +1045,16 @@ class RevPiModIO(object):
return True
def resetioerrors(self):
def resetioerrors(self) -> None:
"""Setzt aktuellen IOError-Zaehler auf 0 zurueck."""
self._ioerror = 0
def setdefaultvalues(self, device=None):
"""Alle Outputbuffer werden auf die piCtory default Werte gesetzt.
@param device nur auf einzelnes Device anwenden"""
def setdefaultvalues(self, device=None) -> None:
"""
Alle Outputbuffer werden auf die piCtory default Werte gesetzt.
:param device: nur auf einzelnes Device anwenden
"""
if self._monitoring:
raise RuntimeError(
"can not set default values, while system is in monitoring "
@@ -1012,14 +1072,14 @@ class RevPiModIO(object):
for io in dev.get_outputs():
io.set_value(io._defaultvalue)
def syncoutputs(self, device=None):
"""Lesen aller aktuell gesetzten Outputs im Prozessabbild.
def syncoutputs(self, device=None) -> bool:
"""
Lesen aller aktuell gesetzten Outputs im Prozessabbild.
Devices mit aktiverem autorefresh werden ausgenommen!
@param device nur auf einzelnes Device anwenden
@return True, wenn Arbeiten an allen Devices erfolgreich waren
:param device: nur auf einzelnes Device anwenden
:return: True, wenn Arbeiten an allen Devices erfolgreich waren
"""
if device is None:
mylist = self.device
@@ -1052,14 +1112,14 @@ class RevPiModIO(object):
return True
def writeprocimg(self, device=None):
"""Schreiben aller Outputs aller Devices ins Prozessabbild.
def writeprocimg(self, device=None) -> bool:
"""
Schreiben aller Outputs aller Devices ins Prozessabbild.
Devices mit aktiverem autorefresh werden ausgenommen!
@param device nur auf einzelnes Device anwenden
@return True, wenn Arbeiten an allen Devices erfolgreich waren
:param device: nur auf einzelnes Device anwenden
:return: True, wenn Arbeiten an allen Devices erfolgreich waren
"""
if self._direct_output:
return True
@@ -1127,14 +1187,13 @@ class RevPiModIO(object):
class RevPiModIOSelected(RevPiModIO):
"""Klasse fuer die Verwaltung einzelner Devices aus piCtory.
"""
Klasse fuer die Verwaltung einzelner Devices aus piCtory.
Diese Klasse uebernimmt nur angegebene Devices der piCtory Konfiguration
und bilded sie inkl. IOs ab. Sie uebernimmt die exklusive Verwaltung des
Adressbereichs im Prozessabbild an dem sich die angegebenen Devices
befinden und stellt sicher, dass die Daten synchron sind.
"""
__slots__ = ()
@@ -1144,15 +1203,15 @@ class RevPiModIOSelected(RevPiModIO):
syncoutputs=True, procimg=None, configrsc=None,
simulator=False, debug=True, replace_io_file=None,
direct_output=False):
"""Instantiiert nur fuer angegebene Devices die Grundfunktionen.
"""
Instantiiert nur fuer angegebene Devices die Grundfunktionen.
Der Parameter deviceselection kann eine einzelne
Device Position / einzelner Device Name sein oder eine Liste mit
mehreren Positionen / Namen
@param deviceselection Positionsnummer oder Devicename
@see #RevPiModIO.__init__ RevPiModIO.__init__(...)
:param deviceselection: Positionsnummer oder Devicename
:ref: :func:`RevPiModIO.__init__(...)`
"""
super().__init__(
autorefresh, monitoring, syncoutputs, procimg, configrsc,
@@ -1197,29 +1256,29 @@ class RevPiModIOSelected(RevPiModIO):
class RevPiModIODriver(RevPiModIOSelected):
"""Klasse um eigene Treiber fuer die virtuellen Devices zu erstellen.
"""
Klasse um eigene Treiber fuer die virtuellen Devices zu erstellen.
Mit dieser Klasse werden nur angegebene Virtuelle Devices mit RevPiModIO
verwaltet. Bei Instantiierung werden automatisch die Inputs und Outputs
verdreht, um das Schreiben der Inputs zu ermoeglichen. Die Daten koennen
dann ueber logiCAD an den Devices abgerufen werden.
"""
__slots__ = ()
def __init__(
self, virtdev, autorefresh=False, monitoring=False,
self, virtdev, autorefresh=False,
syncoutputs=True, procimg=None, configrsc=None, debug=True,
replace_io_file=None, direct_output=False):
"""Instantiiert die Grundfunktionen.
"""
Instantiiert die Grundfunktionen.
Parameter 'monitoring' und 'simulator' stehen hier nicht zur
Verfuegung, da diese automatisch gesetzt werden.
@param virtdev Virtuelles Device oder mehrere als <class 'list'>
@see #RevPiModIO.__init__ RevPiModIO.__init__(...)
:param virtdev: Virtuelles Device oder mehrere als <class 'list'>
:ref: :func:`RevPiModIO.__init__()`
"""
# Parent mit monitoring=False und simulator=True laden

View File

@@ -1,19 +1,20 @@
# -*- coding: utf-8 -*-
"""RevPiModIO Hauptklasse fuer Netzwerkzugriff."""
__author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2018 Sven Sager"
__license__ = "LGPLv3"
import socket
import warnings
from configparser import ConfigParser
from json import loads as jloads
from re import compile
from revpimodio2 import DeviceNotFoundError
from threading import Thread, Event, Lock
from threading import Event, Lock, Thread
from revpimodio2 import DeviceNotFoundError
from .device import Device
from .modio import RevPiModIO as _RevPiModIO
__author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2018 Sven Sager"
__license__ = "LGPLv3"
# Synchronisierungsbefehl
_syssync = b'\x01\x06\x16\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17'
# Disconnectbefehl
@@ -33,42 +34,39 @@ HASH_FAIL = b'\xff' * 16
class AclException(Exception):
"""Probleme mit Berechtigungen."""
pass
class ConfigChanged(Exception):
"""Aenderung der piCtory oder replace_ios Datei."""
pass
class NetFH(Thread):
"""Netzwerk File Handler fuer das Prozessabbild.
"""
Netzwerk File Handler fuer das Prozessabbild.
Dieses FileObject-like Object verwaltet das Lesen und Schriben des
Prozessabbilds ueber das Netzwerk. Ein entfernter Revolution Pi kann
so gesteuert werden.
"""
__slots__ = "__by_buff", "__check_replace_ios", "__config_changed", \
"__int_buff", "__dictdirty", "__flusherr", "__replace_ios_h", \
"__pictory_h", "__position", "__sockact", "__sockerr", "__sockend", \
"__socklock", "__timeout", "__trigger", "__waitsync", "_address", \
"_slavesock", "daemon"
"__int_buff", "__dictdirty", "__flusherr", "__replace_ios_h", \
"__pictory_h", "__position", "__sockact", "__sockerr", "__sockend", \
"__socklock", "__timeout", "__trigger", "__waitsync", "_address", \
"_slavesock", "daemon"
def __init__(self, address, check_replace_ios, timeout=500):
"""Init NetFH-class.
@param address IP Adresse, Port des RevPi als <class 'tuple'>
@param check_replace_ios Prueft auf Veraenderungen der Datei
@param timeout Timeout in Millisekunden der Verbindung
def __init__(self, address: tuple, check_replace_ios: bool, timeout=500):
"""
Init NetFH-class.
:param address: IP Adresse, Port des RevPi als <class 'tuple'>
:param check_replace_ios: Prueft auf Veraenderungen der Datei
:param timeout: Timeout in Millisekunden der Verbindung
"""
super().__init__()
self.daemon = True
@@ -114,10 +112,13 @@ class NetFH(Thread):
"""NetworkFileHandler beenden."""
self.close()
def __check_acl(self, bytecode):
"""Pueft ob ACL auf RevPi den Vorgang erlaubt oder wirft exception."""
if bytecode == b'\x18':
def __check_acl(self, bytecode: bytes) -> None:
"""
Pueft ob ACL auf RevPi den Vorgang erlaubt oder wirft exception.
:param bytecode: Antwort, die geprueft werden solll
"""
if bytecode == b'\x18':
# Alles beenden, wenn nicht erlaubt
self.__sockend.set()
self.__sockerr.set()
@@ -128,10 +129,12 @@ class NetFH(Thread):
"reload revpipyload!"
)
def __set_systimeout(self, value):
"""Systemfunktion fuer Timeoutberechnung.
@param value Timeout in Millisekunden 100 - 60000"""
def __set_systimeout(self, value: int) -> None:
"""
Systemfunktion fuer Timeoutberechnung.
:param value: Timeout in Millisekunden 100 - 60000
"""
if isinstance(value, int) and (100 <= value <= 60000):
self.__timeout = value / 1000
@@ -147,7 +150,7 @@ class NetFH(Thread):
else:
raise ValueError("value must between 10 and 60000 milliseconds")
def _connect(self):
def _connect(self) -> None:
"""Stellt die Verbindung zu einem RevPiSlave her."""
# Neuen Socket aufbauen
so = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -212,13 +215,13 @@ class NetFH(Thread):
for pos in self.__dictdirty:
self.set_dirtybytes(pos, self.__dictdirty[pos])
def _direct_send(self, send_bytes, recv_count):
"""Fuer debugging direktes Senden von Daten.
@param send_bytes Bytes, die gesendet werden sollen
@param recv_count Anzahl der Empfangsbytes
@returns Empfangende Bytes
def _direct_send(self, send_bytes: bytes, recv_count: int) -> bytes:
"""
Fuer debugging direktes Senden von Daten.
:param send_bytes: Bytes, die gesendet werden sollen
:param recv_count: Anzahl der Empfangsbytes
:return: Empfangende Bytes
"""
if self.__sockend.is_set():
raise ValueError("I/O operation on closed file")
@@ -230,9 +233,12 @@ class NetFH(Thread):
self.__trigger = True
return recv
def clear_dirtybytes(self, position=None):
"""Entfernt die konfigurierten Dirtybytes vom RevPi Slave.
@param position Startposition der Dirtybytes"""
def clear_dirtybytes(self, position=None) -> None:
"""
Entfernt die konfigurierten Dirtybytes vom RevPi Slave.
:param position: Startposition der Dirtybytes
"""
if self.__config_changed:
raise ConfigChanged("configuration on revolution pi was changed")
if self.__sockend.is_set():
@@ -255,7 +261,6 @@ class NetFH(Thread):
check = self._slavesock.recv(1)
if check != b'\x1e':
# ACL prüfen und ggf Fehler werfen
self.__check_acl(check)
@@ -279,7 +284,7 @@ class NetFH(Thread):
self.__trigger = True
def close(self):
def close(self) -> None:
"""Verbindung trennen."""
if self.__sockend.is_set():
return
@@ -302,7 +307,7 @@ class NetFH(Thread):
self._slavesock.close()
def flush(self):
def flush(self) -> None:
"""Schreibpuffer senden."""
if self.__config_changed:
raise ConfigChanged("configuration on revolution pi was changed")
@@ -334,35 +339,53 @@ class NetFH(Thread):
self.__trigger = True
def get_closed(self):
"""Pruefen ob Verbindung geschlossen ist.
@return True, wenn Verbindung geschlossen ist"""
def get_closed(self) -> bool:
"""
Pruefen ob Verbindung geschlossen ist.
:return: True, wenn Verbindung geschlossen ist
"""
return self.__sockend.is_set()
def get_config_changed(self):
"""Pruefen ob RevPi Konfiguration geaendert wurde.
@return True, wenn RevPi Konfiguration geaendert ist"""
def get_config_changed(self) -> bool:
"""
Pruefen ob RevPi Konfiguration geaendert wurde.
:return: True, wenn RevPi Konfiguration geaendert ist
"""
return self.__config_changed
def get_name(self):
"""Verbindugnsnamen zurueckgeben.
@return <class 'str'> IP:PORT"""
def get_name(self) -> str:
"""
Verbindugnsnamen zurueckgeben.
:return: <class 'str'> IP:PORT
"""
return "{0}:{1}".format(*self._address)
def get_reconnecting(self):
"""Interner reconnect aktiv wegen Netzwerkfehlern.
@return True, wenn reconnect aktiv"""
def get_reconnecting(self) -> bool:
"""
Interner reconnect aktiv wegen Netzwerkfehlern.
:return: True, wenn reconnect aktiv
"""
return self.__sockerr.is_set()
def get_timeout(self):
"""Gibt aktuellen Timeout zurueck.
@return <class 'int'> in Millisekunden"""
def get_timeout(self) -> int:
"""
Gibt aktuellen Timeout zurueck.
:return: <class 'int'> in Millisekunden
"""
return int(self.__timeout * 1000)
def ioctl(self, request, arg=b''):
"""IOCTL Befehle ueber das Netzwerk senden.
@param request Request as <class 'int'>
@param arg Argument as <class 'byte'>"""
def ioctl(self, request: int, arg=b'') -> None:
"""
IOCTL Befehle ueber das Netzwerk senden.
:param request: Request as <class 'int'>
:param arg: Argument as <class 'byte'>
"""
if self.__config_changed:
raise ConfigChanged("configuration on revolution pi was changed")
if self.__sockend.is_set():
@@ -383,7 +406,6 @@ class NetFH(Thread):
# Rückmeldebyte auswerten
check = self._slavesock.recv(1)
if check != b'\x1e':
# ACL prüfen und ggf Fehler werfen
self.__check_acl(check)
@@ -392,10 +414,13 @@ class NetFH(Thread):
self.__trigger = True
def read(self, length):
"""Daten ueber das Netzwerk lesen.
@param length Anzahl der Bytes
@return Gelesene <class 'bytes'>"""
def read(self, length: int) -> bytes:
"""
Daten ueber das Netzwerk lesen.
:param length: Anzahl der Bytes
:return: Gelesene <class 'bytes'>
"""
if self.__config_changed:
raise ConfigChanged("configuration on revolution pi was changed")
if self.__sockend.is_set():
@@ -423,9 +448,12 @@ class NetFH(Thread):
return bytes(bytesbuff)
def readpictory(self):
"""Ruft die piCtory Konfiguration ab.
@return <class 'bytes'> piCtory Datei"""
def readpictory(self) -> bytes:
"""
Ruft die piCtory Konfiguration ab.
:return: <class 'bytes'> piCtory Datei
"""
if self.__sockend.is_set():
raise ValueError("read of closed file")
@@ -454,9 +482,12 @@ class NetFH(Thread):
self.__sockerr.set()
raise IOError("readpictory error on network")
def readreplaceio(self):
"""Ruft die replace_io Konfiguration ab.
@return <class 'bytes'> replace_io_file"""
def readreplaceio(self) -> bytes:
"""
Ruft die replace_io Konfiguration ab.
:return: <class 'bytes'> replace_io_file
"""
if self.__sockend.is_set():
raise ValueError("read of closed file")
@@ -485,7 +516,7 @@ class NetFH(Thread):
self.__sockerr.set()
raise IOError("readreplaceio error on network")
def run(self):
def run(self) -> None:
"""Handler fuer Synchronisierung."""
state_reconnect = False
while not self.__sockend.is_set():
@@ -532,7 +563,7 @@ class NetFH(Thread):
# Warten nach Sync damit Instantiierung funktioniert
self.__sockerr.wait(self.__waitsync)
def seek(self, position):
def seek(self, position: int) -> None:
"""Springt an angegebene Position.
@param position An diese Position springen"""
if self.__config_changed:
@@ -541,10 +572,13 @@ class NetFH(Thread):
raise ValueError("seek of closed file")
self.__position = int(position)
def set_dirtybytes(self, position, dirtybytes):
"""Konfiguriert Dirtybytes fuer Prozessabbild bei Verbindungsfehler.
@param positon Startposition zum Schreiben
@param dirtybytes <class 'bytes'> die geschrieben werden sollen"""
def set_dirtybytes(self, position: int, dirtybytes: bytes) -> None:
"""
Konfiguriert Dirtybytes fuer Prozessabbild bei Verbindungsfehler.
:param position: Startposition zum Schreiben
:param dirtybytes: <class 'bytes'> die geschrieben werden sollen
"""
if self.__config_changed:
raise ConfigChanged("configuration on revolution pi was changed")
if self.__sockend.is_set():
@@ -564,7 +598,6 @@ class NetFH(Thread):
check = self._slavesock.recv(1)
if check != b'\x1e':
# ACL prüfen und ggf Fehler werfen
self.__check_acl(check)
@@ -585,9 +618,12 @@ class NetFH(Thread):
self.__trigger = True
def set_timeout(self, value):
"""Setzt Timeoutwert fuer Verbindung.
@param value Timeout in Millisekunden"""
def set_timeout(self, value: int) -> None:
"""
Setzt Timeoutwert fuer Verbindung.
:param value: Timeout in Millisekunden
"""
if self.__sockend.is_set():
raise ValueError("I/O operation on closed file")
@@ -612,19 +648,25 @@ class NetFH(Thread):
self.__trigger = True
def tell(self):
"""Gibt aktuelle Position zurueck.
@return int aktuelle Position"""
def tell(self) -> int:
"""
Gibt aktuelle Position zurueck.
:return: Aktuelle Position
"""
if self.__config_changed:
raise ConfigChanged("configuration on revolution pi was changed")
if self.__sockend.is_set():
raise ValueError("I/O operation on closed file")
return self.__position
def write(self, bytebuff):
"""Daten ueber das Netzwerk schreiben.
@param bytebuff Bytes zum schreiben
@return <class 'int'> Anzahl geschriebener bytes"""
def write(self, bytebuff: bytes) -> int:
"""
Daten ueber das Netzwerk schreiben.
:param bytebuff: Bytes zum schreiben
:return: <class 'int'> Anzahl geschriebener bytes
"""
if self.__config_changed:
raise ConfigChanged("configuration on revolution pi was changed")
if self.__sockend.is_set():
@@ -638,10 +680,10 @@ class NetFH(Thread):
# Datenblöcke mit Group Seperator in Puffer ablegen
self.__by_buff += b'\x01SD' + \
self.__position.to_bytes(length=2, byteorder="little") + \
len(bytebuff).to_bytes(length=2, byteorder="little") + \
b'\x1d\x00\x00\x00\x00\x00\x00\x00\x17' + \
bytebuff
self.__position.to_bytes(length=2, byteorder="little") + \
len(bytebuff).to_bytes(length=2, byteorder="little") + \
b'\x1d\x00\x00\x00\x00\x00\x00\x00\x17' + \
bytebuff
# TODO: Bufferlänge und dann flushen?
@@ -655,8 +697,8 @@ class NetFH(Thread):
class RevPiNetIO(_RevPiModIO):
"""Klasse fuer die Verwaltung der piCtory Konfiguration ueber das Netzwerk.
"""
Klasse fuer die Verwaltung der piCtory Konfiguration ueber das Netzwerk.
Diese Klasse uebernimmt die gesamte Konfiguration aus piCtory und bilded
die Devices und IOs ab. Sie uebernimmt die exklusive Verwaltung des
@@ -664,7 +706,6 @@ class RevPiNetIO(_RevPiModIO):
Sollten nur einzelne Devices gesteuert werden, verwendet man
RevPiModIOSelected() und uebergibt bei Instantiierung eine Liste mit
Device Positionen oder Device Namen.
"""
__slots__ = "_address"
@@ -673,17 +714,17 @@ class RevPiNetIO(_RevPiModIO):
self, address, autorefresh=False, monitoring=False,
syncoutputs=True, simulator=False, debug=True,
replace_io_file=None, direct_output=False):
"""Instantiiert die Grundfunktionen.
@param address: IP-Adresse <class 'str'> / (IP, Port) <class 'tuple'>
@param autorefresh Wenn True, alle Devices zu autorefresh hinzufuegen
@param monitoring In- und Outputs werden gelesen, niemals geschrieben
@param syncoutputs Aktuell gesetzte Outputs vom Prozessabbild einlesen
@param simulator Laedt das Modul als Simulator und vertauscht IOs
@param debug Gibt bei allen Fehlern komplette Meldungen aus
@param replace_io_file Replace IO Konfiguration aus Datei laden
@param direct_output Write outputs immediately to process image (slow)
"""
Instantiiert die Grundfunktionen.
:param address: IP-Adresse <class 'str'> / (IP, Port) <class 'tuple'>
:param autorefresh: Wenn True, alle Devices zu autorefresh hinzufuegen
:param monitoring: In- und Outputs werden gelesen, niemals geschrieben
:param syncoutputs: Aktuell gesetzte Outputs vom Prozessabbild einlesen
:param simulator: Laedt das Modul als Simulator und vertauscht IOs
:param debug: Gibt bei allen Fehlern komplette Meldungen aus
:param replace_io_file: Replace IO Konfiguration aus Datei laden
:param direct_output: Write outputs immediately to process image (slow)
"""
check_ip = compile(
r"^(?P<ipn>(25[0-5]|(2[0-4]|[01]?\d|)\d))(\.(?P=ipn)){3}$"
@@ -745,15 +786,20 @@ class RevPiNetIO(_RevPiModIO):
self._configure_replace_io(self._get_cpreplaceio())
def _create_myfh(self):
"""Erstellt NetworkFileObject.
return FileObject"""
"""
Erstellt NetworkFileObject.
:return: FileObject
"""
self._buffedwrite = True
return NetFH(self._address, self._replace_io_file == ":network:")
def _get_cpreplaceio(self):
"""Laed die replace_io Konfiguration ueber das Netzwerk.
@return <class 'ConfigParser'> der replace io daten"""
def _get_cpreplaceio(self) -> ConfigParser:
"""
Laed die replace_io Konfiguration ueber das Netzwerk.
:return: <class 'ConfigParser'> der replace io daten
"""
# Normale Verwendung über Elternklasse erledigen
if self._replace_io_file != ":network:":
return super()._get_cpreplaceio()
@@ -771,48 +817,60 @@ class RevPiNetIO(_RevPiModIO):
)
return cp
def disconnect(self):
def disconnect(self) -> None:
"""Trennt Verbindungen und beendet autorefresh inkl. alle Threads."""
self.cleanup()
def exit(self, full=True):
"""Beendet mainloop() und optional autorefresh.
@see #RevPiModIO.exit(...)"""
def exit(self, full=True) -> None:
"""
Beendet mainloop() und optional autorefresh.
:ref: :func:`RevPiModIO.exit()`
"""
try:
super().exit(full)
except ConfigChanged:
pass
def get_config_changed(self):
"""Pruefen ob RevPi Konfiguration geaendert wurde.
def get_config_changed(self) -> bool:
"""
Pruefen ob RevPi Konfiguration geaendert wurde.
In diesem Fall ist die Verbindung geschlossen und RevPiNetIO muss
neu instanziert werden.
@return True, wenn RevPi Konfiguration geaendert ist"""
:return: True, wenn RevPi Konfiguration geaendert ist
"""
return self._myfh.config_changed
def get_jconfigrsc(self):
"""Laedt die piCotry Konfiguration und erstellt ein <class 'dict'>.
@return <class 'dict'> der piCtory Konfiguration"""
def get_jconfigrsc(self) -> dict:
"""
Laedt die piCotry Konfiguration und erstellt ein <class 'dict'>.
:return: <class 'dict'> der piCtory Konfiguration
"""
mynh = NetFH(self._address, False)
byte_buff = mynh.readpictory()
mynh.close()
return jloads(byte_buff.decode("utf-8"))
def get_reconnecting(self):
"""Interner reconnect aktiv wegen Netzwerkfehlern.
def get_reconnecting(self) -> bool:
"""
Interner reconnect aktiv wegen Netzwerkfehlern.
Das Modul versucht intern die Verbindung neu herzustellen. Es ist
kein weiteres Zutun noetig.
@return True, wenn reconnect aktiv"""
:return: True, wenn reconnect aktiv
"""
return self._myfh.reconnecting
def net_cleardefaultvalues(self, device=None):
"""Loescht Defaultwerte vom PLC Slave.
@param device nur auf einzelnes Device anwenden, sonst auf Alle"""
def net_cleardefaultvalues(self, device=None) -> None:
"""
Loescht Defaultwerte vom PLC Slave.
:param device: nur auf einzelnes Device anwenden, sonst auf Alle
"""
if self.monitoring:
raise RuntimeError(
"can not send default values, while system is in "
@@ -829,10 +887,12 @@ class RevPiNetIO(_RevPiModIO):
for dev in mylist:
self._myfh.clear_dirtybytes(dev._offset + dev._slc_out.start)
def net_setdefaultvalues(self, device=None):
"""Konfiguriert den PLC Slave mit den piCtory Defaultwerten.
@param device nur auf einzelnes Device anwenden, sonst auf Alle"""
def net_setdefaultvalues(self, device=None) -> None:
"""
Konfiguriert den PLC Slave mit den piCtory Defaultwerten.
:param device: nur auf einzelnes Device anwenden, sonst auf Alle
"""
if self.monitoring:
raise RuntimeError(
"can not send default values, while system is in "
@@ -881,14 +941,13 @@ class RevPiNetIO(_RevPiModIO):
class RevPiNetIOSelected(RevPiNetIO):
"""Klasse fuer die Verwaltung einzelner Devices aus piCtory.
"""
Klasse fuer die Verwaltung einzelner Devices aus piCtory.
Diese Klasse uebernimmt nur angegebene Devices der piCtory Konfiguration
und bilded sie inkl. IOs ab. Sie uebernimmt die exklusive Verwaltung des
Adressbereichs im Prozessabbild an dem sich die angegebenen Devices
befinden und stellt sicher, dass die Daten synchron sind.
"""
__slots__ = ()
@@ -897,16 +956,16 @@ class RevPiNetIOSelected(RevPiNetIO):
self, address, deviceselection, autorefresh=False,
monitoring=False, syncoutputs=True, simulator=False, debug=True,
replace_io_file=None, direct_output=False):
"""Instantiiert nur fuer angegebene Devices die Grundfunktionen.
"""
Instantiiert nur fuer angegebene Devices die Grundfunktionen.
Der Parameter deviceselection kann eine einzelne
Device Position / einzelner Device Name sein oder eine Liste mit
mehreren Positionen / Namen
@param address: IP-Adresse <class 'str'> / (IP, Port) <class 'tuple'>
@param deviceselection Positionsnummer oder Devicename
@see #RevPiNetIO.__init__ RevPiNetIO.__init__(...)
:param address: IP-Adresse <class 'str'> / (IP, Port) <class 'tuple'>
:param deviceselection: Positionsnummer oder Devicename
:ref: :func:`RevPiNetIO.__init__()`
"""
super().__init__(
address, autorefresh, monitoring, syncoutputs, simulator, debug,
@@ -951,31 +1010,30 @@ class RevPiNetIOSelected(RevPiNetIO):
class RevPiNetIODriver(RevPiNetIOSelected):
"""Klasse um eigene Treiber fuer die virtuellen Devices zu erstellen.
"""
Klasse um eigene Treiber fuer die virtuellen Devices zu erstellen.
Mit dieser Klasse werden nur angegebene Virtuelle Devices mit RevPiModIO
verwaltet. Bei Instantiierung werden automatisch die Inputs und Outputs
verdreht, um das Schreiben der Inputs zu ermoeglichen. Die Daten koennen
dann ueber logiCAD an den Devices abgerufen werden.
"""
__slots__ = ()
def __init__(
self, address, virtdev, autorefresh=False, monitoring=False,
self, address, virtdev, autorefresh=False,
syncoutputs=True, debug=True, replace_io_file=None,
direct_output=False):
"""Instantiiert die Grundfunktionen.
"""
Instantiiert die Grundfunktionen.
Parameter 'monitoring' und 'simulator' stehen hier nicht zur
Verfuegung, da diese automatisch gesetzt werden.
@param address: IP-Adresse <class 'str'> / (IP, Port) <class 'tuple'>
@param virtdev Virtuelles Device oder mehrere als <class 'list'>
@see #RevPiModIO.__init__ RevPiModIO.__init__(...)
:param address: IP-Adresse <class 'str'> / (IP, Port) <class 'tuple'>
:param virtdev: Virtuelles Device oder mehrere als <class 'list'>
:ref: :func:`RevPiModIO.__init__()`
"""
# Parent mit monitoring=False und simulator=True laden
super().__init__(

View File

@@ -6,13 +6,15 @@ __license__ = "LGPLv3"
class Summary(object):
"""Bildet die Summary-Sektion der config.rsc ab."""
__slots__ = "inptotal", "outtotal"
def __init__(self, summary):
"""Instantiiert die RevPiSummary-Klasse.
@param summary piCtory Summaryinformationen"""
def __init__(self, summary: dict):
"""
Instantiiert die RevPiSummary-Klasse.
:param summary: piCtory Summaryinformationen
"""
self.inptotal = summary["inpTotal"]
self.outtotal = summary["outTotal"]