Files
EMS/configs/ems-config.yaml
Lutz Finsterle fc535d395c Add per-consumer accepted import tolerance for wallboxes
With a 4.6kW inverter and 4kW WallboxB + ~450W house load, any PV dip
causes brief grid import and would trigger shutdown/restart cycling.

New config: wallbox_a_accepted_import_w / wallbox_b_accepted_import_w
WallboxB set to 600W: engine skips shutdown if import <= 600W, keeping
the car charging through short cloud shadows. WallboxA disabled (2kW
leaves sufficient inverter headroom without needing tolerance).

Logic: shutdownLastConsumer checks per-consumer tolerance before
applying min-runtime and initiating turn-off.

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

129 lines
5.9 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# EMS Configuration
# All thresholds and timing parameters are configurable here.
prometheus:
url: "http://192.168.0.23:9090"
# Metric names from the custom Viessmann exporter
metrics:
grid_power_exchange: "pcc_transfer_power_exchange_value" # positive = import, negative = export
battery_soc: "ess_stateOfCharge_value" # 0-100%
pv_production: "photovoltaic_production_current_value" # watts
battery_power: "ess_power_value" # watts
compressor_power: "heating_compressors_0_sensors_power_value * heating_compressors_0_power_value * 10" # actual watts: sensors(%) × rated_kW(10) × 10
ambient_temp: "heating_sensors_temperature_outside_value" # °C
phase_l1_power: "pcc_ac_active_power_phaseOne" # per-phase grid power L1 (W)
phase_l2_power: "pcc_ac_active_power_phaseTwo" # per-phase grid power L2 (W)
phase_l3_power: "pcc_ac_active_power_phaseThree" # per-phase grid power L3 (W)
# Shelly actuators
shelly:
sg_ready:
ip: "192.168.42.90"
gen: 1 # Gen1: /relay/0?turn=on|off
wallbox_a:
ip: "192.168.42.185"
gen: 2 # Gen2: /rpc/Switch.Set
power_w: 2000
password: "" # set Shelly admin password if auth is enabled
wallbox_b:
ip: "192.168.42.51"
gen: 2 # Gen2: /rpc/Switch.Set
power_w: 4000
password: "" # set Shelly admin password if auth is enabled
# Viessmann API (write access for WW temperature)
viessmann:
token_file: "/etc/ems/viessmann-token.json" # OAuth2 refresh token
client_id: "5d1235548dfaa01ce37db8f865d2df4d"
installation_id: "1253124"
gateway_serial: "7637415006796210"
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)
all_consumers: 90 # above 90%: all consumers
# Hysteresis settings
hysteresis:
export_on_duration: "4m" # export must persist for 4 min before switching on
import_off_duration: "6m" # import must persist for 6 min before switching off
min_runtime_wallbox: "15m"
min_runtime_sg_ready: "30m"
# Power thresholds (grid_power_exchange is positive=import, so we check for negative values)
# These are the NEGATIVE thresholds (export) that trigger consumers
thresholds:
sg_ready_export_w: -500 # export > 500W → SG-Ready
ww_export_w: -500 # export > 500W → WW boost
wallbox_a_export_w: -1800 # export > 1800W → Wallbox A (total, fallback if no phase data)
wallbox_a_phase_export_w: -1500 # per-phase export needed for single-phase Wallbox A
wallbox_b_export_w: -3800 # export > 3800W → Wallbox B
import_off_w: 200 # import > 200W → start shutdown timer
# Strategic layer (PV forecast based, daily)
strategic:
forecast_high_kwh: 25
forecast_mid_kwh: 15
ww_base_c: 48 # normal WW setpoint (°C)
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
ww_window_end: "18:00" # ... until 18:00
schedule_on: "08:00"
schedule_off: "20:00"
# Consumer behavior — idle detection thresholds
consumers:
compressor_idle_w: 500 # heat pump compressor below this = idle (W); running starts at ~2700W, so 500W cleanly separates idle (0W) from running
wallbox_min_charge_w: 50 # wallbox below this = car not charging (W)
idle_cycles: 3 # consecutive idle cycles before early release (~6 min at 2-min poll)
wallbox_a_accepted_import_w: 0 # tolerate this much grid import while WallboxA is running (0 = disabled; WallboxA is 2kW, inverter headroom is sufficient)
wallbox_b_accepted_import_w: 600 # tolerate up to 600W import while WallboxB runs (4kW wallbox + 450W house > 4.6kW inverter max on PV dips)
# Season detection
season:
heating_start_month: 10 # October
heating_end_month: 4 # April
heating_min_ambient_c: 15 # above 15°C outdoor temp: no SG-Ready even within heating months
# 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)
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
# EMS operational settings
ems:
poll_interval: "2m"
listen_addr: ":9099" # Prometheus metrics endpoint
log_level: "info"
state_file: "/run/ems/heartbeat" # written each cycle for state recovery
recovery_timeout: "1h" # ignore Shelly state if EMS was down longer
override_timeout: "1h" # default lockout when EMS detects external Shelly change
override_max_import_w: 800 # cancel override immediately if importing more than this (0 = disabled)
monitor_only_file: "/etc/ems/monitor-only" # presence of this file = monitor-only mode active
ww_boost_disable_file: "/etc/ems/ww-boost-off" # presence of this file = WW boost disabled
trip_goal_file: "/var/lib/ems/trip-goal.json" # persisted active trip goal
session_log_file: "/var/lib/ems/sessions.jsonl" # JSONL log of completed charge sessions
# Known car profiles for trip mode
cars:
mini:
name: "Mini Cooper SE"
battery_kwh: 50.0
bmw:
name: "BMW ix2 eDrive20"
battery_kwh: 63.0