diff --git a/fhem/CHANGED b/fhem/CHANGED index 17568ebfc..2e01c7445 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: new special Reading BatRatio - bugfix: 76_SolarForecast: fix consumption till sunset,SOC use of consumption - feature: 76_SolarForecast: Version 1.60.5 - feature: 76_SolarForecast: Version 1.60.4 diff --git a/fhem/FHEM/76_SolarForecast.pm b/fhem/FHEM/76_SolarForecast.pm index 98a6f2604..9c20bd3fb 100644 --- a/fhem/FHEM/76_SolarForecast.pm +++ b/fhem/FHEM/76_SolarForecast.pm @@ -162,6 +162,7 @@ BEGIN { # Versions History intern my %vNotesIntern = ( + "1.60.7" => "21.11.2025 new special Reading BatRatio, minor code chenges ", "1.60.6" => "18.11.2025 _createSummaries: fix tdConFcTillSunset, _batSocTarget: apply 75% of tomorrow consumption ", "1.60.5" => "16.11.2025 ___csmSpecificEpieces: implement EPIECMAXOPHRS , ___batAdjustPowerByMargin: adjust pow with otpMargin ". "__solCast_ApiResponse: evaluation of httpheader ". @@ -1446,7 +1447,7 @@ my %hef = ( "noSchedule" => { f => 1.00, m => 1.00, l => 1.00, mt => DEFMINTIME }, ); -my %hcsr = ( # Funktiontemplate zur Erstellung optionaler Statistikreadings +my %hcsr = ( # Funktiontemplate zur Erstellung optionaler Statistikreadings currentAPIinterval => { fnr => 1, fn => \&StatusAPIVal, par => '', par1 => '', unit => '', def => 0 }, # par = Parameter zur spezifischen Verwendung lastretrieval_time => { fnr => 1, fn => \&StatusAPIVal, par => '', par1 => '', unit => '', def => '-' }, lastretrieval_timestamp => { fnr => 1, fn => \&StatusAPIVal, par => '', par1 => '', unit => '', def => '-' }, @@ -1468,6 +1469,7 @@ my %hcsr = ( BatPowerIn_Sum => { fnr => 5, fn => \&CurrentVal, par => 'batpowerinsum', par1 => '', unit => ' W', def => '-' }, BatPowerOut_Sum => { fnr => 5, fn => \&CurrentVal, par => 'batpoweroutsum', par1 => '', unit => ' W', def => '-' }, BatWeightedTotalSOC => { fnr => 2, fn => \&CurrentVal, par => 'batsoctotal', par1 => '', unit => ' %', def => 0 }, + BatRatio => { fnr => 5, fn => \&CurrentVal, par => 'batRatio', par1 => '', unit => '', def => '-' }, SunHours_Remain => { fnr => 5, fn => \&CurrentVal, par => '', par1 => '', unit => '', def => 0 }, # fnr => 3 -> Custom Calc SunMinutes_Remain => { fnr => 5, fn => \&CurrentVal, par => '', par1 => '', unit => '', def => 0 }, dayAfterTomorrowPVforecast => { fnr => 5, fn => \&CurrentVal, par => 'dayAfterTomorrowPVfc', par1 => '', unit => ' Wh', def => 0 }, @@ -1913,7 +1915,7 @@ sub Set { "plantConfiguration:check,save,restore ". "powerTrigger:textField-long ". "pvCorrectionFactor_Auto:noLearning,on_simple".($ipai ? ',on_simple_ai,' : ',')."on_complex".($ipai ? ',on_complex_ai,on_complex_api_ai,' : ',')."off ". - "reset:widgetList,$resetnum,select,$resets,2,textField,use only when arguments are needed ". + "reset:widgetList,$resetnum,select,$resets,2,textField,fill in only if arguments are needed ". $cf." " ; @@ -2572,29 +2574,29 @@ sub _setreset { ## no critic "not used" delete $data{$name}{pvhist}{$hid}{$circh}{pvcorrf}; } - Log3($name, 3, qq{$name - stored PV correction factor of hour "$circh" from pvCircular and pvHistory deleted}); - return; + Log3 ($name, 3, qq{$name - stored PV correction factor of hour "$circh" from pvCircular and pvHistory deleted}); } + else { + for my $hod (keys %{$data{$name}{circular}}) { + delete $data{$name}{circular}{$hod}{pvcorrf}; + delete $data{$name}{circular}{$hod}{quality}; + delete $data{$name}{circular}{$hod}{pvrlsum}; + delete $data{$name}{circular}{$hod}{pvfcsum}; + delete $data{$name}{circular}{$hod}{dnumsum}; - for my $hod (keys %{$data{$name}{circular}}) { - delete $data{$name}{circular}{$hod}{pvcorrf}; - delete $data{$name}{circular}{$hod}{quality}; - delete $data{$name}{circular}{$hod}{pvrlsum}; - delete $data{$name}{circular}{$hod}{pvfcsum}; - delete $data{$name}{circular}{$hod}{dnumsum}; + for my $k (keys %{$data{$name}{circular}{$hod}}) { + delete $data{$name}{circular}{$hod}{$k} if($k =~ /^(pvrl_|pvfc_)/xs); + } + } - for my $k (keys %{$data{$name}{circular}{$hod}}) { - delete $data{$name}{circular}{$hod}{$k} if($k =~ /^(pvrl_|pvfc_)/xs); - } - } + for my $hid (keys %{$data{$name}{pvhist}}) { + for my $hidh (keys %{$data{$name}{pvhist}{$hid}}) { + delete $data{$name}{pvhist}{$hid}{$hidh}{pvcorrf}; + } + } - for my $hid (keys %{$data{$name}{pvhist}}) { - for my $hidh (keys %{$data{$name}{pvhist}{$hid}}) { - delete $data{$name}{pvhist}{$hid}{$hidh}{pvcorrf}; - } - } - - Log3 ($name, 3, qq{$name - all stored PV correction factors from pvCircular and pvHistory deleted}); + Log3 ($name, 3, qq{$name - all stored PV correction factors from pvCircular and pvHistory deleted}); + } } return; @@ -7765,6 +7767,7 @@ sub _attrBatSocManagement { ## no critic "not used" } else { deleteReadingspec ($hash, 'Battery_.*'); + $data{$name}{current}{'batRatio'.$bn}; } delete $data{$name}{circular}{99}{'lastTsMaxSocRchd'.$bn}; @@ -9188,7 +9191,7 @@ sub centralTask { _calcConsForecast_circular ($centpars); # neue Verbrauchsprognose über pvCircular - _evaluateThresholds ($centpars); # Schwellenwerte bewerten und signalisieren + _evaluateTrigger ($centpars); # Schwellenwerte der Trigger bewerten und signalisieren _calcReadingsTomorrowPVFc ($centpars); # zusätzliche Readings Tomorrow_HourXX_PVforecast berechnen _calcTodayPVdeviation ($centpars); # Vorhersageabweichung erstellen (nach Sonnenuntergang) _calcDataEveryFullHour ($centpars); # Daten berechnen/speichern die nur einmal nach jeder vollen Stunde ermittelt werden @@ -11621,7 +11624,7 @@ sub _batSocTarget { Log3 ($name, 1, "$name DEBUG> SoC Step1 Bat $bn - basics -> Battery share factor of total required load: $sf"); Log3 ($name, 1, "$name DEBUG> SoC Step1 Bat $bn - basics -> today -> PV fc: $pvfctd Wh, con till sunset: $tdconsset Wh, Surp: $surptd Wh"); Log3 ($name, 1, "$name DEBUG> SoC Step1 Bat $bn - basics -> tomorrow -> PV fc: $pvfctm Wh, con till sunset: $constm Wh, Surp: $surptm Wh (".(PERCCONINSOC * 100)."% con)"); - Log3 ($name, 1, "$name DEBUG> SoC Step1 Bat $bn - basics -> selected energy for charging (the higher Surp value from above): $pvexpraw Wh"); + Log3 ($name, 1, "$name DEBUG> SoC Step1 Bat $bn - basics -> selected energy for charging (the higher positive Surp value from above): $pvexpraw Wh"); Log3 ($name, 1, "$name DEBUG> SoC Step1 Bat $bn - basics -> expected energy for charging after application Share factor: $pvexpect Wh"); Log3 ($name, 1, "$name DEBUG> SoC Step1 Bat $bn - compare with SoC history -> preliminary new Target: $target %"); } @@ -12397,7 +12400,14 @@ sub __batChargeOptTargetPower { $achievable = 0; } - storeReading ('Battery_TargetAchievable_'.$sbn, $achievable) if($nhr eq '00'); + if ($nhr eq '00') { + storeReading ('Battery_TargetAchievable_'.$sbn, $achievable); + $ratio = sprintf "%.2f", ___batRatio ($runwhneed, $remainingSurp, $befficiency); + + $data{$name}{current}{'batRatio'.$sbn} = $ratio; + $otp->{$sbn}{ratio} = $ratio; + $otp->{$sbn}{remainingSurp} = $remainingSurp; + } $hsurp->{$hod}{$sbn}{loadrel} = $runwhneed > 0 ? 1 : 0; # Ladefreigabe abhängig von Ziel-SoC Erfüllung $hsurp->{$hod}{$sbn}{achievelog} = "charging target: $goalwh Wh, E requirement incl. efficiency: ". @@ -12465,13 +12475,13 @@ sub __batChargeOptTargetPower { my $pneedmin = $limpower * (1 + $otpMargin / 100); # optPower: Sicherheitsaufschlag if ($strategy eq 'smartPower') { - ($pneedmin) = ___batAdjustPowerByMargin ($limpower, # smartPower: Sicherheitsaufschlag abfallend proportional zum linearen Überschuss - $bpinmax, - $runwhneed, - $otpMargin, - $remainingSurp, - $befficiency - ); + $pneedmin = ___batAdjustPowerByMargin ($limpower, # smartPower: Sicherheitsaufschlag abfallend proportional zum linearen Überschuss + $bpinmax, + $runwhneed, + $otpMargin, + $remainingSurp, + $befficiency + ); } $pneedmin = ___batApplySocAreas ( { name => $name, @@ -12502,16 +12512,13 @@ sub __batChargeOptTargetPower { } if ($strategy eq 'smartPower') { # smartPower: Sicherheitsaufschlag linear absenkend - ($target, $ratio) = ___batAdjustPowerByMargin ($limpower, # smartPower: agressivere Ladeleistung, Sicherheitsaufschlag abfallend proportional zum linearen Überschuss - $bpinmax, - $runwhneed, - $otpMargin, - $remainingSurp, - $befficiency - ); - - $otp->{$sbn}{ratio} = sprintf ("%.2f", $ratio); - $otp->{$sbn}{remainingSurp} = $remainingSurp; + $target = ___batAdjustPowerByMargin ($limpower, # smartPower: agressivere Ladeleistung, Sicherheitsaufschlag abfallend proportional zum linearen Überschuss + $bpinmax, + $runwhneed, + $otpMargin, + $remainingSurp, + $befficiency + ); } my $gfeedin = CurrentVal ($name, 'gridfeedin', 0); # aktuelle Netzeinspeisung @@ -12559,6 +12566,20 @@ sub __batChargeOptTargetPower { return ($hsurp, $otp); } +################################################################ +# Ratio zwischen PV-Überschuß und benötigter Ladeenergie +# berechnen +################################################################ +sub ___batRatio { + my ($rwh, $surp, $beff) = @_; + + return 0 if($surp <= 0.0 || !defined $rwh || $rwh <= 0.0); + + my $ratio = $surp * 100.0 * $beff / $rwh; # beff -> Batterie Effizienz als Anteil 0.1 .. 1 + +return $ratio; +} + ################################################################ # Verbleibende Aktivstunden und deren Überschußsumme liefern ################################################################ @@ -12590,12 +12611,10 @@ return ($remainingSurp, \@remaining_hods); sub ___batAdjustPowerByMargin { my ($limpower, $pinmax, $runwhneed, $otpMargin, $remainingSurp, $befficiency) = @_; + return $limpower if(!defined $runwhneed || $runwhneed <= 0); + my $pow; - my $ratio = 0; - - return ($limpower, $ratio) if(!defined $runwhneed || $runwhneed <= 0); - - $ratio = $remainingSurp * 100.0 * $befficiency / $runwhneed; # befficiency als Anteil 0.1 .. 1 + my $ratio = ___batRatio ($runwhneed, $remainingSurp, $befficiency); my $limWithMargin = $limpower * (1.0 + $otpMargin / 100.0); # Hinweis: das ist bewusst ein permanenter Sicherheitsaufschlag $limpower = min ($limpower, $pinmax); # limpower !> pinmax um invertierte Interpolation zu vermeiden $limWithMargin = min ($limWithMargin, $pinmax); @@ -12605,7 +12624,7 @@ sub ___batAdjustPowerByMargin { elsif ($ratio >= 100 + $otpMargin) { $pow = $limWithMargin } else { $pow = $pinmax - ($pinmax - $limWithMargin) * ($ratio - 100.0) / $otpMargin } -return ($pow, $ratio); +return $pow; } ################################################################ @@ -12967,7 +12986,7 @@ sub _createSummaries { $next4HoursSum->{Consumption} += $confc / 60 * $minute if($h == 4); } - if ($istdy) { + if ($fd == 0) { $restOfDaySum->{PV} += $pvfc; $restOfDaySum->{Consumption} += $confc; $tdConFcTillSunset += $confc if(int ($hod) < int ($dtsset->{hour}) + 1); @@ -15086,15 +15105,15 @@ return; } ################################################################ -# Schwellenwerte auswerten und signalisieren +# Schwellenwerte für Trigger auswerten und signalisieren ################################################################ -sub _evaluateThresholds { +sub _evaluateTrigger { my $paref = shift; my $name = $paref->{name}; - my $bt = ReadingsVal($name, 'batteryTrigger', ''); - my $pt = ReadingsVal($name, 'powerTrigger', ''); - my $eh4t = ReadingsVal($name, 'energyH4Trigger', ''); + my $bt = ReadingsVal ($name, 'batteryTrigger', ''); + my $pt = ReadingsVal ($name, 'powerTrigger', ''); + my $eh4t = ReadingsVal ($name, 'energyH4Trigger', ''); if ($bt) { $paref->{cobj} = 'batsocslidereg'; @@ -15139,13 +15158,13 @@ sub __evaluateArray { my $aaref = CurrentVal ($name, $cobj, ''); my @aa = (); - @aa = @{$aaref} if (ref $aaref eq 'ARRAY'); + @aa = @{$aaref} if(ref $aaref eq 'ARRAY'); return if(scalar @aa < SLIDENUMMAX); - my $gen1 = $aa[0]; - my $gen2 = $aa[1]; - my $gen3 = $aa[2]; + my $gen1 = $aa[0]; + my $gen2 = $aa[1]; + my $gen3 = $aa[2]; my ($a, $h) = parseParams ($tholds); @@ -15155,13 +15174,13 @@ sub __evaluateArray { if ($cond eq "on" && $gen1 > $h->{$key}) { next if($gen2 < $h->{$key}); next if($gen3 < $h->{$key}); - storeReading ("${tname}_${knum}", 'on') if(ReadingsVal($name, "${tname}_${knum}", "off") eq "off"); + storeReading ("${tname}_${knum}", 'on') if(ReadingsVal($name, "${tname}_${knum}", 'off') eq 'off'); } if ($cond eq "off" && $gen1 < $h->{$key}) { next if($gen2 > $h->{$key}); next if($gen3 > $h->{$key}); - storeReading ("${tname}_${knum}", 'off') if(ReadingsVal($name, "${tname}_${knum}", "on") eq "on"); + storeReading ("${tname}_${knum}", 'off') if(ReadingsVal($name, "${tname}_${knum}", 'on') eq 'on'); } } @@ -15697,6 +15716,7 @@ sub _genSpecialReadings { readingsDelete ($hash, $prpo.'_'.$item); deleteReadingspec ($hash, $prpo.'_'.$item.'_.*') if($item eq 'todayConsumptionForecast'); deleteReadingspec ($hash, $prpo.'_'.$item.'_.*') if($item eq 'tomorrowConsumptionForecast'); + deleteReadingspec ($hash, $prpo.'_'.$item.'_.*') if($item eq 'BatRatio'); } return if(!@csr); @@ -15752,6 +15772,14 @@ sub _genSpecialReadings { storeReading ($prpo.'_'.$kpi, $bsum); } + elsif ($kpi eq 'BatRatio') { + for my $bn (1..MAXBATTERIES) { # für jede Batterie + $bn = sprintf "%02d", $bn; + my $ratio = &{$hcsr{$kpi}{fn}} ($name, $hcsr{$kpi}{par}.$bn, $def); + + storeReading ($prpo.'_'.$kpi.'_'.$bn, sprintf ("%.0f", $ratio)) if($ratio ne '-'); + } + } elsif ($kpi =~ /daysUntilBatteryCare_/xs) { my $bn = (split "_", $kpi)[1]; # Batterienummer extrahieren my $d2c = &{$hcsr{$kpi}{fn}} ($hash, $hcsr{$kpi}{par}, 'days2care'.$bn, $def); @@ -28155,6 +28183,7 @@ to ensure that the system configuration is correct.