Files
battery-charging-optimizer/legacy/v1/BUGFIX_v1.2.1_hourly_execution.md

8.9 KiB

🐛 BUGFIX v1.2.1: Stündliche Ausführung funktioniert jetzt!

Das Problem

Symptom: Ladeplan wurde erstellt, aber Batterie lud nicht zur geplanten Zeit.

Log-Meldung:

Keine Daten für aktuelle Stunde 2025-11-09T11:00:00

Was passierte:

  • Plan um 00:10 erstellt mit Ladungen um 03:00 und 04:00 Uhr
  • Stündliche Automatisierung lief um 03:05 und 04:05 Uhr
  • Aber: Plan-Einträge wurden nicht gefunden!
  • Folge: Batterie wurde NICHT geladen

🔍 Root Cause Analysis

Bug 1: Zeitstempel-Matching zu strikt

Alter Code:

if abs((hour_dt - now).total_seconds()) < 1800:  # ±30 Minuten

Problem:

  • Sucht mit datetime.now() (z.B. 03:05:23)
  • Vergleicht mit Plan-Einträgen (03:00:00)
  • Zeitdifferenz: 5 Minuten 23 Sekunden = 323 Sekunden
  • Das ist < 1800 (30 Min), sollte also matchen...
  • ABER: abs() machte es zu tolerant in beide Richtungen

Echter Bug:

  • Bei Erstellung um 00:10 waren Einträge für 00:00-23:00
  • Bei Ausführung um 03:05 verglich es now() statt current_hour
  • Dadurch wurde falsch gerundet

Bug 2: Aktuelle Stunde wird übersprungen

Alter Code:

if dt <= datetime.now():  # Überspringt ALLES bis jetzt
    continue

Problem:

  • Um 00:10 erstellt → datetime.now() = 00:10
  • Eintrag für 00:00 wird übersprungen (00:00 <= 00:10)
  • Eintrag für 01:00 wird genommen (01:00 > 00:10)
  • Aber: Die Stunde 00:00-01:00 läuft noch!

Beispiel:

00:10 Uhr: Plan erstellen
  - 00:00 wird übersprungen ❌ (aber wir sind noch in dieser Stunde!)
  - 01:00 wird geplant ✅
  - 02:00 wird geplant ✅

Bug 3: Fehlende Debug-Info

Alter Code:

log.info(f"Keine Daten für aktuelle Stunde {current_hour_key}")
return

Problem:

  • Keine Info WELCHE Stunden im Plan sind
  • Schwer zu debuggen
  • Man sieht nicht warum es nicht matched

Die Lösung

Fix 1: Besseres Stunden-Matching

Neuer Code:

# Aktuelle Stunde bestimmen (ohne Minuten/Sekunden)
current_hour = now.replace(minute=0, second=0, microsecond=0)

# Suche mit Toleranz: -10min bis +50min
for hour_key, data in schedule.items():
    hour_dt = datetime.fromisoformat(hour_key)
    time_diff = (hour_dt - current_hour).total_seconds()
    
    # Match wenn innerhalb von -10min bis +50min
    if -600 <= time_diff <= 3000:
        hour_data = data
        matched_hour = hour_key
        log.info(f"Gefunden: {hour_key} (Abweichung: {time_diff/60:.1f} min)")
        break

Vorteile:

  • Vergleicht current_hour (03:00:00) statt now() (03:05:23)
  • Toleranz: -10 bis +50 Minuten (erlaubt Ausführung xx:00 bis xx:50)
  • Zeigt welcher Eintrag gematched wurde
  • Zeigt Zeitdifferenz in Minuten

Fix 2: Aktuelle Stunde inkludieren

Neuer Code:

# Aktuelle Stunde ohne Minuten/Sekunden
current_hour = datetime.now().replace(minute=0, second=0, microsecond=0)

if dt < current_hour:  # Nur VERGANGENE Stunden überspringen
    continue

