Notes on the documentation

This description is only intended for the use of trained specialists in control and automation engineering who are familiar with the applicable national standards.
It is essential that the following notes and explanations are followed when installing and commissioning these components.

The responsible staff must ensure that the application or use of the products described satisfy all the requirements for safety, including all the relevant laws, regulations, guidelines and standards.

Disclaimer

The documentation has been prepared with care. The products described are, however, constantly under development.
For that reason the documentation is not in every case checked for consistency with performance data, standards or other characteristics.
In the event that it contains technical or editorial errors, we retain the right to make alterations at any time and without warning.
No claims for the modification of products that have already been supplied may be made on the basis of the data, diagrams and descriptions in this documentation.

Trademarks

Beckhoff®, TwinCAT®, EtherCAT®, Safety over EtherCAT®, TwinSAFE® and XFC® are registered trademarks of and licensed by Beckhoff Automation GmbH.
Other designations used in this publication may be trademarks whose use by third parties for their own purposes could violate the rights of the owners.

Patent Pending

The TwinCAT Technology is covered, including but not limited to the following patent applications and patents:
EP0851348, US6167425 with corresponding applications or registrations in various other countries.

Copyright

© Beckhoff Automation GmbH & Co. KG, Germany.
The reproduction, distribution and utilization of this document as well as the communication of its contents to others without express authorization are prohibited.
Offenders will be held liable for the payment of damages. All rights reserved in the event of the grant of a patent, utility model or design.


Übersicht

TwinCAT ADS Samples - .NET

Overview

TwinCAT ADS .NET

