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

Requirements:

The task

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

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

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

The following data types are available in the TwinCAT PLC:

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

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

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

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

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

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

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

The PLC program

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

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

Delphi 7 program

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

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

Establishing the Ads client connection to TwinCAT PLC

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

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

Separating the Ads client connection to TwinCAT PLC

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

Fetch handle for the PLC variable

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

Releasing the PLC variable handle

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

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

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

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

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

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

Writing DATE_AND_TIME to the PLC

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

Reading DATE_AND_TIME from the PLC

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

Writing DATE to the PLC

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

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

Reading DATE from the PLC

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

Writing TIME_OF_DAY to the PLC

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

Reading TIME_OF_DAY from the PLC

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

Writing TIME (time duration) to the PLC

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

Reading TIME (time duration) from the PLC

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

Language / IDE

Unpack sample program

Delphi XE2

Sample09.exe

Delphi 7 or higher (classic)

Sample09.exe