Hinweise zur Dokumentation

Diese Beschreibung wendet sich ausschließlich an ausgebildetes Fachpersonal der Steuerungs- und Automatisierungstechnik, das mit den geltenden nationalen Normen vertraut ist.
Zur Installation und Inbetriebnahme der Komponenten ist die Beachtung der nachfolgenden Hinweise und Erklärungen unbedingt notwendig.

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

Disclaimer

Diese Dokumentation wurde sorgfältig erstellt. Die beschriebenen Produkte werden jedoch ständig weiter entwickelt.
Deshalb ist die Dokumentation nicht in jedem Fall vollständig auf die Übereinstimmung mit den beschriebenen Leistungsdaten, Normen oder sonstigen Merkmalen geprüft.
Falls sie technische oder redaktionelle Fehler enthält, behalten wir uns das Recht vor, Änderungen jederzeit und ohne Ankündigung vorzunehmen.
Aus den Angaben, Abbildungen und Beschreibungen in dieser Dokumentation können keine Ansprüche auf Änderung bereits gelieferter Produkte geltend gemacht werden.

Marken

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

Patente

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

Die TwinCAT Technologie ist patentrechtlich geschützt, insbesondere durch folgende Anmeldungen und Patente:
EP0851348, US6167425 mit den entsprechenden Anmeldungen und Eintragungen in verschiedenen anderen Ländern.

Copyright

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


Delphi Beispiele: Übersicht

TwinCAT Sample - Delphi

Übersicht

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

√ = Empfohlene Zielplattform

Beispiele ADS-OCX

Beschreibung
Einbinden in Delphi 3, 4, 5, 6, 7, ... (classic)
Einbinden in Borland Developer Studio 2006
ADS-OCX Einschränkungen in Delphi
ADS-OCX Applikation nach einer Exception zurücksetzen
Beschreibung | Delphi XE2 | Delphi 5, 6, 7, ... (classic)
Beispiel 1: Synchron/Asynchron/Connected auf SPS-Variablen zugreifen | Sample01.exe | Sample01.exe
Beispiel 2: Liste der deklarierten Variablen eines ADS-Gerätes auslesen | Sample02.exe | Sample02.exe
Beispiel 3: Array in die SPS schreiben oder aus der SPS lesen | Sample03.exe | Sample03.exe
Beispiel 4: ADS-OCX Property-Seiten aufrufen | Sample04.exe | Sample04.exe
Beispiel 5: Arbeiten mit Handles von SPS-Variablen | Sample05.exe | Sample05.exe
Beispiel 6: String in die SPS schreiben oder aus der SPS lesen | Sample06.exe | Sample06.exe
Beispiel 7: Mehrere boolsche Variablen mit einem Zugriff in ein Array einlesen | Sample07.exe | Sample07.exe
Beispiel 8: Übertragen von Strukturen zur/von der SPS | Sample08.exe | Sample08.exe

Beispiele ADS-DLL für Windows NT/2000/XP

API-Interface

Beschreibung
Einbinden in Delphi (allgemein)
Beschreibung | Delphi XE2 | Delphi 5, 6, 7, ... (classic)
Beispiel 1: All-In-One Beispiel | Sample01.exe | Sample01.exe
Beispiel 2: Ereignisgesteuertes Lesen | Sample02.exe | Sample02.exe
Beispiel 3: Synchroner Zugriff auf die SPS-Variablen | Sample03.exe | Sample03.exe
Beispiel 4: Statusänderung vom TwinCAT-Router und der SPS erkennen | Sample04.exe | Sample04.exe
Beispiel 5: SPS starten/stoppen | Sample05.exe | Sample05.exe
Beispiel 6: Erweiterte Funktionen (für Multithreading-Applikationen) | Sample06.exe | Sample06.exe
Beispiel 7: ADS-Summenkommando: Das Lesen oder Schreiben von mehreren Variablen mit einem ADS-Befehl | Sample07.exe | Sample07.exe
Beispiel 8: ADS-Summenkommando: Das Holen und Freigeben von mehreren Variable-Handles mit einem ADS-Befehl | Sample08.exe | Sample08.exe
Beispiel 9: Lesen und Schreiben von DT/DATE/TOD/TIME-Variablen | Sample09.exe | Sample09.exe

COM-Interface

Beschreibung
Einbinden in Delphi XE2
Einbinden in Delphi 5, 6, 7, ... (classic)
Beschreibung | Delphi XE2 | Delphi 5, 6, 7, ... (classic)
Beispiel 1: Lesen und Schreiben von SPS Variablen | Sample01.exe | Sample01.exe
Beispiel 2: Ereignisgesteuertes Lesen von SPS Variablen | Sample02.exe | Sample02.exe

Beispiele ADS .NET

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

Beispiele ADS-WebService

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


ADS Return Codes

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

Global Error Codes

Hex | Dec | Description | Possible Causes | Solution
0x0 | 0 | no error |  |
0x1 | 1 | Internal error |  |
0x2 | 2 | No Rtime |  |
0x3 | 3 | Allocation locked memory error |  |
0x4 | 4 | Insert mailbox error | No ADS mailbox was available to process this message. | Reduce the number of ADS calls (e.g ADS-Sum commands or Max Delay Parameter)
0x5 | 5 | Wrong receive HMSG |  |
0x6 | 6 | target port not found | ADS Server not started |
0x7 | 7 | target machine not found | Missing ADS routes |
0x8 | 8 | Unknown command ID |  |
0x9 | 9 | Bad task ID |  |
0xA | 10 | No IO |  |
0xB | 11 | Unknown ADS command |  |
0xC | 12 | Win 32 error |  |
0xD | 13 | Port not connected |  |
0xE | 14 | Invalid ADS length |  |
0xF | 15 | Invalid 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 Beispiel: Einbinden in Borland Developer Studio 2006

TwinCAT ADS-OCX: Einbinden in Delphi

Einbinden in Borland Developer Studio 2006 (VCL for Delphi Win32)

Diese Anleitung kann auch benutzt werden um das ADS-OCX in Borland Delphi 2005 einzubinden. Die Unterschiede zu "Borland Delphi 2006" oder "Delphi XE2" sind nur geringfügig.

Schritt 1

Zuerst muss ein Delphi-Unit aus dem ActiveX-Steuerelement abgeleitet werden. Dazu wählen Sie unter dem Menüpunkt "Komponente" die "Komponente importieren..."

Schritt 2

Es öffnet sich der Komponenten-Wizard. Nach der Anwahl von "VCL for Delphi Win32" bestätigen Sie mit "Weiter>>".

Schritt 3

Im nächsten Dialog wählen Sie "ActiveX-Steuerelement importieren" und klicken auf "Weiter>>".

Schritt 4

Aus der Liste der registrierten ActiveX-Steuerelemente wählen Sie nun die entsprechende Komponente aus (AdsOcx OLE Control Module). Falls die Komponente in der Liste der registrierten Elemente nicht erscheint, muss sie über den Button "Hinzufügen" registriert und eingebunden werden. Anschließend klicken Sie auf "Weiter".

Schritt 5

Im nächsten Wizard-Fenster wählen Sie die VCL Palettenseite und das Verzeichnis für das neu erstellte Unit (Standardeinstellung: C:\program files\borland\bds\4.0\Imports\ ).
Bestätigen Sie mit "Weiter".

Schritt 6

Anschließend wird ein Unit für die ActiveX-Komponente generiert. Dazu müssen Sie mit "Beenden" bestätigen.

Das generierte Unit wird zur Kontrolle automatisch geöffnet:

Schritt 7

Im nächsten Schritt muss ein neues Package erzeugt werden. Dazu klicken Sie auf "Datei -> Neu -> Package" im Hauptmenü.

Schritt 8

In das neu angelegte Package muss nun das zuvor generierte Unit eingefügt werden.
Wählen Sie mit einem Klick der rechten Maustaste im Projektmanager den Eintrag "Package1.bpl" und im sich öffnenden Kontextmenü "Hinzufügen".

Schritt 9

Im Fenster "Hinzufügen" geben Sie den Speicherort der Unit an, der vorher für die ActiveX-Komponente generiert wurde (Standardeinstellung: C:\program files\borland\bds\4.0\Imports\ADSOCXLib_TLB.pas).

Schritt 10

Um das Package kompilieren zu können, gehen Sie mit der rechten Maustaste auf "Package1.bpl" und wählen im Kontextmenü "Compilieren".

Schritt 11

Nach dem Compilieren des neuen Package wählen Sie im Kontextmenü "Installieren".

Die Installation ist damit beendet. Die folgende Meldung erscheint:

Nach der Installation

Die ActiveX-Komponente erscheint in der festgelegten Kategorie wenn Sie z.B. eine neue "VCL Forms Application - Delphi for Win32" erstellen.


Delphi AdsOcx Beispiel: Einbinden in Delphi

TwinCAT ADS-OCX: Einbinden in Delphi

Einbinden in Delphi 3,4,5,6,7, ... (classic)

Bitte beachten Sie die Hinweise in der Knowledge-Base über die Einschränkungen und Limitierungen beim Einsatz vom AdsOcx in Delphi-Anwendungen.

ActiveX-Controls können auf zweifache Weise in Delphi eingebunden werden:

1. Einbinden über den Import des ActiveX-Controls

2. Einbinden über den Import der Typ-Bibliothek des ActiveX-Controls

Bei den älteren Versionen von Delphi müssen Sie noch:

3. AdsOcx über die generierte Typ-Bibliothek in die Komponentenpalette installieren

1. Einbinden über den Import des ActiveX-Controls

1.1 Über den Menübefehl Component->Import ActiveX Control das Import ActiveX-Dialogfenster aufrufen.

1.2 In dem Dialogfenster markieren Sie aus der Liste der ActiveX-Controls das AdsOcx OLE Control module und bestätigen mit einem Mausklick auf Install.... Befindet sich das AdsOcx-Control nicht in der Liste, dann können Sie es über den Befehl Add... in die Liste hinzufügen. Das AdsOcx befindet sich standardmäßig in dem .../WinNT/System32 - Ordner.

1.3 In dem Install-Dialogfenster mit OK bestätigen.

1.4 Das Package mit Benutzerdefinierten-Komponenten muss neu kompiliert werden. Bestätigen Sie mit Yes.

1.5 Beim Erfolg wird die AdsOcx-Komponente registriert. Bestätigen sie mit OK.

1.6 Schließen Sie den Package-Editor und speichern Sie die Änderungen mit Yes.

Ab jetzt können Sie die AdsOcx-Komponente aus der ActiveX-Komponentenpalette in einem neuen Projekt benutzen.

2. Einbinden über den Import der Typ-Bibliothek des ActiveX-Controls

2.1 Um das AdsOcx in der Komponenten-Palette von Delphi einbinden zu können, muss zuerst eine Typ-Bibliothek ( mit den Prototypen der Funktionen, Prozeduren und Datentypdefinitionen des ActiveX-Controls ) generiert werden.

Die Typ-Bibliothek kann über das Menü Project -> Import Type Library generiert werden.

2.2 In dem danach folgendem Dialogfenster markieren Sie aus der Liste der ActiveX-Controls das AdsOcx OLE Control module und bestätigen mit Install....

Befindet sich das AdsOcx nicht in der Auswahlliste, dann können Sie es über den Befehl Add... in die Liste hinzufügen. Das AdsOcx befindet sich standardmäßig in dem Ordner .../WinNT/System32 und wird bei der TwinCAT-Installation dorthin kopiert.

2.3 In den neueren Versionen von Delphi (z.B. Delphi 5.0) wird die importierte Typ-Bibliothek sofort in die Komponentenpalette hinzugefügt. Bei den älteren Versionen wird nur die Typ-Bibliothek ( z.B. in dem Ordner .../Delphi 3/Imports ) generiert und Sie müssen das AdsOcx über die generierte Typ-Bibliothek in die Komponentenpalette installieren.. Haben Sie eine neuere Version, bestätigen Sie in dem nachfolgendem Dialog mit OK.

2.4 Das Package mit Benutzerdefinierten-Komponenten muss neu kompiliert werden. Bestätigen Sie mit Yes.

2.5 Beim Erfolg wird die AdsOcx-Komponente registriert. Bestätigen sie mit OK.

2.6 Schließen Sie den Package-Editor und speichern Sie die Änderungen mit Yes.

Ab jetzt können Sie die AdsOcx-Komponente aus der ActiveX-Komponentenpalette in einem neuen Projekt benutzen.

3. AdsOcx über die generierte Typ-Bibliothek in die Komponentenpalette installieren

3.1 Nachdem die Typ-Bibliothek generiert wurde, kann aus der dabei erzeugten Pascal-Datei (standardmäßig wird die Datei ADSOCXLib_TLB.pas erzeugt) das AdsOcx als neue Komponente in die Komponenten-Palette hinzugefügt werden. Dazu müssen Sie den Menübefehl: Component->Install Component... auswählen.

