feat: Major update - Battery Optimizer v3.4.0 with comprehensive fixes

## 🎯 Hauptänderungen

### Version 3.4.0 - SOC-Drift & Charging Capacity
-  Sicherheitspuffer (20-50% konfigurierbar) für untertägige SOC-Schwankungen
-  Monatliche automatische Batterie-Kalibrierung
- 🐛 SOC-Plausibilitäts-Check (filtert 65535% Spikes beim Modus-Wechsel)
- 🐛 Zeitabhängige API-Abfrage (vor/nach 14:00 Uhr)

### Neue Features
- 🔋 **Safety Buffer**: Kompensiert SOC-Drift und Eigenverbrauch
- 🔋 **Auto-Calibration**: Monatlicher Vollzyklus für SOC-Genauigkeit
- 🔋 **Spike Protection**: 4-fach Schutz gegen ungültige SOC-Werte
- 🔋 **Smart API**: Verhindert HTTP 500 Errors bei fehlenden Tomorrow-Preisen

### Dokumentation
- 📚 SOC_CALIBRATION_GUIDE.md - Umfassender Kalibrierungs-Guide
- 📚 FIX_CHARGING_CAPACITY.md - Sicherheitspuffer-Dokumentation
- 📚 FIX_SOC_SPIKE_PROBLEM.md - Spike-Protection-Lösung
- 📚 FIX_API_TIMING.md - Zeitabhängige API-Abfrage
- 📚 DIAGNOSE_LADE_PROBLEM.md - Debug-Guide

### Neue Dateien
- battery_calibration_automation.yaml - 4 Automations für Kalibrierung
- battery_calibration_input_helper.yaml - Input Helper Config
- battery_optimizer_input_helper_safety_buffer.yaml - Puffer Config
- debug_schedule.py - Umfassendes Debug-Script

### Scripts
- battery_charging_optimizer.py v3.4.0
- hastrom_flex_extended.py v1.1.0
- debug_schedule.py v1.0.0

### Fixes
- 🐛 SOC springt auf 65535% beim ESS-Modus-Wechsel → Debounce + Plausibilitäts-Check
- 🐛 API-HTTP-500 vor 14:00 → Zeitabhängige Abfrage
- 🐛 Batterie nicht bis 100% geladen → Sicherheitspuffer
- 🐛 SOC driftet ohne Vollzyklen → Automatische Kalibrierung

## 🚀 Installation

1. Input Helper erstellen (siehe battery_optimizer_input_helper_safety_buffer.yaml)
2. Automations installieren (siehe battery_calibration_automation.yaml)
3. Scripts aktualisieren (battery_charging_optimizer.py v3.4.0)
4. PyScript neu laden

## 📊 Verbesserungen

- Präzisere Ladeplanung durch Sicherheitspuffer
- Robustheit gegen SOC-Drift
- Keine API-Fehler mehr vor 14:00
- Hardware-Stopp bei 100% wird respektiert
- Bessere Batterie-Gesundheit durch regelmäßige Kalibrierung

