From af6b8a1b0e5d9188fd04ed50861b6ae3bf6a28d9 Mon Sep 17 00:00:00 2001 From: DS_Starter Date: Sun, 2 Nov 2025 20:37:27 +0000 Subject: [PATCH] 76_SolarForecast: minor code changes after v 1.60.0 git-svn-id: https://svn.fhem.de/fhem/trunk@30477 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/FHEM/76_SolarForecast.pm | 157 ++++++++++++++++++++-------------- 1 file changed, 94 insertions(+), 63 deletions(-) diff --git a/fhem/FHEM/76_SolarForecast.pm b/fhem/FHEM/76_SolarForecast.pm index 802afdf0e..82d3f55d4 100644 --- a/fhem/FHEM/76_SolarForecast.pm +++ b/fhem/FHEM/76_SolarForecast.pm @@ -160,13 +160,14 @@ BEGIN { # Versions History intern my %vNotesIntern = ( + "1.60.1" => "02.11.2025 ___batAdjustPowerByMargin: minor code change, preparation for barrierSoC ", "1.60.0" => "01.11.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 ". - " Task 2: chamge timestamp of day before to 24:00:00, _restorePlantConfig: fix problem with attr sequence ". + "_restorePlantConfig: fix problem with attr sequence ". "_setreset: set reset is reworked with widgetList, aiData can be deleted by index ". "_flowGraphic: new variable node2home_direction ". "new sub askLogtime to avoid error logs too often, Forum: https://forum.fhem.de/index.php?msg=1350716 ", @@ -7649,6 +7650,7 @@ sub _attrBatSocManagement { ## no critic "not used" 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 }, + barrierSoC => { comp => '(100|[1-9]?[0-9])', must => 0, act => 0 }, stepSoC => { comp => '[0-5]', 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 }, @@ -11700,6 +11702,7 @@ sub __parseAttrBatSoc { my $parsed = { lowSoc => $ph->{lowSoc}, + barrierSoC => $ph->{barrierSoC}, # SoC Barriere ab der eine Ladeleistungssteuerung aktiv sein soll upSoc => $ph->{upSoC}, maxSoc => $ph->{maxSoC} // MAXSOCDEF, # optional (default: MAXSOCDEF) stepSoc => $ph->{stepSoC} // BATSOCCHGDAY, # mögliche SoC-Änderung pro Tag @@ -11871,6 +11874,7 @@ sub _batChargeMgmt { $strategy = 'loadRelease'; # 'loadRelease', 'optPower', 'smartPower' my $wou = 0; # Gewichtung Prognose-Verbrauch als Anteil "Eigennutzung" (https://forum.fhem.de/index.php?msg=1348429) my $lowSoc = 0; + my $barrierSoC = 0; my $loadAbort = ''; my $goalwh = $batinstcap; # initiales Ladeziel (Wh) my $lrMargin = SFTYMARGIN_50; @@ -11878,22 +11882,24 @@ sub _batChargeMgmt { my $lcslot; if ($cgbt) { - my $parsed = __parseAttrBatSoc ($name, $cgbt); - $lowSoc = $parsed->{lowSoc} // 0; - $lcslot = $parsed->{lcslot}; - $loadAbort = $parsed->{loadAbort}; - $lrMargin = $parsed->{lrMargin} // $lrMargin; # Sicherheitszuschlag LR (%) - $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 $parsed = __parseAttrBatSoc ($name, $cgbt); + $lowSoc = $parsed->{lowSoc} // 0; + $barrierSoC = $parsed->{barrierSoC} // $barrierSoC; # SoC-Barriere, ab der die Ladesteuerung akitv sein soll + $lcslot = $parsed->{lcslot}; + $loadAbort = $parsed->{loadAbort}; + $lrMargin = $parsed->{lrMargin} // $lrMargin; # Sicherheitszuschlag LR (%) + $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 % + my $barrierSoCWh = sprintf "%.0f", ___batSocPercentToWh ($batinstcap, $barrierSoC); + my $goalpercent = sprintf "%.0f", ___batSocWhToPercent ($batinstcap, $goalwh); # Ladeziel in % ## generelle Ladeabbruchbedingung evaluieren ############################################## @@ -11932,6 +11938,7 @@ sub _batChargeMgmt { 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 - control barrier SoC - $barrierSoC % / $barrierSoCWh Wh"); Log3 ($name, 1, "$name DEBUG> ChargeMgmt Bat $bn - Battery efficiency used: ".($befficiency * 100)." %"); Log3 ($name, 1, "$name DEBUG> ChargeMgmt Bat $bn - weighted self-consumption: $wou %"); Log3 ($name, 1, "$name DEBUG> ChargeMgmt Bat $bn - charging target: $goalpercent % / $goalwh Wh"); @@ -12017,8 +12024,9 @@ sub _batChargeMgmt { 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} # Ladefreigabe wenn kein BatSoc-Management - if ( !$lcintime ) {$crel = 1} # Ladefreigabe wenn nicht innerhalb Zeitslot für Ladesteuerung + if ( !$cgbt ) {$crel = 1} # generelle Ladefreigabe wenn kein BatSoc/Lade-Management + if ( !$lcintime ) {$crel = 1} # generelle Ladefreigabe wenn nicht innerhalb Zeitslot für Ladesteuerung + if ( $csocwh <= $barrierSoCWh) {$crel = 1} # generelle Ladefreigabe wenn aktueller SoC <= Barriere-SoC if ( $labortCond ) {$crel = 0} # keine Ladefreigabe bei genereller Abbruchbedingung # Steuerhash für optimimierte Ladeleistung erstellen @@ -12037,6 +12045,7 @@ sub _batChargeMgmt { $hsurp->{$fd}{$hod}{$bn}{bpinreduced} = $bpinreduced; # Standardwert bei <=lowSoC -> Anforderungsladung vom Grid $hsurp->{$fd}{$hod}{$bn}{bpoutmax} = $bpoutmax; # max. mögliche Entladeleistung $hsurp->{$fd}{$hod}{$bn}{lowSocwh} = $lowSocwh; # eingestellter lowSoC in Wh + $hsurp->{$fd}{$hod}{$bn}{barrierSoCWh} = $barrierSoCWh; # eingestellter Barriere SoC in Wh $hsurp->{$fd}{$hod}{$bn}{batoptsocwh} = $batoptsocwh; # optimaler SoC in Wh $hsurp->{$fd}{$hod}{$bn}{csocwh} = $csocwh; # aktueller SoC in Wh $hsurp->{$fd}{$hod}{$bn}{otpMargin} = $otpMargin; # Sicherheitszuschlag für Berechnungen @@ -12280,14 +12289,15 @@ sub __batChargeOptTargetPower { } 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 $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 $barrierSoCWh = $hsurp->{$hod}{$sbn}{barrierSoCWh}; # Barriere 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 # Initialisierung / Fortschreibung Prognose-SOC (Wh) ###################################################### @@ -12329,8 +12339,12 @@ sub __batChargeOptTargetPower { if ($nhr eq '00') { $diff = $diff / 60 * (60 - int $minute); # aktuelle (Rest)-Stunde -> zeitgewichteter Ladungsabfluß - $otp->{$sbn}{target} = $csocwh <= $lowSocwh ? $bpinreduced : $bpinmax; $otp->{$sbn}{ratio} = 0; + $otp->{$sbn}{target} = $csocwh <= $lowSocwh + ? $bpinreduced + : $csocwh <= $barrierSoCWh + ? $bpinmax + : $bpinmax; } $runwh += $diff / $befficiency; # um Verbrauch reduzieren @@ -12341,21 +12355,24 @@ sub __batChargeOptTargetPower { next; } - + ## weiter mit Überschuß ######################### my $otpMargin = $hsurp->{$hod}{$sbn}{otpMargin}; - my $fref = ___batFindMinPhWh ( $hsurp, - \@remaining_hods, - $remainingSurp, - $runwhneed, - $replacement, - $achievable + my $fref = ___batFindMinPhWh ( { hsurp => $hsurp, + hodsref => \@remaining_hods, + remainingSurp => $remainingSurp, + Ereq => $runwhneed, + replacement => $replacement, + achievable => $achievable, + befficiency => $befficiency + } ); my $limpower = $strategy eq 'optPower' ? min ($fref->{ph}, $spls) # Ladeleistung auf den kleineren Wert begrenzen (es kommen Nachberechnungen) : $fref->{ph}; - + + $limpower = $limpower // 0 > 0 ? $limpower / $befficiency : 0; # Zielleistung mit Batterie Effizienzgrad erhöhen $limpower = $bpinmax if(!$hsurp->{$hod}{$sbn}{lcintime}); $limpower = max ($limpower, $bpinreduced); # Mindestladeleistung bpinreduced sicherstellen @@ -12366,8 +12383,7 @@ sub __batChargeOptTargetPower { my $pneedmin = $limpower * (1 + $otpMargin / 100); # optPower: Sicherheitsaufschlag if ($strategy eq 'smartPower') { - ($pneedmin) = ___batAdjustPowerByMargin ($name, # smartPower: Sicherheitsaufschlag abfallend proportional zum linearen Überschuss - $limpower, + ($pneedmin) = ___batAdjustPowerByMargin ($limpower, # smartPower: Sicherheitsaufschlag abfallend proportional zum linearen Überschuss $bpinmax, $runwhneed, $otpMargin, @@ -12377,14 +12393,15 @@ sub __batChargeOptTargetPower { $pneedmin = sprintf "%.0f", $pneedmin; $pneedmin = min ($pneedmin, $bpinmax); # Begrenzung auf max. mögliche Batterieladeleistung + $pneedmin = max ($pneedmin, 0); - $hsurp->{$hod}{$sbn}{pneedmin} = $pneedmin > 0 ? $pneedmin : 0; # Ladeleistung abhängig von Ziel-SoC Erfüllung + $hsurp->{$hod}{$sbn}{pneedmin} = $pneedmin; ## NextHour 00 (aktuelle Stunde) behandeln ############################################ if ($nhr eq '00') { - my $target = $limpower > 0 ? $limpower / $befficiency : 0; # Zielleistung mit Batterie Effizienzgrad erhöhen - + my $target = $limpower; + if ($achievable) { # Tagesziel erreichbar: Basisziel um otpMargin% erhöhen $target *= 1 + ($otpMargin / 100); # optPower: Sicherheitsaufschlag } @@ -12393,8 +12410,7 @@ sub __batChargeOptTargetPower { } if ($strategy eq 'smartPower') { # smartPower: Sicherheitsaufschlag linear absenkend - ($target, $ratio) = ___batAdjustPowerByMargin ($name, # smartPower: agressivere Ladeleistung, Sicherheitsaufschlag abfallend proportional zum linearen Überschuss - $limpower, + ($target, $ratio) = ___batAdjustPowerByMargin ($limpower, # smartPower: agressivere Ladeleistung, Sicherheitsaufschlag abfallend proportional zum linearen Überschuss $bpinmax, $runwhneed, $otpMargin, @@ -12410,17 +12426,24 @@ sub __batChargeOptTargetPower { if ( !$bpin && $gfeedin > $fipl ) {$inc = $gfeedin - $fipl} # Ladeleistung wenn akt. keine Bat-Ladung UND akt. Einspeisung > Einspeiselimit der Anlage if ( $bpin && ($gfeedin - $bpin) > $fipl ) {$inc = $bpin + (($gfeedin - $bpin) - $fipl)} # Ladeleistung wenn akt. Bat-Ladung UND Einspeisung - Bat-Ladung > Einspeiselimit der Anlage - - $target = sprintf "%.0f", max ($target, $inc); # Einspeiselimit berücksichtigen - $target = min (($csocwh <= $lowSocwh ? $bpinreduced : $bpinmax), $target); # 2. Begrenzung auf max. mögliche Batterieleistung bzw. bpinreduced bei Unterschreitung lowSoc + + my $lowph = $csocwh <= $lowSocwh + ? $bpinreduced + : $bpinmax; + + $target = max ($target, $inc); # Einspeiselimit berücksichtigen + $target = min ($target, $lowph); # Begrenzung auf diverse Limits + $target = sprintf "%.0f", $target; + $otp->{$sbn}{target} = $target; } - $diff = min ($spls, $hsurp->{$hod}{$sbn}{pneedmin}); # kleinster Wert aus PV-Überschuß oder Ladeleistungsbegrenzung + ## Fortschreibung + ################### + if ($nhr eq '00') { $diff = $otp->{$sbn}{target} / 60 * (60 - int $minute) } # aktuelle (Rest)-Stunde -> zeitgewichteter Ladungszufluß + else { $diff = min ($spls, $hsurp->{$hod}{$sbn}{pneedmin}) } # kleinster Wert aus PV-Überschuß oder Ladeleistungsbegrenzung - if ($nhr eq '00') { # aktuelle (Rest)-Stunde -> zeitgewichteter Ladungszufluß - $diff = $spls / 60 * (60 - int $minute); - } + $diff = sprintf "%.0f", $diff; $runwh = min ($goalwh, $runwh + $diff * $befficiency); # Endwert Prognose $runwh = ___batClampValue ($runwh, $lowSocwh, $batoptsocwh, $batinstcap); # runwh begrenzen @@ -12443,23 +12466,22 @@ return ($hsurp, $otp); # Forum: https://forum.fhem.de/index.php?msg=1349579 ################################################################ sub ___batAdjustPowerByMargin { - my ($name, $limpower, $pinmax, $whneed, $otpMargin, $remainingSurp) = @_; + my ($limpower, $pinmax, $whneed, $otpMargin, $remainingSurp) = @_; my $pow; my $ratio = 0; - $ratio = $remainingSurp * 100 / $whneed if($whneed); - return ($pinmax, $ratio) if($limpower == $pinmax); - return ($limpower * (1 + $otpMargin / 100), $ratio) if($limpower == 0 || !$otpMargin || $ratio >= 100 + $otpMargin); + return ($limpower, $ratio) if(!defined $whneed || $whneed <= 0); + + $ratio = $remainingSurp * 100 / $whneed; + $limpower = min ($limpower, $pinmax); # limpower !> pinmax um invertierte Interpolation zu vermeiden + + if ($limpower <= 0 || !$otpMargin) {$pow = $limpower} + elsif ($limpower == $pinmax || $ratio <= 100) {$pow = $pinmax} + elsif ($ratio >= 100 + $otpMargin) {$pow = $limpower} + else {$pow = $pinmax - ($pinmax - $limpower) * ($ratio - 100) / $otpMargin} - if ($ratio <= 100) { - $pow = $pinmax; - } - else { - $pow = $pinmax - ($pinmax - $limpower) * ($ratio - 100) / $otpMargin; - } - -return ($pow, $ratio); +return ($pow, $ratio); } ################################################################ @@ -12510,9 +12532,17 @@ return $value; # - Rückgabe Nach X Iterationen steht $high als kleinstmöglicher Wert für ph bereit. Er # garantiert entweder das Erreichen von Ereq Wh oder – falls das Ziel unerreichbar war – # die vollständige Ausnutzung der vorhandenen Kapazität. -############################################################################################### +############################################################################################### sub ___batFindMinPhWh { - my ($hsurp, $hodsref, $remainingSurp, $Ereq, $replacement, $achievable) = @_; + my $paref = shift; + + my $hsurp = $paref->{hsurp}; + my $hodsref = $paref->{hodsref}; + my $remainingSurp = $paref->{remainingSurp}; + my $Ereq = $paref->{Ereq}; + my $replacement = $paref->{replacement}; + my $achievable = $paref->{achievable}; + my $befficiency = $paref->{befficiency}; my @hods = @$hodsref; my $low = 0; @@ -12531,7 +12561,7 @@ sub ___batFindMinPhWh { } while (($high - $low) > $eps) { - last if ++$loop > $max_iter; + last if(++$loop > $max_iter); my $mid = ($low + $high) / 2; my $charged = 0; @@ -12540,6 +12570,7 @@ sub ___batFindMinPhWh { my $nhr = $hsurp->{$hod}{nhr}; next if(!defined $nhr); my $cap = $nhr eq '00' ? int $replacement : $hsurp->{$hod}{surplswh}; + $cap *= $befficiency; $charged += min ($mid, $cap); }