Writing an OPC UA Application

Overview

An application for the OPC UA server is a GOAL application. It consists of three functions that are called by GOAL: appl_init, appl_setup, appl_loop.

Additionally, the application can register a callback that is called by the OPC UA Master stack to inform the application about events.

The function appl_init is used to register components in GOAL, e.g. the OPC UA server stack.

The actual initialization of the application happens in appl_setup.

The application must include goal_includes.h and goal_opcua.h.

Registration of the OPC UA Stack

Within the function appl_init the function goal_opcUaInit must be called.

GOAL_STATUS_T res; /* result */ res = goal_opcUaInit(); if (GOAL_RES_ERR(res)) { goal_logErr("Initialization of OPC UA failed"); }

Configuration of the OPC UA Stack

The function appl_setup is called by GOAL during initialization. Within this function, all functions listed in this chapter can be used to configure the behavior of the OPC UA. All functions must be called before calling goal_opcUaNew.

All functions return a status code indicating whether the operation succeeded or not.

Function

Description

Function

Description

goal_opcUaCfgSecurityPolicyBasic256Sha256Enable

Enable support for the SecurityPolicyBasic256Sha256

goal_opcUaCfgSecurityPolicyNoneDisable

Disable support for SecurityPolicyNone

goal_opcUaCfgSecurityModeSet

Set the supported SecurityModes

goal_opcUaCfgUsernamePasswordTableAdd

Add the table with acceptable usernames and passwords

goal_opcUaCfgMsgBufferSizeSet

Set the size of the reception and send buffers

goal_opcUaCfgNumInputArgsSet

Set the maximum number of input arguments for methods

goal_opcUaCfgNumOutputArgsSet

Set the maximum number of output arguments for methods

Creating a new instance of the OPC UA protocol stack

After the stack has been configured, the function goal_opcUaNew must be invoked to create a new instance of the protocol stack. It is also used to register a callback handler for processing events from the stack. The callback handler will be explained in detail in a later chapter.

GOAL_STATUS_T res; /* result */ GOAL_OPCUA_T *pHdlOpcUa; /* GOAL OPC UA handle */ res = goal_opcUaNew(&pHdlOpcUa, GOAL_OPCUA_INSTANCE_DEFAULT, appl_opcUaCallbackExec); if (GOAL_RES_ERR(res)) { goal_logErr("failed to create a new OPC UA instance"); return res; }

This function creates a handle (in this example it is called pHdlOpcUa) that must be used for all other function calls to reference the stack instance.

Application Callback

During initialization, the application can register a callback handler with the function goal_opcUaNew. The callback handler uses the following arguments:

Argument data type

Description

Argument data type

Description

GOAL_OPCUA_T *

GOAL OPC UA instance

GOAL_OPCUA_CB_ID_T

callback ID indicating callback type

GOAL_OPCUA_CB_DATA_T *

  • callback data

  • array of unions

  • actual meaning depends on callback ID

Some callback ID also evaluate the return value of the handler to decide how to proceed.

Callback ID

Description

Callback data

Return value

Callback ID

Description

Callback data

Return value

GOAL_OPCUA_CB_ID_NODE_CREATED

a node was created that is based on an application specific type

pData->data[0].pSessionNodeId
(id of current session)
pData->data[1].pTypeNodeId
(node id of object or variable type)
pData->data[2].pCreatedNodeId
(id of created node)

GOAL_OK: accept new node

other: abort creation

GOAL_OPCUA_CB_ID_NODE_DELETED

a node was deleted that is based on an application specific type

pData->data[0].pSessionNodeId
(id of current session)
pData->data[1].pTypeNodeId
(node id of object or variable type)
pData->data[2].pNodeIdToDelete
(id of node that will be deleted)

don’t care

GOAL_OPCUA_CB_ID_VARIABLE_READ

the value of a variable node is going to be read

pData->data[0].pSessionNodeId
(id of current session)
pData->data[1].pVariableNodeId
(node id of variable)
pData->data[2].pNumericRange
(numeric range if variable is an array)
pData->data[3].pDataValue
(data value of variable, can be updated)

don’t care

GOAL_OPCUA_CB_ID_VARIABLE_WRITTEN

the value of a variable node was written

pData->data[0].pSessionNodeId
(id of current session)
pData->data[1].pVariableNodeId
(node id of variable)
pData->data[2].pNumericRange
(numeric range if variable is an array)
pData->data[3].pDataValue
(data value of variable with new value)

don’t care

The following listing shows how to handle a callback:

static GOAL_STATUS_T appl_opcUaCallbackExec( GOAL_OPCUA_T *pOpcUa, /**< GOAL OPC UA instance */ GOAL_OPCUA_CB_ID_T cbId, /**< callback Id */ GOAL_OPCUA_CB_DATA_T *pData /**< callback data */ ) { GOAL_STATUS_T res = GOAL_OK; /* result */ switch (cbId) { /* ... */ case GOAL_OPCUA_CB_ID_VARIABLE_READ: goal_logInfo("Read access to Node %s", goal_opcUaNodeIdPrint(pOpcUa, pData->data[1].pVariableNodeId)); if (GOAL_TRUE == goal_opcUaNodeIdCompare(pData->data[1].pVariableNodeId, &var01Node)) { goal_logInfo("Variable 01 is going to be read"); /* increase value with each read */ var01++; GOAL_htole32_p((uint8_t *) pData->data[3].pDataValue->value.pValue, var01); } break; /* ... */ } return res; }