3.2 In dem Dialogfenster muss über den Befehl Browse... die vorher erstellte Typ-Bibliothek ausgewählt werden. Die generierten Typ-Bibliotheken befinden sich standardmäßig in dem .../Delphi 3/Imports/ Ordner. Wählen sie die Typ-Bibliothek aus und bestätigen mit Open.

3.3 Die Komponenten-Palette muss anschließend neu Compiliert werden. Bestätigen Sie mit Yes.

3.4 Nach einer erfogreichen Compilierung wird das ActiveX-Control registriert. Bestätigen Sie mit OK.

3.5 Die Änderungen in dem Komponenten-Package müssen beim Schließen gespeichert werden. Das AdsOcx-ActiveX-Control kann jetzt von der Komponenten-Palette auf die Form gezogen und ähnlich wie alle anderen Delphi-Komponenten benutzt werden.


Delphi AdsOcx Sample: Einschränkungen in Delphi-Anwendungen

TwinCAT ADS-OCX: Einbinden in Delphi

ADS-OCX Einschränkungen in Delphi-Anwendungen

Delphi's Memory Manager

In der AdsOcx Applikation müssen Sie sicherstellen daß die Systemvariable: IsMultiThread auf jeden Fall auf True gesetzt ist. Der Memory Manager ist "thread-safe" nur wenn diese Variable gesetzt ist. Nur dann werden auch die Zugriffe auf gemeinsame Resourcen verriegelt. Oft setzt der Memory Manager von Delphi diese Variable nicht wenn eine eingebundene DLL oder ein Control eigene Threads startet.

Fügen Sie folgende Zeile in die initialization section ihrer Applikation:

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

Methoden/Eigenschaften

Folgende Eigenschaften, Methoden und Ereignisse verursachen Fehler in Delphi-Applikationen und dürfen nicht benutzt werden. Wie aus der Tabelle zu sehen ist, sollte die neueste Version von Delphi benutzt werden oder es muss auf einige Funktionalitäten verzichtet werden. Zu den aufgeführten Delphi-Versionen gibt es verschiedene Updates, die möglicherweise einige Fehler beheben.

 | Fehlerbeschreibung | Workaround | Delphi-Version
Eigenschaften |  |  | 3.0 | 4.0 | 5.0 | 6.0
AdsClientType | Beim Zugriff auf die Eigenschaft entsteht Memory-Leak. Speicher für den zurückgelieferten String wird nicht korrekt zurückgegeben. | n/a | Bug | ? | Fixed |
AdsClientAdsState | Beim Zugriff auf die Eigenschaft entsteht Memory-Leak. Speicher für den zurückgelieferten String wird nicht korrekt zurückgegeben. | n/a | Bug | ? | Fixed |
AdsClientAdsControl | Beim Zugriff auf die Eigenschaft entsteht Memory-Leak. Speicher für den zurückgelieferten String wird nicht korrekt zurückgegeben. | n/a | Bug | ? | Fixed |
AdsServerAdsControl | Beim Zugriff auf die Eigenschaft entsteht Memory-Leak. Speicher für den zurückgelieferten String wird nicht korrekt zurückgegeben. | n/a | Bug | ? | Fixed |
AdsServerAdsState | Beim Zugriff auf die Eigenschaft entsteht Memory-Leak. Speicher für den zurückgelieferten String wird nicht korrekt zurückgegeben. | n/a | Bug | ? | Fixed |
AdsServerType | Beim Zugriff auf die Eigenschaft entsteht Memory-Leak. Speicher für den zurückgelieferten String wird nicht korrekt zurückgegeben. | n/a | Bug | ? | Fixed |
AdsServerLastMessage | Beim Zugriff auf die Eigenschaft entsteht Memory-Leak. Speicher für den zurückgelieferten String wird nicht korrekt zurückgegeben. | n/a | Bug | ? | Fixed |
AdsAmsClientNetId | Beim Zugriff auf die Eigenschaft entsteht Memory-Leak. Speicher für den zurückgelieferten String wird nicht korrekt zurückgegeben. | n/a | Bug | ? | Fixed |
AdsAmsServerNetId | Beim Zugriff auf die Eigenschaft entsteht Memory-Leak. Speicher für den zurückgelieferten String wird nicht korrekt zurückgegeben. | n/a | Bug | ? | Fixed |
Methoden |  |  |  |  |  |
Alle Methoden | Die Funktionen der generierten Typbibliothek ADSOCXLib_TLB liefern undefinierte Rückgabeparameter. | Bitte installieren Sie den Delpphi 6 Update Pack 2 und binden Sie das ADSOCX neu ein. | - | - | kein Bug | Bug
AdsSyncReadReq AdsSyncWriteReq | Mit diesen Methoden können Variablen vom beliebigen Typ in die SPS übertragen oder aus der SPS gelesen werden. Die OleVariant-Parameter werden aber bei der AdsSyncReadReq-Methode per Wert und nicht per Reference übergeben. D.h. während des Aufrufs kann der Wert des data-Parameters von der Methode nicht geändert werden. Während des Aufrufs werden die SPS-Variablen zwar in eine entsprechende OleVariant-Variable hineinkopiert, die Variable ist aber nur eine Kopie der Variablen aus der aktuellen Parameterliste. Die Methoden-Prototypen für das ADS-OCX werden von der Delphi-Entwicklungsumgebung beim Einbinden des ADS-OCX automatisch generiert und können nicht beinflußt werden. | Benutzen Sie die "aufgelösten" Methoden um auf die SPS-Variablen synchron zuzugreifen (z.B. AdsSyncReadIntegerReq() u.s.w. ). | Bug | ? | Bug |
AdsReadVarConnectEx | Ähnlich wie bei den AdsSync-Methoden, werden die OleVariant-Parameter in den Ereignisfunktionen per Wert und nicht per Referenz übergeben. | Benutzen Sie die Methode AdsReadVarConnectEx2 | Bug | ? | Bug |
Ereignisse |  |  |  |  |  |
AdsReadConnectUpdateEx | Beim Aufrufen der Ereignisfunktion wird eine Zugriffsverletzung generiert. | Benutzen Sie die Ereignisfunktion AdsReadConnectUpdateEx2 | Bug | ? | Bug |


Delphi AdsOcx Sample: Appliaktion zuruecksetzen

TwinCAT ADS-OCX: Einbinden in Delphi

ADS OCX Applikation zurücksetzen

Nach einem Programmfehler kann oft nicht, wie sonst üblich, über die Bedienfunktion "Start -> Programm Reset" die Applikation beendet werden. Folgende Meldung des Debuggers ist die Folge:

Die Ursache liegt darin, dass bei der Benutzung des ADS-OCX eine Client-Server Verbindung zum TwinCAT Router erzeugt wird, die beim Beenden der Applikation abgebaut werden muss. Die Delphi-Applikation kann über den Menübefehl "Programm Reset" nicht beendet werden weil zu diesem Zeitpunkt eine Verbindung zum TwinCAT Router besteht. In der Applikation wird die Verbindung durch Zuweisung der AdsAmsNetId und der Portnummer erzeugt.

Es gibt folgende Möglichkeiten die Applikation zu beenden ohne den Rechner neu starten zu müssen:

  • Zuerst den Laufzeitfehler mit OK bestätigen, dann TwinCAT System Stop über die Taskleiste aufrufen und dann Delphi-Applikation zurücksetzen. Es werden dabei bestehende Verbindungen zu den Clients abgebaut. Nachteil: Das TwinCAT System und die SPS muss nachher neu gestartet werden;
  • Zuerst den Laufzeitfehler mit OK bestätigen und Router-Cleanup über die Taskleiste aufrufen und dann Delphi-Applikation zurücksetzen;
  • Exception-Handling benutzen. Über die Methode AdsAmsDisconnect() kann die Verbindung zum Router explizit abgebaut werden;
        try
        n:=8; Switch[n].Tag:=0; // Dieser Index ist unzulässig
    except
        on EAccessViolation do
        begin
            AdsOcxSPS.AdsAmsDisconnect();
            Application.Terminate();
        end;
    end;


Delphi AdsOcx Beispiel01: Synchron/Asynchron/Connected auf SPS-Variablen zugreifen

TwinCAT ADS-OCX: Beispiele

Synchron/Asynchron/Connected auf SPS-Variablen zugreifen

Systemvoraussetzungen:

  • Delphi 5.0 oder höher;
  • TwinCAT v2.9 oder höher;

Aufgabe

Das Beispielprogramm zeigt wie Methoden und Events des AdsOcx in einer Delphi-Anwendung benutzt werden können. Dabei werden die unterschiedlichsten Zugriffsarten (Synchron/Asynchron/Connected) auf die SPS-Variablen vorgestellt. In dem SPS-Programm wurde eine Integer-Variable auf der Adresse 100 im Merkerbereich der Prozessdaten der SPS definiert. Auf die SPS-Variable soll über die unterschiedlichsten Zugriffsarten schreibend oder lesend aus der Delphi-Applikation zugegriffen werden.

Beschreibung

Auf die SPS-Variablen ist über das AdsOcx ein Synchroner-, Asynchroner- oder Connected-Zugriff möglich. Bei einem synchronen Zugriff wird die Applikation so lange angehalten, bis die angeforderten Daten vorliegen. Bei einem asynchronen Zugriff wird eine Anforderung (Request) an die SPS gesendet und die Ausführung der Windows-Applikation fortgesetzt. In der Windows-Applikation wird dann eine Callback-Funktion aufgerufen, wenn die angeforderten Daten vorliegen. Bei dem Connected-Zugriff kann in der Windows-Applikation eine Event-Funktion aufgerufen werden, wenn sich der Wert der SPS-Variablen geändert hat.

Delphi 5 Programm

In der Event-Funktion OnFormCreate wird über die Methode AdsCreateVarHandle ein Handle für den Sysmbolnamen der SPS-Variablen angefordert. Das Handle wird dann in der Beispielapplikation für den schreibenden oder lesenden Zugriff auf die SPS-Variable benutzt. In der Event-Funktion OnDestroy wird das Handle beim Beenden der Applikation mit der Methode AdsDeleteVarHandle freigegeben.

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;

Synchroner Zugriff

Bei einem Mausklick auf einen der Buttons in der Gruppe SYNCHRONOUS wird der Wert der SPS-Variablen synchron gelesen bzw. geschrieben und als Text auf der Form ausgegeben. Es kann auf zweifache Weise auf die SPS-Variable zugegriffen werden: Über den Variablen-Namen oder über die Variablen-Adresse.

Zugriff über die Variablen-Adresse

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;

Zugriff über den Variablen-Namen

Bei einem Zugriff über den Variablen-Namen wird das entsprechende Handle der SPS-Variablen als Parameter in der Methode AdsSyncReadIntegerVarReq bzw. AdsSyncWriteIntegerVarReq benutzt. Das Handle der SPS-Variablen wurde in der OnCreate Event-Funktion beim Applikationsstart angefordert.

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;

Asynchroner Zugriff

Über die Methoden AdsReadIntegerReq und AdsWriteIntegerReq kann auf die SPS-Variable asynchron zugegriffen werden.

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;

Die Ausführung der Delphi-Applikation wird nach einem asynchronen Zugriff fortgesetzt und beim Vorliegen der Rückgabeparameter eine Event-Funktion in der Windows-Applikation aufgerufen. In unserem Beispiel wird beim Lesen der SPS-Variablen die Event-Funktion AdsReadIntegerConf und beim Schreiben der SPS-Variablen die Event-Funktion AdsWriteConf aufgerufen.

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 Zugriff

Bei dem Connected-Zugriff wird eine "Verbindung" zu der SPS-Variablen aufgebaut. In Abhängigkeit von den Parametern (ADSTRANS_SERVERCYCLE oder ADSTRANS_SERVERONCHA) werden die Event-Funktionen zyklisch, oder bei einer Änderung der SPS-Variablen aufgerufen.

In der Beispielapplikation wird bei einem Mausklick auf den Button Connected read by address die Methode AdsReadIntegerConnect aufgerufen und bei einem Mausklick auf den Button Connected read by variable name die Methode AdsReadIntegerVarConnect aufgerufen.

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;

Beim Erfolg wird in der Delphi-Applikation die Event-Funktion AdsReadConnectUpdate aufgerufen, unabhängig davon welche der beiden Methoden für den Verbindungsaufbau benutzt wurde.

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

Über die Methode AdsReadIntegerDisconnect kann die Verbindung zu der SPS-Variablen abgebaut werden.

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

ConnectEx-Methoden (Connected-Zugriff mit einem Benutzer-Handle)

