(*********************************************************************
* WakeDevice Version 1.1 21/10/2006                                  *
*                                                                    *
*     Copyright (c)  2006 by                                         *
*                                                                    *
*     Andrei Krasutski                                               *
*       email1:      <Andrei_Krasutski@mail.ru>                      *
*       email2:      <Andrei_Krasutski@4enet.by>                     *
*                                                                    *
*********************************************************************)
unit WakeDevice;

interface

{.$DEFINE COMPORT}
{$DEFINE USBPORT}
{.$DEFINE FILESTREAM}

uses
  // Borland Software Corporation
  Windows, Classes, Registry, SysUtils;

const
//---------------------------------------------------------------------------
// Вид пакета: ADDR, CMD, COUNT, <256 bytes of data>, CRC
//---------------------------------------------------------------------------
  MAX_DATA_SIZE = 256;                 // Макс. размер передаваемых данных в одном пакете

//---------------------------------------------------------------------------
//  Стандартные коды команд протокола WAKE
//---------------------------------------------------------------------------
  CMD_NOP   = $00;                     // Нет операции
  CMD_ERROR = $01;                     // Передача кода ошибки
  CMD_ECHO  = $02;                     // Запрос возврата переданного пакета
  CMD_INFO  = $03;                     // Запрос информации об устройстве

  {$IFDEF FILESTREAM}
  FileName = 'WakeDevice.txt';
  CRLF = #13#10;
  {$ENDIF}
type

//---------------------------------------------------------------------------
//  TWakeDevice = class(TObject)
//---------------------------------------------------------------------------

  PWakeData = ^TWakeData;
  TWakeData = packed array [0..MAX_DATA_SIZE - 1] of byte;

  TOnWakeDeviceError = procedure (ACmd: byte; AErrCode: byte; E: Exception; Action: boolean) of object;

  TWakeDevice = class(TObject)
  private
    FCriticalSection : TRTLCriticalSection; // Секция для безопасного выполнения команд

    FisOpen   : boolean;               // Признак открытого порта
    FPort     : string;                // Текущий порт

    FOnError  : TOnWakeDeviceError;

    // Получить список доступных портов
    procedure GetListPorts(var APortBox : TStringList);

  protected
    {$IFDEF FILESTREAM}
    FFileStream : TFileStream;
    {$ENDIF}

    {$IFDEF COMPORT}
    FBaudRateCOM : integer;            // Скорость двоичной передачи COM порта;
//    FTimeOutCOM  : integer;            // RX Таймаут COM порта, мсек
    {$ENDIF}

    {$IFDEF USBPORT}
    FBaudRateUSB : integer;            // Скорость двоичной передачи USB порта;
