UA Ansi C Server Professional  1.3.3.242
 All Data Structures Functions Variables Typedefs Enumerations Enumerator Groups Pages
Lesson 4: Adding Support for Methods

This lesson will show how to provide Methods in the Address Space.

Content:

Step 1: Add Method to MachineType

Figure 4-1 shows the machine type and the method that we will create in this lesson.

Figure 4-1 Machine Type

First of all we create the according InstanceDeclaration node for the MachineType by adding the method SetSwitchState as component to the object type. This is done in the method CustomProvider_CreateMachineType. Additionally, we mark the method as mandatory for instances of the MachineType.

/* Create switch method */
a_pStartingNodeId->Identifier.Numeric++;
typeNodeId.Identifier.Numeric = OpcUaId_MethodNode;
referenceNodeId.Identifier.Numeric = OpcUaId_HasComponent;
uStatus = UaServer_CreateNode(pAddressSpace,
(OpcUa_BaseNode**)&pMethod,
pNewMachineType,
a_pStartingNodeId,
OpcUa_NodeClass_Method,
&referenceNodeId,
OpcUa_Null,
"SetSwitchState",
"SetSwitchState",
"Switches the machine on or off and returns the current temperature");
OpcUa_GotoErrorIfBad(uStatus);
/* Set ModellingRule Mandatory for switch method child object */
nodeId.NamespaceIndex = 0;
nodeId.Identifier.Numeric = OpcUaId_ModellingRule_Mandatory;
referenceNodeId.Identifier.Numeric = OpcUaId_HasModellingRule;
uStatus = OpcUa_BaseNode_AddReferenceToNodeId(pMethod, &nodeId, &referenceNodeId);
OpcUa_GotoErrorIfBad(uStatus);

The new method shall have one in- and one output argument:
The input argument will specify the switch position to be set by the method, the output argument will return the current value of the machine's temperature sensor.

/* Input arguments */
a_pStartingNodeId->Identifier.Numeric++;
uStatus = UaServer_CreateProperty(pAddressSpace,
&pProperty,
(OpcUa_BaseNode*)pMethod,
a_pStartingNodeId->Identifier.Numeric,
g_uCustomProvider_NamespaceIndex,
"InputArguments");
OpcUa_GotoErrorIfBad(uStatus);
OpcUa_BaseNode_GetBrowseName(pProperty)->NamespaceIndex = 0;
OpcUa_Variable_SetDataType_Numeric(pProperty, OpcUaId_Argument, 0);
OpcUa_Variable_SetValueRank(pProperty, OpcUa_ValueRanks_OneDimension);
pArrayDimensions = (OpcUa_UInt32*)OpcUa_Alloc(sizeof(OpcUa_UInt32));
pArrayDimensions[0] = 1;
OpcUa_Variable_AttachArrayDimensions(pProperty, pArrayDimensions, 1);
pValue = OpcUa_Variable_GetValue(pProperty);
pValue->Datatype = OpcUaType_ExtensionObject;
pValue->ArrayType = OpcUa_VariantArrayType_Array;
pValue->Value.Array.Length = 1;
pValue->Value.Array.Value.ExtensionObjectArray = (OpcUa_ExtensionObject*)UaServer_Alloc(sizeof(OpcUa_ExtensionObject));
OpcUa_EncodeableObject_CreateExtension(&OpcUa_Argument_EncodeableType,
&pValue->Value.Array.Value.ExtensionObjectArray[0],
(OpcUa_Void**)&pArgument);
pArgument->ValueRank = OpcUa_ValueRanks_Scalar;
pArgument->DataType.Identifier.Numeric = OpcUaId_Boolean;
OpcUa_String_StrnCpy(&pArgument->Name,
OpcUa_String_FromCString("SwitchState"),
OPCUA_STRING_LENDONTCARE);
OpcUa_String_StrnCpy(&pArgument->Description.Locale,
OpcUa_String_FromCString("en"),
OPCUA_STRING_LENDONTCARE);
OpcUa_String_StrnCpy(&pArgument->Description.Text,
OpcUa_String_FromCString("The state to set the switch to"),
OPCUA_STRING_LENDONTCARE);
/* Output arguments */
a_pStartingNodeId->Identifier.Numeric++;
uStatus = UaServer_CreateProperty(pAddressSpace,
&pProperty,
(OpcUa_BaseNode*)pMethod,
a_pStartingNodeId->Identifier.Numeric,
g_uCustomProvider_NamespaceIndex,
"OutputArguments");
OpcUa_GotoErrorIfBad(uStatus);
OpcUa_BaseNode_GetBrowseName(pProperty)->NamespaceIndex = 0;
OpcUa_Variable_SetDataType_Numeric(pProperty, OpcUaId_Argument, 0);
OpcUa_Variable_SetValueRank(pProperty, OpcUa_ValueRanks_OneDimension);
pArrayDimensions = (OpcUa_UInt32*)OpcUa_Alloc(sizeof(OpcUa_UInt32));
pArrayDimensions[0] = 1;
OpcUa_Variable_AttachArrayDimensions(pProperty, pArrayDimensions, 1);
pValue = OpcUa_Variable_GetValue(pProperty);
pValue->Datatype = OpcUaType_ExtensionObject;
pValue->ArrayType = OpcUa_VariantArrayType_Array;
pValue->Value.Array.Length = 1;
pValue->Value.Array.Value.ExtensionObjectArray = (OpcUa_ExtensionObject*)UaServer_Alloc(sizeof(OpcUa_ExtensionObject));
OpcUa_EncodeableObject_CreateExtension(&OpcUa_Argument_EncodeableType,
&pValue->Value.Array.Value.ExtensionObjectArray[0],
(OpcUa_Void**)&pArgument);
pArgument->ValueRank = OpcUa_ValueRanks_Scalar;
pArgument->DataType.Identifier.Numeric = OpcUaId_Double;
OpcUa_String_StrnCpy(&pArgument->Name,
OpcUa_String_FromCString("CurrentTemperature"),
OPCUA_STRING_LENDONTCARE);
OpcUa_String_StrnCpy(&pArgument->Description.Locale,
OpcUa_String_FromCString("en"),
OPCUA_STRING_LENDONTCARE);
OpcUa_String_StrnCpy(&pArgument->Description.Text,
OpcUa_String_FromCString("The current temperature of the machine"),
OPCUA_STRING_LENDONTCARE);

