unit Modulu;{ module objects communicating with hardware devices }
{
  (C) 2000 - 2001 Jindrich Jindrich, Pavel Pisa, PiKRON Ltd.

  Originators of the CHROMuLAN project:

  Jindrich Jindrich - http://www.jindrich.com
                      http://orgchem.natur.cuni.cz/Chromulan
                      software developer, project coordinator
  Pavel Pisa        - http://cmp.felk.cvut.cz/~pisa
                      embeded software developer
  PiKRON Ltd.       - http://www.pikron.com
                      project initiator, sponsor, instrument developer

  The CHROMuLAN project is distributed under the GNU General Public Licence.
  See file COPYING for details.

  Originators reserve the right to use and publish sources
  under different conditions too. If third party contributors
  do not accept this condition, they can delete this statement
  and only GNU license will apply.
}

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, {v0.24}ModuUtl, {/v0.24}ModActu
  {$IFDEF DEBUG}
  ,DebugFrm
  {$ENDIF}
  {v0.14}
  ,ULLType, ULLObju, ULNType, ULNObju, ULNDType, ULNDObju,
  ExtDevIntu
  {/v0.14}
  {v0.24}
  ,ExeLogu
  {/v0.24}
  ;

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

type
  Int16 = smallint;
  Int32 = integer;

  PInt16 = ^Int16;
  PInt32 = ^Int32;

type
  EULModException = class(Exception);

  TModuleProps = class;
  TModule = class;

  TModuleProp = class({v0.24}TPersistent{/v0.24 TObject})
  private
    {v0.22}
    FULDP: TULDPObj;
    {/v0.22
    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: longint;
      { 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). }
    {v0.23}
    FUserCoef: single;
    FNumDec: integer;
    FNumLen: integer;
    {/v0.23}
    {v0.24}
    FRDRQSeqNr: byte;
    {/v0.24}
    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 }

    {v0.22}
    procedure SetULDP(AULDPObj: TULDPObj);
    {/v0.22}
  public
    procedure DoTimer;
    procedure RDRQWrite(ms: TMemoryStream{v0.24}; var AOISeqNr: byte{/v0.24});
      { Writes request bytes for RDRQ request (i.e. it's ID in two bytes)
        to the ms memory stream. Get AOISeqNr of the message that will be
        sent to the module (if <> 0, then compared with the SeqNr of received
        message in RDRQRead and the value ignored if SeqNrs are different) }
    procedure RDRQWriteCond(ms: TMemoryStream{v0.24}; var AOISeqNr: byte{/v0.24});
      { Calls RDRQWrite only if necessary (called from DoTimer) }
    procedure RDRQRead(ms: TMemoryStream{v0.24}; AOISeqNr: byte{/v0.24});
      { Reads answer to RDRQ request, i.e. it's new value, from ms memory
        stream (PropID was already read by Module). AOISeqNr is seqence number
        of the received message (if <> 0, then should match the FRDRQSeqNr) }
    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) }
    {v0.24}
    procedure ClearSubmittedFlags;
    {/v0.24}
    {v0.25}
    procedure RequestRead;
    {/v0.25}

    property ModuleProps: TModuleProps read FModuleProps;
    property Module: TModule read GetModule;
    property UpdateInterval: integer read GetUpdateInterval write SetUpdateInterval;
    {v0.22}
    property ULDP: TULDPObj read FULDP write SetULDP;
    {/v0.22}

  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;
    {v0.25}
    function FindPropByDesc(const APropDesc: string; var AProp: TModuleProp): boolean;
    {/v0.25}
  private
    FModule: TModule;
  {v0.24}
  protected
    function GetModuleProp(Index: integer): TModuleProp;
  {/v0.24}
  public
    property Module: TModule read FModule;
    {v0.24}
    property Props[Index: integer]: TModuleProp read GetModuleProp; default;
    {/v0.24}
  end;


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

