Expand UPS telemetry, charts and raw diagnostics with power estimates
This commit is contained in:
49
ups.go
49
ups.go
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -10,6 +11,13 @@ import (
|
||||
)
|
||||
|
||||
type UPSMetrics struct {
|
||||
Raw map[string]string `json:"raw"`
|
||||
NominalPower *float64 `json:"nominalPowerWatts"`
|
||||
Power *float64 `json:"powerWatts"`
|
||||
PowerEstimated bool `json:"powerEstimated"`
|
||||
InputFrequencyRaw *float64 `json:"inputFrequencyRaw"`
|
||||
FrequencyCorrected bool `json:"frequencyCorrected"`
|
||||
|
||||
Available bool `json:"available"`
|
||||
Source string `json:"source"`
|
||||
Name string `json:"name"`
|
||||
@@ -92,14 +100,16 @@ func readAPCUPSD() (UPSMetrics, bool) {
|
||||
}
|
||||
raw := parseColonValues(string(output))
|
||||
values := map[string]string{
|
||||
"device.model": raw["MODEL"], "ups.status": raw["STATUS"],
|
||||
"device.model": raw["MODEL"], "ups.realpower.nominal": raw["NOMPOWER"], "ups.status": raw["STATUS"],
|
||||
"battery.charge": raw["BCHARGE"], "battery.runtime": minutesToSeconds(raw["TIMELEFT"]),
|
||||
"ups.load": raw["LOADPCT"], "input.voltage": raw["LINEV"],
|
||||
"output.voltage": raw["OUTPUTV"], "input.frequency": raw["LINEFREQ"],
|
||||
"battery.voltage": raw["BATTV"], "ups.temperature": raw["ITEMP"],
|
||||
"battery.date": raw["BATTDATE"], "battery.charge.low": raw["MBATTCHG"], "battery.runtime.low": minutesToSeconds(raw["MINTIMEL"]),
|
||||
}
|
||||
return upsFromValues("apcupsd", raw["UPSNAME"], values), true
|
||||
m := upsFromValues("apcupsd", raw["UPSNAME"], values)
|
||||
m.Raw = raw
|
||||
return m, true
|
||||
}
|
||||
|
||||
func parseColonValues(output string) map[string]string {
|
||||
@@ -116,7 +126,7 @@ func parseColonValues(output string) map[string]string {
|
||||
func upsFromValues(source, name string, values map[string]string) UPSMetrics {
|
||||
status := values["ups.status"]
|
||||
metrics := UPSMetrics{
|
||||
Available: true, CommunicationOK: true, Source: source, Name: name, Model: values["device.model"],
|
||||
Available: true, CommunicationOK: true, Source: source, Name: name, Model: firstNonEmpty(values["device.model"], values["ups.model"]), Raw: values,
|
||||
Status: status, StatusLabel: upsStatusLabel(status),
|
||||
ChargePercent: numberPointer(values["battery.charge"]), RuntimeSeconds: numberPointer(values["battery.runtime"]),
|
||||
LoadPercent: numberPointer(values["ups.load"]), InputVoltage: numberPointer(values["input.voltage"]),
|
||||
@@ -124,13 +134,44 @@ func upsFromValues(source, name string, values map[string]string) UPSMetrics {
|
||||
BatteryVoltage: numberPointer(values["battery.voltage"]), Temperature: numberPointer(firstNonEmpty(values["ups.temperature"], values["battery.temperature"])),
|
||||
BatteryDate: firstNonEmpty(values["battery.date"], values["battery.mfr.date"]), ReplaceBattery: strings.Contains(status, "RB"), LowBatteryCharge: numberPointer(values["battery.charge.low"]), LowBatteryRuntime: numberPointer(values["battery.runtime.low"]),
|
||||
}
|
||||
metrics.NominalPower = numberPointer(values["ups.realpower.nominal"])
|
||||
metrics.Power = numberPointer(values["ups.realpower"])
|
||||
if metrics.Power != nil && *metrics.Power < 0 {
|
||||
metrics.Power = nil
|
||||
}
|
||||
if metrics.NominalPower != nil && *metrics.NominalPower <= 0 {
|
||||
metrics.NominalPower = nil
|
||||
}
|
||||
if metrics.Power == nil && metrics.NominalPower != nil && metrics.LoadPercent != nil && *metrics.LoadPercent >= 0 {
|
||||
watts := *metrics.NominalPower * *metrics.LoadPercent / 100
|
||||
if !math.IsInf(watts, 0) {
|
||||
metrics.Power = &watts
|
||||
metrics.PowerEstimated = true
|
||||
}
|
||||
}
|
||||
metrics.InputFrequencyRaw = metrics.InputFrequency
|
||||
mfr := strings.ToLower(firstNonEmpty(values["device.mfr"], values["ups.mfr"]))
|
||||
cyberpower := mfr == "cps" || strings.Contains(mfr, "cyberpower") || values["ups.vendorid"] == "0764"
|
||||
if source == "NUT" && cyberpower && metrics.InputFrequency != nil && *metrics.InputFrequency >= 450 && *metrics.InputFrequency <= 650 {
|
||||
hz := *metrics.InputFrequency / 10
|
||||
metrics.InputFrequency = &hz
|
||||
metrics.FrequencyCorrected = true
|
||||
}
|
||||
if metrics.BatteryDate != "" {
|
||||
validDate := false
|
||||
for _, layout := range []string{"2006/01/02", "2006-01-02", "01/02/2006", "02/01/2006"} {
|
||||
if date, err := time.Parse(layout, metrics.BatteryDate); err == nil {
|
||||
if date.After(time.Now()) {
|
||||
continue
|
||||
}
|
||||
validDate = true
|
||||
metrics.BatteryAgeDays = int(time.Since(date).Hours() / 24)
|
||||
break
|
||||
}
|
||||
}
|
||||
if !validDate {
|
||||
metrics.BatteryDate = ""
|
||||
}
|
||||
}
|
||||
return metrics
|
||||
}
|
||||
@@ -141,7 +182,7 @@ func numberPointer(value string) *float64 {
|
||||
return nil
|
||||
}
|
||||
number, err := strconv.ParseFloat(strings.TrimSuffix(field[0], "%"), 64)
|
||||
if err != nil {
|
||||
if err != nil || math.IsNaN(number) || math.IsInf(number, 0) {
|
||||
return nil
|
||||
}
|
||||
return &number
|
||||
|
||||
Reference in New Issue
Block a user