HOTFIX v3.5.1: Preisschwelle wird jetzt korrekt angewendet (KRITISCH)
## Problem
Die price_threshold wurde geladen aber NIE verwendet!
- System lud auch bei Preisen ÜBER der Schwelle
- Beispiel: Schwelle 25ct, aber Ladung bei 25.93ct
- User-Erwartung komplett ignoriert
## Root Cause
```python
# Zeile 110: Geladen ✓
'price_threshold': float(state.get(...) or 25)
# Zeile 317-340: Aber nie verwendet! ✗
for p in future_price_data:
charging_candidates.append({...}) # Keine threshold-Prüfung!
```
## Fix (v3.5.1)
### 1. Filter VOR Ranking
- Filtere alle Stunden in affordable_hours (≤ threshold)
- Ignoriere teure Stunden komplett
- Wenn keine bezahlbaren Stunden: Keine Ladung (Auto-Modus)
### 2. Besseres Logging
```
💶 Preisschwelle: 25.0 ct/kWh
- Stunden unter Schwelle: 18
- Stunden über Schwelle: 12 (werden ignoriert)
```
### 3. Warnung bei Teilladung
Wenn nicht genug günstige Stunden für volle Ladung
## Verhalten
**VORHER (v3.5.0):**
- Alle Preise 25-30ct, Schwelle 25ct
- → Lädt bei 25.93ct ✗
**NACHHER (v3.5.1):**
- Alle Preise 25-30ct, Schwelle 25ct
- → Keine Ladung, Auto-Modus ✓
## Impact
Severity: 🔴 CRITICAL
- Ungewollte Ladevorgänge bei zu teuren Preisen
- Kosteneinsparungen nicht realisiert
- SOFORT updaten empfohlen!
## Dateien
- battery_charging_optimizer.py: Filter + Logging
- CHANGELOG.md: v3.5.1 Eintrag
- HOTFIX_PRICE_THRESHOLD_v3.5.1.md: Detaillierte Analyse
Danke an Felix für sofortiges Bug-Melden! 🙏
---
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,10 @@ Battery Charging Optimizer für OpenEMS + GoodWe
|
||||
Nutzt das bestehende manuelle Steuerungssystem
|
||||
|
||||
Speicherort: /config/pyscript/battery_charging_optimizer.py
|
||||
Version: 3.5.0 - REMOVED: Sicherheitspuffer und Reservekapazität (Hardware hat eigene Puffer)
|
||||
Version: 3.5.1 - FIXED: Preisschwelle wird jetzt korrekt angewendet (kritischer Bug)
|
||||
Stunden über Preisschwelle werden komplett ignoriert
|
||||
Keine Ladung wenn alle Preise über Schwelle liegen
|
||||
v3.5.0 - REMOVED: Sicherheitspuffer und Reservekapazität (Hardware hat eigene Puffer)
|
||||
Batterie lädt jetzt bis 100% SOC
|
||||
CHANGED: Standardwerte - Preisschwelle 25ct, Ladeleistung 8000W
|
||||
FIXED: SOC-Plausibilitäts-Check (filtert 65535% Spikes beim Modus-Wechsel)
|
||||
@@ -32,7 +35,7 @@ def calculate_charging_schedule():
|
||||
Nutzt Ranking-Methode: Wählt die N günstigsten Stunden aus
|
||||
"""
|
||||
|
||||
log.info("=== Batterie-Optimierung gestartet (v3.5.0 - Volle Ladung bis 100%) ===")
|
||||
log.info("=== Batterie-Optimierung gestartet (v3.5.1 - Preisschwelle aktiv) ===")
|
||||
|
||||
# Prüfe ob Optimierung aktiviert ist
|
||||
if state.get('input_boolean.battery_optimizer_enabled') != 'on':
|
||||
@@ -310,11 +313,35 @@ def optimize_charging(price_data, pv_forecast, current_soc, config):
|
||||
log.info(f"🎯 Benötigte Ladestunden: {needed_hours} (bei {max_charge_per_hour}W pro Stunde)")
|
||||
|
||||
# ==========================================
|
||||
# RANKING: Erstelle Kandidaten-Liste
|
||||
# FILTER: Nur Stunden unter Preisschwelle
|
||||
# ==========================================
|
||||
price_threshold = config['price_threshold']
|
||||
affordable_hours = []
|
||||
expensive_hours = []
|
||||
|
||||
for p in future_price_data:
|
||||
if p['price'] <= price_threshold:
|
||||
affordable_hours.append(p)
|
||||
else:
|
||||
expensive_hours.append(p)
|
||||
|
||||
log.info(f"💶 Preisschwelle: {price_threshold} ct/kWh")
|
||||
log.info(f" - Stunden unter Schwelle: {len(affordable_hours)}")
|
||||
log.info(f" - Stunden über Schwelle: {len(expensive_hours)} (werden ignoriert)")
|
||||
|
||||
# Wenn keine bezahlbaren Stunden verfügbar, nicht laden
|
||||
if not affordable_hours:
|
||||
log.warning(f"⚠️ Keine Stunden unter Preisschwelle {price_threshold} ct/kWh gefunden!")
|
||||
log.warning(f" Günstigster Preis: {min_price:.2f} ct/kWh")
|
||||
log.warning(f" → Keine Ladung, bleibe im Auto-Modus")
|
||||
return create_auto_only_schedule(future_price_data)
|
||||
|
||||
# ==========================================
|
||||
# RANKING: Erstelle Kandidaten-Liste (nur bezahlbare Stunden)
|
||||
# ==========================================
|
||||
charging_candidates = []
|
||||
|
||||
for p in future_price_data:
|
||||
for p in affordable_hours:
|
||||
# PV-Prognose für diese Stunde
|
||||
pv_wh = pv_forecast['hourly'].get(p['hour'], 0)
|
||||
|
||||
@@ -336,8 +363,13 @@ def optimize_charging(price_data, pv_forecast, current_soc, config):
|
||||
# Sortiere nach Score (beste zuerst)
|
||||
charging_candidates.sort(key=lambda x: x['score'])
|
||||
|
||||
# Wähle die N besten Stunden
|
||||
selected_hours = charging_candidates[:needed_hours]
|
||||
# Wähle die N besten Stunden (begrenzt auf verfügbare bezahlbare Stunden)
|
||||
actual_hours_needed = min(needed_hours, len(charging_candidates))
|
||||
selected_hours = charging_candidates[:actual_hours_needed]
|
||||
|
||||
if actual_hours_needed < needed_hours:
|
||||
log.warning(f"⚠️ Nur {actual_hours_needed} von {needed_hours} benötigten Stunden unter Preisschwelle")
|
||||
log.warning(f" Batterie wird nur teilweise geladen")
|
||||
|
||||
# ==========================================
|
||||
# Logging: Zeige Auswahl
|
||||
@@ -414,8 +446,11 @@ def optimize_charging(price_data, pv_forecast, current_soc, config):
|
||||
reason = "Automatik"
|
||||
|
||||
# Debugging: Warum nicht geladen?
|
||||
if p['datetime'] not in selected_datetimes:
|
||||
# Finde Position im Ranking
|
||||
if p['price'] > price_threshold:
|
||||
# Über Preisschwelle
|
||||
reason = f"Zu teuer: {p['price']:.2f}ct (Schwelle: {price_threshold}ct)"
|
||||
elif p['datetime'] not in selected_datetimes:
|
||||
# Finde Position im Ranking (nur in bezahlbaren Stunden)
|
||||
rank = 1
|
||||
for candidate in charging_candidates:
|
||||
if candidate['datetime'] == p['datetime']:
|
||||
@@ -423,7 +458,7 @@ def optimize_charging(price_data, pv_forecast, current_soc, config):
|
||||
rank += 1
|
||||
|
||||
if rank <= len(charging_candidates):
|
||||
reason = f"Rang {rank} (nicht unter Top {needed_hours})"
|
||||
reason = f"Rang {rank} (nicht unter Top {actual_hours_needed})"
|
||||
|
||||
schedule.append({
|
||||
'datetime': p['datetime'].isoformat(),
|
||||
|
||||
Reference in New Issue
Block a user