0

我正在尝试编写 C# 应用程序,当应用程序与 PLC 失去连接或将下载新版本的 PLC 程序时,ADS 客户端将自动重新连接/更新读取/写入值和订阅。

我正在使用来自 NuGet 的TwinCAT.Ads.Reactive v4.4.0库。

我的程序流程是:

连接ADS服务器(连接成功)=>

  1. 使用ValueSymbolExtensions.WhenValueChanged创建反应式通知
  2. 创建反应循环写入值ValueSymbolExtensions.WriteValues

    • 3[a] 创建响应式循环轮询值AnyTypeExtensions.PollValues.T
    • 3[b] 我也尝试过 ValueSymbolExtensions.PollValues,这在 Beckhoff 网站上尚未记录

到目前为止,我发现即使我拔下以太网电缆或将新程序下载到 PLC,1. 和 2. 也能正常工作 -> WriteValues() 和 WhenValueChanged() 在内部自我更新

代码:

// WhenValueChanged()
TreeViewSymbols = SymbolLoaderFactory.Create(_client, SymbolLoaderSettings.Default).Symbols;  // Load symbol tree from plc

IValueSymbol boolVal = (IValueSymbol)TreeViewSymbols["SomeBoolValue"];
boolVal.WhenValueChanged().Subscribe(Observer.Create<object>(val => ArchiveData((bool)val)));

// WriteValues()
IValueSymbol toggleBit = (IValueSymbol)TreeViewSymbols["toggle_bit"];
            toggleBit.WriteValues(
                  Observable.Interval(TimeSpan.FromSeconds(1.0)).Select(x => x % 2 == 0 ? false : (object)true),
                  e => Debug.WriteLine($"Error writing toggle bit")
            );

我每隔一秒读取一次PollValues()自定义数据结构。在正常运行期间,这工作正常,但是在我更改 PLC 程序并将更改下载到 PLC 或连接丢失(拔下以太网电缆)后,此订阅在内部失败,并且不会像上面的那样恢复。

IValueSymbol state = (IValueSymbol)TreeViewSymbols[Cfg.ModuleStateTag];
state.PollValues(TimeSpan.FromSeconds(1.0))
      .Subscribe(Observer.Create<object>(
       val => // val comes as byte[] array
      {
         var a = new ModuleStateData((byte[])val);
         Debug.WriteLine($"Status values Machine Mode:{a.MachineMode}");
      },
      e => Debug.WriteLine($"Error reading status"),
      () => Debug.WriteLine($"OnComplete???? reading status"))
      ).AddDisposableTo(_disposables);

_client.PollValues<ModuleStateData>(
                Cfg.ModuleStateTag,
                TimeSpan.FromSeconds(1.0)
                ).Subscribe(Observer.Create<object>(
                val =>
                {
                    Debug.WriteLine($"Status values Machine Mode:{val.MachineMode});
                },
                e => Debug.WriteLine($"Error reading status - {e.Message}"),
                () => Debug.WriteLine($"OnComplete???? reading status"))
            ).AddDisposableTo(_disposables);

ConnectionStateChanged

此外,仅当我在广告客户端上调用 Connect()/Disconnect() 时才会触发连接状态更改事件,而不是在连接问题时触发。知道如何找出连接问题吗?

4

2 回答 2

0

我已经找到了一半问题的解决方案,即使在连接错误(未插入的以太网电缆)的情况下也可以观察到,但是在下载新的 PLC 程序(广告符号版本已更改)后,它不会用新版本重新创建变量,所以它只会抛出错误.

解决方案是在我指定的地方使用另一个PollValuesFunc<Exception, T> errorHandler重载。这个处理程序就像备份值一样工作,以防出错。

_client.PollValues<ModuleStateData>(
                Cfg.ModuleStateTag,
                TimeSpan.FromSeconds(1.0),
                e =>
                {
                    Debug.WriteLine($"Error reading status {Cfg.Name} - {e.Message}");
                    return new ModuleStateData()
                    {
                        // Set data in case of error
                    };     
                }
            );
于 2020-03-04T14:06:17.317 回答
0

我已将此问题报告给 nuget 包的创建者。

回答 6.3.2020:

感谢您报告此问题,这是一个重要方面。我对你的案子做了一个简短的调查。不幸的是,实际上并不支持 observable 的自动复活。原因是 PollValues 内部使用符号句柄,当更新现在的 PLC 程序时,它将失效。所以实际上,您唯一的解决方案是在 TcAdsClient/AdsConnection 上注册 SymbolVersionChanged 事件(这是在下载/重新启动后发送的)并重新创建 Observable。

https://infosys.beckhoff.de/content/1031/tc3_adsnetref/7313543307.html?id=2192955395989567903

如果您适合等待 AdsClient / Ads.Reactive 包的下一个版本 - 应该可以在 PollValues 代码内部处理这种情况(以您期望的方式)。我现在在我的 TODO List 上有它。


在我得到这个答案之前,我已经编辑了他们的实现并创建了我自己的扩展方法。

TwinCAT.Ads.Reactive v4.4.0 实现

    public static IObservable<T> PollValues<T>(
      this IAdsConnection connection,
      string instancePath,
      int[] args,
      IObservable<Unit> trigger,
      Func<Exception, T> errorHandler)
    {
      DisposableHandleBag bag = new DisposableHandleBag(connection, (IList<string>) new string[1]
      {
        instancePath
      });
      Func<Unit, T> selector = (Func<Unit, T>) (o =>
      {
        try
        {
          return (T) connection.ReadAny(61445U, bag.GetHandle(instancePath), typeof (T), args);
        }
        catch (Exception ex)
        {
          if (errorHandler != null)
            return errorHandler(ex);
          throw;
        }
      });
      Action finallyAction = (Action) (() =>
      {
        bag.Dispose();
        bag = (DisposableHandleBag) null;
      });
      return trigger.Select<Unit, T>(selector).Finally<T>(finallyAction);
    }

我的编辑 - 我为每个读取创建句柄并在读取后删除句柄

    public static IObservable<T> MyPollValues<T>(
            this IAdsConnection connection,
            string instancePath,
            int[] args,
            IObservable<Unit> trigger,
            Func<Exception, T> errorHandler)
        {
            Func<Unit, T> selector = (Func<Unit, T>)(o =>
            {
                try
                {
                    var handle = connection.CreateVariableHandle(instancePath);
                    var data = (T)connection.ReadAny(handle, typeof(T), args);
                    connection.DeleteVariableHandle(handle);
                    return data;
                }
                catch (Exception ex)
                {
                    if (errorHandler != null)
                        return errorHandler(ex);

                    throw;
                }
            });
            return trigger.Select<Unit, T>(selector);
        }
于 2020-03-10T10:10:26.737 回答