diff --git a/fhem/CHANGED b/fhem/CHANGED index 55f01e98f..8795d71f6 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.58.0 - bugfix: 72_FRITZBOX: Fehler bei set phoneBookEntry - feature: 57_Calendar: new attribute userAgent (forum #142335) - bugfix: 72_FRITZBOX: kleinere Fehler (erm upnp) diff --git a/fhem/FHEM/76_SolarForecast.pm b/fhem/FHEM/76_SolarForecast.pm index eb8a436d2..c2cce9b9d 100644 --- a/fhem/FHEM/76_SolarForecast.pm +++ b/fhem/FHEM/76_SolarForecast.pm @@ -160,6 +160,9 @@ BEGIN { # Versions History intern my %vNotesIntern = ( + "1.58.0" => "06.09.2025 _batChargeMgmt: Code change and new loading feature with Reading Battery_ChargeOptTargetPower_XX ". + "ctrlBatSocManagementXX: new parameter safetyMargin ". + "edit Comref, delete obsolete Attr graphicBeamHeightLevelX, new parameter setupBatteryDevXX->pinreduced ", "1.57.3" => "26.08.2025 set default Performance Ratio PRDEF to 0.9, prevent crash when Victron API does not return an Array ". "check global attribute dnsServer in all SF Models, expand plantControl->genPVdeviation for perspective change ". "Household consumption calculation uniformly converted to vector calculation ". @@ -452,6 +455,8 @@ use constant { APITIMEOUT => 30, # default Timeout HTTP API-Call PRDEF => 0.9, # default Performance Ratio (PR) + SFTYMARGIN_20 => 20, # Sicherheitszuschlag 20% + SFTYMARGIN_50 => 50, # Sicherheitszuschlag 50% STOREFFDEF => 0.90, # default Batterie Effizienz (https://www.energie-experten.org/erneuerbare-energien/photovoltaik/stromspeicher/wirkungsgrad) TEMPCOEFFDEF => -0.45, # default Temperaturkoeffizient Pmpp (%/°C) lt. Datenblatt Solarzelle TEMPMODINC => 25, # default Temperaturerhöhung an Solarzellen gegenüber Umgebungstemperatur bei wolkenlosem Himmel @@ -1699,10 +1704,10 @@ sub Initialize { ### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !! ########################################################################################################################## - my $av = 'obsolete#-#use#attr#graphicControl#instead'; + # my $av = 'obsolete#-#use#attr#graphicControl#instead'; # my $av1 = 'obsolete#-#will#be#deleted#soon'; # my $av2 = 'obsolete#-#use#attr#graphicSelect#instead'; - $hash->{AttrList} .= " graphicBeamHeightLevel1:$av graphicBeamHeightLevel2:$av graphicBeamHeightLevel3:$av "; + # $hash->{AttrList} .= " graphicBeamHeightLevel1:$av graphicBeamHeightLevel2:$av graphicBeamHeightLevel3:$av "; ########################################################################################################################## $hash->{FW_hideDisplayName} = 1; # Forum 88667 @@ -7372,18 +7377,19 @@ sub _attrBatteryDev { ## no critic "not used" my $bn = (split 'setupBatteryDev', $aName)[1]; my $valid = { - 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 }, - label => { comp => '(none|below|beside)', must => 0, act => 0 }, - asynchron => { comp => '(0|1)', must => 0, act => 0 }, + pin => { comp => '.+', must => 1, act => 0 }, + pout => { comp => '.+', must => 1, act => 0 }, + pinmax => { comp => '\d+', must => 0, act => 0 }, + pinreduced => { 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 }, + label => { comp => '(none|below|beside)', must => 0, act => 0 }, + asynchron => { comp => '(0|1)', must => 0, act => 0 }, }; if ($paref->{cmd} eq 'set') { @@ -7439,12 +7445,14 @@ sub _attrBatteryDev { ## no critic "not used" delete $data{$name}{batteries}{$bn}{bposingraph}; delete $data{$name}{batteries}{$bn}{blabel}; delete $data{$name}{batteries}{$bn}{bpinmax}; + delete $data{$name}{batteries}{$bn}{bpinreduced}; delete $data{$name}{batteries}{$bn}{bpoutmax}; } elsif ($paref->{cmd} eq 'del') { readingsDelete ($hash, 'Current_PowerBatIn_'.$bn); readingsDelete ($hash, 'Current_PowerBatOut_'.$bn); readingsDelete ($hash, 'Current_BatCharge_'.$bn); + readingsDelete ($hash, 'Battery_ChargeOptTargetPower_'.$bn); readingsDelete ($hash, 'Battery_ChargeRecommended_'.$bn); readingsDelete ($hash, 'Battery_ChargeUnrestricted_'.$bn); readingsDelete ($hash, 'Battery_ChargeRequest_'.$bn); @@ -7490,13 +7498,14 @@ sub _attrBatSocManagement { ## no critic "not used" 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 }, - careCycle => { comp => '\d+', must => 0, act => 0 }, - loadAbort => { comp => '(?:100|[1-9]?[0-9]):\d+(?::(?:100|[1-9]?[0-9]))?', must => 0, act => 0 }, + 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 }, + 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)', must => 0, act => 0 }, }; my ($a, $h) = parseParams ($aVal); @@ -8883,25 +8892,7 @@ sub centralTask { # my $newval = $gco." beamWidth=$gbw"; # CommandAttr (undef, "$name graphicControl $newval"); # ::CommandDeleteAttr (undef, "$name graphicBeamWidth"); - #} - - my $gco = AttrVal ($name, 'graphicControl', ''); - my $hgt1 = AttrNum ($name, 'graphicBeamHeightLevel1', undef); # 02.08. - my $hgt2 = AttrNum ($name, 'graphicBeamHeightLevel2', undef); - my $hgt3 = AttrNum ($name, 'graphicBeamHeightLevel3', undef); - - my $hgt = $hgt1 ? '1:'.$hgt1 : ''; - $hgt .= $hgt2 ? ($hgt ? ',' : '').'2:'.$hgt2 : ''; - $hgt .= $hgt3 ? ($hgt ? ',' : '').'3:'.$hgt3 : ''; - - if ($hgt) { - my $newval = $gco." beamHeightlevel=$hgt"; - CommandAttr (undef, "$name graphicControl $newval"); - ::CommandDeleteAttr (undef, "$name graphicBeamHeightLevel1"); - ::CommandDeleteAttr (undef, "$name graphicBeamHeightLevel2"); - ::CommandDeleteAttr (undef, "$name graphicBeamHeightLevel3"); - } - + #} for my $c (1..MAXCONSUMER) { # 23.07. $c = sprintf "%02d", $c; @@ -11100,8 +11091,9 @@ sub _transferBatteryValues { my ($bout,$boutunit) = split ":", $h->{outtotal} // "-:-"; # Readingname/Unit der total aus der Batterie entnommenen Energie (Zähler) my $batchr = $h->{charge} // ''; # Readingname Ladezustand Batterie my $instcap = $h->{cap}; # numerischer Wert (Wh) oder Readingname installierte Batteriekapazität - my $pinmax = $h->{pinmax} // INFINITE; # max. mögliche Ladeleistung - my $poutmax = $h->{poutmax} // INFINITE; # max. mögliche Entladeleistung + my $pinmax = $h->{pinmax} // INFINITE; # max. mögliche Ladeleistung + my $pinreduced = $h->{pinreduced} // $pinmax; # reduzierte Ladeleistung (z.B. bei Ladung aus dem Grid) + my $poutmax = $h->{poutmax} // INFINITE; # max. mögliche Entladeleistung next if(!$pin || !$pou); @@ -11279,6 +11271,7 @@ sub _transferBatteryValues { $data{$name}{batteries}{$bn}{bpowerin} = $pbi; # momentane Batterieladung $data{$name}{batteries}{$bn}{bpowerout} = $pbo; # momentane Batterieentladung $data{$name}{batteries}{$bn}{bpinmax} = $pinmax; # max. mögliche Ladeleistung + $data{$name}{batteries}{$bn}{bpinreduced} = $pinreduced; # # reduzierte Ladeleistung (z.B. bei Ladung aus dem Grid) $data{$name}{batteries}{$bn}{bpoutmax} = $poutmax; # max. mögliche Entladeleistung $data{$name}{batteries}{$bn}{bcharge} = $soc; # Batterie SoC (%) $data{$name}{batteries}{$bn}{basynchron} = $h->{asynchron} // 0; # asynchroner Modus = X @@ -11519,12 +11512,13 @@ sub __parseAttrBatSoc { my ($pa,$ph) = parseParams ($cgbt); my $parsed = { - lowSoc => $ph->{lowSoc}, - upSoc => $ph->{upSoC}, - maxSoc => $ph->{maxSoC} // MAXSOCDEF, # optional (default: MAXSOCDEF) - careCycle => $ph->{careCycle} // CARECYCLEDEF, # Ladungszyklus (Maintenance) für maxSoC in Tagen - lcslot => $ph->{lcSlot}, - loadAbort => $ph->{loadAbort}, + lowSoc => $ph->{lowSoc}, + upSoc => $ph->{upSoC}, + maxSoc => $ph->{maxSoC} // MAXSOCDEF, # optional (default: MAXSOCDEF) + careCycle => $ph->{careCycle} // CARECYCLEDEF, # Ladungszyklus (Maintenance) für maxSoC in Tagen + lcslot => $ph->{lcSlot}, + loadAbort => $ph->{loadAbort}, + safetyMargin => $ph->{safetyMargin}, }; return $parsed; @@ -11600,14 +11594,15 @@ sub _batChargeMgmt { my $aplim = $icap * $limit / 100; $inplim += $aplim; # max. Leistung aller WR mit Berücksichtigung Wirkleistungsbegrenzung - debugLog ($paref, 'batteryManagement', "Bat XX Charge Rcmd - Inverter '$iname' cap: $icap W, Power limit: $limit % -> Pmax eff: $aplim W"); + debugLog ($paref, 'batteryManagement', "Bat XX ChargeMgmt - Inverter '$iname' cap: $icap W, Power limit: $limit % -> Pmax eff: $aplim W"); } - debugLog ($paref, 'batteryManagement', "Bat XX Charge Rcmd - Summary Power limit of all Inverter (except feed 'grid'): $inplim W"); + debugLog ($paref, 'batteryManagement', "Bat XX ChargeMgmt - Summary Power limit of all Inverter (except feed 'grid'): $inplim W"); ## Schleife über alle Batterien ################################# my %hsoc; # Hilfshash + my $hsurp = {}; # Hashreferenz Überschuß for my $bn (1..MAXBATTERIES) { # für jede Batterie $bn = sprintf "%02d", $bn; @@ -11636,14 +11631,17 @@ sub _batChargeMgmt { my $sf = __batCapShareFactor ($hash, $bn); # Anteilsfaktor der Batterie XX Kapazität an Gesamtkapazität my $lowSoc = 0; my $loadAbort = ''; - my $lcslot; + my ($lcslot, $safetyMargin); if ($cgbt) { - my $parsed = __parseAttrBatSoc ($name, $cgbt); - $lowSoc = $parsed->{lowSoc} // 0; - $lcslot = $parsed->{lcslot}; - $loadAbort = $parsed->{loadAbort}; + my $parsed = __parseAttrBatSoc ($name, $cgbt); + $lowSoc = $parsed->{lowSoc} // 0; + $lcslot = $parsed->{lcslot}; + $loadAbort = $parsed->{loadAbort}; + $safetyMargin = $parsed->{safetyMargin}; } + + my $margin = defined $safetyMargin ? $safetyMargin : SFTYMARGIN_50; # Sicherheitszuschlag (%) ## generelle Ladeabbruchbedingung evaluieren ############################################## @@ -11665,50 +11663,57 @@ sub _batChargeMgmt { } my $labortCond = BatteryVal ($name, $bn, 'bloadAbortCond', 0); # Ladeabbruchbedingung gesetzt 1 oder nicht 0 - - debugLog ($paref, 'batteryManagement', "Bat $bn Charge Rcmd - General load termination condition: $labortCond"); - + ## Zeitfenster für aktives Lademanagement ermitteln ##################################################### $lcslot //= '00:00-23:59'; my ($lcstart, $lcend) = split "-", $lcslot; + + 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 $whneed = $batinstcap - $socwh; - 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; + # Debug Log + ############# + 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 - 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 ChargeUR - used safety margin: $margin %"); + } ## Auswertung für jede kommende Stunde ######################################## - for my $num (0..71) { + for my $num (0..MAXNEXTHOURS) { my ($fd, $fh) = calcDayHourMove ($chour, $num); next if($fd > 2); my $nhr = sprintf "%02d", $num; - my $today = NexthoursVal ($name, 'NextHour'.$nhr, 'today', 0); - my $hod = NexthoursVal ($name, 'NextHour'.$nhr, 'hourofday', ''); - my $confc = NexthoursVal ($name, 'NextHour'.$nhr, 'confc', 0); - my $pvfc = NexthoursVal ($name, 'NextHour'.$nhr, 'pvfc', 0); - my $nhstt = NexthoursVal ($name, 'NextHour'.$nhr, 'starttime', ''); - my $stt = (split /[-:]/, $nhstt)[2] if($nhstt); + my $hod = NexthoursVal ($name, 'NextHour'.$nhr, 'hourofday', undef); + my $nhstt = NexthoursVal ($name, 'NextHour'.$nhr, 'starttime', undef); + + next if(!defined ($hod) || !defined ($nhstt)); + + my $today = NexthoursVal ($name, 'NextHour'.$nhr, 'today', 0); + my $confc = NexthoursVal ($name, 'NextHour'.$nhr, 'confc', 0); + my $pvfc = NexthoursVal ($name, 'NextHour'.$nhr, 'pvfc', 0); + + if ($fd == 2 && $fh == 0) { + $tompvfc = CurrentVal ($name, 'dayAfterTomorrowPVfc', 0); # PV Prognose übernächster Tag + $tomconfc = CurrentVal ($name, 'dayAfterTomorrowConfc', 0); # Verbrauchsprognose übernächster Tag + } ## 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 ($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; @@ -11723,35 +11728,32 @@ sub _batChargeMgmt { $tompvfc = sprintf "%.0f", ($sf * $tompvfc); ## (Rest) PV-Überschuß für den Tag - #################################### - if ($pvfc) { - if ($today) { # heutiger Tag - $confcss -= $confc; # Verbrauch bis Sonnenuntergang - Verbrauch Fc aktuelle Stunde - $confcss = 0 if($confcss < 0); - $rodpvfc -= $pvfc; - $rodpvfc = 0 if($rodpvfc < 0); - $spday = $rodpvfc - $confcss; # PV-Überschußprognose (Rest) heutiger Tag - } - else { # nächster Tag - $tomconfc -= $confc; - $tomconfc = 0 if($tomconfc < 0); - $tompvfc -= $pvfc; - $spday = $tompvfc - $tomconfc; - } + #################################### + if ($today) { # heutiger Tag + $confcss -= $confc; # Verbrauch bis Sonnenuntergang - Verbrauch Fc aktuelle Stunde + $confcss = 0 if($confcss < 0); + $rodpvfc -= $pvfc; + $rodpvfc = 0 if($rodpvfc < 0); + $spday = $rodpvfc - $confcss; # PV-Überschußprognose (Rest) heutiger Tag + } + else { # nächster Tag + $tomconfc -= $confc; + $tomconfc = 0 if($tomconfc < 0); + $tompvfc -= $pvfc; + $spday = $tompvfc - $tomconfc; } - $spday = 0 if($spday < 0); # PV Überschuß Prognose bis Sonnenuntergang - my $sfmargin = $whneed * 0.5; # Sicherheitszuschlag: X% der benötigten Ladeenergie (Wh) - + $spday = 0 if($spday < 0); # PV Überschuß Prognose bis Sonnenuntergang + ## Ladefreigabe ################# - if ( $whneed + $sfmargin >= $spday ) {$crel = 1} # Ladefreigabe wenn benötigte Ladeenergie >= Restüberschuß des Tages zzgl. Sicherheitsaufschlag - 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 ( $labortCond ) {$crel = 0} # keine Ladefreigabe bei genereller Abbruchbedingung + if ( $whneed * (1 + ($margin / 100)) >= $spday ) {$crel = 1} # Ladefreigabe wenn benötigte Ladeenergie >= Restüberschuß des Tages zzgl. Sicherheitsaufschlag + 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 ( $labortCond ) {$crel = 0} # keine Ladefreigabe bei genereller Abbruchbedingung ## SOC-Prognose ################# # change V 1.47.0 @@ -11763,8 +11765,6 @@ sub _batChargeMgmt { $socwh += $crel ? ($fceff > 0 ? $fceff * STOREFFDEF : $fceff / STOREFFDEF) : ($fceff > 0 ? 0 : $fceff / STOREFFDEF); # PV Überschuß (d.h. Aufladung) nur einbeziehen wenn Ladefreigabe - # debugLog ($paref, 'batteryManagement', "Bat $bn Charge - crel: $crel, fceff: $fceff, socwh: $socwh"); - $socwh = $socwh < $lowSocwh ? $lowSocwh : $socwh < $batoptsocwh ? $batoptsocwh : # SoC Prognose in Wh $socwh > $batinstcap ? $batinstcap : @@ -11786,7 +11786,7 @@ sub _batChargeMgmt { $msg = "SoCfc: $progsoc % / $socwh Wh, whneed: $whneed, pvfc: $pvfc, rodpvfc: $rodpvfc, confcss: $confcss, SurpDay: $spday Wh, inTime: ".($cgbt ? $lcintime : '-'); if (!$today) { - $msg = "SoCfc: $progsoc % / $socwh Wh, whneed: $whneed, pvfc: $pvfc, tompvfc: $tompvfc, tomconfc: $tomconfc, SurpDay: $spday Wh, inTime: ".($cgbt ? $lcintime : '-'); + $msg = "SoCfc: $progsoc % / $socwh Wh, whneed: $whneed, pvfc: $pvfc, roTomPV: $tompvfc, roTomCON: $tomconfc, SurpDay: $spday Wh, inTime: ".($cgbt ? $lcintime : '-'); } } else { @@ -11802,16 +11802,40 @@ sub _batChargeMgmt { $data{$name}{nexthours}{'NextHour'.$nhr}{'lcintimebat'.$bn} = $lcintime if($cgbt); # Ladesteuerung "In Time", "nicht In Time" oder nicht verwendet $hsoc{$nhr}{socprogwhsum} += $socwh; # Hilfshash Aufsummierung SoC-Prognose (Wh) über alle Batterien + # Überschußhash für Ermittlung Mindest-Ladeleistung erstellen + ############################################################### + my $spswh = max (0, sprintf ("%.0f", $fceff)); + + if ($today) { # nur Heute wenn Überschuß vorliegt + $hsurp->{$hod}{hod} = $hod; + $hsurp->{$hod}{nhr} = $nhr; + $hsurp->{$hod}{fceff} = $fceff; # Überschuß in Wh der Stunde + $hsurp->{$hod}{spswh} = $spswh; + $hsurp->{$hod}{$bn}{spday} = $spday; # (Rest)PV-Überschuß am laufenden Tag + $hsurp->{$hod}{$bn}{whneedmanaged} = $whneed; # benötigte Ladeenergie Batterie x gemäß Ladesteuerung + $hsurp->{$hod}{$bn}{socwh} = $socwh; + $hsurp->{$hod}{$bn}{batinstcap} = $batinstcap; + $hsurp->{$hod}{$bn}{safetyMargin} = $safetyMargin; # Sicherheitszuschlag für Berechnungen + } + # prognostizierten Daten in pvHistory speichern ################################################# if ($today && $hod) { # heutiger Tag writeToHistory ( { paref => $paref, key => 'batprogsoc'.$bn, val => $progsoc, hour => $hod } ); writeToHistory ( { paref => $paref, key => 'lcintimebat'.$bn, val => $lcintime, hour => $hod } ) if($cgbt); } - - debugLog ($paref, 'batteryManagement', "Bat $bn relLoad $stt -> $crel ($msg)"); + + my $stt = (split /[-:]/, $nhstt)[2]; + $stt =~ s/\s/\//; + debugLog ($paref, 'batteryManagement', "Bat $bn ChargeUR $stt -> $crel ($msg)"); } } + + # Erstellung Mindest Ladeleistung + ################################### + $paref->{hsurp} = $hsurp; + __batChargeOptTargetPower ($paref); + delete $paref->{hsurp}; # prognostizierten SOC über alle Batterien speichern ###################################################### @@ -11853,6 +11877,79 @@ sub __createNextHoursSFCReadings { return; } +################################################################ +# Erstellung Optimum Ladeleistung für jede Batterie +# (Erreichung des max. möglichen SoC mit möglichst geringer +# Ladeleistung verteilt über die Tagstunden mit PV-Überschuß) +################################################################ +sub __batChargeOptTargetPower { + my $paref = shift; + my $name = $paref->{name}; + my $hsurp = $paref->{hsurp} // return; # Hashref Überschußhash + + my $fipl = CurrentVal ($name, 'feedinPowerLimit', INFINITE); + my @sorted = sort { $hsurp->{$a}{spswh} <=> $hsurp->{$b}{spswh} } keys %{$hsurp}; + my @batteries = grep { !/^(?:hod|spswh|fceff|nhr)$/xs } keys %{$hsurp->{24}}; + + for my $shod (@sorted) { + my $spls = 1 * $hsurp->{$shod}{spswh}; + + for my $sbn (sort @batteries) { # jede Batterie + my $runwh = defined $hsurp->{$shod}{$sbn}{fcnextwh} ? # Auswahl des zu verwenden Prognose-SOC (Wh) + $hsurp->{$shod}{$sbn}{fcnextwh} : + $hsurp->{$shod}{$sbn}{socwh}; + + my $bpinreduced = BatteryVal ($name, $sbn, 'bpinreduced', 0); # Standardwert wenn z.B. kein Überschuß oder Zwangsladung vom Grid + + if (!$spls) { # auf kleine Sollladeleistung setzen wenn kein Überschuß + $hsurp->{$shod}{$sbn}{pneedmin} = $bpinreduced; + storeReading ('Battery_ChargeOptTargetPower_'.$sbn, $bpinreduced.' W') if($hsurp->{$shod}{nhr} eq '00'); + next; + } + + my $sbatinstcap = $hsurp->{$shod}{$sbn}{batinstcap}; # Kapa dieser Batterie + my $runwhneed = $sbatinstcap - $runwh; + my $spday = $hsurp->{$shod}{$sbn}{spday}; + my $sphrs = $spday / $spls; # Reststunden mit Überschuß = PV-Tagesüberschuß / Stundenüberschuß + + my $needraw = $sphrs ? $runwhneed / $sphrs : $runwhneed; # Ladeleistung initial + + my $safetyMargin = $hsurp->{$shod}{$sbn}{safetyMargin}; + my $margin = defined $safetyMargin ? $safetyMargin : SFTYMARGIN_20; + $needraw *= 1 + ($margin / 100); # Sicherheitsaufschlag + + if ($spls - $needraw > $fipl) { # Einspeiselimit berücksichtigen + $needraw += ($spls - $needraw) - $fipl; + } + + $needraw = 0 if($needraw < 0); + + $hsurp->{$shod}{$sbn}{runwh} = $runwh; + $hsurp->{$shod}{$sbn}{pneedmin} = sprintf "%.0f", $spls > $needraw ? # Mindestladeleistung bzw. Energie bei 1h (Wh) + $needraw ? $needraw : $bpinreduced : + $spls; + + my $newshod = sprintf "%02d", (int $shod + 1); + $hsurp->{$newshod}{$sbn}{fcnextwh} = $runwh + $hsurp->{$shod}{$sbn}{pneedmin} if(defined $hsurp->{$newshod}); + + storeReading ('Battery_ChargeOptTargetPower_'.$sbn, $hsurp->{$shod}{$sbn}{pneedmin}.' W') if($hsurp->{$shod}{nhr} eq '00'); + } + } + + if ($paref->{debug} =~ /batteryManagement/) { + for my $k (sort { $a <=> $b } keys %{$hsurp}) { + for my $bat (sort @batteries) { + my $ssoc = $hsurp->{$k}{$bat}{runwh} // '-'; + my $safetyMargin = $hsurp->{$k}{$bat}{safetyMargin}; + my $margin = defined $safetyMargin ? $safetyMargin : SFTYMARGIN_20; + Log3 ($name, 1, "$name DEBUG> Bat $bat ChargeOTP - hod: $k, Start SoC: $ssoc Wh, Surplus: $hsurp->{$k}{spswh} Wh, OptTargetPower: $hsurp->{$k}{$bat}{pneedmin} W, safety: $margin %"); + } + } + } + +return; +} + ################################################################ # Zusammenfassungen erstellen ################################################################ @@ -11970,8 +12067,12 @@ sub _createSummaries { } } else { - $tomorrowSum->{PV} += $pvfc if(int($nhday) == int($tmoday)); - $daftertomSum->{PV} += $pvfc if(int($nhday) == int($datmoday)); + $tomorrowSum->{PV} += $pvfc if(int($nhday) == int($tmoday)); + + if (int($nhday) == int($datmoday)) { + $daftertomSum->{PV} += $pvfc; + $daftertomSum->{Consumption} += $confc; + } } } @@ -12061,13 +12162,14 @@ sub _createSummaries { $selfconsumptionrate = sprintf "%.0f", ($selfconsumption / $pv2node * 100) if($pv2node * 1 > 0); $autarkyrate = sprintf "%.0f", ($selfconsumption + $batout) / $divi * 100 if($divi); # vermeide Illegal division by zero - $data{$name}{current}{consumption} = $consumption; - $data{$name}{current}{selfconsumption} = $selfconsumption; - $data{$name}{current}{selfconsumptionrate} = $selfconsumptionrate; - $data{$name}{current}{autarkyrate} = $autarkyrate; - $data{$name}{current}{tdConFcTillSunset} = sprintf "%.0f", $tdConFcTillSunset; - $data{$name}{current}{surplus} = $surplus; - $data{$name}{current}{dayAfterTomorrowPVfc} = $daftertomSum->{PV}; + $data{$name}{current}{consumption} = $consumption; + $data{$name}{current}{selfconsumption} = $selfconsumption; + $data{$name}{current}{selfconsumptionrate} = $selfconsumptionrate; + $data{$name}{current}{autarkyrate} = $autarkyrate; + $data{$name}{current}{tdConFcTillSunset} = sprintf "%.0f", $tdConFcTillSunset; + $data{$name}{current}{surplus} = $surplus; + $data{$name}{current}{dayAfterTomorrowPVfc} = $daftertomSum->{PV}; + $data{$name}{current}{dayAfterTomorrowConfc} = $daftertomSum->{Consumption}; push @{$data{$name}{current}{surplusslidereg}}, $surplus; # Schieberegister PV Überschuß limitArray ($data{$name}{current}{surplusslidereg}, SPLSLIDEMAX); @@ -26670,14 +26772,21 @@ to ensure that the system configuration is correct.
-
  • ctrlBatSocManagementXX lowSoc=<Value> upSoC=<Value> [maxSoC=<Value>] [careCycle=<Value>] [lcSlot=<hh:mm>-<hh:mm>] [loadAbort=<SoC1>:<MinPwr>:<SoC2>]

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

    + + If a battery device (setupBatteryDevXX) is installed, this attribute activates the battery SoC and charge management + for this battery device.
    + A set of control readings is generated; the module itself does not interfere with battery control.
    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 reading Battery_ChargeUnrestricted_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 reading Battery_ChargeUnrestricted_XX indicates whether the battery should be charged at full power without + restriction (1), or not at all, or only when the
    + feed-in limit (see plantControl->feedinPowerLimit) is exceeded (0). + If you want to charge the battery continuously throughout the day, Reading + Battery_ChargeOptTargetPower_XX provides optimized charging power for battery control.
    The readings can be used to control the SoC (State of Charge) and to control the charging power used for the battery.
    Detailed information on battery SoC and charging management is described in the @@ -26686,30 +26795,35 @@ to ensure that the system configuration is correct.
    @@ -26717,7 +26831,7 @@ to ensure that the system configuration is correct. All SoC values are whole numbers in %. The following applies: 'lowSoc' < 'upSoC' < 'maxSoC'.

    Example:
    - attr <name> ctrlBatSocManagement01 lowSoc=10 upSoC=50 maxSoC=99 careCycle=25 lcSlot=11:00-17:30 loadAbort=99:40:90
    + attr <name> ctrlBatSocManagement01 lowSoc=10 upSoC=50 maxSoC=99 careCycle=25 lcSlot=11:00-17:30 loadAbort=99:40:90 safetyMargin=30

  • @@ -27141,13 +27255,13 @@ to ensure that the system configuration is correct. current&nbsp;Gridconsumption:Current_GridConsumption : # - CO&nbsp;until&nbsp;sunset:special_todayConForecastTillSunset - PV&nbsp;Day&nbsp;after&nbsp;tomorrow:special_dayAfterTomorrowPVforecast + CO&nbsp;until&nbsp;sunset:special_todayConForecastTillSunset + PV&nbsp;Day&nbsp;after&nbsp;tomorrow:special_dayAfterTomorrowPVforecast : : #Battery - in&nbsp;today:special_todayBatIn - out&nbsp;today:special_todayBatOut + in&nbsp;today:special_todayBatIn + out&nbsp;today:special_todayBatOut : : #Settings @@ -27383,8 +27497,8 @@ to ensure that the system configuration is correct.
  • setupBatteryDevXX <Battery Device Name> pin=<Readingname>:<Unit> pout=<Readingname>:<Unit> cap=<Option>
    - [pinmax=<Integer>] [poutmax=<Integer>] [intotal=<Readingname>:<Unit>] [outtotal=<Readingname>:<Unit>] - [charge=<Readingname>] [asynchron=<Option>] [show=<Option>]
    + [pinmax=<Integer>] [pinreduced=<Integer>] [poutmax=<Integer>] [intotal=<Readingname>:<Unit>]
    + [outtotal=<Readingname>:<Unit>] [charge=<Readingname>] [asynchron=<Option>] [show=<Option>]
    [label=<Option>] [[icon=<recomm>@<Color>]:[<charge>@<Color>]:[<discharge>@<Color>]:[<omit>@<Color>]]


    Specifies an arbitrary Device and its Readings to deliver the battery performance data.
    @@ -27401,6 +27515,10 @@ to ensure that the system configuration is correct. pinmax the maximum possible charging power in watts (optional) + pinreducedThe reduced charging power in watts (optional). The value is set in Reading Battery_ChargeOptTargetPower_XX + if there is no PV surplus available to adjust the optimal charging power. This means that this + value can also be applied in the case of forced charging from the public grid. + poutmax the maximum possible discharge power in watts (optional) intotal Reading which provides the total battery charge as a continuous counter (optional) @@ -29328,15 +29446,22 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
    -
  • ctrlBatSocManagementXX lowSoc=<Wert> upSoC=<Wert> [maxSoC=<Wert>] [careCycle=<Wert>] [lcSlot=<hh:mm>-<hh:mm>] [loadAbort=<SoC1>:<MinPwr>:<SoC2>]

    +
  • ctrlBatSocManagementXX lowSoc=<Wert> upSoC=<Wert> [maxSoC=<Wert>] [careCycle=<Wert>] + [lcSlot=<hh:mm>-<hh:mm>] [loadAbort=<SoC1>:<MinPwr>:<SoC2>] + [safetyMargin=<Wert>]

    + Sofern ein Batterie Device (setupBatteryDevXX) installiert ist, aktiviert dieses Attribut das Batterie SoC- und Lade-Management für dieses Batteriegerät.
    + Es wird ein Satz Steuerreadings erstellt; das Modul greift selbst nicht in die Batteriesteuerung ein.
    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.
    - Das Reading Battery_ChargeUnrestricted_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).
    + Das Reading Battery_ChargeUnrestricted_XX gibt an, ob die Batterie uneingeschränkt mit voller Leistung (1), oder + nicht bzw. nur bei Überschreitung des
    + Einspeiselimits (siehe plantControl->feedinPowerLimit) + geladen werden sollte (0). Möchte man die Batterie kontinuierlich über den gesamten Tag aufladen, wird im Reading + Battery_ChargeOptTargetPower_XX eine optimierte Ladeleistung zur Batteriesteuerung bereitgestellt.
    Die Readings können zur Steuerung des SoC (State of Charge) sowie zur Steuerung des verwendeten Ladeleistung der Batterie verwendet werden.
    Detaillierte Informationen zum Batterie SoC- und Lade-Management sind im @@ -29345,30 +29470,35 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
    @@ -29376,7 +29506,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. Alle SoC-Werte sind ganze Zahlen in %. Dabei gilt: 'lowSoc' < 'upSoC' < 'maxSoC'.

    Beispiel:
    - attr <name> ctrlBatSocManagement01 lowSoc=10 upSoC=50 maxSoC=99 careCycle=25 lcSlot=11:00-17:30 loadAbort=99:40:90
    + attr <name> ctrlBatSocManagement01 lowSoc=10 upSoC=50 maxSoC=99 careCycle=25 lcSlot=11:00-17:30 loadAbort=99:40:90 safetyMargin=30

  • @@ -30040,8 +30170,8 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
  • setupBatteryDevXX <Batterie Device Name> pin=<Readingname>:<Einheit> pout=<Readingname>:<Einheit> cap=<Option>
    - [pinmax=<Ganzzahl>] [poutmax=<Ganzzahl>] [intotal=<Readingname>:<Einheit>] [outtotal=<Readingname>:<Einheit>] - [charge=<Readingname>] [asynchron=<Option>] [show=<Option>]
    + [pinmax=<Ganzzahl>] [pinreduced=<Ganzzahl>] [poutmax=<Ganzzahl>] [intotal=<Readingname>:<Einheit>]
    + [outtotal=<Readingname>:<Einheit>] [charge=<Readingname>] [asynchron=<Option>] [show=<Option>]
    [label=<Option>] [[icon=<empfohlen>@<Farbe>]:[<aufladen>@<Farbe>]:[<entladen>@<Farbe>]:[icon=<unterlassen>@<Farbe>]]


    Legt ein beliebiges Device und seine Readings zur Lieferung der Batterie Leistungsdaten fest.
    @@ -30058,6 +30188,10 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. pinmax die maximal mögliche Ladeleistung in Watt (optional) + pinreducedDie reduzierte Ladeleistung in Watt (optional). Der Wert wird im Reading Battery_ChargeOptTargetPower_XX + gesetzt wenn kein PV-Überschuß zur Anpassung der optimalen Ladeleistung vorhanden ist. Somit kann dieser + Wert auch im Fall der Zwangsbeladung aus dem öffentlichen Netz zur Anwendung kommen. + poutmax die maximal mögliche Entladeleistung in Watt (optional) intotal Reading welches die totale Batterieladung als fortlaufenden Zähler liefert (optional)