diff --git a/fhem/contrib/DS_Starter/76_SolarForecast.pm b/fhem/contrib/DS_Starter/76_SolarForecast.pm index d6ebb0995..d31d2f605 100644 --- a/fhem/contrib/DS_Starter/76_SolarForecast.pm +++ b/fhem/contrib/DS_Starter/76_SolarForecast.pm @@ -160,6 +160,8 @@ BEGIN { # Versions History intern my %vNotesIntern = ( + "1.59.4" => "13.10.2025 new subs, ctrlBatSocManagementXX: new key loadTarget, replace __batCapShareFactor by __batDeficitShareFactor ", + "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 ". "__normIconInnerScale: add path color filling, Calculation of time-weighted consumption or PV generation ". @@ -7533,8 +7535,9 @@ sub _attrBatSocManagement { ## no critic "not used" lcSlot => { comp => '((?:[01]\d|2[0-3]):[0-5]\d-(?:[01]\d|2[0-3]):[0-5]\d)', must => 0, act => 1 }, careCycle => { comp => '\d+', must => 0, act => 0 }, loadAbort => { comp => '(?:100|[1-9]?[0-9]):\d+(?::(?:100|[1-9]?[0-9]))?', must => 0, act => 0 }, - safetyMargin => { comp => '(?:100|[1-9]?\d)(?::(?:100|[1-9]?\d))?', must => 0, act => 0 }, loadStrategy => { comp => '(loadRelease|optPower)', must => 0, act => 0 }, + loadTarget => { comp => '(100|[1-9]?[0-9])', must => 0, act => 0 }, + safetyMargin => { comp => '(?:100|[1-9]?\d)(?::(?:100|[1-9]?\d))?', must => 0, act => 0 }, weightOwnUse => { comp => '(100|[1-9]?[0-9])', must => 0, act => 0 }, }; @@ -11406,15 +11409,16 @@ sub _batSocTarget { $batymaxsoc >= $maxSoc ? $batysetsoc - BATSOCCHGDAY : $batysetsoc; # neuer Min SOC für den laufenden Tag - ## erwartete PV ermitteln & Anteilsfaktor Bat an Gesamtbatteriekapazität anwenden - ################################################################################### + ## erwartete PV ermitteln & Anteilsfaktor Bat anwenden + ######################################################## my $pvfctm = ReadingsNum ($name, 'Tomorrow_PVforecast', 0); # PV Prognose morgen my $pvfctd = ReadingsNum ($name, 'RestOfDayPVforecast', 0); # PV Prognose Rest heute my $pvexpraw = $pvfctm > $pvfctd ? $pvfctm : $pvfctd - $tdconsset; # erwartete (Rest) PV-Leistung des Tages $pvexpraw = $pvexpraw > 0 ? $pvexpraw : 0; # erwartete PV-Leistung inkl. Verbrauchsprognose bis Sonnenuntergang - my $sf = __batCapShareFactor ($hash, $bn); # Anteilsfaktor der Batterie XX Kapazität an Gesamtkapazität - my $pvexpect = $sf * $pvexpraw; + #my $sf = __batCapShareFactor ($name, $bn); # Anteilsfaktor der Batterie XX Kapazität an Gesamtkapazität + my $sf = __batDeficitShareFactor ($name, $bn); + my $pvexpect = $sf * $pvexpraw; if ($debug eq 'batteryManagement') { Log3 ($name, 1, "$name DEBUG> Bat $bn SoC Step1 - basics -> Battery share factor of total capacity: $sf"); @@ -11443,7 +11447,7 @@ sub _batSocTarget { __batSaveSocKeyFigures ($paref); delete $paref->{days2care}; - $careSoc = $maxSoc - ($days2care * BATSOCCHGDAY); # Pflege-SoC um rechtzeitig den $maxsoc zu erreichen bei 5% Steigerung pro Tag + $careSoc = $maxSoc - ($days2care * BATSOCCHGDAY); # Pflege-SoC um rechtzeitig den $maxsoc zu erreichen bei BATSOCCHGDAY % Steigerung pro Tag $careSoc = $careSoc < $lowSoc ? $lowSoc : $careSoc; if ($careSoc >= $target) { @@ -11508,17 +11512,17 @@ sub _batSocTarget { debugLog ($paref, 'batteryManagement', "Bat $bn SoC Step4 - basics -> docare: $docare, lowSoc: $lowSoc %, upSoc: $upSoc %"); debugLog ($paref, 'batteryManagement', "Bat $bn SoC Step4 - observe low/up limits -> Target: $target %"); - ## auf 5er Schritte anpassen (40,45,50,...) - ############################################# - my $flo = floor ($target / 5); - my $rmn = $target - ($flo * 5); - my $add = $rmn <= 2.5 ? 0 : 5; - $target = ($flo * 5) + $add; + ## auf BATSOCCHGDAY Schritte anpassen (40,45,50,...) + ###################################################### + my $flo = floor ($target / BATSOCCHGDAY); + my $rmn = $target - ($flo * BATSOCCHGDAY); + my $add = $rmn <= 2.5 ? 0 : BATSOCCHGDAY; + $target = ($flo * BATSOCCHGDAY) + $add; - debugLog ($paref, 'batteryManagement', "Bat $bn SoC Step5 - rounding the SoC to steps of 5 % -> Target: $target %"); + debugLog ($paref, 'batteryManagement', "Bat $bn SoC Step5 - rounding the SoC to steps of ".BATSOCCHGDAY." % -> Target: $target %"); - ## Zwangsladeanforderung - ########################## + ## Ladeanforderung + #################### if ($soc < $target) { $chargereq = 1; } @@ -11558,7 +11562,8 @@ sub __parseAttrBatSoc { lcslot => $ph->{lcSlot}, loadAbort => $ph->{loadAbort}, loadStrategy => $ph->{loadStrategy}, - weightOwnUse => $ph->{weightOwnUse}, + loadTarget => $ph->{loadTarget}, + weightOwnUse => $ph->{weightOwnUse}, lrMargin => $lrMargin, otpMargin => $otpMargin, }; @@ -11587,15 +11592,49 @@ sub __batSaveSocKeyFigures { return; } +################################################################ +# Anteilsfaktor der Batterie XX Defizit an Gesamtdefizit +################################################################ +sub __batDeficitShareFactor { + my $name = shift; + my $bn = shift; # Batterienummer + + my $csocwh = BatteryVal ($name, $bn, 'bchargewh', 0); # aktuelle Ladung in Wh + my $binstcap = BatteryVal ($name, $bn, 'binstcap', 0); # installierte Batteriekapazität Wh + my $bdeficit = $binstcap - $csocwh; + my $batwhdeficitsum = CurrentVal ($name, 'batwhdeficitsum', $binstcap); # Summe Ladungsdefizit + + my $sf = (100 * $bdeficit / $batwhdeficitsum) / 100; # Anteilsfaktor Defizit Batt XX an Gesamtdefizit + +return $sf; +} + +################################################################ +# Anteilsfaktor der Batterie XX Ladung an Gesamtladung +################################################################ +sub __batLoadShareFactor { + my $name = shift; + my $bn = shift; # Batterienummer + + my $csocwh = BatteryVal ($name, $bn, 'bchargewh', 0); # aktuelle Ladung in Wh + my $batcapsum = CurrentVal ($name, 'batcapsum', 1); # Summe installierte Batterie Kapazität + my $batwhdeficitsum = CurrentVal ($name, 'batwhdeficitsum', 0); # Summe Ladungsdefizit + my $loadsum = max (1, $batcapsum - $batwhdeficitsum); + + my $sf = (100 * $csocwh / $loadsum) / 100; # Anteilsfaktor Ladung Batt XX an Gesamtladung + +return $sf; +} + ################################################################ # Anteilsfaktor der Batterie XX Kapazität an Gesamtkapazität ################################################################ sub __batCapShareFactor { - my $hash = shift; + my $name = shift; my $bn = shift; # Batterienummer - my $binstcap = BatteryVal ($hash, $bn, 'binstcap', 1); # Kapazität der Batterie XX - my $batcapsum = CurrentVal ($hash, 'batcapsum', $binstcap); # Summe installierte Batterie Kapazität + my $binstcap = BatteryVal ($name, $bn, 'binstcap', 1); # Kapazität der Batterie XX + my $batcapsum = CurrentVal ($name, 'batcapsum', $binstcap); # Summe installierte Batterie Kapazität my $sf = (100 * $binstcap / $batcapsum) / 100; # Anteilsfaktor der Batt XX Kapazität an Gesamtkapazität @@ -11668,7 +11707,7 @@ sub _batChargeMgmt { my $rodpvfc = ReadingsNum ($name, 'RestOfDayPVforecast', 0); # PV Prognose Rest des Tages my $tompvfc = ReadingsNum ($name, 'Tomorrow_PVforecast', 0); # PV Prognose nächster Tag my $tomconfc = ReadingsNum ($name, 'Tomorrow_ConsumptionForecast', 0); # Verbrauchsprognose nächster Tag - my $batoptsoc = ReadingsNum ($name, 'Battery_OptimumTargetSoC_'.$bn, 0); # aktueller optimierter SoC + my $batoptsoc = ReadingsNum ($name, 'Battery_OptimumTargetSoC_'.$bn, 0); # aktueller optimierter SoC in % my $confcss = CurrentVal ($name, 'tdConFcTillSunset', 0); # Verbrauchsprognose bis Sonnenuntergang my $csoc = BatteryVal ($name, $bn, 'bcharge', 0); # aktuelle Ladung in % my $csocwh = BatteryVal ($name, $bn, 'bchargewh', 0); # aktuelle Ladung in Wh @@ -11678,16 +11717,17 @@ sub _batChargeMgmt { my $bpinreduced = BatteryVal ($name, $bn, 'bpinreduced', 0); # Standardwert bei <=lowSoC -> Anforderungsladung vom Grid my $befficiency = BatteryVal ($name, $bn, 'befficiency', STOREFFDEF) / 100; # Speicherwirkungsgrad my $cgbt = AttrVal ($name, 'ctrlBatSocManagement'.$bn, undef); - my $sf = __batCapShareFactor ($hash, $bn); # Anteilsfaktor der Batterie XX Kapazität an Gesamtkapazität + #my $sf = __batCapShareFactor ($name, $bn); # Anteilsfaktor der Batterie XX Kapazität an Gesamtkapazität + my $sf = __batDeficitShareFactor ($name, $bn); # Anteilsfaktor Ladungsdefizit my $strategy = 'loadRelease'; # 'loadRelease' oder 'optPower' my $wou = 0; # Gewichtung Prognose-Verbrauch als Anteil "Eigennutzung" (https://forum.fhem.de/index.php?msg=1348429) my $lowSoc = 0; my $loadAbort = ''; - my $goalwh = $batinstcap; # Ladeziel (Wh) + my $goalwh = $batinstcap; # initiales Ladeziel (Wh) my $lrMargin = SFTYMARGIN_50; my $otpMargin = SFTYMARGIN_20; my $lcslot; - + if ($cgbt) { my $parsed = __parseAttrBatSoc ($name, $cgbt); $lowSoc = $parsed->{lowSoc} // 0; @@ -11697,12 +11737,19 @@ sub _batChargeMgmt { $otpMargin = $parsed->{otpMargin} // $otpMargin; # Sicherheitszuschlag OTP (%) $strategy = $parsed->{loadStrategy} // $strategy; $wou = $parsed->{weightOwnUse} // $wou; + my $tgt = $parsed->{loadTarget}; # Ladeziel-SoC in % + $tgt = $batoptsoc if(defined $tgt && $tgt < $batoptsoc); # Wert Battery_OptimumTargetSoC_XX beachten + $goalwh = defined $tgt + ? sprintf "%.0f", ___batSocPercentToWh ($batinstcap, $tgt) + : $goalwh; # Ladeziel-SoC in Wh } + + my $goalpercent = sprintf "%.0f", ___batSocWhToPercent ($batinstcap, $goalwh); # Ladeziel in % ## generelle Ladeabbruchbedingung evaluieren ############################################## if ($loadAbort) { - my ($abortSoc, $abortpin, $releaseSoC) = split ':', $loadAbort; # Ladeabbruch Forum: https://forum.fhem.de/index.php?msg=1342556 + my ($abortSoc, $abortpin, $releaseSoC) = split ':', $loadAbort; # Ladeabbruch Forum: https://forum.fhem.de/index.php?msg=1342556 $releaseSoC //= $abortSoc; @@ -11718,10 +11765,10 @@ sub _batChargeMgmt { readingsDelete ($hash, 'Battery_ChargeAbort_'.$bn); } - my $labortCond = BatteryVal ($name, $bn, 'bloadAbortCond', 0); # Ladeabbruchbedingung gesetzt 1 oder nicht 0 - my $batoptsocwh = $batinstcap * $batoptsoc / 100; # optimaler SoC in Wh - my $lowSocwh = $batinstcap * $lowSoc / 100; # lowSoC in Wh - my $socwh = sprintf "%.0f", ($batinstcap * $csoc / 100); # aktueller SoC in Wh + my $labortCond = BatteryVal ($name, $bn, 'bloadAbortCond', 0); # Ladeabbruchbedingung gesetzt 1 oder nicht 0 + my $batoptsocwh = ___batSocPercentToWh ($batinstcap, $batoptsoc); # optimaler SoC in Wh + my $lowSocwh = ___batSocPercentToWh ($batinstcap, $lowSoc); # lowSoC in Wh + my $socwh = sprintf "%.0f", ___batSocPercentToWh ($batinstcap, $csoc); # aktueller SoC in Wh my $whneed = $goalwh - $socwh; @@ -11735,10 +11782,11 @@ sub _batChargeMgmt { if ($paref->{debug} =~ /batteryManagement/) { Log3 ($name, 1, "$name DEBUG> Bat $bn ChargeMgmt - General load termination condition: $labortCond"); Log3 ($name, 1, "$name DEBUG> Bat $bn ChargeMgmt - control time Slot - Slot start: $lcstart, Slot end: $lcend"); - Log3 ($name, 1, "$name DEBUG> Bat $bn ChargeMgmt - Installed Battery capacity: $batinstcap Wh, Percentage of total capacity: ".(sprintf "%.1f", $sf*100)." %"); Log3 ($name, 1, "$name DEBUG> Bat $bn ChargeMgmt - Battery efficiency used: ".($befficiency * 100)." %"); - Log3 ($name, 1, "$name DEBUG> Bat $bn ChargeMgmt - The PV generation, consumption and surplus listed below are based on the battery's share of total installed capacity!"); - Log3 ($name, 1, "$name DEBUG> Bat $bn ChargeLR - charging target: $goalwh Wh"); + Log3 ($name, 1, "$name DEBUG> Bat $bn ChargeMgmt - charging target: $goalpercent % / $goalwh Wh"); + #Log3 ($name, 1, "$name DEBUG> Bat $bn ChargeMgmt - Installed Battery capacity: $batinstcap Wh, Percentage of total capacity: ".(sprintf "%.1f", $sf*100)." %"); + Log3 ($name, 1, "$name DEBUG> Bat $bn ChargeMgmt - Percentage of the total amount of charging energy required: ".(sprintf "%.1f", $sf*100)." %"); + Log3 ($name, 1, "$name DEBUG> Bat $bn ChargeMgmt - 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> Bat $bn ChargeLR - used safety margin: $lrMargin %"); Log3 ($name, 1, "$name DEBUG> Bat $bn ChargeLR - weighted self-consumption: $wou %"); } @@ -11863,7 +11911,7 @@ sub _batChargeMgmt { $socwh = ___batClampValue ($socwh, $lowSocwh, $batoptsocwh, $batinstcap); # SoC begrenzen $socwh = sprintf "%.0f", $socwh; # SoC Prognose in Wh - $progsoc = sprintf "%.1f", (100 * $socwh / $batinstcap); # Prognose SoC in % + $progsoc = sprintf "%.1f", ___batSocWhToPercent ($batinstcap, $socwh); # Prognose SoC in % ## Debuglog LR ################ @@ -11877,7 +11925,7 @@ sub _batChargeMgmt { } } - debugLog ($paref, 'batteryManagement', "Bat $bn ChargeLR $stt - lc: $crel, $msg"); + debugLog ($paref, 'batteryManagement', "Bat $bn ChargeLR $stt - lr: $crel, $msg"); ## Fortschreibung ################### @@ -11933,7 +11981,7 @@ sub _batChargeMgmt { ## SOC-Prognose OTP ##################### my $fcendwh = $hopt->{$shod}{$bat}{fcendwh} // 0; - $progsoc = sprintf "%.1f", (100 * $fcendwh / $hopt->{$shod}{$bat}{batinstcap}); # Prognose SoC in % + $progsoc = sprintf "%.1f", ___batSocWhToPercent ($hopt->{$shod}{$bat}{batinstcap}, $fcendwh); # Prognose SoC in % ## Speicherung und Readings erstellen OTP ########################################## @@ -11946,7 +11994,7 @@ sub _batChargeMgmt { hod => $shod, loopid => 'OTP', strategy => $hopt->{$shod}{$bat}{strategy}, - crel => 1, # immer Freigabe bei optPower (für Anzeige) + crel => $hopt->{$shod}{$bat}{loadrel}, }; ___batChargeSaveResults ($paref, $values); @@ -11957,7 +12005,8 @@ sub _batChargeMgmt { my $lcintime = $hopt->{$shod}{$bat}{lcintime}; my $spls = int $hopt->{$shod}{surplswh}; my $pneedmin = $hopt->{$shod}{$bat}{pneedmin}; - my $ttt = $hopt->{$shod}{$bat}{stt}; + my $ttt = $hopt->{$shod}{$bat}{stt}; + my $crel = $hopt->{$shod}{$bat}{loadrel}; if ($nhr eq '00') { $pneedmin = $otp->{$bat}{target} // 0; @@ -11969,7 +12018,7 @@ sub _batChargeMgmt { Log3 ($name, 1, "$name DEBUG> Bat $bat ChargeOTP - $achievelog"); } - Log3 ($name, 1, "$name DEBUG> Bat $bat ChargeOTP $ttt - hod: $shod / $nhr, lc: $lcintime, SoC S/E: $ssocwh / $fcendwh Wh, Surplus: $spls Wh, OTP: $pneedmin W"); + Log3 ($name, 1, "$name DEBUG> Bat $bat ChargeOTP $ttt - hod: $shod / $nhr, lr/lc: $crel/$lcintime, SoC S/E: $ssocwh / $fcendwh Wh, Surplus: $spls Wh, OTP: $pneedmin W"); } } } @@ -12078,16 +12127,17 @@ sub __batChargeOptTargetPower { my $total = 0; $total += $hsurp->{$_}{surplswh} for @remaining_hods; # Gesamtkapazität aller Stunden mit PV-Überschuß ermitteln - if ($total * $befficiency < $goalwh) { # Erreichbarkeit des Ziels (benötigte Ladeenergie total) prüfen + if ($runwhneed > 0 && $total * $befficiency < $goalwh) { # Erreichbarkeit des Ziels (benötigte Ladeenergie total) prüfen $achievable = 0; } + $hsurp->{$hod}{$sbn}{loadrel} = $runwhneed > 0 ? 1 : 0; # Ladefreigabe abhängig von Ziel-SoC Erfüllung $hsurp->{$hod}{$sbn}{achievelog} = "charging target: $goalwh Wh, remaining: ".(sprintf "%.0f", $runwhneed).' Wh -> target likely achievable? '.($achievable ? 'yes' : 'no'); ## kein Überschuß ################### - if (!$spls) { # Ladesteuerung nicht "In Time" - $hsurp->{$hod}{$sbn}{pneedmin} = $bpinmax; + if (!$spls) { + $hsurp->{$hod}{$sbn}{pneedmin} = $runwhneed > 0 ? $bpinmax : 0; # Ladeleistung abhängig von Ziel-SoC Erfüllung $diff = $hsurp->{$hod}{speff}; # Verbrauch @@ -12117,7 +12167,7 @@ sub __batChargeOptTargetPower { $needraw = sprintf "%.0f", $needraw; $needraw = min ($needraw, $bpinmax); # Begrenzung auf max. mögliche Batterieleistung - $hsurp->{$hod}{$sbn}{pneedmin} = $needraw; + $hsurp->{$hod}{$sbn}{pneedmin} = $runwhneed > 0 ? $needraw : 0; # Ladeleistung abhängig von Ziel-SoC Erfüllung ## NextHour 00 bearbeiten ########################### @@ -12165,15 +12215,35 @@ return ($hsurp, $otp); } ################################################################ -# Begrenzungen einhalten zwischen low, mid und high Grenze +# Umrechnung Batterie SoC % in Wh +################################################################ +sub ___batSocPercentToWh { + my $base = shift; # Batteriekapazität in Wh + my $soc = shift; # SoC in % + +return $base / 100 * $soc; +} + +################################################################ +# Umrechnung Batterie Wh in SoC % +################################################################ +sub ___batSocWhToPercent { + my $base = shift; # Batteriekapazität in Wh + my $socwh = shift; # SoC in Wh + +return 100 / $base * $socwh ; +} + +################################################################ +# Begrenzungen einhalten zwischen low, opt und high Grenze # -# $x = ___batClampValue ($value, $low, $mid, $high); +# $x = ___batClampValue ($value, $low, $opt, $high); ################################################################ sub ___batClampValue { - my ($value, $low, $mid, $high) = @_; + my ($value, $low, $opt, $high) = @_; $value = $value < $low ? $low : - $value < $mid ? $mid : + $value < $opt ? $opt : $value > $high ? $high : $value; @@ -27139,14 +27209,15 @@ to ensure that the system configuration is correct.