Kommunikation

Orientierung

Motivation

Moderne industrielle Kommunikation verwendet Ethernet basierte Protokolle. Aus der Vielzahl der verfügbaren Standards hat sich über die letzten Jahre das Protokoll OPC UA etabliert. Dieses Protokoll entwickelte sich aus dem Vorgänger OPC. Da das Protokoll immer mehr Bedeutung und weitere Verbreitung findet, ist es unabdingbar, sich mit dem Protokoll auseinandersetzen. Die weite Verbreitung hat ebenso den Vorteil, dass für viele Programmiersprachen bereits fertige Bibliotheken existieren.

Lernvoraussetzung

  • Kentnisse einer Programmiersprache sind notwendig (C/C++, Python, ...).
  • Kentnisse über objektorientiertes Programmieren sind empfohlen, jedoch nicht notwendig.

Lernergebnisse


Nach der Durchführung der Übung sind Sie in der Lage…
  • … grundlegend mit dem industriellen Kommunikationsprotokoll OPC UA umzugehen.
  • … auszulesen, welche Informationen auf einem Server verfügbar sind.
  • … einzelne Informationspunkte gezielt auszulesen.
  • … sich bei Änderung von ausgewählten Daten benachrichtigen zu lassen.
  • … Funktionen auf dem entfernten Server ausführen zu lassen.
  • Die Grundlagen im nächsten Abschnitt
  • OPC Unified Architecture von Mahnke, Leitner & Damm

Wegweiser durch die Übung

Für die Übung benötigt man zwischen 90 und 120 Minuten. Die konkrete Dauer hängt hier vom individuellen Lernfortschritt ab.

Folgende Aktivitäten/Tätigkeiten werden von Ihnen erwartet:

  • Im Baustein Grundlagen
    • erarbeiten der notwendigen Theorie.
  • Im Baustein Übung
    • lesen Sie sich die Rahmenbedingungen der Übung durch.
    • finden Sie bereits fertigen Quellcode in Python als Musterlösung.
    • finden Sie wiederkehrende Herausforderungen bei der Kommunikation mit entfernten Rechnern.
    • finden Sie verschiedene Fragen um Ihr Wissen abzuprüfen.
  • Im Baustein Anwendung
    • bekommen Sie verschiedene Aufgaben gestellt.
  • Im Baustein Reflexion
    • erhalten Sie eine kurze Zusammenfassung der Ergebnisse

Grundlagen

Diese Übung soll die Verwendung eines industriellen Kommunikationsprotokolls näherbringen. Dafür wird das Protokoll OPC UA verwendet. OPC UA bietet viele Möglichkeiten und kann über verschiedene Programmiersprachen angesprochen werden. Folgende Möglichkeiten bietet das Protokoll:

  • Datenzugriff auf einem Server
  • Ausführen von Prozeduren auf dem Server
  • Erstellen von Benachrichtigungen bei Datenänderung
Mögliche Programmiersprachen:

Sie sind nach der Übung in der Lage, mit dem Kommunikationsprotokoll OPC UA einfache Datentransporte zu realisieren. Dadurch können Sie mit einigen der angebotenen Online Live Übungen interagieren.

Nodes und Node-Klassen

OPC UA arbeitet mit Datenpunkten, auch Nodes genannt. Jeder Node besitzt eine Reihe an Attributen und basierend auf der Nodeklasse noch weitere Informationen:
  • Node-ID
  • Node-Klasse
  • Browse-Name
  • Anzeigename
  • Beschreibung
Die Node-ID ist für jeden Node einzigartig! Damit ist ein Node eindeutig durch die Node-ID beschrieben. Sobald man eine Node-ID besitzt, kann man damit alle Informationen abfragen. Die Node-Klasse gibt an, welche Informationen tatsächlich verfügbar sind. Auf die Node-Klassen und die dadurch zur Verfügung gestellten Informationen wird später eingegangen. Der Browse-Name ist ein Pfadelement, welches zum pfadbasierten Suchen (dem sogenannten Browsen) verwendet wird. Der Anzeigename und die Beschreibung sind Elemente, die einen lokalisierten Text beinhalten. Zusätzlich zu den Attributen besitzt ein Node eine Liste an Referenzen. Referenzen werden im nächsten Abschnitt behandelt.

Node-IDs

Eine Node-ID besitzt zwei Elemente, einen Namespace und einen Identifier. Der Namespace ist ein numerischer Index, der durch einen URI vorgegeben wird. Der URI bezeichnet eine Institution, die für die Vergabe der Identifier zuständig ist. Der Index ist nichts anderes, als die Position der URI in einer auf dem Server abgelegten Liste. Der Namespace-Index 0 bezeichnet dabei die OPC UA Foundation. Es gibt vier verschiedene Arten von Identifiern:
  • Numeric
    Ein numeric Identifier bezeichnet einfach eine vorzeichenlose Zahl. Bei der Serealisierung dieses Identifiers wird die Bezeichnung i verwendet.
  • String
    Bei einem String handelt es sich um eine lesbare Zeichenfolge. Bei der Serealisierung wird der Identifier mit s gekennzeichnet.
  • Bytestring (Opaque)
    Ein Opaque Identifier ist ein Identifier, der nach einem Namespace-Spezifischen Schema erstellt worden ist. Dieser wird zur Serealisierung in einen Base64-String umgewandelt. Dies ist wichtig, da im Bytestring auch Zeichen vorkommen können, die nicht darstellbar sind. Der verwendete Buchstabe zur Kenzeichnung ist b.
  • GUID
    Eine GUID ist eine 16-Byte hexadezimal Zahl, die im folgenden Format abgebildet ist: uint32-uint16-uint16-uint8[2]-uint8[6] Ein Beispiel für eine GUID ist 9EAA3455-92B1-C9C6-89C8-2CA23723B2EB.
Nodes werden generell in der folgenden Form serealisiert: ns=<namespaceIndex>;<identifiertype>=<identifier>. Mit diesem Syntax werden auch alle Nodes in Angaben spezifiziert. Bis jetzt wurden schon der Namespace-Index und der Identifier behandelt. Demnach ist jetzt noch der Identifier-Typ relevant.
Indentifier Identifier-Type Beispiel
Numeric i ns=0;i=85
String s ns=1;s=Temperatur
Bytestring b ns=1;b=M/RbKBsRVkePCePcx24oRA==
GUID g ns=1;g=9EAA3455-92B1-C9C6-89C8-2CA23723B2EB

Node-Klassen

