{*******************************************************}
{                                                       }
{       nrCommLib Communication Library for Delphi      }
{       Base Abstract Classes (VCL)                     }
{       Copyright (c) 1997-2011 Roman Novgorodov        }
{       v9.27                                           }
{       last update: 08 Feb 2008                        }
{                                                       }
{       e-mail:noro@deepsoftware.com                    }
{       DeepSoftware                                    }
{       http://www.deepsoftware.com/                    }
{                                                       }
{*******************************************************}
{$I nrcommlib.inc}
unit nrclasses;
interface
uses Classes,
     windows,messages, nrwin32,
     {$IFDEF NR_WITH_CONTROLS}
       controls,stdctrls,
     {$ENDIF}
     sysutils;

Const

  // The codes of state ....
  csFatal               = -1;
  csNone                =  0;
  csClosed              =  1;
  csOpened              =  2;
  csDeviceIdChanged     =  3;
  nrCommLibVersion      =  'v9.27';

  {$IFDEF NRPRO}
  nrCommLibEdition      =  'Pro';
  {$ELSE}
  nrCommLibEdition      =  'Std';
  {$ENDIF}


  Eoln=#13;

  // ================================================================
  // Error Codes for DoFatalErrors method and OnFatalErrors event ...
  // ----------------------------------------------------------------
  ECC_UNKNOWN           = 0;
  ECC_NRCOMM            = 1;
  ECC_NRTAPI            = 2;
  ECC_NRBARCODE         = 3;
  ECC_NRWAVE            = 4;
  ECC_NRSPEECH          = 5;
  ECC_NRZMODEM          = 6;
  ECC_NRLOG             = 7;
  ECC_NRUSB             = 8;
  ECC_DDBASE            = 9;
  ECC_LPT               = 10;
  ECC_FAX               = 11;
  ECC_GSM               = 12;
  ECC_BT                = 13;
  ECC_NRUSBDRV          = 14;
  ECC_DATAPROC          = 15;
  ECC_HID               = 16;
  ECC_FTBASE            = 17;
  ECC_GPS               = 18;
  ECC_KERMIT            = 19;

  // ================================================================
  // Error Codes (Extended) for DoFatalErrors method and OnFatalErrors event ...
  // ----------------------------------------------------------------

  ECCE_NO_DEVICES               = 1;
  ECCE_NOT_SELECTED_DEVICE      = 2;
  ECCE_GENERAL_INIT             = 3;
  ECCE_INTERNAL                 = 4;
  ECCE_FILE_IO                  = 5;
  ECCE_NO_FILES                 = 6;
  ECCE_TO_MANY_ERRORS           = 7;

Type

  TCommEvent = packed record      // easy event record type
    Reserved:cardinal;
    len:cardinal;
    str:PAnsiChar;
    IdxLast:integer;
    Tag:integer;
    Data:cardinal;
  end;
  PCommEvent = ^TCommEvent;

  TCommEventNew = class
  private
    Reserved:integer;
    fLen:integer;
    fStr: AnsiString;
    procedure SetStr(const Value: AnsiString);
  public      // easy event record type
    Tag:integer;
    iOrder:integer;
    Data:cardinal;
    constructor Create(sEvent:AnsiString);
    procedure Reset;
    function IsEvent(const Ch:byte):boolean;
    function IsPossible:boolean;
    property Str:AnsiString read fStr write SetStr;
    property Len:integer read fLen;
  end;


  TOnFatalErrorEvent = procedure(Sender:TObject;ErrorCode,Detail:cardinal;ErrorMsg:string;var RaiseException:boolean)of object;
  TOnReceived        = procedure(Com:TObject;Buffer:Pointer;Received:Cardinal) of object;
  TOnSent            = procedure(Com:TObject) of object;

  TTerminalUsage        = (tuNone, tuSend, tuReceive, tuBoth);
  TDetailLevel          = (dlSimple, dlDetail, dlDebug);

  {$IFDEF NR_WITH_CONTROLS}
  TCustomTerminal       = TCustomEdit;
  {$ENDIF}

  TnrCustomLogFile=class(TComponent)
  private
    fOnFatalError: TOnFatalErrorEvent;
    function GetOnFatalError: TOnFatalErrorEvent;
    procedure SetOnFatalError(const Value: TOnFatalErrorEvent);
  protected
    procedure DoFatalErrors(Code,Detail:cardinal;Msg:string);virtual;
  public
    procedure Save; virtual;abstract;
    procedure Clear;virtual;abstract;
    procedure Add(s:string); virtual;abstract;
    procedure AddSysInfo; virtual; abstract;
    procedure AddLog(const l:TDetailLevel;const s:string);virtual;abstract;
    procedure AddFmt(const Format: string; const Args: array of const); virtual;abstract;
    procedure AddLogFmt(const l:TDetailLevel;const Format: string; const Args: array of const);virtual;abstract;
    property OnFatalError:TOnFatalErrorEvent read GetOnFatalError write SetOnFatalError;
  end;

  TnrNotifyEvent = procedure(Sender:TObject;Code:integer)of object;

  TnrNotifyList =class(TList)
  protected
    function Get(Index: Integer): TnrNotifyEvent;
    procedure Put(Index: Integer; Item: TnrNotifyEvent);
  public
    function Add(Item: TnrNotifyEvent): Integer;
    procedure Delete(index:integer);
    procedure Clear;{$IFNDEF VER100} override; {$ENDIF}
    function IndexOf(Item: TnrNotifyEvent): Integer;
    procedure Call(Sender:TObject;Code:integer);
    property Items[Index: Integer]:TnrNotifyEvent read Get write Put;
  end;

{$IFDEF GEN3}
  Int64  = array [0..1] of integer;
  UInt64 = array [0..1] of dword;
{$ENDIF}

  // forward declaration ...
  TnrBaseIO = class;
  TnrTimer  = class;

  TIgnoreType=(itTerminal,itEventList,itAfterReceive,itTraceState);
  TIgnoreTypes=set of TIgnoreType;

  TDataSendEvent=procedure (Sender:TObject;ptrBuffer:PAnsiChar;Len:cardinal; flSynchro:boolean; var flHandled:boolean) of object;

  TnrCustomDataProcessor = class(TComponent)
  private
    fDevice:TnrBaseIO;
    fDataProcessor: TnrCustomDataProcessor;
    fActive: boolean;
    fOnFatalError: TOnFatalErrorEvent;
    fLog: TnrCustomLogFile;
    fOnDataSend:TDataSendEvent;
    fTimerAsync:TnrTimer;
    procedure SetLog(const Value: TnrCustomLogFile);
    function GetDevice: TnrBaseIO;
    function GetDataProcessor: TnrCustomDataProcessor;
    function GetLog: TnrCustomLogFile;
    function GetActive: boolean;
    function GetOnFatalError: TOnFatalErrorEvent;
    procedure SetOnFatalError(const Value: TOnFatalErrorEvent);
  protected
    sLogPrefix:string;
    procedure DoHandleNotify(Sender:TObject;CodeState:integer);virtual;
    procedure DoMediumClosed;virtual;
    procedure DoMediumOpened;virtual;
    procedure SetDataProcessor(const Value: TnrCustomDataProcessor);virtual;
    procedure SetDevice(const Value: TnrBaseIO);virtual;
    procedure SetActive(const Value: boolean);virtual;
    procedure Logging(Det:TDetailLevel;Mess:string);
    procedure LoggingFmt(Det:TDetailLevel;Mess:string;const Arg: array of const);
    procedure DoDataSend(pBuff:PAnsiChar;Len:cardinal); virtual;
    procedure DoStringSend(sBuff:AnsiString); virtual;
    procedure DoAsyncTimeout(Sender:TObject); virtual;
    procedure ResetTimeout(aTimeout:cardinal); virtual;
    function IsDeviceEnable:boolean;
    property Device:TnrBaseIO read GetDevice write SetDevice;
    property DataProcessor:TnrCustomDataProcessor read GetDataProcessor write SetDataProcessor;
    property Log:TnrCustomLogFile read GetLog write SetLog;
    property OnDataSend:TDataSendEvent read fOnDataSend write fOnDataSend;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure DoFatalErrors(Code,Detail:cardinal;Msg:string); virtual;
    procedure DoProcessing(Data:PAnsiChar; Len:cardinal; var R:TIgnoreTypes); virtual;
    procedure DataReceive(ptrBuffer:PAnsiChar; Len:cardinal); virtual;
    procedure DoAfterSendComplete(var flIgnore:boolean); virtual;
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
    property Active:boolean read GetActive write SetActive;
    property OnFatalError:TOnFatalErrorEvent read GetOnFatalError write SetOnFatalError;
  end;

  TnrDevice = class(TPersistent)
  protected
    fIndex: integer;
    fParent: TnrBaseIO;
  public
    Handle: THandle;
    DevInst:THandle;
    Data: integer;
    DataEx: integer;
    NameFriendly: string;
    NamePDO: string;
    NameDeviceDos: string;
    NameSymbolic: string;
    RegKey: string;
    Description: string;
    Service: string;
    Driver: string;
    InstanceId:string;
    BusRelations:string;
    PortName:string;
    Manufacturer:string;
    SerialNumber:string;
    GuidClass:TGUID;
    property Index:integer read fIndex;
    property Parent:TnrBaseIO read fParent;
  end;

  TnrBaseIO = class(TComponent)
  private

    nrNotifyList:TnrNotifyList;
    fLog: TnrCustomLogFile;
    fDataProcessor: TnrCustomDataProcessor;
    fOnFatalError: TOnFatalErrorEvent;
    slDevices:TStringList;
    threadData:pointer;
    threadDataSize:dword;

    {$IFDEF NR_WITH_CONTROLS}
    hTerminal:THandle;
    fTerminalUsage:TTerminalUsage;
    fTerminal:TCustomTerminal;
    fTerminalEcho:boolean;
    fOldTerminalWndProc:TWndMethod;
    fTerminalLocked:boolean;
    fOnClose: TNotifyEvent;
    fOnOpen: TNotifyEvent;
    fThreadIO: TThread;
    fOnReceived: TOnReceived;
    fUseMainThread:boolean;
    fPriority : TThreadPriority;

    function GetTerminal: TCustomTerminal;
    function GetTerminalEcho: boolean;
    function GetTerminalUsage: TTerminalUsage;
    procedure SetTerminal(const Value: TCustomTerminal);
    procedure SetTerminalEcho(const Value: boolean);
    procedure SetTerminalUsage(const Value: TTerminalUsage);
    {$ENDIF}

    procedure SetLog(const Value: TnrCustomLogFile);
    procedure SetDataProcessor(const Value: TnrCustomDataProcessor);
    function GetDataProcessor: TnrCustomDataProcessor;
    function GetLog: TnrCustomLogFile;
    function GetOnFatalError: TOnFatalErrorEvent;
    procedure SetOnFatalError(const Value: TOnFatalErrorEvent);
    function GetDevice(Index: integer): TnrDevice;
    function GetDeviceCount: integer;
    procedure DoInputDataInMainThread;

  protected
    fActive:boolean;  //for design time
    hPort:THandle;
    sLogPrefix:string;
    flClearDeviceListWithFree:boolean;
    fOvlRead, fOvlWrite, fOvlAsync: TOverlapped;
    isThreaded : boolean;
    IsThreadIOTerminated: boolean;
    function GetActive: boolean;
    function IsHandleValid:boolean; virtual;
    function RequestDeviceHandle:THandle; virtual;
    procedure RequestDeviceHandleClose(aHandle:THandle); virtual;

    function IsReadNeeded:boolean; virtual;

    procedure SetActive(const Value: boolean);
    procedure Open;  virtual;
    procedure Close; virtual;

    procedure ThreadStart; virtual;
    procedure ThreadStop; virtual;
    procedure ThreadExecute(aThread:TThread); virtual;

    procedure DoFatalErrors(Code,Detail:cardinal;Msg:string; flByThread:boolean);virtual;
    procedure DoNotify(CodeState:integer);virtual;

    function DoInputData(Data : PAnsiChar; Len:cardinal):boolean; virtual;
    function DoOutputData:boolean; virtual;

    procedure SetDeviceIndex(const Value: integer); virtual;
    function GetDeviceIndex: integer; virtual; abstract;
    function AddDevice(aDevice:TnrDevice):integer; virtual;
    function AddDeviceSorted(aDevice:TnrDevice):integer; virtual;
    procedure DeviceListLoad; virtual; abstract;
    function IndexOfDeviceData(aData:integer):integer;
    procedure DeviceListClear; virtual;

    procedure Logging(Det:TDetailLevel;Mess:string);
    procedure LoggingFmt(Det:TDetailLevel;Mess:string;const Arg: array of const);
    procedure Loaded; override;

    {$IFDEF NR_WITH_CONTROLS}
    procedure NewTerminalWndProc(var Message: TMessage);
    procedure UpdateTerminalByChars(Buff:PAnsiChar; Len:integer); virtual;
    property Terminal:TCustomTerminal read GetTerminal write SetTerminal;
    property TerminalUsage:TTerminalUsage       read GetTerminalUsage
                                                write SetTerminalUsage;
    property TerminalEcho:boolean read GetTerminalEcho write SetTerminalEcho;
    {$ENDIF}

    property Handle:THandle read hPort;
    property Log:TnrCustomLogFile read GetLog write SetLog;
    property DataProcessor:TnrCustomDataProcessor read GetDataProcessor write SetDataProcessor;
    property OnFatalError:TOnFatalErrorEvent read GetOnFatalError write SetOnFatalError;
    property OnOpen:TNotifyEvent read fOnOpen write fOnOpen;
    property OnClose:TNotifyEvent read fOnClose write fOnClose;
    property Active:boolean read GetActive write SetActive;
    property UseMainThread:boolean read fUseMainThread write fUseMainThread;
    property OnAfterReceive:TOnReceived read fOnReceived write fOnReceived;
    property Priority : TThreadPriority read fPriority write fPriority;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Update; virtual;
    procedure NotifyAdd(NotifyEvent:TnrNotifyEvent);virtual;
    procedure NotifyDelete(NotifyEvent:TnrNotifyEvent);virtual;
    procedure SendChar(const Ch:Char);    virtual; abstract;
    procedure SendString(const S:AnsiString); virtual; abstract;
    procedure SendData(const Buff:PAnsiChar;const N:cardinal);virtual; abstract;
    procedure ClearDeviceInput; virtual;abstract;
    procedure ClearDeviceOutput;virtual;abstract;
    procedure ClearDeviceBuffers;virtual;abstract;
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
    procedure GetDeviceList(L:TStrings); virtual;
    function WMDeviceChange(var Msg:TMessage):boolean; virtual; //message WM_DEVICECHANGE;
    function IndexOfDevice(aDevice:TnrDevice):integer;
    property DeviceIndex:integer read GetDeviceIndex write SetDeviceIndex;
    property Device[Index:integer]:TnrDevice read GetDevice;
    property DeviceCount:integer read GetDeviceCount;
  end;

  TnrTimer = class//(TComponent)
  private
    FInterval: Cardinal;
    FWindowHandle: HWND;
    FOnTimer: TNotifyEvent;
    FEnabled: Boolean;
    procedure UpdateTimer;
    procedure SetEnabled(Value: Boolean);
    procedure SetInterval(Value: Cardinal);
    procedure SetOnTimer(Value: TNotifyEvent);
    procedure WndProc(var Msg: TMessage);
  protected
    procedure Timer; dynamic;
  public
    constructor Create;//(AOwner: TComponent); override;
    destructor Destroy; override;
//  published
    property Enabled: Boolean read FEnabled write SetEnabled default True;
    property Interval: Cardinal read FInterval write SetInterval default 1000;
    property OnTimer: TNotifyEvent read FOnTimer write SetOnTimer;
  end;

  TnrThreadIO = class(TThread)
  private
    fnrComm:TnrBaseIO;
  public
    constructor Create(anrComm:TnrBaseIO; pr:TThreadPriority);
    procedure Execute; override;
    procedure Synchronize(Method: TThreadMethod);
  end;

  function CheckSequence(ch:AnsiChar;var Sq:TCommEvent):boolean;

implementation
uses activex, nrsysutils;

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

function CheckSequence(ch:AnsiChar;var Sq:TCommEvent):boolean;
begin
  Result := False;
  if Sq.len = 0 then exit;
  if Sq.str[Sq.Reserved] <> ch
    then begin
      if Sq.str[0] = ch
        then Sq.Reserved := 1
        else Sq.Reserved := 0;
    end else begin
      Inc(Sq.Reserved);
      if Sq.Reserved >= Sq.len then begin
        Sq.Reserved := 0;
        Result := True;
      end;
    end;
end;

{ TnrThreadIO }

constructor TnrThreadIO.Create(anrComm: TnrBaseIO; pr:TThreadPriority);
begin
  inherited Create(True);
  fnrComm := anrComm;
  FreeOnTerminate := True;
  Priority := pr;
  Resume;
end;

procedure TnrThreadIO.Execute;
begin
  try
    try
      CoInitializeEx(nil, COINIT_MULTITHREADED);
      fnrComm.ThreadExecute(Self);
    except
    end;
  finally
    CoUninitialize;
  end;
end;

procedure TnrThreadIO.Synchronize(Method: TThreadMethod);
begin
  inherited Synchronize(Method);
end;

{ TnrCustomLogFile }

procedure TnrCustomLogFile.DoFatalErrors(Code, Detail: cardinal;
  Msg: string);
var fRaise:boolean;
begin
  fRaise:=True;
  if Assigned(fOnFatalError) then fOnFatalError(Self,Code,Detail,Msg,fRaise);
  if fRaise then raise Exception.Create(Msg);
end;


function TnrCustomLogFile.GetOnFatalError: TOnFatalErrorEvent;
begin
  Result := fOnFatalError;
end;

procedure TnrCustomLogFile.SetOnFatalError(
  const Value: TOnFatalErrorEvent);
begin
  fOnFatalError:=Value;
end;

{ TnrNotifyList }

function TnrNotifyList.Add(Item: TnrNotifyEvent): Integer;
var e:^TnrNotifyEvent;
begin
  New(e);
  e^ := item;
  Result := inherited Add(e);
end;

procedure TnrNotifyList.Delete(index: integer);
begin
  Dispose(inherited Items[Index]);
  inherited Delete(Index);
end;

procedure TnrNotifyList.Clear;
var i:integer;
begin
  for i := 0 to Count - 1 do Delete(0);
  inherited Clear;
end;

function TnrNotifyList.Get(Index: Integer): TnrNotifyEvent;
begin
  Result:=TnrNotifyEvent((inherited Get(Index))^);
end;

procedure TnrNotifyList.Put(Index: Integer; Item: TnrNotifyEvent);
var e:^TnrNotifyEvent;
begin
  e:=inherited Get(Index);
  e^:=Item;
end;

(*

{$IFDEF WIN64}
function MM(Item: TnrNotifyEvent):Int64;
asm
  lea      rax,[Item]
//  mov      edx,[eax]
//  mov      dword ptr [jj],rdx
//  mov      edx,[eax+4]
//  mov      dword ptr [jj+4],rdx
end;
{$ENDIF}

function TnrNotifyList.IndexOf(Item: TnrNotifyEvent): Integer;
var i:integer;
    j,jj:Int64;
begin
  Result := -1;
  // Assembler equal this jj:=Int64(Item);
  // and I don't know how to do this better
  {$IFNDEF WIN64}
  asm
    lea      eax,[Item]
    mov      edx,[eax]
    mov      dword ptr [jj],edx
    mov      edx,[eax+4]
    mov      dword ptr [jj+4],edx
  end;
  {$ELSE}
  jj := MM(Item);
  {$ENDIF}

  for i := 0 to Count - 1 do begin
    j := Int64((inherited Get(i))^);
    {$IFNDEF GEN3}
    if (jj = j) then begin
    {$ELSE}
    if(jj[0] = j[0])and(jj[1] = j[1])then begin
    {$ENDIF}
      Result:=i; //OK event handler was found!
      break;     //now can break loop
    end;
  end;
end;


{$IFNDEF WIN64}

function TnrNotifyList.IndexOf(Item: TnrNotifyEvent): Integer;
var i:integer;
    j,jj:Int64;
begin
  Result := -1;
  // Assembler equal this jj:=Int64(Item);
  // and I don't know how to do this better
  asm
    lea      eax,[Item]
    mov      edx,[eax]
    mov      dword ptr [jj],edx
    mov      edx,[eax+4]
    mov      dword ptr [jj+4],edx
  end;

  for i := 0 to Count - 1 do begin
    j := Int64((inherited Get(i))^);
    {$IFNDEF GEN3}
    if (jj = j) then begin
    {$ELSE}
    if(jj[0] = j[0])and(jj[1] = j[1])then begin
    {$ENDIF}
      Result:=i; //OK event handler was found!
      break;     //now can break loop
    end;
  end;
end;

{$ELSE}

{
function TnrNotifyList.IndexOf(Item: TnrNotifyEvent): Integer;
var i:integer;
    j, jj: array[0..1] of Int64;
begin
  Result := -1;
  CopyMemory(@jj[0], @@Item, SizeOf(TnrNotifyEvent));
  OutputDebugString(PChar(Format('Compare: %x %x', [jj[0], jj[1]])));
  for i := 0 to Count - 1 do begin
    CopyMemory(@j[0],inherited Get(i), SizeOf(TnrNotifyEvent));
    OutputDebugString(PChar(Format('... with : %x %x', [j[0], j[1]])));
    //j := Int64((inherited Get(i))^);
    if(jj[0] = j[0]) and (jj[1] = j[1]) then begin
      Result := i; //OK event handler was found!
      OutputDebugString('YES!');
      break;
    end;
  end;
end;                     }

{$ENDIF}

*)

function TnrNotifyList.IndexOf(Item: TnrNotifyEvent): Integer;
var i:integer;
    j : TnrNotifyEvent;
begin
  Result := -1;
  for i := 0 to Count - 1 do begin
    j := Get(i);
    if(@j = @Item) then begin
      Result := i; //OK event handler was found!
      break;
    end;
  end;
end;


procedure TnrNotifyList.Call(Sender:TObject;Code:integer);
var i:integer;
    {$IFDEF GEN3}
    e:^TnrNotifyEvent;
    {$ENDIF}
begin
  for i:=0 to Count-1 do begin
    {$IFDEF GEN3}//This code is compatible for Delphi3 and C++Builder3 ...
    e:=inherited Items[i];
    e^(Sender,Code);
    {$ELSE}  //This code is compatible for Delphi4 and above ...
    TnrNotifyEvent(Items[i])(Sender,Code);
    {$ENDIF}
  end;
end;

{ TnrCustomDataProcessor }

constructor TnrCustomDataProcessor.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  fTimerAsync := TnrTimer.Create;//(nil);
  fTimerAsync.Enabled := False;
  fTimerAsync.OnTimer := DoAsyncTimeout;
end;

destructor TnrCustomDataProcessor.Destroy;
begin
  Device := nil;
  fTimerAsync.Free;  
  inherited;
end;

procedure TnrCustomDataProcessor.DoAfterSendComplete(
  var flIgnore: boolean);
begin
  //...
end;

Const
  INVALID_HANDLE_VALUE  = $FFFFFFFF;

procedure TnrCustomDataProcessor.DoFatalErrors(Code, Detail: cardinal;
  Msg: string);
var fRaise:boolean;
begin
  fRaise := True;
  if Assigned(fOnFatalError) then fOnFatalError(Self, Code, Detail, Msg, fRaise);
  if Assigned(fLog) then fLog.AddLogFmt(dlSimple,'Fatal Error: %u, %u, %s',[Code, Detail, Msg]);
  if fRaise then raise Exception.Create(Msg);
end;

procedure TnrCustomDataProcessor.DoHandleNotify(Sender: TObject;CodeState:integer);
begin
  case CodeState of
    csClosed:DoMediumClosed;
    csOpened:DoMediumOpened;
  end;
end;

procedure TnrCustomDataProcessor.DataReceive(ptrBuffer:PAnsiChar; Len:cardinal);
var R:TIgnoreTypes;
begin
  DoProcessing(ptrBuffer,Len,R);
end;

procedure TnrCustomDataProcessor.DoMediumClosed;
begin
  Logging(dlDebug,'Medium Closed');
  {$IFDEF TRIAL}
  cnt_alrt;
  {$endif}
end;

procedure TnrCustomDataProcessor.DoMediumOpened;
begin
  Logging(dlDebug,'Medium Opened');
end;

procedure TnrCustomDataProcessor.DoProcessing(Data: PAnsiChar; Len: cardinal;
  var R: TIgnoreTypes);
begin
  // cascade call ...
  if Assigned(DataProcessor)
    then DataProcessor.DoProcessing(Data,Len,R);
end;

function TnrCustomDataProcessor.GetActive: boolean;
begin
  Result:=fActive;
end;

function TnrCustomDataProcessor.GetDataProcessor: TnrCustomDataProcessor;
begin
  Result:=fDataProcessor;
end;

function TnrCustomDataProcessor.GetDevice: TnrBaseIO;
begin
  Result:=fDevice;
end;

function TnrCustomDataProcessor.GetLog: TnrCustomLogFile;
begin
  Result:=fLog;
end;

function TnrCustomDataProcessor.GetOnFatalError: TOnFatalErrorEvent;
begin
  Result := fOnFatalError;
end;

procedure TnrCustomDataProcessor.Logging(Det: TDetailLevel; Mess: string);
begin
  if Assigned(Log) then Log.AddLog(Det,sLogPrefix+Mess);
end;

procedure TnrCustomDataProcessor.LoggingFmt(Det: TDetailLevel;
  Mess: string; const Arg: array of const);
begin
  if Assigned(Log) then Log.AddLogFmt(Det,sLogPrefix+Mess,Arg);
end;

procedure TnrCustomDataProcessor.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited Notification(AComponent, Operation);
  if Operation=opRemove then begin
    if AComponent=fDataProcessor then DataProcessor := nil;
    if AComponent=fDevice then Device := nil;
    if AComponent=fLog then Log := nil;
  end;
end;

procedure TnrCustomDataProcessor.SetActive(const Value: boolean);
begin
  fActive := Value;
end;

procedure TnrCustomDataProcessor.SetDataProcessor(
  const Value: TnrCustomDataProcessor);
begin
  if Value = Self then exit;
  fDataProcessor := Value;
  if Value <> nil then begin
    Value.FreeNotification(self);
    DataProcessor.Device := fDevice;
  end;
end;

procedure TnrCustomDataProcessor.SetDevice(const Value: TnrBaseIO);
begin
  if((Value=nil)or(fDevice<>Value)) and (fDevice<>nil)
    then fDevice.NotifyDelete(DoHandleNotify);
  fDevice := Value;
  if Value<>nil then begin
    Value.FreeNotification(self);
    Value.NotifyAdd(Self.DoHandleNotify);
  end;
end;

procedure TnrCustomDataProcessor.DoAsyncTimeout(Sender:TObject);
var fl:boolean;
begin
  fTimerAsync.Enabled := False;
  fl := False;
  DoAfterSendComplete(fl);
end;

procedure TnrCustomDataProcessor.DoDataSend(pBuff:PAnsiChar;Len:cardinal);
var flHandled:boolean;
begin

  if(len=0)or(pBuff=nil) then exit;

  try

    flHandled:=False;
    if Assigned(fOnDataSend)
      then fOnDataSend(Self,pBuff,Len,False,flHandled);

    if flHandled
      then begin
        fTimerAsync.Enabled:=False;
        fTimerAsync.Enabled:=True;
      end
      else begin
       Device.SendData(pBuff,Len);
       FlushFileBuffers(Device.Handle);
      end;
    LoggingFmt(dlDebug,'DoDataSend: %u bytes',[Len]);
  except

  end;

end;

procedure TnrCustomDataProcessor.DoStringSend(sBuff:AnsiString);
var flHandled:boolean;
    Len:cardinal;
begin
  Len:=Length(sBuff);
  if Len=0 then exit;

  try
    flHandled:=False;
    if Assigned(fOnDataSend)
      then fOnDataSend(Self,@sBuff[1],Len,True,flHandled);

    if flHandled
      then //DoAfterSendComplete(flHandled)
      else Device.SendString(sBuff);

  except

  end;


end;

procedure TnrCustomDataProcessor.SetLog(const Value: TnrCustomLogFile);
begin
  fLog := Value;
  if Value<>nil then Value.FreeNotification(self);
end;

procedure TnrCustomDataProcessor.SetOnFatalError(
  const Value: TOnFatalErrorEvent);
begin
  fOnFatalError:=Value;
end;

procedure TnrCustomDataProcessor.ResetTimeout(aTimeout: cardinal);
begin
  fTimerAsync.Enabled := False;
  if aTimeout <> 0 then begin
    fTimerAsync.FInterval := aTimeout;
    fTimerAsync.Enabled := True;
  end;
end;

function TnrCustomDataProcessor.IsDeviceEnable: boolean;
begin
  Result := Assigned(Device) and Device.IsHandleValid;
end;

{ TnrBaseIO }

procedure TnrBaseIO.SetDataProcessor(
  const Value: TnrCustomDataProcessor);
begin
  if(Value=nil)and(fDataProcessor<>nil) then fDataProcessor.Device:=nil;
  fDataProcessor := Value;
  if Value<>nil then begin
    Value.Device:=Self;
    Value.FreeNotification(self);
  end;
end;

procedure TnrBaseIO.DoFatalErrors(Code,Detail: cardinal;
  Msg: string; flByThread:boolean);
var fRaise:boolean;
begin
  fRaise := True;
  if Assigned(fOnFatalError) then fOnFatalError(Self,Code,Detail,Msg,fRaise);
  if Assigned(fLog) then fLog.AddLogFmt(dlSimple,'Fatal Error: %u, %u, %s',[Code,Detail,Msg]);
  if fRaise and (not (csLoading in ComponentState))
    then begin
      if flByThread
        then ShowException(Exception.Create(Msg), ErrorAddr)
        else raise Exception.Create(Msg);
    end;
end;

procedure TnrBaseIO.SetLog(const Value: TnrCustomLogFile);
begin
  fLog := Value;
  if Value <> nil then Value.FreeNotification(self);
end;

procedure TnrBaseIO.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited Notification(AComponent, Operation);
  if Operation=opRemove then begin
    if AComponent=DataProcessor then begin
      fDataProcessor.Device := nil;
      fDataProcessor := nil
    end else
    {$IFDEF NR_WITH_CONTROLS}
    if AComponent = fTerminal then fTerminal := nil else
    {$ENDIF}
    if AComponent=fLog then fLog := nil
  end;
end;

constructor TnrBaseIO.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  Priority := tpNormal;
  sLogPrefix := ClassName;
  flClearDeviceListWithFree := True;
  slDevices := TStringList.Create;
  nrNotifyList := TnrNotifyList.Create;
  hPort := INVALID_HANDLE_VALUE;
  fOvlRead.hEvent := CreateEvent(nil,False,False,nil);
  fOvlWrite.hEvent := CreateEvent(nil,False,False,nil);
end;

destructor TnrBaseIO.Destroy;
begin
  CloseHandle(fOvlWrite.hEvent);
  CloseHandle(fOvlRead.hEvent);
  DeviceListClear;
  nrNotifyList.Free;
  slDevices.Free;
  inherited Destroy;
end;

procedure TnrBaseIO.NotifyAdd(NotifyEvent: TnrNotifyEvent);
begin
  if nrNotifyList.IndexOf(NotifyEvent) < 0
    then nrNotifyList.Add(NotifyEvent)
end;

procedure TnrBaseIO.NotifyDelete(NotifyEvent: TnrNotifyEvent);
var i:integer;
begin
  i:=nrNotifyList.IndexOf(NotifyEvent);
  if i>=0 then nrNotifyList.Delete(i);
end;

procedure TnrBaseIO.DoNotify(CodeState:integer);
begin
  nrNotifyList.Call(Self,CodeState);
end;

function TnrBaseIO.GetActive: boolean;
begin
  //For design time ...
  if(csDesigning in ComponentState)or
    (csLoading in ComponentState)
    then Result:=fActive
    else Result:=IsHandleValid;//Boolean(hPort<>INVALID_HANDLE_VALUE);
end;

function TnrBaseIO.IsHandleValid: boolean;
begin
  Result := hPort <> INVALID_HANDLE_VALUE;
end;

procedure TnrBaseIO.SetActive(const Value: boolean);
begin
  if(csLoading in ComponentState)then begin //for Load Time
    fActive:=Value;
    exit;
  end;
  if (Value=Active)and not (csDesigning in ComponentState) then exit;
  if Value then Open
           else Close;
  //For design time ...
  if csDesigning in ComponentState then begin
    fActive:=IsHandleValid;//Boolean(hPort<>INVALID_HANDLE_VALUE);
    if fActive then Close;
  end;
end;

procedure TnrBaseIO.ThreadStart;
begin
  IsThreadIOTerminated := False;
  fThreadIO := TnrThreadIO.Create(Self, Priority);
end;

procedure TnrBaseIO.ThreadStop;
begin
  if not IsThreadIOTerminated then begin
    IsThreadIOTerminated := True;
    fThreadIO.Terminate;
    fThreadIO.WaitFor;
  end;
end;

procedure TnrBaseIO.ThreadExecute(aThread: TThread);
begin
// virtual ...
end;

function TnrBaseIO.RequestDeviceHandle: THandle;
begin
  Result := INVALID_HANDLE_VALUE;
end;

procedure TnrBaseIO.RequestDeviceHandleClose(aHandle: THandle);
begin
  CloseHandle(aHandle);
end;

procedure TnrBaseIO.Open;
begin
  hPort := RequestDeviceHandle;
  if IsHandleValid then begin

    if isThreaded then ThreadStart;

    if Assigned(fOnOpen) then
      try
        fOnOpen(Self);
      except
      end;

  end;
end;

procedure TnrBaseIO.Close;
begin
  if (hPort <> INVALID_HANDLE_VALUE) then begin

    RequestDeviceHandleClose(hPort);

    if isThreaded then ThreadStop;

    hPort := INVALID_HANDLE_VALUE;

    if Assigned(fOnClose) then
      try
        fOnClose(Self);
      except
      end;

  end;
//  hPort := INVALID_HANDLE_VALUE;
end;

procedure TnrBaseIO.Logging(Det: TDetailLevel; Mess: string);
begin
  if Assigned(Log) then begin
    if Length(Mess) = 0
      then Log.AddSysInfo
      else Log.AddLog(Det,sLogPrefix + Mess);
  end;
end;

procedure TnrBaseIO.LoggingFmt(Det: TDetailLevel; Mess: string;
  const Arg: array of const);
begin
  if Assigned(Log) then Log.AddLogFmt(Det,sLogPrefix+Mess,Arg);
end;

procedure TnrBaseIO.Update;
begin
//  Devices.Clear;
  DoNotify(csDeviceIdChanged);
end;

function TnrBaseIO.GetDataProcessor: TnrCustomDataProcessor;
begin
  Result:=fDataProcessor;
end;

function TnrBaseIO.GetLog: TnrCustomLogFile;
begin
  Result:=fLog;
end;

function TnrBaseIO.GetOnFatalError: TOnFatalErrorEvent;
begin
  Result := fOnFatalError;
end;

procedure TnrBaseIO.SetOnFatalError(const Value: TOnFatalErrorEvent);
begin
  fOnFatalError := Value;
end;

function TnrBaseIO.AddDevice(aDevice: TnrDevice): integer;
begin
  Result := slDevices.AddObject(aDevice.NameFriendly,aDevice);
  aDevice.fIndex := Result;
  aDevice.fParent := Self;
end;

function TnrBaseIO.AddDeviceSorted(aDevice: TnrDevice): integer;
var j, cnt:integer;
begin
  // adds device into list in ordering of Data value ...
  cnt := DeviceCount;
  Result := -1;
//  if l.IndexOf(S) >= 0 then exit;
  if (cnt = 0) or (Device[cnt - 1].Data < aDevice.Data)
    then Result := AddDevice(aDevice)
    else begin
      j := 0;
      while j < cnt do begin
        if Device[j].Data = aDevice.Data then exit; // skip duplicates
        if Device[j].Data > aDevice.Data then begin
          slDevices.InsertObject(j, aDevice.NameFriendly, aDevice);
          aDevice.fIndex := j;
          aDevice.fParent := Self;
          Result := j;
          Inc(j);
          while j < cnt do begin
            Device[j].fIndex := Device[j].fIndex + 1;
            inc(j);
          end;
          break;
        end;
        Inc(j);
      end;
    end;
end;

function TnrBaseIO.GetDevice(Index: integer): TnrDevice;
begin
  if (Index < 0) or (Index >= slDevices.Count)
    then Result := nil
    else Result := TnrDevice(slDevices.Objects[Index]);
end;

function TnrBaseIO.GetDeviceCount: integer;
begin
  Result :=  slDevices.Count;
end;

function TnrBaseIO.IndexOfDeviceData(aData: integer): integer;
var i : integer;
begin
  Result := -1;
  for i := 0 to DeviceCount - 1 do
    if Device[i].Data = aData then begin
      Result := i;
      break;
    end;
end;

function TnrBaseIO.IndexOfDevice(aDevice: TnrDevice): integer;
begin
  Result := slDevices.IndexOfObject(aDevice);
end;

procedure TnrBaseIO.DeviceListClear;
var i:integer;
begin
  if flClearDeviceListWithFree then
    for i := 0 to slDevices.Count-1 do
      if slDevices.Objects[i] <> nil
        then TnrDevice(slDevices.Objects[i]).Free;
  slDevices.Clear;
end;

procedure TnrBaseIO.GetDeviceList(L: TStrings);
var i:integer;
begin
  if DeviceCount = 0
    then DeviceListLoad;

  if L<>nil then begin
    L.Clear;
    for i:=0 to slDevices.Count-1
      do L.AddObject(slDevices.Strings[i],slDevices.Objects[i]);
  end;
end;

procedure TnrBaseIO.SetDeviceIndex(const Value: integer);
begin
  DoNotify(csDeviceIdChanged);
end;

{$IFNDEF GEN6}

const
  InstanceCount = 313;

type
  PObjectInstance = ^TObjectInstance;
  TWndMethod = procedure(var Message: TMessage) of object;
  TObjectInstance = packed record
    Code: Byte;
    Offset: Integer;
    case Integer of
      0: (Next: PObjectInstance);
      1: (Method: TWndMethod);
  end;

type
  PInstanceBlock = ^TInstanceBlock;
  TInstanceBlock = packed record
    Next: PInstanceBlock;
    Code: array[1..2] of Byte;
    WndProcPtr: Pointer;
    Instances: array[0..InstanceCount] of TObjectInstance;
  end;

var
  InstBlockList: PInstanceBlock;
  InstFreeList: PObjectInstance;

{ Standard window procedure }
{ In    ECX = Address of method pointer }
{ Out   EAX = Result }

function StdWndProc(Window: HWND; Message, WParam: Longint;
  LParam: Longint): Longint; stdcall; assembler;
asm
        XOR     EAX,EAX
        PUSH    EAX
        PUSH    LParam
        PUSH    WParam
        PUSH    Message
        MOV     EDX,ESP
        MOV     EAX,[ECX].Longint[4]
        CALL    [ECX].Pointer
        ADD     ESP,12
        POP     EAX
end;


function CalcJmpOffset(Src, Dest: Pointer): Longint;
begin
  Result := Longint(Dest) - (Longint(Src) + 5);
end;

function MakeObjectInstance(Method: TWndMethod): Pointer;
const
  BlockCode: array[1..2] of Byte = (
    $59,       { POP ECX }
    $E9);      { JMP StdWndProc }
  PageSize = 4096;
var
  Block: PInstanceBlock;
  Instance: PObjectInstance;
begin
  if InstFreeList = nil then
  begin
    Block := VirtualAlloc(nil, PageSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    Block^.Next := InstBlockList;
    Move(BlockCode, Block^.Code, SizeOf(BlockCode));
    Block^.WndProcPtr := Pointer(CalcJmpOffset(@Block^.Code[2], @StdWndProc));
    Instance := @Block^.Instances;
    repeat
      Instance^.Code := $E8;  { CALL NEAR PTR Offset }
      Instance^.Offset := CalcJmpOffset(Instance, @Block^.Code);
      Instance^.Next := InstFreeList;
      InstFreeList := Instance;
      Inc(Longint(Instance), SizeOf(TObjectInstance));
    until Longint(Instance) - Longint(Block) >= SizeOf(TInstanceBlock);
    InstBlockList := Block;
  end;
  Result := InstFreeList;
  Instance := InstFreeList;
  InstFreeList := Instance^.Next;
  Instance^.Method := Method;
end;

{ Free an object instance }

procedure FreeObjectInstance(ObjectInstance: Pointer);
begin
  if ObjectInstance <> nil then
  begin
    PObjectInstance(ObjectInstance)^.Next := InstFreeList;
    InstFreeList := ObjectInstance;
  end;
end;

var
  UtilWindowClass: TWndClass = (
    style: 0;
    lpfnWndProc: @DefWindowProc;
    cbClsExtra: 0;
    cbWndExtra: 0;
    hInstance: 0;
    hIcon: 0;
    hCursor: 0;
    hbrBackground: 0;
    lpszMenuName: nil;
    lpszClassName: 'TPUtilWindow');

function AllocateHWnd(Method: TWndMethod): HWND;
var
  TempClass: TWndClass;
  ClassRegistered: Boolean;
begin
  UtilWindowClass.hInstance := HInstance;
  ClassRegistered := GetClassInfo(HInstance, UtilWindowClass.lpszClassName,
    TempClass);
  if not ClassRegistered or (TempClass.lpfnWndProc <> @DefWindowProc) then
  begin
    if ClassRegistered then
      Windows.UnregisterClass(UtilWindowClass.lpszClassName, HInstance);
    Windows.RegisterClass(UtilWindowClass);
  end;
  Result := CreateWindowEx(WS_EX_TOOLWINDOW, UtilWindowClass.lpszClassName,
    '', WS_POPUP {!!!0}, 0, 0, 0, 0, 0, 0, HInstance, nil);
  if Assigned(Method) then
    SetWindowLong(Result, GWL_WNDPROC, Longint(MakeObjectInstance(Method)));
end;

procedure DeallocateHWnd(Wnd: HWND);
var
  Instance: Pointer;
begin
  Instance := Pointer(GetWindowLong(Wnd, GWL_WNDPROC));
  DestroyWindow(Wnd);
  if Instance <> @DefWindowProc then FreeObjectInstance(Instance);
end;

{$ENDIF}

{$IFDEF NR_WITH_CONTROLS}

function TnrBaseIO.GetTerminal: TCustomTerminal;
begin
  Result := fTerminal;
end;

procedure TnrBaseIO.SetTerminal(const Value: TCustomTerminal);
{$IFDEF LINUX}
var M : TMethod;
{$ENDIF}
begin

  {$IFNDEF LINUX}
  if Assigned(fTerminal)
    then fTerminal.WindowProc := fOldTerminalWndProc;

  if Value = Nil
    then fOldTerminalWndProc := nil
    else begin
      fOldTerminalWndProc  := Value.WindowProc;
      Value.WindowProc  := NewTerminalWndProc;
    end;

 {$ELSE}
 if objHook <> nil then QWidget_hook_destroy(objHook);
 objHook := nil;
 if Value <> Nil then begin
    objHook := QWidget_hook_create(Value.Handle);
    TEventFunc(M) := EventHandler;
    Qt_hook_hook_events(objHook,M);
  end;
 {$ENDIF}

  hTerminal := 0;
  fTerminal := Value;

  if (csDesigning in ComponentState) and (Value <> nil) then begin
    if TerminalUsage=tuNone then fTerminalUsage:=tuBoth;
  end;
  SetTerminalUsage(fTerminalUsage);
  if Value <> nil then Value.FreeNotification(self);

end;

function TnrBaseIO.GetTerminalEcho: boolean;
begin
  Result := fTerminalEcho;
end;

procedure TnrBaseIO.SetTerminalEcho(const Value: boolean);
begin
  fTerminalEcho := Value;
end;

function TnrBaseIO.GetTerminalUsage: TTerminalUsage;
begin
  Result := fTerminalUsage;
end;

procedure TnrBaseIO.SetTerminalUsage(const Value: TTerminalUsage);
begin
  fTerminalUsage := Value;

  if not Assigned(fTerminal) then exit;

  case Value of
    tuNone,tuSend:    hTerminal := 0;
    tuBoth,tuReceive: hTerminal := fTerminal.Handle;
  end;

end;

{$IFNDEF LINUX}
procedure TnrBaseIO.NewTerminalWndProc(var Message: TMessage);
begin
  if not fTerminalLocked then
   if (Message.Msg = WM_CHAR) then
     if  (fTerminalUsage=tuSend) or (fTerminalUsage=tuBoth)
     then begin
       //OutputDebugString(PChar('Wnd_WM_CHAR:' + Char(Message.wParam)));
       SendChar(Char(Message.wParam));
       if not fTerminalEcho then exit;

     end;
  fOldTerminalWndProc(Message);
end;

{$ELSE}

function TnrBaseIO.EventHandler(Handle : QObjectH; e : QEventH) : Boolean; cdecl;
var ch:char;
begin
  Result:=False;
  if(not TerminalLocked) and ((fTerminalUsage=tuSend)or(fTerminalUsage=tuBoth)) then
    if QEvent_type(e) = QEventType_KeyPress then begin
       Ch := Char(QKeyEvent_ascii(QKeyEventH(e)));
       if ch <> #0 then SendChar(Ch);
       if not fTerminalEcho then Result:=True;
  end;
end;

{$ENDIF}

procedure TnrBaseIO.UpdateTerminalByChars(Buff: PAnsiChar; Len: integer);
var
  {$IFNDEF LINUX}
  i,ii:integer;
  {$ELSE}
  KeyEvent : QKeyEventH;
  s:string;
  sWide:WideString;
  {$ENDIF}
begin
  fTerminalLocked := True;
  {$IFNDEF LINUX}
  if Len=1 then begin
    SendMessageA(hTerminal,WM_CHAR,Integer(Buff[0]),1);
    //PostMessageA(hTerminal,WM_CHAR,Integer(Buff[0]),1);
  end else begin
    i:=0;
    Buff[Len]:=#0;
    while i<=Len do begin
      ii:=i;
      repeat
        inc(i);
      until Buff[i]=#0;
      SendMessageA(hTerminal,EM_REPLACESEL,0,DWORD(Buff+ii));
      //PostMessageA(hTerminal,EM_REPLACESEL,0,DWORD(Buff+ii));
      inc(i);
    end;
  end;
  {$ELSE}
  SetLength(s,Stat^.cbInQue);
  CopyMemory(PChar(s),glBuffer,Stat^.cbInQue);
  sWide:=s;
  KeyEvent:=QKeyEvent_create(QEventType_KeyPress, 0, 0, 0, @sWide, False, 0);
  QOpenWidget_keyPressEvent(QOpenWidgetH(hTerminal), KeyEvent);
  QKeyEvent_destroy(KeyEvent);
  {$ENDIF}

  fTerminalLocked := False;

end;

{$ENDIF}

procedure TnrBaseIO.Loaded;
begin
  inherited Loaded;

  try
    {$IFDEF NR_WITH_CONTROLS}
    SetTerminal(fTerminal);
    SetTerminalUsage(fTerminalUsage);
    {$ENDIF}
    SetActive(fActive);
  except
    MessageBox(GetMainWindowHandle,'error','Error',MB_ICONERROR);
  end;

end;

function TnrBaseIO.WMDeviceChange(var Msg: TMessage):boolean;
begin
  Result := False;
  if (Msg.Msg = WM_DEVICECHANGE) and
     ((Msg.WParam = DBT_DEVICEREMOVECOMPLETE) or
     (Msg.WParam = DBT_DEVICEARRIVAL) or
     (Msg.WParam = DBT_DEVNODES_CHANGED) 
     ) then begin
    if (Msg.WParam = DBT_DEVNODES_CHANGED) or (PDWORD(Msg.lParam + 4)^ = DBT_DEVTYP_PORT)
    then begin
      Update;// nr Reload;// nrCom.Update;
      Result := True;
    end;
  end;
end;

procedure TnrBaseIO.DoInputDataInMainThread;
begin
  DoInputData(threadData, threadDataSize);
end;

function TnrBaseIO.DoInputData(Data: PAnsiChar; Len: cardinal): boolean;
var r:TIgnoreTypes;
begin

  r := [];
  Result := false;

  if (fThreadIO <> nil) and (UseMainThread) and (GetCurrentThreadId = fThreadIO.ThreadID) then begin
    threadData := Data;
    threadDataSize := Len;
    TnrThreadIO(fThreadIO).Synchronize(DoInputDataInMainThread);
    exit;
  end;

//  Result := false;

  if Assigned(fDataProcessor) then begin
    r := [];
    {$IFDEF TRIAL}
    if lim_inp(Len) then
    {$ENDIF}
    fDataProcessor.DoProcessing(Data, Len, r);
  end;

  {$IFDEF NR_WITH_CONTROLS}
  // Terminal working ...
  if Assigned(Terminal) and (not (itTerminal in r))
    then UpdateTerminalByChars(Data, Len);
  {$ENDIF}

  if Assigned(fOnReceived)
    then fOnReceived(Self, Data, Len);

end;

function TnrBaseIO.DoOutputData: boolean;
begin
  Result := False;
  if Assigned(DataProcessor)
    then DataProcessor.DoAfterSendComplete(Result);
end;

function TnrBaseIO.IsReadNeeded: boolean;
begin
  Result :=
    {$IFDEF NR_WITH_CONTROLS}
    Assigned(Terminal) or
    {$ENDIF}
    Assigned(fOnReceived) or
    Assigned(DataProcessor);
end;


{ TnrTimer }

constructor TnrTimer.Create;//(AOwner: TComponent);
begin
  inherited Create;//(AOwner);
  FEnabled := True;
  FInterval := 1000;
  FWindowHandle := AllocateHWnd(WndProc);
end;

destructor TnrTimer.Destroy;
begin
  FEnabled := False;
  UpdateTimer;
  DeallocateHWnd(FWindowHandle);
  inherited Destroy;
end;

procedure TnrTimer.WndProc(var Msg: TMessage);
begin
  with Msg do
    if Msg = WM_TIMER then
      try
        Timer;
      except
//        Application.HandleException(Self);
      end
    else
      Result := DefWindowProc(FWindowHandle, Msg, wParam, lParam);
end;

procedure TnrTimer.UpdateTimer;
begin
  KillTimer(FWindowHandle, 1);
  if (FInterval <> 0) and FEnabled and Assigned(FOnTimer)
    then SetTimer(FWindowHandle, 1, FInterval, nil);
end;

procedure TnrTimer.SetEnabled(Value: Boolean);
begin
  if Value <> FEnabled then
  begin
    FEnabled := Value;
    UpdateTimer;
  end;
end;

procedure TnrTimer.SetInterval(Value: Cardinal);
begin
  if Value <> FInterval then
  begin
    FInterval := Value;
    UpdateTimer;
  end;
end;

procedure TnrTimer.SetOnTimer(Value: TNotifyEvent);
begin
  FOnTimer := Value;
  UpdateTimer;
end;

procedure TnrTimer.Timer;
begin
  if Enabled and Assigned(FOnTimer) then FOnTimer(Self);
end;

(*
{ TnrThread }

function nrThreadProc(aThread:TnrThread):dword; stdcall;
begin
  if aThread <> nil
    then aThread.Execute;
  Result := 0;
  if Assigned(aThread.OnTerminate) then aThread.OnTerminate(aThread);
  if aThread.FreeOnTerminate then aThread.Free;
  ExitThread(0);
end;

constructor TnrThread.Create(flSuspended:boolean);
var flags:dword;
begin
  inherited Create;
  if flSuspended
    then flags := CREATE_SUSPENDED
    else flags := 0;
  fHandle := CreateThread(nil,0,@nrThreadProc,Self,flags,fThreadID);
end;

destructor TnrThread.Destroy;
begin
  Terminate;
  if not FreeOnTerminate then WaitForSingleObject(Handle,3000{INFINITE});
  CloseHandle(Handle);
  inherited Destroy;
end;

procedure TnrThread.Execute;
begin
//
end;

procedure TnrThread.Resume;
begin
  ResumeThread(fHandle);
end;

procedure TnrThread.Suspend;
begin
  SuspendThread(fHandle);
end;

procedure TnrThread.Terminate;
begin
  Terminated := True;
end;

*)

{ TCommEventNew }

constructor TCommEventNew.Create(sEvent: AnsiString);
begin
  inherited Create;
  SetStr(sEvent);
end;

procedure TCommEventNew.Reset;
begin
  Reserved := 1;
end;

function TCommEventNew.IsEvent(const Ch:byte):boolean;
begin
  Result := False;
  if len = 0 then exit;
  if fStr[Reserved] <> AnsiChar(ch) then begin
    if fStr[1] = AnsiChar(ch)
      then Reserved := 2
      else Reserved := 1;
  end else begin
    Inc(Reserved);
    if Reserved > Len then begin
      Reset;
      Result := True;
    end;
  end;
end;

procedure TCommEventNew.SetStr(const Value: AnsiString);
begin
  fStr := Value;
  fLen  := Length(fStr);
  Reset;
end;

function TCommEventNew.IsPossible: boolean;
begin
  Result := Reserved > 1;
end;

end.
