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
#endif
#include <uaserver_module.h>
#include <uaserver_utilities.h>
OpcUa_StatusCode InitializeOpcUaStack(OpcUa_Handle *a_phProxyStubPlatformLayer,
OpcUa_ProxyStubConfiguration *a_pProxyStubConfiguration)
{
OpcUa_InitializeStatus(OpcUa_Module_Server, "InitializeOpcUaStack");
printf("UA Server: Initializing Stack...\n");
a_pProxyStubConfiguration->bProxyStub_Trace_Enabled = OpcUa_True;
a_pProxyStubConfiguration->uProxyStub_Trace_Level = OPCUA_TRACE_OUTPUT_LEVEL_ERROR;
OpcUa_ReturnStatusCode;
OpcUa_BeginErrorHandling;
OpcUa_FinishErrorHandling;
}
OpcUa_StatusCode CleanupOpcUaStack(OpcUa_Handle *a_phProxyStubPlatformLayer)
{
OpcUa_InitializeStatus(OpcUa_Module_Server, "CleanupOpcUaStack");
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;
}
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;
OpcUa_ReferenceParameter(argc);
OpcUa_ReferenceParameter(argv);
uStatus = InitializeOpcUaStack(&hProxyStubPlatformLayer, &proxyStubConfiguration);
if ( OpcUa_IsNotGood(uStatus) )
{
return EXIT_FAILURE;
}
uStatus = ServerMain();
if ( OpcUa_IsNotGood(uStatus) )
{
ret = EXIT_FAILURE;
}
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>
volatile sig_atomic_t iDoShutdown = 0;
static void shutdown_signal_handler(int signo)
{
OpcUa_ReferenceParameter(signo);
iDoShutdown = 1;
}
OpcUa_StatusCode install_signal_handler()
{
struct sigaction new_action, old_action;
new_action.sa_handler = shutdown_signal_handler;
sigemptyset(&new_action.sa_mask);
new_action.sa_flags = 0;
sigaction(SIGINT, NULL, &old_action);
if (old_action.sa_handler != SIG_IGN)
{
sigaction(SIGINT, &new_action, NULL);
}
sigaction(SIGTERM, NULL, &old_action);
if (old_action.sa_handler != SIG_IGN)
{
sigaction(SIGTERM, &new_action, NULL);
}
new_action.sa_handler = SIG_IGN;
sigemptyset(&new_action.sa_mask);
new_action.sa_flags = 0;
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.
#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.
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);
OpcUa_GotoErrorIfBad(uStatus);
OpcUa_String_AttachReadOnly(&pServerConfig->
ApplicationDescription.ApplicationName.Text, UASERVER_APPLICATIONNAME);
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.
pServerConfig->uNoOfEndpoints = 1;
UaServer_Endpoint_Initialize(&pServerConfig->
pEndpoints[0]);
OpcUa_String_AttachReadOnly(&pEndpoint->sEndpointUrl, szEndpointURL);
pEndpoint->
PkiConfig.strPkiType = OPCUA_PKI_TYPE_NONE;
pEndpoint->uNoOfSecurityPolicyConfigurations = 1;
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.
printf("UA Server: Building Provider List...\n");
OpcUa_GotoErrorIfBad(uStatus);
printf("UA Server: Loading Provider Modules...\n");
OpcUa_GotoErrorIfBad(uStatus);
Start Up Server
That was all we needed to do for initializing the server and we can start the server up.
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.
{
}
OpcUa_GotoErrorIfBad(uStatus);
Shut Down OPC UA Server
The following code is used to shut down the OPC UA server:
OpcUa_CertificateStoreConfiguration_Initialize(&pEndpoint->
PkiConfig);
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:
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.