unit Unit1;
{ TODO : - Do sanity check on report interval values in EEPROM.}
{$mode objfpc}{$H+}

interface

uses
  Classes
  , SysUtils
  , FileUtil
  //, SynMemo
  , LResources, Forms, Controls, Graphics
  , Dialogs, StdCtrls, ExtCtrls, ComCtrls
  //, Grids
  , DbCtrls, Buttons, Menus
  , StrUtils, dateutils, math
  , synaser, synautil, blcksock, tlntsend
  , LCLType, Spin, fileview, viewlog, logcont, About, SynHighlighterPosition
  , dlheader //contains the DLHeader code
  , configbrowser //contains the Header browser window
  , unitdirectorylist //directory listing
  , SynEditHighlighter, SynEdit, SynGutterBase, printersdlgs, Printers
  //, EditBtn
  //BCButton
  //, JButton //needs printer4lazarus package installed.
  , vinfo //version info
  , convertlogfileunit //dialog for converting dat to csv files
  , convertoldlogfile //dialog for converting old log to dat files
  , correct49to56 //Correct MPSAS error from firmware 49-56
  , moon //required for Moon calculations
  , dattimecorrect //Required for tool to correct dat file for time differences
  , datlocalcorrect //Required for tool to correct dat file for local times
  , datreplacedates //Tool to replace dates in a .dat file
  , StartupTest
  , Process,appsettings
  , dlerase //Data logger erase-all form
  , dlclock//Data logger clock-setting form
  , plotter //Plotter view
  , dattokml //tool
  , Vector //VectorTab model
  , comterm //Communication terminal for quick testing of commands
  , avgtool //Average tool
  , concattool //Concatenation tool
  , CloudRemUnit //Cloud Removal / Milky Way position tool
  , date2dec //.dat to decimal date conversion tool
  , LazFileUtils //Necessary for filename extraction
  , LazSysUtils //For NowUTC()
  , clipbrd
  , ARPMethod
  , FilterSunMoonUnit

  {$IFDEF LinuxBluetooth}, Bluetooth, ctypes, Sockets, errors{$ENDIF}//fictitious platform until bluetooth feature works.
  {$IFDEF Unix}, BaseUnix{$ENDIF}
  {$IFDEF Windows}, Registry{$ENDIF}
  ;