Vorteile:

  • Um 00:10 erstellt → current_hour = 00:00
  • Eintrag für 00:00 wird genommen (00:00 >= 00:00)
  • Aktuelle Stunde ist im Plan!

Fix 3: Besseres Logging

Neuer Code:

log.info(f"Suche Ladeplan für Stunde: {current_hour.isoformat()}")
log.info(f"Gefunden: {hour_key} (Abweichung: {time_diff/60:.1f} min)")
log.info(f"Stunde {matched_hour}: Aktion={action}, Leistung={power_w}W, Preis={price} ct")
log.info(f"Grund: {reason}")

if not hour_data:
    log.info(f"Keine Daten für aktuelle Stunde {current_hour.isoformat()}")
    log.debug(f"Verfügbare Stunden im Plan: {list(schedule.keys())}")

Vorteile:

  • Sieht genau welche Stunde gesucht wird
  • Sieht ob Match gefunden wurde
  • Sieht Zeitdifferenz
  • Sieht ALLE verfügbaren Stunden bei Fehler
  • Zeigt Aktion, Leistung, Preis, Grund

🧪 Test-Szenarien

Szenario 1: Plan um 00:10 erstellen

Vorher (Bug):

00:10 - Plan erstellen
  ❌ 00:00 übersprungen
  ✅ 01:00 geplant
  ✅ 02:00 geplant
  
03:05 - Ausführung
  ❌ Sucht 03:00, findet nichts (falsches Matching)

Nachher (Fix):

00:10 - Plan erstellen
  ✅ 00:00 geplant (current_hour = 00:00, dt = 00:00 → nicht übersprungen)
  ✅ 01:00 geplant
  ✅ 02:00 geplant
  
03:05 - Ausführung
  ✅ Sucht current_hour = 03:00
  ✅ Findet 03:00 im Plan (time_diff = 0 Sekunden)
  ✅ Lädt!

Szenario 2: Ausführung um xx:45

15:45 - Ausführung
  current_hour = 15:00
  Sucht 15:00 im Plan
  time_diff = 0 → Match! ✅

Szenario 3: Ausführung um xx:55 (Grenzfall)

15:55 - Ausführung
  current_hour = 15:00
  Sucht 15:00 im Plan
  time_diff = 0 → Match! ✅
  
16:05 - Ausführung (nächste Stunde)
  current_hour = 16:00
  Sucht 16:00 im Plan
  time_diff = 0 → Match! ✅

📊 Vergleich Alt vs. Neu

Aspekt Alt (Bug) Neu (Fix)
Zeitvergleich now() (03:05:23) current_hour (03:00:00)
Toleranz ±30 min (zu weit) -10 bis +50 min
Aktuelle Stunde Übersprungen Enthalten
Logging Minimal Ausführlich
Debug-Info Keine Alle Stunden

🔄 Migration

Wenn bereits installiert:

  1. Ersetze die Datei:

    /config/pyscript/battery_charging_optimizer.py
    
  2. PyScript neu laden:

    # Entwicklerwerkzeuge → Dienste
    service: pyscript.reload
    

    ODER: Home Assistant neu starten

  3. Teste sofort:

    service: pyscript.execute_current_schedule
    

    Erwartete Logs:

    INFO: Suche Ladeplan für Stunde: 2025-11-09T15:00:00
    INFO: Gefunden: 2025-11-09T15:00:00 (Abweichung: 0.0 min)
    INFO: Stunde 2025-11-09T15:00:00: Aktion=auto, Leistung=0W, Preis=27.79 ct
    INFO: Grund: Preis zu hoch (27.79 > 27.06 ct)
    INFO: Auto-Modus aktiviert
    

Für neue Installation:

  • Nutze einfach die neue Datei
  • Bug ist bereits behoben

🎯 Verifikation

Nach dem Update kannst du prüfen:

Test 1: Manueller Aufruf

service: pyscript.execute_current_schedule

