Module erstellen

Werfen Sie einen Blick auf unsere verfügbaren Module , um ein Gefühl dafür zu bekommen, wie ein Modul aussehen kann.

Beginnen Sie mit der Erstellung einer Python-Datei in Ihrem entsprechenden Entwicklungsordner (siehe: Entwicklungsumgebung einrichten).


Klassen und Attribute

Module müssen von entsprechenden Klassen abgeleitet werden. Die übergeordneten Module sind AbstractInputModule, AbstractVariableModule, AbstractTagModule, AbstractOutputModule, oder AbstractProcessorModule. Die abgeleiteten Klassen müssen wie folgt benannt werden InputModule, VariableModule, TagModule, OutputModule, oder ProcessorModule!

Jedes Modul benötigt die folgenden Attribute:

__version__: int = 1
"""The auto-generated version of the module."""

class ProcessorModule(AbstractProcessorModule):
    version: int = __version__
    """The version of the module."""
    public: bool = True
    """Is this module public?"""
    description: str = "A short but powerful description of the module."
    """A short description."""
    author: str = "Colin Reiff"
    """The author name."""
    email: str = "colin.reiff@collectu.de"
    """The email address of the author."""
    deprecated: bool = False
    """Is this module deprecated."""
    third_party_requirements: list[str] = []
    """Define your requirements here."""
    can_be_buffer: bool = False  # Only output modules!
    """If True, the child has to implement 'store_buffer_data' and 'get_buffer_data'."""
    field_requirements: list[str] = []  # Only preprocessor and output modules!
    """Data validation requirements for the fields dict."""
    tag_requirements: list[str] = []  # Only preprocessor and output modules!
    """Data validation requirements for the tags dict."""

Die verfügbaren Attribute und Methoden sind in dem folgenden Klassendiagramm dargestellt. Nur die Methode _run ist obligatorisch. Die allgemeinen Methoden werden im Folgenden beschrieben. Die für den Modultyp spezifischen Methoden und Funktionen werden in den entsprechenden Abschnitten beschrieben.

Module Classes

Konfiguration

Die Konfiguration definiert Parameter, die vom Benutzer eingestellt werden können, um das Verhalten des Moduls zu steuern. Eine Konfiguration ist eine Datenklasse, die sich direkt unter der eigentlichen Modulklasse befindet.

Die Felder von Konfigurationen sollten immer die folgenden Attribute besitzen: description, dynamic (Default: False), und required (Default: False).

Zusätzlich kann eine Validierung (validate=) angegeben werden. Die folgenden Validierungen sind derzeit verfügbar:

OneOf Prüft, ob der Wert eine der angegebenen Möglichkeiten ist.
Range Prüft, ob der Wert zwischen einem Mindest- und einem Höchstwert liegt (exklusiv).
Regex Führt eine Überprüfung des regulären Ausdrucks mit der angegebenen regulären Ausdruckszeichenkette durch.
Beispiel
from dataclasses import dataclass, field
from models.validations import Range
from modules.base.inputs.base import AbstractVariableModule, models

class VariableModule(AbstractVariableModule):
    """
    A variable module.
    """

    @dataclass
    class Configuration(models.VariableModule):
        """
        The configuration model of the module.
        """
        key: str = field(
            metadata=dict(description="The key of the constant.",
                          dynamic=True,
                          required=True),
            default=None)  # If the value has to be defined by the user (required=True), provide None as default.
        sleep_time: float = field(
            metadata=dict(description="The interval in seconds until the data is forwarded. "
                                      "Has to be between (exclusive) 0 and 86400 s (24 h).",
                          required=False,
                          dynamic=False,
                          validate=Range(min=0, max=86400)),
            default=1)
Info
Collectu generiert automatisch die Frontend-Elemente aus dieser Definition.

Die Konfigurationsdatenklasse sollte von der entsprechenden übergeordneten Klasse (siehe unten) abgeleitet werden. Achten Sie darauf, die vordefinierten Attribute nicht zu überschreiben.

Config Classes

Verschachtelte Konfiguration

Verschachtelte Konfiguratioenen (hier Listen wie bspw. list[Threshold]) benötigen eine separate Validierung. Dies wird durch den Aufruf von NestedListClassValidation für das entsprechende Feld erreicht.Außerdem muss die Methode __post_init__ in der Konfigurationsklasse aufgerufen werden, um die validierten und deserialisierte Felder und die dynamischen Variablen zu setzen.

Beispiel
from dataclasses import dataclass, field
from models.validations import validate_module, NestedListClassValidation
from modules.base.inputs.base import AbstractVariableModule, models

@dataclass
class Field:
    """
    The configuration of a single field.
    """
    key: str = field(
        metadata=dict(description="The key of the field.",
                      required=True,
                      dynamic=True),
        default=None)
    value: str | float | int | bool | list = field(
        metadata=dict(description="The (default) value of the field.",
                      required=False,
                      dynamic=True),
        default="Your input")

    def __post_init__(self):
        validate_module(self)