Die Nodeklassen definieren, welche zusätzlichen Informationen verfügbar sind.
  • Object
    Ein Object, zu Deutsch Objekt, wird verwendet, um Systeme oder Subsysteme zu erzeugen. Dabei ist es egal, ob es sich um physische oder virtuelle Systeme handelt, die abgebildet werden. Folgende Attribute kommen zu den Grundattributen hinzu:
    • Event-Notifier
      Damit wird signalisiert, welche Events überwacht werden können.
  • Variable
    Eine Variable wird verwendet, um einem Objekt einen Inhalt zu geben. Da eine Variable einen gewissen Wert repräsentiert, besitzt eine Variable sehr viel mehr Attribute:
    • DataType
      Dies ist die Node-ID des Datentypes.
    • ValueRank
      Der ValueRank gibt an, ob die Daten ein einzelner Wert, oder eine Liste an Werten ist. Genauer gesagt, können die Werte als Skalar, eindimensionales Array oder mehrdimensionales Array vorliegen.
    • ArrayDimensions
      Dieses Element gibt an, wie groß die einzelnen Dimensionen sind. Dieses Attribut ist nur relevant, wenn die Daten nicht als Skalar vorliegen.
    • Value
      Dies ist der Wert, der vom Node verwaltet wird. Wie dieses Element zu interpretieren ist, hängt von den Attributen DataType, ValueRank und ArrayDimensions ab.
    • Historizing
      Dieses Element gibt an, ob auf dem Server eine Historie der Daten gespeichert wird.
    • AccessLevel
      Dieses Element gibt an, welche Berechtigungen im Allgemeinen gewährt werden. Mögliche Berechtigungen sind Lesen und Schreiben des aktuellen Wertes und Lesen/Schreiben der Historie.
    • UserAccessLevel
      Dieses Element gibt an, welche Berechtigungen der aktuelle Benutzer besitzt.
    • MinimumSamplingInterval
      Dieses Element gibt an, wie schnell der Server Änderungen eines Wertes erfassen kann. Dies ist vor allem dann interessant, wenn der Server die Information erst von einem Subsystem anfordern muss.
  • Methods
    Methods, zu Deutsch Methoden, sind Funktionen die auf dem Server ausgeführt werden können.
    • Executable
      Dieses Attribut gibt an, ob die Funktion aktuell ausführbar ist.
    • UserExecutable
      Dieses Attribut gibt an, ob die Funktion vom aktuellen Benutzer ausführbar ist.
  • ReferenceType
    ReferenceTypes, zu Deutsch Referenztypen, beschreiben die Relation zwischen Elementen auf dem Server. Eine genaue Erklärung zu Referenzen findet sich im nächsten Abschnitt.
    • isAbstract
      Dieses Attribut gibt an, ob es sich bei dem Typ um ein Organisationselement der Hierarchie handelt, oder ob er tatsächlich verwendet werden kann. Sollte der Typ abstrakt sein, so kann er nicht erzeugt werden, aber sehr wohl verwendet werden um bestehende Elemente eines Subtyps ansprechen zu können. Auf Referenzen wird im späteren Abschnitt noch näher eingegangen.
    • symmetric
      Dieses Attribut gibt an, ob die Referenz symmetrisch ist. Wenn sie symmetrisch ist, dann besitzt die Referenz in die umgekehrte Richtung die selbe Bedeutung. Ein Beispiel für eine nicht symmetrische Referenz ist die Eltern-Kind Beziehung; so ist zB.: A der Elternteil von B und B ist das Kind von A. Ein Beispiel für eine symmetrische Referenz ist die Geschwister-Beziehung.
    • inverseName
      Wenn die Referenz nicht symmetrisch ist, gibt dieses Element den Namen der umgekehrten Referenz an. Beim vorhergehenden Beispiel war der Elternteil von der reguläre Name der Referenz und ist das Kind von der Name der umgekehrten Referenz.
  • DataType
    Ein DataType, zu Deutsch Datentyp, definiert wie die Daten zu interpretieren sind. Die von der OPC UA Foundation vorgegebenen Datentypen sind in Namespace 0 zu finden und stellen grundlegende Datentypen dar. Neben den grundlegenden Datentypen ist es auch möglich eigene Datentypen zu definieren. Es ist jedoch sinnvoller, Variablen-Typen und Objekt-Typen zu definieren.
    • isAbstract
      Dieses Attribut gibt, genau wie bei den Referenzen, an, ob es sich um ein organisatorisches Element handelt oder nicht.
  • VariableType
    Ein VariableType, zu Deutsch Typdefinition einer Variable, wird verwendet um den Datenelementen eine Bedeutung zu geben. Dies ist vor allem interessant, wenn man sich ein Beispiel ansieht. Beispiel: Ein Node vom BaseVariableType, einem generischen Variablen-Type, ist auf dem Server verfügbar. Der Node repräsentiert eine Temperatur. Jetzt ist unbekannt, ob es sich bei der Temperatur um Celsius, Kelvin oder Fahrenheit handelt. Wenn jedoch ein Celsius-Typ definiert und verwendet wird, ist sofort klar, wie die Daten zu interpretieren sind. Alternativ kann auch ein Temperatur-Datentyp definiert werden, bei dem in der Beschreibung die Interpretation deklariert wird. Dies hat jedoch den Nachteil, dass nicht sofort aus dem Variablentyp klar ist, wie die Daten zu interpretieren sind.
    • DataType
      Dies ist die Node-ID des Datentypes.
    • ValueRank
      Der ValueRank gibt an, ob die Daten ein einzelner Wert, oder eine Liste an Werten ist. Genauer gesagt, können die Werte als Skalar, eindimensionales Array oder mehrdimensionales Array vorliegen.
    • ArrayDimensions
      Dieses Element gibt an, wie groß die einzelnen Dimensionen sind. Dieses Attribut ist nur relevant, wenn die Daten nicht als Skalar vorliegen.
    • Value
      Dies ist der Wert, der vom Node verwaltet wird. Wie dieses Element zu interpretieren ist, hängt von den Attributen DataType, ValueRank und ArrayDimensions ab.
    • isAbstract
      Dieses Attribut gibt, genau wie bei den Referenzen, an, ob es sich um ein organisatorisches Element handelt oder nicht.
  • ObjectType
    Ein ObjectType, zu Deutsch Typdefinition eines Objektes, wird verwendet um sicherzustellen, dass ein Objekt bei der Erstellung gewisse Attribute besitzt. In objektorientierten Programmiersprachen würde man dieses Konstrukt auch Klasse nennen.
    • isAbstract
      Dieses Attribut gibt, genau wie bei den Referenzen, an, ob es sich um ein organisatorisches Element handelt oder nicht.
  • View
    Eine View, zu Deutsch Ansicht, bezeichnet eine Liste an Nodes, die für eine bestimmte Aufgabe interessant sein können. So kann zum Beispiel eine Ansicht definiert werden, die für die Wartung interessant ist, und eine andere Ansicht, die für den regulären Gebrauch interessant ist. Damit muss nicht der Gesamte Adressraum des Servers abgesucht werden.
    • ContainsNoLoop
      Dieses Attribut gibt an, ob die dargestellten Nodes eine zirkuläre Hierarchie aufbauen, wenn man den Referenzen folgt.
    • EventNotifier
      Dieses Attribut gibt an, ob man über diese Ansicht Events generieren lassen kann (zB bei Datenänderung) und ob die Event-Historie lesbar und änderbar ist.

Referenzen und Datentypen

Referenzen

Referenzen werden verwendet, um Nodes miteinander in Beziehung zu setzen. Eine Referenz besitzt immer drei Bestandteile:
  • Quelle
  • Ziel
  • Referenztyp
In OPC UA werden abstrakte und konkrete Referenztypen zur Verfügung gestellt. Abstrakte Referenztypen lassen sich nicht erzeugen, können jedoch beim Abfragen von Verbindungen verwendet werden. In der folgenden Abbildung finden sich die vom Standard definierten Referenztypen.
Reference types of opc ua
Abstrakte Referenzen sind durch spezielle Kennzeichnung hervorgehoben. Das Praktische an abstrakten Referenzen ist, dass sie als organisatorische Elemente dienen und bei der Filterung von Ergebnissen verwendet werden können. Im Folgenden werden ausgewählte Referenztypen behandelt.
  • Organizes
    Diese Referenz wird verwendet, wenn Objekte oder Variablen in einem Ordner zusammengefasst werden. Ein Ordner ist ein Objekt, das nur zum Zusammenfassen von Informationspunkten dient. Ordner werden zum Beispiel im Standard-Informations-Modell verwendet, um alle Objekte, alle Typdefinitionen und alle Views zusammenzufassen.
  • HasProperty
    Diese Referenz wird verwendet, um Eigenschaften von Objekten und Variablen abzubilden. Properties selbst können keine Quellen von weiteren Referenzen sein. Ein Beispiel für eine Eigenschaft wäre bei einer Datei das Änderungsdatum.
  • HasComponent
    Diese Referenz wird verwendet um Inhalte eines Objektes zu definieren. Elemente die über diese Referenzart erstellt worden sind, können weiterhin die Quelle für neue Referenzen sein.
  • HasOrderedComponent
    Diese Referenz wird verwendet, um einem Objekt Methoden zu geben.

