Eigener AMS/ADS Client

Neals

Level-1
Beiträge
347
Reaktionspunkte
72
Zuviel Werbung?
-> Hier kostenlos registrieren
Hey Leute, da man beim Verwenden der angeboten ADS Dll's auf den installierten ADS Router angewiesen ist, versuche ich einen eigenen ADS Client zu schreiben. Nach der Beckhoff Doku soll das ja auch funktionieren. Wie in der ADS/AMS-Spezifikation angegeben soll das ganze als TCP-Telegramm versendet werden. Damit soll man z.B. auch unter Linux per ADS kommunizieren können, wo es ja nicht möglich ist den AMS Router zu installieren.

Nur habe ich das Problem, dass wenn ich mir den AMS Header ansehe, ich eine AMS Net ID und einen AMS Port benötige, die mein PC ja nicht hat, denn es ist ja kein AMS Router installiert.

Woher kriege ich eine AMSNetID und den AMS Port?
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Dank Dr.FunFrock, jedoch war es nicht ganz das was ich gesucht habe.

Schreibe an einem eigenen AdsClient, der über Sockets und wahlweise TCP/UDP funktioniert. Das ganze unter C# und dem Compact Framework, was ja leicht auch auf das normale Framework umkompiliert werden kann.

Die Spezifikation die Beckhoff im InfoSys freigegeben hat, habe ich voll implementiert. Um jedoch Handles zu erstellen, an bestimmte Informationen vom Router, den Status des Systems und so zu gelangen, brauch ich mehr Informationen zum Protokoll.

Habe mir schon den Reflector genommen und den Code von der Beckhoff Dll angesehen, jedoch greift die immer auf Funktionen vom instalieren AdsMessageRouter zu.

Die normalen Funktionen wie Variablen aus der SPS holen und so funktionieren, VariablenInformationen auslesen und Handles erstellen klappt auch schon. Habe mir mühsam mit Wireshark die ganzen Telegramme auseinandergenommen und versucht nachzuvollziehen. Jedoch bin ich an einem Punk angekommen, an dem ich nicht weiter komme.

Weiß vlcht jemand ne freie Implementierung die man sich ansehen kann und Informationen zum Protokoll herrausholen kann oder kenn jemand weiter Spezifikationen die ich nutzen könnte?

Bin für jede Anregung dankbar!

PS: Der Weg ist das Ziel ;-)
 
Zuletzt bearbeitet:
Wo hast du denn ein Problem, denn das ADS-Protokoll ist doch dokumentiert? Wo ist die Doku denn lückenhaft? Ansonsten hat Beckhoff auf der Supplement-CD ein Beispiel, wie man an die Symbole herankommt. Dort kann man alle Variablen und die Info per ADS aus dem Server holen.

Übrigens ein Problem war, dass ich immer hatte und weswegen ich mir eine Bibliothek baute, dass nach einem Verbindungsabbruch, die App neu gestartet werden musste, bzw. die Verbindung neu aufgebaut werden musste. Ich habe das mit einer Klassenbibliothek erledigt, die dafür einen extra Thread hat. Allerdings werden die Variablen gepollt und das geht nicht per Notification. Ein Umbau wurde mir zu heftig, weil ich dann 2 Bibliotheken benötigte. Eine für den einmaligen Zugriff auf eine Variable und die andere für den Zugriff über Notifications. Vielleicht kannste das ja einbauen und auch veröffentlichen?

Ich kann dir bei Bedarf auch den Code für den Thread zum Verbindungsaufbau schicken, weil der auf ein paar Eigenheiten von Twincat Rücksicht nimmt.
 
Das währe nett, wenn du mir den schickst...

Wegen deinen Verbindungsabbrüchen, du meinst das die TCP Verbindung gekappt wird? Ansonsten ist Ads ja nicht Verbindungsorientiert, sondern eher so wie UDP aufgebaut. Man könnte ja auch einfach einen UDP Socket anlegen.

