How to get the value of a node with OPC UA and C#?
Asked Answered
G

3

5

I have a panel Siemens TP1200 Comfort that I have configure as OPC AU server. This panel has some tags (nodes) from which I would like to get the value from a C# application.

I have read the examples of the OPC UA github project: https://github.com/OPCFoundation/UA-.NETStandard.

I am able to connect to the panel and get the root, but if I debug and I check the structure of the root, I don't see any property for the value neither a collection of childs nodes, so I don't know how to find a node by its name.

Is there a method something like GetNodeVale(NodeName);

I don't show any code because I am really lost with OPC, it is my first attempt to implement a simple client in which I want to read that of a node (a tag) but I am not able to do it.

Thanks.

Glyptograph answered 5/12, 2022 at 18:40 Comment(3)
Have you tried to browse the HMI with UAExpert? (unified-automation.com/products/development-tools/uaexpert.html)Romish
Yes, with UAExpert I can browse through all the tags.Favus
Have you tried another OPC UA Client? in my opinion this github.com/convertersystems/opc-ua-client simple that native OPC Foundation stack.Ultramicroscope
M
7

To answer your question on how to get a node I am going to use the OPCua fx library: https://docs.traeger.de/en/software/sdk/opc-ua/net/client.development.guide
(below is a version with OPC foundation)

It has very good documentation and is easy to understand.

First of all install OPCua fx using nuget.

Next you need a few things.

  1. is the server adress found in TIA portal,

server adress

  1. The values you want to read. For this make a server interface in the OPC ua commucation tab and drag the values you want in there.

server interface

  1. Get the node IDs you want te read. For this I am using the Softing OPC ua Client: https://industrial.softing.com/products/opc-ua-and-opc-classic-sdks/opc-ua-demo-client.html Connect to your server using the adress from before. Go to the server interface you made earlier and to the value you want to read and click on it. Here you find the node id you need.

Nodeadress

After this. Connect with the adress from before:

string opcUrl = "opc.tcp://192.168.54.200:4840/";
var client = new OpcClient(opcUrl);
client.Connect();

In the documentation is also examples with username, password and certificates.

After you can connect to you OPCua server you can start reading nodes:

var node = client.ReadNode("ns=4;i=3");

Ns means namespace and I believe that the I stands for id. This is how you can read a node. It is also possible to put a subscription on the node. Which is also explained in the documentation.

After this you can write them:

Console.WriteLine("node" + node.ToString());

Good Luck!

EDIT: There is also a good tutorial from a very lovely guy named Hans: https://www.youtube.com/watch?v=KCW23eq4auw

EDIT2: Since most of you not want to spend 900euros for a licence (including me). I made another version for OPC foundation: https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Client/

First of all I have to give credits to: https://github.com/mdjglover/OPC-UA-READER/blob/main/OPC%20UA%20Reader/OPCUAClient.cs

Since this is almost impossible to figure out how this works but I found this repo that explains it very good!

If you use the explanation from the first version for the values you can include them in this code:

// Most basic configuration setup required to create session
        ApplicationConfiguration configuration = new ApplicationConfiguration();
        ClientConfiguration clientConfiguration = new ClientConfiguration();
        configuration.ClientConfiguration = clientConfiguration;

        // Create an endpoint to connect to
        string serverURL = "opc.tcp://192.168.54.200:4840/";

        try
        {
            // As the server instance I'm connecting to isn't using security, I've passed false as the second argument here.
            EndpointDescription endpointDescription = CoreClientUtils.SelectEndpoint(serverURL, false);
            EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(configuration);
            ConfiguredEndpoint endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration);

            // Session options
            // Sets whether or not the discovery endpoint is used to update the endpoint description before connecting.
            bool updateBeforeConnect = false;

            // Sets whether or not the domain in the certificate must match the endpoint used
            bool checkDomain = false;

            // The name to assign to the session
            string sessionName = configuration.ApplicationName;

            // The session's timeout interval
            uint sessionTimeout = 60000;

            // The identity of the user attempting to connect. This can be anonymous as is used here,
            // or can be specified by a variety of means, including username and password, certificate,
            // or token.
            UserIdentity user = new UserIdentity();

            // List of preferred locales
            List<string> preferredLocales = null;

            // Create the session
            Session session = Session.Create(
                configuration,
                endpoint,
                updateBeforeConnect,
                checkDomain,
                sessionName,
                sessionTimeout,
                user,
                preferredLocales
            ).Result;

            NodeId nodeId = new NodeId("ns=4;i=3");
            var value = session.ReadValue(nodeId);
            Console.WriteLine(value);
        }
        catch
        {
            return;
        }

This code is mostly made by someone

Marjie answered 21/2, 2023 at 8:22 Comment(1)
i means int, not IdCher
D
5

If you are using the OPCFoundation.Netstandard.OPC.Ua SDK. I can provide you some examples, since i just had to take a deep dive into this specific topic.