{TModule}
  TModule = class({v0.24}TPersistent{/v0.24 TObject})
    {v0.22}{/v0.22
    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
    {v0.22}
    FULDR: TULDRObj;
    {/v0.22}

    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 }
    {v0.24 moved to ULDRRec}
    FLastRequestTime: TDateTime;
      { When was the last unanswered request to module sent ( used to detect
        disconnected devices). Assign through property (by SetLastRequestTime call). }
    FDieTime: TDateTime;
      { Callculated during setting LastRequestTime (to <> 0 value) by adding
        AliveTimeout (converted to TDateTime) to Now. Checked in DoTimer method
        for died (not responding = dsNotPresent) module. }
    FUseSeqNr: boolean;
    FInTimer: boolean;
    {/v0.24
    FDetectState: TModuleDetectState;}
      { information if the module was really detected (is present),
        see modutype .dsXXXX }
    {v0.44}
    FPropToGetDescCount: integer;
      { count of property descriptions to get from the module (during autodetection) }
    {/v0.44}

  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}
    {v0.51}
    {/v0.51
    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. }
     {v0.51}{/v0.51
    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;
    {v0.22}
    procedure SetULDR(AULDR: TULDRObj);
    {/v0.22}
    {v0.24}
    procedure SetDetectState(ADetectState: TModuleDetectState);
    function GetDetectState: TModuleDetectState;
    procedure SetLastRequestTime(ATime: TDateTime);
    procedure CheckAliveTimeout;
    function LogHead: string;
    {/v0.24}
  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;
    {v0.25}
    function FindPropByDesc(const APropDesc: string; var AProp: TModuleProp): boolean;
    {/v0.25}
    {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 }
    {v0.50}
    function Autodetecting: boolean;
      { was some property autodetect request sent to device and not answered yet? }
    {/v0.50}
    {v0.51}
    function OIMessageCreate: integer;
    function OIMessageWriteBuf(const Buf; BufSize: integer):integer;
    function OIMessageWriteWord(W: word): integer;
    function OIMessageClose: integer;

    procedure OIMessageSendBuf(const ABuf; ASize: integer);
    {/v0.51}

    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 GetDetectState write SetDetectState;
    property OIState: TOIState read FOIState;
    {v0.22}
    property ULDR: TULDRObj read FULDR write SetULDR;
    {/v0.22}
    {v0.24}
    property LastRequestTime: TDateTime read FLastRequestTime write SetLastRequestTime;
    {/v0.24}
  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.
        Individual modules can have different DeviceModes  }
    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}
    {v0.49}
    FULDrvLockCount: integer;
      { opening of ULDrv locked - GetULDrv returns false (used if manupulating
        UL_DRV) }
    {/v0.49}
  protected
    function GetULDrv: TULDriver;
    {procedure WMAppMessage(var Msg:TMessage);message WM_APPMESSAGE;}
    {v0.31}
    function GetModule(Index: integer): TModule;
    {/v0.31}
  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; {v0.31} ADeviceMode: TDeviceMode;{/v0.31}
      var AModule: TModule): boolean; {v0.46} {overload;}{/v0.46}
    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.49}
    procedure ULDrvFree;
      { dispose FULDrv; can be called if UL_DRV.SYS is to be unloaded and
        the chromulan is runnig }
    procedure ULDrvLock;
      { can be called after ULDrvFree, prevents creation of ULDrv }
    procedure ULDrvUnlock;
    function ULDrvLocked: boolean;
    {/v0.49}
    {v0.50}
    function Autodetecting: boolean;
      { is at least one module autodetecting itself? }
    {/v0.50}


    {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;
    {v0.31}
    property Devices[Index: integer]: TModule read GetModule; default;
    {/v0.31}
  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;

{dmXXXX}
const
  dmPropChange = 1;
  dmPropChangeFromDev = 2;
  dmPropNotChanged = 4;
  dmPropChangeNonWrite = 8;

  dmNoMessage = $10;

  dmAll = $FFFFFFFF;
  ModulesDebLogMask: DWORD = 0{dmAll {and (not dmPropChangeFromDev)};{dmAll;}
{/dmXXXX}
{v0.25}
const
  DebLogPropDescList : string = 'CHA';
  DebLogDeviceNameList : string = 'DET';
{/v0.25}


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

{v0.25}

function IsModuleToLog(const ADeviceName: string): boolean;
begin
  Result := (DebLogDeviceNameList = '') or
    (pos(',' + ADeviceName + ',', ',' + DebLogDeviceNameList + ',') > 0);
end;

function IsPropDescToLog(const APropDesc: string): boolean;
begin
  Result := (DebLogPropDescList = '') or
    (pos(',' + APropDesc + ',', ',' + DebLogPropDescList + ',') > 0);
end;

{/v0.25}

{procedure DebLog(const s: string);
begin
  ExeLog.Log(s);
end;}
{TModuleProp}
{v0.22}
procedure TModuleProp.SetULDP(AULDPObj: TULDPObj);
var f: TULObjField;
begin
  if FULDP <> nil then begin
    FULDP.UserUnregister(Self);
    f := FULDP.FindField(pnValueInPC);
    f.OnGetUsrValue := nil;
    f.OnSetUsrValue := nil;
    f := FULDP.FindField(pnValueInDevice);
    f.OnGetUsrValue := nil;
    f.OnSetUsrValue := nil;
    FULDP := nil;
  end;
  if AULDPObj <> nil then begin
    FULDP := AULDPObj;
    f := FULDP.FindField(pnValueInPC);
    f.OnGetUsrValue := OnGetUsrValue;
    f.OnSetUsrValue := OnSetUsrValue;
    f := FULDP.FindField(pnValueInDevice);
    f.OnGetUsrValue := OnGetUsrValue;
    f.OnSetUsrValue := OnSetUsrValue;
    FULDP.UserRegister(Self);
  end;
end;
{/v0.22}

constructor TModuleProp.Create(AModuleProps:TModuleProps; APropID : TModulePropID);
{v0.22}{/v0.22 var f: TULObjField;}
begin
  inherited Create;
  FModuleProps := TModuleProps(AModuleProps);
  {v0.22}
  ULDP := TULDPObj(
    TModule(TModuleProps(FModuleProps).Module).ULDR.FindOrAdd(ULDPID, IntToStr(APropID)));
  {/v0.22
  ULDP := TULDPObj(
    TModule(TModuleProps(FModuleProps).Module).ULDR.FindOrAdd(ULDPID, IntToStr(APropID)));
  ULDP.SetFlag(rfCantDelete, true);

  f := ULDP.FindField(pnValueInPC);
  f.OnGetUsrValue := OnGetUsrValue;
  f.OnSetUsrValue := OnSetUsrValue;
  f := FULDP.FindField(pnValueInDevice);
  f.OnGetUsrValue := OnGetUsrValue;
  f.OnSetUsrValue := OnSetUsrValue;
  }
  PropID := APropID;
  if IsFlagOn(pfRead) then
    SetState(psReadRequested, true);
  UpdateUpdateInterval;
  {v0.22}{/v0.22
  FULDP.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;
{v0.25}
var i:integer;
{/v0.25}
begin
  {v0.25}
  i := FUpdateInterval;
  {/v0.25}
  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}
    {v0.25}
    if (FUpdateInterval mod MinModulePropUpdateInterval) <> 0 then begin
      FUpdateInterval := ((FUpdateInterval div MinModulePropUpdateInterval) + 1) * MinModulePropUpdateInterval;
    end;
    {/v0.25
    if (FUpdateInterval mod MinModulePropUpdateInterval) = 0 then begin
      FUpdateInterval := ((FUpdateInterval div MinModulePropUpdateInterval) + 1) * MinModulePropUpdateInterval;
    end;}
    {/v0.13
    if (FUpdateInterval < MinModulePropUpdateInterval) then
      FUpdateInterval := MinModulePropUpdateInterval;
    }
  end;
  {v0.25}
  if i <> FUpdateInterval then begin
    ExeLog.LogEvent([leAction], 'MODULE PROP. UPDATE INTERVAL SET: ' +
      Module.ULDR.DeviceName + '.' + ULDP.PropDesc + ' ' +
      IntToStr(FUpdateInterval));{exelogu}
  end;
  {/v0.25}
end;

destructor TModuleProp.Destroy;
var
  i: integer;
{  f: TULObjField;}
begin
  {v0.22}
  ULDP := nil;
  {/v0.22
  ULDP.UserUnregister(Self);
  f := ULDP.FindField(pnValueInPC);
  f.OnGetUsrValue := nil;
  f.OnSetUsrValue := nil;
  f := ULDP.FindField(pnValueInDevice);
  f.OnGetUsrValue := nil;
  f.OnSetUsrValue := nil; }
  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;
  {v0.23}
  i: integer;
  {/v0.23}
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
      {v0.50}
      TypeID := ptUnknown;
      ExeLog.Log(ULDP.ULObjPath + ' Unknown property desc: ' + ULDP.TypeDesc);
      exit;
      {/v0.50
      SetResult(mrInvalidTypeDesc, td);
      }
    end;
{v0.23}
    FUserCoef := 0;
    FNumDec := 0;
    FNumLen := 7;
    i := pos('/', td);
    if i > 0 then begin
      td := copy(td, i + 1, length(td));
      if td <> '' then begin
        if td[1] = '.' then begin
          i := StrToInt(copy(td, 2, length(td)));
          FNumDec := i;
          if i > 0 then begin
            FUserCoef := 1;
            while i > 0 do begin
              FUserCoef := FUserCoef * 10;
              dec(i);
            end;
          end;
        end else if td[1] = '*' then begin
          FUserCoef := StrToInt(copy(td, 2, length(td)));
          FNumDec := 2;
        end;
      end;
    end;
{/v0.23}
  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; uldptype}
begin
  Result := false;
  {v0.23}
   if FUserCoef <> 0 then begin
    if not IsFloat(UserVal, f) then
      exit;
    case TypeID of
      ptCommand: ;
      {v0.50}
      ptUnknown: ;
      {/v0.50}
      ptWord: begin
        InternalVal := IntToStr(word(round(f * FUserCoef)));
      end;
      ptInt32: begin
        InternalVal := IntToStr(longint(round(f * FUserCoef)));
      end;
      ptInt16: begin
        InternalVal := IntToStr(smallint(round(f * FUserCoef)));
      end;
      ptSingle: begin
        InternalVal := FloatToStr(f * FUserCoef);
      end;
    else
      exit;
    end;
  end else begin
    InternalVal := UserVal;
  end;
  {/v0.23
  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;
  {v0.23}
  f: extended;
  {/v0.23}
begin
  Result := false;
  {v0.23}
  if FUserCoef <> 0 then begin
    case TypeID of
      ptCommand: ;
      {v0.50}
      ptUnknown:begin
        UserVal := InternalVal;
      end;
      {/v0.50}
      ptWord, ptInt32, ptInt16: begin
        if not IsInteger(InternalVal,i) then
          exit;
        UserVal := FloatToStrF(i / FUserCoef, ffFixed, FNumLen, FNumDec);
      end;
      ptSingle: begin
        if not IsFloat(InternalVal, f) then
          exit;
        UserVal := FloatToStrF(f / FUserCoef, ffFixed, FNumLen, FNumDec);
      end;
    else
      exit;
    end;
  end else begin
    UserVal := InternalVal;
  end;
  {/v0.23
  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) {v0.50} or (TypeID = ptUnknown){/v0.50});
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;
  try
    PCValStr := AVal;
  finally
    FUpdatingFromMod := false;
  end;
  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 ulobju
    exit;
  end;}
  FUpdatingFromProp := true;
  try
    {v0.24}
    changed := true;{force transfer for every assignment to PCValStr}
    {/v0.24
    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;
      {v0.50}
      ptUnknown: begin
        exit;
      end;
      {/v0.50}

    end;

    if AVal <> ULDP.ValueInPC then begin

      changed := true;

      {v0.24}
      ULDP.ValueInPC := AVal;
      {/v0.23
      FUpdatingFromProp := true;
      try
        ULDP.ValueInPC := AVal;
      finally
        FUpdatingFromProp := false;
      end;}

    end;
    {v0.23 uldptype}
    if ULDP.ForceTransfer then begin
      ULDP.ForceTransfer := false;
      changed := true;
    end;
    {/v0.23}

    if (not FUpdatingFromMod) then begin
      if IsFlagOn(pfWrite) then begin
        if changed then begin
          Modified := true;
          {$IFDEF DEBUG}
          if (ModulesDebLogMask and dmPropChange) <> 0 then begin
            {v0.25}
            if IsPropDescToLog(ULDP.PropDesc) then
            {/v0.25}
            DebLog('ModProp ' + ULDP.PropDesc + ' changed: ' + AVal);
          end;
          {$ENDIF}
        end else begin
          {$IFDEF DEBUG}
          if (ModulesDebLogMask and dmPropNotChanged) <> 0 then begin
            {v0.25}
            if IsPropDescToLog(ULDP.PropDesc) then
            {/v0.25}
            DebLog('ModProp ' + ULDP.PropDesc + ' not changed - resent ' + AVal);
          end;
          {$ENDIF}
        end;
      end else begin
        {$IFDEF DEBUG}
        if (ModulesDebLogMask and dmPropChangeNonWrite) <> 0 then begin
          {v0.25}
          if IsPropDescToLog(ULDP.PropDesc) then
          {/v0.25}
          DebLog('ModProp ' + ULDP.PropDesc + ' tried changed non writeable: ' + AVal);
        end;
        {$ENDIF}
      end;
    end else begin
      {$IFDEF DEBUG}
      if (ModulesDebLogMask and dmPropChangeFromDev) <> 0 then begin
        {v0.25}
        if IsPropDescToLog(ULDP.PropDesc) then
        {/v0.25}
        DebLog(IntToString(mstime, 10) + ' ModProp ' + ULDP.PropDesc + ' changed from dev.: ' + AVal);
      end;
      {$ENDIF}
    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 else begin
      {$IFDEF DEBUG}
      if (ModulesDebLogMask and dmPropNotChanged) <> 0 then begin
        DebLog('ModProp ' + ULDP.PropDesc + ' not changed ' + AVal);
      end;
      {$ENDIF}
    end;
    *)
  finally
    FUpdatingFromProp := false;
  end;
