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
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;
#endregion
Create system
public Lesson1NodeManager(ServerManager server) : base(server)
{
m_system = new UnderlyingSystem();
}
Initialize system
public override void Startup()
{
try
{
m_system.Initialize();
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.
...
foreach (BlockConfiguration block in m_system.GetBlocks())
{
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);
}
settings = new CreateObjectSettings()
{
ParentNodeId =
new NodeId(
"Controllers", InstanceNamespaceIndex),
RequestedNodeId =
new NodeId(block.Name, InstanceNamespaceIndex),
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; }
private class SystemAddress
{
public int Address;
public int Offset;
}
...
Update Variables after creation (together with object) Set handling to ExternalPolled Set user data through SystemAddress class Address settings provided from underlying system
settings = new CreateObjectSettings()
{
ParentNodeId =
new NodeId(
"Controllers", InstanceNamespaceIndex),
RequestedNodeId =
new NodeId(block.Name, InstanceNamespaceIndex),
TypeDefinitionId = typeDefinitionId
};
CreateObject(
Server.DefaultRequestContext, settings);
foreach (BlockProperty property in block.Properties)
{
VariableNode variable = SetVariableConfiguration(
new NodeId(block.Name, InstanceNamespaceIndex),
NodeHandleType.ExternalPolled,
new SystemAddress() { Address = block.Address, Offset = property.Offset });
}
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++)
{
SystemAddress address = operationHandles[ii].NodeHandle.UserData as SystemAddress;
if (address != null)
{
object value = m_system.Read(address.Address, address.Offset);
if (value != null)
{
}
}
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++)
{
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;
}
}
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
Adding AnalogItemType Property Values
foreach (BlockProperty property in block.Properties)
{
VariableNode variable = SetVariableConfiguration(
new NodeId(block.Name, InstanceNamespaceIndex),
NodeHandleType.ExternalPolled,
new SystemAddress() { Address = block.Address, Offset = property.Offset });
if (variable != null)
{
lock (InMemoryNodeLock)
{
variable.AccessLevel = (property.Writeable) ? AccessLevels.CurrentReadOrWrite : AccessLevels.CurrentRead;
variable.UserAccessLevel = variable.AccessLevel;
}
if (property.Range != null)
{
SetVariableDefaultValue(
variable.NodeId,
}
}
}