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.


Delpi Sample: Overview

TwinCAT Sample - Delphi

Overview

Platform | ADS-OCX | ADS-DLL | ADS .NET | ADS-WebService
Win32 | √ | √ | - | -
.NET | - | - | √ | √

√ = Recommended platform

Samples ADS-OCX

Description
Linking into Delphi 3, 4, 5, 6, 7, ... (classic)
Linking into Borland Developer Studio 2006
Limitation in Delphi Applications
Resetting Delphi Applications after an exception
Description | Delphi XE2 | Delphi 5, 6, 7, ... (classic)
Sample 1: Accessing PLC variables in synchronous/asynchronous/connected modes | Sample01.exe | Sample01.exe
Sample 2: Read the List of an ADS Device's Declared Variables | Sample02.exe | Sample02.exe
Sample 3: Write an array into the PLC, or read an array out the PLC | Sample03.exe | Sample03.exe
Sample 4: Open the properties page | Sample04.exe | Sample04.exe
Sample 5: Fetch the handle for the PLC variable | Sample05.exe | Sample05.exe
Sample 6: Write a string into the PLC, or read a string out the PLC | Sample06.exe | Sample06.exe
Sample 7: Read a number of Boolean variables into an array with one access | Sample07.exe | Sample07.exe
Sample 8: Transmitting structures to/from the PLC | Sample08.exe | Sample08.exe

Samples ADS-DLL for Windows NT/2000/XP

API interface

Description
Linking into Delphi (general)
Description | Delphi XE2 | Delphi 5, 6, 7, ... (classic)
Sample 1: All-In-One Sample | Sample01.exe | Sample01.exe
Sample 2: Event driven reading | Sample02.exe | Sample02.exe
Sample 3: Synchronous access to PLC variables | Sample03.exe | Sample03.exe
Sample 4: Detect status changes in TwinCAT router and the PLC | Sample04.exe | Sample04.exe
Sample 5: Start/stop PLC | Sample05.exe | Sample05.exe
Sample 6: Extended Funktions (for multithreading applications) | Sample06.exe | Sample06.exe
Sample 7: ADS sum command: Read or write list of variables with one single ADS command | Sample07.exe | Sample07.exe
Sample 8: ADS sum command: Create and release list of variable handles with one single ADS command | Sample08.exe | Sample08.exe
Sample 9: Read and write the PLC variables of type DT/DATE/TOD/TIME | Sample09.exe | Sample09.exe

COM interface

Description
Linking into Delphi XE2
Linking into Delphi 5, 6, 7, ... (classic)
Description | Delphi XE2 | Delphi 5, 6, 7, ... (classic)
Sample 1: Reading and writing of PLC variables | Sample01.exe | Sample01.exe
Sample 2: Event-driven reading of PLC variables | Sample02.exe | Sample02.exe

Samples ADS .NET

Description
Linking into Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET)
Linking into Borland Developer Studio 2006 (Delphi for .NET)
Description | Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Delphi for .NET (Borland Developer Studio 2006)
Sample 1: Accessing an array in the PLC | Sample01.exe | Sample01.exe
Sample 2: Transmitting a structure to the PLC | Sample02.exe | Sample02.exe
Sample 3: Event driven reading | Sample03.exe | Sample03.exe
Sample 4: Reading and writing of string variables | Sample04.exe | Sample04.exe
Sample 5: Reading and writing of DATE/TIME variables | Sample05.exe | Sample05.exe
Sample 6: Read PLC variable declaration | Sample06.exe | Sample06.exe
Sample 7: Reading and writing of PLC variables of any type | Sample07.exe | Sample07.exe
Sample 8: Detect state chenges in TwinCAT router and PLC | Sample08.exe | -
Sample 9: ADS-Sum Command: Reading or writing several variables | Sample09.exe | -
Sample 10: Not existing | - | -
Sample 11: Delete a handle of a PLC variable | Sample11.exe | -
Sample 12: Read flag synchronously from the PLC | Sample12.exe | -
Sample 13: Write flag synchronously into the PLC | Sample13.exe | -
Sample 14: Start/stop PLC | Sample14.exe | -
Sample 15: Access by variable name | Sample15.exe | -

Samples ADS-WebService

Description | Delphi 8 for .NET
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 |  |


Delphi AdsOcx Sample: Linking to Borland Developer Studio 2006

TwinCAT ADS-OCX: Linking to Delphi

Linking to Borland Developer Studio 2006 (VCL for Delphi Win32)

These instructions can also be used for linking ADS-OCX in Borland Delphi 2005. The differences compared with "Borland Delphi 2006" or "Delphi XE2" are only marginal.

Step 1

First a Delphi unit has to be derived from the ActiveX Control. Select "Import component..." under "Component"

Step 2

The Component Wizard opens. Select "VCL for Delphi Win32" and confirm with "Next>>".

Step 3

In next dialog select "Import ActiveX Control" and click on "Next>>".

Step 4

Select the required component from the list of registered ActiveX Controls (AdsOcx OLE Control Module). If the component does not appear in the list of registered elements, it has to be registered and integrated via the "Add" button. Then click on "Next".

Step 5

In next Wizard window select the VCL pallet page and the directory for the newly created unit (Default: C:\program files\borland\bds\4.0\Imports\ ).
Confirm with "Next".

Step 6

In the next step a unit for the ActiveX Component is generated. Confirm with "Finish".

The generated unit is opened automatically for verification:

Step 7

In the next step a new package has to be generated. Click on "File -> New -> Package" in the main menu.

Step 8

The previously generated unit has to be inserted into the newly created package.
In the Project Manager right-click on "Package1.bpl" and select "Add..." in the context menu.

Step 9

In the "Add" window enter the storage location for the unit previously generated for the ActiveX Component (Default: C:\program files\borland\bds\4.0\Imports\ADSOCXLib_TLB.pas).

Step 10

For compiling the package right-click on "Package1.bpl" and select "Compile" in the context menu.

Step 11

After the new package has been compiled select "Install" in the context menu.

The installation is now complete. The following message appears:

After the installation

The ActiveX Component appears in the specified category when a new "VCL Forms Application - Delphi for Win32" is created, for example.


Delphi AdsOcx Sample: Linking to Delphi

TwinCAT ADS-OCX: Linking to Delphi

Linking to Delphi 3,4,5,6,7 ... (classic)

Pleas read the hints about limitations using AdsOcx control in Delphi applications.

You can link ActiveX-Controls in Delphi with two methods:

1. Import ActiveX Control

2. Import ActiveX type library

If you have an older version of Delphi (e.g. 3.0), you must also:

3. Install the component in the component palette .

1. Import ActiveX Control

1.1 Select the Project -> Import ActiveX Control menu command.

1.2 In the dialogue window that will then open, mark the AdsOcx OLE Control module on the list of ActiveX controls, and confirm with ...Install. If the AdsOcx is not available as one of the selections, then you can add it to the list using the Add... command. The AdsOcx is normally located in the .../WinNT/System32 directory, to which it is copied during the installation of TwinCAT.

1.3 In the Install dialog confirm with OK.

1.4 The package must be recompiled. Confirm with Yes.

1.5 The ActiveX Control is registered after successful compilation. Confirm with OK.

1.6 The changes to the component package must be saved when closing.

The AdsOcx ActiveX Control can now be dragged onto the form from the component palette and used similarly to all the other Delphi components.

2. Import ActiveX type library

2.1 In order to be able to link the AdsOcx into Delphi's component palette, it is first necessary to generate a type library with the prototypes for the functions, procedures and data type definitions of the ActiveX Control. The type library can be generated via the Project -> Import Type Library menu selection.

2.2 In the dialogue window that will then open, mark the AdsOcx OLE Control module on the list of ActiveX controls, and confirm with ...Install. If the AdsOcx is not available as one of the selections, then you can add it to the list using the Add... command. The AdsOcx is normally located in the .../WinNT/System32 directory, to which it is copied during the installation of TwinCAT.

2.3 The newer versions of Delphi (e.g. Delphi 5.0) adds automatically imported type library as new component in the component palette. The older versions creates Pascal file that is copied in to the Imports folder. If you have a newer version of Delphi confirm with OK, else install component in the component palette.

2.4 The package must be recompiled. Confirm with Yes.

2.5 The ActiveX Control is registered after successful compilation. Confirm with OK.

2.6 The changes to the component package must be saved when closing.

The AdsOcx ActiveX Control can now be dragged onto the form from the component palette and used similarly to all the other Delphi components.

3. Install component in the component palette

3.1 After the type library has been generated, it is possible to add the AdsOcx from the Pascal file that is created during the process (ADSOCXLib_TLB.pas is the name of the file that is normally created), as a new component to the component palette. For this purpose you must select the menu command: Component -> Install Component....

3.2 The Browse... command must be used in the dialogue window to select the type library that was created beforehand. The type libraries that are generated are usually located in the .../Delphi 3/Imports/ directory. Select the type library, and confirm with Open.

3.3 After this the component palette must be recompiled. Confirm with Yes.

3.4 The ActiveX Control is registered after successful compilation. Confirm with OK.

3.5 The changes to the component package must be saved when closing.

The AdsOcx ActiveX Control can now be dragged onto the form from the component palette and used similarly to all the other Delphi components.


Delphi AdsOcx Sample: Applications I

ADS-OCX: Linking to Delphi

ADS-OCX functionality limitation in Delphi Applications I

Delphi's Memory Manager

Please make sure the system variable IsMultiThread is set in your AdsOcx application. The Delphi's memory manager ist thread-safe only if this variable is set to True. If this variable is set the access to shared resources is protected.
Sometimes the Delphi's memory manager is not setting this variable if your application is using DLL's or controls with own threads.

Add the code below in the initialization section of your application:

Initialization
    IsMultiThread := True;// Setting this system variable makes Delphi's memory manager thread-safe

Methods/Properties

The following properties, methods and events cause errors in Delphi applications, and must not be used. As can be seen in the table, either the latest version of Delphi should be used, or certain functionalities must be omitted. There are a variety of a updates for the Delphi versions listed, and these may correct certain errors.

 | Error descriptions | Workaround | Delphi version
Properties |  |  | 3.0 | 4.0 | 5.0 | 6.0
AdsClientType | There is a memory leak when accessing this property.Memory space for the return string is not correctly allocated. | n/a | Bug | ? | Fixed |
AdsClientAdsState | There is a memory leak when accessing this property.Memory space for the return string is not correctly allocated. | n/a | Bug | ? | Fixed |
AdsClientAdsControl | There is a memory leak when accessing this property.Memory space for the return string is not correctly allocated. | n/a | Bug | ? | Fixed |
AdsServerAdsControl | There is a memory leak when accessing this property.Memory space for the return string is not correctly allocated. | n/a | Bug | ? | Fixed |
AdsServerAdsState | There is a memory leak when accessing this property.Memory space for the return string is not correctly allocated. | n/a | Bug | ? | Fixed |
AdsServerType | There is a memory leak when accessing this property.Memory space for the return string is not correctly allocated. | n/a | Bug | ? | Fixed |
AdsServerLastMessage | There is a memory leak when accessing this property.Memory space for the return string is not correctly allocated. | n/a | Bug | ? | Fixed |
AdsAmsClientNetId | There is a memory leak when accessing this property.Memory space for the return string is not correctly allocated. | n/a | Bug | ? | Fixed |
AdsAmsServerNetId | There is a memory leak when accessing this property.Memory space for the return string is not correctly allocated. | n/a | Bug | ? | Fixed |
Methods |  |  |  |  |  |
All AdsOcx methds | All functions (ADSOCXLib_TLB) returns undefined return values. | Please install Delphi 6 Update Pack 2 |  |  | no Bug | Bug
AdsSyncReadReq | These methods allow variables of any type to be transferred to the PLC or to be read from the PLC. The OleVariant parameters, however, are passed by value and not by reference by the AdsSyncReadReq method. This means that the method cannot alter the value of the data parameter during the call. Although it is true that the PLC variables are copied into a corresponding OleVariant variable during the call, that variable is only a copy of the actual variable from the current parameter list. The method prototypes for the ADS-OCX are generated automatically by the Delphi development environment when the ADS-OCX is linked, and cannot be modified. | Use the "released" methods to have synchronous access the PLC variables (e.g. AdsSyncReadIntegerReq() etc. ). | Bug | ? | Bug |
AdsSyncWriteReq
AdsReadVarConnectEx | Similarly to the process for the AdsSync methods, the OleVariant parameters in the event functions are passed by value and not by reference. | Use the AdsReadVarConnectEx2 method. | Bug | ? | Bug |
Events |  |  |  |  |  |
AdsReadConnectUpdateEx | An access violation is generated when the event function is called. | Use the AdsReadConnectUpdateEx2 event function. | Bug | ? | Bug |


Delphi AdsOcx Sample: Resetting Delphi Applications

Linking to Delphi

ADS-OCX: Resetting Delphi Applications

Following a program error it is often not possible to use the usual "Start->Program Reset" operating function. The following message is given by the debugger:

The cause of this is that using the ADS-OCX generates a client-server connection to the TwinCAT router, and this must be closed when the application stops. The Delphi application cannot be closed using the "Program Reset" menu command, because at this point there is a connection to the TwinCAT router. Their connection is generated in the application through the assignment of the AdsAmsNetId and the port number.

The following methods may be used to close the application without having to restart the computer:

  • First confirm the runtime error with OK, then stop the TwinCAT system via the task bar, and then reset the Delphi application. This causes existing connections to the clients to be closed. The disadvantage is that the TwinCAT system and the PLC must then be restarted;
  • First confirm the runtime error with OK, then call the Router Cleanup via the task bar, and then reset the Delphi application;
  • Make use of exception handling. The AdsAmsDisconnect() can be used to explicitly close the connection to the router;
        try
        n:=8; Switch[n].Tag:=0; // This index is not allowed
    except
        on EAccessViolation do
        begin
            AdsOcxSPS.AdsAmsDisconnect();
            Application.Terminate();
        end;
    end;


Delphi AdsOcx Sampe01: Accessing PLC variables in synchronous/asynchronous/connected modes

TwinCAT ADS-OCX: Examples

Accessing PLC variables in synchronous/asynchronous/connected modes

System requirements:

  • Delphi 5.0 or higher;
  • TwinCAT v2.9 or higher;

Task

The example program shows how AdsOcx methods and events can be used in a Delphi application. The various access types (synchronous/asynchronous/connected) are applied to the PLC variables. The PLC program defines an integer variable at address 100 in the process data flags area. The PLC variable is to be accessed for reading or writing from the Delphi application, using the various access modes.

Description

Synchronous, asynchronous or connected access to the PLC variables is possible by means of the AdsOcx. In a synchronous access the application is stopped until the requested data has arrived. In an asynchronous access, a request is sent to the PLC, after which execution of the Windows application continues. A callback function is then activated in the Windows application when the requested data has arrived. Under the connected access mode, an event function is called in the Windows application whenever the value of the PLC variable has changed.

Delphi 5 program

In the event function OnFormCreate, the AdsCreateVarHandle method requests a handle for the symbol name of the PLC variable. The handle is then used in the example application for read or write access to the PLC variable. The OnDestroy event function releases the handle once more using the AdsDeleteVarHandle method when the application is closed.

var
  Form1             : TForm1;
  varName           :WideString;    {PLC variable symbol name}
  varValue          :Smallint;      {PLC variable value}
  varHandle         :integer;       {PLC variable handle}
  hConnect          :integer;       {PLC variable connection handle}
  adsResult         :integer;       {Ads result}

implementation

{$R *.DFM}

procedure TForm1.OnFormCreate(Sender: TObject);
begin
     AdsOcx1.AdsAmsServerNetId := AdsOcx1.AdsAmsClientNetId;    {Sets PLC server network adress}
     AdsOcx1.AdsAmsServerPort := 801;                           {Sets the PLC run time system}
     varName := 'MAIN.VARINT16';
     varValue := 0;
     varHandle := 0;
     hConnect := 0;
     adsResult := AdsOcx1.AdsCreateVarHandle( varName, varHandle ); {creates variable handle}

     if adsResult = 0 then
        LabelVarHandle.Font.Color := clBlue
     else
        LabelVarHandle.Font.Color := clRed;

     LabelVarHandle.Caption := Format( 'AdsCreateVarHandle adsResult:%d   varName:%s   Handle:%d',[adsResult, varName, varHandle] );
end;

procedure TForm1.OnFormDestroy(Sender: TObject);
begin
     adsResult := AdsOcx1.AdsDeleteVarHandle( varHandle );
end;

Synchronous access

A mouse click on one of the buttons in the SYNCHRONOUS group will cause the value of the PLC variable to be read or written synchronously, and to be displayed as text on the form. There are two ways to access the PLC variable: using the variable name or the variable address.

Access by means of the variable address

procedure TForm1.OnSyncReadByAddrClick(Sender: TObject);
begin
     adsResult := AdsOcx1.AdsSyncReadIntegerReq( $00004020, 100, 2, varValue );
     LabelSyncRetData.Caption:=Format( 'adsResult:%d   Value:%d',[adsResult, varValue] );
end;

procedure TForm1.OnSyncWriteByAddrClick(Sender: TObject);
begin
     varValue := 100;
     adsResult := AdsOcx1.AdsSyncWriteIntegerReq( $00004020, 100, 2, varValue );
     LabelSyncRetData.Caption:=Format( 'adsResult:%d', [adsResult] );
end;

Access by means of the variable name

In the case of access using the variable name, the corresponding handle of the PLC variable is used as a parameter in the AdsSyncReadIntegerVarReq or AdsSyncWriteIntegerVarReq methods. The handle of the PLC variable is requested in the OnCreate event function when the application starts.

procedure TForm1.OnSyncReadByNameClick(Sender: TObject);
begin
     adsResult := AdsOcx1.AdsSyncReadIntegerVarReq( varHandle, 2, varValue );
     LabelSyncRetData.Caption:=Format( 'adsResult:%d   Value:%d', [adsResult, varValue] );
end;

procedure TForm1.OnSyncWriteByNameClick(Sender: TObject);
begin
     varValue := 200;
     adsResult := AdsOcx1.AdsSyncWriteIntegerVarReq( varHandle, 2, varValue );
     LabelSyncRetData.Caption:=Format( 'adsResult:%d', [adsResult] );
end;

Asynchronous access

The PLC variable can be accessed asynchronously by means of the AdsReadIntegerReq and AdsWriteIntegerReq methods.

procedure TForm1.OnAsyncReadByAddrClick(Sender: TObject);
var  varInvokeId       :integer;
begin
     varInvokeId := 33;
     adsResult := AdsOcx1.AdsReadIntegerReq( varInvokeId, $00004020, 100, 2 );
     LabelAsyncRetData.Caption:=Format( 'adsResult:%d', [adsResult] );
end;

procedure TForm1.OnAsyncWriteByAddrClick(Sender: TObject);
var  varInvokeId       :integer;
begin
     varInvokeId := 44;
     varValue := 300;
     adsResult := AdsOcx1.AdsWriteIntegerReq( varInvokeId, $00004020, 100, 2, varValue );
     LabelAsyncRetData.Caption:=Format( 'adsResult:%d', [adsResult] );
end;

After an asynchronous access the execution of the Delphi application is continued, and an event function is called in the Windows application once the return parameter is available. In our example, the event function AdsReadIntegerConf is called when reading the PLC variable, while for writing the PLC variable the event function called is AdsWriteConf.

procedure TForm1.AdsOcx1AdsReadIntegerConf(Sender: TObject; nInvokeId,
  nResult, cbLength: Integer; var pData: Smallint);
begin
     LabelAsyncEventData.Caption :=Format('nInvokeId:%d   nResult:%d   cbLength:%d  pData:%d',
                          [nInvokeId, nResult, cbLength, pData]);
end;

procedure TForm1.AdsOcx1AdsWriteConf(Sender: TObject; nInvokeId,
  nResult: Integer);
begin
     LabelAsyncEventData.Caption :=Format('nInvokeId:%d   nResult:%d', [nInvokeId, nResult]);
end;

Connected access

In the connected access mode, a "connection" to the PLC variable is established. Depending on the parameters (ADSTRANS_SERVERCYCLE or ADSTRANS_SERVERONCHA), the event functions are called either cyclically or when the PLC variable changes.

In the example application, clicking the mouse on the Connected read by address button calls the AdsReadIntegerConnect method, while a click on the Connected read by variable name button calls the AdsReadIntegerVarConnect method.

procedure TForm1.OnConReadByAddrClick(Sender: TObject);
begin
     adsResult := AdsOcx1.AdsReadIntegerConnect( $00004020, 100, 2, ADSTRANS_SERVERCYCLE, 220, varValue );
     LabelConRetData.Caption:=Format( 'adsResult:%d', [adsResult] );
end;

procedure TForm1.OnConReadByNameClick(Sender: TObject);
begin
     adsResult := AdsOcx1.AdsReadIntegerVarConnect( varName, 2, ADSTRANS_SERVERCYCLE, 220, varValue );
     LabelConRetData.Caption:=Format( 'adsResult:%d', [adsResult] );
end;

When successful, the AdsReadConnectUpdate event function is called in the Delphi application, regardless of which of the two methods is used to establish the connection.

procedure TForm1.AdsOcx1AdsReadConnectUpdate(Sender: TObject; nIndexGroup,
  nIndexOffset: Integer);
begin
     LabelConEventData.Caption := Format('nIndexGroup:%d  nIndexOffset:%d   Value:%d',
                          [nIndexGroup, nIndexOffset, varValue]);
end;

The AdsReadIntegerDisconnect method can be used to remove the connection to the PLC variable.

procedure TForm1.OnDisconnectReadClick(Sender: TObject);
begin
     adsResult := AdsOcx1.AdsReadIntegerDisconnect( varValue );
     LabelConRetData.Caption:=Format( 'adsResult:%d', [adsResult] );
end;

ConnectEx methods (connected access with a user handle)

The ConnectEx methods can be used, in a manner similar to that of the Connect methods, to establish connected access to the PLC variables. The ConnectEx methods have the advantage that a user-defined handle can be passed as a parameter in the connect method when the connection is established. This handle can then be evaluated in the event function, and used to identify the PLC variable for which the event function has been called.

Clicking with the mouse on the ConnectEx button will call the AdsReadVarConnectEx2 method in the OnConnectExClick routine.

procedure TForm1.OnConectExClick(Sender: TObject);
var  hUser :integer;
begin
     {disconnect old connection}
     if hConnect <> 0 then
     begin
        adsResult := AdsOcx1.AdsDisconnectEx( hConnect );
        if adsResult = 0 then
            hConnect := 0;
     end;

     hUser := 7;    {create user handle}

     adsResult := AdsOcx1.AdsReadVarConnectEx2( varName, ADSTRANS_SERVERCYCLE, 220, hConnect, hUser );
     LabelConExRetData.Caption:=Format( 'adsResult:%d   hConnect:%d', [adsResult, hConnect] );
end;

If the connection is successfully established, then the parameters will be displayed as text on the form in the event function AdsReadConnectUpdateEx2.

procedure TForm1.AdsOcx1AdsReadConnectUpdateEx2(Sender: TObject;
  dateTime: TDateTime; nMs, hConnect: Integer; var data,
  hUser: OleVariant);
begin
     LabelConExEventData.Caption :=Format('Date/Time:%s   nMs:%d   hConnect:%d   data:%d   hUser:%d',
                            [ TimeToStr(dateTime), nMs, hConnect, integer(data), integer(hUser)]);
end;

Clicking with the mouse on the DisconnectEx button will call the AdsDisconnectEx method, and the connection to the PLC variable will be removed.

procedure TForm1.OnDisconnectExClick(Sender: TObject);
begin
     adsResult := AdsOcx1.AdsDisconnectEx( hConnect );
     if adsResult = 0 then
         hConnect := 0;

     LabelConExRetData.Caption:=Format( 'adsResult:%d', [adsResult] );
end;

Comment:

In the course of linking the ADS-OCX into Delphi applications it has been found that the Delphi development environment generates faulty prototypes (more precisely: faulty parameter passing of OleVariant types) for the AdsReadConnectUpdateEx event function. For this reason, the ADS-OCX has been supplemented with a new AdsReadVarConnectEx2 method and associated AdsReadConnectUpdateEx2 event function. In the new event function the OleVariant parameter is passed by reference instead of by value.

Other
procedure TForm1.Exit1Click(Sender: TObject);
begin
    Close();
end;

procedure TForm1.Properties1Click(Sender: TObject);
begin
    AdsOcx1.BrowseProperties();
end;

procedure TForm1.About1Click(Sender: TObject);
begin
    AdsOcx1.AboutBox();
end;

Initialization
    IsMultiThread := True;// Setting this system variable makes Delphi's memory manager thread-safe

PLC program

PROGRAM MAIN
VAR
    VARINT16	AT%MB100:INT;
END_VAR

;

Language / IDE | Unpack the sample program
Delphi XE2 | Sample01.exe
Delphi 5 or higher (classic) | Sample01.exe


Delphi AdsOcx Sample02: Read the List of an ADS Device's Declared Variables

TwinCAT ADS-OCX: Examples

Read the List of an ADS Device's Declared Variables

System requirements:

  • Delphi 5.0 or higher;
  • TwinCAT v2.9 or higher;

Task

The example program illustrates how the AdsReadSymbolInfo and AdsEnumSymbols methods can be used to read the list of an ADS device's declared variables. By clicking with the mouse on the Read Symbol Info button, the symbol information of the first PLC run-time system (port 801), or of an additional task in the TwinCAT System Manager (port 301), is read and displayed in a table.

Description

Symbol Configuration for the PLC Run-Time System

In order to be able to access the symbol information for a PLC run-time system, it is necessary to activate the symbol generation for the PLC variables or structures, and for the symbol information to be loaded into the PLC run-time system during the project download. The settings necessary for the symbol download can be made in the option dialog for the TwinCAT category in TwinCAT PLC Control. The first PLC run-time system is addressed via port number 801.

Symbol Configuration of the Additional Task in the TwinCAT System Manager

An additional task can be inserted and configured in the TwinCAT System Manager. The variables of the additional task can be linked to other variables (e.g. with the PLC variables, or the I/O variables of a bus terminal controller). In order to be able to access the additional task's symbol information, the checkbox for symbol generation must be activated in the Task settings configuration dialog. The additional task is addressed via port number 301.

Delphi 5 program

The link to the PLC's first run-time system (port 801) on the local PC is created in the OnFormCreate event function. At the same time the ListView component and the necessary variables are initialised. The ReadSymInfoButtonClick method is called through a mouse-click on the Read Symbol Info button. In this method, the AdsReadSymbolInfo method is first called to determine the number of available symbols, after which a for-loop is used to read the symbol information for each individual symbol variable. The values are then added to the ListView component by means of the supplementary AddListViewItem procedure. The AdsEnumSymbols method possesses a boolean flag, bNext. If this flag is set to FALSE, the symbol information of the first symbol is read, but if bNext=TRUE then all the other symbols are read. In order to be able to read the symbol information of the additional task in the TwinCAT System Manager, the AdsAmsServerPort property of the AdsOcx component must be set to 301. The port number can be set at run-time using the AdsOcx component's properties page. The properties page can be called in the example application via the AdsOcx->Properties menu.

unit SampleUnit;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, OleCtrls, ADSOCXLib_TLB, ExtCtrls, ComCtrls, Menus;

type
  TForm1 = class(TForm)
    AdsOcx1: TAdsOcx;
    MainMenu1: TMainMenu;
    File1: TMenuItem;
    Exit1: TMenuItem;
    AdsOcx2: TMenuItem;
    Properties1: TMenuItem;
    About1: TMenuItem;
    ReadSymInfoButton: TButton;
    ListView1: TListView;
    StatusBar1: TStatusBar;
    procedure OnFormCreate(Sender: TObject);
    procedure Exit1Click(Sender: TObject);
    procedure Properties1Click(Sender: TObject);
    procedure About1Click(Sender: TObject);
    procedure ReadSymInfoButtonClick(Sender: TObject);
  private
    { Private declarations }
    procedure CreateColumns(Sender: TObject);
    procedure AddListViewItem(Sender: TObject; strSymbolName, strComment :WideString; nSymbolType, cbSymbolSize , nIndexGroup, nIndexOffset : integer);
  public
    { Public declarations }
  end;

var
  Form1             : TForm1;
  adsResult         : integer;       {Ads result}
  nSymbols          : integer;
  nSymByteSize      : integer;

implementation

{$R *.DFM}

procedure TForm1.CreateColumns(Sender: TObject);
var ListColumn :TListColumn;
begin
     ListView1.ViewStyle := vsReport;
     ListView1.Align := alBottom;
     ListColumn := ListView1.Columns.Add();
     ListColumn.Width := 50;
     ListColumn.Caption := 'No';
     ListColumn.Alignment := taLeftJustify;

     ListColumn := ListView1.Columns.Add();
     ListColumn.Width := 200;
     ListColumn.Caption := 'Symbol name';
     ListColumn.Alignment := taLeftJustify;

     ListColumn := ListView1.Columns.Add();
     ListColumn.Width := 100;
     ListColumn.Caption := 'Type';
     ListColumn.Alignment := taLeftJustify;

     ListColumn := ListView1.Columns.Add();
     ListColumn.Width := 100;
     ListColumn.Caption := 'Size';
     ListColumn.Alignment := taLeftJustify;

     ListColumn := ListView1.Columns.Add();
     ListColumn.Width := 100;
     ListColumn.Caption := 'Comment';
     ListColumn.Alignment := taLeftJustify;

     ListColumn := ListView1.Columns.Add();
     ListColumn.Width := 100;
     ListColumn.Caption := 'Index Group';
     ListColumn.Alignment := taLeftJustify;

     ListColumn := ListView1.Columns.Add();
     ListColumn.Width := 100;
     ListColumn.Caption := 'Index Offset';
     ListColumn.Alignment := taLeftJustify;
end;

procedure TForm1.OnFormCreate(Sender: TObject);
begin
     nSymbols := 0;
     nSymByteSize := 0;
     StatusBar1.SimplePanel := true;

     AdsOcx1.AdsAmsServerNetId := AdsOcx1.AdsAmsClientNetId;    {Sets PLC server network adress}
     AdsOcx1.AdsAmsServerPort := 801;                           {Sets the PLC run time system}
     StatusBar1.SimpleText := AdsOcx1.AdsServerAdsState;

     CreateColumns(Sender);
end;

procedure TForm1.ReadSymInfoButtonClick(Sender: TObject);
var
   strSymbolName : WideString;
   nSymbolType : Integer;
   cbSymbolSize : Integer;
   strComment : WideString;
   nIndexGroup : Integer;
   nIndexOffset : Integer;

   bNext : WordBool;
   nSymNo      :Integer;
begin
     ListView1.Items.Clear();          {clear old items}

     adsResult := AdsOcx1.AdsReadSymbolInfo( nSymbols, nSymByteSize );
     StatusBar1.SimpleText := Format('AdsReadSymbolInfo result:%d, nSymbols:%d, nSymByteSize:%d', [adsResult, nSymbols, nSymByteSize]);

     if ( ( adsResult = 0 ) And ( nSymbols > 0 ) ) then
     begin

          bNext := false;          {read first symbol info}
          adsResult := AdsOcx1.AdsEnumSymbols( strSymbolName, nSymbolType, cbSymbolSize, strComment, nIndexGroup, nIndexOffset, bNext);
          AddListViewItem(Sender, strSymbolName, strComment, nSymbolType, cbSymbolSize, nIndexGroup, nIndexOffset);

          if adsResult > 0 then
             StatusBar1.SimpleText := Format('AdsEnumSymbols result:%d', [adsResult]);

          for nSymNo := 1 to nSymbols-1 do
          begin
               bNext := true;
               adsResult := AdsOcx1.AdsEnumSymbols( strSymbolName, nSymbolType, cbSymbolSize, strComment, nIndexGroup, nIndexOffset, bNext);
               AddListViewItem(Sender, strSymbolName, strComment, nSymbolType, cbSymbolSize, nIndexGroup, nIndexOffset);

               if (adsResult > 0) then
                  StatusBar1.SimpleText := Format('AdsEnumSymbols result:%d', [adsResult]);
         end;
     end;
end;

procedure TForm1.AddListViewItem(Sender: TObject; strSymbolName, strComment :WideString; nSymbolType, cbSymbolSize , nIndexGroup, nIndexOffset : integer);
var   ListItem     :TListItem;
      strAdsType   :String;
begin
     ListItem := ListView1.Items.Add();
     ListItem.Caption :=  Format('%d',[ListView1.Items.Count]);

     ListItem.SubItems.Add(strSymbolName);

     case  nSymbolType of
           0:  strAdsType := 'ADST_VOID';
           16:  strAdsType := 'ADST_INT8';
           17:  strAdsType := 'ADST_UINT8';
           2:  strAdsType := 'ADST_INT16';
           18:  strAdsType := 'ADST_UINT16';
           3:  strAdsType := 'ADST_INT32';
           19:  strAdsType := 'ADST_UINT32';
           20:  strAdsType := 'ADST_INT64';
           21:  strAdsType := 'ADST_UINT64';
           4:  strAdsType := 'ADST_REAL32';
           5:  strAdsType := 'ADST_REAL64';
           65:  strAdsType := 'ADST_BIGTYPE';
           30:  strAdsType := 'ADST_STRING';
           31:  strAdsType := 'ADST_WSTRING';
           32:  strAdsType := 'ADST_REAL80';
           33:  strAdsType := 'ADST_BIT';
           34:  strAdsType := 'ADST_MAXTYPES';
     end;

     ListItem.SubItems.Add(Format('%s',[strAdsType]));
     ListItem.SubItems.Add(Format('0x%x (%d)',[cbSymbolSize, cbSymbolSize]));
     ListItem.SubItems.Add(strComment);
     ListItem.SubItems.Add(Format('0x%x (%d)',[nIndexGroup, nIndexGroup]));
     ListItem.SubItems.Add(Format('0x%x (%d)',[nIndexOffset, nIndexOffset]));
end;

procedure TForm1.Exit1Click(Sender: TObject);
begin
    Close();
end;

procedure TForm1.Properties1Click(Sender: TObject);
begin
    AdsOcx1.BrowseProperties();
    StatusBar1.SimpleText := AdsOcx1.AdsServerAdsState;
end;

procedure TForm1.About1Click(Sender: TObject);
begin
    AdsOcx1.AboutBox();
end;

Initialization
    IsMultiThread := True;// Setting this system variable makes Delphi's memory manager thread-safe

end.

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 *)
END_VAR

;

Language / IDE | Unpack the sample program
Delphi XE2 | Sample02.exe
Delphi 5 or higher (classic) | Sample02.exe


Delpi AdsOcx Sample03: Write an array into the PLC, or read AN ARRAY out the PLC

TwinCAT ADS-OCX: Examples

Write an array into the PLC, or read an array out the PLC

System requirements:

  • Delphi 6.0 or higher;
  • TwinCAT v2.10 or higher;

Use the "released" methods for particular data types. If you want, for example, to read an array in the PLC of type INT, then the following methods may be used, depending on the access type:

AdsSyncReadIntegerVarReq( hVar : Integer, length : Integer, var pData : Smallint )

AdsSyncReadIntegerReq( indexGroup : Integer, indexOffset : Integer, length : Integer, var pData : Smallint )

AdsSyncWriteIntegerVarReq(hVar : Integer, length : Integer, var pData : Smallint )

AdsSyncWriteIntegerReq( indexGroup : Integer, indexOffset : Integer, length : Integer, var pData : Smallint )

AdsReadIntegerReq( nInvokeId : Integer, nIndexGroup : Integer, nIndexOffset : Integer, cbLength : Integer )

AdsWriteIntegerReq( nInvokeId : Integer, nIndexGroup : Integer, nIndexOffset : Integer, cbLength : Integer, var pData : Smallint )

String arrays cannot be accessed in this way. The length of the data that is to be read or written may be found from the number of elements that are to be read or written, multiplied by the byte-size of each element. This length must be passed in the length or cbLength parameters. The parameter pData is used to pass the first element of the Delphi array.

Example:

PROGRAM MAIN
VAR
    varIntArray :ARRAY[1..9] OF INT:=9(1);
END_VAR

Delphi 6 program:

Reading an array from the PLC:

procedure TForm1.SyncReadArrayVarButtonClick(Sender: TObject);
var i, hVar, AdsResult:integer;
    varIntArray       : ARRAY[1..9] OF Smallint;
begin
    AdsResult := AdsOcx1.AdsCreateVarHandle( 'MAIN.VARINTARRAY', hVar );
    if AdsResult = 0 then
    begin
        AdsResult := AdsOcx1.AdsSyncReadIntegerVarReq( hVar, sizeof(varIntArray), varIntArray[1] );
        if AdsResult = 0 then
        begin
            ListBox1.Clear();
            for i:=1 to 9 do
                ListBox1.Items.Add( Format('varIntArray[%d] = %d', [i, varIntArray[i]] ) );
        end
        else Label1.Caption := Format('AdsSyncReadIntegerVarReq error:%d', [AdsResult] );
        AdsOcx1.AdsDeleteVarHandle( hVar );
    end
    else Label1.Caption := Format('AdsCreateVarHandle error:%d', [AdsResult] );
end;

Writing an array into the PLC:

procedure TForm1.SyncWriteArrayVarButtonClick(Sender: TObject);
var i, hVar, AdsResult:integer;
    varIntArray       : ARRAY[1..9] OF Smallint;
begin
    for i:=1 to 9 do
        varIntArray[i] := i;

    AdsResult := AdsOcx1.AdsCreateVarHandle( 'MAIN.VARINTARRAY', hVar );
    if AdsResult = 0 then
    begin
        AdsResult := AdsOcx1.AdsSyncWriteIntegerVarReq( hVar, sizeof(varIntArray), varIntArray[1] );
        if AdsResult > 0 then
            Label1.Caption := Format('AdsSyncWriteIntegerVarReq error:%d', [AdsResult] );
        AdsOcx1.AdsDeleteVarHandle( hVar );
    end
    else Label1.Caption := Format('AdsCreateVarHandle error:%d', [AdsResult] );
