Use of ADS Notifications

If values from a PLC or NC are to be displayed continuously on a user interface, then it is very inefficient to use asynchronous read access, since this function must be called cyclically (polled triggered by a timer). Instead of using a pulling (read) model, ADS Notifications are implementing a push model. That means ADS Notifications are triggered by the sender and form a single or series of ADS messages/events. Together with these ADS Notifications, values can be transmitted. A distinction is drawn between whether the TwinCAT server is to transmit the values cyclically, or only when the values change.

In principle (raw mode) a notification is begun with the registration of the notification AddDeviceNotificationAsync(asynchronous) or AddDeviceNotification (synchronous). After this, events are automatically fired by TwinCAT. DeleteDeviceNotificationAsync (asynchronous) or DeleteDeviceNotification (synchronous) is used to halt the notification again. Since the number of notifications is limited, you should ensure the notifications no longer required by your program are unregistered/deleted.

There exist several 'modes' for different type of ADS Notification triggers. For a complete list please consult AdsTransMode.

All the following examples demonstrate how to receive ADS Notifications. The .NET ADS API supports different information layers which different levels of ADS Notification support. All are using a PLC variable in the PLC and each time the value of the PLC variable changes, an ADS Notification message is sent and the registered callback method is invoked with event arguments that contain all the necessary information (value, time stamp, ...).

Hint: Don't use time intensive executions or ADS commands inside of your callback (not more than approx. 500). Remind to sync your callback in your main thread (typically the UI thread) if necessary, because the ADS Notifications appear on a background thread.

Using ADS Notifications with Symbolic information

C#

private void SymbolValueChanged()
{
    using (AdsClient client = new AdsClient())
    {
    // Connect to target
    client.Connect(AmsNetId.Local, 851);
    Symbol symbol = null;

    try
    {
        ISymbolLoader loader = SymbolLoaderFactory.Create(client, SymbolLoaderSettings.Default);
        // DINT Type (UINT32)
        symbol = (Symbol)loader.Symbols["MAIN.nCounter"];

        // Set the Notification Settings of the Symbol if NotificationSettings.Default is not appropriate
        // Check for change every 500 ms
        symbol.NotificationSettings = new NotificationSettings(AdsTransMode.OnChange, 500, 0);

        symbol.ValueChanged += Symbol_ValueChanged; // Registers the notification
        Thread.Sleep(5000); // Sleep the main thread to get some (asynchronous Notifications)
    }
    finally
    {
        // Unregister the Event and the underlying Handle
        symbol.ValueChanged -= Symbol_ValueChanged; // Unregisters the notification
    }
    }
}

private void Symbol_ValueChanged(object sender, ValueChangedEventArgs e)
{
    Symbol symbol = (Symbol)e.Symbol;

    // Object Value can be cast to int automatically, because it is an Primitive Value (DINT --> Int32).
    // The Symbol information is used internally to cast the value to its appropriate .NET Type.
    int iVal = (int)e.Value;

    // If Synchronization is needed (e.g. in Windows.Forms or WPF applications)
    // we could synchronize via SynchronizationContext into the UI Thread

    /*SynchronizationContext syncContext = SynchronizationContext.Current;
      _context.Post(status => someLabel.Text = iVal.ToString(), null); // Non-blocking post */
}

Using ADS Notifications in 'ANYTYPE' style

C#

//AdsStream readStream = new AdsStream(sizeof(UInt32));

private void ReceiveNotifications()
{
    using (AdsClient client = new AdsClient())
    {
    // Add the Notification event 'Ex' handler
    client.AdsNotificationEx += Client_AdsNotification;

    // Connect to target
    client.Connect(AmsNetId.Local, 851);
    uint notificationHandle = 0;

    try
    {
        // Notification to a ZDINT Type (UINT32)
        // Check for change every 200 ms
        notificationHandle = client.AddDeviceNotificationEx("MAIN.nCounter",new NotificationSettings(AdsTransMode.OnChange, 200, 0), null,typeof(uint));
        Thread.Sleep(5000); // Sleep the main thread to get some (asynchronous Notifications)
    }
    finally
    {
        // Unregister the Event / Handle
        client.DeleteDeviceNotification(notificationHandle);
        client.AdsNotificationEx -= Client_AdsNotification;
    }
    }
}

private void Client_AdsNotification(object sender, AdsNotificationExEventArgs e)
{
    // Or here we know about UDINT type --> can be marshalled as UINT32
    uint nCounter = (uint)e.Value;

    // If Synchronization is needed (e.g. in Windows.Forms or WPF applications)
    // we could synchronize via SynchronizationContext into the UI Thread

    /*SynchronizationContext syncContext = SynchronizationContext.Current;
      _context.Post(status => someLabel.Text = nCounter.ToString(), null); // Non-blocking post */
}

