Files
battery-charging-optimizer/debug_schedule.py

286 lines
10 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Debug Script für Battery Charging Optimizer
Speicherort: /config/pyscript/debug_schedule.py
Verwendung in Home Assistant Developer Tools → Services:
service: pyscript.debug_schedule
data: {}
"""
from datetime import datetime
from zoneinfo import ZoneInfo
TIMEZONE = ZoneInfo("Europe/Berlin")
def get_local_now():
"""Gibt die aktuelle Zeit in lokaler Timezone zurück"""
return datetime.now(TIMEZONE)
@service
def debug_schedule():
"""
Gibt detaillierte Debug-Informationen über den aktuellen Schedule aus
"""
log.info("=" * 60)
log.info("=== DEBUG: Battery Charging Schedule ===")
log.info("=" * 60)
now = get_local_now()
log.info(f"Aktuelle Zeit: {now.strftime('%Y-%m-%d %H:%M:%S %Z')}")
# 1. Prüfe Optimizer-Status
log.info("")
log.info("--- 1. Optimizer Status ---")
optimizer_enabled = state.get('input_boolean.battery_optimizer_enabled')
manual_override = state.get('input_boolean.battery_optimizer_manual_override')
manual_control = state.get('input_boolean.goodwe_manual_control')
log.info(f"Optimizer enabled: {optimizer_enabled}")
log.info(f"Manual override: {manual_override}")
log.info(f"Manual control active: {manual_control}")
if optimizer_enabled != 'on':
log.warning("⚠ Optimizer ist DEAKTIVIERT!")
if manual_override == 'on':
log.warning("⚠ Manual override ist AKTIV - Ausführung wird übersprungen!")
# 2. Prüfe Schedule State
log.info("")
log.info("--- 2. Schedule State ---")
schedule_attr = state.getattr('pyscript.battery_charging_schedule')
if not schedule_attr:
log.error("❌ Kein Schedule vorhanden!")
log.info("Führe aus: service: pyscript.calculate_charging_schedule")
return
log.info(f"Schedule Value: {state.get('pyscript.battery_charging_schedule')}")
log.info(f"Last Update: {schedule_attr.get('last_update', 'N/A')}")
log.info(f"Anzahl Stunden im Plan: {schedule_attr.get('num_hours', 0)}")
log.info(f"Anzahl Ladungen gesamt: {schedule_attr.get('num_charges', 0)}")
log.info(f"Anzahl Ladungen morgen: {schedule_attr.get('num_charges_tomorrow', 0)}")
log.info(f"Gesamt-Energie: {schedule_attr.get('total_energy_kwh', 0)} kWh")
log.info(f"Durchschnittspreis: {schedule_attr.get('avg_charge_price', 0):.2f} ct/kWh")
log.info(f"Tomorrow-Daten vorhanden: {schedule_attr.get('has_tomorrow_data', False)}")
first_charge = schedule_attr.get('first_charge_time')
first_charge_tomorrow = schedule_attr.get('first_charge_tomorrow')
if first_charge:
log.info(f"Erste Ladung: {first_charge}")
else:
log.warning("⚠ Keine Ladungen geplant!")
if first_charge_tomorrow:
log.info(f"Erste Ladung morgen: {first_charge_tomorrow}")
# 3. Prüfe Schedule-Einträge
log.info("")
log.info("--- 3. Schedule Details ---")
schedule = schedule_attr.get('schedule', [])
if not schedule:
log.error("❌ Schedule Array ist leer!")
return
current_hour = now.hour
current_date = now.date()
# Finde aktuellen Eintrag
current_entry = None
current_index = None
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)
entry_date = entry_dt.date()
entry_hour = entry_dt.hour
if entry_date == current_date and entry_hour == current_hour:
current_entry = entry
current_index = i
break
if current_entry:
log.info(f"✓ Aktueller Eintrag gefunden (Index {current_index}):")
log.info(f" Zeit: {current_entry['datetime']}")
log.info(f" Aktion: {current_entry['action']}")
log.info(f" Leistung: {current_entry['power_w']}W")
log.info(f" Preis: {current_entry['price']:.2f} ct/kWh")
log.info(f" Grund: {current_entry.get('reason', 'N/A')}")
if current_entry['action'] == 'charge':
log.info(" → ✓ Sollte LADEN")
else:
log.info(" Sollte AUTO-Modus")
else:
log.warning(f"⚠ Kein Eintrag für {current_date} {current_hour}:00 gefunden!")
# 4. Zeige nächste Stunden
log.info("")
log.info("--- 4. Nächste 24 Stunden ---")
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)
# Sortiere nach Zeit
future_entries.sort(key=lambda x: datetime.fromisoformat(x['datetime']))
# Zeige erste 24
for entry in future_entries[:24]:
entry_dt = datetime.fromisoformat(entry['datetime'])
if entry_dt.tzinfo is None:
entry_dt = entry_dt.replace(tzinfo=TIMEZONE)
action_symbol = "🔋" if entry['action'] == 'charge' else "🔄"
day_str = "morgen" if entry.get('is_tomorrow', False) else "heute"
log.info(
f"{action_symbol} {entry_dt.strftime('%H:%M')} ({day_str}): "
f"{entry['action']:6s} | "
f"{entry['power_w']:6d}W | "
f"{entry['price']:5.2f}ct | "
f"{entry.get('reason', '')}"
)
# 5. Prüfe Ladungen in den nächsten 12 Stunden
log.info("")
log.info("--- 5. Geplante Ladungen (nächste 12h) ---")
upcoming_charges = []
for entry in future_entries[:12]:
if entry['action'] == 'charge':
upcoming_charges.append(entry)
if upcoming_charges:
log.info(f"{len(upcoming_charges)} Ladungen geplant:")
for entry in upcoming_charges:
entry_dt = datetime.fromisoformat(entry['datetime'])
if entry_dt.tzinfo is None:
entry_dt = entry_dt.replace(tzinfo=TIMEZONE)
day_str = "morgen" if entry.get('is_tomorrow', False) else "heute"
log.info(
f" 🔋 {entry_dt.strftime('%H:%M')} ({day_str}): "
f"{abs(entry['power_w'])}W @ {entry['price']:.2f}ct"
)
else:
log.warning("⚠ Keine Ladungen in den nächsten 12 Stunden geplant!")
# 6. Prüfe Batterie-Status
log.info("")
log.info("--- 6. Batterie Status ---")
soc = float(state.get('sensor.esssoc') or 0)
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)
capacity = float(state.get('input_number.battery_capacity_kwh') or 10)
charge_power = float(state.get('input_number.charge_power_battery') or 0)
log.info(f"Aktueller SOC: {soc}%")
log.info(f"SOC-Bereich: {min_soc}% - {max_soc}%")
log.info(f"Batterie-Kapazität: {capacity} kWh")
log.info(f"Ziel-Ladeleistung: {charge_power}W")
available_capacity = (max_soc - soc) / 100 * capacity
log.info(f"Verfügbare Ladekapazität: {available_capacity:.2f} kWh")
if soc >= max_soc:
log.info(" Batterie ist voll - keine Ladung nötig")
elif soc < min_soc:
log.warning(f"⚠ SOC unter Minimum ({min_soc}%)!")
# 7. Prüfe Preis-Sensoren
log.info("")
log.info("--- 7. Strompreis-Sensoren ---")
# Extended Sensor
price_ext_state = state.get('sensor.hastrom_flex_pro_ext')
price_ext_attr = state.getattr('sensor.hastrom_flex_pro_ext')
if price_ext_attr:
log.info("✓ Extended Sensor verfügbar:")
prices_today = price_ext_attr.get('prices_today', [])
prices_tomorrow = price_ext_attr.get('prices_tomorrow', [])
tomorrow_available = price_ext_attr.get('tomorrow_available', False)
log.info(f" Preise heute: {len(prices_today)} Stunden")
log.info(f" Preise morgen: {len(prices_tomorrow)} Stunden")
log.info(f" Tomorrow verfügbar: {tomorrow_available}")
if prices_today:
min_price_today = min(prices_today)
max_price_today = max(prices_today)
log.info(f" Preisspanne heute: {min_price_today:.2f} - {max_price_today:.2f} ct/kWh")
if not tomorrow_available and now.hour >= 14:
log.warning("⚠ Tomorrow-Preise sollten verfügbar sein (nach 14:00), sind es aber nicht!")
else:
log.warning("⚠ Extended Sensor nicht verfügbar")
# 8. Prüfe Automations
log.info("")
log.info("--- 8. Automation Status ---")
automation_hourly = state.get('automation.batterie_optimierung_stundliche_ausfuhrung')
automation_daily = state.get('automation.batterie_optimierung_tagliche_planung')
automation_keepalive = state.get('automation.automation_speicher_manuell_laden')
log.info(f"Stündliche Ausführung: {automation_hourly if automation_hourly else 'NICHT GEFUNDEN'}")
log.info(f"Tägliche Planung: {automation_daily if automation_daily else 'NICHT GEFUNDEN'}")
log.info(f"Keep-Alive (Manuell Laden): {automation_keepalive if automation_keepalive else 'NICHT GEFUNDEN'}")
if automation_keepalive and automation_keepalive == 'off':
if manual_control == 'on':
log.error("❌ PROBLEM: Manual Control ist ON, aber Keep-Alive Automation ist OFF!")
log.info(" → Keine Modbus-Befehle werden gesendet!")
# 9. Zusammenfassung
log.info("")
log.info("=" * 60)
log.info("=== ZUSAMMENFASSUNG ===")
log.info("=" * 60)
issues = []
if optimizer_enabled != 'on':
issues.append("Optimizer ist deaktiviert")
if not schedule:
issues.append("Kein Schedule vorhanden")
if schedule_attr.get('num_charges', 0) == 0:
issues.append("Keine Ladungen geplant")
if not schedule_attr.get('has_tomorrow_data', False) and now.hour >= 14:
issues.append("Tomorrow-Daten fehlen (sollten nach 14:00 da sein)")
if not current_entry:
issues.append(f"Kein Schedule-Eintrag für aktuelle Stunde ({current_hour}:00)")
if manual_control == 'on' and automation_keepalive == 'off':
issues.append("Keep-Alive Automation ist deaktiviert trotz Manual Control")
if issues:
log.warning("⚠ PROBLEME GEFUNDEN:")
for issue in issues:
log.warning(f" - {issue}")
else:
log.info("✓ Keine offensichtlichen Probleme gefunden")
log.info("")
log.info("Debug-Report abgeschlossen")
log.info("=" * 60)