unit UPSUnitu;

interface
uses
  SysUtils, Classes, UtlType, WinUtl, PropUtl;

const
  DefUPSLogFileName: string = 'C:\Program Files\APC\PowerChute Business Edition\agent\datalog';

(* example datalog file content
Date and Time	Maximum Line Voltage	Minimum Line Voltage	Line Voltage	Output Voltage	Battery Voltage	Output Frequency	UPS Load	UPS Internal Temperature	Probe 1 Temperature	Probe 1 Humidity	Probe 2 Temperature	Probe 2 Humidity
03/17/2005 14:12:44	231.8	231.8	231.8	231.8	27.13	50.00	21.4	39.1	N/A	N/A	N/A	N/A
03/17/2005 14:32:44	233.2	230.4	231.8	231.8	27.13	50.00	21.4	39.6	N/A	N/A	N/A	N/A
03/17/2005 14:52:44	231.8	228.9	228.9	230.4	27.13	50.00	31.8	39.6	N/A	N/A	N/A	N/A
03/17/2005 14:56:18	231.8	228.9	230.4	230.4	27.13	50.00	31.8	39.6	N/A	N/A	N/A	N/A
03/17/2005 16:18:18	18.7	0.0	0.0	229.3	26.05	50.00	13.0	38.2	N/A	N/A	N/A	N/A
03/17/2005 16:19:18	18.7	0.0	0.0	229.3	25.51	50.00	22.7	37.8	N/A	N/A	N/A	N/A
03/17/2005 16:20:18	18.7	0.0	0.0	229.3	25.51	50.00	22.7	36.9	N/A	N/A	N/A	N/A
03/17/2005 16:21:18	18.7	0.0	0.0	229.3	25.51	50.00	22.7	36.0	N/A	N/A	N/A	N/A
03/17/2005 16:22:18	18.7	0.0	0.0	229.3	25.51	50.00	22.7	35.1	N/A	N/A	N/A	N/A
03/17/2005 16:59:18	17.2	0.0	0.0	229.3	25.38	50.00	16.2	29.2	N/A	N/A	N/A	N/A
03/17/2005 17:00:18	17.2	0.0	0.0	229.3	25.24	50.00	26.0	29.2	N/A	N/A	N/A	N/A
03/17/2005 17:01:18	17.2	0.0	0.0	230.4	25.11	50.00	25.3	29.2	N/A	N/A	N/A	N/A
03/17/2005 17:02:18	17.2	0.0	0.0	230.4	25.11	50.00	25.3	29.2	N/A	N/A	N/A	N/A
03/17/2005 17:03:18	17.2	0.0	0.0	230.4	25.11	50.00	25.3	29.2	N/A	N/A	N/A	N/A
03/17/2005 17:49:20	234.7	231.8	231.8	231.8	27.27	50.00	0.0	28.8	N/A	N/A	N/A	N/A
03/17/2005 17:50:20	233.2	231.8	233.2	233.2	27.27	50.00	0.0	28.8	N/A	N/A	N/A	N/A
*)
type
  TUPSUnit = class(TComponent)
  private
    FIsInCheck: boolean;
    { 0 if no power down detected yet, otherwise the datetime when power down
      state was detected (and still exists). Set to zero if power on state detected. }
    FPowerDownSince: TDateTime;
    FLastCheckTime: TDateTime;
    FInLineTime: TDateTime;
    FLastInLineTime: TDateTime;
    { Voltage below which the UPS starts working }
    FPowerDownVoltageLimit: integer;
    { Set True if the UPS is present and should be checked. }
    FIsPresent: boolean;
    FChanged: boolean;

    { After how many minutes without power the sequence should be stopped. }
    FShutDownAfterMinutes: integer;
    { How often (in minutes) to check the state of the UPS }
    FStateCheckInterval: integer;
    FUPSLogFileName: shortstring;

    function GetIsPowerDown: boolean;
    function GetShouldShutDown: boolean;
    //function GetIsPresent: boolean;
    function GetPowerDownPeriodStr: string;
    function GetChanged: boolean;
    function GetNextCheckTimeStr: string;
  public
    constructor Create(Owner: TComponent); override;
    destructor Destroy; override;

    { Check what is the state of the UPS. If UPSLogFileName is empty no checks
      are done. Should be called if IsPresent is true. }
    procedure Check;

    { Returns true if the power is down right now (during the last Check) }
    property IsPowerDown: boolean read GetIsPowerDown;
    { Returns true if the power has been down at least
      for ShutDownAfterMinutes interval }
    property ShouldShutDown: boolean read GetShouldShutDown;
    property PowerDownPeriodStr: string read GetPowerDownPeriodStr;
    property NextCheckTimeStr: string read GetNextCheckTimeStr;

    { Returns true if new ups log line was read since last read of Changed }
    property Changed: boolean read GetChanged;

  published
    property IsPresent: boolean read FIsPresent write FIsPresent;
    property UPSLogFileName: shortstring read FUPSLogFileName write FUPSLogFileName;
    property ShutDownAfterMinutes: integer read FShutDownAfterMinutes write FShutDownAfterMinutes;
    property StateCheckInterval: integer read FStateCheckInterval write FStateCheckInterval;
    property PowerDownVoltageLimit: integer read FPowerDownVoltageLimit write FPowerDownVoltageLimit;
  end;