Mit der Hilfe der ConnectEx-Methoden kann ähnlich wie bei den Connect-Methoden ein Connected-Zugriff zu den SPS-Variablen aufgebaut werden. Die ConnectEx-Methoden haben den Vorteil, dass bei dem Verbindungs-Aufbau ein benutzerdefiniertes Handle als Parameter in der Connect-Methode übergeben werden kann. Dieses Handle kann dann in der Event-Funktion ausgewertet und dazu benutzt werden, um die SPS-Variablen zu identifizieren, für die die Event-Funktion aufgerufen wurde.

Bei einem Mausklick auf den Button ConnectEx wird in der OnConnectExClick-Routine die Methode AdsReadVarConnectEx2 aufgerufen.

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;

Wurde die Verbindung erfogreich aufgebaut, dann werden in der Event-Funktion AdsReadConnectUpdateEx2 die Parameter als Text auf der Form ausgegeben.

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;

Bei einem Mausklick auf den Button DisconnectEx wird die Methode AdsDisconnectEx aufgerufen und die Verbindung zu der SPS-Variablen abgebaut.

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

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

Bemerkung:

Bei der Einbindug des ADS-OCX in Delphi-Applikationen wurde festgestellt, dass von der Entwicklungsumgebung von Delphi fehlerhafte Prototypen (genauer: fehlerhafte Parameterübergabe bei den OleVariant-Typen) der Event-Funktion AdsReadConnectUpdateEx generiert wurde. Aus diesem Grund wurde das ADS-OCX um eine neue Methode AdsReadVarConnectEx2 und eine dazugehörende Event-Funktion AdsReadConnectUpdateEx2 ergänzt. In der neuen Event-Funktion werden die OleVariant-Parameter per Referenz statt per Wert übergeben.

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

SPS Programm

PROGRAM MAIN
VAR
    VARINT16	AT%MB100:INT;
END_VAR

;

Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample01.exe
Delphi 5 oder höher (classic) | Sample01.exe


Delphi AdsOcx Beispiel02: Liste der deklarierten Variablen eines ADS-Gerätes auslesen

TwinCAT ADS-OCX: Beispiele

Liste der deklarierten Variablen eines ADS-Gerätes auslesen

Systemvoraussetzungen:

  • Delphi 5.0 oder höher;
  • TwinCAT v2.9 oder höher;

Aufgabe

Das Beispielprogramm zeigt, wie mit den Methoden: AdsReadSymbolInfo und AdsEnumSymbols die Liste der deklarierten Variablen eines ADS-Gerätes ausgelesen werden kann. Bei einem Mausklick auf den Read Symbol Info-Button sollen die Symbolinformationen des ersten SPS-Laufzeitsystems (Port 801) oder einer zusätzlichen Task im TwinCAT System Manager (Port 301) gelesen und in einer Tabelle dargestellt werden.

Beschreibung

Symbolkonfiguration für das SPS-Laufzeitsystem

Um auf die Symbolinformationen eines SPS-Laufzeitsystems zugreifen zu können, muss die Symbolgenerierung für die SPS-Variablen oder Strukturen aktiviert werden und die Symbolinformationen während des Projekt-Downloads in das SPS-Laufzeitsystem geladen werden. In der TwinCAT PLC Control können die notwendigen Einstellungen für den Symbol-Download über den Optionsdialog der Kategorie TwinCAT vorgenommen werden. Das erste Laufzeitsystem der SPS wird über die Portnummer 801 angesprochen.

Symbolkonfiguration der zusätzlichen Task im TwinCAT System Manager

Im TwinCAT System Manager kann eine zusätzliche Task eingefügt und konfiguriert werden. Die Variablen der zusätzlichen Task können mit anderen Variablen (z.B. mit den SPS-Variablen oder IO-Variablen eines Busklemmen-Controllers) verknüpft werden. Um auf die Symbolinformationen der zusätzlichen Task zugreifen zu können, muss die Checkbox für die Symbolgenerierung in dem Konfigurationsdialog der Task-Einstellungen aktiviert werden. Das zusätzliche Task wird über die Portnummer 301angesprochen.

Delphi 5 Programm

In der OnFormCreate-Eventfunktion wird die Verbindung zu dem ersten Laufzeitsystem der SPS (Port 801) auf dem lokalen PC hergestellt. Gleichzeitig wird die ListView-Komponente und die benötigten Variablen initialisiert. Bei einem Mausklick auf den Read Symbol Info-Button wird die Methode ReadSymInfoButtonClick aufgerufen. In dieser Methode wird zuerst über den Methodenaufruf: AdsReadSymbolInfo die Anzahl der verfügbaren Symbole ermittelt und dann in einer For-Schleife die Symbolinformationen für jede einzelne Symbolvariable gelesen. Die Werte werden dann über eine Hilfs-Prozedur AddListViewItem in die ListeView-Komponente eingefügt. Die Methode AdsEnumSymbols besitzt einen booleschen Flag: bNext. Ist dieser Flag auf FALSE gesetzt, dann werden die Symbolinformationen des ersten Symbols und bei bNext=TRUE aller weiteren Symbole gelesen. Um die Symbolinformationen der zusätzlichen Task im TwinCAT System Manager lesen zu können, muss die AdsAmsServerPort -Eigenschaft der AdsOcx-Komponente auf 301 gesetzt werden. Die Portnummer können Sie zur Laufzeit über die Eigenschaften-Seite der AdsOcx-Komponente setzen. Die Eigenschaften-Seite kann in der Beispielapplikation über das Menü: AdsOcx->Properties aufgerufen werden.

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.

SPS Programm

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

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

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

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

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

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

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

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

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

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

;

Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample02.exe
Delphi 5 oder höher (classic) | Sample02.exe


Delphi AdsOcx Beispiel03: Array in die SPS schreiben oder aus der SPS lesen

TwinCAT ADS-OCX: Beispiele

Array in die SPS schreiben oder aus der SPS lesen

Systemvoraussetzungen:

  • Delphi 6.0 oder höher;
  • TwinCAT v2.10 oder höher;

Benutzen Sie die, für bestimmte Datentypen "aufgelösten" Methoden. Wenn Sie z.B. ein Array in der SPS vom Typ INT lesen wollen, können abhängig von der Zugriffsart z.B. folgende Methoden benutzt werden:

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 )

Auf String-Arrays kann auf diese Weise nicht zugegriffen werden. Die Länge der Daten, die gelesen oder geschrieben werden sollen, ergibt sich aus der Anzahl der zu lesenden oder zu schreibenden Elemente multipliziert mit der Byte-Größe eines Elementes. Diese Länge muss in dem length- oder cbLength-Parameter übergeben werden. Über den Parameter pData wird das erste Element des Delphi-Arrays übergeben.

Beispiel:

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

Delphi 6 Programm:

Array aus der SPS lesen:

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;

Array in die SPS schreiben:

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;

Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample03.exe
Delphi 6 oder höher (classic) | Sample03.exe


Delphi AdsOcx Beispiel04: ADS-OCX Property-Seite aufrufen

TwinCAT ADS-OCX: Beispiele

ADS-OCX Property-Seite aufrufen

Systemvoraussetzungen:

  • Delphi 7.0 oder höher;
  • TwinCAT v2.9 oder höher;

Beschreibung

Unter Delphi kann die ADS-OCX-Property-Seite auf folgende Weise aufgerufen werden:

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

Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample04.exe
Delphi 7 oder höher (classic) | Sample04.exe


Delphi AdsOcx Beispiel05: Arbeiten mit Handles von SPS-Variablen

TwinCAT ADS-OCX: Beispiele

Arbeiten mit Handles von SPS-Variablen

Systemvoraussetzungen:

  • Delphi 7.0 oder höher;
  • TwinCAT v2.9 oder höher;

Alle benötigten Handles können ein Mal beim Start der Applikation angefordert werden und beim Beenden der Applikation freigegeben werden. Ständiges Anfordern und Freigeben der Handles verursacht unnötige Belastung des Systems.

Die bereits angeforderten Handles werden beim TwinCAT Restart ungültig und müssen neu angefordert werden, ebenso nach jedem 'Rebuild All' in der SPS. Bei 'Rebuild All' wird in das Laufzeitsystem ein komplett neues Programm geladen, dadurch werden alle bereits angeforderten Handles ungültig und und von TwinCAT automatisch freigegeben. Die nicht mehr benötigten Handles müssen immer freigegeben werden. Dies kann aber nur dann erfolgen, wenn das TwinCAT System noch läuft. Wurde das TwinCAT System bereits gestoppt, dann werden alle Handles automatisch freigegeben.

Mit dem ersten Laufzeitsystem auf dem lokalen PC verbinden und das Handle der SPS-Variablen holen:

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;

Beim Beenden der Applikation Handle freigeben:

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;
Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample05.exe
Delphi 7 oder höher (classic) | Sample05.exe


Delphi AdsOcx Beispiel06: String in die SPS schreiben oder aus der SPS lesen

TwinCAT ADS-OCX: Beispiele

String in die SPS schreiben oder aus der SPS lesen

Systemvoraussetzungen:

  • Delphi 7.0 oder höher;
  • TwinCAT v2.11 Build 2034 oder höher;

Aufgabe

Ein String soll in die SPS geschrieben oder aus der SPS gelesen werden.

Beschreibung

Damit ein String in die SPS geschrieben oder oder aus der SPS gelesen werden kann benötigt man die Länge des SPS-Strings. Die tatsächliche Länge eines SPS-Strings kann mit dem SPS-Operator SIZEOF ermittelt werden. In der SPS werden die Strings mit einer Null terminiert und die tatsächliche String-Länge errechnet sich aus der definierten Länge plus 1. Wurde bei der Stringdefinition keine Länge angegeben, dann besitzt der String eine tatsächliche Länge von 81 Zeichen inklusive der abschließenden Null.

SPS-Programm

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

strColor hat die Länge 11 Zeichen;

strState hat die Länge 21 Zeichen;

strMessage hat die Länge 81 Zeichen;

Delphi 7 Programm

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}

String aus der SPS lesen

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 dem oberen Beispiel werden 7 Zeichen eines SPS-Strings in Delphi eingelesen. Die dynamischen String-Typen haben nach der Initialisierung die Länge Null. Damit das ADS-OCX den SPS-String in die Delphi-Stringvariable hineinkopieren kann, muss vorher die Delphi-Stringvariable auf die gewünschte Länge lokiert werden. Eine WideString-Variable in Delphi benötigt zwei Bytes pro Zeichen. Die Length-Funktion liefert die lokierte Anzahl der Zeichen im String. Der Length-Parameter im Methodenaufruf benötigt aber die Byte-Länge, deshalb wird die mit Length-Funktion ermittelte Länge verdoppelt..

String in die SPS schreiben

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;

Verbindung zur SPS aufbauen, variable handle holen

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;

Resourcen (variable handle) freigeben

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.

Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample06.exe
Delphi 7 oder höher (classic) | Sample06.exe


Delphi AdsOcx Beispiel07: Mehrere boolsche Variablen mit einem Zugriff in ein Array einlesen

TwinCAT ADS-OCX: Beispiele

Mehrere boolsche Variablen mit einem Zugriff in ein Array einlesen

Systemvoraussetzungen:

  • Delphi 7.0 oder höher;
  • TwinCAT v2.11 Build 2034 oder höher;

Aufgabe

Mehrere boolesche SPS-Variablen können mit einem Zugriff in Delphi-Applikation eingelesen werden, wenn diese Variablen auf Adressen lokiert wurden, die hintereinander im Speicher liegen. Wichtig ist aber, dass die erste Variable auf eine Byteadresse lokiert wurde.

Beschreibung

SPS-Programm

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

Delphi 7 Programm

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.
Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample07.exe
Delphi 7 oder höher (classic) | Sample07.exe


Delphi AdsOcx Beispiel08: Übertragen von Strukturen zur/von der SPS

TwinCAT ADS-OCX: Beispiele

Übertragen von Strukturen zur/von der SPS

Systemvoraussetzungen:

  • Delphi 7.0 oder höher;
  • TwinCAT v2.11 Build 2034 oder höher;

Aufgabe

Von der Delphi-Applikation soll eine Struktur in die SPS geschrieben oder gelesen werden. Die Elemente der Struktur haben verschiedene Datentypen.

Beschreibung

Strukturdeklaration in der SPS

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

SPS-Programm

PROGRAM MAIN
VAR
    PLCVar : PLCStruct;
END_VAR
;

Strukturdeklaration in Delphi

Type VBStruct
    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 Programm

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.
Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample08.exe
Delphi 7 oder höher (classic) | Sample08.exe


TcAdsDLl Delphi Beispiel: Einbinden in Delphi (API interface)

TwinCAT TcAdsDLL: Delphi Beispiele

Einbinden in Delphi (API-Schnittstelle)

Benötigte Dateien

Zum Einbinden der DLL in Delphi-Programme benötigen Sie folgende Dateien:

  • TcAdsDll.dll - dynamische Funktionsbibliothek

Die TcAdsDll.dll befindet sich im ‚System32'-Verzeichnis von Windows NT/2000.

