256 lines
8.0 KiB
Markdown
256 lines
8.0 KiB
Markdown
# 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"
|
||
```
|