Descripion
Linking into TwinCAT3 (C#)
Linking into Microsoft Visual Studio (C#)
Linking into Microsoft Visual Studio .NET (VB)
Linking into Borland Developer Studio 2006 (Delphi for .NET)
Linking into Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET)
Description | Visual C# | Visual Basic (for .NET Framework) | Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Delphi for .NET (Borland Developer Studio 2006)
Sample 1: Accessing an array in the PLC | Sample01.zip | Sample01.exe | Sample01.exe | Sample01.exe
Sample 2: Transmitting a structure to the PLC | Sample02.zip | Sample02.exe | Sample02.exe | Sample02.exe
Sample 3: Event driven reading | Sample03.zip | Sample03.exe | Sample03.exe | Sample03.exe
Sample 4: Reading and writing of string variables | Sample04.zip | Sample04.exe TwinCAT.Ads.NET v1.0.0.10 and higher: Sample04b.exe | Sample04.exe | Sample04.exe
Sample 5: Reading and writing of DATE/TIME variables | Sample05.zip | Sample05.exe | Sample05.exe | Sample05.exe
Sample 6: Read PLC variable declaration | Sample06.zip | Sample06.exe | Sample06.exe | Sample06.exe
Sample 7: Reading and writing of PLC variables of any type | Sample07.zip | Sample07.exe | Sample07.exe | Sample07.exe
Sample 8: Detect state changes of TwinCAT router and PLC | Sample08.zip | - | Sample08.exe | -
Sample 9: ADS-Sum Command: Reading or writing several variables | Sample09.zip | - | Sample09.exe | -
Sample 10: Reading of SMB values from TwinCAT I/O driver | - | Sample10.exe | - | -
Sample 11: Delete a handle of a PLC variable | Sample11.zip | - | Sample11.exe | -
Sample 12: Read flag synchronously from the PLC | Sample12.zip | - | Sample12.exe | -
Sample 13: Write flag synchronously into the PLC | Sample13.zip | - | Sample13.exe | -
Sample 14: Start/stop PLC | Sample14.zip | - | Sample14.exe | -
Sample 15: Access by variable name | Sample15.zip | - | Sample15.exe | -

TwinCAT ADS Webservice

Description | Sample code
Sample 1: Consumer in C# to read and write PLC variables | Sample01.exe
Sample 1: Consumer in Delphi 8 for .NET to read and write PLC variables | 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 ADS 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 Component

Linking into Microsoft Visual Studio .NET (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. 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("172.16.3.217.1.1",801);

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 (VB)

TwinCAT ADS .NET component

Integration in Microsoft Visual Studio .NET (VB)

Create new project

The TwinCAT.Ads component can only be integrated in an existing project. Start Visual Studio and select the following command from the menu: File->New->Project...

In the New Project dialog please select as project type: Visual Basic->Windows Forms Application.

Adding a reference

To select the class library TwinCAT.Ads select the command Add Reference... from the Project menu. The Add Reference dialog opens.
Alternatively you double-click on My Project (1) to open the Project Properties window and click the Add... button (3) under Reference (2).

In this dialog you should select the Browse tab and navigate to file TwinCAT.Ads.dll. Confirm the selected dll with OK. By default the TwinCAT.Ads component is located in the ..TwinCAT\Ads Api\.NET\ folder.

Add namespace

All class library types belong to the TwinCAT.Ads namespace. Therefore the following lines should be added at the beginning of the source text:

Imports TwinCAT.Ads
Imports System.IO

This enables access to the data types defined in TwinCAT.Ads without having to specify the namespace repeatedly. The TcAdsClient class is the core of the TwinCAT.Ads class library and enables communication with an Ads device. The first step is to create an instance of the TcAdsClient class. Then make use of the Connect method to establish a connection with the ADS device.

Establishing a connection to an Ads device:

Private tcAds As TcAdsClient
...

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

...

The data are transmitted with the aid of the AdsStream class, which is derived from System.IO.MemoryStream. The class System.IO.BinaryWriter can be used to write data to the stream, the class System.IO.BinaryReader can be used to read data from the stream.

Reading a PLC variable:

' 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()

Writing a PLC variable:

' 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)

Separating a connection to an Ads device:

tcAds.Dispose();

PLC program:

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


Delphi examples: TwinCAT.Ads in BDS2006

TwinCAT ADS .NET component

Integration in Borland Developer Studio 2006

Creating a new project

Create a new project in Borland Developer Studio (Windows Forms Application).

Add reference

To select the class library TwinCAT.ADS select the command Add Reference... from the Project menu. The Add Reference dialog opens.

In this dialog press the Browse... button and select the file TwinCAT.Ads.dll.

Add namespace

The project manager offers an option for checking whether the component was added to the reference list.

All class library types belong to the TwinCAT.ADS namespace. This namespace must be added in the uses section:

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

This enables access to the data types defined in TwinCAT.ADS without having to specify the namespace repeatedly. The TcAdsClient class is the core of the TwinCAT.ADS class library and enables the user to communicate with an Ads device. The first step is to create an instance of the TcAdsClient class. Then make use of the Connect method to establish a connection with the ADS device.

Establishing a connection to an ADS device:

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

Reading a PLC variable:

The data (4 bytes integer in the flag area) are transferred with the aid of the AdsStream class, which is derived from System.IO.MemoryStream. The class System.IO.BinaryWriter can be used to write data to the stream, the class System.IO.BinaryReader can be used to read data from the stream.

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;

Writing a PLC variable:

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;

Separating a connection to an ADS device:

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

PLC program:

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


Delphi Prism examples: TwinCAT.Ads in VS2010

TwinCAT ADS .NET component

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

Embarcadero Prism XE2 = Oxygene for .NET

Delphi Prism uses Microsoft Visual Studio 2010 as IDE and integrates itself in an existing VS installation from there. The Visual Studio IDE is supplied with the Delphi Prism installation if it is not yet available.

Create new project

The TwinCAT.ADS component can only be integrated in an existing project. Start VS and select the following command from the menu: File->New->Project...

In the New Project dialog please select as project type: Delphi Prism->Windows Forms Application.

Adding a reference

To select the class library TwinCAT.ADS select the command Add Reference... from the Project menu. The Add Reference dialog opens.

In this dialog should select the Browse tab and navigate to file TwinCAT.ADS.dll. Confirm the selected dll with Add and OK. By default the TwinCAT.ADS component is located in folder ..TwinCAT\Ads Api\.NET\ .

Add namespace

The project manager offers an option for checking whether the component was added to the reference list.

All class library types belong to the TwinCAT.ADS namespace. This namespace must be added in the uses section:

namespace AdsNETSample;'
interface

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

This enables access to the data types defined in TwinCAT.ADS without having to specify the namespace repeatedly. The TcAdsClient class is the core of the TwinCAT.ADS class library and enables the user to communicate with an ADS device. The first step is to create an instance of the TcAdsClient class. Then make use of the Connect method to establish a connection with the ADS device.

Establishing a connection to an ADS device:

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;

Reading a PLC variable:

The data (4 bytes integer in the flag area) are transferred with the aid of the AdsStream class, which is derived from System.IO.MemoryStream. The class System.IO.BinaryWriter can be used to write data to the stream, the class System.IO.BinaryReader can be used to read data from the stream.

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;

Writing a PLC variable:

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;

Separating a connection to an ADS device:

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

PLC program:

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


Accessing an array in the PLC

Download

Language / IDE | Unpack the example program
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

Task

The PLC contains an array that is to be read by the .Net application using a read command.

Description

The PLC contains an array of 100 elements of type integer (2 bytes). The array in the PLC is to be filled with the values from 3500 to 3599.

In the Form1_Load event method a new instance of the class TcAdsClient is created. Then the method TcAdsClient.Connect of the TcAdsClient object is called to establish a connection to the port 801. Finally the method TcAdsClient.CreateVariableHandle is used to fetch the handle of the PLC variable. When the program finishes, this handle is released in the Form1_Closing event method and the Dispose method of the TcAdsClient object is called.

When the user clicks the button on the form, the entire array is read from the PLC into the AdsStream dataStream by means of the TcAdsClient.Read method. The stream should be of the same size as the data in the SPS. Because we want to read 100 INTs (each 2 Bytes), the stream must be able to hold at least 2 * 100 Bytes.

The class System.IO.BinaryReader is necessary to read the individual fields of the array. First the position of the stream has to be set to 0. Then the individual fields of the array can be read by calling the method BinaryReader.ReadInt16 in a for loop. The position pointer of the stream is incremented automatically by the bytes ( in this case 2) that have been read.

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 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)
        {
            // Create instance of class TcAdsClient
            tcClient = new TcAdsClient();

            // Connect to local PLC - Runtime 1 - TwinCAT2 Port=801, TwinCAT3 Port=851
            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 which gets the data
                AdsStream dataStream = new AdsStream(100 * 2);
                BinaryReader binRead = new BinaryReader(dataStream);

                //read comlpete Array
                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)
        {
            //enable resources
            try
            {
                tcClient.DeleteVariableHandle(hVar);
            }
            catch(Exception err)
            {
                MessageBox.Show(err.Message);
            }
            tcClient.Dispose();
        }
    }
}

PLC program

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

Language / IDE | Unpack the example program
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

Task

A structure is to be written into the PLC by the .NET application. The elements in the structure have different data types.

Description

The structure we want to write into the PLC:

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

In c# the structure would look like this:

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

This structure will not be created directly. Instead we will use a AdsStream as in Sample01. This object is initialized with a size of 19 bytes in the write event method. This is equivalent to the size of the data type PLCStruct in the SPS. A BinaryWriter is used to write the data from the textboxes to the stream. After setting the stream position to 0 the individual fields are written to the stream. Finally the total stream is written into the SPS with the help of TcAdsClient.Write.