Optional für statisches Linken:
HINWEIS: Die Deklarationsdateien TcAdsAPI.pas und TcAdsDEF.pas sind im Archiv delphi_adsdll_api_units.zip enthalten.

  • Portierte TcAdsAPI.h C++ Headerdatei mit den Deklarationen der Ads-Funktionen: TcAdsAPI.pas
  • Portierte TcAdsDEF.h C++ Headerdatei mit den Deklarationen der Strukturen und Konstanten: TcAdsDEF.pas

Um die Bespiele selbst übersetzen zu können benötigen Sie:

  • Delphi 5.0 oder höher;
  • ADS Summenkommando-Beispiele: Delphi 6.0 + Update Pack 2 oder höher;
  • ADS Multithreading-Beispiel: Delphi 7.0 + Update 7.1 oder höher;

Theoretisch können Sie die Beispiele auch mit einer neueren Delphi-Version bearbeiten. Oft sind nach der Konvertierung des Projekts zusätzliche Anpassungen im Sourcecode an die neue Delphi Version vorzunehmen. Diese Anpassungen können z.B. durch eine Änderung im Compiler bei der Behandlung der VARIANT-Datentypen oder Stringdatentypen notwendig sein.

DLLs in Delphi aufrufen

Bevor Sie Routinen aufrufen können, die in der TcAdsDLL definiert sind, müssen Sie diese Routinen importieren. Der Import kann auf zwei Arten durchgeführt werden: Sie deklarieren eine Prozedur oder Funktion als external, oder Sie rufen die Windows-API direkt auf. Bei beiden Methoden werden die Routinen erst zur Laufzeit zur Anwendung gelinkt. Das bedeutet, dass die DLL zur Compilierzeit nicht benötigt wird. Es bedeutet aber auch, dass der Compiler nicht überprüfen kann, ob eine Routine korrekt importiert wird.

Statisches laden der DLL-Funktionen

Die einfachste Art, eine Prozedur oder Funktion zu importieren, besteht darin, sie mit der Direktive external zu deklarieren:

procedure MyDllFunction; external 'MYLIB.DLL';

Durch diese Deklaration wird beim Programmstart die Datei MYLIB.DLLgeladen. Während der Ausführung des Programms bezieht sich der Bezeichner MyDllFunction immer auf denselben Eintrittspunkt in derselben DLL.
Sie können die Deklaration einer importierten Routine direkt in das Programm oder die Unit einfügen, in dem bzw. der sie aufgerufen wird. Um Ihre Programme leichter pflegen zu können, sollten Sie aber alle external-Deklarationen in einer separaten „Import-Unit“ zusammenfassen. Diese Unit enthält dann auch die Konstanten und Typen, die für die Schnittstelle zur DLL erforderlich sind (die Unit Windows von Delphi ist dafür ein gutes Beispiel). Andere Module, die auf die Import-Unit zugreifen, können alle darin deklarierten Routinen aufrufen.

Ausführliche Informationen über external-Deklarationen und dynamisches Laden über Aufrufe der Windows-API finden Sie in der Delphi Online-Hilfe.

Folgende Regeln sind bei einer Portierung der TcAdsDLL in Pascal-Applikationen einzuhalten:

  • Alle C++ Makros müssen in entsprechenden Pascal-Funktionen umgewandelt werden;
  • Alle C++ Datentypen müssen in Pascal-Typen umgewandelt werden;
  • Die Datentypen der C++ Funktionsparameter müssen in entsprechenden Pascal-Datentypen umgewandelt werden;

Die wichtigsten Deklarationen der TcAdsDLL.DLL wurden in zwei Units zusammengefasst: TcAdsDEF.pas und TcAdsAPI.pas. Wenn Sie die Funktionen statisch laden wollen, müssen diese Units über eine Uses-Klausel in die Delphi-Applikation eingebunden werden.

Infos über Delphis memory manager in Multithreading-Applikationen

Der "memory manager" von Delphi benutzt "critical sections" um Threadsicherheit in Multithreading-Applikationen gewährleisten zu können. Die "critical sections" werden aber nur dann verwendet wenn eine nicht so bekannte Systemvariable IsMultiThread auf True gesetzt wurde. Die IsMultiThread Variable ist in dem unit ‘System.pas’ definiert, welches implizit von allen Delphi units verwendet wird.

Immer dann wenn die Applikation oder eine eingebundene Bibliothek mehrere Threads verwendet, müsste die Variable IsMultithread auf True gesetzt werden. Andernfalls ist die Applikation oder die Bibliothek nicht "thread safe".
IsMultiThread wird aber nur dann implizit gesetzt, wenn der Entwickler in der Applikation oder der Bibliothek das Delphi TThread Objekt benutzt.

Der memory manager von Delphi geht z.B. davon aus, dass es sich um eine Singlethread-Applikation handelt wenn:

  • Eine DLL eingebunden wird die mehrere Threads verwendet;
  • Kein TThread Objekt vom Entwickler benutzt wird;

Die Zugriffe auf gemeinsame Resourcen werden in diesem Fall nicht verriegelt.

Die TcAdsDLL.dll verwendet aber eigene Threads. Aus diesem Grund muss sichergestellt werden, dass die IsMultiThread Variable auf True gesetzt ist sobald die TcAdsDLL in eine Delphi-Applikation eingebunden wird.

In den aktuellen Versionen der Units: TcAdsDEF.pas und TcAdsAPI.pas wird die IsMultiThread Variable in der initialization section auf True gesetzt. Der Entwickler ist aber selbst dafür verantwortlich sicherzustellen, dass die Variable in einer Multithreading-Applikation zum richtigen Zeitpunkt auf jeden Fall gesetzt ist.


TAdsDll Delphi Beispiel01

TwinCAT TcAdsDLL: Delphi Beispiele

All-In-One Beispiel

In dieser Beispielapplikation werden alle Funktionen der DLL präsentiert und können durch einen Mausklick auf den entsprechenden Button ausprobiert werden. Wurde der Port erfolgreich geöffnet, dann kann auch ein Remote-PC für die Kommunikation ausgewählt werden.

Voraussetzungen:

  • Delphi 5.0 oder höher;
  • TcAdsDLL.DLL;
  • TcAdsDEF.pas und TcAdsAPI.pas falls Sie den Quelltext selber übersetzen möchten;

Bei einem Mausklick auf eine der Tasten in der Grafik werden Sie zur Seite mit dem Sourcecode weiter geleitet.

Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample01.exe
Delphi 5 oder höher (classic) | Sample01.exe


TcAdsDll Delphi Beispiel01_01

TwinCAT TcAdsDLL: Delphi Beispiele

AdsGetDllVersion

Delphi 5 Programm

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.

Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample01.exe
Delphi 5 oder höher (classic) | Sample01.exe


TcAdsDll Delphi Beispiel01_02

TwinCAT TcAdsDLL: Delphi Beispiele

AdsPortOpen

Delphi 5 Programm

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.

Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample01.exe
Delphi 5 oder höher (classic) | Sample01.exe


TcAdsDll Delphi Beispiel01_03

TwinCAT TcAdsDLL: Delphi Beispiele

AdsPortClose

Delphi 5 Programm

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.

Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample01.exe
Delphi 5 oder höher (classic) | Sample01.exe


TcAdsDll Delphi Beispiel01_04

TwinCAT TcAdsDLL: Delphi Beispiele

AdsGetLocalAddress

Delphi 5 Programm

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.

Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample01.exe
Delphi 5 oder höher (classic) | Sample01.exe


TcAdsDll Delphi Beispiel01_05

TwinCAT TcAdsDLL: Delphi Beispiele

AdsSyncWriteReq

Delphi 5 Programm

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.

Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample01.exe
Delphi 5 oder höher (classic) | Sample01.exe


TcAdsDll Delphi Beispiel01_07

TwinCAT TcAdsDLL: Delphi Beispiele

AdsSyncReadReqEx

Delphi 5 Programm

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.

Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample01.exe
Delphi 5 oder höher (classic) | Sample01.exe


TcAdsDll Delphi Beispiel01_08

TwinCAT TcAdsDLL: Delphi Beispiele

AdsSyncReadWriteReq

Delphi 5 Programm

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.

Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample01.exe
Delphi 5 oder höher (classic) | Sample01.exe


TcAdsDll Delphi Beispiel01_09

TwinCAT TcAdsDLL: Delphi Beispiele

AdsSyncReadWriteReqEx

Delphi 5 Programm

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.

Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample01.exe
Delphi 5 oder höher (classic) | Sample01.exe


TcAdsDll Delphi Beispiel01_10

TwinCAT TcAdsDLL: Delphi Beispiele

AdsSyncReadDeviceInfoReq

Delphi 5 Programm

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.

Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample01.exe
Delphi 5 oder höher (classic) | Sample01.exe


TcAdsDll Delphi Beispiel01_11

TwinCAT TcAdsDLL: Delphi Beispiele

AdsSyncWriteControlReq

Delphi 5 Programm

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.

Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample01.exe
Delphi 5 oder höher (classic) | Sample01.exe


TcAdsDll Delphi Beispiel01_06

TwinCAT TcAdsDLL: Delphi Beispiele

AdsSyncReadReq

Delphi 5 Programm

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.

Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample01.exe
Delphi 5 oder höher (classic) | Sample01.exe


TcAdsDll Delphi Beispiel01_12

TwinCAT TcAdsDLL: Delphi Beispiele

AdsSyncReadStateReq

Delphi 5 Programm

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.

Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample01.exe
Delphi 5 oder höher (classic) | Sample01.exe


TcAdsDll Delphi Beispiel01_13

TwinCAT TcAdsDLL: Delphi Beispiele

AdsSyncAddDeviceNotificationReq/AdsSyncDelDeviceNotificationReq

Delphi 5 Programm

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.

Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample01.exe
Delphi 5 oder höher (classic) | Sample01.exe


TcAdsDll Delphi Beispiel01_14

TwinCAT TcAdsDLL: Delphi Beispiele

AdsSyncSetTimeout

Delphi 5 Programm

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.

Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample01.exe
Delphi 5 oder höher (classic) | Sample01.exe


TcAdsDll Delphi Beispiel01_15

TwinCAT TcAdsDLL: Delphi Beispiele

AdsAms[Un]RegisterRouterNotification

Delphi 5 Programm

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.

Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample01.exe
Delphi 5 oder höher (classic) | Sample01.exe


TcAdsDll Delphi Beispiel01

TwinCAT TcAdsDLL: Delphi Beispiele

Ereignisgesteuertes Lesen (by symbol name)

Voraussetzungen:

  • Delphi 5.0 oder höher;
  • TcAdsDLL.DLL;
  • HINWEIS: Die Deklarationsdateien TcAdsAPI.pas und TcAdsDEF.pas sind im Archiv delphi_adsdll_api_units.zip enthalten.
  • TcAdsDEF.pas und TcAdsAPI.pas falls Sie den Quelltext selber übersetzen möchten;

Delphi 5 Programm

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}

Implementierung der Notification Callback-Funktion

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

Synchronisierung von Main-Thread und Callback-Thread mit Hilfe der 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;

Verbindung zur SPS herstellen

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

Resourcen freigeben, Verbindung trennen

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

Notification anmelden

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

Notification abmelden

////////////////////////////////////////////////////////////////////////////////////////////////
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.

Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample02.exe
Delphi 5 oder höher (classic) | Sample02.exe


TcAdsDll Delphi Beispiel03: Synchroner Zugriff auf die SPS-Variablen

TwinCAT TcAdsDLL: Delphi Beispiele

Synchroner Zugriff auf die SPS-Variablen

Voraussetzungen:

  • Delphi 5.0 oder höher;
  • TcAdsDLL.DLL;
  • TcAdsDEF.pas und TcAdsAPI.pas, enthalten in der Datei delphi_adsdll_api_units.zip, falls Sie den Quelltext selber übersetzen möchten;

Aufgabenstellung

Aus der Windows-Applikation soll synchron schreibend und lesend auf eine SPS-Variable zugegriffen werden. Die Variable ist vom Typ Integer (16 Bit) und liegt auf dem Offset 520 in dem Merkerbereich des ersten Laufzeitsystems der SPS.

Das SPS-Programm

Die SPS-Variable wird beim Start der SPS mit dem Wert 7 initialisiert.

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

Delphi 5 Programm

Die Deklarationen der benutzten TcAdsDLL-Funktionen befinden sich in den Pascal-Units TcAdsDEF.pas und TcAdsAPI.pas. Diese wurden über eine Uses-Klausel in das Projekt eingebunden.

unit DelphiAdsDLLSample1Unit;

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

Die Beispielapplikation

