Skip to content

How-to guide

Manage nodes

Create nodes in the address space of the server, control their visibility, and update them at runtime.

This content was machine translated and may contain errors. Read in German

How-to guide

Manage nodes ​

Create nodes ​

Types usedOpcServerOpcNodeOpcNodeIdOpcNodeManagerOpcAddressSpaceOpcFolderNodeOpcObjectNodeOpcVariableNodeOpcDataVariableNodeOpcMethodNodeOpcFileNode

An OpcNode defines an element of the address space of the server. This can be a logical folder (OpcFolderNode), a variable (OpcVariableNode), a method (OpcMethodNode), a file (OpcFileNode), and much more. An OpcNodeId uniquely identifies an OpcNode. It consists of a value, the underlying ID, and the index of the namespace to which the node belongs. The value is, for example, a text or a number. A URI (Uniform Resource Identifier) defines the namespace. The node managers of the server determine which namespaces are available.

Each node manager defines at least one namespace. The server uses these namespaces to classify the nodes of a node manager. This way, you can assign each node to a specific node manager. The default namespace of a node manager applies whenever a node does not get another namespace. The nodes that a node manager defines are also called the nodes in the address space of the node manager.

At startup, the server asks its node managers to create their address space, that is, their static nodes. While the server runs, you can add further dynamic nodes to the address space of a node manager and remove them again. You can also create an address space that always stays static without a node manager of your own. To do this, pass the static nodes for the http://{host}/{path}/nodes/ namespace directly to the server. The port is not part of the namespace. Instead of the nodes of the static address space, you can also define custom node managers.

Create a custom address space with a root node for the default namespace http://{host}/{path}/nodes/:

csharp
var machineNode = new OpcFolderNode("Machine");
var machineIsRunningNode = new OpcDataVariableNode<bool>(machineNode, "IsRunning");
 
// Note: An enumerable of nodes can be also passed.
var server = new OpcServer("opc.tcp://localhost:4840/", machineNode);
vbnet
Dim machineNode As New OpcFolderNode("Machine")
Dim machineIsRunningNode = New OpcDataVariableNode(Of Boolean)(machineNode, "IsRunning")
 
'Note: An enumerable of nodes can be also passed.
Dim server = New OpcServer("opc.tcp://localhost:4840/", machineNode)

Create an address space through a method: Alternatively, pass the server a method that creates the address space at startup. The method receives an instance of OpcAddressSpace. Through its AddNode method, it adds the nodes below the ObjectsFolder or another parent node:

csharp
var server = new OpcServer("opc.tcp://localhost:4840/", addressSpace => {
    var machineNode = new OpcFolderNode("Machine");
    var isRunningNode = new OpcDataVariableNode<bool>(machineNode, "IsRunning");
 
    // Adds the node beneath the Objects folder (the root of all server nodes).
    addressSpace.AddNode(machineNode);
});
vbnet
Dim server = New OpcServer("opc.tcp://localhost:4840/", Sub(addressSpace)
    Dim machineNode = New OpcFolderNode("Machine")
    Dim isRunningNode = New OpcDataVariableNode(Of Boolean)(machineNode, "IsRunning")

    'Adds the node beneath the Objects folder (the root of all server nodes).
    addressSpace.AddNode(machineNode)
End Sub)

Build an address space from a configuration: Because the server runs the method at startup, the method can also create the nodes from a configuration, for example from a JSON file, a CSV file, or a database. You do not need a node manager class of your own for this. The following example creates a variable node of the matching type for each entry of a list:

csharp
// Name and data type of each variable, e.g. read from a configuration file.
var tags = new Dictionary<string, string> {
    { "Temperature", "double" },
    { "IsRunning", "bool" },
    { "JobName", "string" }
};
 
var server = new OpcServer("opc.tcp://localhost:4840/", addressSpace => {
    var machine = new OpcFolderNode("Machine");
 
    foreach (var tag in tags) {
        switch (tag.Value) {
            case "double":
                new OpcDataVariableNode<double>(machine, tag.Key);
                break;
            case "bool":
                new OpcDataVariableNode<bool>(machine, tag.Key);
                break;
            default:
                new OpcDataVariableNode<string>(machine, tag.Key);
                break;
        }
    }
 
    addressSpace.AddNode(machine);
});
vbnet
'Name and data type of each variable, e.g. read from a configuration file.
Dim tags = New Dictionary(Of String, String) From { _
    {"Temperature", "double"}, _
    {"IsRunning", "bool"}, _
    {"JobName", "string"} _
}

