hylasoft-usa / h-opc

OPC client made simpler, for UA and DA
MIT License
300 stars 144 forks source link

DA - Subscription does not report data change of tags if there is a rapidly changing tag within its monitored tag list #43

Closed MuckersMate closed 7 years ago

MuckersMate commented 7 years ago

I have a situation where I have two tags, the latter is fast changing.

The client "sees" the change of the rapidly updating tag, but any change to the earlier added tag is not being broadcast (or sometimes only rarely) by the Subscription.

If I reverse the order in which the tags are added, changes in both tags are reported as expected.

Here is my code:

public T AddItem<T>(long subscriptionHandle, long clientHandle, string tagName, Action<T, T, T, bool, Action> callback)
    {
        //
        // Find node read from cache or create
        var node = FindNode(tagName);

        if (node != null)
        {

            //
            // Find subscription (group referenced by subscriptionHandle)
            var sub = FindSubscription(subscriptionHandle);

            try
            {
                if (sub == null)
                {
                    sub = AddSubscription<OpcDa.Subscription>(subscriptionHandle.ToString(CultureInfo.InvariantCulture), subscriptionHandle, 0);
                }
                Opc.Da.Item item = null;
                if (clientHandle < 0)
                {
                    if (sub.Items.Count() > 0)
                    {
                        item = sub.Items.FirstOrDefault(m => Practicon.Utilities.Strs.StringsEqual(m.ItemName, tagName));

                        if (item == null)
                        {
                            clientHandle = (long)sub.Items.Max(i => i.ClientHandle) + 1;
                        }
                        else
                        {
                            clientHandle = (long)item.ClientHandle;
                        }
                    }
                    else
                    {
                        clientHandle = 0;
                    }
                }
                else
                {
                    //
                    // Item already monitored?
                    item = sub.Items.FirstOrDefault(m => (long)m.ClientHandle == clientHandle);
                }
                try
                {
                    if (item == null)
                    {
                        item = new OpcDa.Item { ItemName = tagName, ClientHandle = clientHandle };
                        sub.AddItems(new[] { item });
                        // I have to start a new thread here because unsubscribing
                        // the subscription during a datachanged event causes a deadlock
                        Action unsubscribe = () => new Thread(o =>
                            {
                                try
                                {
                                    _server.CancelSubscription(sub);
                                }
                                catch (Exception ex)
                                {

                                }
                            }

                        ).Start();

                        sub.DataChanged += (handle, requestHandle, values) =>
                        {
                            Task.Factory.StartNew(() =>
                            {
                                T casted;

                                TryCastResult(values[0].Value, out casted);

                                callback((T)casted, (T)handle, (T)values[0].ClientHandle, values[0].Quality == OpcDa.Quality.Good, unsubscribe);
                            });
                        };
                        sub.SetEnabled(true);
                    }
                }
                catch (Exception ex)
                {
                    LogMessage(String.Format("DA AddItem.2 Tag : {0}", tagName), ex);
                }
                return (T)(object)item;
            }
            catch (Exception ex)
            {
                LogMessage(String.Format("DA AddItem.3 Tag : {0}", tagName), ex);
            }
        }
        return default(T);
    }

If I place a breakpoint inside the anonymous DataChange method, it is NOT called whenever the earlier (i.e. not the rapidly changing) tag is changed. It's almost as if quickly changing tag overrides the other tag's notification. This is not restricted to just two tags - whenever I have a group of tags followed by a rapidly changing one, these tags do not broadcast datachange events.

jmbeach commented 7 years ago

To ensure the problem isn't with the method of debugging, have you tried having them write to a file instead of pausing the debugger? Each subscription is on a separate thread I believe. You might not be able to see the change after pausing the debugger

MuckersMate commented 7 years ago

That's what I thought - I've experienced such behaviour in the past, so I built the executable and ran it outside the IDE, but it did exactly the same.

It may be worth while mentioning that I'm (unsurprisingly) building an OPC client that can access an OPC server using both DA and UA. The UA works lovely, the DA doesn't.

I'll give the file method a go...

jmbeach commented 7 years ago

So are you not using the H-Opc library?

MuckersMate commented 7 years ago

Nothing relating to that tag is written to the file....

Yes, I am - I'm using the DA and UAClient

