unit MainUnit;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, Spin, StdCtrls,
  ltrapi, ltrapitypes, ltrapidefine, ltr11api, LTR11_ProcessThread;

{ Информация, необходимая для установления соединения с модулем }
type TLTR_MODULE_LOCATION = record
  csn : string; //серийный номер крейта
  slot : Word; //номер слота
end;

const EXAMPLE_MAX_LCH_CNT = 3;

type
  TMainForm = class(TForm)
    grpDevInfo: TGroupBox;
    lblDevSerial: TLabel;
    lblVerPld: TLabel;
    edtDevSerial: TEdit;
    edtVerFirm: TEdit;
    cbbModulesList: TComboBox;
    btnRefreshDevList: TButton;
    btnOpen: TButton;
    btnStart: TButton;
    btnStop: TButton;
    grp1: TGroupBox;
    lbl1: TLabel;
    lbl2: TLabel;
    lbl3: TLabel;
    lbl4: TLabel;
    cbbAdcPrescaler: TComboBox;
    edtAdcFreq: TEdit;
    btnFindAdcFreq: TButton;
    grp2: TGroupBox;
    lbl5: TLabel;
    lbl6: TLabel;
    lbl7: TLabel;
    lbl8: TLabel;
    lbl9: TLabel;
    cbbLCh1_Channel: TComboBox;
    cbbLCh1_Range: TComboBox;
    cbbLCh1_Mode: TComboBox;
    edtLCh1_Result: TEdit;
    cbbLCh2_Channel: TComboBox;
    cbbLCh2_Range: TComboBox;
    cbbLCh2_Mode: TComboBox;
    edtLCh2_Result: TEdit;
    cbbLCh3_Channel: TComboBox;
    cbbLCh3_Range: TComboBox;
    cbbLCh3_Mode: TComboBox;
    edtLCh3_Result: TEdit;
    edtChFreq: TEdit;
    seLChCnt: TSpinEdit;
    seAdcDivider: TSpinEdit;
    procedure btnRefreshDevListClick(Sender: TObject);
    procedure btnOpenClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure btnFindAdcFreqClick(Sender: TObject);
    procedure btnStartClick(Sender: TObject);
    procedure btnStopClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);



  private
    { Private declarations }
    ccbChannels : array[0..EXAMPLE_MAX_LCH_CNT-1] of TComboBox;
    ccbRanges : array[0..EXAMPLE_MAX_LCH_CNT-1] of TComboBox;
    ccbModes : array[0..EXAMPLE_MAX_LCH_CNT-1] of TComboBox;
    edtChResults : array[0..EXAMPLE_MAX_LCH_CNT-1] of TEdit;


    ltr11_list: array of TLTR_MODULE_LOCATION; //список найденных модулей
    hltr11 : TLTR11; // Описатель модуля, с которым идет работа
    threadRunning : Boolean; // Признак, запущен ли поток сбора данных
    thread: TLTR11_ProcessThread; //Объект потока для выполнения сбора данных

    procedure updateControls();
    procedure refreshDeviceList();
    procedure closeDevice();
    procedure OnThreadTerminate(par : TObject);
  public
    { Public declarations }
  end;

var
  MainForm: TMainForm;

implementation

{$R *.dfm}
procedure TMainForm.refreshDeviceList();
var
  srv : TLTR; //Описатель для управляющего соединения с LTR-сервером
  crate: TLTR; //Описатель для соединения с крейтом
  res, crates_cnt, crate_ind, module_ind, modules_cnt : integer;
  serial_list : array [0..LTR_CRATES_MAX-1] of string; //список номеров керйтов
  mids : array [0..LTR_MODULES_PER_CRATE_MAX-1] of Word; //список идентификаторов модулей для текущего крейта