end;

Language / IDE | Unpack the sample program
Delphi XE2 | Sample03.exe
Delphi 6 or higher (classic) | Sample03.exe


Delphi AdsOcx Sample04: Open the properties page

TwinCAT ADS-OCX: Examples

Open the ADS-OCX properties page

System requirements:

  • Delphi 7.0 or higher;
  • TwinCAT v2.9 or higher;

Description

The ADS-OCX properties page is opened under Delphi as follows:

procedure TForm1.btnShowPropertyPageClick(Sender: TObject);
begin
    AdsOcx1.BrowseProperties();
end;

Language / IDE | Unpack the sample program
Delphi XE2 | Sample04.exe
Delphi 7 or higher (classic) | Sample04.exe


Delphi AdsOcx Sample05: Fetch the handle for the PLC variable

TwinCAT ADS-OCX: Examples

Fetch the handle for the PLC variable

System requirements:

  • Delphi 7.0 or higher;
  • TwinCAT v2.9 or higher;

All the required handles can be fetched once at the start of the application, and released again when the application is closed. Continuously requesting and releasing handles places unnecessary loading on the system.

Handles that have already been requested become invalid when TwinCAT is restarted, and must be requested again. The same applies after 'Rebuild All' in the PLC. 'Rebuild All' causes a complete new program to be loaded into the runtime system, so that any handles that have already been requested are invalid, and are automatically released by TwinCAT. The handles that are no longer required must always be released. This can, however, only be done if the TwinCAT system is still running. If the TwinCAT system has already stopped, then all the handles are automatically released.

Connect with the first runtime system on the local PC and fetch the handle of the PLC variables:

procedure TForm1.FormCreate(Sender: TObject);
var adsResult : Integer;
begin
   AdsOcx1.AdsAmsServerNetId := AdsOcx1.AdsAmsClientNetId;
   AdsOcx1.AdsAmsServerPort := 801;
   adsResult := AdsOcx1.AdsCreateVarHandle( 'MAIN.VARINTARRAY', hVar );
   if adsResult <> 0 then
      ShowMessage( Format( 'AdsCreateVarHandle() error:%d', [adsResult] ) );
end;

Release the handle when the application is closed:

procedure TForm1.FormDestroy(Sender: TObject);
var adsResult : Integer;
begin
   adsResult := AdsOcx1.AdsDeleteVarHandle( hVar );
   if AdsResult <> 0 then
      ShowMessage( Format( 'AdsDeleteVarHandle() error:%d', [adsResult] ) );
   hVar := 0;
end;
Language / IDE | Unpack the sample program
Delphi XE2 | Sample05.exe
Delphi 7 or higher (classic) | Sample05.exe


Delphi AdsOcx Sample06: Write a string into the PLC, or read a string out the PLC

TwinCAT ADS-OCX: Examples

Write a string into the PLC, or read a string out the PLC

System requirements:

  • Delphi 7.0 or higher;
  • TwinCAT v2.11 Build 2034 or higher;

Task

Read or write string from/to the PLC.

Description

The actual length of a PLC string can be determined using the PLC operator SIZEOF. Within the PLC the strings are terminated with a null, and the actual string length is calculated from the defined length plus 1. If no length is given when the string is defined, then a string has an actual length of 81 characters, including the final null.

PLC program

PROGRAM MAIN
VAR
    strColor 	:STRING(10)	:='Blue';
    strState 	:STRING(20)	:='STOP';
    strMessage 	:STRING		:='TwinCAT ADS-OCX';
END_VAR

strColor has a length of 11 characters;

strState has a length of 21 characters;

strMessage has a length of 81 characters;

Delphi 7 program

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, OleCtrls, ADSOCXLib_TLB, Grids, ValEdit, ComCtrls;

type
  TForm1 = class(TForm)
    btnWrite: TButton;
    AdsOcx1: TAdsOcx;
    Label1: TLabel;
    Edit1: TEdit;
    btnRead: TButton;
    procedure btnReadClick(Sender: TObject);
    procedure btnWriteClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
    adsResult   : Integer;// Ads return code
    hVar        : Integer;// PLC variable handle
    varString   : WideString;// PLC variable value
  public
    { Public declarations }
  end;

var
   Form1: TForm1;

implementation

{$R *.dfm}

Reading a string from the PLC

procedure TForm1.btnReadClick(Sender: TObject);
begin
   SetLength(varString, 7 );//Realocate string space to a given length
   // Read string from PLC
   adsResult := AdsOcx1.AdsSyncReadStringVarReq( hVar, Length(varString) * 2, varString );
   if  adsResult = 0 then
      edit1.Text := varString
   else ShowMessage( Format( 'AdsSyncReadStringVarReq() error:%d', [adsResult] ) );
end;

In the example above, seven characters of a PLC string were read in Delphi. The dynamic string types have a length of zero immediately after initialisation. The Delphi string variable must first be allocated the correct length if the ADS-OCX is to be able to copy the PLC string into the Delphi string variable. In Delphi, a WideString variable requires two bytes for each character. The length function returns the number of allocated characters in the string. The Length parameter in the method call, however, requires the length in bytes, and the length determined by the length function is therefore doubled.

Writing a string into the PLC

procedure TForm1.btnWriteClick(Sender: TObject);
begin
   varString := Edit1.Text;
   // Write string to the PLC
   adsResult := AdsOcx1.AdsSyncWriteStringVarReq( hVar, Length(varString)*2, varString );
   if adsResult <> 0 then
      ShowMessage( Format( 'AdsSyncWriteStringVarReq() error:%d', [adsResult] ) );
end;

Setup connection, create variable handle

procedure TForm1.FormCreate(Sender: TObject);
begin
  // Connection Setup
  AdsOcx1.AdsAmsServerNetId := AdsOcx1.AdsAmsServerNetId;
  AdsOcx1.AdsAmsServerPort := 801;

  // Create variable handle
  adsResult := AdsOcx1.AdsCreateVarHandle( 'MAIN.STRMESSAGE', hVar );
  if adsResult <> 0 then
     ShowMessage( Format( 'AdsCreateVarHandle() error:%d', [adsResult] ) );
end;

Release variable handle

procedure TForm1.FormDestroy(Sender: TObject);
var adsResult : Integer;
begin
   // Delete variable handle
   adsResult := AdsOcx1.AdsDeleteVarHandle( hVar );
   if AdsResult <> 0 then
      ShowMessage( Format( 'AdsDeleteVarHandle() error:%d', [adsResult] ) );
   hVar := 0;
end;
Initialization
   IsMultiThread := True;// Setting this system variable makes Delphi's memory manager thread-safe

end.

Language / IDE | Unpack the sample program
Delphi XE2 | Sample06.exe
Delphi 7 or higher (classic) | Sample06.exe


Delphi AdsOcx Sample07: Applications VIII

TwinCAT ADS-OCX: Examples

Read a number of boolean variables into an array with one access

System requirements:

  • Delphi 7.0 or higher;
  • TwinCAT v2.11 Build 2034 or higher;

Task

Multiple boolean PLC variables can be read into Delphi applications with one access, provided the variables are stored at addresses that are sequentially located in the memory. It is, however, important that the first variable is located at a byte address.

Description

PLC program

PROGRAM MAIN
VAR
    varBoolean AT%MB6 : ARRAY[1..4] OF BOOL;
END_VAR
;

Delphi 7 program

unit Unit1;
interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, OleCtrls, ADSOCXLib_TLB, StdCtrls;

type
  TForm1 = class(TForm)
    btnRead: TButton;
    AdsOcx1: TAdsOcx;
    ListBox1: TListBox;
    procedure btnReadClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  varBoolArray : ARRAY[1..4] OF WordBool;

implementation
{$R *.dfm}

procedure TForm1.btnReadClick(Sender: TObject);
var 	i, hVar, AdsResult:integer;
begin
    // Create variable handle
    AdsResult := AdsOcx1.AdsCreateVarHandle( 'MAIN.VARBOOLEAN', hVar );
    if AdsResult = 0 then
    begin
              // Read data
        AdsResult := AdsOcx1.AdsSyncReadBoolVarReq( hVar, sizeof(varBoolArray), varBoolArray[1] );
        if AdsResult = 0 then
        begin
                 // Clear list view and show data
            ListBox1.Clear();
            for i:=1 to 4 do
                   ListBox1.Items.Add( Format('varBoolArray[%d] = %s', [i, BoolToStr(varBoolArray[i], true) ] ) );
        end
        else ShowMessage( Format( 'AdsSyncReadBooleanVarReq() error:%d', [AdsResult] ) );

        // Release variable handle
         AdsResult := AdsOcx1.AdsDeleteVarHandle( hVar );
        if AdsResult <> 0 then
            ShowMessage( Format( 'AdsDeleteVarHandle() error:%d', [AdsResult] ) );
    end
    else ShowMessage( Format( 'AdsCreateVarHandle() error:%d', [AdsResult] ) );
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
   // Connection Setup
   AdsOcx1.AdsAmsServerNetId := AdsOcx1.AdsAmsServerNetId;
   AdsOcx1.AdsAmsServerPort := 801;
end;

Initialization
   IsMultiThread := True;// Setting this system variable makes Delphi's memory manager thread-safe

end.

Language / IDE | Unpack the sample program
Delphi XE2 | Sample07.exe
Delphi 7 or higher (classic) | Sample07.exe


Delphi AdsOcx Sample08: Transmitting structures to/from the PLC

TwinCAT ADS-OCX: Examples

Transmitting structures to/from the PLC

System requirements:

  • Delphi 7.0 or higher;
  • TwinCAT v2.11 Build 2034 or higher;

Task

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

Description

Structure in the PLC program

TYPE PLCStruct
STRUCT
    valSmallint 	: INT;
    valLongint 	: DINT;
    valByte 		: BYTE;
    valDouble 	: LREAL;
    valSingle 	: REAL;
END_STRUCT
END_TYPE

PLC program

PROGRAM MAIN
VAR
    PLCVar : PLCStruct;
END_VAR
;

Structure in Delphi program

type
    TPLCStruct = packed record 	// packed == force 1 byte alignment
    valSmallint 	: Smallint; 	// 2 bytes
    valLongint 	: Longint; 	// 4 bytes
    valByte 		: Byte; 		// 1 byte
    valDouble 	: Double; 	// 8 bytes
    valSingle 	: Single; 	// 4 bytes
                    // = 19 bytes in memory
End;

Delphi 7 program

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, OleCtrls, ADSOCXLib_TLB, StdCtrls;

type
  TForm1 = class(TForm)
    GroupBox1: TGroupBox;
    AdsOcx1: TAdsOcx;
    btnWrite: TButton;
    btnRead: TButton;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    Label5: TLabel;
    editSmallint: TEdit;
    editLongint: TEdit;
    editByte: TEdit;
    editDouble: TEdit;
    editSingle: TEdit;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure btnWriteClick(Sender: TObject);
    procedure btnReadClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
   TPLCStruct = packed record // packed == force 1 byte alignment
      valSmallint : Smallint; // 2 bytes
      valLongint  : Longint;  // 4 bytes
      valByte     : Byte;     // 1 byte
      valDouble   : Double;   // 8 bytes
      valSingle   : Single;   // 4 bytes
                              // = 19 bytes in memory
End;

var
  Form1: TForm1;
  hVar : Integer;

  // Create instance and initialize delphi structure members
  PLCStruct : TPLCStruct = ( valSmallint : 1000;
                           valLongint : 100000;
                           valByte : 100;
                           valDouble : 3.1415;
                           valSingle : 3.14 );
implementation
{$R *.dfm}

//--- Is called a the start ---
procedure TForm1.FormCreate(Sender: TObject);
var text : String;
begin
   //--- Enable exception ---
   AdsOcx1.EnableErrorHandling := True;
   //--- Set connection ---
   AdsOcx1.AdsAmsServerPort := 801;
   AdsOcx1.AdsAmsServerNetId := AdsOcx1.AdsAmsClientNetId;
   //--- Get PLC variable handle by variable name
   AdsOcx1.AdsCreateVarHandle('Main.PLCVar', hVar);
   //--- View init values ---
   Str( PLCStruct.valSmallint, text );
   editSmallint.Text := text;
   Str( PLCStruct.valLongint, text );
   editLongint.Text := text;
   Str( PLCStruct.valByte, text );
   editByte.Text := text;
   Str( PLCStruct.valDouble : 0 : 15, text );
   editDouble.Text := text;
   Str( PLCStruct.valSingle : 0 : 8, text );
   editSingle.Text := text;
end;

//--- Is called at the end ---
procedure TForm1.FormDestroy(Sender: TObject);
begin
   //--- Release PLC variable handle ---
   AdsOcx1.AdsDeleteVarHandle(hVar);
end;

//--- Is called by the user  ---
procedure TForm1.btnWriteClick(Sender: TObject);
var code : Integer;
begin
   //--- Fill structure ---
   Val( editSmallint.Text, PLCStruct.valSmallint, code );
   Val( editLongint.Text, PLCStruct.valLongint, code );
   Val( editByte.Text, PLCStruct.valByte, code );
   Val( editDouble.Text, PLCStruct.valDouble, code );
   Val( editSingle.Text, PLCStruct.valSingle, code );
   //--- Write structure to the PLC ---
   AdsOcx1.AdsSyncWriteIntegerVarReq( hVar, sizeof(PLCStruct), PLCStruct.valSmallint );
end;

//--- Is called by the user ---
procedure TForm1.btnReadClick(Sender: TObject);
var text : String;
begin
   //--- Read structure from the PLC ---
   AdsOcx1.AdsSyncReadIntegerVarReq( hVar, sizeof(PLCStruct), PLCStruct.valSmallint );
   //--- View read structure data ---
   Str( PLCStruct.valSmallint, text );
   editSmallint.Text := text;
   Str( PLCStruct.valLongint, text );
   editLongint.Text := text;
   Str( PLCStruct.valByte, text );
   editByte.Text := text;
   Str( PLCStruct.valDouble : 0 : 15, text );
   editDouble.Text := text;
   Str( PLCStruct.valSingle : 0 : 8, text );
   editSingle.Text := text;
end;

Initialization
   IsMultiThread := True;// Setting this system variable makes Delphi's memory manager thread-safe

end.

Language / IDE | Unpack the sample program
Delphi XE2 | Sample08.exe
Delphi 7 or higher (classic) | Sample08.exe


Delphi TcAdsDll Sample: Linking to Delphi (API interface)

TwinCAT TcAdsDLL: Delphi examples

Linking to Delphi (API interface)

Necessary files

The following files are needed in order to link the DLL into Delphi programs:

  • TcAdsDll.dll - dynamic function library

TcAdsDll.dll is located in the Windows NT/2000 'System32' directory.

Optional for static loading: Note: The archive delphi_adsdll_api_units.zip contains the the declaration files TcAdsAPI.pas and TcAdsDEF.pas

  • Ported TcAdsAPI.h C++ header file (declarations of the ADS functions): TcAdsAPI.pas
  • Ported TcAdsDEF.h C++ header file (declarations of the structures and constants): TcAdsDEF.pas

If you want to compile/build the sample code then you need:

  • Delphi 5.0 or higher;
  • ADS sum command examples: Delphi 6.0 + Update Pack 2 or higher;
  • Extended functions (multithreading) example: Delphi 7.0 + Update 7.1 or higher;

In theory you can also work on the examples with a newer Delphi version. Additional adaptations to the new Delphi version often need to be made in the source code after the conversion of the project. These adaptations may be necessary, for example, due to a change in the compiler in the handling of the VARIANT data types or string data types.

Calling DLLs in Delphi

Before routines that are defined in the TcAdsDLL can be called, they must first be imported. There are two ways to carry out the import: One is to declare a procedure or function as external, the other is to call the Windows API directly. In both methods, the routine is not linked until the application's run-time. This means that the DLL is not required at compile time. By the same token, however, it means that the compiler cannot check whether the routine has been correctly imported.

Loading the DLL function statically

The simplest way to import a procedure or function is to declare it with the external directive:

procedure MyDllFunction; external 'MYLIB.DLL';

This declaration will load the MYLIB.DLL file when the program starts. As the program executes, the MyDllFunction identifier always refers to the same entry point in the same DLL.
You can insert the declaration of an imported routine directly into the program or unit in which it is called. In order to be able to maintain your programs more easily you should, however, collect all the external declarations into a separate "import unit". This unit will then contain the constants and the type definitions needed for the interface to the DLL (Delphi's Windows unit is a good example of this). Other modules that access the import unit can call all the routines that are declared within it.

Detailed information about external declarations and dynamic loading via calls to the Windows API are to be found in the Delphi online help.

The following rules should be observed when porting the TcAdsDLL to Pascal applications:

  • all C++ macros must be converted into corresponding Pascal functions;
  • all C++ data types must be converted into corresponding Pascal types;
  • the data types in the C++ function parameters must be converted to corresponding Pascal data types.

The most important TcAdsDLL.DLL declarations have been summarised in two units: TcAdsDEF.pas and TcAdsAPI.pas. If you want to load the functions statically, these units must be integrated into the Delphi application via a Uses clause.

Information about the Delphi memory manager in multi-threading applications

The Delphi memory manager uses ‘critical sections’ in order to ensure thread safety in multi-threading applications. However, the ‘critical sections’ are only used if a not so well-known system variable IsMultiThread is set to True. The IsMultiThread variable is defined in the ‘System.pas’ unit, which is used implicitly by all Delphi units.

The IsMultiThread variable must be set to True whenever the application or an integrated library uses multiple threads. Otherwise the application or the library is not ‘thread safe’.
However, IsMultiThread is only implicitly set if the developer uses the Delphi TThread object in the application or in the library.

The Delphi memory manager assumes, for example, that the application is a single-thread application if:

  • a DLL is integrated that uses multiple threads;
  • no TThread object is used by the developer;

Access to common resources is not locked in this case.

TcAdsDLL.dll uses its own threads, however. For this reason it must be ensured that the IsMultiThread variable is set to True as soon as TcAdsDLL is integrated into a Delphi application.

In the current versions of the TcAdsDEF.pas and TcAdsAPI.pas units, the IsMultiThread variable is set to True in the initialisation section. However, it is the developer’s responsibility to ensure that the variable is always set at the correct point in time in a multi-threading application.


TcAdsDll Delphi Sample01

TwinCAT TcAdsDLL: Delphi examples

All-in-one example

In this sample application all functions are presented to the DLL and can be tested by right-clicking on the associated button. If the port was opened successfully, a remote PC can be selected for communication.

Requirements:

  • Delphi 5.0 or higher;
  • TcAdsDLL.DLL;
  • TcAdsDEF.pas and TcAdsAPI.pas if you wish to translate the source text yourself;

The page containing the source code can be opened by clicking on one of the buttons in the diagram.

Language / IDE | Unpack the sample program
Delphi XE2 | Sample01.exe
Delphi 5 or higher (classic) | Sample01.exe


TcAdsDll Delphi Sample01_01

TwinCAT TcAdsDLL: Delphi examples

AdsGetDllVersion

Delphi 5 program

unit frmAdsGetDllVersionUnit;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, TcAdsDef, TcAdsApi, Buttons;

type
  TfrmAdsGetDllVersion = class(TForm)
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    BitBtn1: TBitBtn;
    procedure FormCreate(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

implementation

{$R *.DFM}

procedure TfrmAdsGetDllVersion.FormCreate(Sender: TObject);
var tmp : Longword;
    pVersion :PAdsVersion;
begin
    tmp := AdsGetDllVersion();
    pVersion := PAdsVersion(@tmp);
    Label1.Caption := Format( 'Version: %d', [pVersion.version]);
    Label2.Caption := Format( 'Revision: %d', [pVersion.revision]);
    Label3.Caption := Format( 'Build: %d', [pVersion.build]);
end;

end.

Language / IDE | Unpack the sample program
Delphi XE2 | Sample01.exe
Delphi 5 or higher (classic) | Sample01.exe


TcAdsDll Delphi Sample01_02

TwinCAT TcAdsDLL: Delphi examples

AdsPortOpen

Delphi 5 program

unit frmAdsPortOpenUnit;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, TcAdsDef, TcAdsApi, Buttons;

type
  TfrmAdsPortOpen = class(TForm)
    Label1: TLabel;
    BitBtn1: TBitBtn;
    procedure FormCreate(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
    port : Longint;
  end;

implementation

{$R *.DFM}
//////////////////////////////////////////////////////////////////////////////
procedure TfrmAdsPortOpen.FormCreate(Sender: TObject);
begin
     port := AdsPortOpen();
     Label1.Caption := Format( 'Client port: %d [0x%x]', [port,port] );
end;

end.
Language / IDE | Unpack the sample program
Delphi XE2 | Sample01.exe
Delphi 5 or higher (classic) | Sample01.exe


TcAdsDll Delphi Sample01_03

TwinCAT TcAdsDLL: Delphi examples

AdsPortClose

Delphi 5 program

unit frmAdsPortCloseUnit;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, TcAdsDef, TcAdsApi, Buttons;

type
  TfrmAdsPortclose = class(TForm)
    Label1: TLabel;
    BitBtn1: TBitBtn;
    procedure FormCreate(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

implementation

{$R *.DFM}
///////////////////////////////////////////////////////////////
procedure TfrmAdsPortclose.FormCreate(Sender: TObject);
var result : longint;
begin
     result :=AdsPortClose();
     Label1.Caption := Format( 'AdsPortClose() result: %d [0x%x]', [result, result]);
end;

end.

Language / IDE | Unpack the sample program
Delphi XE2 | Sample01.exe
Delphi 5 or higher (classic) | Sample01.exe


TcAdsDll Delphi Sample01_04

TwinCAT TcAdsDLL: Delphi examples

AdsGetLocalAddress

Delphi 5 program

unit frmAdsGetLocalAddressUnit;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, TcAdsDef, TcAdsApi, Buttons ;

type
  TfrmAdsGetLocalAddress = class(TForm)
    Label1: TLabel;
    BitBtn1: TBitBtn;
    procedure FormCreate(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
    addr : TAmsAddr;
  end;

implementation

{$R *.DFM}
/////////////////////////////////////////////////////////////////////////
procedure TfrmAdsGetLocalAddress.FormCreate(Sender: TObject);
var result : Longint;
begin
     result  := AdsGetLocalAddress( @addr );

     Label1.Caption := Format( 'Result: %d, AmsNetId: %d.%d.%d.%d.%d.%d, AmsPort: %d [0x%x]',[
                    result,addr.netid.b[0],addr.netid.b[1],addr.netid.b[2],
                    addr.netid.b[3],addr.netid.b[4],addr.netid.b[5],
                    addr.port,  addr.port] );
end;

end.

Language / IDE | Unpack the sample program
Delphi XE2 | Sample01.exe
Delphi 5 or higher (classic) | Sample01.exe


TcAdsDll Delphi Sample01_05

TwinCAT TcAdsDLL: Delphi examples

AdsSyncWriteReq

Delphi 5 program

unit frmAdsSyncWriteReqUnit;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, TcAdsDef, TcAdsApi, Buttons, ExtCtrls ;

type
  TfrmAdsSyncWriteReq = class(TForm)
    editIO: TEdit;
    editIG: TEdit;
    Label2: TLabel;
    Label3: TLabel;
    editValue: TEdit;
    Label1: TLabel;
    Label5: TLabel;
    ComboBox1: TComboBox;
    Label4: TLabel;
    BitBtn1: TBitBtn;
    Button1: TButton;
    Bevel1: TBevel;
    procedure ComboBox1Change(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private-Deklarationen }
    serverAddr          : TAmsAddr;
  public
    { Public-Deklarationen }
    procedure InitData( destAddr : TAmsAddr );
  end;

type TPlcDataTypes =
(
    plcBOOL,
    plcBYTE,  //and USINT
    plcWORD,  //and UINT
    plcDWORD, //and UDINT
    plcSINT,
    plcINT,
    plcDINT,
    plcREAL,
    plcLREAL,
    plcSTRING
);

implementation

{$R *.DFM}
///////////////////////////////////////////////////////////////////////
procedure TfrmAdsSyncWriteReq.InitData( destAddr : TAmsAddr );
begin
     serverAddr := destAddr;
     ComboBox1.itemindex := 0;
     ComboBox1Change(self);
end;
///////////////////////////////////////////////////////////////////////
procedure TfrmAdsSyncWriteReq.Button1Click(Sender: TObject);
var result              : longword;
    dataType            : TPlcDataTypes;
    indexGroup          : Longword;
    indexOffset         : Longword;

    nBYTE               : Byte; //and USINT
    nWORD               : Word; //and UINT
    nDWORD              : Longword; //and UDINT
    iSINT               : Shortint;
    iINT                : Smallint;
    iDINT               : Longint;
    fREAL               : Single;
    fLREAL              : Double;
    sSTRING             : AnsiString;
begin
     result := 0;
     dataType :=  TPlcDataTypes(ComboBox1.ItemIndex);

     indexGroup := Longword(StrToInt(editIG.Text));
     indexOffset := Longword(StrToInt(editIO.Text));

     case dataType of
           plcBOOL:
             begin
                  nBYTE := Byte(StrToInt(editValue.Text) And $01);
                  result := AdsSyncWriteReq(@serverAddr, indexGroup, indexOffset, sizeof(nBYTE), @nBYTE  );
             end;
           plcBYTE:
             begin
                  nBYTE := Byte(StrToInt(editValue.Text));
                  result := AdsSyncWriteReq(@serverAddr, indexGroup, indexOffset, sizeof(nBYTE), @nBYTE  );
             end;
           plcWORD:
             begin
                  nWORD := Word(StrToInt(editValue.Text));
                  result := AdsSyncWriteReq(@serverAddr, indexGroup, indexOffset, sizeof(nWORD), @nWORD  );
             end;
           plcDWORD:
             begin
                  nDWORD := Longword(StrToInt(editValue.Text));
                  result := AdsSyncWriteReq(@serverAddr, indexGroup, indexOffset, sizeof(nDWORD), @nDWORD  );
             end;
           plcSINT:
             begin
                  iSINT := Shortint(StrToInt(editValue.Text));
                  result := AdsSyncWriteReq(@serverAddr, indexGroup, indexOffset, sizeof(iSINT), @iSINT  );
             end;
           plcINT:
             begin
                  iINT := Smallint(StrToInt(editValue.Text));
                  result := AdsSyncWriteReq(@serverAddr, indexGroup, indexOffset, sizeof(iINT), @iINT  );
             end;
           plcDINT:
             begin
                  iDINT := Longint(StrToInt(editValue.Text));
                  result := AdsSyncWriteReq(@serverAddr, indexGroup, indexOffset, sizeof(iDINT), @iDINT  );
             end;
           plcREAL:
             begin
                  fREAL := StrToFloat(editValue.Text);
                  result := AdsSyncWriteReq(@serverAddr, indexGroup, indexOffset, sizeof(fREAL), @fREAL  );
             end;
           plcLREAL:
             begin
                  fLREAL := StrToFloat(editValue.Text);
                  result := AdsSyncWriteReq(@serverAddr, indexGroup, indexOffset, sizeof(fLREAL), @fLREAL  );
             end;
           plcSTRING:
             begin
                sSTRING := AnsiString(editValue.Text);
                if sSTRING = '' then
                  sSTRING := ''#00;
                result := AdsSyncWriteReq(@serverAddr, indexGroup, indexOffset, Length(sSTRING)+1, @sSTRING[1] );
             end;
     end;

     Label5.Caption := Format( 'AdsSyncWriteReq() result: %d [0x%x]', [result, result] );
end;

///////////////////////////////////////////////////////////////////////
procedure TfrmAdsSyncWriteReq.ComboBox1Change(Sender: TObject);
begin
     case  ComboBox1.ItemIndex of
           0:
              editValue.Text := '1';
           1..6:
              editValue.Text := '7';
           7..8:
              editValue.Text := '12.34';
           9:
              editValue.Text := 'my string';
     end;
end;

end.

Language / IDE | Unpack the sample program
Delphi XE2 | Sample01.exe
Delphi 5 or higher (classic) | Sample01.exe


TcAdsDll Delphi Sample01_06

TwinCAT TcAdsDLL: Delphi examples

AdsSyncReadReq

Delphi 5 program

unit frmAdsSyncReadReqUnit;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, TcAdsDef, TcAdsApi, Buttons, ExtCtrls ;

type
  TfrmAdsSyncReadReq = class(TForm)
    editIO: TEdit;
    editIG: TEdit;
    Label2: TLabel;
    Label3: TLabel;
    editValue: TEdit;
    Label1: TLabel;
    Label5: TLabel;
    ComboBox1: TComboBox;
    Label4: TLabel;
    BitBtn1: TBitBtn;
    Button1: TButton;
    Bevel1: TBevel;
    procedure Button1Click(Sender: TObject);
  private
    { Private-Deklarationen }
    serverAddr          : TAmsAddr;
  public
    { Public-Deklarationen }
    procedure InitData( destAddr : TAmsAddr);
  end;

type TPlcDataTypes =
(
    plcBOOL,
    plcBYTE,  //and USINT
    plcWORD,  //and UINT
    plcDWORD, //and UDINT
    plcSINT,
    plcINT,
    plcDINT,
    plcREAL,
    plcLREAL,
    plcSTRING
);

implementation

{$R *.DFM}
//////////////////////////////////////////////////////////////
procedure TfrmAdsSyncReadReq.InitData( destAddr : TAmsAddr);
begin
     serverAddr := destAddr;
     ComboBox1.itemindex := 0;
end;
//////////////////////////////////////////////////////////////
procedure TfrmAdsSyncReadReq.Button1Click( Sender: TObject);
var result              : longword;
    dataType            : TPlcDataTypes;
    indexGroup          : Longword;
    indexOffset         : Longword;

    nBYTE               : Byte; //and USINT
    nWORD               : Word; //and UINT
    nDWORD              : Longword; //and UDINT
    iSINT               : Shortint;
    iINT                : Smallint;
    iDINT               : Longint;
    fREAL               : Single;
    fLREAL              : Double;
    sSTRING             : AnsiString;
begin
     result := 0;
     dataType :=  TPlcDataTypes(ComboBox1.ItemIndex);

     indexGroup := Longword(StrToInt(editIG.Text));
     indexOffset := Longword(StrToInt(editIO.Text));

     case dataType of
           plcBOOL:
             begin
                  result := AdsSyncReadReq(@serverAddr, indexGroup, indexOffset, sizeof(nBYTE), @nBYTE  );
                  editValue.Text := IntToStr(nByte And $01);
             end;
           plcBYTE:
             begin
                  result := AdsSyncReadReq(@serverAddr, indexGroup, indexOffset, sizeof(nBYTE), @nBYTE  );
                  editValue.Text := IntToStr(nByte);
             end;
           plcWORD:
             begin
                  result := AdsSyncReadReq(@serverAddr, indexGroup, indexOffset, sizeof(nWORD), @nWORD  );
                  editValue.Text := IntToStr(nWORD);
             end;
           plcDWORD:
             begin
                  result := AdsSyncReadReq(@serverAddr, indexGroup, indexOffset, sizeof(nDWORD), @nDWORD  );
                  editValue.Text := IntToStr(nDWORD);
             end;
           plcSINT:
             begin
                  result := AdsSyncReadReq(@serverAddr, indexGroup, indexOffset, sizeof(iSINT), @iSINT  );
                  editValue.Text := IntToStr(iSINT);
             end;
           plcINT:
             begin
                  result := AdsSyncReadReq(@serverAddr, indexGroup, indexOffset, sizeof(iINT), @iINT  );
                  editValue.Text := IntToStr(iINT);
             end;
           plcDINT:
             begin
                  result := AdsSyncWriteReq(@serverAddr, indexGroup, indexOffset, sizeof(iDINT), @iDINT  );
                  editValue.Text := IntToStr(iDINT);
             end;
           plcREAL:
             begin
                  result := AdsSyncReadReq(@serverAddr, indexGroup, indexOffset, sizeof(fREAL), @fREAL  );
                  editValue.Text := FloatToStr(fREAL);
             end;
           plcLREAL:
             begin
                  result := AdsSyncReadReq(@serverAddr, indexGroup, indexOffset, sizeof(fLREAL), @fLREAL  );
                  editValue.Text := FloatToStr(fLREAL);
             end;
           plcSTRING:
             begin
                  SetLength(sSTRING, 256);//make sure the delphi string size is >= SIZEOF(plc string)
                  result := AdsSyncReadReq(@serverAddr, indexGroup, indexOffset, Length(sSTRING), @sSTRING[1]  );
                  editValue.Text := String(sSTRING);
             end;
     end;

     Label5.Caption := Format( 'AdsSyncReadReq() result: %d [0x%x]', [result, result] );
end;

end.

Language / IDE | Unpack the sample program
Delphi XE2 | Sample01.exe
Delphi 5 or higher (classic) | Sample01.exe


TcAdsDll Delphi Sample01_07

TwinCAT TcAdsDLL: Delphi examples

AdsSyncReadReqEx

Delphi 5 program

unit frmAdsSyncReadReqExUnit;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, TcAdsDef, TcAdsApi, Buttons, ExtCtrls ;

type
  TfrmAdsSyncReadReqEx = class(TForm)
    editIO: TEdit;
    editIG: TEdit;
    Label2: TLabel;
    Label3: TLabel;
    editValue: TEdit;
    Label1: TLabel;
    Label5: TLabel;
    ComboBox1: TComboBox;
    Label4: TLabel;
    BitBtn1: TBitBtn;
    Button1: TButton;
    Bevel1: TBevel;
    procedure Button1Click(Sender: TObject);
  private
    serverAddr          : TAmsAddr;
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
    procedure InitData( destAddr : TAmsAddr );
  end;

type TPlcDataTypes =
(
    plcBOOL,
    plcBYTE,  //and USINT
    plcWORD,  //and UINT
    plcDWORD, //and UDINT
    plcSINT,
    plcINT,
    plcDINT,
    plcREAL,
    plcLREAL,
    plcSTRING
);

implementation

{$R *.DFM}
////////////////////////////////////////////////////////////////
procedure TfrmAdsSyncReadReqEx.InitData( destAddr : TAmsAddr);
begin
     serverAddr := destAddr;
     ComboBox1.itemindex := 0;
end;
////////////////////////////////////////////////////////////////
procedure TfrmAdsSyncReadReqEx.Button1Click( Sender: TObject);
var result              : longword;
    dataType            : TPlcDataTypes;
    indexGroup          : Longword;
    indexOffset         : Longword;

    nBYTE               : Byte; //and USINT
    nWORD               : Word; //and UINT
    nDWORD              : Longword; //and UDINT
    iSINT               : Shortint;
    iINT                : Smallint;
    iDINT               : Longint;
    fREAL               : Single;
    fLREAL              : Double;
    sSTRING             : AnsiString;
    cbReturn            : Longword;
begin
     result := 0;
     dataType :=  TPlcDataTypes(ComboBox1.ItemIndex);

     indexGroup := Longword(StrToInt(editIG.Text));
     indexOffset := Longword(StrToInt(editIO.Text));

     case dataType of
           plcBOOL:
             begin
                  result := AdsSyncReadReqEx(@serverAddr, indexGroup, indexOffset, sizeof(nBYTE), @nBYTE, @cbReturn );
                  editValue.Text := IntToStr(nByte And $01);
             end;
           plcBYTE:
             begin
                  result := AdsSyncReadReqEx(@serverAddr, indexGroup, indexOffset, sizeof(nBYTE), @nBYTE, @cbReturn  );
                  editValue.Text := IntToStr(nByte);
             end;
           plcWORD:
             begin
                  result := AdsSyncReadReqEx(@serverAddr, indexGroup, indexOffset, sizeof(nWORD), @nWORD, @cbReturn  );
                  editValue.Text := IntToStr(nWORD);
             end;
           plcDWORD:
             begin
                  result := AdsSyncReadReqEx(@serverAddr, indexGroup, indexOffset, sizeof(nDWORD), @nDWORD, @cbReturn  );
                  editValue.Text := IntToStr(nDWORD);
             end;
           plcSINT:
             begin
                  result := AdsSyncReadReqEx(@serverAddr, indexGroup, indexOffset, sizeof(iSINT), @iSINT, @cbReturn  );
                  editValue.Text := IntToStr(iSINT);
             end;
           plcINT:
             begin
                  result := AdsSyncReadReqEx(@serverAddr, indexGroup, indexOffset, sizeof(iINT), @iINT, @cbReturn  );
                  editValue.Text := IntToStr(iINT);
             end;
           plcDINT:
             begin
                  result := AdsSyncReadReqEx(@serverAddr, indexGroup, indexOffset, sizeof(iDINT), @iDINT, @cbReturn  );
                  editValue.Text := IntToStr(iDINT);
             end;
           plcREAL:
             begin
                  result := AdsSyncReadReqEx(@serverAddr, indexGroup, indexOffset, sizeof(fREAL), @fREAL, @cbReturn  );
                  editValue.Text := FloatToStr(fREAL);
             end;
           plcLREAL:
             begin
                  result := AdsSyncReadReqEx(@serverAddr, indexGroup, indexOffset, sizeof(fLREAL), @fLREAL, @cbReturn  );
                  editValue.Text := FloatToStr(fLREAL);
             end;
           plcSTRING:
             begin
                  SetLength( sSTRING, 256 );//make sure the delphi string size is >= SIZEOF(plc string)
                  result := AdsSyncReadReqEx(@serverAddr, indexGroup, indexOffset, Length(sSTRING), @sSTRING[1], @cbReturn  );
                  editValue.Text := String(sSTRING);
             end;
     end;

     Label5.Caption := Format( 'AdsSyncReadReqEx() result: %d [0x%x], Returned bytes: %d', [result, result, cbReturn] );
end;

end.

Language / IDE | Unpack the sample program
Delphi XE2 | Sample01.exe
Delphi 5 or higher (classic) | Sample01.exe


TcAdsDll Delphi Sample01_08

TwinCAT TcAdsDLL: Delphi examples

AdsSyncReadWriteReq

Delphi 5 program

unit frmAdsSyncReadWriteReqUnit;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, TcAdsDef, TcAdsApi, Buttons, ExtCtrls ;

type
  TfrmAdsSyncReadWriteReq = class(TForm)
    editValue: TEdit;
    Label1: TLabel;
    ComboBox1: TComboBox;
    Label4: TLabel;
    BitBtn1: TBitBtn;
    Button1: TButton;
    Bevel1: TBevel;
    editName: TEdit;
    Label2: TLabel;
    Label3: TLabel;
    Memo1: TMemo;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private-Deklarationen }
    serverAddr          : TAmsAddr;
  public
    { Public-Deklarationen }
    procedure InitData( destAddr : TAmsAddr );
  end;

type TPlcDataTypes =
(
    plcBOOL,
    plcBYTE,  //and USINT
    plcWORD,  //and UINT
    plcDWORD, //and UDINT
    plcSINT,
    plcINT,
    plcDINT,
    plcREAL,
    plcLREAL,
    plcSTRING
);

implementation

{$R *.DFM}
//////////////////////////////////////////////////////////////
procedure TfrmAdsSyncReadWriteReq.InitData( destAddr : TAmsAddr);
begin
     serverAddr := destAddr;
     ComboBox1.itemindex := 0;
end;
//////////////////////////////////////////////////////////////
procedure TfrmAdsSyncReadWriteReq.Button1Click( Sender: TObject);
var result              : longword;
    dataType            : TPlcDataTypes;
    hVar                : Longword;

    nBYTE               : Byte; //and USINT
    nWORD               : Word; //and UINT
    nDWORD              : Longword; //and UDINT
    iSINT               : Shortint;
    iINT                : Smallint;
    iDINT               : Longint;
    fREAL               : Single;
    fLREAL              : Double;
    sSTRING             : AnsiString;
    sName               : AnsiString;
begin
     Memo1.Lines.clear;
     dataType :=  TPlcDataTypes(ComboBox1.ItemIndex);

     sName := AnsiString(editName.Text);
     result := AdsSyncReadWriteReq(@serverAddr, ADSIGRP_SYM_HNDBYNAME, $0000, sizeof(hVar), @hVar, Length(sName)+1, @sName[1] );
     Memo1.Lines.add( Format( 'AdsSyncReadWriteReq(ADSIGRP_SYM_HNDBYNAME..) result: %d [0x%x], Handle: %d [0x%x]', [result, result, hVar, hVar]));

     if result = ADSERR_NOERR then
     begin

       case dataType of
           plcBOOL:
             begin
                  result := AdsSyncReadReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(nBYTE), @nBYTE );
                  editValue.Text := IntToStr(nByte And $01);
             end;
           plcBYTE:
             begin
                  result := AdsSyncReadReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(nBYTE), @nBYTE );
                  editValue.Text := IntToStr(nByte);
             end;
           plcWORD:
             begin
                  result := AdsSyncReadReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(nWORD), @nWORD );
                  editValue.Text := IntToStr(nWORD);
             end;
           plcDWORD:
             begin
                  result := AdsSyncReadReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(nDWORD), @nDWORD );
                  editValue.Text := IntToStr(nDWORD);
             end;
           plcSINT:
             begin
                  result := AdsSyncReadReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(iSINT), @iSINT  );
                  editValue.Text := IntToStr(iSINT);
             end;
           plcINT:
             begin
                  result := AdsSyncReadReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(iINT), @iINT  );
                  editValue.Text := IntToStr(iINT);
             end;
           plcDINT:
             begin
                  result := AdsSyncReadReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(iDINT), @iDINT );
                  editValue.Text := IntToStr(iDINT);
             end;
           plcREAL:
             begin
                  result := AdsSyncReadReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(fREAL), @fREAL );
                  editValue.Text := FloatToStr(fREAL);
             end;
           plcLREAL:
             begin
                  result := AdsSyncReadReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(fLREAL), @fLREAL );
                  editValue.Text := FloatToStr(fLREAL);
             end;
           plcSTRING:
             begin
                  SetLength(sSTRING, 256);//make sure the delphi string size is >= SIZEOF(plc string)
                  result := AdsSyncReadReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, Length(sSTRING), @sSTRING[1] );
                  editValue.Text := String(sSTRING);
             end;
       end;

       Memo1.Lines.add( Format( 'AdsSyncReadReq(ADSIGRP_SYM_VALBYHND..) result: %d [0x%x]', [result, result]));

       result := AdsSyncWriteReq( @serverAddr, ADSIGRP_RELEASE_SYMHND, $0000, sizeof( hVar), @hVar );

       Memo1.Lines.add( Format( 'AdsSyncWriteReq(ADSIGRP_RELEASE_SYMHND..) result: %d [0x%x]', [result, result]));

     end;
