PLCopen-Funktionsbausteine

Der TwinCAT OPC UA Client bietet mehrere Möglichkeiten, direkt aus der Steuerungslogik heraus mit einem oder mehreren OPC UA Servern zu kommunizieren. Zum einen gibt es ein TwinCAT-I/O-Gerät, welches eine eine einfache, Mapping-basierte Schnittstelle bietet. Zum anderen stehen durch die PLCopen genormte Funktionsbausteine zur Verfügung, über die eine Verbindung mit einem OPC UA Server direkt aus der SPS-Logik heraus initiiert werden kann. Die Handhabung dieser Bausteine sollen im Folgenden näher beschrieben werden. Dieser Artikel besteht aus den folgenden Sektionen:

Workflow

Der allgemeine Workflow bei der Verwendung der PLCopen-Funktionsbausteine lässt sich wie folgt schematisch darstellen:

PLCopen-Funktionsbausteine 1:

In der Vorbereitungsphase werden die Kommunikationsparameter eingerichtet und eine Verbindung zum Server aufgebaut. Anschliessend erfolgt die Ausführung der gewünschten Funktion (Lesen, Schreiben, Methodenaufrufe), gefolgt vom Trennen der Kommunikationsverbindung.

Bestimmung der Kommunikationsparameter

Im Allgemeinen wird ein grafischen OPC UA Client verwendet, um die Attribute eines Knotens oder Methoden zu bestimmen, die zusammen mit den SPS-Funktionsbausteinen verwendet werden müssen, z. B.:

Die folgende Dokumentation verwendet den generischen OPC UA Client UA Expert als Beispiel. Dieser Client kann über die Webseite von Unified Automation erworben werden: www.unified-automation.com.

Knoten sind durch die folgenden drei Attribute gekennzeichnet, welche die sogenannte NodeID bilden:

Diese Attribute stellen die sogenannte NodeID dar – die Darstellung eines Knotens auf einem OPC UA Server – und werden von vielen nachfolgenden Funktionsbausteinen benötigt.

Mithilfe der Software UA Expert können Sie die Attribute eines Knotens einfach bestimmen, indem Sie eine Verbindung zum OPC UA Server aufbauen und zum gewünschten Knoten browsen. Die Attribute sind dann im Attributes-Panel sichtbar, zum Beispiel:

PLCopen-Funktionsbausteine 2:

Nach der OPC‑UA‑Spezifikation kann der NamespaceIndex ein dynamisch generierter Wert sein. Daher müssen OPC UA Clients immer den entsprechenden NamespaceURI zur Auflösung des NamespaceIndex verwenden, bevor ein Knotenhandle erfasst wird.

Um den NamespaceIndex für einen NamespaceURI zu erfassen, verwenden Sie den Funktionsbaustein UA_GetNamespaceIndex. Den hierfür notwendigen NamespaceURI können Sie mithilfe von UA Expert bestimmen, indem Sie eine Verbindung zum OPC UA Server aufbauen und zum Knoten NamespaceArray browsen.

PLCopen-Funktionsbausteine 3:

Dieser Knoten enthält Informationen über alle eingetragenen Namespaces auf dem OPC UA Server.
Die entsprechenden NamespaceURIs sind im Attributes-Panel sichtbar, zum Beispiel:

PLCopen-Funktionsbausteine 4:

Im obigen Abschnitt wird beispielhaft eine NodeID gezeigt, in der der NamespaceIndex 5 ist. Nach dem in der Abbildung gezeigten NamespaceArray ist der entsprechende NamespaceURI urn://SVENG-NB04/BeckhoffAutomation/Ua/PLC1. Dieser URI kann nun für den Funktionsbaustein UA_GetNamespaceIndex verwendet werden. Der OPC UA Server stellt sicher, dass der URI immer derselbe bleibt, auch nach einem Neustart.

PLCopen-Funktionsbausteine 5:

Korrekten NamespaceIndex beachten

