UA Server SDK C++ Bundle  1.3.2.200
 All Data Structures Namespaces 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:

Overview

Following our example which has been introduced in Lesson 2: Extending the Address Space with real world data we will add method support to the controller objects in this lesson. Methods will be used here as Start and Stop commands for the controllers. Specialized methods StartWithSetPoint provide a mechanism to start a controller and to pass in the setpoints in one consistent command.

Figure 4-1 shows the methods Start and Stop added to the ControllerType besides the InstanceDeclarations we already created in Lesson 2: Extending the Address Space with real world data. The method StartWithSetPoint is added to the object types FurnaceControllerType and AirConditionerControllerType with different parameters.

Figure 4-1: ControllerType

The steps 1 to 3 will show you how to create Methods generally exemplified by implementing Start and Stop. In step 5 you will learn how to create Methods with Arguments to be passed to.

Step 1: Add Methods to Object Type

Creating the InstanceDeclaration

First of all we create the according InstanceDeclaration nodes for the ControllerType by adding the methods Start and Stop as components to the object type. This is done in the method NmBuildingAutomation::createTypeNodes. We need additional local helper variables to create the method nodes.

UaStatus NmBuildingAutomation::createTypeNodes()
{
// Other helpers
// Method helpers
UaMethodGeneric* pMethod = NULL;
UaPropertyMethodArgument* pPropertyArg = NULL;
UaUInt32Array nullarray;

After the code for the creation of the ControllerType and its instance declaration variables we are creating the two method nodes with the following code:

// Add Variable "PowerConsumption"
defaultValue.setDouble(0);
pDataItem = new OpcUa::DataItemType(
UaNodeId(Ba_ControllerType_PowerConsumption, getNameSpaceIndex()),
"PowerConsumption",
getNameSpaceIndex(),
defaultValue,
Ua_AccessLevel_CurrentRead,
this);
addStatus = addNodeAndReference(pControllerType, pDataItem, OpcUaId_HasComponent);
UA_ASSERT(addStatus.isGood());
//++++++ new code begin +++++++++++++++++++++++++++++++++++++++++++
// Add Method "Start"
pMethod = new UaMethodGeneric("Start", UaNodeId(Ba_ControllerType_Start, getNameSpaceIndex()), m_defaultLocaleId);
addStatus = addNodeAndReference(pControllerType, pMethod, OpcUaId_HasComponent);
UA_ASSERT(addStatus.isGood());
// Add Method "Stop"
pMethod = new UaMethodGeneric("Stop", UaNodeId(Ba_ControllerType_Stop, getNameSpaceIndex()), m_defaultLocaleId);
addStatus = addNodeAndReference(pControllerType, pMethod, OpcUaId_HasComponent);
UA_ASSERT(addStatus.isGood());
//++++++ new code end +++++++++++++++++++++++++++++++++++++++++++++
/**************************************************************
Create the AirConditionerController Type
**************************************************************/

Step 2: Add Methods to Object Instances

Continuing the implementation of ControllerObject::ControllerObject

In a first step we are adding two member variables for the method nodes to the class ControllerObject.

class ControllerObject :
public UaObjectBase
{
//...
private:
UaMethodGeneric* m_pMethodStart;
UaMethodGeneric* m_pMethodStop;
};

In order to create the according Object components we add the following local helper variables

// Method helper
OpcUa_Int16 nsIdx = pNodeManager->getNameSpaceIndex();

and the following source code to the constructor of ControllerObject:

// Add Variable "PowerConsumption"
// Get the instance declaration node used as base for this variable instance
pInstanceDeclaration = pNodeManager->getInstanceDeclarationVariable(Ba_ControllerType_PowerConsumption);
UA_ASSERT(pInstanceDeclaration!=NULL);
// Create new variable and add it as component to this object
pDataItem = new OpcUa::DataItemType(this, pInstanceDeclaration, pNodeManager, m_pSharedMutex);
addStatus = pNodeManager->addNodeAndReference(this, pDataItem, OpcUaId_HasComponent);
UA_ASSERT(addStatus.isGood());
// Store information needed to access device
pUserData = new BaUserData(OpcUa_False, deviceAddress, 2);
pDataItem->setUserData(pUserData);
// Change value handling to get read and write calls to the node manager
//++++++ new code begin +++++++++++++++++++++++++++++++++++++++++++
// Add Method "Start"
UaString sName = "Start";
UaString sNodeId = UaString("%1.%2").arg(newNodeId.toString()).arg(sName);
m_pMethodStart = new UaMethodGeneric(sName, UaNodeId(sNodeId, nsIdx), m_defaultLocaleId);
addStatus = pNodeManager->addNodeAndReference(this, m_pMethodStart, OpcUaId_HasComponent);
UA_ASSERT(addStatus.isGood());
// Add Method "Stop"
sName = "Stop";
sNodeId = UaString("%1.%2").arg(newNodeId.toString()).arg(sName);
m_pMethodStop = new UaMethodGeneric(sName, UaNodeId(sNodeId, nsIdx), m_defaultLocaleId);
addStatus = pNodeManager->addNodeAndReference(this, m_pMethodStop, OpcUaId_HasComponent);
UA_ASSERT(addStatus.isGood());
//++++++ new code end +++++++++++++++++++++++++++++++++++++++++++++
}