end;
//////////////////////////////////////////////////////////////
procedure TfrmAdsSyncReadWriteReq.Button2Click(Sender: TObject);
var result              : longword;
    dataType            : TPlcDataTypes;
    hVar                : Longword;

    nBYTE               : Byte; //and USINT
    nWORD               : Word; //and UINT
    nDWORD              : Longword; //and UDINT
    iSINT               : Shortint;
    iINT                : Smallint;
    iDINT               : Longint;
    fREAL               : Single;
    fLREAL              : Double;
    sSTRING             : AnsiString;
    sName               : AnsiString;
begin
     Memo1.Lines.clear;
     dataType :=  TPlcDataTypes(ComboBox1.ItemIndex);

     sName := AnsiString(editName.Text);
     result := AdsSyncReadWriteReq(@serverAddr, ADSIGRP_SYM_HNDBYNAME, $0000, sizeof(hVar), @hVar, Length(sName)+1, @sName[1] );
     Memo1.Lines.add( Format( 'AdsSyncReadWriteReq(ADSIGRP_SYM_HNDBYNAME..) result: %d [0x%x], Handle: %d [0x%x]', [result, result, hVar, hVar]));

     if result = ADSERR_NOERR then
     begin

       case dataType of
           plcBOOL:
             begin
                  nByte := Byte(StrToInt(editValue.Text));
                  result := AdsSyncWriteReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(nBYTE), @nBYTE );
             end;
           plcBYTE:
             begin
                  nByte := Byte(StrToInt(editValue.Text));
                  result := AdsSyncWriteReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(nBYTE), @nBYTE );
             end;
           plcWORD:
             begin
                  nWORD := Word(StrToInt(editValue.Text));
                  result := AdsSyncWriteReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(nWORD), @nWORD );
             end;
           plcDWORD:
             begin
                  nDWORD := Longword(StrToInt(editValue.Text));
                  result := AdsSyncWriteReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(nDWORD), @nDWORD );
             end;
           plcSINT:
             begin
                  iSINT := Shortint(StrToInt(editValue.Text));
                  result := AdsSyncWriteReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(iSINT), @iSINT  );
             end;
           plcINT:
             begin
                  iINT := Smallint(StrToInt(editValue.Text));
                  result := AdsSyncWriteReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(iINT), @iINT  );
             end;
           plcDINT:
             begin
                  iDINT := Longint(StrToInt(editValue.Text));
                  result := AdsSyncWriteReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(iDINT), @iDINT );
             end;
           plcREAL:
             begin
                  fREAL := StrToFloat(editValue.Text);
                  result := AdsSyncWriteReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(fREAL), @fREAL );
             end;
           plcLREAL:
             begin
                  fLREAL := StrToFloat(editValue.Text);
                  result := AdsSyncWriteReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(fLREAL), @fLREAL );
             end;
           plcSTRING:
             begin
                  sSTRING := AnsiString(editValue.Text);
                  if sSTRING = '' then
                    sSTRING := ''#00;
                  result := AdsSyncWriteReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, Length(sSTRING)+1, @sSTRING[1] );
             end;
       end;

       Memo1.Lines.add( Format( 'AdsSyncWriteReq(ADSIGRP_SYM_VALBYHND..) result: %d [0x%x]', [result, result]));

       result := AdsSyncWriteReq( @serverAddr, ADSIGRP_RELEASE_SYMHND, $0000, sizeof( hVar), @hVar );

       Memo1.Lines.add( Format( 'AdsSyncWriteReq(ADSIGRP_RELEASE_SYMHND..) result: %d [0x%x]', [result, result]));

     end;
end;

end.

Language / IDE | Unpack the sample program
Delphi XE2 | Sample01.exe
Delphi 5 or higher (classic) | Sample01.exe


TcAdsDll Delphi Sample01_09

TwinCAT TcAdsDLL: Delphi examples

AdsSyncReadWriteReqEx

Delphi 5 program

unit frmAdsSyncReadWriteReqExUnit;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, TcAdsDef, TcAdsApi, Buttons, ExtCtrls ;

type
  TfrmAdsSyncReadWriteReqEx = class(TForm)
    editValue: TEdit;
    Label1: TLabel;
    ComboBox1: TComboBox;
    Label4: TLabel;
    BitBtn1: TBitBtn;
    Button1: TButton;
    Bevel1: TBevel;
    editName: TEdit;
    Label2: TLabel;
    Label3: TLabel;
    Memo1: TMemo;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private-Deklarationen }
    serverAddr          : TAmsAddr;
  public
    { Public-Deklarationen }
    procedure InitData( destAddr : TAmsAddr );
  end;

type TPlcDataTypes =
(
    plcBOOL,
    plcBYTE,  //and USINT
    plcWORD,  //and UINT
    plcDWORD, //and UDINT
    plcSINT,
    plcINT,
    plcDINT,
    plcREAL,
    plcLREAL,
    plcSTRING
);

implementation

{$R *.DFM}
/////////////////////////////////////////////////////////////////
procedure TfrmAdsSyncReadWriteReqEx.InitData( destAddr : TAmsAddr);
begin
     serverAddr := destAddr;
     ComboBox1.itemindex := 0;
end;
/////////////////////////////////////////////////////////////////
procedure TfrmAdsSyncReadWriteReqEx.Button1Click( Sender: TObject);
var result              : longword;
    dataType            : TPlcDataTypes;
    hVar                : Longword;
    cbReturn            : Longword;

    nBYTE               : Byte; //and USINT
    nWORD               : Word; //and UINT
    nDWORD              : Longword; //and UDINT
    iSINT               : Shortint;
    iINT                : Smallint;
    iDINT               : Longint;
    fREAL               : Single;
    fLREAL              : Double;
    sSTRING             : AnsiString;
    sName               : AnsiString;
begin
     Memo1.Lines.clear;
     dataType :=  TPlcDataTypes(ComboBox1.ItemIndex);

     sName := AnsiString(editName.Text);
     result := AdsSyncReadWriteReqEx(@serverAddr, ADSIGRP_SYM_HNDBYNAME, $0000, sizeof(hVar), @hVar, Length(sName)+1, @sName[1], @cbReturn );
     Memo1.Lines.add( Format( 'AdsSyncReadWriteReqEx(ADSIGRP_SYM_HNDBYNAME..) result: %d [0x%x], Handle: %d [0x%x], Returned bytes: %d', [result, result, hVar, hVar, cbReturn]));

     if result = ADSERR_NOERR then
     begin

       case dataType of
           plcBOOL:
             begin
                  result := AdsSyncReadReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(nBYTE), @nBYTE );
                  editValue.Text := IntToStr(nByte And $01);
             end;
           plcBYTE:
             begin
                  result := AdsSyncReadReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(nBYTE), @nBYTE );
                  editValue.Text := IntToStr(nByte);
             end;
           plcWORD:
             begin
                  result := AdsSyncReadReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(nWORD), @nWORD );
                  editValue.Text := IntToStr(nWORD);
             end;
           plcDWORD:
             begin
                  result := AdsSyncReadReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(nDWORD), @nDWORD );
                  editValue.Text := IntToStr(nDWORD);
             end;
           plcSINT:
             begin
                  result := AdsSyncReadReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(iSINT), @iSINT  );
                  editValue.Text := IntToStr(iSINT);
             end;
           plcINT:
             begin
                  result := AdsSyncReadReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(iINT), @iINT  );
                  editValue.Text := IntToStr(iINT);
             end;
           plcDINT:
             begin
                  result := AdsSyncReadReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(iDINT), @iDINT );
                  editValue.Text := IntToStr(iDINT);
             end;
           plcREAL:
             begin
                  result := AdsSyncReadReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(fREAL), @fREAL );
                  editValue.Text := FloatToStr(fREAL);
             end;
           plcLREAL:
             begin
                  result := AdsSyncReadReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(fLREAL), @fLREAL );
                  editValue.Text := FloatToStr(fLREAL);
             end;
           plcSTRING:
             begin
                  SetLength(sSTRING,256);//make sure the delphi string size is >= SIZEOF(plc string)
                  result := AdsSyncReadReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, Length(sSTRING), @sSTRING[1] );
                  editValue.Text := String(sSTRING);
             end;
       end;

       Memo1.Lines.add( Format( 'AdsSyncReadReq(ADSIGRP_SYM_VALBYHND..) result: %d [0x%x]', [result, result]));

       result := AdsSyncWriteReq( @serverAddr, ADSIGRP_RELEASE_SYMHND, $0000, sizeof( hVar), @hVar );

       Memo1.Lines.add( Format( 'AdsSyncWriteReq(ADSIGRP_RELEASE_SYMHND..) result: %d [0x%x]', [result, result]));

     end;
end;
/////////////////////////////////////////////////////////////////
procedure TfrmAdsSyncReadWriteReqEx.Button2Click(Sender: TObject);
var result              : longword;
    dataType            : TPlcDataTypes;
    hVar                : Longword;
    cbReturn            : Longword;

    nBYTE               : Byte; //and USINT
    nWORD               : Word; //and UINT
    nDWORD              : Longword; //and UDINT
    iSINT               : Shortint;
    iINT                : Smallint;
    iDINT               : Longint;
    fREAL               : Single;
    fLREAL              : Double;
    sSTRING             : AnsiString;
    sName               : AnsiString;
begin
     Memo1.Lines.clear;
     dataType :=  TPlcDataTypes(ComboBox1.ItemIndex);

     sName := AnsiString(editName.Text);
     result := AdsSyncReadWriteReqEx(@serverAddr, ADSIGRP_SYM_HNDBYNAME, $0000, sizeof(hVar), @hVar, Length(sName)+1, @sName[1], @cbReturn );
     Memo1.Lines.add( Format( 'AdsSyncReadWriteReqEx(ADSIGRP_SYM_HNDBYNAME..) result: %d [0x%x], Handle: %d [0x%x], Returned bytes: %d', [result, result, hVar, hVar, cbReturn]));

     if result = ADSERR_NOERR then
     begin

       case dataType of
           plcBOOL:
             begin
                  nByte := Byte(StrToInt(editValue.Text));
                  result := AdsSyncWriteReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(nBYTE), @nBYTE );
             end;
           plcBYTE:
             begin
                  nByte := Byte(StrToInt(editValue.Text));
                  result := AdsSyncWriteReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(nBYTE), @nBYTE );
             end;
           plcWORD:
             begin
                  nWORD := Word(StrToInt(editValue.Text));
                  result := AdsSyncWriteReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(nWORD), @nWORD );
             end;
           plcDWORD:
             begin
                  nDWORD := Longword(StrToInt(editValue.Text));
                  result := AdsSyncWriteReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(nDWORD), @nDWORD );
             end;
           plcSINT:
             begin
                  iSINT := Shortint(StrToInt(editValue.Text));
                  result := AdsSyncWriteReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(iSINT), @iSINT  );
             end;
           plcINT:
             begin
                  iINT := Smallint(StrToInt(editValue.Text));
                  result := AdsSyncWriteReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(iINT), @iINT  );
             end;
           plcDINT:
             begin
                  iDINT := Longint(StrToInt(editValue.Text));
                  result := AdsSyncWriteReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(iDINT), @iDINT );
             end;
           plcREAL:
             begin
                  fREAL := StrToFloat(editValue.Text);
                  result := AdsSyncWriteReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(fREAL), @fREAL );
             end;
           plcLREAL:
             begin
                  fLREAL := StrToFloat(editValue.Text);
                  result := AdsSyncWriteReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(fLREAL), @fLREAL );
             end;
           plcSTRING:
             begin
                  sSTRING := AnsiString(editValue.Text);
                  if sSTRING = '' then
                    sSTRING := ''#00;
                  result := AdsSyncWriteReq(@serverAddr, ADSIGRP_SYM_VALBYHND, hVar, Length(sSTRING)+1, @sSTRING[1] );
             end;
       end;

       Memo1.Lines.add( Format( 'AdsSyncWriteReq(ADSIGRP_SYM_VALBYHND..) result: %d [0x%x]', [result, result]));

       result := AdsSyncWriteReq( @serverAddr, ADSIGRP_RELEASE_SYMHND, $0000, sizeof( hVar), @hVar );

       Memo1.Lines.add( Format( 'AdsSyncWriteReq(ADSIGRP_RELEASE_SYMHND..) result: %d [0x%x]', [result, result]));

     end;
end;

end.

Language / IDE | Unpack the sample program
Delphi XE2 | Sample01.exe
Delphi 5 or higher (classic) | Sample01.exe


TcAdsDll Delphi Sample01_10

TwinCAT TcAdsDLL: Delphi examples

AdsSyncReadDeviceInfoReq

Delphi 5 program

unit frmAdsSyncReadDeviceInfoReqUnit;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, TcAdsDef, TcAdsApi, Buttons, ExtCtrls;

type
  TfrmAdsSyncReadDeviceInfoReq = class(TForm)
    Label1: TLabel;
    BitBtn1: TBitBtn;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    Label5: TLabel;
    Bevel1: TBevel;
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
    procedure InitData( destAddr : TAmsAddr );

  end;

implementation

{$R *.DFM}
procedure TfrmAdsSyncReadDeviceInfoReq.InitData( destAddr : TAmsAddr);
var result : Longint;
    sDevName : AnsiString;
    adsDevVersion : TAdsVersion;
begin
     SetLength(sDevName, ADS_FIXEDNAMESIZE + 1);
     result := AdsSyncReadDeviceInfoReq(@destAddr, @sDevName[1], @adsDevVersion );
     Label1.Caption := Format( 'AdsSyncReadDeviceInfoReq() result: %d [0x%x]', [result,result] );
     Label2.Caption := Format( 'Device name: %s', [String(sDevName)] );
     Label3.Caption := Format( 'Version: %d', [adsDevVersion.Version] );
     Label4.Caption := Format( 'Revision: %d', [adsDevVersion.Revision] );
     Label5.Caption := Format( 'Build: %d', [adsDevVersion.Build] );
end;

end.

Language / IDE | Unpack the sample program
Delphi XE2 | Sample01.exe
Delphi 5 or higher (classic) | Sample01.exe


TcAdsDll Delphi Sample01_11

TwinCAT TcAdsDLL: Delphi examples

AdsSyncWriteControlReq

Delphi 5 program

unit frmAdsSyncWriteControlReqUnit;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, TcAdsDef, TcAdsApi, Buttons, ExtCtrls;

type
  TfrmAdsSyncWriteControlReq = class(TForm)
    Label1: TLabel;
    BitBtn1: TBitBtn;
    ComboAdsState: TComboBox;
    editDevState: TEdit;
    Label2: TLabel;
    Button1: TButton;
    Label3: TLabel;
    Bevel1: TBevel;
    procedure Button1Click(Sender: TObject);
  private
    { Private-Deklarationen }
    serverAddr : TAmsAddr;
  public
    { Public-Deklarationen }
    procedure InitData( destAddr : TAmsAddr );

  end;

implementation

{$R *.DFM}
///////////////////////////////////////////////////////////////////////
procedure TfrmAdsSyncWriteControlReq.InitData( destAddr : TAmsAddr);
begin
     serverAddr := destAddr;
     ComboAdsstate.Items.Add('ADSSTATE_INVALID');
     ComboAdsstate.Items.Add('ADSSTATE_IDLE');
     ComboAdsstate.Items.Add('ADSSTATE_RESET');
     ComboAdsstate.Items.Add('ADSSTATE_INIT');
     ComboAdsstate.Items.Add('ADSSTATE_START');
     ComboAdsstate.Items.Add('ADSSTATE_RUN');
     ComboAdsstate.Items.Add('ADSSTATE_STOP');
     ComboAdsstate.Items.Add('ADSSTATE_SAVECFG');
     ComboAdsstate.Items.Add('ADSSTATE_LOADCFG');
     ComboAdsstate.Items.Add('ADSSTATE_POWERFAILURE');
     ComboAdsstate.Items.Add('ADSSTATE_POWERGOOD');
     ComboAdsstate.Items.Add('ADSSTATE_ERROR');
     ComboAdsstate.Items.Add('ADSSTATE_SHUTDOWN');
     ComboAdsstate.Items.Add('ADSSTATE_SUSPEND');
     ComboAdsstate.Items.Add('ADSSTATE_RESUME');
     ComboAdsstate.Items.Add('ADSSTATE_CONFIG');// system is in config mode
     ComboAdsstate.Items.Add('ADSSTATE_RECONFIG');// system should restart in config mode

     ComboAdsState.ItemIndex := 5;
end;
///////////////////////////////////////////////////////////////////////
procedure TfrmAdsSyncWriteControlReq.Button1Click(Sender: TObject);
var result : Longint;
begin
     result := AdsSyncWriteControlReq( @serverAddr, Word( ComboAdsState.itemIndex ), Word(StrToInt(editDevState.Text)), 0, Nil);
     Label3.Caption := Format('AdsSyncWriteControlReq() result: %d [0x%x]',[result,result] );
end;

end.

Language / IDE | Unpack the sample program
Delphi XE2 | Sample01.exe
Delphi 5 or higher (classic) | Sample01.exe


TcAdsDll Delphi Sample01_12

TwinCAT TcAdsDLL: Delphi examples

AdsSyncReadStateReq

Delphi 5 program

unit frmAdsSyncReadStateReqUnit;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, TcAdsDef, TcAdsApi, Buttons, ExtCtrls;

type
  TfrmAdsSyncReadStateReq = class(TForm)
    Label1: TLabel;
    BitBtn1: TBitBtn;
    ComboAdsState: TComboBox;
    editDevState: TEdit;
    Label2: TLabel;
    Button1: TButton;
    Label3: TLabel;
    Bevel1: TBevel;
    procedure Button1Click(Sender: TObject);
  private
    { Private-Deklarationen }
    serverAddr : TAmsAddr;
  public
    { Public-Deklarationen }
    procedure InitData( destAddr : TAmsAddr );

  end;

implementation

{$R *.DFM}
///////////////////////////////////////////////////////////////////////
procedure TfrmAdsSyncReadStateReq.InitData( destAddr : TAmsAddr);
begin
     serverAddr := destAddr;
     ComboAdsstate.Items.Add('ADSSTATE_INVALID');
     ComboAdsstate.Items.Add('ADSSTATE_IDLE');
     ComboAdsstate.Items.Add('ADSSTATE_RESET');
     ComboAdsstate.Items.Add('ADSSTATE_INIT');
     ComboAdsstate.Items.Add('ADSSTATE_START');
     ComboAdsstate.Items.Add('ADSSTATE_RUN');
     ComboAdsstate.Items.Add('ADSSTATE_STOP');
     ComboAdsstate.Items.Add('ADSSTATE_SAVECFG');
     ComboAdsstate.Items.Add('ADSSTATE_LOADCFG');
     ComboAdsstate.Items.Add('ADSSTATE_POWERFAILURE');
     ComboAdsstate.Items.Add('ADSSTATE_POWERGOOD');
     ComboAdsstate.Items.Add('ADSSTATE_ERROR');
     ComboAdsstate.Items.Add('ADSSTATE_SHUTDOWN');
     ComboAdsstate.Items.Add('ADSSTATE_SUSPEND');
     ComboAdsstate.Items.Add('ADSSTATE_RESUME');
     ComboAdsstate.Items.Add('ADSSTATE_CONFIG');// system is in config mode
     ComboAdsstate.Items.Add('ADSSTATE_RECONFIG');// system should restart in config mode

     Button1Click( self );
end;
///////////////////////////////////////////////////////////////////////
procedure TfrmAdsSyncReadStateReq.Button1Click(Sender: TObject);
var result : Longint;
    adsState, devState : Word;
begin
     adsState := 0;
     devState := 0;

     result := AdsSyncReadStateReq( @serverAddr, @adsState, @devState);
     ComboAdsState.ItemIndex := adsState;
     editDevState.Text :=  IntToStr(devState);
     Label3.Caption := Format('AdsSyncReadStateReq() result: %d [0x%x]',[result,result] );
end;

end.

Language / IDE | Unpack the sample program
Delphi XE2 | Sample01.exe
Delphi 5 or higher (classic) | Sample01.exe


TcAdsDll Delphi Sample01_13

TwinCAT TcAdsDLL: Delphi examples

AdsSyncAddDeviceNotificationReq/AdsSyncDelDeviceNotificationReq

Delphi 5 program

unit frmAdsSyncAddDeviceNotificationReqUnit;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, TcAdsDef, TcAdsApi, Buttons, ExtCtrls, Math;

//define your own message ids
const WM_ADSDEVICENOTIFICATION = WM_APP + 400;

type
  TfrmAdsSyncAddDeviceNotificationReq = class(TForm)
    BitBtn1: TBitBtn;
    Bevel1: TBevel;
    Button1: TButton;
    Label1: TLabel;
    Memo1: TMemo;
    Button2: TButton;
    Label2: TLabel;
    Bevel2: TBevel;
    Button3: TButton;
    Button4: TButton;
    Memo2: TMemo;
    Label3: TLabel;
    Label4: TLabel;
    Edit1: TEdit;
    Label5: TLabel;
    Edit2: TEdit;
    Label6: TLabel;
    Label7: TLabel;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
  protected
    procedure WMAdsDeviceNotification( var Message: TMessage ); message WM_ADSDEVICENOTIFICATION;
  private
    { Private-Deklarationen }
    serverAddr : TAmsAddr;
    hDevNotification : Longword;
    hSymValNotification : Longword;
  public
    { Public-Deklarationen }
    procedure InitData( addr : TAmsAddr);
    destructor Destroy; override;
  end;

{$ALIGN OFF}
type TThreadListItem = record
     netId           : TAmsNetId;
     port            : Word;
       hNotify         : Longword;
        stamp           : TDateTime;
       cbSize          : Longword;
     destObj         : ^TStrings;
     data            : Byte;    //Array[1..ANYSIZE_ARRAY] of Byte;
end;
PThreadListItem = ^TThreadListItem;
{$ALIGN ON}

var
    DevThreadList : TThreadList;
    wndHandle :HWND;

implementation
{$R *.DFM}

//////////////////////////////////////////////////////////////////////////////
procedure TfrmAdsSyncAddDeviceNotificationReq.InitData( addr : TAmsAddr);
begin
     DevThreadList := TThreadList.Create();
     serverAddr := addr;
     wndHandle := Handle;
end;
//////////////////////////////////////////////////////////////////////////////
destructor TfrmAdsSyncAddDeviceNotificationReq.Destroy();
var X : integer;
begin
     //delete notifications on exit
     if hDevNotification <> 0 then
          Button2Click( self );
     if hSymValNotification <> 0 then
          Button4Click( self );

     With DevThreadList.LockList do
     try
        for X := 0 to Count-1 do
            FreeMem( Items[X], sizeof(TThreadListItem) + PThreadListItem(Items[X])^.cbSize - 1 );
     finally
        DevThreadList.UnlockList;
        DevThreadList.Destroy;
     end;

     inherited Destroy;
end;
//////////////////////////////////////////////////////////////////////////////
Procedure Callback(	pAddr:PAmsAddr; pNotification:PAdsNotificationHeader;	hUser:Longword ); stdcall;
var pItem : PThreadListItem;
    FileTime                : _FILETIME;
    SystemTime, LocalTime   : _SYSTEMTIME;
    TimeZoneInformation     : _TIME_ZONE_INFORMATION;
begin
    GetMem( pItem, sizeof(TThreadListItem) + pNotification^.cbSampleSize - 1 );
    pItem^.netId := pAddr^.netId;
    pItem^.port := pAddr^.port;
    pItem^.hNotify := pNotification^.hNotification;
    pItem^.cbSize := pNotification^.cbSampleSize;

    FileTime:=pNotification^.nTimeStamp;
    FileTimeToSystemTime(FileTime, SystemTime);
    GetTimeZoneInformation(TimeZoneInformation);
    SystemTimeToTzSpecificLocalTime(@TimeZoneInformation, SystemTime, LocalTime);
    pItem^.stamp := SystemTimeToDateTime(LocalTime);

    pItem^.destObj := Ptr(hUser);
    //copy the notification data
    Move( pNotification^.data, pItem^.data, pNotification^.cbSampleSize);
    with DevThreadList.LockList do
    try
       Add( pItem );
    finally
       DevThreadList.UnlockList;
       PostMessage( wndHandle, WM_ADSDEVICENOTIFICATION, 0, 0);
    end;
end;
//////////////////////////////////////////////////////////////////////////////
procedure TfrmAdsSyncAddDeviceNotificationReq.WMAdsDeviceNotification( var Message: TMessage );
var pItem                   : PThreadListItem;
    X,i                     : integer;
    dataAsStr               : String;
begin
    with DevThreadList.LockList do
    try
       for X := 0 to Count-1 do
       begin
          pItem := Items[X];
          { convert data to hex string }
          dataAsStr := '';
          for i:= 0 to math.Min(pItem^.cbSize-1, 20) do
              dataAsStr := dataAsStr + IntToHex( PByte(PAnsiChar(@pItem^.data)+i)^, 2 ) + ' ' ;

          pItem^.destObj^.Insert(0,Format( '%s %d.%d.%d.%d.%d.%d[%d], hNot:0x%x, size:%d, hUser:0x%x, data: %s',
                                     [TimeToStr(pItem^.stamp), pItem^.netId.b[0], pItem^.netId.b[1], pItem^.netId.b[2],
                                     pItem^.netId.b[3], pItem^.netId.b[4], pItem^.netId.b[5], pItem^.port,
                                     pItem^.hNotify, pItem^.cbSize, Longword(pItem^.destObj),
                                     dataAsStr ]));
          FreeMem( pItem, sizeof(TThreadListItem) + pItem^.cbSize - 1 ); //free item memory
       end;
       Clear();
    finally
       DevThreadList.UnlockList;
    end;
    inherited;
end;
//////////////////////////////////////////////////////////////////////////////
procedure TfrmAdsSyncAddDeviceNotificationReq.Button1Click(Sender: TObject);
var result : Longint;
    adsNotificationAttrib : TAdsNotificationAttrib;
begin
     adsNotificationAttrib.cbLength := 2;
     adsNotificationAttrib.nTransMode := ADSTRANS_SERVERONCHA;
     adsNotificationAttrib.nMaxDelay := 0;
     adsNotificationAttrib.nCycleTime := 0;

     result := AdsSyncAddDeviceNotificationReq( @serverAddr, ADSIGRP_DEVICE_DATA,
                                                ADSIOFFS_DEVDATA_ADSSTATE,
                                                @adsNotificationAttrib,
                                                @Callback,
                                                Longword(@Memo1.lines), @hDevNotification  );

     Label1.Caption := Format( 'AdsSyncAddDeviceNotificationReq() result: %d [0x%x], handle: 0x%x', [result, result, hDevNotification] );
     Memo1.Lines.Clear;
     Button1.Enabled := not(result = ADSERR_NOERR);
     Button2.Enabled := (result = ADSERR_NOERR);
end;
//////////////////////////////////////////////////////////////////////////////
procedure TfrmAdsSyncAddDeviceNotificationReq.Button2Click(  Sender: TObject);
var result : longint;
begin
     result := AdsSyncDelDeviceNotificationReq( @serverAddr, hDevNotification );
     hDevNotification := 0;
     Label1.Caption := Format( 'AdsSyncDelDeviceNotificationReq() result: %d [0x%x]', [result,result] );
     Button1.Enabled := true;
     Button2.Enabled := false;
end;

//////////////////////////////////////////////////////////////////////////////
procedure TfrmAdsSyncAddDeviceNotificationReq.Button3Click(  Sender: TObject);
var result : Longword;
    adsNotificationAttrib : TAdsNotificationAttrib;
begin
     adsNotificationAttrib.cbLength := 2; //INT
     adsNotificationAttrib.nTransMode := ADSTRANS_SERVERONCHA;
     adsNotificationAttrib.nMaxDelay := 10000000;//1 second
     adsNotificationAttrib.nCycleTime := 0;

     result := AdsSyncAddDeviceNotificationReq( @serverAddr, Longword(StrToInt(Edit1.Text)),
                                                Longword(StrToInt(Edit2.Text)),
                                                @adsNotificationAttrib,
                                                @Callback,
                                                Longword(@Memo2.lines), @hSymValNotification  );

     Label3.Caption := Format( 'AdsSyncAddDeviceNotificationReq() result: %d [0x%x], handle: 0x%x', [result, result, hSymValNotification] );
     Memo2.Lines.Clear;
     Button3.Enabled := not(result = ADSERR_NOERR);
     Button4.Enabled := (result = ADSERR_NOERR);
end;
//////////////////////////////////////////////////////////////////////////////
procedure TfrmAdsSyncAddDeviceNotificationReq.Button4Click(  Sender: TObject);
var result : Longword;
begin
     result := AdsSyncDelDeviceNotificationReq( @serverAddr, hSymValNotification );
     hSymValNotification := 0;
     Label3.Caption := Format( 'AdsSyncDelDeviceNotificationReq() result: %d [0x%x]', [result,result] );
     Button3.Enabled := true;
     Button4.Enabled := false;
end;

end.

Language / IDE | Unpack the sample program
Delphi XE2 | Sample01.exe
Delphi 5 or higher (classic) | Sample01.exe


TcAdsDll Delphi Sample01_14

TwinCAT TcAdsDLL: Delphi examples

AdsSyncSetTimeout

Delphi 5 program

unit frmAdsSyncSetTimeoutUnit;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, TcAdsDef, TcAdsApi, Buttons, ExtCtrls;

type
  TfrmAdsSyncSetTimeout = class(TForm)
    Label1: TLabel;
    BitBtn1: TBitBtn;
    Bevel1: TBevel;
    editTimeout: TEdit;
    Label2: TLabel;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private-Deklarationen }
    serverAddr : TAmsAddr;
  public
    { Public-Deklarationen }
    procedure InitData( destAddr : TAmsAddr );
  end;

implementation

{$R *.DFM}
////////////////////////////////////////////////////////////////
procedure TfrmAdsSyncSetTimeout.InitData( destAddr : TAmsAddr );
begin
     serverAddr := destAddr;
end;
////////////////////////////////////////////////////////////////
procedure TfrmAdsSyncSetTimeout.Button1Click(Sender: TObject);
var result : longint;
begin
     result := AdsSyncSetTimeout(StrToInt(editTimeout.Text));
     Label1.Caption := Format( 'AdsSyncSetTimeout() result: %d [0x%x]', [result,result] );
end;

end.

Language / IDE | Unpack the sample program
Delphi XE2 | Sample01.exe
Delphi 5 or higher (classic) | Sample01.exe


TcAdsDll Delphi Sample01_15

TwinCAT TcAdsDLL: Delphi examples

AdsAms[Un]RegisterRouterNotification

Delphi 5 program

unit frmAdsAmsRegisterRouterNotificationUnit;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, TcAdsDef, TcAdsApi, Buttons, ExtCtrls;

//define your own message id
const WM_ADSROUTERNOTIFICATION = WM_APP + 405;

type
  TfrmAdsAmsRegisterRouterNotification = class(TForm)
    BitBtn1: TBitBtn;
    Bevel1: TBevel;
    Button1: TButton;
    Label1: TLabel;
    Memo1: TMemo;
    Button2: TButton;
    Label2: TLabel;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  protected
    procedure WMAdsRouterNotification( var Message: TMessage ); message WM_ADSROUTERNOTIFICATION;
  private
    { Private-Deklarationen }
    serverAddr : TAmsAddr;
  public
    { Public-Deklarationen }
    procedure InitData( destAddr : TAmsAddr);
  end;

var wndHandle : HWND;

implementation
{$R *.DFM}
//////////////////////////////////////////////////////////////////////////////
Procedure RouterCallback( nEvent:Longint ); stdcall;
begin
     PostMessage(wndHandle, WM_ADSROUTERNOTIFICATION, nEvent, 0);
end;
//////////////////////////////////////////////////////////////////////////////
procedure TfrmAdsAmsRegisterRouterNotification.WMAdsRouterNotification( var Message: TMessage );
var sState : String;
begin
     case Message.WParam of
          AMSEVENT_ROUTERSTOP: sState := 'ROUTER STOP';
          AMSEVENT_ROUTERSTART: sState:= 'ROUTER START';
          AMSEVENT_ROUTERREMOVED: sState:= 'ROUTER REMOVED';
     else
         sState := Format( 'Other: %d', [Message.WParam]);
     end;

     Memo1.lines.add(Format('%s, nEvent: %s',[TimeToStr(time), sState]));
     inherited;
end;
//////////////////////////////////////////////////////////////////////////////
procedure TfrmAdsAmsRegisterRouterNotification.InitData( destAddr : TAmsAddr );
begin
     serverAddr := destAddr;
     wndHandle := Handle;//Save the window handle
end;
//////////////////////////////////////////////////////////////////////////////
procedure TfrmAdsAmsRegisterRouterNotification.Button1Click(Sender: TObject);
var result : longint;
begin
     result := AdsAmsRegisterRouterNotification( @RouterCallback );
     Label1.Caption := Format( 'AdsAmsRegisterRouterNotification() result: %d [0x%x]', [result,result] );
     Button1.Enabled := not(result = ADSERR_NOERR);
     Button2.Enabled := (result = ADSERR_NOERR);