Da sich der gezeigte NamespaceIndex verändern kann, sollten für die spätere Nutzung mit anderen Funktionsbausteinen, z. B. UA_Read, UA_Write, zur Auflösung des korrekten NamespaceIndex immer der NamespaceURI in Kombination mit dem Funktionsbaustein UA_GetNamespaceIndex verwendet werden.

DataType

Der Datentyp eines Knotens ist erforderlich, um zu sehen, welcher SPS-Datentyp verwendet werden muss, um einen ausgelesenen Wert zuzuordnen oder um in einen Knoten zu schreiben. Mithilfe von UA Expert können Sie den Datentyp eine Knotens einfach bestimmen, indem Sie eine Verbindung zum OPC UA Server aufbauen und zum gewünschten Knoten browsen.
Der Datentyp ist dann im Attributes-Panel sichtbar, zum Beispiel:

PLCopen-Funktionsbausteine 6:

In diesem Fall ist der Datentyp (DataType) „Int16“. Dieser muss einem äquivalenten Datentyp in der SPS zugeordnet werden, z. B. „INT“.

MethodNodeID und ObjectNodeID

Beim Aufruf von Methoden aus dem OPC‑UA‑Namensraum sind zwei Identifier erforderlich, wenn der Methodenhandle unter Verwendung des Funktionsbausteins UA_MethodGetHandle erfasst wird:

Mithilfe von UA Expert können Sie beide NodeIDs einfach bestimmen, indem Sie eine Verbindung zum OPC UA Server aufbauen und zu der gewünschten Methode bzw. dem gewünschten UA-Objekt, das die Methode enthält, browsen.

Beispiel Methode M_Mul:

PLCopen-Funktionsbausteine 7:

Der Method Identifier ist dann im Attributes-Panel sichtbar.

PLCopen-Funktionsbausteine 8:

Beispiel Objekt fbMathematics:

PLCopen-Funktionsbausteine 9:

Der Object Identifier ist dann im Attributes-Panel sichtbar.

PLCopen-Funktionsbausteine 10:

Herstellen einer Verbindung

Im nachfolgenden Abschnitt wird beschrieben, wie Sie die Funktionsbausteine TcX_PLCopen_OpcUa verwenden, um eine Verbindung zu einem lokalen oder remote OPC UA Server herzustellen. Diese Verbindung kann dann verwendet werden, um weitere Funktionalitäten aufzurufen, z. B. Knoten auslesen oder schreiben, oder Methoden aufrufen.

Die folgenden Funktionsbausteine sind erforderlich, um eine Verbindung zu einem OPC UA Server herzustellen und später die Sitzung zu unterbrechen: UA_Connect, UA_Disconnect.

PLCopen-Funktionsbausteine 11:

Lesen Sie zunächst den Abschnitt Wie Kommunikationsparameter zu bestimmen sind, um bestimmte UA-Funktionalitäten besser verstehen zu können (z. B., wie NodeIdentifier bestimmt werden können).

Der Funktionsbaustein UA_Connect erfordert die folgenden Informationen, um eine Verbindung zu einem lokalen oder remote OPC UA Server herstellen zu können:

Die Server URL besteht grundsätzlich aus einem Präfix, einem Hostnamen und einem Port. Das Präfix beschreibt das OPC‑UA‑Transportprotokoll, das für die Verbindung verwendet werden sollte, z. B. “opc.tcp://” für eine binäre TCP-Verbindung (Standard). Der Hostname bzw. IP-Adressenteil beschreibt die Adressinformationen des OPC‑UA‑Zielservers, z. B. „192.168.1.1“ oder „CX-12345“. Die Portnummer ist der Zielport des OPC UA Servers, z. B. „4840“. Insgesamt kann die Server URL dann wie folgt aussehen: opc.tcp://CX-12345:4840.

Deklaration:

(* Declarations for UA_Connect *)
fbUA_Connect : UA_Connect;
SessionConnectInfo : ST_UASessionConnectInfo;
nConnectionHdl : DWORD;

(* Declarations for UA_Disconnect *)
fbUA_Disconnect : UA_Disconnect;

(* Declarations for state machine and output handling *)
iState : INT;
bDone : BOOL;
bBusy : BOOL;
bError : BOOL;
nErrorID : DWORD;

