UA Server SDK C++ Bundle
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Friends Groups Pages
C++ SDK Demo Server

The C++ SDK Demo Server is a collection of examples for developing different features of an OPC UA server. It is mainly composed of the final results of the Server Getting Started lessons with instance nodes in the BuildingAutomation folder and the Unified Automation Demo address space with nodes in the Demo folder.

The complete project can be found in Examples → Server Cpp Demo.

User Authentication Example

There are several steps necessary to implement user authentication and user authorization. The provided sample code for user authentication in the first three steps provides a lot of reusable code. User authorization shown in the rest of the example is very application specific and the example shows one simplified option.

The example for user authentication is applied to the Unified Automation Demo address space. Depending on the user, access to nodes in Objects → Demo → 005_AccessRights is limited. The server has the three users John, Operator and Admin. The password equals user name. John and Operator are members of the Operators group. Admin is member of the Administrators group.

The example is composed of the following steps:

  1. An application specific session class derived from UaSession must be implemented. It must store whatever is needed later on for user authorization. This could be a role, the user name, a user group or the OPC UA rights the user has. The example provides a class MySession that stores a role and the user name. It is implemented in the files mysession.h and mysession.cpp.
  2. ServerConfig::createSession() must be implemented to create your application-specific session object and ServerConfig::logonSessionUser() must be implemented to authenticate the user and to store the user context in the session object. Since the helper class OpcServer is used to implement the main server entry, the callback interface OpcServerCallback must be implemented to provide this functionality. The class MyServerCallback implements the callback in the files myservercallback.h and myservercallback.cpp.
  3. You need to set the callback in the OpcServer object. This happens in the file servermain.cpp after creating the OpcServer object.
  4. User Authorization: All SDK interface calls triggered by an OPC UA client service call have the Session object as parameter. You can cast the Session to your session class (like MySession) and get all user information you need to decide whether the user is allowed to access information.

On the toolkit layer used in the example there are different methods you can overwrite or callbacks you can implement to influence the behaviour based on the user context. These overwrites are implemented in the class NodeManagerImpl (see files demo_nodemanagerimpl.h and demo_nodemanagerimpl.cpp).

The following methods overwrite default functionality in the class NodeManagerImpl

  • browseNode() and returnBrowseResultNode() implement the interface BrowseUaNodeCallback defined by NodeManagerUaNode. They allow restricting browse results.
  • beforeSetAttributeValue() allows to restrict write operations
  • afterGetAttributeValue() allows to restrict read operations
  • beforeMonitorAttributeValue() allows to restrict monitoring of nodes

Sampling on Request


An example on how to update variable values is already shown in the building automation server introduced in the Server Getting Started Tutorials. This section describes a different approach.

In this tutorial, the variable value handling is set to UaVariable_Value_Cache to indicate that the value needs to be polled through readValues() if a client is interested in the latest value. The method readValues() is called by the SDK for a Read service call and value change checks for monitored items.

If a server should decide whether a value is checked for changes, the value handling of these variables can be set to UaVariable_Value_CacheIsSource | UaVariable_Value_CacheIsUpdatedOnRequest. In this case, the implementer is informed about changes in monitoring and can implement his or her own logic for checking currently monitored variables for changes. The methods readValues() and writeValues() are still called for Read and Write service calls from clients.

The following code snippets show the differences to the tutorial code and can be found in the following files

  • controllerobject.cpp
  • nmbuildingautomation.h
  • nmbuildingautomation.cpp

Alterations are marked by comments in the following form:

// SamplingOnRequestExample change begin
// SamplingOnRequestExample change end


Replace the following line in the file controllerobject.cpp

// Change value handling to get read and write calls to the node manager

by this code snippet:

// Set value handling to handle sampling on request
// Change value of variable to bad status BadWaitingForInitialData
// This makes sure we do not deliver an old value before we update the cache with internal monitoring
UaDataValue badStatusValue;
pAnalogItem->setValue(NULL, badStatusValue, OpcUa_False);

The most important change in nmbuildingautomation.h is to overwrite IOManagerUaNode::variableCacheMonitoringChanged(). This method informs the derived class that the monitoring for a variable has been changed.

In addition, the class is derived from UaThread to implement the sampling in a background worker thread. The other additions, like the method run() or the member variables, are necessary to sample the variables which are active in monitoring.