end;
//////////////////////////////////////////////////////////////////////////////
procedure TfrmAdsAmsRegisterRouterNotification.Button2Click(  Sender: TObject);
var result : longint;
begin
     Memo1.Lines.Clear();
     result := AdsAmsUnRegisterRouterNotification();
     Label1.Caption := Format( 'AdsAmsUnRegisterRouterNotification() result: %d [0x%x]', [result,result] );
     Button1.Enabled := true;
     Button2.Enabled := false;
end;

end.

Language / IDE | Unpack the sample program
Delphi XE2 | Sample01.exe
Delphi 5 or higher (classic) | Sample01.exe


TcAdsDll Delphi Sample02

TwinCAT TcAdsDLL: Delphi examples

Event driven reading (by symbol name)

Requirements:

  • Delphi 5.0 or higher;
  • TcAdsDLL.DLL;
  • Note: The archive delphi_adsdll_api_units.zip contains the the declaration files TcAdsAPI.pas and TcAdsDEF.pas
  • TcAdsDEF.pas and TcAdsAPI.pas if you wish to translate the source text yourself;

Delphi 5 program

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, TcAdsDef, TcAdsAPI,
  StdCtrls, Math, ComCtrls;

const WM_ADSDEVICENOTIFICATION = WM_APP + 400;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Edit1: TEdit;
    Button2: TButton;
    Memo1: TMemo;
    Label1: TLabel;
    Label2: TLabel;
    editDelayTime: TEdit;
    Label3: TLabel;
    editCycleTime: TEdit;
    StatusBar1: TStatusBar;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure WMAdsDeviceNotification( var Message: TMessage ); message WM_ADSDEVICENOTIFICATION;
    procedure FormDestroy(Sender: TObject);

  private
    server : TAmsAddr;
    hVar : Longword;
    hNotification : Longword;
    nCnt          : Longword;
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

{$ALIGN OFF}
type
    TNotifyData = record
                netid                : TAmsNetId;
                port                 : Word;
                hNotification        : Longword;
                dateTime             : TDateTime;
                cbSampleSize         : Longword;
                hUser                : Longword;
                data                 : Byte;
    end;
PNotifyData = ^TNotifyData;
{$ALIGN ON}

var
  Form1: TForm1;
  DevThreadList : TThreadList;

implementation

{$R *.DFM}

Implementation of notification callback function

////////////////////////////////////////////////////////////////////////////////////////////////
Procedure NotificationCallback(	pAddr:PAmsAddr; pNotification:PAdsNotificationHeader;	hUser:Longword ); stdcall;
var pItem                   : PNotifyData;
    FileTime                : _FILETIME;
    SystemTime, LocalTime   : _SYSTEMTIME;
    TimeZoneInformation     : _TIME_ZONE_INFORMATION;
begin
     System.GetMem( pItem, sizeof(TNotifyData) - 1 + pNotification^.cbSampleSize );
     pItem^.netId := pAddr^.netId;
     pItem^.port := pAddr^.port;
     pItem^.hNotification := pNotification^.hNotification;
     pItem^.cbSampleSize := pNotification^.cbSampleSize;
     System.Move( pNotification^.data, pItem^.data, pNotification^.cbSampleSize);

     FileTime:=pNotification^.nTimeStamp;
     GetTimeZoneInformation(TimeZoneInformation);
     FileTimeToSystemTime(FileTime, SystemTime);
     SystemTimeToTzSpecificLocalTime(@TimeZoneInformation, SystemTime, LocalTime);
     pItem^.dateTime:=SystemTimeToDateTime(LocalTime);

     with DevThreadList.LockList do
     try
          Add( pItem );
     finally
          DevThreadList.UnlockList;
          PostMessage( HWND(hUser), WM_ADSDEVICENOTIFICATION, 0, 0);
     end;
end;

Synchronization of main-thread and callback-thread using windows messages

////////////////////////////////////////////////////////////////////////////////////////////////
procedure TForm1.WMAdsDeviceNotification( var Message: TMessage );
var pItem                   : PNotifyData;
    X                       : integer;
    sHex                    : String;
    i                       : Longword;
begin
    with DevThreadList.LockList do
    try
       Memo1.Lines.BeginUpdate();
       for X := 0 to Count-1 do
       begin
           nCnt := nCnt + 1;
           pItem := Items[X];
           {convert data to hex string}
           sHex := '';
           if pItem^.cbSampleSize > 0 then
           begin
                for i:= 0 to math.Min(pItem^.cbSampleSize-1, 20) do
                    sHex := sHex + IntToHex( PBYTE(PAnsiChar(@pItem^.data) + i)^, 2 ) + ' ' ;
           end;
           {}
           Memo1.Lines.Insert(0,Format( 'idx:%-4d stamp:%s, %d.%d.%d.%d.%d.%d[%d], hNot:0x%x, size:%d, data:%s',
                           [nCnt,TimeToStr(pItem^.dateTime), pItem^.netId.b[0], pItem^.netId.b[1], pItem^.netId.b[2],
                           pItem^.netId.b[3], pItem^.netId.b[4], pItem^.netId.b[5], pItem^.port,
                           pItem^.hNotification, pItem^.cbSampleSize, sHex]));
           FreeMem( pItem, sizeof(TNotifyData) -1 + pItem^.cbSampleSize );
       end;
       Clear();
   finally
      if memo1.Lines.Count > 1000 then
        memo1.Lines.Clear();

      Memo1.Lines.EndUpdate();
      DevThreadList.UnlockList;
   end;
   inherited;
end;

Establish connection to the PLC

////////////////////////////////////////////////////////////////////////////////////////////////
procedure TForm1.FormCreate(Sender: TObject);
begin
     AdsPortOpen();
     AdsGetLocalAddress(@server);
     server.port := AMSPORT_R0_PLC_RTS1;
     DevThreadList := TThreadList.Create();
     StatusBar1.SimpleText := Format('Server netId: %d.%d.%d.%d.%d.%d port:%d', [server.netId.b[0], server.netId.b[1],server.netId.b[2],
                           server.netId.b[3], server.netId.b[4],server.netId.b[5], server.port]);
end;

Release resources, close connection

////////////////////////////////////////////////////////////////////////////////////////////////
procedure TForm1.FormDestroy(Sender: TObject);
var X : integer;
begin
    if hNotification <> 0 then
        Button2Click(Sender);

     with DevThreadList.LockList do
     try
          for X := 0 to Count -1 do
              FreeMem( Items[X], sizeof(TNotifyData) + PNotifyData(Items[X])^.cbSampleSize - 1 );
          Clear;
     finally
          DevThreadList.UnlockList;
          DevThreadList.Destroy;
     end;
     AdsPortClose();
end;

Create notification

////////////////////////////////////////////////////////////////////////////////////////////////
procedure TForm1.Button1Click(Sender: TObject);
var result : Longint;
    adsNotificationAttrib : TAdsNotificationAttrib;
    szVarName : AnsiString;
begin
     Memo1.Lines.Clear;
     nCnt := 0;

     { get symbol handle by symbol name }
     szVarName := AnsiString(Edit1.Text);
     result := AdsSyncReadWriteReq( @server, ADSIGRP_SYM_HNDBYNAME, 0,
                                    sizeof(hVar), @hVar, Length(szVarName)+1, @szVarName[1] );
     Memo1.Lines.Insert(0, Format('AdsSyncReadWriteReq(..ADSIGRP_SYM_HNDBYNAME..) result: %d [0x%x], symbol handle: 0x%x', [result, result, hVar]));

     { add notification (connect to variable)}
     adsNotificationAttrib.cbLength := 20;{Should be right}
     adsNotificationAttrib.nTransMode := ADSTRANS_SERVERONCHA;
     adsNotificationAttrib.nMaxDelay := StrToInt(editDelayTime.Text) * 10000; //ms
     adsNotificationAttrib.nCycleTime := StrToInt(editCycleTime.Text) * 10000;//ms

     result := AdsSyncAddDeviceNotificationReq( @server, ADSIGRP_SYM_VALBYHND,
                                                hVar,
                                                @adsNotificationAttrib,
                                                @NotificationCallback,
                                                Handle, @hNotification );

     Memo1.Lines.Insert(0, Format( 'AdsSyncAddDeviceNotificationReq(..ADSIGRP_SYM_VALBYHND..) result: %d [0x%x], notification handle: 0x%x', [result, result, hNotification] ));
     Button1.Enabled := not( result = ADSERR_NOERR);
     Button2.Enabled := ( result = ADSERR_NOERR);
end;

Release notification

////////////////////////////////////////////////////////////////////////////////////////////////
procedure TForm1.Button2Click(Sender: TObject);
var result : Longint;
begin
     { delete notification }
     result := AdsSyncDelDeviceNotificationReq( @server, hNotification );
     hNotification := 0;
     Memo1.Lines.Insert(0, Format( 'AdsSyncDelDeviceNotificationReq() result: %d [0x%x]', [result,result] ));

     {release symbol handle}
     result := AdsSyncWriteReq( @server, ADSIGRP_RELEASE_SYMHND, 0,sizeof(hVar), @hVar);
     Memo1.Lines.Insert(0, Format('AdsSyncReadWriteReq(..ADSIGRP_RELEASE_SYMHND..) result: %d [0x%x]', [result, result]));
     Button1.Enabled := true;
     Button2.Enabled := false;
end;

initialization

end.

Language / IDE | Unpack the sample program
Delphi XE2 | Sample02.exe
Delphi 5 or higher (classic) | Sample02.exe


TcAdsDll Delphi Sample03: Synchronous access to PLC variables

TwinCAT TcAdsDLL: Delphi examples

Synchronous access to PLC variables

Requirements:

  • Delphi 5.0 or higher;
  • TcAdsDLL.DLL;
  • TcAdsDEF.pas and TcAdsAPI.pas, archived in the file delphi_adsdll_api_units.zip if you wish to translate the source text yourself;

The task

Synchronous read and write access to a PLC variable is required from the Windows application. The variable is of type integer (16 bits), and is located at offset 520 in the flags area of the PLC's first run-time system.

The PLC program

The PLC variable is initialised with the value 7 when the PLC starts.

PROGRAM MAIN
VAR
VARINT16 AT%MB520 :INT:=7;
END_VAR

Delphi 5 program

The declarations of the TcAdsDLL functions in use are located in the Pascal units TcAdsDEF.pas and TcAdsAPI.pas. This are linked into the project by means of a uses clause.

unit DelphiAdsDLLSample1Unit;

interface
uses
TcAdsDEF, TcAdsAPI, Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls, ComCtrls;

The example application

The DLL function AdsPortOpen is called in the FormCreate event function. This function returns new port number when successful, otherwise it returns a null. If any errors do occur, they are reported to the user through a message box. The port must be closed again when the application finishes. The DLL function AdsPortClose is called in the FormDestroy event function.

If the Ads port is opened successfully, a further DLL function is called: AdsGetLocalAddress. This returns the AMS address of the local TwinCAT PC. The AMS address is needed for write/read access to the PLC variables.

var
Form1 : TForm1;
ClientPort : longint;
LocalAddr : TAmsAddr;
IndexGroup : Integer;
IndexOffset : Integer;
varHandle : Integer;
Result : longint;

varValue : Smallint;
const varName: AnsiString = 'MAIN.VARINT16'; //Symbol name

implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
ClientPort:= AdsPortOpen();
if ClientPort > 0 then {OK}
begin
Result:=AdsGetLocalAddress( @LocalAddr );
if Result = 0 Then {OK}
begin
AdsState.Panels.Items[5].Text:= Format('%d.%d.%d.%d.%d.%d',[
LocalAddr.netId.b[0], LocalAddr.netId.b[1], LocalAddr.netId.b[2],
LocalAddr.netId.b[3], LocalAddr.netId.b[4], LocalAddr.netId.b[5]]);
AdsState.Panels.Items[1].Text:=IntToStr( ClientPort );
LocalAddr.port := 801; //Set the Ads port number for read/write/readwrite
IndexGroup := $00004020; {memory range}
IndexOffset := 520; {byte offset of the PLC variable}
end
else {error}
begin
AdsState.Panels.Items[3].Text:=IntToStr( Result );
MessageBox(NULL,'AdsGetLocalAddress()', 'Error!', MB_OK)
end;
end
else {error}
MessageBox( NULL,'AdsPortOpen()', 'Error!', MB_OK );
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
AdsPortClose();
end;

When the AdsSyncRead button is clicked, the value of the PLC variable is read synchronously via its identifying address, an displayed on the form. This involves the DLL function AdsSyncReadReq being called in the OnSyncReadButtonClick event function. The IndexGroup and IndexOffset parameters are defined as global variables. There values are, correspondingly, set when the application starts.

procedure TForm1.OnSyncReadButtonClick(Sender: TObject);
begin
Result := AdsSyncReadReq( @LocalAddr, IndexGroup, IndexOffset, sizeof(varValue), @varValue );
ReadLabel.Caption := Format( 'Result: %d, Value: %d', [Result, varValue]);
end;

Write access to the variable is implemented in a similar way. In the event function OnSyncWriteButtonClick the DLL function AdsSyncWriteReq is called, and the value of the PLC variable set to 200.

procedure TForm1.OnSyncWriteButtonClick(Sender: TObject);
begin
varValue := 200;
Result := AdsSyncWriteReq( @LocalAddr, IndexGroup, IndexOffset, sizeof(varValue), @varValue );
WriteLabel.Caption := Format( 'Result: %d', [Result]);
end;

Simultaneous read and write access to PLC variables can be implemented with the aid of the DLL function AdsSyncReadWriteReq. The value of the variable is read in the example by means of a handle. The variable's handle is first requested by the AdsSyncReadWriteReq function. With this handle, the value of the variable can be read with the function AdsSyncReadReq. The handle must always be released by means of the AdsSyncWriteReq function.

procedure TForm1.OnGetValueByHandleClick(Sender: TObject);
begin
{get handle}
Result := AdsSyncReadWriteReq( @LocalAddr, ADSIGRP_SYM_HNDBYNAME, 0, sizeof(varHandle), @varHandle, Length(varName) + 1, @varName[1] );
GetHandleLabel.Caption := Format('Get handle result: %d, Handle: 0x%x', [Result, varHandle] );
{get value}
Result := AdsSyncReadReq( @LocalAddr, ADSIGRP_SYM_VALBYHND, varHandle, sizeof(varValue), @varValue );
GetValueLabel.Caption := Format( 'Get value result: %d, Value: %d', [Result, varValue] );
{release handle}
Result := AdsSyncWriteReq( @LocalAddr, ADSIGRP_RELEASE_SYMHND, 0, sizeof(varHandle), @varHandle );
ReleaseHandleLabel.Caption := Format( 'Release handle result: %d', [Result] );
end;

If the values of several PLC variables are to be read frequently, the necessary handles can be requested when the application starts in the FormCreate event function. They can then only be released in the FormDestroy event function when the application is closed.

Language / IDE | Unpack the sample program
Delphi XE2 | Sample03.exe
Delphi 5 or higher (classic) | Sample03.exe


TcAdsDll Delphi Sample04: Detect status changes in TwinCAT router and the PLC

TwinCAT TcAdsDLL: Delphi examples

Detect status changes in TwinCAT router and the PLC

Requirements:

  • Delphi 5.0 or higher;
  • TcAdsDLL.DLL;
  • TcAdsDEF.pas and TcAdsAPI.pas, archived in the file delphi_adsdll_api_units.zip if you wish to translate the source text yourself;

Description

When an application is actually running it is often important to interrogate the status of TwinCAT and/or of its components (devices) ( e.g., whether the PLC is in the RUN state). So that the required status information is not always checked by polling there is the possibility of registering a message (notification) at the TwinCAT components. These can then register with the aid of callback functions a status change to the client applications that are being addressed. In the following example program the status of the PLC (run time system 1) and that of the TwinCAT router on the local computer is monitored. The application can monitor only the status of the TwinCAT router on the local computer. In other words, the status of a router on a remote PC cannot be monitored in this manner. The status of the PLC can however be monitored both on the local computer and on a remote PC using the functions presented.

With the start of the application a connection is made to the TwinCAT router on the local PC. A notification is registered by pressing the appropriate keys, either to the TwinCAT router, or to the first run-time system of the PLC. The arriving messages (callbacks) are added to two lists. Using further keys the notifications can be cancelled on the respective TwinCAT devices. With termination of the application the connection to the TwinCAT router is closed. By starting/stopping of the TwinCAT system via the task bar the router notifications are, for example, transmitted, and by starting/stopping of the PLC the device notifications are transmitted. The registered device notifications are administered by the TwinCAT components themselves. For this reason the device notifications must again be registered when the TwinCAT system is stopped, since the component (here the run time system of the PLC) is removed from the memory when the TwinCAT system is stopped.

Delphi 5 program

The DLL function AdsPortOpen is called in the FormCreate event function. This function returns a port number when successful, otherwise it returns a zero. If any errors do occur, they are reported to the user through a message box. If the Ads port is opened successfully, a further DLL function is called: AdsGetLocalAddress. This returns the AMS address of the local TwinCAT PC (on which our application is running). In order to register the notifications for the first run time system of the PLC another further AMS Address is generated. This possesses the same NetId as our application, because we wish to address the PLC on the same TwinCAT PC. By assignment of the port number 801 the first RTS is selected. The port must be closed again when the application finishes. The DLL function AdsPortClose is called in the FormDestroy event function.

unit AdsDLLEventSampleForm;
interface
uses
  TcAdsDEF, TcAdsAPI, Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, Math,
  StdCtrls, ComCtrls;
const WM_ADSROUTERNOTIFICATION = WM_APP + 400;
const WM_ADSDEVICENOTIFICATION = WM_APP + 401;
type
  TForm1 = class(TForm)
    StatusBar1: TStatusBar;
    GroupBox1: TGroupBox;
    RegRouterNotify: TButton;
    UnregRouterNotify: TButton;
    ListBox1: TListBox;
    GroupBox2: TGroupBox;
    AddDevNotify: TButton;
    DelDevNotify: TButton;
    ListBox2: TListBox;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure RegRouterNotifyClick(Sender: TObject);
    procedure UnregRouterNotifyClick(Sender: TObject);
    procedure AddDevNotifyClick(Sender: TObject);
    procedure DelDevNotifyClick(Sender: TObject);
    procedure WMAdsRouterNotification( var Message: TMessage ); message WM_ADSROUTERNOTIFICATION;
    procedure WMAdsDeviceNotification( var Message: TMessage ); message WM_ADSDEVICENOTIFICATION;
  private
    { Private declarations }
    LocalAddr     :TAmsAddr;
    ServerAddr    :TAmsAddr;
    hNotification :DWORD;
  public
    { Public declarations }
  end;
{$ALIGN OFF}
type TThreadListItem = record
     netId           : TAmsNetId;
     port            : Word;
       hNotify         : Longword;
        stamp           : _FILETIME;  {int64}
       cbSize          : Longword;
     hUser           : Longword;
     data            : Byte;    //Array[1..ANYSIZE_ARRAY] of Byte;
     end;
PThreadListItem = ^TThreadListItem;
{$ALIGN ON}
var
  Form1: TForm1;
  wndHandle : HWND;
  DevThreadList : TThreadList;
implementation
{$R *.DFM}
//////////////////////////////////////////////////////////////////////////////
procedure TForm1.FormCreate(Sender: TObject);
var AdsResult:Longint;
    ClientPort:Word;
begin
  GroupBox1.Caption := 'Router notifications:';
  GroupBox2.Caption := 'Device notifications:';
  StatusBar1.SimplePanel := true;
  wndHandle := Handle;
  DevThreadList := TThreadList.Create();
  ClientPort:= AdsPortOpen();
  if ClientPort = 0 then {error!}
    ShowMessage( 'AdsPortOpen() error!' )
  else  {OK}
  begin
    AdsResult:=AdsGetLocalAddress( @LocalAddr );
    if AdsResult = 0 then {OK}
    begin
      ServerAddr.netId:=LocalAddr.netId;{connect to the PLC on the local PC}
      ServerAddr.port:=801; {first RTS}
      StatusBar1.SimpleText:=Format('Client NetId:%d.%d.%d.%d.%d.%d  Client Port:%d,  Server Port:%d',[
              LocalAddr.netId.b[0], LocalAddr.netId.b[1], LocalAddr.netId.b[2],
              LocalAddr.netId.b[3], LocalAddr.netId.b[4], LocalAddr.netId.b[5],
              ClientPort, ServerAddr.port]);
    end
    else
      ShowMessageFmt('AdsGetLocalAddress() error:%d', [AdsResult]);
  end;
end;
//////////////////////////////////////////////////////////////////////////////
procedure TForm1.FormDestroy(Sender: TObject);
var AdsResult:longint;
    X : integer;
begin
  UnregRouterNotifyClick( Sender );
  DelDevNotifyClick( Sender );
  With DevThreadList.LockList do
  try
     for X := 0 to Count-1 do
         FreeMem( Items[X], sizeof(TThreadListItem) + PThreadListItem(Items[X])^.cbSize - 1 );
     Clear();
  finally
     DevThreadList.UnlockList;
     DevThreadList.Destroy;
  end;
  AdsResult := AdsPortClose();
  if AdsResult > 0 then
    ShowMessageFmt('AdsPortClose() error:%d', [AdsResult]);
end;

Registration/cancelling of the router notifications

A notification by the router is registered by the call of the function AdsAmsRegisterRouterNotification(). The router notification can only be registered by the router on the local TwinCAT PC. As a single function parameter a function pointer is transferred to our callback function (in our case it is actually a procedure). By means of the function call AdsAmsUnRegisterRouterNotification() the notification is cancelled from the TwinCAT router.

procedure TForm1.RegRouterNotifyClick(Sender: TObject);
var AdsResult:longint;
begin
  AdsResult:= AdsAmsRegisterRouterNotification(@AdsRouterCallback);
  if AdsResult > 0 then
    ListBox1.Items.Insert(0, Format('AdsAmsRegisterRouterNotification() error:%d', [AdsResult]));
end;
//////////////////////////////////////////////////////////////////////////////
procedure TForm1.UnregRouterNotifyClick(Sender: TObject);
var AdsResult:longint;
begin
  AdsResult:=AdsAmsUnRegisterRouterNotification();
  if AdsResult > 0 then
    ListBox1.Items.Insert(0, Format('AdsAmsUnRegisterRouterNotification() error:%d', [AdsResult]));
end;

Registration/cancelling of the device notifications

The device notifications are not administered in a central location but by the TwinCAT component (device) itself. The device notifications can also be registered from a remote TwinCAT PC. Here the target device is selected via the so-called AdsAms address. The notification is registered by the call of the DLL function AdsSyncAddDeviceNotificationReq(). The attributes of the message must be determined beforehand. These will transfer to the DLL function as parameters alongside the function pointer for the callback routine. If successful the function supplies no error and a notification handle. This handle is then required for cancelling of the notification with the DLL function AdsSyncDelDeviceNotificationReq(). The application can register several notifications at the same time. Each notification can be identified via a notification handle or a user handle. These parameters are always sent back in the callback function to the application.

procedure TForm1.AddDevNotifyClick(Sender: TObject);
var AdsResult:Longint;
    huser :Longword;
    adsNotificationAttrib :TadsNotificationAttrib;
begin
  adsNotificationAttrib.cbLength       := sizeof(DWORD);
  adsNotificationAttrib.nTransMode     := ADSTRANS_SERVERONCHA;
  adsNotificationAttrib.nMaxDelay      := 0; // jede Aenderung sofort melden
  adsNotificationAttrib.nCycleTime     := 0; //
  hUser := 7;
  AdsResult:=AdsSyncAddDeviceNotificationReq( @ServerAddr,
                    ADSIGRP_DEVICE_DATA,
                    ADSIOFFS_DEVDATA_ADSSTATE,
                    @adsNotificationAttrib,
                    @AdsDeviceCallback, hUser, @hNotification  );
  if AdsResult > 0 then
    ListBox2.Items.Insert(0, Format('AdsSyncAddDeviceNotificationReq() error:%d', [AdsResult]));
end;
//////////////////////////////////////////////////////////////////////////////
procedure TForm1.DelDevNotifyClick(Sender: TObject);
var AdsResult:Longint;
begin
  AdsResult := AdsSyncDelDeviceNotificationReq( @ServerAddr, hNotification );
  if AdsResult > 0 then
    ListBox2.Items.Insert(0, Format('AdsSyncDelDeviceNotificationReq() error:%d', [AdsResult]));
end;

The callback functions

The callback functions for the router callback and the device notification callback have been defined as procedures. They could equally well be defined as functions. Since however the C++ functions do not supply any return parameters and in Pascal a function must always supply a parameter, procedures were selected for the callback functions. The function calls of the DLL should be decoupled with the aid of the API functions (e.g. threads). In other words, they cannot again call a further DLL function in the callback function. This could then trigger a further call-back and so on.

Procedure AdsDeviceCallback(	pAddr:PAmsAddr; pNotification:PAdsNotificationHeader;	hUser:Longword ); stdcall;
var pItem : PThreadListItem;
begin
    pItem := Nil;
    try
       GetMem( pItem, sizeof(TThreadListItem) + pNotification^.cbSampleSize - 1 );
       pItem^.netId := pAddr^.netId;
       pItem^.port := pAddr^.port;
       pItem^.hNotify := pNotification^.hNotification;
       pItem^.stamp := pNotification^.nTimeStamp;
       pItem^.cbSize := pNotification^.cbSampleSize;
       pItem^.hUser := hUser;
       //copy the notification data
       Move( pNotification^.data, pItem^.data, pNotification^.cbSampleSize);
    finally
        with DevThreadList.LockList do
        try
           Add( pItem );
        finally
           DevThreadList.UnlockList;
           PostMessage(wndHandle, WM_ADSDEVICENOTIFICATION, 0, 0);
        end;
    end;
end;
//////////////////////////////////////////////////////////////////////////////
procedure AdsRouterCallback( nEvent:Longint ); stdcall;
begin
  PostMessage(wndHandle, WM_ADSROUTERNOTIFICATION, nEvent, 0);
end;

procedure TForm1.WMAdsRouterNotification( var Message: TMessage );
var tmpString:String;
begin
  case Message.WParam of
    AMSEVENT_ROUTERSTOP:
      tmpString:='Router STOP!';
    AMSEVENT_ROUTERSTART:
      tmpString:='Router START!';
    AMSEVENT_ROUTERREMOVED:
      tmpString:='Router REMOVED!';
  else
      tmpString:=Format('Unknown state:%d', [Message.WParam]);
  end;
  ListBox1.Items.Insert(0, Format('%s %s', [TimeToStr(time), tmpString]));
  inherited;
end;
//////////////////////////////////////////////////////////////////////////////
procedure TForm1.WMAdsDeviceNotification( var Message: TMessage );
var pItem                   : PThreadListItem;
    X                       : integer;
    FileTime                : _FILETIME;
    SystemTime, LocalTime   : _SYSTEMTIME;
    TimeZoneInformation     : _TIME_ZONE_INFORMATION;
    DateTime                : TDateTime;
    adsState                : Smallint;
    sState                  : String;
    cbData                  : Longword;
begin
    with DevThreadList.LockList do
    try
       for X := 0 to Count-1 do
       begin
           pItem := Items[X];
           {convert file time to local system time}
           FileTime:=pItem^.stamp;
           FileTimeToSystemTime(FileTime, SystemTime);
           GetTimeZoneInformation(TimeZoneInformation);
           SystemTimeToTzSpecificLocalTime(@TimeZoneInformation, SystemTime, LocalTime);
           DateTime:=SystemTimeToDateTime(LocalTime);
           cbData :=Min(pItem^.cbSize, sizeof(adsState));
           System.Move( pItem^.data, adsState, cbData );
           case adsState of
                ADSSTATE_STOP:  sState := 'STOP';
                ADSSTATE_RUN:  sState := 'RUN';
           else
                sState := Format('Other: %d', [adsState]);
           end;
           ListBox2.Items.Add(Format( '%s %d.%d.%d.%d.%d.%d[%d], hNot:0x%x, size:%d, hUser:0x%x, State:%s',
                                     [TimeToStr(DateTime), pItem^.netId.b[0], pItem^.netId.b[1], pItem^.netId.b[2],
                                     pItem^.netId.b[3], pItem^.netId.b[4], pItem^.netId.b[5], pItem^.port,
                                     pItem^.hNotify, pItem^.cbSize, pItem^.hUser,
                                     sState ]));
           FreeMem( pItem, sizeof(TThreadListItem) + pItem^.cbSize - 1 ); //free item memory
       end;
       Clear();
    finally
       DevThreadList.UnlockList;
    end;
    inherited;
end;

Language / IDE | Unpack the sample program
Delphi XE2 | Sample04.exe
Delphi 5 or higher (classic) | Sample04.exe


TcAdsDll Delphi Sample05: Start/stop PLC

TwinCAT TcAdsDLL: Delphi examples

Start/stop PLC

The following program starts or stops run-time system 1 to 4 in the PLC.

Requirements:

  • Delphi 5.0 or higher;
  • TcAdsDLL.DLL;
  • TcAdsDEF.pas and TcAdsAPI.pas, archived in the file delphi_adsdll_api_units.zip if you wish to translate the source text yourself;

Delphi 5 program:

unit AdsDLLWriteControlForm;

interface

uses
  TcAdsDEF, TcAdsAPI, Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ExtCtrls, StdCtrls, ComCtrls;

type
  TForm1 = class(TForm)
    StatusBar1: TStatusBar;
    GroupBox1: TGroupBox;
    Start: TButton;
    Stop: TButton;
    RadioGroup1: TRadioGroup;
    Label1: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure StartClick(Sender: TObject);
    procedure StopClick(Sender: TObject);
    procedure RadioGroup1Click(Sender: TObject);
  private
    { Private declarations }
    LocalAddr     :TAmsAddr;
    ServerAddr    :TAmsAddr;
    AdsResult     :Longint;
    ClientPort    :Word;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation
{$R *.DFM}
//////////////////////////////////////////////////////////////////////////////
procedure TForm1.FormCreate(Sender: TObject);
begin
  GroupBox1.Caption := 'PLC';
  GroupBox1.Align := alClient;
  RadioGroup1.Caption := 'RTS';
  RadioGroup1.Items.Add('801');
  RadioGroup1.Items.Add('811');
  RadioGroup1.Items.Add('821');
  RadioGroup1.Items.Add('831');
  RadioGroup1.ItemIndex:=0;
  StatusBar1.SimplePanel := true;
  Label1.Caption:='Ads result:';

  ClientPort:= AdsPortOpen();
  if ClientPort = 0 then {error!}
    ShowMessage('AdsPortOpen() error!')
  else  {OK}
  begin
    AdsResult:=AdsGetLocalAddress( @LocalAddr );
    if AdsResult = 0 then {OK}
    begin
      ServerAddr.netId:=LocalAddr.netId;{connect to the PLC on the local PC}
      ServerAddr.port:=801; {select first RTS}

      StatusBar1.SimpleText:=Format('Client NetId:%d.%d.%d.%d.%d.%d  Client Port:%d',[
              LocalAddr.netId.b[0], LocalAddr.netId.b[1], LocalAddr.netId.b[2],
              LocalAddr.netId.b[3], LocalAddr.netId.b[4], LocalAddr.netId.b[5],
              ClientPort]);
    end
    else
      ShowMessageFmt('AdsGetLocalAddress() error:%d', [AdsResult]);
  end;
end;

//////////////////////////////////////////////////////////////////////////////
procedure TForm1.FormDestroy(Sender: TObject);
begin
  AdsResult := AdsPortClose();
  if AdsResult > 0 then
    ShowMessageFmt('AdsPortClose() error:%d', [AdsResult]);
end;

//////////////////////////////////////////////////////////////////////////////
procedure TForm1.StartClick(Sender: TObject);
begin
  AdsResult:=AdsSyncWriteControlReq( @ServerAddr, ADSSTATE_RUN, 0, 0, Nil );
  Label1.Caption := Format('AdsSyncWriteControlReq result:%d', [AdsResult]);
end;

//////////////////////////////////////////////////////////////////////////////
procedure TForm1.StopClick(Sender: TObject);
begin
  AdsResult:=AdsSyncWriteControlReq( @ServerAddr, ADSSTATE_STOP, 0, 0, Nil );
  Label1.Caption := Format('AdsSyncWriteControlReq result:%d', [AdsResult]);
end;

//////////////////////////////////////////////////////////////////////////////
procedure TForm1.RadioGroup1Click(Sender: TObject);
begin
  ServerAddr.port := StrToInt(RadioGroup1.Items[RadioGroup1.ItemIndex]);
end;

end.

Language / IDE | Unpack the sample program
Delphi XE2 | Sample05.exe
Delphi 5 or higher (classic) | Sample05.exe


TcAdsDll Delphi Sample06

TwinCAT TcAdsDLL: Delphi examples

Example with extended functions (for multithreaded applications)

The application is a simple Delphi-MDI application. Each Child window (GUI) has its own ADS thread (ADS port for data communication) in which the extended ADS functions are called. The application demonstrates how ADS function calls (longer actions) can be outsourced to a separate thread without blocking the main thread. The application can be used as a starting point for your own implementations.

The ADS thread functions were implemented in a separate unit: AdsThread.pas. The Child window functions were similarly implemented in a separate unit: CHILDWIN.PAS / CHILDWIN.DFM.

Communication between the GUI (Child window) and the ADS thread was realized with the aid of WM messages (PostThreadMessage/SendMessage functions). As a result, the main thread was decoupled from the ADS threads. However, other synchronization mechanisms can also be used.

Requirements:

  • Delphi 7.0 + Update 7.1 or higher;
  • TcAdsDLL.DLL;
  • TcAdsDEF.pas and TcAdsAPI.pas, archived in the file delphi_adsdll_api_units.zip if you wish to translate the source text yourself;

Description

Start the associated PLC project Sample.pro and MDIAPP.exe. Using File -> New, a new ADS-Client connection and the associated GUI windows can be created. Create several Child windows.

PROGRAM MAIN
VAR
    vUINT AT%MB0 :UINT;
END_VAR
vUINT := vUINT +1;

The following actions are started with a mouse click on the corresponding buttons:

  • Start read loop...: Reads the vUINT variable synchronously (by address) in a For loop (duration approx. 10 seconds).
  • Start write loop...: Writes the vUINT variable synchronously (by address) in a For loop (duration approx. 10 seconds).
  • Start read by name loop...: Reads the vUINT variable synchronously (by variable name) in a For loop (duration approx. 10 seconds).
  • Add notification: Announces a notification. Whenever the value of the vUINT variable changes, it is transmitted to the Client application and displayed in Memo Control (notification messages).
  • Delete notification: Deactivates the notification.
  • Start PLC: Starts the PLC.
  • Stop PLC: Stops the PLC.
  • Exit thread: Ends the ADS thread (exits the TThread-Execute call).
In order to test, select for example the Start read loop...button in one window and the Start write loop...button in another window.

Delphi 7 program

Main.pas:

unit MAIN;
interface

uses Windows, SysUtils, Classes, Graphics, Forms, Controls, Menus,
  StdCtrls, Buttons, Messages, ExtCtrls, ComCtrls, StdActns,
  ActnList, ToolWin, ImgList, CHILDWIN, TcAdsDef, TcAdsAPI, AdsThread;
const
  DEFAULT_SERVERADDR : TAmsAddr =    ( netID : ( b : (0,0,0,0,0,0 ) );
//  DEFAULT_SERVERADDR : TAmsAddr =    ( netID : ( b : (172,16,7,53,1,1 ) );
                                      port : 801);
  DEFAULT_ADSTIMEOUT : Longint = 5000;  {default ads timeout = 5 seconds}
  DEFAULT_IG  : Longint = $00004020;    {memory range}
  DEFAULT_IO  : Longint = $00000000;    {byte offset of the PLC variable}
type
  TMainForm = class(TForm)
    MainMenu1: TMainMenu;
    ActionList1: TActionList;
    StatusBar: TStatusBar;
    ImageList1: TImageList;
    File1: TMenuItem;
    FileNewItem: TMenuItem;
    FileCloseItem: TMenuItem;
    FileExitItem: TMenuItem;
    FileExit1: TAction;
    FileClose1: TWindowClose;
    Window1: TMenuItem;
    WindowCascadeItem: TMenuItem;
    WindowTileItem: TMenuItem;
    WindowTileItem2: TMenuItem;
    WindowMinimizeItem: TMenuItem;
    WindowArrangeItem: TMenuItem;
    WindowCascade1: TWindowCascade;
    WindowTileHorizontal1: TWindowTileHorizontal;
    WindowTileVertical1: TWindowTileVertical;
    WindowMinimizeAll1: TWindowMinimizeAll;
    WindowArrangeAll1: TWindowArrange;
    ToolBar2: TToolBar;
    ToolButton9: TToolButton;
    ToolButton8: TToolButton;
    ToolButton10: TToolButton;
    ToolButton11: TToolButton;
    procedure FileNew1Execute(Sender: TObject);
    procedure FileExit1Execute(Sender: TObject);
  private
    { Private declarations }
    procedure CreateMDIChild(const Name: string);
  public
    { Public declarations }
  end;
var
  MainForm: TMainForm;
implementation
{$R *.dfm}
///////////////////////////////////////////////////////////////////////////////
procedure TMainForm.CreateMDIChild(const Name: string);
var
  Child: TMDIChild;
  params : TAdsParams;
begin
  params.hOwner := 0;
  params.server := DEFAULT_SERVERADDR;
  params.timeout := DEFAULT_ADSTIMEOUT;
  params.varIG   := DEFAULT_IG;
  params.varIO := DEFAULT_IO;
  params.length := 2; // INT
  params.name := 'MAIN.VUINT';
  { create a new MDI child window }
  Child := TMDIChild.Create(Application, params );
  Child.Caption := Name;
