UA Ansi C Server Professional  1.3.1.232
 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.

Content:

Utilities Used in this Lesson

The lesson uses servermain.c from examples/server_gettingstarted/lesson01.

Step 1: Create New Project

Set up a console application.

Windows:

Create a new project in the SDK base folder. Use the following settings:

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

Step 2: Add Files to the Project

Add the files listed below to your application:

  • examples/server_gettingstarted/lesson01/servermain.c

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 we use the helper function InitializeOpcUaStack. A default configuration is created and passed to the UA Stack initialization function of the SDK.

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
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"
#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;
}
/* Get fully qualified hostname. */
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;
strncpy( 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);
/* Setup 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;
}
/* Cleanup 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.

#ifdef _WIN32
# include <winsock2.h>
#else
# include <unistd.h>
# include <stdlib.h>
# include <netdb.h>
#endif
/* Server/application URLs, URIs and names */
#define UASERVER_PORT 4842
#define UASERVER_APPLICATIONURI "UnifiedAutomation:AnsiCSDKSampleServer:Lesson01"
#define UASERVER_PRODUCTURI "http://www.unifiedautomation.com/server-sdk.htm"
#define UASERVER_APPLICATIONNAME "ANSI C SDK UA Sample Server Lesson01"
#define UASERVER_MANUFACTURERNAME "UnifiedAutomation"
#define UASERVER_SOFTWAREVERSION "1.2.1"
#define UASERVER_BUILDNUMBER "123"
#define UASERVER_BUILDDATE "2011-05-23T14:05:50.000Z"

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 object is initialized. The PKI settings are set to use no security, hence the security parameters are left empty.

The configuration structure of the server contains additional members that are used to create the address space of the server, these members are filled using the defines from above.

/* 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 without security */
&uaServer,
szEndpointURL,
szApplicationUri,
UASERVER_PRODUCTURI,
UASERVER_APPLICATIONNAME,
OpcUa_NO_PKI,
"",
"",
"",
"",
"");
OpcUa_GotoErrorIfBad(uStatus);
/* Get configuration structure of the server */
pServerConfig = UaServer_GetConfiguration(&uaServer);
/* Configure BuildInfo of the server */
OpcUa_String_AttachReadOnly(&pServerConfig->BuildInfo.ManufacturerName, UASERVER_MANUFACTURERNAME);
OpcUa_String_AttachReadOnly(&pServerConfig->BuildInfo.ProductName, UASERVER_APPLICATIONNAME);
OpcUa_String_AttachReadOnly(&pServerConfig->BuildInfo.SoftwareVersion, UASERVER_SOFTWAREVERSION);
OpcUa_String_AttachReadOnly(&pServerConfig->BuildInfo.BuildNumber, UASERVER_BUILDNUMBER);
OpcUa_String_AttachReadOnly(&pServerConfig->BuildInfo.ProductUri, UASERVER_APPLICATIONURI);
OpcUa_DateTime_GetDateTimeFromString(UASERVER_BUILDDATE, &pServerConfig->BuildInfo.BuildDate);

Setting the endpoint configuration
In this example, we want to use no authentication, so we need to clear all preconfigured policies and only add the Anonymous user token policy.

/* Clean preconfigured endpoint configuration */
for (i = 0; i < pServerConfig->uNoOfUserTokenPolicy; i++)
{
OpcUa_UserTokenPolicy_Clear(&pServerConfig->pUserTokenPolicy[i]);
}
OpcUa_Free(pServerConfig->pUserTokenPolicy);
/* Set the endpoint configuration to use no security and anonymous logon */
pServerConfig->uNoOfUserTokenPolicy = 1;
pServerConfig->pUserTokenPolicy = (OpcUa_UserTokenPolicy*)OpcUa_Alloc(sizeof(OpcUa_UserTokenPolicy));
OpcUa_UserTokenPolicy_Initialize(&pServerConfig->pUserTokenPolicy[0]);

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 server provider */
printf("UA Server: Building Provider List...\n");
uStatus = UaServer_ProviderList_Create(&uaServer);
OpcUa_GotoErrorIfBad(uStatus);
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. When 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 */
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 */
UaServer_P_ClearShutdownFlag();

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

/* Clean up server */
UaServer_Clear(&uaServer);

Step 3: Add Include Directories

Add the following include paths to your application:

  • ../../include/uastack
  • ../../include/serverlib

For more information see Library Overview.

Step 4: Add Linker Settings

Windows:
For Additional Library Directories enter the following values:

  • ../../lib
  • ../../third-party/win32/[VisualStudioVersion]/openssl/out32dll.dbg

For Additional Dependencies enter:

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

Linux:
For Additional Library Directories enter the following values:

  • -L../../lib

For Additional Dependencies enter:

  • -lserverlibd -lmodels_did -lmodels_plcopend -lserverproviderd -luastack -lssl

Please remember that the link order is important for GCC!

For more information see Library Overview.

Step 5: Add Preprocessor Defines

Windows:
Add:

  • OPCUA_SUPPORT_PKI=1
  • OPCUA_SUPPORT_SECURITYPOLICY_BASIC128RSA15=1
  • OPCUA_SUPPORT_SECURITYPOLICY_BASIC256=1
  • OPCUA_SUPPORT_SECURITYPOLICY_NONE=1
  • OPCUA_HAVE_HTTPS=0
  • OPCUA_P_SOCKETMANAGER_SUPPORT_SSL=0
  • UASERVER_SERVICES_HISTORYREAD=1
  • UASERVER_SERVICES_HISTORYUPDATE=1
  • UASERVER_SERVICES_CALL=1
  • UASERVER_SUPPORT_EVENTS=1
  • OPCUA_MULTITHREADED=0
  • OPCUA_USE_SYNCHRONISATION=0
  • UASERVER_SUPPORT_AUTHORIZATION=1
  • UASERVER_SUPPORT_AUTHENTICATION_INTERNAL=1
  • UASERVER_SUPPORT_AUTHENTICATION_WIN32=1
  • UASERVER_SUPPORT_DISCOVERY=1
  • _UA_STACK_USE_DLL
  • _CRT_SECURE_NO_DEPRECATE
  • _CRT_SECURE_NO_WARNINGS

Step 6: Set Output Path

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

Windows:
Enter these values:

  • Output Directory: ../../bin

Step 7: Run Application

Compile and run the server application.

Try to connect to the server. Run UaExpert for it. Figure 1-1 shows how to add the server.

Figure 1-1 Add server to UaExpert

Edit the server properties as shown in Figure 1-2.

Figure 1-2 Edit server properties.