MuckersMate commented 7 years ago

Btw, the tags that are not picking up a broadcast change, these OpcDa.Items can still write to the PLC (and even then they do not pick up on the datachange). If there are no rapidly updating tags AFTER them in the subscription list, they receive the datachange fine...

jmbeach commented 7 years ago

20 Look at this issue. Might be part of the problem.

MuckersMate commented 7 years ago

No I don't think #20 is related. I'm calling the AddItem as follows (note I'm using "object" between "_client.AddTag" and before "(groupHandle, tagHandle," (which for some reason is being stripped from my comment(?))....

`var result = _client.AddTag(groupHandle, tagHandle, tagName,

                (opcValue, groupID, tagID, goodQuality, stop) =>
                {
                    long t = -1;

                    if (tagID != null)
                    {
                        t = (long)tagID;
                    }
                    if (StopMonitoring)
                        try
                        {
                            stop();
                        }
                        catch (Exception ex)
                        {

                        }
                    else
                    {
                        DoChange((long)groupID, tagID != null ? (long)tagID : -1, goodQuality, opcValue);
                    }
                }`

The opcValue is being sent back to whatever client asked the class to add the tag to a subscription. And besides, the adding of the tag (whose value is fast changing) on its own is fine - it's when it is added in a group of other tags that the earlier added tags IMMEDIATELY stop receiving any datachange. If you add another tag AFTER the fast changing tag, the new tag behaves fine, with it and the fast changing tag receiving datachange events.

jmbeach commented 7 years ago

_client.AddTag is not part of h-opc. Are you creating a client from scratch?

MuckersMate commented 7 years ago

No, I'm not. "AddTag" uses h-opc. "AddItem"...

For my purpose, it's important that the the DA/UA wrapper returns the tag handle, not the tag (which AddItem does)

Here's my code for AddTag..

public T AddTag<T>(long subscriptionHandle, long clientHandle, string tagName, Action<T, T, T, bool, Action> callback)
    {

        Opc.Da.Item tag = (Opc.Da.Item)((object)AddItem<T>(subscriptionHandle, clientHandle, tagName, callback));
        return (T)tag.ClientHandle;
    }
jmbeach commented 7 years ago

For multiline codeuse three of these: `

MuckersMate commented 7 years ago

I modified your TestApplication to suit our purposes - i.e. a client that can accept OPC operations on either a DA or UA access type, without the consumers being aware of the Access type used. The implementation uses either a DA or UAClient, depending upon supplied Connect() parameters.

jmbeach commented 7 years ago

The AddItem function is part of the Opc Foundation UA libraries

jmbeach commented 7 years ago

which is utilized by OPC. The equivalent function for DA is AddItems

MuckersMate commented 7 years ago

I modified the IClient interface to expose the AddTag function. I then added this function to the DAClient and UAClient classes, with each calling their AddItem function.

jmbeach commented 7 years ago

The AddItem function is from the OPC UA library though

MuckersMate commented 7 years ago

Yes, I'm using the same method as your test app is doing - choosing to use either the DAClient or UAClient class. This wrapper class also exposes a COM interface so it can be used by a VB6 DLL.

jmbeach commented 7 years ago

What is the test app? h-opc-cli or the unit test project

jmbeach commented 7 years ago

https://gitter.im/H-Opc Let's continue this here

jmbeach commented 7 years ago
    [Test]
    public void DaTestMonitorChangesAfterFastChanging()
    {
      var slowTag = "storage.numeric.reg01";
      var fastTag = "numeric.sin.double";
      var i = 0;
      var j = 0;
      // initialize it with value 0
      _client.Write(slowTag, 0);
      Thread.Sleep(100);
      _client.Monitor<double>(slowTag, (newValue, unsub) =>
      {
        i++;
      });
      Thread.Sleep(100);
      _client.Write(slowTag, 1);
      Thread.Sleep(100);
      _client.Monitor<double>(fastTag, (newValue, unsub) =>
      {
        j++;
      });
      System.Threading.SpinWait.SpinUntil(() => j >= 10, 2000);
      _client.Write(slowTag, 0);
      System.Threading.SpinWait.SpinUntil(() => i == 3, 1000);
      Assert.AreEqual(3, i);
    }

This test passes. Could not reproduce.