// NoDaveComponent.pas
//
// A unit implementing a wrapper component for easy access to a S7-PLC with
// the libnodave.dll of Thomas Hergenhahn (http://libnodave.sourceforge.net)
//
// (C) 2005, 2006 Gebr. Schmid GmbH + Co., Freudenstadt, Germany
//
// Author: Axel Kinting (akinting@schmid-online.de)
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

{$IFDEF FPC}
  {$MODE Delphi}
{$ENDIF}

unit NoDaveComponent;

//The Unit NoDaveComponent implements the class TNoDave, which encapsulates the access to the libnodave.dll.~[BR]
//With TNoDave and libnodave.dll it is very easy to read and write data from and to a S7 PLC.~[BR]~[BR]
//Simatic, Simatic S5, Simatic S7, S7-200, S7-300, S7-400 are registered Trademarks of Siemens Aktiengesellschaft, Berlin und Muenchen.
//~Author Axel Kinting - Gebr. Schmid GmbH + Co.
//~todo Before Installation:~[BR]
//Please copy the file \pascal\nodave.pas into the directory, where the file nodavecomponent.pas is located !~[BR]~[BR]
//Delphi-Installation:~[BR]
//1. Select Component - Install in the Delphi-menu~[BR]
//2. Select Add... button~[BR]
//3. Select Browse~[BR]
//4. Select NoDaveComponent.pas~[BR]
//5. Select OK~[BR]~[BR]
//Lazarus-Installation:~[BR]
//1. Select Components - Open package file~[BR]
//2. Select nodavepackage.lpk~[BR]
//3. Select Open~[BR]
//4. Select Compile~[BR]
//5. Select Install~[BR]
//6. Select Yes~[BR]

interface

uses
  SysUtils, Classes, NoDave, SyncObjs, Controls, {$IFDEF FPC} LCLIntf, LResources {$ELSE} Windows {$ENDIF};

type

  TNoDaveArea = (                                               //The area of the PLC-Data for the TNoDave-Component.
                  daveSysInfo,                                  //System information of 200 family
                  daveSysFlags,                                 //System flag area of 200 family
                  daveAnaIn,                                    //Analog input words of 200 family
                  daveAnaOut,                                   //Analog output words of 200 family
                  daveInputs,                                   //Input memory image
                  daveOutputs,                                  //Output memory image
                  daveFlags,                                    //Flags/Markers
                  daveDB,                                       //Data Blocks (global data)
                  daveDI,                                       //Data Blocks (instance data) ?
                  daveLocal,                                    //Data Blocks (local data) ?
                  daveV,                                        //unknown Area
                  daveCounter,                                  //Counter
                  daveTimer,                                    //Timer
                  daveP                                         //Peripherie Input/Output
  );

  TNoDaveDebugOption = (                                        //The debug-options for the libnodave.dll
                         daveDebugRawRead,
                         daveDebugSpecialChars,
                         daveDebugRawWrite,
                         daveDebugListReachables,
                         daveDebugInitAdapter,
                         daveDebugConnect,
                         daveDebugPacket,
                         daveDebugByte,
                         daveDebugCompare,
                         daveDebugExchange,
                         daveDebugPDU,
                         daveDebugUpload,
                         daveDebugMPI,
                         daveDebugPrintErrors,
                         daveDebugPassive
  );

  TNoDaveDebugOptions = Set of TNoDaveDebugOption;

  TNoDaveProtocol = (                                           //The type of the communication-protocol for the TNoDave-Component.
                      daveProtoMPI,                             //MPI-Protocol
                      daveProtoMPI2,                            //MPI-Protocol (Andrew's version without STX)
                      daveProtoMPI3,                            //MPI-Protocol (Step 7 Version version)
                      daveProtoMPI4,                            //MPI-Protocol (Andrew's version with STX)
                      daveProtoPPI,                             //PPI-Protocol
                      daveProtoISOTCP,                          //ISO over TCP
                      daveProtoISOTCP243,                       //ISO over TCP (for CP243)
                      daveProtoIBH,                             //IBH-Link TCP/MPI-Adapter
                      daveProtoIBH_PPI,                         //IBH-Link TCP/MPI-Adapter with PPI-Protocol
                      daveProtoS7Online,                        //use S7Onlinx.dll for transport via Siemens CP
                      daveProtoAS511,                           //S5 via programmer-port
                      daveProtoNLPro                            //Deltalogic NetLink-PRO TCP/MPI-Adapter
  );

  TNoDaveSpeed = (                                              //The speed of the MPI-protocol for the TNoDave-Component.
                   daveSpeed9k,
                   daveSpeed19k,
                   daveSpeed187k,
                   daveSpeed500k,
                   daveSpeed1500k,
                   daveSpeed45k,
                   daveSpeed93k
  );

  TNoDaveComSpeed = (                                           //The speed of the COM-Port for the TNoDave-Component.
                      daveComSpeed9_6k,
                      daveComSpeed19_2k,
                      daveComSpeed38_4k,
                      daveComSpeed57_6k,
                      daveComSpeed115_2k
  );

//This is the type of the Event-Handler for the OnError-Event of the TNoDave component.
//~param Sender The TNoDave-instance which is the source of the event.
//~param ErrorMsg A clear text message describing the error.
  TNoDaveOnErrorEvent = procedure(Sender: TObject; ErrorMsg: String) of Object;

//List of reachable Partners in the MPI-Network, True = Station is available at this address.
  TNoDaveReachablePartnersMPI = Array [0..126] of Boolean;

