Search code examples
c#system.reactivetwincattwincat-adstwincat-ads-.net

Twincat Ads Reactive weird Handle behaviour


I'm working with TwincatAds.Reactive 6.0.190 in .NET 6 WPF Desktop application.
I'm also using MVVM pattern.
My goal is to create a Class that is going to observe for a PLC Variable changes, collect those variables to a dictionary, and later on use those values in the ViewModel.
Here's the method where I'm attaching the notification and action where I'm handling the notification.

    public void AttachNotification(IEnumerable<(string key, Type type)> Symbols)
    {
        _observerValueNotification = Observer.Create<ValueNotification>(val =>
        {
            // Does handle really start from 2?
            var handle = val.Handle;
            if (val.UserData is object[] objects)
            {
                string tag = objects[handle - 2].ToString();

                if (!_values.Any(x => x.Key == tag))
                    _values.Add(new SymbolModel { Key = tag, Value = val.Value });
                else
                {
                    var symbol = _values.First(x => x.Key == tag);
                    symbol.Value = val.Value;
                }
            }

            ValuesChanged?.Invoke(_values);
        });

        if (_plcWrapper.AdsClient != null)
        {
            // Get Symbols from SymbolLoader
            List<AnySymbolSpecifier> list = new();
            List<string> userData = new();
            foreach (var (key, type) in Symbols)
            {
                list.Add(new AnySymbolSpecifier(key, new AnyTypeSpecifier(type)));
                userData.Add(key);
            }
            _subscription2 = _plcWrapper.AdsClient.WhenNotificationEx(list, NotificationSettings.ImmediatelyOnChange, userData.ToArray())
                                                  .Subscribe(_observerValueNotification);
        }
    }

I'm using ValueNotification simply because, I'd like to use this pattern also for complex PLC Variables like Structs.
As You can see, in the WhenNotificationEx method I'm using UserData[] to provide some sort of identification of what Variable has changed when handling the change.
My idea was to use Handle property from ValueNotification as an indexer in UserData[] to identify what variable I'm dealing with, but for some reason Handle starts from 2.

My question is, is it expected behaviour, does the Handle value really always start from 2?


Solution

  • I've decided that relying on the Handle being index in the UserData array is quite unpredictable as Handle is being created by the Twincat Ads server.

    Solved the issue by creating own extension method to the WhenNotificationEx. Turned out IDisposableHandleBag has exactly what I was looking for, which is SourceResultHandles property, where AnySymbolSpecifier and ResultHandle are both stored!

    Here's created extension method
    public static Dictionary<string, uint> Handles { get; private set; } = new();

        public static IObservable<ValueNotification> WhenNotificationWithHandle(this IAdsConnection connection, IList<AnySymbolSpecifier> symbols, NotificationSettings settings)
        {
            IAdsConnection connection2 = connection;
            IList<AnySymbolSpecifier> symbols2 = symbols;
            NotificationSettings settings2 = settings;
            if (connection2 == null)
            {
                throw new ArgumentNullException("connection");
            }
    
            if (symbols2 == null)
            {
                throw new ArgumentNullException("symbols");
            }
    
            if (symbols2.Count == 0)
            {
                throw new ArgumentOutOfRangeException("symbols", "Symbol list is empty!");
            }
    
            IDisposableHandleBag<AnySymbolSpecifier> bag = null;
            EventLoopScheduler scheduler = new EventLoopScheduler();
            IObservable<int> whenSymbolChangeObserver = connection2.WhenSymbolVersionChanges(scheduler);
            IDisposable whenSymbolChanges = null;
            Action<EventHandler<AdsNotificationExEventArgs>> addHandler = delegate (EventHandler<AdsNotificationExEventArgs> h)
            {
                connection2.AdsNotificationEx += h;
                bag = ((IAdsHandleCacheProvider)connection2).CreateNotificationExHandleBag(symbols2, relaxSubErrors: false, settings2, null);
                bag.CreateHandles();
    
                // Collect Handles
                Handles.Clear();
                foreach (var item in bag.SourceResultHandles)
                    Handles.Add(item.source.InstancePath, item.result.Handle);
    
                whenSymbolChanges = whenSymbolChangeObserver.Subscribe((Action<int>)delegate
                {
                    bag.CreateHandles();
                    Handles.Clear();
                    foreach (var item in bag.SourceResultHandles)
                        Handles.Add(item.source.InstancePath, item.result.Handle);
    
                }, (Action<Exception>)delegate
                {
                    TcTraceSource traceAds = AdsModule.TraceAds;
                    DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(101, 1);
                    defaultInterpolatedStringHandler.AppendLiteral("The AdsServer '");
                    defaultInterpolatedStringHandler.AppendFormatted(connection2.Address);
                    defaultInterpolatedStringHandler.AppendLiteral("' doesn't support SymbolVersionChanged Notifications! Handle recreation is not active!");
                    traceAds.TraceInformation(defaultInterpolatedStringHandler.ToStringAndClear());
                });
            };
            Action<EventHandler<AdsNotificationExEventArgs>> removeHandler = delegate (EventHandler<AdsNotificationExEventArgs> h)
            {
                if (whenSymbolChanges != null)
                {
                    whenSymbolChanges.Dispose();
                }
    
                scheduler.Dispose();
                if (bag != null)
                {
                    bag.Dispose();
                    bag = null;
    
                    Handles.Clear();
                }
    
                connection2.AdsNotificationEx -= h;
            };
    
            return from ev in Observable.FromEventPattern<EventHandler<AdsNotificationExEventArgs>, AdsNotificationExEventArgs>(addHandler, removeHandler)
                   where bag.Contains(ev.EventArgs.Handle)
                   select new ValueNotification(ev.EventArgs, ev.EventArgs.Value);
        }