Skip to content

Commit

Permalink
More robust float parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
viral32111 committed Aug 28, 2024
1 parent 3c92709 commit 801aecc
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 37 deletions.
2 changes: 1 addition & 1 deletion source/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
// Metadata
const (
PROJECT_NAME = "APC UPS Exporter"
PROJECT_VERSION = "1.2.0"
PROJECT_VERSION = "1.2.1"

AUTHOR_NAME = "viral32111"
AUTHOR_WEBSITE = "https://viral32111.com"
Expand Down
65 changes: 29 additions & 36 deletions source/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ func ParseStatusText( text string ) ( status Status, err error ) {
for _, line := range lines {

// Skip lines that are empty
line = strings.TrimSpace( line )
if line == "" { continue }

// Parse the line into key & value
Expand Down Expand Up @@ -202,79 +203,79 @@ func ParseStatusText( text string ) ( status Status, err error ) {

// "The current line voltage as returned by the UPS"
case "LINEV": {
parsedFloat, floatParseError := strconv.ParseFloat( value, 64 )
parsedFloat, floatParseError := parseAsFloat( value, -1 )
if floatParseError != nil { return Status{}, floatParseError }

status.UPS.LineVoltage = parsedFloat
}

// "The percentage of load capacity as estimated by the UPS"
case "LOADPCT": {
parsedFloat, floatParseError := strconv.ParseFloat( value, 64 )
parsedFloat, floatParseError := parseAsFloat( value, -1 )
if floatParseError != nil { return Status{}, floatParseError }

status.UPS.LoadPercent = parsedFloat
}

// "The percentage charge on the batteries"
case "BCHARGE": {
parsedFloat, floatParseError := strconv.ParseFloat( value, 64 )
parsedFloat, floatParseError := parseAsFloat( value, -1 )
if floatParseError != nil { return Status{}, floatParseError }

status.UPS.Battery.ChargePercent = parsedFloat
}

// "The remaining runtime left on batteries as estimated by the UPS"
case "TIMELEFT": {
parsedFloat, floatParseError := strconv.ParseFloat( value, 64 )
parsedFloat, floatParseError := parseAsFloat( value, -1 )
if floatParseError != nil { return Status{}, floatParseError }

status.UPS.Battery.RemainingRuntimeMinutes = parsedFloat
}

// "If the battery charge percentage (BCHARGE) drops below this value, apcupsd will shutdown your system. Value is set in the configuration file (BATTERYLEVEL)"
case "MBATTCHG": {
parsedFloat, floatParseError := strconv.ParseFloat( value, 64 )
parsedFloat, floatParseError := parseAsFloat( value, -1 )
if floatParseError != nil { return Status{}, floatParseError }

status.Daemon.Configuration.MinimumBatteryChargePercent = parsedFloat
}

// "apcupsd will shutdown your system if the remaining runtime equals or is below this point. Value is set in the configuration file (MINUTES)"
case "MINTIMEL": {
parsedFloat, floatParseError := strconv.ParseFloat( value, 64 )
parsedFloat, floatParseError := parseAsFloat( value, -1 )
if floatParseError != nil { return Status{}, floatParseError }

status.Daemon.Configuration.MinimumBatteryRemainingRuntimeMinutes = parsedFloat
}

// "apcupsd will shutdown your system if the time on batteries exceeds this value. A value of zero disables the feature. Value is set in the configuration file (TIMEOUT)"
case "MAXTIME": {
parsedFloat, floatParseError := strconv.ParseFloat( value, 64 )
parsedFloat, floatParseError := parseAsFloat( value, -1 )
if floatParseError != nil { return Status{}, floatParseError }

status.Daemon.Configuration.MaximumTimeoutMinutes = parsedFloat
}

// SmartUPS X 3000 - "The maximum line voltage since the last STATUS as returned by the UPS."
case "MAXLINEV": {
parsedFloat, floatParseError := strconv.ParseFloat( value, 64 )
parsedFloat, floatParseError := parseAsFloat( value, -1 )
if floatParseError != nil { return Status{}, floatParseError }

status.UPS.MaximumLineVoltage = parsedFloat
}

// SmartUPS X 3000 - "The minimum line voltage since the last STATUS as returned by the UPS."
case "MINLINEV": {
parsedFloat, floatParseError := strconv.ParseFloat( value, 64 )
parsedFloat, floatParseError := parseAsFloat( value, -1 )
if floatParseError != nil { return Status{}, floatParseError }

status.UPS.MinimumLineVoltage = parsedFloat
}

// SmartUPS X 3000 - "The voltage the UPS is supplying to your equipment."
case "OUTPUTV": {
parsedFloat, floatParseError := strconv.ParseFloat( value, 64 )
parsedFloat, floatParseError := parseAsFloat( value, -1 )
if floatParseError != nil { return Status{}, floatParseError }

status.UPS.OutputVoltage = parsedFloat
Expand All @@ -285,59 +286,55 @@ func ParseStatusText( text string ) ( status Status, err error ) {

// SmartUPS X 3000 - "The remaining runtime below which the UPS sends the low battery signal. At this point apcupsd will force an immediate emergency shutdown. "
case "DLOWBATT": {
parsedFloat, floatParseError := strconv.ParseFloat( value, 64 )
parsedFloat, floatParseError := parseAsFloat( value, -1 )
if floatParseError != nil { return Status{}, floatParseError }

status.UPS.Battery.LowBatterySignalThreshold = parsedFloat
}

// "The line voltage below which the UPS will switch to batteries"
case "LOTRANS": {
parsedFloat, floatParseError := strconv.ParseFloat( value, 64 )
parsedFloat, floatParseError := parseAsFloat( value, -1 )
if floatParseError != nil { return Status{}, floatParseError }

status.Daemon.Battery.Transfer.LowLineVoltage = parsedFloat
}

// "The line voltage above which the UPS will switch to batteries"
case "HITRANS": {
parsedFloat, floatParseError := strconv.ParseFloat( value, 64 )
parsedFloat, floatParseError := parseAsFloat( value, -1 )
if floatParseError != nil { return Status{}, floatParseError }

status.Daemon.Battery.Transfer.HighLineVoltage = parsedFloat
}

// SmartUPS X 3000 - "The internal UPS temperature as supplied by the UPS."
case "ITEMP": {
parsedFloat, floatParseError := strconv.ParseFloat( value, 64 )
parsedFloat, floatParseError := parseAsFloat( value, -1 )
if floatParseError != nil { return Status{}, floatParseError }

status.UPS.Temperature = parsedFloat
}

// "The delay period for the UPS alarm"
case "ALARMDEL": {
if ( value == "No alarm" ) {
status.UPS.AlarmIntervalSeconds = -1
} else {
parsedFloat, floatParseError := strconv.ParseFloat( value, 64 )
if floatParseError != nil { return Status{}, floatParseError }
parsedFloat, floatParseError := parseAsFloat( value, -1 )
if floatParseError != nil { return Status{}, floatParseError }

status.UPS.AlarmIntervalSeconds = parsedFloat
}
status.UPS.AlarmIntervalSeconds = parsedFloat
}

// "Battery voltage as supplied by the UPS"
case "BATTV": {
parsedFloat, floatParseError := strconv.ParseFloat( value, 64 )
parsedFloat, floatParseError := parseAsFloat( value, -1 )
if floatParseError != nil { return Status{}, floatParseError }

status.UPS.Battery.OutputVoltage = parsedFloat
}

// SmartUPS X 3000 - "The line frequency in Hertz as given by the UPS."
case "LINEFREQ": {
parsedFloat, floatParseError := strconv.ParseFloat( value, 64 )
parsedFloat, floatParseError := parseAsFloat( value, -1 )
if floatParseError != nil { return Status{}, floatParseError }

status.UPS.LineFrequency = parsedFloat
Expand All @@ -348,31 +345,31 @@ func ParseStatusText( text string ) ( status Status, err error ) {

// "The number of transfers to batteries since apcupsd startup"
case "NUMXFERS": {
parsedFloat, floatParseError := strconv.ParseFloat( value, 64 )
parsedFloat, floatParseError := parseAsFloat( value, -1 )
if floatParseError != nil { return Status{}, floatParseError }

status.Daemon.Battery.Transfer.Total = parsedFloat
}

// "Time in seconds currently on batteries, or 0"
case "TONBATT": {
parsedFloat, floatParseError := strconv.ParseFloat( value, 64 )
parsedFloat, floatParseError := parseAsFloat( value, -1 )
if floatParseError != nil { return Status{}, floatParseError }

status.Daemon.Battery.TimeSpent.Current = parsedFloat
}

// "Total (cumulative) time on batteries in seconds since apcupsd startup"
case "CUMONBATT": {
parsedFloat, floatParseError := strconv.ParseFloat( value, 64 )
parsedFloat, floatParseError := parseAsFloat( value, -1 )
if floatParseError != nil { return Status{}, floatParseError }

status.Daemon.Battery.TimeSpent.Total = parsedFloat
}

// SmartUPS X 3000 - "Time and date of last transfer from batteries, or N/A."
case "XOFFBATT": {
if ( value == "N/A" ) {
if (value == "N/A") {
status.Daemon.Battery.Transfer.LastAt = time.Unix(0, 0)
} else {
parsedDate, dateParseError := time.Parse( "2006-01-02 15:04:05 -0700", value )
Expand All @@ -387,11 +384,7 @@ func ParseStatusText( text string ) ( status Status, err error ) {

// SmartUPS X 3000 - "The interval in hours between automatic self tests."
case "STESTI": {
if (value == "OFF") {
status.UPS.SelfTestInterval = -1
}

parsedFloat, floatParseError := strconv.ParseFloat( value, 64 )
parsedFloat, floatParseError := parseAsFloat( value, -1 )
if floatParseError != nil { return Status{}, floatParseError }

status.UPS.SelfTestInterval = parsedFloat
Expand Down Expand Up @@ -432,31 +425,31 @@ func ParseStatusText( text string ) ( status Status, err error ) {

// "The input voltage that the UPS is configured to expect"
case "NOMINV": {
parsedFloat, floatParseError := strconv.ParseFloat( value, 64 )
parsedFloat, floatParseError := parseAsFloat( value, -1 )
if floatParseError != nil { return Status{}, floatParseError }

status.UPS.Expect.MainsInputVoltage = parsedFloat
}

// "The nominal battery voltage"
case "NOMBATTV": {
parsedFloat, floatParseError := strconv.ParseFloat( value, 64 )
parsedFloat, floatParseError := parseAsFloat( value, -1 )
if floatParseError != nil { return Status{}, floatParseError }

status.UPS.Expect.BatteryOutputVoltage = parsedFloat
}

// SmartUPS X 3000 - "The number of external batteries as defined by the user. A correct number here helps the UPS compute the remaining runtime more accurately.""
case "EXTBATTS": {
parsedFloat, floatParseError := strconv.ParseFloat( value, 64 )
parsedFloat, floatParseError := parseAsFloat( value, -1 )
if floatParseError != nil { return Status{}, floatParseError }

status.UPS.Battery.ExternalCount = parsedFloat
}

// "The maximum power in Watts that the UPS is designed to supply"
case "NOMPOWER": {
parsedFloat, floatParseError := strconv.ParseFloat( value, 64 )
parsedFloat, floatParseError := parseAsFloat( value, -1 )
if floatParseError != nil { return Status{}, floatParseError }

status.UPS.Expect.PowerOutputWattage = parsedFloat
Expand Down
19 changes: 19 additions & 0 deletions source/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package main

import (
"regexp"
"strconv"
"strings"
)

// Smartly parses a string as a float using only the numeric components.
func parseAsFloat(value string, fallback float64) (float64, error) {
numericValue := regexp.MustCompile("[^0-9.]+").ReplaceAllString(value, "") // Strip any non-numeric characters (except for the decimal point)
numericValue = regexp.MustCompile(`\.{2,}`).ReplaceAllString(numericValue, ".") // Collapse multiple decimal points into one
numericValue = strings.Trim(numericValue, ".") // Trim any leading/trailing decimal points
numericValue = strings.TrimSpace(numericValue) // Trim any whitespace

if numericValue == "" { return fallback, nil }

return strconv.ParseFloat(numericValue, 64)
}

0 comments on commit 801aecc

Please sign in to comment.