Step 2: Add Method to the Machine Instance

Now we need to add the method as child of the machine instance we create. This is done in CustomProvider_CreateMachine, similarly to the creation of the method of the MachineType in Step 1: Add Method to MachineType. For identifying the method when being called, we set the user data of the machine also as user data of the newly created method.

/* Set new machine struct as user data of the method */
OpcUa_BaseNode_SetUserData(pMethod, pNewMachine);

Step 3: Implementing the Handling of Method Calls

If a method is called, the CallAsync service handler of the provider is invoked, so we need to implement this handler in our provider in order to process method calls. The function CustomProvider_CallAsync is set as the handler routine in CustomProvider_Initialize.

IFMETHODIMP(CustomProvider_Initialize)(UaServer_Provider *a_pProvider,
UaServer_pProviderCBInterface *a_pProviderCBInterface,
UaServer_pProviderInterface *a_pProviderInterface)
{
...
a_pProviderInterface->CallAsync = CustomProvider_CallAsync;

The implementation of the CustomProvider_CallAsync function checks if the method call should be processed by the provider. Therefor it checks if MethodId and ObjectId are nodes belonging to the provider.

IFMETHODIMP(CustomProvider_CallAsync)(UaServer_ProviderCallContext *a_pCallContext)
{
...
if (pRequest->MethodsToCall[i].MethodId.NamespaceIndex == g_uCustomProvider_NamespaceIndex &&
pRequest->MethodsToCall[i].ObjectId.NamespaceIndex == g_uCustomProvider_NamespaceIndex)
{
UaServer_GetNode(pAddressSpace, &pRequest->MethodsToCall[i].MethodId, &pNode);
if (pNode != OpcUa_Null)
{

If this is the case, the UserData of the method being called is retrieved and evaluated. In case the UserData is of type machine, the method call gets processed. Additionally we check if the method was called on the corresponding machine, for that we compare the NodeId of the machine with the ObjectId passed in the method call.

UserDataCommon *pUserData = (UserDataCommon*)OpcUa_BaseNode_GetUserData(pNode);
if (pUserData != OpcUa_Null)
{
switch (pUserData->Type)
{
case UserDataMachine:
{
Machine *pMachine = (Machine*)pUserData;
/* Check if the method was called on the according Machine */
if (OpcUa_NodeId_Compare(&pRequest->MethodsToCall[i].ObjectId, &pMachine->nodeId) != 0)
{
pResponse->Results[i].StatusCode = OpcUa_BadNotFound;
break;
}

If the method call passes valid input arguments, the HeaterSwitch position is set to the position passed in the first input argument of the method call. After this the output argument is created and set to the current temperature of the temperature sensor.

/* Check input arguments */
if (pRequest->MethodsToCall[i].NoOfInputArguments == 1)
{
OpcUa_Variant *pInArg = &pRequest->MethodsToCall[i].InputArguments[0];
OpcUa_CallMethodResult *pResult = &pResponse->Results[i];
pResult->NoOfInputArgumentResults = 1;
pResult->InputArgumentResults = (OpcUa_StatusCode*)OpcUa_Alloc(sizeof(OpcUa_StatusCode));
if (pInArg->ArrayType == OpcUa_VariantArrayType_Scalar &&
pInArg->Datatype == OpcUaType_Boolean)
{
/* Set switch state */
*pMachine->pHeaterSwitch->pValue = pInArg->Value.Boolean;
pResult->InputArgumentResults[0] = OpcUa_Good;
}
else
{
pResult->InputArgumentResults[0] = OpcUa_BadTypeMismatch;
}
/* Fill output arguments */
pResult->NoOfOutputArguments = 1;
pResult->OutputArguments = (OpcUa_Variant*)OpcUa_Alloc(sizeof(OpcUa_Variant));
OpcUa_Variant_Initialize(&pResult->OutputArguments[0]);
pResult->OutputArguments[0].Datatype = OpcUaType_Double;
pResult->OutputArguments[0].Value.Double = *pMachine->pTemperatureSensor->pValue;
pResult->StatusCode = OpcUa_Good;
}
else
{
pResponse->Results[i].StatusCode = OpcUa_BadArgumentsMissing;
}

Step 4: Calling the Method with a Client

After implementing the method handling, we can test it by using a client to call the method.

Figure 4-2 Monitoring a Variable using UA Expert

Drag&drop HeaterSwitch into the Default DA View tab. Note that the value is false. Right-click the SetSwitchState method in the Address Space browser and click Call....

Figure 4-3 Calling SetSwitchState

Set the SwitchState argument to true and click Call. The value of HeaterSwitch in the DA View changes to true and the current temperature is shown in the according output argument field.