unit Unit1; 

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs,
  StdCtrls, ExtCtrls, ComCtrls, Grids, DbCtrls, Buttons, Menus, TAGraph,
  Process, StrUtils, synaser, synautil, blcksock,dateutils,math, LCLType;

type

  { TForm1 }

  TForm1 = class(TForm)
    DLBatteryCapacityComboBox: TComboBox;
    DLBatteryDurationTime: TEdit;
    DLBatteryDurationUntil: TEdit;
    DLThreshold: TEdit;
    Label26: TLabel;
    Label27: TLabel;
    Label29: TLabel;
    Label30: TLabel;
    Label31: TLabel;
    DLDBSizeProgressBarText: TLabel;
    LogFirstRecord: TButton;
    LogLastRecord: TButton;
    LogPreviousRecord: TButton;
    DLLogOneButton: TButton;
    DLEraseAllButton: TButton;
    LogNextRecord: TButton;
    DLRetrieveAllButton: TButton;
    DLTrigMinutesCurrent: TEdit;
    DLTrigSeconds: TEdit;
    DLTrigMinutes: TEdit;
    DLTrigSecondsCurrent: TEdit;
    GroupBox2: TGroupBox;
    DLCancelRetrieveButton: TButton;
    LogRecordResult: TMemo;
    DLDBSizeProgressBar: TProgressBar;
    MainMenu1: TMainMenu;
    HelpMenuItem: TMenuItem;
    AboutItem: TMenuItem;
    Label20: TLabel;
    Label21: TLabel;
    Label22: TLabel;
    Label23: TLabel;
    Label24: TLabel;
    Label25: TLabel;
    DLSaveDialog: TSaveDialog;
    Threshold: TGroupBox;
    Timer1: TTimer;
    TriggerGroup: TRadioGroup;
    UnitClock: TLabel;
    SetDeviceClock: TButton;
    Button18: TButton;
    DeviceClockGroup: TGroupBox;
    StorageGroup: TGroupBox;
    EstimatedBatteryGroup: TGroupBox;
    ResetForFirmwareProgressBar: TProgressBar;
    SelectFirmwareDialog: TOpenDialog;
    RequestButton: TButton;
    Button10: TButton;
    Button11: TButton;
    Button12: TButton;
    SelectFirmware: TButton;
    CheckLock: TButton;
    LoadFirmware: TButton;
    Button16: TButton;
    Button17: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    Button5: TButton;
    Button6: TButton;
    Button7: TButton;
    Button8: TButton;
    Button9: TButton;
    FoundDevices: TListBox;
    PCClock: TLabel;
    USBSerialNumber: TEdit;
    Edit10: TEdit;
    Edit11: TEdit;
    Edit12: TEdit;
    Edit13: TEdit;
    Edit14: TEdit;
    Edit15: TEdit;
    Edit16: TEdit;
    Edit17: TEdit;
    Edit18: TEdit;
    Edit19: TEdit;
    USBPort: TEdit;
    FirmwareFile: TEdit;
    CheckLockResult: TEdit;
    EthernetMAC: TEdit;
    EthernetIP: TEdit;
    EthernetPort: TEdit;
    RS232Baud: TEdit;
    RS232Port: TEdit;
    Edit8: TEdit;
    Edit9: TEdit;
    FindButton: TButton;
    GroupBox1: TGroupBox;
    Label10: TLabel;
    Label11: TLabel;
    Label12: TLabel;
    Label13: TLabel;
    Label14: TLabel;
    Label15: TLabel;
    Label16: TLabel;
    Label17: TLabel;
    Label18: TLabel;
    Label19: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    Label5: TLabel;
    Label6: TLabel;
    Label7: TLabel;
    Label8: TLabel;
    Label9: TLabel;
    VersionListBox: TListBox;
    ReadingListBox: TListBox;
    CommNotebook: TNotebook;
    DataNotebook: TNotebook;
    Ethernet: TPage;
    Information: TPage;
    Calibration: TPage;
    Firmware: TPage;
    Data_Logging: TPage;
    Configuration: TPage;
    GPS: TPage;
    LoadFirmwareProgressBar: TProgressBar;
    Report_Interval: TPage;
    RS232: TPage;
    StatusBar1: TStatusBar;
    USB: TPage;
    VersionButton: TButton;
    procedure AboutItemClick(Sender: TObject);
    procedure CommNotebookPageChanged(Sender: TObject);
    procedure DLBatteryCapacityComboBoxChange(Sender: TObject);
    procedure DLCancelRetrieveButtonClick(Sender: TObject);
    procedure DLEraseAllButtonClick(Sender: TObject);
    procedure DLLogOneButtonClick(Sender: TObject);
    procedure DLRetrieveAllButtonClick(Sender: TObject);
    procedure DLThresholdChange(Sender: TObject);
    procedure DLThresholdKeyPress(Sender: TObject; var Key: char);
    procedure DLTrigMinutesChange(Sender: TObject);
    procedure DLTrigMinutesKeyPress(Sender: TObject; var Key: char);
    procedure DLTrigSecondsChange(Sender: TObject);
    procedure DLTrigSecondsKeyPress(Sender: TObject; var Key: char);
    procedure FirmwareFileChange(Sender: TObject);
    procedure LogFirstRecordClick(Sender: TObject);
    procedure LogLastRecordClick(Sender: TObject);
    procedure LogNextRecordClick(Sender: TObject);
    procedure LogPreviousRecordClick(Sender: TObject);
    procedure SelectFirmwareClick(Sender: TObject);
    procedure LoadFirmwareClick(Sender: TObject);
    procedure CheckLockClick(Sender: TObject);
    procedure DataNotebookPageChanged(Sender: TObject);
    procedure RequestButtonClick(Sender: TObject);
    procedure FindButtonClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FoundDevicesClick(Sender: TObject);
    procedure InformationContextPopup(Sender: TObject; MousePos: TPoint;
      var Handled: Boolean);
    procedure SetDeviceClockClick(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure TriggerGroupClick(Sender: TObject);
    procedure VersionButtonClick(Sender: TObject);
  private
    { private declarations }
  public
    { public declarations }
    procedure FindDevices;
    procedure StatusMessage(Mstring:string; BackColor:TColor = clBtnFace);
    function SendGet(command:string; LeaveOpen:boolean = False; Timeout:Integer=1000) : string;
    function OpenComm() : boolean;
    function CloseComm() : boolean;
    procedure findeth;
    procedure findusbdarwin;
    procedure GetVersion;
    procedure ClearResults;
    procedure EstimateBatteryLife;
    procedure LogRecordGet(RecordNumber:Integer);
  end;


  FoundDevice = record
    SerialNumber : String;
    Connection : String;
    Hardware : String;
  end;


var
  Form1: TForm1;
  ser: TBlockSerial;
  EthSocket: TTCPBlockSocket;
  FoundDevicesArray: array of FoundDevice;
  DataLoggingAvailable: Boolean;
  SelectedModel:Integer;
  FirmwareFilename : AnsiString;
  PortName: AnsiString;
  //DL global variables
  DLCue: Array[0..10] of String;
  DLTrigSeconds,DLTrigMinutes: Integer;
  DLRefreshed:Boolean;
  DLCurrentRecord:Integer;
  DLCancelRetrieve:Boolean;
implementation


procedure TForm1.ClearResults;
begin

     VersionListBox.Items.Clear;
     ReadingListBox.Items.Clear;
     SelectedModel:=0;
     ResetForFirmwareProgressBar.Position:=0;
     LoadFirmwareProgressBar.Position:=0;
     StatusMessage('');
     DLRefreshed:=False;
     LogRecordResult.Clear;
     UnitClock.Caption:='';
     PCClock.Caption:='';

{	$SCLOR->Contents("");
	$SLCTR->Contents("");
	$SDCPR->Contents("");
	$SDCTR->Contents("");
	$ITiE->Contents("");
	$ITiR->Contents("");
	$IThE->Contents("");
	$IThR->Contents("");
	$StatusText->Contents("");
	$PortEntry->configure(-background=>$PortEntryBackground);
	$EthIPEntry->configure(-background=>$EthIPEntryBackground);
	$EthPortEntry->configure(-background=>$EthPortEntryBackground);
	$CheckLockResult->Contents("");
	$LRTCTime->Contents("");
	$LogMode=-1;
	$LIThresholdE->delete(0,'end');
	$LISecondsE->delete(0,'end');
	$LIMinutesE->delete(0,'end');
}
end;

// Send a command strings then return the result
function TForm1.SendGet(command:string; LeaveOpen:boolean = False; Timeout:Integer=1000) : string;
//LeaveOpen indicates that the communication port should be left open
var
   ErrorString: AnsiString;
begin
     OpenComm;
     ErrorString:='';
     //Check selected comm. tab
     case CommNotebook.PageIndex of
          0,2: begin               //USB or RS232
              if ser.CanWrite(10) then
                 //writeln('can write')
              else
                 //writeln('can not write');
              writeln(ser.InstanceActive);
              ser.SendString(command);
              SendGet:=ser.Recvstring(Timeout);
              If CompareStr(ser.LastErrorDesc,'OK')<>0 then
                 ErrorString:='Error: '+ser.LastErrorDesc;
          end;
          1: begin               //Ethernet
              EthSocket.SendString(command);
              SendGet:=EthSocket.RecvString(1000);
              If CompareStr(EthSocket.LastErrorDesc,'OK')<>0 then
                 ErrorString:=EthSocket.LastErrorDesc;
          end;
     end;

     if (not LeaveOpen) then CloseComm;
     StatusMessage('Sent: '+command+'   To: '+PortName+'   Received: '+SendGet+ErrorString);
end;

// Open the communications port
//Will only open if not already opened
function TForm1.OpenComm() : boolean;
begin
     //Check selected comm. tab
     case CommNotebook.PageIndex of
          0: begin               //USB
              ser.LinuxLock:=False; //lock file sometimes persists stuck if program closes before port
              ser.Connect(USBPort.Text);
              //sleep(1000);
              ser.config(115200, 8, 'N', SB1, False, False);
              PortName:=USBPort.Text;
          end;
          1: begin               //Ethernet
              EthSocket := TTCPBlockSocket.Create;
              EthSocket.ConvertLineEnd := True;
              EthSocket.Connect(EthernetIP.Text, EthernetPort.Text);
              PortName:=EthernetIP.Text;
          end;
          2: begin               //RS232
              ser.Connect(RS232Port.Text);
              ser.config(StrToIntDef(RS232Baud.Text,115200), 8, 'N', SB1, False, False);
              PortName:=RS232Port.Text;
          end;
     end;
     OpenComm:=True;    //Indicate success
end;
// Close the communications port
function TForm1.CloseComm() : boolean;
begin
     //Check selected comm. tab
     case CommNotebook.PageIndex of
          0,2: begin             //USB or RS232
             ser.CloseSocket;
             //ser.Free;
          end;
          1: begin               //Ethernet
              EthSocket.CloseSocket;
              EthSocket.Free;
          end;
     end;
     CloseComm:=True;       //Indicate success
end;

procedure TForm1.StatusMessage(Mstring:string; BackColor:TColor = clBtnFace);

begin
StatusBar1.Panels.Items[0].Text:=Mstring;
     //StatusBar1.SimpleText:=Mstring;
     StatusBar1.Color:=BackColor;
     //Writeln(Mstring);
     { TODO : log file}
end;

//multicast procdedure
procedure TForm1.findeth;
var
  sndsock:TUDPBlockSocket;
  buf:string;
  requeststring:Longword;
  i:Integer;
  MACstring:string;
begin
  StatusMessage('Looking for Ethernet devices');
  requeststring:=246;//f6 is the request to the Lantronix XPort
  sndsock:=TUDPBlockSocket.Create;
  try
    sndsock.EnableBroadcast(True);
    sndsock.connect('255.255.255.255','30718');
    sndsock.SendInteger(SwapEndian(requeststring));
    repeat
      buf:=sndsock.RecvPacket(1000);
      if (Length(buf) = 30) then
         begin
             MACstring:='';
             for i:=25 to 30 do
                 //Write(IntToHex(ord(buf[i]),2));
                 MACstring:=MACstring+IntToHex(ord(buf[i]),2);
             //Writeln(MACstring);
             //Writeln(sndsock.GetRemoteSinIP + ' : '+ InttoStr(sndsock.GetRemoteSinPort));
             //save found device
             with FoundDevicesArray[high(FoundDevicesArray)] do
                  begin
                       SerialNumber:=MACstring;//MAC address
                       Connection:=sndsock.GetRemoteSinIP;
                       Hardware:='Eth';
                  end;
             SetLength(FoundDevicesArray,high(FoundDevicesArray)+2);

        end;
    until (Length(buf) = 0);
    sndsock.CloseSocket;
  finally
    sndsock.free;
  end;
end;

procedure findusblinux;
//Find USB attached FTDI devices
const
  READ_BYTES = 2048;
var
  OurCommand: String;
  OutputLines: TStringList;
  MemStream: TMemoryStream;
  OurProcess: TProcess;
  NumBytes: LongInt;
  BytesRead: LongInt;
  LookForState: Integer;
  USBDeviceSerial: String;
  LinuxDeviceFile: String;
  lStrings: TStringList;
  StringPos:Integer;
begin
  // A temp Memorystream is used to buffer the output
  MemStream := TMemoryStream.Create;
  BytesRead := 0;

  lStrings := TStringList.Create;
  lStrings.Delimiter := ',';

  OurProcess := TProcess.Create(nil);
  // Recursive dir is a good example.
  OurCommand:='invalid command, please fix the IFDEFS.';
  {$IFDEF Windows}
  //Can't use dir directly, it's built in
  //so we just use the shell:
  OurCommand:='cmd.exe /c "dir /s c:\windows\"';
  {$ENDIF Windows}
  {$IFDEF Unix}
  //Needs to be tested on Linux/Unix:
  //do we need a full path or not?
  //DirCommand:=FindDefaultExecutablePath('ls') + ' --recursive --all -l /';
  //OurCommand := '/bin/ls --recursive --all -l /';
  OurCommand := 'lshal';
  {$ENDIF Unix}
  OurProcess.CommandLine := OurCommand;

  // We cannot use poWaitOnExit here since we don't
  // know the size of the output. On Linux the size of the
  // output pipe is 2 kB; if the output data is more, we
  // need to read the data. This isn't possible since we are
  // waiting. So we get a deadlock here if we use poWaitOnExit.
  OurProcess.Options := [poUsePipes];
  OurProcess.Execute;
  while OurProcess.Running do
  begin
    // make sure we have room
    MemStream.SetSize(BytesRead + READ_BYTES);

    // try reading it
    NumBytes := OurProcess.Output.Read((MemStream.Memory + BytesRead)^, READ_BYTES);
    if NumBytes > 0
    then begin
      Inc(BytesRead, NumBytes);
      //Write('.') //Output progress to screen.
    end
    else begin
      // no data, wait 100 ms
      Sleep(100);
    end;
  end;
  // read last part
  repeat
    // make sure we have room
    MemStream.SetSize(BytesRead + READ_BYTES);
    // try reading it
    NumBytes := OurProcess.Output.Read((MemStream.Memory + BytesRead)^, READ_BYTES);
    if NumBytes > 0
    then begin
      Inc(BytesRead, NumBytes);
    end;
  until NumBytes <= 0;
  if BytesRead > 0 then WriteLn;
  MemStream.SetSize(BytesRead);

  OutputLines := TStringList.Create;
  OutputLines.LoadFromStream(MemStream);
  LookForState:=0;

  for NumBytes := 0 to OutputLines.Count - 1 do
  begin
    if (LookForState=0) and
     (AnsiStartsStr('udi = ',OutputLines[NumBytes])) and
     (AnsiContainsStr(OutputLines[NumBytes],'_403_6001_')) then

    begin
        StringPos:=NPos('_403_6001_',OutputLines[NumBytes],1);
        WriteLn(StringPos);
        WriteLn(AnsiMidStr(OutputLines[NumBytes],StringPos+10,8));
        LookForState:=1;
    end;

    if (LookForState=1) and
        (AnsiContainsStr(OutputLines[NumBytes],'usb.serial')) then
    begin
        //get serial number, and remove single quotes
        lStrings.Delimiter := ',';
        lStrings.DelimitedText := OutputLines[NumBytes];
        lStrings.Delimiter := '''';
        lStrings.DelimitedText := lStrings.Strings[2];
        USBDeviceSerial:=lStrings.Strings[1];
        LookForState:=2;
    end;

    //Look for linux.device_file:
    if (LookForState=2) and
        (AnsiContainsStr(OutputLines[NumBytes],'linux.device_file')) then
    begin
        //get port, and remove single quotes
        lStrings.DelimitedText := OutputLines[NumBytes];
        LinuxDeviceFile:=lStrings.Strings[2];
        //save found device
        with FoundDevicesArray[high(FoundDevicesArray)] do
        begin
             SerialNumber:=USBDeviceSerial;
             Connection:=LinuxDeviceFile;
             Hardware:='USB';
        end;
        SetLength(FoundDevicesArray,high(FoundDevicesArray)+2);

        LookForState:=0;
    end;

  end;

  OutputLines.Free;
  OurProcess.Free;
  MemStream.Free;
end;

procedure TForm1.findusbdarwin;
{ This program demonstrates the FindFirst function }
Var Info : TSearchRec;
    Count : Longint;
    USBDeviceSerial: String;
    LinuxDeviceFile: String;
Begin
  Count:=0;
  If FindFirst ('/dev/tty.usbserial*',faAnyFile ,Info)=0 then
    begin
    Repeat
      Inc(Count);
      With Info do
        begin
        Writeln (Name:40,Size:15);
        USBDeviceSerial:=AnsiMidStr(Name,15,8);
        LinuxDeviceFile:='/dev/' + Name;
        with FoundDevicesArray[high(FoundDevicesArray)] do
        begin
             SerialNumber:=USBDeviceSerial;
             Connection:=LinuxDeviceFile;
             Hardware:='USB';
        end;
        SetLength(FoundDevicesArray,high(FoundDevicesArray)+2);
        end;
    Until FindNext(info)<>0;
    end;
  FindClose(Info);
  Writeln ('Finished search. Found ',Count,' matches');


End;

{ TForm1 }

procedure TForm1.RequestButtonClick(Sender: TObject);
var
   result:string;
   pieces: TStringList;
begin
     //Clear out existing results
     ReadingListBox.Items.Clear;

     //Try to ensure a model version has been found.
     if SelectedModel=0 then
        GetVersion;

     //Get response to "Request"
     pieces := TStringList.Create;
     pieces.Delimiter := ',';
     result:=SendGet('rx');
     StatusMessage(result);
     pieces.DelimitedText := result;

     case SelectedModel of
       3,5,6,7: begin   //3=Standard SQM-LE/LU, 5=SQM-LR, 6=SQM-LU-DL, 7 =SQM-LU-GPS
          if pieces.count=6 then
            begin
              ReadingListBox.Items.Add(Format('  Reading: %1.2fmpsas',[StrToFloatDef(AnsiMidStr(pieces.Strings[1],1,5),0)]));
              ReadingListBox.Items.Add(Format('Frequency: %dHz',      [StrToIntDef  (AnsiMidStr(pieces.Strings[2],1,10),0)]));
              ReadingListBox.Items.Add(Format('  Counter: %dcounts',  [StrToIntDef  (AnsiMidStr(pieces.Strings[3],1,10),0)]));
              ReadingListBox.Items.Add(Format('     Time: %1.3fs',    [StrToFloatDef(AnsiMidStr(pieces.Strings[4],1,11),0)]));
              ReadingListBox.Items.Add(Format('     Tint: %1.1fC',    [StrToFloatDef(AnsiMidStr(pieces.Strings[5],1,5),0)]));
            end
          else
              ReadingListBox.Items.Add('No response');
       end;
       8: begin         //8=Magnetometer
          if pieces.count=3 then
          begin
            ReadingListBox.Items.Add(Format('M1: %dc',    [StrToIntDef(AnsiMidStr(pieces.Strings[1],1,10),0)]));
            ReadingListBox.Items.Add(Format('T1: %10.7fC',[StrToFloatDef(pieces.Strings[2],0)/128.0]));
          end
          else
            ReadingListBox.Items.Add('No response');
       end;
       1: begin         //1=ADA
          if pieces.count=8 then
            begin
              ReadingListBox.Items.Add(Format('Frequency: %dHz',      [StrToIntDef(AnsiMidStr(pieces.Strings[1],1,10),0)]));
              ReadingListBox.Items.Add(Format(' Counter1: %dcounts',  [StrToIntDef(AnsiMidStr(pieces.Strings[2],1,10),0)]));
              ReadingListBox.Items.Add(Format('    Time1: %1.3fs',    [StrToFloatDef(AnsiMidStr(pieces.Strings[3],1,11),0)]));
              ReadingListBox.Items.Add(Format(' Counter2: %dcounts',  [StrToIntDef(AnsiMidStr(pieces.Strings[4],1,10),0)]));
              ReadingListBox.Items.Add(Format('    Time2: %1.3fs',    [StrToFloatDef(AnsiMidStr(pieces.Strings[5],1,11),0)]));
            end
          else
              ReadingListBox.Items.Add('No response');
       end;
       4: begin         //4=Colour

       end;
     otherwise
        ReadingListBox.Items.Add('Could not get version.');
     end;
end;

procedure TForm1.SelectFirmwareClick(Sender: TObject);
begin
   if SelectFirmwareDialog.Execute then
   begin
     FirmwareFilename := SelectFirmwareDialog.Filename;
     FirmwareFile.Text:=FirmwareFilename;
     LoadFirmware.Enabled:=not(FirmwareFile.Text='');
     StatusMessage('Selected file: '+FirmwareFile.Text);
   end;

end;

procedure TForm1.FirmwareFileChange(Sender: TObject);
begin
     LoadFirmware.Enabled:=not(FirmwareFile.Text='');
end;

procedure TForm1.LogFirstRecordClick(Sender: TObject);
begin
  DLCurrentRecord:=0;
  DLCue[0]:='Get Record';
end;

procedure TForm1.LogLastRecordClick(Sender: TObject);
begin
  DLCurrentRecord:=DLDBSizeProgressBar.Position-1;
  DLCue[0]:='Get Record';
end;

procedure TForm1.LogNextRecordClick(Sender: TObject);
begin
  DLCurrentRecord:=DLCurrentRecord+1;
  DLCue[0]:='Get Record';
end;

procedure TForm1.LogPreviousRecordClick(Sender: TObject);
begin
  DLCurrentRecord:=DLCurrentRecord-1;
  DLCue[0]:='Get Record';
end;

procedure TForm1.CommNotebookPageChanged(Sender: TObject);
begin
  ClearResults;
  FoundDevices.ClearSelection;
end;

procedure TForm1.EstimateBatteryLife;

{
Battery life calculated accoding to readings provided in manual.
Battery capacity estimates in combo box provided from spec sheets when
 each 1.5V cell drops to 0.9V which is plenty since the batery to USB
 adapter will still work at 5.1V in (0.85V/cell), and the SQM-LU-DL
 will still operate down to 3.3 (3.4 at battery, 0.57V/cell).

Example batteries:

-- Alkaline batteries -----------------------------------------------------
 Panasonic LR6XWA Alkaline-Zinc/Manganese Dioxide
   for 260hrs @ 10mA, down to 0.9V = 2600mAH

 ENERGIZER EN91 Alkaline Zinc-Manganese Dioxide (Zn/MnO 2)
   from chart, 25mA discharge = ~2600mAH

 Panasonic ZR6XT Oxyride Alkaline 1.7V
   from datasheet, Discharge characteristics plot ~260hrs @ 10mA = 2600mAH

-- Litium -----------------------------------------------------------------

ENERGIZER L91 Lithium/Iron Disulfide
   From datasheet Milliamp-Hours Capacity = 3000maH


-- Carbon Zinc ------------------------------------------------------------
 Eveready 1215 datasheet "Constant Current Discharge"
   1000hrs @ 1mA down to 0.8V = 1000maH
}
var
   BatteryCapacity:Real;
   IAverage:Real;
   TQuiescent:Real;
   TMeasure:Real;
   TBattery:Real;
   NMinutes:Integer;//Number of minutes between samples
   lStrings: TStringList;
const
     IQuiescent=600e-9;
     IWake=10e-3;
     IMeasure=55e-3;
     TWake=3/60;
begin

  //Determine battery capacity from ComboBox text
  lStrings:=TStringList.Create;
  lStrings.Delimiter := ' ';
  lStrings.DelimitedText := DLBatteryCapacityComboBox.Text;
  BatteryCapacity:=StrToIntDef(lStrings.Strings[0],0);

  NMinutes:=5;//default
  case TriggerGroup.ItemIndex of
    0: //Off
      begin
        DLBatteryDurationTime.Text:='Not applicable';
        DLBatteryDurationUntil.Text:='Not applicable';
      end;

    1: //Every x seconds
      begin
        TMeasure:=5.0/(Min(Max(StrToFloatDef(DLTrigSeconds.Text,1),1),5.0));
        IAverage:=IWake+TMeasure*IMeasure;
        TBattery:=BatteryCapacity/(1000*IAverage);
        DLBatteryDurationTime.Text:=Format('%.0fhours, or %.0fdays, or %.1f months',[TBattery,TBattery/24,TBattery/(24*31)]);
        DLBatteryDurationUntil.Text:=FormatDateTime('yy-mm-dd ddd hh:nn:ss',IncHour(Now,Round(TBattery)));
      end;

    2..7: //Every x minutes
        begin
          case TriggerGroup.ItemIndex of
            2:NMinutes:=StrToIntDef(DLTrigMinutes.Text,1);
            3:NMinutes:=5;
            4:NMinutes:=10;
            5:NMinutes:=15;
            6:NMinutes:=30;
            7:NMinutes:=50;
          end;
        TMeasure:=5/(NMinutes*60);
        TQuiescent:=1-(TWake+TMeasure);
        IAverage:=TQuiescent*IQuiescent+TWake*IWake+TMeasure*IMeasure;
        TBattery:=BatteryCapacity/(1000*IAverage);
        DLBatteryDurationTime.Text:=Format('%.0fhours, or %.0fdays, or %.1f months',[TBattery,TBattery/24,TBattery/(24*31)]);
        DLBatteryDurationUntil.Text:=FormatDateTime('yyyy-mm-dd ddd hh:nn:ss',IncHour(Now,Round(TBattery)));
        end;
  end;
end;

procedure TForm1.DLBatteryCapacityComboBoxChange(Sender: TObject);
begin
     EstimateBatteryLife;
end;

procedure TForm1.DLCancelRetrieveButtonClick(Sender: TObject);
begin
  DLCancelRetrieve:=True;
end;

procedure TForm1.DLEraseAllButtonClick(Sender: TObject);
var
  Reply: Integer;
begin
   Reply:=Application.MessageBox(Pchar('Erase entire database of records?' + sLineBreak +
      'Are you sure?' + sLineBreak +
      'Cancel if not sure.'),'Erase all records',MB_ICONWARNING + MB_OKCANCEL);
   if Reply=mrOK then
      DLCue[0]:='DLEraseAll';
end;

procedure TForm1.DLLogOneButtonClick(Sender: TObject);
begin
  DLCue[0]:='Log one record';
end;

procedure TForm1.DLRetrieveAllButtonClick(Sender: TObject);
var
  Reply: Integer;
begin
     DLCancelRetrieve:=False;
     DLSaveDialog.FileName:=FormatDateTime('yyyymmdd-hhnnss',Now)+'.txt';
     if DLSaveDialog.Execute then
        begin
             if FileExists(DLSaveDialog.FileName) then
                begin
                     Reply:=Application.MessageBox(Pchar('Overwrite file: '+ DLSaveDialog.FileName + ' ?'),'File exists',MB_ICONWARNING + MB_YESNO);
                     if Reply=mrYes then
                        DLCue[0]:='DLRetrieveAll';
                end
             else
                DLCue[0]:='DLRetrieveAll';
        end;
end;

procedure TForm1.DLThresholdChange(Sender: TObject);
begin
     DLThreshold.Color:=clFuchsia;
end;

procedure TForm1.DLThresholdKeyPress(Sender: TObject; var Key: char);
begin
     if Ord(Key)=13 then
        begin
           DLCue[0]:='Set Trigger Threshold';
           DLThreshold.Color:=clWindow;
        end;
end;

procedure TForm1.DLTrigMinutesChange(Sender: TObject);
begin
     DLTrigMinutes.Color:=clFuchsia;
end;

procedure TForm1.DLTrigMinutesKeyPress(Sender: TObject; var Key: char);
begin
     if Ord(Key)=13 then
        begin
             DLCue[0]:='Set Trigger Minutes';
             DLTrigMinutes.Color:=clWindow;
        end;
end;

procedure TForm1.DLTrigSecondsChange(Sender: TObject);
begin
     DLTrigSeconds.Color:=clFuchsia;
end;

procedure TForm1.DLTrigSecondsKeyPress(Sender: TObject; var Key: char);
begin
     if Ord(Key)=13 then
        begin
             DLCue[0]:='Set Trigger Seconds';
             DLTrigSeconds.Color:=clWindow;
        end;
end;

procedure TForm1.AboutItemClick(Sender: TObject);
begin
     ShowMessage(
                 'Serial Library verion: ' +ser.GetVersion + sLineBreak +
                 '' );
end;

procedure TForm1.LoadFirmwareClick(Sender: TObject);
var
 File1: TextFile;
 Str: String;
 i:Integer;
 begin

  if FileExists(FirmwareFile.Text) then
    begin
  AssignFile(File1,FirmwareFile.Text );
  {$I-}//Temprarily turn off IO checking
  try
    Reset(File1);

    LoadFirmwareProgressBar.Max:=0;
    repeat
      Readln(File1, Str); // Reads a whole line from the file.
      {do something with str}
      LoadFirmwareProgressBar.Max:=LoadFirmwareProgressBar.Max+1;
    until(EOF(File1)); // EOF(End Of File) keep reading new lines until end.

    Reset(File1);

    OpenComm;
    StatusMessage('Resetting unit ...');
    Application.ProcessMessages;
    sendget(chr($19),False,1);
    for i:=0 to 20 do
      begin
        sleep(50);
        ResetForFirmwareProgressBar.Position:=i;
        Application.ProcessMessages;
      end;
     StatusMessage('Unit should have been reset ...');

    LoadFirmwareProgressBar.Position:=0;
    StatusMessage('Loading firmware ...');
    repeat
      Readln(File1, Str); // Reads a whole line from the file.
      {do something with str}
      Write(Str+' '); // Writes the line read
      WriteLn(sendget(Str,True));
      LoadFirmwareProgressBar.Position:=LoadFirmwareProgressBar.Position+1;
      Application.ProcessMessages;
    until(EOF(File1)); // EOF(End Of File) keep reading new lines until end.
    StatusMessage('Firmware loaded.');
    CloseFile(File1);
  except
    StatusMessage('File: '+FirmwareFile.Text+' IOERROR!', clYellow);
  end;
  {$I+}//Turn IO checking back on
    end
  else
   StatusMessage('File '+FirmwareFile.Text+' does not exist!', clYellow);

end;

procedure TForm1.CheckLockClick(Sender: TObject);
var
   result:ansistring;
begin
     result:=SendGet('zcalDx');
     StatusMessage(result);

     if AnsiContainsStr(result, 'L') then
        CheckLockResult.Text:='Locked'
     else
         if AnsiContainsStr(result, 'U') then
            CheckLockResult.Text:='Unlocked'
         else
            CheckLockResult.Text:='Unknown';


end;

procedure TForm1.DataNotebookPageChanged(Sender: TObject);
begin
     //Writeln('tab changed');
     DLRefreshed:=False;
{     case DataNotebook.PageIndex of
     0:     writeln('info page');
     1:     writeln('cal page');
     2:     writeln('ri page');
     3:     writeln('firmware page');
     end;}
end;

procedure TForm1.FindDevices;
var
  i: Integer;
begin
     //Clear out existing results
     FoundDevices.Items.Clear;

     screen.Cursor:= crHourGlass;
     Application.ProcessMessages;

     SetLength(FoundDevicesArray,1);     //Resize "Found devices" to accept first device.

     {$ifdef Linux}
     findusblinux;                  //Try finding USB attached devices.
     {$endif}
     {$ifdef Darwin}
     // Darwin is the base OS name of Mac OS X, like NT is the name of the Win2k/XP/Vista kernel. Mac OS X = Darwin + GUI.
     findusbdarwin;                  //Try finding USB attached devices.
     {$endif}

     findeth;                       //Try finding Ethernet devices.

  //All USB devices have been found ... or not.
  if (high(FoundDevicesArray)=0) then
     StatusMessage('No devices were found',clYELLOW)
  else
      begin
      //Writeln('device(s) have been found:');
      begin
      for i:=low(FoundDevicesArray) to high(FoundDevicesArray)-1 do
          begin
             //Writeln('  ' + FoundDevicesArray[i].SerialNumber + ' : ' + FoundDevicesArray[i].Connection);
             FoundDevices.Items.Add(FoundDevicesArray[i].Hardware +
              ' : ' + format('%12s',[FoundDevicesArray[i].SerialNumber]) +
              ' : ' + FoundDevicesArray[i].Connection);
          end;
      end;
      end;

     screen.Cursor:= crDefault;
end;


procedure TForm1.FindButtonClick(Sender: TObject);
begin
     FindDevices;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
   FixedFont:String;
begin
  {$ifdef MSWindows}
     // ce + win32 + win64, delphi compat
     //Writeln('MSWindows');
  {$endif}
  {$ifdef Windows}
     // ce + win32 + win64, more logical.
     //Writeln('Windows');
  {$endif}
  {$ifdef Linux}
   //Fixed font chose to properly display the zero character
   FixedFont:='Monospace';
  {$endif}
  {$ifdef Darwin}
   FixedFont:='Monaco';
  {$endif}

   FindDevices;
   ser:=TBlockSerial.Create;

   SelectedModel:=0; //default: no selected model

   //Datalogger initialzation
   DLCue[0]:='';// No commands cued up
   DLRefreshed:=False;
   DLCancelRetrieve:=False;

   //Disable normally unused pages
   DataNoteBook.Page[4].Enabled:=False; //Datalogging page
   DataNoteBook.Page[6].Enabled:=False; //GPS page

   //Default notebook pages
   DataNoteBook.PageIndex:=0;
   CommNotebook.PageIndex:=0;

   //Set up all fixed font widgets
   FoundDevices.Font.Name:=FixedFont;
   VersionListBox.Font.Name:=FixedFont;
   ReadingListBox.Font.Name:=FixedFont;
   USBSerialNumber.Font.Name:=FixedFont;
   USBPort.Font.Name:=FixedFont;
   EthernetMAC.Font.Name:=FixedFont;
   EthernetIP.Font.Name:=FixedFont;
   EthernetPort.Font.Name:=FixedFont;
   RS232Port.Font.Name:=FixedFont;
   RS232Baud.Font.Name:=FixedFont;
   LogRecordResult.Font.Name:=FixedFont;
   DLTrigSeconds.Font.Name:=FixedFont;
   DLTrigMinutes.Font.Name:=FixedFont;
   DLThreshold.Font.Name:=FixedFont;

end;

procedure TForm1.FoundDevicesClick(Sender: TObject);
var
   ItemSelected:Integer;
begin
     ItemSelected:=FoundDevices.ItemIndex;
     ClearResults;
      if FoundDevicesArray[ItemSelected].Hardware = 'USB' then
         begin
              USBSerialNumber.Text:=FoundDevicesArray[ItemSelected].SerialNumber;
              USBPort.Text:=FoundDevicesArray[ItemSelected].Connection;
              CommNotebook.PageIndex:=0;
         end;
      if FoundDevicesArray[ItemSelected].Hardware = 'Eth' then
         begin
              EthernetMAC.Text:=FoundDevicesArray[ItemSelected].SerialNumber;
              EthernetIP.Text:=FoundDevicesArray[ItemSelected].Connection;
              EthernetPort.Text:='10001';
              CommNotebook.PageIndex:=1;
         end;
      GetVersion;
      FoundDevices.Selected[ItemSelected]:=True;
end;

procedure TForm1.InformationContextPopup(Sender: TObject; MousePos: TPoint;
  var Handled: Boolean);
begin

end;

procedure TForm1.SetDeviceClockClick(Sender: TObject);
begin
  DLCue[0]:='SetClock';
end;
function FixDate(incoming:AnsiString): AnsiString;
//Fix the date from the DataLogging unit to a readable string
var
   dowval:Integer;
   weekday: Array[1..7] of string = ('Sun','Mon','Tue','Wed','Thu','Fri','Sat');
begin
  dowval:=StrToInt(AnsiMidStr(incoming,10,1));
  if ((dowval>=1) and (dowval<=7)) then
    FixDate:=AnsiMidStr(incoming,1,9)+weekday[dowval]+AnsiMidStr(incoming,11,9)
  else
    FixDate:=AnsiMidStr(incoming,1,9)+'???'+AnsiMidStr(incoming,11,9);
end;
function LimitInteger(Source:Integer; Minimum:Integer; Maximum:Integer):Integer;
begin
LimitInteger:=Source;
     if Source<Minimum then LimitInteger:=Minimum;
     if Source>Maximum then LimitInteger:=Maximum;
end;

procedure TForm1.LogRecordGet(RecordNumber:Integer);
var
   result:AnsiString;
begin
   RecordNumber:=LimitInteger(RecordNumber,0,DLDBSizeProgressBar.Position-1);
   result:=sendget(Format('L4%010.10dx',[RecordNumber]));
   if Length(result)=40 then
   begin
     LogRecordResult.Lines.Clear;
     LogRecordResult.Lines.Add(Format('     Record: %d',[RecordNumber+1]));
     LogRecordResult.Lines.Add(Format('       Date: %s',[FixDate(AnsiMidStr(result,4,19))]));
     LogRecordResult.Lines.Add(Format('    Reading: %1.2fmpsas',[StrToFloat(AnsiMidStr(result,24,5))]));
     LogRecordResult.Lines.Add(Format('Temperature: %1.1fC',[StrToFloatDef(AnsiMidStr(result,30,5),0)]));
     LogRecordResult.Lines.Add(Format('    Voltage: %1.2fV',[(2.048 + (3.3 * StrToFloatDef(AnsiMidStr(result,38,3),0))/256.0)]));
   end;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
//This timer runs once per second.
//Mostly to update readings from the DL (for now).
//Cued instructions sent here also to avoid collision with timed requests.
var
   pieces: TStringList;
   result:AnsiString;
   ThisMoment : TDateTime;
   DLRecFile: TextFile;
begin
     pieces := TStringList.Create;
     pieces.Delimiter := ',';

     ThisMoment:=Now;
  if ((DataNotebook.PageIndex=4) and ((SelectedModel=6) or (SelectedModel=7))) then
     begin
       if not DLRefreshed then
          begin
               result:=sendget('LIx');
               DLTrigSeconds.Text:=IntToStr(StrToIntDef(AnsiMidStr(result,4,10),0));
               DLTrigSeconds.Color:=clWindow;
               DLTrigMinutes.Text:=IntToStr(StrToIntDef(AnsiMidStr(result,16,10),0));
               DLTrigMinutes.Color:=clWindow;
               DLTrigSecondsCurrent.Text:=IntToStr(StrToIntDef(AnsiMidStr(result,28,10),0));
               DLTrigMinutesCurrent.Text:=IntToStr(StrToIntDef(AnsiMidStr(result,40,10),0));
               DLThreshold.Text:=FloatToStr(StrToFloatDef(AnsiMidStr(result,52,11),0));
               DLThreshold.Color:=clWindow;

               //Read log trigger mode
               result:=sendget('Lmx');
               TriggerGroup.ItemIndex:=StrToIntDef(AnsiMidStr(result,4,1),0);

               //Get log pointer
               result:=sendget('L1x');
               DLDBSizeProgressBar.Position:=StrToIntDef(AnsiMidStr(result,4,6),0);
               DLDBSizeProgressBarText.Caption:=Format('%3.2f%%',[100.0 * DLDBSizeProgressBar.Position/DLDBSizeProgressBar.Max]);

               //Get most recent record
               if DLDBSizeProgressBar.Position>0 then
                  begin
                     DLCurrentRecord:=DLDBSizeProgressBar.Position-1;
                     LogRecordGet(DLCurrentRecord);
                  end;

               //result:=sendget('L5x');
               //DLInternalVoltage.Text:=Format('%.2f',[(2.048 + (3.3 * StrToFloatDef(AnsiMidStr(result,4,3),0))/256.0)]);

               DLRefreshed:=True
          end
       else
           begin
             if DLCue[0]='SetClock' then
                begin
                    result:=SendGet('LC'+FormatDateTime('yy-mm-dd ',ThisMoment)+IntToStr(DayOfWeek(ThisMoment))+FormatDateTime(' hh:nn:ss',ThisMoment)+'x');
                    { TODO : check result }
                    DLCue[0]:='';
                end
             else if DLCue[0]='Log one record' then
                begin
                   result:=SendGet('L3x');//Log one record
                   Application.ProcessMessages;
                   result:=sendget('L1x');//Get current point
                   Application.ProcessMessages;
                   DLDBSizeProgressBar.Position:=StrToIntDef(AnsiMidStr(result,4,6),0);
                   //Get most recent record
                   if DLDBSizeProgressBar.Position>0 then
                      begin
                         DLCurrentRecord:=DLDBSizeProgressBar.Position-1;
                         LogRecordGet(DLCurrentRecord);
                      end;
                   DLCue[0]:='';
                end
             else if DLCue[0]='DLEraseAll' then
                begin
                     LogRecordResult.Lines.Clear;
                     LogRecordResult.Lines.Add('Database being erased, please wait a few seconds ...');
                     Application.ProcessMessages;
                     SendGet('L2x');//Erase all records
                     Application.ProcessMessages;
                     sleep(2000);
                     result:=sendget('L1x');//Get current point
                     Application.ProcessMessages;
                     DLDBSizeProgressBar.Position:=StrToIntDef(AnsiMidStr(result,4,6),0);
                     LogRecordResult.Lines.Clear;
                     if StrToIntDef(AnsiMidStr(result,4,6),0)=0 then
                        LogRecordResult.Lines.Add('Database successfully erased.')
                     else
                        LogRecordResult.Lines.Add('Database NOT erased!  Please try again.');
                     DLCue[0]:='';
                end
             else if DLCue[0]='Get Record' then
                begin
                   LogRecordGet(DLCurrentRecord);
                   DLCue[0]:='';
                end
             else if DLCue[0]='Change Trigger' then
                begin
                    result:=SendGet('LM'+DLCue[1]+'x');
                    { TODO : check result }
                    DLCue[0]:='';
                end
             else if DLCue[0]='DLRetrieveAll' then
                begin
                     AssignFile(DLRecFile,DLSaveDialog.FileName);
                     try
                        Rewrite(DLRecFile); //create the file
                        for DLCurrentRecord:=0 to DLDBSizeProgressBar.Position-1 do
                           begin
                            if DLCancelRetrieve then
                               begin
                                    { TODO : log the break }
                                    break;
                               end;
                             result:=sendget(Format('L4%010.10dx',[DLCurrentRecord]));
                             if Length(result)=40 then
                             begin
                               LogRecordResult.Lines.Clear;
                               LogRecordResult.Lines.Add(Format('Retrieved record: %d / %d',[DLCurrentRecord+1,DLDBSizeProgressBar.Position]));
                               Application.ProcessMessages;
                               Writeln(DLRecFile,
                                 Format('%d,',[DLCurrentRecord+1]) +
                                 Format('%s,',[FixDate(AnsiMidStr(result,4,19))]) +
                                 Format('%1.2f,',[StrToFloat(AnsiMidStr(result,24,5))]) +
                                 Format('%1.1f,',[StrToFloatDef(AnsiMidStr(result,30,5),0)]) +
                                 Format('%1.2f',[(2.048 + (3.3 * StrToFloatDef(AnsiMidStr(result,38,3),0))/256.0)])
                               );
                             end;
                           end;
                         LogRecordResult.Lines.Clear;
                         if DLCancelRetrieve then
                            LogRecordResult.Lines.Add('Partially retrieved records written to:')
                         else
                             LogRecordResult.Lines.Add('Retrieved records written to:');
                         LogRecordResult.Lines.Add(Format('%s',[DLSaveDialog.FileName]));
                         DLCancelRetrieve:=False;
                     except
                           Writeln('ERROR! IORESULT: ' + IntToStr(IOResult));
                     end;
                     CloseFile(DLRecFile);


                    DLCue[0]:='';
                end
             else if DLCue[0]='Set Trigger Seconds' then
                begin
                    result:=SendGet(Format('LPS%010dx',[(StrToIntDef(DLTrigSeconds.Text,0))]));
                    { TODO : check result }
                    DLCue[0]:='';
                end
             else if DLCue[0]='Set Trigger Minutes' then
                begin
                    result:=SendGet(Format('LPM%010dx',[(StrToIntDef(DLTrigMinutes.Text,0))]));
                    { TODO : check result }
                    DLCue[0]:='';
                end
             else if DLCue[0]='Set Trigger Threshold' then
                begin
                    result:=SendGet(Format('LT%011.2fx',[(StrToFloatDef(DLThreshold.Text,0))]));
                    { TODO : check result }
                    DLCue[0]:='';
                end
             else
                 begin
                  result:=SendGet('Lcx');
                  if Length(result)>=21 then
                    begin
                      UnitClock.Caption:=FixDate(AnsiMidStr(result,4,19));
                    end
                  else
                    UnitClock.Caption:='unknown';

                  PCClock.Caption:=FormatDateTime('yy-mm-dd ddd hh:nn:ss',ThisMoment);
                  end;
             end
       end
     else
         DLRefreshed:=False;

end;

procedure TForm1.TriggerGroupClick(Sender: TObject);
begin
     DLCue[0]:='Change Trigger';
     DLCue[1]:=IntToStr(TriggerGroup.ItemIndex);
     EstimateBatteryLife;
end;

procedure TForm1.GetVersion;
var
   result:string;
   pieces: TStringList;
   ModelDescription: String;
begin
     //Clear out existing results
     VersionListBox.Items.Clear;

     pieces := TStringList.Create;
     pieces.Delimiter := ',';

     result:=SendGet('ix');
     //StatusMessage(result);
     //writeln('trying',length(result));

     pieces.DelimitedText := result;
     //check size of array. 5 Sections normally, 6 sections when checksum is sent.
     if pieces.Count>=5 then
     begin
       VersionListBox.Items.Add('Protocol: '+ IntToStr(StrToIntDef(pieces.Strings[1],0)));
       Case StrToIntDef(pieces.Strings[2],0) of
               1: ModelDescription:='ADA';
               3: begin
                  if CommNotebook.PageIndex=0 then
                     ModelDescription:='SQM-LU'
                  else
                     ModelDescription:='SQM-LE';
               end;
               4: ModelDescription:='Colour';
               5: ModelDescription:='SQM-LR';
               6: ModelDescription:='SQM-LU-DL';
               7: ModelDescription:='SQM-LU-GPS';
               8: ModelDescription:='Magnetometer';
       otherwise
                ModelDescription:='Unknown';
       end;
       VersionListBox.Items.Add('   Model: '+ IntToStr(StrToIntDef(pieces.Strings[2],0))+ ' ('+ ModelDescription + ')');
       VersionListBox.Items.Add(' Feature: '+ IntToStr(StrToIntDef(pieces.Strings[3],0)));
       VersionListBox.Items.Add('  Serial: '+ IntToStr(StrToIntDef(pieces.Strings[4],0)));

     SelectedModel:=StrToIntDef(pieces.Strings[2],0);

     //Datalogging tab: 6=SQM-LU-DL, 7 =SQM-LU-GPS
     if ((SelectedModel=6)or (SelectedModel=7)) then
     begin
          DataNoteBook.Page[4].Enabled:=True;
          DataLoggingAvailable:=True;
     end
     else
     begin
          DataNoteBook.Page[4].Enabled:=False;
	  DataLoggingAvailable:=False;
     end;

     //GPS tab: 7 =SQM-LU-GPS
     if (SelectedModel=7) then
          DataNoteBook.Page[6].Enabled:=True
     else
          DataNoteBook.Page[6].Enabled:=False;

     //CheckLock enable/disable
     CheckLockResult.Text:='';
     if ((SelectedModel=3) and (CommNotebook.PageIndex=1)) then //3=standard SQM-LE/U, and Ethernet
        begin
          CheckLock.Enabled:=True;
          CheckLockResult.Enabled:=True;
        end
     else
         begin
          CheckLock.Enabled:=False;
          CheckLockResult.Enabled:=False;
         end;
     end;


end;


procedure TForm1.VersionButtonClick(Sender: TObject);
begin
     GetVersion;
end;

initialization
  {$I unit1.lrs}

end.

