Update: Battery Optimizer v3.4.0 mit allen Fixes und Features
This commit is contained in:
214
hastrom_flex_extended.py
Normal file
214
hastrom_flex_extended.py
Normal file
@@ -0,0 +1,214 @@
|
||||
# /homeassistant/pyscript/hastrom_flex_extended.py
|
||||
# Version: 1.1.0 - FIXED: Zeitabhängige API-Abfrage
|
||||
# VOR 14:00: Nur heute (verhindert HTTP 500 Error)
|
||||
# AB 14:00: Heute + morgen
|
||||
import requests, json
|
||||
import datetime
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
# Konstante für Timezone
|
||||
TIMEZONE = ZoneInfo("Europe/Berlin")
|
||||
|
||||
|
||||
def get_local_now():
|
||||
"""Gibt die aktuelle Zeit in lokaler Timezone zurück (Europe/Berlin)"""
|
||||
return datetime.datetime.now(TIMEZONE)
|
||||
|
||||
|
||||
@service
|
||||
def getprices_extended():
|
||||
"""
|
||||
Erweiterte Version von haStrom FLEX PRO Preisabfrage mit Tomorrow-Support.
|
||||
Erstellt neue Sensoren: sensor.hastrom_flex_ext und sensor.hastrom_flex_pro_ext
|
||||
|
||||
FIXED: Proper timezone handling - alle Datetimes in Europe/Berlin
|
||||
"""
|
||||
now = get_local_now()
|
||||
today = now.strftime("%Y%m%d")
|
||||
tomorrow_date = now + datetime.timedelta(days=1)
|
||||
tomorrow = tomorrow_date.strftime("%Y%m%d")
|
||||
hr = int(now.strftime("%H"))
|
||||
|
||||
# ==========================================
|
||||
# Zeitabhängige API-Abfrage
|
||||
# ==========================================
|
||||
# VOR 14:00: Nur heute abfragen (Tomorrow-Preise noch nicht verfügbar)
|
||||
# AB 14:00: Heute + morgen abfragen
|
||||
if hr < 14:
|
||||
end_date = today
|
||||
log.info(f"Lade Preise nur für {today} (vor 14:00 - Tomorrow nicht verfügbar)")
|
||||
else:
|
||||
end_date = tomorrow
|
||||
log.info(f"Lade Preise für {today} bis {tomorrow} (ab 14:00 - Tomorrow verfügbar)")
|
||||
|
||||
log.info(f"Lokale Zeit: {now.strftime('%Y-%m-%d %H:%M:%S %Z')}")
|
||||
|
||||
# ==========================================
|
||||
# API-Call für heute (+ morgen ab 14:00) - FLEX PRO
|
||||
# ==========================================
|
||||
url = f"http://eex.stwhas.de/api/spotprices/flexpro?start_date={today}&end_date={end_date}"
|
||||
|
||||
try:
|
||||
response = task.executor(requests.get, url, timeout=10)
|
||||
|
||||
# Check HTTP status
|
||||
if response.status_code != 200:
|
||||
log.error(f"❌ API-Fehler: HTTP {response.status_code}")
|
||||
log.error(f"URL: {url}")
|
||||
log.error(f"Response: {response.text[:200]}")
|
||||
return
|
||||
|
||||
# Try to parse JSON
|
||||
try:
|
||||
data = response.json()
|
||||
except ValueError as json_err:
|
||||
log.error(f"❌ JSON Parse-Fehler: {json_err}")
|
||||
log.error(f"Response Text: {response.text[:200]}")
|
||||
return
|
||||
|
||||
# Check if data structure is valid
|
||||
if 'data' not in data:
|
||||
log.error(f"❌ API-Response hat kein 'data' Feld")
|
||||
log.error(f"Response keys: {list(data.keys())}")
|
||||
return
|
||||
|
||||
log.info(f"✓ API-Abfrage erfolgreich: {len(data.get('data', []))} Datenpunkte")
|
||||
|
||||
except Exception as e:
|
||||
log.error(f"❌ Fehler beim Abrufen der Strompreise: {e}")
|
||||
log.error(f"URL: {url}")
|
||||
return
|
||||
|
||||
# ==========================================
|
||||
# Verarbeite Daten mit TIMEZONE AWARENESS
|
||||
# ==========================================
|
||||
today_date = now.date()
|
||||
tomorrow_date_obj = tomorrow_date.date()
|
||||
|
||||
# Sammle Daten
|
||||
price_list_today = []
|
||||
datetime_list_today = []
|
||||
price_list_tomorrow = []
|
||||
datetime_list_tomorrow = []
|
||||
|
||||
current_price = None
|
||||
|
||||
for item in data["data"]:
|
||||
# FIXED: Parse timestamps und lokalisiere nach Europe/Berlin
|
||||
start_dt_naive = datetime.datetime.strptime(item["start_timestamp"], "%Y-%m-%d %H:%M:%S")
|
||||
end_dt_naive = datetime.datetime.strptime(item["end_timestamp"], "%Y-%m-%d %H:%M:%S")
|
||||
|
||||
# Timestamps from API are in local time (Europe/Berlin), so we add timezone info
|
||||
start_dt = start_dt_naive.replace(tzinfo=TIMEZONE)
|
||||
end_dt = end_dt_naive.replace(tzinfo=TIMEZONE)
|
||||
|
||||
# FLEX PRO Preis: t_price_has_pro_incl_vat
|
||||
price = item["t_price_has_pro_incl_vat"]
|
||||
timestamp = item["start_timestamp"]
|
||||
|
||||
# FIXED: Aktueller Preis - vergleiche timezone-aware datetimes
|
||||
if start_dt <= now < end_dt:
|
||||
current_price = price
|
||||
|
||||
# Sortiere nach Datum
|
||||
if start_dt.date() == today_date:
|
||||
price_list_today.append(price)
|
||||
datetime_list_today.append(timestamp)
|
||||
elif start_dt.date() == tomorrow_date_obj:
|
||||
price_list_tomorrow.append(price)
|
||||
datetime_list_tomorrow.append(timestamp)
|
||||
|
||||
# ==========================================
|
||||
# UPDATE: sensor.hastrom_flex_ext
|
||||
# ==========================================
|
||||
if current_price is not None:
|
||||
state.set("sensor.hastrom_flex_ext", value=float(current_price))
|
||||
|
||||
# Tariff info (angepasst für FLEX PRO)
|
||||
if "tariff_info_flex_pro" in data:
|
||||
for key, value in data["tariff_info_flex_pro"].items():
|
||||
state.setattr(f"sensor.hastrom_flex_ext.{key}", value)
|
||||
|
||||
# Prices
|
||||
state.setattr("sensor.hastrom_flex_ext.prices_today", price_list_today)
|
||||
state.setattr("sensor.hastrom_flex_ext.datetime_today", datetime_list_today)
|
||||
state.setattr("sensor.hastrom_flex_ext.prices_tomorrow", price_list_tomorrow)
|
||||
state.setattr("sensor.hastrom_flex_ext.datetime_tomorrow", datetime_list_tomorrow)
|
||||
|
||||
# Status
|
||||
state.setattr("sensor.hastrom_flex_ext.tomorrow_available", len(price_list_tomorrow) > 0)
|
||||
state.setattr("sensor.hastrom_flex_ext.tomorrow_count", len(price_list_tomorrow))
|
||||
state.setattr("sensor.hastrom_flex_ext.last_update", now.strftime("%Y-%m-%d %H:%M:%S"))
|
||||
|
||||
# ==========================================
|
||||
# UPDATE: sensor.hastrom_flex_pro_ext
|
||||
# ==========================================
|
||||
if current_price is not None:
|
||||
state.set("sensor.hastrom_flex_pro_ext", value=float(current_price))
|
||||
|
||||
# Tariff info
|
||||
if "tariff_info_flex_pro" in data:
|
||||
for key, value in data["tariff_info_flex_pro"].items():
|
||||
state.setattr(f"sensor.hastrom_flex_pro_ext.{key}", value)
|
||||
|
||||
# Prices
|
||||
state.setattr("sensor.hastrom_flex_pro_ext.prices_today", price_list_today)
|
||||
state.setattr("sensor.hastrom_flex_pro_ext.datetime_today", datetime_list_today)
|
||||
state.setattr("sensor.hastrom_flex_pro_ext.prices_tomorrow", price_list_tomorrow)
|
||||
state.setattr("sensor.hastrom_flex_pro_ext.datetime_tomorrow", datetime_list_tomorrow)
|
||||
|
||||
# Status
|
||||
state.setattr("sensor.hastrom_flex_pro_ext.tomorrow_available", len(price_list_tomorrow) > 0)
|
||||
state.setattr("sensor.hastrom_flex_pro_ext.tomorrow_count", len(price_list_tomorrow))
|
||||
state.setattr("sensor.hastrom_flex_pro_ext.last_update", now.strftime("%Y-%m-%d %H:%M:%S"))
|
||||
|
||||
# ==========================================
|
||||
# Logging & Debug
|
||||
# ==========================================
|
||||
tomorrow_expected = hr >= 14
|
||||
tomorrow_available = len(price_list_tomorrow) > 0
|
||||
|
||||
log.info(f"📊 haStrom FLEX PRO Extended - Preise aktualisiert:")
|
||||
log.info(f" ├─ Heute: {len(price_list_today)} Stunden")
|
||||
|
||||
if tomorrow_expected:
|
||||
if tomorrow_available:
|
||||
log.info(f" └─ Morgen: {len(price_list_tomorrow)} Stunden ✓ verfügbar (nach 14:00)")
|
||||
else:
|
||||
log.warning(f" └─ Morgen: {len(price_list_tomorrow)} Stunden ⚠ NICHT verfügbar (sollte verfügbar sein nach 14:00!)")
|
||||
else:
|
||||
log.info(f" └─ Morgen: {len(price_list_tomorrow)} Stunden (noch nicht erwartet vor 14:00)")
|
||||
|
||||
if price_list_today:
|
||||
min_today = min(price_list_today)
|
||||
max_today = max(price_list_today)
|
||||
avg_today = sum(price_list_today) / len(price_list_today)
|
||||
log.info(f" 📈 Heute: Min={min_today:.2f}, Max={max_today:.2f}, Avg={avg_today:.2f} ct/kWh")
|
||||
|
||||
if price_list_tomorrow:
|
||||
min_tomorrow = min(price_list_tomorrow)
|
||||
max_tomorrow = max(price_list_tomorrow)
|
||||
avg_tomorrow = sum(price_list_tomorrow) / len(price_list_tomorrow)
|
||||
log.info(f" 📈 Morgen: Min={min_tomorrow:.2f}, Max={max_tomorrow:.2f}, Avg={avg_tomorrow:.2f} ct/kWh")
|
||||
|
||||
|
||||
# ==========================================
|
||||
# Automatische Aktualisierung
|
||||
# ==========================================
|
||||
|
||||
@time_trigger("cron(0 * * * *)") # Jede volle Stunde
|
||||
def update_prices_hourly():
|
||||
"""Aktualisiere Preise jede Stunde"""
|
||||
pyscript.getprices_extended()
|
||||
|
||||
@time_trigger("cron(5 14 * * *)") # Täglich um 14:05
|
||||
def update_prices_afternoon():
|
||||
"""Extra Update um 14:05 wenn Preise für morgen verfügbar werden"""
|
||||
log.info("=== TRIGGER: 14:05 Update für Tomorrow-Preise ===")
|
||||
pyscript.getprices_extended()
|
||||
|
||||
@time_trigger("cron(5 0 * * *)") # Um Mitternacht
|
||||
def update_prices_midnight():
|
||||
"""Update um Mitternacht für neuen Tag"""
|
||||
log.info("=== TRIGGER: Midnight Update ===")
|
||||
pyscript.getprices_extended()
|
||||
Reference in New Issue
Block a user