Implementierung:

CASE iState OF

  0:
      bError := FALSE;
      nErrorID := 0;
      SessionConnectInfo.tConnectTimeout := T#1M;
      SessionConnectInfo.tSessionTimeout := T#1M;
      SessionConnectInfo.sApplicationName := '';
      SessionConnectInfo.sApplicationUri := '';
      SessionConnectInfo.eSecurityMode := eUASecurityMsgMode_None;
      SessionConnectInfo.eSecurityPolicyUri := eUASecurityPolicy_None;
      SessionConnectInfo.eTransportProfileUri := eUATransportProfileUri_UATcp;
      stNodeAddInfo.nIndexRangeCount := nIndexRangeCount;
      stNodeAddInfo.stIndexRange := stIndexRange;
      iState := iState + 1;

  1:
    fbUA_Connect(
      Execute := TRUE,
      ServerURL := ‘opc.tcp://192.168.1.1:4840’,
      SessionConnectInfo := SessionConnectInfo,
      Timeout := T#5S,
      ConnectionHdl => nConnectionHdl);
    IF NOT fbUA_Connect.Busy THEN
      fbUA_Connect(Execute := FALSE);
      IF NOT fbUA_Connect.Error THEN
        iState := iState + 1;
      ELSE
        bError := TRUE;
        nErrorID := fbUA_Connect.ErrorID;
        nConnectionHdl := 0;
        iState := 0;
      END_IF
    END_IF

  2:
    fbUA_Disconnect(
      Execute := TRUE,
      ConnectionHdl := nConnectionHdl);

    IF NOT fbUA_Disconnect.Busy THEN
      fbUA_Disconnect(Execute := FALSE);
      IF NOT fbUA_Disconnect.Error THEN
        iState := 0;
      ELSE
        bError := TRUE;
        nErrorID := fbUA_Disconnect.ErrorID;
        iState := 0;
        nConnectionHdl := 0;
      END_IF
    END_IF

END_CASE

Lesen von Variablen

Im nachfolgenden Abschnitt wird beschrieben, wie Sie die Funktionsbausteine TcX_PLCopen_OpcUa verwenden, um einen OPC‑UA‑Knoten von einem lokalen oder remote OPC UA Server auszulesen. Die folgenden Funktionsbausteine sind erforderlich, um eine Verbindung zu einem OPC UA Server herzustellen, UA-Knoten auszulesen und später die Sitzung zu unterbrechen: UA_Connect, UA_GetNamespaceIndex, UA_NodeGetHandle, UA_Read, UA_NodeReleaseHandle, UA_Disconnect.

Der schematische Arbeitsablauf jedes TwinCAT OPC UA Clients kann in drei verschiedene Phasen kategorisiert werden: Preparation, Work und Cleanup.

Der in diesem Abschnitt beschriebene Verwendungsfall kann wie folgt visualisiert werden:

PLCopen-Funktionsbausteine 12:

Deklaration:

(* Declarations for UA_GetNamespaceIndex *)
fbUA_GetNamespaceIndex : UA_GetNamespaceIndex;
nNamespaceIndex : UINT;

(* Declarations for UA_NodeGetHandle *)
fbUA_NodeGetHandle : UA_NodeGetHandle;
NodeID : ST_UANodeID;
nNodeHdl : DWORD;

(* Declarations for UA_Read *)
fbUA_Read : UA_Read;
stIndexRange : ARRAY [1..nMaxIndexRange] OF ST_UAIndexRange;
nIndexRangeCount : UINT;
stNodeAddInfo : ST_UANodeAdditionalInfo;
sNodeIdentifier : STRING(MAX_STRING_LENGTH) := 'MAIN.nCounter';
nReadData : INT;
cbDataRead : UDINT;

(* Declarations for UA_NodeReleaseHandle *)
fbUA_NodeReleaseHandle : UA_NodeReleaseHandle;

Implementierung:

CASE iState OF
  0:
    [...]

  2: (* GetNS Index *)
    fbUA_GetNamespaceIndex(
      Execute := TRUE,
      ConnectionHdl := nConnectionHdl,
      NamespaceUri := sNamespaceUri,
      NamespaceIndex => nNamespaceIndex
      );
    IF NOT fbUA_GetNamespaceIndex.Busy THEN
      fbUA_GetNamespaceIndex(Execute := FALSE);
      IF NOT fbUA_GetNamespaceIndex.Error THEN
        iState := iState + 1;
      ELSE
        bError := TRUE;
        nErrorID := fbUA_GetNamespaceIndex.ErrorID;
        iState := 6;
      END_IF
    END_IF

  3: (* UA_NodeGetHandle *)
    NodeID.eIdentifierType := eUAIdentifierType_String;
    NodeID.nNamespaceIndex := nNamespaceIndex;
    NodeID.sIdentifier := sNodeIdentifier;
    fbUA_NodeGetHandle(
      Execute := TRUE,
      ConnectionHdl := nConnectionHdl,
      NodeID := NodeID,
      NodeHdl => nNodeHdl);
    IF NOT fbUA_NodeGetHandle.Busy THEN
      fbUA_NodeGetHandle(Execute := FALSE);
      IF NOT fbUA_NodeGetHandle.Error THEN
        iState := iState + 1;
      ELSE
        bError := TRUE;
        nErrorID := fbUA_NodeGetHandle.ErrorID;
        iState := 6;
      END_IF
    END_IF

  4: (* UA_Read *)
    fbUA_Read(
      Execute := TRUE,
      ConnectionHdl := nConnectionHdl,
      NodeHdl := nNodeHdl,
      cbData := SIZEOF(nReadData),
      stNodeAddInfo := stNodeAddInfo,
      pVariable := ADR(nReadData));
    IF NOT fbUA_Read.Busy THEN
      fbUA_Read( Execute := FALSE, cbData_R => cbDataRead);
      IF NOT fbUA_Read.Error THEN
        iState := iState + 1;
      ELSE
        bError := TRUE;
        nErrorID := fbUA_Read.ErrorID;
        iState := 6;
      END_IF
    END_IF

  5: (* Release Node Handle *)
    fbUA_NodeReleaseHandle(
      Execute := TRUE,
      ConnectionHdl := nConnectionHdl,
      NodeHdl := nNodeHdl);
    IF NOT fbUA_NodeReleaseHandle.Busy THEN
      fbUA_NodeReleaseHandle(Execute := FALSE);
      IF NOT fbUA_NodeReleaseHandle.Error THEN
        iState := iState + 1;
      ELSE
        bError := TRUE;
        nErrorID := fbUA_NodeReleaseHandle.ErrorID;
        iState := 6;
      END_IF
    END_IF

  6:
    [...]

END_CASE

Schreiben von Variablen

Im nachfolgenden Abschnitt wird beschrieben, wie Sie die Funktionsbausteine TcX_PLCopen_OpcUa verwenden, um Werte in einem OPC‑UA‑Knoten von einem lokalen oder remote OPC UA Server zu schreiben. Die folgenden Funktionsbausteine sind erforderlich, um eine Verbindung zu einem OPC UA Server herzustellen, UA-Knoten zu schreiben und später die Sitzung zu unterbrechen: UA_Connect, UA_GetNamespaceIndex, UA_NodeGetHandle, UA_Write, UA_NodeReleaseHandle, UA_Disconnect.

Der schematische Arbeitsablauf jedes TwinCAT OPC UA Clients kann in drei verschiedene Phasen kategorisiert werden: Preparation, Work und Cleanup.

Der in diesem Abschnitt beschriebene Verwendungsfall kann wie folgt visualisiert werden:

PLCopen-Funktionsbausteine 13:

Deklaration:

(* Declarations for UA_GetNamespaceIndex *)
fbUA_GetNamespaceIndex : UA_GetNamespaceIndex;
nNamespaceIndex : UINT;

(* Declarations for UA_NodeGetHandle *)
fbUA_NodeGetHandle : UA_NodeGetHandle;
NodeID : ST_UANodeID;
nNodeHdl : DWORD;