end;

procedure TModuleProp.WMAppMessage(var Msg:TMessage);
begin
  case Msg.wParam of
    {v0.24}
    cmULObjMainPropUpdated: begin
      if TULDPObj(Msg.lParam) = ULDP then begin
        if (not FUpdatingFromProp) and (not FUpdatingFromMod) then begin
          PCValStr := ULDP.ValueInPC;
        end;
      end;
    end;
    {/v0.24}
    cmULObjUpdated: begin
      if TULDPObj(Msg.lParam) = ULDP then begin
        if (not FUpdatingFromProp) and (not FUpdatingFromMod) then begin
          UpdateUpdateInterval;
          {v0.24}
          if ULDP.ForceTransfer then
          {/v0.24}
            PCValStr := ULDP.ValueInPC
        end;
      end;
    end;
    cmULObjDestroyed: begin
      if TULDPObj(Msg.lParam) = ULDP then begin
        {v0.22}
        ULDP := nil;
        {/v0.22}
        Free;
      end;
    end;
    {v0.24}
    cmULObjUpdateNow: begin
      Module.DoTimer;{!!! ???}
    end;
    {/v0.24}
  end;
end;

procedure TModuleProp.RDRQWrite(ms: TMemoryStream{v0.24}; var AOISeqNr: byte{/v0.24});
var
  w: word;
