UA Server SDK C++ Bundle  1.4.1.271
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Friends Groups Pages
Lesson 5: Register Nodes and Reconnection Scenarios

Step 1: Call registerNodes to Get Optimised NodeIds and Write Values Using the Optimised NodeIds

The method UaSession::registerNodes allows clients to optimize the cyclic access to Nodes for example for writing variable values or for calling methods. There are two levels of optimization.

The first level is to reduce the amount of data on the wire for the addressing information. Since NodeIds are used for addressing in Nodes and NodeIds can be very long, a more optimized addressing method is desirable for cyclic use of Nodes. Classic OPC provided the concept to create handles for items by adding them to a group. RegisterNodes provides a similar concept to create handles for Nodes by returning a numeric NodeId that can be used in all functions accessing information from the server. The transport of numeric NodeIds is very efficient in the OPC UA binary protocol.

The second level of optimization is possible inside the server. Since the client is telling the server that it wants to use the Node more frequently by registering the Node, the server is able to prepare everything that is possible to optimize the access to the Node.

The handles returned by the server are only valid during the lifetime of the Session that was used to register the Nodes. Clients must call unregisterNodes if the Node is no longer used to free the resources used in the server for the optimization. This method should not be used to optimize the cyclic read of data since OPC UA provides a much more optimized mechanism to subscribe for data changes.

Clients do not have to use the Service and servers can simply implement the Service only returning the same list of NodeIds that was passed in if there is no need to optimize the access.

Add the following code to sampleclient.h:

...
// OPC UA service calls
UaStatus discover();
UaStatus connect();
UaStatus connectSecure();
UaStatus disconnect();
UaStatus browseSimple();
UaStatus browseContinuationPoint();
UaStatus read();
UaStatus write();
UaStatus subscribe();
UaStatus unsubscribe();
UaStatus registerNodes(); // Add this line
UaStatus unregisterNodes(); // Add this line
...
private:
UaSession* m_pSession;
SampleSubscription* m_pSampleSubscription;
Configuration* m_pConfiguration;
UaClient::ServerStatus m_serverStatus;
UaNodeIdArray m_registeredNodes; // Add this line
};

Add the method registerNodes to sampleclient.cpp:

UaStatus SampleClient::registerNodes()
{
UaStatus result;
ServiceSettings serviceSettings;
UaNodeIdArray nodesToRegister;
// register all nodes to write from the configuration
nodesToRegister = m_pConfiguration->getNodesToWrite();
printf("\nRegisterNodes...\n");
result = m_pSession->registerNodes(
serviceSettings,
nodesToRegister,
m_registeredNodes);
if (result.isGood())
{
// RegisterNodes service succeded
printf("RegisterNodes succeeded\n");
for (OpcUa_UInt32 i = 0; i < nodesToRegister.length(); i++)
{
printf("Original NodeId[%d]: %s\tRegistered NodeId[%d]: %s\n", i, UaNodeId(nodesToRegister[i]).toXmlString().toUtf8(), i, UaNodeId(m_registeredNodes[i]).toXmlString().toUtf8());
}
}
else
{
// Service call failed
printf("RegisterNodes failed with status %s\n", result.toString().toUtf8());
}
return result;
}

The nodes to register are read from the configuration file (the nodes to write), then UaSession::registerNodes is called.

If successful, the original NodeId and the registered NodeId are displayed.

Documentation:

Add the method unregisterNodes to sampleclient.cpp:

UaStatus SampleClient::unregisterNodes()
{
UaStatus result;
ServiceSettings serviceSettings;
// unregister all nodes we registered before
printf("\nUnregisterNodes...\n");
result = m_pSession->unregisterNodes(
serviceSettings,
m_registeredNodes);
if (result.isGood())
{
// RegisterNodes service succeded
printf("UnregisterNodes succeeded\n");
}
else
{
// Service call failed
printf("UnregisterNodes failed with status %s\n", result.toString().toUtf8());
}
return result;
}

The method calls UaSession::unregisterNodes

Extend method SampleClient::connectionStatusChanged

case UaClient::NewSessionCreated:
printf("Connection status changed to NewSessionCreated\n");
m_pConfiguration->updateNamespaceIndexes(m_pSession->getNamespaceTable());
// New code begins
// the registered nodes are no longer valid in the new session
registerNodes();
// New code ends
break;

As the registering of nodes is only valid for the lifetime of a Session, the nodes have to be registered again when a new session is created.

To actually write the registered nodes, we create a new method writeRegistered.

Add new method writeRegistered and new helper method writeInternal to sampleclient.h:

// OPC UA service calls
...
UaStatus read();
UaStatus write();
UaStatus writeRegistered(); // Add this line
UaStatus subscribe();
UaStatus unsubscribe();
UaStatus registerNodes();
UaStatus unregisterNodes();
private:
// helper methods
UaStatus browseInternal(const UaNodeId& nodeToBrowse, OpcUa_UInt32 maxReferencesToReturn);
UaStatus connectInternal(const UaString& serverUrl, SessionSecurityInfo& sessionSecurityInfo);
UaStatus writeInternal(const UaNodeIdArray& nodesToWrite, const UaVariantArray& valuesToWrite); // Add this line
UaStatus findSecureEndpoint(SessionSecurityInfo& sessionSecurityInfo);
UaStatus checkServerCertificateTrust(SessionSecurityInfo& sessionSecurityInfo);
void printBrowseResults(const UaReferenceDescriptions& referenceDescriptions);
void printCertificateData(const UaByteString& serverCertificate);
int userAcceptCertificate();

Add the helper method writeInternal to sampleclient.cpp. It will be used by both methods write and writeRegistered. Note that it contains code which was previously used in the method SampleClient::write with some modifications.

UaStatus SampleClient::writeInternal(const UaNodeIdArray& nodesToWrite, const UaVariantArray& valuesToWrite)
{
UaStatus result;
ServiceSettings serviceSettings;
UaWriteValues writeValues;
UaStatusCodeArray results;
UaDiagnosticInfos diagnosticInfos;
// check in parameters
if (nodesToWrite.length() != valuesToWrite.length())
{
return OpcUa_BadInvalidArgument;
}
// write all nodes from the configuration
writeValues.create(nodesToWrite.length());
for (OpcUa_UInt32 i = 0; i < writeValues.length(); i++)
{
writeValues[i].AttributeId = OpcUa_Attributes_Value;
OpcUa_NodeId_CopyTo(&nodesToWrite[i], &writeValues[i].NodeId);
// set value to write
OpcUa_Variant_CopyTo(&valuesToWrite[i], &writeValues[i].Value.Value);
}
printf("\nWriting...\n");
result = m_pSession->write(
serviceSettings,
writeValues,
results,
diagnosticInfos);
if (result.isGood())
{
// Write service succeded - check individual status codes
for (OpcUa_UInt32 i = 0; i < results.length(); i++)
{
if (OpcUa_IsGood(results[i]))
{
printf("Write succeeded for item[%d]\n", i);
}
else
{
printf("Write failed for item[%d] with status %s\n", i, UaStatus(results[i]).toString().toUtf8());
}
}
}
else
{
// Service call failed
printf("Write failed with status %s\n", result.toString().toUtf8());
}
return result;
}

The helper method takes two parameters, the nodes to write and the values to write.

Replace the existing method write with the following code. Note that the helper method writeInternal is used.

UaStatus SampleClient::write()
{
UaStatus result;
// write all nodes from the configuration
result = writeInternal(
m_pConfiguration->getNodesToWrite(),
m_pConfiguration->getWriteValues());
return result;
}

This method reads the nodes and values to write from the configuration file.

Add a new method writeRegistered to sampleclient.cpp

UaStatus SampleClient::writeRegistered()
{
UaStatus result;
// write all registered nodes
result = writeInternal(
m_registeredNodes,
m_pConfiguration->getWriteValues());
return result;
}

writeRegistered writes the nodes that have been registered before. The values to write are read from the configuration file.

Modify main:

Replace everything starting from

// Loading configuration succeeded

to (not including these lines)

// Wait for user command.
printf("\nPress Enter to disconnect\n");

with the following code:

// Loading configuration succeeded
if (status.isGood())
{
// set configuration
pMyClient->setConfiguration(pMyConfiguration);
// Connect to OPC UA Server
status = pMyClient->connect();
}
else
{
delete pMyConfiguration;
pMyConfiguration = NULL;
}
// Connect succeeded
if (status.isGood())
{
// register nodes and do a write with the registered nodeIds
pMyClient->registerNodes();
pMyClient->writeRegistered();

Step 2: Read List of Monitored Items from Configuration

In Lesson 2: Create DataMonitoredItem, we already monitored data changes of a node using a hard coded NodeId. In this step, we will replace this hard coded NodeId by a list of monitored items retrieved from configuration.

First we extend the class Configuration.

Add the following code to configuration.h

...
// get lists of NodeIds and values
UaNodeIdArray getNodesToRead() const;
UaNodeIdArray getNodesToWrite() const;
UaNodeIdArray getNodesToMonitor() const; // Add this line
UaVariantArray getWriteValues() const;
...
private:
...
// NamespaceArray and NodeIds
UaStringArray m_namespaceArray;
UaNodeIdArray m_nodesToRead;
UaNodeIdArray m_nodesToWrite;
UaNodeIdArray m_nodesToMonitor; // Add this line
UaVariantArray m_writeValues;
...

Add the method Configuration::getNodesToMonitor to configuration.cpp.

UaNodeIdArray Configuration::getNodesToMonitor() const
{
return m_nodesToMonitor;
}

Now we have to extend the method Configuration::loadConfiguration to read the nodes to monitor from the configuration file.

...
pSettings->endGroup(); // NodesToWrite
// New code begins
// Read NodeIds to use for monitoring
m_nodesToMonitor.clear();
pSettings->beginGroup("NodesToMonitor");
value = pSettings->value("size", (OpcUa_UInt32)0);
value.toUInt32(size);
m_nodesToMonitor.resize(size);
for (i=0; i<size;i++)
{
sTempKey = UaString("Variable0%1").arg((int)i);
value = pSettings->value(sTempKey.toUtf16(), UaString(""));
UaNodeId::fromXmlString(value.toString()).copyTo(&m_nodesToMonitor[i]);
}
pSettings->endGroup(); // NodesToMonitor
// New code ends
pSettings->endGroup(); // UaClientConfig
...

Then we extend the method Configuration::updateNamespaceIndexes to replace the namespace index of the nodes to monitor as well.

...
// NodeToWrite
for (i = 0; i < m_nodesToWrite.length(); i++)
{
m_nodesToWrite[i].NamespaceIndex = mappingTable[m_nodesToWrite[i].NamespaceIndex];
}
// New code begins
// NodesToMonitor
for (i = 0; i < m_nodesToMonitor.length(); i++)
{
m_nodesToMonitor[i].NamespaceIndex = mappingTable[m_nodesToMonitor[i].NamespaceIndex];
}
// New code ends
// update namespace array
...

The class Subscription has to be modified so that we can retrieve the items to monitor from the configuration file.

Add the following code to samplesubscription.h:

...
using namespace UaClientSdk;
class Configuration; // Add this line
class SampleSubscription :
public UaSubscriptionCallback
{
UA_DISABLE_COPY(SampleSubscription);
public:
SampleSubscription(Configuration* pConfiguration); // Modifiy this line
virtual ~SampleSubscription();
...
// Create monitored items in the subscription
UaStatus createMonitoredItems();
// New code begins
// set the configuration where we get the list of NodeIds to monitored from.
void setConfiguration(Configuration* pConfiguration);
// New code ends
private:
UaSession* m_pSession;
UaSubscription* m_pSubscription;
Configuration* m_pConfiguration; // Add this line
};
#endif // SAMPLESUBSCRIPTION_H

Add include to samplesubscription.cpp:

#include "samplesubscription.h"
#include "uasubscription.h"
#include "uasession.h"
#include "configuration.h" // Add this line

Replace the constructor of SampleSubscription with the following code:

SampleSubscription::SampleSubscription(Configuration* pConfiguration)
: m_pSession(NULL),
m_pSubscription(NULL),
m_pConfiguration(pConfiguration)
{
}

Add the method SampleSubscription::setConfiguration:

void SampleSubscription::setConfiguration(Configuration* pConfiguration)
{
m_pConfiguration = pConfiguration;
}

We have to replace the hard coded NodeId of the item to monitor in SampleSubscription::createMonitoredItems with the nodes to monitor retrieved from the configuration file.

Replace

UaStatus result;
OpcUa_UInt32 i;
ServiceSettings serviceSettings;
UaMonitoredItemCreateRequests itemsToCreate;
UaMonitoredItemCreateResults createResults;
// Configure one item to add to subscription
// We monitor the value of the ServerStatus -> CurrentTime
itemsToCreate.create(1);
itemsToCreate[0].ItemToMonitor.AttributeId = OpcUa_Attributes_Value;
itemsToCreate[0].ItemToMonitor.NodeId.Identifier.Numeric = OpcUaId_Server_ServerStatus_CurrentTime;
itemsToCreate[0].RequestedParameters.ClientHandle = 1;
itemsToCreate[0].RequestedParameters.SamplingInterval = 100;
itemsToCreate[0].RequestedParameters.QueueSize = 1;
itemsToCreate[0].RequestedParameters.DiscardOldest = OpcUa_True;
itemsToCreate[0].MonitoringMode = OpcUa_MonitoringMode_Reporting;

with

UaStatus result;
OpcUa_UInt32 size, i;
ServiceSettings serviceSettings;
UaMonitoredItemCreateRequests itemsToCreate;
UaMonitoredItemCreateResults createResults;
// Configure items to add to the subscription
UaNodeIdArray lstNodeIds = m_pConfiguration->getNodesToMonitor();
size = lstNodeIds.length();
itemsToCreate.create(size);
for (i = 0; i < size; i++)
{
itemsToCreate[i].ItemToMonitor.AttributeId = OpcUa_Attributes_Value;
OpcUa_NodeId_CopyTo(&lstNodeIds[i], &itemsToCreate[i].ItemToMonitor.NodeId);
itemsToCreate[i].RequestedParameters.ClientHandle = i;
itemsToCreate[i].RequestedParameters.SamplingInterval = 500;
itemsToCreate[i].RequestedParameters.QueueSize = 1;
itemsToCreate[i].RequestedParameters.DiscardOldest = OpcUa_True;
itemsToCreate[i].MonitoringMode = OpcUa_MonitoringMode_Reporting;
}

Add the following include to sampleclient.cpp

#include "sampleclient.h"
#include "uasession.h"
#include "samplesubscription.h"
#include "configuration.h"
#include "uadiscovery.h"
#include "uapkicertificate.h"
#include "uaplatformdefs.h" // Add this line

Replace the constructor of SampleClient with the following code:

SampleClient::SampleClient()
{
m_pSession = new UaSession();
m_pConfiguration = new Configuration();
m_pSampleSubscription = new SampleSubscription(m_pConfiguration);
}

Extend the method SampleClient::setConfiguration

void SampleClient::setConfiguration(Configuration* pConfiguration)
{
// New code begins
if (m_pSampleSubscription)
{
m_pSampleSubscription->setConfiguration(pConfiguration);
}
// New code ends
if (m_pConfiguration)
{
delete m_pConfiguration;
}
m_pConfiguration = pConfiguration;
}

Finally, we have to extend main to create a subscription:

...
// Connect succeeded
if (status.isGood())
{
// register nodes and do a write with the registered nodeIds
pMyClient->registerNodes();
pMyClient->writeRegistered();
// New code begins
// Wait for user command.
printf("\n*************************************************************************\n");
printf("* Press Enter to create a subscription\n");
printf("* Press Enter again to stop the subscription\n");
printf("*************************************************************************\n");
getchar();
// Create subscription
status = pMyClient->subscribe();
if (status.isGood())
{
// Wait for user command.
getchar();
// Delete subscription
status = pMyClient->unsubscribe();
}
// New code ends
// Wait for user command.
printf("\nPress Enter to disconnect\n");
getchar();
...

Step 3: Enhance Subscription to Automatically Handle Reconnection Scenarios

In this step we enhance the client to automatically recover the subcription after losing the connection to the server. First we add the method recoverSubscription to the class SampleSubscription.

Add the following code to samplesubscription.h

...
public:
...
// New code begins
private:
UaStatus recoverSubscription();
// New code ends
private:
UaSession* m_pSession;
UaSubscription* m_pSubscription;
Configuration* m_pConfiguration;
};

Add the method SampleSubscription::recoverSubscription:

UaStatus SampleSubscription::recoverSubscription()
{
UaStatus result;
// delete existing subscription
if (m_pSubscription)
{
deleteSubscription();
}
// create a new subscription
result = createSubscription(m_pSession);
// create monitored items
if (result.isGood())
{
result = createMonitoredItems();
}
printf("-------------------------------------------------------------\n");
if (result.isGood())
{
printf("RecoverSubscription succeeded.\n");
}
else
{
printf("RecoverSubscription failed with status %s\n", result.toString().toUtf8());
}
printf("-------------------------------------------------------------\n");
return result;
}

When this method is called (see below) the subscription is no longer valid. To free the resource for the subscription in the SDK we still have to call deleteSubscription. This causes the SDK to delete the subscription object. After that a new subscription with monitored items is created.

We extend the method SampleSubscription::subscriptionStatusChanged to recover the subscription in case it is no longer valid.

void SampleSubscription::subscriptionStatusChanged(
OpcUa_UInt32 clientSubscriptionHandle,
const UaStatus& status)
{
OpcUa_ReferenceParameter(clientSubscriptionHandle); // We use the callback only for this subscription
// New code begins
if (status.isBad())
{
// New code ends
printf("Subscription not longer valid - failed with status %s\n", status.toString().toUtf8());
// New code begins
// recover subscription on the server
recoverSubscription();
}
// New code ends
}

Complete main to demonstrate the recovery of the subscription. As the nodes to write are re-registered when creating a new session, writeRegistered can still be called after the connection interruption.

...
// Wait for user command.
printf("\n*************************************************************************\n");
printf("* Press Enter to create a subscription\n");
// New code begins
printf("* Once you see DataChange Notifications - kill and restart the server.\n");
printf("* You will see the client reconnect and recover the subscription.\n");
// New code ends
printf("* Press Enter again to stop the subscription\n");
printf("*************************************************************************\n");
getchar();
// Create subscription
status = pMyClient->subscribe();
if (status.isGood())
{
// Wait for user command.
getchar();
// Delete subscription
status = pMyClient->unsubscribe();
}
// New code begins
// after the connection interruption the registered nodes have been restored in the SampleClient.
// write registered again and unregister the nodes
pMyClient->writeRegistered();
pMyClient->unregisterNodes();
// New code ends
// Wait for user command.
printf("\nPress Enter to disconnect\n");
getchar();
...