unit Deviceu;{see Device.def}
interface
uses
  UlanType, DeviceType;

function ExtDevInit(var AExtDevDrv: PExtDevDrv; const AName: shortstring): integer; export;
function ExtDevDoAction(AExtDevDrv: PExtDevDrv; ea: TExtDevAction; AInfo: longint): integer;export;
function ExtDevSetStr(AExtDevDrv: PExtDevDrv; ep: TExtDevProperty; const S: shortstring):integer; export;
function ExtDevGetStr(AExtDevDrv: PExtDevDrv; ep: TExtDevProperty; var S: shortstring):integer; export;
function ExtDevStart(AExtDevDrv: PExtDevDrv): integer; export;
function ExtDevStop(AExtDevDrv: PExtDevDrv): integer; export;
function ExtDevDoCharIn(AExtDevDrv: PExtDevDrv; ch:char): integer; export;
function ExtDevReadPoint(AExtDevDrv: PExtDevDrv; var APoint: TExpPoint): integer; export;
function ExtDevDone(var AExtDevDrv: PExtDevDrv): integer; export;


implementation
uses
  SysUtils, Classes, IniFiles,
  Fifou, Timer, WinUtl, PropUtl, BinHex, DLLUtl, xmlu {commint}, ExeLogu;
const
  BufSlotCount: integer = 1024;

type
  TComIniParam = string[10];
  TComIniParams = record
    bps: TComIniParam;
    parity: TComIniParam;
    stopbits: TComIniParam;
    databits: TComIniParam;
    FlowControl: TComIniParam;
    ParityCheck: TComIniParam;
    DsrSensitivity: TComIniParam;
    IgnoreXOff: TComIniParam;
    UseErrorChar: TComIniParam;
    NullStrip: TComIniParam;
    AbortOnError: TComIniParam;
    UseIniPars: TComIniParam;
    DTROnOpen: TComIniParam;
    RTSOnOpen: TComIniParam;
    XOnOnOpen: TComIniParam;
    BreakOnOpen: TComIniParam;
    ComMode: shortstring;
  end;