In der Event-Funktion FormCreate wird die DLL-Funktion AdsPortOpenaufgerufen. Beim Erfolg liefert diese Funktion die geöffnete Portnummer, sonst eine Null. Eventuelle Fehler werden über eine Message-Box an den Benutzer ausgegeben. Beim Beenden der Anwendung muß der Port wieder geschlossen werden. In der Event-Funktion FormDestroy wird dabei die DLL-Funktion AdsPortCloseaufgerufen.

Wurde der Ads-Port erfolgreich geöffnet, dann wird eine weitere DLL-Funktion aufgerufen: AdsGetLocalAddress. Diese liefert die AMS-Addresse des lokalen TwinCAT-PC's zurück. Die AMS-Adresse wird für Schreib/Lese-Zugriffe auf die SPS-Variablen benötigt.

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;

Bei der Betätigung des Buttons AdsSyncRead, wird der Wert der SPS-Variablen synchron über deren lokierte Adresse ausgelesen und auf der Form ausgegeben. Dabei wird in der Event-Funktion OnSyncReadButtonClick die DLL-Funktion AdsSyncReadReqaufgerufen. Die Parameter IndexGroup, IndexOffset sind als globale Variablen definiert. Deren Werte werden beim Start der Applikation entsprechend gesetzt.

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

Ein Schreib-Zugriff auf die Variable wurde auf ähnliche Weise implementiert. In der Event-Funktion OnSyncWriteButtonClick wird die DLL-Funktion AdsSyncWriteReqaufgerufen und der Wert der SPS-Variablen =200 gesetzt.

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

Ein gleichzeitiger Lese- und Schreib-Zugriff auf SPS-Variablen kann über die DLL-Funktion AdsSyncReadWriteReqrealisiert werden. In dem Beispiel wird der Wert der Variablen über ein Handle gelesen. Dabei wird über die AdsSyncReadWriteReq-Funktion das Handle der Variablen zuerst angefordert. Mit diesem Handle kann dann der Wert der Variablen über die Funktion AdsSyncReadReq gelesen werden. Das Handle muß dann über die Funktion AdsSyncWriteReq frei gegeben werden.

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;

Sollen die Werte mehrerer SPS-Variablen öfter asynchron gelesen werden, dann können die benötigten Handles in der Event-Funktion FormCreate beim Start der Applikation angefordert werden. Erst beim Beenden der Applikation in der Event-Funktion FormDestroy können sie wieder frei gegeben werden.

Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample03.exe
Delphi 5 oder höher (classic) | Sample03.exe


TcAdsDll Delphi Beispiel04: Statusänderung vom TwinCAT-Router und der SPS erkennen

TwinCAT TcAdsDLL: Delphi Beispiele

Statusänderungen vom TwinCAT-Router und der SPS erkennen

Voraussetzungen:

  • Delphi 5.0 oder höher;
  • TcAdsDLL.DLL;
  • TcAdsDEF.pas und TcAdsAPI.pas, enthalten in der Datei delphi_adsdll_api_units.zip, falls Sie den Quelltext selber übersetzen möchten;

Beschreibung

Zur Laufzeit einer Applikation ist es oft von Bedeutung, den Status von TwinCAT und/oder dessen Komponenten (Devices) abzufragen ( ob die SPS sich z.B. im RUN-Status befindet ). Um die benötigten Statusinformationen nicht immer pollend abzufragen gibt es die Möglichkeit, eine Mitteilung (Notification) bei den TwinCAT Komponenten anzumelden. Diese können dann mit Hilfe von Callback-Funktionen eine Statusänderung an die angemeldeten Client-Applikationen melden. In dem folgenden Beispielprogramm wird der Status der SPS (Laufzeitsystem 1) und der des TwinCAT-Routers auf dem lokalem Rechner überwacht. Die Applikation kann nur den Status des TwinCAT-Routers auf dem lokalem Rechner überwachen. D.h. der Status eines Routers auf einem Remote-PC kann auf diese Weise nicht überwacht werden. Der Status der SPS kann dagegen sowohl auf dem lokalen Rechner als auch auf einem Remote-PC mit den vorgestellten Funktionen überwacht werden.

Beim Start der Applikation wird eine Verbindung zum TwinCAT Router auf dem lokalen PC aufgebaut. Durch das Betätigen der entsprechenden Tasten wird eine Notification entweder an den TwinCAT-Router oder an das erste Lufzeitsystem der SPS angemeldet. Die ankommenden Mitteilungen (Callbacks) werden zwei Listen hinzugefügt. Über weitere Tasten können die Notifications bei den jeweiligen TwinCAT Geräten (Devices) gelöscht werden. Beim Beenden der Applikation wird die Verbindung zum TwinCAT Router geschlossen. Durch Starten/Stoppen des TwinCAT Systems über die Taskleiste werden z.B. die Router-Notifications und durch Starten/Stoppen der SPS die Device-Notifications gesendet. Die angemeldeten Device-Notifications werden von den TwinCAT Komponenten selbst verwaltet. Aus diesem Grund müssen die Device-Notifications beim Stoppen des TwinCAT Systems neu angemeldet werden, da die Komponente (hier das Laufzeitsystem der SPS) beim TwinCAT System-Stop aus dem Speicher entfernt wird.

Delphi 5 Programm

In der Event-Funktion FormCreate wird die DLL-Funktion AdsPortOpenaufgerufen. Beim Erfolg liefert diese Funktion eine Portnummer, sonst eine Null. Eventuelle Fehler werden über eine Message-Box an den Benutzer ausgegeben. Wurde der Ads-Port erfolgreich geöffnet, dann wird eine weitere DLL-Funktion aufgerufen: AdsGetLocalAddress. Diese liefert die AMS-Addresse des lokalen TwinCAT-PC's ( auf dem unsere Applikation läuft ) zurück. Um die Notifications beim ersten Laufzeitsystem der SPS anzumelden wird noch eine weitere AMS-Addresse generiert. Diese besitzt die gleiche NetId wie unsere Applikation, weil wir die SPS auf dem gleichen TwinCAT-PC ansprechen wollen. Durch Zuweisung der Portnummer 801 wird das erste LZS ausgewählt. Beim Beenden der Anwendung muß der Port wieder geschlossen werden. In der Event-Funktion FormDestroy wird dabei die DLL-Funktion AdsPortCloseaufgerufen.

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;

Anmelden/Abmelden der Router-Notifications

Eine Notification beim Router wird durch den Aufruf der Funktion AdsAmsRegisterRouterNotification()angemeldet. Die Router-Notification kann immer nur bei dem Router auf dem lokalen TwinCAT-PC angemeldet werden. Als einziger Funktionsparameter wird ein Funktionszeiger auf unsere Callback-Funktion ( in unserem Fall ist es eigentlich eine Prozedur ) übergeben. Über den Funktionsaufruf AdsAmsUnRegisterRouterNotification()wird die Notification vom TwinCAT-Router gelöscht.

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;

Anmelden/Abmelden der Device-Notifications

Die Device-Notifications werden nicht an einer zentralen Stelle sondern von der TwinCAT Komponente (Device) selbst verwaltet . Die Device-Notifications können auch bei einem Remote TwinCAT-PC angemeldet werden. Das Zielgerät wird dabei über die sogenannte AdsAms-Adresse ausgewählt. Die Notification wird durch den aufruf der DLL-Funktion AdsSyncAddDeviceNotificationReq()angemeldet. Vorher müssen die Attribute der Mitteilung festgelegt werden. Diese werden als Parameter neben dem Funktionszeiger der Callback-Routine an die DLL-Funktion übergeben. Beim Erfolg liefert die Funktion kein Fehler und ein Notification-Handle. Dieses Handle wird dann benötigt um die Notification mit der DLL-Funktion AdsSyncDelDeviceNotificationReq()zu löschen. Eine Applikation kann gleichzeitig mehrere Notifications anmelden. Jede Notification kann über ein Notification-Handle oder über ein User-Handle identifiziert werden. Diese Parameter werden immer in der Callback-Funktion an die Applikation zurückgesendet.

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;

Die Callback-Funktionen

Die Callback-Funktionen für den Router-Callback und den DeviceNotification-Callback wurden als Proceduren definiert. Es können aber genauso Funktionen sein. Da die C++ Funktionen keine Rückgabeparameter zurückliefern und in Pascal eine Funktion immer einen Parameter zurückliefern muß, wurden für die Callback-Funktionen Proceduren gewählt. Sie können in der Callback-Funktion nicht erneut eine weitere DLL-Funktion aufrufen.. Diese könnte dann einen weiteren Callback auslösen usw. Um die Callback-Funktionen zu entkoppeln und mit dem Thread der Anwedung zu synchronisieren wurden PostMessage-API-Funktionen benutzt. Die Router-Callback-Daten werden an den Applikations-Thread diekt über die Message-Parameter übergeben. Die Daten der Device-Notification werden an den Applikations-Thread über eine Thread-Sichere-Liste übergeben.

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;

Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample04.exe
Delphi 5 oder höher (classic) | Sample04.exe


TcAdsDll Delphi Beispiel05: SPS starten/stoppen

TwinCAT TcAdsDLL: Delphi Beispiele

SPS starten/stoppen

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

Voraussetzungen:

  • Delphi 5.0 oder höher;
  • TcAdsDLL.DLL;
  • TcAdsDEF.pas und TcAdsAPI.pas, enthalten in der Datei delphi_adsdll_api_units.zip, falls Sie den Quelltext selber übersetzen möchten;

Delphi 5 Programm:

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.

Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample05.exe
Delphi 5 oder höher (classic) | Sample05.exe


TcAdsDll Delphi Beispiel06

TwinCAT TcAdsDLL: Delphi Beispiele

Beispiel mit erweiterten Funktionen (für Multithreaded-Applikationen)

Bei der Applikation handelt es sich um eine einfache Delphi-MDI-Applikation. Jedes Child-Fenster (GUI) besitzt einen eigenen Ads-Thread (ADS-Port für die Datenkommunikation) in dem die erweiterten Ads-Funktionen aufgerufen werden. Die Applikation demonstriert wie ADS-Funktionsaufrufe (längere Aktionen) in einen separaten Thread ausgelagert werden können ohne den Main-Thread zu blokieren. Die Applikation kann als Startpunkt für eigene Implementierungen benutzt werden.

Die Ads-Thread-Funktionen wurden in einer separaten Unit: AdsThread.pas implementiert. Die Child-Fensterfunktionen wurden ebenfalls in einer separaten Unit: CHILDWIN.PAS / CHILDWIN.DFM implementiert.

Die Kommunikation zwischen der GUI (Child-Fenster) und dem Ads-Thread wurde mit Hilfe der WM-Messages realisiert ( PostThreadMessage-/SendMessage-Funktionen ). Dadurch wurde der Main-Thread von den Ads-Threads entkoppelt. Es können aber auch andere Synchronisierungsmechanismen benutzt werden.

Voraussetzungen:

  • Delphi 7.0 + Update 7.1 oder höher;
  • TcAdsDLL.DLL;
  • TcAdsDEF.pas und TcAdsAPI.pas, enthalten in der Datei delphi_adsdll_api_units.zip, falls Sie den Quelltext selber übersetzen möchten;

Beschreibung

Starten sie das dazugehörige SPS-Projekt Sample.pro und die MDIAPP.exe. Mit File->New kann eine neue Ads-Client-Verbindung und das dazugehörige GUI-Fenster erzeugt werden. Erzeugen sie mehrere Child-Fenster.

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

Bei einem Mausklick auf die entsprechenden Buttons werden folgende Aktionen gestartet:

  • Start read loop...: Liest synchron (by address) in einer For-Schleife die vUINT-Variable (Dauer ca. 10 Sekunden).
  • Start write loop...: Schreibt synchron (by address) in einer For-Schleife die vUINT-Variable (Dauer ca. 10 Sekunden).
  • Start read by name loop...: Liest syncchron (by variable name) in einer For-Schleife die vUINT-Variable (Dauer ca. 10 Sekunden).
  • Add notification: Meldet eine Notification an. Immer wenn sich der Wert der vUINT-Variablen ändert wird er an die Client-Applikation übertragen un im (Notification mesages) Memo-Control angezeigt.
  • Delete notification: Deaktiviert die Notification.
  • Start PLC: Startet die SPS.
  • Stop PLC: Stoppt die SPS.
  • Exit thread: Beendet den Ads-Thread (verlässt den TThread-Execute-Aufruf).
Zum Test wählen Sie z.B. in einem Fenster den Start read loop...-Button und in einem anderem Fenster Start write loop...-Button.

Delphi 7 Programm

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.

Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample06.exe
Delphi 7 oder höher (classic) | Sample06.exe


TcAdsDll Delphi Beispiel07

TwinCAT TcAdsDLL: Delphi Beispiele

ADS Summenkommando: Lesen oder schreiben von mehreren Variablen mit einem ADS-Befehl.

