C# 倍福ADS的正确打开方式,使用AdsRemote组件优雅的通过ADS通讯,支持WPF窗体控件的绑定机制,做上位机页面很方便,大大节省了开发时间。
倍福的官方文档给的例子我就不多说了,今天介绍一种更改优雅的使用ADS组件进行通讯的方式,非常符合高级语言的编程风格,在也不用到处readany,writeany了。
https://github.com/nikvoronin/AdsRemote
AdsRemote:Beckhoff的TwinCAT.Ads API库的高级接口可以节省大量的开发时间。您不需要网络线程或句柄。只需声明一个C#变量,并通过变量属性将其绑定到PLC var。就这样。
我最喜欢的使用方式是变量变化后自动通知,类似观察者模式,不用傻傻的死等结果的反馈。Adsremote组件内部会使用一个线程来对取变量,当值发生变化时,调用ValueChanged事件。
PLC instance
First you have to create an instance of PLC object. This one wiil be like a factory that produces linked variables.
PLC plc = new PLC("5.2.100.109.1.1");
When device connected or disconnected
plc.DeviceReady += Plc_DeviceReady; plc.DeviceLost += Plc_DeviceLost; [...] private void Plc_DeviceReady(object sender, AdsDevice e) { Log("READY [" + e.Address.Port.ToString() + "]"); }
How to create and link variables
Create a copy of your PLC's variable then use it like an ordinary variable We use PLC object that produces linked variables. After that variables will autoupdating their state and value.
//定义变量就这么简单,很容易做其他抽象。直接复杂的结构体类型
Var<short> main_count = plc.Var<short> ("MAIN.count"); Var<ushort> main_state = plc.Var<ushort>("MAIN.state"); Var<short> g_Version = plc.Var<ushort>(".VERSION"); Var<ushort> frm0 = plc.Var<ushort>("Inputs.Frm0InputToggle", 27907); Var<ushort> devState = plc.Var<ushort>(0xF030, 0x5FE, 27907); long framesTotal += frm0 / 2; // automatic type casting MessageBox.Show(frm0); // cast into the string type without call of the ToString()
From now you can subscribe on value changing.
main_count.ValueChanged += delegate { counterStatusLabel.Text = main_count; };
or
main_count.ValueChanged += delegate (object src, Var v) { ushort val = (ushort)v.GetValue(); framesTotal += val / 2; counterStatusLabel.Text = val.ToString(); };
Write-back to the PLC
Use "RemoteValue" propertie to write a new value to the PLC runtime.
main_count.RemoteValue = 123;
WinForms data binding
For example we will bind Text
propertie of the Label
control with default name label1
. At the PLC side we have MAIN.count
variable that contains value of counter that we should show.
Var<short> main_count = plc.Var<short>("MAIN.count"); Binding b = new Binding("Text", main_count, "RemoteValue"); b.ControlUpdateMode = ControlUpdateMode.OnPropertyChanged; b.DataSourceUpdateMode = DataSourceUpdateMode.Never; label1.DataBindings.Add(b);
If we have to convert given value we define a format converter
Var<short> main_count = plc.Var<short>("MAIN.count"); Binding b2 = new Binding("ForeColor", main_count, "RemoteValue"); b2.ControlUpdateMode = ControlUpdateMode.OnPropertyChanged; b2.DataSourceUpdateMode = DataSourceUpdateMode.Never; b2.Format += (s, ea) => { ea.Value = (short)ea.Value < 0 ? Color.Blue : Color.Red; }; label1.DataBindings.Add(b2);
WPF data bindings
In WPF you must use properties instead of variables.
PLC plc; public Var<ushort> frm0 { get; set; } private void Window_Loaded(object sender, RoutedEventArgs e) { plc = new PLC("5.2.100.109.1.1"); frm0 = plc.Var<ushort>("Inputs.Frm0InputToggle", Port: 27907); DataContext = this; }
And explicitly specifying the field .RemoteValue
of the remote variable
<Grid> <Label x:Name="label" Content="{Binding frm0.RemoteValue}" /> </Grid>
Create variables with help of attributes
You can create special class with several variables then mark those ones as remote PLC variables. Remember, all variables must declare a type. Otherwise you'll get NULL.
Public fields only!
public class PRG_Main { [LinkedTo("MAIN.count", As: typeof(short), Port: (int)AmsPort3.PlcRuntime1)] public Var count; [LinkedTo("MAIN.state", Port: (int)AmsPort3.PlcRuntime1)] public Var<ushort> state; [LinkedTo("Inputs.Frm0InputToggle", Port: 27907)] public Var<ushort> frm0_1; [LinkedTo(IGrp: 0xF030, IOffs: 0x5F4, Port: 27907)] public Var<ushort> frm0; }
or more concisely for the PLC's Runtime #1
public class PRG_Main { [LinkedTo("MAIN.count")] public Var<short> count; [LinkedTo("MAIN.state")] public Var<ushort> state; }
Again in WPF-project you should use properties
public class PRG_Main { [LinkedTo("MAIN.count")] public Var<short> count { get; set; } [LinkedTo("MAIN.state")] public Var<ushort> state { get; set; } }
It's time to create instance of our class.
If you don't need of special class constructor just write:
PRG_Main Main = plc.Class<PRG_Main>();
otherwise for cunstructor with parameter list or something else we use it in this maner
Main = new PRG_Main(param1, param2, ...); plc.Class(Main);
在看看倍福TwinCAT.Ads组件的例子
using TwinCAT.Ads;
/// <summary>
/// 读、写PLC变量
/// </summary>
static void ReadAndWrite()
{
//Create a new instance of class TcAdsClient
TcAdsClient tcClient = new TcAdsClient();
AdsStream dataStream = new AdsStream(8);
AdsBinaryReader binReader = new AdsBinaryReader(dataStream);
int iHandle = 0;
double iValue = 0;
AdsBinaryWriter binWriter = new AdsBinaryWriter(dataStream);//共用一个变量,要控制Position
int iHandle2 = 0;
try
{
//tcClient.Connect("5.49.89.132.1.1", 851);//远程连接,具体的设备
tcClient.Connect(851);//本地测试
//Get the handle of the SPS variable "PLCVar"
iHandle = tcClient.CreateVariableHandle("GVL_Remote.SensorMM");
iHandle2 = tcClient.CreateVariableHandle("GVL_Remote.Score");
while (iValue <= 999)
{
//Use the handle to read PLCVar
dataStream.Position = 0;
tcClient.Read(iHandle, dataStream);
iValue = binReader.ReadDouble();
Console.WriteLine($"Current Distance = {iValue} at time {DateTime.Now}");
//write
double newValue = iValue * 3;
dataStream.Position = 0;
binWriter.Write(newValue);
tcClient.Write(iHandle2, dataStream);
Console.WriteLine($"new value = {newValue} at time {DateTime.Now}");
System.Threading.Thread.Sleep(1000);
}
Console.WriteLine("Exit");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.ReadKey();
}
finally
{
tcClient.Dispose();
}
}