//The Class TNoDave encapsulates the access to the libnodave.dll of Thomas Hergenhahn.
//All the settings for the communication are available in the properties of TNoDave.
  TNoDave = class(TComponent)
  private
    FActive: Boolean;
    FArea: TNoDaveArea;
    FBuffer: Pointer;
    FBufLen: Integer;
    FBufOffs: Integer;
    FComPort: String;
    FCpuRack: Integer;
    FCpuSlot: Integer;
    FCycleTime: Cardinal;
    FDBNumber: Integer;
    FDebugOptions: Integer;
    FInterval: Cardinal;
    FIntfName: String;
    FIntfTimeout: Integer;
    FIPAddress: String;
    FIPPort: Integer;
    FMPILocal: Integer;
    FMPIRemote: Integer;
    FMPISpeed: TNoDaveSpeed;
    FLastError: Integer;
    FOnConnect: TNotifyEvent;
    FOnDisconnect: TNotifyEvent;
    FOnError: TNoDaveOnErrorEvent;
    FOnRead: TNotifyEvent;
    FOnWrite: TNotifyEvent;
    FProtocol: TNoDaveProtocol;
    FSZLBuffer: Pointer;
    FComSpeed: TNoDaveComSpeed;
    FHandle: THandle;
    function GetActive: Boolean;
    function GetBuffer: String;
    function GetDebugOptions: TNoDaveDebugOptions;
    function GetLastErrMsg: String;
    procedure SetActive(V: Boolean);
    procedure SetArea(V: TNoDaveArea);
    procedure SetBufLen(V: Integer);
    procedure SetBufOffs(V: Integer);
    procedure SetComPort(V: String);
    procedure SetCpuRack(V: Integer);
    procedure SetCpuSlot(V: Integer);
    procedure SetDBNumber(V: Integer);
    procedure SetDebugOptions(V: TNoDaveDebugOptions);
    procedure SetInterval(V: Cardinal);
    procedure SetIntfName(V: String);
    procedure SetIntfTimeout(V: Integer);
    procedure SetIPAddress(V: String);
    procedure SetIPPort(V: Integer);
    procedure SetMPILocal(V: Integer);
    procedure SetMPIRemote(V: Integer);
    procedure SetMPISpeed(V: TNoDaveSpeed);
    procedure SetProtocol(V: TNoDaveProtocol);
    function GetSZLCount: Integer;
    function GetSZLItemSize: Integer;
    function GetSZLItem(Index: Integer): Pointer;
    function GetMaxPDUData: Integer;
    procedure SetComSpeed(V: TNoDaveComSpeed);
    function GetHandle: THandle;
  protected
    ConnectPending: Boolean;
    DaveFDS: _daveOSSerialType;
    DaveConn: PDaveConnection;
    DaveIntf: PDaveInterface;
    LockNoDave: TCriticalSection;
    ReadThread: TThread;
    procedure DoConnect(OnlyIntf: Boolean = False);
    procedure DoOnConnect;
    procedure DoOnDisconnect;
    procedure DoOnError(ErrorMsg: String);
    procedure DoOnRead;
    procedure DoOnWrite;
    procedure DoReadBytes(Area: TNoDaveArea; DB, Start, Size: Integer; Buffer: Pointer = Nil);
    procedure DoWriteBytes(Area: TNoDaveArea; DB, Start, Size: Integer; Buffer: Pointer = Nil);
    procedure DoWriteValue(Address, Size: Integer; Value: Pointer);
    procedure Loaded; override;
    procedure WriteBit(Area: TNoDaveArea; DB, Address, Bit: Integer; Value: Boolean); overload;
    function AreaCode(Area: TNoDaveArea): Integer;
    function ProtCode(Prot: TNoDaveProtocol): Integer;
    function BufferAt(Address: Integer; Size: Integer = 1; Buffer: Pointer = Nil; BufOffs: Integer = 0; BufLen: Integer = 0): Pointer;
  public
    AreaID: Integer;                                                                        //S7-ID of the selected ~[link .Area Area]
    constructor Create(aOwner: TComponent); override;
    destructor Destroy; override;
    procedure Connect(Wait: Boolean = True);
    procedure Disconnect;
    procedure Lock;
    procedure Unlock;
    procedure ResetInterface;
    procedure DoSetDebug(Options: Integer);
    procedure ReadBytes(Buffer: Pointer = Nil); overload;
    Procedure WriteBytes(Buffer: Pointer = Nil); overload;
    procedure ReadBytes(Area: TNoDaveArea; DB, Start, Size: Integer; Buffer: Pointer = Nil); overload;
    procedure WriteBytes(Area: TNoDaveArea; DB, Start, Size: Integer; Buffer: Pointer = Nil); overload;
    procedure WriteBit(Address, Bit: Integer; Value: Boolean); overload;
    procedure WriteByte(Address: Integer; Value: Byte);
    procedure WriteWord(Address: Integer; Value: Word);
    procedure WriteInt(Address: Integer; Value: SmallInt);
    procedure WriteDWord(Address: Integer; Value: LongWord);
    procedure WriteDInt(Address: Integer; Value: LongInt);
    procedure WriteFloat(Address: Integer; Value: Single);
    function GetBit(Address, Bit: Integer; Buffer: Pointer = Nil; BufOffs: Integer = 0; BufLen: Integer = 0): Boolean;
    function GetByte(Address: Integer; Buffer: Pointer = Nil; BufOffs: Integer = 0; BufLen: Integer = 0): Byte;
    function GetWord(Address: Integer; Buffer: Pointer = Nil; BufOffs: Integer = 0; BufLen: Integer = 0): Word;
    function GetInt(Address: Integer; Buffer: Pointer = Nil; BufOffs: Integer = 0; BufLen: Integer = 0): SmallInt;
    function GetDWord(Address: Integer; Buffer: Pointer = Nil; BufOffs: Integer = 0; BufLen: Integer = 0): LongWord;
    function GetDInt(Address: Integer; Buffer: Pointer = Nil; BufOffs: Integer = 0; BufLen: Integer = 0): LongInt;
    function GetFloat(Address: Integer; Buffer: Pointer = Nil; BufOffs: Integer = 0; BufLen: Integer = 0): Double;
    function GetDBSize(DB: Integer): Integer;
    function ListReachablePartners: TNoDaveReachablePartnersMPI;
    function Swap16(Value: SmallInt): SmallInt;
    function Swap32(Value: Integer): Integer;
    function GetErrorMsg(Error: Integer): String;
    function ReadSZL(ID, Index: Integer): Integer;
    property SZLCount: Integer read GetSZLCount;                                            //Property for the number of items in the internal SZL-Buffer
    property SZLItem[Index: Integer]: Pointer read GetSZLItem;                              //Property for the items in the internal SZL-Buffer
    property SZLItemSize: Integer read GetSZLItemSize;                                      //Property for the size of one item in the internal SZL-Buffer
    property MaxPDUData: Integer read GetMaxPDUData;                                        //Property for the maximum datasize of one read-request
    property Handle: THandle read GetHandle;                                                //Property for the Windows-Handle used for system-calls
  published
    property Active: Boolean read GetActive write SetActive;                                //Property for the connection-status.
    property Area: TNoDaveArea read FArea write SetArea;                                    //Property for the PLC-area
    property Buffer: String read GetBuffer;                                                 //Property for the pointer to the internal buffer memory.
    property BufLen: Integer read FBufLen write SetBufLen;                                  //Property for the length of the buffer.
    property BufOffs: Integer read FBufOffs write SetBufOffs;                               //Property for the offset of the buffer within the address-range of the PLC.
    property COMPort: String read FComPort write SetComPort;                                //Property for the name of the COM-Port used for the serial-to-MPI adapter.
    property COMSpeed: TNoDaveComSpeed read FComSpeed write SetComSpeed;                    //Property for the speed of the COM-Port used for the serial-to-MPI adapter.
    property CPURack: Integer read FCpuRack write SetCpuRack;                               //Property for the number of the rack containing the CPU of the PLC.
    property CPUSlot: Integer read FCpuSlot write SetCpuSlot;                               //Property for the number of the slot containing the CPU of the PLC.
    property CycleTime: Cardinal read FCycleTime;                                           //Property for the duration in ms of the last communication cycle.
    property DBNumber: Integer read FDBNumber write SetDBNumber;                            //Property for the number of the datablock in the PLC.
    property DebugOptions: TNoDaveDebugOptions read GetDebugOptions write SetDebugOptions;  //Property for the debug-options.
    property Interval: Cardinal read FInterval write SetInterval;                           //Property for the minimal round-trip cycle time for the background-communication with the PLC in milliseconds.
    property IntfName: String read FIntfName write SetIntfName;                             //Property for the symbolic name of the interface.
    property IntfTimeout: Integer read FIntfTimeout write SetIntfTimeout;                   //Property for the timeout of the interface in milliseconds.
    property IPAddress: String read FIPAddress write SetIPAddress;                          //Property for the IP-address or name of the TCP/IP partner station.
    property IPPort: Integer read FIPPort write SetIPPort;                                  //Property for the IP-port of the TCP/IP partner station.
    property LastError: Integer read FLastError;                                            //Property for the return-code of the last call of a communication-method.
    property LastErrMsg: String read GetLastErrMsg;                                         //Property for the text describing the code in ~[link .LastError LastError].
    property MPILocal: Integer read FMPILocal write SetMPILocal;                            //Property for the local MPI-address used for the MPI-communication.
    property MPIRemote: Integer read FMPIRemote write SetMPIRemote;                         //Property for the remote MPI-address used for the MPI-communication.
    property MPISpeed: TNoDaveSpeed read FMPISpeed write SetMPISpeed;                       //Property for the MPI-speed used for the MPI-communication.
    property OnConnect: TNotifyEvent read FOnConnect write FOnConnect;                      //Property for the OnConnect-eventhandler
    property OnDisconnect: TNotifyEvent read FOnDisconnect write FOnDisconnect;             //Property for the OnDisconnect-eventhandler
    property OnError: TNoDaveOnErrorEvent read FOnError write FOnError;                     //Property for the OnError-eventhandler
    property OnRead: TNotifyEvent read FOnRead write FOnRead;                               //Property for the OnRead-eventhandler
    property OnWrite: TNotifyEvent read FOnWrite write FOnWrite;                            //Property for the OnWrite-eventhandler
    property Protocol: TNoDaveProtocol read FProtocol write SetProtocol;                    //Property for the Protocol used for the communication with the PLC.
  end;


  TSzlLed = (NONE, SF, INTF, EXTF, RUN, STOP, FRCE, CRST, BAF, USR, USR1, BUS1F, BUS2F, REDF, MSTR, RACK0, RACK1, RACK2, IFM1F, IFM2F);

  TSzlBGIdent = Record
    Index:      Word;
    MlfB:       packed Array[1..20] of Char;
    BGTyp:      Word;
    AusBG:      packed Array[1..4] of Byte;
  end;
  PSzlBGIdent = ^TSzlBGIdent;

  TSzlUserMemory = Record
    Index:      Word;
    Code:       Word;
    Size:       LongWord;
    Mode:       Word;
    Granu:      Word;
    Area1:      LongWord;
    Used1:      LongWord;
    Free1:      LongWord;
    Area2:      LongWord;
    Used2:      LongWord;
    Free2:      LongWord;
  end;
  PSzlUserMemory = ^TSzlUserMemory;

  TSzlSystemMemory = Record
    Index:      Word;
    Code:       Word;
    Count:      Word;
    Reman:      Word;
  end;
  PSzlSystemMemory = ^TSzlSystemMemory;

  TSzlBlockType = Record
    Index:      Word;
    Count:      Word;
    Size:       Word;
    MemSize:    LongWord;
  end;
  PSzlBlockType = TSzlBlockType;

  TSzlLedState = packed Record
    Index:      Word;
    State:      Byte;
    Blink:      Byte;
  end;
  PSzlLedState = ^TSzlLedState;

  TSzlBGState = Record
    Addr1:      Word;
    Addr2:      Word;
    LogAddr:    Word;
    ConfTyp:    Word;
    RealTyp:    Word;
    Count:      Word;
    EAStatus:   Word;
    Area:       Word;
  end;
  PSzlBGState = ^TSzlBGState;

  TSzlStationState = Record
    Status:     packed Array[0..15] of Byte;
  end;
  PSzlStationState = ^TSzlStationState;

  TSzlDiagMessage = packed Record
    ID:         Word;
    Info:       Array[1..5] of Word;
    Year:       Byte;
    Month:      Byte;
    Day:        Byte;
    Hour:       Byte;
    Minute:     Byte;
    Second:     Byte;
    MSec:       Word;
  end;
  PSzlDiagMessage = ^TSzlDiagMessage;

  TSzlBGDiagInfo = Record
    Info:       packed Array[1..4] of Byte;
  end;
  PSzlBGDiagInfo = ^TSzlBGDiagInfo;


