UA ANSI C Server Professional  1.4.1.289
 All Data Structures Functions Variables Typedefs Enumerations Enumerator Groups Pages
Lesson 1: Setting up a Basic OPC UA Server Console Application

This lesson will guide you through the process of setting up a basic OPC UA Server console application.

Files used in this lesson:

Step 1: Create New Project

Set up a console application.

Windows:

Create a new project. Use the following settings:

  • Win32 Console Application
  • No precompiled headers
  • Empty project.

Step 2: Add Files to the Project

Add the file servermain.c to your project.

Create the Main function in servermain.c and Initialize UA Stack

The example is using a console application.

The following code provides a generic main function where we will add the OPC UA specific code. The UA Stack requires global initialization before it can be used, for this purpose we use the helper function InitializeOpcUaStack. A default configuration is created and passed to the UA Stack initialization function of the SDK.

Warning
None of the SDK functionality can be used before the UA stack initialization is done.
This requirement includes static members or global variables using UA SDK or UA Stack classes and functions.
If UA SDK or UA Stack classes and functions are used before UaServer_Module_InitializeUaStack is called the server will crash since the platform layer of the UA stack is not loaded.
#include <uaserver_config.h>
#if defined(_WIN32) || defined(_WIN32_WCE)
# include <winsock2.h>
#else
# include <unistd.h>
# include <stdlib.h>
# include <netdb.h>
# ifdef VXWORKS
# include <hostLib.h>
# endif /* VXWORKS */
#endif
#include <uaserver_module.h>
#include <uaserver_utilities.h>
/* Initializes OPC UA Stack.
* Here you can configure trace settings and other stack configuration options.
*/
OpcUa_StatusCode InitializeOpcUaStack(OpcUa_Handle *a_phProxyStubPlatformLayer,
OpcUa_ProxyStubConfiguration *a_pProxyStubConfiguration)
{
OpcUa_InitializeStatus(OpcUa_Module_Server, "InitializeOpcUaStack");
/* Initialize Stack */
printf("UA Server: Initializing Stack...\n");
/* Default values can be changed here */
a_pProxyStubConfiguration->bProxyStub_Trace_Enabled = OpcUa_True;
a_pProxyStubConfiguration->uProxyStub_Trace_Level = OPCUA_TRACE_OUTPUT_LEVEL_ERROR;
uStatus = UaServer_Module_InitializeUaStack(a_phProxyStubPlatformLayer, a_pProxyStubConfiguration);
OpcUa_ReturnStatusCode;
OpcUa_BeginErrorHandling;
OpcUa_FinishErrorHandling;
}
/* Cleanup counterpart to InitializeOpcUaStack. */
OpcUa_StatusCode CleanupOpcUaStack(OpcUa_Handle *a_phProxyStubPlatformLayer)
{
OpcUa_InitializeStatus(OpcUa_Module_Server, "CleanupOpcUaStack");
/* Clean Up UA Stack */
uStatus = UaServer_Module_ClearUaStack(a_phProxyStubPlatformLayer);
OpcUa_ReturnStatusCode;
OpcUa_BeginErrorHandling;
OpcUa_FinishErrorHandling;
}
int GetFQHostname( char *szHostname, int len )
{
struct hostent *pEnt = 0;
int ret = gethostname( szHostname, len );
if ( ret != 0 ) return ret;
pEnt = gethostbyname( szHostname );
if ( pEnt == 0 ) return -1;
strlcpy( szHostname, pEnt->h_name, len );
szHostname[len - 1] = 0;
return 0;
}
/* Main OPC UA Server Loop. */
OpcUa_StatusCode ServerMain()
{
OpcUa_InitializeStatus(OpcUa_Module_Server, "ServerMain");
OpcUa_ReturnStatusCode;
OpcUa_BeginErrorHandling;
OpcUa_FinishErrorHandling;
}
int main(int argc, char *argv[])
{
int ret = EXIT_SUCCESS;
OpcUa_StatusCode uStatus = OpcUa_Good;
OpcUa_Handle hProxyStubPlatformLayer = OpcUa_Null;
OpcUa_ProxyStubConfiguration proxyStubConfiguration;
/* The main method parameters are unused */
OpcUa_ReferenceParameter(argc);
OpcUa_ReferenceParameter(argv);
/* Set up OPC UA */
uStatus = InitializeOpcUaStack(&hProxyStubPlatformLayer, &proxyStubConfiguration);
if ( OpcUa_IsNotGood(uStatus) )
{
return EXIT_FAILURE;
}
/* Start the main server loop */
uStatus = ServerMain();
if ( OpcUa_IsNotGood(uStatus) )
{
ret = EXIT_FAILURE;
}
/* Clean up OPC UA */
uStatus = CleanupOpcUaStack(&hProxyStubPlatformLayer);
if ( OpcUa_IsNotGood(uStatus) )
{
ret = EXIT_FAILURE;
}
return ret;
}