(* Declarations for UA_Write *)
fbUA_Write : UA_Write;
stIndexRange : ARRAY [1..nMaxIndexRange] OF ST_UAIndexRange;
nIndexRangeCount : UINT;
stNodeAddInfo : ST_UANodeAdditionalInfo;
sNodeIdentifier: STRING(MAX_STRING_LENGTH) := 'MAIN.nNumber';
nWriteData: INT := 42;

(* Declarations for UA_NodeReleaseHandle *)
fbUA_NodeReleaseHandle : UA_NodeReleaseHandle;

Implementierung:

CASE iState OF
  0:
    [...]

  2: (* GetNS Index *)
    fbUA_GetNamespaceIndex(
      Execute := TRUE,
      ConnectionHdl := nConnectionHdl,
      NamespaceUri := sNamespaceUri,
      NamespaceIndex => nNamespaceIndex
      );
    IF NOT fbUA_GetNamespaceIndex.Busy THEN
      fbUA_GetNamespaceIndex(Execute := FALSE);
      IF NOT fbUA_GetNamespaceIndex.Error THEN
        iState := iState + 1;
      ELSE
        bError := TRUE;
        nErrorID := fbUA_GetNamespaceIndex.ErrorID;
        iState := 6;
      END_IF
    END_IF

  3: (* UA_NodeGetHandle *)
    NodeID.eIdentifierType := eUAIdentifierType_String;
    NodeID.nNamespaceIndex := nNamespaceIndex;
    NodeID.sIdentifier := sNodeIdentifier;
    fbUA_NodeGetHandle(
      Execute := TRUE,
      ConnectionHdl := nConnectionHdl,
      NodeID := NodeID,
      NodeHdl => nNodeHdl);
    IF NOT fbUA_NodeGetHandle.Busy THEN
      fbUA_NodeGetHandle(Execute := FALSE);
      IF NOT fbUA_NodeGetHandle.Error THEN
        iState := iState + 1;
      ELSE
        bError := TRUE;
        nErrorID := fbUA_NodeGetHandle.ErrorID;
        iState := 6;
      END_IF
    END_IF

  4: (* UA_Write *)
    fbUA_Write(
      Execute := TRUE,
      ConnectionHdl := nConnectionHdl,
      NodeHdl := nNodeHdl,
      stNodeAddInfo := stNodeAddInfo,
      cbData := SIZEOF(nWriteData),
      pVariable := ADR(nWriteData));
    IF NOT fbUA_Write.Busy THEN
      fbUA_Write(
        Execute := FALSE,
        pVariable := ADR(nWriteData));
      IF NOT fbUA_Write.Error THEN
        iState := iState + 1;
      ELSE
        bError := TRUE;
        nErrorID := fbUA_Write.ErrorID;
        iState := 6;
      END_IF
    END_IF

  5: (* Release Node Handle *)
    fbUA_NodeReleaseHandle(
      Execute := TRUE,
      ConnectionHdl := nConnectionHdl,
      NodeHdl := nNodeHdl);
    IF NOT fbUA_NodeReleaseHandle.Busy THEN
      fbUA_NodeReleaseHandle(Execute := FALSE);
      IF NOT fbUA_NodeReleaseHandle.Error THEN
        iState := iState + 1;
      ELSE
        bError := TRUE;
        nErrorID := fbUA_NodeReleaseHandle.ErrorID;
        iState := 6;
      END_IF
    END_IF

  6:
    [...]

END_CASE

Aufrufen von Methoden

Im nachfolgenden Abschnitt wird beschrieben, wie Sie die Funktionsbausteine TcX_PLCopen_OpcUa verwenden, um Methoden auf einem lokalen oder remote OPC UA Server aufzurufen. Die folgenden Funktionsbausteine sind erforderlich, um eine Verbindung zu einem OPC UA Server herzustellen, UA-Methoden aufzurufen und später die Sitzung zu unterbrechen: UA_Connect, UA_GetNamespaceIndex, UA_MethodGetHandle, UA_MethodCall, UA_MethodReleaseHandle, UA_Disconnect.

