""" 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)