ANSI C Based OPC UA Client/Server/PubSub SDK  1.9.4.474
Lesson 2: Subscription and Reconnection Scenarios

This lesson describes how to continuously read values from a server using a subscription.

For an introduction to the concept of subscriptions and monitored items see OPC UA Client/Server Subscription Concept.

Files used in this lesson:

Step 1: Introducing the Sample Subscription

The OPC UA Client SDK supports subscriptions through UaClient_Subscription and its associated functions. This lesson introduces the Sample Subscription to show how to realize client specific functionality. It is implemented in samplesubscription.h and samplesubscription.c and based on the SDK subscription.

#ifndef _SAMPLESUBSCRIPTION_H_
#define _SAMPLESUBSCRIPTION_H_ 1
#include <uaclient_subscription.h>
OPCUA_BEGIN_EXTERN_C
typedef enum _SampleSubscriptionStateMachine
{
State_SampleSubscription_Idle,
State_SampleSubscription_SubscriptionCreate,
State_SampleSubscription_SubscriptionCreateDone,
State_SampleSubscription_MonitoredItemCreate,
State_SampleSubscription_MonitoredItemCreateDone,
State_SampleSubscription_MonitoredItemDelete,
State_SampleSubscription_MonitoredItemDeleteDone,
State_SampleSubscription_SubscriptionDelete,
State_SampleSubscription_SubscriptionDeleteDone,
State_SampleSubscription_Error
} SampleSubscriptionStateMachine;
typedef struct _SampleSubscription
{
SampleSubscriptionStateMachine State;
UaClient_Subscription *pSubscription;
OpcUa_UInt32 uMonitoredItemId0;
OpcUa_UInt32 uMonitoredItemId1;
OpcUa_UInt32 uDataCount;
} SampleSubscription;
SampleSubscription *SampleSubscription_Create(UaClient_Session *pSession);
OpcUa_Void SampleSubscription_Delete(SampleSubscription **ppSampleSubscription);
OpcUa_StatusCode SampleSubscription_CreateSubscription(SampleSubscription *pSampleSubscription);
OpcUa_StatusCode SampleSubscription_DeleteSubscription(SampleSubscription *pSampleSubscription);
OpcUa_StatusCode SampleSubscription_CreateMonitoredItem(SampleSubscription *pSampleSubscription);
OpcUa_StatusCode SampleSubscription_DeleteMonitoredItem(SampleSubscription *pSampleSubscription);
OPCUA_END_EXTERN_C
#endif /* _SAMPLESUBSCRIPTION_H_ */

The Sample Subscription has its own state machine, reflecting the life cycle of the subscription. After creation, the Sample Subscription is in the Idle state, only the client side resources are allocated so far. The call to SampleSubscription_CreateSubscription causes the creation of a subscription on the server, and the state changes to SubscriptionCreate. Then the SampleSubscription_CreateMonitoredItem function requests that the server creates the new monitored items, then the Sample Subscription proceeds to the state MonitoredItemCreate.

Cleaning up resources works in the reverse order. First the monitored items are deleted and then the subscription on the server. The local resources on the client are freed with SampleSubscription_Delete.

The Error state can be reached from any state and is the final state in case of an error. If the service callbacks are invoked, the Sample Subscription enters the corresponding states ending with Done.This indicates that the corresponding operation has finished and that the next operation can be started.

The following steps explain how the Sample Subscription is implemented and how it is integrated into the clientmain.c from Lesson 1: Setting up a Basic OPC UA Client Console Application.

Step 2: Create the Subscription

