Hinweise zur Dokumentation

Diese Beschreibung wendet sich ausschließlich an ausgebildetes Fachpersonal der Steuerungs- und Automatisierungstechnik, das mit den geltenden nationalen Normen vertraut ist.
Zur Installation und Inbetriebnahme der Komponenten ist die Beachtung der Dokumentation und der nachfolgenden Hinweise und Erklärungen unbedingt notwendig.
Das Fachpersonal ist verpflichtet, für jede Installation und Inbetriebnahme die zu dem Zeitpunkt veröffentlichte Dokumentation zu verwenden.

Das Fachpersonal hat sicherzustellen, dass die Anwendung bzw. der Einsatz der beschriebenen Produkte alle Sicherheitsanforderungen, einschließlich sämtlicher anwendbaren Gesetze, Vorschriften, Bestimmungen und Normen erfüllt.

Disclaimer

Diese Dokumentation wurde sorgfältig erstellt. Die beschriebenen Produkte werden jedoch ständig weiter entwickelt.
Wir behalten uns das Recht vor, die Dokumentation jederzeit und ohne Ankündigung zu überarbeiten und zu ändern.
Aus den Angaben, Abbildungen und Beschreibungen in dieser Dokumentation können keine Ansprüche auf Änderung bereits gelieferter Produkte geltend gemacht werden.

Marken

Beckhoff®, TwinCAT®, EtherCAT®, EtherCAT G®, EtherCAT G10®, EtherCAT P®, Safety over EtherCAT®, TwinSAFE®, XFC®, XTS® und XPlanar® sind eingetragene und lizenzierte Marken der Beckhoff Automation GmbH.
Die Verwendung anderer in dieser Dokumentation enthaltenen Marken oder Kennzeichen durch Dritte kann zu einer Verletzung von Rechten der Inhaber der entsprechenden Bezeichnungen führen.

Patente

Die EtherCAT Technologie ist patentrechtlich geschützt, insbesondere durch folgende Anmeldungen und Patente:
EP1590927, EP1789857, EP1456722, EP2137893, DE102015105702
mit den entsprechenden Anmeldungen und Eintragungen in verschiedenen anderen Ländern.

EtherCAT® ist eine eingetragene Marke und patentierte Technologie lizenziert durch die Beckhoff Automation GmbH, Deutschland.

Copyright

© Beckhoff Automation GmbH & Co. KG, Deutschland.
Weitergabe sowie Vervielfältigung dieses Dokuments, Verwertung und Mitteilung seines Inhalts sind verboten, soweit nicht ausdrücklich gestattet.
Zuwiderhandlungen verpflichten zu Schadenersatz. Alle Rechte für den Fall der Patent-, Gebrauchsmuster- oder Geschmacksmustereintragung vorbehalten.


Übersicht

TwinCAT ADS .NET