//    FTimeOutUSB  : integer;            // RX Таймаут USB порта, мсек
    {$ENDIF}
    FTimeOut  : integer;               // RX Таймаут, мсек

    FDeviceName         : string;      // Имя устройства
    FDeviceVersion      : string;      // Версия устройства
    FDeviceSerialNumber : string;      // Серийный номер устройства

    FtxData : TWakeData;               // Буфер передачи
    FrxData : TWakeData;               // Буфер приема

    // заблокировать секцию
    procedure Lock;

    // разблокировать секцию
    procedure UnLock;

    // Выполнение команды; См. const CMD_XXX;
    procedure ExeCmd(const AtxCmd: byte; const AtxCount: byte = 0); overload;
    procedure ExeCmd(const AtxCmd: byte; out ArxData: TWakeData); overload;
    procedure ExeCmd(const AtxCmd: byte; const AtxCount: byte; const AtxData: TWakeData; out ArxData: TWakeData); overload;
    procedure ExeCmd(const ATimeOut: integer; const AtxCmd: byte; const AtxCount: byte; const AtxData: TWakeData; out ArxData: TWakeData); overload;
    procedure ExeCmd(const ATimeOut: integer; const AAddr: byte; const AtxCmd: byte; const AtxCount: byte; const AtxData: TWakeData; out ArxData: TWakeData); overload;

    // Открывает порт
    function OpenPort(APort: string): boolean;

    // Закрыть порт
    function ClosePort: boolean;

    // Очистка буфера порта
    function PurgePort: boolean;

    function TxFrame(const AtxAddr: byte; const AtxCmd: byte; const AtxCount: byte; const AtxData: TWakeData): boolean;

    function RxFrame(const ATimeOut: integer; out ArxAddr: byte; out ArxCmd: byte; out ArxCount: byte; out ArxData: TWakeData): boolean;

    // Отправляет устройству AtxData, возвращает в ArxData, сравнивает пакеты
    function GetEcho(const AtxData: TWakeData; var ArxData: TWakeData; const ACount: byte): boolean;

    // Возвращает инфо
    function GetInfo(out Inf: string):boolean; overload;

    // Возвращает инфо
    function GetInfo(out ADeviceName : string; out ADeviceVersion : string; out ADeviceSerialNumber: string): boolean; overload;

    // Проверка на наличие ошибки
    procedure CheckErrorCode(const ACmd: byte; const AErrCode: byte; const AHelpContext: integer); virtual;

  public
    constructor Create; virtual;

    destructor Destroy; override;

    // Поиск устройства
    function DeviceSearch(ADeviceName : string; ADeviceVersion : string; ADeviceSerialNumber: string): boolean; overload;
    function DeviceSearch(const AInf : string): boolean; overload;

    // Список подключенных устройств
    procedure DiveceList(AList: TStrings); virtual;

    // Проверка подключенного устройства
    function isConnect(ADeviceName : string; ADeviceVersion : string; ADeviceSerialNumber: string): boolean; overload;
    function isConnect(const Inf: string): boolean; overload;

    // Признак открытого порта
    property isOpen: boolean read FisOpen;

    // Текущий порт
    property Port: string read FPort;

    {$IFDEF COMPORT}
    // Скорость двоичной передачи COM порта
    property BaudRateCOM: integer read FBaudRateCOM;
    {$ENDIF}
    {$IFDEF USBPORT}
    // Скорость двоичной передачи USB порта
    property BaudRateUSB: integer read FBaudRateUSB;
    {$ENDIF}
    // Имя устройства
    property DeviceName: string read FDeviceName;

    // Версия устройства
    property DeviceVersion: string read FDeviceVersion;

    // Серийный номер устройства
    property DeviceSerialNumber: string read FDeviceSerialNumber;

    // Событие ошибки
    property OnError : TOnWakeDeviceError read FOnError write FOnError;
  end;

//---------------------------------------------------------------------------
//  EWakePortError = class (Exception); Ошибки при работе с портом
//---------------------------------------------------------------------------

  EWakePortError = class (Exception);

//---------------------------------------------------------------------------
//  EWakeDeviceError = class (Exception); Ошибки при работе с устройством
//---------------------------------------------------------------------------

  EWakeDeviceError = class (Exception);

//---------------------------------------------------------------------------
//  Ошибки
//---------------------------------------------------------------------------
resourcestring

// EWakePortError: Ошибки при работе с портом

  SPortNotOpened      = 'Wake: Порт не открыт.';                      // код = $01
  SCantOpenPort       = 'Wake: Невозможно открыть порт.';             // код = $02
  SSendFrameError     = 'Wake: Ошибка при передаче пакета в порт.';   // код = $03
  SReceiveFrameError  = 'Wake: Ошибка при приёме пакета из порта.';   // код = $04
//  SFlushError         = 'Wake: Ошибка порта в вызове Flush().';     // код = $05
  SPurgeError         = 'Wake: Ошибка порта в вызове Purge.';         // код = $06
  SCloseError         = 'Wake: Ошибка при закрытии порта.';           // код = $07
  SPortOpened         = 'Wake: Порт открыт.';                         // код = $08