SampleSubscription *SampleSubscription_Create(UaClient_Session *a_pSession)
{
SampleSubscription *pSampleSubscription = OpcUa_Null;
UaClient_Subscription_Callback subscriptionCallback;
UaClient_Subscription *pSubscription = OpcUa_Null;
/* Allocate memory for the SampleSubscription */
pSampleSubscription = OpcUa_Alloc(sizeof(*pSampleSubscription));
if (pSampleSubscription == OpcUa_Null)
{
return OpcUa_Null;
}
/* Set callbacks */
OpcUa_MemSet(&subscriptionCallback, 0, sizeof(subscriptionCallback));
subscriptionCallback.pfStatusChanged_CB = SampleSubscription_StatusChanged_CB;
subscriptionCallback.pfDataChange_CB = SampleSubscription_DataChange_CB;
/* Create the underlying UaClient_Subscription */
uStatus = UaClient_Subscription_Create(a_pSession, &subscriptionCallback, &pSubscription);
if (OpcUa_IsBad(uStatus))
{
OpcUa_Free(pSampleSubscription);
return OpcUa_Null;
}
/* Initialize the SampleSubscription structure */
pSampleSubscription->pSubscription = pSubscription;
pSampleSubscription->uDataCount = 0;
pSampleSubscription->uMonitoredItemId0 = 0;
pSampleSubscription->uMonitoredItemId1 = 0;
pSampleSubscription->State = State_SampleSubscription_Idle;
/* Set the most important Subscription properties */
pSubscription->pUserData = pSampleSubscription;
pSubscription->PublishingInterval = 1000.0;
return pSampleSubscription;
}

The Constructor of the Sample Subscription allocates memory for its own structure and creates a new UaClient_Subscription. The subscription has properties that are initialized with default values by the SDK. The one that must be set here is pUserData, attaching the newly created Sample Subscription to the UaClient_Subscription. The latter is passed to the subscription callback functions and with it the Sample Subscription structure. The PublishingInterval determines the rate at which data changes are sent to the client. For this example, it is set to 1000 milliseconds.

OpcUa_StatusCode SampleSubscription_CreateSubscription(SampleSubscription *a_pSampleSubscription)
{
a_pSampleSubscription->State = State_SampleSubscription_SubscriptionCreate;
return UaClient_Subscription_BeginCreateSubscription(a_pSampleSubscription->pSubscription,
OpcUa_Null,
SampleSubscription_Created_CB,
OpcUa_Null);
}

The server side subscription is created via a service call that takes the encapsulated subscription and a callback function as arguments. Further arguments are a UaClient_ServiceSettings structure and a UserData Pointer, which are not needed for the Sample Subscription.

static OpcUa_Void SampleSubscription_Created_CB(UaClient_Subscription *a_pUaSubscription,
OpcUa_StatusCode a_status,
OpcUa_ResponseHeader *a_pResponseHeader,
OpcUa_Void *a_pUserData)
{
SampleSubscription *pSampleSubscription = a_pUaSubscription->pUserData;
OpcUa_ReferenceParameter(a_pResponseHeader);
OpcUa_ReferenceParameter(a_pUserData);
if (OpcUa_IsGood(a_status))
{
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, "SampleSubscription_Created_CB succeeded (0x%08x)\n", a_status);
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, " Revised PublishingInterval: %f\n", a_pUaSubscription->RevisedPublishingInterval);
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, " Revised LifetimeCount: %u\n", a_pUaSubscription->RevisedLifetimeCount);
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, " Revised MaxKeepAliveCount: %u\n", a_pUaSubscription->RevisedMaxKeepAliveCount);
if (pSampleSubscription->State == State_SampleSubscription_SubscriptionCreate)
pSampleSubscription->State = State_SampleSubscription_SubscriptionCreateDone;
else
pSampleSubscription->State = State_SampleSubscription_Error;
}
else
{
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, "SampleSubscription_Created_CB failed (0x%08x)\n", a_status);
pSampleSubscription->State = State_SampleSubscription_Error;
}
}

When the server has created the subscription, the result will be delivered to the client through the registered callback implementing UaClient_Subscription_CreateSubscription_CB. The Sample Client prints the result and proceeds to the next state.

Step 3: Create the Monitored Item

