Node IDs
Whenever you want to read or write in OPC UA, you need to specify a node you want to deal with. In PicoOPC, you use the NodeId type to identify the node. The NodeId contains a namespace index (an integer; in the NamespaceIndex property), and an identifier (in the Identifier property) that unambiguously distinguishes the node in its namespace.
OPC UA allows four different types of identifiers, and the OPC UA server implementor chooses which to use (there may be multiple identifier types used in the same server). PicoOPC uses the NodeIdType type to represent these identifier types, and you can obtain the type of the node ID by getting its NodeIdType property. The node ID types are:
- Numeric value.
- String value.
- Globally Unique Identifier (a GUID).
- Namespace specific format (represented as a byte string).
Node Namespace
In the OPC UA address space model, each node exists in its namespace, and the namespaces are unambiguously identified by namespace URIs (strings). In the OPC UA protocol "on the wire", however, node namespaces are identified by numbers (unsigned 16-bit integers). Each OPC UA server then has a so-called namespace array, a table that defines the correspondence between the namespace URIs and the namespace indexes. The use of namespace indexes, which are much shorter than the namespace URI strings, is important for communication efficiency.
You will see lots of code that hard-codes namespace indexes, or persists them and then reuses them in later communication. Except for the reserved OPC UA namespace with index 0 (which corresponds to namespace URI "http://opcfoundation.org/UA/"), this practice is generally wrong. The server is free to rearrange the namespace array (and thus mix up the indexes) e.g. with new versions, server restarts, and even between the different sessions. Relying on the actual value of namespace indexes is only possible if you have full knowledge and control over the OPC UA server you are connecting to, and can guarantee that the namespace index you are using will never change.
In all other situations, you should write your code so that the namespaces are known by their namespace URIs, and you first look up the namespace index for that URI in the namespace array. The namespace array can be obtained by reading a node with integer identifier 2255 in namespace with index 0. The example below illustrates the technique.
// This example shows how to look up the namespace index of a node with a given namespace URI.
using System;
using System.Collections.Generic;
using System.Linq;
using OpcLabs.BaseLib.Collections.Generic.Extensions;
using OpcLabs.PicoOpc.UA;
namespace ConsoleApp._Client
{
partial class Read
{
public static void SearchNamespaceArray()
{
// Instantiate the client object.
var client = new Client();
try
{
try
{
// Connect to the server.
Console.WriteLine();
Console.WriteLine("Connecting...");
client.Connect(new Uri("opc.tcp://opcua.demo-this.com:51210/UA/SampleServer"));
// Read the namespace array.
Console.WriteLine();
Console.WriteLine("Reading the namespace array...");
DataValue namespaceArrayDataValue = client.Read(TimeSpan.Zero, new[] { new ReadValueId(new NodeId(2255)) })[0];
if (namespaceArrayDataValue.StatusCode != 0)
{
Console.WriteLine($"*** Status not good: 0x{namespaceArrayDataValue.StatusCode:X8}");
return;
}
IEnumerable<string> namespaceArray = ((object[]) namespaceArrayDataValue.Variant.Value).Cast<string>();
// Search the namespace array.
Console.WriteLine();
Console.WriteLine("Searching the namespace array...");
int namespaceIndex = namespaceArray.IndexOf("http://test.org/UA/Data/");
if (namespaceIndex == -1)
{
Console.WriteLine("*** Namespace URI not found.");
return;
}
Console.WriteLine($"Namespace index: {namespaceIndex}");
// Read the node, using the namespace index found.
Console.WriteLine();
Console.WriteLine("Reading the node...");
var nodeId = new NodeId(10221, (ushort)namespaceIndex);
DataValue dataValue = client.Read(TimeSpan.Zero, new[] { new ReadValueId(nodeId) })[0];
// Display the result.
Console.WriteLine();
Console.WriteLine($"Status code: 0x{dataValue.StatusCode:X8}");
Console.WriteLine($"Server timestamp: {DateTime.FromFileTimeUtc(dataValue.ServerTimestamp)}");
Console.WriteLine($"Source timestamp: {DateTime.FromFileTimeUtc(dataValue.SourceTimestamp)}");
if (dataValue.StatusCode == 0)
{
Console.WriteLine($"Built-in type: {dataValue.Variant.BuiltInType}");
Console.WriteLine($"Is array: {dataValue.Variant.IsArray}");
Console.WriteLine($"Value: {dataValue.Variant.Value}");
}
}
finally
{
if (client.IsConnected)
{
// Disconnect from the server.
Console.WriteLine();
Console.WriteLine("Disconnecting...");
client.Disconnect();
}
}
}
catch (AggregateException aggregateException)
{
Console.WriteLine("*** Failure: {0}", aggregateException.GetBaseException().Message);
}
}
}
}