//  SCantAccessPort     = 'Wake: Порт недоступен.';
//  SWaitEventError     = 'Wake: Ошибка при ожидании события порта.';
//  SSendDataError      = 'Wake: Ошибка при передаче данных в порт.';
//  SReceiveDataError   = 'Wake: Ошибка при приёме данных из порта.';
//  SGetModemLinesError = 'Wake: Ошибка порта в вызове GetModLns().';
//  SSetModemLinesError = 'Wake: Ошибка порта в вызове SetModLns().';
//  SGetMaskError       = 'Wake: Ошибка порта в вызове GetMaskCOM().';
//  SSetMaskError       = 'Wake: Ошибка порта в вызове SetMaskCOM().';

  // EWakeDeviceError: Ошибки при работе с устройством

  SIOError                  = 'Неправильный пакет.';     // код = $01
  SDeviceBusyError          = 'Устройство занято.';      // код = $02
  SDeviceNotReady           = 'Устройство не готово.';   // код = $03
  SParametersError          = 'Неправильные параметры.'; // код = $04
  SDeviceNotRespondingError = 'Устройство не отвечает.'; // код = $05
  SNoCarrier                = 'Отсутствует несущая.';    // код = $06
  SUnknown                  = 'Неизвестная ошибка.'#13'Код: %d'#13'Class: %s';

implementation

//---------------------------------------------------------------------------
//  Имортировать функции из wsp32.dll
//---------------------------------------------------------------------------

{$IFDEF COMPORT}
const
  wsp32_dll = 'wsp32.dll';

  // Проверяет доступность порта.
  // В качестве параметра передается имя порта (например, "COM1").
  // Возвращает true в случае доступности порта.
  // bool AccessCOM(char *P);
//  function AccessCOM(P: PChar): BOOL; stdcall; external wsp32_dll;

  // Открыть порт.
  // В качестве параметров передаются имя порта (например, "COM1") и скорость
  // в бодах, которая может принимать одно из стандартных значений (например, 115200).
  // Возвращает true в случае успешного выполнения. Функция устанавливает на линии
  // DTR уровень -12В, а на линии RTS уровень +12В.
  // bool WINAPI OpenCOM(char *P, DWORD baud);
  function OpenCOM(P: PChar; Baud: DWORD): BOOL; stdcall; external wsp32_dll;

  // Закрыть порт.
  // Возвращает true в случае успешного выполнения.
  // bool CloseCOM(void);
  function CloseCOM: BOOL; stdcall; external wsp32_dll;

  // Функция осуществляет управление линиями RTS и DTR.
  // Возвращает true в случае успешного выполнения.
  // Параметр такой же, как у функции API EscapeCommFunction.
  // bool SetModLns(DWORD F);
//  function SetModLns(F: DWORD): BOOL; stdcall; external wsp32_dll;

  // Функция считывает линии управления модема CTS и DSR.
  // Возвращает true в случае успешного выполнения.
  // Параметр такой же, как у функции API GetCommModemStatus.
  // bool GetModLns(LPDWORD lpD);
//  function GetModLns(var lpD: DWORD): BOOL; stdcall; external wsp32_dll;

  // Очистить буфер COM-порта и прерывает текущие операции приема/передачи.
  // Возвращает true в случае успешного выполнения.
  // bool PurgeCOM(void);
  function PurgeCOM: BOOL; stdcall; external wsp32_dll;

  // Очистить буфер COM-порта, дождавшись завершения передачи.
  // Возвращает true в случае успешного выполнения.
  // bool FlushCOM(void);
//  function FlushCOM: BOOL; stdcall; external wsp32_dll;

  // Функция считывает маску событий COM-порта.
  // Возвращает true в случае успешного выполнения.
  // Параметр такой же, как у функции API GetCommMask.
  // bool GetMaskCOM(LPDWORD lpEvtMask)
//  function GetMaskCOM(var lpEvtMask: DWORD): BOOL; stdcall; external wsp32_dll;

  // Функция устанавливает маску Возвращает true в случае успешного выполнения.
  // Параметр такой же, как у функции API SetCommMask.
  // bool SetMaskCOM(DWORD EvtMask)
