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>
77 lines
3.5 KiB
Markdown
77 lines
3.5 KiB
Markdown
# 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
|
|
```bash
|
|
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
|