procedure Register;

implementation

type

//Worker-thread for asynchronous connecting with the PLC.
  TNoDaveConnectThread = class(TThread)
  private
    NoDave: TNoDave;
    ErrMsg: String;
  protected
    procedure DoOnError;
    procedure DoOnConnect;
    procedure Execute; override;
  public
    constructor Create(Target: TNoDave);
  end;

//Worker-thread for the background-communication with the PLC.
  TNoDaveReadThread = class(TThread)
  private
    NoDave: TNoDave;
    ErrMsg: String;
  protected
    procedure DoOnError;
    procedure DoOnRead;
    procedure Execute; override;
  public
    constructor Create(Target: TNoDave);
  end;

//Installation of TNoDave in the component palette
procedure Register;
begin
  RegisterComponents('System',[TNoDave]);
end;

{ TNoDaveConnectThread }

//Create the worker-thread for asynchronous connecting with the PLC.
//~param Target The TNoDave-instance to connect with the PLC.
constructor TNoDaveConnectThread.Create(Target: TNoDave);
begin
  inherited Create(False);
  NoDave:=Target;
  FreeOnTerminate:=True;
end;

//Synchronization-method for calling the OnConnect-Event of the TNoDave-instance.
procedure TNoDaveConnectThread.DoOnConnect;
begin
  NoDave.DoOnConnect;
end;

//Synchronization-method for calling the OnError-Event of the TNoDave-instance.
procedure TNoDaveConnectThread.DoOnError;
begin
  NoDave.DoOnError(ErrMsg);
end;

//Open the connection to the PLC. If successfull then call the OnConnect-Event else call the OnError-Event of the TNoDave-instance.
procedure TNoDaveConnectThread.Execute;
begin
  try
    NoDave.DoConnect;
    If NoDave.Active then Synchronize(DoOnConnect);
  except
    on E: Exception do
    begin
      ErrMsg:='Error in function TNoDaveConnectThread.Execute: ' + E.Message;
      Synchronize(DoOnError);
    end;
  end;
  NoDave.ConnectPending:=False;
end;

{ TNoDaveReadThread }

//Create the worker-thread for the background-communication with the PLC.
//~param Target The TNoDave-instance for the communication with the PLC.
constructor TNoDaveReadThread.Create(Target: TNoDave);
begin
  inherited Create(False);
  NoDave:=Target;
end;

//Synchronization-method for calling the OnError-Event of the TNoDave-instance.
procedure TNoDaveReadThread.DoOnError;
begin
  NoDave.DoOnError(ErrMsg);
end;

//Synchronization-method for calling the OnRead-Event of the TNoDave-instance.
procedure TNoDaveReadThread.DoOnRead;
begin
  NoDave.DoOnRead;
end;

//Read the data from the PLC, call the OnRead-Event of the TNoDave-instance, wait until the round-trip cycle time is reached and
//then start again from the beginning until the Connection of the TNoDave-instance is active. Disconnect the TNoDave-instance if
//the connection is not longer valid.
procedure TNoDaveReadThread.Execute;
var
  StartTime: Cardinal;
  NextTime: Cardinal;
begin
  While NoDave.Active and (NoDave.Interval > 0) do
  begin
    StartTime:=GetTickCount;
    NextTime:=StartTime + NoDave.Interval;
    try
      NoDave.ReadBytes;
      If NoDave.LastError = 0 then Synchronize(DoOnRead) else
      begin
        If NoDave.LastError = -1 then NoDave.Disconnect else
        begin
          ErrMsg:=NoDave.LastErrMsg;
          Synchronize(DoOnError);
        end;
      end;
    except
      on E: Exception do
      begin
        ErrMsg:='Error in function TNoDaveReadThread.Execute: ' + E.Message;
        Synchronize(DoOnError);
      end;
    end;
    While GetTickCount < NextTime do Sleep(1);
  end;
end;

{ TNoDave }

//Initialize a new instance of the TNoDave component.
//~param aOwner Owner of the created instance.
constructor TNoDave.Create(aOwner: TComponent);
begin
  inherited;
  AreaID:=-1;
  FActive:=False;
  FArea:=daveDB;
  FBuffer:=Nil;
  FBufLen:=0;
  FBufOffs:=0;
  FComPort:='COM1:';
  FComSpeed:=daveComSpeed38_4k;
  FCpuRack:=0;
  FCpuSlot:=2;
  FDBNumber:=1;
  FDebugOptions:=0;
  FIntfName:='IF1';
  FIntfTimeout:=1500000;
  FIPAddress:='';
  FIPPort:=102;
  FMPILocal:=0;
  FMPIRemote:=2;
  FMPISpeed:=daveSpeed187k;
  FLastError:=0;
  FProtocol:=daveProtoMPI;
  LockNoDave:=TCriticalSection.Create;
  daveSetDebug(FDebugOptions);
end;

//Close an active connection and call the inherited Destroy method.
destructor TNoDave.Destroy;
begin
  Disconnect;
  If Assigned(FBuffer) then
  try
    FreeMem(FBuffer);
  except
  end;
  If FHandle <> 0 then
  try
    DeallocateHWnd(FHandle);
  except
  end;
  FHandle:=0;
  LockNoDave.Free;
  inherited;
end;

//Determine the S7-ID of an Area.
//~param Area Requested Area.
//~result S7-ID of the Area.
function TNoDave.AreaCode(Area: TNoDaveArea): Integer;
begin
  Result:=AreaID;
  If Result = -1 then
  begin
    Case Area of
      daveSysInfo:    Result:=3;
      daveSysFlags:   Result:=5;
      daveAnaIn:      Result:=6;
      daveAnaOut:     Result:=7;
      daveCounter:    Result:=28;
      daveTimer:      Result:=29;
      daveP:          Result:=128;
      daveInputs:     Result:=129;
      daveOutputs:    Result:=130;
      daveFlags:      Result:=131;
      daveDB:         Result:=132;
      daveDI:         Result:=133;
      daveLocal:      Result:=134;
      daveV:          Result:=135;
    end;
  end;
end;

//Return a Pointer to the requested PLC-data point within the buffer.
//~param Address PLC-Address of  the datapoint.
//~param Size Size of the datapoint in bytes.
//~param Buffer Pointer to the buffer holding the PLC-data. The internal buffer is used, if Nil (default).
//~param BufOffs Offset-address of the buffer within the address-range of the PLC.
//~param BufLen Length of the buffer in bytes.
//~result Pointer to the requested data point if the address is located in the buffer, else Nil.
function TNoDave.BufferAt(Address: Integer; Size: Integer; Buffer: Pointer; BufOffs: Integer; BufLen: Integer): Pointer;
var
  Offset: Integer;