end;
///////////////////////////////////////////////////////////////////////////////
procedure TMainForm.FileNew1Execute(Sender: TObject);
begin
  CreateMDIChild('TwinCAT TcAdsDll Client ' + IntToStr(MDIChildCount + 1));
end;
///////////////////////////////////////////////////////////////////////////////
procedure TMainForm.FileExit1Execute(Sender: TObject);
begin
  Close;
end;
end.

ChildWin.pas

unit CHILDWIN;
interface
uses Windows, Classes, Messages, Graphics, Forms, Controls, StdCtrls, TcAdsDef, TcAdsApi, AdsThread,
  ComCtrls;
type
  TMDIChild = class(TForm)
    Memo1: TMemo;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    Button5: TButton;
    Button6: TButton;
    Memo2: TMemo;
    Label1: TLabel;
    Label2: TLabel;
    Memo3: TMemo;
    Label3: TLabel;
    Button7: TButton;
    Button8: TButton;
    ProgressBar1: TProgressBar;
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure Button5Click(Sender: TObject);
    procedure Button6Click(Sender: TObject);
    procedure Button7Click(Sender: TObject);
    procedure Button8Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
    adsThread : TAdsThread;
    procedure OnNotification(var Message: TMessage); message WM_NOTIFICATION;
    procedure OnReadWrite(var Message: TMessage); message WM_READWRITE;
    procedure OnLog(var Message: TMessage); message WM_LOG;
    procedure OnProgress(var Message: TMessage); message WM_PROGRESS;
  public
    { Public declarations }
    constructor Create( AOwner: TComponent; params : TAdsParams );
  end;
implementation
{$R *.dfm}
//////////////////////////////////////////////////////////////////
constructor TMDIChild.Create(AOwner: TComponent; params : TAdsParams );
begin
  inherited Create(AOwner);   // create child window
  params.hOwner := self.Handle; // set owner window handle
  adsThread := TAdsThread.Create(params ); // create ads thread  (open ads connection)
end;
//////////////////////////////////////////////////////////////////
procedure TMDIChild.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action := caFree;
end;
//////////////////////////////////////////////////////////////////
procedure TMDIChild.FormDestroy(Sender: TObject);
begin
  adsThread.Free();// release thread resources (close ads connection)
  inherited;
end;
//////////////////////////////////////////////////////////////////
procedure TMDIChild.OnLog(var Message: TMessage);
begin
 Memo1.Lines.Add(String(Message.WParam));
end;
//////////////////////////////////////////////////////////////////
procedure TMDIChild.OnReadWrite(var Message: TMessage);
begin
  Memo3.Lines.Add(String(Message.WParam));
end;
//////////////////////////////////////////////////////////////////
procedure TMDIChild.OnNotification(var Message: TMessage);
begin
  Memo2.Lines.Add(String(Message.WParam));
end;
//////////////////////////////////////////////////////////////////
procedure TMDIChild.OnProgress(var Message: TMessage);
begin
  if Message.LParam = 0 then
    Progressbar1.Position := Message.WParam
  else
  begin
    Progressbar1.Min := Message.WParam;
    Progressbar1.Max := Message.LParam;
  end;
end;
//////////////////////////////////////////////////////////////////
procedure TMDIChild.Button1Click(Sender: TObject);
begin
  Memo3.Lines.Clear();
  PostThreadMessage( adsThread.ThreadID, WM_READLOOP, 0, 0 );
end;
//////////////////////////////////////////////////////////////////
procedure TMDIChild.Button2Click(Sender: TObject);
begin
  Memo3.Lines.Clear();
  PostThreadMessage( adsThread.ThreadID, WM_WRITELOOP, 0, 0 );
end;
//////////////////////////////////////////////////////////////////
procedure TMDIChild.Button3Click(Sender: TObject);
begin
  Memo2.Lines.Clear();
  PostThreadMessage( adsThread.ThreadID, WM_ADDNOTIFICATION, 0, 0 );
end;
//////////////////////////////////////////////////////////////////
procedure TMDIChild.Button4Click(Sender: TObject);
begin
  PostThreadMessage( adsThread.ThreadID, WM_DELNOTIFICATION, 0, 0 );
end;
//////////////////////////////////////////////////////////////////
procedure TMDIChild.Button5Click(Sender: TObject);
begin
  PostThreadMessage( adsThread.ThreadID, WM_STARTPLC, 0, 0 );
end;
//////////////////////////////////////////////////////////////////
procedure TMDIChild.Button6Click(Sender: TObject);
begin
  PostThreadMessage( adsThread.ThreadID, WM_STOPPLC, 0, 0 );
end;
//////////////////////////////////////////////////////////////////
procedure TMDIChild.Button8Click(Sender: TObject);
begin
  PostThreadMessage( adsThread.ThreadID, WM_READBYNAME, 0, 0 );
end;
//////////////////////////////////////////////////////////////////
procedure TMDIChild.Button7Click(Sender: TObject);
begin
 adsThread.Terminate();
 Button1.Enabled := false;
 Button2.Enabled := false;
 Button3.Enabled := false;
 Button4.Enabled := false;
 Button5.Enabled := false;
 Button6.Enabled := false;
 Button7.Enabled := false;
 Button8.Enabled := false;
end;
end.

AdsThread.pas:

unit AdsThread;
interface
uses
  Messages, Windows, Classes, SysUtils, TcAdsDef, TcAdsApi;
const
  // messages send from form to ads thread
  WM_READLOOP       = WM_APP + $0001;
  WM_WRITELOOP      = WM_APP + $0002;
  WM_ADDNOTIFICATION = WM_APP + $0003;
  WM_DELNOTIFICATION = WM_APP + $0004;
  WM_STARTPLC       = WM_APP + $0005;
  WM_STOPPLC        = WM_APP + $0006;
  WM_READBYNAME     = WM_APP + $0007;
  // messages send from ads thread to form
  WM_LOG            = WM_APP + $1001;
  WM_NOTIFICATION   = WM_APP + $1002;
  WM_READWRITE      = WM_APP + $1003;
  WM_PROGRESS       = WM_APP + $1004;
  MAX_TEST_LOOPS  : Longint = 100;
type
  TAdsParams = record
      hOwner : HWND;
      server  : TAmsAddr;  // TwincAT ADS server network address
      timeout : Longint;  // ads communication timeout
      varIG   : Longint;  // index offset of the plc variable
      varIO   : Longint;  // index group of the plc variable
      length  : Longint; // byte size of the plc variable
      name    : String; // plc variable symbol name
  end;
type
  TAdsThread = class(TThread)
  private
    { Private declarations }
    port    : Longint;
    params   : TAdsParams;
    hNotification : Longint;  // notification handle
    procedure LogMessage( msg : String );
    procedure LogReadWrite( msg : String );
    procedure StartProgress( min : Longint; max: Longint );
    procedure DoProgress( position : Longint );
    function ReadByName( port : Longint; pAddr:PAmsAddr; sName : String; pReadData:Pointer; cbReadLength : Longword; pcbReturn :PLONGWORD ): Longint;
    procedure OnReadLoop(var Message: TMessage); message WM_READLOOP;
    procedure OnWriteLoop(var Message: TMessage); message WM_WRITELOOP;
    procedure OnAddNotification(var Message: TMessage); message WM_ADDNOTIFICATION;
    procedure OnDelNotification(var Message: TMessage); message WM_DELNOTIFICATION;
    procedure OnStartPlc(var Message: TMessage); message WM_STARTPLC;
    procedure OnStopPlc(var Message: TMessage); message WM_STOPPLC;
    procedure OnReadByName(var Message: TMessage); message WM_READBYNAME;
  protected
    procedure Execute; override;
  public
    constructor Create( params : TAdsParams );
  end;
implementation

//////////////////////////////////////////////////////////////////////////////
// Notification callback
Procedure Callback( pAddr:PAmsAddr; pNotification:PAdsNotificationHeader; hUser:Longword ); stdcall;
var sValue : String;
    nValue : Word;
begin
  // only to test
  if pNotification^.cbSampleSize = sizeof(nValue) then
  begin
    nValue := (PWORD(@pNotification^.data))^;
    sValue := Format('Value: %d', [ nValue ] );
    SendMessage( HWND(hUser), WM_NOTIFICATION, Integer(sValue), 0 );
  end;
end;

//////////////////////////////////////////////////////////////////////////////
constructor TAdsThread.Create( params : TAdsParams );
begin
  hNotification := 0;
  port := 0;
  self.params := params; // save parameter for later use
  inherited Create(false);
end;

//////////////////////////////////////////////////////////////////////////////
procedure TAdsThread.Execute();
var result      : Longint;
    client      : TAmsAddr;
    oldTimeout  : Longint;
    enabled     : LongBool;
    adsState, deviceState : Word;
    adsVersion: TAdsVersion;
    szDevName : AnsiString;
    pDllVersion : PAdsVersion;
    Msg: TMsg;
    DMsg: TMessage;
begin
  LogMessage( 'Ads thread started!' );
  result := AdsGetDllVersion();
  if result <> 0 then
  begin
    pDllVersion := PAdsVersion(@result);
    LogMessage( Format('---- TcAdsDll.dll version:%d, revision:%d, build:%d ----',
                [pDllVersion^.version, pDllVersion^.revision, pDllVersion^.build]));
  end;
  port := 0;
  port := AdsPortOpenEx();
  if port <> 0 then
  begin
    result := AdsGetLocalAddressEx( port, @client );
    LogMessage(Format('AdsGetLocalAddressEx() result: %d [0x%x]', [result, result]));
    if result = 0 then
    begin

      if (params.server.netId.b[0] = 0)
      And (params.server.netId.b[1] = 0)
      And (params.server.netId.b[2] = 0)
      And (params.server.netId.b[3] = 0)
      And (params.server.netId.b[4] = 0)
      And (params.server.netId.b[5] = 0)then
      begin
        params.server.netId := client.netId;
      end;
      // show client address
      LogMessage(Format('Client port:%d [0x%x], netID:%d.%d.%d.%d.%d.%d',
                      [client.port, client.port,
                      client.netid.b[0],client.netid.b[1],client.netid.b[2],
                      client.netid.b[3],client.netid.b[4],client.netid.b[5]]));
      // show server address
      LogMessage(Format('Server port:%d [0x%x], netID:%d.%d.%d.%d.%d.%d',
                      [params.server.port, params.server.port,
                      params.server.netid.b[0],params.server.netid.b[1],params.server.netid.b[2],
                      params.server.netid.b[3],params.server.netid.b[4],params.server.netid.b[5]]));
      // read current ads timeout
      oldTimeout := 0;
      result := AdsSyncGetTimeoutEx( port, @oldTimeout );
      LogMessage(Format('AdsSyncGetTimeoutEx() result: %d [0x%x], timeout: %d', [result, result, oldTimeout]));
      if oldTimeout <> params.timeout then
      begin
        // set new ads timeout
        result := AdsSyncSetTimeoutEx( port, params.timeout );
        LogMessage(Format('AdsSyncSetTimeoutEx() result: %d [0x%x], timeout: %d', [result, result, params.timeout]));
      end;
      result := AdsAmsPortEnabledEx( port, @enabled );
      LogMessage(Format('AdsAmsPortEnabledEx() result: %d [0x%x], enabled: %d', [result, result, Ord(enabled)]));
      if enabled then
      begin
        result := AdsSyncReadStateReqEx( port, @params.server, @adsState, @deviceState );
        LogMessage(Format('AdsSyncReadStateReqEx() result: %d [0x%x], adsState: %d, deviceState: %d',
                  [result, result, adsState, deviceState]));
        SetLength( szDevName, ADS_FIXEDNAMESIZE + 1 );
        result := AdsSyncReadDeviceInfoReqEx( port, @params.server, @szDevName[1], @adsVersion );
        LogMessage(Format('AdsSyncReadDeviceInfoReqEx() result: %d [0x%x], name: %s, version:%d, revision:%d, build:%d',
                  [result, result, szDevName, adsVersion.version, adsVersion.revision, adsVersion.build]));
      end;
      /////////////////////////////////////////////////
      PeekMessage(Msg,0,0,0,PM_NOREMOVE); // Create Message Queue
      repeat
        if PeekMessage(Msg,0,0,0,PM_REMOVE) then
        begin
          DMsg.Msg:=Msg.message;
          DMsg.wParam:=Msg.wParam;
          DMsg.lParam:=Msg.lParam;
          DMsg.Result:=0;
          Dispatch(DMsg);
        end;
        Sleep(10);
      until Terminated;
      if hNotification <> 0 then
      begin
         result := AdsSyncDelDeviceNotificationReqEx( port, @params.server, hNotification );
         hNotification := 0;
      end;
      /////////////////////////////////////////////////
      result := AdsPortcloseEx(port); // close ads port
      LogMessage(Format('AdsPortcloseEx() result: %d [0x%x]', [result, result]));
    end
  end
  else
    LogMessage('AdsPortOpenEx() failed!');
  LogMessage( 'Ads thread stopped!' );
end;

//////////////////////////////////////////////////////////////////////////////
procedure TAdsThread.OnReadLoop(var Message: TMessage);
var i, error : Longint;
    varValue    : Word;
    cbReturned    : Longint;
begin
  StartProgress( 0, MAX_TEST_LOOPS);
  for i:= 1 to MAX_TEST_LOOPS  do
  begin
    cbReturned := 0;
    varValue := 0;
    error := AdsSyncReadReqEx2( port, @params.server, params.varIG, params.varIO, sizeof(varValue), @varValue, @cbReturned );
    LogReadWrite( Format('AdsSyncReadReqEx2() result: %d [0x%x], value:0x%x, length: %d', [error, error, varValue, cbReturned]));
    if Terminated then break;
    DoProgress(i);
    Sleep(100);
  end;
end;

//////////////////////////////////////////////////////////////////////////////
procedure TAdsThread.OnWriteLoop(var Message: TMessage);
var i, error : Longint;
    varValue    : Word;
begin
  StartProgress( 0, MAX_TEST_LOOPS);
  varValue := 1;
  for i:= 1 to MAX_TEST_LOOPS  do
  begin
    varValue := i; // write some test value
    error := AdsSyncWriteReqEx( port, @params.server, params.varIG, params.varIO, sizeof(varValue), @varValue );
    LogReadWrite( Format('AdsSyncWriteReqEx() result:%d [0x%x], value:0x%x', [error, error, varValue]));
    if Terminated then break;
    DoProgress(i);
    Sleep(100);
  end;
end;

//////////////////////////////////////////////////////////////////////////////
procedure TAdsThread.OnReadByName(var Message: TMessage);
var i, cbReturned, error : Longint;
    varValue : Word;
begin
  StartProgress( 0, MAX_TEST_LOOPS);
  for i:= 1 to MAX_TEST_LOOPS  do
  begin
    cbReturned := 0;
    varValue := 0;
    error := ReadByName( port, @params.server, params.name, @varValue, sizeof(varValue), @cbReturned);
    LogReadWrite( Format('Ads read value by name result:%d [0x%x], value: 0x%x, length: %d', [error, error, varValue, cbReturned]));
    if Terminated then break;
    DoProgress(i);
    Sleep(100);
  end;
end;

//////////////////////////////////////////////////////////////////////////////
procedure TAdsThread.OnAddNotification(var Message: TMessage);
var error : Longword;
    adsNotificationAttrib : TAdsNotificationAttrib;
begin
     adsNotificationAttrib.cbLength := params.length;
     adsNotificationAttrib.nTransMode := ADSTRANS_SERVERONCHA;
     adsNotificationAttrib.nMaxDelay := 10000000;//1 second
     adsNotificationAttrib.nCycleTime := 0;
     error := AdsSyncAddDeviceNotificationReqEx( port, @params.server,
                                                params.varIG,
                                                params.varIO,
                                                @adsNotificationAttrib,
                                                @Callback,
                                                params.hOwner, // send window handle as user data
                                                @hNotification  );
    LogMessage( Format('AdsSyncAddDeviceNotificationReqEx() result:%d [0x%x], hNotification:0x%x', [error, error, hNotification]));
end;

//////////////////////////////////////////////////////////////////////////////
procedure TAdsThread.OnDelNotification(var Message: TMessage);
var error : Longint;
begin
  if hNotification <> 0 then
  begin
    error := AdsSyncDelDeviceNotificationReqEx( port, @params.server, hNotification );
    hNotification := 0;
    LogMessage( Format('AdsSyncDelDeviceNotificationReqEx() result:%d [0x%x], hNotification:0x%x', [error, error, hNotification]));
  end;
end;

//////////////////////////////////////////////////////////////////////////////
procedure TAdsThread.OnStartPlc(var Message: TMessage);
var error : Longint;
begin
  error:=AdsSyncWriteControlReqEx( port, @params.server, ADSSTATE_RUN, 0, 0, Nil );
  LogMessage( Format('AdsSyncWriteControlReqEx() result: %d [0x%x]', [error, error]));
end;

//////////////////////////////////////////////////////////////////////////////
procedure TAdsThread.OnStopPlc(var Message: TMessage);
var error : Longint;
begin
  error:=AdsSyncWriteControlReqEx( port, @params.server, ADSSTATE_STOP, 0, 0, Nil );
  LogMessage( Format('AdsSyncWriteControlReqEx() result: %d [0x%x]', [error, error]));
end;

//////////////////////////////////////////////////////////////////////////////
function TAdsThread.ReadByName( port : Longint; pAddr:PAmsAddr; sName : String; pReadData:Pointer; cbReadLength : Longword; pcbReturn :PLONGWORD ) : Longint;
var hSymbol : Longword;
    error : Longint;
    varName : AnsiString;
begin
  {get handle}
  hSymbol := 0;
  varName := AnsiString(sName);
  error := AdsSyncReadWriteReqEx2( port, pAddr, ADSIGRP_SYM_HNDBYNAME, 0, sizeof(hSymbol), @hSymbol, Length(varName)+1, @varName[1], pcbReturn );
  if error = 0 then
  begin
    {get value}
    error := AdsSyncReadReqEx2( port, pAddr, ADSIGRP_SYM_VALBYHND, hSymbol, cbReadLength, pReadData, pcbReturn );
    {release handle}
    error := AdsSyncWriteReqEx( port, pAddr, ADSIGRP_RELEASE_SYMHND, 0, sizeof(hSymbol), @hSymbol );
  end;
  result := error;
end;
end.

Language / IDE | Unpack the sample program
Delphi XE2 | Sample06.exe
Delphi 7 or higher (classic) | Sample06.exe


TcAdsDll Delphi Sample07

TwinCAT TcAdsDLL: Delphi Examples

ADS sum command: Read or write list of variables with one single ADS command

System requirements:

  • TwinCAT v2.10 Build >= 1324;
  • Delphi 6.0 + Update Pack 2 or higher;
  • TcAdsDLL.DLL;
  • TcAdsDEF.pas and TcAdsAPI.pas, archived in the file delphi_adsdll_api_units.zip if you wish to translate the source text yourself;

Project definition

15 PLC variables of different type have to be read or written via an ADS sum command .

Delphi allows to deliver open array parameters to functions. This means the number of array elements in a function parameter is not specified. Three new functions are implemented in the TcAdsApi.pas file: AdsSyncSumReadReq , AdsSyncSumWriteReq and AdsSyncSumReadWriteReq. These Functions use the functionality of open array parameters and simplify the usage of the sum command under Delphi. The example project uses these new wrapper functions.

The PLC program

The PLC variables have been declared as global variables. At a started PLC, value changes are simulated in the simple main program.

VAR_GLOBAL
    nBits : BYTE;
    wBits : WORD;
    dwBits : DWORD;
    f32AxPos : REAL;
    f64AxPos : LREAL;
    bEnable : BOOL;
    sValue : STRING := 'INIT';
    ui8Num : USINT;
    ui16Num : UINT;
    ui32Num : UDINT;
    i8Num : SINT;
    i16Num : INT;
    i32Num : DINT;
    aui32 : ARRAY[0..9] OF DWORD;
    out8Bit AT%QX0.1 : BOOL;
END_VAR

Delphi 6 program

The declarations of the TcAdsDLL functions in use are located in the Pascal units TcAdsDEF.pas and TcAdsAPI.pas. This are linked into the project by means of a uses clause.

unit Unit1;
interface
uses
  TcAdsDEF, TcAdsAPI, Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, Grids, ValEdit, ComCtrls, StdCtrls, Math;

At program start the communication port is opened and closed at the ending. The required symbol information of the PLC variables like index group, index offset etc. also read once at program start by an sum command.

/////////////////////////////////////////////////////////////////////////
type
    TAdsSymbolInfo = packed record
    entry   : TAdsSymbolEntry; // ADS symbol info
    name    : AnsiString; // ADS symbol name
    arraySize : Longword;
    case typeID : ADS_DATATYPE of           //variable part value
        ADST_VOID:();                       //nothing
        ADST_INT16:(vINT : Smallint);       //2 byte signed number
        ADST_INT32:(vDINT: Longint);        //4 byte signed number
        ADST_REAL32:(vREAL : Single);       //4 byte real
        ADST_REAL64:(vLREAL : Double);      //8 byte real
        ADST_INT8:(vSINT : Shortint);       //1 byte signed number
        ADST_UINT8:(vBYTE : Byte);          //1 byte unsigned number
        ADST_UINT16:(vWORD : Word);         //2 byte unsigned number
        ADST_UINT32:(vDWORD : DWORD);       //4 byte unsigned number
        ADST_STRING:(vSTRING : Array[0..80] OF AnsiChar); //null terminated string
        ADST_BIT:(vBOOL : Boolean);         //boolean
        ADST_BIGTYPE:(data : Array[0..255] OF Byte); //data byte array
    end;