Dim server = New OpcServer("opc.tcp://localhost:4840/", Sub(addressSpace)
    Dim machine = New OpcFolderNode("Machine")

    For Each tag In tags
        Select Case tag.Value
            Case "double"
                Dim node = New OpcDataVariableNode(Of Double)(machine, tag.Key)
            Case "bool"
                Dim node = New OpcDataVariableNode(Of Boolean)(machine, tag.Key)
            Case Else
                Dim node = New OpcDataVariableNode(Of String)(machine, tag.Key)
        End Select
    Next

    addressSpace.AddNode(machine)
End Sub)

If the data types should also come from the configuration, the Data types at runtime page describes the procedure.

Define a custom node manager:

csharp
public class MyNodeManager : OpcNodeManager
{
    public MyNodeManager()
        : base("http://mynamespace/")
    {
    }
}
vbnet
Public Class MyNodeManager
    Inherits OpcNodeManager

    Public Sub New()
        MyBase.New("http://mynamespace/")
    End Sub
End Class

Create a custom address space with a root node through a custom node manager:

csharp
protected override IEnumerable<IOpcNode> CreateNodes(OpcNodeReferenceCollection references)
{
    // Define custom root node.
    var machineNode = new OpcFolderNode(new OpcName("Machine", this.DefaultNamespaceIndex));
 
    // Add custom root node to the Objects-Folder (the root of all server nodes):
    references.Add(machineNode, OpcObjectTypes.ObjectsFolder);
 
    // Add custom sub node beneath of the custom root node:
    var isMachineRunningNode = new OpcDataVariableNode<bool>(machineNode, "IsRunning");
 
    // Return each custom root node using yield return.
    yield return machineNode;
}
vbnet
Protected Overrides Iterator Function CreateNodes(ByVal references As OpcNodeReferenceCollection) As IEnumerable(Of IOpcNode)
    'Define custom root node.
    Dim machineNode = New OpcFolderNode(New OpcName("Machine", Me.DefaultNamespaceIndex))

    'Add custom root node to the Objects-Folder (the root of all server nodes):
    references.Add(machineNode, OpcObjectTypes.ObjectsFolder)

    'Add custom sub node beneath of the custom root node:
    Dim isMachineRunningNode = New OpcDataVariableNode(Of Boolean)(machineNode, "IsRunning")

    'Return each custom root node using yield return.
    Yield machineNode
End Function

Make a custom node manager known to the server:

csharp
// Note: An enumerable of node managers can be also passed.
var server = new OpcServer("opc.tcp://localhost:4840/", new MyNodeManager());
vbnet
'Note: An enumerable of node managers can be also passed.
Dim server = New OpcServer("opc.tcp://localhost:4840/", New MyNodeManager())

Add and remove nodes at runtime: After the server has started, add further nodes through the AddNode method of the node manager and remove them again through its RemoveNode method. The node manager adds a new node below its parent node. The parent node must already be part of the address space:

csharp
// The parent node is already part of the address space of the node manager.
var jobNode = new OpcDataVariableNode<string>(machineNode, "Job", value: "JOB0815");
nodeManager.AddNode(jobNode);
 
// Removes the node from the address space.
nodeManager.RemoveNode(jobNode);
vbnet
'The parent node is already part of the address space of the node manager.
Dim jobNode = New OpcDataVariableNode(Of String)(machineNode, "Job", value:="JOB0815")
nodeManager.AddNode(jobNode)

'Removes the node from the address space.
nodeManager.RemoveNode(jobNode)

Restrict the visibility of nodes ​

Types usedOpcServerIOpcNodeOpcNodeIdOpcNodeManagerOpcObjectNodeOpcDataVariableNode

Not all nodes always need to be visible to all users of the server. In your own node manager, you can restrict the visibility of the nodes by any criteria. Often, restricting by user identity and a specific node is enough. The following simple tree illustrates this:

csharp
protected override IEnumerable<IOpcNode> CreateNodes(OpcNodeReferenceCollection references)
{
    var machine = new OpcObjectNode(
            "Machine",
            new OpcDataVariableNode<int>("Speed", value: 123),
            new OpcDataVariableNode<string>("Job", value: "JOB0815"));

    references.Add(machine, OpcObjectTypes.ObjectsFolder);
    yield return machine;
}
vbnet
Protected Overrides Iterator Function CreateNodes(ByVal references As OpcNodeReferenceCollection) As IEnumerable(Of IOpcNode)
    Dim machine = New OpcObjectNode( _
            "Machine", _
            New OpcDataVariableNode(Of Integer)("Speed", value:=123), _
            New OpcDataVariableNode(Of String)("Job", value:="JOB0815"))

    references.Add(machine, OpcObjectTypes.ObjectsFolder)
    Yield machine
End Function

If a user may access all nodes except the "Speed" node, the implementation of the IsNodeAccessible method can look like this. Call the base implementation for all other nodes. Among other things, it hides variables whose AccessLevel or UserAccessLevel has the value None:

csharp
protected override bool IsNodeAccessible(OpcContext context, OpcNodeId viewId, IOpcNodeInfo node)
{
    if (context.Identity.DisplayName == "a")
        return true;

    if (context.Identity.DisplayName == "b" && node.Name.Value == "Speed")
        return false;

    return base.IsNodeAccessible(context, viewId, node);
}
vbnet
Protected Overrides Function IsNodeAccessible(ByVal context As OpcContext, ByVal viewId As OpcNodeId, ByVal node As IOpcNodeInfo) As Boolean
    If context.Identity.DisplayName = "a" Then
        Return True
    End If
 
    If context.Identity.DisplayName = "b" AndAlso node.Name.Value = "Speed" Then
        Return False
    End If
 
    Return MyBase.IsNodeAccessible(context, viewId, node)
End Function

Update nodes ​

Types usedOpcDataVariableNodeOpcStatusCodeOpcContext

While the server runs, you often have to update nodes to match the underlying system. Simple object, folder, or method nodes change rarely, while variable nodes change regularly. Depending on the information available, you store or update a timestamp and the quality of the value in the variable node in addition to the value itself. The following example outlines how this works:

csharp
speedNode.Status.Update(OpcStatusCode.Good);
speedNode.Timestamp = DateTime.UtcNow;
speedNode.Value = 1200;
 
// Notifies subscribed clients about the changes.
speedNode.ApplyChanges(server.SystemContext);
vbnet
speedNode.Status.Update(OpcStatusCode.Good)
speedNode.Timestamp = DateTime.UtcNow
speedNode.Value = 1200

'Notifies subscribed clients about the changes.
speedNode.ApplyChanges(server.SystemContext)

Only when you call ApplyChanges does the server notify client applications about the change of the node, provided they have set up an active subscription for the node and its Value attribute. Outside a node manager, the SystemContext property of the server serves as the context; inside a node manager, its own SystemContext property does. The ApplyChanges(context, recursive: true) overload also applies the changes of all child nodes.

Define namespaces ​

Types usedOpcServerOpcNamespace

At startup, the server assigns the namespace indexes in the order in which its node managers register their namespaces. Clients that address nodes through a fixed NamespaceIndex (for example configured SCADA or PLC clients) therefore depend on a fixed order. With RegisterNamespace or RegisterNamespaces, you register your own namespaces in a fixed order before startup. The server does not add an already registered namespace again. The methods return instances of OpcNamespace whose Index property contains the assigned index:

csharp
using (var server = new OpcServer("opc.tcp://localhost:4840/")) {
    OpcNamespace[] namespaces = server.RegisterNamespaces(
            "http://example.com/machine",
            "http://example.com/plant");
 
    Console.WriteLine("Machine: ns={0}", namespaces[0].Index);
    Console.WriteLine("Plant: ns={0}", namespaces[1].Index);
 
    server.Start();
}
vbnet
Using server = New OpcServer("opc.tcp://localhost:4840/")
    Dim namespaces As OpcNamespace() = server.RegisterNamespaces( _
            "http://example.com/machine", _
            "http://example.com/plant")

    Console.WriteLine("Machine: ns={0}", namespaces(0).Index)
    Console.WriteLine("Plant: ns={0}", namespaces(1).Index)

    server.Start()
End Using