begin
  Result:=Nil;
  try
    If Assigned(Buffer) then
    begin
      Offset:=Address - BufOffs;
      If Offset + Size <= BufLen then Result:=Pointer(Integer(Buffer) +  Offset);
    end else begin
      Offset:=Address - FBufOffs;
      If Offset + Size <= FBufLen then Result:=Pointer(Integer(FBuffer) + Offset);
    end;
  except
    On E: Exception do DoOnError('Error in function TNoDave.BufferAt(' + IntToStr(Address) + ', ' + IntToStr(Size) + ', $' +
                                 IntToHex(Integer(Buffer), 8) + ', ' + IntToStr(BufOffs) + ', ' + IntToStr(BufLen) + '): ' +
                                 E.Message);
  end;
end;

//Open the connection to the PLC.
//~param Wait If False the connection is opened asyncronous in a separate thread. Default is True.
procedure TNoDave.Connect(Wait: Boolean = True);
begin
  If not FActive and not ConnectPending then
  begin
    ConnectPending:=True;
    If Wait then
    begin
      try
        DoConnect;
        If Active and not (csLoading in ComponentState) then DoOnConnect;
      except
        On E: Exception do DoOnError('Error in function TNoDave.Connect: ' + E.Message);
      end;
      ConnectPending:=False;
    end else begin
      TNoDaveConnectThread.Create(Self);
    end;
  end;
end;

//Close the connection to the PLC.
procedure TNoDave.Disconnect;
begin
  If Active then
  begin
    try
      daveDisconnectPLC(DaveConn);
      daveFree(DaveConn);
      daveDisconnectAdapter(DaveIntf);
      daveFree(DaveIntf);
      If FProtocol <> daveProtoS7Online then closePort(DaveFDS.rfd) else closeS7online(DaveFDS.rfd);
    except
      On E: Exception do DoOnError('Error in function TNoDave.Disconnect: ' + E.Message);
    end;
    FActive:=False;
    DoOnDisconnect;
  end;
end;

//Open the connection to the PLC specified by the properties ~[link .Protocol Protocol], ~[link .CPURack CPURack], ~[link .CPUSlot CPUSlot],
//~[link .COMPort COMPort], ~[link .IPAddress IPAddress], ~[link .IPPort IPPort], ~[link .MPILocal MPILocal], ~[link .MPIRemote MPIRemote]
// and/or ~[link .MPISpeed MPISpeed]
//~param OnlyIntf Open only the interface, don't connect to the PLC
procedure TNoDave.DoConnect(OnlyIntf: Boolean = False);
var
  Address: String;
  Speed: PChar;
begin
  If not FActive then
  begin
    If not (csLoading in ComponentState) then
    begin
      Case FProtocol of
        daveProtoMPI, daveProtoMPI2, daveProtoMPI3, daveProtoMPI4, daveProtoPPI, daveProtoAS511:
          begin
            Address:=FComPort + #0;
            Case ComSpeed of
              daveComSpeed9_6k:   Speed:='9600';
              daveComSpeed19_2k:  Speed:='19200';
              daveComSpeed38_4k:  Speed:='38400';
              daveComSpeed57_6k:  Speed:='57600';
              daveComSpeed115_2k: Speed:='115200';
              else                Speed:='38400';
            end;
            DaveFDS.rfd:=SetPort(@Address[1], Speed, 'O');
          end;
        daveProtoISOTCP, daveProtoISOTCP243, daveProtoIBH, daveProtoIBH_PPI, daveProtoNLPro:
          begin
            Address:=FIPAddress + #0;
            DaveFDS.rfd:=OpenSocket(FIPPort, @Address[1]);
          end;
        daveProtoS7Online:
          begin
            Address:=FComPort + #0;
            DaveFDS.rfd:=OpenS7Online(@Address[1], Handle);
          end;
      end;
      DaveFDS.wfd:=DaveFDS.rfd;
      If (DaveFDS.rfd > 0) or ((DaveFDS.rfd = 0) and (FProtocol = daveProtoS7Online)) then
      begin
        Address:=FIntfName + #0;
        DaveIntf:=daveNewInterface(DaveFDS, @Address[1], Ord(FMPIlocal), ProtCode(FProtocol), Ord(FMPISpeed));
        DaveIntf^.timeout:=FIntfTimeout;
        If not OnlyIntf then
        begin
          FLastError:=daveInitAdapter(DaveIntf);
          If FLastError = 0 then
          begin
            DaveConn:=daveNewConnection(DaveIntf, FMPIRemote, FCpuRack, FCpuSlot);
            FLastError:=daveConnectPLC(DaveConn);
            FActive:=(FLastError = 0);
            If Active then ReadBytes else DoOnError(daveStrerror(FLastError));
          end;
        end;
      end;
    end else FActive:=True;
  end;
end;

//Create the worker-thread for cyclic reading if neccessary and call the OnConnect-eventhandler if specified.
procedure TNoDave.DoOnConnect;
begin
  If (FInterval > 0) and not Assigned(ReadThread) and not (csDesigning in ComponentState)
    then ReadThread:=TNoDaveReadThread.Create(Self);
  If Assigned(OnConnect) then OnConnect(Self);
end;

//Stop and Destroy the worker-thread for cyclic reading if neccessary and call the OnDisconnect-eventhandler if specified.
procedure TNoDave.DoOnDisconnect;
begin
  If Assigned(ReadThread) then
  begin
    ReadThread.WaitFor;
    FreeAndNil(ReadThread);
  end;
  If Assigned(OnDisconnect) then OnDisconnect(Self);
end;

//Call the OnError-eventhandler if specified.
//~param ErrorMsg The text-message for the OnError-event
procedure TNoDave.DoOnError(ErrorMsg: String);
begin
  If Assigned(OnError) then OnError(Self, ErrorMsg);
end;

//Call the OnRead-eventhandler if specified.
procedure TNoDave.DoOnRead;
begin
  If Assigned(OnRead) then OnRead(Self);
end;

//Call the OnWrite-eventhandler if specified.
procedure TNoDave.DoOnWrite;
begin
  If Assigned(OnWrite) then OnWrite(Self);
end;

//Read the PLC-data into the buffer.
//~param Area Requested PLC-area.
//~param DB Number of requested datablock. Only used, if reading from Datablocks in the PLC.
//~param Start Start-address of the requested data within the address-range of the PLC.
//~param Size Length of the requested PLC-data in bytes.
//~param Buffer Pointer to the buffer. The internal buffer of the instance is used, if Nil (default).
procedure TNoDave.DoReadBytes(Area: TNoDaveArea; DB, Start, Size: Integer; Buffer: Pointer);
var
  Index, Length, MaxLen: Integer;
  StartTime: Cardinal;
begin
  Index:=0;
  StartTime:=GetTickCount;
  MaxLen:=MaxPDUData;
  While (Index < Size) and (MaxLen > 0) do
  begin
    Length:=(Size - Index);
    If Length > MaxLen then Length:=MaxLen;
    try
      LockNoDave.Enter;
      FLastError:=daveReadBytes(DaveConn, AreaCode(Area), DB, Index+Start, Size, Pointer(Integer(Buffer) + Index));
    except
      On E: Exception do DoOnError('Error in function TNoDave.DoReadBytes: ' + E.Message);
    end;
    LockNoDave.Leave;
    Inc(Index, Length);
  end;
  try
    FCycleTime:=GetTickCount - StartTime;
  except
  end;
end;

//Write the Buffer-data into the PLC.
//~param Area Requested PLC-area.
//~param DB Number of requested datablock. Only used, if reading from Datablocks in the PLC.
//~param Start Start-address of the requested data within the address-range of the PLC.
//~param Size Length of the requested PLC-data in bytes.
//~param Buffer Pointer to the buffer. The internal buffer of the instance is used, if Nil (default).
procedure TNoDave.DoWriteBytes(Area: TNoDaveArea; DB, Start, Size: Integer; Buffer: Pointer);
var
  Index, Length, MaxLen: Integer;
begin
  Index:=0;
  MaxLen:=MaxPDUData-4;
  While (Index < Size) and (MaxLen > 0) do
  begin
    Length:=(Size - Index);
    If Length > MaxLen then Length:=MaxLen;
    try
      LockNoDave.Enter;
      FLastError:=daveWriteBytes(DaveConn, AreaCode(Area), DB, Index+Start, Size, Pointer(Integer(Buffer) + Index));
    except
      On E: Exception do DoOnError('Error in function TNoDave.DoWriteBytes: ' + E.Message);
    end;
    LockNoDave.Leave;
    Inc(Index, Length);
  end;
end;

