Beckhoff / TF6000_ADS_DOTNET_V5_Samples

Sample code for the Version 6.X series of the TwinCAT ADS .NET Packages
https://infosys.beckhoff.com/content/1033/tc3_ads.net/9407515403.html?id=6770980177009971601
BSD Zero Clause License
37 stars 15 forks source link

Cannot subscribe to value changes #29

Closed drvic10k closed 2 years ago

drvic10k commented 2 years ago

when trying to subscribe to the value changed notifications, I receive this error:

TwinCAT.Ads.AdsErrorException: 'Couldn't register for SymbolVersion change Notifications on the connected ADS Server. Don't register for AdsClient.SymbolVersionChanged events! (Service is not supported by server. (AdsErrorCode: 1793, 0x701))'

my subscriptions work fine with the real TwinCat server

RalfHeitmann commented 2 years ago

Which Beckhoff.TwinCAT.Ads Version you are using? Is it the latest? This Exception should be catched internally.

drvic10k commented 2 years ago

I updated to the latest 6.0.164 and it behaves the same I am using TwinCAT.Ads.Reactive.AdsClientExtensions.WhenNotification I checked your Reactive sample, where you use client.WhenValueChangedAnnotated and this is not throwing the exception

RalfHeitmann commented 2 years ago

Could you post here the Callstack of WhenNotification And/Or the exact overload you are using?

drvic10k commented 2 years ago

I am using this overload: public static IObservable<SymbolValueNotification> WhenNotification( this IAdsConnection client, ISymbol symbol, NotificationSettings settings)

with NotificationSettings.Default

call stack: This exception was originally thrown at this call stack: TwinCAT.Ads.AdsClient.AdsSymbolVersionChanged.add(System.EventHandler<TwinCAT.Ads.AdsSymbolVersionChangedEventArgs>) System.Reactive.Linq.ObservableImpl.ClassicEventProducer<TDelegate, TArgs>.AddHandler(TDelegate) in FromEvent.cs System.Reactive.Linq.ObservableImpl.EventProducer<TDelegate, TArgs>.Session.AddHandler(TDelegate) in FromEvent.cs System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() System.Reactive.AnonymousSafeObserver<T>.OnError(System.Exception) in AnonymousSafeObserver.cs System.Reactive.Sink<TSource, TTarget>.OnError(System.Exception) in Sink.cs System.Reactive.Subjects.Subject<T>.OnError(System.Exception) in Subject.cs System.Reactive.Linq.ObservableImpl.EventProducer<TDelegate, TArgs>.Session.AddHandler(TDelegate) in FromEvent.cs System.Reactive.Concurrency.Scheduler.ScheduleAction.AnonymousMethod__75_0(System.Reactive.Concurrency.IScheduler, (System.Action<TState> action, TState state)) in Scheduler.Simple.cs System.Reactive.Concurrency.ScheduledItem<TAbsolute, TValue>.InvokeCore() in ScheduledItem.cs ... [Call Stack Truncated]

drvic10k commented 2 years ago

so after some more investigation, it looks like WhenValueChangedAnnotated is really working, I will have to check how it behaves with the real TwinCat

interesting finding though, when I use SymbolIterator to get the symbol for the parameter of WhenValueChangedAnnotated, I get the notifications, when I use Connection.TryReadSymbol, the notifications don't trigger

RalfHeitmann commented 2 years ago

You wont get ADS Notification when you call TryReadSymbol. Notifications are sent when you are changing values (with write). Please try the Version 6.0.178 of the Beckhoff.TwinCAT.Ads packages. I have done several improvements for the AdsSymbolicServer scenario for ArrayTypes, ElementTypes and Notifications. Furthermore, the AdsSymbolicServerSample here on GitHub is extended now to demonstrate Notification subscriptions in many variations (commit 936fa6265fbf66f8545bb53f00f42c926303cc1d)

RalfHeitmann commented 2 years ago

The problem of the Exception stack above is fixed also (SymbolVersionChanged issue).

drvic10k commented 2 years ago

