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": [
{
"type": "ValidationTextBox",
"name": "URL",
"caption": "Solcast URL"
},
{
"type": "SelectVariable",
"name": "ActualVariableID",
"caption": "Ist-Produktion Variable (Leistung)"
},
{
"type": "Select",
"name": "RefreshMode",
@@ -14,14 +24,28 @@
"name": "RefreshMinutes",
"caption": "Intervall (Minuten)",
"minimum": 1,
"maximum": 240,
"visible": true
"maximum": 240
},
{
"type": "TimeSpinner",
"type": "ValidationTextBox",
"name": "RefreshTime",
"caption": "Tägliche Uhrzeit",
"visible": true
"caption": "Tägliche Uhrzeit (HH:MM, z.B. 06:00)"
},
{
"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,42 +10,40 @@ class PV_Forecast extends IPSModule
$this->RegisterPropertyString("URL", "");
$this->RegisterPropertyInteger("ActualVariableID", 0);
// Scheduler
$this->RegisterPropertyString("RefreshMode", "interval"); // interval | daily
$this->RegisterPropertyInteger("RefreshMinutes", 5);
$this->RegisterPropertyInteger("RefreshTime", 6 * 3600); // 06:00 in Sekunden
$this->RegisterPropertyString("RefreshTime", "06:00"); // HH:MM
$this->RegisterPropertyBoolean("ActualIsWatt", true);
// Timer fires RequestAction(UpdateForecast)
$this->RegisterTimer("UpdateForecastTimer", 0, 'IPS_RequestAction($_IPS["TARGET"], "UpdateForecast", 0);');
// WebHook endpoint
$this->RegisterHook("/hook/solcastcompare");
}
public function ApplyChanges()
{
parent::ApplyChanges();
public function ApplyChanges()
{
parent::ApplyChanges();
$this->SetVisualizationType(1);
// Tile Visualization aktivieren
$this->SetVisualizationType(1);
$mode = $this->ReadPropertyString("RefreshMode");
$mode = $this->ReadPropertyString("RefreshMode");
if ($mode === "interval") {
// 🔁 Intervall-Modus
$mins = max(1, (int)$this->ReadPropertyInteger("RefreshMinutes"));
$this->SetTimerInterval(
"UpdateForecastTimer",
$mins * 60 * 1000
);
} else {
// ⏰ Tageszeit-Modus
$this->SetTimerInterval(
"UpdateForecastTimer",
60 * 1000 // jede Minute prüfen
);
}
$this->RegisterHook("/hook/solcastcompare");
if ($mode === "interval") {
$mins = max(1, (int)$this->ReadPropertyInteger("RefreshMinutes"));
$this->SetTimerInterval("UpdateForecastTimer", $mins * 60 * 1000);
} else {
// daily: jede Minute prüfen
$this->SetTimerInterval("UpdateForecastTimer", 60 * 1000);
}
// Hook (optional, falls Auto-Register bei dir klappt)
$this->RegisterHook("/hook/solcastcompare");
}
public function RequestAction($Ident, $Value)
{
@@ -53,11 +51,59 @@ class PV_Forecast extends IPSModule
$this->HandleScheduledUpdate();
return;
}
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"));
if ($url === "") {
@@ -82,6 +128,8 @@ class PV_Forecast extends IPSModule
$this->SendDebug("UpdateForecast", "Forecast aktualisiert", 0);
}
// ----------------- Tile Visualization -----------------
public function GetVisualizationTile(): string
{
if ($this->GetBuffer("ForecastRaw") === "") {
@@ -93,6 +141,8 @@ class PV_Forecast extends IPSModule
return $html;
}
// ----------------- WebHook: data for chart -----------------
protected function ProcessHookData()
{
$instance = isset($_GET["instance"]) ? (int)$_GET["instance"] : 0;
@@ -140,10 +190,14 @@ class PV_Forecast extends IPSModule
echo json_encode($out);
}
// ----------------- Series: Forecast (Solcast) -----------------
private function GetForecastSeriesFiltered(int $startTs, int $endTs): array
{
$raw = $this->GetBuffer("ForecastRaw");
if ($raw === "") return [];
if ($raw === "") {
return [];
}
$data = json_decode($raw, true);
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
if ($ts === false) continue;
// nur HEUTE (lokal) anzeigen
// nur HEUTE (lokal)
if ($ts < $startTs || $ts >= $endTs) continue;
// Solcast: kW
@@ -169,13 +223,19 @@ class PV_Forecast extends IPSModule
return $series;
}
// ----------------- Series: Actual (Archive) -----------------
private function GetActualSeriesFromArchive(int $startTs, int $endTs, int $bucketSeconds, int $nowTs): array
{
$varId = (int)$this->ReadPropertyInteger("ActualVariableID");
if ($varId <= 0 || !IPS_VariableExists($varId)) return [];
if ($varId <= 0 || !IPS_VariableExists($varId)) {
return [];
}
$archiveId = $this->GetArchiveInstanceID();
if ($archiveId === 0) return [];
if ($archiveId === 0) {
return [];
}
if (!AC_GetLoggingStatus($archiveId, $varId)) {
$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);
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) {
if (!isset($row["TimeStamp"], $row["Value"])) continue;
@@ -200,12 +263,12 @@ class PV_Forecast extends IPSModule
$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 = [];
$isWatt = (bool)$this->ReadPropertyBoolean("ActualIsWatt");
// sauberes Raster über den Tag, Zukunft => null
for ($t = $startTs + $bucketSeconds; $t <= $endTs; $t += $bucketSeconds) {
// Zukunft: null (Linie bricht)
if ($t > $nowTs + $bucketSeconds) {
$series[] = [$t * 1000, null];
@@ -219,8 +282,10 @@ class PV_Forecast extends IPSModule
$avg = $buckets[$t][0] / $buckets[$t][1];
// W -> kW, damit es zu Solcast passt
if ($isWatt) $avg = $avg / 1000.0;
// W -> kW
if ($isWatt) {
$avg = $avg / 1000.0;
}
$series[] = [$t * 1000, $avg];
}
@@ -233,37 +298,8 @@ class PV_Forecast extends IPSModule
$list = IPS_GetInstanceListByModuleID(self::ARCHIVE_GUID);
return (is_array($list) && count($list) > 0) ? (int)$list[0] : 0;
}
private function HandleScheduledUpdate()
{
$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);
}
}
// ----------------- optional: auto register hook -----------------
private function RegisterHook(string $Hook)
{