Step 3: Implementing the handling of the MethodManager

In general the call of a method is based on the NodeId of the particular Object and the NodeId of the according Method.

getMethodHandle()

NodeManagerUaNode, a base class of NmBuildingAutomation implements NodeManager::getMethodHandle() which takes exactly these NodeIds to find the object that implements the MethodManager for the method to call

Provide MethodManager

In addition we have to implement the interface MethodManager. The implementation is on the class that implements the Object, in our case ControllerObject, by default.

In preparation for this we derive ControllerObject from MethodManager:

#ifndef __CONTROLLEROBJECT_H__
#define __CONTROLLEROBJECT_H__
#include "uaobjecttypes.h"
// ++
#include "methodmanager.h"
// ++
class NmBuildingAutomation;
class ControllerObject :
public UaObjectBase,
// ++
// ++
{
UA_DISABLE_COPY(ControllerObject);
public:
ControllerObject(
const UaString& name,
const UaNodeId& newNodeId,
const UaString& defaultLocaleId,
NmBuildingAutomation* pNodeManager,
OpcUa_UInt32 deviceAddress);
virtual ~ControllerObject(void);
OpcUa_Byte eventNotifier() const;
protected:
OpcUa_UInt32 m_deviceAddress;
private:
UaMethodGeneric* m_pMethodStart;
UaMethodGeneric* m_pMethodStop;
};

Then we override UaObject::getMethodManager()

#ifndef __CONTROLLEROBJECT_H__
#define __CONTROLLEROBJECT_H__
#include "uaobjecttypes.h"
#include "methodmanager.h"
class NmBuildingAutomation;
class ControllerObject :
public UaObjectBase,
{
UA_DISABLE_COPY(ControllerObject);
public:
ControllerObject(
const UaString& name,
const UaNodeId& newNodeId,
const UaString& defaultLocaleId,
NmBuildingAutomation* pNodeManager,
OpcUa_UInt32 deviceAddress);
virtual ~ControllerObject(void);
OpcUa_Byte eventNotifier() const;
// ++
// ++
protected:
OpcUa_UInt32 m_deviceAddress;
private:
UaMethodGeneric* m_pMethodStart;
};
#endif

and add its implementation to source file:

...
ControllerObject::~ControllerObject(void)
{
}
{
return Ua_EventNotifier_None;
}
// ++
{
return (MethodManager*)this;
}
// ++

