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)

Sample07.exe

Delphi for .NET (Borland Developer Studio 2006)

Sample07.exe