diff --git a/fhem/contrib/DS_Starter/76_SolarForecast.pm b/fhem/contrib/DS_Starter/76_SolarForecast.pm index ef40ecd7a..18dfed858 100644 --- a/fhem/contrib/DS_Starter/76_SolarForecast.pm +++ b/fhem/contrib/DS_Starter/76_SolarForecast.pm @@ -163,14 +163,14 @@ my %vNotesIntern = ( "1.59.6" => "24.10.2025 ___ownSpecGetFWwidget: handling of line breaks in attributes & can hamdle a key=value pair separateley ". "Width of a text field in graphicHeaderOwnspec fixed to 10, edit commandref ". "__batChargeOptTargetPower: use an average for the charging power if smartPower set and charging target are not achievable ". - "__createOwnSpec: an empty field can be created within a line by simply using a colon (:). ". - "add new key pvshare to CustomerXX attributes -> __setConsRcmdState add PV share calculation ". - "___doPlanning: code improvements and implement PV share needed ", + "__createOwnSpec: an empty field can be created within a line by simply using a colon (:). ". + "add new key pvshare to CustomerXX attributes -> __setConsRcmdState add PV share calculation ". + "___doPlanning: code improvements and implement PV share needed ", "1.59.5" => "15.10.2025 new sub ___batAdjustPowerByMargin: implement optPower Safety margin decreasing proportionally to the linear surplus ". "new Reading Battery_TargetAchievable_XX, _batSocTarget: minor code change ", "1.59.4" => "14.10.2025 new subs, ctrlBatSocManagementXX: new key loadTarget, replace __batCapShareFactor by __batDeficitShareFactor ". "__batChargeOptTargetPower: use pinmax if achievable==0, new ctrlBatSocManagementXX->stepSoC key ". - "loadStrategy: possible value smartPower ", + "loadStrategy: possible value smartPower ", "1.59.3" => "10.10.2025 ___batChargeSaveResults: fix writing 'rcdchargebatXX' ", "1.59.2" => "09.10.2025 one more fix of color filling of svg icon ", "1.59.1" => "08.10.2025 fixed transfer at day change, optimal SoC consideration in SoC forecast for optPower strategy ". @@ -11420,18 +11420,18 @@ sub _batSocTarget { Log3 ($name, 1, "$name - WARNING - Attribute ctrlBatSocManagement${bn} is active, but required keys 'lowSoc' and 'upSoC' are not set. Go to Next..."); next; } - - if (!$stepSoc) { - debugLog ($paref, 'batteryManagement', "SoC Step1 Bat $bn - The SoC-Management is switched off. Battery_OptimumTargetSoC_$bn is set to lowSoC and Battery_ChargeRequest_$bn to '0'."); - - ## pvHistory/Readings schreiben - ################################# - writeToHistory ( { paref => $paref, key => 'batsetsoc'.$bn, val => $lowSoc, hour => 99 } ); - storeReading ('Battery_OptimumTargetSoC_'.$bn, $lowSoc.' %'); - storeReading ('Battery_ChargeRequest_'.$bn, 0); + + if (!$stepSoc) { + debugLog ($paref, 'batteryManagement', "SoC Step1 Bat $bn - The SoC-Management is switched off. Battery_OptimumTargetSoC_$bn is set to lowSoC and Battery_ChargeRequest_$bn to '0'."); + + ## pvHistory/Readings schreiben + ################################# + writeToHistory ( { paref => $paref, key => 'batsetsoc'.$bn, val => $lowSoc, hour => 99 } ); + storeReading ('Battery_OptimumTargetSoC_'.$bn, $lowSoc.' %'); + storeReading ('Battery_ChargeRequest_'.$bn, 0); - next; - } + next; + } my $oldd2care = CircularVal ($name, 99, 'days2care'.$bn, 0); my $ltsmsr = CircularVal ($name, 99, 'lastTsMaxSocRchd'.$bn, undef); @@ -11465,7 +11465,7 @@ sub _batSocTarget { #my $sf = __batCapShareFactor ($name, $bn); # Anteilsfaktor der Batterie XX Kapazität an Gesamtkapazität my $sf = __batDeficitShareFactor ($name, $bn); # V 1.59.5 Anteilsfaktor der Batterie XX Ladebedarf an Gesamtladebedarf - my $pvexpect = $sf * $pvexpraw; + my $pvexpect = $sf * $pvexpraw; if ($debug =~ /batteryManagement/xs) { Log3 ($name, 1, "$name DEBUG> SoC Step1 Bat $bn - basics -> Battery share factor of total required load: $sf"); @@ -11478,10 +11478,10 @@ sub _batSocTarget { ##################################################################### my $sunset = CurrentVal ($name, 'sunsetTodayTs', $t); my $sunrise = CurrentVal ($name, 'sunriseTodayTs', $t); - #my $delayts = $sunset - 5400; # Pflege-SoC/Erhöhung SoC erst ab 1,5h vor Sonnenuntergang berechnen/anwenden + #my $delayts = $sunset - 5400; # Pflege-SoC/Erhöhung SoC erst ab 1,5h vor Sonnenuntergang berechnen/anwenden my $delayts = $sunrise + (($sunset - $sunrise) / 2); # V 1.59.5 neues SoC-Ziel ab ca. Mittag berechnen/anwenden - my $nt = (timestampToTimestring ($delayts, $paref->{lang}))[0]; - my $la = ''; + my $nt = (timestampToTimestring ($delayts, $paref->{lang}))[0]; + my $la = ''; my $careSoc = $target; my $ntsmsc = CircularVal ($name, 99, 'nextTsMaxSocChge'.$bn, $t); @@ -11833,14 +11833,14 @@ sub _batChargeMgmt { # Debuglog ############ if ($paref->{debug} =~ /batteryManagement/) { - Log3 ($name, 1, "$name DEBUG> ChargeMgmt Bat $bn - selected charging strategy: $strategy"); + Log3 ($name, 1, "$name DEBUG> ChargeMgmt Bat $bn - selected charging strategy: $strategy"); Log3 ($name, 1, "$name DEBUG> ChargeMgmt Bat $bn - General load termination condition: $labortCond"); Log3 ($name, 1, "$name DEBUG> ChargeMgmt Bat $bn - control time Slot - Slot start: $lcstart, Slot end: $lcend"); Log3 ($name, 1, "$name DEBUG> ChargeMgmt Bat $bn - Battery efficiency used: ".($befficiency * 100)." %"); Log3 ($name, 1, "$name DEBUG> ChargeMgmt Bat $bn - charging target: $goalpercent % / $goalwh Wh"); #Log3 ($name, 1, "$name DEBUG> ChargeMgmt Bat $bn - Installed Battery capacity: $batinstcap Wh, Percentage of total capacity: ".(sprintf "%.1f", $sf*100)." %"); Log3 ($name, 1, "$name DEBUG> ChargeMgmt Bat $bn - Percentage of the total amount of charging energy required: ".(sprintf "%.1f", $sf*100)." %"); - Log3 ($name, 1, "$name DEBUG> ChargeMgmt Bat $bn - The PV generation, consumption and surplus listed below are based on the battery's share of the total amount of charging energy required!"); + Log3 ($name, 1, "$name DEBUG> ChargeMgmt Bat $bn - The PV generation, consumption and surplus listed below are based on the battery's share of the total amount of charging energy required!"); Log3 ($name, 1, "$name DEBUG> ChargeMgmt Bat $bn - used safety margin: $lrMargin %"); Log3 ($name, 1, "$name DEBUG> ChargeMgmt Bat $bn - weighted self-consumption: $wou %"); } @@ -11928,7 +11928,7 @@ sub _batChargeMgmt { $hsurp->{$fd}{$hod}{nhr} = $nhr; $hsurp->{$fd}{$hod}{speff} = $surpls; # effektiver PV Überschuß bzw. effektiver Verbrauch wenn < 0 $hsurp->{$fd}{$hod}{surplswh} = $surplswh.'.'.$hod; # absoluter Überschuß in Wh der Stunde mit Sortierhilfe - $hsurp->{$fd}{$hod}{$bn}{spday} = $spday; # (Rest)PV-Überschuß am laufenden Tag + $hsurp->{$fd}{$hod}{$bn}{spday} = $spday; # (Rest)PV-Überschuß am laufenden Tag $hsurp->{$fd}{$hod}{$bn}{initsocwh} = $socwh; # durch LR fortgeschriebener SoC $hsurp->{$fd}{$hod}{$bn}{batinstcap} = $batinstcap; # installierte Batteriekapazität (Wh) $hsurp->{$fd}{$hod}{$bn}{goalwh} = $goalwh; # Ladeziel @@ -12031,7 +12031,7 @@ sub _batChargeMgmt { my @batteries = grep { !/^(?:fd|speff|surplswh|nhr)$/xs } keys %{$hopt->{24}}; for my $bat (sort @batteries) { - next if(!defined $hopt->{$shod}{$bat}{batinstcap}); + next if(!defined $hopt->{$shod}{$bat}{batinstcap}); my $ssocwh = $hopt->{$shod}{$bat}{runwh} // '-'; ## SOC-Prognose OTP @@ -12140,20 +12140,20 @@ sub __batChargeOptTargetPower { my $spls00 = 0; for my $k (keys %$hsurp) { - my $nh = $hsurp->{$k}{nhr}; + my $nh = $hsurp->{$k}{nhr}; - if ($nh eq '00') { - my $val = $hsurp->{$k}{surplswh}; - - if (defined $val && $val =~ /^(\d+)\.(\w+)$/) { - $spls00 = $1; - my $replacement = sprintf "%.0f", ($spls00 / 60 * (60 - int $minute)); # aktuelle (Rest)-Stunde -> zeitgewichteter PV-Überschuß - $replacement .= '.'.$2; - $hsurp->{$k}{surplswh} = $replacement; - } - - last; # da Stunde 00 nur einmal vorkommt, können wir abbrechen - } + if ($nh eq '00') { + my $val = $hsurp->{$k}{surplswh}; + + if (defined $val && $val =~ /^(\d+)\.(\w+)$/) { + $spls00 = $1; + my $replacement = sprintf "%.0f", ($spls00 / 60 * (60 - int $minute)); # aktuelle (Rest)-Stunde -> zeitgewichteter PV-Überschuß + $replacement .= '.'.$2; + $hsurp->{$k}{surplswh} = $replacement; + } + + last; # da Stunde 00 nur einmal vorkommt, können wir abbrechen + } } my $fipl = CurrentVal ($name, 'feedinPowerLimit', INFINITE); @@ -12164,28 +12164,28 @@ sub __batChargeOptTargetPower { my $otp; for my $hod (sort { $a <=> $b } keys %{$hsurp}) { - my $nhr = $hsurp->{$hod}{nhr}; - next if(!defined $nhr); - + my $nhr = $hsurp->{$hod}{nhr}; + next if(!defined $nhr); + my $spls = int ($hsurp->{$hod}{surplswh} // 0); $spls = $spls00 if($nhr eq '00'); # aktuelle Stunde: zeitgewichteter PV-Überschuß mit Original ersetzen - - my $nexthod = sprintf "%02d", (int $hod + 1); - my $nextnhr = $hsurp->{$nexthod}{nhr}; + + my $nexthod = sprintf "%02d", (int $hod + 1); + my $nextnhr = $hsurp->{$nexthod}{nhr}; my @remaining_hods = grep { int $_ >= int $hod } @sortedhods; - my $total = 0; - $total += $hsurp->{$_}{surplswh} for @remaining_hods; # Gesamtkapazität aller Stunden mit PV-Überschuß ermitteln + my $total = 0; + $total += $hsurp->{$_}{surplswh} for @remaining_hods; # Gesamtkapazität aller Stunden mit PV-Überschuß ermitteln - for my $sbn (sort { $a <=> $b } @batteries) { # jede Batterie - my $bpinmax = $hsurp->{$hod}{$sbn}{bpinmax}; # Bat max. mögliche Ladelesitung + for my $sbn (sort { $a <=> $b } @batteries) { # jede Batterie + my $bpinmax = $hsurp->{$hod}{$sbn}{bpinmax}; # Bat max. mögliche Ladelesitung my $batinstcap = $hsurp->{$hod}{$sbn}{batinstcap}; # Kapa dieser Batterie my $lowSocwh = $hsurp->{$hod}{$sbn}{lowSocwh}; # eingestellter lowSoc in Wh my $batoptsocwh = $hsurp->{$hod}{$sbn}{batoptsocwh}; # optimaler SoC in Wh my $csocwh = $hsurp->{$hod}{$sbn}{csocwh}; # aktueller SoC in Wh my $bpinreduced = $hsurp->{$hod}{$sbn}{bpinreduced}; # Standardwert bei <=lowSoC -> Anforderungsladung vom Grid my $befficiency = $hsurp->{$hod}{$sbn}{befficiency}; # Speicherwirkungsgrad - my $strategy = $hsurp->{$hod}{$sbn}{strategy}; # Ladestrategie + my $strategy = $hsurp->{$hod}{$sbn}{strategy}; # Ladestrategie # Initialisierung / Fortschreibung Prognose-SOC (Wh) ###################################################### @@ -12233,8 +12233,8 @@ sub __batChargeOptTargetPower { $runwh += $diff / $befficiency; # um Verbrauch reduzieren $runwh = ___batClampValue ($runwh, $lowSocwh, $batoptsocwh, $batinstcap); # runwh begrenzen - $hsurp->{$hod}{$sbn}{fcendwh} = sprintf "%.0f", $runwh; - $hsurp->{$nexthod}{$sbn}{fcnextwh} = $hsurp->{$hod}{$sbn}{fcendwh} if(defined $nextnhr); # Startwert kommende Stunde + $hsurp->{$hod}{$sbn}{fcendwh} = sprintf "%.0f", $runwh; + $hsurp->{$nexthod}{$sbn}{fcnextwh} = $hsurp->{$hod}{$sbn}{fcendwh} if(defined $nextnhr); # Startwert kommende Stunde next; } @@ -12275,8 +12275,8 @@ sub __batChargeOptTargetPower { $target *= (1 + $otpMargin / 100) ** 2; if ($strategy eq 'smartPower') { # smartPower: agressivere Ladeleistung - $hs2sunset -= 1; - $target = $runwhneed > 0 && $hs2sunset > 0 ? $runwhneed / $hs2sunset : $target; + $hs2sunset -= 1; + $target = $runwhneed > 0 && $hs2sunset > 0 ? $runwhneed / $hs2sunset : $target; $target *= 1 + ($otpMargin / 100); } } @@ -12301,7 +12301,7 @@ sub __batChargeOptTargetPower { $runwh = min ($goalwh, $runwh + $diff * $befficiency); # Endwert Prognose $runwh = ___batClampValue ($runwh, $lowSocwh, $batoptsocwh, $batinstcap); # runwh begrenzen - + $hsurp->{$hod}{$sbn}{fcendwh} = sprintf "%.0f", $runwh; $hsurp->{$nexthod}{$sbn}{fcnextwh} = $hsurp->{$hod}{$sbn}{fcendwh} if(defined $nextnhr); # Startwert kommende Stunde } @@ -12317,6 +12317,7 @@ return ($hsurp, $otp); ################################################################ # Zielleistung mit Sicherheitszuschlag behandeln # abfallend proportional zum linearen Rest-Überschuss des Tages +# Forum: https://forum.fhem.de/index.php?msg=1349579 ################################################################ sub ___batAdjustPowerByMargin { my ($name, $target, $pinmax, $runwhneed, $otpMargin) = @_; @@ -12465,8 +12466,8 @@ sub ___batChargeSaveResults { ## abhängig von Strategie in entsprechender Schleife setzen ############################################################# if (($loopid eq 'LR' && $strategy eq 'loadRelease') || ($loopid eq 'OTP' && $strategy =~ /(?:opt|smart)Power/xs)) { - $data{$name}{nexthours}{'NextHour'.$nhr}{'rcdchargebat'.$bn} = $crel; - + $data{$name}{nexthours}{'NextHour'.$nhr}{'rcdchargebat'.$bn} = $crel; + if ($today && $hod) { writeToHistory ( { paref => $paref, key => 'batprogsoc'.$bn, val => $progsoc, hour => $hod } ); } @@ -13343,7 +13344,7 @@ sub ___doPlanning { if (ref $epieces ne 'HASH') { debugLog ($paref, "consumerPlanning", qq{consumer "$c" - no first energy piece found. Exiting...}); - return; + return; } my $cicfip = CurrentVal ($name, 'consForecastInPlanning', 0); # soll Consumption Vorhersage in die Überschußermittlung eingehen ? @@ -14211,7 +14212,7 @@ sub ___setConsumerSwitchingState { delete $paref->{supplement}; - $state = qq{Consumer '$calias' was external switched on}; + $state = qq{Consumer '$calias' was switched on externally}; $dowri = 1; } elsif ($oldpsw eq 'on' && isConsumerPhysOff ($hash, $c)) { @@ -14221,7 +14222,7 @@ sub ___setConsumerSwitchingState { delete $paref->{supplement}; - $state = qq{Consumer '$calias' was external switched off}; + $state = qq{Consumer '$calias' was switched off externally}; $dowri = 1; } @@ -16928,21 +16929,32 @@ sub __createOwnSpec { my @fields = split (/\s+/sx, $spec); - my (@cats, @vals); + my (@props, @cats, @vals); # @props - Eigenschaften, @cats - Kategorien, @vals - Label:Werte Paare for my $f (@fields) { - if ($f =~ /^\#(.*)/xs) { + if ($f =~ /^\#(.*)/xs) { # eine Kategorie erkannt push @cats, $1; next; } + elsif ($f =~ /^\.([^\s]+)/xs) { # eine Eigenschaft erkannt + push @props, $1; + next; + } push @vals, $f; } - my ($ownv, $cakey); my $isize = INPUTSIZE; - my $rows = ceil (scalar(@vals) / $vinr); - my $col = 0; + + if (@props) { + my $properties = join (' ', @props); + my ($pa, $ph) = parseParams ($properties); + $isize = $ph->{inputSize} if(defined $ph->{inputSize}); + } + + my $rows = ceil (scalar(@vals) / $vinr); + my $col = 0; + my ($ownv, $cakey); for (my $i = 1 ; $i <= $rows; $i++) { my ($h, $v, $u); @@ -16959,10 +16971,8 @@ sub __createOwnSpec { $col++; if (!defined $h->{$k}{label}) { - #undef $h->{$k}{label}; - $h->{$k}{label} = ''; - #next; - $h->{$k}{elm} = ''; + $h->{$k}{label} = ''; + $h->{$k}{elm} = ''; } ## Set-Kommandos identifizieren @@ -17002,12 +17012,12 @@ sub __createOwnSpec { ## Reading identifizieren ########################### if ($elm) { - $v->{$k} = ReadingsVal ($dev, $elm, undef); + $v->{$k} = ReadingsVal ($dev, $elm, undef); - if (defined $v->{$k} && $v->{$k} =~ /^\s*(-?\d+(\.\d+)?)/xs) { - ($v->{$k}, $u->{$k}) = split /\s+/, ReadingsVal ($dev, $elm, ''); # Value und Unit trennen wenn Value numerisch - } - } + if (defined $v->{$k} && $v->{$k} =~ /^\s*(-?\d+(\.\d+)?)/xs) { + ($v->{$k}, $u->{$k}) = split /\s+/, ReadingsVal ($dev, $elm, ''); # Value und Unit trennen wenn Value numerisch + } + } $u->{$k} //= q{}; @@ -18586,7 +18596,7 @@ sub __batteryOnBeam { my $stysymbol = $strategy eq 'loadRelease' ? 'ldreleas' : $strategy eq 'optPower' ? 'optchpow' : - $strategy eq 'smartPower' ? 'smtchpow' : + $strategy eq 'smartPower' ? 'smtchpow' : 'norate'; my ($bpower, $currsoc); @@ -18988,7 +18998,7 @@ END0 } ); - my $ccicon = (split '@', $cicon)[1]; + my $ccicon = (split '@', $cicon)[1]; $cicon = FW_makeImage ($cicon, ''); $cicon = __normIconInnerScale ($cicon, $ccicon); @@ -19054,7 +19064,7 @@ END1 my $cdicon = (split '@', $dicon)[1]; $dicon = FW_makeImage ($dicon, ''); - $dicon = __normIconInnerScale ($dicon, $cdicon); + $dicon = __normIconInnerScale ($dicon, $cdicon); $ret .= qq{}; $ret .= "$dumtxt".$dicon; @@ -19533,11 +19543,11 @@ sub __addInputProducerIcon { } ); - my $gcolor = (split '@', $genericon)[1]; + my $gcolor = (split '@', $genericon)[1]; $genericon = FW_makeImage ($genericon, ''); - $genericon = __normIconInnerScale ($genericon, $gcolor); - - $ret .= qq{}; + $genericon = __normIconInnerScale ($genericon, $gcolor); + + $ret .= qq{}; $ret .= "$genertxt".$genericon; $ret .= ' '; } @@ -19555,8 +19565,8 @@ sub __addInputProducerIcon { my $cpicon = (split '@', $picon)[1]; $picon = FW_makeImage ($picon, ''); - $picon = __normIconInnerScale ($picon, $cpicon); - + $picon = __normIconInnerScale ($picon, $cpicon); + $ret .= qq{}; $ret .= "$ptxt".$picon; $ret .= ' '; @@ -19593,7 +19603,7 @@ sub __addNodeIcon { my $cnicon = (split '@', $nicon)[1]; $nicon = FW_makeImage ($nicon, ''); $nicon = __normIconInnerScale ($nicon, $cnicon); - + my $ret = qq{}; # translate(X-Koordinate,Y-Koordinate) $ret .= "$ntxt".$nicon; $ret .= ' '; @@ -27179,7 +27189,7 @@ to ensure that the system configuration is correct. Depending on these values, the switching times of the consumer are planned and the cycle of the consumer is started depending on the sufficient PV surplus at the time of planning.
If power=0 or pvshare=0 is set, the consumer is switched on as planned, regardless of whether there is sufficient - PV surplus. + PV surplus.