class VariableModule(AbstractVariableModule):
    """
    A variable module.
    """

    @dataclass
    class Configuration(models.VariableModule):
        """
        The configuration model of the module.
        """
        title: str = field(
            metadata=dict(description="The title of the user input element.",
                          required=False),
            default="User Input")
        fields: list[Field] = field(
            metadata=dict(description="The list of fields for this user input module. Example: "
                                      '{"key": "test", "value": ["choice 1", "choice 2"]}',
                          required=False,
                          dynamic=True,
                          validate=NestedListClassValidation(child_class=Field, child_field_name="fields")),
            default_factory=list)

        def __post_init__(self):
            super().__post_init__()
            fields = []
            for field_dict in self.fields:
                fields.append(Field(**field_dict))
            setattr(self, "fields", fields)

Weitere Beispiele für verschachtelte Modulkonfigurationen sind:

  • inputs.general.user_input_1
  • processors.dashboard.threshold_1
  • processors.opcua.ads_2_opcua_server_1
  • inputs.control.ads_client_struct_1
  • inputs.control.ads_client_struct_2
  • processors.general.template_var_setter_1

Hinweis: Verschachtelte Konfigurationen werden derzeit nicht auf dem Frontend dargestellt. Benutzer müssen die verschachtelte Konfiguration als json in das entsprechende Eingabefeld eingeben.


Anforderungen von Drittanbietern

Wenn Anforderungen von Drittanbietern von einem Modul genutzt werden, listen Sie diese in third_party_requirements auf. Anschließend müssen sie mit der Methode import_third_party_requirements importiert werden.

Vergewissern Sie sich, dass die Anforderungen nicht im Widerspruch zu den globalen Anforderungen der Anwendung stehen (aufgeführt in requirements.txt). Sie müssen dieselben wie die globalen Anforderungen sein. Sie können dies überprüfen, indem Sie das Test-Framework ausführen (siehe Testen).

Beispiel
class VariableModule(AbstractVariableModule):
    """
    A variable module.
    """
    third_party_requirements: list[str] = ["redis==4.3.4"]
    """Define your requirements here."""

    ...

    @classmethod
    def import_third_party_requirements(cls) -> bool:
        """
        Import your requirements here and make them globally accessible.
        """
        try:
            global redis
            import redis
            return True
        except Exception:
            raise ImportError("Could not import required packages. Please install '{0}'."
                              .format(' '.join(map(str, cls.third_party_requirements))))
Info
Collectu prüft automatisch, ob die Anforderungen von Drittanbietern installiert sind, wenn ein Modul in einer Konfiguration verwendet wird. Wenn nicht, werden sie automatisch installiert (wenn in den Einstellungen aktiviert: AUTO_INSTALL=1).

Konfig-Daten abrufen

Die statische Methode get_config_data wird vom Frontend verwendet, um mögliche Konfigurationsoptionen abzurufen.

Beispiel
@staticmethod
def get_config_data(input_module_instance=None) -> dict[str, Any]:
    """
    Retrieve options for selected configuration parameters of this module.

    :param input_module_instance: If it is a variable or tag module, the input_module_instance is given here,
    if it is required for that module.
    :returns: A dictionary containing the parameters as key and a list of options as value.
    """
    return {"qos": [0, 1, 2],
            "data_type": ['str', 'bool', 'int', 'float', 'list/int', 'list/str', 'list/bool', 'list/float']}

Starten und Stoppen

Die Start- und Stopp-Methoden werden vom zugrunde liegenden Framework aufgerufen. Wenn während der Startphase etwas schief geht, sollte eine Ausnahme mit einer aussagekräftigen Fehlermeldung ausgelöst werden. Wenn der Benutzer ein Wiederholungsverfahren konfiguriert hat, wird die Startmethode in einem bestimmten Intervall aufgerufen, bis die Methode ohne Ausnahmen ausgeführt wird.

Die Module werden in der folgenden Reihenfolge gestartet (und in umgekehrter Reihenfolge gestoppt):

  1. Puffer-Module
  2. Ausgangsmodule
  3. Verarbeitungsmodule
  4. Eingangsmodule
  5. Tag-Module
  6. Variablen-Module

Wenn ein Modul angehalten wird, wird das Attribut self.active auf false gesetzt.


Dynamische Variablen

Wenn eine Konfigurationsvariable dynamisch sein kann (dynamic=True), müssen Sie self._dyn(self.configuration.value) aufrufen, um den Marker (z.B. ${module_id.key}) durch den tatsächlichen Wert zu ersetzen.

Wenn die dynamische Variable nicht ersetzt werden konnte, wird eine DynamicVariableException ausgelöst.

Beispiel
from modules.base.base import DynamicVariableException

try:
    field_key = self._dyn(self.configuration.key)
except DynamicVariableException as e:
    ...