Beschreibung
Einbinden in TwinCAT3 (C#)
Einbinden in Microsoft Visual Studio (C#)
Einbinden in Microsoft Visual Studio (VB)
Einbinden in Borland Developer Studio 2006 (Delphi for .NET)
Einbinden in Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET)

Beschreibung | Visual C# | Visual Basic (for .NET Framework) | Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Delphi for .NET (Borland Developer Studio 2006)
Beispiel 1: Zugriff auf ein Array in der SPS | Sample01.zip | Sample01.exe | Sample01.exe | Sample01.exe
Beispiel 2: Übertragen von Strukturen an die SPS | Sample02.zip | Sample02.exe | Sample02.exe | Sample02.exe
Beispiel 3: Ereignisgesteuertes Lesen | Sample03.zip | Sample03.exe | Sample03.exe | Sample03.exe
Beispiel 4: Lesen und Schreiben von String Variablen | Sample04.zip | Sample04.exe TwinCAT.Ads.NET v1.0.0.10 und höher: Sample04b.exe | Sample04.exe | Sample04.exe
Beispiel 5: Lesen und Schreiben von DATE/TIME-Variablen | Sample05.zip | Sample05.exe | Sample05.exe | Sample05.exe
Beispiel 6: SPS Variablen-Deklaration auslesen | Sample06.zip | Sample06.exe | Sample06.exe | Sample06.exe
Beispiel 7: Lesen und Schreiben von SPS Variablen eines beliebigen Typs | Sample07.zip | Sample07.exe | Sample07.exe | Sample07.exe
Beispiel 8: Statusänderung vom TwinCAT-Router und der SPS erkennen | Sample08.zip | - | Sample08.exe | -
Beispiel 9: ADS-Summenkommando: Lesen oder Schreiben von mehreren Variablen | Sample09.zip | - | Sample09.exe | -
Beispiel 10: Auslesen von SMB-Werten aus dem TwinCAT I/O-Treiber | - | Sample10.exe | - | -
Beispiel 11: Handle einer SPS Variablen löschen | Sample11.zip | - | Sample11.exe | -
Beispiel 12: Merker synchron aus der SPS lesen | Sample12.zip | - | Sample12.exe | -
Beispiel 13: Merker synchron in die SPS schreiben | Sample13.zip | - | Sample13.exe | -
Beispiel 14: SPS starten/stoppen | Sample14.zip | - | Sample14.exe | -
Beispiel 15: Zugriff per Variablenname | Sample15.zip | - | Sample15.exe | -

TwinCAT ADS Webservice

Beschreibung | Beispielcode
Sample 1: Consumer in C# zum Lesen und Schreiben von SPS-Variablen | Sample01.exe
Sample 1: Consumer in Delphi 8 for .NET zum Lesen und Schreiben von SPS-Variablen | Sample01.exe


ADS Return Codes

Error codes: 0x000..., 0x500..., 0x700..., 0x1000..., 0x274C...

Global Error Codes

Hex | Dec | Description | Possible Causes | Solution
0x0 | 0 | no error |  |
0x1 | 1 | Internal error |  |
0x2 | 2 | No Rtime |  |
0x3 | 3 | Allocation locked memory error |  |
0x4 | 4 | Insert mailbox error | No ADS mailbox was available to process this message. | Reduce the number of ADS calls (e.g ADS-Sum commands or Max Delay Parameter)
0x5 | 5 | Wrong receive HMSG |  |
0x6 | 6 | target port not found | ADS Server not started |
0x7 | 7 | target machine not found | Missing ADS routes |
0x8 | 8 | Unknown command ID |  |
0x9 | 9 | Bad task ID |  |
0xA | 10 | No IO |  |
0xB | 11 | Unknown ADS command |  |
0xC | 12 | Win 32 error |  |
0xD | 13 | Port not connected |  |
0xE | 14 | Invalid ADS length |  |
0xF | 15 | Invalid AMS Net ID |  |
0x10 | 16 | Low Installation level |  |
0x11 | 17 | No debug available |  |
0x12 | 18 | Port disabled |  |
0x13 | 19 | Port already connected |  |
0x14 | 20 | ADS Sync Win32 error |  |
0x15 | 21 | ADS Sync Timeout |  |
0x16 | 22 | ADS Sync AMS error |  |
0x17 | 23 | ADS Sync no index map |  |
0x18 | 24 | Invalid ADS port |  |
0x19 | 25 | No memory |  |
0x1A | 26 | TCP send error |  |
0x1B | 27 | Host unreachable |  |
0x1C | 28 | Invalid AMS fragment |  |

Router Error Codes

Hex | Dec | Description | Possible Causes | Solution
0x500 | 1280 | ROUTERERR_NOLOCKEDMEMORY | No locked memory can be allocated |
0x501 | 1281 | ROUTERERR_RESIZEMEMORY | The size of the router memory could not be changed |
0x502 | 1282 | ROUTERERR_MAILBOXFULL | The mailbox has reached the maximum number of possible messages. The current sent message was rejected | Check the connection between the communication partners
0x503 | 1283 | ROUTERERR_DEBUGBOXFULL | The mailbox has reached the maximum number of possible messages. The sent message will not be displayed in the debug monitor | Check the connection to the debug monitor
0x504 | 1284 | ROUTERERR_UNKNOWNPORTTYPE | The port type is unknown |
0x505 | 1285 | ROUTERERR_NOTINITIALIZED | Router is not initialised |
0x506 | 1286 | ROUTERERR_PORTALREADYINUSE | The desired port number is already assigned |
0x507 | 1287 | ROUTERERR_NOTREGISTERED | Port not registered |
0x508 | 1288 | ROUTERERR_NOMOREQUEUES | The maximum number of Ports reached |
0x509 | 1289 | ROUTERERR_INVALIDPORT | The port is invalid. |
0x50A | 1290 | ROUTERERR_NOTACTIVATED | TwinCAT Router not active |
0x50B | 1291 | ROUTERERR_FRAGMENTBOXFULL |  |
0x50C | 1292 | ROUTERERR_FRAGMENTTIMEOUT |  |
0x50D | 1293 | ROUTERERR_TOBEREMOVED |  |

General ADS Error Codes

Hex | Dec | Description | Possible Causes | Solution
0x700 | 1792 | error class <device error> |  |
0x701 | 1793 | Service is not supported by server |  |
0x702 | 1794 | invalid index group |  |
0x703 | 1795 | invalid index offset |  |
0x704 | 1796 | reading/writing not permitted |  |
0x705 | 1797 | parameter size not correct |  |
0x706 | 1798 | invalid parameter value(s) |  |
0x707 | 1799 | device is not in a ready state |  |
0x708 | 1800 | device is busy |  |
0x709 | 1801 | invalid context (must be in Windows) |  |
0x70A | 1802 | out of memory |  |
0x70B | 1803 | invalid parameter value(s) |  |
0x70C | 1804 | not found (files, ...) |  |
0x70D | 1805 | syntax error in command or file |  |
0x70E | 1806 | objects do not match |  |
0x70F | 1807 | object already exists |  |
0x710 | 1808 | symbol not found |  |
0x711 | 1809 | symbol version invalid | Onlinechange | Release handle and get a new one
0x712 | 1810 | server is in invalid state |  |
0x713 | 1811 | AdsTransMode not supported |  |
0x714 | 1812 | Notification handle is invalid | Onlinechange | Release handle and get a new one
0x715 | 1813 | Notification client not registered |  |
0x716 | 1814 | no more notification handles |  |
0x717 | 1815 | size for watch too big |  |
0x718 | 1816 | device not initialized |  |
0x719 | 1817 | device has a timeout |  |
0x71A | 1818 | query interface failed |  |
0x71B | 1819 | wrong interface required |  |
0x71C | 1820 | class ID is invalid |  |
0x71D | 1821 | object ID is invalid |  |
0x71E | 1822 | request is pending |  |
0x71F | 1823 | request is aborted |  |
0x720 | 1824 | signal warning |  |
0x721 | 1825 | invalid array index |  |
0x722 | 1826 | symbol not active | Onlinechange | Release handle and get a new one
0x723 | 1827 | access denied |  |
0x724 | 1828 | missing license |  | Activate license for TwinCAT 3 function
0x72c | 1836 | exception occured during system start |  | Check each device transistions
0x740 | 1856 | Error class <client error> |  |
0x741 | 1857 | invalid parameter at service |  |
0x742 | 1858 | polling list is empty |  |
0x743 | 1859 | var connection already in use |  |
0x744 | 1860 | invoke ID in use |  |
0x745 | 1861 | timeout elapsed |  | Check ADS routes of sender and receiver and your firewall setting
0x746 | 1862 | error in win32 subsystem |  |
0x747 | 1863 | Invalid client timeout value |  |
0x748 | 1864 | ads-port not opened |  |
0x750 | 1872 | internal error in ads sync |  |
0x751 | 1873 | hash table overflow |  |
0x752 | 1874 | key not found in hash |  |
0x753 | 1875 | no more symbols in cache |  |
0x754 | 1876 | invalid response received |  |
0x755 | 1877 | sync port is locked |  |

RTime Error Codes

Hex | Dec | Description | Possible Causes
0x1000 | 4096 | RTERR_INTERNAL | Internal fatal error in the TwinCAT real-time system
0x1001 | 4097 | RTERR_BADTIMERPERIODS | Timer value not vaild
0x1002 | 4098 | RTERR_INVALIDTASKPTR | Task pointer has the invalid value ZERO
0x1003 | 4099 | RTERR_INVALIDSTACKPTR | Task stack pointer has the invalid value ZERO
0x1004 | 4100 | RTERR_PRIOEXISTS | The demand task priority is already assigned
0x1005 | 4101 | RTERR_NOMORETCB | No more free TCB (Task Control Block) available. Maximum number of TCBs is 64
0x1006 | 4102 | RTERR_NOMORESEMAS | No more free semaphores available. Maximum number of semaphores is 64
0x1007 | 4103 | RTERR_NOMOREQUEUES | No more free queue available. Maximum number of queue is 64
0x1008 | 4104 | TwinCAT reserved. |
0x1009 | 4105 | TwinCAT reserved. |
0x100A | 4106 | TwinCAT reserved. |
0x100B | 4107 | TwinCAT reserved. |
0x100C | 4108 | TwinCAT reserved. |
0x100D | 4109 | RTERR_EXTIRQALREADYDEF | An external synchronisation interrupt is already applied
0x100E | 4110 | RTERR_EXTIRQNOTDEF | No external synchronsiation interrupt applied
0x100F | 4111 | RTERR_EXTIRQINSTALLFAILED | The apply of the external synchronisation interrupt failed
0x1010 | 4112 | RTERR_IRQLNOTLESSOREQUAL | Call of a service function in the wrong context
0x1017 | 4119 | RTERR_VMXNOTSUPPORTED | Intel VT-x extension is not supported.
0x1018 | 4120 | RTERR_VMXDISABLED | Intel VT-x extension is not enabled in BIOS.
0x1019 | 4121 | RTERR_VMXCONTROLSMISSING | Missing feature in Intel VT-x extension.
0x101A | 4122 | RTERR_VMXENABLEFAILS | Enabling Intel VT-x fails.

TCP Winsock Error Codes

Hex | Dec | Description | Possible Causes | Solution
0x274c | 10060 | A socket operation was attempted to an unreachable host | Host unreachable | Check network connection via ping
0x274d | 10061 | A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond. | Host unreachable | Check network connection via ping
0x2751 | 10065 | No connection could be made because the target machine actively refused it |  |
 |  | Further Winsock error codes: Win32 Error Codes |  |


TwinCAT.Ads in Visual Studio .NET (C#)

TwinCAT ADS .NET Component

Linking into TwinCAT3 (C#)

Creating new project

Start Microsoft Visual Studio and create new project (Windows Forms Application).

Adding reference

In order to select the TwinCAT.Ads class library you must choose the command Add Reference... under the Project menu. You will find the .Net Library per default in following TwinCAT folder:

C:\TwinCAT3\AdsApi\.NET\v2.0.50727

This opens the Add Reference dialog:

In this dialog you have to press the Browse button and select the file TwinCAT.Ads.dll. In the Solution Explorer you can check, if the component has been added to the list of references.

All accessible types (classes,structures ...) belong to the namespace TwinCAT.Ads. Therefore one has to insert the following line at the beginning of the source :

using System.IO, TwinCAT.Ads;

This enables access to the types defined in TwinCAT.Ads without including the name of the namespace. The class TcAdsClient is the core of the TwinCAT.Ads class library and enables the user to communicate with an ads device. To begin with an instance of the class must be created. Then a connection to the ADS device is established by means of the Connect method.

Establish a connection to an ADS device:

TcAdsClient tcAds = new TcAdsClient();
tcAds.Connect("127.0.0.1.1.1",851);

The data is transmitted with the help of the AdsStream class, that inherits form System.IO.MemoryStream. To write data to the stream one can utilize the System.IO.BinaryWriter class and the System.IO.BinaryReader class is necessary to read data from the stream.

Reading a PLC variable:

// creates a stream with a length of 4 byte
AdsStream ds = new AdsStream(4);
BinaryReader br = new BinaryReader(ds);

// reads a DINT from PLC
tcAds.Read(0x4020,0,ds);

ds.Position = 0;
textBox1.Text = br.ReadInt32().ToString();

Writing to a PLC variable:

// creates a stream with a length of 4 byte
AdsStream ds = new AdsStream(4);
BinaryWriter bw = new BinaryWriter(ds);

ds.Position = 0;
bw.Write((int) 100);

// writes a DINT to PLC
tcAds.Write(0x4020,0,ds);

Disconnect a connection to an ADS device:

tcAds.Dispose();

PLC Program:

PROGRAM MAIN
VAR
    test AT%MD0 : DINT;
END_VAR
test := test + 1;


TwinCAT.Ads in Visual Studio .NET (C#)

TwinCAT ADS .NET Komponente

Einbinden in Microsoft Visual Studio .NET (C#)

Neues Projekt erstellen

Erstellen Sie im Microsoft Visual Studio ein neues Projekt (Windows Forms Application).

Referenz hinzufügen

Um die Klassenbibliothek TwinCAT.Ads zu selektieren müssen Sie das Kommando Add Reference... des Project -Menus auswählen. Dies öffnet den Add Reference Dialog.

In diesem Dialog müssen Sie den Browse Button betätigen und die Datei TwinCAT.Ads.dll auswählen. Im Solution Explorer kann man überprüfen, ob die Komponente zu der Liste der Referenzen hinzugefügt worden ist.

Alle Typen der Klassenbibliothek gehören zum Namespace TwinCAT.Ads. Deshalb muss man folgende Zeilen am Anfang des Quelltextes anfügen:

using System.IO, TwinCAT.Ads;

Dies ermöglicht den Zugriff auf die in der TwinCAT.Ads definierten Datentype ohne jedes mal den Namespace anzugeben. Die Klasse TcAdsClient ist der Kern der TwinCAT.Ads Klassenbibliothek und ermöglicht es dem Anwender mit einem Ads-Gerät zu kommunizieren. Als erstes muss eine Instanz der Klasse TcAdsClient erzeugt werden. Dann wird mit Hilfe der Connect Methode eine Verbindung mit dem Ads-Gerät hergestell.

Eine Verbindung zu einem Ads-Gerät herstellen:

TcAdsClient tcAds = new TcAdsClient();
tcAds.Connect("172.16.3.217.1.1",801);

Die Daten werden mit Hilfe der AdsStream Klasse, die von System.IO.MemoryStream abgeleitet, übermittelt. Um Daten in Stream zu schreiben, kann die Klasse System.IO.BinaryWriter verwendet werden, und für das Lesen der Daten aus dem Stream ist die Klasse Klasse System.IO.BinaryReader verantwortlich.

Lesen einer SPS-Variablen:

// creates a stream with a length of 4 byte
AdsStream ds = new AdsStream(4);
BinaryReader br = new BinaryReader(ds);

// reads a DINT from PLC
tcAds.Read(0x4020,0,ds);

ds.Position = 0;
textBox1.Text = br.ReadInt32().ToString();

Schreiben einer SPS-Variablen:

// creates a stream with a length of 4 byte
AdsStream ds = new AdsStream(4);
BinaryWriter bw = new BinaryWriter(ds);

ds.Position = 0;
bw.Write((int) 100);

// writes a DINT to PLC
tcAds.Write(0x4020,0,ds);

Eine Verbindung zu einem Ads-Gerät trennen:

tcAds.Dispose();

SPS-Programm:

PROGRAM MAIN
VAR
    test AT%MD0 : DINT;
END_VAR
test := test + 1;


TwinCAT.Ads in Visual Studio .NET (VB)

TwinCAT ADS .NET Komponente

Einbinden in Microsoft Visual Studio .NET (VB)

Neues Projekt erstellen

Die TwinCAT.Ads-Komponente kann nur in ein existierendes Projekt eingebunden werden. Starten Sie Visual StudioS und wählen Sie aus dem Menü das Kommando: File->New->Project...

Im New Project-Dialog wählen Sie bitte als Projekttyp: Visual Basic->Windows Forms Application.

Referenz hinzufügen

Um die Klassenbibliothek TwinCAT.Ads zu selektieren müssen Sie das Kommando Add Reference... des Project -Menus auswählen. Dies öffnet den Add Reference Dialog.
Alternativ können Sie auch mit einem Doppelklick auf My Project (1) die Projekt-Eigenschaften öffnen und dort unter References (2) den den Add...-Button (3) betätigen.

In diesem Dialog müssen Sie den Browse-Reiter auswählen und dort zu der Datei TwinCAT.Ads.dll navigieren. Bestätigen Sie dann die ausgewählte dll mit OK. Standardmäßig befindet sich die TwinCAT.Ads-Komponente im ..TwinCAT\Ads Api\.NET\-Ordner.

Namespace hinzufügen

Alle Typen der Klassenbibliothek gehören zum Namespace TwinCAT.Ads. Deshalb müssen folgende Zeilen am Anfang des Quelltextes anfügt werden:

Imports TwinCAT.Ads
Imports System.IO

Dies ermöglicht den Zugriff auf die in der TwinCAT.Ads definierten Datentypen ohne jedes mal den Namespace anzugeben. Die Klasse TcAdsClient ist der Kern der TwinCAT.Ads Klassenbibliothek und ermöglicht es dem Anwender mit einem ADS-Gerät zu kommunizieren. Als erstes muss eine Instanz der Klasse TcAdsClient erzeugt werden. Dann wird mit Hilfe der Connect Methode eine Verbindung mit dem ADS-Gerät hergestellt.

Eine Verbindung zu einem Ads-Gerät herstellen:

Private tcAds As TcAdsClient
...

tcAds = New TcAdsClient
tcAds.Connect("1.2.3.4.1.1", 801)

...

Die Daten werden mit Hilfe der AdsStream Klasse, die von System.IO.MemoryStream abgeleitet, übermittelt. Um Daten in Stream zu schreiben, kann die Klasse System.IO.BinaryWriter verwendet werden, und für das Lesen der Daten aus dem Stream ist die Klasse System.IO.BinaryReader verantwortlich.

Lesen einer SPS-Variablen:

' creates a stream with a length of 4 byte
Dim ds = New AdsStream(4)
Dim br = New BinaryReader(ds)

' reads a DINT from PLC
tcAds.Read(&H4020, 0, ds)
ds.Position = 0
TextBox1.Text = br.ReadInt32().ToString()

Schreiben einer SPS-Variablen:

' creates a stream with a length of 4 byte
Dim ds = New AdsStream(4)
Dim bw = New BinaryWriter(ds)

ds.Position = 0
bw.Write(New Integer = 100)

' writes a DINT to PLC
tcAds.Write(&H4020, 0, ds)

Eine Verbindung zu einem Ads-Gerät trennen:

tcAds.Dispose();

SPS-Programm:

PROGRAM MAIN
VAR
    test AT%MD0 : DINT;
END_VAR
test := test + 1


Delphi Beispiele: TwinCAT.Ads in BDS2006

TwinCAT ADS .NET Komponente

Einbinden in Borland Developer Studio 2006

Neues Projekt erstellen

Erstellen Sie im Borland Developer Studio ein neues Projekt (Windows Forms Application).

Referenz hinzufügen

Um die Klassenbibliothek TwinCAT.ADS zu selektieren müssen Sie das Kommando Add Reference... des Project -Menüs auswählen. Dies öffnet den Add Reference Dialog.

In diesem Dialog müssen Sie den Browse... Button betätigen und die Datei TwinCAT.Ads.dll auswählen.

Namespace hinzufügen

Im Project Manager kann man überprüfen, ob die Komponente zu der Liste der Referenzen hinzugefügt worden ist.

Alle Typen der Klassenbibliothek gehören zum Namespace TwinCAT.ADS. Dieses Namespace muss im im uses-Abschnitt hinzugefügt werden:

uses
    System.Drawing, System.Collections, System.ComponentModel,
    System.Windows.Forms, System.Data, System.IO, TwinCAT.Ads;

Dies ermöglicht den Zugriff auf die in der TwinCAT.ADS definierten Datentypen ohne jedes mal den Namespace angeben zu müssen. Die Klasse TcAdsClient ist der Kern der TwinCAT.ADS Klassenbibliothek und ermöglicht es dem Anwender mit einem Ads-Gerät zu kommunizieren. Als erstes muss eine Instanz der Klasse TcAdsClient erzeugt werden. Dann wird mit Hilfe der Connect Methode eine Verbindung mit dem ADS-Gerät hergestellt.

Eine Verbindung zu einem ADS-Gerät herstellen:

var	tcAds : TcAdsClient;
procedure TWinForm.Connect();
begin
    tcAds := TcAdsClient.Create();
    tcAds.Connect('172.16.3.217.1.1', 801);
end;

Lesen einer SPS-Variablen:

Die Daten (4 Byte integer im Merkerbereich) werden mit Hilfe der AdsStream Klasse, die von System.IO.MemoryStream abgeleitet werden, übermittelt. Um Daten in Stream zu schreiben, kann die Klasse System.IO.BinaryWriter verwendet werden, und für das Lesen der Daten aus dem Stream ist die Klasse Klasse System.IO.BinaryReader verantwortlich.

procedure TWinForm.ReadVar();
var 	ds : AdsStream;
    br : BinaryReader;
begin
    // creates a stream with a length of 4 byte
    ds := AdsStream.Create(4);
    br := BinaryReader.Create(ds);

    // reads a DINT from PLC
    tcAds.Read($4020,0,ds);

    ds.Position := 0;
    textBox1.Text := br.ReadInt32().ToString();
end;

Schreiben einer SPS-Variablen:

procedure TWinForm.WriteVar();
var 	ds : AdsStream;
    bw : BinaryWriter;
begin
    // creates a stream with a length of 4 byte
    ds := AdsStream.Create(4);
    bw := BinaryWriter.Create(ds);

    ds.Position := 0;
    bw.Write( Int32(100));

    // writes a DINT to PLC
    tcAds.Write($4020,0,ds);
end;

Eine Verbindung zu einem ADS-Gerät trennen:

procedure TWinForm.Disconnect();
begin
    tcAds.Dispose();
end;

SPS Programm:

PROGRAM MAIN
VAR
    test AT%MD0 : DINT;
END_VAR
test := test + 1;


Delphi Prism Beispiele: TwinCAT.Ads in VS2010

TwinCAT ADS .NET Komponente

Einbinden in Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET)

Embarcadero Prism XE2 = Oxygene for .NET

Delphi Prism verwendet als IDE das Microsoft Visual Studio 2010 und integriert sich dort bei einer bereits vorhandenen Installation von VS. Die Visual Studio IDE wird mit der Delphi Prism Installation mitgeliefert wenn sie noch nicht vorhanden ist.

Neues Projekt erstellen

Die TwinCAT.ADS-Komponente kann nur in ein existierendes Projekt eingebunden werden. Starten Sie VS und wählen Sie aus dem Menü das Kommando: File->New->Project...

Im New Project-Dialog wählen Sie bitte als Projekttyp: Delphi Prism->Windows Forms Application.

Referenz hinzufügen

Um die Klassenbibliothek TwinCAT.ADS zu selektieren müssen Sie das Kommando Add Reference... des Project -Menüs auswählen, dies öffnet den Add Reference Dialog.

In diesem Dialog müssen Sie den Browse-Reiter auswählen und dort zu der Datei TwinCAT.ADS.dll navigieren. Bestätigen Sie dann die ausgewählte dll mit Add. und OK. Standardmäßig befindet sich die TwinCAT.ADS-Komponente im Ordner ..TwinCAT\Ads Api\.NET\ .

Namespace hinzufügen

Im Project Manager können Sie überprüfen, ob die Komponente zu der Liste der Referenzen hinzugefügt worden ist.

Alle Typen der Klassenbibliothek gehören zum Namespace TwinCAT.ADS. Dieses Namespace muss im uses-Abschnitt hinzugefügt werden:

namespace AdsNETSample;'
interface

uses
  System.Drawing,
  System.Collections,
  System.Collections.Generic,
  System.Windows.Forms,
  System.ComponentModel,
  System.IO,
  TwinCAT.Ads;

Dies ermöglicht den Zugriff auf die in der TwinCAT.ADS definierten Datentypen ohne jedes mal den Namespace angeben zu müssen. Die Klasse TcAdsClient ist der Kern der TwinCAT.ADS Klassenbibliothek und ermöglicht es dem Anwender mit einem ADS-Gerät zu kommunizieren. Als erstes muss eine Instanz der Klasse TcAdsClient erzeugt werden. Dann wird mit Hilfe der Connect Methode eine Verbindung mit dem ADS-Gerät hergestellt.

Eine Verbindung zu einem ADS-Gerät herstellen:

type

//...

  MainForm = partial class(System.Windows.Forms.Form)
  private
    tcAds : TcAdsClient;
    method MainForm_Load(sender: System.Object; e: System.EventArgs);
    method MainForm_FormClosing(sender: System.Object; e: System.Windows.Forms.FormClosingEventArgs);
    method ReadVar_Click(sender: System.Object; e: System.EventArgs);
    method WriteVar_Click(sender: System.Object; e: System.EventArgs);
  protected
    method Dispose(disposing: Boolean); override;
  public
    constructor;
  end;

implementation
//...
ethod MainForm.MainForm_Load(sender: System.Object; e: System.EventArgs);
begin
    tcAds := new TcAdsClient();
    tcAds.Connect('1.2.3.4.1.1', 801);
end;

Lesen einer SPS-Variablen:

Die Daten (4 Byte integer im Merkerbereich) werden mit Hilfe der AdsStream Klasse, die von System.IO.MemoryStream abgeleitet werden, übermittelt. Um Daten in Stream zu schreiben, kann die Klasse System.IO.BinaryWriter verwendet werden, und für das Lesen der Daten aus dem Stream ist die Klasse Klasse System.IO.BinaryReader verantwortlich.

method MainForm.ReadVar_Click(sender: System.Object; e: System.EventArgs);
begin
    // creates a stream with a length of 4 byte
    var ds := new AdsStream(4);
    var br := new BinaryReader(ds);
    // reads a DINT from PLC
    tcAds.Read($4020,0,ds);
    ds.Position := 0;
    textBox1.Text := br.ReadInt32().ToString();
end;

Schreiben einer SPS-Variablen:

method MainForm.WriteVar_Click(sender: System.Object; e: System.EventArgs);
begin
    // creates a stream with a length of 4 byte
    var ds := new AdsStream(4);
    var bw := new BinaryWriter(ds);
    ds.Position := 0;
    bw.Write( Int32(100));
    // writes a DINT to PLC
    tcAds.Write($4020,0,ds);
end;

Eine Verbindung zu einem ADS-Gerät trennen:

method MainForm.MainForm_FormClosing(sender: System.Object; e: System.Windows.Forms.FormClosingEventArgs);
begin
    tcAds.Dispose();
end;

SPS Programm:

PROGRAM MAIN
VAR
    test AT%MD0 : DINT;
END_VAR
test := test + 1;


Accessing an array in the PLC

TwinCAT ADS .NET Komponente: Beispiele

Zugriff auf ein Array in der SPS

Download

Sprache / IDE | Beispielprogram auspacken
Visual C# | Sample01.zip
Visual Basic (for .NET Framework) | Sample01.exe
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample01.exe
Delphi for .NET (Borland Developer Studio 2006) | Sample01.exe

Aufgabe

In der SPS befindet sich ein Array, welches von einer .NET Applikation mit einem Lesebefehl ausgelesen werden soll.

Beschreibung

In der SPS befindet sich ein Array mit 100 Elementen vom Typ Integer (2 Byte). Das Array wird in der SPS mit den Werten 3500 bis 3599 aufgefüllt.

In der Event-Methode Form1_Load wird eine neue Instanz der Klasse TcAdsClient kreiert. Dann wird die Methode TcAdsClient.Connect des TcAdsClient Objektes aufgerufen, um eine Verbindung zum Port 801 zu erstellen. Zum Schluss wird dann die Methode TcAdsClient.CreateVariableHandle aufgerufen, um sich ein Handle auf die SPS-Variable zu besorgen. Wenn das Programm beendet wird, wird das Handle in der Event-Methode Form1_Closing wieder freigegeben und die Dispose-Methode des TcAdsClient Objektes wird aufgerufen.

Wenn der Anwender den Button betätigt, wird das gesamte Array mit Hilfe der Methode TcAdsClient.Read aus der SPS in ein AdsStream Objekt eingelesen. Der Stream sollte genauso groß wie die Daten in der SPS sein. Da wir 100 INTs (je 2 Bytes) lesen wollen, muss der Stream mindestens eine Größe von 2 * 100 Bytes haben.

Die Klasse System.IO.BinaryReader ist notwendig, um die einzelnen Felder des Arrays aus dem Stream lesen. Dazu wird als erstes die Position des Streams auf 0 gesetzt. Dann können die einzelnen Felder des Arrays mit Hilfe der Methode BinaryReader.ReadInt16 in einer for-Schleife ausgelesen werden. Der Positionszeiger des Streams wird automatisch um die Anzahl der gelesenen Bytes erhöht.

C# Programm

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using TwinCAT.Ads;
using System.IO;

namespace Sample01
{
    public class Form1 : System.Windows.Forms.Form
    {
        private System.Windows.Forms.Button btnRead;
        private System.ComponentModel.Container components = null;
        private System.Windows.Forms.ListBox lbArray;

        private int hVar;
        private TcAdsClient tcClient;

        public Form1()
        {
            InitializeComponent();
        }

        protected override void Dispose( bool disposing ) ...
        private void InitializeComponent() ...

        [STAThread]
        static void Main()
        {
            Application.Run(new Form1());
        }

        private void Form1_Load(object sender, System.EventArgs e)
        {
            // Instanz der Klasse TcAdsClient erzeugen
            tcClient = new TcAdsClient();

            // Verbindung mit Port 801 auf dem lokalen Computer herstellen
            tcClient.Connect(801);

            try
            {
                hVar = tcClient.CreateVariableHandle("MAIN.PLCVar");
            }
            catch(Exception err)
            {
                MessageBox.Show(err.Message);
            }
        }

        private void btnRead_Click(object sender, System.EventArgs e)
        {
            try
            {
                // AdsStream der die Daten aufnehmen soll
                AdsStream dataStream = new AdsStream(100 * 2);
                BinaryReader binRead = new BinaryReader(dataStream);

                //Array komplett auslesen
                tcClient.Read(hVar,dataStream);

                lbArray.Items.Clear();
                dataStream.Position = 0;
                for(int i=0; i<100; i++)
                {
                    lbArray.Items.Add(binRead.ReadInt16().ToString());
                }
            }
            catch(Exception err)
            {
                MessageBox.Show(err.Message);
            }
        }

        private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            //Resourcen wieder freigeben
            try
            {
                tcClient.DeleteVariableHandle(hVar);
            }
            catch(Exception err)
            {
                MessageBox.Show(err.Message);
            }
            tcClient.Dispose();
        }
    }
}

SPS-Programm

PROGRAM MAIN
VAR
  PLCVar : ARRAY [0..99] OF INT;
  Index: BYTE;
END_VAR

FOR Index := 0 TO 99 DO
  PLCVar[Index] := 3500 + INDEX;
END_FOR


Transmitting structures to the PLC

TwinCAT ADS .NET Komponente: Beispiele

Übertragen von Strukturen an die SPS

Download

Sprache / IDE | Beispielprogram auspacken
Visual C# | Sample02.zip
Visual Basic (for .NET Framework) | Sample02.exe
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample02.exe
Delphi for .NET (Borland Developer Studio 2006) | Sample02.exe

Aufgabe

Von einer .NET Applikation soll eine Struktur in die SPS geschrieben werden. Die Elemente der Struktur haben verschiedene Datentypen.

Beschreibung

Die Struktur, die in die SPS geschrieben werden soll:

TYPE PLCStruct
STRUCT
  intVal   : INT;  (*Offset 0*)
  dintVal  : DINT; (*Offset 2*)
  byteVal  : SINT; (*Offset 6*)
  lrealVal : LREAL;(*Offset 7*)
  realVal  : REAL; (*Offset 15 --> Gesamtgrösse 19 Bytes*)
END_STRUCT
END_TYPE

In C# würde die Struktur wie folgt aussehen:

public struct PLCStruct
{
  public short 	intVal;
  public int 	dintVal;
  public byte 	byteVal;
  public double 	lrealVal;
  public float	realVal;
}

Anstatt diese Struktur direkt zu verwenden, wird wie in Sample01 ein AdsStream verwendet. Das AdsStream Objekt wird mit einer Grösse von 19 Bytes initialisiert. Dies entspricht der Grösse des Datentyps PLCStruct in der SPS. Ein BinaryWriter wird verwendet, um die Daten der Editierfelder in den Stream zu schreiben. Nachdem die Position des Streams auf 0 gesetzt wird, werden die einzelnen Felder der Struktur in den Stream geschrieben. Als letztes wird der gesamte Stream mit Hilfe der TcAdsClient.Write in die SPS geschrieben.

C# Programm

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.IO;
using TwinCAT.Ads;

namespace Sample02
{
    ///
    /// Summary description for Form1.
    ///
    public class Form1 : System.Windows.Forms.Form
    {
        private System.Windows.Forms.GroupBox groupBox1;
        private System.Windows.Forms.Label label1;
        private System.Windows.Forms.Label label2;
        private System.Windows.Forms.Label label3;
        private System.Windows.Forms.Label label4;
        private System.Windows.Forms.Label label5;
        private System.Windows.Forms.Button btnWrite;
        private System.Windows.Forms.TextBox tbInt;
        private System.Windows.Forms.TextBox tbDint;
        private System.Windows.Forms.TextBox tbByte;
        private System.Windows.Forms.TextBox tbLReal;
        private System.Windows.Forms.TextBox tbReal;

        private System.ComponentModel.Container components = null;

        private int hVar;
        private TcAdsClient tcClient;

        public Form1()
        {
            InitializeComponent();
        }

        protected override void Dispose( bool disposing )...

        private void InitializeComponent()...

        [STAThread]
        static void Main()
        {
            Application.Run(new Form1());
        }

        private void Form1_Load(object sender, System.EventArgs e)
        {
            // Instanz der Klasse TcAdsClient erzeugen
            tcClient = new TcAdsClient();

            // Verbindung mit Port 801 auf dem lokalen Computer herstellen
            tcClient.Connect(801);

            try
            {
                hVar = tcClient.CreateVariableHandle("MAIN.PLCVar");
            }
            catch(Exception err)
            {
                MessageBox.Show(err.Message);
            }
        }

        private void btnWrite_Click(object sender, System.EventArgs e)
        {
            AdsStream dataStream = new AdsStream(19);
            BinaryWriter binWrite = new BinaryWriter(dataStream);

            dataStream.Position = 0;
            try
            {
                //Stream der Reihenfolge nach mit den Daten aus
                //den Textboxen füllen
                binWrite.Write(short.Parse(tbInt.Text));
                binWrite.Write(int.Parse(tbDint.Text));
                binWrite.Write(byte.Parse(tbByte.Text));
                binWrite.Write(double.Parse(tbLReal.Text));
                binWrite.Write(float.Parse(tbReal.Text));

                //kompletten Stream in die SPS-schreiben
                tcClient.Write(hVar,dataStream);
            }
            catch( Exception err)
            {
                MessageBox.Show(err.Message);
            }
        }

        private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            //Resourcen wieder freigeben
            try
            {
                tcClient.DeleteVariableHandle(hVar);
            }
            catch(Exception err)
            {
                MessageBox.Show(err.Message);
            }
            tcClient.Dispose();
        }
    }
}

SPS-Programm

TYPE PLCStruct
STRUCT
  intVal : INT;
  dintVal    : DINT;
  byteVal    : SINT;
  lrealVal  : LREAL;
  realVal  : REAL;
END_STRUCT
END_TYPE

PROGRAM MAIN
VAR
  PLCVar : PLCStruct;
END_VAR


Ereignisgesteuertes Lesen

Download

Sprache / IDE | Beispielprogram auspacken
Visual C# | Sample03.zip
Visual Basic (for .NET Framework) | Sample03.exe
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample03.exe
Delphi for .NET (Borland Developer Studio 2006) | Sample03.exe

Aufgabe

In der SPS befinden sich 7 globale Variablen. Jede SPS-Variable ist von einem anderen Datentyp. Die Werte der Variablen sollen auf möglichst effektive Weise ausgelesen und der Wert mit Zeitstempel auf einer Form in Visual Basic dargestellt werden.

Beschreibung

In dem Load-Ereignis der Form wird mit der Methode TcAdsClient.AddDeviceNotification() eine Verbindung zu jeder Variablen in der SPS erzeugt. Das Handle dieser Verbindung wird in einem Array gespeichert. Der Parameter TransMode spezifiziert die Art des Datenaustausches. In diesem Fall wird mit AdsTransMode.OnChange festgelegt, dass die SPS-Variable nur übermittelt wird falls sich der Wert in der SPS geändert hat(siehe AdsTransMode ). Der Parameter cycleTime bestimmt, wie oft die PLC checken soll, ob sich die entsprechende Varaiable geändert hat. MaxDelay ermöglicht es die Notifications für einen Zeitraum zu sammeln und nach Ablauf des Intervall im Block zu verschicken.

Wenn die SPS-Variable sich geändert hat, wird das TcAdsClient.AdsNotification() gefeuert. Der Parameter e der Eventhandler-Methode ist vom Typ AdsNotificationEventArgs und enthält die Zeit, das Handle, den Wert und das Textfeld, das die Daten anzeigen soll. In dem Closing-Ereignis werden die Verbindungen wieder mit der Methode TcAdsClient.DeleteDeviceNotification() freigegeben. Dieses sollten Sie umbedingt beachten, da jede Verbindung die mit TcAdsClient.AddDeviceNotification() hergestellt wurde Resourcen verbraucht. Setzen Sie außerdem die CycleTime auf angemessene Werte, da zu viele Schreib / Leseoperationen das System so stark auslasten kann, dass die Bedieneroberfläche stark verlangsamt wird.

Hinweis: Im Callback (OnNotification()) dürfen keine zeitintensiven Aktionen ausgeführt werden.

C# Programm

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.IO;
using TwinCAT.Ads;

namespace Sample03
{
    public class Form1 : System.Windows.Forms.Form
    {
        private System.Windows.Forms.Label label1;
        private System.Windows.Forms.Label label2;
        private System.Windows.Forms.Label label3;
        private System.Windows.Forms.Label label4;
        private System.Windows.Forms.Label label5;
        private System.Windows.Forms.Label label6;
        private System.Windows.Forms.Label label7;
        private System.Windows.Forms.Label label8;
        private System.Windows.Forms.Label label9;
        private System.Windows.Forms.Label label10;
        private System.Windows.Forms.TextBox tbInt;
        private System.Windows.Forms.TextBox tbDint;
        private System.Windows.Forms.TextBox tbSint;
        private System.Windows.Forms.TextBox tbLreal;
        private System.Windows.Forms.TextBox tbReal;
        private System.Windows.Forms.TextBox tbString;
        private System.Windows.Forms.Label label11;
        private System.Windows.Forms.TextBox tbBool;
        private System.ComponentModel.Container components = null;

        private TcAdsClient tcClient;
        private int[] hConnect;
        private AdsStream dataStream;
        private BinaryReader binRead;

        public Form1()
        {
            InitializeComponent();
        }

        protected override void Dispose( bool disposing ) ...

        private void InitializeComponent() ...

        [STAThread]
        static void Main()
        {
            Application.Run(new Form1());
        }

        private void Form1_Load(object sender, System.EventArgs e)
        {
            dataStream = new AdsStream(31);
            //Encoding wird auf ASCII gesetzt, um Strings lesen zu können
            binRead = new BinaryReader(dataStream, System.Text.Encoding.ASCII);
            // Instanz der Klasse TcAdsClient erzeugen
            tcClient = new TcAdsClient();
            // Verbindung mit Port 801 auf dem lokalen Computer herstellen
            tcClient.Connect(801);

            hConnect = new int[7];

            try
            {
                hConnect[0] = tcClient.AddDeviceNotification("MAIN.boolVal",dataStream,0,1,
                    AdsTransMode.OnChange,100,0,tbBool);
                hConnect[1] = tcClient.AddDeviceNotification("MAIN.intVal",dataStream,1,2,
                    AdsTransMode.OnChange,100,0,tbInt);
                hConnect[2] = tcClient.AddDeviceNotification("MAIN.dintVal",dataStream,3,4,
                    AdsTransMode.OnChange,100,0,tbDint);
                hConnect[3] = tcClient.AddDeviceNotification("MAIN.sintVal",dataStream,7,1,
                    AdsTransMode.OnChange,100,0,tbSint);
                hConnect[4] = tcClient.AddDeviceNotification("MAIN.lrealVal",dataStream,8,8,
                    AdsTransMode.OnChange,100,0,tbLreal);
                hConnect[5] = tcClient.AddDeviceNotification("MAIN.realVal",dataStream,16,4,
                    AdsTransMode.OnChange,100,0,tbReal);
                hConnect[6] = tcClient.AddDeviceNotification("MAIN.stringVal",dataStream,20,11,
                    AdsTransMode.OnChange,100,0,tbString);

                tcClient.AdsNotification += new AdsNotificationEventHandler(OnNotification);
            }
            catch(Exception err)
            {
                MessageBox.Show(err.Message);
            }
        }

        private void OnNotification(object sender, AdsNotificationEventArgs e)
        {
            DateTime time = DateTime.FromFileTime(e.TimeStamp);
            e.DataStream.Position = e.Offset;
            string strValue = "";

            if( e.NotificationHandle == hConnect[0])
                strValue = binRead.ReadBoolean().ToString();
            else if( e.NotificationHandle == hConnect[1] )
                strValue = binRead.ReadInt16().ToString();
            else if( e.NotificationHandle == hConnect[2] )
                strValue = binRead.ReadInt32().ToString();
            else if( e.NotificationHandle == hConnect[3] )
                strValue = binRead.ReadSByte().ToString();
            else if( e.NotificationHandle == hConnect[4] )
                strValue = binRead.ReadDouble().ToString();
            else if( e.NotificationHandle == hConnect[5] )
                strValue = binRead.ReadSingle().ToString();
            else if( e.NotificationHandle == hConnect[6] )
            {
                strValue = new String(binRead.ReadChars(11));
            }

            ((TextBox)e.UserData).Text = String.Format("DateTime: {0},{1}ms; {2}",time,time.Millisecond,strValue);
        }

        private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            try
            {
                for(int i=0; i<7; i++)
                {
                    tcClient.DeleteDeviceNotification(hConnect[i]);
                }
            }
            catch(Exception err)
            {
                MessageBox.Show(err.Message);
            }
            tcClient.Dispose();
        }

    }
}

SPS programm

PROGRAM MAIN
VAR
 boolVal	  : BOOL;
 intVal    : INT;
 dintVal   : DINT;
 sintVal   : SINT;
 lrealVal  : LREAL;
 realVal   : REAL;
 stringVal : STRING(10);
END_VAR

PROGRAM MAIN
VAR
    ;
END_VAR


Lesen und Schreiben von String-Variablen

Sprache / IDE | Beispielprogram auspacken
Visual C# | Sample04.zip
Visual Basic (for .NET Framework) | Sample04.exe TwinCAT.Ads.NET v1.0.0.10 und höher: Sample04b.exe
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample04.exe
Delphi for .NET (Borland Developer Studio 2006) | Sample04.exe

Aufgabe

Eine .NET Applikation soll einen String von der SPS lesen und einen String in die SPS schreiben.

Beschreibung

Im Load-Ereignis wird eine neue Instanz der Klasse TcAdsClient erzeugt. Dann wird die Methode TcAdsClient.Connect des TcAdsClient Objektes aufgerufen, um eine Verbindung mit dem Port 801 herzustellen. Zum Schluss wird dann die Methode TcAdsClient.CreateVariableHandle aufgerufen, um sich ein Handle auf die SPS-Variable zu besorgen. Wenn das Programm beendet wird, wird das Handle in der Event-Methode Form1_Closing wieder freigegeben und die Dispose-Methode des TcAdsClient Objektes wird aufgerufen.

Wenn der Anwender den "Read"-Button betätigt, wird die String-Variable MAIN.text mit Hilfe der Methode TcAdsClient.Read aus der SPS gelesen und in dem Editierfeld angezeigt. Wenn der Anwender den "Write"-Button betätigt, wird der Text der im Editierfeld steht in die SPS-Variable MAIN.text geschrieben.

Mit Hilfe der Klassen AdsBinaryReader and AdsBinaryWriter können SPS-Strings aus einem AdsStream gelesen und in einen AdsStream geschrieben werden( siehe kommentierten Abschnitt des Beispiel-Programmes). Dies Klassen sind direkt von den Klassen BinaryReader und BinaryWriter abgeleitet. Um einen String aus dem Stream zu lesen wird die Methode AdsBinaryReader.ReadPlcString aufgerufen. Um einen String in den Stream zu schreiben wird die Methode AdsBinaryWriter.WritePLCString aufgerufen. Die Länge vom AdsStream muss der Länge des Strings in der SPS entsprechen (ohne Null-Terminierung). Die Länge der String-Variablen wird der Methode als Parameter übergeben und muss der Länge des Strings in der SPS entsprechen (ohne Null-Terminierung).

TwinCAT.Ads.NET Version < 1.0.0.10:

Die Länge vom AdsStream muss der Länge des Strings in der SPS + 1 (mit der Null-Terminierung) entsprechen. Der Parameter System.Text.Encoding.ASCII wird dem Konstruktor der BinaryReader Klasse übergeben. Dies ist notwendig, damit die BinaryReader Klasse in der Lage ist, die ANSI-Zeichen in den Typen char zu konvertieren.

C# program

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using TwinCAT.Ads;
using System.IO;

namespace Sample04
{
    public class Form1 : System.Windows.Forms.Form
    {
        private System.ComponentModel.Container components = null;
        private System.Windows.Forms.TextBox textBox1;
        private System.Windows.Forms.Button btnRead;
        private System.Windows.Forms.Button btnWrite;
        private System.Windows.Forms.Label label1;
        private TcAdsClient adsClient;
        private int varHandle;

        public Form1()
        {
           InitializeComponent();
        }

        protected override void Dispose( bool disposing )
        {
    ...
        }

        private void InitializeComponent()
        {
    ...
        }

        [STAThread]
        static void Main()
        {
            Application.Run(new Form1());
        }

            private void Form1_Load(object sender, System.EventArgs e)
        {
            try
            {
                adsClient = new TcAdsClient();
                adsClient.Connect(801);
                varHandle = adsClient.CreateVariableHandle("MAIN.text");
            }
            catch( Exception err)
            {
                MessageBox.Show(err.Message);
            }
        }

        private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            adsClient.Dispose();
        }

        private void btnRead_Click(object sender, System.EventArgs e)
        {
            try
            {
                //length of the stream = length of string in sps + 1
                AdsStream adsStream = new AdsStream(31);
                BinaryReader reader = new BinaryReader(adsStream,System.Text.Encoding.ASCII);

                int length = adsClient.Read(varHandle, adsStream);
                string text = new string(reader.ReadChars(length));
                //necessary if you want to compare the string to other strings
                //text = text.Substring(0,text.IndexOf('\0'));
                textBox1.Text = text;
       }
            catch(Exception err)
            {
                MessageBox.Show(err.Message)
            }
        }

        private void btnWrite_Click(object sender, System.EventArgs e)
        {
            try
            {
                //length of the stream = length of string + 1
                AdsStream adsStream = new AdsStream(textBox1.Text.Length+1);
                BinaryWriter writer = new BinaryWriter(adsStream, System.Text.Encoding.ASCII);
                writer.Write(textBox1.Text.ToCharArray());
                //add terminating zero
                writer.Write('\0');
                adsClient.Write(varHandle,adsStream);
            }
            catch(Exception err)
            {
                MessageBox.Show(err.Message);
            }
        }

/*From version 1.0.0.10 and higher the classes AdsBinaryReader and AdsBinaryWriter
  can be used to read and write strings
            private void btnRead_Click(object sender, System.EventArgs e)
         {
            try
            {
                AdsStream adsStream = new AdsStream(30);
                AdsBinaryReader reader = new AdsBinaryReader(adsStream);
                adsClient.Read(varHandle, adsStream);
                textBox1.Text = reader.ReadPlcString(30);
            }
            catch(Exception err)
            {
                MessageBox.Show(err.Message);
            }
         }

        private void btnWrite_Click(object sender, System.EventArgs e)
        {
            try
            {
                AdsStream adsStream = new AdsStream(30);
                AdsBinaryWriter writer = new AdsBinaryWriter(adsStream);
                writer.WritePlcString(textBox1.Text, 30);
                adsClient.Write(varHandle, adsStream);
            }
            catch(Exception err)
            {
                MessageBox.Show(err.Message);
            }
        }
*/

    }
}