C# Program

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)
        {
            // Create instance of class TcAdsClient
            tcClient = new TcAdsClient();

            // Connect to local PLC - Runtime 1 - TwinCAT2 Port=801, TwinCAT3 Port=851
            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
            {
                //Fill stream according to the order with data from the text boxes
                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));

                //Write complete stream in the PLC
                tcClient.Write(hVar,dataStream);
            }
            catch( Exception err)
            {
                MessageBox.Show(err.Message);
            }
        }

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

PLC program

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

PROGRAM MAIN
VAR
  PLCVar : PLCStruct;
END_VAR


Event driven reading

Language / IDE | Unpack the example program
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

Task

There are 7 global variables in the PLC. Each of these PLC variables is of a different data type. The values of the variables should be read in the most effective manner, and the value with its timestamp is to be displayed on a form.

Description

In the form's load event, a connection to each of the PLC variables is created with the TcAdsClient.AddDeviceNotification() method. The handle for this connection is stored in a array.
The parameter TransMode specifies the type of data exchange. AdsTransMode.OnChange has been selected here. This means that the value of the PLC variable is only transmitted if its value within the PLC has changed (see the AdsTransMode data type). The parameter cycleTime indicates that the PLC is to check whether the corresponding variable has changed every 100 ms. MaxDelay allows to collect notification for a specified interval. If the maxDelay elapse, all notifications will be send at once.
When the PLC variable changes, the TcAdsClient.AdsNotification() event is called. The parameter e of the event handling method is of the type AdsNotificationEventArgs and contains the time stamp, the handle, the value and the control in which the value is to be displayed. The connections are released again in the closing event by means of the TcAdsClient.DeleteDeviceNotification() method. It is essential that you do this, since every connection established by TcAdsClient.AddDeviceNotification() uses resources.
You should also choose appropriate values for the cycle time, since too many write / read operations load the system so heavily that the user interface becomes much slower.

Advice: Don't use time intensive executions in callbacks (OnNotification()).

C# program

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 is set to ASCII, to read strings
            binRead = new BinaryReader(dataStream, System.Text.Encoding.ASCII);
            // Creaste instance of class TcAdsClient
            tcClient = new TcAdsClient();

            // PLC1 Port: TwinCAT2=801, TwinCAT3=851
            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();
        }

    }
}

PLC program

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


Accessing an array in the PLC

TwinCAT ADS .NET Component: Samples

Reading and writing of string variables

Download

Language / IDE | Unpack the sample program
Visual C# | Sample04.zip
Visual Basic (for .NET Framework) | Sample04.exe TwinCAT.Ads.NET v1.0.0.10 and higher: Sample04b.exe
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample04.exe
Delphi for .NET (Borland Developer Studio 2006) | Sample04.exe

Task

A .Net application should read a string from the PLC and write a string to the PLC.

Description

The PLC contains the string MAIN.text.

In the Form1_Load event method a new instance of the class TcAdsClient is created. Then the method TcAdsClient.Connect of the TcAdsClient object is called to establish a connection to the port 801. Finally the method TcAdsClient.CreateVariableHandle is used to fetch the handle of the PLC variable . When the program finishes, the Dispose method of the TcAdsClient object is called.

When the user clicks the "Read" button on the form, the string is read by means of the TcAdsClient.Read method and is displayed in the text box. When the user clicks the "Write" button ont the form, the string is wirtten to PLC and is displayed in the text box.

TwinCAT.Ads.NET version < 1.0.0.10:

The length of the AdsStream must be equal to size of the string in the PLC plus 1 (inclusive the terminating zero character). The parameter System.Text.Encoding.ASCII has to be passed to constructor of the BinaryReader class. This is necessary for the BinaryReader to be able to convert the ANSI characters to the char type.

TwinCAT.Ads.NET version >= 1.0.0.10:

The classes AdsBinaryReader and AdsBinaryWriter can be used to read and write strings ( see commented section in sample program ). These classes derive from the BinaryReader/Writer classes. To read a string from the stream one has to call the method AdsBinaryReader.ReadPlcString. To write a string to the stream one has to call the method AdsBinaryWriter.WritePLCString. The length of the AdsStream must be equal to size of the string in the PLC (without terminating zero character). The length is passed to these method and have to be equal to the length of the string in the PLC (without the terminating zero character).

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();

           // PLC1 Port: TwinCAT2=801, TwinCAT3=851
           tcClient.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);
            }
        }
*/

    }
}

PLC program

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


Accessing an array in the PLC

TwinCAT ADS .NET Component: Samples

Reading and writing of TIME/DATE variables

Download

Language / IDE | Unpack the example program
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

Task

A .Net application should read and write a date and a time.

Description

The PLC contains the TIME variable MAIN.Time1 and the DT variable MAIN.Date1.

In the Form1_Load event method a new instance of the class TcAdsClient is created. Then the method TcAdsClient.Connect of the TcAdsClient object is called to establish a connection to the port 801. Finally the method TcAdsClient.CreateVariableHandle is used to fetch the handle of the PLC variables . When the program finishes, the Dispose method of the TcAdsClient object is called.

When the user clicks the "Read" button on the form, the variables MAIN.Time1 and MAIN.Date1 are read by means of the TcAdsClient.Read method. The class AdsBinaryReader, that derives from BinaryReader, is used to read the time and date from the PLC. The method AdsBinaryReader.ReadPlcTIME reads the time from the AdsStream and converts it to the .NET type TimeSpan. The method AdsBinaryReader.ReadPlcDATE reads the date from the AdsStream and converts it to the .NET type DateTime.

