Files
EMS/CLAUDE.md
Lutz Finsterle 99613c52ae Initial commit: EMS — Energie Management System
Complete self-consumption optimisation system for 7 kWp PV installation:
- Prometheus collector (grid power, SOC, PV, per-phase, compressor)
- Pure decision engine with SOC gates, hysteresis, priority ordering
- Shelly Gen1/Gen2 actuator (SHA-256 Digest auth, PM power readback)
- Viessmann OAuth2 client for DHW temperature control
- PV forecast integration (forecast.solar)
- Wallbox mutual exclusion (VX3 4.6 kW AC output constraint)
- Car-not-charging detection via Shelly PM
- Compressor idle → early SG-Ready release
- Per-phase grid power for single-phase wallbox decisions
- Manual override detection and web UI with override buttons
- Full unit test coverage for decision engine
- systemd service, Makefile, complete documentation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 10:02:16 +02:00

3.5 KiB

EMS — Energie Management System

Project overview

Eigenverbrauchsoptimiertes Energie Management System für eine Heim-PV-Anlage. Liest Zustandsdaten aus einem bestehenden Prometheus, trifft Schaltentscheidungen basierend auf SOC-Schwellen und Hysterese, und steuert Shelly-Aktoren per HTTP.

Architecture

Prometheus (192.168.0.23:9090) → Collector (PromQL) → Decision Engine → Actuator → Shelly HTTP
                                                            ↓
                                                   Prometheus Exporter (:9099)

Layers

  • Collector (internal/collector/): Reads system state via PromQL instant queries from existing Prometheus
  • Engine (internal/engine/): Pure decision logic — SOC gates, hysteresis, priority ordering. No side effects, fully testable.
  • Actuator (internal/actuator/): Executes switching via Shelly Gen1/Gen2 HTTP APIs
  • Metrics (internal/metrics/): Exports EMS decisions as Prometheus metrics on :9099

Key design decisions

  • Data reads come from Prometheus (already scraped every 2min by custom Viessmann exporter)
  • Data writes (Shelly switching, Viessmann API for WW) are done directly by the EMS
  • pcc_transfer_power_exchange_value: positive = grid import, negative = grid export
  • Decision engine is pure: func Decide(state, now) → []Action — easy to unit test
  • All thresholds configurable via YAML (configs/ems-config.yaml)

Hardware setup

Component Details
PV 7 kWp, Trina VX3 4.6 hybrid inverter
Battery 8 kWh, managed autonomously by VX3
Heat pump Viessmann Vitocal 200-S AWB-E-AC
SG-Ready Shelly 1 Gen1 @ 192.168.42.90 → heating buffer +5°C
Wallbox A 2kW, Shelly Plus 1 PM Gen2 @ 192.168.42.107
Wallbox B 4kW, Shelly Plus 1 Gen2 @ 192.168.42.51
WW control Viessmann API (OAuth2, refresh token) — NOT YET IMPLEMENTED

Prometheus metrics (read from existing instance)

  • pcc_transfer_power_exchange_value — grid power (pos=import, neg=export)
  • ess_stateOfCharge_value — battery SOC (0-100%)
  • photovoltaic_production_current_value — current PV production (W)
  • ess_power_value — battery charge/discharge (W)
  • heating_compressors_0_power_value — heat pump compressor power (W)
  • heating_sensors_temperature_outside_value — outdoor temp (°C)

Decision logic summary

  1. SOC gates: <50% all blocked, 50-70% SG-Ready only, 70-90% +WB_A, >90% all
  2. Export threshold + hysteresis (4min on, 6min off)
  3. Priority: SG-Ready (heating only) → WB_A (2kW) → WB_B (4kW)
  4. Shutdown: reverse order, respecting min runtimes (15min WB, 30min SG-Ready)
  5. Emergency brake: SOC drops below threshold → immediate shutdown

TODO / open items

  • Viessmann API OAuth2 write integration (WW-Solltemperatur)
  • PV Forecast integration (Solcast/forecast.solar) for strategic layer
  • Modbus TCP/RS485 for faster PCC data (Phase 3)
  • Grafana dashboard for EMS metrics
  • go.sum generation (go mod tidy)
  • Integration tests with mock Prometheus / mock Shelly endpoints

Commands

make build      # Build binary
make test       # Run unit tests
make dry-run    # Run without switching (log only)
make run        # Run with live switching
make install    # Install as systemd service

Code style

  • Go 1.23+, structured logging via log/slog
  • Idiomatic Go: no frameworks, standard library where possible
  • Engine is pure (no I/O) — all side effects in Collector and Actuator
  • Config via YAML, parsed with gopkg.in/yaml.v3
  • Prometheus metrics via prometheus/client_golang