From 9d85c7bdc08336f7f76dd72922316e78265bf917 Mon Sep 17 00:00:00 2001 From: Akira Naru Takizawa Date: Sun, 20 Oct 2019 23:51:57 +0200 Subject: [PATCH] Docstrings optimieren 2 - Code-Cleanup --- revpimodio2/app.py | 2 +- revpimodio2/modio.py | 371 ++++++++++++++++++++++++----------------- revpimodio2/netio.py | 346 ++++++++++++++++++++++---------------- revpimodio2/summary.py | 10 +- 4 files changed, 424 insertions(+), 305 deletions(-) diff --git a/revpimodio2/app.py b/revpimodio2/app.py index 1977f60..bad49b4 100644 --- a/revpimodio2/app.py +++ b/revpimodio2/app.py @@ -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. diff --git a/revpimodio2/modio.py b/revpimodio2/modio.py index 75e0c9c..d6a1f07 100644 --- a/revpimodio2/modio.py +++ b/revpimodio2/modio.py @@ -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 of JSON""" + def _configure(self, jconfigrsc: dict) -> None: + """ + Verarbeitet die piCtory Konfigurationsdatei. + :param jconfigrsc: Data to build IOs as 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 - + :param creplaceio: Data to replace ios as """ 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 der replace io daten""" + def _get_cpreplaceio(self) -> ConfigParser: + """ + Laed die replace_io_file Konfiguration und verarbeitet sie. + + :return: 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 in Millisekunden""" + def _set_cycletime(self, milliseconds: int) -> None: + """ + Setzt Aktualisierungsrate der Prozessabbild-Synchronisierung. + + :param milliseconds: 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 . - @return der piCtory Konfiguration""" + def get_jconfigrsc(self) -> dict: + """ + Laedt die piCtory Konfiguration und erstellt ein . + + :return: 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 - @see #RevPiModIO.__init__ RevPiModIO.__init__(...) + :param virtdev: Virtuelles Device oder mehrere als + :ref: :func:`RevPiModIO.__init__()` """ # Parent mit monitoring=False und simulator=True laden diff --git a/revpimodio2/netio.py b/revpimodio2/netio.py index c09317e..d56b2e8 100644 --- a/revpimodio2/netio.py +++ b/revpimodio2/netio.py @@ -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 - @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 + :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 IP:PORT""" + def get_name(self) -> str: + """ + Verbindugnsnamen zurueckgeben. + + :return: 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 in Millisekunden""" + def get_timeout(self) -> int: + """ + Gibt aktuellen Timeout zurueck. + + :return: in Millisekunden + """ return int(self.__timeout * 1000) - def ioctl(self, request, arg=b''): - """IOCTL Befehle ueber das Netzwerk senden. - @param request Request as - @param arg Argument as """ + def ioctl(self, request: int, arg=b'') -> None: + """ + IOCTL Befehle ueber das Netzwerk senden. + + :param request: Request as + :param arg: Argument as + """ 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 """ + def read(self, length: int) -> bytes: + """ + Daten ueber das Netzwerk lesen. + + :param length: Anzahl der Bytes + :return: Gelesene + """ 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 piCtory Datei""" + def readpictory(self) -> bytes: + """ + Ruft die piCtory Konfiguration ab. + + :return: 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 replace_io_file""" + def readreplaceio(self) -> bytes: + """ + Ruft die replace_io Konfiguration ab. + + :return: 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 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: 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 Anzahl geschriebener bytes""" + def write(self, bytebuff: bytes) -> int: + """ + Daten ueber das Netzwerk schreiben. + + :param bytebuff: Bytes zum schreiben + :return: 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 / (IP, Port) - @param autorefresh Wenn True, alle Devices zu autorefresh hinzufuegen - @param monitoring In- und Outputs werden gelesen, niemals geschrieben - @param syncoutputs Aktuell gesetzte Outputs vom Prozessabbild einlesen - @param simulator 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 / (IP, Port) + :param autorefresh: Wenn True, alle Devices zu autorefresh hinzufuegen + :param monitoring: In- und Outputs werden gelesen, niemals geschrieben + :param syncoutputs: Aktuell gesetzte Outputs vom Prozessabbild einlesen + :param simulator: 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(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 der replace io daten""" + def _get_cpreplaceio(self) -> ConfigParser: + """ + Laed die replace_io Konfiguration ueber das Netzwerk. + :return: 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 . - @return der piCtory Konfiguration""" + def get_jconfigrsc(self) -> dict: + """ + Laedt die piCotry Konfiguration und erstellt ein . + + :return: 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 / (IP, Port) - @param deviceselection Positionsnummer oder Devicename - @see #RevPiNetIO.__init__ RevPiNetIO.__init__(...) - + :param address: IP-Adresse / (IP, Port) + :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 / (IP, Port) - @param virtdev Virtuelles Device oder mehrere als - @see #RevPiModIO.__init__ RevPiModIO.__init__(...) - + :param address: IP-Adresse / (IP, Port) + :param virtdev: Virtuelles Device oder mehrere als + :ref: :func:`RevPiModIO.__init__()` """ # Parent mit monitoring=False und simulator=True laden super().__init__( diff --git a/revpimodio2/summary.py b/revpimodio2/summary.py index 361f5fa..91c7165 100644 --- a/revpimodio2/summary.py +++ b/revpimodio2/summary.py @@ -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"]