Beginner's Guide to Network UPS Tools (NUT) on a Raspberry Pi

UPDATE (February 15, 2022): Updated with instructions for adding a second UPS


Since I don’t want my OH installation to be corrupted by a power failure, I purchased a CyberPower UPS, and connected it to my Raspberry Pi using Network UPS Tools (NUT). There are various topics in the community about NUT, and this one was the most helpful for my purposes.

With everything I’ve learned, here are some instructions to help others get started. Note that this guide is for is a simple setup with the UPS only having to turn off a single computer (the Raspberry Pi running OH and NUT). You can also have NUT turn off other computers, but I don’t have experience with that. However, the link I reference below for installing NUT talks about it in detail.

1. Choosing a UPS

My primary concern when a power failure occurs is to shut down the Raspberry Pi, so that the SD card doesn’t get corrupted by immediate power loss. I’m less concerned about OH continuing to run during the outage, since it doesn’t have anything to control.

This meant that my minimum requirement was a UPS with a USB data port and NUT compatibility. Battery run-time was a secondary benefit…it just has to run long enough to shut down the RPi.

I chose a CyberPower ST625VA. It’s the smallest CyberPower I could find with a USB port (at the time of writing), and with my RPi, modem, and mesh router in the battery-supported outlets, it’ll keep OH and my WiFi running for about two hours.

There’s an old and outdated compatibility list on networkupstools.org. My UPS isn’t on it, but many of the CyberPower devices use the same “usbhid-ups” driver, and that worked for me.

2. Install NUT on your Raspberry Pi

Once your UPS is plugged in and connected to your Raspberry Pi via USB, you’ll need to install NUT. Use SSH to access your Raspberry Pi, then follow the instructions in this excellent blog post by Mel Grubb. But first, read my notes below the link.

Notes

  1. The guide says to use nano to edit the ups.conf and nut.conf files. However, nowadays it is strongly recommended to use visudo to edit configuration files.
  2. On an RPi4 running Debian Buster, I get syntax errors when I save the ups.conf and nut.conf files…even if I don’t edit the original contents. After confirming that the contents were as they should be, I saved the files and everything comes out fine. I’m not sure why sudo is throwing the errors.
  3. After saving nut.conf, the service did not work until I rebooted my RPi4. So if you have any issues, I suggest rebooting before trying anything else.
  4. If you’re using a CyberPower UPS, I also recommend looking at this post to avoid the “Error: Data Stale” issue: NUT & CyberPower UPS | /dev/urandom
  5. If you’re only trying to shut down one Raspberry Pi, you can stop at “Email notifications (optional)”. If you want to shut down multiple computers, there are additional instructions on how to do that.

3. Connect OH to NUT

You could stop at this point and your RPi will be protected from power loss. NUT will detect a power failure, monitor the battery, and shut down the RPI when the battery is nearly dead. But where’s the fun in that? With the NUT binding, you can see the status of your UPS and get notifications when your power is out/restored.

There are also ways to have OH control NUT, but I’m not interested. I’m just going to let NUT do its job and keep an eye on it with OH.

Install the Network UPS Tools Binding

In the openHAB MainUI, go to Administration → Settings → Bindings and install the “Network UPS Tools Binding”.

Create a thing for your UPS

If you want to configure via text files, add a ups.things file with the following thing:

Thing networkupstools:ups:ups1 [ device="yourupsname", host="localhost", refresh=60, username="admin", password="yourpasswordhere", refresh="60" ]

Alternatively, you can add the thing via MainUI. openHAB 3.x won’t autodiscover your UPS, so you’ll need to manually add the thing.

  1. Go to Administration → Settings → Things.
  2. Click the “+” button to add a thing, then choose the “Network UPS Tools Binding” and “Network UPS Tool”.

The details will be the same as the text config above (I call my device “openhabups”).

Once this is done, OH will be able to get status updates from NUT.

Create items

Before we can create items, we need to know what data can be read from your UPS. SSH into your RPi and type:

upsc yourupsname

My UPS gives me a list that looks something like this:

