{*******************************************************}
{                                                       }
{       nrCommLib Communication Library for Delphi      }
{       GSM functionality for AT GSM 07.07 (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 nrgsm;

interface

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

type

  TnrPhoneBookType = (pltMem,pltSim,pltDialed,pltReceived,pltMissed,pltFixedSim,pltUnknown);
  TnrPhoneRecordType = (prtUnknown,prtHome,prtWork,prtMobile,prtFax,prtOther);

  TnrPhoneRecord = class
  private
    fIndex: integer;
    fCaption: string;
    fPhone: string;
    fType: TnrPhoneBookType;
    fDateTime: TDateTime;
    fPhoneType: TnrPhoneRecordType;
    procedure SetCaption(const Value: string);
    function GetDateTime: TDateTime;
    function GetIndex: integer;
    function GetPhone: string;
    function GetPhoneType: TnrPhoneRecordType;
    function GetType: TnrPhoneBookType;
    function GetCaption: string;
    procedure SetPhone(const Value: string);
    procedure SetType(const Value: TnrPhoneBookType);
  public
    function IsMatchPattern(aPattern:string; flCaseSens, flPhone, flCaption:boolean):boolean;
    procedure Assign(aSource:TnrPhoneRecord);
    property Index:integer read GetIndex;
    property Phone:string read GetPhone write SetPhone;
    property Caption:string read GetCaption write SetCaption;
    property DateTime:TDateTime read GetDateTime;
    property ItemType:TnrPhoneBookType read GetType write SetType;
    property PhoneType:TnrPhoneRecordType read GetPhoneType;
  end;

  TnrPhoneBook = class(TList)
  private
    fUsed: integer;
    fTotal: integer;
    fMaxNumLen: integer;
    fMaxTxtLen: integer;
    fValid: boolean;
    fType: TnrPhoneBookType;
    function GetPhoneItem(Index: integer): TnrPhoneRecord;
    function GetMaxNumLen: integer;
    function GetMaxTxtLen: integer;
    function GetTotal: integer;
    function GetType: TnrPhoneBookType;
    function GetUsed: integer;
    function GetValid: boolean;
    procedure SetMaxNumLen(const Value: integer);
    procedure SetMaxTxtLen(const Value: integer);
    procedure SetType(const Value: TnrPhoneBookType);
    procedure SetValid(const Value: boolean);
  public
    procedure Clear; {$IFNDEF VER100} override; {$ENDIF}
    procedure Delete(Index:integer);
    procedure Assign(aSource:TnrPhoneBook);
    function AddRecord(aIndex:integer; aPhone,aCaption:string):TnrPhoneRecord;
    procedure DeleteRecord(aRec:TnrPhoneRecord);
    procedure SaveToFile(aFileName:string); virtual;
    procedure LoadFromFile(aFileName:string); virtual;
    function GetFirstAvailableIndex(var aListIndex:integer):integer;
    property Item[Index:integer]:TnrPhoneRecord read GetPhoneItem; default;
    property Total:integer read GetTotal;
    property Used:integer read GetUsed;
    property MaxCaptionLength:integer read GetMaxTxtLen write SetMaxTxtLen;
    property MaxNumberLength:integer read GetMaxNumLen write SetMaxNumLen;
    property Valid:boolean read GetValid write SetValid;
    property TypeList:TnrPhoneBookType read GetType write SetType;
  end;

  EnrGsmError=class(Exception);

  TgsmMemStorage = (msSIM,msMEM,msOther,msOther2);
  TgsmATType = (gtNokia,gtSE);
  TgsmNewSmsMode = (nsmNotifyOnly, nsmSkipSave, nsmRead);

  TGsmInternalState = (gssIdle,gssInit,gssSend,gssSent,gssRead,gssWrite,gssNotifySms,
                       gssNotifySmsDirect,
                       gssNotifyRing, gssNotifyOther, gssError,gssReadPhoneBook,gssDiagnostic,
                       gssGetSmsList, gssGetSmsListItem,gssDetect);

  TGsmPinStatus = (pinUnknown, pinError, pinReady, pinPIN, pinPUK,
                   pinPIN2, pinPUK2, pinPhSimPIN, pinPhNetPIN);

  TSmsStatus = (ssUnRead, ssRead, ssUnSent, ssSent, ssAll);

  TnrSmsNotifyEvent = procedure (Sender:TObject; aMem:string; idSms:integer; aSms:TnrPduSms) of object;
  TnrGsmNotifyEvent = procedure (Sender:TObject; sData:string) of object;
  TnrGsmProgressEvent = procedure (Sender:TObject; var flBreak:boolean) of object;
//  TnrGsmPINEvent = procedure (Sender:TObject; var aPIN:string) of object;

  TnrGsm = class(TnrCustomATProcessor)
  private
    fText: string;
    fPhoneNumber: string;{ Private declarations }
    sSmsNotifyData :string;
    fOnSmsReceived: TnrSmsNotifyEvent;
    fOnSmsSent: TnrSmsNotifyEvent;
    fDeviceManufacturer: string;
    fDeviceName: string;
    fDeviceIMEI: string;
    fDeviceIMSI: string;
    fOnAfterInit: TNotifyEvent;
    fDeviceVersion: string;
    fPduSms:TnrPduSms;
    slReply:TStringList;
    fOnRing: TnrGsmNotifyEvent;
    fOnUnsolicited :TnrGsmNotifyEvent;
    sRingNotifyData :string;
    sSmsSentNotifyData : string;
    sGsmErrorNotifyData :string;
    sOtherNotifyData :string;
    sSmsListItemData : string;
    fOnGsmError: TnrGsmNotifyEvent;
    fMemTypesWrite: TStrings;
    fMemTypesRead: TStrings;
    fMemTypesSpecial: TStrings;
    fMemorySpecial: string;
    fMemoryWrite: string;
    fMemoryRead: string;
    fMemReadTotal: integer;
    fMemSpecTotal: integer;
    fMemSpecUsed: integer;
    fMemWriteUsed: integer;
    fMemWriteTotal: integer;
    fMemReadUsed: integer;
    flSmsNotifyNokiaMode:boolean;
    fPhoneList: TnrPhoneBook;
    fPhoneListType: TnrPhoneBookType;
    slPhoneBookType:TStringList;
//    fCountIteration:integer;
    fOnPhoneListFinish: TNotifyEvent;
    fOnPhoneListProgress: TnrGsmProgressEvent;
    lStateStack:TList;
    fOnGsmChangeState: TNotifyEvent;
    fGsmAT: TgsmATType;
    fTmpSMSPdu:string;
    fTmpSMSLength:integer;
    fTmpSMSId:integer;
    fTmpRecNo:integer;
    fTmpRecPhone : string;
    fTmpRecName : string;
    slGsmCommands:TStringList;
    fBatteryIsCharging: boolean;
    fBatteryLevel: integer;
    fSignalQuality: integer;
    fSignalQualityMax: integer;
    fAutodetect: boolean;
    fIdMultiPartPDU:integer;
    fNewSmsMode : TgsmNewSmsMode;
    fOnSmsListItem: TnrSmsNotifyEvent;
    fReplyLinesCnt : integer;
    fCallPhoneNumber :string;
    hSmsSentEvent:THandle;
    slSmsFifo:TStringList;
    fPIN:string;
    //fPINStatus:string; // last pins status ...
    fPINStatus:TGsmPinStatus; // last pins status ...
    fIgnoreInitErrors : boolean;
    fGetSmsListStatus: TSmsStatus;
    fInitCommands:string;
    fReplyLastSentSms:TATReply;
    function GetGsmData(aReply:string):string;
    function GetAutodetect: boolean;
    function GetBatteryIsCharging: boolean;
    function GetBatteryLevel: integer;
    function GetDeviceIMEI: string;
    function GetDeviceIMSI: string;
    function GetDeviceManufacturer: string;
    function GetDeviceName: string;
    function GetDeviceVersion: string;
    function GetGsmAT: TgsmATType;
    function GetMemoryRead: string;
    function GetMemorySpecial: string;
    function GetMemoryWrite: string;
    function GetMemReadTotal: integer;
    function GetMemReadUsed: integer;
    function GetMemSpecTotal: integer;
    function GetMemSpecUsed: integer;
    function GetMemTypesRead: TStrings;
    function GetMemTypesSpecial: TStrings;
    function GetMemTypesWrite: TStrings;
    function GetMemWriteTotal: integer;
    function GetMemWriteUsed: integer;
    function GetPhoneList: TnrPhoneBook;
    function GetPhoneListType: TnrPhoneBookType;
    function GetPhoneNumber: string;
    function GetSignalQuality: integer;
    function GetSignalQualityMax: integer;
    function GetText: string;
    procedure SetAutodetect(const Value: boolean);
    procedure SetGsmAT(const Value: TgsmATType);
    procedure SetMemoryRead(const Value: string);
    procedure SetMemorySpecial(const Value: string);
    procedure SetMemoryWrite(const Value: string);
    procedure SetPhoneListType(const Value: TnrPhoneBookType);
    procedure SetPhoneNumber(const Value: string);
    procedure SetText(const Value: string);
    function GetNewSmsMode: TgsmNewSmsMode;
    procedure SetNewSmsMode(const Value: TgsmNewSmsMode);
    function GetPIN: string;
    procedure SetPIN(const Value: string);
    function GetIgnoreInitErrors: boolean;
    procedure SetIgnoreInitErrors(const Value: boolean);
    function GetInitCommands:string;
    procedure SetInitCommands(const Value: string);

  protected
    procedure AddATInit;                  virtual;
    procedure InitDevice;                 virtual;
    function DoCommandFinished : boolean; override;
    procedure DoCommandsFinished;         override;
    procedure DoMediumClosed;             override;
    procedure DoMediumOpened;             override;

    procedure DoProcessReply(aReply:TATReply);        override;

    procedure DoProcessNotifySms(aSmsNotify:string); virtual;
    procedure DoProcessNotifySmsDirect(aSmsNotify:string); virtual;
    procedure DoProcessRing(aRing:string); virtual;
    procedure DoProcessNotifyOther(aData:string); virtual;
    procedure DoProcessSent(aSent:string); virtual;
    procedure DoProcessError(aErr:string); virtual;
    procedure DoProcessSmsListItem(sData:string); virtual;
    function IsErrorReply(aReply:TATReply):boolean;   override;

    function DoProcessChar(Ch:byte):boolean;         override;
    function ApplySmsAnswer(aReply:AnsiString):boolean;
    procedure DoUpdateMemoryTypes;//(sData:string);
    procedure DoUpdateMemoryState(sData:string;flShort:boolean);
    procedure DoUpdatePhoneBookSize(sData:string; aCmd:TATCommand);
    procedure DoUpdatePhoneBookRecSize(sData:string; aCmd:TATCommand);
    procedure DoUpdateBatteryInfo(sData:string; aCmd:TATCommand);
    procedure DoUpdateSignalInfo(sData:string; aCmd:TATCommand);
    function DoPhoneBookAddRecord(sData:string;aCmd:TATCommand):boolean;

    function GetPhoneListTypeAsString:string;
    procedure StateGsmPush(aState:TGsmInternalState);
    function StateGsmPop:TGsmInternalState;
    procedure StateGsmReset(aState:TGsmInternalState);
    function StateGsmGet:TGsmInternalState;
    function ReplaceMacros(aTemplate:string):string; override;
    function GetDriverCmdId(aCmd:TATCommand):integer; override;
    function GetDecodedPinStatus(aReply:TATReply):TGsmPinStatus;

  public     { Public declarations }
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    procedure SmsSend(aPhone, aText:WideString; aConfirm :boolean);
    {$IFDEF NOOVERLOAD}
//    procedure SmsSendW(aPhone:WideString; aText:WideString; aConfirm:boolean);
    procedure SmsSendPdu(aPDU:TnrPduSms);
    {$ELSE}
    overload;
//    procedure SmsSend(aPhone:WideString; aText:WideString; aConfirm:boolean); overload;
    procedure SmsSend(aPDU:TnrPduSms); overload;
    {$ENDIF}
    function SmsSendWait:boolean;

    function SmsSendWaitRefID(aPhone, aText:WideString; aConfirm :boolean):string;
    {$IFDEF NOOVERLOAD}
    function SmsSendPduWaitRefID(aPDU:TnrPduSms):string;
    {$ELSE}
    overload;
    function SmsSendWaitRefID(aPDU:TnrPduSms) : string; overload;
    {$ENDIF}

    function SmsGet(Index: integer): TnrPduSms;
    function SmsGetText(Index: integer): WideString;
    function SmsDelete(aIndex:integer):boolean;
    procedure SmsList(const flWait:boolean);
    {$IFDEF NOOVERLOAD}
    procedure SmsList2(const flWait:boolean; status: TSmsStatus);
    {$ELSE}
    overload;
    procedure SmsList(const flWait:boolean; status: TSmsStatus); overload;
    {$ENDIF}

    procedure Send;
    procedure Abort;

    procedure UpdateMemoryState;
    procedure UpdatePhoneListState;
    procedure DoError;  override;
    procedure PhoneBookRead;
    function PhoneBookDel(aIndex:integer):boolean;
    function PhoneBookWrite(aPhone,aName:string; aIndex:integer; aType:TnrPhoneRecordType):boolean;
    function IsBusy:boolean;
    procedure UpdateBatteryInfo;
    procedure UpdateSignalInfo;
    function MakeCall(aPhoneNumber:string):boolean;
    procedure AnswerCall;
    procedure Hungup;
    procedure RunDiagnostic;
    function PINGetStatus:string;
    function PINApply: boolean;
    procedure Update;
    function AddDriverFromFile(aCaption, aFilePath:string):TnrCustomATDriver; override;
    procedure DriversClearAll; override;
    function CmdSendAndWaitResult(sAT:AnsiString): string; override;

    property DeviceName:string read GetDeviceName;
    property DeviceVersion:string read GetDeviceVersion;
    property DeviceIMEI:string read GetDeviceIMEI;
    property DeviceIMSI:string read GetDeviceIMSI;
    property DeviceManufacturer:string read GetDeviceManufacturer;
    property MemoryRead:string read GetMemoryRead write SetMemoryRead;
    property MemoryWrite:string read GetMemoryWrite write SetMemoryWrite;
    property MemorySpecial:string read GetMemorySpecial write SetMemorySpecial;
    //property Sms[Index : integer]:TnrPduSms read GetSms;

    property MemTypesRead:TStrings    read GetMemTypesRead;
    property MemTypesWrite:TStrings   read GetMemTypesWrite;
    property MemTypesSpecial:TStrings read GetMemTypesSpecial;

    property MemReadUsed:integer      read GetMemReadUsed;
    property MemReadTotal:integer     read GetMemReadTotal;
    property MemWriteUsed:integer     read GetMemWriteUsed;
    property MemWriteTotal:integer    read GetMemWriteTotal;
    property MemSpecUsed:integer      read GetMemSpecUsed;
    property MemSpecTotal:integer     read GetMemSpecTotal;

    property PhoneList:TnrPhoneBook read GetPhoneList;
    property PhoneListType:TnrPhoneBookType read GetPhoneListType write SetPhoneListType;
    property GsmAT:TgsmATType read GetGsmAT write SetGsmAT;

    property BatteryIsCharging:boolean read GetBatteryIsCharging;
    property BatteryLevel:integer read GetBatteryLevel;
    property SignalQuality:integer read GetSignalQuality;
    property SignalQualityMax:integer read GetSignalQualityMax;

    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 PINStatus:string read fPINStatus;
    property PINStatus:TGsmPinStatus read fPINStatus;

  published  { Published declarations }
    property Active;
    property Autodetect:boolean read GetAutodetect write SetAutodetect;
    property DataProcessor; //cascade processing ...
    property Text:string read GetText write SetText;
    property PhoneNumber:string read GetPhoneNumber write SetPhoneNumber;
    property IgnoreInitErrors:boolean read GetIgnoreInitErrors write SetIgnoreInitErrors;
//    property PhoneSMSC:string read fPhoneSMSC write fPhoneSMSC;
    property TimeOut;
    property Log;
    property NewSmsMode:TgsmNewSmsMode read GetNewSmsMode write SetNewSmsMode;
    property PIN:string read GetPIN write SetPIN;
    property OnDataSend;
    property OnFatalError;
    property OnSmsReceived:TnrSmsNotifyEvent read fOnSmsReceived write fOnSmsReceived;
    property OnSmsSent:TnrSmsNotifyEvent read fOnSmsSent write fOnSmsSent;
    property OnSmsListItem:TnrSmsNotifyEvent read fOnSmsListItem write fOnSmsListItem;

    property OnAfterInit:TNotifyEvent read fOnAfterInit write fOnAfterInit;
    property OnRing:TnrGsmNotifyEvent read fOnRing write fOnRing;
    property OnGsmError:TnrGsmNotifyEvent read fOnGsmError write fOnGsmError;
    property OnPhoneListProgress:TnrGsmProgressEvent read fOnPhoneListProgress
                                                     write fOnPhoneListProgress;
    property OnPhoneListFinish:TNotifyEvent read fOnPhoneListFinish
                                            write fOnPhoneListFinish;

    property OnGsmChangeState:TNotifyEvent read fOnGsmChangeState
                                           write fOnGsmChangeState;
    property OnUnsolicited: TnrGsmNotifyEvent read fOnUnsolicited write fOnUnsolicited; 
    property InitCommands:string read GetInitCommands
                                 write SetInitCommands;
    property OnDiagnostic;
  end;

implementation
uses nrsysutils
{$IFDEF TRIAL}
, shellapi, dialogs
{$ENDIF}
;


{$R ..\Res\nrgsmdrv.res}

//{$I nrgsmdrv.pas}

Const
  // codes of GSM drivers responses  ...
  CID_RESPONSE_GSM_SMS_PROMPT             = 101;
  CID_RESPONSE_GSM_SMS_SENT               = 102;
  CID_RESPONSE_GSM_SMS_SENT_ERR           = 103;
  CID_RESPONSE_GSM_SMS_RECEIVED           = 104;
  CID_RESPONSE_GSM_RING                   = 105;
  CID_RESPONSE_GSM_SMS_LIST_ITEM          = 106;
  CID_RESPONSE_GSM_SMS_RECEIVED_DIRECT    = 107;
  CID_RESPONSE_GSM_DETECT                 = 109;
  CID_RESPONSE_GSM_GET_PIN                = 110;
  CID_RESPONSE_UNSOLICITED                = 111;

  // codes for GSM drivers commands ...
  CID_GSM_INIT                            = 200;
  CID_GSM_DETECT                          = 201;
  CID_GSM_GET_MODEL                       = 202;
  CID_GSM_GET_IMEI                        = 203;
  CID_GSM_GET_VER                         = 204;
  CID_GSM_GET_MANUF                       = 205;
  CID_GSM_SET_MODE_TEXT                   = 206;
  CID_GSM_SET_MODE_PDU                    = 207;
  CID_GSM_GET_STATUS_BATTERY              = 208;
  CID_GSM_GET_STATUS_SIGNAL               = 209;
  CID_GSM_SMS_NOTIFY_ENABLE               = 210;
  CID_GSM_RING_NOTIFY_ENABLE              = 211;
  CID_GSM_SET_SMS_MEM                     = 212;
  CID_GSM_GET_SMS_MEM                     = 213;
  CID_GSM_DIAGNOSTICS                     = 214;
  CID_GSM_SMS_NOTIFY_DIRECT               = 215;
  CID_GSM_GET_PIN_STATUS                  = 216;
  CID_GSM_SET_PIN                         = 217;
  CID_GSM_GET_IMSI                        = 218;
  CID_GSM_SMS_DIRECT_CONFIRMATION         = 219;


  CID_GSM_DELETE_SMS                      = 300;
  CID_GSM_SEND_SMS_PDU                    = 301;
  CID_GSM_READ_SMS_PDU                    = 302;
  CID_GSM_LIST_SMS_PDU                    = 303;

  CID_GSM_PHONEBOOK_DEL                   = 403;
  CID_GSM_PHONEBOOK_WRITE                 = 404;
  CID_GSM_PHONEBOOK_READ                  = 405;
  CID_GSM_SET_PHONEBOOK_MEM               = 406;
  CID_GSM_GET_PHONEBOOK_MEM               = 407;
  CID_GSM_GET_PHONEBOOK_MEM_RANGE         = 408;

  CID_GSM_OPT_SMS_MEM_WRITE               = 501;
  CID_GSM_OPT_SMS_MEM_READ                = 502;
  CID_GSM_OPT_SMS_MEM_SPEC                = 503;

  CID_GSM_OPT_PHONEBOOK_MEM               = 504;

  CID_GSM_CALL_VOICE                      = 601;
  CID_GSM_CALL_DATA                       = 602;
  CID_GSM_CALL_FAX                        = 603;
  CID_GSM_CALL_ANSWER                     = 604;
  CID_GSM_CALL_HUNGUP                     = 605;

Type
  TSmsThread = class(TThread)
  private
    fGsm:TnrGsm;
    fSms:string;
    flDirect:boolean;
    procedure DoSynchroEvent;
  public
    constructor Create(aGsm:TnrGsm; aSms:string; aDirect:boolean);
    procedure Execute; override;
  end;

{ TSmsThread }

constructor TSmsThread.Create(aGsm:TnrGsm; aSms:string; aDirect:boolean);
begin
  inherited Create(True);
  fGsm := aGsm;
  fSms := aSms;
  flDirect := aDirect;
  FreeOnTerminate := True;
  Resume;
end;

procedure TSmsThread.DoSynchroEvent;
begin
  if flDirect
    then fGsm.DoProcessNotifySmsDirect(fSms)
    else fGsm.DoProcessNotifySms(fSms);
end;

procedure TSmsThread.Execute;
begin
//  Synchronize(DoSynchroEvent);
  DoSynchroEvent;
end;

{ TnrPhoneRecord }

procedure TnrPhoneRecord.Assign(aSource: TnrPhoneRecord);
begin
  if aSource <> nil then begin
    fCaption := aSource.fCaption;
    fPhone := aSource.fPhone;
    fType := aSource.fType;
    fDateTime := aSource.fDateTime;
    fPhoneType := aSource.fPhoneType;
  end;
end;

procedure TnrPhoneRecord.SetCaption(const Value: string);
begin
  fCaption := Value;
end;

function TnrPhoneRecord.GetDateTime: TDateTime;
begin
  Result := fDateTime;
end;

function TnrPhoneRecord.GetIndex: integer;
begin
  Result := fIndex;
end;

function TnrPhoneRecord.GetPhone: string;
begin
  Result := fPhone;
end;

function TnrPhoneRecord.GetPhoneType: TnrPhoneRecordType;
begin
  Result := fPhoneType;
end;

function TnrPhoneRecord.GetType: TnrPhoneBookType;
begin
  Result := fType;
end;

function TnrPhoneRecord.IsMatchPattern(aPattern: string; flCaseSens, flPhone, flCaption:boolean): boolean;
var s:string;
begin
  Result := False;

  if aPattern='' then exit;

  s := Copy(fPhone,1,Length(fPhone));
  if (not flCaseSens) and (s <> '') then begin
    s := CharLower(PChar(s));
    aPattern := CharLower(PChar(aPattern));
  end;

  if flPhone and (s <>'')
    then Result := MatchPatternSimple(@s[1],@aPattern[1]);

  s := Copy(fCaption,1,Length(fCaption));
//  s := fCaption;
  if (not flCaseSens) and (s <> '') then s := CharLower(PChar(s));

  if (not Result) and (flCaption) and (s <>'')
    then Result := MatchPatternSimple(@s[1],@aPattern[1]);

end;

function TnrPhoneRecord.GetCaption: string;
begin
  Result := fCaption;
end;

procedure TnrPhoneRecord.SetPhone(const Value: string);
begin
  fPhone := Value;
end;

procedure TnrPhoneRecord.SetType(const Value: TnrPhoneBookType);
begin
  fType := Value;
end;

{ TnrPhoneList }

function TnrPhoneBook.AddRecord(aIndex: integer; aPhone,
  aCaption: string): TnrPhoneRecord;
var idPos,len:integer;
    s:string;
begin
  Result := TnrPhoneRecord.Create;
  Result.fPhone := aPhone;
  Result.fType := fType;
  Result.fDateTime := 0;
  len := Length(aCaption);
  if len > 2 then begin
    s := Copy(aCaption,len-1,2);
    if s[1] = '/' then begin
      case s[2] of
        'H': Result.fPhoneType := prtHome;
        'M': Result.fPhoneType := prtMobile;
        'W': Result.fPhoneType := prtWork;
        'F': Result.fPhoneType := prtFax;
        'O': Result.fPhoneType := prtOther;
      end;
      if Result.fPhoneType <> prtUnknown
        then aCaption := Copy(aCaption,1,len-2);
    end;
  end;

  Result.fCaption := aCaption;

  if aIndex = -1 then begin
    aIndex := GetFirstAvailableIndex(idPos);
    Result.fIndex := aIndex;
    Inc(fUsed);
    inherited Insert(idPos,Result);
  end else begin
    Result.fIndex := aIndex;
    inherited Add(Result);
  end;
end;

{$IFDEF TRIAL}
var  ecx:integer = $18735241;
{$ENDIF}

procedure TnrPhoneBook.Assign(aSource:TnrPhoneBook);
var i : integer;
begin
  if aSource <> nil then begin
    Clear;
    for i := 0 to aSource.Count-1 do begin
      AddRecord(aSource.Item[i].Index,
                aSource.Item[i].Phone,
                aSource.Item[i].Caption).Assign(aSource.Item[i]);
    end;
    fTotal := aSource.fTotal;
    fUsed := aSource.fUsed;
    fType := aSource.fType;
    fMaxNumLen := aSource.fMaxNumLen;
    fMaxTxtLen := aSource.fMaxTxtLen;
  end;
end;

procedure TnrPhoneBook.Clear;
var i:integer;
begin
  for i := 0 to Count-1 do
    if Item[i] <> nil then begin
      Item[i].Free;
      Items[i] := nil;
    end;

  inherited Clear;
end;

procedure TnrPhoneBook.Delete(Index: integer);
begin
  if Items[Index] <> nil then begin
    Item[Index].Free;
    Items[Index] := nil;
  end;
  inherited Delete(Index);
end;

procedure TnrPhoneBook.DeleteRecord(aRec: TnrPhoneRecord);
var i:integer;
begin
  i := IndexOf(aRec);
  if i >= 0 then Delete(i);
end;

function TnrPhoneBook.GetFirstAvailableIndex(var aListIndex:integer): integer;
var i,idPrev,id:integer;
begin
  Result := -1;
  aListIndex := -1;
  idPrev := 0;
  id := 0;
  for i := 0 to Count-1 do begin
    id := Item[i].fIndex;
    if (id > idPrev) and ((id - idPrev)>1) then begin
      Result := id - 1;
      aListIndex := i;
      break;
    end;
    idPrev := id;
  end;

  if Result = -1 then begin
//    if id < fTotal then begin
      Result := id + 1;
      aListIndex := Count;
//    end;
  end;

end;

function TnrPhoneBook.GetMaxNumLen: integer;
begin
  Result := fMaxNumLen;
end;

function TnrPhoneBook.GetMaxTxtLen: integer;
begin
  Result := fMaxTxtLen;
end;

function TnrPhoneBook.GetPhoneItem(Index: integer): TnrPhoneRecord;
begin
  Result :=  TnrPhoneRecord(inherited Items[Index]);
end;

function TnrPhoneBook.GetTotal: integer;
begin
  Result := fTotal;
end;

function TnrPhoneBook.GetType: TnrPhoneBookType;
begin
  Result := fType;
end;

function TnrPhoneBook.GetUsed: integer;
begin
  Result := fUsed;
end;

function TnrPhoneBook.GetValid: boolean;
begin
  Result := fValid;
end;

{$IFDEF TRIAL}
{$I nr_trial.inc}
{$ENDIF}


procedure TnrPhoneBook.LoadFromFile(aFileName: string);
var i:integer;
    slFile:TStringList;
    s:string;
begin
  slFile := TStringList.Create;
  try
    slFile.LoadFromFile(aFileName);
  except
    slFile.Free;
    exit;
  end;
  Clear;
  for i := 0 to slFile.Count-1 do begin
    s := slFile[i];
    AddRecord(StrToIntDef(atGetParam(s,1),1),
              atGetParam(s,3),
              atGetParam(s,4)).fType :=
              TnrPhoneBookType(StrToIntDef(atGetParam(s,2),Integer(pltUnknown)));
  end;
  slFile.Free;
end;

procedure TnrPhoneBook.SaveToFile(aFileName: string);
var fs:TFileStream;
    i:integer;
    s:string;
begin
  try
    fs := TFileStream.Create(aFileName,fmCreate);
  except
    exit;
  end;

  try
    for i := 0 to Count-1 do begin
      s := Format('%u,%u,"%s","%s"'#13#10,[Item[i].Index, Integer(Item[i].fType),
                Item[i].Phone,Item[i].Caption]);
      fs.Write(s[1],Length(s));
    end;
  except
  end;

  fs.Free;
end;

procedure TnrPhoneBook.SetMaxNumLen(const Value: integer);
begin
  fMaxNumLen := Value;
end;

procedure TnrPhoneBook.SetMaxTxtLen(const Value: integer);
begin
  fMaxTxtLen := Value;
end;

procedure TnrPhoneBook.SetType(const Value: TnrPhoneBookType);
begin
  fType := Value;
end;

procedure TnrPhoneBook.SetValid(const Value: boolean);
begin
  fValid := Value;
end;

{ TnrGsm }

constructor TnrGsm.Create(AOwner: TComponent);
begin
  inherited;
  sLogPrefix:='nrGsm: ';
  fIdMultiPartPDU := GetTickCount;
//  sqReplySms.len := 0;
  lStateStack := TList.Create;
  fMemTypesWrite := TStringList.Create;
  fMemTypesRead := TStringList.Create;
  fMemTypesSpecial := TStringList.Create;

  slSmsFifo := TStringList.Create;

  slGsmCommands := TStringList.Create;
  slGsmCommands.Sorted := True;
  {$IFDEF GEN6}
  slGsmCommands.CaseSensitive := False;
  {$ENDIF}
  slGsmCommands.AddObject('gsmInit',TObject(CID_GSM_INIT));
  slGsmCommands.AddObject('gsmGetIMEI',TObject(CID_GSM_GET_IMEI));
  slGsmCommands.AddObject('gsmGetIMSI',TObject(CID_GSM_GET_IMSI));
  slGsmCommands.AddObject('gsmGetVersion',TObject(CID_GSM_GET_VER));
  slGsmCommands.AddObject('gsmGetModel',TObject(CID_GSM_GET_MODEL));
  slGsmCommands.AddObject('gsmGetManufacturer',TObject(CID_GSM_GET_MANUF));
  slGsmCommands.AddObject('gsmSetTextMode',TObject(CID_GSM_SET_MODE_TEXT));
  slGsmCommands.AddObject('gsmSetPDUMode',TObject(CID_GSM_SET_MODE_PDU));
  slGsmCommands.AddObject('gsmGetBatteryStatus',TObject(CID_GSM_GET_STATUS_BATTERY));
  slGsmCommands.AddObject('gsmGetSignalStatus',TObject(CID_GSM_GET_STATUS_SIGNAL));
  slGsmCommands.AddObject('gsmSendSMSPdu',TObject(CID_GSM_SEND_SMS_PDU));
  slGsmCommands.AddObject('gsmSMSDelete',TObject(CID_GSM_DELETE_SMS));
  slGsmCommands.AddObject('gsmSMSList',TObject(CID_GSM_LIST_SMS_PDU));
  slGsmCommands.AddObject('gsmGetPINStatus',TObject(CID_GSM_GET_PIN_STATUS));
  slGsmCommands.AddObject('gsmSetPIN',TObject(CID_GSM_SET_PIN));

  slGsmCommands.AddObject('gsmSMSNotifyEnable',TObject(CID_GSM_SMS_NOTIFY_ENABLE));
  slGsmCommands.AddObject('gsmSMSNotifyDirect',TObject(CID_GSM_SMS_NOTIFY_DIRECT));
  slGsmCommands.AddObject('gsmRingNotifyEnable',TObject(CID_GSM_RING_NOTIFY_ENABLE));
  slGsmCommands.AddObject('gsmDiagnostics',TObject(CID_GSM_DIAGNOSTICS));
  slGsmCommands.AddObject('gsmDetect',TObject(CID_GSM_DETECT));
  slGsmCommands.AddObject('gsmSMSDirectConfirm',TObject(CID_GSM_SMS_DIRECT_CONFIRMATION));

  slGsmCommands.AddObject('gsmSetSMSMem',TObject(CID_GSM_SET_SMS_MEM));
  slGsmCommands.AddObject('gsmGetSMSMem',TObject(CID_GSM_GET_SMS_MEM));
  slGsmCommands.AddObject('gsmSMSReadPDU',TObject(CID_GSM_READ_SMS_PDU));

  slGsmCommands.AddObject('gsmPhoneBookDel',TObject(CID_GSM_PHONEBOOK_DEL));
  slGsmCommands.AddObject('gsmPhoneBookWrite',TObject(CID_GSM_PHONEBOOK_WRITE));
  slGsmCommands.AddObject('gsmPhoneBookRead',TObject(CID_GSM_PHONEBOOK_READ));
  slGsmCommands.AddObject('gsmSetPhoneBookMem',TObject(CID_GSM_SET_PHONEBOOK_MEM));
  slGsmCommands.AddObject('gsmGetPhoneBookMem',TObject(CID_GSM_GET_PHONEBOOK_MEM));
  slGsmCommands.AddObject('gsmGetPhoneBookMemRange',TObject(CID_GSM_GET_PHONEBOOK_MEM_RANGE));

  // settings ...
  slGsmCommands.AddObject('gsmSMSMemWrite',TObject(CID_GSM_OPT_SMS_MEM_WRITE));
  slGsmCommands.AddObject('gsmSMSMemRead',TObject(CID_GSM_OPT_SMS_MEM_READ));
  slGsmCommands.AddObject('gsmSMSMemSpec',TObject(CID_GSM_OPT_SMS_MEM_SPEC));

  slGsmCommands.AddObject('gsmPhoneBookMem',TObject(CID_GSM_OPT_PHONEBOOK_MEM));

  slGsmCommands.AddObject('gsmCallVoice',TObject(CID_GSM_CALL_VOICE));
  slGsmCommands.AddObject('gsmCallData',TObject(CID_GSM_CALL_DATA));
  slGsmCommands.AddObject('gsmCallFax',TObject(CID_GSM_CALL_FAX));

  slGsmCommands.AddObject('gsmCallAnswer',TObject(CID_GSM_CALL_ANSWER));
  slGsmCommands.AddObject('gsmCallHungup',TObject(CID_GSM_CALL_HUNGUP));

  fPduSms := TnrPduSms.Create(True);
  slReply := TStringList.Create;

  fMemoryWrite := 'SM';
  fMemoryRead := 'SM';

  slPhoneBookType := TStringList.Create;
  fPhoneList := TnrPhoneBook.Create;

  hSmsSentEvent := CreateEvent(nil, False, False, nil);
  fReplyLastSentSms := TATReply.Create(Self);

  {$IFDEF TRIAL}
  ng_alrt;
  {$ENDIF}

  AddDriver('Nokia 6230i',String(GetResAsStr('nrgsm_Nokia6230i')));
  AddDriver('Nokia 7110',String(GetResAsStr('nrgsm_Nokia7110')));
  AddDriver('Nokia 6120',String(GetResAsStr('nrgsm_Nokia6120')));
  AddDriver('Nokia 5310',String(GetResAsStr('nrgsm_Nokia5310')));
  AddDriver('Nokia CS-10',String(GetResAsStr('nrgsm_NokiaCS10')));
  AddDriver('Samsung SGH-C120',String(GetResAsStr('nrgsm_Samsung_SGH_C120')));
  AddDriver('Sony Ericsson T630',String(GetResAsStr('nrgsm_SET630')));
  AddDriver('Siemens A65',String(GetResAsStr('nrgsm_Siemens_A65')));
  AddDriver('Siemens M35i[t]',String(GetResAsStr('nrgsm_Siemens_MC35i')));  // no 6
  AddDriver('Wavecom Modem',String(GetResAsStr('nrgsm_Wavecom_MULTIBAND_900E')));
  AddDriver('Wavecom Wireless CPU',String(GetResAsStr('nrgsm_Wavecom_Wireless_CPU')));  
  AddDriver('BenQ M2x',String(GetResAsStr('nrgsm_BenQ_M22')));
  AddDriver('Huawei E220',String(GetResAsStr('nrgsm_Huawei_E220')));
  AddDriver('Simcom SIM340',String(GetResAsStr('nrgsm_Simcom_SIM340')));
  AddDriver('iTegno (iWOW)',String(GetResAsStr('nrgsm_iTegno_iWOW')));
  AddDriver('Novatel Ovation MC950D',String(GetResAsStr('nrgsm_Novatel_Ovation_MC950')));
  AddDriver('Sierra Wireless',String(GetResAsStr('nrgsm_Sierra_Wireless')));
  AddDriver('ZTE',String(GetResAsStr('nrgsm_ZTE')));

  DriverIndex := 0;
  Autodetect := True;

end;

destructor TnrGsm.Destroy;
begin
  fPhoneList.Free;
  slPhoneBookType.Free;
  fMemTypesWrite.Free;
  fMemTypesRead.Free;
  fMemTypesSpecial.Free;
  slReply.Free;
  fPduSms.Free;
  lStateStack.Free;
  slSmsFifo.Free;
  slGsmCommands.Free;
  fReplyLastSentSms.Free;
  inherited;
end;

function TnrGsm.GetDriverCmdId(aCmd: TATCommand): integer;
begin
  Result := slGsmCommands.IndexOf(aCmd.fName);
  if Result >=0
    then Result := Integer(slGsmCommands.Objects[Result])
    else Result := inherited GetDriverCmdId(aCmd);
end;

function TnrGsm.GetGsmData(aReply: string): string;
var i,len:integer;
begin
  // skip "+XXXX:" information in GSM reply ...
  Result := Trim(aReply);
  len := Length(Result);
  if len = 0 then exit;
  if Result[1] <> '+' then exit;
  i := Pos(':',Result);
  if i > 0 then Result := Copy(Result, i+1, len);
end;

procedure TnrGsm.Abort;
begin

end;

procedure TnrGsm.DoUpdateMemoryTypes;
var i,j:integer;
    k: TnrPhoneBookType;
begin

  fMemTypesWrite.Clear;
  fMemTypesRead.Clear;
  fMemTypesSpecial.Clear;

  i := 1;
  repeat
    j := Driver.IndexOfSetting(CID_GSM_OPT_SMS_MEM_WRITE,i);
    if j >= 0 then begin
      fMemTypesWrite.Add(Driver.Setting[j].fCommand);
      Inc(i);
//      OutputDebugString(PChar(Driver.Setting[j].fCommand));
    end;
  until j < 0;

  i := 1;
  repeat
    j := Driver.IndexOfSetting(CID_GSM_OPT_SMS_MEM_READ,i);
    if j >= 0 then begin
      fMemTypesRead.Add(Driver.Setting[j].fCommand);
      Inc(i);
//      OutputDebugString(PChar(Driver.Setting[j].fCommand));
    end;
  until j < 0;

  i := 1;
  repeat
    j := Driver.IndexOfSetting(CID_GSM_OPT_SMS_MEM_SPEC,i);
    if j >= 0 then begin
      fMemTypesSpecial.Add(Driver.Setting[j].fCommand);
      Inc(i);
//      OutputDebugString(PChar(Driver.Setting[j].fCommand));
    end;
  until j < 0;


  for k := pltMem to High(TnrPhoneBookType) do begin
    j := Driver.IndexOfSetting(CID_GSM_OPT_PHONEBOOK_MEM,Integer(k));
    if j > 0
      then slPhoneBookType.AddObject(Driver.Setting[j].fCommand,TObject(k));
  end;

end;

procedure TnrGsm.DoUpdateMemoryState(sData: string; flShort:boolean);
var s:string;
    i:integer;
begin
//  if Pos('+CPMS:',sData) = 1
//    then sData := Copy(sData,7,Length(sData));

  sData := GetGsmData(sData);
  i:=1;
  if not flShort then begin
    s := atGetParam(sData,1);
    if s <> '' then fMemoryRead := s;
    inc(i);
  end;

  fMemReadUsed  := StrToIntDef( atGetParam(sData,i),-1);
  inc(i);
  fMemReadTotal  := StrToIntDef( atGetParam(sData,i),-1);
  inc(i);

  if not flShort then begin
    s := atGetParam(sData,i);
    if s <> '' then fMemoryWrite := s;
    inc(i);
  end;

  fMemWriteUsed  := StrToIntDef( atGetParam(sData,i),-1);
  inc(i);
  fMemWriteTotal  := StrToIntDef( atGetParam(sData,i),-1);
  inc(i);

  if not flShort then begin
    s := atGetParam(sData,i);
    if s <> '' then fMemorySpecial := s;
    inc(i);
  end;

  fMemSpecUsed  := StrToIntDef( atGetParam(sData,i),-1);
  inc(i);
  fMemSpecTotal  := StrToIntDef( atGetParam(sData,i),-1);

end;

//(pinUnknown, pinError, pinReady, pinPIN, pinPUK,
//                   pinPIN2, pinPUK2, pinPhSimPIN, pinPhNetPIN);

Const
  markersPIN: array[0..7] of AnsiString =
  (
    ' ERROR:',
    ': READY'#13,
    ': SIM PIN'#13,
    ': SIM PUK'#13,
    ': SIM PIN2'#13,
    ': SIM PUK2'#13,
    ': PH-SIM PIN'#13,
    ': PH-NET PIN'#13
  );

  titlesPINStatus : array[TGsmPinStatus] of string =
  ('Unknown', 'Error', 'Ready',
   'SIM PIN', 'SIM PUK',
   'SIM PIN2','SIM PUK2',
   'PH-SIM PIN', 'PH-NET PIN');

function TnrGsm.GetDecodedPinStatus(aReply:TATReply):TGsmPinStatus;
var i:integer;
begin
  Result := pinUnknown;
  for  i := 0 to 7 do
    if Pos(markersPIN[i], aReply.sData) > 0 then begin
      Result := TGsmPinStatus(i + 1);
      break;
    end;
end;

function TnrGsm.DoCommandFinished:boolean;
var sData, sDataAll:string;
    idCmd:integer;
    aCmd:TATCommand;
    flSkipNextCmd : boolean;
begin
//  OutputDebugString('DoCommandFinished');
  Result := True;
  flSkipNextCmd := False;
  //sData := GetData(fReply.sData);
  sData := String(fReply.sDataUseful);
  sDataAll := String(fReply.sData);
  idCmd := fReply.idDriverCmd;

  if idCmd >= 0
    then aCmd := Driver.Command[idCmd]
    else aCmd := nil;

  case StateGsmGet of

    gssDiagnostic: begin
      if (idCmd >= 0) and (aCmd.idCmd = CID_GSM_DIAGNOSTICS) then begin
        Result := True;
        if Assigned(OnDiagnostic)
          then OnDiagnostic(Self, Trim(aCmd.fCommand),Trim(String(fReply.sData)), Result);
        Logging(dlDetail,'Diagnostic:' + aCmd.fCommand + ' Reply:' + String(fReply.sData));
      end;
    end;

    gssInit: begin
      if idCmd >=0 then
        case aCmd.idCmd of
          CID_GSM_GET_MODEL: fDeviceName := sData;
          CID_GSM_GET_IMEI:  begin
            fDeviceIMEI := sData;
            DoUpdateMemoryTypes;
          end;
          CID_GSM_GET_IMSI: fDeviceIMSI := sData;
          CID_GSM_GET_MANUF: fDeviceManufacturer := sData;
          CID_GSM_GET_VER:   fDeviceVersion := sData;
          CID_GSM_GET_SMS_MEM: DoUpdateMemoryState(sData,False);
          CID_GSM_GET_STATUS_BATTERY: DoUpdateBatteryInfo(sData,aCmd);
          CID_GSM_GET_STATUS_SIGNAL: DoUpdateSignalInfo(sData,aCmd);
          CID_GSM_GET_PIN_STATUS: begin
            fPINStatus := GetDecodedPinStatus(fReply);
            case fPINStatus of
              pinReady:
                begin
                  Logging(dlDetail,'PIN is READY');
                  flSkipNextCmd := True;  // skip next command that enters PIN ...
                end;
              pinPIN:
                begin
                  Logging(dlDetail,'PIN is NECESSARY');
                  if (Length(fPIN) = 0) then begin
                    Logging(dlDetail,'PIN is empty. Entering is skipped.');
                    inc(fCommandIndex);
                    flSkipNextCmd := True; // skip next command that enters PIN ...
                    DoProcessError(sData);
                  end;
                end;
              else begin
                Logging(dlDetail,'PIN has unacceptable status.');
                flSkipNextCmd := True;
                DoProcessError(sData);
              end;
            end;
            {
            fPINStatus := sData;
            if (AnsiCompareText(fPINStatus,'READY') = 0) then begin
              Logging(dlDetail,'PIN is READY');
              flSkipNextCmd := True;  // skip next command that enters PIN ...
            end else
              if (fPIN = '') then begin
                inc(fCommandIndex);
                flSkipNextCmd := True; // skip next command that enters PIN ...
                DoProcessError(sData);
              end;
              }
          end;
        end;

    end;

    gssReadPhoneBook: begin
      if idCmd >=0 then
        case aCmd.idCmd of
          CID_GSM_GET_PHONEBOOK_MEM:
            DoUpdatePhoneBookSize(sData,aCmd);
          CID_GSM_GET_PHONEBOOK_MEM_RANGE:
            DoUpdatePhoneBookRecSize(sData,aCmd);

          //                           DoUpdatePhoneBookRecSize(sData,Driver.Command[idCmd]);
          CID_GSM_PHONEBOOK_READ:
            if aCmd.Data[0] > 0 then begin // mark of the loop line ...
              Inc(fTmpRecNo);
              DoPhoneBookAddRecord(sData,aCmd);
              Result := (fTmpRecNo >= fPhoneList.Total) or
                        ((fPhoneList.Used <> -1) and (fPhoneList.Used <= fPhoneList.Count));
              if Assigned(fOnPhoneListProgress)
                then fOnPhoneListProgress(Self,Result);
              if not Result then begin
                slCommands[CommandIndex-1] := ReplaceMacros(aCmd.fCommand);
              end else begin
                if (fPhoneList.Used = -1) then fPhoneList.fUsed := fPhoneList.Count;
              end;
            end;
        end;
    end;
  end;

//  if CommandIndex > 0 then Logging(dlDebug,slCommands[CommandIndex-1]);

  if flSkipNextCmd then Inc(fCommandIndex);

  Result := Result and inherited DoCommandFinished;
end;

procedure TnrGsm.DoCommandsFinished;
var fl:boolean;
begin
//  OutputDebugString('DoCommandsFinished');
  fl := False;
  case StateGsmPop of
    gssInit:
      if Assigned(fOnAfterInit) then fOnAfterInit(Self);
    gssReadPhoneBook:
      if Assigned(fOnPhoneListFinish) then fOnPhoneListFinish(Self);
    gssDiagnostic:
      if Assigned(OnDiagnostic) then OnDiagnostic(Self,'','',fl);
    gssGetSmsList:
      if Assigned(OnSmsListItem) then OnSmsListItem(Self, '', 0, nil);
  end;
  inherited;
end;

function TnrGsm.DoProcessChar(Ch: byte): boolean;
begin
  Result := inherited DoProcessChar(Ch);
  if not Result then begin
    case StateGsmGet of
      //gssIdle,gssSend:
      gssNotifySms:
        begin
          if Ch <> 13
            then sSmsNotifyData := sSmsNotifyData + Char(Ch)
            else begin
              StateGsmReset(gssIdle);
              TSmsThread.Create(Self, sSmsNotifyData, False);
            end;
        end;

      gssNotifySmsDirect:
        begin
          sSmsNotifyData := sSmsNotifyData + Char(Ch);
          if Ch = 13 then begin
            Dec(fReplyLinesCnt);
            if fReplyLinesCnt = 0 then begin
              StateGsmReset(gssIdle);
              //DoProcessNotifySmsDirect(sSmsNotifyData);
              TSmsThread.Create(Self, sSmsNotifyData, True);
            end;
          end;
        end;


      gssNotifyRing:
        begin
          if Ch <> 13
            then sRingNotifyData := sRingNotifyData + Char(Ch)
            else begin
              StateGsmReset(gssIdle);
              DoProcessRing(sRingNotifyData);
            end;
        end;

      gssNotifyOther:
        begin
          if Ch <> 13
            then sOtherNotifyData := sOtherNotifyData + Char(Ch)
            else begin
              StateGsmReset(gssIdle);
              DoProcessNotifyOther(sOtherNotifyData);
            end;
        end;

      gssSent:
        begin
          if Ch <> 13
            then sSmsSentNotifyData := sSmsSentNotifyData + Char(Ch)
            else begin
              StateGsmReset(gssIdle);
              DoProcessSent(sSmsSentNotifyData);
            end;
        end;
        
      gssError:
        begin
          if Ch <> 13
            then sGsmErrorNotifyData := sGsmErrorNotifyData + Char(Ch)
            else begin
              StateGsmReset(gssIdle);
              DoProcessError(sGsmErrorNotifyData);
            end;
        end;

      gssGetSmsListItem:
        begin
          sSmsListItemData := sSmsListItemData + Char(Ch);
          if Ch = 13 then begin
            Dec(fReplyLinesCnt);
            if fReplyLinesCnt = 0 then begin
              StateGsmReset(gssGetSmsList);
              DoProcessSmsListItem(sSmsListItemData);
            end;
          end;
        end;

    end;
  end;
end;

procedure TnrGsm.DoProcessReply(aReply: TATReply);
var aState:TGsmInternalState;
begin

  aState := StateGsmGet;
  if aState = gssDiagnostic then begin
    inherited DoProcessReply(aReply);
    exit;
  end;

{  if (aState = gssDetect) and (ExpectedReplyID <> Driver.Response[aReply.idResponse].Tag) then begin
    DoError;
    exit;
  end;
 }
  case Driver.Response[aReply.idResponse].Tag of
    {CID_RESPONSE_GSM_SMS_PROMPT:begin
    end;}
    CID_RESPONSE_GSM_SMS_SENT:begin
      StateGsmReset(gssSent);
      sSmsSentNotifyData := '';
    end;

    CID_RESPONSE_GSM_SMS_SENT_ERR:begin
      StateGsmReset(gssError);
      sGsmErrorNotifyData := '';
    end;

    CID_RESPONSE_GSM_SMS_RECEIVED:begin
      flSmsNotifyNokiaMode := (Driver.Response[aReply.idResponse].Data = 1);
      StateGsmReset(gssNotifySms);
      sSmsNotifyData := '';
    end;

    CID_RESPONSE_GSM_SMS_RECEIVED_DIRECT:begin
      StateGsmReset(gssNotifySmsDirect);
      fReplyLinesCnt := 2;
      sSmsNotifyData := '';
    end;

    CID_RESPONSE_GSM_RING:begin
      StateGsmReset(gssNotifyRing);
      sRingNotifyData := '';
    end;

    CID_RESPONSE_GSM_SMS_LIST_ITEM:
      if aState = gssGetSmsList then begin
        StateGsmReset(gssGetSmsListItem);
        fReplyLinesCnt := 2;
        sSmsListItemData := '';
      end;

    CID_RESPONSE_UNSOLICITED:begin
      StateGsmReset(gssNotifyOther);
      sOtherNotifyData := '';
    end;

    else begin
      //if Driver.Response[aReply.idResponse].Tag = CID_RESPONSE_GSM_SMS_PROMPT
      //  then OutputDebugString('CID_RESPONSE_GSM_SMS_PROMPT');
      inherited DoProcessReply(aReply);
    end;
  end;
end;

procedure TnrGsm.DoProcessSmsListItem(sData: string);
var smsId:integer;
begin
  LoggingFmt(dlDetail,'Sms List Item: %s',[sData]);
  if not Assigned(fOnSmsListItem) then exit;
  smsId := StrToIntDef(atGetParam(sData,1),1);
  fPduSms.AsPDU := Trim(Copy(sData, Pos(#13, sData)+2, Length(sData)));
  fOnSmsListItem(Self, fMemoryRead, smsId, fPduSms);
end;

procedure TnrGsm.DoProcessNotifySms(aSmsNotify: string);
var smsId, offId:integer;
    sMem,sId:string;
    Pdu:TnrPduSms;
begin

  if not Assigned(fOnSmsReceived) then exit;

  sMem := atGetParam(aSmsNotify,1);
  sId := atGetParam(aSmsNotify,2);
  smsId := StrToIntDef(sId,1);

//  OutputDebugString(PChar('NOtify: '+sId+' '+sMem));

  offId := 0;

  if (flSmsNotifyNokiaMode) then begin
    if (sMem = 'ME') or (sMem = 'MT') then begin
      MemoryRead :='SM';
      UpdateMemoryState;
      offId := MemReadTotal;
      sMem := 'ME';
    end;
  end;

  if offId < smsId then smsId := smsId - offId;

  LoggingFmt(dlDetail,'Sms Notify: %s mem: %s, id: %u',[aSmsNotify, sMem, smsId]);

  if fNewSmsMode = nsmRead then begin
    MemoryRead := sMem;
    Pdu := SmsGet(smsId);
  end else Pdu := nil;

  fOnSmsReceived(Self, sMem, smsId, Pdu);

  {$IFDEF TRIAL}
  Logging(dlSimple, eax);
  {$ENDIF}
end;

procedure TnrGsm.DoProcessNotifySmsDirect(aSmsNotify: string);
begin
  LoggingFmt(dlDetail,'Sms Direct Notify: %s',[aSmsNotify]);
  CmdSendByDriverId(CID_GSM_SMS_DIRECT_CONFIRMATION);
  ResetTimeout(0);
  if not Assigned(fOnSmsReceived) then exit;
  fPduSms.AsPDU := Trim(Copy(aSmsNotify, Pos(#13, aSmsNotify)+2, Length(aSmsNotify)));
  fOnSmsReceived(Self, '', 0, fPduSms);
end;

procedure TnrGsm.DoProcessRing(aRing: string);
var sCID : string;
begin
  sCID := atGetParam(aRing,1);
  LoggingFmt(dlDetail,'Ring Notify: %s',[aRing]);
  {$IFDEF TRIAL}
  Logging(dlSimple, eax);
  {$ENDIF}
  if Assigned(fOnRing) then fOnRing(Self,sCID);
end;

procedure TnrGsm.DoProcessNotifyOther(aData:string);
begin
  LoggingFmt(dlDetail,'Other Notify: %s',[aData]);
  {$IFDEF TRIAL}
  Logging(dlSimple, eax);
  {$ENDIF}
  if Assigned(fOnUnsolicited) then fOnUnsolicited(Self, aData);
end;

Const
  CID_RESPONSE_SENT_OK       = 0;
  CID_RESPONSE_SENT_ERROR    = 3;

procedure TnrGsm.DoProcessSent(aSent: string);
var pdu:TnrPduSms;
begin
  if slSmsFifo.Count < 2 then begin
    fReplyLastSentSms.sDataUseful := AnsiString(aSent);
    fReplyLastSentSms.idResponse := CID_RESPONSE_SENT_OK;
    SetEvent(hSmsSentEvent);
  end;
  LoggingFmt(dlDetail,'Sms Sent Notify: %s',[aSent]);
  if Assigned(fOnSmsSent) then begin
    if slSmsFifo.Count > 0 then begin
      pdu := TnrPduSms.Create(False);
      pdu.AsPDU := slSmsFifo[0];
      slSmsFifo.Delete(0);
    end else pdu := nil;
    fOnSmsSent(Self, aSent, StrToIntDef(aSent, 0), pdu);
    if pdu <> nil then pdu.Free;
  end;
  {$IFDEF TRIAL}
  if ecx and 3 = 0 then Logging(dlSimple, eax);
  {$ENDIF}

  //SetEvent(hSmsSentEvent);

end;

procedure TnrGsm.DoProcessError(aErr: string);
begin
  fReplyLastSentSms.sDataUseful := '';
  fReplyLastSentSms.idResponse := CID_RESPONSE_SENT_ERROR;
  SetEvent(hSmsSentEvent);
  LoggingFmt(dlDetail,'GSM Error: %s',[aErr]);
  if slSmsFifo.Count > 0 then slSmsFifo.Delete(0);
  if Assigned(fOnGsmError) then fOnGsmError(Self,aErr);
  //SetEvent(hSmsSentEvent);
end;

procedure TnrGsm.RunDiagnostic;
begin
  StateGsmPush(gssDiagnostic);
  CmdClear;
  CmdAddByDriverId(CID_GSM_DIAGNOSTICS);
  CmdRun;
end;

procedure TnrGsm.AddATInit;
var slCmd:TStringList;
    i:integer;
begin
  CmdAddByDriverId(CID_GSM_INIT);
  CmdAddByDriverId(CID_GSM_GET_MANUF);
  CmdAddByDriverId(CID_GSM_GET_MODEL);
  CmdAddByDriverId(CID_GSM_GET_VER);

  CmdAddByDriverId(CID_GSM_GET_PIN_STATUS);
  CmdAddByDriverId(CID_GSM_SET_PIN);  // < --  maybe will be skipped!

  CmdAddByDriverId(CID_GSM_GET_IMEI);
  CmdAddByDriverId(CID_GSM_GET_IMSI);

  CmdAddByDriverId(CID_GSM_GET_SMS_MEM);

  CmdAddByDriverId(CID_GSM_GET_STATUS_BATTERY);
  CmdAddByDriverId(CID_GSM_GET_STATUS_SIGNAL);

  CmdAddByDriverId(CID_GSM_SET_MODE_PDU);

  if fNewSmsMode = nsmSkipSave
    then CmdAddByDriverId(CID_GSM_SMS_NOTIFY_DIRECT)
    else CmdAddByDriverId(CID_GSM_SMS_NOTIFY_ENABLE);

  CmdAddByDriverId(CID_GSM_RING_NOTIFY_ENABLE);

  // add additional (user) init commands into command queue ...
  if Length(fInitCommands) > 0 then begin
    slCmd := TStringList.Create;
    slCmd.Text := ReplaceMacros(fInitCommands);
    for i := 0 to slCmd.Count - 1 do CmdAdd(slCmd[i] + #13);
    slCmd.Free;
  end;

end;

procedure TnrGsm.InitDevice;
var i:integer;
    flOk:boolean;
begin

//  CmdClear;
//  if (not CmdSendAndWait('ATZ'#13)) or ReplyLast.IsError then begin
//    DoFatalErrors(ECC_GSM, 1, 'Not reply from device.');
//    exit;
//  end;

  fPINStatus := pinUnknown;

  if Autodetect then begin
    CashReply := True;

    StateGsmPush(gssDetect);
//    DriverIndex := 0;
    flOk := False;
    CmdClear;
    for i := 0 to DriverCount - 1 do begin
      DriverIndex := i;
      Logging(dlDetail,'Check driver: ' + Driver.Caption );
      //OutputDebugString('Try ...');
      if CmdSendAndWaitByDriver(CID_GSM_DETECT) and (not IsErrorReply(ReplyLast))
        then begin
          //OutputDebugString('Try ... done OK');
          Logging(dlDetail,'=== Autodetect driver: ' + Driver.Caption);

          flOk := True;
          break;
        end;
      //OutputDebugString('Try ... done ERROR');
    end;

    if not flOk then begin
      DriverIndex := 0;
      Logging(dlDetail,'=== Autodetect failed. Use default driver: '+Driver.Caption);
    end;
    StateGsmPop;
    CashReply := False;
  end
    else
      Logging(dlDetail,'=== Use selected driver: ' + Driver.Caption);

  {$IFDEF TRIAL}
  Inc(ecx);
  {$ENDIF}

  fGsmAT := gtNokia;
  StateGsmPush(gssInit);
  CmdClear;
  AddATInit;
  CmdRun;
end;

procedure TnrGsm.DoMediumOpened;
begin
  inherited;
  slSmsFifo.Clear;
  if Active then begin
    InitDevice;
  end;
end;

procedure TnrGsm.DoMediumClosed;
begin
//  OutputDebugString('Medium Is Closed');
  fDeviceManufacturer := '';
  fDeviceIMEI := '';
  fDeviceVersion := '';
  fDeviceName := '';
  slSmsFifo.Clear;
  inherited DoMediumClosed;
  {$IFDEF TRIAL}
  if ecx and 7 = 0 then ShowMessage(eax);
  {$ENDIF}

end;

function TnrGsm.SmsGet(Index: integer): TnrPduSms;
begin
  if fMemoryRead = '' then fMemoryRead := 'SM';
  fTmpSMSId := Index;
  StateGsmPush(gssRead);
  if CmdSendAndWaitByDriver(CID_GSM_READ_SMS_PDU) and (not ReplyLast.IsError)
    then ApplySmsAnswer(ReplyLast.sData);
  StateGsmPop;
  Result := fPduSms;
end;

function TnrGsm.SmsGetText(Index: integer): WideString;
begin
  Result := SmsGet(Index).Text;
end;

function TnrGsm.ApplySmsAnswer(aReply: AnsiString): boolean;
begin
  Result := False;
  fPduSms.Clear;
  slReply.Text := Trim(String(aReply));
  if slReply.Count < 3 then exit;
  fPduSms.AsPDU := Trim(slReply[1]);
  Result := True;
end;

{$IFDEF NOOVERLOAD}
procedure TnrGsm.SmsSendPdu(aPDU: TnrPduSms);
{$ELSE}
procedure TnrGsm.SmsSend(aPDU: TnrPduSms);
{$ENDIF}

var i : integer;
begin
  StateGsmPush(gssSend);
  CmdClear;
  aPDU.ServiceCenter := '';
  aPDU.Incoming := False;
  aPDU.PartId := fIdMultiPartPDU;
  Inc(fIdMultiPartPDU);
  if aPDU.AutoCutText then begin
    for i := 1 to aPDU.PartCount do begin
      aPDU.PartNumber := i;
      fTmpSMSPdu := aPDU.AsPDU;
      fTmpSMSLength := (Length(fTmpSMSPdu) div 2) - 1;
      slSmsFifo.Add(fTmpSMSPdu);
      CmdAddByDriverId(CID_GSM_SEND_SMS_PDU);
    end;
  end else begin
    fTmpSMSPdu := aPDU.AsPDU;
    fTmpSMSLength := (Length(fTmpSMSPdu) div 2) - 1;
    slSmsFifo.Add(fTmpSMSPdu);
    CmdAddByDriverId(CID_GSM_SEND_SMS_PDU);
  end;
  ResetEvent(hSmsSentEvent);
  CmdRun;
end;

procedure TnrGsm.SmsSend(aPhone, aText: WideString; aConfirm:boolean);
var aPDU: TnrPduSms;
begin
  aPDU := TnrPduSms.Create(False);
  aPDU.Phone := aPhone;
  aPDU.AutoCutText := True;
  aPDU.Text := aText;
  aPDU.DateTime := Now;
  aPDU.Confirm := aConfirm;
  {$IFDEF NOOVERLOAD}
  SmsSendPdu(aPDU);
  {$ELSE}
  SmsSend(aPDU);
  {$ENDIF}
  aPDU.Free;
end;

procedure TnrGsm.Send;
begin
  SmsSend(String(PhoneNumber), String(Text), False);
end;

function TnrGsm.SmsSendWait:boolean;
begin
  Result := WaitReply(hSmsSentEvent, 10000) and (fReplyLastSentSms.idResponse = CID_RESPONSE_SENT_OK);
end;

function TnrGsm.SmsSendWaitRefID(aPhone, aText:WideString; aConfirm :boolean):string;
begin
  LoggingFmt(dlDebug, 'SmsSendWaitRefID ("%s" , "%s", %u) ... ', [aPhone, aText, Integer(aConfirm)]);
  SmsSend(aPhone, aText, aConfirm);
  Logging(dlDebug, '... wait reply SmsSendWaitRefID ... ');
  SmsSendWait;
  Result := String(fReplyLastSentSms.sDataUseful);
  LoggingFmt(dlDebug, '... SmsSendWaitRefID done: %s', [Result]);
end;

{$IFDEF NOOVERLOAD}
function TnrGsm.SmsSendPduWaitRefID(aPDU:TnrPduSms):string;
{$ELSE}
function TnrGsm.SmsSendWaitRefID(aPDU:TnrPduSms) : string;
{$ENDIF}
begin
  Logging(dlDebug, 'SmsSendWaitRefID(PDU) ... ');
{$IFDEF NOOVERLOAD}
  SmsSendPdu(aPDU);
{$ELSE}
  SmsSend(aPDU);
{$ENDIF}
  Logging(dlDebug, '... wait reply SmsSendWaitRefID(pdu) ... ');
  SmsSendWait;
  Result := String(fReplyLastSentSms.sDataUseful);
  LoggingFmt(dlDebug, '... SmsSendWaitRefID (pdu) done: %s ', [Result]);
end;

function TnrGsm.SmsDelete(aIndex: integer): boolean;
begin
  fTmpSMSId := aIndex;
  Result := CmdSendAndWaitByDriver(CID_GSM_DELETE_SMS) and (not ReplyLast.IsError);
end;

procedure TnrGsm.SmsList(const flWait:boolean);
begin
{$IFDEF NOOVERLOAD}
 SmsList2(flWait, ssAll);
{$ELSE}
 SmsList(flWait, ssAll);
{$ENDIF}
end;

{$IFDEF NOOVERLOAD}
procedure TnrGsm.SmsList2(const flWait:boolean; status: TSmsStatus);
{$ELSE}
procedure TnrGsm.SmsList(const flWait:boolean; status: TSmsStatus);
{$ENDIF}
begin
  fGetSmsListStatus := status;
  StateGsmPush(gssGetSmsList);
  if flWait
    then CmdSendAndWaitByDriver(CID_GSM_LIST_SMS_PDU)
    else begin
      CmdClear;
      CmdAddByDriverId(CID_GSM_LIST_SMS_PDU);
      CmdRun;
    end;
end;


procedure TnrGsm.UpdateMemoryState;
begin
  if CmdSendAndWaitByDriver(CID_GSM_SET_SMS_MEM) and (not ReplyLast.IsError)
    then DoUpdateMemoryState(String(ReplyLast.sDataUseful), True);
end;

function TnrGsm.GetPhoneListTypeAsString: string;
var i : integer;
begin
  i := slPhoneBookType.IndexOfObject(TObject(Integer(PhoneListType)));
  if i >= 0 then Result := slPhoneBookType[i]
            else Result := '??';
end;

procedure TnrGsm.DoUpdatePhoneBookRecSize(sData: string; aCmd: TATCommand);
var i1,i2:integer;
begin
  fPhoneList.fMaxNumLen := -1;
  fPhoneList.fMaxTxtLen := -1;
  i1  := aCmd.Data[0];
  i2 := aCmd.Data[1];
  if i1 > 0
    then fPhoneList.fMaxNumLen := StrToIntDef(atGetParam(sData,i1),-1);
  if i2 > 0
    then fPhoneList.fMaxTxtLen := StrToIntDef(atGetParam(sData,i2),-1);
end;

procedure TnrGsm.DoUpdatePhoneBookSize(sData: string; aCmd:TATCommand);
var s:string;
    i,iUsed,iTotal:integer;
begin
  fPhoneList.fUsed := -1;
  fPhoneList.fTotal := -1;

  iUsed  := aCmd.Data[0];
  iTotal := aCmd.Data[1];
  if iUsed > 0
    then fPhoneList.fUsed := StrToIntDef(atGetParam(sData,iUsed),-1);

  if iTotal > 0 then begin
    s := atGetParam(sData,iTotal);
    i :=  Pos('-',s);  // if as range - take last value ...
    if i > 0 then s := Copy(s,i+1,Length(s) - i);
    fPhoneList.fTotal := StrToIntDef(s,-1);
  end;
end;

procedure TnrGsm.UpdatePhoneListState;
begin
  if CmdSendAndWaitByDriver(CID_GSM_SET_PHONEBOOK_MEM) then begin
    if CmdSendAndWaitByDriver(CID_GSM_GET_PHONEBOOK_MEM)
      then DoUpdatePhoneBookSize(String(ReplyLast.sDataUseful),Driver.Command[ReplyLast.idDriverCmd]);
    if CmdSendAndWaitByDriver(CID_GSM_GET_PHONEBOOK_MEM_RANGE)
      then DoUpdatePhoneBookRecSize(String(ReplyLast.sDataUseful),Driver.Command[ReplyLast.idDriverCmd]);
  end;

end;

procedure TnrGsm.PhoneBookRead;
begin
//  fCountIteration := 1;
  StateGsmPush(gssReadPhoneBook);
  fPhoneList.Clear;
  CmdClear;
  fTmpRecNo := 1;
  CmdAddByDriverId(CID_GSM_SET_PHONEBOOK_MEM);
  CmdAddByDriverId(CID_GSM_GET_PHONEBOOK_MEM);
  CmdAddByDriverId(CID_GSM_GET_PHONEBOOK_MEM_RANGE);
  CmdAddByDriverId(CID_GSM_PHONEBOOK_READ);        // read first record ...
  CmdRun;

end;

function TnrGsm.DoPhoneBookAddRecord(sData: string; aCmd:TATCommand):boolean;
var sPh,sN, sT:string;
    iNo,iPh,iTxt,iTm,i:integer;
    phRec:TnrPhoneRecord;
    aMem:TATCommand;
begin
  Result := False;
  sData := GetGsmData(sData);
  if sData = '' then exit;

  i := Driver.IndexOfSetting(CID_GSM_OPT_PHONEBOOK_MEM, Integer(PhoneListType));

  if i >= 0 then begin // take param numbers from Meme settings . ..
    aMem := Driver.Setting[i];
    iNo := aMem.Data[0];
    iPh := aMem.Data[1];
    iTxt := aMem.Data[2];
    iTm := aMem.Data[3];
  end else begin
    iNo := aCmd.Data[0];
    iPh := aCmd.Data[1];
    iTxt := aCmd.Data[2];
    iTm := aCmd.Data[3];
  end;

  if iTm > 0 then begin
    sT := atGetParam(sData,iTm mod 100);
    if iTm > 100 then sT := OctetsToUnicode(sT,1);
  end else sT := '';

  if iPh > 0 then begin
    sPh := atGetParam(sData,iPh mod 100);
    if iPh > 100 then sPh := OctetsToUnicode(sPh,1);
  end;

  if iTxt > 0 then begin
    sN := atGetParam(sData,iTxt mod 100);
    if iTxt > 100 then sN := OctetsToUnicode(sN,1);
  end;

  if (sPh = '') and (sN = '') then exit;

  phRec := fPhoneList.AddRecord(StrToIntDef(atGetParam(sData,iNo),1),sPh,sN);
  phRec.ItemType := PhoneListType;
  if sT <> '' then phRec.fDateTime := DecodeGsmDateTime(sT);
  Result := True;

end;

Const
  ca_PhoneTypes: array [TnrPhoneRecordType] of string = ('', '/H', '/W', '/M', '/F', '/O');

function TnrGsm.PhoneBookWrite(aPhone, aName: string; aIndex: integer; aType:TnrPhoneRecordType): boolean;
begin
  fTmpRecNo := aIndex;
  fTmpRecPhone := aPhone;
  fTmpRecName  := aName;
  Result := CmdSendAndWaitByDriver(CID_GSM_PHONEBOOK_WRITE) and (not ReplyLast.IsError);
end;

function TnrGsm.PhoneBookDel(aIndex: integer):boolean;
begin
  fTmpRecNo := aIndex;
  Result := CmdSendAndWaitByDriver(CID_GSM_PHONEBOOK_DEL) and (not ReplyLast.IsError);
end;

procedure TnrGsm.DoError;
var st:TGsmInternalState;
    flContinue : boolean;
begin
  st := StateGsmGet;
  flContinue := ((st = gssReadPhoneBook) and ((CommandIndex = 6) or (CommandIndex = 7)))
     or (st = gssDiagnostic) or ((st = gssInit) and fIgnoreInitErrors);

  if (st = gssInit) and (fReply.idDriverCmd >= 0) and
     (Driver.Command[fReply.idDriverCmd].idCmd = CID_GSM_SET_PIN)
    then Logging(dlSimple,'Enter PIN/PUK is failed!');


  if flContinue
    then DoNextCommand // continue execute commands from list ...
    else inherited DoError;
end;

procedure TnrGsm.StateGsmPush(aState: TGsmInternalState);
begin
  lStateStack.Add(Pointer(Integer(aState)));
  if Assigned(fOnGsmChangeState) then fOnGsmChangeState(self);
end;

function TnrGsm.StateGsmPop: TGsmInternalState;
begin
  Result := StateGsmGet;
  if lStateStack.Count > 0 then lStateStack.Delete(lStateStack.Count-1);
  if Assigned(fOnGsmChangeState) then fOnGsmChangeState(self);  
end;

procedure TnrGsm.StateGsmReset(aState: TGsmInternalState);
begin
  lStateStack.Clear;
  StateGsmPush(aState);
end;

function TnrGsm.StateGsmGet: TGsmInternalState;
begin
  if lStateStack.Count > 0
    then Result :=  TGsmInternalState(lStateStack[lStateStack.Count-1])
    else Result :=  gssIdle;
end;

function TnrGsm.IsBusy: boolean;
begin
  Result := StateGsmGet <> gssIdle;
end;

procedure TnrGsm.UpdateBatteryInfo;
begin
  if CmdSendAndWaitByDriver(CID_GSM_GET_STATUS_BATTERY) and (not ReplyLast.IsError)
    then DoUpdateBatteryInfo(String(ReplyLast.sDataUseful),Driver.Command[ReplyLast.idDriverCmd]);
end;

procedure TnrGsm.DoUpdateBatteryInfo(sData: string; aCmd: TATCommand);
var sV:string;
    i1,i2:integer;
begin
  if aCmd.idCmd <> CID_GSM_GET_STATUS_BATTERY then exit;
  sData := GetGsmData(sData);

  i1 := aCmd.Data[0]; // battery is being charged
  if i1 <> 0 then begin
    sV := atGetParam(sData,i1);
    fBatteryIsCharging := (sV = '1');
  end;

  i2 := aCmd.Data[1]; // battery charge level
  if i2 <> 0 then begin
    sV := atGetParam(sData,i2);
    fBatteryLevel := StrToIntDef(sV,0);
  end;

end;

procedure TnrGsm.UpdateSignalInfo;
begin
  if CmdSendAndWaitByDriver(CID_GSM_GET_STATUS_SIGNAL) and (not ReplyLast.IsError)
    then DoUpdateSignalInfo(String(ReplyLast.sDataUseful),Driver.Command[ReplyLast.idDriverCmd]);
end;

procedure TnrGsm.DoUpdateSignalInfo(sData: string; aCmd: TATCommand);
var sV:string;
    i1,i2:integer;
begin
  if aCmd.idCmd <> CID_GSM_GET_STATUS_SIGNAL then exit;
  sData := GetGsmData(sData);

  i1 := aCmd.Data[0];
  if i1 > 0 then begin
    sV := atGetParam(sData,i1);
    fSignalQuality := StrToIntDef(sV,0);
  end;

  i2 := aCmd.Data[1];
  if i2 > 0
    then fSignalQualityMax := i2
    else fSignalQualityMax := 31;

  if fSignalQuality > fSignalQualityMax
    then fSignalQuality := 0;

end;

function TnrGsm.MakeCall(aPhoneNumber: string): boolean;
begin
  fCallPhoneNumber := aPhoneNumber;
  Result := CmdSendAndWaitByDriver(CID_GSM_CALL_VOICE) and (not ReplyLast.IsError);
end;

procedure TnrGsm.AnswerCall;
begin
  CmdSendAndWaitByDriver(CID_GSM_CALL_ANSWER);
end;

procedure TnrGsm.Hungup;
begin
  CmdSendAndWaitByDriver(CID_GSM_CALL_HUNGUP);
end;

function TnrGsm.ReplaceMacros(aTemplate: string): string;
begin
  Result := inherited ReplaceMacros(aTemplate);

  Result := StringReplace(Result,'<SMSLen>',IntToStr(fTmpSMSLength),[rfReplaceAll,rfIgnoreCase]);
  Result := StringReplace(Result,'<SMSPdu>',fTmpSMSPdu,[rfReplaceAll,rfIgnoreCase]);
  Result := StringReplace(Result,'<SMSid>',IntToStr(fTmpSMSId),[rfReplaceAll,rfIgnoreCase]);

  Result := StringReplace(Result,'<SMSrmem>',fMemoryRead,[rfReplaceAll,rfIgnoreCase]);
  Result := StringReplace(Result,'<SMSwmem>',fMemoryWrite,[rfReplaceAll,rfIgnoreCase]);
  Result := StringReplace(Result,'<SMSsmem>',fMemorySpecial,[rfReplaceAll,rfIgnoreCase]);

  Result := StringReplace(Result,'<PhBkMem>',GetPhoneListTypeAsString,[rfReplaceAll,rfIgnoreCase]);
  Result := StringReplace(Result,'<PhBkRecNo>',IntToStr(fTmpRecNo),[rfReplaceAll,rfIgnoreCase]);

  Result := StringReplace(Result,'<PhBkPhone>',fTmpRecPhone,[rfReplaceAll,rfIgnoreCase]);
  Result := StringReplace(Result,'<PhBkName>',fTmpRecName,[rfReplaceAll,rfIgnoreCase]);

  Result := StringReplace(Result,'<PhBkPhoneU>',UnicodeToOctets(fTmpRecPhone),[rfReplaceAll,rfIgnoreCase]);
  Result := StringReplace(Result,'<PhBkNameU>',UnicodeToOctets(fTmpRecName),[rfReplaceAll,rfIgnoreCase]);

  Result := StringReplace(Result,'<CallPhoneNumber>',fCallPhoneNumber,[rfReplaceAll,rfIgnoreCase]);

  Result := StringReplace(Result,'<PIN>',fPIN,[rfReplaceAll,rfIgnoreCase]);

  Result := StringReplace(Result,'<SmsStatus>',IntToStr(Integer(fGetSmsListStatus)),[rfReplaceAll,rfIgnoreCase]);

end;

function TnrGsm.GetAutodetect: boolean;
begin
  Result := fAutodetect;
end;

function TnrGsm.GetBatteryIsCharging: boolean;
begin
  Result := fBatteryIsCharging;
end;

function TnrGsm.GetBatteryLevel: integer;
begin
  Result := fBatteryLevel;
end;

function TnrGsm.GetDeviceIMEI: string;
begin
  Result := fDeviceIMEI;
end;

function TnrGsm.GetDeviceIMSI: string;
begin
  Result := fDeviceIMSI;
end;

function TnrGsm.GetDeviceManufacturer: string;
begin
  Result := fDeviceManufacturer;
end;

function TnrGsm.GetDeviceName: string;
begin
  Result := fDeviceName;
end;

function TnrGsm.GetDeviceVersion: string;
begin
  Result := fDeviceVersion;
end;

function TnrGsm.GetGsmAT: TgsmATType;
begin
  Result := fGsmAT;
end;

function TnrGsm.GetMemoryRead: string;
begin
  Result := fMemoryRead;
end;

function TnrGsm.GetMemorySpecial: string;
begin
  Result := fMemorySpecial;
end;

function TnrGsm.GetMemoryWrite: string;
begin
  Result := fMemoryWrite;
end;

function TnrGsm.GetMemReadTotal: integer;
begin
  Result := fMemReadTotal;
end;

function TnrGsm.GetMemReadUsed: integer;
begin
  Result := fMemReadUsed;
end;

function TnrGsm.GetMemSpecTotal: integer;
begin
  Result :=fMemSpecTotal;
end;

function TnrGsm.GetMemSpecUsed: integer;
begin
  Result := fMemSpecUsed;
end;

function TnrGsm.GetMemTypesRead: TStrings;
begin
  Result := fMemTypesRead;
end;

function TnrGsm.GetMemTypesSpecial: TStrings;
begin
  Result := fMemTypesSpecial;
end;

function TnrGsm.GetMemTypesWrite: TStrings;
begin
  Result := fMemTypesWrite;
end;

function TnrGsm.GetMemWriteTotal: integer;
begin
  Result := fMemWriteTotal;
end;

function TnrGsm.GetMemWriteUsed: integer;
begin
  Result := fMemWriteUsed;
end;

function TnrGsm.GetPhoneList: TnrPhoneBook;
begin
  Result := fPhoneList;
end;

function TnrGsm.GetPhoneListType: TnrPhoneBookType;
begin
  Result := fPhoneListType;
end;

function TnrGsm.GetPhoneNumber: string;
begin
  Result := fPhoneNumber;
end;

function TnrGsm.GetSignalQuality: integer;
begin
  Result := fSignalQuality;
end;

function TnrGsm.GetSignalQualityMax: integer;
begin
  Result := fSignalQualityMax;
end;

function TnrGsm.GetText: string;
begin
  Result := fText;
end;

procedure TnrGsm.SetAutodetect(const Value: boolean);
begin
  fAutodetect := Value;
end;

procedure TnrGsm.SetGsmAT(const Value: TgsmATType);
begin
  fGsmAT := Value;
end;

procedure TnrGsm.SetMemoryRead(const Value: string);
begin
  fMemoryRead := Value;
end;

procedure TnrGsm.SetMemorySpecial(const Value: string);
begin
  fMemorySpecial := Value;
end;

procedure TnrGsm.SetMemoryWrite(const Value: string);
begin
  fMemoryWrite := Value;
end;

procedure TnrGsm.SetPhoneListType(const Value: TnrPhoneBookType);
begin
  fPhoneListType := Value;
end;

procedure TnrGsm.SetPhoneNumber(const Value: string);
begin
  fPhoneNumber := Value;
end;

procedure TnrGsm.SetText(const Value: string);
begin
  fText := Value;
end;

function TnrGsm.GetNewSmsMode: TgsmNewSmsMode;
begin
  Result := fNewSmsMode;
end;

procedure TnrGsm.SetNewSmsMode(const Value: TgsmNewSmsMode);
begin
  if fNewSmsMode <> Value then begin
    fNewSmsMode := Value;
    if Value = nsmSkipSave
      then CmdSendAndWaitByDriver(CID_GSM_SMS_NOTIFY_DIRECT)
      else CmdSendAndWaitByDriver(CID_GSM_SMS_NOTIFY_ENABLE);
  end;
end;

function TnrGsm.IsErrorReply(aReply: TATReply): boolean;
begin
  if StateGsmGet = gssDetect
    then Result := ( (ExpectedReplyID <> Driver.Response[aReply.idResponse].Tag) or
                   ((aReply.idDriverCmd>=0) and (Driver.Command[aReply.idDriverCmd].idOrder <> Driver.Response[aReply.idResponse].iOrder)))
    else Result := inherited IsErrorReply(aReply);
end;

function TnrGsm.GetPIN: string;
begin
  Result := fPIN;
end;

procedure TnrGsm.SetPIN(const Value: string);
begin
  fPIN := Value;
end;

function TnrGsm.PINGetStatus:string;
begin
  fPINStatus := pinUnknown;
  if CmdSendAndWaitByDriver(CID_GSM_GET_PIN_STATUS) and (not ReplyLast.IsError)
    then fPINStatus := GetDecodedPinStatus(ReplyLast);
  Result := titlesPINStatus[fPINStatus];
end;

function TnrGsm.PINApply: boolean;
begin
  Result := (fPIN <> '') and CmdSendAndWaitByDriver(CID_GSM_SET_PIN) and (not ReplyLast.IsError);
  if Result then sleep(500);
end;

function TnrGsm.GetIgnoreInitErrors: boolean;
begin
  Result := fIgnoreInitErrors;
end;

procedure TnrGsm.SetIgnoreInitErrors(const Value: boolean);
begin
  fIgnoreInitErrors := Value;
end;

procedure TnrGsm.Update;
begin
  if (Device <> nil) and Active
    then DoMediumOpened;
end;

function TnrGsm.AddDriverFromFile(aCaption, aFilePath:string):TnrCustomATDriver;
begin
  Result := inherited AddDriverFromFile(aCaption, aFilePath);
end;

procedure TnrGsm.DriversClearAll;
begin
  inherited;
end;

function TnrGsm.CmdSendAndWaitResult(sAT:AnsiString): string;
begin
  Result := inherited CmdSendAndWaitResult(sAT);
end;

function TnrGsm.GetInitCommands:string;
begin
  Result := fInitCommands;
end;

procedure TnrGsm.SetInitCommands(const Value: string);
begin
  fInitCommands := Value;
end;


end.


