Ups Helper
Provides helper functions to interact with UPS devices via the external tool upsc.exe , part of NUT (Network UPS Tools).
The required upsc.exe and additional DLL can be imported in the project via the NuGet package Ipercube.NUT.Ups.Query.
- Relies on the presence of upsc.exe in the current working directory.
- Used to query status and parameters of network-connected UPS devices.
- Supports extraction and display of key UPS metrics.
VB
Imports System.IO
Imports System.Text
Public NotInheritable Class UpsHelper
''' <summary>
''' Executes <c>upsc.exe</c> for the given UPS name and returns all key/value
''' pairs as a dictionary.
''' </summary>
''' <category>Methods</category>
''' <param name="upsName">UPS name, e.g. <c>"apcups@10.69.1.42"</c>.</param>
''' <returns>A <c>Dictionary(Of String, String)</c> with all available key/value pairs.</returns>
''' <remarks>
''' <list type="bullet">
''' <item><description>Throws an exception if <c>upsc.exe</c> is missing or execution fails.</description></item>
''' <item><description>Output is parsed line by line; expected format is <c>key: value</c>.</description></item>
''' <item><description>If a line has no colon, it is treated as a key with empty value.</description></item>
''' <item><description>In case of duplicate keys, the last occurrence overwrites the previous one.</description></item>
''' </list>
''' </remarks>
Public Shared Function GetUpsInfo(upsName As String) As Dictionary(Of String, String)
If String.IsNullOrWhiteSpace(upsName) Then
Throw New ArgumentException("UPS name is empty.", NameOf(upsName))
End If
If Not File.Exists("upsc.exe") Then
Throw New FileNotFoundException("upsc.exe not found.", "upsc.exe")
End If
Dim psi As New ProcessStartInfo() With {
.FileName = "upsc.exe",
.Arguments = upsName,
.UseShellExecute = False,
.RedirectStandardOutput = True,
.RedirectStandardError = True,
.CreateNoWindow = True
}
Dim output As String
Dim err As String
Using proc As New Process()
proc.StartInfo = psi
If Not proc.Start() Then
Throw New InvalidOperationException("Unable to start upsc.exe process.")
End If
output = proc.StandardOutput.ReadToEnd()
err = proc.StandardError.ReadToEnd()
proc.WaitForExit()
Dim exitCode As Integer = proc.ExitCode
If exitCode <> 0 Then
Dim msg As New StringBuilder()
msg.AppendLine("upsc.exe returned a non-zero exit code: " & exitCode.ToString())
If Not String.IsNullOrWhiteSpace(err) Then
msg.AppendLine()
msg.AppendLine("Error output:")
msg.AppendLine(err.Trim())
End If
Throw New ApplicationException(msg.ToString())
End If
End Using
If String.IsNullOrWhiteSpace(output) Then
Throw New ApplicationException("upsc.exe returned no data.")
End If
Dim result As New Dictionary(Of String, String)(StringComparer.OrdinalIgnoreCase)
Dim lines() As String = output.Replace(vbCrLf, vbLf).Split(ControlChars.Lf)
For Each rawLine As String In lines
Dim line As String = rawLine.Trim()
If line.Length = 0 Then Continue For
' Expected format: key: value
Dim idx As Integer = line.IndexOf(":"c)
If idx <= 0 Then
' Line without ':' -> store whole line as key with empty value
If Not result.ContainsKey(line) Then
result(line) = String.Empty
End If
Else
Dim key As String = line.Substring(0, idx).Trim()
Dim value As String = line.Substring(idx + 1).Trim()
If Not result.ContainsKey(key) Then
result(key) = value
Else
' In case of duplicated keys, you might want to append or overwrite.
' Here we overwrite with the last occurrence.
result(key) = value
End If
End If
Next
Return result
End Function
''' <summary>
''' Executes <c>upsc.exe</c> for the given UPS name and shows all key/value pairs
''' in a MessageBox, one per line. Some important keys are shown first.
''' </summary>
''' <category>Methods</category>
''' <param name="upsName">UPS name, e.g. <c>"apcups@10.69.1.42"</c>.</param>
''' <remarks>
''' <list type="bullet">
''' <item><description>Uses <c>GetUpsInfo</c> to extract data from the UPS device.</description></item>
''' <item><description>Important keys like battery charge, voltage, and status are shown at the top.</description></item>
''' <item><description>Remaining keys are sorted alphabetically and shown below.</description></item>
''' <item><description>All values are formatted with units when recognized.</description></item>
''' <item><description>Displays the result in a styled HTML MessageBox using <c>ShowMessageBoxEx</c>.</description></item>
''' </list>
''' </remarks>
Public Shared Sub ShowUpsInfo(upsName As String)
Dim info As Dictionary(Of String, String)
Try
info = GetUpsInfo(upsName)
Catch ex As Exception
ShowMessageBoxEx(ex.Message, "UPS info - Error",
CustomButtons.Ok, CustomIcon.ErrorBug)
Return
End Try
If info Is Nothing OrElse info.Count = 0 Then
ShowMessageBoxEx("No data returned from upsc.exe.", "UPS info",
CustomButtons.Ok, CustomIcon.ErrorBug)
Return
End If
' Important keys to show first (if present)
Dim importantKeys As String() = {
"ups.mfr",
"ups.model",
"ups.status",
"battery.charge",
"battery.runtime",
"ups.load",
"input.voltage",
"input.frequency",
"battery.voltage",
"ups.realpower.nominal",
"ups.delay.shutdown"
}
Dim sb As New StringBuilder()
sb.AppendLine("Key" & vbTab & "Value")
' Track printed keys so they are not duplicated later
Dim printed As New HashSet(Of String)(StringComparer.OrdinalIgnoreCase)
' 1) Print important keys (in fixed order)
For Each k As String In importantKeys
Dim value As String = Nothing
If info.TryGetValue(k, value) Then
value = AddUnitOfMeasure(k, value)
sb.AppendLine(k & vbTab & value)
printed.Add(k)
End If
Next
' 3) Print all keys alphabetically, skipping the already printed ones
Dim allKeys As List(Of String) = info.Keys.ToList()
allKeys.Sort(StringComparer.OrdinalIgnoreCase)
For Each k As String In allKeys
If printed.Contains(k) Then Continue For
Dim v As String = info(k)
v = AddUnitOfMeasure(k, v)
sb.AppendLine(k & vbTab & v)
Next
Dim caption As String = "UPS status - " & upsName
ShowMessageBoxEx(
ConvertTabbedString2HtmlPage(sb.ToString()),
caption,
CustomButtons.Ok,
CustomIcon.Statistics,
layoutKey:="ShowUpsInfo"
)
End Sub
Private Shared Function AddUnitOfMeasure(Key As String, ByVal value As String) As String
Select Case Key
Case "battery.runtime"
Dim totalSeconds As Integer
If Integer.TryParse(Convert.ToString(value), totalSeconds) Then
Dim hours As Integer = totalSeconds \ 3600
Dim minutes As Integer = totalSeconds \ 60
Dim seconds As Integer = totalSeconds Mod 60
If totalSeconds < 3600 Then
value = $"{minutes:00}:{seconds:00} (mm:ss)"
Else
value = $"{hours}:{minutes:00}:{seconds:00} (hh:mm:ss)"
End If
Else
value = "00:00"
End If
Case "battery.voltage.nominal", "input.voltage.nominal", "input.voltage", "battery.voltage", "input.transfer.high", "input.transfer.low"
value += " Volt"
Case "ups.realpower.nominal"
value += " Watt"
Case "ups.load", "battery.charge.low", "battery.charge.warning", "battery.charge"
value += "%"
Case "ups.delay.shutdown", "battery.runtime.low", "ups.timer.reboot", "driver.parameter.pollfreq", "driver.parameter.pollinterval"
value += " sec"
Case "ups.timer.shutdown"
value = If(value.Trim = "-1", "Disabled", value & " sec")
Case "ups.status"
Select Case value
Case "OL" : value = "On Line, AC power is available"
Case "OB" : value = "Running on Battery"
Case "LB" : value = "Battery level LOW"
Case "FSD" : value = "Forced Shutdown"
Case Else
If value.Contains("OB") And value.Contains("OL") Then
value = "Running on Battery, Battery level LOW"
Else
value = "Unknown status: " & value
End If
End Select
End Select
Return value
End Function
End Class