{
    This file is part of SkantiControl

    Skanti TRP8000 series control program (CU8000 control unit)
    This program was developed for a CU8000 control unit marked
    TRP 8255 S R GB1. May work or not with other units. The CU
    firmware version can be either 80R or 92.

    Copyright (C) 2012-2025 G. Perotti, I1EPJ, i1epj@aricasale.it

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/.


    Scan a frequency interval and graph the results
}

{$include defines.inc}

unit uscan;

{$mode objfpc}

interface

uses
  Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, StdCtrls,
  JLabeledIntegerEdit, JButton, TAGraph, TASeries, lclproc, LazUtilities, StrUtils;

type

  { TFSCAN }

  TFSCAN = class(TForm)
    BLOADCSV: TJButton;
    BSAVESCAN: TJButton;
    BCLOSEW: TJButton;
    BSTARTScaN: TJButton;
    BSTOPSCAN: TJButton;
    BCLEARSCAN: TJButton;
    CBSPKR: TComboBox;
    DSAVECSV: TSaveDialog;
    LBSPKR: TLabel;
    DOPENCSV: TOpenDialog;
    ScanPlot: TChart;
    CBBANDW: TComboBox;
    CBMODE: TComboBox;
    LBBANDW: TLabel;
    LBMODE: TLabel;
    SignalLevel: TLineSeries;
    IEFSTART: TJLabeledIntegerEdit;
    IEFSTOP: TJLabeledIntegerEdit;
    IEFSTEP: TJLabeledIntegerEdit;
    procedure BLOADCSVClick(Sender: TObject);
    procedure BSAVESCANClick(Sender: TObject);
    procedure BCLEARSCANClick(Sender: TObject);
    procedure BCLOSEWClick(Sender: TObject);
    procedure BSTARTScaNClick(Sender: TObject);
    procedure BSTOPSCANClick(Sender: TObject);
    procedure CBMODEChange(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    function SetupRTX: boolean;
    function SetupChart: boolean;
  private

  public

  end;

var
  FSCAN: TFSCAN;
  StopScan: boolean = FALSE;
  SaveStatusTimer: boolean;
  SaveStatusReading: boolean;

// Messages - Declared here for i18n
resourcestring
  CANTCREATEFILE = 'Can''t create file %s.';
  CANTOPENFILE = 'Can''t open file %s.';
  CANTWRITEFILE = 'Can''t write file %s.';
  CANTREADFILE = 'Can''t read file %s.';
  ERRORSCANNING = 'Error during scan, aborted.';
  STARTSCAN = 'Start scan';
  SOMECOMMANDSFAILED = 'One or more commands failed.';
  SCANNING = 'Scanning%s(%d/%d)';
  STOPGREATERSTART = 'Stop frequency must be greater that start frequency.';
  SCANCSVFILENAME = 'Scan-%s-%d-%d.csv';
  SOFF = 'Off';
  SON = 'On';
  SWIDE = 'Wide';
  SINTERMEDIATE = 'Intermediate';
  SNARROW = 'Narrow';
  SVERYNARROW = 'Very narrow';
  SETUPNOTFOUND = 'SETUP line not found, not loading file.';
  USINGPROGSTEP = 'Using programmable step feature, tuning speed reduced';
implementation

{$R *.lfm}

uses skanti;

{ TFSCAN }

function TFSCAN.SetupRTX: boolean;

var tunestep: integer;
    Ok: boolean = TRUE;

begin
  with TRP do begin
    // Read required tune step
    tunestep := IEFSTEP.Value;

    if (tunestep=1000) or (tunestep=100) or (tunestep=10) then begin
      // use default steps
      case tunestep of
        1000: Ok := Ok and SendCommand(TuneStep1k,'');
        100: Ok := Ok and SendCommand(TuneStep100,'');
        10: Ok := Ok and SendCommand(TuneStep10,'');
      end;
    end else begin
      // Warn user
      MsgToShow := USINGPROGSTEP;

      // Enable programmable step
      EnableProgTuneStep;

      tunestep := IEFSTEP.Value div 100; // Make 100 Hz units

      // Set and enable programmable step value
      if tunestep = 0 then tunestep := 1;
      IEFSTEP.Value := 100*tunestep;

      Ok := Ok and SendCommand(Read_Set_Step,IntTostr(tunestep)+stCR);
      Ok := Ok and SendCommand(TuneStepProg,'');
    end;

    // Set mode
    case CBMODE.Itemindex of
      0: Ok := Ok and SendCommand(Mode_AM,'');
      1: Ok := Ok and SendCommand(Mode_CW,'');
      2: Ok := Ok and SendCommand(Mode_USB,'');
      3: Ok := Ok and SendCommand(Mode_LSB,'');
    end;

    // Set bandwidth
    case CBBANDW.Itemindex of
      0: Ok := Ok and SendCommand(Filt_Wide,'');
      1: Ok := Ok and SendCommand(Filt_Inter,'');
      2: Ok := Ok and SendCommand(Filt_Narrow,'');
      3: Ok := Ok and SendCommand(Filt_VNarrow,'');
    end;

    // Set speaker
    case CBSPKR.ItemIndex of
      0: Ok := Ok and SendCommand(Speaker_Off,'');
      1: Ok := Ok and SendCommand(Speaker_On,'');
    end;

    // Set start frequency
    Ok := Ok and SendCommand(RX_Prefix,IntToStr(IEFSTART.Value div 100) + stCR);

    // Check if setup succeeded
    if not Ok then
      ShowMessage(SOMECOMMANDSFAILED)
    else begin
      // All ok
      // Save MStatus status and enable it
      SaveStatusReading := TRP.MStatus.Checked;
      TRP.MStatus.Checked := TRUE;

      // Save status timer status and disable it
      SaveStatusTimer := TStatus.Enabled;
      TStatus.Enabled := FALSE;
    end;
    SetupRTX := Ok;
  end;
end;

// Check bounds and setup chart X axis for selected start/stop frequencies
function TFSCAN.SetupChart: boolean;

begin
  // Check bounds
  if IEFSTART.Value < 10000 then IEFSTART.Value := 100000;
  if IEFSTART.Value > 29999900 then IEFSTART.Value := 29999900;
  if IEFSTOP.Value < 10000 then IEFSTOP.Value := 100000;
  if IEFSTOP.Value > 29999900 then IEFSTOP.Value := 29999900;
  // Make 100 Hz multiples
  IEFSTART.Value := 100*(IEFSTART.Value div 100);
  IEFSTOP.Value := 100*(IEFSTOP.Value div 100);
  if IEFSTOP.Value <= IEFSTART.Value then begin
    ShowMessage(STOPGREATERSTART);
    Exit(FALSE);
  end else
    SetupChart := TRUE;

   // Setup chart
   with ScanPlot do begin
     AxisList[1].Range.Max := Double(IEFSTOP.Value)/1e6;
     AxisList[1].Range.Min := Double(IEFSTART.Value)/1e6;
     ScanPlot.Bottomaxis.Range.Usemin := TRUE;
     ScanPlot.BottomAxis.Range.Usemax := TRUE;
  end;
end;

// Start scan button
procedure TFSCAN.BSTARTSCANClick(Sender: TObject);

var level, freq, fstep, fstop: double;
    {$IFDEF TEST_CHART}
    x, y: double;
    {$ENDIF}
    nsamples: integer;
    Error: boolean = FALSE;

begin
  if not SetupChart then exit;
  if not SetupRTX then exit;
  StopScan := FALSE;
  freq := Double(IEFStart.Value)/1e6;
  fstop := IEFSTOP.Value/1e6;
  fstep := Double(IEFSTEP.Value)/1e6;
  nsamples := (IEFSTOP.Value - IEFSTART.Value) div IEFSTEP.Value;
  {$IFDEF TEST_CHART}
  y:=0;
  randomize;
  repeat
    BSTARTSCAN.LCaption.Caption := Format(SCANNING,
      [LineEnding,SignalLevel.Count,nsamples]);
    x := Random(1000);
    if x >= 990 then x := Random(10000);
    y := 0.3*(-y+x/100);
    level :=  Double(Trunc(abs(y)));
    if level > 20 then level := 20;
    SignalLevel.AddXY(freq,level);
    freq := freq + fstep;
    Application.ProcessMessages;
  until (freq >= fstop) or StopScan or Error;
  {$ELSE}
  repeat
    BSTARTSCAN.LCaption.Caption := Format(SCANNING,
      [LineEnding,SignalLevel.Count,nsamples]);

    // read level
    TRP.TStatusTimer(self);
    level := Double(s);
    SignalLevel.AddXY(freq,level);

    // tune to next frequency
    freq := freq + fstep;
    Error := not TRP.SendCommand(Tune_Up,'');
    Application.ProcessMessages;
  until (freq >= fstop) or StopScan or Error;
  if Error then
    MsgToShow := ERRORSCANNING;
  {$ENDIF}
  BSTARTSCAN.LCaption.Caption := STARTSCAN;
end;

// Stop scan button
procedure TFSCAN.BSTOPSCANClick(Sender: TObject);
begin
  StopScan := TRUE;
  BSTARTSCAN.LCaption.Caption := STARTSCAN;
end;

// Disable bandwidth selection if not AM or CW modes.
procedure TFSCAN.CBMODEChange(Sender: TObject);
begin
  if CBMode.ItemIndex in [0..1] then
    CBBANDW.Enabled := TRUE
  else
    CBBANDW.Enabled := FALSE;
end;

// Initializations at startup
procedure TFSCAN.FormCreate(Sender: TObject);
begin
  CBSPKR.Items.Clear;
  CBSPKR.Items.Add(SOFF);
  CBSPKR.Items.Add(SON);
  CBBANDW.Items.Clear;
  CBBANDW.Items.Add(SWIDE);
  CBBANDW.Items.Add(SINTERMEDIATE);
  CBBANDW.Items.Add(SNARROW);
  CBBANDW.Items.Add(SVERYNARROW);
end;

// Clear scan button
procedure TFSCAN.BCLEARSCANClick(Sender: TObject);
begin
  SignalLevel.Clear;
  ScanPlot.Bottomaxis.Range.Min := 0;
  ScanPlot.BottomAxis.Range.Max := 30;
  ScanPlot.Bottomaxis.Range.Usemin := TRUE;
  ScanPlot.BottomAxis.Range.Usemax := TRUE;
end;

// Save scan data
procedure TFSCAN.BSAVESCANClick(Sender: TObject);

var i: integer;
    f: textfile;
    s: string;
    freq,level: Double;

begin
  DSAVECSV.Filename := Format(SCANCSVFILENAME,
                    [FormatDateTime('DD-MM-YYYY-hh-mm-ss',now),IEFSTART.Value,
                    IEFSTOP.Value]);

  DSAVECSV.Initialdir := BandScansDir;
  if DSAVECSV.Execute then begin
    try
      AssignFile(f,DSAVECSV.Filename);
      Rewrite(f);
    except
      ShowMessage(Format(CANTCREATEFILE, [DSAVECSV.Filename]));
    end;
    // write this scan configuration
    WriteLn(f,Format('"SETUP";"%d:%d:%d:%d:%d:%d:"', [IEFSTART.Value, IEFSTOP.Value,
          IEFSTEP.Value,CBMode.ItemIndex,CBBANDW.ItemIndex, CBSPKR.ItemIndex]));

    // Save data points
    for i := 0 to SignalLevel.Count - 1 do begin
      freq := SignalLevel.GetXValue(i);
      level := SignalLevel.GetYValue(i);
      s := FormatFloat('00.000000',freq)+';'+FormatFloat('00',level);
      try
         WriteLn(f,s);
      except
        ShowMessage(Format(CANTWRITEFILE, [DSAVECSV.Filename]));
      end;
    end;
    CloseFile(f);
  end;
end;

// Load & display CSV data saved
procedure TFSCAN.BLOADCSVClick(Sender: TObject);

var f: textfile;
    s,st: string;
    p,v: integer;
    freq, level: Double;

begin
  DOPENCSV.Initialdir := BandScansDir;
  ScanPlot.Bottomaxis.Range.Usemin := FALSE;
  ScanPlot.BottomAxis.Range.Usemax := FALSE;
  if DOPENCSV.Execute then begin
    SignalLevel.Clear;
    try
      AssignFile(f,DOPENCSV.Filename);
      Reset(f);
    except
      ShowMessage(Format(CANTOPENFILE, [DOPENCSV.Filename]));
    end;

    // Read and set up scan configuration
    try
      ReadLn(f,s);
    except
      ShowMessage(Format(CANTREADFILE, [DOPENCSV.Filename]));
    end;
    if Pos('SETUP',s) <> 0 then begin
      p := pos(';',s);
      if p > 0 then begin
        // Remove "SETUP" string and " leaving only data
        s := copy(s, p+2, Length(s)-1);
      end;

      // Parse configuration fields
      st :=  ExtractDelimited(1,s,[':']);
      IEFSTART.Value := StrToInt(st);

      st := ExtractDelimited(2,s,[':']);
      IEFSTOP.Value := StrToInt(st);

      st := ExtractDelimited(3,s,[':']);
      IEFSTEP.Value := StrToInt(st);

      st := ExtractDelimited(4,s,[':']);
      CBMODE.ItemIndex := StrToInt(st);
      case CBMODE.ItemIndex of
        0,1: CBBANDW.Enabled := TRUE;
        2,3: CBBANDW.Enabled := FALSE;
      end;

      st := ExtractDelimited(5,s,[':']);
      CBBANDW.ItemIndex := StrToInt(st);

      st := ExtractDelimited(6,s,[':','"']);
      if TryStrToInt(st,v) then
        CBSPKR.ItemIndex := v;
    end else begin
      ShowMessage(SETUPNOTFOUND);
      exit;
    end;

    // Read and show scan data
    while not Eof(f) do begin
      try
        ReadLn(f,s);
      except
        ShowMessage(Format(CANTREADFILE, [DOPENCSV.Filename]));
      end;
      p := Pos(';',s);
      if p > 0 then begin
        freq := LazUtilities.StrToDouble(copy(s,1,p-1));
        level :=LazUtilities.StrToDouble(Copy(s,p+1,Length(s)));
        SignalLevel.AddXY(freq,level);
      end;
    end;
    CloseFile(f);
  end;
end;

// Close window button
procedure TFSCAN.BCLOSEWClick(Sender: TObject);

begin
   FSCAN.Hide;
   with TRP do begin
     RestoreState;
     TStatus.Enabled := SaveStatusTimer;
     Mstatus.Checked := SaveStatusReading;
   end;
end;

end.