//Write a single value into the specified address of the PLC without changing the properties of the TNoDave-instance.
//~param Address PLC-Address of the data point.
//~param Size Size in bytes of the value.
//~param Value The value to be written.
procedure TNoDave.DoWriteValue(Address, Size: Integer; Value: Pointer);
begin
  try
    LockNoDave.Enter;
    FLastError:=daveWriteBytes(DaveConn, AreaCode(FArea), FDBNumber, Address, Size, Value);
  except
    On E: Exception do DoOnError('Error in function TNoDave.DoWriteValue: ' + E.Message);
  end;
  LockNoDave.Leave;
end;

//Set the debug-options of the libnodave.dll
//~param Options Value of the debug-options.
procedure TNoDave.DoSetDebug(Options: Integer);
begin
  daveSetDebug(Options);
  FDebugOptions:=Options;
end;

//Return the state of the connection to the PLC.
//~result True, if connection is open, else False.
function TNoDave.GetActive: Boolean;
begin
  Result:=FActive and not (csLoading in ComponentState);
end;

//Return a textual representation of the content of the internal buffer.
//~result String with the hexadecimal value of the bytes in the buffer.
function TNoDave.GetBuffer: String;
var
  Index: Integer;
  Value: Byte;
begin
  Result:='';
  If Assigned(FBuffer) then
  begin
    Index:=0;
    While Index < FBufLen do
    begin
      Value:=Byte(Pointer(Integer(FBuffer) + Index)^);
      If Result <> '' then Result:=Result + ' ';
      Result:=Result + IntToHex(Value, 2);
      Inc(Index);
    end;
  end;
end;

//Return the Bit-value read last from the PLC at the specified address.
//~param Address Byte-address of the requested value
//~param Bit Bit-address of the requested value
//~param Buffer Pointer to the buffer holding the PLC-data. The internal buffer is used, if Nil (default).
//~param BufOffs Offset-address of the buffer within the address-range of the PLC.
//~param BufLen Length of the buffer in bytes.
//~result The requested value or False, if the requested address was not found within the buffer.
function TNoDave.GetBit(Address, Bit: Integer; Buffer: Pointer; BufOffs: Integer; BufLen: Integer): Boolean;
var
  BufPtr: Pointer;
begin
  BufPtr:=BufferAt(Address, 1, Buffer, BufOffs, BufLen);
  If Assigned(BufPtr) then Result:=(daveGetU8From(BufPtr) and (1 shl Bit)) <> 0 else Result:=False;
end;

//Return the Byte-value read last from the PLC at the specified address.
//~param Address Address of the requested value
//~param Buffer Pointer to the buffer holding the PLC-data. The internal buffer is used, if Nil (default).
//~param BufOffs Offset-address of the buffer within the address-range of the PLC.
//~param BufLen Length of the buffer in bytes.
//~result The requested value or 0, if the requested address was not found within the buffer.
function TNoDave.GetByte(Address: Integer; Buffer: Pointer; BufOffs: Integer; BufLen: Integer): Byte;
var
  BufPtr: Pointer;
begin
  BufPtr:=BufferAt(Address, 1, Buffer, BufOffs, BufLen);
  If Assigned(BufPtr) then Result:=daveGetU8From(BufPtr) else Result:=0;
end;

//Return the LongInt-value read last from the PLC at the specified address.
//~param Address Address of the requested value
//~param Buffer Pointer to the buffer holding the PLC-data. The internal buffer is used, if Nil (default).
//~param BufOffs Offset-address of the buffer within the address-range of the PLC.
//~param BufLen Length of the buffer in bytes.
//~result The requested value or 0, if the requested address was not found within the buffer.
function TNoDave.GetDInt(Address: Integer; Buffer: Pointer; BufOffs: Integer; BufLen: Integer): LongInt;
var
  BufPtr: Pointer;
begin
  BufPtr:=BufferAt(Address, 4, Buffer, BufOffs, BufLen);
  If Assigned(BufPtr) then Result:=daveGetS32From(BufPtr) else Result:=0;
end;

//Return the LongWord-value read last from the PLC at the specified address.
//~param Address Address of the requested value
//~param Buffer Pointer to the buffer holding the PLC-data. The internal buffer is used, if Nil (default).
//~param BufOffs Offset-address of the buffer within the address-range of the PLC.
//~param BufLen Length of the buffer in bytes.
//~result The requested value or 0, if the requested address was not found within the buffer.
function TNoDave.GetDWord(Address: Integer; Buffer: Pointer; BufOffs: Integer; BufLen: Integer): LongWord;
var
  BufPtr: Pointer;
begin
  BufPtr:=BufferAt(Address, 4, Buffer, BufOffs, BufLen);
  If Assigned(BufPtr) then Result:=daveGetU32From(BufPtr) else Result:=0;
end;

//Return the Float-value read last from the PLC at the specified address.
//~param Address Address of the requested value
//~param Buffer Pointer to the buffer holding the PLC-data. The internal buffer is used, if Nil (default).
//~param BufOffs Offset-address of the buffer within the address-range of the PLC.
//~param BufLen Length of the buffer in bytes.
//~result The requested value or 0, if the requested address was not found within the buffer.
function TNoDave.GetFloat(Address: Integer; Buffer: Pointer; BufOffs: Integer; BufLen: Integer): Double;
var
  BufPtr: Pointer;
begin
  BufPtr:=BufferAt(Address, 4, Buffer, BufOffs, BufLen);
  If Assigned(BufPtr) then Result:=daveGetFloatFrom(BufPtr) else Result:=0;
end;

//Return the Window-Handle (HWND) used for system-calls.
//~result The Window-Handle (HWND).
function TNoDave.GetHandle: THandle;
var
  Parent: TComponent;
begin
  Parent:=Self;
  while Assigned(Parent.Owner) and not (Parent is TWinControl) do Parent:=Parent.Owner;
  if Parent is TWinControl then Result:=TWinControl(Parent).Handle else
  begin
    If FHandle = 0 then FHandle:=AllocateHwnd(Nil);
    Result:=FHandle;
  end;
end;

//Return the SmallInt-value read last from the PLC at the specified address.
//~param Address Address of the requested value
//~param Buffer Pointer to the buffer holding the PLC-data. The internal buffer is used, if Nil (default).
//~param BufOffs Offset-address of the buffer within the address-range of the PLC.
//~param BufLen Length of the buffer in bytes.
//~result The requested value or 0, if the requested address was not found within the buffer.
function TNoDave.GetInt(Address: Integer; Buffer: Pointer; BufOffs: Integer; BufLen: Integer): SmallInt;
var
  BufPtr: Pointer;
begin
  BufPtr:=BufferAt(Address, 2, Buffer, BufOffs, BufLen);
  If Assigned(BufPtr) then Result:=daveGetS16From(BufPtr) else Result:=0;
end;

//Return the Word-value read last from the PLC at the specified address.
//~param Address Address of the requested value
//~param Buffer Pointer to the buffer holding the PLC-data. The internal buffer is used, if Nil (default).
//~param BufOffs Offset-address of the buffer within the address-range of the PLC.
//~param BufLen Length of the buffer in bytes.
//~result The requested value or 0, if the requested address was not found within the buffer.
function TNoDave.GetWord(Address: Integer; Buffer: Pointer; BufOffs: Integer; BufLen: Integer): Word;
var
  BufPtr: Pointer;
begin
  BufPtr:=BufferAt(Address, 2, Buffer, BufOffs, BufLen);
  If Assigned(BufPtr) then Result:=daveGetU16From(BufPtr) else Result:=0;
end;

//Return the description of the return-code in ~[link .LastError LastError].
//~result The description of the return-code.
function TNoDave.GetLastErrMsg: String;
begin
  If FLastError = 0 then Result:='' else Result:=GetErrorMsg(FLastError);
end;

//Scan the MPI-bus for all reachable partners
//~result List with True for available partners and False for unavailable partners.
function TNoDave.ListReachablePartners: TNoDaveReachablePartnersMPI;
type
  TByteList = Array [0..126] of Byte;
  PByteList = ^TByteList;
var
  List: Pointer;
  WasActive: Boolean;
  Index: Integer;
begin
  GetMem(List, SizeOf(TByteList));
  try
    WasActive:=Active;
    Active:=True;
    If Active then
    begin
      daveListReachablePartners(DaveIntf, List);
      Index:=0;
      While Index < 127 do
      begin
        Result[Index]:=(PByteList(List)^[Index] = daveMPIReachable);
        Inc(Index);
      end;
    end else begin
      Index:=0;
      While Index < 127 do
      begin
        Result[Index]:=False;
        Inc(Index);
      end;
    end;
    Active:=WasActive;
  finally
    FreeMem(List);
  end;