Linux specific: Signal handlers

On Linux you SHOULD install a signal handler to handle SIGPIPE. SIGPIPE is sent when writing on a closed socket which can happen when a connection is broken. The default handler terminates the application which is the desired behaviour when using pipes for inter-process communication, but not for networking applications like OPC UA. You can ignore this signal by calling signal(SIGPIPE, SIG_IGN) or by installing a signal handler.

You should also handle the signals SIGINT and SIGTERM like shown in the following example. SIGINT is sent when a user wants to interrupt a process by pressing CTRL-C on controlling terminal. SIGTERM is sent when a process is requested to terminate. SIGTERM is the default signal sent to a process by the kill or killall command. Unlike the SIGKILL signal, it can be caught by an application so that it can properly terminate. SIGTERM is also sent by the init process during system shutdown, it then waits a few seconds and then sends SIGKILL to all remaining processes to forcibly terminate them.

For more information on signals please see man signal(2), man sigaction(2), or the book UNIX Network Programming from Richard Stevens, Addison Wesley.

In our examples we setup signal handlers in the utility function UaServer_P_InitShutdownFlag. You can either use this utility function or setup your own signal handlers like shown in the following example.

#include <signal.h>
/* Shutdown flag: Note that this must be defined volatile, because it gets set by a signal handler. */
volatile sig_atomic_t iDoShutdown = 0;
/* Signal handler for SIGINT and SIGTERM. */
static void shutdown_signal_handler(int signo)
{
OpcUa_ReferenceParameter(signo);
iDoShutdown = 1;
}
OpcUa_StatusCode install_signal_handler()
{
struct sigaction new_action, old_action;
/* Set up the structure to specify the new action. */
new_action.sa_handler = shutdown_signal_handler;
sigemptyset(&new_action.sa_mask);
new_action.sa_flags = 0;
/* install new signal handler for SIGINT */
sigaction(SIGINT, NULL, &old_action);
if (old_action.sa_handler != SIG_IGN)
{
sigaction(SIGINT, &new_action, NULL);
}
/* install new signal handler for SIGTERM */
sigaction(SIGTERM, NULL, &old_action);
if (old_action.sa_handler != SIG_IGN)
{
sigaction(SIGTERM, &new_action, NULL);
}
/* Set up the structure to prevent program termination on interrupted connections. */
new_action.sa_handler = SIG_IGN;
sigemptyset(&new_action.sa_mask);
new_action.sa_flags = 0;
/* install new signal handler for SIGPIPE*/
sigaction(SIGPIPE, NULL, &old_action);
if (old_action.sa_handler != SIG_IGN)
{
sigaction(SIGPIPE, &new_action, NULL);
}
return OpcUa_Good;
}

If the UaServer_Module_InitializeUaStack call succeeds, the UA SDK and UA Stack classes and functions can be used.

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. After that, the shutdown flag of the SDK is initialized, which will be set to true if the user presses a certain key combination, shutting down the server.

Server/Application URLs, URIs and Names

Using following defines, the server configuration is created.

/* Server/application URLs, URIs and names */
#define UASERVER_PORT 48020
#define UASERVER_APPLICATIONNAME "UaSdkC - Lesson01"
#define UASERVER_APPLICATIONURI "UnifiedAutomation:UaSdkC:Lesson01"
#define UASERVER_PRODUCTNAME "UaSdkC - Lesson01"
#define UASERVER_PRODUCTURI "http://www.unifiedautomation.com/server-sdk.htm"
#define UASERVER_MANUFACTURERNAME UASDK_VENDOR_INFO
#define UASERVER_SOFTWAREVERSION UASDK_VERSION
#define UASERVER_BUILDNUMBER chSTR2(UASDK_BUILD_VERSION)

Creating the EndpointURL, the ApplicationURI and Initializing the Server Object

The hostname is retrieved and used for creating the EndpointURL and ApplicationURI of the server. Using these values and the defines from above, the server configuration is set. The PKI settings are set to use no security, hence the other security parameters are left empty.