begin
  //обнуляем список ранее найденных модулей
  modules_cnt:=0;
  cbbModulesList.Items.Clear;
  SetLength(ltr11_list, 0);

  // устанавливаем связь с управляющим каналом сервера, чтобы получить список крейтов
  LTR_Init(srv);
  res:=LTR_OpenSvcControl(srv, LTRD_ADDR_DEFAULT, LTRD_PORT_DEFAULT);
  if res <> LTR_OK then
    MessageDlg('Не удалось установить связь с сервером: ' + LTR_GetErrorString(res), mtError, [mbOK], 0)
  else
  begin
    //получаем список серийных номеров всех подключенных крейтов
    res:=LTR_GetCrates(srv, serial_list, crates_cnt);
    //серверное соединение больше не нужно - можно закрыть
    LTR_Close(srv);

    if (res <> LTR_OK) then
      MessageDlg('Не удалось получить список крейтов: ' + LTR_GetErrorString(res), mtError, [mbOK], 0)
    else
    begin
      for crate_ind:=0 to crates_cnt-1 do
      begin
        //устанавливаем связь с каждым крейтом, чтобы получить список модулей
        LTR_Init(crate);
        res:=LTR_OpenCrate(crate, LTRD_ADDR_DEFAULT, LTRD_PORT_DEFAULT,
                           LTR_CRATE_IFACE_UNKNOWN, serial_list[crate_ind]);
        if res=LTR_OK then
        begin
          //получаем список модулей
          res:=LTR_GetCrateModules(crate, mids);
          if res = LTR_OK then
          begin
              for module_ind:=0 to LTR_MODULES_PER_CRATE_MAX-1 do
              begin
                //ищем модули LTR11
                if mids[module_ind]=LTR_MID_LTR11 then
                begin
                    // сохраняем информацию о найденном модуле, необходимую для
                    // последующего установления соединения с ним, в список
                    modules_cnt:=modules_cnt+1;
                    SetLength(ltr11_list, modules_cnt);
                    ltr11_list[modules_cnt-1].csn := serial_list[crate_ind];
                    ltr11_list[modules_cnt-1].slot := module_ind+LTR_CC_CHNUM_MODULE1;
                    // и добавляем в ComboBox для возможности выбора нужного
                    cbbModulesList.Items.Add('Крейт ' + ltr11_list[modules_cnt-1].csn +
                                            ', Слот ' + IntToStr(ltr11_list[modules_cnt-1].slot));
                end;
              end;
          end;
          //закрываем соединение с крейтом
          LTR_Close(crate);
        end;
      end;
    end;

    cbbModulesList.ItemIndex := 0;
    updateControls;

  end;
end;

procedure TMainForm.updateControls();
var
  module_opened, devsel, cfg_en: Boolean;
  ch : Integer;
begin
  module_opened:=LTR11_IsOpened(hltr11)=LTR_OK;
  devsel := (Length(ltr11_list) > 0) and (cbbModulesList.ItemIndex >= 0);

   //обновление списка устройств и выбор можно делать только пока не открыто конкретное устройство
  btnRefreshDevList.Enabled := not module_opened;
  cbbModulesList.Enabled := not module_opened;

  //установить связь можно только если выбрано устройство
  btnOpen.Enabled := devsel;
  if module_opened then
    btnOpen.Caption := 'Закрыть соединение'
  else
    btnOpen.Caption := 'Установить соединение';


  btnStart.Enabled := module_opened and not threadRunning;
  btnStop.Enabled := module_opened and threadRunning;

  //изменение настроек возможно только при открытом устройстве и не запущенном сборе
  cfg_en:= module_opened and not threadRunning;

  cbbAdcPrescaler.Enabled := cfg_en;
  seAdcDivider.Enabled := cfg_en;
  btnFindAdcFreq.Enabled := cfg_en;
  edtAdcFreq.Enabled := cfg_en;

  seLChCnt.Enabled := cfg_en;
  for ch:=0 to EXAMPLE_MAX_LCH_CNT-1 do
  begin
      ccbChannels[ch].Enabled := cfg_en;
      ccbRanges[ch].Enabled := cfg_en;
      ccbModes[ch].Enabled := cfg_en;
  end;

end;

procedure TMainForm.closeDevice();
begin
  // остановка сбора и ожидание завершения потока
  if threadRunning then
  begin
    thread.stop:=True;
    thread.WaitFor;
  end;

  LTR11_Close(hltr11);
end;

//функция, вызываемая по завершению потока сбора данных
//разрешает старт нового, устанавливает threadRunning
procedure TMainForm.OnThreadTerminate(par : TObject);
begin
    if thread.err <> LTR_OK then
    begin
        MessageDlg('Сбор данных завершен с ошибкой: ' + LTR11_GetErrorString(thread.err),
                  mtError, [mbOK], 0);
    end;

    threadRunning := false;
    updateControls;
end;


procedure TMainForm.btnRefreshDevListClick(Sender: TObject);
begin
  refreshDeviceList;
end;



procedure TMainForm.btnOpenClick(Sender: TObject);
var
  location :  TLTR_MODULE_LOCATION;
  res : Integer;
