在<T> PLC 连接丢失或 PLC 程序上传后自动重新创建轮询值



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

我使用的是NuGet的TwinCAT.Ads.Rreactivev4.4.0库。

我的程序流程是:

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

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

    • 3[a]创建反应循环轮询值AnyTypeExtensions.PollValues.T
    • 3[b]我也尝试过ValueSymbolExtensions.PollValues,它还没有在Beckhoff网站上记录下来

到目前为止,我已经发现1。和2。即使我拔下以太网电缆或将新程序下载到PLC->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")
);

我每隔一秒读取一次数据的自定义结构。在正常运行期间,这很好,但在我更改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

此外,只有当我在ads客户端上调用Connect((/Disconnect((时,才会触发连接状态更改事件,而不是在连接问题上。知道我怎么才能发现有连接问题吗?

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

解决方案是使用PollValues的另一个重载,其中我指定Func<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
};     
}
);

我已经向nuget包的创建者报告了这个问题。

答案6.3.2020:

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

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

如果等待AdsClient/Ads.Reactive包的下一个版本是合适的,那么应该可以在PollValues代码内部处理这种情况(以您期望的方式(。我现在已将其列入我的TODO列表。


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

TwinCAT.Ads.Rreactive 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);
}

最新更新