Datentypen

OPC UA definiert eine Reihe an Datentypen, die verwendet werden können. Zusätzlich können noch eigene Datentypen definiert werden. Beispiele dafür sind Aufzählungen; dafür gibt es den Basistyp Enumeration. Ebenso ist es möglich, eigene Strukturen zu definieren. Hier muss jedoch im Regelfall auf beiden Seiten, also auf Server und Client, eine Funktion existieren, die die Daten interpretieren kann. Die gesamte Hierarchie der Datentypen ist in der folgenden Abbildung dargestellt.
Data types of opc ua
Im Folgenden werden einige Datentypen behandelt:
NodeId
ExpandedNodeId
NodeIDs werden verwendet um Datenelemente eines Servers zu bezeichnen. Darauf wurde im vorhergehenden Abschnitt bereits eingegangen. NodeIDs werden verwendet um lokale Nodes eines Servers zu identifizieren. ExpandedNodeIDs erlauben es, Nodes eines anderen Servers zu referenzieren. So wäre es zum Beispiel möglich alle Variablen und Objektdefinitionen auf einem zentralen Server zu hinterlegen und diese von anderen Servern referenzieren zu lassen.
DateTime
DateTime wird verwendet, um einen Zeitstempel zu erzeugen.
DataValue
Dies bezeichnet einen Datenwert mit einem Statuscode und einem Zeitstempel.
QualifiedName
Ein QualifiedName bezeichnet einen String, der mit einem Namespace-Index versehen ist. Der Browse-Name eines Nodes ist ein Beispiel für einen QualifiedName. Dieser wird in den Angaben dieser Übung wie folgt gekennzeichnet: qn=<namespaceIndex>;<name>.
Number
Dieser Datentyp fasst alle Zahlen zusammen. Die Art der Zahl ist hierbei irrelevant.
Integer
UInteger
Hierbei handelt es sich um vorzeichenbehaftete oder vorzeichenfreie Ganzzahlen. Hier gibt die Zahl an, wie viele Bits zur Verfügung stehen.
Float
Double
Dies sind Gleitkommazahlen mit einfacher und doppelter Genauigkeit.
DiagnosticInfo
Dieser Datentyp bietet detaillierte Informationen in einem Fehlerfall.
Boolean
Dieser Datentyp repräsentiert einen Wert der Wahr oder Falsch sein kann.
String
ByteString
Strings und ByteStrings sind Zeichenketten. Während Strings Zeichenketten aus druckbaren Zeichen bestehen, bestehen ByteStrings aus allen möglichen Zeichen. Da ein ByteString sehr häufig Zeichen beinhaltet, die nicht dargestellt werden können, werden sie Base64 kodiert dargestellt.
LocalizedText
Ein LocalizedText ist eine Struktur, die Informationen in einer gewissen Sprache kommuniziert. Ein LocalizedText besitzt damit zwei Elemente, die Sprache in der die Information vorliegt und die Information selbst.
GUID
Ein 16 Byte Wert, der als globale einzigartige Identifikationsnummer verwendet wird. Der Wert setzt sich aus einem 32-Bit Wert, zwei 16-Bit Werten und acht 8-Bit Werten.
XMLElement
Ein XML Element ist ein Element der Extensible Markup Language.

Discovery und Browsing

OPC UA definiert ebenso Möglichkeiten Server zu finden und Informationen auszulesen. Discovery definiert, wie ein Server gefunden werden kann. Browsing beschreibt das Erhalten von Informationen über die verfügbaren Nodes.

Discovery

OPC bietet die Möglichkeit mit Discovery-Servern zu arbeiten. Ein Discovery-Server ist ein Server, bei dem sich andere Server registrieren können. Dadurch muss man nur eine/n Teilnehmer/in im Netzwerk kennen, um alle verfügbaren Server zu finden.
Browsing
Beim Browsen werden folgende Funktionen geboten:
  • Browse
    Beim Browsen gibt man einen Node an und erhält alle Nodes, die den angegebenen Node als Quelle einer Referenz haben. Man kann zusätzlich noch nach Referenzen und Node-Klassen filtern. An diesem Punkt sind die zuvor erwähnten abstrakten Referenzen hilfreich, da dadurch eine Vielzahl an konkreten Referenzen abgedeckt werden kann. Folgende Informationen werden immer für jeden Node zurückgegeben:
    • Referenz
    • Node-ID
    • Browsename
    • Displayname
    • Node-Klasse
    Ausgewählte vordefinierte Elemente des Standard-Informations-Modells von OPC-UA sind:
    • qn=0;Root
      • qn=0;Objects
        • qn=0;Server
          • qn=0;NamespaceArray
          • qn=0;ServerArray
      • qn=0;Types
        • qn=0;DataTypes
        • qn=0;ObjectTypes
        • qn=0;ReferennceTypes
        • qn=0;VariableTypes
      • qn=0;Views
  • BrowseNext
    Diese Funktion kann verwendet werden, wenn nicht alle Nodes zurückgegeben werden konnten. Dies kann vor allem dann hilfreich sein, wenn mehr Nodes gefunden wurden, als auf einmal gesendet werden können.
  • TranslateBrowsePathToNodeID
    Mit diesem Service kann ein Browsepath in eine Node-ID umgewandelt werden. Dies ist gerade dann interessant, wenn man zwar weiß wie der Datenpunkt heißt, aber die Node-ID unbekannt ist. Da man aber die Node-ID braucht um Nodes ansprechen zu können, ist dieses Service unumgänglich. Der Browsepath ist eine Liste von Browsenames der einzelnen Nodes. Diese Funktionalität ist vor allem dann wichtig, wenn die Elemente auf dem Server semantisch oder durch eine Ontologie beschrieben werden können.
  • Register/Unregister Nodes
    Mit dieser Funktionalität kann man einem Server mitteilen, dass die aktuelle Verbindung vermehrt auf die angegebenen Nodes zugreifen wird. Dabei geschehen zwei Sachen. Erstens wird die Menge an Information reduziert, die übertragen werden muss. Dies geschieht dadurch, dass der Client eine numerische Node-ID bekommt. Diese ist am einfachsten zu übertragen. Zweitens optimiert der Server den Zugriff auf den Datenpunkt selbst, damit das Schreiben auf oder Ausführen von dem Node effizienter geschieht. Unregister teilt dem Server mit, dass der Node nicht mehr vermehrt benötigt wird. Diese Funktionalität sollte nicht verwendet werden, wenn der Node nur ausgelesen wird, da es dazu effizientere Mechanismen gibt. Auf diese Mechanismen wird im Punkt Read/Write eingegangen.
Die folgende Abbildung zeigt ein rekursives Browsing-Ergebnis, ausgehend vom Objekt-Folder und ohne Elemente, die in anderen Namespaces außer 0 und 1 existieren. Browsing-Result starting from the object folder

Read/Write

Read

Es können im Regelfall alle Attribute eines Nodes gelesen werden. Beim Lesen des Wertes eines Nodes, dem Value-Attribute, werden die Zugriffsrechte in Betracht gezogen.

Write

Im Regelfall kann nur das Value-Attribut eines Nodes beschrieben werden. Dabei werden auch die Zugriffsrechte der aktuellen Verbindung in Betracht gezogen.

Subscriptions und Monitored Items

