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:
...
UaStatus discover();
...
private:
SampleSubscription* m_pSampleSubscription;
Configuration* m_pConfiguration;
UaClient::ServerStatus m_serverStatus;
UaNodeIdArray m_registeredNodes;
};
Add the method registerNodes to sampleclient.cpp:
{
ServiceSettings serviceSettings;
UaNodeIdArray nodesToRegister;
nodesToRegister = m_pConfiguration->getNodesToWrite();
printf("\nRegisterNodes...\n");
serviceSettings,
nodesToRegister,
m_registeredNodes);
{
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
{
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()
{
ServiceSettings serviceSettings;
printf("\nUnregisterNodes...\n");
serviceSettings,
m_registeredNodes);
{
printf("UnregisterNodes succeeded\n");
}
else
{
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");
registerNodes();
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:
...
UaStatus read();
private:
UaStatus browseInternal(
const UaNodeId& nodeToBrowse, OpcUa_UInt32 maxReferencesToReturn);
UaStatus connectInternal(
const UaString& serverUrl, SessionSecurityInfo& sessionSecurityInfo);
UaStatus writeInternal(
const UaNodeIdArray& nodesToWrite,
const UaVariantArray& valuesToWrite);
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)
{
ServiceSettings serviceSettings;
UaWriteValues writeValues;
UaStatusCodeArray results;
UaDiagnosticInfos diagnosticInfos;
if (nodesToWrite.length() != valuesToWrite.length())
{
return OpcUa_BadInvalidArgument;
}
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);
OpcUa_Variant_CopyTo(&valuesToWrite[i], &writeValues[i].Value.Value);
}
printf("\nWriting...\n");
result = m_pSession->
write(
serviceSettings,
writeValues,
results,
diagnosticInfos);
{
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
{
}
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.
{
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()
{
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
to (not including these lines)
printf("\nPress Enter to disconnect\n");
with the following code:
{
pMyClient->setConfiguration(pMyConfiguration);
status = pMyClient->connect();
}
else
{
delete pMyConfiguration;
pMyConfiguration = NULL;
}
{
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
...
UaNodeIdArray getNodesToRead() const;
UaNodeIdArray getNodesToWrite() const;
UaNodeIdArray getNodesToMonitor() const;
UaVariantArray getWriteValues() const;
...
private:
...
UaStringArray m_namespaceArray;
UaNodeIdArray m_nodesToRead;
UaNodeIdArray m_nodesToWrite;
UaNodeIdArray m_nodesToMonitor;
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();
m_nodesToMonitor.clear();
value = pSettings->
value(
"size", (OpcUa_UInt32)0);
m_nodesToMonitor.resize(size);
for (i=0; i<size;i++)
{
}
...
Then we extend the method Configuration::updateNamespaceIndexes to replace the namespace index of the nodes to monitor as well.
...
for (i = 0; i < m_nodesToWrite.length(); i++)
{
m_nodesToWrite[i].NamespaceIndex = mappingTable[m_nodesToWrite[i].NamespaceIndex];
}
for (i = 0; i < m_nodesToMonitor.length(); i++)
{
m_nodesToMonitor[i].NamespaceIndex = mappingTable[m_nodesToMonitor[i].NamespaceIndex];
}
...
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:
...
class Configuration;
class SampleSubscription :
public UaSubscriptionCallback
{
UA_DISABLE_COPY(SampleSubscription);
public:
SampleSubscription(Configuration* pConfiguration);
virtual ~SampleSubscription();
...
UaStatus createMonitoredItems();
void setConfiguration(Configuration* pConfiguration);
private:
Configuration* m_pConfiguration;
};
#endif // SAMPLESUBSCRIPTION_H
Add include to samplesubscription.cpp:
#include "samplesubscription.h"
#include "uasubscription.h"
#include "uasession.h"
#include "configuration.h"
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
OpcUa_UInt32 i;
ServiceSettings serviceSettings;
UaMonitoredItemCreateRequests itemsToCreate;
UaMonitoredItemCreateResults createResults;
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
OpcUa_UInt32 size, i;
ServiceSettings serviceSettings;
UaMonitoredItemCreateRequests itemsToCreate;
UaMonitoredItemCreateResults createResults;
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"
Replace the constructor of SampleClient with the following code:
SampleClient::SampleClient()
{
m_pConfiguration = new Configuration();
m_pSampleSubscription = new SampleSubscription(m_pConfiguration);
}
Extend the method SampleClient::setConfiguration
void SampleClient::setConfiguration(Configuration* pConfiguration)
{
if (m_pSampleSubscription)
{
m_pSampleSubscription->setConfiguration(pConfiguration);
}
if (m_pConfiguration)
{
delete m_pConfiguration;
}
m_pConfiguration = pConfiguration;
}
Finally, we have to extend main to create a subscription:
...
{
pMyClient->registerNodes();
pMyClient->writeRegistered();
printf("\n*************************************************************************\n");
printf("* Press Enter to create a subscription\n");
printf("* Press Enter again to stop the subscription\n");
printf("*************************************************************************\n");
getchar();
status = pMyClient->subscribe();
{
getchar();
status = pMyClient->unsubscribe();
}
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:
...
private:
private:
Configuration* m_pConfiguration;
};
Add the method SampleSubscription::recoverSubscription:
UaStatus SampleSubscription::recoverSubscription()
{
if (m_pSubscription)
{
deleteSubscription();
}
result = createSubscription(m_pSession);
{
result = createMonitoredItems();
}
printf("-------------------------------------------------------------\n");
{
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,
{
OpcUa_ReferenceParameter(clientSubscriptionHandle);
{
printf(
"Subscription not longer valid - failed with status %s\n", status.
toString().
toUtf8());
recoverSubscription();
}
}
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.
...
printf("\n*************************************************************************\n");
printf("* Press Enter to create a subscription\n");
printf("* Once you see DataChange Notifications - kill and restart the server.\n");
printf("* You will see the client reconnect and recover the subscription.\n");
printf("* Press Enter again to stop the subscription\n");
printf("*************************************************************************\n");
getchar();
status = pMyClient->subscribe();
{
getchar();
status = pMyClient->unsubscribe();
}
pMyClient->writeRegistered();
pMyClient->unregisterNodes();
printf("\nPress Enter to disconnect\n");
getchar();
...