class NmBuildingAutomation :
// SamplingOnRequestExample change begin
// Added: Worker thread to execute the sampling
public UaThread,
// SamplingOnRequestExample change end
virtual ~NmBuildingAutomation();
// SamplingOnRequestExample change begin
// Added: Overwrite of function variableCacheMonitoringChanged() to get informed by NodeManagerBase
void variableCacheMonitoringChanged(UaVariableCache* pVariable, TransactionType transactionType);
// Added: Main function for worker thread used to execute the sampling
void run();
// SamplingOnRequestExample change end
// SamplingOnRequestExample change begin
// Added: Member variables for internal sampling in worker thread
bool m_stopThread;
UaMutex m_mutexMonitoredVariables;
bool m_changedMonitoredVariables;
std::map<UaVariableCache*, UaVariableCache*> m_mapMonitoredVariables;
UaVariableArray m_arrayMonitoredVariables;
// SamplingOnRequestExample change end

The new members are initialized in the constructor of the class NmBuildingautomation():

: NodeManagerBase("urn:UnifiedAutomation:CppDemoServer:BuildingAutomation", OpcUa_True),
// SamplingOnRequestExample change begins
// Initialization of new members
// SamplingOnRequestExample change ends
m_pCommIf = new BaCommunicationInterface;

Start the sampling worker thread in NmBuildingautomation::afterStartUp():

// SamplingOnRequestExample change begins
// Start worker thread
// SamplingOnRequestExample change ends
return ret;

Stop the sampling worker thread in NmBuildingAutomation::beforeShutDown():

UaStatus NmBuildingAutomation::beforeShutDown()
UaStatus ret;
#if SUPPORT_Historical_Access
#endif // SUPPORT_Historical_Access
// SamplingOnRequestExample change begins
// Stop worker thread
m_stopThread = true;
// Wait for thread completion
// SamplingOnRequestExample change ends
return ret;

To configure the sampling in the worker thread we have to implement variableCacheMonitoringChanged(). Based on the UaVariableCache::signalCount() the variable is added to sampling when the first monitored item is created, and removed from sampling when the last monitored item is removed. UaVariableCache::getMinSamplingInterval() returns the shortest sampling interval currently used for the variable. This information is not used in this example but it may be used if variables can be sampled with different rates.