Voraussetzungen:

  • TwinCAT v2.10 Build >= 1324;
  • Delphi 6.0 + Update Pack 2 oder höher;
  • TcAdsDLL.DLL;
  • TcAdsDEF.pas und TcAdsAPI.pas, enthalten in der Datei delphi_adsdll_api_units.zip, falls Sie den Quelltext selber übersetzen möchten;

Aufgabenstellung

Es sollen 15-SPS-Variablen, unterschiedlichen Typs mit einem ADS-Summenkommando gelesen oder geschrieben werden.

In Delphi ist es möglich offene Arrayparameter an Funktionen zu übergeben. D.h. die Anzahl der Arrayelemente in so einem Funktionsparameter ist nicht festgelegt. In der TcAdsApi.pas-Datei wurden drei neue Funktionen implementiert: AdsSyncSumReadReq, AdsSyncSumWriteReq und AdsSyncSumReadWriteReq. Diese Funktionen benutzen die Funktionalität der offenen Arrayparameter und vereinfachen die Benutzung des Summenkomandos unter Delphi. Das Beispielprojekt nutzt diese neuen Wrapper-Funktionen.

Das SPS-Programm

Die SPS-Variablen wurden als globale Variablen deklariert. Bei einer gestarteten SPS werden Wertänderungen der Variablen im einfachen MAIN-Programm simuliert.

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 Programm

Die Deklarationen der benutzten TcAdsDLL-Funktionen befinden sich in den Pascal-Units TcAdsDEF.pas und TcAdsAPI.pas. Diese wurden über eine Uses-Klausel in das Projekt eingebunden.

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

Beim Programmstart wird der Kommunikationsport geöffnet und beim Beenden geschlossen. Die benötigten Symbolinformationen der SPS-Variablen wie Index-Group, index-Offset usw. werden beim Programmstart ebenfalls mit Hilfe eines Summenkommandos einmalig gelesen.

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

Symbolinformationen lesen

/////////////////////////////////////////////////////////////////////////
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
    else
        ShowMessageFmt('AdsSyncSumReadWriteReq() error: %d [0x%X]', [adsErr, adsErr]);
end;

Werte lesen

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

Werte schreiben

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

Werte lesen "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;

Einfache Proceduren um gelesenen Werte im Dialog anzeigen zu können oder die zu schreibenden Werte vom Dialog in Daten die übertragen werden müssen zu konvertieren.

/////////////////////////////////////////////////////////////////////////
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.

Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample07.exe
Delphi 6 oder höher (classic) | Sample07.exe


TcAdsDll Delphi Beispiel08

TwinCAT TcAdsDLL: Delphi Beispiele

ADS Summenkommando: Das Holen und Freigeben von mehreren Variable-Handles mit einem ADS-Kommando.

Voraussetzungen:

  • TwinCAT v2.11 Build >= 1550;
  • Delphi 6.0 + Update Pack 2 oder höher;
  • TcAdsDLL.DLL;
  • TcAdsDEF.pas und TcAdsAPI.pas, enthalten in der Datei delphi_adsdll_api_units.zip, falls Sie den Quelltext selber übersetzen möchten;

Aufgabenstellung

Beim Start der einfachen Windows-Applikation sollen Handles für 15 SPS-Variablen mit einem Summenkommando geholt werden. Mit diesen Handles kann z.B. auf die Variablen lesend zugegriffen werden. Beim Beenden der Applikation sollen die Handles mit einem Summenkommando freigegeben werden.

In Delphi ist es möglich offene Arrayparameter an Funktionen zu übergeben. D.h. die Anzahl der Arrayelemente in so einem Funktionsparameter ist nicht festgelegt. In der TcAdsApi.pas-Datei wurden drei neue Funktionen implementiert: AdsSyncSumReadReq, AdsSyncSumWriteReq und AdsSyncSumReadWriteReq. Diese Funktionen benutzen die Funktionalität der offenen Arrayparameter und vereinfachen die Benutzung des Summenkomandos unter Delphi. Das Beispielprojekt nutzt diese neuen Wrapper-Funktionen.

Das SPS-Programm

Die SPS-Variablen wurden als globale Variablen deklariert. Bei einer gestarteten SPS werden Wertänderungen der Variablen im einfachen MAIN-Programm simuliert.

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 Programm

Die Deklarationen der benutzten TcAdsDLL-Funktionen befinden sich in den Pascal-Units TcAdsDEF.pas und TcAdsAPI.pas. Diese wurden über eine Uses-Klausel in das Projekt eingebunden.

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

Beim Programmstart wird der Kommunikationsport geöffnet und beim Beenden geschlossen.

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;

Handles holen

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;

Handles freigeben

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

Werte lesen "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.

Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample08.exe
Delphi 6 oder höher (classic) | Sample08.exe


TcAdsDll Delphi Beispiel09

TwinCAT TcAdsDLL: Delphi Beispiele

Lesen und Schreiben von DT/DATE/TOD/TIME-Variablen

Voraussetzungen:

  • TwinCAT v2.11 Build >= 2034;
  • Delphi 7.0 + + Update 7.1 oder höher;
  • TcAdsDLL.DLL;
  • TcAdsDEF.pas und TcAdsAPI.pas, enthalten in der Datei delphi_adsdll_api_units.zip, falls Sie den Quelltext selber übersetzen möchten;

Aufgabenstellung

Das Datum und/oder Uhrzeit und eine Zeitdauer soll aus der Delphi-Applikation in die TwinCAT SPS geschrieben bzw. aus der TwinCAT SPS in die Delphi-Applikation eingelesen werden. Beim Schreiben der Werte in die SPS wird die in der visuellen Komponente TDateTimePicker eingestellte Uhrzeit und/oder Datum zuerst in das passende SPS-Format konvertiert und dann in die entsprechende SPS-Variable geschrieben. Die gelesenen Werte aus der TwinCAT SPS werden wiederum in das passenden Delphi-Format konvertiert und durch die TDateTimePicker-Komponente angezeigt. Die Zeitdauer soll in Millisekunden von einem TEdit control angezeigt bzw. editiert werden können.

In der Delphi-Applikation stehen unter anderem folgende Datentypen zur Verfügung:

  • TDateTime: 64 Bit Fließkommazahl. Beinhaltet das Datum (ganzzahliger Teil) und die Uhrzeit (Bruchteil). Der ganzzahlige Anteil repräsentiert die Anzahl der vergangenen Tage seit dem 30.12.1899. Der Bruchteil gibt die Uhrzeit an (Bruchteil eines 24-Stunden Tages). Durch die Kodierung der Fließkommazahl ist die Auflösung bei sehr großen Werten kleiner (kleinere Anzahl der Nachkommastellen die zur Verfügung stehen).
  • TDate: 64 Bit Fließkommazahl, TDateTime-Aliastyp, beinhaltet nur das Datum (ganzzahliger Teil). Anzahl der vergangenen Tage seit dem 30.12.1899;
  • TTime: 64 Bit Fließkommazahl, TDateTime-Aliastyp, beinhaltet nur die Uhrzeit (Bruchteil eines 24 Stunden Tages);
  • Longword, 32 Bit Zählwert, in unserer Applikation soll er die Zeitdauer in Millisekunden Auflösung beinhalten (keine Uhrzeit);

Die Verwendung des TDateTime-Datentyps ermöglicht die Verwendung der zur Verfügung stehenden Delphi-Zeitkonvertierungsfunktionen. Insbesondere die zwei Konvertierungsfunktionen DateTimeToUnix() und UnixToDateTime(). Sie können aber stattdessen genauso einen 32 Bit Integer Wert oder einen anderen Datentyp verwenden, dort die Uhrzeit und Datum passend kodieren, konvertieren und dann in die SPS schreiben. Die unten vorgestellte Typkonvertierung/Mapping der Datentypen soll nicht als Vorschrift angesehen werden.

In der TwinCAT SPS stehen folgende Datentypen zur Verfügung:

  • DT bzw. DATE_AND_TIME: vorzeichenloser 32 Bit Zählwert, beinhaltet das Datum und Uhrzeit, Anzahl der vergangenen Sekunden seit dem 01.01.1970, Auflösung in Sekunden;
  • DATE: vorzeichenloser 32 Bit Zählwert, beinhaltet nur das Datum, Anzahl der vergangenen Sekunden seit dem 01.01.1970. Auflösung in Sekunden;
  • TOD bzw TIME_OF_DAY: vorzeichenloser 32 Bit Zählwert, beinhaltet nur die Uhrzeit. Anzahl der vergangenen Millisekunden seit dem Anfang des Tages (00:00Uhr), Auflösung in Millisekunden;
  • TIME, vorzeichenloser 32 Bit Zählwert, beinhaltet die Zeitdauer (keine Uhrzeit), Auflösung in Millisekunden;

Beim Schreiben der Werte in die TwinCAT SPS wird folgende Typkonvertierung/Mapping durchgeführt:

  • TDateTimePicker.DateTime property (TDateTime) -> DT bzw. DATE_AND_TIME
  • TDateTimePicker.Date property (TDate) -> DATE
  • TDateTimePicker.Time property (TTime) -> TOD bzw. TIME_OF_DAY
  • TEdit.Text property (Longword) -> TIME

Beim Lesen der Werte aus der TwinCAT SPS wird die Typkonvertierung in die umgekehrte Richtung durchgeführt.

Je nach dem, ob die Pick->Date- oder Pick->Time-Option angewählt wurde kann das Datum bzw. die Uhrzeit in der visuellen TDateTimePicker-Komponente eingestellt werden.

Die Zeitdauer in Millisekunden kann über das TEdit control im unteren Bereich eingestellt werden.

Beim Mausklick auf den entsprechenden Befehl-Button wird die Uhrzeit und/oder Datum oder die Zeitdauer in die TwinCAT-SPS geschrieben bzw. aus der SPS gelesen und angezeigt.

Das SPS-Programm

In MAIN wurde von jedem der SPS-Typen (Datum/Zeit) jeweils eine SPS-Variable deklariert. Die Delphi-Applikation soll die Werte schreiben oder lesen.

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

Delphi 7 Programm

Die Deklarationen der benutzten TcAdsDLL-Funktionen befinden sich in den Pascal-Units TcAdsDEF.pas und TcAdsAPI.pas. Diese wurden über eine Uses-Klausel in das Projekt eingebunden. Die Unit: DateUtils beinhaltet die benötigten Zeitkonvertierungsfunktionen und muss ebenfalls eingebunden werden.

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;

Aufbauen der Ads-Clientverbindung zur TwinCAT SPS

Die Delphi-Applikation baut beim Start eine Ads-Clientverbindung zur TwinCAT SPS (erstes Laufzeitsystem, Port: 801) auf und holt Handles für die 4 SPS-Variablen. Beim Beenden der Applikation werden die Handles freigegeben und zum Schluss die Ads-Clientverbindung geschlossen. Der Code zum Holen und Freigeben der Variablen-Handles wurde in zwei internen Hilfsfunktionen gekapselt: AdsCreateVarHandle() und 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;

Trennung der Ads-Clientverbindung zur TwinCAT SPS

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

Handle der SPS-Variablen holen

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

Handle der SPS-Variablen freigeben

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

Wert in die SPS schreiben (32 bit unsigned integer)

In der TwinCAT SPS werden die Zeitdatentypen intern in vorzeichenlose 32 Bit Zählwerten gehalten/abgebildet. Nach der Konvertierung in das Zeitformat der SPS müssen die Werte als vorzeichenlose 32 Bit Zahl in die SPS geschrieben werden. Der Code zum Schreiben und Lesen der 32 Bit Werte wurde in zwei internen Hilfsfunktionen gekapselt: AdsWriteUI32() und 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;

