2 Commits

Author SHA1 Message Date
0cd481a5ea Add Docker Compose stack for son's installation (evcc + Prometheus + Grafana)
Self-contained stack for DS218 Synology targeting:
- 2x Fronius inverters (11+22 kW) via shared DataManager, Solar.API v1
- BYD 12 kWh battery connected through Fronius
- 2x Elli Charger Connect 2 via OCPP 1.6j
- evcc handles PV surplus charging, OCPP server built-in
- Prometheus scrapes evcc /metrics, 90d retention
- Grafana auto-provisioned with evcc dashboard and Prometheus datasource

Setup: edit .env (3 IPs + 2 station IDs), point Elli app to OCPP backend,
docker compose up -d. README in German for non-IT handover.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 15:53:44 +02:00
ca0f9ed1c3 house/son: placeholder config for son's installation
All house-specific values (IPs, coordinates, Viessmann credentials, kWp)
replaced with <...> placeholders. Ready to fill in once hardware is known.
Notable differences from house/lutz template:
- installation_id left empty (disables WW boost until credentials obtained)
- base_load_w set to 400W (adjust after measuring)
- forecast lat/lon/declination/azimuth/kwp all require local values

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 11:19:30 +02:00
9 changed files with 326 additions and 21 deletions

View File

@@ -1,10 +1,11 @@
# EMS Configuration
# EMS Configuration — house/son
# Fill in all values marked with <...> before deploying.
# All thresholds and timing parameters are configurable here.
prometheus:
url: "http://192.168.0.23:9090"
url: "http://<prometheus-ip>:9090"
# Metric names from the custom Viessmann exporter
# Metric names from the custom Viessmann exporter (same as house/lutz)
metrics:
grid_power_exchange: "pcc_transfer_power_exchange_value" # positive = import, negative = export
battery_soc: "ess_stateOfCharge_value" # 0-100%
@@ -19,32 +20,33 @@ prometheus:
# Shelly actuators
shelly:
sg_ready:
ip: "192.168.42.90"
ip: "<shelly-sg-ready-ip>"
gen: 1 # Gen1: /relay/0?turn=on|off
wallbox_a:
ip: "192.168.42.185"
ip: "<shelly-wallbox-a-ip>"
gen: 2 # Gen2: /rpc/Switch.Set
power_w: 2000
power_w: 2000 # rated power of the wallbox (W)
password: "" # set Shelly admin password if auth is enabled
wallbox_b:
ip: "192.168.42.51"
ip: "<shelly-wallbox-b-ip>"
gen: 2 # Gen2: /rpc/Switch.Set
power_w: 4000
power_w: 4000 # rated power of the wallbox (W)
password: "" # set Shelly admin password if auth is enabled
# Viessmann API (write access for WW temperature)
# Leave installation_id empty to disable WW boost (EMS runs without it)
viessmann:
token_file: "/etc/ems/viessmann-token.json" # OAuth2 refresh token
client_id: "5d1235548dfaa01ce37db8f865d2df4d"
installation_id: "1253124"
gateway_serial: "7637415006796210"
client_id: "<viessmann-oauth2-client-id>"
installation_id: "" # leave empty to disable WW boost
gateway_serial: "<gateway-serial>"
device_id: "0"
# SOC thresholds — which consumers are allowed at which battery level
soc_thresholds:
block_all: 50 # below 50%: no consumers
sg_ready_only: 70 # 50-70%: only SG-Ready (heating period)
plus_wallbox_a: 90 # 70-90%: + Wallbox A (2kW)
plus_wallbox_a: 90 # 70-90%: + Wallbox A
all_consumers: 90 # above 90%: all consumers
# Hysteresis settings
@@ -68,7 +70,7 @@ thresholds:
strategic:
forecast_high_kwh: 25
forecast_mid_kwh: 15
ww_base_c: 48 # normal WW setpoint (°C)
ww_base_c: 48 # normal WW setpoint (°C) — adjust to your heat pump
ww_boost_high_c: 5 # +5°C on high-forecast days (>25 kWh)
ww_boost_mid_c: 3 # +3°C on medium-forecast days (>15 kWh)
ww_window_start: "12:30" # WW boost only allowed from 12:30
@@ -91,16 +93,16 @@ season:
# PV forecast — forecast.solar (free, no API key required)
forecast:
enabled: true
lat: 48.7839
lon: 9.3889
declination: 20 # panel tilt in degrees
azimuth: 5 # degrees from south (south=0, west=90, east=-90)
kwp: 7.0 # installed peak power
fetch_interval: "4h" # re-fetch during the day (free tier: 12 req/day → 3 fetches)
lat: <latitude> # decimal degrees, e.g. 48.7839
lon: <longitude> # decimal degrees, e.g. 9.3889
declination: <tilt> # panel tilt in degrees from horizontal (0=flat, 90=vertical)
azimuth: <azimuth> # degrees from south (south=0, west=90, east=-90)
kwp: <kwp> # installed peak power in kWp, e.g. 8.5
fetch_interval: "4h" # re-fetch during the day (free tier: 12 req/day → 3 fetches)
fetch_window_start: "07:00"
fetch_window_end: "19:00"
base_load_w: 450 # steady-state house consumption (W) — spikes from kettle/dishwasher are hourly averages and negligible
min_surplus_w: 1800 # min export surplus to trigger wallbox — matches wallbox_a threshold
base_load_w: 400 # steady-state house consumption (W) — measure and adjust
min_surplus_w: 1800 # min export surplus to trigger wallbox charging (W)
# EMS operational settings
ems:

15
deploy/son/.env Normal file
View File

@@ -0,0 +1,15 @@
# ── Son's EMS stack — edit these values before first start ──────────────────
# Static IP of this Synology on the LAN (no trailing slash)
SYNOLOGY_IP=192.168.1.100
# Fronius DataManager IP (the box with the display / web interface)
FRONIUS_IP=192.168.1.XXX
# Elli Charger Connect 2 — OCPP station IDs
# Find these in the Elli app under Settings → Charging Station → Station ID
ELLI_A_STATION_ID=ELLI-XXXXXXXXXX-1
ELLI_B_STATION_ID=ELLI-XXXXXXXXXX-2
# Timezone
TZ=Europe/Berlin

101
deploy/son/README.md Normal file
View File

@@ -0,0 +1,101 @@
# EMS Stack — Einrichtung auf der Synology
## Was läuft hier?
| Dienst | Adresse | Funktion |
|---|---|---|
| evcc | `http://SYNOLOGY-IP:7070` | Ladesteuerung, PV-Überschuss, Haupt-UI |
| Grafana | `http://SYNOLOGY-IP:3000` | Historische Auswertungen |
| Prometheus | `http://SYNOLOGY-IP:9090` | Metrik-Speicher (intern) |
**Passwort Grafana:** `ems2024` (Benutzer: `admin`)
---
## Einmalige Einrichtung
### 1. Synology vorbereiten
Im Synology **Package Center**: **Container Manager** installieren (kostenlos).
### 2. Dateien auf die Synology kopieren
Den gesamten Ordner `deploy/son/` per FileStation oder SCP auf die Synology kopieren,
z.B. nach `/volume1/docker/ems/`.
### 3. `.env` anpassen
Die Datei `.env` im Texteditor öffnen und ausfüllen:
```
SYNOLOGY_IP=192.168.1.100 ← IP-Adresse der Synology
FRONIUS_IP=192.168.1.XXX ← IP-Adresse des Fronius DataManagers
ELLI_A_STATION_ID=ELLI-... ← Station-ID aus der Elli-App (Wallbox A)
ELLI_B_STATION_ID=ELLI-... ← Station-ID aus der Elli-App (Wallbox B)
```
**Station-ID in der Elli-App:** Einstellungen → Ladestation → Station-ID
### 4. Elli Wallboxes konfigurieren
In der **Elli-App** für jede Wallbox:
- Einstellungen → Backend / OCPP
- Backend-URL: `ws://SYNOLOGY-IP:8887/ocpp`
- Station-ID: wie in `.env` eingetragen
### 5. Stack starten
Im Synology **Container Manager****Projekt****Erstellen**:
- Projektname: `ems`
- Pfad: Ordner mit der `docker-compose.yaml`
- Starten
Oder per SSH:
```bash
cd /volume1/docker/ems
docker compose up -d
```
### 6. Prüfen ob alles läuft
- evcc öffnen: `http://SYNOLOGY-IP:7070`
- Unter **Geräte** sollten Fronius (PV + Batterie + Netz) und beide Wallboxen grün erscheinen
- Grafana: `http://SYNOLOGY-IP:3000` → Dashboard **evcc — Laden & PV**
---
## Wenn ein Fronius-Wechselrichter eine eigene IP hat
Falls doch zwei getrennte DataManager (zwei IP-Adressen):
In `evcc.yaml` den Abschnitt `meters``pv` auf zwei Einträge aufteilen:
```yaml
- name: pv1
type: template
template: fronius-solarapi-v1
host: 192.168.1.XXX # Wechselrichter 1 (11 kW)
usage: pv
- name: pv2
type: template
template: fronius-solarapi-v1
host: 192.168.1.YYY # Wechselrichter 2 (22 kW)
usage: pv
```
Und in `site.meters.pv` beide eintragen:
```yaml
pv:
- pv1
- pv2
```
---
## Updates
```bash
cd /volume1/docker/ems
docker compose pull
docker compose up -d
```

View File

