Event driven reading with Delphi

Task

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

Description

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

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

 

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

namespace Sample03;

interface

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

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

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

    constructor;
  end;

implementation

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

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

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

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

method MainForm.MainForm_Load(sender: System.Object; e: System.EventArgs);
begin
  try
    // Creaste instance of class TcAdsClient 
    tcClient := new TcAdsClient();
    // Create connectin with Port 801 on the local PC
    tcClient.Connect(801);
    // Length of the stream = Byte length of all PLC variables
    dataStream := new AdsStream(MAX_ADSSTREAM_SIZE);
    // Encoding is set to ASCII, to read strings
    binRead := new BinaryReader(dataStream, System.Text.Encoding.ASCII);
    // Add notifications
   hConnect[0] := tcClient.AddDeviceNotification('MAIN.boolVal', dataStream, 0, 1, AdsTransMode.OnChange, 100, 0, tbBool);
        hConnect[1] := tcClient.AddDeviceNotification('MAIN.intVal', dataStream, 1, 2, AdsTransMode.OnChange, 100, 0, tbInt);
        hConnect[2] := tcClient.AddDeviceNotification('MAIN.dintVal', dataStream, 3, 4, AdsTransMode.OnChange, 100, 0, tbDint);
        hConnect[3] := tcClient.AddDeviceNotification('MAIN.sintVal', dataStream, 7, 1, AdsTransMode.OnChange, 100, 0, tbSint);
        hConnect[4] := tcClient.AddDeviceNotification('MAIN.lrealVal', dataStream, 8, 8, AdsTransMode.OnChange, 100, 0, tbLreal);
        hConnect[5] := tcClient.AddDeviceNotification('MAIN.realVal', dataStream, 16, 4, AdsTransMode.OnChange, 100, 0, tbReal);
        hConnect[6] := tcClient.AddDeviceNotification('MAIN.stringVal', dataStream, 20, MAX_STRINGVAL_LENGTH + 1, AdsTransMode.OnChange, 100, 0, tbString);
    // Add notification event handler
    tcClient.AdsNotification += tcClient_AdsNotification
  except
    on err: Exception do
      MessageBox.Show(err.Message, err.Source)
  end;
end;

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

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

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


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

end.

 

PLC program

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

 

Requirements

Language / IDE

Unpack the sample program

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

Sample03.exe

Delphi for .NET (Borland Developer Studio 2006)

Sample03.exe