UA Ansi C Server Professional  1.3.1.232
 All Data Structures Functions Variables Typedefs Enumerations Enumerator Groups Pages
Lesson 3: Connecting the Nodes to Real World Data

For representing the machine we created in the previous lesson, we will use structures that hold all the information needed to interact with the server. If there would be an underlying protocol to get the data, the structures would contain the protocol information needed for retrieving the required data.

Content:

Step 1: Introducing Custom User Data Structures

Our example has three types of structures that inherit from one UserDataCommon structure. This makes it possible to get the type of user data stored in our nodes and to cast them to the appropriate type:

/* Enum of custom user data types */
enum _UserDataType
{
UserDataTemperature,
UserDataMachine,
UserDataMachineSwitch
};
typedef enum _UserDataType UserDataType;
/* All user data structs contain the same header with type information.
This concept is application specific and only an example.
You can store whatever you like in UserData.
*/
struct _UserDataCommon
{
UserDataType Type; /* Currently only the type info is needed in the common header */
};
typedef struct _UserDataCommon UserDataCommon;
struct _TemperatureSensor
{
/* User data header */
UserDataType Type;
/* Protocol information */
OpcUa_Double *pValue;
};
typedef struct _TemperatureSensor TemperatureSensor;
struct _MachineSwitch
{
/* User data header */
UserDataType Type;
/* Protocol information */
OpcUa_Boolean *pValue;
};
typedef struct _MachineSwitch MachineSwitch;
struct _Machine
{
/* User data header */
UserDataType Type;
/* Machine data */
TemperatureSensor *pTemperatureSensor;
MachineSwitch *pHeaterSwitch;
};
typedef struct _Machine Machine;

Step 2: Appending the User Data to the Created Nodes

The next step is to create instances of the user data strucures and to append them to the nodes that are being created.

OpcUa_StatusCode CustomProvider_CreateMachine(OpcUa_StringA a_sMachineName,
OpcUa_StringA a_sTemperatureSensorName,
OpcUa_BaseNode *a_pOwner,
OpcUa_NodeId *a_pMachineTypeId,
OpcUa_NodeId *a_pTemperatureSensorTypeId,
OpcUa_NodeId *a_pStartingNodeId,
Machine **a_ppNewMachine)
{
Machine *pNewMachine = OpcUa_Null;
MachineSwitch *pNewHeaterSwitch = OpcUa_Null;
...
/* Create and initialize new machine struct */
pNewMachine = (Machine*)OpcUa_Alloc(sizeof(Machine));
OpcUa_ReturnErrorIfAllocFailed(pNewMachine);
OpcUa_MemSet(pNewMachine, 0, sizeof(Machine));
pNewMachine->Type = UserDataMachine;
...
/* Set new machine struct as user data of the new node */
OpcUa_BaseNode_SetUserData(pObject, pNewMachine);
...
/* Create and initialize new heater switch struct */
pNewHeaterSwitch = (MachineSwitch*)OpcUa_Alloc(sizeof(MachineSwitch));
OpcUa_ReturnErrorIfAllocFailed(pNewHeaterSwitch);
OpcUa_MemSet(pNewHeaterSwitch, 0, sizeof(MachineSwitch));
pNewHeaterSwitch->Type = UserDataMachineSwitch;
OpcUa_BaseNode_SetUserData(pVariable, pNewHeaterSwitch);
pNewMachine->pHeaterSwitch = pNewHeaterSwitch;

Step 3: Connecting the User Data Structures to Real World Data

After the user data structures have been created, we can connect them to the real world data. In our case there are two global variables that are simulated by a timer. Instead of this, the appropriate protocol information would be set in the user data in a scenario with an underlying protocol.

/* Create instance of MachineType */
uStatus = CustomProvider_CreateMachine("MyCustomMachine",
"MyCustomTemperatureSensor",
(OpcUa_BaseNode*)pCustomProvider,
OpcUa_BaseNode_GetId(pMachineType),
OpcUa_BaseNode_GetId(pTemperatureSensorType),
&nodeId,
&g_pMyCustomMachine);
OpcUa_GotoErrorIfBad(uStatus);
g_pMyCustomMachine->pHeaterSwitch->pValue = &g_bMyCustomMachineSwitch;
g_pMyCustomMachine->pTemperatureSensor->pValue = &g_bMyCustomMachineTemperature;
g_bMyCustomMachineSwitch = OpcUa_False;
g_bMyCustomMachineTemperature = 25.0;

The simulation of our real world data is being done by a timer which is triggered every 250 ms. It calls the CustomProvider_SimulationTimerCallback function and increases or decreases the temperature depending on the state of the machine's heater switch.

Step 4: Extending the UA Service Callback Functions for Working with User Data

Until now, the UA service callback functions were generic functions, working just on the OpcUa_BaseNode structures, without any knowledge about the user data contained in them. This needs to be changed, as now the values of our newly created nodes are not stored in the OpcUa_BaseNode itself, but in the user data structures.

The functions to be changed are CustomProvider_ReadAsync, CustomProvider_WriteAsync and CustomProvider_SampleData. The browse functions do not need to be changed as the address space information is still stored in the OpcUa_BaseNode structures.

In the affected functions, we need to check if a node contains user data. If so, we get the type of user data, cast to the appropriate type and execute the desired actions. As an example, the interesting part of CustomProvider_ReadAsync is shown:

UserDataCommon *pUserData = OpcUa_Null;
...
pUserData = (UserDataCommon*)OpcUa_BaseNode_GetUserData(pNode);
if (pUserData != OpcUa_Null &&
a_pReadCtx->pRequest->NodesToRead[i].AttributeId == OpcUa_Attributes_Value)
{
switch (pUserData->Type)
{
case UserDataTemperature:
{
TemperatureSensor *pSensor = (TemperatureSensor*)pUserData;
pRes->Results[i].Value.Datatype = OpcUaType_Double;
pRes->Results[i].Value.Value.Double = *pSensor->pValue;
pRes->Results[i].StatusCode = OpcUa_Good;
break;
}
case UserDataMachineSwitch:
{
MachineSwitch *pSwitch = (MachineSwitch*)pUserData;
pRes->Results[i].Value.Datatype = OpcUaType_Boolean;
pRes->Results[i].Value.Value.Boolean = *pSwitch->pValue;
pRes->Results[i].StatusCode = OpcUa_Good;
break;
}
default:
{
pRes->Results[i].StatusCode = OpcUa_BadNotReadable;
break;
}
}
/* Set timestamps */
if (OpcUa_IsGood(pRes->Results[i].StatusCode))
{
if (a_pReadCtx->pRequest->TimestampsToReturn == OpcUa_TimestampsToReturn_Source ||
a_pReadCtx->pRequest->TimestampsToReturn == OpcUa_TimestampsToReturn_Both)
{
pRes->Results[i].SourceTimestamp = OpcUa_DateTime_UtcNow();
}
if (a_pReadCtx->pRequest->TimestampsToReturn == OpcUa_TimestampsToReturn_Server ||
a_pReadCtx->pRequest->TimestampsToReturn == OpcUa_TimestampsToReturn_Both)
{
pRes->Results[i].ServerTimestamp = OpcUa_DateTime_UtcNow();
}
}
}
else
{
/* Use the SDK's helper function to process the request */
UaServer_ReadInternal(pNode, a_pReadCtx, i);
}