unit AcqInfou;{v0.14}

interface
uses
  {v0.15}Windows,{/v0.15}
  SysUtils, Classes, Forms,
  MyType, Conftype, ListType, Listu,
  UlanType, ApexType,
  ul_lcabs, timer,
  CommInt, ComPort,
  Spectrum, Channelsu, DevMode, ExtDevIntu, UlanGlob
  {v0.19},TVType, FileMenuHdl, ShowMsg, PortLog, LogType, Logu{/v0.19};

type
  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;
    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) }
    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) }
    FStopTime: longint;
      { if <> 0 then at this ms time the acquisiton will auto stop,
        calculated from AcqData.ULI.Duration at Start }
  protected
    function GetDeviceMode: TDeviceMode;
    function GetPortName: shortstring;
    function GetUVDetAddr: integer;
    function GetChannel: TChannel;
    function GetExtDevDrvName: TExtDevDrvName;
    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);
    procedure PointsAddStart;
    procedure PointsAddAdd(dr: TExpPoint);
    procedure PointsAddStop;
    {/v0.19}
  public
    constructor Create(AData: TAcqData);reintroduce;
    destructor Destroy;override;

    function PortOpen: boolean;
    procedure PortClose;
    procedure ResetPacketInfo;
    {v0.15}
    procedure Start;
    {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.19}
    procedure DoTimer;
      { called from specform window timer }
    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?  }
    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 SpecForm: TForm read FForm write FForm;
    property ExtDevDrvName: TExtDevDrvName read GetExtDevDrvName;
  end;

implementation
uses main, specform;

constructor TAcqInfo.Create(AData: TAcqData);
var
  dm: TDeviceMode;
  li: TListInfo;
begin
  inherited Create;
  FData := AData;
  ExtDev := nil;
  ZeroTime := mstime;
  ZeroValue := 0;
  FRunningState := rsStopped;
  CommPort := TComm.Create(nil);
  {v0.19}
  PortSetDefaults;
  {/v0.19}
  MainForm.ComPort := CommPort;
  dm := DeviceMode;
  {v0.15}
  FNextAcqTime := 0;
  FSampleInt := FData.ULI.SamplingInterval * 1000;
  {/v0.15}
  {v0.19}
  FRecentPoints := TMemoryStream.Create;
  {/v0.19}
  DeviceModeCheck(dm);
  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;
  CurUlanTime := 0;
  FillChar(li, sizeof(li), 0);
  case dm of
    dmUlan, dmApex: 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(ExtDevDrvName);
      if (ExtDev = nil) or  (ExtDev.Init('') <> 0) then begin
        raise EAcqInfo.Create('AcqInfo ExtDevInit failed.');
      end;
    end;
  else
    raise EInvalidDeviceMode.Create('Unknown DeviceMode');
  end;
end;

destructor TAcqInfo.Destroy;
begin
  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.GetExtDevDrvName: TExtDevDrvName;
begin
  Result := Channel.ExtDevDrvName;
end;

procedure TAcqInfo.PortSetDefaults;
var
  op:TCommOptions;
begin
  {$IFDEF WIN32}
  with CommPort do begin
    AfterOpenState := aoSpecified;
    BaudRate := br9600;
    DataBits := da8;
    DeviceName := 'COM2';
    FlowControl := 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}

procedure TAcqInfo.ComPortReceive(Sender: TObject; Count: Integer);
var
  CommChar:Char;
  i:Word;
  b:word;
begin
  if {v0.19} RunningState in [rsStopped, rsSuspended] {/v0.19 RunningState = rsStopped} then
    exit;
  {v0.15}
  {v0.17 moved to CheckStop method, called from SpecForm.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);{commint}
      {v0.19}
      PortLogAddRecord(StrToInt(copy(CommPort.DeviceName,4,1)) ,'g', b);
      {/v0.19}
      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
        if RunningState =  rsReadyToRun then begin
          {if FForm <> nil then
            TSpectrumForm(FForm).Run;}
          Run;
        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
        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}
          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
  ExtDev.DoCharIn(CommChar);
end;

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

function TAcqInfo.PortOpen: boolean;
begin
  Result := false;
  case DeviceMode of
    dmUlan: begin
      Result := RcvInit(SII, UVDetAddr);
    end;
  else
    CommPort.OnRxChar := ComPortReceive;
    CommPort.DeviceName := PortName;
    ReadWriteComConfig(rwRead, CommPort);
    case DeviceMode of
      dmExtDev: begin
        CommPort.DTROnOpen := esOn;
        CommPort.RTSOnOpen := esOff;
      end;
    end;
    if CommPort = MainForm.ComPort then
      MainForm.ComPort := nil;
    CommPort.Open;
    Result := true;
  end;
end;

procedure TAcqInfo.PortClose;
begin
  case DeviceMode of
    dmUlan: begin
      RcvDone(SII);
    end;
  else
    //$IFDEF WIN32
    CommPort.Close;
    CommPort.OnRxChar := nil;
    ReadWriteComConfig(rwWrite, CommPort);
    if MainForm.ComPort = nil then
      MainForm.ComPort := CommPort;
    //$ELSE
    //CommPort.Port := tptNone;
    //CommPort.OnReceive := nil;
    //CommPort := nil;
    //$ENDIF
  end;