It is quite a bit complicated at first, since there isn't really any form of full documentation.

Since the answer above me explained setting up the session pretty well already, i wanted to supply some more examples, that worked for me.

When getting any NodeId you need to pass in the symbolicsNamespace Id. Since i'm using a Siemens PLC in the background, i know that this namespace is always called "SYM:". Once you know the name of your node, you could use a method like this one:

public Task<ushort> GetSymbolicsNameSpaceId()
    {
        _session.FetchNamespaceTables();
        var namespaces = _session.NamespaceUris.ToArray();

        ushort index = (ushort)Array.IndexOf(namespaces, "SYM:");
        return Task.FromResult(index);

    }

Firstly, if you want to check the current server status before sending or reading anything, you can do that, by getting the Variables.Server_ServerStatus NodeId and pass it into a regular _session.ReadValue() to receive the corresponding DataValue.

 public Task<ServerStatusDataType> GetServerStatus()
    {
        // Get the current DataValue object for the ServerStatus node
        NodeId nodeId = new NodeId(Variables.Server_ServerStatus);
        DataValue dataValue = _session.ReadValue(nodeId);

        // Unpack the ExtensionObject that the DataValue contains, then return ServerStatusDataType object
        // that represents the current server status
        ExtensionObject extensionObject = (ExtensionObject)dataValue.Value;
        ServerStatusDataType serverStatus = (ServerStatusDataType)extensionObject.Body;

        return Task.FromResult(serverStatus);
    }

To get a value from a specific OPC Server variable, you need to get the corresponding NodeId first, which can be done by simply calling the NodeId constructor with the NodeIdentifier, VariableName and the symbolics namespaceId:

public Task<DataValue>? GetValue(string nodeName, string varName, ushort namespaceIndex)
    {
        // To read a value, you require the node's ID. This can be either its unique integer ID, or a string
        // identifier along with the namespace which that identifier belongs to. Integer IDs are most useful
        // for acquiring nodes defined in the OPC UA standard, such as the ServerStatus node. The namespace
        // of a tag may differ depending on the OPC server being used, with KEPServer having a tag namespace
        // of 2. The only namespace that is guaranteed to remain the same is namespace 0, which contains the
        // nodes defined in the OPC UA standard.

        NodeId nodeId = new NodeId($"{nodeName}.{varName}", namespaceIndex);

        try
        {
            return Task.FromResult(_session.ReadValue(nodeId));
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
            return null;
        }
    }

Writing a value was a bit more of a challenge to understand, but i'm gonna try to explain it a bit further.

Since the PLC behind the OPC Server has it's own datatypes which do not directly correspond to our C# datatypes, we have to cast the value into the correct corresponding type, here is my full implementation of writing a variable:

// Get type of variable in OPC Server which should be written and cast the value before actually writing it
    public Task<bool> WriteValue(string nodeName, string varName, ushort namespaceIndex, string value)
    {
        try
        {
            NodeId nodeId = new NodeId($"{nodeName}.{varName}", namespaceIndex);

            // Read the node you want to write to
            var nodeToWrIteTo = _session.ReadValue(nodeId);

            // Get type of the specific variable you want to write 
            BuiltInType type = nodeToWrIteTo.WrappedValue.TypeInfo.BuiltInType;

            // Get the corresponding C# datatype
            Type csType = Type.GetType($"System.{type}");

            // Cast the value
            var castedValue = Convert.ChangeType(value, csType);

            // Create a WriteValue object with the new value
            var writeValue = new WriteValue
            {
                NodeId = nodeId,
                AttributeId = Attributes.Value,
                Value = new DataValue(new Variant(castedValue))
            };

            // Write the new value to the node
            _session.Write(null, new WriteValueCollection { writeValue }, out StatusCodeCollection statusCodeCollection, out DiagnosticInfoCollection diagnosticInfo);

            // Check the results to make sure the write succeeded
            if (statusCodeCollection[0].Code != Opc.Ua.StatusCodes.Good)
            {
                return Task.FromResult(false);
            }

            return Task.FromResult(true);
        }
        catch (Exception)
        {
            return Task.FromResult(false);
        }            
    }

Good Luck!

P.S.: This is still a poc on my end, so do not rely on my errorhandling.

Dianthus answered 23/3, 2023 at 9:44 Comment(2)
Does GetSymbolicsNameSpaceId provide all identifiers in a nameplace? Like if I have 6 nodes I get 6 nodeIds?Marjie
Yes, it provides you with an Array of namespaces. As for me, i get the result shown in this screenshot --> imgur.com/a/IcK8dJYDianthus
S
2

There is the sample repos UA-.NETStandard-Samples. Maybe the examples help you?

Edit: I also found an better example for the UA-.NETStandard here my be this helps also. Also the workflow described in the other answer is very good and should more or less the same.

Sokol answered 19/12, 2022 at 14:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.