//  function SetMaskCOM(EvtMask: DWORD): BOOL; stdcall; external wsp32_dll;

  // Функция служит для ожидания события COM-порта.
  // Возвращает true в случае успешного выполнения.
  // bool WaitEventCOM(LPDWORD lpEvtMask)
//  function WaitEventCOM(var lpEvtMask: DWORD): BOOL; stdcall; external wsp32_dll;

  // Осуществляет прием WAKE-пакета.
  // Возвращает true в случае успешного выполнения
  // bool RxFrame(DWORD To, unsigned char &ADD,
  //   unsigned char &CMD, unsigned char &N, unsigned char *Data);
  function RxFrameCOM(TimeOut: DWORD; var Addr: UCHAR; var Cmd: UCHAR; var Count: UCHAR;
       var Data): BOOL;  stdcall; external wsp32_dll name 'RxFrame';

  // Осуществляет передачу WAKE-пакета.
  // Возвращает true в случае успешного выполнения.
  // bool TxFrame(unsigned char ADDR, unsigned char CMD,
  //   unsigned char N, unsigned char *Data);
  function TxFrameCOM(Addr: UCHAR; Cmd: UCHAR; Count: UCHAR;
       const Data): BOOL; stdcall; external wsp32_dll name 'TxFrame';
{$ENDIF}

//---------------------------------------------------------------------------
//  Имортировать функции из wusb32.dll
//---------------------------------------------------------------------------
{$IFDEF USBPORT}
const
  wusb32_dll = 'wusb32.dll';

  function AccessUSB(devID: integer): BOOL; stdcall; external wusb32_dll;
  function OpenUSB(devID: integer; Baud: DWORD): BOOL; stdcall; external wusb32_dll;
  function CloseUSB: BOOL; stdcall; external wusb32_dll;
  function PurgeUSB: BOOL; stdcall; external wusb32_dll;
  function RxFrameUSB(Timeout: DWORD; var Addr: UCHAR; var Cmd: UCHAR; var Count: UCHAR;
           var Data): BOOL;  stdcall; external wusb32_dll;
  function TxFrameUSB(Addr: UCHAR; Cmd: UCHAR; Count: UCHAR;
           const Data): BOOL; stdcall; external wusb32_dll;
  function NumUSB(var numDevs: DWORD): BOOL; stdcall; external wusb32_dll;

{$ENDIF}

//---------------------------------------------------------------------------
//  TWakeDevice = class(TObject)
//---------------------------------------------------------------------------

constructor TWakeDevice.Create;
begin
  InitializeCriticalSection(FCriticalSection);

  FDeviceName         := '';
  FDeviceVersion      := '';
  FDeviceSerialNumber := '';
  {$IFDEF COMPORT}
  FBaudRateCOM        := 115200;
//  FTimeOutCOM         := 200;
  {$ENDIF}
  {$IFDEF USBPORT}
  FBaudRateUSB        := 115200;
//  FTimeOutUSB         := 200;
  {$ENDIF}
  FTimeOut            := 200;

  FisOpen             := false;

end;

destructor TWakeDevice.Destroy();
begin
  if FisOpen then
      ClosePort;
  DeleteCriticalSection(FCriticalSection);
end;

procedure TWakeDevice.Lock;
begin
  EnterCriticalSection(FCriticalSection);
end;

procedure TWakeDevice.Unlock;
begin
  LeaveCriticalSection(FCriticalSection);
end;

procedure TWakeDevice.GetListPorts(var APortBox: TStringList);
var
  i : integer;
{$IFDEF USBPORT}
  USBCount : DWORD;
{$ENDIF}
begin
{$IFDEF COMPORT}
  with TRegistry.Create() do
  try
    RootKey := HKEY_LOCAL_MACHINE;
    if OpenKey('\HARDWARE\DEVICEMAP\SERIALCOMM', false) then
    begin
      GetValueNames(APortBox);
      for i := 0 to APortBox.Count - 1 do
          APortBox.Strings[i] := ReadString(APortBox.Strings[i]);

      CloseKey;
    end
    else
    begin
      APortBox.Add('COM1');
      APortBox.Add('COM2');
    end;

  finally
    Free;
  end;
{$ENDIF}

