Hi, sowas ähnliches hatte ich mir vor einiger Zeit mit der Ki gebaut.
ich wollte aber wissen ob es sich lohnt, noch eine Batterie hinzustellen (schon 2x 15kWh vorhanden)
wie sich herausgestellt hat, NEIN (zumindest im aktuellen Zustand)
meine Logik dahinter war:
An welchem Tag im Jahr habe ich eingespeist UND war am nächsten früh die Batterie am min SoC angekommen (denn nur in diesem Fall hätte ich meiner Meinung nach mit einem größeren Akku etwas sparen können)
das Script listet genau die Tage auf:
=== Analyse 2025-01-01 bis 2025-12-31 (SoC≤Limit±3%, BEIDE Bedingungen) ===
Logging: Einspeisung=AKTIV SOC=AKTIV Limit=AKTIV
Monat laden: 2025-01
Monat laden: 2025-02
Monat laden: 2025-03
Monat laden: 2025-04
Monat laden: 2025-05
Monat laden: 2025-06
Monat laden: 2025-07
Monat laden: 2025-08
Monat laden: 2025-09
Monat laden: 2025-10
Monat laden: 2025-11
Monat laden: 2025-12
Top 15 Matches (Einspeisung>3kWh UND SoC≤Limit±3%):
Tag | E(kWh) | heute(lim%) | morgen(lim%)
2025-02-03 | 9,80 | JA(50%) | NEIN(50%)
2025-11-22 | 9,70 | NEIN(40%) | JA(60%)
Summary 2025-01-01 bis 2025-12-31: matches=2, Potenzial_kWh=19.50
Kosten: vermieden=7.41€, vergütung=1.46€, netto=5.95€
Amortisation (ohne Hochrechnung): 218.58 Jahre
→ ZEIGT: Zusätzlicher Akku würde 19.5kWh/Jahr speichern!
im Script habe ich angegeben welche Kosten alles eine Rolle spielen, bei der Batt bin ich mit 1300 Euro rangegangen (was den Kosten entspricht wenn ich ihn selbst baue)
hier mein Script, evtl findest du noch ein paar sinvolle Dinge für dein Modul falls du es erweitern willst:
<?php
// =================== Konfiguration ===================
$archiveID = 15951;
$pvIDs = [25769, 52353, 21906]; // PV: Garage Nord, Ost, West
$varEins = 28197; // Einspeisung (kWh, zählt am Tag hoch)
$varSOC = 31049; // Batterie SoC (%), geloggt bei Änderung
$varMinSOCLimit = 36308; // Min-SoC-Limit (%), manueller Untergrenzwert
// Analysezeitraum 2025 (bis 02.12.2025)
$rangeLabel = '2025-01-01 bis 2025-12-31';
$t0 = strtotime("2025-01-01 00:00:00");
$t1 = strtotime("2025-12-31 23:59:59");
// Wirtschaftlichkeit
$preisBezug = 0.38; // €/kWh
$preisEinspeise = 0.075; // €/kWh
$invest = 1300.0; // €
// Fenster-/Datenqualität
$fallbackEveningHour = 18; // Fallback PV-Stop
$fallbackMorningHour = 10; // Fallback PV-Start
$socTolerance = 3.0; // ±3% Toleranz um Limit
$minSocPoints = 1; // min Messpunkte (reduziert wg. Toleranz)
$showLimitPerMatch = true; // ← NEU: Limits in Top-15 anzeigen
// =================== Hilfsfunktionen ===================
function getLogsSorted($archiveID,$var,$t0,$t1){
$a=@AC_GetLoggedValues($archiveID,$var,$t0,$t1,0);
if(!is_array($a)) $a=[];
usort($a,function($x,$y){return $x['TimeStamp']<=>$y['TimeStamp'];});
return $a;
}
function socClean($logs){
$o=[]; foreach($logs as $r){ $v=$r['Value']; if(is_numeric($v)&&$v>=0&&$v<=100) $o[]=$r; } return $o;
}
function dayKey($ts){ return date('Y-m-d',$ts); }
function sumPositiveDeltasPerDay($logs){
$sum=0.0;
if(count($logs)==1 && is_numeric($logs[0]['Value']) && $logs[0]['Value']>0) return $logs[0]['Value'];
for($i=1;$i<count($logs);$i++){
$d=$logs[$i]['Value']-$logs[$i-1]['Value'];
if(is_numeric($d) && $d>0) $sum+=$d;
}
return $sum;
}
function monthStartTS($y,$m){ return strtotime(sprintf("%04d-%02d-01 00:00:00",$y,$m)); }
function monthEndTS($y,$m){ return strtotime(date('Y-m-t 23:59:59', monthStartTS($y,$m))); }
function pvStopOnDay($archiveID,$pvIDs,$dayStart,$dayEnd){
$lastIncTS=null;
foreach($pvIDs as $vid){
$L=getLogsSorted($archiveID,$vid,$dayStart,$dayEnd);
for($i=1;$i<count($L);$i++){
$inc=$L[$i]['Value']-$L[$i-1]['Value'];
if(is_numeric($inc)&&$inc>0) $lastIncTS = max($lastIncTS??0,$L[$i]['TimeStamp']);
}
}
return $lastIncTS;
}
function pvStartNextDay($archiveID,$pvIDs,$nextStart,$nextEnd){
$firstIncTS=null;
foreach($pvIDs as $vid){
$L=getLogsSorted($archiveID,$vid,$nextStart,$nextEnd);
if(count($L)>0){
$base=$L[0]['Value'];
for($i=1;$i<count($L);$i++){
if(is_numeric($L[$i]['Value']) && $L[$i]['Value']>$base){
$firstIncTS = min($firstIncTS??PHP_INT_MAX,$L[$i]['TimeStamp']);
break;
}
}
}
}
return $firstIncTS;
}
function socHitLimitInRange($logs,$tA,$tB,$limit,&$count,&$minHit){
$count=0; $minHit=null; $hits=[];
foreach($logs as $r){
$ts=$r['TimeStamp']; if($ts<$tA||$ts>$tB) continue;
$v=$r['Value']; if(!is_numeric($v)) continue;
$count++;
if(abs($v - $limit) <= 3.0) { // ±3% Toleranz
$hits[]=$v;
if($minHit===null || $v<$minHit) $minHit=$v;
}
}
return count($hits)>0;
}
// =================== Logging-Check ===================
$okE = @AC_GetLoggingStatus($archiveID,$varEins);
$okS = @AC_GetLoggingStatus($archiveID,$varSOC);
$okL = @AC_GetLoggingStatus($archiveID,$varMinSOCLimit);
echo "=== Analyse $rangeLabel (SoC≤Limit±3%, BEIDE Bedingungen) ===\n";
echo "Logging: Einspeisung=".($okE?'AKTIV':'INAKTIV')." SOC=".($okS?'AKTIV':'INAKTIV')." Limit=".($okL?'AKTIV':'INAKTIV')."\n";
if(!$okE||!$okS||!$okL){ die("Abbruch: fehlendes Logging.\n"); }
// =================== Monats-Batches ===================
$y0=intval(date('Y',$t0)); $y1=intval(date('Y',$t1));
$m0=intval(date('n',$t0)); $m1=intval(date('n',$t1));
$lastKnownLimit = 10;
$E_day=[]; $limitPerDay=[]; $socLogsByMonth=[];
for($y=$y0;$y<=$y1;$y++){
$mStart=($y==$y0)?$m0:1; $mEnd=($y==$y1)?$m1:12;
for($m=$mStart;$m<=$mEnd;$m++){
$ms=monthStartTS($y,$m); $me=monthEndTS($y,$m);
if($ms<$t0) $ms=$t0; if($me>$t1) $me=$t1;
echo "Monat laden: ".date('Y-m',$ms)."\n";
$logsE = getLogsSorted($archiveID,$varEins,$ms,$me);
$byDayE = [];
foreach($logsE as $r){ $byDayE[dayKey($r['TimeStamp'])][]=$r; }
foreach($byDayE as $d=>$arr){ $E_day[$d] = sumPositiveDeltasPerDay($arr); }
$logsL = getLogsSorted($archiveID,$varMinSOCLimit,$ms,$me);
$lastPerDay=[];
foreach($logsL as $r){ $lastPerDay[dayKey($r['TimeStamp'])]=$r['Value']; }
ksort($lastPerDay);
for($ts=$ms;$ts<=$me;$ts+=86400){
$d=dayKey($ts);
if(isset($lastPerDay[$d])) $lastKnownLimit=$lastPerDay[$d];
$limitPerDay[$d]=$lastKnownLimit;
}
$socLogsByMonth[$m] = socClean(getLogsSorted($archiveID,$varSOC,$ms,$me));
}
}
// =================== Analyse pro Tag (BEIDE Bedingungen) ===================
$allDays=[]; for($ts=$t0;$ts<=$t1;$ts+=86400){ $allDays[]=dayKey($ts); }
$matches=[];
for($i=0;$i<count($allDays);$i++){
$d = $allDays[$i];
$dayStart = strtotime("$d 00:00:00");
$dayEnd = strtotime("$d 23:59:59");
$E = $E_day[$d] ?? 0.0;
// BEDINGUNG 1: Einspeisung > 0
if($E<=3.0) continue;
$pvStop = pvStopOnDay($archiveID,$pvIDs,$dayStart,$dayEnd);
if($pvStop===null) $pvStop = strtotime("$d ".$fallbackEveningHour.":00:00");
// BEDINGUNG 2: SoC ≤ Limit±3% (heute Abend ODER morgen früh)
$hitToday=false; $hitTomorrow=false; $eveMin=null; $morMin=null;
// Abend-Check (heute nach PV-Stop)
$m = intval(date('n',$dayStart));
$socMonth = $socLogsByMonth[$m] ?? [];
$cntE=0;
$limToday = $limitPerDay[$d] ?? 10;
$hitToday = socHitLimitInRange($socMonth, $pvStop, $dayEnd, $limToday, $cntE, $eveMin);
// Morgen-Check (nächster Tag bis PV-Start)
if($i+1 < count($allDays)){
$dNext = $allDays[$i+1];
$nStart=strtotime("$dNext 00:00:00");
$nEnd =strtotime("$dNext 23:59:59");
$pvStart = pvStartNextDay($archiveID,$pvIDs,$nStart,$nEnd);
if($pvStart===null) $pvStart = strtotime("$dNext ".$fallbackMorningHour.":00:00");
$m2=intval(date('n',strtotime($dNext)));
$socMonth2 = $socLogsByMonth[$m2] ?? [];
$cntM=0;
$limNext = $limitPerDay[$dNext] ?? $limToday;
$hitTomorrow = socHitLimitInRange($socMonth2, $nStart, $pvStart, $limNext, $cntM, $morMin);
}
// BEIDE Bedingungen erfüllt?
if($hitToday || $hitTomorrow){
$matches[] = [
'day'=>$d,'E'=>$E,
'hitToday'=>$hitToday,'eveMin'=>$eveMin,'limToday'=>$limToday,
'hitTomorrow'=>$hitTomorrow,'morMin'=>$morMin,'limNext'=>$limNext??$limToday
];
}
}
// =================== Ausgabe ===================
usort($matches,function($a,$b){ return $b['E'] <=> $a['E']; });
echo "Top 15 Matches (Einspeisung>3kWh UND SoC≤Limit±3%):\n";
echo "Tag | E(kWh) | heute(lim%) | morgen(lim%)\n";
for($i=0;$i<min(15,count($matches));$i++){
$m=$matches[$i];
printf("%s | %6.2f | %s(%.0f%%) | %s(%.0f%%)\n",
$m['day'],$m['E'],
($m['hitToday']?'JA':'NEIN'),$m['limToday'],
($m['hitTomorrow']?'JA':'NEIN'),$m['limNext']);
}
$sumE=0.0; foreach($matches as $m){ $sumE+=$m['E']; }
$vermieden = $sumE*$preisBezug;
$verguet = $sumE*$preisEinspeise;
$netto = $vermieden - $verguet;
echo "\nSummary $rangeLabel: matches=".count($matches).", Potenzial_kWh=".number_format($sumE,2)."\n";
echo "Kosten: vermieden=".number_format($vermieden,2)."€, vergütung=".number_format($verguet,2)."€, netto=".number_format($netto,2)."€\n";
if($netto>0){
$amort = $invest/$netto;
echo "Amortisation (ohne Hochrechnung): ".number_format($amort,2)." Jahre\n";
}else{
echo "Amortisation: nicht sinnvoll (netto <= 0)\n";
}
echo "→ ZEIGT: Zusätzlicher Akku würde ".number_format($sumE,1)."kWh/Jahr speichern!\n";
?>
das Script durchsucht rückwirkend den angegebenen Zeitraum im Archiv und holt sich alles, dauert also einen Moment