2026-08-10 20:49:20 +02:00
2026-08-10 18:58:29 +02:00
2026-08-10 20:49:20 +02:00

APC Back-UPS BX950MI on Raspberry Pi 5

This document describes the working setup for monitoring an APC Back-UPS BX950MI connected to a Raspberry Pi 5 over USB.

The setup uses:

  • NUT (Network UPS Tools) for communication with the UPS
  • usbhid-ups as the USB/HID driver
  • upsd as the NUT server
  • upsc to read UPS values
  • RRDTool for historical data
  • A Python script to periodically collect the UPS data and generate graphs

The UPS is connected to the Raspberry Pi using its USB data cable.


1. Hardware

UPS:

American Power Conversion
Back-UPS BX950MI

USB identification:

Vendor ID:  051d
Product ID: 0002
Serial:     9B2545A39513

The Raspberry Pi detects the UPS as:

American Power Conversion Uninterruptible Power Supply

The USB device can be verified with:

lsusb

Expected output includes:

Bus 002 Device 004: ID 051d:0002 American Power Conversion Uninterruptible Power Supply

More detailed information:

lsusb -v -d 051d:0002

The device identifies itself as:

Manufacturer: American Power Conversion
Product:      Back-UPS BX950MI

2. Install NUT

Install the NUT packages:

sudo apt install nut nut-client nut-server

Also install RRDTool for the historical data collector:

sudo apt install librrd-dev python3-dev python3-venv
cd /opt/ups
python3 -m venv .venv
.venv/bin/python -m pip install -r requirements.txt

The collector uses the virtual environment at /opt/ups/.venv. Calling its Python executable directly means cron does not need to activate the environment.

Check the NUT version:

nut-scanner --version

The working installation used:

Network UPS Tools 2.8.1

3. USB detection

The Raspberry Pi kernel recognizes the UPS as a USB HID device.

The relevant kernel message is:

hid-generic 0003:051D:0002.0003:
hiddev0,hidraw0:
USB HID v1.10 Device
[American Power Conversion Back-UPS BX950MI]

The device is therefore using the standard USB HID interface.

The relevant kernel modules are:

lsmod | grep hid

which showed:

hid_generic
usbhid

The USB interface is:

Interface Class: HID
Endpoint:       EP 1 IN
Transfer Type:  Interrupt

This is exactly what the NUT usbhid-ups driver is designed to communicate with.


4. Finding the UPS with nut-scanner

The USB scanner was eventually able to identify the UPS with:

sudo nut-scanner -U

The important part of the result was:

[nutdev1]
    driver = "usbhid-ups"
    port = "auto"
    vendorid = "051D"
    productid = "0002"
    product = "Back-UPS BX950MI"
    serial = "9B2545A39513"
    vendor = "American Power Conversion"

The important information is therefore:

driver   = usbhid-ups
vendorid = 051D
productid = 0002
serial   = 9B2545A39513

The other warnings from nut-scanner about missing optional libraries are not relevant to this USB UPS.

For example:

Cannot load SNMP library
Cannot load XML library
Cannot load AVAHI library
Cannot load IPMI library

Those simply mean that NUT cannot scan those other types of UPS/network devices.


5. NUT configuration

The UPS is configured in:

/etc/nut/ups.conf

The working configuration is:

[BX950MI]
    driver = usbhid-ups
    port = auto
    vendorid = 051D
    productid = 0002
    serial = 9B2545A39513

The name:

BX950MI

is important.

It becomes the UPS name used by commands such as:

upsc BX950MI

6. NUT operating mode

The NUT operating mode is configured in:

/etc/nut/nut.conf

The required setting is:

MODE=standalone

This tells NUT that the Raspberry Pi is operating as a standalone UPS monitoring system.

It allows the local NUT server (upsd) and monitor to operate on the Raspberry Pi.


7. NUT users

NUT authentication is configured in:

/etc/nut/upsd.users

The working monitoring user is:

[monuser]
    password = elvis
    upsmon primary

This user is used by upsmon to monitor the UPS.

The password should of course be changed if this machine is accessible to other users.

