diff --git a/README.md b/README.md index ca004e7..173a79b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,34 @@ -Package nut implements the Network UPS Tools network protocol. +# uni-nut +This is a subset of [go-nut](https://github.com/dominikh/go-nut) meant specifically for connecting to and monitoring variables on Ubiquiti UniFi UPS NUT servers. -This package only implements those functions necessary for the -nut_exporter; it is therefore not complete. +Ubiquiti UPS units seem to use a somewhat non-standard format for responses from the in-built NUT server. Because of this, many existing NUT clients (including the module this project was forked off of) fail to function with Ubiquiti UPS units. + +## Usage +1. Enable the NUT server on your Ubiquiti UniFi-connected UPS unit. + - Do **NOT** enable the "Login Credential" option! +2. This module can now be used to get variables from your UPS's NUT server as follows: +```go +package main + +import ( + "fmt" + + nut "github.com/rwinkhart/uni-nut" +) + +func main() { + host := "IP:PORT" + upsID := "UPS-ID" + client, err := nut.Dial(host) + if err != nil { + panic(err) + } + err = client.GetListVar(upsID) + if err != nil { + panic(err) + } + for key, value := range nut.NutKeyValMap { + fmt.Println(key + ": " + value) + } +} +``` diff --git a/nut.go b/nut.go index d4f7428..a8f605a 100644 --- a/nut.go +++ b/nut.go @@ -1,15 +1,9 @@ -// Package nut implements the Network UPS Tools network protocol. -// -// This package only implements those functions necessary for the -// nut_exporter; it is therefore not complete. package nut import ( "bufio" - "errors" "fmt" "net" - "strconv" "strings" ) @@ -19,6 +13,9 @@ type Client struct { br *bufio.Reader } +// Global map to be updated by GetListVar and read by the importing program. +var NutKeyValMap = make(map[string]string) + // Dial dials a NUT server using TCP. If the address does not contain // a port number, it will default to 3493. func Dial(addr string) (*Client, error) { @@ -31,12 +28,7 @@ func Dial(addr string) (*Client, error) { if err != nil { return nil, err } - return NewClient(conn), nil -} - -// NewClient wraps an existing net.Conn. -func NewClient(conn net.Conn) *Client { - return &Client{conn, bufio.NewReader(conn)} + return newClient(conn), nil } // Close closes the connection. @@ -44,80 +36,34 @@ func (c *Client) Close() error { return c.conn.Close() } -func (c *Client) list(typ string) ([]string, error) { - cmd := "LIST " + typ +func (c *Client) GetListVar(upsID string) error { + cmd := "LIST VAR \"" + upsID + "\"" if err := c.write(cmd); err != nil { - return nil, err + return err } l, err := c.read() if err != nil { - return nil, err + return err } expected := "BEGIN " + cmd if l != expected { - return nil, fmt.Errorf("expected %q, got %q", expected, l) + return fmt.Errorf("pre-loop error: expected %q, got %q", expected, l) } - var lines []string - expected = typ + " " for { - l, err := c.read() - if err != nil { - return nil, err - } - if l == "END "+cmd { + l, _ := c.read() + if !strings.HasPrefix(l, "VAR \""+upsID+"\" ") { break } - if !strings.HasPrefix(l, expected) { - return nil, fmt.Errorf("expected %q, got %q", expected, l) - } - l = l[len(expected):] - lines = append(lines, l) + lSplit := strings.Split(l, " ") + NutKeyValMap[strings.Trim(lSplit[2], "\"")] = strings.Trim(strings.Join(lSplit[3:], " "), "\"") } - return lines, nil + return nil } -// UPSs returns a list of all UPSs on the server. -func (c *Client) UPSs() ([]string, error) { - lines, err := c.list("UPS") - if err != nil { - return nil, err - } - - var upss []string - for _, l := range lines { - idx := strings.IndexByte(l, ' ') - if idx == -1 { - return nil, errors.New("protocol error") - } - ups := l[:idx] - upss = append(upss, ups) - } - return upss, nil -} - -// Variables returns all variables and their values for a UPS. -func (c *Client) Variables(ups string) (map[string]string, error) { - lines, err := c.list("VAR " + ups) - if err != nil { - return nil, err - } - vars := map[string]string{} - for _, l := range lines { - idx := strings.IndexByte(l, ' ') - if idx == -1 { - return nil, errors.New("protocol error") - } - k := l[:idx] - v := l[idx+1:] - v, err = strconv.Unquote(v) - if err != nil { - return nil, err - } - - vars[k] = v - } - return vars, nil +// newClient wraps an existing net.Conn. +func newClient(conn net.Conn) *Client { + return &Client{conn, bufio.NewReader(conn)} } func (c *Client) write(s string) error {