Reading and writing of PLC variables of any type with Delphi
From TwinCAT.Ads.NET version >= 1.0.0.15
Task
Read and Write variables of any type with the help of the ReadAny and WriteAny methods.
Description
ReadAny
In the event method btnRead_Click the method TcAdsClient.ReadAny is used to read a variable by handle:
TwinCAT.Ads.TcAdsClient.ReadAny(System.Int32, System.Type)
TwinCAT.Ads.TcAdsClient.ReadAny(System.Int32, System.Type, System.Int32[])
The type of the variable is passed to the method in the parameter type. In case the method was successfull, the read data will be returned as a object. The type of the object is equal to the type passed in the parameter type. Because some data types(arrays and strings) need additional information, an overload of the method ReadAny exists, that takes an additional parameter args. Full list of supported types can be found in the documentation of the overloaded method.
Example:
A global PLC variable of the type ARRAY[0..3] OF DINT should be read:
var hArr : Integer;
var arr : Arrayof Integer;
hArr := adsClient.CreateVariableHandle(".arr");
arr := Arrayof Integer(adsClient.ReadAny(hArr, TypeOf(Arrayof Integer), [4]));
...
adsClient.DeleteVariableHandle(hArr);
WriteAny
In the event method btnWrite_Click the method TcAdsClient.WriteAny is used to write to a variable by handle:
TwinCAT.Ads.TcAdsClient.WriteAny(System.Int32, System.Object)
TwinCAT.Ads.TcAdsClient.WriteAny(System.Int32, System.Object, System.Int32[])
The parameter value is a reference to the object, that should be written to the PLC variable. Full list of supported types of the object value can be found in the documentation of the overloaded method.
Example:
A global PLC variable of the type ARRAY[0..3] OF DINT should be written:
var hArr : Integer;
var arr : Arrayof Integer := [1, 2, 3, 4];
hArr := adsClient.CreateVariableHandle(".arr");
adsClient.WriteAny(hArr, arr);
...
adsClient.DeleteVariableHandle(hArr);
Reading and writing of structures (not possible with the Compact Framework, CE):
To be able to read or write PLC structures the memory layout of the structure or class in .NET must be the same as in the PLC. The layout of a structure or class can be specified with the attribute StructLayoutAttribute. The LayoutKind must be set to LayoutKind.Sequential and the pack must be set to 1. Therefore the class SimpleStruct is defined as followed:
[StructLayout(LayoutKind.Sequential, Pack:=1)]
TSimpleStruct = record
lrealVal : Double;
dintVal : Longint;
end;
If arrays, strings or boolean values are define the class, one has to specify how these fields should be marshalled. This is accomplished with help of the MarshalAs attribute. Because arrays and strings do not have a fixed length in .NET, the property SizeConst is necessary for arrays and strings. It is not possible to marshal multidimensional arrays or arrays of structures with the :NET Framework 1.1. Multidimensional arrays in the PLC must be mapped to one dimensional arrays in .NET.
In the example the MarshalAsAttribute is used in the class ComplexStruct:
[StructLayout(LayoutKind.Sequential, Pack:=1)]
TComplexStruct = record
intVal : Smallint;
// Specifies how .NET should marshal the array
// SizeConst specifies the number of elements the array has.
[MarshalAs(UnmanagedType.ByValArray, SizeConst:=4)]
dintArr : Array[0..3] of Longint;
[MarshalAs(UnmanagedType.I1)]
boolVal : Boolean;
byteVal : Byte;
// Specifies how .NET should marshal the string
// SizeConst specifies the number of characters the string has.
// (inclusive the terminating null ).
[MarshalAs(UnmanagedType.ByValTStr, SizeConst:=6)]
stringVal : String;
simpleStruct1 : TSimpleStruct;
end;
Register ADS notifcations
In the event method btnAddNotifications_Click the method AddDeviceNotificationEx is used to register notifications for a PLC variable. If the value of a variable changes the event AdsNotificationEx is fired. The difference to the event AdsNotification, is that the value of the variable is stored in an object instead of in an AdsStream. Therefore one has to pass the type of the object to the method AddDeviceNotificationEx:
notificationHandles.Add(adsClient.AddDeviceNotificationEx('MAIN.dint1', AdsTransMode.OnChange, 100, 0, tbDint1, TypeOf(Longint)));
As user object the textbox that should display the value is passed. If the event is fired, the event method adsClient_AdsNotificationEx is called. For this the event must be registered in the MainForm_Load method.
adsClient.AdsNotificationEx += adsClient_AdsNotificationEx;
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) program
namespace Sample07;
interface
uses
System.Drawing,
System.Collections,
System.Collections.Generic,
System.Windows.Forms,
System.ComponentModel, System.IO, TwinCAT.Ads, System.Data, System.Runtime.InteropServices;
type
///////////////////////////////////////////////////////
//TYPE TSimpleStruct :
//STRUCT
// lrealVal : LREAL;
// dintVal : DINT;
//END_STRUCT
//END_TYPE
[StructLayout(LayoutKind.Sequential, Pack:=1)]
TSimpleStruct = record
lrealVal : Double;
dintVal : Longint;
end;
///////////////////////////////////////////////////////
//TYPE TComplexStruct :
//STRUCT
// intVal : INT;
// dintArr : ARRAY[0..3] OF DINT;
// boolVal : BOOL;
// byteVal : BYTE;
// stringVal : STRING(5);
// simpleStruct1 : TSimpleStruct;
//END_STRUCT
//END_TYPE
[StructLayout(LayoutKind.Sequential, Pack:=1)]
TComplexStruct = record
intVal : Smallint;
// Specifies how .NET should marshal the array
// SizeConst specifies the number of elements the array has.
[MarshalAs(UnmanagedType.ByValArray, SizeConst:=4)]
dintArr : Array[0..3] of Longint;
[MarshalAs(UnmanagedType.I1)]
boolVal : Boolean;
byteVal : Byte;
// Specifies how .NET should marshal the string
// SizeConst specifies the number of characters the string has.
// (inclusive the terminating null ).
[MarshalAs(UnmanagedType.ByValTStr, SizeConst:=6)]
stringVal : String;
simpleStruct1 : TSimpleStruct;
end;
/// <summary>
/// Summary description for MainForm.
/// </summary>
MainForm = partial class(System.Windows.Forms.Form)
private
adsClient : TcAdsClient;
//PLC variable handles
hDint1 : Longint;
hInt1 : Longint;
hBool1 : Longint;
hUsint1 : Longint;
hReal1 : Longint;
hLreal1 : Longint;
hStr1 : Longint;
hStr2 : Longint;
hComplexStruct : Longint;
notificationHandles : ArrayList;
method adsClient_AdsNotificationEx(sender: Object; e: AdsNotificationExEventArgs);
method FillStructControls(structure : TComplexStruct);
method GetStructFromControls(var structure : TComplexStruct);
method MainForm_Load(sender: System.Object; e: System.EventArgs);
method MainForm_FormClosing(sender: System.Object; e: System.Windows.Forms.FormClosingEventArgs);
method btnRead_Click(sender: System.Object; e: System.EventArgs);
method btnWrite_Click(sender: System.Object; e: System.EventArgs);
method btnAddNotifications_Click(sender: System.Object; e: System.EventArgs);
method btnDeleteNotifications_Click(sender: System.Object; e: System.EventArgs);
protected
method Dispose(disposing: Boolean); override;
public
constructor;
end;
implementation
{$REGION Construction and Disposition}
constructor MainForm;
begin
//
// Required for Windows Form Designer support
//
InitializeComponent();
//
// TODO: Add any constructor code after InitializeComponent call
//
end;
method MainForm.Dispose(disposing: Boolean);
begin
if disposing then begin
if assigned(components) then
components.Dispose();
//
// TODO: Add custom disposition code here
//
end;
inherited Dispose(disposing);
end;
{$ENDREGION}
////////////////////////////////////////////////////////////////////////////
//
method MainForm.MainForm_Load(sender: System.Object; e: System.EventArgs);
begin
try
// Create instance of TcAdsClient class
adsClient := new TcAdsClient();
// Create connection to the local PLC port 801
adsClient.Connect(801);
// Create handles for the PLC variables;
hBool1 := adsClient.CreateVariableHandle('MAIN.bool1');
hInt1 := adsClient.CreateVariableHandle('MAIN.Int1');
hDint1 := adsClient.CreateVariableHandle('MAIN.dint1');
hUsint1 := adsClient.CreateVariableHandle('MAIN.usint1');
hReal1 := adsClient.CreateVariableHandle('MAIN.real1');
hLreal1 := adsClient.CreateVariableHandle('MAIN.lreal1');
hStr1 := adsClient.CreateVariableHandle('MAIN.str1');
hStr2 := adsClient.CreateVariableHandle('MAIN.str2');
hComplexStruct := adsClient.CreateVariableHandle('MAIN.ComplexStruct1');
// Add notification event handler
adsClient.AdsNotificationEx += adsClient_AdsNotificationEx;
btnAddNotifications.Enabled := True;
btnDelNotifications.Enabled := False;
except
on err: Exception do
MessageBox.Show(err.Message, err.Source)
end;
end;
////////////////////////////////////////////////////////////////////////////
//
method MainForm.MainForm_FormClosing(sender: System.Object; e: System.Windows.Forms.FormClosingEventArgs);
begin
try
// Release handles of PLC variables
adsClient.DeleteVariableHandle(hBool1);
adsClient.DeleteVariableHandle(hInt1);
adsClient.DeleteVariableHandle(hDint1);
adsClient.DeleteVariableHandle(hUsint1);
adsClient.DeleteVariableHandle(hReal1);
adsClient.DeleteVariableHandle(hLreal1);
adsClient.DeleteVariableHandle(hStr1);
adsClient.DeleteVariableHandle(hStr2);
adsClient.DeleteVariableHandle(hComplexStruct);
// Remove notificaiton event handler
adsClient.AdsNotificationEx -= adsClient_AdsNotificationEx;
except
on err: Exception do
MessageBox.Show(err.Message, err.Source)
end;
// Close connection
adsClient.Dispose();
end;
////////////////////////////////////////////////////////////////////////////
//
method MainForm.FillStructControls(structure : TComplexStruct);
begin
tbIntVal.Text := structure.intVal.ToString();
tbdintArr.Text := System.String.Format('{0:d}, {1:d}, {2:d}, {3:d}',
[structure.dintArr[0], structure.dintArr[1], structure.dintArr[2], structure.dintArr[3]]);
tbBoolVal.Text := structure.boolVal.ToString();
tbByteVal.Text := structure.byteVal.ToString();
tbStringVal.Text := structure.stringVal;
tbLrealVal.Text := structure.simpleStruct1.lrealVal.ToString();
tbDintVal.Text := structure.simpleStruct1.dintVal.ToString();
end;
////////////////////////////////////////////////////////////////////////////
//
method MainForm.GetStructFromControls(var structure : TComplexStruct);
begin
structure.intVal := System.Int16.Parse(tbIntVal.Text);
var sArr := tbDintArr.Text.Split([',']);
for i : Integer := 0 to 3 do
structure.dintArr[i] := System.Int32.Parse(sArr[i]);
structure.boolVal := System.Boolean.Parse(tbboolVal.Text);
structure.byteVal := System.Byte.Parse(tbByteVal.Text);
structure.stringVal := tbStringVal.Text;
structure.simpleStruct1.dintVal := System.Int32.Parse(tbDintVal.Text);
structure.simpleStruct1.lrealVal := System.Double.Parse(tbLrealVal.Text);
end;
////////////////////////////////////////////////////////////////////////////
//
method MainForm.btnRead_Click(sender: System.Object; e: System.EventArgs);
begin
try
// Read by handle, the second parameter specifies the type of the variable
tbBool1.Text := adsClient.ReadAny(hBool1, TypeOf(Boolean)).ToString();
tbInt1.Text := adsClient.ReadAny(hInt1, TypeOf(Smallint)).ToString();
tbDint1.Text := adsClient.ReadAny(hDint1, TypeOf(Longint)).ToString();
tbUsint1.Text := adsClient.ReadAny(hUsint1, TypeOf(Byte)).ToString();
tbReal1.Text := adsClient.ReadAny(hReal1, TypeOf(Single)).ToString();
tbLreal1.Text := adsClient.ReadAny(hLreal1, TypeOf(Double)).ToString();
// To read strings we have to pass the number of string characters
// specified in the PLC project(default 80). This value is passed as an int array.
tbStr1.Text := adsClient.ReadAny(hstr1, TypeOf(String), [80]).ToString();
tbStr2.Text := adsClient.ReadAny(hstr2, TypeOf(String), [5]).ToString();
FillStructControls(TComplexStruct(adsClient.ReadAny(hComplexStruct, TypeOf(TComplexStruct))));
except
on err: Exception do
MessageBox.Show(err.Message, err.Source)
end;
end;
////////////////////////////////////////////////////////////////////////////
//
method MainForm.btnWrite_Click(sender: System.Object; e: System.EventArgs);
begin
try
var structure := new TComplexStruct();
// Write by handle, the second parameter is the object to be written to the PLC variable
adsClient.WriteAny(hBool1, System.Boolean.Parse(tbBool1.Text));
adsClient.WriteAny(hInt1, System.Int16.Parse(tbInt1.Text));
adsClient.WriteAny(hDint1, System.Int32.Parse(tbDint1.Text));
adsClient.WriteAny(hUsint1, System.Byte.Parse(tbUsint1.Text));
adsClient.WriteAny(hReal1, System.Single.Parse(tbReal1.Text));
adsClient.WriteAny(hLreal1, System.Double.Parse(tbLreal1.Text));
// To write strings we have to pass the number of string characters
// specified in the PLC project(default 80). This value is passed as an int array.
adsClient.WriteAny(hStr1, tbStr1.Text, [80]);
adsClient.WriteAny(hStr2, tbStr2.Text, [5]);
GetStructFromControls( var structure );
adsClient.WriteAny(hComplexStruct, structure) ;
except
on err: Exception do
MessageBox.Show(err.Message, err.Source)
end;
end;
////////////////////////////////////////////////////////////////////////////
//
method MainForm.btnAddNotifications_Click(sender: System.Object; e: System.EventArgs);
begin
try
if (notificationHandles = Nil) then
notificationhandles := new ArrayList();
notificationHandles.Clear();
// Register notification
notificationHandles.Add(adsClient.AddDeviceNotificationEx('MAIN.bool1', AdsTransMode.OnChange, 100, 0, tbBool1, TypeOf(Boolean)));
notificationHandles.Add(adsClient.AddDeviceNotificationEx('MAIN.int1', AdsTransMode.OnChange, 100, 0, tbInt1, TypeOf(Smallint)));
notificationHandles.Add(adsClient.AddDeviceNotificationEx('MAIN.dint1', AdsTransMode.OnChange, 100, 0, tbDint1, TypeOf(Longint)));
notificationHandles.Add(adsClient.AddDeviceNotificationEx('MAIN.usint1', AdsTransMode.OnChange, 100, 0, tbUsint1, TypeOf(Byte)));
notificationHandles.Add(adsClient.AddDeviceNotificationEx('MAIN.real1', AdsTransMode.OnChange, 100, 0, tbReal1, TypeOf(Single)));
notificationHandles.Add(adsClient.AddDeviceNotificationEx('MAIN.lreal1', AdsTransMode.OnChange, 100, 0, tbLreal1, TypeOf(Double)));
notificationHandles.Add(adsClient.AddDeviceNotificationEx('MAIN.str1', AdsTransMode.OnChange, 100, 0, tbStr1, TypeOf(String), [80]));
notificationHandles.Add(adsClient.AddDeviceNotificationEx('MAIN.str2', AdsTransMode.OnChange, 100, 0, tbStr2, TypeOf(String), [5]));
notificationHandles.Add(adsClient.AddDeviceNotificationEx('MAIN.complexStruct1', AdsTransMode.OnChange, 100, 0, tbDint1, TypeOf(TComplexStruct)));
btnDelNotifications.Enabled := true;
btnAddNotifications.Enabled := false;
except
on err: Exception do
MessageBox.Show(err.Message, err.Source)
end;
end;
////////////////////////////////////////////////////////////////////////////
//
method MainForm.btnDeleteNotifications_Click(sender: System.Object; e: System.EventArgs);
begin
try
btnAddNotifications.Enabled := true;
btnDelNotifications.Enabled := false;
// Delete registered notifications.
for handle : Integer in notificationHandles do
adsClient.DeleteDeviceNotification(handle);
notificationHandles.Clear();
except
on err: Exception do
MessageBox.Show(err.Message, err.Source)
end;
end;
////////////////////////////////////////////////////////////////////////////
//
method MainForm.adsClient_AdsNotificationEx(sender: Object; e: AdsNotificationExEventArgs);
begin
if( e.Value.GetType() = TypeOf(TComplexStruct))then
FillStructControls(TComplexStruct(e.Value))
else
System.Windows.Forms.TextBox(e.UserData).Text := e.Value.ToString();
end;
end.
PLC program
TYPE TSimpleStruct :
STRUCT
lrealVal : LREAL := 1.23;
dintVal : DINT := 120000;
END_STRUCT
END_TYPE
TYPE TComplexStruct :
STRUCT
intVal : INT :=1200;
dintArr : ARRAY[0..3] OF DINT:= 1,2,3,4;
boolVal : BOOL := FALSE;
byteVal : BYTE :=10;
stringVal : STRING(5) := 'hallo';
simpleStruct1 : TSimpleStruct;
END_STRUCT
END_TYPE
PROGRAM MAIN
VAR
(*primitive Types*)
bool1 :BOOL := FALSE;
int1 :INT := 30000;
dint1 :DINT :=125000;
usint1 :USINT :=200;
real1 :REAL := 1.2;
lreal1 :LREAL :=3.5;
(*string Types*)
str1 :STRING := 'this is a test string';
str2 :STRING(5):= 'hallo';
(*struct Types*)
complexStruct1 : TComplexStruct;
END_VAR
;
Download
Language / IDE | Unpack the sample program |
---|---|
Delphi Prism (Embarcadero Prism XE2, Oxygene for .NET) | |
Delphi for .NET (Borland Developer Studio 2006) |