Asynchronous registering of Notifications

Trigger on changed values by ADS Notifications

private async Task RegisterNotificationsAsync()
{
    CancellationToken cancel = CancellationToken.None;

    using (AdsClient client = new AdsClient())
    {
    // Add the Notification event handler
    client.AdsNotification += Client_AdsNotification2;

    // Connect to target
    client.Connect(AmsNetId.Local, 851);
    uint notificationHandle = 0;

    // Notification to a DINT Type (UINT32)
    // Check for change every 200 ms

    //byte[] notificationBuffer = new byte[sizeof(UInt32)];
    int size = sizeof(UInt32);

    ResultHandle result = await client.AddDeviceNotificationAsync("MAIN.nCounter", size, new NotificationSettings(AdsTransMode.OnChange, 200, 0), null, cancel);

    if (result.Succeeded)
    {
        notificationHandle = result.Handle;
        await Task.Delay(5000); // Wait asynchronously without blocking the UI Thread.
                    // Unregister the Event / Handle
        ResultAds result2 = await client.DeleteDeviceNotificationAsync(notificationHandle, cancel);
    }
    client.AdsNotification -= Client_AdsNotification2;
    }    
}

private void Client_AdsNotification2(object sender, AdsNotificationEventArgs e)
{
    // Or here we know about UDINT type --> can be marshalled as UINT32
    uint nCounter = BinaryPrimitives.ReadUInt32LittleEndian(e.Data.Span);

    // If Synchronization is needed (e.g. in Windows.Forms or WPF applications)
    // we could synchronize via SynchronizationContext into the UI Thread

    /*SynchronizationContext syncContext = SynchronizationContext.Current;
      _context.Post(status => someLabel.Text = nCounter.ToString(), null); // Non-blocking post */
}

Synchronous registering of Notifications

Trigger on changed values by ADS Notifications

private void RegisterNotifications()
{
    using (AdsClient client = new AdsClient())
    {
    // Add the Notification event handler
    client.AdsNotification += Client_AdsNotification;

    // Connect to target
    client.Connect(AmsNetId.Local, 851);
    uint notificationHandle = 0;

    try
    {
        // Notification to a DINT Type (UINT32)
        // Check for change every 200 ms

        int size = sizeof(UInt32);
        //byte[] notificationBuffer = new byte[sizeof(UInt32)];

        notificationHandle = client.AddDeviceNotification("MAIN.nCounter", size, new NotificationSettings(AdsTransMode.OnChange, 200, 0), null);
        Thread.Sleep(5000); // Sleep the main thread to get some (asynchronous Notifications)
    }
    finally
    {
        // Unregister the Event / Handle
        client.DeleteDeviceNotification(notificationHandle);
        client.AdsNotification -= Client_AdsNotification;
    }
    }
}

private void Client_AdsNotification(object sender, AdsNotificationEventArgs e)
{
    // Or here we know about UDINT type --> can be marshalled as UINT32
    uint nCounter = BinaryPrimitives.ReadUInt32LittleEndian(e.Data.Span);

    // If Synchronization is needed (e.g. in Windows.Forms or WPF applications)
    // we could synchronize via SynchronizationContext into the UI Thread

    /*SynchronizationContext syncContext = SynchronizationContext.Current;
      _context.Post(status => someLabel.Text = nCounter.ToString(), null); // Non-blocking post */
}

Using reactive ADS Notifications

C#

// To Test the Observer run a project on the local PLC System (Port 851)
using (AdsClient client = new AdsClient())
{
    // Connect to target
    client.Connect(new AmsAddress(AmsNetId.Local, 851));

    // Create Symbol information
    var symbolLoader = SymbolLoaderFactory.Create(client, SymbolLoaderSettings.Default);
    IValueSymbol cycleCount = (IValueSymbol)symbolLoader.Symbols["TwinCAT_SystemInfoVarList._TaskInfo[1].CycleCount"];

    // Reactive Notification Handler
    var valueObserver = Observer.Create<object>(val =>
    {
    Console.WriteLine(string.Format("Instance: {0}, Value: {1}", cycleCount.InstancePath, val.ToString()));
    }
    );

    cycleCount.NotificationSettings = new NotificationSettings(AdsTransMode.OnChange, 500, 5000); // optional: Change NotificationSettings on Symbol

    // Turning ADS Notifications into sequences of Value Objects (Taking 20 Values)
    // and subscribe to them.
    IDisposable subscription = cycleCount.WhenValueChanged().Take(20).Subscribe(valueObserver);

    Console.ReadKey(); // Wait for Key press
    subscription.Dispose(); // Dispose the Subscription
}