Also absorbs PFVCluster operator tooling: tools/ha-ws-call.py and docs/reference-packs/ (historical, NOT loaded by HA — clearly marked). No live-config changes in this commit. Detail: https://projects.knownelement.com/issues/762
172 lines
6.8 KiB
Markdown
172 lines
6.8 KiB
Markdown
# 🏠 pfv-bms — the house brain
|
||
|
||
> **Datacenter-grade monitoring and access control for a private residence + server room** —
|
||
> 372 entities, ~260 sensors, and a badge-actuated door, all deployed by `git push`.
|
||
|
||

|
||

|
||

|
||

|
||

|
||
|
||
Production Home Assistant configuration for the Turnsys PFV —
|
||
environmental monitoring (temperatures, power, UPS) across a seven-node
|
||
Proxmox fleet plus single-board computers, server-room badge access
|
||
control, and tiered alerting to mobile devices.
|
||
|
||
Work tracking: [Redmine](https://projects.knownelement.com)
|
||
([#344](https://projects.knownelement.com/issues/344) umbrella) ·
|
||
Docs: [VP TechOps on Discourse](https://community.turnsys.com/c/vp-techops)
|
||
|
||
---
|
||
|
||
## 🗺️ System architecture
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
subgraph FIELD[📡 The field]
|
||
TS[🖥️ 7× Proxmox hosts<br/>lm-sensors + iDRAC extends]
|
||
BOARDS[🥧 SBCs<br/>pfvsvrpi · jetson · 3× subopi]
|
||
UPS[🔋 UPS on tsys1<br/>NUT server :3493]
|
||
PDU[🔌 Garage PDU<br/>SNMPv1 relay]
|
||
METER[⚡ Utility meter<br/>Smart Meter Texas]
|
||
READER[🪪 Badge readers<br/>pfvsvrpi · ultix-field]
|
||
end
|
||
|
||
subgraph HA[🏠 pfv-bms — this repo]
|
||
INGEST[📦 packages/plant_snmp.yaml<br/>SNMP pollers → raw sensors]
|
||
TEMPLATE[🧮 template sensors<br/>raw → °F/units + thresholds]
|
||
DOOR[🚪 packages/doorman.yaml<br/>scan ingest + decision engine]
|
||
ROSTER[🔒 roster sensors<br/>REST poll of private access-roster]
|
||
ENERGY[📊 utility_meter +<br/>Riemann-sum energy]
|
||
AUTO[🚨 automations.yaml<br/>tiered thermal + UPS alerts]
|
||
end
|
||
|
||
subgraph OUT[📲 Outcomes]
|
||
ALERT[📱 push alerts<br/>companion apps]
|
||
LOG[📓 logbook + history<br/>every scan, every degree]
|
||
DASH[🖥️ dashboards<br/>server-room + plant]
|
||
RELAY[⚡ doorctl → usbrelay<br/>→ door strike]
|
||
end
|
||
|
||
READER -->|webhook| DOOR
|
||
DOOR -->|whitelist| ROSTER
|
||
DOOR -->|armed + valid| RELAY
|
||
TS & BOARDS -->|SNMP| INGEST
|
||
UPS -->|NUT| INGEST
|
||
PDU -->|SNMP relay| INGEST
|
||
METER -->|REST| INGEST
|
||
INGEST --> TEMPLATE --> AUTO
|
||
INGEST --> ENERGY
|
||
AUTO --> ALERT
|
||
TEMPLATE & DOOR --> LOG & DASH
|
||
```
|
||
|
||
### 🛡️ Badge access lane (server-room door)
|
||
|
||
| Component | Role | Lives |
|
||
|-----------|------|-------|
|
||
| USB HID readers | 13.56 MHz badge readers (keyboard-emulating) | door + dev bench |
|
||
| [`KNEL/doorman`](https://git.knownelement.com/KNEL/doorman) | pure-bash listener, decode, webhook, doorctl | `pfvsvrpi` (prod), `ultix-field` (dev) |
|
||
| `packages/doorman.yaml` | scan ingest, whitelist decision, alerts, unlock dispatch | **this repo** |
|
||
| [`KNEL/access-roster`](https://git.knownelement.com/KNEL/access-roster) 🔒 | badge IDs + holders — **private**, two-human PR gate | gitea (private) |
|
||
| `doorctl` + usbrelay | socket-activated, token + source-IP ACL, EXIT-trap secured | `pfvsvrpi` |
|
||
|
||
Fail-closed end to end: HA unreachable → the door does not open.
|
||
Break-glass badges fire the relay locally (works with HA down) and
|
||
report `local_unlock` so HA never double-fires. Arm switch
|
||
(`input_boolean.doorman_unlock_enabled`) is founder-level control.
|
||
Verified end-to-end 2026-09-03: real armed scan → dispatch → relay.
|
||
|
||
---
|
||
|
||
## 🌡️ Environmental monitoring
|
||
|
||
Raw SNMP pollers (`packages/plant_snmp.yaml`) → unit-normalized
|
||
template sensors → tiered automations. Every host contributes
|
||
lm-sensors extends; iDRAC hosts add inlet/exhaust/CPU; NUT feeds UPS
|
||
electricals.
|
||
|
||
| Family | Coverage |
|
||
|--------|----------|
|
||
| 🖥️ Host silicon | CPU/drive/NVMe/PCH temps on 7 Proxmox hosts |
|
||
| 🌡️ Thermal zones | rack ambient, DIMM banks, GPU, tsys7 iDRAC inlet→exhaust ΔT |
|
||
| 🥧 Boards | pfvsvrpi, jetson, 3× subopi |
|
||
| 🔋 UPS | tsys1 battery charge/voltage, on-battery + runtime alerts |
|
||
| ⚡ Power | whole-house kWh (utility), tsys6/7 node watts + kWh integrals, garage PDU amps |
|
||
| 🏠 Rooms | temp/humidity/battery sensors (Govee, cloud-fed) |
|
||
|
||
### 🚨 Alert tiers (automations.yaml)
|
||
|
||
| Trigger | Threshold |
|
||
|---------|-----------|
|
||
| Drive / DIMM temps | > 140 °F |
|
||
| NVMe | > 150 °F |
|
||
| PCH | > 160 °F |
|
||
| GPU | > 185 °F |
|
||
| Ambient | > 104 °F |
|
||
| UPS | on-battery transitions + runtime |
|
||
| Watchdog | plant sensor freshness sweep every 30 min |
|
||
|
||
### 📊 Snapshot (2026-09-03)
|
||
|
||
372 entities · ~260 sensors · 45 temperature sensors · 21
|
||
electrical/energy sensors · 8 todo lists · 2-way alerting to companion
|
||
apps.
|
||
|
||
---
|
||
|
||
## 🔐 Security model (read this before forking)
|
||
|
||
- **No secrets in git, ever.** Everything sensitive is `!secret`
|
||
indirection into an on-box `secrets.yaml` (gitignored). Verified by
|
||
full-history scans.
|
||
- **Badge IDs are NOT in this repo.** The roster lives in the *private*
|
||
[`KNEL/access-roster`](https://git.knownelement.com/KNEL/access-roster)
|
||
repo (two-human PR gate) and is fetched live at runtime.
|
||
- **Fail-closed access control** — see the badge lane above.
|
||
- Battery room sensors and locks are cloud-fed; credentials also live
|
||
in `secrets.yaml`.
|
||
|
||
## 🚀 Deployment pipeline
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
PUSH[📝 git push] --> HOOK[gitea webhook<br/>fast path]
|
||
PUSH --> SHA[sha-watch<br/>safety net · 300 s poll]
|
||
HOOK & SHA --> CHECK{changed files?}
|
||
CHECK -->|packages/**| RESTART[♻️ core restart<br/>webhook triggers need it]
|
||
CHECK -->|other YAML| RELOAD[⚡ reload only<br/>~5 min to live]
|
||
```
|
||
|
||
Gate before pushing `packages/**`: `ha core check` must pass on-box.
|
||
Measured end-to-end: 4 m 52 s commit → live.
|
||
|
||
## 🗺️ Repo map
|
||
|
||
| Path | What |
|
||
|------|------|
|
||
| `configuration.yaml` | entrypoint + package includes |
|
||
| `packages/doorman.yaml` | 🚪 badge scan ingest + decisions |
|
||
| `packages/plant_snmp.yaml` | 🌡️ SNMP pollers + templates |
|
||
| `automations.yaml` | 🚨 tiered alerts + watchdogs |
|
||
| `dashboards/` | Lovelace dashboards |
|
||
| `tools/` | operator tooling (websocket CLI) |
|
||
| `docs/reference-packs/` | historical YAML references — NOT loaded |
|
||
|
||
## 🔗 Related
|
||
|
||
- Badge listener + relay code: [`KNEL/doorman`](https://git.knownelement.com/KNEL/doorman)
|
||
- Wider fleet (Proxmox, DNS, monitoring): [`KNEL/PFVCluster`](https://git.knownelement.com/KNEL/PFVCluster)
|
||
- Badge roster (private): `KNEL/access-roster` 🔒
|
||
|
||
## License
|
||
|
||
Copyright © 2026 Known Element Enterprises / Charles Wyble.
|
||
|
||
This program is free software: you can redistribute it and/or modify
|
||
it under the terms of the GNU Affero General Public License as
|
||
published by the Free Software Foundation, either version 3 of the
|
||
License, or (at your option) any later version. See [LICENSE](LICENSE)
|
||
for the full text.
|