OpcUa_StatusCode SampleSubscription_CreateMonitoredItem(SampleSubscription *a_pSampleSubscription)
{
OpcUa_Int32 i;
OpcUa_Int32 numItemsToCreate = 2;
a_pSampleSubscription->State = State_SampleSubscription_MonitoredItemCreate;
for (i = 0; i < numItemsToCreate; i++)
{
OpcUa_MonitoredItemCreateRequest_Initialize(&createRequest[i]);
createRequest[i].ItemToMonitor.AttributeId = OpcUa_Attributes_Value;
createRequest[i].RequestedParameters.SamplingInterval = 1000.0;
createRequest[i].RequestedParameters.QueueSize = 1;
createRequest[i].RequestedParameters.DiscardOldest = OpcUa_True;
/* Sample code for creation of data change filter */
/* Trigger setting (default is StatusValue) */
/* Deadband setting is 0.1% of last value (absolute)
OpcUa_DataChangeFilter* pDataChangeFilter = NULL;
OpcUa_EncodeableObject_CreateExtension(
&OpcUa_DataChangeFilter_EncodeableType,
&createRequest[i].RequestedParameters.Filter,
(OpcUa_Void**)&pDataChangeFilter);
if (pDataChangeFilter)
{
pDataChangeFilter->DeadbandType = OpcUa_DeadbandType_Absolute;
pDataChangeFilter->DeadbandValue = 0.1;
pDataChangeFilter->Trigger = OpcUa_DataChangeTrigger_StatusValue;
}
*/
}
createRequest[0].RequestedParameters.ClientHandle = 0;
createRequest[0].ItemToMonitor.NodeId.Identifier.Numeric = OpcUaId_Server_ServerStatus_CurrentTime;
createRequest[1].RequestedParameters.ClientHandle = 1;
createRequest[1].ItemToMonitor.NodeId.Identifier.Numeric = OpcUaId_Server_ServerStatus_State;
return UaClient_Subscription_BeginCreateMonitoredItems(a_pSampleSubscription->pSubscription,
OpcUa_Null,
numItemsToCreate,
createRequest,
SampleSubscription_MonitoredItems_Created_CB,
OpcUa_Null);
}

Creating the monitored items requires another service call. This one allows creating multiple items at once, each having different parameters. The Sample Subscription creates two monitored items, one for the current time of the server and one for the server state.

static OpcUa_Void SampleSubscription_MonitoredItems_Created_CB(const UaClient_Subscription *a_pSubscription,
OpcUa_ResponseHeader *a_pResponseHeader,
OpcUa_Int32 a_NoOfResults,
OpcUa_Int32 a_NoOfDiagnosticInfos,
OpcUa_DiagnosticInfo *a_pDiagnosticInfos,
OpcUa_Void *a_pUserData)
{
SampleSubscription *pSampleSubscription = a_pSubscription->pUserData;
OpcUa_ReferenceParameter(a_pUserData);
OpcUa_ReferenceParameter(a_NoOfDiagnosticInfos);
OpcUa_ReferenceParameter(a_pDiagnosticInfos);
if (OpcUa_IsGood(a_pResponseHeader->ServiceResult) && a_NoOfResults == 2)
{
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, "SampleSubscription_MonitoredItems_Created_CB succeeded\n");
if (OpcUa_IsGood(a_pResults[0].StatusCode))
{
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, " [0]: ClientHandle: 0\n");
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, " MonitoredItemId: %u\n", a_pResults[0].MonitoredItemId);
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, " RevisedSamplingInterval: %f\n", a_pResults[0].RevisedSamplingInterval);
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, " RevisedQueueSize: %u\n", a_pResults[0].RevisedQueueSize);
pSampleSubscription->uMonitoredItemId0 = a_pResults[0].MonitoredItemId;
}
else
{
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, " [0]: failed with status 0x%08x\n", a_pResults[0].StatusCode);
}
if (OpcUa_IsGood(a_pResults[1].StatusCode))
{
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, " [1]: ClientHandle: 1\n");
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, " MonitoredItemId: %u\n", a_pResults[1].MonitoredItemId);
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, " RevisedSamplingInterval: %f\n", a_pResults[1].RevisedSamplingInterval);
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, " RevisedQueueSize: %u\n", a_pResults[1].RevisedQueueSize);
pSampleSubscription->uMonitoredItemId1 = a_pResults[1].MonitoredItemId;
}
else
{
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, " [1]: failed with status 0x%08x\n", a_pResults[1].StatusCode);
}
if (pSampleSubscription->State == State_SampleSubscription_MonitoredItemCreate)
pSampleSubscription->State = State_SampleSubscription_MonitoredItemCreateDone;
else
pSampleSubscription->State = State_SampleSubscription_Error;
}
else
{
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, "SampleSubscription_MonitoredItems_Created_CB failed (0x%08x)\n", a_pResponseHeader->ServiceResult);
pSampleSubscription->State = State_SampleSubscription_Error;
}
}