When the user clicks the "Write" button ont the form, the variables MAIN.Time1 and MAIN.Date1 are wirtten to the PLC. The class AdsBinaryWriter, that derives from BinaryWriter, is used to write the time and date to the PLC. The method AdsBinaryWriter.WritePlcType writes the .NET types TimeSpan and DateTime in the PLC format for time and date types to the AdsStream.

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[] 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();

                // PLC1 Port: TwinCAT2=801, TwinCAT3=851
                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);
            }
        }
    }
}

PLC program

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


SPS Variablen-Deklaration auslesen

TwinCAT ADS .NET Component: Examples

Read PLC variable declaration

Download

Language / IDE | Unpack the example program
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

Task

All variables that are declared in the PLC should be displayed in a tree view.

Description

In the Form1_Load method an instance of the TcAdsSymbolInfoLoader is created with a call to TcAdsClient.CreateSymbolInfoLoader. This class is responsible for loading the symbol information from the PLC. By clicking the Load Symbols button the symbols are loaded by means of the TcAdsSymbolInfoLoader.GetFirstSymbol method. This method returns the first loaded symbol as a TcAdsSymbolInfo object. The methods TcAdsSymbolInfo.NextSymbol und TcAdsSymbolInfo.FirstSubSymbol are used to iterate over the symbols and to display the symbols hierarchically in the tree view. If the check box flat is checked, the symbols are displayed in a flat list. In this case foreach is used to enumerate over the symbols loaded by the TcAdsSymbolInfoLoader object. This includes the sub symbols.

By selecting a tree view item the edit boxes are filled with the symbol information. Additionally the value of the variable is read with the help of TcAdClient.ReadSymbol and is displayed in the value edit box. To write a value to a variable one has to click the Write Value button, which leads to a call of TcAdsCient.WriteSymbol.

To read the symbol information for a specific variable, one can either click the Read Symbol Info or the Find Symbol button. In the first case the method TcAdsClient.ReadSymbolInfo is called. This leads to an ADS call, to load the information for this symbol from the PLC. In the second case the method TcAdsSymbolInfoLoader.FindSymbol is called, to search for the symbol in the list of the previous loaded symbols. If no symbols have been loaded before, all symbols are loaded from the PLC.

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 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();

          // Connect to local PLC - Runtime 1 - TwinCAT2 Port=801, TwinCAT3 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);
            }
         }
      }
}

PLC program

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


Reading and Writing of variable of any type

TwinCAT ADS .NET Component: Samples

Reading and writing of PLC variables of any type (ReadAny, WriteAny)

Download

Language / IDE | Unpack the example program
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

Task

Read and Write variables of any type with the help of the ReadAny and WriteAny methods.

Description

ReadAny

In the event method btnRead_Click the method TcAdsClient.ReadAny is used to read a variable by handle:

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

The type of the variable is passed to the method in the parameter type. In case the method was successfull, the read data will be returned as a object. The type of the object is equal to the type passed in the parameter type. Because some data types(arrays and strings) need additional information, an overload of the method ReadAny exists, that takes an additional parameter args. E.g. with strings one has to pass an integer array of the length 1. Full list of supported types can be found in the documentation of the overloaded method.

Example:

A PLC variable of the type ARRAY[0..3] OF DINT should be read:

int hArr;
int[] arr;

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

...
adsClient.DeleteVariableHandle(hArr)

WriteAny

In the event method btnWrite_Click the method TcAdsClient.WriteAny is used to write to a variable by handle:

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

The parameter value is a reference to the object, that should be written to the PLC variable. Full list of supported types of the object value can be found in the documentation of the overloaded method.

Example:

A PLC variable of the type ARRAY[0..3] OF DINT should be written:

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

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

...
adsClient.DeleteVariableHandle(hArr)

Reading and writing of structures:

(not possible with the Compact Framework(CE) )

To be able to read or write PLC structures the memory layout of the structure or class in .NET must be the same as in the PLC. The layout of a structure or class can be specified with the attribute StructLayoutAttribute. The LayoutKind must be set to LayoutKind.Sequential and the pack must be set to 1. Therefore the class SimpleStruct is defined as followed:

[StructLayout(LayoutKind.Sequential, Pack=1)]
public class SimpleStruct

{
    public double lrealVal;
    public int dintVal1;
}

If arrays, strings or boolean values are define the class, one has to specify how these fields should be marshalled. This is accomplished with help of the MarshalAs attribute. Because arrays and strings do not have a fixed length in .NET, the property SizeConst is necessary for arrays and strings. It is not possible to marshal multidimensional arrays or arrays of structures with the :NET Framework 1.1. Multidimensional arrays in the PLC must be mapped to one dimensional arrays in .NET.

In the example the MarshalAsAttribute is used in the class ComplexStruct:

[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();
}

Register ADS notifcations

In the event method btnAddNotifications_Click the method AddDeviceNotificationEx is used to register notifications for a PLC variable. If the value of a variable changes the event AdsNotificationEx is fired. The difference to the event AdsNotification, is that the value of the variable is stored in an object instead of in an AdsStream. Therefore one has to pass the type of the object to the method AddDeviceNotificationEx:

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

As user object the textbox that should display the value is passed. If the event is fired, the event method adsClient_AdsNotificationEx is called. For this the event must be registered in the Form_Load method.

adsClient.AdsNotificationEx+=new AdsNotificationExEventHandler(adsClient_AdsNotificationEx);