Probleme habe ich zum Beispiel beim verwenden der Dienste.
Ich möchte die Symbol Informationen auslesen. Dazu benutzte ich die IndexGroup SymbolInfoByName = 0xf007 und den ReadWrite Command. Da fängt es schon an, was soll ich versenden innerhalb der AdsDaten? Einfach nur einen String mit dem Namen der Variablen? Wenn ich den Response erhalte, wie sind die Daten darin angeordnet?
Beispielsweise IndexGroup 4 bytes, IndexOffset 4 bytes, dann Typ 2 byte etc.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Einen Verbindungsabbruch, kann man bekommen, wenn man ein Update der SPS macht oder einfach das Kabel fehlerhaft ist.

Hier die Routine:
Code:
  Private Sub ConnectStateMachine()
        While True
            Select Case Me.ADSConnectState
                Case ADS2.ADSConnectState.ADSInitialize
                    SendConnectMsg("Initialize " + Me.ADSAdress.ToString + ":" + Me.ADSPort.ToString)
                    Me.ADSConnectState = ADS2.ADSConnectState.ADSTryConnectToPLC
                    If Not RunThreads Then
                        Me.ADSConnectState = ADS2.ADSConnectState.ADSExit
                    End If

                Case ADS2.ADSConnectState.ADSTryConnectToPLC
                    SendConnectMsg("Connecting " + Me.ADSAdress.ToString + ":" + Me.ADSPort.ToString)
                    Try
                        Connect()
                        Me.ADSConnectState = ADS2.ADSConnectState.ADSWaitOnRun
                    Catch ex As TwinCAT.Ads.AdsException
                        Me.ADSConnectState = ADS2.ADSConnectState.ADSTryConnectToPLC
                    End Try
                    If (Not RunThreads) Then
                        Me.ADSConnectState = ADS2.ADSConnectState.ADSExit
                    End If

                Case ADS2.ADSConnectState.ADSWaitOnRun
                    SendConnectMsg("Check for PLC-state RUN at " + Me.ADSAdress.ToString + ":" + Me.ADSPort.ToString)
                    Try
                        If Me.ADSClient.ReadState().AdsState = TwinCAT.Ads.AdsState.Run Then
                            Me.ADSConnectState = ADS2.ADSConnectState.ADSCheckPLCProgram
                        End If
                    Catch ex As TwinCAT.Ads.AdsException
                        Disconnect()
                        Me.ADSConnectState = ADS2.ADSConnectState.ADSTryConnectToPLC
                    End Try
                    If (Not RunThreads) Then
                        Me.ADSConnectState = ADS2.ADSConnectState.ADSExit
                    End If

                Case ADS2.ADSConnectState.ADSCheckPLCProgram
                    SendConnectMsg("Check for PLC-program at " + Me.ADSAdress.ToString + ":" + Me.ADSPort.ToString)
                    Try
                        Dim plc_version As New PLCVarString(Me, ".VERSION", 1)
                        Me.ADSConnectState = ADS2.ADSConnectState.ADSConnectFields
                        plc_version.Dispose()
                    Catch ex As TwinCAT.Ads.AdsException
                        Me.ADSConnectState = ADS2.ADSConnectState.ADSCheckPLCProgram
                    End Try
                    If (Not RunThreads) Then
                        Me.ADSConnectState = ADS2.ADSConnectState.ADSExit
                    End If

                Case ADS2.ADSConnectState.ADSConnectFields
                    SendConnectMsg("Connecting PLC vars at " + Me.ADSAdress.ToString + ":" + Me.ADSPort.ToString)
                    Try
                        Me.GetPLCVars()
                        Dim plc_version As New PLCVarString(Me, ".VERSION", 50)
                        Me.ADSConnectState = ADS2.ADSConnectState.ADSConnectFields
                        plc_version.Dispose()
                        Me.ADSConnectState = ADS2.ADSConnectState.ADSConnected

                    Catch ex As TwinCAT.Ads.AdsException
                        Me.DropPLCVars()
                        Me.ADSConnectState = ADS2.ADSConnectState.ADSCheckPLCProgram
                    End Try
                    If (Not RunThreads) Then
                        Me.ADSConnectState = ADS2.ADSConnectState.ADSExit
                    End If

                Case ADS2.ADSConnectState.ADSConnected
                    SendConnectMsg("Connected at " + Me.ADSAdress.ToString + ":" + Me.ADSPort.ToString)
                    SyncLock Me.lock
                        If Me.ADSConnectState = ADS2.ADSConnectState.ADSConnected Then
                            Try
                                If Me.ADSClient.ReadState().AdsState <> TwinCAT.Ads.AdsState.Run Then
                                    Me.ADSConnectState = ADS2.ADSConnectState.ADSWaitOnRun
                                    Me.DropPLCVars()
                                End If
                            Catch ex As TwinCAT.Ads.AdsException
                                Me.ADSConnectState = ADS2.ADSConnectState.ADSCleanUp
                            End Try
                        End If
                        If (Not RunThreads) Then
                            Me.ADSConnectState = ADS2.ADSConnectState.ADSExit
                        End If
                    End SyncLock

                Case ADS2.ADSConnectState.ADSCleanUp
                    SendConnectMsg("Clean up " + Me.ADSAdress.ToString + ":" + Me.ADSPort.ToString)
                    Me.ADSCleanUp()
                    Me.ADSConnectState = ADS2.ADSConnectState.ADSTryConnectToPLC

                Case ADS2.ADSConnectState.ADSExit
                    SendConnectMsg("Closed " + Me.ADSAdress.ToString + ":" + Me.ADSPort.ToString)
                    Me.ADSCleanUp()
                    Exit While
            End Select
            Thread.Sleep(timePLC)
        End While
    End Sub

