From 83c74ad05dde027288d886cbbcbd1426d712115f Mon Sep 17 00:00:00 2001 From: "belevo\\mh" Date: Tue, 20 Jan 2026 10:34:26 +0100 Subject: [PATCH] no message --- PV_Forecast/module.php | 70 ++++++++++++++++++++++++++++-------------- 1 file changed, 47 insertions(+), 23 deletions(-) diff --git a/PV_Forecast/module.php b/PV_Forecast/module.php index d10a942..cb93288 100644 --- a/PV_Forecast/module.php +++ b/PV_Forecast/module.php @@ -17,10 +17,10 @@ class PV_Forecast extends IPSModule $this->RegisterPropertyString("RefreshTime", "06:00"); // HH:MM $this->RegisterPropertyBoolean("ActualIsWatt", true); - // Timer fires RequestAction(UpdateForecast) + // Timer -> ruft RequestAction(UpdateForecast) auf $this->RegisterTimer("UpdateForecastTimer", 0, 'IPS_RequestAction($_IPS["TARGET"], "UpdateForecast", 0);'); - // WebHook endpoint + // WebHook Endpoint $this->RegisterHook("/hook/solcastcompare"); } @@ -41,7 +41,7 @@ class PV_Forecast extends IPSModule $this->SetTimerInterval("UpdateForecastTimer", 60 * 1000); } - // Hook (optional, falls Auto-Register bei dir klappt) + // optional: auto-register hook (wenn GUID passt) $this->RegisterHook("/hook/solcastcompare"); } @@ -51,6 +51,7 @@ class PV_Forecast extends IPSModule $this->HandleScheduledUpdate(); return; } + throw new Exception("Unknown Ident: " . $Ident); } @@ -159,26 +160,38 @@ class PV_Forecast extends IPSModule return; } - // Zeitraum: HEUTE (lokal) - $tz = new DateTimeZone(date_default_timezone_get()); - $startDT = new DateTime('today', $tz); - $endDT = new DateTime('tomorrow', $tz); + $tzLocal = new DateTimeZone(date_default_timezone_get()); + $tzUtc = new DateTimeZone('UTC'); - $start = $startDT->getTimestamp(); - $end = $endDT->getTimestamp(); - $now = time(); + // Lokaler Tag (für Archiv / Anzeige) + $startLocal = new DateTime('today', $tzLocal); + $endLocal = new DateTime('tomorrow', $tzLocal); - $forecast = $this->GetForecastSeriesFiltered($start, $end); - $actual = $this->GetActualSeriesFromArchive($start, $end, 1800, $now); + $startLocalTs = $startLocal->getTimestamp(); + $endLocalTs = $endLocal->getTimestamp(); + + // Lokaler Tagesbereich als UTC-Grenzen (für Solcast "Z") + $startUtcTs = (clone $startLocal)->setTimezone($tzUtc)->getTimestamp(); + $endUtcTs = (clone $endLocal)->setTimezone($tzUtc)->getTimestamp(); + + $nowTs = time(); + + // Forecast: komplett für HEUTE (lokaler Tag), korrekt über UTC gefiltert + $forecast = $this->GetForecastSeriesFilteredUtc($startUtcTs, $endUtcTs); + + // Actual: komplett HEUTE lokal, aber nach "jetzt" abbrechen (null) + $actual = $this->GetActualSeriesFromArchive($startLocalTs, $endLocalTs, 1800, $nowTs); $out = [ "meta" => [ "forecast_cached_at" => (int)$this->GetBuffer("ForecastTS"), "bucket_seconds" => 1800, "actual_is_watt" => (bool)$this->ReadPropertyBoolean("ActualIsWatt"), - "start" => $start * 1000, - "end" => $end * 1000, - "now" => $now * 1000 + "start_local" => $startLocalTs * 1000, + "end_local" => $endLocalTs * 1000, + "start_utc" => $startUtcTs * 1000, + "end_utc" => $endUtcTs * 1000, + "now" => $nowTs * 1000 ], "series" => [ "forecast" => $forecast, @@ -192,7 +205,7 @@ class PV_Forecast extends IPSModule // ----------------- Series: Forecast (Solcast) ----------------- - private function GetForecastSeriesFiltered(int $startTs, int $endTs): array + private function GetForecastSeriesFilteredUtc(int $startUtcTs, int $endUtcTs): array { $raw = $this->GetBuffer("ForecastRaw"); if ($raw === "") { @@ -208,11 +221,12 @@ class PV_Forecast extends IPSModule foreach ($data["estimated_actuals"] as $row) { if (!isset($row["period_end"], $row["pv_power_rooftop"])) continue; - $ts = strtotime($row["period_end"]); // UTC korrekt + // period_end ist UTC ("Z") + $ts = strtotime($row["period_end"]); if ($ts === false) continue; - // nur HEUTE (lokal) - if ($ts < $startTs || $ts >= $endTs) continue; + // Filter: "heute lokal" als UTC-Grenzen + if ($ts < $startUtcTs || $ts >= $endUtcTs) continue; // Solcast: kW $val = (float)$row["pv_power_rooftop"]; @@ -244,10 +258,11 @@ class PV_Forecast extends IPSModule $logged = AC_GetLoggedValues($archiveId, $varId, $startTs, $endTs, 0); if (!is_array($logged) || count($logged) === 0) { - return []; + // trotzdem Raster mit null zurückgeben, damit Linie sauber endet + return $this->BuildNullRaster($startTs, $endTs, $bucketSeconds); } - // Bucket End -> [sum, count] + // bucketEnd -> [sum, count] $buckets = []; foreach ($logged as $row) { if (!isset($row["TimeStamp"], $row["Value"])) continue; @@ -266,10 +281,10 @@ class PV_Forecast extends IPSModule $series = []; $isWatt = (bool)$this->ReadPropertyBoolean("ActualIsWatt"); - // sauberes Raster über den Tag, Zukunft => null + // Sauberes Raster über den Tag, nach "jetzt" -> null for ($t = $startTs + $bucketSeconds; $t <= $endTs; $t += $bucketSeconds) { - // Zukunft: null (Linie bricht) + // Zukunft: null, damit Istlinie nicht parallel bis 24:00 weiterläuft if ($t > $nowTs + $bucketSeconds) { $series[] = [$t * 1000, null]; continue; @@ -293,6 +308,15 @@ class PV_Forecast extends IPSModule return $series; } + private function BuildNullRaster(int $startTs, int $endTs, int $bucketSeconds): array + { + $series = []; + for ($t = $startTs + $bucketSeconds; $t <= $endTs; $t += $bucketSeconds) { + $series[] = [$t * 1000, null]; + } + return $series; + } + private function GetArchiveInstanceID(): int { $list = IPS_GetInstanceListByModuleID(self::ARCHIVE_GUID);