{$IFDEF USBPORT}
  if NumUSB(USBCount) then
     for i := 0 to USBCount - 1 do
         APortBox.Add(IntToStr(i));
{$ENDIF}

end;

// Поиск устройства
function TWakeDevice.DeviceSearch(ADeviceName: string; ADeviceVersion: string; ADeviceSerialNumber: string): boolean;
var
  i       : integer;
  PortBox : TStringList;

  DeviceName,
  DeviceVersion,
  DeviceSerialNumber : string;
begin
  result := false;

  if FisOpen then
      ClosePort;

  PortBox := TStringList.Create;
  try
    GetListPorts(PortBox);

    for i := 0 to PortBox.Count - 1 do
    begin
      try
        if OpenPort(PChar(PortBox.Strings[i])) then
        begin
          Sleep(1);
          if GetInfo(DeviceName, DeviceVersion, DeviceSerialNumber) then
          begin
            if ((AnsiCompareText(ADeviceName, DeviceName) = 0 ) or (ADeviceName = '')) and
               ((AnsiCompareText(ADeviceVersion, DeviceVersion) = 0  ) or (ADeviceVersion = '')) and
               ((AnsiCompareText(ADeviceSerialNumber, DeviceSerialNumber) = 0 ) or (ADeviceSerialNumber = ''))
               then
            begin
              result := true;
              FDeviceName         := DeviceName;
              FDeviceVersion      := DeviceVersion;
              FDeviceSerialNumber := DeviceSerialNumber;
              exit;
            end;
          end

        end;
      except //on EWakePortError do
      end;
      if FisOpen then
          ClosePort;

    end;

  finally
    PortBox.free;
  end;

end;

// Поиск устройства
function TWakeDevice.DeviceSearch(const AInf : string): boolean;
var
  PortBox : TStringList;
  i : integer;
  s         : string;
begin
  Result := false;

  if FisOpen then
      ClosePort;

  PortBox := TStringList.Create;
  try
    GetListPorts(PortBox);

    for i := 0 to PortBox.Count - 1 do
    begin
      try
        if OpenPort(PChar(PortBox.Strings[i])) then
        begin
          Sleep(1);
          if GetInfo(s) then
          begin
            if AnsiSameText(s, AInf) then
            begin
              Result := true;
//              FDeviceName         := DeviceName;
//              FDeviceVersion      := DeviceVersion;
//              FDeviceSerialNumber := DeviceSerialNumber;
              Exit;
            end;
          end

        end;
      except //on EWakePortError do
      end;
      if FisOpen then
          ClosePort;
    end;

  finally
    PortBox.free;
  end;

end;

procedure TWakeDevice.DiveceList(AList: TStrings);
var
  i       : integer;
  PortBox : TStringList;

  Inf : string;
begin
  AList.Clear;
  if FisOpen then
      ClosePort;

  PortBox := TStringList.Create;
  try
    GetListPorts(PortBox);

    for i := 0 to PortBox.Count - 1 do
    begin
      try
        if OpenPort(PChar(PortBox.Strings[i])) then
        begin
          Sleep(1);
          if GetInfo(Inf) then
               AList.Add(Inf);
        end;
      except //on EWakePortError do
      end;
      if FisOpen then
          ClosePort;

    end;

  finally
    PortBox.free;
  end;

end;

// Проверка подключенного устройства
function TWakeDevice.isConnect(ADeviceName: string; ADeviceVersion: string; ADeviceSerialNumber: string): boolean;
var
  DeviceName,
  DeviceVersion,
  DeviceSerialNumber : string;
