215 lines
8.4 KiB
Python
215 lines
8.4 KiB
Python
# /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()
|