ADS sum command: fetching and releasing multiple variable handles with one ADS command.

Requirements:

The task

When starting the simple Windows application, handles for 15 PLC variables are to be fetched with a sum command. These handles can be used for read access to the variables, for example. When the application is terminated, the handles are to be released with a sum command.

In Delphi it is possible to pass open array parameters to functions. I.e. the number of array elements in such a function parameter is not fixed. Three new functions have been implemented in the TcAdsApi.pas file: AdsSyncSumReadReq, AdsSyncSumWriteReq and AdsSyncSumReadWriteReq. These functions use the functionality of the open array parameters and simplify the use of the sum command under Delphi. The sample project uses these new wrapper functions.

ADS sum command: fetching and releasing multiple variable handles with one ADS command. 1:

The PLC program

The PLC variables were declared as global variables. With a started PLC, value changes of the variables are simulated in the simple MAIN program.

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

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

The communication port is opened when the program starts and closed when it ends.

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;

Fetch handles

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;

Release handles

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

 

Read values "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. 

Language / IDE

Unpack sample program

Delphi XE2

Sample08.exe

Delphi 6 or higher (classic)

Sample08.exe