# /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()