SPS program

PROGRAM MAIN
VAR
 text : STRING[30] := 'hello';
END_VAR


Lesen und Schreiben von DATE/TIME-Variablen

Sprache / IDE | Beispielprogram auspacken
Visual C# | Sample05.zip
Visual Basic (for .NET Framework) | Sample05.exe
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample05.exe
Delphi for .NET (Borland Developer Studio 2006) | Sample05.exe

Aufgabe

Es soll eine .NET Applikation geschrieben werden die DATE/TIME-Variablen liest bzw. beschreibt.

Beschreibung

Die SPS enthält die Variable MAIN.Time1 vom Typ TIME und die Variable MAIN.Date1 vom Typ DT

Im Load-Ereignis des Forms wird eine neue Instanz der Klasse TcAdsClient erzeugt. Dann wird die Methode TcAdsClient.Connect des TcAdsClient Objektes aufgerufen, um eine Verbindung mit dem Port 801 herzustellen. Zum Schluss wird dann die Methode TcAdsClient.CreateVariableHandle aufgerufen, um sich ein Handle auf die SPS-Variable zu besorgen. Wenn das Programm beendet wird, wird das Handle in der Event-Methode Form1_Closing wieder freigegeben und die Dispose-Methode des TcAdsClient Objektes wird aufgerufen.