/////////////////////////////////////////////////////////////////////////
type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    ValueListEditor1: TValueListEditor;
    Button3: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure FormCreate(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    { Private declarations }
  public
    infos : Array of TAdsSymbolInfo;
    client : Longint;
    amsAddr : TAmsAddr;
    { Public declarations }
    procedure ReadListOfSymbolInfo();
    procedure ReadListOfVars();
    procedure WriteListOfVars();
    procedure ReadListByName();
    procedure DataToView( var info : TAdsSymbolInfo);
    procedure ViewToData( var info : TAdsSymbolInfo);
  end;
var
  Form1: TForm1;
implementation
{$R *.dfm}
/////////////////////////////////////////////////////////////////////////
procedure TForm1.FormCreate(Sender: TObject);
var adsErr : Longint;
begin
    Button1.Enabled := True;
    Button2.Enabled := False;
    Button3.Enabled := False;
    client := AdsPortOpen(); // open ads port
    if client <> 0 then
    begin
        adsErr := AdsGetLocalAddress( @amsAddr );
        if adsErr = 0 then
        begin
            amsAddr.port := AMSPORT_R0_PLC_RTS1; // set port to 801  (first PLC run time system)
            ReadListOfSymbolInfo();// read symbol info
        end
        else
            ShowMessageFmt('AdsGetLocalAddress() error: %d [0x%X]', [adsErr, adsErr]);
    end
    else
        ShowMessage('AdsPortOpen() failed!');
end;
/////////////////////////////////////////////////////////////////////////
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
    if client <> 0 then
        AdsPortClose();// close ads port
end;

Read symbol information

/////////////////////////////////////////////////////////////////////////
procedure TForm1.ReadListOfSymbolInfo();
var igList, ioList, writeLenList, readLenList, retLenList : Array of Longword;
    writePtrList, readPtrList : Array of Pointer;
    resList : Array of Longint;
    i, adsErr : Longint;
    nInfos : Longword;
begin
    nInfos := 15;
    SetLength(infos, nInfos);
    infos[0].name := '.nBits';
    infos[1].name := '.wBits';
    infos[2].name := '.dwBits';
    infos[3].name := '.f32AxPos';
    infos[4].name := '.f64AxPos';
    infos[5].name := '.bEnable';
    infos[6].name := '.sValue';
    infos[7].name := '.ui8Num';
    infos[8].name := '.ui16Num';
    infos[9].name := '.ui32Num';
    infos[10].name := '.i8Num';
    infos[11].name := '.i16Num';
    infos[12].name := '.i32Num';
    infos[13].name := '.aui32';
    infos[14].name := '.out8Bit';
    SetLength(igList, nInfos);
    SetLength(ioList, nInfos);
    SetLength(readLenList, nInfos);
    SetLength(readPtrList, nInfos);
    SetLength(writeLenList, nInfos);
    SetLength(writePtrList, nInfos);
    SetLength(resList, nInfos);
    SetLength(retLenList, nInfos);
    for i:=Low(infos) to High(infos) do
    begin
        igList[i]       := ADSIGRP_SYM_INFOBYNAMEEX;
        ioList[i]       := $0;
        readLenList[i]  := SizeOf(infos[i].entry);
        readPtrList[i]  := @infos[i].entry;
        writeLenList[i] := Length(infos[i].name) + 1;
        writePtrList[i] := @infos[i].name[1];
        resList[i]      := 0;
        retLenList[i]   := 0;
    end;
    adsErr := AdsSyncSumReadWriteReq(   @amsAddr, igList, ioList,
                                        readLenList, readPtrList,
                                        writeLenList, writePtrList,
                                        resList,retLenList );
    if adsErr = 0 then
    begin
        for i:=Low(resList) to High(resList) do
        begin
            if resList[i] <> 0 then
                ShowMessageFmt('AdsSyncSumReadWriteReq(reading info "%s") error: %d [0x%X]', [infos[i].name, resList[i], resList[i]]);
        end;
    end
    else
        ShowMessageFmt('AdsSyncSumReadWriteReq() error: %d [0x%X]', [adsErr, adsErr]);
end;

Read values

/////////////////////////////////////////////////////////////////////////
procedure TForm1.Button1Click(Sender: TObject);
begin
    ValueListEditor1.Strings.Clear();
    ReadListOfVars();
end;
/////////////////////////////////////////////////////////////////////////
procedure TForm1.ReadListOfVars();
var igList, ioList, lenList : Array of Longword;
    ptrList : Array of Pointer;
    resList : Array of Longint;
    i, adsErr : Longint;
begin
    SetLength(igList, Length(infos));
    SetLength(ioList, Length(infos));
    SetLength(lenList, Length(infos));
    SetLength(ptrList, Length(infos));
    SetLength(resList, Length(infos));
    for i:=Low(infos) to High(infos) do
    begin
        igList[i] := infos[i].entry.iGroup;
        ioList[i] := infos[i].entry.iOffs;
        lenList[i] := infos[i].entry.size;
        ptrList[i] := @infos[i].data[0];
        resList[i] := 0;
    end;
    adsErr := AdsSyncSumReadReq(@amsAddr, igList, ioList, lenList, ptrList, resList );
    if adsErr = 0 then
    begin
        for i:= Low(resList) to High(resList) do
        begin
            if resList[i] <> 0 then
                ShowMessageFmt('AdsSyncSumReadReq(reading value "%s") error: %d [0x%X]', [infos[i].name, resList[i], resList[i]]);
            DataToView( infos[i] )// Output read data
        end;
    end
    else
        ShowMessageFmt('AdsSyncSumReadReq() error: %d [0x%X]', [adsErr, adsErr]);
    Button2.Enabled := adsErr = 0;
    Button3.Enabled := adsErr = 0;
end;

Write values

/////////////////////////////////////////////////////////////////////////
procedure TForm1.Button2Click(Sender: TObject);
begin
    WriteListOfVars();
end;
/////////////////////////////////////////////////////////////////////////
procedure TForm1.WriteListOfVars();
var igList, ioList, lenList : Array of Longword;
    srcList : Array of Pointer;
    resList : Array of Longint;
    i, adsErr : Longint;
begin
    SetLength(igList, Length(infos));
    SetLength(ioList, Length(infos));
    SetLength(lenList, Length(infos));
    SetLength(srcList, Length(infos));
    SetLength(resList, Length(infos));
    for i:= Low(infos) to High(infos) do
    begin
        try
            ViewToData( infos[i] ); // refresh value
        finally
            igList[i] := infos[i].entry.iGroup;
            ioList[i] := infos[i].entry.iOffs;
            lenList[i] := infos[i].entry.size;
            srcList[i] := @infos[i].data[0];
       resList[i] := 0;
        end;
    end;
    adsErr := AdsSyncSumWriteReq(  @amsAddr,
                                   igList,
                                   ioList,
                                   lenList,
                                   srcList,
                                   resList );
    if adsErr = 0 then
    begin
        for i:= Low(resList) to High(resList) do
        begin
            if resList[i] <> 0 then
                ShowMessageFmt('AdsSyncSumWriteReq(writing value "%s") error: %d [0x%X]', [infos[i].name, resList[i], resList[i]]);
        end;
    end
    else
        ShowMessageFmt('AdsSyncSumWriteReq() error: %d [0x%X]', [adsErr, adsErr]);
end;

Read values "by name"

/////////////////////////////////////////////////////////////////////////
procedure TForm1.Button3Click(Sender: TObject);
begin
    ValueListEditor1.Strings.Clear();
    ReadListByName();
end;
/////////////////////////////////////////////////////////////////////////
procedure TForm1.ReadListByName();
var igList, ioList, writeLenList, readLenList, retLenList : Array of Longword;
    writePtrList, readPtrList : Array of Pointer;
    resList : Array of Longint;
    i, adsErr : Longint;
begin
    SetLength(igList, Length(infos));
    SetLength(ioList, Length(infos));
    SetLength(readLenList, Length(infos));
    SetLength(readPtrList, Length(infos));
    SetLength(writeLenList, Length(infos));
    SetLength(writePtrList, Length(infos));
    SetLength(resList, Length(infos));
    SetLength(retLenList, Length(infos));
    for i:=Low(infos) to High(infos) do
    begin
        igList[i]       := ADSIGRP_SYM_VALBYNAME;
        ioList[i]       := $0;
        readLenList[i]  := SizeOf(infos[i].data);
        readPtrList[i]  := @infos[i].data[0];
        writeLenList[i] := Length(infos[i].name) + 1;
        writePtrList[i] := @infos[i].name[1];
        resList[i]      := 0;
        retLenList[i]   := 0;
    end;
    adsErr := AdsSyncSumReadWriteReq(   @amsAddr, igList, ioList,
                                        readLenList, readPtrList,
                                        writeLenList, writePtrList,
                                        resList,retLenList );
    if adsErr = 0 then
    begin
        for i:=Low(resList) to High(resList) do
        begin
            if resList[i] <> 0 then
                ShowMessageFmt('AdsSyncSumReadWriteReq(reading value "%s) error:%d [0x%X]', [infos[i].name, resList[i], resList[i]]);
            DataToView( infos[i] )// Output read data
        end;
    end
    else
        ShowMessageFmt('AdsSyncSumReadWriteReq() error: %d [0x%X]', [adsErr, adsErr]);
end;

Simple procedures to show read values or to read user input.

/////////////////////////////////////////////////////////////////////////
procedure TForm1.DataToView( var info : TAdsSymbolInfo );
var sValue : String;
    index : integer;
    list : TStringList;
    bPickList : Boolean;
begin
    list := TStringList.Create();
    bPickList := False;
    sValue := '';
    info.arraySize := 0;
    info.typeID := ADS_DATATYPE(info.entry.dataType);
    case info.typeID of
        ADST_INT8:
            begin
                info.arraySize := info.entry.size Div sizeof(info.vSINT);
                sValue := Format('%d', [info.vSINT]);
            end;
        ADST_INT16:
            begin
                info.arraySize := info.entry.size Div sizeof(info.vINT);
                sValue := Format('%d', [info.vINT]);
            end;
        ADST_INT32:
            begin
                info.arraySize := info.entry.size Div sizeof(info.vDINT);
                sValue := Format('%d', [info.vDINT]);
            end;
        ADST_UINT8:
            begin
                info.arraySize := info.entry.size Div sizeof(info.vBYTE);
                sValue := Format('%u', [info.vBYTE]);
            end;
        ADST_UINT16:
            begin
                info.arraySize := info.entry.size Div sizeof(info.vWORD);
                sValue := Format('%u', [info.vWORD]);
            end;
        ADST_UINT32:
            begin
                info.arraySize := info.entry.size Div sizeof(info.vDWORD);
                sValue := Format('%u', [info.vDWORD]);
            end;
        ADST_BIT:
            begin
                info.arraySize := info.entry.size Div sizeof(info.vBOOL);
                sValue := Format('%s', [BoolToStr(info.vBOOL, true)]);
                list.Add('True');
                list.Add('False');
                bPickList := True;
            end;
        ADST_STRING:
            begin
                info.arraySize := info.entry.size Div sizeof(info.vSTRING);
                sValue := Format('%s', [Trim(String(info.vSTRING))]); //Trim = Cut characters behind of string end delimiter
            end;
        ADST_REAL32:
            begin
                info.arraySize := info.entry.size Div sizeof(info.vREAL);
                sValue := Format('%f', [info.vREAL]);
            end;
        ADST_REAL64:
            begin
                info.arraySize := info.entry.size Div sizeof(info.vLREAL);
                sValue := Format('%f', [info.vLREAL]);
            end;
       ADST_BIGTYPE:
            sValue := 'BIGTYPE';
    else
        sValue := 'Unknown type?';
    end;
    index := ValueListEditor1.Strings.Add(Format('%s=%s', [info.name, sValue]));
    if bPickList then
    begin
        ValueListEditor1.itemProps[index].EditStyle := esPickList;
        ValueListEditor1.itemProps[index].PickList.AddStrings(list);
    end;
    list.Destroy();
end;
//////////////////////////////////////////////////////////////////////////////
procedure TForm1.ViewToData( var info : TAdsSymbolInfo);
var sValue : String;
begin
    try
        sValue := ValueListEditor1.Values[String(info.name)];
        case info.typeID of
            ADST_INT8:
                begin
                    info.vSINT := Shortint(StrToInt(sValue));
                end;
            ADST_INT16:
                begin
                    info.vINT := Smallint(StrToInt(sValue));
                end;
            ADST_INT32:
                begin
                    info.vDINT := Longint(StrToInt64(sValue));
                end;
            ADST_UINT8:
                begin
                    info.vBYTE := Byte(StrToInt(sValue));
                end;
            ADST_UINT16:
                begin
                    info.vWORD := Word(StrToInt(sValue));
                end;
            ADST_UINT32:
                begin
                    info.vDWORD := Longword(StrToInt64(sValue));
                end;
            ADST_BIT:
                begin
                    info.vBOOL := StrToBool(sValue);
                end;
            ADST_STRING:
                begin
                    StrPCopy(@info.vSTRING[0], AnsiString(sValue));
                end;
            ADST_REAL32:
                begin
                    info.vREAL:= StrToFloat(sValue);
                end;
            ADST_REAL64:
                begin
                    info.vLREAL:= StrToFloat(sValue);
                end;
           ADST_BIGTYPE:
            ;
        else
        ;
        end;
    except
         on E: EConvertError do
            ShowMessage(E.ClassName + E.Message);
    end;
end;
end.

Language / IDE | Unpack the sample program
Delphi XE2 | Sample07.exe
Delphi 6 or higher (classic) | Sample07.exe


TcAdsDll Delphi Sample08

TwinCAT TcAdsDLL: Delphi Beispiele

ADS sum command: Create and release list of variable handles with one single ADS command

System requirements:

  • TwinCAT v2.11 Build >= 1550;
  • Delphi 6.0 + Update Pack 2 or higher;
  • TcAdsDLL.DLL;
  • TcAdsDEF.pas and TcAdsAPI.pas, archived in the file delphi_adsdll_api_units.zip if you wish to translate the source text yourself;

Project definition

Handles for 15 PLC variables have to be taken with one sum command at the start of the simple windows application.
With these handles you can e.g. make reading access to the variables. If the application is stopped these handles should be deallocated with one sum command.

Delphi allows to deliver open array parameters to functions. This means the number of array elements in a function parameter is not specified. Three new functions are implemented in the TcAdsApi.pas file: AdsSyncSumReadReq , AdsSyncSumWriteReq and AdsSyncSumReadWriteReq. These Functions use the functionality of open array parameters and simplify the usage of the sum command under Delphi. The example project uses these new wrapper functions.

The PLC program

The PLC variables have been declared as global variables. At a started PLC, value changes are simulated in the simple main program.

VAR_GLOBAL
nBits : BYTE;
wBits : WORD;
dwBits : DWORD;
f32AxPos : REAL;
f64AxPos : LREAL;
bEnable : BOOL;
sValue : STRING := 'INIT';
ui8Num : USINT;
ui16Num : UINT;
ui32Num : UDINT;
i8Num : SINT;
i16Num : INT;
i32Num : DINT;
aui32 : ARRAY[0..9] OF DWORD;
out8Bit AT%QX0.1 : BOOL;
END_VAR

Delphi 6 program

The declarations of the TcAdsDLL functions in use are located in the Pascal units TcAdsDEF.pas and TcAdsAPI.pas. This are linked into the project by means of a uses clause.

unit Unit1;
interface
uses
  TcAdsDEF, TcAdsAPI, Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Math;

At program start the communication port is opened and closed at the ending.

type TAdsHandleInfo = packed record // packed = 1 byte alignment
    name : AnsiString;// symbol name
    handle : Longword; // symbol handle
    data : Array Of Byte;// data bytes (resize the dynamic array to size of the plc variables)
end;
type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure CreateHandleList();
    procedure ReleaseHandleList();
    procedure ReadListByHandle();
    procedure Button1Click(Sender: TObject);
  private
    infos : Array of TAdsHandleInfo;
    client : Longint;
    amsAddr : TAmsAddr;
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

Create variable handles

implementation
{$R *.dfm}
////////////////////////////////////////////////////////////////////////////
procedure TForm1.FormCreate(Sender: TObject);
var adsErr : Longint;
begin
    client := AdsPortOpen(); // open ads port
    if client <> 0 then
    begin
        adsErr := AdsGetLocalAddress( @amsAddr );
        if adsErr = 0 then
        begin
            amsAddr.port := AMSPORT_R0_PLC_RTS1; // set port to 801  (first PLC run time system)
            CreateHandleList();
        end
        else
            ShowMessageFmt('AdsGetLocalAddress() error: %d [0x%X]', [adsErr, adsErr]);
    end
    else
        ShowMessage('AdsPortOpen() failed!');
end;
////////////////////////////////////////////////////////////////////////////
procedure TForm1.CreateHandleList();
var igList, ioList, writeLenList, readLenList, retLenList : Array of Longword;
    writePtrList, readPtrList : Array of Pointer;
    resList : Array of Longint;
    i, adsErr : Longint;
    nInfos : Longword;
begin
    nInfos := 15;
    SetLength(infos, nInfos);
    infos[0].name := '.nBits';
    SetLength( infos[0].data, 1);//BYTE
    infos[1].name := '.wBits';
    SetLength( infos[1].data, 2);//WORD
    infos[2].name := '.dwBits';
    SetLength( infos[2].data, 4);//DWORD
    infos[3].name := '.f32AxPos';
    SetLength( infos[3].data, 4);//REAL
    infos[4].name := '.f64AxPos';
    SetLength( infos[4].data, 8);//LREAL
    infos[5].name := '.bEnable';
    SetLength( infos[5].data, 1);//BOOL
    infos[6].name := '.sValue';
    SetLength( infos[6].data, 81);//STRING(80) + 1 byte null delimiter
    infos[7].name := '.ui8Num';
    SetLength( infos[7].data, 1);//USINT
    infos[8].name := '.ui16Num';
    SetLength( infos[8].data, 2);//UINT
    infos[9].name := '.ui32Num';
    SetLength( infos[9].data, 4);//UDINT
    infos[10].name := '.i8Num';
    SetLength( infos[10].data, 1);//SINT
    infos[11].name := '.i16Num';
    SetLength( infos[11].data, 2);//INT
    infos[12].name := '.i32Num';
    SetLength( infos[12].data, 4);//DINT
    infos[13].name := '.aui32';
    SetLength( infos[13].data, 40);//ARRARY[..9] OF DWORD
    infos[14].name := '.out8Bit';
    SetLength( infos[14].data, 1);// 1 Byte
    SetLength(igList, nInfos);
    SetLength(ioList, nInfos);
    SetLength(readLenList, nInfos);
    SetLength(readPtrList, nInfos);
    SetLength(writeLenList, nInfos);
    SetLength(writePtrList, nInfos);
    SetLength(resList, nInfos);
    SetLength(retLenList, nInfos);
    for i:=Low(infos) to High(infos) do
    begin
        igList[i]       := ADSIGRP_SYM_HNDBYNAME;
        ioList[i]       := $0000000;
        readLenList[i]  := SizeOf(infos[i].handle);
        readPtrList[i]  := @infos[i].handle;
        writeLenList[i] := Length(infos[i].name) + 1;
        writePtrList[i] := @infos[i].name[1];
        resList[i]      := 0;
        retLenList[i]   := 0;
    end;
    adsErr := AdsSyncSumReadWriteReq(   @amsAddr, igList, ioList,
                                        readLenList, readPtrList,
                                        writeLenList, writePtrList,
                                        resList,retLenList );
    // parse received data
    if adsErr = 0 then
    begin
        for i:=Low(resList) to High(resList) do
        begin
            if resList[i] <> 0 then
                ShowMessageFmt('AdsSyncSumReadWriteReq(ADSIGRP_SYM_HNDBYNAME) error:%d [0x%X], handle:0x%X', [resList[i], resList[i], infos[i].handle]);
            ListBox1.Items.Add(Format('CREATE name: %s, result:%d [0x%X], handle:0x%X', [infos[i].name, resList[i], resList[i], infos[i].handle] ));        end;
    end
    else
        ShowMessageFmt('AdsSyncSumReadWriteReq() error:%d [0x%X]', [adsErr, adsErr]);
end;

Release variable handles

////////////////////////////////////////////////////////////////////////////
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
    if client <> 0 then
    begin
        ReleaseHandleList();
        AdsPortClose();// close ads port
    end;
end;
////////////////////////////////////////////////////////////////////////////
procedure TForm1.ReleaseHandleList();
var igList, ioList, lenList : Array of Longword;
    srcList : Array of Pointer;
    resList : Array of Longint;
    i, adsErr : Longint;
begin
    SetLength(igList, Length(infos));
    SetLength(ioList, Length(infos));
    SetLength(lenList, Length(infos));
    SetLength(srcList, Length(infos));
    SetLength(resList, Length(infos));
    for i:= Low(infos) to High(infos) do
    begin
        igList[i] := ADSIGRP_RELEASE_SYMHND;
        ioList[i] := $00000000;
        lenList[i] := SizeOf(infos[i].handle);
        srcList[i] := @infos[i].handle;
        resList[i] := 0;
    end;
    adsErr := AdsSyncSumWriteReq(  @amsAddr,
                                   igList,
                                   ioList,
                                   lenList,
                                   srcList,
                                   resList );
    if adsErr = 0 then
    begin
        for i:= Low(resList) to High(resList) do
        begin
            if resList[i] = 0 then // release successfull
                infos[i].handle := 0// set to null
            else
                ShowMessageFmt('AdsSyncSumWriteReq(ADSIGRP_RELEASE_SYMHND) error: %d [0x%X], handle:0x%X', [resList[i], resList[i], infos[i].handle]);
            ListBox1.Items.Add(Format('RELEASE name: %s, result:%d [0x%X], handle:0x%X', [infos[i].name, resList[i], resList[i], infos[i].handle] ));
        end;
    end
    else
        ShowMessageFmt('AdsSyncSumWriteReq() error: %d [0x%X]', [adsErr, adsErr]);
end;

Read values "by name"

////////////////////////////////////////////////////////////////////////////
procedure TForm1.Button1Click(Sender: TObject);
begin
    ListBox1.Items.Clear();
    ReadListByHandle();
end;
////////////////////////////////////////////////////////////////////////////
procedure TForm1.ReadListByHandle();
var igList, ioList, lenList : Array of Longword;
    destList : Array of Pointer;
    resList : Array of Longint;
    i, adsErr : Longint;
    b : Longint;
    sValue : String;
    cbValue : Longint;
begin
    SetLength(igList, Length(infos));
    SetLength(ioList, Length(infos));
    SetLength(lenList, Length(infos));
    SetLength(destList, Length(infos));
    SetLength(resList, Length(infos));
    for i:= Low(infos) to High(infos) do
    begin
        igList[i] := ADSIGRP_SYM_VALBYHND;
        ioList[i] := infos[i].handle;
        lenList[i] := Length(infos[i].data){ * SizeOf(infos[i].data[0])};
        destList[i] := @infos[i].data[0];
        resList[i] := 0;
    end;
    adsErr := AdsSyncSumReadReq(  @amsAddr,
                                   igList,
                                   ioList,
                                   lenList,
                                   destList,
                                   resList );
    if adsErr = 0 then
    begin
        for i:= Low(resList) to High(resList) do
        begin
            if resList[i] = 0 then
            begin
                //TODO: save read data...
                //Convert data to hex string and show it on the form
                sValue := '';
                cbValue := Length(infos[i].data){ * SizeOf(infos[i].data[0])};
                if cbValue > 0 then
                    for b:= 0 to (cbValue - 1) do
                        sValue := sValue + IntToHex( infos[i].data[b], 2 ) + ' ' ;
                ListBox1.Items.Add(Format('READ name: %s, value:%s', [infos[i].name, sValue] ));
            end
            else
                ShowMessageFmt('AdsSyncSumReadReq(ADSIGRP_SYM_VALBYHND) error: %d [0x%X], handle:0x%X', [resList[i], resList[i], infos[i].handle]);
        end;
    end
    else
        ShowMessageFmt('AdsSyncSumReadReq() error: %d [0x%X]', [adsErr, adsErr]);
end;

end.

Language / IDE | Unpack the sample program
Delphi XE2 | Sample08.exe
Delphi 6 or higher (classic) | Sample08.exe


TcAdsDll Delphi Example09

TwinCAT TcAdsDLL: Delphi examples

Reading and writing of DT/DATE/TOD/TIME variables

Requirements:

  • TwinCAT v2.11 Build >= 2034;
  • Delphi 7.0 + + update 7.1 or higher;
  • TcAdsDLL.DLL;
  • TcAdsDEF.pas and TcAdsAPI.pas, archived in the file delphi_adsdll_api_units.zip if you wish to translate the source text yourself;

The task

The date and/or time and a duration should be written from the Delphi application to the TwinCAT PLC or read from the TwinCAT PLC into the Delphi application. When the values are written to the PLC, the time and/or date set in the visual component TDateTimePicker is first converted to the appropriate PLC format and then written into the appropriate PLC variable. The values read from the TwinCAT PLC are converted to the appropriate Delphi format and indicated by the TDateTimePicker component. The time duration should be indicated in milliseconds and editable via TEdit control.

The data types available in the Delphi application include the following:

  • TDateTime: 64 bit floating point number. Contains the date (integer part) and the time (fraction). The integer part represents the number of days that have past since 30 December 1899. The fraction indicates the time (fraction of a 24-hour day). Through coding of the floating-point number the resolution is smaller for very large values (smaller number of decimal places available).
  • TDate: 64 bit floating-point number, TDateTime alias type, contains only the date (integer part). Number of days past since 30 December 1899;
  • TTime: 64 bit floating-point number, TDateTime alias type, contains only the time (fraction of a 24-hour day);
  • Long word, 32-bit count value, in our application it should contain the time duration with millisecond resolution (no time);

The utilisation of the TDateTime data type enables the available Delphi time conversion functions to be used, particularly the two conversion functions DateTimeToUnix() and UnixToDateTime(). Instead, a 32-bit integer value or another data type can be used as well, the time and date suitably encoded, converted and then written to the PLC. The type conversion/mapping of data types described below should not be regarded as mandatory.

The following data types are available in the TwinCAT PLC:

  • DT or DATE_AND_TIME: unsigned 32-bit count value, contains the date and time, number of seconds past since 1 January 1970, resolution in seconds;
  • DATE: unsigned 32-bit count value, contains the date only, number of seconds past since 1 January 1970, resolution in seconds;
  • TOD or TIME_OF_DAY: unsigned 32-bit count value, contains only the time. Number of milliseconds past since the beginning of the day (00:00 hours), resolution in milliseconds;
  • TIME, unsigned 32-bit count value, contains the time duration (no the time), resolution in milliseconds;

During writing of the values into the TwinCAT PLC the following type conversion/mapping is carried out:

  • TDateTimePicker.DateTime property (TDateTime) -> DT or DATE_AND_TIME
  • TDateTimePicker.Date property (TDate) -> DATE
  • TDateTimePicker.Time property (TTime) -> TOD or TIME_OF_DAY
  • TEdit.text property (long word) -> time

During reading of values from the TwinCAT PLC the type conversion takes place in the other direction.

Depending on whether the Pick->Date or Pick->Time option was selected, the date or time can be set in the visual TDateTimePicker component.

The time duration in milliseconds can be set via TEdit control in the lower section.

A mouse click on the corresponding command button causes the time and/or date or the time duration to be written into the TwinCAT PLC or read from the PLC and displayed.

The PLC program

In MAIN a PLC variable was declared for each PLC type (date/time). The Delphi application should write or read the values.

PROGRAM MAIN
VAR
    vDateAndTime	: DATE_AND_TIME;
    vDate		: DATE;
    vTimeOfDay	: TIME_OF_DAY;
    vTime		: TIME;
END_VAR
;

Delphi 7 program

The declarations for the TcAdsDLL functions used are located in the Pascal units TcAdsDEF.pas and TcAdsAPI.pas. They were linked in the project via a Uses clause. The unit DateUtils contains the required time conversion functions and must also be linked.

unit Unit1;
interface
uses
  TcAdsDEF, TcAdsAPI, Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ComCtrls, Grids, Calendar, StdCtrls, DateUtils, ExtCtrls;
type
  TForm1 = class(TForm)
    Button2: TButton;
    Button3: TButton;
    StatusBar1: TStatusBar;
    Button4: TButton;
    Button5: TButton;
    Button6: TButton;
    Button7: TButton;
    Button9: TButton;
    Button8: TButton;
    Edit1: TEdit;
    UpDown1: TUpDown;
    RadioGroup1: TRadioGroup;
    DateTimePicker1: TDateTimePicker;
    Label2: TLabel;
    procedure Button2Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure Button5Click(Sender: TObject);
    procedure Button6Click(Sender: TObject);
    procedure Button7Click(Sender: TObject);
    procedure Button8Click(Sender: TObject);
    procedure Button9Click(Sender: TObject);
    procedure RadioGroup1Click(Sender: TObject);
  private
    { Private declarations }
    LocalAddr     : TAmsAddr;
    ServerAddr    : TAmsAddr;
    adsResult     : Longint;
    hDateAndTime  : Longword;
    hDate         : Longword;
    hTimeOfDay    : Longword;
    hTime         : Longword;
    function AdsCreateVarHandle( const name : AnsiString ) : Longword;
    function AdsReleaseVarHandle( hVar : Longword ) : boolean;
    function AdsWriteUI32( hVar : Longword; value : Longword ) : boolean;
    function AdsReadUI32( hVar : Longword; var value : Longword ) : boolean;
  public
    { Public declarations }
  end;
var
  Form1: TForm1;

Establishing the Ads client connection to TwinCAT PLC

During startup the Delphi application establishes an Ads client connection to the TwinCAT PLC (first runtime system, port: 801) and fetches handles for the 4 PLC variables. When the application is terminated the handles are released and the Ads client connection is closed. The code for fetching and releasing the variable handles is encapsulated in two internal help functions: AdsCreateVarHandle() and AdsReleaseVarHandle().

//////////////////////////////////////////////////////////////////
// Create ads client connection, create PLC variable handles
procedure TForm1.FormCreate(Sender: TObject);
var   ClientPort:Word;
begin
   StatusBar1.SimplePanel := true;
   ClientPort:= AdsPortOpen();
   if ClientPort = 0 then {error!}
      ShowMessage( 'AdsPortOpen() error!' )
   else  {OK}
   begin
      adsResult:=AdsGetLocalAddress( @LocalAddr );
      if adsResult = 0 then {OK}
      begin
         ServerAddr.netId:=LocalAddr.netId;{connect to the PLC on the local PC}
         ServerAddr.port:=801; {first RTS}
         StatusBar1.SimpleText:=Format('Client NetId:%d.%d.%d.%d.%d.%d  Client Port:%d,  Server Port:%d',[
                 LocalAddr.netId.b[0], LocalAddr.netId.b[1], LocalAddr.netId.b[2],
                 LocalAddr.netId.b[3], LocalAddr.netId.b[4], LocalAddr.netId.b[5],
                  ClientPort, ServerAddr.port]);
         hDateAndTime := AdsCreateVarHandle( 'MAIN.vDateAndTime' );
         hDate := AdsCreateVarHandle( 'MAIN.vDate' );
         hTimeOfDay := AdsCreateVarHandle( 'MAIN.vTimeOfDay' );
         hTime := AdsCreateVarHandle( 'MAIN.vTime' );
      end
      else
         ShowMessageFmt('AdsGetLocalAddress() error:%d', [adsResult]);
   end;
end;

Separating the Ads client connection to TwinCAT PLC

//////////////////////////////////////////////////////////////////
// Release PLC variable handles, close ads connection
procedure TForm1.FormDestroy(Sender: TObject);
begin
   AdsReleaseVarHandle( hDateAndTime );
   AdsReleaseVarHandle( hDate );
   AdsReleaseVarHandle( hTimeOfDay );
   AdsReleaseVarHandle( hTime );
   adsResult := AdsPortClose();
   if AdsResult > 0 then
      ShowMessageFmt('AdsPortClose() error:%d', [adsResult]);
end;

Fetch handle for the PLC variable

/////////////////////////////////////////////////////////////////
// Create PLC variable handle
function TForm1.AdsCreateVarHandle( const name : AnsiString ) : Longword;
var hVar : Longword;
begin
   adsResult := AdsSyncReadWriteReq( @serverAddr, ADSIGRP_SYM_HNDBYNAME, 0, sizeof(hVar), @hVar, Length(name)+1, @name[1] );
   if adsResult <> 0 then
   begin
      ShowMessageFmt( 'AdsSyncReadWriteReq(ADSIGRP_SYM_HNDBYNAME) error: %d', [adsResult] );
      AdsCreateVarHandle := 0;
   end
   else
      AdsCreateVarHandle := hVar;
end;

Releasing the PLC variable handle

//////////////////////////////////////////////////////////////////
// Release PLC variable handle
function TForm1.AdsReleaseVarHandle( hVar : Longword ) : boolean;
begin
   adsResult := AdsSyncWriteReq( @serverAddr, ADSIGRP_RELEASE_SYMHND, hVar, 0, Nil );
   if adsResult <> 0 then
      ShowMessageFmt( 'AdsSyncReadWriteReq(ADSIGRP_RELEASE_SYMHND) error: %d', [adsResult] );
   AdsReleaseVarHandle := adsResult = 0;
end;

Writing value into the PLC (32-bit unsigned integer)

In the TwinCAT PLC the time data types are stored/mapped as unsigned 32-bit count values. After conversion into the time format of the PLC the values should be written to the PLC as unsigned 32-bit numbers. The code for writing and reading of the 32-bit values is encapsulated in two internal help functions: AdsWriteUI32() and AdsReadUI32().

//////////////////////////////////////////////////////////////////
// Write unsigned 32 bit integer value to the PLC
function TForm1.AdsWriteUI32( hVar : Longword; value : Longword ) : boolean;
begin
   adsResult := AdsSyncWriteReq( @ServerAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(value), @value );
   if adsResult <> 0 then
      ShowMessageFmt('AdsSyncWriteReq(ADSIGRP_SYM_VALBYHND) error:%d', [adsResult]);
   AdsWriteUI32 := adsResult = 0;
end;

Reading value from the PLC (32-bit unsigned integer)

//////////////////////////////////////////////////////////////////
// Read unsigned 32 bit integer value from the PLC
function TForm1.AdsReadUI32( hVar : Longword; var value : Longword ) : boolean;
begin
   adsResult := AdsSyncReadReq( @ServerAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(value), @value );
   if adsResult <> 0 then
      ShowMessageFmt('AdsSyncReadReq(ADSIGRP_SYM_VALBYHND) error:%d', [adsResult]);
   AdsReadUI32 := adsResult = 0;
end;

Writing DATE_AND_TIME to the PLC

//////////////////////////////////////////////////////////////////
// Convert TDateTime -> DATE_AND_TIME
// Write DATE_AND_TIME value to the PLC
procedure TForm1.Button2Click(Sender: TObject);
var
   seconds : Longword;
   unix : Int64;
   dt : TDateTime;
begin
   // Get TDateTime value from TDateTimePicker component
   dt := DateTimePicker1.DateTime;
   // Truncate milliseconds (prevents from rounding up)
   dt := RecodeMillisecond( dt, 0 );
   // Convert TDateTime -> Unix time, 64 bit, seconds since 1.1.1970-00:00:00
   unix := DateTimeToUnix( dt );
   // Convert 64 bit -> 32 bit, seconds since 1.1.1970-00:00:00
   seconds := unix;
   // Wite DATE_AND_TIME value to the PLC
   AdsWriteUI32( hDateAndTime, seconds );
end;

Reading DATE_AND_TIME from the PLC

//////////////////////////////////////////////////////////////////
// Read DATE_AND_TIME value from the PLC
// Convert DATE_AND_TIME -> TDateTime
procedure TForm1.Button3Click(Sender: TObject);
var
   seconds : Longword;
   unix : Int64;
   dt : TDateTime;
begin
   // Read DATE_AND_TIME value from PLC, 32 bit, seconds since 1.1.1970-00:00:00
   if AdsReadUI32( hDateAndTime, seconds ) then
   begin
      // Create Unix time, 64 bit, seconds since 1.1.1970-00:00:00
      unix := seconds;
      // Convert Unix time -> TDateTime
      dt := UnixToDateTime( unix );
      // Assign TDateTime value to the TDateTimePicker component
      DateTimePicker1.DateTime := dt;
   end;
end;

Writing DATE to the PLC

//////////////////////////////////////////////////////////////////
// Convert TDate -> DATE
// Write DATE value to the PLC
procedure TForm1.Button4Click(Sender: TObject);
var
   seconds : Longword;
   unix  : Int64;
   d : TDate;
begin
   // Get TDate value from TDateTimePicker component
   d := DateOf( DateTimePicker1.DateTime );

   // Convert TDate -> Unix time, 64 bit, seconds since 1.1.1970-00:00:00
   unix := DateTimeToUnix( d );
   // Convert 64 bit -> 32 bit, seconds since 1.1.1970-00:00:00
   seconds := unix;
   // Wite DATE value to the PLC
   AdsWriteUI32( hDate, seconds );
end;

Reading DATE from the PLC

//////////////////////////////////////////////////////////////////
// Read DATE value from the PLC
// Convert DATE -> TDate
procedure TForm1.Button5Click(Sender: TObject);
var
   seconds : Longword;
   unix  : Int64;
   d : TDate;
begin
   // Read DATE value from PLC, 32 bit, seconds since 1.1.1970-00:00:00
   if AdsReadUI32( hDate, seconds ) then
   begin
      // Create Unix time, 64 bit, seconds since 1.1.1970-00:00:00
      unix := seconds;
      // Convert Unix time -> TDate
      d := DateOf( UnixToDateTime( unix ) );
      // Assign TDate value to the TDateTimePicker component
      DateTimePicker1.Date := d;
   end;
end;

Writing TIME_OF_DAY to the PLC

//////////////////////////////////////////////////////////////////
// Convert TTime -> TIME_OF_DAY
// Write TIME_OF_DAY value to the PLC
procedure TForm1.Button6Click(Sender: TObject);
var
   milliseconds : Longword;
   t : TTime;
begin
   // Get TTime value from TDateTimePicker component
   t := TimeOf(DateTimePicker1.DateTime);
   // Truncate milliseconds (prevents from rounding up)
   t := RecodeMillisecond( t, 0 );
   // Convert TTime -> Milliseconds of the day, 32 bit
   milliseconds := MillisecondOfTheDay( t );
   // Write TIME_OF_DAY value to the PLC
   AdsWriteUI32( hTimeOfDay, milliseconds );
end;

Reading TIME_OF_DAY from the PLC

//////////////////////////////////////////////////////////////////
// Read TIME_OF_DAY value from the PLC
// Convert TIME_OF_DAY -> TTime
procedure TForm1.Button7Click(Sender: TObject);
var
   milliseconds : Longword;
   unix : Int64;
   t   : TTime;
begin
   // Read TIME_OF_DAY value from PLC, 32 bit, milliseconds of the day
   if AdsReadUI32( hTimeOfDay, milliseconds ) then
   begin
      // Create Unix time, 64 bit, seconds since 1.1.1970-00:00:00
      unix := milliseconds Div 1000;
      // Convert Unix time -> TTime
      t := TimeOf( UnixToDateTime( unix ) );
      // Assign TTime value to the TDateTimePicker component
      DateTimePicker1.Time := t;
   end;
end;

Writing TIME (time duration) to the PLC

//////////////////////////////////////////////////////////////////
// Write TIME value to the PLC
procedure TForm1.Button8Click(Sender: TObject);
var milliseconds : Longword;
begin
   milliseconds := UpDown1.Position;
   AdsWriteUI32( hTime, milliseconds );
end;

Reading TIME (time duration) from the PLC

//////////////////////////////////////////////////////////////////
// Read TIME value from the PLC
procedure TForm1.Button9Click(Sender: TObject);
var milliseconds : Longword;
begin
   if AdsReadUI32( hTime, milliseconds ) then
      UpDown1.Position := milliseconds;
end;

end.

Language / IDE | Unpack the sample program
Delphi XE2 | Sample09.exe
Delphi 7 or higher (classic) | Sample09.exe


Delphi TcAdsDll Sample: Linking to Delphi XE2 (COM interface)

TwinCAT TcAdsDLL: Delphi examples

Linking to Delphi XE2 (COM interface)

The TcAdsDll.dll type library has to be imported (TcAdsDll_TLB.pas unit). Depending on the Delphi version, manual adjustments may be required in order to ensure error-free translation.

Requirements

  • Delphi XE2;
  • TwinCAT 2.9 or higher;
  • TcAdsDll.dll - dynamic function library located in the Windows NT/2000 /XP/... 'System32' directory.

Import Type Library

1. Select Component -> Import component menu command. In dialog window select the option: Import type library and confirm with the Next button.

2. In the dialogue window that will then open, mark the Beckhoff TcAdsDll x.x Type Library, and confirm with the Next button.

3. In the dialogue window that will then open confirm with the Next button.

4. Select the Create unit option and confirm with the Finish button.

New unit is generated in the .../RAD Studio/9.0/Imports folder. Include this unit in your Application.


Delphi TcAdsDll Sample: Linking to Delphi (COM interface)

TwinCAT TcAdsDLL: Delphi examples

Linking to Delphi (COM interface)

The TcAdsDll.dll type library has to be imported (TcAdsDll_TLB.pas unit). Depending on the Delphi version, manual adjustments may be required in order to ensure error-free translation.

Requirements

  • TcAdsDll.dll - dynamic function library located in the Windows NT/2000 'System32' directory.
  • Delphi 5.0 or higher;
  • TwinCAT 2.9 or higher;

Import Type Library

1. Select Project -> Import Type Library menu command.

2. In the dialogue window that will then open, mark the Beckhoff TcAdsDll x.x Type Library, and confirm with Create Unit...

New unit is generated in the .../Delphi 5/Imports folder. Include this unit in your Application.


TcAdsDll Delphi Sample01: Reading and writing of PLC variables

TwinCAT TcAdsDLL: Delphi examples

Reading and writing of PLC variables

Requirements:

  • Delphi 5.0 or higher;
  • TwinCAT 2.9 or higher;
  • The TcAdsDll.dll type library has to be imported (TcAdsDll_TLB.pas unit). Depending on the Delphi version, manual adjustments may be required in order to ensure error-free translation.

This sample program demonstrates how the TcAdsDll COM interfaces can be addressed by Delphi for reading variables from the PLC or writing to the PLC.

In the FormCreate event function a connection to the server in the local system is established via CoTcClient.Create. The Connect method is then used to establish a connection to the first PLC runtime system (port 801) on the local computer (netid = '0.0.0.0.0.0').

The variables are read from the PLC by pressing the Read button and written to the PLC by pressing the Write button.

The Delphi program

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ExtCtrls, StdCtrls, TcAdsDll_TLB, OleServer, ComObj;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    Edit2: TEdit;
    Edit3: TEdit;
    Edit4: TEdit;
    Edit5: TEdit;
    Edit6: TEdit;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    Label5: TLabel;
    Label6: TLabel;
    Bevel1: TBevel;
    Button1: TButton;
    Button2: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private-Deklarationen }
    ads     : ITcAdsSync;
    client  : ITcClient;
    netId   : AmsNetId;
    cbRead  : integer;
    valBool : Byte;     // PLCBoolVar
    valInt  : Smallint; // PLCIntVar
    valDint : Longint;  // PLCDIntVar
    valReal : Single;   // PLCRealVar
    valLReal : Double;  // PLCLRealVar
    valString : AnsiString;// PLCStringVar
  public
    { Public-Deklarationen }
  end;
var
  Form1: TForm1;

implementation
{$R *.DFM}
/////////////////////////////////////////////////////////////////////////
procedure TForm1.FormCreate(Sender: TObject);
begin
    netId.b[0] := 0;  // 172
    netId.b[1] := 0;  // 16
    netId.b[2] := 0;  // 4
    netId.b[3] := 0;  // 23
    netId.b[4] := 0;  // 1
    netId.b[5] := 0;  // 1
    try
        client := CoTcClient.Create;
        try
            OleCheck(client.Connect( netid, 801, ads ));
        except
            ShowMessage('client.Connect() failed!');
        end;
    except
        ShowMessage('CoTcClient.Create() failed!');
    end;
end;
/////////////////////////////////////////////////////////////////////////
procedure TForm1.Button1Click(Sender: TObject);
begin
    SetLength( valString, 256);// make sure the string >= SIZEOF(PLCStringVar) variable
    try
        OleCheck(ads.Read($4020, 0, sizeof(valBool), cbRead, PByte(@valBool)^ ));
        OleCheck(ads.Read($4020, 2, sizeof(valInt), cbRead, PByte(@valInt)^ ));
        OleCheck(ads.Read($4020, 4, sizeof(valDint), cbRead, PByte(@valDint)^ ));
        OleCheck(ads.Read($4020, 8, sizeof(valReal), cbRead, PByte(@valReal)^ ));
        OleCheck(ads.Read($4020, 12, sizeof(valLReal), cbRead, PByte(@valLReal)^ ));
        OleCheck(ads.Read($4020, 100, Length(valString) + 1, cbRead, PByte(@valString[1])^ ));
    except
        ShowMessage('ads.Read(...) failed!');
    end;
    Edit1.Text := IntToStr( valBool );
    Edit2.Text := IntToStr( valInt );
    Edit3.Text := IntToStr( valDint );
    Edit4.Text := FloatToStr( valReal );
    Edit5.Text := FloatToStr( valLReal );
    Edit6.Text := String(valString);
end;
/////////////////////////////////////////////////////////////////////////
procedure TForm1.Button2Click(Sender: TObject);
begin
    valBool := StrToInt( Edit1.Text );
    valInt := StrToInt( Edit2.Text );
    valDint := StrToInt( Edit3.Text );
    valReal := StrToFloat( Edit4.Text );
    valLReal := StrToFloat( Edit5.Text );
    valString := AnsiString( Edit6.Text );
    if valString = '' then
        valString := #00;
    try
        OleCheck(ads.Write($4020, 0, sizeof(valBool), PByte(@valBool)^ ));
        OleCheck(ads.Write($4020, 2, sizeof(valInt), PByte(@valInt)^ ));
        OleCheck(ads.Write($4020, 4, sizeof(valDint), PByte(@valDint)^ ));
        OleCheck(ads.Write($4020, 8, sizeof(valReal), PByte(@valReal)^ ));
        OleCheck(ads.Write($4020, 12, sizeof(valLReal), PByte(@valLReal)^ ));
        OleCheck(ads.Write($4020, 100, Length(valString) + 1, PByte(@valString[1])^));
    except
        ShowMessage('ads.Write(...) failed!');
    end;
end;

initialization
    IsMultiThread := True;

end.

PLC program

PROGRAM MAIN
VAR
  PLCBoolVar AT %MX0.0 : BOOL := TRUE;
  PLCIntVar AT %MB2 : INT := 75;
  PLCDIntVar AT %MB4 : DINT := 43234532;
  PLCRealVar AT %MB8 : REAL := 3.1415;
  PLCLRealVar AT %MB12 : LREAL := 3.141592654;
  PLCStringVar AT %MB100: STRING := '12345';
END_VAR

;

Language / IDE | Unpack the sample program
Delphi XE2 | Sample01.exe
Delphi 5 or higher (classic) | Sample01.exe


TcAdsDll Delphi Sample02: Event-driven reading of PLC variables

TwinCAT TcAdsDLL: Delphi examples

Event-driven reading of PLC variables

Requirements:

  • Delphi 5.0 or higher;
  • TwinCAT 2.9 or higher;
  • The TcAdsDll.dll type library has to be imported (TcAdsDll_TLB.pas unit). Depending on the Delphi version, manual adjustments may be required in order to ensure error-free translation.

In the FormCreate event function a connection to the server in the local system is established via CoTcClient.Create. The Connect method is then used to establish a connection to the first PLC runtime system (port 801) on the local computer (netid = '0.0.0.0.0.0'). When the Start button is pressed, a connection to the PLC variables is established via the AddDeviceNotification method. If a PLC variable changes, the server calls the DeviceNotification function of the _ITcAdsSyncEvents interface. In order to be able to send events to the client, this interface must be implemented in the client. In addition, the server has to be informed about their existence. This is achieved by calling the InterfaceConnect procedure. The connections are cancelled via the DelDeviceNotification method by pressing the Stop button.

The Delphi program

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ExtCtrls, StdCtrls, TcAdsDll_TLB, OleServer, ComObj,activex, Math;

type
  TForm1 = class( TForm, IUnknown, _ITcAdsSyncEvents )
    Label1: TLabel;
    Label2: TLabel;
    Button1: TButton;
    Button2: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    TcClient          : ITcClient;
    TcAdsSync         : ITcAdsSync;
    dwCookie          : integer;
    hInteger, hDouble   : integer;
    serverAddr      : AmsNetId;
    function  DeviceNotification(var pTime: TimeStamp; hNotification: Integer; cbLen: Integer; var pData: Byte): HResult; stdcall;
  end;
type
    PINT = ^Smallint;
    PLREAL= ^Double;
var
  Form1: TForm1;

implementation
{$R *.DFM}
/////////////////////////////////////////////////////////////////////////
procedure TForm1.FormCreate(Sender: TObject);
begin
     serverAddr.b[0] := 0;
     serverAddr.b[1] := 0;
     serverAddr.b[2] := 0;
     serverAddr.b[3] := 0;
     serverAddr.b[4] := 0;
     serverAddr.b[5] := 0;
     Button1.Enabled := True;
     Button2.Enabled := False;

     try
        TcClient := CoTcClient.Create();
        try
            OleCheck(TcClient.Connect( serverAddr, 801,TcAdsSync));
            InterfaceConnect(TcAdsSync ,IID__ITcAdsSyncEvents,self as IUnknown, dwCookie);
        except
            ShowMessage('TcClient.Connect() failed!');
        end;
     except
        ShowMessage('CoTcClient.Create() failed!');
     end;
end;
/////////////////////////////////////////////////////////////////////////
procedure TForm1.FormDestroy(Sender: TObject);
begin
    if hInteger <> 0 then
        OleCheck(TcAdsSync.DelDeviceNotification(hInteger));

    if hDouble <> 0 then
        OleCheck(TcAdsSync.DelDeviceNotification(hDouble));

     InterfaceDisconnect(TcAdsSync,IID__ITcAdsSyncEvents, dwCookie);
end;
/////////////////////////////////////////////////////////////////////////
procedure TForm1.Button1Click(Sender: TObject);
begin
    hInteger := 0;
    hDouble := 0;
    try
        OleCheck(TcAdsSync.AddDeviceNotification($4020, 2, sizeof(Smallint), ADSTRANS_SERVERONCHA, 0, 1000000, hInteger ));
        OleCheck(TcAdsSync.AddDeviceNotification($4020, 12, sizeof(Double), ADSTRANS_SERVERONCHA, 0, 1000000, hDouble ));
        Button1.Enabled := False;
        Button2.Enabled := True;
    except
        ShowMessage('TcAdsSync.AddDeviceNotification() failed!');
    end;
end;
/////////////////////////////////////////////////////////////////////////
procedure TForm1.Button2Click(Sender: TObject);
begin
    try
        OleCheck(TcAdsSync.DelDeviceNotification(hInteger));
        OleCheck(TcAdsSync.DelDeviceNotification(hDouble));
        Button1.Enabled := True;
        Button2.Enabled := False;
    except
        ShowMessage('TcAdsSync.DelDeviceNotification() failed!');
    end;
    hInteger := 0;
    hDouble := 0;
end;
/////////////////////////////////////////////////////////////////////////
function  TForm1.DeviceNotification(var pTime: TimeStamp; hNotification: Integer; cbLen: Integer; var pData: Byte): HResult; stdcall;
var
    SystemTime, LocalTime   : _SYSTEMTIME;
    TimeZoneInformation     : _TIME_ZONE_INFORMATION;
    stamp                   : TDateTime;
begin
    FileTimeToSystemTime(_FILETIME(pTime), SystemTime);
    GetTimeZoneInformation(TimeZoneInformation);
    SystemTimeToTzSpecificLocalTime(@TimeZoneInformation, SystemTime, LocalTime);
    stamp := SystemTimeToDateTime(LocalTime);

    if hNotification = hInteger then
       Label1.Caption := Format( 'PLCIntVar, %s %s, hNot:%d, cbLen:%d, data:%d',[DateToStr(stamp), TimeToStr(stamp), hNotification, cbLen, PINT(Addr(pData))^] )
    else
       Label2.Caption := Format( 'PLCLRealVar, %s %s, hNot:%d, cbLen:%d, data:%f',[DateToStr(stamp), TimeToStr(stamp), hNotification, cbLen, PLREAL(Addr(pData))^] );

    Result := S_OK;
end;

initialization
    IsMultiThread := True;

end.

PLC program

PROGRAM MAIN
VAR
  PLCBoolVar AT %MX0.0 : BOOL := TRUE;
  PLCIntVar AT %MB2 : INT := 75;
  PLCDIntVar AT %MB4 : DINT := 43234532;
  PLCRealVar AT %MB8 : REAL := 3.1415;
  PLCLRealVar AT %MB12 : LREAL := 3.141592654;
END_VAR
PLCLRealVar := PLCLRealVar +1;
PLCIntVar :=PLCIntVar +1;

Language / IDE | Unpack the sample program
Delphi XE2 | Sample02.exe
Delphi 5 or higher (classic) | Sample02.exe


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;


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 ADS.NET Sample01

TwinCAT Sample - Delphi - ADS .NET

Accessing an array in the PLC

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.

Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) program

namespace Sample01;

interface

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

type
  /// <summary>
  /// Summary description for MainForm.
  /// </summary>
  MainForm = partial class(System.Windows.Forms.Form)
  private
    method MainForm_Load(sender: System.Object; e: System.EventArgs);
    method MainForm_FormClosing(sender: System.Object; e: System.Windows.Forms.FormClosingEventArgs);
    method btnRead_Click(sender: System.Object; e: System.EventArgs);
    tcClient  : TcAdsClient;
    hVar      : Int32;
  protected
    method Dispose(disposing: Boolean); override;
  public
    // PLCVar : ARRAY [0..99] OF INT; array of 2 byte signed integer
    const MAX_ARRAY_ELEMENTS = 100;// Max. number of PLC array elements
    const MAX_ADSSTREAM_SIZE = MAX_ARRAY_ELEMENTS * 2;// Max. byte length of array data
    constructor;
  end;