Wie gesagt, schau einfach mal auf der Supplement-CD nach. Da war ein .Net-Beispiel!
 
Ich möchte die Symbol Informationen auslesen. Dazu benutzte ich die IndexGroup SymbolInfoByName = 0xf007 und den ReadWrite Command.

Erfahrungsgemäß kann dies bei sehr vielen Variablen zum Teil etwas lange dauern. Dafür hast Da aber immer die aktuellen Informationen direkt vom System. Alternativ könntest Du auch das TPY File verwenden, dort steht eigentlich alles drinnen. Allerdings musst Du Dich dann um ev. geänderte Variablen Handles bei Änderungen kümmern. Solange sich zumindest der Variablenname nicht ändert, klappt das. Wir sind gerade daran, einen OPC Server für Beckhoff zu basteln. (ADS)

Warum willst Du eigentlich auf den installierten ADS Router verzichten? So empfiehlt z.B. Beckhoff TwinCAT CP zu installieren, da dann die beste Funktionalität gewährleistet ist, und Du hast keinen Kopfweh.

Viel Spaß noch!
 
Darum geht es mir in erster Linie :) ausserdem lerne ich dabei noch was und vielleicht kommt am Ende ja sogar was ganz gutes dabei rum.

Beispiele:
Wenn ich einen Server habe, möchte ich da ungerne eine Echtzeitinstanz drauf installieren. Meine Software ist unabhängig von jeglicher Beckhoff-Installation. Ausserdem hat ADS ziemliche Schwierigkeiten mit Routing, was durch die Kapselung in TCP erledigt ist. Ich kann meinen Client erweitern und zusätzliche Funktionen einbauen, bin nicht mehr eingeschränkt durch die Beckhoff-Dll. Baue meine dll modular auf, das man auch ein Server implementieren könnte. Da bei Beckhoff alles auf ADS aufbaut, habe man ja extrem viele Möglichkeiten damit zu arbeiten. Die ADS Bauteile sind alle inklusive und kostenlos. Kann mir sehr leicht mit Hilfe der Datei nen Proxy zwischen z.B. ADS und ner Datenbank bauen. Dann könnte ich aus der SPS über ADS auf meine Datenbank zugreifen, lesen, schreiben. Mir fällt da viel ein was ich damit machen könnte. :rolleyes:

Warum baut ihr denn den OPC Server, den gibts doch schon: TwinCAT OPC Server.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Find' ich schon gut, dass es immer noch Idealisten wie Dich gibt, die auch noch des Spaßes wegen so was auf die Beine stellen! :s12:

Den OPC-Server machen wird deshalb, da ich beim Beckhoff OPC einiges vermisse, das ich von früher verwendeten Servern kenne, aber nicht missen möchte. (Inbetriebnahmetools, Debugging, Fehlerprotokollierung etc.) Und laut Google, gibt es für Beckhoff (via ADS wohlgemerkt) keinen anderen OPC-Server! Also müsste da doch "internationaler" Bedarf bestehen....

