C++ Based OPC UA Client/Server/PubSub SDK  1.7.6.537
Tutorial Server Hello World

This simple Hello World tutorial explains how to create an OPC UA server with a few lines of code.

The complete source code can be found in Examples → Hello World Server project.

Utilities Used for the Example

The example is using a console application. A description on how to set up an empty project to develop an OPC UA Server using the Unified Automation C++ based SDK can be found in Lesson 1: Setting up a Basic OPC UA Server Console Application.

The following utility files are used from examples/utilities. Add these file to your project:

  • shutdown.h/shutdown.cpp provide platform neutral functions to wait for the user shutdown command in a console application
  • opcserver.h/opcserver.cpp provide the OpcServer utility class used for loading and unloading all SDK modules

Main Function for the Console Application

The following code provides a generic main method without OPC UA code that extracts the application path from the input arguments and calls the method OpcServerMain where we will add the OPC UA specific code. As a first step, OpcServerMain contains only the waiting loop for a user shutdown command.

#include <stdio.h>
#include <string.h>
#include "shutdown.h"
#include "uaplatformlayer.h"
int OpcServerMain(const char* szAppPath)
{
int ret = 0;
printf("***************************************************\n");
printf(" Press %s to shut down server\n", SHUTDOWN_SEQUENCE);
printf("***************************************************\n");
while (ShutDownFlag() == 0)
{
}
return ret;
}
int main(int, char*[])
{
int ret = 0;
RegisterSignalHandler();
// Extract application path
char* pszAppPath = getAppPath();
//-------------------------------------------
// Call the OPC server main method
ret = OpcServerMain(pszAppPath);
//-------------------------------------------
if ( pszAppPath ) delete [] pszAppPath;
return ret;
}

Initializing UA Stack and XML Parser

The UA Stack and the XML Parser modules are ANSI C based modules and do require a global initialization before any of their functionality is used.

The initialization of the XML Parser is only needed if the XML configuration file is used through the class ServerConfigXml.

If the UaPlatformLayer::init call succeeds, the UA SDK and UA Stack classes and functions can be used.

Note
It is important that UaXmlDocument::initParser is only called once on start-up (or only from the main thread) and UaXmlDocument::cleanupParser only once on exit. Calling initParser multiple times from different threads will cause a memory leak (see also class documentation for UaXmlDocument).
#include "xmldocument.h"

Replace OpcServerMain:

int OpcServerMain(const char* szAppPath)
{
int ret = 0;
//- Initialize the environment --------------
// Initialize the XML Parser
// Initialize the UA Stack platform layer
//-------------------------------------------
if ( ret == 0 )
{
// SDK can be used now
printf("***************************************************\n");
printf(" Press %s to shut down server\n", SHUTDOWN_SEQUENCE);
printf("***************************************************\n");
while (ShutDownFlag() == 0)
{
}
}
//- Clean up the environment --------------
// Clean up the UA Stack platform layer
// Clean up the XML Parser
//-------------------------------------------
return ret;
}

Create OPC UA Server

The following code is used to initialize and start the server object. OPC UA clients can connect to the server after this code is executed.