Subscriptions und Monitored Items sind ein Mechanismus, um Nodes zu überwachen und bei Bedarf die neue Information an den Client zu schicken. Zuerst legt ein Client eine Subscription an und definiert wie die Informationen transportiert werden. Dabei kann er festlegen welche Priorität die Daten haben, wieviele Informationen auf einmal übertragen werden können und in welchem Intervall die Daten gesendet werden. Danach können, basierend auf der Subscription, Monitored Items angelegt werden. Hier wird angegeben, welche Node-ID überwacht werden soll, in welchem Intervall der Node auf Änderungen überprüft werden soll und wann eine Änderung gesendet werden soll. Dabei kann man sich entscheiden, ob man informiert werden möchte wenn sich der Status ändert, wenn sich Status und Wert ändern, oder wenn sich Status, Wert und Zeitstempel ändern. Sollten bestimmte Werte überwacht werden, so ist es sehr empfehlenswert diesen Mechanismus zu wählen.

Methoden

Methoden sind Funktionen, die auf dem Server hinterlegt sind und über das Netzwerk ausgeführt werden können. Methoden können Eingabeparameter, Ausgabewerte, beide, oder keine von beiden besitzen. Eingabeparameter werden verwendet, um dem Methodenaufruf zusätzliche Information mitzugeben. Ausgabewerte werden verwendet, um Information zurück zur/zum Aufrufenden zu übermitteln. Man kann einfach ermitteln, ob eine Methode Argumente besitzt, indem man den Methoden-Node durchsucht. Besitzt er eine Referenz auf einen Node mit dem Browsename qn=0;InputArguments, so benötigt die Methode Eingabeparaemter. Besitzt er eine Referenz auf einen Node mit dem Browsename qn=0;OutputArguments, so liefert die Methode Ausgabewerte.

Sicherheit

OPC UA verwendet zur Kommunikation verschiedene Sicherheitsmechanismen. Wenn ein Client Kontakt zu einem Server aufnimmt, wird zuerst eine Verbindung initiiert. Basierend auf der Verbindung wird ein sicherer Kanal erzeugt. Erst nachdem der sichere Kanal erzeugt worden ist, wird eine Session angelegt. Die gesamte Kommunikation zwischen Client und Server wird in einer bestehenden Session abgehandelt. Die Session selbst ist nicht an einen speziellen sicheren Kanal gebunden. Dadurch bleibt eine Session erhalten, auch wenn der sichere Kanal erneuert wird. Bei der Anmeldung gibt es verschiedene Authentifizierungsarten:
  • Anonym
  • Benutzername/Passwort
  • X509v3 Zertifikat
  • WS-SecurityToken
Neben den Authentifizierungsmethoden gibt es aktuell drei definierte Verschlüsselungsarten, mit denen die Kommunikation verschlüsselt wird:
  • None - Die Kommunikation wird nicht verschlüsselt.
  • Basic128RSA15
  • Basic256

Architektur

OPC UA kann auf verschiedene Arten verwendet werden. Im Folgenden sind die häufigsten Konfigurationen zu finden.

Server/Client

Dabei stellt ein Rechner, der Server, Informationen und Methoden zur Verfügung. Andere Rechner, die Clients, verbinden sich zum Server, um die Informationen zu erhalten. Dies ist die häufigste Art, auf die das Protokoll verwendet wird.
Server/Client

Chained Server

Bei der Chained-Server Architektur erfolgt die Kommunikation über einen Zwischenserver. Der Zwischenserver besitzt einen eingebetteten Client, der mit dem eigentlichen Server kommuniziert. Dieser Server kann zum Beispiel zur Protokollkonvertierung verwendet werden. Ein weiterer Einsatzfall wäre, dass der Zwischenserver von außerhalb des Produktionsnetzwerkes erreichbar ist, der eigentliche Server jedoch nicht. Der Server mit dem in den angebotenen Online-Übungen interagiert wird, ist ein Chained Server. Jeder angeschlossene Server besitzt in diesem Fall einen eigenene Namespace. Der Namespace des Servers beinhaltet lediglich organisatorische Elemente, einen Ordner für jeden angeschlossenen Server und einen Indikator ob der Server online ist.
Chained Server

Server to Server

Die Server to Server Kommunikation wird eingesetzt, wenn Daten auf mehreren Servern zur Verfügung stehen müssen. In diesem Szenario besitzt jeder Server einen eingebetteten Client, um dem zweiten Server Änderungen mitteilen zu können. Dies ist vor allem dann ein Vorteil, wenn Server-Redundanzen notwendig sind.
Server to Server

Aggregating Server

Ein Aggregating Server bindet mehrere Server an und verarbeitet die gewonnenen Informationen weiter bevor er diese weitergibt. Während der Chained Server lediglich die Daten der angebundenen Server weitergibt, konzentriert der Aggregating Server die Informationen, die aus den Daten gewonnen werden können.
Aggregating Server

Übung

Im Folgenden werden verschiedene Beispielaufgaben mit fertigen Python-Lösungen präsentiert. Dazu werden die einzelnen Schritte des Python-Codes und die Funktionen der Python OPC-UA Bibliothek erklärt. Die Installation der Bibliothek wird HIER erklärt, während die Funktionen der Bibliothek HIER nachgeschlagen werden können.

Verbindung ohne Zugangsdaten aufbauen

Dieses Beispiel demonstriert lediglich, wie man sich mit einem OPC-UA-Server mithilfe der vorgegebenen Funktionen verbindet. Es soll eine Verbindung zum Server der FH Technikum Wien hergestellt werden. Folgende Eckdaten sind zum Verbinden notwendig:
  • Server: engine.ie.technikum-wien.at
  • Port: 4840
Als erster Schritt müssen die nötigen Python-Bibliotheken importiert werden. Die Bibliothek time wird später verwendet, um ein Beenden des Python-Programms zu vermeiden. Da nur eine Verbindung hergestellt werden soll, wird nur die Klasse Client von opcua importiert. Danach werden der Servername und der Port in den Variablen HOST bzw. PORT gespeichert. Nun kann eine Instanz der Klasse Client erstellt werden. Dazu muss der URL des Servers als String übergeben werden.
#!/usr/bin/env python3
"""
This example shows how to connect to the server without credentials
"""
import time
from opcua import Client

if __name__ == "__main__":
    # Create a OPC-UA Client with the following specification:
    # Server: engine.ie.technikum-wien.at
    # Port: 4840
    HOST = "engine.ie.technikum-wien.at"
    PORT = 4840
    # This syntax does not provide credentials and we are logging in anonymously
    # at the specified server. The Server has been configured to allow anonymous
    # users to read every datapoint. All other actions are prohibited.
    CLIENT = Client("opc.tcp://{}:{}/".format(HOST, PORT))
Im nächsten Schritt wird versucht, eine Verbindung mit der Methode connect() herzustellen. Wird dieser Vorgang erfolgreich abgeschlossen, wird eine Endlosschleife aufgerufen, die durch einen KeyboardInterrupt abgebrochen wird. Das Programm kann deshalb durch Eingabe von Control-C bzw. Delete vom User beendet werden. Um eine erfolgreiche Verbindung mit dem Server zu demonstrieren, wird eine entsprechende Information in jedem Schleifendurchlauf ausgegeben.
#!/usr/bin/env python3
"""
This example shows how to connect to the server without credentials
"""
import time
from opcua import Client