Init SSL without certificate database
battery.charge: 100
battery.charge.low: 10
battery.charge.warning: 20
battery.mfr.date: CPS
battery.runtime: 6322
battery.runtime.low: 300
battery.type: PbAcid
battery.voltage: 13.9
battery.voltage.nominal: 12
...
ups.status: OL
ups.timer.shutdown: -60
ups.timer.start: -60
ups.vendorid: 0764

And based on this list, here are the items I’ve set up. You can also set these items up in MainUI, but I’ve left them as text files.

String UPS_Status "Status [MAP(ups.map):%s]" <poweroutlet_us>{networkupstools="ups1:ups.status"}
Number UPS_Battery_Runtime "Runtime [JS(duration_from_seconds.js):%s]" <time>{networkupstools="ups1:battery.runtime"}
Number UPS_Battery_Charge "Battery Charge [%.0f%%]" <batterylevel> {networkupstools="ups1:battery.charge"}

With these three items, I can have OH notify me when there’s a power outage (UPS_Status changes from “OL” to “OB DISCHRG”), and again when power is back. While the power’s out, I can monitor the remaining runtime and battery charge in my sitemap. No need to shut down the RPi, because NUT will do that for me if it becomes necessary.

Transformations

I’ve used a couple of transformations in my items, so that they’ll display nicely in my sitemap.

The “ups.map” file referenced in the first item converts the CyberPower outputs for display on my site map as follows:

OL=AC power
OB\ DISCHRG=Battery discharging
OL\ CHRG=Battery charging
=Unknown

In the second item, I reference “duration_from_seconds.js”. This is a JavaScript that converts seconds to hours/minutes/seconds. I originally used a script I found in Network UPS Tools (nut) setup issues, but have since changed to using this one from Stack Overflow. They both work, but VS Code told me that the substr method is deprecated, so I thought I’d find something different in case it stops working.

(function convertSeconds(seconds) {
        var convert = function(x) { return (x < 10) ? "0"+x : x; }
        return convert(parseInt(seconds / (60*60))) + ":" +
               convert(parseInt(seconds / 60 % 60)) + ":" +
               convert(seconds % 60)
})(input)

To use these transformations, you’ll need to add the MAP Transformation and Javascript Transformation bindings via MainUI, and create the ups.map and duration_from_seconds.js files in the conf/transform directory. I’m not going to go into more detail than that, since this guide isn’t about transformation.

3.1 Adding a second UPS

Since writing this tutorial, I’ve added a second UPS for another RPi that I use for my 3D printer (with OctoPrint) and laser printer (with CUPS). I set up NUT on the RPi exactly as before, then configured a thing in OH.

Thing networkupstools:ups:ups2 [ device="newupsname", host="RPi's IP address", refresh=60, username="admin", password="newpasswordhere", refresh="60" ]

It all looked good, but when I saved the thing I got an error saying that the connection was refused. I realized that NUT was configured to only listen for connections internally, so it was rejecting the request from my OH server. I needed to tell NUT to listen for connections on port 3493.

I went back to Mel Grubb’s tutorial and found the answer in the " Reconfigure NUT on the server" section. As stated in the tutorial, I had to edit /etc/nut/upsd.conf and add two lines:

Listen 127.0.0.1 3493

This line tells NUT to continue listening to local requests (127.0.0.1), ensuring that the NUT monitoring client can still access the NUT server.

Listen 192.xxx.xxx.xxx 3493

This line tells NUT that it should also listen to requests sent to my print server’s IP address.

I saved these changes, restarted the NUT server, and OH was able to connect. Then I just repeated the steps above to make items for my new UPS.

4. Get notified about power outages

Here are the two rules I use to get notified when there’s a power failure at home. You’ll need to have the openHAB Cloud Connector installed and working to use the sendNotification action.

There are other ways to set up these rules (Blocky, ECMA, etc.), but for now I’m sticking with Rules DSL. I’ll update this tutorial if I revise them in the future.

rule "Power outtage"
when
    Item UPS_Status changed from "OL" to "OB DISCHRG"
then
    sendNotification("me@email.com", "UPS has switched to backup battery")
end

rule "Power returned"
when
    Item UPS_Status changed from "OB DISCHRG" to "OL"
then
    sendNotification("me@email.com", "Power has returned to UPS")
end

