unit Modulu;

interface
uses
  Windows, Messages, SysUtils, Classes, Dialogs, Math,
  InfoFrm, WinUtl, UlanGlob, BinHex,
  ULDrvTyp, ULDrvUtl, ULDriver,
  LogType, Logu, Stru,
  MyType, UlanType,
  ULRecTyp, ULObju, uLDType, uLDObju, Timer,
  ULDPType, ULDPObju, ULDRType, ULDRObju, ULFObju,
  ModuType, ModActu
  {$IFDEF DEBUG}
  ,DebugFrm
  {$ENDIF}
  {v0.14}
  ,ULLType, ULLObju, ULNType, ULNObju, ULNDType, ULNDObju,
  ExtDevIntu
  {/v0.14}
  ;

const
  NoModule = nil;
  MaxModulePropCount = 100;
  ModuleAddrStrLen = 2;
    { how many starting chars of module description string
      take Addr (fix len) }

      {mytype}
type
  Int16 = smallint;
  Int32 = integer;

  PInt16 = ^Int16;
  PInt32 = ^Int32;

type
  EULModException = class(Exception);

  TModuleProps = class;
  TModule = class;

  TModuleProp = class(TObject)
  private
    ULDP: TULDPObj;
    {
    FPropID: TModulePropID;
    FPropDesc: TModulePropDesc;
    FTypeDesc: TModulePropTypeDesc;
    FTypeID: TModulePropTypeID;
    FFlags: TModulePropFlags;  }

    FState: TModulePropState;
    FInPCValue: pointer;
      { value set in PC (nil if not set yet) }
    FInModValue: pointer;
      { value found in module (nil if not set yet) }
    {FDisplayedCount:longint;}
    FValSize:longint;
      { size of value in Module; set during assigning TypeDesc }
    FModuleProps: TModuleProps;
      { Owner }
    FUpdatingFromMod: boolean;
      { Set true just during SetModValStr to prevent creating of update
        request to device }
    FUpdatingFromProp: boolean;
      { Set true just during changing some value of ULDP from
        TModuleProp methods (SetPCValStr, UpdateTypeDesc,..)
        (prevent accepting cmULObjUpdate messages from ULDP by TModuleProp) }
    FOnChangeFromDevice: TNotifyEvent;
      { Called when value of the property was modified by incoming ulan message
        from device }
    FNextFromTimerUpdateTime: integer;
      { if updating from module periodically, then this field is set to the
        next time (in ms), when next update should be made
        (= mstime + FUpdateInterval) }
    FUpdateInterval: integer;
      { How often the property should be read from device. Updated
        by method UpdateUpdateInterval to either ULDP.UpdateInterval
        if this is > 0, or to ModulePropUpdateInterval
        (if ULDP.UpdateInterval = 0 and flag pfUpdateFromDeviceInTimer is set). }
    function UserToInternal(const UserVal: string; var InternalVal: string): boolean;
    function InternalToUser(const InternalVal: string; var UserVal: string): boolean;
    procedure UpdateUpdateInterval;
  protected
    function GetPCValStr: shortstring;
    procedure SetPCValStr(AVal: shortstring);
    function GetModValStr: shortstring;
    procedure SetModValStr(AVal: shortstring);
      function GetModValInt: integer;
      procedure SetModValInt(AVal: integer);
    procedure SetTypeDesc(const ADesc: TModulePropTypeDesc);
    procedure UpdateTypeDesc;
      { Set other properties according to ULDP.TypeDesc value, raise exception
        upon unknown typdesc }
    procedure SetValSize(ASize: longint);
    procedure SetModified(OnOff: boolean);
      { same as SetState(psModifiedFromPC, OnOff) }
    function IsModified: boolean;
      { same as IsStateOn(psModifiedFromPC) }

    procedure SetState(ps: TModulePropState; OnOff: boolean);
    function IsStateOn(ps: TModulePropState): boolean;
    function CheckValMem: boolean;
      { makes sure that Size bytes allocated for FInPCValue,
        FInModValue; call before every direct access to
        those; returns true if pointers non-nil (size <> 0) }
    {function ReadNeeded:boolean;
      { needs the property to be updated from module? }
    {function WriteNeeded:boolean;
      { needs the property to update the value in module? }

    function GetPropID: TModulePropID;
    procedure SetPropID(APropID: TModulePropID);
    function GetPropDesc: TModulePropDesc;
    procedure SetPropDesc(APropDesc: TModulePropDesc);
    function GetTypeDesc: TModulePropTypeDesc;

    function GetTypeID: TModulePropTypeID;
    procedure SetTypeID(ATypeID: TModulePropTypeID);
    function GetFlags: TModulePropFlags;
    procedure SetFlags(AFlags: TModulePropFlags);
    function GetUserValStr: shortstring;
    procedure SetUserValStr(const AVal: shortstring);
    procedure SetResult(mr: TModuleResult; const msg: string);
    function GetModule: TModule;
    procedure SetUpdateInterval(AInterval: integer);
    function GetUpdateInterval: integer;

    procedure DoOnChangeFromDevice;
    procedure WMAppMessage(var Msg:TMessage);message WM_APPMESSAGE;
    function IsTimeForUpdateFromTimer: boolean;
      { returns true if the property:
         a) has pfUpdateFromDeviceInTimer set and last time of update
            was at least UlanType.ModulePropUpdateInterval ms ago
         b) has UpdateInterval property set to > 0 value, then this
            value is used instead of UlanType.ModulePropUpateInterval }

  public
    procedure DoTimer;
    procedure RDRQWrite(ms: TMemoryStream);
      { Writes request bytes for RDRQ request (i.e. it's ID in two bytes)
        to the ms memory stream. }
    procedure RDRQWriteCond(ms: TMemoryStream);
      { Calls RDRQWrite only if necessary (called from DoTimer) }
    procedure RDRQRead(ms: TMemoryStream);
      { Reads answer to RDRQ request, i.e. it's new value, from ms memory
        stream (PropID was already read by Module) }
    procedure WRRQWrite(ms: TMemoryStream);
      { Writes its ID and the InPCValue to ms memory stream = the request for
        writing the value to the module. }
    procedure WRRQWriteCond(ms: TMemoryStream);
      { Calls WRRQWrite only if necessary (called from DoTimer) }

    constructor Create(AModuleProps: TModuleProps; APropID: TModulePropID); reintroduce;
    destructor Destroy; override;

    function IsFlagOn(pf: TModulePropFlags): boolean;
    procedure SetFlag(pf: TModulePropFlags; OnOff: boolean);

    function OnGetUsrValue(const AValue: string):string;
    function OnSetUsrValue(const AUsrValue: string):string;
      { handlers for ULDP fields objects (ValueInPC, ValueInDevice) }

    property ModuleProps: TModuleProps read FModuleProps;
    property Module: TModule read GetModule;
    property UpdateInterval: integer read GetUpdateInterval write SetUpdateInterval;

  published
    property PropID: TModulePropID read GetPropID write SetPropID;
    property PropDesc: TModulePropDesc read GetPropDesc write SetPropDesc;
    property TypeDesc: TModulePropTypeDesc read GetTypeDesc write SetTypeDesc;
    property TypeID: TModulePropTypeID read GetTypeID write SetTypeID;
    property Flags: TModulePropFlags read GetFlags write SetFlags;

    property PCValStr: shortstring read GetPCValStr write SetPCValStr;
      { Use to set new value of property entered in PC (e.g. in visual control),
        it will handle updating the value in the device. }
    property ModValStr: shortstring read GetModValStr write SetModValStr;
      { Use to set new value that came from device }
    property ModValInt: integer read GetModValInt write SetModValInt;
      { Uses ModValStr before/after conversion StrToInt/IntToStr }
    property UserValStr: shortstring read GetUserValStr write SetUserValStr;
      { Use to Set/Get value in user format (conversion takes place),
        useful for setting values of visual controls. Uses PCValStr
        before/after conversion from ulan internal format. }
    property ValSize: longint read FValSize write SetValSize;
      { Size of value in ulan device }
    property Modified: boolean read IsModified write SetModified;
      { True if property modified from PC and not updated in device yet. }
    property OnChangeFromDevice: TNotifyEvent read FOnChangeFromDevice
      write FOnChangeFromDevice;
  end;

  TModuleProps = class(TList)
    constructor Create(AModule: TModule);
    destructor Destroy;override;
    function AddProp(APropID:TModulePropID):TModuleProp;
    function FindProp(APropID:TModulePropID; var AProp:TModuleProp):boolean;
  private
    FModule: TModule;
  public
    property Module: TModule read FModule;
  end;


  TModules = class;
  TOIState = (osClosed, osCreate);

{TModule}
  TModule = class(TObject)
    ULDR: TULDRObj;
    {procedure ScanProperties;}
      { finds out what properties the module has
        (autodetection) }
    procedure UpdatePropertiesFromULDR;
      { Called from TModule.Create to create property objects
        from ULDR objects (loaded from file) }
  private
    FProps: TModuleProps;
      { module properties }
    {FAddr: TModuleAddr;
      { unique number of the module on the net }
    {FIDName: string;
      { string returned by uldrv as description of module }
    FWinPtr:TWinPtr;
    FModules:TModules;

    FOIState: TOIState;
    FOIDrv: TULDriver;
      { Opened if FOIState = osCreate }
    FOISndStream: TMemoryStream;
    FOICommand: integer;        //* service/cmd number for uLOI on target */
    FOIReplyCommand: byte;	//* service/cmd number for returned messages */
    FOISeqNr: byte;	        //* sequence counter */
    FOIReplySeqNr: byte;	//* sequence counter of target module */
    FOIOutFlags: integer;      //* flags used for outgoing messages */
    FOIStamp: integer;
      { stamp of last OI message sent = result of last OIMessageClose }
    FOIRcvStream: TMemoryStream;
    FOIRcvBuf: PByteArray;
    FOIRcvBufLen: integer;
    FActionList: TModuleActionList;
    FRDRQCount: integer;
      { How many properties will want to write RDRQ during next DoTimer }
    FWRRQCount: integer;
      { How many properties will want to write WRRQ during next DoTimer }
    FOnChangeFromDevice: TNotifyEvent;
      { Called if some propety was changed from device }
    FDetectState: TModuleDetectState;
      { information if the module was really detected (is present),
        see modutype .dsXXXX }
  protected
    procedure SetAddr(AAddr:TModuleAddr);
    function GetAddr: TModuleAddr;
    procedure SetIDName(AName: string);
    function GetIDName: string;
    procedure SetIDDesc(ADesc: string);
    function GetIDDesc: string;
    function GetPropCount:integer;
    procedure SetDeviceName(AName: TDeviceName);
    function GetDeviceName: TDeviceName;
    procedure SetDeviceType(AType: TDeviceType);
    function GetDeviceType: TDeviceType;
    procedure SetDeviceMode(AMode: TDeviceMode);
    function GetDeviceMode: TDeviceMode;
    function GetULDrv: TULDriver;
    procedure SetResult(mr: TModuleResult; const msg: string);
    procedure UpdateProperty(ReadOnly: boolean; APropID: TModulePropID;
     const desc: shortstring; const typdesc: shortstring);
      { called from OIHandlePropDesc }
    function GetShouldSendRDRQ: boolean;
    function GetShouldSendWRRQ: boolean;
    procedure SetShouldSendRDRQ(OnOff: boolean);
    procedure SetShouldSendWRRQ(OnOff: boolean);

    function GetUserValStr(APropID: TModulePropID): string;
    procedure SetUserValStr(APropID: TModulePropID; AVal: string);
    function GetUserValInt(APropID: TModulePropID): Integer;
    procedure SetUserValInt(APropID: TModulePropID; AVal: Integer);

    procedure CheckOIState(os: TOIState);
    {OI = Object Interface related methods}
    function OIMessageCreate: integer;
    function OIMessageWriteGetOIDList(AList: longint): integer;
      { Call between OIMessageCreate and OIMessageClose the fill data buffer
        with request for getting OID list }
    function OIMessageWriteGetOIDDesc(AList: longint; APropID: integer): integer;
      { Call between OIMessageCreate and OIMessageClose the fill data buffer
        with request for getting description of property with OID = APropID.
        Set ReadOnly=true for read only properties }
    {function OIMessageWriteGetPropVal(APropID: integer): integer;
      { Call between OIMessageCreate and OIMessageClose to fill data buffer with
        request for getting property value. }
    function OIMessageWriteBuf(const Buf; BufSize: integer):integer;
    function OIMessageWriteWord(W: word): integer;
    function OIMessageClose: integer;

    procedure OIMessageSendGetOIDList(AList: integer);
    procedure OIMessageSendGetOIDDesc(AList: integer; APropID: TModulePropID);
    procedure OIMessageSendGetPropVal(APropID: TModulePropID);
    procedure OIMessageSendSetPropVal(APropID: TModulePropID);
    procedure OIMessageSendGetPropVals;
      { send request for values of all readable properites from device }
    procedure OIMessageSendSetPropVals;
      { send request for setting values or all writable proprties to device }

    {function GetOIMessageSndLogString: string;
    function GetOIMessageRcvLogString: string;}

    function OIMessageReceive: integer;
      { Called from MessageReceive if ULDrv.Message.cmd = FOIReplyCommand,
        returns > 0 if data from the message read (handled the message OK)
        = number of read bytes. }

    procedure OIHandleRWPropIDList;
      { Work up the data in FOIRcvBuf from incoming message holding
        list of rw property IDs (submit actions for retrieving descriptions
        of individual properties) }
    procedure OIHandleROPropIDList;
      { Work up the data in FOIRcvBuf from incoming message holding
        list of rw property IDs (submit actions for retrieving descriptions
        of individual properties) }
    procedure OIHandlePropDesc(ReadOnly: boolean);
    procedure OIHandlePropVal;

    procedure OIRequestPropInfo;
      { Create module actions requesting info about all properties
        of the module }
    procedure OIRcvBufInit(ASize: integer);
      { Frees old FRcvBuf and FRcvStream, then creates new ones with
        given ASize (if ASize > 0). FRcvBuf = FRcvStream.Memory. }
    {/OI = Object Interface related methods}
    procedure WMAppMessage(var Msg:TMessage);message WM_APPMESSAGE;
  public
    {v0.14}
    constructor Create(AModules:TModules; AAddr: TModuleAddr; AULDR: TULDRObj);
      { if AAddr = 0 then AULDR must be non nil }
    {/v0.14
    constructor Create(AModules: TModules; AAddr: TModuleAddr);}

    destructor Destroy; override;
    function GetProp(Index: integer; var AProp: TModuleProp): boolean;
    function FindProp(APropID: TModulePropID; var AProp: TModuleProp): boolean;
    function FindOrAddProp(APropID: TModulePropID; var AProp: TModuleProp): boolean;

    {procedure DownloadPropVal(AProp: TModuleProp);}
      { just one }
    {procedure DownloadPropVals;}
      { all InModVals+PCVals will be updated by values from module }
    {procedure UploadPropVals;}
      { all needed PCVals will be uploaded to module }
    {procedure DoUploadPropValStr(APropID: TModulePropID;
      AValue: string; Convert: boolean);}
      { Set text value of property APropID and imediately
        send new value to the device; if Convert = true,
        then the text representation of the value is
        converted according to TypeDesc from user value
        (that is in AValue string) to internal value }
    {function DoDownloadPropVal(APropID: TModulePropID; Convert: boolean): string;}
      { Download immediately and get text value of the
        property APropID; if Convert = true, then also
        convert internal value to user value, that will
        be returned }
    procedure DoCommand(APropID: TModulePropID);
      { Send the command to the device as soon as possible }

    function MessageReceive: integer;
      { Called by Modules (from its DoTimer method) if message came with
        dadr = Addr of this module, ULDrv.MessageOpen was already called. }
    procedure DoAction(AAction: TModuleAction);
      { Send OI message according to info in AAction, called from DoTimer
        using actions retrieved from FActionList. }
    procedure DoTimer;
      { Called from Modules.DoTimer after all MessageReceive methods finished }
    procedure DoOnChangeFromDevice(AProp: TModuleProp);
      { Called when some property value was changed from device }

    property UserValStr[Index: TModulePropID]: string read GetUserValStr write SetUserValStr;
    property UserValInt[Index: TModulePropID]: integer read GetUserValInt write SetUserValInt;
    property Modules: TModules read FModules;
    property ULDrv: TULDriver read GetULDrv;
    property ShouldSendRDRQ: boolean read GetShouldSendRDRQ write SetShouldSendRDRQ;
    property ShouldSendWRRQ: boolean read GetShouldSendWRRQ write SetShouldSendWRRQ;
    property DetectState: TModuleDetectState read FDetectState write FDetectState;
    property OIState: TOIState read FOIState;
  published
    property Addr: TModuleAddr read GetAddr write SetAddr;
    property IDName: string read GetIDName write SetIDName;
    property IDDesc: string read GetIDDesc write SetIDDesc;
    property PropCount: integer read GetPropCount;
    property WinPtr: TWinPtr read FWinPtr write FWinPtr;
    property Props: TModuleProps read FProps;
    property DeviceType: TDeviceType read GetDeviceType write SetDeviceType;
    property DeviceMode: TDeviceMode read GetDeviceMode write SetDeviceMode;
    property DeviceName: TDeviceName read GetDeviceName write SetDeviceName;
    property OnChangeFromDevice: TNotifyEvent read FOnChangeFromDevice
      write FOnChangeFromDevice;
  end;

  TModules = class(TList)
  private
    FDeviceMode: TDeviceMode;
      { should be always dmUlan; only if the driver can not be
        initialized (in Windows95/98) set to something else,
        ULDrv will be then always nil  }
    FULDrv: TULDriver;
      { Used if DeviceMode = dmUlan }
    FScanningForModules: boolean;
    FScannedForModules: boolean;
    FIsInTimer: boolean;
    {v0.11}
    FULDrvFailed: boolean;
      { true if tried to init FULDrv but failed. Reset after calling
        setup dialog. }
    {/v0.11}
    {v0.14}
    FDestroying: boolean;
    {/v0.14}
  protected
    function GetULDrv: TULDriver;
    {procedure WMAppMessage(var Msg:TMessage);message WM_APPMESSAGE;}
  public
    ULF: TULFObj;
      { Top owner of all modules related ULObj objects }
    ULD: TULDObj;
      { Owner of module ULDR objects }
    constructor Create; reintroduce;
    function ScanForModules: integer;
      { If global DeviceMode = dmUlan, uses ULDrv to scan
        for modules and if found some calls UpdateModules.
        In other device mode ignored. }
    function GetModulesList(var AList: TStringList): boolean;
      { Fills AList with description lines of the modules
        (the same format as obtained during UpdateModules) }
    procedure UpdateModules(List: TStringList);
      { Inspect string list of autodetected modules
        (ulan driver), eventually create new module
        objects (or delete disappeared ones?) }
    procedure UpdateModulesFromULD;
      { update (i.e. create if not exist) modules using
        info in ULD object loaded from file }
    function FindModule(AAddr: TModuleAddr; var AModule: TModule): boolean;
    function FindModuleWithPropVal(pi: integer;
      AValue: String; var AModule: TModule): boolean;
      { Find the module that has property pi and value of this property
        is equal to AValue. If module with such pi not found, returns false
        and AModule = nil; if finds module with such pi, AModule will be <> nil,
        if does not have AValue, returns false. }
    function AddModule(AAddr: TModuleAddr{v0.14}; AULDR: TULDRObj{/v0.14}): TModule;
    destructor Destroy; override;
    procedure DoTimer;
      { Called from application timer periodically }
    procedure SendSetPropVals;
      { All modules will send request for setting values of all properties
        to device }
    procedure SendGetPropVals;
      { All modules will send request for getting values of all properties
        from device }
    {v0.14}
    procedure CheckDefaultModules;
      { Makes sure that there is at least one device with values:
       DeviceMode=dmApex, DeviceType=dtDetector, DeviceName=Apex Default, DevPortName=COM2 }
    {/v0.14}

    {v0.11}
    procedure SetResult(mr: TModuleResult; const msg:string);
    property ULDrvFailed: boolean read FULDrvFailed write FULDrvFailed;
    {/v0.11}
    property DeviceMode: TDeviceMode read FDeviceMode write FDeviceMode;
    property ULDrv: TULDriver read GetULDrv;
    property ScannedForModules: boolean read FScannedForModules;
  end;

{EXPORT}
{
procedure ModuleSetPropInt(AModule:TModule; mp:TModulePropID; AValue:longint);
function ModulesFindModule(AAddr:TModuleAddr; var AModule:TModule):boolean;
procedure ModulesUpdate(List:TStringList);
function ModuleFindOrAddProp(AModule:TModule; AID:TModulePropID; var AProp:TModuleProp):boolean;
}
  { find property of id = AID; if property of AID was not
    present yet, new property of this AID is added.
    Function returns false if no more space for property
    found or if Props = nil }
{procedure ModulesDone;}
{/EXPORT}

const
  Modules: TModules = nil;

const
  dmPropChange = 1;
  dmPropChangeFromDev = 2;
  dmAll = $FFFFFFFF;
  ModulesDebLogMask: DWORD = 0;{dmAll;}

implementation
{v0.14}
uses Channelsu, DevMode;
{/v0.14}

{TModuleProp}
constructor TModuleProp.Create(AModuleProps:TModuleProps; APropID : TModulePropID);
var f: TULObjField;
begin
  inherited Create;
  FModuleProps := TModuleProps(AModuleProps);
  ULDP := TULDPObj(
    TModule(TModuleProps(FModuleProps).Module).ULDR.FindOrAdd(ULDPID, IntToStr(APropID)));
  ULDP.SetFlag(rfCantDelete, true);
  {v0.06}
  f := ULDP.FindField(pnValueInPC);
  f.OnGetUsrValue := OnGetUsrValue;
  f.OnSetUsrValue := OnSetUsrValue;
  f := ULDP.FindField(pnValueInDevice);
  f.OnGetUsrValue := OnGetUsrValue;
  f.OnSetUsrValue := OnSetUsrValue;
  {/v.06}
  PropID := APropID;
  if IsFlagOn(pfRead) then
    SetState(psReadRequested, true);
  UpdateUpdateInterval;
  ULDP.UserRegister(Self);
end;

procedure TModuleProp.SetUpdateInterval(AInterval: integer);
begin
  FUpdatingFromProp := true;
  try
    ULDP.UpdateInterval := AInterval;
    UpdateUpdateInterval;
  finally
    FUpdatingFromProp := false;
  end;
end;

function TModuleProp.GetUpdateInterval: integer;
begin
  Result := FUpdateInterval;
end;

procedure TModuleProp.UpdateUpdateInterval;
begin
  if (ULDP.UpdateInterval > 0) then
    FUpdateInterval := ULDP.UpdateInterval
  else if IsFlagOn(pfUpdateFromDeviceInTimer) then
    FUpdateInterval := ModulePropUpdateInterval
  else
    FUpdateInterval := 0;
  if FUpdateInterval < 0 then
    FUpdateInterval := 0;

  if (FUpdateInterval > 0) then begin
    {v0.13}
    if (FUpdateInterval mod MinModulePropUpdateInterval) = 0 then begin
      FUpdateInterval := ((FUpdateInterval div MinModulePropUpdateInterval) + 1) * MinModulePropUpdateInterval;
    end;
    {/v0.13
    if (FUpdateInterval < MinModulePropUpdateInterval) then
      FUpdateInterval := MinModulePropUpdateInterval;
    }
  end;
end;

destructor TModuleProp.Destroy;
var
  i: integer;
  f: TULObjField;
begin
  ULDP.UserUnregister(Self);
  {v0.06}
  f := ULDP.FindField(pnValueInPC);
  f.OnGetUsrValue := nil;
  f.OnSetUsrValue := nil;
  f := ULDP.FindField(pnValueInDevice);
  f.OnGetUsrValue := nil;
  f.OnSetUsrValue := nil;
  {/v0.06}

  i := ModuleProps.IndexOf(Self);
  if i >= 0 then
    ModuleProps.Delete(i);
  inherited;
end;

procedure TModuleProp.SetTypeDesc(const ADesc:TModulePropTypeDesc);
begin
  if TypeDesc <> ADesc then begin
    FUpdatingFromProp := true;
    try
      ULDP.TypeDesc := ADesc;
    finally
      FUpdatingFromProp := false;
    end;
    UpdateTypeDesc;
  end;
end;

procedure TModuleProp.UpdateTypeDesc;
var
  td: string;
begin
  td := ULDP.TypeDesc;
  if td = 'e' then begin
    TypeID := ptCommand;
  end else begin
    if (pos('u2', td) = 1) then begin
      {(td = 'u2/*256') or
       (td = 'u2/.1') or
       (td = 'u2/.3')}
      TypeID := ptWord;
      {PCSize := 4;}
      ValSize := 2;
    end else if pos('f4', td) = 1 then begin
      TypeID := ptSingle;
      {PCSize := 4;}
      ValSize := 4;
    end else if pos('s2', td) = 1 then begin
      TypeID := ptInt16;
      ValSize := 2;
    end else if pos('s4', td) = 1 then begin
      TypeID := ptInt32;
      ValSize := 4;
    end else begin
      SetResult(mrInvalidTypeDesc, td);{modutype}
    end;
  end;
end;

function TModuleProp.IsFlagOn(pf: TModulePropFlags): boolean;
begin
  Result := (Flags and pf) = pf;
end;

procedure TModuleProp.SetFlag(pf: TModulePropFlags; OnOff: boolean);
begin
  if OnOff then begin
    Flags := Flags or pf
  end else begin
    Flags := (Flags and (not pf));
  end;
end;

function IsInteger(const s: string; var i:integer): boolean;
var code: integer;
begin
  Result := false;
  val(s, i, code);
  if code <> 0 then
    exit;
  Result := true;
end;

function IsFloat(const s: string; var f:extended): boolean;
var
  code: integer;
  st:string;
begin
  Result := false;
  st := trim(s);
  for code := 1 to length(st) do begin
    if st[code] = ',' then
      st[code] := '.';
  end;
  val(st, f, code);
  if code <> 0 then
    exit;
  Result := true;
end;

function TModuleProp.UserToInternal(const UserVal: string; var InternalVal: string): boolean;
var f: extended;{i: integer;}
begin
  Result := false;
  if TypeDesc = 'u2/*256' then begin
    if not IsFloat(UserVal, f) then
      exit;
    InternalVal := IntToStr(word(round(f * 256)));
  end else if TypeDesc = 'u2/.1' then begin
    if not IsFloat(UserVal, f) then
       exit;
     InternalVal := IntToStr(word(round(f * 10)));
  end else if TypeDesc = 'u2/.3' then begin
    if not IsFloat(UserVal, f) then
       exit;
     InternalVal := IntToStr(word(round(f * 1000)));
  end else
    InternalVal := UserVal;
  Result := true;
end;

function TModuleProp.InternalToUser(const InternalVal: string;
  var UserVal: string): boolean;
var i: integer;
begin
  Result := false;
  if TypeDesc = 'u2/*256' then begin
    if not IsInteger(InternalVal, i) then
      exit;
    UserVal := FloatToStrF(i / 256, ffFixed, 7, 2);
  end else if TypeDesc = 'u2/.1' then begin
    if not IsInteger(InternalVal, i) then
       exit;
    UserVal := FloatToStrF(i / 10, ffFixed, 7, 1);
  end else if TypeDesc = 'u2/.3' then begin
    if not IsInteger(InternalVal, i) then
      exit;
    UserVal := FloatToStrF(i / 1000, ffFixed, 7, 3);
  end else
    UserVal := InternalVal;
  Result := true;
end;

function TModuleProp.GetPropID: TModulePropID;
begin
  Result := StrToInt(ULDP.PropIDStr);
end;

procedure TModuleProp.SetPropID(APropID: TModulePropID);
begin
  FUpdatingFromProp := true;
  try
    ULDP.PropIDStr := IntToStr(APropID);
  finally
    FUpdatingFromProp := false;
  end;
end;

function TModuleProp.GetPropDesc: TModulePropDesc;
begin
  Result := ULDP.PropDesc;
end;

procedure TModuleProp.SetPropDesc(APropDesc: TModulePropDesc);
begin
  FUpdatingFromProp := true;
  try
    ULDP.PropDesc := APropDesc;
  finally
    FUpdatingFromProp := false;
  end;
end;

function TModuleProp.GetTypeDesc: TModulePropTypeDesc;
begin
  Result := ULDP.TypeDesc;
end;

function TModuleProp.GetTypeID: TModulePropTypeID;
begin
  Result := ULDP.TypeID;
end;

procedure TModuleProp.SetTypeID(ATypeID: TModulePropTypeID);
begin
  FUpdatingFromProp := true;
  try
    ULDP.TypeID := ATypeID;
  finally
    FUpdatingFromProp := false;
  end;
end;

function TModuleProp.GetFlags: TModulePropFlags;
begin
  Result := ULDP.PropFlags;
end;

procedure TModuleProp.SetFlags(AFlags: TModulePropFlags);
begin
  FUpdatingFromProp := true;
  try
    ULDP.PropFlags := AFlags;
  finally
    FUpdatingFromProp := false;
  end;
end;

function TModuleProp.CheckValMem:boolean;
begin
  CheckValMem := ((FValSize > 0) {and (FPCSize > 0)} and (FInPCValue <> nil)
    and (FInModValue <> nil)) or (TypeID = ptCommand);
end;
{
procedure TModuleProp.SetPCSize(ASize:longint);
begin
  if FPCSize <> ASize then begin
    if FInPCValue <> nil then
      FreeMem(FInPCValue);
    FPCSize := ASize;
    GetMem(FInPCValue, FPCSize);
    if FInPCValue <>  nil then
      FillChar(FInPCValue^, FPCSize, 0);
  end;
end;
}
procedure TModuleProp.SetValSize(ASize:longint);
begin
  if FValSize <> ASize then begin
    if FInModValue <> nil then
      FreeMem(FInModValue);
    if FInPCValue <> nil then
      FreeMem(FInPCValue);
    GetMem(FInPCValue, ASize);
    GetMem(FInModValue, ASize);
    FValSize := ASize;
    FillChar(FInPCValue^, FValSize, 0);
    FillChar(FInModValue^, FValSize, 0);
  end;
end;

function TModuleProp.GetUserValStr: shortstring;
var s: string;
begin
  if InternalToUser(PCValStr, s) then
    Result := s
  else
    Result := '';
end;

function TModuleProp.OnGetUsrValue(const AValue: string):string;
{ handler for ULDP field }
var
  s: string;
begin
  if InternalToUser(AValue, s) then
    Result := s
  else
    Result := AValue;
end;

procedure TModuleProp.SetUserValStr(const AVal:shortstring);
var s: string;
begin
  if UserToInternal(AVal, s) then
    PCValStr := s;
end;

function TModuleProp.OnSetUsrValue(const AUsrValue: string):string;
{ handler for ULDP fields }
var
  s: string;
begin
  if UserToInternal(AUsrValue, s) then
    Result := s
  else
    Result := AUsrValue;
end;

function TModuleProp.GetModValStr:shortstring;
begin
  GetModValStr := '';
  if FInModValue <> nil then begin
    case TypeID of
      ptWord: GetModValStr := IntToStr(PWord(FInModValue)^);
      ptSingle: GetModValStr := FloatToStr(PSingle(FInModValue)^);
      ptInt32: GetModValStr := IntToStr(PInt32(FInModValue)^);
      ptInt16: GetModValStr := IntToStr(PInt16(FInModValue)^);
    else
      GetModValStr := ULDP.ValueInDevice;
    end;
  end;
end;

procedure TModuleProp.SetModValStr(AVal:shortstring);
begin
  if not CheckValMem then
    exit;
  case TypeID of
    ptWord: PWord(FInModValue)^ := word(Stru .StrToInt(AVal));
    ptInt16: PInt16(FInModValue)^ := Int16(Stru. StrToInt(AVal));
    ptInt32: PInt32(FInModValue)^ := Int32(Stru. StrToInt(AVal));
    ptSingle: begin
      if AVal = '' then
        AVal := '0';
      PSingle(FInModValue)^ := StrToFloat(AVal);
    end;
  end;

  FUpdatingFromProp := true;
  try
    ULDP.ValueInDevice := AVal;
  finally
    FUpdatingFromProp := false;
  end;


  FUpdatingFromMod := true;
  PCValStr := AVal;
  FUpdatingFromMod := false;
  Modified := false;
  {v0.13}
  if FUpdateInterval > 0 then begin
    FNextFromTimerUpdateTime := (mstime div MinModulePropUpdateInterval) *
      MinModulePropUpdateInterval + FUpdateInterval;
  end;
  {/v0.13
  FNextFromTimerUpdateTime := mstime + FUpdateInterval;}
end;

function TModuleProp.GetModValInt: integer;
var i,code:integer;
begin
  val(ModValStr, i, code);
  GetModValInt := i;
end;

procedure TModuleProp.SetModValInt(AVal:integer);
begin
  if not CheckValMem then
    exit;
  SetModValStr(IntToStr(AVal));
end;

function TModuleProp.GetPCValStr:shortstring;
begin
  Result := ULDP.ValueInPC;
  if CheckValMem then begin
    case TypeID of
      ptWord: Result := IntToStr(PWord(FInPCValue)^);
      ptInt16: Result := IntToStr(PInt16(FInPCValue)^);
      ptInt32: Result := IntToStr(PInt32(FInPCValue)^);
      ptSingle: Result := FloatToStr(PSingle(FInPCValue)^);
    end;
  end;
end;

procedure TModuleProp.SetPCValStr(AVal:shortstring);
var
  l: word;
  f: single;
  i16:int16;
  i32:int32;
  changed:boolean;
begin
  if not CheckValMem then
    exit;
  {if not IsFlagOn(pfWrite) then begin
    exit;
  end;}
  FUpdatingFromProp := true;
  try
    changed := false;
    case TypeID of

      ptWord: begin
        l := word(Stru.StrToInt(AVal));
        if l <> PWord(FInPCValue)^ then begin
          PWord(FInPCValue)^ := l;
          changed := true;
        end;
      end;
      ptInt16: begin
        i16 := int16(Stru.StrToInt(AVal));
        if i16 <> PInt16(FInPCValue)^ then begin
          PInt16(FInPCValue)^ := i16;
          changed := true;
        end;
      end;
      ptInt32: begin
        i32 := Int32(Stru.StrToInt(AVal));
        if i32 <> PInt32(FInPCValue)^ then begin
          PInt32(FInPCValue)^ := i32;
          changed := true;
        end;
      end;

      ptSingle: begin
        if AVal = '' then
          AVal := '0';
        f := StrToFloat(AVal);
        if f <> PSingle(FInPCValue)^ then begin
          PSingle(FInPCValue)^ := f;
          changed := true;
        end;
      end;

      ptCommand: begin
        AVal := '0';
      end;

    end;

    if AVal <> ULDP.ValueInPC then begin

      changed := true;

      FUpdatingFromProp := true;
      try
        ULDP.ValueInPC := AVal;
      finally
        FUpdatingFromProp := false;
      end;

    end;

    if changed then begin
      if IsFlagOn(pfWrite) and (not FUpdatingFromMod) then begin
        Modified := true;
        {$IFDEF DEBUG}
        if (ModulesDebLogMask and dmPropChange) <> 0 then begin
          DebLog('ModProp ' + ULDP.PropDesc + ' changed: ' + AVal);
        end;
        {$ENDIF}
      end else begin
        {$IFDEF DEBUG}
        if (ModulesDebLogMask and dmPropChangeFromDev) <> 0 then begin
          DebLog('ModProp ' + ULDP.PropDesc + ' changed from dev.: ' + AVal);
        end;
        {$ENDIF}
      end;
    end;
  finally
    FUpdatingFromProp := false;
  end;
end;

procedure TModuleProp.WMAppMessage(var Msg:TMessage);
begin
  case Msg.wParam of
    cmULObjUpdated: begin
      if TULDPObj(Msg.lParam) = ULDP then begin
        if (not FUpdatingFromProp) and (not FUpdatingFromMod) then begin
          UpdateUpdateInterval;
          PCValStr := ULDP.ValueInPC;
        end;
      end;
    end;
    cmULObjDestroyed: begin
      if TULDPObj(Msg.lParam) = ULDP then begin
        Free;
      end;
    end;
  end;
end;

procedure TModuleProp.RDRQWrite(ms: TMemoryStream);
var
  w: word;
begin
{  if ReadNeeded then begin}
  w := PropID;
  ms.Write(w, sizeof(w));
  SetState(psReadSubmitted, true);
{  end;}
end;

procedure TModuleProp.DoTimer;
begin
  if FUpdateInterval > 0 then begin
    if (mstime >= FNextFromTimerUpdateTime) and
       (not IsStateOn(psReadSubmitted))
    then
      Module.ShouldSendRDRQ := true;
  end;
end;

function TModuleProp.IsTimeForUpdateFromTimer: boolean;
begin
  Result := false;
  if FUpdateInterval > 0 then begin
    if mstime >= FNextFromTimerUpdateTime then
      Result := true;
  end;
end;

procedure TModuleProp.RDRQWriteCond(ms: TMemoryStream);
begin
  if IsFlagOn(pfRead) and (not IsStateOn(psReadSubmitted)) then begin
    if IsTimeForUpdateFromTimer or
       IsStateOn(psReadRequested) then
    begin
      RDRQWrite(ms);
    end;
  end;
end;

procedure TModuleProp.WRRQWrite(ms: TMemoryStream);
var
  w: word;
begin
  case TypeID of
    ptCommand: begin
      w := PropID;
      ms.Write(w, sizeof(w));
    end;
  else
    w := PropID;
    ms.Write(w, sizeof(w));
    ms.Write(FInPCValue^, FValSize);
  end;
  if IsFlagOn(pfRead) then begin
    { i.e. we can read the value back (find out if submitted write request
      succeded):}
    SetState(psWriteSubmitted, true);
    SetState(psReadRequested, true);
      { the following call RDRQWriteCond will include request for value
        of this property in the device }
  end;
end;

procedure TModuleProp.WRRQWriteCond(ms: TMemoryStream);
begin
  if IsFlagOn(pfWrite) and Modified then begin
    if (not IsStateOn(psWriteSubmitted)) then
    begin
      WRRQWrite(ms);
    end else begin
      {check for timeout}
    end;
  end;
end;

procedure TModuleProp.RDRQRead(ms:TMemoryStream);
begin
  ms.Read(FInModValue^, FValSize);
  ModValStr := ModValStr;
  SetState(psReadSubmitted, false);
  SetState(psWriteSubmitted, false);
end;

{function TModuleProp.ReadNeeded:boolean;
begin
  ReadNeeded := (TypeDesc <> '') and
    (FInModValue <> nil) and
    (FValSize > 0) and
    ((Flags and pfRead) <> 0);
end;

function TModuleProp.WriteNeeded:boolean;
begin
  WriteNeeded := (TypeDesc <> '') and ((Flags and pfWrite) <> 0)
    and Modified;
end;
}

procedure TModuleProp.SetState(ps: TModulePropState; OnOff: boolean);
begin
  if OnOff then begin
    if (ps and psModifiedFromPC) <> 0 then begin
      if Module.DeviceMode = dmUlan then begin
        if IsFlagOn(pfUpdateToDeviceInTimer) or (Module.OIState <> osClosed) then begin
          Module.ShouldSendWRRQ := true;
          Module.ShouldSendRDRQ := true;
        end else begin
          Module.OIMessageSendSetPropVal(PropID);
        end;
      end;
    end;

    if (ps and psReadRequested) <> 0 then begin
      if not IsFlagOn(pfRead) then begin
        SetResult(mrPropIsWriteOnly, ULDP.PropIDStr);
      end;
      Module.ShouldSendRDRQ := true;
    end;

    if (ps and psReadSubmitted) <> 0 then begin
      SetState(psReadRequested, false);
      {FState := FState and (not psReadRequested);}
    end;

    FState := FState or ps

  end else begin

    FState := FState and (not ps);

  end;
end;

function TModuleProp.IsStateOn(ps: TModulePropState): boolean;
begin
  Result := (FState and ps) = ps;
end;

procedure TModuleProp.SetModified(OnOff:boolean);
begin
  SetState(psModifiedFromPC, OnOff);
end;

function TModuleProp.IsModified:boolean;
begin
  IsModified := IsStateOn(psModifiedFromPC);{(FState and psModifiedFromPC) <> 0;}
end;

procedure TModuleProp.SetResult(mr: TModuleResult; const msg: string);
var s:string;
begin
  if mr <> mrOK then begin
    s := 'TModuleProp ' + IntToStr(mr) + ' ' + msg;
    SysLogLog(leError, s);
    raise EULModException.Create(s);
  end;
end;

function TModuleProp.GetModule: TModule;
begin
  Result := FModuleProps.Module;
end;

procedure TModuleProp.DoOnChangeFromDevice;
begin
  if Assigned(FOnChangeFromDevice) then
    FOnChangeFromDevice(Self)
  else
    Module.DoOnChangeFromDevice(Self);
end;
{/TModuleProp}

{TModuleProps}
constructor TModuleProps.Create(AModule: TModule);
begin
  inherited Create;
  FModule := AModule;
end;

function TModuleProps.AddProp(APropID:TModulePropID):TModuleProp;
var m:TModuleProp;
begin
  m := TModuleProp.Create(Self, APropID);
  if m <> nil then begin
    if Add(m) < 0 then begin
      m.Free;
      m := nil;
    end;
  end;
  AddProp := m;
end;

function TModuleProps.FindProp(APropID:TModulePropID; var AProp:TModuleProp):boolean;
var j:integer;
begin
  FindProp := false;
  for j := 0 to Count - 1 do begin
    if TModuleProp(Items[j]).PropID = APropID then begin
      FindProp := true;
      AProp := TModuleProp(Items[j]);
      exit;
    end;
  end;
end;

destructor TModuleProps.Destroy;
{var i:integer;}
begin
  while Count > 0 do
    TModuleProp(Items[0]).Free;
{ if Count > 0 then begin
    for i := 0 to Count - 1 do begin
      TModuleProp(Items[i]).Free;
    end;
  end;}
  inherited Destroy;
end;

{/TModuleProps}

{TModule}
constructor TModule.Create(AModules:TModules; AAddr: TModuleAddr{v0.14}; AULDR: TULDRObj{/v0.14});
begin
  inherited Create;
  FModules := AModules;
  FProps := TModuleProps.Create(Self);
  {v0.14}
  if AULDR <> nil then begin
    ULDR := AULDR;
    AAddr := StrToInt(ULDR.AddrStr);
  end else
  {/v0.14}
  begin
    ULDR := TULDRObj(FModules.ULD.FindOrAdd(ULDRID, IntToStr(AAddr)));
  end;
  ULDR.SetFlag(rfCantDelete, true);
  UpdatePropertiesFromULDR;
  Addr := AAddr;
  FOIState := osClosed;
  FOIDrv := nil;
  FOICommand := $10;            //* service/cmd number for uLOI on target */
  FOIReplyCommand := $11;       //* service/cmd number for returned messages */
  FOISeqNr := 0;                //* sequence counter */
  FOIReplySeqNr := 0;           //* sequence counter of target module */
  FOIOutFlags := 0;             //* flags used for outgoing messages */

  FActionList := TModuleActionList.Create;
  ULDR.UserRegister(Self);
end;

procedure TModule.UpdatePropertiesFromULDR;
var
  i: integer;
  uldp: TULDPObj;
  p: TModuleProp;
begin
  for i := 0 to ULDR.ChildCount - 1 do begin
    uldp := TULDPObj(ULDR.Childs[i]);
    if FindOrAddProp(StrToInt(uldp.PropIDStr), p) then
      p.UpdateTypeDesc;
  end;
end;

destructor TModule.Destroy;
var i: integer;
begin
  ULDR.UserUnregister(Self);
  i := Modules.IndexOf(Self);
  if i >= 0 then
    Modules.Delete(i);
  OIMessageClose;
  Props.Free;
  FActionList.Free;
  OIRcvBufInit(0);
  FOISndStream.Free;
  inherited Destroy;
end;

{
procedure TModule.DownloadPropVal(AProp:TModuleProp);
var
  coninfo:Puloi_coninfo;
  val:longint;
begin
  coninfo := uloi_open(ul_dev_name, Addr, $10, 0, 10);
  if coninfo <> nil then begin
    if uloi_get_var(coninfo, AProp.PropID, @val, sizeof(val)) >= 0 then begin
      AProp.SetModValStr(IntToStr(val));
    end;
    uloi_close(coninfo);
  end;
end;


procedure TModule.DownloadPropVals;
var
  coninfo:Puloi_coninfo;
  ms:TMemoryStream;
  i:integer;

  bufout:PByteBuffer;
  lenout:integer;

  headout:array[0..4] of byte;
  tailout:array[0..1] of byte;

  headin: array[0..4] of byte;
begin
  if Props.Count = 0 then
    exit;
  coninfo := uloi_open(ul_dev_name, Addr, $10, 0, 10);
  if coninfo <> nil then begin
    ms := TMemoryStream.Create;
    try
      FillChar(headout, sizeof(headout), 0);
      headout[3] := ULOI_RDRQ;
      headout[4] := 0;
      ms.Write(headout, sizeof(headout));

      for i := 0 to Props.Count - 1 do begin
        TModuleProp(Props.Items[i]).RDRQWrite(ms);
      end;
      FillChar(tailout, sizeof(tailout), 0);
      ms.Write(tailout, sizeof(tailout));

      bufout := nil;
      if uloi_transfer( coninfo, ms.memory, ms.size, bufout, lenout) > 0 then
      begin
        ms.Clear;
        ms.Write(bufout^, lenout);
        FreeMem(bufout);

        ms.Seek(0, soFromBeginning);
        ms.Read(headin, sizeof(headin));
        if (headin[3] = 21) and (headin[4] = 0) then begin
          for i := 0 to Props.Count - 1 do begin
            TModuleProp(Props.Items[i]).RDRQRead(ms);
          end;
        end;
        ms.Clear;
      end;
    finally
      uloi_close(coninfo);
      ms.Free;
    end;
  end;
end;


procedure TModule.DoDownloadAllPropVals;
var
  i: integer;
  p: TModuleProp;
begin
  if Props.Count = 0 then
    exit;
  for i := 0 to Props.Count - 1 do begin
    p := TModuleProp(Props.Items[i]);
    case p.TypeID of
      ptCommand: ;
    else
      p.Mo

  UploadPropVals;
end;


procedure TModule.UploadPropVals;
var
  coninfo:Puloi_coninfo;
  ms:TMemoryStream;
  i:integer;

  bufout:PByteBuffer;
  lenout:integer;

  headout:array[0..2] of byte;
begin
  if Props.Count = 0 then
    exit;
  coninfo := uloi_open(ul_dev_name, Addr, $10, 0, 1);
  if coninfo <> nil then begin
    ms := TMemoryStream.Create;
    try
      FillChar(headout, sizeof(headout), 0);
      ms.Write(headout, sizeof(headout));
      for i := 0 to Props.Count - 1 do begin
        TModuleProp(Props.Items[i]).WRRQWrite(ms);
      end;
      bufout := nil;
      if uloi_transfer( coninfo, ms.memory, ms.size, bufout, lenout) > 0 then
      begin
        ms.Clear;
      end;
    finally
      uloi_close(coninfo);
      ms.Free;
    end;
  end;
end;

procedure TModule.DoUploadPropValStr(APropID: TModulePropID;
  AValue: string; Convert: boolean);
var
  p: TModuleProp;
begin
  if FindProp(APropID, p) then begin
    if Convert then
      p.UserValStr := AValue
    else
      p.PCValStr := AValue;
    UploadPropVals;
  end;
end;

function TModule.DoDownloadPropVal(APropID: TModulePropID; Convert: boolean): string;
var p: TModuleProp;
begin
  Result := '';
  if FindProp(APropID, p) then begin
    DownloadPropVal(p);
    if Convert then
      Result := p.UserValStr
    else
      Result := p.PCValStr;
  end;
end;
}
function TModule.GetUserValStr(APropID: TModulePropID): string;
var p: TModuleProp;
begin
  Result := '';
  if FindProp(APropID, p) then
    Result := p.UserValStr;
end;

procedure TModule.SetUserValStr(APropID: TModulePropID; AVal: string);
var p: TModuleProp;
begin
  if FindProp(APropID, p) then
    p.UserValStr := AVal;
end;

function TModule.GetUserValInt(APropID: TModulePropID): Integer;
var i, code: integer;
begin
  val(UserValStr[APropID], i, code);
  Result := i;
end;

procedure TModule.SetUserValInt(APropID: TModulePropID; AVal: Integer);
begin
  UserValStr[APropID] := IntToStr(AVal);
end;


procedure TModule.DoCommand(APropID: TModulePropID);
var p: TModuleProp;
begin
  if FindProp(APropID, p) then begin
    p.Modified := true;
    {UploadPropVals;}
  end;
end;

(*
procedure TModule.ScanProperties;
var
  sp: TScanParams;
  vo, ty: PByteBuffer;
{ i:integer;
  p:TModuleProp; }
begin
  sp.Module := Addr;
  InfoFormShow('Detecting Module properties');
  try
    oi_var_test(sp, nil, nil, vo, ty, Self);
    DownloadPropVals;
{  for i := 0 to Props.Count r- 1 do begin
    p := TModuleProp(Props.Items[i]);
    if ((p.Flags and pfRead) <> 0) and (p.TypeDesc <> '') then
      DownloadPropVal(p);
  end;}
  finally
    InfoFormHide;
  end;
end;
*)
{procedure TModule.DownloadPropValues;
begin

end;}

procedure TModule.SetIDName(AName: string);
begin
  ULDR.IDName := AName;
  {v0.14}
  if DeviceName = '' then
    DeviceName := AName;
end;

function TModule.GetIDName: string;
begin
  Result := ULDR.IDName;
end;

procedure TModule.SetIDDesc(ADesc: string);
begin
  ULDR.IDDesc := ADesc;
end;

function TModule.GetIDDesc: string;
begin
  Result := ULDR.IDDesc;
end;

procedure TModule.SetAddr(AAddr:TModuleAddr);
begin
  ULDR.AddrStr := IntToStr(AAddr);
end;

function TModule.GetAddr: TModuleAddr;
begin
  Result := StrToInt(ULDR.AddrStr);
end;

procedure TModule.SetDeviceName(AName: TDeviceName);
begin
  ULDR.DeviceName := AName;
end;

function TModule.GetDeviceName: TDeviceName;
begin
  Result := ULDR.DeviceName;
end;

procedure TModule.SetDeviceType(AType: TDeviceType);
begin
  ULDR.DeviceType := AType;
end;

function TModule.GetDeviceType: TDeviceType;
begin
  Result := ULDR.DeviceType;
end;

procedure TModule.SetDeviceMode(AMode: TDeviceMode);
begin
  ULDR.DeviceMode := AMode;
end;

function TModule.GetDeviceMode: TDeviceMode;
begin
  Result := ULDR.DeviceMode;
end;

function TModule.FindProp(APropID:TModulePropID; var AProp:TModuleProp):boolean;
begin
  FindProp := Props.FindProp(APropID, AProp);
end;

function TModule.FindOrAddProp(APropID:TModulePropID; var AProp:TModuleProp):boolean;
begin
  FindOrAddProp := true;
  if not FindProp(APropID, AProp) then begin
    AProp := TModuleProp(Props.AddProp(APropID));
  end;
end;

function TModule.GetPropCount:integer;
begin
  GetPropCount := Props.Count;
end;

function TModule.GetProp(Index:integer; var AProp:TModuleProp):boolean;
begin
  if (Index >= 0) and (Index < Props.Count) then begin
    AProp := TModuleProp(Props.Items[Index]);
    GetProp := true;
  end else begin
    GetProp := false;
  end;
end;

function TModule.GetULDrv: TULDriver;
begin
  Result := Modules.ULDrv;
end;

procedure TModule.CheckOIState(os: TOIState);
begin
  if os <> FOIState then begin
    SetResult(mrInvalidOIState, '');
  end;
end;

{OIMessage}
function TModule.OIMessageCreate: integer;
begin
  Result := 0;
  if ULDrv = nil then begin
    Result := -1;
    exit;{not initialized driver}
  end;
  CheckOIState(osClosed);
  FOIDrv := ULDrv.Clone;
  FOIState := osCreate;
  ULDrv.FilterAdd(Addr, FOIReplyCommand);
{  Result := FOIDrv.MessageCreate(Addr, FOICommand, FOIOutFlags);}
  FOISndStream := TMemoryStream.Create;
  FOIDrv.MessageCreate(Addr, FOICommand, FOIOutFlags and (not UL_TAILMSG));

  FOISndStream.WriteBuffer(FOIReplyCommand, 1);
  { inc(FOISeqNr);
    bufin[1] := (FOISeqNr and $3F) + $40; }
  FOISndStream.WriteBuffer(FOISeqNr, 1);
  FOISndStream.WriteBuffer(FOIReplySeqNr, 1);
end;

function TModule.OIMessageWriteGetOIDList(AList: longint): integer;
var
  at_once: longint;
  last_oid: integer;
begin
  Result := 0;
  at_once := 64;
  last_oid := 0;
  FOISndStream.WriteBuffer(AList, 2);
  FOISndStream.WriteBuffer(last_oid, 2);
  FOISndStream.WriteBuffer(at_once, 2);
end;

function TModule.OIMessageWriteGetOIDDesc(AList: longint; APropID: integer): integer;
var
  stop:word;
begin
  stop := 0;
  FOISndStream.WriteBuffer(AList, 2);
  FOISndStream.WriteBuffer(APropID, 2);
  FOISndStream.WriteBuffer(stop, 2);
  Result := 0;
end;

{function TModule.OIMessageWriteGetPropVal(APropID: integer): integer;
begin
  FOISndStream.WriteBuffer(APropID, 2);
end;}

function TModule.OIMessageWriteWord(W: word): integer;
begin
  FOISndStream.WriteBuffer(W, 2);
  Result := 0;
end;



{
function TModule.OIMessageOpen: integer;
begin
  Result := 0;
  CheckOIState(osClosed);
  FOIDrv := ULDrv.Clone;
  Result := FOIDrv.MessageOpen;
end;
}

function TModule.OIMessageWriteBuf(const Buf; BufSize: integer): integer;
begin
  CheckOIState(osCreate);
  Result := 0;
  FOISndStream.WriteBuffer(Buf, BufSize);
  {FOIDrv.MessageWriteBuf(Buf, BufSize);}
end;

function TModule.OIMessageClose: integer;
begin
  Result := 0;
  if FOIDrv = nil then
    exit;
  CheckOIState(osCreate);
  FOIDrv.MessageWriteBuf(FOISndStream.Memory^, FOISndStream.Size);
  FOIStamp := FOIDrv.MessageClose;
  Result := FOIStamp;
  {ModLog(GetOIMessageLogString);}
  FOIDrv.Free;
  FOIDrv := nil;
  FOISndStream.Free;
  FOISndStream := nil;
  FOIState := osClosed;
end;

{
function TModule.GetOIMessageLogString: string;
var
  s: string;
  b: byte;
begin
  FOISndStream.Position := 0;
  s := '';
  while FOISndStream.Position < FOISndStream.Size do begin
    FOISndStream.ReadBuffer(b, 1);
    s  := s + ByteToHex(b);
  end;
  Result := s;
end;
}
procedure TModule.OIMessageSendGetOIDList(AList: integer);
begin
  if OIMessageCreate = 0 then begin
    OIMessageWriteGetOIDList(AList);
    OIMessageClose;
  end;
end;

procedure TModule.OIMessageSendGetOIDDesc(AList: integer; APropID: TModulePropID);
begin
  if OIMessageCreate = 0 then begin
    OIMessageWriteGetOIDDesc(AList, APropID);
    OIMessageClose;
  end;
end;

procedure TModule.OIMessageSendGetPropVal(APropID: TModulePropID);
var p: TModuleProp;
begin
  if FindProp(APropID, p) then begin
    if OIMessageCreate = 0 then begin
      OIMessageWriteWord(ULOI_RDRQ);
      p.RDRQWrite(FOISndStream);
      {OIMessageWriteWord(APropID);}
      OIMessageWriteWord(0);
      OIMessageClose;
    end;
  end;
end;


procedure TModule.OIMessageSendSetPropVal(APropID: TModulePropID);
var p: TModuleProp;
begin
  if FindProp(APropID, p) then begin
    if p.IsFlagOn(pfWrite) then begin
      if OIMessageCreate = 0 then begin
        p.WRRQWrite(FOISndStream);
        if p.IsFlagOn(pfRead) then begin
          OIMessageWriteWord(ULOI_RDRQ);
          p.RDRQWrite(FOISndStream);
        end;
        OIMessageWriteWord(0);
        OIMessageClose;
      end;
    end;
  end;
end;

procedure TModule.OIMessageSendSetPropVals;
var
  p: TModuleProp;
  i: integer;
begin
  if OIMessageCreate = 0 then begin
    for i := 0 to Props.Count - 1 do begin
      p := TModuleProp(Props[i]);
      if p.IsFlagOn(pfWrite) then begin
        p.WRRQWrite(FOISndStream);
      end;
    end;
    OIMessageWriteWord(ULOI_RDRQ);
    for i := 0 to Props.Count - 1 do begin
      p := TModuleProp(Props[i]);
      if p.IsFlagOn(pfRead+pfWrite) then begin
        p.RDRQWrite(FOISndStream);
      end;
    end;
    OIMessageWriteWord(0);
    OIMessageClose;
  end;
end;


procedure TModule.OIMessageSendGetPropVals;
var
  p: TModuleProp;
  i: integer;
begin
  if OIMessageCreate = 0 then begin
    OIMessageWriteWord(ULOI_RDRQ);
    for i := 0 to Props.Count - 1 do begin
      p := TModuleProp(Props[i]);
      if p.IsFlagOn(pfRead) then begin
        p.RDRQWrite(FOISndStream);
      end;
    end;
    OIMessageWriteWord(0);
    OIMessageClose;
  end;
end;

{/OIMessage}

procedure TModule.OIRequestPropInfo;
begin
  FActionList.AddAction(maGetRWPropIDList, 1, 0);
  FActionList.AddAction(maGetROPropIDList, 1, 0);
end;

function TModule.OIMessageReceive: integer;
begin
  Result := 0;
  {v0.11}
  if ULDrv = nil then
    exit;
  {/v0.11}
  if ULDrv.Message.len < 3 then begin
    exit;
  end;
  OIRcvBufInit(ULDrv.Message.len);

  ULDrv.MessageReadBuf(FOIRcvBuf^, FOIRcvBufLen);
  FOIRcvStream.Position := 0;
  FOIRcvStream.WriteBuffer(FOIRcvBuf^, FOIRcvBufLen);
  case FOIRcvBuf^[3] of
    ULOI_QOII+1: OIHandleRWPropIDList;
    ULOI_QOIO+1: OIHandleROPropIDList;
    ULOI_DOII+1: OIHandlePropDesc(false);
    ULOI_DOIO+1: OIHandlePropDesc(true);
    ULOI_RDRQ+1: OIHandlePropVal;
  end;

  {
  if((FOIRcvBuf^[2] and $3F) = (FOISeqNr and $3F)) then begin
    FOIReplySeqNr := FOIRcvBuf^[2];
  end else begin
    error
  end;
  }
  Result := FOIRcvBufLen;
end;

procedure TModule.OIRcvBufInit(ASize: integer);
begin
  if ASize <> FOIRcvBufLen then begin
    ClassFree(FOIRcvStream);
    MemFree(FOIRcvBuf);
    FOIRcvBufLen := 0;

    if ASize > 0 then begin
      try
        GetMem(FOIRcvBuf, ASize);
        FOIRcvStream := TMemoryStream.Create;
        FOIRcvStream.SetSize(ASize);
        FOIRcvBufLen := ASize;
      except
        on EOutOfMemory do begin
          MemFree(FOIRcvBuf);
          ClassFree(FOIRcvStream);
          raise;
        end;
      else
        raise;
      end;
    end;
  end;
  if FOIRcvStream <> nil then
    FOIRcvStream.Position := 0;
end;

{OIHandle}
procedure TModule.OIHandleRWPropIDList;
var
  IDList: PWordArray;
  i, cnt: Integer;
begin
  IDList := @FOIRcvBuf^[5];
  cnt := (FOIRcvBufLen - 5) div 2;
  for i := 0 to cnt - 1 do begin
    FActionList.AddAction(maGetRWPropIDDesc, 1, IDList^[i]);
  end;
end;

procedure TModule.OIHandleROPropIDList;
var
  IDList: PWordArray;
  i, cnt: Integer;
begin
  IDList := @FOIRcvBuf^[5];
  cnt := (FOIRcvBufLen - 5) div 2;
  for i := 0 to cnt - 1 do begin
    FActionList.AddAction(maGetROPropIDDesc, 1, IDList^[i]);
  end;
end;

procedure TModule.OIHandlePropDesc(ReadOnly: boolean);
var
  oid: word;
  name: shortstring;
  typdesc: shortstring;
  len, remains: integer;
  dlen: byte;

  function CalcRemains: boolean;
  begin
    remains := FOIRcvStream.Size - FOIRcvStream.Position;
    if (remains = 0) then begin
      if len > 0 then
        SetResult(mrInvalidPropDesc, 'OIHandlePropDesc');
      Result := false;
    end else begin
      Result := true;
      if len > remains then
       len := remains;
    end;
  end;

begin
  if (FOIRcvBufLen < 3 + 2 + 2 + 1) then begin
    {FreeMem(bufout);
    Result := -1;}
    exit;
  end;
  FOIRcvStream.Seek(5, soFromBeginning);
  FOIRcvStream.ReadBuffer(oid, 2);
  FOIRcvStream.ReadBuffer(dlen, 1);

  name := '';
  typdesc := '';
  FOIRcvStream.ReadBuffer(name, 1);
  len := length(name);
  if CalcRemains then begin
    if len > 0 then
      FOIRcvStream.ReadBuffer(name[1], len);
    len := 0;
    if CalcRemains then begin
      FOIRcvStream.ReadBuffer(typdesc, 1);
      len := length(typdesc);
      if CalcRemains then begin
        FOIRcvStream.ReadBuffer(typdesc[1], len);
      end;
    end;
  end;
  UpdateProperty(ReadOnly, oid, name, typdesc);
end;

procedure TModule.OIHandlePropVal;
var
  APropID: TModulePropID;{modutype}
  p: TModuleProp;
begin
  FOIRcvStream.Seek(5, soFromBeginning);
  ULDR.DoChangeLock;
  try
    repeat
      FOIRcvStream.ReadBuffer(APropID, sizeof(APropID));
      if APropID = 0 then
        break;
      if FindProp(APropID, p) then begin
        p.RDRQRead(FOIRcvStream);
      end else begin
        {FOIRcvStream.ReadBuffer(val, 2);}
      end;
    until FOIRcvStream.Position >= FOIRcvStream.Size;
  finally
    ULDR.DoChangeUnlock;
  end;
end;
{/OIHandle}

procedure TModule.UpdateProperty(ReadOnly: boolean; APropID: TModulePropID;
  const desc: shortstring; const typdesc: shortstring);
var
  prop: TModuleProp;
begin
  if typdesc <> '' then begin
    if FindOrAddProp(APropID, prop) then begin
      Prop.PropID := APropID;
      Prop.PropDesc := desc;
      Prop.TypeDesc := typdesc;
      if ReadOnly then
        Prop.Flags := Prop.Flags or pfRead
      else begin
        Prop.Flags := Prop.Flags or pfWrite;
        {if Prop.TypeID <> ptCommand then
          Prop.Flags := Prop.Flags or pfRead;}
      end;
      FActionList.AddAction(maGetPropVal, 1, longint(Prop));
    end;
  end;
end;

procedure TModule.DoAction(AAction: TModuleAction);
begin
  if DeviceMode <> dmUlan then
    exit;
    
  case AAction.ActionID of

    maGetRWPropIDList: begin
      OIMessageSendGetOIDList(ULOI_QOII);
    end;

    maGetROPropIDList: begin
      OIMessageSendGetOIDList(ULOI_QOIO);
    end;

    maGetROPropIDDesc: begin
      OIMessageSendGetOIDDesc(ULOI_DOIO, AAction.Info);
    end;

    maGetRWPropIDDesc: begin
      OIMessageSendGetOIDDesc(ULOI_DOII, AAction.Info);
    end;

    maGetPropVal: begin
      OIMessageSendGetPropVal(TModuleProp(AAction.Info).PropID);
    end;

    maSetPropVal: begin
      OIMessageSendSetPropVal(TModuleProp(AAction.Info).PropID);
    end;

    maGetPropVals: begin
      OIMessageSendGetPropVals;
    end;

    maSetPropVals: begin
      OIMessageSendSetPropVals;
    end;
  end;
end;

procedure TModule.DoTimer;
var
  a: TModuleAction;
  i, p: integer;
  prop: TModuleProp;
begin
  if (FDetectState <> dsPresent) or (OIState <> osClosed) then
    exit;
  repeat
    if FActionList.GetAction(a) then begin
      DoAction(a);
      p := a.Priority;
      a.Free;
      if p = 0 then
        break;
    end else
      break;
  until false;
  for i := 0 to Props.Count - 1 do begin
    TModuleProp(Props[i]).DoTimer;
  end;
  if ShouldSendWRRQ then begin
    ShouldSendWRRQ := false;
    if OIMessageCreate = 0 then begin
      for i := 0 to Props.Count - 1 do begin
        prop := TModuleProp(Props[i]);
        prop.WRRQWriteCond(FOISndStream);
      end;
      OIMessageWriteWord(0);
      OIMessageClose;
    end;
  end;
  if ShouldSendRDRQ then begin
    ShouldSendRDRQ := false;
    if OIMessageCreate = 0 then begin
      OIMessageWriteWord(ULOI_RDRQ);
      for i := 0 to Props.Count - 1 do begin
        prop := TModuleProp(Props[i]);
        prop.RDRQWriteCond(FOISndStream);
      end;
      OIMessageWriteWord(0);
      OIMessageClose;
    end;
  end;
end;

function TModule.MessageReceive: integer;
begin
  Result := 0;
  {v0.11}
  if ULDrv = nil then
    exit;
  {/v0.11}
  if ULDrv.Message.cmd = FOIReplyCommand then begin
    Result := OIMessageReceive;
  end else begin

  end;
end;

procedure TModule.SetResult(mr: TModuleResult; const msg: string);
var s: string;
begin
  case mr of
    mrOK:;
  else
    s := 'TModule ' + IntToStr(mr) + ' ' + msg;
    SysLogLog(leError, s);
    raise EULModException.Create(s);
  end;
end;

function TModule.GetShouldSendRDRQ: boolean;
begin
  Result := FRDRQCount > 0;
end;

function TModule.GetShouldSendWRRQ: boolean;
begin
  Result := FWRRQCount > 0;
end;

procedure TModule.SetShouldSendRDRQ(OnOff: boolean);
begin
  if not OnOff then
    FRDRQCount := 0
  else
    inc(FRDRQCount);
end;

procedure TModule.SetShouldSendWRRQ(OnOff: boolean);
begin
  if not OnOff then
    FWRRQCount := 0
  else
    inc(FWRRQCount);
end;

procedure TModule.DoOnChangeFromDevice(AProp: TModuleProp);
begin
  if Assigned(FOnChangeFromDevice) then
    FOnChangeFromDevice(AProp);
end;

procedure TModule.WMAppMessage(var Msg:TMessage);
var o: TULDRObj;
begin
  o := TULDRObj(Msg.lParam);
  case Msg.wParam of
    cmULObjDestroyed: begin
      if o = ULDR then
        Free;
    end;
  end;
end;

{/TModule}

procedure ModuleSetPropInt(AModule:TModule; mp:TModulePropID; AValue:longint);
begin

end;

{TModules}
const
  DeviceListFileName: shortstring = 'ULD.ULD';
  DeviceListAscName: shortstring = 'ULD.ASC';




constructor TModules.Create;

  {v0.14}
  var
    i:integer;
    d: TULDRObj;
  {/v0.14}

begin
  inherited Create;
  ULF := TULFObj.Create(nil);
  if FileExists(DeviceListFileName) then begin
    try
      ULF.LoadFromFile(DeviceListFileName);
    except
      { do nothing}
    end;
  end;
  ULD := TULDObj(ULF.FindOrAdd(ULDID, ''));
  ULD.SetFlag(rfCantDelete, true);
  UpdateModulesFromULD;
  {v0.14}
  CheckDefaultModules;
  {/v0.14}
  FDeviceMode := {v0.11}{v0.14}CurDeviceMode{/v0.14 dmUlan} {/v0.11 CurDeviceMode};
  {if DeviceMode = dmUlan then begin
    FULDrv := TULDriver.Create(nil, UL_DEV_NAME);
  end else}
  {v0.14}
  {devmode}
  {/v0.14}
  FULDrv := nil;
  ULD.UserRegister(Self);

  {v0.14}
  for i := 0 to ULD.ChildCount - 1 do begin
    d := TULDRObj(ULD.Childs[i]);
    if d.DeviceName = '' then
      d.DeviceName := d.IDName;
  end;

  {ChannelsInit;}
  {/v0.14}
end;

procedure TModules.UpdateModulesFromULD;
var
  i: integer;
  uldr: TULDRObj;
  m: TModule;
begin
  for i := 0 to ULD.ChildCount - 1 do begin
    uldr := TULDRObj(ULD.Childs[i]);
    {v0.14}
    if not FindModuleWithPropVal(piULDR, IntToStr(longint(uldr)), m) then begin
      m := AddModule(0, uldr);
    end;
    {/v0.14
    ad := StrToInt(uldr.AddrStr);
    if (not FindModule(ad, m)) then begin
      m := AddModule(ad);
    end;
    }
  end;
end;

procedure TModules.UpdateModules(List:TStringList);
var
  s, n: string;
  i: integer;
  ad: TModuleAddr;
  m: TModule;
begin
  if List.Count > 0 then begin
    for i := 0 to List.Count - 1 do begin
      s := List.Strings[i];
      ad := StrToInt(copy(s, 1, ModuleAddrStrLen));
      n := copy(s, ModuleAddrStrLen + 1, 255);
      if not FindModule(ad, m) then begin
        m := AddModule(ad{v0.14}, nil{/v0.14});
        {m.Addr := ad;}
        m.IDName := n;
        m.IDDesc := s;
        if pos('LCD', n) > 0 then begin
          m.DeviceType := dtDetector;
          {v0.14}
          m.ULDR.DevicePortName := ULDrv.PortName;
          {/v0.14}
        end;
        if pos('LCP', n) > 0 then
          m.DeviceType := dtPump;
        {v0.14}
        if pos('LCS', n) > 0 then
          m.DeviceType := dtAutoSampler;
        {/v0.14}
        m.DeviceMode := dmUlan;
        {m.ScanProperties;}
        m.DetectState := dsPresent;
        m.OIRequestPropInfo;
      end else begin
        if m.IDName <> n then begin
          {ShowMessage('Two devices with same addr ' +
            IntToStr(ad) + ' - ' + m.IDName + ', ' +
            n + '!');}
          {m.ScanProperties;}
          m.OIRequestPropInfo;
        end else begin
          m.IDDesc := s;
        end;
        m.DetectState := dsPresent;
      end;

    end;
    {v0.14}
    Channels.CheckUlanChannels;
    {/v0.14}
  end;
end;

function TModules.FindModuleWithPropVal(pi: integer;
  AValue: String; var AModule: TModule): boolean;
var
  m: TModule;
  p: TModuleProp;
  i: integer;
begin
  Result := false;
  AModule := NoModule;
  for i := 0 to Count - 1 do begin
    m := TModule(Items[i]);
    if pi < 0 then begin
      if AModule = nil then
        AModule := m;
      case pi of
        piULDR: begin
          if IntToStr(longint(m.ULDR)) = AValue then begin
            AModule := m;
            Result := true;
            exit;
          end;
        end;
      end;
    end else begin
      if m.FindProp(pi, p) then begin
        if AModule = nil then
          AModule := m;
        if p.PCValStr = AValue then begin
          AModule := m;
          Result := true;
          exit;
        end;
      end;
    end;
  end;
end;

function TModules.FindModule(AAddr:TModuleAddr; var AModule:TModule):boolean;
var i:integer;
begin
  FindModule := false;
  AModule := NoModule;
  if Count > 0 then begin
    for i := 0 to Count - 1 do begin
      if TModule(Items[i]).Addr = AAddr then begin
        FindModule := true;
        AModule := TModule(Items[i]);
        exit;
      end;
    end;
  end;
end;

function TModules.AddModule(AAddr: TModuleAddr{v0.14}; AULDR: TULDRObj{/v0.14}): TModule;
var m: TModule;
begin
  m := TModule.Create(Self, AAddr{v0.14}, AULDR{/v0.14});
  if m <> NoModule then begin
    if Add(m) < 0 then begin
      m.Free;
      m := NoModule;
    end;
  end;
  AddModule := m;
end;

function TModules.ScanForModules: integer;
var
  sl: TStringList;
  d: TULDriver;
begin
  Result := 0;
  if DeviceMode <> dmUlan then begin
    SetResult(mrNotInUlanMode, 'Modules can be scanned only in Ulan mode.');
  end;
  {v0.11}
  FScannedForModules := true;
  {/v0.11}
  if ULDrv = nil then begin
    SetResult(mrUlanDriverInitFailed, 'Ulan driver failed to initialize for autodetect.');
  end;
  if FScanningForModules then
    exit;
  d := nil;
  sl := nil;
  try
    FScanningForModules := true;
    sl := TStringList.Create;
    d := ULDrv.Clone;
    if d.ScanForModules(sl) > 0 then begin
      UpdateModules(sl);
      Result := sl.Count;
    end;
    {v0.11}{/v0.11 FScannedForModules := true;}
  finally
    sl.Free;
    d.Free;
    FScanningForModules := false;
  end;
end;

function TModules.GetModulesList(var AList: TStringList): boolean;
var i: integer;
begin
  Result := false;
  if Count > 0 then begin
    AList.Clear;
    for i := 0 to Count - 1 do begin
      AList.Add(TModule(Items[i]).IDDesc);
    end;
    Result := true;
  end;
end;

function TModules.GetULDrv: TULDriver;
begin
  if FULDrv = nil then begin
    {v0.14}
    DeviceModeCheck(FDeviceMode);
    {/v0.14}
    if {v0.14}DeviceMode {/v0.14 CurDeviceMode} = dmUlan then begin
      {DeviceMode := dmUlan;}
      {v0.11}
      if not FULDrvFailed then
      {/v0.11}
      begin
        try
          FULDrv := TULDriver.Create(nil, UL_DEV_NAME);
        except
          on EULOSException do begin
            {v0.11}
            FULDrvFailed := true;
            {/v0.11
            CurDeviceMode := dmApex;
            DeviceMode := dmApex;
            }
          end;
        else
          raise;
        end;
      end;
    end;
  end;
  Result := FULDrv;
end;

destructor TModules.Destroy;
{var i:integer;}
begin
  {v0.14}
  FDestroying := true;
  {/v0.14}
  ULD.UserUnregister(Self);
  while Count > 0 do
    TModule(Items[0]).Free;
    {
  if Count > 0 then begin
    for i := 0 to Count - 1 do begin
      TModule(Items[i]).Free;
    end;
  end;}
  if ULF <> nil then begin
    ULF.SaveToFile(DeviceListFileName);
    ULF.SaveToFile(DeviceListAscName);{ just for debugging }
    ULF.Free;
  end;
  if ULDrv <> nil then
    ULDrv.Free;
  inherited Destroy;
  Modules := nil;
end;

procedure TModules.DoTimer;
var
  d: TULDriver;
  m: TModule;
  i: integer;
begin
  d := ULDrv;
  if d = nil then
    exit;
  if FScanningForModules then
    exit;
  if FIsInTimer then
    exit;
  {v0.14}
  if FDestroying then
    exit;
  {/v0.14}
  try
    FIsInTimer := true;
    if not FScannedForModules then begin
      ScanForModules;
      exit;
    end;
    while d.MessageAvailable do begin
      d.MessageOpen;
      try
        if FindModule(d.Message.sadr, m) then begin
          m.MessageReceive;
        end;
      finally
        d.MessageClose;
      end;
    end;
    for i := 0 to Count - 1 do begin
      TModule(Items[i]).DoTimer;
    end;
  finally
    FIsInTimer := false;
  end;
end;

procedure TModules.SendSetPropVals;
var
  i: integer;
begin
  if DeviceMode <> dmUlan then
    exit;

  for i := 0 to Count - 1 do begin
    TModule(Items[i]).OIMessageSendSetPropVals;
  end;
end;

procedure TModules.SendGetPropVals;
var
  i: integer;
begin
  if DeviceMode <> dmUlan then
    exit;
  for i := 0 to Count - 1 do begin
    TModule(Items[i]).OIMessageSendGetPropVals;
  end;
end;

{v0.11}
procedure TModules.SetResult(mr:TModuleResult; const msg:string);
var s:string;
begin
  if mr <> mrOK then begin
    s := 'Modules Error ' + IntToStr(mr) + ' ' + msg;
    SysLogLog(leError, s);
    raise EULModException.Create(s);
  end;
end;

{/v0.11}

{v0.14}
procedure TModules.CheckDefaultModules;
var
  dr: TULDRObj;
  e: TExtDev;
  i: integer;
begin
  if not ULD.HasChildWithFieldUsrValue(fnDeviceName, pvApexDefaultDevice, TULObj(dr)) then begin
    dr := TULDRObj(ULD.Add(ULDRID));
    dr.DeviceName := pvApexDefaultDevice;
    dr.DeviceMode := dmApex;
    dr.DeviceType := dtDetector;
    if ULDrv <> nil then begin
      dr.DevicePortName := ULDrv.PortName;
    end else begin
      ULDrvReqReadWrite(true, PortAddr, PortIrq, PCUlanAddr);
      dr.DevicePortName := GetComNameFromBaseAddr(PortAddr);
      if dr.DevicePortName = '' then
        dr.DevicePortName := CurPortName;
    end;
    dr.SetFlag(rfCantDelete, true);
    AddModule(0, dr);
  end;

  if ExtDevs <> nil then begin
    for i := 0 to ExtDevs.Count - 1 do begin
      e := TExtDev(ExtDevs[i]);
      if not ULD.HasChildWithFieldUsrValue(fnExtDevDrvName, e.ExtDevDrvName, TULObj(dr)) then begin
        dr := TULDRObj(ULD.Add(ULDRID));
        dr.DeviceName := e.ExtDevDrvName;
        dr.DeviceMode := dmExtDev;
        dr.DeviceType := dtDetector;
        dr.DevicePortName := CurPortName{'COM2'};
        dr.ExtDevDrvName := e.ExtDevDrvName;
        dr.SetFlag(rfCantDelete, true);
        AddModule(0, dr);
      end;
    end;
  end;
end;
{/v0.14}
{/TModules}

{EXPORT}
{
function ModuleFindOrAddProp(AModule:TModule; AID:TModulePropID; var AProp:TModuleProp):boolean;
begin
  ModuleFindOrAddProp := AModule.FindOrAddProp(AID, AProp);
end;

function ModulesFindModule(AAddr:TModuleAddr; var AModule:TModule):boolean;
begin
  ModulesFindModule := Modules.FindModule(AAddr, AModule);
end;

procedure ModulesUpdate(List:TStringList);
begin
  Modules.UpdateModules(List);
end;

function ModulesScanForModules: integer;
begin
  Result := Modules.ScanForModules;
end;

procedure ModulesDone;
begin
  Modules.Free;
end;
}
{/EXPORT}

initialization
  DeviceListFileName := ExtractFilePath(Paramstr(0)) + 'ULD.ULD';
  DeviceListAscName := ExtractFilePath(Paramstr(0)) + 'ULD.ASC';
  {v0.14 moved to ulmaglob.init} {/v0.14 Modules := TModules.Create;}
{
finalization
  Channels.Free;
  Modules.Free;
  done in ulmaglob
}
end.