This method will be invoked if a Method belonging to ControllerObject is called. Because the latter is derived from MethodManager we just need to return this casted to the MethodManager interface.

Method invocation

In order to implement the MethodManager interface we add beginCall() to header:

@code
#ifndef __CONTROLLEROBJECT_H__
#define __CONTROLLEROBJECT_H__
#include "uaobjecttypes.h"
#include "methodmanager.h"
class NmBuildingAutomation;
class ControllerObject :
public UaObjectBase,
{
UA_DISABLE_COPY(ControllerObject);
public:
ControllerObject(
const UaString& name,
const UaNodeId& newNodeId,
const UaString& defaultLocaleId,
NmBuildingAutomation* pNodeManager,
OpcUa_UInt32 deviceAddress);
virtual ~ControllerObject(void);
OpcUa_Byte eventNotifier() const;
// ++
// Implement MethodManager interface
const ServiceContext& serviceContext,
OpcUa_UInt32 callbackHandle,
MethodHandle* pMethodHandle,
const UaVariantArray& inputArguments);
// ++
protected:
OpcUa_UInt32 m_deviceAddress;
private:
UaMethodGeneric* m_pMethodStart;
};
#endif

Then we add the first edition of beginCall to source file as shown below:

MethodManagerCallback* pCallback, // Callback interface for the transaction
const ServiceContext& serviceContext, // Context for the service calls
OpcUa_UInt32 callbackHandle, // Handle for the Node in the callback
MethodHandle* pMethodHandle, // Handle for the Node of the Method
const UaVariantArray& inputArguments) // Actual input Argument(s)
{
UaStatus ret;
UaVariantArray outputArguments;
UaStatusCodeArray inputArgumentResults;
UaDiagnosticInfos inputArgumentDiag;
MethodHandleUaNode* pMethodHandleUaNode = static_cast<MethodHandleUaNode*>(pMethodHandle);
UaMethod* pMethod = NULL;
if(pMethodHandleUaNode)
{
pMethod = pMethodHandleUaNode->pUaMethod();
if(pMethod)
{
// Check if we have the start method
if ( pMethod->nodeId() == m_pMethodStart->nodeId() )
{
// Number of input arguments must be 0
if ( inputArguments.length() > 0 )
{
ret = OpcUa_BadInvalidArgument;
}
else
{
// To be done: the method call itself
}
}
// Check if we have the stop method
else if ( pMethod->nodeId() == m_pMethodStop->nodeId())
{
// Number of input arguments must be 0
if ( inputArguments.length() > 0 )
{
ret = OpcUa_BadInvalidArgument;
}
else
{
// To be done: the method call itself
}
}
else
{
assert(false);
ret = OpcUa_BadInvalidArgument;
}
pCallback->finishCall(
callbackHandle,
inputArgumentResults,
inputArgumentDiag,
outputArguments,
ret);
ret = OpcUa_Good;
}
else
{
assert(false);
ret = OpcUa_BadInvalidArgument;
}
}
else
{
assert(false);
ret = OpcUa_BadInvalidArgument;
}
return ret;
}

beginCall() calls a particular Method of an UA Object. It takes

  • a callback interface used by the IOManager to finish the action for each Node passed in the transaction,
  • a general context for the service calls containing information like the session object, return diagnostic mask, and timeout hint,
  • the handle for the Node in the callback. It has been passed to the IOManager with the beginModifyMonitoring method,
  • a handle for the Method Node, and
  • the actual input Arguments.

MethodManagerCallback::finishCall finishes the according Method call. It takes

  • the callback interface,
  • the result(s) of the actual input Argument(s),
  • the actual output Argument(s), and
  • the result of the StopMonitoring operation.

This version of beginCall() only allows ControllerObject::Start(). Because Start() is not taking any arguments beginCall() checks if the number of arguments equals zero. Before we are going to implement the method call in itself we have to introduce BaCommunicationInterface.

Introducing BaCommunicationInterface