You wont get ADS Notification when you call TryReadSymbol. Notifications are sent when you are changing values (with write). Please try the Version 6.0.178 of the Beckhoff.TwinCAT.Ads packages. I have done several improvements for the AdsSymbolicServer scenario for ArrayTypes, ElementTypes and Notifications. Furthermore, the AdsSymbolicServerSample here on GitHub is extended now to demonstrate Notification subscriptions in many variations (commit 936fa62)

I meant this:

public override IObservable<T> GetObservable<T>(string symbolPath, NotificationSettings settings)
        {
            var symbolLoader = SymbolLoaderFactory.Create(this.Connection, SymbolLoaderSettings.DefaultDynamic);
            SymbolIterator recursiveIterator = new SymbolIterator(symbolLoader.Symbols, true);
            var symbol = recursiveIterator.Where(x => x.InstancePath == symbolPath);

            return this.Connection
                .WhenValueChangedAnnotated(symbol)
                .Select(x => (T)x.Value);
        }

whan I use TryReadSymbol to get the symbol to pass to WhenValueChangedAnnotated I get no notification, I need to use it like this

also I tried it with the new version, the problem with WhenNotification stays, but I cannot find out the stack trace, because it's coming async from the observable

drvic10k commented 2 years ago

so after testing with the real TwinCat system I found out, that the workaround that works with the AdsSymbolServer does not work here

@RalfHeitmann can you please reopen this issue? it is not resolved, the same approach is not working for both real twincat and the AdsSymbolServer

RalfHeitmann commented 2 years ago

Still not able to reproduce the issue, what I tried: Changed Program.Main in the SymbolicServerSample to this:

// ...
                    Console.WriteLine($"Symbolic Test Server runnning on Address: '{server.ServerAddress}' ...\n");
                    Console.WriteLine($"For testing the server see the ReadMe.md file in project root");
                    Console.WriteLine($"or type the following command from Powrshell with installed 'TcXaeMgmt' module:\n");
                    Console.WriteLine($"PS> test-adsroute -NetId {server.ServerAddress.NetId} -port {server.ServerAddress.Port}\n\n");
                    Console.WriteLine("Press the ENTER key to cancel...\n");

                    // Beneath the external access from an out-of-process ADS client
                    // We could also access the server from within this process
                    // What is done in the following for demonstration and testing purposes

                    // Instantiate ADS Session / Client
                    // and execute some operations against the SymbolicServer

                    SessionSettings settings = new SessionSettings(120000);
                    List<IDisposable> disposables = new List<IDisposable>();

                    using (AdsSession session = new AdsSession(AmsNetId.Local, server.ServerPort, settings))
                    {

                        // Connect to SymbolicServer
                        session.Connect();
                        IAdsConnection connection = (IAdsConnection)session.Connection;
                        ISymbolLoader factory = SymbolLoaderFactory.Create(session.Connection, SymbolLoaderSettings.Default);
                        // Load DataTypes in Symbol Instances
                        var types = factory.DataTypes;
                        var symbols = factory.Symbols;

                        int count1 = 0;
                        int count2 = 0;

                        IDisposable observer1 = GetObservable1<bool>(connection, symbols, "Globals.bool1", NotificationSettings.Default).Take(10).Subscribe(v => Console.WriteLine($"Observer1: Count:{++count1} Value: {v}"));
                        IDisposable observer2 = GetObservable2<bool>(connection, "Globals.bool1", NotificationSettings.Default).Take(10).Subscribe(v => Console.WriteLine($"Observer2: Count:{++count2} Value: {v}"));

                        // Wait for stopping Server Task or cancellation
                        await Task.WhenAny(new[] { cancelTask, serverTask });
                        errorCode = await serverTask;
                    }
// ...

