Update: Battery Optimizer v3.4.0 mit allen Fixes und Features
This commit is contained in:
98
legacy/v2/ENTITY_CHECKLIST.md
Normal file
98
legacy/v2/ENTITY_CHECKLIST.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# ✅ Entitäten-Checkliste
|
||||
|
||||
## Nach Installation sollten folgende Entitäten existieren:
|
||||
|
||||
### Input Boolean (2 Stück)
|
||||
- [ ] `input_boolean.battery_optimizer_enabled`
|
||||
- [ ] `input_boolean.battery_optimizer_manual_override`
|
||||
|
||||
### Input Number (7 Stück)
|
||||
- [ ] `input_number.battery_capacity_kwh`
|
||||
- [ ] `input_number.battery_optimizer_min_soc`
|
||||
- [ ] `input_number.battery_optimizer_max_soc`
|
||||
- [ ] `input_number.battery_optimizer_max_charge_power`
|
||||
- [ ] `input_number.battery_optimizer_price_threshold`
|
||||
- [ ] `input_number.battery_optimizer_reserve_capacity`
|
||||
- [ ] `input_number.battery_optimizer_pv_threshold`
|
||||
|
||||
### Input Text (1 Stück)
|
||||
- [ ] `input_text.battery_optimizer_status`
|
||||
|
||||
### Template Sensors (3 Stück) - werden AUTOMATISCH erstellt
|
||||
Diese Sensoren werden erst nach Home Assistant Neustart und Laden der Templates erstellt:
|
||||
- [ ] `sensor.battery_charging_plan_status`
|
||||
- [ ] `sensor.battery_next_action`
|
||||
- [ ] `sensor.battery_estimated_savings`
|
||||
|
||||
**Hinweis:** Template Sensors zeigen "unavailable" bis der erste Plan berechnet wurde!
|
||||
|
||||
### PyScript States (1 Stück) - wird AUTOMATISCH erstellt
|
||||
Dieser State wird beim ersten Aufruf von `calculate_charging_schedule()` erstellt:
|
||||
- [ ] `pyscript.battery_charging_schedule`
|
||||
|
||||
### Bestehende Entitäten (müssen bereits vorhanden sein)
|
||||
- [ ] `input_boolean.goodwe_manual_control` (dein bestehendes System)
|
||||
- [ ] `input_number.charge_power_battery` (dein bestehendes System)
|
||||
- [ ] `sensor.openems_ess0_soc` (OpenEMS Modbus)
|
||||
- [ ] `sensor.hastrom_flex_pro` (Strompreis-Sensor)
|
||||
- [ ] `sensor.energy_production_today` (Forecast.Solar Ost)
|
||||
- [ ] `sensor.energy_production_today_2` (Forecast.Solar West)
|
||||
- [ ] `sensor.energy_production_tomorrow` (Forecast.Solar Ost)
|
||||
- [ ] `sensor.energy_production_tomorrow_2` (Forecast.Solar West)
|
||||
|
||||
## Prüfen nach Installation
|
||||
|
||||
### Schritt 1: Input Helper prüfen
|
||||
```
|
||||
Einstellungen → Geräte & Dienste → Helfer
|
||||
```
|
||||
Suche nach "battery_optimizer" - sollte 10 Einträge zeigen
|
||||
|
||||
### Schritt 2: Template Sensors prüfen
|
||||
```
|
||||
Entwicklerwerkzeuge → Zustände
|
||||
```
|
||||
Suche nach "battery_" - Template Sensors sollten existieren (können "unavailable" sein)
|
||||
|
||||
### Schritt 3: Ersten Plan berechnen
|
||||
```
|
||||
Entwicklerwerkzeuge → Dienste
|
||||
service: pyscript.calculate_charging_schedule
|
||||
```
|
||||
|
||||
### Schritt 4: PyScript State prüfen
|
||||
```
|
||||
Entwicklerwerkzeuge → Zustände
|
||||
```
|
||||
Suche nach "pyscript.battery_charging_schedule" - sollte jetzt existieren!
|
||||
|
||||
### Schritt 5: Template Sensors sollten jetzt Werte haben
|
||||
```
|
||||
Entwicklerwerkzeuge → Zustände
|
||||
```
|
||||
- `sensor.battery_charging_plan_status` sollte z.B. "3 Ladungen geplant" zeigen
|
||||
- `sensor.battery_next_action` sollte nächste Aktion zeigen
|
||||
- `sensor.battery_estimated_savings` zeigt Ersparnis (oder 0)
|
||||
|
||||
## Fehlende Entitäten beheben
|
||||
|
||||
### Wenn Input Helper fehlen:
|
||||
1. Prüfe ob `battery_optimizer_config.yaml` richtig in `/config/packages/` liegt
|
||||
2. Prüfe ob in `configuration.yaml` Packages aktiviert sind:
|
||||
```yaml
|
||||
homeassistant:
|
||||
packages: !include_dir_named packages
|
||||
```
|
||||
3. Home Assistant neu starten
|
||||
4. Logs prüfen auf Fehler
|
||||
|
||||
### Wenn Template Sensors fehlen:
|
||||
1. Prüfe ob der `template:` Abschnitt in der Config ist
|
||||
2. Home Assistant neu starten
|
||||
3. Yaml-Konfiguration prüfen in Developer Tools
|
||||
|
||||
### Wenn PyScript State fehlt:
|
||||
1. PyScript muss installiert sein
|
||||
2. `battery_optimizer.py` muss in `/config/pyscript/` sein
|
||||
3. Dienst `pyscript.calculate_charging_schedule` manuell aufrufen
|
||||
4. Logs prüfen auf Fehler
|
||||
377
legacy/v2/INSTALLATION.md
Normal file
377
legacy/v2/INSTALLATION.md
Normal file
@@ -0,0 +1,377 @@
|
||||
# 🚀 Battery Charging Optimizer - Installations-Anleitung
|
||||
|
||||
## Übersicht
|
||||
|
||||
Dieses System optimiert die Batterieladung basierend auf:
|
||||
- ✅ Dynamischen Strompreisen (haStrom FLEX PRO)
|
||||
- ✅ PV-Prognose (Forecast.Solar Ost/West)
|
||||
- ✅ Batterie-SOC und Kapazität
|
||||
- ✅ Haushalts-Reserve
|
||||
|
||||
**Besonderheit:** Nutzt dein bestehendes, bewährtes manuelles Steuerungssystem!
|
||||
|
||||
---
|
||||
|
||||
## 📋 Voraussetzungen
|
||||
|
||||
### Bereits vorhanden (✅):
|
||||
1. **PyScript Integration** installiert und aktiviert
|
||||
2. **OpenEMS** läuft auf BeagleBone mit GoodWe Batterie
|
||||
3. **Modbus TCP Integration** für OpenEMS
|
||||
4. **haStrom FLEX PRO** Sensor für Strompreise
|
||||
5. **Forecast.Solar** Integration für PV-Prognose (Ost + West)
|
||||
6. **Funktionierendes manuelles System**:
|
||||
- `pyscript/ess_set_power.py`
|
||||
- 3 Automationen für manuelles Laden
|
||||
- REST Commands für Mode-Wechsel
|
||||
- Input Helper `goodwe_manual_control` und `charge_power_battery`
|
||||
|
||||
### Zu installieren:
|
||||
- Neue Konfigurationsdateien
|
||||
- Neues Optimierungs-Script
|
||||
- Dashboard (optional)
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Installation
|
||||
|
||||
### Schritt 1: Konfiguration installieren
|
||||
|
||||
**Methode A: Als Package (empfohlen)**
|
||||
|
||||
1. Erstelle Verzeichnis `/config/packages/` falls nicht vorhanden
|
||||
2. Kopiere `battery_optimizer_config.yaml` nach `/config/packages/`
|
||||
3. In `configuration.yaml` sicherstellen:
|
||||
```yaml
|
||||
homeassistant:
|
||||
packages: !include_dir_named packages
|
||||
```
|
||||
|
||||
**Methode B: In configuration.yaml**
|
||||
|
||||
Kopiere den Inhalt von `battery_optimizer_config.yaml` in die entsprechenden Sektionen deiner `configuration.yaml`.
|
||||
|
||||
### Schritt 2: PyScript installieren
|
||||
|
||||
1. Kopiere `battery_optimizer.py` nach `/config/pyscript/`
|
||||
2. **Wichtig:** Dein bestehendes `ess_set_power.py` bleibt unverändert!
|
||||
|
||||
### Schritt 3: Home Assistant neu starten
|
||||
|
||||
```bash
|
||||
# Developer Tools → YAML → Restart
|
||||
```
|
||||
|
||||
Oder über CLI:
|
||||
```bash
|
||||
ha core restart
|
||||
```
|
||||
|
||||
### Schritt 4: Input Helper prüfen
|
||||
|
||||
Nach dem Neustart, gehe zu **Einstellungen → Geräte & Dienste → Helfer**
|
||||
|
||||
Folgende Helper sollten jetzt existieren:
|
||||
- `input_boolean.battery_optimizer_enabled`
|
||||
- `input_boolean.battery_optimizer_manual_override`
|
||||
- `input_number.battery_capacity_kwh`
|
||||
- `input_number.battery_optimizer_min_soc`
|
||||
- `input_number.battery_optimizer_max_soc`
|
||||
- `input_number.battery_max_charge_power`
|
||||
- `input_number.battery_optimizer_price_threshold`
|
||||
- `input_number.battery_reserve_capacity_kwh`
|
||||
- `input_number.battery_optimizer_pv_threshold`
|
||||
- `input_text.battery_optimizer_status`
|
||||
|
||||
**Falls nicht:** Prüfe die Konfiguration und Logs!
|
||||
|
||||
### Schritt 5: PyScript-Dienste prüfen
|
||||
|
||||
Gehe zu **Entwicklerwerkzeuge → Dienste**
|
||||
|
||||
Suche nach `pyscript` - folgende Dienste sollten vorhanden sein:
|
||||
- `pyscript.calculate_charging_schedule`
|
||||
- `pyscript.execute_charging_schedule`
|
||||
- `pyscript.ess_set_power` (bestehend)
|
||||
|
||||
**Falls nicht:**
|
||||
- Prüfe `/config/pyscript/battery_optimizer.py` existiert
|
||||
- Prüfe Logs: `custom_components.pyscript`
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Konfiguration
|
||||
|
||||
### Batterie-Parameter einstellen
|
||||
|
||||
Gehe zu **Einstellungen → Geräte & Dienste → Helfer** und setze:
|
||||
|
||||
1. **Batterie-Kapazität**: `10 kWh` (GoodWe)
|
||||
2. **Min. SOC**: `20%` (Schutz vor Tiefentladung)
|
||||
3. **Max. SOC**: `100%` (oder z.B. 90% für Langlebigkeit)
|
||||
4. **Max. Ladeleistung**: `5000W` (5 kW - dein System-Limit)
|
||||
|
||||
### Optimierungs-Parameter anpassen
|
||||
|
||||
1. **Preis-Schwellwert**: `28 ct/kWh` (Startwert, wird automatisch angepasst)
|
||||
2. **Reserve-Kapazität**: `2 kWh` (Reserve für Haushalt)
|
||||
3. **PV-Schwellwert**: `500 Wh` (Bei mehr PV keine Netz-Ladung)
|
||||
|
||||
### Sensor-Namen prüfen
|
||||
|
||||
Falls deine Sensoren andere Namen haben, passe diese in `battery_optimizer.py` an:
|
||||
|
||||
```python
|
||||
# Zeile ~100
|
||||
sensor_east = 'sensor.energy_production_today' # Dein Ost-Array
|
||||
sensor_west = 'sensor.energy_production_today_2' # Dein West-Array
|
||||
|
||||
# Zeile ~35
|
||||
price_entity = 'sensor.hastrom_flex_pro' # Dein Strompreis-Sensor
|
||||
|
||||
# Zeile ~185
|
||||
current_soc = float(state.get('sensor.openems_ess0_soc') or 50) # Dein SOC-Sensor
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testen
|
||||
|
||||
### Test 1: Manuelle Berechnung
|
||||
|
||||
```yaml
|
||||
# Entwicklerwerkzeuge → Dienste
|
||||
service: pyscript.calculate_charging_schedule
|
||||
```
|
||||
|
||||
**Erwartetes Ergebnis:**
|
||||
- Log-Einträge in **Einstellungen → System → Protokolle**
|
||||
- State `pyscript.battery_charging_schedule` existiert
|
||||
- Attribute `schedule` enthält Array mit Stunden
|
||||
|
||||
**Prüfen in Developer Tools → Zustände:**
|
||||
```
|
||||
pyscript.battery_charging_schedule
|
||||
Attributes:
|
||||
schedule: [...]
|
||||
last_update: 2025-11-09T17:30:00
|
||||
num_hours: 24
|
||||
num_charges: 3
|
||||
```
|
||||
|
||||
### Test 2: Plan ansehen
|
||||
|
||||
```yaml
|
||||
# Entwicklerwerkzeuge → Template
|
||||
{{ state_attr('pyscript.battery_charging_schedule', 'schedule') }}
|
||||
```
|
||||
|
||||
Sollte eine Liste mit Stunden-Einträgen zeigen:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"datetime": "2025-11-09T23:00:00",
|
||||
"hour": 23,
|
||||
"action": "charge",
|
||||
"power_w": -5000,
|
||||
"price": 26.85,
|
||||
"reason": "Günstig (26.85 ct) + wenig PV (0 Wh)"
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
### Test 3: Manuelle Ausführung
|
||||
|
||||
**ACHTUNG:** Nur testen wenn Batterie geladen werden kann!
|
||||
|
||||
```yaml
|
||||
# Entwicklerwerkzeuge → Dienste
|
||||
service: pyscript.execute_charging_schedule
|
||||
```
|
||||
|
||||
**Was passiert:**
|
||||
- Wenn aktuelle Stunde = Ladezeit → Aktiviert `input_boolean.goodwe_manual_control`
|
||||
- Wenn nicht → Deaktiviert manuellen Modus
|
||||
|
||||
**Prüfen:**
|
||||
- Log-Einträge zeigen welche Aktion ausgeführt wird
|
||||
- Bei Ladung: `goodwe_manual_control` schaltet auf "on"
|
||||
- Deine bestehende Automation übernimmt → Batterie lädt!
|
||||
|
||||
### Test 4: Automatische Zeitsteuerung warten
|
||||
|
||||
Die Zeit-Trigger sind aktiv:
|
||||
- **14:05 Uhr täglich**: Neue Berechnung
|
||||
- **xx:05 Uhr stündlich**: Ausführung
|
||||
|
||||
Warte bis zur nächsten vollen Stunde + 5 Min und prüfe Logs!
|
||||
|
||||
---
|
||||
|
||||
## 📊 Dashboard installieren (Optional)
|
||||
|
||||
1. Gehe zu deinem Lovelace Dashboard
|
||||
2. **Bearbeiten** → **Raw-Konfigurations-Editor** (3 Punkte oben rechts)
|
||||
3. Füge den Inhalt von `battery_optimizer_dashboard.yaml` ein
|
||||
|
||||
**Oder:** Manuell Cards erstellen über die UI
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Troubleshooting
|
||||
|
||||
### Problem: "Keine Strompreis-Daten"
|
||||
|
||||
**Lösung:**
|
||||
1. Prüfe Sensor `sensor.hastrom_flex_pro` existiert
|
||||
2. Prüfe Attribut `hours` ist vorhanden:
|
||||
```yaml
|
||||
# Developer Tools → Zustände
|
||||
sensor.hastrom_flex_pro
|
||||
Attributes:
|
||||
hours: [...]
|
||||
```
|
||||
3. Falls anderer Name → Anpassen in `battery_optimizer.py` Zeile ~35
|
||||
|
||||
### Problem: "Keine PV-Prognose"
|
||||
|
||||
**Lösung:**
|
||||
1. Prüfe Forecast.Solar Sensoren existieren:
|
||||
- `sensor.energy_production_today`
|
||||
- `sensor.energy_production_today_2`
|
||||
- `sensor.energy_production_tomorrow`
|
||||
- `sensor.energy_production_tomorrow_2`
|
||||
2. Falls andere Namen → Anpassen in `battery_optimizer.py` Zeile ~100
|
||||
|
||||
### Problem: PyScript-Dienste nicht sichtbar
|
||||
|
||||
**Lösung:**
|
||||
1. Prüfe PyScript ist installiert: **HACS → Integrationen → PyScript**
|
||||
2. Prüfe `/config/pyscript/battery_optimizer.py` existiert
|
||||
3. Home Assistant neu starten
|
||||
4. Logs prüfen: `grep pyscript /config/home-assistant.log`
|
||||
|
||||
### Problem: Batterie lädt nicht trotz Plan
|
||||
|
||||
**Lösung:**
|
||||
1. Prüfe `input_boolean.battery_optimizer_enabled` ist "on"
|
||||
2. Prüfe `input_boolean.battery_optimizer_manual_override` ist "off"
|
||||
3. Prüfe deine bestehenden Automationen sind aktiv:
|
||||
- "Switch: Manuelle Speicherbeladung aktivieren"
|
||||
- "Automation: Speicher manuell laden"
|
||||
4. Manuell testen:
|
||||
```yaml
|
||||
service: input_boolean.turn_on
|
||||
target:
|
||||
entity_id: input_boolean.goodwe_manual_control
|
||||
```
|
||||
→ Sollte Ladung starten über deine Automation!
|
||||
|
||||
### Problem: "Keine Daten für aktuelle Stunde"
|
||||
|
||||
**Ursache:** Plan wurde zu spät erstellt oder enthält nicht alle Stunden
|
||||
|
||||
**Lösung:**
|
||||
1. Plan manuell neu erstellen:
|
||||
```yaml
|
||||
service: pyscript.calculate_charging_schedule
|
||||
```
|
||||
2. Prüfe ob tägliche Automation um 14:05 läuft
|
||||
3. Plan sollte **aktuelle Stunde inkludieren**
|
||||
|
||||
---
|
||||
|
||||
## 🎓 Wie das System funktioniert
|
||||
|
||||
### Täglicher Ablauf
|
||||
|
||||
**14:05 Uhr:**
|
||||
1. PyScript berechnet optimalen Ladeplan für nächste 24-36h
|
||||
2. Berücksichtigt:
|
||||
- Strompreise (dynamischer Schwellwert = 90. Perzentil)
|
||||
- PV-Prognose Ost + West kombiniert
|
||||
- Aktueller Batterie-SOC
|
||||
- Konfigurierte Parameter
|
||||
3. Speichert Plan als `pyscript.battery_charging_schedule` State
|
||||
|
||||
**Jede Stunde um xx:05:**
|
||||
1. PyScript prüft was für aktuelle Stunde geplant ist
|
||||
2. **Wenn "charge":**
|
||||
- Setzt `input_number.charge_power_battery` auf Ziel-Leistung
|
||||
- Aktiviert `input_boolean.goodwe_manual_control`
|
||||
- Deine Automation übernimmt → Schreibt alle 30s via Modbus
|
||||
3. **Wenn "auto":**
|
||||
- Deaktiviert `goodwe_manual_control`
|
||||
- System läuft im Automatik-Modus
|
||||
|
||||
### Strategie-Logik
|
||||
|
||||
**Laden wenn:**
|
||||
- Strompreis < dynamischer Schwellwert (90. Perzentil)
|
||||
- UND PV-Prognose < 500 Wh für diese Stunde
|
||||
- UND Batterie nicht voll
|
||||
- Sortiert nach: Niedrigster Preis zuerst
|
||||
|
||||
**Nicht laden wenn:**
|
||||
- Preis zu hoch
|
||||
- Genug PV-Erzeugung erwartet
|
||||
- Batterie voll (inkl. Reserve)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Nächste Schritte
|
||||
|
||||
### Nach erfolgreicher Installation:
|
||||
|
||||
1. **Erste Woche beobachten**
|
||||
- Prüfe Logs täglich
|
||||
- Verifiziere dass Plan erstellt wird (14:05)
|
||||
- Verifiziere dass Ausführung läuft (stündlich)
|
||||
- Prüfe ob Batterie zur richtigen Zeit lädt
|
||||
|
||||
2. **Parameter optimieren**
|
||||
- Preis-Schwellwert anpassen falls zu oft/selten lädt
|
||||
- PV-Schwellwert anpassen basierend auf Erfahrung
|
||||
- Reserve-Kapazität optimieren
|
||||
|
||||
3. **Statistiken sammeln**
|
||||
- Notiere Einsparungen
|
||||
- Vergleiche mit vorherigem Verbrauch
|
||||
- Dokumentiere für Community
|
||||
|
||||
4. **Community-Veröffentlichung vorbereiten**
|
||||
- Anonymisiere IP-Adressen und Passwörter
|
||||
- Erstelle README mit deinen Erfahrungen
|
||||
- Screenshots vom Dashboard
|
||||
- Beispiel-Logs
|
||||
|
||||
---
|
||||
|
||||
## 📝 Wartung
|
||||
|
||||
### Regelmäßig prüfen:
|
||||
- Logs auf Fehler durchsuchen
|
||||
- Plan-Qualität bewerten (gute Vorhersagen?)
|
||||
- Sensor-Verfügbarkeit (Strompreis, PV-Forecast)
|
||||
|
||||
### Bei Problemen:
|
||||
1. Logs prüfen: `custom_components.pyscript`
|
||||
2. Sensor-Zustände prüfen
|
||||
3. Manuell Plan neu berechnen
|
||||
4. Bei Bedarf Parameter anpassen
|
||||
|
||||
---
|
||||
|
||||
## 🎉 Fertig!
|
||||
|
||||
Dein intelligentes Batterie-Optimierungs-System ist jetzt installiert und nutzt dein bewährtes manuelles Steuerungssystem als solide Basis.
|
||||
|
||||
**Das System wird:**
|
||||
- ✅ Automatisch täglich planen (14:05)
|
||||
- ✅ Automatisch stündlich ausführen (xx:05)
|
||||
- ✅ Zu günstigsten Zeiten laden
|
||||
- ✅ PV-Eigenverbrauch maximieren
|
||||
- ✅ Stromkosten minimieren
|
||||
|
||||
**Viel Erfolg! ⚡💰**
|
||||
551
legacy/v2/battery_optimizer.py
Normal file
551
legacy/v2/battery_optimizer.py
Normal file
@@ -0,0 +1,551 @@
|
||||
"""
|
||||
Battery Charging Optimizer für OpenEMS + GoodWe
|
||||
Nutzt das bestehende manuelle Steuerungssystem
|
||||
|
||||
Speicherort: /config/pyscript/battery_optimizer.py
|
||||
Version: 2.0.0
|
||||
"""
|
||||
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
|
||||
@service
|
||||
def calculate_charging_schedule():
|
||||
"""
|
||||
Berechnet den optimalen Ladeplan für die nächsten 24-36 Stunden
|
||||
Wird täglich um 14:05 Uhr nach Strompreis-Update ausgeführt
|
||||
"""
|
||||
|
||||
log.info("=== Batterie-Optimierung gestartet ===")
|
||||
|
||||
# Prüfe ob Optimierung aktiviert ist
|
||||
if state.get('input_boolean.battery_optimizer_enabled') != 'on':
|
||||
log.info("Optimierung ist deaktiviert")
|
||||
input_text.battery_optimizer_status = "Deaktiviert"
|
||||
return
|
||||
|
||||
try:
|
||||
# Konfiguration laden
|
||||
config = load_configuration()
|
||||
log.info(f"Konfiguration geladen: SOC {config['min_soc']}-{config['max_soc']}%, Max {config['max_charge_power']}W")
|
||||
|
||||
# Strompreise laden
|
||||
price_data = get_electricity_prices()
|
||||
if not price_data:
|
||||
log.error("Keine Strompreis-Daten verfügbar")
|
||||
input_text.battery_optimizer_status = "Fehler: Keine Preisdaten"
|
||||
return
|
||||
|
||||
log.info(f"Strompreise geladen: {len(price_data)} Stunden")
|
||||
|
||||
# PV-Prognose laden
|
||||
pv_forecast = get_pv_forecast()
|
||||
pv_today = pv_forecast.get('today', 0)
|
||||
pv_tomorrow = pv_forecast.get('tomorrow', 0)
|
||||
log.info(f"PV-Prognose: Heute {pv_today} kWh, Morgen {pv_tomorrow} kWh")
|
||||
|
||||
# Batterie-Status laden
|
||||
current_soc = float(state.get('sensor.openems_ess0_soc') or 50)
|
||||
log.info(f"Aktueller SOC: {current_soc}%")
|
||||
|
||||
# Optimierung durchführen
|
||||
schedule = optimize_charging(
|
||||
price_data=price_data,
|
||||
pv_forecast=pv_forecast,
|
||||
current_soc=current_soc,
|
||||
config=config
|
||||
)
|
||||
|
||||
# Plan speichern
|
||||
save_schedule(schedule)
|
||||
|
||||
# Statistiken ausgeben
|
||||
log_statistics(schedule, price_data)
|
||||
|
||||
log.info("=== Optimierung abgeschlossen ===")
|
||||
|
||||
except Exception as e:
|
||||
log.error(f"Fehler bei Optimierung: {e}")
|
||||
input_text.battery_optimizer_status = f"Fehler: {str(e)[:100]}"
|
||||
|
||||
|
||||
def load_configuration():
|
||||
"""Lädt alle Konfigurations-Parameter aus Input Helpern"""
|
||||
return {
|
||||
'battery_capacity': float(state.get('input_number.battery_capacity_kwh') or 10) * 1000, # in Wh
|
||||
'min_soc': float(state.get('input_number.battery_optimizer_min_soc') or 20),
|
||||
'max_soc': float(state.get('input_number.battery_optimizer_max_soc') or 100),
|
||||
'max_charge_power': float(state.get('input_number.battery_optimizer_max_charge_power') or 5000),
|
||||
'price_threshold': float(state.get('input_number.battery_optimizer_price_threshold') or 28),
|
||||
'reserve_capacity': float(state.get('input_number.battery_optimizer_reserve_capacity') or 2) * 1000, # in Wh
|
||||
'pv_threshold': float(state.get('input_number.battery_optimizer_pv_threshold') or 500), # in Wh
|
||||
}
|
||||
|
||||
|
||||
def get_electricity_prices():
|
||||
"""
|
||||
Holt Strompreise von haStrom FLEX PRO
|
||||
Erwartet Attribute 'prices_today', 'datetime_today' (und optional tomorrow)
|
||||
"""
|
||||
from datetime import datetime
|
||||
|
||||
price_entity = 'sensor.hastrom_flex_pro'
|
||||
prices_attr = state.getattr(price_entity)
|
||||
|
||||
if not prices_attr:
|
||||
log.error(f"Sensor {price_entity} nicht gefunden")
|
||||
return None
|
||||
|
||||
# Heute
|
||||
prices_today = prices_attr.get('prices_today', [])
|
||||
datetime_today = prices_attr.get('datetime_today', [])
|
||||
|
||||
# Morgen (falls verfügbar)
|
||||
prices_tomorrow = prices_attr.get('prices_tomorrow', [])
|
||||
datetime_tomorrow = prices_attr.get('datetime_tomorrow', [])
|
||||
|
||||
if not prices_today or not datetime_today:
|
||||
log.error(f"Keine Preis-Daten in {price_entity} (prices_today oder datetime_today fehlt)")
|
||||
return None
|
||||
|
||||
if len(prices_today) != len(datetime_today):
|
||||
log.error(f"Preis-Array und DateTime-Array haben unterschiedliche Längen")
|
||||
return None
|
||||
|
||||
# Konvertiere zu einheitlichem Format
|
||||
price_data = []
|
||||
|
||||
# Heute
|
||||
for i, price in enumerate(prices_today):
|
||||
try:
|
||||
# datetime_today enthält Strings wie "2025-11-09 00:00:00"
|
||||
dt_str = datetime_today[i]
|
||||
dt = datetime.strptime(dt_str, "%Y-%m-%d %H:%M:%S")
|
||||
|
||||
price_data.append({
|
||||
'datetime': dt,
|
||||
'hour': dt.hour,
|
||||
'date': dt.date(),
|
||||
'price': float(price)
|
||||
})
|
||||
except Exception as e:
|
||||
log.warning(f"Fehler beim Verarbeiten von Preis {i}: {e}")
|
||||
continue
|
||||
|
||||
# Morgen (falls vorhanden)
|
||||
if prices_tomorrow and datetime_tomorrow and len(prices_tomorrow) == len(datetime_tomorrow):
|
||||
for i, price in enumerate(prices_tomorrow):
|
||||
try:
|
||||
dt_str = datetime_tomorrow[i]
|
||||
dt = datetime.strptime(dt_str, "%Y-%m-%d %H:%M:%S")
|
||||
|
||||
price_data.append({
|
||||
'datetime': dt,
|
||||
'hour': dt.hour,
|
||||
'date': dt.date(),
|
||||
'price': float(price)
|
||||
})
|
||||
except Exception as e:
|
||||
log.warning(f"Fehler beim Verarbeiten von Morgen-Preis {i}: {e}")
|
||||
continue
|
||||
|
||||
return price_data
|
||||
|
||||
|
||||
def get_pv_forecast():
|
||||
"""
|
||||
Holt PV-Prognose von Forecast.Solar (Ost + West Array)
|
||||
"""
|
||||
# Sensor-Namen für deine beiden Arrays
|
||||
sensor_east = 'sensor.energy_production_today'
|
||||
sensor_west = 'sensor.energy_production_today_2'
|
||||
|
||||
sensor_east_tomorrow = 'sensor.energy_production_tomorrow'
|
||||
sensor_west_tomorrow = 'sensor.energy_production_tomorrow_2'
|
||||
|
||||
# Heute
|
||||
east_today_attr = state.getattr(sensor_east) or {}
|
||||
west_today_attr = state.getattr(sensor_west) or {}
|
||||
|
||||
pv_today = (float(east_today_attr.get('wh_period', 0)) +
|
||||
float(west_today_attr.get('wh_period', 0)))
|
||||
|
||||
# Morgen
|
||||
east_tomorrow_attr = state.getattr(sensor_east_tomorrow) or {}
|
||||
west_tomorrow_attr = state.getattr(sensor_west_tomorrow) or {}
|
||||
|
||||
pv_tomorrow = (float(east_tomorrow_attr.get('wh_period', 0)) +
|
||||
float(west_tomorrow_attr.get('wh_period', 0)))
|
||||
|
||||
# Stündliche Werte kombinieren
|
||||
pv_wh_per_hour = {}
|
||||
|
||||
# Ost-Array
|
||||
for hour_str, wh in (east_today_attr.get('wh_hours', {}) or {}).items():
|
||||
pv_wh_per_hour[int(hour_str)] = wh
|
||||
|
||||
# West-Array addieren
|
||||
for hour_str, wh in (west_today_attr.get('wh_hours', {}) or {}).items():
|
||||
hour = int(hour_str)
|
||||
pv_wh_per_hour[hour] = pv_wh_per_hour.get(hour, 0) + wh
|
||||
|
||||
# Morgen-Werte (24+ Stunden)
|
||||
for hour_str, wh in (east_tomorrow_attr.get('wh_hours', {}) or {}).items():
|
||||
hour = int(hour_str) + 24
|
||||
pv_wh_per_hour[hour] = wh
|
||||
|
||||
for hour_str, wh in (west_tomorrow_attr.get('wh_hours', {}) or {}).items():
|
||||
hour = int(hour_str) + 24
|
||||
pv_wh_per_hour[hour] = pv_wh_per_hour.get(hour, 0) + wh
|
||||
|
||||
return {
|
||||
'today': pv_today / 1000, # in kWh
|
||||
'tomorrow': pv_tomorrow / 1000, # in kWh
|
||||
'hourly': pv_wh_per_hour # in Wh per Stunde
|
||||
}
|
||||
|
||||
|
||||
def optimize_charging(price_data, pv_forecast, current_soc, config):
|
||||
"""
|
||||
Kern-Optimierungs-Algorithmus
|
||||
|
||||
Strategie:
|
||||
1. Finde Stunden mit niedrigen Preisen UND wenig PV
|
||||
2. Berechne verfügbare Ladekapazität
|
||||
3. Erstelle Ladeplan für günstigste Stunden
|
||||
"""
|
||||
|
||||
# Berechne dynamischen Preis-Schwellwert (90. Perzentil)
|
||||
all_prices = [p['price'] for p in price_data]
|
||||
all_prices.sort()
|
||||
threshold_index = int(len(all_prices) * 0.9)
|
||||
price_threshold = all_prices[threshold_index] if all_prices else config['price_threshold']
|
||||
|
||||
log.info(f"Preis-Schwellwert: {price_threshold:.2f} ct/kWh")
|
||||
|
||||
# Verfügbare Ladekapazität berechnen
|
||||
available_capacity_wh = (config['max_soc'] - current_soc) / 100 * config['battery_capacity']
|
||||
available_capacity_wh -= config['reserve_capacity'] # Reserve abziehen
|
||||
|
||||
if available_capacity_wh <= 0:
|
||||
log.info("Batterie ist voll oder Reserve erreicht - keine Ladung nötig")
|
||||
# Erstelle Plan nur mit "auto" Einträgen
|
||||
return create_auto_only_schedule(price_data)
|
||||
|
||||
log.info(f"Verfügbare Ladekapazität: {available_capacity_wh/1000:.2f} kWh")
|
||||
|
||||
# Finde günstige Lade-Gelegenheiten
|
||||
charging_opportunities = []
|
||||
|
||||
current_hour = datetime.now().hour
|
||||
current_date = datetime.now().date()
|
||||
|
||||
for price_hour in price_data:
|
||||
hour = price_hour['hour']
|
||||
hour_date = price_hour['date']
|
||||
price = price_hour['price']
|
||||
|
||||
# Berechne absolute Stunde (0-47 für heute+morgen)
|
||||
if hour_date == current_date:
|
||||
abs_hour = hour
|
||||
elif hour_date > current_date:
|
||||
abs_hour = hour + 24
|
||||
else:
|
||||
continue # Vergangenheit ignorieren
|
||||
|
||||
# Nur zukünftige Stunden
|
||||
if abs_hour < current_hour:
|
||||
continue
|
||||
|
||||
# PV-Prognose für diese Stunde
|
||||
pv_wh = pv_forecast['hourly'].get(abs_hour, 0)
|
||||
|
||||
# Kriterien: Günstiger Preis UND wenig PV
|
||||
if price < price_threshold and pv_wh < config['pv_threshold']:
|
||||
charging_opportunities.append({
|
||||
'datetime': price_hour['datetime'],
|
||||
'hour': hour,
|
||||
'abs_hour': abs_hour,
|
||||
'price': price,
|
||||
'pv_wh': pv_wh,
|
||||
'score': price - (pv_wh / 1000) # Je niedriger, desto besser
|
||||
})
|
||||
|
||||
# Sortiere nach Score (beste zuerst)
|
||||
charging_opportunities.sort(key=lambda x: x['score'])
|
||||
|
||||
log.info(f"Gefundene Lade-Gelegenheiten: {len(charging_opportunities)}")
|
||||
|
||||
# Erstelle Ladeplan
|
||||
schedule = []
|
||||
remaining_capacity = available_capacity_wh
|
||||
total_charge_energy = 0
|
||||
total_charge_cost = 0
|
||||
|
||||
for price_hour in price_data:
|
||||
hour = price_hour['hour']
|
||||
abs_hour = price_hour['hour']
|
||||
hour_date = price_hour['date']
|
||||
|
||||
# Berechne absolute Stunde
|
||||
if hour_date == current_date:
|
||||
abs_hour = hour
|
||||
elif hour_date > current_date:
|
||||
abs_hour = hour + 24
|
||||
else:
|
||||
continue
|
||||
|
||||
# Nur zukünftige Stunden (inkl. aktuelle!)
|
||||
if abs_hour < current_hour:
|
||||
continue
|
||||
|
||||
# Prüfe ob diese Stunde zum Laden vorgesehen ist
|
||||
should_charge = False
|
||||
charge_opportunity = None
|
||||
|
||||
for opp in charging_opportunities:
|
||||
if opp['abs_hour'] == abs_hour:
|
||||
should_charge = True
|
||||
charge_opportunity = opp
|
||||
break
|
||||
|
||||
if should_charge and remaining_capacity > 0:
|
||||
# Ladeleistung berechnen
|
||||
charge_wh = min(config['max_charge_power'], remaining_capacity)
|
||||
|
||||
schedule.append({
|
||||
'datetime': price_hour['datetime'].isoformat(),
|
||||
'hour': hour,
|
||||
'action': 'charge',
|
||||
'power_w': -int(charge_wh), # Negativ = Laden
|
||||
'price': price_hour['price'],
|
||||
'pv_wh': charge_opportunity['pv_wh'],
|
||||
'reason': f"Günstig ({price_hour['price']:.2f} ct) + wenig PV ({charge_opportunity['pv_wh']} Wh)"
|
||||
})
|
||||
|
||||
remaining_capacity -= charge_wh
|
||||
total_charge_energy += charge_wh / 1000 # kWh
|
||||
total_charge_cost += (charge_wh / 1000) * (price_hour['price'] / 100) # Euro
|
||||
else:
|
||||
# Auto-Modus (Standard-Betrieb)
|
||||
reason = "Automatik"
|
||||
if not should_charge and abs_hour < current_hour + 24:
|
||||
if price_hour['price'] >= price_threshold:
|
||||
reason = f"Preis zu hoch ({price_hour['price']:.2f} > {price_threshold:.2f} ct)"
|
||||
else:
|
||||
pv_wh = pv_forecast['hourly'].get(abs_hour, 0)
|
||||
if pv_wh >= config['pv_threshold']:
|
||||
reason = f"Genug PV ({pv_wh} Wh)"
|
||||
|
||||
schedule.append({
|
||||
'datetime': price_hour['datetime'].isoformat(),
|
||||
'hour': hour,
|
||||
'action': 'auto',
|
||||
'power_w': 0,
|
||||
'price': price_hour['price'],
|
||||
'reason': reason
|
||||
})
|
||||
|
||||
# Berechne Anzahl Ladungen für Log
|
||||
num_charges = 0
|
||||
for s in schedule:
|
||||
if s['action'] == 'charge':
|
||||
num_charges += 1
|
||||
|
||||
log.info(f"Ladeplan erstellt: {len(schedule)} Stunden, davon {num_charges} Ladungen")
|
||||
log.info(f"Gesamte Ladeenergie: {total_charge_energy:.2f} kWh, Kosten: {total_charge_cost:.2f} €")
|
||||
|
||||
return schedule
|
||||
|
||||
|
||||
def create_auto_only_schedule(price_data):
|
||||
"""Erstellt einen Plan nur mit Auto-Modus (keine Ladung)"""
|
||||
schedule = []
|
||||
current_hour = datetime.now().hour
|
||||
|
||||
for price_hour in price_data:
|
||||
if price_hour['hour'] >= current_hour:
|
||||
schedule.append({
|
||||
'datetime': price_hour['datetime'].isoformat(),
|
||||
'hour': price_hour['hour'],
|
||||
'action': 'auto',
|
||||
'power_w': 0,
|
||||
'price': price_hour['price'],
|
||||
'reason': "Keine Ladung nötig (Batterie voll)"
|
||||
})
|
||||
|
||||
return schedule
|
||||
|
||||
|
||||
def save_schedule(schedule):
|
||||
"""
|
||||
Speichert den Schedule als PyScript State mit Attributen
|
||||
"""
|
||||
if not schedule:
|
||||
log.warning("Leerer Schedule - nichts zu speichern")
|
||||
return
|
||||
|
||||
# Berechne Statistiken
|
||||
num_charges = 0
|
||||
total_energy = 0
|
||||
total_price = 0
|
||||
first_charge = None
|
||||
|
||||
for s in schedule:
|
||||
if s['action'] == 'charge':
|
||||
num_charges += 1
|
||||
total_energy += abs(s['power_w'])
|
||||
total_price += s['price']
|
||||
if first_charge is None:
|
||||
first_charge = s['datetime']
|
||||
|
||||
total_energy = total_energy / 1000 # kWh
|
||||
avg_price = total_price / num_charges if num_charges > 0 else 0
|
||||
|
||||
# Speichere als PyScript State
|
||||
state.set(
|
||||
'pyscript.battery_charging_schedule',
|
||||
value='active',
|
||||
new_attributes={
|
||||
'schedule': schedule,
|
||||
'last_update': datetime.now().isoformat(),
|
||||
'num_hours': len(schedule),
|
||||
'num_charges': num_charges,
|
||||
'total_energy_kwh': round(total_energy, 2),
|
||||
'avg_charge_price': round(avg_price, 2),
|
||||
'first_charge_time': first_charge,
|
||||
'estimated_savings': 0 # Wird später berechnet
|
||||
}
|
||||
)
|
||||
|
||||
log.info(f"Ladeplan gespeichert: {len(schedule)} Stunden als Attribut")
|
||||
|
||||
# Status aktualisieren
|
||||
if num_charges > 0:
|
||||
input_text.battery_optimizer_status = f"{num_charges} Ladungen geplant, ab {first_charge}"
|
||||
else:
|
||||
input_text.battery_optimizer_status = "Keine Ladung nötig"
|
||||
|
||||
|
||||
def log_statistics(schedule, price_data):
|
||||
"""Gibt Statistiken über den erstellten Plan aus"""
|
||||
# Filter charges
|
||||
charges = []
|
||||
for s in schedule:
|
||||
if s['action'] == 'charge':
|
||||
charges.append(s)
|
||||
|
||||
if not charges:
|
||||
log.info("Keine Ladungen geplant")
|
||||
return
|
||||
|
||||
total_energy = 0
|
||||
total_price = 0
|
||||
for s in charges:
|
||||
total_energy += abs(s['power_w'])
|
||||
total_price += s['price']
|
||||
|
||||
total_energy = total_energy / 1000 # kWh
|
||||
avg_price = total_price / len(charges)
|
||||
first_charge = charges[0]['datetime']
|
||||
|
||||
log.info(f"Geplante Ladungen: {len(charges)} Stunden")
|
||||
log.info(f"Gesamte Lademenge: {total_energy:.1f} kWh")
|
||||
log.info(f"Durchschnittspreis beim Laden: {avg_price:.2f} ct/kWh")
|
||||
log.info(f"Erste Ladung: {first_charge}")
|
||||
|
||||
|
||||
@service
|
||||
def execute_charging_schedule():
|
||||
"""
|
||||
Führt den aktuellen Ladeplan aus (stündlich aufgerufen)
|
||||
Nutzt das bestehende manuelle Steuerungs-System
|
||||
"""
|
||||
|
||||
# Prüfe ob Optimierung aktiv ist
|
||||
if state.get('input_boolean.battery_optimizer_enabled') != 'on':
|
||||
return
|
||||
|
||||
# Prüfe auf manuelle Überschreibung
|
||||
if state.get('input_boolean.battery_optimizer_manual_override') == 'on':
|
||||
log.info("Manuelle Überschreibung aktiv - überspringe Ausführung")
|
||||
return
|
||||
|
||||
# Lade Schedule
|
||||
schedule_attr = state.getattr('pyscript.battery_charging_schedule')
|
||||
if not schedule_attr or 'schedule' not in schedule_attr:
|
||||
log.warning("Kein Ladeplan vorhanden")
|
||||
return
|
||||
|
||||
schedule = schedule_attr['schedule']
|
||||
|
||||
# Aktuelle Stunde ermitteln
|
||||
now = datetime.now()
|
||||
current_hour_dt = now.replace(minute=0, second=0, microsecond=0)
|
||||
|
||||
log.info(f"Suche Ladeplan für Stunde: {current_hour_dt.isoformat()}")
|
||||
|
||||
# Finde passenden Eintrag im Schedule
|
||||
current_entry = None
|
||||
for entry in schedule:
|
||||
entry_dt = datetime.fromisoformat(entry['datetime'])
|
||||
entry_hour = entry_dt.replace(minute=0, second=0, microsecond=0)
|
||||
|
||||
# Prüfe ob diese Stunde passt (mit 30 Min Toleranz)
|
||||
time_diff = abs((entry_hour - current_hour_dt).total_seconds() / 60)
|
||||
|
||||
if time_diff < 30: # Innerhalb 30 Minuten
|
||||
current_entry = entry
|
||||
log.info(f"Gefunden: {entry_dt.isoformat()} (Abweichung: {time_diff:.0f} min)")
|
||||
break
|
||||
|
||||
if not current_entry:
|
||||
log.warning(f"Keine Daten für aktuelle Stunde {current_hour_dt.hour}:00")
|
||||
return
|
||||
|
||||
# Führe Aktion aus
|
||||
action = current_entry['action']
|
||||
power_w = current_entry['power_w']
|
||||
price = current_entry['price']
|
||||
reason = current_entry.get('reason', '')
|
||||
|
||||
log.info(f"Stunde {current_hour_dt.isoformat()}: Aktion={action}, Leistung={power_w}W, Preis={price:.2f} ct")
|
||||
log.info(f"Grund: {reason}")
|
||||
|
||||
if action == 'charge':
|
||||
# Aktiviere Laden über bestehendes System
|
||||
log.info(f"Aktiviere Laden mit {power_w}W")
|
||||
|
||||
# Setze Ziel-Leistung
|
||||
input_number.charge_power_battery = float(power_w)
|
||||
|
||||
# Aktiviere manuellen Modus (triggert deine Automationen)
|
||||
input_boolean.goodwe_manual_control = "on"
|
||||
|
||||
log.info("Manuelles Laden aktiviert")
|
||||
|
||||
elif action == 'auto':
|
||||
# Deaktiviere manuelles Laden, zurück zu Auto-Modus
|
||||
if state.get('input_boolean.goodwe_manual_control') == 'on':
|
||||
log.info("Deaktiviere manuelles Laden, aktiviere Auto-Modus")
|
||||
input_boolean.goodwe_manual_control = "off"
|
||||
else:
|
||||
log.info("Auto-Modus bereits aktiv")
|
||||
|
||||
|
||||
# ====================
|
||||
# Zeit-Trigger
|
||||
# ====================
|
||||
|
||||
@time_trigger("cron(5 14 * * *)")
|
||||
def daily_optimization():
|
||||
"""Tägliche Berechnung um 14:05 Uhr (nach haStrom Preis-Update)"""
|
||||
log.info("=== Tägliche Optimierungs-Berechnung gestartet ===")
|
||||
pyscript.calculate_charging_schedule()
|
||||
|
||||
|
||||
@time_trigger("cron(5 * * * *)")
|
||||
def hourly_execution():
|
||||
"""Stündliche Ausführung des Plans um xx:05 Uhr"""
|
||||
pyscript.execute_charging_schedule()
|
||||
188
legacy/v2/battery_optimizer_config.yaml
Normal file
188
legacy/v2/battery_optimizer_config.yaml
Normal file
@@ -0,0 +1,188 @@
|
||||
# ============================================
|
||||
# Battery Charging Optimizer - Konfiguration
|
||||
# ============================================
|
||||
# Speicherort: /config/packages/battery_optimizer_config.yaml
|
||||
# oder in configuration.yaml unter entsprechenden Sektionen
|
||||
|
||||
# ====================
|
||||
# Input Boolean
|
||||
# ====================
|
||||
input_boolean:
|
||||
battery_optimizer_enabled:
|
||||
name: "Batterie-Optimierung aktiviert"
|
||||
icon: mdi:battery-charging-wireless
|
||||
|
||||
battery_optimizer_manual_override:
|
||||
name: "Manuelle Überschreibung"
|
||||
icon: mdi:hand-back-right
|
||||
|
||||
# ====================
|
||||
# Input Number
|
||||
# ====================
|
||||
input_number:
|
||||
# Batterie-Parameter
|
||||
battery_capacity_kwh:
|
||||
name: "Batterie-Kapazität"
|
||||
min: 1
|
||||
max: 50
|
||||
step: 0.5
|
||||
unit_of_measurement: "kWh"
|
||||
icon: mdi:battery
|
||||
mode: box
|
||||
initial: 10
|
||||
|
||||
battery_optimizer_min_soc:
|
||||
name: "Minimaler SOC"
|
||||
min: 0
|
||||
max: 50
|
||||
step: 5
|
||||
unit_of_measurement: "%"
|
||||
icon: mdi:battery-low
|
||||
mode: slider
|
||||
initial: 20
|
||||
|
||||
battery_optimizer_max_soc:
|
||||
name: "Maximaler SOC"
|
||||
min: 50
|
||||
max: 100
|
||||
step: 5
|
||||
unit_of_measurement: "%"
|
||||
icon: mdi:battery-high
|
||||
mode: slider
|
||||
initial: 100
|
||||
|
||||
battery_optimizer_max_charge_power:
|
||||
name: "Max. Ladeleistung"
|
||||
min: 1000
|
||||
max: 10000
|
||||
step: 500
|
||||
unit_of_measurement: "W"
|
||||
icon: mdi:lightning-bolt
|
||||
mode: box
|
||||
initial: 5000
|
||||
|
||||
# Optimierungs-Parameter
|
||||
battery_optimizer_price_threshold:
|
||||
name: "Preis-Schwellwert"
|
||||
min: 0
|
||||
max: 50
|
||||
step: 0.5
|
||||
unit_of_measurement: "ct/kWh"
|
||||
icon: mdi:currency-eur
|
||||
mode: box
|
||||
initial: 28
|
||||
|
||||
battery_optimizer_reserve_capacity:
|
||||
name: "Reserve-Kapazität (Haushalt)"
|
||||
min: 0
|
||||
max: 5
|
||||
step: 0.5
|
||||
unit_of_measurement: "kWh"
|
||||
icon: mdi:home-lightning-bolt
|
||||
mode: box
|
||||
initial: 2
|
||||
|
||||
battery_optimizer_pv_threshold:
|
||||
name: "PV-Schwellwert (keine Ladung)"
|
||||
min: 0
|
||||
max: 5000
|
||||
step: 100
|
||||
unit_of_measurement: "Wh"
|
||||
icon: mdi:solar-power
|
||||
mode: box
|
||||
initial: 500
|
||||
|
||||
# ====================
|
||||
# Input Text
|
||||
# ====================
|
||||
input_text:
|
||||
battery_optimizer_status:
|
||||
name: "Optimierungs-Status"
|
||||
max: 255
|
||||
icon: mdi:information-outline
|
||||
|
||||
# ====================
|
||||
# Sensor Templates
|
||||
# ====================
|
||||
template:
|
||||
- sensor:
|
||||
# Aktueller Ladeplan-Status
|
||||
- name: "Batterie Ladeplan Status"
|
||||
unique_id: battery_charging_plan_status
|
||||
state: >
|
||||
{% set schedule = state_attr('pyscript.battery_charging_schedule', 'schedule') %}
|
||||
{% if schedule %}
|
||||
{% set num_charges = schedule | selectattr('action', 'eq', 'charge') | list | count %}
|
||||
{{ num_charges }} Ladungen geplant
|
||||
{% else %}
|
||||
Kein Plan
|
||||
{% endif %}
|
||||
icon: mdi:calendar-clock
|
||||
attributes:
|
||||
last_update: >
|
||||
{{ state_attr('pyscript.battery_charging_schedule', 'last_update') }}
|
||||
total_hours: >
|
||||
{{ state_attr('pyscript.battery_charging_schedule', 'num_hours') }}
|
||||
next_charge: >
|
||||
{% set schedule = state_attr('pyscript.battery_charging_schedule', 'schedule') %}
|
||||
{% if schedule %}
|
||||
{% set charges = schedule | selectattr('action', 'eq', 'charge') | list %}
|
||||
{% if charges | count > 0 %}
|
||||
{{ charges[0].hour }}:00 Uhr ({{ charges[0].price }} ct/kWh)
|
||||
{% else %}
|
||||
Keine Ladung geplant
|
||||
{% endif %}
|
||||
{% else %}
|
||||
Kein Plan vorhanden
|
||||
{% endif %}
|
||||
|
||||
# Nächste geplante Aktion
|
||||
- name: "Batterie Nächste Aktion"
|
||||
unique_id: battery_next_action
|
||||
state: >
|
||||
{% set schedule = state_attr('pyscript.battery_charging_schedule', 'schedule') %}
|
||||
{% if schedule %}
|
||||
{% set now_hour = now().hour %}
|
||||
{% set future = schedule | selectattr('hour', 'ge', now_hour) | list %}
|
||||
{% if future | count > 0 %}
|
||||
{{ future[0].hour }}:00 - {{ future[0].action }}
|
||||
{% else %}
|
||||
Keine weiteren Aktionen heute
|
||||
{% endif %}
|
||||
{% else %}
|
||||
Kein Plan
|
||||
{% endif %}
|
||||
icon: mdi:clock-outline
|
||||
attributes:
|
||||
power: >
|
||||
{% set schedule = state_attr('pyscript.battery_charging_schedule', 'schedule') %}
|
||||
{% if schedule %}
|
||||
{% set now_hour = now().hour %}
|
||||
{% set future = schedule | selectattr('hour', 'ge', now_hour) | list %}
|
||||
{% if future | count > 0 %}
|
||||
{{ future[0].power_w }} W
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
price: >
|
||||
{% set schedule = state_attr('pyscript.battery_charging_schedule', 'schedule') %}
|
||||
{% if schedule %}
|
||||
{% set now_hour = now().hour %}
|
||||
{% set future = schedule | selectattr('hour', 'ge', now_hour) | list %}
|
||||
{% if future | count > 0 %}
|
||||
{{ future[0].price }} ct/kWh
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
# Geschätzte Ersparnis
|
||||
- name: "Batterie Geschätzte Ersparnis"
|
||||
unique_id: battery_estimated_savings
|
||||
state: >
|
||||
{% set schedule = state_attr('pyscript.battery_charging_schedule', 'schedule') %}
|
||||
{% if schedule %}
|
||||
{{ state_attr('pyscript.battery_charging_schedule', 'estimated_savings') | float(0) | round(2) }}
|
||||
{% else %}
|
||||
0
|
||||
{% endif %}
|
||||
unit_of_measurement: "€"
|
||||
device_class: monetary
|
||||
icon: mdi:piggy-bank
|
||||
113
legacy/v2/battery_optimizer_dashboard.yaml
Normal file
113
legacy/v2/battery_optimizer_dashboard.yaml
Normal file
@@ -0,0 +1,113 @@
|
||||
# ============================================
|
||||
# Battery Charging Optimizer - Dashboard
|
||||
# ============================================
|
||||
# Füge diese Cards zu deinem Lovelace Dashboard hinzu
|
||||
|
||||
type: vertical-stack
|
||||
cards:
|
||||
# Status-Übersicht
|
||||
- type: entities
|
||||
title: Batterie-Optimierung Status
|
||||
show_header_toggle: false
|
||||
entities:
|
||||
- entity: input_boolean.battery_optimizer_enabled
|
||||
name: Optimierung aktiviert
|
||||
- entity: input_boolean.battery_optimizer_manual_override
|
||||
name: Manuelle Überschreibung
|
||||
- entity: input_text.battery_optimizer_status
|
||||
name: Status
|
||||
|
||||
# Manuelle Steuerung (dein bestehendes System)
|
||||
- type: entities
|
||||
title: Manuelle Steuerung
|
||||
show_header_toggle: false
|
||||
entities:
|
||||
- entity: input_boolean.goodwe_manual_control
|
||||
name: Manueller Modus
|
||||
- entity: input_number.charge_power_battery
|
||||
name: Ladeleistung
|
||||
- type: divider
|
||||
- entity: sensor.esssoc
|
||||
name: Batterie SOC
|
||||
- entity: sensor.battery_power
|
||||
name: Batterie Leistung
|
||||
|
||||
# Konfiguration
|
||||
- type: entities
|
||||
title: Optimierungs-Einstellungen
|
||||
show_header_toggle: false
|
||||
entities:
|
||||
- entity: input_number.battery_capacity_kwh
|
||||
name: Batterie-Kapazität
|
||||
- entity: input_number.battery_optimizer_min_soc
|
||||
name: Min. SOC
|
||||
- entity: input_number.battery_optimizer_max_soc
|
||||
name: Max. SOC
|
||||
- entity: input_number.battery_optimizer_max_charge_power
|
||||
name: Max. Ladeleistung
|
||||
- type: divider
|
||||
- entity: input_number.battery_optimizer_price_threshold
|
||||
name: Preis-Schwellwert
|
||||
- entity: input_number.battery_optimizer_reserve_capacity
|
||||
name: Reserve-Kapazität
|
||||
- entity: input_number.battery_optimizer_pv_threshold
|
||||
name: PV-Schwellwert
|
||||
|
||||
# Aktionen
|
||||
- type: entities
|
||||
title: Aktionen
|
||||
show_header_toggle: false
|
||||
entities:
|
||||
- type: button
|
||||
name: Plan neu berechnen
|
||||
icon: mdi:refresh
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: pyscript.calculate_charging_schedule
|
||||
- type: button
|
||||
name: Plan jetzt ausführen
|
||||
icon: mdi:play
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: pyscript.execute_charging_schedule
|
||||
|
||||
# Ladeplan-Tabelle
|
||||
- type: markdown
|
||||
title: Geplante Ladungen (nächste 24h)
|
||||
content: >
|
||||
{% set schedule = state_attr('pyscript.battery_charging_schedule', 'schedule') %}
|
||||
{% if schedule %}
|
||||
{% set charges = schedule | selectattr('action', 'eq', 'charge') | list %}
|
||||
{% if charges | count > 0 %}
|
||||
| Zeit | Leistung | Preis | Grund |
|
||||
|------|----------|-------|-------|
|
||||
{% for charge in charges[:10] %}
|
||||
| {{ charge.datetime[11:16] }} | {{ charge.power_w }}W | {{ charge.price }}ct | {{ charge.reason }} |
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
*Keine Ladungen geplant*
|
||||
{% endif %}
|
||||
{% else %}
|
||||
*Kein Plan vorhanden - bitte neu berechnen*
|
||||
{% endif %}
|
||||
|
||||
# Statistiken
|
||||
- type: markdown
|
||||
title: Plan-Statistiken
|
||||
content: >
|
||||
{% set attrs = state_attr('pyscript.battery_charging_schedule', 'schedule') %}
|
||||
{% if attrs %}
|
||||
**Letzte Aktualisierung:** {{ state_attr('pyscript.battery_charging_schedule', 'last_update') }}
|
||||
|
||||
**Anzahl Stunden:** {{ state_attr('pyscript.battery_charging_schedule', 'num_hours') }}
|
||||
|
||||
**Geplante Ladungen:** {{ state_attr('pyscript.battery_charging_schedule', 'num_charges') }}
|
||||
|
||||
**Gesamtenergie:** {{ state_attr('pyscript.battery_charging_schedule', 'total_energy_kwh') }} kWh
|
||||
|
||||
**Durchschnittspreis:** {{ state_attr('pyscript.battery_charging_schedule', 'avg_charge_price') }} ct/kWh
|
||||
|
||||
**Erste Ladung:** {{ state_attr('pyscript.battery_charging_schedule', 'first_charge_time') }}
|
||||
{% else %}
|
||||
*Keine Statistiken verfügbar*
|
||||
{% endif %}
|
||||
Reference in New Issue
Block a user