begin
  result := false;
  if isOpen then
      try
        if GetInfo(DeviceName, DeviceVersion, DeviceSerialNumber) then
            if ((AnsiCompareText(ADeviceName, DeviceName) = 0 ) or (ADeviceName = '')) and
               ((AnsiCompareText(ADeviceVersion, DeviceVersion) = 0  ) or (ADeviceVersion = '')) and
               ((AnsiCompareText(ADeviceSerialNumber, DeviceSerialNumber) = 0 ) or (ADeviceSerialNumber = '')) then
                result := true;

      except
      end;
end;

function TWakeDevice.isConnect(const Inf: string): boolean;
var
  s : string;
begin
  result := false;
  if isOpen then
      try
        if GetInfo(s) then
            if AnsiSameText(s, Inf) then
                result := true;
      except
      end;
end;

// Открывает порт
function TWakeDevice.OpenPort(APort: string): boolean;
{$IFDEF FILESTREAM}
var
  s : string;
{$ENDIF}
begin
  Lock;
  try
    result := false;
    if FisOpen then
        raise EWakePortError.CreateHelp(SPortOpened, $08);
    FisOpen := false;
    FPort := '';          // TODO: clear param
    {$IFDEF COMPORT}
    if Pos('COM', APort) > 0 then
    begin
      FisOpen := OpenCOM(PChar(APort), FBaudRateCOM);
      FPort := APort;
    end;
    {$ENDIF}

    {$IFDEF USBPORT}
    if Pos('COM', APort) = 0 then
    begin
      FisOpen := OpenUSB(StrToInt(APort), FBaudRateUSB);
      FPort := 'USB'{ + APort};
    end;
    {$ENDIF}

    if not FisOpen then
        raise EWakePortError.CreateHelp(SCantOpenPort, $02);

    {$IFDEF FILESTREAM}
    FileClose(FileCreate(FileName));
    FFileStream := TFileStream.Create(FileName, fmOpenReadWrite or fmShareDenyWrite);
    if Assigned(FFileStream) then
    begin
      FFileStream.Seek(soFromBeginning, 0);
      FFileStream.Size := 0;

      s := Format('OpenPort:%s [TIME]%s' + CRLF + CRLF, [FPort, DateTimeToStr(Now)]);
      FFileStream.Write(s[1], length(s));
    end;
    {$ENDIF}

    result := FisOpen;
  finally
    UnLock;
  end;
end;

// закрыть порт
function TWakeDevice.ClosePort: boolean;
{$IFDEF FILESTREAM}
var
  s : string;
{$ENDIF}
begin
  Lock;
  try
    result  := false;
    if (not FisOpen) or (FPort = '') then
        raise EWakePortError.CreateHelp(SPortNotOpened, $01);

    {$IFDEF COMPORT}
    if Pos('COM', FPort) > 0 then
        if not CloseCOM then
            raise EWakePortError.CreateHelp(SCloseError, $07);
    {$ENDIF}

    {$IFDEF USBPORT}
    if Pos('COM', FPort) = 0 then
        if not CloseUSB then
            raise EWakePortError.CreateHelp(SCloseError, $07);
    {$ENDIF}

    {$IFDEF FILESTREAM}
    if Assigned(FFileStream) then
    begin
      s := Format('ClosePort:%s [TIME]%s'+ CRLF, [FPort, DateTimeToStr(Now)]);
      FFileStream.Write(s[1], length(s));

      FreeAndNil(FFileStream);
    end;
    {$ENDIF}

    FDeviceName         := '';            // TODO: clear param
    FDeviceVersion      := '';
    FDeviceSerialNumber := '';
    FPort               := '';
    FisOpen := false;

    result  := true;
  finally
    UnLock;
  end;
end;

// Очистка буфера порта
function TWakeDevice.PurgePort: boolean;
begin
  Lock;
  try
    result := false;
    if (not FisOpen) or (FPort = '') then
        raise EWakePortError.CreateHelp(SPortNotOpened, $01);

    {$IFDEF COMPORT}
    if Pos('COM', FPort) > 0 then
    begin
    if not PurgeCOM() then
        raise EWakePortError.CreateHelp(SPurgeError, $06);
    end;
    {$ENDIF}

    {$IFDEF USBPORT}
    if Pos('COM', FPort) = 0 then
    if not PurgeUSB() then
        raise EWakePortError.CreateHelp(SPurgeError, $06);
    {$ENDIF}
    result := true;
  finally
    UnLock;
  end;