begin
{  if ReadNeeded then begin}
  {v0.24}
  FRDRQSeqNr := AOISeqNr;
  w := PropID;
  ms.Write(w, sizeof(w));
  SetState(psReadSubmitted, true);
{  end;}
  {v0.25}
{  if IsPropDescToLog(ULDP.PropDesc) then begin
    DebLog(IntToString(mstime, 10) + ' ' + ULDP.PropDesc + '.DoTimer RDRQWrite');
  end;}
  {/v0.25}
end;

procedure TModuleProp.DoTimer;
{v0.25}
{var
  need: boolean;}
{/v0.25}
begin
  {v0.25}
  {need := IsFlagOn(pfNeedUpdateFromDevice);}
  if (FUpdateInterval > 0) {or need }then begin
    if ((mstime - FNextFromTimerUpdateTime) >= 0) {or need }then begin
      {FNextFromTimerUpdateTime := FNextFromTimerUpdateTime + FUpdateInterval;}
      if (not IsStateOn(psReadSubmitted)) {or need }then begin
        {v0.25}
        {if IsPropDescToLog(ULDP.PropDesc) then begin
          DebLog(IntToString(mstime, 10) + ' ' + ULDP.PropDesc + '.DoTimer requesting RDRQ'
          );
        end;}
        {/v0.25}
        Module.ShouldSendRDRQ := true;
        {if need then
          SetFlag(pfNeedUpdateFromDevice, false);}
      end else begin
        {if IsPropDescToLog(ULDP.PropDesc) then begin
          DebLog(IntToString(mstime, 10) + ' ' + ULDP.PropDesc + '.DoTimer not sending RDRQ due to pending one');
        end;}
      end;
    end;
  end else begin
    {DebLog(IntToString(mstime, 10) + ' ' + ULDP.PropDesc + '.DoTimer not time');}
  end;
  {/v0.25
  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) >= 0 then
      Result := true;
  end;