type

  { TForm1 }

  TForm1 = class(TForm)
    AccSnowOffLinRdg: TEdit;
    AccSnowLinDiff: TEdit;
    ARLYThreshold: TTrackBar;
    AccSnowLEDOnButton: TButton;
    AccSnowLEDOffButton: TButton;
    AccSnowLinRq: TButton;
    ARPTelnetButton: TButton;
    LockCommCheckBox: TCheckBox;
    datReplaceDatesMenuItem: TMenuItem;
    StartupTestMenuItem: TMenuItem;
    RptIntButton: TButton;
    LogSettingsButton: TButton;
    LabeledEdit1: TLabeledEdit;
    LabelTextButton: TButton;
    bXPortDefaults: TButton;
    FinalResetForXPortProgressBar: TProgressBar;
    FWUSBExistsLabel: TLabel;
    FirmwareFile: TEdit;
    FWCounter: TLabel;
    FWUSBGroup: TGroupBox;
    FWEthGroup: TGroupBox;
    ConcatenateMenu: TMenuItem;
    CloudRemovalMilkyWay: TMenuItem;
    ConfigBrowserMenuItem: TMenuItem;
    FilterSunMoon: TMenuItem;
    OnlineResources: TMenuItem;
    OnlineLRmanual: TMenuItem;
    OnlineLUmanual: TMenuItem;
    OnlineLEmanual: TMenuItem;
    OnlineDLmanual: TMenuItem;
    OnlineVmanual: TMenuItem;
    OnlineManuals: TMenuItem;
    RptIntInd: TShape;
    StartUpMenuItem: TMenuItem;
    ResetXPortProgressBar: TProgressBar;
    SelectFirmwareButton: TButton;
    FWWaitUSBButton: TButton;
    ConfLightCalButton: TButton;
    DLSetSeconds1: TButton;
    DLTrigMinutes: TEdit;
    ConfLightCalReq: TShape;
    ConfDarkCalReq: TShape;
    AccSnowOnLinRdg: TEdit;
    Label24: TLabel;
    DLMutualAccessGroup: TRadioGroup;
    SnowReadSkipEdit: TEdit;
    SnowReadingsLabel: TLabel;
    SnowSampleLabel: TLabel;
    SnowGroupBox: TGroupBox;
    Label23: TLabel;
    SnowLoggingEnableBox: TCheckBox;
    FirmwareTimer: TTimer;
    FWStopUSBButton: TButton;
    LogRecordResult: TSynEdit;
    TriggerComboBox: TComboBox;
    DLSetSeconds: TButton;
    DLTrigSeconds: TEdit;
    Label22: TLabel;
    DLSecMinPages: TPageControl;
    CommOpen: TShape;
    ACCSnowLEDStatus: TShape;
    Displayedcdm2: TLabel;
    DisplayedNELM: TLabel;
    DisplayedNSU: TLabel;
    DisplayedReading: TLabel;
    AccSNOWLEDGroupBox: TGroupBox;
    HeaderButton: TButton;
    Label46: TLabel;
    Label47: TLabel;
    LogContinuousButton: TButton;
    LoggingGroup: TGroupBox;
    LogOneRecordButton: TButton;
    MeasurementGroup: TGroupBox;
    DetailsGroup: TGroupBox;
    DatReconstructLocalTime: TMenuItem;
    Correction49to56MenuItem: TMenuItem;
    AverageTool: TMenuItem;
    datToDecimalDate: TMenuItem;
    ColourCyclingRadio: TRadioGroup;
    ReadingListBox: TListBox;
    ReadingUnits: TLabel;
    RequestButton: TButton;
    Button1: TButton;
    AHTRefreshButton: TButton;
    ADISEnable: TCheckBox;
    AHTModelSelect: TComboBox;
    AHTEnable: TCheckBox;
    ALEDEnable: TCheckBox;
    ADISModelSelect: TComboBox;
    ARLYOn: TButton;
    ARLYOff: TButton;
    ARLYModeComboBox: TComboBox;
    AccRefreshButton: TBitBtn;
    ConfRecWarning: TLabel;
    ContCheckGroup: TCheckGroup;
    CurrentFirmware: TLabeledEdit;
    GetReportInterval: TBitBtn;
    TimedReportsGroupBox: TGroupBox;
    ADISPGroup: TGroupBox;
    AHTGroup: TGroupBox;
    ALEDGroup: TGroupBox;
    ADISBrightnessGroup: TGroupBox;
    GroupBox5: TGroupBox;
    ColourControls: TGroupBox;
    ARLYThresholdValue: TLabel;
    LockedImage: TImage;
    IThDes: TEdit;
    IThE: TEdit;
    IThERButton: TButton;
    IThR: TEdit;
    IThRButton: TButton;
    ITiDes: TEdit;
    ITiE: TEdit;
    ITiERButton: TButton;
    ITiR: TEdit;
    ITiRButton: TButton;
    Label16: TLabel;
    Label17: TLabel;
    Label18: TLabel;
    Label19: TLabel;
    ARLYModeLabel: TLabel;
    ARLYThresholdLabel: TLabel;
    ARLYStatusLabeledEdit: TLabeledEdit;
    ARLYTValue: TLabeledEdit;
    ARLYHValue: TLabeledEdit;
    ARLYTDPValue: TLabeledEdit;
    LoadingStatus: TLabel;
    Label28: TLabel;
    Label36: TLabel;
    Label37: TLabel;
    Label38: TLabel;
    Label39: TLabel;
    Label40: TLabel;
    Label41: TLabel;
    AHTHumidityValue: TLabeledEdit;
    AHTHumidityStatus: TLabeledEdit;
    AHTTemperatureValue: TLabeledEdit;
    LHComboLabel: TLabel;
    FilterComboLabel: TLabel;
    LensComboLabel: TLabel;
    LHCombo: TComboBox;
    LensCombo: TComboBox;
    FilterCombo: TComboBox;
    ColourScalingRadio: TRadioGroup;
    ColourRadio: TRadioGroup;
    DatTimeCorrection: TMenuItem;
    PlotterMenuItem: TMenuItem;
    CommBusy: TTimer;
    DLSecSheet: TTabSheet;
    DLMinSheet: TTabSheet;
    UnLockedImage: TImage;
    LockSwitchOptions: TCheckGroup;
    mnDATtoKML: TMenuItem;
    OpenDLRMenuItem: TMenuItem;
    ADISFixed: TRadioButton;
    ADISAuto: TRadioButton;
    ALEDMode: TRadioGroup;
    ADISMode: TRadioGroup;
    SimFromFile: TButton;
    AccTab: TTabSheet;
    ADISFixedBrightness: TTrackBar;
    VersionButton: TButton;
    VersionListBox: TListBox;
    VThresholdSet: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    Button5: TButton;
    Button6: TButton;
    Button7: TButton;
    Button8: TButton;
    Button9: TButton;
    ConfDarkCaluxButton: TButton;
    DLRetrieveButton: TButton;
    DLClockSettingsButton: TButton;
    DLThresholdSet: TButton;
    VThreshold: TEdit;
    FindBluetooth: TBitBtn;
    FindUSBMenuItem: TMenuItem;
    FindEthMenuItem: TMenuItem;
    ConvertLogFileItem: TMenuItem;
    ThresholdVibrationGroup: TGroupBox;
    DeviceClockGroupBox: TGroupBox;
    DLClockDifference: TLabeledEdit;
    DLClockDifferenceLabel: TLabel;
    LogNextRecord10: TButton;
    LogPreviousRecord10: TButton;
    GPSResponse: TMemo;
    MenuItem1: TMenuItem;
    CmdLineItem: TMenuItem;
    CommTermMenuItem: TMenuItem;
    Panel1: TImage;
    ScrollBox1: TScrollBox;
    TrickleOffButton: TButton;
    TrickleOnButton: TButton;
    VCalButton: TButton;
    VectorTab: TTabSheet;
    ToolsMenuItem: TMenuItem;
    PrintLabelButton: TBitBtn;
    DLBatteryDurationRecords: TEdit;
    FirmwareInfoButton: TButton;
    LogCalButton: TButton;
    ConfGetCal: TBitBtn;
    DirectoriesMenuItem: TMenuItem;
    DLHeaderMenuItem: TMenuItem;
    Process1: TProcess;
    RS232Baud: TComboBox;
    RS232Port: TComboBox;
    VersionItem: TMenuItem;
    PrintCalReport: TButton;
    ConfRdgmpsas: TLabeledEdit;
    ConfRdgPer: TLabeledEdit;
    GroupBox1: TGroupBox;
    GroupBox3: TGroupBox;
    Label3: TLabel;
    FinalResetForFirmwareProgressBar: TProgressBar;
    Label42: TLabel;
    ConfRdgTemp: TLabeledEdit;
    Label43: TLabel;
    Label44: TLabel;
    Label45: TLabel;
    ConfTempWarning: TLabel;
    PrintDialog1: TPrintDialog;
    Memo1: TMemo;
    ViewSimMenuItem: TMenuItem;
    ViewConfigMenuItem: TMenuItem;
    SimFreqMax: TLabeledEdit;
    SimPeriodMax: TLabeledEdit;
    SimResults: TMemo;
    SimTempDiv: TSpinEdit;
    SimTempMax: TLabeledEdit;
    SimTempMin: TLabeledEdit;
    SimTimingDiv: TSpinEdit;
    SimVerbose: TCheckBox;
    StopSimulation: TButton;
    StartSimulation: TButton;
    StartResetting: TButton;
    StopResetting: TButton;
    GetCalInfoButton: TBitBtn;
    ConfDarkCalButton: TButton;
    Button18: TButton;
    LogCalInfoButton: TButton;
    Label32: TLabel;
    Label33: TLabel;
    Label34: TLabel;
    Label35: TLabel;
    LCOSet: TButton;
    LCTSet: TButton;
    DCPSet: TButton;
    DCTSet: TButton;
    CheckLockButton: TButton;
    CheckLockResult: TEdit;
    CommNotebook: TPageControl;
    DLBatteryCapacityComboBox: TComboBox;
    DLBatteryDurationTime: TEdit;
    DLBatteryDurationUntil: TEdit;
    DLDBSizeProgressBar: TProgressBar;
    DLDBSizeProgressBarText: TLabel;
    DLEraseAllButton: TButton;
    DLLogOneButton: TButton;
    DLThreshold: TEdit;
    DCPDes: TEdit;
    DCTDes: TEdit;
    LCOAct: TEdit;
    LCTAct: TEdit;
    DCPAct: TEdit;
    DCTAct: TEdit;
    LCODes: TEdit;
    LCTDes: TEdit;
    EstimatedBatteryGroup: TGroupBox;
    EthernetIP: TEdit;
    EthernetMAC: TEdit;
    EthernetPort: TEdit;
    FindButton: TBitBtn;
    FoundDevices: TListBox;
    TriggerGroupBox: TGroupBox;
    Label1: TLabel;
    Label10: TLabel;
    Label11: TLabel;
    Label12: TLabel;
    Label13: TLabel;
    Label14: TLabel;
    Label15: TLabel;
    Label2: TLabel;
    Label27: TLabel;
    Label29: TLabel;
    CapacityLabel: TLabel;
    Label31: TLabel;
    Label4: TLabel;
    Label5: TLabel;
    Label6: TLabel;
    Label7: TLabel;
    Label8: TLabel;
    Label9: TLabel;
    ListBox1: TListBox;
    LoadFirmware: TButton;
    LoadFirmwareProgressBar: TProgressBar;
    LogFirstRecord: TButton;
    LogLastRecord: TButton;
    LogNextRecord: TButton;
    LogPreviousRecord: TButton;
    MainMenu1: TMainMenu;
    HelpMenuItem: TMenuItem;
    AboutItem: TMenuItem;
    DLSaveDialog: TSaveDialog;
    DataNoteBook: TPageControl;
    FileMenuItem: TMenuItem;
    Simulation: TTabSheet;
    ViewLogMenuItem: TMenuItem;
    ViewMenuItem: TMenuItem;
    OpenLogDialog: TOpenDialog;
    OpenMenuItem: TMenuItem;
    QuitItem: TMenuItem;
    ResetForFirmwareProgressBar: TProgressBar;
    StorageGroup: TGroupBox;
    InformationTab: TTabSheet;
    CalibrationTab: TTabSheet;
    ReportIntervalTab: TTabSheet;
    FirmwareTab: TTabSheet;
    DataLoggingTab: TTabSheet;
    ConfigurationTab: TTabSheet;
    GPSTab: TTabSheet;
    TabEthernet: TTabSheet;
    TabRS232: TTabSheet;
    TabUSB: TTabSheet;
    TroubleshootingTab: TTabSheet;
    Threshold: TGroupBox;
    SelectFirmwareDialog: TOpenDialog;
    StatusBar1: TStatusBar;
    USBPort: TEdit;
    USBSerialNumber: TEdit;
    procedure AboutItemClick(Sender: TObject);
    procedure AccRefreshButtonClick(Sender: TObject);
    procedure AccSnowLEDOffButtonClick(Sender: TObject);
    procedure AccSnowLEDOnButtonClick(Sender: TObject);
    procedure AccSnowLinRqClick(Sender: TObject);
    procedure SnowReadSkipEditChange(Sender: TObject);
    procedure ADISAutoClick(Sender: TObject);
    procedure ADISEnableChange(Sender: TObject);
    procedure ADISFixedBrightnessKeyUp(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure ADISFixedBrightnessMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure ADISFixedClick(Sender: TObject);
    procedure ADISModeClick(Sender: TObject);
    procedure ADISModelSelectChange(Sender: TObject);
    procedure AHTEnableChange(Sender: TObject);
    procedure AHTModelSelectChange(Sender: TObject);
    procedure AHTRefreshButtonClick(Sender: TObject);
    procedure ALEDEnableChange(Sender: TObject);
    procedure ALEDModeClick(Sender: TObject);
    procedure ARLYModeComboBoxChange(Sender: TObject);
    procedure ARLYOffClick(Sender: TObject);
    procedure ARLYOnClick(Sender: TObject);
    procedure ARLYThresholdKeyUp(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure ARLYThresholdMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure ARPTelnetButtonClick(Sender: TObject);
    procedure CloudRemovalMilkyWayClick(Sender: TObject);
    procedure CommNotebookChanging(Sender: TObject; var AllowChange: Boolean);
    procedure FilterSunMoonClick(Sender: TObject);
    procedure FormActivate(Sender: TObject);
    procedure ConfigBrowserMenuItemClick(Sender: TObject);
    procedure FoundDevicesSelectionChange(Sender: TObject; User: boolean);
    procedure LabelTextButtonClick(Sender: TObject);
    procedure ConcatenateMenuClick(Sender: TObject);
    procedure LockCommCheckBoxChange(Sender: TObject);
    procedure LogSettingsButtonClick(Sender: TObject);
    procedure datReplaceDatesMenuItemClick(Sender: TObject);
    procedure OnlineDLmanualClick(Sender: TObject);
    procedure OnlineLEmanualClick(Sender: TObject);
    procedure OnlineLRmanualClick(Sender: TObject);
    procedure OnlineLUmanualClick(Sender: TObject);
    procedure OnlineResourcesClick(Sender: TObject);
    procedure OnlineVmanualClick(Sender: TObject);
    procedure RptIntButtonClick(Sender: TObject);
    procedure RS232PortChange(Sender: TObject);
    procedure SelectFirmwareButtonClick(Sender: TObject);
    procedure FWWaitUSBButtonClick(Sender: TObject);
    procedure FirmwareTimerTimer(Sender: TObject);
    procedure ColourCyclingRadioClick(Sender: TObject);
    procedure CommBusyTimer(Sender: TObject);
    procedure CommNotebookChange(Sender: TObject);
    procedure Correction49to56MenuItemClick(Sender: TObject);
    procedure datToDecimalDateClick(Sender: TObject);
    procedure DLMutualAccessGroupClick(Sender: TObject);
    procedure ColourRadioClick(Sender: TObject);
    procedure ColourScalingRadioClick(Sender: TObject);
    procedure ContCheckGroupItemClick(Sender: TObject; Index: integer);
    procedure FilterComboChange(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    //procedure FoundDevicesDblClick(Sender: TObject);
    procedure LensComboChange(Sender: TObject);
    procedure LHComboChange(Sender: TObject);
    procedure LockSwitchOptionsItemClick(Sender: TObject; Index: integer);
    procedure DatTimeCorrectionClick(Sender: TObject);
    procedure DatReconstructLocalTimeClick(Sender: TObject);
    procedure AverageToolMenuItemClick(Sender: TObject);
    procedure mnDATtoKMLClick(Sender: TObject);
    procedure OpenDLRMenuItemClick(Sender: TObject);
    procedure PlotterMenuItemClick(Sender: TObject);
    procedure RS232BaudChange(Sender: TObject);
    procedure RS232PortEditingDone(Sender: TObject);
    procedure SimFromFileClick(Sender: TObject);
    procedure Button18Click(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure Button5Click(Sender: TObject);
    procedure Button6Click(Sender: TObject);
    procedure Button7Click(Sender: TObject);
    procedure Button8Click(Sender: TObject);
    procedure Button9Click(Sender: TObject);
    procedure CmdLineItemClick(Sender: TObject);
    procedure CommTermMenuItemClick(Sender: TObject);
    procedure ConfDarkCaluxButtonClick(Sender: TObject);
    procedure ConvertLogFileItemClick(Sender: TObject);
    procedure DLClockSettingsButtonClick(Sender: TObject);
    procedure DLRetrieveButtonClick(Sender: TObject);
    procedure DLSetSeconds1Click(Sender: TObject);
    procedure DLSetSecondsClick(Sender: TObject);
    procedure DLThresholdSetClick(Sender: TObject);
    procedure FindBluetoothClick(Sender: TObject);
    procedure EthernetIPChange(Sender: TObject);
    procedure EthernetPortChange(Sender: TObject);
    procedure FindEthMenuItemClick(Sender: TObject);
    procedure FindUSBMenuItemClick(Sender: TObject);
    procedure FirmwareInfoButtonClick(Sender: TObject);
    procedure HeaderButtonClick(Sender: TObject);
    procedure LogCalButtonClick(Sender: TObject);
    procedure bXPortDefaultsClick(Sender: TObject);
    procedure ConfDarkCalButtonClick(Sender: TObject);
    procedure ConfLightCalButtonClick(Sender: TObject);
    procedure ConfGetCalClick(Sender: TObject);
    procedure FormPaint(Sender: TObject);
    procedure DirectoriesMenuItemClick(Sender: TObject);
    procedure DLHeaderMenuItemClick(Sender: TObject);
    procedure LogContinuousButtonClick(Sender: TObject);
    procedure LogNextRecord10Click(Sender: TObject);
    procedure LogOneRecordButtonClick(Sender: TObject);
    procedure LogPreviousRecord10Click(Sender: TObject);
    procedure MenuItem1Click(Sender: TObject);
    procedure PrintLabelButtonClick(Sender: TObject);
    procedure RS232PortDropDown(Sender: TObject);
    procedure SnowLoggingEnableBoxChange(Sender: TObject);
    procedure FWStopUSBButtonClick(Sender: TObject);
    procedure StartUpMenuItemClick(Sender: TObject);
    procedure StartupTestMenuItemClick(Sender: TObject);
    procedure TestResponseButtonClick(Sender: TObject);
    procedure TrickleOffButtonClick(Sender: TObject);
    procedure TrickleOnButtonClick(Sender: TObject);
    procedure TriggerComboBoxChange(Sender: TObject);
    procedure TriggerGroupSelectionChanged(Sender: TObject);
    procedure USBPortChange(Sender: TObject);
    procedure VCalButtonClick(Sender: TObject);
    procedure VersionItemClick(Sender: TObject);
    procedure PrintCalReportClick(Sender: TObject);
    procedure StartResettingClick(Sender: TObject);
    procedure DataNoteBookChange(Sender: TObject);
    procedure DCPSetClick(Sender: TObject);
    procedure DCTSetClick(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure GetCalInfoButtonClick(Sender: TObject);
    procedure DLBatteryCapacityComboBoxChange(Sender: TObject);
    procedure DLEraseAllButtonClick(Sender: TObject);
    procedure DLLogOneButtonClick(Sender: TObject);
    procedure DLThresholdChange(Sender: TObject);
    procedure DLTrigMinutesChange(Sender: TObject);
    procedure DLTrigSecondsChange(Sender: TObject);
    procedure FirmwareFileChange(Sender: TObject);
    procedure GetReportIntervalClick(Sender: TObject);
    procedure IThERButtonClick(Sender: TObject);
    procedure IThRButtonClick(Sender: TObject);
    procedure ITiERButtonClick(Sender: TObject);
    procedure ITiRButtonClick(Sender: TObject);
    procedure LCOSetClick(Sender: TObject);
    procedure LCTSetClick(Sender: TObject);
    procedure LogCalInfoButtonClick(Sender: TObject);
    procedure LogFirstRecordClick(Sender: TObject);
    procedure LogLastRecordClick(Sender: TObject);
    procedure LogNextRecordClick(Sender: TObject);
    procedure LogPreviousRecordClick(Sender: TObject);
    procedure OpenMenuItemClick(Sender: TObject);
    procedure QuitItemClick(Sender: TObject);
    procedure LoadFirmwareClick(Sender: TObject);
    procedure CheckLockButtonClick(Sender: TObject);
    procedure DataNotebookPageChanged(Sender: TObject);
    procedure RequestButtonClick(Sender: TObject);
    procedure FindButtonClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FoundDevicesClick(Sender: TObject);
    procedure StartSimulationClick(Sender: TObject);
    procedure StopResettingClick(Sender: TObject);
    procedure StopSimulationClick(Sender: TObject);
    procedure VersionButtonClick(Sender: TObject);
    procedure ViewConfigMenuItemClick(Sender: TObject);
    procedure ViewLogMenuItemClick(Sender: TObject);
    procedure ViewSimMenuItemClick(Sender: TObject);
    procedure VThresholdChange(Sender: TObject);
    procedure VThresholdSetClick(Sender: TObject);
  private
    { private declarations }
  public
    { public declarations }
    procedure SnowLEDStatus(Status:String);
    procedure ContCheck(command:string);
    procedure A1Check(command:string);//Check Accessory 1 (Humidity/temperature) options
    procedure A2Check(command:string);//Check Accessory 2 (Display) options
    procedure A3Check(command:string);//Check Accessory 3 (LED blink) options
    procedure A4Check(command:string);//Check Accessory 4 (Relay) options
    procedure SelectDevice;
    procedure FindAllDevices(LimitScope:String='');
    procedure findEth;
    procedure EnableFirmware();

    {$IFDEF Darwin}
     procedure findusbdarwin();
    {$ELSE}
     procedure FindUSB();
    {$ENDIF}
    procedure GetCalInfo();
    procedure ParseReportInterval(Response: String);
    procedure ClearResults();
    procedure EstimateBatteryLife;
    procedure LogRecordGet(RecordNumber:LongInt);
    procedure GetConfReading();
    procedure GetConfCal;
    function TelnetExpect(expectstring: string; sendstring:string): boolean;
    function TelnetWaitFor(expectstring: string): boolean;
    procedure EnableControls(EnableControl:Boolean);
    function ConnectBT: boolean;
    procedure DLGetSettings();
    procedure LogUpdateLogPointer();
    function CheckLockSwitch():Boolean;
    procedure LockSettingsCheck(command:string);
    procedure ParseColourScaling();

    {$IFDEF Unix}
     Procedure InstallSigHandler;
    {$ENDIF}

  end;


  FoundDevice = record
    SerialNumber : String;
    Connection : String;
    Hardware : String;
    Index : Integer; //Index into found list
  end;

const
  WM_OUTPUT_CHANNEL    =       $11;
  WM_INPUT_CHANNEL     =       $13;
  model_ADA = 1; //Auroral Detection Alarm
  model_LELU = 3; //SQM-LE/LU Ethernet or USB model
  model_C = 4; //Colour model
  model_LR = 5; //SQM-LR RS232 model
  model_DL = 6; //SQM-LU-DL Datalogging model and Wifi unit
  model_GPS = 7; //SQM-LU-DL-GPS GPS model
  model_GDM = 8; //Magentometer model
  model_TC = 10; //Temperture Chamber model
  model_V = 11; //SQM-LU-DL-V Vector model
  model_HDR = 12; //(reserved for High Dynamic Range I2C sensor model)
  model_DLS = 13; //DataLogger with Snow LED option.
  model_RPi = 14; //RPi module.
  model_LL = 15; //SQM-LL Lite model.

  CommBusyLimit = 12; //Timer preset for com busy in 100ms chunks (12=1.2s)

var
  Form1: TForm1;
  ser: TBlockSerial;
  GPSser: TBlockSerial;
  GoToser: TBlockSerial;
  EthSocket: TTCPBlockSocket;
  TCPTransferEthSocket: TTCPBlockSocket;
  FoundDevicesArray: array of FoundDevice;
  DataLoggingAvailable: Boolean;
  SelectedInterface: String;
  SelectedPort: String;
  SelectedDevicesArray: array of FoundDevice; //Used for multiple defined devices in LogContinuous
  SelectedIP: String;
  SelectedModel:Integer;
  SelectedModelDescription: String;
  SelectedFeature:String;
  SelectedProtocol:String;
  SelectedHasRTC:Boolean;
  SelectedRTC:String;
  SelectedUnitSerialNumber: String;
  SelectedHardwareID: String = '';
  SelectedLH, SelectedLens,SelectedFilter: Integer; //Holder,Lens,Filter definitions
  SelectedTZRegion, SelectedTZLocation: String;
  NumberOfMultipleDevices: Integer = 0; //default no multiple devices available, and just use the already selected one.
  PortName: AnsiString;
  UDMversion: String;
  RS232PortName: String;
  RS232PortBaud: Integer;
  Freshness: boolean = False; //Flag indicates user selected "Freshness" type capture option.
  FreshReading: Boolean = False; //Flag to indicate fresh status of current reading
  EthConnected:Boolean = False; //Flag for initial Ethernet connection (checked before closing).
  LockComm: Boolean = False; //Lock the communication port during selection.

  SingleDatFile:boolean = False; // Do not roll over to new dat file at end of day.

  //Colour model
  SelectedColour, SelectedColourScaling:Integer;
  ColourUpdating:Boolean=False; //Prevents colour selection radios from self triggering an update.
  ColourCyclingFlag: Boolean; //Indicates if colour changes after each reading.

  //Rotational Stage
  RSser: TBlockSerial;
  rotstage: Boolean;

  //DL global variables
  DLQueue: Array[0..10] of String;
  DLTrigSeconds,DLTrigMinutes: Integer;
  DLRefreshed:Boolean;
  DLCurrentRecord:LongInt;
  DLCancelRetrieve:Boolean;
  DLNumRecords:Integer;
  DLRecFile: TextFile;
  LogFileName: AnsiString;
  DLLogOnBatt:Boolean = False; //True=log on battery only, False = log on both OPC and battery connection.

  //Log Continuous variables
  LCRecFile: TextFile;
  LCLoggingUnderway: Boolean = False;
  GPSLoggingEnabled: Boolean = False;

  SimEnable:Boolean;
  ViewedLog:TStrings;
  ViewingLog:Boolean;
  gettingversion:Boolean;
  gettingreading:Boolean;
  ResetContinuous:Boolean;
  DLLogFileDirectory:String;

  //Fix for multi-language issues
  FPointSeparator, FCommaSeparator: TFormatSettings;

  //For date settings
  FDateSettings: TFormatSettings;

  //Telnet send function for SQM-LE
  tsend: TTelnetSend;

  //Memo highlighter
  Highlighter: TSynPositionHighlighter;

  //Configuration variables
  ConfCalmpsas: Real;
  ConfCalLightTemp: Real;
  ConfCalPeriod: Real;
  ConfCalDarkTemp: Real;
  LHFUpdating:Boolean=False; //Prevents LHF comboboxes from self triggering an update.

  //Report Interval variables
  RITiE: Integer = 0; //Report Interval Time in EEPROM
  RITiR: Integer = 0; //Report Interval Time in RAM
  RIThE: Real = 0.0; //Report Interval Threshold in EEPROM
  RIThR: Real = 0.0; //Report Interval Threshold in RAM


  //Printer variable
  PrintingLine: Integer;
  CalPrint: Boolean;

  FixedFont:String;

  //Open file filter
  OpenFileFilter: String = 'All files|*.*|Comma Separated Variables|*.csv|Calibration logs|*.cal|UDM usage log|*.log|Text files|*.txt';

  //Firmware settings
  FirmwareFilter: String='All files|*.hex|SQM-LE/LU|SQMLE-?-3-*.hex|DL (Datalogger)|SQM-LU-DL-?-6-??.hex|V (Vector)|SQM-LU-DL-V*.hex|LR (RS232)|SQM-LR*.hex|DL-Snow|SQM-LU-DLS-?-13-??.hex|GDM|MAG*.hex|C (Color)|SQMLE-?-4-*.hex|RPi module|SQM-RPi-?-14-??.hex|SQM-LL Lite module|SQM-LL-?-15-??.hex';
  FirmwareFilterIndex:Integer = 0;
  FirmwareFilename : AnsiString;
  FirmwareDirectory:String;
  FirmwareCounter:Integer = 0;
  FirmwareState: Integer = 0; //0=start,

  //Plotter settings
  PlotFileFilter: String;
  PlotFileDirectory: String;
  SelectedColourScheme: String;


  //Accessory settings
  A1Enabled:Boolean = False; //Accessory 1 (external Humidity/Temperature sensor)

  SnowLoggingEnabled:Boolean = False;
  AccSnowLEDState:Boolean = False;
  AccSnowLEDOnReading:Integer =0;
  AccSnowLEDOffReading:Integer=0;
  AccSnowLEDDifference:Integer=0;

  //Staged snow logging: take an LED-off reading, then an LED-on reading, and
  //combine both into one logged record. Gated so existing single-flag DLS
  //logging is unchanged when this is False. Wire SnowStagedLogging to a UI
  //checkbox later if a runtime toggle is wanted.
  SnowStagedLogging:Boolean = True;  //Enable the LED-off/LED-on staged capture.
  SnowFreshMaxTries:Integer = 30;    //Max GetReading polls while waiting for a fresh reading.
  SnowReadSkip:Integer = 0;          //Fresh readings to discard after the LED turns on, so the running average settles before capture.

  //Position
  MyLatitude: extended = 0.0; //Latitude
  MyLongitude: extended = 0.0; //Longitude
  MyElevation: extended = 0.0; //Elevation

  //Initial window loading
  InitialLoad: Boolean = True;
  StartLogging:Boolean = False;
  StartUpSettings:String;
  StartupParamcount:Integer;
  StartupParamStrings: array of String;

  {Communications busy timer counter.
    Reaching the CommBusyLimit will close the comm port.}
  CommBusyTime: Integer = 0;
  CommOpened: Boolean = False;


  {Created objects indicator}
  ViewedLogCreated:Boolean = False;
  serCreated:Boolean = False;
  GPSserCreated:Boolean = False;
  GoToserCreated:Boolean = False;
  ParameterValueCreated:Boolean = False;


  {$IFDEF LinuxBluetooth}
  //Bluetooth variables
  bdaddr: bdaddr_t;
  OutSocket: cint;
  InSocket: cint;
  {$ENDIF Linux}


   {UDM log file}
   UDMLogFileName:String;
   UDMLogFile: TextFile;

   {Serial number specific log file}
   SNLogFileName:String;
   SNLogFile:TextFile;

   //Operating system
   OperatingSystemString: String = '';


  {$IFDEF Linux}
  //Bluetooth functions
  function c_close(fd: cint): cint; external name 'close';
  {$ENDIF Linux}

  //Utilities
  function CleanFileName(const InputString: string): string;
  function LimitInteger(Source:Integer; Minimum:Integer; Maximum:Integer):Integer;
  function RemoveMultiSlash(Input: String): String;

  procedure LHFCheck(command:string);

  function Darkness2MPSASString(Darkness:Float):String;

  function Darkness2CDM2(Darkness:Float):Float;
  function Darkness2CDM2String(Darkness:Float):String;
  function Darkness2MCDM2(Darkness:Float):Float;
  function Darkness2MCDM2String(Darkness:Float):String;

  function Darkness2NELM(Darkness:Float):Float;
  function Darkness2NELMString(Darkness:Float):String;

  function Darkness2NSU(Darkness:Float):Float;
  function Darkness2NSUString(Darkness:Float):String;
  procedure WriteLnDebug(TextLine: String);

implementation

uses

  sockets, //required to determin the Ethernet IP address of this machine.
  header_utils,
  dlretrieve,
  textfileviewer,
  startupoptions,
  LCLIntf;

{Replace non-filename characters with an underscore}
function CleanFileName(const InputString: string): string;
var
  i: integer;
  ResultWithSpaces: string;
begin
  ResultWithSpaces := InputString;
  for i := 1 to Length(ResultWithSpaces) do
  begin
    // These chars are invalid in file names
    if (ResultWithSpaces[i] in [' ', '/', '\', ':', '*', '?', '"', '<', '>', '|']) then
      ResultWithSpaces[i] := '_';
  end;
  Result := ResultWithSpaces;
end;

procedure GetIPAddr(var buf: array of char; const len: longint);
const
  CN_GDNS_ADDR = '127.0.0.1';
  CN_GDNS_PORT = 53;
var
  s: string;
  sock: longint;
  HostAddr: TSockAddr;
  l: integer;
  IPAddr: TInetSockAddr;
begin
  Assert(len >= 16);
  sock := fpsocket(AF_INET, SOCK_DGRAM, 0);
  assert(sock <> -1);
  IPAddr.sin_family := AF_INET;
  IPAddr.sin_port := htons(CN_GDNS_PORT);
  IPAddr.sin_addr.s_addr := StrToHostAddr(CN_GDNS_ADDR).s_addr;
  if (fpConnect(sock, @IPAddr, SizeOf(IPAddr)) <> 0) then exit;
  try
    l := SizeOf(HostAddr);
    if (fpgetsockname(sock, @HostAddr, @l) = 0) then
    begin
      s := NetAddrToStr(HostAddr.sin_addr);
      StrPCopy(PChar(Buf), s);
    end;
  finally
    if (CloseSocket(sock) <> 0) then;
  end;
end;

function GetInternalIP: string;
var
  IPStr: array[0..30] of char;
begin
  GetIPAddr(IPStr, SizeOf(IPStr));
  Result := IPStr;
end;


procedure TForm1.FormCreate(Sender: TObject);
var
  Info:TVersionInfo;
  UDMLogDirectory:String;
begin
  // Touch Screen.Monitors to force UpdateMonitors to run now,
  // while both monitors are already active
  if Screen.MonitorCount > 0 then ; // side-effect: triggers UpdateMonitors

  {Create new log file named udm.log}
  { start new one at beginning of session (overwrites last session) }
  UDMLogDirectory:=RemoveMultiSlash(appsettings.LogsDirectory + DirectorySeparator);
  {Check for log dirfectory exists}
  if not DirectoryExists(UDMLogDirectory)  then begin
    MessageDlg ('Log directory does not exist:'+sLineBreak + UDMLogDirectory +sLineBreak + 'Resetting to default.', mtConfirmation,[mbOK],0);

    {Reset}
    appsettings.LogsDirectoryReset();
    {Save setting}
    vConfigurations.WriteString('Directories','LogsDirectory',appsettings.LogsDirectory);

  end;

   UDMLogFileName:=RemoveMultiSlash(appsettings.LogsDirectory + DirectorySeparator)+'udm.log';
   AssignFile(UDMLogFile,UDMLogFileName);
   Rewrite(UDMLogFile); // File is opened for write, and emptied.
   //View->Log has not been created yet and does not contain the folloing message, but the log file does.
   WriteLn(UDMLogFile,Format('%s : UDM Started. Local time: %s',[FormatDateTime('yyyy-mm-dd hh:nn:ss.zzz',NowUTC()) ,FormatDateTime('yyyy-mm-dd hh:nn:ss',Now()) ])); //Write first line to empty file.
   Flush(UDMLogFile);
   System.Close(UDMLogFile); //Allow other instances to write to file.


  //try
  // AssignFile(UDMLogFile,UDMLogFileName);
  // Rewrite(UDMLogFile); //Open file for writing
  //except
  //    MessageDlg ('Log file does not exist:'+sLineBreak + UDMLogFileName, mtConfirmation,[mbOK],0);
  //    Halt;
  //end;


KMLColorsCleardarksky:= TKMLColors.create(
'ff000000'
, 'ff1a1a1a'
, 'ff363636'
, 'ff841400'
, 'fff92600'
, 'ff148139'
, 'ff26f46c'
, 'ff1eaeb3'
, 'ff2bfaff'
, 'ff1560be'
, 'ff1a75e9'
, 'ff0e22ab'
, 'ff132ee2'
, 'ffb1b2b1'
, 'fffafafa'
);

KMLValuesCleardarksky:= TKMLValues.create(
  21.99
, 21.93
, 21.89
, 21.81
, 21.69
, 21.51
, 21.25
, 20.91
, 20.49
, 20.02
, 19.50
, 18.95
, 18.38
, 17.80
, 0
);

{ Documentation and example:
  Colours for the New Atlas
   Darkness KML_colour_value
    > 21.97 ff000000
    > 21.96 ff333333
    > 21.94 ff808080
    > 21.90 ff690000
    > 21.82 ffff0000
    > 21.68 ffff4040
    > 21.45 ff387c25
    > 21.09 ff259699
    > 20.60 ff40ffff
    > 20.01 ff0baff9
    > 19.35 ff0000ff
    > 18.65 ff8d0ac4
    > 17.93 ffce91e9
    > 0     ffffffff

  For example, if the darkenss is greater than 21.90 and less than or eaual
  to 21.94, then the KML colour will be ff690000

  The KMl colouring is defined as:
    hexBinary value: aabbggrr
    aa=Alpha (transparency)

    The original values (in mcd/m2) and colors from:
      The new world atlas of artificial night sky brightness
      http://advances.sciencemag.org/content/advances/2/6/e1600377.full.pdf
      Page 9
    Use conversion formula from:
      http://unihedron.com/projects/darksky/magconv.php
      [value in mag/arcsec2] = Log10([value in cd/m2]/108000)/-0.4
    For example:  0.176mcd/m2=.000176cd/m2 then get mpsas
    See spreadsheet colorlegend.ods for conversions.
    Use a screen color converter to pull RGB colors from PDF.

  }
  //calculated from new sky atlas conversion from mcd/m^2 (22 based) to mpsas.
  KMLColorsNewatlas:= TKMLColors.create(
  'ff000000'
, 'ff333333'
, 'ff808080'
, 'ff690000'
, 'ffff0000'
, 'ffff4040'
, 'ff387c25'
, 'ff259699'
, 'ff40ffff'
, 'ff0baff9'
, 'ff0000ff'
, 'ff8d0ac4'
, 'ffce91e9'
, 'ffffffff'
  );

  KMLValuesNewatlas:=  TKMLValues.create(
    21.97
  , 21.96
  , 21.94
  , 21.90
  , 21.82
  , 21.68
  , 21.45
  , 21.09
  , 20.60
  , 20.01
  , 19.35
  , 18.65
  , 17.93
  , 0
  );


// InstallSigHandler;
// FpKill(GetProcessID,SIGUSR1);

  //test sunrise
{ TODO : sunrise test }
//  Sun_Rise(Now(); latitude, longitude:extended):TDateTime;

  // Format settings to convert a string to a float
  FPointSeparator := DefaultFormatSettings;
  FPointSeparator.DecimalSeparator := '.';
  FPointSeparator.ThousandSeparator := '#';// disable the thousand separator
  FCommaSeparator := DefaultFormatSettings;
  FCommaSeparator.DecimalSeparator := ',';
  FCommaSeparator.ThousandSeparator := '#';// disable the thousand separator

  {$ifdef MSWindows}
     // ce + win32 + win64, delphi compat
  {$endif}
  {$ifdef Windows}
     // ce + win32 + win64, more logical.
     FixedFont:='Consolas'; //Courier was too wide for the Configuration panel.
  {$endif}
  {$ifdef Linux}
   //Fixed font chose to properly display the zero character
   //FixedFont:='Monospace'; //gtk3 does not display spaces properly
   FixedFont:='Andale Mono'; //qt5 displays spaces properly
   {Only Linux users have access to the ARP method tool until it works for all platforms.}
   ARPTelnetButton.Visible:=True;
  {$endif}
  {$ifdef Darwin}
   FixedFont:='Monaco';
  {$endif}

   ser:=TBlockSerial.Create;
   serCreated:=True;
   RS232PortBaud:=StrToIntDef(RS232Baud.Text,115200);
   RS232Portname:=RS232Port.Text;

   GPSser:=TBlockSerial.Create;
   GPSserCreated:=True;
   GoToser:=TBlockSerial.Create;
   GoToserCreated:=True;

   gettingversion:=False;

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

   ViewedLog:=TStringList.Create;
   ViewedLogCreated:=True;
   ViewingLog:=False;

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

   //Hide normally unused pages
   DataNoteBook.Page[4].TabVisible:=False; //Datalogging page
   DataNoteBook.Page[6].TabVisible:=False; //GPS page
   DataNoteBook.Page[9].TabVisible:=False; //Vector page

   //Hide unfinshed pages
   DataNoteBook.Page[7].TabVisible:=False; //Troubleshooting
   //CommNoteBook.Page[2].TabVisible:=False; //RS232

   //View menu-selected pages
   // defaults are checked True/False in object properties for menu items
   DataNoteBook.Page[5].TabVisible:=ViewConfigMenuItem.Checked; //Configuration
   DataNoteBook.Page[8].TabVisible:=ViewSimMenuItem.Checked; //Simulation

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

   //Simulation defaults
   SimPeriodMax.Text:=Format('%d',[3]);//Seconds (1 - 300)
   SimFreqMax.Text:=Format('%d',[1]); //Hz (1 - 100000)
   SimTimingDiv.Value:=0;
   SimTempMin.Text:=Format('%d',[244]); //Lower limit 0
   SimTempMax.Text:=Format('%d',[246]); //Upper limit 1024??
   SimTempDiv.Value:=0;
   SimEnable:=False;

   //Set up all fixed font widgets
   {$ifdef Darwin}
     {Allow Mac Cocoa GUI to use other fonts.}
     FoundDevices.Style:= lbOwnerDrawFixed;
     VersionListBox.Style:= lbOwnerDrawFixed;
     ReadingListBox.Style:= lbOwnerDrawFixed;
   {$endif}
   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;
   LCODes.Font.Name:=FixedFont;
   LCTDes.Font.Name:=FixedFont;
   DCPDes.Font.Name:=FixedFont;
   DCTDes.Font.Name:=FixedFont;
   LCOAct.Font.Name:=FixedFont;
   LCTAct.Font.Name:=FixedFont;
   DCPAct.Font.Name:=FixedFont;
   DCTAct.Font.Name:=FixedFont;
   ITiDes.Font.Name:=FixedFont;
   ITiE.Font.Name:=FixedFont;
   ITiR.Font.Name:=FixedFont;
   IThDes.Font.Name:=FixedFont;
   IThE.Font.Name:=FixedFont;
   IThR.Font.Name:=FixedFont;

   //Title:
   Info := TVersionInfo.Create;
   Info.Load(HINSTANCE);
   UDMversion:=IntToStr(Info.FixedInfo.FileVersion[0])+'.'+
     IntToStr(Info.FixedInfo.FileVersion[1])+'.'+
     IntToStr(Info.FixedInfo.FileVersion[2])+'.'+
     IntToStr(Info.FixedInfo.FileVersion[3]);
   Form1.Caption:=Form1.Caption+'  ('+UDMversion+')';
   Info.Free;

   //Configuration page
   CalPrint:=False;
   Panel1.Canvas.Font.Name := FixedFont;// FixedFont Defined earlier. Was 'Arial'
   {$ifdef Darwin}
     Panel1.Canvas.Font.Size := 12; //was 8, but MAC highDPI looked like 3x5pixel
   {$else}
     Panel1.Canvas.Font.Size := 8; //was 10, 8 for fitting all text on screen
   {$endif}
   //Printing only tested in Windows and Linux,
   // MacOSX has paper sizing issues:
   {$if defined(MSWindows) or defined(Linux)}
     PrintCalReport.Visible:=True;
     PrintLabelButton.Visible:=True;
   {$ifend}


   //VectorTab tab
   //****vector_utils.startup();

   //Paramaters from command line
   ParameterValue := TStringList.Create;
   ParameterValueCreated:=True;

   FDateSettings:= DefaultFormatSettings;
   FDateSettings.DateSeparator:='-';
   FDateSettings.TimeSeparator:=':';
   FDateSettings.ShortDateFormat:='yyyy-mm-dd';
   FDateSettings.ShortTimeFormat:='HH:MM:SS';

   {Snow staged-logging: restore the read-skip count and wire its change handler
    in code (no .lfm event needed). This is the number of fresh readings to
    discard after the snow LED turns on, so the average settles before capture.}
   SnowReadSkip:= LimitInteger(StrToIntDef(
     vConfigurations.ReadString('LogContinuousSettings','SnowReadSkip','0'),0),0,100);
   SnowReadSkipEdit.Text:= IntToStr(SnowReadSkip);
   SnowReadSkipEdit.Hint:= 'Fresh readings to discard after the snow LED turns on'
     + ' before capturing the snow reading (lets the average settle).';
   SnowReadSkipEdit.ShowHint:= True;
   SnowReadSkipEdit.OnChange:= @SnowReadSkipEditChange;

   //Update GUI (last item in this section)
   Application.ProcessMessages;

end;


function Darkness2MPSASString(Darkness:Float):String;
begin
     if Darkness=0 then
       Darkness2MPSASString:='--'
     else
       Darkness2MPSASString:=FormatFloat('#0.00', Darkness, FPointSeparator);
end;


function Darkness2CDM2(Darkness:Float):Float;
begin
  Darkness2CDM2:=10.8e4 * power(10, (-0.4 * Darkness));
end;

function Darkness2CDM2String(Darkness:Float):String;
begin
     if Darkness=0 then
       Darkness2CDM2String:='--'
     else
       Darkness2CDM2String:=FloatToStrf(Darkness2CDM2(Darkness),ffExponent,4,2,FPointSeparator);
end;

function Darkness2MCDM2(Darkness:Float):Float;
begin
  Darkness2MCDM2:=10.8e7 * power(10, (-0.4 * Darkness));
end;

function Darkness2MCDM2String(Darkness:Float):String;
begin
     if Darkness=0 then
       Darkness2MCDM2String:='--'
     else
       Darkness2MCDM2String:=Format('%0.5f',[Darkness2MCDM2(Darkness)]);
end;

function Darkness2NELM(Darkness:Float):Float;
begin
  Darkness2NELM:=7.93 - 5 * log10(power(10, (4.316 - (Darkness / 5))) + 1);
end;

function Darkness2NELMString(Darkness:Float):String;
begin
     if Darkness=0 then
       Darkness2NELMString:='--'
     else
       Darkness2NELMString:=Format('%1.2f',[Darkness2NELM(Darkness)],FPointSeparator);
end;

function Darkness2NSU(Darkness:Float):Float;
begin
  Darkness2NSU:=power(10, (0.4 * (21.6 - Darkness)));
end;

function Darkness2NSUString(Darkness:Float):String;
begin
  if Darkness=0 then
    Darkness2NSUString:='--'
  else
    Darkness2NSUString:=Format('%1.2f',[Darkness2NSU(Darkness)],FPointSeparator);
end;


procedure TForm1.ClearResults();
const
   {$WRITEABLECONST ON}
   IsInside:Boolean=False;
   {$WRITEABLECONST OFF}
begin

     if IsInSide then begin
       //StatusMessage('Is inside ClearResults already.');
       Exit;
     end;
     IsInside:=True;

     try

     {Information tab}
     VersionListBox.Items.Clear;
     ReadingListBox.Items.Clear;
     //HeaderButton.Enabled:=False;
     DisplayedReading.Caption:='';
     DisplayedNELM.Caption:='';
     Displayedcdm2.Caption:='';
     DisplayedNSU.Caption:='';

     SelectedModel:=0;
     SelectedFeature:='0';
     SelectedProtocol:='0';
     SelectedUnitSerialNumber:='0';
     SelectedModelDescription:='unknown';

     ResetForFirmwareProgressBar.Position:=0;
     LoadFirmwareProgressBar.Position:=0;
     FinalResetForFirmwareProgressBar.Position:=0;
     StatusMessage('Press the Version or Reading button to see results from the selected device.');
     DLRefreshed:=False;
     LogRecordResult.Clear;
     ITiE.Text:='';
     ITiR.Text:='';
     IThE.Text:='';
     IThR.Text:='';
     LCOAct.Text:='';
     LCTAct.Text:='';
     DCPAct.Text:='';
     DCTAct.Text:='';
     ClearLockVisibility();
     USBSerialNumber.Text:='';
     USBPort.Text:='';
     EthernetMAC.Text:='';
     EthernetIP.Text:='';
     EthernetPort.Text:='';
     //RS232Port.Text:='';
     //RS232Baud.Text:='';

     //Configuration page
     ConfRdgmpsas.Caption:='';
     ConfRdgPer.Caption:='';
     ConfRdgTemp.Caption:='';
     ConfCalmpsas:=0;
     ConfCalLightTemp:=0;
     ConfCalPeriod:=0;
     ConfCalDarkTemp:=0;
     Form1.Panel1.Canvas.Clear;
     LHCombo.ItemIndex:=-1;
     SelectedLH:=-1;
     LensCombo.ItemIndex:=-1;
     SelectedLens:=-1;
     FilterCombo.ItemIndex:=-1;
     SelectedFilter:=-1;

     //Firmware page
     ResetXPortProgressBar.Position:=0;
     FinalResetForXPortProgressBar.Position:=0;
     bXPortDefaults.Enabled:=False;

{	$SCLOR->Contents("");
	$SLCTR->Contents("");
	$SDCPR->Contents("");
	$SDCTR->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');
}

     finally
       IsInside:=False;
     end;
end;

//multicast procedure
procedure TForm1.findEth;
var
  sndsock:TUDPBlockSocket;
  buf:string;
  requeststring:Integer;//Longword;
  i:Integer;
  MACfound: Boolean = False;
  MACstring, MACcheck:string;
  MACstrings:  array of String;
  IPstring,IPBroadcast:String;
  IPstrings:TStringList;
  pieces:TStringList;
  IPBroadcastStrings:TStringList;

begin
  SetLength(MACstrings,1);
  MACstrings[0]:='';

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

  IPstrings:=TStringList.Create;

  StatusMessage('Looking for Ethernet connections on this machine.');
  IPBroadcastStrings:=TStringList.Create;
  sndsock:=TUDPBlockSocket.Create;
  sndsock.GetLocalSinIP;
  sndsock.GetSinLocal;

  try

    { // Getting the IP address worked for many years, then Apple ccrippled access to the IP address in Sequoia 15.3 circa 2025-02
    sndsock.ResolveNameToIP(sndsock.LocalName,IPstrings);
    for IPstring in IPstrings do begin
    {$IFNDEF Windows}
    writeln(IPstring); {Debug only works on Mac and Linux, crashes Windows since terminal is not available}
    {$ENDIF Windows}
      if not(AnsiContainsText(IPstring,':')) then //Ignore with IPV6 addresses
       if AnsiStartsStr('127.',IPstring) or AnsiStartsStr('169.254.',IPstring) or AnsiStartsStr('0.0.0.0',IPstring) then begin
          IPBroadcast:='255.255.255.255';
          StatusMessage('This machine uses IP: ' + IPstring + ' , will use a broadcast to: ' + IPBroadcast);
          IPBroadcastStrings.Add(IPBroadcast);
       end
       else begin
             pieces.DelimitedText:=IPstring;
             IPBroadcast:=pieces.Strings[0]+'.'+pieces.Strings[1]+'.'+pieces.Strings[2]+'.255'; //Triple subnet. was this a long time ago.
             StatusMessage('This machine uses IP: ' + IPstring + ' , will use a multicast to: ' + IPBroadcast);
             IPBroadcastStrings.Add(IPBroadcast);
             IPBroadcast:='255.255.255.255'; //no subnet. fix for double octet subnet mask (was this before 20230426)
             StatusMessage('Also doing full broadcast to fix for double octet subnet mask, using multicast to: ' + IPBroadcast);
             IPBroadcastStrings.Add(IPBroadcast);
       end;
    end;
    sndsock.CloseSocket;}

   //Trying a new method to get the IP address circa 2025-03
   IPstring:=GetInternalIP;
    if AnsiStartsStr('127.',IPstring) or AnsiStartsStr('169.254.',IPstring) or AnsiStartsStr('0.0.0.0',IPstring) then begin
       IPBroadcast:='255.255.255.255';
       StatusMessage('This machine uses IP: ' + IPstring + ' , will use a broadcast to: ' + IPBroadcast);
       IPBroadcastStrings.Add(IPBroadcast);
    end
    else begin
          pieces.DelimitedText:=IPstring;

          StatusMessage('pieces.Count: ' + IntToStr(pieces.Count));
          if ( pieces.Count >= 3) then begin {Confirm proper length of IP string}
            IPBroadcast:=pieces.Strings[0]+'.'+pieces.Strings[1]+'.'+pieces.Strings[2]+'.255'; //Triple subnet. was this a long time ago.
            StatusMessage('This machine uses IP: ' + IPstring + ' , will use a multicast to: ' + IPBroadcast);
            IPBroadcastStrings.Add(IPBroadcast);
            IPBroadcast:='255.255.255.255'; //no subnet. fix for double octet subnet mask (was this before 20230426)
            StatusMessage('Also doing full broadcast to fix for double octet subnet mask, using multicast to: ' + IPBroadcast);
            IPBroadcastStrings.Add(IPBroadcast);

          end else
            StatusMessage('IPString too short, probably no connection to the network. Should be >=3 but is: ' + IntToStr(pieces.Count));

    end;



  finally
    sndsock.free;
    if Assigned(pieces) then FreeAndNil(pieces);
    if Assigned(IPstrings) then FreeAndNil(IPstrings);
  end;

  try
    for IPBroadcast in IPBroadcastStrings do begin
      requeststring:=246; //246 = f6 is the request to the Lantronix XPort
      sndsock:=TUDPBlockSocket.Create;
      try
        sndsock.EnableBroadcast(True);
        sndsock.connect(IPBroadcast,'30718');
        sndsock.SendInteger(SwapEndian(requeststring)); //caused a rangecheck error
        sndsock.SendInteger(LongInt(SwapEndian(requeststring)));
        repeat
          buf:=sndsock.RecvPacket(2000);
          //writeln('Response length: '+inttostr(Length(buf)));//debug
          //for i:=1 to Length(buf) do write(IntToHex(ord(buf[i]),2)+' ');//debug
          // writeln(Length(buf)); //debug to show numberof bytes received

          { Expecting 30 byte response starting with:
              000000f7   from Lantronix XPort
              000001f7   from ESP32 model}
          //if ((Length(buf) = 30) and buf[3]=)then begin
          if ((Length(buf) = 30) and (byte(buf[4])=$f7)) then begin
                 //writeln(IntToHex(ord(buf[4]),2)); //debug length of UDP response
                 //if (byte(buf[4])=$f7) then writeln('equals f7');
                 MACstring:=''; //Clear MAC address for populating

                 {grab MACstring}
                 for i:=25 to 30 do
                     MACstring:=MACstring+IntToHex(ord(buf[i]),2);


                 { Check for duplicate Ethernet devices }
                 MACfound:=False;
                 if high(MACstrings)>0 then begin
                   for MACcheck in MACstrings do begin
                     if MACstring=MACcheck then MACfound:=True;
                   end;
                 end;

                 if not MACfound then begin
                        { Save found device}
                        with FoundDevicesArray[high(FoundDevicesArray)] do begin
                                  SerialNumber:=MACstring;//MAC address
                                  Connection:=sndsock.GetRemoteSinIP;
                                  if (byte(buf[3])=$0) then Hardware:='Eth'
                                  else if (byte(buf[3])=$1) then Hardware:='WiFi'
                                  else Hardware:='????';
                                  StatusMessage('Found: '+ SerialNumber + ' ' + Connection);
                        end;
                        SetLength(FoundDevicesArray,high(FoundDevicesArray)+2);

                        {Add MACstring to array of found MACstrings}
                        MACstrings[high(MACstrings)]:=MACstring;
                        SetLength(MACstrings,high(MACstrings)+2);
                 end;

            end;
        until (Length(buf) = 0);
        sndsock.CloseSocket;
      finally
        sndsock.free;
      end;
    end;
  finally
    if Assigned(IPBroadcastStrings) then FreeAndNil(IPBroadcastStrings);
  end;
  StatusMessage('Finished looking for Ethernet devices.');
end;

{$IFDEF Darwin}
procedure TForm1.findusbdarwin;
Var Info : TSearchRec;
    Count : Longint;
    USBDeviceSerial: String;
    LinuxDeviceFile: String;
Begin
  Count:=0;
  If FindFirst ('/dev/cu.usbserial*',faAnyFile ,Info)=0 then {was "tty.", but that was blocking badly on MacOS Sequoia 15.x}
    begin
    Repeat
      Inc(Count);
      With Info do
        begin
        Writeln(Name:40,Size:15); //Debug, write found USB devicename to terminal
        USBDeviceSerial:=AnsiMidStr(Name,15,8);
        LinuxDeviceFile:='/dev/' + Name;
        if (length(USBDeviceSerial)>0) then begin
          with FoundDevicesArray[high(FoundDevicesArray)] do
          begin
               SerialNumber:=USBDeviceSerial;
               Connection:=LinuxDeviceFile;
               Hardware:='USB';
          end;
          SetLength(FoundDevicesArray,high(FoundDevicesArray)+2);
        end;
        end;
    Until FindNext(info)<>0;
    end;
  FindClose(Info);
  StatusMessage('Finished USB search. Found '+IntToStr(Count)+' matches');
End;
{$ENDIF}

{$IFDEF Windows}
procedure TForm1.FindUSB();
var
  usbdevname,usbserial: String;
  pieces: TStringList;
  kAvailable: TStringlist;
  kConnected: TStringlist;
  i,foundindex: Integer;
  reg,reg2,reg3,reg4: TRegistry;
  Regkey: String;
  //RegCOMkey: String;
  keys: TStringlist;
  FTDIDriverName,FTDIDriverVersion:String;
  begin

    //Note: The serialnumber gets garbled if FTDI's instructions to
    //      "Ignore Hardware Serial Number" is installed.
    //      all serial numbers then look like this: 5&1846f4dd&0&2

    kAvailable := TStringList.Create;

    StatusMessage('FindUSB: Checking for Windows USB attached devices.');

    kConnected := TStringList.Create;

    // Find FTDI USB device on a Windows machine
    Regkey:='HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\';
    //RegCOMkey:='HKEY_LOCAL_MACHINE\\HARDWARE\\DEVICEMAP\\SERIALCOMM\\';

    //Check for all connected serial commuication devices
    keys := TStringList.Create;
    reg := TRegistry.Create;
    reg.RootKey := HKEY_LOCAL_MACHINE;
    if reg.KeyExists('HARDWARE\DEVICEMAP\SERIALCOMM\') then
        begin
          reg.OpenKeyReadOnly('HARDWARE\DEVICEMAP\SERIALCOMM\');
          StatusMessage('FindUSB: At least one COMM port is active.');
          reg.GetValueNames(keys);
          for i := 0 to keys.Count-1 do
            begin
              kConnected.Add(reg.ReadString(keys.ValueFromIndex[i]));
              StatusMessage('FindUSB: Connected device = "'+reg.ReadString(keys.ValueFromIndex[i])+'"');
            end;
          //Form2.Memo1.Lines.AddStrings(keys);
        end
     else
        StatusMessage('FindUSB: No COMM ports are active.');
     reg.Free;

    // Check all installed FTDI drivers.
    pieces := TStringList.Create;
    pieces.Delimiter := '+';
    keys := TStringList.Create;
    StatusMessage('FindUSB: '+format ('Checking Registry for: %s ...',[Regkey]));
    reg := TRegistry.Create;
    reg.RootKey := HKEY_LOCAL_MACHINE;
    if reg.KeyExists('SYSTEM\CurrentControlSet\Enum\FTDIBUS\') then
       begin
         reg.OpenKeyReadOnly('SYSTEM\CurrentControlSet\Enum\FTDIBUS\');
         StatusMessage('FindUSB: FTDI driver has been installed at least once.');
         reg.GetKeyNames(keys);
         StatusMessage('FindUSB: FTDI driver has been installed '+inttostr(keys.Count)+' times.');
         for i := 0 to keys.Count-1 do
           begin
             pieces.DelimitedText := keys[i];
             reg2 := TRegistry.Create;
             reg2.RootKey := HKEY_LOCAL_MACHINE;
             reg2.OpenKeyReadOnly('SYSTEM\CurrentControlSet\Enum\FTDIBUS\'+keys[i]+'\0000\Device Parameters\');
             StatusMessage('FindUSB: Checking registry key = "'+keys[i]+'" , PortName = "'+reg2.ReadString('PortName')+'"');
             kConnected.Sort;
             kConnected.Sorted:=True;
             if kConnected.Find(reg2.ReadString('PortName'),foundindex) then begin
                  StatusMessage('FindUSB: FTDIBUS entry matched: "'+keys[i]+'" has PortName "'+reg2.ReadString('PortName')+'"');

                  if pieces.Count>2 then
                    usbserial:=AnsiLeftStr(pieces.Strings[2], 8)
                  else
                    usbserial:=AnsiLeftStr('NoSerial', 8);

                  usbdevname:=kConnected[foundindex];

                  with FoundDevicesArray[high(FoundDevicesArray)] do
                       begin
                          SerialNumber:=usbserial;
                          Connection:=usbdevname;
                          Hardware:='USB';
                       end;
                  SetLength(FoundDevicesArray,high(FoundDevicesArray)+2);

                  kAvailable.Add(kConnected[foundindex]+' : '+usbserial);
                  reg3:= TRegistry.Create;
                  reg3.RootKey := HKEY_LOCAL_MACHINE;
                  reg4:= TRegistry.Create;
                  reg4.RootKey := HKEY_LOCAL_MACHINE;
                  if reg3.KeyExists('SYSTEM\CurrentControlSet\Enum\USB\VID_0403&PID_6001\'+
                    FoundDevicesArray[high(FoundDevicesArray)].SerialNumber) then
                    begin
                      //StatusMessage('FTDI driver is being found.');
                      reg3.OpenKeyReadOnly('SYSTEM\CurrentControlSet\Enum\USB\VID_0403&PID_6001\'+
                         usbserial);
                      //StatusMessage('Serialnumber= '+usbserial);

                      FTDIDriverName:=reg3.ReadString('Driver');
                      //StatusMessage('Driver= '+FTDIDriverName);

                      reg4.OpenKeyReadOnly('SYSTEM\CurrentControlSet\Control\Class\'+FTDIDriverName);
                      //HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\{drivername\0040 . DriverVersion REG_SZ
                      StatusMessage('FindUSB: ProviderName = '+reg4.ReadString('ProviderName'));
                      StatusMessage('FindUSB: DriverDesc = '+reg4.ReadString('DriverDesc'));
                      FTDIDriverVersion:=reg4.ReadString('DriverVersion');
                      StatusMessage('FindUSB: DriverVersion = '+FTDIDriverVersion);

                    end
                  else
                  StatusMessage('FindUSB: FTDI driver not found.');

                  reg3.Free;
                end;
             reg2.Free;
           end;
       end
    else
        StatusMessage('FindUSB: FTDI driver has never been installed.');

    //HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USB\VID_0403&PID_6001\FTF4FO08.Driver= {drivername\0040 REG_SZ
    //HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Class\{drivername\0040 . DriverVersion REG_SZ

    reg.Free;
    keys.Free;
end;
{$ENDIF Windows}

{$IFDEF Linux}
//Examples:
//ls -al /dev/serial/by-id/
//lrwxrwxrwx 1 root root  13 May 16 10:09 usb-FTDI_MM232R_USB_MODULE_FTF4FO08-if00-port0 -> ../../ttyUSB1
//lrwxrwxrwx 1 root root  13 May 16 10:09 usb-FTDI_MM232R_USB_MODULE_FTFTX9UT-if00-port0 -> ../../ttyUSB2
//lrwxrwxrwx 1 root root  13 Jan 10 15:55 usb-FTDI_USB__-__Serial_Cable_FTSC89JG-if00-port0 -> ../../ttyUSB2

procedure TForm1.FindUSB();
const
  USBDevicePath = '/dev/serial/by-id/';
Var Info : TSearchRec;
    Count : Longint;
    USBDeviceSerial: String;
    LinuxDeviceFile: String;
    pieces:TStringList;
Begin
  pieces := TStringList.Create;
  pieces.Delimiter := '-';

  StatusMessage('FindUSB: Searching for Linux USB devices ...'); //Troubleshooting information

  Count:=0;

  try
    StatusMessage('FindUSB: Searching here : '+USBDevicePath+'usb-FTDI_*'); //Troubleshooting information
    If FindFirst (USBDevicePath+'usb-FTDI_*',faAnyFile ,Info)=0 then
      begin
      Repeat
        Inc(Count);
        With Info do
          begin
          //Writeln (Name:40,Size:15);
          pieces.DelimitedText:=Name;
          StatusMessage('FindUSB: Found this : '+Name); //Troubleshooting information
          USBDeviceSerial:=AnsiRightStr(pieces.Strings[pieces.Count-3],8);
          //USBDeviceSerial:=AnsiMidStr(Name,28,8);
          LinuxDeviceFile:=ExpandFileName(USBDevicePath+fpReadLink(USBDevicePath+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);
    StatusMessage('FindUSB: Finished Linux USB search. Found '+IntToStr(Count)+' matches');
  finally
  end;

  if Assigned(pieces) then FreeAndNil(pieces);
End;
{$ENDIF Linux}


{$IFDEF OldLinux}
//This code no longer works because udevadm was made accessible only by root.
procedure TForm1.FindUSB();
const
     READ_BYTES = 2048;
var
  usbdevname,usbserial: String;
  pieces: TStringList;
  kAvailable: TStringlist;
  OutputLines: TStringList;
  MemStream: TMemoryStream;
  OurProcess: TProcess;
  NumBytes: LongInt;
  BytesRead: LongInt;
  LookForState: Integer;

  begin

    kAvailable := TStringList.Create;

    StatusMessage('Checking for USB attached devices.');


  // A temp Memorystream is used to buffer the output
  MemStream := TMemoryStream.Create;
  BytesRead := 0;

  pieces := TStringList.Create;
  pieces.Delimiter := '=';
  OurProcess := TProcess.Create(nil);
  OurProcess.Executable := 'udevadm';
  OurProcess.Parameters.Add('info');
  OurProcess.Parameters.Add('--export-db');

  // 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

      {Look for starting line that identifies ttyUSB, this is an indicator of the FTDI serial to USB connection.}
      if ((AnsiStartsStr('P:',OutputLines[NumBytes])) and (AnsiContainsText(OutputLines[NumBytes],'tty/ttyUSB'))) then
         begin
           LookForState:=1;
         end;

      {Reset looking if a blank line is found.}
      if (Length(OutputLines[NumBytes])=0) then
         begin
           LookForState:=0;
         end;

      if ((LookForState=1) and (AnsiStartsStr('E: DEVNAME=',OutputLines[NumBytes]))) then //get device name
         begin
           pieces.DelimitedText := OutputLines[NumBytes];
           usbdevname:=pieces.Strings[2];
           LookForState:=2;
         end;

      if ((LookForState=2) and (AnsiStartsStr('E: ID_SERIAL_SHORT',OutputLines[NumBytes]))) then //add found device
         begin
           pieces.DelimitedText := OutputLines[NumBytes];
           usbserial:=AnsiLeftStr(pieces.Strings[2], 8);
           kAvailable.Add(usbdevname+' : '+usbserial);


           //save found device
           with FoundDevicesArray[high(FoundDevicesArray)] do
           begin
                SerialNumber:=usbserial;
                Connection:=usbdevname;
                Hardware:='USB';
           end;
           SetLength(FoundDevicesArray,high(FoundDevicesArray)+2);

           LookForState:=0;
         end;

    end;

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

end;
{$ENDIF OldLinux}

{ TForm1 }

procedure TForm1.RequestButtonClick(Sender: TObject);
begin
  StatusMessage('Button pressed: Information: Reading');
  GetReading;

  {GetReportInterval}
  SendGet('rx');//to purge old interval reports if any
  ParseReportInterval(SendGet('Ix'));
  if (RITiE <> 0) or (RITiR<>0) or (RIThE <> 0.0) or (RIThR<>0.0) then begin
   rptintbutton.Visible:=True;
   RptIntInd.Brush.Color:=clRed;
   RptIntInd.Visible:=True;
  end
  else begin
   RptIntButton.Visible:=False;
   RptIntInd.Brush.Color:=clGray;
   RptIntInd.Visible:=False;
  end;


end;

//procedure TForm1.SelectFirmwareClick(Sender: TObject);
//begin
//    case CommNotebook.PageIndex of
//         0: begin  //USB
//            case SelectedModel of
//                 6: begin
//                  SelectFirmwareDialog.Filter:='SQM-LU-DL firmware files|SQM-LU-DL-?-?-??.hex|All firmware files|*.hex';
//                 end;
//                 3: begin
//                  SelectFirmwareDialog.Filter:='SQM-LU firmware files|SQMLE-?-3-*.hex|All firmware files|*.hex';
//                 end;
//                 8: begin
//                  SelectFirmwareDialog.Filter:='GDM firmware files|MAG*.hex|All firmware files|*.hex';
//                 end;
//                 11: begin
//                  SelectFirmwareDialog.Filter:='SQM-LU-DL-V firmware files|SQM-LU-DL-V*.hex|All firmware files|*.hex';
//                 end;
//            end;
//         end;
//         1: begin    //Ethernet
//         case SelectedModel of
//               3:   SelectFirmwareDialog.Filter:='SQM-LE files|SQMLE-?-3-*.hex|All firmware files|*.hex';
//               4:   SelectFirmwareDialog.Filter:='SQM-LE files|SQMLE-?-4-*.hex|All firmware files|*.hex';
//         end;
//
//         end;
//         2: begin  //RS232
//                  SelectFirmwareDialog.Filter:='SQM-LR files|SQM-LR*.hex|All firmware files|*.hex';
//         end;
//    end;
//   SelectFirmwareDialog.InitialDir:=appsettings.FirmwareDirectory;
//   if SelectFirmwareDialog.Execute then
//   begin
//     FirmwareFilename := SelectFirmwareDialog.Filename;
//     FirmwareFile.Text:=FirmwareFilename;
//
//     //To enable the Load button:
//     // - the firmware file has to be selected
//     // - a device must be selected, or RS232 model selected
//     LoadFirmware.Enabled:=((not(FirmwareFile.Text='')) and ((FoundDevices.SelCount>0) or (SelectedModel=model_LR)));
//
//     StatusMessage('Selected file: '+FirmwareFile.Text);
//   end;
//
//end;

procedure TForm1.EnableFirmware();
var
  FirmwareSelected: Boolean;
  PortDefined:Boolean;
begin
  case CommNotebook.PageIndex of
     0: begin //USB
       PortDefined:=not(USBPort.Text='');
     end;
     1: begin //Ethernet
       PortDefined:=(not(EthernetIP.Text='') and not(EthernetPort.Text=''));
     end;
     2: begin  //RS232
       PortDefined:=not(RS232Port.Text='');
     end;
  end;

     FirmwareSelected:=not(FirmwareFile.Text='');
     LoadFirmware.Enabled:=(FirmwareSelected and PortDefined);
     FWWaitUSBButton.Enabled:=(FirmwareSelected and PortDefined);
end;

procedure TForm1.FirmwareFileChange(Sender: TObject);
begin
     EnableFirmware();
end;

procedure TForm1.LogFirstRecordClick(Sender: TObject);
begin
  StatusMessage('Button pressed: Data Logging: First record');
  DLCurrentRecord:=1; //Point to the first record (record 1).
  LogRecordGet(DLCurrentRecord);
end;
//Get last record in the meter database
procedure TForm1.LogLastRecordClick(Sender: TObject);
begin
  StatusMessage('Button pressed: Data Logging: Last record');
  LogUpdateLogPointer();
  DLCurrentRecord:=DLEStoredRecords;
  LogRecordGet(DLCurrentRecord);
end;
procedure TForm1.LogUpdateLogPointer();
var
  pieces: TStringList;
begin
  if (SelectedModel>0) then begin
    //Set up parsing
    pieces := TStringList.Create;
    pieces.Delimiter := ',';
    pieces.StrictDelimiter := False; //Parse spaces also

    //Update next log pointer
    pieces.DelimitedText := sendget('L1x'); // Get pointer to next place [0 to last record]
    if (pieces.Count=2) then
      DLEStoredRecords:=StrToIntDef(pieces.Strings[1],0);

    if (DLEStorageCapacity>0) then begin
      DLDBSizeProgressBar.Position:= round((DLEStoredRecords / DLEStorageCapacity) * 100.0);
      DLDBSizeProgressBarText.Caption:=Format('%d from [1 to %d / %d] (%3.4f%% used)',[
           DLCurrentRecord,
           DLEStoredRecords,
           round(DLEStorageCapacity),
           (DLEStoredRecords / DLEStorageCapacity) * 100.0]);
      end else begin
        DLDBSizeProgressBar.Position:= 100;
        DLDBSizeProgressBarText.Caption:='Faulty storage size = '+IntToStr(DLEStorageCapacity);
      end
    end;

    if Assigned(pieces) then FreeAndNil(pieces);
end;

procedure TForm1.LogNextRecordClick(Sender: TObject);
begin
  StatusMessage('Button pressed: Data Logging: Next record');
  LogUpdateLogPointer();
  inc(DLCurrentRecord);
  DLCurrentRecord:=min(DLCurrentRecord,DLEStoredRecords);
  LogRecordGet(DLCurrentRecord);
end;

procedure TForm1.LogPreviousRecordClick(Sender: TObject);
begin
  StatusMessage('Button pressed: Data Logging: Previous record');
  LogUpdateLogPointer();
  dec(DLCurrentRecord);
  DLCurrentRecord:=max(DLCurrentRecord,1);
  LogRecordGet(DLCurrentRecord);
end;

procedure TForm1.OpenMenuItemClick(Sender: TObject);
//Open log files in text editor;
// Calibration logs (.cal)
// DL logs (.???)
begin
     OpenLogDialog.InitialDir:= appsettings.LogsDirectory;
     OpenLogDialog.Filter:=OpenFileFilter;
     if OpenLogDialog.Execute then begin
            Form2.Memo1.Lines.LoadFromFile(OpenLogDialog.Filename);
            Form2.show;
     end;
end;

procedure TForm1.QuitItemClick(Sender: TObject);
begin
  Close;
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; //Time that the battery will last in hours
   NSeconds:Real;  //Number of seconds between samples
   NMinutes:Integer;  //Number of minutes between samples
   lStrings: TStringList;
   NRecordsPrefix: String;
const
     //IQuiescent=600e-9;//for uP only
     IQuiescent=209e-6;//See design.ods:Power_budget:SleepModeTheoretical
     IWake=10e-3;
     IMeasure=55e-3;
     TWake=3.0/60.0;
begin

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

  if StrToIntDef(DLThreshold.Text,0)>0 then
     NRecordsPrefix:='<'
  else
      NRecordsPrefix:='';


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

    1: //Every x seconds
      begin
        NSeconds:=StrToFloatDef(DLTrigSeconds.Text,1,FPointSeparator);
        if NSeconds > 0 then
          begin
            TMeasure:=5.0/(Min(Max(NSeconds,5.0),1.0));
            IAverage:=IWake+TMeasure*IMeasure;
            TBattery:=BatteryCapacity/(1000*IAverage);
            DLBatteryDurationTime.Text:=Format('%.0fhours, or %.0fdays, or %.1f months',[TBattery,TBattery/24,TBattery/(24*31)]);
            DLBatteryDurationRecords.Text:=Format('%s %d',[NRecordsPrefix,Round(TBattery) * (3600 div Round(NSeconds))]);
            DLBatteryDurationUntil.Text:=FormatDateTime('yy-mm-dd ddd hh:nn',IncHour(Now,Round(TBattery)));
          end
        else
          begin
            DLBatteryDurationTime.Text:='Not applicable';
            DLBatteryDurationRecords.Text:='N/A';
            DLBatteryDurationUntil.Text:='Not applicable';
          end;
      end;

    2..7: //Every x minutes
        begin
          case TriggerComboBox.ItemIndex of
            2:NMinutes:=StrToIntDef(DLTrigMinutes.Text,1);
            3:NMinutes:=5;
            4:NMinutes:=10;
            5:NMinutes:=15;
            6:NMinutes:=30;
            7:NMinutes:=60;
          end;
          if NMinutes > 0 then
            begin
              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)]);
              DLBatteryDurationRecords.Text:=Format('%s %d',[NRecordsPrefix, (Round(TBattery) * 60) div NMinutes]);
              DLBatteryDurationUntil.Text:=FormatDateTime('yyyy-mm-dd ddd hh:nn',IncHour(Now,Round(TBattery)));
            end
          else
          begin
            DLBatteryDurationTime.Text:='Not applicable';
            DLBatteryDurationRecords.Text:='N/A';
            DLBatteryDurationUntil.Text:='Not applicable';
          end;
        end;
  end;
  if Assigned(lStrings) then FreeAndNil(lStrings);
end;

procedure TForm1.DLBatteryCapacityComboBoxChange(Sender: TObject);
begin
  StatusMessage('Button pressed: Data Logging: Battery capacity');
  EstimateBatteryLife;
end;

procedure TForm1.DLEraseAllButtonClick(Sender: TObject);
begin
  StatusMessage('Button pressed: Data Logging: Erase all');
  dlerase.FormDLErase.ShowModal; //*** crashes in Laz 4.0-1
  //dlerase.FormDLErase.Show;
  DLGetSettings();
  LogUpdateLogPointer();
  DLCurrentRecord:=DLEStoredRecords;
  LogRecordGet(DLCurrentRecord);
end;

procedure TForm1.DLLogOneButtonClick(Sender: TObject);
var
  pieces: TStringList;
  DLOldRecord: LongInt;
begin
     StatusMessage('Button pressed: Data Logging: Log one');
     LogUpdateLogPointer();
     DLOldRecord:=DLEStoredRecords;


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

      //Check if special data is returned:
      // - A record number is usually returned for instant recordings
      // - From firmware>=63, if snow data is being recorded, then the record is not ready and the record number is -1
      //Log one record
      pieces.DelimitedText:=SendGet('L3x');
      if pieces.Count>0 then begin
         if (StrToInt(pieces[1])=-1) then begin //"No record ready yet to view, press >| in a few seconds"
           LogRecordResult.Clear;
           LogRecordResult.Append('No record ready yet to view,');
           LogRecordResult.Lines.Append('  press >| in a few seconds.');

         end else begin
           //Get most recent record
           LogUpdateLogPointer();
           DLCurrentRecord:=DLEStoredRecords;
           LogRecordGet(DLCurrentRecord);
           {check if no record was logged, then issue warning}
           if DLOldRecord=DLCurrentRecord then begin
             MessageDlg('No record logged','No record was logged.' + sLineBreak +
             'Check that the threshold is lower than the actual reading, or set the threshold to 0.',
             mtConfirmation,[mbOK],0);           end;
         end;
      end;

      if Assigned(pieces) then FreeAndNil(pieces);

end;

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

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

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

procedure TForm1.AboutItemClick(Sender: TObject);
begin
     About.Form4.ShowModal; //laz 4.0 showmodal crashes
     //About.Form4.Show;
     //ShowMessage(
                 //'Serial Library verion: ' +ser.GetVersion + sLineBreak +
                 //'' );

end;

procedure TForm1.AccRefreshButtonClick(Sender: TObject);
begin
     A1Check('A1x');
     A2Check('A2x');
     A3Check('A3x');
     A4Check('A4x');
end;

{Write line to terminal for debugging.}
procedure WriteLnDebug(TextLine: String);
begin
{$ifdef Windows}
 {Do nothing since windows cannot handle writeln to a terminal.}
{$endif}

{$if defined(Linux) or defined(Darwin)}
 Writeln(TextLine);
{$ifend}
end;


{Snow LED Status}
procedure TForm1.SnowLEDStatus(Status:String);
var
  pieces: TStringList;
begin
pieces := TStringList.Create;
pieces.Delimiter := ',';
pieces.DelimitedText:=Status;

  case pieces.Strings[1] of
    '1': Begin
        AccSnowLEDState:=True;
        ACCSnowLEDStatus.Brush.Color:=clLime;
    end;
    '0': Begin
        AccSnowLEDState:=False;
        ACCSnowLEDStatus.Brush.Color:=clBlack;
    end;
    else Begin
        AccSnowLEDState:=False;
        ACCSnowLEDStatus.Brush.Color:=clGray;
    end;
  end;
  case pieces.Strings[2] of
    'e': SnowLoggingEnabled:=True;
    'd': SnowLoggingEnabled:=False;
  end;
  SnowLoggingEnableBox.Checked:=SnowLoggingEnabled;
end;


{Snow LED ON}
procedure TForm1.AccSnowLEDOnButtonClick(Sender: TObject);
begin
     SnowLEDStatus(SendGet('A51x'));
end;

procedure TForm1.AccSnowLinRqClick(Sender: TObject);
var
  pieces: TStringList;
begin
  pieces := TStringList.Create;
  pieces.Delimiter := ',';
  pieces.DelimitedText:=SendGet('rfx');
  if pieces.Count>0 then begin
    if AccSnowLEDState then begin
      AccSnowLEDOnReading:=StrToInt64Def(pieces[1],0);
      AccSnowOnLinRdg.Text:=IntToStr(AccSnowLEDOnReading);
    end
    else begin
      AccSnowLEDOffReading:=StrToInt64Def(pieces[1],0);
      AccSnowOffLinRdg.Text:=IntToStr(AccSnowLEDOffReading);
    end;
  end;

  AccSnowLEDDifference:=AccSnowLEDOnReading-AccSnowLEDOffReading;
  AccSnowLinDiff.Text:=IntToStr(AccSnowLEDDifference);

  if Assigned(pieces) then FreeAndNil(pieces);

end;

{Snow LED OFF}
procedure TForm1.AccSnowLEDOffButtonClick(Sender: TObject);
begin
     SnowLEDStatus(SendGet('A50x'));
end;

{Number of fresh readings to discard after the snow LED turns on, so the running
 average settles before the snow reading is captured during staged logging.}
procedure TForm1.SnowReadSkipEditChange(Sender: TObject);
begin
  SnowReadSkip:= LimitInteger(StrToIntDef(Trim(SnowReadSkipEdit.Text), 0), 0, 100);
  vConfigurations.WriteString('LogContinuousSettings', 'SnowReadSkip', IntToStr(SnowReadSkip));
end;

procedure TForm1.ADISEnableChange(Sender: TObject);
var
  command:string;
begin
  if ADISEnable.Checked then command:='E' else command:='D';//Enable/Disable
  A2Check('A2' + command + 'x');
end;

//Change Fixed brightness value after keyboard change.
procedure TForm1.ADISFixedBrightnessKeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
A2Check('A2V' + IntToStr(ADISFixedBrightness.Position) + 'x');
end;

//Change Fixed brightness value after mouse change.
procedure TForm1.ADISFixedBrightnessMouseUp(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
A2Check('A2V' + IntToStr(ADISFixedBrightness.Position) + 'x');
end;

procedure TForm1.ADISFixedClick(Sender: TObject);
begin
     A2Check('A2Fx');
end;

procedure TForm1.ADISModeClick(Sender: TObject);
begin
  if ADISMode.ItemIndex=0 then A2Check('A2Px');
  if ADISMode.ItemIndex=1 then A2Check('A2Rx');
end;

procedure TForm1.ADISModelSelectChange(Sender: TObject);
begin
     A2Check('A2M'+IntToStr(ADISModelSelect.ItemIndex)+'x');
end;

procedure TForm1.ADISAutoClick(Sender: TObject);
begin
     A2Check('A2Ax');
end;

procedure TForm1.AHTEnableChange(Sender: TObject);
var
  command:string;
begin
  if AHTEnable.Checked then command:='E' else command:='D';//Enable/Disable
  A1Check('A1' + command + 'x');
end;

procedure TForm1.AHTModelSelectChange(Sender: TObject);
begin
     A1Check('A1M'+IntToStr(AHTModelSelect.ItemIndex)+'x');
end;

//All accessory commands return the complete status which is parsed below
procedure TForm1.A1Check(command:string);//Check Accessory 1 (Humidity/temperature sensor) options.
var
  pieces: TStringList;
  Status: string;
  Humidity,Temperature:Float;
begin
  pieces := TStringList.Create;
  pieces.Delimiter := ',';
  pieces.DelimitedText:=SendGet(command);
  if pieces.Count=7 then begin
    if pieces.Strings[2]='E' then begin //Enabled.
      AHTEnable.Checked:=True;
      AHTRefreshButton.Enabled:=True;
      AHTHumidityValue.Enabled:=True;
      AHTTemperatureValue.Enabled:=True;
      AHTHumidityStatus.Enabled:=True;
      case pieces.Strings[3] of //model
        '0': AHTModelSelect.ItemIndex:=0;
        '1': AHTModelSelect.ItemIndex:=1;
        '7': AHTModelSelect.Text:='';
      end;
      case pieces.Strings[4] of
        '0': Status:='Normal';
        '1': Status:='Stale';
        '2': Status:='Command';
        '3': Status:='N/A';
      end;

      //Check if Humidity sensor is reporting anything valid
      if ((pieces.Strings[5]='16383') and (pieces.Strings[6]='16383')) then begin
        Status:='N/C'; //No connection
        AHTHumidityStatus.Text:=Status;
        AHTHumidityValue.Text:='';
        AHTTemperatureValue.Text:='';
      end else begin
        AHTHumidityStatus.Text:=Status;
        Humidity:=StrToFloatDef(pieces.Strings[5],0)/( power(2,14) - 2)*100;
        AHTHumidityValue.Text:=Format('%2.1f%%',[Humidity]);
        Temperature:=StrToFloatDef(pieces.Strings[6],0)/( power(2,14)-2) * 165 - 40;
        AHTTemperatureValue.Text:=Format('%2.1f°C',[Temperature]);
        end
      end else begin //Disabled.
        AHTEnable.Checked:=False;
        AHTRefreshButton.Enabled:=False;
        AHTHumidityValue.Enabled:=False;
        AHTTemperatureValue.Enabled:=False;
        AHTHumidityStatus.Enabled:=False;
        AHTHumidityStatus.Text:='';
        AHTHumidityValue.Text:='';
        AHTTemperatureValue.Text:='';
    end;
  end else begin
    AHTHumidityStatus.Text:='';
    AHTHumidityValue.Text:='';
    AHTTemperatureValue.Text:='';
  end;

  //Set global variable indicating that this accessory is enabled
  A1Enabled:=AHTEnable.Checked;

end;
//All accessory commands return the complete status which is parsed below
procedure TForm1.A2Check(command:string);//Check Accessory 2 (Display) options.
var
  pieces: TStringList;
begin
  pieces := TStringList.Create;
  pieces.Delimiter := ',';
  pieces.DelimitedText:=SendGet(command);
  if pieces.Count=7 then begin

    if pieces.Strings[2]='E' then begin //Enabled.
      ADISEnable.Checked:=True;
      ADISBrightnessGroup.Enabled:=True;
      end else begin //Disabled.
      ADISEnable.Checked:=False;
      ADISBrightnessGroup.Enabled:=False;
    end;

    case pieces.Strings[3] of //model
      '0': ADISModelSelect.ItemIndex:=0;
    end;

    if pieces.Strings[4]='A' then
      ADISAuto.Checked:=True //Auto mode
      else
      ADISFixed.Checked:=True;//Fixed mode

    ADISFixedBrightness.Position:=StrToIntDef(pieces.Strings[5],0);

    if pieces.Strings[6]='P' then
      ADISMode.ItemIndex:=0 //periodic update mode
      else
      ADISMode.ItemIndex:=1;//reading request mode

  end;
end;

procedure TForm1.AHTRefreshButtonClick(Sender: TObject);
begin
  A1Check('A1x');
end;

procedure TForm1.ALEDEnableChange(Sender: TObject);
var
  command:string;
begin
  command:='A3';
  if ALEDEnable.Checked then command:=command+'E' else command:=command+'D';//LED accessory Enable/Disable
  A3Check(command + 'x');
end;

procedure TForm1.ALEDModeClick(Sender: TObject); //Change the LED blink mode of operation.
begin
  A3Check('A3' +IntToStr(ALEDMode.ItemIndex)+ 'x');
end;

procedure TForm1.ARLYModeComboBoxChange(Sender: TObject);
begin
     A4Check('A4M' + IntToStr(ARLYModeComboBox.ItemIndex) + 'x');
end;

procedure TForm1.ARLYOffClick(Sender: TObject);
begin
     A4Check('A40x');
end;

procedure TForm1.ARLYOnClick(Sender: TObject);
begin
     A4Check('A41x');
end;

procedure TForm1.ARLYThresholdKeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
     A4Check('A4T' + format('%.2d',[ARLYThreshold.Position]) + 'x');
end;

procedure TForm1.ARLYThresholdMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  A4Check('A4T' + format('%.2d',[ARLYThreshold.Position]) + 'x');
end;

procedure TForm1.ARPTelnetButtonClick(Sender: TObject);
begin
  //Open ARPMethodMenuItem window
   ARPMethod.Formarpmethod.Show;
end;

procedure TForm1.CloudRemovalMilkyWayClick(Sender: TObject);
begin
  CloudRemUnit.CloudRemMilkyWay.Show;
end;

procedure TForm1.CommNotebookChanging(Sender: TObject; var AllowChange: Boolean
  );
var
  CommPageIndex: Integer;
begin

  //CommPageIndex:=CommNotebook.PageIndex;
  //StatusMessage(format('commpage changing to: %d.',[CommPageIndex]));
  //Application.ProcessMessages;
  //
  //DataNoteBook.Enabled:=False;
  //
  //{Check if showing RS232, then disble selecting found devices}
  //case CommPageIndex of
  //2: FoundDevices.Enabled:=False;
  //else FoundDevices.Enabled:=True;
  //end;

end;

procedure TForm1.FilterSunMoonClick(Sender: TObject);
begin
  FilterSunMoonUnit.FilterSunMoonForm.Show;
end;

procedure TForm1.LabelTextButtonClick(Sender: TObject);
var
  MyProcess: TProcess;
  OutputList: TStringList;
  Lines: TStringList;
  i: Integer;
begin
  Lines := TStringList.Create;
  try
    // %9s right-justifies each label to 9 chars (= length of the longest,
    // "Unit S/N:" / "Capacity:"), so the colons line up in a monospaced font.
    Lines.Add(Format('%10s %s', ['Unit S/N:', SelectedUnitSerialNumber]));
    Lines.Add(Format('%10s %s', ['Model:',    SelectedModelDescription]));

    if (CommNotebook.PageIndex = 1) then                 // Ethernet device
      Lines.Add(Format('%10s %s', ['MAC:', EthernetMAC.Text]));

    if SelectedModel in [6, 7, 9, 11, 13] then           // data-logging device
      Lines.Add(Format('%10s %d rec', ['Capacity:', DLEStorageCapacity]));

    if (CommNotebook.PageIndex = 0) and (SelectedModel <> 5) then  // USB, not RS232
      Lines.Add(Format('%10s %s', ['USB S/N:', USBSerialNumber.Text]));

    MyProcess := TProcess.Create(nil);
    try
      MyProcess.Options := MyProcess.Options + [poUsePipes];
      MyProcess.Executable := 'ptouch-print';            // no bash, no shell quoting
      MyProcess.Parameters.Add('--fontsize');
      MyProcess.Parameters.Add('18');
      MyProcess.Parameters.Add('--font');
      MyProcess.Parameters.Add('Liberation Mono');        // space is fine, it's one argv
      MyProcess.Parameters.Add('--cutmark');
      MyProcess.Parameters.Add('--text');
      for i := 0 to Lines.Count - 1 do                    // each line = one arg after --text
        MyProcess.Parameters.Add(Lines[i]);

      StatusMessage('Label lines:' + LineEnding + Lines.Text);
      MyProcess.Execute;

      OutputList := TStringList.Create;
      try
        OutputList.LoadFromStream(MyProcess.Output);
        StatusMessage('OutputList.Text=' + OutputList.Text);
      finally
        OutputList.Free;
      end;
    finally
      MyProcess.Free;
    end;
  finally
    Lines.Free;
  end;
end;

{// Print text label lines to console for Linux
procedure TForm1.LabelTextButtonClick(Sender: TObject);
var
  PrintString : String;
  MyProcess: TProcess;
  OutputList: TStringList;
begin
  PrintString:='';

//All devices:
  PrintString:=PrintString+(''' Unit S/N: '+ SelectedUnitSerialNumber+'''');
  PrintString:=PrintString+(' ''   Model: '+ SelectedModelDescription+'''');
  //check if Ethernet device:
  if (Form1.CommNotebook.PageIndex=1) then
     PrintString:=PrintString+(' ''     MAC: '+ Form1.EthernetMAC.text+'''');
  //check if DataLoging device:
  if ((SelectedModel=6) or (SelectedModel=7) or (SelectedModel=9) or (SelectedModel=11)or (SelectedModel=13)) then begin
     //DLEGetCapacity();
     PrintString:=PrintString+(Format(' '' Capacity: %d rec''',[DLEStorageCapacity]));
  end;
  //check if USB device:
  if ((Form1.CommNotebook.PageIndex=0) and not (SelectedModel=5)) then    // USB device but not RS232 model
     PrintString:=PrintString+(' '' USB S/N: '+ Form1.USBSerialNumber.text+'''');

  MyProcess := TProcess.Create(nil);
  try
    // Enable capturing output to a stream
    MyProcess.Options := MyProcess.Options + [poUsePipes];

    // Set the executable and its parameters
    MyProcess.Executable := '/bin/bash';
    // Use bash -c to run an arbitrary command string
    MyProcess.Parameters.Add('-c');
    //MyProcess.Parameters.Add('echo "Hello from Lazarus" && whoami');
    MyProcess.Parameters.Add('ptouch-print --fontsize 18 --font "Liberation Mono" --cutmark --text '+PrintString);
    StatusMessage('Label PrintString='+PrintString);

    // Execute the command
    MyProcess.Execute;

    // Load the output from the process's output stream
    OutputList := TStringList.Create;
    try
      OutputList.LoadFromStream(MyProcess.Output);
      StatusMessage('OutputList.Text='+OutputList.Text); // Print output to the console/memo
    finally
      OutputList.Free;
    end;

  finally
    // Ensure the process object is freed
    MyProcess.Free;
  end;

end;}

procedure TForm1.ConcatenateMenuClick(Sender: TObject);
begin
     concattool.ConcatToolForm.Show;
end;

procedure TForm1.LockCommCheckBoxChange(Sender: TObject);
begin
  LockComm:=LockCommCheckBox.Checked;
  {Save setting}
  vConfigurations.WriteBool('CommSettings','LockComm',LockComm);

end;

procedure TForm1.LogSettingsButtonClick(Sender: TObject);
var
  Reply: Integer;
begin
  StatusMessage('Button pressed: Information: Settings');
  if SelectedModel=0 then
    GetVersion;

  if ((Length(DelSpace(SelectedTZRegion))>0) and (Length(DelSpace(SelectedTZLocation))>0)) then begin
      {Start Modal window with status of: timed countdown, number of records stored, Stop logging button}
      {Had to switch from showmodal to show because automatic minimize would not work properly.}
      Form1.Enabled:=False;  {Make-believe showmodal}
      FormLogCont.Show;
    end
  else begin
       //'Do this by pressing the Header button, then selecting your timezone.');
        Reply:=Application.MessageBox('The time zone information must be defined, do you want to do it now?', 'Time zone missing', MB_ICONQUESTION + MB_YESNO );
        if (Reply = IDYES) then DLHeaderForm.ShowModal;
      end;
end;

procedure TForm1.datReplaceDatesMenuItemClick(Sender: TObject);
begin
  datreplacedates.Form9.Show;

end;

procedure TForm1.OnlineDLmanualClick(Sender: TObject);
begin
  OpenURL('http://unihedron.com/projects/darksky/cd/SQM-LU-DL/SQM-LU-DL_Users_manual.pdf');
end;

procedure TForm1.OnlineLEmanualClick(Sender: TObject);
begin
  OpenURL('http://unihedron.com/projects/darksky/cd/SQM-LE/SQM-LE_Users_manual.pdf');
end;

procedure TForm1.OnlineLRmanualClick(Sender: TObject);
begin
  OpenURL('http://unihedron.com/projects/darksky/cd/SQM-LR/SQM-LR_Users_manual.pdf');
end;

procedure TForm1.OnlineLUmanualClick(Sender: TObject);
begin
     OpenURL('http://unihedron.com/projects/darksky/cd/SQM-LU/SQM-LU_Users_manual.pdf');
end;

procedure TForm1.OnlineResourcesClick(Sender: TObject);
begin
  OpenURL('http://unihedron.com/projects/darksky/cd/');
end;

procedure TForm1.OnlineVmanualClick(Sender: TObject);
begin
  OpenURL('http://unihedron.com/projects/darksky/cd/SQM-LU-DL-V/SQM-LU-DL-V_Users_manual.pdf');
end;

procedure TForm1.RptIntButtonClick(Sender: TObject);
begin
  StatusMessage('Button pressed: Information: Rpt. Int.');
  DataNoteBook.PageIndex:=2;
end;

procedure TForm1.RS232PortChange(Sender: TObject);
begin
       EnableFirmware();
end;

procedure TForm1.SelectFirmwareButtonClick(Sender: TObject);
var
  File1: TextFile;
  Str: String;
  i:Integer; //counter
begin
  OpenLogDialog.InitialDir:= appsettings.FirmwareDirectory;
  OpenLogDialog.Filter:=FirmwareFilter;
  OpenLogDialog.FilterIndex:=FirmwareFilterIndex;
  if OpenLogDialog.Execute then begin
    FirmwareFilename:=OpenLogDialog.Filename;
    FirmwareFile.Text:=FirmwareFilename;
    if fileexists(FirmwareFilename) then begin
      AssignFile(File1,FirmwareFilename);
    {$I-}//Temporarily turn off IO checking
    try
      Reset(File1);

      StatusMessage('Found and reset Firmwarefile');
      Application.ProcessMessages;

     // Determine the size of the progress bar.
     i:=0;
     repeat
       Readln(File1, Str); // Read a whole line from the file.
       Inc(i);
     until(EOF(File1)); // EOF(End Of File) keep reading new lines until end.
     LoadFirmwareProgressBar.Max:=i;

     StatusMessage('Firmware file size determined for progress bar.');
     Application.ProcessMessages;

    finally
    end;

    end;

  end;
end;

procedure TForm1.ColourCyclingRadioClick(Sender: TObject);
var
  ColourCycleResult: String;
  ColourCycleCommand: String;
begin
  if not ColourUpdating then begin

    {Get desired colour cycling setting}
    case ColourCyclingRadio.ItemIndex of
          1: begin// Cycling
              ColourRadio.Visible:=False;
              ColourCyclingFlag:=True;
              ColourCycleCommand:='fCx';
          end;
       else begin// Assume Fixed
              ColourRadio.Visible:=True;
              ColourCyclingFlag:=False;
              ColourCycleCommand:='fFx';
       end;
    end;

    {Send request to change colour cycling mode}
    ColourCycleResult:=SendGet(ColourCycleCommand);

    GetReading(); //Update reading (with colour settings shown).
  end;

end;

{Communication busy timer
  Ticks at 100ms, rest of logic takes place below.
  Set for a slightly smaller time than the SQM-LE auto-disconnected (which is defaulted at 2s).
  This allows for multiple requests to keep the port open,
   and will close the port for others to use if UDM is idle.
}
procedure TForm1.CommBusyTimer(Sender: TObject);
begin
  if not LockComm then begin
    Inc(CommBusyTime);
    if CommBusyTime>=CommBusyLimit then begin
      CloseComm();
    end;
  end;
end;


procedure TForm1.CommNotebookChange(Sender: TObject);
var
  CommPageIndex: Integer;
begin

  CommPageIndex:=CommNotebook.PageIndex;
  Application.ProcessMessages;

  DataNoteBook.Enabled:=False;

  {Check if showing RS232, then disble selecting found devices}
  case CommPageIndex of
  2: FoundDevices.Enabled:=False;
  else FoundDevices.Enabled:=True;
  end;

end;

procedure TForm1.Correction49to56MenuItemClick(Sender: TObject);
begin
  correct49to56.CorrectForm.Show;
end;

procedure TForm1.datToDecimalDateClick(Sender: TObject);
begin
  date2dec.Form10.Show;
end;

procedure TForm1.DLMutualAccessGroupClick(Sender: TObject);
var
  pieces:TStringList;

begin

 pieces := TStringList.Create;
 pieces.Delimiter := ',';
 pieces.StrictDelimiter := True; //Do not parse spaces

//Set mutual access logging setting
case DLMutualAccessGroup.ItemIndex of
      0:pieces.DelimitedText:=sendget('LD1x');//Battery only logging
      1:pieces.DelimitedText:=sendget('LD0x');//Battery and PC logging
end;

//Read mutual access logging setting
if (StrToInt(SelectedFeature)>=68) then begin;
    pieces.DelimitedText:=sendget('Ldx');
    if pieces.Count=2 then begin
      DLLogOnBatt:=pieces.Strings[1]='1';
    end
    else
      DLLogOnBatt:=False;

    case DLLogOnBatt of
       True:DLMutualAccessGroup.ItemIndex:=0;
       False:DLMutualAccessGroup.ItemIndex:=1;
    end;
end;

end;

procedure TForm1.ColourRadioClick(Sender: TObject);
begin
  if not ColourUpdating then begin
    SelectedColour:=ColourRadio.ItemIndex; //Get desired colour status
    SelectedColourScaling:=StrToIntDef(ansimidstr(SendGet('fx'),3,1),0);//Get current scaling status

    //Modify status as selected
    SendGet('f'+IntToStr(SelectedColourScaling)+IntToStr(SelectedColour)+'x');

    //ParseColourScaling(); //Update the displayed settings.

    { Clear reading box since color selection has changed. }
    ReadingListBox.Items.Clear;

  end;
end;

procedure TForm1.ColourScalingRadioClick(Sender: TObject);
begin
  if not ColourUpdating then begin
    SelectedColourScaling:=ColourScalingRadio.ItemIndex;//Get desired scaling
    SelectedColour:=StrToIntDef(ansimidstr(SendGet('fx'),5,1),0);//Get current colour status

    //Modify status as selected
    SendGet('f'+IntToStr(SelectedColourScaling)+IntToStr(SelectedColour)+'x');

    //ParseColourScaling(); //Update the displayed settings.

    GetReading(); //Update reading (with colour settings shown).
  end;
end;

procedure TForm1.ParseColourScaling();
var
  result:String;
begin
  result:=SendGet('fx');//Get current colour control settings.
  SelectedColourScaling:=StrToIntDef(ansimidstr(result,3,1),0);//Update current scaling status.
  ColourScalingRadio.ItemIndex:=SelectedColourScaling;
  SelectedColour:=StrToIntDef(ansimidstr(result,5,1),0);//Update current colour status.
  ColourRadio.ItemIndex:=SelectedColour;
end;

//Accessory command returns the complete status which is parsed below
procedure TForm1.A4Check(command:string);//Check Accessory 4 (Relay) options.
var
  pieces: TStringList;
begin

pieces := TStringList.Create;
pieces.Delimiter := ',';
pieces.DelimitedText:=SendGet(command);
if pieces.Count=8 then begin

  //Status
  if pieces.Strings[2]='1' then
     ARLYStatusLabeledEdit.Text:='On'
  else
      ARLYStatusLabeledEdit.Text:='Off';

  //Mode
  ARLYModeComboBox.ItemIndex:=StrToInt(pieces.Strings[3]);

  //Light threshold
  ARLYThreshold.Position:= StrToIntDef(pieces.Strings[4],0);

  //Temperature
  ARLYTValue.Text:=format('%d',[StrToIntDef(pieces.Strings[5],0)]);
  //Humidity
  ARLYHValue.Text:=format('%d',[StrToIntDef(pieces.Strings[6],0)]);
  //Dewpoint temperature (Tdp)
  ARLYTDPValue.Text:=format('%d',[StrToIntDef(pieces.Strings[7],0)]);

end;
    ARLYThresholdValue.Caption:=IntToStr(ARLYThreshold.Position);

end;

//Accessory command returns the complete status which is parsed below
procedure TForm1.A3Check(command:string);//Check Accessory 3 (LED blink) options.
var
  pieces: TStringList;
begin

pieces := TStringList.Create;
pieces.Delimiter := ',';
pieces.DelimitedText:=SendGet(command);
if pieces.Count=5 then begin

  if pieces.Strings[2]='E' then begin //Enabled.
    ALEDEnable.Checked:=True;
    end else begin //Disabled.
    ALEDEnable.Checked:=False;
  end;

  //Blink Mode; 0=at reading creation,1=at reading request
  ALEDMode.ItemIndex:= StrToIntDef(pieces.Strings[4],0);
end;

end;

procedure TForm1.ContCheckGroupItemClick(Sender: TObject; Index: integer);
var
  command:string;
begin
//Create setting command
 command:='Y';
 if Index=0 then if ContCheckGroup.Checked[Index] then command:=command+'R' else command:=command+'r';//Report enabled
 if Index=1 then if ContCheckGroup.Checked[Index] then command:=command+'P' else command:=command+'p';//Report compressed
 if Index=2 then if ContCheckGroup.Checked[Index] then command:=command+'U' else command:=command+'u';//Report un-averaged
 if Index=3 then if ContCheckGroup.Checked[Index] then command:=command+'L' else command:=command+'l';//LED blink
 if Index=4 then if ContCheckGroup.Checked[Index] then command:=command+'C' else command:=command+'c';//Ideal crossover
 command:=command + 'x';

ContCheck(command);//Send command and populate checkbox
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  if ViewedLogCreated then ViewedLog.Destroy;
  if serCreated then ser.Destroy;
  if GPSserCreated then GPSser.Destroy;
  if GoToserCreated then GoToser.Destroy;
  if ParameterValueCreated then ParameterValue.Destroy;
  Close;
end;

{Double clicking messes up, especially on the Mac where other devices are
selected than being used, probably shouyld lock out single clicking to get
this to work, but it's really not that great of a feature for the hassle.}
//procedure TForm1.FoundDevicesDblClick(Sender: TObject);
//begin
//     SelectDevice;
//     //GetReading;
//     RequestButtonClick(nil);
//end;

//Lens Holder information
procedure TForm1.LHComboChange(Sender: TObject);
begin
  //if not LHFUpdating then begin
  //  CheckLockSwitch();
    SelectedLH:=LHCombo.ItemIndex;
    LHFCheck(Format('M0%03.03Dx',[SelectedLH]));
  //end;
end;

//Lens information
procedure TForm1.LensComboChange(Sender: TObject);
begin
  //if not LHFUpdating then begin
  //  CheckLockSwitch();
    SelectedLens:=LensCombo.ItemIndex;
    LHFCheck(Format('M1%03.03Dx',[SelectedLens]));
  //end;
end;

//Filter information
procedure TForm1.FilterComboChange(Sender: TObject);
begin
  //if not LHFUpdating then begin
  //  CheckLockSwitch();
    SelectedFilter:=FilterCombo.ItemIndex;
    LHFCheck(Format('M2%03.03Dx',[SelectedFilter]));
  //end;
end;

{ Lens/Holder/Filter check}
procedure LHFCheck(command:string);
begin
     //SendGet('rx'); //Flush unwanted report interval info
     LHFUpdating:=True;

     if command<>'' then begin
       SendGet(command); //Set value
     end;

     try //Sometimes these commands do not reply on time
       //Update comboboxes
       //Results sent as m_x, received as m_,123
       SelectedLH:=StrToIntDef(AnsiMidStr(SendGet('m0x'),4,3),-1);
       SelectedLens:=StrToIntDef(AnsiMidStr(SendGet('m1x'),4,3),-1);
       SelectedFilter:=StrToIntDef(AnsiMidStr(SendGet('m2x'),4,3),-1);

       Form1.LHCombo.ItemIndex:=SelectedLH;
       Form1.LensCombo.ItemIndex:=SelectedLens;
       Form1.FilterCombo.ItemIndex:=SelectedFilter;
     except
       StatusMessage('Warning: LHFCheck exception!');
     end;

     LHFUpdating:=False;
end;

{ send the lock settings command and populate the screen }
procedure TForm1.LockSettingsCheck(command:string);
var
  pieces: TStringList;
begin
  SendGet('rx'); //Flush unwanted report interval info
  pieces := TStringList.Create;
  pieces.Delimiter := ',';
  pieces.DelimitedText:=SendGet(command);
  if pieces.Count=2 then begin
    if AnsiContainsStr(pieces.Strings[1],'C') then
      LockSwitchOptions.Checked[0]:=True;
    if AnsiContainsStr(pieces.Strings[1],'c') then
      LockSwitchOptions.Checked[0]:=False;
    if AnsiContainsStr(pieces.Strings[1],'R') then
      LockSwitchOptions.Checked[1]:=True;
    if AnsiContainsStr(pieces.Strings[1],'r') then
      LockSwitchOptions.Checked[1]:=False;
    if AnsiContainsStr(pieces.Strings[1],'G') then
      LockSwitchOptions.Checked[2]:=True;
    if AnsiContainsStr(pieces.Strings[1],'g') then
      LockSwitchOptions.Checked[2]:=False;
    if AnsiContainsStr(pieces.Strings[1],'T') then
      LockSwitchOptions.Checked[3]:=True;
    if AnsiContainsStr(pieces.Strings[1],'t') then
      LockSwitchOptions.Checked[3]:=False;

  end else
      StatusMessage('Invalid lock setting response, pieces = '+IntToStr(pieces.Count));

  //Update lock indicator
  CheckLockSwitch();
end;

//Lock switch protect settings
procedure TForm1.LockSwitchOptionsItemClick(Sender: TObject; Index: integer);
begin
  StatusMessage('Lock switch protection settings being altered');
case Index of
  0: begin //Calibration settings
     if LockSwitchOptions.Checked[Index] then
        LockSettingsCheck('KCx')
     else
         LockSettingsCheck('Kcx');
  end;

  1: begin //Report Interval settings
     if LockSwitchOptions.Checked[Index] then
        LockSettingsCheck('KRx')
     else
         LockSettingsCheck('Krx');
  end;

  2: begin  //Configuration settings
     if LockSwitchOptions.Checked[Index] then
        LockSettingsCheck('KGx')
     else
         LockSettingsCheck('Kgx');
  end;

  3: begin //These settings
    If LockSwitchOptions.Checked[Index] then begin //Tried to turn ON "Respect lock for These settings"
       if CheckLockSwitch() then begin //Unit locked
       case QuestionDlg ('Are you sure?','Checking this will instantly prevent other changes!',mtWarning,[mrYes,'OK', mrNo, 'Cancel', 'IsDefault'],'') of
           mrYes: begin
                       LockSettingsCheck('KTx');
                       StatusMessage('Locked out by user. Lock switch protects "These settings".');
                  end;
           else begin
             StatusMessage('Lock switch protects "These settings" cancelled.');
             LockSettingsCheck('Kx');;
           end;
       end;
    end else begin //Unit unlocked
      case QuestionDlg ('Are you sure?','Checking this will prevent other changes when the lock switch is locked!',mtWarning,[mrYes,'OK', mrNo, 'Cancel', 'IsDefault'],'') of
          mrYes: begin
                      LockSettingsCheck('KTx');
                      StatusMessage('Lock switch protects "These settings".');
                 end;
          else begin
            StatusMessage('Lock switch protects "These settings" cancelled.');
            LockSettingsCheck('Kx');;
          end;
      end;
    end;
    end else begin //Tried to turn OFF "Respect lock for These settings"
        if CheckLockSwitch() then begin  //Unit locked
           MessageDlg('Unit locked','The unit is locked.' + sLineBreak +
           'Switch the Lock switch to the Unlock position' + sLineBreak +
           'If you still have problems, then contact Unihedron for possible solutions.',
           mtConfirmation,[mbOK],0);
           LockSettingsCheck('Kx');
        end
        else begin  //Unit unlocked
            LockSettingsCheck('Ktx');
        end;
    end;


  end;

end;


end;

procedure TForm1.DatTimeCorrectionClick(Sender: TObject);
begin
  dattimecorrect.dattimecorrectform.Show;
end;

procedure TForm1.DatReconstructLocalTimeClick(Sender: TObject);
begin
   datlocalcorrect.datlocalcorrectform.Show;
end;

procedure TForm1.AverageToolMenuItemClick(Sender: TObject);
begin
  avgtool.Form8.Show;
end;

procedure TForm1.mnDATtoKMLClick(Sender: TObject);
begin
     dattokml.Form7.ShowModal;
end;

procedure TForm1.OpenDLRMenuItemClick(Sender: TObject);
begin
     LogUpdateLogPointer();
     dlretrieve.VectorPlotOverride:=true;
     dlretrieve.DLRetrieveForm.ShowModal;
end;

procedure TForm1.PlotterMenuItemClick(Sender: TObject);
begin
  plotter.PlotterForm.Show;
end;

procedure TForm1.RS232BaudChange(Sender: TObject);
begin
  RS232PortBaud:=StrToIntDef(RS232Baud.Text,115200);
end;

procedure TForm1.RS232PortEditingDone(Sender: TObject);
begin
  SelectedInterface:='RS232';
  RS232Portname:=RS232Port.Text;
  DataNoteBook.Enabled:=True;
  FoundDevices.ItemIndex:=-1;
  Application.ProcessMessages;
  SelectDevice;
end;

//Grab simin.csv from log directory
// and feed that through simulator
// producing simout.csv
procedure TForm1.SimFromFileClick(Sender: TObject);

var
  Period:Int64; //nS
  Frequency:Int64; //Hz
  Temperature:Integer; //ADC
  tempreal:real;
  InFileName, OutFileName: String;
  InFile, OutFile: TextFile;
  Str, ResultStr: string;
  pieces: TStringList;
  Info:     TVersionInfo;

  //Show and save reply
  procedure writeresult(output:string);
  begin
    SimResults.Lines.Add(output);
    Writeln(OutFile,output);
  end;


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

  SimEnable:=True;
  SimResults.Lines.Clear;

  //Check filenames
  InFileName:=appsettings.LogsDirectory+ DirectorySeparator+'simin.csv';
  OutFileName:=appsettings.LogsDirectory+ DirectorySeparator+'simout.csv';
  if (not fileexists(InFileName)) then
    MessageDlg ('Infile does not exist!'+InFileName, mtConfirmation,[mbIgnore],0)
  else begin
    AssignFile(InFile, InFileName);
    Reset(InFile); //Open file for reading
  end;

  if (fileexists(OutFileName)) then
    MessageDlg ('Outfile already exists!', mtConfirmation,[mbIgnore],0);

  AssignFile(OutFile, OutFileName);
  Rewrite(OutFile); //Open file for writing

  writeresult('# Simulation from file.');
    Info := TVersionInfo.Create;
    Info.Load(HINSTANCE);
    Str:=IntToStr(Info.FixedInfo.FileVersion[0])+'.'+
        IntToStr(Info.FixedInfo.FileVersion[1])+'.'+
        IntToStr(Info.FixedInfo.FileVersion[2])+'.'+
        IntToStr(Info.FixedInfo.FileVersion[3]);
    Info.Free;
  writeresult('# UDM version: '+Str);
  writeresult('# Unit information cx: '+sendget('ix'));
  writeresult('# Calibration cx: '+sendget('cx'));

  repeat
    // Read one line at a time from the file.
    Readln(InFile, Str);

    //Separate the fields of the record.
    pieces.DelimitedText := Str;

    //Make sure the number of fields is correct
    if ((pieces.Count <> 3)) then
    begin
      MessageDlg('Error', 'Got '+IntToStr(pieces.Count)+' fields, need 3 fields in record.',
        mtError, [mbOK], 0);
      break;
    end
    else
    begin

     { Period of sensor in counts, counts occur at a rate of 460.8 kHz (14.7456MHz/32).
       Start simulation range and log to a text file or plot on a chart. }

       //Parse the fields and convert as necessary.
       Period:= StrToInt64Def(pieces.Strings[0],0);
       Frequency:=StrToInt64Def(pieces.Strings[1],0);
       tempreal:=((((StrToFloatDef(pieces.Strings[2],0.0) *0.01 +0.5) * 1024)) / 3.3);
       Temperature:=round(tempreal);
       Application.ProcessMessages;

       //Send request
       ResultStr:=sendget(Format('S%10.10d,%10.10d,%05.05dx',[
         Period,
         Frequency,
         Temperature
       ]));

       //Show and save reply
       writeresult(ResultStr);

    end;//End of checking number of fields in record.

  until (EOF(InFile) or not SimEnable);
  CloseFile(InFile);
  CloseFile(OutFile);
end;

procedure TForm1.Button18Click(Sender: TObject);
var
    pieces:TStringList;

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

    pieces.DelimitedText:=SendGet('g0x');
    GPSResponse.Clear;

    try
      GPSResponse.Append(pieces.Strings[0]); //Title (GGA message) need not be displayed
      GPSResponse.Append(pieces.Strings[1]+' hhmmss.sss');
      GPSResponse.Append(pieces.Strings[2]+' ddmm.mmmm '+ pieces.Strings[3]); //Latitude N/S
      GPSResponse.Append(pieces.Strings[4]+' ddmm.mmmm '+ pieces.Strings[5]); //Longitude E/W
      case StrToIntDef(pieces.Strings[6],-1) of
        -1: GPSResponse.Append('error');
        0: GPSResponse.Append('Fix not available or invalid');
        1: GPSResponse.Append('GPS SPS Mode, fix valid');
        2: GPSResponse.Append('Differential GPS, SPS Mode, fix valid');
        3..5: GPSResponse.Append('Not supported');
        6: GPSResponse.Append('Dead Reckoning Mode, fix valid');
      end;
      GPSResponse.Append(pieces.Strings[7]+' Satellites Used');
      GPSResponse.Append('HDOP: '+pieces.Strings[8]+' Horizontal Dilution of Precision');
      GPSResponse.Append('MSL Altitude: '+pieces.Strings[9]+' '+pieces.Strings[10]);
      GPSResponse.Append('Geoid Separation: '+pieces.Strings[11]+' '+pieces.Strings[12]);
      GPSResponse.Append('Age of Diff. Corr.: '+pieces.Strings[13]+' s');
      pieces.Delimiter := '*';
      pieces.DelimitedText:=pieces.Strings[14];
      GPSResponse.Append('Diff. Ref. Station ID: '+pieces.Strings[0]);
    except
        GPSResponse.Append('Failed parsing GGV data.');
    end;


end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  //GPSResponse.Append(SendGet('g1x'));
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
    //GPSResponseEdit.Text:=SendGet('g2x');
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
  //GPSResponseEdit.Text:=SendGet('g4x');
end;

procedure TForm1.Button4Click(Sender: TObject);
begin
    //GPSResponseEdit.Text:=SendGet('g5x');
end;

procedure TForm1.Button5Click(Sender: TObject);
begin
    //GPSResponseEdit.Text:=SendGet('g6x');
end;

procedure TForm1.Button6Click(Sender: TObject);
begin
    //GPSResponseEdit.Text:=SendGet('g8x');
end;

procedure TForm1.Button7Click(Sender: TObject);
begin
    //GPSResponseEdit.Text:=SendGet('gAx');
end;

procedure TForm1.Button8Click(Sender: TObject);
begin
    //GPSResponseEdit.Text:=SendGet('gBx');
end;

procedure TForm1.Button9Click(Sender: TObject);
begin
    //GPSResponseEdit.Text:=SendGet('gCx');
end;

procedure TForm1.CmdLineItemClick(Sender: TObject);
begin
     textfileviewer.fillview('Command Line Options','commandlineoptions.txt');
end;

procedure TForm1.CommTermMenuItemClick(Sender: TObject);
begin
  ComTermForm.ShowModal;
end;

procedure TForm1.ConfDarkCaluxButtonClick(Sender: TObject);
var
  Reply: Integer;
  pieces: TStringList;
begin
  StatusMessage('Button pressed: Configuration: Dark cal UX calibration');
  pieces := TStringList.Create;
  pieces.Delimiter := ',';

  StatusMessage('Unaveraged dark calibration attempted.');
  Reply:=Application.MessageBox(Pchar('Set dark calibration to the un averaged time?' + sLineBreak +
      'Are you sure?' + sLineBreak +
      'Cancel if not sure.'),'Configure Dark Calibration',MB_ICONWARNING + MB_OKCANCEL);
  if Reply=mrOK then begin
     GetConfReading(); //get the unaveraged period along with other unneccessary details

     StatusMessage('Unaveraged dark calibration performed.');
     pieces.DelimitedText := SendGet(Format('zcal7%sx',[FormatFloat('0000000.000',StrToFloatDef(ConfRdgPer.text,0,FPointSeparator))]));
     if ((pieces.Count>=3) and (pieces.Strings[0]='z')) then
        DCPAct.Text:=Format('%.3f',[StrToFloatDef(AnsiReplaceStr(pieces.Strings[2],'s',''),0,FPointSeparator)]);

     GetConfCal;
     end else
       StatusMessage('Unaveraged dark calibration cancelled.');


end;

procedure TForm1.ConvertLogFileItemClick(Sender: TObject);
begin
  //Convert dat to Moon csv file dialog
  convertlogfileunit.convertdialog.Show;
end;

procedure TForm1.DLClockSettingsButtonClick(Sender: TObject);
begin
  StatusMessage('Button pressed: Data Logging: Threshold Device Clock Settings');
  dlclock.Form6.ShowModal;
  DLGetSettings();
end;

procedure TForm1.DLRetrieveButtonClick(Sender: TObject);
begin
  StatusMessage('Button pressed: Data Logging: Retrieve');
  LogUpdateLogPointer();
  dlretrieve.DLRetrieveForm.ShowModal;
end;

procedure TForm1.DLSetSeconds1Click(Sender: TObject);
begin
  StatusMessage('Button pressed: Data Logging: Set Seconds');
  SendGet(Format('LPM%010.10dx',[(StrToIntDef(DLTrigMinutes.Text,0))]));
  EstimateBatteryLife;
  { TODO 2 : check result }
  DLTrigMinutes.Color:=clWindow;
end;

procedure TForm1.DLSetSecondsClick(Sender: TObject);
begin
     SendGet(Format('LPS%010.10dx',[(StrToIntDef(DLTrigSeconds.Text,0))]));
     EstimateBatteryLife;
     { TODO 2 : check result }
     DLTrigSeconds.Color:=clWindow;
end;

procedure TForm1.DLThresholdSetClick(Sender: TObject);
begin
     StatusMessage('Button pressed: Data Logging: Threshold Set');
     SendGet(Format('LT%011.2fx',[(StrToFloatDef(DLThreshold.Text,0,FPointSeparator))]));
     { TODO 2 : check result }
     DLThreshold.Color:=clWindow;
     EstimateBatteryLife;
end;

procedure TForm1.FindBluetoothClick(Sender: TObject);
{$IFDEF LinuxBluetooth}
var
  device_id, device_sock: cint;
  scan_info: array[0..127] of inquiry_info;
  scan_info_ptr: Pinquiry_info;
  found_devices: cint;
  DevName: array[0..255] of Char;
  PDevName: PCChar;
  RemoteName: array[0..255] of Char;
  PRemoteName: PCChar;
  i: Integer;
  timeout1: Integer = 5;
  timeout2: Integer = 5000;
  s: Integer; //socket

  sendstring:array[0..255] of Char;
  Psendstring: PCChar;
  Addr: sockaddr_rc;

  linkkey:Integer;
  {$ENDIF Linux}

begin
{$IFDEF LinuxBluetooth}
  // get the id of the first bluetooth device.
  device_id := hci_get_route(nil);
  if (device_id < 0) then
    raise Exception.Create('FindBlueTooth: hci_get_route')
  else
    writeln('device_id = ',device_id);

  // create a socket to the device
  device_sock := hci_open_dev(device_id);
  if (device_sock < 0) then
    raise Exception.Create('FindBlueTooth: hci_open_dev')
  else
    writeln('device_sock = ',device_sock);

  // scan for bluetooth devices for 'timeout1' seconds
  scan_info_ptr:=@scan_info[0];
  FillByte(scan_info[0],SizeOf(inquiry_info)*128,0);
  found_devices := hci_inquiry_1(device_id, timeout1, 128, nil,
                                 @scan_info_ptr, IREQ_CACHE_FLUSH);

  writeln('found_devices (if any) = ',found_devices);

  if (found_devices < 0) then
    raise Exception.Create('FindBlueTooth: hci_inquiry')
  else
      begin
        PDevName:=@DevName[0];
        ba2str(@scan_info[0].bdaddr, PDevName);
        writeln('Bluetooth Device Address (bdaddr) DevName = ',PChar(PDevName));

        PRemoteName:=@RemoteName[0];
        // Read the remote name for 'timeout2' milliseconds
        if (hci_read_remote_name(device_sock,@scan_info[0].bdaddr,255,PRemoteName,timeout2) < 0) then
          writeln('No remote name found, check timeout.')
        else
          writeln('RemoteName = ',PChar(RemoteName));
      end;
  s := fpsocket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
  writeln('s=',s);

  Addr.rc_bdaddr:=scan_info[0].bdaddr;
  Addr.rc_channel:=1;
  Addr.rc_family:=AF_BLUETOOTH;

  if (fpconnect(s, psockaddr(@Addr), SizeOf(Addr)) < 0) then
     writeln('Error: ',socketerror,' ',StrError(socketerror));

  sendstring:='something string.';
  Psendstring:=@sendstring[0];

  writeln('fpsend = ',fpsend(s,Psendstring,length(sendstring),0));

  FpClose(s);
  FpClose(device_sock);
  {$ENDIF Linux}

end;


function TForm1.ConnectBT: boolean;
{$IFDEF LinuxBluetooth}
var
  Addr: sockaddr_l2;
  {$ENDIF}
begin
{$IFDEF LinuxBluetooth}
  Addr.l2_family:=AF_BLUETOOTH;
  Addr.l2_bdaddr:=bdaddr;

  // OUTPUT CHANNEL
  OutSocket:=fpsocket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
  if (OutSocket = -1) then
    exit(false);
  writeln('OutSocket=',OutSocket);

  {$IFDEF BIG_ENDIAN}
  {$ERROR ToDo BIG_ENDIAN}
  {$ENDIF}
  Addr.l2_psm := WM_OUTPUT_CHANNEL; // htobs

  // connect to wiimote
  writeln('fpconnect=', fpconnect(OutSocket, psockaddr(@addr), SizeOf(addr)));

  // INPUT CHANNEL
  InSocket:=fpsocket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
  if (InSocket = -1) then begin
    CloseSocket(OutSocket);
    OutSocket := -1;
    exit(false);
  end;

  Addr.l2_psm := WM_INPUT_CHANNEL; // htobs

  // connect to wiimote
  if (fpconnect(InSocket, psockaddr(@addr), SizeOf(addr)) < 0) then
  begin
    CloseSocket(OutSocket);
    OutSocket := -1;
    raise Exception.Create('fpconnect input');
  end;

  writeln('Connected to wiimote ');

  // do the handshake
  //Connected:=true;

  //EnableHandshake;
  //RealizeReportType;

  {$ENDIF Linux}
  Result:=true;
end;

procedure TForm1.EthernetIPChange(Sender: TObject);
begin
  SelectedInterface:='Eth';
  bXPortDefaults.Enabled:=True;
  EnableControls(True);
  EnableFirmware();
end;

procedure TForm1.EthernetPortChange(Sender: TObject);
begin
  SelectedInterface:='Eth';
  EnableControls(True);
  EnableFirmware();
end;

procedure TForm1.FindEthMenuItemClick(Sender: TObject);
begin
  //FindEthDevices;
  FindAllDevices('Eth');
end;

procedure TForm1.FindUSBMenuItemClick(Sender: TObject);
begin
  FindAllDevices('USB');
end;

procedure TForm1.FirmwareInfoButtonClick(Sender: TObject);
begin
  textfileviewer.fillview('Version information','fchanges.txt');
end;

procedure TForm1.HeaderButtonClick(Sender: TObject);
begin
  StatusMessage('Button pressed: Information: Header');
  if SelectedModel=0 then
    GetVersion;

  DLHeaderForm.ShowModal;
end;

procedure TForm1.LogCalButtonClick(Sender: TObject);
// Save Calibration information to a text file in the current directory.
var
  OutFileName, MessageString:String;
  CalLogFilename: TextFile;
  AllowFlag: Boolean=False;
  LogfileSuffix: String;
  AccCalPos: Integer; //Accelerometer position
begin
  StatusMessage('Button pressed: Configuration: Log calibration');
  GetVersion;
  GetConfCal;
  Application.ProcessMessages;

  //Determine logfile suffix from Selected model name and interface type.
  case SelectedModel of
    model_LELU: begin
          case CommNotebook.PageIndex of
            0:LogfileSuffix:='LU';
            1:LogfileSuffix:='LE';
          end;
       end;
    model_LR : LogfileSuffix:='LR';
    model_DL : LogfileSuffix:='LU-DL';
    model_GPS: LogfileSuffix:='LU-DL-GPS';
    model_V  : LogfileSuffix:='LU-DL-V';
    model_DLS: LogfileSuffix:='LU-DLS';
    model_RPi: LogfileSuffix:='RPi';
    model_LL: LogfileSuffix:='LL';
  end;

  OutFileName:=RemoveMultiSlash(appsettings.LogsDirectory+ DirectorySeparator+Format('%s-%s.txt',[SelectedUnitSerialNumber,LogfileSuffix]));

  AssignFile(CalLogFilename,OutFileName);

  //Check that file does not already exist
  if fileexists(OutFileName) then begin
    MessageString:=OutFileName+ ' exists!';
    StatusMessage(MessageString);
    case QuestionDlg('Configuration data file exists',MessageString,mtCustom,[mrOK,'Overwrite',mrCancel,'Cancel'],'') of
         mrOK: begin
               AllowFlag:=True; //User allowed overwriting file.
               StatusMessage('Configuration data file ('+OutFileName+') exists, user allowed overwriting.');
         end;
         mrCancel: begin
               StatusMessage('Configuration data file ('+OutFileName+') exists already, user cancelled overwrite.');
           end;
    end;
  end
  else //File did not exist.
      AllowFlag:=True; //Allow writing.

  if AllowFlag then begin
    try
      Rewrite(CalLogFilename); //create the file

      Writeln(CalLogFilename,Format('%s-%s.txt',[SelectedUnitSerialNumber,LogfileSuffix]));
      Writeln(CalLogFilename,Format('%s Calibration data',[SelectedModelDescription]));
      Writeln(CalLogFilename,Format('Report Date: %s',[FormatDateTime('yyyy-mm-dd',Now())]));
      Writeln(CalLogFilename,Format('Serial Number: %s',[SelectedUnitSerialNumber]));
      //check if USB device:
      if ((CommNotebook.PageIndex=0) and not ((SelectedModel=5) or (SelectedModel=15))) then    // USB device but not RS232 or LL model
         Writeln(CalLogFilename,Format('USB Serial Number: %s',[USBSerialNumber.text]));
      //check if Ethernet device:
      if (CommNotebook.PageIndex=1) then
         Writeln(CalLogFilename,Format('MAC: %s',[EthernetMAC.text]));
      Writeln(CalLogFilename,Format('Model Number: %s (%s)',[Inttostr(SelectedModel),SelectedModelDescription]));
      Writeln(CalLogFilename,Format('Feature version: %s',[SelectedFeature]));
      Writeln(CalLogFilename,Format('Protocol version: %s',[SelectedProtocol]));

      case SelectedModel of
        model_DL, model_GPS, model_DLS:
            Writeln(CalLogFilename,Format('Data logging capacity: %d  records',[DLEStorageCapacity]));
      end;

      Writeln(CalLogFilename,Format('Light calibration offset: %2.2f mags/arcsec²',[ConfCalmpsas]));
      Writeln(CalLogFilename,Format('Light calibration temperature: %2.1f °C',[ConfCalLightTemp]));
      Writeln(CalLogFilename,Format('Dark calibration period: %2.3f seconds',[ConfCalPeriod]));
      Writeln(CalLogFilename,Format('Dark calibration temperature: %2.1f °C',[ConfCalDarkTemp]));
      Writeln(CalLogFilename,'Calibration offset: 8.71 mags/arcsec²');
      if SelectedModel=model_V then begin
        for AccCalPos:=1 to 6 do begin
          Writeln(CalLogFilename,Format('Acceleration position %d:  %6.0f  %6.0f  %6.0f',[AccCalPos,
                     w.getv(AccCalPos-1, 0),
                     w.getv(AccCalPos-1, 1),
                     w.getv(AccCalPos-1, 2)]));
          end;
          Writeln(CalLogFilename,Format('Magnetic maximum XYZ: %7.0f %7.0f %7.0f',[Mxmax,Mymax,Mzmax]));
          Writeln(CalLogFilename,Format('Magnetic minimum XYZ: %7.0f %7.0f %7.0f',[Mxmin,Mymin,Mzmin]));
      end;
      StatusMessage('Logged calibration file stored at: '+OutFileName);
    except
        MessageDlg('ERROR! IORESULT','ERROR! IORESULT: ' + IntToStr(IOResult) + ' during LogCalButtonClick',mtWarning,[mbOK],0);
    end;
    CloseFile(CalLogFilename);
  end;

end;

function TForm1.TelnetExpect(expectstring: string; sendstring:string): boolean;
begin
     TelnetExpect:=tsend.WaitFor(expectstring);
     if TelnetExpect then begin
       tsend.Send(sendstring+chr($0D));
       StatusMessage('Expected string from XPort telnet: "'+expectstring+'"  Sent: "'+ StringReplace(sendstring,chr($0D),'',[rfReplaceAll])+'"' );
     end
     else begin
       StatusMessage('Failed to see expected string from XPort telnet: '+expectstring);
     end;
     ResetXPortProgressBar.Position:=ResetXPortProgressBar.Position+1;
     Application.ProcessMessages;
end;

function TForm1.TelnetWaitFor(expectstring: string): boolean;
begin
     TelnetWaitFor:=tsend.WaitFor(expectstring);
     if TelnetWaitFor then begin
       StatusMessage('Waited string from XPort telnet: "'+expectstring +'"');
     end
     else begin
       StatusMessage('Failed to see expected string from XPort telnet: '+expectstring);
     end;
     ResetXPortProgressBar.Position:=ResetXPortProgressBar.Position+1;
     Application.ProcessMessages;
end;



procedure TForm1.bXPortDefaultsClick(Sender: TObject);
var
  i : Integer;
begin
  ResetXPortProgressBar.Position:=0;
  FinalResetForXPortProgressBar.Position:=0;

  LoadingStatus.Caption:='';
  StatusMessage('Set XPort defaults operation requested ...');

  tsend:= TTelnetSend.Create;
  tsend.TargetHost:=EthernetIP.Text;
  tsend.TargetPort:='9999';
  //CR is sent after each TelnetExpect.
  tsend.Login;
  StatusMessage('Sending XPort defaults to: '+tsend.TargetHost+' '+tsend.TargetPort);
  if (not TelnetExpect('Press Enter for Setup Mode', '')) then begin
    StatusMessage('Failed to: telnet '+tsend.TargetHost+' '+tsend.TargetPort);
    tsend.Destroy;
  end
  else begin
  TelnetExpect('Your choice ?', '7');
  TelnetExpect('Your choice ?', '1');
  TelnetExpect('Baudrate', '115200');
  TelnetExpect('I/F Mode', '');
  TelnetExpect('Flow (', '02');
  TelnetExpect('Port No (', '');
  TelnetExpect('ConnectMode ', '');
  TelnetExpect(' in Modem Mode  (', '');
  TelnetExpect('Show IP addr after ', '');
  TelnetExpect('Auto increment source port  ', '');
  TelnetExpect('Remote IP Address :',chr($0D)+chr($0D)+chr($0D));
  TelnetExpect('Remote Port  (', '');
  TelnetExpect('DisConnMode (', '');
  TelnetExpect('FlushMode   (', '80');
  TelnetExpect('Pack Cntrl  (', '01');
  TelnetExpect('DisConnTime (', '00'+chr($0D)+'02');
  TelnetExpect('SendChar 1', '');
  TelnetExpect('SendChar 2', '');
  TelnetExpect('Your choice ?', '9');
  TelnetWaitFor('Parameters stored');
  TelnetWaitFor('Connection closed by foreign host.'); // Added mainly for timeout, seems to properly save using the new XPort circa 20230723
  StatusMessage('Finished Sending XPort defaults using: telnet '+tsend.TargetHost+' '+tsend.TargetPort);
  tsend.Logout;
  tsend.Destroy;

  //Reset unit command resets unit for three seconds.
  StatusMessage('Resetting XPort ...');
  Application.ProcessMessages;
  //Wait about ten seconds for XPort to reset before moving on.
  for i:=0 to 200 do
    begin
      sleep(50);
      FinalResetForXPortProgressBar.Position:=i;
      Application.ProcessMessages;
    end;
   StatusMessage('XPort should have been reset, and ready to use now. Press FIND now');
  end;



end;

procedure TForm1.ConfDarkCalButtonClick(Sender: TObject);
var
  Reply: Integer;
begin
   StatusMessage('Button pressed: Configuration: Dark cal calibration');
   StatusMessage('Dark calibration attempted.');
   Reply:=Application.MessageBox(Pchar('Has the unit been in the dark for a long enough time?' + sLineBreak +
      'Are you sure?' + sLineBreak +
      'Cancel if not sure.'),'Configure Dark Calibration',MB_ICONWARNING + MB_OKCANCEL);
   if Reply=mrOK then begin
       StatusMessage('Dark calibration performed.');
       SendGet('zcalBx');
       GetConfCal;
       GetConfReading();
     end else StatusMessage('Dark calibration cancelled.');
end;

procedure TForm1.GetConfCal();
var
   pieces: TStringList;
begin
StatusMessage('GetConfCal called.');
//Clear out existing results
ConfCalmpsas:=0;
ConfCalLightTemp:=0;
ConfCalPeriod:=0;
ConfCalDarkTemp:=0;

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

  //do not check magnetometer until it has the proper firmware
  if (SelectedModel<>model_GDM) then begin
  pieces.DelimitedText := SendGet('cx');
  //Check size of array. 5 Sections normally, 6 sections when checksum is sent.
  //   and that returned value is "info".
  if ((pieces.Count>=6) and (pieces.Strings[0]='c')) then
    begin
         ConfCalmpsas:=StrToFloatDef(AnsiReplaceStr(pieces.Strings[1],'m',''),0,FPointSeparator);
         if ((ConfCalmpsas > 25) or (ConfCalmpsas < 15)) then begin //Colorize button when value is out of range
            ConfLightCalReq.Brush.Color:=clRed;
         end else begin
            ConfLightCalReq.Brush.Color:=$CBCBFF;
         end;
         ConfCalLightTemp:=StrToFloatDef(AnsiReplaceStr(pieces.Strings[3],'C',''),0,FPointSeparator);
         ConfCalPeriod:=StrToFloatDef(AnsiReplaceStr(pieces.Strings[2],'s',''),0,FPointSeparator);
         if ((ConfCalPeriod>300) or (ConfCalPeriod<40)) then begin
             ConfDarkCalReq.Brush.Color:=clRed;
         end else begin
             ConfDarkCalReq.Brush.Color:=$CBCBFF;
         end;
         ConfCalDarkTemp:=StrToFloatDef(AnsiReplaceStr(pieces.Strings[5],'C',''),0,FPointSeparator);
    end;

    //Update calibration report preview
    UpdateCalReport;
  end;

end;

procedure TForm1.ConfLightCalButtonClick(Sender: TObject);
var
  Reply: Integer;
begin
   StatusMessage('Button pressed: Configuration: Light calibration');
   StatusMessage('Light calibration attempted.');
   Reply:=Application.MessageBox(Pchar('Is the unit looking at a calibrated light source?' + sLineBreak +
      'Are you sure?' + sLineBreak +
      'Cancel if not sure.'),'Configure Light Calibration',MB_ICONWARNING + MB_OKCANCEL);
   if Reply=mrOK then begin
       StatusMessage('Light calibration performed.');
       SendGet('zcalAx');
       GetConfCal;
       GetConfReading();
   end
   else
     StatusMessage('Light calibration cancelled.');

end;

procedure TForm1.ConfGetCalClick(Sender: TObject);
begin
    StatusMessage('Button pressed: Configuration: Refresh conf cal');
    GetVersion;
    GetConfReading();
    GetConfCal;
    CheckLockSwitch();
end;

procedure TForm1.GetConfReading();
var
   result:string;
   pieces: TStringList;
   TemperatureReading:Real;
begin
  StatusMessage('GetConfReading called.');
  //Clear out existing results
  ConfRdgmpsas.Caption:='';
  ConfRdgPer.Caption:='';
  ConfRdgTemp.Caption:='';
  TemperatureReading:=-100.0;

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

  //Get response to "Request"
  pieces := TStringList.Create;
  pieces.StrictDelimiter := true; //Do not parse spaces
  pieces.Delimiter := ',';
  if StrToIntDef(SelectedFeature,0)>=22 then
     result:=SendGet('ux')
  else
     result:=SendGet('rx');
  pieces.DelimitedText := result;

  //Parse reading
  case SelectedModel of
    model_LELU,model_LR,model_DL,model_GPS,model_v, model_DLS, model_RPi, model_LL: begin   //3=Standard SQM-LE/LU, 5=SQM-LR, 6=SQM-LU-DL, 7 =SQM-LU-GPS, 11=Vector, 14=RPi, 15=LL
       if pieces.count>=6 then begin
           ConfRdgmpsas.Caption:=Format('%1.2f',[StrToFloatDef(AnsiMidStr(pieces.Strings[1],1,6),0,FPointSeparator)]);
           ConfRdgPer.Caption:=Format('%1.3f',[StrToFloatDef(AnsiMidStr(pieces.Strings[4],1,11),0,FPointSeparator)]);
           TemperatureReading:=StrToFloatDef(AnsiMidStr(pieces.Strings[5],0,6),0,FPointSeparator);
           ConfRdgTemp.Caption:=Format('%1.1f',[TemperatureReading]);
           if SelectedModel=model_V then begin //Gather extra Vector model calibration values
               GetAccelCal();
           end;
         end
       else
       begin
         ConfRdgmpsas.Caption:='xxx';
         ConfRdgPer.Caption:='xxx';
         ConfRdgTemp.Caption:='xxx';
       end;
    end;
    model_C:  begin   //Colour model
       if pieces.count=9 then
         begin
           ConfRdgmpsas.Caption:=Format('%1.2f',[StrToFloatDef(AnsiMidStr(pieces.Strings[1],1,6),0,FPointSeparator)]);
           ConfRdgPer.Caption:=Format('%1.3f',[StrToFloatDef(AnsiMidStr(pieces.Strings[4],1,11),0,FPointSeparator)]);
           TemperatureReading:=StrToFloatDef(AnsiMidStr(pieces.Strings[5],0,6),0,FPointSeparator);
           ConfRdgTemp.Caption:=Format('%1.1f',[TemperatureReading]);
         end
       else
       begin
         StatusMessage('Expected 9, got '+IntToStr(pieces.Count));
         ConfRdgmpsas.Caption:='xxx';
         ConfRdgPer.Caption:='xxx';
         ConfRdgTemp.Caption:='xxx';
       end;
    end;
  otherwise
     ConfRdgmpsas.Caption:='version?';
  end;
  if TemperatureReading > 65.0 then begin
    ConfTempWarning.Caption:=' Too High ';
    ConfTempWarning.Color:=clRed;
    ConfTempWarning.Font.Color:=clWhite;
  end
  else if TemperatureReading < 3.0 then begin
    ConfTempWarning.Caption:=' Too Low ';
    ConfTempWarning.Color:=clBlue;
    ConfTempWarning.Font.Color:=clWhite;
    end
    else begin
       ConfTempWarning.Caption:=' Normal ';
       ConfTempWarning.Color:=clDefault;//clBackground was black on Windows
       ConfTempWarning.Font.Color:=clDefault;
    end;

  if StrToIntDef(SelectedFeature,0)>=35 then begin //Enable lens model selections
    LHFCheck(''); //get settings
  end;

end;

//Continuous functions added to firmware version 40
//Results sent as Yx, received as Yrlcpud to YRLCPUD
procedure TForm1.ContCheck(command:string);
var
  result:string;
  DesiredLength:Integer;
begin
  StatusMessage('ContCheck('+command+')');

  DesiredLength:=5;
  result:=(SendGet(command));
  if Length(result)=DesiredLength then begin
    case AnsiMidStr(result,2,1) of
      'R': ContCheckGroup.Checked[0]:=True;
      'r': ContCheckGroup.Checked[0]:=False;
    end;
    case AnsiMidStr(result,3,1) of
      'C': ContCheckGroup.Checked[4]:=True;
      'c': ContCheckGroup.Checked[4]:=False;
    end;
    case AnsiMidStr(result,4,1) of
      'P': ContCheckGroup.Checked[1]:=True;
      'p': ContCheckGroup.Checked[1]:=False;
    end;
    case AnsiMidStr(result,5,1) of
      'U': ContCheckGroup.Checked[2]:=True;
      'u': ContCheckGroup.Checked[2]:=False;
    end;
  end
  else
    StatusMessage(Format('Sent: %s ContCheck result %s not proper length: Expecting %d, got %d',[command, result, DesiredLength,Length(result)]));
end;

procedure TForm1.FormPaint(Sender: TObject);
begin

//Painting on Windows is all messed up.
//- Again now with Lazarus 3.0, try removing UpdateCalReport below, now it paints controls properly
{$IFDEF Linux}
    //UpdateCalReport;
{$endif}

end;

procedure TForm1.DirectoriesMenuItemClick(Sender: TObject);
begin
  unitdirectorylist.Directories.Show;
end;

procedure TForm1.DLHeaderMenuItemClick(Sender: TObject);
begin
DLHeaderForm := TDLHeaderForm.Create(Application.MainForm);
DLHeaderForm.PopupParent := Application.MainForm;
DLHeaderForm.PopupMode := pmExplicit;

Application.MainForm.SetFocus;          // ensure main form is active in X11
Application.ProcessMessages;            // let X11 process the activation
DLHeaderForm.HandleNeeded;
Application.ProcessMessages;            // let X11 register the new window

DLHeaderForm.ShowModal;
FreeAndNil(DLHeaderForm);

     //DLHeaderForm.ShowModal;
end;

procedure TForm1.LogContinuousButtonClick(Sender: TObject);
var
  Reply: Integer;
begin
  StatusMessage('Button pressed: Information: Continuous');
  if SelectedModel=0 then
    GetVersion;

  {Indicate that Logging Continuous button was pressed.}
  StartLogging:=True;

  if ((Length(DelSpace(SelectedTZRegion))>0) and (Length(DelSpace(SelectedTZLocation))>0)) then begin
      {Start Modal window with status of: timed countdown, number of records stored, Stop logging button}
      {Had to switch from showmodal to show because automatic minimize would not work properly.}
      Form1.Enabled:=False; {Make-believe showmodal}
      FormLogCont.Show;
    end
  else begin
       //'Do this by pressing the Header button, then selecting your timezone.');
        Reply:=Application.MessageBox('The time zone information must be defined, do you want to do it now?', 'Time zone missing', MB_ICONQUESTION + MB_YESNO );
        if (Reply = IDYES) then DLHeaderForm.ShowModal;
      end;
end;

procedure TForm1.LogNextRecord10Click(Sender: TObject);
begin
  StatusMessage('Button pressed: Data Logging: Next record tenth');
  LogUpdateLogPointer();
  inc(DLCurrentRecord,max(round(DLEStoredRecords/10),1));
  DLCurrentRecord:=min(DLCurrentRecord,DLEStoredRecords);
  LogRecordGet(DLCurrentRecord);
end;

procedure TForm1.LogOneRecordButtonClick(Sender: TObject);
var
  result: string;
  pieces: TStringList;
  Altitude: Float;
begin
  StatusMessage('Button pressed: Information: One record');
  pieces := TStringList.Create;
  pieces.Delimiter := ',';

  if SelectedModel=0 then
    GetVersion;

  if ((Length(SelectedTZRegion)>0) and (Length(SelectedTZLocation)>0)) then
    begin
      try
        if SelectedModel=model_ADA then begin //ADA
         WriteDLHeader('ADA','One record logged');
         result:=GetReading;
         pieces.DelimitedText := result;
         pieces.StrictDelimiter := False; //Parse spaces also

        if (pieces.Count=8) then
           // YYYY-MM-DDTHH:mm:ss.fff;YYYY-MM-DDTHH:mm:ss.fff;Hz;Count1;Time1;Count2;Time2
           //r,0000000238Hz,0000000000c,0000000.000s,0000000000c,0000000.000s,000,109
           AssignFile(DLRecFile, LogFileName);
           Reset(DLRecFile);
           Append(DLRecFile); //Open file for appending
           SetTextLineEnding(DLRecFile, #13#10);
           Writeln(DLRecFile,
              FormatDateTime('yyyy-mm-dd"T"hh:nn:ss.zzz";"',NowUTC()) //Date UTC
              + FormatDateTime('yyyy-mm-dd"T"hh:nn:ss.zzz";"',Now()) //Date Local
              +  Format('%d;',[StrToIntDef(AnsiMidStr(pieces.Strings[1],1,10),0)]) //Frequency
              +  Format('%d;',[StrToIntDef(AnsiMidStr(pieces.Strings[2],1,10),0)]) //Counter1
              +  Format('%1.3f;',[StrToFloatDef(AnsiMidStr(pieces.Strings[3],1,11),0)]) //Time1
              +  Format('%d;',[StrToIntDef(AnsiMidStr(pieces.Strings[4],1,10),0)]) //Counter2
              +  Format('%1.3f;',[StrToFloatDef(AnsiMidStr(pieces.Strings[5],1,11),0)]) //Time2
           );
           Flush(DLRecFile);
           CloseFile(DLRecFile);
          end

        else if SelectedModel=model_V then begin //Vector model
         WriteDLHeader('DL-V-Log','One record logged');
         result:=GetReading;
         pieces.DelimitedText := result;
         pieces.StrictDelimiter := False; //Parse spaces also

        if (pieces.Count=6) then begin
           // YYYY-MM-DDTHH:mm:ss.fff;YYYY-MM-DDTHH:mm:ss.fff;Celsius;mag/arcsec^2;Ax;Ay;Az;Mx;My;Mz;degrees;degrees;degrees;count

           ComputeAzimuth();
           Altitude := ComputeAltitude(Ax1, Ay1, Az1);
           Heading := radtodeg(arctan2(-1 * Mz2, Mx2)) + 180;
           AssignFile(DLRecFile, LogFileName);
           Reset(DLRecFile);
           Append(DLRecFile); //Open file for appending
           SetTextLineEnding(DLRecFile, #13#10);
           Writeln(DLRecFile,
           FormatDateTime('yyyy-mm-dd"T"hh:nn:ss.zzz";"',LazSysUtils.NowUTC()) //Date UTC
            + FormatDateTime('yyyy-mm-dd"T"hh:nn:ss.zzz";"',Now()) //Date Local
            +  Format('%1.1f;',[StrToFloatDef(AnsiLeftStr(pieces.Strings[5],length(pieces.Strings[5])-1),0)]) //Temperature
            +  Format('%d;',[StrToIntDef(AnsiLeftStr(pieces.Strings[3],length(pieces.Strings[3])-1),0)]) //counts
            +  Format('%d;',[StrToIntDef(AnsiLeftStr(pieces.Strings[2],length(pieces.Strings[2])-2),0)]) //Hz
            +  Format('%1.2f;',[StrToFloatDef(AnsiLeftStr(pieces.Strings[1],length(pieces.Strings[1])-1),0)]) //mpsas value
            +  Format('%0.0f;%0.0f;%0.0f;%0.0f;%0.0f;%0.0f;%0.1f;%0.1f;%0.0f', [
               Ax, Ay, Az,  // Raw Accelerometer values
               Mx, My, Mz,  // Raw magnetometer values
               Altitude ,  // Altitude
               abs(Altitude - 90.0) ,  // Zenith
               Heading])  // Azimuth
             );
           Flush(DLRecFile);
           CloseFile(DLRecFile);
         end;
       end

        else if SelectedModel=model_C then begin //Colour model
          WriteDLHeader('C','One record logged');
          result:=GetReading;
          pieces.DelimitedText := result;
          pieces.StrictDelimiter := False; //Parse spaces also

         if (pieces.Count=8) then begin
            // YYYY-MM-DDTHH:mm:ss.fff;YYYY-MM-DDTHH:mm:ss.fff;Celsius;number;Hz;mag/arcsec^2;Scale;Color')
            AssignFile(DLRecFile, LogFileName);
            Reset(DLRecFile);
            Append(DLRecFile); //Open file for appending
            SetTextLineEnding(DLRecFile, #13#10);
            Writeln(DLRecFile,
               FormatDateTime('yyyy-mm-dd"T"hh:nn:ss.zzz";"',NowUTC()) //Date UTC
               + FormatDateTime('yyyy-mm-dd"T"hh:nn:ss.zzz";"',Now()) //Date Local
               +  Format('%1.1f;',[StrToFloatDef(AnsiLeftStr(pieces.Strings[5],length(pieces.Strings[5])-1),0)]) //Temperature
               +  Format('%d;',[StrToIntDef(AnsiLeftStr(pieces.Strings[3],length(pieces.Strings[3])-1),0)]) //counts
               +  Format('%d;',[StrToIntDef(AnsiLeftStr(pieces.Strings[2],length(pieces.Strings[2])-2),0)]) //Hz
               +  Format('%1.2f;',[StrToFloatDef(AnsiLeftStr(pieces.Strings[1],length(pieces.Strings[1])-1),0)]) //mpsas value
               +  Format('%d;',[StrToIntDef(pieces.Strings[6],0)]) //Scale
               +  Format('%d',[StrToIntDef(pieces.Strings[7],0)]) //Colour
            );
            Flush(DLRecFile);
            CloseFile(DLRecFile);
          end
         else begin
            StatusMessage('Expected 8 fields, got '+IntToStr(pieces.Count)+'.');
         end;

        end

        else begin //assume LE
         WriteDLHeader('LE','One record logged');
         result:=GetReading;
         pieces.DelimitedText := result;
         pieces.StrictDelimiter := False; //Parse spaces also

        if (pieces.Count>=6) then begin
           // YYYY-MM-DDTHH:mm:ss.fff;YYYY-MM-DDTHH:mm:ss.fff;Celsius;number;Hz;mag/arcsec^2')
           AssignFile(DLRecFile, LogFileName);
           Reset(DLRecFile);
           Append(DLRecFile); //Open file for appending
           SetTextLineEnding(DLRecFile, #13#10);
           Writeln(DLRecFile,
              FormatDateTime('yyyy-mm-dd"T"hh:nn:ss.zzz";"',NowUTC()) //Date UTC
              + FormatDateTime('yyyy-mm-dd"T"hh:nn:ss.zzz";"',Now()) //Date Local
              +  Format('%1.1f;',[StrToFloatDef(AnsiLeftStr(pieces.Strings[5],length(pieces.Strings[5])-1),0)]) //Temperature
              +  Format('%d;',[StrToIntDef(AnsiLeftStr(pieces.Strings[3],length(pieces.Strings[3])-1),0)]) //counts
              +  Format('%d;',[StrToIntDef(AnsiLeftStr(pieces.Strings[2],length(pieces.Strings[2])-2),0)]) //Hz
              +  Format('%1.2f',[StrToFloatDef(AnsiLeftStr(pieces.Strings[1],length(pieces.Strings[1])-1),0)]) //mpsas value
           );
           Flush(DLRecFile);
           CloseFile(DLRecFile);
         end
        else begin
           StatusMessage('Expected at least 6 fields, got '+IntToStr(pieces.Count)+'.');
        end;

       end;


      except
            StatusMessage('ERROR! IORESULT: ' + IntToStr(IOResult) + ' during LogOneRecordButtonClick');
      end;
    end
  else
      ShowMessage('Enter Time zone information into Header first. '+ sLineBreak+
      'Do this by pressing the Header button, then selecting your timezone.');
  if Assigned(pieces) then FreeAndNil(pieces);
end;

procedure TForm1.LogPreviousRecord10Click(Sender: TObject);
begin
  StatusMessage('Button pressed: Data Logging: Previous record tenth');
  LogUpdateLogPointer();
  dec(DLCurrentRecord,max(round(DLEStoredRecords/10),1));
  DLCurrentRecord:=max(DLCurrentRecord,1);
  LogRecordGet(DLCurrentRecord);
end;

procedure TForm1.MenuItem1Click(Sender: TObject);
begin
//Convert old log to datfile dialog
convertoldlogfile.ConvertOldLogForm.Show;

end;

procedure TForm1.PrintLabelButtonClick(Sender: TObject);
//Print to DYMO LabelWriter 320 in Landscape setting using Address Labels 2" tall, 1" wide

{Brother PT-P700 label printer
 - use driver Brother PT-2420PC Foomatic/ptouch
 switch off the USB drive (left button)

Hint: Do not print the test page. It is just a waste of band.
 Printer Options:
   - Page size: 24x100mm (only 24mm wide tape dimension is used)
   - Print quality: High quality
   - Concatenate pages
   - Print density: Dark
   - Roll fed media: Continuous roll
   - Advance distance: Small
   - Cut mark: Print cut mark after each labels
   - Align: Right aligned
}


  procedure PrintLabelLine(LabelText:String);
  var
    WhereTo: TCanvas;
    CH: Integer;//Column height
    CellMargin: Integer;
    H: Integer;
  begin
      WhereTo:=Printer.Canvas;
      CellMargin:=5;
      H := WhereTo.TextHeight(LabelText);
      CH:=H+2*CellMargin;

      WhereTo.TextOut(
                -30+PrintingLine*CH, //X axis (from left)//was 10 for DYMO320
                0, // Y axis from center downwards//was 10 for DYMO320
                LabelText);

    Inc(PrintingLine);
  end;

begin
  StatusMessage('Button pressed: Configuration: Label calibration');
  GetVersion;
  GetConfCal;
  Application.ProcessMessages;
  if PrintDialog1.Execute then begin
    Printer.BeginDoc;
    Printer.Canvas.Font.Name := 'Liberation Mono';
    Printer.Canvas.Font.Size := 10;//9 is a good size for Dymo address labels rotated 90deg.
    Printer.Canvas.Font.Orientation:=-900; //degrees * 10 (90 degrees)

    Application.ProcessMessages;
        PrintingLine:=0;
        //check if USB device:
        if ((Form1.CommNotebook.PageIndex=0) and not (SelectedModel=5)) then    // USB device but not RS232 model
           PrintLabelLine(' USB S/N: '+ Form1.USBSerialNumber.text);
        //check if DataLoging device:
        if ((SelectedModel=6) or (SelectedModel=7) or (SelectedModel=9) or (SelectedModel=11)) then begin
           //DLEGetCapacity();
           PrintLabelLine(Format('Capacity: %d rec',[DLEStorageCapacity]));
        end;
        //check if Ethernet device:
        if (Form1.CommNotebook.PageIndex=1) then
           PrintLabelLine('     MAC: '+ Form1.EthernetMAC.text);
        //All devices:
        PrintLabelLine('   Model: '+ SelectedModelDescription);
        PrintLabelLine('Unit S/N: '+ SelectedUnitSerialNumber);

        //writeln(Printer.PaperSize.Width);//debug
        //writeln(Printer.PaperSize.Height);//debug

    Printer.EndDoc;
  end;
end;

procedure TForm1.RS232PortDropDown(Sender: TObject);
Var Info : TSearchRec;
    Count : Longint;

Begin
  {$IFDEF Linux}
  RS232Port.Clear;
  Count:=0;

  If FindFirst ('/dev/ttyUSB*',faAnyFile ,Info)=0 then begin
    Repeat
      Inc(Count);
      With Info do begin
        //Writeln (Name:40,Size:15);
        RS232Port.AddItem('/dev/'+Name,Nil);
      end;
    Until FindNext(info)<>0;
  end;

  If FindFirst ('/dev/ttyS*',faAnyFile ,Info)=0 then begin
    Repeat
      Inc(Count);
      With Info do begin
        //Writeln (Name:40,Size:15);
        RS232Port.AddItem('/dev/'+Name,Nil);
      end;
    Until FindNext(info)<>0;
  end;

  FindClose(Info);
  RS232Port.Sorted:=True;
  {$ENDIF Linux}
  {$IFDEF Darwin}
  RS232Port.Clear;
  Count:=0;

  If FindFirst ('/dev/ttyUSB*',faAnyFile ,Info)=0 then begin
    Repeat
      Inc(Count);
      With Info do begin
        RS232Port.AddItem('/dev/'+Name,Nil);
      end;
    Until FindNext(info)<>0;
  end;

  If FindFirst ('/dev/ttyS*',faAnyFile ,Info)=0 then begin
    Repeat
      Inc(Count);
      With Info do begin
        RS232Port.AddItem('/dev/'+Name,Nil);
      end;
    Until FindNext(info)<>0;
  end;

  If FindFirst ('/dev/cu.*',faAnyFile ,Info)=0 then begin
    Repeat
      Inc(Count);
      With Info do begin
        RS232Port.AddItem('/dev/'+Name,Nil);
      end;
    Until FindNext(info)<>0;
  end;

  FindClose(Info);
  RS232Port.Sorted:=True;
  {$ENDIF Darwin}
end;

procedure TForm1.SnowLoggingEnableBoxChange(Sender: TObject);
begin
if SnowLoggingEnableBox.Checked then
     SnowLEDStatus(SendGet('A5ex'))
   else
     SnowLEDStatus(SendGet('A5dx'));
end;

procedure TForm1.USBPortChange(Sender: TObject);
begin
     if length(USBPort.Text)>0 then
        EnableControls(True)
     else
          begin
               EnableControls(False);
               ClearResults();
          end;
     EnableFirmware();
end;

procedure TForm1.VCalButtonClick(Sender: TObject);
begin
  Vector.VectorForm.VectorPageControl.TabIndex:=0;
  Vector.VectorForm.ShowModal;
end;

procedure TForm1.VersionItemClick(Sender: TObject);
begin
     textfileviewer.fillview('Version information','changelog.txt');
end;

procedure TForm1.PrintCalReportClick(Sender: TObject);
begin
  StatusMessage('Button pressed: Configuration: Print calibration');
  GetVersion;
  GetConfCal;
  //writeln(Printer.Printers[Printer.PrinterIndex]);
  //vConfigurations.WriteString('PrintCal','PrinterName',Printer.Printers[Printer.PrinterIndex]);
  Application.ProcessMessages;
  if PrintDialog1.Execute then begin
    with Printer do
    try
      BeginDoc;
      //writeln(Printer.Printers[Printer.PrinterIndex]);
  //    Printer.PrinterName:=vConfigurations.ReadString('PrintCal','PrinterName',Printer.PrinterName);
      Canvas.Font.Name:=FixedFont;// was 'Arial'
      Canvas.Font.Size := 8; //was 9, 8 for good  fit on paper inside grid

      Canvas.Font.Orientation:=0; //normal rotation in case rotated label was previously printed
      CalPrint:=True;
      UpdateCalReport;
      CalPrint:=False;
      UpdateCalReport;
    finally
      EndDoc;
    end;
    //vConfigurations.WriteString('PrintCal','PrinterName',Printer.Printers[Printer.PrinterIndex]);
    end;
end;

procedure TForm1.StartResettingClick(Sender: TObject);
begin
//Reset unit command resets unit for three seconds.
Application.ProcessMessages;
//Wait about one second before moving on.
ResetContinuous:=True;
while ResetContinuous do
  begin
    sendget(chr($19),False,1,False);
    sleep(100);
    Application.ProcessMessages;
  end;

end;


procedure TForm1.DLGetSettings();
var
 result: AnsiString;
 UnitClock: AnsiString;
 ThisMomentUTC, UnitTime: TDateTime;
 pieces:TStringList;
 ClockDiffSeconds:Int64;
 TriggerModeNumber:Integer;
begin

  pieces := TStringList.Create;
  pieces.Delimiter := ',';
  pieces.StrictDelimiter := True; //Do not parse spaces

//Read Logging Interval settings
  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,FPointSeparator));
  DLThreshold.Color:=clWindow;

  //Read log trigger mode
  result:=sendget('Lmx');
  TriggerModeNumber:=StrToIntDef(AnsiMidStr(result,4,1),0);
  TriggerComboBox.ItemIndex:=TriggerModeNumber;
  case TriggerModeNumber of
     1..2: Begin //Show Seconds or Minutes selection
             DLSecMinPages.Visible:=True;
             DLSecMinPages.TabIndex:=TriggerModeNumber-1;
        end;
     else DLSecMinPages.Visible:=False;
  end;

  SnowGroupBox.Visible:=SnowLoggingEnabled;

  //Get EEPROM capactity
  DLEGetCapacity();

  //Get log pointer
  LogUpdateLogPointer();

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

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

  result:=sendget('Lcx'); { Read the RTC }
  ThisMomentUTC:=NowUTC();
   if Length(result)>=21 then begin
       UnitClock:=FixDate(AnsiMidStr(Trim(result),4,19));
       try
          UnitTime:=ScanDateTime('yy-mm-dd hh:nn:ss',LeftStr(UnitClock,9)+RightStr(UnitClock,8));
       except
             StatusMessage('Invalid RTC from device = '+UnitClock);
             UnitTime:=ThisMomentUTC;
       end;
       ClockDiffSeconds:=SecondsBetween(ThisMomentUTC,UnitTime);
       StatusMessage('Real Time Clock difference: '+IntToStr(ClockDiffSeconds));
       DLClockDifference.Text:=IntToStr(ClockDiffSeconds);
       if ClockDiffSeconds=1 then
         DLClockDifferenceLabel.Caption:='second'
       else
         DLClockDifferenceLabel.Caption:='seconds';
       if ThisMomentUTC>UnitTime then
        DLClockDifferenceLabel.Caption:=DLClockDifferenceLabel.Caption+' slow'
       else
        DLClockDifferenceLabel.Caption:=DLClockDifferenceLabel.Caption+' fast';
     end
   else
     DLClockDifference.Text:='unknown';

   //Read the vibration threshold
   if SelectedModel=model_V then begin
     VThreshold.Text:='';     //default to no setting
     ThresholdVibrationGroup.Visible:=True;
       pieces.DelimitedText:=sendget('vtx');
       if pieces.Count=2 then begin
         VThreshold.Text:=format('%d',[StrToIntDef(pieces.Strings[1],0)]);
       end
       else
         VThreshold.Text:='0';

       VThreshold.Color:=clWindow;
   end
   else
      ThresholdVibrationGroup.Visible:=False;

  //Read mutual access logging setting
  if (StrToInt(SelectedFeature)>=68) then begin;
      pieces.DelimitedText:=sendget('Ldx');
      if pieces.Count=2 then begin
        DLLogOnBatt:=pieces.Strings[1]='1';
      end
      else
        DLLogOnBatt:=False;

      case DLLogOnBatt of
         True:DLMutualAccessGroup.ItemIndex:=0;
         False:DLMutualAccessGroup.ItemIndex:=1;
      end;
  end;

   if Assigned(pieces) then FreeAndNil(pieces);
end;

procedure TForm1.DataNoteBookChange(Sender: TObject);
begin

  //Check if udm found any devices or if the RSR232 tab was selected
  if (FoundDevices.SelCount>0) or (CommNotebook.PageIndex=2) then begin

         //Calibration page
         if DataNoteBook.ActivePage.Caption='Calibration' then begin
           StatusMessage('Button pressed: Calibration tab');
           GetCalInfo;
         end;

         //Enable accessory access
         if StrToIntDef(SelectedFeature,0)>=40 then begin
           if DataNoteBook.Pages[DataNoteBook.PageIndex].Caption='Accessories' then begin
             StatusMessage('Button pressed: Accessories tab');
             AccRefreshButtonClick(Nil);
           end;
         end;

         //Report Interval Page
         if DataNoteBook.ActivePage.Caption='Report Interval' then begin
           StatusMessage('Button pressed: Report Interval tab');
           SendGet('rx');//to purge old interval reports if any
           ParseReportInterval(SendGet('Ix'));

           //The continuous functions are available on feature version is 40 and higher
           if StrToIntDef(SelectedFeature,0)>=40 then begin
              ContCheckGroup.Visible:=True;
              ContCheck('Yx');//Show continuous selections
           end
           else begin
              ContCheckGroup.Visible:=False;
           end;
         end;

         //All other tabs
         if DataNoteBook.PageIndex>=0 then begin
            if DataNoteBook.Page[DataNoteBook.PageIndex].Caption='Vector' then begin
                StatusMessage('Button pressed: Vector tab');
              //show VectorTab screen
              end
            else //If not on VectorTab page
              begin //Shutdown monitoring

             //VectorTab.stopmonitoring();
            end;
         end;

         //Firmware Page
         if DataNoteBook.ActivePage.Caption='Firmware'  then begin
           StatusMessage('Button pressed: Firmware tab');

             FirmwareFilterIndex:=1; //All

             case CommNotebook.PageIndex of
                0: begin //USB
                   case SelectedModel of
                      model_LELU: FirmwareFilterIndex:=2;
                      model_DL  : FirmwareFilterIndex:=3;
                      model_V   : FirmwareFilterIndex:=4;
                      model_DLS : FirmwareFilterIndex:=6;
                      model_GDM : FirmwareFilterIndex:=7;
                      model_RPi : FirmwareFilterIndex:=9;
                      model_LL : FirmwareFilterIndex:=10;
                    else FirmwareFilterIndex:=1;
                   end;
                   {$ifndef Windows} {Windows is too slow for USB unplugging method}
                     FWUSBGroup.Visible:=True;
                   {$else}
                     FWUSBGroup.Visible:=False;
                   {$endif}
                   FWEthGroup.Visible:=False;
                end;
                1: begin //Ethernet
                  case SelectedModel of
                    model_LELU: FirmwareFilterIndex:=2;
                    model_C   : FirmwareFilterIndex:=8;
                    else FirmwareFilterIndex:=1;
                  end;
                  FWUSBGroup.Visible:=False;
                  FWEthGroup.Visible:=True;
                end;
                2: begin  //RS232
                     FirmwareFilterIndex:=5;
                     FWUSBGroup.Visible:=False;
                     FWEthGroup.Visible:=False;
                end;
             end;

             CurrentFirmware.Text:=Format('%s-%d-%s',[SelectedProtocol, SelectedModel, SelectedFeature]);
         end;

         //Data Logging Page
         if DataNoteBook.ActivePage.Caption='Data Logging'  then begin
           StatusMessage('Button pressed: Data Logging tab');
           DLGetSettings();
           LogUpdateLogPointer();
           DLCurrentRecord:=1;
           LogRecordGet(DLCurrentRecord);
           DLMutualAccessGroup.Visible:=StrToInt(SelectedFeature)>=68;
         end;

         //Configuration Page
         if DataNoteBook.ActivePage.Caption='Configuration' then begin
             StatusMessage('Button pressed: Configuration tab');
             //Clear out old images (to be updated below)
             LockedImage.Visible:=False;
             UnLockedImage.Visible:=False;
             LockSwitchOptions.Visible:=False;

             GetVersion;

             //The lens model information is available on feature version is 35 and higher
             if StrToIntDef(SelectedFeature,0)>=35 then begin //Enable lens model selections
                LHCombo.Visible:=True;             LHComboLabel.Visible:=True;
                LensCombo.Visible:=True;           LensComboLabel.Visible:=True;
                FilterCombo.Visible:=True;         FilterComboLabel.Visible:=True;
             end
             else begin
                LHCombo.Visible:=False;            LHComboLabel.Visible:=False;
                LensCombo.Visible:=False;          LensComboLabel.Visible:=False;
                FilterCombo.Visible:=False;        FilterCombolabel.Visible:=False;
             end;

             if ((StrToIntDef(SelectedFeature,0)>=46) and ((SelectedModelDescription='SQM-LE') or (SelectedModel=model_C))) then begin //Enable Lock switch settings
                LockSwitchOptions.Visible:=True;
                LockSettingsCheck('Kx');
             end;

             GetConfReading();
             GetConfCal;
             UpdateCalReport;
           end;
    end;

end;

procedure TForm1.FWWaitUSBButtonClick(Sender: TObject);
begin
  FirmwareCounter:=0;
  FirmwareState:=0;
  FWCounter.Caption:='';
  FirmwareTimer.Enabled:=True;
  FWWaitUSBButton.Enabled:=False;
  FWStopUSBButton.Enabled:=True;
end;

procedure TForm1.FWStopUSBButtonClick(Sender: TObject);
begin
  FirmwareTimer.Enabled:=False;
  FirmwareState:=0;
  FWStopUSBButton.Enabled:=False;
  FWCounter.Caption:='';
end;

procedure TForm1.StartUpMenuItemClick(Sender: TObject);
begin
  {Allow editing of startup optional parameters.}
  startupoptions.fillview();

end;

procedure TForm1.StartupTestMenuItemClick(Sender: TObject);
begin
     StartupTestForm.show;
end;

//Test the SQM-LL response time
procedure TForm1.TestResponseButtonClick(Sender: TObject);
var
 i:  integer = 0;
begin
     //Send reset then multiple rx to test the response time of getting a reading after power up.

     //Force Freshness reporting r1x to report Fresh/Stale state for getreading routine.
     //Freshness:=true;

     StatusMessage('Testing response time: Send reset command.');

     sendget(chr($19),False,1,False);

     sleep(1); //Pause while unit is resetting

     StatusMessage('Get readings.');
     //gather a few readings
     for i := 1 to 50 do begin
         //GetReading;
         SendGet('U1x');
         sleep(32); //Sleep between readings to allow interrupt handler to keep up.
     end;

end;

procedure TForm1.FirmwareTimerTimer(Sender: TObject);
var
  {$ifdef Windows}
  reg: TRegistry;
  Regkey: String;
  keys: TStringlist;
  i: Integer;
  {$endif}
  portexists:Boolean = False;
begin

  {$ifdef Windows}
  keys := TStringList.Create;
  reg := TRegistry.Create;
  reg.RootKey := HKEY_LOCAL_MACHINE;
  if reg.KeyExists('HARDWARE\DEVICEMAP\SERIALCOMM\') then begin
    reg.OpenKeyReadOnly('HARDWARE\DEVICEMAP\SERIALCOMM\');
    reg.GetValueNames(keys);
    for i := 0 to keys.Count-1 do
      begin
       if SelectedPort=reg.ReadString(keys.ValueFromIndex[i]) then portexists:=True;
        //StatusMessage('FindUSB: Connected device = "'+reg.ReadString(keys.ValueFromIndex[i])+'"');
      end;
  end;
  {$endif}

  {$if defined(Linux) or defined(Darwin)}
   portexists:=FileExists(SelectedPort);
  {$ifend}

  case FirmwareState of
     0: begin
          if not portexists then begin
            FWUSBExistsLabel.caption:=SelectedPort+' :unplugged, PLUG IN NOW';
            StatusMessage('FW: Unit was unplugged.');
            FirmwareState:=1;
            FirmwareCounter:=0;
          end
          else
            FWUSBExistsLabel.caption:=SelectedPort+' :connected, UNPLUG NOW.';

          FWCounter.Visible:=True;
        end;
     1: // wait a few seconds for power to drain from inside meter (n x 100ms timer)
         begin
           if not portexists then begin
             FWUSBExistsLabel.caption:=SelectedPort+' :unplugged, PLEASE WAIT.';
             if FirmwareCounter>=30 then begin//30 x 100ms = 3.0s (too seconds off was too short)
               FirmwareState:=2;
               FWUSBExistsLabel.caption:=SelectedPort+' :unplugged, PLUG IN NOW.';
             end;
           end
           else //must have been plugged in prematurely
              FirmwareState:=0;
           end;
     2: if portexists then begin
          FirmwareTimer.Enabled:=False; {shut off reset oscillator}
          Application.ProcessMessages;
          FWUSBExistsLabel.caption:=SelectedPort+' :plugged in, Loading firmware.';
          StatusMessage('FW: Unit was plugged in.');
          FWStopUSBButton.Enabled:=False;
          FWCounter.Visible:=False;
          LoadFirmwareClick(nil);
        end;
  end;
  Inc(FirmwareCounter);
  FWCounter.Caption:=format('%0.1fs',[FirmwareCounter/10.0]);

end;

procedure TForm1.LoadFirmwareClick(Sender: TObject);
var
 File1: TextFile;
 Str,Result: String;
 i:Integer;
 begin
   StatusMessage('Loading firmware started.');
   FWCounter.Caption:='';
   FirmwareTimer.Enabled:=False; {shut off reset oscillator}
   FirmwareState:=0;

   FinalResetForFirmwareProgressBar.Position:=0;

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

     Reset(File1); //Reset position of file to beginning.

     StatusMessage('Assigned and reset firmware file.');
     Application.ProcessMessages;


    //Reset unit command resets unit for three seconds.
    LoadingStatus.Caption:='Resetting unit ...';
    StatusMessage(LoadingStatus.Caption);
    sendget(chr($19),False,1,False);

    //Wait a while before moving on.
    case SelectedModel of
      model_LL: begin   //15=LL, wait for one millisecond before sending commands,
            //Does not work since the bootload timeout is way too short now).
            sleep(1);
            ResetForFirmwareProgressBar.Position:=ResetForFirmwareProgressBar.Max;
            Application.ProcessMessages;
      end;
      else begin //All other meters have longer bootload timeout
        for i:=0 to 20 do begin
            sleep(50); // ??Caused problems when bricked unit was reset from power up.??
            ResetForFirmwareProgressBar.Position:=i;
            Application.ProcessMessages;
        end;
      end;
    end;


    LoadingStatus.Caption:='Unit should have been reset ...';
    StatusMessage(LoadingStatus.Caption);

    LoadFirmwareProgressBar.Position:=0;
    LoadingStatus.Caption:='Loading firmware ...';
    StatusMessage(LoadingStatus.Caption);
    OpenComm();
    repeat
      //Read a whole line from the hex file.
      Readln(File1, Str);

      //Send the programming line to device and expect 'OK'.
      //For some reason, status messages of the programming crashes a Mac, HideStatus.
      Result:=sendget(Str,True,7000,True,True);

      //Update progress bar.
      LoadFirmwareProgressBar.Position:=LoadFirmwareProgressBar.Position+1;
      Application.ProcessMessages;

      //Wait after programming line packet is sent
      case SelectedModel of
        model_LL:
            sleep(1); //1ms should be good enough
        else
          sleep(10);  //Wait 10ms for Ethernet module to recuperate.
      end;
    until(EOF(File1) or (not(AnsiContainsText(Result,'Ok')))); // EOF(End Of File) keep reading new lines until end.

    if AnsiContainsText(Result,'Ok') then begin
        //Wait about one second before moving on.
        LoadingStatus.Caption:='Unit is resetting.';
        StatusMessage(LoadingStatus.Caption);
        for i:=0 to 20 do begin
            sleep(150);
            FinalResetForFirmwareProgressBar.Position:=i;
            Application.ProcessMessages;
          end;
        LoadingStatus.Caption:='Firmware loaded and unit reset. PRESS FIND.';
        StatusMessage(LoadingStatus.Caption);
        CurrentFirmware.Text:=''; //make user manually check firmware version
        ClearResults();
      end
    else begin
         LoadingStatus.Caption:='Firmware load failed!  Result: ' + Result;
         StatusMessage(LoadingStatus.Caption);
    end;
    //CloseComm;
    CloseFile(File1);
  except
        LoadingStatus.Caption:='File: '+FirmwareFile.Text+' IOERROR!';
        StatusMessage(LoadingStatus.Caption);
  end;
  {$I+}//Turn IO checking back on.
    end
  else begin
       LoadingStatus.Caption:='File '+FirmwareFile.Text+' does not exist!';
       StatusMessage(LoadingStatus.Caption);
  end;

  FWWaitUSBButton.Enabled:=True;
  FWUSBExistsLabel.caption:='';
  FWCounter.Caption:='';
  EnableFirmware();
end;
//Check if LE lock is set:
// Returns true if locked or unknown.
// Returns false if unlocked.
function TForm1.CheckLockSwitch():Boolean;
var
   resultstr:ansistring;
   State: Boolean;
begin
     StatusMessage('CheckLockSwitch called.');
     SendGet('rx'); //Flush unwanted report interval info
     resultstr:=SendGet('zcalDx');
     if AnsiContainsStr(resultstr, 'L') then
        State:=True
     else
         if AnsiContainsStr(resultstr, 'U') then
            State:=False
         else //All unknowns indicate locked (safety state)
            State:=True;

   LockedImage.Visible:=State;
   UnlockedImage.Visible:=not State;
   CheckLockSwitch:=State;
end;

procedure TForm1.CheckLockButtonClick(Sender: TObject);
var
   result:ansistring;
begin
     SendGet('rx'); //Flush unwanted report interval info
     result:=SendGet('zcalDx');
     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
     DLRefreshed:=False;
end;

procedure TForm1.FindAllDevices(LimitScope:String='');
var
  i: Integer; //General purpose counter
  j: Integer; //General purpose counter
  k: Integer = 0; //General purpose counter
  s : FoundDevice; //General purpose record
  SectionNames: TStringList;
  Section: String;
  InstID: String;
  SelectionString:String = '';
  SelectionFound:Boolean = False;
  SelectionInListFound:Boolean = False;
begin

     {Close all communications ports}
     CloseComm();

     DataNoteBook.Enabled:=False;
     { Clear out existing results }
     ClearResults();
     FoundDevices.Items.Clear;

     { Get saved section names for displaying Instrument ID. }
     SectionNames:= TStringList.Create;
     vConfigurations.ReadSectionNames(SectionNames);

     screen.Cursor:= crHourGlass;
     Application.ProcessMessages;

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

     if ((LimitScope='') or (LimitScope='USB') or (ParameterCommand('-SU'))) then begin
     {$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}

     {$ifdef Windows}
             FindUSB();                  //Try finding USB attached devices.
     {$endif}

     {$ifdef Linux}
             FindUSB();                  //Try finding USB attached devices.
     {$endif}
     end;

     if (((LimitScope='') or (LimitScope='Eth')) and not (ParameterCommand('-SU'))) then begin
        findEth;                       //Try finding Ethernet devices.
     end;

  StatusMessage('Getting InstrumentIDs.');

  //All USB devices have been found ... or not.
  if (high(FoundDevicesArray)=0) then
     StatusMessage('No devices were found')
  else begin
        //Show list of items that were found
        for i:=low(FoundDevicesArray) to high(FoundDevicesArray)-1 do begin

              //Get instrument ID of displayed device
              InstID:=''; //Default empty ID
              for Section in SectionNames do begin
                 if (FoundDevicesArray[i].SerialNumber = vConfigurations.ReadString(Section, 'HardwareID', '')) then begin
                   if vConfigurations.ReadString(Section, 'Instrument ID', '')<>'' then
                     InstID:=vConfigurations.ReadString(Section, 'Instrument ID', '');
                 end;
               end;

               FoundDevices.Items.Add(
                FoundDevicesArray[i].Hardware +
                ' : ' + format('%12s',[FoundDevicesArray[i].SerialNumber]) +
                ' : ' + FoundDevicesArray[i].Connection +
                ' : ' + InstID);
            end;
      end;
  StatusMessage('Populated FoundDevices window.');

  {Allow screens to be populated (may not be necessary)}
  Application.ProcessMessages;

  {Check command line option to automatically select device}
  if ((ParameterCommand('-SEI')) and (ParameterValue.Count>1)) then begin {Look to select Ethernet device by IP address}
    SelectionString:='IP address: ' + ParameterValue.Strings[1];
    for i:=low(FoundDevicesArray) to high(FoundDevicesArray)-1 do begin
      if (FoundDevicesArray[i].Connection=ParameterValue.Strings[1]) then begin
        FoundDevices.Selected[i]:=True;
        StatusMessage('Selecting '+SelectionString);
        SelectionFound:=True;
        SelectDevice;
       end;
     end;
  end
  else if  ((ParameterCommand('-SEM')) and (ParameterValue.Count>1)) then begin {Look to select Ethernet device by MAC address}
    SelectionString:='model: ' + ParameterValue.Strings[1];
    for i:=low(FoundDevicesArray) to high(FoundDevicesArray)-1 do begin
      if (FoundDevicesArray[i].SerialNumber=ParameterValue.Strings[1]) then begin
        FoundDevices.Selected[i]:=True;
        StatusMessage('Selecting '+SelectionString);
        SelectionFound:=True;
        SelectDevice;
      end;
    end;
  end
  else if  ((ParameterCommand('-SUC')) and (ParameterValue.Count>1)) then begin {Look to select USB device by COMPORT}
    SelectionString:='communications port: ' + ParameterValue.Strings[1];
    for i:=low(FoundDevicesArray) to high(FoundDevicesArray)-1 do begin
      if (FoundDevicesArray[i].Connection=ParameterValue.Strings[1]) then begin
        FoundDevices.Selected[i]:=True;
        StatusMessage('Selecting '+SelectionString);
        SelectionFound:=True;
        SelectDevice;
      end;
    end;
    end

  else if  ((ParameterCommand('-SUI')) and (ParameterValue.Count>1)) then begin {Look to select USB device by ID value, i.e. FTD12345}
    {*** TODO: parse multiple IDs (for use in logging more than one meter), and then get the port information for each of them for use in logcontinuous}
    {i.e. -SUI,FTD12345
     i.e. -SUI,FTD12345,FTD12346,FTD12347
     - Priority of selected deive is first to last in the list.
     - LogContinuous will get data from each selected found device.}
    StatusMessage('Devices available for LogContinuous: '+IntToStr(high(SelectedDevicesArray)+1));;
    for j:= ParameterValue.Count-1 downto 1 do begin //grab all entries except the initial command
      SelectionString:='device: ' + ParameterValue.Strings[j];
      StatusMessage('Looking for USB '+SelectionString);
      SelectionInListFound:=False;
      for i:=low(FoundDevicesArray) to high(FoundDevicesArray)-1 do begin
        if (FoundDevicesArray[i].SerialNumber=ParameterValue.Strings[j]) then begin
          FoundDevices.Selected[i]:=True;
          StatusMessage('Found, selecting '+SelectionString);
          SelectionInListFound:=True;
          SelectionFound:=True;
          SelectDevice;
          {Add selected device to array for LogContinuos to use.}
          SetLength(SelectedDevicesArray,high(SelectedDevicesArray)+2);
          StatusMessage('Devices available for LogContinuous: '+IntToStr(high(SelectedDevicesArray)+1));;
          SelectedDevicesArray[k]:=FoundDevicesArray[i];
          SelectedDevicesArray[k].Index:=i;
          Inc(k); //increment
        end;
      end;
        if not SelectionInListFound then
         StatusMessage('Desired USB ID not found: '+ParameterValue.Strings[j] );
    end;
    for s in SelectedDevicesArray do begin
      StatusMessage('Device available for logging: '+ s.Hardware +' : '+ s.SerialNumber+' : '+s.Connection );
    end;
    {Check how many available selected devices are defined for LogContinuous
     - if 0 in array, then just use the already selected device
     - else use the array
     NumberOfMultipleDevices=0  no multiples selected at the command line, user selsected one meter from the FoundDevices list
     NumberOfMultipleDevices>0  multiples selected at the command line}
    if (high(SelectedDevicesArray))>0 then begin
      NumberOfMultipleDevices:=high(SelectedDevicesArray)+1;
      StatusMessage('Number of Multiple devices: '+ IntToStr(NumberOfMultipleDevices));
    end;

end


    else
    begin
      {For convenience, if only one device was found, select it.}
      if FoundDevices.Count=1 then begin
           DataNoteBook.Enabled:=True;
           FoundDevices.Selected[0]:=True;
           StatusMessage('Only one device found, selecting it.');
           SelectDevice; //enables all allowed control buttons
           GetReading;
        end
      else if FoundDevices.Count>1 then begin
           DataNoteBook.Enabled:=False;
           StatusMessage('More than one possible device found. Select the desired device.');
           SelectDevice; {disables all disallowed control buttons.}
        end
      else begin
        DataNoteBook.Enabled:=False;
        StatusMessage('No devices found, disabling all disallowed control buttons.');
        SelectDevice; {disables all disallowed control buttons.}
      end;
    end;
  screen.Cursor:= crDefault;

  if Assigned(SectionNames) then FreeAndNil(SectionNames);

  //StatusMessage('FoundDevices listed.');

  if ((SelectionString <>'') and (not SelectionFound)) then
    StatusMessage(SelectionString+' not found.');


end;


procedure TForm1.FindButtonClick(Sender: TObject);
begin
     {Do not allow more clicks because it will produce multiple requests that show up in the found devices table.}
     FindButton.Enabled:=False;

     {Flip to Information tab since MacOS permanently disables widgets on Configuration page if shown.}
     DataNoteBook.PageIndex:=0;
     Application.ProcessMessages;

     FindAllDevices();

     {Re enable this button after finding devices.}
     FindButton.Enabled:=True;
end;

procedure TForm1.SelectDevice;
const
   {$WRITEABLECONST ON}
   IsInside:Boolean=False;
   {$WRITEABLECONST OFF}
var
   ItemSelected:Integer;
   EnableControl:Boolean;
begin

  if IsInSide then begin
    StatusMessage('Is inside SelectDevice already, possible pressed Find more than once quickly.');
    Exit;
  end;
  IsInside:=True;
  try
   if (not(gettingversion) and not(gettingreading)) then begin

   {Close existing comm if opened}
   CloseComm();

   //Defaults
   ItemSelected:=0;
   EnableControl:= False;
   LoadFirmware.Enabled:= False;
   LoadingStatus.Caption:='';

   //Make sure that Information page is selected since some pages depend on model and version.
   DataNoteBook.PageIndex:=0;

   //Hide normally unused pages
   DataNoteBook.Page[4].TabVisible:=False; //Datalogging page
   DataNoteBook.Page[6].TabVisible:=False; //GPS page
   DataNoteBook.Page[9].TabVisible:=False; //Vector page

   if FoundDevices.Count>0 then begin

      ItemSelected:=FoundDevices.ItemIndex;

      { Check that an item is selected. }
      if (ItemSelected>-1) then begin
        DataNoteBook.Enabled:=True;
        FoundDevices.Selected[ItemSelected]:=True;
        ClearResults();
        StatusMessage(Format('ItemSelected: row %d.',[ItemSelected]));
           if FoundDevicesArray[ItemSelected].Hardware = 'USB' then begin
             SelectedHardwareID:=FoundDevicesArray[ItemSelected].SerialNumber;
             USBSerialNumber.Text:=SelectedHardwareID;
             USBPort.Text:=FoundDevicesArray[ItemSelected].Connection;
             CommNotebook.PageIndex:=0;
             Application.ProcessMessages;
             SelectedInterface:='USB';
             SelectedPort:=USBPort.Text;
             StatusMessage(Format('USB SN: %s Device: %s has been selected.',[SelectedHardwareID, SelectedPort]));
           end;
           if ((FoundDevicesArray[ItemSelected].Hardware = 'Eth') or (FoundDevicesArray[ItemSelected].Hardware = 'WiFi')) then begin
             SelectedHardwareID:=FoundDevicesArray[ItemSelected].SerialNumber;
             EthernetMAC.Text:=SelectedHardwareID;
             SelectedIP:=FoundDevicesArray[ItemSelected].Connection;
             EthernetIP.Text:=SelectedIP;
             EthernetPort.Text:='10001';
             CommNotebook.PageIndex:=1;
             Application.ProcessMessages;
             SelectedInterface:=FoundDevicesArray[ItemSelected].Hardware;
             SelectedPort:=EthernetPort.Text;
             bXPortDefaults.Enabled:=True;
             StatusMessage(Format('%s MAC: %s Device: %s has been selected.',[SelectedInterface,SelectedHardwareID, SelectedIP]));
           end;
           { Highlight the selected device. }
              //FoundDevices.Selected[ItemSelected]:=True;

              { Enable controls if a device is selected. }
              EnableControl:= (ItemSelected>-1);

              LoadFirmware.Enabled:=((not(FirmwareFile.Text='')) and (FoundDevices.SelCount>0));
         end
        else
        if CommNotebook.PageIndex=2 then begin  {RS232 selected}
          SelectedInterface:='RS232';
          SelectedPort:=RS232PortName;
          ClearResults();
          EnableControl:= True;
        end;


   end;

   EnableControls(EnableControl);

   end; //end of checking if getting version or getting reading
  finally
      IsInside:=False;
  end;

end;

procedure TForm1.EnableControls(EnableControl:Boolean);
begin
    VersionButton.Enabled:=EnableControl;
    RequestButton.Enabled:=EnableControl;
    LogOneRecordButton.Enabled:=EnableControl;
    LogContinuousButton.Enabled:=EnableControl;
    GetCalInfoButton.Enabled:=EnableControl;
    LogCalInfoButton.Enabled:=EnableControl;
    LCOSet.Enabled:=EnableControl;
    LCTSet.Enabled:=EnableControl;
    DCPSet.Enabled:=EnableControl;
    DCTSet.Enabled:=EnableControl;
    ITiERButton.Enabled:=EnableControl;
    ITiRButton.Enabled:=EnableControl;
    IThERButton.Enabled:=EnableControl;
    IThRButton.Enabled:=EnableControl;
    GetReportInterval.Enabled:=EnableControl;
end;

procedure TForm1.FoundDevicesClick(Sender: TObject);
begin
     SelectDevice;
end;
procedure TForm1.FoundDevicesSelectionChange(Sender: TObject; User: boolean);
begin
     //SelectDevice;
end;


procedure TForm1.StartSimulationClick(Sender: TObject);
var
  Period, PeriodMin, PeriodMax, PeriodStep:Int64; //nS
  Temperature, TemperatureMin, TemperatureMax, TempStep:Integer; //ADC
  Verbose:Boolean;
begin
   SimEnable:=True;

   Verbose:=SimVerbose.Checked;

  { Period of sensor in counts, counts occur at a rate of 460.8 kHz (14.7456MHz/32).
    Start simulation range and log to a text file or plot on a chart. }

    //Todo : start frequency gers messed up eg. 450000 -> 450045
    //  because of conversion to period and loss of decimal point.

  PeriodMin:=StrToInt64('1000000000') div StrToInt64(SimFreqMax.Text);
  PeriodMax:=StrToInt64(SimPeriodMax.Text) * StrToInt64('1000000000');
  if SimTimingDiv.Value >0 then
    PeriodStep:=(PeriodMax - PeriodMin) div (SimTimingDiv.Value)
  else
    PeriodStep:=0;

  SimResults.Lines.Clear;

  if Verbose then begin
      SimResults.Lines.Add('===============================');
      SimResults.Lines.Add(Format('  Period Min: %d ns',[PeriodMin]));
      SimResults.Lines.Add(Format('  Period Max: %d ns',[PeriodMax]));
      if SimTimingDiv.Value >0 then
         SimResults.Lines.Add(Format(' Period Step: %d ns',[PeriodStep]));
      SimResults.Lines.Add('------------------------------');
  end;

  TemperatureMin:=StrToInt(SimTempMin.Text);
  TemperatureMax:=StrToInt(SimTempMax.Text);
  if SimTempDiv.Value > 0 then
     TempStep:=(TemperatureMax - TemperatureMin) div (SimTempDiv.Value)
  else
     TempStep:=0;

  if Verbose then begin
      SimResults.Lines.Add(Format('  Temperature Min: %d ADC',[TemperatureMin]));
      SimResults.Lines.Add(Format('  Temperature Max: %d ADC',[TemperatureMax]));
      if SimTempDiv.Value > 0 then
         SimResults.Lines.Add(Format(' Temperature Step: %d ADC',[TempStep]));
      SimResults.Lines.Add('------------------------------');
  end;

  Temperature:=TemperatureMin;
  while ((Temperature <= TemperatureMax) and SimEnable) do begin
    Period:=PeriodMin;
    while ((Period <= PeriodMax) and SimEnable) do begin
          Application.ProcessMessages;
           if Verbose then
             begin
              SimResults.Lines.Add(Format('     Period: %d ns',[Period]));
              SimResults.Lines.Add(Format('  Frequency: %d Hz',[(StrToInt64('1000000000') div  Period)]));
              SimResults.Lines.Add(Format('Temperature: %d ADC',[Temperature]));
              SimResults.Lines.Add(' ');
             end;

          //Send request and Parse reply
          SimResults.Lines.Add(sendget(Format(
             'S%10.10d,%10.10d,%05.05dx',
             [(StrToInt64('4608')*Period div StrToInt64('10000000')),
              (StrToInt64('1000000000') div Period),
              Temperature])));
          if SimTimingDiv.Value > 0 then
             Inc(Period, PeriodStep)
          else
             Break;
    end;
    if SimTempDiv.Value > 0 then
       Inc(Temperature, TempStep)
    else
       Break;
  end;

end;

procedure TForm1.StopResettingClick(Sender: TObject);
begin
  ResetContinuous:=False;
end;

procedure TForm1.StopSimulationClick(Sender: TObject);
begin
     SimEnable:=False;
end;

//RecordNumber value starts at 1 (as displayed on screen)
procedure TForm1.LogRecordGet(RecordNumber:LongInt);
var
   Attr1: TtkTokenKind;
   Voltage: Real;
   pieces: TStringList;
   DesiredPieces: Integer;
   FirstRecordIndicator:Integer;
begin
     //default
     Voltage:=0.0;

     //Define number of fields. Do not include first record indicator here.
     case SelectedModel of
       model_DL  : DesiredPieces:=5;
       model_DLS : DesiredPieces:=6;
       model_V   : DesiredPieces:=12;
     end;

     if StrToInt(SelectedFeature)>=49 then // feature 49 and above have 1st record indicator
       Inc(DesiredPieces);

     LimitInteger(RecordNumber, 1, DLEStorageCapacity);

     pieces := TStringList.Create;
     pieces.Delimiter := ',';
     pieces.StrictDelimiter := True; //Do not parse spaces

     // create highlighter
     Highlighter:=TSynPositionHighlighter.Create(Self);

     // add some attributes
     Attr1:=Highlighter.CreateTokenID('Attr1',clWhite,clRed,[]);

     // Get number of stored records
     LogUpdateLogPointer();

     if DLEStoredRecords>0 then begin
         LogRecordResult.Clear;
         pieces.DelimitedText := sendget(Format('L4%010.10dx',[RecordNumber-1]));
         if (pieces.Count>=DesiredPieces) then begin

             //Get first record indicator
             if StrToInt(SelectedFeature)>=49 then begin
               if SelectedModel=model_V then
                 FirstRecordIndicator:=StrToInt(pieces.Strings[12])
               else
                 FirstRecordIndicator:=StrToInt(pieces.Strings[5]);
             end;

             LogRecordResult.Append(Format('     Record: %d',[RecordNumber]));
             LogRecordResult.Lines.Append(Format('   UTC Date: %s',[FixDate(pieces.Strings[1])]));
             LogRecordResult.Lines.Append(Format('    Reading:   %4.2fmpsas',[StrToFloatDef(pieces.Strings[2],0,FPointSeparator)]));
             LogRecordResult.Lines.Append(Format('Temperature:  %2.1fC',[StrToFloatDef(AnsiLeftStr(pieces.Strings[3],Length(pieces.Strings[3])-1),0,FPointSeparator)]));

             Voltage:=(2.048 + (3.3 * StrToFloatDef(pieces.Strings[4],0,FPointSeparator))/256.0);
             LogRecordResult.Lines.Append(Format('    Voltage:   %1.2fV',[Voltage]));

             // define highlighted areas
             if (Voltage < 4.0) then
                  Highlighter.AddToken(4,30,Attr1)
             else
                 Highlighter.AddToken(4,30,tkText);

             LogRecordResult.Highlighter:=Highlighter;  // use highlighter

             if SelectedModel=model_V then begin
               Ax:= -1.0 * StrToFloatDef(pieces.Strings[5],0);
               Ay:=        StrToFloatDef(pieces.Strings[6],0);
               Az:=        StrToFloatDef(pieces.Strings[7],0);
               NormalizeAccel(); //Compute acceleration values (In the future, this may be done inside the PIC)
               //LogRecordResult.Lines.Append(Format('   Altitude:  %4.0f°',[radtodeg(arcsin(-1.0*Ax1))]));
               LogRecordResult.Lines.Append(Format('   Altitude:  %4.1f°',[ComputeAltitude(Ax1, Ay1, Az1)]));

               Mx:=        StrToFloatDef(pieces.Strings[ 8],0);
               My:= -1.0 * StrToFloatDef(pieces.Strings[ 9],0);
               Mz:= -1.0 * StrToFloatDef(pieces.Strings[10],0);
               NormalizeMag();
               ComputeAzimuth();
               Heading:=radtodeg(arctan2(-1*Mz2,Mx2))+180;
               LogRecordResult.Lines.Append(Format('    Azimuth:  %4.0f°',[Heading]));
               LogRecordResult.Lines.Append(Format('  Vibration: %5d',[StrToIntDef(pieces.Strings[11],0)]));
             end;

             if StrToInt(SelectedFeature)>=49 then begin
               if FirstRecordIndicator=1 then
                  LogRecordResult.Lines.Append('       Type: Subsequent')
               else
                  LogRecordResult.Lines.Append('       Type: Initial');
             end;

             //Snow factor
             //Optional flag 0/1 indicates if snow factor exists in record.
             if ((SelectedModel=model_DLS) and (pieces.Count>=DesiredPieces)) then begin
               case pieces.Strings[6] of
                  '0': ;
                  '1': begin
                         LogRecordResult.Lines.Append(Format('   Std Lin.: %u',[StrToDWordDef(pieces.Strings[7],0)]));
                         LogRecordResult.Lines.Append(Format('   Snow rdg:   %4.2fmpsas',[StrToFloatDef(pieces.Strings[8],0,FPointSeparator)]));
                         LogRecordResult.Lines.Append(Format('  Snow Lin.: %u',[StrToDWordDef(pieces.Strings[9],0)]));
                       end;
                  end;
               end;

           end
         else
              LogRecordResult.Append(Format('result pieces should be %d, but is %d',[DesiredPieces,pieces.Count]));

     end
       else begin
           LogRecordResult.Clear;
           LogRecordResult.Append('No records stored yet.');
         end;
end;

procedure TForm1.TrickleOnButtonClick(Sender: TObject);
begin
  SendGet('LBx');
  StatusMessage('Trickle On');
end;

procedure TForm1.TrickleOffButtonClick(Sender: TObject);
begin
  SendGet('Lbx');
  StatusMessage('Trickle Off');
end;

procedure TriggerModeChange();
var
  result:String;
  TriggerModeNumber, CurrentTriggerModeNumber:Integer;
begin

  StatusMessage('DL Trigger mode button changed.');

  //Only set mode if received mode is different than displayed mode. This will save on EEPROM write life.

  with Form1 do begin
    //Read log trigger mode
    CurrentTriggerModeNumber:=StrToIntDef(AnsiMidStr(sendget('Lmx'),4,1),0);
    if (TriggerComboBox.ItemIndex <> CurrentTriggerModeNumber) then begin
      result:=SendGet('LM'+IntToStr(TriggerComboBox.ItemIndex)+'x');
      TriggerModeNumber:=StrToInt(AnsiMidStr(result,4,1));
      StatusMessage('Set trigger mode to value '+ IntToStr(TriggerModeNumber));
      // Check result
      case TriggerModeNumber of
         0..7: TriggerComboBox.ItemIndex:=TriggerModeNumber;
         else begin
          TriggerComboBox.ItemIndex:=0;
          StatusMessage(Format('Invalid trigger mode: %d',[TriggerModeNumber]));
        end;
      end;
      case TriggerModeNumber of
         1..2: Begin //Show Seconds or Minutes selection
                 DLSecMinPages.Visible:=True;
                 DLSecMinPages.TabIndex:=TriggerModeNumber-1;
            end;
         else DLSecMinPages.Visible:=False;
      end;

    end
    else begin
         StatusMessage(Format('Current trigger mode: %d = Desired trigger mode %d',[CurrentTriggerModeNumber,TriggerModeNumber]));
    end;

    EstimateBatteryLife;

  end;
end;

procedure TForm1.TriggerComboBoxChange(Sender: TObject);
begin
  StatusMessage('Button pressed: Fata Logging: Trigger Combo Box');
  TriggerModeChange();
end;

procedure TForm1.TriggerGroupSelectionChanged(Sender: TObject);
begin
     TriggerModeChange();
end;

procedure TForm1.LCOSetClick(Sender: TObject);
var
   pieces: TStringList;
begin
  StatusMessage('Light calibration offset set.');
  pieces := TStringList.Create;
  pieces.Delimiter := ',';
  pieces.DelimitedText:=SendGet(Format('zcal5%sx',[FormatFloat('00000000.00',StrToFloatDef(LCODes.text,0,FPointSeparator))]));
  if ((pieces.Count>=3) and (pieces.Strings[0]='z')) then
     LCOAct.Text:=Format('%.2f',[StrToFloatDef(AnsiReplaceStr(pieces.Strings[2],'m',''),0,FPointSeparator)]);
end;

procedure TForm1.LCTSetClick(Sender: TObject);
var
   pieces: TStringList;
begin
  StatusMessage('Light calibration temperature set.');
  pieces := TStringList.Create;
  pieces.Delimiter := ',';
  pieces.DelimitedText := SendGet(Format('zcal6%sx',[FormatFloat('00000000.00',StrToFloatDef(LCTDes.text,0,FPointSeparator))]));
  if ((pieces.Count>=3) and (pieces.Strings[0]='z')) then
     LCTAct.Text:=Format('%.1f',[StrToFloatDef(AnsiReplaceStr(pieces.Strings[2],'C',''),0,FPointSeparator)]);
end;

procedure TForm1.DCPSetClick(Sender: TObject);
var
   pieces: TStringList;
begin
  StatusMessage('Dark calibration period set.');
  pieces := TStringList.Create;
  pieces.Delimiter := ',';
  pieces.DelimitedText := SendGet(Format('zcal7%sx',[FormatFloat('0000000.000',StrToFloatDef(DCPDes.text,0,FPointSeparator))]));
  if ((pieces.Count>=3) and (pieces.Strings[0]='z')) then
     DCPAct.Text:=Format('%.3f',[StrToFloatDef(AnsiReplaceStr(pieces.Strings[2],'s',''),0,FPointSeparator)]);
end;

procedure TForm1.DCTSetClick(Sender: TObject);
var
   pieces: TStringList;
begin
  StatusMessage('Dark calibration temperature set.');
  pieces := TStringList.Create;
  pieces.Delimiter := ',';
  pieces.DelimitedText := SendGet(Format('zcal8%sx',[FormatFloat('00000000.00',StrToFloatDef(DCTDes.text,0,FPointSeparator))]));
  if ((pieces.Count>=3) and (pieces.Strings[0]='z')) then
     DCTAct.Text:=Format('%.1f',[StrToFloatDef(AnsiReplaceStr(pieces.Strings[2],'C',''),0,FPointSeparator)]);
end;

procedure TForm1.FormShow(Sender: TObject);
var
  i: Integer;
  str: String;
  StartupParmeterPointer:Integer=1;
  pieces: TStringList;
begin
  if Screen.MonitorCount > 0 then ;  // move this here from FormCreate

  Application.ProcessMessages;
  StatusMessage('UDM Version: '+UDMversion); //program version

  //Operating system details
  {$if defined(MSWindows)}
    OperatingSystemString:='Operating system: Windows';
  {$ifend}
  {$if defined(Linux)}
    OperatingSystemString:='Operating system: Linux';
  {$ifend}
  {$ifdef Darwin}
    OperatingSystemString:='Operating system: MacOSX (Darwin)';
  {$ifend}
  StatusMessage(OperatingSystemString);


  //Indicate start time
  StatusMessage('Started at: '+FormatDateTime('yyyy-mm-dd hh:nn:ss',Now()));

  //Show command line with options
  str:='Started as: ';
  for i:=0 to Paramcount do
      str:=str + ' ' + ParamStr(i);
  StatusMessage(str);

  {Read Startup options.}
  StartUpSettings:= vConfigurations.ReadString('StartUp','Settings','');
  LockCommCheckBox.Checked:= vConfigurations.ReadBool('CommSettings','LockComm');

  pieces := TStringList.Create;
  pieces.Delimiter := ' ';
  pieces.DelimitedText:=StartUpSettings;
  StartupParamcount:=pieces.Count;
  StartupParamStrings:=pieces.ToStringArray;
  pieces.Destroy;
  if StartupParamcount>0 then StatusMessage('StartupOptions: '+StartUpSettings);

  //Indicate configuration path
  StatusMessage('ConfigFilePath: '+ConfigFilePath);

  //Search for attached devices or not
  if (not ParameterCommand('-N')) then
     FindAllDevices('');

end;

procedure TForm1.FormActivate(Sender: TObject);

begin
  {If another window was supposed to be shown via command line action, then put it into focus.}
  If InitialLoad then begin

    {Disable this code after initial loading}
    InitialLoad:=False;

    {Check if GPS logging should be performed}
    if (ParameterCommand('-GPSL')) then begin
        GPSLoggingEnabled:= True;
    end;

    {Check if Log continuous record command line option is selected}
    if (ParameterCommand('-LCR') or ParameterCommand('-LCGRS')) then begin
      LogContinuousButtonClick(Form1);
    end;

    {Check if Log continuous was interrupted by a crash}
    LCLoggingUnderway:=vConfigurations.ReadBool('LogContinuousPersistence', 'LoggingUnderway', False);
    if (LCLoggingUnderway and (SelectedHardwareID<>'')) then
        LogContinuousButtonClick(Form1);

    {Check if plotter view should be initially displayed}
    if ParameterCommand('-P') then
        plotter.PlotterForm.show;

    {Check if DL retreive view should be initially displayed}
    if ParameterCommand('-DLR') then begin
       LogUpdateLogPointer();
       dlretrieve.VectorPlotOverride:=true;
       dlretrieve.DLRetrieveForm.ShowModal;
    end;

    {Check if DL Retreive Send Shutdown}
    if ParameterCommand('-DLRSS') then begin
      StatusMessage('-DLRSS command line paramater selected to retrieve readings then shutdown.');
      StatusMessage('-DLRSS: read selected meter details.');
      GetReading;
      StatusMessage('-DLRSS: Getting time zone for the selected meter.');
      dlheader.DLHeaderForm.ReadINI();
      StatusMessage('-DLRSS: Retrieve all DL readings.');
      dlretrieve.DLRetrieveForm.BlockClick(nil);
      StatusMessage('-DLRSS: Exiting as requested by.');
      Close;
    end;

    {Check if DL Retreive Erase Send Shutdown}
    if ParameterCommand('-DLRESS') then begin
      StatusMessage('-DLRESS command line paramater selected to retrieve readings then shutdown.');

      StatusMessage('-DLRESS: read selected meter details.');
      GetReading;

      StatusMessage('-DLRESS: Getting time zone for the selected meter.');
      dlheader.DLHeaderForm.ReadINI();

      StatusMessage('-DLRESS: Retrieve all DL readings.');
      dlretrieve.DLRetrieveForm.BlockClick(nil);

      StatusMessage('-DLRESS: Erase all DL readings.');
      dlerase.FormDLErase.EraseAllButtonClick(nil);

      StatusMessage('-DLRESS: Exiting as requested.');
      Close;
    end;

    {Check if Concatenate tool should be initilally displayed}
    if ParameterCommand('-TCA') then begin
      concattool.ConcatToolForm.Show;
    end;

    {Check if Cloud removal / Milky Way tool should be initilally displayed}
    if (ParameterCommand('-TCM') or ParameterCommand('-TCMR'))then begin
      CloudRemUnit.CloudRemMilkyWay.Show;
    end;

  end;

end;

procedure TForm1.ConfigBrowserMenuItemClick(Sender: TObject);
begin
  ConfigBrowserForm.ShowModal;
end;

procedure TForm1.GetCalInfo();
var
   pieces: TStringList;
   ResultCount:Integer;
begin
  //Clear out existing results
  LCOAct.clear;
  LCTAct.clear;
  DCPAct.clear;
  DCTAct.clear;

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

  pieces.DelimitedText := SendGet('cx');
  //Check size of array. 5 Sections normally, 6 sections when checksum is sent.
  //   and that returned value is "info".
  if ((pieces.Count>=6) and (pieces.Strings[0]='c')) then
    begin
         ResultCount:=1;
    end
  else //Try once again. Sometimes dual responses get through here.
      begin
      pieces.DelimitedText := SendGet('cx');
       if ((pieces.Count>=6) and (pieces.Strings[0]='c')) then
            ResultCount:=2
       else
          begin
            StatusMessage('GetCalInfo: Could not get calibration information on second try.');
          end;
      end;

  if ResultCount>0 then begin
     LCOAct.Text:=Format('%.2f',[StrToFloatDef(AnsiReplaceStr(pieces.Strings[1],'m',''),0,FPointSeparator)]);
     LCTAct.Text:=Format('%.1f',[StrToFloatDef(AnsiReplaceStr(pieces.Strings[3],'C',''),0,FPointSeparator)]);
     DCPAct.Text:=Format('%.3f',[StrToFloatDef(AnsiReplaceStr(pieces.Strings[2],'s',''),0,FPointSeparator)]);
     DCTAct.Text:=Format('%.1f',[StrToFloatDef(AnsiReplaceStr(pieces.Strings[5],'C',''),0,FPointSeparator)]);
  end;
end;

procedure TForm1.GetCalInfoButtonClick(Sender: TObject);
begin
  GetCalInfo();
end;

// This function is deprecated, use "Log Cal" button on Configuration page.
procedure TForm1.LogCalInfoButtonClick(Sender: TObject);
var
  LogCalFile: TextFile;
begin
     //Example of logged calibration data:
     //FTF4FOV0
     //i,00000004,00000006,00000020,00001839
     //c,00000019.96m,0000151.490s, 019.6C,00000008.71m, 021.2C

     begin
          AssignFile(LogCalFile,(appsettings.LogsDirectory+FormatDateTime('yyyymmdd-hhnnss',Now())+'.cal'));
          try
              Rewrite(LogCalFile); //create new file
              if CommNotebook.PageIndex=0 then //USB device
                 Writeln(LogCalFile,USBSerialNumber.Text);
              if CommNotebook.PageIndex=1 then //Ethernet device
                 Writeln(LogCalFile,EthernetMAC.Text);
              Writeln(LogCalFile,sendget('ix'));
              Writeln(LogCalFile,sendget('cx'));
          except
              StatusMessage('ERROR! IORESULT: ' + IntToStr(IOResult) + ' during LogCalInfoButtonClick');
          end;
          CloseFile(LogCalFile);
     end
end;

procedure TForm1.ParseReportInterval(Response: String);
var
   pieces: TStringList;
begin

     StatusMessage('ParseReportInterval response: '+Response);

    //Clear out existing results
    ITiE.clear;
    ITiR.clear;
    IThE.clear;
    IThR.clear;

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

    pieces.DelimitedText := Response;
    if (pieces.Count=4) then
      begin
        RITiE:=StrToIntDef(AnsiReplaceStr(pieces.Strings[0],'s',''),0); //Report Interval Time in EEPROM
        RITiR:=StrToIntDef(AnsiReplaceStr(pieces.Strings[1],'s',''),0); //Report Interval Time in RAM
        RIThE:=StrToFloatDef(AnsiReplaceStr(pieces.Strings[2],'m',''),0,FPointSeparator); //Report Interval Threshold in EEPROM
        RIThR:=StrToFloatDef(AnsiReplaceStr(pieces.Strings[3],'m',''),0,FPointSeparator); //Report Interval Threshold in RAM

        ITiE.Text:=Format('%d',[RITiE]);
        ITiR.Text:=Format('%d',[RITiR]);
        IThE.Text:=Format('%.2f',[RIThE]);
        IThR.Text:=Format('%.2f',[RIThR]);
      end;
    if Assigned(pieces) then FreeAndNil(pieces);

end;

procedure TForm1.GetReportIntervalClick(Sender: TObject);
begin
  SendGet('rx');//to purge old interval reports if any
  ParseReportInterval(SendGet('Ix'));
end;

procedure TForm1.IThERButtonClick(Sender: TObject);
begin
     SendGet('rx');//to purge old interval reports if any
     ParseReportInterval(SendGet(Format('T%sx',[FormatFloat('00000000.00',StrToFloatDef(IThDes.text,0,FPointSeparator))])));
end;

procedure TForm1.IThRButtonClick(Sender: TObject);
begin
     SendGet('rx');//to purge old interval reports if any
     ParseReportInterval(SendGet(Format('t%sx',[FormatFloat('00000000.00',StrToFloatDef(IThDes.text,0,FPointSeparator))])));
end;

procedure TForm1.ITiERButtonClick(Sender: TObject);
begin
  SendGet('rx');//to purge old interval reports if any
  ParseReportInterval(SendGet(Format('P%sx',[FormatFloat('0000000000',StrToFloatDef(ITiDes.text,0,FPointSeparator))])));
end;

procedure TForm1.ITiRButtonClick(Sender: TObject);
begin
  SendGet('rx');//to purge old interval reports if any
  ParseReportInterval(SendGet(Format('p%sx',[FormatFloat('0000000000',StrToFloatDef(ITiDes.text,0,FPointSeparator))])));
end;


procedure TForm1.VersionButtonClick(Sender: TObject);
begin
     StatusMessage('Button pressed: Information: Version');
     GetVersion;
end;

procedure TForm1.ViewConfigMenuItemClick(Sender: TObject);
begin
   DataNoteBook.Page[5].TabVisible:=ViewConfigMenuItem.Checked; //Configuration
end;

procedure TForm1.ViewLogMenuItemClick(Sender: TObject);
begin
     Form5.Show;
     Form5.SynEdit1.Clear;
     ViewingLog:=True;
     Form5.SynEdit1.Lines.AddStrings(ViewedLog);
end;

procedure TForm1.ViewSimMenuItemClick(Sender: TObject);
begin
   DataNoteBook.Page[8].TabVisible:=ViewSimMenuItem.Checked; //Simulation
end;

procedure TForm1.VThresholdChange(Sender: TObject);
begin
    VThreshold.Color:=clFuchsia;
end;

procedure TForm1.VThresholdSetClick(Sender: TObject);
begin
  StatusMessage('Button pressed: Data Logging: Vibration Threshold Set');
  SendGet(Format('vT%06.5dx',[(StrToIntDef(VThreshold.Text,0))]));
  { TODO 2 : check result }
  VThreshold.Color:=clWindow;
end;

{$IFDEF Unix}
Procedure MySigPipe(Sig : Longint; Info : PSigInfo; Context : PSigContext);cdecl;
Begin
ShowMessage('signal was triggered');
end;

procedure TForm1.InstallSigHandler;
Var
  act : SigactionRec;
begin
  //fpSigaction(SIGTERM,nil,@act);//the old action is stored there
  act.sa_handler:=@MySigPipe;
  //act.sa_flags:=act.sa_flags or SA_SIGINFO;
  //act.sa_flags:=act.sa_flags;
  //fpSigaction(SIGPIPE,@act,nil);
  fpSigaction(SIGUSR1,@act,nil);//then the new action for signal Sig is taken from it
end;
{$ENDIF}

{ TODO : Consider using standard EnsureRange }
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;

{Removes multiple slashes from string}
function RemoveMultiSlash(Input: String): String;
var
  tempstr:String;
begin

tempstr:=Input;
while Pos('//',tempstr)>0 do begin
  tempstr := StringReplace(tempstr,'//','/',[rfReplaceAll]);
end;
while Pos('\\',tempstr)>0 do begin
  tempstr := StringReplace(tempstr,'\\','\',[rfReplaceAll]);
end;

Result:=tempstr;

end;


initialization
  {$I unit1.lrs}



finalization

end.