implementation

{$REGION Construction and Disposition}
constructor MainForm;
begin
  //
  // Required for Windows Form Designer support
  //
  InitializeComponent();

  //
  // TODO: Add any constructor code after InitializeComponent call
  //
end;

method MainForm.Dispose(disposing: Boolean);
begin
  if disposing then begin
    if assigned(components) then
      components.Dispose();

    //
    // TODO: Add custom disposition code here
    //
  end;
  inherited Dispose(disposing);
end;
{$ENDREGION}

method MainForm.MainForm_Load(sender: System.Object; e: System.EventArgs);
begin
  try
    // Create instance of class TcAdsClient
    tcClient := new TcAdsClient();
    // Create connection with Port 801 on local PC
    tcClient.Connect(801);
    // Create PLC variable handle
    hVar := tcClient.CreateVariableHandle('MAIN.PLCVar')
  except
    on err: Exception do
      MessageBox.Show(err.Message, err.Source)
  end;
end;

method MainForm.MainForm_FormClosing(sender: System.Object; e: System.Windows.Forms.FormClosingEventArgs);
begin
  try
    // Release PLC variable handle
    tcClient.DeleteVariableHandle(hVar)
  except
    on err: Exception do
      MessageBox.Show(err.Message, err.Source)
  end;
  // Close connection
  tcClient.Dispose();
end;

method MainForm.btnRead_Click(sender: System.Object; e: System.EventArgs);
begin
  try
    // Create stream which gets the data
    var dataStream := new AdsStream(MAX_ADSSTREAM_SIZE);
    // Create BinaryReader
    var binRead := new BinaryReader(dataStream);
    // Read complete array from PLC to stream
    tcClient.Read(hVar,dataStream);
    // Clear list box
    lbArray.Items.Clear();
    // Reset stream position
    dataStream.Position := 0;
    // Read all data from stream, convert to string and add list box items
    for I:Integer := 1 to MAX_ARRAY_ELEMENTS do
      lbArray.Items.Add(binRead.ReadInt16().ToString());
  except
      on err: Exception do
        MessageBox.Show(err.Message, err.Source)
  end;
end;

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

Language / IDE | Unpack the sample program
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample01.exe
Delphi for .NET (Borland Developer Studio 2006) | Sample01.exe


Delphi ADS.NET Sample02

TwinCAT Sample - Delphi - ADS .NET

Transmitting a structure to the PLC

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

The AdsStream 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.

Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) program

namespace Sample02;

interface

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

type
  /// <summary>
  /// Summary description for MainForm.
  /// </summary>
  MainForm = partial class(System.Windows.Forms.Form)
  private
    method btnWrite_Click(sender: System.Object; e: System.EventArgs);
    method MainForm_Load(sender: System.Object; e: System.EventArgs);
    method MainForm_FormClosing(sender: System.Object; e: System.Windows.Forms.FormClosingEventArgs);
    tcClient  : TcAdsClient;
    hVar      : Integer;
  protected
    method Dispose(disposing: Boolean); override;
  public
    // TYPE PLCStruct :
    // STRUCT
    //    intVal	: INT; 2 byte, signed integer
    //    dintVal	: DINT; 4 byte, signed integer
    //    sintVal	: SINT; 1 byte, signed integer
    //    lrealVal: LREAL; 8 byte, floating point value
    //    realVal	: REAL; 4 byte, floating point value
    // END_STRUCT
    // END_TYPE
    // Length of all PLC structure members = 19 byte (x86, byte alignment)
    const MAX_ADSSTREAM_SIZE = 19;// SIZEOF(PLCStruct), Max. byte length of PLC structure
    constructor;
  end;

implementation

{$REGION Construction and Disposition}
constructor MainForm;
begin
  //
  // Required for Windows Form Designer support
  //
  InitializeComponent();

  //
  // TODO: Add any constructor code after InitializeComponent call
  //
end;

method MainForm.Dispose(disposing: Boolean);
begin
  if disposing then begin
    if assigned(components) then
      components.Dispose();

    //
    // TODO: Add custom disposition code here
    //
  end;
  inherited Dispose(disposing);
end;
{$ENDREGION}

method MainForm.btnWrite_Click(sender: System.Object; e: System.EventArgs);
begin
  try
    // Create stream which holds the data
    var dataStream := new AdsStream(MAX_ADSSTREAM_SIZE);
    // Create BinaryWriter
    var binWrite := new BinaryWriter(dataStream);
    // Reset stream to start position
    dataStream.Position := 0;
    // Write structure member data to the stream
    binWrite.Write(System.Int16.Parse(tbInt.Text));
    binWrite.Write(System.Int32.Parse(tbDint.Text));
    binWrite.Write(System.SByte.Parse(tbSInt.Text));
    binWrite.Write(System.Double.Parse(tbLReal.Text));
    binWrite.Write(System.Single.Parse(tbReal.Text));
    // Write stream to the PLC
    tcClient.Write(hVar,dataStream)
  except
    on err: Exception do
      MessageBox.Show(err.Message, err.Source)
  end;
end;

method MainForm.MainForm_FormClosing(sender: System.Object; e: System.Windows.Forms.FormClosingEventArgs);
begin
  try
    // Release PLC variable handle
    tcClient.DeleteVariableHandle(hVar)
  except
    on err: Exception do
      MessageBox.Show(err.Message, err.Source)
  end;
  // Close connection
  tcClient.Dispose();
end;

method MainForm.MainForm_Load(sender: System.Object; e: System.EventArgs);
begin
  try
    // Create instance of class TcAdsClient
    tcClient := new TcAdsClient();
    // Create connection with Port 801 on local PC
    tcClient.Connect(801);
    // Create PLC variable handle
    hVar := tcClient.CreateVariableHandle('MAIN.PLCVar')
  except
    on err: Exception do
      MessageBox.Show(err.Message, err.Source)
  end;
end;

end.

PLC program

TYPE PLCStruct :
STRUCT
    intVal : INT;
    dintVal : DINT;
    sintVal : SINT;
    lrealVal : LREAL;
    realVal : REAL;
END_STRUCT
END_TYPE
PROGRAM MAIN
VAR
    PLCVar:PLCStruct;
END_VAR

Language / IDE | Unpack the sample program
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample02.exe
Delphi for .NET (Borland Developer Studio 2006) | Sample02.exe


Delphi ADS.NET Sample03

TwinCAT Sample - Delphi - ADS .NET

Event driven reading

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 MainForm_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 MainForm_FormClosing 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()).

Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) program

namespace Sample03;

interface

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

type
  /// <summary>
  /// Summary description for MainForm.
  /// </summary>
  MainForm = partial class(System.Windows.Forms.Form)
  private
    method tcClient_AdsNotification(sender: Object; e: AdsNotificationEventArgs);
    method MainForm_Load(sender: System.Object; e: System.EventArgs);
    method MainForm_FormClosing(sender: System.Object; e: System.Windows.Forms.FormClosingEventArgs);
    tcClient    : TcAdsClient;
    hConnect    : Array[0..6] of Integer;
        dataStream  : AdsStream;
        binRead     : BinaryReader;
  protected
    method Dispose(disposing: Boolean); override;
  public
    // boolVal	: BOOL; 1 byte, unsigned integer
    // intVal	: INT; 2 byte, signed integer
    // dintVal	: DINT; 4 byte, signed integer
    // sintVal	: SINT; 1 byte, signed integer
    // lrealVal	: LREAL; 8 byte, floating point value
    // realVal	: REAL; 4 byte, floating point value
    // stringVal: STRING(10); 11 byte, string length inclusive terminating zero character
    // Length of the stream = 31 byte, Byte length of all PLC variables
    const MAX_ADSSTREAM_SIZE = 31;

    // max. length of PLC string variable without terminating zero character
    const MAX_STRINGVAL_LENGTH = 10;

    constructor;
  end;

implementation

{$REGION Construction and Disposition}
constructor MainForm;
begin
  //
  // Required for Windows Form Designer support
  //
  InitializeComponent();

  //
  // TODO: Add any constructor code after InitializeComponent call
  //
end;

method MainForm.Dispose(disposing: Boolean);
begin
  if disposing then begin
    if assigned(components) then
      components.Dispose();

    //
    // TODO: Add custom disposition code here
    //
  end;
  inherited Dispose(disposing);
end;
{$ENDREGION}

method MainForm.MainForm_Load(sender: System.Object; e: System.EventArgs);
begin
  try
    // Creaste instance of class TcAdsClient
    tcClient := new TcAdsClient();
    // Create connectin with Port 801 on the local PC
    tcClient.Connect(801);
    // Length of the stream = Byte length of all PLC variables
    dataStream := new AdsStream(MAX_ADSSTREAM_SIZE);
    // Encoding is set to ASCII, to read strings
    binRead := new BinaryReader(dataStream, System.Text.Encoding.ASCII);
    // Add notifications
      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, MAX_STRINGVAL_LENGTH + 1, AdsTransMode.OnChange, 100, 0, tbString);
    // Add notification event handler
    tcClient.AdsNotification += tcClient_AdsNotification
  except
    on err: Exception do
      MessageBox.Show(err.Message, err.Source)
  end;
end;

method MainForm.MainForm_FormClosing(sender: System.Object; e: System.Windows.Forms.FormClosingEventArgs);
begin
  try
    // Release notifications
    for i : Integer := 0 to 6 do
      tcClient.DeleteDeviceNotification(hConnect[i]);
    // Remove notification handler
    tcClient.AdsNotification -= tcClient_AdsNotification;
  except
    on err: Exception do
      MessageBox.Show(err.Message, err.Source)
  end;
  // Close connection
  tcClient.Dispose();
end;

method MainForm.tcClient_AdsNotification(sender: Object; e: AdsNotificationEventArgs);
begin
  var strValue : String := '';
  // Convert PLC time stamp to DateTime type
  var time := DateTime.FromFileTime(e.TimeStamp);
  // Get notification data (PLC value) byte offset
  e.DataStream.Position := e.Offset;

  // Convert PLC value to string
  if ( e.NotificationHandle = hConnect[0]) then
      strValue := binRead.ReadBoolean().ToString()
  else if( e.NotificationHandle = hConnect[1] ) then
      strValue := binRead.ReadInt16().ToString()
  else if( e.NotificationHandle = hConnect[2] ) then
    strValue := binRead.ReadInt32().ToString()
  else if( e.NotificationHandle = hConnect[3] ) then
    strValue := binRead.ReadSByte().ToString()
  else if( e.NotificationHandle = hConnect[4] ) then
    strValue := binRead.ReadDouble().ToString()
  else if( e.NotificationHandle = hConnect[5] ) then
    strValue := binRead.ReadSingle().ToString()
  else if ( e.NotificationHandle = hConnect[6] ) then
  begin
    strValue := new String(binRead.ReadChars(MAX_STRINGVAL_LENGTH + 1));
    // Search for terminating zero character and cut at found position
    // Necessary if you want to compare the string to other strings
    strValue := strValue.Substring(0, strValue.IndexOf(Char(0)));
  end
  else
    ;

  TextBox(e.UserData).Text := System.String.Format('DateTime: {0}, {1}ms; {2}', [time, time.Millisecond, strValue]);
end;

end.

PLC program

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

Language / IDE | Unpack the sample program
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample03.exe
Delphi for .NET (Borland Developer Studio 2006) | Sample03.exe


Delphi ADS.NET Sample04

TwinCAT Sample - Delphi - ADS .NET

Reading and writing of string variables

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 MainForm_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).

Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) program

namespace Sample04;

interface

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

////////////////////////////////////////////////////////////////////////
// Define OLD_ADSNET_INTF only if you are using Ads.NET version < 1.0.0.10
// From Ads.NET version >= 1.0.0.10 and higher the classes
// AdsBinaryReader and AdsBinaryWriter can be used to read and write strings
// {$DEFINE OLD_ADSNET_INTF}
////////////////////////////////////////////////////////////////////////

type
  /// <summary>
  /// Summary description for MainForm.
  /// </summary>
  MainForm = partial class(System.Windows.Forms.Form)
  private
    adsClient   : TcAdsClient;
    varHandle   : Integer;
    method MainForm_Load(sender: System.Object; e: System.EventArgs);
    method MainForm_FormClosing(sender: System.Object; e: System.Windows.Forms.FormClosingEventArgs);
    method btnRead_Click(sender: System.Object; e: System.EventArgs);
    method btnWrite_Click(sender: System.Object; e: System.EventArgs);
  protected
    method Dispose(disposing: Boolean); override;
  public
    // text : STRING[30] := 'hello';
    // Byte length of the PLC string (without terminating zero character)
    const MAX_TEXT_LENGTH  = 30;
    constructor;
  end;

implementation

{$REGION Construction and Disposition}
constructor MainForm;
begin
  //
  // Required for Windows Form Designer support
  //
  InitializeComponent();

  //
  // TODO: Add any constructor code after InitializeComponent call
  //
end;

method MainForm.Dispose(disposing: Boolean);
begin
  if disposing then begin
    if assigned(components) then
      components.Dispose();

    //
    // TODO: Add custom disposition code here
    //
  end;
  inherited Dispose(disposing);
end;
{$ENDREGION}

method MainForm.MainForm_Load(sender: System.Object; e: System.EventArgs);
begin
  try
    // Create instance of class TcAdsClient
    adsClient := new TcAdsClient();
    // Create connection with Port 801 on local PC
    adsClient.Connect(801);
    // Create PLC variable handle
    varHandle := adsClient.CreateVariableHandle('MAIN.text');
  except
    on err: Exception do
      MessageBox.Show(err.Message, err.Source);
  end;
end;

method MainForm.MainForm_FormClosing(sender: System.Object; e: System.Windows.Forms.FormClosingEventArgs);
begin
  try
    // Release PLC variable handle
    adsClient.DeleteVariableHandle(varHandle);
  except
    on err: Exception do
      MessageBox.Show(err.Message, err.Source)
  end;
  // Close connection
  adsClient.Dispose();
end;

{$IFNDEF OLD_ADSNET_INTF} // Ads.NET version >= 1.0.0.10 (new)
method MainForm.btnRead_Click(sender: System.Object; e: System.EventArgs);
begin
  try
    // Create stream to hold the data. Length of the stream = Length of string in PLC
    var dataStream := new AdsStream(MAX_TEXT_LENGTH);
    // Create AdsBinaryReader. Encoding is set to ASCII, to read strings
    var reader := new AdsBinaryReader(dataStream);
    // Read string characters from PLC to the stream
    adsClient.Read(varHandle, dataStream);
    // Read stream characters from stream
    textBox1.Text := reader.ReadPlcString(MAX_TEXT_LENGTH)
  except
    on err : Exception do
      MessageBox.Show(err.Message, err.Source)
  end;
 end;

method MainForm.btnWrite_Click(sender: System.Object; e: System.EventArgs);
begin
  try
    // Create stream to hold the data. Length of the stream = Length of string in PLC
    var dataStream := new AdsStream(MAX_TEXT_LENGTH);
    // Create AdsBinaryWriter. Encoding is set to ASCII, to read strings
    var writer := new AdsBinaryWriter(dataStream);
    // Add string characters to the stream
    writer.WritePlcString(textBox1.Text, MAX_TEXT_LENGTH);
    // Write stream to the PLC
    adsClient.Write(varHandle, dataStream)
  except
    on err : Exception do
      MessageBox.Show(err.Message, err.Source)
  end;
end;

{$ELSE} // OLD_ADSNET_INTF defined => Ads.NET version < 1.0.0.10
method MainForm.btnRead_Click(sender: System.Object; e: System.EventArgs);
begin
  try
    // Length of the stream = Length of string in PLC + 1
    var dataStream := new AdsStream(MAX_TEXT_LENGTH + 1);
    // Create BinaryReader. Encoding is set to ASCII, to read strings
    var reader := new BinaryReader(dataStream, System.Text.Encoding.ASCII);
    // Read string characters from PLC to the stream
    var length := adsClient.Read(varHandle, dataStream);
    // Read stream characters from stream
    var text := new String(reader.ReadChars(length));
    // Search for terminating zero character and cut at found position
    // Necessary if you want to compare the string to other strings
    text := text.Substring(0,text.IndexOf(Char(0)));
    textBox1.Text := text
  except
    on err: Exception do
      MessageBox.Show(err.Message, err.Source)
  end;
end;

method MainForm.btnWrite_Click(sender: System.Object; e: System.EventArgs);
begin
  try
    // Length of the stream = Length of string in PLC + 1
    var dataStream := new AdsStream(MAX_TEXT_LENGTH + 1);
    // Create BinaryWriter. Encoding is set to ASCII, to read strings
    var writer := new BinaryWriter(dataStream, System.Text.Encoding.ASCII);
    // Add string characters to the stream
    writer.Write(textBox1.Text.ToCharArray());
    // Add terminating zero to the stream
    writer.Write(Char(0));
    // Write stream to the PLC
    adsClient.Write(varHandle, dataStream)
  except
    on err: Exception do
      MessageBox.Show(err.Message, err.Source)
  end;
end;
{$ENDIF}

end.

PLC program

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

Language / IDE | Unpack the sample program
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample04.exe
Delphi for .NET (Borland Developer Studio 2006) | Sample04.exe


Delphi ADS.NET Sample05

TwinCAT Sample - Delphi - ADS .NET

Reading and writing of DATE/TIME variables

From TwinCAT.Ads.NET version >= 1.0.0.10

Task

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

Description

The PLC program contains variables of following type:

  • MAIN.Time1 vom Typ TIME;
  • MAIN.Date1 vom Typ DATE;
  • MAIN.DateTime1 vom Typ DT (DATE_AND_TIME);
  • MAIN.TimeOfDay1 vom Typ TOD (TIME_OF_DAY);

In the MainForm_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 MainForm_FormClosing event is called and the Dispose method of the TcAdsClient object is called.

When the user clicks the "Read" button on the form, the variables MAIN.Time1, MAIN.Date1 and so on 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.

Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) program

namespace Sample05;

interface

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

type
  /// <summary>
  /// Summary description for MainForm.
  /// </summary>
  MainForm = partial class(System.Windows.Forms.Form)
  private
    adsClient   : TcAdsClient;
    varHandles  : Array Of Integer;
    method MainForm_Load(sender: System.Object; e: System.EventArgs);
    method MainForm_FormClosing(sender: System.Object; e: System.Windows.Forms.FormClosingEventArgs);
    method btnRead_Click(sender: System.Object; e: System.EventArgs);
    method btnWrite_Click(sender: System.Object; e: System.EventArgs);
  protected
    method Dispose(disposing: Boolean); override;
  public
    // Time1	  :TIME; Time duration, 4 byte unsigned integer, resolution in milliseconds
    // Date1	  :DATE; Date, 4 byte unsigned integer, resolution in seconds since 01.01.1970
    // DateTime1	  :DATE_AND_TIME; Date and time, 4 byte unsigned integer, resolution in seconds since 01.01.1970
    // TimeOfDay1	  :TIME_OF_DAY; Time of day, 4 byte unsigned integer, resolution in milliseconds
    const MAX_ADSSTREAM_SIZE = 4;// SIZEOF(Time1) = SIZEOF( Date1) = SIZEOF(DateTime1) = SIZEOF(TimeOfDay1)
    constructor;
  end;

implementation

{$REGION Construction and Disposition}
constructor MainForm;
begin
  //
  // Required for Windows Form Designer support
  //
  InitializeComponent();

  //
  // TODO: Add any constructor code after InitializeComponent call
  //
end;

method MainForm.Dispose(disposing: Boolean);
begin
  if disposing then begin
    if assigned(components) then
      components.Dispose();

    //
    // TODO: Add custom disposition code here
    //
  end;
  inherited Dispose(disposing);
end;
{$ENDREGION}

method MainForm.MainForm_Load(sender: System.Object; e: System.EventArgs);
begin
  try
    // Set some initialization values
    TextBox1.Text := new TimeSpan(0, 10, 10, 12, 345).ToString();
    TextBox2.Text := new DateTime(2011, 9, 14).ToString();
    TextBox3.Text := new DateTime(2011, 9, 14, 11, 6, 21).ToString();
    TextBox4.Text := new TimeSpan(10, 11, 22).ToString();
    // Creaste instance of class TcAdsClient
    adsClient := new TcAdsClient();
    // Create connectin with Port 801 on the local PC
    adsClient.Connect(801);
    varHandles := new Integer[4];
    // Create PLC variable handles
    varHandles[0] := adsClient.CreateVariableHandle('MAIN.Time1');
    varHandles[1] := adsClient.CreateVariableHandle('MAIN.Date1');
    varHandles[2] := adsClient.CreateVariableHandle('MAIN.DateTime1');
    varHandles[3] := adsClient.CreateVariableHandle('MAIN.TimeOfDay1')
  except
    on err: Exception do
      MessageBox.Show(err.Message, err.Source)
  end;
end;

method MainForm.MainForm_FormClosing(sender: System.Object; e: System.Windows.Forms.FormClosingEventArgs);
begin
  try
    // Release PLC variable handles
    for i:Integer := varHandles.GetLowerBound(0) to varHandles.GetUpperBound(0) do
      adsClient.DeleteVariableHandle(varHandles[i]);
  except
    on err: Exception do
      MessageBox.Show(err.Message, err.Source)
  end;
  // Close connection
  adsClient.Dispose();
end;

method MainForm.btnRead_Click(sender: System.Object; e: System.EventArgs);
begin
  try
    // Create data stream instance
    var dataStream := new AdsStream(MAX_ADSSTREAM_SIZE);
    // Create AdsBinaryReader instance
    var binRead := new AdsBinaryReader(dataStream);
    // Read TIME value
    dataStream.Position := 0;
    adsClient.Read(varHandles[0], dataStream);
    textBox1.Text := binRead.ReadPlcTIME().ToString();
    // Read DATE value
    dataStream.Position := 0;
    adsClient.Read(varHandles[1], dataStream);
    textBox2.Text := binRead.ReadPlcDATE().Date.ToString();
    // Read DATE_AND_TIME value
    dataStream.Position := 0;
    adsClient.Read(varHandles[2], dataStream);
    textBox3.Text := binRead.ReadPlcDATE().ToString();
    // Read TIME_OF_DAY value
    dataStream.Position := 0;
    adsClient.Read(varHandles[3], dataStream);
    textBox4.Text := binRead.ReadPlcTime().ToString()
  except
    on err: Exception do
      MessageBox.Show(err.Message, err.Source)
  end;
end;

method MainForm.btnWrite_Click(sender: System.Object; e: System.EventArgs);
begin
  try
    // Create data stream instance
    var dataStream := new AdsStream(MAX_ADSSTREAM_SIZE);
    // Create AdsBinaryWriter instance
    var binWrite := new AdsBinaryWriter(dataStream);
    // Write TIME value
    dataStream.Position := 0;
    binWrite.WritePlcType(TimeSpan.Parse(textBox1.Text));
    adsClient.Write(varHandles[0], dataStream);
    // Write DATE value
    dataStream.Position := 0;
    binWrite.WritePlcType(DateTime.Parse(textBox2.Text));
    adsClient.Write(varHandles[1], dataStream);
    // Write DATE_AND_TIME value
    dataStream.Position := 0;
    binWrite.WritePlcType(DateTime.Parse(textBox3.Text));
    adsClient.Write(varHandles[2], dataStream);
    // Write TIME_OF_DATE value
    dataStream.Position := 0;
    binWrite.WritePlcType(TimeSpan.Parse(textBox4.Text));
    adsClient.Write(varHandles[3], dataStream)
  except
    on err: Exception do
      MessageBox.Show(err.Message, err.Source)
  end;
end;

end.

PLC program

PROGRAM MAIN
VAR
    Time1 	:TIME := T#521h33m23s231ms;
    Date1 	:DATE := D#1993-06-12;
    DateTime1 :DATE_AND_TIME := DT#1993-06-12-15:36:55;
    TimeOfDay1 :TIME_OF_DAY := TOD#12:34:56.123;
END_VAR
;

Language / IDE | Unpack the sample program
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample05.exe
Delphi for .NET (Borland Developer Studio 2006) | Sample05.exe


Delphi ADS.NET Sample06

TwinCAT Sample - Delphi - ADS .NET

Read PLC variable declaration

From TwinCAT.Ads.NET version >= 1.0.0.12

Task

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

Description

In the MainForm_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 the application enumerates 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.

Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) program

namespace Sample06;

interface

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

type
  /// <summary>
  /// Summary description for MainForm.
  /// </summary>
  MainForm = partial class(System.Windows.Forms.Form)
  private
    tcClient      : TcAdsClient;
    symbolLoader  : TcAdsSymbolInfoLoader;
    currentSymbol : ITcAdsSymbol;

    method CreateNewNode(var symbol: TcAdsSymbolInfo) : TreeNode;
    method SetSymbolInfo(symbol: ITcAdsSymbol);
    method MainForm_Load(sender: System.Object; e: System.EventArgs);
    method MainForm_FormClosing(sender: System.Object; e: System.Windows.Forms.FormClosingEventArgs);
    method btnLoad_Click(sender: System.Object; e: System.EventArgs);
    method btnWrite_Click(sender: System.Object; e: System.EventArgs);
    method btnFindSymbol_Click(sender: System.Object; e: System.EventArgs);
    method btnReadSymbolInfo_Click(sender: System.Object; e: System.EventArgs);
    method treeView1_AfterSelect(sender: System.Object; e: System.Windows.Forms.TreeViewEventArgs);
  protected
    method Dispose(disposing: Boolean); override;
  public
    constructor;
  end;

implementation

{$REGION Construction and Disposition}
constructor MainForm;
begin
  //
  // Required for Windows Form Designer support
  //
  InitializeComponent();

  //
  // TODO: Add any constructor code after InitializeComponent call
  //
end;

method MainForm.Dispose(disposing: Boolean);
begin
  if disposing then begin
    if assigned(components) then
      components.Dispose();

    //
    // TODO: Add custom disposition code here
    //
  end;
  inherited Dispose(disposing);
end;
{$ENDREGION}

method MainForm.MainForm_Load(sender: System.Object; e: System.EventArgs);
begin
  tcClient := new TcAdsClient();
  tcClient.Connect(801);
  try
    symbolLoader := tcClient.CreateSymbolInfoLoader();
  except
    on err: Exception do
      MessageBox.Show(err.Message, err.Source);
  end;
end;

method MainForm.MainForm_FormClosing(sender: System.Object; e: System.Windows.Forms.FormClosingEventArgs);
begin
  try
  except
    on err: Exception do
      MessageBox.Show(err.Message, err.Source)
  end;
  tcClient.Dispose();
end;

method MainForm.btnLoad_Click(sender: System.Object; e: System.EventArgs);
var symbol  : TcAdsSymbolInfo;
    node    : TreeNode;
begin
  treeViewSymbols.Nodes.Clear();

  if not cbFlat.Checked then
  begin
    symbol := symbolLoader.GetFirstSymbol(true);
    while ( symbol <> Nil )do
    begin
      treeViewSymbols.Nodes.Add(CreateNewNode(var symbol));
      symbol := symbol.NextSymbol;
    end;
  end
  else
  begin
    for symbol : TcAdsSymbolInfo in symbolLoader do
    begin
      node := new TreeNode(symbol.Name);
      node.Tag := symbol;
      treeViewSymbols.Nodes.Add(node);
    end;
  end;
end;

method MainForm.CreateNewNode(var symbol: TcAdsSymbolInfo) : TreeNode;
var node      : TreeNode;
  subSymbol : TcAdsSymbolInfo;
begin
  node := new TreeNode(symbol.Name);
  node.Tag := symbol;
  subSymbol := symbol.FirstSubSymbol;
  while( subSymbol <> Nil ) do
  begin
    node.Nodes.Add(CreateNewNode(var subSymbol));
    subSymbol := subSymbol.NextSymbol;
  end;
  result := node;
end;

method MainForm.SetSymbolInfo(symbol: ITcAdsSymbol);
begin
  currentSymbol := symbol;
  tbSymbolname.Text := symbol.Name;
  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 := System.Object(symbol.Datatype).ToString();

  try
    tbValue.Text := tcClient.ReadSymbol(symbol).ToString();
  except
    on err: AdsDatatypeNotSupportedException do
      tbValue.Text := err.Message;
    on err: Exception do
      MessageBox.Show(err.Message, err.Source);
  end;
end;

method MainForm.btnWrite_Click(sender: System.Object; e: System.EventArgs);
begin
    try
      if( currentSymbol <> nil )then
        tcClient.WriteSymbol(currentSymbol, tbValue.Text);
    except
      on err: Exception do
        MessageBox.Show('Unable to write Value! ' + err.Message, err.Source);
    end;
end;

method MainForm.btnFindSymbol_Click(sender: System.Object; e: System.EventArgs);
var symbol : ITcAdsSymbol;
begin
  try
    symbol := symbolLoader.FindSymbol(tbSymbolname.Text);
    if( symbol = Nil ) then
      MessageBox.Show('Symbol ' + tbSymbolname.Text + ' not found!', 'TwinCAT.Ads')
    else
      SetSymbolInfo(symbol);
  except
    on err: Exception do
      MessageBox.Show(err.Message, err.Source);
  end;
end;

method MainForm.btnReadSymbolInfo_Click(sender: System.Object; e: System.EventArgs);
var symbol : ITcAdsSymbol;
begin
  try
    symbol := tcClient.ReadSymbolInfo(tbSymbolname.Text);
    if( symbol = Nil ) then
      MessageBox.Show('Symbol ' + tbSymbolname.Text + ' not found!', 'TwinCAT.Ads')
    else
      SetSymbolInfo(symbol);
  except
    on err: Exception do
      MessageBox.Show(err.Message, err.Source);
  end;
end;

method MainForm.treeView1_AfterSelect(sender: System.Object; e: System.Windows.Forms.TreeViewEventArgs);
begin
  if( e.Node.Text.Length > 0 ) then
  begin
    if( e.Node.Tag is TcAdsSymbolInfo ) then
    begin
      SetSymbolInfo(ITcAdsSymbol(e.Node.Tag));
    end;
  end;
end;

end.

PLC program

PROGRAM MAIN
VAR
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
;

Language / IDE | Unpack the sample program
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample06.exe
Delphi for .NET (Borland Developer Studio 2006) | Sample06.exe


Delphi ADS.NET Sample07

TwinCAT Sample - Delphi - ADS .NET

Reading and writing of PLC variables of any type

From TwinCAT.Ads.NET version >= 1.0.0.15

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:

TwinCAT.Ads.TcAdsClient.ReadAny(System.Int32, System.Type)
TwinCAT.Ads.TcAdsClient.ReadAny(System.Int32, System.Type, System.Int32[])

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. Full list of supported types can be found in the documentation of the overloaded method.

Example:

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

var hArr : Integer;
var arr : Array of Integer;

hArr := adsClient.CreateVariableHandle(".arr");
arr := Array of Integer(adsClient.ReadAny(hArr, TypeOf(Array of Integer), [4]));

...
adsClient.DeleteVariableHandle(hArr);

WriteAny

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

TwinCAT.Ads.TcAdsClient.WriteAny(System.Int32, System.Object)
TwinCAT.Ads.TcAdsClient.WriteAny(System.Int32, System.Object, System.Int32[])

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 global PLC variable of the type ARRAY[0..3] OF DINT should be written:

var hArr : Integer;
var arr : Array of Integer := [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)]
TSimpleStruct = record
    lrealVal : Double;
    dintVal  : Longint;
end;

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)]
TComplexStruct = record
  intVal : Smallint;
  // Specifies how .NET should marshal the array
  // SizeConst specifies the number of elements the array has.
  [MarshalAs(UnmanagedType.ByValArray, SizeConst:=4)]
  dintArr : Array[0..3] of Longint;
  [MarshalAs(UnmanagedType.I1)]
  boolVal : Boolean;
  byteVal : Byte;
  // 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)]
  stringVal : String;
  simpleStruct1 : TSimpleStruct;
end;

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

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 MainForm_Load method.

adsClient.AdsNotificationEx += adsClient_AdsNotificationEx;

Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) program

namespace Sample07;

interface

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

type

  ///////////////////////////////////////////////////////
  //TYPE TSimpleStruct :
  //STRUCT
  //	lrealVal	: LREAL;
  //	dintVal 	: DINT;
  //END_STRUCT
  //END_TYPE
  [StructLayout(LayoutKind.Sequential, Pack:=1)]
  TSimpleStruct = record
    lrealVal : Double;
    dintVal  : Longint;
  end;

  ///////////////////////////////////////////////////////
  //TYPE TComplexStruct :
  //STRUCT
  //	intVal 		: INT;
  //  	dintArr		: ARRAY[0..3] OF DINT;
  //	boolVal		: BOOL;
  //	byteVal		: BYTE;
  //	stringVal 	: STRING(5);
  //	simpleStruct1	: TSimpleStruct;
  //END_STRUCT
  //END_TYPE
  [StructLayout(LayoutKind.Sequential, Pack:=1)]
  TComplexStruct = record
        intVal : Smallint;
        // Specifies how .NET should marshal the array
        // SizeConst specifies the number of elements the array has.
        [MarshalAs(UnmanagedType.ByValArray, SizeConst:=4)]
        dintArr : Array[0..3] of Longint;
        [MarshalAs(UnmanagedType.I1)]
        boolVal : Boolean;
        byteVal : Byte;
        // 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)]
        stringVal : String;
    simpleStruct1 : TSimpleStruct;
  end;

  /// <summary>
  /// Summary description for MainForm.
  /// </summary>
  MainForm = partial class(System.Windows.Forms.Form)
  private
    adsClient      : TcAdsClient;
    //PLC variable handles
    hDint1 : Longint;
    hInt1 : Longint;
      hBool1 : Longint;
    hUsint1 : Longint;
      hReal1 : Longint;
      hLreal1 : Longint;
      hStr1 : Longint;
      hStr2 : Longint;
      hComplexStruct : Longint;
    notificationHandles : ArrayList;
    method adsClient_AdsNotificationEx(sender: Object; e: AdsNotificationExEventArgs);
    method FillStructControls(structure : TComplexStruct);
    method GetStructFromControls(var structure : TComplexStruct);
    method MainForm_Load(sender: System.Object; e: System.EventArgs);
    method MainForm_FormClosing(sender: System.Object; e: System.Windows.Forms.FormClosingEventArgs);
    method btnRead_Click(sender: System.Object; e: System.EventArgs);
    method btnWrite_Click(sender: System.Object; e: System.EventArgs);
    method btnAddNotifications_Click(sender: System.Object; e: System.EventArgs);
    method btnDeleteNotifications_Click(sender: System.Object; e: System.EventArgs);
  protected
    method Dispose(disposing: Boolean); override;
  public
    constructor;
  end;

implementation

{$REGION Construction and Disposition}
constructor MainForm;
begin
  //
  // Required for Windows Form Designer support
  //
  InitializeComponent();

  //
  // TODO: Add any constructor code after InitializeComponent call
  //
end;

method MainForm.Dispose(disposing: Boolean);
begin
  if disposing then begin
    if assigned(components) then
      components.Dispose();

    //
    // TODO: Add custom disposition code here
    //
  end;
  inherited Dispose(disposing);
end;
{$ENDREGION}

////////////////////////////////////////////////////////////////////////////
//
method MainForm.MainForm_Load(sender: System.Object; e: System.EventArgs);
begin
  try
    // Create instance of TcAdsClient class
    adsClient := new TcAdsClient();
    // Create connection to the local PLC port 801
    adsClient.Connect(801);
    // Create handles for the PLC variables;
    hBool1 := adsClient.CreateVariableHandle('MAIN.bool1');
    hInt1 := adsClient.CreateVariableHandle('MAIN.Int1');
    hDint1 := adsClient.CreateVariableHandle('MAIN.dint1');
    hUsint1 := adsClient.CreateVariableHandle('MAIN.usint1');
    hReal1 := adsClient.CreateVariableHandle('MAIN.real1');
    hLreal1 := adsClient.CreateVariableHandle('MAIN.lreal1');
    hStr1 := adsClient.CreateVariableHandle('MAIN.str1');
    hStr2 := adsClient.CreateVariableHandle('MAIN.str2');
    hComplexStruct := adsClient.CreateVariableHandle('MAIN.ComplexStruct1');
    // Add notification event handler
    adsClient.AdsNotificationEx += adsClient_AdsNotificationEx;

    btnAddNotifications.Enabled := True;
    btnDelNotifications.Enabled := False;
  except
    on err: Exception do
      MessageBox.Show(err.Message, err.Source)
  end;
end;

////////////////////////////////////////////////////////////////////////////
//
method MainForm.MainForm_FormClosing(sender: System.Object; e: System.Windows.Forms.FormClosingEventArgs);
begin
  try
    // Release handles of PLC variables
    adsClient.DeleteVariableHandle(hBool1);
    adsClient.DeleteVariableHandle(hInt1);
    adsClient.DeleteVariableHandle(hDint1);
    adsClient.DeleteVariableHandle(hUsint1);
    adsClient.DeleteVariableHandle(hReal1);
    adsClient.DeleteVariableHandle(hLreal1);
    adsClient.DeleteVariableHandle(hStr1);
    adsClient.DeleteVariableHandle(hStr2);
    adsClient.DeleteVariableHandle(hComplexStruct);
    // Remove notificaiton event handler
    adsClient.AdsNotificationEx -= adsClient_AdsNotificationEx;
  except
    on err: Exception do
      MessageBox.Show(err.Message, err.Source)
  end;
  // Close connection
  adsClient.Dispose();
end;

