feat(ups): add Home Assistant NUT integration via REST config-flow API
Drive HA's REST config-flow endpoint to add the NUT integration programmatically,
no manual UI clicks required. The script (setup-ha-nut.sh + ha-nut-setup.py) is
idempotent — skips if a NUT entry already exists.
Key finding: HAOS runs Tailscale as an isolated add-on container, so the HA core
container cannot route to Tailscale IPs. Added a LAN listener (192.168.3.11:3493)
to upsd so HA can reach it over the shared vmbr0 bridge. Both VMs (pfv-bms HA at
192.168.3.12 and pfv-tsys1 at 192.168.3.11) are on the same bridge.
Integration is live — sensors for battery charge (100%), status (Online), and
status data (OL) are reporting.
Secrets (HA token, NUT password) are read from ~/.config/pfvcluster/ and never
committed to the repo.
💘 Generated with Crush
Assisted-by: Crush:glm-5.2
This commit is contained in:
+46
-11
@@ -63,17 +63,18 @@ pfv-tsys1 (192.168.3.11 / Tailscale 100.121.189.98)
|
||||
▼
|
||||
nut-driver@apc-smartups-c1500 (usbhid-ups)
|
||||
▼
|
||||
upsd :3493 (LISTEN 127.0.0.1 + Tailscale)
|
||||
upsd :3493 (LISTEN 127.0.0.1 + Tailscale + LAN)
|
||||
▼ ▼
|
||||
upsmon (local) Home Assistant (NUT integration)
|
||||
graceful shutdown power/load/runtime sensors
|
||||
graceful shutdown via LAN 192.168.3.11 (HAOS can't
|
||||
route to Tailscale IPs)
|
||||
```
|
||||
|
||||
- **Driver layer** — `usbhid-ups` process, pinned by USB serial. Debian uses
|
||||
templated `nut-driver@<upsname>.service` units managed by
|
||||
`nut-driver-enumerator`.
|
||||
- **Server layer** — `upsd` exposes UPS data on TCP 3493 (localhost + Tailscale
|
||||
only). Clients authenticate via `upsd.users`.
|
||||
+ LAN). Clients authenticate via `upsd.users`.
|
||||
- **Monitor layer** — `upsmon` runs locally as `master` to trigger
|
||||
`SHUTDOWNCMD` (`/sbin/shutdown -h now`) when a UPS reports `LOWBATT`.
|
||||
- **Home Assistant** — native NUT integration connects to `upsd` over Tailscale
|
||||
@@ -89,7 +90,7 @@ it needs write access to those raw USB files. The udev rule must match
|
||||
|
||||
## Scripts
|
||||
|
||||
All scripts run on the target host (pfv-tsys1) via `tests/remote.sh`:
|
||||
NUT host scripts run on pfv-tsys1 via `tests/remote.sh`:
|
||||
|
||||
```bash
|
||||
# Idempotent install + configure (safe to re-run):
|
||||
@@ -102,6 +103,13 @@ PROX_HOST=pfv-tsys1 bash tests/remote.sh prox-file ups/discover.sh
|
||||
PROX_HOST=pfv-tsys1 bash tests/remote.sh prox-file ups/status.sh
|
||||
```
|
||||
|
||||
The HA integration script runs from your workstation (needs HA API access):
|
||||
|
||||
```bash
|
||||
# Add the NUT integration to Home Assistant (idempotent):
|
||||
bash ups/setup-ha-nut.sh
|
||||
```
|
||||
|
||||
`setup.sh` accepts environment overrides for serials/VIDs/PIDs/usernames, so it
|
||||
can be repurposed for other hosts or UPS units. Passwords for `monuser` and
|
||||
`homeassistant` are auto-generated on first run and reused on subsequent runs
|
||||
@@ -116,27 +124,54 @@ unplugged).
|
||||
|------|---------|
|
||||
| `/etc/udev/rules.d/99-nut-ups.rules` | Grant nut group rw on raw USB + hidraw devices (both subsystems) |
|
||||
| `/etc/nut/ups.conf` | `usbhid-ups` device(s), pinned by serial + subdriver |
|
||||
| `/etc/nut/upsd.conf` | `LISTEN 127.0.0.1` + `LISTEN <tailscale>` on port 3493 |
|
||||
| `/etc/nut/upsd.conf` | `LISTEN 127.0.0.1` + `LISTEN <tailscale>` + `LISTEN <lan>` on port 3493 |
|
||||
| `/etc/nut/upsd.users` | `monuser` (master) + `homeassistant` (read-only) credentials |
|
||||
| `/etc/nut/upsmon.conf` | Local master monitor + `SHUTDOWNCMD` |
|
||||
| `/etc/nut/nut.conf` | `MODE=netserver` |
|
||||
|
||||
## Home Assistant integration
|
||||
|
||||
The NUT integration is added automatically by `setup-ha-nut.sh`, which drives
|
||||
HA's REST config-flow API. It is idempotent (skips if the entry exists).
|
||||
|
||||
```bash
|
||||
# Prerequisites: create token + password files (one-time):
|
||||
mkdir -p ~/.config/pfvcluster
|
||||
# HA → Profile → Long-Lived Access Tokens → Create Token:
|
||||
echo -n 'YOUR_HA_TOKEN' > ~/.config/pfvcluster/ha-token
|
||||
# Password is in /etc/nut/upsd.users on pfv-tsys1 (the homeassistant user):
|
||||
echo -n 'YOUR_NUT_PASS' > ~/.config/pfvcluster/nut-password
|
||||
chmod 600 ~/.config/pfvcluster/{ha-token,nut-password}
|
||||
|
||||
# Run:
|
||||
bash ups/setup-ha-nut.sh
|
||||
```
|
||||
|
||||
### Why LAN IP, not Tailscale
|
||||
|
||||
upsd listens on **both** the Tailscale IP (`100.121.189.98`) **and** the LAN IP
|
||||
(`192.168.3.11`). The HA NUT integration uses the **LAN IP** because HAOS runs
|
||||
Tailscale as an isolated add-on container — the HA core container cannot route
|
||||
to Tailscale IPs. Since pfv-bms (HA, `192.168.3.12`) and pfv-tsys1 (`192.168.3.11`)
|
||||
share the same vmbr0 bridge, LAN connectivity is instant and reliable.
|
||||
|
||||
### Manual UI alternative
|
||||
|
||||
In Home Assistant → **Settings → Devices & Services → Add Integration → NUT**:
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Host | `pfv-tsys1` (or `100.121.189.98`) over Tailscale |
|
||||
| Host | `192.168.3.11` (LAN — HAOS can't reach Tailscale IPs from the HA container) |
|
||||
| Port | `3493` |
|
||||
| Username | `homeassistant` |
|
||||
| Password | *(printed by `setup.sh` / stored in `/etc/nut/upsd.users`)* |
|
||||
| Password | *(stored in `/etc/nut/upsd.users` on pfv-tsys1)* |
|
||||
| UPS | `apc-smartups-c1500` |
|
||||
|
||||
HA auto-discovers available UPS names after authentication. Useful sensors for
|
||||
power-usage tracking: `sensor.apc_smartups_c1500_ups_load` (Watts, via `ups.load`
|
||||
× VA rating), `sensor.apc_smartups_c1500_battery_runtime`,
|
||||
`sensor.apc_smartups_c1500_ups_realpower`.
|
||||
### Live sensors
|
||||
|
||||
HA exposes UPS data as sensors (prefix `sensor.apc_smartups_c1500_`):
|
||||
`battery_charge`, `status` (Online/On Battery), `status_data` (OL/OB/DISCHRG).
|
||||
Additional sensors (load, runtime, voltage) populate as the UPS reports them.
|
||||
|
||||
## Daily operations
|
||||
|
||||
|
||||
Reference in New Issue
Block a user