Wenn der Anwender den "Read"-Button betätigt, werden die Variablen MAIN.Time1 und MAIN.Date1 mit Hilfe der Methode TcAdsClient.Read aus dem AdsStream gelesen. Die Klasse AdsBinaryReader, die von BinaryReader abgeleitet ist, wird dazu eingesetzt , eine Zeit bzw. ein Datum aus der SPS zu lesen. Die Methode AdsBinaryReader.ReadPlcTIME liest die TIME-Variable aus dem Stream und konvertiert diese in den .NET Datentypen TimeSpan. Die Methode AdsBinaryReader.ReadPlcDATE liest die DT-Variable aus dem AdsStream und konvertiert diese in den .NET Typen DataTime.

Wenn der Anwender den "Write"-Button betätigt, werden die SPS-Variablen MAIN.Time1 und MAIN.Date1 beschrieben. Die Klasse AdsBinaryWriter, die von BinaryWriter abgeleitet ist, kann eingesetzt werden, um eine Zeit bzw. ein Datum in diese zu schreiben. Die Methode AdsBinaryWriter.WritePlcType schreibt die .NET Typen TimeSpan und DateTime im SPS-Format in den Stream.

C# Programmm

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using TwinCAT.Ads;
using System.IO;

namespace Sample04
{
    public class Form1 : System.Windows.Forms.Form
    {
        private System.ComponentModel.Container components = null;
        private System.Windows.Forms.TextBox textBox1;
        private System.Windows.Forms.Button btnRead;
        private System.Windows.Forms.Button btnWrite;
        private System.Windows.Forms.Label label1;
        private TcAdsClient adsClient;
        private int[] varHandles;

        public Form1()
        {
           InitializeComponent();
        }

        protected override void Dispose( bool disposing )
        {
    ...
        }

        private void InitializeComponent()
        {
    ...
        }

        [STAThread]
        static void Main()
        {
            Application.Run(new Form1());
        }

            private void Form1_Load(object sender, System.EventArgs e)
        {
            try
            {
                adsClient = new TcAdsClient();
                adsClient.Connect(801);
                varHandles = new int[2];
                varHandles[0] = adsClient.CreateVariableHandle("MAIN.Time1");
                varHandles[1] = adsClient.CreateVariableHandle("MAIN.Date1");
            }
            catch( Exception err)
            {
                MessageBox.Show(err.Message);
            }
        }

        private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            adsClient.Dispose();
        }

        private void btnRead_Click(object sender, System.EventArgs e)
        {
            try
            {
                AdsStream adsStream = new AdsStream(4);
                AdsBinaryReader reader = new AdsBinaryReader(adsStream);
                adsClient.Read(varHandles[0], adsStream);
                textBox1.Text = reader.ReadPlcTIME().ToString();
                adsStream.Position = 0;
                adsClient.Read(varHandles[1], adsStream);
                textBox2.Text = reader.ReadPlcDATE().ToString();
       }
            catch(Exception err)
            {
                MessageBox.Show(err.Message)
            }
        }

        private void btnWrite_Click(object sender, System.EventArgs e)
        {
            try
            {
                AdsStream adsStream = new AdsStream(4);
                AdsBinaryWriter writer = new AdsBinaryWriter(adsStream);
                writer.WritePlcType(TimeSpan.Parse(textBox1.Text));
                adsClient.Write(varHandles[0], adsStream);

                adsStream.Position = 0;
                writer.WritePlcType(DateTime.Parse(textBox2.Text));
                adsClient.Write(varHandles[1], adsStream)
            }
            catch(Exception err)
            {
                MessageBox.Show(err.Message);
            }
        }
    }
}

SPS Programm

PROGRAM MAIN
VAR
    Time1:TIME := T#21h33m23s231ms;
    Date1:DT:=DT#1993-06-12-15:36:55.40;
END_VAR


SPS Variablen-Deklaration auslesen

Sprache / IDE | Beispielprogram auspacken
Visual C# | Sample06.zip
Visual Basic (for .NET Framework) | Sample06.exe
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample06.exe
Delphi for .NET (Borland Developer Studio 2006) | Sample06.exe

Aufgabe

Alle Variablen, die in der SPS deklariert sind, sollen in einer Baumansicht dargestellt.

Beschreibung

In der Methode Form1_Load wird mit TcAdsClient.CreateSymbolInfoLoader eine Instanz der Klasse TcAdsSymbolInfoLoader erzeugt. Diese Klasse ist verantwortlich für das laden der Symbol-Information von der SPS. Durch das Betätigen des Buttons Load Symbols werden die Symbole mit Hilfe der Methode TcAdsSymbolInfoLoader.GetFirstSymbol von der SPS geladen. Als Rückgabewert wird das erste geladen Symbol in Form eines TcAdsSymbolInfo-Objektes zurückgeliefert. Die Methoden TcAdsSymbolInfo.NextSymbol und TcAdsSymbolInfo.FirstSubSymbol werden dann verwendet, um über alle Symbole zu itererieren und damit die Symbols-Baumansicht hierarchisch zu füllen. Wenn die Checkbox flat gecheckt ist werden die Symbole als flache Liste dargestellt. Dazu wird einfach mit foreach über die Symbole von TcAdsSymbolInfoLoader enumeriert. Dabei werden auch die Sub-Symbole dargestellt.

Beim Selektieren eines TreeView-Items werden die Editierfelder mit der Symbol-Information gefüllt. Zusätzlich wird der Wert des Symbols mit TcAdClient.ReadSymbol ausgelesen und im Editierfeld Value ausgegeben. Durch Betätigen des Write Value Buttons wird die Methode TcAdsCient.WriteSymbol verwendet, um einen Wert in die SPS-Variable zu schreiben.

Um die Symbol-Information einer bestimmten Variablen auszulesen, kann entweder Read Symbol Info oder Find Symbol verwendet werden. Die erste Variante verwendet die Methode TcAdsClient.ReadSymbolInfo. Hierbei wird ein ADS-Aufruf verwendet, um die Symbol-Information für dieses Symbol von der SPS zu lesen. Die zweite Variante verwendet die Methode TcAdsSymbolInfoLoader.FindSymbol, die das Symbol in der Liste der bereits geladenen Symbole sucht. Wenn vorher noch keine Symbole geladen wurden, werden alle Symbole von der SPS geladen.

C# Programmm

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.IO;
using TwinCAT.Ads;

namespace Sample06
{
      /// <summary>
      /// Summary description for Form1.
      /// </summary>
      public class Form1 : System.Windows.Forms.Form
      {
         private System.Windows.Forms.TreeView treeViewSymbols;
         private System.Windows.Forms.GroupBox groupBox1;
         private System.Windows.Forms.Button btnLoad;
         private System.Windows.Forms.Label label3;
         private System.Windows.Forms.TextBox tbDatatype;
         private System.Windows.Forms.Label label2;
         private System.Windows.Forms.TextBox tbIndexGroup;
         private System.Windows.Forms.Label label1;
         private System.Windows.Forms.TextBox tbIndexOffset;
         private System.Windows.Forms.Button btnFindSymbol;
         private System.Windows.Forms.Button btnReadSymbolInfo;
         private System.Windows.Forms.GroupBox groupBox2;
         private System.Windows.Forms.Label label4;
         private System.Windows.Forms.Label label5;
         private System.Windows.Forms.TextBox tbDatatypeId;
         private System.Windows.Forms.Label label6;
         private System.Windows.Forms.TextBox tbValue;
         /// <summary>
         /// Required designer variable.
         /// </summary>
         private System.ComponentModel.Container components = null;
         private TcAdsClient adsClient;
         private System.Windows.Forms.TextBox tbSymbolname;
         private System.Windows.Forms.Label label9;
         private System.Windows.Forms.TextBox tbSize;
         private System.Windows.Forms.Button btnWrite;
         private TcAdsSymbolInfoLoader symbolLoader;
         private System.Windows.Forms.TextBox tbName;
         private System.Windows.Forms.Label label7;
         private System.Windows.Forms.CheckBox cbFlat;
         private ITcAdsSymbol currentSymbol = null;

         public Form1()
         {
            InitializeComponent();
         }

         /// <summary>
         /// Clean up any resources being used.
         /// </summary>
         protected override void Dispose( bool disposing )
         {
            if( disposing )
            {
               if (components != null)
               {
                  components.Dispose();
               }
            }
            base.Dispose( disposing );
         }

         #region Windows Form Designer generated code
            ...
         #endregion

         /// <summary>
         /// The main entry point for the application.
         /// </summary>
         [STAThread]
         static void Main()
         {
            Application.Run(new Form1());
         }

         private void Form1_Load(object sender, System.EventArgs e)
         {
            try
            {
               adsClient = new TcAdsClient();

         // TwinCAT2 RTS1 Port = 801, TwinCAT3 RTS1 Port = 851
            adsClient.Connect(801);
               symbolLoader = adsClient.CreateSymbolInfoLoader();
            }
            catch(Exception err)
            {
               MessageBox.Show(err.Message);
            }
         }

         private void btnLoad_Click(object sender, System.EventArgs e)
         {
            treeViewSymbols.Nodes.Clear();

            if( !cbFlat.Checked )
            {
               TcAdsSymbolInfo symbol = symbolLoader.GetFirstSymbol(true);
               while( symbol != null )
               {
                  treeViewSymbols.Nodes.Add(CreateNewNode(symbol));
                  symbol = symbol.NextSymbol;
               }
            }
            else
            {
               foreach( TcAdsSymbolInfo symbol in symbolLoader )
               {
                  TreeNode node = new TreeNode(symbol.Name);
                  node.Tag = symbol;
                  treeViewSymbols.Nodes.Add(node);
               }
            }
         }

         private void btnReadSymbolInfo_Click(object sender, System.EventArgs e)
         {
            try
            {
               ITcAdsSymbol symbol = adsClient.ReadSymbolInfo(tbSymbolname.Text);
               if( symbol == null)
               {
                  MessageBox.Show("Symbol " + tbSymbolname.Text + " not found");
                  return;
               }
               SetSymbolInfo(symbol);
            }
            catch( Exception err )
            {
               MessageBox.Show(err.Message);
            }
         }

         private void btnFindSymbol_Click(object sender, System.EventArgs e)
         {
            try
            {
               ITcAdsSymbol symbol = symbolLoader.FindSymbol(tbSymbolname.Text);
               if( symbol == null)
               {
                  MessageBox.Show("Symbol " + tbSymbolname.Text + " not found");
                  return;
               }
               SetSymbolInfo(symbol);
            }
            catch( Exception err )
            {
               MessageBox.Show(err.Message);
            }
         }

         private void btnWrite_Click(object sender, System.EventArgs e)
         {
            try
            {
               if( currentSymbol != null )
                  adsClient.WriteSymbol(currentSymbol, tbValue.Text);
            }
            catch(Exception err)
            {
               MessageBox.Show("Unable to write Value. " + err.Message);
            }
         }

         private void treeViewSymbols_AfterSelect(object sender, System.Windows.Forms.TreeViewEventArgs e)
         {
            if( e.Node.Text.Length > 0 )
            {
               if( e.Node.Tag is TcAdsSymbolInfo )
               {
                  SetSymbolInfo((ITcAdsSymbol)e.Node.Tag);
               }
            }
         }

         private TreeNode CreateNewNode(TcAdsSymbolInfo symbol)
         {
            TreeNode node = new TreeNode(symbol.Name);

            node.Tag = symbol;
            TcAdsSymbolInfo subSymbol = symbol.FirstSubSymbol;
            while( subSymbol != null )
            {
               node.Nodes.Add(CreateNewNode(subSymbol));
               subSymbol = subSymbol.NextSymbol;
            }
            return node;
         }

         private void SetSymbolInfo(ITcAdsSymbol symbol)
         {
            currentSymbol = symbol;
            tbName.Text = symbol.Name.ToString();
            tbIndexGroup.Text = symbol.IndexGroup.ToString();
            tbIndexOffset.Text = symbol.IndexOffset.ToString();
            tbSize.Text = symbol.Size.ToString();
            tbDatatype.Text = symbol.Type;
            tbDatatypeId.Text = symbol.Datatype.ToString();
            try
            {
               tbValue.Text = adsClient.ReadSymbol(symbol).ToString();
            }
            catch( AdsDatatypeNotSupportedException err )
            {
               tbValue.Text = err.Message;
            }
            catch(Exception err)
            {
               MessageBox.Show("Unable to read Symbol Info. " + err.Message);
            }
         }
      }
}

SPS Programm