end;

//Open the connection to the PLC after the instance is completely loaded from the stream and if Active is True.
procedure TNoDave.Loaded;
begin
  inherited;
  If FActive then
  begin
    FActive:=False;
    Connect;
  end;
end;

//Lock the communication-routines for the current tread.
procedure TNoDave.Lock;
begin
  LockNoDave.Enter;
end;

//Determine the libnodave.dll-code of a protocol
//~param Prot The requested protocol
//~result The libnodave.dll code for the protocol
function TNoDave.ProtCode(Prot: TNoDaveProtocol): Integer;
begin
  Result:=-1;
  Case Prot of
    daveProtoMPI:       Result:=0;
    daveProtoMPI2:      Result:=1;
    daveProtoMPI3:      Result:=2;
    daveProtoMPI4:      Result:=3;
    daveProtoPPI:       Result:=10;
    daveProtoAS511:     Result:=20;
    daveProtoS7Online:  Result:=50;
    daveProtoISOTCP:    Result:=122;
    daveProtoISOTCP243: Result:=123;
    daveProtoIBH:       Result:=223;
    daveProtoIBH_PPI:   Result:=224;
    daveProtoNLPro:     Result:=230;
  end;
end;

//Read the Data specified by the properties ~[link .Area Area], ~[link .DBNumber DBNumber], ~[link .BufOffs BufOffs]
//and ~[link .BufLen BufLen] from the PLC into the buffer.
//~param Buffer Pointer to the buffer for PLC-data. The internal buffer is used, if Nil (default).
procedure TNoDave.ReadBytes(Buffer: Pointer);
begin
  If not Assigned(Buffer) then Buffer:=FBuffer;
  If Active then
  begin
    DoReadBytes(FArea, FDBNumber, FBufOffs, FBufLen, Buffer);
    DoOnRead;
  end;
end;

//Read the specified Data from the PLC into the buffer.
//~param Area Requested PLC-area.
//~param DB Number of requested datablock. Only used, if reading from Datablocks in the PLC.
//~param Start Start-address of the requested data within the address-range of the PLC.
//~param Size Length of the requested PLC-data in bytes.
//~param Buffer Pointer to the buffer for PLC-data. The internal buffer is used, if Nil (default).
procedure TNoDave.ReadBytes(Area: TNoDaveArea; DB, Start, Size: Integer; Buffer: Pointer);
begin
  If not Assigned(Buffer) then
  begin
    FArea:=Area;
    FDBNumber:=DB;
    FBufOffs:=Start;
    FBufLen:=Size;
    ReadBytes(Buffer);
  end else begin
    DoReadBytes(Area, DB, Start, Size, Buffer);
  end;
end;

//Set the property ~[link .Active Active] and call either ~[link .Connect Connect] or ~[link .Disconnect Disconnect].
//depending on the requested value.
//~param V The requested state.
procedure TNoDave.SetActive(V: Boolean);
begin
  If V then Connect else Disconnect;
end;

//Set the property ~[link .Area Area].
//~param V The ~[link TNoDaveArea PLC-Area].
procedure TNoDave.SetArea(V: TNoDaveArea);
begin
  FArea:=V;
  If V in [daveDB, daveDI, daveLocal] then
  begin
    If FDBNumber = 0 then FDBNumber:=1;
  end else FDBNumber:=0;
  If Active and (csDesigning in ComponentState) then ReadBytes;
end;

//Set the property ~[link .BufLen BufLen] and reserve the required memory.
//~param V The length of the buffer in bytes.
procedure TNoDave.SetBufLen(V: Integer);
begin
  If Assigned(FBuffer) then
  try
    FreeMem(FBuffer);
  except
  end;
  FBufLen:=V;
  GetMem(FBuffer, FBufLen);
  If Active and (csDesigning in ComponentState) then ReadBytes;
end;

//Set the property ~[link .BufOffs BufOffs].
//~param V The address within the address-range of the PLC.
procedure TNoDave.SetBufOffs(V: Integer);
begin
  FBufOffs:=V;
  If Active and (csDesigning in ComponentState) then ReadBytes;
end;

//Set the property ~[link .ComPort ComPort].
//~param V The Name of the ComPort.
procedure TNoDave.SetComPort(V: String);
begin
  If FProtocol in [daveProtoMPI, daveProtoMPI2, daveProtoMPI3, daveProtoMPI4, daveProtoPPI, daveProtoAS511] then Disconnect;
  FComPort:=V;
end;

//Set the property ~[link .ComSpeed ComComSpeed].
//~param V The Name of the ComPort.
procedure TNoDave.SetComSpeed(V: TNoDaveComSpeed);
begin
  If FProtocol in [daveProtoMPI, daveProtoMPI2, daveProtoMPI3, daveProtoMPI4, daveProtoPPI, daveProtoAS511] then Disconnect;
  FComSpeed:=V;
end;

//Set the property ~[link .CpuRack CpuRack].
//~param V The rack of the PLC containing the CPU.
procedure TNoDave.SetCpuRack(V: Integer);
begin
  Disconnect;
  FCpuRack:=V;
end;

//Set the property ~[link .CpuSlot CpuSlot].
//~param V The Slot of the PLC containing the CPU.
procedure TNoDave.SetCpuSlot(V: Integer);
begin
  Disconnect;
  FCpuSlot:=V;
end;

//Set the property ~[link .DBNumber DBNumber].
//~param V The requested number of the datablock.
procedure TNoDave.SetDBNumber(V: Integer);
begin
  FDBNumber:=V;
  If Active and (csDesigning in ComponentState) then ReadBytes;
end;

//Set the property ~[link .Interval Interval].
//~param V The Interval for the background-communication in milliseconds. 0 if no background-communication is desired.
procedure TNoDave.SetInterval(V: Cardinal);
begin
  FInterval:=V;
  If Active then
  begin
    If (FInterval > 0) then
    begin
      If not Assigned(ReadThread) and not (csDesigning in ComponentState) then ReadThread:=TNoDaveReadThread.Create(Self);
    end else begin
      If Assigned(ReadThread) then
      begin
        ReadThread.WaitFor;
        FreeAndNil(ReadThread);
      end;
    end;
  end;
end;

//Set the property ~[link .IntfName IntfName].
//~param V The symbolic name of the interface.
procedure TNoDave.SetIntfName(V: String);
begin
  Disconnect;
  FIntfName:=V;
end;

//Set the property ~[link .IntfTimeout IntfTimeout].
//~param V The timeout of the interface in milliseconds.
procedure TNoDave.SetIntfTimeout(V: Integer);
begin
  FIntfTimeout:=V;
  If Active then DaveIntf^.timeout:=FIntfTimeout;
end;

//Set the property ~[link .IPAddress IPAddress].
//~param V The IP-address or name of the TCP/IP partner station.
procedure TNoDave.SetIPAddress(V: String);
begin
  If FProtocol in [daveProtoISOTCP, daveProtoISOTCP243, daveProtoIBH, daveProtoIBH_PPI, daveProtoNLPro] then Disconnect;
  FIPAddress:=V;
end;

//Set the property ~[link .IPPort IPPort].
//~param V The IP-Port of the TCP/IP parter station.
procedure TNoDave.SetIPPort(V: Integer);
begin
  If FProtocol in [daveProtoISOTCP, daveProtoISOTCP243, daveProtoIBH, daveProtoIBH_PPI, daveProtoNLPro] then Disconnect;
  FIPPort:=V;
end;

//Set the property ~[link .MPILocal MPILocal].
//~param V The local MPI-address for the MPI communication.
procedure TNoDave.SetMPILocal(V: Integer);
begin
  If FProtocol in [daveProtoMPI, daveProtoMPI2, daveProtoMPI3, daveProtoMPI4, daveProtoPPI, daveProtoIBH, daveProtoIBH_PPI, daveProtoNLPro] then Disconnect;
  FMPILocal:=V;
end;

//Set the property ~[link .MPIRemote MPIRemote].
//~param V The remote MPI-address for the MPI communication.
procedure TNoDave.SetMPIRemote(V: Integer);
begin
  If FProtocol in [daveProtoMPI, daveProtoMPI2, daveProtoMPI3, daveProtoMPI4, daveProtoPPI, daveProtoIBH, daveProtoIBH_PPI, daveProtoNLPro] then Disconnect;
  FMPIRemote:=V;
