OPC-UA node browsing using Traeger SDK

OPC-UA is a modern protocol to unlock M2M communcation.

The OPC Unified Architecture (UA) is a platform independent service-oriented architecture that integrates all the functionality of the individual OPC Classic specifications into one extensible framework. Building on the success of OPC Classic, OPC UA was designed to enhance and surpass the capabilities of the OPC Classic specifications.

Its popularity is still growing in many markets and for multiple reasons!

Most importantly, from an IoT Developer view, the protocol supports devices to offer a secure communication layers and the exposed tags can be made human readable.

For example, this OPC-UA clients looks at the exposed tags of an OPC-UA server running on an Advantech Wise 710:

In this Prosys OPC-UA client, the tag values are shown:

The nodes are actually mapped Modbus values read from Wise 4012E.

As you can see, it exposes these six values. The potentio meter values shown is made available as a double.

I was asked to provide documentation like this, listing all exposed nodes together with the DataType.

Is it possible to browse nodes?

The short answer is yes. And I will demonstrate this with a code sample.

Note: for this demo, I use the Traeger OPC-UA libraries for C#. We use the client side code. Traeger also offers server-side code so you can build your own OPC-UA server. There is a commercial license model involved. Check thier website for up-to-date license related information. For this demonstration, it runs fine with the default Nuget libraries.

The Advantech Wise 710 exposes multiple fields (eg. user defined tags, calculated tags and IO tags). I’m only interested in the ‘child device’ nodes so I wish to filter the list:

I found this ‘browse’ example but it only showed me the node names, not the actual data types.

There are also plenty of examples on GitHub but it took me some time to get to the actual data type:

internal class Program
{
    private static OpcClient client;

    private static void Main(string[] args)
    {
        Console.WriteLine("Hello OPC-UA Discovery World!");

        client = new OpcClient("opc.tcp://192.168.1.82:4840/");

        client.Connect();

        var node = client.BrowseNode(OpcObjectTypes.ObjectsFolder);

        Browse(node);

        Console.WriteLine("Press a key to exit");
        Console.ReadKey();
    }

    private static void Browse(OpcNodeInfo node, int level = 0)
    {
        // ns=2;s=Wise4012E:Potentio01
        if (node.Attribute(OpcAttribute.DisplayName).Value.ToString().ToLower().Contains("wise"))
        {
            try
            {
                OpcNodeInfo opcNodeInfo = client.BrowseNode(node.NodeId);

                var dataType = "SUBNODE";

                var opcVariableNodeInfo = (opcNodeInfo as OpcVariableNodeInfo);

                if (opcVariableNodeInfo != null)
                {
                    // https://reference.opcfoundation.org/v104/PLCopen/v102/docs/9.2.1/
                    // datatypeid looks like 'i=1'; (Compare with number in 'OPC UA built-in data types' column)
                    var value = opcVariableNodeInfo.DataTypeId.ToString().Split('=')[1];

                    switch (value)
                    {
                        case "1":
                            dataType = "BOOLEAN";
                            break;

                        case "2":
                            dataType = "SBYTE";
                            break;

                        case "3":
                            dataType = "BYTE";
                            break;

                        case "4":
                            dataType = "INT16";
                            break;

                        case "5":
                            dataType = "UINT16";
                            break;

                        case "6":
                            dataType = "INT32";
                            break;

                        case "7":
                            dataType = "UINT32";
                            break;

                        case "8":
                            dataType = "INT64";
                            break;

                        case "9":
                            dataType = "UINT64";
                            break;

                        case "10":
                            dataType = "FLOAT";
                            break;

                        case "11":
                            dataType = "DOUBLE";
                            break;

                        case "12":
                            dataType = "STRING";
                            break;

                        case "13":
                            dataType = "DATETIME";
                            break;

                        default:
                            dataType = "TBD";
                            break;
                    }

                    dataType = $"{ dataType} ({ opcVariableNodeInfo.DataTypeId})";
                }

                Console.WriteLine($"{node.NodeId.ToString(OpcNodeIdFormat.Foundation)} - {dataType}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Exception: {ex.Message}");
            }
        }

        level++;

        foreach (var childNode in node.Children())
            Browse(childNode, level);
    }
}

If you want to test this code in a C# console app, add this Traeger Nuget package in the project file:

<PackageReference Include="Opc.UaFx.Advanced" Version="2.18.3" />

As you can see, I can access the data type number (like ‘i=7’). The trick is making use of that cast to ‘OpcVariableNodeInfo’.

Using the official OPC-UA documentation, I can now map this number to the actual OPC-UA data type:

Note: The row number is not the same as the actual data type number!

So, in the end, I was able to filter out all the tags containing the string ‘wise’:

Conclusion

Now I’m in control in documenting the OPC-UA connection just the way I like it.

The code seen in this demo is made available as GitHub Gist.

Advertentie