Files
Symcon_Belevo_Energiemanage…/Energy_Pie/module.php
2025-12-17 08:34:38 +01:00

294 lines
8.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
class Energy_Pie extends IPSModule
{
// Attribute keys for UI state
private const ATTR_RANGE = 'Range';
private const ATTR_DATE = 'Date';
public function Create(): void
{
parent::Create();
// Source variables (logged counters, kWh)
$this->RegisterPropertyInteger('VarProduction', 0);
$this->RegisterPropertyInteger('VarConsumption', 0);
$this->RegisterPropertyInteger('VarFeedIn', 0);
$this->RegisterPropertyInteger('VarGrid', 0);
// Default range for first startup
$this->RegisterPropertyString('DefaultRange', 'day');
// Persisted UI state
$this->RegisterAttributeString(self::ATTR_RANGE, 'day');
$this->RegisterAttributeString(self::ATTR_DATE, date('Y-m-d'));
// Enable individual visualization (HTML-SDK)
$this->SetVisualizationType(1);
}
public function ApplyChanges(): void
{
parent::ApplyChanges();
// First-time init: if range is empty, set it to property default
$range = $this->ReadAttributeString(self::ATTR_RANGE);
if ($range === '') {
$this->WriteAttributeString(self::ATTR_RANGE, $this->ReadPropertyString('DefaultRange'));
}
// Push initial view data
$this->RecalculateAndPush();
}
public function GetVisualizationTile(): string
{
$path = __DIR__ . '/module.html';
if (!file_exists($path)) {
return '<div style="padding:12px;font-family:sans-serif;">module.html fehlt</div>';
}
return file_get_contents($path);
}
public function RequestAction($Ident, $Value): void
{
switch ($Ident) {
case 'SetRange':
$range = (string)$Value;
if (!in_array($range, ['day', 'week', 'month', 'total'], true)) {
throw new Exception('Invalid range: ' . $range);
}
$this->WriteAttributeString(self::ATTR_RANGE, $range);
$this->RecalculateAndPush();
break;
case 'SetDate':
// Expect YYYY-MM-DD from HTML <input type="date">
$date = (string)$Value;
if (!$this->isValidDate($date)) {
throw new Exception('Invalid date: ' . $date);
}
$this->WriteAttributeString(self::ATTR_DATE, $date);
$this->RecalculateAndPush();
break;
case 'Prev':
case 'Next':
case 'Today':
$this->ShiftDate($Ident);
$this->RecalculateAndPush();
break;
case 'Refresh':
$this->RecalculateAndPush();
break;
default:
throw new Exception('Unknown Ident: ' . $Ident);
}
}
/**
* Compute values (later from archive), then push to tile via UpdateVisualizationValue.
*/
private function RecalculateAndPush(): void
{
$range = $this->ReadAttributeString(self::ATTR_RANGE);
$date = $this->ReadAttributeString(self::ATTR_DATE);
[$tStart, $tEnd] = $this->getRange($range, $date);
// Archiv-Deltas
$prod = $this->readDelta($this->ReadPropertyInteger('VarProduction'), $tStart, $tEnd);
$feed = $this->readDelta($this->ReadPropertyInteger('VarFeedIn'), $tStart, $tEnd);
$grid = $this->readDelta($this->ReadPropertyInteger('VarGrid'), $tStart, $tEnd);
// Hausverbrauch = Produktion - Einspeisung + Netz
$house = $prod - $feed + $grid;
if ($house < 0) {
$house = 0.0;
}
$payload = [
'range' => $range,
'date' => $date,
'tStart' => $tStart,
'tEnd' => $tEnd,
'values' => [
'Produktion' => (float)$prod,
'Einspeisung' => (float)$feed,
'Netz' => (float)$grid,
'Hausverbrauch'=> (float)$house
],
'debug' => [
'prod' => $dbgProd,
'feed' => $dbgFeed,
'grid' => $dbgGrid
]
];
$this->UpdateVisualizationValue(json_encode($payload, JSON_THROW_ON_ERROR));
}
/**
* Range rules:
* - day: chosen date 00:00:00 .. next day 00:00:00
* - week: Monday 00:00:00 .. next Monday 00:00:00 (MoSo)
* - month: first of month 00:00:00 .. first of next month 00:00:00
* - total: 0 .. now (placeholder)
*/
private function getRange(string $range, string $dateYmd): array
{
$now = time();
if ($range === 'total') {
// Later: use oldest log timestamp as start
return [0, $now];
}
$base = strtotime($dateYmd . ' 00:00:00');
if ($base === false) {
// fallback to today
$base = strtotime(date('Y-m-d') . ' 00:00:00');
}
switch ($range) {
case 'day':
$start = $base;
$end = $start + 86400;
return [$start, $end];
case 'week':
// date('N'): 1=Mon .. 7=Sun
$dow = (int)date('N', $base);
$start = $base - (($dow - 1) * 86400); // Monday
$end = $start + (7 * 86400); // next Monday
return [$start, $end];
case 'month':
$start = strtotime(date('Y-m-01 00:00:00', $base));
$end = strtotime(date('Y-m-01 00:00:00', strtotime('+1 month', $start)));
return [$start, $end];
default:
return [$base, $base + 86400];
}
}
private function readDelta(int $varId, int $tStart, int $tEnd, ?array &$dbg = null): float
{
$dbg = [
'varId' => $varId,
'count' => 0,
'vStart' => null,
'vEnd' => null
];
if ($varId <= 0 || !IPS_VariableExists($varId)) {
return 0.0;
}
$archiveID = IPS_GetInstanceListByModuleID('{43192F0B-135B-4CE7-A0A7-1475603F3060}')[0] ?? 0;
if ($archiveID <= 0) {
return 0.0;
}
$values = @AC_GetLoggedValues($archiveID, $varId, $tStart - 86400, $tEnd + 86400, 0);
if (empty($values)) {
return 0.0;
}
usort($values, static fn($a, $b) => (int)$a['TimeStamp'] <=> (int)$b['TimeStamp']);
$dbg['count'] = count($values);
// Fallback: erster/letzter Eintrag im Fenster
$first = (float)$values[0]['Value'];
$last = (float)$values[count($values)-1]['Value'];
$vStart = null;
$vEnd = null;
foreach ($values as $v) {
$ts = (int)$v['TimeStamp'];
if ($ts <= $tStart) {
$vStart = (float)$v['Value'];
}
if ($ts <= $tEnd) {
$vEnd = (float)$v['Value'];
}
if ($ts > $tEnd) {
break;
}
}
// Wenn kein Wert vor Start/Ende gefunden wurde -> nimm ersten/letzten Logwert
if ($vStart === null) $vStart = $first;
if ($vEnd === null) $vEnd = $last;
$dbg['vStart'] = $vStart;
$dbg['vEnd'] = $vEnd;
$diff = $vEnd - $vStart;
return ($diff < 0) ? 0.0 : (float)$diff;
}
/**
* Buttons for quick navigation.
*/
private function ShiftDate(string $action): void
{
$range = $this->ReadAttributeString(self::ATTR_RANGE);
if ($range === 'total') {
// total ignores date
return;
}
if ($action === 'Today') {
$this->WriteAttributeString(self::ATTR_DATE, date('Y-m-d'));
return;
}
$date = $this->ReadAttributeString(self::ATTR_DATE);
$base = strtotime($date . ' 00:00:00');
if ($base === false) {
$base = strtotime(date('Y-m-d') . ' 00:00:00');
}
$sign = ($action === 'Prev') ? -1 : 1;
switch ($range) {
case 'day':
$base = strtotime(($sign === -1 ? '-1 day' : '+1 day'), $base);
break;
case 'week':
$base = strtotime(($sign === -1 ? '-7 day' : '+7 day'), $base);
break;
case 'month':
$base = strtotime(($sign === -1 ? '-1 month' : '+1 month'), $base);
break;
}
$this->WriteAttributeString(self::ATTR_DATE, date('Y-m-d', $base));
}
private function isValidDate(string $ymd): bool
{
// expects YYYY-MM-DD
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $ymd)) {
return false;
}
[$y, $m, $d] = array_map('intval', explode('-', $ymd));
return checkdate($m, $d, $y);
}
}
?>