2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-01-31 06:39:11 +00:00

76_SolarForecast: internal code change for data collection

git-svn-id: https://svn.fhem.de/fhem/trunk@29531 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
nasseeder1 2025-01-17 15:45:50 +00:00
parent 46e2aaa51e
commit 212641ccbd
3 changed files with 418 additions and 181 deletions

View File

@ -1,5 +1,6 @@
# Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Add changes at the top of the list. Keep it in ASCII, and 80-char wide.
# Do not insert empty lines here, update check depends on it # Do not insert empty lines here, update check depends on it
- change: 76_SolarForecast: internal code change for data collection
- change: 76_SolarForecast: FlowGraphic: SoC as a Cluster value of all Batts - change: 76_SolarForecast: FlowGraphic: SoC as a Cluster value of all Batts
- bugfix: 76_SolarForecast: bugfix of version 1.43.3 - bugfix: 76_SolarForecast: bugfix of version 1.43.3
- change: 76_SolarForecast: consumption fc calc switch from average to median - change: 76_SolarForecast: consumption fc calc switch from average to median

View File

@ -157,6 +157,7 @@ BEGIN {
# Versions History intern # Versions History intern
my %vNotesIntern = ( my %vNotesIntern = (
"1.43.6" => "17.01.2025 _calcCaQcomplex: additional write pvrl, pvfc to separate circular hash Array elements, listDataPool: show these Arrays ",
"1.43.5" => "15.01.2025 _flowGraphic: calculate the resulting SoC as a cluster of batteries ", "1.43.5" => "15.01.2025 _flowGraphic: calculate the resulting SoC as a cluster of batteries ",
"1.43.4" => "14.01.2025 batsocslidereg: calculate the SoC as summary over all capacities in Wh, bugfix https://forum.fhem.de/index.php?msg=1330559 ", "1.43.4" => "14.01.2025 batsocslidereg: calculate the SoC as summary over all capacities in Wh, bugfix https://forum.fhem.de/index.php?msg=1330559 ",
"1.43.3" => "13.01.2025 add Wiki icon in graphic header, _calcConsumptionForecast: switch calc from average to median, edit comref ", "1.43.3" => "13.01.2025 add Wiki icon in graphic header, _calcConsumptionForecast: switch calc from average to median, edit comref ",
@ -7093,9 +7094,8 @@ sub writeCacheToFile {
return ('', $nr, $na); return ('', $nr, $na);
} }
my @arr;
return if(!keys %{$data{$name}{$cachename}}); return if(!keys %{$data{$name}{$cachename}});
push @arr, encode_json ($data{$name}{$cachename}); push my @arr, encode_json ($data{$name}{$cachename});
$error = FileWrite ($file, @arr); $error = FileWrite ($file, @arr);
@ -8747,7 +8747,7 @@ sub _transferAPIRadiationValues {
my $est = __calcPVestimates ($paref); my $est = __calcPVestimates ($paref);
my ($msg, $pvaifc) = aiGetResult ($paref); # KI Entscheidungen abfragen my ($msg, $pvaifc) = aiGetResult ($paref); # KI Entscheidungen abfragen
$data{$name}{nexthours}{$nhtstr}{pvapifc} = $est; # durch API gelieferte PV Forecast $data{$name}{nexthours}{$nhtstr}{pvapifc} = $est; # durch API gelieferte PV Forecast
delete $paref->{fd}; delete $paref->{fd};
delete $paref->{fh1}; delete $paref->{fh1};
@ -12225,20 +12225,28 @@ sub _calcCaQcomplex {
debugLog ($paref, 'pvCorrectionWrite', "Autolearning is switched off for hour: $h -> skip the recalculation of the complex correction factor"); debugLog ($paref, 'pvCorrectionWrite', "Autolearning is switched off for hour: $h -> skip the recalculation of the complex correction factor");
return; return;
} }
my $pvrl = CircularVal ($hash, sprintf("%02d",$h), 'pvrl', 0); my $hh = sprintf "%02d", $h;
my $pvfc = CircularVal ($hash, sprintf("%02d",$h), 'pvapifc', 0); my $pvrl = CircularVal ($hash, $hh, 'pvrl', 0); # real erzeugte PV Energie am Ende der vorherigen Stunde
my $pvfc = CircularVal ($hash, $hh, 'pvapifc', 0); # vorhergesagte PV Energie am Ende der vorherigen Stunde
if (!$pvrl || !$pvfc) { if (!$pvrl || !$pvfc) {
storeReading ('.pvCorrectionFactor_'.sprintf("%02d",$h).'_cloudcover', 'done'); storeReading ('.pvCorrectionFactor_'.$hh.'_cloudcover', 'done');
return; return;
} }
my $chwcc = HistoryVal ($hash, $day, sprintf("%02d",$h), 'wcc', 0); # Wolkenbedeckung Heute & abgefragte Stunde my $chwcc = HistoryVal ($hash, $day, $hh, 'wcc', 0); # Wolkenbedeckung heute & abgefragte Stunde
my $sunalt = HistoryVal ($hash, $day, sprintf("%02d",$h), 'sunalt', 0); # Sonne Altitude my $sunalt = HistoryVal ($hash, $day, $hh, 'sunalt', 0); # Sonne Altitude
my $crang = cloud2bin ($chwcc); my $crang = cloud2bin ($chwcc);
my $sabin = sunalt2bin ($sunalt); my $sabin = sunalt2bin ($sunalt);
## Speicherarrays schreiben
#############################
push @{$data{$name}{circular}{$hh}{'pvrl_'.$sabin}{"$crang"}}, $pvrl;
push @{$data{$name}{circular}{$hh}{'pvfc_'.$sabin}{"$crang"}}, $pvfc;
## neuen Korrekturfaktor berechnen
####################################
$paref->{pvrl} = $pvrl; $paref->{pvrl} = $pvrl;
$paref->{pvfc} = $pvfc; $paref->{pvfc} = $pvfc;
$paref->{crang} = $crang; $paref->{crang} = $crang;
@ -12253,16 +12261,16 @@ sub _calcCaQcomplex {
delete $paref->{sabin}; delete $paref->{sabin};
delete $paref->{calc}; delete $paref->{calc};
storeReading ('.pvCorrectionFactor_'.sprintf("%02d",$h).'_cloudcover', 'done'); storeReading ('.pvCorrectionFactor_'.$hh.'_cloudcover', 'done');
$aihit = $aihit ? ' AI result used,' : ''; $aihit = $aihit ? ' AI result used,' : '';
if ($acu =~ /on_complex/xs) { if ($acu =~ /on_complex/xs) {
if ($paref->{cpcf} !~ /manual/xs) { # pcf-Reading nur überschreiben wenn nicht 'manual xxx' gesetzt if ($paref->{cpcf} !~ /manual/xs) { # pcf-Reading nur überschreiben wenn nicht 'manual xxx' gesetzt
storeReading ('pvCorrectionFactor_'.sprintf("%02d",$h), $factor." (automatic - old factor: $oldfac,$aihit Sun Alt range: $sabin, Cloud range: $crang, Days in range: $dnum)"); storeReading ('pvCorrectionFactor_'.$hh, $factor." (automatic - old factor: $oldfac,$aihit Sun Alt range: $sabin, Cloud range: $crang, Days in range: $dnum)");
} }
else { else {
storeReading ('pvCorrectionFactor_'.sprintf("%02d",$h), $paref->{cpcf}." / flexmatic result $factor for Sun Alt range: $sabin,$aihit Cloud range: $crang, Days in range: $dnum"); storeReading ('pvCorrectionFactor_'.$hh, $paref->{cpcf}." / flexmatic result $factor for Sun Alt range: $sabin,$aihit Cloud range: $crang, Days in range: $dnum");
} }
} }
@ -12284,28 +12292,29 @@ sub _calcCaQsimple {
my $aihit = $paref->{aihit}; my $aihit = $paref->{aihit};
my $hash = $defs{$name}; my $hash = $defs{$name};
my $sr = ReadingsVal ($name, '.pvCorrectionFactor_'.sprintf("%02d",$h).'_apipercentil', ''); my $hh = sprintf "%02d", $h;
my $sr = ReadingsVal ($name, '.pvCorrectionFactor_'.$hh.'_apipercentil', '');
if($sr eq "done") { if($sr eq "done") {
# debugLog ($paref, 'pvCorrectionWrite', "Simple Corrf factor Hour: ".sprintf("%02d",$h)." already calculated"); # debugLog ($paref, 'pvCorrectionWrite', "Simple Corrf factor Hour: ".$hh." already calculated");
return; return;
} }
if (!$aln) { if (!$aln) {
storeReading ('.pvCorrectionFactor_'.sprintf("%02d",$h).'_apipercentil', 'done'); storeReading ('.pvCorrectionFactor_'.$hh.'_apipercentil', 'done');
debugLog ($paref, 'pvCorrectionWrite', "Autolearning is switched off for hour: $h -> skip the recalculation of the simple correction factor"); debugLog ($paref, 'pvCorrectionWrite', "Autolearning is switched off for hour: $h -> skip the recalculation of the simple correction factor");
return; return;
} }
my $pvrl = CircularVal ($hash, sprintf("%02d",$h), 'pvrl', 0); my $pvrl = CircularVal ($hash, $hh, 'pvrl', 0);
my $pvfc = CircularVal ($hash, sprintf("%02d",$h), 'pvapifc', 0); my $pvfc = CircularVal ($hash, $hh, 'pvapifc', 0);
if (!$pvrl || !$pvfc) { if (!$pvrl || !$pvfc) {
storeReading ('.pvCorrectionFactor_'.sprintf("%02d",$h).'_apipercentil', 'done'); storeReading ('.pvCorrectionFactor_'.$hh.'_apipercentil', 'done');
return; return;
} }
my $sunalt = HistoryVal ($hash, $day, sprintf("%02d",$h), 'sunalt', 0); # Sonne Altitude my $sunalt = HistoryVal ($hash, $day, $hh, 'sunalt', 0); # Sonne Altitude
my $sabin = sunalt2bin ($sunalt); my $sabin = sunalt2bin ($sunalt);
$paref->{pvrl} = $pvrl; $paref->{pvrl} = $pvrl;
@ -12322,16 +12331,16 @@ sub _calcCaQsimple {
delete $paref->{crang}; delete $paref->{crang};
delete $paref->{calc}; delete $paref->{calc};
storeReading ('.pvCorrectionFactor_'.sprintf("%02d",$h).'_apipercentil', 'done'); storeReading ('.pvCorrectionFactor_'.$hh.'_apipercentil', 'done');
$aihit = $aihit ? ' AI result used,' : ''; $aihit = $aihit ? ' AI result used,' : '';
if ($acu =~ /on_simple/xs) { if ($acu =~ /on_simple/xs) {
if ($paref->{cpcf} !~ /manual/xs) { # pcf-Reading nur überschreiben wenn nicht 'manual xxx' gesetzt if ($paref->{cpcf} !~ /manual/xs) { # pcf-Reading nur überschreiben wenn nicht 'manual xxx' gesetzt
storeReading ('pvCorrectionFactor_'.sprintf("%02d",$h), $factor." (automatic - old factor: $oldfac,$aihit Days in range: $dnum)"); storeReading ('pvCorrectionFactor_'.$hh, $factor." (automatic - old factor: $oldfac,$aihit Days in range: $dnum)");
} }
else { else {
storeReading ('pvCorrectionFactor_'.sprintf("%02d",$h), $paref->{cpcf}." / flexmatic result $factor,$aihit Days in range: $dnum"); storeReading ('pvCorrectionFactor_'.$hh, $paref->{cpcf}." / flexmatic result $factor,$aihit Days in range: $dnum");
} }
} }
@ -12359,8 +12368,9 @@ sub __calcNewFactor {
debugLog ($paref, 'pvCorrectionWrite', "$calc Corrf -> Start calculation correction factor for hour: $h"); debugLog ($paref, 'pvCorrectionWrite', "$calc Corrf -> Start calculation correction factor for hour: $h");
my ($oldfac, $oldq) = CircularSunCloudkorrVal ($hash, sprintf("%02d",$h), $sabin, $crang, 0); # bisher definierter Korrekturfaktor / Qualität my $hh = sprintf "%02d", $h;
my ($pvhis, $fchis, $dnum) = CircularSumVal ($hash, sprintf("%02d",$h), $sabin, $crang, 0); my ($oldfac, $oldq) = CircularSunCloudkorrVal ($hash, $hh, $sabin, $crang, 0); # bisher definierter Korrekturfaktor / Qualität
my ($pvhis, $fchis, $dnum) = CircularSumVal ($hash, $hh, $sabin, $crang, 0);
$oldfac = 1 if(1 * $oldfac == 0); $oldfac = 1 if(1 * $oldfac == 0);
debugLog ($paref, 'pvCorrectionWrite', "$calc Corrf -> read historical values: pv real sum: $pvhis, pv forecast sum: $fchis, days sum: $dnum"); debugLog ($paref, 'pvCorrectionWrite', "$calc Corrf -> read historical values: pv real sum: $pvhis, pv forecast sum: $fchis, days sum: $dnum");
@ -12403,18 +12413,18 @@ sub __calcNewFactor {
if ($crang ne 'simple') { if ($crang ne 'simple') {
my $idx = $sabin.'.'.$crang; # value für pvcorrf Sonne Altitude my $idx = $sabin.'.'.$crang; # value für pvcorrf Sonne Altitude
$data{$name}{circular}{sprintf("%02d",$h)}{pvrlsum}{$idx} = $pvrlsum; # PV Erzeugung Summe speichern $data{$name}{circular}{$hh}{pvrlsum}{$idx} = $pvrlsum; # PV Erzeugung Summe speichern
$data{$name}{circular}{sprintf("%02d",$h)}{pvfcsum}{$idx} = $pvfcsum; # PV Prognose Summe speichern $data{$name}{circular}{$hh}{pvfcsum}{$idx} = $pvfcsum; # PV Prognose Summe speichern
$data{$name}{circular}{sprintf("%02d",$h)}{dnumsum}{$idx} = $dnum; # Anzahl aller historischen Tade dieser Range $data{$name}{circular}{$hh}{dnumsum}{$idx} = $dnum; # Anzahl aller historischen Tade dieser Range
$data{$name}{circular}{sprintf("%02d",$h)}{pvcorrf}{$idx} = $factor; $data{$name}{circular}{$hh}{pvcorrf}{$idx} = $factor;
$data{$name}{circular}{sprintf("%02d",$h)}{quality}{$idx} = $qual; $data{$name}{circular}{$hh}{quality}{$idx} = $qual;
} }
else { else {
$data{$name}{circular}{sprintf("%02d",$h)}{pvrlsum}{$crang} = $pvrlsum; $data{$name}{circular}{$hh}{pvrlsum}{$crang} = $pvrlsum;
$data{$name}{circular}{sprintf("%02d",$h)}{pvfcsum}{$crang} = $pvfcsum; $data{$name}{circular}{$hh}{pvfcsum}{$crang} = $pvfcsum;
$data{$name}{circular}{sprintf("%02d",$h)}{dnumsum}{$crang} = $dnum; $data{$name}{circular}{$hh}{dnumsum}{$crang} = $dnum;
$data{$name}{circular}{sprintf("%02d",$h)}{pvcorrf}{$crang} = $factor; $data{$name}{circular}{$hh}{pvcorrf}{$crang} = $factor;
$data{$name}{circular}{sprintf("%02d",$h)}{quality}{$crang} = $qual; $data{$name}{circular}{$hh}{quality}{$crang} = $qual;
} }
$oldfac = sprintf "%.2f", $oldfac; $oldfac = sprintf "%.2f", $oldfac;
@ -12422,6 +12432,27 @@ sub __calcNewFactor {
return ($oldfac, $factor, $dnum); return ($oldfac, $factor, $dnum);
} }
################################################################
# Qualität der Vorhersage berechnen
################################################################
sub __calcFcQuality {
my $pvfc = shift; # PV Vorhersagewert
my $pvrl = shift; # PV reale Erzeugung
return if(!$pvfc || !$pvrl);
$pvrl = sprintf "%.0f", $pvrl;
$pvfc = sprintf "%.0f", $pvfc;
my $diff = $pvfc - $pvrl;
my $hdv = 1 - abs ($diff / $pvrl); # Abweichung der Stunde, 1 = bestmöglicher Wert
$hdv = $hdv < 0 ? 0 : $hdv;
$hdv = sprintf "%.2f", $hdv;
return $hdv;
}
################################################################ ################################################################
# Berechnen Tag / Stunden Verschieber # Berechnen Tag / Stunden Verschieber
# aus aktueller Stunde + lfd. Nummer # aus aktueller Stunde + lfd. Nummer
@ -16252,27 +16283,6 @@ sub _addHourAiRawdata {
return; return;
} }
################################################################
# Qualität der Vorhersage berechnen
################################################################
sub __calcFcQuality {
my $pvfc = shift; # PV Vorhersagewert
my $pvrl = shift; # PV reale Erzeugung
return if(!$pvfc || !$pvrl);
$pvrl = sprintf "%.0f", $pvrl;
$pvfc = sprintf "%.0f", $pvfc;
my $diff = $pvfc - $pvrl;
my $hdv = 1 - abs ($diff / $pvrl); # Abweichung der Stunde, 1 = bestmöglicher Wert
$hdv = $hdv < 0 ? 0 : $hdv;
$hdv = sprintf "%.2f", $hdv;
return $hdv;
}
############################################################### ###############################################################
# Eintritt in den KI Train Prozess normal/Blocking # Eintritt in den KI Train Prozess normal/Blocking
############################################################### ###############################################################
@ -17112,14 +17122,6 @@ sub listDataPool {
my $sub = sub { my $sub = sub {
my $day = shift; my $day = shift;
#for my $dh (keys %{$h->{$day}}) {
# if (!isNumeric ($dh)) {
# delete $data{$name}{pvhist}{$day}{$dh};
# Log3 ($name, 2, qq{$name - INFO - invalid key "$day -> $dh" was deleted from pvHistory storage});
# }
#}
my $ret; my $ret;
for my $key (sort {$a<=>$b} keys %{$h->{$day}}) { for my $key (sort {$a<=>$b} keys %{$h->{$day}}) {
@ -17404,7 +17406,7 @@ sub listDataPool {
for my $ckey (sort keys %{$h->{$idx}}) { for my $ckey (sort keys %{$h->{$idx}}) {
if (ref $h->{$idx}{$ckey} eq 'ARRAY') { if (ref $h->{$idx}{$ckey} eq 'ARRAY') {
my $aser = join " ",@{$h->{$idx}{$ckey}}; my $aser = join " ", @{$h->{$idx}{$ckey}};
$cret .= ($s1 ? $sp1 : "").$ckey." => ".$aser."\n"; $cret .= ($s1 ? $sp1 : "").$ckey." => ".$aser."\n";
} }
@ -17431,6 +17433,35 @@ sub listDataPool {
if (!keys %{$h}) { if (!keys %{$h}) {
return qq{Circular cache is empty.}; return qq{Circular cache is empty.};
} }
### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !!
##########################################################################################################################
delete $data{$name}{circular}{'01'}{pvrl_5};
delete $data{$name}{circular}{'01'}{pvrl_10};
delete $data{$name}{circular}{'01'}{pvrl_25};
delete $data{$name}{circular}{'01'}{pvrl_60};
delete $data{$name}{circular}{'01'}{pvrl_65};
delete $data{$name}{circular}{'01'}{pvrl_90};
#push @{$data{$name}{circular}{'01'}{pvrl_65}{100}}, 4561;
#push @{$data{$name}{circular}{'01'}{pvrl_65}{100}}, 4562;
#push @{$data{$name}{circular}{'01'}{pvrl_65}{100}}, 4563;
#push @{$data{$name}{circular}{'01'}{pvrl_65}{100}}, 4564;
#push @{$data{$name}{circular}{'01'}{pvrl_65}{100}}, 4565;
#
#push @{$data{$name}{circular}{'01'}{pvrl_65}{60}}, 3561;
#push @{$data{$name}{circular}{'01'}{pvrl_65}{60}}, 3562;
#
#push @{$data{$name}{circular}{'01'}{pvrl_90}{100}}, 4561;
#push @{$data{$name}{circular}{'01'}{pvrl_90}{100}}, 4562;
#push @{$data{$name}{circular}{'01'}{pvrl_90}{100}}, 4563;
#push @{$data{$name}{circular}{'01'}{pvrl_90}{100}}, 4564;
#push @{$data{$name}{circular}{'01'}{pvrl_90}{100}}, 4565;
#push @{$data{$name}{circular}{'01'}{pvrl_90}{60}}, 3561;
#push @{$data{$name}{circular}{'01'}{pvrl_90}{60}}, 3562;
############################################################################################################
for my $idx (sort keys %{$h}) { for my $idx (sort keys %{$h}) {
my $pvrl = CircularVal ($hash, $idx, 'pvrl', '-'); my $pvrl = CircularVal ($hash, $idx, 'pvrl', '-');
@ -17490,17 +17521,41 @@ sub listDataPool {
$bout .= "batout${bn}: $batout"; $bout .= "batout${bn}: $batout";
} }
$sq .= $idx." => pvapifc: $pvapifc, pvaifc: $pvaifc, pvfc: $pvfc, aihit: $aihit, pvrl: $pvrl\n"; my ($pvrlnew, $pvfcnew);
$sq .= " $bin\n"; my @pvrlkeys = map { $_ =~ /^pvrl_/xs ? $_ : '' } sort keys %{$h->{$idx}};
$sq .= " $bout\n"; my @pvfckeys = map { $_ =~ /^pvfc_/xs ? $_ : '' } sort keys %{$h->{$idx}};
$sq .= " confc: $confc, gcon: $gcons, gfeedin: $gfeedin, wcc: $wccv, rr1c: $rr1c\n";
$sq .= " temp: $temp, wid: $wid, wtxt: $wtxt\n"; for my $prl (@pvrlkeys) {
$sq .= " $prdl\n"; next if(!$prl);
$sq .= " pvcorrf: $pvcf\n"; my $lref = CircularVal ($hash, $idx, $prl, '');
$sq .= " quality: $cfq\n"; next if(!$lref);
$sq .= " pvrlsum: $pvrs\n";
$sq .= " pvfcsum: $pvfs\n"; $pvrlnew .= "\n " if($pvrlnew);
$sq .= " dnumsum: $dnus"; $pvrlnew .= _ldchash2val ( { pool => $h, idx => $idx, key => $prl, cval => $lref } );
}
for my $pfc (@pvfckeys) {
next if(!$pfc);
my $cref = CircularVal ($hash, $idx, $pfc, '');
next if(!$cref);
$pvfcnew .= "\n " if($pvfcnew);
$pvfcnew .= _ldchash2val ( { pool => $h, idx => $idx, key => $pfc, cval => $cref } );
}
$sq .= $idx." => pvapifc: $pvapifc, pvaifc: $pvaifc, pvfc: $pvfc, aihit: $aihit, pvrl: $pvrl";
$sq .= "\n $bin";
$sq .= "\n $bout";
$sq .= "\n confc: $confc, gcon: $gcons, gfeedin: $gfeedin, wcc: $wccv, rr1c: $rr1c";
$sq .= "\n temp: $temp, wid: $wid, wtxt: $wtxt";
$sq .= "\n $prdl";
$sq .= "\n pvcorrf: $pvcf";
$sq .= "\n quality: $cfq";
$sq .= "\n pvrlsum: $pvrs";
$sq .= "\n pvfcsum: $pvfs";
$sq .= "\n dnumsum: $dnus";
$sq .= "\n $pvrlnew" if($pvrlnew);
$sq .= "\n $pvfcnew" if($pvfcnew);
} }
else { else {
my ($batvl1, $batvl2, $batvl3, $batvl4, $batvl5, $batvl6, $batvl7); my ($batvl1, $batvl2, $batvl3, $batvl4, $batvl5, $batvl6, $batvl7);
@ -17772,7 +17827,24 @@ sub _ldchash2val {
for my $f (sort {$a<=>$b} keys %{$pool->{$idx}{$key}}) { for my $f (sort {$a<=>$b} keys %{$pool->{$idx}{$key}}) {
next if($f eq 'simple'); next if($f eq 'simple');
if ($f !~ /\./xs) {
if (ref $pool->{$idx}{$key}{$f} eq 'ARRAY') {
my @sub_arrays = arraySplitBy (20, @{$pool->{$idx}{$key}{$f}}); # Array in Teil-Arrays zu je 20 Elemente aufteilen
for my $suaref (@sub_arrays) { # für jedes Teil-Array Join ausführen
my $suajoined = join ' ', @{$suaref};
if (!$ret) {
$ret .= $key.' => ';
$ret .= $f.' @ '.$suajoined;
}
else {
$ret .= "\n ";
$ret .= $f.' @ '.$suajoined;
}
}
}
elsif ($f !~ /\./xs) {
$ret .= " " if($ret); $ret .= " " if($ret);
$ret .= "$f=".$pool->{$idx}{$key}{$f}; $ret .= "$f=".$pool->{$idx}{$key}{$f};
my $ct = ($ret =~ tr/=// // 0) / 10; my $ct = ($ret =~ tr/=// // 0) / 10;
@ -20219,6 +20291,48 @@ sub sunalt2bin {
return $bin; return $bin;
} }
###############################################################################
# Teilt das Original-Array in Unter-Arrays auf, die den Inhalt des
# Originals enthalten. Die Größe jedes Unterarrays ist gleich oder kleiner als
# $split_size, wobei das letzte Array in der Regel kleiner ist, wenn nicht
# genügend Elemente in @original vorhanden sind.
# (aus https://metacpan.org/dist/Array-Split/source/lib/Array/Split.pm)
#
# arraySplitBy ($split_size, @original)
###############################################################################
sub arraySplitBy {
my $split_size = shift;
$split_size = max ($split_size, 1);
my @sub_arrays;
while (@_) {
push @sub_arrays, [splice @_, 0, $split_size];
}
return @sub_arrays;
}
###############################################################################
# Teilt das angegebene Array in die Anzahl $count Unterarrays auf.
# Es wird versucht, so viele Unter-Arrays zu erstellen, wie $count angibt,
# aber es werden weniger zurückgegeben, wenn nicht genügend Elemente in
# @original vorhanden sind.
#
# Gibt eine Liste von Array-Referenzen zurück.
# (aus https://metacpan.org/dist/Array-Split/source/lib/Array/Split.pm)
#
# arraySplitInto ($count, @original)
###############################################################################
sub arraySplitInto {
my ($count, @original) = @_;
$count = max( $count, 1 );
my $size = ceil @original / $count;
return arraySplitBy ($size, @original);
}
############################################################################### ###############################################################################
# verscrambelt einen String # verscrambelt einen String
############################################################################### ###############################################################################

View File

@ -157,6 +157,8 @@ BEGIN {
# Versions History intern # Versions History intern
my %vNotesIntern = ( my %vNotesIntern = (
"1.43.6" => "17.01.2025 _calcCaQcomplex: additional write pvrl, pvfc to separate circular hash Array elements, listDataPool: show these Arrays ",
"1.43.5" => "15.01.2025 _flowGraphic: calculate the resulting SoC as a cluster of batteries ",
"1.43.4" => "14.01.2025 batsocslidereg: calculate the SoC as summary over all capacities in Wh, bugfix https://forum.fhem.de/index.php?msg=1330559 ", "1.43.4" => "14.01.2025 batsocslidereg: calculate the SoC as summary over all capacities in Wh, bugfix https://forum.fhem.de/index.php?msg=1330559 ",
"1.43.3" => "13.01.2025 add Wiki icon in graphic header, _calcConsumptionForecast: switch calc from average to median, edit comref ", "1.43.3" => "13.01.2025 add Wiki icon in graphic header, _calcConsumptionForecast: switch calc from average to median, edit comref ",
"1.43.2" => "12.01.2025 _batChargeRecmd: bugfix calc socwh, Attr graphicBeam1MaxVal, (experimental) ctrlAreaFactorUsage are obsolete ". "1.43.2" => "12.01.2025 _batChargeRecmd: bugfix calc socwh, Attr graphicBeam1MaxVal, (experimental) ctrlAreaFactorUsage are obsolete ".
@ -7092,9 +7094,8 @@ sub writeCacheToFile {
return ('', $nr, $na); return ('', $nr, $na);
} }
my @arr;
return if(!keys %{$data{$name}{$cachename}}); return if(!keys %{$data{$name}{$cachename}});
push @arr, encode_json ($data{$name}{$cachename}); push my @arr, encode_json ($data{$name}{$cachename});
$error = FileWrite ($file, @arr); $error = FileWrite ($file, @arr);
@ -8746,7 +8747,7 @@ sub _transferAPIRadiationValues {
my $est = __calcPVestimates ($paref); my $est = __calcPVestimates ($paref);
my ($msg, $pvaifc) = aiGetResult ($paref); # KI Entscheidungen abfragen my ($msg, $pvaifc) = aiGetResult ($paref); # KI Entscheidungen abfragen
$data{$name}{nexthours}{$nhtstr}{pvapifc} = $est; # durch API gelieferte PV Forecast $data{$name}{nexthours}{$nhtstr}{pvapifc} = $est; # durch API gelieferte PV Forecast
delete $paref->{fd}; delete $paref->{fd};
delete $paref->{fh1}; delete $paref->{fh1};
@ -9571,14 +9572,15 @@ sub _transferBatteryValues {
storeReading ('Current_PowerBatOut_'.$bn, $pbo.' W'); storeReading ('Current_PowerBatOut_'.$bn, $pbo.' W');
storeReading ('Current_BatCharge_'. $bn, $soc.' %'); storeReading ('Current_BatCharge_'. $bn, $soc.' %');
$data{$name}{batteries}{$bn}{bname} = $badev; # Batterie Devicename $data{$name}{batteries}{$bn}{bname} = $badev; # Batterie Devicename
$data{$name}{batteries}{$bn}{balias} = AttrVal ($badev, 'alias', $badev); # Alias Batterie Device $data{$name}{batteries}{$bn}{balias} = AttrVal ($badev, 'alias', $badev); # Alias Batterie Device
$data{$name}{batteries}{$bn}{bpowerin} = $pbi; # momentane Batterieladung $data{$name}{batteries}{$bn}{bpowerin} = $pbi; # momentane Batterieladung
$data{$name}{batteries}{$bn}{bpowerout} = $pbo; # momentane Batterieentladung $data{$name}{batteries}{$bn}{bpowerout} = $pbo; # momentane Batterieentladung
$data{$name}{batteries}{$bn}{bcharge} = $soc; # aktuelle Batterieladung $data{$name}{batteries}{$bn}{bcharge} = $soc; # Batterie SoC (%)
$data{$name}{batteries}{$bn}{basynchron} = $h->{asynchron} // 0; # asynchroner Modus = X $data{$name}{batteries}{$bn}{basynchron} = $h->{asynchron} // 0; # asynchroner Modus = X
$data{$name}{batteries}{$bn}{bicon} = $h->{icon} if($h->{icon}); # Batterie Icon $data{$name}{batteries}{$bn}{bicon} = $h->{icon} if($h->{icon}); # Batterie Icon
$data{$name}{batteries}{$bn}{bshowingraph} = $h->{show} // 0; # Batterie in Balkengrafik anzeigen $data{$name}{batteries}{$bn}{bshowingraph} = $h->{show} // 0; # Batterie in Balkengrafik anzeigen
$data{$name}{batteries}{$bn}{bchargewh} = BatteryVal ($name, $bn, 'binstcap', 0) * $soc / 100; # Batterie SoC (Wh)
writeToHistory ( { paref => $paref, key => 'batsoc'.$bn, val => $soc, hour => $nhour } ); writeToHistory ( { paref => $paref, key => 'batsoc'.$bn, val => $soc, hour => $nhour } );
@ -9591,7 +9593,7 @@ sub _transferBatteryValues {
} }
if ($num) { if ($num) {
my $soctotal = sprintf "%.0f", ($socwhsum / $bcapsum * 100) if($bcapsum); # resultierender SoC (%) aller Batterien als "eine" my $soctotal = sprintf "%.0f", ($socwhsum / $bcapsum * 100) if($bcapsum); # resultierender SoC (%) aller Batterien als "eine"
push @{$data{$name}{current}{batsocslidereg}}, $soctotal; # Schieberegister average SOC aller Batterien push @{$data{$name}{current}{batsocslidereg}}, $soctotal; # Schieberegister average SOC aller Batterien
limitArray ($data{$name}{current}{batsocslidereg}, $slidenummax); limitArray ($data{$name}{current}{batsocslidereg}, $slidenummax);
@ -12223,20 +12225,28 @@ sub _calcCaQcomplex {
debugLog ($paref, 'pvCorrectionWrite', "Autolearning is switched off for hour: $h -> skip the recalculation of the complex correction factor"); debugLog ($paref, 'pvCorrectionWrite', "Autolearning is switched off for hour: $h -> skip the recalculation of the complex correction factor");
return; return;
} }
my $pvrl = CircularVal ($hash, sprintf("%02d",$h), 'pvrl', 0); my $hh = sprintf "%02d", $h;
my $pvfc = CircularVal ($hash, sprintf("%02d",$h), 'pvapifc', 0); my $pvrl = CircularVal ($hash, $hh, 'pvrl', 0); # real erzeugte PV Energie am Ende der vorherigen Stunde
my $pvfc = CircularVal ($hash, $hh, 'pvapifc', 0); # vorhergesagte PV Energie am Ende der vorherigen Stunde
if (!$pvrl || !$pvfc) { if (!$pvrl || !$pvfc) {
storeReading ('.pvCorrectionFactor_'.sprintf("%02d",$h).'_cloudcover', 'done'); storeReading ('.pvCorrectionFactor_'.$hh.'_cloudcover', 'done');
return; return;
} }
my $chwcc = HistoryVal ($hash, $day, sprintf("%02d",$h), 'wcc', 0); # Wolkenbedeckung Heute & abgefragte Stunde my $chwcc = HistoryVal ($hash, $day, $hh, 'wcc', 0); # Wolkenbedeckung heute & abgefragte Stunde
my $sunalt = HistoryVal ($hash, $day, sprintf("%02d",$h), 'sunalt', 0); # Sonne Altitude my $sunalt = HistoryVal ($hash, $day, $hh, 'sunalt', 0); # Sonne Altitude
my $crang = cloud2bin ($chwcc); my $crang = cloud2bin ($chwcc);
my $sabin = sunalt2bin ($sunalt); my $sabin = sunalt2bin ($sunalt);
## Speicherarrays schreiben
#############################
push @{$data{$name}{circular}{$hh}{'pvrl_'.$sabin}{"$crang"}}, $pvrl;
push @{$data{$name}{circular}{$hh}{'pvfc_'.$sabin}{"$crang"}}, $pvfc;
## neuen Korrekturfaktor berechnen
####################################
$paref->{pvrl} = $pvrl; $paref->{pvrl} = $pvrl;
$paref->{pvfc} = $pvfc; $paref->{pvfc} = $pvfc;
$paref->{crang} = $crang; $paref->{crang} = $crang;
@ -12251,16 +12261,16 @@ sub _calcCaQcomplex {
delete $paref->{sabin}; delete $paref->{sabin};
delete $paref->{calc}; delete $paref->{calc};
storeReading ('.pvCorrectionFactor_'.sprintf("%02d",$h).'_cloudcover', 'done'); storeReading ('.pvCorrectionFactor_'.$hh.'_cloudcover', 'done');
$aihit = $aihit ? ' AI result used,' : ''; $aihit = $aihit ? ' AI result used,' : '';
if ($acu =~ /on_complex/xs) { if ($acu =~ /on_complex/xs) {
if ($paref->{cpcf} !~ /manual/xs) { # pcf-Reading nur überschreiben wenn nicht 'manual xxx' gesetzt if ($paref->{cpcf} !~ /manual/xs) { # pcf-Reading nur überschreiben wenn nicht 'manual xxx' gesetzt
storeReading ('pvCorrectionFactor_'.sprintf("%02d",$h), $factor." (automatic - old factor: $oldfac,$aihit Sun Alt range: $sabin, Cloud range: $crang, Days in range: $dnum)"); storeReading ('pvCorrectionFactor_'.$hh, $factor." (automatic - old factor: $oldfac,$aihit Sun Alt range: $sabin, Cloud range: $crang, Days in range: $dnum)");
} }
else { else {
storeReading ('pvCorrectionFactor_'.sprintf("%02d",$h), $paref->{cpcf}." / flexmatic result $factor for Sun Alt range: $sabin,$aihit Cloud range: $crang, Days in range: $dnum"); storeReading ('pvCorrectionFactor_'.$hh, $paref->{cpcf}." / flexmatic result $factor for Sun Alt range: $sabin,$aihit Cloud range: $crang, Days in range: $dnum");
} }
} }
@ -12282,28 +12292,29 @@ sub _calcCaQsimple {
my $aihit = $paref->{aihit}; my $aihit = $paref->{aihit};
my $hash = $defs{$name}; my $hash = $defs{$name};
my $sr = ReadingsVal ($name, '.pvCorrectionFactor_'.sprintf("%02d",$h).'_apipercentil', ''); my $hh = sprintf "%02d", $h;
my $sr = ReadingsVal ($name, '.pvCorrectionFactor_'.$hh.'_apipercentil', '');
if($sr eq "done") { if($sr eq "done") {
# debugLog ($paref, 'pvCorrectionWrite', "Simple Corrf factor Hour: ".sprintf("%02d",$h)." already calculated"); # debugLog ($paref, 'pvCorrectionWrite', "Simple Corrf factor Hour: ".$hh." already calculated");
return; return;
} }
if (!$aln) { if (!$aln) {
storeReading ('.pvCorrectionFactor_'.sprintf("%02d",$h).'_apipercentil', 'done'); storeReading ('.pvCorrectionFactor_'.$hh.'_apipercentil', 'done');
debugLog ($paref, 'pvCorrectionWrite', "Autolearning is switched off for hour: $h -> skip the recalculation of the simple correction factor"); debugLog ($paref, 'pvCorrectionWrite', "Autolearning is switched off for hour: $h -> skip the recalculation of the simple correction factor");
return; return;
} }
my $pvrl = CircularVal ($hash, sprintf("%02d",$h), 'pvrl', 0); my $pvrl = CircularVal ($hash, $hh, 'pvrl', 0);
my $pvfc = CircularVal ($hash, sprintf("%02d",$h), 'pvapifc', 0); my $pvfc = CircularVal ($hash, $hh, 'pvapifc', 0);
if (!$pvrl || !$pvfc) { if (!$pvrl || !$pvfc) {
storeReading ('.pvCorrectionFactor_'.sprintf("%02d",$h).'_apipercentil', 'done'); storeReading ('.pvCorrectionFactor_'.$hh.'_apipercentil', 'done');
return; return;
} }
my $sunalt = HistoryVal ($hash, $day, sprintf("%02d",$h), 'sunalt', 0); # Sonne Altitude my $sunalt = HistoryVal ($hash, $day, $hh, 'sunalt', 0); # Sonne Altitude
my $sabin = sunalt2bin ($sunalt); my $sabin = sunalt2bin ($sunalt);
$paref->{pvrl} = $pvrl; $paref->{pvrl} = $pvrl;
@ -12320,16 +12331,16 @@ sub _calcCaQsimple {
delete $paref->{crang}; delete $paref->{crang};
delete $paref->{calc}; delete $paref->{calc};
storeReading ('.pvCorrectionFactor_'.sprintf("%02d",$h).'_apipercentil', 'done'); storeReading ('.pvCorrectionFactor_'.$hh.'_apipercentil', 'done');
$aihit = $aihit ? ' AI result used,' : ''; $aihit = $aihit ? ' AI result used,' : '';
if ($acu =~ /on_simple/xs) { if ($acu =~ /on_simple/xs) {
if ($paref->{cpcf} !~ /manual/xs) { # pcf-Reading nur überschreiben wenn nicht 'manual xxx' gesetzt if ($paref->{cpcf} !~ /manual/xs) { # pcf-Reading nur überschreiben wenn nicht 'manual xxx' gesetzt
storeReading ('pvCorrectionFactor_'.sprintf("%02d",$h), $factor." (automatic - old factor: $oldfac,$aihit Days in range: $dnum)"); storeReading ('pvCorrectionFactor_'.$hh, $factor." (automatic - old factor: $oldfac,$aihit Days in range: $dnum)");
} }
else { else {
storeReading ('pvCorrectionFactor_'.sprintf("%02d",$h), $paref->{cpcf}." / flexmatic result $factor,$aihit Days in range: $dnum"); storeReading ('pvCorrectionFactor_'.$hh, $paref->{cpcf}." / flexmatic result $factor,$aihit Days in range: $dnum");
} }
} }
@ -12357,8 +12368,9 @@ sub __calcNewFactor {
debugLog ($paref, 'pvCorrectionWrite', "$calc Corrf -> Start calculation correction factor for hour: $h"); debugLog ($paref, 'pvCorrectionWrite', "$calc Corrf -> Start calculation correction factor for hour: $h");
my ($oldfac, $oldq) = CircularSunCloudkorrVal ($hash, sprintf("%02d",$h), $sabin, $crang, 0); # bisher definierter Korrekturfaktor / Qualität my $hh = sprintf "%02d", $h;
my ($pvhis, $fchis, $dnum) = CircularSumVal ($hash, sprintf("%02d",$h), $sabin, $crang, 0); my ($oldfac, $oldq) = CircularSunCloudkorrVal ($hash, $hh, $sabin, $crang, 0); # bisher definierter Korrekturfaktor / Qualität
my ($pvhis, $fchis, $dnum) = CircularSumVal ($hash, $hh, $sabin, $crang, 0);
$oldfac = 1 if(1 * $oldfac == 0); $oldfac = 1 if(1 * $oldfac == 0);
debugLog ($paref, 'pvCorrectionWrite', "$calc Corrf -> read historical values: pv real sum: $pvhis, pv forecast sum: $fchis, days sum: $dnum"); debugLog ($paref, 'pvCorrectionWrite', "$calc Corrf -> read historical values: pv real sum: $pvhis, pv forecast sum: $fchis, days sum: $dnum");
@ -12401,18 +12413,18 @@ sub __calcNewFactor {
if ($crang ne 'simple') { if ($crang ne 'simple') {
my $idx = $sabin.'.'.$crang; # value für pvcorrf Sonne Altitude my $idx = $sabin.'.'.$crang; # value für pvcorrf Sonne Altitude
$data{$name}{circular}{sprintf("%02d",$h)}{pvrlsum}{$idx} = $pvrlsum; # PV Erzeugung Summe speichern $data{$name}{circular}{$hh}{pvrlsum}{$idx} = $pvrlsum; # PV Erzeugung Summe speichern
$data{$name}{circular}{sprintf("%02d",$h)}{pvfcsum}{$idx} = $pvfcsum; # PV Prognose Summe speichern $data{$name}{circular}{$hh}{pvfcsum}{$idx} = $pvfcsum; # PV Prognose Summe speichern
$data{$name}{circular}{sprintf("%02d",$h)}{dnumsum}{$idx} = $dnum; # Anzahl aller historischen Tade dieser Range $data{$name}{circular}{$hh}{dnumsum}{$idx} = $dnum; # Anzahl aller historischen Tade dieser Range
$data{$name}{circular}{sprintf("%02d",$h)}{pvcorrf}{$idx} = $factor; $data{$name}{circular}{$hh}{pvcorrf}{$idx} = $factor;
$data{$name}{circular}{sprintf("%02d",$h)}{quality}{$idx} = $qual; $data{$name}{circular}{$hh}{quality}{$idx} = $qual;
} }
else { else {
$data{$name}{circular}{sprintf("%02d",$h)}{pvrlsum}{$crang} = $pvrlsum; $data{$name}{circular}{$hh}{pvrlsum}{$crang} = $pvrlsum;
$data{$name}{circular}{sprintf("%02d",$h)}{pvfcsum}{$crang} = $pvfcsum; $data{$name}{circular}{$hh}{pvfcsum}{$crang} = $pvfcsum;
$data{$name}{circular}{sprintf("%02d",$h)}{dnumsum}{$crang} = $dnum; $data{$name}{circular}{$hh}{dnumsum}{$crang} = $dnum;
$data{$name}{circular}{sprintf("%02d",$h)}{pvcorrf}{$crang} = $factor; $data{$name}{circular}{$hh}{pvcorrf}{$crang} = $factor;
$data{$name}{circular}{sprintf("%02d",$h)}{quality}{$crang} = $qual; $data{$name}{circular}{$hh}{quality}{$crang} = $qual;
} }
$oldfac = sprintf "%.2f", $oldfac; $oldfac = sprintf "%.2f", $oldfac;
@ -12420,6 +12432,27 @@ sub __calcNewFactor {
return ($oldfac, $factor, $dnum); return ($oldfac, $factor, $dnum);
} }
################################################################
# Qualität der Vorhersage berechnen
################################################################
sub __calcFcQuality {
my $pvfc = shift; # PV Vorhersagewert
my $pvrl = shift; # PV reale Erzeugung
return if(!$pvfc || !$pvrl);
$pvrl = sprintf "%.0f", $pvrl;
$pvfc = sprintf "%.0f", $pvfc;
my $diff = $pvfc - $pvrl;
my $hdv = 1 - abs ($diff / $pvrl); # Abweichung der Stunde, 1 = bestmöglicher Wert
$hdv = $hdv < 0 ? 0 : $hdv;
$hdv = sprintf "%.2f", $hdv;
return $hdv;
}
################################################################ ################################################################
# Berechnen Tag / Stunden Verschieber # Berechnen Tag / Stunden Verschieber
# aus aktueller Stunde + lfd. Nummer # aus aktueller Stunde + lfd. Nummer
@ -15195,9 +15228,12 @@ sub _flowGraphic {
## definierte Batterien ermitteln und zusammenfassen ## definierte Batterien ermitteln und zusammenfassen
###################################################### ######################################################
my ($batin, $bat2home, $soc, @batsoc); my ($batin, $bat2home);
for my $bn (1..$maxbatteries) { # für jede definierte Batterie my $socwhsum = 0;
$bn = sprintf "%02d", $bn; my $soc = 0;
for my $bn (1..$maxbatteries) { # für jede definierte Batterie
$bn = sprintf "%02d", $bn;
my ($err, $badev, $h) = isDeviceValid ( { name => $name, obj => 'setupBatteryDev'.$bn, method => 'attr' } ); my ($err, $badev, $h) = isDeviceValid ( { name => $name, obj => 'setupBatteryDev'.$bn, method => 'attr' } );
next if($err); next if($err);
@ -15206,10 +15242,11 @@ sub _flowGraphic {
$batin += $batinpow if(defined $batinpow); $batin += $batinpow if(defined $batinpow);
$bat2home += $bat2homepow if(defined $bat2homepow); $bat2home += $bat2homepow if(defined $bat2homepow);
push @batsoc, ReadingsNum ($name, 'Current_BatCharge_'.$bn, 0); $socwhsum += BatteryVal ($name, $bn, 'bchargewh', 0); # Batterie SoC in Wh
} }
$soc = avgArray (\@batsoc, scalar @batsoc) if(@batsoc); my $batcapsum = CurrentVal ($hash, 'batcapsum', 0); # Summe installierte Batterie Kapazität
$soc = sprintf "%.0f", ($socwhsum / $batcapsum * 100) if($batcapsum); # resultierender SoC (%) aller Batterien als Cluster
if (!defined $batin && !defined $bat2home) { if (!defined $batin && !defined $bat2home) {
$hasbat = 0; $hasbat = 0;
@ -16246,27 +16283,6 @@ sub _addHourAiRawdata {
return; return;
} }
################################################################
# Qualität der Vorhersage berechnen
################################################################
sub __calcFcQuality {
my $pvfc = shift; # PV Vorhersagewert
my $pvrl = shift; # PV reale Erzeugung
return if(!$pvfc || !$pvrl);
$pvrl = sprintf "%.0f", $pvrl;
$pvfc = sprintf "%.0f", $pvfc;
my $diff = $pvfc - $pvrl;
my $hdv = 1 - abs ($diff / $pvrl); # Abweichung der Stunde, 1 = bestmöglicher Wert
$hdv = $hdv < 0 ? 0 : $hdv;
$hdv = sprintf "%.2f", $hdv;
return $hdv;
}
############################################################### ###############################################################
# Eintritt in den KI Train Prozess normal/Blocking # Eintritt in den KI Train Prozess normal/Blocking
############################################################### ###############################################################
@ -17106,14 +17122,6 @@ sub listDataPool {
my $sub = sub { my $sub = sub {
my $day = shift; my $day = shift;
#for my $dh (keys %{$h->{$day}}) {
# if (!isNumeric ($dh)) {
# delete $data{$name}{pvhist}{$day}{$dh};
# Log3 ($name, 2, qq{$name - INFO - invalid key "$day -> $dh" was deleted from pvHistory storage});
# }
#}
my $ret; my $ret;
for my $key (sort {$a<=>$b} keys %{$h->{$day}}) { for my $key (sort {$a<=>$b} keys %{$h->{$day}}) {
@ -17398,7 +17406,7 @@ sub listDataPool {
for my $ckey (sort keys %{$h->{$idx}}) { for my $ckey (sort keys %{$h->{$idx}}) {
if (ref $h->{$idx}{$ckey} eq 'ARRAY') { if (ref $h->{$idx}{$ckey} eq 'ARRAY') {
my $aser = join " ",@{$h->{$idx}{$ckey}}; my $aser = join " ", @{$h->{$idx}{$ckey}};
$cret .= ($s1 ? $sp1 : "").$ckey." => ".$aser."\n"; $cret .= ($s1 ? $sp1 : "").$ckey." => ".$aser."\n";
} }
@ -17425,6 +17433,35 @@ sub listDataPool {
if (!keys %{$h}) { if (!keys %{$h}) {
return qq{Circular cache is empty.}; return qq{Circular cache is empty.};
} }
### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !!
##########################################################################################################################
delete $data{$name}{circular}{'01'}{pvrl_5};
delete $data{$name}{circular}{'01'}{pvrl_10};
delete $data{$name}{circular}{'01'}{pvrl_25};
delete $data{$name}{circular}{'01'}{pvrl_60};
delete $data{$name}{circular}{'01'}{pvrl_65};
delete $data{$name}{circular}{'01'}{pvrl_90};
#push @{$data{$name}{circular}{'01'}{pvrl_65}{100}}, 4561;
#push @{$data{$name}{circular}{'01'}{pvrl_65}{100}}, 4562;
#push @{$data{$name}{circular}{'01'}{pvrl_65}{100}}, 4563;
#push @{$data{$name}{circular}{'01'}{pvrl_65}{100}}, 4564;
#push @{$data{$name}{circular}{'01'}{pvrl_65}{100}}, 4565;
#
#push @{$data{$name}{circular}{'01'}{pvrl_65}{60}}, 3561;
#push @{$data{$name}{circular}{'01'}{pvrl_65}{60}}, 3562;
#
#push @{$data{$name}{circular}{'01'}{pvrl_90}{100}}, 4561;
#push @{$data{$name}{circular}{'01'}{pvrl_90}{100}}, 4562;
#push @{$data{$name}{circular}{'01'}{pvrl_90}{100}}, 4563;
#push @{$data{$name}{circular}{'01'}{pvrl_90}{100}}, 4564;
#push @{$data{$name}{circular}{'01'}{pvrl_90}{100}}, 4565;
#push @{$data{$name}{circular}{'01'}{pvrl_90}{60}}, 3561;
#push @{$data{$name}{circular}{'01'}{pvrl_90}{60}}, 3562;
############################################################################################################
for my $idx (sort keys %{$h}) { for my $idx (sort keys %{$h}) {
my $pvrl = CircularVal ($hash, $idx, 'pvrl', '-'); my $pvrl = CircularVal ($hash, $idx, 'pvrl', '-');
@ -17484,17 +17521,41 @@ sub listDataPool {
$bout .= "batout${bn}: $batout"; $bout .= "batout${bn}: $batout";
} }
$sq .= $idx." => pvapifc: $pvapifc, pvaifc: $pvaifc, pvfc: $pvfc, aihit: $aihit, pvrl: $pvrl\n"; my ($pvrlnew, $pvfcnew);
$sq .= " $bin\n"; my @pvrlkeys = map { $_ =~ /^pvrl_/xs ? $_ : '' } sort keys %{$h->{$idx}};
$sq .= " $bout\n"; my @pvfckeys = map { $_ =~ /^pvfc_/xs ? $_ : '' } sort keys %{$h->{$idx}};
$sq .= " confc: $confc, gcon: $gcons, gfeedin: $gfeedin, wcc: $wccv, rr1c: $rr1c\n";
$sq .= " temp: $temp, wid: $wid, wtxt: $wtxt\n"; for my $prl (@pvrlkeys) {
$sq .= " $prdl\n"; next if(!$prl);
$sq .= " pvcorrf: $pvcf\n"; my $lref = CircularVal ($hash, $idx, $prl, '');
$sq .= " quality: $cfq\n"; next if(!$lref);
$sq .= " pvrlsum: $pvrs\n";
$sq .= " pvfcsum: $pvfs\n"; $pvrlnew .= "\n " if($pvrlnew);
$sq .= " dnumsum: $dnus"; $pvrlnew .= _ldchash2val ( { pool => $h, idx => $idx, key => $prl, cval => $lref } );
}
for my $pfc (@pvfckeys) {
next if(!$pfc);
my $cref = CircularVal ($hash, $idx, $pfc, '');
next if(!$cref);
$pvfcnew .= "\n " if($pvfcnew);
$pvfcnew .= _ldchash2val ( { pool => $h, idx => $idx, key => $pfc, cval => $cref } );
}
$sq .= $idx." => pvapifc: $pvapifc, pvaifc: $pvaifc, pvfc: $pvfc, aihit: $aihit, pvrl: $pvrl";
$sq .= "\n $bin";
$sq .= "\n $bout";
$sq .= "\n confc: $confc, gcon: $gcons, gfeedin: $gfeedin, wcc: $wccv, rr1c: $rr1c";
$sq .= "\n temp: $temp, wid: $wid, wtxt: $wtxt";
$sq .= "\n $prdl";
$sq .= "\n pvcorrf: $pvcf";
$sq .= "\n quality: $cfq";
$sq .= "\n pvrlsum: $pvrs";
$sq .= "\n pvfcsum: $pvfs";
$sq .= "\n dnumsum: $dnus";
$sq .= "\n $pvrlnew" if($pvrlnew);
$sq .= "\n $pvfcnew" if($pvfcnew);
} }
else { else {
my ($batvl1, $batvl2, $batvl3, $batvl4, $batvl5, $batvl6, $batvl7); my ($batvl1, $batvl2, $batvl3, $batvl4, $batvl5, $batvl6, $batvl7);
@ -17766,7 +17827,24 @@ sub _ldchash2val {
for my $f (sort {$a<=>$b} keys %{$pool->{$idx}{$key}}) { for my $f (sort {$a<=>$b} keys %{$pool->{$idx}{$key}}) {
next if($f eq 'simple'); next if($f eq 'simple');
if ($f !~ /\./xs) {
if (ref $pool->{$idx}{$key}{$f} eq 'ARRAY') {
my @sub_arrays = arraySplitBy (20, @{$pool->{$idx}{$key}{$f}}); # Array in Teil-Arrays zu je 20 Elemente aufteilen
for my $suaref (@sub_arrays) { # für jedes Teil-Array Join ausführen
my $suajoined = join ' ', @{$suaref};
if (!$ret) {
$ret .= $key.' => ';
$ret .= $f.' @ '.$suajoined;
}
else {
$ret .= "\n ";
$ret .= $f.' @ '.$suajoined;
}
}
}
elsif ($f !~ /\./xs) {
$ret .= " " if($ret); $ret .= " " if($ret);
$ret .= "$f=".$pool->{$idx}{$key}{$f}; $ret .= "$f=".$pool->{$idx}{$key}{$f};
my $ct = ($ret =~ tr/=// // 0) / 10; my $ct = ($ret =~ tr/=// // 0) / 10;
@ -20213,6 +20291,48 @@ sub sunalt2bin {
return $bin; return $bin;
} }
###############################################################################
# Teilt das Original-Array in Unter-Arrays auf, die den Inhalt des
# Originals enthalten. Die Größe jedes Unterarrays ist gleich oder kleiner als
# $split_size, wobei das letzte Array in der Regel kleiner ist, wenn nicht
# genügend Elemente in @original vorhanden sind.
# (aus https://metacpan.org/dist/Array-Split/source/lib/Array/Split.pm)
#
# arraySplitBy ($split_size, @original)
###############################################################################
sub arraySplitBy {
my $split_size = shift;
$split_size = max ($split_size, 1);
my @sub_arrays;
while (@_) {
push @sub_arrays, [splice @_, 0, $split_size];
}
return @sub_arrays;
}
###############################################################################
# Teilt das angegebene Array in die Anzahl $count Unterarrays auf.
# Es wird versucht, so viele Unter-Arrays zu erstellen, wie $count angibt,
# aber es werden weniger zurückgegeben, wenn nicht genügend Elemente in
# @original vorhanden sind.
#
# Gibt eine Liste von Array-Referenzen zurück.
# (aus https://metacpan.org/dist/Array-Split/source/lib/Array/Split.pm)
#
# arraySplitInto ($count, @original)
###############################################################################
sub arraySplitInto {
my ($count, @original) = @_;
$count = max( $count, 1 );
my $size = ceil @original / $count;
return arraySplitBy ($size, @original);
}
############################################################################### ###############################################################################
# verscrambelt einen String # verscrambelt einen String
############################################################################### ###############################################################################
@ -21972,13 +22092,14 @@ to ensure that the system configuration is correct.
<ul> <ul>
<table> <table>
<colgroup> <col width="25%"> <col width="75%"> </colgroup> <colgroup> <col width="25%"> <col width="75%"> </colgroup>
<tr><td> <b>bname </b> </td><td>Name of the device </td></tr> <tr><td> <b>bname </b> </td><td>Name of the device </td></tr>
<tr><td> <b>balias </b> </td><td>Alias of the device </td></tr> <tr><td> <b>balias </b> </td><td>Alias of the device </td></tr>
<tr><td> <b>basynchron </b> </td><td>Mode of processing received battery events </td></tr> <tr><td> <b>basynchron </b> </td><td>Mode of processing received battery events </td></tr>
<tr><td> <b>bcharge </b> </td><td>SOC (State of Charge) of the battery (%) </td></tr> <tr><td> <b>bcharge </b> </td><td>current SoC (State of Charge) of the battery (%) </td></tr>
<tr><td> <b>binstcap </b> </td><td>installed battery capacity (Wh) </td></tr> <tr><td> <b>bchargewh </b> </td><td>current SoC (State of Charge) of the battery (Wh) </td></tr>
<tr><td> <b>bpowerin </b> </td><td>current charging power (W) </td></tr> <tr><td> <b>binstcap </b> </td><td>installed battery capacity (Wh) </td></tr>
<tr><td> <b>bpowerout </b> </td><td>current discharge power (W) </td></tr> <tr><td> <b>bpowerin </b> </td><td>current charging power (W) </td></tr>
<tr><td> <b>bpowerout </b> </td><td>current discharge power (W) </td></tr>
</table> </table>
</ul> </ul>
@ -23687,7 +23808,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
<li><b>batteryTrigger &lt;1on&gt;=&lt;Wert&gt; &lt;1off&gt;=&lt;Wert&gt; [&lt;2on&gt;=&lt;Wert&gt; &lt;2off&gt;=&lt;Wert&gt; ...] </b> <br><br> <li><b>batteryTrigger &lt;1on&gt;=&lt;Wert&gt; &lt;1off&gt;=&lt;Wert&gt; [&lt;2on&gt;=&lt;Wert&gt; &lt;2off&gt;=&lt;Wert&gt; ...] </b> <br><br>
Generiert Trigger bei Über- bzw. Unterschreitung bestimmter Batterieladungswerte (SoC in %). <br> Generiert Trigger bei Über- bzw. Unterschreitung bestimmter Batterieladungswerte (SoC in %). <br>
Der verwendete SoC wird als resultierender SoC (Summe aktuelle Ladung aller Batterie Geräte zur im Verhältnis zur installierten Der verwendete SoC wird als resultierender SoC (Summe aktuelle Ladung aller Batterie Geräte im Verhältnis zur installierten
Gesamtkapazität) gebildet, d.h. alle Batterien werden als ein Cluster betrachtet. <br> Gesamtkapazität) gebildet, d.h. alle Batterien werden als ein Cluster betrachtet. <br>
Überschreiten die letzten drei SoC-Messungen eine definierte <b>Xon-Bedingung</b>, wird das Reading Überschreiten die letzten drei SoC-Messungen eine definierte <b>Xon-Bedingung</b>, wird das Reading
<b>batteryTrigger_X = on</b> erstellt/gesetzt. <br> <b>batteryTrigger_X = on</b> erstellt/gesetzt. <br>
@ -24444,13 +24565,14 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
<ul> <ul>
<table> <table>
<colgroup> <col width="25%"> <col width="75%"> </colgroup> <colgroup> <col width="25%"> <col width="75%"> </colgroup>
<tr><td> <b>bname </b> </td><td>Name des Gerätes </td></tr> <tr><td> <b>bname </b> </td><td>Name des Gerätes </td></tr>
<tr><td> <b>balias </b> </td><td>Alias des Gerätes </td></tr> <tr><td> <b>balias </b> </td><td>Alias des Gerätes </td></tr>
<tr><td> <b>basynchron </b> </td><td>Modus der Verarbeitung empfangener Batterie-Events </td></tr> <tr><td> <b>basynchron </b> </td><td>Modus der Verarbeitung empfangener Batterie-Events </td></tr>
<tr><td> <b>bcharge </b> </td><td>SOC (State of Charge) der Batterie (%) </td></tr> <tr><td> <b>bcharge </b> </td><td>aktueller SoC (State of Charge) der Batterie (%) </td></tr>
<tr><td> <b>binstcap </b> </td><td>installierte Batteriekapazität (Wh) </td></tr> <tr><td> <b>bchargewh </b> </td><td>aktueller SoC (State of Charge) der Batterie (Wh) </td></tr>
<tr><td> <b>bpowerin </b> </td><td>momentane Ladeleistung (W) </td></tr> <tr><td> <b>binstcap </b> </td><td>installierte Batteriekapazität (Wh) </td></tr>
<tr><td> <b>bpowerout </b> </td><td>momentane Entladeleistung (W) </td></tr> <tr><td> <b>bpowerin </b> </td><td>momentane Ladeleistung (W) </td></tr>
<tr><td> <b>bpowerout </b> </td><td>momentane Entladeleistung (W) </td></tr>
</table> </table>
</ul> </ul>