|
|
|
|
@@ -162,7 +162,11 @@ BEGIN {
|
|
|
|
|
|
|
|
|
|
# Versions History intern
|
|
|
|
|
my %vNotesIntern = (
|
|
|
|
|
"1.60.5" => "14.11.2025 ___csmSpecificEpieces: implement EPIECMAXOPHRS , ___batAdjustPowerByMargin: adjust pow with otpMargin ",
|
|
|
|
|
"1.60.7" => "19.11.2025 new special Reading BatRatio ",
|
|
|
|
|
"1.60.6" => "18.11.2025 _createSummaries: fix tdConFcTillSunset, _batSocTarget: apply 75% of tomorrow consumption ",
|
|
|
|
|
"1.60.5" => "16.11.2025 ___csmSpecificEpieces: implement EPIECMAXOPHRS , ___batAdjustPowerByMargin: adjust pow with otpMargin ".
|
|
|
|
|
"__solCast_ApiResponse: evaluation of httpheader ".
|
|
|
|
|
"___csmSpecificEpieces: fix {epiecAVG}{hour} calculation, edit comref, isConsumerLogOn: small fix ",
|
|
|
|
|
"1.60.4" => "13.11.2025 smoothValue as OOP implemantation, battery efficiency rework, edit comref, expand loadTarget by time target ".
|
|
|
|
|
"_batSocTarget: surplus for next day adjusted, some code changes ",
|
|
|
|
|
"1.60.3" => "06.11.2025 more preparation for barrierSoC, ___batFindMinPhWh: code change, new parameter ctrlBatSocManagementXX->barrierSoC ",
|
|
|
|
|
@@ -178,7 +182,7 @@ my %vNotesIntern = (
|
|
|
|
|
"_setreset: set reset is reworked with widgetList, aiData can be deleted by index ".
|
|
|
|
|
"_flowGraphic: new variable node2home_direction ".
|
|
|
|
|
"new sub askLogtime to avoid error logs too often, Forum: https://forum.fhem.de/index.php?msg=1350716 ",
|
|
|
|
|
"1.59.5" => "15.10.2025 new sub ___batAdjustPowerByMargin: implement optPower Safety margin decreasing proportionally to the linear surplus ".
|
|
|
|
|
"1.59.5" => "15.10.2025 new ___batAdjustPowerByMargin: implement optPower Safety margin decreasing proportionally to the linear surplus ".
|
|
|
|
|
"new Reading Battery_TargetAchievable_XX, _batSocTarget: minor code change ",
|
|
|
|
|
"1.59.4" => "14.10.2025 new subs, ctrlBatSocManagementXX: new key loadTarget, replace __batCapShareFactor by __batDeficitShareFactor ".
|
|
|
|
|
"__batChargeOptTargetPower: use pinmax if achievable==0, new ctrlBatSocManagementXX->stepSoC key ".
|
|
|
|
|
@@ -457,7 +461,7 @@ use constant {
|
|
|
|
|
CONDAYSLIDEMAX => 30, # max. Anzahl der Arrayelemente im Register pvCircular -> con_all / gcons_a -> <Tag>
|
|
|
|
|
WHISTREPEAT => 851, # Wiederholungsintervall Cache File Daten schreiben
|
|
|
|
|
EPIECMAXCYCLES => 10, # Anzahl Einschaltzyklen für verbraucherspezifische Energiestück Ermittlung (EnergyPieces)
|
|
|
|
|
EPIECMAXOPHRS => 5, # max. Anzahl ununterbrochene Betriebsstunden für Verbraucher ohne Cycle Switch (EnergyPieces)
|
|
|
|
|
EPIECMAXOPHRS => 10, # max. Anzahl ununterbrochene Betriebsstunden für Verbraucher ohne Cycle Switch (EnergyPieces)
|
|
|
|
|
|
|
|
|
|
MAXWEATHERDEV => 3, # max. Anzahl Wetter Devices (Attr setupWeatherDevX)
|
|
|
|
|
MAXBATTERIES => 3, # maximale Anzahl der möglichen Batterien
|
|
|
|
|
@@ -469,7 +473,9 @@ use constant {
|
|
|
|
|
MAXSOCDEF => 95, # default Wert (%) auf den die Batterie maximal aufgeladen werden soll bzw. als aufgeladen gilt
|
|
|
|
|
CARECYCLEDEF => 20, # default max. Anzahl Tage die zwischen der Batterieladung auf maxSoC liegen dürfen
|
|
|
|
|
BATSOCCHGDAY => 5, # Batterie: prozentuale SoC Anpassung pro Tag
|
|
|
|
|
|
|
|
|
|
PERCCONINSOC => 0.75, # Batterie SoC-Management: Anteilsfaktor für Verbrauch
|
|
|
|
|
STOREFFDEF => 87, # default Batterie Effizienz (https://www.energie-experten.org/erneuerbare-energien/photovoltaik/stromspeicher/wirkungsgrad)
|
|
|
|
|
|
|
|
|
|
GMFBLTO => 30, # Timeout Aholen Message File aus GIT
|
|
|
|
|
GMFILEREPEAT => 3600, # Base Wiederholungsuntervall Abholen Message File aus GIT
|
|
|
|
|
GMFILERANDOM => 10800, # Random AddOn zu GMFILEREPEAT
|
|
|
|
|
@@ -500,7 +506,6 @@ use constant {
|
|
|
|
|
PRDEF => 0.9, # default Performance Ratio (PR)
|
|
|
|
|
SFTYMARGIN_20 => 20, # Sicherheitszuschlag 20%
|
|
|
|
|
SFTYMARGIN_50 => 50, # Sicherheitszuschlag 50%
|
|
|
|
|
STOREFFDEF => 87, # default Batterie Effizienz (https://www.energie-experten.org/erneuerbare-energien/photovoltaik/stromspeicher/wirkungsgrad)
|
|
|
|
|
OTPDEADBAND => 10.0, # Smoother Standard OTP Power Schwellenwert für Änderungen
|
|
|
|
|
OTPALPHA => 1.0, # Smoother Standard OTP Power Alpha default
|
|
|
|
|
TEMPCOEFFDEF => -0.45, # default Temperaturkoeffizient Pmpp (%/°C) lt. Datenblatt Solarzelle
|
|
|
|
|
@@ -1442,7 +1447,7 @@ my %hef = (
|
|
|
|
|
"noSchedule" => { f => 1.00, m => 1.00, l => 1.00, mt => DEFMINTIME },
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
my %hcsr = ( # Funktiontemplate zur Erstellung optionaler Statistikreadings
|
|
|
|
|
my %hcsr = ( # Funktiontemplate zur Erstellung optionaler Statistikreadings
|
|
|
|
|
currentAPIinterval => { fnr => 1, fn => \&StatusAPIVal, par => '', par1 => '', unit => '', def => 0 }, # par = Parameter zur spezifischen Verwendung
|
|
|
|
|
lastretrieval_time => { fnr => 1, fn => \&StatusAPIVal, par => '', par1 => '', unit => '', def => '-' },
|
|
|
|
|
lastretrieval_timestamp => { fnr => 1, fn => \&StatusAPIVal, par => '', par1 => '', unit => '', def => '-' },
|
|
|
|
|
@@ -1464,6 +1469,7 @@ my %hcsr = (
|
|
|
|
|
BatPowerIn_Sum => { fnr => 5, fn => \&CurrentVal, par => 'batpowerinsum', par1 => '', unit => ' W', def => '-' },
|
|
|
|
|
BatPowerOut_Sum => { fnr => 5, fn => \&CurrentVal, par => 'batpoweroutsum', par1 => '', unit => ' W', def => '-' },
|
|
|
|
|
BatWeightedTotalSOC => { fnr => 2, fn => \&CurrentVal, par => 'batsoctotal', par1 => '', unit => ' %', def => 0 },
|
|
|
|
|
BatRatio => { fnr => 5, fn => \&CurrentVal, par => 'batRatio', par1 => '', unit => '', def => '-' },
|
|
|
|
|
SunHours_Remain => { fnr => 5, fn => \&CurrentVal, par => '', par1 => '', unit => '', def => 0 }, # fnr => 3 -> Custom Calc
|
|
|
|
|
SunMinutes_Remain => { fnr => 5, fn => \&CurrentVal, par => '', par1 => '', unit => '', def => 0 },
|
|
|
|
|
dayAfterTomorrowPVforecast => { fnr => 5, fn => \&CurrentVal, par => 'dayAfterTomorrowPVfc', par1 => '', unit => ' Wh', def => 0 },
|
|
|
|
|
@@ -3202,7 +3208,7 @@ sub __solCast_ApiResponse {
|
|
|
|
|
my $caller = $paref->{caller};
|
|
|
|
|
my $string = $paref->{string};
|
|
|
|
|
my $allstrings = $paref->{allstrings};
|
|
|
|
|
my $stc = $paref->{stc}; # Startzeit API Abruf
|
|
|
|
|
my $stc = $paref->{stc}; # Startzeit API Abruf
|
|
|
|
|
my $lang = $paref->{lang};
|
|
|
|
|
my $debug = $paref->{debug};
|
|
|
|
|
my $type = $paref->{type};
|
|
|
|
|
@@ -3211,8 +3217,33 @@ sub __solCast_ApiResponse {
|
|
|
|
|
|
|
|
|
|
my $msg;
|
|
|
|
|
my $hash = $defs{$name};
|
|
|
|
|
my $sta = [gettimeofday]; # Start Response Verarbeitung
|
|
|
|
|
my $sta = [gettimeofday]; # Start Response Verarbeitung
|
|
|
|
|
|
|
|
|
|
my $head = $paref->{httpheader} // 'empty header';
|
|
|
|
|
|
|
|
|
|
if ($head !~ /200.OK/ixs) { # Auswertung Header
|
|
|
|
|
___setSolCastAPIcallKeyData ($paref);
|
|
|
|
|
|
|
|
|
|
$data{$name}{statusapi}{SolCast}{'?All'}{response_message} = $head;
|
|
|
|
|
|
|
|
|
|
if ($head =~ /429.Too.Many.Requests/xs) {
|
|
|
|
|
$data{$name}{statusapi}{SolCast}{'?All'}{todayRemainingAPIrequests} = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
singleUpdateState ( {hash => $hash, state => $msg, evt => 1} );
|
|
|
|
|
$data{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln
|
|
|
|
|
$data{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln
|
|
|
|
|
|
|
|
|
|
if ($debug =~ /apiProcess|apiCall/x) {
|
|
|
|
|
my $apimaxreq = AttrVal ($name, 'ctrlSolCastAPImaxReq', SOLCMAXREQDEF);
|
|
|
|
|
|
|
|
|
|
Log3 ($name, 1, "$name DEBUG> SolCast API Call - Header response content: ".$head);
|
|
|
|
|
Log3 ($name, 1, "$name DEBUG> SolCast API Call - todayRemainingAPIrequests: ".StatusAPIVal ($hash, 'SolCast', '?All', 'todayRemainingAPIrequests', $apimaxreq));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($err ne "") {
|
|
|
|
|
$msg = 'SolCast API server response: '.$err;
|
|
|
|
|
|
|
|
|
|
@@ -3221,12 +3252,12 @@ sub __solCast_ApiResponse {
|
|
|
|
|
$data{$name}{statusapi}{SolCast}{'?All'}{response_message} = $err;
|
|
|
|
|
|
|
|
|
|
singleUpdateState ( {hash => $hash, state => $msg, evt => 1} );
|
|
|
|
|
$data{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln
|
|
|
|
|
$data{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln
|
|
|
|
|
$data{$name}{current}{runTimeLastAPIProc} = sprintf "%.4f", tv_interval($sta); # Verarbeitungszeit ermitteln
|
|
|
|
|
$data{$name}{current}{runTimeLastAPIAnswer} = sprintf "%.4f", (tv_interval($stc) - tv_interval($sta)); # API Laufzeit ermitteln
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
elsif ($myjson ne "") { # Evaluiere ob Daten im JSON-Format empfangen wurden
|
|
|
|
|
elsif ($myjson ne "") { # Evaluiere ob Daten im JSON-Format empfangen wurden
|
|
|
|
|
my ($success) = evaljson ($hash, $myjson);
|
|
|
|
|
|
|
|
|
|
if (!$success) {
|
|
|
|
|
@@ -3341,7 +3372,7 @@ sub __solCast_ApiResponse {
|
|
|
|
|
|
|
|
|
|
$k++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Log3 ($name, 4, qq{$name - SolCast API answer received for string "$string"});
|
|
|
|
|
|
|
|
|
|
@@ -3425,7 +3456,7 @@ sub ___setSolCastAPIcallKeyData {
|
|
|
|
|
$data{$name}{statusapi}{SolCast}{'?All'}{lastretrieval_time} = (timestampToTimestring ($t, $lang))[3]; # letzte Abrufzeit
|
|
|
|
|
$data{$name}{statusapi}{SolCast}{'?All'}{lastretrieval_timestamp} = $t; # letzter Abrufzeitstempel
|
|
|
|
|
|
|
|
|
|
my $apimaxreq = AttrVal ($name, 'ctrlSolCastAPImaxReq', SOLCMAXREQDEF);
|
|
|
|
|
my $apimaxreq = AttrVal ($name, 'ctrlSolCastAPImaxReq', SOLCMAXREQDEF);
|
|
|
|
|
my $mpl = StatusAPIVal ($hash, 'SolCast', '?All', 'solCastAPIcallMultiplier', 1);
|
|
|
|
|
my $ddc = StatusAPIVal ($hash, 'SolCast', '?All', 'todayDoneAPIcalls', 0);
|
|
|
|
|
|
|
|
|
|
@@ -7736,6 +7767,7 @@ sub _attrBatSocManagement { ## no critic "not used"
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
deleteReadingspec ($hash, 'Battery_.*');
|
|
|
|
|
$data{$name}{current}{'batRatio'.$bn};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
delete $data{$name}{circular}{99}{'lastTsMaxSocRchd'.$bn};
|
|
|
|
|
@@ -8386,7 +8418,7 @@ sub delConsumerFromMem {
|
|
|
|
|
my $hash = $defs{$name};
|
|
|
|
|
my $calias = ConsumerVal ($hash, $c, 'alias', '');
|
|
|
|
|
|
|
|
|
|
for my $d (1..31) {
|
|
|
|
|
for my $d (1..31) { # Consumer aus phHistory entfernen
|
|
|
|
|
$d = sprintf("%02d", $d);
|
|
|
|
|
delete $data{$name}{pvhist}{$d}{99}{"csme${c}"};
|
|
|
|
|
delete $data{$name}{pvhist}{$d}{99}{"cyclescsm${c}"};
|
|
|
|
|
@@ -8400,8 +8432,14 @@ sub delConsumerFromMem {
|
|
|
|
|
delete $data{$name}{pvhist}{$d}{$i}{"minutescsm${c}"};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for my $ridx (sort keys %{ $data{$name}{aidectree}{airaw} // {} }) { # Consumer aus AI Raw Data löschen
|
|
|
|
|
next unless defined $ridx && length $ridx;
|
|
|
|
|
|
|
|
|
|
delete $data{$name}{consumers}{$c};
|
|
|
|
|
delete $data{$name}{aidectree}{airaw}{$ridx}{'csme'.$c};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
delete $data{$name}{consumers}{$c}; # Consumerhash löschen
|
|
|
|
|
|
|
|
|
|
Log3 ($name, 2, qq{$name - Consumer "$c - $calias" deleted from memory});
|
|
|
|
|
|
|
|
|
|
@@ -11571,11 +11609,11 @@ sub _batSocTarget {
|
|
|
|
|
|
|
|
|
|
## erwartete PV ermitteln & Anteilsfaktor Bat anwenden
|
|
|
|
|
########################################################
|
|
|
|
|
my $pvfctm = ReadingsNum ($name, 'Tomorrow_PVforecast', 0); # PV Prognose morgen
|
|
|
|
|
my $constm = CurrentVal ($name, 'tomorrowConsHoursWithPVGen', 0); # Verbrauch während PV-Erzeugung
|
|
|
|
|
my $pvfctd = ReadingsNum ($name, 'RestOfDayPVforecast', 0); # PV Prognose Rest heute
|
|
|
|
|
my $pvfctm = ReadingsNum ($name, 'Tomorrow_PVforecast', 0); # PV Prognose morgen
|
|
|
|
|
my $constm = CurrentVal ($name, 'tmConFcTillSunset', 0); # Verbrauch nächster Tag bis Sonnenuntergang Wh
|
|
|
|
|
my $pvfctd = ReadingsNum ($name, 'RestOfDayPVforecast', 0); # PV Prognose Rest heute
|
|
|
|
|
my $surptd = $pvfctd - $tdconsset; # erwarteter (Rest)Überschuß des aktuellen Tages
|
|
|
|
|
my $surptm = sprintf "%.0f", ($pvfctm - $constm * 0.5); # anteilig Überschuß am kommenden Tages während PV-Erzeugung -> Platz lassen!
|
|
|
|
|
my $surptm = sprintf "%.0f", ($pvfctm - $constm * PERCCONINSOC); # anteilig Verbrauch am kommenden Tages während PV-Erzeugung -> Platz lassen!
|
|
|
|
|
my $pvexpraw = $surptm > $surptd ? $surptm : $surptd; # V 1.60.4
|
|
|
|
|
$pvexpraw = max ($pvexpraw, 0); # erwartete PV-Leistung inkl. Verbrauchsprognose bis Sonnenuntergang
|
|
|
|
|
|
|
|
|
|
@@ -11584,8 +11622,10 @@ sub _batSocTarget {
|
|
|
|
|
|
|
|
|
|
if ($debug =~ /batteryManagement/xs) {
|
|
|
|
|
Log3 ($name, 1, "$name DEBUG> SoC Step1 Bat $bn - basics -> Battery share factor of total required load: $sf");
|
|
|
|
|
Log3 ($name, 1, "$name DEBUG> SoC Step1 Bat $bn - basics -> Expected energy for charging with proportional consumption: $pvexpraw Wh");
|
|
|
|
|
Log3 ($name, 1, "$name DEBUG> SoC Step1 Bat $bn - basics -> Expected energy for charging after application Share factor: $pvexpect Wh");
|
|
|
|
|
Log3 ($name, 1, "$name DEBUG> SoC Step1 Bat $bn - basics -> today -> PV fc: $pvfctd Wh, con till sunset: $tdconsset Wh, Surp: $surptd Wh");
|
|
|
|
|
Log3 ($name, 1, "$name DEBUG> SoC Step1 Bat $bn - basics -> tomorrow -> PV fc: $pvfctm Wh, con till sunset: $constm Wh, Surp: $surptm Wh (".(PERCCONINSOC * 100)."% con)");
|
|
|
|
|
Log3 ($name, 1, "$name DEBUG> SoC Step1 Bat $bn - basics -> selected energy for charging (the higher positive Surp value from above): $pvexpraw Wh");
|
|
|
|
|
Log3 ($name, 1, "$name DEBUG> SoC Step1 Bat $bn - basics -> expected energy for charging after application Share factor: $pvexpect Wh");
|
|
|
|
|
Log3 ($name, 1, "$name DEBUG> SoC Step1 Bat $bn - compare with SoC history -> preliminary new Target: $target %");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -11623,7 +11663,7 @@ sub _batSocTarget {
|
|
|
|
|
$docare = 1; # Zwangsanwendung care SoC
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$la = "calc care SoC -> docare: $docare, care SoC: $careSoc %, Remaining days until care SoC: $days2care, Target: $target %";
|
|
|
|
|
$la = "calc care SoC -> docare: $docare, care SoC: $careSoc %, remain days until care SoC: $days2care, Target: $target %";
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
$la = "calc care SoC -> docare: $docare, care SoC: $careSoc %, use preliminary Target: $target % (new care SoC calc & act postponed to after $nt)";
|
|
|
|
|
@@ -11640,18 +11680,14 @@ sub _batSocTarget {
|
|
|
|
|
|
|
|
|
|
debugLog ($paref, 'batteryManagement', "SoC Step3 Bat $bn - basics -> max SOC so that predicted PV can be stored: $cantarget %, newtarget: $newtarget %");
|
|
|
|
|
|
|
|
|
|
if ($newtarget > $careSoc) {
|
|
|
|
|
$docare = 0; # keine Zwangsanwendung care SoC
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
$newtarget = $careSoc;
|
|
|
|
|
}
|
|
|
|
|
if ($newtarget > $careSoc) { $docare = 0; } # keine Zwangsanwendung care SoC
|
|
|
|
|
else { $newtarget = $careSoc; }
|
|
|
|
|
|
|
|
|
|
my $logadd = '';
|
|
|
|
|
|
|
|
|
|
if ($newtarget > $csopt && $t > $delayts) { # Erhöhung des SoC (wird ab delayts angewendet)
|
|
|
|
|
$target = $newtarget;
|
|
|
|
|
$logadd = "(new target > $csopt % and Sunset has passed)";
|
|
|
|
|
$logadd = "(new target > $csopt % and time limit has passed)";
|
|
|
|
|
}
|
|
|
|
|
elsif ($newtarget > $csopt && $t <= $delayts && !$docare) { # bisheriges Optimum bleibt vorerst
|
|
|
|
|
$target = $csopt;
|
|
|
|
|
@@ -12364,7 +12400,14 @@ sub __batChargeOptTargetPower {
|
|
|
|
|
$achievable = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
storeReading ('Battery_TargetAchievable_'.$sbn, $achievable) if($nhr eq '00');
|
|
|
|
|
if ($nhr eq '00') {
|
|
|
|
|
storeReading ('Battery_TargetAchievable_'.$sbn, $achievable);
|
|
|
|
|
$ratio = sprintf "%.2f", ___batRatio ($runwhneed, $remainingSurp, $befficiency);
|
|
|
|
|
|
|
|
|
|
$data{$name}{current}{'batRatio'.$sbn} = $ratio;
|
|
|
|
|
$otp->{$sbn}{ratio} = $ratio;
|
|
|
|
|
$otp->{$sbn}{remainingSurp} = $remainingSurp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$hsurp->{$hod}{$sbn}{loadrel} = $runwhneed > 0 ? 1 : 0; # Ladefreigabe abhängig von Ziel-SoC Erfüllung
|
|
|
|
|
$hsurp->{$hod}{$sbn}{achievelog} = "charging target: $goalwh Wh, E requirement incl. efficiency: ".
|
|
|
|
|
@@ -12411,6 +12454,7 @@ sub __batChargeOptTargetPower {
|
|
|
|
|
Ereq => $runwhneed,
|
|
|
|
|
replacement => $replacement,
|
|
|
|
|
achievable => $achievable,
|
|
|
|
|
befficiency => $befficiency,
|
|
|
|
|
minute => $minute
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
@@ -12431,13 +12475,13 @@ sub __batChargeOptTargetPower {
|
|
|
|
|
my $pneedmin = $limpower * (1 + $otpMargin / 100); # optPower: Sicherheitsaufschlag
|
|
|
|
|
|
|
|
|
|
if ($strategy eq 'smartPower') {
|
|
|
|
|
($pneedmin) = ___batAdjustPowerByMargin ($limpower, # smartPower: Sicherheitsaufschlag abfallend proportional zum linearen Überschuss
|
|
|
|
|
$bpinmax,
|
|
|
|
|
$runwhneed,
|
|
|
|
|
$otpMargin,
|
|
|
|
|
$remainingSurp,
|
|
|
|
|
$befficiency
|
|
|
|
|
);
|
|
|
|
|
$pneedmin = ___batAdjustPowerByMargin ($limpower, # smartPower: Sicherheitsaufschlag abfallend proportional zum linearen Überschuss
|
|
|
|
|
$bpinmax,
|
|
|
|
|
$runwhneed,
|
|
|
|
|
$otpMargin,
|
|
|
|
|
$remainingSurp,
|
|
|
|
|
$befficiency
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$pneedmin = ___batApplySocAreas ( { name => $name,
|
|
|
|
|
@@ -12451,7 +12495,7 @@ sub __batChargeOptTargetPower {
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$pneedmin = ___batAdjustEfficiencyAndLimits ($pneedmin, $befficiency, $bpinmax, 0); # Apply Bat Effizienz und Ladeleistungsbegrenzungen
|
|
|
|
|
$pneedmin = ___batAdjustLimits ($pneedmin, $bpinmax, 0); # Ladeleistungsbegrenzungen
|
|
|
|
|
|
|
|
|
|
$hsurp->{$hod}{$sbn}{pneedmin} = $pneedmin;
|
|
|
|
|
|
|
|
|
|
@@ -12468,16 +12512,13 @@ sub __batChargeOptTargetPower {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($strategy eq 'smartPower') { # smartPower: Sicherheitsaufschlag linear absenkend
|
|
|
|
|
($target, $ratio) = ___batAdjustPowerByMargin ($limpower, # smartPower: agressivere Ladeleistung, Sicherheitsaufschlag abfallend proportional zum linearen Überschuss
|
|
|
|
|
$bpinmax,
|
|
|
|
|
$runwhneed,
|
|
|
|
|
$otpMargin,
|
|
|
|
|
$remainingSurp,
|
|
|
|
|
$befficiency
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$otp->{$sbn}{ratio} = sprintf ("%.2f", $ratio);
|
|
|
|
|
$otp->{$sbn}{remainingSurp} = $remainingSurp;
|
|
|
|
|
$target = ___batAdjustPowerByMargin ($limpower, # smartPower: agressivere Ladeleistung, Sicherheitsaufschlag abfallend proportional zum linearen Überschuss
|
|
|
|
|
$bpinmax,
|
|
|
|
|
$runwhneed,
|
|
|
|
|
$otpMargin,
|
|
|
|
|
$remainingSurp,
|
|
|
|
|
$befficiency
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
my $gfeedin = CurrentVal ($name, 'gridfeedin', 0); # aktuelle Netzeinspeisung
|
|
|
|
|
@@ -12499,7 +12540,7 @@ sub __batChargeOptTargetPower {
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$target = ___batAdjustEfficiencyAndLimits ($target, $befficiency, $bpinmax, $bpinreduced); # Apply Bat Effizienz und Ladeleistungsbegrenzungen
|
|
|
|
|
$target = ___batAdjustLimits ($target, $bpinmax, $bpinreduced); # Ladeleistungsbegrenzungen
|
|
|
|
|
|
|
|
|
|
$otp->{$sbn}{target} = $target;
|
|
|
|
|
}
|
|
|
|
|
@@ -12525,6 +12566,20 @@ sub __batChargeOptTargetPower {
|
|
|
|
|
return ($hsurp, $otp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
################################################################
|
|
|
|
|
# Ratio zwischen PV-Überschuß und benötigter Ladeenergie
|
|
|
|
|
# berechnen
|
|
|
|
|
################################################################
|
|
|
|
|
sub ___batRatio {
|
|
|
|
|
my ($rwh, $surp, $beff) = @_;
|
|
|
|
|
|
|
|
|
|
return 0 if($surp <= 0.0 || !defined $rwh || $rwh <= 0.0);
|
|
|
|
|
|
|
|
|
|
my $ratio = $surp * 100.0 * $beff / $rwh; # beff -> Batterie Effizienz als Anteil 0.1 .. 1
|
|
|
|
|
|
|
|
|
|
return $ratio;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
################################################################
|
|
|
|
|
# Verbleibende Aktivstunden und deren Überschußsumme liefern
|
|
|
|
|
################################################################
|
|
|
|
|
@@ -12556,12 +12611,10 @@ return ($remainingSurp, \@remaining_hods);
|
|
|
|
|
sub ___batAdjustPowerByMargin {
|
|
|
|
|
my ($limpower, $pinmax, $runwhneed, $otpMargin, $remainingSurp, $befficiency) = @_;
|
|
|
|
|
|
|
|
|
|
return $limpower if(!defined $runwhneed || $runwhneed <= 0);
|
|
|
|
|
|
|
|
|
|
my $pow;
|
|
|
|
|
my $ratio = 0;
|
|
|
|
|
|
|
|
|
|
return ($limpower, $ratio) if(!defined $runwhneed || $runwhneed <= 0);
|
|
|
|
|
|
|
|
|
|
$ratio = $remainingSurp * 100.0 * $befficiency / $runwhneed ; # befficiency als Anteil 0.1 .. 1
|
|
|
|
|
my $ratio = ___batRatio ($runwhneed, $remainingSurp, $befficiency);
|
|
|
|
|
my $limWithMargin = $limpower * (1.0 + $otpMargin / 100.0); # Hinweis: das ist bewusst ein permanenter Sicherheitsaufschlag
|
|
|
|
|
$limpower = min ($limpower, $pinmax); # limpower !> pinmax um invertierte Interpolation zu vermeiden
|
|
|
|
|
$limWithMargin = min ($limWithMargin, $pinmax);
|
|
|
|
|
@@ -12571,7 +12624,7 @@ sub ___batAdjustPowerByMargin {
|
|
|
|
|
elsif ($ratio >= 100 + $otpMargin) { $pow = $limWithMargin }
|
|
|
|
|
else { $pow = $pinmax - ($pinmax - $limWithMargin) * ($ratio - 100.0) / $otpMargin }
|
|
|
|
|
|
|
|
|
|
return ($pow, $ratio);
|
|
|
|
|
return $pow;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
################################################################
|
|
|
|
|
@@ -12637,15 +12690,14 @@ return $ph;
|
|
|
|
|
|
|
|
|
|
################################################################
|
|
|
|
|
# Endbehandlung einer Leistungsvorgabe für Batterieladung
|
|
|
|
|
################################################################
|
|
|
|
|
sub ___batAdjustEfficiencyAndLimits {
|
|
|
|
|
my ($ph, $eff, $max, $min) = @_;
|
|
|
|
|
|
|
|
|
|
$ph /= $eff;
|
|
|
|
|
################################################################
|
|
|
|
|
sub ___batAdjustLimits {
|
|
|
|
|
my ($ph, $max, $min) = @_;
|
|
|
|
|
|
|
|
|
|
$ph = min ($ph, $max); # Begrenzung auf max. mögliche Batterieladeleistung
|
|
|
|
|
$ph = max ($ph, $min); # Begrenzung auf min. gewünschte Batterieladeleistung
|
|
|
|
|
$ph = sprintf "%.0f", $ph;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return $ph;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -12696,6 +12748,7 @@ sub ___batFindMinPhWh {
|
|
|
|
|
my $Ereq = $paref->{Ereq};
|
|
|
|
|
my $replacement = $paref->{replacement};
|
|
|
|
|
my $achievable = $paref->{achievable};
|
|
|
|
|
my $befficiency = $paref->{befficiency};
|
|
|
|
|
my $minute = $paref->{minute};
|
|
|
|
|
|
|
|
|
|
my @hods = @$hodsref;
|
|
|
|
|
@@ -12704,6 +12757,7 @@ sub ___batFindMinPhWh {
|
|
|
|
|
my $eps = 0.5; # minimale Genauigkeit in Wh (1e-3)
|
|
|
|
|
my $max_iter = 100; # Zwangsabbruch nach X Durchläufen
|
|
|
|
|
my $loop = 0;
|
|
|
|
|
$Ereq /= $befficiency;
|
|
|
|
|
my $cap;
|
|
|
|
|
|
|
|
|
|
if (!$achievable) {
|
|
|
|
|
@@ -12713,6 +12767,7 @@ sub ___batFindMinPhWh {
|
|
|
|
|
} @hods;
|
|
|
|
|
|
|
|
|
|
$max_cap //= 0;
|
|
|
|
|
$max_cap /= $befficiency;
|
|
|
|
|
|
|
|
|
|
return { ph => (sprintf "%.0f", $max_cap), iterations => $loop, blur => (sprintf "%.4f", 0) };
|
|
|
|
|
}
|
|
|
|
|
@@ -12729,7 +12784,7 @@ sub ___batFindMinPhWh {
|
|
|
|
|
|
|
|
|
|
if ($nhr eq '00') { $cap = min ($mid, $hsurp->{$hod}{surplswh}) / 60 * (60 - int $minute) } # Zeitgewichtung aktuelle Stunde
|
|
|
|
|
else { $cap = min ($mid, $hsurp->{$hod}{surplswh}) }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$charged += $cap // 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -12838,12 +12893,6 @@ sub _createSummaries {
|
|
|
|
|
|
|
|
|
|
$minute = int ($minute) + 1; # Minute Range umsetzen auf 1 bis 60
|
|
|
|
|
|
|
|
|
|
my $dt = timestringsFromOffset ($t, 86400);
|
|
|
|
|
my $tmoday = $dt->{day}; # Tomorrow Day (01..31)
|
|
|
|
|
|
|
|
|
|
$dt = timestringsFromOffset ($t, 172800);
|
|
|
|
|
my $datmoday = $dt->{day}; # Übermorgen Day (01..31)
|
|
|
|
|
|
|
|
|
|
## Initialisierung
|
|
|
|
|
####################
|
|
|
|
|
my $next1HoursSum = { "PV" => 0, "Consumption" => 0 };
|
|
|
|
|
@@ -12856,11 +12905,15 @@ sub _createSummaries {
|
|
|
|
|
my $todaySumFc = { "PV" => 0, "Consumption" => 0 };
|
|
|
|
|
my $todaySumRe = { "PV" => 0, "Consumption" => 0 };
|
|
|
|
|
|
|
|
|
|
my $tdaysset = CurrentVal ($name, 'sunsetTodayTs', 0); # Timestamp Sonneuntergang am aktuellen Tag
|
|
|
|
|
my $dtsset = timestringsFromOffset ($tdaysset, 0);
|
|
|
|
|
my $tmorsset = CurrentVal ($name, 'sunsetTomorrowTs', 0); # Timestamp Sonneuntergang kommenden Tag
|
|
|
|
|
my $htmsset = timestringsFromOffset ($tmorsset, 0);
|
|
|
|
|
my $tdaysset = CurrentVal ($name, 'sunsetTodayTs', 0); # Timestamp Sonneuntergang am aktuellen Tag
|
|
|
|
|
my $dtsset = timestringsFromOffset ($tdaysset, 0);
|
|
|
|
|
|
|
|
|
|
my $tdConFcTillSunset = 0;
|
|
|
|
|
my $remainminutes = 60 - $minute; # verbleibende Minuten der aktuellen Stunde
|
|
|
|
|
my $tdConFcTillSunset = 0;
|
|
|
|
|
my $tmConFcTillSunset = 0;
|
|
|
|
|
my $tmConInHrWithPVGen = 0;
|
|
|
|
|
my $remainminutes = 60 - $minute; # verbleibende Minuten der aktuellen Stunde
|
|
|
|
|
|
|
|
|
|
my $hour00pvfc = NexthoursVal ($name, "NextHour00", 'pvfc', 0) / 60 * $remainminutes;
|
|
|
|
|
my $hour00confc = NexthoursVal ($name, "NextHour00", 'confc', 0);
|
|
|
|
|
@@ -12933,36 +12986,28 @@ sub _createSummaries {
|
|
|
|
|
$next4HoursSum->{Consumption} += $confc / 60 * $minute if($h == 4);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($istdy) {
|
|
|
|
|
if ($fd == 0) {
|
|
|
|
|
$restOfDaySum->{PV} += $pvfc;
|
|
|
|
|
$restOfDaySum->{Consumption} += $confc;
|
|
|
|
|
$tdConFcTillSunset += $confc if($don);
|
|
|
|
|
$tdConFcTillSunset += $confc if(int ($hod) < int ($dtsset->{hour}) + 1);
|
|
|
|
|
|
|
|
|
|
if (int ($hod) == int ($dtsset->{hour}) + 1) { # wenn die berücksichtigte Stunde die Stunde des Sonnenuntergangs ist
|
|
|
|
|
my $diflasth = 60 - int ($dtsset->{minute}) + 1; # fehlende Minuten zur vollen Stunde in der Stunde des Sunset
|
|
|
|
|
if (int ($hod) == int ($dtsset->{hour}) + 1) { # wenn die berücksichtigte Stunde die Stunde des Sonnenuntergangs ist
|
|
|
|
|
my $diflasth = 60 - int ($dtsset->{minute}) + 1; # fehlende Minuten zur vollen Stunde in der Stunde des Sunset
|
|
|
|
|
$tdConFcTillSunset -= ($confc / 60) * $diflasth;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
elsif ($nhday eq $tmoday) {
|
|
|
|
|
elsif ($fd == 1) {
|
|
|
|
|
$tomorrowSum->{PV} += $pvfc;
|
|
|
|
|
$tmConFcTillSunset += $confc if(int ($hod) <= int ($htmsset->{hour}) + 1); # Verbrauch kommender Tag bis inkl. Stunde des Sonnenuntergangs
|
|
|
|
|
|
|
|
|
|
if ($pvfc) { # Summe Verbrauch der Stunden mit PV-Erzeugung am kommenden Tag
|
|
|
|
|
$tmConInHrWithPVGen += $confc;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
elsif ($nhday eq $datmoday) {
|
|
|
|
|
elsif ($fd == 2) {
|
|
|
|
|
$daftertomSum->{PV} += $pvfc;
|
|
|
|
|
$daftertomSum->{Consumption} += $confc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
## Summe Verbrauch der Stunden mit PV-Erzeugung am kommenden Tag
|
|
|
|
|
################################################################## # V 1.60.4
|
|
|
|
|
if ($fd == 1) { # für den nächsten Tag
|
|
|
|
|
if ($fh == 0) {
|
|
|
|
|
delete $data{$name}{current}{tomorrowConsHoursWithPVGen}; # alte Summe bereinigen
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
if ($pvfc) {
|
|
|
|
|
$data{$name}{current}{tomorrowConsHoursWithPVGen} += $confc;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for my $th (1..24) {
|
|
|
|
|
@@ -13056,6 +13101,8 @@ sub _createSummaries {
|
|
|
|
|
$data{$name}{current}{selfconsumptionrate} = $selfconsumptionrate;
|
|
|
|
|
$data{$name}{current}{autarkyrate} = $autarkyrate;
|
|
|
|
|
$data{$name}{current}{tdConFcTillSunset} = sprintf "%.0f", $tdConFcTillSunset;
|
|
|
|
|
$data{$name}{current}{tmConFcTillSunset} = $tmConFcTillSunset;
|
|
|
|
|
$data{$name}{current}{tmConInHrWithPVGen} = $tmConInHrWithPVGen;
|
|
|
|
|
$data{$name}{current}{surplus} = $surplus;
|
|
|
|
|
$data{$name}{current}{dayAfterTomorrowPVfc} = $daftertomSum->{PV};
|
|
|
|
|
$data{$name}{current}{dayAfterTomorrowConfc} = $daftertomSum->{Consumption};
|
|
|
|
|
@@ -13199,7 +13246,7 @@ sub _manageConsumerData {
|
|
|
|
|
if (!$paread){
|
|
|
|
|
my $timespan = $t - ConsumerVal ($hash, $c, "old_etottime", $t);
|
|
|
|
|
my $delta = $etot - ConsumerVal ($hash, $c, "old_etotal", $etot);
|
|
|
|
|
$pcurr = sprintf "%.6f", $delta / (3600 * $timespan) if($delta); # Einheitenformel beachten !!: W = Wh / (3600 * s)
|
|
|
|
|
$pcurr = sprintf "%.6f", ($delta / 3600 * $timespan) if($delta); # Einheitenformel beachten !!: W = Wh / (3600 * s)
|
|
|
|
|
|
|
|
|
|
$data{$name}{consumers}{$c}{old_etotal} = $etot;
|
|
|
|
|
$data{$name}{consumers}{$c}{old_etottime} = $t;
|
|
|
|
|
@@ -13222,7 +13269,7 @@ sub _manageConsumerData {
|
|
|
|
|
Log3 ($name, $vl, "$name $pre The calculated Energy consumption of >$consumer< is negative. This appears to be an error and the energy consumption of the consumer for the current hour is set to '0'.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$paref->{val} = $consumerco; # Verbrauch des Consumers aktuelle Stunde
|
|
|
|
|
$paref->{val} = sprintf "%.2f", $consumerco; # Verbrauch des Consumers aktuelle Stunde
|
|
|
|
|
$paref->{histname} = "csme${c}";
|
|
|
|
|
|
|
|
|
|
setPVhistory ($paref);
|
|
|
|
|
@@ -13368,8 +13415,7 @@ sub __calcEnergyPieces {
|
|
|
|
|
my $name = $paref->{name};
|
|
|
|
|
my $c = $paref->{consumer};
|
|
|
|
|
|
|
|
|
|
my $hash = $defs{$name};
|
|
|
|
|
my $etot = HistoryVal ($hash, $paref->{day}, sprintf("%02d",$paref->{nhour}), "csmt${c}", 0);
|
|
|
|
|
my $etot = HistoryVal ($name, $paref->{day}, sprintf("%02d",$paref->{nhour}), "csmt${c}", 0);
|
|
|
|
|
|
|
|
|
|
if ($etot) {
|
|
|
|
|
$paref->{etot} = $etot;
|
|
|
|
|
@@ -13380,7 +13426,7 @@ sub __calcEnergyPieces {
|
|
|
|
|
delete $data{$name}{consumers}{$c}{epiecAVG};
|
|
|
|
|
delete $data{$name}{consumers}{$c}{epiecAVG_hours};
|
|
|
|
|
delete $data{$name}{consumers}{$c}{epiecStartEtotal};
|
|
|
|
|
delete $data{$name}{consumers}{$c}{epiecHist};
|
|
|
|
|
delete $data{$name}{consumers}{$c}{epiecActive};
|
|
|
|
|
delete $data{$name}{consumers}{$c}{epiecHour};
|
|
|
|
|
|
|
|
|
|
for my $h (1..EPIECMAXCYCLES) {
|
|
|
|
|
@@ -13391,7 +13437,7 @@ sub __calcEnergyPieces {
|
|
|
|
|
|
|
|
|
|
delete $data{$name}{consumers}{$c}{epieces};
|
|
|
|
|
|
|
|
|
|
my $cotype = ConsumerVal ($hash, $c, 'type', DEFCTYPE);
|
|
|
|
|
my $cotype = ConsumerVal ($name, $c, 'type', DEFCTYPE);
|
|
|
|
|
my ($err, $mintime) = getConsumerMintime ( { name => $name,
|
|
|
|
|
c => $c,
|
|
|
|
|
nolog => 1,
|
|
|
|
|
@@ -13405,13 +13451,14 @@ sub __calcEnergyPieces {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
my $hours = ceil ($mintime / 60); # Einplanungsdauer in h
|
|
|
|
|
my $ctote = ConsumerVal ($hash, $c, "avgenergy", undef); # gemessener durchschnittlicher Energieverbrauch pro Stunde (Wh)
|
|
|
|
|
$ctote = $ctote ? $ctote :
|
|
|
|
|
ConsumerVal ($hash, $c, "power", 0); # alternativer nominaler Energieverbrauch in W (bzw. Wh bezogen auf 1 h)
|
|
|
|
|
my $hours = ceil ($mintime / 60); # Einplanungsdauer in h
|
|
|
|
|
my $ctote = ConsumerVal ($name, $c, "avgenergy", undef); # gemessener durchschnittlicher Energieverbrauch pro Stunde (Wh)
|
|
|
|
|
$ctote = $ctote
|
|
|
|
|
? $ctote
|
|
|
|
|
: ConsumerVal ($name, $c, "power", 0); # alternativer nominaler Energieverbrauch in W (bzw. Wh bezogen auf 1 h)
|
|
|
|
|
|
|
|
|
|
if (int($hef{$cotype}{f}) == 1) { # bei linearen Verbrauchertypen die nominale Leistungsangabe verwenden statt Durchschnitt
|
|
|
|
|
$ctote = ConsumerVal ($hash, $c, "power", 0);
|
|
|
|
|
$ctote = ConsumerVal ($name, $c, "power", 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
my $epiecef = $ctote * $hef{$cotype}{f}; # Gewichtung erste Laufstunde
|
|
|
|
|
@@ -13425,7 +13472,7 @@ sub __calcEnergyPieces {
|
|
|
|
|
$he = $epiecem if($h > 1 && $h < $hours); # kalk. Energieverbrauch Folgestunde(n)
|
|
|
|
|
$he = $epiecel if($h == $hours ); # kalk. Energieverbrauch letzte Stunde
|
|
|
|
|
|
|
|
|
|
$data{$name}{consumers}{$c}{epieces}{${h}} = sprintf('%.2f', $he);
|
|
|
|
|
$data{$name}{consumers}{$c}{epieces}{${h}} = sprintf ('%.2f', $he);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
@@ -13436,10 +13483,10 @@ return;
|
|
|
|
|
#
|
|
|
|
|
# EPIECMAXCYCLES => gibt an wie viele Zyklen betrachtet werden
|
|
|
|
|
# sollen
|
|
|
|
|
# epiecHist => ist die Nummer x des Zyklus der aktuell
|
|
|
|
|
# epiecActive => ist die Nummer x des Zyklus der aktuell
|
|
|
|
|
# beschrieben wird (epiecHist_x).
|
|
|
|
|
# epiecStartTime => Start der letzten neuen Aufzeichnung
|
|
|
|
|
# epiecHour => aktuelle Anzahl der Betriebsstunden nach 'epiecStartTime'
|
|
|
|
|
# epiecSwitchTime => Start der letzten neuen Aufzeichnung
|
|
|
|
|
# epiecHour => aktuelle Anzahl der Betriebsstunden nach 'epiecSwitchTime'
|
|
|
|
|
# oder Cycle Switch
|
|
|
|
|
# epiecHist_x => 1=.. 2=.. 3=.. 4=.. epieces eines Zyklus
|
|
|
|
|
# epiecHist_x_hours => Stunden des Durchlauf bzw. wie viele
|
|
|
|
|
@@ -13458,7 +13505,17 @@ sub ___csmSpecificEpieces {
|
|
|
|
|
my $etot = $paref->{etot};
|
|
|
|
|
my $t = $paref->{t};
|
|
|
|
|
|
|
|
|
|
if (ConsumerVal ($name, $c, 'onoff', 'off') eq 'on') { # Status "Aus" verzögern um Pausen im Waschprogramm zu überbrücken
|
|
|
|
|
### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !!
|
|
|
|
|
########################################################################################################################
|
|
|
|
|
if (defined $data{$name}{consumers}{$c}{epiecHist}) { # 15.11.2025
|
|
|
|
|
$data{$name}{consumers}{$c}{epiecActive} = delete $data{$name}{consumers}{$c}{epiecHist};
|
|
|
|
|
}
|
|
|
|
|
if (defined $data{$name}{consumers}{$c}{epiecStartTime}) { # 15.11.2025
|
|
|
|
|
$data{$name}{consumers}{$c}{epiecSwitchTime} = delete $data{$name}{consumers}{$c}{epiecStartTime};
|
|
|
|
|
}
|
|
|
|
|
########################################################################################################################
|
|
|
|
|
|
|
|
|
|
if (ConsumerVal ($name, $c, 'onoff', 'off') eq 'on') { # Status "Aus" verzögern um Pausen im Waschprogramm zu überbrücken
|
|
|
|
|
$data{$name}{consumers}{$c}{lastOnTime} = $t;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -13466,91 +13523,94 @@ sub ___csmSpecificEpieces {
|
|
|
|
|
$t - $data{$name}{consumers}{$c}{lastOnTime} :
|
|
|
|
|
0;
|
|
|
|
|
|
|
|
|
|
my $curr_epiecHour = ConsumerVal ($name, $c, 'epiecHour', 0);
|
|
|
|
|
|
|
|
|
|
my $curr_epiecHour = ConsumerVal ($name, $c, 'epiecHour', 0);
|
|
|
|
|
my $hourSinceSwitch = int (($t - ConsumerVal ($name, $c, 'epiecSwitchTime', $t)) / 3600) + 1; # aktuelle Betriebsstunde ermitteln
|
|
|
|
|
|
|
|
|
|
debugLog ($paref, 'epiecesCalc', qq{specificEpieces -> consumer "$c" - time since last Switch Off (tsloff): $tsloff seconds});
|
|
|
|
|
|
|
|
|
|
if ($tsloff < 300 && $curr_epiecHour <= EPIECMAXOPHRS) { # neuen epiec-Zyklus starten: nach OFF >= X Sekunden oder mehr als EPIECMAXOPHRS ununterbrochenen Betriebsstunden
|
|
|
|
|
if (($tsloff < 300.0 && $hourSinceSwitch < EPIECMAXOPHRS + 1)) { # aktuellen Zyklus beschreiben
|
|
|
|
|
my $ecycle = q{};
|
|
|
|
|
my $epiecHist_hours = q{};
|
|
|
|
|
my $epiecActive = ConsumerVal ($name, $c, 'epiecActive', 0);
|
|
|
|
|
|
|
|
|
|
if ($curr_epiecHour < 0) { # neue Aufzeichnung
|
|
|
|
|
my $histActiveNew = $epiecActive + 1;
|
|
|
|
|
$data{$name}{consumers}{$c}{epiecSwitchTime} = $t;
|
|
|
|
|
|
|
|
|
|
if ($histActiveNew > EPIECMAXCYCLES) { $data{$name}{consumers}{$c}{epiecActive} = 1 }
|
|
|
|
|
else { $data{$name}{consumers}{$c}{epiecActive} = $histActiveNew }
|
|
|
|
|
|
|
|
|
|
if ($curr_epiecHour < 0) { # neue Aufzeichnung
|
|
|
|
|
$data{$name}{consumers}{$c}{epiecStartTime} = $t;
|
|
|
|
|
$data{$name}{consumers}{$c}{epiecHist} += 1;
|
|
|
|
|
$data{$name}{consumers}{$c}{epiecHist} = 1 if(ConsumerVal ($name, $c, 'epiecHist', 0) > EPIECMAXCYCLES);
|
|
|
|
|
|
|
|
|
|
$ecycle = 'epiecHist_'.ConsumerVal ($name, $c, 'epiecHist', 0);
|
|
|
|
|
|
|
|
|
|
delete $data{$name}{consumers}{$c}{$ecycle}; # Löschen, wird neu erfasst
|
|
|
|
|
$ecycle = 'epiecHist_'.$data{$name}{consumers}{$c}{epiecActive};
|
|
|
|
|
delete $data{$name}{consumers}{$c}{$ecycle}; # Löschen, wird neu erfasst
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$ecycle = 'epiecHist_'.ConsumerVal ($name, $c, 'epiecHist', 0); # Zyklusnummer für Namen
|
|
|
|
|
$epiecHist_hours = 'epiecHist_'.ConsumerVal ($name, $c, 'epiecHist', 0).'_hours';
|
|
|
|
|
my $epiecHour = floor (($t - ConsumerVal ($name, $c, 'epiecStartTime', $t)) / 60 / 60) + 1; # aktuelle Betriebsstunde ermitteln, ( / 60min) mögliche wäre auch durch 15min /Minute /Stunde
|
|
|
|
|
|
|
|
|
|
$epiecActive = ConsumerVal ($name, $c, 'epiecActive', 0);
|
|
|
|
|
$ecycle = 'epiecHist_'.$epiecActive; # Zyklusnummer für Namen
|
|
|
|
|
$epiecHist_hours = 'epiecHist_'.$epiecActive.'_hours';
|
|
|
|
|
|
|
|
|
|
debugLog ($paref, 'epiecesCalc', qq{specificEpieces -> consumer "$c" - current cycle number (ecycle): $ecycle});
|
|
|
|
|
debugLog ($paref, 'epiecesCalc', qq{specificEpieces -> consumer "$c" - Operating hours after switch on or cycle switch: $epiecHour});
|
|
|
|
|
debugLog ($paref, 'epiecesCalc', qq{specificEpieces -> consumer "$c" - current operating hour after switch on or cycle switch: $hourSinceSwitch});
|
|
|
|
|
|
|
|
|
|
if ($curr_epiecHour != $epiecHour) { # Betriebsstundenwechsel ? Differenz von etot noch auf die vorherige Betriebsstunde anrechnen
|
|
|
|
|
my $epiecHour_last = $epiecHour - 1;
|
|
|
|
|
if ($curr_epiecHour != $hourSinceSwitch) { # Betriebsstundenwechsel ? Differenz von etot noch auf die vorherige Betriebsstunde anrechnen
|
|
|
|
|
my $epiecHour_last = $hourSinceSwitch - 1;
|
|
|
|
|
|
|
|
|
|
$data{$name}{consumers}{$c}{$ecycle}{$epiecHour_last} = sprintf '%.2f', ($etot - ConsumerVal ($name, $c, "epiecStartEtotal", 0)) if($epiecHour > 1);
|
|
|
|
|
$data{$name}{consumers}{$c}{$ecycle}{$epiecHour_last} = sprintf '%.2f', ($etot - ConsumerVal ($name, $c, 'epiecStartEtotal', 0)) if($hourSinceSwitch > 1);
|
|
|
|
|
$data{$name}{consumers}{$c}{epiecStartEtotal} = $etot;
|
|
|
|
|
|
|
|
|
|
debugLog ($paref, 'epiecesCalc', qq{specificEpieces -> consumer "$c" - Operating hours change - new etotal (epiecStartEtotal): $etot});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
my $ediff = $etot - ConsumerVal ($name, $c, "epiecStartEtotal", 0);
|
|
|
|
|
$ediff = sprintf ('%.2f', $ediff);
|
|
|
|
|
$data{$name}{consumers}{$c}{$ecycle}{$epiecHour} = $ediff;
|
|
|
|
|
$data{$name}{consumers}{$c}{epiecHour} = $epiecHour;
|
|
|
|
|
$data{$name}{consumers}{$c}{$epiecHist_hours} = $ediff ? $epiecHour : $epiecHour - 1; # wenn mehr als 1 Wh verbraucht wird die Stunde gezählt
|
|
|
|
|
my $ediff = $etot - ConsumerVal ($name, $c, "epiecStartEtotal", 0);
|
|
|
|
|
$ediff = sprintf ('%.2f', $ediff);
|
|
|
|
|
$data{$name}{consumers}{$c}{$ecycle}{$hourSinceSwitch} = $ediff;
|
|
|
|
|
$data{$name}{consumers}{$c}{epiecHour} = $hourSinceSwitch;
|
|
|
|
|
$data{$name}{consumers}{$c}{$epiecHist_hours} = $ediff > 0.0 ? $hourSinceSwitch : $hourSinceSwitch - 1; # Stunde akzeptieren wenn mehr als 1 Wh verbraucht
|
|
|
|
|
|
|
|
|
|
debugLog ($paref, 'epiecesCalc', qq{specificEpieces -> consumer "$c" - energy consumption in operating hour $epiecHour (ediff): $ediff Wh});
|
|
|
|
|
debugLog ($paref, 'epiecesCalc', qq{specificEpieces -> consumer "$c" - energy consumption in operating hour $hourSinceSwitch (ediff): $ediff Wh});
|
|
|
|
|
}
|
|
|
|
|
else { # Off-Status länger als 300 Sek. her Durchschnitt ermitteln
|
|
|
|
|
else { # neuen epiec-Zyklus starten: nach OFF >= X Sekunden oder mehr als EPIECMAXOPHRS ununterbrochenen Betriebsstunden
|
|
|
|
|
if ($curr_epiecHour > 0) {
|
|
|
|
|
my $hours = 0;
|
|
|
|
|
my $operhours = 0;
|
|
|
|
|
|
|
|
|
|
for my $h (1..EPIECMAXCYCLES) { # durchschnittliche Betriebsstunden über alle epieces ermitteln und aufrunden
|
|
|
|
|
$hours += ConsumerVal ($name, $c, 'epiecHist_'.$h.'_hours', 0);
|
|
|
|
|
for my $h (1..EPIECMAXCYCLES) { # durchschnittliche Betriebsstunden über alle epieces ermitteln
|
|
|
|
|
$operhours += ConsumerVal ($name, $c, 'epiecHist_'.$h.'_hours', 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
my $avghours = ceil ($hours / EPIECMAXCYCLES);
|
|
|
|
|
$data{$name}{consumers}{$c}{epiecAVG_hours} = $avghours; # durchschnittliche Betriebsstunden pro Zyklus
|
|
|
|
|
my $avghours = ceil ($operhours / EPIECMAXCYCLES);
|
|
|
|
|
$data{$name}{consumers}{$c}{epiecAVG_hours} = $avghours; # durchschnittliche Betriebsstunden pro Zyklus
|
|
|
|
|
delete $data{$name}{consumers}{$c}{epiecAVG}; # Durchschnitt für epics neu ermitteln
|
|
|
|
|
|
|
|
|
|
debugLog ($paref, 'epiecesCalc', qq{specificEpieces -> consumer "$c" - Average operating hours per cycle (epiecAVG_hours): $avghours});
|
|
|
|
|
for my $hour (1..$avghours) {
|
|
|
|
|
my $hoursE = 0;
|
|
|
|
|
my $hsum;
|
|
|
|
|
|
|
|
|
|
delete $data{$name}{consumers}{$c}{epiecAVG}; # Durchschnitt für epics ermitteln
|
|
|
|
|
|
|
|
|
|
for my $hour (1..$avghours) { # jede Stunde durchlaufen
|
|
|
|
|
my $hoursE = 1;
|
|
|
|
|
|
|
|
|
|
for my $h (1..EPIECMAXCYCLES) { # jedes epiec durchlaufen
|
|
|
|
|
for my $h (1..EPIECMAXCYCLES) {
|
|
|
|
|
my $ecycle = 'epiecHist_'.$h;
|
|
|
|
|
|
|
|
|
|
if (defined $data{$name}{consumers}{$c}{$ecycle}{$hour}) {
|
|
|
|
|
if ($data{$name}{consumers}{$c}{$ecycle}{$hour} > 5) {
|
|
|
|
|
$data{$name}{consumers}{$c}{epiecAVG}{$hour} += $data{$name}{consumers}{$c}{$ecycle}{$hour};
|
|
|
|
|
$hoursE += 1;
|
|
|
|
|
my $pth = ConsumerVal ($name, $c, 'powerthreshold', 0);
|
|
|
|
|
|
|
|
|
|
if ($data{$name}{consumers}{$c}{$ecycle}{$hour} > $pth) {
|
|
|
|
|
$hsum += $data{$name}{consumers}{$c}{$ecycle}{$hour};
|
|
|
|
|
$hoursE++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
my $eavg = defined $data{$name}{consumers}{$c}{epiecAVG}{$hour} ?
|
|
|
|
|
$data{$name}{consumers}{$c}{epiecAVG}{$hour} :
|
|
|
|
|
0;
|
|
|
|
|
|
|
|
|
|
my $ahval = sprintf '%.2f', $eavg / $hoursE; # Durchschnitt ermittelt und speichern
|
|
|
|
|
my $ahval;
|
|
|
|
|
$ahval = sprintf '%.2f', ($hsum / $hoursE) if($hoursE > 0); # Durchschnitt ermittelt und speichern
|
|
|
|
|
|
|
|
|
|
$data{$name}{consumers}{$c}{epiecAVG}{$hour} = $ahval;
|
|
|
|
|
|
|
|
|
|
debugLog ($paref, 'epiecesCalc', qq{specificEpieces -> consumer "$c" - Average epiece of operating hour $hour: $ahval});
|
|
|
|
|
debugLog ($paref, 'epiecesCalc', qq{specificEpieces -> consumer "$c" - Average epiece of operating hour $hour: }.(defined $ahval ? $ahval : 'undef'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
debugLog ($paref, 'epiecesCalc', qq{specificEpieces -> consumer "$c" - Average operating hours per cycle (epiecAVG_hours): $avghours});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$data{$name}{consumers}{$c}{epiecHour} = -1; # epiecHour bei nächsten Durchlauf erhöhen
|
|
|
|
|
delete $data{$name}{consumers}{$c}{epiecSwitchTime};
|
|
|
|
|
$data{$name}{consumers}{$c}{epiecHour} = -1; # epiecHour bei nächsten Durchlauf erhöhen
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
@@ -14617,31 +14677,31 @@ sub __getCyclesAndRuntime {
|
|
|
|
|
my ($starthour, $startday);
|
|
|
|
|
|
|
|
|
|
if (isConsumerLogOn ($hash, $c, $pcurr)) { # Verbraucher ist logisch "an"
|
|
|
|
|
if (ConsumerVal ($hash, $c, 'onoff', 'off') eq 'off') { # Status im letzen Zyklus war "off"
|
|
|
|
|
if (ConsumerVal ($name, $c, 'onoff', 'off') eq 'off') { # Status im letzen Zyklus war "off"
|
|
|
|
|
$data{$name}{consumers}{$c}{onoff} = 'on';
|
|
|
|
|
$data{$name}{consumers}{$c}{startTime} = $t; # startTime ist nicht von "Automatic" abhängig -> nicht identisch mit planswitchon !!!
|
|
|
|
|
$data{$name}{consumers}{$c}{cycleStarttime} = $t;
|
|
|
|
|
$data{$name}{consumers}{$c}{cycleTime} = 0;
|
|
|
|
|
$data{$name}{consumers}{$c}{lastMinutesOn} = ConsumerVal ($hash, $c, 'minutesOn', 0);
|
|
|
|
|
$data{$name}{consumers}{$c}{lastMinutesOn} = ConsumerVal ($name, $c, 'minutesOn', 0);
|
|
|
|
|
|
|
|
|
|
$data{$name}{consumers}{$c}{cycleDayNum}++; # Anzahl der On-Schaltungen am Tag
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
$data{$name}{consumers}{$c}{cycleTime} = (($t - ConsumerVal ($hash, $c, 'cycleStarttime', $t)) / 60); # Minuten
|
|
|
|
|
$data{$name}{consumers}{$c}{cycleTime} = sprintf "%.2f", ($t - ConsumerVal ($name, $c, 'cycleStarttime', $t)) / 60; # Minuten
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$starthour = strftime "%H", localtime(ConsumerVal ($hash, $c, 'startTime', $t));
|
|
|
|
|
$startday = strftime "%d", localtime(ConsumerVal ($hash, $c, 'startTime', $t)); # aktueller Tag (range 01 to 31)
|
|
|
|
|
$starthour = strftime "%H", localtime(ConsumerVal ($name, $c, 'startTime', $t));
|
|
|
|
|
$startday = strftime "%d", localtime(ConsumerVal ($name, $c, 'startTime', $t)); # aktueller Tag (range 01 to 31)
|
|
|
|
|
|
|
|
|
|
if ($chour eq $starthour) {
|
|
|
|
|
my $runtime = (($t - ConsumerVal ($hash, $c, 'startTime', $t)) / 60); # in Minuten ! (gettimeofday sind ms !)
|
|
|
|
|
$data{$name}{consumers}{$c}{minutesOn} = ConsumerVal ($hash, $c, 'lastMinutesOn', 0) + $runtime;
|
|
|
|
|
my $runtime = sprintf "%.2f", ($t - ConsumerVal ($name, $c, 'startTime', $t)) / 60; # in Minuten ! (gettimeofday sind ms !)
|
|
|
|
|
$data{$name}{consumers}{$c}{minutesOn} = ConsumerVal ($name, $c, 'lastMinutesOn', 0) + $runtime;
|
|
|
|
|
}
|
|
|
|
|
else { # Stundenwechsel
|
|
|
|
|
if (ConsumerVal ($hash, $c, 'onoff', 'off') eq 'on') { # Status im letzen Zyklus war "on"
|
|
|
|
|
if (ConsumerVal ($name, $c, 'onoff', 'off') eq 'on') { # Status im letzen Zyklus war "on"
|
|
|
|
|
my $newst = timestringToTimestamp ($date.' '.sprintf("%02d", $chour).':00:00');
|
|
|
|
|
$data{$name}{consumers}{$c}{startTime} = $newst;
|
|
|
|
|
$data{$name}{consumers}{$c}{minutesOn} = ($t - ConsumerVal ($hash, $c, 'startTime', $newst)) / 60; # in Minuten ! (gettimeofday sind ms !)
|
|
|
|
|
$data{$name}{consumers}{$c}{minutesOn} = ($t - ConsumerVal ($name, $c, 'startTime', $newst)) / 60; # in Minuten ! (gettimeofday sind ms !)
|
|
|
|
|
$data{$name}{consumers}{$c}{lastMinutesOn} = 0;
|
|
|
|
|
|
|
|
|
|
if ($day ne $startday) { # Tageswechsel
|
|
|
|
|
@@ -14651,8 +14711,8 @@ sub __getCyclesAndRuntime {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else { # Verbraucher soll nicht aktiv sein
|
|
|
|
|
$starthour = strftime "%H", localtime(ConsumerVal ($hash, $c, 'startTime', 1));
|
|
|
|
|
$startday = strftime "%d", localtime(ConsumerVal ($hash, $c, 'startTime', 1)); # aktueller Tag (range 01 to 31)
|
|
|
|
|
$starthour = strftime "%H", localtime (ConsumerVal ($name, $c, 'startTime', 1));
|
|
|
|
|
$startday = strftime "%d", localtime (ConsumerVal ($name, $c, 'startTime', 1)); # aktueller Tag (range 01 to 31)
|
|
|
|
|
|
|
|
|
|
if ($chour ne $starthour) { # Stundenwechsel
|
|
|
|
|
$data{$name}{consumers}{$c}{minutesOn} = 0;
|
|
|
|
|
@@ -14667,25 +14727,25 @@ sub __getCyclesAndRuntime {
|
|
|
|
|
|
|
|
|
|
if ($debug =~ /consumerSwitching${c}/xs) {
|
|
|
|
|
my $sr = 'still running';
|
|
|
|
|
my $son = isConsumerLogOn ($hash, $c, $pcurr) ? $sr : ConsumerVal ($hash, $c, 'cycleTime', 0) * 60; # letzte Cycle-Zeitdauer in Sekunden
|
|
|
|
|
my $cst = ConsumerVal ($hash, $c, 'cycleStarttime', 0);
|
|
|
|
|
my $son = isConsumerLogOn ($hash, $c, $pcurr) ? $sr : ConsumerVal ($name, $c, 'cycleTime', 0) * 60; # letzte Cycle-Zeitdauer in Sekunden
|
|
|
|
|
my $cst = ConsumerVal ($name, $c, 'cycleStarttime', 0);
|
|
|
|
|
$son = $son && $son ne $sr ? timestampToTimestring ($cst + $son, $paref->{lang}) :
|
|
|
|
|
$son eq $sr ? $sr :
|
|
|
|
|
'-';
|
|
|
|
|
$cst = $cst ? timestampToTimestring ($cst, $paref->{lang}) : '-';
|
|
|
|
|
|
|
|
|
|
Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - cycleDayNum: }.ConsumerVal ($hash, $c, 'cycleDayNum', 0));
|
|
|
|
|
Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - cycleDayNum: }.ConsumerVal ($name, $c, 'cycleDayNum', 0));
|
|
|
|
|
Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - last cycle start time: $cst});
|
|
|
|
|
Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - last cycle end time: $son \n});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
## History schreiben
|
|
|
|
|
######################
|
|
|
|
|
$paref->{val} = ConsumerVal ($hash, $c, "cycleDayNum", 0); # Anzahl Tageszyklen des Verbrauchers speichern
|
|
|
|
|
$paref->{val} = ConsumerVal ($name, $c, "cycleDayNum", 0); # Anzahl Tageszyklen des Verbrauchers speichern
|
|
|
|
|
$paref->{histname} = "cyclescsm${c}";
|
|
|
|
|
setPVhistory ($paref);
|
|
|
|
|
|
|
|
|
|
$paref->{val} = ceil ConsumerVal ($hash, $c, "minutesOn", 0); # Verbrauchsminuten akt. Stunde des Consumers speichern
|
|
|
|
|
$paref->{val} = ceil ConsumerVal ($name, $c, "minutesOn", 0); # Verbrauchsminuten akt. Stunde des Consumers speichern
|
|
|
|
|
$paref->{histname} = "minutescsm${c}";
|
|
|
|
|
setPVhistory ($paref);
|
|
|
|
|
|
|
|
|
|
@@ -15656,6 +15716,7 @@ sub _genSpecialReadings {
|
|
|
|
|
readingsDelete ($hash, $prpo.'_'.$item);
|
|
|
|
|
deleteReadingspec ($hash, $prpo.'_'.$item.'_.*') if($item eq 'todayConsumptionForecast');
|
|
|
|
|
deleteReadingspec ($hash, $prpo.'_'.$item.'_.*') if($item eq 'tomorrowConsumptionForecast');
|
|
|
|
|
deleteReadingspec ($hash, $prpo.'_'.$item.'_.*') if($item eq 'BatRatio');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return if(!@csr);
|
|
|
|
|
@@ -15711,6 +15772,14 @@ sub _genSpecialReadings {
|
|
|
|
|
|
|
|
|
|
storeReading ($prpo.'_'.$kpi, $bsum);
|
|
|
|
|
}
|
|
|
|
|
elsif ($kpi eq 'BatRatio') {
|
|
|
|
|
for my $bn (1..MAXBATTERIES) { # für jede Batterie
|
|
|
|
|
$bn = sprintf "%02d", $bn;
|
|
|
|
|
my $ratio = &{$hcsr{$kpi}{fn}} ($name, $hcsr{$kpi}{par}.$bn, $def);
|
|
|
|
|
|
|
|
|
|
storeReading ($prpo.'_'.$kpi.'_'.$bn, sprintf ("%.0f", $ratio)) if($ratio ne '-');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
elsif ($kpi =~ /daysUntilBatteryCare_/xs) {
|
|
|
|
|
my $bn = (split "_", $kpi)[1]; # Batterienummer extrahieren
|
|
|
|
|
my $d2c = &{$hcsr{$kpi}{fn}} ($hash, $hcsr{$kpi}{par}, 'days2care'.$bn, $def);
|
|
|
|
|
@@ -16637,16 +16706,6 @@ sub _parseShowdiffModes {
|
|
|
|
|
|
|
|
|
|
my $mo = CurrentVal ($name, 'showDiff', '');
|
|
|
|
|
|
|
|
|
|
### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !! 03.07.
|
|
|
|
|
###########################################################################################
|
|
|
|
|
if ($mo) {
|
|
|
|
|
$mo = $mo eq 'no' ? qq{1:'',2:'',3:''} :
|
|
|
|
|
$mo eq 'top' ? qq{1:top,2:top,3:top} :
|
|
|
|
|
$mo eq 'bottom' ? qq{1:bottom,2:bottom,3:bottom} :
|
|
|
|
|
$mo;
|
|
|
|
|
}
|
|
|
|
|
##########################################################################################
|
|
|
|
|
|
|
|
|
|
if ($mo) {
|
|
|
|
|
my @moa = split ',', $mo;
|
|
|
|
|
|
|
|
|
|
@@ -21266,7 +21325,7 @@ sub aiAddRawData {
|
|
|
|
|
last if(int $pvd > int $day);
|
|
|
|
|
|
|
|
|
|
if (!$ood) { # V 1.47.2 -> für manuelles Auffüllen mit Setter
|
|
|
|
|
$dayname = HistoryVal ($hash, $pvd, 99, 'dayname', undef);
|
|
|
|
|
$dayname = HistoryVal ($name, $pvd, 99, 'dayname', undef);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for my $hod (sort keys %{$data{$name}{pvhist}{$pvd}}) {
|
|
|
|
|
@@ -21274,17 +21333,17 @@ sub aiAddRawData {
|
|
|
|
|
|
|
|
|
|
my $ridx = _aiMakeIdxRaw ($pvd, $hod, $paref->{yt});
|
|
|
|
|
|
|
|
|
|
my $temp = HistoryVal ($hash, $pvd, $hod, 'temp', undef);
|
|
|
|
|
my $sunalt = HistoryVal ($hash, $pvd, $hod, 'sunalt', 0);
|
|
|
|
|
my $sunaz = HistoryVal ($hash, $pvd, $hod, 'sunaz', 0);
|
|
|
|
|
my $con = HistoryVal ($hash, $pvd, $hod, 'con', undef);
|
|
|
|
|
my $temp = HistoryVal ($name, $pvd, $hod, 'temp', undef);
|
|
|
|
|
my $sunalt = HistoryVal ($name, $pvd, $hod, 'sunalt', 0);
|
|
|
|
|
my $sunaz = HistoryVal ($name, $pvd, $hod, 'sunaz', 0);
|
|
|
|
|
my $con = HistoryVal ($name, $pvd, $hod, 'con', undef);
|
|
|
|
|
my $gcons = HistoryVal ($name, $pvd, $hod, 'gcons', undef);
|
|
|
|
|
my $wcc = HistoryVal ($hash, $pvd, $hod, 'wcc', undef);
|
|
|
|
|
my $wid = HistoryVal ($hash, $pvd, $hod, 'weatherid', undef); # Wetter ID
|
|
|
|
|
my $rr1c = HistoryVal ($hash, $pvd, $hod, 'rr1c', undef);
|
|
|
|
|
my $rad1h = HistoryVal ($hash, $pvd, $hod, 'rad1h', undef);
|
|
|
|
|
my $pvrlvd = HistoryVal ($hash, $pvd, $hod, 'pvrlvd', 1); # PV Generation valide?
|
|
|
|
|
my $pvrl = HistoryVal ($hash, $pvd, $hod, 'pvrl', undef);
|
|
|
|
|
my $wcc = HistoryVal ($name, $pvd, $hod, 'wcc', undef);
|
|
|
|
|
my $wid = HistoryVal ($name, $pvd, $hod, 'weatherid', undef); # Wetter ID
|
|
|
|
|
my $rr1c = HistoryVal ($name, $pvd, $hod, 'rr1c', undef);
|
|
|
|
|
my $rad1h = HistoryVal ($name, $pvd, $hod, 'rad1h', undef);
|
|
|
|
|
my $pvrlvd = HistoryVal ($name, $pvd, $hod, 'pvrlvd', 1); # PV Generation valide?
|
|
|
|
|
my $pvrl = HistoryVal ($name, $pvd, $hod, 'pvrl', undef);
|
|
|
|
|
|
|
|
|
|
$data{$name}{aidectree}{airaw}{$ridx}{sunalt} = $sunalt;
|
|
|
|
|
$data{$name}{aidectree}{airaw}{$ridx}{sunaz} = $sunaz;
|
|
|
|
|
@@ -21300,6 +21359,13 @@ sub aiAddRawData {
|
|
|
|
|
$data{$name}{aidectree}{airaw}{$ridx}{pvrl} = $pvrl if(defined $pvrl && $pvrl > 0);
|
|
|
|
|
$data{$name}{aidectree}{airaw}{$ridx}{pvrlvd} = $pvrlvd;
|
|
|
|
|
|
|
|
|
|
for my $c (1..MAXCONSUMER) {
|
|
|
|
|
$c = sprintf "%02d", $c;
|
|
|
|
|
my $csme = HistoryVal ($name, $pvd, $hod, 'csme'.$c, undef);
|
|
|
|
|
|
|
|
|
|
if (defined $csme) { $data{$name}{aidectree}{airaw}{$ridx}{'csme'.$c} = sprintf ("%.0f", $csme) }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$dosave++;
|
|
|
|
|
|
|
|
|
|
debugLog ($paref, 'aiProcess', "AI raw add - idx: $ridx, day: $pvd, hod: $hod, sunalt: $sunalt, sunaz: $sunaz, rad1h: ".(defined $rad1h ? $rad1h : '-').", pvrl: ".(defined $pvrl ? $pvrl : '-').", con: ".(defined $con ? $con : '-').", wcc: ".(defined $wcc ? $wcc : '-').", rr1c: ".(defined $rr1c ? $rr1c : '-').", temp: ".(defined $temp ? $temp : '-'), 4);
|
|
|
|
|
@@ -22474,10 +22540,23 @@ sub _listDataPoolAiRawData {
|
|
|
|
|
my $nod = AiRawdataVal ($name, $idx, 'dayname', '-');
|
|
|
|
|
my $con = AiRawdataVal ($name, $idx, 'con', '-');
|
|
|
|
|
my $gcons = AiRawdataVal ($name, $idx, 'gcons', '-');
|
|
|
|
|
|
|
|
|
|
my $csm;
|
|
|
|
|
for my $c (1..MAXCONSUMER) { # + alle Consumer
|
|
|
|
|
$c = sprintf "%02d", $c;
|
|
|
|
|
my $csme = AiRawdataVal ($name, $idx, 'csme'.$c, undef);
|
|
|
|
|
|
|
|
|
|
if (defined $csme) {
|
|
|
|
|
$csm .= ", " if($csm);
|
|
|
|
|
$csm .= "csme${c}: $csme";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$sq .= "\n";
|
|
|
|
|
$sq .= "$idx => hod: $hod, nod: $nod, sunaz: $sunaz, sunalt: $sunalt, rad1h: $rad1h, ";
|
|
|
|
|
$sq .= "wcc: $wcc, wid: $wid, rr1c: $rr1c, pvrl: $pvrl, pvrlvd: $pvrlvd, con: $con, gcons: $gcons, temp: $temp";
|
|
|
|
|
|
|
|
|
|
if (defined $csm) { $sq .= "\n "; $sq .= $csm; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $sq;
|
|
|
|
|
@@ -24291,23 +24370,24 @@ sub isConsumerLogOn {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isConsumerPhysOff ($hash, $c)) { # Device ist physisch ausgeschaltet
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
my $type = $hash->{TYPE};
|
|
|
|
|
my $nompower = ConsumerVal ($name, $c, "power", 0); # nominale Leistung lt. Typenschild
|
|
|
|
|
my $rpcurr = ConsumerVal ($name, $c, "rpcurr", ""); # Reading für akt. Verbrauch angegeben ?
|
|
|
|
|
my $pthreshold = ConsumerVal ($name, $c, "powerthreshold", 0); # Schwellenwert (W) ab der ein Verbraucher als aktiv gewertet wird
|
|
|
|
|
|
|
|
|
|
if (!$rpcurr && isConsumerPhysOn ($hash, $c)) { # Workaround wenn Verbraucher ohne Leistungsmessung
|
|
|
|
|
$pcurr = $nompower;
|
|
|
|
|
if (!$rpcurr) { # Workaround wenn Verbraucher ohne Leistungsmessung
|
|
|
|
|
if (isConsumerPhysOn ($hash, $c)) { $pcurr = $nompower }
|
|
|
|
|
else { $pcurr = 0 }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
my $currpowerpercent = $pcurr;
|
|
|
|
|
$currpowerpercent = ($pcurr / $nompower) * 100 if($nompower > 0);
|
|
|
|
|
my $currpowerpercent;
|
|
|
|
|
if ($nompower > 0) { $currpowerpercent = sprintf ('%.2f', $pcurr / $nompower * 100) }
|
|
|
|
|
else { $currpowerpercent = 0 }
|
|
|
|
|
|
|
|
|
|
$data{$name}{consumers}{$c}{currpowerpercent} = $currpowerpercent;
|
|
|
|
|
|
|
|
|
|
if (isConsumerPhysOff ($hash, $c)) { # Device ist physisch ausgeschaltet
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($pcurr > $pthreshold || (!$pthreshold && $currpowerpercent > DEFPOPERCENT)) { # Verbraucher ist logisch aktiv
|
|
|
|
|
return 1;
|
|
|
|
|
@@ -28103,6 +28183,7 @@ to ensure that the system configuration is correct.
|
|
|
|
|
<colgroup> <col width="27%"> <col width="73%"> </colgroup>
|
|
|
|
|
<tr><td> <b>BatPowerIn_Sum</b> </td><td>the sum of the current battery charging power of all defined battery devices </td></tr>
|
|
|
|
|
<tr><td> <b>BatPowerOut_Sum</b> </td><td>the sum of the current battery discharge power of all defined battery devices </td></tr>
|
|
|
|
|
<tr><td> <b>BatRatio</b> </td><td>The ratio of PV surplus/required charging energy to the battery's charging target (loadTarget) </td></tr>
|
|
|
|
|
<tr><td> <b>BatWeightedTotalSOC</b> </td><td>the resulting (weighted) SOC across all installed batteries in % </td></tr>
|
|
|
|
|
<tr><td> <b>allStringsFullfilled</b> </td><td>Fulfillment status of error-free generation of all strings </td></tr>
|
|
|
|
|
<tr><td> <b>conForecastComingNight</b> </td><td>Consumption forecast from the coming sunset to the coming sunrise. If the sunset has already passed, </td></tr>
|
|
|
|
|
@@ -28869,7 +28950,7 @@ to ensure that the system configuration is correct.
|
|
|
|
|
<tr><td> <b>pvOut</b> </td><td>A reading that provides the current power from PV generation that is supplied to the battery(ies) or to a battery inverter. </td></tr>
|
|
|
|
|
<tr><td> </td><td>A positive numerical value is expected. </td></tr>
|
|
|
|
|
<tr><td> </td><td> </td></tr>
|
|
|
|
|
<tr><td> <b>etotal</b> </td><td>The Reading which provides the total PV energy generated (a steadily increasing counter). </td></tr>
|
|
|
|
|
<tr><td> <b>etotal</b> </td><td>The reading, which provides the total PV energy generated by the inverter as a continuously increasing counter. </td></tr>
|
|
|
|
|
<tr><td> </td><td>If the reading violates the specification of a continuously rising counter, </td></tr>
|
|
|
|
|
<tr><td> </td><td>SolarForecast handles this error and reports the situation by means of a log message. </td></tr>
|
|
|
|
|
<tr><td> </td><td> </td></tr>
|
|
|
|
|
@@ -30887,6 +30968,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
|
|
|
|
|
<colgroup> <col width="27%"> <col width="73%"> </colgroup>
|
|
|
|
|
<tr><td> <b>BatPowerIn_Sum</b> </td><td>die Summe der momentanen Batterieladeleistung aller definierten Batterie Geräte </td></tr>
|
|
|
|
|
<tr><td> <b>BatPowerOut_Sum</b> </td><td>die Summe der momentanen Batterieentladeleistung aller definierten Batterie Geräte </td></tr>
|
|
|
|
|
<tr><td> <b>BatRatio</b> </td><td>das Verhältnis (Ratio) von PV-Überschuß / benötigter Ladeenergie zum Ladeziel (loadTarget) der Batterie(n) </td></tr>
|
|
|
|
|
<tr><td> <b>BatWeightedTotalSOC</b> </td><td>der resultierende (gewichtete) SOC über alle installierten Batterien in % </td></tr>
|
|
|
|
|
<tr><td> <b>allStringsFullfilled</b> </td><td>Erfüllungsstatus der fehlerfreien Generierung aller Strings </td></tr>
|
|
|
|
|
<tr><td> <b>conForecastComingNight</b> </td><td>Verbrauchsprognose vom kommenden Sonnenuntergang bis zum kommenden Sonnenaufgang. Ist der Sonnenuntergang </td></tr>
|
|
|
|
|
@@ -31639,7 +31721,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
|
|
|
|
|
<tr><td> <b>pvOut</b> </td><td>Ein Reading, welches die aktuelle Leistung aus PV-Erzeugung, die an das Hausnetz oder öffentliche Netz </td></tr>
|
|
|
|
|
<tr><td> </td><td>geliefert wird, bereitstellt. Es wird ein positiver numerischer Wert erwartet. </td></tr>
|
|
|
|
|
<tr><td> </td><td> </td></tr>
|
|
|
|
|
<tr><td> <b>etotal</b> </td><td>Das Reading, welches die gesamte erzeugte PV-Energie liefert (ein stetig aufsteigender Zähler). </td></tr>
|
|
|
|
|
<tr><td> <b>etotal</b> </td><td>Das Reading, welches die gesamte erzeugte PV-Energie des Wechselrichters als stetig aufsteigenden Zähler liefert. </td></tr>
|
|
|
|
|
<tr><td> </td><td>Sollte des Reading die Vorgabe eines stetig aufsteigenden Zählers verletzen, behandelt </td></tr>
|
|
|
|
|
<tr><td> </td><td>SolarForecast diesen Fehler und meldet die aufgetretene Situation durch einen Logeintrag. </td></tr>
|
|
|
|
|
<tr><td> </td><td> </td></tr>
|
|
|
|
|
|