An Deiner Lösung hätte ich auch Interesse, vielleicht können wir uns ja austauschen?

Schönen Tag noch
 
Habe jetzt was vorläufiges fertig.
Aber ganz nett: Habe ne Methode geschrieben, mir der sich Routen auf Geräten eintragen lassen, ist ganz Hilfreich, wenn man kein TwinCAT hat :TOOL:
Wer sich das mal ansehen will, schickt einfach seine Mailadresse per PM an mich und ich schick ihm das Programm.

Habe noch Probleme bei Notifications:
Ich habe ja jetzt alles Synchron gemacht, zB:
Schicke Packet Read, warte auf Eingang von Daten, auswerten und zurückgeben.

Die Notifications kommen jetzt ja zwischendurch, entweder schreibe ich den kompletten Client Multithreaded, dann müsste man mit jeder Funktion nen Callback mitgeben, ein Thread der durchgehend eingehende Daten ließt und danach dann den richtigen Callback feuert. Das währe eh der nächste Schritt gewesen, wenn alles synchron läuft, jedoch sollten die synchronen Methoden eigendlich bestehen bleiben.

Mir fällt keine Art ein, wie ich in den synchronen Client die Notifications einbauen soll.
 
Zuletzt bearbeitet:
Richtig, du musst den synchronen Teil, vom asynchronen Teil trennen. Ist leider nicht anders zu machen. Irgendwie überschneiden sich dabei die Klassen.

Code:
Variable ---> Int ----> Access Async 
                       ----> Access Sync
Variable ---> Byte ----> Access Async 
                        ----> Access Sync
Massiver Threadeinsatz ist eigentlich nur bei der Kopplung Programm-Twincat gefragt. Mit async-Logik braucht es eigentlich keine weiteren Threads. Das hat auch später im Anwenderprogramm Vorteile, weil man da dann nicht bei den Controls darauf Rücksicht nehmen muss.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Jetzt ist auch eine Implementierung für UDP eingebaut, damit ist man nicht mehr auf TCP beschränkt... Nachteil an der Sache ist, das ich den Port von TwinCAT verwenden muss. Um an die eingehenden Daten zu kommen, muss also TwinCAT aus sein... deswegen überlege ich die UDP Lösung raus zu nehmen.

Sorry Frog, aber irgendwie hab ich nicht ganz verstanden wie du das meinst...

Ich muss ja einen Thread haben, der durchgehend Empfängt und guckt ob ne Notification da ist. Hatte überlegt, bei ner Anfrage den zu blockieren und Synchron weiter zu arbeiten. Dann sende, Daten empfangen, wenns ne Notification ist, Event feuern und nochmal empfangen... wenn Daten da sind, verarbeiten, ausgeben, Thread wieder starte.
 
Zuletzt bearbeitet:
Eine Notification arbeitet doch so, dass du eine Msg. von der SPS bekommst. Eigentlich braucht es nur einen Thread, der die Variablen aktualisiert.

Ansonsten wärst du gezwungen, für jede Notification einen Thread aufzumachen. Das dumme dabei ist, dass dann auch bei der Programmierung der GUI nicht einfach losprogammiert werden kann, sondern da bestimmte Regeln eingehalten werden müssen. Bisher habe ich mich darum drücken können, aber wenn man auf VS2008 schaut, wird das bestimmt nicht mehr möglich sein.
 
ich würdes es möglicherweise so machen

ich würde erstmal folgende einschränkungen machen:

pro thread nur eine synchronaktion - d.h. dein read, write oder notify ist blockierend

jeder thread braucht eine "connection" zu deinem receiver_thread

connection
buffer: answer_data
event: answer_received

mit irgendwas in deinen empfangsdaten musst du die connection(client)
eindeutig identifizieren und zuordnen - damit du den answer_received.event der entsprechenden connection triggern kannst

also in der art

communicator::
receiver_thread
{
buffer: data;
while ( true )
{
dein_ADS_receive( data );

// informieren die entsprechende connection
map[ data.key ].anwer_data = data;
map[ data.key ].answer_received.set(); // feedback an den client
}
}

