UA Server SDK C++ Bundle  1.3.2.200
 All Data Structures Namespaces Functions Variables Typedefs Enumerations Enumerator Groups Pages
Lesson 3: Connecting the nodes to real time data

In the previous Lesson 2: Extending the Address Space with real world data we created a nice object oriented address space but the data provided by this address space is only initial values. There is no connection to real time data implemented yet.

If the source of the real time data delivers data changes through an event based mechanism, the connection to the data source is very simple. The only thing that needs to be implemented is the call to UaVariable::setValue on the variable nodes if a data change arrives for this variable. All the read and data monitoring is handled already by the SDK. An example for this updata mechanism can be found in the Hello World server example -> Create a variable in the server -> Simulate Data.
The application specific node manager implementation gets informed about write action to the variables by overwriting the IOManagerUaNode methods IOManagerUaNode::beforeSetAttributeValue() or IOManagerUaNode::afterSetAttributeValue() in the node manager implementation.

If the source of the real time data requires data polling, this lesson explains the steps necessary to implement read, monitoring and write access to device data.

Content:

Step 1: Introducing BaUserData

The read and write access to the node attributes is implemented in the SDK class IOManagerUaNode by accesssing the UaNode interfaces like UaVariable or UaObject. All necessary information is already provided during the creation of the node in NodeManagerUaNode::afterStartUp() or during runitme.

The variable specific value attribute handling is defined by the bit mask returned from UaVariable::valueHandling. There are three options

  • UaVariable_Value_Cache | UaVariable_Value_CacheIsSource (Default setting)
    This default setting tells the SDK that the value is always up-to-date and all read, write and monitoring actions can be executed on the UaVariable node. This option is used if the variable represents internal configuration data or the data source is event based and delivers data changes automatically.
  • UaVariable_Value_Cache (Used in this lesson)
    This setting is used if the read and write access for this variable will be implemented by overwriting the methods IOManagerUaNode::readValues() and IOManagerUaNode::writeValues() in the node manager implementation.
  • UaVariable_Value_None
    This setting is used if the IOManager interface is implemented directly for this variable. This requires to overwrite NodeManagerUaNode::getIOManager() and to return the own IOManager responsible for the value attribute of this variable.

We need to change all controller variables that provide data from the devices to the option UaVariable_Value_Cache to get the read and write calls for these variables. To execute these read and write actions we need to store the information to access the device in the variable. This is done by using the UaNode functionality UaNode::setUserData() and UaNode::getUserData(). For this storage capability we need to derive our data class from UserDataBase to allow the node to delete the data object when the node is deleted. The following figure shows the new BaUserData class and how it is related to the other classes.

Figure 3-1 Introducing BaUserData

UserDataBase Class Definition

// controllerobject.h
class BaUserData : public UserDataBase
{
UA_DISABLE_COPY(BaUserData);
public:
BaUserData(
OpcUa_Boolean isState,
OpcUa_UInt32 deviceAddress,
OpcUa_UInt32 variableOffset)
: m_isState(isState),
m_deviceAddress(deviceAddress),
m_variableOffset(variableOffset)
{}
virtual ~BaUserData(){}
inline OpcUa_UInt32 isState() const { return m_isState; }
inline OpcUa_UInt32 deviceAddress() const { return m_deviceAddress; }
inline OpcUa_UInt32 variableOffset() const { return m_variableOffset; }
private:
OpcUa_Boolean m_isState;
OpcUa_UInt32 m_deviceAddress;
OpcUa_UInt32 m_variableOffset;
};

The class stores the following information:

  • m_isState
    Flag indicating if the variable represents the state of the controller
  • m_deviceAddress
    The device address used to access the device
  • m_variableOffset
    The offset of the variable in the device data area

Step 2: Use of BaUserData in Controller Classes

Two settings are necessary to change the behaviour of a UaVariable to the device read and write actions.

To pass the necessary device address and the communication interface to the controller classes we need to extend the constructors. The changes are shown in the following code snippets.