The callback function checks the result and stores the IDs of the newly created monitored items in its structure.

Step 4: Receive Data Changes

static OpcUa_Void SampleSubscription_DataChange_CB(UaClient_Subscription *a_pSubscription,
OpcUa_Int32 a_noOfMonitoredItems,
OpcUa_MonitoredItemNotification *a_pMonitoredItems,
OpcUa_Int32 a_noOfDiagnosticInfos,
OpcUa_DiagnosticInfo *a_pDiagnosticInfos)
{
SampleSubscription *pSampleSubscription = a_pSubscription->pUserData;
OpcUa_Int32 i;
OpcUa_ReferenceParameter(a_noOfDiagnosticInfos);
OpcUa_ReferenceParameter(a_pDiagnosticInfos);
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, "SampleSubscription_DataChange_CB\n");
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, " noOfMonitoredItems: %i\n", a_noOfMonitoredItems);
for (i = 0; i < a_noOfMonitoredItems; i++)
{
OpcUa_CharA szDateTime[64] = {0};
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, " [%i]:\n", i);
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, " ClientHandle: %u\n", a_pMonitoredItems[i].ClientHandle);
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, " StatusCode: 0x%08x\n", a_pMonitoredItems[i].Value.StatusCode);
if (OpcUa_IsGood(a_pMonitoredItems[i].Value.StatusCode))
{
OpcUa_DateTime_GetStringFromDateTime(a_pMonitoredItems[i].Value.SourceTimestamp, szDateTime, sizeof(szDateTime));
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, " SourceTimestamp: %s\n", szDateTime);
OpcUa_DateTime_GetStringFromDateTime(a_pMonitoredItems[i].Value.ServerTimestamp, szDateTime, sizeof(szDateTime));
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, " ServerTimestamp: %s\n", szDateTime);
if (a_pMonitoredItems[i].ClientHandle == 0 &&
a_pMonitoredItems[i].Value.Value.Datatype == OpcUaType_DateTime &&
a_pMonitoredItems[i].Value.Value.ArrayType == OpcUa_VariantArrayType_Scalar)
{
OpcUa_DateTime_GetStringFromDateTime(a_pMonitoredItems[i].Value.Value.Value.DateTime, szDateTime, sizeof(szDateTime));
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, " Value: %s\n", szDateTime);
}
else if (a_pMonitoredItems[i].ClientHandle == 1 &&
a_pMonitoredItems[i].Value.Value.Datatype == OpcUaType_Int32 &&
a_pMonitoredItems[i].Value.Value.ArrayType == OpcUa_VariantArrayType_Scalar)
{
const char *szState = "Invalid state value";
switch (a_pMonitoredItems[i].Value.Value.Value.Int32)
{
case OpcUa_ServerState_Running : szState = "Running (0)"; break;
case OpcUa_ServerState_Failed : szState = "Failed (1)"; break;
case OpcUa_ServerState_NoConfiguration : szState = "NoConfiguration (2)"; break;
case OpcUa_ServerState_Suspended : szState = "Suspended (3)"; break;
case OpcUa_ServerState_Shutdown : szState = "Shutdown (4)"; break;
case OpcUa_ServerState_Test : szState = "Test (5)"; break;
case OpcUa_ServerState_CommunicationFault: szState = "CommunicationFault (6)"; break;
case OpcUa_ServerState_Unknown : szState = "Unknown (7)"; break;
default: break;
}
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, " Value: %s\n", szState);
}
pSampleSubscription->uDataCount++;
}
}
}

Sending Publishing requests and receiving the responses is handled by the SDK. The only task left for the Sample Subscription is processing the data changes. This is done by implementing the UaClient_Subscription_DataChange_CB callback function. It iterates through all received data changes and prints the status code of the value, its timestamps, and the value itself (which is a timestamp or a Int32, depending on the monitored item). Additionally, the Sample Subscription has a counter of received values that is incremented.