C# program

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
            {
                // Connect to local PLC - Runtime 1 - TwinCAT2 Port=801, TwinCAT3 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 Pack = 1, TwinCAT3 Pack = 0
    [StructLayout(LayoutKind.Sequential, Pack=1)]
    public class SimpleStruct
    {
        public double lrealVal;
        public int dintVal1;
    }

    // TwinCAT2 Pack = 1, TwinCAT3 Pack = 0
    [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();
    }
}

PLC program

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


Detect status change in TwinCAT router and the PL

TwinCAT ADS .NET Component:Samples

Detect state changes in TwinCAT router and PLC

Download

Language / IDE | Extract the sample program
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) | -

Task

Detect state changes in TwinCAT router and PLC.

Description

State changes in ADS devices can be detected effectivly by registering callback functions to the devices. These function are called on state changes of the ADS devices.

State changes in the TwinCAT router can be detected via the TcAdsClient.AmsRouterNotification in the TcAdsClient class . In order to detect state changes in the PLC an ADS Notification to the ADS status word has to be registered. Callback functions can be registered for both. These are called on state changes of the devices.

The following program monitors the state of the TwinCAT router and the PLC using the above techniques.

C# Program

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();

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

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

        /* register callback function to detect state changes in the router  */
        _tcClient.AmsRouterNotification += new AmsRouterNotificationEventHandler(AmsRouterNotificationCallback);

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

        /* register callback function to react on notifications */
        _tcClient.AdsNotification += new AdsNotificationEventHandler(OnAdsNotification);
      }
        catch (AdsErrorException ex)
      {
        MessageBox.Show(ex.Message);
      }
    }

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

    /* Ccallback function called on state changes of the PLC */
    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

TwinCAT ADS .NET Komponente: Beispiele

ADS-Sum Command: Reading or writing several variables

Requirement: TwinCAT 2.11 >= Build 1550

Download

Language / IDE | Extract the sample program
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) | -

Using the ADS Sum Command it is possible to read or write several variables in one command. Designed as TcAdsClient.ReadWrite it is used as a container, which transports all sub-commands in one ADS stream.

In the beginning it's important, binding 'TwinCAT.Ads.dll' to your project! To do so, open 'Solution Explorer' and choose 'Add References...' via'References'. 'Browse' for the DLL in the folder 'TwinCAT -> ADS API -> .NET' .

C# program

1. Read variables

First, define two structures:
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;
    }

and declare some global variables.
    public class Form1 : System.Windows.Forms.Form
    {
        [...]

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

On applications start an ADS connection to PLC is established and Handle parameters are written into the structure.
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;
            }
        }

After clicking 'Read' Button, 'BlockRead' method returns an ADS Stream. Check for ADS return codes (Err) for error handling, before data is read out of the stream und stored in the text boxes.
        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);
            }
        }

'BlockRead' methode takes an object of type 'VariableInfo[]', where the handle parameters are strored. After reserving memory for the values to be read an write, an ADS stream is written with the parameters delivered by 'VariableInfo[]'. At the end the sum command transfers the commands and an ADS stream is returned.
        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;
        }
Sum Command's Parameters consists of IndexGroup (0xF080) - sum command call, IndexOffset (variables.Length) - number of sub commands, rdDataStream (rdStream) - memory, taking the read values, wrDataStream (writer.BaseStream) - memory, containing values to be written.

sub commands in wrDataStream

response in rdDataStream

2. writing variables

After clicking 'Write' button, 'BlockRead2' methode returns an ADS stream. Following is a check of ADS return codes (Err) for error handlings.
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);
            }
        }

'BlockRead2' methode takes an object of type 'VariableInfo[]', where handle parameters are stored. After reserving memory for the values to be read an write, 'MyStruct' object is filled with the values stored in the textboxes. Next, an ADS stream containing parameters from 'VariableInfo[]' objects an the values of 'MyStruct' object is written. At the end sum command transfers the commands and an ADS Stream is returned.

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;
        }

Sum Command's Parameters consists of IndexGroup (0xF081) - sum command call, IndexOffset (variables.Length) - number of sub commands, rdDataStream (rdStream) - memory, taking the read values, wrDataStream (writer.BaseStream) - memory, containing values to be written.

sub commands in wrDataStream

response in rdDataStream


ADS-Summenkommando: Lesen oder Schreiben von mehreren Variablen

TwinCAT ADS .NET Komponente: Beispiele

