[Script] solcast.com vs. open-meteo.com

Sieht nicht nach einem perfekten Tag für Berechnungsänderungen bei Dir aus!

Komando zurück - sieht doch jetzt perfekt bei Dir aus!?!?

Ja, das plus 1 kannste ja lassen, aber deine IST-Wert-Zeiten passen nicht dazu??? Holst Du immer noch aller 5 Minuten Daten ab???

ja, hatte es war auf 30min geändert, sehe aber das wieder 5 min drin sind.

1 „Gefällt mir“

Hast Du das erwartet? Zwischen Theorie & Praxis liegt immer ein ???. Allein schon Verschmutzung usw. … ich finde das Ergebnis heute schon richtig gut, ob nun +/- 1,2 KW stört mich nicht. Größer 5-10 wäre aber schon blöd!

das finde ich gut, möchte nunr die 1h weg bekommen

Soo, @ralf 's Extended Version + Optimierte Parameter (Anlagenkennwerte) und meine Verschattungs-Funktion ergeben bis jetzt ein Wahnsinns Ergebnis :slight_smile:

Ich schicke dir gleich im DIscord noch mal meinen letzten Stand, mit den Wiederholungen, etc.

Vielleicht kommen wir dann mal zu einem “öffentlichen” neuen Ergebnis :smiley: .

Lage bei mir wie befürchtet, finster und sehr stürmisch.

Aber jetzt sieht es wieder so aus als ob ne Stunde versatz bei Dir ist!

nee, die Solarvorhersage und das echte Wetter sind hier 1 Stunde versetzt :smiley:

1 „Gefällt mir“

Dann ein aktueller Stand von mir komplett.

Für die “automatische” Berechnung des Realitätsfaktors nutze ich in einem Auswertescript, das jede Nacht um 23:59 läuft folgenden Code Teil

// IDs der Live-Werte (Heute)
$id_soll_heute = 11812; 
$id_ist_heute  = 37275; 

// IDs der Archiv-Werte (Gestern)
$id_soll_gestern = 50171; 
$id_ist_gestern  = 35755;
$id_perf_gestern = 49649; 

$soll_heute = GetValue($id_soll_heute);
$ist_heute = GetValue($id_ist_heute);
$perf_heute = ($soll_heute > 0) ? round(($ist_heute / $soll_heute) * 100, 1) : 0;

SetValue($id_soll_gestern, $soll_heute);
SetValue($id_ist_gestern, $ist_heute);
SetValue($id_perf_gestern, $perf_heute);

Das Script pvForecast DWD ICON

<?php
//
// pvForecast DWD ICON
//
// ==========================================
// KONFIGURATION & DEBUG
// ==========================================
$debug = true; // Im Normalbetrieb auf false stellen, wenn keine Logs gewünscht sind
$scriptName = "EMS: " . IPS_GetName($_IPS['SELF']);
$parentID = $_IPS['SELF'];

// Position der Anlage
$lat = 52.42;
$lon = 9.80;

// PV‑Derating (Realitätsfaktor aus "PV_Daily_Summary"-Script)
// sollte die VariablenID nicht existieren, wird der Wert 0.78 genommen
$id_pv_derate = 43499;
$pv_derate = IPS_VariableExists($id_pv_derate) ? GetValue($id_pv_derate) : 0.78;

// NOCT‑Temperaturmodell (typische Werte: 18–22°C Temperaturanstieg bei 800 W/m²)
$noct_delta_temp = 22;

// Clipping-Faktor (WR‑Limit)
$clipping_factor = 0.91;

// Open-Meteo Konvention direkt im Array
$panels = [
    'Sued1' => ['kwp' => 0.88, 'tilt' => 60, 'azimuth' => 12],
    'Sued2' => ['kwp' => 1.00, 'tilt' => 30, 'azimuth' => 12],
    'West'  => ['kwp' => 0.88, 'tilt' => 45, 'azimuth' => 102],
    'Ost'   => ['kwp' => 0.88, 'tilt' => 45, 'azimuth' => -92]
];