type

  Tpkt = class(TComponent)
  private
    FPrefix: string;
    FSuffix: string;
    FMask: string;

    FLen: string;
    FProp: string;
    FUnits: string;
    FScale: string;
    FCR: boolean;
    procedure SetFmt(AFmt: string);
    function GetFmt: string;
  public
    //constructor Create(AOwner: TComponent); override;
    property Prefix: string read FPrefix write FPrefix;
    property Suffix: string read FSuffix write FSuffix;
    property Mask: string read FMask write FMask;
  published
  {xml attr}
    property len: string read FLen write FLen;
    property fmt: string read GetFmt write SetFmt;
    property prop: string read FProp write FProp;
    property units: string read FUnits write FUnits;
    property scale: string read FScale write FScale;
  {/xml attr}
  end;

  Tdevice = class(TComponent)
  private
    FFifo: TFifo;
    FCurLine: shortstring;
      { currently beeing input line from incoming bytes }
    FCurVal: single;
    FCurTime: single; // current time in ms, used if Interval specified
    FZeroTime: single; // time in ms when device started, used if no Interval specified



    FQuantityName: shortstring;
      { name of the measured quantity extracted from the
        last received line from voltmeter (DC, AC, .. }
    FUnitName: shortstring;
      { name of the unit of current quantity (V, mV, Ohm, MOhm..)
        extracted from the last received line from voltmeter }
    FZeroValue: single;
    FRunning: boolean;
    FLastResult: TExtDevResult;
    {xml attr}
    {FID: shortstring;
      { manufacturer-model name of the voltmeter - definition of the packets set to Name prop }
    FComMode: string;
    FOrigComIniParams: TComIniParams;
    FDesc: string; { Eventual detailed description of the device. Not mandatory. }
    FEqualTo: string; { if non empty then defines ID of device definition that is totally equal }
    FTermStr: string; { packets termination string (if any common to all packets) }
    FMaxPktLen: integer; { maximal length of the packet, used for error checking }
    FInterval: integer;{ in ms }
    FMultY: single; { Multiply incoming Y value by this coef. Default = 1. }
    FAddY: single;  { Add this value to incoming Y value (after multiplication with MultY). Default = 0. }
    FPktReqStr: string; { string that must be sent to the device to get the packet }
    {/xml attr}
    function GetTermStrHex: string;
    procedure SetTermStrHex(AStr: string);
    function GetPkt(Index: integer): TPkt;
    function GetPktCount: integer;
    function GetPktReqStrHex: string;
    procedure SetPktReqStrHex(AStr: string);

  protected
    function DoLine: integer;
    procedure SetResult(er: TExtDevResult; const msg: string);
    procedure PortBeforeOpen(AInfo:longint);
    procedure PortAfterClose(AInfo: longint);
    //procedure SetID(const S: shortstring);
    //function GetID: shortstring;
    function PktFind(const ALine: string; var APkt: Tpkt): boolean;
  public
    constructor Create(AOwner: TComponent); override;
    function Start: integer;
    function Stop: integer;
    function DoAction(ea: TExtDevAction; AInfo: longint): integer;
    destructor Destroy; override;
    function DoCharIn(ch:char): integer;
    function ReadPoint(var APoint: TExpPoint): integer;
    function GetStr(ep: TExtDevProperty; var S: shortstring): integer;
    function SetStr(ep: TExtDevProperty; const S: shortstring): integer;
    //procedure AfterConstruction; override;
    property Pkts[Index:integer]: Tpkt read GetPkt;
    property PktCount: integer read GetPktCount;
  published
    property interval: integer read FInterval write FInterval;
    property commode: string read FComMode write FComMode;
    property desc: string read FDesc write FDesc;
    property equalto: string read FEqualTo write FEqualTo;
    property termstrhex: string read GetTermStrHex write SetTermStrHex;
    property pktreqstrhex: string read GetPktReqStrHex write SetPktReqStrHex;
    property maxpktlen: integer read FMaxPktLen write FMaxPktLen;
    property multy: single read FMultY write FMultY;
    property addy: single read FAddY write FAddY;
  end;

  Tdevices = class(TComponent)
  private
    FDesc: string;
    function GetDeviceList: shortstring;
  public
    function DeviceFind(const AName: shortstring): Tdevice;
    property DeviceList: shortstring read GetDeviceList;
  published
    property Desc: string read FDesc write FDesc;
  end;

var
  FDevices: TDevices;

{Tpkt.}
procedure Tpkt.SetFmt(AFmt: string);
type
  TFmtPos = (fpPrefix, fpMask, fpSuffix);
var
  i: integer;
  fp: TFmtPos;
begin
  FPrefix := '';
  FMask := '';
  FSuffix := '';
  fp := fpPrefix;
  for i := 1 to length(AFmt) do begin
    if AFmt[i] in ['-','0'..'9','.'] then begin
      case fp of
        fpPrefix, fpMask: begin
          fp := fpMask;
          FMask := FMask + AFmt[i];
        end;
      else
        FSuffix := FSuffix + AFmt[i];
      end;
    end else begin
      case fp of
        fpPrefix: begin
          FPrefix := FPrefix + AFmt[i];
        end;
        fpMask: begin
          fp := fpSuffix;
          FSuffix := FSuffix + AFmt[i];
        end;
        fpSuffix: begin
          FSuffix := FSuffix + AFmt[i];
        end;
      end;
    end;
  end;

  i := pos('/n', FSuffix);
  if i > 0 then begin
    FCR := true;
    FSuffix := copy(FSuffix, 1, i - 1);
  end else begin
    FCR := false;
  end;
end;

function Tpkt.GetFmt: string;
begin
  Result := FPrefix + FMask + FSuffix;
  if FCR then begin
    Result := Result + '/n';
  end;
end;
{/Tpkt.}

{Tdevice.}
function Tdevice.GetPkt(Index: integer): TPkt;
begin
  Result := TPkt(Components[Index]);
end;

function Tdevice.GetPktCount: integer;
begin
  Result := ComponentCount;
end;

function Tdevice.GetTermStrHex: string;
begin
  Result := StringToHex(FTermStr);
end;

procedure Tdevice.SetTermStrHex(AStr: string);
begin
  FTermStr := HexToString(AStr);
end;

function Tdevice.GetPktReqStrHex: string;
begin
  Result := StringToHex(FPktReqStr);
end;

procedure Tdevice.SetPktReqStrHex(AStr: string);
begin
  FPktReqStr := HexToString(AStr);
end;

procedure Tdevice.SetResult(er: TExtDevResult; const msg: string);
begin
  if er <> 0 then
    ExeLog.Log('Device.' + Name + ' ERROR: ' + IntToStr(er) + ' ' + msg);
  FLastResult := er;
end;

constructor Tdevice.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FAddY := 0;
  FMultY := 1;
  FFifo := TFifo.Create(sizeof(TExpPoint), BufSlotCount);
end;

function Tdevice.Start: integer;
begin
  Result := 0;
  FCurTime := 0;
  FZeroTime := mstime;
  FFifo.Clear;
  if FTermStr = '' then begin
    SetResult(edrNoTermStrPacketsNotSupported, '');
    Result := FLastResult;
  end else begin
    FRunning:= true;
  end;
end;

function Tdevice.Stop: integer;
begin
  Result := 0;
  FRunning := false;
end;

destructor Tdevice.Destroy;
begin
  FFifo.Free;
  inherited Destroy;
end;

procedure Tdevice.PortBeforeOpen(AInfo:longint);
//  COMx:1200,N,7,2,RS,CD,DS,CD
//  (flow control none,RTS Off, DTR On, all other off)

// for Metex3850: COM1:1200,N,7,2,RS,CS,DS,CD
{ from qbasic.hlp:
OPEN "COMn: optlist1 optlist2" [FOR mode] AS [#]filenum% [LEN=reclen%]

    ¦ n           The communications port to open (1 = COM1, 2 = COM2).
    ¦ optlist1    The most-often-used communications parameters:
                    [baud] [,[parity] [,[data] [,[stop]]]]
                  baud is the baud rate of the device to be opened:
                    75, 110, 150, 300, 600, 1200, 2400, 4800, 9600
                  parity is the method of parity checking:
                    N (none)     E (even)    O (odd)
                    S (space)    M (mark)    PE (enable error checking)
                  data is the number of data bits per byte:
                    5, 6, 7, 8
                  stop is the number of stop bits:
                    1, 1.5, 2
                  Defaults: 300 baud, even parity, 7 data bits, 1 stop bit.
    ¦ optlist2    A list of less-often-used parameters, separated by commas:
  Option    Description
 ------    --------------------------------------------------
 ASC       Opens the device in ASCII mode.
 BIN       Opens the device in binary mode.
 CD[m]     Sets the timeout period (in milliseconds) on the
           Data Carrier Detect (DCD) line.
 CS[m]     Sets the timeout period (in milliseconds) on the
           Clear to Send (CTS) line.
 DS[m]     Sets the timeout period (in milliseconds) on the
           Data Set Ready (DS) line.
 LF        Sends a line-feed character after a carriage
           return.
 OP[m]     Specifies how long (in milliseconds) OPEN COM
           waits for all communications lines to become open.
 RB[n]     Sets the size (in bytes) of the receive buffer.
 RS        Suppresses detection of Request to Send (RTS).
 TB[n]     Sets the size (in bytes) of the transmit buffer.
}
var
  sec: shortstring;
  f: TIniFile;
begin        //winutl
  if AInfo = 0 then begin
    {setresult}
    exit;
  end;
  f := TIniFile.Create(FindParentIniFile('Chromulan.ini'));
  sec := PShortString(AInfo)^;
  try
    with FOrigComIniParams do begin
      bps := f.ReadString(sec, 'bps','');
      parity := f.ReadString(sec, 'parity','');
      stopbits := f.ReadString(sec, 'stopbits','');
      databits := f.ReadString(sec, 'databits','');
      FlowControl := f.ReadString(sec, 'FlowControl','');
      ParityCheck := f.ReadString(sec, 'ParityCheck', '');
      DsrSensitivity := f.ReadString(sec, 'DsrSensitivity', '');
      IgnoreXOff := f.ReadString(sec, 'IgnoreXOff','');
      UseErrorChar := f.ReadString(sec, 'UseErrorChar','');
      NullStrip := f.ReadString(sec, 'NullStrip','');
      AbortOnError := f.ReadString(sec, 'AbortOnError','');
      UseIniPars := f.ReadString(sec, 'UseIniPars','');
      DTROnOpen := f.ReadString(sec, 'DTROnOpen','');
      RTSOnOpen := f.ReadString(sec, 'RTSOnOpen','');
      XOnOnOpen := f.ReadString(sec, 'XOnOnOpen','');
      BreakOnOpen := f.ReadString(sec, 'BreakOnOpen','');
      ComMode := f.ReadString(sec, 'ComMode','');
    end;

    f.WriteString(sec, 'bps','2400');//usually 1200
    f.WriteString(sec, 'parity','N');
    f.WriteString(sec, 'stopbits','2');
    f.WriteString(sec, 'databits','7');
    f.WriteString(sec, 'FlowControl','NONE');//CTS, DEFAULT?
    f.WriteString(sec, 'ParityCheck', '0');
    f.WriteString(sec, 'DsrSensitivity', '0');
    f.WriteString(sec, 'IgnoreXOff','0');
    f.WriteString(sec, 'UseErrorChar','0');
    f.WriteString(sec, 'NullStrip','0');
    f.WriteString(sec, 'AbortOnError','0');
    f.WriteString(sec, 'UseIniPars','1');
    f.WriteString(sec, 'DTROnOpen','1');
    f.WriteString(sec, 'RTSOnOpen','0');
    f.WriteString(sec, 'XOnOnOpen','0');
    f.WriteString(sec, 'BreakOnOpen','0');

    f.WriteString(sec, 'ComMode', commode); // overrides some of the above settings, if different
  finally
    f.Free;
  end;
end;

procedure Tdevice.PortAfterClose(AInfo:longint);
var
  f: TIniFile;
  sec: string;
begin
  f := TIniFile.Create(FindParentIniFile('Chromulan.ini'));//ulantype
  try
    sec := PShortString(AInfo)^;
    with FOrigComIniParams do begin
      f.WriteString(sec, 'bps', bps);
      f.WriteString(sec, 'parity', parity);
      f.WriteString(sec, 'stopbits', stopbits);
      f.WriteString(sec, 'databits', databits);
      f.WriteString(sec, 'FlowControl', FlowControl);
      f.WriteString(sec, 'ParityCheck', ParityCheck);
      f.WriteString(sec, 'DsrSensitivity', DsrSensitivity);
      f.WriteString(sec, 'IgnoreXOff', IgnoreXOff);
      f.WriteString(sec, 'UseErrorChar', UseErrorChar);
      f.WriteString(sec, 'NullStrip', NullStrip);
      f.WriteString(sec, 'AbortOnError', AbortOnError);
      f.WriteString(sec, 'UseIniPars', UseIniPars);
      f.WriteString(sec, 'DTROnOpen', DTROnOpen);
      f.WriteString(sec, 'RTSOnOpen', RTSOnOpen);
      f.WriteString(sec, 'XOnOnOpen', XOnOnOpen);
      f.WriteString(sec, 'BreakOnOpen', BreakOnOpen);
      f.WriteString(sec, 'ComMode', ComMode);
    end;
  finally
    f.Free;
  end;
end;


function Tdevice.DoAction(ea: TExtDevAction; AInfo: longint): integer;
begin
  FLastResult := 0;
  case ea of
    eaPortBeforeOpen: PortBeforeOpen(AInfo);
    eaPortAfterClose: PortAfterClose(AInfo);//ulantype
    eaPortBeforeClose: ; {just ignore}
  else
    SetResult(edrInvalidExtDevAction, IntToStr(ea));
  end;
  Result := FLastResult;
end;

function Tdevice.PktFind(const ALine: string; var APkt: Tpkt): boolean;
var
  p: Tpkt;
  i: integer;
begin
  Result := false;
  for i := 0 to PktCount - 1 do begin
    p := Pkts[i];
    if pos(p.Prefix, ALine) = 1 then begin
      if pos(p.Suffix, ALine) = length(ALine) - length(p.Suffix) + 1 then begin
        Result := true;
        APkt := p;
        exit;
      end;
    end;
  end;
end;

function Tdevice.DoLine: integer;
var
  ep: TExpPoint;
  code: integer;
  pkt: Tpkt;
begin
  Result := 0;
  if PktFind(copy(FCurLine, 1, length(FCurLine) - length(FTermStr)), pkt) then begin

    val(copy(FCurLine, length(pkt.Prefix) + 1, length(pkt.Mask)), FCurVal, code);

    FCurVal := FCurVal * FMultY + FAddY;
  {  if FCurLine[10]='m' then
      FCurVal := FCurVal / 1000;}
    FCurLine := '';

    ep.Y := FCurVal - FZeroValue;

    if Interval <> 0 then begin
      ep.X := FCurTime / 1000;
      FCurTime := FCurTime + Interval;
    end else begin
      ep.X := (mstime - FZeroTime) / 1000
    end;

    if not FFifo.Put(ep) then begin
      SetResult(edrBufferFull, FCurLine);
      Result := FLastResult;
    end;

  end else begin
    if length(FCurLine) > maxpktlen then begin
      SetResult(edrPktDefNotFound, FCurLine);
      Result := FLastResult;
    end;
  end;
end;

function Tdevice.DoCharIn(ch:char): integer;
var
  b: byte absolute ch;

  i: integer;
  p: Tpkt;
begin
  if FRunning then begin
    FLastResult := 0;
    FCurLine := FCurLine + ch;
    if FTermStr <> '' then begin
      if copy(FCurLine, length(FCurLine) - length(FTermStr) + 1,
        length(FTermStr)) = FTermStr then
      begin
        DoLine;
      end else begin
        if MaxPktLen = 0 then begin
          for i := 0 to PktCount - 1 do begin
            p := Pkts[i];
            if StrToInt(p.len) > MaxPktLen then
              MaxPktLen := StrToInt(p.len);
          end;
        end;
        if length(FCurLine) > MaxPktLen then begin
          SetResult(edrInvalidInput, FCurLine);
          FCurLine := '';
        end;
      end;

    {
    if ch = #13 then begin
      if length(FCurLine) = LineLen - 1 then begin
        DoLine;
      end else begin
        SetResult(edrInvalidInput, FCurLine + ch);
      end;
      FCurLine := '';
    end else begin
      if length(FCurLine) < LineLen then begin
        FCurLine := FCurLine + ch;
      end else begin
        SetResult(edrInvalidInput, FCurLine + ch);
        FCurLine := '';
      end;
    end;}
    {v0.74}{/v0.74
      Result := FLastResult;}
    end else begin
      { can not be FRunning if FTermStr = '' }
    end;
  end else begin
    SetResult(edrNotRunning, '');
  end;
  Result := FLastResult;
end;

function Tdevice.ReadPoint(var APoint: TExpPoint): integer;
begin
  if FFifo.Get(APoint) then
    Result := 0
  else
    Result := -1;
end;

function Tdevice.GetStr(ep: TExtDevProperty; var S: shortstring): integer;
begin
  FLastResult := 0;
  S := '';
  case ep of
    epQuantityName: S := FQuantityName;
    epUnitName: S := FUnitName;
    {epID: S := ID;}
    epComMode: S := ComMode;
    epPktReqStr: S := FPktReqStr;
  else
    SetResult(edrInvalidGetStrProp, IntToStr(ep));
  end;
  Result := FLastResult;
end;

function Tdevice.SetStr(ep: TExtDevProperty; const S: shortstring): integer;
begin
  FLastResult := 0;
  {case ep of
    epID: ID := s;
  else
    SetResult(edrInvalidSetStrProp, IntToStr(ep));
  end;}
  Result := FLastResult;
end;

{function Tdevice.GetID: shortstring;
begin
  Result := Name;
end;
 }
{procedure Tdevice.SetID(const S: shortstring);}
{const
  SupportedTypesCount = 3;
  SupportedTypes: array[0..SupportedTypesCount - 1] of shortstring =
  ('VoltCraft M-4650CR', 'Metex M3850', 'Metex M3830');
var
  i: integer;
  found: boolean;}
{begin}
{
  if FID <> S then begin
    found := false;
    for i := 0 to SupportedTypesCount - 1 do begin
      if UpperCase(S) = UpperCase(SupportedTypes[i]) then begin
        found := true;
        break;
      end;
    end;
    if not found then begin
      SetResult(edrUnsupportedDeviceType, S);
    end;
  end;
}
{  Name := S;
end;}
{/Tdevice.}

{Tdevices.}
function Tdevices.DeviceFind(const AName: shortstring): Tdevice;
begin
  Result := Tdevice(FindComponent(AName));
end;

function Tdevices.GetDeviceList: shortstring;
begin
  Result := ComponentChildList(Self);
end;//proputl
{/Tdevices.}

(*
function Valid(ADev: PExtDevDrv; var Res: integer): boolean;
begin
  Result := false;
  if ADev = nil then begin
    Res := edrDeviceNotInitialized;
  end else begin
    {if not (ADev is TDevice) then begin
      Res := edrInvalidDeviceHandle;
    end else }
    begin
      Res := 0;
      Result := true;
    end;
  end;
end;
*)
function Devices: TDevices;
var s: string;
begin
  if FDevices = nil then begin
    s := ChangeFileExt(GetModuleName(HInstance), '.def');
    XMLFileToComp(TComponent(FDevices), s);
  end;
  Result := FDevices;
end;

function ExtDevInit(var AExtDevDrv: PExtDevDrv; const AName: shortstring): integer;export;
var
  adrv: Tdevice absolute AExtDevDrv;
  e: Tdevice;
begin
  try
    if Devices <> nil then begin
      adrv := Devices.DeviceFind(AName);
      if adrv <> nil then begin
        if adrv.equalto <> '' then begin
          e := Devices.DeviceFind(adrv.equalto);
          if e <> nil then begin
            ClassAssign(adrv, e, [caoChildrenClear, caoChildrenAdd]);
          end;
        end;
        Result := 0
      end else
        Result := edrUnsupportedDeviceType;
     end else begin
       Result := edrDevicesInitFailed;
     end;
  except
    adrv := nil;
    Result := -1;
  end;
end;

function ExtDevSetStr(AExtDevDrv: PExtDevDrv; ep: TExtDevProperty; const S: shortstring):integer; export;
var
  adrv: Tdevice absolute AExtDevDrv;
begin
  Result := edrDeviceNotInitialized;
  if adrv <> nil then
    Result := adrv.SetStr(ep, s);
end;

function ExtDevGetStr(AExtDevDrv: PExtDevDrv; ep: TExtDevProperty; var S: shortstring):integer; export;
var
  adrv: Tdevice absolute AExtDevDrv;
begin
  Result := 0;
  if adrv <> nil then
    Result := adrv.GetStr(ep, s)
  else begin
    case ep of
      epDeviceList: begin
        if Devices <> nil then
          s := Devices.DeviceList;
      end;
    else
      Result := edrInvalidGetStrProp;//ulantype
    end;
  end;
end;

function ExtDevDoAction(AExtDevDrv: PExtDevDrv; ea: TExtDevAction;
  AInfo: longint): integer;
var
  adrv: Tdevice absolute AExtDevDrv;
begin
  Result := edrDeviceNotInitialized;
  if adrv <> nil then
    Result := adrv.DoAction(ea, AInfo);
end;


function ExtDevDone(var AExtDevDrv: PExtDevDrv): integer;export;
var
  adrv: Tdevice absolute AExtDevDrv;
begin
  Result := edrDeviceNotInitialized;
  if adrv <> nil then begin
    AExtDevDrv := nil;
    //adrv.Free;
    Result := 0;
  end;
end;

function ExtDevStart(AExtDevDrv: PExtDevDrv): integer;
var
  adrv: Tdevice absolute AExtDevDrv;
begin
  Result := edrDeviceNotInitialized;
  if adrv <> nil then
    Result := adrv.Start;
end;

function ExtDevStop(AExtDevDrv: PExtDevDrv): integer;
var adrv: Tdevice absolute AExtDevDrv;
begin
  Result := edrDeviceNotInitialized;
  if adrv <> nil then
    Result := adrv.Stop;
end;

function ExtDevDoCharIn(AExtDevDrv: PExtDevDrv; ch:char): integer;
var
  ADrv: Tdevice absolute AExtDevDrv;
begin
  Result := edrDeviceNotInitialized;
  if ADrv <> nil then
    Result := ADrv.DoCharIn(ch);
end;

function ExtDevReadPoint(AExtDevDrv: PExtDevDrv; var APoint: TExpPoint): integer;
var
  ADrv: Tdevice absolute AExtDevDrv;
begin
  Result := edrDeviceNotInitialized;
  if ADrv <> nil then
    Result := ADrv.ReadPoint(APoint);
end;


//  Devices := Tdevices.Create(nil);

initialization
  RegisterClasses([Tpkt, Tdevice, Tdevices]);
  FDevices := nil;
finalization
  if FDevices <> nil then begin
    CompToXMLFile(FDevices, 'DevicesTest.def');
    FDevices.Free;
  end;
end.