if __name__ == "__main__":
    # Create a OPC-UA Client with the following specification:
    # Server: engine.ie.technikum-wien.at
    # Port: 4840
    HOST = "engine.ie.technikum-wien.at"
    PORT = 4840
    # This syntax does not provide credentials and we are logging in anonymously
    # at the specified server. The Server has been configured to allow anonymous
    # users to read every datapoint. All other actions are prohibited.
    CLIENT = Client("opc.tcp://{}:{}/".format(HOST, PORT))
    try:
        # Connect to the Server
        # This function connects to the server. If the connection has already
        # been implemented, nothing is done. If the connection terminated the
        # function reconnects
        CLIENT.connect()
        try:
            # Loop
            while True:
                print("connected with server")
                # Sleep
                time.sleep(1)
        except KeyboardInterrupt:
            pass
    finally:
        # Disconnect
        CLIENT.disconnect()
Die Ausgabe der Beispiellösung ist in der folgenden Abbildung dargestellt.
solution output connection without user credentials

Verbindung mit Zugangsdaten aufbauen

Dieses Beispiel unterscheidet sich vom vorherigen nur dadurch, dass eine Verbindung mit Userdaten hergestellt werden soll. Zusätzlich zu den Daten der vorhergehenden Aufgabe kommen folgende Informationen:
  • Server: engine.ie.technikum-wien.at
  • Port: 4840
  • Username: student
  • Password: student
Der Programmcode beinhaltet bereits das gesamte Programm, da nur die Zugangsdaten für die Serververbindung hinzugefügt werden müssen. Diese Daten werden in den Variablen USER und PASS gespeichert. Die korrekte URL benötigt die Logininformationen durch ein : getrennt, was in der Variable CREDENTIALS gespeichert wird. Anschließend kann wiederum die Instanz Client erstellt werden, wobei im Gegensatz zum vorigen Beispiel, die Loginformationen vor dem Servernamen übergeben werden. Die Userinformationen werden wie bei E-Mailadressen durch das @ Symbol vom Servernamen getrennt. Der weitere Programm ist ident zu jenem aus dem vorigen Beispiel.
#!/usr/bin/env python3
"""
This example shows how to connect to the server with credentials
"""
import time
from opcua import Client

if __name__ == "__main__":
    # Create a OPC-UA Client with the following specification:
    # Server: engine.ie.technikum-wien.at
    # Port: 4840
    HOST = "engine.ie.technikum-wien.at"
    PORT = 4840
    USER = "student"
    PASS = "student"
    CREDENTIALS = "{}:{}".format(USER, PASS)
    # This syntax does provide credentials.
    CLIENT = Client("opc.tcp://{}@{}:{}/".format(CREDENTIALS, HOST, PORT))
    try:
        # Connect to the Server
        CLIENT.connect()
        try:
            # Loop
            while True:
                print("connected with server")
                # Sleep
                time.sleep(1)
        except KeyboardInterrupt:
            pass
    finally:
        # Disconnect
        CLIENT.disconnect()
Die Ausgabe der Beispiellösung ist in der folgenden Abbildung dargestellt.
solution output connection with user credentials

Browsen

In der folgenden Aufgabe geht es darum, die Nodes des Hauptservers und des Beispiel-Servers zu ermitteln. Dafür werden zwei Namespaces benötigt, der Namespace des Hauptservers selbst und der Namespace des Beispiel-Servers. Es sollen die verfügbaren Namespaces angezeigt sowie die Elemente des Server-Namespaces und die Elemente des Beispiel-Namespaces ausgegeben werden. Zusätzlich zu den Daten der vorhergehenden Aufgabe kommen folgende Informationen:
  • Namespace des Hauptservers: opc.tcp://engine.ie.technikum-wien.at
  • Namespace des Beispielservers: opc.tcp://engine.ie.technikum-wien.at/OPCExamples
  • NodeID des Beispielservers: ns=1;s=OPC Examples
Da nun das Programm nach Ausgabe der nötigen Informationen automatisch beendet werden soll, wird die Bibliothek time nicht benötigt. Die Verbindung mit dem Server erfolgt wieder wie gewohnt, wobei es reicht, sich anonym am Server anzumelden. Um zu demonstrieren, welche Namespaces am Hauptserver verfügbar sind, werden diese im ersten Schritt ausgegeben. Die Namespaces des Servers werden durch die Methode get_namespace_array() als Python-Liste in der Variable NAMESPACES gespeichert. Dies ermöglicht es, die einzelnen Namespaces des Servers in der nachfolgenden for-Schleife auszugeben.
#!/usr/bin/env python3
"""
This example shows how to connect to the server without credentials
"""
from opcua import Client
from opcua import ua

if __name__ == "__main__":
    # Create a OPC-UA Client with the following specification:
    # Server: engine.ie.technikum-wien.at
    # Port: 4840
    HOST = "engine.ie.technikum-wien.at"
    PORT = 4840
    # This syntax does not provide credentials and we are logging in anonymously
    # at the specified server. The Server has been configured to allow anonymous
    # users to read every datapoint. All other actions are prohibited.
    CLIENT = Client("opc.tcp://{}:{}/".format(HOST, PORT))
    try:
        # Connect to the Server
        # This function connects to the server. If the connection has already
        # been implemented, nothing is done. If the connection terminated the
        # function reconnects
        CLIENT.connect()
        # There are multiple Namespaces on the Server and we want to query them
        NAMESPACES = CLIENT.get_namespace_array()
        print(">Namespaces")
        for namespace in NAMESPACES:
            print(" {}".format(namespace))
Im nächsten Schritt werden die Nodes des Hauptservers ermittelt. Beim Node des Hauptservers handelt es ich um die Klasse Object, das durch die Methode get_objects_node() in der Variable STARTNODE gespeichert wird. Um nun die Referenznodes von diesem Object zu erhalten, wird die Methode get_children() aufgerufen. Diese Methode gibt eine Python-Liste mit den Nodes zurück, die der Variable NODES zugewiesen werden. Dadurch sind nun alle Nodes des Hauptservers ermittelt. Im Anschluss werden die Nodeinformationen durch eine for-Schleife über die Liste NODES ausgegeben. Die Ausgabeinformationen beinhalten die NodeID, den Browse-Namen sowie den Namen der NodeClass. Um den Namen der NodeClass zu erhalten, muss zuvor eine ua.NodeClass erstellt werden, weshalb zu Beginn des Programms die Bibliothekt ua importiert wurde. Ein ua.NodeClass Objekt benötigt die NodeClass des Nodes, welche durch die Methode get_node_class() in der letzten Zeile des Programmcodes ermittelt wird.
#!/usr/bin/env python3
"""
This example shows how to connect to the server without credentials
"""
from opcua import Client
from opcua import ua

if __name__ == "__main__":
    # Create a OPC-UA Client with the following specification:
    # Server: engine.ie.technikum-wien.at
    # Port: 4840
    HOST = "engine.ie.technikum-wien.at"
    PORT = 4840
    # This syntax does not provide credentials and we are logging in anonymously
    # at the specified server. The Server has been configured to allow anonymous
    # users to read every datapoint. All other actions are prohibited.
    CLIENT = Client("opc.tcp://{}:{}/".format(HOST, PORT))
    try:
        # Connect to the Server
        # This function connects to the server. If the connection has already
        # been implemented, nothing is done. If the connection terminated the
        # function reconnects
        CLIENT.connect()
        # There are multiple Namespaces on the Server and we want to query them
        NAMESPACES = CLIENT.get_namespace_array()
        print(">Namespaces")
        for namespace in NAMESPACES:
            print(" {}".format(namespace))
        # In order to browse, we need some node as a starting point.
        STARTNODE = CLIENT.get_objects_node()
        # The method get_children returns all children of the Node in question
        NODES = STARTNODE.get_children()
        print(">Browsing")
        print(" Children of: {}".format(STARTNODE.nodeid.to_string()))
        for node in NODES:
            print(" {} -- {} -- {}".format(
                node.nodeid.to_string(),                  # Short form of NodeID
                node.get_browse_name().to_string(),       # Short form
                ua.NodeClass(node.get_node_class()).name))# Name of the nodeclass
