no message
This commit is contained in:
@@ -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."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user