begin
  // если соединение с модулем закрыто - то открываем новое
  if LTR11_IsOpened(hltr11)<>LTR_OK then
  begin
    // информацию о крейте и слоте берем из сохраненного списка по индексу
    // текущей выбранной записи
    location := ltr11_list[ cbbModulesList.ItemIndex ];
    LTR11_Init(hltr11);
    res:=LTR11_Open(hltr11, LTRD_ADDR_DEFAULT, LTRD_PORT_DEFAULT, location.csn, location.slot);
    if res<>LTR_OK then
      MessageDlg('Не удалось установить связь с модулем: ' + LTR11_GetErrorString(res), mtError, [mbOK], 0);

    if res=LTR_OK then
    begin
      // чтение информации из Flash-памяти (включая калибровочные коэффициенты)
      res:=LTR11_GetConfig(hltr11);
      if res <> LTR_OK then
        MessageDlg('Не удалось прочитать конфигурацию из модуля: ' + LTR11_GetErrorString(res), mtError, [mbOK], 0);
    end;

    if res=LTR_OK then
    begin
      edtDevSerial.Text := String(hltr11.ModuleInfo.Serial);
      edtVerFirm.Text := IntToStr((hltr11.ModuleInfo.Ver shr 8) AND $FF) + '.' +
                          IntToStr(hltr11.ModuleInfo.Ver AND $FF);
    end
    else
    begin
      LTR11_Close(hltr11);
    end;
  end
  else
  begin
    closeDevice;
  end;

  updateControls;
end;

procedure TMainForm.FormCreate(Sender: TObject);
begin
  ccbChannels[0]  := cbbLCh1_Channel;
  ccbRanges[0]    := cbbLCh1_Range;
  ccbModes[0]     := cbbLCh1_Mode;
  edtChResults[0] := edtLCh1_Result;

  ccbChannels[1]  := cbbLCh2_Channel;
  ccbRanges[1]    := cbbLCh2_Range;
  ccbModes[1]     := cbbLCh2_Mode;
  edtChResults[1] := edtLCh2_Result;

  ccbChannels[2]  := cbbLCh3_Channel;
  ccbRanges[2]    := cbbLCh3_Range;
  ccbModes[2]     := cbbLCh3_Mode;
  edtChResults[2] := edtLCh3_Result;

  LTR11_Init(hltr11);
  refreshDeviceList;
end;

procedure TMainForm.btnFindAdcFreqClick(Sender: TObject);
var
  freq : Double;
begin
  // Подбор по частоте АЦП значения делителей 
  freq := StrToFloat(edtAdcFreq.Text);
  LTR11_FindAdcFreqParams(freq, hltr11.ADCRate.prescaler, hltr11.ADCRate.divider, freq);
  seAdcDivider.Value := hltr11.ADCRate.divider;
  cbbAdcPrescaler.Text := IntToStr(hltr11.ADCRate.prescaler);
  edtAdcFreq.Text := FloatToStr(freq);
end;

procedure TMainForm.btnStartClick(Sender: TObject);
var
  res, ch, phy_ch, range, mode : Integer;
begin
  res:=LTR_OK;

  { Сохраняем значения из элементов управления в соответствующие
    поля описателя модуля. Для простоты здесь не делается доп. проверок, что
    введены верные значения... }
  hltr11.ADCRate.prescaler := StrToInt(cbbAdcPrescaler.Text);
  hltr11.ADCRate.divider   := seAdcDivider.Value;
  hltr11.LChQnt := seLChCnt.Value;

  for ch:=0 to hltr11.LChQnt-1 do
  begin
    phy_ch := ccbChannels[ch].ItemIndex;
    range  := ccbRanges[ch].ItemIndex;
    mode   := ccbModes[ch].ItemIndex;

    if (phy_ch < 0) or (phy_ch >= 32) or (range < 0) or (mode < 0) then
      res := LTR_ERROR_PARAMETERS
    else
      hltr11.LChTbl[ch] := LTR11_CreateLChannel(phy_ch, mode, range);
  end;


  if res = LTR_OK then
      res := LTR11_SetADC(hltr11);

  if res = LTR_OK then
  begin
    if thread <> nil then
    begin
      FreeAndNil(thread);
    end;

    thread := TLTR11_ProcessThread.Create(True);
    { Так как структура должна быть одна и та же, что используемая потоком,
     что в классе основного окна, то вынуждены передать ее как pointer }
    thread.phltr11 := @hltr11;
    { Сохраняем элементы интерфейса, которые должны изменяться обрабатывающим
      потоком в класс потока }
    for ch:=0 to EXAMPLE_MAX_LCH_CNT-1 do
    begin
      thread.edtChAvg[ch] := edtChResults[ch];
      edtChResults[ch].Text := '';
    end;


    { устанавливаем функцию на событие завершения потока (в частности,
    чтобы отследить, если поток завершился сам из-за ошибки при сборе
    данных) }
    thread.OnTerminate := OnThreadTerminate;
    thread.Resume; //для Delphi 2010 и выше рекомендуется использовать Start
    threadRunning := True;
  end;

  if res <> LTR_OK then
      MessageDlg('Не удалось установить настройки: ' + LTR11_GetErrorString(res), mtError, [mbOK], 0);
      
  updateControls;
end;

procedure TMainForm.btnStopClick(Sender: TObject);
begin
  // устанавливаем запрос на завершение потока
  if threadRunning then
      thread.stop := true;

  btnStop.Enabled := false;
end;

procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  closeDevice;
end;

end.