Helper functions

The functions listed in this chapter are provided for convenience.

Function

Description

Function

Description

goal_opcUaNodeIdCompare

check if two node IDs have the same value

goal_opcUaNodeIdPrint

create a printable string of a node ID

goal_opcUaByteStringPrint

create a printable string of a ByteString

goal_opcUaNodeFindByName

find a node by reference and BrowseName

goal_opcUaNodeIdNumericSet

create a numeric node ID

goal_opcUaNodeIdStringSet

create a String node ID from a String

goal_opcUaNodeIdStringCharSet

create a String node ID from a char array

goal_opcUaNodeIdGuidSet

create a GUID node ID

goal_opcUaNodeIdByteStringSet

create a ByteString node ID

goal_opcUaStringSet

populate a String with a char array

goal_opcUaQualifiedNameSet

populate a QualifiedName with a char array

goal_opcUaLocalTextSet

populate a LocalicedText with two char arrays (locale and text)

goal_opcUaVariantScalarSet

populate a Variant with a scalar value

goal_opcUaVariantArraySet

populate a Variant with an array value

goal_opcUaVariantArrayDimensionsSet

populate a Variant with an array value and its dimension sizes

Fieldbus Variable Mapping

The GOAL OPC UA stack provides functions that allow the mapping of variables from a fieldbus to variable nodes.

Each fieldbus must register a callback handler first. The fieldbus is identified by an ID that must be unique for each fieldbus. For every fieldbus that is supported by GOAL there is already a GOAL_ID_* value. The callback handler is called whenever a variable node that is mapped to a fieldbus variable was written. Additionally, a callback argument can be set that will be passed to the callback handler.

For each variable that should be mapped to a variable node the function goal_opcUaFieldbusVarMap must be called. Each variable can be identified by up to three IDs. The meaning of these IDs is completely defined by the fieldbus protocol. For example a PROFINET variable can be identified by its Module ID and its Submodule ID and an index. An EtherCAT variable could be identified by an index and a subindex.

The data type of the variable must be specified. For non-numeric data types also the maximum size of the variable must be set. The node Id must reference a node that already exists.

Whenever a new value is received from the fieldbus the function goal_opcUaFbVarWrite must be invoked to update the value of the corresponding variable node.

As mentioned above, the registered callback handler is executed whenever a value was written to a mapped variable node.

AddressSpace node creation

The stack provides functions to create new nodes in the AddressSpace. Each function expects a node Id that will be used for the new node. Furthermore, other node IDs are passed to indicate the parent node of the new node, the reference type between the parent node and the new node, the type node (for object, variable or variableType nodes). Each node must have a BrowseName and attributes that are specific for each node class. If the parameter pCreatedNodeId is not NULL, the ID of the newly created node is written to this handle.

Function

Description

Function

Description

goal_opcUaNodeClassObjectAdd

create an object node

goal_opcUaNodeClassObjectTypeAdd

create an objectType node

goal_opcUaNodeClassVariableAdd

create a variable node

goal_opcUaNodeClassVariableTypeAdd

create a variableType node

goal_opcUaNodeClassViewAdd

create a view node

goal_opcUaNodeClassReferenceTypeAdd

create a referenceType node

goal_opcUaNodeClassDataTypeAdd

create a dataType node

goal_opcUaNodeClassMethodAdd

create a method node

goal_opcUaNodeReferenceAdd

create an additional reference between a source and a target node

goal_opcUaNodePropertyAdd

add a property to a node

The function goal_opcUaNodeClassMethodAdd require additional arguments. A callback handler that is triggered whenever the method is executed. Additionally, the number and properties of input and output arguments.

Event handling

EventTypes are objectTypes that are subtypes of the BaseEventType or its subtypes.

The function goal_opcUaEventCreate is used to create events of a specific eventType. The function also sets the properties severity (1: notification .. 1000: catastrophe), message and source name. The function creates a new node. Its ID is passed to pEventId.

The function goal_opcUaEventTrigger is used to trigger the event that is specified by its node ID.

Date Time base

This part only describes the GOAL OPC UA server stack. The open62541 stack uses the Linux time stamps to create current date time values. All API, which is presented here, will return a “Not Supported“ GOAL error code.

OPC UA messages contain a current data time value. Because not all platforms support a Real Time Clock (RTC), the data time must be emulated. Therefore, a data time value is stored at a specific time, and the tuple (date time, timestamp) is used as base to create date time values on given timestamps. Before a valid date time is extracted, the stack uses 0 values for date times, which are interpreted as “unset“ or “unused“. The tuple (date time, timestamp) can be created in two different ways.

  • Automatically: In this case, the stack tries to extract current date times from client messages. After the first successful extraction, the tuple (date time, timestamp) is used for all further calculations.

  • Manually: If an external time source is used (e.g. linux, a RTC or any other protocol), the stack can be informed directly using the function goal_opcUaDateTimeSet. Therefore, the current date time must be calculated, which is defined as the number of 100ns steps from 1601-01-01T00:00:00.000 (UTC) encoded as 64-bit integer. E.g. 1970-01-01T00:00:00.000Z corresponds to the date time value 0x019db1ded53e8000. This function could be called multiple times, overwriting the last tuple (date time, timestamp).