Nun müssen noch die Nodes des Beispielservers ermittelt werden. Da die NodeID bekannt ist, kann der Node einfach über die Methode get_node(NodeID) der Variable STARTNODE zugewiesen werden. Beachten Sie, dass der Node des Hauptservers dadurch nicht mehr gespeichert ist. Die weitere Vorgehensweise entspricht jener des vorherigen Punktes, wobei nach Durchlauf des Programms die Verbindung zum Server durch die Methode disconnect() getrennt wird. Der gesamte Programmcode zur Lösung dieses Beispiels lautet:
#!/usr/bin/env python3
"""
This example shows how to connect to the server without credentials
"""
from opcua import Client
from opcua import ua

if __name__ == "__main__":
    # Create a OPC-UA Client with the following specification:
    # Server: engine.ie.technikum-wien.at
    # Port: 4840
    HOST = "engine.ie.technikum-wien.at"
    PORT = 4840
    # This syntax does not provide credentials and we are logging in anonymously
    # at the specified server. The Server has been configured to allow anonymous
    # users to read every datapoint. All other actions are prohibited.
    CLIENT = Client("opc.tcp://{}:{}/".format(HOST, PORT))
    try:
        # Connect to the Server
        # This function connects to the server. If the connection has already
        # been implemented, nothing is done. If the connection terminated the
        # function reconnects
        CLIENT.connect()
        # There are multiple Namespaces on the Server and we want to query them
        NAMESPACES = CLIENT.get_namespace_array()
        print(">Namespaces")
        for namespace in NAMESPACES:
            print(" {}".format(namespace))
        # In order to browse, we need some node as a starting point.
        STARTNODE = CLIENT.get_objects_node()
        # The method get_children returns all children of the Node in question
        NODES = STARTNODE.get_children()
        print(">Browsing")
        print(" Children of: {}".format(STARTNODE.nodeid.to_string()))
        for node in NODES:
            print(" {} -- {} -- {}".format(
                node.nodeid.to_string(),                  # Short form of NodeID
                node.get_browse_name().to_string(),       # Short form
                ua.NodeClass(node.get_node_class()).name))# Name of the nodeclass
        # As we know the Node-ID, we can access the node directly
        STARTNODE = CLIENT.get_node("ns=1;s=OPC Examples")
        NODES = STARTNODE.get_children()
        print(" Children of: {}".format(STARTNODE.nodeid.to_string()))
        for node in NODES:
            print(" {} -- {} -- {}".format(
                node.nodeid.to_string(),                  # Short form of NodeID
                node.get_browse_name().to_string(),       # Short form
                ua.NodeClass(node.get_node_class()).name))# Name of the nodeclass
    finally:
        # Disconnect
        CLIENT.disconnect()
Die Ausgabe der Beispiellösung ist in der folgenden Abbildung dargestellt.
solution output browse example

Lesen

Im Folgenden geht es darum, Informationen von einem Node zu erhalten. Die Aufgabe sieht vor, dass die angegebene Variable ExampleVariable ausgelesen wird. Folgende Eckdaten sind zum Verbinden notwendig:
  • Namespace des Nodes: opc.tcp://engine.ie.technikum-wien.at/OPCExamples
  • Node ID Part des auszulesenden Nodes: s=ExampleVariable
Wiederum erfolgt eine anonyme Verbindung zum Server der FH Technikum Wien. Mithilfe des Namespaces wird der Namespace-Index durch die Methode get_namespace_index() ermittelt und in der Variable IDX gespeichert. Dazu muss der URL des Namespaces der Methode übergeben werden. Nun wird eine Node-Instanz (NODE) durch die bereits bekannte Methode get_node(NodeID) erzeugt. Die NodeID muss der Methode als Parameter übergeben werden, wobei der zuvor ermittelte Namespace-Index benötigt wird. Im Anschluss kann der Wert des Nodes einfach durch die Methode get_value() in der Variable VALUE gespeichert und ausgegeben werden.
#!/usr/bin/env python3
"""
This example shows how to read a variable from the server
"""
from opcua import Client

if __name__ == "__main__":
    # Create a OPC-UA Client with the following specification:
    # Server: engine.ie.technikum-wien.at
    # Port: 4840
    HOST = "engine.ie.technikum-wien.at"
    PORT = 4840
    # This syntax does not provide credentials and we are logging in anonymously
    # at the specified server. The Server has been configured to allow anonymous
    # users to read every datapoint. All other actions are prohibited.
    CLIENT = Client("opc.tcp://{}:{}/".format(HOST, PORT))
    try:
        # Connect to the Server
        CLIENT.connect()
        IDX = CLIENT.get_namespace_index("opc.tcp://engine.ie.technikum-wien.at/OPCExamples")
        # Get Node from the client based on the NodeID
        NODE = CLIENT.get_node("ns={};s=ExampleVariable".format(IDX))
        # Read the current value
        VALUE = NODE.get_value()
        # Print the Information
        print("{}: {}".format(NODE, VALUE))
    finally:
        # Disconnect
        CLIENT.disconnect()
Die Ausgabe der Beispiellösung ist in der folgenden Abbildung dargestellt.
solution output read example

Subscriben

Diese Aufgabe sieht vor, dass eine Subscription mit einem Monitored Item für den entsprechenden Node erzeugt wird. Dies ist die bevorzugte Variante, um Daten periodisch zu empfangen. Bei jeder Wertänderung der Variable ExampleVariable soll dies ausgegeben werden.
  • Namespace des Nodes: opc.tcp://engine.ie.technikum-wien.at/OPCExamples
  • Node ID Part des auszulesenden Nodes: s=ExampleVariable
Im ersten Schritt wird die Klasse SubscriptionHandler erstellt. Der SubscriptionHandler hat die Aufgabe einen Node zu beobachten. Die Funktionen der Klasse übernehmen folgende Aufgaben:
  • __init__(self)
    Nachdem eine Instanz der Klasse erstellt wird, wird ein Zähler initiiert. Der Zähler soll die Anzahl der Wertänderungen speichern.
  • __del__(self)
    Wenn eine Instanz der Klasse gelöscht wird, wird mitgeteilt wie viele Wertänderungen registriert wurden.
  • datachange_notification(self, node, value, _)
    Diese Funktion wird aufgerufen, wenn eine Wertänderung des übergebenen node registriert wird. Der Parameter value beinhaltet den aktuellen Wert des Nodes. Der Parameter _ speichert die Rohdaten der Benachrichtigung, der hier aber nicht weiter beachtet werden muss. In diesem Beispiel soll der aktuelle Wert ausgegeben werden. Zusätzlich wird noch der Zähler bei jeder Wertänderung um eins erhöht. Der Funktionsname datachange_notification ist durch die OPC-UA Bibliothek vorgegeben und wird bei Wertänderungen automatisch aufgerufen, wenn dies gewollt ist. Wie die Benachrichtigung bei Wertänderungen definiert wird, wird im nächsten Abschnitt erklärt.
#!/usr/bin/env python3
"""
This example shows how to create a subscription to a variable
"""
import time
from opcua import Client

class SubscriptionHandler(object): # pylint: disable=too-few-public-methods
    """
    Subsrciption handler.
    """
    def __init__(self):
        self.counter = 0

    def __del__(self):
        print("Observed {} datachanges".format(self.counter))

    def datachange_notification(self, node, value, _):
        """
        Function that's called when data is changed

        Args:
            node: The node id that had a datachange
            value: The current value
            _: The raw data of the notification

        Returns:
            None
        """
        print("{}: {}".format(node, value))
        self.counter = self.counter + 1
