mirror of
https://github.com/rwinkhart/uni-nut.git
synced 2026-08-28 04:36:29 -04:00
164 lines
3.8 KiB
Go
164 lines
3.8 KiB
Go
package nut
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
)
|
|
|
|
// A Client wraps a connection to a NUT server.
|
|
type Client struct {
|
|
conn net.Conn
|
|
br *bufio.Reader
|
|
upsID string
|
|
upsIDLength int
|
|
}
|
|
|
|
// 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) {
|
|
_, _, err := net.SplitHostPort(addr)
|
|
if err != nil {
|
|
addr = net.JoinHostPort(addr, "3493")
|
|
}
|
|
|
|
conn, err := net.Dial("tcp", addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return newClient(conn), nil
|
|
}
|
|
|
|
// Close closes the connection.
|
|
func (c *Client) Close() error {
|
|
return c.conn.Close()
|
|
}
|
|
|
|
// Authenticate logs into credential-protected sessions.
|
|
// If authentication is enabled on your NUT server, run
|
|
// this immediately after dialing.
|
|
func (c *Client) Authenticate(username, password string) error {
|
|
var err error
|
|
if err = c.write("USERNAME " + username + "\nPASSWORD " + password); err != nil {
|
|
return err
|
|
}
|
|
_, err = c.clearBuffer("OK L", "")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// AutomaticallySetID detects the ID of the connected UPS.
|
|
// This only works when there is only one UPS per
|
|
// NUT server, which is the case with UniFi UPS units.
|
|
func (c *Client) AutomaticallySetID() error {
|
|
if err := c.write("LIST UPS"); err != nil {
|
|
return err
|
|
}
|
|
l, err := c.clearBuffer("END", "UPS")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
lSplit := strings.Split(l, " ")
|
|
c.upsID = strings.Join(lSplit[1:len(lSplit)-2], " ")
|
|
c.upsIDLength = len(strings.Split(c.upsID, " "))
|
|
return nil
|
|
}
|
|
|
|
// ManuallySetID allows the user to specify the UPS ID
|
|
// manually if auto-detection is not desired.
|
|
func (c *Client) ManuallySetID(upsID string) {
|
|
c.upsID = upsID
|
|
c.upsIDLength = len(strings.Split(upsID, " "))
|
|
}
|
|
|
|
// ListVar updates NutKeyValMap with the current status of all variables from the target UPS.
|
|
func (c *Client) ListVar() error {
|
|
cmd := "LIST VAR \"" + c.upsID + "\""
|
|
if err := c.write(cmd); err != nil {
|
|
return err
|
|
}
|
|
l, err := c.read()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
expectedPrefix := "BEGIN LIST VAR"
|
|
if !strings.HasPrefix(l, expectedPrefix) {
|
|
return fmt.Errorf("pre-loop error: expected prefix %q, got line %q", expectedPrefix, l)
|
|
}
|
|
|
|
outer:
|
|
for {
|
|
l, err := c.read()
|
|
if err != nil {
|
|
return fmt.Errorf("in-loop error: %w", err)
|
|
}
|
|
lSplit := strings.Split(l, " ")
|
|
switch lSplit[0] {
|
|
case "VAR":
|
|
NutKeyValMap[lSplit[1+c.upsIDLength]] = strings.Trim(strings.Join(lSplit[2+c.upsIDLength:], " "), "\"")
|
|
default:
|
|
break outer
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetVar returns the value of the specified variable for the target UPS.
|
|
func (c *Client) GetVar(varName string) (string, error) {
|
|
if err := c.write("GET VAR \"" + c.upsID + "\" \"" + varName + "\""); err != nil {
|
|
return "", err
|
|
}
|
|
l, err := c.read()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
lSplit := strings.Split(l, " ")
|
|
if len(lSplit) < 4 {
|
|
return "", fmt.Errorf("invalid response to GET VAR; check your UPS ID")
|
|
}
|
|
value := strings.Trim(strings.Join(lSplit[2+c.upsIDLength:], " "), "\"")
|
|
return value, nil
|
|
}
|
|
|
|
func newClient(conn net.Conn) *Client {
|
|
return &Client{conn, bufio.NewReader(conn), "", 0}
|
|
}
|
|
|
|
func (c *Client) write(s string) error {
|
|
_, err := c.conn.Write([]byte(s + "\n"))
|
|
return err
|
|
}
|
|
|
|
func (c *Client) read() (string, error) {
|
|
l, err := c.br.ReadString('\n')
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if len(l) > 0 {
|
|
l = l[:len(l)-1]
|
|
}
|
|
return l, nil
|
|
}
|
|
|
|
func (c *Client) clearBuffer(tillPrefix, storePrefix string) (string, error) {
|
|
var stored string
|
|
for {
|
|
l, err := c.read()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if storePrefix != "" && strings.HasPrefix(l, storePrefix) {
|
|
stored = l
|
|
} else if strings.HasPrefix(l, tillPrefix) {
|
|
break
|
|
}
|
|
}
|
|
return stored, nil
|
|
}
|