master
go 137 lines 2.95 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package apcupsd
4
5 import (
6 "bufio"
7 "bytes"
8 "fmt"
9 "strconv"
10 "strings"
11 )
12
13 var upsStatuses = []string{
14 "CAL",
15 "TRIM",
16 "BOOST",
17 "ONLINE",
18 "ONBATT",
19 "OVERLOAD",
20 "LOWBATT",
21 "REPLACEBATT",
22 "NOBATT",
23 "SLAVE",
24 "SLAVEDOWN",
25 "COMMLOST",
26 "SHUTTING_DOWN",
27 }
28
29 var upsSelftestStatuses = []string{
30 "NO",
31 "NG",
32 "WN",
33 "IP",
34 "OK",
35 "BT",
36 "UNK",
37 }
38
39 // examples: https://github.com/therealbstern/apcupsd/tree/master/examples/status
40 type apcupsdStatus struct {
41 bcharge *float64 // battery charge level (percentage)
42 battv *float64 // battery voltage (Volts)
43 nombattv *float64 // nominal battery voltage (Volts)
44 linev *float64 // line voltage (Volts)
45 minlinev *float64 // min line voltage (Volts)
46 maxlinev *float64 // max line voltage (Volts)
47 linefreq *float64 // line frequency (Hz)
48 outputv *float64 // output voltage (Volts)
49 nomoutv *float64 // nominal output voltage (Volts)
50 loadpct *float64 // UPS Load (Percent Load Capacity)
51 itemp *float64 // internal UPS temperature (Celsius)
52 nompower *float64 // nominal power (Watts)
53 timeleft *float64 // estimated runtime left (minutes)
54 battdate string // Last battery change date (MM/DD/YY or YYYY-MM-DD)
55 status string
56 selftest string
57 }
58
59 func parseStatus(resp []byte) (*apcupsdStatus, error) {
60 var st apcupsdStatus
61 sc := bufio.NewScanner(bytes.NewBuffer(resp))
62
63 for sc.Scan() {
64 line := sc.Text()
65
66 key, value, ok := strings.Cut(line, ":")
67 if !ok {
68 continue
69 }
70
71 key, value = strings.TrimSpace(key), strings.TrimSpace(value)
72
73 if value == "N/A" {
74 continue
75 }
76
77 var err error
78
79 // https://github.com/therealbstern/apcupsd/blob/224d19d5faa508d04267f6135fe53d50800550de/src/lib/apcstatus.c#L30
80 switch key {
81 case "BCHARGE":
82 st.bcharge, err = parseFloat(value)
83 case "BATTV":
84 st.battv, err = parseFloat(value)
85 case "NOMBATTV":
86 st.nombattv, err = parseFloat(value)
87 case "LINEV":
88 st.linev, err = parseFloat(value)
89 case "MINLINEV":
90 st.minlinev, err = parseFloat(value)
91 case "MAXLINEV":
92 st.maxlinev, err = parseFloat(value)
93 case "LINEFREQ":
94 st.linefreq, err = parseFloat(value)
95 case "OUTPUTV":
96 st.outputv, err = parseFloat(value)
97 case "NOMOUTV":
98 st.nomoutv, err = parseFloat(value)
99 case "LOADPCT":
100 st.loadpct, err = parseFloat(value)
101 case "ITEMP":
102 st.itemp, err = parseFloat(value)
103 case "NOMPOWER":
104 st.nompower, err = parseFloat(value)
105 case "TIMELEFT":
106 st.timeleft, err = parseFloat(value)
107 case "BATTDATE":
108 st.battdate = value
109 case "STATUS":
110 if value == "SHUTTING DOWN" {
111 value = "SHUTTING_DOWN"
112 }
113 st.status = value
114 case "SELFTEST":
115 if value == "??" {
116 value = "UNK"
117 }
118 st.selftest = value
119 default:
120 continue
121 }
122 if err != nil {
123 return nil, fmt.Errorf("line '%s': %v", line, err)
124 }
125 }
126
127 return &st, nil
128 }
129
130 func parseFloat(s string) (*float64, error) {
131 val, _, _ := strings.Cut(s, " ")
132 f, err := strconv.ParseFloat(val, 64)
133 if err != nil {
134 return nil, err
135 }
136 return &f, nil
137 }