dann kannst du schön waitforsingleobject - or whatever auf antworten für deine "connection" warten - das reduziert die menge der freifliegenden callbacks

connection = communicator::connect();

read_var test_read( connection, "welche var" );

test_read.request();
--> schickt die anfrage über die leitung
--> (merkt sich im sender_thread das nachricht erwartet wird)

test_read.wait( timeout );
--> wartet darauf das connection.answer_received
durch den reciver_thread signalisiert wird

x = test_read.value();

mit einer notifiy koennte es so aussehen

notifiy test_notfy( connection, "auf was auch immer" );

test_notify.start();

while( test_notify.wait() )
{
x = test_notiy.value();

test_notify.stop(); // nur einmal erwarten
}

communicator::disconnect( connection );

und die Anbindung an ein GUI(Konsolen) System würde ich dann darauf setzen weil das gibts viele Wege - Windows Messages, COM, QT-Slots usw.

desweiteren hast du dann in der basis eine voll mach-dein-threading-selber system das zum benutzer hin leicht auf synchrone konstruktionen ala

read_synchron( connection, var )
{
read_var var( connect, var )
var.wait();
}

verbogen werden kann

hoffe mein c++-java-pascal-artiger pseudocode ist nicht zu verwirrend
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Warum sollte man mehrere Threads aufmachen, wenn einer reicht? Sobald etwas über den die Verbindung hineinkommt und es gibt nur eine(!), müssen die entsprechenden Variablen ein Update erfahren. Hätte es pro Variable eine Verbindung gegeben, wäre ein Thread pro Variable sinnvoll gewesen.
 
ich weiss schon was du meinst

Ich hab ja auch nicht über den Teil gesprochen den der Benutzer in
die Finger bekommt - eher den Aufbau der dahinterliegenden Kommunikationsschicht - um seine (a)synchron Aktione besser(leichter) implementieren zu können ohne eine Callback-Party zu starten

noch dazu ist er dann freier in der Anbindung andere System z.b. VB würde sicher von einem COM-Frontend profitieren, QT denke eher an einer SLOT integration - und in der Konsole gibts das alles nicht - da hätte ich eher gern so einen Wait-Mechanismus

und das schöne daran ist - deine Anforderung oder Idee lässt sich damit genauso implementieren

ciao LowLevelMahn

BTW:

die connection ist keine ADS-Verbindung sondern eher ein Identifikationshandle für die client(read,write,notify) <-> receiver_thread Kommunikation
wobei Client auch wieder ein grosses Wort für ein Trivialding ist

und noch was: bist du dir bewusst das über den receive viele verschiedene pakete kommen, d.h. unsortiert read- und write-request antworten durchmischt mit notifies?
Neals Problem ist das er gerne ein Verhalten in der Leitung hätte als würde alles synchron ablaufen (vereinfach die Implemtierung sehr sehr stark) - aber trotzedem nicht die
asynchronen sachen wie die notifies (die einfach so zwischendurch das plappern anfang) mit wilden orgien da reinzupfuschen

mein Ansatz verändert mal primär das Verhalten so das sich die einzelnen Funktionen nur mit ihren Aufgaben beschäftigen müssen - von was du spricht ist schon ein Treppenstufe weiter oben

@Neals
wie kannst du die Pakete eindeutig Identifierzen ( also z.B. das Paket gehört zu Read1, das zu Write2 usw.)?
und unter welchem System entwickelst du gerade? Windows, WinAPI, C/C++
 
Zuletzt bearbeitet:
Eine Callback-Party braucht es auch nicht, wenn die Bibliothek die Werte der Variablen updatet. Ich mache es jedenfalls so mit meiner Lib.

Die Kunst besteht eher darin, die Verbindung nach einem Abbruch (wegen Kabel etc) abzubauen und wieder aufzubauen, damit der PC nicht irgendwann stehenbleibt. Du brauchst eine Liste oder ähnliches, in der die Daten zu allen Variablen gespeichert werden. Der Kommunikationsthread sorgt dann für den Abbau und den Neuaufbau.

Und COM benötigt man eigentlich kaum, denn .Net bietet soweit alles. Nur 1 oder 2 Interfaces müssen definiert werden.
 
Zurück
Oben