master
go 115 lines 2.07 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package apcupsd
4
5 import (
6 "bytes"
7 "encoding/binary"
8 "io"
9 "net"
10 "time"
11 )
12
13 type apcupsdConn interface {
14 connect() error
15 disconnect() error
16 status() ([]byte, error)
17 }
18
19 func newUpsdConn(conf Config) apcupsdConn {
20 return &apcupsdClient{
21 address: conf.Address,
22 timeout: conf.Timeout.Duration(),
23 }
24 }
25
26 type apcupsdClient struct {
27 address string
28 timeout time.Duration
29 conn net.Conn
30 }
31
32 func (c *apcupsdClient) connect() error {
33 if c.conn != nil {
34 _ = c.disconnect()
35 }
36
37 conn, err := net.DialTimeout("tcp", c.address, c.timeout)
38 if err != nil {
39 return err
40 }
41
42 c.conn = conn
43
44 return nil
45 }
46
47 func (c *apcupsdClient) disconnect() error {
48 if c.conn != nil {
49 err := c.conn.Close()
50 c.conn = nil
51 return err
52 }
53 return nil
54 }
55
56 func (c *apcupsdClient) status() ([]byte, error) {
57 if err := c.send("status"); err != nil {
58 return nil, err
59 }
60 return c.receive()
61 }
62
63 func (c *apcupsdClient) send(cmd string) error {
64 // https://github.com/therealbstern/apcupsd/blob/224d19d5faa508d04267f6135fe53d50800550de/src/lib/apclibnis.c#L153
65
66 msgLength := make([]byte, 2)
67
68 binary.BigEndian.PutUint16(msgLength, uint16(len(cmd)))
69
70 if err := c.conn.SetWriteDeadline(c.deadline()); err != nil {
71 return err
72 }
73
74 if _, err := c.conn.Write(append(msgLength, cmd...)); err != nil {
75 return err
76 }
77
78 return nil
79 }
80
81 func (c *apcupsdClient) receive() ([]byte, error) {
82 // https://github.com/therealbstern/apcupsd/blob/224d19d5faa508d04267f6135fe53d50800550de/src/apcnis.c#L54
83
84 var buf bytes.Buffer
85 msgLength := make([]byte, 2)
86
87 for {
88 if err := c.conn.SetReadDeadline(c.deadline()); err != nil {
89 return nil, err
90 }
91
92 if _, err := io.ReadFull(c.conn, msgLength); err != nil {
93 return nil, err
94 }
95
96 length := binary.BigEndian.Uint16(msgLength)
97 if length == 0 {
98 break
99 }
100
101 if err := c.conn.SetReadDeadline(c.deadline()); err != nil {
102 return nil, err
103 }
104
105 if _, err := io.CopyN(&buf, c.conn, int64(length)); err != nil {
106 return nil, err
107 }
108 }
109
110 return buf.Bytes(), nil
111 }
112
113 func (c *apcupsdClient) deadline() time.Time {
114 return time.Now().Add(c.timeout)
115 }