end;

//Set the property ~[link .MPISpeed MPISpeed].
//~param V The ~[link TNoDaveSpeed speed] of the MPI-bus.
procedure TNoDave.SetMPISpeed(V: TNoDaveSpeed);
begin
  If FProtocol in [daveProtoMPI, daveProtoMPI2, daveProtoMPI3, daveProtoMPI4, daveProtoPPI, daveProtoIBH, daveProtoIBH_PPI, daveProtoNLPro] then Disconnect;
  FMPISpeed:=V;
end;

//Set the property ~[link .Protocol Protocol].
//~param V The requested ~[link TNoDaveProtocol Protocol].
procedure TNoDave.SetProtocol(V: TNoDaveProtocol);
begin
  Disconnect;
  FProtocol:=V;
  If FProtocol in [daveProtoIBH, daveProtoIBH_PPI] then FIPPort:=1099;
  If FProtocol in [daveProtoNLPro] then FIPPort:=7777;
  If FProtocol in [daveProtoISOTCP, daveProtoISOTCP243] then FIPPort:=102;
end;

//Swap the byte-order in a 16-bit value.
//~param Value The value for the conversion.
//~result The converted value.
function TNoDave.Swap16(Value: SmallInt): SmallInt;
begin
  Result:=daveSwapIed_16(Value);
end;

//Swap the byte-order in a 32-bit value.
//~param Value The value for the conversion.
//~result The converted value.
function TNoDave.Swap32(Value: Integer): Integer;
begin
  Result:=daveSwapIed_32(Value);
end;

//Unlock the communication-routines for other threads.
procedure TNoDave.Unlock;
begin
  LockNoDave.Leave;
end;

//Write a Bit-value into the PLC at the specified address without changing the properties of the TNoDave-instance.
//~param Area Requested PLC-area.
//~param DB Number of requested datablock. Only used, if writing into datablocks of the PLC.
//~param Address Byte-address of the value
//~param Bit Bit-address of the value
//~param Value Value to write into the PLC.
procedure TNoDave.WriteBit(Area: TNoDaveArea; DB, Address, Bit: Integer; Value: Boolean);
var
  Output: LongInt;
begin
  try
    LockNoDave.Enter;
    If Value then Output:=1 else Output:=0;
    FLastError:=daveWriteBits(DaveConn, AreaCode(Area), DB, (Address*8)+Bit, 1, @Output);
  except
    On E: Exception do DoOnError('Error in function TNoDave.WriteBit: ' + E.Message);
  end;
  LockNoDave.Leave;
end;

//Write the buffer into the PLC at the address specified by the properties ~[link .Area Area], ~[link .DBNumber DBNumber],
//~[link .BufOffs BufOffs] and ~[link .BufLen BufLen].
//~param Buffer Pointer to the buffer for PLC-data. The internal buffer is used, if Nil (default).
procedure TNoDave.WriteBytes(Buffer: Pointer);
begin
  If not Assigned(Buffer) then Buffer:=FBuffer;
  If Active then
  begin
    DoWriteBytes(FArea, FDBNumber, FBufOffs, FBufLen, Buffer);
    DoOnWrite;
  end;
end;

//Write the buffer into the PLC at the specified address after setting up the properties with the given values.
//~param Area Requested PLC-area. Changes the property ~[link .Area Area].
//~param DB Number of requested datablock. Changes the property ~[link .DBNumber DBNumber]. Only used, if writing into datablocks of the PLC.
//~param Start Start-address of the buffer within the address-range of the PLC. Changes the property ~[link BufOffs].
//~param Size Length of the buffer in bytes. Changes the property ~[link .BufLen BufLen].
//~param Buffer Pointer to the buffer for PLC-data. The internal buffer is used, if Nil (default).
procedure TNoDave.WriteBytes(Area: TNoDaveArea; DB, Start, Size: Integer; Buffer: Pointer);
begin
  If not Assigned(Buffer) then
  begin
    FArea:=Area;
    FDBNumber:=DB;
    FBufOffs:=Start;
    FBufLen:=Size;
    WriteBytes(Buffer);
  end else begin
    DoWriteBytes(Area, DB, Start, Size, Buffer);
  end;
end;

//Write a Bit-value into the PLC at the specified address without changing the properties of the TNoDave-instance.
//~param Address Byte-address of the value
//~param Bit Bit-address of the value
//~param Value Value to write into the PLC.
procedure TNoDave.WriteBit(Address, Bit: Integer; Value: Boolean);
begin
  WriteBit(FArea, FDBNumber, Address, Bit, Value);
end;

//Write a Byte-value into the PLC at the specified address without changing the properties of the TNoDave-instance.
//~param Address Byte-address of the value
//~param Value Value to write into the PLC.
procedure TNoDave.WriteByte(Address: Integer; Value: Byte);
begin
  DoWriteValue(Address, 1, @Value);
end;

//Write a LongInt-value into the PLC at the specified address without changing the properties of the TNoDave-instance.
//~param Address Byte-address of the value
//~param Value Value to write into the PLC.
procedure TNoDave.WriteDInt(Address, Value: Integer);
var
  Dummy: LongInt;
begin
  Dummy:=daveSwapIed_32(Value);
  DoWriteValue(Address, 4, @Dummy);
end;

//Write a LongWord-value into the PLC at the specified address without changing the properties of the TNoDave-instance.
//~param Address Byte-address of the value
//~param Value Value to write into the PLC.
procedure TNoDave.WriteDWord(Address: Integer; Value: LongWord);
var
  Dummy: LongInt;
begin
  Dummy:=daveSwapIed_32(Value);
  DoWriteValue(Address, 4, @Dummy);
end;

//Write a Float-value into the PLC at the specified address without changing the properties of the TNoDave-instance.
//~param Address Byte-address of the value
//~param Value Value to write into the PLC.
procedure TNoDave.WriteFloat(Address: Integer; Value: Single);
var
  Dummy: LongInt;
  FltVal: Single absolute Dummy;
begin
  FltVal:=Value;
  Dummy:=daveSwapIed_32(Dummy);
  DoWriteValue(Address, 4, @Dummy);
end;

//Write a SmallInt-value into the PLC at the specified address without changing the properties of the TNoDave-instance.
//~param Address Byte-address of the value
//~param Value Value to write into the PLC.
procedure TNoDave.WriteInt(Address: Integer; Value: SmallInt);
var
  Dummy: SmallInt;
begin
  Dummy:=daveSwapIed_16(Value);
  DoWriteValue(Address, 2, @Dummy);
end;

//Write a Word-value into the PLC at the specified address without changing the properties of the TNoDave-instance.
//~param Address Byte-address of the value
//~param Value Value to write into the PLC.
procedure TNoDave.WriteWord(Address: Integer; Value: Word);
var
  Dummy: SmallInt;
begin
  Dummy:=daveSwapIed_16(Value);
  DoWriteValue(Address, 2, @Dummy);
end;

//Return the length of a DB.
//~param DB The number of the DB.
//~result Length of the DB in Bytes.
function TNoDave.GetDBSize(DB: Integer): Integer;
var
  Info: daveBlockInfo;
begin
  try
    Result:=daveGetBlockInfo(DaveConn, @Info, daveBlockType_DB, DB);
    If Result = 0 then
    begin
      Result:=Info.length;
    end else Result:=0;
  except
  end;
end;

//Return the actual debug-options.
//~result Set of the actual debug-options.
function TNoDave.GetDebugOptions: TNoDaveDebugOptions;
begin
  Result:=[];
  If FDebugOptions and $0001 <> 0 then Result:=Result + [daveDebugRawRead];
  If FDebugOptions and $0002 <> 0 then Result:=Result + [daveDebugSpecialChars];
  If FDebugOptions and $0004 <> 0 then Result:=Result + [daveDebugRawWrite];
  If FDebugOptions and $0008 <> 0 then Result:=Result + [daveDebugListReachables];
  If FDebugOptions and $0010 <> 0 then Result:=Result + [daveDebugInitAdapter];
  If FDebugOptions and $0020 <> 0 then Result:=Result + [daveDebugConnect];
  If FDebugOptions and $0040 <> 0 then Result:=Result + [daveDebugPacket];
  If FDebugOptions and $0080 <> 0 then Result:=Result + [daveDebugByte];
  If FDebugOptions and $0100 <> 0 then Result:=Result + [daveDebugCompare];
  If FDebugOptions and $0200 <> 0 then Result:=Result + [daveDebugExchange];
  If FDebugOptions and $0400 <> 0 then Result:=Result + [daveDebugPDU];
  If FDebugOptions and $0800 <> 0 then Result:=Result + [daveDebugUpload];
  If FDebugOptions and $1000 <> 0 then Result:=Result + [daveDebugMPI];
  If FDebugOptions and $2000 <> 0 then Result:=Result + [daveDebugPrintErrors];
  If FDebugOptions and $4000 <> 0 then Result:=Result + [daveDebugPassive];