PROGRAM MAIN
VAR
    REAL32_1 AT %MB0 : REAL;  (* 1 *)
    REAL32_2 AT %MB4 : REAL;  (* 2 *)
    REAL32_3 AT %MB8 : REAL;  (* 3 *)
    REAL32_4 AT %MB12: REAL;  (* 4 *)
    REAL32_5 AT %MB16: REAL;  (* 5 *)

    REAL64_1 AT %MB20 : LREAL;  (* 6 *)
    REAL64_2 AT %MB28 : LREAL;  (* 7 *)
    REAL64_3 AT %MB36 : LREAL;  (* 8 *)
    REAL64_4 AT %MB44 : LREAL;  (* 9 *)
    REAL64_5 AT %MB52 : LREAL;  (* 10 *)

    INT32_1 AT %MB60 : DINT;  (* 11 *)
    INT32_2 AT %MB64 : DINT;  (* 12 *)
    INT32_3 AT %MB68 : DINT;  (* 13 *)
    INT32_4 AT %MB72 : DINT;  (* 14 *)
    INT32_5 AT %MB76 : DINT;  (* 15 *)

    UINT32_1 AT %MB80 : UDINT;  (* 16 *)
    UINT32_2 AT %MB84 : UDINT;  (* 17 *)
    UINT32_3 AT %MB88 : UDINT;  (* 18 *)
    UINT32_4 AT %MB92 : UDINT;  (* 19 *)
    UINT32_5 AT %MB96 : UDINT;  (* 20 *)

    INT16_1 AT %MB100 : INT;  (* 21 *)
    INT16_2 AT %MB102 : INT;  (* 22 *)
    INT16_3 AT %MB104 : INT;  (* 23 *)
    INT16_4 AT %MB106 : INT;  (* 24 *)
    INT16_5 AT %MB108 : INT;  (* 25 *)

    UINT16_1 AT %MB110 : UINT;  (* 26 *)
    UINT16_2 AT %MB112 : UINT;  (* 27 *)
    UINT16_3 AT %MB114 : UINT;  (* 28 *)
    UINT16_4 AT %MB116 : UINT;  (* 29 *)
    UINT16_5 AT %MB118 : UINT;  (* 30 *)

    INT8_1 AT %MB120 : SINT;  (* 31 *)
    INT8_2 AT %MB121 : SINT;  (* 32 *)
    INT8_3 AT %MB122 : SINT;  (* 33 *)
    INT8_4 AT %MB123 : SINT;  (* 34 *)
    INT8_5 AT %MB124 : SINT;  (* 35 *)

    UINT8_1 AT %MB125 : USINT;  (* 36 *)
    UINT8_2 AT %MB126 : USINT;  (* 37 *)
    UINT8_3 AT %MB128 : USINT;  (* 38 *)
    UINT8_4 AT %MB129 : USINT;  (* 39 *)
    UINT8_5 AT %MB130 : USINT;  (* 40 *)

    BOOL_1 AT %MX131.0 : BOOL;  (* 41 *)
    BOOL_2 AT %MX131.1 : BOOL;  (* 42 *)
    BOOL_3 AT %MX131.2 : BOOL;  (* 43 *)
    BOOL_4 AT %MX131.3 : BOOL;  (* 44 *)
    BOOL_5 AT %MX131.4 : BOOL;  (* 45 *)

    ARRAY_1 : ARRAY[1 .. 10] OF SINT; (* 46 *)
    ARRAY_2 : ARRAY[1 .. 10] OF INT;  (* 47 *)
    ARRAY_3 : ARRAY[1 .. 10] OF DINT; (* 48 *)
    ARRAY_4 : ARRAY[1 .. 10] OF LREAL;(* 49 *)
    ARRAY_5 : ARRAY[1 .. 10] OF BOOL; (* 50 *)

    STRING_1 : STRING(20);
END_VAR


Lesen und Schreiben von SPS Variablen eines beliebigen Typs (ReadAny, WriteAny)

Sprache / IDE | Beispielprogram auspacken
Visual C# | Sample07.zip
Visual Basic (for .NET Framework) | Sample07.exe
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample07.exe
Delphi for .NET (Borland Developer Studio 2006) | Sample07.exe

Aufgabe

Auslesen bzw. Beschreiben von Variablen eines beliebigen Typs mit Hilfe der Methode ReadAny bzw. WriteAny.

Beschreibung

ReadAny

In der Event-Methode btnRead_Click wird die Methode TcAdsClient.ReadAny verwendet um die Variablen per Handle auszulesen:

public object ReadAny(int variableHandle, Type type)
public object ReadAny(int variableHandle, Type type, int[] args)

Der Methode wird im Parameter type der Typ der zu lesenden Variable übergeben. Wenn die Methode erfolgreich gewesen ist, werden die gelesenen Daten als Objekt zurückgeliefert. Der Typ des Objektes entspricht dem Parameter type. Da bei einigen Datentypen(Arrays und Strings) zusätzliche Informationen notwendig sind gibt es eine Überladung der Methode ReadAny, die den zusätzlichen Parameter args enthält. Bei Strings z.B. müsste ein Integer-Array der Länge 1 übergeben werden. Eine vollständige Liste der unterstützten Typen finden Sie in der Dokumentation der überladenen Methode.

Beispiel:

Eine SPS-Variable vom Type ARRAY[0..3] OF DINT soll gelesen werden:

int hArr;
int[] arr;

hArr = adsClient.CreateVariableHandle(".arr")
arr = (int[]) adsClient.ReadAny(hArr, typeof(int[]), new int[] {4})

...
adsClient.DeleteVariableHandle(hArr)

WriteAny

In der Event-Methode btnWrite_Click wird die Methode TcAdsClient.WriteAny verwendet um die Variablen per Handle zu beschreiben:

public void WriteAny(int variableHandle, object value)
public void WriteAny(int variableHandle, object value, int[] args)

Der Parameter value enthält das Objekt, das in die SPS geschrieben werden soll. Eine vollständige Liste der unterstützten Typen von value finden Sie in der Dokumentation der überladenen Methode.

Beispiel:

Eine SPS-Variable vom Typ ARRAY[0..3] OF DINT soll beschrieben werden:

int hArr;
int[] arr = new int[]{1,2,3,4}

hArr = adsClient.CreateVariableHandle(".arr")
adsClient.WriteAny(hArr, arr)

...
adsClient.DeleteVariableHandle(hArr)

Lesen und Schreiben von Strukturen:
( nicht möglich mit dem Compact Framework)

Beim Lesen und beschreiben von Strukturen und Klassen, muss darauf geachtet werden, dass das Speicherlayout der Struktur dem Layout in der SPS entspricht. Das Layout kann mit dem Attribut StructLayoutAttribute festgelegt werden. Dabei muss als LayoutKind LayoutKind.Sequential ausgewählt werden und Pack auf 1 gesetzt werden. Die Klasse SimpleStruct ist deshalb wie folgt definiert worden:

[StructLayout(LayoutKind.Sequential, Pack=1)] // TwinCAT2->Pack=1 TwinCAT3->Pack=0
public class SimpleStruct

{
    public double lrealVal;
    public int dintVal1;
}

Wenn Arrays, Strings oder boolsche Werte in einer Struktur/Klasse definiert sind, muss man festlegen, wie diese gemarshallt werden. Dies geschieht mit Hilfe des MarshalAs Attributs. Da Arrays und Strings unter .Net normalerweise keine feste Länge haben, muss mit dem Property SizeConst die Grösse des Arrays bzw. des Strings festgelegt werden. Das Marshallen von Mehrdimensionale Arrays oder Arrays von Strukturen ist mit dem Marhaler des .NET Frameworks 1.1 nicht möglich. Mehrdimensionale Arrays müssten auf eindimensionale Arrays abgebildet werden. Arrays von Strukturen müssten in der .NET Struktur aufgelöst werden.

In dem Beispiel wird das MarshalAsAttribute in der Klasse ComplexStruct verwendet:

[StructLayout(LayoutKind.Sequential, Pack=1)] // TwinCAT2->Pack=1 TwinCAT3->Pack=0
public class ComplexStruct

{
    public short intVal;
    //specifies how .NET should marshal the array
    //SizeConst specifies the number of elements the array has.
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=4)]
    public int[] dintArr = new int[4];
    [MarshalAs(UnmanagedType.I1)]
    public bool boolVal;
    public byte byteVal;
    //specifies how .NET should marshal the string
    //SizeConst specifies the number of characters the string has.
    //'(inclusive the terminating null ).
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=6)]
    public string stringVal = "";
    public SimpleStruct simpleStruct1 =new SimpleStruct();
}

Anmelden von ADS Notifications

In der Event-Methode btnAddNotifications_Click wird die Methode AddDeviceNotificationEx dazu verwendet, Notifications für die SPS Variable anzumelden. Bei Änderung des Wertes der Variable wird das Event AdsNotificationEx ausgelöst. Der Unterschied zu dem Event AdsNotifcation, besteht darin das der Wert einer Variablen in einem Objekt gespeichert ist, anstatt in einem AdsStream. Der Methode AddNotificationEx muss dazu der Typ dieses Objektes übergeben werden:

notificationHandles.Add(adsClient.AddDeviceNotificationEx("MAIN.dint1", AdsTransMode.OnChange, 100, 0, tbDint1, typeof(int)));

Als User-Objekt wurde hier die Textbox, die den Wert anzeigen soll, übergeben. Wenn das Event ausgelöst wird, wird die Event-Methode adsClient_AdsNotificationEx aufgerufen. Dazu wird das Event in Form_Load angemeldet:

adsClient.AdsNotificationEx+=new AdsNotificationExEventHandler(adsClient_AdsNotificationEx);

C# Programmm

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Runtime.InteropServices;
using TwinCAT.Ads;

namespace Sample07
{
    /// <summary>
    /// Summary description for Form1.
    /// </summary>
    public class Form1 : System.Windows.Forms.Form
    {
        internal System.Windows.Forms.Button btnDeleteNotifications;
        internal System.Windows.Forms.Button btnAddNotifications;
        internal System.Windows.Forms.Button btnWrite;
        internal System.Windows.Forms.Button btnRead;
        internal System.Windows.Forms.GroupBox GroupBox3;
        internal System.Windows.Forms.TextBox tbComplexStruct_dintArr;
        internal System.Windows.Forms.Label Label14;
        internal System.Windows.Forms.TextBox tbComplexStruct_ByteVal;
        internal System.Windows.Forms.Label Label13;
        internal System.Windows.Forms.TextBox tbComplexStruct_SimpleStruct1_lrealVal;
        internal System.Windows.Forms.Label Label12;
        internal System.Windows.Forms.TextBox tbComplexStruct_SimpleStruct_dintVal;
        internal System.Windows.Forms.Label Label11;
        internal System.Windows.Forms.Label Label5;
        internal System.Windows.Forms.TextBox tbComplexStruct_stringVal;
        internal System.Windows.Forms.Label Label3;
        internal System.Windows.Forms.TextBox tbComplexStruct_boolVal;
        internal System.Windows.Forms.Label Label9;
        internal System.Windows.Forms.TextBox tbComplexStruct_IntVal;
        internal System.Windows.Forms.Label Label10;
        internal System.Windows.Forms.GroupBox GroupBox2;
        internal System.Windows.Forms.TextBox tbStr2;
        internal System.Windows.Forms.Label Label7;
        internal System.Windows.Forms.TextBox tbStr1;
        internal System.Windows.Forms.Label Label8;
        internal System.Windows.Forms.GroupBox GroupBox1;
        internal System.Windows.Forms.TextBox tblreal1;
        internal System.Windows.Forms.Label Label6;
        internal System.Windows.Forms.TextBox tbUsint1;
        internal System.Windows.Forms.Label Label4;
        internal System.Windows.Forms.TextBox tbDint1;
        internal System.Windows.Forms.Label Label2;
        internal System.Windows.Forms.TextBox tbBool1;
        internal System.Windows.Forms.Label Label1;

        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.Container components = null;

        //PLC variable handles
        private int hdint1;
        private int hbool1;
        private int husint1;
        private int hlreal1;
        private int hstr1;
        private int hstr2;
        private int hcomplexStruct;
        private ArrayList notificationHandles;

        private TcAdsClient adsClient;

        public Form1()
        {
            //
            // Required for Windows Form Designer support
            //
            InitializeComponent();
        }

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        protected override void Dispose( bool disposing )
        {
            if( disposing )
            {
                if (components != null)
                {
                    components.Dispose();
                }
            }
            base.Dispose( disposing );
        }

        #region Windows Form Designer generated code
        ..
        #endregion

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
          Application.Run(new Form1());
        }