end;

procedure TModuleProp.RDRQWriteCond(ms: TMemoryStream{v0.24}; var AOISeqNr: byte{/v0.24});
begin
  if IsFlagOn(pfRead) and (not IsStateOn(psReadSubmitted)) then begin
    if IsTimeForUpdateFromTimer or
       IsStateOn(psReadRequested) then
    begin
      RDRQWrite(ms{v0.24}, AOISeqNr{/v0.24});
    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;
    {v0.50}
    ptUnknown: begin
      exit;
    end;
    {/v0.50}
  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);{todo.txt}
    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{v0.24}; AOISeqNr: byte{/v0.24});
begin
  {v0.24}
  if FRDRQSeqNr <> AOISeqNr then begin
    ms.Seek(FValSize, soFromCurrent);{tstream}
    exit;
  end;
  {/v0.24}
  ms.Read(FInModValue^, FValSize);
  ModValStr := ModValStr;
  SetState(psReadSubmitted, false);
  SetState(psWriteSubmitted, false);
  {if IsPropDescToLog(ULDP.PropDesc) then begin
    DebLog(IntToString(mstime, 10) + ' ' + ULDP.PropDesc + '.RDRQRead');
  end;}
end;

{v0.24}
procedure TModuleProp.ClearSubmittedFlags;
begin
  SetState(psReadSubmitted or psWriteSubmitted, false);
end;
{/v0.24}
{v0.25}
procedure TModuleProp.RequestRead;
begin
  SetState(psReadRequested, true);
end;
{/v0.25}


