# 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) ww_top_temp: "heating_dhw_sensors_temperature_dhwCylinder_top_value" pv_yield_today: "photovoltaic_production_cumulated_currentDay" # Wh, converted to kWh by collector (÷1000) # DHW cylinder top temperature (°C) # 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_max_setpoint_c: 60 # absolute maximum WW setpoint (°C) ww_hysteresis_c: 5 # Viessmann switchOn = setpoint - hysteresis (°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) sg_ready_startup_min: 10 # grace period after SG-Ready activation before compressor idle check starts (min) 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 # Battery physical properties (for EOD soft-stop calculation) battery: capacity_kwh: 8.0 # usable battery capacity (kWh) # Proactive car charging strategy (forecast-driven, no export threshold required) car_charging: min_soc: 25 # start car charging only if battery SOC ≥ this (%) soc_floor: 5 # emergency brake floor — stop if SOC drops below this (%) soc_high_bypass_pct: 90 # activate without forecast check when SOC ≥ this % (battery full → always charge) eod_soc_target: 90 # desired battery SOC at end of day / sunset (%) eod_time: "16:00" # start EOD soft-stop checks after this time no_car_retry_min: 30 # minutes before retrying after no-car detection pv_threshold_a_w: 1000 # min PV production to start WallboxA (W) — 50% of 2kW rated pv_threshold_b_w: 2000 # min PV production to start WallboxB (W) — 50% of 4kW rated grid_delta_thresh_w: 2000 # min grid power shift after WallboxB activation = car detected (W) # 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