        private void Form1_Load(object sender, System.EventArgs e)
        {
            adsClient = new TcAdsClient();
            notificationHandles = new ArrayList();
            try
            {
                // TwinCAT2 RTS1 Port = 801, TwinCAT3 RTS1 Port = 851
                adsClient.Connect(801);
                adsClient.AdsNotificationEx+=new AdsNotificationExEventHandler(adsClient_AdsNotificationEx);
                btnDeleteNotifications.Enabled = false;
                //create handles for the PLC variables;
                hbool1 = adsClient.CreateVariableHandle("MAIN.bool1");
                hdint1 = adsClient.CreateVariableHandle("MAIN.dint1");
                husint1 = adsClient.CreateVariableHandle("MAIN.usint1");
                hlreal1 = adsClient.CreateVariableHandle("MAIN.lreal1");
                hstr1 = adsClient.CreateVariableHandle("MAIN.str1");
                hstr2 = adsClient.CreateVariableHandle("MAIN.str2");
                hcomplexStruct = adsClient.CreateVariableHandle("MAIN.ComplexStruct1");
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
        adsClient.Dispose();
        }

        private void btnRead_Click(object sender, System.EventArgs e)
        {
            try
            {
                //read by handle
                //the second parameter specifies the type of the variable
                tbDint1.Text = adsClient.ReadAny(hdint1, typeof(int)).ToString();
                tbUsint1.Text = adsClient.ReadAny(husint1, typeof(Byte)).ToString();
                tbBool1.Text = adsClient.ReadAny(hbool1, typeof(Boolean)).ToString();
                tblreal1.Text = adsClient.ReadAny(hlreal1, typeof(Double)).ToString();
                //with strings one has to additionally pass the number of characters
                //specified in the PLC project(default 80).
                //This value is passed is an int array.
                tbStr1.Text = adsClient.ReadAny(hstr1, typeof(String), new int[] {80}).ToString();
                tbStr2.Text = adsClient.ReadAny(hstr2, typeof(String), new int[] {5}).ToString();
                FillStructControls((ComplexStruct)adsClient.ReadAny(hcomplexStruct, typeof(ComplexStruct)));
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void btnWrite_Click(object sender, System.EventArgs e)
        {
            try
            {
                //write by handle
                //the second parameter is the object to be written to the PLC variable
                adsClient.WriteAny(hdint1, int.Parse(tbDint1.Text));
                adsClient.WriteAny(husint1, Byte.Parse(tbUsint1.Text));
                adsClient.WriteAny(hbool1, Boolean.Parse(tbBool1.Text));
                adsClient.WriteAny(hlreal1, Double.Parse(tblreal1.Text));
                //with strings one has to additionally pass the number of characters
                //the variable has in the PLC(default 80).
                adsClient.WriteAny(hstr1, tbStr1.Text, new int[] {80});
                adsClient.WriteAny(hstr2, tbStr2.Text, new int[] {5});
                adsClient.WriteAny(hcomplexStruct, GetStructFromControls()) ;
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void btnAddNotifications_Click(object sender, System.EventArgs e)
        {
            notificationHandles.Clear();
            try
            {
                //register notification
                notificationHandles.Add(adsClient.AddDeviceNotificationEx("MAIN.dint1", AdsTransMode.OnChange, 100, 0, tbDint1, typeof(int)));
                notificationHandles.Add(adsClient.AddDeviceNotificationEx("MAIN.usint1", AdsTransMode.OnChange, 100, 0, tbUsint1, typeof(Byte)));
                notificationHandles.Add(adsClient.AddDeviceNotificationEx("MAIN.bool1", AdsTransMode.OnChange, 100, 0, tbBool1, typeof(Boolean)));
                notificationHandles.Add(adsClient.AddDeviceNotificationEx("MAIN.lreal1", AdsTransMode.OnChange, 100, 0, tblreal1, typeof(Double)));
                notificationHandles.Add(adsClient.AddDeviceNotificationEx("MAIN.str1", AdsTransMode.OnChange, 100, 0, tbStr1, typeof(String), new int[] {80}));
                notificationHandles.Add(adsClient.AddDeviceNotificationEx("MAIN.str2", AdsTransMode.OnChange, 100, 0, tbStr2, typeof(String), new int[] {5}));
                notificationHandles.Add(adsClient.AddDeviceNotificationEx("MAIN.complexStruct1", AdsTransMode.OnChange, 100, 0, tbDint1, typeof(ComplexStruct)));
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
            btnDeleteNotifications.Enabled = true;
            btnAddNotifications.Enabled = false;
        }

        private void btnDeleteNotifications_Click(object sender, System.EventArgs e)
        {
            //delete registered notifications.
            try
            {
                foreach(int handle in notificationHandles)
                adsClient.DeleteDeviceNotification(handle);
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
            notificationHandles.Clear();
            btnAddNotifications.Enabled = true;
            btnDeleteNotifications.Enabled = false;
        }

        private void adsClient_AdsNotificationEx(object sender, AdsNotificationExEventArgs e)
        {
            TextBox textBox = (TextBox)e.UserData;
            Type type = e.Value.GetType();
            if(type == typeof(string) || type.IsPrimitive)
                textBox.Text = e.Value.ToString();
            else if(type == typeof(ComplexStruct))
                FillStructControls((ComplexStruct)e.Value);
        }

        private void FillStructControls(ComplexStruct structure)
        {
            tbComplexStruct_IntVal.Text = structure.intVal.ToString();
            tbComplexStruct_dintArr.Text = String.Format("{0:d}, {1:d}, {2:d}, {3:d}", structure.dintArr[0],
            structure.dintArr[1], structure.dintArr[2], structure.dintArr[3]);
            tbComplexStruct_boolVal.Text = structure.boolVal.ToString();
            tbComplexStruct_ByteVal.Text = structure.byteVal.ToString();
            tbComplexStruct_stringVal.Text = structure.stringVal;
            tbComplexStruct_SimpleStruct1_lrealVal.Text = structure.simpleStruct1.lrealVal.ToString();
            tbComplexStruct_SimpleStruct_dintVal.Text = structure.simpleStruct1.dintVal1.ToString();
        }

        private ComplexStruct GetStructFromControls()
        {
            ComplexStruct structure = new ComplexStruct();
            String[] stringArr = tbComplexStruct_dintArr.Text.Split(new char[] {','});
            structure.intVal = short.Parse(tbComplexStruct_IntVal.Text);
            for(int i=0; i<stringArr.Length; i++)
            structure.dintArr[i] = int.Parse(stringArr[i]);

            structure.boolVal = Boolean.Parse(tbComplexStruct_boolVal.Text);
            structure.byteVal = Byte.Parse(tbComplexStruct_ByteVal.Text);
            structure.stringVal = tbComplexStruct_stringVal.Text;
            structure.simpleStruct1.dintVal1 = int.Parse(tbComplexStruct_SimpleStruct_dintVal.Text);
            structure.simpleStruct1.lrealVal = double.Parse(tbComplexStruct_SimpleStruct1_lrealVal.Text);
            return structure;
        }
    }

    // TwinCAT2 Pack1 = 1, TwinCAT3 Pack = 0
    [StructLayout(LayoutKind.Sequential, Pack=1)]
    public class SimpleStruct
    {
        public double lrealVal;
        public int dintVal1;
    }

    [StructLayout(LayoutKind.Sequential, Pack=1)]
    public class ComplexStruct
    {
        public short intVal;
        //specifies how .NET should marshal the array
        //SizeConst specifies the number of elements the array has.
        [MarshalAs(UnmanagedType.ByValArray, SizeConst=4)]
        public int[] dintArr = new int[4];
        [MarshalAs(UnmanagedType.I1)]
        public bool boolVal;
        public byte byteVal;
        //specifies how .NET should marshal the string
        //SizeConst specifies the number of characters the string has.
        //'(inclusive the terminating null ).
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst=6)]
        public string stringVal = "";
        public SimpleStruct simpleStruct1 =new SimpleStruct();
    }
}

SPS Programme:

TYPE TSimpleStruct :
STRUCT
    lrealVal: LREAL := 1.23;
    dintVal1: DINT := 120000;
END_STRUCT
END_TYPE

TYPE TComplexStruct :
STRUCT
    intVal : INT:=1200;
    dintArr: ARRAY[0..3] OF DINT:= 1,2,3,4;
    boolVal: BOOL := FALSE;
    byteVal: BYTE:=10;
    stringVal : STRING(5) := 'hallo';
    simpleStruct1: TSimpleStruct;
END_STRUCT
END_TYPE

PROGRAM MAIN
VAR
    (*primitive Types*)
    Bool1:BOOL := FALSE;
    int1:INT := 30000;
    dint1:DINT:=125000;
    usint1:USINT:=200;
    real1:REAL:= 1.2;
    lreal1:LREAL:=3.5;

    (*string Types*)
    str1:STRING := 'this is a test string';
    str2:STRING(5) := 'hallo';

    (*struct Types*)
    complexStruct1 : TComplexStruct;
END_VAR


Statusänderung vom TwinCAT-Router und der SPS erkennen

Sprache / IDE | Beispielprogram auspacken
Visual C# | Sample08.zip
Visual Basic (for .NET Framework) | -
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample08.exe
Delphi for .NET (Borland Developer Studio 2006) | -

Aufgabe

Erkennen von Statusänderungen des Routers und der SPS.

Beschreibung

Statusänderungen von ADS-Geräten können effektiv über Callback-Funktionen erkannt werden. Diese werden aufgerufen, wenn sich der Status eines ADS-Gerätes ändert.

Zur Erkennung von Statusänderungen des Routers enthält die Klasse TcAdsClient das Event TcAdsClient.AmsRouterNotification. Zur Erkennung von Statusänderungen der SPS kann eine ADS Notification auf den Status der SPS angemeldet werden. Sowohl für das Router-Event als auch für die SPS-Notification können Callback-Funktionen registriert werden, die bei den entsprechenden Statusänderungen aufgerufen werden.

Im folgenden Beispielprogramm werden die Statusänderungen des Routers und der SPS nach dem obigen Verfahren überwacht.

C# Programmm

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using TwinCAT.Ads;

namespace TwinCATAds_Sample08
{
  public partial class Form1 : Form
  {
    private TcAdsClient _tcClient = null;
    private AdsStream _adsStream = null;
    private BinaryReader _binRead = null;
    private int _notificationHandle = 0;

    public Form1()
    {
      InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
      try
      {
        _tcClient = new TcAdsClient();

        /* TwinCAT2 RTS1 Port = 801, TwinCAT3 RTS1 Port = 851 */
        _tcClient.Connect(801);

        _adsStream = new AdsStream(2); /* Stream zum Speichern des ADS Status der SPS */
        _binRead = new BinaryReader(_adsStream); /* Reader zum Auslesen des Status */

        /* Callback zur Erkennung von Statusänderungen des Routers anmelden  */
        _tcClient.AmsRouterNotification += new AmsRouterNotificationEventHandler(AmsRouterNotificationCallback);

        /* ADS Notification zur Überwachung des PLC Status anmelden */
        _notificationHandle = _tcClient.AddDeviceNotification(
                             (int)AdsReservedIndexGroups.DeviceData, /* index group des Gerätestatus */
                             (int)AdsReservedIndexOffsets.DeviceDataAdsState, /*index offset des Gerätestatus */
                             _adsStream, /* stream zum Speichern des Gerätestatus */
                             AdsTransMode.OnChange, /* transfer mode: bei Änderung */
                             0, /* bei Änderungen sofort senden */
                             0,
                             null);

        /* Callback zur Reaktion auf Notifications anmelden */
        _tcClient.AdsNotification += new AdsNotificationEventHandler(OnAdsNotification);
      }
        catch (AdsErrorException ex)
      {
        MessageBox.Show(ex.Message);
      }
    }

    /* Callback-Funktion: Wird bei Statusänderungen des Routers aufgerufen */
    void OnAdsNotification(object sender, AdsNotificationEventArgs e)
    {
      if (e.NotificationHandle == _notificationHandle)
      {
        AdsState plcState = (AdsState)_binRead.ReadInt16(); /* Status wurde in den Stream geschrieben */
       _plcLabelValue.Text = plcState.ToString();
      }
    }

    /* Callback-Funktion: Wird bei Statusänderungen der SPS aufgerufen */
    void AmsRouterNotificationCallback(object sender, AmsRouterNotificationEventArgs e)
    {
      _routerLabelValue.Text = e.State.ToString();
    }

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

    private void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
      try
      {
        _tcClient.DeleteDeviceNotification(_notificationHandle);
        _tcClient.Dispose();
      }
      catch(AdsErrorException ex)
      {
        MessageBox.Show(ex.Message);
      }
    }
  }
}


ADS-Summenkommando: Lesen oder Schreiben von mehreren Variablen

Anforderung: TwinCAT 2.11 >= Build 1550

Sprache / IDE | Beispielprogram auspacken
Visual C# | Sample09.zip
Visual Basic (for .NET Framework) | -
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample09.exe
Delphi for .NET (Borland Developer Studio 2006) | -
Dieses Beispiel zeigt, wie man unter Zuhilfenahme des ADS-Summenkommandos, viele Variablen lesen oder schreiben kann. Aufgebaut als TcAdsClient.ReadWrite dient es als Behälter, in dem die Unterkommandos in einem ADS-Stream transportiert werden.

Wichtig ist vor Beginn, dass die 'TwinCAT.Ads.dll' eingebunden wird! Hierfür einfach den 'Solution Explorer' öffnen und 'Add References...' unter 'References' auswählen. Auf den Reiter 'Browse' klicken und die DLL aus dem 'TwinCAT -> ADS API -> .NET' Verzeichnis hinzufügen.

C# Programm

1. Variablen lesen

Zu Beginn werden zwei Strukturen definiert
namespace AdsBlockRead
{
    // Structure declaration for values
    internal struct MyStruct
    {
        public ushort uintValue;
        public int dintValue;
        public bool boolValue;
    }

    // Structure declaration for handles
    internal struct VariableInfo
    {
        public int indexGroup;
        public int indexOffset;
        public int length;
    }

und ein paar Variablen global deklariert.
    public class Form1 : System.Windows.Forms.Form
    {
        [...]

        private TcAdsClient adsClient;
        private string[] variableNames;
        private int[] variableLengths;
        VariableInfo[] variables;

Beim Start der Anwendung wird eine Verbindung mit der PLC hergestellt und Handle Parameter in die Struktur geschrieben.
private void Form1_Load(object sender, System.EventArgs e)
        {
            try
            {
                // Connect to PLC
                adsClient = new TcAdsClient();
                adsClient.Connect(801);

                // Fill structures with name and size of PLC variables
                variableNames = new string[] { "MAIN.uintValue", "MAIN.dintValue", "MAIN.boolValue" };
                variableLengths = new int[] { 2, 4, 1 };

                // Write handle parameter into structure
                variables = new VariableInfo[variableNames.Length];
                for (int i = 0; i < variables.Length; i++)
                {
                    variables[i].indexGroup = (int)AdsReservedIndexGroups.SymbolValueByHandle;
                    variables[i].indexOffset = adsClient.CreateVariableHandle(variableNames[i]);
                    variables[i].length = variableLengths[i];
                }
            }
            catch (Exception err)
            {
                MessageBox.Show(err.Message);
                adsClient = null;
            }
        }

Nach einem Klick auf den 'Read' Button, gibt die 'BlockRead' Methode einen ADS Stream zurück. Es folgt das Prüfen der ADS Return Codes (Err), bevor die restlichen Daten aus dem Stream gelesen und in die Textboxen geschrieben werden.
        private void button1_Click(object sender, System.EventArgs e)
        {
            if (adsClient == null)
                return;

            try
            {
                // Get the ADS return codes and examine for errors
                BinaryReader reader = new BinaryReader(BlockRead(variables));
                for (int i = 0; i < variables.Length; i++)
                {
                    int error = reader.ReadInt32();
                    if (error != (int)AdsErrorCode.NoError)
                        System.Diagnostics.Debug.WriteLine(
                            String.Format("Unable to read variable {0} (Error = {1})", i, error));
                }

                // Read the data from the ADS stream
                MyStruct myStruct;
                myStruct.uintValue = reader.ReadUInt16();
                myStruct.dintValue = reader.ReadInt32();
                myStruct.boolValue = reader.ReadBoolean();

                // Write data from the structure into the text boxes
                tbUint.Text = myStruct.uintValue.ToString();
                tbDint.Text = myStruct.dintValue.ToString();
                tbBool.Text = myStruct.boolValue.ToString();
            }
            catch (Exception err)
            {
                MessageBox.Show(err.Message);
            }
        }

Die 'BlockRead' Methode nimmt ein Objekt vom Typ 'VariableInfo[]' entgegen, in dem die Handle Parameter enthalten sind. Nach der Reservierung von Speicher, für die zu lesenden und schreibenden Werte, wird ein ADS Stream mit den Parametern vom 'VariableInfo[]' Objekt beschrieben. Am Ende überträgt das Summenkommando die Subkommandos und ein ADS Stream wird zurück gegeben.
        private AdsStream BlockRead(VariableInfo[] variables)
        {
            // Allocate memory
            int rdLength = variables.Length * 4;
            int wrLength = variables.Length * 12;

            // Write data for handles into the ADS Stream
            BinaryWriter writer = new BinaryWriter(new AdsStream(wrLength));
            for (int i = 0; i < variables.Length; i++)
            {
                writer.Write(variables[i].indexGroup);
                writer.Write(variables[i].indexOffset);
                writer.Write(variables[i].length);
                rdLength += variables[i].length;
            }

            // Sum command to read variables from the PLC
            AdsStream rdStream = new AdsStream(rdLength);
            adsClient.ReadWrite(0xF080, variables.Length, rdStream, (AdsStream)writer.BaseStream);

            // Return the ADS error codes
            return rdStream;
        }
Die Parameter für das Summenkommando bestehen aus IndexGroup (0xF080) - Aufruf des Summenkommandos, IndexOffset (variables.Length) - Anzahl der Unterkommandos, rdDataStream (rdStream) - Speicher, der gelesene Daten entgegen nimmt, wrDataStream (writer.BaseStream) - Speicher, der zu sendende Daten enthält.

Subkommandos in wrDataStream

Response in rdDataStream

2. Variablen schreiben

Nach einem Klick auf den 'Write' Button, gibt die 'BlockRead2' Methode einen ADS Stream zurück. Es folgt das Prüfen der ADS Return Codes (Err).
private void button2_Click(object sender, EventArgs e)
        {
            if (adsClient == null)
                return;

            try
            {
                // Get the ADS return codes and examine for errors
                BinaryReader reader = new BinaryReader(BlockRead2(variables));
                for (int i = 0; i < variables.Length; i++)
                {
                    int error = reader.ReadInt32();
                    if (error != (int)AdsErrorCode.NoError)
                        System.Diagnostics.Debug.WriteLine(
                            String.Format("Unable to read variable {0} (Error = {1})", i, error));
                }
            }
            catch (Exception err)
            {
                MessageBox.Show(err.Message);
            }
        }

Die 'BlockRead2' Methode nimmt ein Objekt vom Typ 'VariableInfo[]' entgegen, in dem die Handle Parameter enthalten sind. Nach der Reservierung von Speicher, für die zu lesenden und schreibenden Werte, wird das 'MyStruct' Objekt mit den Werten der Textboxen gefüllt. Anschließend wird ein ADS Stream mit den Parametern vom 'VariableInfo[]' Objekt und den Werten vom 'MyStruct' Objekt beschrieben. Am Ende überträgt das Summenkommando die Subkommandos und ein ADS Stream wird zurück gegeben.

private AdsStream BlockRead2(VariableInfo[] variables)
        {
            // Allocate memory
            int rdLength = variables.Length * 4;
            int wrLength = variables.Length * 12 + 7;

            BinaryWriter writer = new BinaryWriter(new AdsStream(wrLength));
            MyStruct myStruct;
            myStruct.uintValue = ushort.Parse(tbUint2.Text);
            myStruct.dintValue = int.Parse(tbDint2.Text);
            myStruct.boolValue = bool.Parse(tbBool2.Text);

            // Write data for handles into the ADS stream
            for (int i = 0; i < variables.Length; i++)
            {
                writer.Write(variables[i].indexGroup);
                writer.Write(variables[i].indexOffset);
                writer.Write(variables[i].length);
            }

            // Write data to send to PLC behind the structure
            writer.Write(myStruct.uintValue);
            writer.Write(myStruct.dintValue);
            writer.Write(myStruct.boolValue);

            // Sum command to write the data into the PLC
            AdsStream rdStream = new AdsStream(rdLength);
            adsClient.ReadWrite(0xF081, variables.Length, rdStream, (AdsStream)writer.BaseStream);

            // Return the ADS error codes
            return rdStream;
        }

Die Parameter für das Summenkommando bestehen aus IndexGroup (0xF081) - Aufruf des Summenkommandos, IndexOffset (variables.Length) - Anzahl der Unterkommandos, rdDataStream (rdStream) - Speicher, der gelesene Daten entgegen nimmt, wrDataStream (writer.BaseStream) - Speicher, der zu sendende Daten enthält.

Subkommandos in wrDataStream

Response in rdDataStream


ADS-Summenkommando: Lesen oder Schreiben von mehreren Variablen

TwinCAT ADS .NET Komponente: Beispiele

Auslesen von SMB-Werten aus dem TwinCAT I/O Treiber

Download

Language / IDE | Extract the sample program
Visual C# | -
Visual Basic (for .NET Framework) | Sample10.exe
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | -
Delphi for .NET (Borland Developer Studio 2006) | -

TwinCAT CP ist ein kostenfreies TwinCAT-Level zur Kommunikation mit den Beckhoff Control Panels bzw. Control Panel-PC's C6xxx und C7xxx, den industriellen Bedien- und Anzeigegeräten und deren Tastererweiterungen von Beckhoff.
Neben der Unterstützung der Control Panels und deren Tastererweiterungen, ist TwinCAT CP dahingehend erweitert worden, um zum Beispiel auch auf das SMB (System Management Bus)-Interface der unterstützten Motherboards zugreifen zu können.

Falls statt einer TwinCAT PLC Laufzeitumgebung nur TwinCAT-CP zur Verfügung steht, kann das SMB-Device auch mit Hilfe des ADS-Clients in (z.B.) Visual Basic ausgelesen werden. Die Visualisierung erlaubt ein ständiges Überwachen der gewünschten Temperatur- oder Lüfterwerte. Somit kann frühzeitig erkannt werden, ob sich die aktuelle Temperatur einem kritischen Wert annähert. Nachfolgend ein Beispiel einer möglichen Implementierung in Visual Basic.NET.

Bestelloption:

TwinCAT-CP | kostenfreie Lizenz zur Nutzung des Treibers für Beckhoff Control Panel zur Kommunikation zwischen allgemeinen Windows-Programmen

Erforderliche Software:

  • Microsoft.NET Framework Version 2.0: http://msdn2.microsoft.com
  • Microsoft Visual Studio 2005 oder 2008: http://msdn2.microsoft.com
    • um das Programm „Motherboard Diagnosis“ zu öffnen (siehe unten)
  • TwinCAT 2.10: http://www.beckhoff.de/tcatweb/tcatdowd.htm
    • um Level CP zu installieren: Bei einer Neuinstallation von TwinCAT muss beim Setup 'TwinCAT CP' als “Installation Level” ausgewählt werden (alternativ kann natürlich auch jedes andere angebotene TwinCAT Level zur Kommunikation per untenstehendem Beispielprogramm verwendet werden).
      Achtung: Bei einem Update von TwinCAT ist ein Up- oder Downgrade der TwinCAT-Installation nicht möglich. D.h., das vorher ausgewählte Level kann ohne Deinstallation und anschliessender Neuinstallation nicht auf TwinCAT CP geändert werden.

Visual Basic (for .NET Framework) Programm

Beispiel: Temperatur

1. Referenz hinzufügen

Zunächst muss eine Referenz zur TwinCAT.Ads.dll hinzugefügt werden. Die TwinCAT.ADS.NET Komponente wird mit einem Rechtsklick auf das Projekt → Add Reference eingebunden.

2. Bestimmen der Geräteadresse (hier Index-Group)

Private Sub MotherboardDiagnosis_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
    Try
        'Create a new instance of class TcAdsClient
                    tcClient = New TwinCAT.Ads.TcAdsClient()

        'Connect to local PLC - Runtime 1 - Port 300
        tcClient.Connect(300)

        'Gets the device count of devices appended to active configuration if
        'TwinCAT is in run mode
        Dim state = tcClient.ReadState.AdsState

        If Not state = AdsState.Run Then
            txtCurrent.Text = "Bad TwinCAT state ("& state.ToString() &").
            Please restart application."
        Else
            Dim deviceCount As UInt32
            deviceCount = tcClient.ReadAny(&H5000, &H2,
            deviceCount.GetType())

            'Gets the device count of devices appended to active
            'configuration as zero-based index
            'and gets the device ID's of all active devices for the
            'remaining indices (inverted sort)
            Dim arrayDeviceIDs(deviceCount) As UInt16
            arrayDeviceIDs = tcClient.ReadAny(&H5000, &H1,
            arrayDeviceIDs.GetType(), New Integer() {deviceCount + 1})
            For i As Integer = 1 To (deviceCount)
                'gets the device identification number (Motherboard
                'System Management Bus(SMB) => 32)
                Dim devIdent = tcClient.ReadAny(arrayDeviceIDs(i) +
                &H5000, &H7, arrayDeviceIDs(i).GetType)

                'checks if the device type is a SMB-Type
                If devIdent = 32 Then
                    _iIndexGroup = &H9000 + arrayDeviceIDs(i)
            Exit For
        End If

        Next
        'Enable timer to start monitoring with determined IGroup
        tmrTimer1.Enabled = True
    End If

    Catch ex As Exception
        txtCurrent.Text = ex.Message
    End Try
End Sub

3. Bestimmen der Variablen Adresse (hier Index-Offset)

Um den Index-Offset der Variablen zu bestimmen kann der TwinCAT System Manager als Hilfsmittel verwendet werden. Durch Anklicken des gewünschten Inputs (links) wird im unteren Bereich des rechten Fensters der gewünschte Wert als hexadezimal angezeigt:

4. Zyklisches Lesen der Werte

Private Sub tmrTimer1_Tick(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles tmrTimer1.Tick
    Try
        'IOffset may be adjusted to monitor different inputs e.g. fan-speeds
        'or voltages
        _iTemp = tcClient.ReadAny(_iIndexGroup, 0, _iTemp.GetType())
        ProgressBar1.Value = _iTemp
        lblCurrent.Text = ("Current: " + (Convert.ToString(_iTemp)) + "°C")

        Catch ex As Exception
            'Catches if certain circumstances turned TwinCAT to offline mode.
            'Restarts monitoring if configuration remains unchanged and TwinCat
            'is turned online again.
            If ex.Message = "Ads-Error 0x12 : Port is disabled." Then
                lblCurrent.Text = "Ports are disabled. TwinCAT may be in
                offline mode."
                ProgressBar1.Value = 0
            Else
                MessageBox.Show(ex.Message)
            End If
    End Try
End Sub

Beispiel: Resultierende Messagebox bei Überschreitung der definierten Temperaturgrenze


Beckhoff Information System - German

TwinCAT ADS .NET Komponente: Beispiele

Handle einer SPS Variablen löschen

Download

Sprache / IDE | Beispielprogram auspacken
Visual C# | Sample11.zip
Visual Basic (for .NET Framework) | -
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample11.exe
Delphi for .NET (Borland Developer Studio 2006) | -

Beschreibung

In diesem Beispiel wird das Handle einer SPS Variablen gelöscht:

C# Programm

static void Main(string[] args)
{
    //Create a new instance of class TcAdsClient
    TcAdsClient tcClient = new TcAdsClient();
    int iHandle = 0;

    try
    {
        // Connect to local PLC - Runtime 1 - TwinCAT2 Port=801, TwinCAT3 Port=851
        tcClient.Connect(801);

        //Get the handle of the PLC variable "PLCVar"
        iHandle = tcClient.CreateVariableHandle("MAIN.PLCVar");

        //Release the specific handle of "PLCVar"
        tcClient.DeleteVariableHandle(iHandle);
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        Console.ReadKey();
    }
    finally
    {
        tcClient.Dispose();
    }
}


Beckhoff Information System - German

TwinCAT ADS .NET Komponente: Beispiele

Merker synchron aus der SPS lesen

Download

Sprache / IDE | Beispielprogram auspacken
Visual C# | Sample12.zip
Visual Basic (for .NET Framework) | -
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample12.exe
Delphi for .NET (Borland Developer Studio 2006) | -

Beschreibung

Bei diesem Beispielprogramm wird der Wert aus dem Merkerdoppelwort 0 der SPS ausgelesen und am Bildschirm angezeigt:

C# Programm

static void Main(string[] args)
{
    //Create a new instance of class TcAdsClient
    TcAdsClient tcClient = new TcAdsClient();

    try
    {
        // Connect to local PLC - Runtime 1 - TwinCAT2 Port=801, TwinCAT3 Port=851
        tcClient.Connect(801);

        //Specify IndexGroup, IndexOffset and read SPSVar
        int iFlag = (int)tcClient.ReadAny(0x4020, 0x0, typeof(Int32));

        Console.WriteLine("" + iFlag);
        Console.ReadKey();
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        Console.ReadKey();
    }
    finally
    {
        tcClient.Dispose();
    }
}


Beckhoff Information System - German

TwinCAT ADS .NET Komponente: Beispiele

Merker synchron in die SPS schreiben

Download

Sprache / IDE | Beispielprogram auspacken
Visual C# | Sample13.zip
Visual Basic (for .NET Framework) | -
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample13.exe
Delphi for .NET (Borland Developer Studio 2006) | -

Beschreibung

Bei diesem Beispielprogramm wird der Wert, den der Bediener eingegeben hat, in das Merkerdoppelwort 0 geschrieben:

C# Programm

static void Main(string[] args)
{
    //Create a new instance of class TcAdsClient
    TcAdsClient tcClient = new TcAdsClient();

    try
    {
        // Connect to local PLC - Runtime 1 - TwinCAT2 Port=801, TwinCAT3 Port=851
        tcClient.Connect(801);

        //Specify IndexGroup, IndexOffset and write SPSVar
        int iNewValue = 0;
        tcClient.WriteAny(0x4020, 0x0, iNewValue);
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        Console.ReadKey();
    }
    finally
    {
        tcClient.Dispose();
    }
}


Beckhoff Information System - German

TwinCAT ADS .NET Komponente: Beispiele

SPS starten/stoppen

Download

Sprache / IDE | Beispielprogram auspacken
Visual C# | Sample14.zip
Visual Basic (for .NET Framework) | -
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample14.exe
Delphi for .NET (Borland Developer Studio 2006) | -

Beschreibung

Mit dem folgenden Programm wird das Laufzeitsystem 1 der SPS gestartet, bzw. gestoppt:

C# Programm

static void Main(string[] args)
{
    //Create a new instance of class TcAdsClient
    TcAdsClient tcClient = new TcAdsClient();

    try
    {
        // Connect to local PLC - Runtime 1 - TwinCAT2 Port=801, TwinCAT3 Port=851
        tcClient.Connect(801);

        Console.WriteLine(" PLC Run\t[R]");
        Console.WriteLine(" PLC Stop\t[S]");
        Console.WriteLine("\r\nPlease choose \"Run\" or \"Stop\" and confirm with enter..");
        string sInput = Console.ReadLine().ToLower();

        //Process user input and apply chosen state
        do{
            switch (sInput)
            {
                case "r": tcClient.WriteControl(new StateInfo(AdsState.Run, tcClient.ReadState().DeviceState)); break;
                case "s": tcClient.WriteControl(new StateInfo(AdsState.Stop, tcClient.ReadState().DeviceState)); break;
                default: Console.WriteLine("Please choose \"Run\" or \"Stop\" and confirm with enter.."); sInput = Console.ReadLine().ToLower(); break;
            }
        } while (sInput != "r" && sInput != "s");
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        Console.ReadKey();
    }
    finally
    {
        tcClient.Dispose();
    }
}


Beckhoff Information System - German

TwinCAT ADS .NET Komponente: Beispiele

Zugriff per Variablenname

Download

Sprache / IDE | Beispielprogram auspacken
Visual C# | Sample15.zip
Visual Basic (for .NET Framework) | -
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample15.exe
Delphi for .NET (Borland Developer Studio 2006) | -

Beschreibung

Das folgende Programm greift auf eine SPS-Variable zu, die keine Adresse besitzt. Der Zugriff muss deshalb per Variablenname erfolgen. Die SPS-Variable wird vom Beispielprogramm wieder auf 0 zurückgesetzt, wenn diese den Wert 10 überschritten hat.

C# Programm

static void Main(string[] args)
{
    //Create a new instance of class TcAdsClient
    TcAdsClient tcClient = new TcAdsClient();
    AdsStream dataStream = new AdsStream(4);
    AdsBinaryReader binReader = new AdsBinaryReader(dataStream);

    int iHandle = 0;
    int iValue = 0;

    try
    {
        // Connect to local PLC - Runtime 1 - TwinCAT2 Port=801, TwinCAT3 Port=851
        tcClient.Connect(801);

        //Get the handle of the PLC variable "PLCVar"
        iHandle = tcClient.CreateVariableHandle("MAIN.PLCVar");

        Console.WriteLine("Press enter to continue and any other key to abort..");

        do
        {
            //Use the handle to read PLCVar
            tcClient.Read(iHandle, dataStream);
            iValue = binReader.ReadInt32();
            dataStream.Position = 0;

            Console.WriteLine("Current value is: " + iValue);

            if (iValue >= 10)
            {
                //Reset PLC variable to zero
                tcClient.WriteAny(iHandle, 0);
            }

        } while (Console.ReadKey().Key.Equals(ConsoleKey.Enter));
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        Console.ReadKey();
    }
    finally
    {
        //Delete variable handle
        tcClient.DeleteVariableHandle(iHandle);
        tcClient.Dispose();
    }
}


TwinCAT ADS Web Service: C# Beispiel

Consumer in C# zum Lesen und Schreiben von SPS-Variablen

In diesem Beispiel wird erklärt, wie ein ADS-WebService-Consumer zum Lesen und Schreiben von SPS-Variablen via ADS-HTTP erstellt wird.

Erstellen eines WebService-Consumers mit Hilfe des Microsoft Visual Studio 2005

1. Neues Projekt erstellen:

Starten Sie das Visual Studio 2005.
Erstellen Sie ein neues Projekt, indem Sie im Menü -> File -> New -> Project klicken. Im Dialogfeld New Project können Sie ein neues Projekt auf der Basis verschiedener Vorlagen erstellen.
Hier wählen Sie unter Project Types die Programmiersprache Visual C# und unter den Vorlagen Windows Application. Geben Sie Ihrem Projekt ein neuen Namen, in diesem Fall bitte den Namen TcAdsWebService01 eingeben. Wählen Sie dann den Verzeichnispfad unter Location in dem Sie ihr Arbeitsprojekt erstellen wollen.
  • Durch Auswahl der Vorlage Windows Application ist die neue Applikation ausschließlich auf vollständigen .NET Plattformen lauffähig.
  • Durch Auswahl der Vorlage Smart Device Applikation ist die neue Applikation auf vollständigen .NET Plattformen und auf .NET-CompactFramework (CE-) Plattformen lauffähig.
    Durch Auswahl der NET-CompactFramework (CE-) Plattform ergeben sich Einschränkungen.

2. Bedienungsoberfläche erstellen

Hierzu wird zuerst eine Oberfläche in dem Design Modus der Form Form1 mit der Toolbox erstellt. Die Einstellungen der verschiedenen Controls (wie z.B. 6 Bezeichnungsfelder , 6 Textfelder , 2 Schaltflächen) können Sie im Properties Window des Visual Studio einsehen und verändern.
// Bezeichnungsfelder (Label)
lblWebServiceUrl.Text = "WebServiceUrl:";
lblAdsPort.Text = "ADS Port:";
lblAmsNetID.Text = "Ams Net ID:";
lblBool.Text = "VarBool:";
lblInt.Text = "VarInt:";
lblString.Text = "VarString:";

// Textfelder (TextBox)
txtWebServiceUrl.Text = "";
txtAdsPort.Text = "";
txtAmsNetId.Text = "";
txtBool.Text = "";
txtInt.Text = "";
txtString.Text = "";

// Schaltflächen (Button)
btnRead.Text = "&Read";
btnWrite.Text = "&Write";

3. Web Referenz hinzufügen

Wählen Sie nach dem Erstellen des Projekts unter Projects --> Add Web Reference....
Geben Sie im darauf erscheinenden Fenster die URL der WebService-WSDL-Datei an oder lassen Sie mit Web services on the local machine danach suchen.

Geben Sie der Referenz einen Namen und klicken Sie auf Add Reference.

4. Quelltext bearbeiten

Instanzen
Nach dem Erstellen der Oberfläche und das Einbinden des TwinCAT ADS Webservice kann zum C# Quelltext gewechselt werden. In die oberste Zeile des Quelltextes wird die benötigte Namespace System.Web.Services.Protocols eingefügt, die später die SOAP-Exceptions abfängt.
using System.Web.Services.Protocols;

Als nächstes folgen die Deklarationen innerhalb der Klasse Form1. Zuerst müssen Sie die Instanz zu dem TwinCAT ADS Webservice herstellen, um in Ihrem Programm ein WebService-Objekt zur Verfügung zu haben:
TcAdsWebService.TcAdsWebService TcWebService = new TcAdsWebService.TcAdsWebService();

Hier wird eine Instanz der Klasse System.Text.ASCIIEncoding benötigt, um das Byte-Array in ein String umzuwandeln und umgekehrt.
private System.Text.ASCIIEncoding encoder = new System.Text.ASCIIEncoding();

Variablen
private string szWebServiceUrl = "http://localhost/TcAdsWebService/TcAdsWebService.dll";
public string szAmsNetId = "192.168.0.2.1.1";
public int iPort = 801;
public UInt32 iIndexGroup = 0x4020;
Mehr Information zu den Themen AmsNetID, ADS Ports und Laufzeitsystemen finden Sie unter: Identifikation Ads-Gerät

Der Syntax der URL sollte dementsprechend angepasst werden, zum Beispiel:
// Beispiel 1:
TcWebService.Url = "http://192.168.0.2/TcAdsWebService/TcAdsWebService.dll";

// Beispiel 2:
TcWebService.Url = "http://CX_xxxxxx/TcAdsWebService/TcAdsWebService.dll";
Die Adresse muss die URL Ihrer TwinCAT ADS WebService-DLL-Datei angeben.

Konstruktor
Der Konstruktor public Form1() wird beim Aufruf der Windows Applikation gestartet. Hier werden die Steuerelemente mit der Methode InitializeComponent() initialisiert. Zusätzlich muss hier die Variable szWebServiceUrl, in der die Information der WebService-URL vorhanden ist, mit dem TwinCAT ADS Webservice verbunden werden:
 TcWebService.Url = szWebServiceUrl;

Methoden
Hier fehlen noch zwei Click-Methoden der Schaltflächen, in ihnen werden die Methode Read zum Lesen der SPS-Variablen und Write zum Schreiben der SPS-Variablen initialisiert. Hier werden vorher die benötigten Variablen, wie Adresse der TcAdsWebservie-URL, die Ams Net ID des Gerätes, sowie Port 801 des SPS Laufzeitsystems 1 übergeben, welche bereits definiert wurden.
//  Click-Methode für die Schaltfläche btnRead
private void btnRead_Click(object sender, System.EventArgs e)
{
    szWebServiceUrl = txtWebServiceUrl.Text;
    szAmsNetId = txtAmsNetId.Text;
    iPort = Convert.ToInt32(txtAdsPort.Text);
    Read();
}

// Click-Methode für die Schaltfläche btnWrite
private void btnWrite_Click(object sender, System.EventArgs e)
{
    szWebServiceUrl = txtWebServiceUrl.Text;
    szAmsNetId = txtAmsNetId.Text;
    iPort = Convert.ToInt32(txtAdsPort.Text);
    Write();
}
Zum Schluss muss noch dafür gesorgt werden, dass die Methode btnRead_Click und btnWrite_Click auch beim richtigen Ereignis aufgerufen werden. Dazu wechseln Sie in die Design Ansicht, markieren die Schaltfläche btnRead , klicken im Properities Window bei Events auf Click und wählen dann die Methode btnRead_Click. Gleichegilt für die Schaltfläche btnWrite mit der Methode btnWrite_Click.

Konvertieren von Variablen
Die Werte der Variablen werden als Byte-Array gelesen und geschrieben. Es ist somit erforderlich, die Werte der Variablen in Byte-Arrays und zurück konvertieren zu können.

Byte-Arrays in lesbare Variablen-Werte konvertieren
Der ausgelesene Datenpuffer aus der SPS wird wie folgt aufgeteilt: 1 Byte für PlcVarBool, 2 Bytes für PlcVarInt und 81 Bytes für PlcVarString = 84 Bytes.
byte[] abDataBuffer = new byte[84];

Das Konvertieren von Byte-Arrays in lesbare Werte der Variablen von Datentyp int und bool lässt sich mit der Klasse BitConverter realisieren. Byte-Arrays in String-Variablen werden mit der Klasse System.Text.ASCIIEncoding umgewandelt.

// Konvertiert den ersten Byte von dem Byte-Array in ein bool-Variable
bool bVarBool = BitConverter.ToBoolean(abDataBuffer,0);

// Konvertiert den zweiten und dritten Byte von dem Byte Array in int-Variable
int iVarInt = BitConverter.ToInt16(abDataBuffer,1);

// Konvertiert den anderen Byte von dem Byte Array in string-Variable
System.Text.ASCIIEncoding encoder = new System.Text.ASCIIEncoding();
string szVarString = encoder.GetString(abDataBuffer, 3, 81);

Lesbare Variablen-Werte in Byte-Arrays konvertieren
Das Konvertieren von lesbaren Werten der Variablen von Datentyp int und bool in Byte-Array lässt sich mit der Klasse BitConverter realisieren. String-Variablen in Byte-Arrays werden mit der Klasse System.Text.ASCIIEncoding umgewandelt.

// Byte-Arrays für die verschiedenen Variablen
byte[] abDataBool   = new byte[1];
byte[] abDataInt       = new byte[2];
byte[] abDataString = new byte[81];

// Konvertiert die Werte der verschiedenen Variablen in Byte-Arrays
abDataBool = BitConverter.GetBytes(bValueBool);
abDataInt = BitConverter.GetBytes((Int16)iValueInt);

System.Text.ASCIIEncoding encoder = new System.Text.ASCIIEncoding();
encoder.GetBytes(szValueString, 0, encoder.GetByteCount(szValueString), abDataString, 0);

Genauere Informationen zu diesen Methoden und Klassen finden Sie in der entsprechenden Fachliteratur zu C#.

SPS-Variablen auslesen
Zum Auslesen der SPS-Variablen benutzen Sie die Read-Methode des WebService-Objektes:
TcWebService.Read(string netId, int nPort, System.UInt32 indexGroup, System.UInt32 indexOffset, int cblen, out System.Byte[] ppData);
  • netId: String der die AMS-Net-Id angibt, auf der die SPS zu finden ist
  • nPort: Port-Nummer des Laufzeitsystems
  • indexGroup: IndexGroup der zu lesenden Variablen
  • indexOffset: Erstes Byte, das gelesen werden soll
  • cblen: Anzahl der Bytes, die gelesen werden sollen
  • ppData: Byte-Array, in das die SPS-Daten geschrieben werden sollen.
Beispiel:
TcWebService.Read("192.168.0.2.1.1", 801, 0x4020, 0, 84, out abDataBuffer);

SPS-Variablen schreiben
Zum Schreiben von SPS-Variablen benutzen Sie die Write-Methode des WebService-Objektes:
TcWebService.Write(string netId, int nPort, System.UInt32 indexGroup, System.UInt32 indexOffset, System.Byte[] pData);
  • netId: String der die AMS-Net-Id angibt, auf der die SPS zu finden ist
  • nPort: Port-Nummer des Laufzeitsystems
  • indexGroup: IndexGroup der SPS- Variablen
  • indexOffset: Erstes Byte, in das geschrieben werden soll
  • pData: Byte-Array, das die zu schreibenden Daten enthält.
Beispiel:
byte[] Data = new byte[10];
TcWebService.Write("192.168.0.2.1.1", 801, 0x4020, 0, Data);

Zusammengefasst sehen dann die Methoden Read() und Write() wie folgt aus:
private void Read()
{
    // Der ausgelesene Datenpuffer aus der SPS wird wie folgt aufgeteilt
    // 1 Byte für 'PlcVarBool',
    // 2 Bytes für 'PlcVarInt'
    // und 81 Bytes für 'PlcVarString' = 84 Bytes.
    byte[] abDataBuffer = new byte[84];

    try
    {
        TcWebService.Read(szAmsNetId, iPort, iIndexGroup, 0, 84, out abDataBuffer);

        // Konvertiert den ersten Byte von dem Byte-Array in ein bool-Variable
        bool bVarBool = BitConverter.ToBoolean(abDataBuffer,0);

        // Konvertiert den zweiten und dritten Byte von dem Byte Array in int-Variable
        int iVarInt = BitConverter.ToInt16(abDataBuffer,1);

        // Konvertiert den anderen Byte von dem Byte Array in string-Variable
        string szVarString = encoder.GetString(abDataBuffer, 3, 81);

        // schreibt die Werte in die Textfelder
        txtBool.Text = bVarBool.ToString();
        txtInt.Text = iVarInt.ToString();
        txtString.Text = szVarString;
    }
    catch (SoapException ex)
    {
        MessageBox.Show(ex.Message);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

private void Write()
{
    try
    {
        // Byte-Arrays für die verschiedenen Variablen
        byte[] abDataBool = new byte[1];
        byte[] abDataInt = new byte[2];
        byte[] abDataString = new byte[81];

        // Werte von Textfelder erhalten
        bool bValueBool = Convert.ToBoolean(txtBool.Text);
        int iValueInt = Convert.ToInt16(txtInt.Text);
        string szValueString = txtString.Text;

        // Konvertiert die Werte der verschiedenen Variablen in Byte-Arrays
        abDataBool = BitConverter.GetBytes(bValueBool);
        abDataInt = BitConverter.GetBytes((Int16)iValueInt);

        encoder.GetBytes(szValueString,			// Source
                            0,				// Position of the first character to convert
                            encoder.GetByteCount(szValueString),// Gets the length of the source
                            abDataString,			// Byte-array to save the conversion
                            0);				// First byte in the array to save the conversion

        // Schreibt die Werte in SPS
        TcWebService.Write(szAmsNetId, iPort, iIndexGroup, 0, abDataBool);
        TcWebService.Write(szAmsNetId, iPort, iIndexGroup, 1, abDataInt);
        TcWebService.Write(szAmsNetId, iPort, iIndexGroup, 3, abDataString);
    }
    catch (SoapException ex)
    {
        MessageBox.Show(ex.Message);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

5. SPS-Programm, IEC1131 Variablendeklaration

PROGRAM MAIN
VAR
    PlcVarBool	AT %MX0.0	: BOOL := TRUE;
    PlcVarInt 	AT %MW1	: INT := 1234;
    PlcVarString	AT %MD3	: STRING := 'Hallo Automation';
END_VAR

6. C#-Applikation, das Layout :

7. C# Applikation und SPS Quelltext Download:

Sprache / IDE | Beispielprogram auspacken
Visual C# | Sample01.exe


TwinCAT ADS Web Service: Delphi 8 .NET Beispiel

TwinCAT ADS Web Service: Delphi Beispiel

Consumer in Delphi 8 for .NET zum Lesen und Schreiben von SPS-Variablen

In diesem Beispiel wird erklärt, wie ein ADS-WebService-Consumer zum Lesen und Schreiben von SPS-Variablen via ADS-HTTP erstellt wird.

Voraussetzungen:

  • Delphi 8 für Microsoft .NET Framework;
  • Webservice Consumer für Windows XP-Plattform (Windows Forms .NET Application);
  • Webservice Server wurde auf einem Embeded PC: CX9000 mit Windows CE (ARM) konfiguriert.

Import in Delphi

Die Schnittstellenbeschreibung des Servers wird in einer WSDL-Datei (Web Service Definition Language) veröffentlicht. Die WSDL-Beschreibung wird in Delphi importiert und von Delphi in Delphi-Units konvertiert.

Erstellen Sie als erstes ein neues Projekt (Windows Forms-Anwendung Delphi .NET). Speichern Sie das Projekt. Als nächstes können Sie die WSDL-Datei importieren. Klicken Sie im Projektmanager mit der rechten Maustaste auf den Projektnamen und wählen "Webreferenz hinzufügen" aus.

Es erscheint ein Dialog mit Webservice-Verzeichnissen von IBM, Microsoft usw. Wir wollen aber die WSDL-Datei vom Datenträger öffnen.

Die WSDL-Dateien vom TwinCAT ADS Webservice finden Sie im Ordner: ..TwinCAT\Ads API\TcAdsWebService\V100\... Navigieren Sie in den entsprechenden Ordner und wählen sie die WSDL-Datei aus. In userem Fall ist es die WSDL-Datei aus dem Ordner für die Windows CE (ARM) Zielplattform.

Hinweis:

Es gibt keine Unterschiede bei den WSDL-Dateien für die XP/CE (X86 oder ARM) Plattform. D.h. es handelt sich überal um die gleiche WSDL-Datei. Sie können die WSDL-Datei aus dem CE/ARM-Ordner in Ihr Consumer-Projekt einbinden und trotzdem mit einem ADS WebService-Server auf einer XP-Plattform kommunizieren.

Der Inhalt der WSDL-Datei wird in einem Dialogfenster angezeigt.

Wählen Sie "Webreferenz hinzufügen" aus. Die benötigten Units werden automatisch generiert und zum Projekt hinzugefügt.

Webservice verwenden

Um den Webservice verwenden zu können müssen Sie die Webservice-Unit in die Unit des Hauptfensters einbinden. Bei der Gelegenheit können Sie gleich noch zwei weitere Units einbinden die Sie später in Ihrem Projekt benötigen werden einbinden: System.Text und System.Web.Services.Protocols

uses
System.Drawing, System.Collections, System.ComponentModel,
System.Windows.Forms, System.Data, WebReference.TcAdsWebService, System.Text,
System.Web.Services.Protocols;

Jetzt können Sie eine ADS Webservice-Instanz erzeugen.

 private
{ Private Declarations }
TcWebService : WebReference.TcAdsWebService.TcAdsWebService;

Und im Konstruktor von TWinForm:

TcWebService := WebReference.TcAdsWebService.TcAdsWebService.Create();

Die Codevervollständigung hilft Ihnen weiter. Tippen Sie TcWebService mit einem Punkt ein und sehen Sie sich die zur Verfügung stehenden Methoden an.

Wir benutzen in unserem Beispiel folgende Methoden:

  • Read zum Lesen der SPS-Variablen;
  • Write zum Schreiben der SPS-Variablen;
  • ReadState zum Lesen des SPS-Server-Status;
  • WriteControl zum Ändern des SPS-Server-Status;
type TArrayOfByte = array of Byte;

procedure Read(netId: string; nPort: Integer; indexGroup: Cardinal; indexOffset: Cardinal; cbLen: Integer; out ppData: TArrayOfByte);
procedure Write(netId: string; nPort: Integer; indexGroup: Cardinal; indexOffset: Cardinal; pData: TArrayOfByte);
procedure ReadState(netId: string; nPort: Integer; out pAdsState: Integer; out pDeviceState: Integer);
procedure WriteControl(netId: string; nPort: Integer; adsState: Integer; deviceState: Integer; pData: TArrayOfByte);

netId: String mit der Netzwerkadresse des TwinCAT Zielsystems. In unserem Fall die TwinCAT AMS NetID des PC's mit dem SPS-Laufzeitsystem;

nPort: TwinCAT ADS Port-Nummer des SPS-Laufzeitsystems;

indexGroup: Index-Group der zu lesenden SPS-Variablen;

indexOffset: Index-Offset (Byte-/Bit-Offset) der der SPS-Variablen die gelesen werden soll;

cbLen: Anzahl der Bytes, die gelesen werden sollen;

ppData: Variable in die die gelesenen Daten gespeichert werden (dynamisches Byte-Array);

pData: Variable mit den zu schreibenden Daten (dynamisches Array);

adsState / pAdsState: PLC Serverstatus.

deviceState / pDeviceState: PLC Gerätestatus;

Die SPS Applikation

PROGRAM MAIN
VAR
    VarBool		AT %MX0.0:	BOOL := TRUE;
    VarInt		AT %MB1:	INT := 123;
    VarString	AT %MB3:	STRING := 'Hello Automation';
END_VAR

SPS-Variablen auslesen

procedure TWinForm.ReadData();
var cbLen, nIG, nIO   : Integer;
    data    : TArrayOfByte;
begin
  try
    // read BOOL at adress AT%MX0.0
    nIG   := $4021;
    nIO   := 0;
    cbLen := SizeOf(varBool);
    TcWebService.Read( sNetID, iPort, nIG, nIO, cbLen, data );
    varBool         := BitConverter.ToBoolean(data, 0);
    // read INT at adress AT%MB1
    nIG   := $4020;
    nIO   := 1;
    cbLen := SizeOf(varInt);
    TcWebService.Read( sNetID, iPort, nIG, nIO, cbLen, data );
    varInt          := BitConverter.ToInt16(data, 0);
    // read STRING at adress AT%MB3
    nIG   := $4020;
    nIO   := 3;
    cbLen := 81;
    TcWebService.Read( sNetID, iPort, nIG, nIO, cbLen, data );
    varString := System.Text.Encoding.Default.GetString(data,0, Length(data));
  except
    On E:SoapException do
      HandleSoapException(E.Message, E.HelpLink, E.Detail.InnerText);
  end;
end;

SPS Variablen schreiben

procedure TWinForm.WriteData();
var data    : TArrayOfByte;
    cbLen, nIG, nIO : Integer;
begin
  try
    // write BOOL at adress AT%MX0.0
    nIG   := $4021;
    nIO   := 0;
    data  := BitConverter.GetBytes(varBool);
    TcWebService.Write( sNetID, iPort, nIG, nIO, data );
    // write INT at adress AT%MB1
    nIG   := $4020;
    nIO   := 1;
    data  := BitConverter.GetBytes(varInt);
    TcWebService.Write( sNetID, iPort, nIG, nIO, data );
    // write STRING at adress AT%MB3
    nIG   := $4020;
    nIO   := 3;
    cbLen := System.Text.Encoding.Default.GetByteCount(varString);
    // Allocate one byte more! We need it to write the string end delimiter!.
    SetLength(data, cbLen + 1);
    System.Text.Encoding.Default.GetBytes( varString, 0, cbLen, data, 0);
    data[cbLen] := 0;
    TcWebService.Write( sNetID, iPort, nIG, nIO, data );
  except
    On E:SoapException do
      HandleSoapException(E.Message, E.HelpLink, E.Detail.InnerText);
  end;
end;

SPS Status lesen

procedure TWinForm.ReadState();
var adsState, deviceState : Integer;
sState : String;
begin
  try
    TcWebService.ReadState( sNetID, iPort, adsState, deviceState );
    case adsState of
      PLC_ADSSTATE_RUN:
        sState := 'RUN';
      PLC_ADSSTATE_STOP:
        sState := 'STOP';
    else
      sState := 'OTHER';
    end;
    StatusBar1.Panels.Item[0].Text := 'PLC server state: ' + sState;
  except
    On E:SoapException do
      HandleSoapException(E.Message, E.HelpLink, E.Detail.InnerText);
  end;
end;

SPS Starten/Stoppen

procedure TWinForm.WriteControl(adsState : Integer);
begin
  try
    TcWebService.WriteControl( sNetID, iPort, adsState, 0, nil );
  except
    On E:SoapException do
      HandleSoapException(E.Message, E.HelpLink, E.Detail.InnerText);
  end;
end;

Language / IDE | Extract the sample program
Delphi 8 for .NET | Sample01.exe