#include "opcserver.h"
...
//-------------------------------------------
if ( ret == 0 )
{
// New code begins
// Create configuration file name
UaString sConfigFileName(szAppPath);
sConfigFileName += "/ServerConfig.xml";
//- Start up OPC server ---------------------
// Create and initialize server object
OpcServer* pServer = new OpcServer;
pServer->setServerConfig(sConfigFileName, szAppPath);
// Start server object
ret = pServer->start();
if ( ret != 0 )
{
delete pServer;
return ret;
}
//-------------------------------------------
// New code ends
...
Create configuration file name
This code uses the class UaString to create the configuration file name including the path. The configuration file in the example can be found in [SDK Installation Directory]/bin (which contains the server executable file as well). It is also possible to use an ini configuration file (see the file [SDK Installation Directory]/examples/server_hello_world/servermain.cpp). An example is included in the same directory as the xml configuration file.
Start up OPC server
This code can be used in the startup sequence of the application where the OPC server should be integrated into.
Create and initialize server object
This code creates an instance of the class OpcServer used to manage all OPC UA SDK modules. In a second step the configuration file and application path is set to allow the server object to find its configuration and PKI folder.
Start server object
After the start function of the OpcServer object is called successfully, OPC UA clients can connect to the server.

Shut down OPC UA Server

The following code is used to wait for the user sending the shutdown command and to shut down the OPC UA server.

#include "uathread.h"
...
printf("***************************************************\n");
printf(" Press %s to shut down server\n", SHUTDOWN_SEQUENCE);
printf("***************************************************\n");
// Wait for user command to terminate the server thread.
while(ShutDownFlag() == 0)
{
UaThread::msleep(1000); // Add this line
}
// New code begins
printf("***************************************************\n");
printf(" Shutting down server\n");
printf("***************************************************\n");
//- Stop OPC server -------------------------
// Stop the server and wait three seconds if clients are connected
// to allow them to disconnect after they received the shutdown signal
pServer->stop(3, UaLocalizedText("", "User shutdown"));
delete pServer;
pServer = NULL;
//-------------------------------------------
// New code ends
...
Wait for user command
The loop checks for a user shutdown command and sleeps between the checks. The platform independent UaThread::msleep function is used.
Stop OPC server
This code can be used in the shutdown sequence of the application where the OPC server should be integrated into. It stops the server and provides information that is sent to the clients connected to the server to allow them to disconnect from the server.

Create a variable in the server’s address space

The following code is used to create a UA Variable in the server’s address space and to simulate value changes.

#include "opcua_basedatavariabletype.h"
...
// Start server object
ret = pServer->start();
if ( ret != 0 )
{
delete pServer;
return ret;
}
//-------------------------------------------
// New code begins
//- Add variable to address space -----------
// Get the default node manager for server specific nodes from the SDK
NodeManagerConfig* pNodeConfig = pServer->getDefaultNodeManager();
// Create a variable node with a string data type
UaVariant defaultValue;
defaultValue.setString("Hello World");
UaNodeId("HelloWorld", pNodeConfig->getNameSpaceIndex()), // NodeId of the node with string identifier "HelloWorld" and the namespace index of the default node manager which is 1
"HelloWorld", // Name of the node used for display name and browse name
pNodeConfig->getNameSpaceIndex(), // The same namespace index is also used for the browse name
defaultValue, // Setting the default value and the data type of the variable
OpcUa_AccessLevels_CurrentRead, // Setting the access level to read only
pNodeConfig); // The node manager config interface used for this node
// Add the node to the node manager using the objects folder as source node and the reference type HasComponent
pNodeConfig->addNodeAndReference(UaNodeId(OpcUaId_ObjectsFolder, 0), pVariable, OpcUaId_HasComponent);
//-------------------------------------------
// New code ends
...
Get default NodeManager
The server object provides a method to get the default node manager of the server with namespace index 1 that can be used to add nodes that have no specific namespace.
Create a variable node
This code can be integrated after starting the server. It uses one of the SDK provided classes to create a UA Variable with a string data type. After creating the node it is added to the node manager using the method NodeManagerConfig::addNodeAndReference. This method adds the node to the node manager and creates a reference from the source node to the newly created target node. The source node can be passed in as node pointer or as NodeId. In this case the NodeId of the OPC UA defined Objects Folder is passed in as source node. The Objects Folder is the main entry point for instance nodes.
// Wait for user command to terminate the server thread.
// New code begins
// Simulate data
OpcUa_Boolean useOtherText = OpcUa_True;
UaVariant newValue;
UaDataValue dataValue;
// New code ends
while(ShutDownFlag() == 0)
{
// New code begins
// Toggle string value
if ( useOtherText == OpcUa_False )
{
newValue.setString("Hello World");
useOtherText = OpcUa_True;
}
else
{
newValue.setString("My Text");
useOtherText = OpcUa_False;
}
// Set new value
dataValue.setValue(newValue, OpcUa_False, OpcUa_True);
pVariable->setValue(NULL, dataValue, OpcUa_False);
// New code ends
}
printf("***************************************************\n");
printf(" Shutting down server\n");
printf("***************************************************\n");
Simulate data
To simulate data the shut down waiting loop is used to change the data. The sample code toggles the string value between “Hello World” and “My Text”. The new value is then passed to the variable by using the method UaVariable::setValue. The value is changed every second based on the sleep for 1000ms.