Files
Symcon_Belevo_Energiemanage…/Abrechnung/module.php
2025-12-11 14:10:21 +01:00

814 lines
29 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);
include_once __DIR__ . '/libs/vendor/autoload.php'; // TCPDF via Composer
/**
* Eigene TCPDF-Klasse mit Logo im Header und Text in der Fusszeile
*/
class InvoicePDF extends TCPDF
{
public $logoFile = null;
public $footerText = '';
public function Header()
{
if ($this->logoFile !== null && file_exists($this->logoFile)) {
// x, y, width in mm
$this->Image($this->logoFile, 15, 10, 40);
$this->SetY(28);
} else {
$this->SetY(15);
}
}
public function Footer()
{
$this->SetY(-15);
$this->SetFont('dejavusans', '', 8);
$this->Cell(0, 10, $this->footerText, 0, 0, 'C');
}
}
class Abrechnung extends IPSModule
{
public function Create()
{
parent::Create();
$this->RegisterPropertyString('Users', '[]');
$this->RegisterPropertyString('PowerMeters', '[]');
$this->RegisterPropertyString('WaterMeters', '[]');
$this->RegisterPropertyString('Tariffs', '[]');
$this->RegisterPropertyInteger('LogoMediaID', 0);
$this->RegisterPropertyString('PropertyText', 'Liegenschaft');
$this->RegisterPropertyString('FooterText', 'Belevo AG • 6122 Menznau • www.belevo.ch');
$this->RegisterVariableInteger('FromDate', 'Startdatum', '~UnixTimestamp', 1);
$this->RegisterVariableInteger('ToDate', 'Enddatum', '~UnixTimestamp', 2);
$this->RegisterVariableString('LastResult', 'Letzte Abrechnung', '', 3);
$this->EnableAction('FromDate');
$this->EnableAction('ToDate');
$this->RegisterScript(
'StartBilling',
'Abrechnung starten',
"<?php IPS_RequestAction(" . $this->InstanceID . ", 'StartBilling', ''); ?>"
);
$this->RegisterMediaDocument('InvoicePDF', 'Letzte Rechnung', 'pdf');
}
public function ApplyChanges()
{
parent::ApplyChanges();
}
private function RegisterMediaDocument($Ident, $Name, $Extension, $Position = 0)
{
$mid = @IPS_GetObjectIDByIdent($Ident, $this->InstanceID);
if ($mid === false) {
$mid = IPS_CreateMedia(5);
IPS_SetParent($mid, $this->InstanceID);
IPS_SetIdent($mid, $Ident);
IPS_SetName($mid, $Name);
IPS_SetPosition($mid, $Position);
IPS_SetMediaFile($mid, 'media/' . $mid . '.' . $Extension, false);
}
}
public function RequestAction($Ident, $Value)
{
switch ($Ident) {
case 'FromDate':
case 'ToDate':
SetValue($this->GetIDForIdent($Ident), $Value);
break;
case 'StartBilling':
try {
$pdfContent = $this->GenerateInvoices();
if (!$pdfContent) {
echo "❌ Fehler bei der PDF-Erstellung";
return;
}
$mediaID = $this->GetIDForIdent('InvoicePDF');
IPS_SetMediaContent($mediaID, base64_encode($pdfContent));
SetValue($this->GetIDForIdent('LastResult'), 'Abrechnung vom ' . date('d.m.Y H:i'));
echo "✅ PDF erfolgreich erstellt.";
} catch (Throwable $e) {
echo "❌ Ausnahmefehler: " . $e->getMessage();
}
break;
}
}
// ====================== PDF-Erstellung ======================
public function GenerateInvoices()
{
$from = GetValue($this->GetIDForIdent('FromDate'));
$to = GetValue($this->GetIDForIdent('ToDate'));
$users = json_decode($this->ReadPropertyString('Users'), true);
$power = json_decode($this->ReadPropertyString('PowerMeters'), true);
$water = json_decode($this->ReadPropertyString('WaterMeters'), true);
$tariffs = json_decode($this->ReadPropertyString('Tariffs'), true);
if (!class_exists('TCPDF')) {
return false;
}
// Stromkosten einmal für alle User berechnen (15-Minuten-Logik)
$this->CalculateAllPowerCosts($power, $tariffs, $from, $to);
// Logo & Fusszeile vorbereiten
$logoFile = $this->GetLogoFile();
$footerText = $this->ReadPropertyString('FooterText');
// PDF-Objekt
$pdf = new InvoicePDF('P', 'mm', 'A4', true, 'UTF-8', false);
$pdf->logoFile = $logoFile;
$pdf->footerText = $footerText;
$pdf->SetCreator('IPSymcon Abrechnung');
// Oben mehr Platz für Logo lassen
$pdf->SetMargins(15, 35, 15);
$pdf->SetAutoPageBreak(true, 20);
$pdf->SetFont('dejavusans', '', 8);
foreach ($users as $user) {
$pdf->AddPage();
$this->BuildUserInvoice($pdf, $user, $power, $water, $tariffs, $from, $to);
}
return $pdf->Output('Abrechnung.pdf', 'S');
}
private function BuildUserInvoice($pdf, $user, $power, $water, $tariffs, $from, $to)
{
// Kopfbereich
$html = "
<h1 style='text-align:center; margin-bottom:0;'>Elektro- und Nebenkostenabrechnung</h1>
<h4 style='text-align:center; margin-bottom:0;'>Betrifft Liegenschaft: " . $this->ReadPropertyString("PropertyText") . " </h4>
<hr>
<table width='100%' style='font-size:10px;'>
<tr>
<td width='60%'>
<strong>Zählpunkte:</strong><br>";
// Alle Zählerpunkte des Users auflisten
foreach ($power as $m) {
if ($m['user_id'] == $user['id']) {
$html .= htmlspecialchars($m['name']) . "<br>";
}
}
foreach ($water as $m) {
if ($m['user_id'] == $user['id']) {
$html .= htmlspecialchars($m['name']) . "<br>";
}
}
$html .= "
</td>
<td width='40%'>
<strong>Rechnungsadresse:</strong><br>
{$user['name']}<br>
{$user['address']}<br>
{$user['city']}<br>
</td>
</tr>
</table>
<p style='font-size:8px; margin-top:10px;'>
<strong>Abrechnungszeitraum:</strong> " . date('d.m.Y', $from) . " " . date('d.m.Y', $to) . "
</p>
<hr><br>
";
// ========================= Elektrizität =========================
$html .= "<br><h2 style='margin-bottom:3px;margin-top:10px; font-size:10px;'>Elektrizität</h2>";
$powerResult = $this->GetCalculatedPowerCosts($user['id']);
$html .= $powerResult['html'];
$totalPower = $powerResult['sum'];
// Elektrizitätstarife anzeigen
$appliedTariffs = $this->CollectTariffsForUser($tariffs, ['Netztarif', 'Solartarif', 'Einspeisetarif']);
if (!empty($appliedTariffs)) {
$html .= "<p style='font-size:8px; margin-top:4px;'><strong>Angewendete Elektrizitätstarife:</strong></p>
<ul style='font-size:8px;'>";
foreach ($appliedTariffs as $t) {
$html .= "<li>"
. date('d.m.Y', $t['start']) . " " . date('d.m.Y', $t['end'])
. " — <strong>" . number_format($t['price'], 2) . " Rp/kWh</strong>"
. "</li>";
}
$html .= "</ul><br><hr><br>";
}
// ========================= Nebenkosten =========================
$html .= "<br><h2 style='margin-bottom:3px; font-size:10px;'>Nebenkosten</h2>";
$additionalResult = $this->CalculateAdditionalCosts($water, $tariffs, $user['id'], $from, $to);
$html .= $additionalResult['html'];
$totalAdditional = $additionalResult['sum'];
// ========================= Gesamttotal =========================
$grandTotal = $totalPower + $totalAdditional;
$html .= "
<h2 style='text-align:right; margin:5px 0 0 0; font-size:11px;'>
Gesamttotal: <strong>CHF " . number_format($grandTotal, 2) . ".-</strong>
</h2>
";
$pdf->writeHTML($html, true, false, true, false, '');
}
// ====================== Stromkosten (15-Minuten, alle User) ======================
private function CalculateAllPowerCosts($powerMeters, $tariffs, $from, $to)
{
$this->powerCostCache = [];
// Zähler je User aufbauen
$metersByUser = [];
foreach ($powerMeters as $m) {
if (!isset($m['user_id'])) {
continue;
}
$userId = (int)$m['user_id'];
$name = $m['name'] ?? ('Meter_' . $m['user_id']);
if (!isset($metersByUser[$userId])) {
$metersByUser[$userId] = [];
}
$metersByUser[$userId][$name] = [
'importVar' => $m['var_consumption'] ?? null,
'exportVar' => $m['var_feed'] ?? null
];
if (!isset($this->powerCostCache[$userId])) {
$this->powerCostCache[$userId] = [];
}
$this->powerCostCache[$userId][$name] = [
'name' => $name,
'imp' => 0.0,
'exp' => 0.0,
'solar_bezug' => 0.0,
'netz_bezug' => 0.0,
'solareinspeisung' => 0.0,
'solarverkauf' => 0.0,
'cost_solar' => 0.0,
'cost_grid' => 0.0,
'rev_feedin' => 0.0
];
}
if (empty($metersByUser)) {
return;
}
// 15-Minuten-Schritte
for ($ts = $from; $ts < $to; $ts += 900) {
$slotEnd = min($to, $ts + 900);
// Tarife (gleich für alle User)
$pGrid = $this->getTariffPriceAt($tariffs, ['Netztarif'], $ts);
$pSolar = $this->getTariffPriceAt($tariffs, ['Solartarif'], $ts);
$pFeed = $this->getTariffPriceAt($tariffs, ['Einspeisetarif'], $ts);
foreach ($metersByUser as $userId => $meters) {
$impTotal = 0.0;
$expTotal = 0.0;
$slot = [];
// Deltas je Zähler des Users
foreach ($meters as $name => $mm) {
IPS_LogMessage("KostenModul", "---- Zähler '$name' wird verarbeitet ----");
$impDelta = 0.0;
$expDelta = 0.0;
// IMPORT
if (!empty($mm['importVar'])) {
$varId = (int)$mm['importVar'];
if (IPS_VariableExists($varId)) {
IPS_LogMessage("KostenModul", "Import-Variable vorhanden: $varId");
$impDelta = $this->getDeltaFromArchive($varId, $ts, $slotEnd);
IPS_LogMessage("KostenModul", "importDelta = $impDelta");
} else {
IPS_LogMessage("KostenModul", "Import-Variable existiert NICHT: $varId");
}
} else {
IPS_LogMessage("KostenModul", "Keine importVar für '$name' definiert.");
}
// EXPORT
if (!empty($mm['exportVar'])) {
$varId = (int)$mm['exportVar'];
if (IPS_VariableExists($varId)) {
IPS_LogMessage("KostenModul", "Export-Variable vorhanden: $varId");
$expDelta = $this->getDeltaFromArchive($varId, $ts, $slotEnd);
IPS_LogMessage("KostenModul", "exportDelta = $expDelta");
} else {
IPS_LogMessage("KostenModul", "Export-Variable existiert NICHT: $varId");
}
} else {
IPS_LogMessage("KostenModul", "Keine exportVar für '$name' definiert.");
}
// Nur hinzufügen, wenn Werte vorhanden sind
if ($impDelta > 0.0 || $expDelta > 0.0) {
IPS_LogMessage("KostenModul", "Zähler '$name' wird berücksichtigt: imp=$impDelta | exp=$expDelta");
$slot[$name] = [
'imp' => $impDelta,
'exp' => $expDelta
];
$impTotal += $impDelta;
$expTotal += $expDelta;
} else {
IPS_LogMessage("KostenModul", "Zähler '$name' hat keine Deltas > 0 wird übersprungen.");
}
IPS_LogMessage("KostenModul", "---- Ende Zähler '$name' ----");
}
// Logging der Eingangswerte
IPS_LogMessage("KostenModul", "impTotal: $impTotal | expTotal: $expTotal");
// Fall 1: Weder Import noch Export -> nichts zu berechnen
if ($impTotal == 0.0 && $expTotal == 0.0) {
IPS_LogMessage("KostenModul", "Beide Werte sind 0 -> continue()");
continue;
}
// Verhältnis PV / Netz pro User
if ($impTotal == 0.0 && $expTotal > 0.0) {
// Kein Import, aber Export → PV deckt alles
$ratio = 0.0;
$pvCoversAll = true;
IPS_LogMessage("KostenModul", "Fall: impTotal=0, expTotal>0 -> PV deckt alles");
} elseif ($expTotal == 0.0 && $impTotal > 0.0) {
// Kein Export, aber Import → Netz deckt alles
$ratio = 0.0;
$pvCoversAll = false;
IPS_LogMessage("KostenModul", "Fall: expTotal=0, impTotal>0 -> Netz deckt alles");
} elseif ($impTotal <= $expTotal) {
// PV produziert genug oder mehr
$ratio = $expTotal > 0 ? ($impTotal / $expTotal) : 0.0;
$pvCoversAll = true;
IPS_LogMessage("KostenModul", "Fall: impTotal <= expTotal -> PV deckt genug");
} else {
// Import ist größer als Export → Netz deckt den Rest
$ratio = $impTotal > 0 ? ($expTotal / $impTotal) : 0.0;
$pvCoversAll = false;
IPS_LogMessage("KostenModul", "Fall: impTotal > expTotal -> Netz deckt den Rest");
}
IPS_LogMessage("KostenModul", "Ergebnis ratio=$ratio | pvCoversAll=" . ($pvCoversAll ? "true" : "false"));
// Werte pro Zähler verteilen
foreach ($slot as $name => $v) {
$imp = $v['imp'];
$exp = $v['exp'];
$this->powerCostCache[$userId][$name]['imp'] += $imp;
$this->powerCostCache[$userId][$name]['exp'] += $exp;
if ($pvCoversAll) {
// PV deckt gesamten Verbrauch
$this->powerCostCache[$userId][$name]['solar_bezug'] += $imp;
$this->powerCostCache[$userId][$name]['solareinspeisung'] += (1 - $ratio) * $exp;
$this->powerCostCache[$userId][$name]['solarverkauf'] += $ratio * $exp;
if ($pSolar !== null) {
$this->powerCostCache[$userId][$name]['cost_solar'] += ($imp * $pSolar) / 100.0;
}
if ($pFeed !== null) {
$this->powerCostCache[$userId][$name]['rev_feedin'] += ((1 - $ratio) * $exp * $pFeed) / 100.0;
}
} else {
// Teil Netzbezug
$this->powerCostCache[$userId][$name]['solar_bezug'] += $ratio * $imp;
$this->powerCostCache[$userId][$name]['netz_bezug'] += (1 - $ratio) * $imp;
$this->powerCostCache[$userId][$name]['solarverkauf'] += $exp;
if ($pGrid !== null) {
$this->powerCostCache[$userId][$name]['cost_grid'] += ((1 - $ratio) * $imp * $pGrid) / 100.0;
}
if ($pSolar !== null) {
$this->powerCostCache[$userId][$name]['cost_solar'] += ($ratio * $imp * $pSolar) / 100.0;
}
if ($pFeed !== null) {
$this->powerCostCache[$userId][$name]['rev_feedin'] += ($exp * $pFeed) / 100.0;
}
}
}
}
}
}
private function GetCalculatedPowerCosts($userId)
{
$html = "
<table border='1' cellspacing='0' cellpadding='2' width='100%' style='font-size:4px;'>
<tr style='background-color:#f0f0f0;'>
<th>ID</th>
<th>Import</th>
<th>Export</th>
<th>ZEV-Haus</th>
<th>Netz-Haus</th>
<th>Solar-Netz</th>
<th>Solar-ZEV</th>
<th>Kauf-Solar</th>
<th>Kauf-Netz</th>
<th>Verkauf</th>
<th>Total CHF</th>
</tr>";
if (empty($this->powerCostCache) || !isset($this->powerCostCache[$userId])) {
$html .= "<tr><td colspan='11' align='center'>Keine Stromzähler für diesen Benutzer</td></tr></table><br>";
return ['html' => $html, 'sum' => 0.0];
}
$sum = 0.0;
foreach ($this->powerCostCache[$userId] as $name => $a) {
$subtotal = $a['cost_grid'] + $a['cost_solar'] - $a['rev_feedin'];
$sum += $subtotal;
$html .= "<tr>
<td>{$a['name']}</td>
<td align='right'>" . number_format($a['imp'], 3) . " kWh</td>
<td align='right'>" . number_format($a['exp'], 3) . " kWh</td>
<td align='right'>" . number_format($a['solar_bezug'], 3) . " kWh</td>
<td align='right'>" . number_format($a['netz_bezug'], 3) . " kWh</td>
<td align='right'>" . number_format($a['solareinspeisung'], 3) . " kWh</td>
<td align='right'>" . number_format($a['solarverkauf'], 3) . " kWh</td>
<td align='right'>CHF " . number_format($a['cost_solar'], 2) . ".-</td>
<td align='right'>CHF " . number_format($a['cost_grid'], 2) . ".-</td>
<td align='right'>CHF " . number_format($a['rev_feedin'], 2) . ".-</td>
<td align='right'>CHF " . number_format($subtotal, 2) . ".-</td>
</tr>";
}
$html .= "<tr style='background-color:#f9f9f9; font-weight:bold;'>
<td colspan='25' align='right'><b>Total</b></td>
<td align='right'></td>
<td align='right'></td>
<td align='right'></td>
<td align='right'></td>
<td align='right'></td>
<td align='right'></td>
<td align='right'></td>
<td align='right'></td>
<td align='right'></td>
<td align='right'><b>CHF " . number_format($sum, 2) . ".-</b></td>
</tr>";
return ['html' => $html, 'sum' => $sum];
}
// ====================== Nebenkosten Wasser/Wärme ======================
private function CalculateAdditionalCosts($waterMeters, $tariffs, $userId, $from, $to)
{
$html = "
<table border='1' cellspacing='0' cellpadding='2' width='100%' style='font-size:4px;'>
<tr style='background-color:#f0f0f0;'>
<th>ID</th>
<th>Typ</th>
<th>Datum von</th>
<th>Datum bis</th>
<th>Zähler Start</th>
<th>Zähler Ende</th>
<th>Verbrauch</th>
<th>Tarif</th>
<th>Kosten CHF</th>
</tr>";
$total = 0.0;
$usedTariffs = [];
foreach ($waterMeters as $m) {
if ($m['user_id'] != $userId) {
continue;
}
$type = $m['meter_type'] ?? 'Warmwasser';
$cost = $this->AddMeterToPDFRow($m, $tariffs, $from, $to, $type);
$html .= $cost['row'];
$total += $cost['value'];
$usedTariffs = array_merge($usedTariffs, $cost['tariffs']);
}
$html .= "<tr style='background-color:#f9f9f9; font-weight:bold;'>
<td colspan='25' align='right'><b>Total</b></td>
<td align='right'></td>
<td align='right'></td>
<td align='right'></td>
<td align='right'></td>
<td align='right'></td>
<td align='right'></td>
<td align='right'></td>
<td align='right'><b>CHF " . number_format($total, 2) . ".-</b></td>
</tr>";
if (!empty($usedTariffs)) {
$html .= "<p><strong>Angewendete Nebenkostentarife</strong></p><ul>";
foreach ($usedTariffs as $t) {
$html .= "<li>" . date('d.m.Y', $t['start']) . " " . date('d.m.Y', $t['end']) . " — <strong> " .
number_format($t['price'], 2) . " Rp/m3</strong></li>";
}
$html .= "</ul><br><hr>";
}
return ['html' => $html, 'sum' => $total];
}
private function AddMeterToPDFRow($meter, $tariffs, $from, $to, $type)
{
$rows = '';
$totalCost = 0.0;
$usedTariffs = [];
$varId = $meter['var_consumption'];
if (!IPS_VariableExists($varId)) {
return ['row' => '', 'value' => 0, 'tariffs' => []];
}
// Relevante Tarife nach Typ filtern
$filteredTariffs = array_filter($tariffs, fn($t) =>
strtolower(trim($t['unit_type'] ?? '')) === strtolower(trim($type))
);
$activeTariff = null;
foreach ($filteredTariffs as $t) {
$startTs = $this->toUnixTs($t['start'], false);
$endTs = $this->toUnixTs($t['end'], true);
if ($startTs === null || $endTs === null) {
continue;
}
// Tarif überlappt den Abrechnungszeitraum
if ($endTs < $from || $startTs > $to) {
continue;
}
$activeTariff = [
'start' => $startTs,
'end' => $endTs,
'price' => (float)$t['price']
];
$usedTariffs[] = $activeTariff;
break; // erster passender Tarif reicht
}
$tariffPrice = $activeTariff ? $activeTariff['price'] : 0.0;
// Verbrauch = letzter Wert vor/gleich "to" minus letzter Wert vor/gleich "from"
$startValue = $this->GetValueAt($varId, $from, false);
$endValue = $this->GetValueAt($varId, $to, false);
if ($startValue === null || $endValue === null) {
return ['row' => '', 'value' => 0, 'tariffs' => []];
}
$verbrauch = max(0, $endValue - $startValue);
$kosten = round(($tariffPrice / 100) * $verbrauch, 2);
$totalCost = $kosten;
$rows .= "<tr>
<td>{$meter['name']}</td>
<td>{$type}</td>
<td align='center'>" . date('d.m.Y', $from) . "</td>
<td align='center'>" . date('d.m.Y', $to) . "</td>
<td align='right'>" . number_format($startValue, 2) . "</td>
<td align='right'>" . number_format($endValue, 2) . "</td>
<td align='right'>" . number_format($verbrauch, 2) . "</td>
<td align='right'>" . number_format($tariffPrice, 2) . "</td>
<td align='right'>" . number_format($kosten, 2) . "</td>
</tr>";
if ($rows === '') {
return ['row' => '', 'value' => 0, 'tariffs' => []];
}
return ['row' => $rows, 'value' => $totalCost, 'tariffs' => array_values($usedTariffs)];
}
// ====================== Hilfsfunktionen ======================
private function GetValueAt($varId, $timestamp, $nearestAfter = true)
{
$archiveID = @IPS_GetInstanceListByModuleID('{43192F0B-135B-4CE7-A0A7-1475603F3060}')[0];
if (!$archiveID || !IPS_VariableExists($varId)) {
return null;
}
// 30 Tage Fenster um den Zeitpunkt
$values = @AC_GetLoggedValues($archiveID, $varId, $timestamp - 30 * 86400, $timestamp + 30 * 86400, 0);
if (empty($values)) {
return (float)GetValue($varId);
}
$closest = null;
foreach ($values as $v) {
if ($nearestAfter && $v['TimeStamp'] >= $timestamp) {
// erster Wert NACH oder GENAU zum Timestamp
$closest = $v['Value'];
break;
} elseif (!$nearestAfter && $v['TimeStamp'] <= $timestamp) {
// letzter Wert DAVOR oder GENAU zum Timestamp
$closest = $v['Value'];
}
}
return $closest ?? (float)GetValue($varId);
}
private function getDeltaFromArchive(int $varId, int $tStart, int $tEnd): float
{
// Beide Werte: immer letzter geloggter Wert VOR/GENAU zum Zeitpunkt
$startValue = $this->GetValueAt($varId, $tStart, false);
$endValue = $this->GetValueAt($varId, $tEnd, false);
if ($startValue === null || $endValue === null) {
return 0.0;
}
$diff = $endValue - $startValue;
if ($diff < 0) {
// Sicherheitsnetz bei Zähler-Reset
$diff = 0.0;
}
return (float)$diff;
}
private function toUnixTs($val, $endOfDay = false)
{
if (is_int($val)) {
return $val;
}
if (is_numeric($val)) {
return (int)$val;
}
if (is_string($val)) {
$s = trim($val);
if ($s !== '' && $s[0] === '{') {
$obj = json_decode($s, true);
if (isset($obj['year'], $obj['month'], $obj['day'])) {
$time = $endOfDay ? '23:59:59' : '00:00:00';
return strtotime(sprintf(
'%04d-%02d-%02d %s',
$obj['year'],
$obj['month'],
$obj['day'],
$time
));
}
}
$ts = strtotime($s);
return $ts === false ? null : $ts;
}
return null;
}
private function readDelta($varId, $tStart, $tEnd)
{
if (!is_int($tStart)) {
$tStart = strtotime($tStart);
}
if (!is_int($tEnd)) {
$tEnd = strtotime($tEnd);
}
$archiveID = @IPS_GetInstanceListByModuleID('{43192F0B-135B-4CE7-A0A7-1475603F3060}')[0];
if (!$archiveID || !IPS_VariableExists($varId)) {
return 0.0;
}
$values = @AC_GetLoggedValues($archiveID, $varId, $tStart - 86400, $tEnd + 86400, 0);
if (empty($values)) {
return 0.0;
}
usort($values, fn($a, $b) => (int)$a['TimeStamp'] <=> (int)$b['TimeStamp']);
$vStart = null;
$vEnd = null;
foreach ($values as $v) {
if ($v['TimeStamp'] <= $tStart) {
$vStart = $v['Value'];
}
if ($v['TimeStamp'] <= $tEnd) {
$vEnd = $v['Value'];
}
if ($v['TimeStamp'] > $tEnd) {
break;
}
}
if ($vStart === null) {
$vStart = (float)GetValue($varId);
}
if ($vEnd === null) {
$vEnd = (float)GetValue($varId);
}
$diff = $vEnd - $vStart;
return ($diff < 0) ? 0.0 : $diff;
}
private function getTariffPriceAt($tariffs, $typeSynonyms, $ts)
{
$wanted = array_map('strtolower', $typeSynonyms);
$cands = [];
foreach ($tariffs as $t) {
$u = strtolower(trim($t['unit_type'] ?? ''));
if (!in_array($u, $wanted, true)) {
continue;
}
$s = $this->toUnixTs($t['start'], false);
$e = $this->toUnixTs($t['end'], true);
if (!$s || !$e) {
continue;
}
if ($s <= $ts && $ts <= $e) {
$cands[] = (float)$t['price'];
}
}
if (empty($cands)) {
return null;
}
return end($cands);
}
private function CollectTariffsForUser($tariffs, $types)
{
$result = [];
$wanted = array_map('strtolower', $types);
foreach ($tariffs as $t) {
$type = strtolower(trim($t['unit_type'] ?? ''));
if (!in_array($type, $wanted, true)) {
continue;
}
$start = $this->toUnixTs($t['start'], false);
$end = $this->toUnixTs($t['end'], true);
$result[] = [
'start' => $start,
'end' => $end,
'price' => (float)$t['price']
];
}
return $result;
}
private function GetLogoFile()
{
$mediaID = (int)$this->ReadPropertyInteger('LogoMediaID');
if ($mediaID <= 0 || !IPS_MediaExists($mediaID)) {
return null;
}
$media = IPS_GetMedia($mediaID);
$ext = 'png';
if (!empty($media['MediaFile'])) {
$extFromFile = pathinfo($media['MediaFile'], PATHINFO_EXTENSION);
if ($extFromFile !== '') {
$ext = $extFromFile;
}
}
$path = IPS_GetKernelDir() . 'media/logo_' . $mediaID . '.' . $ext;
// Bild aus der Symcon-Media-Datenbank extrahieren
$raw = IPS_GetMediaContent($mediaID); // base64
$bin = base64_decode($raw);
if ($bin === false) {
return null;
}
file_put_contents($path, $bin);
return $path;
}
}
?>