BALKONKRAFTWERK: INTELLIGENTES ENERGIE‐MANAGEMENT - Kieft-C/Zendure-BKW-PV GitHub Wiki
Stand: 04.10.2026 20:00
# BALKONKRAFTWERK: INTELLIGENTES ENERGIE-MANAGEMENT
# ═══════════════════════════════════════════════════════════════════════════
#
# Vollautomatische, preis-intelligente Steuerung von:
# • Zendure Hyper A (2 kWh) + Hyper B (2 kWh)
# • Intelligente Lade-Schwellen basierend auf Forecast + Verfügbar
# • Smart_Charging für 5 Großverbraucher
# • SMART-Bias für Hyper A/B Ausgleich
#
# Struktur:
# SECTION 1: TEMPLATE-SENSOREN (berechnet)
# SECTION 2: INPUT-HELPER (State-Tracking, Zähler, etc.)
# SECTION 3: AUTOMATIONEN (8-10 Autos, je Schicht eine)
#
# ═══════════════════════════════════════════════════════════════════════════
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 1: TEMPLATE-SENSOREN (Berechnet aus Raw-Daten)
# ═══════════════════════════════════════════════════════════════════════════
# Diese Sensoren berechnen dynamisch die Lade-Schwellen, Verfügbar-Werte, etc.
template:
- sensor:
# ─────────────────────────────────────────────────────────────────
# 1.1: PREIS-TEMPLATES (Min, Schwellen, etc.)
# ─────────────────────────────────────────────────────────────────
# 1.1: ROHSENSOREN (EPEX SPOT)au
# ─────────────────────────────────────────────────────────────────
# Nutze direkt:
# sensor.epex_spot_data_average_price
# sensor.epex_spot_data_lowest_price ← MIN-PREIS!
# sensor.epex_spot_data_market_price ← AKTUELLER PREIS!
# sensor.epex_spot_data_median_price
# sensor.epex_spot_data_total_price
# Hinweis: Nutze die vorhandenen Sensoren stattdessen:
# sensor.hyper_a_anzahl_tage_seit_100
# sensor.hyper_b_anzahl_tage_seit_100
# SCHWELLE LADEN (ADAPTIV)
# 70% Wirkungsgrad, 21ct Fixkosten, 3ct Sicherheitsmarge
# MIT MwSt 19% berechnet, aber NETTO-Output
- unique_id: balkonkraftwerk_price_threshold_load_wirkungsgrad
name: "BKW: Preis-Schwelle Laden Wirkungsgrad"
unit_of_measurement: "€/kWh"
availability: >
{{ states('sensor.epex_spot_data_lowest_price') not in ['unknown', 'unavailable', 'none', '']
and states('sensor.epex_spot_data_highest_price') not in ['unknown', 'unavailable', 'none', '']
and states('sensor.epex_spot_data_median_price') not in ['unknown', 'unavailable', 'none', ''] }}
state: >
{% set lowest = states('sensor.epex_spot_data_lowest_price') | float(999) %}
{% set highest = states('sensor.epex_spot_data_highest_price') | float(999) %}
{% set median = states('sensor.epex_spot_data_median_price') | float(999) %}
{% if lowest == 999 or highest == 999 or median == 999 %}
{{ none }}
{% elif lowest < 0.00 %}
{# NEGATIVE Preise = fix Wert 0.00! #}
{{ (0.00) | round(4) }}
{% else %}
{% set spread = highest - lowest %}
{% if spread > 0.15 %}
{# SPREAD zu groß: Nutze Quantil-Range (lowest bis median) #}
{# LADEN = 20% des Quantils #}
{% set quantil_range = median - lowest %}
{% set schwelle = lowest + (quantil_range * 0.20) %}
{{ [schwelle, 0] | max | round(4) }}
{% else %}
{# SPREAD normal: Break-Even Berechnung mit HIGHEST #}
{% set threshold_brutto = ((highest + 0.21) * 1.19 * 0.70) - 0.21 - 0.03 %}
{% set threshold_netto = threshold_brutto / 1.19 %}
{{ [threshold_netto, 0] | max | round(4) }}
{% endif %}
{% endif %}
# SCHWELLE KALIBRIERUNG (ADAPTIV)
# 70% Wirkungsgrad, 21ct Fixkosten, 1ct Sicherheitsmarge (1ct teurer erlaubt!)
# MIT MwSt 19% berechnet, aber NETTO-Output
- unique_id: balkonkraftwerk_price_threshold_calibration_wirkungsgrad
name: "BKW: Preis-Schwelle Kalibrierung Wirkungsgrad"
unit_of_measurement: "€/kWh"
availability: >
{{ states('sensor.epex_spot_data_lowest_price') not in ['unknown', 'unavailable', 'none', '']
and states('sensor.epex_spot_data_highest_price') not in ['unknown', 'unavailable', 'none', '']
and states('sensor.epex_spot_data_median_price') not in ['unknown', 'unavailable', 'none', ''] }}
state: >
{% set lowest = states('sensor.epex_spot_data_lowest_price') | float(999) %}
{% set highest = states('sensor.epex_spot_data_highest_price') | float(999) %}
{% set median = states('sensor.epex_spot_data_median_price') | float(999) %}
{% if lowest == 999 or highest == 999 or median == 999 %}
{{ none }}
{% elif lowest < 0.00 %}
{# NEGATIVE Preise = fix Wert 0.00! #}
{{ (0.00) | round(4) }}
{% else %}
{% set spread = highest - lowest %}
{% if spread > 0.15 %}
{# SPREAD zu groß: Nutze Quantil-Range (lowest bis median) #}
{# LADEN = 35% des Quantils #}
{% set quantil_range = median - lowest %}
{% set schwelle = lowest + (quantil_range * 0.35) %}
{{ [schwelle, 0] | max | round(4) }}
{% else %}
{# SPREAD normal: Break-Even Berechnung mit HIGHEST #}
{% set threshold_brutto = ((highest + 0.21) * 1.19 * 0.70) - 0.21 - 0.01 %}
{% set threshold_netto = (threshold_brutto / 1.19) + 0.01 %}
{{ [threshold_netto, 0] | max | round(4) }}
{% endif %}
{% endif %}
# SCHWELLE Smartcharged
# 70% Wirkungsgrad, 21ct Fixkosten, 1ct Sicherheitsmarge (1ct teurer erlaubt!)
# MIT MwSt 19% berechnet, aber NETTO-Output
- unique_id: balkonkraftwerk_price_threshold_smartcharge_wirkungsgrad
name: "BKW: Preis-Schwelle SmartCharge Wirkungsgrad"
unit_of_measurement: "€/kWh"
availability: >
{{ states('sensor.epex_spot_data_lowest_price') not in ['unknown', 'unavailable', 'none', '']
and states('sensor.epex_spot_data_highest_price') not in ['unknown', 'unavailable', 'none', '']
and states('sensor.epex_spot_data_median_price') not in ['unknown', 'unavailable', 'none', ''] }}
state: >
{% set lowest = states('sensor.epex_spot_data_lowest_price') | float(999) %}
{% set highest = states('sensor.epex_spot_data_highest_price') | float(999) %}
{% set median = states('sensor.epex_spot_data_median_price') | float(999) %}
{% if lowest == 999 or highest == 999 or median == 999 %}
{{ none }}
{% elif lowest < 0.00 %}
{# NEGATIVE Preise = fix Wert 0.00! #}
{{ (0.00) | round(4) }}
{% else %}
{% set spread = highest - lowest %}
{% if spread > 0.15 %}
{# SPREAD zu groß: Nutze Quantil-Range (lowest bis median) #}
{# LADEN = 50% des Quantils #}
{% set quantil_range = median - lowest %}
{% set schwelle = lowest + (quantil_range * 0.50) %}
{{ [schwelle, 0] | max | round(4) }}
{% else %}
{# SPREAD normal: Break-Even Berechnung mit HIGHEST #}
{% set threshold_brutto = ((highest + 0.21) * 1.19 * 0.70) - 0.21 - 0.01 %}
{% set threshold_netto = (threshold_brutto / 1.19) + 0.01 %}
{{ [threshold_netto, 0] | max | round(4) }}
{% endif %}
{% endif %}
# ═══════════════════════════════════════════════════════════════════════
# INFO: Aktives Preis-Szenario
# ═══════════════════════════════════════════════════════════════════════
- unique_id: balkonkraftwerk_price_scenario_active
name: "Balkonkraftwerk: Aktives Preis-Szenario"
icon: mdi:lightning-bolt
state: |
{% set current_price = states('sensor.epex_spot_data_market_price') | float(999) %}
{% set threshold_laden = states('sensor.bkw_preis_schwelle_laden_wirkungsgrad') | float(999) %}
{% set threshold_kalibrierung = states('sensor.bkw_preis_schwelle_kalibrierung_wirkungsgrad') | float(999) %}
{% set mult_laden_str = states('sensor.bkw_multiplikator_laden') %}
{% set mult_kalibrierung_str = states('sensor.bkw_multiplikator_kalibrierung') %}
{#% set mult_teuer_str = states('sensor.bkw_multiplikator_teuer') %#}
{% set mult_laden = mult_laden_str | float(1.15) if mult_laden_str not in ['unknown', 'unavailable', 'none', ''] else 1.15 %}
{% set mult_kalibrierung = mult_kalibrierung_str | float(1.25) if mult_kalibrierung_str not in ['unknown', 'unavailable', 'none', ''] else 1.25 %}
{#% set mult_teuer = mult_teuer_str | float(1.30) if mult_teuer_str not in ['unknown', 'unavailable', 'none', ''] else 1.30 %#}
{% if current_price == 999 or threshold_laden == 999 %}
Unbekannt
{% elif current_price <= threshold_laden %}
A - LADEN (min × {{ mult_laden | round(2) }})
{% elif current_price <= threshold_kalibrierung %}
B - KALIBRIERUNG (min × {{ mult_kalibrierung | round(2) }})
{% else %}
C - ZU TEUER
{% endif %}
# Zeitabhängiges Lade-Ziel (gesamt)
- unique_id: balkonkraftwerk_lade_ziel_kwh
name: "Balkonkraftwerk: Lade-Ziel"
unit_of_measurement: "kWh"
state: |
{% set stunde = now().hour %}
{% if stunde >= 6 and stunde < 12 %}
2.75
{% elif stunde >= 12 and stunde < 18 %}
2.25
{% elif stunde >= 18 and stunde < 23 %}
1.75
{% else %}
1.25
{% endif %}
# Hyper A Lade-Ziel (2/3 des Gesamtziels)
- unique_id: balkonkraftwerk_lade_ziel_hyper_a_kwh
name: "Balkonkraftwerk: Lade-Ziel Hyper A"
unit_of_measurement: "kWh"
state: |
{% set ziel = states('sensor.balkonkraftwerk_lade_ziel') | float(0) %}
{{ ((ziel * 2/3) | round(2)) }}
# Hyper B Lade-Ziel (1/3 des Gesamtziels)
- unique_id: balkonkraftwerk_lade_ziel_hyper_b_kwh
name: "Balkonkraftwerk: Lade-Ziel Hyper B"
unit_of_measurement: "kWh"
state: |
{% set ziel = states('sensor.balkonkraftwerk_lade_ziel') | float(0) %}
{{ ((ziel * 1/3) | round(2)) }}
# ─────────────────────────────────────────────────────────────────────
# TEMPLATE SENSOR: PV-Anteil A (für alle Berechnungen)
# = (PV_Rest - 1 kWh) × (2/3) × 75%
# ─────────────────────────────────────────────────────────────────────
- unique_id: bkw_pv_anteil_a_template
name: "BKW: PV-Anteil A (Template)"
unit_of_measurement: "kWh"
icon: mdi:solar-panel
availability: >
{{ states('sensor.solcast_pv_forecast_prognose_verbleibende_leistung_heute') not in ['unknown', 'unavailable', 'none', ''] }}
state: >
{% set pv_rest = states('sensor.solcast_pv_forecast_prognose_verbleibende_leistung_heute') | float(0) %}
{% set pv_a = (pv_rest - 1) * (2/3) * 0.75 | float(0) %}
{{ [pv_a, 0] | max | round(3) }}
# ─────────────────────────────────────────────────────────────────────
# TEMPLATE SENSOR: PV-Anteil B (für alle Berechnungen)
# = (PV_Rest - 1 kWh) × (1/3) × 75%
# ─────────────────────────────────────────────────────────────────────
- unique_id: bkw_pv_anteil_b_template
name: "BKW: PV-Anteil B (Template)"
unit_of_measurement: "kWh"
icon: mdi:solar-panel
availability: >
{{ states('sensor.solcast_pv_forecast_prognose_verbleibende_leistung_heute') not in ['unknown', 'unavailable', 'none', ''] }}
state: >
{% set pv_rest = states('sensor.solcast_pv_forecast_prognose_verbleibende_leistung_heute') | float(0) %}
{% set pv_a = (pv_rest - 1) * (1/3) * 0.75 %}
{{ [pv_a, 0] | max | round(3) }}
# ─────────────────────────────────────────────────────────────────
# 1.5: DETAILLIERTER STATUS (Option 3 - Kombination mit Werten)
# ─────────────────────────────────────────────────────────────────
- unique_id: balkonkraftwerk_status_detailed
name: "Balkonkraftwerk: Detaillierter Status"
state: |
{% set state = states('input_select.balkonkraftwerk_current_state') %}
{% set forecast = states('sensor.forecast_5_tage_durchschnitt') | float(0) %}
{% set verfuegbar = states('sensor.zendure_manager_available_kwh') | float(0) %}
{% set ziel = states('sensor.balkonkraftwerk_lade_ziel') | float(0) %}
{% set verfueg_gesamt = states("sensor.zendure_manager_available_kwh") | float(0) %}
{% set preis = states('sensor.epex_spot_data_market_price') | float(0) %}
{% set min_preis = states('sensor.epex_spot_data_lowest_price') | float(0) %}
{% if state == 'INIT' %}
🔄 Init | Forecast 5Days: {{ forecast|round(1) }} kWh | Ziel: {{ ziel|round(2) }} kWh
{% elif state == 'AUTARKIE' %}
☀️ AUTARKIE (0W Netz) | Forecast: {{ forecast|round(1) }} kWh | Ziel: {{ ziel|round(2) }} kWh
{% elif state == 'LADE_MODUS' %}
🔌 LADEN | Preis: {{ preis|round(3) }}€ | Min: {{ min_preis|round(3) }}€ | Ziel: {{ ziel|round(2) }} kWh
{% elif state == 'KALIBRIERUNG' %}
🔧 KALIBRIERUNG | Preis: {{ preis|round(3) }}€ | Ziel: 100% SoC
{% elif state == 'SMART_CHARGING' %}
⚡ SMART_CHARGING ( Preis: {{ preis|round(3) }}€ | Forecast: {{ forecast|round(1) }} kWh
{% elif state == 'WARTEN' %}
⏱️ WARTEN (zu teuer!) | Preis: {{ preis|round(3) }}€ > {{ (min_preis*1.20)|round(3) }}€ | Minuten: {{ states('sensor.balkonkraftwerk_minuten_im_warten') }}
{% else %}
❓ {{ state }}
{% endif %}
# ─────────────────────────────────────────────────────────────────
# 1.4: KALIBRIERUNGS-STATUS-DETAIL (Welcher Hyper lädt gerade?)
# ─────────────────────────────────────────────────────────────────
# Kalibrierungs-Status A
- unique_id: balkonkraftwerk_kalibrierungs_status_a
name: "Balkonkraftwerk: Kalibrierungs-Status Hyper A"
state: |
{% set state = states('input_select.balkonkraftwerk_current_state') %}
{% set days_a = states('sensor.hyper_a_anzahl_tage_seit_100') | float(0) %}
{% set soc_a = states('sensor.hyper_2000_electric_level') | float(0) %}
{% set manager = states('select.zendure_manager_operation') %}
{% if state == 'KALIBRIERUNG' and manager == 'off' %}
🔧 LADEN - Hyper A: {{ soc_a | round(0) }}% → 100% | Tage seit 100%: {{ days_a | round(0) }}
{% elif state == 'KALIBRIERUNG' and manager == 'smart' %}
☀️ PV-MODUS - Hyper A: {{ soc_a | round(0) }}% → 100% (PV reicht!) | Tage seit 100%: {{ days_a | round(0) }}
{% else %}
-
{% endif %}
# Kalibrierungs-Status B
- unique_id: balkonkraftwerk_kalibrierungs_status_b
name: "Balkonkraftwerk: Kalibrierungs-Status Hyper B"
state: |
{% set state = states('input_select.balkonkraftwerk_current_state') %}
{% set days_b = states('sensor.hyper_b_anzahl_tage_seit_100') | float(0) %}
{% set soc_b = states('sensor.hyper_2000_b_electric_level') | float(0) %}
{% set manager = states('select.zendure_manager_operation') %}
{% set soc_a = states('sensor.hyper_2000_electric_level') | float(0) %}
{% if state == 'KALIBRIERUNG' and manager == 'off' and soc_a >= 99.5 %}
🔧 LADEN - Hyper B: {{ soc_b | round(0) }}% → 100% | Tage seit 100%: {{ days_b | round(0) }}
{% elif state == 'KALIBRIERUNG' and manager == 'smart' and soc_a >= 99.5 %}
☀️ PV-MODUS - Hyper B: {{ soc_b | round(0) }}% → 100% (PV reicht!) | Tage seit 100%: {{ days_b | round(0) }}
{% else %}
-
{% endif %}
# ═══════════════════════════════════════════════════════════════════════════
# NEUE LADE-BEDARF SENSOREN (SMART mit PV-Abzug & Dynamisches Ziel)
# ═══════════════════════════════════════════════════════════════════════════
# Ersetzt die 4 alten STUFE 2 & 3 Sensoren!
# Neu: Nur EINE Logik, dynamisches Lade-Ziel, PV-Abzug, SoC-aware
# ─────────────────────────────────────────────────────────────────────
# HYPER A: SMART LADE-BEDARF (mit dynamischem Ziel & PV-Abzug)
# ─────────────────────────────────────────────────────────────────────
- unique_id: balkonkraftwerk_lade_bedarf_a_smart
name: "Lade-Bedarf A SMART (-PV & -Ziel)"
unit_of_measurement: "kWh"
icon: mdi:battery-charging
state: |
{% set ziel_a = states('sensor.balkonkraftwerk_lade_ziel_hyper_a') | float(0) %}
{% set verfueg_a = states('sensor.hyper_2000_available_kwh') | float(0) %}
{% set pv_a = states('sensor.bkw_pv_anteil_a_template') | float(0) %}
{# FINAL: Was muss noch geladen werden? #}
{# Lade-Bedarf = Ziel - Verfügbar - PV (aber min 0!) #}
{% set lade_bedarf = [ziel_a - verfueg_a - pv_a, 0] | max %}
{{ lade_bedarf | round(3) }}
# ─────────────────────────────────────────────────────────────────────
# HYPER B: SMART LADE-BEDARF (KORREKT: Ziel - Verfügbar - PV)
# ─────────────────────────────────────────────────────────────────────
- unique_id: balkonkraftwerk_lade_bedarf_b_smart
name: "Lade-Bedarf B SMART (-PV & -Ziel)"
unit_of_measurement: "kWh"
icon: mdi:battery-charging
state: |
{% set ziel_b = states('sensor.balkonkraftwerk_lade_ziel_hyper_b') | float(0) %}
{% set verfueg_b = states('sensor.hyper_2000_b_available_kwh') | float(0) %}
{% set pv_b = states('sensor.bkw_pv_anteil_b_template') | float(0) %}
{# FINAL: Was muss noch geladen werden? #}
{# Lade-Bedarf = Ziel - Verfügbar - PV (aber min 0!) #}
{% set lade_bedarf = [ziel_b - verfueg_b - pv_b, 0] | max %}
{{ lade_bedarf | round(3) }}
# ─────────────────────────────────────────────────────────────────────
# INFO SENSOREN (für Dashboard/Debugging)
# ─────────────────────────────────────────────────────────────────────
- unique_id: balkonkraftwerk_lade_bedarf_a_info
name: "Lade-Bedarf A Info"
state: |
{% set soc_a = states('sensor.hyper_2000_electric_level') | float(0) %}
{% set ziel_a = states('sensor.balkonkraftwerk_lade_ziel_hyper_a') | float(0) %}
{% set verfueg_a = states('sensor.hyper_2000_available_kwh') | float(0) %}
{% set pv_a = states('sensor.bkw_pv_anteil_a_template') | float(0) %}
{% set lade_bedarf = [ziel_a - verfueg_a - pv_a, 0] | max %}
SoC: {{ soc_a | round(1) }}% | Ziel: {{ ziel_a | round(2) }}kWh | Verfügbar: {{ verfueg_a | round(2) }}kWh | PV: {{ pv_a | round(2) }}kWh | Bedarf: {{ lade_bedarf | round(2) }}kWh
- unique_id: balkonkraftwerk_lade_bedarf_b_info
name: "Lade-Bedarf B Info"
state: |
{% set soc_b = states('sensor.hyper_2000_b_electric_level') | float(0) %}
{% set ziel_b = states('sensor.balkonkraftwerk_lade_ziel_hyper_b') | float(0) %}
{% set verfueg_b = states('sensor.hyper_2000_b_available_kwh') | float(0) %}
{% set pv_b = states('sensor.bkw_pv_anteil_b_template') | float(0) %}
{% set lade_bedarf = [ziel_b - verfueg_b - pv_b, 0] | max %}
SoC: {{ soc_b | round(1) }}% | Ziel: {{ ziel_b | round(2) }}kWh | Verfügbar: {{ verfueg_b | round(2) }}kWh | PV: {{ pv_b | round(2) }}kWh | Bedarf: {{ lade_bedarf | round(2) }}kWh
# Netz-Bedarfe STUFE 2 (intelligent)
- unique_id: balkonkraftwerk_netz_bedarf_a_stufe2
name: "Netz-Bedarf A Stufe2 (-PV)"
unit_of_measurement: "kWh"
state: |
{% set days_a = states('sensor.hyper_a_anzahl_tage_seit_100') | float(0) %}
{% set soc_a = states('sensor.hyper_2000_electric_level') | float(0) %}
{% set min_soc_a = states('number.hyper_2000_min_soc') | float(5) %}
{% set max_soc_a = states('number.hyper_2000_soc_set') | float(100) %}
{% set pv_rest = states('sensor.solcast_pv_forecast_prognose_verbleibende_leistung_heute') | float(0) %}
{# WICHTIG: Nur wenn days_a >= 10 (Kalibrierung nötig!) #}
{% if days_a >= 10 and days_a < 20 %}
{# Verfügbare Kapazität (MaxSoC - MinSoC) #}
{% set verfueg_a = (max_soc_a - min_soc_a) / 100 * 2.88 %}
{# Aktuell genutzt (SoC - MinSoC) #}
{% set aktuell_a = (soc_a - min_soc_a) / 100 * 2.88 %}
{# Lade-Bedarf bis MaxSoC #}
{% set lade_bedarf_a = verfueg_a - aktuell_a %}
{# PV-Anteil für A (intelligente Aufteilung) #}
{% if days_a < 10 %}
{% set pv_a = (pv_rest - 1) * (1/2) * 0.75 %}
{% else %}
{% set pv_a = states('sensor.bkw_pv_anteil_a_template') | float(0) %}
{% endif %}
{# Netz-Bedarf = MAX(0, lade_bedarf - pv) #}
{{ ([0, lade_bedarf_a - pv_a] | max) | round(3) }}
{% else %}
{# Keine Kalibrierung nötig (days_a < 10) #}
0.0
{% endif %}
- unique_id: balkonkraftwerk_netz_bedarf_b_stufe2
name: "Netz-Bedarf B Stufe2 (-PV)"
unit_of_measurement: "kWh"
state: |
{% set days_b = states('sensor.hyper_b_anzahl_tage_seit_100') | float(0) %}
{% set days_a = states('sensor.hyper_a_anzahl_tage_seit_100') | float(0) %}
{% set soc_b = states('sensor.hyper_2000_b_electric_level') | float(0) %}
{% set min_soc_b = states('number.hyper_2000_b_min_soc') | float(5) %}
{% set max_soc_b = states('number.hyper_2000_b_soc_set') | float(100) %}
{% set pv_rest = states('sensor.solcast_pv_forecast_prognose_verbleibende_leistung_heute') | float(0) %}
{# WICHTIG: Nur wenn days_b >= 10 (Kalibrierung nötig!) #}
{% if days_b >= 10 and days_b < 20 %}
{# Verfügbare Kapazität (MaxSoC - MinSoC) #}
{% set verfueg_b = (max_soc_b - min_soc_b) / 100 * 1.92 %}
{# Aktuell genutzt (SoC - MinSoC) #}
{% set aktuell_b = (soc_b - min_soc_b) / 100 * 1.92 %}
{# Lade-Bedarf bis MaxSoC #}
{% set lade_bedarf_b = verfueg_b - aktuell_b %}
{# PV-Anteil für B (intelligente Aufteilung) #}
{% if days_a < 10 %}
{# A < 10: B bekommt 1/2 × 75% (A gibt was ab!) #}
{% set pv_b = (pv_rest - 1) * (1/2) * 0.75 %}
{% else %}
{# A >= 10: B bekommt 1/3 × 75% (normal) #}
{% set pv_b = states('sensor.bkw_pv_anteil_b_template') | float(0) %}
{% endif %}
{# WICHTIG: Wenn rest_pv >= lade_bedarf → 0W AC nötig! #}
{% if pv_rest >= lade_bedarf_b %}
{# PV reicht aus - kein Netz! #}
0.0
{% else %}
{# PV reicht nicht - Netz = lade_bedarf - pv_anteil #}
{{ ([0, lade_bedarf_b - pv_b] | max) | round(3) }}
{% endif %}
{% else %}
{# Keine Kalibrierung nötig (days_b < 10) #}
0.0
{% endif %}
# Netz-Bedarfe STUFE 3 (stumpf, keine PV-Abzüge)
- unique_id: balkonkraftwerk_netz_bedarf_a_stufe3
name: "Netz-Bedarf A Stufe3 (hard)"
unit_of_measurement: "kWh"
state: |
{% set days_a = states('sensor.hyper_a_anzahl_tage_seit_100') | float(0) %}
{% set soc_a = states('sensor.hyper_2000_electric_level') | float(0) %}
{# WICHTIG: Nur wenn days_a >= 20 (Stufe 3 / HARD) #}
{% if days_a >= 20 %}
{# Lade-Bedarf bis 100% (STUMPF!) #}
{% set lade_bedarf_a_hard = (100 - soc_a) / 100 * 2.88 %}
{{ lade_bedarf_a_hard | round(3) }}
{% else %}
{# Keine STUFE 3 nötig (days_a < 20) #}
0.0
{% endif %}
- unique_id: balkonkraftwerk_netz_bedarf_b_stufe3
name: "Netz-Bedarf B Stufe3 (hard)"
unit_of_measurement: "kWh"
state: |
{% set days_b = states('sensor.hyper_b_anzahl_tage_seit_100') | float(0) %}
{% set soc_b = states('sensor.hyper_2000_b_electric_level') | float(0) %}
{# WICHTIG: Nur wenn days_b >= 20 (Stufe 3 / HARD) #}
{% if days_b >= 20 %}
{# Lade-Bedarf bis 100% (STUMPF!) #}
{% set lade_bedarf_b_hard = (100 - soc_b) / 100 * 1.92 %}
{{ lade_bedarf_b_hard | round(3) }}
{% else %}
{# Keine STUFE 3 nötig (days_b < 20) #}
0.0
{% endif %}
# ─────────────────────────────────────────────────────────────────
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 2: INPUT-HELPER (State-Tracking, Zähler, Daten)
# ═══════════════════════════════════════════════════════════════════════════
input_select:
balkonkraftwerk_current_state:
name: "Balkonkraftwerk: Aktueller State"
options:
- INIT
- AUTARKIE
- LADE_MODUS
- KALIBRIERUNG
- SMART_CHARGING
- LADE_NOTFALL
initial: INIT
icon: mdi:state-machine
input_boolean:
# Kalibrierung Hyper A aktiv?
balkonkraftwerk_kalibrierung_a_active:
name: "Balkonkraftwerk: Kalibrierung Hyper A aktiv"
# Kalibrierung Hyper B aktiv?
balkonkraftwerk_kalibrierung_b_active:
name: "Balkonkraftwerk: Kalibrierung Hyper B aktiv"
z0_smart_soc_bias_active:
name: "Z0 SMART B Bias aktiv"
smart_charging_enabled:
name: "SMART_CHARGING Aktiviert"
input_text:
balkonkraftwerk_last_automation_debug:
name: "BKW: Letzte Automation Debug"
icon: mdi:history
max: 255
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 3: AUTOMATIONEN
# ═══════════════════════════════════════════════════════════════════════════
# 8-10 Automationen: Jede Schicht eine Auto!
automation:
# ───────────────────────────────────────────────────────────────────────
# AUTO 0: INITIALIZATION / TRIGGER-ROUTER
# ───────────────────────────────────────────────────────────────────────
# Triggert alle 15 Min + bei Sensor-Änderungen
# Setzt State = INIT damit andere Autos triggern
- id: balkonkraftwerk_auto_0_init
alias: "BK-AUTO-0: Initialization"
description: "Triggert beim Start + alle 15 Min, setzt INIT State mit Delays"
mode: single
trigger:
# ⚠️ BEIM START SOFORT triggern! (Wichtig!)
- trigger: homeassistant
event: start
id: startup
# Alle 15 Min regelmäßig prüfen (EPEX Update-Intervall)
# - trigger: time_pattern
# minutes: "/15"
# id: time_trigger
- trigger: state
entity_id: input_select.balkonkraftwerk_current_state
id: state_changed
# ⚠️ WICHTIG: Wenn sich die Tage-Sensoren ändern → sofort prüfen!
# (Damit INIT Status sofort neu evaluiert wird!)
- trigger: state
entity_id:
- sensor.hyper_a_anzahl_tage_seit_100
- sensor.hyper_b_anzahl_tage_seit_100
id: days_changed
- trigger: state
entity_id: select.zendure_manager_operation
id: manager_changed
condition:
# ⚠️ WICHTIG: Nur ausführen wenn HA vollständig hochgefahren ist (1 Min nach Restart!)
- condition: template
value_template: |
{% set last_restart = states('homeassistant.last_restart') %}
{% if last_restart and last_restart not in ['unknown', 'unavailable', 'none', ''] %}
{{ now() > (as_datetime(last_restart) + timedelta(minutes=1)) }}
{% else %}
True
{% endif %}
action:
# ⚠️ INTELLIGENTE LOGIK: Nur auf INIT setzen wenn nötig!
- if:
- condition: template
value_template: |
{% set state = states('input_select.balkonkraftwerk_current_state') %}
{{ state not in ['INIT', 'KALIBRIERUNG', 'LADE_MODUS', 'SMART_CHARGING'] }}
then:
# Setze State auf INIT (nur wenn nicht schon Hochprioritäts-State!)
- service: input_select.select_option
target:
entity_id: input_select.balkonkraftwerk_current_state
data:
option: INIT
# ⚠️ Stoppe Kalibrierung (beide Hyper!)
- service: input_boolean.turn_off
target:
entity_id:
- input_boolean.smart_charging_enabled
# ⚠️ Stoppe Smart Charging (beide Hyper!)
- service: input_boolean.turn_off
target:
entity_id:
- input_boolean.balkonkraftwerk_kalibrierung_a_active
- input_boolean.balkonkraftwerk_kalibrierung_b_active
- service: input_text.set_value
target:
entity_id: input_text.balkonkraftwerk_last_automation_debug
data:
value: "AUTO 0 | Kalibirerung Boolean aus"
# ⚠️ ZUSATZ: Wenn STATE == KALIBRIERUNG aber Tage < 10 → zu INIT!
# (Falls sich die Tage-Sensoren geändert haben und Kalibrierung nicht mehr nötig!)
- if:
- condition: trigger
id: days_changed
- condition: template
value_template: "{{ states('input_select.balkonkraftwerk_current_state') == 'KALIBRIERUNG' }}"
then:
- variables:
days_a: "{{ states('sensor.hyper_a_anzahl_tage_seit_100') | int(0) }}"
days_b: "{{ states('sensor.hyper_b_anzahl_tage_seit_100') | int(0) }}"
# Wenn BEIDE < 10: Kalibrierung nicht nötig!
- if: "{{ states('sensor.hyper_a_anzahl_tage_seit_100') | int(0) < 10 and states('sensor.hyper_b_anzahl_tage_seit_100') | int(0) < 10 }}"
then:
# ⚠️ STOP Laden sofort!
- service: number.set_value
target:
entity_id: number.hyper_2000_input_limit
data:
value: 0
- delay: "00:00:01"
- service: number.set_value
target:
entity_id: number.hyper_2000_b_input_limit
data:
value: 0
- delay: "00:00:01"
# ⚠️ AUSSCHALTEN Booleans
- service: input_boolean.turn_off
target:
entity_id:
- input_boolean.balkonkraftwerk_kalibrierung_a_active
- input_boolean.balkonkraftwerk_kalibrierung_b_active
- delay: "00:00:01"
# STATE zu INIT
- service: input_select.select_option
target:
entity_id: input_select.balkonkraftwerk_current_state
data:
option: INIT
- service: notify.mobile_app_iphone_von_b
data:
title: "✅ Kalibrierung beendet"
message: |
Tage A: {{ days_a | round(0) }}, Tage B: {{ days_b | round(0) }}
Beide < 10 Tage → INIT
- service: input_text.set_value
target:
entity_id: input_text.balkonkraftwerk_last_automation_debug
data:
value: "AUTO 0 | Kalibirerung beendet"
# ═══════════════════════════════════════════════════════════════
# INIT-Base Ausführung: Manager intelligent
# ═══════════════════════════════════════════════════════════════
# Nur wenn STATE == INIT → Setze Warte-Modus
- if: "{{ states('input_select.balkonkraftwerk_current_state') == 'INIT' }}"
then:
# ⚠️ DELAY: 2 Sekunden MQTT Synchronisation!
- delay:
milliseconds: 1000
# ⚠️ MANAGER: "smart" AM ENDE setzen (mit Priorität!)
# Das wird als letztes gesetzt, damit andere AUTOs nicht überschreiben!
- service: select.select_option
target:
entity_id: select.zendure_manager_operation
data:
option: "smart"
- delay:
milliseconds: 500
- service: select.select_option
target:
entity_id: select.hyper_2000_ac_mode
data:
option: "input"
- delay:
milliseconds: 500
# HYPER B: Warten (input_limit=0, AC mode "input")
- service: number.set_value
target:
entity_id: number.hyper_2000_b_input_limit
data:
value: 0
- delay:
milliseconds: 500
- service: select.select_option
target:
entity_id: select.hyper_2000_b_ac_mode
data:
option: "input"
- delay:
milliseconds: 500
# GRID REVERSE: "allow" (Netz-Einspeisung erlaubt!)
- service: select.select_option
target:
entity_id: select.hyper_2000_grid_reverse
data:
option: "allow"
- delay:
milliseconds: 500
# HYPER A: Warten (input_limit=0, AC mode "input")
- service: number.set_value
target:
entity_id: number.hyper_2000_input_limit
data:
value: 0
- service: input_text.set_value
target:
entity_id: input_text.balkonkraftwerk_last_automation_debug
data:
value: "AUTO 0 | Smart gesetzt"
# ───────────────────────────────────────────────────────────────────────
# ───────────────────────────────────────────────────────────────────────
# AUTO 1: AUTARKIE (kombiniert 1, 1b, 11)
# ───────────────────────────────────────────────────────────────────────
# AUTARKIE wenn:
# - Forecast >= 2.5 kWh (gutes Wetter) ODER
# - Verfügbar >= 1.5 kWh (Akku ok) ODER
# - Heute-Rest >= 3 kWh (viel Solar heute noch)
- id: balkonkraftwerk_auto_1_combined_autarkie
alias: "BK-AUTO-1: AUTARKIE (Forecast OR Verfügbar OR Heute-Rest)"
description: "AUTARKIE wenn: Forecast>=2.5 OR Verfügbar>=1.5 OR Heute-Rest>=3"
mode: restart
trigger:
# Bei Verfügbar Update
- trigger: state
entity_id: sensor.zendure_manager_available_kwh
id: verfuegbar_trigger
# Bei Forecast Update
- trigger: state
entity_id: sensor.forecast_5_tage_durchschnitt
id: forecast_trigger
# Bei Heute-Rest Update
- trigger: state
entity_id: sensor.solcast_pv_forecast_prognose_verbleibende_leistung_heute
id: heute_rest_trigger
# Bei State Wechsel zu INIT
- trigger: state
entity_id: input_select.balkonkraftwerk_current_state
to: INIT
condition:
# ⚠️ WICHTIG: Nur ausführen wenn HA vollständig hochgefahren ist (1 Min nach Restart!)
- condition: template
value_template: |
{% set last_restart = states('homeassistant.last_restart') %}
{% if last_restart and last_restart not in ['unknown', 'unavailable', 'none', ''] %}
{{ now() > (as_datetime(last_restart) + timedelta(minutes=1)) }}
{% else %}
True
{% endif %}
# ⚠️ PRIORITÄTS-CHECK: AUTARKIE hat NIEDRIGSTE Priorität!
# Kann NICHT überschreiben: KALIBRIERUNG, LADE_MODUS, SMART_CHARGING
- condition: template
value_template: |
{% set current_state = states('input_select.balkonkraftwerk_current_state') %}
{{ current_state not in ['KALIBRIERUNG', 'LADE_MODUS', 'SMART_CHARGING'] }}
# HAUPTBEDINGUNG: OR-Logik für AUTARKIE
# Wenn EINE Bedingung erfüllt → AUTARKIE
- condition: template
value_template: |
{% set forecast = states('sensor.forecast_5_tage_durchschnitt') | float(0) %}
{% set verfuegbar = states('sensor.zendure_manager_available_kwh') | float(0) %}
{% set heute_rest = states('sensor.solcast_pv_forecast_prognose_verbleibende_leistung_heute') | float(0) %}
{{ forecast >= 2.5 or verfuegbar >= 1.5 or heute_rest >= 3 }}
# ⚠️ STOP: Nicht triggern wenn schon AUTARKIE!
- condition: template
value_template: "{{ states('input_select.balkonkraftwerk_current_state') != 'AUTARKIE' }}"
action:
# State = AUTARKIE
- service: input_select.select_option
target:
entity_id: input_select.balkonkraftwerk_current_state
data:
option: AUTARKIE
# Manager auf "smart" (0W Netz halten)
- service: select.select_option
target:
entity_id: select.zendure_manager_operation
data:
option: "smart"
- service: input_text.set_value
target:
entity_id: input_text.balkonkraftwerk_last_automation_debug
data:
value: "AUTO 1 | AUTARKIE (Forecast>=2.5 OR Verfügbar>=1.5 OR Heute-Rest>=3)"
# Notification
- service: notify.mobile_app_iphone_von_b
data:
title: "☀️ AUTARKIE aktiv"
message: |
Forecast (5d): {{ states('sensor.forecast_5_tage_durchschnitt') }} kWh
Verfügbar: {{ states('sensor.zendure_manager_available_kwh') }} kWh
Heute-Rest: {{ states('sensor.solcast_pv_forecast_prognose_verbleibende_leistung_heute') }} kWh
Manager → Smart
# ───────────────────────────────────────────────────────────────────────
# ───────────────────────────────────────────────────────────────────────
# AUTO 5: NOT-LADEN (Emergency charging)
# ───────────────────────────────────────────────────────────────────────
# Wenn Akku SEHR leer (A & B < 0.2 kWh) → 15 Min laden mit Kalibrierungs-Schwelle
- id: balkonkraftwerk_auto_5_notfall_laden
alias: "BK-AUTO-5: NOT-LADEN (A&B<0.2kWh)"
description: "Notfall-Laden: 15 Min bei Hyper A&B unter 0.2 kWh Akku"
mode: single
trigger:
# Bei Verfügbar Update
- trigger: state
entity_id: sensor.zendure_manager_available_kwh
id: verfuegbar_trigger
# Bei Forecast Update
- trigger: state
entity_id: sensor.forecast_5_tage_durchschnitt
id: forecast_trigger
# Bei State INIT
- trigger: state
entity_id: input_select.balkonkraftwerk_current_state
to: INIT
condition:
# ⚠️ WICHTIG: Nur ausführen wenn HA vollständig hochgefahren ist (1 Min nach Restart!)
- condition: template
value_template: |
{% set last_restart = states('homeassistant.last_restart') %}
{% if last_restart and last_restart not in ['unknown', 'unavailable', 'none', ''] %}
{{ now() > (as_datetime(last_restart) + timedelta(minutes=1)) }}
{% else %}
True
{% endif %}
# ⚠️ PRIORITÄTS-CHECK: NOTLADEN hat HÖCHSTE Priorität (außer KALIBRIERUNG)!
- condition: template
value_template: |
{% set current_state = states('input_select.balkonkraftwerk_current_state') %}
{{ current_state not in ['KALIBRIERUNG'] }}
# ⚠️ UND: Verfügbar < 0.4 kWh (sonst Oszillation!)
- condition: template
value_template: |
{% set hyper_a = states('sensor.hyper_2000_available_kwh') | float(999) %}
{% set hyper_b = states('sensor.hyper_2000_b_available_kwh') | float(999) %}
{{ hyper_a < 0.2 and hyper_b < 0.2 }}
# Forecast < 2.5 (wenig PV erwartet)
- condition: template
value_template: "{{ states('sensor.forecast_5_tage_durchschnitt') | float(0) < 2.5 }}"
# Bedarf > 0.2
- condition: template
value_template: |
{% set bedarf_a = states('sensor.lade_bedarf_a_smart_pv_ziel') | float(0) %}
{% set bedarf_b = states('sensor.lade_bedarf_b_smart_pv_ziel') | float(0) %}
{{ bedarf_a > 0.2 or bedarf_b > 0.2 }}
# Sensoren ready
- condition: template
value_template: |
{{ states('sensor.bkw_preis_schwelle_smartcharge_wirkungsgrad') != 'unknown' }}
# Preis <= Kalibrierungs-Schwelle
- condition: template
value_template: |
{% set current = states('sensor.epex_spot_data_market_price') | float(999) %}
{% set threshold = states('sensor.bkw_preis_schwelle_smartcharge_wirkungsgrad') | float(999) %}
{{ current <= threshold }}
# ⚠️ STOP: Nicht triggern wenn schon LADE_NOTFALL!
- condition: template
value_template: "{{ states('input_select.balkonkraftwerk_current_state') != 'LADE_NOTFALL' }}"
action:
# ═══════════════════════════════════════════════════════════════════════
# SCHRITT 1: NOT-LADEN starten
# ═══════════════════════════════════════════════════════════════════════
- service: input_select.select_option
target:
entity_id: input_select.balkonkraftwerk_current_state
data:
option: LADE_NOTFALL
- service: select.select_option
target:
entity_id: select.zendure_manager_operation
data:
option: "off"
- delay: "00:00:02"
- service: select.select_option
target:
entity_id:
- select.hyper_2000_ac_mode
- select.hyper_2000_b_ac_mode
data:
option: "input"
- delay: "00:00:02"
- service: number.set_value
target:
entity_id:
- number.hyper_2000_input_limit
- number.hyper_2000_b_input_limit
data:
value: 800
- service: input_text.set_value
target:
entity_id: input_text.balkonkraftwerk_last_automation_debug
data:
value: "AUTO 5 | NOT-LADEN gestartet (Akku < 0.4 kWh)"
- service: notify.mobile_app_iphone_von_b
data:
title: "🚨 NOT-LADEN aktiv"
message: |
Akku KRITISCH: {{ states('sensor.zendure_manager_available_kwh') }} kWh
Lade 15 Minuten:
800W × 15min × 2 Geräte = +0.4 kWh
Manager → off & Input A/B → 800W
# ═══════════════════════════════════════════════════════════════════════
# SCHRITT 2: 15 Minuten Delay (= 0.4 kWh nachladen)
# ═══════════════════════════════════════════════════════════════════════
- delay: "00:15:00"
# ═══════════════════════════════════════════════════════════════════════
# SCHRITT 3: NACH 15 Min - STOP laden
# ═══════════════════════════════════════════════════════════════════════
- service: number.set_value
target:
entity_id:
- number.hyper_2000_input_limit
- number.hyper_2000_b_input_limit
data:
value: 0
- delay: "00:00:02"
- service: select.select_option
target:
entity_id: select.zendure_manager_operation
data:
option: "smart"
- delay: "00:00:02"
- service: input_select.select_option
target:
entity_id: input_select.balkonkraftwerk_current_state
data:
option: INIT
- service: input_text.set_value
target:
entity_id: input_text.balkonkraftwerk_last_automation_debug
data:
value: "AUTO 5 | NOT-LADEN beendet (15min delay)"
- service: notify.mobile_app_iphone_von_b
data:
title: "✅ NOT-LADEN beendet"
message: |
Nach 15 Minuten:
+0.4 kWh nachgeladen
Input A/B → 0W
Manager → smart
State → INIT
- service: input_text.set_value
target:
entity_id: input_text.balkonkraftwerk_last_automation_debug
data:
value: "AUTO 5 | NOT-LADEN beendet (15min delay)"
# ───────────────────────────────────────────────────────────────────────
# AUTO 2: SCHICHT 2 - SMART_CHARGING
# ───────────────────────────────────────────────────────────────────────
# Wenn Forecast < 2.5 + Großverbraucher > 25W + Preis günstig
- id: balkonkraftwerk_auto_2_smart_charging
alias: "BK-AUTO-2: Smart_Charging"
description: "Manager smart_charging wenn Großverbraucher läuft + Preis ok"
mode: restart
trigger:
- trigger: numeric_state
entity_id:
- sensor.waschmaschine_leistung
- sensor.kaffeeecke_leistung
- sensor.kuhlschrank_und_geschirrspuler_leistung
- sensor.tv_leistung
- sensor.wasserkocher_toaster_leistung
above: 25
for: "00:01:00"
- trigger: numeric_state
entity_id:
- sensor.hyper_2000_pack_input_power
above: 250
for: "00:0:30"
condition:
# ⚠️ WICHTIG: Nur ausführen wenn HA vollständig hochgefahren ist (2 Min nach Restart!)
- condition: template
value_template: |
{% set last_restart = states('homeassistant.last_restart') %}
{% if last_restart and last_restart not in ['unknown', 'unavailable', 'none', ''] %}
{{ now() > (as_datetime(last_restart) + timedelta(minutes=2)) }}
{% else %}
True
{% endif %}
# ⚠️ PRIORITÄTS-CHECK: Nicht wenn höhere Priorität aktiv!
- condition: template
value_template: |
{% set current_state = states('input_select.balkonkraftwerk_current_state') %}
{# SMART_CHARGING kann NICHT überschreiben: #}
{# - KALIBRIERUNG (Batterie-Health) #}
{# - LADE_MODUS (normales Laden) #}
{{ current_state not in ['KALIBRIERUNG', 'LADE_MODUS'] }}
# Forecast < 2.5 kWh
- condition: template
value_template: "{{ states('sensor.forecast_5_tage_durchschnitt') | float(0) < 2.5 }}"
# verfügbar < 2.25 kWh
- condition: template
value_template: "{{ states('sensor.zendure_manager_available_kwh') | float(0) < 2.25 }}"
# Preis <= sensor.bkw_preis_schwelle_laden_wirkungsgrad (MODERAT - Smart_Charging darf teurer sein als normales Laden!)
- condition: template
value_template: |
{% set current = states('sensor.epex_spot_data_market_price') | float(999) %}
{% set threshold_laden = states('sensor.bkw_preis_schwelle_smartcharge_wirkungsgrad') | float(999) %}
{{ current <= threshold_laden }}
action:
# Prüfe welcher Großverbraucher läuft (> 25W)
- choose:
- conditions:
- condition: template
value_template: |
{{ (states('sensor.waschmaschine_leistung') | float(0) > 25) or
(states('sensor.kaffeeecke_leistung') | float(0) > 25) or
(states('sensor.kuhlschrank_und_geschirrspuler_leistung') | float(0) > 25) or
(states('sensor.tv_leistung') | float(0) > 25) or
(states('sensor.wasserkocher_toaster_leistung') | float(0) > 25) or
(states('sensor.hyper_2000_pack_input_power') | float(0) > 250) }}
sequence:
# Nur EIN State: SMART_CHARGING (nicht SMART_CHARGING_WASCH, _KAFFEE, etc!)
- service: input_select.select_option
target:
entity_id: input_select.balkonkraftwerk_current_state
data:
option: SMART_CHARGING
- service: select.select_option
target:
entity_id: select.zendure_manager_operation
data:
option: "smart_charging"
- service: input_boolean.turn_on
target:
entity_id: input_boolean.smart_charging_enabled
- service: notify.mobile_app_iphone_von_b
data:
title: "⚡ SMART_CHARGING gestartet"
message: |
Großverbraucher aktiv und forecast<2.5
- service: input_text.set_value
target:
entity_id: input_text.balkonkraftwerk_last_automation_debug
data:
value: "AUTO 2 | SmartCharging ON"
# ───────────────────────────────────────────────────────────────────────
# ───────────────────────────────────────────────────────────────────────
# ───────────────────────────────────────────────────────────────────────
# AUTO 2a: SMART_CHARGING STOP - Wenn Bedingungen nicht mehr erfüllt
# ───────────────────────────────────────────────────────────────────────
- alias: 'BK-AUTO-2a: SMART_CHARGING STOP'
description: >-
Stoppt SMART_CHARGING wenn: alle Großverbraucher < 10W für 1 Min ODER Preis zu
teuer ODER Forecast > 2.5
mode: restart
triggers:
- trigger: template
value_template: |
{{ states('sensor.waschmaschine_leistung') | float(0) < 10
and states('sensor.kaffeeecke_leistung') | float(0) < 10
and states('sensor.kuhlschrank_und_geschirrspuler_leistung') | float(0) < 10
and states('sensor.tv_leistung') | float(0) < 10
and states('sensor.wasserkocher_toaster_leistung') | float(0) < 10
and states('sensor.hyper_2000_pack_input_power') | float(0) < 50 }}
for: '00:01:00'
id: verbraucher_low_1min
- trigger: state
entity_id: sensor.epex_spot_data_market_price
id: price_change
- trigger: state
entity_id: sensor.forecast_5_tage_durchschnitt
id: forecast_change
conditions:
- condition: template
value_template: >
{{ states('input_select.balkonkraftwerk_current_state') == 'SMART_CHARGING' }}
actions:
- choose:
# ─────────────────────────────────────────────
# 1. ALLE VERBRAUCHER < 10 W
# ─────────────────────────────────────────────
- conditions:
- condition: trigger
id: verbraucher_low_1min
sequence:
- action: select.select_option
target:
entity_id: select.zendure_manager_operation
data:
option: smart
- action: input_select.select_option
target:
entity_id: input_select.balkonkraftwerk_current_state
data:
option: INIT
- action: notify.mobile_app_iphone_von_b
data:
title: ⏹️ SMART_CHARGING STOP
message: |
Alle Geräte < 10W für 1 Minute!
SMART_CHARGING → INIT
- service: input_text.set_value
target:
entity_id: input_text.balkonkraftwerk_last_automation_debug
data:
value: "AUTO 2a | SmartCharging OFF Device<10W"
# ─────────────────────────────────────────────
# 2. PREIS ZU HOCH
# ─────────────────────────────────────────────
- conditions:
- condition: template
value_template: >
{% if has_value('sensor.epex_spot_data_market_price')
and has_value('sensor.bkw_preis_schwelle_smartcharge_wirkungsgrad') %}
{% set current_price =
states('sensor.epex_spot_data_market_price') | float %}
{% set threshold =
states('sensor.bkw_preis_schwelle_smartcharge_wirkungsgrad') | float %}
{{ current_price > threshold }}
{% else %}
false
{% endif %}
sequence:
- action: select.select_option
target:
entity_id: select.zendure_manager_operation
data:
option: smart
- action: input_select.select_option
target:
entity_id: input_select.balkonkraftwerk_current_state
data:
option: INIT
- action: notify.mobile_app_iphone_von_b
data:
title: ⏹️ SMART_CHARGING STOP (Preis zu teuer!)
message: |
Preis: {{ states('sensor.epex_spot_data_market_price') }} €/kWh
Threshold (Laden): {{ states('sensor.bkw_preis_schwelle_smartcharge_wirkungsgrad') }} €/kWh
→ Zu teuer zum Laden!
- service: input_text.set_value
target:
entity_id: input_text.balkonkraftwerk_last_automation_debug
data:
value: "AUTO 2a | SmartCharging OFF PriceHigh"
# ─────────────────────────────────────────────
# 3. FORECAST > 2.5
# ─────────────────────────────────────────────
- conditions:
- condition: template
value_template: >
{{ states('sensor.forecast_5_tage_durchschnitt') | float(0) > 2.5 }}
sequence:
- action: select.select_option
target:
entity_id: select.zendure_manager_operation
data:
option: smart
- action: input_select.select_option
target:
entity_id: input_select.balkonkraftwerk_current_state
data:
option: INIT
- action: notify.mobile_app_iphone_von_b
data:
title: ⏹️ SMART_CHARGING STOP (Gutes Wetter!)
message: |
Forecast: {{ states('sensor.forecast_5_tage_durchschnitt') }} kWh
> 2.5 kWh
- service: input_text.set_value
target:
entity_id: input_text.balkonkraftwerk_last_automation_debug
data:
value: "AUTO 2a | SmartCharging OFF Forecast>2.5"
# ─────────────────────────────────────────────
# 4. PREIS GÜNSTIG + VERBRAUCHER LOW
# → INIT setzen (Manager wählt dann SMART)
# ─────────────────────────────────────────────
- conditions:
- condition: trigger
id: price_change
- condition: template
value_template: >
{% if has_value('sensor.epex_spot_data_market_price')
and has_value('sensor.bkw_preis_schwelle_smartcharge_wirkungsgrad') %}
{% set current_price =
states('sensor.epex_spot_data_market_price') | float %}
{% set threshold =
states('sensor.bkw_preis_schwelle_smartcharge_wirkungsgrad') | float %}
{{ current_price < threshold }}
{% else %}
false
{% endif %}
- condition: template
value_template: |
{{ states('sensor.waschmaschine_leistung') | float(0) < 10
and states('sensor.kaffeeecke_leistung') | float(0) < 10
and states('sensor.kuhlschrank_und_geschirrspuler_leistung') | float(0) < 10
and states('sensor.tv_leistung') | float(0) < 10
and states('sensor.wasserkocher_toaster_leistung') | float(0) < 10
and states('sensor.hyper_2000_pack_input_power') | float(0) < 50 }}
- condition: state
entity_id: input_boolean.smart_charging_enabled
state: 'on'
sequence:
- action: select.select_option
target:
entity_id: select.zendure_manager_operation
data:
option: smart
- action: input_select.select_option
target:
entity_id: input_select.balkonkraftwerk_current_state
data:
option: INIT # ← HIER! Nicht SMART_CHARGING
- action: notify.mobile_app_iphone_von_b
data:
title: 🔋 SMART_CHARGING TRIGGER (Günstiger Preis!)
message: |
Preis: {{ states('sensor.epex_spot_data_market_price') }} €/kWh
Threshold: {{ states('sensor.bkw_preis_schwelle_smartcharge_wirkungsgrad') }} €/kWh
Alle Verbraucher < 10W
→ INIT gesetzt (Manager wählt SMART)
- service: input_text.set_value
target:
entity_id: input_text.balkonkraftwerk_last_automation_debug
data:
value: "AUTO 2a | INIT triggered PriceLow+DeviceLow"
# AUTO 3: SCHICHT 3 - KALIBRIERUNG (Intelligente PV-Nutzung + Sequenziell)
# ───────────────────────────────────────────────────────────────────────
# Stufe 1 (< 10 Tage): Ignorieren
# Stufe 2 (10-20 Tage): SOFT - intelligente PV-Nutzung, bis MaxSoC (nicht 100%!)
# Stufe 3 (>= 20 Tage): HARD - STUMPF bis 100% (ignoriert PV!)
#
# PV-Intelligenz (Stufe 2 nur!):
# lade_bedarf = (MaxSoC - aktueller_SoC) / 100 × kapazität
# pv_fuer_x = pv_rest_heute × anteil × 0.75
# netz_bedarf = MAX(0, lade_bedarf - pv_fuer_x)
#
# Notfall (Stufe 3):
# lade_bedarf = (100 - aktueller_SoC) / 100 × kapazität (STUMPF!)
# netz_bedarf = lade_bedarf (KEINE PV-Abzüge!)
#
# Sequenziell: Erst A bis MaxSoC/100%, dann B bis MaxSoC/100%
# BIAS: Deaktiviert während Kalibrierung, MaxSoC = 100% (Stufe 3) oder MaxSoC (Stufe 2)
- id: balkonkraftwerk_auto_3_kalibrierung_stufen_correct
alias: "BK-AUTO-3: Kalibrierung - Stufen 2 & 3 (getrennte A & B)"
description: "STUFE 2 (10-20T, mit PV) + STUFE 3 (>=20T, ohne PV) | A & B UNABHÄNGIG | Preis entscheidet WANN"
mode: restart
trigger:
# Oder wenn sich Tage ändern
- trigger: state
entity_id:
- sensor.hyper_a_anzahl_tage_seit_100
- sensor.hyper_b_anzahl_tage_seit_100
id: days_change
# ⚠️ ODER WENN SoC SICH ÄNDERT (für 100% STOP!)
- trigger: state
entity_id:
- sensor.hyper_2000_electric_level
- sensor.hyper_2000_b_electric_level
id: soc_change
# Oder wenn netz_bedarf sich ändert
- trigger: state
entity_id:
- sensor.netz_bedarf_a_stufe2_pv
- sensor.netz_bedarf_a_stufe3_hard
- sensor.netz_bedarf_b_stufe2_pv
- sensor.netz_bedarf_b_stufe3_hard
id: netz_bedarf_change
# Oder wenn Preis sich ändert
- trigger: state
entity_id: sensor.epex_spot_data_market_price
id: price_change
action:
- variables:
# TAGE (MÜSSEN INT sein für Vergleiche!)
days_a: "{{ states('sensor.hyper_a_anzahl_tage_seit_100') | int(0) }}"
days_b: "{{ states('sensor.hyper_b_anzahl_tage_seit_100') | int(0) }}"
# SoC
soc_a: "{{ states('sensor.hyper_2000_electric_level') | float(0) }}"
soc_b: "{{ states('sensor.hyper_2000_b_electric_level') | float(0) }}"
# NETZ-BEDARFE (mit/ohne PV-Abzug je nach Stufe!)
netz_bedarf_a_stufe2: "{{ states('sensor.netz_bedarf_a_stufe2_pv') | float(0) }}"
netz_bedarf_a_stufe3: "{{ states('sensor.netz_bedarf_a_stufe3_hard') | float(0) }}"
netz_bedarf_b_stufe2: "{{ states('sensor.netz_bedarf_b_stufe2_pv') | float(0) }}"
netz_bedarf_b_stufe3: "{{ states('sensor.netz_bedarf_b_stufe3_hard') | float(0) }}"
# PREIS-CHECK (entscheidet WANN geladen wird!)
current_price: "{{ states('sensor.epex_spot_data_market_price') | float(999) }}"
threshold_kalibrierung: "{{ states('sensor.bkw_preis_schwelle_kalibrierung_wirkungsgrad') | float(999) }}"
preis_günstig: "{{ states('sensor.epex_spot_data_market_price') | float(999) <= states('sensor.bkw_preis_schwelle_kalibrierung_wirkungsgrad') | float(999) }}"
# ═════════════════════════════════════════════════════════════════
# HYPER A - VÖLLIG UNABHÄNGIG!
# ═════════════════════════════════════════════════════════════════
# ─── A: START-Phase (kalibrierung_a_active = OFF) ───
- if: "{{ states('input_boolean.balkonkraftwerk_kalibrierung_a_active') == 'off' and states('sensor.hyper_a_anzahl_tage_seit_100') | int(0) >= 10 }}"
then:
- choose:
# A STUFE 2 (10-20 Tage): netz_bedarf_a_stufe2 > 0 + preis_günstig → START
- conditions:
- condition: template
value_template: "{{ states('sensor.hyper_a_anzahl_tage_seit_100') | int(0) >= 10 and states('sensor.hyper_a_anzahl_tage_seit_100') | int(0) < 20 and states('sensor.netz_bedarf_a_stufe2_pv') | float(0) > 0 and (states('sensor.epex_spot_data_market_price') | float(999) <= states('sensor.bkw_preis_schwelle_kalibrierung_wirkungsgrad') | float(999)) and states('sensor.hyper_2000_electric_level') | float(0) < 100 }}"
sequence:
- service: input_boolean.turn_on
target:
entity_id: input_boolean.balkonkraftwerk_kalibrierung_a_active
- service: input_select.select_option
target:
entity_id: input_select.balkonkraftwerk_current_state
data:
option: KALIBRIERUNG
- service: number.set_value
target:
entity_id: number.hyper_2000_soc_set
data:
value: 100
- service: select.select_option
target:
entity_id: select.zendure_manager_operation
data:
option: "off"
- delay: "00:00:15"
- service: select.select_option
target:
entity_id: select.hyper_2000_ac_mode
data:
option: "input"
- service: number.set_value
target:
entity_id: number.hyper_2000_input_limit
data:
value: 800
- service: number.set_value
target:
entity_id: number.hyper_2000_b_input_limit
data:
value: 0
- service: input_text.set_value
target:
entity_id: input_text.balkonkraftwerk_last_automation_debug
data:
value: "AUTO 3 | days_a >= 10 and < 20 and netz_bedarf_a_stufe2 > 0 and preis_günstig and soc_a < 100"
# A STUFE 3 (>=20 Tage): soc_a < 100 + preis_günstig → START
- conditions:
- condition: template
value_template: "{{ states('sensor.hyper_a_anzahl_tage_seit_100') | int(0) >= 20 and states('sensor.hyper_2000_electric_level') | float(0) < 100 and (states('sensor.epex_spot_data_market_price') | float(999) <= states('sensor.bkw_preis_schwelle_kalibrierung_wirkungsgrad') | float(999)) }}"
sequence:
- service: input_boolean.turn_on
target:
entity_id: input_boolean.balkonkraftwerk_kalibrierung_a_active
- service: number.set_value
target:
entity_id: number.hyper_2000_soc_set
data:
value: 100
- service: input_select.select_option
target:
entity_id: input_select.balkonkraftwerk_current_state
data:
option: KALIBRIERUNG
- service: select.select_option
target:
entity_id: select.zendure_manager_operation
data:
option: "off"
- delay: "00:00:15"
- service: select.select_option
target:
entity_id: select.hyper_2000_ac_mode
data:
option: "input"
- service: number.set_value
target:
entity_id: number.hyper_2000_input_limit
data:
value: 800
- service: input_text.set_value
target:
entity_id: input_text.balkonkraftwerk_last_automation_debug
data:
value: "AUTO 3 | days_a >= 20 and soc_a < 100 and preis_günstig"
# ─── A: LAUFEND-Phase (kalibrierung_a_active = ON) ───
- if: "{{ states('input_boolean.balkonkraftwerk_kalibrierung_a_active') == 'on' }}"
then:
- choose:
# A STUFE 2 LAUFEND: netz_bedarf_a_stufe2 > 0 + preis_günstig → 800W
- conditions:
- condition: template
value_template: "{{ states('sensor.hyper_a_anzahl_tage_seit_100') | int(0) >= 10 and states('sensor.hyper_a_anzahl_tage_seit_100') | int(0) < 20 }}"
sequence:
- choose:
# Bedingungen erfüllt: WEITERLADEN
- conditions:
- condition: template
value_template: "{{ states('sensor.netz_bedarf_a_stufe2_pv') | float(0) > 0 and preis_günstig }}"
sequence:
- service: number.set_value
target:
entity_id: number.hyper_2000_input_limit
data:
value: 800
# Bedingungen NICHT erfüllt: STOP
- conditions:
- condition: template
value_template: "{{ states('sensor.netz_bedarf_a_stufe2_pv') | float(0) <= 0 or not preis_günstig }}"
sequence:
- service: number.set_value
target:
entity_id: number.hyper_2000_input_limit
data:
value: 0
- service: number.set_value
target:
entity_id: number.hyper_2000_b_input_limit
data:
value: 0
- service: input_boolean.turn_off
target:
entity_id: input_boolean.balkonkraftwerk_kalibrierung_a_active
# ⚠️ STATE NICHT zu INIT! (Andere AUTOs können jetzt triggern!)
# A STUFE 3 LAUFEND: soc_a < 100 + preis_günstig → 800W
- conditions:
- condition: template
value_template: "{{ states('sensor.hyper_a_anzahl_tage_seit_100') | int(0) >= 20 }}"
sequence:
- choose:
# Bedingungen erfüllt: WEITERLADEN
- conditions:
- condition: template
value_template: "{{ states('sensor.hyper_2000_electric_level') | float(0) < 100 and (states('sensor.epex_spot_data_market_price') | float(999) <= states('sensor.bkw_preis_schwelle_kalibrierung_wirkungsgrad') | float(999)) }}"
sequence:
- service: number.set_value
target:
entity_id: number.hyper_2000_input_limit
data:
value: 800
- service: number.set_value
target:
entity_id: number.hyper_2000_b_input_limit
data:
value: 0
- service: number.set_value
target:
entity_id: number.hyper_2000_soc_set
data:
value: 100
# Bedingungen NICHT erfüllt: STOP (SoC >= 100 ODER Preis zu teuer)
- conditions:
- condition: template
value_template: "{{ states('sensor.hyper_2000_electric_level') | float(0) >= 100 or not (states('sensor.epex_spot_data_market_price') | float(999) <= states('sensor.bkw_preis_schwelle_kalibrierung_wirkungsgrad') | float(999)) }}"
sequence:
- service: number.set_value
target:
entity_id: number.hyper_2000_input_limit
data:
value: 0
- service: number.set_value
target:
entity_id: number.hyper_2000_b_input_limit
data:
value: 0
- service: input_boolean.turn_off
target:
entity_id: input_boolean.balkonkraftwerk_kalibrierung_a_active
# ⚠️ STATE NICHT zu INIT! (Andere AUTOs können jetzt triggern!)
# ═════════════════════════════════════════════════════════════════
# HYPER B - VÖLLIG UNABHÄNGIG VON A!
# ═════════════════════════════════════════════════════════════════
# ─── B: START-Phase (kalibrierung_b_active = OFF) ───
- if: "{{ states('input_boolean.balkonkraftwerk_kalibrierung_b_active') == 'off' and states('sensor.hyper_b_anzahl_tage_seit_100') | int(0) >= 10 }}"
then:
- choose:
# B STUFE 2 (10-20 Tage): netz_bedarf_b_stufe2 > 0 + preis_günstig → START
- conditions:
- condition: template
value_template: "{{ states('sensor.hyper_b_anzahl_tage_seit_100') | int(0) >= 10 and states('sensor.hyper_b_anzahl_tage_seit_100') | int(0) < 20 and netz_bedarf_b_stufe2 > 0 and preis_günstig and soc_b < 100 }}"
sequence:
- service: input_boolean.turn_on
target:
entity_id: input_boolean.balkonkraftwerk_kalibrierung_b_active
- service: input_select.select_option
target:
entity_id: input_select.balkonkraftwerk_current_state
data:
option: KALIBRIERUNG
- service: select.select_option
target:
entity_id: select.zendure_manager_operation
data:
option: "off"
- delay: "00:00:15"
- service: select.select_option
target:
entity_id: select.hyper_2000_b_ac_mode
data:
option: "input"
- service: number.set_value
target:
entity_id: number.hyper_2000_b_input_limit
data:
value: 800
- service: number.set_value
target:
entity_id: number.hyper_2000_input_limit
data:
value: 0
# B STUFE 3 (>=20 Tage): soc_b < 100 + preis_günstig → START
- conditions:
- condition: template
value_template: "{{ states('sensor.hyper_b_anzahl_tage_seit_100') | int(0) >= 20 and soc_b < 100 and preis_günstig }}"
sequence:
- service: input_boolean.turn_on
target:
entity_id: input_boolean.balkonkraftwerk_kalibrierung_b_active
- service: input_select.select_option
target:
entity_id: input_select.balkonkraftwerk_current_state
data:
option: KALIBRIERUNG
- service: select.select_option
target:
entity_id: select.zendure_manager_operation
data:
option: "off"
- delay: "00:00:15"
- service: select.select_option
target:
entity_id: select.hyper_2000_b_ac_mode
data:
option: "input"
- service: number.set_value
target:
entity_id: number.hyper_2000_b_input_limit
data:
value: 800
# ─── B: LAUFEND-Phase (kalibrierung_b_active = ON) ───
- if: "{{ states('input_boolean.balkonkraftwerk_kalibrierung_b_active') == 'on' }}"
then:
- choose:
# B STUFE 2 LAUFEND: netz_bedarf_b_stufe2 > 0 + preis_günstig → 800W
- conditions:
- condition: template
value_template: "{{ states('sensor.hyper_b_anzahl_tage_seit_100') | int(0) >= 10 and states('sensor.hyper_b_anzahl_tage_seit_100') | int(0) < 20 }}"
sequence:
- choose:
# Bedingungen erfüllt: WEITERLADEN
- conditions:
- condition: template
value_template: "{{ states('sensor.netz_bedarf_b_stufe2_pv') | float(0) > 0 and (states('sensor.epex_spot_data_market_price') | float(999) <= states('sensor.bkw_preis_schwelle_kalibrierung_wirkungsgrad') | float(999)) }}"
sequence:
- service: number.set_value
target:
entity_id: number.hyper_2000_b_input_limit
data:
value: 800
# Bedingungen NICHT erfüllt: STOP
- conditions:
- condition: template
value_template: "{{ states('sensor.netz_bedarf_b_stufe2_pv') | float(0) <= 0 or not (states('sensor.epex_spot_data_market_price') | float(999) <= states('sensor.bkw_preis_schwelle_kalibrierung_wirkungsgrad') | float(999)) }}"
sequence:
- service: number.set_value
target:
entity_id: number.hyper_2000_b_input_limit
data:
value: 0
- service: number.set_value
target:
entity_id: number.hyper_2000_input_limit
data:
value: 0
- service: input_boolean.turn_off
target:
entity_id: input_boolean.balkonkraftwerk_kalibrierung_b_active
# ⚠️ STATE NICHT zu INIT! (Andere AUTOs können jetzt triggern!)
# B STUFE 3 LAUFEND: soc_b < 100 + preis_günstig → 800W
- conditions:
- condition: template
value_template: "{{ states('sensor.hyper_b_anzahl_tage_seit_100') | int(0) >= 20 }}"
sequence:
- choose:
# Bedingungen erfüllt: WEITERLADEN
- conditions:
- condition: template
value_template: "{{ states('sensor.hyper_2000_b_electric_level') | float(0) < 100 and (states('sensor.epex_spot_data_market_price') | float(999) <= states('sensor.bkw_preis_schwelle_kalibrierung_wirkungsgrad') | float(999)) }}"
sequence:
- service: number.set_value
target:
entity_id: number.hyper_2000_input_limit
data:
value: 0
- service: number.set_value
target:
entity_id: number.hyper_2000_b_input_limit
data:
value: 800
# Bedingungen NICHT erfüllt: STOP (SoC >= 100 ODER Preis zu teuer)
- conditions:
- condition: template
value_template: "{{ states('sensor.hyper_2000_b_electric_level') | float(0) >= 100 or not (states('sensor.epex_spot_data_market_price') | float(999) <= states('sensor.bkw_preis_schwelle_kalibrierung_wirkungsgrad') | float(999)) }}"
sequence:
- service: number.set_value
target:
entity_id: number.hyper_2000_input_limit
data:
value: 0
- service: number.set_value
target:
entity_id: number.hyper_2000_b_input_limit
data:
value: 0
- service: input_boolean.turn_off
target:
entity_id: input_boolean.balkonkraftwerk_kalibrierung_b_active
# ⚠️ STATE NICHT zu INIT! (Andere AUTOs können jetzt triggern!)
# ───────────────────────────────────────────────────────────────────────
# AUTO 4: SCHICHT 4 - Laden
# ───────────────────────────────────────────────────────────────────────
- id: balkonkraftwerk_auto_4_preemptive_laden
alias: "BK-AUTO-4: Schicht 4 - Preemptive Laden"
description: "Laden heute noch für morgen (Puffer-Strategie)"
mode: single
trigger:
# START Trigger A: bedarf_a über 0.2 für 10 sec
- trigger: numeric_state
entity_id: sensor.lade_bedarf_a_smart_pv_ziel
above: 0.2
for: "00:00:15"
id: bedarf_a_start_trigger
# START Trigger B: bedarf_b über 0.2 für 10 sec
- trigger: numeric_state
entity_id: sensor.lade_bedarf_b_smart_pv_ziel
above: 0.2
for: "00:00:15"
id: bedarf_b_start_trigger
# STOP Trigger A: bedarf_a unter 0.05 für 10 sec
- trigger: numeric_state
entity_id: sensor.lade_bedarf_a_smart_pv_ziel
below: 0.05
for: "00:00:10"
id: bedarf_a_stop_trigger
# STOP Trigger B: bedarf_b unter 0.05 für 10 sec
- trigger: numeric_state
entity_id: sensor.lade_bedarf_b_smart_pv_ziel
below: 0.05
for: "00:00:10"
id: bedarf_b_stop_trigger
# Preis Trigger: Wenn Preis sich ändert
- trigger: state
entity_id: sensor.epex_spot_data_market_price
id: price_trigger
# BKW State Trigger: Wenn State sich ändert
- trigger: state
entity_id: input_select.balkonkraftwerk_current_state
id: bkw_state_trigger
variables:
lade_bedarf_a: "{{ states('sensor.lade_bedarf_a_smart_pv_ziel') | float(0) }}"
lade_bedarf_b: "{{ states('sensor.lade_bedarf_b_smart_pv_ziel') | float(0) }}"
current_state: "{{ states('input_select.balkonkraftwerk_current_state') }}"
current_price: "{{ states('sensor.epex_spot_data_market_price') | float(999) }}"
threshold_price: "{{ states('sensor.bkw_preis_schwelle_laden_wirkungsgrad') | float(999) }}"
condition:
# ⚠️ PRIORITÄTS-CHECK: NICHT wenn höhere Priorität!
- condition: template
value_template: |
{% set current_state = states('input_select.balkonkraftwerk_current_state') %}
{{ current_state not in ['KALIBRIERUNG', 'LADE_NOTFALL'] }}
action:
# ═══════════════════════════════════════════════════════════════════════
# ERST PRÜFEN: STOP oder START?
# ═══════════════════════════════════════════════════════════════════════
# ═══════════════════════════════════════════════════════════════════════
# STOP-LOGIK: Wenn bereits LADE_MODUS
# ═══════════════════════════════════════════════════════════════════════
- if: "{{ states('input_select.balkonkraftwerk_current_state') == 'LADE_MODUS' }}"
then:
# STOP SZENARIO 0 (ERSTE PRIORITÄT!): PREIS ZU TEUER
# Wenn aktueller Preis > Schwelle → STOP SOFORT!
- if: "{{ states('sensor.epex_spot_data_market_price') | float(999) > states('sensor.bkw_preis_schwelle_laden_wirkungsgrad') | float(999) }}"
then:
- service: input_select.select_option
target:
entity_id: input_select.balkonkraftwerk_current_state
data:
option: INIT
- service: number.set_value
target:
entity_id:
- number.hyper_2000_input_limit
- number.hyper_2000_b_input_limit
data:
value: 0
- delay: "00:00:02"
- service: select.select_option
target:
entity_id: select.zendure_manager_operation
data:
option: "smart"
- delay: "00:00:02"
- service: input_text.set_value
target:
entity_id: input_text.balkonkraftwerk_last_automation_debug
data:
value: "AUTO 4 STOP SZENARIO 0 | PREIS ZU TEUER ({{ current_price|round(4) }} > {{ threshold_price|round(4) }})"
- service: notify.mobile_app_iphone_von_b
data:
title: "🚨 AUTO 4: STOP - PREIS ZU TEUER!"
message: |
Aktueller Preis: {{ current_price | round(4) }} €/kWh
Schwelle: {{ threshold_price | round(4) }} €/kWh
STOP laden sofort!
Manager → smart
State → INIT
# STOP SZENARIO 1: BEIDE FERTIG (bedarf_a <= 0 UND bedarf_b <= 0)
- if: >
{{ states('sensor.lade_bedarf_a_smart_pv_ziel') | float(0) <= 0.05
and
states('sensor.lade_bedarf_b_smart_pv_ziel') | float(0) <= 0.05 }}
then:
- service: input_select.select_option
target:
entity_id: input_select.balkonkraftwerk_current_state
data:
option: INIT
- service: number.set_value
target:
entity_id:
- number.hyper_2000_input_limit
- number.hyper_2000_b_input_limit
data:
value: 0
- delay: "00:00:02"
- service: select.select_option
target:
entity_id: select.zendure_manager_operation
data:
option: "smart"
- delay: "00:00:02"
- service: input_text.set_value
target:
entity_id: input_text.balkonkraftwerk_last_automation_debug
data:
value: "AUTO 4 STOP SZENARIO 1 | BEIDE fertig (bedarf_a={{ lade_bedarf_a|round(3) }}, bedarf_b={{ lade_bedarf_b|round(3) }})"
- service: notify.mobile_app_iphone_von_b
data:
title: "✅ LADE_MODUS beendet"
message: |
Beide Hyper fertig geladen!
Bedarf A: {{ lade_bedarf_a | round(3) }} kWh
Bedarf B: {{ lade_bedarf_b | round(3) }} kWh
Manager → smart
State → INIT
# STOP SZENARIO 2: NUR A FERTIG (bedarf_a <= 0 UND bedarf_b > 0.2)
- if: "{{ states('sensor.lade_bedarf_a_smart_pv_ziel') | float(0) <= 0.05 and states('sensor.lade_bedarf_b_smart_pv_ziel') | float(0) > 0.2 }}"
then:
- service: number.set_value
target:
entity_id: number.hyper_2000_input_limit
data:
value: 0
- service: input_text.set_value
target:
entity_id: input_text.balkonkraftwerk_last_automation_debug
data:
value: "AUTO 4 STOP SZENARIO 2 | NUR A fertig (bedarf_a={{ lade_bedarf_a|round(3) }}, bedarf_b={{ lade_bedarf_b|round(3) }})"
- service: notify.mobile_app_iphone_von_b
data:
title: "⏹️ Hyper A fertig"
message: |
Bedarf A: {{ lade_bedarf_a | round(3) }} kWh ≤ 0
Input A → 0W
(B lädt noch weiter!)
# STOP SZENARIO 3: NUR B FERTIG (bedarf_b <= 0 UND bedarf_a > 0.2)
- if: "{{ states('sensor.lade_bedarf_b_smart_pv_ziel') | float(0) <= 0.05 and states('sensor.lade_bedarf_a_smart_pv_ziel') | float(0) > 0.2 }}"
then:
- service: number.set_value
target:
entity_id: number.hyper_2000_b_input_limit
data:
value: 0
- service: input_text.set_value
target:
entity_id: input_text.balkonkraftwerk_last_automation_debug
data:
value: "AUTO 4 STOP SZENARIO 3 | NUR B fertig (bedarf_a={{ lade_bedarf_a|round(3) }}, bedarf_b={{ lade_bedarf_b|round(3) }})"
- service: notify.mobile_app_iphone_von_b
data:
title: "⏹️ Hyper B fertig"
message: |
Bedarf B: {{ lade_bedarf_b | round(3) }} kWh ≤ 0
Input B → 0W
(A lädt noch weiter!)
# ═══════════════════════════════════════════════════════════════════════
# START-LOGIK: Wenn NICHT LADE_MODUS und Bedarf > 0.2 UND Preis ok
# ═══════════════════════════════════════════════════════════════════════
- if: >
{{ states('input_select.balkonkraftwerk_current_state') != 'LADE_MODUS'
and (
states('sensor.lade_bedarf_a_smart_pv_ziel') | float(0) > 0.2
or
states('sensor.lade_bedarf_b_smart_pv_ziel') | float(0) > 0.2
)
and
states('sensor.epex_spot_data_market_price') | float(999)
<=
states('sensor.bkw_preis_schwelle_laden_wirkungsgrad') | float(999)
and
states('sensor.zendure_manager_available_kwh') | float(0) < 2.25
and
states('sensor.solcast_pv_forecast_prognose_verbleibende_leistung_heute') | float(0) < 3
and
states('sensor.solcast_pv_forecast_prognose_morgen') | float(0) < 3
}}
then:
- service: input_select.select_option
target:
entity_id: input_select.balkonkraftwerk_current_state
data:
option: LADE_MODUS
- delay: "00:00:02"
# SCHRITT 0: Manager = off + AC-Mode = input
- service: select.select_option
target:
entity_id: select.zendure_manager_operation
data:
option: "off"
- delay: "00:00:02"
- service: select.select_option
target:
entity_id:
- select.hyper_2000_ac_mode
- select.hyper_2000_b_ac_mode
data:
option: "input"
# SZENARIO 1: NUR A hat Bedarf (a > 0.2, b <= 0)
- if: "{{ states('sensor.lade_bedarf_a_smart_pv_ziel') | float(0) > 0.2 and states('sensor.lade_bedarf_b_smart_pv_ziel') | float(0) <= 0 }}"
then:
- service: input_select.select_option
target:
entity_id: input_select.balkonkraftwerk_current_state
data:
option: LADE_MODUS
- delay: "00:00:02"
- service: number.set_value
target:
entity_id: number.hyper_2000_input_limit
data:
value: 800
- delay: "00:00:02"
- service: number.set_value
target:
entity_id: number.hyper_2000_b_input_limit
data:
value: 0
- delay: "00:00:02"
- service: input_text.set_value
target:
entity_id: input_text.balkonkraftwerk_last_automation_debug
data:
value: "AUTO 4 Szenario 1 | NUR Hyper A laden (bedarf_a={{ lade_bedarf_a|round(2) }}, bedarf_b={{ lade_bedarf_b|round(2) }})"
- service: notify.mobile_app_iphone_von_b
data:
title: "📈 AUTO 4: NUR HYPER A LADEN"
message: |
Bedarf A: {{ lade_bedarf_a | round(2) }} kWh
Bedarf B: {{ lade_bedarf_b | round(2) }} kWh
Preis: {{ current_price | round(4) }} €/kWh
Lade nur Hyper A mit 800W
# SZENARIO 2: NUR B hat Bedarf (a <= 0, b > 0.2)
- if: "{{ states('sensor.lade_bedarf_a_smart_pv_ziel') | float(0) <= 0.05 and states('sensor.lade_bedarf_b_smart_pv_ziel') | float(0) > 0.2 }}"
then:
- service: input_select.select_option
target:
entity_id: input_select.balkonkraftwerk_current_state
data:
option: LADE_MODUS
- delay: "00:00:02"
- service: number.set_value
target:
entity_id: number.hyper_2000_input_limit
data:
value: 0
- delay: "00:00:02"
- service: number.set_value
target:
entity_id: number.hyper_2000_b_input_limit
data:
value: 800
- delay: "00:00:02"
- service: input_text.set_value
target:
entity_id: input_text.balkonkraftwerk_last_automation_debug
data:
value: "AUTO 4 Szenario 2 | NUR Hyper B laden (bedarf_a={{ lade_bedarf_a|round(2) }}, bedarf_b={{ lade_bedarf_b|round(2) }})"
- service: notify.mobile_app_iphone_von_b
data:
title: "📈 AUTO 4: NUR HYPER B LADEN"
message: |
Bedarf A: {{ lade_bedarf_a | round(2) }} kWh
Bedarf B: {{ lade_bedarf_b | round(2) }} kWh
Preis: {{ current_price | round(4) }} €/kWh
Lade nur Hyper B mit 800W
# SZENARIO 3: BEIDE haben Bedarf (a > 0.2, b > 0.2)
- if: "{{ states('sensor.lade_bedarf_a_smart_pv_ziel') | float(0) > 0.2 and states('sensor.lade_bedarf_b_smart_pv_ziel') | float(0) > 0.2 }}"
then:
- service: input_select.select_option
target:
entity_id: input_select.balkonkraftwerk_current_state
data:
option: LADE_MODUS
- delay: "00:00:02"
- service: number.set_value
target:
entity_id:
- number.hyper_2000_input_limit
- number.hyper_2000_b_input_limit
data:
value: 800
- delay: "00:00:02"
- service: notify.mobile_app_iphone_von_b
data:
title: "📈 AUTO 4: BEIDE HYPER LADEN"
message: |
Bedarf A: {{ lade_bedarf_a | round(2) }} kWh
Bedarf B: {{ lade_bedarf_b | round(2) }} kWh
Preis: {{ current_price | round(4) }} €/kWh
Lade beide Hyper mit je 800W
- service: input_text.set_value
target:
entity_id: input_text.balkonkraftwerk_last_automation_debug
data:
value: "AUTO 4 Szenario 3 | BEIDE Hyper laden (bedarf_a={{ lade_bedarf_a|round(2) }}, bedarf_b={{ lade_bedarf_b|round(2) }})"
# ───────────────────────────────────────────────────────────────────────
# AUTO 6: MaxSoC 70% BIAS - Tage-basiert + Delta-basiert + Boolean-Tracking
# ───────────────────────────────────────────────────────────────────────
# Kombiniert AUTO 3.4 (kontinuierliche Prüfung) + AUTO 6 (Boolean-Tracking)
- id: balkonkraftwerk_auto_6_smart_bias
alias: "BK-AUTO-6: MaxSoC 70% BIAS - Tage & Delta Steuerung"
description: "Kontinuierliche MaxSoC 70% Bias: IF tage_b > tage_a OR delta >= 35% → 70% (mit Boolean-Tracking)"
mode: restart
trigger:
# Oder wenn sich Tage ändern (wichtig!)
- trigger: state
entity_id:
- sensor.hyper_a_anzahl_tage_seit_100
- sensor.hyper_b_anzahl_tage_seit_100
id: days_change
# Oder wenn SoC sich ändert (für ΔSoC Check)
- trigger: state
entity_id:
- sensor.hyper_2000_electric_level
- sensor.hyper_2000_b_electric_level
id: soc_change
action:
- variables:
days_a: "{{ states('sensor.hyper_a_anzahl_tage_seit_100') | float(0) }}"
days_b: "{{ states('sensor.hyper_b_anzahl_tage_seit_100') | float(0) }}"
soc_a: "{{ states('sensor.hyper_2000_electric_level') | float(0) }}"
soc_b: "{{ states('sensor.hyper_2000_b_electric_level') | float(0) }}"
delta_soc: "{{ (soc_a - soc_b) | round(1) }}"
current_max_soc: "{{ states('number.hyper_2000_soc_set') | float(100) }}"
bias_active: "{{ states('input_boolean.z0_smart_soc_bias_active') == 'on' }}"
- choose:
# ═══════════════════════════════════════════════════════════════
# AKTIVIERE MaxSoC 70%: wenn Delta >= 35% ODER days_b > days_a
# ═════════════════════════════════════════════════════════════════
- conditions:
- condition: template
value_template: "{{ ((states('sensor.hyper_2000_electric_level') | float(0) - states('sensor.hyper_2000_b_electric_level') | float(0)) >= 35) or (states('sensor.hyper_b_anzahl_tage_seit_100') | int(0) > states('sensor.hyper_a_anzahl_tage_seit_100') | int(0)) }}"
- condition: template
value_template: "{{ states('input_select.balkonkraftwerk_current_state') != 'KALIBRIERUNG' }}"
sequence:
# Nur setzen wenn nicht schon 70%
- if: "{{ states('number.hyper_2000_soc_set') | float(100) != 70 }}"
then:
# Setze MaxSoC = 70%
- service: number.set_value
target:
entity_id: number.hyper_2000_soc_set
data:
value: 70
# Setze Boolean auf ON (Bias aktiv!)
- service: input_boolean.turn_on
target:
entity_id: input_boolean.z0_smart_soc_bias_active
# Notification
- service: notify.mobile_app_iphone_von_b
data:
title: "MaxSoC A = 70%"
message: |
{% if days_b > days_a %}
Tage: A={{ days_a | round(0) }}, B={{ days_b | round(0) }} (B älter!)
{% else %}
ΔSoC: {{ delta_soc }}% (>= 35%!)
{% endif %}
→ B wird bevorzugt gezogen!
- service: input_text.set_value
target:
entity_id: input_text.balkonkraftwerk_last_automation_debug
data:
value: "AUTO 6 | MaxSoC A = 70%"
# ═══════════════════════════════════════════════════════════════
# DEAKTIVIERE MaxSoC 70%: wenn Delta < 15% UND days_a > days_b
# ═════════════════════════════════════════════════════════════════
- conditions:
- condition: template
value_template: "{{ ((states('sensor.hyper_2000_electric_level') | float(0) - states('sensor.hyper_2000_b_electric_level') | float(0)) < 15) and (states('sensor.hyper_a_anzahl_tage_seit_100') | int(0) >= states('sensor.hyper_b_anzahl_tage_seit_100') | int(0)) }}"
sequence:
# Nur setzen wenn nicht schon 100%
- if: "{{ states('number.hyper_2000_soc_set') | float(100) != 100 }}"
then:
# Setze MaxSoC = 100%
- service: number.set_value
target:
entity_id: number.hyper_2000_soc_set
data:
value: 100
# Setze Boolean auf OFF (Bias aus!)
- service: input_boolean.turn_off
target:
entity_id: input_boolean.z0_smart_soc_bias_active
# Notification
- service: notify.mobile_app_iphone_von_b
data:
title: "MaxSoC A = 100% ("
message: |
Tage: A={{ days_a | round(0) }}, B={{ days_b | round(0) }} (A älter/gleich!)
ΔSoC: {{ delta_soc }}% (< 15%!)
ΔSoC: {{ delta_soc }}%
- service: input_text.set_value
target:
entity_id: input_text.balkonkraftwerk_last_automation_debug
data:
value: "AUTO 6 | MaxSoC A = 100%"
# ═══════════════════════════════════════════════════════════════════════════
# END OF FILE
# ═══════════════════════════════════════════════════════════════════════════