/* Overwrite of base class function to get informed by NodeManagerBase about a change in monitoring
void NmBuildingAutomation::variableCacheMonitoringChanged(UaVariableCache* pVariable, TransactionType transactionType)
// Just make sure only handle the right variables
// Get fastest requested sampling interval requested by a client
// Can be used to change polling rate to device if fastest rate changed
OpcUa_Double fastedRequestedRate = pVariable->getMinSamplingInterval();
// This is not used in this example
if ( (transactionType == IOManager::TransactionMonitorBegin) && (pVariable->signalCount() == 1) )
// The first monitored item was created for variable (pVariable)
// Lock access to variable list
UaMutexLocker lock(&m_mutexMonitoredVariables);
// Add to map and set changed flag
m_mapMonitoredVariables[pVariable] = pVariable;
m_changedMonitoredVariables = true;
// Increment reference counter for the entry in the map
else if ( (transactionType == IOManager::TransactionMonitorStop) && (pVariable->signalCount() == 0) )
// The last monitored item was removed for variable (pVariable)
// Lock access to variable list
UaMutexLocker lock(&m_mutexMonitoredVariables);
// Add to map and set changed flag
std::map<UaVariableCache*, UaVariableCache*>::iterator it = m_mapMonitoredVariables.find(pVariable);
if ( it != m_mapMonitoredVariables.end() )
m_changedMonitoredVariables = true;
// Decrement reference counter since we removed the entry from the map

The internal sampling is implemented in the main method of the worker thread:

void NmBuildingAutomation::run()
UaStatus ret;
OpcUa_UInt32 i;
OpcUa_UInt32 count;
std::map<UaVariableCache*, UaVariableCache*>::iterator it;
while ( m_stopThread == false )
// Lock access to variable list
UaMutexLocker lock(&m_mutexMonitoredVariables);
// Check if the list was changed
if ( m_changedMonitoredVariables )
// Update list for sampling
// First release reference for all variables in array
count = m_arrayMonitoredVariables.length();
for ( i=0; i<count; i++ )
// Check if the variable is still used
it = m_mapMonitoredVariables.find((UaVariableCache*)m_arrayMonitoredVariables[i]);
if ( it == m_mapMonitoredVariables.end() )
// Change value of variable to bad status BadWaitingForInitialData - it is not longer used
// This makes sure we do not deliver an old value when the monitoring is activated later for this variable
UaDataValue badStatusValue;
m_arrayMonitoredVariables[i]->setValue(NULL, badStatusValue, OpcUa_False);
// Decrement reference counter for the variable - we removed it from the list
// And clear old array
// Create the new array and increment reference counter for added variables
it = m_mapMonitoredVariables.begin();
count = m_arrayMonitoredVariables.length();
for ( i=0; i<count; i++ )
m_arrayMonitoredVariables[i] = it->first;
// Increment reference counter - it was added to the list
// Reset the change flag
m_changedMonitoredVariables = false;
// Check if we have anything to sample
if ( m_arrayMonitoredVariables.length() > 0 )
count = m_arrayMonitoredVariables.length();
// Call readValues to update variable values
ret = readValues(m_arrayMonitoredVariables, results);
if ( ret.isGood() )
// Update values
for ( i=0; i<count; i++ )
m_arrayMonitoredVariables[i]->setValue(NULL, results[i], OpcUa_True);
// Set bad status for all variables
UaDataValue badStatusValue;
for ( i=0; i<count; i++ )
m_arrayMonitoredVariables[i]->setValue(NULL, badStatusValue, OpcUa_True);

Loading Address Space from UANodeSet XML File

The demo server contains an example for loading all or part of the address space from a UANodeSet XML file.

The sample code can be found in

  • servermain.cpp
  • mynodemanagernodesetxmlcreator.h
  • mynodemanagernodesetxmlcreator.cpp
  • mynodemanagernodesetxml.h
  • mynodemanagernodesetxml.cpp
  • buildingautomationxml.xml

The example XML file is located in the directory “bin” of the demo server executable. The file buildingautomationxml.xml contains the same controller objects as the building automation server introduced in the Server Getting Started Tutorials. Some of the sample code to connect the loaded variables to the controller simulation is similar to the code used in the tutorials.

The parser and the necessary classes are initialized and added with the following code in the file servermain.cpp.

// Create and initialize server object
OpcServer* pServer = new OpcServer;
pServer->setServerConfig(sConfigFileName, szAppPath);
// XML UANodeSet file to load
UaString sNodesetFile(UaString("%1/buildingautomationxml.xml").arg(szAppPath));
// We create our own BaseNode factory to create the user data from XML
MyBaseNodeFactory* pBaseNodeFactory = new MyBaseNodeFactory;
// We create our own NodeManager creator to instantiate our own NodeManager
MyNodeManagerNodeSetXmlCreator* pNodeManagerCreator = new MyNodeManagerNodeSetXmlCreator;
UaNodeSetXmlParserUaNode* pXmlParser = new UaNodeSetXmlParserUaNode(sNodesetFile, pNodeManagerCreator, pBaseNodeFactory, NULL);
// Add UANodeSet XML parser as module
// Start server object
ret = pServer->start();

The class MyBaseNodeFactory is derived from UaBase::BaseNodeFactory and overwrites the method UaBase::BaseNodeFactory::createVariable(). The factory creates data classes for the OPC UA nodes in the XML file. These data classes are used in a second step to create the nodes in the NodeManager. Overwriting the factory allows the creation of specialized data classes. In the example, the data class MyVariable derived from UaBase::Variable is used to parse the extension in the XML file that contains the user data for the variable.

The class MyNodeManagerNodeSetXmlCreator is used to create specialized NodeManagers for a known namespace in the XML file. In this example the special NodeManager is implemented in the class MyNodeManagerNodeSetXml. This class implements all logic necessary to initialize the controller objects and to implement data access to variable values and methods as well as event and alarm handling.

Alarm Object Handling with and without Nodes in the Address Space

The demo address space code contains sample code for the handling of alarm objects of different types. The example includes alarm objects which show up as OPC UA nodes in the address space as well as alarm objects not visible in the address space. The latter are working completely without nodes in the address space.

The sample code is contained in the class NodeManagerDemo which can be found in the files

  • demo_nodemanagerdemo.h
  • demo_nodemanagerdemo.cpp

The alarm objects are created and initialized in the method createAlarmNodes(). The objects visible in the address space are also added to the NodeManager. These objects are deleted by the NodeManager at shutdown. In addition this method creates variables used to trigger the alarm states.

The alarm objects without nodes have to be deleted in the destructor of the NodeManagerDemo.

The state of the alarm objects can be activated by writing “true” to the OffNormalAlarm variables or by assigning analog values for the level alarms ranging from 0 to 100. Values below 30 trigger a low alarm. Values above 70 trigger a high alarm. To modify the alarm states on write, the method IOManagerUaNode::afterSetAttributeValue() is overwritten in NodeManagerDemo. This method contains the sample code for changing the alarm states.

To handle alarm acknowledgement the methods EventManagerUaNode::OnAcknowledge and EventManagerUaNode::OnConfirm are overwritten in the class NodeManagerDemo. This works only for alarm objects visible in the address space. To handle the Acknowledge and AddComment methods for alarm objects without nodes, the methods getMethodHandle() and beginCall() are implemented to provide the MethodManager functionality for these alarm objects.

Structured Data Type Example

The demo address space code contains sample code for the handling of structured data types. A variable with a nested structure can be found in Objects → BuildingAutomation → ControllerConfigurations. This variable contains a structure with two arrays of structures with the configuration parameters for all controllers.

The sample code can be found in the file nmbuildingautomation.cpp

The enumeration, structured data type nodes, and dictionary are created in the function NmBuildingAutomation::createTypeNodes().

The structure value is filled up in the function NmBuildingAutomation::readValues().

Event History Access Example

The demo address space code contains sample code for the handling of historical access for events. The area object providing event history can be found in Objects → Server → AreaAirConditioner. This object event notifier attribute indicates availablity of event history.

The sample code can be found in the files

  • nmbuildingautomation.cpp
  • historymanagercache.h
  • historymanagercache.cpp

The internal event monitored item is created in the function NmBuildingAutomation::afterStartUp().

The class HistoryManagerCache implements all data and event history functions and data and event historizing. The events and data changes are monitored with internal monitored items and are stored in memory.

The sample code expects that the nodes providing event history are managed by the NodeManager that also manages the HistoryManager. This is necessary to forward HistoryRead requests for a node to the right HistoryManager.

If a node is managed by another NodeManager, the responsible HistoryManager must be registered for the a UaNode or for the whole NodeManager. The class NodeManagerBase provides two options. The first is to use NodeManagerBase::setHistoryManager() to set the default HistoryManager for all UaNodes in the NodeManager. The second option is to set a HistoryManager for single UaNodes using the methods NodeManagerBase::setHistoryManagerForUaNode() and NodeManagerBase::removeHistoryManagerForUaNode().

The following sample code shows how to handle event history for the Server object managed by NodeManagerRoot.

HistoryManagerCache* pMyHistoryManager = NULL;
// Get history manager
// Get NodeManagerRoot and Server object pointers
UaObjectServer* pServerNode = pNodeManagerRoot->pServerObject();
// Create event monitored item for Server object to historize events in HistoryManager
// See HistoryManagerCache::addEventNotifierToHistorize for sample code
// Update the EventNotifier attribute of the Server object with HistoryRead flag
pServerNode->setEventNotifier(OpcUa_EventNotifiers_SubscribeToEvents | OpcUa_EventNotifiers_HistoryRead );
// Set the HistoryManager interface for the Server object
// The NodeManagerRoot manages the Server object node and needs to know that
// an external HistoryManager is now responsible for processing HistoryRead for Events
// for the Server object
pNodeManagerRoot->setHistoryManagerForUaNode(OpcUaId_Server, pMyHistoryManager);

Model Change Event Example

The demo address space code contains sample code for dynamic creation of nodes including model change event. The folder with the functionality can be found in Objects → Demo → 008_DynamicNodes. This folder contains two methods for node creation and deletion, a NodeVersion property and the dynamic node. Only nodes with a NodeVersion property are allowed to fire specific model change events of type GeneralModelChangeEventType. This event indicates the changed nodes with a NodeVersion property and the changes (node added/deleted, reference added/deleted). Since the new node does not have a NodeVersion property, only the added reference from the node (008_DynamicNodes) with NodeVersion property has a new reference contained in the model change event.

Changes of nodes without NodeVersion property can be indicated by using the BaseModelChangeEvent. But this event does not indicate which part of the address space has changed.

The sample code can be found in the files

  • demo_nodemanagerdemo.cpp
  • demo_nodemanagerdemo.h

During start-up of the server the GeneralModelChangeEventType must be created in the event type hierarchy. This can be done with the following code contained in NodeManagerDemo::afterStartUp().

The full sample code for the generation of the model change event can be found in the methods NodeManagerDemo::Demo_DynamicNodes_CreateDynamicNode() and NodeManagerDemo::Demo_DynamicNodes_DeleteDynamicNode(). The following code is the essential part of the event generation.

UaModelChangeStructureDataTypes changes;
// Prepare model change structure event data
// Only a node with a NodeVersion property (DemoId_Demo_DynamicNodes)
// indicates its changes -> we added a reference to this node
// Prepare model change event
eventData.setMessage(UaLocalizedText("", "Node added"));
// Fire model change event

The sample code creates only one change. If more than one node with a NodeVersion property is affected, all changes should be combined in one event.