We extend the definition of ControllerObject as shown above:

#ifndef __CONTROLLEROBJECT_H__
#define __CONTROLLEROBJECT_H__
#include "uaobjecttypes.h"
#include "methodmanager.h"
class NmBuildingAutomation;
// ++
class BaCommunicationInterface;
// ++
class ControllerObject :
public UaObjectBase,
{
UA_DISABLE_COPY(ControllerObject);
public:
ControllerObject(
const UaString& name,
const UaNodeId& newNodeId,
const UaString& defaultLocaleId,
NmBuildingAutomation* pNodeManager,
OpcUa_UInt32 deviceAddress,
// ++
BaCommunicationInterface *pCommIf);
// ++
virtual ~ControllerObject(void);
OpcUa_Byte eventNotifier() const;
// Implement MethodManager interface
const ServiceContext& serviceContext,
OpcUa_UInt32 callbackHandle,
MethodHandle* pMethodHandle,
const UaVariantArray& inputArguments);
protected:
OpcUa_UInt32 m_deviceAddress;
// ++
BaCommunicationInterface *m_pCommIf;
// ++
private:
UaMethodGeneric* m_pMethodStart;
UaMethodGeneric* m_pMethodStop;
};
#endif

And add to source file:

#include "controllerobject.h"
#include "nmbuildingautomation.h"
#include "uadatavariablecache.h"
#include "bacontrollervariable.h"
// ++
#include "bacommunicationinterface.h"
// ++
ControllerObject::ControllerObject(
const UaString& name,
const UaNodeId& newNodeId,
const UaString& defaultLocaleId,
NmBuildingAutomation* pNodeManager,
OpcUa_UInt32 deviceAddress,
// ++
BaCommunicationInterface *pCommIf)
// ++
: UaObjectBase(name, newNodeId, defaultLocaleId)
m_pSharedMutex(NULL),
m_deviceAddress(deviceAddress),
// ++
m_pCommIf(pCommIf)
// ++
{
//. . .

Completing first edition of beginCall()

For this we replace

// Check if we have the start method
if ( pMethod->nodeId() == m_pMethodStart->nodeId() )
{
// Number of input arguments must be 0
if ( inputArguments.length() > 0 )
{
ret = OpcUa_BadInvalidArgument;
}
else
{
// To be done: the method call itself
}
}

by

// Check if we have the start method
if ( pMethod->nodeId() == m_pMethodStart->nodeId())
{
// Number of input arguments must be 0
if ( inputArguments.length() > 0 )
{
ret = OpcUa_BadInvalidArgument;
}
else
{
ret = m_pCommIf->setControllerState(
m_deviceAddress,
BaCommunicationInterface::Ba_ControllerState_On );
}
}

Step 4: Calling Start Method with UA client

It is time to test Start with a client.

Monitor State

Figure 4-2 uses UA Expert for this:

Figure 4-2: Monitoring a Variable using UA Expert

Drag and Drop State into the Default DA View tab. Note that Value is 0. Right-click the Start item in the Address Space browser and click Call. We do not have to enter any arguments:

Figure 4-3: Calling the Start Method

Value has switched to 1.

Step 5: Creating a Method having Arguments

Finally we are going to create StartWithSetPoint, an InstanceDeclaration of the two subtypes of ControllerType as Figure 4-4 indicates:

Figure 4-4: ControllerType subtypes

We will implement this Method as we did above. However this Method takes Argument(s), unlike Start and Stop do.

As demonstrated in figure 4-4 AirConditionerControllerType has InstanceDeclaration HumiditySetPoint in addition to InstanceDeclaration TemperatureSetPoint derived from ControllerType. FurnaceControllerType, however, only derives TemperatureSetPoint. Due to this we will implement the Method depending on the controller type.

Preparations

For this reason we introduce a particular Method to ControllerObject, call(), to be overwritten in the sub classes:

. . .
// Implement MethodManager interface
virtual UaStatus beginCall(
const ServiceContext& serviceContext,
OpcUa_UInt32 callbackHandle,
MethodHandle* pMethodHandle,
const UaVariantArray& inputArguments);
// ++
// Own synchronous call implementation the can be overridden in subclasses
virtual UaStatus call(
UaMethod* /*pMethod*/,
const UaVariantArray& /*inputArguments*/,
UaVariantArray& /*outputArguments*/,
UaStatusCodeArray& /*inputArgumentResults*/,
UaDiagnosticInfos& /*inputArgumentDiag*/) { return OpcUa_BadMethodInvalid; }
// ++
. . .

Unlike beginCall() this Method is called synchronously. That is call() does not ask for callback handle and object but takes a pointer to UaMethod instance explicitly and returns output arguments, status codes, and diagnostic information.

The implementation in FurnaceControllerType is shown below, acting for both sub classes:

virtual UaNodeId typeDefinitionId() const;
// override Controller::call()
virtual UaStatus call(
UaMethod* pMethod,
const UaVariantArray& inputArguments,
UaVariantArray& /*outputArguments*/,
UaStatusCodeArray& inputArgumentResults,
UaDiagnosticInfos& /*inputArgumentDiag*/);

and:

UaStatus FurnaceControllerObject::call(
UaMethod* pMethod,
const UaVariantArray& inputArguments,
UaVariantArray& /*outputArguments*/,
UaStatusCodeArray& inputArgumentResults,
UaDiagnosticInfos& /*inputArgumentDiag*/)
{
UaStatus ret;
if(pMethod)
{
// Check if we have the start method
if ( pMethod->nodeId() == m_pMethodStartWithSetpoint->nodeId())
{
// Number of input arguments must be 1
if ( inputArguments.length() != 1 )
{
ret = OpcUa_BadInvalidArgument;
}
else
{
inputArgumentResults.create(1);
// validate input parameter
if ( inputArguments[0].Datatype != OpcUaType_Double )
{
ret = OpcUa_BadInvalidArgument;
inputArgumentResults[0] = OpcUa_BadTypeMismatch;
}
else
{
// To be done
}
}
}
else
{
ret = OpcUa_BadMethodInvalid;
}
}
return ret;
}

This Method resembles in some extend to ControllerObject::beginCall(). Note that we still leave StartWithSetPoint functionality unimplemented in order to extend the definition:

class FurnaceControllerObject :
public ControllerObject
{
public:
FurnaceControllerObject(
const UaString& name,
const UaNodeId& newNodeId,
const UaString& defaultLocaleId,
NmBuildingAutomation* pNodeManager,
OpcUa_UInt32 deviceAddress,
// ++
BaCommunicationInterface *pCommIf
//++
);
virtual ~FurnaceControllerObject(void);
virtual UaNodeId typeDefinitionId() const;
// override Controller::call()
virtual UaStatus call(
UaMethod* pMethod,
const UaVariantArray& inputArguments,
UaVariantArray& /*outputArguments*/,
UaStatusCodeArray& inputArgumentResults,
UaDiagnosticInfos& /*inputArgumentDiag*/);
private:
// ++
UaMethodGeneric *m_pMethodStartWithSetpoint;
// ++
};

and the implementation:

#include "furnacecontrollerobject.h"
#include "buildingautomationtypeids.h"
#include "nmbuildingautomation.h"
#include "bacontrollervariable.h"
// ++
#include "bacommunicationinterface.h"
// ++
FurnaceControllerObject::FurnaceControllerObject(
const UaString& name,
const UaNodeId& newNodeId,
const UaString& defaultLocaleId,
NmBuildingAutomation* pNodeManager,
OpcUa_UInt32 deviceAddress,
// ++
BaCommunicationInterface *pCommIf
// ++
)
: ControllerObject(name, newNodeId, defaultLocaleId, pNodeManager, deviceAddress /*++*/, pCommIf /*++*/ )
{
. . .

of the two sub classes as shown above in examplary manner.

Create InstanceDeclarations

In order to do this we add the following code snippet to NmBuildingAutomation::createTypeNode():

. . .
UaPropertyMethodArgument* pPropertyArg = NULL;
UaUInt32Array nullarray;
. . .
/***************************************************************
Create the AirConditionerController Type Instance declaration
***************************************************************/
. . .
// Add Method "StartWithSetpoint"
pMethod = new UaMethodGeneric(
"StartWithSetpoint",
UaNodeId(Ba_AirConditionerControllerType_StartWithSetpoint, getNameSpaceIndex()),
m_defaultLocaleId);
addStatus = addNodeAndReference(pAirConditionerControllerType, pMethod, OpcUaId_HasComponent);
UA_ASSERT(addStatus.isGood());
// Add Property "InputArguments"
pPropertyArg = new UaPropertyMethodArgument(
UaNodeId(Ba_AirConditionerControllerType_StartWithSetpoint_In, getNameSpaceIndex()), // NodeId of the property
OpcUa_AccessLevels_CurrentRead, // Access level of the property
2, // Number of arguments
// Argument TemperatureSetPoint
pPropertyArg->setArgument(
0, // Index of the argument
"TemperatureSetPoint", // Name of the argument
UaNodeId(OpcUaId_Double),// Data type of the argument
-1, // Array rank of the argument
nullarray, // Array dimensions of the argument
UaLocalizedText("en", "Controller set point for temperature")); // Description
// Argument HumiditySetpoint
pPropertyArg->setArgument(
1, // Index of the argument
"HumiditySetpoint", // Name of the argument
UaNodeId(OpcUaId_Double),// Data type of the argument
-1, // Array rank of the argument
nullarray, // Array dimensions of the argument
UaLocalizedText("en", "Controller set point for humidity")); // Description
// Add property to method
addStatus = addNodeAndReference(pMethod, pPropertyArg, OpcUaId_HasProperty);
UA_ASSERT(addStatus.isGood());
. . .

Note that we have to set the number of Arguments to two when instantiating the Method in AirConditionControllerType context. The implementation for FurnaceControllerType is quite similar, however it provides only one Argument that is TemperatureSetPoint:

. . .
/**************************************************************
Create the FurnaceController Type
**************************************************************/
. . .
// Add Method "StartWithSetpoint"
pMethod = new UaMethodGeneric(
"StartWithSetpoint",
UaNodeId(Ba_FurnaceControllerType_StartWithSetpoint, getNameSpaceIndex()),
m_defaultLocaleId);
addStatus = addNodeAndReference(pFurnaceControllerType, pMethod, OpcUaId_HasComponent);
UA_ASSERT(addStatus.isGood());
// Add Property "InputArguments"
pPropertyArg = new UaPropertyMethodArgument(
UaNodeId(Ba_FurnaceControllerType_StartWithSetpoint_In, getNameSpaceIndex()), // NodeId of the property
OpcUa_AccessLevels_CurrentRead, // Access level of the property
1, // Number of arguments
// Argument TemperatureSetPoint
pPropertyArg->setArgument(
0, // Index of the argument
"TemperatureSetPoint", // Name of the argument
UaNodeId(OpcUaId_Double),// Data type of the argument
-1, // Array rank of the argument
nullarray, // Array dimensions of the argument
UaLocalizedText("en", "Controller set point for temperature")); // Description
// Add property to method
addStatus = addNodeAndReference(pMethod, pPropertyArg, OpcUaId_HasProperty);
UA_ASSERT(addStatus.isGood());
. . .

Create methods as components of object instances

In order to do this we add the following code snippet to FurnaceControllerObject::FurnaceControllerObject

// FurnaceControllerObject::FurnaceControllerObject
// Method helpers
UaPropertyMethodArgument* pPropertyArg = NULL;
UaUInt32Array nullarray;
OpcUa_Int16 nsIdx = pNodeManager->getNameSpaceIndex();
// . . .
// Add Method "StartWithSetpoint"
UaString sName = "StartWithSetpoint";
UaString sNodeId = UaString("%1.%2").arg(newNodeId.toString()).arg(sName);
m_pMethodStartWithSetpoint = new UaMethodGeneric(
sName,
UaNodeId(sNodeId, nsIdx),
m_defaultLocaleId);
addStatus = pNodeManager->addNodeAndReference(this, m_pMethodStartWithSetpoint, OpcUaId_HasComponent);
UA_ASSERT(addStatus.isGood());
// Add Property "InputArguments"
sName = "StartWithSetpoint";
sNodeId = UaString("%1.%2").arg(m_pMethodStartWithSetpoint->nodeId().toString()).arg(sName);
pPropertyArg = new UaPropertyMethodArgument(
UaNodeId(sNodeId, nsIdx), // NodeId of the property
Ua_AccessLevel_CurrentRead, // Access level of the property
1, // Number of arguments
// Argument TemperatureSetPoint
pPropertyArg->setArgument(
0, // Index of the argument
"TemperatureSetPoint", // Name of the argument
UaNodeId(OpcUaId_Double),// Data type of the argument
OpcUa_ValueRanks_Scalar, // Array rank of the argument
nullarray, // Array dimensions of the argument
UaLocalizedText("en", "Controller set point for temperature")); // Description
// Add property to method
addStatus = pNodeManager->addNodeAndReference(m_pMethodStartWithSetpoint, pPropertyArg, OpcUaId_HasProperty);
UA_ASSERT(addStatus.isGood());
}

and to AirConditionerControllerObject::AirConditionerControllerObject:

// AirConditionerControllerObject::AirConditionerControllerObject
// Method helpers
UaPropertyMethodArgument* pPropertyArg = NULL;
UaUInt32Array nullarray;
OpcUa_Int16 nsIdx = pNodeManager->getNameSpaceIndex();
// . . .
// Add Method "StartWithSetpoint"
UaString sName = "StartWithSetpoint";
UaString sNodeId = UaString("%1.%2").arg(newNodeId.toString()).arg(sName);
m_pMethodStartWithSetpoint = new UaMethodGeneric(
sName,
UaNodeId(sNodeId, nsIdx),
m_defaultLocaleId);
addStatus = pNodeManager->addNodeAndReference(this, m_pMethodStartWithSetpoint, OpcUaId_HasComponent);
UA_ASSERT(addStatus.isGood());
// Add Property "InputArguments"
sName = "StartWithSetpoint";
sNodeId = UaString("%1.%2").arg(m_pMethodStartWithSetpoint->nodeId().toString()).arg(sName);
pPropertyArg = new UaPropertyMethodArgument(
UaNodeId(sNodeId, nsIdx), // NodeId of the property
Ua_AccessLevel_CurrentRead, // Access level of the property
2, // Number of arguments
// Argument TemperatureSetPoint
pPropertyArg->setArgument(
0, // Index of the argument
"TemperatureSetPoint", // Name of the argument
UaNodeId(OpcUaId_Double),// Data type of the argument
OpcUa_ValueRanks_Scalar, // Array rank of the argument
nullarray, // Array dimensions of the argument
UaLocalizedText("en", "Controller set point for temperature")); // Description
// Argument HumiditySetpoint
pPropertyArg->setArgument(
1, // Index of the argument
"HumiditySetpoint", // Name of the argument
UaNodeId(OpcUaId_Double),// Data type of the argument
OpcUa_ValueRanks_Scalar, // Array rank of the argument
nullarray, // Array dimensions of the argument
UaLocalizedText("en", "Controller set point for humidity")); // Description
// Add property to method
addStatus = pNodeManager->addNodeAndReference(m_pMethodStartWithSetpoint, pPropertyArg, OpcUaId_HasProperty);
UA_ASSERT(addStatus.isGood());
}

Complete call() implementation in sub classes

Now we finish this lesson in completing call() as shown below for FurnaceControllerObject.

UaStatus FurnaceControllerObject::call(
UaMethod* pMethod,
const UaVariantArray& inputArguments,
UaVariantArray& /*outputArguments*/,
UaStatusCodeArray& inputArgumentResults,
UaDiagnosticInfos& /*inputArgumentDiag*/)
{
UaStatus ret;
if(pMethod)
{
// Check if we have the start method
if ( pMethod->nodeId() == m_pMethodStartWithSetpoint->nodeId())
{
// Number of input arguments must be 1
if ( inputArguments.length() != 1 )
{
ret = OpcUa_BadInvalidArgument;
}
else
{
inputArgumentResults.create(1);
// validate input parameter
if ( inputArguments[0].Datatype != OpcUaType_Double )
{
ret = OpcUa_BadInvalidArgument;
inputArgumentResults[0] = OpcUa_BadTypeMismatch;
}
else
{
// ++
UaVariant vTemp;
OpcUa_Double value;
ret = m_pCommIf->setControllerState(
m_deviceAddress,
BaCommunicationInterface::Ba_ControllerState_On );
if ( ret.isGood() )
{
// Setting TemperatureSetPoint
vTemp = inputArguments[0];
vTemp.toDouble(value);
ret = m_pCommIf->setControllerData(
m_deviceAddress,
1,
value);
}
// ++
}
}
}
else
{
ret = OpcUa_BadMethodInvalid;
}
}
return ret;
}

In addition to the state flag this implementation sets also the setpoint for temperature. The code below demonstrates that for AirConditionControllerObject whereas two setpoints are set:

UaStatus AirConditionerControllerObject::call(
UaMethod* pMethod,
const UaVariantArray& inputArguments,
UaVariantArray& /*outputArguments*/,
UaStatusCodeArray& inputArgumentResults,
UaDiagnosticInfos& /*inputArgumentDiag*/)
{
UaStatus ret;
if(pMethod)
{
// Check if we have the start method
if ( pMethod->nodeId() == m_pMethodStartWithSetpoint->nodeId())
{
// Number of input arguments must be 2
if ( inputArguments.length() != 2 )
{
ret = OpcUa_BadInvalidArgument;
}
else
{
inputArgumentResults.create(2);
// validate input parameters
if ( inputArguments[0].Datatype != OpcUaType_Double )
{
ret = OpcUa_BadInvalidArgument;
inputArgumentResults[0] = OpcUa_BadTypeMismatch;
}
if ( inputArguments[1].Datatype != OpcUaType_Double )
{
ret = OpcUa_BadInvalidArgument;
inputArgumentResults[1] = OpcUa_BadTypeMismatch;
}
if (ret.isGood())
{
// ++
UaVariant vTemp;
OpcUa_Double value;
ret = m_pCommIf->setControllerState(
m_deviceAddress,
BaCommunicationInterface::Ba_ControllerState_On );
if ( ret.isGood() )
{
// Setting TemperatureSetPoint
vTemp = inputArguments[0];
vTemp.toDouble(value);
ret = m_pCommIf->setControllerData(
m_deviceAddress,
1,
value);
// Setting HumiditySetPoint
vTemp = inputArguments[1];
vTemp.toDouble(value);
ret = m_pCommIf->setControllerData(
m_deviceAddress,
4,
value);
}
// ++
}
}
}
else
{
ret = OpcUa_BadMethodInvalid;
}
}
return ret;
}

Call StartWithSetpoint with UA Expert

Figure 4-5 demonstrates how to call StartWithSetpoint and passing two arguments to it with UA Expert:

Figure 4-5: UA Expert calling StartWithSetpoint