From 80896e6ac886e99ea46e3b726d262a3e5b19fd43 Mon Sep 17 00:00:00 2001 From: gbakeman Date: Tue, 12 Dec 2023 16:25:22 -0500 Subject: [PATCH 1/3] Troubleshoot nominalpower parse errors Several people are reporting that WinNUT is crashing when it attempts to parse the `ups.realpower.nominal` variable that's returned from the server. Adding in some logging to troubleshoot that, as well as a few other log lines to better describe the startup procedure in log files. --- WinNUT_V2/WinNUT-Client_Common/UPS_Device.vb | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/WinNUT_V2/WinNUT-Client_Common/UPS_Device.vb b/WinNUT_V2/WinNUT-Client_Common/UPS_Device.vb index 0eb955e..4c979c5 100644 --- a/WinNUT_V2/WinNUT-Client_Common/UPS_Device.vb +++ b/WinNUT_V2/WinNUT-Client_Common/UPS_Device.vb @@ -216,13 +216,15 @@ Public Class UPS_Device ''' ''' Private Function GetUPSProductInfo() As UPSData + LogFile.LogTracing("Retrieving basic UPS product information...", LogLvl.LOG_NOTICE, Me) + Dim freshData = New UPSData( Trim(GetUPSVar("ups.mfr", "Unknown")), Trim(GetUPSVar("ups.model", "Unknown")), Trim(GetUPSVar("ups.serial", "Unknown")), Trim(GetUPSVar("ups.firmware", "Unknown"))) - ' Determine available power & load data + LogFile.LogTracing("Determining best method to calculate power usage...", LogLvl.LOG_NOTICE, Me) Try GetUPSVar("ups.realpower") _PowerCalculationMethod = PowerMethod.RealPower @@ -251,6 +253,7 @@ Public Class UPS_Device freshData.UPS_Value.Batt_Capacity = Double.Parse(GetUPSVar("battery.capacity", 7), ciClone) Freq_Fallback = Double.Parse(GetUPSVar("output.frequency.nominal", (50 + CInt(Arr_Reg_Key.Item("FrequencySupply")) * 10)), ciClone) + LogFile.LogTracing("Completed retrieval of basic UPS product information.", LogLvl.LOG_NOTICE, Me) Return freshData End Function @@ -333,8 +336,16 @@ Public Class UPS_Device If _PowerCalculationMethod = PowerMethod.RealPower Then Return Integer.Parse(GetUPSVar("ups.realpower")) ElseIf _PowerCalculationMethod = PowerMethod.NominalPowerCalc Then - Return Integer.Parse(GetUPSVar("ups.realpower.nominal")) * - (UPS_Datas.UPS_Value.Load / 100.0) + Dim nomPower = GetUPSVar("ups.realpower.nominal") + Try + Return Integer.Parse(nomPower) * (UPS_Datas.UPS_Value.Load / 100.0) + Catch ex As Exception + LogFile.LogTracing("Failed to parse the nominal realpower: " & vbNewLine & ex.ToString() & + vbNewLine & vbNewLine & "nomPower is: " & nomPower & " Type: " & + nomPower.GetType().ToString(), LogLvl.LOG_ERROR, Me) + Return 0 + End Try + ElseIf _PowerCalculationMethod = PowerMethod.VoltAmpCalc Then Dim nomCurrent = Double.Parse(GetUPSVar("input.current.nominal")) Dim nomVoltage = Double.Parse(GetUPSVar("input.voltage.nominal")) From fb071ef2e75353c62fc473a8cf066df6d5a87275 Mon Sep 17 00:00:00 2001 From: gbakeman Date: Wed, 13 Dec 2023 14:10:00 -0500 Subject: [PATCH 2/3] Updates to UPS data retrieval and parsing Applying several changes to the `UPS_Device` class: - Made `Retrieve_UPS_Datas` private, and removed leftover commented code - Merged `GetPowerUsage` function into main data retrieval loop since no other code referred to it. - Wrapped power variable parsing into try block to log parsing errors. NUT and other exceptions will continue to be raised. - Reorganized some comments around battery calculations. --- WinNUT_V2/WinNUT-Client_Common/UPS_Device.vb | 68 ++++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/WinNUT_V2/WinNUT-Client_Common/UPS_Device.vb b/WinNUT_V2/WinNUT-Client_Common/UPS_Device.vb index 4c979c5..ef96c42 100644 --- a/WinNUT_V2/WinNUT-Client_Common/UPS_Device.vb +++ b/WinNUT_V2/WinNUT-Client_Common/UPS_Device.vb @@ -259,8 +259,9 @@ Public Class UPS_Device Private oldStatusBitmask As Integer - Public Sub Retrieve_UPS_Datas() Handles Update_Data.Tick ' As UPSData + Private Sub Retrieve_UPS_Datas() Handles Update_Data.Tick LogFile.LogTracing("Enter Retrieve_UPS_Datas", LogLvl.LOG_DEBUG, Me) + Try Dim UPS_rt_Status As String @@ -273,8 +274,37 @@ Public Class UPS_Device .Input_Voltage = Double.Parse(GetUPSVar("input.voltage", 220), ciClone) .Output_Voltage = Double.Parse(GetUPSVar("output.voltage", .Input_Voltage), ciClone) .Load = Double.Parse(GetUPSVar("ups.load", 0), ciClone) - .Output_Power = If(_PowerCalculationMethod <> PowerMethod.Unavailable, GetPowerUsage(), 0) + ' Retrieve and/or calculate output power if possible. + If _PowerCalculationMethod <> PowerMethod.Unavailable Then + Dim parsedValue As Double + + Try + If _PowerCalculationMethod = PowerMethod.RealPower Then + parsedValue = Double.Parse(GetUPSVar("ups.realpower")) + + ElseIf _PowerCalculationMethod = PowerMethod.NominalPowerCalc Then + parsedValue = Double.Parse(GetUPSVar("ups.realpower.nominal")) + parsedValue *= UPS_Datas.UPS_Value.Load / 100.0 + + ElseIf _PowerCalculationMethod = PowerMethod.VoltAmpCalc Then + Dim nomCurrent = Double.Parse(GetUPSVar("input.current.nominal")) + Dim nomVoltage = Double.Parse(GetUPSVar("input.voltage.nominal")) + + parsedValue = (nomCurrent * nomVoltage * 0.8) * (UPS_Datas.UPS_Value.Load / 100.0) + Else + Throw New InvalidOperationException("Insufficient variables to calculate power.") + End If + 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) + End Try + + .Output_Power = parsedValue + End If + + ' Handle cases of UPSs that are unable to report battery runtime or load correctly while on battery. Dim PowerDivider As Double = 0.5 Select Case .Load Case 76 To 100 @@ -282,14 +312,13 @@ Public Class UPS_Device Case 51 To 75 PowerDivider = 0.3 End Select + If .Batt_Charge = 255 Then Dim nBatt = Math.Floor(.Batt_Voltage / 12) .Batt_Charge = Math.Floor((.Batt_Voltage - (11.6 * nBatt)) / (0.02 * nBatt)) End If + If .Batt_Runtime >= 86400 Then - 'If Load is 0, the calculation results in infinity. This causes an exception in DataUpdated(), causing Me.Disconnect to run in the exception handler below. - 'Thus a connection is established, but is forcefully disconneced almost immediately. This cycle repeats on each connect until load is <> 0 - '(Example: I have a 0% load if only Pi, Microtik Router, Wifi AP and switches are running) .Load = If(.Load <> 0, .Load, 0.1) Dim BattInstantCurrent = (.Output_Voltage * .Load) / (.Batt_Voltage * 100) .Batt_Runtime = Math.Floor(.Batt_Capacity * 0.6 * .Batt_Charge * (1 - PowerDivider) * 3600 / (BattInstantCurrent * 100)) @@ -327,35 +356,6 @@ Public Class UPS_Device End Try End Sub - ''' - ''' Attempts to get the power usage of this UPS. - ''' - ''' - ''' - Private Function GetPowerUsage() As Double - If _PowerCalculationMethod = PowerMethod.RealPower Then - Return Integer.Parse(GetUPSVar("ups.realpower")) - ElseIf _PowerCalculationMethod = PowerMethod.NominalPowerCalc Then - Dim nomPower = GetUPSVar("ups.realpower.nominal") - Try - Return Integer.Parse(nomPower) * (UPS_Datas.UPS_Value.Load / 100.0) - Catch ex As Exception - LogFile.LogTracing("Failed to parse the nominal realpower: " & vbNewLine & ex.ToString() & - vbNewLine & vbNewLine & "nomPower is: " & nomPower & " Type: " & - nomPower.GetType().ToString(), LogLvl.LOG_ERROR, Me) - Return 0 - End Try - - ElseIf _PowerCalculationMethod = PowerMethod.VoltAmpCalc Then - Dim nomCurrent = Double.Parse(GetUPSVar("input.current.nominal")) - Dim nomVoltage = Double.Parse(GetUPSVar("input.voltage.nominal")) - - Return (nomCurrent * nomVoltage * 0.8) * (UPS_Datas.UPS_Value.Load / 100.0) - Else - Throw New InvalidOperationException("Insufficient variables to calculate power.") - End If - End Function - Private Const MAX_VAR_RETRIES = 3 Public Function GetUPSVar(varName As String, Optional Fallback_value As Object = Nothing, Optional recursing As Boolean = False) As String If Not IsConnected Then From 7c770865d9ff72c79290891ef9544b192a250810 Mon Sep 17 00:00:00 2001 From: gbakeman Date: Sun, 17 Dec 2023 13:38:19 -0500 Subject: [PATCH 3/3] UPS_Device: small cleanup, culture tweak - Create region for Static/ReadOnly variables for reference. - Change how the invariant culture is provided to code in this class, and attempting to make it as static as possible. - Applied invariant culture to load variable parsing code, hopefully fixing #125. - Completely define timers and connect event handlers in initializing code. --- WinNUT_V2/WinNUT-Client_Common/UPS_Device.vb | 60 ++++++++++++-------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/WinNUT_V2/WinNUT-Client_Common/UPS_Device.vb b/WinNUT_V2/WinNUT-Client_Common/UPS_Device.vb index ef96c42..3d17f24 100644 --- a/WinNUT_V2/WinNUT-Client_Common/UPS_Device.vb +++ b/WinNUT_V2/WinNUT-Client_Common/UPS_Device.vb @@ -11,6 +11,15 @@ Imports System.Globalization Imports System.Windows.Forms Public Class UPS_Device +#Region "Statics/Defaults" + Private ReadOnly INVARIANT_CULTURE = CultureInfo.InvariantCulture + Private Const CosPhi As Double = 0.6 + + ' How many milliseconds to wait before the Reconnect routine tries again. + Private Const DEFAULT_RECONNECT_WAIT_MS As Double = 5000 + Private Const DEFAULT_UPDATE_INTERVAL_MS As Double = 1000 +#End Region + #Region "Properties" Public ReadOnly Property Name As String @@ -35,6 +44,10 @@ Public Class UPS_Device End Get End Property + ''' + ''' How often UPS data is updated, in milliseconds. + ''' + ''' Public Property PollingInterval As Integer Get Return Update_Data.Interval @@ -102,16 +115,11 @@ Public Class UPS_Device #End Region - Private Const CosPhi As Double = 0.6 - ' How many milliseconds to wait before the Reconnect routine tries again. - Private Const DEFAULT_RECONNECT_WAIT_MS As Double = 5000 - Private WithEvents Update_Data As New Timer Private WithEvents Reconnect_Nut As New Timer Private WithEvents Nut_Socket As Nut_Socket Private Freq_Fallback As Double - Private ciClone As CultureInfo Public Nut_Config As Nut_Parameter Public Retry As Integer = 0 Public MaxRetry As Integer = 30 @@ -121,13 +129,18 @@ Public Class UPS_Device Me.LogFile = LogFile Me.Nut_Config = Nut_Config PollingInterval = pollInterval - ciClone = CType(CultureInfo.InvariantCulture.Clone(), CultureInfo) - ciClone.NumberFormat.NumberDecimalSeparator = "." Nut_Socket = New Nut_Socket(Me.Nut_Config, LogFile) With Reconnect_Nut .Interval = DEFAULT_RECONNECT_WAIT_MS .Enabled = False + AddHandler .Tick, AddressOf AttemptReconnect + End With + + With Update_Data + .Interval = DEFAULT_UPDATE_INTERVAL_MS + .Enabled = False + AddHandler .Tick, AddressOf Retrieve_UPS_Datas End With End Sub @@ -191,7 +204,7 @@ Public Class UPS_Device End If End Sub - Private Sub Reconnect_Socket(sender As Object, e As EventArgs) Handles Reconnect_Nut.Tick + Private Sub AttemptReconnect(sender As Object, e As EventArgs) Retry += 1 If Retry <= MaxRetry Then RaiseEvent New_Retry() @@ -250,16 +263,15 @@ Public Class UPS_Device End Try ' Other constant values for UPS calibration. - freshData.UPS_Value.Batt_Capacity = Double.Parse(GetUPSVar("battery.capacity", 7), ciClone) - Freq_Fallback = Double.Parse(GetUPSVar("output.frequency.nominal", (50 + CInt(Arr_Reg_Key.Item("FrequencySupply")) * 10)), ciClone) + freshData.UPS_Value.Batt_Capacity = Double.Parse(GetUPSVar("battery.capacity", 7), INVARIANT_CULTURE) + Freq_Fallback = Double.Parse(GetUPSVar("output.frequency.nominal", (50 + CInt(Arr_Reg_Key.Item("FrequencySupply")) * 10)), INVARIANT_CULTURE) LogFile.LogTracing("Completed retrieval of basic UPS product information.", LogLvl.LOG_NOTICE, Me) Return freshData End Function Private oldStatusBitmask As Integer - - Private Sub Retrieve_UPS_Datas() Handles Update_Data.Tick + Private Sub Retrieve_UPS_Datas(sender As Object, e As EventArgs) LogFile.LogTracing("Enter Retrieve_UPS_Datas", LogLvl.LOG_DEBUG, Me) Try @@ -267,13 +279,13 @@ Public Class UPS_Device If IsConnected Then With UPS_Datas.UPS_Value - .Batt_Charge = Double.Parse(GetUPSVar("battery.charge", 255), ciClone) - .Batt_Voltage = Double.Parse(GetUPSVar("battery.voltage", 12), ciClone) - .Batt_Runtime = Double.Parse(GetUPSVar("battery.runtime", 86400), ciClone) - .Power_Frequency = Double.Parse(GetUPSVar("input.frequency", Double.Parse(GetUPSVar("output.frequency", Freq_Fallback), ciClone)), ciClone) - .Input_Voltage = Double.Parse(GetUPSVar("input.voltage", 220), ciClone) - .Output_Voltage = Double.Parse(GetUPSVar("output.voltage", .Input_Voltage), ciClone) - .Load = Double.Parse(GetUPSVar("ups.load", 0), ciClone) + .Batt_Charge = Double.Parse(GetUPSVar("battery.charge", 255), INVARIANT_CULTURE) + .Batt_Voltage = Double.Parse(GetUPSVar("battery.voltage", 12), INVARIANT_CULTURE) + .Batt_Runtime = Double.Parse(GetUPSVar("battery.runtime", 86400), INVARIANT_CULTURE) + .Power_Frequency = Double.Parse(GetUPSVar("input.frequency", Double.Parse(GetUPSVar("output.frequency", Freq_Fallback), INVARIANT_CULTURE)), INVARIANT_CULTURE) + .Input_Voltage = Double.Parse(GetUPSVar("input.voltage", 220), INVARIANT_CULTURE) + .Output_Voltage = Double.Parse(GetUPSVar("output.voltage", .Input_Voltage), INVARIANT_CULTURE) + .Load = Double.Parse(GetUPSVar("ups.load", 0), INVARIANT_CULTURE) ' Retrieve and/or calculate output power if possible. If _PowerCalculationMethod <> PowerMethod.Unavailable Then @@ -281,15 +293,15 @@ Public Class UPS_Device Try If _PowerCalculationMethod = PowerMethod.RealPower Then - parsedValue = Double.Parse(GetUPSVar("ups.realpower")) + parsedValue = Double.Parse(GetUPSVar("ups.realpower"), INVARIANT_CULTURE) ElseIf _PowerCalculationMethod = PowerMethod.NominalPowerCalc Then - parsedValue = Double.Parse(GetUPSVar("ups.realpower.nominal")) + 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")) - Dim nomVoltage = Double.Parse(GetUPSVar("input.voltage.nominal")) + 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 @@ -331,7 +343,7 @@ Public Class UPS_Device .UPS_Status = [Enum].Parse(GetType(UPS_States), UPS_rt_Status) Catch ex As ArgumentException LogFile.LogTracing("Likely encountered an unknown/invalid UPS status. Using previous status." & - vbNewLine & ex.Message, LogLvl.LOG_ERROR, Me) + vbNewLine & ex.Message, LogLvl.LOG_ERROR, Me) End Try ' Get the difference between the old and new statuses, and filter only for active ones.