no message

This commit is contained in:
belevo\mh
2026-01-20 10:25:14 +01:00
parent 33667256ec
commit 27536d3d16
2 changed files with 129 additions and 69 deletions

View File

@@ -1,5 +1,15 @@
{ {
"elements": [ "elements": [
{
"type": "ValidationTextBox",
"name": "URL",
"caption": "Solcast URL"
},
{
"type": "SelectVariable",
"name": "ActualVariableID",
"caption": "Ist-Produktion Variable (Leistung)"
},
{ {
"type": "Select", "type": "Select",
"name": "RefreshMode", "name": "RefreshMode",
@@ -14,14 +24,28 @@
"name": "RefreshMinutes", "name": "RefreshMinutes",
"caption": "Intervall (Minuten)", "caption": "Intervall (Minuten)",
"minimum": 1, "minimum": 1,
"maximum": 240, "maximum": 240
"visible": true
}, },
{ {
"type": "TimeSpinner", "type": "ValidationTextBox",
"name": "RefreshTime", "name": "RefreshTime",
"caption": "Tägliche Uhrzeit", "caption": "Tägliche Uhrzeit (HH:MM, z.B. 06:00)"
"visible": true },
{
"type": "CheckBox",
"name": "ActualIsWatt",
"caption": "Istwerte sind in Watt (in kW umrechnen)"
}
],
"actions": [
{
"type": "Button",
"caption": "Forecast jetzt aktualisieren",
"onClick": "IPS_RequestAction($id, \"UpdateForecast\", 0);"
},
{
"type": "Label",
"caption": "Hinweis: Istwerte werden aus dem Archiv gelesen. Variable muss im Archiv geloggt werden."
} }
] ]
} }

View File

