unit AcqInfou;{v0.14}
{
  (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.
}
{$I define.pas}
interface
uses
  Windows, SysUtils, Classes, Forms,
  UtlType, ListType, Listu,
  UlanType, ApexType,
  ModuType,
  {$IFDEF USEDLL}
  ModuProc, {modules\ul_lcabs}
  {$ELSE}
  ul_lcabs, DevMode,
  {$ENDIF}
  ExtDevIntu, Timer, CommInt, Spectrum, Channelsu, UlanGlob,
  FileMenuHdl, Msgu, ExeLogu,
  Language, Simulationu;

type
  EInvalidDeviceMode = class(Exception);

  EAcqInfo = class(Exception);

  TAcqInfo = class(TObject)
    {v0.19}{/v0.19 RunningState: TRunningState;}
    CommPort: TComm;
    RcvBuf: array[0..RcvBufSize - 1] of char;
    SII: PScanInputInfo;
    PacketFifo: TLst;
    ApexPktInfo: TApexPacketInfo;
    ExtDev: TExtDev;
    {ExtDevDrv: PExtDevDrv;}
    ZeroValue: single;  //currently set zero value (in absolute units)
                       //of the y value, used to  calculate apexdatarec.y value from
                      // apexpacket values
    ZeroTime: Longint;
    {v0.31}{/v0.31
    CurUlanTime: single; }//gets incremented for every received experimental
                         // point by ulanglob . UlanPointTimeInterval (= 40 ms)
    LogPackets: boolean;
    IsInReceive: boolean;
    {v0.19 moved to private}{/v0.19
    FNextAcqTime: single;
    FSampleInt: single;}
  private
    {v0.19}
    FIgnoredCharCount: longint;
    FRecentPoints: TStream;
    FLastSuspendTime: single;
      { at what time (s) was the acquisition suspended, used upon resume to
        recalculate FAllSuspendInterval }
    FAllSuspendInterval: single;
      { sum of all intervals for which the acquisition was suspended
        (in seconds), just informative }
    FAllAcqTime: single;
    FLastResumeTime: single;
      { sum of all acqusition times (useful if suspend was called one or
        more times), in (s) }
    FSuspendCount: longint;
      { how many times was the acquisition suspended; (if > 0, then
        FAllSuspendInterval is used adjust the point's device time) }
    {/v0.19}
    FNextAcqTime: single;
    FSampleInt: single; { in ms }
    FRunningState: TRunningState;
    FData: TAcqData;
    FForm: TForm;
      { that should be informed to Run, when eiMark received
        (only TSpectrumForm accepted now) }
    {v0.24}
    FOwnerForm: TForm;
      { if non nil then this form will destroy the AcqInfo upon its destroy }
    {v0.24}
    FStopTime: longint;
      { if <> 0 then at this ms time the acquisiton will auto stop,
        calculated from AcqData.ULI.Duration at Start }
    {v0.21}
    FDataWaitTimeout: longint;
      { for how long can not come any date from port in running state
        (if not comming for longer time - error) (assigned ULI.DataWaitTimeout * 1000)}
    FNoDataInLimit: longint;
      { if mstime becomes > FNoDataInLimit, error will be reported, acq.suspended }
    FLastDataTime: longint;
      { mstime value at the moment when last data point was added }
    FSuspendedForNoData: boolean;
      { Suspend method was called before data stopped to come from device
        (i.e. some data already came but suddenly stopped comming) }
    {v0.53}
    FNoDataDetected: boolean;
      { set true if in FDataWaitTimeout interval did not come any data;
        cleared in DeviceStart method, called from Start, Resume }
    FGotDataInTimer: boolean;
      { set true in modetimers if some data came from device; used
        to decide if simulation data should be created (if SimulatingData
        is on and FNoDataDetected is true) }
    FTimerInterval: integer;
      { how ofthe is DoTimer called; should be set by the object
        that calls DoTimer }
    {/v0.53}
    FIsInTimer: boolean;

    FLastExpPoint: TExpPoint;
    {FLastDataX: TXValue;}
      { last added data point X value }
    {/v0.21}
    {v0.41}
    {FCurTime: single;{last data acqtime}
    FOnMark: TNotifyEvent;
      { if assigned, then Run won't be called (if RunningState = rsReadyToRun),
        it is responsibility of the FOnMark user }
    {/v0.41}
    FLastExtDevReadPointMSTime: integer;
    procedure DoStartResume;
    procedure DoMark(const AI: TApexPacketInfo);{called when mark packet received, decides accorging
      to source address if should react on it, if yes, calls Run or
      FOnMark }
    { If the extdev needs to receive request before sending a packet, then this
      method does it. }
    procedure ExtDevReqPkt;
    function GetCurTime:TXValue;
  protected
    function GetDeviceMode: TDeviceMode;
    function GetPortName: shortstring;
    function GetUVDetAddr: integer;
    function GetChannel: TChannel;
    function GetExtDevDeviceName: TDeviceName;
    //function GetExtDevPortName: shortstring;
    function GetPortDeviceName: shortstring;

    procedure PortSetDefaults;

    procedure ComPortReceive(Sender: TObject; Count: Integer);
      procedure DoInChar(CommChar: char);
        procedure DoApexChar(CommChar: char);
        procedure DoExtDevInChar(CommChar: char);
    {v0.19}
    function GetFMH: TFileMenuHandler;
    procedure DoApexTimer;
    procedure DoUlanTimer;
    procedure DoExtDevTimer;
    procedure DoApexPacket(var ai:TApexPacketInfo);
    {v0.24}{/v0.24
    procedure PointsAddStart;
    procedure PointsAddAdd(ADataIndex: integer; dr: TExpPoint);
    procedure PointsAddStop;}
    {/v0.19}
    {v0.21}
    procedure DeviceStart;
      { (re)starts the device (and set its ZeroTime to mstime) }
    procedure DeviceStop;
      { stop device's time }
    {/v0.21}
    {v0.50 moved from public}
    function PortOpen: boolean;
    procedure PortClose;
    {/v0.50}

  public
    constructor Create(AData: TAcqData);reintroduce;
    destructor Destroy;override;
    {v0.24}
    procedure PointsAddStart;
    procedure PointsAddAdd(ADataIndex: integer; dr: TExpPoint);
    procedure PointsAddStop;
    {/v0.24}
    {v0.50}
    {procedure PortSuspend;
    procedure PortResume;}
    {/0.50
    function PortOpen: boolean;
    procedure PortClose; }
    procedure ResetPacketInfo;
    {v0.41}
    function IsPortOpen: boolean;
    {/v0.41}
    {v0.15}
    function Start: boolean;
      {v0.41 start changed to function, returns false if could not open port }{/v0.41}
    {v0.19}{/v0.19 procedure RequestStop;}
    {/v0.15}
    {v0.19}
    procedure DoRunStop;
      procedure Run;
      procedure Stop;
    {/v0.19}
    {v0.17}
    procedure CheckStop;
      { called from Timer, finds out if it is time to stop, if yes
        calls requestStop }
    {/v0.17}
    {v0.21}
    procedure CheckNoData;
      { called from Timer if running, finds out if no data came from the port
        for longer time then DataWaitTimeout (ms), if so, suspends
        the acquisition and shows warning to the user }
    {/v0.21}
    {v0.19}
    procedure DoTimer;
      { called from TheSpecForm window timer }
      {0.v21}
      procedure DoModeTimers;
        { called from DoTimer and from Suspend (reads buffers) }
      {/v0.21}
      {v0.53}
      procedure DoSimulationTimer;
      {/v0.53}
    procedure DoSuspendResume;
      { called when menuitem Suspen/Resume clicked, calls one of the following }
      procedure Suspend;
      procedure Resume;
    function CanClose: boolean;
      { can destroy the object, stop/abort acquisition if running?  }
    {v0.21}
    procedure NotifyForms(cm:integer);
      { send WM_APPMessage to FForm }
    {/v0.21}
    { Returns true if APortDeviceName is just beeing used by this object
      for data acquisition. APortDeviceName can be name of the COM port,
      eventually followed (dot separated) by name of the device (used if
      more devices can be scanned on the same COM port). }
    function DevicePortInUse(APortDeviceName: shortstring): boolean;
    property Data: TAcqData read FData;
    property FMH: TFileMenuHandler read GetFMH;
    property RunningState: TRunningState read FRunningState;
    property RecentPoints: TStream read FRecentPoints;
    {/v0.19}
    property DeviceMode: TDeviceMode read GetDeviceMode;
    property PortName: shortstring read GetPortName;
    property UVDetAddr: integer read GetUVDetAddr;
    property Channel: TChannel read GetChannel;
    property TheSpecForm: TForm read FForm write FForm;
    property ExtDevDeviceName: TDeviceName read GetExtDevDeviceName;
    {v0.24}
    property OwnerForm: TForm read FOwnerForm write FOwnerForm;
    {/v0.24}
    {v0.41}
    property SuspendedForNoData: boolean read FSuspendedForNoData;
    property CurTime: single read GetCurTime{FLastDataX};
    property OnMark: TNotifyEvent read FOnMark write FOnMark;
    {/v0.41}
    {v0.53}
    property TimerInterval: integer read FTimerInterval write FTimerInterval;
    {/v0.53}
    { Returns PortName + '.' + ExtDevDeviceName }
    property PortDeviceName: shortstring read GetPortDeviceName;
    property LastExpPoint: TExpPoint read FLastExpPoint;
  end;

implementation
uses Main, SpecForm{v0.50}, Processoru{/v0.50}{v0.53}
{$IFDEF USEDLL}
{$ELSE}
,Modulu
{$ENDIF}

{/v0.53};

constructor TAcqInfo.Create(AData: TAcqData);
var
  dm: TDeviceMode;
  li: TListInfo;
begin
  inherited Create;
  FData := AData;
  {v0.78}
  FData.CreateDataStreams;
  {/v0.78}
  ExtDev := nil;
  ZeroTime := mstime;
  ZeroValue := 0;
  FRunningState := rsStopped;
  CommPort := TComm.Create(nil);
  {v0.19}
  {v0.21}
  FDataWaitTimeout := round(FData.ULI.DataWaitTimeout * 1000);
  if FDataWaitTimeout = 0 then
    FDataWaitTimeout := DefDataWaitTimeout * 1000;
  CommPort.DeviceName := PortName;
  {/v0.21}
  PortSetDefaults;
  {/v0.19}
  {v0.43}{/v0.43
  MainForm.ComPort := CommPort;}
  dm := DeviceMode;
  {v0.15}
  FNextAcqTime := 0;
  FSampleInt := FData.ULI.SamplingInterval * 1000;
  {/v0.15}
  {v0.19}
  FRecentPoints := TMemoryStream.Create;
  {/v0.19}
  {$IFDEF USEDLL}
  ModulesDoAction(Modules, msaDeviceModeCheck, @dm, sizeof(dm));
  {$ELSE}
  DeviceModeCheck(dm);
  {$ENDIF}
  if dm <> DeviceMode then begin
    raise EInvalidDeviceMode.Create('Specified device mode not supported.');
  end;
  {v0.15}
  if dm = dmUlan then begin
    if FSampleInt > 0 then begin
      FSampleInt := ( round((FSampleInt - 1) / UlanPointTimeInterval) + 1) * UlanPointTimeInterval;
    end;
  end;
  {/v0.15}
  ResetPacketInfo;
  {v0.31 now in ULADRc}{/v0.31
  CurUlanTime := 0;}
  FillChar(li, sizeof(li), 0);
  case dm of
    dmUlan, dmPasive: begin
      li.Capacity := 100;
      li.RecordSize := sizeof(TApexPacketInfo);
      if not ListInit(ltRecords or ltAutoDestroy, @li, PacketFifo) then
        raise EAcqInfo.Create('AcqInfo ListInit PacketFifo failed.');
    end;
    dmExtDev: begin
      ExtDev := ExtDevs.FindDev(ExtDevDeviceName);
      if (ExtDev = nil) or  (ExtDev.Init{v0.65}{/v0.65 ('')} <> 0) then begin
        raise EAcqInfo.Create('AcqInfo ExtDevInit failed.');
      end;
    end;
    {v0.51pi}
    dmUnspecified: begin
       {!!!!!!!!!!}
    end;
    {/v0.51pi}
  else
    raise EInvalidDeviceMode.Create('Unknown DeviceMode');
  end;
end;

destructor TAcqInfo.Destroy;
begin
  if TheSpecForm <> nil then begin
    if OwnerForm <> TheSpecForm then begin
      NotifyForms(cmAcqInfoDestroyed);{TheSpecForm}
    end;
  end;
  {v0.43}{/v0.43
  if MainForm.ComPort = CommPort then
    MainForm.ComPort := nil;}
  CommPort.Free;
  {v0.19}
  FRecentPoints.Free;
  {/v0.19}
  ListDone(PacketFifo);
  inherited;
end;

procedure TAcqInfo.ResetPacketInfo;
begin
  FillChar(ApexPktInfo, sizeof(ApexPktInfo), 0);
end;

function TAcqInfo.GetChannel: TChannel;
begin
  Result := Channels.FindChannel(FData.ULI.ChannelName);
end;

function TAcqInfo.GetDeviceMode: TDeviceMode;
begin
  Result := Channel.DeviceMode;
end;

function TAcqInfo.GetPortName: shortstring;
begin
  Result := Channel.PortName;
end;

function TAcqInfo.GetUVDetAddr: integer;
begin
  Result := Channel.DetectorAddress;
end;

function TAcqInfo.GetExtDevDeviceName: TDeviceName;
begin
  Result := Channel.ExtDevDeviceName;
end;

procedure TAcqInfo.PortSetDefaults;
var
  op:TCommOptions;
begin
  {$IFDEF WIN32}
  with CommPort do begin
    AfterOpenState := aoSpecified;
    BaudRate := br9600;
    DataBits := da8;
    DeviceName := 'COM2';
    FlowControl := {v0.21}fcNone;{/v0.21 fcDefault;}
    MonitorEvents := [evRxChar];
    Options := [];
    Parity := paNone; {paSpace;}
    ReadBufSize := 4096;
    ReadTimeout := 1000;
    StopBits := sb10;
    WriteBufSize := 2048;
    WriteTimeout := 1000;
    AfterOpenState := aoSpecified;
    op := [];
    Options := op;
    DTROnOpen := TCommEscapeState(1);
    RTSOnOpen := TCommEscapeState(0);
    XOnOnOpen := TCommEscapeState(2);
    BreakOnOpen := TCommEscapeState(2);
   end;
  {$ELSE}
  {comm}
  with CommPort do begin
    BaudRate := tbr9600;
    DataBits := tdbEight;
    Parity := tpSpace;
    ReadBufferSize := 2048;
    Stopbits := tsbOne;
    TxLowCount := 1024;
    WriteBufferSize := 1024;
    Events := [tceRxChar];
  end;
  {$ENDIF}
end;

{v0.17}
procedure TAcqInfo.CheckStop;
begin
  if FStopTime <> 0 then begin
    if mstime >= FStopTime then begin
      {v0.19}
      Stop;
      {/v0.18
      RequestStop;}
    end;
  end;
end;
{/v0.17}

{v0.21}
procedure TAcqInfo.CheckNoData;
begin
  {v0.42}
{  if not ShouldCheckNoData then
    exit;}
  {/v0.42}
  if mstime > FNoDataInLimit then begin
    {v0.53}
    FNoDataDetected := true;
    if SimulatingData then
      exit;
    {/v0.53}
    if Data.{v0.24}DataSize{/v0.24 ULAD.Data.Size} = 0 then begin
      Stop;
    end else begin
      FSuspendedForNoData := true;
      Suspend;
    end;
    ShowMessage(GetTxt({#}'No data from acquisition'), smError, 0);
  end;
end;
{/v0.21}

procedure TAcqInfo.ComPortReceive(Sender: TObject; Count: Integer);
var
  CommChar:Char;
  i:Word;
  b:word;
begin
  if {v0.19} RunningState in [rsStopped{v0.21}{/v0.21, rsSuspended}] {/v0.19 RunningState = rsStopped} then
    exit;
  {v0.15}
  {v0.17 moved to CheckStop method, called from TheSpecForm.TimerTimer }{/v0.17
  if FStopTime <> 0 then begin
    if mstime >= FStopTime then
      RequestStop;
  end;}
  {/v0.15}
  if IsInReceive then
    exit;
  if Count = 0 then
    exit;
  try
    IsInReceive := true;
    repeat
      if Count <= sizeof(RcvBuf) then begin
        b := Count;
      end else begin
        b := sizeof(RcvBuf);
      end;
      dec(Count, b);
      CommPort.Read(RcvBuf, b);
      {v0.19}
      {
      PortLogAddRecord(StrToInt(copy(CommPort.DeviceName,4,1)) ,'g', b);}
      {/v0.19}

      {v0.21}
      if RunningState <> rsSuspended then
      {/v0.21}
      for i := 0 to b - 1 do begin
        CommChar := RcvBuf[i];
        DoInChar(CommChar);
      end;
    until Count = 0;
  finally
    IsInReceive := false;
  end;
end;

procedure TAcqInfo.DoApexChar(CommChar:char);

  procedure HandlePacket(var ApexPktInfo:TApexPacketInfo);
  begin
    case ApexPktInfo.Pkt.Data[ApexEventIDPos] of
      eiMark: begin
        {v0.53}
        DoMark(ApexPktInfo);
        {/v0.53
        if Assigned(FOnMark) then begin
          FOnMark(Self);
        end else
        begin
          if RunningState =  rsReadyToRun then begin
            Run;
          end;
        end;
        }
      end;
      eiData: begin
        if RunningState = rsRunning then
          ListRecAdd(PacketFifo, ApexPktInfo);
      end;
    else
      begin
      end;
    end;
  end;

begin
  with ApexPktInfo do begin
    if (CurPos < 4) and (CommChar <> #0) then begin
      {v0.19}
      inc(FIgnoredCharCount);
      if FIgnoredCharCount = ApexPacketSize then begin
        {v0.24}
        ExeLog.LogEvent([leError], 'DoApexChar: Too many non packet bytes received -'
          + IntToStr(FIgnoredCharCount));
        {/v0.24 SysLogLog(leError, 'DoApexChar: Too many non packet bytes received -'
          + IntToStr(FIgnoredCharCount));}
        FIgnoredCharCount := 0;
      end;
      {/v0.19}
      CurPos := 0;
      EndTime := mstime;
    end else begin
      {v0.19}
      FIgnoredCharCount := 0;
      {/v0.19}
      Pkt.Data[CurPos] := ord(CommChar);
      if CurPos = 0 then
        Time := EndTime;
      inc(CurPos);
      if CurPos = ApexPacketSize then begin
        CurPos := 0;
        EndTime := mstime;
        if Pkt.Data[ApexEventIDPos] <> eiData then begin
          {v0.19}
          {v0.24}
          ExeLog.LogEvent([leError], 'DoApexChar: Invalid ID byte - ' +
            IntToStr(Pkt.Data[ApexEventIDPos]) + ' x ' + IntToStr(eiData));
          {/v0.24
          SysLogLog(leError, 'DoApexChar: Invalid ID byte - ' +
            IntToStr(Pkt.Data[ApexEventIDPos]) + ' x ' + IntToStr(eiData));}
          FIgnoredCharCount := 0;
          {/v0.19}
          CurPos := 0;
        end;
        HandlePacket(ApexPktInfo);
      end;
    end;
  end;
end;

procedure TAcqInfo.DoExtDevInChar(CommChar:char);
begin
  if ExtDev.DoCharIn(CommChar) = edrNeedsToSendPacket then
    ExtDevReqPkt
end;

procedure TAcqInfo.DoInChar(CommChar:char);
begin
  case DeviceMode of
    dmPasive: DoApexChar(CommChar);
    dmExtDev: DoExtDevInChar(CommChar);
  end;
end;

function TAcqInfo.PortOpen: boolean;
var
  pn: shortstring;
  s: shortstring;
  res: integer;
  edHandlingIO: boolean;
begin
{  Result := false;}
  case DeviceMode of
    dmUlan: begin
      Result := RcvInit(SII, {v0.31} FData.ULDetAddrList{/v0.31 UVDetAddr});
    end;
  else
    CommPort.OnRxChar := ComPortReceive;
    CommPort.DeviceName := PortName;

    //pn := CommPort.DeviceName;

    res := -1;
    edHandlingIO := (ExtDev <> nil) and (ExtDev.GetStr(epHandlingIO, s) = 0) and (s = 'true');
    if ExtDev <> nil then begin
      if edHandlingIO then begin
        pn := PortDeviceName // can contain something else then just COMx, if the ExtDev handles IO
      end else begin
        pn := PortName;
      end;
      res := ExtDev.DoAction(eaPortBeforeOpen, longint(@pn));{ulantype extdevintu}
      if (res <> 0) and (res <> -1 { -1 = not implemented}) then begin
        ExeLog.LogErr('ExtDev PortBeforeOpen ' + pn + ' Failed: ' + IntToStr(res));
      end;
    end;

    if edHandlingIO then
    begin
      { do nothing, the port should have been open in the above call to ExtDev.DoAction,
        in that case the DoAction should return 0 if OK. }
      Result := (res = 0);
      if res <> 0 then begin
        ExeLog.LogErr('ExtDev HandlingIO but PortBeforeOpen failed.')
      end;
    end else begin
      ReadWriteComConfig(rwRead, CommPort);
      CommPort.Open;
      Result := true;
    end;

  end;
end;

{v0.50}
{procedure TAcqInfo.PortSuspend;
begin
  if DeviceMode <> dmUlan then
    PortClose;
end;
procedure TAcqInfo.PortResume;
begin
  if DeviceMode <> dmUlan then
    PortOpen;
end;}
{/v0.50}

procedure TAcqInfo.PortClose;
var
  pn: shortstring;
  res, i: integer;
begin
  case DeviceMode of
    dmUlan: begin
      RcvDone(SII);
    end;
  else
    pn := PortName;
    if ExtDev <> nil then begin
      res := ExtDev.DoAction(eaPortBeforeClose, longint(@pn));
    end;

    if CommPort.Enabled then begin
      CommPort.Close;
      CommPort.OnRxChar := nil;
      ReadWriteComConfig(rwWrite, CommPort);

      res := ExtDev.DoAction(eaPortAfterClose, longint(@pn));
    end;
  end;
end;

{v0.41}
function TAcqInfo.IsPortOpen: boolean;
begin
  case DeviceMode of
    dmUlan: Result := SII <> nil;
  else
    Result := CommPort.Enabled;
  end;
end;
{/v0.41}

{v0.31}
procedure TAcqInfo.DoStartResume;
begin
  if FData.ULI.Duration <> 0 then begin
    FStopTime := mstime + round(FData.ULI.Duration * 1000)
      - round(FAllAcqTime * 1000);
  end else begin
    FStopTime := 0;
  end;
  if FSampleInt <> 0 then begin
    FNextAcqTime := mstime + FSampleInt;
  end;
  FNoDataInLimit := mstime + FDataWaitTimeout;
  FRunningState := rsRunning;
  {v0.65}
  {/v0.65}
end;
{/v0.31}

function TAcqInfo.Start: boolean;
begin
  {v0.46}
  Result := false;
  {v0.41}
  if not IsPortOpen then begin
    if not PortOpen then
      exit;
  end;
  {/v0.41}
  {v0.31}
  Data.Clear;
  DeviceStart;
  DoStartResume;
  {/v0.31
  if FData.ULI.Duration <> 0 then begin
    FStopTime := mstime + round(FData.ULI.Duration * 1000)
      - round(FAllAcqTime * 1000);
  end else begin
    FStopTime := 0;
  end;
  if FSampleInt <> 0 then begin
    FNextAcqTime := mstime + FSampleInt;
  end;
  FNoDataInLimit := mstime + FDataWaitTimeout;}

  {v0.24}
  {v0.31}{/v0.31 FRunningState := rsRunning;}
  NotifyForms(cmAcquisitionRun);
  {/v0.24}
  {v0.46}
  Result := true;
  {/v0.46}
  {v0.50}{channelsu processoru}
{  Processors.ProcessorGet(Channel.ChannelName).StartProgram(FData.Prg, Data);}
  {/v0.50}
end;

{v0.19}{/v0.19
procedure TAcqInfo.RequestStop;
begin
  if FForm <> nil then begin
    PostMessage(FForm.Handle, WM_APPMESSAGE, cmAcquisitionStop, 0);
  end else begin
  end;
end;
}

{procedure TAcqInfo.Stop;
begin
  RunningState := rsStopped;
  PortClose;
end;}


{v0.19}
procedure TAcqInfo.DoRunStop;
begin
  if RunningState <> rsStopped then begin
    {v0.50}
    if ShowMessage(GetTxt({#}'Really stop the aquisition?'), smYesNo, 0) = cmYes then
    {/v0.50}
    Stop;
  end else begin
    {v0.51pi}
    if DeviceMode<>dmUnspecified then
    {/v0.51p}
    begin
      Run;
    end {v0.62} else begin
      ShowMessage(GetTxt({#}'Unspecified device for current channel'), smError, 0);
    end {/v0.62};
  end;
end;

{v0.65}
procedure TAcqInfo.ExtDevReqPkt;
var s: shortstring;
begin
  if Assigned(ExtDev.ExtDevGetStr) then begin
    if (ExtDev.GetStr(epPktReqStr, s) = 0) and (s <> '') then begin
      CommPort.Write(s[1], length(s));
    end;
  end;
end;
{/v0.65}

function TAcqInfo.GetCurTime:TXValue;
begin
  Result := FLastExpPoint.X;
end;


{v0.21}
procedure TAcqInfo.DeviceStart;
begin
  ResetPacketInfo;
  ZeroTime := mstime;
  {v0.31}
  Data.ULAD.Start;
  {/v0.31
  CurUlanTime := 0;}
  {v0.53}
  FNoDataDetected := false;
  {/v0.53}
  ApexPktInfo.EndTime := ZeroTime;
  case DeviceMode of
    dmUlan: RcvSetProp(SII, rpZeroTime, @ZeroTime);
    dmExtDev: begin
      ExtDev.Start;
      {v0.65}
      ExtDevReqPkt;
      {/v0.65}
    end;
  end;
end;

procedure TAcqInfo.DeviceStop;
begin
  case DeviceMode of
    dmExtDev: ExtDev.Stop;
  end;
end;
{/v0.21}


procedure TAcqInfo.Run;
var pn: shortstring;
begin
  if RunningState = rsStopped then begin
    {pn := PortName;}
    pn := PortDeviceName;
    if SendMessage(MainForm.Handle, WM_APPMESSAGE, cmQueryRunning,
      longint(@pn)) = 1 then
    begin
      ShowMessage(GetTxt({#}'Acquisition is already running'), smError,0);
      exit;
    end;
    if (Data <> nil) and (Data.{v0.24}DataSize{/v0.24 ULAD.Data.Size} <> 0) then begin
      if ShowMessage(GetTxt({#}'Discard already acquired data?'), smNoYes, 0) <> cmYes then
        exit;
      {v0.31}
      Data.Clear;
      {/v0.31}
      {v0.21}
      NotifyForms(cmAcquisitionAborted);
      {/v0.21}
    end;
    ResetPacketInfo;
    if not PortOpen then
      exit;
    FRunningState := rsReadyToRun;
    {v0.31}
    Data.Clear;
    {/v0.31}
    {v0.24}
    NotifyForms(cmAcquisitionRun);
    {/v0.24}
  end else begin
    { i.e. RunningState = rsReadytorun, Run don't get called in other states }
    {ClearData;}

    {v0.21}
    FLastSuspendTime := 0;
    FAllSuspendInterval := 0;
    FAllAcqTime := 0;
    FLastResumeTime := 0;
    FSuspendCount := 0;
    FNextAcqTime := 0;
    {v0.31 moved to start}{/v0.31
    DeviceStart;}
    {/v0.21
    ResetPacketInfo;
    ZeroTime := mstime;
    CurUlanTime := 0;
    ApexPktInfo.EndTime := ZeroTime;
    case DeviceMode of
      dmUlan: RcvSetProp(SII, rpZeroTime, @ZeroTime);
      dmExtDev: ExtDev.Start;
    end;}
    Start;
    {v0.24 moved to Start}{/v0.24 FRunningState := rsRunning;}
  end;
  {v0.21}
  {v0.24}{/v0.24
  NotifyForms(cmAcquisitionRun);}
  {/v0.21
  if FForm <> nil then begin
    SendMessage(FForm.Handle, WM_APPMESSAGE, cmAcquisitionRun, longint(Self));
  end;}
end;

{v0.21}
procedure TAcqInfo.NotifyForms(cm:integer);
begin
  if FForm <> nil then begin
    SendMessage(FForm.Handle, WM_APPMESSAGE, cm, Longint(Self));
  end;
end;
{/v0.21}

procedure TAcqInfo.Stop;
begin
  if RunningState <> rsStopped then begin
    FRunningState := rsStopped;
    try
      PortClose;
    except;
    end;
    {v0.21}
    DeviceStop;
    {/v0.21
    case DeviceMode of
      dmExtDev: ExtDev.Stop;
    end;}

    try
      {v0.28}{ulantype}
      Data.ULA.DateTime := DateTimeToFileDate(Now);
      {/v0.28}
      if Data.Spectrum <> nil then begin
        {v0.28}
        if not Data.ULA.AutodetectLater then
        {/v0.28}
        begin
          if not NoAutoPeakAutodetect then
            Data.Spectrum.Autodetect;
        end;
      end;
    except
      {v0.21}
      {FLastSuspendTime := 0;
      FAllSuspendInterval := 0;
      FAllAcqTime := 0;
      FLastResumeTime := 0;
      FSuspendCount := 0;
      FNextAcqTime := 0;}
      {/v0.21}
    end;
  end;
  {v0.21}
  NotifyForms(cmAcquisitionStop);
  {/v0.21
  if FForm <> nil then begin
    SendMessage(FForm.Handle, WM_APPMESSAGE, cmAcquisitionStop, longint(Self));
  end;}
end;

{v0.21}
procedure TAcqInfo.DoModeTimers;
begin
  {v0.53}
  FGotDataInTimer := false;
  {/v0.53}
  case DeviceMode of
    dmPasive: DoApexTimer;
    dmUlan: DoUlanTimer;
    dmExtDev: DoExtDevTimer;
  else
    raise EAcqInfo.Create('Invalid DeviceMode');
  end;
  {v0.53}
  if (not FGotDataInTimer) and  SimulatingData and
     FNoDataDetected and (FRunningState = rsRunning)
  then
    DoSimulationTimer;
  {/v0.53}
end;
{/v0.21}

procedure TAcqInfo.DoTimer;
  { called from window timer }
begin
  {v0.21}
  if FIsInTimer then
    exit;
  FIsInTimer := true;
  try
  {/v0.21}
  if RunningState = rsStopped then
  begin
    if DeviceMode = dmUlan then begin
      RcvRun(SII);
    end;
    exit;
  end else if RunningState = rsRunning then begin
    CheckStop;
    {v0.21}
    CheckNoData;
    {/v0.21}
  end;
  {v0.21}
  DoModeTimers;
  {/v0.21
  case DeviceMode of
    dmApex: DoApexTimer;
    dmUlan: DoUlanTimer;
    dmExtDev: DoExtDevTimer;
  else
    raise EAcqInfo.Create('Invalid DeviceMode');
  end;
  }

  {v0.21}
  finally
    FIsInTimer := false;
  end;
  {/v0.21}
end;

procedure TAcqInfo.DoApexTimer;
var
  ai:TApexPacketInfo;
begin
  if PacketFifo <> nil then begin
    while ListRecGet(PacketFifo, ai) do begin
      DoApexPacket(ai);
      {v0.53}
      FGotDataInTimer := true;
      {/v0.53}
    end;
  end;
end;

procedure TAcqInfo.DoUlanTimer;
var
  ai:TApexPacketInfo;
begin
  if PacketFifo = nil then
    exit;
  if SII = nil then
    exit;
  RcvRun(SII);
  while RcvGetPktInfo(SII, ai) do begin
    {v0.53}
    FGotDataInTimer := true;
    {/v0.53}
    case ai.Pkt.Data[ApexEventIDPos] of
      eiMark: begin
        {v0.51pi}
        {Check for dmUlan that pktinfo.Pkt.X2 is equal
         to address of some of devices grouped into channel}
        {/v0.51}
        {v0.53}
        DoMark(ai);
        {/v0.53
        if Assigned(FOnMark) then begin
          FOnMark(Self);
        end else
        begin
          if RunningState =  rsReadyToRun then
            Run;
        end;}
      end;
    else
      {v0.50}
      if RunningState <> rsSuspended then
      {/v0.50}
        DoApexPacket(ai);
    end;

  end;
end;

{v0.53}
procedure TAcqInfo.DoSimulationTimer;
var
  dr: TExpPoint;
  int: single;
  di: integer;
  uladrc: TULADRc;

  i, cnt: integer;
begin
  di := 0;
  uladrc := Data.ULAD[di];
  int := uladrc.PointTimeInterval;
  cnt := TimerInterval div round(int);
  if cnt = 0 then
    cnt := 1;
  PointsAddStart;
  for i := 0 to cnt - 1 do begin
    uladrc.CurTime := uladrc.CurTime + int;
    dr.X := uladrc.CurTime / 1000;
    dr.Y := GetDebugAbsorbance(0, dr.X) * uladrc.YCoef - ZeroValue;
    PointsAddAdd(0, dr);
  end;
  PointsAddStop;
end;

procedure TAcqInfo.DoMark(const AI: TApexPacketInfo);
var m: TModule;
begin
  {Check for dmUlan that pktinfo.Pkt.X2 is equal
         to address of some of devices grouped into channel}
  if DeviceMode = dmUlan then begin          {fieldbyname ulantype apextype}
    if not Channel.DeviceFindByAddr(AI.Pkt.X2, m) then  {channelsu}
      exit;
  end;
  if Assigned(FOnMark) then begin
    FOnMark(Self);
  end else begin
    if RunningState =  rsReadyToRun then
      Run;
  end;
end;
{/v0.53}

procedure TAcqInfo.DoSuspendResume;
      { called when menuitem Suspen/Resume clicked, calls one of the following }
begin
  if RunningState = rsSuspended then begin
    Resume
  end else begin
    {v0.50}
    if ShowMessage(GetTxt({#}'Really suspend acquisition'), smYesNo, 0) <> cmYes then
      exit;
    {/v0.50}
    Suspend;
  end;
end;

procedure TAcqInfo.Suspend;
var t: longint;
begin
  {v0.21}
  DeviceStop;
  FRunningState := rsSuspended;
  DoModeTimers;{read+ignores=clear device data buffers }
  t := mstime;
  if (FLastDataTime > 0) and (FSuspendedForNoData) then begin
    t := FLastDataTime;
  end;
  FAllAcqTime := {v0.21}FLastExpPoint.X{FLastDataX}{/v0.21 FAllAcqTime + (t - ZeroTime)/1000};
  FLastSuspendTime := t / 1000;
  {/v0.21
  DoTimer;
  FRunningState := rsSuspended;
  if FLastSuspendTime = 0 then begin
    FAllAcqTime := FAllAcqTime + (mstime - ZeroTime)/1000;
  end else begin
    FAllAcqTime := FAllAcqTime + (mstime/1000 - FLastResumeTime);
  end;
  FLastSuspendTime := mstime / 1000;
  }
  inc(FSuspendCount);
  {v0.21}
  NotifyForms(cmAcquisitionSuspended);
  {/v0.21
  if FForm <> nil then begin
    SendMessage(FForm.Handle, WM_APPMESSAGE, cmAcquisitionSuspended, longint(Self));
  end;}
end;

procedure TAcqInfo.Resume;
{var l: single;}
begin
  FLastResumeTime := mstime / 1000;
  FAllSuspendInterval := FAllSuspendInterval + (FLastResumeTime - FLastSuspendTime);
  {v0.21}
  DeviceStart;
  {/v0.21}
  {v0.31}
  DoStartResume;
  {/v0.31
  Start;}
  {v0.24 moved to Start}{/v0.24 FRunningState := rsRunning;}
  {v0.21}
  NotifyForms(cmAcquisitionResumed);
  FSuspendedForNoData := false;
  {/v0.21
  if FForm <> nil then begin
    SendMessage(FForm.Handle, WM_APPMESSAGE, cmAcquisitionResumed, longint(Self));
  end;}
end;

function TAcqInfo.GetFMH: TFileMenuHandler;
begin
 with Application.MainForm as TMainForm do
   GetFMH := FMH;
end;

function TAcqInfo.CanClose: boolean;
begin
  Result := true;
  if not VerifyAbort then
    exit;
  if RunningState <> rsStopped then begin
    if ShowMessage(GetTxt({#}'Abort acquisition?'), smNoYes, 0) <> cmYes then begin
      Result := false;
      exit;
    end;
    Stop;
  end;
  if (Data <> nil) (*v0.30 moved to TAcqData*) then begin
    CanClose := Data.CanClose;
  end;
  (*/v0.30 and (Data.ULF <> nil) then begin
    if (pos('NONAME', Data.ULF.FileName) <> 0) then
    begin
      if Data.{v0.24}DataSize{/v0.24 ULAD.Data.Size} > 0 then begin
        if not FMH.SaveAs then begin
          if ShowMessage(GetTxt({#}'Discard data?'), smNoYes, 0) <> cmYes then begin
            Result := false;
          end else begin
            Data.AcquiredDataDiscard;
          end;
        end;
      end;
    end else begin
      if Data.ULF.Modified then begin
        if Data.ULF.ReadOnly then begin
          case ShowMessage(Data.ULF.FileName + ' ' + GetTxt({#}'was opened Read Only but modified.') +
            ' ' + GetTxt({#}'Save to other file?'), smYesNoCancel, 0) of
            cmCancel: CanClose := false;
            cmYes: begin
              if not FMH.SaveAs then begin
                CanClose := false;
              end else begin
                Data.ULF.Modified := false;
              end;
            end;
            cmNo: Data.ULF.Modified := false;
          end;
        end else begin
          case ShowMessage(Data.ULF.FileName, smFileModifiedSave,0) of
            cmCancel: CanClose := false;
            cmNo: Data.ULF.Modified := false;
          end;
        end;
      end;
    end
  end;
  *)
end;

procedure TAcqInfo.PointsAddStart;
begin
  FRecentPoints.Size := 0;
end;

procedure TAcqInfo.PointsAddAdd(ADataIndex: integer; dr: TExpPoint);
var
  t: single;
  x: single;

  {v0.24}
  rep: TRecentExpPoint;
  {/v0.24}
begin
  FLastDataTime := mstime;
  FNoDataInLimit := FLastDataTime + FDataWaitTimeout;
  {/v0.21}
  if (FSampleInt <> 0) then begin
    t := dr.X * 1000;
    if t >= FNextAcqTime then begin
      FNextAcqTime := t + FSampleInt;
    end else begin
      exit;
    end;
  end;
  if FSuspendCount > 0 then begin
    {v0.21}
    if dr.X = 0 then
      exit;
    x := dr.X + FAllAcqTime;
    {/v0.21
      x := dr.X  - FAllSuspendInterval;
      if x < FAllAcqTime then
        exit;
    end;}
    dr.X := x;
  end;
  {v0.21}
  FLastExpPoint := dr;
  {FLastDataX := dr.X;}
  {/v0.21}
  {v0.24}
  Data.DataIndex := ADataIndex;
  Data.AddPoint(dr);
  rep.Point := dr;
  rep.Index := ADataIndex;
  FRecentPoints.WriteBuffer(rep, sizeof(rep));
  {/v0.24
  Data.AddPoint(dr);
  FRecentPoints.WriteBuffer(dr, sizeof(dr));}
end;

procedure TAcqInfo.PointsAddStop;
begin
  if FRecentPoints.Size > 0 then begin
    {v0.21}
    NotifyForms(cmExpPointsAdded);
    {/v0.21
    if FForm <> nil then begin
      SendMessage(FForm.Handle, WM_APPMESSAGE, cmExpPointsAdded, longint(Self));
    end;}
  end;
end;

procedure TAcqInfo.DoApexPacket(var ai:TApexPacketInfo);
var
  dr: TExpPoint;
{  sr: TScreenPoint;}
  i: integer;
  values: PApexValues;
  {v0.31}
  di:integer;
  int: single;
  uladrc: TULADRc;
  {/v0.31}

{  acq: boolean;}
  {
  function MakeLogLine(var ai:TApexPacketInfo):string;
  var
    s:string;
    l:single;
    b:array[0..3]of byte absolute l;

  begin
    s := '';
    s := RecToHex(ai.Pkt.Data[ApexSourceIDPos], 3) + ' ' +
      RecToHex(ai.Pkt.Data[ApexFootPos], 3) + ' ' +
      RecToHex(ai.pkt.Data[ApexValuesPos], 4) + ' ';
    s := s + bin(b[0]) + ' ' + bin(b[1]) + ' ' +bin(b[2])+ ' ' +bin(b[3]);
    MakeLogLine := s;
  end;
   }
begin
  {
  if LogPackets then begin
    SpectrumMemo.Lines.Add(MakeLogLine(ai));
  end;}
  if Data <> nil then begin
    if not ai.ScanningValues then
      values := @ai.Pkt.Data[ApexValuesPos]
    else
      values := @ai.Pkt.Values;
    {v0.31}
    di := Data.GetAddrULADIndex(ai.Pkt.X2);{??X1??!!!};
    uladrc := Data.ULAD[di];
    int := uladrc.PointTimeInterval;
    {/v0.31}

    PointsAddStart;
    for i := 0 to ApexPacketValueCount - 1 do begin
      {v0.31}
      uladrc.CurTime := uladrc.CurTime + int;
      dr.X := uladrc.CurTime / 1000;
      {/v0.31
      CurUlanTime := CurUlanTime + UlanPointTimeInterval;
      dr.X := CurUlanTime / 1000;}
      dr.Y := (values^[i]) {v0.31}* uladrc.YCoef{/v0.31} - ZeroValue;
      PointsAddAdd({v0.31}di{/v0.31 0}, dr);
      {if (FSampleInt <> 0) then begin
        if CurUlanTime >= FNextAcqTime then begin
          FNextAcqTime := CurUlanTime + FSampleInt;
        end else begin
          acq := false
        end;
      end;
      if acq then
      begin
        Data.AddPoint(dr);
        FRecentPoints.WriteBuffer(dr, sizeof(dr));
      end;}
    end;
    {v0.41}
    {FCurTime := dr.X;}
    {/v0.41}
    PointsAddStop;
  end;
{  DrawPoints(PaintBox.Canvas, ScreenDisp);}
end;

procedure TAcqInfo.DoExtDevTimer;
var
  dr: TExpPoint;
{  sr: TScreenPoint;}
begin
  if Data = nil then
    exit;
  PointsAddStart;
  while ExtDev.ReadPoint(dr) = 0 do begin
    {v0.53}
    FGotDataInTimer := true;
    {/v0.53}
    {v0.65}
    FLastExtDevReadPointMSTime := mstime;
    {/v0.65}
    if FRunningState = rsRunning then
    begin
      PointsAddAdd(0, dr);
    end;
  end;
  PointsAddStop;
  {v0.65}
  if FGotDataInTimer or ((mstime - FLastExtDevReadPointMSTime) > 1000) then begin
    ExtDevReqPkt;
  end;
  {/v0.65}
end;
{/v0.19}

function TAcqInfo.GetPortDeviceName: shortstring;
begin
  Result := PortName + '.' + ExtDevDeviceName;
end;

function TAcqInfo.DevicePortInUse(APortDeviceName: shortstring): boolean;
begin
  if ExtDev <> nil then begin
    Result := PortDeviceName = APortDeviceName;
  end else begin
    Result := APortDeviceName = PortName
  end;
end;


end.