Step 5: Delete the Monitored Item and Subscription

Cleaning up the Sample Subscription works similar to creating the Sample Subscription and requires two service calls. First, the monitored items must be deleted by passing the IDs of the monitored items to delete to the server. Second, the subscription on the server side must be deleted. Finally, the local resources for the Sample Subscription and the encapsulated UaClient_Subscription must be freed.

OpcUa_StatusCode SampleSubscription_DeleteMonitoredItem(SampleSubscription *a_pSampleSubscription)
{
OpcUa_Int32 numItemsToDelete = 2;
OpcUa_UInt32 MonitoredItemIds[2];
a_pSampleSubscription->State = State_SampleSubscription_MonitoredItemDelete;
MonitoredItemIds[0] = a_pSampleSubscription->uMonitoredItemId0;
MonitoredItemIds[1] = a_pSampleSubscription->uMonitoredItemId1;
return UaClient_Subscription_BeginDeleteMonitoredItems(a_pSampleSubscription->pSubscription,
OpcUa_Null,
numItemsToDelete,
MonitoredItemIds,
SampleSubscription_MonitoredItems_Deleted_CB,
OpcUa_Null);
}
static OpcUa_Void SampleSubscription_MonitoredItems_Deleted_CB(const UaClient_Subscription *a_pSubscription,
OpcUa_ResponseHeader *a_pResponseHeader,
OpcUa_Int32 a_NoOfResults,
OpcUa_StatusCode *a_pResults,
OpcUa_Int32 a_NoOfDiagnosticInfos,
OpcUa_DiagnosticInfo *a_pDiagnosticInfos,
OpcUa_Void *a_pUserData)
{
SampleSubscription *pSampleSubscription = a_pSubscription->pUserData;
OpcUa_ReferenceParameter(a_pUserData);
OpcUa_ReferenceParameter(a_NoOfDiagnosticInfos);
OpcUa_ReferenceParameter(a_pDiagnosticInfos);
if (OpcUa_IsGood(a_pResponseHeader->ServiceResult) && a_NoOfResults == 2)
{
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, "SampleSubscription_MonitoredItems_Deleted_CB succeeded\n");
if (OpcUa_IsGood(a_pResults[0]))
{
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, " [0]: succeeded\n");
}
else
{
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, " [0]: failed with status 0x%08x\n", a_pResults[0]);
}
if (OpcUa_IsGood(a_pResults[1]))
{
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, " [1]: succeeded\n");
}
else
{
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, " [1]: failed with status 0x%08x\n", a_pResults[1]);
}
if (pSampleSubscription->State == State_SampleSubscription_MonitoredItemDelete)
pSampleSubscription->State = State_SampleSubscription_MonitoredItemDeleteDone;
else
pSampleSubscription->State = State_SampleSubscription_Error;
}
else
{
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, "SampleSubscription_MonitoredItems_Deleted_CB failed (0x%08x)\n", a_pResponseHeader->ServiceResult);
pSampleSubscription->State = State_SampleSubscription_Error;
}
}
OpcUa_StatusCode SampleSubscription_DeleteSubscription(SampleSubscription *a_pSampleSubscription)
{
a_pSampleSubscription->State = State_SampleSubscription_SubscriptionDelete;
return UaClient_Subscription_BeginDeleteSubscription(a_pSampleSubscription->pSubscription,
OpcUa_Null,
SampleSubscription_Deleted_CB,
OpcUa_Null);
}
static OpcUa_Void SampleSubscription_Deleted_CB(UaClient_Subscription *a_pUaSubscription,
OpcUa_StatusCode a_status,
OpcUa_ResponseHeader *a_pResponseHeader,
OpcUa_Void *a_pUserData)
{
SampleSubscription *pSampleSubscription = a_pUaSubscription->pUserData;
OpcUa_ReferenceParameter(a_pResponseHeader);
OpcUa_ReferenceParameter(a_pUserData);
if (OpcUa_IsGood(a_status))
{
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, "SampleSubscription_Deleted_CB succeeded (0x%08x)\n", a_status);
if (pSampleSubscription->State == State_SampleSubscription_SubscriptionDelete)
pSampleSubscription->State = State_SampleSubscription_SubscriptionDeleteDone;
else
pSampleSubscription->State = State_SampleSubscription_Error;
}
else
{
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, "SampleSubscription_Deleted_CB failed (0x%08x)\n", a_status);
pSampleSubscription->State = State_SampleSubscription_Error;
}
}
OpcUa_Void SampleSubscription_Delete(SampleSubscription **a_ppSampleSubscription)
{
SampleSubscription *pSampleSubscription = *a_ppSampleSubscription;
if (pSampleSubscription == OpcUa_Null)
return;
UaClient_Subscription_Delete(&pSampleSubscription->pSubscription);
OpcUa_Free(pSampleSubscription);
*a_ppSampleSubscription = OpcUa_Null;
}