Der schematische Arbeitsablauf jedes TwinCAT OPC UA Clients kann in drei verschiedene Phasen kategorisiert werden: Preparation, Work und Cleanup.

Der in diesem Abschnitt beschriebene Verwendungsfall kann wie folgt visualisiert werden:

PLCopen-Funktionsbausteine 14:

Initialisierungsmethode M_Init des Funktionsbausteins, der den UA-Methodenaufruf enthält:

MEMSET(ADR(nInputData),0,SIZEOF(nInputData));
nArg := 1;

(********** Input parameter 1 **********)
InputArguments[nArg].DataType := eUAType_Int16;
InputArguments[nArg].ValueRank := -1; (* Scalar = -1 or Array *)
InputArguments[nArg].ArrayDimensions[1] := 0; (* Number of Dimension in case its an array *)
InputArguments[nArg].nLenData := SIZEOF(numberIn1); (* Length if its a STRING *)
IF nOffset + SIZEOF(numberIn1) > nInputArgSize THEN
  bInputDataError := TRUE;
  RETURN;
ELSE
  MEMCPY(ADR(nInputData)+nOffset,ADR(numberIn1),SIZEOF(numberIn1)); (* VALUE in BYTES FORM *)
  nOffset := nOffset + SIZEOF(numberIn1);
END_IF
nArg := nArg + 1;

(********** Input parameter 2 **********)
InputArguments[nArg].DataType := eUAType_Int16;
InputArguments[nArg].ValueRank := -1; (* Scalar = -1 or Array *)
InputArguments[nArg].ArrayDimensions[1] := 0; (* Number of Dimension in case its an array *)
InputArguments[nArg].nLenData := SIZEOF(numberIn2); (* Length if its a STRING *)
IF nOffset + SIZEOF(numberIn2) > nInputArgSize THEN
  bInputDataError := TRUE;
  RETURN;
ELSE
  MEMCPY(ADR(nInputData)+nOffset,ADR(numberIn2),SIZEOF(numberIn2));(* VALUE in BYTES FORM *)
  nOffset := nOffset + SIZEOF(numberIn2);
END_IF

cbWriteData := nOffset;

Deklaration:

(* Declarations for UA_GetNamespaceIndex *)
fbUA_GetNamespaceIndex : UA_GetNamespaceIndex;
nNamespaceIndex : UINT;

(* Declarations for UA_MethodGetHandle *)
fbUA_MethodGetHandle: UA_MethodGetHandle;
ObjectNodeID: ST_UANodeID;
MethodNodeID: ST_UANodeID;
nMethodHdl: DWORD;

(* Declarations for UA_MethodCall *)
fbUA_MethodCall: UA_MethodCall;
sObjectNodeIdIdentifier : STRING(MAX_STRING_LENGTH) := 'MAIN.fbMathematics';
sMethodNodeIdIdentifier : STRING(MAX_STRING_LENGTH) := 'MAIN.fbMathematics#M_Mul';
nAdrWriteData: PVOID;
numberIn1: INT := 42; // change according to input value and data type
numberIn2: INT := 42; // change according to input value and data type
numberOutPro: DINT; // result (output parameter of M_Mul())
cbWriteData: UDINT; // calculated automatically by M_Init()
InputArguments: ARRAY[1..2] OF ST_UAMethodArgInfo; // change according to input parameters
stOutputArgInfo: ARRAY[1..1] OF ST_UAMethodArgInfo; // change according to output parameters
stOutputArgInfoAndData: ST_OutputArgInfoAndData;
nInputData: ARRAY[1..4] OF BYTE; // numberIn1(INT16)(2) + numberIn2(INT16)(2)
nOffset: UDINT; // calculated by M_Init()
nArg: INT; // used by M_Init()

(* Declarations for UA_MethodReleaseHandle *)
fbUA_MethodReleaseHandle: UA_MethodReleaseHandle;

Implementierung:

