From ea6319eb9a969a6613717d6e92a59766b0e38ab6 Mon Sep 17 00:00:00 2001 From: Randall Winkhart Date: Thu, 11 Dec 2025 22:38:05 -0500 Subject: [PATCH] Add support for authentication and auto-ID-detection --- README.md | 25 ++++++++++++++++++++++--- nut.go | 50 +++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 69 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 173a79b..e13c15c 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Ubiquiti UPS units seem to use a somewhat non-standard format for responses from ## Usage 1. Enable the NUT server on your Ubiquiti UniFi-connected UPS unit. - - Do **NOT** enable the "Login Credential" option! + - The "Login Credential" setting is optional; if enabled, be sure to use the .Authenticate() method immediately after dialing! 2. This module can now be used to get variables from your UPS's NUT server as follows: ```go package main @@ -18,12 +18,29 @@ import ( func main() { host := "IP:PORT" - upsID := "UPS-ID" + username := "username" + password := "password" + + // connect to NUT server client, err := nut.Dial(host) if err != nil { panic(err) } - err = client.GetListVar(upsID) + + // authenticate (if "Login Credential" is enabled on the NUT server) + err = client.Authenticate(username, password) + if err != nil { + panic(err) + } + + // auto-detect UPS ID + upsID, err := client.Identify() + if err != nil { + panic(err) + } + + // list all variables from your UPS + err = client.ListVar(upsID) if err != nil { panic(err) } @@ -32,3 +49,5 @@ func main() { } } ``` + +This module also features a .GetVar() method for retrieving the value of only one variable. diff --git a/nut.go b/nut.go index 8bb8f89..ac8523d 100644 --- a/nut.go +++ b/nut.go @@ -36,6 +36,36 @@ 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 +} + +// Identify 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) Identify() (string, error) { + if err := c.write("LIST UPS"); err != nil { + return "", err + } + l, err := c.clearBuffer("END", "UPS") + if err != nil { + return "", err + } + + return strings.Split(l, " ")[1], nil +} + // ListVar updates NutKeyValMap with the current status of all variables from . func (c *Client) ListVar(upsID string) error { cmd := "LIST VAR \"" + upsID + "\"" @@ -70,8 +100,7 @@ outer: // GetVar returns the value of the specified variable for . func (c *Client) GetVar(upsID, varName string) (string, error) { - cmd := "GET VAR \"" + upsID + "\" \"" + varName + "\"" - if err := c.write(cmd); err != nil { + if err := c.write("GET VAR \"" + upsID + "\" \"" + varName + "\""); err != nil { return "", err } l, err := c.read() @@ -86,7 +115,6 @@ func (c *Client) GetVar(upsID, varName string) (string, error) { return value, nil } -// newClient wraps an existing net.Conn. func newClient(conn net.Conn) *Client { return &Client{conn, bufio.NewReader(conn)} } @@ -106,3 +134,19 @@ func (c *Client) read() (string, error) { } 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 +}