Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correct FormatException when parsing Invariant-locale numbers in user's locale #125

Merged
merged 3 commits into from
Dec 20, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
115 changes: 69 additions & 46 deletions WinNUT_V2/WinNUT-Client_Common/UPS_Device.vb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -35,6 +44,10 @@ Public Class UPS_Device
End Get
End Property

''' <summary>
''' How often UPS data is updated, in milliseconds.
''' </summary>
''' <returns></returns>
Public Property PollingInterval As Integer
Get
Return Update_Data.Interval
Expand Down Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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()
Expand All @@ -216,13 +229,15 @@ Public Class UPS_Device
''' </summary>
''' <returns></returns>
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
Expand All @@ -248,45 +263,74 @@ 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

Public Sub Retrieve_UPS_Datas() Handles Update_Data.Tick ' As UPSData
Private Sub Retrieve_UPS_Datas(sender As Object, e As EventArgs)
LogFile.LogTracing("Enter Retrieve_UPS_Datas", LogLvl.LOG_DEBUG, Me)

Try
Dim UPS_rt_Status As String

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)
.Output_Power = If(_PowerCalculationMethod <> PowerMethod.Unavailable, GetPowerUsage(), 0)
.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
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
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
PowerDivider = 0.4
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))
Expand All @@ -299,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.
Expand All @@ -324,27 +368,6 @@ Public Class UPS_Device
End Try
End Sub

''' <summary>
''' Attempts to get the power usage of this UPS.
''' </summary>
''' <returns></returns>
''' <throws><see cref="NutException"/></throws>
Private Function GetPowerUsage() As Double
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)
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
Expand Down