Commit 334c851
feat(ha): Home Assistant integration v1 — wall power, efficiency, rack env
DCGM measures GPU power; node_exporter has none of the rest. The blind
spots — real socket-side W per host, tokens/W efficiency joined against
LiteLLM throughput, and rack temperature/humidity/AC state — get filled
by a tiny Python exporter that polls Home Assistant REST every 15 s.
The exporter exposes ha_* Prometheus metrics on :9105, obs Prometheus
scrapes them like node_exporter, recording rules fold them into
cluster:wall_power_w:sum / cluster:tokens_per_watt, and the FastAPI
cluster payload picks them up via _ha_power / _ha_env helpers. Three
new tm-cards in the Telemetry section render the values; if the
exporter is absent the cards self-hide and nothing else changes.
Hearth-OSS friendly: NO node ids or plug IDs are hardcoded. Operators
declare the topology in hearth.yaml:
ha:
base_url: "http://homeassistant.local:8123"
rack_sensor_id: "..."
rack_ac_plug_id: "..."
blocklist: ["2051674991"] # never monitor router plug
nodes:
- id: spark-01
sources:
ha_plug_id: "2029950736"
Hard safety: the MT6000 router id is in a default blocklist that the
exporter cannot bypass — if any node points at a blocklisted plug, the
exporter refuses to start. The exporter only does GET /api/states/*;
control endpoints stay explicitly out of v1 scope.
Honest degradation:
- HA exporter absent → /api/cluster.power / .env return null → the
three tm-cards in Telemetry are hidden entirely.
- Stale sensor → that field null; siblings still render.
- Node without ha_plug_id (e.g. Atlas with no smart plug) → its wall-
power slot is "—", not "0 W".
Files:
- server/integration/ha-exporter.py (NEW, config-driven, ~180 lines)
- server/deploy/ha-exporter.service (NEW, systemd unit)
- server/deploy/install-ha-exporter.sh (NEW, install + smoke test)
- config/hearth.example.yaml (docs new ha: block)
- docs/topology.md (new ha: section)
- docs/requirements/ha-integration.md (NEW, full requirements + spec handoffs)
- docs/requirements/01..05-*.md (NEW, prefixed split for workflow tooling)
- server/api/main.py (+_ha_power, _ha_env; /api/cluster adds power, env)
- server/prometheus/prometheus.yml (new home_assistant scrape job)
- server/prometheus/rules/recording.yml (cluster:wall_power_w:sum, tokens_per_watt, node:power_overhead_ratio)
- server/prometheus/rules/alerts.yml (HAExporterDown, RackOverTemp, RackACOff, NodePowerLost, RackSensorLowBattery)
- sections-b.jsx (TelemetrySection adds 3 tm-cards)
- data.js (mirrors p.cluster.power / .env into live.*)
- styles.css (.tm-card)
- index.html (cache bump to 20260601a)
- CHANGELOG.md (Added entry)
- .gitignore (ignore .claude/ workflow hooks)
NOT YET done in this commit (left to operator + a follow-up session):
- T4 systemd deploy on Atlas (run install-ha-exporter.sh)
- T5-T7 Prometheus reload + verify scrape job comes up
- T11 Playwright end-to-end UI verification
- T12 HA-offline degradation smoke test
- T13 verify-no-prod-impact + DeepSeek inference 200
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>1 parent fe89423 commit 334c851
21 files changed
Lines changed: 994 additions & 8 deletions
File tree
- config
- docs
- requirements
- server
- api
- deploy
- integration
- prometheus
- rules
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
45 | 45 | | |
46 | 46 | | |
47 | 47 | | |
| 48 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| 12 | + | |
12 | 13 | | |
13 | 14 | | |
14 | 15 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
187 | 187 | | |
188 | 188 | | |
189 | 189 | | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
342 | 342 | | |
343 | 343 | | |
344 | 344 | | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
345 | 349 | | |
346 | 350 | | |
347 | 351 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
0 commit comments