convertersystems / opc-ua-client

Visualize and control your enterprise using OPC Unified Architecture (OPC UA) and Visual Studio.
MIT License
403 stars 119 forks source link

Missing notification #217

Closed LouisBaranger closed 1 year ago

LouisBaranger commented 2 years ago

Hello,

I'm working with your client with a Siemens CPU 1500 server.

It works well except sometimes, quite randomly, I don't get the notification from some changes on monitored items. I have watched on PLC, the item has changed of value. I don't have any exceptions, neither notification for this new value. I used Wireshark to see what append on Tcp and I don't see any dataChange for this item.

If I manualy change the value few seconds later, I'm getting a notification...

Some details about my sub and items. Subscriptions : Publish interval : 500, Lifetime count : 600, MaxKeepAlive : 60

MonitoredItems : Sampling interval : 500 Queue Size : 10 DiscardOldest : True

This problem is blocking my program because I wait the end of a process with a subscription on PLC but sometimes, I never get it.

Thanks,

PS : I already have watched the bkohlmaier issue, I think this is the same problem.

awcullen commented 2 years ago

Hi Louis,

I would like to learn more about your program, for example: subscription count, items per subscription. You mentioned you have experience with Wireshark. Could you share some details about the returned values from CreateSubscriptionResponse and CreateMonitoredItemsResponse?

Also, I am curious if you can share the PublishResponse. If the server sends a PublishResponse and the client doesn't acknowledge it. There will be a growing list of AvailableSequenceNumbers.

If you are on a tight schedule with your project, maybe you can program a periodic read of this Item and determine the end of your process that way.

I do hope to find the source of this bug since both you and bkohlmaier have reported it.

Andrew

LouisBaranger commented 2 years ago

Thanks for your interest,

It's a variable application, in this case, I use 1 session, 1 sub, 68 monitored items, it can be in the worse case around 200 monitored items. I tested to read all this items (200), it takes me around 1200ms...

I have checked the AvailableSequenceNumbers in publish around the bug, the array size is always 0.

Here you can see some capture from Wireshark. GitHub doesn't support .pcapng file, if you give me an email I can send you all the detail.

Channel response ChannelResponse

Session Response SessionResponse

Subscription Response SubscriptionResponse

MonitoredItem Response MonitoredItemResponse

Publish Request PublishRequest

Publish Response PublishResponse

quinmars commented 2 years ago

We also had problems with "missing" notifications of the Siemens OPC UA server - btw unrelated to this library. I do not know if it is the same scenario as in your case, but I will explain it shortly. Our buttons usually set in the Click event the command bit in the PLC to true. The PLC will reset it to false. Unfortunately, we never (or not always) got a notification that the PLC changed it to false. The reason can be found in the way the notification system works. 1) The OPC UA server scans the values of all monitored items. 2) It waits some time. 3) It collects the values of the items again. 4) It sends messages for all values that have changed. But in our case the command bit was maybe one PLC cycle true, but in most cases in step 1) and in step 4) false, hence no notification.

If you write and read the same value you have to poll or you can split the communication in a write (request) bit and a read (acknowledge) bit.

awcullen commented 2 years ago

Thanks for all the good info.

I setup a test using PLCSim with a tag that increments every 0.1 s. The ua server settings are:

image

I wrote a console app to log the data change notifications. The item is set to sample at 1s and the subscription is set to publish at the same rate. I also tested sampling at 1s and publishing at 2s. In this case, I get PublishResponse every 2s, but the response has two notifications in it.

An alternative to subscription is to read the list of nodes. You may notice that reading can be slower. To speed up reading you pass the list of nodes to the RegisterNodes method. The response gives you a list of nodeID that use to read instead of the original. [Let me know if you would like an example]

Info about RegisterNodes is here: https://reference.opcfoundation.org/v104/Core/docs/Part4/5.8.5/

awcullen commented 2 years ago

I found an example

        private static async Task TestAsync2()
        {

            // var discoveryUrl = "opc.tcp://localhost:48010"; // UaCppServer - see  http://www.unified-automation.com/
            var discoveryUrl = $"opc.tcp://192.168.1.94:4840"; // Siemens

            Console.WriteLine("Step 1 - Describe this app.");
            var appDescription = new ApplicationDescription()
            {
                ApplicationName = "MyHomework",
                ApplicationUri = $"urn:{System.Net.Dns.GetHostName()}:MyHomework",
                ApplicationType = ApplicationType.Client,
            };

            Console.WriteLine("Step 2 - Create a certificate store.");
            var certificateStore = new DirectoryStore(
                Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Workstation.ConsoleApp", "pki"));

            Console.WriteLine("Step 3 - Create a session with your server.");
            var channel = new UaTcpSessionChannel(
                appDescription,
                certificateStore,
                ShowSignInDialog,
                discoveryUrl);
            try
            {
                await channel.OpenAsync();

                Console.WriteLine($"  Opened channel with endpoint '{channel.RemoteEndpoint.EndpointUrl}'.");
                Console.WriteLine($"  SecurityPolicyUri: '{channel.RemoteEndpoint.SecurityPolicyUri}'.");
                Console.WriteLine($"  SecurityMode: '{channel.RemoteEndpoint.SecurityMode}'.");
                Console.WriteLine($"  UserIdentity: '{channel.UserIdentity}'.");

                Console.WriteLine("Press any key to continue...");
                Console.ReadKey(true);

                // Prepare array of nodes to read.
                var registerNodesRequest = new RegisterNodesRequest
                {
                    NodesToRegister = new[]
                    {
                        NodeId.Parse("ns=3;s=\"TestData\".\"TestVector\".\"X\""),
                    }
                };

                var registerNodesResponse = await channel.RegisterNodesAsync(registerNodesRequest).ConfigureAwait(false);

                // Read the nodes.
                var readRequest = new ReadRequest
                {
                    NodesToRead = registerNodesResponse.RegisteredNodeIds.Select(
                        id => new ReadValueId { NodeId = id, AttributeId = AttributeIds.Value })
                    .ToArray()
                };

                Console.WriteLine("Press any key to end the program...");

                while (!Console.KeyAvailable)
                {
                    // Read the nodes.
                    var readResponse = await channel.ReadAsync(readRequest).ConfigureAwait(false);

                    // Write the results.
                    for (int i = 0; i < readRequest.NodesToRead.Length; i++)
                    {
                        Console.WriteLine($"{readRequest.NodesToRead[i].NodeId}; value: {readResponse.Results[i]}");
                    }

                    await Task.Delay(1000);
                }
                await channel.CloseAsync();
            }
            catch (ServiceResultException ex)
            {
                if ((uint)ex.HResult == StatusCodes.BadSecurityChecksFailed)
                {
                    Console.WriteLine("Error connecting to endpoint. Did the server reject our certificate?");
                }

                await channel.AbortAsync();
                throw;
            }
        }
LouisBaranger commented 2 years ago

Thank you all,

I actually do a request (Set a bit at true) and waits for an answer (An other bit at true). So the request answer bit is always true until I write false on the request but I get no notification of this change.

I will try to read with RegisterNodes later, I hope I can read all my 200 variables in less than 500ms.