{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
        {v0.24}
        if IsFlagOn(pfWrite) then
        {/v0.24}
        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;
    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 <> morOK then begin
    s := 'TModuleProp ' + IntToStr(mr) + ' ' + msg;
    {v0.24} ExeLog.LogEvent([leError], s);{/v0.24 SysLogLog(leError, s);}
    raise EULModException.Create({v0.23}ModuleProps.Module.DeviceName + '.' + PropDesc {/v0.23}  + 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;

{v0.25}
function TModuleProps.FindPropByDesc(const APropDesc: string; var AProp:TModuleProp):boolean;
var j:integer;
begin
  Result := false;
  for j := 0 to Count - 1 do begin
    if TModuleProp(Items[j]).PropDesc = APropDesc then begin
      Result := true;
      AProp := TModuleProp(Items[j]);
      exit;
    end;
  end;
end;
{/v0.25}

function TModuleProps.GetModuleProp(Index: integer): TModuleProp;
begin
  Result := TModuleProp(Items[Index]);
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;
  {v0.22}{/v0.22 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 */
  {v0.24}
  DetectState := dsUnknown;
  {/v0.24}
  FActionList := TModuleActionList.Create;
  {v0.22}{/v0.22
  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
  {v0.23}
  ULDR := nil;
  {/v0.23
  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 begin
    DeviceName := AName;

    {v0.24 moved from update..}
    if pos('LCD', AName) > 0 then begin
      DeviceType := dtDetector;
    end;
    if pos('LCP', AName) > 0 then begin
      DeviceType := dtPump;
    end;
    if pos('LCS', AName) > 0 then begin
      DeviceType := dtAutoSampler;
    end;
    if pos('MDET', AName) > 0 then begin
      DeviceType := dtDetector;
    end;
    {/v0.24}

    {v0.23}

    (*v0.50 replaced by CheckAAADevices method*)
    (*/v0.50
    if AAAAutoAssignDefNames then
    begin
      if pos('AAA_IKB', AName) > 0 then begin
        DeviceName := 'IK'; { .mt AAA_IKB v0.9 .uP 51x}
        {v0.46}
        DeviceType := dtDetector;{ just for now, must be as many detectors as data lines }
        {/v0.46}
      end;
      if pos('LCP', AName) > 0 then begin
        if Addr = 4 then
          DeviceName := 'P1';
        if Addr = 5 then
          DeviceName := 'P2';
        { .mt LCP5020 v1.3 .uP 51x .dy}
      end;
      if pos('DET', AName) > 0 then begin
        DeviceName := 'DET';{ .mt AAA_DET v0.8 .uP 51x}
        {v0.46}
        DeviceType := dtDetector;{ulantype}
        {/v0.46}
      end;
      if pos('POD', AName) > 0 then begin
        DeviceName := 'AS';{ .mt AAA_POD v1.4 .uP 51x}
        {v0.34}
        DeviceType := dtAutoSampler;
        {/v0.34}
      end;
    end; *)
    {/v0.23}
  end;
end;

{v0.50}
{/v0.50}

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;

{v0.24}
procedure TModule.SetDetectState(ADetectState: TModuleDetectState);
var
  i: integer;
begin
  if ADetectState = dsNotPresent then begin
    for i := 0 to PropCount - 1 do begin
      Props[i].ClearSubmittedFlags;
    end;
  end;
  ULDR.DetectState := ADetectState;
end;

function TModule.GetDetectState: TModuleDetectState;
begin
  Result := ULDR.DetectState;
end;

procedure TModule.SetLastRequestTime(ATime: TDateTime);
var at: TAcqTime;
begin
  if ATime = 0 then begin
    FLastRequestTime := 0;
    FDieTime := 0;
  end else begin
    FLastRequestTime := ATime;
    at := ULDR.AliveTimeout;
    if at = 0 then
      at := DefaultAliveTimeout;
    FDieTime := at / SecsPerDay + ATime;
  end;
end;

{/v0.24}

function TModule.FindProp(APropID:TModulePropID; var AProp:TModuleProp):boolean;
begin
  FindProp := Props.FindProp(APropID, AProp);
end;
{v0.25}
function TModule.FindPropByDesc(const APropDesc: string; var AProp: TModuleProp): boolean;
begin
  Result := Props.FindPropByDesc(APropDesc, AProp);
end;
{/v0.25}


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; {select waitformultipleobjects}
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);{uldriver}
{  Result := FOIDrv.MessageCreate(Addr, FOICommand, FOIOutFlags); uledfrm}
  FOISndStream := TMemoryStream.Create;
  {v0.24}
  if DetectState = dsPresent then begin
    FOIOutFlags := (FOIOutFlags or UL_BFL_ARQ) and (not UL_BFL_NORE);
  end else begin
    FOIOutFlags := FOIOutFlags and (not UL_BFL_ARQ) or UL_BFL_NORE;
  end;
  {/v0.24}

  FOIDrv.MessageCreate(Addr, FOICommand, FOIOutFlags);

  FOISndStream.WriteBuffer(FOIReplyCommand, 1);
  {v0.24}
  if FUseSeqNr then begin
    inc(FOISeqNr);
    FOISeqNr := (FOISeqNr and $3F) + $40;
  end;
  {/v0.24}
  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;
  {v0.24}
  if LastRequestTime = 0 then begin
    LastRequestTime := Now;
  end;
  {/v0.24}
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{v0.24}, FOISeqNr{/v0.24});
      {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{v0.24}, FOISeqNr{/v0.24});
        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{v0.24}, FOISeqNr{/v0.24});
      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{v0.24}, FOISeqNr{/v0.24});
      end;
    end;
    OIMessageWriteWord(0);
    OIMessageClose;
  end;
end;

{/OIMessage}

procedure TModule.OIRequestPropInfo;
begin
  {v0.44}ULDR.DetectState := dsDetecting;{/v0.44}
  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);

  {v0.24}
  if FUseSeqNr then begin
    FOIReplySeqNr := FOIRcvBuf^[2];
    if((FOIRcvBuf^[2] and $3F) = (FOISeqNr and $3F)) then begin
      LastRequestTime := 0;
    end else begin
      {error}
    end;
  end else begin
    LastRequestTime := 0;
  end;
  {/v0.24}

  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;


  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;
  {v0.44}
  inc(FPropToGetDescCount, cnt);
  {/v0.44}
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;
  {v0.44}
  inc(FPropToGetDescCount, cnt);
  {/v0.44}
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
  {v0.44}
  if FPropToGetDescCount > 0 then
    dec(FPropToGetDescCount, 1); {ulstringgrid}
  if FPropToGetDescCount = 0 then
    ULDR.DetectState := dsPresent;
  {/v0.44}
  if (FOIRcvBufLen < 3 + 2 + 2 + 1) then begin
    {FreeMem(bufout);
    Result := -1;}
    {ExeLog.LogErr('OIHandlePropDesc BufLen=' + IntToStr(FOIRcvBufLen));}
    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{v0.24}, FOIReplySeqNr{/v0.24});
      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;{history.txt}