public static IObservable<T> GetObservable1<T>(IAdsConnection connection, IEnumerable<ISymbol> symbols, string symbolPath, NotificationSettings settings)
        {
            SymbolIterator recursiveIterator = new SymbolIterator(symbols, true);
            var symbol = recursiveIterator.Where(x => x.InstancePath == symbolPath);

            return connection
                .WhenValueChangedAnnotated(symbol)
                .Select(x => (T)x.Value);
        }

        public static IObservable<T> GetObservable2<T>(IAdsConnection connection, string symbolPath, NotificationSettings settings)
        {
            IAdsSymbol symbol2 = null;
            connection.TryReadSymbol(symbolPath, out symbol2);

            return connection
                .WhenNotification(symbol2,NotificationSettings.Default)
                .Select(x => (T)(x.Value));
        }

Output:

Observer1: Count:1 Value: True
Observer1: Count:2 Value: True
Observer2: Count:1 Value: True
Observer1: Count:3 Value: False
Observer1: Count:4 Value: False
Observer2: Count:2 Value: False
Observer1: Count:5 Value: True
Observer1: Count:6 Value: True
Observer2: Count:3 Value: True
Observer1: Count:7 Value: False
Observer2: Count:4 Value: False
Observer1: Count:8 Value: True
Observer1: Count:9 Value: True
Observer2: Count:5 Value: True
Observer1: Count:10 Value: False
Observer2: Count:6 Value: False
Observer2: Count:7 Value: True
Observer2: Count:8 Value: False
Observer2: Count:9 Value: True
Observer2: Count:10 Value: False

Both observers work equally well on the boolean value that is toggled per timer in the sample

I'm getting confused about the issue here ... There are also some things mixed up here ... Please try to create a little demonstration sample (best from SymbolicServerSample as starting point)

drvic10k commented 2 years ago

I tried with unmodified AdsSymbolicServerSample and I was surprised that AmsNetId.Local is not localhost but it's the address of the system configured in my router so I tried to specify localhost explicitly but the result was the same

therefore I suspect there is either some problem with my setup or somewhere internally the AmsNetId is overriden again by the router

the exception is raised in this code:

        private static void CallReadValueByInstancePath(AdsSession session)
        {
            bool bValue = (bool) session.Connection.ReadValue("Main.bool1", typeof(bool));
            string sValue = (string) session.Connection.ReadValue("Main.string1", typeof(string));
        }

on the first ReadValue

drvic10k commented 2 years ago

I tried it with disabled twincat services running only the AdsRouter from nuget and it behaves the same I get the exception on ReadValue

RalfHeitmann commented 2 years ago

I tried with unmodified AdsSymbolicServerSample and I was surprised that AmsNetId.Local is not localhost but it's the address of the system configured in my router

By default, AmsNetId.Local gets the AmsNetId from the router (1 ADS roundtrip) and should't show 'localhost'. So thats as-designed.

the exception is raised in this code:

        private static void CallReadValueByInstancePath(AdsSession session)
        {
            bool bValue = (bool) session.Connection.ReadValue("Main.bool1", typeof(bool));
            string sValue = (string) session.Connection.ReadValue("Main.string1", typeof(string));
        }

on the first ReadValue

Is this still this Exception (thought we talk about different issues now)

'Couldn't register for SymbolVersion change ...

I thought you get that error out of an 'WhenNotification' Call. The exception was mistakenly leaked by that one and that was one of the latest fixes.

The actual behaviour is, if you register for SymbolVersionChanged (which is in fact only supported by the PLC) you can get that Exception on AdsServers that doesn't support it (like the SymbolicServer).

  1. On the the Connect() call when the Event was registered before Connect()
  2. On the EventRegistration when the Client/Session is already connected

So please (double) check, that you are not Registering SymbolVersionChanged in your code to the SymbolicServer and be sure to use the latest (fixed) version of the Beckhoff.TwinCAT.Ads and Beckhoff.TwinCAT.Ads.Reactive package also from your client side (check the loaded TwinCAT.Ads.dll and TwinCAT.Ads.Reactive.dll in debugger, often old versions of Assemblies are hanging around in bin/obj folders). If you really reproduce the Exception in the unchanged sample, this could be the only cause).

drvic10k commented 2 years ago

you are right, there were references to older versions of Ads and Ads.Reactive (6.0.129) held through another librtary I was using

the exception is now not thrown anymore and the notifications work

thank you very much for your patience