Sollte zeigen:

  • "Suche Ladeplan für Stunde: [Aktuelle Stunde]"
  • "Gefunden: ..." ODER "Keine Daten..."
  • Bei "Keine Daten": Liste aller verfügbaren Stunden

Test 2: Neuen Plan erstellen

service: pyscript.calculate_charging_schedule

Prüfe danach:

# Entwicklerwerkzeuge → Zustände
pyscript.battery_charging_schedule

# Attribute → schedule sollte enthalten:
# - Aktuelle Stunde (auch wenn schon xx:10 oder xx:30)
# - Alle folgenden Stunden bis morgen gleiche Zeit

Test 3: Warte auf nächste Stunde

Z.B. jetzt 15:30, warte bis 16:05:

Logs prüfen um 16:05:

INFO: Suche Ladeplan für Stunde: 2025-11-09T16:00:00
INFO: Gefunden: 2025-11-09T16:00:00 (Abweichung: 0.0 min)
INFO: Stunde 2025-11-09T16:00:00: Aktion=auto, Leistung=0W

💡 Warum das Problem so subtil war

  1. Timing: Passierte nur nachts (wenn niemand guckt)
  2. Logs: Zeigten nur "Keine Daten" ohne Details
  3. Reproduktion: Schwer zu testen (muss bis nächste Stunde warten)
  4. Code-Review: abs() und <= sahen auf ersten Blick richtig aus

🎓 Was wir gelernt haben

Best Practices:

  1. Immer mit "vollen Stunden" arbeiten (ohne Minuten/Sekunden)
  2. Asymmetrische Toleranzen für zeitbasiertes Matching
  3. Ausführliches Logging für zeitkritische Operationen
  4. Debug-Info zeigen bei Fehlschlägen
  5. Edge Cases testen (Mitternacht, Stundenübergang)

Debugging-Tricks:

  1. Zeige immer welche Daten verfügbar sind
  2. Zeige Zeitdifferenzen in menschenlesbarer Form (Minuten)
  3. Logge jeden Matching-Versuch
  4. Unterscheide "keine Daten" vs "Daten nicht gefunden"

🚀 Erwartetes Verhalten jetzt

Plan-Erstellung um 00:10:

✅ 00:00 geplant (aktuelle Stunde!)
✅ 01:00 geplant
✅ 02:00 geplant
✅ 03:00 geplant (LADEN!)
✅ 04:00 geplant (LADEN!)
...
✅ 23:00 geplant

Ausführung um 03:05:

INFO: Suche Ladeplan für Stunde: 2025-11-09T03:00:00
INFO: Gefunden: 2025-11-09T03:00:00 (Abweichung: 0.0 min)
INFO: Stunde 2025-11-09T03:00:00: Aktion=charge, Leistung=-5000W, Preis=26.99 ct
INFO: Grund: Günstiger Preis (26.99 ct), Wenig PV (0.0 kWh)
INFO: Aktiviere Laden: -5000W
INFO: ESS in REMOTE Mode gesetzt
INFO: Laden aktiviert (ESS in REMOTE Mode)

Ausführung um 05:05:

INFO: Suche Ladeplan für Stunde: 2025-11-09T05:00:00
INFO: Gefunden: 2025-11-09T05:00:00 (Abweichung: 0.0 min)
INFO: Stunde 2025-11-09T05:00:00: Aktion=auto, Leistung=0W, Preis=27.06 ct
INFO: Grund: Preis zu hoch (27.06 > 27.06 ct)
INFO: Deaktiviere manuelles Laden, aktiviere Auto-Modus
INFO: Auto-Modus aktiviert (ESS in INTERNAL Mode)

Status

Version: v1.2.1
Bug: Behoben
Getestet: Code-Review
Kritikalität: Hoch (Kernfunktion)

Für heute Nacht sollte es jetzt funktionieren! 🎉


Wichtig: Nach dem Update einen neuen Plan erstellen!

service: pyscript.calculate_charging_schedule

Dann wird heute Nacht um 23:00 geladen!