Um das Skript zu verwenden, trag deine eigenen Daten an folgenden Stellen ein: PV_SENSOR_METADATA_IDS (Zeile 14): Ersetz die metadata_id-Werte (z. B. 50, 51, 52, 53) durch die tatsächlichen IDs deiner PV-Sensoren. Pass die Sensornamen (pv1_watt, pv2_watt, etc.) an die Namen deiner Sensoren an, falls sie abweichen. WEATHER_SENSOR_METADATA_IDS (Zeile 20): Ersetz die metadata_id-Werte (z. B. 441, 464, etc.) durch die tatsächlichen IDs deiner Wetter-Sensoren. Pass die Sensornamen (radiation_wm2, temperature_c, etc.) an die Namen deiner Sensoren an, falls sie abweichen.OUTPUT_CSV_FILE (Zeile 27): Gib den gewünschten Dateinamen und Pfad für die Ausgabedatei an (Standard: solar_data_export.csv). CONFIG_FILE (Zeile 28): Erstell eine Datei config.json mit deiner Datenbankkonfiguration und gib den korrekten Pfad an, falls er abweicht. Die config.json muss folgende Struktur haben: start_date (Zeile 94): Passe die das Startdatum (datetime(2025, 6, 1)) an den gewünschten Zeitraum für den Datenexport an. code: import pandas as pd import sqlalchemy import json import logging from datetime import datetime import numpy as np # --- Konfiguration --- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') # NEUE PV-Sensoren (Leistung in Watt) PV_SENSOR_METADATA_IDS = { 'pv1_watt': None, # Ersetzen Sie None durch die metadata_id Ihres ersten PV-Sensors 'pv2_watt': None, # Ersetzen Sie None durch die metadata_id Ihres zweiten PV-Sensors 'pv3_watt': None, # Ersetzen Sie None durch die metadata_id Ihres dritten PV-Sensors 'pv4_watt': None # Ersetzen Sie None durch die metadata_id Ihres vierten PV-Sensors } WEATHER_SENSOR_METADATA_IDS = { 'radiation_wm2': None, # Ersetzen Sie None durch die metadata_id Ihres Strahlungssensors 'temperature_c': None, # Ersetzen Sie None durch die metadata_id Ihres Temperatursensors 'humidity_percent': None, # Ersetzen Sie None durch die metadata_id Ihres Feuchtigkeitssensors 'pressure_hpa': None, # Ersetzen Sie None durch die metadata_id Ihres Luftdrucksensors 'wind_speed_kmh': None, # Ersetzen Sie None durch die metadata_id Ihres Windgeschwindigkeitssensors 'lux': None # Ersetzen Sie None durch die metadata_id Ihres Lichtsensors } OUTPUT_CSV_FILE = 'solar_data_export.csv' # Anpassen an den gewünschten Dateinamen/Pfad CONFIG_FILE = 'config.json' # Anpassen an den Pfad Ihrer Konfigurationsdatei def load_config(): """Lädt die Datenbank-Konfiguration.""" try: with open(CONFIG_FILE, 'r') as f: config = json.load(f) db_config = config['database'] db_url = f"mysql+pymysql://{db_config['user']}:{db_config['password']}@{db_config['host']}/{db_config['database']}" return db_url except (FileNotFoundError, KeyError) as e: logging.error(f"Fehler in '{CONFIG_FILE}': {e}") return None def fetch_data(engine, metadata_id, start_date): """Holt die Rohdaten für einen Sensor und bereinigt Duplikate.""" query = f""" SELECT state, last_updated_ts FROM states WHERE metadata_id = {metadata_id} AND state REGEXP '^[0-9]+[.]?[0-9]*$' AND last_updated_ts >= {start_date.timestamp()} ORDER BY last_updated_ts """ df = pd.read_sql(query, engine) if df.empty: logging.warning(f"Keine Daten für metadata_id {metadata_id} gefunden!") return pd.DataFrame() df['timestamp'] = pd.to_datetime(df['last_updated_ts'], unit='s') df['state'] = pd.to_numeric(df['state'], errors='coerce') df = df.dropna().drop(columns=['last_updated_ts']) df = df.groupby('timestamp').mean().reset_index() logging.info(f" → {len(df)} Datenpunkte geladen (Bereich: {df['timestamp'].min()} bis {df['timestamp'].max()})") return df.set_index('timestamp') def calculate_energy_from_power_trapz(power_series): """ Berechnet die Energie (kWh) aus Leistungsdaten (Watt) mit Trapez-Integration. Formel: E = ∫ P dt ≈ Σ (P[i] + P[i+1])/2 * Δt Args: power_series: TimeSeries mit Leistungsdaten in Watt Returns: TimeSeries mit kumulativer Energie in kWh """ if power_series.empty or power_series.isna().all(): logging.warning("Keine Leistungsdaten vorhanden für Trapez-Integration!") return pd.Series(dtype=float) # Sortiere nach Zeit power_series = power_series.sort_index() # Berechne Zeitdifferenzen in Stunden time_diff_hours = power_series.index.to_series().diff().dt.total_seconds() / 3600.0 # Trapez-Regel: (P[i] + P[i+1]) / 2 * Δt power_avg = (power_series + power_series.shift(1)) / 2 energy_incremental_kwh = (power_avg * time_diff_hours) / 1000.0 # Watt → kWh # Erster Wert ist NaN, setze auf 0 energy_incremental_kwh.iloc[0] = 0 return energy_incremental_kwh def main(): """Hauptfunktion: Laden, Verarbeiten und Speichern der Daten.""" db_url = load_config() if not db_url: return try: engine = sqlalchemy.create_engine(db_url) logging.info("Erfolgreich mit der Datenbank verbunden.") except Exception as e: logging.error(f"Fehler beim Verbinden: {e}") return start_date = datetime(2025, 6, 1) # Anpassen an Ihr gewünschtes Startdatum logging.info(f"Datenexport startet ab dem {start_date.strftime('%Y-%m-%d')}") # ========== SCHRITT 1: PV-Leistungsdaten laden ========== logging.info("\n=== LADE PV-LEISTUNGSDATEN (Watt) ===") pv_dfs = [] for name, metadata_id in PV_SENSOR_METADATA_IDS.items(): if metadata_id is None: logging.warning(f"Überspringe '{name}' - metadata_id nicht definiert") continue logging.info(f"Lade '{name}'...") df = fetch_data(engine, metadata_id, start_date) if df.empty: logging.warning(f"Überspringe '{name}' - keine Daten vorhanden") continue df.rename(columns={'state': name}, inplace=True) pv_dfs.append(df) if not pv_dfs: logging.error("FEHLER: Keine PV-Daten geladen!") return # Kombiniere alle PV-Strings logging.info("Kombiniere PV-Daten...") pv_combined = pd.concat(pv_dfs, axis=1) # Berechne Gesamt-Leistung (Summe aller Strings) logging.info("Berechne Gesamt-Leistung (Summe aller PV-Strings)...") pv_combined['total_power_watt'] = pv_combined.sum(axis=1) logging.info(f" → Leistungsstatistik:") logging.info(f" Min: {pv_combined['total_power_watt'].min():.1f} W") logging.info(f" Max: {pv_combined['total_power_watt'].max():.1f} W") logging.info(f" Mittelwert: {pv_combined['total_power_watt'].mean():.1f} W") logging.info(f" Datenpunkte: {len(pv_combined)}") # ========== SCHRITT 2: Energie mit Trapez-Integration berechnen ========== logging.info("\n=== BERECHNE ENERGIE (TRAPEZ-INTEGRATION) ===") energy_incremental = calculate_energy_from_power_trapz(pv_combined['total_power_watt']) # Erstelle DataFrame mit Zeitstempel und inkrementeller Energie energy_df = pd.DataFrame({ 'energy_kwh_incremental': energy_incremental }) logging.info(f" → Energie-Inkremente berechnet") logging.info(f" Gesamt-Energie (vor Resampling): {energy_incremental.sum():.3f} kWh") # ========== SCHRITT 3: Auf stündlich aggregieren ========== logging.info("\n=== AGGREGIERE AUF STÜNDLICH ===") # Energie: Summiere die Inkremente pro Stunde hourly_yield = energy_df['energy_kwh_incremental'].resample('h').sum() hourly_yield.name = 'yield_kwh_hourly' # Leistung: Mittelwert pro Stunde (für Info) hourly_power = pv_combined['total_power_watt'].resample('h').mean() logging.info(f" → Stündlicher Ertrag berechnet:") logging.info(f" Stunden mit Ertrag: {(hourly_yield > 0).sum()}") logging.info(f" Gesamt-Ertrag: {hourly_yield.sum():.3f} kWh") logging.info(f" Max. Stunden-Ertrag: {hourly_yield.max():.3f} kWh") # ========== SCHRITT 4: Wetterdaten laden ========== logging.info("\n=== LADE WETTERDATEN ===") weather_dfs = [] for name, metadata_id in WEATHER_SENSOR_METADATA_IDS.items(): if metadata_id is None: logging.warning(f"Überspringe '{name}' - metadata_id nicht definiert") continue logging.info(f"Lade '{name}'...") df = fetch_data(engine, metadata_id, start_date) if df.empty: logging.warning(f"Überspringe '{name}' - keine Daten vorhanden") continue df.rename(columns={'state': name}, inplace=True) weather_dfs.append(df) if not weather_dfs: logging.error("Keine Wetterdaten geladen!") return # Kombiniere Wetterdaten logging.info("Kombiniere Wetterdaten...") weather_df = pd.concat(weather_dfs, axis=1) # Resample Wetterdaten auf stündlich logging.info("Resample Wetterdaten auf stündlich...") weather_resampled = weather_df.resample('h').mean() # ========== SCHRITT 5: Alles kombinieren ========== logging.info("\n=== KOMBINIERE UND BEREINIGE ===") # Fülle Wetterdaten intelligent weather_resampled.interpolate(method='time', inplace=True) weather_resampled.bfill(inplace=True) weather_resampled.ffill(inplace=True) weather_resampled.fillna(0, inplace=True) # Fülle Yield mit 0 (wo keine Daten sind) hourly_yield = hourly_yield.fillna(0) # Kombiniere alles final_df = pd.concat([weather_resampled, hourly_yield], axis=1) # Bereinigung: Setze Ertrag auf 0, wenn keine Sonneneinstrahlung no_sun_mask = final_df['radiation_wm2'] < 0.5 final_df.loc[no_sun_mask, 'yield_kwh_hourly'] = 0 # Entferne extrem kleine Werte very_small_mask = (final_df['yield_kwh_hourly'] < 0.0001) & (final_df['yield_kwh_hourly'] > 0) final_df.loc[very_small_mask, 'yield_kwh_hourly'] = 0 # ========== SCHRITT 6: Speichern ========== logging.info("\n=== FINALE STATISTIK ===") logging.info(f"Zeitraum: {final_df.index.min()} bis {final_df.index.max()}") logging.info(f"Stunden gesamt: {len(final_df)}") logging.info(f"Stunden mit Ertrag: {(final_df['yield_kwh_hourly'] > 0).sum()}") logging.info(f"Gesamt-Ertrag im Export: {final_df['yield_kwh_hourly'].sum():.3f} kWh") logging.info(f"Max. Stunden-Ertrag: {final_df['yield_kwh_hourly'].max():.3f} kWh") # Speichern final_df.to_csv(OUTPUT_CSV_FILE) logging.info(f"\n✅ Daten erfolgreich in '{OUTPUT_CSV_FILE}' gespeichert.") # Zeige Vorschau mit Ertrag print("\n=== VORSCHAU (erste 10 Zeilen mit Ertrag > 0) ===") preview = final_df[final_df['yield_kwh_hourly'] > 0].head(10) if not preview.empty: print(preview) else: print("Keine Stunden mit Ertrag gefunden!") print("\n=== ERSTE 10 ZEILEN INSGESAMT ===") print(final_df.head(10)) if __name__ == '__main__': main()