@@ -10,13 +10,17 @@ class PV_Forecast extends IPSModule
$this->RegisterPropertyString("URL", ""); $this->RegisterPropertyString("URL", "");
$this->RegisterPropertyInteger("ActualVariableID", 0); $this->RegisterPropertyInteger("ActualVariableID", 0);
// Scheduler
$this->RegisterPropertyString("RefreshMode", "interval"); // interval | daily $this->RegisterPropertyString("RefreshMode", "interval"); // interval | daily
$this->RegisterPropertyInteger("RefreshMinutes", 5); $this->RegisterPropertyInteger("RefreshMinutes", 5);
$this->RegisterPropertyInteger("RefreshTime", 6 * 3600); // 06:00 in Sekunden $this->RegisterPropertyString("RefreshTime", "06:00"); // HH:MM
$this->RegisterPropertyBoolean("ActualIsWatt", true); $this->RegisterPropertyBoolean("ActualIsWatt", true);
// Timer fires RequestAction(UpdateForecast)
$this->RegisterTimer("UpdateForecastTimer", 0, 'IPS_RequestAction($_IPS["TARGET"], "UpdateForecast", 0);'); $this->RegisterTimer("UpdateForecastTimer", 0, 'IPS_RequestAction($_IPS["TARGET"], "UpdateForecast", 0);');
// WebHook endpoint
$this->RegisterHook("/hook/solcastcompare"); $this->RegisterHook("/hook/solcastcompare");
} }
@@ -24,40 +28,82 @@ class PV_Forecast extends IPSModule
{ {
parent::ApplyChanges(); parent::ApplyChanges();
// Tile Visualization aktivieren
$this->SetVisualizationType(1); $this->SetVisualizationType(1);
$mode = $this->ReadPropertyString("RefreshMode"); $mode = $this->ReadPropertyString("RefreshMode");
if ($mode === "interval") { if ($mode === "interval") {
// 🔁 Intervall-Modus
$mins = max(1, (int)$this->ReadPropertyInteger("RefreshMinutes")); $mins = max(1, (int)$this->ReadPropertyInteger("RefreshMinutes"));
$this->SetTimerInterval( $this->SetTimerInterval("UpdateForecastTimer", $mins * 60 * 1000);
"UpdateForecastTimer",
$mins * 60 * 1000
);
} else { } else {
// ⏰ Tageszeit-Modus // daily: jede Minute prüfen
$this->SetTimerInterval( $this->SetTimerInterval("UpdateForecastTimer", 60 * 1000);
"UpdateForecastTimer",
60 * 1000 // jede Minute prüfen
);
} }
// Hook (optional, falls Auto-Register bei dir klappt)
$this->RegisterHook("/hook/solcastcompare"); $this->RegisterHook("/hook/solcastcompare");
} }
public function RequestAction($Ident, $Value) public function RequestAction($Ident, $Value)
{ {
if ($Ident === "UpdateForecast") { if ($Ident === "UpdateForecast") {
$this->HandleScheduledUpdate(); $this->HandleScheduledUpdate();
return; return;
} }
throw new Exception("Unknown Ident: " . $Ident); throw new Exception("Unknown Ident: " . $Ident);
} }
private function UpdateForecast() // ----------------- Scheduler -----------------
private function HandleScheduledUpdate(): void
{
$mode = $this->ReadPropertyString("RefreshMode");
if ($mode === "interval") {
$this->UpdateForecast();
return;
}
// daily
$timeStr = trim($this->ReadPropertyString("RefreshTime")); // "06:00"
$targetSec = $this->ParseHHMMToSeconds($timeStr);
if ($targetSec === null) {
$this->SendDebug("Scheduler", "Ungueltige RefreshTime: " . $timeStr, 0);
return;
}
$now = time();
$nowSec = ((int)date("H", $now) * 3600) + ((int)date("i", $now) * 60);
// Schon heute gelaufen?
$lastRun = (int)$this->GetBuffer("LastDailyRun");
$today0 = strtotime("today");
if ($nowSec >= $targetSec && $lastRun < $today0) {
$this->SendDebug("Scheduler", "Taegliches Update ausgeloest (" . $timeStr . ")", 0);
$this->UpdateForecast();
$this->SetBuffer("LastDailyRun", (string)$now);
}
}
private function ParseHHMMToSeconds(string $hhmm): ?int
{
// akzeptiert "6:00" oder "06:00"
if (!preg_match('/^(\d{1,2}):(\d{2})$/', $hhmm, $m)) {
return null;
}
$h = (int)$m[1];
$min = (int)$m[2];
if ($h < 0 || $h > 23 || $min < 0 || $min > 59) {
return null;
}
return $h * 3600 + $min * 60;
}
// ----------------- Forecast Fetch -----------------
private function UpdateForecast(): void
{ {
$url = trim($this->ReadPropertyString("URL")); $url = trim($this->ReadPropertyString("URL"));
if ($url === "") { if ($url === "") {
@@ -82,6 +128,8 @@ class PV_Forecast extends IPSModule
$this->SendDebug("UpdateForecast", "Forecast aktualisiert", 0); $this->SendDebug("UpdateForecast", "Forecast aktualisiert", 0);
} }
// ----------------- Tile Visualization -----------------
public function GetVisualizationTile(): string public function GetVisualizationTile(): string
{ {
if ($this->GetBuffer("ForecastRaw") === "") { if ($this->GetBuffer("ForecastRaw") === "") {
@@ -93,6 +141,8 @@ class PV_Forecast extends IPSModule
return $html; return $html;
} }
// ----------------- WebHook: data for chart -----------------
protected function ProcessHookData() protected function ProcessHookData()
{ {
$instance = isset($_GET["instance"]) ? (int)$_GET["instance"] : 0; $instance = isset($_GET["instance"]) ? (int)$_GET["instance"] : 0;
@@ -140,10 +190,14 @@ class PV_Forecast extends IPSModule
echo json_encode($out); echo json_encode($out);
} }
// ----------------- Series: Forecast (Solcast) -----------------
private function GetForecastSeriesFiltered(int $startTs, int $endTs): array private function GetForecastSeriesFiltered(int $startTs, int $endTs): array
{ {
$raw = $this->GetBuffer("ForecastRaw"); $raw = $this->GetBuffer("ForecastRaw");
if ($raw === "") return []; if ($raw === "") {
return [];
}
$data = json_decode($raw, true); $data = json_decode($raw, true);
if (!is_array($data) || !isset($data["estimated_actuals"]) || !is_array($data["estimated_actuals"])) { if (!is_array($data) || !isset($data["estimated_actuals"]) || !is_array($data["estimated_actuals"])) {
@@ -157,7 +211,7 @@ class PV_Forecast extends IPSModule
$ts = strtotime($row["period_end"]); // UTC korrekt $ts = strtotime($row["period_end"]); // UTC korrekt
if ($ts === false) continue; if ($ts === false) continue;
// nur HEUTE (lokal) anzeigen // nur HEUTE (lokal)
if ($ts < $startTs || $ts >= $endTs) continue; if ($ts < $startTs || $ts >= $endTs) continue;
// Solcast: kW // Solcast: kW
@@ -169,13 +223,19 @@ class PV_Forecast extends IPSModule
return $series; return $series;
} }
// ----------------- Series: Actual (Archive) -----------------
private function GetActualSeriesFromArchive(int $startTs, int $endTs, int $bucketSeconds, int $nowTs): array private function GetActualSeriesFromArchive(int $startTs, int $endTs, int $bucketSeconds, int $nowTs): array
{ {
$varId = (int)$this->ReadPropertyInteger("ActualVariableID"); $varId = (int)$this->ReadPropertyInteger("ActualVariableID");
if ($varId <= 0 || !IPS_VariableExists($varId)) return []; if ($varId <= 0 || !IPS_VariableExists($varId)) {
return [];
}
$archiveId = $this->GetArchiveInstanceID(); $archiveId = $this->GetArchiveInstanceID();
if ($archiveId === 0) return []; if ($archiveId === 0) {
return [];
}
if (!AC_GetLoggingStatus($archiveId, $varId)) { if (!AC_GetLoggingStatus($archiveId, $varId)) {
$this->SendDebug("Actual", "Variable wird nicht geloggt im Archiv", 0); $this->SendDebug("Actual", "Variable wird nicht geloggt im Archiv", 0);
@@ -183,9 +243,12 @@ 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) return []; if (!is_array($logged) || count($logged) === 0) {
return [];
}
$buckets = []; // bucketEnd => [sum, count] // Bucket End -> [sum, count]
$buckets = [];
foreach ($logged as $row) { foreach ($logged as $row) {
if (!isset($row["TimeStamp"], $row["Value"])) continue; if (!isset($row["TimeStamp"], $row["Value"])) continue;
@@ -200,12 +263,12 @@ class PV_Forecast extends IPSModule
$buckets[$bucketEnd][1] += 1; $buckets[$bucketEnd][1] += 1;
} }
// Wir wollen ein sauberes 30-Minuten Raster über den ganzen Tag,
// aber nach "jetzt" soll die Ist-Linie abbrechen (null).
$series = []; $series = [];
$isWatt = (bool)$this->ReadPropertyBoolean("ActualIsWatt"); $isWatt = (bool)$this->ReadPropertyBoolean("ActualIsWatt");
// sauberes Raster über den Tag, Zukunft => null
for ($t = $startTs + $bucketSeconds; $t <= $endTs; $t += $bucketSeconds) { for ($t = $startTs + $bucketSeconds; $t <= $endTs; $t += $bucketSeconds) {
// Zukunft: null (Linie bricht) // Zukunft: null (Linie bricht)
if ($t > $nowTs + $bucketSeconds) { if ($t > $nowTs + $bucketSeconds) {
$series[] = [$t * 1000, null]; $series[] = [$t * 1000, null];
@@ -219,8 +282,10 @@ class PV_Forecast extends IPSModule
$avg = $buckets[$t][0] / $buckets[$t][1]; $avg = $buckets[$t][0] / $buckets[$t][1];
// W -> kW, damit es zu Solcast passt // W -> kW
if ($isWatt) $avg = $avg / 1000.0; if ($isWatt) {
$avg = $avg / 1000.0;
}
$series[] = [$t * 1000, $avg]; $series[] = [$t * 1000, $avg];
} }
@@ -234,36 +299,7 @@ class PV_Forecast extends IPSModule
return (is_array($list) && count($list) > 0) ? (int)$list[0] : 0; return (is_array($list) && count($list) > 0) ? (int)$list[0] : 0;
} }
private function HandleScheduledUpdate() // ----------------- optional: auto register hook -----------------
{
$mode = $this->ReadPropertyString("RefreshMode");
if ($mode === "interval") {
// 🔁 immer aktualisieren
$this->UpdateForecast();
return;
}
// ⏰ Tageszeit-Modus
$targetSec = (int)$this->ReadPropertyInteger("RefreshTime");
$now = time();
// Sekunden seit Mitternacht
$nowSec =
(int)date("H", $now) * 3600 +
(int)date("i", $now) * 60;
// Schon heute gelaufen?
$lastRun = (int)$this->GetBuffer("LastDailyRun");
$today = strtotime("today");
if ($nowSec >= $targetSec && $lastRun < $today) {
$this->SendDebug("Scheduler", "Tägliches Update ausgelöst", 0);
$this->UpdateForecast();
$this->SetBuffer("LastDailyRun", (string)$now);
}
}
private function RegisterHook(string $Hook) private function RegisterHook(string $Hook)
{ {