/* Create EndpointURL and ApplicationURI strings */
GetFQHostname(szHostname, sizeof(szHostname));
OpcUa_SnPrintfA(szEndpointURL, sizeof(szEndpointURL)-1, "opc.tcp://%s:%u", szHostname, UASERVER_PORT);
OpcUa_SnPrintfA(szApplicationUri, sizeof(szApplicationUri)-1, "urn:%s:%s", szHostname, UASERVER_APPLICATIONURI);
/* Initialize Server */
uStatus = UaServer_Initialize(&uaServer);
OpcUa_GotoErrorIfBad(uStatus);
/* Get configuration structure of the server */
pServerConfig = UaServer_GetConfiguration(&uaServer);
/* Configure ApplicationDescription of the server */
OpcUa_String_AttachReadOnly(&pServerConfig->ApplicationDescription.ApplicationUri, szApplicationUri);
OpcUa_String_AttachReadOnly(&pServerConfig->ApplicationDescription.ProductUri, UASERVER_PRODUCTURI);
OpcUa_String_AttachReadOnly(&pServerConfig->ApplicationDescription.ApplicationName.Text, UASERVER_APPLICATIONNAME);
pServerConfig->ApplicationDescription.ApplicationType = OpcUa_ApplicationType_Server;
pServerConfig->ApplicationDescription.NoOfDiscoveryUrls = 1;
pServerConfig->ApplicationDescription.DiscoveryUrls = OpcUa_Alloc(sizeof(OpcUa_String));
OpcUa_String_Initialize(&pServerConfig->ApplicationDescription.DiscoveryUrls[0]);
OpcUa_String_AttachReadOnly(&pServerConfig->ApplicationDescription.DiscoveryUrls[0], szEndpointURL);
/* Configure BuildInfo of the server */
OpcUa_String_AttachReadOnly(&pServerConfig->BuildInfo.ManufacturerName, UASERVER_MANUFACTURERNAME);
OpcUa_String_AttachReadOnly(&pServerConfig->BuildInfo.ProductName, UASERVER_PRODUCTNAME);
OpcUa_String_AttachReadOnly(&pServerConfig->BuildInfo.SoftwareVersion, UASERVER_SOFTWAREVERSION);
OpcUa_String_AttachReadOnly(&pServerConfig->BuildInfo.BuildNumber, UASERVER_BUILDNUMBER);
OpcUa_String_AttachReadOnly(&pServerConfig->BuildInfo.ProductUri, UASERVER_APPLICATIONURI);
pServerConfig->BuildInfo.BuildDate = UaServer_GetBuildDate();

Setting the Endpoint Configuration

In this example, we want to use no authentication, so we need to create one endpoint and only add the Anonymous user token policy.

/* Create one endpoint */
pServerConfig->uNoOfEndpoints = 1;
pServerConfig->pEndpoints = OpcUa_Alloc(sizeof(UaServer_Endpoint));
UaServer_Endpoint_Initialize(&pServerConfig->pEndpoints[0]);
pEndpoint = &pServerConfig->pEndpoints[0];
/* Set the endpoint URL */
OpcUa_String_AttachReadOnly(&pEndpoint->sEndpointUrl, szEndpointURL);
/* This example does not use security, disable PKI */
pEndpoint->PkiConfig.strPkiType = OPCUA_PKI_TYPE_NONE;
/* Set the endpoint configuration to use no security */
pEndpoint->uNoOfSecurityPolicyConfigurations = 1;
pEndpoint->pSecurityPolicyConfigurations = OpcUa_Alloc(sizeof(OpcUa_Endpoint_SecurityPolicyConfiguration));
OpcUa_MemSet(pEndpoint->pSecurityPolicyConfigurations, 0, sizeof(OpcUa_Endpoint_SecurityPolicyConfiguration));
OpcUa_String_AttachReadOnly(&pEndpoint->pSecurityPolicyConfigurations[0].sSecurityPolicy, OpcUa_SecurityPolicy_None);
pEndpoint->pSecurityPolicyConfigurations[0].uMessageSecurityModes = OPCUA_ENDPOINT_MESSAGESECURITYMODE_NONE;
/* Set the endpoint configuration to use anonymous logon */
pEndpoint->uNoOfUserTokenPolicy = 1;
pEndpoint->pUserTokenPolicy = OpcUa_Alloc(sizeof(OpcUa_UserTokenPolicy));
OpcUa_UserTokenPolicy_Initialize(&pEndpoint->pUserTokenPolicy[0]);
pEndpoint->pUserTokenPolicy[0].TokenType = OpcUa_UserTokenType_Anonymous;
OpcUa_String_AttachReadOnly(&pEndpoint->pUserTokenPolicy[0].PolicyId, "Anonymous");

Initialize Server Provider

Now the providers have to be initialized. In this example only the server provider is used, so we just create and initialize the provider list as the server provider is added automatically by the SDK.