Wert aus der SPS lesen (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;

DATE_AND_TIME in die SPS schreiben

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

DATE_AND_TIME aus der SPS lesen

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

DATE in die SPS schreiben

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

DATE aus der SPS lesen

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

TIME_OF_DAY in die SPS schreiben

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

TIME_OF_DAY aus der SPS lesen

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

TIME (Zeitdauer) in die SPS schreiben

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

TIME (Zeitdauer) aus der SPS lesen

//////////////////////////////////////////////////////////////////
// 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.
Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample09.exe
Delphi 7 oder höher (classic) | Sample09.exe


TcAdsDll Delphi Beispiel: Einbinden in Delphi XE2 (COM-Interface)

TwinCAT TcAdsDLL: Delphi Beispiele

Einbinden in Delphi XE2 (COM-Schnittstelle)

Die Typbibliothek der TcAdsDll.dll muss importiert werden (TcAdsDll_TLB.pas Unit ). Abhängig von der Delphi-Version müssen teilweise manuelle Anpassungen gemacht werden um diese fehlerfrei übersetzen zu können.

Systemvoraussetzungen

  • Delphi XE2;
  • TwinCAT 2.9 or higher;
  • TcAdsDll.dll - dynamische Funktion-Bibliothek die sich im ‚System32'-Verzeichnis von Windows NT/2000/XP/.. befindet.

Importieren der Typbibliothek

1. Im Menue den Befehl: Komponente -> Komponente importieren aufrufen. Im Dialog dann die Option: Typbibliothek importieren anwählen und mit Weiter bestätigen.

2. Aus der Liste der registrierten Bibliotheken die Beckhoff TcAdsDll x.x Type Library auswählen und dann mit Weiter bestätigen.

3. Den nächsten Dialog ebenfalls mit Weiter bestätigen.

4. Im nächsten Dialog die Option: Unit anlegen auswählen und mit Fertig stellen bestätigen.

Die Typ-Bibliothek wird z.B. in dem Ordner .../RAD Studio/9.0/Imports generiert. Binden Sie bitte das Unit in ihre Applikation ein.


TcAdsDll Delphi Beispiel: Einbinden in Delphi (COM-Interface)

TwinCAT TcAdsDLL: Delphi Beispiele

Einbinden in Delphi (COM-Schnittstelle)

Die Typbibliothek der TcAdsDll.dll muss importiert werden (TcAdsDll_TLB.pas Unit ). Abhängig von der Delphi-Version müssen teilweise manuelle Anpassungen gemacht werden um diese fehlerfrei übersetzen zu können.

Systemvoraussetzungen

Zum Einbinden der DLL in Delphi-Programme benötigen Sie folgende Dateien:

  • TcAdsDll.dll - dynamische Funktion-Bibliothek die sich im ‚System32'-Verzeichnis von Windows NT/2000 befindet.
  • Delphi 5.0 or higher;
  • TwinCAT 2.9 or higher;

Importieren der Typbibliothek

1. Über den Menübefehl: Projekt -> Import Type Library das Import Type Library-Dialogfenster aufrufen.

2. In dem Dialogfenster markieren Sie aus der Liste die Beckhoff TcAdsDll x.x Type Library und bestätigen mit einem Mausklick auf Create Unit....

Die Typ-Bibliothek wird z.B. in dem Ordner .../Delphi 5/Imports generiert. Binden Sie bitte das Unit in ihre Applikation ein.


TcAdsDll Delphi Beispiel01: Lesen und Schreiben von SPS Variablen

TwinCAT TcAdsDLL: Delphi Beispiele

Lesen und Schreiben von SPS Variablen

Voraussetzungen:

  • Delphi 5.0 oder höher;
  • TwinCAT 2.9 oder höher;
  • Die Typbibliothek der TcAdsDll.dll muss importiert werden (TcAdsDll_TLB.pas Unit ). Abhängig von der Delphi-Version müssen teilweise manuelle Anpassungen gemacht werden um diese fehlerfrei übersetzen zu können.

Dieses Beispiel-Programm demonstriert wie die COM-Schnittstellen der TcAdsDll von Delphi angesprochen werden können, um Variablen aus der SPS zu lesen bzw. in die SPS zu schreiben.

In der FormCreate-Ereignisfunktion wird mit CoTcClient.Create eine Verbindung zum Server auf dem lokalen System aufgebaut. Mit Hilfe der Methode Connect wird dann eine Verbindung zum ersten Laufzeitsystem der SPS (port 801) auf dem lokalen Rechner (netid = '0.0.0.0.0.0') hergestellt.

Durch betätigen des Read-Buttons werden die Variablen aus der SPS gelesen und beim Betätigen des Write-Buttons in die SPS geschrieben.

Das Delphi Programm

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.

SPS-Programm

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

;

Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample01.exe
Delphi 5 oder höher (classic) | Sample01.exe


TcAdsDll Delphi Beispiel02: Ereignisgesteuertes Lesen von SPS Variablen

TwinCAT TcAdsDLL: Delphi Beispiele

Ereignisgesteuertes Lesen von SPS Variablen

Voraussetzungen:

  • Delphi 5.0 oder höher;
  • TwinCAT 2.9 oder höher;
  • Die Typbibliothek der TcAdsDll.dll muss importiert werden (TcAdsDll_TLB.pas Unit ). Abhängig von der Delphi-Version müssen teilweise manuelle Anpassungen gemacht werden um diese fehlerfrei übersetzen zu können.

In der FormCreate-Ereignisfunktion wird mit CoTcClient.Create eine Verbindung zum Server auf dem lokalen System aufgebaut. Mit Hilfe der Methode Connect wird dann eine Verbindung zum ersten Laufzeitsystem der SPS (port 801) auf dem lokalen Rechner (netid = '0.0.0.0.0.0') hergestellt. Beim Betätigen des Start-Buttons wird mit der Methode AddDeviceNotification eine Verbindung zu den SPS-Variablen hergestellt. Bei einer Änderung der SPS-Variablen wird von dem Server die Funktion DeviceNotification der _ITcAdsSyncEvents-Schnittstelle aufgerufen. Damit Events an den Client gesendet werden können, muss diese Schnittstelle von dem Client implementiert werden. Ausserdem muss der Server über deren existenz informiert werden. Dies geschieht durch den Aufruf der InterfaceConnect-Prozedur. Beim Betätigen des Stop-Buttons werden die Verbindungen mit der Methode DelDeviceNotification wieder aufgehoben.

Das Delphi Programm

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.

SPS-Programm

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;

Sprache / IDE | Beispielprogram auspacken
Delphi XE2 | Sample02.exe
Delphi 5 oder höher (classic) | Sample02.exe


Delphi Prism Beispiele: TwinCAT.Ads in VS2010

TwinCAT ADS .NET Komponente

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

Embarcadero Prism XE2 = Oxygene for .NET

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

Neues Projekt erstellen

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

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

Referenz hinzufügen

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

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

Namespace hinzufügen

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

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

namespace AdsNETSample;'
interface

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

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

Eine Verbindung zu einem ADS-Gerät herstellen:

type

//...

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

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

Lesen einer SPS-Variablen:

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

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

Schreiben einer SPS-Variablen:

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

Eine Verbindung zu einem ADS-Gerät trennen:

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

SPS Programm:

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


Delphi Beispiele: TwinCAT.Ads in BDS2006

TwinCAT ADS .NET Komponente

Einbinden in Borland Developer Studio 2006

Neues Projekt erstellen

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

Referenz hinzufügen

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

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

Namespace hinzufügen

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

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

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

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

Eine Verbindung zu einem ADS-Gerät herstellen:

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

Lesen einer SPS-Variablen:

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

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

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

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

Schreiben einer SPS-Variablen:

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

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

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

Eine Verbindung zu einem ADS-Gerät trennen:

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

SPS Programm:

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


Delphi ADS.NET Beispiel01

TwinCAT Sample - Delphi - ADS .NET

Zugriff auf ein Array in der SPS

Aufgabe

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

Beschreibung

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

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

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;

SPS-Programm

PROGRAM MAIN
VAR
    PLCVar : ARRAY [0..99] OF INT;
    Index: BYTE;
END_VAR
FOR Index := 0 TO 99 DO
    PLCVar[Index] := 3500 + INDEX;
END_FOR

Sprache / IDE | Beispielprogram auspacken
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample01.exe
Delphi for .NET (Borland Developer Studio 2006) | Sample01.exe


Delphi ADS.NET Beispiel02

TwinCAT Sample - Delphi - ADS .NET

Übertragen von Strukturen an die SPS

Aufgabe

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

Beschreibung

Die Struktur, die in die SPS geschrieben werden soll:

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

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

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

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.

SPS-Programm

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

Sprache / IDE | Beispielprogram auspacken
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample02.exe
Delphi for .NET (Borland Developer Studio 2006) | Sample02.exe


Delphi ADS.NET Beispiel03

TwinCAT Sample - Delphi - ADS .NET

Ereignissgesteuertes Lesen

Aufgabe

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

Beschreibung

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

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

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

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

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.

SPS-Programm

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

Sprache / IDE | Beispielprogram auspacken
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample03.exe
Delphi for .NET (Borland Developer Studio 2006) | Sample03.exe


Delphi ADS.NET Beispiel04

TwinCAT Sample - Delphi - ADS .NET

Lesen und Schreiben von STRING-Variablen

Aufgabe

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

Beschreibung

Der SPS-String hat den Namen: MAIN.text.

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

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

TwinCAT.Ads.NET Version < 1.0.0.10:

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

TwinCAT.Ads.NET Version >= 1.0.0.10:

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

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

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.

SPS Programm

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

Sprache / IDE | Beispielprogram auspacken
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample04.exe
Delphi for .NET (Borland Developer Studio 2006) | Sample04.exe


Delphi ADS.NET Beispiel05

TwinCAT Sample - Delphi - ADS .NET

Lesen und Schreiben von DATE/TIME-Variablen

Ab TwinCAT.Ads.NET Version >= 1.0.0.10

Aufgabe

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

Beschreibung

Das SPS-Program enthält folgende Variablen:

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

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

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

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

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

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.

SPS Programm

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
;

Sprache / IDE | Beispielprogram auspacken
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample05.exe
Delphi for .NET (Borland Developer Studio 2006) | Sample05.exe


Delphi ADS.NET Beispiel06

TwinCAT Sample - Delphi - ADS .NET

SPS-Variablendeklaration auslesen

Ab TwinCAT.Ads.NET version >= 1.0.0.12

Aufgabe

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

Beschreibung

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

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

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

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

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.

SPS-Programm

PROGRAM MAIN
VAR
    REAL32_1 AT %MB0 : REAL;		(* 1 *)
    REAL32_2 AT %MB4 : REAL;		(* 2 *)
    REAL32_3 AT %MB8 : REAL;		(* 3 *)
    REAL32_4 AT %MB12: REAL;		(* 4 *)
    REAL32_5 AT %MB16: REAL;		(* 5 *)
    REAL64_1 AT %MB20 : LREAL;		(* 6 *)
    REAL64_2 AT %MB28 : LREAL;		(* 7 *)
    REAL64_3 AT %MB36 : LREAL;		(* 8 *)
    REAL64_4 AT %MB44 : LREAL;		(* 9 *)
    REAL64_5 AT %MB52 : LREAL;		(* 10 *)
    INT32_1 AT %MB60 : DINT;		(* 11 *)
    INT32_2 AT %MB64 : DINT;		(* 12 *)
    INT32_3 AT %MB68 : DINT;		(* 13 *)
    INT32_4 AT %MB72 : DINT;		(* 14 *)
    INT32_5 AT %MB76 : DINT;		(* 15 *)
    UINT32_1 AT %MB80 : UDINT;		(* 16 *)
    UINT32_2 AT %MB84 : UDINT;		(* 17 *)
    UINT32_3 AT %MB88 : UDINT;		(* 18 *)
    UINT32_4 AT %MB92 : UDINT;		(* 19 *)
    UINT32_5 AT %MB96 : UDINT;		(* 20 *)
    INT16_1 AT %MB100 : INT;		(* 21 *)
    INT16_2 AT %MB102 : INT;		(* 22 *)
    INT16_3 AT %MB104 : INT;		(* 23 *)
    INT16_4 AT %MB106 : INT;		(* 24 *)
    INT16_5 AT %MB108 : INT;		(* 25 *)
    UINT16_1 AT %MB110 : UINT;		(* 26 *)
    UINT16_2 AT %MB112 : UINT;		(* 27 *)
    UINT16_3 AT %MB114 : UINT;		(* 28 *)
    UINT16_4 AT %MB116 : UINT;		(* 29 *)
    UINT16_5 AT %MB118 : UINT;		(* 30 *)
    INT8_1 AT %MB120 : SINT;		(* 31 *)
    INT8_2 AT %MB121 : SINT;		(* 32 *)
    INT8_3 AT %MB122 : SINT;		(* 33 *)
    INT8_4 AT %MB123 : SINT;		(* 34 *)
    INT8_5 AT %MB124 : SINT;		(* 35 *)
    UINT8_1 AT %MB125 : USINT;		(* 36 *)
    UINT8_2 AT %MB126 : USINT;		(* 37 *)
    UINT8_3 AT %MB128 : USINT;		(* 38 *)
    UINT8_4 AT %MB129 : USINT;		(* 39 *)
    UINT8_5 AT %MB130 : USINT;		(* 40 *)
    BOOL_1 AT %MX131.0 : BOOL;		(* 41 *)
    BOOL_2 AT %MX131.1 : BOOL;		(* 42 *)
    BOOL_3 AT %MX131.2 : BOOL;		(* 43 *)
    BOOL_4 AT %MX131.3 : BOOL;		(* 44 *)
    BOOL_5 AT %MX131.4 : BOOL;		(* 45 *)
    ARRAY_1	: ARRAY[1 .. 10] OF SINT;	(* 46 *)
    ARRAY_2	: ARRAY[1 .. 10] OF INT;	(* 47 *)
    ARRAY_3	: ARRAY[1 .. 10] OF DINT;	(* 48 *)
    ARRAY_4	: ARRAY[1 .. 10] OF LREAL;	(* 49 *)
    ARRAY_5	: ARRAY[1 .. 10] OF BOOL;	(* 50 *)
    STRING_1 : STRING(20);
END_VAR
;

Sprache / IDE | Beispielprogram auspacken
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample06.exe
Delphi for .NET (Borland Developer Studio 2006) | Sample06.exe


Delphi ADS.NET Beispiel07

TwinCAT Sample - Delphi - ADS .NET

Lesen und Schreiben von SPS-Variablen eines beliebigen Types

Ab TwinCAT.Ads.NET Version >= 1.0.0.15

Aufgabe

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

Beschreibung

ReadAny

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

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

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

Beispiel:

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

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 der Event-Methode btnWrite_Click wird die Methode TcAdsClient.WriteAny verwendet um die Variablen per Handle zu beschreiben:

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

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

Beispiel:

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

var hArr : Integer;
var arr : Array of Integer := [1, 2, 3, 4];

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

...
adsClient.DeleteVariableHandle(hArr);

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

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

[StructLayout(LayoutKind.Sequential, Pack:=1)]
TSimpleStruct = record
    lrealVal : Double;
    dintVal  : Longint;
end;

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

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

[StructLayout(LayoutKind.Sequential, Pack:=1)]
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;

Anmelden von ADS Notifications

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

notificationHandles.Add(adsClient.AddDeviceNotificationEx('MAIN.dint1', AdsTransMode.OnChange, 100, 0, tbDint1, TypeOf(Longint)));

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

adsClient.AdsNotificationEx += adsClient_AdsNotificationEx;

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

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.

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

Sprache / IDE | Beispielprogram auspacken
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample07.exe
Delphi for .NET (Borland Developer Studio 2006) | Sample07.exe


Delphi ADS.NET Beispiel08

TwinCAT Sample - Delphi - ADS .NET

Statusänderung vom TwinCAT-Router und der SPS erkennen

Ab TwinCAT.Ads.NET Version >= 1.0.0.15

Aufgabe

Erkennen von Statusänderungen des TwinCAT-Routers und der TwinCAT-SPS.

Beschreibung

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

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

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

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.

Sprache / IDE | Beispielprogram auspacken
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample08.exe


Delphi ADS.NET Beispiel09

TwinCAT Sample - Delphi - ADS .NET

ADS-Summenkommando: Lesen oder Schreiben von mehreren Variablen

Systemvoraussetzungen:

  • TwinCAT v2.11 Build >= 1550;

Aufgabe

Dieses Beispiel zeigt, wie man mit einem TcAdsClient.ReadWrite-Befehl mehrere SPS-Variablen lesen oder schreiben kann. Dieses Beispiel zeigt aber auch wie mehrere SPS-Variablen-Handles mit einem ADS-Summenkommando geholt und freigegeben werden können.

Beschreibung

Die einzelnen Unterkommandos werden in einem AdsStream (Write-Daten) zur SPS transportiert und dort hintereinander ausgeführt. Das Ergebnis wird ebenfalls in einem weiteren AdsStream (Rückgabewerte+Read-Daten) von der SPS zum Client transportiert.

Dynamische Arrays eignen sich hervorragend um eine variable Anzahl an Parametern (ADS-Subkommandos) an eine Methode oder Funktion zu übergeben. Das Beispiel nutzt die dynamischen Arrays und macht den Code universell wiederverwendbar. Andere Ansätze sind aber ebenfalls denkbar (Array mit fixer Größe, Strukturelemente usw.).

Folgende Hilfsmethoden dienen als generische ADS-Summenkommando-Befehle um die SPS-Variablenwerte zu schreiben, zu lesen, um Handles zu holen oder sie freigeben usw. In diesen Methoden wird der AdsStream für das eigentliche ADS-Summenkommando mit Parametern gefüllt und das ADS-Summenkommando dann über den TcAdsClient.ReadWrite-Befehl aufgerufen:

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;

Die generischen Methoden werden dann von vier weiteren Methoden wiederverwendet:

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, Oxygene for .NET) Programm

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}