// Temperatur-Koeffizient (% / °C) und Referenzwerte (Datenblatt PV Zellen)
$temp_coeff = -0.29;
$t_ref = 25.0;

// VariablenID gemäß eigener Umgebung eintragen
$id_lokale_temp = 20861; 
$id_ist_gesamt_kwh = 37275; 

// Symcon Modul / Archiv ID
$archiveID = IPS_GetInstanceListByModuleID('{43192F0B-135B-4CE7-A0A7-1475603F3060}')[0];

// ==========================================
// VARIABLEN ANLEGEN
// ==========================================
for ($i = 0; $i <= 3; $i++) {
    $dayLabel = ($i == 0) ? "Heute" : "Tag $i";
    CreateVariableByIdent($parentID, "Total_kWh_Day_$i", "Ertrag Gesamt ($dayLabel)", 2, $i + 10, "kWh");
    CreateVariableByIdent($parentID, "HTML_Day_$i", "Chart $dayLabel", 3, $i + 20, "~HTMLBox");
    foreach ($panels as $key => $p) {
        CreateVariableByIdent($parentID, "kWh_{$key}_Day_$i", "Ertrag $key ($dayLabel)", 2, $i + 100, "kWh");
    }
}

// ==========================================
// EINZELNE API-ABRUFE PRO PANEL
// ==========================================
$panelData = [];
$timeArray = [];
$tempArray = [];
$fetchFailed = false;

// HTTP-Context definieren (User-Agent signalisieren, Timeout setzen)
$context = stream_context_create([
    'http' => [
        'method'  => 'GET',
        'header'  => "User-Agent: SymconPVForecast/1.0\r\n",
        'timeout' => 5
    ]
]);

foreach ($panels as $key => $p) {
    $url = "https://api.open-meteo.com/v1/dwd-icon?latitude=$lat&longitude=$lon&hourly=temperature_2m,global_tilted_irradiance&tilt={$p['tilt']}&azimuth={$p['azimuth']}&forecast_days=4&timezone=Europe%2FBerlin";
    
    $json = @file_get_contents($url, false, $context);
    
    if (!$json) {
        if ($debug) IPS_LogMessage($scriptName, "Fehler beim Abrufen der Open-Meteo Daten für Panel $key. Erneuter Versuch in 21 Sekunden...");
        $fetchFailed = true;
        break; // Abfrage der restlichen Panels abbrechen
    }

    $response = json_decode($json, true);

    if (!isset($response['hourly']['global_tilted_irradiance'])) {
        if ($debug) IPS_LogMessage($scriptName, "Ungültige API-Antwort für Panel $key. Erneuter Versuch in 21 Sekunden...");
        $fetchFailed = true;
        break;
    }

    $panelData[$key] = $response['hourly']['global_tilted_irradiance'];

    if (empty($timeArray)) {
        $timeArray = $response['hourly']['time'];
        $tempArray = $response['hourly']['temperature_2m'];
    }

    // 1000 ms Pause zwischen den Anfragen gegen Rate-Limiting
    IPS_sleep(1000);
}

// ==========================================
// RETRY-TIMER STEUERUNG
// ==========================================
if ($fetchFailed) {
    // Im Fehlerfall: Scripttimer auf 21 Sekunden setzen und Ausführung beenden
    IPS_SetScriptTimer($_IPS['SELF'], 21);
    return;
} else {
    // Erfolgreich durchgelaufen: Scripttimer deaktivieren (auf 0 setzen)
    IPS_SetScriptTimer($_IPS['SELF'], 0);
    if ($debug) IPS_LogMessage($scriptName, "Wetterdaten erfolgreich abgerufen und verarbeitet.");
}

// ==========================================
// BERECHNUNG DER PV-LEISTUNG (WATT)
// ==========================================
$days_data = [];
$current_temp = IPS_VariableExists($id_lokale_temp) ? GetValue($id_lokale_temp) : 20;
$current_hour_str = date('Y-m-d H');