begin
  if {v0.24}{/v0.24 (DetectState <> dsPresent) or }(OIState <> osClosed) then
    exit;

  if FInTimer then
    exit;

  FInTimer := true;
  try
    repeat
      if FActionList.GetAction(a) then begin
        DoAction(a);
        p := a.Priority;
        a.Free;
        if p = 0 then
          break;
      end else
        break;
    until false;

    {v0.24}
    if DetectState = dsSuspended then
      exit;
    {/v0.24}{ulantype ulrectyp modutype}

    for i := 0 to Props.Count - 1 do begin
      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 := Props[i];
          prop.WRRQWriteCond(FOISndStream);
        end;
        OIMessageWriteWord(0);
        OIMessageClose;
      end;
    end;
    if ShouldSendRDRQ then begin
      {v0.25}
      {if IsModuleToLog(ULDR.DeviceName) then
        DebLog(IntToString(mstime, 10) + ' Module.'+ ULDR.DeviceName + ' Sending RDRQ.');}
      {/v0.25}
      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{v0.24}, FOISeqNr{/v0.24});
        end;
        OIMessageWriteWord(0);
        OIMessageClose;
      end;
    end;

    {v0.24}
    CheckAliveTimeout;
    {/v0.24}
  finally
    FInTimer := false;
  end;
end;

{v0.24}
procedure TModule.CheckAliveTimeout;
begin
  if FDieTime <> 0 then begin
    if Now > FDieTime then begin
      DetectState := dsNotPresent;
      LastRequestTime := 0;
    end;
  end;
end;

function TModule.LogHead: string;
begin
  Result := 'TModule.' + DeviceName + ' ';
end;

{/v0.24}

function TModule.MessageReceive: integer;
begin
  Result := 0;
  {v0.11}
  if ULDrv = nil then
    exit;
  {/v0.11}
  {v0.24}
  if (ULDrv.Message.flg and UL_BFL_FAIL) <> 0 then begin
    if DetectState = dsPresent then
      ExeLog.LogEvent([leError], LogHead + 'Message Transmit Failed.');{exelogu}
  end;
  {/v0.24}

  if ULDrv.Message.cmd = FOIReplyCommand then begin
    Result := OIMessageReceive;
    {v0.24}
    {v0.44}
    if DetectState <> dsDetecting then
    {/v0.44}
      DetectState := dsPresent;
    {/v0.24}
  end else begin

  end;
end;