Reading of SMB values from TwinCAT I/O driver

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 (royalty-free) is a product containing drivers for the Beckhoff Control Panels respectively Control Panel-PC's of the CP6xxx und CP7xxx series, the industrial human machine interfaces from Beckhoff.
It gets typically used if there is no full TwinCAT version considered but basic functions of the Control Panel or Control Panel-PC need to get accessed via application software (like pushbutton extensions or the named SMB (System Management Bus) on supported Motherboards, etc...
TwinCAT CP is a subset of the full TwinCAT suite, means all contained drivers are binary identical to those in the other TwinCAT levels available. Written application software doesn't need to get touched in any way, if TwinCAT CP gets upgraded to a full TwinCAT version at a later date.

Even if only TwinCAT-CP is installed on the controller or HMI device, the SMB device can get accessed by the ADS client (i.e. from a Visual Basic or similar DLL-, Active X- or .NET-capable application).

The visualization allows a continuous supervision of operation temperatures, fan speeds or even the different power supply voltages. That way, the crossing of a critical treshold value can be detected (i.e. if an implementation of an advanced maintenance system is considered). The following example shows a possible implementation under Visual Basic.NET.

Order options:

TwinCAT-CP | License for using the I/O driver for Beckhoff Control Panels for communication between generic Windows applications (alternatively, any other level of TwinCAT of course)

Required software:

  • Microsoft.NET Framework version 2.0: http://msdn2.microsoft.com
  • Microsoft Visual Studio 2005 or 2008: http://msdn2.microsoft.com
    • to create the application "Motherboard Diagnosis“ (see below)
  • TwinCAT 2.10: http://www.beckhoff.de/tcatweb/tcatdowd.htm
    • during a new installation of TwinCAT:
      In order to install the royalty-free TwinCAT "CP" level, it's important that TwinCAT level "CP" and no other optional level gets selected during the initial installation.

      Note: During an Update installation, the previously selected level cannot be changed. Instead, TwinCAT needs to get uninstalled first (in case of a wished upgrade/downgrade).

Visual Basic (for .NET Framework) program

Example: Temperature

1. Add Reference

At first, a Reference to the necessary TwinCAT.Ads.dll needs to be added. The TwinCAT.ADS.NET component gets added via right mouse-click on Project → Add Reference.

2. Determination of device address (here: 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. Determination of the Variable address (here: Index-Offset)

To determine the Index-Offset of the variable, it's very comfortable to use TwinCAT System Manager . By selecting the required Input in the left tree-view, the related window on the right shows the whished address in hexa-decimal notation.

4. Cyclic read update of the value in your application

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

Example: Resulting Messagebox when exceeding a temperature treshold value


Beckhoff Information System - English

TwinCAT ADS .NET : Examples

Delete a handle of a PLC variable

Download

Language / IDE | Extract the sample program
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) | -

This Sample shows how to delete a handle of a PLC variable:

C# program

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 - English

TwinCAT TcAdsDLL: Examples

Read flag synchronously from the PLC

Download

Language / IDE | Extract the sample program
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) | -

In this example program the value in flag double word 0 in the PLC is read and displayed on the screen:

C# program

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 - English

TwinCAT TcAdsDLL: Examples

Write flag synchronously into the PLC

Download

Language / IDE | Extract the sample program
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) | -

In this example program, the value that the user has entered is written into flag double word 0:

C# program

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 - English

TwinCAT TcAdsDLL: Examples

Start/stop PLC

Download

Language / IDE | Extract the sample program
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) | -

The following program starts or stops run-time system 1 in the PLC:

C# program

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 - English

TwinCAT TcAdsDLL: Examples

Access by variable name

Download

Language / IDE | Extract the sample program
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) | -

The following program accesses a PLC variable that does not have an address. Access must therefore be made by the variable name. Once the PLC variable in the example program exceeds 10 it is reset to 0:

C# program

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#-Sample

TwinCAT ADS Web Service: C# example

Consumer in C# to read and write PLC variables

This sample explains how to create an ADS-WebService-Consumer to read and write PLC-Variables via ADS-HTTP.

Creating a WebService-Consumers within the Microsoft VisualStudio.NET

In Visual Studio select "New Project" and create a new C# - Windows Application.

  • By selecting the template "Windows-Application" the new application is only running on .NET-platforms.
  • By selecting the template "Smart Device Application" the new application is running on .NET-platforms and on .NET-CompactFramework (CE-) platforms.
    By selecting the .NET-CompactFramework (CE-) platform restrictions may occure.

After creating the projects go to "Projects" "Add Web Reference...".

In the following dialog you may insert the URL of the WebService-WSDL-file or you can browse for it by clicking on "Web services on the local machine".

Name the reference and click the button "Add Reference" to complete .

To catch SOAP-Exceptions add the following line to the first lines of the source-code:

using System.Web.Services.Protocols;

To create a WebService-object insert the following line after "public class NameOfYourForm: System.Windows.Forms.Form":

TcAdsWebService.TcAdsWebService TcWebService = new TcAdsWebService.TcAdsWebService();

Add the following line in the constructor ("public NameOfYourForm") after "InitializeComponent();":

TcWebService.Url = "http://localhost/TcAdsWebService/TcAdsWebService.dll";

// Sample 1 :
TcWebService.Url = "http://192.168.0.2/TcAdsWebService/TcAdsWebService.dll";

// Sample 2 :
TcWebService.Url = "http://CX_xxxxxx/TcAdsWebService/TcAdsWebService.dll";

The address must be the URL of your TwinCAT ADS WebService-DLL-file .

Converting variables to byte-arrays

Variables have to be converted to byte-arrays if they should be used with the ADS-WebService. Therefore it is necessary to be able to convert variables to byte-arrays and vice versa:

This could be realized with the BitConverter-class:

byte[] abDataBool = new byte[1];
abDataBool = BitConverter.GetBytes(bValueBool);

byte[] abDataInt = new byte[2];
abDataInt = BitConverter.GetBytes((Int16)iValueInt)

In this example a "bool"- and an "int"-variable were converted to byte-arrays.

To convert a byte-array back to a variable use the BitConverter-class as followed:

byte[] abDataBuffer = new byte[3];
bool bVarBool = BitConverter.ToBoolean(abDataBuffer,0);
int iVarInt = BitConverter.ToInt16(abDataBuffer,1);

Strings are converted with the the "ASCII-Encoder":

string szValueString;
byte[] abDataString = new byte[81];
System.Text.ASCIIEncoding encoder = new System.Text.ASCIIEncoding();
encoder.GetBytes(szValueString, 0, encoder.GetByteCount(szValueString), abDataString, 0);

To convert a byte-array back to a string use the following code:

byte[] abDataBuffer = new byte[81];
System.Text.ASCIIEncoding encoder = new System.Text.ASCIIEncoding();
string VarString = encoder.GetString(abDataBuffer, 3, 81);

You can get more detailed information about this methods and classes in literature for C#-Application-Developement.

Reading PLC-variables

To read a PLC-Variable use the "read"-method of the WebService-object:

WebService.Read(string netId, int nPort, System.UInt32 indexGroup, System.UInt32 indexOffset, int cblen, out System.Byte[] ppData);

  • string netId: String indicating the AMS-Net-Id of the PLC
  • int nPort: Port-number of the Runtime-System
  • System.UInt32 indexGroup: IndexGroup of the variables to read
  • System.UInt32 indexOffset: First byte to read
  • int cblen: Number of bytes to read
  • out System.Byte[] ppData: Byte-array to save the PLC-data into

Example:

try
{
    TcWebService.Read("192.168.0.2.1.1", 801, 0x4020, 0, 84, out abDataBuffer);
}
catch (SoapException ex)
{
    MessageBox.Show(ex.Message);
}
catch (Exception ex)
{
    MessageBox.Show(ex.Message);
}

Writing PLC-variables

To write a PLC-Variable use the "write"-method of the WebService-object:

WebService.Write(string netId, int nPort, System.UInt32 indexGroup, System.UInt32 indexOffset, System.Byte[] pData);

  • string netId: String indicating the AMS-Net-Id of the PLC
  • int nPort: Port-number of the Runtime-System
  • System.UInt32 indexGroup: IndexGroup of the variable
  • System.UInt32 indexOffset: First byte to write to
  • System.Byte[] pData: Byte-array containing the data to write

Example:

byte[] Data = new byte[10];

try
{
    TcWebService.Write("192.168.0.2.1.1", 801, 0x4020, 0, Data);
}
catch (SoapException ex)
{
    MessageBox.Show(ex.Message);
}
catch (Exception ex)
{
    MessageBox.Show(ex.Message);
}

Sample-Application:

PLC-program, IEC1131 declaration of the variables

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

C#-Application, the layout :

The source-code:

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Web.Services.Protocols;	// Catches the SoapExceptions

namespace WebServiceTestConsumer
{
    public class frmWebServiceTestConsumer : System.Windows.Forms.Form
    {
        private System.ComponentModel.Container components = null;

        #region forms
        private System.Windows.Forms.Label lblBool;
        private System.Windows.Forms.Label lblInt;
        private System.Windows.Forms.Label lblString;
        private System.Windows.Forms.TextBox txtBool;
        private System.Windows.Forms.TextBox txtInt;
        private System.Windows.Forms.TextBox txtString;
        private System.Windows.Forms.Button btnRead;
        private System.Windows.Forms.Button btnWrite;
        #endregion

        #region variables and instances

        // Encodes byte-array to Strings and vice versa
        private	System.Text.ASCIIEncoding		encoder			=	new	System.Text.ASCIIEncoding();

        // Instance of the TcAdsWebService
        private	TcAdsWebService.TcAdsWebService	TcWebService	=	new	TcAdsWebService.TcAdsWebService();

        private	string	szWebServiceUrl		= "http://192.168.0.2/TcAdsWebService/TcAdsWebService.dll";
        public	string	szAmsNetId			=	"192.168.0.2.1.1";
        public	int		iPort			=	801;
        public	UInt32	iIndexGroup			=	0x4020;

        #endregion

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

            // Links the WebService with its library
            TcWebService.Url = szWebServiceUrl;
        }

        protected override void Dispose( bool disposing )
        {
            if( disposing )
            {
                if (components != null)
                {
                    components.Dispose();
                }
            }
            base.Dispose( disposing );
        }

        #region Windows Form Designer generated code
        private void InitializeComponent()
        {
            this.lblBool = new System.Windows.Forms.Label();
            this.lblInt = new System.Windows.Forms.Label();
            this.lblString = new System.Windows.Forms.Label();
            this.txtBool = new System.Windows.Forms.TextBox();
            this.txtInt = new System.Windows.Forms.TextBox();
            this.txtString = new System.Windows.Forms.TextBox();
            this.btnRead = new System.Windows.Forms.Button();
            this.btnWrite = new System.Windows.Forms.Button();
            this.SuspendLayout();
            //
            // lblBool
            //
            this.lblBool.Location = new System.Drawing.Point(40, 24);
            this.lblBool.Name = "lblBool";
            this.lblBool.TabIndex = 0;
            this.lblBool.Text = "VarBool:";
            //
            // lblInt
            //
            this.lblInt.Location = new System.Drawing.Point(40, 80);
            this.lblInt.Name = "lblInt";
            this.lblInt.TabIndex = 1;
            this.lblInt.Text = "VarInt:";
            //
            // lblString
            //
            this.lblString.Location = new System.Drawing.Point(40, 136);
            this.lblString.Name = "lblString";
            this.lblString.TabIndex = 2;
            this.lblString.Text = "VarString:";
            //
            // txtBool
            //
            this.txtBool.Location = new System.Drawing.Point(152, 24);
            this.txtBool.Name = "txtBool";
            this.txtBool.TabIndex = 3;
            this.txtBool.Text = "";
            //
            // txtInt
            //
            this.txtInt.Location = new System.Drawing.Point(152, 80);
            this.txtInt.Name = "txtInt";
            this.txtInt.TabIndex = 4;
            this.txtInt.Text = "";
            //
            // txtString
            //
            this.txtString.Location = new System.Drawing.Point(152, 136);
            this.txtString.Name = "txtString";
            this.txtString.TabIndex = 5;
            this.txtString.Text = "";
            //
            // btnRead
            //
            this.btnRead.FlatStyle = System.Windows.Forms.FlatStyle.System;
            this.btnRead.Location = new System.Drawing.Point(48, 216);
            this.btnRead.Name = "btnRead";
            this.btnRead.TabIndex = 6;
            this.btnRead.Text = "&Read";
            this.btnRead.Click += new System.EventHandler(this.btnRead_Click);
            //
            // btnWrite
            //
            this.btnWrite.FlatStyle = System.Windows.Forms.FlatStyle.System;
            this.btnWrite.Location = new System.Drawing.Point(168, 216);
            this.btnWrite.Name = "btnWrite";
            this.btnWrite.TabIndex = 7;
            this.btnWrite.Text = "&Write";
            this.btnWrite.Click += new System.EventHandler(this.btnWrite_Click);
            //
            // frmWebServiceTestConsumer
            //
            this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
            this.ClientSize = new System.Drawing.Size(292, 266);
            this.Controls.Add(this.lblBool);
            this.Controls.Add(this.lblInt);
            this.Controls.Add(this.lblString);
            this.Controls.Add(this.txtBool);
            this.Controls.Add(this.txtInt);
            this.Controls.Add(this.txtString);
            this.Controls.Add(this.btnRead);
            this.Controls.Add(this.btnWrite);
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Fixed3D;
            this.MaximizeBox = false;
            this.MinimizeBox = false;
            this.Name = "frmWebServiceTestConsumer";
            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
            this.Text = "WebServiceTestConsumer";
            this.ResumeLayout(false);

        }
        #endregion

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