end;

procedure TAcqInfo.Start;
begin
  if FData.ULI.Duration <> 0 then begin
    FStopTime := mstime + round(FData.ULI.Duration * 1000)
      {v0.19} - round(FAllAcqTime * 1000){/v0.19};
  end else begin
    FStopTime := 0;
  end;
  {v0.15}
  if FSampleInt <> 0 then begin
    FNextAcqTime := mstime + FSampleInt;
  end;
  {/v0.15}
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
    Stop
  else
    Run;
end;

procedure TAcqInfo.Run;
var pn: shortstring;
begin
  if RunningState = rsStopped then begin
    pn := PortName;
    if SendMessage(MainForm.Handle, WM_APPMESSAGE, cmQueryRunning,
      longint(@pn)) = 1 then
    begin
      ShowMsg.ShowMessage('Acquisition is already running', smError,0);
      exit;
    end;
    if (Data <> nil) and (Data.ULAD.Data.Size <> 0) then begin
      if ShowMessage('Discard already acquired data?', smNoYes, 0) <> cmYes then
        exit;
    end;
    ResetPacketInfo;
    if not PortOpen then
      exit;
    FRunningState := rsReadyToRun;
  end else begin
    { i.e. RunningState = rsReadytorun, Run don't get called in other states }
    {ClearData;}
    ResetPacketInfo;
    ZeroTime := mstime;
    CurUlanTime := 0;
    ApexPktInfo.EndTime := ZeroTime;
    case DeviceMode of
      dmUlan: RcvSetProp(SII, rpZeroTime, @ZeroTime);
      dmExtDev: ExtDev.Start;
    end;
    Start;
    FRunningState := rsRunning;
  end;
  if FForm <> nil then begin
    {PostMessage}SendMessage(FForm.Handle, WM_APPMESSAGE, cmAcquisitionRun, longint(Self));
  end;
end;

procedure TAcqInfo.Stop;
begin
  if RunningState <> rsStopped then begin
    FRunningState := rsStopped;
    PortClose;
    {RunPanel.Hide;}
    case DeviceMode of
      dmExtDev: ExtDev.Stop;
    end;
    try
      if Data.Spectrum <> nil then begin
        Data.Spectrum.Autodetect;
      end;
    except
    end;
  end;
  {UpdateCaption;}
  if FForm <> nil then begin
    {PostMessage}SendMessage(FForm.Handle, WM_APPMESSAGE, cmAcquisitionStop, longint(Self));
  end;
end;

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

procedure TAcqInfo.DoApexTimer;
var  ai:TApexPacketInfo;
begin
  if PacketFifo <> nil then begin
    while ListRecGet(PacketFifo, ai) do begin
      DoApexPacket(ai);
    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
    case ai.Pkt.Data[ApexEventIDPos] of
      eiMark: begin
        if RunningState =  rsReadyToRun then
          Run;
      end;
    else
      DoApexPacket(ai);
    end;
  end;
end;

procedure TAcqInfo.DoSuspendResume;
      { called when menuitem Suspen/Resume clicked, calls one of the following }
begin
  if RunningState = rsSuspended then
    Resume
  else
    Suspend;
end;

procedure TAcqInfo.Suspend;
begin
  DoTimer;{read-clear device data buffers}
  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);
  if FForm <> nil then begin
    {PostMessage}SendMessage(FForm.Handle, WM_APPMESSAGE, cmAcquisitionSuspended, longint(Self));
  end;
end;

procedure TAcqInfo.Resume;
var l:single;
begin
  FLastResumeTime := mstime / 1000;
  FAllSuspendInterval := FAllSuspendInterval + FLastResumeTime - FLastSuspendTime;
  Start;
  FRunningState := rsRunning;
  if FForm <> nil then begin
    {PostMessage}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('Abort acquisition?', smNoYes, 0) <> cmYes then begin
      Result := false;
      exit;
    end;
    Stop;
  end;
  if (Data <> nil) and (Data.ULF <> nil) then begin
    if (pos('NONAME', Data.ULF.FileName) <> 0) then
    begin
      if Data.ULAD.Data.Size > 0 then begin
        if not FMH.SaveAs then begin
          if ShowMessage('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 + ' was opened Read Only but modified. ' +
            '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(dr: TExpPoint);
var
  t: single;
  x: single;
begin
  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
    {if dr.X > FLastSuspendTime then}
    x := dr.X  - FAllSuspendInterval;
    if x < FAllAcqTime then
      exit;
    dr.X := x;
  end;
  Data.AddPoint(dr);
  FRecentPoints.WriteBuffer(dr, sizeof(dr));
end;

procedure TAcqInfo.PointsAddStop;
begin
  if FRecentPoints.Size > 0 then begin
    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;

{  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;
    PointsAddStart;
    for i := 0 to ApexPacketValueCount - 1 do begin
      CurUlanTime := CurUlanTime + UlanPointTimeInterval;
      dr.X := CurUlanTime / 1000;
      dr.Y := (values^[i]) - ZeroValue;
      PointsAddAdd(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;
    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
    if FRunningState = rsRunning then
    begin
      PointsAddAdd(dr);
    end;
  end;
  PointsAddStop;
end;
{/v0.19}


end.