🤖 Generated with Claude Code (claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
felix.zoesch
2025-12-12 08:04:07 +01:00
parent 5ab422426f
commit 0fa03a566a
90 changed files with 22002 additions and 0 deletions

View File

@@ -0,0 +1,255 @@
# Diagnose: Battery Charging nicht funktioniert
## Problem-Beschreibung
Heute Nacht wurde nicht geladen, obwohl:
- Der Speicher von INTERNAL auf REMOTE geschaltet wurde
- `goodwe_manual_control` aktiviert wurde
- Nach ein paar Sekunden wurde es wieder deaktiviert
## Wahrscheinliche Ursachen
### 1. Stündliche Ausführung stoppt das Laden
**Problem**: Die Automation "Batterie Optimierung: Stündliche Ausführung" läuft **jede Stunde** um xx:05 und prüft den Schedule für die aktuelle Stunde.
**Verhalten**:
- Stunde mit `action='charge'` → Laden wird aktiviert
- Stunde mit `action='auto'` → Laden wird deaktiviert
**Szenario**:
```
23:05 → Schedule sagt "charge" → Laden startet
00:05 → Schedule sagt "auto" → Laden stoppt
```
**Mögliche Ursachen**:
- Schedule enthält nur 1 Ladestunde statt mehrere
- Zeitzone-Problem: Falsche Stunde wird geprüft
- Tomorrow-Daten fehlen im Schedule
### 2. Schedule wurde nicht richtig erstellt
**Problem**: Die tägliche Berechnung um 14:05 hat keinen sinnvollen Ladeplan erstellt.
**Mögliche Ursachen**:
- Batterie war schon voll (kein Ladebedarf)
- Tomorrow-Preise waren noch nicht verfügbar
- Alle Nachtstunden waren nicht unter den N günstigsten
### 3. Zeitzone-Problem in execute_charging_schedule
**Problem**: Der Vergleich zwischen aktueller Zeit und Schedule-Einträgen schlägt fehl.
Code in Zeile 593-606:
```python
entry_dt = datetime.fromisoformat(entry['datetime'])
if entry_dt.tzinfo is None:
entry_dt = entry_dt.replace(tzinfo=TIMEZONE)
entry_date = entry_dt.date()
entry_hour = entry_dt.hour
if entry_date == current_date and entry_hour == current_hour:
current_entry = entry
```
**Mögliches Problem**: Die Datetimes im Schedule sind nicht korrekt timezone-aware.
## Debug-Schritte
### Schritt 1: Schedule prüfen
Gehe zu **Developer Tools → States** und suche nach:
```
pyscript.battery_charging_schedule
```
**Was zu prüfen**:
1. Wann wurde `last_update` zuletzt aktualisiert?
2. Wie viele `num_charges` sind geplant?
3. Wie viele `num_charges_tomorrow` für die Nacht?
4. Schau dir das `schedule` Array in den Attributen an
**Interpretation**:
- `num_charges = 0` → Keine Ladungen geplant (Batterie war voll?)
- `num_charges_tomorrow = 0` → Keine Nachtstunden geplant
- `has_tomorrow_data = false` → Morgen-Preise fehlen
### Schritt 2: Home Assistant Logs prüfen
Öffne die Home Assistant Logs und suche nach PyScript-Ausgaben:
**Für die tägliche Berechnung** (sollte um 14:05 laufen):
```
=== Batterie-Optimierung gestartet (v3.2 - FIXED Timezones) ===
Konfiguration geladen: SOC 20-100%, Max 5000W
Strompreise geladen: X Stunden (Tomorrow: true/false)
Benötigte Ladestunden: X (bei 5000W pro Stunde)
Top X günstigste Stunden ausgewählt
```
**Für die stündliche Ausführung** (läuft jede Stunde um xx:05):
```
Suche Ladeplan für YYYY-MM-DD HH:00 Uhr (lokal)
✓ Gefunden: YYYY-MM-DDTHH:00:00+01:00
⚡ Stunde HH:00 [heute/morgen]: action=charge/auto, power=XW, price=X.XXct
```
**Kritische Warnungen zu suchen**:
```
⚠ Keine Daten für YYYY-MM-DD HH:00
Keine zukünftigen Preise verfügbar
```
### Schritt 3: Zeitzone-Verifikation
Führe manuell den Schedule aus und beobachte die Logs:
**Developer Tools → Services**:
```yaml
service: pyscript.execute_charging_schedule
data: {}
```
**Was zu beobachten**:
1. Welche Zeit wird gesucht? "Suche Ladeplan für ..."
2. Wird ein Eintrag gefunden? "✓ Gefunden: ..."
3. Welche Aktion wird ausgeführt? "action=charge" oder "action=auto"
### Schritt 4: Manuellen Test durchführen
Um zu verifizieren, dass das Laden grundsätzlich funktioniert:
**Developer Tools → Services**:
```yaml
service: input_number.set_value
target:
entity_id: input_number.charge_power_battery
data:
value: -5000
```
Dann:
```yaml
service: input_boolean.turn_on
target:
entity_id: input_boolean.goodwe_manual_control
```
**Erwartetes Verhalten**:
1. ESS schaltet auf REMOTE
2. Keep-Alive Automation startet (alle 30s)
3. Batterie beginnt zu laden
**Wenn das funktioniert**: Problem liegt im Schedule/Optimizer
**Wenn das nicht funktioniert**: Problem liegt in den Automations/OpenEMS
### Schritt 5: Schedule-Berechnung triggern
Führe eine neue Berechnung aus:
**Developer Tools → Services**:
```yaml
service: pyscript.calculate_charging_schedule
data: {}
```
Dann prüfe:
1. Die Logs für die Berechnung
2. Den neuen Schedule in `pyscript.battery_charging_schedule`
3. Ob Ladestunden für heute Nacht geplant sind
## Typische Probleme und Lösungen
### Problem A: "Keine Ladung nötig (Batterie voll)"
**Symptom**: `num_charges = 0` im Schedule
**Ursache**: `current_soc >= max_soc - reserve`
**Lösung**: Normal, wenn Batterie voll ist. Warte bis SOC sinkt.
### Problem B: "Tomorrow-Daten fehlen"
**Symptom**: `has_tomorrow_data = false`, nur wenige Stunden im Schedule
**Ursache**: Berechnung lief vor 14:00, als Preise für morgen noch nicht verfügbar waren
**Lösung**: Warte bis nach 14:00, dann läuft automatische Neuberechnung
### Problem C: "Zeitzone-Mismatch"
**Symptom**: "⚠ Keine Daten für YYYY-MM-DD HH:00" obwohl Schedule existiert
**Ursache**: Zeitvergleich matcht nicht
**Lösung**: Siehe Code-Fix unten
### Problem D: "Keep-Alive Automation läuft nicht"
**Symptom**: ESS ist auf REMOTE, aber keine Modbus-Befehle werden gesendet
**Ursache**: Automation "Speicher manuell laden" ist deaktiviert oder fehlerhaft
**Lösung**: Prüfe ob Automation aktiviert ist und läuft
## Code-Verbesserungsvorschläge
### Fix 1: Logging verbessern in execute_charging_schedule
Füge mehr Debug-Output hinzu in Zeile 587:
```python
log.info(f"=== Stündliche Ausführung gestartet ===")
log.info(f"Lokale Zeit: {now.strftime('%Y-%m-%d %H:%M:%S %Z')}")
log.info(f"Suche Ladeplan für {current_date} {current_hour}:00 Uhr (lokal)")
log.info(f"Schedule hat {len(schedule)} Einträge")
# Zeige alle Schedule-Einträge für Debugging
for i, entry in enumerate(schedule):
entry_dt = datetime.fromisoformat(entry['datetime'])
if entry_dt.tzinfo is None:
entry_dt = entry_dt.replace(tzinfo=TIMEZONE)
log.debug(f" [{i}] {entry_dt}{entry['action']}")
```
### Fix 2: Robusteres Datetime-Matching
Ersetze den exakten Stunden-Match durch ein Zeitfenster (Zeile 603):
```python
# Statt exaktem Match:
if entry_date == current_date and entry_hour == current_hour:
# Verwende Zeitfenster (z.B. ±5 Minuten):
entry_start = entry_dt.replace(minute=0, second=0)
entry_end = entry_start + timedelta(hours=1)
if entry_start <= now < entry_end:
current_entry = entry
log.info(f"✓ Match gefunden für Zeitfenster {entry_start} - {entry_end}")
break
```
### Fix 3: Fallback für fehlende Matches
Nach der Schleife (Zeile 608):
```python
if not current_entry:
log.warning(f"⚠ Keine Daten für {current_date} {current_hour}:00")
# Debug: Zeige nächsten verfügbaren Eintrag
future_entries = []
for entry in schedule:
entry_dt = datetime.fromisoformat(entry['datetime'])
if entry_dt.tzinfo is None:
entry_dt = entry_dt.replace(tzinfo=TIMEZONE)
if entry_dt > now:
future_entries.append((entry_dt, entry['action']))
if future_entries:
next_entry = min(future_entries, key=lambda x: x[0])
log.info(f" Nächster Schedule-Eintrag: {next_entry[0]}{next_entry[1]}")
else:
log.warning("⚠ Keine zukünftigen Schedule-Einträge gefunden!")
return
```
## Nächste Schritte
1. **JETZT**: Führe Debug-Schritte 1-3 aus und notiere die Ergebnisse
2. **Prüfe**: Wie sieht der aktuelle Schedule aus?
3. **Teste**: Funktioniert manuelles Laden (Schritt 4)?
4. **Entscheide**: Basierend auf den Ergebnissen, welche Fix-Strategie anzuwenden ist
## Monitoring für die nächste Nacht
Um zu sehen, was heute Nacht passiert:
1. **Prüfe Schedule um 14:05**: Nach der täglichen Berechnung
2. **Setze Benachrichtigung**: Für 23:05 (erste mögliche Ladestunde)
3. **Überwache Logs**: Live während der Ladestunden
4. **Prüfe OpenEMS**: Logs auf dem BeagleBone für Controller-Aktivität
```bash
# Auf BeagleBone
tail -f /var/log/openems/openems.log | grep -i "active.*power"
```