procedure TModule.SetResult(mr: TModuleResult; const msg: string);
var s: string;
begin
  case mr of
    morOK:;
  else
    s := 'TModule ' + IntToStr(mr) + ' ' + msg;
    {v0.24} ExeLog.LogEvent([leError], s);{/v0.24 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 begin
        {v0.22} ULDR := nil;{/v0.22}
        Free;
      end;
    end;
  end;
end;

{v0.22}
procedure TModule.SetULDR(AULDR: TULDRObj);
begin
  if FULDR <> nil then begin
    FULDR.UserUnregister(Self);
    FULDR := nil;
  end;
  if AULDR <> nil then begin
    FULDR := AULDR;
    FULDR.UserRegister(Self);
    UpdatePropertiesFromULDR;
  end;
end;
{/v0.22}

{v0.50}
function TModule.Autodetecting: boolean;
begin
  Result := (ULDR.DetectState = dsDetecting) or (FPropToGetDescCount > 0);
end;
{/v0.50}

{v0.51}
procedure TModule.OIMessageSendBuf(const ABuf; ASize: integer);
begin
  if OIMessageCreate = 0 then begin
    OIMessageWriteBuf(ABuf, ASize);
    OIMessageClose;
  end;
end;
{/v0.51}

{/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}{uldptype}

begin
  inherited Create; {ulmaglob}
  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;
  {v0.24}{/v0.24
  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;

{v0.24}

{/v0.24}
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, {v0.31} dmUlan,{/v0.31} m) then begin
        m := AddModule(ad{v0.14}, nil{/v0.14});
        {m.Addr := ad;}
        m.IDName := n;
        m.IDDesc := s;

        m.ULDR.DevicePortName := ULDrv.PortName;

        m.DeviceMode := dmUlan;
        {v0.44}{/v0.44 m.DetectState := dsPresent;}
        m.OIRequestPropInfo;
      end else begin                                     {uldrtype}
        if (m.IDName <> n) {v0.44} or (m.ULDR.DetectState = dsDetecting){/v0.44} then begin
          {ShowMessage('Two devices with same addr ' +
            IntToStr(ad) + ' - ' + m.IDName + ', ' +
            n + '!');}
          {m.ScanProperties;}
          {v0.24}
          m.IDName := n;
          m.IDDesc := s;
          {/v0.24}
          m.OIRequestPropInfo;
        end else begin
          m.IDDesc := s;
          {v0.44}
          m.DetectState := dsPresent;
          {/v0.44}
        end;
        {v0.44}{/v0.44 m.DetectState := dsPresent;}
      end;

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

{v0.50}
function TModules.Autodetecting: boolean;
var i: integer;
begin
  Result := false;
  for i := 0 to Count - 1 do begin
    if Devices[i].Autodetecting then begin
      Result := true;
      exit;
    end;
  end;
end;
{/v0.50}

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;
        {v0.25}
        piDeviceName: begin
          if m.ULDR.DeviceName = AValue then begin
            AModule := m;
            Result := true;
            exit;
          end;
        end;
        {/v0.25}
        {v0.47}

      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; {v0.31} ADeviceMode: TDeviceMode;{/v0.31}
  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) {v0.31} and (Devices[i].DeviceMode = ADeviceMode) {/v0.31} 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});{uldrtype uldrobju}
  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 {uldriver}
      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) {v0.49} and (not ULDrvLocked) {/v0.49} 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}
  {v0.24}{/v0.24
  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;   {channelsu}
end;

procedure TModules.DoTimer;
var
  d: TULDriver;
  m: TModule;
  i: integer;
{v0.25}
{  rcvMsgCnt:integer;}
{/v0.25}
begin
  d := ULDrv;
  if d = nil then
    exit;
  if FScanningForModules then begin
    {v0.25}
    ExeLog.Log('ulmaglob.ulMDoTimer skipped due to ScanningForModules');
    {/v0.25}
    exit;
  end;
  if FIsInTimer then begin
    {v0.25}
    ExeLog.Log('Modulu.Modules.DoTimer tried to call recursively');
    {/v0.25}
    exit;
  end;
  {v0.14}
  if FDestroying then begin
    {v0.25}
    ExeLog.Log('ulmaglob.ulMDoTimer skipped due to destroying');
    {/v0.25}
    exit;
  end;
  {/v0.14}
  try
    FIsInTimer := true;
    if not FScannedForModules then begin
      ScanForModules;
      {v0.25}
      ExeLog.Log('ulmaglob.ulMDoTimer skipped due to scanning modules');
      {/v0.25}
      exit;
    end;
    {v0.25}
    {rcvMsgCnt := 0;}
    {/v0.25}
    while d.MessageAvailable do begin
      {v0.25}
      {inc(rcvMsgCnt);}
      {/v0.25}
      d.MessageOpen;
      try
        {v0.50}
        if not d.MessageFilteredOut then
        {/v0.50}
        begin
          if FindModule(d.Message.sadr, {v0.31} dmUlan,{/v0.31}m) then begin
            m.MessageReceive;
          end;
        end;
      finally
        d.MessageClose;
      end;
    end;
    for i := 0 to Count - 1 do begin
      TModule(Items[i]).DoTimer;
    end;
    {v0.25}
{    if (ModulesDebLogMask and dmNoMessage) <> 0 then begin
      DebLog('Modulu.Modules.DoTimer no message came.');
    end;}
    {/v0.25}
  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 <> morOK then begin
    s := 'Modules Error ' + IntToStr(mr) + ' ' + msg;
    {v0.24} ExeLog.LogEvent([leError], s);{/v0.24 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, pvPasiveDefaultDevice, TULObj(dr)) then begin
    dr := TULDRObj(ULD.Add(ULDRID));
    dr.DeviceName := pvPasiveDefaultDevice;
    dr.DeviceMode := dmPasive;
    dr.DeviceType := dtDetector;
    if ULDrv <> nil then begin
      dr.DevicePortName := ULDrv.PortName;
    end else begin
      ULDrvReqReadWrite(true, PortAddr, PortIrq, PCUlanAddr{v0.21},UlanBaudRate{/v0.21}
      {v0.49}, ULDrvStartType{/v0.49});
      dr.DevicePortName := GetComNameFromBaseAddr(PortAddr);
      if dr.DevicePortName = '' then
        dr.DevicePortName := CurPortName;
    end;
    {v0.22}{/v0.22 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;
        {v0.22}{/0v.22 dr.SetFlag(rfCantDelete, true);}
        AddModule(0, dr);
      end;
    end;
  end;
end;
{/v0.14}
{v0.31}
function TModules.GetModule(Index: integer): TModule;
begin
  Result := nil;
  if (Index >= 0) and (Index < Count) then
    Result := TModule(Items[Index]);
end;
{/v0.31}

{v0.49}
procedure TModules.ULDrvFree;
  { dispose FULDrv; can be called if UL_DRV.SYS is to be unloaded and
    the chromulan is runnig }
begin
  if FULDrv <> nil then begin
    if FULDrv.InstanceCount > 1 then begin
      SetResult(mrCanNotFreeULDrv, 'UL_DRV is opened more than once, can not unload');
    end;
    FULDrv.Free;
    FULDrv := nil;
  end;
end;

procedure TModules.ULDrvLock;
  { can be called after ULDrvFree, prevents creation of ULDrv }
begin
  inc(FULDrvLockCount);
end;

procedure TModules.ULDrvUnlock;
begin
  if FULDrvLockCount > 0 then
    dec(FULDrvLockCount);
end;

function TModules.ULDrvLocked: boolean;
begin
  Result := FULDrvLockCount > 0;
end;
{/v0.49}

{/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
}
{v0.24}
  RegisterClasses([TModule, TModuleProp]);
{/v0.24}
end.