end;

//Set the debug-options to a new value.
//~param V The new set of debug-options.
procedure TNoDave.SetDebugOptions(V: TNoDaveDebugOptions);
var
  Result: Integer;
begin
  Result:=0;
  If daveDebugRawRead in V then Result:=Result + $0001;
  If daveDebugSpecialChars in V then Result:=Result + $0002;
  If daveDebugRawWrite in V then Result:=Result + $0004;
  If daveDebugListReachables in V then Result:=Result + $0008;
  If daveDebugInitAdapter in V then Result:=Result + $0010;
  If daveDebugConnect in V then Result:=Result + $0020;
  If daveDebugPacket in V then Result:=Result + $0040;
  If daveDebugByte in V then Result:=Result + $0080;
  If daveDebugCompare in V then Result:=Result + $0100;
  If daveDebugExchange in V then Result:=Result + $0200;
  If daveDebugPDU in V then Result:=Result + $0400;
  If daveDebugUpload in V then Result:=Result + $0800;
  If daveDebugMPI in V then Result:=Result + $1000;
  If daveDebugPrintErrors in V then Result:=Result + $2000;
  If daveDebugPassive in V then Result:=Result + $4000;
  DoSetDebug(Result);
end;

//Read a SZL-list from the connected PLC.
//~param ID The SZL-ID of the list.
//~param Index The SZL-Index of the list.
//~result Error code for the function result, 0 if OK.
function TNoDave.ReadSZL(ID, Index: Integer): Integer;
var
  WasActive: Boolean;
  HeaderID: Integer;
  Size, Count: Integer;
  Buffer: Pointer;
begin
  WasActive:=Active;
  Active:=True;
  FreeMem(FSZLBuffer);
  FSZLBuffer:=Nil;
  GetMem(Buffer, 8{000});
  HeaderID:=ID; // or $0F00;
  try
    LockNoDave.Enter;
    Count:=daveReadSZL(daveConn, HeaderID, Index, Buffer, 8);
    LockNoDave.Leave;
    If Count = 0 then
    begin
      Size:=GetWord(4, Buffer, 0, 8);
      Count:=GetWord(6, Buffer, 0, 8);
      GetMem(FSZLBuffer, (Size * Count) + 8);
    end;
  except
    Result:=-1;
    Exit;
  end;
  If Assigned(FSZLBuffer) then
  begin
    LockNoDave.Enter;
    Result:=daveReadSZL(daveConn, ID, Index, FSZLBuffer, (Size * Count) + 8);
    LockNoDave.Leave;
  end;
  If Result <> 0 then
  begin
    FreeMem(FSZLBuffer);
    FSZLBuffer:=nil;
  end;
  FreeMem(Buffer);
  Active:=WasActive;
end;


//Return the text message for an error code.
//~param Error The error code.
//~result Text message correspondig to the error code.
function TNoDave.GetErrorMsg(Error: Integer): String;
begin
  Result:=daveStrerror(FLastError);
end;

//Return the count of items in the SZL buffer
//~result Actual item count.
function TNoDave.GetSZLCount: Integer;
begin
  Result:=0;
  try
    If Assigned(FSZLBuffer) then
    begin
      Result:=GetWord(6, FSZLBuffer, 0, 8);
    end;
  except
  end;
end;

//Return the size of one single item in the SZL buffer.
//~result Actual item size.
function TNoDave.GetSZLItemSize: Integer;
begin
  Result:=0;
  try
    If Assigned(FSZLBuffer) then
    begin
      Result:=GetWord(4, FSZLBuffer, 0, 8);
    end;
  except
  end;
end;

//Return a pointer to an item in the SZL buffer.
//~param Index The SZL-Index of the list.
//~result Pointer to the Item[Index].
function TNoDave.GetSZLItem(Index: Integer): Pointer;
var
  BufIdx: Integer;
begin
  try
    If (Index >= 0) and (SZLCount > Index) then
    begin
      BufIdx:=(Index * SZLItemSize) + 8;
      Result:=Pointer(Integer(FSZLBuffer) + BufIdx);
    end;
  except
    Result:=Nil;
  end;
end;

//Reset the NetLink-adapter via network-command
procedure TNoDave.ResetInterface;
begin
  If (Protocol in [daveProtoIBH, daveProtoIBH_PPI]) and not Active then
  begin
    DoConnect(True);
    daveResetIBH(DaveIntf);
    daveFree(DaveIntf);
    closePort(DaveFDS.rfd);
    Sleep(5000);
  end;
end;

//Return the max. datasize in a single read-request.
//~result maximal datasize.
function TNoDave.GetMaxPDUData: Integer;
begin
  Result:=0;
  If Assigned(DaveConn) then
  begin
    Result:=DaveConn^.maxPDUlength - 24;
    If Protocol in [daveProtoIBH, daveProtoIBH_PPI] then Result:=220;
  end;
  if Result <= 0 then Result:=200;
end;

{$IFDEF FPC}
initialization
  {$I nodavecomponent.lrs}
{$ENDIF}
end.

// 24.02.2005 15:15:12 [c:\programme\borland\delphi6\User\gsfexperts\NoDave\LibNoDave.pas]
//     Created !
// 02.03.2005 15:57:15 [c:\programme\borland\delphi6\User\gsfexperts\NoDave\LibNoDave.pas]
//     Cyclic reading implemented in an own thread.
// 13.04.2005 16:47:25 [c:\programme\borland\delphi6\User\gsfexperts\NoDave\LibNoDave.pas]
//     DelphiDoc - comments added.
// 02.05.2005 11:47:36 [c:\programme\borland\delphi6\user\gsfexperts\nodave\LibNoDave.pas]
//     Property for the debug-options added.
// 03.05.2005 08:59:45 [c:\programme\borland\delphi6\user\gsfexperts\nodave\nodavecomponent.pas]
//     Compatibility with FreePascal/Lazarus implemented and renamed to nodavecomponent.
// 22.09.2005 09:05:19 [c:\programme\borland\delphi6\user\gsfexperts\nodave\NoDaveComponent.pas]
//     BugFixes in TNoDave.ReadBytes and TNoDave.WriteBytes.
// 22.09.2005 09:16:15 [c:\programme\borland\delphi6\user\gsfexperts\nodave\NoDaveComponent.pas]
//     Closing the serial port or the socket and cleaning up memory used by libnodave in TNoDave.Disconnect now.
// 24.10.2005 08:15:29 [c:\programme\borland\delphi6\User\gsfexperts\NoDave\NoDaveComponent.pas]
//     Handle = 0 for daveProtoS7Online allowed.
// 28.11.2005 10:37:45 [c:\programme\borland\delphi6\user\gsfexperts\nodave\NoDaveComponent.pas]
//     Function for reading of SZL-lists added.
// 02.12.2005 09:21:42 [c:\programme\borland\delphi6\user\gsfexperts\nodave\NoDaveComponent.pas]
//     Usage of daveReadManyBytes implemented.
// 06.12.2005 18:25:20 [c:\programme\borland\delphi6\user\gsfexperts\nodave\NoDaveComponent.pas]
//     Changed private functions DoOnRead and DoOnWrite.
// 21.02.2006 18:18:50 [c:\programme\borland\delphi6\user\gsfexperts\nodave\NoDaveComponent.pas]
//     Procedure TNoDave.ResetInterface implemented.
// 21.02.2006 19:44:04 [c:\programme\borland\delphi6\user\gsfexperts\nodave\NoDaveComponent.pas]
//     Added function TNoDave.MaxPDUData.
// 27.10.2006 09:21:43 [C:\Programme\Borland\BDS\User\GsfExperts\NoDave\NoDaveComponent.pas]
//     Bugfix in TNoDaveReadThread.Execute. Thanks to Jose for the hint !
// 20.12.2006 09:19:09 [C:\Programme\Borland\BDS\User\GsfExperts\NoDave\NoDaveComponent.pas]
//     Using a windows-handle in OpenS7Online.