That’s pretty much it. If you’ve also installed NUT, please let me know if you have any suggestions or corrections to improve this guide.

14 Likes

I am waiting on a cable for my APC UPS. I have bookmarked this thread.
Thanks.

Thanks a lot for detailing this approach!

Did you ever use the default cyberpower daemon? (pwrstatd)
I have been using this on my ubuntu system, and it did most of what I wanted. I could simply add the ssh command to shutdown the pi to the pwrstatd-lowbatt.sh file, and get the bare-bones functionality covered, although without any of the potential notification features, or potentially more complex power loss preparation that could happen within openhab.

I skipped pwrstatd, as I had already done enough research on NUT to know that it was the direction I wanted to go. In particular, I really like having notifications for power outages.

If you haven’t already done so, I previously wrote a guide to add a manual shutdown/reboot switch in OH. It uses pretty much the same sudo permissions that you implemented for pwrstatd, except that my concern was only controlling the host RPi (not a client). Sorry I didn’t realize that sooner!

If someone wanted to have OH shut itself down during a power outage, they’d just need a rule to trigger when the UPS’s battery runtime or status falls below a certain threshold and run the shutdown command. I left that out of the guide, since I think it’s better to just let NUT do it.

1 Like

Is there a reason why you used visudo? Usually that’s only needed for the sudoers file.
sudoedit or sudo -e seems way more appropriate for this case

Actually I think they meant vi or vim which are editor some people prefer to nano.

visudo is the command to edit the sudoers file for sudo udsing a modified vi editor.

Hi All,

Everything is good regarding configuration of NUT bindings items and etc

As issue faced with Rule creation, it is simply not reacting / not sending push notification. Event viewer shows line like changed from OL to OB but still rule not catch this for trigger notification

notifications for other switches ON/OFF working fine, it’s seems like issue in String value catch (found some notice about this issue but not resolved)

Can you please advise ?

Then open a new thread fir your other issue. Why thread crap here?? Please learn basic forum etiquette.

check the header message, point 4 pointing for rule which is not working, that’s exactly correct topic.

Sorry. You said the configuration was correct.

It might be a NUT configuration issue or it could be a notification issue. Are other rules successfully sending you notifications?

The NUT itself working perfect, tested with cutting input power for ups, items refreshes instantly and event log reflect status changes, but for some reason notification not triggering on this value change event.
The thing which I though that maybe this caused due to mapping which probably built in in binding (because I didn’t create such mapping as OL=Online, OB=On Battery) but somehow it automatically converted on sitemap to full word

In this case could it be that rule expects converted value ?

The rule trigger needs to be whatever value the item is reporting without mapping. Mapping only affects the sitemap. So if your items are reporting different values, use those values. Those values come from your device, not the binding, so it’s possible that your UPS reports them differently from mine. Can you show us what it’s actually reporting?

Strange thing that events showing exactly same which was define in rule

2020-08-06 21:30:27.506 [vent.ItemStateChangedEvent] - ups_status changed from OL to OB
2020-08-06 21:31:36.699 [vent.ItemStateChangedEvent] - ups_status changed from OB to OL

Rule:

    rule "Power outtage"
when
    Item ups_status changed from "OL" to "OB"
then
    sendNotification("myemailhere", "UPS has switched to backup battery")
end

rule "Power returned"
when
    Item ups_status changed from "OB" to "OL"
then
    sendNotification("myemailhere", "Power has returned to UPS")
end

If that’s the case, you need to determine whether the problem is the trigger or the action. The easy way to do this is to add a logInfo action, so that when the rule runs you’ll see a log entry for it. Similarly, you could add a different trigger that you know works, and see if the sendNotification goes through to your phone.

My guess is that sendNotification is the problem. Sometimes a restart of openHAB gets them going, but other times the Android or iPhone app is the problem. Which app are you using?

thanks for support guys, seems like it’s really bug on rules engine fixed with:

  1. Removed ups.rules file
  2. Created new ups.rules file
  3. Reboot RPi

strange thing that i have another rule with sendnotification which was working stable on daily basis

Good stuff. I’d guess that it was a caching issue, rather than a bug. It’s possible that the reboot alone would have fixed it.