By default, all nodes are in the custom namespace with index two. The namespace with index one is the namespace of the server application: the SDK enters the OpcServer.ApplicationUri property there, provided you do not register any namespaces before you start the server. To manage nodes in the namespace with index one, your own OpcNodeManager must use the value of OpcServer.ApplicationUri as its NamespaceUri.

Multilingual texts ​

Types usedOpcServerOpcServerGlobalizationOpcText

You can provide the display names, descriptions, and other texts of a node (OpcText) in several languages without creating separate nodes for each language. To do this, the text gets a key in addition to the default text and its language. Through the Globalization property of the server, you store the translations per language for each key (AddResource for one entry, AddResources for several). Specify a specific culture as the language (for example de-DE, not just de):

csharp
var temperature = new OpcDataVariableNode<double>("Temperature");
 
temperature.DisplayName = new OpcText(
        "Temperature",
        cultureName: "en-US",
        key: "Temperature.DisplayName");
 
temperature.Description = new OpcText(
        "Temperature of the machine",
        cultureName: "en-US",
        key: "Temperature.Description");
 
using (var server = new OpcServer("opc.tcp://localhost:4840/", temperature)) {
    server.Globalization.AddResource(
            cultureName: "de-DE",
            key: "Temperature.DisplayName",
            value: "Temperatur");
 
    server.Globalization.AddResource(
            cultureName: "de-DE",
            key: "Temperature.Description",
            value: "Temperatur der Maschine");
 
    server.Start();
}
vbnet
Dim temperature = New OpcDataVariableNode(Of Double)("Temperature")

temperature.DisplayName = New OpcText( _
        "Temperature", _
        cultureName:="en-US", _
        key:="Temperature.DisplayName")

temperature.Description = New OpcText( _
        "Temperature of the machine", _
        cultureName:="en-US", _
        key:="Temperature.Description")

Using server = New OpcServer("opc.tcp://localhost:4840/", temperature)
    server.Globalization.AddResource( _
            cultureName:="de-DE", _
            key:="Temperature.DisplayName", _
            value:="Temperatur")

    server.Globalization.AddResource( _
            cultureName:="de-DE", _
            key:="Temperature.Description", _
            value:="Temperatur der Maschine")

    server.Start()
End Using

When a client reads such a text, the server translates it based on the languages that the client prefers when it creates the session. In the SDK, the client sets these languages through OpcClient.Locales. The Configure the application and session section in the client chapter describes this. If no translation exists for any of the preferred languages, the server returns the default text.

Report changes to the address space ​

Types usedOpcAutomatismOpcNodeOpcNodeManager

If the address space changes at runtime, for example because a machine or a module is added, the server can notify clients through model change events (GeneralModelChangeEvent). By default, the server does not generate these events. For all nodes outside the namespaces of the OPC Foundation, you turn them on through the static OpcAutomatism.RaiseModelChangeEvents property:

csharp
OpcAutomatism.RaiseModelChangeEvents = true;
vbnet
OpcAutomatism.RaiseModelChangeEvents = True

After that, the server reports, for example, a node added at runtime to the clients that have subscribed to the events of the server:

csharp
var machine = new OpcObjectNode(machines, "Machine3");
var temperature = new OpcDataVariableNode<double>(machine, "Temperature");
 
manager.AddNode(server.SystemContext, machine);
vbnet
Dim machine = New OpcObjectNode(machines, "Machine3")
Dim temperature = New OpcDataVariableNode(Of Double)(machine, "Temperature")

manager.AddNode(server.SystemContext, machine)

If you change many nodes at once, temporarily suspend the recording of changes for a node with SuspendModelChangeEvents and resume it with ResumeModelChangeEvents. With recursive: true, this also applies to all of its child nodes:

csharp
machines.SuspendModelChangeEvents(recursive: true);
 
foreach (var name in new string[] { "Machine4", "Machine5", "Machine6" })
    manager.AddNode(server.SystemContext, new OpcObjectNode(machines, name));
 
machines.ResumeModelChangeEvents(recursive: true);
vbnet
machines.SuspendModelChangeEvents(recursive:=True)

For Each name In New String() {"Machine4", "Machine5", "Machine6"}
    manager.AddNode(server.SystemContext, New OpcObjectNode(machines, name))
Next

machines.ResumeModelChangeEvents(recursive:=True)