diff --git a/applications/foobot/foobot_utility.pas b/applications/foobot/foobot_utility.pas index 643790075..cd1e4e609 100644 --- a/applications/foobot/foobot_utility.pas +++ b/applications/foobot/foobot_utility.pas @@ -40,7 +40,8 @@ const FOOBOT_DATA_LAST_URL = 'https://api.foobot.io/v2/device/%s/datapoint/%s/%s/%s/'; FOOBOT_DATA_START_FINISH_URL = 'https://api.foobot.io/v2/device/%s/datapoint/%s/%s/%s/'; - HIGHLOWMAX = 6; + + // Used throughout in arrays, counts etc. C_TIME = 0; C_PM = 1; C_TMP = 2; @@ -48,12 +49,11 @@ const C_CO2 = 4; C_VOC = 5; C_ALLPOLLU = 6; - C_NONE = 7; type - TDataFetchType = (dfLast, dfStartEnd); - TSensorType = (st_time, st_pm, st_tmp, st_hum, st_co2, st_voc, st_allpollu); - TAlertType = (at_high, at_low); + TDataFetchType = (dfLast, dfStartEnd); // FetchFoobotData + TSensorType = (st_time, st_pm, st_tmp, st_hum, st_co2, st_voc, st_allpollu); // Unused + TAlertType = (at_high, at_low); // TAlertRec TAlertRec = record AlertTriggered: boolean; @@ -78,6 +78,10 @@ function FetchFoobotData(DataFetchType: TDataFetchType = dfLast; // - also populates HighLow arrays function FoobotDataObjectToArrays: boolean; +// Sets internal FooBotTriggerArray which can be tested against in FoobotDataObjectToArrays +// aSensor use consts: C_PM,C_TMP etc. +function SetHighTrigger(const aSensor: integer; const aValue: variant): boolean; +function SetLowTrigger(const aSensor: integer; const aValue: variant): boolean; // Utility functions function ResetArrays: boolean; @@ -85,20 +89,21 @@ function ResetObjects: boolean; function ResetHighLows: boolean; function SaveHighLows: boolean; function LoadHighLows: boolean; +function SaveTriggers: boolean; +function LoadTriggers: boolean; var // Used to fetch server data HttpClient: TFPHTTPClient; - // Holds identity values for multiple Foobots + // Holds identity values for multiple Foobots (FoobotIdentityObject.FoobotIdentityList) FoobotIdentityObject: TFoobotIdentityObject; // Holds data for current Foobot FoobotDataObject: TFoobotDataObject; - + // Used in FetchAuthenticationKey sAuthenticationKey: string; - SensorType: TSensorType; // st_time, st_pm, st_tmp etc. // Boolean to enable/disable serialisation of HighLows. Default = TRUE SaveLoadHighLows: boolean; - // Boolean to enable/disable Trigger functions + // Boolean to enable/disable Trigger functions. Default = FALSE UseTriggers: boolean; // Used in data fetch TheCurrentFoobot: integer; @@ -114,10 +119,10 @@ var FoobotData_voc: array of integer; FoobotData_allpollu: array of double; // Set in FoobotDataObjectToArrays - FoobotDataHighs: array[0..HIGHLOWMAX] of variant; - FoobotDataLows: array[0..HIGHLOWMAX] of variant; - FoobotDataHighTimes: array[0..HIGHLOWMAX] of variant; - FoobotDataLowTimes: array[0..HIGHLOWMAX] of variant; + FoobotDataHighs: array[C_TIME..C_ALLPOLLU] of variant; + FoobotDataLows: array[C_TIME..C_ALLPOLLU] of variant; + FoobotDataHighTimes: array[C_TIME..C_ALLPOLLU] of variant; + FoobotDataLowTimes: array[C_TIME..C_ALLPOLLU] of variant; // [0=Low(at_low)..1=High(at_high), C_PM..C_ALLPOLLU] // Dynamic in case of future changes (e.g. more triggers) @@ -128,105 +133,217 @@ var implementation -function SaveHighLows: boolean; - // Save values to an INI data file +function SaveTriggers: boolean; + // To foobotmonitor.ini var sFoobotName: string; begin - if SaveLoadHighLows = False then - Exit(False); + Result:=FALSE; // assume failure + if FoobotIdentityObject.FoobotIdentityList.Count = 0 then Exit(FALSE); sFoobotName := FoobotIdentityObject.FoobotIdentityList[TheCurrentFoobot].Name; + if (sFoobotName = '') then + Exit(False); if not Assigned(HLINI) then HLINI := TIniFile.Create(ChangeFileExt(GetAppConfigFile(False), '.ini')); - // Store current Foobot info - HLINI.WriteInteger('Foobot', 'CurrentFoobot', TheCurrentFoobot); - HLINI.WriteString('Foobot', 'CurrentFoobotName', sFoobotName); - - // Particulates - HLINI.WriteFloat(sFoobotName, 'pmHigh', double(FoobotDataHighs[C_PM])); - HLINI.WriteDateTime(sFoobotName, 'pmHighTime', TDateTime(FoobotDataHighTimes[C_PM])); - HLINI.WriteFloat(sFoobotName, 'pmLow', double(FoobotDataLows[C_PM])); - HLINI.WriteDateTime(sFoobotName, 'pmLowTime', TDateTime(FoobotDataLowTimes[C_PM])); - // Temp - HLINI.WriteFloat(sFoobotName, 'tmpHigh', double(FoobotDataHighs[C_TMP])); - HLINI.WriteDateTime(sFoobotName, 'tmpHighTime', TDateTime(FoobotDataHighTimes[C_TMP])); - HLINI.WriteFloat(sFoobotName, 'tmpLow', double(FoobotDataLows[C_TMP])); - HLINI.WriteDateTime(sFoobotName, 'tmpLowTime', TDateTime(FoobotDataLowTimes[C_TMP])); - // Humidity - HLINI.WriteFloat(sFoobotName, 'humHigh', double(FoobotDataHighs[C_HUM])); - HLINI.WriteDateTime(sFoobotName, 'humHighTime', TDateTime(FoobotDataHighTimes[C_HUM])); - HLINI.WriteFloat(sFoobotName, 'humLow', double(FoobotDataLows[C_HUM])); - HLINI.WriteDateTime(sFoobotName, 'humLowTime', TDateTime(FoobotDataLowTimes[C_HUM])); - // CO2 - HLINI.WriteInteger(sFoobotName, 'co2High', integer(FoobotDataHighs[C_CO2])); - HLINI.WriteDateTime(sFoobotName, 'co2HighTime', TDateTime(FoobotDataHighTimes[C_CO2])); - HLINI.WriteInteger(sFoobotName, 'co2Low', integer(FoobotDataLows[C_CO2])); - HLINI.WriteDateTime(sFoobotName, 'co2LowTime', TDateTime(FoobotDataLowTimes[C_CO2])); - // Volatile Compounds - HLINI.WriteInteger(sFoobotName, 'vocHigh', integer(FoobotDataHighs[C_VOC])); - HLINI.WriteDateTime(sFoobotName, 'vocHighTime', TDateTime(FoobotDataHighTimes[C_VOC])); - HLINI.WriteInteger(sFoobotName, 'vocLow', integer(FoobotDataLows[C_VOC])); - HLINI.WriteDateTime(sFoobotName, 'vocLowTime', TDateTime(FoobotDataLowTimes[C_VOC])); - // All Pollution - HLINI.WriteFloat(sFoobotName, 'allpolluHigh', double(FoobotDataHighs[C_ALLPOLLU])); - HLINI.WriteDateTime(sFoobotName, 'allpolluHighTime', - TDateTime(FoobotDataHighTimes[C_ALLPOLLU])); - HLINI.WriteFloat(sFoobotName, 'allpolluLow', double(FoobotDataLows[C_ALLPOLLU])); - HLINI.WriteDateTime(sFoobotName, 'allpolluLowTime', - TDateTime(FoobotDataLowTimes[C_ALLPOLLU])); - Result := True; + try + // Store current Foobot triggers + with HLINI do + begin + WriteInteger('Foobot', 'CurrentFoobot', TheCurrentFoobot); + WriteString('Foobot', 'CurrentFoobotName', sFoobotName); + WriteFloat(sFoobotName, 'pmTriggerHigh', + double(FooBotTriggerArray[Ord(at_high), C_PM])); + WriteFloat(sFoobotName, 'pmTriggerLow', + double(FooBotTriggerArray[Ord(at_Low), C_PM])); + WriteFloat(sFoobotName, 'tmpTriggerHigh', + double(FooBotTriggerArray[Ord(at_high), C_TMP])); + WriteFloat(sFoobotName, 'tmpTriggerLow', + double(FooBotTriggerArray[Ord(at_Low), C_TMP])); + WriteFloat(sFoobotName, 'humTriggerHigh', + double(FooBotTriggerArray[Ord(at_high), C_HUM])); + WriteFloat(sFoobotName, 'humTriggerLow', + double(FooBotTriggerArray[Ord(at_Low), C_HUM])); + WriteFloat(sFoobotName, 'co2TriggerHigh', + double(FooBotTriggerArray[Ord(at_high), C_CO2])); + WriteFloat(sFoobotName, 'co2TriggerLow', + double(FooBotTriggerArray[Ord(at_Low), C_CO2])); + WriteFloat(sFoobotName, 'vocTriggerHigh', + double(FooBotTriggerArray[Ord(at_high), C_VOC])); + WriteFloat(sFoobotName, 'vocTriggerLow', + double(FooBotTriggerArray[Ord(at_Low), C_VOC])); + WriteFloat(sFoobotName, 'allpolluTriggerHigh', + double(FooBotTriggerArray[Ord(at_high), C_ALLPOLLU])); + WriteFloat(sFoobotName, 'allpolluTriggerLow', + double(FooBotTriggerArray[Ord(at_Low), C_ALLPOLLU])); + end; + Result := True; + except + raise Exception.Create('Could not save Triggers'); + end; end; -function LoadHighLows: boolean; - // Load values from an INI data file +function LoadTriggers: boolean; + // From foobotmonitor.ini var sFoobotName: string; begin - if SaveLoadHighLows = False then - begin - ShowMessage('Unable to load All-Time stats'); - Exit(False); - end; + Result:=FALSE; // assume failure + if FoobotIdentityObject.FoobotIdentityList.Count = 0 then Exit(FALSE); sFoobotName := FoobotIdentityObject.FoobotIdentityList[TheCurrentFoobot].Name; + if (sFoobotName = '') then + Exit(False); if not Assigned(HLINI) then HLINI := TIniFile.Create(ChangeFileExt(GetAppConfigFile(False), '.ini')); // Make sure the High-Lows are for the current Foobot if (HLINI.ReadString('Foobot', 'CurrentFoobotName', 'unknown') <> sFoobotName) then Exit(False); + try + // Load current Foobot triggers + with HLINI do + begin + FooBotTriggerArray[Ord(at_high), C_PM] := ReadFloat(sFoobotName, 'pmTriggerHigh', 0); + FooBotTriggerArray[Ord(at_low), C_PM] := ReadFloat(sFoobotName, 'pmTriggerLow', 0); + FooBotTriggerArray[Ord(at_high), C_TMP] := ReadFloat(sFoobotName, 'tmpTriggerHigh', 0); + FooBotTriggerArray[Ord(at_low), C_TMP] := ReadFloat(sFoobotName, 'tmpTriggerLow', 0); + FooBotTriggerArray[Ord(at_high), C_HUM] := ReadFloat(sFoobotName, 'humTriggerHigh', 0); + FooBotTriggerArray[Ord(at_low), C_HUM] := ReadFloat(sFoobotName, 'humTriggerLow', 0); + FooBotTriggerArray[Ord(at_high), C_CO2] := ReadFloat(sFoobotName, 'co2TriggerHigh', 0); + FooBotTriggerArray[Ord(at_low), C_CO2] := ReadFloat(sFoobotName, 'co2TriggerLow', 0); + FooBotTriggerArray[Ord(at_high), C_VOC] := ReadFloat(sFoobotName, 'vocTriggerHigh', 0); + FooBotTriggerArray[Ord(at_low), C_VOC] := ReadFloat(sFoobotName, 'vocTriggerLow', 0); + FooBotTriggerArray[Ord(at_high), C_ALLPOLLU] := + ReadFloat(sFoobotName, 'allpolluTriggerHigh', 0); + FooBotTriggerArray[Ord(at_low), C_ALLPOLLU] := + ReadFloat(sFoobotName, 'allpolluTriggerLow', 0); + end; + Result := True; + except + raise Exception.Create('Could not load Triggers'); + end; +end; - // Particulates - FoobotDataHighs[C_PM] := HLINI.ReadFloat(sFoobotName, 'pmHigh', 0); - FoobotDataHighTimes[C_PM] := HLINI.ReadDateTime(sFoobotName, 'pmHighTime', Now); - FoobotDataLows[C_PM] := HLINI.ReadFloat(sFoobotName, 'pmLow', 0); - FoobotDataLowTimes[C_PM] := HLINI.ReadDateTime(sFoobotName, 'pmLowTime', Now); - // Temp - FoobotDataHighs[C_TMP] := HLINI.ReadFloat(sFoobotName, 'tmpHigh', 0); - FoobotDataHighTimes[C_TMP] := HLINI.ReadDateTime(sFoobotName, 'tmpHighTime', Now); - FoobotDataLows[C_TMP] := HLINI.ReadFloat(sFoobotName, 'tmpLow', 0); - FoobotDataLowTimes[C_TMP] := HLINI.ReadDateTime(sFoobotName, 'tmpLowTime', Now); - // Humidity - FoobotDataHighs[C_HUM] := HLINI.ReadFloat(sFoobotName, 'humHigh', 0); - FoobotDataHighTimes[C_HUM] := HLINI.ReadDateTime(sFoobotName, 'humHighTime', Now); - FoobotDataLows[C_HUM] := HLINI.ReadFloat(sFoobotName, 'humLow', 0); - FoobotDataLowTimes[C_HUM] := HLINI.ReadDateTime(sFoobotName, 'humLowTime', Now); - // CO2 - FoobotDataHighs[C_CO2] := HLINI.ReadInteger(sFoobotName, 'co2High', 0); - FoobotDataHighTimes[C_CO2] := HLINI.ReadDateTime(sFoobotName, 'co2HighTime', Now); - FoobotDataLows[C_CO2] := HLINI.ReadInteger(sFoobotName, 'co2Low', 0); - FoobotDataLowTimes[C_CO2] := HLINI.ReadDateTime(sFoobotName, 'co2LowTime', Now); - // Volatile Compounds - FoobotDataHighs[C_VOC] := HLINI.ReadInteger(sFoobotName, 'vocHigh', 0); - FoobotDataHighTimes[C_VOC] := HLINI.ReadDateTime(sFoobotName, 'vocHighTime', Now); - FoobotDataLows[C_VOC] := HLINI.ReadInteger(sFoobotName, 'vocLow', 0); - FoobotDataLowTimes[C_VOC] := HLINI.ReadDateTime(sFoobotName, 'vocLowTime', Now); - // All Pollution - FoobotDataHighs[C_ALLPOLLU] := HLINI.ReadFloat(sFoobotName, 'allpolluHigh', 0); - FoobotDataHighTimes[C_ALLPOLLU] := - HLINI.ReadDateTime(sFoobotName, 'allpolluHighTime', Now); - FoobotDataLows[C_ALLPOLLU] := HLINI.ReadFloat(sFoobotName, 'allpolluLow', 0); - FoobotDataLowTimes[C_ALLPOLLU] := - HLINI.ReadDateTime(sFoobotName, 'allpolluLowTime', Now); - Result := True; +function SaveHighLows: boolean; + // Save values to an INI data file + // To foobotmonitor.ini +var + sFoobotName: string; +begin + Result:=FALSE; // assume failure + if SaveLoadHighLows = False then + Exit(False); + if FoobotIdentityObject.FoobotIdentityList.Count = 0 then Exit(FALSE); + sFoobotName := FoobotIdentityObject.FoobotIdentityList[TheCurrentFoobot].Name; + if (sFoobotName = '') then + Exit(False); + if not Assigned(HLINI) then + HLINI := TIniFile.Create(ChangeFileExt(GetAppConfigFile(False), '.ini')); + // Store current Foobot info + try + with HLINI do + begin + WriteInteger('Foobot', 'CurrentFoobot', TheCurrentFoobot); + WriteString('Foobot', 'CurrentFoobotName', sFoobotName); + + // Particulates + WriteFloat(sFoobotName, 'pmHigh', double(FoobotDataHighs[C_PM])); + WriteDateTime(sFoobotName, 'pmHighTime', TDateTime(FoobotDataHighTimes[C_PM])); + WriteFloat(sFoobotName, 'pmLow', double(FoobotDataLows[C_PM])); + WriteDateTime(sFoobotName, 'pmLowTime', TDateTime(FoobotDataLowTimes[C_PM])); + // Temp + WriteFloat(sFoobotName, 'tmpHigh', double(FoobotDataHighs[C_TMP])); + WriteDateTime(sFoobotName, 'tmpHighTime', TDateTime(FoobotDataHighTimes[C_TMP])); + WriteFloat(sFoobotName, 'tmpLow', double(FoobotDataLows[C_TMP])); + WriteDateTime(sFoobotName, 'tmpLowTime', TDateTime(FoobotDataLowTimes[C_TMP])); + // Humidity + WriteFloat(sFoobotName, 'humHigh', double(FoobotDataHighs[C_HUM])); + WriteDateTime(sFoobotName, 'humHighTime', TDateTime(FoobotDataHighTimes[C_HUM])); + WriteFloat(sFoobotName, 'humLow', double(FoobotDataLows[C_HUM])); + WriteDateTime(sFoobotName, 'humLowTime', TDateTime(FoobotDataLowTimes[C_HUM])); + // CO2 + WriteInteger(sFoobotName, 'co2High', integer(FoobotDataHighs[C_CO2])); + WriteDateTime(sFoobotName, 'co2HighTime', TDateTime(FoobotDataHighTimes[C_CO2])); + WriteInteger(sFoobotName, 'co2Low', integer(FoobotDataLows[C_CO2])); + WriteDateTime(sFoobotName, 'co2LowTime', TDateTime(FoobotDataLowTimes[C_CO2])); + // Volatile Compounds + WriteInteger(sFoobotName, 'vocHigh', integer(FoobotDataHighs[C_VOC])); + WriteDateTime(sFoobotName, 'vocHighTime', TDateTime(FoobotDataHighTimes[C_VOC])); + WriteInteger(sFoobotName, 'vocLow', integer(FoobotDataLows[C_VOC])); + WriteDateTime(sFoobotName, 'vocLowTime', TDateTime(FoobotDataLowTimes[C_VOC])); + // All Pollution + WriteFloat(sFoobotName, 'allpolluHigh', double(FoobotDataHighs[C_ALLPOLLU])); + WriteDateTime(sFoobotName, 'allpolluHighTime', + TDateTime(FoobotDataHighTimes[C_ALLPOLLU])); + WriteFloat(sFoobotName, 'allpolluLow', double(FoobotDataLows[C_ALLPOLLU])); + WriteDateTime(sFoobotName, 'allpolluLowTime', + TDateTime(FoobotDataLowTimes[C_ALLPOLLU])); + Result := True; + end; + except + raise Exception.Create('Could not save HighLows'); + end; +end; + +function LoadHighLows: boolean; + // Load values from an INI data file + // From foobotmonitor.ini +var + sFoobotName: string; +begin + Result:=FALSE; // assume failure + if SaveLoadHighLows = False then + begin + ShowMessage('Unable to load All-Time stats'); + Exit(False); + end; + if FoobotIdentityObject.FoobotIdentityList.Count = 0 then Exit(FALSE); + sFoobotName := FoobotIdentityObject.FoobotIdentityList[TheCurrentFoobot].Name; + if (sFoobotName = '') then + Exit(False); + if not Assigned(HLINI) then + HLINI := TIniFile.Create(ChangeFileExt(GetAppConfigFile(False), '.ini')); + // Make sure the High-Lows are for the current Foobot + if (HLINI.ReadString('Foobot', 'CurrentFoobotName', 'unknown') <> sFoobotName) then + Exit(False); + try + with HLINI do + begin + // Particulates + FoobotDataHighs[C_PM] := ReadFloat(sFoobotName, 'pmHigh', 0); + FoobotDataHighTimes[C_PM] := ReadDateTime(sFoobotName, 'pmHighTime', Now); + FoobotDataLows[C_PM] := ReadFloat(sFoobotName, 'pmLow', 0); + FoobotDataLowTimes[C_PM] := ReadDateTime(sFoobotName, 'pmLowTime', Now); + // Temp + FoobotDataHighs[C_TMP] := ReadFloat(sFoobotName, 'tmpHigh', 0); + FoobotDataHighTimes[C_TMP] := ReadDateTime(sFoobotName, 'tmpHighTime', Now); + FoobotDataLows[C_TMP] := ReadFloat(sFoobotName, 'tmpLow', 0); + FoobotDataLowTimes[C_TMP] := ReadDateTime(sFoobotName, 'tmpLowTime', Now); + // Humidity + FoobotDataHighs[C_HUM] := ReadFloat(sFoobotName, 'humHigh', 0); + FoobotDataHighTimes[C_HUM] := ReadDateTime(sFoobotName, 'humHighTime', Now); + FoobotDataLows[C_HUM] := ReadFloat(sFoobotName, 'humLow', 0); + FoobotDataLowTimes[C_HUM] := ReadDateTime(sFoobotName, 'humLowTime', Now); + // CO2 + FoobotDataHighs[C_CO2] := ReadInteger(sFoobotName, 'co2High', 0); + FoobotDataHighTimes[C_CO2] := ReadDateTime(sFoobotName, 'co2HighTime', Now); + FoobotDataLows[C_CO2] := ReadInteger(sFoobotName, 'co2Low', 0); + FoobotDataLowTimes[C_CO2] := ReadDateTime(sFoobotName, 'co2LowTime', Now); + // Volatile Compounds + FoobotDataHighs[C_VOC] := ReadInteger(sFoobotName, 'vocHigh', 0); + FoobotDataHighTimes[C_VOC] := ReadDateTime(sFoobotName, 'vocHighTime', Now); + FoobotDataLows[C_VOC] := ReadInteger(sFoobotName, 'vocLow', 0); + FoobotDataLowTimes[C_VOC] := ReadDateTime(sFoobotName, 'vocLowTime', Now); + // All Pollution + FoobotDataHighs[C_ALLPOLLU] := ReadFloat(sFoobotName, 'allpolluHigh', 0); + FoobotDataHighTimes[C_ALLPOLLU] := + ReadDateTime(sFoobotName, 'allpolluHighTime', Now); + FoobotDataLows[C_ALLPOLLU] := ReadFloat(sFoobotName, 'allpolluLow', 0); + FoobotDataLowTimes[C_ALLPOLLU] := + ReadDateTime(sFoobotName, 'allpolluLowTime', Now); + Result := True; + end; + except + raise Exception.Create('Could not Load HighLows'); + end; end; // Function to make the Foobot data accessible @@ -361,15 +478,15 @@ begin SetHigh(J, FoobotData_allpollu[K], FoobotData_time[K]); SetLow(J, FoobotData_allpollu[K], FoobotData_time[K]); end; - else raise Exception.Create('Error in FoobotDataObjectToArrays Case'); + else + raise Exception.Create('Error in FoobotDataObjectToArrays Case'); end; // of Case end; end; end; - -function SetHighTrigger(const aSensor: TSensorType; const aValue: variant): boolean; +function SetHighTrigger(const aSensor: integer; const aValue: variant): boolean; begin Result := False; if UseTriggers = False then @@ -381,7 +498,7 @@ begin end; end; -function SetLowTrigger(const aSensor: TSensorType; const aValue: variant): boolean; +function SetLowTrigger(const aSensor: integer; const aValue: variant): boolean; begin Result := False; if UseTriggers = False then @@ -398,7 +515,7 @@ function ResetHighLows: boolean; var iCount: integer; begin - for iCount := 0 to HIGHLOWMAX do + for iCount := C_TIME to C_ALLPOLLU do begin FoobotDataHighs[iCount] := 0; FoobotDataLows[iCount] := 0; diff --git a/applications/foobot/latest_stable/foobot_objects.pas b/applications/foobot/latest_stable/foobot_objects.pas index 66e6e14cd..3c6aa73f5 100644 --- a/applications/foobot/latest_stable/foobot_objects.pas +++ b/applications/foobot/latest_stable/foobot_objects.pas @@ -1,4 +1,5 @@ unit foobot_objects; + { Objects for Foobot Lazarus Copyright (C)2016 Gordon Bamber minsadorada@charcodelvalle.com @@ -40,7 +41,7 @@ type property uuid: string read Fuuid write Fuuid; property userId: integer read FuserId write FuserId; property mac: string read FMac write FMac; - property name: string read FName write FName; + property Name: string read FName write FName; end; {TFoobotIdentityList} @@ -65,26 +66,24 @@ type type TFoobotDataObject = class(TPersistent) - private - FDataPoints:Variant; - FSensors:TStrings; - FUnits:TStrings; - Fuuid:String; - FStart:Int64; - FEnd:Int64; - public - constructor Create; - Destructor Destroy; override; - function SaveToFile(const AFilename: string): boolean; - published - property uuid:String read Fuuid write Fuuid; - property start:Int64 read FStart write FStart; - property &end:Int64 read FEnd write FEnd; - property sensors:TStrings - read FSensors write FSensors; - property units:TStrings - read FUnits write FUnits; - property datapoints : Variant read FDataPoints write FDataPoints; + private + FDataPoints: variant; + FSensors: TStrings; + FUnits: TStrings; + Fuuid: string; + FStart: int64; + FEnd: int64; + public + constructor Create; + destructor Destroy; override; + function SaveToFile(const AFilename: string): boolean; + published + property uuid: string read Fuuid write Fuuid; + property start: int64 read FStart write FStart; + property &end:Int64 read FEnd write FEnd; + property sensors: TStrings read FSensors write FSensors; + property units: TStrings read FUnits write FUnits; + property datapoints: variant read FDataPoints write FDataPoints; end; @@ -93,11 +92,12 @@ implementation constructor TFoobotDataObject.Create; begin inherited; - FSensors:=TStringList.Create; - FUnits:=TstringList.Create; + FSensors := TStringList.Create; + FUnits := TStringList.Create; end; -Destructor TFoobotDataObject.Destroy; +destructor TFoobotDataObject.Destroy; + begin FSensors.Free; FUnits.Free; diff --git a/applications/foobot/monitor/foobotmonitor.lps b/applications/foobot/monitor/foobotmonitor.lps index 3f180fc99..cab99b542 100644 --- a/applications/foobot/monitor/foobotmonitor.lps +++ b/applications/foobot/monitor/foobotmonitor.lps @@ -3,14 +3,14 @@ - + - + - + @@ -19,9 +19,9 @@ - - - + + + @@ -31,10 +31,10 @@ - + - + @@ -44,7 +44,7 @@ - + @@ -53,16 +53,15 @@ - + - - - - - + + + + @@ -72,15 +71,15 @@ - + - + - + @@ -91,7 +90,7 @@ - + @@ -101,25 +100,25 @@ - - + + - + - + - - - - + + + + @@ -128,12 +127,12 @@ - + - + @@ -141,21 +140,21 @@ - + - + - + @@ -163,34 +162,34 @@ - + - + - + - + - + - + @@ -198,73 +197,71 @@ - + - + - + - + - + - + - + - + - - + - + - + - + - - + - + @@ -272,130 +269,130 @@ - + - + - + - + - + - + - + - - + + - - + + - + - - + + - + - + - + - + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - + - + - - + + - - + + - - + + - + - - + + - - + + - - + + diff --git a/applications/foobot/monitor/umainform.lfm b/applications/foobot/monitor/umainform.lfm index cb7b61644..3e11e7507 100644 --- a/applications/foobot/monitor/umainform.lfm +++ b/applications/foobot/monitor/umainform.lfm @@ -494,7 +494,6 @@ object mainform: Tmainform end object mnu_optionsFoobotTriggers: TMenuItem Caption = 'Foobot Triggers...' - OnClick = mnu_optionsFoobotTriggersClick object mnu_options_triggersSetTriggers: TMenuItem Caption = 'Set Trigger Values...' OnClick = mnu_options_triggersSetTriggersClick @@ -516,6 +515,10 @@ object mainform: Tmainform Caption = 'Online Help' OnClick = mnu_optionsOnlineHelpClick end + object mnu_helpFoobotAPIPage: TMenuItem + Caption = 'Foobot API page' + OnClick = mnu_helpFoobotAPIPageClick + end object mnu_helpAbout: TMenuItem Caption = 'A&bout..' OnClick = mnu_helpAboutClick diff --git a/applications/foobot/monitor/umainform.pas b/applications/foobot/monitor/umainform.pas index dfbb27cd9..43662c171 100644 --- a/applications/foobot/monitor/umainform.pas +++ b/applications/foobot/monitor/umainform.pas @@ -27,7 +27,7 @@ V0.0.3.0: Added Help menu. Updated Options menu V0.0.4.0: Graph added V0.1.0.0: Save/Load Alltime High/Lows. Reset values from menu V0.1.1.0: Save/Load Colours, Min and Max values to cfg file -V0.1.2.0: ?? +V0.2.1.0: Triggers,Multiple Foobots } {$ifopt D+} // Debug mode does not load data from web @@ -37,10 +37,10 @@ V0.1.2.0: ?? interface -uses +uses // If Lazarus auto-inserts 'sensors' in the clause then delete it SysUtils, TAGraph, TAIntervalSources, TASeries, foobot_sensors, Forms, Controls, Graphics, Dialogs, ExtCtrls, StdCtrls, Menus, lclIntf, - foobot_utility, uCryptIni, dateutils, uconfigform,utriggersform, Classes; + foobot_utility, uCryptIni, dateutils, uconfigform, utriggersform, Classes; const // Timer milliseconds @@ -123,6 +123,7 @@ type lbl_voclow: TLabel; lbl_allpollulow: TLabel; MainMenu1: TMainMenu; + mnu_helpFoobotAPIPage: TMenuItem; mnu_options_triggersActivateTriggers: TMenuItem; mnu_options_triggersSetTriggers: TMenuItem; mnu_optionsFoobotTriggers: TMenuItem; @@ -162,6 +163,7 @@ type procedure mnupopup_fileRestoreClick(Sender: TObject); procedure mnu_fileExitClick(Sender: TObject); procedure mnu_helpAboutClick(Sender: TObject); + procedure mnu_helpFoobotAPIPageClick(Sender: TObject); procedure mnu_optionsDisplayGuagesOnlyClick(Sender: TObject); procedure mnu_optionsDisplayRedLinesClick(Sender: TObject); procedure mnu_optionsDisplayYellowLinesClick(Sender: TObject); @@ -169,7 +171,6 @@ type procedure mnu_optionsOnlineHelpClick(Sender: TObject); procedure mnu_optionsResetHighsLowsClick(Sender: TObject); procedure mnu_optionsSaveHighLowsClick(Sender: TObject); - procedure mnu_optionsFoobotTriggersClick(Sender: TObject); procedure mnu_optionsTakeReadingNowClick(Sender: TObject); procedure mnu_options_triggersActivateTriggersClick(Sender: TObject); procedure mnu_options_triggersSetTriggersClick(Sender: TObject); @@ -246,7 +247,9 @@ begin TrayIcon1.Hint := Application.Title; DateTimeIntervalChartSource1.DateTimeFormat := 'hh:nn'; LoadConfig; - {$IFDEF DEBUGMODE}UseTriggers:=FALSE;{$ENDIF} + {$IFDEF DEBUGMODE} + UseTriggers := False; + {$ENDIF} end; procedure Tmainform.FormActivate(Sender: TObject); @@ -293,6 +296,8 @@ begin // Everything OK - lets go! iCurrentFoobot := 0; PopulateFoobotMenu; + LoadTriggers; // This can only be done if we have a Foobot Identity + // as each Foobot has its own trigger values Show; grp_sensorDisplay.Refresh; grp_highlow.Refresh; @@ -336,17 +341,18 @@ begin end; procedure Tmainform.ChangeCurrentFoobot(Sender: TObject); +// Called from 'Foobot' TSubmenuitem.click begin iCurrentFoobot := (Sender as TMenuItem).Tag; mnu_optionsTakeReadingNow.Click; end; procedure Tmainform.PopulateFoobotMenu; -// uses foobotmenuarray +// Uses dynamic foobotmenuarray var iCount: integer; begin - if FoobotIdentityObject.FoobotIdentityList.Count = 0 then + if (FoobotIdentityObject.FoobotIdentityList.Count = 0) then Exit; SetLength(foobotmenuarray, FoobotIdentityObject.FoobotIdentityList.Count); for iCount := 0 to Pred(FoobotIdentityObject.FoobotIdentityList.Count) do @@ -368,7 +374,9 @@ end; procedure Tmainform.FormClose(Sender: TObject; var CloseAction: TCloseAction); begin - SaveConfig; + SaveConfig; // to .cfg file + if (FoobotIdentityObject.FoobotIdentityList.Count > 0) then + SaveTriggers; CloseAction := caFree; end; @@ -379,6 +387,7 @@ begin end; procedure Tmainform.SaveConfig; +// For all Foobots begin INI.PlainTextMode := True; // Colours @@ -408,6 +417,7 @@ begin end; procedure Tmainform.LoadConfig; +// For all Foobots begin INI.PlainTextMode := True; // Colours @@ -482,6 +492,11 @@ begin mtInformation, [mbOK], 0); end; +procedure Tmainform.mnu_helpFoobotAPIPageClick(Sender: TObject); +begin + OpenURL('http://api.foobot.io/apidoc/index.html'); +end; + procedure Tmainform.mnu_optionsDisplayGuagesOnlyClick(Sender: TObject); begin bDisplayGuagesOnly := mnu_optionsDisplayGuagesOnly.Checked; @@ -534,10 +549,6 @@ begin INI.WriteBool('Foobot', 'SaveLoadHighLows', SaveLoadHighLows); end; -procedure Tmainform.mnu_optionsFoobotTriggersClick(Sender: TObject); -begin -end; - procedure Tmainform.mnu_optionsTakeReadingNowClick(Sender: TObject); begin mainform.Cursor := crHourGlass; @@ -550,21 +561,22 @@ end; procedure Tmainform.mnu_options_triggersActivateTriggersClick(Sender: TObject); begin - mnu_options_triggersActivateTriggers.Checked:= NOT mnu_options_triggersActivateTriggers.Checked; - UseTriggers:=mnu_options_triggersActivateTriggers.Checked; - If UseTriggers then - mnu_options_triggersActivateTriggers.Caption:='Set Triggers Off' + mnu_options_triggersActivateTriggers.Checked := + not mnu_options_triggersActivateTriggers.Checked; + UseTriggers := mnu_options_triggersActivateTriggers.Checked; + if UseTriggers then + mnu_options_triggersActivateTriggers.Caption := 'Set Triggers Off' else - mnu_options_triggersActivateTriggers.Caption:='Set Triggers On'; + mnu_options_triggersActivateTriggers.Caption := 'Set Triggers On'; end; procedure Tmainform.mnu_options_triggersSetTriggersClick(Sender: TObject); begin - triggersform.ShowModal; - If triggersform.ModalResult = mrCancel then - ShowMessage('Cancel') + triggersform.ShowModal; + if triggersform.ModalResult = mrCancel then + ShowMessage('Cancel') else - mnu_options_triggersActivateTriggers.Enabled:=TRUE; + mnu_options_triggersActivateTriggers.Enabled := True; end; procedure Tmainform.mnu_SampleEveryHalfHourClick(Sender: TObject); @@ -613,7 +625,11 @@ end; procedure Tmainform.tmr_foobotTimer(Sender: TObject); begin if FetchFoobotData(dfLast, iCurrentFoobot, 0, 0, 0, 0, sSecretKey) then - DisplayReadings; + DisplayReadings + else + mainform.Caption := Format('Foobot "%s" - Read failure on %s', + [FoobotIdentityObject.FoobotIdentityList[iCurrentFoobot].Name, + FormatDateTime('dd/mm/yyyy - tt', Now)]); end; procedure Tmainform.TrayIcon1Click(Sender: TObject); @@ -746,6 +762,8 @@ begin FoobotDataObject.Units[SensorNumber]]) + LineEnding + 'on ' + FormatDateTime('dd/mm tt', TDateTime(FoobotDataLowTimes[SensorNumber])); end; + else + Exception.Create('Error in UpdateHighLow Case statement'); end; end; @@ -802,7 +820,7 @@ begin if FoobotDataObjectToArrays = True then begin mainform.Caption := Format('Foobot "%s" - Last reading: ', - [FoobotIdentityObject.FoobotIdentityList[0].Name]) + + [FoobotIdentityObject.FoobotIdentityList[iCurrentFoobot].Name]) + FormatDateTime('dd/mm/yyyy - tt', FoobotData_time[0]); UpdateGuage(as_pm, C_PM); UpdateGuage(as_tmp, C_TMP); @@ -817,46 +835,62 @@ begin end; GraphCurrentReading; - // Process Trigger Alerts - If UseTriggers then - For iCount:=C_PM to C_ALLPOLLU do - If AlertRec[iCount].AlertTriggered then - If AlertRec[iCount].AlertType = at_high then - begin - ShowMessageFmt('High alert member %d (value %f) exceeded', - [iCount,Double(AlertRec[iCount].AlertValue)]); - end - else - begin - ShowMessageFmt('Low alert member %d (value %f) exceeded', - [iCount,Double(AlertRec[iCount].AlertValue)]); - end; - end; + // Process Trigger Alerts on each call to FoobotDataObjectToArrays + if UseTriggers then + try + // Look for alerts in each sensor + for iCount := C_PM to C_ALLPOLLU do + if AlertRec[iCount].AlertTriggered then + // Alert found. High or low? + if AlertRec[iCount].AlertType = at_high then + begin + // A high alert - do something + ShowMessageFmt('High alert member %d (value %f) exceeded', + [iCount, double(AlertRec[iCount].AlertValue)]); + end + else + begin + // A low alert - do something + ShowMessageFmt('Low alert member %d (value %f) exceeded', + [iCount, double(AlertRec[iCount].AlertValue)]); + end; + except + raise Exception.Create('Unable to process triggers in DisplayReadings'); + end; + end + else + raise Exception.Create('FoobotDataObjectToArrays error in DisplayReadings'); end; function AsPercent(aValue, aMin, aMax: double): double; begin if aMax > 0 then - Result := aValue / (aMax - aMin) * 100 + Result := (aValue / (aMax - aMin) * 100) else Result := 0; end; procedure Tmainform.GraphCurrentReading; begin - {$IFDEF DEBUGMODE}Exit;{$ENDIF} - lineseries_pm.AddXY(FoobotData_time[0], - AsPercent(FoobotData_pm[0], as_pm.ValueMin, as_pm.ValueMax)); - lineseries_tmp.AddXY(FoobotData_time[0], AsPercent(FoobotData_tmp[0], - as_tmp.ValueMin, as_tmp.ValueMax)); - lineseries_hum.AddXY(FoobotData_time[0], - AsPercent(FoobotData_hum[0], as_hum.ValueMin, as_hum.ValueMax)); - lineseries_co2.AddXY(FoobotData_time[0], - AsPercent(FoobotData_co2[0], as_co2.ValueMin, as_co2.ValueMax)); - lineseries_voc.AddXY(FoobotData_time[0], - AsPercent(FoobotData_voc[0], as_voc.ValueMin, as_voc.ValueMax)); - lineseries_allpollu.AddXY(FoobotData_time[0], - AsPercent(FoobotData_allpollu[0], as_allpollu.ValueMin, as_allpollu.ValueMax)); + {$IFDEF DEBUGMODE} + Exit; + {$ENDIF} + try + lineseries_pm.AddXY(FoobotData_time[0], + AsPercent(FoobotData_pm[0], as_pm.ValueMin, as_pm.ValueMax)); + lineseries_tmp.AddXY(FoobotData_time[0], AsPercent(FoobotData_tmp[0], + as_tmp.ValueMin, as_tmp.ValueMax)); + lineseries_hum.AddXY(FoobotData_time[0], + AsPercent(FoobotData_hum[0], as_hum.ValueMin, as_hum.ValueMax)); + lineseries_co2.AddXY(FoobotData_time[0], + AsPercent(FoobotData_co2[0], as_co2.ValueMin, as_co2.ValueMax)); + lineseries_voc.AddXY(FoobotData_time[0], + AsPercent(FoobotData_voc[0], as_voc.ValueMin, as_voc.ValueMax)); + lineseries_allpollu.AddXY(FoobotData_time[0], + AsPercent(FoobotData_allpollu[0], as_allpollu.ValueMin, as_allpollu.ValueMax)); + except + raise Exception.Create('Unable to update graph in GraphCurrentReading'); + end; end; procedure Tmainform.GraphHistory; @@ -866,32 +900,37 @@ var iCount: integer; iStartSeconds, iEndSeconds: int64; begin - {$IFDEF DEBUGMODE}Exit;{$ENDIF} + {$IFDEF DEBUGMODE} + Exit; + {$ENDIF} iEndSeconds := DateTimeToUnix(Now) - 3600; iStartSeconds := iEndSeconds - (2 * (24 * 3600)); // 49 hours before Now - grp_chart.Caption := Format('History from %s', + grp_chart.Caption := Format('History since %s', [FormatDateTime('dd/mm/yyyy hh:nn', UnixToDateTime(iStartSeconds))]); if FetchFoobotData(dfStartEnd, iCurrentFoobot, 0, 3600, iStartSeconds, iEndSeconds, sSecretKey) = False then exit; - if FoobotDataObjectToArrays then - for iCount := 0 to Pred(High(FoobotData_time)) do - begin - lineseries_pm.AddXY(FoobotData_time[iCount], - AsPercent(FoobotData_pm[iCount], as_pm.ValueMin, as_pm.ValueMax)); - lineseries_tmp.AddXY(FoobotData_time[iCount], - AsPercent(FoobotData_tmp[iCount], as_tmp.ValueMin, as_tmp.ValueMax)); - lineseries_hum.AddXY(FoobotData_time[iCount], - AsPercent(FoobotData_hum[iCount], as_hum.ValueMin, as_hum.ValueMax)); - lineseries_co2.AddXY(FoobotData_time[iCount], - AsPercent(FoobotData_co2[iCount], as_co2.ValueMin, as_co2.ValueMax)); - lineseries_voc.AddXY(FoobotData_time[iCount], - AsPercent(FoobotData_voc[iCount], as_voc.ValueMin, as_voc.ValueMax)); - lineseries_allpollu.AddXY(FoobotData_time[iCount], - AsPercent(FoobotData_allpollu[iCount], as_allpollu.ValueMin, - as_allpollu.ValueMax)); - end; - ResetArrays; // at end + try + if FoobotDataObjectToArrays then + for iCount := 0 to Pred(High(FoobotData_time)) do + begin + lineseries_pm.AddXY(FoobotData_time[iCount], + AsPercent(FoobotData_pm[iCount], as_pm.ValueMin, as_pm.ValueMax)); + lineseries_tmp.AddXY(FoobotData_time[iCount], + AsPercent(FoobotData_tmp[iCount], as_tmp.ValueMin, as_tmp.ValueMax)); + lineseries_hum.AddXY(FoobotData_time[iCount], + AsPercent(FoobotData_hum[iCount], as_hum.ValueMin, as_hum.ValueMax)); + lineseries_co2.AddXY(FoobotData_time[iCount], + AsPercent(FoobotData_co2[iCount], as_co2.ValueMin, as_co2.ValueMax)); + lineseries_voc.AddXY(FoobotData_time[iCount], + AsPercent(FoobotData_voc[iCount], as_voc.ValueMin, as_voc.ValueMax)); + lineseries_allpollu.AddXY(FoobotData_time[iCount], + AsPercent(FoobotData_allpollu[iCount], as_allpollu.ValueMin, + as_allpollu.ValueMax)); + end; + finally + ResetArrays; // at end + end; end; end. diff --git a/applications/foobot/monitor/utriggersform.lfm b/applications/foobot/monitor/utriggersform.lfm index 029982fbc..f984962cf 100644 --- a/applications/foobot/monitor/utriggersform.lfm +++ b/applications/foobot/monitor/utriggersform.lfm @@ -1,7 +1,7 @@ object triggersform: Ttriggersform - Left = 546 + Left = 686 Height = 484 - Top = 142 + Top = 279 Width = 794 BorderIcons = [biSystemMenu] BorderStyle = bsSingle @@ -17,12 +17,71 @@ object triggersform: Ttriggersform Scaled = True object grp_main: TGroupBox Left = 0 - Height = 424 + Height = 428 Top = 0 Width = 794 Align = alTop - Caption = 'Foobot Triggers' + AutoSize = True + Caption = 'All Foobot Triggers' + ChildSizing.EnlargeVertical = crsScaleChilds + ChildSizing.ControlsPerLine = 1 + ClientHeight = 408 + ClientWidth = 790 TabOrder = 0 + object grp_pm: TGroupBox + Left = 0 + Height = 68 + Top = 0 + Width = 790 + Align = alTop + Caption = 'Particulates' + TabOrder = 0 + end + object grp_tmp: TGroupBox + Left = 0 + Height = 68 + Top = 340 + Width = 790 + Align = alTop + Caption = 'All Pollution' + TabOrder = 1 + end + object grp_hum: TGroupBox + Left = 0 + Height = 68 + Top = 272 + Width = 790 + Align = alTop + Caption = 'Volatile Componds' + TabOrder = 2 + end + object grp_co2: TGroupBox + Left = 0 + Height = 68 + Top = 204 + Width = 790 + Align = alTop + Caption = 'Carbon Diaoxide' + TabOrder = 3 + end + object grp_voc: TGroupBox + Left = 0 + Height = 68 + Top = 136 + Width = 790 + Align = alTop + Caption = 'Humidity' + TabOrder = 4 + end + object grp_allpollu: TGroupBox + Left = 0 + Height = 68 + Top = 68 + Width = 790 + Align = alTop + Caption = 'Temperature' + TabOrder = 5 + end end object cmd_OK: TBitBtn Left = 360 diff --git a/applications/foobot/monitor/utriggersform.pas b/applications/foobot/monitor/utriggersform.pas index 946382ac7..4b2ab8d07 100644 --- a/applications/foobot/monitor/utriggersform.pas +++ b/applications/foobot/monitor/utriggersform.pas @@ -15,6 +15,12 @@ type Ttriggersform = class(TForm) cmd_cancel: TBitBtn; cmd_OK: TBitBtn; + grp_pm: TGroupBox; + grp_tmp: TGroupBox; + grp_hum: TGroupBox; + grp_co2: TGroupBox; + grp_voc: TGroupBox; + grp_allpollu: TGroupBox; grp_main: TGroupBox; procedure FormCreate(Sender: TObject); private