Step 6: Integration into clientmain.c

The clientmain.c from this lesson is similar to the one from Lesson 1: Setting up a Basic OPC UA Client Console Application. Code related to the read service is removed and replaced by subscription handling.

typedef enum _SampleStateMachine
{
State_Idle,
State_Connect,
State_Connected,
State_Subscription,
State_Disconnect,
State_Disconnected,
State_Error
} SampleStateMachine;
typedef struct _SampleClientContext
{
SampleStateMachine State;
} SampleClientContext;

The client’s state machine has a new Subscription state, which replaces the states Read and ReadDone from the previous lesson.

/* Main loop */
while (!(bComplete && pSession->ConnectionStatus == UaClient_ConnectionStatus_Disconnected))
{
/* process sample state machine */
switch (clientContext.State)
{
case State_Idle:
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, "UA Client: Connecting to %s ...\n", SERVER_ENDPOINT_URL);
uStatus = UaClient_Session_BeginConnect(pSession);
OpcUa_GotoErrorIfBad(uStatus);
clientContext.State = State_Connect;
break;
case State_Connected:
/* create the sample subscription context */
pSampleSubscription = SampleSubscription_Create(pSession);
OpcUa_GotoErrorIfArgumentNull(pSampleSubscription);
/* create the server-side subscription */
uStatus = SampleSubscription_CreateSubscription(pSampleSubscription);
OpcUa_GotoErrorIfBad(uStatus);
clientContext.State = State_Subscription;
break;
case State_Subscription:
OpcUa_GotoErrorIfNull(pSampleSubscription, OpcUa_BadInternalError);
switch (pSampleSubscription->State)
{
case State_SampleSubscription_SubscriptionCreateDone:
/* create monitored item */
uStatus = SampleSubscription_CreateMonitoredItem(pSampleSubscription);
OpcUa_GotoErrorIfBad(uStatus);
break;
case State_SampleSubscription_MonitoredItemCreateDone:
/* wait for five data changes */
if (pSampleSubscription->uDataCount < 5)
break;
/* delete the monitored item */
uStatus = SampleSubscription_DeleteMonitoredItem(pSampleSubscription);
OpcUa_GotoErrorIfBad(uStatus);
break;
case State_SampleSubscription_MonitoredItemDeleteDone:
/* delete the subscription */
uStatus = SampleSubscription_DeleteSubscription(pSampleSubscription);
OpcUa_GotoErrorIfBad(uStatus);
break;
case State_SampleSubscription_SubscriptionDeleteDone:
/* delete the sample subscription structure */
SampleSubscription_Delete(&pSampleSubscription);
pSampleSubscription = OpcUa_Null;
/* disconnect from the server */
uStatus = UaClient_Session_BeginDisconnect(pSession, OpcUa_False);
OpcUa_GotoErrorIfBad(uStatus);
/* exit the subscription state */
clientContext.State = State_Disconnect;
break;
case State_SampleSubscription_Error:
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, "An error occured. Terminating now.\n");
bComplete = OpcUa_True;
break;
default:
break;
}
break;
case State_Disconnected:
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, "Sample successfully completed. Terminating now.\n");
bComplete = OpcUa_True;
break;
case State_Error:
uStatus = UaClient_Session_BeginDisconnect(pSession, OpcUa_True);
if (OpcUa_IsBad(uStatus) && uStatus != OpcUa_BadInvalidState) { OpcUa_GotoError; }
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, "An error occured. Terminating now.\n");
bComplete = OpcUa_True;
break;
default:
break;
}
/* Process Ua Client events */
uStatus = UaBase_DoCom();
if (OpcUa_IsBad(uStatus))
{
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, "UaBase_DoCom failed (0x%08x)\n", uStatus);
bComplete = OpcUa_True;
}
}