foreach ($timeArray as $index => $timestamp) {
    $ts = strtotime($timestamp);
    $dateKey = date('Y-m-d', $ts);
    $hourStr = date('Y-m-d H', $ts);
    
    $temp_air = ($hourStr == $current_hour_str) ? $current_temp : $tempArray[$index];
    $hourly_sum = 0;

    foreach ($panels as $key => $p) {
        $total_rad_tilted = $panelData[$key][$index] ?? 0;

        // NOCT‑Temperaturmodell
        $t_cell = $temp_air + ($total_rad_tilted / 800) * $noct_delta_temp;

        // PV‑Leistung
        $p_out = ($total_rad_tilted / 1000) * $p['kwp'] * 1000;
        $p_out *= (1 + (($temp_coeff / 100) * ($t_cell - $t_ref)));

        // PV‑Derating (automatisch kalibriert)
        $p_out *= $pv_derate;

        // Clipping
        $p_out = min($p_out, $p['kwp'] * 1000 * $clipping_factor);

        $p_out = max(0, $p_out);

        $days_data[$dateKey]['groups'][$key][] = $p_out;
        $hourly_sum += $p_out;
    }

    $days_data[$dateKey]['times'][] = date('H:i', $ts);
    $days_data[$dateKey]['values'][] = round($hourly_sum);
}

// ==========================================
// IST-WERTE AUS SYMCON ARCHIV
// ==========================================
$istValuesFinal = array_fill(0, 17, null); 
if (IPS_VariableExists($id_ist_gesamt_kwh)) {
    $logData = AC_GetLoggedValues($archiveID, $id_ist_gesamt_kwh, strtotime("today 00:00:00"), time(), 0);
    if ($logData && count($logData) > 1) {
        $logData = array_reverse($logData);
        $tempIst = [];
        for ($i = 1; $i < count($logData); $i++) {
            $delta_kwh = $logData[$i]['Value'] - $logData[$i-1]['Value'];
            $delta_t = $logData[$i]['TimeStamp'] - $logData[$i-1]['TimeStamp'];
            if ($delta_t > 0 && $delta_kwh >= 0) {
                $watt = ($delta_kwh * 3600 / $delta_t) * 1000;
                $h = (int)date('H', $logData[$i]['TimeStamp']);
                
                if ($h >= 5 && $h <= 22) {
                    $tempIst[$h][] = $watt;
                }
            }
        }
        foreach ($tempIst as $h => $vals) {
            $istValuesFinal[$h - 5] = round(array_sum($vals) / count($vals));
        }
    }
}

// ==========================================
// OUTPUT & CHART-GENERIERUNG
// ==========================================
$dayIndex = 0;
foreach ($days_data as $date => $content) {
    if ($dayIndex > 3) break;
    
    SetValue(IPS_GetObjectIDByIdent("Total_kWh_Day_$dayIndex", $parentID), array_sum($content['values']) / 1000);
    foreach ($panels as $key => $p) {
        SetValue(IPS_GetObjectIDByIdent("kWh_{$key}_Day_$dayIndex", $parentID), array_sum($content['groups'][$key]) / 1000);
    }
    
    $fLabels = []; $fValues = []; $nowIdx = null;
    foreach ($content['times'] as $idx => $time) {
        $h = (int)substr($time, 0, 2);
        if ($h >= 5 && $h <= 22) {
            $fLabels[] = $time;
            $fValues[] = $content['values'][$idx];
            if ($dayIndex == 0 && $h == (int)date('H')) $nowIdx = count($fLabels) - 1;
        }
    }
    
    $datasets = [[
        "label" => "Prognose (W)", "data" => $fValues, "borderColor" => "#FFD700",
        "backgroundColor" => "rgba(255, 215, 0, 0.1)", "fill" => true, "tension" => 0.3, "pointRadius" => 0, "borderWidth" => 2
    ]];
    
    if ($dayIndex == 0) {
        $datasets[] = [
            "label" => "Ist (W)", "data" => $istValuesFinal, "borderColor" => "#00BFFF",
            "backgroundColor" => "transparent", "fill" => false, "tension" => 0.4, "pointRadius" => 0, "borderWidth" => 3, "borderDash" => [3, 3],
            "spanGaps" => true
        ];
    }
    
    $html = generateCombinedChartHTML("chart_" . $dayIndex, $fLabels, $datasets, $nowIdx);
    SetValue(IPS_GetObjectIDByIdent("HTML_Day_" . $dayIndex, $parentID), $html);
    $dayIndex++;
}