A stronger example would be:

[monuser]
    password = <strong-password>
    upsmon primary

8. NUT server

The NUT server is upsd.

Its job is to provide UPS information to NUT clients such as:

upsc
upsmon

The server listens on:

127.0.0.1:3493

The successful startup showed:

listening on 127.0.0.1 port 3493

and:

Connected to UPS [BX950MI]: usbhid-ups-BX950MI

This confirms that:

  1. upsd is running
  2. the UPS is defined
  3. the usbhid-ups driver is running
  4. upsd can communicate with the driver

Check it with:

sudo systemctl status nut-server

A successful status contains:

Active: active (running)

and:

Connected to UPS [BX950MI]

9. NUT driver

The actual USB communication is handled by:

usbhid-ups

On this system it runs as the systemd service:

nut-driver@BX950MI.service

Check it with:

sudo systemctl status nut-driver@BX950MI

The successful configuration showed:

Active: active (running)

and:

Using subdriver: APC HID 0.100

followed by:

Startup successful

This is the important indication that the UPS is correctly communicating with NUT.


10. Reading UPS information

The easiest way to test the complete setup is:

upsc BX950MI

The working UPS returned:

battery.charge: 100
battery.charge.low: 10
battery.mfr.date: 2001/01/01
battery.runtime: 3584
battery.runtime.low: 120
battery.type: PbAc
battery.voltage: 13.6
battery.voltage.nominal: 12.0

device.mfr: American Power Conversion
device.model: Back-UPS BX950MI
device.serial: 9B2545A39513
device.type: ups

input.sensitivity: medium
input.transfer.high: 295
input.transfer.low: 145
input.voltage: 234.0
input.voltage.nominal: 230

ups.beeper.status: enabled
ups.load: 0
ups.mfr: American Power Conversion
ups.mfr.date: 2025/11/16
ups.model: Back-UPS BX950MI
ups.productid: 0002
ups.realpower.nominal: 520
ups.serial: 9B2545A39513
ups.status: OL
ups.test.result: Done and passed
ups.vendorid: 051d

This confirms that the complete chain is working:

APC UPS
   │
   │ USB
   ▼
Raspberry Pi USB
   │
   ▼
Linux HID
   │
   ▼
usbhid-ups
   │
   ▼
NUT
   │
   ▼
upsc

11. Important UPS values

The Python collector uses these NUT values:

Battery charge

battery.charge

Current example:

100 %

This is the UPS's estimated battery charge.


Battery runtime

battery.runtime

NUT reports this in seconds.

Example:

3584

which is approximately:

59.7 minutes

The Python graphs convert this to minutes.


UPS load

ups.load

Example:

0 %

This is the estimated current load on the UPS.


Nominal power

ups.realpower.nominal

Example:

520 W

This is the nominal real-power capacity reported by the UPS.

It should not be confused with actual current power consumption.


UPS status

ups.status

Example:

OL

The commonly encountered values are:

OL = On Line
OB = On Battery
LB = Low Battery
RB = Replace Battery

The Python collector converts these into numeric values for RRDTool.


12. The Python RRD collector

The Python program is:

/opt/ups/ups2rrd.py

Its job is deliberately simple:

NUT
 │
 │ PyNUTClient
 ▼
Python
 │
 ├── battery.charge
 ├── battery.runtime
 ├── ups.load
 ├── ups.realpower.nominal
 └── ups.status
 │
 ▼
RRDTool
 │
 ▼
/var/lib/rrd/bx950mi.rrd
 │
 ▼
PNG graphs

The script performs four main tasks:

  1. Create the RRD database if it doesn't exist
  2. Read the current UPS values
  3. Update the RRD and regenerate the graphs
  4. Generate the HTML dashboard

13. RRD database

The database is:

/var/lib/rrd/bx950mi.rrd

RRDTool means:

Round Robin Database

It is particularly suitable for time-series measurements such as UPS monitoring.

Instead of growing indefinitely, the database has predefined storage periods.

Old high-resolution data is automatically represented at lower resolution.


14. One-minute collection

The RRD uses a primary step of:

60 seconds

The corresponding configuration is:

"--step",
"60",

The cron job runs the Python script every minute:

* * * * * /opt/ups/.venv/bin/python /opt/ups/ups2rrd.py >> /var/log/ups_rrd.log 2>&1

Therefore, approximately one measurement is stored every minute.


15. RRD retention

The database uses different resolutions for different periods.

Three months

The status and load retain:

1 minute

for approximately:

90 days

This is useful for detecting short power failures.

For example, an event like:

18:21 OL
18:22 OL
18:23 OB
18:24 OB
18:25 OB
18:26 OL

will clearly show up as a battery event.


One year

Battery charge, battery runtime and nominal power are retained at:

5 minute

resolution.

This is enough detail to see long-term battery behaviour without requiring every minute for the entire year.


Five years

Long-term data is consolidated to:

1 hour

resolution.

This keeps the database small while allowing trends such as battery degradation to be observed over several years.


16. Status conversion

RRDTool works with numeric values, so the Python program converts the UPS status.

The mapping is:

STATUS_VALUES = {
    "OL": 0,
    "OB": 1,
    "LB": 2,
    "RB": 3,
    "UNKNOWN": 4,
}

Therefore:

0 = On Line
1 = On Battery
2 = Low Battery
3 = Replace Battery
4 = Unknown

The graph can then display the status history.


17. Handling missing data

The Python program converts unavailable measurements into:

U

which means:

Unknown

in RRDTool.

For example, if NUT temporarily fails to return:

battery.runtime

the script doesn't write a bogus zero.

Instead it writes:

U

RRDTool will leave a gap in the graph.

This is important because:

0 seconds

and:

measurement unavailable

are completely different things.


18. Generated graphs

The script creates:

/var/www/html/ups/index.html
/var/www/html/ups/week.html

/var/www/html/ups/status-3m.png
/var/www/html/ups/status-1w.png

/var/www/html/ups/battery-1w.png
/var/www/html/ups/battery-1y.png
/var/www/html/ups/battery-5y.png

/var/www/html/ups/runtime-1w.png
/var/www/html/ups/runtime-1y.png
/var/www/html/ups/runtime-5y.png

/var/www/html/ups/load-1w.png
/var/www/html/ups/load-3m.png

/var/www/html/ups/power-1w.png
/var/www/html/ups/power-1y.png
/var/www/html/ups/power-5y.png

If /var/www/html is served by the web server, these can therefore be viewed as:

/ups/
/ups/week.html

/ups/status-3m.png
/ups/status-1w.png
/ups/battery-1w.png
/ups/battery-1y.png
/ups/battery-5y.png
/ups/runtime-1w.png
/ups/runtime-1y.png
/ups/runtime-5y.png
/ups/load-1w.png
/ups/load-3m.png
/ups/power-1w.png
/ups/power-1y.png
/ups/power-5y.png

The main responsive dashboard groups the long-term graphs on one page. The weekly dashboard at /ups/week.html shows one-week history for every collected value. The pages link to each other, refresh every 60 seconds, and open a full-size PNG when a graph is clicked.


19. Testing the Python program

Run it manually:

sudo /opt/ups/.venv/bin/python /opt/ups/ups2rrd.py

A successful run should produce something similar to:

2026-08-10 18:00:00 status=OL battery=100.0 runtime=3584.0 load=0.0 nominal=520.0

Check the database:

ls -lh /var/lib/rrd/bx950mi.rrd

Check the most recent RRD values:

rrdtool lastupdate /var/lib/rrd/bx950mi.rrd

20. Checking cron

Install the cron entry in root's crontab because the collector writes to /var/lib/rrd and /var/www/html/ups:

sudo crontab -e

Add:

* * * * * /opt/ups/.venv/bin/python /opt/ups/ups2rrd.py >> /var/log/ups_rrd.log 2>&1

There is no need to run source, activate the virtual environment, or use a wrapper script.

After a few minutes:

tail -f /var/log/ups_rrd.log

You should see a new line approximately every minute.


21. Checking NUT services

Useful commands:

