diff --git a/fhem/CHANGED b/fhem/CHANGED index abbabd0dd..ed03a3041 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: options for conprice, feedprice and more - change: 49_SSCam: check SSChatBot/TelegramBot is disabled before send data - feature: 70_PylonLowVoltage: add specific Alarm readings, support of US5000 - bugfix: 73_PRESENCE2: BlockingCall loglevel = GetVerbose($name) diff --git a/fhem/FHEM/76_SolarForecast.pm b/fhem/FHEM/76_SolarForecast.pm index 68f664460..760c24173 100644 --- a/fhem/FHEM/76_SolarForecast.pm +++ b/fhem/FHEM/76_SolarForecast.pm @@ -158,6 +158,11 @@ BEGIN { # Versions History intern my %vNotesIntern = ( + "1.17.6" => "07.04.2024 new sub writeToHistory with many internal changes in pvHistory write process ". + "_transferInverterValues: react on inverter etotal behavior ", + "1.17.5" => "04.04.2024 currentInverterDev: check syntax of key capacity if set, change defmaxvar back from 0.8 to 0.5 ". + "currentMeterDev: [conprice=::] [feedprice=::] ". + "___setOpenMeteoAPIcallKeyData: new sub to calculate the minimum Open-Meteo request intervalls ", "1.17.4" => "01.04.2024 fix ctrlWeatherDev1 Drop-Down list if no DWD Device exists, edit commandref ", "1.17.3" => "31.03.2024 edit commandref, valDecTree: more infos in aiRuleStrings output, integrate OpenMeteoDWDEnsemble-API ". "change Call interval Open-Meteo API to 900s, OpenMeteo-API: fix todayDoneAPIcalls, implement callequivalent". @@ -327,12 +332,6 @@ my %vNotesIntern = ( "0.77.1" => "07.05.2023 rewrite function pageRefresh ", "0.77.0" => "03.05.2023 new attribute ctrlUserExitFn ", "0.76.0" => "01.05.2023 new ctrlStatisticReadings SunMinutes_Remain, SunHours_Remain ", - "0.75.3" => "23.04.2023 fix Illegal division by zero at ./FHEM/76_SolarForecast.pm line 6199 ", - "0.75.2" => "16.04.2023 some minor changes ", - "0.75.1" => "24.03.2023 change epieces for consumer type washingmachine, PV Vorhersage auf WR Kapazität begrenzen ", - "0.75.0" => "16.02.2023 new attribute ctrlSolCastAPImaxReq, rename attr ctrlOptimizeSolCastInterval to ctrlSolCastAPIoptimizeReq ", - "0.74.8" => "11.02.2023 change description of 'mintime', mintime with SunPath value possible ", - "0.74.7" => "23.01.2023 fix evaljson evaluation ", "0.1.0" => "09.12.2020 initial Version " ); @@ -344,7 +343,7 @@ my @chours = (5..21); my $kJtokWh = 0.0002777777778; # Umrechnungsfaktor kJ in kWh my $kJtoWh = 0.2777777778; # Umrechnungsfaktor kJ in Wh my $WhtokJ = 3.6; # Umrechnungsfaktor Wh in kJ -my $defmaxvar = 0.8; # max. Varianz pro Tagesberechnung Autokorrekturfaktor +my $defmaxvar = 0.5; # max. Varianz pro Tagesberechnung Autokorrekturfaktor my $definterval = 70; # Standard Abfrageintervall my $defslidenum = 3; # max. Anzahl der Arrayelemente in Schieberegistern my $weatherDevMax = 3; # max. Anzahl Wetter Devices (Attr ctrlWeatherDevX) @@ -384,7 +383,7 @@ my $forapirepdef = 900; my $ometeorepdef = 900; # default Abrufintervall Open-Meteo API (s) my $vrmapirepdef = 300; # default Abrufintervall Victron VRM API Forecast my $solcmaxreqdef = 50; # max. täglich mögliche Requests SolCast API -my $ometmaxreq = 9500; # Beschränkung auf max. mögliche Requests Open-Meteo API +my $ometmaxreq = 9700; # Beschränkung auf max. mögliche Requests Open-Meteo API my $leadtime = 3600; # relative Zeit vor Sonnenaufgang zur Freigabe API Abruf / Verbraucherplanung my $lagtime = 1800; # Nachlaufzeit relativ zu Sunset bis Sperrung API Abruf @@ -1032,7 +1031,7 @@ my %hfspvh = ( batoutthishour => { fn => \&_storeVal, storname => 'batout', validkey => undef, fpar => 'comp99' }, # Batterieentladung in Stunde pvfc => { fn => \&_storeVal, storname => 'pvfc', validkey => undef, fpar => 'comp99' }, # prognostizierter Energieertrag confc => { fn => \&_storeVal, storname => 'confc', validkey => undef, fpar => 'comp99' }, # prognostizierter Hausverbrauch - cons => { fn => \&_storeVal, storname => 'gcons', validkey => undef, fpar => 'comp99' }, # bezogene Energie + gcons => { fn => \&_storeVal, storname => 'gcons', validkey => undef, fpar => 'comp99' }, # bezogene Energie gfeedin => { fn => \&_storeVal, storname => 'gfeedin', validkey => undef, fpar => 'comp99' }, # eingespeiste Energie con => { fn => \&_storeVal, storname => 'con', validkey => undef, fpar => 'comp99' }, # realer Hausverbrauch Energie pvrl => { fn => \&_storeVal, storname => 'pvrl', validkey => 'pvrlvd', fpar => 'comp99' }, # realer Energieertrag @@ -1756,13 +1755,17 @@ sub _setinverterDevice { ## no critic "not used" my ($a,$h) = parseParams ($arg); my $indev = $a->[0] // ""; - if(!$indev || !$defs{$indev}) { + if (!$indev || !$defs{$indev}) { return qq{The device "$indev" doesn't exist!}; } - if(!$h->{pv} || !$h->{etotal}) { + if (!$h->{pv} || !$h->{etotal}) { return qq{The syntax of "$opt" is not correct. Please consider the commandref.}; } + + if ($h->{capacity} && !isNumeric($h->{capacity})) { + return qq{The syntax of key "capacity" is not correct. Please consider the commandref.}; + } readingsSingleUpdate ($hash, 'currentInverterDev', $arg, 1); createAssociatedWith ($hash); @@ -1835,14 +1838,14 @@ sub _setmeterDevice { ## no critic "not used" return qq{Incorrect input. It is not allowed that the keys gcon and gfeedin refer to each other.}; } - if ($h->{conprice}) { - my ($gcp,$gcpcucy) = split ":", $h->{conprice}; - return qq{Incorrect input for key 'conprice'. Please consider the commandref.} if(!$gcp || !$gcpcucy); + if ($h->{conprice}) { # Bezugspreis (Arbeitspreis) pro kWh + my @acp = split ":", $h->{conprice}; + return qq{Incorrect input for key 'conprice'. Please consider the commandref.} if(scalar(@acp) != 2 && scalar(@acp) != 3); } - if ($h->{feedprice}) { - my ($gfr,$gfrcucy) = split ":", $h->{feedprice}; - return qq{Incorrect input for key 'feedprice'. Please consider the commandref.} if(!$gfr || !$gfrcucy); + if ($h->{feedprice}) { # Einspeisevergütung pro kWh + my @afp = split ":", $h->{feedprice}; + return qq{Incorrect input for key 'feedprice'. Please consider the commandref.} if(scalar(@afp) != 2 && scalar(@afp) != 3); } ## alte Speicherwerte löschen @@ -2231,7 +2234,9 @@ sub _setreset { ## no critic "not used" $paref->{reorg} = 1; # den Tag Stunde "99" reorganisieren $paref->{reorgday} = $dday; + setPVhistory ($paref); + delete $paref->{reorg}; delete $paref->{reorgday}; } @@ -3433,7 +3438,7 @@ sub __forecastSolar_ApiResponse { $data{$type}{$name}{solcastapi}{'?All'}{'?All'}{requests_limit} = $jdata->{'message'}{'ratelimit'}{'limit'}; # Requests Limit in Periode $data{$type}{$name}{solcastapi}{'?All'}{'?All'}{place} = encode ("utf8", $jdata->{'message'}{'info'}{'place'}); - if($debug =~ /apiCall/x) { + if ($debug =~ /apiCall/x) { Log3 ($name, 1, qq{$name DEBUG> ForecastSolar API Call - server response for PV string "$string"}); Log3 ($name, 1, "$name DEBUG> ForecastSolar API Call - request time: ". $rt." ($rts)"); Log3 ($name, 1, "$name DEBUG> ForecastSolar API Call - requests remaining: ".$jdata->{'message'}{'ratelimit'}{'remaining'}); @@ -4132,8 +4137,9 @@ sub __openMeteoDWD_ApiRequest { my $submodel = $paref->{submodel}; # abzufragendes Wettermodell if (!$allstrings) { # alle Strings wurden abgerufen - writeCacheToFile ($hash, 'solcastapi', $scpicache.$name); - readingsSingleUpdate ($hash, 'nextRadiationAPICall', $hqtxt{after}{$lang}.' '.(timestampToTimestring ($t + $ometeorepdef, $lang))[0], 1); + writeCacheToFile ($hash, 'solcastapi', $scpicache.$name); + my $apiitv = SolCastAPIVal ($hash, '?All', '?All', 'currentAPIinterval', $ometeorepdef); + readingsSingleUpdate ($hash, 'nextRadiationAPICall', $hqtxt{after}{$lang}.' '.(timestampToTimestring ($t + $apiitv, $lang))[0], 1); $data{$type}{$name}{solcastapi}{'?All'}{'?All'}{todayDoneAPIcalls} += 1; return; } @@ -4300,7 +4306,6 @@ sub __openMeteoDWD_ApiResponse { if ($debug =~ /apiCall/xs) { Log3 ($name, 1, qq{$name DEBUG> Open-Meteo API Call - server response for PV string "$string"}); - Log3 ($name, 1, "$name DEBUG> Open-Meteo API Call - request time: ".$rt." ($t)"); Log3 ($name, 1, "$name DEBUG> Open-Meteo API Call - status: success"); } @@ -4445,7 +4450,7 @@ sub __openMeteoDWD_ApiResponse { } } - $data{$type}{$name}{solcastapi}{'?All'}{'?All'}{todayDoneAPIrequests} += $paref->{callequivalent}; + ___setOpenMeteoAPIcallKeyData ($paref); Log3 ($name, 4, qq{$name - Open-Meteo DWD ICON API answer received for string "$string"}); @@ -4466,6 +4471,49 @@ sub __openMeteoDWD_ApiResponse { return &$caller($param); } +################################################################ +# Kennzahlen aus letzten Open-Meteo Request ableiten +################################################################ +sub ___setOpenMeteoAPIcallKeyData { + my $paref = shift; + my $hash = $paref->{hash}; + my $lang = $paref->{lang}; + my $debug = $paref->{debug}; + my $cequ = $paref->{callequivalent}; + my $t = $paref->{t} // time; + + my $name = $hash->{NAME}; + my $type = $hash->{TYPE}; + + $data{$type}{$name}{solcastapi}{'?All'}{'?All'}{todayDoneAPIrequests} += $cequ; + + my $dar = SolCastAPIVal ($hash, '?All', '?All', 'todayDoneAPIrequests', 0); + my $dac = SolCastAPIVal ($hash, '?All', '?All', 'todayDoneAPIcalls', 0); + my $asc = CurrentVal ($hash, 'allstringscount', 1); + + my $drr = $ometmaxreq - $dar; + $drr = 0 if($drr < 0); + + $data{$type}{$name}{solcastapi}{'?All'}{'?All'}{todayRemainingAPIrequests} = $drr; + $data{$type}{$name}{solcastapi}{'?All'}{'?All'}{currentAPIinterval} = $ometeorepdef; + + ## Berechnung des optimalen Request Intervalls + ################################################ + my $edate = strftime "%Y-%m-%d 23:58:00", localtime($t); + my $ets = timestringToTimestamp ($edate); + my $rmdif = $ets - int $t; + + if ($drr) { + my $optrep = $rmdif / ($drr / ($cequ * $asc)); + $optrep = $ometeorepdef if($optrep < $ometeorepdef); + $data{$type}{$name}{solcastapi}{'?All'}{'?All'}{currentAPIinterval} = $optrep; + } + + debugLog ($paref, "apiProcess|apiCall", "Open-Meteo API Call - remaining API Requests: $drr, Request equivalents p. call: $cequ, new call interval: ".SolCastAPIVal ($hash, '?All', '?All', 'currentAPIinterval', $ometeorepdef)); + +return; +} + ############################################################### # Getter data ############################################################### @@ -6205,6 +6253,8 @@ sub centralTask { RemoveInternalTimer ($hash, 'FHEM::SolarForecast::centralTask'); RemoveInternalTimer ($hash, 'FHEM::SolarForecast::singleUpdateState'); + + return if(!$init_done); ### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !! ########################################################################################################################## @@ -6235,8 +6285,6 @@ sub centralTask { } ####################################################################################################################### - return if(!$init_done); - setModel ($hash); # Model setzen my (undef, $disabled, $inactive) = controller ($name); @@ -6893,30 +6941,11 @@ sub _transferWeatherValues { } if ($fd == 0 && $fh1) { # Weather in pvHistory speichern - $paref->{val} = $wid; - $paref->{histname} = 'weatherid'; - $paref->{nhour} = sprintf "%02d", $fh1; - setPVhistory ($paref); - - $paref->{val} = $neff // 0; - $paref->{histname} = 'weathercloudcover'; - setPVhistory ($paref); - - $paref->{val} = $rr1c; - $paref->{histname} = 'totalrain'; - setPVhistory ($paref); - - $paref->{val} = $temp; - $paref->{histname} = 'temperature'; - setPVhistory ($paref); - - $paref->{val} = $don; - $paref->{histname} = 'DoN'; - setPVhistory ($paref); - - delete $paref->{histname}; - delete $paref->{val}; - delete $paref->{nhour}; + writeToHistory ( { paref => $paref, key => 'weatherid', val => $wid, hour => $fh1 } ); + writeToHistory ( { paref => $paref, key => 'weathercloudcover', val => $neff // 0, hour => $fh1 } ); + writeToHistory ( { paref => $paref, key => 'totalrain', val => $rr1c, hour => $fh1 } ); + writeToHistory ( { paref => $paref, key => 'temperature', val => $temp, hour => $fh1 } ); + writeToHistory ( { paref => $paref, key => 'DoN', val => $don, hour => $fh1 } ); } } @@ -7300,18 +7329,8 @@ sub _transferAPIRadiationValues { } if ($fd == 0 && $fh1) { - $paref->{nhour} = sprintf "%02d", $fh1; - $paref->{val} = $pvfc; - $paref->{histname} = 'pvfc'; - setPVhistory ($paref); - - $paref->{val} = $rad1h; - $paref->{histname} = 'radiation'; - setPVhistory ($paref); - - delete $paref->{histname}; - delete $paref->{val}; - delete $paref->{nhour}; + writeToHistory ( { paref => $paref, key => 'pvfc', val => $pvfc, hour => $fh1 } ); + writeToHistory ( { paref => $paref, key => 'radiation', val => $rad1h, hour => $fh1 } ); } } @@ -7362,19 +7381,9 @@ sub __calcSunPosition { debugLog ($paref, 'collectData', "Sun position: day: $wtday, hod: $hodn, $tstr, azimuth: $az, altitude: $alt"); - if ($fd == 0 && $hodn) { # Sun Position in pvHistory speichern - $paref->{nhour} = $hodn; - $paref->{val} = $az; - $paref->{histname} = 'sunaz'; - setPVhistory ($paref); - - $paref->{val} = $alt; - $paref->{histname} = 'sunalt'; - setPVhistory ($paref); - - delete $paref->{histname}; - delete $paref->{val}; - delete $paref->{nhour}; + if ($fd == 0 && $hodn) { # Sun Position in pvHistory speichern + writeToHistory ( { paref => $paref, key => 'sunaz', val => $az, hour => $hodn } ); + writeToHistory ( { paref => $paref, key => 'sunalt', val => $alt, hour => $hodn } ); } return; @@ -7494,8 +7503,8 @@ return $pvsum; ###################################################################### # Complex: -# Liest bewölkungsabhängige Korrekturfaktor/Qualität und -# speichert die Werte im Nexthours / pvHistory Hash +# Liest bewölkungsabhängige Korrekturfaktor/Qualität aus pvCircular +# und speichert die Werte im Nexthours / pvHistory Hash # # Simple: # Liest Korrekturfaktor/Qualität aus pvCircular simple und @@ -7547,16 +7556,8 @@ sub ___readCandQ { $data{$type}{$name}{nexthours}{"NextHour".sprintf("%02d",$num)}{pvcorrf} = $hc."/".$hq; - if($fd == 0 && $fh1) { - $paref->{val} = $hc.'/'.$hq; - $paref->{nhour} = sprintf "%02d", $fh1; - $paref->{histname} = 'pvcorrfactor'; - - setPVhistory ($paref); - - delete $paref->{histname}; - delete $paref->{val}; - delete $paref->{nhour}; + if ($fd == 0 && $fh1) { + writeToHistory ( { paref => $paref, key => 'pvcorrfactor', val => $hc.'/'.$hq, hour => $fh1 } ); } return ($hc, $hq); @@ -7675,7 +7676,7 @@ sub _transferInverterValues { my ($pvread,$pvunit) = split ":", $h->{pv}; # Readingname/Unit für aktuelle PV Erzeugung my ($edread,$etunit) = split ":", $h->{etotal}; # Readingname/Unit für Energie total (PV Erzeugung) - $data{$type}{$name}{current}{invertercapacity} = $h->{capacity} if($h->{capacity}); # optionale Angabe max. WR-Leistung + $data{$type}{$name}{current}{invertercapacity} = $h->{capacity} if(defined $h->{capacity}); # optionale Angabe max. WR-Leistung return if(!$pvread || !$edread); @@ -7696,47 +7697,43 @@ sub _transferInverterValues { debugLog ($paref, "collectData", "pv: $pv W, etotal: $etotal Wh"); my $nhour = $chour + 1; - my $histetot = HistoryVal ($hash, $day, sprintf("%02d",$nhour), "etotal", 0); # etotal zu Beginn einer Stunde - - my $ethishour; - if (!$histetot) { # etotal der aktuelle Stunde gesetzt ? - $paref->{val} = $etotal; - $paref->{nhour} = sprintf "%02d", $nhour; - $paref->{histname} = 'etotal'; - - setPVhistory ($paref); - - delete $paref->{histname}; - delete $paref->{nhour}; - delete $paref->{val}; - - my $etot = CurrentVal ($hash, "etotal", $etotal); - $ethishour = int ($etotal - $etot); + my $histetot = HistoryVal ($hash, $day, sprintf("%02d",$nhour), 'etotal', 0); # etotal zu Beginn einer Stunde + my $warn = ''; + my ($ethishour, $etotsvd); + + if (!$histetot) { # etotal der aktuelle Stunde gesetzt ? + writeToHistory ( { paref => $paref, key => 'etotal', val => $etotal, hour => $nhour } ); + + $etotsvd = CurrentVal ($hash, 'etotal', $etotal); + $ethishour = int ($etotal - $etotsvd); } else { $ethishour = int ($etotal - $histetot); + if (defined $h->{capacity} && $ethishour > 2 x $h->{capacity}) { # Schutz vor plötzlichem Anstieg von 0 auf mehr als doppelte WR-Kapazität + Log3 ($name, 1, "$name - WARNING - The generated PV of Inverter '$indev' is much more higher than inverter capacity. It seems to be a failure and Energy Total is reinitialized."); + $warn = ' (WARNING: too much generated PV was registered - see log file)'; + + writeToHistory ( { paref => $paref, key => 'etotal', val => $etotal, hour => $nhour } ); + + $etotsvd = CurrentVal ($hash, 'etotal', $etotal); + $ethishour = int ($etotal - $etotsvd); + } } $data{$type}{$name}{current}{etotal} = $etotal; # aktuellen etotal des WR speichern + + if ($ethishour < 0) { + $ethishour = 0; + Log3 ($name, 1, "$name - WARNING - The Total Energy from Inverter '$indev' is lower than the value saved before. This situation is invalid and the Energy generated of current hour is set to '0'."); + $warn = ' (WARNING invalid real PV occured - see Logfile)'; + } - $ethishour = 0 if($ethishour < 0); - - storeReading ('Today_Hour'.sprintf("%02d",$nhour).'_PVreal', $ethishour.' Wh'); + storeReading ('Today_Hour'.sprintf("%02d",$nhour).'_PVreal', $ethishour.' Wh'.$warn); $data{$type}{$name}{circular}{sprintf("%02d",$nhour)}{pvrl} = $ethishour; # Ringspeicher PV real Forum: https://forum.fhem.de/index.php/topic,117864.msg1133350.html#msg1133350 my ($acu, $aln) = isAutoCorrUsed ($name); - - $paref->{val} = $ethishour; - $paref->{nhour} = sprintf "%02d", $nhour; - $paref->{histname} = 'pvrl'; - $paref->{pvrlvd} = $aln; # 1: beim Learning berücksichtigen, 0: nicht - - setPVhistory ($paref); - - delete $paref->{pvrlvd}; - delete $paref->{histname}; - delete $paref->{nhour}; - delete $paref->{val}; + + writeToHistory ( { paref => $paref, key => 'pvrl', val => $ethishour, hour => $nhour, valid => $aln } ); # valid=1: beim Learning berücksichtigen, 0: nicht return; } @@ -7765,16 +7762,44 @@ sub _transferMeterValues { return if(!$gc || !$gf || !$gt || !$ft); - if ($h->{conprice}) { - my ($gcp,$gcpcucy) = split ":", $h->{conprice}; # Bezugspreis (Arbeitspreis) pro kWh - $data{$type}{$name}{current}{ePurchasePrice} = $gcp; - $data{$type}{$name}{current}{ePurchasePriceCcy} = $gcpcucy; + if ($h->{conprice}) { # Bezugspreis (Arbeitspreis) pro kWh + my @acp = split ":", $h->{conprice}; + + if (scalar(@acp) == 3) { + $data{$type}{$name}{current}{ePurchasePrice} = ReadingsNum ($acp[0], $acp[1], 0); + $data{$type}{$name}{current}{ePurchasePriceCcy} = $acp[2]; + } + + if (scalar(@acp) == 2) { + if (isNumeric($acp[0])) { + $data{$type}{$name}{current}{ePurchasePrice} = $acp[0]; + $data{$type}{$name}{current}{ePurchasePriceCcy} = $acp[1]; + } + else { + $data{$type}{$name}{current}{ePurchasePrice} = ReadingsNum ($medev, $acp[0], 0); + $data{$type}{$name}{current}{ePurchasePriceCcy} = $acp[1]; + } + } } - - if ($h->{feedprice}) { - my ($gfr,$gfrcucy) = split ":", $h->{feedprice}; # Einspeisevergütung pro kWh - $data{$type}{$name}{current}{eFeedInTariff} = $gfr; - $data{$type}{$name}{current}{eFeedInTariffCcy} = $gfrcucy; + + if ($h->{feedprice}) { # Einspeisevergütung pro kWh + my @afp = split ":", $h->{feedprice}; + + if (scalar(@afp) == 3) { + $data{$type}{$name}{current}{eFeedInTariff} = ReadingsNum ($afp[0], $afp[1], 0); + $data{$type}{$name}{current}{eFeedInTariffCcy} = $afp[2]; + } + + if (scalar(@afp) == 2) { + if (isNumeric($afp[0])) { + $data{$type}{$name}{current}{eFeedInTariff} = $afp[0]; + $data{$type}{$name}{current}{eFeedInTariffCcy} = $afp[1]; + } + else { + $data{$type}{$name}{current}{eFeedInTariff} = ReadingsNum ($medev, $afp[0], 0); + $data{$type}{$name}{current}{eFeedInTariffCcy} = $afp[1]; + } + } } $gfunit //= $gcunit; @@ -7863,16 +7888,8 @@ sub _transferMeterValues { my $nhour = $chour + 1; storeReading ('Today_Hour'.sprintf("%02d",$nhour).'_GridConsumption', $gctotthishour.' Wh'); $data{$type}{$name}{circular}{sprintf("%02d",$nhour)}{gcons} = $gctotthishour; # Hilfshash Wert Bezug (Wh) Forum: https://forum.fhem.de/index.php/topic,117864.msg1133350.html#msg1133350 - - $paref->{val} = $gctotthishour; - $paref->{nhour} = sprintf "%02d", $nhour; - $paref->{histname} = 'cons'; - - setPVhistory ($paref); - - delete $paref->{histname}; - delete $paref->{nhour}; - delete $paref->{val}; + + writeToHistory ( { paref => $paref, key => 'gcons', val => $gctotthishour, hour => $nhour } ); } my $dofeed = 0; @@ -7902,16 +7919,8 @@ sub _transferMeterValues { my $nhour = $chour+1; storeReading ('Today_Hour'.sprintf("%02d",$nhour).'_GridFeedIn', $gftotthishour.' Wh'); $data{$type}{$name}{circular}{sprintf("%02d",$nhour)}{gfeedin} = $gftotthishour; - - $paref->{val} = $gftotthishour; - $paref->{nhour} = sprintf "%02d", $nhour; - $paref->{histname} = 'gfeedin'; - - setPVhistory ($paref); - - delete $paref->{histname}; - delete $paref->{nhour}; - delete $paref->{val}; + + writeToHistory ( { paref => $paref, key => 'gfeedin', val => $gftotthishour, hour => $nhour } ); } return; @@ -8013,15 +8022,17 @@ sub _transferBatteryValues { my $batinthishour; if (!defined $histbatintot) { # totale Batterieladung der aktuelle Stunde gesetzt ? - $paref->{val} = $btotin; - $paref->{nhour} = sprintf "%02d", $nhour; - $paref->{histname} = 'batintotal'; + #$paref->{val} = $btotin; + #$paref->{nhour} = sprintf "%02d", $nhour; + #$paref->{histname} = 'batintotal'; - setPVhistory ($paref); + #setPVhistory ($paref); - delete $paref->{histname}; - delete $paref->{nhour}; - delete $paref->{val}; + #delete $paref->{histname}; + #delete $paref->{nhour}; + #delete $paref->{val}; + + writeToHistory ( { paref => $paref, key => 'batintotal', val => $btotin, hour => $nhour } ); my $bitot = CurrentVal ($hash, "batintotal", $btotin); $batinthishour = int ($btotin - $bitot); @@ -8034,15 +8045,17 @@ sub _transferBatteryValues { $data{$type}{$name}{circular}{sprintf("%02d",$nhour)}{batin} = $batinthishour; # Ringspeicher Battery In Forum: https://forum.fhem.de/index.php/topic,117864.msg1133350.html#msg1133350 - $paref->{val} = $batinthishour; - $paref->{nhour} = sprintf "%02d", $nhour; - $paref->{histname} = 'batinthishour'; + #$paref->{val} = $batinthishour; + #$paref->{nhour} = sprintf "%02d", $nhour; + #$paref->{histname} = 'batinthishour'; - setPVhistory ($paref); + #setPVhistory ($paref); - delete $paref->{histname}; - delete $paref->{nhour}; - delete $paref->{val}; + #delete $paref->{histname}; + #delete $paref->{nhour}; + #delete $paref->{val}; + + writeToHistory ( { paref => $paref, key => 'batinthishour', val => $batinthishour, hour => $nhour } ); # Batterieentladung aktuelle Stunde in pvHistory speichern ############################################################ @@ -8050,15 +8063,17 @@ sub _transferBatteryValues { my $batoutthishour; if (!defined $histbatouttot) { # totale Betterieladung der aktuelle Stunde gesetzt ? - $paref->{val} = $btotout; - $paref->{nhour} = sprintf "%02d", $nhour; - $paref->{histname} = 'batouttotal'; + #$paref->{val} = $btotout; + #$paref->{nhour} = sprintf "%02d", $nhour; + #$paref->{histname} = 'batouttotal'; - setPVhistory ($paref); + #setPVhistory ($paref); - delete $paref->{histname}; - delete $paref->{nhour}; - delete $paref->{val}; + #delete $paref->{histname}; + #delete $paref->{nhour}; + #delete $paref->{val}; + + writeToHistory ( { paref => $paref, key => 'batouttotal', val => $btotout, hour => $nhour } ); my $botot = CurrentVal ($hash, 'batouttotal', $btotout); $batoutthishour = int ($btotout - $botot); @@ -8071,30 +8086,34 @@ sub _transferBatteryValues { $data{$type}{$name}{circular}{sprintf("%02d",$nhour)}{batout} = $batoutthishour; # Ringspeicher Battery In Forum: https://forum.fhem.de/index.php/topic,117864.msg1133350.html#msg1133350 - $paref->{val} = $batoutthishour; - $paref->{nhour} = sprintf "%02d", $nhour; - $paref->{histname} = 'batoutthishour'; + #$paref->{val} = $batoutthishour; + #$paref->{nhour} = sprintf "%02d", $nhour; + #$paref->{histname} = 'batoutthishour'; - setPVhistory ($paref); + #setPVhistory ($paref); - delete $paref->{histname}; - delete $paref->{nhour}; - delete $paref->{val}; + #delete $paref->{histname}; + #delete $paref->{nhour}; + #delete $paref->{val}; + + writeToHistory ( { paref => $paref, key => 'batoutthishour', val => $batoutthishour, hour => $nhour } ); # täglichen max. SOC in pvHistory speichern ############################################# my $batmaxsoc = HistoryVal ($hash, $day, 99, 'batmaxsoc', 0); # gespeicherter max. SOC des Tages if ($soc >= $batmaxsoc) { - $paref->{val} = $soc; - $paref->{nhour} = 99; - $paref->{histname} = 'batmaxsoc'; + #$paref->{val} = $soc; + #$paref->{nhour} = 99; + #$paref->{histname} = 'batmaxsoc'; - setPVhistory ($paref); + #setPVhistory ($paref); - delete $paref->{histname}; - delete $paref->{nhour}; - delete $paref->{val}; + #delete $paref->{histname}; + #delete $paref->{nhour}; + #delete $paref->{val}; + + writeToHistory ( { paref => $paref, key => 'batmaxsoc', val => $soc, hour => 99 } ); } ###### @@ -8240,19 +8259,10 @@ sub _batSocTarget { ($chargereq ? 'yes (battery charge is below minimum SoC)' : 'no (Battery is sufficiently charged)')); ## pvHistory/Readings schreiben - ################################# - $paref->{val} = $target; - $paref->{nhour} = 99; - $paref->{histname} = 'batsetsoc'; - - setPVhistory ($paref); - - delete $paref->{histname}; - delete $paref->{nhour}; - delete $paref->{val}; - - storeReading ('Battery_OptimumTargetSoC', $target.' %'); - storeReading ('Battery_ChargeRequest', $chargereq); + ################################# + writeToHistory ( { paref => $paref, key => 'batsetsoc', val => $target, hour => 99 } ); + storeReading ('Battery_OptimumTargetSoC', $target.' %'); + storeReading ('Battery_ChargeRequest', $chargereq); return; } @@ -10045,17 +10055,8 @@ sub _estConsumptionForecast { $data{$type}{$name}{nexthours}{$k}{confc} = $conavg; # Durchschnittsverbrauch aller gleicher Wochentage pro Stunde if (NexthoursVal ($hash, $k, "today", 0)) { # nur Werte des aktuellen Tag speichern - $data{$type}{$name}{circular}{sprintf("%02d",$nhhr)}{confc} = $conavg; - - $paref->{val} = $conavg; - $paref->{nhour} = sprintf "%02d", $nhhr; - $paref->{histname} = 'confc'; - - setPVhistory ($paref); - - delete $paref->{histname}; - delete $paref->{nhour}; - delete $paref->{val}; + $data{$type}{$name}{circular}{sprintf("%02d",$nhhr)}{confc} = $conavg; + writeToHistory ( { paref => $paref, key => 'confc', val => $conavg, hour => $nhhr } ); } debugLog ($paref, "consumption", "estimated Consumption for $nhday -> starttime: $nhtime, confc: $conavg, days for avg: $dnum, hist. consumption registered consumers: ".sprintf "%.2f", $consumerco); @@ -10558,24 +10559,15 @@ sub saveEnergyConsumption { my $name = $paref->{name}; my $chour = $paref->{chour}; - my $shr = $chour+1; + my $shr = $chour + 1; my $pvrl = ReadingsNum ($name, "Today_Hour".sprintf("%02d",$shr)."_PVreal", 0); my $gfeedin = ReadingsNum ($name, "Today_Hour".sprintf("%02d",$shr)."_GridFeedIn", 0); my $gcon = ReadingsNum ($name, "Today_Hour".sprintf("%02d",$shr)."_GridConsumption", 0); my $batin = ReadingsNum ($name, "Today_Hour".sprintf("%02d",$shr)."_BatIn", 0); my $batout = ReadingsNum ($name, "Today_Hour".sprintf("%02d",$shr)."_BatOut", 0); - - my $con = $pvrl - $gfeedin + $gcon - $batin + $batout; - - $paref->{val} = $con; - $paref->{nhour} = sprintf "%02d", $shr; - $paref->{histname} = 'con'; - - setPVhistory ($paref); - - delete $paref->{histname}; - delete $paref->{nhour}; - delete $paref->{val}; + my $con = $pvrl - $gfeedin + $gcon - $batin + $batout; + + writeToHistory ( { paref => $paref, key => 'con', val => $con, hour => $shr } ); return; } @@ -11506,7 +11498,7 @@ sub _graphicHeader { $api .= '  ('; $api .= SolCastAPIVal ($hash, '?All', '?All', 'todayDoneAPIrequests', 0); $api .= '/'; - $api .= SolCastAPIVal ($hash, '?All', '?All', 'todayRemainingAPIrequests', 50); + $api .= SolCastAPIVal ($hash, '?All', '?All', 'todayRemainingAPIrequests', $solcmaxreqdef); $api .= ')'; $api .= ''; } @@ -11593,9 +11585,11 @@ sub _graphicHeader { $scicon = "$img"; $api .= '  '.$scicon; - $api .= ''; + $api .= ''; $api .= '  ('; $api .= SolCastAPIVal ($hash, '?All', '?All', 'todayDoneAPIrequests', 0); + $api .= '/'; + $api .= SolCastAPIVal ($hash, '?All', '?All', 'todayRemainingAPIrequests', $ometmaxreq); $api .= ')'; $api .= ''; } @@ -14192,6 +14186,39 @@ sub _aiMakeIdxRaw { return $ridx; } + +################################################################ +# einen Schlüssel-Wert in die pvHistory schreiben +# $valid - Wert für Valid-Key festgelegt in $hfspvh Hash +# z.B. pvrlvd = 1: beim Learning berücksichtigen, 0: nicht +################################################################ +sub writeToHistory { + my $ph = shift; + + my $paref = $ph->{paref}; + my $key = $ph->{key}; + my $val = $ph->{val}; + my $hour = $ph->{hour}; + my $valid = $ph->{valid}; + + $paref->{val} = $val; + $paref->{nhour} = sprintf "%02d", $hour; + $paref->{histname} = $key; + + if (defined $hfspvh{$key}{validkey}) { + $paref->{$hfspvh{$key}{validkey}} = $valid; + } + + setPVhistory ($paref); + + delete $paref->{histname}; + delete $paref->{nhour}; + delete $paref->{val}; + delete $paref->{$hfspvh{$key}{validkey}} if(defined $hfspvh{$key}{validkey}); + +return; +} + ################################################################ # History-Hash verwalten ################################################################ @@ -14205,7 +14232,6 @@ sub setPVhistory { my $nhour = $paref->{nhour}; my $histname = $paref->{histname}; my $val = $paref->{val}; # Wert zur Speicherung in pvHistory (soll mal generell verwendet werden -> Change) - my $pvrlvd = $paref->{pvrlvd}; # 1: Eintrag 'pvrl' wird im Lernprozess berücksichtigt my $reorg = $paref->{reorg} // 0; # Neuberechnung von Werten in Stunde "99" nach Löschen von Stunden eines Tages my $reorgday = $paref->{reorgday} // q{}; # Tag der reorganisiert werden soll @@ -14313,7 +14339,7 @@ sub _storeVal { ## no critic "not used" $data{$type}{$name}{pvhist}{$day}{$nhour}{$store} = $val; - if (defined $hfspvh{$histname}{validkey}) { + if (defined $hfspvh{$histname}{validkey}) { # 1: bestimmter Eintrag wird intern für Prozesse (z.B. Lernprozess) berücksichtigt oder nicht (0) $validkey = $hfspvh{$histname}{validkey}; $validval = $paref->{$validkey}; $data{$type}{$name}{pvhist}{$day}{$nhour}{$validkey} = $validval; @@ -14358,7 +14384,7 @@ sub listDataPool { my $pvrl = HistoryVal ($hash, $day, $key, 'pvrl', '-'); my $pvrlvd = HistoryVal ($hash, $day, $key, 'pvrlvd', '-'); my $pvfc = HistoryVal ($hash, $day, $key, 'pvfc', '-'); - my $gcon = HistoryVal ($hash, $day, $key, 'gcons', '-'); + my $gcons = HistoryVal ($hash, $day, $key, 'gcons', '-'); my $con = HistoryVal ($hash, $day, $key, 'con', '-'); my $confc = HistoryVal ($hash, $day, $key, 'confc', '-'); my $gfeedin = HistoryVal ($hash, $day, $key, 'gfeedin', '-'); @@ -14384,7 +14410,7 @@ sub listDataPool { $ret .= $key." => "; $ret .= "etotal: $etotal, pvfc: $pvfc, pvrl: $pvrl, pvrlvd: $pvrlvd, rad1h: $rad1h"; $ret .= "\n "; - $ret .= "confc: $confc, con: $con, gcon: $gcon, gfeedin: $gfeedin"; + $ret .= "confc: $confc, con: $con, gcon: $gcons, gfeedin: $gfeedin"; $ret .= "\n "; $ret .= "DoN: $don, sunaz: $sunaz, sunalt: $sunalt"; $ret .= "\n "; @@ -14648,7 +14674,7 @@ sub listDataPool { } for my $idx (sort keys %{$h}) { if (ref $h->{$idx} ne "ARRAY") { - $sq .= $idx." => ".$h->{$idx}."\n"; + $sq .= $idx." => ".(defined $h->{$idx} ? $h->{$idx} : '')."\n"; } else { my $aser = join " ",@{$h->{$idx}}; @@ -17770,11 +17796,12 @@ to ensure that the system configuration is correct.
    - - - - -
    pv Reading which provides the current PV generation
    etotal Reading which provides the total PV energy generated (a steadily increasing counter).
    Einheit the respective unit (W,kW,Wh,kWh)
    capacity Rated power of the inverter according to data sheet (max. possible output in watts)
    + pv Reading which provides the current PV generation + etotal Reading which provides the total PV energy generated (a steadily increasing counter). + Einheit the respective unit (W,kW,Wh,kWh) + capacity Rated power of the inverter according to data sheet, i.e. max. possible output in Watts + (The entry is optional, but is strongly recommended) +

@@ -17791,9 +17818,9 @@ to ensure that the system configuration is correct.
    -
  • currentMeterDev <Meter Device Name> gcon=<Readingname>:<Einheit> contotal=<Readingname>:<Einheit> - gfeedin=<Readingname>:<Einheit> feedtotal=<Readingname>:<Einheit> - [conprice=<Wert>:<Currency>] [feedprice=<Wert>:<Currency>]

    +
  • currentMeterDev <Meter Device Name> gcon=<Readingname>:<Unit> contotal=<Readingname>:<Unit> + gfeedin=<Readingname>:<Unit> feedtotal=<Readingname>:<Unit> + [conprice=<Field>] [feedprice=<Field>]

    Sets any device and its readings for energy measurement. The module assumes that the numeric value of the readings is positive. @@ -17803,13 +17830,21 @@ to ensure that the system configuration is correct.
      - - - - - - - + + + + + + + + + + + + + + +
      gcon Reading which supplies the power currently drawn from the grid
      contotal Reading which provides the sum of the energy drawn from the grid (a constantly increasing meter)
      gfeedin Reading which supplies the power currently fed into the grid
      feedtotal Reading which provides the sum of the energy fed into the grid (a constantly increasing meter)
      Einheit the respective unit (W,kW,Wh,kWh)
      conprice Price and currency for the purchase of one kWh (optional)
      feedprice Price and currency for the feed-in of one kWh (optional)
      gcon Reading which supplies the power currently drawn from the grid
      contotal Reading which provides the sum of the energy drawn from the grid (a constantly increasing meter)
      gfeedin Reading which supplies the power currently fed into the grid
      feedtotal Reading which provides the sum of the energy fed into the grid (a constantly increasing meter)
      Unit the respective unit (W,kW,Wh,kWh)
      conprice Price for the purchase of one kWh (optional). The <field> can be specified in one of the following variants:
      <Price>:<Currency> - Price as a numerical value and its currency
      <Reading>:<Currency> - Reading of the meter device that contains the price : Currency
      <Device>:<Reading>:<Currency> - any device and reading containing the price : Currency
      feedprice Remuneration for the feed-in of one kWh (optional). The <field> can be specified in one of the following variants:
      <Remuneration>:<Currency> - Remuneration as a numerical value and its currency
      <Reading>:<Currency> - Reading of the meter device that contains the remuneration : Currency
      <Device>:<Reading>:<Currency> - any device and reading containing the remuneration : Currency

    @@ -17826,7 +17861,7 @@ to ensure that the system configuration is correct.
      Example:
      - set <name> currentMeterDev Meter gcon=Wirkleistung:W contotal=BezWirkZaehler:kWh gfeedin=-gcon feedtotal=EinWirkZaehler:kWh conprice=0.2958:€ feedprice=0.1269:€
      + set <name> currentMeterDev Meter gcon=Wirkleistung:W contotal=BezWirkZaehler:kWh gfeedin=-gcon feedtotal=EinWirkZaehler:kWh conprice=powerCost:€ feedprice=0.1269:€

      # Device Meter provides the current grid reference in the reading "Wirkleistung" (W), the sum of the grid reference in the reading "BezWirkZaehler" (kWh), the current feed in "Wirkleistung" if "Wirkleistung" is negative, @@ -19247,7 +19282,7 @@ to ensure that the system configuration is correct.
    • ctrlWeatherDevX

      - Specifies the device or API that provides the required weather data (cloud cover, precipitation, etc.).
      + Specifies the device or API for providing the required weather data (cloud cover, precipitation, etc.).
      The attribute 'ctrlWeatherDev1' specifies the leading weather service and is mandatory.
      If an Open-Meteo API is selected in the 'ctrlWeatherDev1' attribute, this Open-Meteo service is automatically set as the source of the radiation data (Setter currentRadiationAPI).

      @@ -20000,10 +20035,11 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
        - - - - + + + + +
        pv Reading welches die aktuelle PV-Erzeugung liefert
        etotal Reading welches die gesamte erzeugte PV-Energie liefert (ein stetig aufsteigender Zähler)
        Einheit die jeweilige Einheit (W,kW,Wh,kWh)
        capacity Bemessungsleistung des Wechselrichters gemäß Datenblatt (max. möglicher Output in Watt)
        pv Reading welches die aktuelle PV-Erzeugung liefert
        etotal Reading welches die gesamte erzeugte PV-Energie liefert (ein stetig aufsteigender Zähler)
        Einheit die jeweilige Einheit (W,kW,Wh,kWh)
        capacity Bemessungsleistung des Wechselrichters gemäß Datenblatt, d.h. max. möglicher Output in Watt
        (Die Angabe ist optional, wird aber dringend empfohlen zu setzen)

      @@ -20023,7 +20059,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
    • currentMeterDev <Meter Device Name> gcon=<Readingname>:<Einheit> contotal=<Readingname>:<Einheit> gfeedin=<Readingname>:<Einheit> feedtotal=<Readingname>:<Einheit> - [conprice=<Wert>:<Currency>] [feedprice=<Wert>:<Currency>]

      + [conprice=<Feld>] [feedprice=<Feld>]

      Legt ein beliebiges Device und seine Readings zur Energiemessung fest. Das Modul geht davon aus, dass der numerische Wert der Readings positiv ist. @@ -20033,14 +20069,22 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
        - - - - - - - -
        gcon Reading welches die aktuell aus dem Netz bezogene Leistung liefert
        contotal Reading welches die Summe der aus dem Netz bezogenen Energie liefert (ein sich stetig erhöhender Zähler)
        gfeedin Reading welches die aktuell in das Netz eingespeiste Leistung liefert
        feedtotal Reading welches die Summe der in das Netz eingespeisten Energie liefert (ein sich stetig erhöhender Zähler)
        Einheit die jeweilige Einheit (W,kW,Wh,kWh)
        conprice Preis und Währung für den Bezug einer kWh (optional)
        feedprice Preis und Währung für die Einspeisung einer kWh (optional)
        + gcon Reading welches die aktuell aus dem Netz bezogene Leistung liefert + contotal Reading welches die Summe der aus dem Netz bezogenen Energie liefert (ein sich stetig erhöhender Zähler) + gfeedin Reading welches die aktuell in das Netz eingespeiste Leistung liefert + feedtotal Reading welches die Summe der in das Netz eingespeisten Energie liefert (ein sich stetig erhöhender Zähler) + Einheit die jeweilige Einheit (W,kW,Wh,kWh) + + conprice Preis für den Bezug einer kWh (optional). Die Angabe <Feld> ist in einer der folgenden Varianten möglich: + <Preis>:<Währung> - Preis als numerischer Wert und dessen Währung + <Reading>:<Währung> - Reading des Meter Device das den Preis enthält : Währung + <Device>:<Reading>:<Währung> - beliebiges Device und Reading welches den Preis enthält : Währung + + feedprice Vergütung für die Einspeisung einer kWh (optional). Die Angabe <Feld> ist in einer der folgenden Varianten möglich: + <Vergütung>:<Währung> - Vergütung als numerischer Wert und dessen Währung + <Reading>:<Währung> - Reading des Meter Device das die Vergütung enthält : Währung + <Device>:<Reading>:<Währung> - beliebiges Device und Reading welches die Vergütung enthält : Währung +

      @@ -20056,7 +20100,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
        Beispiel:
        - set <name> currentMeterDev Meter gcon=Wirkleistung:W contotal=BezWirkZaehler:kWh gfeedin=-gcon feedtotal=EinWirkZaehler:kWh conprice=0.2958:€ feedprice=0.1269:€
        + set <name> currentMeterDev Meter gcon=Wirkleistung:W contotal=BezWirkZaehler:kWh gfeedin=-gcon feedtotal=EinWirkZaehler:kWh conprice=powerCost:€ feedprice=0.1269:€

        # Device Meter liefert den aktuellen Netzbezug im Reading "Wirkleistung" (W), die Summe des Netzbezugs im Reading "BezWirkZaehler" (kWh), die aktuelle Einspeisung in "Wirkleistung" wenn "Wirkleistung" negativ ist, @@ -21489,7 +21533,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
      • ctrlWeatherDevX

        - Gibt das Gerät oder die API an, das/die die erforderlichen Wetterdaten (Wolkendecke, Niederschlag usw.) liefert.
        + Gibt das Gerät oder die API zur Lieferung der erforderlichen Wetterdaten (Wolkendecke, Niederschlag usw.) an.
        Das Attribut 'ctrlWeatherDev1' definiert den führenden Wetterdienst und ist zwingend erforderlich.
        Ist eine Open-Meteo API im Attribut 'ctrlWeatherDev1' ausgewählt, wird dieser Open-Meteo Dienst automatisch auch als Quelle der Strahlungsdaten (Setter currentRadiationAPI) eingestellt.