// ==========================================
// HILFSFUNKTIONEN
// ==========================================
function CreateVariableByIdent($parent, $ident, $name, $type, $position, $profile = "") {
    $vid = @IPS_GetObjectIDByIdent($ident, $parent);
    if ($vid === false) {
        $vid = IPS_CreateVariable($type);
        IPS_SetParent($vid, $parent);
        IPS_SetName($vid, $name);
        IPS_SetIdent($vid, $ident); // Fix: Verwende die neu erstellte Variablen-ID ($vid)
        IPS_SetPosition($vid, $position);
    }
    if ($profile != "" && IPS_VariableProfileExists($profile)) IPS_SetVariableCustomProfile($vid, $profile);
    return $vid;
}

function generateCombinedChartHTML($id, $labels, $datasets, $nowIdx) {
    $jsonLabels = json_encode($labels);
    $jsonDatasets = json_encode($datasets);
    return '
    <div style="width: 100%; height: 100%; background: #111; display: flex; flex-direction: column; border: 1px solid #444; box-sizing: border-box; overflow: hidden;">
        <canvas id="'.$id.'"></canvas>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
    <script>
        (function() {
            const vLine = { id: "vLine", afterDraw: (c) => { 
                if (c.config.options.nowIdx !== null) {
                    const ctx = c.ctx; const x = c.scales.x.getPixelForValue(c.config.options.nowIdx);
                    ctx.save(); ctx.beginPath(); ctx.moveTo(x, c.scales.y.top); ctx.lineTo(x, c.scales.y.bottom);
                    ctx.lineWidth = 2; ctx.strokeStyle = "rgba(255, 0, 0, 0.5)"; ctx.setLineDash([5, 5]); ctx.stroke(); ctx.restore();
                }
            }};
            var render = function() {
                var ctx = document.getElementById("'.$id.'");
                if (!ctx || !window.Chart) return;
                if (window["inst_'.$id.'"]) window["inst_'.$id.'"].destroy();
                window["inst_'.$id.'"] = new Chart(ctx, {
                    type: "line", plugins: [vLine],
                    data: { labels: '.$jsonLabels.', datasets: '.$jsonDatasets.' },
                    options: {
                        nowIdx: '.($nowIdx !== null ? $nowIdx : "null").',
                        responsive: true, maintainAspectRatio: false,
                        plugins: { legend: { display: true, labels: { color: "#999", boxWidth: 10, font: { size: 10 } } } },
                        scales: {
                            x: { ticks: { color: "#bbb", maxTicksLimit: 9 }, grid: { display: false } },
                            y: { beginAtZero: true, ticks: { color: "#bbb" }, grid: { color: "rgba(255,255,255,0.1)" } }
                        }
                    }
                });
            };
            var check = setInterval(function() { if (window.Chart) { clearInterval(check); render(); } }, 100);
        })();
    </script>';
}
1 „Gefällt mir“

Morgen wird es interessant

Hier eher normal, dafür waren heute spannende Kurven.

1 „Gefällt mir“

mal schauen, lasse noch die Daten von heute, morgen mit speichern, sodass ich morgen noch mal sehen kann, wie der Soll/Ist verlaufen ist (mit Hilfe KI)

so sieht das dann aus:

Ich habe noch mal etwas ergänzt, was mit quickchart.io ein PNG erzeugt, Abends nach 23:00, damit hebe ich ein paar Bilder der vergangenen Tage auf, die Daten sind ja “eh da” :smiley: .

bei mir pastt es ganz gut:

$temp_coeff = -0.8

2 „Gefällt mir“