class ControllerObject :
public UaObjectBase
{
UA_DISABLE_COPY(ControllerObject);
public:
ControllerObject(
const UaString& name,
const UaNodeId& newNodeId,
const UaString& defaultLocaleId,
NmBuildingAutomation* pNodeManager,
//++++++ new code begin +++++++++++++++++++++++++++++++++++++++++++
OpcUa_UInt32 deviceAddress,
BaCommunicationInterface *pCommIf);
//++++++ new code end +++++++++++++++++++++++++++++++++++++++++++++
class AirConditionerControllerObject :
public ControllerObject
{
public:
AirConditionerControllerObject(
const UaString& name,
const UaNodeId& newNodeId,
const UaString& defaultLocaleId,
NmBuildingAutomation* pNodeManager,
//++++++ new code begin +++++++++++++++++++++++++++++++++++++++++++
OpcUa_UInt32 deviceAddress,
BaCommunicationInterface *pCommIf);
//++++++ new code end +++++++++++++++++++++++++++++++++++++++++++++
class FurnaceControllerObject :
public ControllerObject
{
public:
FurnaceControllerObject(
const UaString& name,
const UaNodeId& newNodeId,
const UaString& defaultLocaleId,
NmBuildingAutomation* pNodeManager,
//++++++ new code begin +++++++++++++++++++++++++++++++++++++++++++
OpcUa_UInt32 deviceAddress,
BaCommunicationInterface *pCommIf);
//++++++ new code end +++++++++++++++++++++++++++++++++++++++++++++

The use of the new parameters and the necessary settings to the variable objects is shown in the following code:

ControllerObject::ControllerObject(
const UaString& name,
const UaNodeId& newNodeId,
const UaString& defaultLocaleId,
NmBuildingAutomation* pNodeManager,
//++++++ new / modicied code begin ++++++++++++++++++++++++++++++++
OpcUa_UInt32 deviceAddress,
BaCommunicationInterface *pCommIf)
: UaObjectBase(name, newNodeId, defaultLocaleId)
m_pSharedMutex(NULL),
m_deviceAddress(deviceAddress),
m_pCommIf(pCommIf)
//++++++ new code end +++++++++++++++++++++++++++++++++++++++++++++
{
// Use a mutex shared across all variables of this object
m_pSharedMutex = new UaMutexRefCounted;
UaVariable* pInstanceDeclaration = NULL;
OpcUa::BaseDataVariableType* pDataVariable = NULL;
OpcUa::DataItemType* pDataItem = NULL;
OpcUa::AnalogItemType* pAnalogItem = NULL;
BaUserData* pUserData = NULL;
UaStatus addStatus;
/**************************************************************
Create the Controller components
**************************************************************/
// Add Variable "State"
// Get the instance declaration node used as base for this variable instance
pInstanceDeclaration = pNodeManager->getInstanceDeclarationVariable(Ba_ControllerType_State);
UA_ASSERT(pInstanceDeclaration!=NULL);
pDataVariable = new OpcUa::BaseDataVariableType(
this, // Parent node
pInstanceDeclaration, // Instance declaration variable this variable instance is based on
pNodeManager, // Node manager responsible for this variable
m_pSharedMutex); // Shared mutex used across all variables of this object
addStatus = pNodeManager->addNodeAndReference(this, pDataVariable, OpcUaId_HasComponent);
UA_ASSERT(addStatus.isGood());
//++++++ new code begin +++++++++++++++++++++++++++++++++++++++++++
// Store information needed to access device
pUserData = new BaUserData(OpcUa_True, deviceAddress, 0);
pDataVariable->setUserData(pUserData);
// Change value handling to get read and write calls to the node manager
//++++++ new code end +++++++++++++++++++++++++++++++++++++++++++++
// Add Variable "Temperature"
// ...
//++++++ new code begin +++++++++++++++++++++++++++++++++++++++++++
// Store information needed to access device
pUserData = new BaUserData(OpcUa_False, deviceAddress, 0);
pAnalogItem->setUserData(pUserData);
// Change value handling to get read and write calls to the node manager
//++++++ new code end +++++++++++++++++++++++++++++++++++++++++++++
// Add Variable "TemperatureSetPoint"
// ...
//++++++ new code begin +++++++++++++++++++++++++++++++++++++++++++
// Store information needed to access device
pUserData = new BaUserData(OpcUa_False, deviceAddress, 1);
pAnalogItem->setUserData(pUserData);
// Change value handling to get read and write calls to the node manager
//++++++ new code end +++++++++++++++++++++++++++++++++++++++++++++
// Add Variable "PowerConsumption"
// ...
//++++++ new code begin +++++++++++++++++++++++++++++++++++++++++++
// 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 end +++++++++++++++++++++++++++++++++++++++++++++

The two setting calls on the variables are added to each variable instance of the controller base class in the constructor of the class ControllerObject.

Please note the different device offsets used in the examples above. The offset is based on the knowledge about the controller device.

AirConditionerControllerObject::AirConditionerControllerObject(
const UaString& name,
const UaNodeId& newNodeId,
const UaString& defaultLocaleId,
NmBuildingAutomation* pNodeManager,
//++++++ new / modified code begin +++++++++++++++++++++++++++++++++++++++++++
OpcUa_UInt32 deviceAddress,
BaCommunicationInterface *pCommIf)
: ControllerObject(name, newNodeId, defaultLocaleId, pNodeManager, deviceAddress, pCommIf)
//++++++ new / modified code end +++++++++++++++++++++++++++++++++++++++++++++
{
UaVariable* pInstanceDeclaration = NULL;
OpcUa::AnalogItemType* pAnalogItem = NULL;
BaUserData* pUserData = NULL;
UaStatus addStatus;
/**************************************************************
Create the AirConditionerController components
**************************************************************/
// Add Variable "Humidity"
pInstanceDeclaration = pNodeManager->getInstanceDeclarationVariable(Ba_AirConditionerControllerType_Humidity);
UA_ASSERT(pInstanceDeclaration!=NULL);
// Create new variable and add it as component to this object
pAnalogItem = new OpcUa::AnalogItemType(
this, // Parent node
pInstanceDeclaration, // Instance declaration variable this variable instance is based on
pNodeManager, // Node manager responsible for this variable
m_pSharedMutex); // Shared mutex used across all variables of this object
addStatus = pNodeManager->addNodeAndReference(this, pAnalogItem, OpcUaId_HasComponent);
UA_ASSERT(addStatus.isGood());
// Set property values - the property nodes are already created
UaRange tempRange(0, 100);
pAnalogItem->setEURange(tempRange);
UaEUInformation tempEUInformation("", -1, UaLocalizedText("en", "%"), UaLocalizedText("en", "Percent"));
pAnalogItem->setEngineeringUnits(tempEUInformation);
//++++++ new code begin +++++++++++++++++++++++++++++++++++++++++++
// Store information needed to access device
pUserData = new BaUserData(OpcUa_False, deviceAddress, 3);
pAnalogItem->setUserData(pUserData);
// Change value handling to get read and write calls to the node manager
//++++++ new code end +++++++++++++++++++++++++++++++++++++++++++++
// Add Variable "HumiditySetpoint"
// Get the instance declaration node used as base for this variable instance
pInstanceDeclaration = pNodeManager->getInstanceDeclarationVariable(Ba_AirConditionerControllerType_HumiditySetpoint);
UA_ASSERT(pInstanceDeclaration!=NULL);
// Create new variable and add it as component to this object
pAnalogItem = new OpcUa::AnalogItemType(this, pInstanceDeclaration, pNodeManager, m_pSharedMutex);
addStatus = pNodeManager->addNodeAndReference(this, pAnalogItem, OpcUaId_HasComponent);
UA_ASSERT(addStatus.isGood());
// Set property values - the property nodes are already created
pAnalogItem->setEURange(tempRange);
pAnalogItem->setEngineeringUnits(tempEUInformation);
//++++++ new code begin +++++++++++++++++++++++++++++++++++++++++++
// Store information needed to access device
pUserData = new BaUserData(OpcUa_False, deviceAddress, 4);
pAnalogItem->setUserData(pUserData);
// Change value handling to get read and write calls to the node manager
//++++++ new code end +++++++++++++++++++++++++++++++++++++++++++++

The two setting calls on the variables are also added to each variable instance of the air conditioner controller class in the constructor of the class AirConditionerControllerObject.

FurnaceControllerObject::FurnaceControllerObject(
const UaString& name,
const UaNodeId& newNodeId,
const UaString& defaultLocaleId,
NmBuildingAutomation* pNodeManager,
//++++++ new / modified code begin +++++++++++++++++++++++++++++++++++++++++++
OpcUa_UInt32 deviceAddress,
BaCommunicationInterface *pCommIf)
: ControllerObject(name, newNodeId, defaultLocaleId, pNodeManager, deviceAddress, pCommIf)
//++++++ new / modified code end +++++++++++++++++++++++++++++++++++++++++++++
{
UaVariable* pInstanceDeclaration = NULL;
OpcUa::DataItemType* pDataItem = NULL;
BaUserData* pUserData = NULL;
UaStatus addStatus;
/**************************************************************
Create the FurnaceController components
**************************************************************/
// Add Variable "GasFlow"
// Get the instance declaration node used as base for this variable instance
pInstanceDeclaration = pNodeManager->getInstanceDeclarationVariable(Ba_FurnaceControllerType_GasFlow);
UA_ASSERT(pInstanceDeclaration!=NULL);
pDataItem = new OpcUa::DataItemType(
this, // Parent node
pInstanceDeclaration, // Instance declaration variable this variable instance is based on
pNodeManager, // Node manager responsible for this variable
m_pSharedMutex); // Shared mutex used across all variables of this object
addStatus = pNodeManager->addNodeAndReference(this, pDataItem, OpcUaId_HasComponent);
UA_ASSERT(addStatus.isGood());
//++++++ new code begin +++++++++++++++++++++++++++++++++++++++++++
// Store information needed to access device
pUserData = new BaUserData(OpcUa_False, deviceAddress, 3);
pDataItem->setUserData(pUserData);
// Change value handling to get read and write calls to the node manager
//++++++ new code end +++++++++++++++++++++++++++++++++++++++++++++

The two setting calls on the variables are also added to each variable instance of the furnace controller class in the constructor of the class FurnaceControllerObject.

Step 3: Implementing IOManagerUaNode Functionality

After changing the UaVariable settings, the method IOManagerUaNode::readValues is called for UA Read service invocations and data monitoring and the method IOManagerUaNode::writeValues is called for UA Write service invocations. We need to overwrite these methods in the class NmBuildingAutomation since the implementation in IOManagerUaNode is empty.

Figure 3-2 Overwriting IOManagerUaNode methods for read and write

NmBuildingAutomation Class Definition

NmBuildingAutomation has to overwrite readValues() and writeValues() shown in the following class definition. We need to add also the member variable for the communication interface pointer.

#ifndef __NMBUILDINGAUTOMATION_H__
#define __NMBUILDINGAUTOMATION_H__
#include "nodemanagerbase.h"
//++++++ new code begin +++++++++++++++++++++++++++++
class BaCommunicationInterface;
//++++++ new code end +++++++++++++++++++++++++++++++
class NmBuildingAutomation : public NodeManagerBase
{
UA_DISABLE_COPY(NmBuildingAutomation);
public:
NmBuildingAutomation();
virtual ~NmBuildingAutomation();
// NodeManagerUaNode implementation
//++++++ new code begin +++++++++++++++++++++++++++++
// IOManagerUaNode implementation
const UaVariableArray &arrUaVariables,
UaDataValueArray &arrDataValues);
const UaVariableArray &arrUaVariables,
const PDataValueArray &arrpDataValues,
UaStatusCodeArray &arrStatusCodes);
//++++++ new code end +++++++++++++++++++++++++++++++
UaVariable* getInstanceDeclarationVariable(OpcUa_UInt32 numericIdentifier);
private:
UaStatus createTypeNodes();
//++++++ new code begin +++++++++++++++++++++++++++++
BaCommunicationInterface *m_pCommIf;
//++++++ new code end +++++++++++++++++++++++++++++++
};
#endif // __NMBUILDINGAUTOMATION_H__

NmBuildingAutomation Class Implementation

The following code provides the method prototypes and the creation of the communication interface. This BaCommunicationInterface interface is introduced in the next step.

#include "nmbuildingautomation.h"
#include "buildingautomationtypeids.h"
#include "airconditionercontrollerobject.h"
#include "furnacecontrollerobject.h"
#include "opcua_analogitemtype.h"
#include "bacommunicationinterface.h"
NmBuildingAutomation::NmBuildingAutomation()
: NodeManagerBase("MyUaServer/BuildingAutomation")
{
//++++++ new code begin +++++++++++++++++++++++++++++
m_pCommIf = new BaCommunicationInterface;
//++++++ new code end +++++++++++++++++++++++++++++++
}
NmBuildingAutomation::~NmBuildingAutomation()
{
//++++++ new code begin +++++++++++++++++++++++++++++
delete m_pCommIf;
//++++++ new code end +++++++++++++++++++++++++++++++
}
// . . .
//++++++ new code begin +++++++++++++++++++++++++++++
const UaVariableArray &arrUaVariables,
UaDataValueArray &arrDataValues)
{
UaStatus ret;
return ret;
}
const UaVariableArray &arrUaVariables,
const PDataValueArray &arrpDataValues,
UaStatusCodeArray &arrStatusCodes)
{
UaStatus ret;
return ret;
}
//++++++ new code end +++++++++++++++++++++++++++++++

For now we leave the function bodies almost empty due to the lack of a communication interface in order to get the information to access devices. The communicatin interface is introduced in the next step.

Step 4: Introducing BaCommunicationInterface

The remaining part of this lesson aims on linking IOManagerUaNode functionality with device data. Therefore we introduce the class BaCommunicationInterface, a class being prepared for providing simulation data for our scenario through the interface shown in Figure 3-3.

Figure 3-3 BaCommunicationInterface

Whereas

  • getCountControllers(): returns number of available controllers
  • getControllerConfig(): provides configuration information of a controller e.g. address information and type
  • getControllerState(): provides the current state of a controller
  • getControllerData(): allows read access to one data point in a controller with controller index and data offset in controller
  • setControllerData(): allows write access to one data point in a controller with controller index and data offset in controller

Step 5: Implementing NmBuildingAutomation::readValues() for Non-State Variables

In the next setp we are implementing NmBuildingAutomation::readValues() for Non-State Variables.

const UaVariableArray &arrUaVariables,
UaDataValueArray &arrDataValues)
{
UaStatus ret;
OpcUa_UInt32 i;
OpcUa_UInt32 count = arrUaVariables.length();
UaDateTime timeStamp = UaDateTime::now();
// Create result array
arrDataValues.create(count);
for (i=0; i<count; i++)
{
// Set time stamps
arrDataValues[i].setSourceTimestamp(timeStamp);
arrDataValues[i].setServerTimestamp(timeStamp);
// Cast UaVariable to BaControllerVariable
UaVariable* pVariable = arrUaVariables[i];
if (pVariable)
{
BaUserData* pUserData = (BaUserData*)pVariable->getUserData();
if ( pUserData )
{
UaVariant vTemp;
UaStatusCode status;
if ( pUserData->isState() == OpcUa_False )
{
// Read of a data variable
OpcUa_Double value;
// Get the data for the controller from the communication interface
status = m_pCommIf->getControllerData(
pUserData->deviceAddress(),
pUserData->variableOffset(),
value);
if ( status.isGood() )
{
// Set value
vTemp.setDouble(value);
arrDataValues[i].setValue(vTemp, OpcUa_True, OpcUa_False);
}
else
{
// Set Error
arrDataValues[i].setStatusCode(status.statusCode());
}
}
else
{
arrDataValues[i].setStatusCode(OpcUa_BadNotImplemented);
}
}
else
{
arrDataValues[i].setStatusCode(OpcUa_BadInternalError);
}
}
else
{
arrDataValues[i].setStatusCode(OpcUa_BadInternalError);
}
}
return ret;
}

readValues() provides an array of UaVariable interface pointers used to indicate which variables should be read. The second parameter is an array of UaDataValue classes used to return the values read.

After creating the output array for the read values, the requested variables are processed in a loop.

For every variable the user data is requested by using the method UaNode::getUserData(). The returned user data object is casted to BaUserData. The user data is then used to detect if the state or one of the data variables is requested.
If a data value is requested, the method getControllerData of the communication interface is called. The device address and the offset stored in the user data are used to get the current value. The returned value or, in the case of an error, the status are used to set the data value in the out parameter array.

Step 6: Creating devices based on BaCommunicationInterface information

We can use BaCommunicationInterface also in order to create devices in NmBuildingAutomation::afterStartUp().

For this we need the number of available controllers provided by getControllerCount(), and additionally configuration information, especially the type of each controller and its address provided by getControllerConfig().

We replace the method NmBuildingAutomation::afterStartUp with the following code.

{
UaStatus ret;
UaFolder *pFolder = NULL;
AirConditionerControllerObject *pAirConditioner = NULL;
FurnaceControllerObject *pFurnace = NULL;
UaString sControllerName;
OpcUa_UInt32 i;
OpcUa_UInt32 controllerAddress;
BaCommunicationInterface::ControllerType controllerType;
// Get the count of configured controllers
OpcUa_UInt32 count = m_pCommIf->getCountControllers();
createTypeNodes();
/**************************************************************
Create a folder for the controller objects and add the folder to the ObjectsFolder
***************************************************************/
pFolder = new UaFolder("BuildingAutomation", UaNodeId("BuildingAutomation", getNameSpaceIndex()), m_defaultLocaleId);
ret = addNodeAndReference(OpcUaId_ObjectsFolder, pFolder, OpcUaId_Organizes);
/**************************************************************
Create the Controller Object Instances
**************************************************************/
for ( i=0; i<count; i++ )
{
ret = m_pCommIf->getControllerConfig(
i,
controllerType,
sControllerName,
controllerAddress);
if ( controllerType == BaCommunicationInterface::AIR_CONDITIONER )
{
pAirConditioner = new AirConditionerControllerObject(
sControllerName,
UaNodeId(sControllerName, getNameSpaceIndex()),
m_defaultLocaleId,
this,
controllerAddress,
m_pCommIf);
ret = addNodeAndReference(pFolder, pAirConditioner, OpcUaId_Organizes);
UA_ASSERT(ret.isGood());
}
else
{
pFurnace = new FurnaceControllerObject(
sControllerName,
UaNodeId(sControllerName, getNameSpaceIndex()),
m_defaultLocaleId,
this,
controllerAddress,
m_pCommIf);
ret = addNodeAndReference(pFolder, pFurnace, OpcUaId_Organizes);
UA_ASSERT(ret.isGood());
}
}
return ret;
}

getCountControllers() provides the number of configured controllers. This number is used to iterate over the controller list and to create the controller objects based on the returned configuration.

getControllerConfig() returns the configuration for a controller based on the passed index. It returns the controller type, name, and address. The type of the controller indicates whether we have to create an air conditioner controller or a furnace controller. The name and the address are used to intialize the object to create.

Step 7: Extending NmBuildingAutomation::readValues() to access State Variables

Since afterStartUp() has been completed we need to complete the readValues() implementation by handling also the read access to the State Variables. In order to achieve this, we have to replace

else
{
arrDataValues[i].setStatusCode(OpcUa_BadNotImplemented);
}

with

else
{
// Read of a state variable
// We need to get the state of the controller
BaCommunicationInterface::ControllerState state;
// Get the data for the controller from the communication interface
status = m_pCommIf->getControllerState(
pUserData->deviceAddress(),
state);
if ( status.isGood() )
{
// Set value
vTemp.setUInt32(state);
arrDataValues[i].setValue(vTemp, OpcUa_True, OpcUa_False);
}
else
{
// Set Error
arrDataValues[i].setStatusCode(status.statusCode());
}
}

The read value for the state can be received via BaCommunicationInterface::getControllerState() which takes a controller index (like getControllerData() and returns the state of the controller.

Step 8: Implementing NmBuildingAutomation::writeValues()

We still have to implement NmBuildingAutomation::writeValues() in order to finish this lesson.

UaStatus NmBuildingAutomation::writeValues(const UaVariableArray &arrUaVariables, const PDataValueArray &arrpDataValues, UaStatusCodeArray &arrStatusCodes)
{
UaStatus ret;
OpcUa_UInt32 i;
OpcUa_UInt32 count = arrUaVariables.length();
// Create result array
arrStatusCodes.create(count);
for ( i=0; i<count; i++ )
{
// Cast UaVariable to BaControllerVariable
UaVariable* pVariable = arrUaVariables[i];
if ( pVariable )
{
BaUserData* pUserData = (BaUserData*)pVariable->getUserData();
if ( pUserData )
{
if ( pUserData->isState() == OpcUa_False )
{
UaVariant vTemp(arrpDataValues[i]->Value);
UaStatusCode status;
OpcUa_Double value;
status = vTemp.toDouble(value);
if ( status.isGood() )
{
// Get the data for the controller from the communication interface
status = m_pCommIf->setControllerData(
pUserData->deviceAddress(),
pUserData->variableOffset(),
value);
}
arrStatusCodes[i] = status.statusCode();
}
else
{
// State variable can not be written
arrStatusCodes[i] = OpcUa_BadNotWritable;
}
}
else
{
arrStatusCodes[i] = OpcUa_BadInternalError;
}
}
else
{
arrStatusCodes[i] = OpcUa_BadInternalError;
}
}
return ret;
}

writeValues() provides an array of UaVariable interface pointers used to indicate which variables should be written. The second parameter is an array of OpcUa_DataValue pointers containing the values to be written. This method returns an array of OpcUa_StatusCode values indicating the success for every requested variable.

After creating the output array for the write results, the requested variables are processed in a loop.

For every variable the user data is requested by using the method UaNode::getUserData(). The returned user data object is casted to BaUserData. The user data is then used to detect if the state or one of the data variables is requested.
If the state is requested an error OpcUa_BadNotWritable is returned.
If a data value is requested, the method setControllerData of the communication interface is called. The device address and the offset stored in the user data are used to set the new value. The returned status is set to the corresponding out parameter array element.