286 lines
10 KiB
Python
286 lines
10 KiB
Python
"""
|
||
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)
|