Nun kann mit der Implementierung des Hauptprogramms begonnen werden. Wie gewohnt wird eine anonyme Verbindung mit dem Server hergestellt und der zu überwachende Node in der Variable NODE gespeichert. Im nächsten Schritt wird die Instanz HANDLER der zuvor erstellten Klasse SubscriptionHandler erzeugt. Um eine Subscription zu erstellen, wird die Methode create_subscription(500, HANDLER) auf die Client-Variable angewandt. Der erste Parameter, 500, gibt die Updatezeit in Millisekunden an. Also wird in diesem Beispiel zweimal pro Sekunde überprüft, ob eine Wertänderung vorliegt. Zusätzlich muss der Methode der gewünschte Handler übergeben werden. Dadurch wird der Variable SUB die zuvor definierte Klasse SubscriptionHandler übergeben. Nun muss der Subscription noch mitgeteilt werden, was überwacht werden soll. Dies erfolgt durch die Methode subscribe_data_change(NODE), der der zu überwachende Node übergeben wird. Um ein Beenden des Programms an dieser Stelle zu vermeiden, wird noch eine Endlosschleife aufgerufen, die durch die Usereingabe Control-C oder Delete beendet werden kann. Ist dies der Fall, soll am Ende des Programms die Anzahl der registrierten Wertänderung ausgegeben werden. Dies erfolgt einfach durch das Löschen der Instanz durch die Methode delete().
#!/usr/bin/env python3
"""
This example shows how to create a subscription to a variable
"""
import time
from opcua import Client

class SubscriptionHandler(object): # pylint: disable=too-few-public-methods
    """
    Subsrciption handler.
    """
    def __init__(self):
        self.counter = 0

    def __del__(self):
        print("Observed {} datachanges".format(self.counter))

    def datachange_notification(self, node, value, _):
        """
        Function that's called when data is changed

        Args:
            node: The node id that had a datachange
            value: The current value
            _: The raw data of the notification

        Returns:
            None
        """
        print("{}: {}".format(node, value))
        self.counter = self.counter + 1

if __name__ == "__main__":
    CLIENT = Client("opc.tcp://engine.ie.technikum-wien.at:4840/")
    try:
        CLIENT.connect()
        IDX = CLIENT.get_namespace_index("opc.tcp://engine.ie.technikum-wien.at/OPCExamples")
        NODE = CLIENT.get_node("ns={};s=ExampleVariable".format(IDX))
        # Create a handler to work with the updated data
        HANDLER = SubscriptionHandler()
        # Create a subscription
        SUB = CLIENT.create_subscription(500, HANDLER)
        # Connect NodeIDs to the Subscription
        SUB.subscribe_data_change(NODE)
        try:
            # Loop -- only needed so the program doesn't stop right away
            while True:
                # Sleep
                time.sleep(1)
        except KeyboardInterrupt:
            pass
        SUB.delete()
    finally:
        CLIENT.disconnect()
Die Ausgabe der Beispiellösung ist in der folgenden Abbildung dargestellt.
solution output subscription example

Anwendung

Zugangsdaten

Folgende Informationen benötigen Sie, um auf die Übung zugreifen zu können:

  • Host: engine.ie.technikum-wien.at
  • Port: 4840
  • Username: student
  • Password: student
  • Namespace: opc.tcp://engine.ie.technikum-wien.at/OPCExercises

Browsing

Finden Sie heraus, welche Datenpunkte über den Node ns=1;s=OPC Exercises erreicht werden können. Ermitteln Sie die Node-IDs im angegebenen Namespace, den Anzeigenamen und die Klasse.

Lösung Es sind folgende Nodes auf dem Server verfügbar:
  • ns=3;i=119 -- Sensor 1 -- Variable
  • ns=3;i=316 -- Sensor 2 -- Variable
  • ns=3;i=317 -- Sensor 3 -- Variable
  • ns=3;i=318 -- Test-Method -- Method
  • ns=3;i=320 -- Convert String -- Method
Dies kann man mit folgender Beispiel-Lösung ermitteln:
#!/usr/bin/env python3
"""
This is an example-solution
"""
from opcua import Client

if __name__ == "__main__":
    CLIENT = Client("opc.tcp://student:student@engine.ie.technikum-wien.at:4840/")
    try:
        CLIENT.connect()
        IDX = CLIENT.get_namespace_index("opc.tcp://engine.ie.technikum-wien.at/OPCExercises")
        print("Namespace Index of Exercises: {}".format(IDX))
        print("Nodes in ns={}:".format(IDX))
        NODE = CLIENT.get_node("ns=1;s=OPC Exercises")
        NODES = NODE.get_children()
        for node in NODES:
            if node.nodeid.NamespaceIndex != IDX: # Required as we get all children of the node
                continue
            print("{} -- {} -- {}".format(
                node.nodeid.to_string(),             # Get a short printable NodeId
                node.get_display_name().to_string(), # Get a short printable Display Name
                node.get_node_class().name))         # Get a short readbale nodeclass
    finally:
        CLIENT.disconnect()

Auslesen der Variablen

Geben Sie die Beschreibung der in der vorhergehenden Aufgabe ermittelten Nodes aus. Lesen Sie - bei den Variablen - über 10 Sekunden im 1-Sekunden-Intervall die Werte der einzelnen Nodes und geben Sie die ermittelten Werte aus.

Lösung Folgende Beschreibungen und Variablenwerte existieren für die einzelnen Nodes. Beachten Sie, dass es sich bei den Werten der Variablen um zufällige Werte handelt und somit variieren.
ns=3;i=119
The sensor reading of an analogue sensor with a range of 4 to 20 mA.
Values: [4.251206457216534, 4.289866899644205, 4.895675476539514, 4.9098246865436375, 5.420688888428982, 5.88977037896077, 6.270517873818549, 7.583171043162588, 8.316529417004658, 8.831909074195444]
ns=3;i=316
The sensor reading of an analogue sensor with a range of -10 to 10 V.
Values: [-2.9375731073518523, -1.7124065024717305, -0.7790857976530966, 0.14439170175511734, 1.2071837310511713, 2.121364889782571, 3.1357310629583113, 4.92733921542168, 5.738893638260663, 6.580431443068198]
ns=3;i=317
The sensor reading of a digital sensor with either true or false
Values: [True, True, True, False, False, True, True, True, False, False]
ns=3;i=318
Method that returns how many times it has been called so far
ns=3;i=320
Method that converts a string. Specifically it flips upper and lowercase
Dies kann man mit folgender Beispiel-Lösung ermitteln:
#!/usr/bin/env python3
"""
This is an example-solution
"""
import time
from opcua import Client
from opcua import ua

if __name__ == "__main__":
    CLIENT = Client("opc.tcp://student:student@engine.ie.technikum-wien.at:4840/")
    try:
        CLIENT.connect()
        IDX = CLIENT.get_namespace_index("opc.tcp://engine.ie.technikum-wien.at/OPCExercises")
        print("Namespace Index of Exercises: {}".format(IDX))
        NODE = CLIENT.get_node("ns=1;s=OPC Exercises")
        NODES = NODE.get_children()
        for node in NODES:
            if node.nodeid.NamespaceIndex != IDX:
                continue
            print("{}: {}".format(node.nodeid.to_string(), node.get_description().to_string()))
            if node.get_node_class() == ua.NodeClass.Variable:
                values = []
                for i in range(10):
                    values.append(node.get_value())
                    time.sleep(1)
                print("Values: {}".format(values))
    finally:
        CLIENT.disconnect()

Methoden analysieren

Ermitteln Sie, welche Parameter die von Ihnen ermittelten Methoden benötigen und welche Ergebnisse die Methoden liefern.

