From a068ddbf69ba7fd1d5644adb35972b8d768c6b74 Mon Sep 17 00:00:00 2001 From: gbakeman Date: Sat, 4 May 2024 16:49:48 -0400 Subject: [PATCH 1/2] Power variable calc updates, new method for Huawei - Added output current as new `UPS_Value` - Renamed a few `PowerMethod` enums to be more descriptive of what they do - Trying a new well-known variable initialization method that utilizes nullable values. Variable is left null if there's a problem retrieving it from the NUT server, then it will not be updated in the future. Only applying this to `output.current` since its use is limited to power calculation only. Applying this to other variables will likely require greater structural changes throughout WinNUT. - Added power calculation method involving output voltage and current, doable in a Huawei UPS (see #150) - GetUPSVar no longer prints a line assuming that the raised exception will go unhandled if a fallback value was not provided. --- .../WinNUT-Client_Common/Common_Classes.vb | 1 + .../WinNUT-Client_Common/Common_Enums.vb | 8 +- WinNUT_V2/WinNUT-Client_Common/UPS_Device.vb | 89 +++++++++++++------ 3 files changed, 70 insertions(+), 28 deletions(-) diff --git a/WinNUT_V2/WinNUT-Client_Common/Common_Classes.vb b/WinNUT_V2/WinNUT-Client_Common/Common_Classes.vb index f76544e..7b0943f 100644 --- a/WinNUT_V2/WinNUT-Client_Common/Common_Classes.vb +++ b/WinNUT_V2/WinNUT-Client_Common/Common_Classes.vb @@ -16,6 +16,7 @@ Public Class UPS_Values Public Batt_Runtime As Double = Nothing Public Input_Voltage As Double = Nothing Public Output_Voltage As Double = Nothing + Public Output_Current As Single? Public Power_Frequency As Double = Nothing Public Load As Double = Nothing Public Output_Power As Double = Nothing diff --git a/WinNUT_V2/WinNUT-Client_Common/Common_Enums.vb b/WinNUT_V2/WinNUT-Client_Common/Common_Enums.vb index 7018e2b..4809be3 100644 --- a/WinNUT_V2/WinNUT-Client_Common/Common_Enums.vb +++ b/WinNUT_V2/WinNUT-Client_Common/Common_Enums.vb @@ -118,9 +118,13 @@ Public Enum UPS_States ALARM = 1 << 13 End Enum +''' +''' Ref. https://github.com/nutdotnet/WinNUT-Client/pull/112 +''' Public Enum PowerMethod Unavailable ' No methods are available to calculate power. RealPower ' The ups.realpower variable is available for direct reading. - NominalPowerCalc ' Power can be calculated by taking the load percentage of the nominal power variable. - VoltAmpCalc ' Power will have be calculated as a function of volts and amps. + RPNomLoadPct ' Power is calcualted as the load percentage of nominal realpower. + InputNomVALoadPct ' Power is given as the nominal power calculated from nominal input volts/amps (and PF), multiplied by the percent load. + OutputVACalc ' Power is calculated with output voltage and current (seen on Huawei, issue #150) End Enum diff --git a/WinNUT_V2/WinNUT-Client_Common/UPS_Device.vb b/WinNUT_V2/WinNUT-Client_Common/UPS_Device.vb index 516bcbe..982e789 100644 --- a/WinNUT_V2/WinNUT-Client_Common/UPS_Device.vb +++ b/WinNUT_V2/WinNUT-Client_Common/UPS_Device.vb @@ -13,7 +13,7 @@ Imports System.Windows.Forms Public Class UPS_Device #Region "Statics/Defaults" Private ReadOnly INVARIANT_CULTURE = CultureInfo.InvariantCulture - Private Const CosPhi As Double = 0.6 + Private Const POWER_FACTOR = 0.8 ' How many milliseconds to wait before the Reconnect routine tries again. Private Const DEFAULT_RECONNECT_WAIT_MS As Double = 5000 @@ -238,27 +238,57 @@ Public Class UPS_Device Trim(GetUPSVar("ups.serial", "Unknown")), Trim(GetUPSVar("ups.firmware", "Unknown"))) + With freshData.UPS_Value + LogFile.LogTracing("Initializing other well-known UPS variables...", LogLvl.LOG_DEBUG, Me) + Try + Dim value = Single.Parse(GetUPSVar("output.current"), INVARIANT_CULTURE) + .Output_Current = value + LogFile.LogTracing("output.current: " & value, LogLvl.LOG_DEBUG, Me) + Catch ex As Exception + If ex.GetType() <> GetType(NutException) Then + LogFile.LogException(ex, Me) + End If + End Try + Try + Dim value = Single.Parse(GetUPSVar("output.voltage"), INVARIANT_CULTURE) + .Output_Voltage = value + LogFile.LogTracing("output.voltage: " & value, LogLvl.LOG_DEBUG, Me) + Catch ex As Exception + If ex.GetType() <> GetType(NutException) Then + LogFile.LogException(ex, Me) + End If + End Try + End With + + ' Determine optimal method for measuring power output from the UPS. LogFile.LogTracing("Determining best method to calculate power usage...", LogLvl.LOG_NOTICE, Me) + ' Start with directly reading a variable from the UPS. Try GetUPSVar("ups.realpower") _PowerCalculationMethod = PowerMethod.RealPower - LogFile.LogTracing("Using RealPower method to calculate power usage.", LogLvl.LOG_NOTICE, Me) + LogFile.LogTracing("Using RealPower method.", LogLvl.LOG_NOTICE, Me) Catch Try GetUPSVar("ups.realpower.nominal") GetUPSVar("ups.load") - _PowerCalculationMethod = PowerMethod.NominalPowerCalc - LogFile.LogTracing("Using NominalPowerCalc method to calculate power usage.", LogLvl.LOG_NOTICE, Me) + _PowerCalculationMethod = PowerMethod.RPNomLoadPct + LogFile.LogTracing("Using RPNomLoadPct method.", LogLvl.LOG_NOTICE, Me) Catch Try GetUPSVar("input.current.nominal") GetUPSVar("input.voltage.nominal") GetUPSVar("ups.load") - _PowerCalculationMethod = PowerMethod.VoltAmpCalc - LogFile.LogTracing("Using VoltAmpCalc method to calculate power usage.", LogLvl.LOG_NOTICE, Me) + _PowerCalculationMethod = PowerMethod.InputNomVALoadPct + LogFile.LogTracing("Using InputNomVALoadPct method.", LogLvl.LOG_NOTICE, Me) Catch - _PowerCalculationMethod = PowerMethod.Unavailable - LogFile.LogTracing("Unable to find a suitable method to calculate power usage.", LogLvl.LOG_WARNING, Me) + If freshData.UPS_Value.Output_Current IsNot Nothing AndAlso + freshData.UPS_Value.Output_Voltage <> Nothing Then + _PowerCalculationMethod = PowerMethod.OutputVACalc + LogFile.LogTracing("Using OutputVACalc method.", LogLvl.LOG_NOTICE, Me) + Else + _PowerCalculationMethod = PowerMethod.Unavailable + LogFile.LogTracing("Unable to find a suitable method to calculate power usage.", LogLvl.LOG_WARNING, Me) + End If End Try End Try End Try @@ -283,9 +313,9 @@ Public Class UPS_Device .Batt_Charge = Double.Parse(GetUPSVar("battery.charge", -1), INVARIANT_CULTURE) .Batt_Voltage = Double.Parse(GetUPSVar("battery.voltage", -1), INVARIANT_CULTURE) .Batt_Runtime = Double.Parse(GetUPSVar("battery.runtime", -1), INVARIANT_CULTURE) - .Power_Frequency = Double.Parse(GetUPSVar("input.frequency", Double.Parse(GetUPSVar("output.frequency", Freq_Fallback), INVARIANT_CULTURE)), INVARIANT_CULTURE) + .Power_Frequency = Double.Parse(GetUPSVar("input.frequency", Freq_Fallback), INVARIANT_CULTURE) .Input_Voltage = Double.Parse(GetUPSVar("input.voltage", -1), INVARIANT_CULTURE) - .Output_Voltage = Double.Parse(GetUPSVar("output.voltage", .Input_Voltage), INVARIANT_CULTURE) + .Output_Voltage = Double.Parse(GetUPSVar("output.voltage", -1), INVARIANT_CULTURE) .Load = Double.Parse(GetUPSVar("ups.load", 0), INVARIANT_CULTURE) ' Retrieve and/or calculate output power if possible. @@ -293,25 +323,33 @@ Public Class UPS_Device Dim parsedValue As Double Try - If _PowerCalculationMethod = PowerMethod.RealPower Then - parsedValue = Double.Parse(GetUPSVar("ups.realpower"), INVARIANT_CULTURE) - - ElseIf _PowerCalculationMethod = PowerMethod.NominalPowerCalc Then - parsedValue = Double.Parse(GetUPSVar("ups.realpower.nominal"), INVARIANT_CULTURE) - parsedValue *= UPS_Datas.UPS_Value.Load / 100.0 - - ElseIf _PowerCalculationMethod = PowerMethod.VoltAmpCalc Then - Dim nomCurrent = Double.Parse(GetUPSVar("input.current.nominal"), INVARIANT_CULTURE) - Dim nomVoltage = Double.Parse(GetUPSVar("input.voltage.nominal"), INVARIANT_CULTURE) - - parsedValue = (nomCurrent * nomVoltage * 0.8) * (UPS_Datas.UPS_Value.Load / 100.0) - Else - Throw New InvalidOperationException("Insufficient variables to calculate power.") - End If + Select Case _PowerCalculationMethod + Case PowerMethod.RealPower + parsedValue = Double.Parse(GetUPSVar("ups.realpower"), INVARIANT_CULTURE) + + Case PowerMethod.RPNomLoadPct + parsedValue = Double.Parse(GetUPSVar("ups.realpower.nominal"), INVARIANT_CULTURE) + parsedValue *= UPS_Datas.UPS_Value.Load / 100.0 + + Case PowerMethod.InputNomVALoadPct + Dim nomCurrent = Double.Parse(GetUPSVar("input.current.nominal"), INVARIANT_CULTURE) + Dim nomVoltage = Double.Parse(GetUPSVar("input.voltage.nominal"), INVARIANT_CULTURE) + + parsedValue = nomCurrent * nomVoltage * POWER_FACTOR + parsedValue = UPS_Datas.UPS_Value.Load / 100.0 + Case PowerMethod.OutputVACalc + .Output_Current = Single.Parse(GetUPSVar("output.current"), INVARIANT_CULTURE) + parsedValue = .Output_Current * .Output_Voltage * POWER_FACTOR + Case Else + ' Should not trigger - something has gone wrong. + Throw New InvalidOperationException("Insufficient variables to calculate power.") + End Select Catch ex As FormatException LogFile.LogTracing("Unexpected format trying to parse value from UPS. Exception:", LogLvl.LOG_ERROR, Me) LogFile.LogTracing(ex.ToString(), LogLvl.LOG_ERROR, Me) LogFile.LogTracing("parsedValue: " & parsedValue, LogLvl.LOG_ERROR, Me) + Catch ex As Exception + LogFile.LogException(ex, Me) End Try .Output_Power = parsedValue @@ -428,7 +466,6 @@ Public Class UPS_Device LogFile.LogTracing("Apply Fallback Value when retrieving " & varName, LogLvl.LOG_WARNING, Me) Return Fallback_value Else - LogFile.LogTracing("Unhandled error while getting " & varName, LogLvl.LOG_ERROR, Me) Throw End If End Try From 4ab53c178aa937bdb7ea1d80dac2eac908d018e8 Mon Sep 17 00:00:00 2001 From: gbakeman Date: Mon, 6 May 2024 11:02:54 -0400 Subject: [PATCH 2/2] Adding RealOutputPower calc method, small fixes - Adding the `RealOutputPower` calculation method for UPS power output, specifically for Huawei UPSes. - Corrected mistake in `InputNomVALoadPct` calculation method that omitted a multiplication - Make error more clear if execution ever reaches Else case in power calculation switch statement. --- .../WinNUT-Client_Common/Common_Enums.vb | 1 + WinNUT_V2/WinNUT-Client_Common/UPS_Device.vb | 29 +++++++++++++++---- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/WinNUT_V2/WinNUT-Client_Common/Common_Enums.vb b/WinNUT_V2/WinNUT-Client_Common/Common_Enums.vb index 4809be3..37906d4 100644 --- a/WinNUT_V2/WinNUT-Client_Common/Common_Enums.vb +++ b/WinNUT_V2/WinNUT-Client_Common/Common_Enums.vb @@ -124,6 +124,7 @@ End Enum Public Enum PowerMethod Unavailable ' No methods are available to calculate power. RealPower ' The ups.realpower variable is available for direct reading. + RealOutputPower ' output.realpower is available for direct reading. RPNomLoadPct ' Power is calcualted as the load percentage of nominal realpower. InputNomVALoadPct ' Power is given as the nominal power calculated from nominal input volts/amps (and PF), multiplied by the percent load. OutputVACalc ' Power is calculated with output voltage and current (seen on Huawei, issue #150) diff --git a/WinNUT_V2/WinNUT-Client_Common/UPS_Device.vb b/WinNUT_V2/WinNUT-Client_Common/UPS_Device.vb index 982e789..67c8da6 100644 --- a/WinNUT_V2/WinNUT-Client_Common/UPS_Device.vb +++ b/WinNUT_V2/WinNUT-Client_Common/UPS_Device.vb @@ -258,15 +258,27 @@ Public Class UPS_Device LogFile.LogException(ex, Me) End If End Try + Try + Dim value = Single.Parse(GetUPSVar("output.realpower"), INVARIANT_CULTURE) + .Output_Power = value + LogFile.LogTracing("output.power: " & value, LogLvl.LOG_DEBUG, Me) + Catch ex As Exception + + End Try End With ' Determine optimal method for measuring power output from the UPS. LogFile.LogTracing("Determining best method to calculate power usage...", LogLvl.LOG_NOTICE, Me) ' Start with directly reading a variable from the UPS. Try - GetUPSVar("ups.realpower") - _PowerCalculationMethod = PowerMethod.RealPower - LogFile.LogTracing("Using RealPower method.", LogLvl.LOG_NOTICE, Me) + If freshData.UPS_Value.Output_Power <> Nothing Then + _PowerCalculationMethod = PowerMethod.RealOutputPower + LogFile.LogTracing("Using RealOutputPower method.", LogLvl.LOG_NOTICE, Me) + Else + GetUPSVar("ups.realpower") + _PowerCalculationMethod = PowerMethod.RealPower + LogFile.LogTracing("Using RealPower method.", LogLvl.LOG_NOTICE, Me) + End If Catch Try GetUPSVar("ups.realpower.nominal") @@ -327,6 +339,9 @@ Public Class UPS_Device Case PowerMethod.RealPower parsedValue = Double.Parse(GetUPSVar("ups.realpower"), INVARIANT_CULTURE) + Case PowerMethod.RealOutputPower + parsedValue = Single.Parse(GetUPSVar("output.realpower"), INVARIANT_CULTURE) + Case PowerMethod.RPNomLoadPct parsedValue = Double.Parse(GetUPSVar("ups.realpower.nominal"), INVARIANT_CULTURE) parsedValue *= UPS_Datas.UPS_Value.Load / 100.0 @@ -336,13 +351,13 @@ Public Class UPS_Device Dim nomVoltage = Double.Parse(GetUPSVar("input.voltage.nominal"), INVARIANT_CULTURE) parsedValue = nomCurrent * nomVoltage * POWER_FACTOR - parsedValue = UPS_Datas.UPS_Value.Load / 100.0 + parsedValue *= UPS_Datas.UPS_Value.Load / 100.0 Case PowerMethod.OutputVACalc .Output_Current = Single.Parse(GetUPSVar("output.current"), INVARIANT_CULTURE) parsedValue = .Output_Current * .Output_Voltage * POWER_FACTOR Case Else ' Should not trigger - something has gone wrong. - Throw New InvalidOperationException("Insufficient variables to calculate power.") + Throw New InvalidOperationException("Reached Else case when attempting to get power output for method " & _PowerCalculationMethod) End Select Catch ex As FormatException LogFile.LogTracing("Unexpected format trying to parse value from UPS. Exception:", LogLvl.LOG_ERROR, Me) @@ -352,7 +367,9 @@ Public Class UPS_Device LogFile.LogException(ex, Me) End Try - .Output_Power = parsedValue + ' Apply rounding to this number since calculations have extended to three decimal places. + ' TODO: Remove this round function once gauges can handle decimal places better. + .Output_Power = Math.Round(parsedValue, 1) End If ' Handle out-of-range battery charge