        #region Event-Methods

        private void btnRead_Click(object sender, System.EventArgs e)
        {
            Read();
        }

        private void btnWrite_Click(object sender, System.EventArgs e)
        {
            Write();
        }

        #endregion

        #region Worker-Methods

        private void Read()
        {
            // DataBuffer for the incoming data
            byte[] abDataBuffer = new byte[84]; //1 byte for "PlcVarBool", 2 bytes for "PlcVarInt" and 81 bytes for "PlcVarString" = 84 bytes

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

                // Converts the first byte of the buffer to bool
                bool bVarBool		=	BitConverter.ToBoolean(abDataBuffer, 0);

                // Converts the second and third byte of the buffer to int
                int iVarInt			=	BitConverter.ToInt16(abDataBuffer, 1);

                // Converts the other bytes of the buffer to string
                string szVarString	=	encoder.GetString(abDataBuffer, 3, 81);

                // Writes values into the text-boxes
                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 for the variables
                byte[]	abDataBool		=	new	byte[1];
                byte[]	abDataInt		=	new	byte[2];
                byte[]	abDataString	=	new	byte[81];

                // Gets values for the text-boxes
                bool	bValueBool		=	Convert.ToBoolean(txtBool.Text);
                int		iValueInt		=	Convert.ToInt16(txtInt.Text);
                string	szValueString	=	txtString.Text;

                // Converting variables to 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 to
                                    0);										// First byte in the array to save the conversion to

                // Writing values to PLC
                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);
            }
        }

        #endregion
    }
}

Language / IDE | Extract the sample program
Visual C# | Sample01.exe


Delphi ADS Web Service: Delphi 8 .NET example

TwinCAT ADS Web Service: Delphi example

Consumer in Delphi 8 for .NET to read and write PLC variables

The example explains how to create an ADS-Web Service consumer for reading and writing PLC variables via ADS-HTTP.

Requirements:

  • Delphi 8 for Microsoft .NET framework;
  • Web service consumer for Windows XP platform (Windows Forms .NET application);
  • Web service server was configured on an Embedded PC: CX9000 configured with Windows CE (ARM).

Import into Delphi

The server interface description is published in a WSDL file (Web Service Definition Language). The WSDL description is imported into Delphi and converted by Delphi into Delphi units.

First of all create a new project (Windows Forms application Delphi .NET). Save the project. You can now import the WSDL file. In the Project Manager, right click with the mouse on the project name and select “Add Web Reference”.

A dialog opens showing Web service directories from IBM, Microsoft etc. However, we want to open the WSDL file from the data storage device.

The WSDL files from the TwinCAT ADS Web service can be found in the folder: ..TwinCAT\Ads API\TcAdsWebService\V100\... Navigate to the appropriate folder and select the WSDL file. In our case it is the WSDL file from the folder for the Windows CE (ARM) target platform.

Note:

There is no difference between the WSDL files for the XP/CE (X86 or ARM) platforms. I.e. it is always the same WSDL file. You can integrate the WSDL file from the CE/ARM folder in your consumer project and nonetheless still communicate with an ADS Web Service server on an XP platform.

The contents of the WSDL file are displayed in a dialog box.

Select “Add Web Reference”. The required units are generated automatically and added to the project.

Using the web service

In order to be able to use the web service you must integrate the web service unit in the main window unit. You can take the opportunity at the same time to integrate two further units that you will need in your project later: System.Text and System.Web.Services.Protocols

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

Now you can create an ADS Web service instance.

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

And in the TwinForm constructor:

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

The code completion will help you further. Type in TcWebService with a dot and look at the methods which are available.

We use the following methods in our example:

  • Read for reading the PLC variables;
  • Write for writing the PLC variables;
  • ReadState for reading the PLC server status;
  • WriteControl to change the PLC 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 with the network address of the TwinCAT target system. In our case the TwinCAT AMS NetID of the PC with the PLC runtime system;

nPort: TwinCAT ADS port number of the PLC runtime system;

indexGroup: Index group of the PLC variables to be read;

indexOffset: Index offset (byte-/bit offset) of the PLC variables to be read;

cbLen: Number of bytes which are to be read;

ppData: Variable in which the read data is saved (dynamic byte array);

pData: Variable with the data to be written (dynamic array);

adsState / pAdsState: PLC server status.

deviceState / pDeviceState: PLC device status;

The PLC application

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

Read PLC variables

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;

Write PLC variables

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;

Read PLC status

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;

Start/stop PLC

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