function UPSUnit: TUPSUnit;

implementation
var
  FUPSUnit: TUPSUnit = nil;

function UPSUnit: TUPSUnit;
begin
  if FUPSUnit = nil then
    FUPSUnit := TUPSUnit.Create(nil);
  Result := FUPSUnit;
end;

const
  DayInMinutes = 24 * 60;
  MinLogLineLen = 50;
  DefPowerDownVoltageLimit = 150;

constructor TUPSUnit.Create(Owner: TComponent);
begin
  inherited;

  FShutDownAfterMinutes := 30;
  FStateCheckInterval := 5;
  FUPSLogFileName := DefUPSLogFileName;
  FPowerDownVoltageLimit := DefPowerDownVoltageLimit;
  {proputl}
  ClassReadWriteIniFile(Self, 0, '', true);
end;

destructor TUPSUnit.Destroy;
begin
  ClassReadWriteIniFile(Self, 0, '', false);
  inherited;
end;

function TUPSUnit.GetChanged: boolean;
begin
  Result := FChanged;
  FChanged := false;
end;

function TUPSUnit.GetIsPowerDown: boolean;
begin
  Result := FPowerDownSince <> 0;
end;

function TUPSUnit.GetNextCheckTimeStr: string;
begin
  Result := TimeToStr(FLastCheckTime + FStateCheckInterval / DayInMinutes);
end;

function TUPSUnit.GetPowerDownPeriodStr: string;
begin
  if IsPowerDown then begin
    Result := TimeToStr(Now - FPowerDownSince);
  end else begin
    Result := '00:00:00';
  end;
end;

{function TUPSUnit.GetIsPresent: boolean;
begin
  Result := UPSLogFileName <> '';
end;}

function TUPSUnit.GetShouldShutDown: boolean;
begin {utltype winutl}
  Result := (FPowerDownSince <> 0) and ( (Now - FPowerDownSince) > (FShutDownAfterMinutes / DayInMinutes) )
end;

procedure TUPSUnit.Check;
var
  sl: TStringList;
  flds: TStringList;
  fs: TFileStream;
  i: integer;
  line: string;
const
  checkSize = 1000;
begin
  try
    if not IsPresent then
      exit;
    if UPSLogFileName = '' then
      exit;
    if (Now - FLastCheckTime) < (FStateCheckInterval / DayInMinutes) then
      exit;
    if FIsInCheck then
      exit;
    FIsInCheck := true;
    try
      try
        try
          sl := nil;
          flds := nil;
          fs := nil;
          FLastCheckTime := Now;
          sl := TStringList.Create;
          fs := TFileStream.Create(UPSLogFileName, fmOpenRead + fmShareDenyNone);
          if fs.Size > checkSize then begin
            fs.Position := fs.Size - checkSize;
          end;

          sl.LoadFromStream(fs);
          if sl.Count = 0 then
            exit;
          i := sl.Count;
          line := '';
          repeat
            dec(i);
            line := sl[i];
            if length(line) >= MinLogLineLen then
              break;
          until i = 0;
          if length(line) < MinLogLineLen then
            exit;
          if pos('Date', line) = 1 then
            exit;// no data, just header line
          flds := TStringList.Create;
          if not ParseLine([' ', #9], line, flds) then
            exit;
          //1234567890 12345678
          //03/17/2005 17:50:20	233.2	231.8	233.2	233.2	27.27	50.00	0.0	28.8	N/A	N/A	N/A	N/A
          //    0         1           2      3       4       5
          FInLineTime :=
             //function EncodeDate(Year, Month, Day: Word): TDateTime;
             //1234567890
             //03/17/2005
             EncodeDate(
                StrToInt(copy(flds[0], 7, 4)),
                StrToInt(copy(flds[0], 1, 2)),
                StrToInt(copy(flds[0], 4, 2)) )
             +
             //function EncodeTime(Hour, Min, Sec, MSec: Word):
             //12345678
             EncodeTime(
               StrToInt(copy(flds[1], 1, 2)),
               StrToInt(copy(flds[1], 4, 2)),
               StrToInt(copy(flds[1], 7, 2)),
               0);
          if FInLineTime = FLastInLineTime then
            exit;
          FChanged := true;
          FLastInLineTime := FInLineTime;
          if StrToFloat(flds[4]) < PowerDownVoltageLimit then begin
            // the power vxxx§§§§oltage is below the limit when the UPS starts to work
            if FPowerDownSince = 0 then begin
              FPowerDownSince := FLastInLineTime;
            end;
          end else begin
            FPowerDownSince := 0;
          end;

        finally
          sl.Free;
          flds.Free;
          fs.Free;
        end;
      except
        // some errors encountered, exception silently ignored
      end;
    finally
      FIsInCheck := false;
    end;
  except// just ignore any ups related exceptions
  end;
end;

initialization
  UPSUnit;
finalization
  FUPSUnit.Free;
end.
