{*******************************************************}
{                                                       }
{       nrCommLib Communication Library for Delphi      }
{       AT commands processor  (VCL)                    }
{       Copyright (c) 1997-2011 Roman Novgorodov        }
{       v9.27                                           }
{       last update: 07 Jan 2010                        }
{                                                       }
{       e-mail:noro@deepsoftware.com                    }
{       DeepSoftware                                    }
{       http://www.deepsoftware.com/                    }
{                                                       }
{*******************************************************}
{$I nrcommlib.inc}
unit nratcmd;

interface

uses nrcomm, nrclasses, nrwin32,
     {$IFDEF NR_WITH_CONTROLS}
     comctrls, forms,
     {$ENDIF}
     {$IFNDEF LINUX}
       Windows,
     {$ENDIF}
     SysUtils, Classes;

type

  EnrAtError=class(Exception);

  TatInternalState = (atIdle,atWaitReply,atQWaitReply);

  TnrDiagnosticEvent = procedure (Sender:TObject; aAT,aReply:string;var flBreak:boolean) of object;

  TnrCustomATProcessor = class;

  TATReply = class
  public
    fOwner:TnrCustomATProcessor;
    sData:AnsiString;
    sDataUseful:AnsiString;
    idResponse:integer;
    idDriverCmd:integer;
    flReady:boolean;
    constructor Create(aOwner:TnrCustomATProcessor);
    procedure Clear;
    procedure Assign(aSrc:TATReply);
    function IsError:boolean;
  end;

  TATCommand = class
    fName:string;
    fCommand:string;
    idExpectedReply:integer;
    idOrder:integer;
    idCmd:integer; // unique identifier ...
    Data:array[0..7] of byte;
  end;

  TnrCustomATDriver = class
  private
    slCommands:TStringList;
    slResponses:TStringList;
    slSettings:TStringList;
    fCaption: string;
    function GetResponse(Index: integer): TCommEventNew;
    function GetCommands(Index: integer): TATCommand;
    function GetSettings(Index: integer): TATCommand;
    function IndexOf2(aSl:TStringList; CmdId:integer; idxLine: integer):integer;
  protected
    function LoadFromList(slText:TStrings):boolean;
    procedure Clear;
    procedure SetResponsePriority(aTag,aPos:integer);
  public
    constructor Create;
    destructor Destroy;   override;
    function LoadFromString(aStr:PAnsiChar):boolean;
    function LoadFromFile(aFileName:string):boolean;
    function IsResponse(const Ch:byte):integer;   virtual;
    function ReplaceStr(aStr:string):string;
    procedure ResponsesResetState;                virtual;
    function IndexOfSetting(aID: integer; aLine:integer):integer;
    property Caption:string read fCaption write fCaption;
    property Response[Index:integer]:TCommEventNew read GetResponse;
    property Command[Index:integer]:TATCommand read GetCommands;
    property Setting[Index:integer]:TATCommand read GetSettings;
  end;

  TnrCustomATProcessor = class(TnrCustomDataProcessor)
  private
    fDrivers:TList;
    fATState:TatInternalState;
    fTimeOut:cardinal;
    hReplyEvent : THandle;
    fOnDiagnostic: TnrDiagnosticEvent;
    fDriverIndex: integer;
    fCurrentDriver: TnrCustomATDriver;
    fCashReply:boolean;
    function GetTimeOut: cardinal;
    procedure SetTimeOut(const Value: cardinal);
    procedure DoAbort(aBy:boolean);
//    procedure ClearReplies;
    procedure ClearReply;
    function GetDriver(Index: integer): TnrCustomATDriver;
    function GetDriverCount: integer;
    procedure SetDriverIndex(const Value: integer);
    procedure SetExpectedReplyID(const Value: integer);
    procedure SetCashReply(const Value:boolean);
  protected
    fReply:TATReply;
    fReplyLast:TATReply;   // special reply object for wait methods ...
    fDefaultDriver:TnrCustomATDriver;
    slCommands:TStringList;
    slDiagnosticCmd:TStringList;
    fExpectedReplyID:integer;
    fCommandIndex:integer;
    slCashCommands:TStringList;
    slCashReplies:TStringList;
    procedure CmdClear;
    procedure CmdAdd(sAT:string);
    procedure CmdAddByDriverId(aCmdId:integer);
    function CmdSendByDriverId(aCmdId:integer):boolean;
    function ReplaceMacros(aTemplate:string):string; virtual;
    function ReplaceMacrosBack(aTemplate:string):string; virtual;
    procedure CmdRun;
    procedure CmdSend(sAT: AnsiString);              virtual;
    function CmdSendAndWait(sAT:AnsiString):boolean; virtual;
    function CmdSendAndWaitResult(sAT:AnsiString):string; virtual;
    function CmdSendAndWaitByDriver(aCmdId:integer):boolean; virtual;
    function WaitReply(aEvent:THandle; aTimeout:cardinal):boolean; virtual;
    function DoProcessChar(Ch:byte):boolean;          virtual;
    function IsReply(ch:byte):boolean;                virtual;
    function IsReplyStr(s:string):boolean;            virtual;
    function IsErrorReply(aReply:TATReply):boolean;   virtual;
    procedure DoProcessReply(aReply:TATReply);        virtual;
    procedure DoMediumClosed;             override;
    procedure DoAsyncTimeout(Sender:TObject);  override;
    procedure DoError;  virtual;
    procedure DoNextCommand;              virtual;
    function DoCommandFinished:boolean;   virtual;
    procedure DoCommandsFinished;         virtual;
    function GetData(aStr:string):string; virtual;
    procedure Diagnostic; virtual;
    function AddDriver(aCaption, aText:string):TnrCustomATDriver; virtual;
    function AddDriverFromFile(aCaption, aFilePath:string):TnrCustomATDriver; virtual;
    function GetDriverCmdId(aCmd:TATCommand):integer; virtual;
    procedure DriversClearAll; virtual;
    property CommandIndex:integer read fCommandIndex;

    property Drivers[Index:integer]:TnrCustomATDriver read GetDriver;
    property DriverCount:integer read GetDriverCount;
    property DriverIndex:integer read fDriverIndex write SetDriverIndex;
    property Driver:TnrCustomATDriver read fCurrentDriver;

    property OnDiagnostic:TnrDiagnosticEvent read fOnDiagnostic
                                             write fOnDiagnostic;
    property ExpectedReplyID:integer read fExpectedReplyID write SetExpectedReplyID;
    property CashReply:boolean read fCashReply write SetCashReply;


  public     { Public declarations }
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure DoProcessing(Data:PAnsiChar; Len:cardinal; var R:TIgnoreTypes); override;
    property ReplyLast:TATReply read fReplyLast;
  public
    property TimeOut:cardinal read GetTimeOut write SetTimeOut;
  end;

  function atGetParam(sAnswer:string; aIndex:integer):string;

implementation
uses {$IFDEF LINUX}
      Math, DateUtils,
     {$ENDIF}
      nrsysutils;

Const
  CID_RESPONSE_OK       = 0;
  CID_RESPONSE_ERROR    = 3;


  cs_Default_AT_Driver = AnsiString(
  'HKR, Responses, "<cr><lf>OK<cr><lf>",                      1, 00, 00, 00,00,00,00, 00,00,00,00'#13#10+
  'HKR, Responses, "<cr><lf>ERROR<cr><lf>",                   1, 03, 00, 00,00,00,00, 00,00,00,00'#13#10);


function atGetParam(sAnswer:string; aIndex:integer):string;
var i,idx,len:integer;
    s:string;
    chBreak:string;
begin
  sAnswer := Trim(sAnswer);
  Result := '';
  len := length(sAnswer);
  i := 1;
  idx := 1;
  s := '';
  chBreak := ',';
  while i <= len do begin
    if sAnswer[i] = chBreak then begin
      if chBreak = ',' then begin
        Inc(idx);
        if idx > aIndex then break;
      end else chBreak := ',';
    end else begin
      if chBreak = ',' then begin
        if sAnswer[i] ='('
          then chBreak := ')'
        else
        if sAnswer[i] ='"'
          then chBreak := '"'
        else
        if idx = aIndex then s := s + sAnswer[i];
      end else
        if idx = aIndex then s := s + sAnswer[i];
    end;
    inc(i);
    if (i <= len) and (sAnswer[i]=#13) then break;
  end;
  Result := Trim(s);
end;

{ TATReply }

constructor TATReply.Create(aOwner: TnrCustomATProcessor);
begin
  inherited Create;
  fOwner := aOwner;
end;

function TATReply.IsError: boolean;
begin
  Result := True;
  if idResponse >= 0 then begin
    if fOwner.Driver.Response[idResponse].Tag = CID_RESPONSE_ERROR
      then exit;
  end;
  Result := False;
end;

procedure TATReply.Assign(aSrc: TATReply);
begin
  if aSrc <> nil then begin
    sData       := aSrc.sData;
    sDataUseful := aSrc.sDataUseful;
    idResponse  := aSrc.idResponse;
    idDriverCmd := aSrc.idDriverCmd;
    flReady     := aSrc.flReady;
  end;
end;

procedure TATReply.Clear;
begin
  sData := '';
  sDataUseful := '';
  idResponse := -1;
  idDriverCmd := -1;
  flReady := False;
end;

{---------------------------------------------}
{ TnrCustomATDriver                           }
{---------------------------------------------}
constructor TnrCustomATDriver.Create;
begin
  inherited Create;
  slCommands := TStringList.Create;
  slResponses := TStringList.Create;
  slSettings := TStringList.Create;
end;

destructor TnrCustomATDriver.Destroy;
begin
  Clear;
  slCommands.Free;
  slResponses.Free;
  slSettings.Free;
  inherited Destroy;
end;

function TnrCustomATDriver.ReplaceStr(aStr: string): string;
begin
  Result := StringReplace(aStr,'<cr>',#13,[rfReplaceAll,rfIgnoreCase]);
  Result := StringReplace(Result,'<lf>',#10,[rfReplaceAll,rfIgnoreCase]);
end;

function TnrCustomATDriver.LoadFromList(slText: TStrings): boolean;
var i, j :integer;
    s,sPar, sInc:string;
    aResp:TCommEventNew;
    aCmd:TATCommand;
begin
  Result :=True;
  try
    //Clear;
    for i := 0 to slText.Count-1 do begin
      s := Trim(slText[i]);

      if s = '' then continue;
      j := Pos(';',s);
      if j = 1 then continue;
      if j > 1 then s := Copy(s,1, j - 1);

      j := Pos('//',s);
      if j = 1 then continue;
      if j > 1 then s := Copy(s, 1, j - 1);

      // processing include directeive ...
      sInc := StringReplace(s,' ','',[rfReplaceAll]);
      j := Pos('include=', Lowercase(sInc));
      if j >= 1 then begin
        sInc := Trim(Copy(sInc,j + 8, Length(sInc)));
        try
          LoadFromString(PAnsiChar(AnsiString(GetResAsStr(sInc))));
        except
        end;
        continue;
      end;

      sPar := atGetParam(s,1);
      if AnsiCompareText(sPar,'HKR') = 0 then begin
        sPar := atGetParam(s,2);
        s := Trim(Copy(s,Pos(',',s)+1,Length(s)));
        if AnsiCompareText(sPar,'Responses') = 0 then begin
          s := Trim(Copy(s,Pos(',',s)+1,Length(s)));
          aResp := TCommEventNew.Create(AnsiString(ReplaceStr(atGetParam(s,1))));
          aResp.iOrder := StrToIntDef(atGetParam(s,2),1);
          aResp.Tag := StrToIntDef(atGetParam(s,3),0);
          aResp.Data := StrToIntDef(atGetParam(s,4),0);
          slResponses.AddObject(s, aResp);
          //OutPutDebugString(PChar('Resp: ' + IntToStr(slResponses.Count-1)+':' + s+' / '+aResp.Str+'/'+IntToStr(aResp.Tag)));
        end else
        if AnsiCompareText(sPar,'Settings') = 0 then begin
          s := Trim(Copy(s,Pos(',',s)+1,Length(s)));
          //slSettings.Add(s);
          //OutPutDebugString(PChar('Sett:'+s));
          aCmd := TATCommand.Create;
          aCmd.fName := atGetParam(s,1);
          aCmd.idOrder := StrToIntDef(atGetParam(s,2),0);
          aCmd.fCommand := atGetParam(s,3);          
          //aCmd.idExpectedReply := StrToIntDef(atGetParam(s,4),0);
          aCmd.Data[0] := StrToIntDef(atGetParam(s,4),0);
          aCmd.Data[1] := StrToIntDef(atGetParam(s,5),0);
          aCmd.Data[2] := StrToIntDef(atGetParam(s,6),0);
          aCmd.Data[3] := StrToIntDef(atGetParam(s,7),0);
          aCmd.Data[4] := StrToIntDef(atGetParam(s,8),0);
          aCmd.Data[5] := StrToIntDef(atGetParam(s,9),0);
          aCmd.Data[6] := StrToIntDef(atGetParam(s,10),0);
          aCmd.Data[7] := StrToIntDef(atGetParam(s,11),0);

          slSettings.AddObject(s,aCmd);
        end else begin
          aCmd := TATCommand.Create;
          aCmd.fName := atGetParam(s,1);
          aCmd.fCommand := atGetParam(s,2);
          aCmd.idOrder := StrToIntDef(atGetParam(s,3),0);
          aCmd.idExpectedReply := StrToIntDef(atGetParam(s,4),0);
          aCmd.Data[0] := StrToIntDef(atGetParam(s,5),0);
          aCmd.Data[1] := StrToIntDef(atGetParam(s,6),0);
          aCmd.Data[2] := StrToIntDef(atGetParam(s,7),0);
          aCmd.Data[3] := StrToIntDef(atGetParam(s,8),0);
          aCmd.Data[4] := StrToIntDef(atGetParam(s,9),0);
          aCmd.Data[5] := StrToIntDef(atGetParam(s,10),0);
          aCmd.Data[6] := StrToIntDef(atGetParam(s,11),0);
          aCmd.Data[7] := StrToIntDef(atGetParam(s,12),0);
          slCommands.AddObject(s,aCmd);
          //OutPutDebugString(PChar('Cmd:'+s+':'+aCmd.fCommand));
        end;

      end;
    end;
  except
    Result := False;
  end;
end;

function TnrCustomATDriver.LoadFromString(aStr: PAnsiChar):boolean;
var slTemp : TStringList;
begin
  slTemp := TStringList.Create;
  slTemp.Text := String(AnsiString(aStr));
  Result := LoadFromList(slTemp);
  slTemp.Free;
end;

function TnrCustomATDriver.LoadFromFile(aFileName: string):boolean;
var slFile : TStringList;
begin
  slFile := TStringList.Create;
  try
    slFile.LoadFromFile(aFileName);
    Result := LoadFromList(slFile);
  except
    Result := False;
  end;
  slFile.Free;
end;

function TnrCustomATDriver.IndexOf2(aSl:TStringList; CmdId: integer;
  idxLine: integer): integer;
var i : integer;
//    s:string;
begin
  Result := -1;
  for i := 0 to aSl.Count - 1 do begin
    if (TATCommand(aSl.Objects[i]).idCmd = CmdId) and
       (TATCommand(aSl.Objects[i]).idOrder = idxLine) then begin
      Result := i;
      break;
    end;
  end;
end;

function TnrCustomATDriver.IndexOfSetting(aID: integer;
  aLine: integer): integer;
begin
  Result := IndexOf2(slSettings, aId, aLine);
end;

procedure TnrCustomATDriver.Clear;
var i:integer;
begin
  for i := 0 to slResponses.Count - 1 do Response[i].Free;
  slResponses.Clear;
  for i := 0 to slCommands.Count - 1 do Command[i].Free;
  slCommands.Clear;
  for i := 0 to slSettings.Count - 1 do Setting[i].Free;
  slSettings.Clear;
end;

function TnrCustomATDriver.GetResponse(Index: integer): TCommEventNew;
begin
  Result := TCommEventNew(slResponses.Objects[Index]);
end;

function TnrCustomATDriver.GetCommands(Index: integer): TATCommand;
begin
  Result := TATCommand(slCommands.Objects[Index]);
end;

function TnrCustomATDriver.GetSettings(Index: integer): TATCommand;
begin
  Result := TATCommand(slSettings.Objects[Index]);
end;

function TnrCustomATDriver.IsResponse(const Ch: byte): integer;
var i:integer;
begin
  Result := -1;
  for i := 0 to slResponses.Count - 1 do begin
    if Response[i].IsEvent(ch) then begin

      //OutputDebugString(PChar(' is resp:['+ IntToStr(i)+'] ' + Response[i].Str +' = '+slResponses[i]));
      ResponsesResetState;
      Result := i;
      break;
    end;
  end;
end;

procedure TnrCustomATDriver.ResponsesResetState;
var i:integer;
begin
  for i := 0 to slResponses.Count - 1 do Response[i].Reset;
end;

procedure TnrCustomATDriver.SetResponsePriority(aTag,aPos: integer);
var i:integer;
    s:string;
    res:TCommEventNew;
begin

  if aPos >= slResponses.Count then exit;

  if Response[aPos].Tag = aTag then exit;

  for i := 0 to slResponses.Count-1 do
    if Response[i].Tag = aTag then
      if i <> aPos then begin
        s := slResponses[i];
        res := Response[i];
        slResponses.Delete(i);
        slResponses.InsertObject(aPos,s,res);
        //break;
      end;
end;

{---------------------------------------------}
{ TnrCustomATProcessor                        }
{---------------------------------------------}

constructor TnrCustomATProcessor.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  slCashCommands := TStringList.Create;
  slCashReplies := TStringList.Create;
  fReply := TATReply.Create(Self);
  fReplyLast := TATReply.Create(Self);   // special reply object for wait methods ...
  fDefaultDriver := TnrCustomATDriver.Create;
  fDefaultDriver.LoadFromString(cs_Default_AT_Driver);
  fDrivers := TList.Create;
  DriverIndex := -1;
  sLogPrefix:='nrAT: ';
  slDiagnosticCmd := TStringList.Create;
  slCommands := TStringList.Create;
  hReplyEvent := CreateEvent(nil,True,False,nil);
  fTimeOut := 7000;
//  Timer:=TnrTimer.Create;//(Self);
//  Timer.Enabled:=False;
//  Timer.OnTimer:=DoTimeOut;
end;

destructor TnrCustomATProcessor.Destroy;
begin
  if fATState<> atIdle then DoAbort(False);
//  Timer.Free;
  slDiagnosticCmd.Free;
  slCommands.Free;
  CloseHandle(hReplyEvent);
  DriversClearAll;
  fDrivers.Free;
  fDefaultDriver.Free;
  fReplyLast.Free;   // special reply object for wait methods ...
  fReply.Free;
  slCashCommands.Free;
  slCashReplies.Free;
  inherited Destroy;
end;

{procedure TnrCustomATProcessor.RestartTimer;
begin
  Timer.Enabled:=False;
  Timer.OnTimer:=nil;
  Timer.Interval:=fTimeOut;
  Timer.OnTimer:=DoTimeOut;
  Timer.Enabled:=True;
end;}

function TnrCustomATProcessor.GetData(aStr: string): string;
begin
  Result := Trim(StringReplace(aStr,#13#10'OK'#13#10,'',[rfReplaceAll]));
  Result := Trim(StringReplace(Result,#13#10'ERROR'#13#10,'',[rfReplaceAll]));
end;

function TnrCustomATProcessor.GetTimeOut: cardinal;
begin
  Result := fTimeOut;
end;

procedure TnrCustomATProcessor.SetTimeOut(const Value: cardinal);
begin
  fTimeOut := Value;
end;

procedure TnrCustomATProcessor.SetExpectedReplyID(const Value: integer);
begin
  fExpectedReplyID := Value;
  Driver.SetResponsePriority(Value,1);
end;

procedure TnrCustomATProcessor.DoProcessing(Data: PAnsiChar; Len: cardinal;
   var R: TIgnoreTypes);
label GoToAbort;
var i:integer;
begin
  if Active then begin
    for i := 0 to Len - 1 do DoProcessChar(Byte(Data[i]));
  end;

  inherited DoProcessing(Data, Len, R);

end;

function TnrCustomATProcessor.DoProcessChar(Ch:byte): boolean;
begin
  Result := False;
  if IsReply(ch) then begin
    if (fATState <> atIdle) and IsErrorReply(fReply)
      then DoError
      else DoProcessReply(fReply);
    Result := True;
    ClearReply;
  end;
end;

function TnrCustomATProcessor.IsReply(ch: byte): boolean;
var i:integer;
begin
  fReply.sData := fReply.sData + AnsiChar(Ch);

  i := Driver.IsResponse(Ch);
  if i >= 0 then begin
    //OutputDebugString('is reply!');
    fReply.idResponse := i;
    fReply.flReady := True;
    fReply.sDataUseful := AnsiString(Trim(Copy(String(fReply.sData),1,
                          Length(fReply.sData) - Driver.Response[i].Len)));
    if fCommandIndex > 0 then begin
      fReply.idDriverCmd :=  Integer(slCommands.Objects[fCommandIndex-1]);
    end else fReply.idDriverCmd := -1;
    ResetTimeout(0);
    //Timer.Enabled:=False;
  end;

  Result := fReply.flReady;
end;

function TnrCustomATProcessor.IsReplyStr(s: string): boolean;
var i, j:integer;
begin

  ClearReply;
  fReply.sData := AnsiString(s);
  for j := 1 to Length(s) do begin
    i := Driver.IsResponse(Byte(s[j]));
    if i >= 0 then begin
      fReply.idResponse := i;
      fReply.flReady := True;
      fReply.sDataUseful := AnsiString(Trim(Copy(String(fReply.sData),1,
                          Length(fReply.sData) - Driver.Response[i].Len)));
      if fCommandIndex > 0
        then fReply.idDriverCmd :=  Integer(slCommands.Objects[fCommandIndex-1])
        else fReply.idDriverCmd := -1;
      ResetTimeout(0);
      break;
    end;

  end;

  Result := fReply.flReady;
  fReplyLast.Assign(fReply);
  ClearReply;

end;


function TnrCustomATProcessor.IsErrorReply(aReply:TATReply): boolean;
begin
  Result := Driver.Response[aReply.idResponse].Tag = CID_RESPONSE_ERROR;
end;

procedure TnrCustomATProcessor.DoProcessReply(aReply:TATReply);
begin
  if (fATState <> atIdle) and (ExpectedReplyID = Driver.Response[aReply.idResponse].Tag) then begin
    if fATState = atWaitReply
       then DoCommandFinished
       else DoNextCommand;
  end;
end;

procedure TnrCustomATProcessor.DoAsyncTimeout(Sender: TObject);
begin
//  Timer.Enabled:=False;
  ResetTimeout(0);
  //fLastReply := RxReply.sData;
  LoggingFmt(dlDetail,'Timeout!!! %u ms "%s"',[TimeOut,fReply.sData]);
  ClearReply;
  fATState := atIdle;
  SetEvent(hReplyEvent);
end;

procedure TnrCustomATProcessor.DoError;
begin
  LoggingFmt(dlDetail,'ERROR Returned !!! %s',[fReply.sData]);
  fReplyLast.Assign(fReply);
  ClearReply;
  fATState := atIDLE;
  SetEvent(hReplyEvent);
end;

function TnrCustomATProcessor.DoCommandFinished:boolean;
begin
  sleep(20);
  Result := True;
  LoggingFmt(dlDetail,'OK Returned !!! %s',[fReply.sData]);
  fReplyLast.Assign(fReply);
  ClearReply;
  fATState := atIDLE;
  SetEvent(hReplyEvent);
end;

procedure TnrCustomATProcessor.DoCommandsFinished;
begin
  Logging(dlDetail,'Queue finished');
end;

procedure TnrCustomATProcessor.DoAbort(aBy: boolean);
begin

end;

procedure TnrCustomATProcessor.ClearReply;
begin
  fReply.Clear;
end;

{procedure TnrCustomATProcessor.CmdAddByDriver(aCmdFilter: string);
var iLine,iCmd:integer;
    sCmd:string;
begin
  iLine := 1;
  repeat
    iCmd := Driver.IndexOf(aCmdFilter,iLine);
    if iCmd >= 0 then begin
      sCmd := Driver.Command[iCmd].fCommand;// atGetParam(,2);
      sCmd := ReplaceMacros(sCmd);
      slCommands.AddObject(sCmd,TObject(iCmd));
      Inc(iLine);
    end;
  until iCmd < 0;
end; }

function TnrCustomATProcessor.CmdSendByDriverId(aCmdId: integer):boolean;
var iLine,iCmd:integer;
    sCmd:string;
begin
  Result := False;
  if not IsDeviceEnable then exit;
  iLine := 1;
  repeat
    iCmd := Driver.IndexOf2(Driver.slCommands,aCmdId,iLine);
    if not IsDeviceEnable then break;
    if iCmd >= 0 then begin
      sCmd := Driver.Command[iCmd].fCommand;// atGetParam(,2);
      sCmd := ReplaceMacros(sCmd);
      CmdSend(AnsiString(sCmd));
{      ExpectedReplyID := Driver.Command[iCmd].idExpectedReply;
      Result :=  WaitReply(0,0);
      if (not Result) then begin
        fReplyLast.Clear;
        break;
      end else begin
        fReplyLast.idDriverCmd := iCmd;
        if IsErrorReply(fReplyLast) then break;
      end;}
      Inc(iLine);
      Result := True;
    end;
  until iCmd < 0;
end;

procedure TnrCustomATProcessor.CmdAddByDriverId(aCmdId: integer);
var iLine,iCmd:integer;
    sCmd:string;
begin
  iLine := 1;
  repeat
    iCmd := Driver.IndexOf2(Driver.slCommands,aCmdId,iLine);
    if iCmd >= 0 then begin
      sCmd := Driver.Command[iCmd].fCommand;// atGetParam(,2);
      sCmd := ReplaceMacros(sCmd);
      slCommands.AddObject(sCmd,TObject(iCmd));
      Inc(iLine);
    end;
  until iCmd < 0;
end;

function TnrCustomATProcessor.CmdSendAndWaitByDriver(aCmdId:integer): boolean;
var iLine, iCmd, j :integer;
    sCmd, sCmdCashed:string;
begin
  Result := False;
  if not IsDeviceEnable then exit;
  iLine := 1;
  repeat
    iCmd := Driver.IndexOf2(Driver.slCommands,aCmdId,iLine);
    if not IsDeviceEnable then break;
    if iCmd >= 0 then begin

      sCmdCashed := Driver.Command[iCmd].fCommand;// atGetParam(,2);

      Logging(dlDetail, 'cmd: ' + sCmdCashed);

//      Result := False;

      j := -1;
      if fCashReply then begin
        j := slCashCommands.IndexOf(sCmdCashed);
        //if j >= 0 then begin
          //Logging(dlDetail, 'Using cashed reply: ' + slCashReplies[j] + ' for cmd:' + sCmdCashed);
          //Result := true;
          //IsReplyStr(ReplaceMacros(slCashReplies[j]));
        //end;
      end;

      if (not fCashReply) or (j < 0) then begin
        sCmd := ReplaceMacros(sCmdCashed);
        CmdSend(AnsiString(sCmd));
      end else begin
       //ExpectedReplyID := CID_RESPONSE_OK;
       ClearReply;
      end;

      ExpectedReplyID := Driver.Command[iCmd].idExpectedReply;

      if (not fCashReply) or (j < 0) then begin
        Result :=  WaitReply(0,0);
      end else begin
        //Logging(dlDetail, 'Using cashed reply: ' + slCashReplies[j] + ' for cmd:' + sCmdCashed);
        IsReplyStr(ReplaceMacros(slCashReplies[j]));
        Result := true;
      end;

      if (not Result) then begin
        fReplyLast.Clear;
        //break;
      end else begin
        //Logging(dlDetail, 'Check reply: ' + fReplyLast.sData + ' for cmd:' + sCmdCashed + ' ...');
        fReplyLast.idDriverCmd := iCmd;
        Result := not IsErrorReply(fReplyLast);
        //Logging(dlDetail, ' result: ' + BoolToStr(Result, True));
      end;

      if fCashReply and (j < 0) and (Length(fReplyLast.sData) > 0) then begin
        //Logging(dlDetail, 'Add to cash: ' + sCmdCashed + ' ' + fReplyLast.sData);
        slCashCommands.Add(sCmdCashed);
        slCashReplies.Add(ReplaceMacrosBack(String(fReplyLast.sData)));
      end;

      if not Result then break;

      Inc(iLine);
    end;
  until iCmd < 0;
end;

procedure TnrCustomATProcessor.CmdAdd(sAT: string);
begin
  slCommands.AddObject(sAT,TObject(-1));
end;

procedure TnrCustomATProcessor.CmdClear;
begin
  slCommands.Clear;
  fCommandIndex := -1;
//  OutputDebugString('Clear');
end;

procedure TnrCustomATProcessor.CmdRun;
begin
  fCommandIndex := 0;
//  OutputDebugString('Run');
  DoNextCommand;
end;

procedure TnrCustomATProcessor.DoNextCommand;
var i:integer;
begin
  if (fCommandIndex > 0) and (fCommandIndex <= slCommands.Count) then begin
    if not DoCommandFinished then Dec(fCommandIndex);
  end;

  if (fCommandIndex < 0) or (fCommandIndex >= slCommands.Count)
     or (not IsDeviceEnable)then begin
    fATState := atIdle;
    DoCommandsFinished;
    exit;
  end;

  CmdSend(AnsiString(slCommands[fCommandIndex]));

  i := Integer(slCommands.Objects[fCommandIndex]);
  if i = -1
    then ExpectedReplyID := CID_RESPONSE_OK
    else ExpectedReplyID := Driver.Command[i].idExpectedReply;
  fATState := atQWaitReply;
  Inc(fCommandIndex);
end;

procedure TnrCustomATProcessor.CmdSend(sAT: AnsiString);
begin
  if (Length(sAT) = 0) or (not IsDeviceEnable) then exit;
  ResetEvent(hReplyEvent);
  //Device.SendData(@sAT[1], Length(sAT));
  DoDataSend(@sAT[1], Length(sAT));
  Logging(dlDebug,'send:' + String(sAT));
//  RestartTimer;
  ResetTimeout(fTimeOut);
  fATState := atWaitReply;
  ExpectedReplyID := CID_RESPONSE_OK;
  ClearReply;
end;

function TnrCustomATProcessor.CmdSendAndWaitResult(sAT:AnsiString): string;
begin
  CmdSend(ReplaceMacros(sAT));
  WaitReply(0,0);
  Result := String(fReplyLast.sData);
end;

function TnrCustomATProcessor.CmdSendAndWait(sAT: AnsiString) : boolean;
begin
  CmdSend(sAT);
  Result :=  WaitReply(0,0);
  if not Result then fReplyLast.Clear;
end;

function TnrCustomATProcessor.WaitReply(aEvent:THandle; aTimeout:cardinal): boolean;
var r:integer;
    tm:cardinal;
begin
  Result := False;
  if aEvent = 0 then aEvent := hReplyEvent;
  if aTimeout = 0 then aTimeout := fTimeOut;
  tm := GetTickCount + aTimeOut;
//  Reset  aEvent
  while (GetTickCount < tm) and (IsDeviceEnable) do begin
    r := WaitForSingleObject(aEvent,50);
    if r = WAIT_OBJECT_0 then begin
      Result := True;
      //OutputDebugString('Wait DONE !!');
      break;
    end;
    if (r <> WAIT_TIMEOUT) then begin
      //Result := fReply.sData;
      //OutputDebugString('wait Err!!');
      break;
    end;
    //OutputDebugString('wait Timeout!!');
    {$IFDEF NR_WITH_CONTROLS}
    if Assigned(Application) then Application.ProcessMessages;
    {$ENDIF}
  end;
end;

procedure TnrCustomATProcessor.Diagnostic;
begin

end;

function TnrCustomATProcessor.GetDriverCmdId(aCmd: TATCommand): integer;
begin
  Result := 0;
end;

function TnrCustomATProcessor.AddDriver(aCaption,
  aText: string): TnrCustomATDriver;
var i:integer;
begin
  Result := TnrCustomATDriver.Create;
  Result.Caption := aCaption;
  Result.LoadFromString(PAnsiChar(AnsiString(aText)));
  fDrivers.Add(Result);

  for i := 0 to Result.slCommands.Count-1
    do Result.Command[i].idCmd := GetDriverCmdId(Result.Command[i]);

  for i := 0 to Result.slSettings.Count-1
    do Result.Setting[i].idCmd := GetDriverCmdId(Result.Setting[i]);

  Result.SetResponsePriority(0,CID_RESPONSE_ERROR);
  Result.SetResponsePriority(1,CID_RESPONSE_OK);

end;

function TnrCustomATProcessor.AddDriverFromFile(aCaption,
  aFilePath: string): TnrCustomATDriver;
var fileInf: TStringList;
begin
  if Length(aCaption) = 0 then aCaption := ExtractFileName(aFilePath);
  fileInf := TStringList.Create;
  fileInf.LoadFromFile(aFilePath);
  Result := AddDriver(aCaption, fileInf.Text);
  fileInf.Free;
end;

procedure TnrCustomATProcessor.DriversClearAll;
var i:integer;
begin
  for i := 0 to fDrivers.Count-1 do Drivers[i].Free;
  fDrivers.Clear;
  fDriverIndex := -1;
end;

function TnrCustomATProcessor.GetDriver(Index: integer): TnrCustomATDriver;
begin
  Result := TnrCustomATDriver(fDrivers[Index]);
end;

function TnrCustomATProcessor.GetDriverCount: integer;
begin
  Result := fDrivers.Count;
end;

procedure TnrCustomATProcessor.SetDriverIndex(const Value: integer);
begin
  fDriverIndex := Value;
  if Value < 0
    then fCurrentDriver := fDefaultDriver
    else fCurrentDriver := Drivers[Value];
end;

function TnrCustomATProcessor.ReplaceMacros(aTemplate: string): string;
begin
  Result := StringReplace(aTemplate,'<cr>',#13,[rfReplaceAll,rfIgnoreCase]);
  Result := StringReplace(Result,'<lf>',#10,[rfReplaceAll,rfIgnoreCase]);
  Result := StringReplace(Result,'<#26>',#26,[rfReplaceAll,rfIgnoreCase]);
  Result := StringReplace(Result,'<q>','"',[rfReplaceAll,rfIgnoreCase]);
  Result := StringReplace(Result,'<sem>',';',[rfReplaceAll,rfIgnoreCase]);
end;

function TnrCustomATProcessor.ReplaceMacrosBack(aTemplate: string): string;
begin
  Result := StringReplace(aTemplate,#13,'<cr>',[rfReplaceAll,rfIgnoreCase]);
  Result := StringReplace(Result,#10,'<lf>',[rfReplaceAll,rfIgnoreCase]);
  Result := StringReplace(Result,#26,'<#26>',[rfReplaceAll,rfIgnoreCase]);
  Result := StringReplace(Result,'"','<q>',[rfReplaceAll,rfIgnoreCase]);
  Result := StringReplace(Result,';','<sem>',[rfReplaceAll,rfIgnoreCase]);
end;

procedure TnrCustomATProcessor.DoMediumClosed;
begin
  ResetTimeout(0);
  inherited DoMediumClosed;
end;

procedure TnrCustomATProcessor.SetCashReply(const Value:boolean);
begin
  slCashCommands.Clear;
  slCashReplies.Clear;
  fCashReply := Value;
end;


end.