sudo systemctl status nut-driver@BX950MI
sudo systemctl status nut-server

And:

sudo systemctl list-units --all 'nut*'

The important services are:

nut-driver@BX950MI.service
nut-server.service

The driver should be:

active (running)

The server should also be:

active (running)

22. Troubleshooting

UPS not detected

Check:

lsusb

The UPS should appear as:

051d:0002

Then:

sudo nut-scanner -U

Driver not running

Check:

sudo systemctl status nut-driver@BX950MI

Then:

sudo journalctl -u nut-driver@BX950MI -n 50

A successful driver startup contains:

Using subdriver: APC HID 0.100

and:

Startup successful

upsc says connection refused

Check:

sudo systemctl status nut-server

If nut-server isn't running, start it:

sudo systemctl start nut-server

Then:

upsc BX950MI

The server should report:

listening on 127.0.0.1 port 3493

23. USB device permissions

The UPS is a USB HID device.

NUT's usbhid-ups driver runs as the:

nut

user.

The NUT systemd service therefore needs to be allowed to access the USB device.

The working setup uses the NUT/systemd configuration supplied by the Ubuntu NUT package.

It is important not to manually run the driver as root as part of the normal operation.

For example, this is useful for debugging:

sudo /lib/nut/usbhid-ups -a BX950MI -DD

but normal operation should be handled by:

nut-driver@BX950MI.service

24. Current working architecture

The complete system now looks like this:

                         APC Back-UPS BX950MI
                                  │
                                  │ USB
                                  ▼
                         Raspberry Pi 5
                                  │
                                  ▼
                         Linux USB HID
                                  │
                                  ▼
                           usbhid-ups
                                  │
                                  ▼
                               NUT
                    ┌─────────────┴─────────────┐
                    │                           │
                  upsd                        upsmon
                    │
                    │ PyNUTClient
                    ▼
              Python script
                    │
                    ▼
                 RRDTool
                    │
                    ▼
              bx950mi.rrd
                    │
                    ▼
                PNG graphs

The important point is that Python does not communicate directly with the UPS.

It communicates with the NUT server through a Python client:

from PyNUTClient import PyNUT

client = PyNUT.PyNUTClient(host="127.0.0.1")
values = client.GetUPSVars("BX950MI")

This is preferable because NUT handles all of the USB/HID details.

The Python program only needs to understand normal NUT values.


25. Useful commands

Show all UPS values:

upsc BX950MI

Show battery charge:

upsc BX950MI battery.charge

Show battery runtime:

upsc BX950MI battery.runtime

Show UPS load:

upsc BX950MI ups.load

Show UPS status:

upsc BX950MI ups.status

Show nominal power:

upsc BX950MI ups.realpower.nominal

Check USB:

lsusb

Check NUT driver:

sudo systemctl status nut-driver@BX950MI

Check NUT server:

sudo systemctl status nut-server

Check recent driver messages:

sudo journalctl -u nut-driver@BX950MI -n 50

Check recent server messages:

sudo journalctl -u nut-server -n 50

Check RRD:

rrdtool info /var/lib/rrd/bx950mi.rrd

Check latest RRD values:

rrdtool lastupdate /var/lib/rrd/bx950mi.rrd

Check Python collector:

tail -f /var/log/ups_rrd.log

26. Summary

The successful setup consists of four layers:

1. USB

The Raspberry Pi detects:

051d:0002
American Power Conversion Back-UPS BX950MI

2. NUT driver

usbhid-ups

communicates with the UPS using USB HID.

3. NUT server

upsd

provides the UPS information locally on:

127.0.0.1:3493

4. Python + RRDTool

The Python program runs every minute, obtains selected values from:

upsd on 127.0.0.1:3493 (through PyNUTClient)

and stores them in:

/var/lib/rrd/bx950mi.rrd

RRDTool then provides historical data for:

3 months @ 1 minute
1 year  @ 5 minutes
5 years @ 1 hour

This provides a lightweight UPS monitoring system running entirely on the Raspberry Pi, without requiring an external monitoring service.

S
Description
No description provided
Readme
193 KiB
Languages
Python 100%