Update: Battery Optimizer v3.4.0 mit allen Fixes und Features

This commit is contained in:
felix.zoesch
2025-12-12 08:20:19 +01:00
commit d2a41aad2d
78 changed files with 18053 additions and 0 deletions

285
debug_schedule.py Normal file
View File

@@ -0,0 +1,285 @@
"""
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)