end;

function TWakeDevice.TxFrame(const AtxAddr: byte; const AtxCmd: byte; const AtxCount: byte; const AtxData: TWakeData): boolean;
{$IFDEF FILESTREAM}
var
  i : integer;
  s : string;
{$ENDIF}
begin
  result := false;
  {$IFDEF COMPORT}
  if Pos('COM', FPort) > 0 then
      result := TxFrameCOM(AtxAddr, AtxCmd, AtxCount, AtxData);
  {$ENDIF}

  {$IFDEF USBPORT}
  if Pos('COM', FPort) = 0 then
      result := TxFrameUSB(AtxAddr, AtxCmd, AtxCount, AtxData);
  {$ENDIF}

  {$IFDEF FILESTREAM}
  if Assigned(FFileStream) then
  begin
    if result then
        s := s + 'TX: '
    else
        s := s + 'TX Error: ';

    // TX: [TIME]01.01.2006 12:59:59 [ADDR]00, [CMD]05, [COUNT]02, [FRAME]01 02
    s := s + Format('[TIME]%s, [ADDR]$%.2x, [CMD]$%.2x, [COUNT]$%.2x, [FRAME]', [DateTimeToStr(Now), AtxAddr, AtxCmd, AtxCount]);
    for i := 0 to AtxCount - 1 do
        s := s + '$' + IntToHex(AtxData[i], 2) + ' ';
    s := s + CRLF;
    FFileStream.Write(s[1], length(s));
  end;
  {$ENDIF}

end;

function TWakeDevice.RxFrame(const ATimeOut: integer; out ArxAddr: byte; out ArxCmd: byte; out ArxCount: byte; out ArxData: TWakeData): boolean;
{$IFDEF FILESTREAM}
var
  i : integer;
  s : string;
{$ENDIF}
begin
  result := false;
  {$IFDEF COMPORT}
  if Pos('COM', FPort) > 0 then
      result := RxFrameCOM(ATimeOut, ArxAddr, ArxCmd, ArxCount, ArxData);
  {$ENDIF}

  {$IFDEF USBPORT}
  if Pos('COM', FPort) = 0 then
      result := RxFrameUSB(ATimeOut, ArxAddr, ArxCmd, ArxCount, ArxData);
  {$ENDIF}

  {$IFDEF FILESTREAM}
  if Assigned(FFileStream) then
  begin
    if result then
    begin
      // RX: [TIME]01.01.2006 12:59:59, [ADDR]00, [CMD]05, [COUNT]02, [FRAME]01 02
      s := Format('RX: [TIME]%s, [TimeOut]%d, [ADDR]$%.2x, [CMD]$%.2x, [COUNT]$%.2x, [FRAME]', [DateTimeToStr(Now), ATimeOut, ArxAddr, ArxCmd, ArxCount]);
      for i := 0 to ArxCount - 1 do
          s := s + '$' + IntToHex(ArxData[i], 2) + ' ';
    end
    else
        s := Format('RX Error: [TIME]%s, [TimeOut]%d', [DateTimeToStr(Now), ATimeOut]);
    s := s + CRLF + CRLF;
    FFileStream.Write(s[1], length(s));
  end;
  {$ENDIF}

end;

// Выполнение команды
procedure TWakeDevice.ExeCmd(const AtxCmd: byte; const AtxCount: byte = 0);
begin
  ExeCmd(AtxCmd, AtxCount, FtxData, FrxData);
end;

procedure TWakeDevice.ExeCmd(const AtxCmd: byte; out ArxData: TWakeData);
begin
  ExeCmd(AtxCmd, 0, FtxData, ArxData);
end;