/* Initialize the provider list, the server provider is added automatically */
printf("UA Server: Building Provider List...\n");
uStatus = UaServer_ProviderList_Create(&uaServer);
OpcUa_GotoErrorIfBad(uStatus);
/* Load providers */
printf("UA Server: Loading Provider Modules...\n");
uStatus = UaServer_Providers_Initialize(&uaServer);
OpcUa_GotoErrorIfBad(uStatus);

Start Up Server

That was all we needed to do for initializing the server and we can start the server up.

/* Start up server */
uStatus = UaServer_StartUp(&uaServer);
OpcUa_GotoErrorIfBad(uStatus);
printf("\n#############################################");
printf("\n# Server started! Press %s to stop!", UASERVER_P_SHUTDOWN_SEQUENCE);
printf("\n#############################################\n\n");
printf("Endpoint URL: %s\n", szEndpointURL);

Running the Serve Loop

For running the server, UaServer_DoCom needs to be called in a loop. While the serve loop is running, OPC UA clients can connect to the server. UaServer_P_IsShutdownFlagSet checks for a shutdown key sequence pressed by the user and returns OpcUa_True if it was pressed.

/* Initialize the check for shutdown keystrokes. On Linux, default signal handlers
are added in UaServer_P_InitShutdownFlag. */
/******************************************************************************/
/* Serve! */
while (UaServer_P_IsShutdownFlagSet() == OpcUa_False && OpcUa_IsGood(uStatus))
{
uStatus = UaServer_DoCom();
}
OpcUa_GotoErrorIfBad(uStatus);
/******************************************************************************/
/* Clean up the check for shutdown keystrokes */

Shut Down OPC UA Server

The following code is used to shut down the OPC UA server:

/* UaServer_Clear clears the PkiConfig of the endpoint and attempts to free the strings
set there. As we have set literal string constants, we don't want those to be freed, so
we set them to NULL by initializing the structure. */
OpcUa_CertificateStoreConfiguration_Initialize(&pEndpoint->PkiConfig);
/* Clean up server */
UaServer_Clear(&uaServer);

Step 3: Add Include Directories

Add the following include paths to your application (SDK_INSTALL_DIR is the installation folder of the SDK):

  • <SDK_INSTALL_DIR>/include/uastack
  • <SDK_INSTALL_DIR>/include/serverlib

For more information see Library Overview.

Step 4: Add Linker Settings

Windows:

For Additional Library Directories enter the following values:

  • <SDK_INSTALL_DIR>/lib
  • <SDK_INSTALL_DIR>/third-party/win32/[VisualStudioVersion]/openssl/out32dll.dbg (Debug)
  • <SDK_INSTALL_DIR>/third-party/win32/[VisualStudioVersion]/openssl/out32dll (Release)

For Additional Dependencies (Debug) enter:

  • uastackd.lib
  • serverlibd.lib
  • models_did.lib
  • models_plcopend.lib
  • serverproviderd.lib
  • dataloggerd.lib
  • libeay32d.lib
  • ws2_32.lib

For Additional Dependencies (Release) enter:

  • uastack.lib
  • serverlib.lib
  • models_di.lib
  • models_plcopen.lib
  • serverprovider.lib
  • datalogger.lib
  • libeay32.lib
  • ws2_32.lib

Linux:

For Additional Library Directories enter the following values:

  • -L<SDK_INSTALL_DIR>/lib

For Additional Dependencies (Debug) enter:

  • -lserverlibd -lmodels_did -lmodels_plcopend -lserverproviderd -ldataloggerd -luastackd -lssl

For Additional Dependencies (Release) enter:

  • -lserverlib -lmodels_di -lmodels_plcopen -lserverprovider -ldatalogger -luastack -lssl
Note
Please remember that the link order is important for GCC!

For more information see Library Overview.

Step 5: Add Preprocessor Defines

Add:

UASERVER_HAVE_CONFIG

Additional define for Windows:

_CRT_SECURE_NO_WARNINGS

Step 6: Set Output Path

Set output path to bin where the UA stack (Windows: and OpenSSL) libraries reside.

Enter these values:

  • Output Directory: <SDK_INSTALL_DIR>/bin

Step 7: Run Application

Compile and run the server application.

Try to connect to the server with an OPC UA Client, e.g. UaExpert. Figure 1-1 shows how to add a server in UaExpert.

Figure 1-1 Add server to UaExpert

Double click on “<Double click to Add Server...>” beneath “Custom Discovery” and edit the server properties as shown in Figure 1-2.

Figure 1-2 Edit server properties.