no message

This commit is contained in:
mh
2026-01-20 10:34:26 +01:00
parent 27536d3d16
commit 83c74ad05d
+47 -23
View File
@@ -17,10 +17,10 @@ class PV_Forecast extends IPSModule
$this->RegisterPropertyString("RefreshTime", "06:00"); // HH:MM $this->RegisterPropertyString("RefreshTime", "06:00"); // HH:MM
$this->RegisterPropertyBoolean("ActualIsWatt", true); $this->RegisterPropertyBoolean("ActualIsWatt", true);
// Timer fires RequestAction(UpdateForecast) // Timer -> ruft RequestAction(UpdateForecast) auf
$this->RegisterTimer("UpdateForecastTimer", 0, 'IPS_RequestAction($_IPS["TARGET"], "UpdateForecast", 0);'); $this->RegisterTimer("UpdateForecastTimer", 0, 'IPS_RequestAction($_IPS["TARGET"], "UpdateForecast", 0);');
// WebHook endpoint // WebHook Endpoint
$this->RegisterHook("/hook/solcastcompare"); $this->RegisterHook("/hook/solcastcompare");
} }
@@ -41,7 +41,7 @@ class PV_Forecast extends IPSModule
$this->SetTimerInterval("UpdateForecastTimer", 60 * 1000); $this->SetTimerInterval("UpdateForecastTimer", 60 * 1000);
} }
// Hook (optional, falls Auto-Register bei dir klappt) // optional: auto-register hook (wenn GUID passt)
$this->RegisterHook("/hook/solcastcompare"); $this->RegisterHook("/hook/solcastcompare");
} }
@@ -51,6 +51,7 @@ class PV_Forecast extends IPSModule
$this->HandleScheduledUpdate(); $this->HandleScheduledUpdate();
return; return;
} }
throw new Exception("Unknown Ident: " . $Ident); throw new Exception("Unknown Ident: " . $Ident);
} }
@@ -159,26 +160,38 @@ class PV_Forecast extends IPSModule
return; return;
} }
// Zeitraum: HEUTE (lokal) $tzLocal = new DateTimeZone(date_default_timezone_get());
$tz = new DateTimeZone(date_default_timezone_get()); $tzUtc = new DateTimeZone('UTC');
$startDT = new DateTime('today', $tz);
$endDT = new DateTime('tomorrow', $tz);
$start = $startDT->getTimestamp(); // Lokaler Tag (für Archiv / Anzeige)
$end = $endDT->getTimestamp(); $startLocal = new DateTime('today', $tzLocal);
$now = time(); $endLocal = new DateTime('tomorrow', $tzLocal);
$forecast = $this->GetForecastSeriesFiltered($start, $end); $startLocalTs = $startLocal->getTimestamp();
$actual = $this->GetActualSeriesFromArchive($start, $end, 1800, $now); $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 = [ $out = [
"meta" => [ "meta" => [
"forecast_cached_at" => (int)$this->GetBuffer("ForecastTS"), "forecast_cached_at" => (int)$this->GetBuffer("ForecastTS"),
"bucket_seconds" => 1800, "bucket_seconds" => 1800,
"actual_is_watt" => (bool)$this->ReadPropertyBoolean("ActualIsWatt"), "actual_is_watt" => (bool)$this->ReadPropertyBoolean("ActualIsWatt"),
"start" => $start * 1000, "start_local" => $startLocalTs * 1000,
"end" => $end * 1000, "end_local" => $endLocalTs * 1000,
"now" => $now * 1000 "start_utc" => $startUtcTs * 1000,
"end_utc" => $endUtcTs * 1000,
"now" => $nowTs * 1000
], ],
"series" => [ "series" => [
"forecast" => $forecast, "forecast" => $forecast,
@@ -192,7 +205,7 @@ class PV_Forecast extends IPSModule
// ----------------- Series: Forecast (Solcast) ----------------- // ----------------- Series: Forecast (Solcast) -----------------
private function GetForecastSeriesFiltered(int $startTs, int $endTs): array private function GetForecastSeriesFilteredUtc(int $startUtcTs, int $endUtcTs): array
{ {
$raw = $this->GetBuffer("ForecastRaw"); $raw = $this->GetBuffer("ForecastRaw");
if ($raw === "") { if ($raw === "") {
@@ -208,11 +221,12 @@ class PV_Forecast extends IPSModule
foreach ($data["estimated_actuals"] as $row) { foreach ($data["estimated_actuals"] as $row) {
if (!isset($row["period_end"], $row["pv_power_rooftop"])) continue; 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; if ($ts === false) continue;
// nur HEUTE (lokal) // Filter: "heute lokal" als UTC-Grenzen
if ($ts < $startTs || $ts >= $endTs) continue; if ($ts < $startUtcTs || $ts >= $endUtcTs) continue;
// Solcast: kW // Solcast: kW
$val = (float)$row["pv_power_rooftop"]; $val = (float)$row["pv_power_rooftop"];
@@ -244,10 +258,11 @@ class PV_Forecast extends IPSModule
$logged = AC_GetLoggedValues($archiveId, $varId, $startTs, $endTs, 0); $logged = AC_GetLoggedValues($archiveId, $varId, $startTs, $endTs, 0);
if (!is_array($logged) || count($logged) === 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 = []; $buckets = [];
foreach ($logged as $row) { foreach ($logged as $row) {
if (!isset($row["TimeStamp"], $row["Value"])) continue; if (!isset($row["TimeStamp"], $row["Value"])) continue;
@@ -266,10 +281,10 @@ class PV_Forecast extends IPSModule
$series = []; $series = [];
$isWatt = (bool)$this->ReadPropertyBoolean("ActualIsWatt"); $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) { 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) { if ($t > $nowTs + $bucketSeconds) {
$series[] = [$t * 1000, null]; $series[] = [$t * 1000, null];
continue; continue;
@@ -293,6 +308,15 @@ class PV_Forecast extends IPSModule
return $series; 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 private function GetArchiveInstanceID(): int
{ {
$list = IPS_GetInstanceListByModuleID(self::ARCHIVE_GUID); $list = IPS_GetInstanceListByModuleID(self::ARCHIVE_GUID);