procedure TWakeDevice.ExeCmd(const AtxCmd: byte; const AtxCount: byte; const AtxData: TWakeData; out ArxData: TWakeData);
begin
  ExeCmd(FTimeOut, AtxCmd, AtxCount, AtxData, ArxData);
end;

procedure TWakeDevice.ExeCmd(const ATimeOut:integer; const AtxCmd: byte; const AtxCount: byte; const AtxData: TWakeData; out ArxData: TWakeData);
begin
  ExeCmd(ATimeOut, 0, AtxCmd, AtxCount, AtxData, ArxData);
end;

procedure TWakeDevice.ExeCmd(const ATimeOut:integer; const AAddr: byte; const AtxCmd: byte; const AtxCount: byte; const AtxData: TWakeData; out ArxData: TWakeData);
var
  rxAddr,
  rxCmd,
  rxCount : byte;
begin
  ZeroMemory(@ArxData, SizeOf(ArxData)); // очищаем буфер приема

  PurgePort;

  if not TxFrame(AAddr, AtxCmd, AtxCount, AtxData) then
      raise EWakePortError.CreateHelp(SSendFrameError, $03);

  if not RxFrame(ATimeOut, rxAddr, rxCmd, rxCount, ArxData) then
      raise EWakePortError.CreateHelp(SReceiveFrameError, $04);

  CheckErrorCode(rxCmd, ArxData[0], 0);
end;

// Возвращает эхо
function TWakeDevice.GetEcho(const AtxData: TWakeData; var ArxData: TWakeData; const ACount: byte): boolean;
begin
  Lock;
  try
    result := false;
    ExeCmd(CMD_ECHO, ACount, AtxData, ArxData);
    if CompareMem(@AtxData, @ArxData, ACount) then
        result := true;

  finally
    UnLock;
  end;
end;

// Возвращает инфо
function TWakeDevice.GetInfo(out Inf: string): boolean;
begin
  Lock;
  try
    result := false;
    ExeCmd(CMD_INFO);
    Inf := StrPas(@FrxData);
    result := true;
  finally
    UnLock;
  end;

end;

// Возвращает инфо
function TWakeDevice.GetInfo(out ADeviceName: string; out ADeviceVersion: string; out ADeviceSerialNumber: string): boolean;
var
  i, j : integer;
  Inf, Tmp : string;
begin
  if GetInfo(Inf) then
  begin
    Inf                 := Inf + ' ';
    Tmp                 := '';
    ADeviceName         := '';
    ADeviceVersion      := '';
    ADeviceSerialNumber := '';
    j := 0;
    for i := 1 to Length(Inf) do
    begin
      if (Inf[i] = ' ')then
      begin
        case j of
          0 : ADeviceName := Tmp;
          1 : ADeviceVersion := Tmp;
          2 : ADeviceSerialNumber := Tmp;
        end;
        Tmp := '';
        inc(j);
      end
      else
          Tmp := Tmp + Inf[i];

    end;
    result := true;
  end
  else
      result := false;
end;

// Проверка на наличие ошибки
procedure TWakeDevice.CheckErrorCode(const ACmd: byte; const AErrCode: byte; const AHelpContext: integer);
var
  E      : Exception;
  ErrStr : string;
  Action : boolean;
begin
  if (not (ACmd in [CMD_INFO, CMD_ECHO])) and (AErrCode <> 0) then
  begin
    case AErrCode of
      $01 : ErrStr := SIOError;
      $02 : ErrStr := SDeviceBusyError;
      $03 : ErrStr := SDeviceNotReady;
      $04 : ErrStr := SParametersError;
      $05 : ErrStr := SDeviceNotRespondingError;
      $06 : ErrStr := SNoCarrier;
      else
          ErrStr := SUnknown;
    end;

    E := EWakeDeviceError.CreateFmtHelp(ErrStr, [AErrCode, Self.ClassName], $06 * AHelpContext + AErrCode);
    Action := true;
    if Assigned(FOnError) then
        FOnError(ACmd, AErrCode, E, Action);
    if Action then
        raise E;
    E.free;
  end;
end;

end.
