rest_command integration silently fails to register on this box (404 services, valid config+secret; root-cause pending #741). shell_command curl is the working mechanism; arm-switch gate unchanged. https://projects.knownelement.com/issues/355
🏠 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 (#344 umbrella) · Docs: VP TechOps on Discourse
🗺️ System architecture
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 |
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 🔒 |
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
!secretindirection into an on-boxsecrets.yaml(gitignored). Verified by full-history scans. - Badge IDs are NOT in this repo. The roster lives in the private
KNEL/access-rosterrepo (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. - HomeKit is controller-only (founder ruling 2026-09-04). HA pulls
from HomeKit accessories when one enters the fleet; HA entities are
NEVER exposed to Apple Home — no
homekitbridge integration, no outward QR pairing.
🚀 Deployment pipeline
Branch model (2026-09-04): dev = WIP integration — the pipeline
IGNORES it. release = what the box runs — pushing release IS the deploy.
master is frozen (pre-branch-model history). A PR process with AI +
human reviewers will gate dev → release (founder, upcoming).
flowchart LR
PUSH[📝 push to dev] --> NOOP[🚫 pipeline ignores dev]
REL[🚀 merge dev → release<br/>and push] --> HOOK[gitea webhook<br/>fast path]
REL --> SHA[sha-watch<br/>release sha · 300 s poll]
HOOK & SHA --> CHECK{changed files?}
CHECK -->|configuration.yaml,<br/>packages/**| RESTART[♻️ core restart<br/>webhook triggers need it]
CHECK -->|other YAML| RELOAD[⚡ reload only<br/>~5 min to live]
Gate before pushing release (and after any packages/** change on
dev): 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 - Wider fleet (Proxmox, DNS, monitoring):
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 for the full text.