Files
ups/README.md
T
2026-08-10 18:51:41 +02:00

1193 lines
16 KiB
Markdown

# 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:
```text
American Power Conversion
Back-UPS BX950MI
```
USB identification:
```text
Vendor ID: 051d
Product ID: 0002
Serial: 9B2545A39513
```
The Raspberry Pi detects the UPS as:
```text
American Power Conversion Uninterruptible Power Supply
```
The USB device can be verified with:
```bash
lsusb
```
Expected output includes:
```text
Bus 002 Device 004: ID 051d:0002 American Power Conversion Uninterruptible Power Supply
```
More detailed information:
```bash
lsusb -v -d 051d:0002
```
The device identifies itself as:
```text
Manufacturer: American Power Conversion
Product: Back-UPS BX950MI
```
---
# 2. Install NUT
Install the NUT packages:
```bash
sudo apt install nut nut-client nut-server
```
Also install RRDTool for the historical data collector:
```bash
sudo apt install librrd-dev python3-dev
python3 -m pip install -r requirements.txt
```
Check the NUT version:
```bash
nut-scanner --version
```
The working installation used:
```text
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:
```text
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:
```bash
lsmod | grep hid
```
which showed:
```text
hid_generic
usbhid
```
The USB interface is:
```text
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:
```bash
sudo nut-scanner -U
```
The important part of the result was:
```text
[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:
```text
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:
```text
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:
```text
/etc/nut/ups.conf
```
The working configuration is:
```ini
[BX950MI]
driver = usbhid-ups
port = auto
vendorid = 051D
productid = 0002
serial = 9B2545A39513
```
The name:
```text
BX950MI
```
is important.
It becomes the UPS name used by commands such as:
```bash
upsc BX950MI
```
---
# 6. NUT operating mode
The NUT operating mode is configured in:
```text
/etc/nut/nut.conf
```
The required setting is:
```ini
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:
```text
/etc/nut/upsd.users
```
The working monitoring user is:
```ini
[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:
```ini
[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:
```text
upsc
upsmon
```
The server listens on:
```text
127.0.0.1:3493
```
The successful startup showed:
```text
listening on 127.0.0.1 port 3493
```
and:
```text
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:
```bash
sudo systemctl status nut-server
```
A successful status contains:
```text
Active: active (running)
```
and:
```text
Connected to UPS [BX950MI]
```
---
# 9. NUT driver
The actual USB communication is handled by:
```text
usbhid-ups
```
On this system it runs as the systemd service:
```text
nut-driver@BX950MI.service
```
Check it with:
```bash
sudo systemctl status nut-driver@BX950MI
```
The successful configuration showed:
```text
Active: active (running)
```
and:
```text
Using subdriver: APC HID 0.100
```
followed by:
```text
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:
```bash
upsc BX950MI
```
The working UPS returned:
```text
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:
```text
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
```text
battery.charge
```
Current example:
```text
100 %
```
This is the UPS's estimated battery charge.
---
## Battery runtime
```text
battery.runtime
```
NUT reports this in seconds.
Example:
```text
3584
```
which is approximately:
```text
59.7 minutes
```
The Python graphs convert this to minutes.
---
## UPS load
```text
ups.load
```
Example:
```text
0 %
```
This is the estimated current load on the UPS.
---
## Nominal power
```text
ups.realpower.nominal
```
Example:
```text
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
```text
ups.status
```
Example:
```text
OL
```
The commonly encountered values are:
```text
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:
```text
/usr/local/bin/ups_rrd.py
```
Its job is deliberately simple:
```text
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 three 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
---
# 13. RRD database
The database is:
```text
/var/lib/rrd/bx950mi.rrd
```
RRDTool means:
```text
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:
```text
60 seconds
```
The corresponding configuration is:
```python
"--step",
"60",
```
The cron job runs the Python script every minute:
```cron
* * * * * /usr/local/bin/ups_rrd.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:
```text
1 minute
```
for approximately:
```text
90 days
```
This is useful for detecting short power failures.
For example, an event like:
```text
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:
```text
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:
```text
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:
```python
STATUS_VALUES = {
"OL": 0,
"OB": 1,
"LB": 2,
"RB": 3,
"UNKNOWN": 4,
}
```
Therefore:
```text
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:
```text
U
```
which means:
```text
Unknown
```
in RRDTool.
For example, if NUT temporarily fails to return:
```text
battery.runtime
```
the script doesn't write a bogus zero.
Instead it writes:
```text
U
```
RRDTool will leave a gap in the graph.
This is important because:
```text
0 seconds
```
and:
```text
measurement unavailable
```
are completely different things.
---
# 18. Generated graphs
The script creates:
```text
/var/www/html/ups/status-3m.png
/var/www/html/ups/battery-1y.png
/var/www/html/ups/battery-5y.png
/var/www/html/ups/runtime-1y.png
/var/www/html/ups/runtime-5y.png
/var/www/html/ups/load-3m.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:
```text
/ups/status-3m.png
/ups/battery-1y.png
/ups/battery-5y.png
/ups/runtime-1y.png
/ups/runtime-5y.png
/ups/load-3m.png
/ups/power-1y.png
/ups/power-5y.png
```
---
# 19. Testing the Python program
Run it manually:
```bash
sudo /usr/local/bin/ups_rrd.py
```
A successful run should produce something similar to:
```text
2026-08-10 18:00:00 status=OL battery=100.0 runtime=3584.0 load=0.0 nominal=520.0
```
Check the database:
```bash
ls -lh /var/lib/rrd/bx950mi.rrd
```
Check the most recent RRD values:
```bash
rrdtool lastupdate /var/lib/rrd/bx950mi.rrd
```
---
# 20. Checking cron
The cron entry is:
```cron
* * * * * /usr/local/bin/ups_rrd.py >> /var/log/ups_rrd.log 2>&1
```
After a few minutes:
```bash
tail -f /var/log/ups_rrd.log
```
You should see a new line approximately every minute.
---
# 21. Checking NUT services
Useful commands:
```bash
sudo systemctl status nut-driver@BX950MI
```
```bash
sudo systemctl status nut-server
```
And:
```bash
sudo systemctl list-units --all 'nut*'
```
The important services are:
```text
nut-driver@BX950MI.service
nut-server.service
```
The driver should be:
```text
active (running)
```
The server should also be:
```text
active (running)
```
---
# 22. Troubleshooting
## UPS not detected
Check:
```bash
lsusb
```
The UPS should appear as:
```text
051d:0002
```
Then:
```bash
sudo nut-scanner -U
```
---
## Driver not running
Check:
```bash
sudo systemctl status nut-driver@BX950MI
```
Then:
```bash
sudo journalctl -u nut-driver@BX950MI -n 50
```
A successful driver startup contains:
```text
Using subdriver: APC HID 0.100
```
and:
```text
Startup successful
```
---
## `upsc` says connection refused
Check:
```bash
sudo systemctl status nut-server
```
If `nut-server` isn't running, start it:
```bash
sudo systemctl start nut-server
```
Then:
```bash
upsc BX950MI
```
The server should report:
```text
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:
```text
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:
```bash
sudo /lib/nut/usbhid-ups -a BX950MI -DD
```
but normal operation should be handled by:
```text
nut-driver@BX950MI.service
```
---
# 24. Current working architecture
The complete system now looks like this:
```text
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:
```python
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:
```bash
upsc BX950MI
```
Show battery charge:
```bash
upsc BX950MI battery.charge
```
Show battery runtime:
```bash
upsc BX950MI battery.runtime
```
Show UPS load:
```bash
upsc BX950MI ups.load
```
Show UPS status:
```bash
upsc BX950MI ups.status
```
Show nominal power:
```bash
upsc BX950MI ups.realpower.nominal
```
Check USB:
```bash
lsusb
```
Check NUT driver:
```bash
sudo systemctl status nut-driver@BX950MI
```
Check NUT server:
```bash
sudo systemctl status nut-server
```
Check recent driver messages:
```bash
sudo journalctl -u nut-driver@BX950MI -n 50
```
Check recent server messages:
```bash
sudo journalctl -u nut-server -n 50
```
Check RRD:
```bash
rrdtool info /var/lib/rrd/bx950mi.rrd
```
Check latest RRD values:
```bash
rrdtool lastupdate /var/lib/rrd/bx950mi.rrd
```
Check Python collector:
```bash
tail -f /var/log/ups_rrd.log
```
---
# 26. Summary
The successful setup consists of four layers:
### 1. USB
The Raspberry Pi detects:
```text
051d:0002
American Power Conversion Back-UPS BX950MI
```
### 2. NUT driver
```text
usbhid-ups
```
communicates with the UPS using USB HID.
### 3. NUT server
```text
upsd
```
provides the UPS information locally on:
```text
127.0.0.1:3493
```
### 4. Python + RRDTool
The Python program runs every minute, obtains selected values from:
```text
upsd on 127.0.0.1:3493 (through PyNUTClient)
```
and stores them in:
```text
/var/lib/rrd/bx950mi.rrd
```
RRDTool then provides historical data for:
```text
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.
```