From c91176a3e38bc3dd5828183eefdedbf39a4d9425 Mon Sep 17 00:00:00 2001 From: DS_Starter Date: Sun, 25 May 2025 19:08:03 +0000 Subject: [PATCH] 76_SolarForecast: Version 1.52.5 git-svn-id: https://svn.fhem.de/fhem/trunk@29997 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 1 + fhem/FHEM/76_SolarForecast.pm | 560 ++++++++++++++++++++++------------ 2 files changed, 366 insertions(+), 195 deletions(-) diff --git a/fhem/CHANGED b/fhem/CHANGED index 2abceb852..a470d7cba 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # 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 + - feature: 76_SolarForecast: Version 1.52.5 - feature: 76_SolarForecast: Version 1.52.4 - feature: 98_weekprofile: attribute extraClientModules to support further modules with attribute weekprofile diff --git a/fhem/FHEM/76_SolarForecast.pm b/fhem/FHEM/76_SolarForecast.pm index a6b93ba95..aedfd7a0a 100644 --- a/fhem/FHEM/76_SolarForecast.pm +++ b/fhem/FHEM/76_SolarForecast.pm @@ -160,6 +160,10 @@ BEGIN { # Versions History intern my %vNotesIntern = ( + "1.52.5" => "25.05.2025 edit commandref, _batChargeMgmt: add load management time slot, ctrlBatSocManagementXX: new key lcSlot ". + "check attribute values for prohibited occurrence [...] Forum: https://forum.fhem.de/index.php?msg=1342147 ". + "_flowGraphic: bugfix chain style in case of logical on/off Forum: https://forum.fhem.de/index.php?msg=1342122 ". + "_attrBatteryDev: more checks (cap) ", "1.52.4" => "20.05.2025 commandref edited, setupInverterDevXX: change pv to pvOut, new key pvIn ". "fix devision by zero -Forum: https://forum.fhem.de/index.php?msg=1341884, __calcFcQuality: minor code change ". "ctrlSpecialReadings: new Topic BatWeightedTotalSOC ", @@ -208,7 +212,7 @@ my %vNotesIntern = ( "1.50.1" => "07.04.2025 new pvCorrectionFactor_Auto option 'on_complex_api_ai' to use average of AI + API forecast if AI Hit ". "some code changes ", "1.50.0" => "05.04.2025 changes V 1.49.1 - 1.49.6 as new major release ", - "1.49.6" => "05.04.2025 some code changes, _flowGraphic: position of home text element, new attr consumerControl->dummyIcon, _batChargeRecmd: change loading release ". + "1.49.6" => "05.04.2025 some code changes, _flowGraphic: position of home text element, new attr consumerControl->dummyIcon, _batChargeMgmt: change loading release ". "attr consumerAdviceIcon replaced by consumerControl->adviceIcon ". "attr consumerLegend replaced by consumerControl->showLegend ". "attr consumerLink replaced by consumerControl->detailLink ", @@ -218,7 +222,7 @@ my %vNotesIntern = ( "attr ctrlInterval replaced by plantControl->cycleInterval ". "attr ctrlGenPVdeviation replaced by plantControl->genPVdeviation ". "setupBatteryDevXX: new keys pinmax, poutmax ", - "1.49.4" => "28.03.2025 _batChargeRecmd: revert Loading release changes of V 1.49.0, _transferAPIRadiationValues: fix sunalt for next day ". + "1.49.4" => "28.03.2025 _batChargeMgmt: revert Loading release changes of V 1.49.0, _transferAPIRadiationValues: fix sunalt for next day ". "Home Node: Mouse over show Autarky Rate, flowGraphicControl: new key strokeconsumerdyncol ", "1.49.3" => "27.03.2025 flowGraphicControl: new key homenodedyncol ", "1.49.2" => "26.03.2025 ___enableSwitchByBatPrioCharge: fix usage of rusulting SOC of all batteries ", @@ -228,7 +232,7 @@ my %vNotesIntern = ( "add Attr graphicBeamHeightLevel3, Compatibility of Rad1h data between DWD and OpenMeteo established ". "set reset aiData deletes raw data also, _transferAPIRadiationValues: AI PV estimate limited to inverter capacity summary ". "__calcPVestimates: pv power summary of all strings connected to inverter limited to inverter capacity summary ". - "_batChargeRecmd: fix calc if more than one batteries are installed, set aiDecTree: new option rawDataGHIreplace ". + "_batChargeMgmt: fix calc if more than one batteries are installed, set aiDecTree: new option rawDataGHIreplace ". "new Attr plantControl with keys feedinPowerLimit, batteryPreferredCharge, consForecastInPlanning ". "Attr affectBatteryPreferredCharge, affectConsForecastInPlanning, ctrlShowLink are obsolete ", "1.48.0" => "14.03.2025 edit commandref, add graphicBeam layer 5 and 6, attr ctrlAIdataStorageDuration, ctrlAIshiftTrainStart removed ", @@ -240,7 +244,7 @@ my %vNotesIntern = ( "1.47.1" => "07.03.2025 __substituteIcon: consider Tooltip content if ctrlBatSocManagementXX is set ", "1.47.0" => "05.03.2025 aiInit: change AI init sequence, use Random Forest with Ensemble algorithm, use Scalar::Util ". "_beamGraphic.*: change decimal places für battery SoC, set aiDecTree: change addInstances to addInstAndTrain ". - "addInstAndTrain is generally executed non-blocking, _batChargeRecmd: use effective surplus for soc forecast, ". + "addInstAndTrain is generally executed non-blocking, _batChargeMgmt: use effective surplus for soc forecast, ". "consider !ctrlBatSocManagement for permanent Bat loading release, _transferBatteryValues: change verbose 2 -> 3 ". "new attr aiControl, attr ctrlAIdataStorageDuration, ctrlAIshiftTrainStart are obsolete ", "1.46.5" => "28.02.2025 new ctrlSpecialReadings key todayConsumptionForecastDay ", @@ -267,7 +271,7 @@ my %vNotesIntern = ( "change weather display management (don), some minor bugfixes ", "1.45.1" => "02.02.2025 _specialActivities: Task 1 __deleteEveryHourControls changed, all Tasks adapted ". "_retrieveMessageFile: fix path in __updWriteFile, fix https://forum.fhem.de/index.php?msg=1332721 ", - "1.45.0" => "01.02.2025 new function timestringsFromOffset, _batChargeRecmd: change condition for load release ". + "1.45.0" => "01.02.2025 new function timestringsFromOffset, _batChargeMgmt: change condition for load release ". "_addHourAiRawdata: add hour 24 (of day before), remove x-migrate -> auto migrate pv data ". "Pool output width limited to 140 characters, checkPlantConfig: add installen Perl Modules check ", "1.44.5" => "30.01.2025 temp2bin: expand to more negative bins, bugfix: https://forum.fhem.de/index.php?msg=1332421 ". @@ -275,7 +279,7 @@ my %vNotesIntern = ( "1.44.4" => "26.01.2025 _getlistPVCircular: change width of output, new sub _listDataPoolPvHist, fix bug in hrepl Hash ". "remove Attr graphicBeam1MaxVal,ctrlAreaFactorUsage ", "1.44.3" => "25.01.2025 Notification System: minor changes, special Readings todayBatInSum todayBatOutSum ", - "1.44.2" => "23.01.2025 _batChargeRecmd: user storeffdef, show historical battery SoC when displaying the battery in the bar graph ", + "1.44.2" => "23.01.2025 _batChargeMgmt: user storeffdef, show historical battery SoC when displaying the battery in the bar graph ", "1.44.1" => "20.01.2025 Notification system: minor fixes, integration of controls_solarforecast_messages_test/prod ". "Define: random start of Timer subs, consumerXX: consumer device may have specified an own alias ", "1.44.0" => "19.01.2025 _listDataPoolCircular: may select a dedicated hour, add temporary Migrate funktion x_migrate ". @@ -285,15 +289,15 @@ my %vNotesIntern = ( "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.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 _batChargeMgmt: bugfix calc socwh, Attr graphicBeam1MaxVal, (experimental) ctrlAreaFactorUsage are obsolete ". "trackFlex now default in DWD Model, replace title Charging recommendation by Charging release ". "_saveEnergyConsumption: add dowrite flag, edit comref ", - "1.43.1" => "11.01.2025 _batChargeRecmd: bugfix PV daily surplus update, _collectAllRegConsumers: fix interruptable hysteresis ". + "1.43.1" => "11.01.2025 _batChargeMgmt: bugfix PV daily surplus update, _collectAllRegConsumers: fix interruptable hysteresis ". "__batteryOnBeam: show soc forecast for hour 00 and fix english translation ". - "_batChargeRecmd: consider battery capacity as part of total capacity ", + "_batChargeMgmt: consider battery capacity as part of total capacity ", "1.43.0" => "10.01.2025 graphicShowNight: add possible Time Sync of chart bar level 1 and the other ". "_addDynAttr: minor fix for graphicBeamXContent, new attr ctrlNextHoursSoCForecastReadings ", - "1.42.0" => "07.01.2025 change socslidereg to batsocslidereg, _batChargeRecmd: add value to nexthours ". + "1.42.0" => "07.01.2025 change socslidereg to batsocslidereg, _batChargeMgmt: add value to nexthours ". "entryGraphic: enrich hfcg hash, __normDecPlaces: use it from/to battery, ". "setupBatteryDevXX : new icon & show key, colour of icon can be changed separately, maxbatteries set to 3 ". "medianArray: switch to simpel array sort, Task 1: delete Weather-API status data at night ". @@ -758,7 +762,8 @@ my %hattr = ( # H for my $bn (1..MAXBATTERIES) { $bn = sprintf "%02d", $bn; - $hattr{'setupBatteryDev'.$bn}{fn} = \&_attrBatteryDev; + $hattr{'setupBatteryDev'.$bn}{fn} = \&_attrBatteryDev; + $hattr{'ctrlBatSocManagement'.$bn}{fn} = \&_attrBatSocManagement; } for my $in (1..MAXINVERTER) { @@ -782,7 +787,7 @@ my $hcompoattr = { # C setupStringDeclination => '', setupStringPeak => '', }; - + for my $cn (1..MAXCONSUMER) { $cn = sprintf "%02d", $cn; $hcompoattr->{'consumer'.${cn}} = ''; @@ -804,7 +809,7 @@ my $hcompoattr = { # C $hcompoattr->{'setupOtherProducer'.${pn}} = ''; } -my @hcompoattrkeys = keys %{$hcompoattr}; # Array der Schlüssel aller Composit-Attribute +my @hcompoattrkeys = sort keys %{$hcompoattr}; # Array der Schlüssel aller Composit-Attribute my %htr = ( # Hash even/odd für 0 => { cl => 'even' }, @@ -1082,6 +1087,10 @@ my %htitles = ( DE => qq{prognostizierte PV-Erzeugung} }, onlybatw => { EN => qq{Battery}, DE => qq{Batterie} }, + simplyes => { EN => qq{yes}, + DE => qq{ja} }, + simpleno => { EN => qq{no}, + DE => qq{nein} }, socrfcba => { EN => qq{real battery charge achieved or SoC forecast Battery}, DE => qq{real erreichte Batterieladung bzw. SoC Prognose Batterie} }, socfcbat => { EN => qq{SoC forecast Battery}, @@ -1098,6 +1107,8 @@ my %htitles = ( DE => qq{SoC Prognose} }, socbaths => { EN => qq{SoC at the end of the hour}, DE => qq{SoC am Ende der Stunde} }, + lcactive => { EN => qq{Charge management activated}, + DE => qq{Lademanagement aktiviert} }, bcharrel => { EN => qq{Charging release (activate release for charging the battery if necessary)}, DE => qq{Ladefreigabe (evtl. Freigabe zum Laden der Batterie aktivieren)} }, bncharel => { EN => qq{only charge if the feed-in limit is exceeded}, @@ -1532,6 +1543,11 @@ my %hfspvh = ( $hfspvh{'batprogsoc'.$bn}{storname} = 'batprogsoc'.$bn; $hfspvh{'batprogsoc'.$bn}{validkey} = undef; $hfspvh{'batprogsoc'.$bn}{fpar} = undef; + + $hfspvh{'lcintimebat'.$bn}{fn} = \&_storeVal; # Ladesteurung der Batterie In Time, d.h. war sie aktiv? (1 - Ja, 0 - Nein) + $hfspvh{'lcintimebat'.$bn}{storname} = 'lcintimebat'.$bn; + $hfspvh{'lcintimebat'.$bn}{validkey} = undef; + $hfspvh{'lcintimebat'.$bn}{fpar} = undef; $hfspvh{'batmaxsoc'.$bn}{fn} = \&_storeVal; # max. erreichter SOC des Tages $hfspvh{'batmaxsoc'.$bn}{storname} = 'batmaxsoc'.$bn; @@ -1801,7 +1817,7 @@ sub Set { closedir (DIR); my $rf = @bkps ? ','.join ",", reverse sort @bkps : ''; - my $cakeys = join ',', sort @hcompoattrkeys; + my $cakeys = join ',', @hcompoattrkeys; my $keynum = scalar @hcompoattrkeys + 1; ## allg. gültige Setter @@ -6026,28 +6042,28 @@ sub Attr { deleteReadingspec ($hash, "Battery_NextHour.._SoCforecast_.."); } - if ($aName =~ /ctrlBatSocManagement/xs && $init_done) { - my $bn = (split 'ctrlBatSocManagement', $aName)[1]; + #if ($aName =~ /ctrlBatSocManagement/xs && $init_done) { + # my $bn = (split 'ctrlBatSocManagement', $aName)[1]; - if ($cmd eq 'set') { - return qq{Define the key 'cap' with "attr $name setupBatteryDev${bn}" before this attribute in the correct form.} - if(!BatteryVal ($hash, $bn, 'binstcap', 0)); # https://forum.fhem.de/index.php?msg=1310930 + # if ($cmd eq 'set') { + # return qq{Define the key 'cap' with "attr $name setupBatteryDev${bn}" before this attribute in the correct form.} + # if(!BatteryVal ($hash, $bn, 'binstcap', 0)); # https://forum.fhem.de/index.php?msg=1310930 - my ($lowSoc, $upSoc, $maxsoc, $careCycle) = __parseAttrBatSoc ($name, $aVal); + # my ($lowSoc, $upSoc, $maxsoc, $careCycle) = __parseAttrBatSoc ($name, $aVal); - return 'The attribute syntax is wrong' if(!$lowSoc || !$upSoc || $lowSoc !~ /[0-9]+$/xs); + # return 'The attribute syntax is wrong' if(!$lowSoc || !$upSoc || $lowSoc !~ /[0-9]+$/xs); - if (!($lowSoc > 0 && $lowSoc < $upSoc && $upSoc < $maxsoc && $maxsoc <= 100)) { - return 'The specified values are not plausible. Compare the attribute help.'; - } - } - else { - deleteReadingspec ($hash, 'Battery_.*'); - } + # if (!($lowSoc > 0 && $lowSoc < $upSoc && $upSoc < $maxsoc && $maxsoc <= 100)) { + # return 'The specified values are not plausible. Compare the attribute help.'; + # } + # } + # else { + # deleteReadingspec ($hash, 'Battery_.*'); + # } - delete $data{$name}{circular}{99}{'lastTsMaxSocRchd'.$bn}; - delete $data{$name}{circular}{99}{'nextTsMaxSocChge'.$bn}; - } + # delete $data{$name}{circular}{99}{'lastTsMaxSocRchd'.$bn}; + # delete $data{$name}{circular}{99}{'nextTsMaxSocChge'.$bn}; + #} if ($aName eq 'graphicHeaderOwnspecValForm') { $err = isGhoValFormValid ($name, $aVal); @@ -6121,11 +6137,13 @@ sub _attrconsumer { ## no critic "not used" exconfc => '', }; - if ($cmd eq "set") { + if ($cmd eq "set") { my ($err, $codev, $h) = isDeviceValid ( { name => $name, obj => $aVal, method => 'string' } ); return $err if($err); for my $key (keys %{$h}) { + return 'The keys entered must not contain square brackets [...]' if($key =~ /[\[\]]+/xs); # Absturzschutz! + if (!grep /^$key$/, keys %{$valid}) { return qq{The key '$key' is not a valid key in attribute '$aName'}; } @@ -6365,10 +6383,12 @@ sub _attrconsumerControl { ## no critic "not used" my ($a, $h) = parseParams ($aVal); - if ($cmd eq 'set') { + if ($cmd eq 'set') { ## 1. Durchlauf - Prüfungen ############################# for my $key (keys %{$h}) { + return 'The keys entered must not contain square brackets [...]' if($key =~ /[\[\]]+/xs); # Absturzschutz! + if (!grep /^$key$/, keys %{$valid}) { return qq{The key '$key' is not a valid key in attribute '$aName'}; } @@ -6510,10 +6530,12 @@ sub _attrgraphicControl { ## no critic "not used" my ($a, $h) = parseParams ($aVal); - if ($cmd eq 'set') { + if ($cmd eq 'set') { ## 1. Durchlauf - Prüfungen ############################# for my $key (keys %{$h}) { + return 'The keys entered must not contain square brackets [...]' if($key =~ /[\[\]]+/xs); # Absturzschutz! + if (!grep /^$key$/, keys %{$valid}) { return qq{The key '$key' is not a valid key in attribute '$aName'}; } @@ -6595,6 +6617,8 @@ sub _attrflowGraphicControl { ## no critic "not used" my ($a, $h) = parseParams ($aVal); if ($cmd eq 'set') { + return 'The parameters entered must not contain square brackets [...]' if($aVal =~ /[\[\]]+/xs); # Absturzschutz! + ## 1. Durchlauf - Prüfungen ############################# for my $key (keys %{$h}) { @@ -6661,10 +6685,12 @@ sub _attraiControl { ## no critic "not used" my ($a, $h) = parseParams ($aVal); - if ($cmd eq 'set') { + if ($cmd eq 'set') { ## 1. Durchlauf - Prüfungen ############################# for my $key (keys %{$h}) { + return 'The keys entered must not contain square brackets [...]' if($key =~ /[\[\]]+/xs); # Absturzschutz! + if (!grep /^$key$/, keys %{$valid}) { return qq{The key '$key' is not a valid key in attribute '$aName'}; } @@ -6735,10 +6761,12 @@ sub _attrplantControl { ## no critic "not used" my ($a, $h) = parseParams ($aVal); - if ($cmd eq 'set') { + if ($cmd eq 'set') { ## 1. Durchlauf - Prüfungen ############################# for my $key (keys %{$h}) { + return 'The keys entered must not contain square brackets [...]' if($key =~ /[\[\]]+/xs); # Absturzschutz! + if (!grep /^$key$/, keys %{$valid}) { return qq{The key '$key' is not a valid key in attribute '$aName'}; } @@ -6808,11 +6836,13 @@ sub _attrMeterDev { ## no critic "not used" asynchron => '', }; - if ($paref->{cmd} eq 'set') { + if ($paref->{cmd} eq 'set') { my ($err, $medev, $h) = isDeviceValid ( { name => $name, obj => $aVal, method => 'string' } ); return $err if($err); for my $key (keys %{$h}) { + return 'The keys entered must not contain square brackets [...]' if($key =~ /[\[\]]+/xs); # Absturzschutz! + if (!grep /^$key$/, keys %{$valid}) { return qq{The key '$key' is not a valid key in attribute '$aName'}; } @@ -6883,11 +6913,13 @@ sub _attrProducerDev { ## no critic "not used" etotal => '', }; - if ($paref->{cmd} eq 'set') { + if ($paref->{cmd} eq 'set') { my ($err, $dev, $h) = isDeviceValid ( { name => $name, obj => $aVal, method => 'string' } ); return $err if($err); for my $key (keys %{$h}) { + return 'The keys entered must not contain square brackets [...]' if($key =~ /[\[\]]+/xs); # Absturzschutz! + if (!grep /^$key$/, keys %{$valid}) { return qq{The key '$key' is not a valid key in attribute '$aName'}; } @@ -6927,7 +6959,6 @@ sub _attrInverterDev { ## no critic "not used" my $name = $paref->{name}; my $aVal = $paref->{aVal}; my $aName = $paref->{aName}; - my $type = $paref->{type}; return if(!$init_done); @@ -6948,7 +6979,7 @@ sub _attrInverterDev { ## no critic "not used" asynchron => { comp => '(0|1)', act => 0 }, }; - if ($paref->{cmd} eq 'set') { + if ($paref->{cmd} eq 'set') { my ($err, $indev, $h) = isDeviceValid ( { name => $name, obj => $aVal, method => 'string' } ); return $err if($err); @@ -6957,6 +6988,8 @@ sub _attrInverterDev { ## no critic "not used" } for my $key (keys %{$h}) { + return 'The keys entered must not contain square brackets [...]' if($key =~ /[\[\]]+/xs); # Absturzschutz! + if (!grep /^$key$/, keys %{$valid}) { return qq{The key '$key' is not a valid key in attribute '$aName'}; } @@ -7307,50 +7340,57 @@ sub _attrBatteryDev { ## no critic "not used" my $bn = (split 'setupBatteryDev', $aName)[1]; my $valid = { - pin => '', - pout => '', - pinmax => '', - poutmax => '', - intotal => '', - outtotal => '', - cap => '', - charge => '', - icon => '', - show => '', - asynchron => '', + pin => { comp => '.+', must => 1, act => 0 }, + pout => { comp => '.+', must => 1, act => 0 }, + pinmax => { comp => '\d+', must => 0, act => 0 }, + poutmax => { comp => '\d+', must => 0, act => 0 }, + intotal => { comp => '.*', must => 0, act => 0 }, + outtotal => { comp => '.*', must => 0, act => 0 }, + cap => { comp => '((?:\d+$|(?!\d+(?:\.\d+)?:)[^:]+:(?:k?Wh)$))', must => 1, act => 0 }, + charge => { comp => '.*', must => 0, act => 0 }, + icon => { comp => '.*', must => 0, act => 0 }, + show => { comp => '(?:[0-3](?::(?:top|bottom))?)', must => 0, act => 0 }, + asynchron => { comp => '(0|1)', must => 0, act => 0 }, }; - if ($paref->{cmd} eq 'set') { + if ($paref->{cmd} eq 'set') { my ($err, $badev, $h) = isDeviceValid ( { name => $name, obj => $aVal, method => 'string' } ); return $err if($err); + + for my $mkey (keys %{$valid}) { + return qq{The key '$mkey' is mandatory for setting in attribute '$aName'} if($valid->{$mkey}{must} && !exists $h->{$mkey}); + } for my $key (keys %{$h}) { + return 'The keys entered must not contain square brackets [...]' if($key =~ /[\[\]]+/xs); # Absturzschutz! + if (!grep /^$key$/, keys %{$valid}) { return qq{The key '$key' is not a valid key in attribute '$aName'}; } - } + + my $comp = $valid->{$key}{comp}; + next if(!$comp); - if (!$h->{pin} || !$h->{pout} || !$h->{cap}) { - return qq{One or more of the keys 'pin, pout, cap' are missing. Please note the command reference.}; - } + if ($h->{$key} =~ /^$comp$/xs) { + if ($valid->{$key}{act}) { + $paref->{akey} = $key; + $paref->{keyval} = $h->{$key}; - if ($h->{pinmax} && $h->{pinmax} !~ /^\d+$/xs) { - return qq{The key “pinmax” may only be specified by whole numbers}; - } + my $err = __attrKeyAction ($paref); - if ($h->{poutmax} && $h->{poutmax} !~ /^\d+$/xs) { - return qq{The key “poutmax” may only be specified by whole numbers}; - } + delete $paref->{keyval}; + delete $paref->{akey}; - if ($h->{show} && $h->{show} =~ /:/xs) { - my ($show, $pos) = split ':', $h->{show}; - $pos //= 'xx'; - - if ($pos !~ /^(top|bottom)$/xs) { - return qq{The key 'show' is not set correctly. Please note the command reference.}; + return $err if($err); + } + } + else { + return "The key '$key=$h->{$key}' is not specified correctly. Please refer to the command reference."; } } + ## 2. Durchlauf - Endprüfung + ############################# if (($h->{pin} !~ /-/xs && $h->{pin} !~ /:/xs) || ($h->{pout} !~ /-/xs && $h->{pout} !~ /:/xs)) { return qq{The keys 'pin' and/or 'pout' are not set correctly. Please note the command reference.}; @@ -7364,6 +7404,8 @@ sub _attrBatteryDev { ## no critic "not used" delete $data{$name}{batteries}{$bn}{bicon}; delete $data{$name}{batteries}{$bn}{bshowingraph}; delete $data{$name}{batteries}{$bn}{bposingraph}; + delete $data{$name}{batteries}{$bn}{bpinmax}; + delete $data{$name}{batteries}{$bn}{bpoutmax}; } elsif ($paref->{cmd} eq 'del') { readingsDelete ($hash, 'Current_PowerBatIn_'.$bn); @@ -7394,6 +7436,87 @@ sub _attrBatteryDev { ## no critic "not used" return; } +################################################################ +# Attr ctrlBatSocManagementXX +################################################################ +sub _attrBatSocManagement { ## no critic "not used" + my $paref = shift; + my $name = $paref->{name}; + my $aName = $paref->{aName}; + my $aVal = $paref->{aVal}; + my $cmd = $paref->{cmd}; + + return if(!$init_done); + + my $hash = $defs{$name}; + my $bn = (split 'ctrlBatSocManagement', $aName)[1]; + + return qq{Define the key 'cap' with "attr $name setupBatteryDev${bn}" before this attribute.} + if(!BatteryVal ($name, $bn, 'binstcap', 0)); # https://forum.fhem.de/index.php?msg=1310930 + + my $valid = { + lowSoc => { comp => '(100|[1-9]?[0-9])', must => 1, act => 0 }, + upSoC => { comp => '(100|[1-9]?[0-9])', must => 1, act => 0 }, + maxSoC => { comp => '(100|[1-9]?[0-9])', must => 0, act => 0 }, + careCycle => { comp => '\d+', must => 0, act => 0 }, + lcSlot => { comp => '((?:[01]\d|2[0-3]):[0-5]\d-(?:[01]\d|2[0-3]):[0-5]\d)', must => 0, act => 1 }, + }; + + my ($a, $h) = parseParams ($aVal); + + if ($cmd eq 'set') { + ## 1. Durchlauf - Prüfungen + ############################# + for my $mkey (keys %{$valid}) { + return qq{The key '$mkey' is mandatory for setting in attribute '$aName'} if($valid->{$mkey}{must} && !exists $h->{$mkey}); + } + + for my $key (keys %{$h}) { + return 'The keys entered must not contain square brackets [...]' if($key =~ /[\[\]]+/xs); # Absturzschutz! + + if (!grep /^$key$/, keys %{$valid}) { + return qq{The key '$key' is not a valid key in attribute '$aName'}; + } + + my $comp = $valid->{$key}{comp}; + next if(!$comp); + + if ($h->{$key} =~ /^$comp$/xs) { + if ($valid->{$key}{act}) { + $paref->{akey} = $key; + $paref->{keyval} = $h->{$key}; + + my $err = __attrKeyAction ($paref); + + delete $paref->{keyval}; + delete $paref->{akey}; + + return $err if($err); + } + } + else { + return "The key '$key=$h->{$key}' is not specified correctly. Please refer to the command reference."; + } + } + + ## 2. Durchlauf - Endprüfung + ############################# + my ($lowSoc, $upSoc, $maxsoc, $careCycle, $lcSlot) = __parseAttrBatSoc ($name, $aVal); + + if (!($lowSoc > 0 && $lowSoc < $upSoc && $upSoc < $maxsoc)) { + return 'The specified values are not plausible. Compare the attribute help.'; + } + } + else { + deleteReadingspec ($hash, 'Battery_.*'); + } + + delete $data{$name}{circular}{99}{'lastTsMaxSocRchd'.$bn}; + delete $data{$name}{circular}{99}{'nextTsMaxSocChge'.$bn}; + +return; +} + ################################################################ # Attr setupWeatherDevX ################################################################ @@ -7559,8 +7682,15 @@ sub __attrKeyAction { } } } - - if ($akey eq 'genPVdeviation' && $keyval eq 'daily') { + + if ($akey eq 'lcSlot') { + my $dt = timestringsFromOffset (time, 0); + my ($lcstart, $lcend) = split "-", $keyval; + my $lcstartts = timestringToTimestamp ("$dt->{date} ${lcstart}:00"); + my $lcendts = timestringToTimestamp ("$dt->{date} ${lcend}:59"); + return qq{The value '$keyval' is not valid for key '$akey'. The slot start must be earlier than the slot end.} if($lcstartts > $lcendts); + } + elsif ($akey eq 'genPVdeviation' && $keyval eq 'daily') { readingsDelete ($hash, 'Today_PVdeviation'); delete $data{$name}{circular}{99}{tdayDvtn}; } @@ -8810,7 +8940,7 @@ sub centralTask { _transferMeterValues ($centpars); # Energy Meter auswerten _transferBatteryValues ($centpars); # Batteriewerte einsammeln _batSocTarget ($centpars); # Batterie Optimum Ziel SOC berechnen - _batChargeRecmd ($centpars); # Batterie Ladefreigabe berechnen und erstellen + _batChargeMgmt ($centpars); # Batterie Ladefreigabe berechnen und erstellen _manageConsumerData ($centpars); # Consumer Daten sammeln und Zeiten planen _calcConsForecast_circular ($centpars); # neue Verbrauchsprognose über pvCircular @@ -11245,10 +11375,11 @@ sub __parseAttrBatSoc { my ($pa,$ph) = parseParams ($cgbt); my $lowSoc = $ph->{lowSoc}; my $upSoc = $ph->{upSoC}; + my $lcslot = $ph->{lcSlot}; my $maxsoc = $ph->{maxSoC} // MAXSOCDEF; # optional (default: MAXSOCDEF) my $careCycle = $ph->{careCycle} // CARECYCLEDEF; # Ladungszyklus (Maintenance) für maxSoC in Tagen -return ($lowSoc, $upSoc, $maxsoc, $careCycle); +return ($lowSoc, $upSoc, $maxsoc, $careCycle, $lcslot); } ################################################################ @@ -11290,7 +11421,7 @@ return $sf; ################################################################ # Erstellung Batterie Ladefreigabe + SoC Prognose ################################################################ -sub _batChargeRecmd { +sub _batChargeMgmt { my $paref = shift; my $name = $paref->{name}; my $chour = $paref->{chour}; @@ -11339,7 +11470,7 @@ sub _batChargeRecmd { my $batinstcap = BatteryVal ($name, $bn, 'binstcap', 0); # installierte Batteriekapazität Wh if (!$inplim || !$batinstcap) { - debugLog ($paref, 'batteryManagement', "WARNING - The requirements for dynamic battery charge recommendation are not met. Exit."); + debugLog ($paref, 'batteryManagement', "WARNING - The requirements for dynamic battery charge recommendation are not met. Check the key 'cap' for Bat '$bn'. Exit."); return; } @@ -11355,20 +11486,28 @@ sub _batChargeRecmd { my $cgbt = AttrVal ($name, 'ctrlBatSocManagement'.$bn, undef); my $sf = __batCapShareFactor ($hash, $bn); # Anteilsfaktor der Batterie XX Kapazität an Gesamtkapazität my $lowSoc = 0; + my $lcslot; if ($cgbt) { - ($lowSoc) = __parseAttrBatSoc ($name, $cgbt); + ($lowSoc, undef, undef, undef, $lcslot) = __parseAttrBatSoc ($name, $cgbt); } + ## Zeitfenster für aktives Lademanagement ermitteln + ##################################################### + $lcslot //= '00:00-23:59'; + my ($lcstart, $lcend) = split "-", $lcslot; + + debugLog ($paref, 'batteryManagement', "Bat $bn Charge Rcmd - control time Slot - Slot start: $lcstart, Slot end: $lcend"); + my $batoptsocwh = $batinstcap * $batoptsoc / 100; # optimaler SoC in Wh my $lowSocwh = $batinstcap * $lowSoc / 100; # lowSoC in Wh debugLog ($paref, 'batteryManagement', "Bat $bn Charge Rcmd - Installed Battery capacity: $batinstcap Wh, Percentage of total capacity: ".(sprintf "%.1f", $sf*100)." %"); debugLog ($paref, 'batteryManagement', "Bat $bn Charge Rcmd - The PV generation, consumption and surplus listed below are based on the battery's share of the total capacity!"); - my $socwh = sprintf "%.0f", ($batinstcap * $csoc / 100); # aktueller SoC in Wh - my $whneed = $batinstcap - $socwh; - + my $socwh = sprintf "%.0f", ($batinstcap * $csoc / 100); # aktueller SoC in Wh + my $whneed = $batinstcap - $socwh; + ## Auswertung für jede kommende Stunde ######################################## for my $num (0..47) { @@ -11380,10 +11519,22 @@ sub _batChargeRecmd { my $hod = NexthoursVal ($name, 'NextHour'.$nhr, 'hourofday', ''); my $confc = NexthoursVal ($name, 'NextHour'.$nhr, 'confc', 0); my $pvfc = NexthoursVal ($name, 'NextHour'.$nhr, 'pvfc', 0); - my $stt = NexthoursVal ($name, 'NextHour'.$nhr, 'starttime', ''); - $stt = (split /[-:]/, $stt)[2] if($stt); - - my $crel = 0; # Ladefreigabe 0 per Default + my $nhstt = NexthoursVal ($name, 'NextHour'.$nhr, 'starttime', ''); + my $stt = (split /[-:]/, $nhstt)[2] if($nhstt); + + ## Zeitfenster für aktives Lademanagement anwenden + ##################################################### + my $lcintime = 1; + + if ($nhstt) { + my ($date) = (split " ", $nhstt)[0]; + my $sttts = timestringToTimestamp ($nhstt); + my $lcstartts = timestringToTimestamp ("$date ${lcstart}:00"); + my $lcendts = timestringToTimestamp ("$date ${lcend}:59"); + $lcintime = $sttts >= $lcstartts && $sttts <= $lcendts ? 1 : 0; # 1 wenn innerhalb Time Slot -> Lademanagement freigegeben, sonst Batterie Ladung immer freigeben + } + + my $crel = 0; # Ladefreigabe 0 Ausgangswert my $spday = 0; ## Aufteilung Energie auf Batterie XX im Verhältnis aller Bat @@ -11422,7 +11573,8 @@ sub _batChargeRecmd { if ( !$num && ($pvCu - $curcon) >= $inplim ) {$crel = 1} # Ladefreigabe wenn akt. PV Leistung - Abschläge >= WR-Leistungsbegrenzung if ( !$bpin && $gfeedin > $feedinlim ) {$crel = 1} # V 1.49.6 Ladefreigabe wenn akt. keine Bat-Ladung UND akt. Einspeisung > Einspeiselimit der Anlage if ( $bpin && ($gfeedin - $bpin) > $feedinlim ) {$crel = 1} # V 1.49.6 Ladefreigabe wenn akt. Bat-Ladung UND Eispeisung - Bat-Ladung > Einspeiselimit der Anlage - if ( !$cgbt ) {$crel = 1} # immer Ladefreigabe wenn kein BatSoc-Management + if ( !$cgbt ) {$crel = 1} # Ladefreigabe wenn kein BatSoc-Management + if ( !$lcintime ) {$crel = 1} # Ladefreigabe wenn nicht innerhalb Zeitslot für Ladesteuerung ## SOC-Prognose ################# # change V 1.47.0 @@ -11452,13 +11604,13 @@ sub _batChargeRecmd { } ); # Readings NextHourXX_Bat_XX_ChargeForecast erstellen - my $msg = "CurrSoc: $csoc %, SoCfc: $socwh Wh, whneed: $whneed, pvfc: $pvfc, rodpvfc: $rodpvfc, confcss: $confcss, SurpDay: $spday Wh, CurrPV: $pvCu W, CurrCons: $curcon W, Limit: $inplim W"; + my $msg = "CurrSoc: $csoc %, SoCfc: $socwh Wh, whneed: $whneed, pvfc: $pvfc, rodpvfc: $rodpvfc, confcss: $confcss, SurpDay: $spday Wh, CurrPV: $pvCu W, CurrCons: $curcon W, Limit: $inplim W, inTime: $lcintime"; if ($num) { - $msg = "SoCfc: $progsoc % / $socwh Wh, whneed: $whneed, pvfc: $pvfc, rodpvfc: $rodpvfc, confcss: $confcss, SurpDay: $spday Wh"; + $msg = "SoCfc: $progsoc % / $socwh Wh, whneed: $whneed, pvfc: $pvfc, rodpvfc: $rodpvfc, confcss: $confcss, SurpDay: $spday Wh, inTime: $lcintime"; if (!$today) { - $msg = "SoCfc: $progsoc % / $socwh Wh, whneed: $whneed, pvfc: $pvfc, tompvfc: $tompvfc, tomconfc: $tomconfc, SurpDay: $spday Wh"; + $msg = "SoCfc: $progsoc % / $socwh Wh, whneed: $whneed, pvfc: $pvfc, tompvfc: $tompvfc, tomconfc: $tomconfc, SurpDay: $spday Wh, inTime: $lcintime"; } } else { @@ -11469,12 +11621,14 @@ sub _batChargeRecmd { $data{$name}{nexthours}{'NextHour'.$nhr}{'rcdchargebat'.$bn} = $crel; $data{$name}{nexthours}{'NextHour'.$nhr}{'soc'.$bn} = $progsoc; + $data{$name}{nexthours}{'NextHour'.$nhr}{'lcintimebat'.$bn} = $lcintime; # Ladesteuerung ist "In Time" oder nicht $hsoc{$nhr}{socprogwhsum} += $socwh; # Hilfshash Aufsummierung SoC-Prognose (Wh) über alle Batterien - # prognostizierten SOC in pvHistory speichern - ############################################### + # prognostizierten Daten in pvHistory speichern + ################################################# if ($today && $hod) { # heutiger Tag - writeToHistory ( { paref => $paref, key => 'batprogsoc'.$bn, val => $progsoc, hour => $hod } ); + writeToHistory ( { paref => $paref, key => 'batprogsoc'.$bn, val => $progsoc, hour => $hod } ); + writeToHistory ( { paref => $paref, key => 'lcintimebat'.$bn, val => $lcintime, hour => $hod } ); } debugLog ($paref, 'batteryManagement', "Bat $bn relLoad $stt -> $crel ($msg)"); @@ -16442,7 +16596,8 @@ sub _beamFillupBatValues { my (undef,undef,$day_str,$time_str) = $stt =~ m/(\d{4})-(\d{2})-(\d{2})\s(\d{2})/xs; $hh->{$day_str}{$time_str}{'rcdchargebat'.$bn} = $rcdc; - $hh->{$day_str}{$time_str}{'soc'.$bn} = NexthoursVal ($name, $idx, 'soc'.$bn, undef); + $hh->{$day_str}{$time_str}{'lcintimebat'.$bn} = NexthoursVal ($name, $idx, 'lcintimebat'.$bn, undef); + $hh->{$day_str}{$time_str}{'soc'.$bn} = NexthoursVal ($name, $idx, 'soc'.$bn, undef); } } @@ -16463,16 +16618,19 @@ sub _beamFillupBatValues { ## Einfügen prepared NextHour Werte ##################################### $hfcg->{$kdx}{'rcdchargebat'.$bn} = $hh->{$ds}{$ts}{'rcdchargebat'.$bn} if(defined $hh->{$ds}{$ts}{'rcdchargebat'.$bn}); + $hfcg->{$kdx}{'lcintimebat'.$bn} = $hh->{$ds}{$ts}{'lcintimebat'.$bn} if(defined $hh->{$ds}{$ts}{'lcintimebat'.$bn}); $hfcg->{$kdx}{'soc'.$bn} = $hh->{$ds}{$ts}{'soc'.$bn} if(defined $hh->{$ds}{$ts}{'soc'.$bn}); ## Auffüllen mit History Werten (Achtung: Stundenverschieber relativ zu Nexthours) #################################################################################### if (!defined $hh->{$ds}{$ts}{'rcdchargebat'.$bn}) { - my $histsoc = HistoryVal ($hash, $ds, (sprintf "%02d", $ts+1), 'batsoc'.$bn, undef); + my $histsoc = HistoryVal ($hash, $ds, (sprintf "%02d", $ts+1), 'batsoc'.$bn, undef); + my $lcintime = HistoryVal ($hash, $ds, (sprintf "%02d", $ts+1), 'lcintimebat'.$bn, undef); if (defined $histsoc) { - $hfcg->{$kdx}{'soc'.$bn} = $histsoc; $hfcg->{$kdx}{'rcdchargebat'.$bn} = 'hist'; + $hfcg->{$kdx}{'lcintimebat'.$bn} = $lcintime; + $hfcg->{$kdx}{'soc'.$bn} = $histsoc; } } } @@ -16993,8 +17151,8 @@ sub __batteryOnBeam { my $bpos = BatteryVal ($name, $bn, 'bposingraph', 'totp'); next if($bshow != $paref->{chartlvl} || $bpos ne $paref->{beampos}); # Anzeige nur auf Grafikebene "chartlvl" bzw. oberhalb/unterhalb der Balken - $ret .= ""; # freier Platz am Anfang - my $ii = 0; + $ret .= ""; # freier Platz am Anfang + my $ii = 0; for my $i (0..($maxhours * 2) - 1) { my $skip = __dontNightshowSkipSync ($name, $paref, $i); @@ -17014,10 +17172,11 @@ sub __batteryOnBeam { my $day_str = $hfcg->{$i}{day_str}; my $time_str = $hfcg->{$i}{time_str}; - $time_str = (split ":", $time_str)[0]; # Forum: https://forum.fhem.de/index.php?msg=1332721 + $time_str = (split ":", $time_str)[0]; # Forum: https://forum.fhem.de/index.php?msg=1332721 my $soc = $hfcg->{$i}{'soc'.$bn}; - - my ($bpower, $currsoc); + my $lcintime = $hfcg->{$i}{'lcintimebat'.$bn}; # Lademanagement für Batterie XX ist aktiviert + + my ($bpower, $currsoc); if ($day_str eq $day && $time_str eq $chour) { # akt. Leistung nur für aktuelle Stunde $bpower = $bpowerin ? $bpowerin : @@ -17032,6 +17191,7 @@ sub __batteryOnBeam { ptyp => 'battery', flag => $hfcg->{$i}{'rcdchargebat'.$bn}, msg1 => $balias, + msg2 => $lcintime, soc => $soc, pcurr => $bpower, lang => $lang @@ -17045,7 +17205,7 @@ sub __batteryOnBeam { debugLog ($paref, 'graphic', "Battery $bn pos >$i< day: $day_str, time: $time_str, Power ('-' = out): ".(defined $bpower ? $bpower : 'undef'). " W, Rcmd: ".(defined $hfcg->{$i}{'rcdchargebat'.$bn} ? $hfcg->{$i}{'rcdchargebat'.$bn} : 'undef'). - ", SoC: ".(defined $hfcg->{$i}{'soc'.$bn} ? $hfcg->{$i}{'soc'.$bn} : 'undef')." %"); + ", SoC: ".(defined $soc ? $soc : 'undef')." %, lcintime: ".(defined $lcintime ? $lcintime : 'undef')); } $ret .= "" if($ret); # freier Platz am Ende der Icon Zeile @@ -17601,20 +17761,13 @@ END3 my $consumer_style; for my $c (@consumers) { - my $power = ConsumerVal ($name, $c, 'power', 0); - my $rpcurr = ConsumerVal ($name, $c, 'rpcurr', ''); # Reading für akt. Verbrauch angegeben ? - $cnsmrpower = $cnsmr->{$c}{p}; - - if (!$rpcurr && isConsumerPhysOn($hash, $c)) { # Workaround wenn Verbraucher ohne Leistungsmessung - $cnsmrpower = $power; - } - - my $p = $cnsmrpower; - $p = (($cnsmrpower / $power) * 100) if ($power > 0); - $consumer_style = $p > DEFPOPERCENT ? "$stna active_normal" : "$stna inactive"; + $cnsmrpower = $cnsmr->{$c}{p}; + my $cilon = isConsumerLogOn ($hash, $c, $cnsmrpower); + + $consumer_style = $cilon ? "$stna active_normal" : "$stna inactive"; my $chain_color = ""; # Farbe der Laufkette des Consumers - if ($p > 0.5 && CurrentVal ($name, 'strokeconsumerdyncol', 0)) { + if ($cilon && CurrentVal ($name, 'strokeconsumerdyncol', 0)) { $chain_color = 'style="stroke: #'.__dynColor ($cnsmrpower, $strokeredlim).';"'; } @@ -17699,7 +17852,7 @@ END3 elsif ($lpv1 == 4) {$xtext -= 15} elsif ($lpv1 == 3) {$xtext -= 5} elsif ($lpv1 == 2) {$xtext += 10} - elsif ($lpv1 == 1) {$xtext += 30} + elsif ($lpv1 == 1) {$xtext += 25} $ret .= qq{$pdrpow} if($flowgPrdsPower); } @@ -17978,7 +18131,8 @@ return $ret; # ptyp - Typ der Entität # $pn - Positionsnummer (01...max) # flag - ein beliebiges Statusflag zur Auswertung -# msg1 - Text zur freien Verwendung +# msg1 - zur freien Verwendung +# msg2 - zur freien Verwendung # soc - der SOC bei Batterien # $don - Day or Night # $pcurr - aktuelle Leistung / Verbrauch @@ -17990,6 +18144,7 @@ sub __substituteIcon { my $ptyp = $paref->{ptyp}; my $pn = $paref->{pn}; my $msg1 = $paref->{msg1}; + my $msg2 = $paref->{msg2}; my $flag = $paref->{flag}; my $soc = $paref->{soc}; my $don = $paref->{don}; @@ -18070,6 +18225,8 @@ sub __substituteIcon { $pretxt = $htitles{onlybatw}{$lang}." $pn: $msg1".($cgbt ? "\n".$htitles{bncharel}{$lang} : ''); } } + + $pretxt .= "\n".$htitles{lcactive}{$lang}.": ".(defined $msg2 ? ($msg2 == 1 ? $htitles{simplyes}{$lang} : $htitles{simpleno}{$lang}) : '-'); if (defined $pcurr) { # aktueller Zustand if ($pcurr > 0) { # Batterie wird aufgeladen @@ -19793,7 +19950,7 @@ sub _listDataPoolPvHist { $prdl .= "pprl${pn}: $pprl"; } - my ($btotin, $batin, $btotout, $batout, $batmsoc, $batssoc, $batprogsoc, $batsoc); + my ($btotin, $batin, $btotout, $batout, $batmsoc, $batssoc, $batprogsoc, $batsoc, $lcintime); for my $bn (1..MAXBATTERIES) { # + alle Batterien $bn = sprintf "%02d", $bn; my $hbtotin = HistoryVal ($name, $day, $key, 'batintotal'.$bn, '-'); @@ -19804,6 +19961,7 @@ sub _listDataPoolPvHist { my $hbatssoc = HistoryVal ($name, $day, $key, 'batsetsoc'.$bn, '-'); my $hbatprogsoc = HistoryVal ($name, $day, $key, 'batprogsoc'.$bn, '-'); my $hbatsoc = HistoryVal ($name, $day, $key, 'batsoc'.$bn, '-'); + my $intime = HistoryVal ($name, $day, $key, 'lcintimebat'.$bn, '-'); if ($export eq 'csv') { $hexp->{$day}{$key}{"BatteryInTotal${bn}"} = $hbtotin; @@ -19814,6 +19972,7 @@ sub _listDataPoolPvHist { $hexp->{$day}{$key}{"BatterySetSoc${bn}"} = $hbatssoc; $hexp->{$day}{$key}{"BatteryProgSoc${bn}"} = $hbatprogsoc; $hexp->{$day}{$key}{"BatterySoc${bn}"} = $hbatsoc; + $hexp->{$day}{$key}{"BatteryLCinTime${bn}"} = $intime; } $btotin .= ', ' if($btotin); @@ -19832,6 +19991,8 @@ sub _listDataPoolPvHist { $batprogsoc .= "batprogsoc${bn}: $hbatprogsoc"; $batsoc .= ', ' if($batsoc); $batsoc .= "batsoc${bn}: $hbatsoc"; + $lcintime .= ', ' if($lcintime); + $lcintime .= "lcintimebat${bn}: $intime"; } $ret .= "\n " if($ret); @@ -19862,6 +20023,8 @@ sub _listDataPoolPvHist { $ret .= "\n " if($key ne '99'); $ret .= $batsoc.", socwhsum: $socwhsum" if($key ne '99'); $ret .= "\n " if($key ne '99'); + $ret .= $lcintime if($key ne '99'); + $ret .= "\n " if($key ne '99'); $ret .= $batin; $ret .= "\n "; @@ -20261,13 +20424,16 @@ sub _listDataPoolNextHours { my $socprgs = NexthoursVal ($name, $idx, 'socprogwhsum', '-'); my $dinrang = NexthoursVal ($name, $idx, 'DaysInRange', '-'); - my ($rcdbat, $socs); + my ($rcdbat, $socs, $lcintime); for my $bn (1..MAXBATTERIES) { # alle Batterien $bn = sprintf "%02d", $bn; my $rcdcharge = NexthoursVal ($name, $idx, 'rcdchargebat'.$bn, '-'); - my $socxx = NexthoursVal ($name, $idx, 'soc'.$bn, '-'); + my $intime = NexthoursVal ($name, $idx, 'lcintimebat'.$bn, '-'); + my $socxx = NexthoursVal ($name, $idx, 'soc'.$bn, '-'); $rcdbat .= ', ' if($rcdbat); $rcdbat .= "rcdchargebat${bn}: $rcdcharge"; + $lcintime .= ', ' if($lcintime); + $lcintime .= "lcintimebat${bn}: $intime"; $socs .= ', ' if($socs); $socs .= "soc${bn}: $socxx"; } @@ -20287,6 +20453,8 @@ sub _listDataPoolNextHours { $sq .= $socs.", socprogwhsum: $socprgs"; $sq .= "\n "; $sq .= $rcdbat; + $sq .= "\n "; + $sq .= $lcintime; } return $sq; @@ -24838,33 +25006,34 @@ to ensure that the system configuration is correct. @@ -24909,7 +25078,8 @@ to ensure that the system configuration is correct. gfeedin real feed-in (Wh) into the electricity grid feedprice Remuneration for the feed-in of one kWh. The currency of the price is defined in the setupMeterDev. hourscsmeXX total active hours of the day from ConsumerXX - minutescsmXX total active minutes in the hour of ConsumerXX + lcintimebatXX the charge management for battery XX was activated (1 - Yes, 0 - No) + minutescsmXX total active minutes in the hour of ConsumerXX pprlXX Energy generation of producer XX (see attribute setupOtherProducerXX) in the hour (Wh) pvfc the predicted PV yield (Wh) pvrlXX real PV generation (Wh) of inverter XX @@ -25464,6 +25634,10 @@ to ensure that the system configuration is correct. 0 - the stored energy consumption shares are retained as part of the general consumption forecast (default) 1 - the general consumption forecast is reduced by the stored energy consumption shares. 2 - as with '1', but the consumer's planning data is included in the forecast for the coming hours. + Note: When using exconfc, plantControl->consForecastIdentWeekdays=1 and plantControl->consForecastLastDays=4 + should be set. + See the explanations in the German Wiki +
@@ -25482,53 +25656,48 @@ to ensure that the system configuration is correct.
-
  • ctrlBatSocManagementXX lowSoc=<Value> upSoC=<Value> [maxSoC=<Value>] [careCycle=<Value>]

    - If a battery device (setupBatteryDevXX) is installed, this attribute activates the battery SoC management for this +
  • ctrlBatSocManagementXX lowSoc=<Value> upSoC=<Value> [maxSoC=<Value>] [careCycle=<Value>] [lcSlot=<hh:mm>-<hh:mm>]

    + If a battery device (setupBatteryDevXX) is installed, this attribute activates the battery SoC and charge management for this battery device.
    The Battery_OptimumTargetSoC_XX reading contains the optimum minimum SoC calculated by the module.
    The Battery_ChargeRequest_XX reading is set to '1' if the current SoC has fallen below the minimum SoC.
    In this case, the battery should be forcibly charged, possibly with mains power.
    - The readings can be used to control the SoC (State of Charge) and to control the charging current used for the + The reading Battery_ChargeRecommended_XX indicates whether the battery should be charged at full power (1) without restriction or not + or only with limited power if a feed-in limit is exceeded (0).
    + The readings can be used to control the SoC (State of Charge) and to control the charging power used for the battery.
    - The module itself does not control the battery.

    + Detailed information on battery SoC and charging management is described in the + german Wiki.


    - All values are whole numbers in %. The following applies: 'lowSoc' < 'upSoC' < 'maxSoC'.
    - The optimum SoC is determined according to the following scheme:

    + All SoC values are whole numbers in %. The following applies: 'lowSoc' < 'upSoC' < 'maxSoC'.

    - - - - - - - - - - - -
    1. Starting from 'lowSoc', the minimum SoC is increased by 5% on the following day but not higher than
    'upSoC', if 'maxSoC' has not been reached on the current day.
    2. If 'maxSoC' is reached (again), the minimum SoC is reduced by 5%, but not lower than 'lowSoc'.
    3. Minimum SoC is reduced to the extent that the predicted PV energy for the current or following
    day can be absorbed by the battery. Minimum SoC is typically reduced to 'upSoc' and not lower than 'lowSoc'.
    4. The module records the last point in time at the 'maxSoC' level in order to ensure a charge to 'maxSoC'
    at least every 'careCycle' days. For this purpose, the optimized SoC is changed depending on the remaining days
    until the next 'careCycle' point in such a way that 'maxSoC' is mathematically achieved by a daily 5% SoC increase
    at the 'careCycle' time point. If 'maxSoC' is reached in the meantime, the 'careCycle' period starts again.
    -
    - - + attr <name> ctrlBatSocManagement01 lowSoc=10 upSoC=50 maxSoC=99 careCycle=25 lcSlot=11:00-17:30

  • @@ -26201,7 +26370,7 @@ to ensure that the system configuration is correct. this error and reports the situation that has occurred with a log entry with verbose 2. cap installed battery capacity. Option can be: - numerical value - direct specification of the battery capacity in Wh without specifying the unit! + Integer - direct specification of the battery capacity in Wh without specifying the unit! <Readingname>:<unit> - Reading which provides the capacity and unit (Wh, kWh) charge Reading which provides the current state of charge (SOC in percent) (optional) @@ -26348,7 +26517,7 @@ to ensure that the system configuration is correct.
    @@ -27463,7 +27632,8 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. sunalt Höhe der Sonne (in Dezimalgrad) temp vorhergesagte Außentemperatur today hat Wert '1' wenn Startdatum am aktuellen Tag - rcdchargebatXX Aufladeempfehlung für Batterie XX (1 - Ja, 0 - Nein) + rcdchargebatXX Aufladeempfehlung mit voller Leistung für Batterie XX (1 - Ja, 0 - Nein) + lcintimebatXX Lademanagement für Batterie XX ist aktiviert bzw. wird aktiviert sein (1 - Ja, 0 - Nein) rr1c Gesamtniederschlag in der letzten Stunde kg/m2 rrange Bereich des Gesamtniederschlags socXX aktueller (NextHour00) oder prognostizierter SoC (%) der Batterie XX @@ -27515,7 +27685,8 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. feedprice Vergütung für die Einpeisung einer kWh. Die Währung des Preises ist im setupMeterDev definiert. avgcycmntscsmXX durchschnittliche Dauer eines Einschaltzyklus des Tages von ConsumerXX in Minuten hourscsmeXX Summe Aktivstunden des Tages von ConsumerXX - minutescsmXX Summe Aktivminuten in der Stunde von ConsumerXX + lcintimebatXX das Lademanagement für Batterie XX war aktiviert (1 - Ja, 0 - Nein) + minutescsmXX Summe Aktivminuten in der Stunde von ConsumerXX pprlXX Energieerzeugung des Produzenten XX (siehe Attribut setupOtherProducerXX) in der Stunde (Wh) pvfc der prognostizierte PV Ertrag (Wh) pvrlXX reale PV Erzeugung (Wh) von Inverter XX @@ -28070,6 +28241,10 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. 0 - die gespeicherten Energieverbrauchsanteile bleiben als Bestandteil der allgemeinen Verbrauchsprognose erhalten (default) 1 - die allgemeine Verbrauchsprognose wird um die gespeicherten Energieverbrauchsanteile reduziert. 2 - wie bei '1', jedoch gehen die Planungsdaten des Verbrauchers bei der Prognose der kommenden Stunden wieder mit ein. + Hinweis: Bei Verwendung von exconfc sollte plantControl->consForecastIdentWeekdays=1 und plantControl->consForecastLastDays=4 + gesetzt werden. + Siehe dazu die Erläuterungen im Wiki +
    @@ -28088,54 +28263,49 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
    -
  • ctrlBatSocManagementXX lowSoc=<Wert> upSoC=<Wert> [maxSoC=<Wert>] [careCycle=<Wert>]

    +
  • ctrlBatSocManagementXX lowSoc=<Wert> upSoC=<Wert> [maxSoC=<Wert>] [careCycle=<Wert>] [lcSlot=<hh:mm>-<hh:mm>]

    Sofern ein Batterie Device (setupBatteryDevXX) installiert ist, aktiviert dieses Attribut das Batterie - SoC-Management für dieses Batteriegerät.
    + SoC- und Lade-Management für dieses Batteriegerät.
    Das Reading Battery_OptimumTargetSoC_XX enthält den vom Modul berechneten optimalen Mindest-SoC.
    Das Reading Battery_ChargeRequest_XX wird auf '1' gesetzt, wenn der aktuelle SoC unter den Mindest-SoC gefallen ist.
    In diesem Fall sollte die Batterie, unter Umständen mit Netzstrom, zwangsgeladen werden.
    - Die Readings können zur Steuerung des SoC (State of Charge) sowie zur Steuerung des verwendeten Ladestroms + Das Reading Battery_ChargeRecommended_XX gibt an, ob die Batterie uneingeschränkt mit voller Leistung (1), oder nicht + bzw. nur mit eingeschränkter Leistung bei Überschreitung eines Einspeiselimits geladen werden sollte (0).
    + Die Readings können zur Steuerung des SoC (State of Charge) sowie zur Steuerung des verwendeten Ladeleistung der Batterie verwendet werden.
    - Durch das Modul selbst findet keine Steuerung der Batterie statt.

    + Detaillierte Informationen zum Batterie SoC- und Lade-Management sind im + Wiki beschrieben.


    - Alle Werte sind ganze Zahlen in %. Dabei gilt: 'lowSoc' < 'upSoC' < 'maxSoC'.
    - Die Ermittlung des optimalen SoC erfolgt nach folgendem Schema:

    + Alle SoC-Werte sind ganze Zahlen in %. Dabei gilt: 'lowSoc' < 'upSoC' < 'maxSoC'.

    - - - - - - - - - - - -
    1. Ausgehend von 'lowSoc' wird der Mindest-SoC kurz vor Sonnenuntergang um 5% inkrementiert sofern am laufenden
    Tag 'maxSoC' nicht erreicht wurde und die PV-Prognose keinen hinreichenden Ertrag des kommenden Tages vorhersagt.
    2. Wird 'maxSoC' (wieder) erreicht, wird Mindest-SoC um 5%, aber nicht tiefer als 'lowSoc', verringert.
    3. Mindest-SoC wird soweit verringert, dass die prognostizierte PV Energie des aktuellen bzw. des folgenden Tages
    von der Batterie aufgenommen werden kann. Mindest-SoC wird typisch auf 'upSoc' und nicht tiefer als 'lowSoc' verringert.
    4. Das Modul erfasst den letzten Zeitpunkt am 'maxSoC'-Level, um eine Ladung auf 'maxSoC' mindestens alle 'careCycle'
    Tage zu realisieren. Zu diesem Zweck wird der optimierte SoC in Abhängigkeit der Resttage bis zum nächsten
    'careCycle' Zeitpunkt derart verändert, dass durch eine tägliche 5% SoC-Steigerung 'maxSoC' am 'careCycle' Zeitpunkt
    rechnerisch erreicht wird. Wird zwischenzeitlich 'maxSoC' erreicht, beginnt der 'careCycle' Zeitraum erneut.
    -
    - - + attr <name> ctrlBatSocManagement01 lowSoc=10 upSoC=50 maxSoC=99 careCycle=25 lcSlot=11:00-17:30

  • @@ -28806,7 +28976,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. SolarForecast diesen Fehler und meldet die aufgetretene Situation durch einen Logeintrag mit verbose 2. cap installierte Batteriekapazität. Option kann sein: - numerischer Wert - direkte Angabe der Batteriekapazität in Wh ohne die Einheit anzugeben! + Ganzzahl - direkte Angabe der Batteriekapazität in Wh ohne die Einheit anzugeben! <Readingname>:<Einheit> - Reading welches die Kapazität liefert und Einheit (Wh, kWh) charge Reading welches den aktuellen Ladezustand (SOC in Prozent) liefert (optional) @@ -28952,7 +29122,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.