////////////////////////////////////////////////////////////////////////////
//
method MainForm.FillStructControls(structure : TComplexStruct);
begin
  tbIntVal.Text := structure.intVal.ToString();
  tbdintArr.Text := System.String.Format('{0:d}, {1:d}, {2:d}, {3:d}',
  [structure.dintArr[0], structure.dintArr[1], structure.dintArr[2], structure.dintArr[3]]);

  tbBoolVal.Text := structure.boolVal.ToString();
  tbByteVal.Text := structure.byteVal.ToString();
  tbStringVal.Text := structure.stringVal;
  tbLrealVal.Text := structure.simpleStruct1.lrealVal.ToString();
  tbDintVal.Text := structure.simpleStruct1.dintVal.ToString();
end;

////////////////////////////////////////////////////////////////////////////
//
method MainForm.GetStructFromControls(var structure : TComplexStruct);
begin
  structure.intVal := System.Int16.Parse(tbIntVal.Text);
  var sArr := tbDintArr.Text.Split([',']);
  for i : Integer := 0 to 3 do
    structure.dintArr[i] := System.Int32.Parse(sArr[i]);

  structure.boolVal := System.Boolean.Parse(tbboolVal.Text);
  structure.byteVal := System.Byte.Parse(tbByteVal.Text);
  structure.stringVal := tbStringVal.Text;

  structure.simpleStruct1.dintVal := System.Int32.Parse(tbDintVal.Text);
  structure.simpleStruct1.lrealVal := System.Double.Parse(tbLrealVal.Text);
end;

////////////////////////////////////////////////////////////////////////////
//
method MainForm.btnRead_Click(sender: System.Object; e: System.EventArgs);
begin
  try
    // Read by handle, the second parameter specifies the type of the variable
    tbBool1.Text := adsClient.ReadAny(hBool1, TypeOf(Boolean)).ToString();
    tbInt1.Text := adsClient.ReadAny(hInt1, TypeOf(Smallint)).ToString();
    tbDint1.Text := adsClient.ReadAny(hDint1, TypeOf(Longint)).ToString();
    tbUsint1.Text := adsClient.ReadAny(hUsint1, TypeOf(Byte)).ToString();
    tbReal1.Text := adsClient.ReadAny(hReal1, TypeOf(Single)).ToString();
    tbLreal1.Text := adsClient.ReadAny(hLreal1, TypeOf(Double)).ToString();

    // To read strings we have to pass the number of string characters
    // specified in the PLC project(default 80). This value is passed as an int array.
    tbStr1.Text := adsClient.ReadAny(hstr1, TypeOf(String), [80]).ToString();
    tbStr2.Text := adsClient.ReadAny(hstr2, TypeOf(String), [5]).ToString();

    FillStructControls(TComplexStruct(adsClient.ReadAny(hComplexStruct, TypeOf(TComplexStruct))));
  except
    on err: Exception do
      MessageBox.Show(err.Message, err.Source)
  end;
end;

////////////////////////////////////////////////////////////////////////////
//
method MainForm.btnWrite_Click(sender: System.Object; e: System.EventArgs);
begin
  try
    var structure := new TComplexStruct();
    // Write by handle, the second parameter is the object to be written to the PLC variable
    adsClient.WriteAny(hBool1, System.Boolean.Parse(tbBool1.Text));
    adsClient.WriteAny(hInt1, System.Int16.Parse(tbInt1.Text));
    adsClient.WriteAny(hDint1, System.Int32.Parse(tbDint1.Text));
    adsClient.WriteAny(hUsint1, System.Byte.Parse(tbUsint1.Text));
    adsClient.WriteAny(hReal1, System.Single.Parse(tbReal1.Text));
    adsClient.WriteAny(hLreal1, System.Double.Parse(tbLreal1.Text));

    // To write strings we have to pass the number of string characters
    // specified in the PLC project(default 80).  This value is passed as an int array.
    adsClient.WriteAny(hStr1, tbStr1.Text, [80]);
    adsClient.WriteAny(hStr2, tbStr2.Text, [5]);

    GetStructFromControls( var structure );
    adsClient.WriteAny(hComplexStruct, structure) ;
  except
    on err: Exception do
      MessageBox.Show(err.Message, err.Source)
  end;
end;

////////////////////////////////////////////////////////////////////////////
//
method MainForm.btnAddNotifications_Click(sender: System.Object; e: System.EventArgs);
begin
  try
    if (notificationHandles = Nil) then
      notificationhandles := new ArrayList();
    notificationHandles.Clear();

    // Register notification
    notificationHandles.Add(adsClient.AddDeviceNotificationEx('MAIN.bool1', AdsTransMode.OnChange, 100, 0, tbBool1, TypeOf(Boolean)));
    notificationHandles.Add(adsClient.AddDeviceNotificationEx('MAIN.int1', AdsTransMode.OnChange, 100, 0, tbInt1, TypeOf(Smallint)));
    notificationHandles.Add(adsClient.AddDeviceNotificationEx('MAIN.dint1', AdsTransMode.OnChange, 100, 0, tbDint1, TypeOf(Longint)));
    notificationHandles.Add(adsClient.AddDeviceNotificationEx('MAIN.usint1', AdsTransMode.OnChange, 100, 0, tbUsint1, TypeOf(Byte)));
    notificationHandles.Add(adsClient.AddDeviceNotificationEx('MAIN.real1', AdsTransMode.OnChange, 100, 0, tbReal1, TypeOf(Single)));
    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), [80]));
    notificationHandles.Add(adsClient.AddDeviceNotificationEx('MAIN.str2', AdsTransMode.OnChange, 100, 0, tbStr2, TypeOf(String), [5]));
    notificationHandles.Add(adsClient.AddDeviceNotificationEx('MAIN.complexStruct1', AdsTransMode.OnChange, 100, 0, tbDint1, TypeOf(TComplexStruct)));

    btnDelNotifications.Enabled := true;
    btnAddNotifications.Enabled := false;
  except
    on err: Exception do
      MessageBox.Show(err.Message, err.Source)
  end;
end;

////////////////////////////////////////////////////////////////////////////
//
method MainForm.btnDeleteNotifications_Click(sender: System.Object; e: System.EventArgs);
begin
  try
    btnAddNotifications.Enabled := true;
    btnDelNotifications.Enabled := false;
    // Delete registered notifications.
    for handle : Integer in notificationHandles do
      adsClient.DeleteDeviceNotification(handle);
    notificationHandles.Clear();
  except
    on err: Exception do
      MessageBox.Show(err.Message, err.Source)
  end;
end;

////////////////////////////////////////////////////////////////////////////
//
method MainForm.adsClient_AdsNotificationEx(sender: Object; e: AdsNotificationExEventArgs);
begin
  if( e.Value.GetType() = TypeOf(TComplexStruct))then
    FillStructControls(TComplexStruct(e.Value))
  else
    System.Windows.Forms.TextBox(e.UserData).Text := e.Value.ToString();
end;

end.

PLC program

TYPE TSimpleStruct :
STRUCT
    lrealVal : LREAL := 1.23;
    dintVal : 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
;

Language / IDE | Unpack the sample program
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample07.exe
Delphi for .NET (Borland Developer Studio 2006) | Sample07.exe


Delphi ADS.NET Sample08

TwinCAT Sample - Delphi - ADS .NET

Detect state changes in TwinCAT router and PLC

From TwinCAT.Ads.NET version >= 1.0.0.15

Task

Detect state changes in TwinCAT router and TwinCAT 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 TwinCAT-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.

Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) program

namespace Sample08;

interface

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

type
  /// <summary>
  /// Summary description for MainForm.
  /// </summary>
  MainForm = partial class(System.Windows.Forms.Form)
  private
    tcClient:TcAdsClient;
    adsStream : AdsStream;
    binRead : BinaryReader;
    hNotification : Int32;

    method tcClient_AdsNotification(sender: Object; e: AdsNotificationEventArgs);
    method tcClient_AmsRouterNotification(sender: Object; e: AmsRouterNotificationEventArgs);
    method MainForm_Load(sender: System.Object; e: System.EventArgs);
    method MainForm_FormClosing(sender: System.Object; e: System.Windows.Forms.FormClosingEventArgs);
  protected
    method Dispose(disposing: Boolean); override;
  public
    constructor;
  end;

implementation

{$REGION Construction and Disposition}
constructor MainForm;
begin
  //
  // Required for Windows Form Designer support
  //
  InitializeComponent();

  //
  // TODO: Add any constructor code after InitializeComponent call
  //
end;

method MainForm.Dispose(disposing: Boolean);
begin
  if disposing then begin
    if assigned(components) then
      components.Dispose();

    //
    // TODO: Add custom disposition code here
    //
  end;
  inherited Dispose(disposing);
end;
{$ENDREGION}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
method MainForm.MainForm_Load(sender: System.Object; e: System.EventArgs);
begin
  try
    // Create instance
    tcClient := new TcAdsClient();
    // Connect the client to the local PLC
    tcClient.Connect(801);

    adsStream := new AdsStream(2);		// Stream storing the ADS state of the PLC
    binRead := new BinaryReader(adsStream);	// Reader to read the state data

    // Add TwinCAT Router notification event handler
    tcClient.AmsRouterNotification += tcClient_AmsRouterNotification;

    // Add TwinCAT PLC device notification
    hNotification := tcClient.AddDeviceNotification(
                  Int32( AdsReservedIndexGroups.DeviceData ), // Index group of the device state
        Int32( AdsReservedIndexOffsets.DeviceDataAdsState ), // Index offsset of the device state
        adsStream,// Stream to store the state
        AdsTransMode.OnChange,// Transfer mode: transmit state on change
        0,// Transmit changes immediately
        0, Nil);

    // Register callback to react on state changes of the local PLC
    tcClient.AdsNotification += tcClient_AdsNotification

  except
    On err : AdsErrorException do
       MessageBox.Show(err.Message, err.Source)
  end;
end;

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
method MainForm.MainForm_FormClosing(sender: System.Object; e: System.Windows.Forms.FormClosingEventArgs);
begin
  try
    // Remove TwinCAT PLC notification event handler
    tcClient.DeleteDeviceNotification(hNotification);
    tcClient.AdsNotification -= tcClient_AdsNotification;
    // Remove TwinCAT Router notification event handler
    tcClient.AmsRouterNotification -= tcClient_AmsRouterNotification;
    // Close connection
    tcClient.Dispose()
  except
    On err : AdsErrorException do
       MessageBox.Show(err.Message, err.Source)
  end;
end;

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
method MainForm.tcClient_AmsRouterNotification(sender: Object; e: AmsRouterNotificationEventArgs);
begin
   txtRouterState.Text := e.State.ToString();
end;

/////////////////////////////////////////////////////////////////////////////////////////////////////////////
method MainForm.tcClient_AdsNotification(sender: Object; e: AdsNotificationEventArgs);
begin
  if (e.NotificationHandle = hNotification) then
    txtPLCState.Text := AdsState(binRead.ReadInt16()).ToString();
end;

end.

Language / IDE | Unpack the sample program
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample08.exe


Delphi ADS.NET Sample09

TwinCAT Sample - Delphi - ADS .NET

ADS sum command: reading or writing several variables

System requirements:

  • TwinCAT v2.11 Build >= 1550;

Task

Read or write several PLC variables with TcAdsClient.ReadWrite command. This sample shows also how to get and enable several PLC variables handles with a ADS sum command.

Description

The singles sub commands are moved in an AdsStream (Write Data) to the PLC and executed there in a row. The result is moved with a further AdsStream (Return value + Read Data) from the PLC to the client.

Dynamic arrays can be used to assign a variable number of parameters (ADS sub commands). The Sample uses the dynamic arrays and makes the code universal reusable. Other methods are possible (array with fixed size, structure elements, etc.)

The following auxiliary methods are used as generic ADS sum commands to write or read the PLC variables value, to get or enable handles, etc. In these methods the AdsStream for the ADS sum command is filled with parameters and the ADS sum command is called by the TcAdsClient.ReadWrite command.

method BlockRead(ig : Array of Int32; io : Array of Int32; var rLen : Array of Int32; var data : AdsStream) : Array of Int32;
method BlockWrite(ig : Array of Int32; io : Array of Int32; wLen : Array of Int32; data : AdsStream) : Array of Int32;
method BlockReadWrite(ig, io, wLen : Array of Int32;  wData : AdsStream; var rLen : Array of Int32; var rData : AdsStream) : Array of Int32;

The generic methods are used by four more methods:

method BlockCreateVariableHandle(names : Array Of String) : Array of Int32;
method BlockDeleteVariableHandle(handles : Array of Int32) : Array of Int32;
method btnRead_Click(sender: System.Object; e: System.EventArgs);
method btnWrite_Click(sender: System.Object; e: System.EventArgs);

Delphi Prism (Embarcadero Prism XE2, Oxygen for .NET) program

namespace Sample09;

interface

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

type
  /// <summary>
  /// Summary description for MainForm.
  /// </summary>
  MainForm = partial class(System.Windows.Forms.Form)
  private
    adsClient : TcAdsClient;
    vNames  : Array of String := ["MAIN.uintValue", "MAIN.dintValue", "MAIN.boolValue"];
    vLengths : Array of Int32 := [2,4,1];
    vHandles : Array of Int32 := [0,0,0];

    method BlockRead(ig : Array of Int32; io : Array of Int32; var rLen : Array of Int32; var data : AdsStream) : Array of Int32;
    method BlockWrite(ig : Array of Int32; io : Array of Int32; wLen : Array of Int32; data : AdsStream) : Array of Int32;
    method BlockReadWrite(ig, io, wLen : Array of Int32;  wData : AdsStream; var rLen : Array of Int32; var rData : AdsStream) : Array of Int32;

    method BlockCreateVariableHandle(names : Array Of String) : Array of Int32;
    method BlockDeleteVariableHandle(handles : Array of Int32) : Array of Int32;

    method MainForm_Load(sender: System.Object; e: System.EventArgs);
    method MainForm_FormClosing(sender: System.Object; e: System.Windows.Forms.FormClosingEventArgs);
    method btnRead_Click(sender: System.Object; e: System.EventArgs);
    method btnWrite_Click(sender: System.Object; e: System.EventArgs);
  protected
    method Dispose(disposing: Boolean); override;
  public
    const ADSIGRP_SUMUP_READ = $0000F080;	// AdsRW  IOffs list size or 0 (=0 -> list size == WLength/3*sizeof(ULONG))
                    // W: {list of IGrp, IOffs, Length}
                    // if IOffs != 0 then R: {list of results} and {list of data}
                    // if IOffs == 0 then R: only data (sum result)

    const ADSIGRP_SUMUP_WRITE = $0000F081;	// AdsRW  IOffs list size
                    // W: {list of IGrp, IOffs, Length} followed by {list of data}
                    // R: {list of results}

    const ADSIGRP_SUMUP_READWRITE = $0000F082;// AdsRW  IOffs list size
                    // W: {list of IGrp, IOffs, RLength, WLength} followed by {list of data}
                    // R: {list of results, RLength} followed by {list of data}
    constructor;
  end;

implementation

{$REGION Construction and Disposition}
constructor MainForm;
begin
  //
  // Required for Windows Form Designer support
  //
  InitializeComponent();
  //
  // TODO: Add any constructor code after InitializeComponent call
  //
end;

method MainForm.Dispose(disposing: Boolean);
begin
  if disposing then begin
    if assigned(components) then
      components.Dispose();
    //
    // TODO: Add custom disposition code here
    //
  end;
  inherited Dispose(disposing);
end;
{$ENDREGION}

Create connection

On starting the application a connection to the PLC is established and variable handles are fetched via the BlockCreateVariableHandle method call. The names of the three PLC variables are transferred to the BlockCreateVariableHandle method as a dynamic array.

//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Create form, connection and variable handles
method MainForm.MainForm_Load(sender: System.Object; e: System.EventArgs);
begin
  try
    // Connect to PLC
    adsClient := new TcAdsClient();
    adsClient.Connect(801);

    // Create variable handles
    vHandles := BlockCreateVariableHandle(vNames);

//    Conventional method:
//    for each name in vNames index i do
//      vHandles[i] := adsClient.CreateVariableHandle(name);

  except
    On err : AdsErrorException do
      MessageBox.Show(err.Message, err.Source);
  end;
end;

Close connection

On terminating the application the variable handles are released and the connection to the PLC is cut. The handles to be released are transferred to the BlockDeleteVariableHandle method as a dynamic array.

/////////////////////////////////////////////////////////////////////////////////////////////////////////
// Close form and release resources
method MainForm.MainForm_FormClosing(sender: System.Object; e: System.Windows.Forms.FormClosingEventArgs);
begin
  try
    // Release variable handles
    BlockDeleteVariableHandle(vHandles);

//  Conventional method
//    for each handle in vHandles index i do
//      adsClient.DeleteVariableHandle(handle);

    // Close connection
    adsClient.Dispose()
  except
    On err : AdsErrorException do
      MessageBox.Show(err.Message, err.Source)
  end;
end;

Read PLC values

The PLC variable values are read by clicking on the Read button. The btnRead_Click event handling routing uses the generic BlockRead method. However, the instance of the AdsStream for the PLC data must be generated beforehand with a suitable size so that the values of all PLC variables fit into it. The parameters rLen (read lengths) and data (AdsStream) are transferred by reference. As a result, the BlockRead method can fill the AdsStream with data and change the actually returned data lengths (rLen) (e.g.: in case of error). If successful, the data are successively read from the AdsStream and displayed on the form.

//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Read block data (multiple PLC values)
method MainForm.btnRead_Click(sender: System.Object; e: System.EventArgs);
begin
  var ig := new Int32[vHandles.Length];
  // Calculate the size of PLC value data
  var cbRead : Int32 := 0;
  for each len in vLengths index i do
  begin
    cbRead := cbRead + len;
    ig[i] := Int32(AdsReservedIndexGroups.SymbolValueByHandle);
  end;

  var data := new AdsStream(cbRead);
  var reader := new BinaryReader(data);
  var rLen := new Int32[vLengths.Length];
  rLen := vLengths;

  // Read PLC values
  var blockResult := BlockRead(ig, vHandles, var rLen, var data);
  for each adsResult in blockResult index i do
    if ( adsResult <> 0 ) then
      MessageBox.Show("Read failed! Error: " + adsResult.ToString() , "TwinCAT.Ads");

  data.Position := 0;
  tbRuint.Text := iif((blockResult[0] = 0) And (rLen[0] = 2), reader.ReadUInt16().ToString(), "0");
  tbRdint.Text := iif((blockResult[1] = 0) And (rLen[1] = 4), reader.ReadInt32().ToString(), "0");
  tbRbool.Text := iif((blockResult[2] = 0) And (rLen[2] = 1), reader.ReadBoolean().ToString(), "False");
end;

Write PLC values

The values are written from the application to the PLC by clicking on the Write button. The btnWrite_Click event handling routine uses the generic BlockWrite method. The instance of the AdsStream is generated before the call with a suitable size and filled with the PLC variable values to be written.

//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Write block data (multiple PLC values)
method MainForm.btnWrite_Click(sender: System.Object; e: System.EventArgs);
begin
  var ig := new Int32[vHandles.Length];
  // Calculate the size of PLC value data
  var cbWrite : Int32 := 0;
  for each len in vLengths index i do
  begin
    cbWrite := cbWrite + len;
    ig[i] := Int32(AdsReservedIndexGroups.SymbolValueByHandle);
  end;

  var data := new AdsStream(cbWrite);
  var writer := new BinaryWriter(data);

  writer.Write(UInt16.Parse(tbWuint.Text));
  writer.Write(Int32.Parse(tbWdint.Text));
  writer.Write(boolean.Parse(tbWbool.Text));

  // Write PLC values
  var blockResult := BlockWrite(ig, vHandles, vLengths, data);
  for each adsResult in blockResult do
    if adsResult <> 0 then
      MessageBox.Show("Write failed! Error: " + adsResult.ToString() , "TwinCAT.Ads");
end;

Create variable handles

The generic BlockReadWrite method is used to fetch the variable handles. The symbol names of the PLC variables are transferred to the BlockReadWrite method as write data in the AdsStream. The handles are received in a further AdsStream as read data. As with the BlockWrite method, the AdsStream: rData (read data) and rLen are transferred by reference here also. Only in this way can the contents of the stream be filled with data from the BlockReadWrite method. If successful, the handles are returned by the method as return parameters in the form of a dynamic array.

//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Create multiple symbol handles
method MainForm.BlockCreateVariableHandle( names : Array Of String ) : Array of Int32;
begin
  var getHandles := new Int32[names.Length];
  var ig := new Int32[names.Length];
  var io := new Int32[names.Length];
  var rLen := new Int32[names.Length];
  var wLen := new Int32[names.Length];
  var cbWrite : Int32 := 0;
  for each name in names do
    cbWrite := cbWrite + name.Length;
  var wData := new AdsStream(cbWrite);
  var writer := new BinaryWriter(wData, System.Text.Encoding.ASCII);

  var rData := new AdsStream(getHandles.length * 4);
  var reader := new BinaryReader(rData);

  for each name in names index i do
  begin
    writer.Write(name.ToCharArray());// Copy PLC variable names to the stream
    ig[i] := Int32(AdsReservedIndexGroups.SymbolHandleByName);// indexGroup
    io[i] := 0;// indexOffset = 0
    rLen[i] := sizeOf(getHandles[i]);// readLen = 4 byte
    wLen[i] := name.Length;// writeLen = length of plc variable name
  end;

  var blockResult := BlockReadWrite(ig, io, wLen, wData, var rLen, var rData);
  rData.Position := 0;
  for each adsResult in blockResult index i do
  begin
    if (adsResult = 0) and (rLen[i] = 4) then
      getHandles[i] := reader.ReadInt32()
    else
      MessageBox.Show("CreateVariableHandle failed! Error: " + adsResult.ToString() , "TwinCAT.Ads");
  end;

  result := getHandles;
end;

Realease variable handles

The method BlockDeleteVariableHandle uses the method BlockWrite. The handles to be enabled are copied before in the AdsStream (Write-Data).

//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Release multiple symbol handles
method MainForm.BlockDeleteVariableHandle(handles : Array of Int32 ): Array of Int32;
begin
  var data := new AdsStream(handles.Length * 4);
  var writer := new BinaryWriter(data);
  var ig := new Int32[handles.Length];
  var wLen := new Int32[handles.Length];

  for each handle in handles index i do
  begin
    writer.Write(handle);
    ig[i] := Int32(AdsReservedIndexGroups.SymbolReleaseHandle);
    wLen[i] := sizeOf(handle);// = 4 byte
  end;

  var blockResult := BlockWrite(ig, handles, wLen, data);
  for each adsResult in blockResult do
    if adsResult <> 0 then
      MessageBox.Show("DeleteVariableHandle failed! Error: " + adsResult.ToString() , "TwinCAT.Ads");

  result := blockResult;
end;

Generic ADS read sum command

The parameter for the ADS sum command consists of:
  • IndexGroup = ADSIGRP_SUMUP_READ = 0xF080;
  • IndexOffset = Number of ADS sub commands (in the sample = 3);
  • readData (AdsStream) = Memory that takes read data = ListOf(Ads result) + ListOf(data);
  • writeData (AdsStream) = Memory that contains data to be sent = ADS sub commands = ListOf(indexGroup + indexOffset + readLength);

Arrangement of the ADS sub command in writeData during writing the PLC variables values:

Arrangement of return data in readData during reading the PLC variables values:

The return values of the single ADS subcommands are extracted from the readData-Stream and returned from the BlockWrite method as return value. The read data (e.g. PLC variables values) are copied into the data method parameter.

The rLen values are aligned ( in error case the data length = Null).

//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Ads block read
method MainForm.BlockRead( ig : Array of Int32; io : Array of Int32; var rLen : Array of Int32;  var data : AdsStream) : Array of Int32;
begin
  var count := ig.Length;
  var blockResult := new Int32[count];
  try
    var writeData := new AdsStream(count * 12);
    var writer := new BinaryWriter(writeData);
    var cbData : Int32 := 0;

    for each len in rLen index i do
    begin
      writer.Write(ig[i]);
      writer.Write(io[i]);
      writer.Write(len);
      cbData := cbData + len;
    end;

    if cbData <> data.Length then
      MessageBox.Show("BlockRead()::Invalid read data length!", "TwinCAT.Ads");

    var readData := new AdsStream((count * 4) + data.Length);
    var reader := new BinaryReader(readData);

    adsClient.ReadWrite(ADSIGRP_SUMUP_READ, count, readData, writeData);

    readData.Position := 0;
    for each adsResult in blockResult index i do
      blockResult[i] := reader.ReadInt32();

    // Copy read data
    var copy := new BinaryWriter(data);
    data.Position := 0;
    for each adsResult in blockResult index i do
    begin
      if adsResult = 0 then// no error => copy data, error => don't copy data
        copy.Write(reader.ReadBytes(rLen[i]))
      else
        rLen[i] := 0;
    end;
  except
    On err : AdsErrorException do
      MessageBox.Show(err.Message, err.Source)
  end;

  result := blockResult;
end;

Generic ADS write sum command

The parameter of the ADS write sum command consists of:
  • IndexGroup = ADSIGRP_SUMUP_WRITE = 0xF081;
  • IndexOffset = Number of ADS sub commands (in the sample = 3);
  • readData (AdsStream) = Memory that takes read data = ListOf(Ads result);
  • writeData (AdsStream) = Memory that contains data to be sent = ADS sub commands = ListOf(indexGroup + indexOffset + writeLength) + ListOf(data);

The BlockWrite method is used in two cases ( in this sample):

  • Writing the PLC variables values => the method parameter data contains the new PLC variables values;
  • Enable the variable handles => the method parameter data contains handles to be enabled;

The content of data is copied into writeData.

Arrangement of the ADS sub command in writeData during writing the PLC variables values:

Arrangement of return data in readData during reading the PLC variables values:

The return values of the single ADS subcommands are extracted from the readData-Stream and returned from the BlockWrite method as return value.

//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Ads block write
method MainForm.BlockWrite(ig : Array of Int32; io : Array of Int32; wLen : Array of Int32; data : AdsStream) : Array of Int32;
begin
  var count := ig.Length;
  var blockResult := new Int32[count];
  try
    var writeData := new AdsStream((count * 12 ) + data.Length);
    var writer := new BinaryWriter(writeData);
    var cbData : Int32 := 0;

    for each len in wLen index i do
    begin
      writer.Write(ig[i]);
      writer.Write(io[i]);
      writer.Write(len);
      cbData := cbData + len;
    end;

    if cbData <> data.Length then
      MessageBox.Show("BlockWrite()::Invalid write data length!", "TwinCAT.Ads");

    writer.Write(data.ToArray());

    var readData := new AdsStream(count * 4);
    var reader := new BinaryReader(readData);

    adsClient.ReadWrite(ADSIGRP_SUMUP_WRITE, count, readData, writeData);
    readData.Position := 0;
    for each adsResult in blockResult index i do
      blockResult[i] := reader.ReadInt32();

  except
    On err : AdsErrorException do
      MessageBox.Show(err.Message, err.Source)
  end;

  result := blockResult;
end;

Generic ADS read/write sum command

The BlockReadWrite method is a combination of BlockRead and BlockWrite method. The parameter of the ADS readwrite sum command consists of:

  • IndexGroup = ADSIGRP_SUMUP_READWRITE = 0xF082;
  • IndexOffset = Number of ADS sub commands (in the sample = 3);
  • readData (AdsStream) = Memory that takes read data = ListOf(Ads result + readLength) + ListOf(data);
  • writeData (AdsStream) = Memory that contains data to be sent = ADS sub commands = ListOf(indexGroup + indexOffset + readLength + writeLength) + ListOf(data);

The BlockReadWrite method is used in this example when fetching the handles. In this case the wData method parameter contains the strings with the PLC symbol names. The symbol names are copied into writeData before the call. The handles are located in readData after the call and are copied from there into the rData method parameter. The rLen values are also adapted (in case of error the data length = zero). The return values of the individual ADS sub-commands are extracted from the readData stream and returned by the BlockReadWrite method as a return value.

//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Ads block read/write
method MainForm.BlockReadWrite( ig, io, wLen : Array of Int32;  wData : AdsStream;
                              var rLen : Array of Int32; var rData : AdsStream) : Array of Int32;
begin
  var count := ig.Length;
  var blockResult := new Int32[count];
  try
    var writeData := new AdsStream((count * 16) + wData.Length);// 16 = sizeof(indexGroup + indexOffset + readLen + writeLen)
    var writer := new BinaryWriter(writeData);
    var cbWData : Int32 := 0;
    var cbRData : Int32 := 0;

    for each len in wLen index i do
    begin
      writer.Write(ig[i]);
      writer.Write(io[i]);
      writer.Write(rLen[i]);
      writer.Write(wLen[i]);
      cbWData := cbWData + wLen[i];
      cbRData := cbRData + rLen[i];
    end;

    if cbRData <> rData.Length then
      MessageBox.Show("BlockReadWrite()::Invalid read data length!", "TwinCAT.Ads");

    if cbWData <> wData.Length then
      MessageBox.Show("BlockReadWrite()::Invalid write data length!", "TwinCAT.Ads");

    writer.Write(wData.ToArray());

    var readData := new AdsStream((count * 8) + rData.Length);
    var reader := new BinaryReader(readData);

    adsClient.ReadWrite(ADSIGRP_SUMUP_READWRITE, count, readData, writeData);

    readData.Position := 0;
    for each adsResult in blockResult index i do
    begin
      blockResult[i] := reader.ReadInt32();
      rLen[i] := reader.ReadInt32();
    end;

    // Copy data
    var copy := new BinaryWriter(rData);
    rData.Position := 0;
    for each adsResult in blockResult index i do
    begin
      if adsResult = 0 then// no error => copy data, error => don't copy data
        copy.Write(reader.ReadBytes(rLen[i]));
    end;

  except
    On err : AdsErrorException do
      MessageBox.Show(err.Message, err.Source)
  end;

  result := blockResult;
end;

end.

PLC program

PROGRAM MAIN
VAR
    uintValue : UDINT := 54321;
    boolValue : BOOL := TRUE;
    dintValue : DINT := -12345678;
END_VAR
;

Language / IDE | Unpack the sample program
Delphi Prism (Embarcadero Prism XE2, Oxygen for .NET) | Sample09.exe


Delphi ADS.NET Sample11

TwinCAT Sample - Delphi - ADS .NET

Delete a handle of a PLC variable

Console application. This Sample shows how to delete a handle of a PLC variable.

Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) program

namespace Sample11;

interface
uses TwinCAT.Ads;

type
  ConsoleApp = class
  public
    class method Main(args: array of string);
  end;

implementation

class method ConsoleApp.Main(args: array of string);
begin
  // Create a new instance of class TcAdsClient
  var tcClient := new TcAdsClient();
  var iHandle : Int32;

  try
    // Connect to local PLC - Runtime 1 - Port 801
    tcClient.Connect(801);

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

    // Use the handle to read from PLC variable
    var sOldValue := tcClient.ReadAny(iHandle, TypeOf(UInt32)).ToString();
    Console.WriteLine("PLCVar: " + sOldValue);

    // Use the handle to write to PLC variable, e.g. reset PLCVar
    var newValue := UInt32(0);
    tcClient.WriteAny(iHandle, newValue);
  except
    on err : Exception do
      Console.WriteLine(err.Message);
  finally
    // Release the variable handle
    tcClient.DeleteVariableHandle(iHandle);

    // Close connection to the PLC
    tcClient.Dispose();
  end;

  // Wait for user input
  Console.ReadKey();
end;

end.

PLC program

PROGRAM MAIN
VAR
    PLCVar : DWORD;
    timer : TON;
END_VAR
timer( IN := TRUE, PT := t#1s);
IF timer.Q THEN
    timer( IN := FALSE );
    timer( IN := TRUE );
    PLCVar := PLCVar + 1;
END_IF

Language / IDE | Unpack the sample program
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample11.exe


Delphi ADS.NET Sample12

TwinCAT Sample - Delphi - ADS .NET

Read flag synchronously from the PLC

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

Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) program

namespace Sample12;

interface
uses System.IO,TwinCAT.Ads;

type
  ConsoleApp = class
  public
    class method Main(args: array of string);
  end;

implementation

class method ConsoleApp.Main(args: array of string);
begin
   Console.WriteLine("Please enter 'e' to exit or any key to read...");
  // Create a new instance of class TcAdsClient
  var tcClient := new TcAdsClient();
  try
    // Connect to local PLC - Runtime 1 - Port 801
    tcClient.Connect(801);
    // Read PLC variable
    while (Console.ReadKey().KeyChar <> 'e') do
    begin
      // Specify indexGroup, indexOffset and read PLCVar
      var dwFlag := tcClient.ReadAny($4020, $0, typeof(UInt32));
      Console.WriteLine("PLCVar: " + dwFlag.ToString());
    end;
  except
    on err : Exception do
    begin
      Console.WriteLine(err.Message);
      Console.ReadKey();
    end;
  finally
    // Close connection
    tcClient.Dispose();
  end;
end;

end.

PLC program

PROGRAM MAIN
VAR
    PLCVar AT %MD0 : DWORD;
    timer : TON;
END_VAR
timer( IN := TRUE, PT := t#1s);
IF timer.Q THEN
    timer( IN := FALSE );
    timer( IN := TRUE );
    PLCVar := PLCVar + 1;
END_IF

Language / IDE | Unpack the sample program
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample12.exe


Delphi ADS.NET Sample13

TwinCAT Sample - Delphi - ADS .NET

Write flag synchronously into the PLC

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

Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) program

namespace Sample13;

interface
uses TwinCAT.Ads;

type
  ConsoleApp = class
  public
    class method Main(args: array of string);
  end;

implementation

class method ConsoleApp.Main(args: array of string);
begin
  Console.WriteLine("Please enter 'e' to exit or integer value and press Enter when ready...");
  // Create a new instance of class TcAdsClient
  var tcClient := new TcAdsClient();
  var sInput : String;
  var newValue : UInt32;
  try
    // Connect to local PLC - Runtime 1 - Port 801
    tcClient.Connect(801);
    repeat
      sInput := Console.ReadLine();
      if sInput = 'e' then
        break;
      newValue := UInt32.Parse(sInput);
      // Specify indexGroup, indexOffset and write PLCVar
      tcClient.WriteAny($4020, $0, newValue );
    until sInput = 'e';

  except
    on err : Exception do
    begin
      Console.WriteLine(err.Message);
      Console.ReadKey();
    end;
  finally
    // Close connection
    tcClient.Dispose();
  end;
end;

end.

PLC program

PROGRAM MAIN
VAR
    PLCVar AT %MD0 : DWORD;
END_VAR
;

Language / IDE | Unpack the sample program
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample13.exe


Delphi ADS.NET Sample14

TwinCAT Sample - Delphi - ADS .NET

Start/stop PLC

Console application. The following program starts or stops run-time system 1 in the PLC.

Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) program

namespace Sample14;

interface

uses System.IO, TwinCAT.Ads;

type
  ConsoleApp = class
  public
    class method Main(args: array of string);
  end;

implementation

class method ConsoleApp.Main(args: array of string);
begin
  Console.WriteLine("Please enter 'r' to RUN or 's' to STOP the PLC or any key to exit...");
  // Create a new instance of class TcAdsClient
  var tcClient := new TcAdsClient();
  var sInput : String;
  try
    // Connect to local PLC - Runtime 1 - Port 801
    tcClient.Connect(801);
    repeat
      // Get current state
      var oldState := tcClient.ReadState();
      Console.WriteLine("State: " + oldState.AdsState.ToString());

      // Read user input
      sInput := Console.ReadKey().KeyChar;
      if (sInput <> 'r') And (sInput <> 's') then
        break;

      // Set new state
      var newState := new StateInfo(iif(sInput = 'r', AdsState.Run, AdsState.Stop), oldState.DeviceState);
      tcClient.WriteControl(newState);

    until (sInput <> 'r') And (sInput <> 's');
  except
    On err : Exception do
    begin
      Console.WriteLine(err.Message);
      Console.ReadKey();
    end;
  finally
    // Close connection
    tcClient.Dispose();
  end;

end;

end.

Language / IDE | Unpack the sample program
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample14.exe


Delphi ADS.NET Sample15

TwinCAT Sample - Delphi - ADS .NET

Access by variable name

Console application. 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.

Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) program

namespace Sample15;

interface

uses System.IO, TwinCAt.Ads, System.Data, System.Runtime.InteropServices;

type
  ConsoleApp = class
  public
    class method Main(args: array of string);
  end;

implementation

class method ConsoleApp.Main(args: array of string);
begin
  Console.WriteLine("Please enter 'e' to exit or any key to read...");

  // Create a new instance of class TcAdsClient
  var tcClient := new TcAdsClient();
  var dwValue : UInt32 := 0;
  var iHandle : Int32 := 0;
  try
    // Connect to local PLC - Runtime 1 - Port 801
    tcClient.Connect(801);
    // Get the handle of the PLC variable "SPSVar"
    iHandle := tcClient.CreateVariableHandle("MAIN.PLCVar");

    while (Console.ReadKey().KeyChar <> 'e') do
    begin
      dwValue := UInt32(tcClient.ReadAny(iHandle, TypeOf(UInt32)));
      Console.WriteLine("Value: " + dwValue.ToString());

      if dwValue > 10 then
        tcClient.WriteAny(iHandle, UInt32(0));
    end;

  except
    On err : Exception do
    begin
      Console.WriteLine(err.Message);
      Console.ReadKey();
    end;
  finally
    // Release variable handle
    tcClient.DeleteVariableHandle(iHandle);

    // Close connection
    tcClient.Dispose();
  end;

end;

end.

PLC program

PROGRAM MAIN
VAR
    PLCVar : DWORD;
    timer : TON;
END_VAR
timer( IN := TRUE, PT := T#1s );
IF timer.Q THEN
    timer( IN := FALSE );
    timer( IN := TRUE );
    PLCVar := PLCVar + 1;
END_IF

Language / IDE | Unpack the sample program
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample15.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