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
36 stars 15 forks source link

PollValues<T> throws on failure #43

Closed maxxie85 closed 11 months ago

maxxie85 commented 1 year ago

The method Task<ResultReadValueAccess> ReadValueAsync(CancellationToken cancel) from IValueSymbol throws an exception if the symbol cannot be read, where an ResultReadValueAccess is expected with the failed status code.

Exception message

IValueSymbol.ReadValueAsync failed with ErrorCode: 1861!

This exception will cause the application to crash because it cannot be handled by try catch. However I expect a ResultReadValueAccess where I can check the Succeeded status, the error code and then decide what to do.

Stack trace

at TwinCAT.Ads.Reactive.ValueSymbolExtensions.<>c__DisplayClass11_0`1.<PollValues>b__0(ResultReadValueAccess2`2 x)
at System.Reactive.Linq.ObservableImpl.Select`2.Selector._.OnNext(TSource value) in /_/Rx.NET/Source/src/System.Reactive/Linq/Observable/Select.cs:line 39

edit: The version I'm currently using, 6.0.216

maxxie85 commented 1 year ago

This doesn't happen when using 6.0.194 so this appears to be a regression in the latest version.

RalfHeitmann commented 1 year ago

Actually, I am not able to reproduce the issue. With the following code

using TwinCAT;
using TwinCAT.Ads;
using TwinCAT.Ads.Reactive;
using TwinCAT.Ads.TypeSystem;
using TwinCAT.TypeSystem;
using TwinCAT.ValueAccess;

using (AdsClient client = new AdsClient())
{
    client.Connect("172.17.60.197.1.1", 851);

    AdsSymbolLoader loader = (AdsSymbolLoader)SymbolLoaderFactory.Create(client, SymbolLoaderSettings.Default);
    var symbol = (IValueSymbol)loader.Symbols["TwinCAT_SystemInfoVarList._TaskInfo[1].CycleCount"];

    IDisposable dispose = symbol.PollValues2(TimeSpan.FromMilliseconds(500)).Subscribe(r =>
    {
        Console.WriteLine($"Observed value: {r.Value}, AdsErrorCode: {r.ErrorCode}");
    });

    while (true)
    {
        var result = await symbol.ReadValueAsync(CancellationToken.None);
        Console.WriteLine($"ReadValue: {result.Value}, AdsErrorCode: {result.ErrorCode}");
        await Task.Delay(500);
    }
}

I get such an output (no Exceptions, stopped the Remote PLC to force Error 1861):

ReadValue: 3892, AdsErrorCode: 0
Observed value: 3926, AdsErrorCode: 0
ReadValue: 3945, AdsErrorCode: 0
Observed value: 3975, AdsErrorCode: 0
ReadValue: 3997, AdsErrorCode: 0
Observed value: 4026, AdsErrorCode: 0
ReadValue: 4049, AdsErrorCode: 0
Observed value: , AdsErrorCode: 1861
Observed value: , AdsErrorCode: 1861
ReadValue: , AdsErrorCode: 1861
Observed value: , AdsErrorCode: 1861
ReadValue: , AdsErrorCode: 1861
Observed value: , AdsErrorCode: 1861

What was the incident triggering the 1861 in your case?

maxxie85 commented 1 year ago

That's strange. We have optional feedback for some of our basic control logic. (e.g. a cylinder can have open and close feedback but is not strict) So in the initialize routine of our debug interface, I perform a check if I can read the status of that optional IDigitalInput symbol. And if succeeded I start a PollValues on it.

protected override async Task OnStartDetailView(IObservable<Unit> observable)
{
    AtHomeSensor = await Symbol.AtHomeSensor.IsTrue.ReadValueAsync(CancellationToken.None);

    if (AtHomeSensor.Succeeded)
    {
        DetailDisposables.Add(Symbol.AtHomeSensor.IsTrue.PollValues2(observable).Subscribe(o => { AtHomeSensor = o; }));
    }
}

So in this scenario it's possbile that the IsTrue, which is a IValueSymbol<bool> is 0 in the PLC, as it's not linked. On 6.0.194 and before I get a result wich Succeeded false and an error code. But on 6.0.216 an exception is thrown within the ReadValueAsync method.

I will create demo project to isolate the issue

maxxie85 commented 1 year ago

A I was mislead by threading. It's not the Task<ResultReadValueAccess> ReadValueAsync(CancellationToken cancel) method that has a regression. But it's the IObservable<T> PollValues<T>(this IValueSymbol symbol, TimeSpan period) extension from ValueSymbolExtensions.

ReadValueAsyncDemo.zip ReadValueAsyncDemo 6.0.216.zip

RalfHeitmann commented 1 year ago

But that one is intended to throw an exception because it returns the pure values. If you don't want Exceptions, than an overload producing ResultXXX objects should be used.

maxxie85 commented 1 year ago

I don't mind exceptions, but what I find interesting is that the same code doesn't throw with 6.0.194, but does with 6.0.216.

maxxie85 commented 1 year ago

This caused me to get trapped in a fun little rabbit hole.

But the problem I'm facing comes from a different source entirely. In a previous issue I had a discussion with @RalfHeitmann about sharing the IObservable<Unit> trigger between multiple PollValues to reduce threading. And my issue has to do with that. Because in our application we have a specific debugging screen with has a lot of communication with the PLC. All done via PollValues and sharing the same trigger source. And this screen has the ability to even add more communication load with detail views you can open or close. And upon opening a detail view, different random observables that had already started, are throwing the 1861 ADS error. As I don't attach a error handler to the observers this is being trowed to the background thread that created them.

So there is apparently a limit to how many PollValues observables can be shared with a IObservable<Unit> trigger source. Because having to much of them, or adding a bunch of new observables will cause timeouts on existing ones. It still seems to me something changed in the ADS source between .194 and .216 for this to become a issue for me.

maxxie85 commented 1 year ago

Finally able to reproduce in a demo.

In the solution you find a PLC program, DemoPLC. When you run AdsTimeOutDemo after a second of 10 I get the AdsTimeOutException.

edit: If you set the debugger to break on the AdsException, you can see that it's every time a different symbol causing the timeout.

ReadValueAsyncDemo.zip

RalfHeitmann commented 11 months ago

Sorry for the late answer :-( , other topics than ADS occupied me the last months. Just probed your sample code: I could reproduce the issue with Beckhoff.TwinCAT.Ads 6.0.216

With the actual Versions 6.1.86 and 6.0.302 your code seems to run as intended :-) Please reopen in case you still have problems.

maxxie85 commented 10 months ago

I will try the new versions.

Are there noteworthy changes between 6.1 and 6.0, as I don't see any mentioning about 6.1 on infosys.

RalfHeitmann commented 10 months ago

Actually, the package Version 6.1 is created with different dependencies (which still should fulfill all needs): 6.0 --> net6.0, netstandard2.0, netcoreapp3.1, net461 6.1 --> net7.0, net6.0, netstandard2.0 By now, the internal implementations are the same