CASE iState OF
  0:
    [...]

  2: (* GetNS Index *)
    fbUA_GetNamespaceIndex(
      Execute := TRUE,
      ConnectionHdl := nConnectionHdl,
      NamespaceUri := sNamespaceUri,
      NamespaceIndex => nNamespaceIndex);
    IF NOT fbUA_GetNamespaceIndex.Busy THEN
      fbUA_GetNamespaceIndex(Execute := FALSE);
      IF NOT fbUA_GetNamespaceIndex.Error THEN
        iState := iState + 1;
      ELSE
        bError := TRUE;
        nErrorID := fbUA_GetNamespaceIndex.ErrorID;
        iState := 7;
      END_IF
    END_IF

  3: (* Get Method Handle *)
    ObjectNodeID.eIdentifierType := eUAIdentifierType_String;
    ObjectNodeID.nNamespaceIndex := nNamespaceIndex;
    ObjectNodeID.sIdentifier := sObjectNodeIdIdentifier;
    MethodNodeID.eIdentifierType := eUAIdentifierType_String;
    MethodNodeID.nNamespaceIndex := nNamespaceIndex;
    MethodNodeID.sIdentifier := sMethodNodeIdIdentifier;

    M_Init();

    IF bInputDataError = FALSE THEN
      iState := iState + 1;
    ELSE
      bBusy := FALSE;
      bError := TRUE;
      nErrorID := 16#70A; //out of memory
    END_IF

  4: (* Method Get Handle *)
    fbUA_MethodGetHandle(
      Execute := TRUE,
      ConnectionHdl := nConnectionHdl,
      ObjectNodeID := ObjectNodeID,
      MethodNodeID := MethodNodeID,
      MethodHdl => nMethodHdl);
    IF NOT fbUA_MethodGetHandle.Busy THEN
      fbUA_MethodGetHandle(Execute := FALSE);
      IF NOT fbUA_MethodGetHandle.Error THEN
        iState := iState + 1;
      ELSE
        bError := TRUE;
        nErrorID := fbUA_MethodGetHandle.ErrorID;
        iState := 6;
      END_IF
    END_IF

  5: (* Method Call *)
    stOutputArgInfo[1].nLenData := SIZEOF(stOutputArgInfoAndData.pro);
    fbUA_MethodCall(
      Execute := TRUE,
      ConnectionHdl := nConnectionHdl,
      MethodHdl := nMethodHdl,
      nNumberOfInputArguments := nNumberOfInputArguments,
      pInputArgInfo := ADR(InputArguments),
      cbInputArgInfo := SIZEOF(InputArguments),
      pInputArgData := ADR(nInputData),
      cbInputArgData := cbWriteData,
      pInputWriteData := 0,
      cbInputWriteData := 0,
      nNumberOfOutputArguments := nNumberOfOutputArguments,
      pOutputArgInfo := ADR(stOutputArgInfo),
      cbOutputArgInfo := SIZEOF(stOutputArgInfo),
      pOutputArgInfoAndData := ADR(stOutputArgInfoAndData),
      cbOutputArgInfoAndData := SIZEOF(stOutputArgInfoAndData));
    IF NOT fbUA_MethodCall.Busy THEN
      fbUA_MethodCall(Execute := FALSE);
      IF NOT fbUA_MethodCall.Error THEN
        iState := iState + 1;
        numberOutPro := stOutputArgInfoAndData.pro;
      ELSE
        bError := TRUE;
        nErrorID := fbUA_MethodCall.ErrorID;
        iState := 6;
      END_IF
    END_IF

  6: (* Release Method Handle *)
    fbUA_MethodReleaseHandle(
      Execute := TRUE,
      ConnectionHdl := nConnectionHdl,
      MethodHdl := nMethodHdl);
    IF NOT fbUA_MethodReleaseHandle.Busy THEN
      fbUA_MethodReleaseHandle(Execute := FALSE);
      bBusy := FALSE;
      IF NOT fbUA_MethodReleaseHandle.Error THEN
        iState := 7;
      ELSE
        bError := TRUE;
        nErrorID := fbUA_MethodReleaseHandle.ErrorID;
        iState := 7;
      END_IF
    END_IF

  7:
    [...]

END_CASE