# Twincat 3 Ads Notification Handler Event Handler - Ereignisgesteuertes Lesen



## Neuling2014 (8 September 2014)

Hallo zusammen,

in dem InfoSystem von Beckhoff existiert ein Beispiel über das ereignisgesteuerte Lesen von Beckhoff Variablen.
http://infosys.beckhoff.com/index.p...es_net/html/twincat.ads.sample03.htm&id=15632

Quellcode heruntergeladen ausprobiert. Super!! Es läuft.
Nun habe ich versucht diesen Quellcode auf eine Consolen Application mit C# aufzusetzen.
Ohne Erfolg :-( Das Programm läuft durch, aber es springt nicht in die Event Methoden.

Hat jemand damit schon Erfahrungen gesammelt?

Danke und Grüße
Neuling2014

PS: Hier ist der richtige Link
http://infosys.beckhoff.com/index.p...es_net/html/twincat.ads.sample03.htm&id=15632
Falsch kopiert


----------



## mac203 (9 September 2014)

Hallo!

Die Methoden aus der TcAdsDll.dll haben eigentlich nichts mit der Oberfläche bzw. der Umgebung zu tun.
Ob Konsole oder Windows Form....sollte da keinen Unterschied machen.


----------



## Neuling2014 (9 September 2014)

Hallo mac203,

das dachte ich auch, aber es muß irgendeinen Unterschied geben.
Denn das Konsolen Projekt läuft nicht in die Notification Methods rein.
Hast du eine Idee wieso ?

Grüße


----------



## Larry Laffer (9 September 2014)

Hallo,
sieht dein Script denn genauso aus wie in dem verlinkten Beispiel ? 
Hast du im Form_Load-Script den Eventhandler zugewiesen ?
Ansonsten poste das doch mal bitte dein Script ...

Gruß
Larry


----------



## Neuling2014 (9 September 2014)

Hallo Larry,

das Projekt besteht aktuell nur aus der Program.cs. 
Die wie folgt aussieht:


```
namespace ConsoleTest
{
    class Program
    {

        private TcAdsClient _tcClient = null;
        private AdsStream _adsStream = null;
        private BinaryReader _binRead = null;
        private int _notificationHandle = 0;


        static void Main (string[] args)
        {
            Load();
        }

       
        private void Load()
        {
            try
            {
                _tcClient = new TcAdsClient();

                /* connect the client to the local PLC */
                _tcClient.Connect(851);

                _adsStream = new AdsStream(2);                /* stream storing the ADS state of the PLC */
                _binRead = new BinaryReader(_adsStream);    /* reader to read the state data */

                /* register callback to react on state changes of the local AMS router */
                _tcClient.AmsRouterNotification +=
                                        new AmsRouterNotificationEventHandler(AmsRouterNotificationCallback);


                _notificationHandle = _tcClient.AddDeviceNotification(
                                            (int)AdsReservedIndexGroups.DeviceData,    /* index group of the device state*/
                                            (int)AdsReservedIndexOffsets.DeviceDataAdsState, /*index offsset of the device state */
                                            _adsStream,    /* stream to store the state */
                                            AdsTransMode.OnChange,    /* transfer mode: transmit ste on change */
                                            0,    /* transmit changes immediately */
                                            0,
                                            null);

                /* register callback to react on state changes of the local PLC */
                _tcClient.AdsNotification += new AdsNotificationEventHandler(OnAdsNotification);
            }
            catch (AdsErrorException ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        /* callback function called on state changes of the PLC */
        void OnAdsNotification(object sender, AdsNotificationEventArgs e)
        {
            if (e.NotificationHandle == _notificationHandle)
            {
                AdsState plcState = (AdsState)_binRead.ReadInt16(); /* state was written to the stream */
                Console.WriteLine(plcState.ToString());
            }
        }

        /* callback function called on state changes of the local AMS router */
        void AmsRouterNotificationCallback(object sender, AmsRouterNotificationEventArgs e)
        {
            Console.WriteLine(e.State.ToString());
        }

        //private void _exitButton_Click(object sender, EventArgs e)
        //{
        //    this.Close();
        //}

        //private void Close ()
        //{
        //    try
        //    {
                
        //        if(_notificationHandle != null) _tcClient.DeleteDeviceNotification(_notificationHandle);
        //        _tcClient.Dispose();
        //    }
        //    catch(AdsErrorException ex)
        //    {
        //        MessageBox.Show(ex.Message);
        //    }
        //}
    }
}
```


----------



## MasterOhh (9 September 2014)

Du musst dein Programm am laufen halten. Entweder mit einer Schleife (while (!Abbruchbedingung) {}) oder mit einem Console.ReadKey() oder Console.ReadLine() am Ende.
Obwohl ich mir bei den letzten beiden nicht sicher bin, ob die nicht andere Events blocken während sie auf die Tasten warten. Versuch macht Kluch!


----------



## Neuling2014 (9 September 2014)

Hmm, beide Varianten funktionieren nicht while und ReadKey()


```
namespace TestNotification
{
    class Program
    {
        static void Main (string[] args)
        {

            Twincat test = new Twincat();
            test.Load();
            DateTime now = DateTime.Now;
            DateTime future = now.AddMinutes(1);

            while (now < future)
            {
                now = DateTime.Now;
            }
        }
    }

    public class Twincat
    {
        private TcAdsClient _tcClient = null;
        private AdsStream _adsStream = null;
        private BinaryReader _binRead = null;
        private int _notificationHandle = 0;
       
        public void Load()
        {
            try
            {
                _tcClient = new TcAdsClient();

                /* connect the client to the local PLC */
                _tcClient.Connect(851);

                _adsStream = new AdsStream(2);                /* stream storing the ADS state of the PLC */
                _binRead = new BinaryReader(_adsStream);    /* reader to read the state data */

                /* register callback to react on state changes of the local AMS router */
                _tcClient.AmsRouterNotification +=
                                        new AmsRouterNotificationEventHandler(AmsRouterNotificationCallback);


                _notificationHandle = _tcClient.AddDeviceNotification(
                                            (int)AdsReservedIndexGroups.DeviceData,    /* index group of the device state*/
                                            (int)AdsReservedIndexOffsets.DeviceDataAdsState, /*index offsset of the device state */
                                            _adsStream,    /* stream to store the state */
                                            AdsTransMode.OnChange,    /* transfer mode: transmit ste on change */
                                            0,    /* transmit changes immediately */
                                            0,
                                            null);

                /* register callback to react on state changes of the local PLC */
                _tcClient.AdsNotification += new AdsNotificationEventHandler(OnAdsNotification);
            }
            catch (AdsErrorException ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        /* callback function called on state changes of the PLC */
        void OnAdsNotification(object sender, AdsNotificationEventArgs e)
        {
            if (e.NotificationHandle == _notificationHandle)
            {
                AdsState plcState = (AdsState)_binRead.ReadInt16(); /* state was written to the stream */
                Console.WriteLine(plcState.ToString());
            }
        }

        /* callback function called on state changes of the local AMS router */
        void AmsRouterNotificationCallback(object sender, AmsRouterNotificationEventArgs e)
        {
            Console.WriteLine(e.State.ToString());
        }

        //private void _exitButton_Click(object sender, EventArgs e)
        //{
        //    this.Close();
        //}

        //private void Close ()
        //{
        //    try
        //    {
                
        //        if(_notificationHandle != null) _tcClient.DeleteDeviceNotification(_notificationHandle);
        //        _tcClient.Dispose();
        //    }
        //    catch(AdsErrorException ex)
        //    {
        //        MessageBox.Show(ex.Message);
        //    }
        //}
    }
}
```


----------



## Larry Laffer (9 September 2014)

@MasterOhh:
Die Form selbst hält schon alles offen. Solange du die nicht schliesst und disposed passiert dem Prozess selbst erstmal nichts.

@TE:
In der OnNotification-Methode lasst doch mal das IF weg und führe den Code darin absolut aus - eventuell auch mal einen Breakpoint setzen um zu sehen ob die Methode aufgerufen wird und von wem (Sender).
Also etwa so :
	
	



```
void OnAdsNotification(object sender, AdsNotificationEventArgs e)
        {
                AdsState plcState = (AdsState)_binRead.ReadInt16(); /* state was written to the stream */
                Console.WriteLine(plcState.ToString());
           }
```

Gruß
Larry


----------



## Neuling2014 (9 September 2014)

Hallo Larry,

habe auch schon einen Breakpoint gesetzt, aber das Consolen Programm springt überhaupt nicht in die Methode ...


----------



## Larry Laffer (9 September 2014)

Für mich ist das hier auch ein wenig Rätselraten.
Was ist mir dem hier :
	
	



```
_notificationHandle = _tcClient.AddDeviceNotification(
                                            (int)AdsReservedIndexGroups.DeviceData,    /* index group of the device state*/
                                            (int)AdsReservedIndexOffsets.DeviceDataAdsState, /*index offsset of the device state */
                                            _adsStream,    /* stream to store the state */
                                            AdsTransMode.OnChange,    /* transfer mode: transmit ste on change */
                                            0,    /* transmit changes immediately */
                                            0,
                                           [COLOR=#ff0000] null[/COLOR]);
```
In dem Beispiel wird dort der Datentyp übergeben und bei dir NULL - hast du das mal versucht ?


----------



## Larry Laffer (9 September 2014)

Was ist, wenn du mal eine Variable deines Programms abfragst ?
	
	



```
hConnect[0] = tcClient.AddDeviceNotification([COLOR=#ff0000]"MAIN.boolVal"[/COLOR],dataStream,0,1,
                                        AdsTransMode.OnChange,100,0,[COLOR=#ff0000]tbBool[/COLOR]);
```
... idealerweise sollte die eine Art Blink-Merker sein ...


----------



## Neuling2014 (9 September 2014)

Gerade getestet....
Ändert sich auch nichts. Na wie vor springt das Programm nicht in die Notifications Methode rein.

Noch andere Ideen?


----------



## Larry Laffer (10 September 2014)

Hallo,
dann müssen wir ggf. etwas weiter unten anfassen ...
In dem Beckhoff-Beispiel wird das Ganze ja auf eine Form bezogen.
Hier erfolgt die Initialisierung in der überschriebenen Methode, die beim Laden der Form automatisch aufgerufen wird.
Du hast dir ja eine eigene Klasse erstellt (was ja auch nicht verwerflich ist). Zu welchem Zeitpunkt und wie wird diese Klasse denn instanziert, und die beinhalteten Methoden aufgerufen (z.B. die Load-Methode) ?
Hast du darin schon mal einen Breakpoint gesetzt ?
Wenn das richtig aufgerufen wird, sind denn deine Objekte alle sinnvoll und auch sinnvoll zugewiesen ?

Prinzipiell würde ich dazu tendieren, dir vorzuschlagen, das Beckhoff-Beispiel genau identisch zu erstellen und wenn das funktioniert, dieses abzuwandeln. Du hast in deinem Konstrukt nach meiner Meinung zu viele Unbekannte drin (ich würde so, wie beschrieben, vorgehen um weiter zu kommen).

Gruß
Larry


----------



## soma (10 September 2014)

Das Problem liegt nicht an der SPS oder an ADS Funktionen.
sondern schlicht und ergreifend daran das dein C# Programm 
also Consolenprogramm den Message Loop nicht abarbeitet.
Die einfachst Methode  um dies zu tun,ist Application.Run() welche auch jede Winformsanwendung nutzt.
D.h. für Dich.
Twincat test = new Twincat();
test.Load();
Application.Run();


----------



## Neuling2014 (10 September 2014)

Hallo soma,

das war des Pudels Kern 
mit Application.Run(); springt er automatisch in die Notification Methode!

D.h. ich kann diese Twincat ADS Klasse nicht in einer .NET Library Class aufrufen ...
Sehr ärgerlich. Oder kennst du da auch einen Trick?

Grüße


----------



## Larry Laffer (10 September 2014)

Hallo,
das das Problem so weit unten liegt hatte ich nun nicht vermutet - man braucht ja eigentlich immer eine Anzeige - sogar und vor Allem zum Testen ...

Aber natürlich kannst du das in eine Bibliothek einbauen. Du baust dir doch darin eine Methode (oder mehrere) oder ggf. sogar eine Komponente oder ein Control, dass schlußendlich an einer Form (z.B.) laufen würden - oder in einem Thread. Es muss am Ende (in der Applikation) nur jemanden geben, der hin und wieder das Application.DoEvents anstößt ...

Gruß
Larry


----------



## Neuling2014 (10 September 2014)

Hallo,

kannst du mir ein Beispiel zeigen/schreiben, in dem das ganze in einem Thread verpackt ist?
Ich wüßte jetzt nicht, wie ich das ganze einbauen sollte.

Danke und Grüße
Neuling2014


----------



## Larry Laffer (10 September 2014)

Nein ... da ich nicht weiß, welche der Klassen Thread-fest sind (also wo du ggf. mit Invoke arbeiten mußt).
Du kannst aber eine zyklische Abfrage auch relativ einfach über die Verwendung eines Timers realisieren. Dann hast du es wieder im Haupt-Thread und arbeitest auch von daher mit Events.

Gruß
Larry


----------



## Neuling2014 (10 September 2014)

Die Idee mit dem Timer hatte ich auch schon.
Das hatte in dem Consolen Programm eingebaut. Aber das hatte nicht den gewünschten Effekt.
Die zyklische Abfrage wollte ich eigentlich vermeiden.
Hintergrund: Die Notification geht auf eine Beckhoff Variable und wenn diese sich ändern, erfolgt eine Abarbeitung im .NET Code.
Eine zyklische Abfrage würde beeinhalten einen eigenen Thread für diese Abfrage aufzusetzen. Was ich eigentlich vermeiden wollte.


----------



## Larry Laffer (10 September 2014)

Wohin soll die Reise denn überhaupt gehen ?
Vielleicht kann ich dir dann auch etwas spezifischer helfen ...

Gruß
Larry


----------



## Neuling2014 (11 September 2014)

Die TwincatADS Implementierung möchte ich in einem Project Class Library einbauen.
In der weder Consolenausgaben noch UI Elemente enthalten ist. Daher ist die Frage, wie man aus diesem Project Notifications abfangen kann von der Beckhoff....


----------



## Larry Laffer (11 September 2014)

Naja ... so richtig viel schlauer bin ich nun immer noch nicht.
In der Bibliothek selber kannst du es gar nicht anwenden (siehe Beitrag #14). Allerdings wird doch deine Bibliothek letztendlich wieder von einer Applikation benutzt - und damit hast du doch dann wieder deine Anbindung. Oder habe ich da jetzt etwas komplett falsch verstanden ?

Wie auch immer ... gib doch bitte mal ein konkretes Beispiel, was sich in der Lib befinden soll und wofür das wo eingesetzt werden soll ...

Gruß
Larry


----------



## Neuling2014 (11 September 2014)

Naja, die Twincat ADS Implementierung muß in einer eigenen Library Project drin sein, weil dort alle Gerätetreiber implementiert werden. 
So aktuell die Architektur. 
Die Gerätetreiber haben auch eigene Unit Tests, die ebenfalls ohne UI getestet werden. (Daher die Frage, wie kann man das ohne UI testen.)

Die UI an sich entwickelt jemand anders, d.h. sie steht mir nicht zur Verfügung und daher kann und *darf *ich *keine Element* von der UI verwenden.
Die Implementierung von der Twincat ADS muß in sich abgeschlossen sein.

Ist das so verständlich?


```
public class Notification
    {

        private TcAdsClient _tcClient = null;
        private AdsStream _adsStream = null;
        private BinaryReader _binRead = null;
        private int _notificationHandle = 0;

        public Notification()
        {
            System.Threading.Tasks.Task task = System.Threading.Tasks.Task.Factory.StartNew(() => Load());
            task.Start();
        }

        private void Load()
        {
            try
            {
                _tcClient = new TcAdsClient();

                /* connect the client to the local PLC */
                _tcClient.Connect(851);

                _adsStream = new AdsStream(2);                /* stream storing the ADS state of the PLC */
                _binRead = new BinaryReader(_adsStream);    /* reader to read the state data */

                /* register callback to react on state changes of the local AMS router */
                _tcClient.AmsRouterNotification +=
                                        new AmsRouterNotificationEventHandler(AmsRouterNotificationCallback);


                _notificationHandle = _tcClient.AddDeviceNotification(
                                            (int)AdsReservedIndexGroups.DeviceData,    /* index group of the device state*/
                                            (int)AdsReservedIndexOffsets.DeviceDataAdsState, /*index offsset of the device state */
                                            _adsStream,    /* stream to store the state */
                                            AdsTransMode.OnChange,    /* transfer mode: transmit ste on change */
                                            0,    /* transmit changes immediately */
                                            0,
                                            null);

                /* register callback to react on state changes of the local PLC */
                _tcClient.AdsNotification += new AdsNotificationEventHandler(OnAdsNotification);
            }
            catch (AdsErrorException ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        /* callback function called on state changes of the PLC */
        void OnAdsNotification(object sender, AdsNotificationEventArgs e)
        {
            if (e.NotificationHandle == _notificationHandle)
            {
                AdsState plcState = (AdsState)_binRead.ReadInt16(); /* state was written to the stream */
                Console.WriteLine(plcState.ToString());
            }
        }

        /* callback function called on state changes of the local AMS router */
        void AmsRouterNotificationCallback(object sender, AmsRouterNotificationEventArgs e)
        {
            Console.WriteLine(e.State.ToString());
        }
```


----------



## Larry Laffer (11 September 2014)

Ich würde mir ein Develop-Projekt erstellen, dass die DLL einbindet und sie so verwendet, wie sie der andere verwendet (oder wie du erwartest, dass er sie verwendet).
Sonst kannst du m.E. auch keinen Unit-Test machen ...


----------



## Neuling2014 (11 September 2014)

Das ist auch alles schon geschehen, sonst hätte ich erst gar nicht die Klassen von der Twincat ADS verwenden können...


----------



## Larry Laffer (11 September 2014)

... dann verstehe ich dein Problem gerade nicht.
Deine Methode / Klasse funktioniert doch anscheinend in einer Applikation - und woanders wird sie doch nie zum Einsatz kommen.


----------



## Neuling2014 (11 September 2014)

Doch doch ... meine Implementierung wird in einem großen Projekt eingebunden. Wobei die Twincat ADS Implementierung nur ein kleiner Teil des ganzen ist.
Dieser Teil kann aufgerufen und verwendet werden. Aber die Implementierung muß in sich abgeschlossen sein, d.h. incl. Notification Benachrichtigung.
D.h. nur innerhalb dieser Twincat-Klasse muß es möglich sein, den Status der Beckhoff abzufragen und falls Fehler auftreten, muß ich diese Weitergeben.
Es soll nichts von UI direkt aufgerufen werden. Dieser Programmcode läuft im Hintergrund. Unsichtbar für den Benutzer.


----------



## Larry Laffer (11 September 2014)

Das muss doch auch gar nicht.
Deine eigenen Events stehen natürlich ggf. auch nach Aussen zur Verfügung - aber deine Anbindungen bleiben doch in deiner Klasse und deine Eventhandler doch auch. 
In dem Moment, wo du es instanzierst (also irgendwo in der Applikation verwendest) und initialisierst (und das könntest du auch noch an den Konstruktor packen), fängt es sofort an zu leben - solange die Applikation responsiv ist (also nicht durch irgendwelche Funktionen geblockt wird).


----------

