UA Bundle SDK .NET  2.2.1.258
 All Classes Namespaces Functions Variables Enumerations Enumerator Properties Events Groups Pages
Lesson 3: Connecting the nodes to real time data

Introduction

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 are 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 update the value of the Variable node if a data change arrives for this variable. All the read and data monitoring is already handled by the SDK.

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.

The following figure shows the example communication interface used for polling access to the simulated device data.

Figure 3.1 Communication Interface for Devices

Communication Interface for Devices

Configuration Information

GetBlocks
Number of available controllers and their configuration through BlockConfiguration and BlockProperty classes

Runtime Data

Read
Read block property
Write
Write block property
Start
Start controller
Stop
Stop controller

Integration of the Device Interface

In a first step the example device interface including the device simulation must be added to the project.

Add system as a member.

#region Private Fields
private UnderlyingSystem m_system; // Add this line
#endregion

Create system

public Lesson1NodeManager(ServerManager server) : base(server)
{
m_system = new UnderlyingSystem(); // Add this line
}

Initialize system

public override void Startup()
{
try
{
// New Code Begin
// initialize the underlying system.
m_system.Initialize();
// New Code End
Console.WriteLine("Starting Lesson1NodeManager.");
...

Creation of controller instances from Device Interface information

Replace hardcoded object generation added in the previous lesson with information about available controllers provided by the Device Interface.

// Create a Folder for Controllers
...
// Create controllers from configuration
foreach (BlockConfiguration block in m_system.GetBlocks())
{
// set type definition NodeId
NodeId typeDefinitionId = ObjectTypeIds.BaseObjectType;
if (block.Type == BlockType.AirConditioner)
{
typeDefinitionId = new NodeId(yourorganisation.BA.ObjectTypes.AirConditionerControllerType, TypeNamespaceIndex);
}
else if (block.Type == BlockType.Furnace)
{
typeDefinitionId = new NodeId(yourorganisation.BA.ObjectTypes.FurnaceControllerType, TypeNamespaceIndex);
}
// create object.
settings = new CreateObjectSettings()
{
ParentNodeId = new NodeId("Controllers", InstanceNamespaceIndex),
ReferenceTypeId = ReferenceTypeIds.Organizes,
RequestedNodeId = new NodeId(block.Name, InstanceNamespaceIndex),
BrowseName = new QualifiedName(block.Name, TypeNamespaceIndex),
TypeDefinitionId = typeDefinitionId
};
CreateObject(Server.DefaultRequestContext, settings);
}

Figure 3.2: Result in the server’s address space

Connect variable value attribute to real time data

Our lesson specific NodeManager is derived from BaseNodeManager class. The project specific information integration is done through overwriting methods of the base class. Startup is overwritten to create the address space. For polling based data integration we need to overwrite the methods Read and Write.

Figure 3.3:

The value attribute of a variable node can be provided in different ways based on the source of the value. The source can be the in memory node like for server configuration data or it could be in an external device.

Depending on the type of source and communication, different modes can be configured for the variable. For Var1 the mode NodeHandleType.Internal would be used. For Var2 the mode NodeHandleType.ExternalPush would be used. Var3 matches our example and mode NodeHandleType.ExternalPolled is used. The different modes are described in the overview for Data Access, Handle Types and the IIOManager.

Figure 3.4:

In the next steps we need to change variable value handling setting to NodeHandleType.ExternalPolled and we need to provide the address information in variable user data

In addition we need to implement Read and Write in the Lesson NodeManager. Read and Write are defined by BaseNodeManager and are overwritten in the Lesson NodeManager.

In the first step we define the data class for variable user data. The Address is used to address the controller. The Offset is used to address the variable inside the controller.

internal class Lesson1NodeManager : BaseNodeManager
{
public ushort InstanceNamespaceIndex { get; set; }
public ushort TypeNamespaceIndex { get; set; }
// New Code Begin
private class SystemAddress
{
public int Address;
public int Offset;
}
// New Code End
...

Update Variables after creation (together with object) Set handling to ExternalPolled Set user data through SystemAddress class Address settings provided from underlying system

// create object.
settings = new CreateObjectSettings()
{
ParentNodeId = new NodeId("Controllers", InstanceNamespaceIndex),
ReferenceTypeId = ReferenceTypeIds.Organizes,
RequestedNodeId = new NodeId(block.Name, InstanceNamespaceIndex),
BrowseName = new QualifiedName(block.Name, TypeNamespaceIndex),
TypeDefinitionId = typeDefinitionId
};
CreateObject(Server.DefaultRequestContext, settings);
// New Code Begin
foreach (BlockProperty property in block.Properties)
{
// the node was already created when the controller object was instantiated.
// this call links the node to the underlying system data.
VariableNode variable = SetVariableConfiguration(
new NodeId(block.Name, InstanceNamespaceIndex),
new QualifiedName(property.Name, TypeNamespaceIndex),
NodeHandleType.ExternalPolled,
new SystemAddress() { Address = block.Address, Offset = property.Offset });
}
// New Code End

Implement Read in Lesson NodeManager

protected override void Read(
RequestContext context,
TransactionHandle transaction,
IList<NodeAttributeOperationHandle> operationHandles,
IList<ReadValueId> settings)
{
for (int ii = 0; ii < operationHandles.Count; ii++)
{
// Initialize with bad status
DataValue dv = new DataValue(new StatusCode(StatusCodes.BadNodeIdUnknown));
// the data passed to CreateVariable is returned as the UserData in the handle.
SystemAddress address = operationHandles[ii].NodeHandle.UserData as SystemAddress;
if (address != null)
{
// read the data from the underlying system.
object value = m_system.Read(address.Address, address.Offset);
if (value != null)
{
dv = new DataValue(new Variant(value, null), DateTime.UtcNow);
}
}
// return the data to the caller.
((ReadCompleteEventHandler)transaction.Callback)(
operationHandles[ii],
transaction.CallbackData,
dv,
false);
}
}

Implement Write in Lesson NodeManager

protected override void Write(
RequestContext context,
TransactionHandle transaction,
IList<NodeAttributeOperationHandle> operationHandles,
IList<WriteValue> settings)
{
for (int ii = 0; ii < operationHandles.Count; ii++)
{
// initialize with bad status
StatusCode error = StatusCodes.BadNodeIdUnknown;
// the data passed to CreateVariable is returned as the UserData in the handle.
SystemAddress address = operationHandles[ii].NodeHandle.UserData as SystemAddress;
if (address != null)
{
error = StatusCodes.Good;
if (!m_system.Write(address.Address, address.Offset, settings[ii].Value.Value))
{
error = StatusCodes.BadUserAccessDenied;
}
}
// return the data to the caller.
((WriteCompleteEventHandler)transaction.Callback)(
operationHandles[ii],
transaction.CallbackData,
error,
false);
}
}

Status: Temperature gets value from Device

  • Drag&Drop variables of Controller to DA View
  • Variable Temperature gets values from Device
  • Value provided through Lesson01NodeManager::Read()

Figure 3.5

Testing implementation in UaExpert

Adding AnalogItemType Property Values

foreach (BlockProperty property in block.Properties)
{
// the node was already created when the controller object was instantiated.
// this call links the node to the underlying system data.
VariableNode variable = SetVariableConfiguration(
new NodeId(block.Name, InstanceNamespaceIndex),
new QualifiedName(property.Name, TypeNamespaceIndex),
NodeHandleType.ExternalPolled,
new SystemAddress() { Address = block.Address, Offset = property.Offset });
// New Code Begin
if (variable != null)
{
// in-memory nodes must be locked before updates.
// reads do not require locks for simple types and references.
// value reads require a lock.
lock (InMemoryNodeLock)
{
variable.AccessLevel = (property.Writeable) ? AccessLevels.CurrentReadOrWrite : AccessLevels.CurrentRead;
variable.UserAccessLevel = variable.AccessLevel;
}
if (property.Range != null)
{
SetVariableDefaultValue(
variable.NodeId,
new QualifiedName(BrowseNames.EURange),
new Variant(property.Range));
}
}
// New Code End
}