@@ -0,0 +1,64 @@
services:
# ── evcc — EV charging controller ─────────────────────────────────────────
evcc:
image: evcc/evcc:latest
container_name: evcc
restart: unless-stopped
ports:
- "7070:7070" # evcc web UI
- "8887:8887" # OCPP server (Elli wallboxes connect here)
volumes:
- ./evcc.yaml:/etc/evcc.yaml:ro
- evcc-data:/root/.evcc
environment:
- TZ=${TZ}
networks:
- ems-net
# ── Prometheus — metrics storage ───────────────────────────────────────────
prometheus:
image: prom/prometheus:latest
container_name: prometheus
restart: unless-stopped
ports:
- "9090:9090"
volumes:
- ./prometheus/prometheus.yaml:/etc/prometheus/prometheus.yml:ro
- prometheus-data:/prometheus
command:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.retention.time=90d
- --storage.tsdb.path=/prometheus
environment:
- TZ=${TZ}
networks:
- ems-net
# ── Grafana — dashboards ───────────────────────────────────────────────────
grafana:
image: grafana/grafana-oss:latest
container_name: grafana
restart: unless-stopped
ports:
- "3000:3000"
volumes:
- ./grafana/provisioning:/etc/grafana/provisioning:ro
- grafana-data:/var/lib/grafana
environment:
- TZ=${TZ}
- GF_SECURITY_ADMIN_PASSWORD=ems2024
- GF_USERS_ALLOW_SIGN_UP=false
- GF_ANALYTICS_REPORTING_ENABLED=false
- GF_SERVER_ROOT_URL=http://${SYNOLOGY_IP}:3000
networks:
- ems-net
volumes:
evcc-data:
prometheus-data:
grafana-data:
networks:
ems-net:
driver: bridge

94
deploy/son/evcc.yaml Normal file
View File

@@ -0,0 +1,94 @@
# evcc configuration — son's installation
# Edit the IPs/IDs below to match your hardware.
# All values with XXX must be filled in before starting.
network:
schema: http
host: 0.0.0.0 # listen on all interfaces inside container
port: 7070
log: warn
levels:
core: info
charger: info
# ── Interval ─────────────────────────────────────────────────────────────────
interval: 30s # control loop interval
# ── Grid meter (Fronius Smart Meter via DataManager) ─────────────────────────
meters:
- name: grid
type: template
template: fronius-solarapi-v1
host: ${FRONIUS_IP} # Fronius DataManager IP
usage: grid
- name: pv
type: template
template: fronius-solarapi-v1
host: ${FRONIUS_IP} # same DataManager — covers both inverters aggregated
usage: pv
- name: battery
type: template
template: fronius-solarapi-v1
host: ${FRONIUS_IP} # BYD battery connected to Fronius
usage: battery
# ── Site ─────────────────────────────────────────────────────────────────────
site:
title: "Haus"
meters:
grid: grid
pv:
- pv
battery:
- battery
# ── Chargers (Elli Charger Connect 2 via OCPP) ───────────────────────────────
# The Elli app: Settings → Charging Station → Backend URL
# Set to: ws://<SYNOLOGY_IP>:8887/ocpp
# The station ID is shown in the same screen.
chargers:
- name: wallbox_a
type: template
template: ocpp
stationid: ${ELLI_A_STATION_ID}
connecttimeout: 5m
- name: wallbox_b
type: template
template: ocpp
stationid: ${ELLI_B_STATION_ID}
connecttimeout: 5m
# ── Load points ──────────────────────────────────────────────────────────────
loadpoints:
- title: "Wallbox A (11 kW)"
charger: wallbox_a
mode: pv # default: charge on PV surplus only
mincurrent: 6 # A — minimum OCPP current (legal minimum = 6A)
maxcurrent: 16 # A — 16A × 3 phases × 230V ≈ 11 kW
phases: 3
- title: "Wallbox B (8 kW)"
charger: wallbox_b
mode: pv
mincurrent: 6
maxcurrent: 12 # A — 12A × 3 phases × 230V ≈ 8.3 kW
phases: 3
# ── Tariff (optional — enables cost display) ─────────────────────────────────
# Uncomment and adjust if you want cost/CO2 tracking
# tariffs:
# currency: EUR
# grid:
# type: fixed
# price: 0.30 # €/kWh
# ── OCPP server ───────────────────────────────────────────────────────────────
ocpp:
port: 8887
# ── Prometheus metrics ────────────────────────────────────────────────────────
# evcc exposes /metrics on the main port (7070)

View File

@@ -0,0 +1,10 @@
apiVersion: 1
providers:
- name: evcc
type: file
disableDeletion: false
updateIntervalSeconds: 60
options:
path: /etc/grafana/provisioning/dashboards
foldersFromFilesStructure: false

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,9 @@
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
editable: false

View File

@@ -0,0 +1,9 @@
global:
scrape_interval: 30s
evaluation_interval: 30s
scrape_configs:
- job_name: evcc
static_configs:
- targets: ['evcc:7070']
metrics_path: /metrics