When the session is established, the main loop now creates a Sample Subscription, starts the creation of the server-side subscription, and changes to the new Subscription state. All further subscription handling takes place in this state, based on the state of the Sample Subscription.

SampleSubscription_Delete(&pSampleSubscription);
pSession->pUserData = OpcUa_Null;

Cleaning up local resources now includes deleting the Sample Subscription in addition to the session.

Step 7: Enhance for Automatic Handling of Reconnection Scenarios

The flag "AutomaticReconnect" indicates if the client SDK should try to reconnect in the case of a connection error. It is enabled by default in the client SDK and can be modified through the config file. In the sample client automatic reconnect is disabled and can be enabled explicitly by setting "AutomaticReconnect" flag to true.

/*Set AutomaticReconnect to OpcUa_True to enable automatic reconnect */
pSession->AutomaticReconnect = OpcUa_False;

ConnectionStatusChanged and ConnectError callbacks have been modified for subscription recovery upon automatic reconnect.

OpcUa_Void Sample_ConnectionStatusChanged_CB(UaClient_Session *a_pSession,
{
SampleClientContext *pClientContext = a_pSession->pUserData;
const char *pStatus = "INVALID";
switch (a_status)
{
pStatus = "Disconnected";
if (pClientContext->State == State_Connect)
{
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, "UA Client: failed to connect to server!\n");
}
if (pClientContext->State == State_Disconnect)
{
pClientContext->State = State_Disconnected;
}
else
{
pClientContext->State = State_Error;
}
break;
pStatus = "Connected";
if (pClientContext->State == State_Connect || pClientContext->State == State_Subscription)
{
pClientContext->State = State_Connected;
}
else
{
pClientContext->State = State_Error;
}
break;
pStatus = "Connecting";
break;
pStatus = "ConnectionWarningWatchdogTimeout";
break;
pStatus = "ConnectionErrorClientReconnect";
break;
pStatus = "SessionAutomaticallyRecreated";
break;
default: break;
}
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, "\n--> Sample_ConnectionStatusChanged_CB: %s\n\n", pStatus);
}
OpcUa_Boolean Sample_ConnectError_CB(UaClient_Session *a_pSession,
OpcUa_Boolean a_overridable)
{
const char *pServiceType = "INVALID";
OpcUa_Boolean bRet = OpcUa_False;
OpcUa_ReferenceParameter(a_pSession);
OpcUa_ReferenceParameter(a_error);
OpcUa_ReferenceParameter(a_overridable);
switch (a_serviceType)
{
case UaClient_ConnectServiceType_CertificateValidation: pServiceType = "CertificateValidation"; break;
case UaClient_ConnectServiceType_OpenSecureChannel: pServiceType = "OpenSecureChannel"; break;
case UaClient_ConnectServiceType_CreateSession: pServiceType = "CreateSession"; break;
case UaClient_ConnectServiceType_UserIdentityToken: pServiceType = "UserIdentityToken"; break;
case UaClient_ConnectServiceType_ActivateSession: pServiceType = "ActivateSession"; break;
default: break;
}
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, "Sample_ConnectError_CB:\n");
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, " ServiceType: %s\n", pServiceType);
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, " Error: 0x%08x\n", a_error);
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, " Overridable: %s\n", a_overridable == OpcUa_False ? "false" : "true");
if (a_overridable)
{
bRet = OpcUa_True;
}
return bRet;
}

Step 8: Run Application

Again, start an OPC UA Server and then the client. You should see how the connection is established and the subscription and monitored items are created. Then data changes appear in an interval of one second, until five data changes are received. If the server is restarted in between and automatic reconnect is enabled, client automatically reconnects and recovers the subscription. In the end, the client cleans up all resources and quits.