This is the client implementation of the Twincat Ads protocol from Beckhoff.
The implementation is in C# and targets .NET Framework 4.6.2, .NET Standard 2.0 and .NET Standard 2.1.
All communication methods are async.
First you have to give your device/machine the permission to communicate with the Twincat Ads server by adding a route.
There are different ways of doing this depending on the device. You can use the Twincat Remote Manager for example. On a CX9001 device you can connect with cerhost.exe and add a route with \Hard Disk\System\TcAmsRemoteMgr.exe (You may not to reboot after this!)
If the library is not working, an incorrect/missing route may be the problem!.
You only need this library. Twincat is not needed. It will not work if you have programs like system manager or PLC control running.
The package is available from NuGet.
using var client = new AdsClient(amsNetIdSource: "10.0.0.120.1.1", ipTarget: "10.0.0.2",
amsNetIdTarget: "10.0.0.2.1.1");
await client.Ams.ConnectAsync();
AdsDeviceInfo deviceInfo = await client.ReadDeviceInfoAsync();
Console.WriteLine(deviceInfo.ToString());
var varHandle = await client.GetSymhandleByNameAsync(".TestVar");
await client.WriteAsync<byte>(varHandle, 0);
var value = await client.ReadAsync<byte>(varHandle);
await client.ReleaseSymhandleAsync(varHandle);
You can also use the AdsCommands directly if you need to write directly with IndexGroup/IndexOffset
client.OnNotification += (sender, e) => { Console.WriteLine(e.Notification.ToString()); };
var varHandle1 = await client.GetSymhandleByNameAsync(".VarTest1");
var varHandle2 = await client.GetSymhandleByNameAsync(".VarTest2");
var notificationHandle1 = await client.AddNotificationAsync<byte>(varHandle1, AdsTransmissionMode.Cyclic, 2000, null);
var notificationHandle2 = await client.AddNotificationAsync<byte>(varHandle2, AdsTransmissionMode.OnChange, 10, null);
Here is a sample which shows usage of most basic functions.
using Viscon.Communication.Ads;
using Viscon.Communication.Ads.Common;
namespace Samples;
public static class Program
{
static async Task Main()
{
var timeout = Task.Delay(10000);
var task = await Task.WhenAny(RunTestAsync(), timeout);
if (task == timeout)
{
Console.Error.WriteLine("Operation timed out!");
}
else
{
Console.WriteLine("Done!");
}
}
private static async Task RunTestAsync()
{
using var client = new AdsClient(
amsNetIdSource:"192.168.5.6.1.1",
ipTarget:"192.168.3.4",
amsNetIdTarget:"192.168.3.4.1.1");
await client.Ams.ConnectAsync();
var deviceInfo = await client.ReadDeviceInfoAsync();
Console.WriteLine($"Device info: {deviceInfo}");
var state = await client.ReadStateAsync();
Console.WriteLine($"State: {state}");
client.OnNotification += (sender,e) => {
Console.WriteLine(e.Notification.ToString());
};
var varHandle1 = await client.GetSymhandleByNameAsync(".VariableName1");
Console.WriteLine($"Variable1 handle: {varHandle1}");
var varHandle2 = await client.GetSymhandleByNameAsync(".VariableName2");
Console.WriteLine($"Variable2 handle: {varHandle2}");
var notification1Handle = await client.AddNotificationAsync<byte>(
varHandle1, AdsTransmissionMode.Cyclic, 5000, null);
var notification2Handle = await client.AddNotificationAsync<byte>(
varHandle2, AdsTransmissionMode.OnChange, 10, null);
var value = await client.ReadAsync<byte>(varHandle1);
Console.WriteLine($"Value before write: {value}");
await client.WriteAsync<byte>(varHandle1, 1);
Console.WriteLine("I turned something on");
value = await client.ReadAsync<byte>(varHandle1);
Console.WriteLine($"Value after write: {value}");
await Task.Delay(5000);
await client.WriteAsync<byte>(varHandle1, 0);
Console.WriteLine("I turned something off");
Console.WriteLine("Deleting active notifications...");
await client.DeleteActiveNotificationsAsync();
}
}
var stateCmd = new AdsReadStateCommand();
var state = (await stateCmd.RunAsync(client.Ams, CancellationToken.None)).AdsState.ToString();
Console.WriteLine($"State: {state}");
It's possible to read directly to a class or write from a class.
You need to set the AdsSerializable attribute on the class and the Ads attribute on the fields/properties you need.
The fields without the Ads attribute are ignored.
[AdsSerializable]
public class TestClass
{
[Ads]
public ushort Var1 { get; set; }
[Ads]
public byte Var2 { get; set; }
}
var handle = await client.GetSymhandleByNameAsync(".Test");
var testInstance = await client.ReadAsync<TestClass>(handle);
await client.WriteAsync(handle, testInstance);
This is an example struct in Twincat:
TYPE TestStruct :
STRUCT
Var1 : INT;
Var2 : BYTE;
END_STRUCT
END_TYPE
These functions aren't documented by Beckhoff:
var xml = await client.Special.GetTargetDescAsync();
xml = XDocument.Parse(xml).ToString();