Lösung Die Funktionen besitzen folgende Parameter:
ns=3;i=318
  • Output
    1. Calls -- Number of calls to the function
    2. Message -- The number of calls as a printable string message
ns=3;i=320
  • Input
    1. imput -- Input string
  • Output
    1. output -- Output string with flipped case
Dies kann man mit folgender Beispiel-Lösung ermitteln:
#!/usr/bin/env python3
"""
This is an example solution
"""
from opcua import Client
from opcua import ua

if __name__ == "__main__":
    CLIENT = Client("opc.tcp://student:student@engine.ie.technikum-wien.at:4840/")
    try:
        CLIENT.connect()
        IDX = CLIENT.get_namespace_index("opc.tcp://engine.ie.technikum-wien.at/OPCExercises")
        print("Namespace Index of Exercises: {}".format(IDX))
        PARENT = CLIENT.get_node("ns=1;s=OPC Exercises")
        for child in PARENT.get_children():
            if child.nodeid.NamespaceIndex != IDX:
                continue
            if child.get_node_class() != ua.NodeClass.Method:
                continue
            print("{}: {}".format(child.nodeid.to_string(), child.get_display_name().to_string()))
            for arg in child.get_children():
                print(arg.get_display_name().to_string())
                for param in arg.get_value():
                    print("{}: {}".format(param.Name, param.Description.to_string()))
    finally:
        CLIENT.disconnect()

Methoden aufrufen

Führen Sie alle Methoden aus, die Sie ermittelt haben.

Lösung Dies kann man mit folgender Beispiel-Lösung erreichen:
#!/usr/bin/env python3
"""
This is an example solution
"""
from opcua import Client

if __name__ == "__main__":
    CLIENT = Client("opc.tcp://student:student@engine.ie.technikum-wien.at:4840/")
    try:
        CLIENT.connect()
        IDX = CLIENT.get_namespace_index("opc.tcp://engine.ie.technikum-wien.at/OPCExercises")
        print("Namespace Index of Exercises: {}".format(IDX))
        METHOD1 = CLIENT.get_node("ns={};i=318".format(IDX))
        METHOD2 = CLIENT.get_node("ns={};i=320".format(IDX))
        PARENT = CLIENT.get_node("ns=1;s=OPC Exercises")
        for idx, res in enumerate(PARENT.call_method(METHOD1)):
            print("{}: {}".format(idx, res))
        RES = PARENT.call_method(METHOD2, "Test String")
        print(RES)
    finally:
        CLIENT.disconnect()

Subscription

Subscriben Sie zu den Variablen für 10 Sekunden und lassen Sie sich die empfangenen Werte nachher anzeigen.

Lösung Dies kann man mit folgender Beispiel-Lösung erreichen:
#!/usr/bin/env python3
"""
This is an example-solution
"""
import time
from opcua import Client
from opcua import ua

class SubscriptionHandler(object): # pylint: disable=too-few-public-methods
    """
    Subsrciption handler.
    """
    def __init__(self):
        self.counter = 0
        self.items = {}

    def __del__(self):
        print("Observed {} datachanges".format(self.counter))
        for key, value in self.items.items():
            print("{} ({} values): {}".format(key.nodeid.to_string(), len(value), value))

    def datachange_notification(self, node, value, _):
        """
        Function that's called when data is changed

        Args:
            node: The node id that had a datachange
            value: The current value
            _: The raw data of the notification

        Returns:
            None
        """
        if node not in self.items.keys():
            self.items[node] = []
        self.items[node].append(value)
        self.counter = self.counter + 1

if __name__ == "__main__":
    CLIENT = Client("opc.tcp://student:student@engine.ie.technikum-wien.at:4840/")
    try:
        CLIENT.connect()
        IDX = CLIENT.get_namespace_index("opc.tcp://engine.ie.technikum-wien.at/OPCExercises")
        print("Namespace Index of Exercises: {}".format(IDX))
        NODE = CLIENT.get_node("ns=1;s=OPC Exercises")
        NODES = NODE.get_children()
        HANDLER = SubscriptionHandler()
        SUB = CLIENT.create_subscription(500, HANDLER)
        for child in NODES:
            if child.nodeid.NamespaceIndex != IDX:
                continue
            if child.get_node_class() == ua.NodeClass(2):
                print("{}: {}".format(
                    child.nodeid.to_string(),
                    child.get_description().to_string()))
                SUB.subscribe_data_change(child)
        print("Setup done; sleep 10 Seconds")
        time.sleep(10)
        SUB.delete()
    finally:
        CLIENT.disconnect()

Reflexion

Nach der Durchführung der Onlineübung sind Sie in der Lage mit den Laboren zu interagieren. Sie haben die Grundlagen des Kommunikationsprotokolles kennen gelernt und einige der wichtigsten Funktionen bereits ausprobiert. Sie sind damit in der Lage …
  • … grundlegend mit dem industriellen Kommunikationsprotokoll OPC UA umzugehen.
  • … auszulesen, welche Informationen auf einem Server verfügbar sind.
  • … einzelne Informationspunkte gezielt auszulesen.
  • … sich bei Änderung von ausgewählten Daten benachrichtigen zu lassen.
  • … Funktionen auf dem entfernten Server ausführen zu lassen.

Selbstevaluierung

Welches Attribut ist für jeden Node einzigartig?

Die NodeID ist für jeden Node einzigartig!

Aus welchen Elementen setzt sich die NodeID zusammen?

Die NodeID besitzt zwei Elemente: Namespace und Identifier

Welche Informationen beinhaltet das NodeID-Beispiel ns=2;b=aGVsbG8gd29ybGQ=?

Es kann einerseits der Namespace-Index 2 entnommen werden. Andererseits ist der Identifier angegeben, welcher im Base64-Format vorliegt. Das Base64-Format wird durch den Identifier-Typ b angegeben.
Anmerkung: Der Inhalt des Identifiers kann in diesem Fall durch Online-Tools in einen für Menschen lesbaren Text umgewandelt werden!

Wofür wird die Node-Klasse VariableType verwendet?

VariableType wird verwendet, um einem Datenelement eine Bedeutung zu geben. Dadurch kann das Datenelement interpretiert werden.

Welche Bestandteile besitzt eine Referenz?

Eine Referenz besitzt immer folgende drei Bestandteile: Quelle, Ziel und Referenztyp

Wozu werden Subscriptions verwendet?

Subscriptions dienen in Kombination mit Monitored Items zur Überwachung von Nodes. Dadurch können Daten von ausgewählten Nodes periodisch und unter definierten Umständen Änderungen des Nodes gesendet werden.

Welche Verschlüsselungsarten existieren, um die Kommunikation zwischen Server und Client zu verschlüsseln?

  • None - Die Kommunikation wird nicht verschlüsselt.
  • Basic128RSA15
  • Basic256

Take-Home-Messages

  • Jeder Node wird durch seine NodeID eindeutig beschreiben
  • Durch Node-Klasse eiens Nodes wird definiert, welche zusätzlichen Informationen verfügbar sind
  • Die Node-Klasse Object dient zur Erzeugung weiterer (Sub-)Systeme
  • Die Node-Klasse Variable wird verwendet, um einem Node einen Inhalt zu geben
  • Die Node-Klasse Methods beschreibt auf dem Server ausführbare Funktionen
  • Referenzen werden verwendet, um Nodes miteinander in Beziehung zu setzen
  • Subscriptions und Monitored Items dienen zur Überwachung von Nodes

Weiterführende Themengebiete

  • Client-Side Node Management
  • Server-Implementierung
  • Datenmodellierung

This site uses cookies

Cookies help us to improve your browsing experience and analyze site traffic. Find out more on how we use cookies.
I accept cookies
I refuse cookies