Mit der 3rd Edition der IEC 61131-3 wurde das Prinzip der Namespaces (Namensräume) eingeführt. Namespaces gruppieren Elemente wie Variablen, Bausteine, Datentypen und Bibliotheken in zusammengehörige Einheiten. Ein Element wird dadurch nicht mehr nur durch seinen Namen, sondern auch durch seinen zugehörigen Namespace benannt.
Mit diesem Thema hatte ich mich bereits Ende 2010 beschäftigt. Damals noch unter CoDeSys V3. Die folgenden Beispiele wurden mit TwinCAT 3 erstellt und stellen die verschiedenen Bereiche vor, in denen Namespaces zum Tragen kommen.
Bibliotheken
Bezeichner von Funktionsblöcken, Funktionen und Datentypen müssen eindeutig sein. Das betrifft auch die Elemente, die aus Bibliotheken in ein Projekt eingebunden werden. Sollen mehrere SPS-Bibliotheken von unterschiedlichen Quellen zum Einsatz kommen, können aber genau hier Namenskonflikte auftreten. Um dieses zu vermeiden, wurde meistens vom Entwickler der Bibliothek vor jedem POU- oder Datentypbezeichner ein Prefix gestellt (z.B. MC_MoveAbsolute). Hiermit wird versucht, die Wahrscheinlichkeit eines Namenkonflikts möglichst gering zu halten.
Bei TwinCAT 3 wird dieses Problem mit Namespaces gelöst. Bei jedem SPS-Projekt, und somit auch bei SPS-Bibliotheken, kann ein Namespace bei den Projektinformationen definiert werden. Groß- und Kleinschreibung wird nicht berücksichtigt.
Sehr häufig entspricht der Namespace einer Bibliothek auch dem Namen der Bibliothek (in diesem Beispiel ist dieses nicht der Fall). Bleibt das Feld für den Namespace leer, so wird als Namespace der Name der Bibliothek (das Feld Title) benutzt.
Werden zu einem SPS-Projekt Bibliotheken hinzugefügt, in denen Elemente gleich benannt worden sind, so kann mit Hilfe der Namespaces dieser Namenskonflikt aufgelöst werden.
Beispiel:
Zwei SPS-Bibliotheken enthalten jeweils den Baustein FB_Foo. Diese beiden Bibliotheken werden einem Projekt hinzugefügt. Der Versuch, eine Instanz eines dieser Bausteine anzulegen führt zwangsläufig zu einer Fehlermeldung. Für den Compiler ist es nicht eindeutig, aus welcher Bibliothek er den Baustein nehmen soll.
Wird der Namespace der jeweiligen Bibliothek zusätzlich angegeben, so wird dieser Namenskonflikt aufgelöst.
Ohne die Angabe eines Namespace wird erst im lokalen SPS-Projekt nach dem jeweiligen Element gesucht. Somit lässt sich ein POU FB_Foo instanziieren, welcher im aktuellen SPS-Projekt enthalten ist. Ein in den Projektinformationen angegebener Namespace, ist hierbei ohne Bedeutung.
PROGRAM MAIN VAR fbFoo1 : MyNamespace01.FB_Foo; fbFoo2 : MyNamespace02.FB_Foo; fbFoo3 : FB_Foo; END_VAR
Attribut init_namespace
Hilfreich ist das Attribut init_namespace. Wird eine Variable vom Typ STRING oder WSTRING mit diesem Attribut dekoriert, so wird diese Variable mit dem Namen des aktuellen Namespace initialisiert.
FUNCTION_BLOCK PUBLIC FB_Foo VAR_INPUT END_VAR VAR_OUTPUT {attribute 'init_namespace'} sNamespace : STRING; END_VAR VAR END_VAR
Somit kann zur Laufzeit der jeweilige Namespace ermittelt werden:
Beispiel 1 (TwinCAT 3.1) auf GitHub
Namespace anpassen
Der Namespace einer Bibliothek wird auch bei den Eigenschaften der Referenz angezeigt:
Sollte der Fall eintreten, dass auch der Namespace nicht eindeutig ist, so kann dieser in den Eigenschaften der Referenz angepasst werden.
Dadurch ergibt sich auch die Möglichkeit, verschiedene Versionen der gleichen Bibliothek in ein Projekt einzubinden.
Sofern der Namespace angepasst wurde, so kann gezielt auf die gewünschten Elemente der Bibliothek zugegriffen werden.
PROGRAM MAIN VAR eAdsErr01 : Tc2_System_A.E_AdsErr; eAdsErr02 : Tc2_System_B.E_AdsErr; END_VAR
Obwohl in beiden SPS-Bibliotheken der Enumerator E_AdsErr gleich aufgebaut ist, handelt es sich um zwei verschiedene Datentypen. Das bedeutet, dass in dem oberen Beispiel die Variablen eAdsErr01 und eAdsErr02 nicht miteinander kompatibel sind.
eAdsErr01 := eAdsErr02;
Diese Zuweisung wird vom Compiler mit der folgenden Fehlermeldung ablehnt:
Implicit conversion from one enumeration type (E_ADSERR (tc2_system, 3.4.14.0 (beckhoff automation gmbh))) to another (E_ADSERR (tc2_system, 3.4.13.0 (beckhoff automation gmbh)))
Global Variable List
Eine Globale Variable List (GVL) dient zum Deklarieren von globalen Variablen. Der Name der GVL muss in einem Projekt eindeutig sein, wobei Groß- und Kleinschreibung nicht von Bedeutung ist. Jede GVL ist ein in sich abgeschlossener Bereich (Namespace), in dem, unabhängig von anderen GVLs, globale Variablen deklariert werden.
Im folgenden Beispiel sind drei Variablen deklariert. Alle drei Variablen haben den Namen var01, wovon zwei in getrennten GVLs (GVL01 und GVL02) deklariert werden und eine lokal in MAIN.
Um den Zugriff auf die Variablen wieder eindeutig zu machen, muss zusätzlich der Name der GVL vor den Variablennamen, getrennt durch einen Punkt, gestellt werden.
GVL01.var01 := 1; // global variable in GVL01 GVL02.var01 := 2; // global variable in GVL02 var01 := 3; // local variable in MAIN
Existiert die Variable var01 nur in einer GVL, so kann die Angabe der GVL auch entfallen. Gibt es allerdings eine lokale Variable mit dem gleichen Namen, so muss der Name der GVL wieder angegeben werden. Alternativ kann vor dem Variablennamen auch nur ein Punkt gestellt werden, welches den global Namespace kennzeichnet.
.var01 := 1; // global variable var01 := 3; // local variable
Der Punkt vor dem Variablennamen gibt an, dass auf die globale Variable mit dem Namen var01 zugegriffen werden soll. In diesem Fall darf die Variable aber auch nur in einer GVL existieren.
Attribut qualified_only
Der Name der GVL sollte immer mit angegeben werden, auch dann, wenn es nicht unbedingt notwendig ist. Befindet sich in der GVL das Attribut qualified_only, so wird eine Fehlermeldung ausgegeben, wenn bei einem Zugriff auf eine der Variablen die Angabe der GVL fehlt.
{attribute 'qualified_only'} VAR_GLOBAL var01 : INT; var02 : INT; END_VAR
Wird auf eine der globalen Variablen zugegriffen, so muss der Name der GVL mit angegeben werden. Das Programm wird dadurch lesbarer und Seiteneffekte zwischen lokalen und globalen Variablen können besser vermieden werden.
Beispiel 2 (TwinCAT 3.1) auf GitHub
GVLs in Bibliotheken
Befindet sich eine GVL in einer Bibliothek, so kann neben den Namen der GVL auch noch der Namespace der Bibliothek genutzt werden.
In dem folgenden Beispiel wurden drei Variablen mit den Namen var01 an verschiedenen Stellen deklariert:
– In der GVL01 einer Bibliothek mit dem Namespace MyNamespace01
– In der GVL01 des lokalen SPS-Projektes
– Im lokalen POU
Für den Zugriff auf die jeweilige Variable kann, neben den Namen der GVL, noch zusätzlich der Namespace der Bibliothek benutzt werden:
MyNamespace01.GVL01.var01 := 1; // global variable of the GVL inside the library GVL01.var01 := 2; // global variable of the local GVL var01 := 3; // local variable
Wird in der lokalen GVL das Attribut qualified_only nicht verwendet, so kann auf die Angabe der GVL verzichtet werden:
MyNamespace01.GVL01.var01 := 1; // global variable of the GVL inside the library .var01 := 2; // global variable of the local GVL var01 := 3; // local variable
Auch kann auf die Angabe der GVL der Bibliothek verzichtet werden, wenn dort das Attribut qualified_only nicht zum Einsatz kommt.
MyNamespace01.var01 := 1; // global variable of the GVL inside the library .var01 := 2; // global variable of the local GVL var01 := 3; // local variable
Wären die Namen der GVLs eindeutig, so könnte auch der Namespace entfallen:
GVL01.var01 := 1; // global variable of the GVL of the library .var01 := 2; // global variable of the local GVL var01 := 3; // local variable
Die letzten Beispiele verdeutlichen, das durch den Wegfall der Namespaces die Lesbarkeit des Programms abnimmt. Es ist schwerer zu erkennen, wo die referenzierten Variablen deklariert wurden. Daher sollte in jeder GVL das Attribut qualified_only enthalten sein und bei der Verwendung von Elementen aus Bibliotheken der Namespace benutzt werden.
Auch bei der Definition von FBs die von einem FBs aus einer Bibliothek erben, als auch bei der Verwendung von Interfaces aus Bibliotheken sollte der Namespace angegeben werden:
FUNCTION_BLOCK FB_MyFoo EXTENDS MyNamespace01.FB_Foo IMPLEMENTS MyNamespace01.I_Foo VAR_INPUT END_VAR VAR_OUTPUT END_VAR VAR END_VAR
Der Dialog zum Anlegen von neuen FBs bietet hierzu entsprechende Unterstützung an:
Beispiel 3 (TwinCAT 3.1) auf GitHub
Enumerationen
Sofern auf die Werte einer Enumeration zugegriffen wird, kann der entsprechende Wert direkt benutzt werden. Um einen eindeutigen Zugriff auf eine Enumerations-Konstante zu gewährleisten, kann zusätzlich noch der Typ-Name hinzugefügt werden.
TYPE E_Foo01 : ( eNoError := 0, eErrorA := 1, eErrorB := 2 ); END_TYPE PROGRAM MAIN VAR eFoo01 : E_Foo01; END_VAR eFoo01 := eErrorA; eFoo01 := E_Foo01.eErrorA;
Ist ein Bezeichner für eine Enumerations-Konstante in verschiedenen Typ-Definitionen enthalten, so ist der Typ-Name beim Zugriff notwendig. Dadurch können Konstanten gleichen Namens in mehreren Enumerationen verwendet werden. Somit ist es nicht mehr notwendigen jede Enumerations-Konstante mit einem Prefix zu erweitern, um Namenskonflikte zu vermeiden.
TYPE E_Foo01 : ( eNoError := 0, eErrorA := 1, eErrorB := 2 ); END_TYPE TYPE E_Foo02 : ( eNoError := 0, eError1 := 1, eError2 := 2 ); END_TYPE PROGRAM MAIN VAR eFoo01 : E_Foo01; eFoo02 : E_Foo02; END_VAR eFoo01 := E_Foo01.eNoError; eFoo02 := E_Foo02.eNoError;
Befindet sich die Typ-Definition in einer Bibliothek, ist die Angabe des Namespace zusätzlich noch möglich:
eFoo01 := MyNamespace01.E_Foo01.eNoError;
Attribut qualified_only
Ähnlich wie bei den GVLs, kann durch das Attribut qualified_only die Angabe des Typ-Namens erzwungen werden:
{attribute 'qualified_only'} TYPE E_Foo01 : ( eNoError := 0, eErrorA := 1, eErrorB := 2 ); END_TYPE
Beispiel 4 (TwinCAT 3.1) auf GitHub
Attribut strict
Das Attribut strict hat keinen direkten Bezug zu Namespaces. Es kann aber ebenfalls dazu dienen ein Programm lesbarer zu machen. Deshalb soll es hier kurz vorgestellt werden.
Eine Variable vom Typ einer Enumeration wird intern wie ein INT behandelt. Somit sind mit Enumerationen arithmetische Operationen möglich oder es können Werte zugewiesen werden, die nicht durch die Enumerationen definiert wurden. Das folgende Beispiel weist der Variablen eFoo01 den Wert 3 zu. Die Enumeration E_Foo01 enthält jedoch keinen entsprechenden Wert.
eFoo01 := E_Foo01.eErrorA + 2;
Wird das Attribut strict bei der Definition einer Enumeration angegeben, so wird sichergestellt, dass Variablen von diesem Typ nur Werte annehmen können, die durch die Enumeration auch definiert worden sind.
{attribute 'strict'} TYPE E_Foo01 : ( eNoError := 0, eErrorA := 1, eErrorB := 2 ); END_TYPE
Durch das Attribut strict sind keine Operationen mehr erlaubt, die Werte zur Folge haben, die nicht durch die Enumeration definiert worden sind. Dieses betrifft folgende Operationen:
Zuweisung von Konstanten die nicht durch die Enumeration definiert wurden
eFoo01 := 5;
Arithmetische Operationen mit Werten der Enumeration
eFoo01 := E_Foo01.eNoError + E_Foo01.eErrorA;
Zuweisen von Variablen die nicht vom Typ der Enumeration sind
nVar := 1; // variable nVar is a INT eFoo01 := nVar;
Zusammenfassung
Namespaces bieten einen guten Mechanismus um Namenskonflikte zu vermeiden. Entsprechende Prefixe in den Bezeichnern von Funktionen und Datentypen sind somit nicht mehr notwendig. Die Elementnamen werden dadurch kompakter, der Quelltext besser lesbar.
One thought on “IEC 61131-3: Namensräume”