Verbindung aufbauen

Beim Start der Anwendung wird eine Verbindung zur SPS hergestellt und Variablen-Handles über den Methodenaufruf BlockCreateVariableHandle geholt. Die Namen der drei SPS-Variablen werden als dynamisches Array an die Methode BlockCreateVariableHandle übergeben.

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

Verbindung trennen

Beim Beenden der Anwendung werden die Variable-Handles freigegeben und die Verbindung zur SPS geschlossen. Die freizugebenden Handles werden als dynamisches Array an die Methode BlockDeleteVariableHandle übergeben.

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

Werte aus der SPS lesen

Bei einem Mausklick auf den Read-Button werden die SPS-Variablenwerte gelesen. Die btnRead_Click-Ereignisbehandlungsroutine benutzt die generische BlockRead-Methode. Die Instanz des AdsStream für die SPS-Daten muss aber vorher mit einer passenden Größe erzeugt werden so dass die Werte aller SPS-Variablen dort hineinpassen. Die Parameter rLen (Read-Längen) und data (AdsStream) werden per Referenz übergeben. Die BlockRead-Methode kann dadurch den AdsStream mit Daten füllen und die tatsächlich zurückgelieferten Datenlängen (rLen) verändern (z.B.: beim Fehler). Beim Erfolg werden die Daten aus dem AdsStream nacheinander gelesen und auf der Form angezeigt.

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

Werte in die SPS schreiben

Beim Mausklick auf den Write-Button werden die Werte aus der Anwendung in die SPS geschrieben. Bie btnWrite_Click-Ereignisbehandlungsroutine benutzt die generische BlockWrite-Methode. Die Instanz des AdsStream wird vor dem Aufruf mit einer passenden Größe erzeugt und mit den zu schreibenden SPS-Variablenwerten gefüllt.

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

Variablen-Handles holen

Beim Holen der Variable-Handles wird die generische BlockReadWrite-Methode verwendet. Die Symbolnamen der SPS-Variablen werden im AdsStream als Write-Daten an die BlockReadWrite-Methode übergeben. Die Handles werden in einem weiteren AdsStream als Read-Daten empfangen. Wie bei der BlockRead-Methode wird auch hier der AdsStream: rData (Read-Daten) und rLen per Referenz übergeben. Nur so kann der Inhalt des Streams von der BlockReadWrite-Methode mit Daten gefüllt werden. Beim Erfolg werden die Handles als dynamisches Array von der Methode als Rückgabeparameter zurückgegeben.

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

Variablen-Handles freigeben

Die Methode: BlockDeleteVariableHandle verwendet die BlockWrite-Methode. Die freizugebenden Handles werden vorher in den AdsStream (Write-Daten) reinkopiert.

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

Generisches ADS-Read-Summenkommando

Die Parameter für das ADS-Read-Summenkommando bestehen aus:
  • IndexGroup = ADSIGRP_SUMUP_READ = 0xF080;
  • IndexOffset = Anzahl der ADS-Subkommandos (im Beispiel = 3);
  • readData (AdsStream) = Speicher, der gelesene Daten entgegen nimmt = ListOf(Ads result) + ListOf(data);
  • writeData (AdsStream) = Speicher, der zu sendende Daten enthält = ADS-Subkommandos = ListOf(indexGroup + indexOffset + readLength);

Die Anordnung der ADS-Subkommandos in writeData beim Lesen der SPS-Variablenwerte:

Die Anordnung der Antwortdaten in readData beim Lesen der SPS-Variablenwerte:

Die Rückgabewerte der einzelnen ADS-Subkommandos werden aus dem readData-Stream extrahiert und von der BlockRead-Methode als Rückgabewert zurückgeliefert. Die eigentlichen Read-Daten (z.B.: SPS-Variablenwerte) werden in den data Methodenparameter umkopiert. Die rLen-Werte werden angepasst (beim Fehler ist die Datenlänge = 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;

Generisches ADS-Write-Summenkommando

Die Parameter für das ADS-Write-Summenkommando bestehen aus:
  • IndexGroup = ADSIGRP_SUMUP_WRITE = 0xF081;
  • IndexOffset = Anzahl der ADS-Subkommandos (im Beispiel = 3);
  • readData (AdsStream) = Speicher, der gelesene Daten entgegen nimmt = ListOf(Ads result);
  • writeData (AdsStream) = Speicher, der zu sendende Daten enthält = ADS-Subkommandos = ListOf(indexGroup + indexOffset + writeLength) + ListOf(data);

Die BlockWrite-Methode wird in diesem Beispiel in folgenden zwei Fällen benutzt:

  • Beim Schreiben der SPS-Variablenwerte => der Methodenparameter: data beinhaltet die neuen SPS-Variablenwerte;
  • Beim Freigeben der Variablen-Handles => der Methodenparameter: data beinhaltet die freizugebenden Handles;

Der Inhalt von data wird in die writeData umkopiert.

Die Anordnung der ADS-Subkommandos in writeData beim Schreiben der SPS-Variablenwerte:

Die Anordnung der Antwortdaten in readData beim Schreiben der SPS-Variablenwerte:

Die Rückgabewerte der einzelnen ADS-Subkommandos werden aus dem readData-Stream extrahiert und von der BlockWrite-Methode als Rückgabewert zurückgeliefert.

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

Generisches ADS-Read/Write-Summenkommando

Die BlockReadWrite-Methode ist eine Kombination aus BlockRead- und BlockWrite-Methode. Die Parameter für das ADS-Read/Write-Summenkommando bestehen aus:

  • IndexGroup = ADSIGRP_SUMUP_READWRITE = 0xF082;
  • IndexOffset = Anzahl der ADS-Subkommandos (im Beispiel = 3);
  • readData (AdsStream) = Speicher, der gelesene Daten entgegen nimmt = ListOf(Ads result + readLength) + ListOf(data);
  • writeData (AdsStream) = Speicher, der zu sendende Daten enthält = ADS-Subkommandos = ListOf(indexGroup + indexOffset + readLength + writeLength) + ListOf(data);

Die BlockReadWrite-Methode wird in diesem Beispiel beim Holen der Handles benutzt. Der Methodenparameter: wData beinhaltet in diesem Fall die Stirngs mit den SPS-Symbolnamen. Vor dem Aufruf werden die Symbolnamen in writeData umkopiert. Die Handles befinden sich nach dem Aufruf in readData und werden von dort in den Methodenparameter rData umkopiert. Die rLen-Werte werden ebenfalls angepasst (beim Fehler ist die Datenlänge = Null). Die Rückgabewerte der einzelnen ADS-Subkommandos werden aus dem readData-Stream extrahiert und von der BlockReadWrite-Methode als Rückgabewert zurückgeliefert.

//////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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.

SPS Programm

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

Sprache / IDE | Beispielprogram auspacken
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample09.exe


Delphi ADS.NET Beispiel11

TwinCAT Sample - Delphi - ADS .NET

Handle einer SPS Variablen löschen

Konsolenapplikation. Dieses Beispiel zeigt wie das Handle einer SPS Variablen gelöscht wird.

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

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.

SPS Programm

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

Sprache / IDE | Beispielprogram auspacken
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample11.exe


Delphi ADS.NET Beispiel12

TwinCAT Sample - Delphi - ADS .NET

Merker synchron aus der SPS lesen

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

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

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.

SPS Programm

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

Sprache / IDE | Beispielprogram auspacken
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample12.exe


Delphi ADS.NET Beispiel13

TwinCAT Sample - Delphi - ADS .NET

Merker synchron in die SPS schreiben

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

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

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.

SPS Programm

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

Sprache / IDE | Beispielprogram auspacken
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample13.exe


Delphi ADS.NET Beispiel14

TwinCAT Sample - Delphi - ADS .NET

SPS starten/stoppen

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

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

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.

Sprache / IDE | Beispielprogram auspacken
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample14.exe


Delphi ADS.NET Beispiel15

TwinCAT Sample - Delphi - ADS .NET

Zugriff per Variablenname

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

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

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.

SPS Programm

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

Sprache / IDE | Beispielprogram auspacken
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | Sample15.exe


TwinCAT ADS Web Service: Delphi 8 .NET Beispiel

TwinCAT ADS Web Service: Delphi Beispiel

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

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

Voraussetzungen:

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

Import in Delphi

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

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

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

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

Hinweis:

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

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

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

Webservice verwenden

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

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

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

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

Und im Konstruktor von TWinForm:

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

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

Wir benutzen in unserem Beispiel folgende Methoden:

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

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

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

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

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

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

cbLen: Anzahl der Bytes, die gelesen werden sollen;

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

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

adsState / pAdsState: PLC Serverstatus.

deviceState / pDeviceState: PLC Gerätestatus;

Die SPS Applikation

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

SPS-Variablen auslesen

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

SPS Variablen schreiben

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

SPS Status lesen

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

SPS Starten/Stoppen

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

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