diff --git a/fhem/CHANGED b/fhem/CHANGED index d6c062ce3..1febd556d 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 + - change: 76_SolarForecast: change set currentMeterDev to attr setupMeterDev - feature: 76_SolarForecast: ctrlStatisticReadings: new runTimeAvgDayConsumer - change: 76_SolarForecast: ctrlDebug consumerSwitching splitted into separated consumers, minor bug fix diff --git a/fhem/FHEM/76_SolarForecast.pm b/fhem/FHEM/76_SolarForecast.pm index 9314b77c1..226d604af 100644 --- a/fhem/FHEM/76_SolarForecast.pm +++ b/fhem/FHEM/76_SolarForecast.pm @@ -157,6 +157,8 @@ BEGIN { # Versions History intern my %vNotesIntern = ( + "1.22.0" => "01.06.2024 change setter currentMeterDev to attr setupMeterDev, plantConfiguration: setModel after restore ", + "1.21.5" => "30.05.2024 listDataPool: list current can operate three hash levels, first preparation for remote objects ", "1.21.4" => "28.05.2024 __getCyclesAndRuntime: rename numberDayStarts to cycleDayNum ". "currentRunMtsConsumer_XX: edit commandref, Consumers: replace avgruntime by runtimeAvgDay ". "ctrlStatisticReadings: new runTimeAvgDayConsumer_XX, pvHistory: new key avgcycmntscsmXX", @@ -489,7 +491,6 @@ my @fs = qw( ftui_forecast.css my @rconfigs = qw( pvCorrectionFactor_Auto currentBatteryDev currentInverterDev - currentMeterDev currentRadiationAPI inverterStrings moduleAzimuth @@ -521,6 +522,7 @@ my @aconfigs = qw( affect70percentRule affectBatteryPreferredCharge affectConsFo graphicHeaderDetail graphicHeaderShow graphicHistoryHour graphicHourCount graphicHourStyle graphicLayoutType graphicSelect graphicShowDiff graphicShowNight graphicShowWeather graphicSpaceSize graphicStartHtml graphicEndHtml graphicWeatherColor graphicWeatherColorNight + setupMeterDev ); for my $cinit (1..$maxconsumer) { @@ -542,7 +544,6 @@ my %hset = ( # Ha inverterStrings => { fn => \&_setinverterStrings }, clientAction => { fn => \&_setclientAction }, currentInverterDev => { fn => \&_setinverterDevice }, - currentMeterDev => { fn => \&_setmeterDevice }, currentBatteryDev => { fn => \&_setbatteryDevice }, energyH4Trigger => { fn => \&_setTrigger }, plantConfiguration => { fn => \&_setplantConfiguration }, @@ -603,6 +604,7 @@ my %hattr = ( # H ctrlWeatherDev1 => { fn => \&_attrWeatherDev }, ctrlWeatherDev2 => { fn => \&_attrWeatherDev }, ctrlWeatherDev3 => { fn => \&_attrWeatherDev }, + setupMeterDev => { fn => \&_attrMeterDev }, ); my %htr = ( # Hash even/odd für @@ -651,8 +653,8 @@ my %hqtxt = ( DE => qq{Bitte geben sie den Strahlungsvorhersage Dienst mit "set LINK currentRadiationAPI" an} }, cid => { EN => qq{Please specify the Inverter device with "set LINK currentInverterDev"}, DE => qq{Bitte geben sie das Wechselrichter Device mit "set LINK currentInverterDev" an} }, - mid => { EN => qq{Please specify the device for energy measurement with "set LINK currentMeterDev"}, - DE => qq{Bitte geben sie das Device zur Energiemessung mit "set LINK currentMeterDev" an} }, + mid => { EN => qq{Please specify the device for energy measurement with "attr LINK setupMeterDev"}, + DE => qq{Bitte geben sie das Device zur Energiemessung mit "attr LINK setupMeterDev" an} }, ist => { EN => qq{Please define all of your used string names with "set LINK inverterStrings"}, DE => qq{Bitte geben sie alle von Ihnen verwendeten Stringnamen mit "set LINK inverterStrings" an} }, mps => { EN => qq{Please enter the DC peak power of each string with "set LINK modulePeakString"}, @@ -1094,6 +1096,7 @@ my %hfspvh = ( # Information zu verwendeten internen Datenhashes # $data{$type}{$name}{circular} # Ringspeicher # $data{$type}{$name}{current} # current values +# $data{$type}{$name}{current}{x_remote} # Steuerung und Werte remote Devices # $data{$type}{$name}{pvhist} # historische Werte # $data{$type}{$name}{nexthours} # NextHours Werte # $data{$type}{$name}{consumers} # Consumer Hash @@ -1210,6 +1213,7 @@ sub Initialize { "graphicEndHtml ". "graphicWeatherColor:colorpicker,RGB ". "graphicWeatherColorNight:colorpicker,RGB ". + "setupMeterDev:textField-long ". $consumer. $readingFnAttributes; @@ -1463,7 +1467,6 @@ sub Set { "currentRadiationAPI:$rdd ". "currentBatteryDev:textField-long ". "currentInverterDev:textField-long ". - "currentMeterDev:textField-long ". "energyH4Trigger:textField-long ". "inverterStrings ". "modulePeakString ". @@ -1861,56 +1864,6 @@ sub _setinverterStrings { ## no critic "not used" return $ret; } -################################################################ -# Setter currentMeterDev -################################################################ -sub _setmeterDevice { ## no critic "not used" - my $paref = shift; - my $hash = $paref->{hash}; - my $name = $paref->{name}; - my $type = $paref->{type}; - my $opt = $paref->{opt}; - my $arg = $paref->{arg}; - - if (!$arg) { - return qq{The command "$opt" needs an argument !}; - } - - my ($err, $medev, $h) = isDeviceValid ( { name => $name, obj => $arg, method => 'string' } ); - return $err if($err); - - if (!$h->{gcon} || !$h->{contotal} || !$h->{gfeedin} || !$h->{feedtotal}) { - return qq{The syntax of "$opt" is not correct. Please consider the commandref.}; - } - - if ($h->{gcon} eq "-gfeedin" && $h->{gfeedin} eq "-gcon") { - return qq{Incorrect input. It is not allowed that the keys gcon and gfeedin refer to each other.}; - } - - 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}) { # 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 - ############################### - delete $data{$type}{$name}{circular}{'99'}{feedintotal}; - delete $data{$type}{$name}{circular}{'99'}{initdayfeedin}; - delete $data{$type}{$name}{circular}{'99'}{gridcontotal}; - delete $data{$type}{$name}{circular}{'99'}{initdaygcon}; - - readingsSingleUpdate ($hash, 'currentMeterDev', $arg, 1); - createAssociatedWith ($hash); - writeCacheToFile ($hash, 'plantconfig', $plantcfg.$name); # Anlagenkonfiguration File schreiben - -return; -} - ################################################################ # Setter currentBatteryDev ################################################################ @@ -2168,6 +2121,7 @@ sub _setplantConfiguration { ## no critic "not used" if (!$err) { if ($nr || $na) { + setModel ($hash); return qq{Plant Configuration restored from file "$plantcfg.$name". Number of restored Readings/Attributes: $nr/$na}; } else { @@ -2449,7 +2403,6 @@ sub _setreset { ## no critic "not used" if ($prop eq 'currentMeterSet') { readingsDelete ($hash, "Current_GridConsumption"); readingsDelete ($hash, "Current_GridFeedIn"); - readingsDelete ($hash, 'currentMeterDev'); delete $data{$type}{$name}{circular}{'99'}{initdayfeedin}; delete $data{$type}{$name}{circular}{'99'}{gridcontotal}; delete $data{$type}{$name}{circular}{'99'}{initdaygcon}; @@ -2461,6 +2414,11 @@ sub _setreset { ## no critic "not used" delete $data{$type}{$name}{current}{autarkyrate}; delete $data{$type}{$name}{current}{selfconsumption}; delete $data{$type}{$name}{current}{selfconsumptionrate}; + delete $data{$type}{$name}{current}{eFeedInTariff}; + delete $data{$type}{$name}{current}{eFeedInTariffCcy}; + delete $data{$type}{$name}{current}{ePurchasePrice}; + delete $data{$type}{$name}{current}{ePurchasePriceCcy}; + delete $data{$type}{$name}{current}{x_remote}; writeCacheToFile ($hash, "plantconfig", $plantcfg.$name); # Anlagenkonfiguration File schreiben } @@ -4671,7 +4629,7 @@ sub _getlistCurrent { my $hash = $paref->{hash}; my $ret = listDataPool ($hash, 'current'); - $ret .= lineFromSpaces ($ret, 5); + $ret .= lineFromSpaces ($ret, 30); return $ret; } @@ -5595,7 +5553,7 @@ sub _attrconsumer { ## no critic "not used" delete $data{$type}{$name}{consumers}{$c}; # Consumer Hash Verbraucher löschen } - writeCacheToFile ($hash, "consumers", $csmcache.$name); # Cache File Consumer schreiben + writeCacheToFile ($hash, 'consumers', $csmcache.$name); # Cache File Consumer schreiben $data{$type}{$name}{current}{consumerCollected} = 0; # Consumer neu sammeln @@ -5677,6 +5635,52 @@ sub _attrctrlDebug { ## no critic "not used" return; } +################################################################ +# Attr setupMeterDev +################################################################ +sub _attrMeterDev { ## no critic "not used" + my $paref = shift; + my $hash = $paref->{hash}; + my $name = $paref->{name}; + my $aVal = $paref->{aVal}; + my $aName = $paref->{aName}; + my $type = $paref->{type}; + + return if(!$init_done); + + if ($paref->{cmd} eq 'set' ) { + my ($err, $medev, $h) = isDeviceValid ( { name => $name, obj => $aVal, method => 'string' } ); + return $err if($err); + + if (!$h->{gcon} || !$h->{contotal} || !$h->{gfeedin} || !$h->{feedtotal}) { + return qq{The syntax of "$aName" is not correct. Please consider the commandref.}; + } + + if ($h->{gcon} eq "-gfeedin" && $h->{gfeedin} eq "-gcon") { + return qq{Incorrect input. It is not allowed that the keys gcon and gfeedin refer to each other.}; + } + + 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}) { # 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); + } + } + + if ($paref->{cmd} eq 'del' ) { + delete $data{$type}{$name}{current}{x_remote}; + } + + InternalTimer (gettimeofday()+2, 'FHEM::SolarForecast::createAssociatedWith', $hash, 0); + InternalTimer (gettimeofday()+3, 'FHEM::SolarForecast::writeCacheToFile', [$name, 'plantconfig', $plantcfg.$name], 0); # Anlagenkonfiguration File schreiben + +return; +} + ################################################################ # Attr ctrlWeatherDevX ################################################################ @@ -5724,16 +5728,16 @@ sub _attrgraphicBeamXContent { ## no critic "not used" return if(!$init_done); - my $medev = ReadingsVal ($name, "currentMeterDev", ""); # aktuelles Meter device + my $medev = AttrVal ($name, 'setupMeterDev', ''); # aktuelles Meter device my ($a,$h) = parseParams ($medev); - if ($cmd eq "set") { + if ($cmd eq 'set') { if ($aVal eq 'energycosts') { - return "Define key 'conprice' in the currentMeterDev first before setting $aVal" if(!defined $h->{conprice}); + return "Define key 'conprice' in the setupMeterDev attribute first before setting $aVal" if(!defined $h->{conprice}); } if ($aVal eq 'feedincome') { - return "Define key 'feedprice' in the currentMeterDev first before setting $aVal" if(!defined $h->{feedprice}); + return "Define key 'feedprice' in the setupMeterDev attribute first before setting $aVal" if(!defined $h->{feedprice}); } } @@ -6062,8 +6066,18 @@ sub writeCacheToFile { my $hash = shift; my $cachename = shift; my $file = shift; + + my $name; + if (ref $hash eq 'HASH') { + $name = $hash->{NAME}; + } + elsif (ref $hash eq 'ARRAY') { # Array Referenz wurde übergeben + $name = $hash->[0]; + $cachename = $hash->[1]; + $file = $hash->[2]; + $hash = $defs{$name}; + } - my $name = $hash->{NAME}; my $type = $hash->{TYPE}; my @data; @@ -6390,7 +6404,11 @@ sub centralTask { ### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !! ########################################################################################################################## - + my $val = ReadingsVal ($name, 'currentMeterDev', ''); # 01.06.2024 + if ($val) { + CommandAttr (undef, "$name setupMeterDev $val"); + readingsDelete ($hash, 'currentMeterDev'); + } ########################################################################################################################## setModel ($hash); # Model setzen @@ -6450,6 +6468,7 @@ sub centralTask { $centpars->{state} = 'updated'; # kann durch Subs überschrieben werden! + _composeRemoteObj ($centpars); # Remote Objekte identifizieren und zusammenstellen _collectAllRegConsumers ($centpars); # alle Verbraucher Infos laden _specialActivities ($centpars); # zusätzliche Events generieren + Sonderaufgaben _transferWeatherValues ($centpars); # Wetterwerte übertragen @@ -6679,6 +6698,92 @@ sub controller { return ($interval, $disabled, $inactive); } +##################################################################### +# Remote Objekte identifizieren und zusammenstellen +# @fhem.myds.me:8088/api/ +##################################################################### +sub _composeRemoteObj { + my $paref = shift; + my $name = $paref->{name}; + + $paref->{obj} = 'setupMeterDev'; + $paref->{method} = 'attr'; + + __remoteMeterObj ($paref); + + delete $paref->{method}; + delete $paref->{obj}; + +return; +} + +##################################################################### +# Remote Meter Objekt identifizieren und zusammenstellen +##################################################################### +sub __remoteMeterObj { + my $paref = shift; + my $name = $paref->{name}; + my $type = $paref->{type}; + my $obj = $paref->{obj}; + my $method = $paref->{method} // return qq{no extraction method for Object '$obj' defined}; + + my $err = ''; + my $dev = ''; + my $rem = ''; + my @acp = (); + my @afp = (); + + if ($method eq 'reading') { + $dev = ReadingsVal ($name, $obj, ''); + return qq{Reading '$obj' is not set or is empty} if(!$dev); + } + elsif ($method eq 'attr') { + $dev = AttrVal ($name, $obj, ''); + return qq{Attribute '$obj' is not set} if(!$dev); + } + elsif ($method eq 'string') { + return qq{Object '$obj' is empty} if(!$obj); + $dev = $obj; + } + + my ($a, $h) = parseParams ($dev); + $dev = $a->[0]; # das Device aus dem Object + ($dev, $rem) = split '@', $dev; # Meterdev extrahieren + + return if(!$rem); # keine remote Devicekonfiguration + + my ($server, $infix) = split '/', $rem; + my $gc = (split ":", $h->{gcon})[0]; # Readingname für aktuellen Netzbezug + my $gf = (split ":", $h->{gfeedin})[0]; # Readingname für aktuelle Netzeinspeisung + my $gt = (split ":", $h->{contotal})[0]; # Readingname für Bezug total + my $ft = (split ":", $h->{feedtotal})[0]; # Readingname für Einspeisung total + @acp = split ":", $h->{conprice} if($h->{conprice}); + @afp = split ":", $h->{feedprice} if($h->{feedprice}); + + $data{$type}{$name}{current}{x_remote}{$dev}{server} = $server; + $data{$type}{$name}{current}{x_remote}{$dev}{infix} = $infix; + $data{$type}{$name}{current}{x_remote}{$dev}{readings}{$gt} = undef; + $data{$type}{$name}{current}{x_remote}{$dev}{readings}{$ft} = undef; + $data{$type}{$name}{current}{x_remote}{$dev}{readings}{$gc} = undef if($h->{gcon} ne '-gfeedin'); + $data{$type}{$name}{current}{x_remote}{$dev}{readings}{$gf} = undef if($h->{gfeedin} ne '-gcon'); + + if (scalar(@acp) == 2) { + $data{$type}{$name}{current}{x_remote}{$dev}{readings}{$acp[0]} = undef; # conprice wird durch Reading im Meterdev geliefert + } + elsif (scalar(@acp) == 3) { # conprice wird durch weiteres Device / Reading geliefert + $data{$type}{$name}{current}{x_remote}{$acp[0]}{readings}{$acp[1]} = undef; + } + + if (scalar(@afp) == 2) { + $data{$type}{$name}{current}{x_remote}{$dev}{readings}{$afp[0]} = undef; # feedprice wird durch Reading im Meterdev geliefert + } + elsif (scalar(@afp) == 3) { # feedprice wird durch weiteres Device / Reading geliefert + $data{$type}{$name}{current}{x_remote}{$afp[0]}{readings}{$afp[1]} = undef; + } + +return; +} + ################################################################ # Grunddaten aller registrierten Consumer speichern ################################################################ @@ -7998,7 +8103,7 @@ sub _transferMeterValues { my $t = $paref->{t}; my $chour = $paref->{chour}; - my ($err, $medev, $h) = isDeviceValid ( { name => $name, obj => 'currentMeterDev', method => 'reading' } ); + my ($err, $medev, $h) = isDeviceValid ( { name => $name, obj => 'setupMeterDev', method => 'attr' } ); return if($err); my $type = $paref->{type}; @@ -8019,8 +8124,7 @@ sub _transferMeterValues { $data{$type}{$name}{current}{ePurchasePrice} = ReadingsNum ($acp[0], $acp[1], 0); $data{$type}{$name}{current}{ePurchasePriceCcy} = $acp[2]; } - - if (scalar(@acp) == 2) { + elsif (scalar(@acp) == 2) { if (isNumeric($acp[0])) { $data{$type}{$name}{current}{ePurchasePrice} = $acp[0]; $data{$type}{$name}{current}{ePurchasePriceCcy} = $acp[1]; @@ -8046,8 +8150,7 @@ sub _transferMeterValues { $data{$type}{$name}{current}{eFeedInTariff} = ReadingsNum ($afp[0], $afp[1], 0); $data{$type}{$name}{current}{eFeedInTariffCcy} = $afp[2]; } - - if (scalar(@afp) == 2) { + elsif (scalar(@afp) == 2) { if (isNumeric($afp[0])) { $data{$type}{$name}{current}{eFeedInTariff} = $afp[0]; $data{$type}{$name}{current}{eFeedInTariffCcy} = $afp[1]; @@ -8079,7 +8182,7 @@ sub _transferMeterValues { my $params; - if ($gc eq "-gfeedin") { # Spezialfall gcon bei neg. gfeedin # Spezialfall: bei negativen gfeedin -> $gco = abs($gf), $gf = 0 + if ($gc eq '-gfeedin') { # Spezialfall gcon bei neg. gfeedin # Spezialfall: bei negativen gfeedin -> $gco = abs($gf), $gf = 0 $params = { dev => $medev, rdg => $gf, @@ -8089,7 +8192,7 @@ sub _transferMeterValues { ($gfin,$gco) = substSpecialCases ($params); } - if ($gf eq "-gcon") { # Spezialfall gfeedin bei neg. gcon + if ($gf eq '-gcon') { # Spezialfall gfeedin bei neg. gcon $params = { dev => $medev, rdg => $gc, @@ -10245,16 +10348,16 @@ sub _estConsumptionForecast { my $dayname = $paref->{dayname}; # aktueller Tagname my ($err, $medev, $h) = isDeviceValid ( { name => $name, - obj => 'currentMeterDev', - method => 'reading', + obj => 'setupMeterDev', + method => 'attr', } ); # aktuelles Meter device return if($err); - my $swdfcfc = AttrVal ($name, "affectConsForecastIdentWeekdays", 0); # nutze nur gleiche Wochentage (Mo...So) für Verbrauchsvorhersage - my ($am,$hm) = parseParams ($medev); - my $type = $paref->{type}; - my $acref = $data{$type}{$name}{consumers}; + my $swdfcfc = AttrVal ($name, "affectConsForecastIdentWeekdays", 0); # nutze nur gleiche Wochentage (Mo...So) für Verbrauchsvorhersage + my ($am, $hm) = parseParams ($medev); + my $type = $paref->{type}; + my $acref = $data{$type}{$name}{consumers}; ## Verbrauchsvorhersage für den nächsten Tag ############################################## @@ -11401,7 +11504,7 @@ sub _checkSetupNotComplete { my $wedev = AttrVal ($name, 'ctrlWeatherDev1', undef); # Device Vorhersage Wetterdaten (Bewölkung etc.) my $radev = ReadingsVal ($name, 'currentRadiationAPI', undef); # Device Strahlungsdaten Vorhersage my $indev = ReadingsVal ($name, 'currentInverterDev', undef); # Inverter Device - my $medev = ReadingsVal ($name, 'currentMeterDev', undef); # Meter Device + my $medev = AttrVal ($name, 'setupMeterDev', undef); # Meter Device my $peaks = ReadingsVal ($name, 'modulePeakString', undef); # String Peak my $maz = ReadingsVal ($name, 'moduleAzimuth', undef); # Modulausrichtung Konfig (Azimut) @@ -14818,7 +14921,7 @@ sub listDataPool { my $cret; for my $ckey (sort keys %{$h->{$idx}}) { - if(ref $h->{$idx}{$ckey} eq "HASH") { + if (ref $h->{$idx}{$ckey} eq 'HASH') { my $hk = qq{}; for my $f (sort {$a<=>$b} keys %{$h->{$idx}{$ckey}}) { $hk .= " " if($hk); @@ -14977,13 +15080,49 @@ sub listDataPool { if (!keys %{$h}) { return qq{Current values cache is empty.}; } + for my $idx (sort keys %{$h}) { - if (ref $h->{$idx} ne "ARRAY") { - $sq .= $idx." => ".(defined $h->{$idx} ? $h->{$idx} : '')."\n"; + if (ref $h->{$idx} eq 'ARRAY') { + my $aser = join " ",@{$h->{$idx}}; + $sq .= $idx." => ".$aser."\n"; + } + elsif (ref $h->{$idx} eq 'HASH') { + my $s1; + my $sp1 = _ldpspaces ($idx, q{}); + $sq .= $idx." => "; + + for my $idx1 (sort keys %{$h->{$idx}}) { + if (ref $h->{$idx}{$idx1} eq 'HASH') { + my $s2; + my $sp2 = _ldpspaces ($idx1, $sp1); + $sq .= ($s1 ? $sp1 : "").$idx1." => "; + + for my $idx2 (sort keys %{$h->{$idx}{$idx1}}) { + my $s3; + my $sp3 = _ldpspaces ($idx2, $sp2); + $sq .= ($s2 ? $sp2 : "").$idx2." => "; + + if (ref $h->{$idx}{$idx1}{$idx2} eq 'HASH') { + for my $idx3 (sort keys %{$h->{$idx}{$idx1}{$idx2}}) { + $sq .= ($s3 ? $sp3 : "").$idx3." => ".(defined $h->{$idx}{$idx1}{$idx2}{$idx3} ? $h->{$idx}{$idx1}{$idx2}{$idx3} : '')."\n"; + $s3 = 1; + } + } + else { + $sq .= (defined $h->{$idx}{$idx1}{$idx2} ? $h->{$idx}{$idx1}{$idx2} : '')."\n"; + } + + $s1 = 1; + $s2 = 1; + } + } + else { + $sq .= (defined $h->{$idx}{$idx1} ? $h->{$idx}{$idx1} : '')."\n"; + } + } } else { - my $aser = join " ",@{$h->{$idx}}; - $sq .= $idx." => ".$aser."\n"; + $sq .= $idx." => ".(defined $h->{$idx} ? $h->{$idx} : '')."\n"; } } } @@ -15016,7 +15155,7 @@ sub listDataPool { while (my ($tag, $item) = each %{$git->($itref->{$idx})}) { $sq .= ($s1 ? $sp1 : "").$tag." => "; - if (ref $item eq "HASH") { + if (ref $item eq 'HASH') { my $s2; my $sp2 = _ldpspaces ($tag, $sp1); @@ -16012,7 +16151,7 @@ sub createAssociatedWith { my $name = $hash->{NAME}; my $type = $hash->{TYPE}; - RemoveInternalTimer($hash, "FHEM::SolarForecast::createAssociatedWith"); + RemoveInternalTimer ($hash, 'FHEM::SolarForecast::createAssociatedWith'); if ($init_done) { my (@cd, @nd); @@ -16030,24 +16169,24 @@ sub createAssociatedWith { ($afc,$h) = parseParams ($fcdev3); $fcdev3 = $afc->[0] // ""; - my $radev = ReadingsVal($name, 'currentRadiationAPI', ''); # Radiation forecast Device + my $radev = ReadingsVal ($name, 'currentRadiationAPI', ''); # Radiation forecast Device ($ara,$h) = parseParams ($radev); $radev = $ara->[0] // ""; - my $indev = ReadingsVal($name, 'currentInverterDev', ''); # Inverter Device + my $indev = ReadingsVal ($name, 'currentInverterDev', ''); # Inverter Device ($ain,$h) = parseParams ($indev); $indev = $ain->[0] // ""; - my $medev = ReadingsVal($name, 'currentMeterDev', ''); # Meter Device + my $medev = AttrVal ($name, 'setupMeterDev', ''); # Meter Device ($ame,$h) = parseParams ($medev); $medev = $ame->[0] // ""; - my $badev = ReadingsVal($name, 'currentBatteryDev', ''); # Battery Device + my $badev = ReadingsVal ($name, 'currentBatteryDev', ''); # Battery Device ($aba,$h) = parseParams ($badev); $badev = $aba->[0] // ""; for my $c (sort{$a<=>$b} keys %{$data{$type}{$name}{consumers}}) { # Consumer Devices - my $consumer = AttrVal($name, "consumer${c}", ""); + my $consumer = AttrVal ($name, "consumer${c}", ""); my ($ac,$hc) = parseParams ($consumer); my $codev = $ac->[0] // ''; my $dswitch = $hc->{switchdev} // ''; # alternatives Schaltdevice @@ -16085,7 +16224,7 @@ sub createAssociatedWith { } } else { - InternalTimer(gettimeofday()+3, "FHEM::SolarForecast::createAssociatedWith", $hash, 0); + InternalTimer (gettimeofday() + 3, 'FHEM::SolarForecast::createAssociatedWith', $hash, 0); } return; @@ -16777,6 +16916,11 @@ sub isDeviceValid { my ($a, $h) = parseParams ($dev); + if ($a->[0] && $a->[0] =~ /\@/xs ) { # Remote Device + $a->[0] = (split '@', $a->[0])[0]; + return ($err, $a->[0], $h); # ToDo: $h aus remote Werten anreichern + } + if (!$a->[0] || !$defs{$a->[0]}) { $a->[0] //= ''; $err = qq{The device '$a->[0]' doesn't exist or is not a valid device.}; @@ -17964,19 +18108,19 @@ To create the solar forecast, the SolarForecast module can use different service
-AI support can be enabled when using the Model DWD.
The use of the mentioned API's is limited to the respective free version of the selected service.
-In the assigned DWD_OpenData Device (attribute "ctrlWeatherDevX") the suitable weather station is to be specified -to get meteorological data (cloudiness, sunrise, etc.) or a radiation forecast (Model DWD) for the plant -location.

+AI support can be activated depending on the model used.

In addition to the PV generation forecast, consumption values or grid reference values are recorded and used for a consumption forecast.
@@ -18003,7 +18147,7 @@ to ensure that the system configuration is correct.
After the definition of the device, depending on the forecast sources used, it is mandatory to store additional - plant-specific information with the corresponding set commands.
+ plant-specific information.
The following set commands and attributes are used to store information that is relevant for the function of the module:

@@ -18013,7 +18157,7 @@ to ensure that the system configuration is correct. ctrlWeatherDevX DWD_OpenData Device which provides meteorological data (e.g. cloud cover) currentRadiationAPI DWD_OpenData Device or API for the delivery of radiation data. currentInverterDev Device which provides PV performance data - currentMeterDev Device which supplies network I/O data + setupMeterDev Device which supplies network I/O data currentBatteryDev Device which provides battery performance data (if available) inverterStrings Identifier of the existing plant strings moduleAzimuth Azimuth of the plant strings @@ -18229,65 +18373,6 @@ to ensure that the system configuration is correct.
- -
- @@ -20236,20 +20378,20 @@ Zur Erstellung der solaren Vorhersage kann das Modul SolarForecast unterschiedli
-Bei Verwendung des Model DWD kann eine KI-Unterstützung aktiviert werden.
Die Nutzung der erwähnten API's beschränkt sich auf die jeweils kostenlose Version des Dienstes.
-Im zugeordneten DWD_OpenData Device (Attribut "ctrlWeatherDevX") ist die passende Wetterstation -festzulegen um meteorologische Daten (Bewölkung, Sonnenaufgang, u.a.) bzw. eine Strahlungsprognose (Model DWD) -für den Anlagenstandort zu erhalten.

+Die KI-Unterstützung kann in Abhängigkeit vom verwendeten Model aktiviert werden.

Über die PV Erzeugungsprognose hinaus werden Verbrauchswerte bzw. Netzbezugswerte erfasst und für eine Verbrauchsprognose verwendet.
@@ -20257,7 +20399,7 @@ Das Modul errechnet aus den Prognosewerten einen zukünftigen Energieüberschuß genutzt wird. Weiterhin bietet das Modul eine Consumer Integration zur integrierten Planung und Steuerung von PV Überschuß abhängigen Verbraucherschaltungen.

-Bei der ersten Definition des Moduls wird der Benutzer über eine Guided Procedure unterstützt um alle initialen Eingaben +Bei der ersten Definition des Moduls wird der Benutzer über eine Guided Procedure unterstützt um alle initial notwendigen Eingaben vorzunehmen.
Am Ende des Vorganges und nach relevanten Änderungen der Anlagen- bzw. Devicekonfiguration sollte unbedingt mit einem set <name> plantConfiguration ceck @@ -20277,8 +20419,8 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
Nach der Definition des Devices sind in Abhängigkeit der verwendeten Prognosequellen zwingend weitere - anlagenspezifische Angaben mit den entsprechenden set-Kommandos zu hinterlegen.
- Mit nachfolgenden set-Kommandos und Attributen werden für die Funktion des Moduls maßgebliche Informationen + anlagenspezifische Angaben zu hinterlegen.
+ Mit nachfolgenden Set-Kommandos und Attributen werden für die Funktion des Moduls maßgebliche Informationen hinterlegt:


- -
-
- Hinweis: Die Auswahl der Parameter energycosts und feedincome ist nur sinnvoll wenn in currentMeterDev die + Hinweis: Die Auswahl der Parameter energycosts und feedincome ist nur sinnvoll wenn in setupMeterDev die optionalen Schlüssel conprice und feedprice gesetzt sind.
@@ -22500,6 +22583,63 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. Farbe der Wetter-Icons für die Nachtstunden.
+ + +
  • setupMeterDev <Meter Device Name> gcon=<Readingname>:<Einheit> contotal=<Readingname>:<Einheit> + gfeedin=<Readingname>:<Einheit> feedtotal=<Readingname>:<Einheit> + [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. + Es kann auch ein Dummy Device mit entsprechenden Readings sein. Die Bedeutung des jeweiligen "Readingname" ist: +

    + + +
    + + Sonderfälle: Sollte das Reading für gcon und gfeedin identisch, aber vorzeichenbehaftet sein, + können die Schlüssel gfeedin und gcon wie folgt definiert werden:

    + +
    + + Die Einheit entfällt in dem jeweiligen Sonderfall.

    + + +
  • +
    diff --git a/fhem/contrib/DS_Starter/76_SolarForecast.pm b/fhem/contrib/DS_Starter/76_SolarForecast.pm index 1da629960..226d604af 100644 --- a/fhem/contrib/DS_Starter/76_SolarForecast.pm +++ b/fhem/contrib/DS_Starter/76_SolarForecast.pm @@ -157,6 +157,14 @@ BEGIN { # Versions History intern my %vNotesIntern = ( + "1.22.0" => "01.06.2024 change setter currentMeterDev to attr setupMeterDev, plantConfiguration: setModel after restore ", + "1.21.5" => "30.05.2024 listDataPool: list current can operate three hash levels, first preparation for remote objects ", + "1.21.4" => "28.05.2024 __getCyclesAndRuntime: rename numberDayStarts to cycleDayNum ". + "currentRunMtsConsumer_XX: edit commandref, Consumers: replace avgruntime by runtimeAvgDay ". + "ctrlStatisticReadings: new runTimeAvgDayConsumer_XX, pvHistory: new key avgcycmntscsmXX", + "1.21.3" => "27.05.2024 __getCyclesAndRuntime: change procedure determine consumer runtime and cycles per day ". + "__calcPVestimates: correct printout 'Estimated PV generation (calc)' and '(raw)' ". + "ctrlDebug: consumerSwitching splitted into separated consumers ", "1.21.2" => "26.05.2024 __VictronVRM_ApiRequestForecast: change request time from current time to ':00:00'", "1.21.1" => "23.05.2024 new sub isDeviceValid, replace Smartmatch Forum:#137776 ", "1.21.0" => "14.05.2024 currentMeterDev: meter can be a Day meter, contotal and feedtotal can be reset at day begin ", @@ -248,7 +256,7 @@ my %vNotesIntern = ( "1.6.4" => "09.01.2024 fix get Automatic State, use key switchdev for auto-Reading if switchdev is set in consumer attr ", "1.6.3" => "08.01.2024 optimize battery management once more ", "1.6.2" => "07.01.2024 optimize battery management ", - "1.6.1" => "04.01.2024 new sub __setPhysSwState, edit ___setConsumerPlanningState, boost performance of _collectAllRegConsumers ". + "1.6.1" => "04.01.2024 new sub __setPhysLogSwState, edit ___setConsumerPlanningState, boost performance of _collectAllRegConsumers ". "CurrentVal ctrunning - Central Task running Statusbit, edit comref ", "1.6.0" => "22.12.2023 store daily batmaxsoc in pvHistory, new attr ctrlBatSocManagement, reading Battery_OptimumTargetSoC ". "currentBatteryDev: new optional key 'cap', adapt cloud2bin,temp2bin,rain2bin ". @@ -456,15 +464,13 @@ my $cssdef = qq{.flowg.text { stroke: none; fill: gray; font-size: 60p ; # mögliche Debug-Module -my @dd = qw( none - aiProcess +my @dd = qw( aiProcess aiData apiCall apiProcess batteryManagement collectData consumerPlanning - consumerSwitching consumption dwdComm epiecesCalc @@ -485,7 +491,6 @@ my @fs = qw( ftui_forecast.css my @rconfigs = qw( pvCorrectionFactor_Auto currentBatteryDev currentInverterDev - currentMeterDev currentRadiationAPI inverterStrings moduleAzimuth @@ -517,13 +522,14 @@ my @aconfigs = qw( affect70percentRule affectBatteryPreferredCharge affectConsFo graphicHeaderDetail graphicHeaderShow graphicHistoryHour graphicHourCount graphicHourStyle graphicLayoutType graphicSelect graphicShowDiff graphicShowNight graphicShowWeather graphicSpaceSize graphicStartHtml graphicEndHtml graphicWeatherColor graphicWeatherColorNight + setupMeterDev ); - for my $cinit (1..$maxconsumer) { # Anlagenkonfiguration: add Consumer Attribute - $cinit = sprintf "%02d", $cinit; - my $consumer = "consumer${cinit}"; - push @aconfigs, $consumer; - } +for my $cinit (1..$maxconsumer) { + $cinit = sprintf "%02d", $cinit; + push @aconfigs, "consumer${cinit}"; # Anlagenkonfiguration: add Consumer Attribute + push @dd, "consumerSwitching${cinit}"; # ctrlDebug: add specific Consumer +} my $allwidgets = 'icon|sortable|uzsu|knob|noArg|time|text|slider|multiple|select|bitfield|widgetList|colorpicker'; @@ -538,7 +544,6 @@ my %hset = ( # Ha inverterStrings => { fn => \&_setinverterStrings }, clientAction => { fn => \&_setclientAction }, currentInverterDev => { fn => \&_setinverterDevice }, - currentMeterDev => { fn => \&_setmeterDevice }, currentBatteryDev => { fn => \&_setbatteryDevice }, energyH4Trigger => { fn => \&_setTrigger }, plantConfiguration => { fn => \&_setplantConfiguration }, @@ -595,9 +600,11 @@ my %hattr = ( # H consumer => { fn => \&_attrconsumer }, ctrlConsRecommendReadings => { fn => \&_attrcreateConsRecRdgs }, ctrlStatisticReadings => { fn => \&_attrcreateStatisticRdgs }, + ctrlDebug => { fn => \&_attrctrlDebug }, ctrlWeatherDev1 => { fn => \&_attrWeatherDev }, ctrlWeatherDev2 => { fn => \&_attrWeatherDev }, ctrlWeatherDev3 => { fn => \&_attrWeatherDev }, + setupMeterDev => { fn => \&_attrMeterDev }, ); my %htr = ( # Hash even/odd für @@ -646,8 +653,8 @@ my %hqtxt = ( DE => qq{Bitte geben sie den Strahlungsvorhersage Dienst mit "set LINK currentRadiationAPI" an} }, cid => { EN => qq{Please specify the Inverter device with "set LINK currentInverterDev"}, DE => qq{Bitte geben sie das Wechselrichter Device mit "set LINK currentInverterDev" an} }, - mid => { EN => qq{Please specify the device for energy measurement with "set LINK currentMeterDev"}, - DE => qq{Bitte geben sie das Device zur Energiemessung mit "set LINK currentMeterDev" an} }, + mid => { EN => qq{Please specify the device for energy measurement with "attr LINK setupMeterDev"}, + DE => qq{Bitte geben sie das Device zur Energiemessung mit "attr LINK setupMeterDev" an} }, ist => { EN => qq{Please define all of your used string names with "set LINK inverterStrings"}, DE => qq{Bitte geben sie alle von Ihnen verwendeten Stringnamen mit "set LINK inverterStrings" an} }, mps => { EN => qq{Please enter the DC peak power of each string with "set LINK modulePeakString"}, @@ -1040,11 +1047,18 @@ my %hcsr = ( for my $csr (1..$maxconsumer) { $csr = sprintf "%02d", $csr; + $hcsr{'currentRunMtsConsumer_'.$csr}{fnr} = 4; $hcsr{'currentRunMtsConsumer_'.$csr}{fn} = \&ConsumerVal; $hcsr{'currentRunMtsConsumer_'.$csr}{par} = 'cycleTime'; $hcsr{'currentRunMtsConsumer_'.$csr}{unit} = ' min'; $hcsr{'currentRunMtsConsumer_'.$csr}{def} = 0; + + $hcsr{'runTimeAvgDayConsumer_'.$csr}{fnr} = 4; + $hcsr{'runTimeAvgDayConsumer_'.$csr}{fn} = \&ConsumerVal; + $hcsr{'runTimeAvgDayConsumer_'.$csr}{par} = 'runtimeAvgDay'; + $hcsr{'runTimeAvgDayConsumer_'.$csr}{unit} = ' min'; + $hcsr{'runTimeAvgDayConsumer_'.$csr}{def} = 0; } # Funktiontemplate zur Speicherung von Werten in pvHistory @@ -1082,6 +1096,7 @@ my %hfspvh = ( # Information zu verwendeten internen Datenhashes # $data{$type}{$name}{circular} # Ringspeicher # $data{$type}{$name}{current} # current values +# $data{$type}{$name}{current}{x_remote} # Steuerung und Werte remote Devices # $data{$type}{$name}{pvhist} # historische Werte # $data{$type}{$name}{nexthours} # NextHours Werte # $data{$type}{$name}{consumers} # Consumer Hash @@ -1113,7 +1128,7 @@ sub Initialize { } my $allcs = join ",", @allc; - my $dm = join ",", @dd; + my $dm = 'none,'.join ",", sort @dd; $hash->{DefFn} = \&Define; $hash->{UndefFn} = \&Undef; @@ -1198,6 +1213,7 @@ sub Initialize { "graphicEndHtml ". "graphicWeatherColor:colorpicker,RGB ". "graphicWeatherColorNight:colorpicker,RGB ". + "setupMeterDev:textField-long ". $consumer. $readingFnAttributes; @@ -1451,7 +1467,6 @@ sub Set { "currentRadiationAPI:$rdd ". "currentBatteryDev:textField-long ". "currentInverterDev:textField-long ". - "currentMeterDev:textField-long ". "energyH4Trigger:textField-long ". "inverterStrings ". "modulePeakString ". @@ -1849,56 +1864,6 @@ sub _setinverterStrings { ## no critic "not used" return $ret; } -################################################################ -# Setter currentMeterDev -################################################################ -sub _setmeterDevice { ## no critic "not used" - my $paref = shift; - my $hash = $paref->{hash}; - my $name = $paref->{name}; - my $type = $paref->{type}; - my $opt = $paref->{opt}; - my $arg = $paref->{arg}; - - if (!$arg) { - return qq{The command "$opt" needs an argument !}; - } - - my ($err, $medev, $h) = isDeviceValid ( { name => $name, obj => $arg, method => 'string' } ); - return $err if($err); - - if (!$h->{gcon} || !$h->{contotal} || !$h->{gfeedin} || !$h->{feedtotal}) { - return qq{The syntax of "$opt" is not correct. Please consider the commandref.}; - } - - if ($h->{gcon} eq "-gfeedin" && $h->{gfeedin} eq "-gcon") { - return qq{Incorrect input. It is not allowed that the keys gcon and gfeedin refer to each other.}; - } - - 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}) { # 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 - ############################### - delete $data{$type}{$name}{circular}{'99'}{feedintotal}; - delete $data{$type}{$name}{circular}{'99'}{initdayfeedin}; - delete $data{$type}{$name}{circular}{'99'}{gridcontotal}; - delete $data{$type}{$name}{circular}{'99'}{initdaygcon}; - - readingsSingleUpdate ($hash, 'currentMeterDev', $arg, 1); - createAssociatedWith ($hash); - writeCacheToFile ($hash, 'plantconfig', $plantcfg.$name); # Anlagenkonfiguration File schreiben - -return; -} - ################################################################ # Setter currentBatteryDev ################################################################ @@ -2156,6 +2121,7 @@ sub _setplantConfiguration { ## no critic "not used" if (!$err) { if ($nr || $na) { + setModel ($hash); return qq{Plant Configuration restored from file "$plantcfg.$name". Number of restored Readings/Attributes: $nr/$na}; } else { @@ -2437,7 +2403,6 @@ sub _setreset { ## no critic "not used" if ($prop eq 'currentMeterSet') { readingsDelete ($hash, "Current_GridConsumption"); readingsDelete ($hash, "Current_GridFeedIn"); - readingsDelete ($hash, 'currentMeterDev'); delete $data{$type}{$name}{circular}{'99'}{initdayfeedin}; delete $data{$type}{$name}{circular}{'99'}{gridcontotal}; delete $data{$type}{$name}{circular}{'99'}{initdaygcon}; @@ -2449,6 +2414,11 @@ sub _setreset { ## no critic "not used" delete $data{$type}{$name}{current}{autarkyrate}; delete $data{$type}{$name}{current}{selfconsumption}; delete $data{$type}{$name}{current}{selfconsumptionrate}; + delete $data{$type}{$name}{current}{eFeedInTariff}; + delete $data{$type}{$name}{current}{eFeedInTariffCcy}; + delete $data{$type}{$name}{current}{ePurchasePrice}; + delete $data{$type}{$name}{current}{ePurchasePriceCcy}; + delete $data{$type}{$name}{current}{x_remote}; writeCacheToFile ($hash, "plantconfig", $plantcfg.$name); # Anlagenkonfiguration File schreiben } @@ -4659,7 +4629,7 @@ sub _getlistCurrent { my $hash = $paref->{hash}; my $ret = listDataPool ($hash, 'current'); - $ret .= lineFromSpaces ($ret, 5); + $ret .= lineFromSpaces ($ret, 30); return $ret; } @@ -5583,7 +5553,7 @@ sub _attrconsumer { ## no critic "not used" delete $data{$type}{$name}{consumers}{$c}; # Consumer Hash Verbraucher löschen } - writeCacheToFile ($hash, "consumers", $csmcache.$name); # Cache File Consumer schreiben + writeCacheToFile ($hash, 'consumers', $csmcache.$name); # Cache File Consumer schreiben $data{$type}{$name}{current}{consumerCollected} = 0; # Consumer neu sammeln @@ -5618,7 +5588,7 @@ sub _attrcreateStatisticRdgs { ## no critic "not used" my $aName = $paref->{aName}; my $aVal = $paref->{aVal}; - my $te = 'currentRunMtsConsumer_'; + my $te = 'currentRunMtsConsumer_|runTimeAvgDayConsumer_'; if ($aVal =~ /$te/xs && $init_done) { my @aa = split ",", $aVal; @@ -5637,6 +5607,80 @@ sub _attrcreateStatisticRdgs { ## no critic "not used" return; } +################################################################ +# Attr ctrlDebug +################################################################ +sub _attrctrlDebug { ## no critic "not used" + my $paref = shift; + my $hash = $paref->{hash}; + my $name = $paref->{name}; + my $aName = $paref->{aName}; + my $aVal = $paref->{aVal}; + + my $te = 'consumerSwitching'; + + if ($aVal =~ /$te/xs && $init_done) { + my @aa = split ",", $aVal; + + for my $elm (@aa) { + next if($elm !~ /$te/xs); + $elm =~ /([0-9]{2})/xs; # Consumer Nummer filetieren + + if (!AttrVal ($name, 'consumer'.$1, '')) { + return qq{The consumer 'consumer$1' is currently not registered as an active consumer!}; + } + } + } + +return; +} + +################################################################ +# Attr setupMeterDev +################################################################ +sub _attrMeterDev { ## no critic "not used" + my $paref = shift; + my $hash = $paref->{hash}; + my $name = $paref->{name}; + my $aVal = $paref->{aVal}; + my $aName = $paref->{aName}; + my $type = $paref->{type}; + + return if(!$init_done); + + if ($paref->{cmd} eq 'set' ) { + my ($err, $medev, $h) = isDeviceValid ( { name => $name, obj => $aVal, method => 'string' } ); + return $err if($err); + + if (!$h->{gcon} || !$h->{contotal} || !$h->{gfeedin} || !$h->{feedtotal}) { + return qq{The syntax of "$aName" is not correct. Please consider the commandref.}; + } + + if ($h->{gcon} eq "-gfeedin" && $h->{gfeedin} eq "-gcon") { + return qq{Incorrect input. It is not allowed that the keys gcon and gfeedin refer to each other.}; + } + + 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}) { # 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); + } + } + + if ($paref->{cmd} eq 'del' ) { + delete $data{$type}{$name}{current}{x_remote}; + } + + InternalTimer (gettimeofday()+2, 'FHEM::SolarForecast::createAssociatedWith', $hash, 0); + InternalTimer (gettimeofday()+3, 'FHEM::SolarForecast::writeCacheToFile', [$name, 'plantconfig', $plantcfg.$name], 0); # Anlagenkonfiguration File schreiben + +return; +} + ################################################################ # Attr ctrlWeatherDevX ################################################################ @@ -5684,16 +5728,16 @@ sub _attrgraphicBeamXContent { ## no critic "not used" return if(!$init_done); - my $medev = ReadingsVal ($name, "currentMeterDev", ""); # aktuelles Meter device + my $medev = AttrVal ($name, 'setupMeterDev', ''); # aktuelles Meter device my ($a,$h) = parseParams ($medev); - if ($cmd eq "set") { + if ($cmd eq 'set') { if ($aVal eq 'energycosts') { - return "Define key 'conprice' in the currentMeterDev first before setting $aVal" if(!defined $h->{conprice}); + return "Define key 'conprice' in the setupMeterDev attribute first before setting $aVal" if(!defined $h->{conprice}); } if ($aVal eq 'feedincome') { - return "Define key 'feedprice' in the currentMeterDev first before setting $aVal" if(!defined $h->{feedprice}); + return "Define key 'feedprice' in the setupMeterDev attribute first before setting $aVal" if(!defined $h->{feedprice}); } } @@ -6022,8 +6066,18 @@ sub writeCacheToFile { my $hash = shift; my $cachename = shift; my $file = shift; + + my $name; + if (ref $hash eq 'HASH') { + $name = $hash->{NAME}; + } + elsif (ref $hash eq 'ARRAY') { # Array Referenz wurde übergeben + $name = $hash->[0]; + $cachename = $hash->[1]; + $file = $hash->[2]; + $hash = $defs{$name}; + } - my $name = $hash->{NAME}; my $type = $hash->{TYPE}; my @data; @@ -6350,7 +6404,11 @@ sub centralTask { ### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !! ########################################################################################################################## - + my $val = ReadingsVal ($name, 'currentMeterDev', ''); # 01.06.2024 + if ($val) { + CommandAttr (undef, "$name setupMeterDev $val"); + readingsDelete ($hash, 'currentMeterDev'); + } ########################################################################################################################## setModel ($hash); # Model setzen @@ -6410,6 +6468,7 @@ sub centralTask { $centpars->{state} = 'updated'; # kann durch Subs überschrieben werden! + _composeRemoteObj ($centpars); # Remote Objekte identifizieren und zusammenstellen _collectAllRegConsumers ($centpars); # alle Verbraucher Infos laden _specialActivities ($centpars); # zusätzliche Events generieren + Sonderaufgaben _transferWeatherValues ($centpars); # Wetterwerte übertragen @@ -6639,6 +6698,92 @@ sub controller { return ($interval, $disabled, $inactive); } +##################################################################### +# Remote Objekte identifizieren und zusammenstellen +# @fhem.myds.me:8088/api/ +##################################################################### +sub _composeRemoteObj { + my $paref = shift; + my $name = $paref->{name}; + + $paref->{obj} = 'setupMeterDev'; + $paref->{method} = 'attr'; + + __remoteMeterObj ($paref); + + delete $paref->{method}; + delete $paref->{obj}; + +return; +} + +##################################################################### +# Remote Meter Objekt identifizieren und zusammenstellen +##################################################################### +sub __remoteMeterObj { + my $paref = shift; + my $name = $paref->{name}; + my $type = $paref->{type}; + my $obj = $paref->{obj}; + my $method = $paref->{method} // return qq{no extraction method for Object '$obj' defined}; + + my $err = ''; + my $dev = ''; + my $rem = ''; + my @acp = (); + my @afp = (); + + if ($method eq 'reading') { + $dev = ReadingsVal ($name, $obj, ''); + return qq{Reading '$obj' is not set or is empty} if(!$dev); + } + elsif ($method eq 'attr') { + $dev = AttrVal ($name, $obj, ''); + return qq{Attribute '$obj' is not set} if(!$dev); + } + elsif ($method eq 'string') { + return qq{Object '$obj' is empty} if(!$obj); + $dev = $obj; + } + + my ($a, $h) = parseParams ($dev); + $dev = $a->[0]; # das Device aus dem Object + ($dev, $rem) = split '@', $dev; # Meterdev extrahieren + + return if(!$rem); # keine remote Devicekonfiguration + + my ($server, $infix) = split '/', $rem; + my $gc = (split ":", $h->{gcon})[0]; # Readingname für aktuellen Netzbezug + my $gf = (split ":", $h->{gfeedin})[0]; # Readingname für aktuelle Netzeinspeisung + my $gt = (split ":", $h->{contotal})[0]; # Readingname für Bezug total + my $ft = (split ":", $h->{feedtotal})[0]; # Readingname für Einspeisung total + @acp = split ":", $h->{conprice} if($h->{conprice}); + @afp = split ":", $h->{feedprice} if($h->{feedprice}); + + $data{$type}{$name}{current}{x_remote}{$dev}{server} = $server; + $data{$type}{$name}{current}{x_remote}{$dev}{infix} = $infix; + $data{$type}{$name}{current}{x_remote}{$dev}{readings}{$gt} = undef; + $data{$type}{$name}{current}{x_remote}{$dev}{readings}{$ft} = undef; + $data{$type}{$name}{current}{x_remote}{$dev}{readings}{$gc} = undef if($h->{gcon} ne '-gfeedin'); + $data{$type}{$name}{current}{x_remote}{$dev}{readings}{$gf} = undef if($h->{gfeedin} ne '-gcon'); + + if (scalar(@acp) == 2) { + $data{$type}{$name}{current}{x_remote}{$dev}{readings}{$acp[0]} = undef; # conprice wird durch Reading im Meterdev geliefert + } + elsif (scalar(@acp) == 3) { # conprice wird durch weiteres Device / Reading geliefert + $data{$type}{$name}{current}{x_remote}{$acp[0]}{readings}{$acp[1]} = undef; + } + + if (scalar(@afp) == 2) { + $data{$type}{$name}{current}{x_remote}{$dev}{readings}{$afp[0]} = undef; # feedprice wird durch Reading im Meterdev geliefert + } + elsif (scalar(@afp) == 3) { # feedprice wird durch weiteres Device / Reading geliefert + $data{$type}{$name}{current}{x_remote}{$afp[0]}{readings}{$afp[1]} = undef; + } + +return; +} + ################################################################ # Grunddaten aller registrierten Consumer speichern ################################################################ @@ -6830,10 +6975,6 @@ sub _specialActivities { if ($t > $planswitchoff && $simpCstat =~ /planned|finished|unknown/xs) { deleteConsumerPlanning ($hash, $c); - - $data{$type}{$name}{consumers}{$c}{minutesOn} = 0; - $data{$type}{$name}{consumers}{$c}{numberDayStarts} = 0; - $data{$type}{$name}{consumers}{$c}{onoff} = 'off'; } } @@ -6920,12 +7061,7 @@ sub _specialActivities { for my $c (keys %{$data{$type}{$name}{consumers}}) { # Planungsdaten regulär löschen next if(ConsumerVal ($hash, $c, "plandelete", "regular") ne "regular"); - deleteConsumerPlanning ($hash, $c); - - $data{$type}{$name}{consumers}{$c}{minutesOn} = 0; - $data{$type}{$name}{consumers}{$c}{numberDayStarts} = 0; - $data{$type}{$name}{consumers}{$c}{onoff} = 'off'; } writeCacheToFile ($hash, "consumers", $csmcache.$name); # Cache File Consumer schreiben @@ -7655,14 +7791,16 @@ sub __calcPVestimates { } $peak *= 1000; - my $est = SolCastAPIVal ($hash, $string, $wantdt, 'pv_estimate50', 0) * $hc; # Korrekturfaktor anwenden - my $pv = sprintf "%.1f", $est; + my $est = SolCastAPIVal ($hash, $string, $wantdt, 'pv_estimate50', 0); + my $pv = sprintf "%.1f", ($est * $hc); # Korrekturfaktor anwenden if ($debug =~ /radiationProcess/xs) { $lh = { # Log-Hash zur Ausgabe "modulePeakString" => $peak. " W", "Estimated PV generation (raw)" => $est. " Wh", "Estimated PV generation (calc)" => $pv. " Wh", + "PV correction factor" => $hc, + "PV correction quality" => $hq, }; if ($acu =~ /on_complex/xs) { @@ -7706,8 +7844,6 @@ sub __calcPVestimates { "Cloudcover" => $cloudcover, "Total Rain last hour" => $totalrain." kg/m2", "PV Correction mode" => ($acu ? $acu : 'no'), - "PV correction factor" => $hc, - "PV correction quality" => $hq, "PV generation forecast" => $pvsum." Wh ".$logao, }; @@ -7967,7 +8103,7 @@ sub _transferMeterValues { my $t = $paref->{t}; my $chour = $paref->{chour}; - my ($err, $medev, $h) = isDeviceValid ( { name => $name, obj => 'currentMeterDev', method => 'reading' } ); + my ($err, $medev, $h) = isDeviceValid ( { name => $name, obj => 'setupMeterDev', method => 'attr' } ); return if($err); my $type = $paref->{type}; @@ -7988,8 +8124,7 @@ sub _transferMeterValues { $data{$type}{$name}{current}{ePurchasePrice} = ReadingsNum ($acp[0], $acp[1], 0); $data{$type}{$name}{current}{ePurchasePriceCcy} = $acp[2]; } - - if (scalar(@acp) == 2) { + elsif (scalar(@acp) == 2) { if (isNumeric($acp[0])) { $data{$type}{$name}{current}{ePurchasePrice} = $acp[0]; $data{$type}{$name}{current}{ePurchasePriceCcy} = $acp[1]; @@ -8015,8 +8150,7 @@ sub _transferMeterValues { $data{$type}{$name}{current}{eFeedInTariff} = ReadingsNum ($afp[0], $afp[1], 0); $data{$type}{$name}{current}{eFeedInTariffCcy} = $afp[2]; } - - if (scalar(@afp) == 2) { + elsif (scalar(@afp) == 2) { if (isNumeric($afp[0])) { $data{$type}{$name}{current}{eFeedInTariff} = $afp[0]; $data{$type}{$name}{current}{eFeedInTariffCcy} = $afp[1]; @@ -8048,7 +8182,7 @@ sub _transferMeterValues { my $params; - if ($gc eq "-gfeedin") { # Spezialfall gcon bei neg. gfeedin # Spezialfall: bei negativen gfeedin -> $gco = abs($gf), $gf = 0 + if ($gc eq '-gfeedin') { # Spezialfall gcon bei neg. gfeedin # Spezialfall: bei negativen gfeedin -> $gco = abs($gf), $gf = 0 $params = { dev => $medev, rdg => $gf, @@ -8058,7 +8192,7 @@ sub _transferMeterValues { ($gfin,$gco) = substSpecialCases ($params); } - if ($gf eq "-gcon") { # Spezialfall gfeedin bei neg. gcon + if ($gf eq '-gcon') { # Spezialfall gfeedin bei neg. gcon $params = { dev => $medev, rdg => $gc, @@ -8658,7 +8792,6 @@ sub _manageConsumerData { my $name = $paref->{name}; my $type = $paref->{type}; my $t = $paref->{t}; # aktuelle Zeit - my $date = $paref->{date}; # aktuelles Datum my $chour = $paref->{chour}; my $day = $paref->{day}; @@ -8666,8 +8799,9 @@ sub _manageConsumerData { $paref->{nhour} = sprintf "%02d", $nhour; for my $c (sort{$a<=>$b} keys %{$data{$type}{$name}{consumers}}) { - my $consumer = ConsumerVal ($hash, $c, "name", ""); - my $alias = ConsumerVal ($hash, $c, "alias", ""); + $paref->{consumer} = $c; + my $consumer = ConsumerVal ($hash, $c, "name", ""); + my $alias = ConsumerVal ($hash, $c, "alias", ""); ## aktuelle Leistung auslesen ############################## @@ -8730,69 +8864,30 @@ sub _manageConsumerData { } deleteReadingspec ($hash, "consumer${c}_currentPower") if(!$etotread && !$paread); + + __getAutomaticState ($paref); # Automatic Status des Consumers abfragen + __calcEnergyPieces ($paref); # Energieverbrauch auf einzelne Stunden für Planungsgrundlage aufteilen + __planInitialSwitchTime ($paref); # Consumer Switch Zeiten planen + __setTimeframeState ($paref); # Timeframe Status ermitteln + __setConsRcmdState ($paref); # Consumption Recommended Status setzen + __switchConsumer ($paref); # Consumer schalten + + $paref->{pcurr} = $pcurr; + + __getCyclesAndRuntime ($paref); # Verbraucher - Laufzeit, Tagesstarts und Aktivminuten pro Stunde ermitteln + __setPhysLogSwState ($paref); # physischen / logischen Schaltzustand festhalten + __reviewSwitchTime ($paref); # Planungsdaten überprüfen und ggf. neu planen + __remainConsumerTime ($paref); # Restlaufzeit Verbraucher ermitteln - ## Verbraucher - Laufzeit und Zyklen pro Tag ermitteln - ## Laufzeit (in Minuten) wird pro Stunde erfasst - ## bei Tageswechsel Rücksetzen in _specialActivities - ####################################################### - my $starthour; - if (isConsumerLogOn ($hash, $c, $pcurr)) { # Verbraucher ist logisch "an" - if (ConsumerVal ($hash, $c, "onoff", "off") eq "off") { - $data{$type}{$name}{consumers}{$c}{onoff} = 'on'; - $data{$type}{$name}{consumers}{$c}{startTime} = $t; # startTime ist nicht von "Automatic" abhängig -> nicht identisch mit planswitchon !!! - $data{$type}{$name}{consumers}{$c}{cycleStarttime} = $t; - $data{$type}{$name}{consumers}{$c}{cycleTime} = 0; - my $stimes = ConsumerVal ($hash, $c, "numberDayStarts", 0); # Anzahl der On-Schaltungen am Tag - $data{$type}{$name}{consumers}{$c}{numberDayStarts} = $stimes+1; - $data{$type}{$name}{consumers}{$c}{lastMinutesOn} = ConsumerVal ($hash, $c, "minutesOn", 0); - } - else { - $data{$type}{$name}{consumers}{$c}{cycleTime} = (($t - ConsumerVal ($hash, $c, 'cycleStarttime', $t)) / 60); - } - - $starthour = strftime "%H", localtime(ConsumerVal ($hash, $c, "startTime", $t)); - - if ($chour eq $starthour) { - my $runtime = (($t - ConsumerVal ($hash, $c, "startTime", $t)) / 60); # in Minuten ! (gettimeofday sind ms !) - $data{$type}{$name}{consumers}{$c}{minutesOn} = ConsumerVal ($hash, $c, "lastMinutesOn", 0) + $runtime; - } - else { # neue Stunde hat begonnen - if (ConsumerVal ($hash, $c, "onoff", "off") eq 'on') { - $data{$type}{$name}{consumers}{$c}{startTime} = timestringToTimestamp ($date." ".sprintf("%02d", $chour).":00:00"); - $data{$type}{$name}{consumers}{$c}{minutesOn} = ($t - ConsumerVal ($hash, $c, "startTime", $t)) / 60; # in Minuten ! (gettimeofday sind ms !) - $data{$type}{$name}{consumers}{$c}{lastMinutesOn} = 0; - } - } - } - else { # Verbraucher soll nicht aktiv sein - $data{$type}{$name}{consumers}{$c}{onoff} = 'off'; - $data{$type}{$name}{consumers}{$c}{cycleTime} = 0; - $starthour = strftime "%H", localtime(ConsumerVal ($hash, $c, "startTime", $t)); - - if ($chour ne $starthour) { - $data{$type}{$name}{consumers}{$c}{minutesOn} = 0; - delete $data{$type}{$name}{consumers}{$c}{startTime}; - } - } - - $paref->{val} = ConsumerVal ($hash, $c, "numberDayStarts", 0); # Anzahl Tageszyklen des Verbrauchers speichern - $paref->{histname} = "cyclescsm${c}"; - setPVhistory ($paref); - - $paref->{val} = ceil ConsumerVal ($hash, $c, "minutesOn", 0); # Verbrauchsminuten akt. Stunde des Consumers - $paref->{histname} = "minutescsm${c}"; - setPVhistory ($paref); - - delete $paref->{histname}; - delete $paref->{val}; - + delete $paref->{pcurr}; + ## Durchschnittsverbrauch / Betriebszeit ermitteln + speichern ################################################################ my $consumerco = 0; my $runhours = 0; my $dnum = 0; - for my $n (sort{$a<=>$b} keys %{$data{$type}{$name}{pvhist}}) { # Betriebszeit und gemessenen Verbrauch ermitteln + for my $n (sort{$a<=>$b} keys %{$data{$type}{$name}{pvhist}}) { # Betriebszeit und gemessenen Verbrauch ermitteln my $csme = HistoryVal ($hash, $n, 99, "csme${c}", 0); my $hours = HistoryVal ($hash, $n, 99, "hourscsme${c}", 0); next if(!$hours); @@ -8810,29 +8905,16 @@ sub _manageConsumerData { delete $data{$type}{$name}{consumers}{$c}{avgenergy}; } - $data{$type}{$name}{consumers}{$c}{avgruntime} = sprintf "%.2f", (($runhours / $dnum) * 60); # Durchschnittslaufzeit am Tag in Minuten + $data{$type}{$name}{consumers}{$c}{runtimeAvgDay} = sprintf "%.2f", (($runhours / $dnum) * 60); # Durchschnittslaufzeit am Tag in Minuten } - - $paref->{consumer} = $c; - - __getAutomaticState ($paref); # Automatic Status des Consumers abfragen - __calcEnergyPieces ($paref); # Energieverbrauch auf einzelne Stunden für Planungsgrundlage aufteilen - __planInitialSwitchTime ($paref); # Consumer Switch Zeiten planen - __setTimeframeState ($paref); # Timeframe Status ermitteln - __setConsRcmdState ($paref); # Consumption Recommended Status setzen - __switchConsumer ($paref); # Consumer schalten - __reviewSwitchTime ($paref); # Planungsdaten überprüfen und ggf. neu planen - __remainConsumerTime ($paref); # Restlaufzeit Verbraucher ermitteln - __setPhysSwState ($paref); # physischen Schaltzustand festhalten - + ## Consumer Schaltstatus und Schaltzeit für Readings ermitteln ################################################################ - my $costate = isConsumerPhysOn ($hash, $c) ? "on" : - isConsumerPhysOff ($hash, $c) ? "off" : + my $costate = isConsumerPhysOn ($hash, $c) ? 'on' : + isConsumerPhysOff ($hash, $c) ? 'off' : "unknown"; - $data{$type}{$name}{consumers}{$c}{state} = $costate; - + $data{$type}{$name}{consumers}{$c}{state} = $costate; my ($pstate,$starttime,$stoptime,$supplmnt) = __getPlanningStateAndTimes ($paref); my ($iilt,$rlt) = isInLocktime ($paref); # Sperrzeit Status ermitteln my $mode = ConsumerVal ($hash, $c, 'mode', 'can'); @@ -8994,8 +9076,8 @@ sub ___csmSpecificEpieces { } my $tsloff = defined $data{$type}{$name}{consumers}{$c}{lastOnTime} ? - $t - $data{$type}{$name}{consumers}{$c}{lastOnTime} : - 99; + $t - $data{$type}{$name}{consumers}{$c}{lastOnTime} : + 99; debugLog ($paref, "epiecesCalc", qq{specificEpieces -> consumer "$c" - time since last Switch Off (tsloff): $tsloff seconds}); @@ -9760,23 +9842,23 @@ sub ___switchConsumerOn { my ($iilt,$rlt) = isInLocktime ($paref); # Sperrzeit Status ermitteln - if ($debug =~ /consumerSwitching/x) { # nur für Debugging + if ($debug =~ /consumerSwitching${c}/x) { # nur für Debugging my $cons = CurrentVal ($hash, 'consumption', 0); my $nompow = ConsumerVal ($hash, $c, 'power', '-'); my $sp = CurrentVal ($hash, 'surplus', 0); - Log3 ($name, 1, qq{$name DEBUG> ############### consumerSwitching consumer "$c" ############### }); + Log3 ($name, 1, qq{$name DEBUG> ############### consumerSwitching consumer "$c" ###############}); Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - general switching parameters => }. - qq{auto mode: $auto, current Consumption: $cons W, nompower: $nompow, surplus: $sp W, }. + qq{auto mode: $auto, Current household consumption: $cons W, nompower: $nompow, surplus: $sp W, }. qq{planstate: $pstate, starttime: }.($startts ? (timestampToTimestring ($startts, $lang))[0] : "undef") ); Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - isInLocktime: $iilt}.($rlt ? ", remainLockTime: $rlt seconds" : '')); - Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - current Context is >switch on< => }. + Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - in Context 'switch on' => }. qq{swoncond: $swoncond, on-command: $oncom } ); Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - isAddSwitchOnCond Info: $infon}) if($swoncond && $infon); Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - isAddSwitchOffCond Info: $infoff}) if($swoffcond && $infoff); - Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - device >$dswname< is used as switching device}); + Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - device '$dswname' is used as switching device}); if ($simpCstat =~ /planned|priority|starting|continuing/xs && $isInTime && $iilt) { Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - switching on postponed by >isInLocktime<}); @@ -9801,7 +9883,7 @@ sub ___switchConsumerOn { my $mode = ConsumerVal ($hash, $c, "mode", $defcmode); # Consumer Planungsmode my $enable = ___enableSwitchByBatPrioCharge ($paref); # Vorrangladung Batterie ? - debugLog ($paref, "consumerSwitching", qq{$name DEBUG> Consumer switch enable by battery state: $enable}); + debugLog ($paref, "consumerSwitching${c}", qq{$name DEBUG> Consumer switch enable by battery state: $enable}); if ($mode eq "can" && !$enable) { # Batterieladung - keine Verbraucher "Einschalten" Freigabe $paref->{ps} = "priority charging battery"; @@ -9876,8 +9958,8 @@ sub ___switchConsumerOff { my ($iilt,$rlt) = isInLocktime ($paref); # Sperrzeit Status ermitteln - if ($debug =~ /consumerSwitching/x) { # nur für Debugging - Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - current Context is >switch off< => }. + if ($debug =~ /consumerSwitching${c}/x) { # nur für Debugging + Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - in Context 'switch off' => }. qq{swoffcond: $swoffcond, off-command: $offcom} ); @@ -9944,7 +10026,7 @@ sub ___setConsumerSwitchingState { my $oldpsw = ConsumerVal ($hash, $c, 'physoffon', 'off'); # gespeicherter physischer Schaltzustand my $dowri = 0; - debugLog ($paref, "consumerSwitching", qq{consumer "$c" - current planning state: $simpCstat \n}); + debugLog ($paref, "consumerSwitching${c}", qq{consumer "$c" - current planning state: $simpCstat}); if (isConsumerPhysOn ($hash, $c) && $simpCstat eq 'starting') { my $mintime = ConsumerVal ($hash, $c, "mintime", $defmintime); @@ -10038,6 +10120,122 @@ sub ___setConsumerSwitchingState { return $state; } +################################################################ +# Verbraucher - Laufzeit, Tagesstarts und Aktivminuten pro +# Stunde ermitteln +# Stundenwechsel + Tageswechsel Management +# +# startTime - wichtig für Wechselmanagement!! +################################################################ +sub __getCyclesAndRuntime { + my $paref = shift; + my $hash = $paref->{hash}; + my $name = $paref->{name}; + my $type = $paref->{type}; + my $t = $paref->{t}; + my $chour = $paref->{chour}; + my $day = $paref->{day}; # aktueller Tag (range 01 to 31) + my $date = $paref->{date}; # aktuelles Datum + my $pcurr = $paref->{pcurr}; + my $c = $paref->{consumer}; + my $debug = $paref->{debug}; + + ### nicht mehr benötigte Daten verarbeiten - Bereich kann später wieder raus !! + ########################################################################################################################## + my $nds = ConsumerVal ($hash, $c, 'numberDayStarts', 'leer'); # 28.05.2024 + my $art = ConsumerVal ($hash, $c, 'avgruntime', 'leer'); + if ($nds ne 'leer') { + $data{$type}{$name}{consumers}{$c}{cycleDayNum} = $nds; + delete $data{$type}{$name}{consumers}{$c}{numberDayStarts}; + } + + if ($art ne 'leer') { + $data{$type}{$name}{consumers}{$c}{runtimeAvgDay} = $art; + delete $data{$type}{$name}{consumers}{$c}{avgruntime}; + } + ########################################################################################################################## + + my ($starthour, $startday); + + if (isConsumerLogOn ($hash, $c, $pcurr)) { # Verbraucher ist logisch "an" + if (ConsumerVal ($hash, $c, 'onoff', 'off') eq 'off') { # Status im letzen Zyklus war "off" + $data{$type}{$name}{consumers}{$c}{onoff} = 'on'; + $data{$type}{$name}{consumers}{$c}{startTime} = $t; # startTime ist nicht von "Automatic" abhängig -> nicht identisch mit planswitchon !!! + $data{$type}{$name}{consumers}{$c}{cycleStarttime} = $t; + $data{$type}{$name}{consumers}{$c}{cycleTime} = 0; + $data{$type}{$name}{consumers}{$c}{lastMinutesOn} = ConsumerVal ($hash, $c, 'minutesOn', 0); + + $data{$type}{$name}{consumers}{$c}{cycleDayNum}++; # Anzahl der On-Schaltungen am Tag + } + else { + $data{$type}{$name}{consumers}{$c}{cycleTime} = (($t - ConsumerVal ($hash, $c, 'cycleStarttime', $t)) / 60); # Minuten + } + + $starthour = strftime "%H", localtime(ConsumerVal ($hash, $c, 'startTime', $t)); + $startday = strftime "%d", localtime(ConsumerVal ($hash, $c, 'startTime', $t)); # aktueller Tag (range 01 to 31) + + if ($chour eq $starthour) { + my $runtime = (($t - ConsumerVal ($hash, $c, 'startTime', $t)) / 60); # in Minuten ! (gettimeofday sind ms !) + $data{$type}{$name}{consumers}{$c}{minutesOn} = ConsumerVal ($hash, $c, 'lastMinutesOn', 0) + $runtime; + } + else { # Stundenwechsel + if (ConsumerVal ($hash, $c, 'onoff', 'off') eq 'on') { # Status im letzen Zyklus war "on" + my $newst = timestringToTimestamp ($date.' '.sprintf("%02d", $chour).':00:00'); + $data{$type}{$name}{consumers}{$c}{startTime} = $newst; + $data{$type}{$name}{consumers}{$c}{minutesOn} = ($t - ConsumerVal ($hash, $c, 'startTime', $newst)) / 60; # in Minuten ! (gettimeofday sind ms !) + $data{$type}{$name}{consumers}{$c}{lastMinutesOn} = 0; + + if ($day ne $startday) { # Tageswechsel + $data{$type}{$name}{consumers}{$c}{cycleDayNum} = 1; + } + } + } + } + else { # Verbraucher soll nicht aktiv sein + $starthour = strftime "%H", localtime(ConsumerVal ($hash, $c, 'startTime', 1)); + $startday = strftime "%d", localtime(ConsumerVal ($hash, $c, 'startTime', 1)); # aktueller Tag (range 01 to 31) + + if ($chour ne $starthour) { # Stundenwechsel + $data{$type}{$name}{consumers}{$c}{minutesOn} = 0; + } + + if ($day ne $startday) { # Tageswechsel + $data{$type}{$name}{consumers}{$c}{cycleDayNum} = 0; + } + + $data{$type}{$name}{consumers}{$c}{onoff} = 'off'; + } + + if ($debug =~ /consumerSwitching${c}/xs) { + my $sr = 'still running'; + my $son = isConsumerLogOn ($hash, $c, $pcurr) ? $sr : ConsumerVal ($hash, $c, 'cycleTime', 0) * 60; # letzte Cycle-Zeitdauer in Sekunden + my $cst = ConsumerVal ($hash, $c, 'cycleStarttime', 0); + $son = $son && $son ne $sr ? timestampToTimestring ($cst + $son, $paref->{lang}) : + $son eq $sr ? $sr : + '-'; + $cst = $cst ? timestampToTimestring ($cst, $paref->{lang}) : '-'; + + Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - cycleDayNum: }.ConsumerVal ($hash, $c, 'cycleDayNum', 0)); + Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - last cycle start time: $cst}); + Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - last cycle end time: $son}); + } + + ## History schreiben + ###################### + $paref->{val} = ConsumerVal ($hash, $c, "cycleDayNum", 0); # Anzahl Tageszyklen des Verbrauchers speichern + $paref->{histname} = "cyclescsm${c}"; + setPVhistory ($paref); + + $paref->{val} = ceil ConsumerVal ($hash, $c, "minutesOn", 0); # Verbrauchsminuten akt. Stunde des Consumers speichern + $paref->{histname} = "minutescsm${c}"; + setPVhistory ($paref); + + delete $paref->{histname}; + delete $paref->{val}; + +return; +} + ################################################################ # Restlaufzeit Verbraucher ermitteln ################################################################ @@ -10050,7 +10248,7 @@ sub __remainConsumerTime { my $t = $paref->{t}; # aktueller Unixtimestamp my ($planstate,$startstr,$stoptstr) = __getPlanningStateAndTimes ($paref); - my $stopts = ConsumerVal ($hash, $c, "planswitchoff", undef); # geplante Unix Stopzeit + my $stopts = ConsumerVal ($hash, $c, 'planswitchoff', undef); # geplante Unix Stopzeit $data{$type}{$name}{consumers}{$c}{remainTime} = 0; @@ -10063,18 +10261,27 @@ return; } ################################################################ -# Consumer physischen Schaltstatus setzen +# Consumer physischen & logischen Schaltstatus setzen ################################################################ -sub __setPhysSwState { +sub __setPhysLogSwState { my $paref = shift; my $hash = $paref->{hash}; my $name = $paref->{name}; my $type = $paref->{type}; my $c = $paref->{consumer}; + my $pcurr = $paref->{pcurr}; + my $debug = $paref->{debug}; - my $pon = isConsumerPhysOn ($hash, $c) ? 'on' : 'off'; - - $data{$type}{$name}{consumers}{$c}{physoffon} = $pon; + my $cpo = isConsumerPhysOn ($hash, $c) ? 'on' : 'off'; + my $clo = isConsumerLogOn ($hash, $c, $pcurr) ? 'on' : 'off'; + + $data{$type}{$name}{consumers}{$c}{physoffon} = $cpo; + $data{$type}{$name}{consumers}{$c}{logoffon} = $clo; + + if ($debug =~ /consumerSwitching${c}/xs) { + Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - physical Switchstate: $cpo}); + Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - logical Switchstate: $clo \n}); + } return; } @@ -10141,16 +10348,16 @@ sub _estConsumptionForecast { my $dayname = $paref->{dayname}; # aktueller Tagname my ($err, $medev, $h) = isDeviceValid ( { name => $name, - obj => 'currentMeterDev', - method => 'reading', + obj => 'setupMeterDev', + method => 'attr', } ); # aktuelles Meter device return if($err); - my $swdfcfc = AttrVal ($name, "affectConsForecastIdentWeekdays", 0); # nutze nur gleiche Wochentage (Mo...So) für Verbrauchsvorhersage - my ($am,$hm) = parseParams ($medev); - my $type = $paref->{type}; - my $acref = $data{$type}{$name}{consumers}; + my $swdfcfc = AttrVal ($name, "affectConsForecastIdentWeekdays", 0); # nutze nur gleiche Wochentage (Mo...So) für Verbrauchsvorhersage + my ($am, $hm) = parseParams ($medev); + my $type = $paref->{type}; + my $acref = $data{$type}{$name}{consumers}; ## Verbrauchsvorhersage für den nächsten Tag ############################################## @@ -10783,6 +10990,11 @@ sub genStatisticReadings { for my $kpi (@csr) { my $def = $hcsr{$kpi}{def}; my $par = $hcsr{$kpi}{par}; + + if (!defined $def || !defined $par) { + Log3 ($name, 1, "$name - ERROR in Application - attribute ctrlStatisticReadings KPI '$kpi' has no Parameter or default value set. Set the attribute again or inform Maintainer."); + next; + } if ($def eq 'apimaxreq') { $def = AttrVal ($name, 'ctrlSolCastAPImaxReq', $solcmaxreqdef); @@ -10903,6 +11115,19 @@ sub genStatisticReadings { storeReading ('statistic_'.$kpi, (sprintf "%.0f", $mion).$hcsr{$kpi}{unit}); } + + if ($kpi =~ /runTimeAvgDayConsumer_/xs) { + my $c = (split "_", $kpi)[1]; # Consumer Nummer extrahieren + + if (!AttrVal ($name, 'consumer'.$c, '')) { + deleteReadingspec ($hash, 'statistic_runTimeAvgDayConsumer_'.$c); + return; + } + + my $radc = &{$hcsr{$kpi}{fn}} ($hash, $c, $hcsr{$kpi}{par}, $def); + + storeReading ('statistic_'.$kpi, $radc.$hcsr{$kpi}{unit}); + } if ($kpi eq 'todayConsumptionForecast') { my $type = $paref->{type}; @@ -11279,7 +11504,7 @@ sub _checkSetupNotComplete { my $wedev = AttrVal ($name, 'ctrlWeatherDev1', undef); # Device Vorhersage Wetterdaten (Bewölkung etc.) my $radev = ReadingsVal ($name, 'currentRadiationAPI', undef); # Device Strahlungsdaten Vorhersage my $indev = ReadingsVal ($name, 'currentInverterDev', undef); # Inverter Device - my $medev = ReadingsVal ($name, 'currentMeterDev', undef); # Meter Device + my $medev = AttrVal ($name, 'setupMeterDev', undef); # Meter Device my $peaks = ReadingsVal ($name, 'modulePeakString', undef); # String Peak my $maz = ReadingsVal ($name, 'moduleAzimuth', undef); # Modulausrichtung Konfig (Azimut) @@ -14393,7 +14618,11 @@ sub setPVhistory { } my $cycles = HistoryVal ($hash, $day, 99, "cyclescsm${num}", 0); - $data{$type}{$name}{pvhist}{$day}{99}{"hourscsme${num}"} = sprintf "%.2f", ($minutes / 60 ) if($cycles); + + if ($cycles) { + $data{$type}{$name}{pvhist}{$day}{99}{"hourscsme${num}"} = sprintf "%.2f", ($minutes / 60 ); + $data{$type}{$name}{pvhist}{$day}{99}{"avgcycmntscsm${num}"} = sprintf "%.2f", ($minutes / $cycles); + } } if ($histname =~ /cyclescsm[0-9]+$/xs) { # Anzahl Tageszyklen des Verbrauchers @@ -14588,18 +14817,20 @@ sub listDataPool { for my $c (1..$maxconsumer) { $c = sprintf "%02d", $c; my $nl = 0; - my $csmc = HistoryVal ($hash, $day, $key, "cyclescsm${c}", undef); - my $csmt = HistoryVal ($hash, $day, $key, "csmt${c}", undef); - my $csme = HistoryVal ($hash, $day, $key, "csme${c}", undef); - my $csmm = HistoryVal ($hash, $day, $key, "minutescsm${c}", undef); - my $csmh = HistoryVal ($hash, $day, $key, "hourscsme${c}", undef); + my $csmc = HistoryVal ($hash, $day, $key, "cyclescsm${c}", undef); + my $csmt = HistoryVal ($hash, $day, $key, "csmt${c}", undef); + my $csme = HistoryVal ($hash, $day, $key, "csme${c}", undef); + my $csmm = HistoryVal ($hash, $day, $key, "minutescsm${c}", undef); + my $csmh = HistoryVal ($hash, $day, $key, "hourscsme${c}", undef); + my $csma = HistoryVal ($hash, $day, $key, "avgcycmntscsm${c}", undef); if ($export eq 'csv') { - $hexp->{$day}{$key}{"CyclesCsm${c}"} = $csmc if(defined $csmc); - $hexp->{$day}{$key}{"Csmt${c}"} = $csmt if(defined $csmt); - $hexp->{$day}{$key}{"Csme${c}"} = $csme if(defined $csme); - $hexp->{$day}{$key}{"MinutesCsm${c}"} = $csmm if(defined $csmm); - $hexp->{$day}{$key}{"HoursCsme${c}"} = $csmh if(defined $csmh); + $hexp->{$day}{$key}{"CyclesCsm${c}"} = $csmc if(defined $csmc); + $hexp->{$day}{$key}{"Csmt${c}"} = $csmt if(defined $csmt); + $hexp->{$day}{$key}{"Csme${c}"} = $csme if(defined $csme); + $hexp->{$day}{$key}{"MinutesCsm${c}"} = $csmm if(defined $csmm); + $hexp->{$day}{$key}{"HoursCsme${c}"} = $csmh if(defined $csmh); + $hexp->{$day}{$key}{"AvgCycleMinutesCsm${c}"} = $csma if(defined $csma); } if (defined $csmc) { @@ -14630,6 +14861,12 @@ sub listDataPool { $csm .= "hourscsme${c}: $csmh"; $nl = 1; } + + if (defined $csma) { + $csm .= ", " if($nl); + $csm .= "avgcycmntscsm${c}: $csma"; + $nl = 1; + } $csm .= "\n " if($nl); } @@ -14684,7 +14921,7 @@ sub listDataPool { my $cret; for my $ckey (sort keys %{$h->{$idx}}) { - if(ref $h->{$idx}{$ckey} eq "HASH") { + if (ref $h->{$idx}{$ckey} eq 'HASH') { my $hk = qq{}; for my $f (sort {$a<=>$b} keys %{$h->{$idx}{$ckey}}) { $hk .= " " if($hk); @@ -14843,13 +15080,49 @@ sub listDataPool { if (!keys %{$h}) { return qq{Current values cache is empty.}; } + for my $idx (sort keys %{$h}) { - if (ref $h->{$idx} ne "ARRAY") { - $sq .= $idx." => ".(defined $h->{$idx} ? $h->{$idx} : '')."\n"; + if (ref $h->{$idx} eq 'ARRAY') { + my $aser = join " ",@{$h->{$idx}}; + $sq .= $idx." => ".$aser."\n"; + } + elsif (ref $h->{$idx} eq 'HASH') { + my $s1; + my $sp1 = _ldpspaces ($idx, q{}); + $sq .= $idx." => "; + + for my $idx1 (sort keys %{$h->{$idx}}) { + if (ref $h->{$idx}{$idx1} eq 'HASH') { + my $s2; + my $sp2 = _ldpspaces ($idx1, $sp1); + $sq .= ($s1 ? $sp1 : "").$idx1." => "; + + for my $idx2 (sort keys %{$h->{$idx}{$idx1}}) { + my $s3; + my $sp3 = _ldpspaces ($idx2, $sp2); + $sq .= ($s2 ? $sp2 : "").$idx2." => "; + + if (ref $h->{$idx}{$idx1}{$idx2} eq 'HASH') { + for my $idx3 (sort keys %{$h->{$idx}{$idx1}{$idx2}}) { + $sq .= ($s3 ? $sp3 : "").$idx3." => ".(defined $h->{$idx}{$idx1}{$idx2}{$idx3} ? $h->{$idx}{$idx1}{$idx2}{$idx3} : '')."\n"; + $s3 = 1; + } + } + else { + $sq .= (defined $h->{$idx}{$idx1}{$idx2} ? $h->{$idx}{$idx1}{$idx2} : '')."\n"; + } + + $s1 = 1; + $s2 = 1; + } + } + else { + $sq .= (defined $h->{$idx}{$idx1} ? $h->{$idx}{$idx1} : '')."\n"; + } + } } else { - my $aser = join " ",@{$h->{$idx}}; - $sq .= $idx." => ".$aser."\n"; + $sq .= $idx." => ".(defined $h->{$idx} ? $h->{$idx} : '')."\n"; } } } @@ -14882,7 +15155,7 @@ sub listDataPool { while (my ($tag, $item) = each %{$git->($itref->{$idx})}) { $sq .= ($s1 ? $sp1 : "").$tag." => "; - if (ref $item eq "HASH") { + if (ref $item eq 'HASH') { my $s2; my $sp2 = _ldpspaces ($tag, $sp1); @@ -15878,7 +16151,7 @@ sub createAssociatedWith { my $name = $hash->{NAME}; my $type = $hash->{TYPE}; - RemoveInternalTimer($hash, "FHEM::SolarForecast::createAssociatedWith"); + RemoveInternalTimer ($hash, 'FHEM::SolarForecast::createAssociatedWith'); if ($init_done) { my (@cd, @nd); @@ -15896,24 +16169,24 @@ sub createAssociatedWith { ($afc,$h) = parseParams ($fcdev3); $fcdev3 = $afc->[0] // ""; - my $radev = ReadingsVal($name, 'currentRadiationAPI', ''); # Radiation forecast Device + my $radev = ReadingsVal ($name, 'currentRadiationAPI', ''); # Radiation forecast Device ($ara,$h) = parseParams ($radev); $radev = $ara->[0] // ""; - my $indev = ReadingsVal($name, 'currentInverterDev', ''); # Inverter Device + my $indev = ReadingsVal ($name, 'currentInverterDev', ''); # Inverter Device ($ain,$h) = parseParams ($indev); $indev = $ain->[0] // ""; - my $medev = ReadingsVal($name, 'currentMeterDev', ''); # Meter Device + my $medev = AttrVal ($name, 'setupMeterDev', ''); # Meter Device ($ame,$h) = parseParams ($medev); $medev = $ame->[0] // ""; - my $badev = ReadingsVal($name, 'currentBatteryDev', ''); # Battery Device + my $badev = ReadingsVal ($name, 'currentBatteryDev', ''); # Battery Device ($aba,$h) = parseParams ($badev); $badev = $aba->[0] // ""; for my $c (sort{$a<=>$b} keys %{$data{$type}{$name}{consumers}}) { # Consumer Devices - my $consumer = AttrVal($name, "consumer${c}", ""); + my $consumer = AttrVal ($name, "consumer${c}", ""); my ($ac,$hc) = parseParams ($consumer); my $codev = $ac->[0] // ''; my $dswitch = $hc->{switchdev} // ''; # alternatives Schaltdevice @@ -15951,7 +16224,7 @@ sub createAssociatedWith { } } else { - InternalTimer(gettimeofday()+3, "FHEM::SolarForecast::createAssociatedWith", $hash, 0); + InternalTimer (gettimeofday() + 3, 'FHEM::SolarForecast::createAssociatedWith', $hash, 0); } return; @@ -16305,7 +16578,7 @@ sub isAddSwitchOffCond { } } - $info .= qq{-> the effect depends on the switch context\n}; + $info .= qq{-> the effect depends on the switch context}; } return ($swoff, $info, $err); @@ -16342,7 +16615,7 @@ sub isSurplusIgnoCond { my $spignorecondregex = ConsumerVal ($hash, $c, 'spignorecondregex', ''); # Regex einer zusätzliche Einschaltbedingung my $condval = ReadingsVal ($digncond, $rigncond, ''); # Wert zum Vergleich mit Regex - if ($condval && $debug =~ /consumerSwitching/x) { + if ($condval && $debug =~ /consumerSwitching${c}/x) { my $name = $hash->{NAME}; Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - PV surplus ignore condition ist set - device: $digncond, reading: $rigncond, condition: $spignorecondregex}); } @@ -16453,7 +16726,7 @@ sub isInterruptable { my ($swoffcond,$info,$err) = isAddSwitchOffCond ($hash, $c, $intable, $hyst); Log3 ($name, 1, "$name - $err") if($err); - if ($print && $debug =~ /consumerSwitching/x) { + if ($print && $debug =~ /consumerSwitching${c}/x) { Log3 ($name, 1, qq{$name DEBUG> consumer "$c" - Interrupt Info: $info}); } @@ -16643,6 +16916,11 @@ sub isDeviceValid { my ($a, $h) = parseParams ($dev); + if ($a->[0] && $a->[0] =~ /\@/xs ) { # Remote Device + $a->[0] = (split '@', $a->[0])[0]; + return ($err, $a->[0], $h); # ToDo: $h aus remote Werten anreichern + } + if (!$a->[0] || !$defs{$a->[0]}) { $a->[0] //= ''; $err = qq{The device '$a->[0]' doesn't exist or is not a valid device.}; @@ -17707,6 +17985,7 @@ return $def; # oncom - Einschaltkommando # offcom - Ausschaltkommando # physoffon - physischer Schaltzustand ein/aus +# logoffon - logischer Schaltzustand ein/aus # onoff - logischer ein/aus Zustand des am Consumer angeschlossenen Endverbrauchers # asynchron - Arbeitsweise des FHEM Consumer Devices # retotal - Reading der Leistungsmessung @@ -17716,7 +17995,7 @@ return $def; # energythreshold - Schwellenwert (Wh pro Stunde) ab der ein Verbraucher als aktiv gewertet wird # upcurr - Unit des aktuellen Verbrauchs # avgenergy - initialer / gemessener Durchschnittsverbrauch pro Stunde -# avgruntime - durchschnittliche Einschalt- bzw. Zykluszeit (Minuten) +# runtimeAvgDay - durchschnittliche 'On'-Zeit an einem Tag (Minuten) # epieces - prognostizierte Energiescheiben (Hash) # ehodpieces - geplante Energiescheiben nach Tagesstunde (hour of day) (Hash) # dswoncond - Device zur Lieferung einer zusätzliche Einschaltbedingung @@ -17829,19 +18108,19 @@ To create the solar forecast, the SolarForecast module can use different service
      - - - - + + + + + + +
      DWD solar forecast based on the radiation forecast of the German Weather Service (Model DWD)
      SolCast-API uses forecast data of the SolCast API (Model SolCastAPI)
      ForecastSolar-API uses forecast data of the Forecast.Solar API (Model ForecastSolarAPI)
      VictronKI-API Victron Energy API of the VRM Portal (Model VictronKiAPI)
      DWD solar forecast based on MOSMIX data of the German Weather Service
      SolCast-API uses forecast data of the SolCast API
      ForecastSolar-API uses forecast data of the Forecast.Solar API
      OpenMeteoDWD-API ICON weather models of the German Weather Service (DWD) via Open-Meteo
      OpenMeteoDWDEnsemble-API Access to the global ensemble forecast system (EPS) of the DWD
      OpenMeteoWorld-API Seamlessly combines weather models from organizations such as NOAA, DWD, CMCC and ECMWF via Open-Meteo
      VictronKI-API Victron Energy API of the VRM Portal

    -AI support can be enabled when using the Model DWD.
    The use of the mentioned API's is limited to the respective free version of the selected service.
    -In the assigned DWD_OpenData Device (attribute "ctrlWeatherDevX") the suitable weather station is to be specified -to get meteorological data (cloudiness, sunrise, etc.) or a radiation forecast (Model DWD) for the plant -location.

    +AI support can be activated depending on the model used.

    In addition to the PV generation forecast, consumption values or grid reference values are recorded and used for a consumption forecast.
    @@ -17868,7 +18147,7 @@ to ensure that the system configuration is correct.
    After the definition of the device, depending on the forecast sources used, it is mandatory to store additional - plant-specific information with the corresponding set commands.
    + plant-specific information.
    The following set commands and attributes are used to store information that is relevant for the function of the module:

    @@ -17878,7 +18157,7 @@ to ensure that the system configuration is correct. ctrlWeatherDevX DWD_OpenData Device which provides meteorological data (e.g. cloud cover) currentRadiationAPI DWD_OpenData Device or API for the delivery of radiation data. currentInverterDev Device which provides PV performance data - currentMeterDev Device which supplies network I/O data + setupMeterDev Device which supplies network I/O data currentBatteryDev Device which provides battery performance data (if available) inverterStrings Identifier of the existing plant strings moduleAzimuth Azimuth of the plant strings @@ -18094,65 +18373,6 @@ to ensure that the system configuration is correct.
    -
      - -
    • 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. - It can also be a dummy device with corresponding readings. The meaning of the respective "Readingname" is: -

      - -
        - - - - - - - - - - - - - - - - - - - - - -
        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)
        If the counter is reset to '0' at the beginning of the day (daily counter), the module handles this situation accordingly.
        In this case, a message is displayed in the log with verbose 3.
        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)
        If the counter is reset to '0' at the beginning of the day (daily counter), the module handles this situation accordingly.
        In this case, a message is displayed in the log with verbose 3.
        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
        -
      -
      - - Special cases: If the reading for gcon and gfeedin should be identical but signed, - the keys gfeedin and gcon can be defined as follows:

      -
        - gfeedin=-gcon    (a negative value of gcon is used as gfeedin)
        - gcon=-gfeedin    (a negative value of gfeedin is used as gcon) -
      -
      - - The unit is omitted in the particular special case.

      - -
        - Example:
        - 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, - the sum of the feed in the reading "EinWirkZaehler". (kWh) -
      -
    • -
    -
    -
    • currentRadiationAPI

      @@ -18844,7 +19064,7 @@ to ensure that the system configuration is correct. batsetsoc optimum SOC setpoint (%) for the day confc expected energy consumption (Wh) con real energy consumption (Wh) of the house - conprice Price for the purchase of one kWh. The currency of the price is defined in the currentMeterDev. + conprice Price for the purchase of one kWh. The currency of the price is defined in the setupMeterDev. csmtXX total energy consumption of ConsumerXX csmeXX Energy consumption of ConsumerXX in the hour of the day (hour 99 = daily energy consumption) cyclescsmXX Number of active cycles of ConsumerXX of the day @@ -18852,8 +19072,8 @@ to ensure that the system configuration is correct. etotal total energy yield (Wh) at the beginning of the hour gcon real power consumption (Wh) from the electricity grid gfeedin real feed-in (Wh) into the electricity grid - feedprice Remuneration for the feed-in of one kWh. The currency of the price is defined in the currentMeterDev. - hourscsmeXX average hours of an active cycle of ConsumerXX of the day + feedprice Remuneration for the feed-in of one kWh. The currency of the price is defined in the setupMeterDev. + hourscsmeXX total active hours of the day from ConsumerXX minutescsmXX total active minutes in the hour of ConsumerXX pvfc the predicted PV yield (Wh) pvrl real PV generation (Wh) @@ -19388,7 +19608,7 @@ to ensure that the system configuration is correct.
        - + @@ -19396,7 +19616,7 @@ to ensure that the system configuration is correct. - + @@ -19499,12 +19719,13 @@ to ensure that the system configuration is correct. - + + @@ -19788,8 +20009,8 @@ to ensure that the system configuration is correct. - - + + @@ -19799,7 +20020,7 @@ to ensure that the system configuration is correct.
        Hinweis: The selection of the parameters energycosts and feedincome only makes sense if the optional keys - conprice and feedprice are set in currentMeterDev. + conprice and feedprice are set in setupMeterDev.
        @@ -20080,6 +20301,63 @@ to ensure that the system configuration is correct. Color of the weather icons for the night hours.
        + + +
      • setupMeterDev <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. + It can also be a dummy device with corresponding readings. The meaning of the respective "Readingname" is: +

        + +
          +
      • aiProcess Data enrichment and training process for AI support
        aiData Data use AI in the forecasting process
        apiCall Retrieval API interface without data output
        batteryManagement Battery management control values (SoC)
        collectData detailed data collection
        consumerPlanning Consumer scheduling processes
        consumerSwitching Operations of the internal consumer switching module
        consumerSwitchingXX Operations of the internal consumer switching module of consumer XX
        consumption Consumption calculation and use
        dwdComm Communication with the website or server of the German Weather Service (DWD)
        epiecesCalc Calculation of specific energy consumption per operating hour and consumer
        allStringsFullfilled Fulfillment status of error-free generation of all strings
        conForecastTillNextSunrise Consumption forecast from current hour to the coming sunrise
        currentAPIinterval the current call interval of the SolCast API (only model SolCastAPI) in seconds
        currentRunMtsConsumer_XX the running time (minutes) of the consumer "XX" since the last switch-on. (0 - consumer is off)
        currentRunMtsConsumer_XX the running time (minutes) of the consumer "XX" since the last switch-on. (last running cycle)
        dayAfterTomorrowPVforecast provides the forecast of PV generation for the day after tomorrow (if available) without autocorrection (raw data)
        daysUntilBatteryCare Days until the next battery maintenance (reaching the charge 'maxSoC' from attribute ctrlBatSocManagement)
        lastretrieval_time the last call time of the API (only Model SolCastAPI, ForecastSolarAPI)
        lastretrieval_timestamp the timestamp of the last call time of the API (only Model SolCastAPI, ForecastSolarAPI)
        response_message the last status message of the API (only Model SolCastAPI, ForecastSolarAPI)
        runTimeAvgDayConsumer_XX the average running time (minutes) of consumer "XX" on one day
        runTimeCentralTask the runtime of the last SolarForecast interval (total process) in seconds
        runTimeTrainAI the runtime of the last AI training cycle in seconds
        runTimeLastAPIAnswer the last response time of the API call to a request in seconds (only model SolCastAPI, ForecastSolarAPI)
        consumption Energy consumption
        consumptionForecast forecasted energy consumption
        energycosts Cost of energy purchased from the grid. The currency is defined in the currentMeterDev, key conprice.
        feedincome Remuneration for feeding into the grid. The currency is defined in the currentMeterDev, key feedprice.
        energycosts Cost of energy purchased from the grid. The currency is defined in the setupMeterDev, key conprice.
        feedincome Remuneration for feeding into the grid. The currency is defined in the setupMeterDev, key feedprice.
        gridconsumption Energy purchase from the public grid
        gridfeedin Feed into the public grid
        pvReal real PV generation (default for graphicBeam1Content)
        + + + + + + + + + + + + + + + + + + + + +
        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)
        If the counter is reset to '0' at the beginning of the day (daily counter), the module handles this situation accordingly.
        In this case, a message is displayed in the log with verbose 3.
        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)
        If the counter is reset to '0' at the beginning of the day (daily counter), the module handles this situation accordingly.
        In this case, a message is displayed in the log with verbose 3.
        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
        +
      +
      + + Special cases: If the reading for gcon and gfeedin should be identical but signed, + the keys gfeedin and gcon can be defined as follows:

      +
        + gfeedin=-gcon    (a negative value of gcon is used as gfeedin)
        + gcon=-gfeedin    (a negative value of gfeedin is used as gcon) +
      +
      + + The unit is omitted in the particular special case.

      + +
        + Example:
        + attr <name> setupMeterDev 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, + the sum of the feed in the reading "EinWirkZaehler". (kWh) +
      +
    • +
    @@ -20100,20 +20378,20 @@ Zur Erstellung der solaren Vorhersage kann das Modul SolarForecast unterschiedli
      - - - - - + + + + + + + +
      DWD solare Vorhersage basierend auf der Strahlungsprognose des Deutschen Wetterdienstes (Model DWD)
      SolCast-API verwendet Prognosedaten der SolCast API (Model SolCastAPI)
      ForecastSolar-API verwendet Prognosedaten der Forecast.Solar API (Model ForecastSolarAPI)
      VictronKI-API Victron Energy API des VRM Portals (Model VictronKiAPI)
      DWD solare Vorhersage basierend auf MOSMIX Daten des Deutschen Wetterdienstes
      SolCast-API verwendet Prognosedaten der SolCast API
      ForecastSolar-API verwendet Prognosedaten der Forecast.Solar API
      OpenMeteoDWD-API ICON-Wettermodelle des Deutschen Wetterdienstes (DWD) über Open-Meteo
      OpenMeteoDWDEnsemble-API Zugang zum globalen Ensemble-Vorhersagesystem (EPS) des DWD
      OpenMeteoWorld-API vereint nahtlos Wettermodelle von Organisationen wie NOAA, DWD, CMCC und ECMWF über Open-Meteo
      VictronKI-API Victron Energy API des VRM Portals

    -Bei Verwendung des Model DWD kann eine KI-Unterstützung aktiviert werden.
    Die Nutzung der erwähnten API's beschränkt sich auf die jeweils kostenlose Version des Dienstes.
    -Im zugeordneten DWD_OpenData Device (Attribut "ctrlWeatherDevX") ist die passende Wetterstation -festzulegen um meteorologische Daten (Bewölkung, Sonnenaufgang, u.a.) bzw. eine Strahlungsprognose (Model DWD) -für den Anlagenstandort zu erhalten.

    +Die KI-Unterstützung kann in Abhängigkeit vom verwendeten Model aktiviert werden.

    Über die PV Erzeugungsprognose hinaus werden Verbrauchswerte bzw. Netzbezugswerte erfasst und für eine Verbrauchsprognose verwendet.
    @@ -20121,7 +20399,7 @@ Das Modul errechnet aus den Prognosewerten einen zukünftigen Energieüberschuß genutzt wird. Weiterhin bietet das Modul eine Consumer Integration zur integrierten Planung und Steuerung von PV Überschuß abhängigen Verbraucherschaltungen.

    -Bei der ersten Definition des Moduls wird der Benutzer über eine Guided Procedure unterstützt um alle initialen Eingaben +Bei der ersten Definition des Moduls wird der Benutzer über eine Guided Procedure unterstützt um alle initial notwendigen Eingaben vorzunehmen.
    Am Ende des Vorganges und nach relevanten Änderungen der Anlagen- bzw. Devicekonfiguration sollte unbedingt mit einem set <name> plantConfiguration ceck @@ -20141,8 +20419,8 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
    Nach der Definition des Devices sind in Abhängigkeit der verwendeten Prognosequellen zwingend weitere - anlagenspezifische Angaben mit den entsprechenden set-Kommandos zu hinterlegen.
    - Mit nachfolgenden set-Kommandos und Attributen werden für die Funktion des Moduls maßgebliche Informationen + anlagenspezifische Angaben zu hinterlegen.
    + Mit nachfolgenden Set-Kommandos und Attributen werden für die Funktion des Moduls maßgebliche Informationen hinterlegt:

      @@ -20151,7 +20429,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. ctrlWeatherDevX DWD_OpenData Device welches meteorologische Daten (z.B. Bewölkung) liefert currentRadiationAPI DWD_OpenData Device bzw. API zur Lieferung von Strahlungsdaten currentInverterDev Device welches PV Leistungsdaten liefert - currentMeterDev Device welches Netz I/O-Daten liefert + setupMeterDev Device welches Netz I/O-Daten liefert currentBatteryDev Device welches Batterie Leistungsdaten liefert (sofern vorhanden) inverterStrings Bezeichner der vorhandenen Anlagenstrings moduleAzimuth Ausrichtung (Azimut) der Anlagenstrings @@ -20367,65 +20645,6 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.

    -
      - -
    • currentMeterDev <Meter Device Name> gcon=<Readingname>:<Einheit> contotal=<Readingname>:<Einheit> - gfeedin=<Readingname>:<Einheit> feedtotal=<Readingname>:<Einheit> - [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. - Es kann auch ein Dummy Device mit entsprechenden Readings sein. Die Bedeutung des jeweiligen "Readingname" ist: -

      - -
        - - - - - - - - - - - - - - - - - - - - - -
        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)
        Wird der Zähler zu Beginn des Tages auf '0' zurückgesetzt (Tageszähler), behandelt das Modul diese Situation entsprechend.
        In diesem Fall erfolgt eine Meldung im Log mit verbose 3.
        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)
        Wird der Zähler zu Beginn des Tages auf '0' zurückgesetzt (Tageszähler), behandelt das Modul diese Situation entsprechend.
        In diesem Fall erfolgt eine Meldung im Log mit verbose 3.
        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
        -
      -
      - - Sonderfälle: Sollte das Reading für gcon und gfeedin identisch, aber vorzeichenbehaftet sein, - können die Schlüssel gfeedin und gcon wie folgt definiert werden:

      -
        - gfeedin=-gcon    (ein negativer Wert von gcon wird als gfeedin verwendet)
        - gcon=-gfeedin    (ein negativer Wert von gfeedin wird als gcon verwendet) -
      -
      - - Die Einheit entfällt in dem jeweiligen Sonderfall.

      - -
        - Beispiel:
        - 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, - die Summe der Einspeisung im Reading "EinWirkZaehler" (kWh) -
      -
    • -
    -
    -
    • currentRadiationAPI

      @@ -21119,35 +21338,36 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
        - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        batintotal totale Batterieladung (Wh) zu Beginn der Stunde
        batin Batterieladung der Stunde (Wh)
        batouttotal totale Batterieentladung (Wh) zu Beginn der Stunde
        batout Batterieentladung der Stunde (Wh)
        batmaxsoc maximaler SOC (%) des Tages
        batsetsoc optimaler SOC Sollwert (%) für den Tag
        csmtXX Energieverbrauch total von ConsumerXX
        csmeXX Energieverbrauch von ConsumerXX in der Stunde des Tages (Stunde 99 = Tagesenergieverbrauch)
        confc erwarteter Energieverbrauch (Wh)
        con realer Energieverbrauch (Wh) des Hauses
        conprice Preis für den Bezug einer kWh. Die Einheit des Preises ist im currentMeterDev definiert.
        cyclescsmXX Anzahl aktive Zyklen von ConsumerXX des Tages
        DoN Sonnenauf- und untergangsstatus (0 - Nacht, 1 - Tag)
        etotal totaler Energieertrag (Wh) zu Beginn der Stunde
        gcon realer Leistungsbezug (Wh) aus dem Stromnetz
        gfeedin reale Einspeisung (Wh) in das Stromnetz
        feedprice Vergütung für die Einpeisung einer kWh. Die Währung des Preises ist im currentMeterDev definiert.
        hourscsmeXX durchschnittliche Stunden eines Aktivzyklus von ConsumerXX des Tages
        minutescsmXX Summe Aktivminuten in der Stunde von ConsumerXX
        pvfc der prognostizierte PV Ertrag (Wh)
        pvrl reale PV Erzeugung (Wh)
        pvrlvd 1-'pvrl' ist gültig und wird im Lernprozess berücksichtigt, 0-'pvrl' ist als abnormal bewertet
        pvcorrf verwendeter Autokorrekturfaktor / erreichte Prognosequalität
        rad1h Globalstrahlung (kJ/m2)
        sunalt Höhe der Sonne (in Dezimalgrad)
        sunaz Azimuth der Sonne (in Dezimalgrad)
        wid Identifikationsnummer des Wetters
        wcc effektive Wolkenbedeckung
        rr1c Gesamtniederschlag in der letzten Stunde kg/m2
        batintotal totale Batterieladung (Wh) zu Beginn der Stunde
        batin Batterieladung der Stunde (Wh)
        batouttotal totale Batterieentladung (Wh) zu Beginn der Stunde
        batout Batterieentladung der Stunde (Wh)
        batmaxsoc maximaler SOC (%) des Tages
        batsetsoc optimaler SOC Sollwert (%) für den Tag
        csmtXX Energieverbrauch total von ConsumerXX
        csmeXX Energieverbrauch von ConsumerXX in der Stunde des Tages (Stunde 99 = Tagesenergieverbrauch)
        confc erwarteter Energieverbrauch (Wh)
        con realer Energieverbrauch (Wh) des Hauses
        conprice Preis für den Bezug einer kWh. Die Einheit des Preises ist im setupMeterDev definiert.
        cyclescsmXX Anzahl aktive Zyklen von ConsumerXX des Tages
        DoN Sonnenauf- und untergangsstatus (0 - Nacht, 1 - Tag)
        etotal totaler Energieertrag (Wh) zu Beginn der Stunde
        gcon realer Leistungsbezug (Wh) aus dem Stromnetz
        gfeedin reale Einspeisung (Wh) in das Stromnetz
        feedprice Vergütung für die Einpeisung einer kWh. Die Währung des Preises ist im setupMeterDev definiert.
        avgcycmntscsmXX durchschnittliche Dauer eines Einschaltzyklus des Tages von ConsumerXX in Minuten
        hourscsmeXX Summe Aktivstunden des Tages von ConsumerXX
        minutescsmXX Summe Aktivminuten in der Stunde von ConsumerXX
        pvfc der prognostizierte PV Ertrag (Wh)
        pvrl reale PV Erzeugung (Wh)
        pvrlvd 1-'pvrl' ist gültig und wird im Lernprozess berücksichtigt, 0-'pvrl' ist als abnormal bewertet
        pvcorrf verwendeter Autokorrekturfaktor / erreichte Prognosequalität
        rad1h Globalstrahlung (kJ/m2)
        sunalt Höhe der Sonne (in Dezimalgrad)
        sunaz Azimuth der Sonne (in Dezimalgrad)
        wid Identifikationsnummer des Wetters
        wcc effektive Wolkenbedeckung
        rr1c Gesamtniederschlag in der letzten Stunde kg/m2
      @@ -21660,7 +21880,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. Für die ausgewählten Consumer (Nummer) werden Readings der Form consumerXX_ConsumptionRecommended erstellt.
      Diese Readings signalisieren ob das Einschalten dieses Consumers abhängig von seinen Verbrauchsdaten und der aktuellen PV-Erzeugung bzw. des aktuellen Energieüberschusses empfohlen ist. Der Wert des erstellten Readings korreliert - mit den berechneten Planungsdaten das Consumers, kann aber von dem Planungszeitraum abweichen.
      + mit den berechneten Planungsdaten des Consumers, kann aber von dem Planungszeitraum abweichen.

    • @@ -21672,7 +21892,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
        - + @@ -21680,7 +21900,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. - + @@ -21783,12 +22003,13 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. - + + @@ -22072,8 +22293,8 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. - - + + @@ -22082,7 +22303,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
        - Hinweis: Die Auswahl der Parameter energycosts und feedincome ist nur sinnvoll wenn in currentMeterDev die + Hinweis: Die Auswahl der Parameter energycosts und feedincome ist nur sinnvoll wenn in setupMeterDev die optionalen Schlüssel conprice und feedprice gesetzt sind.
        @@ -22362,6 +22583,63 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. Farbe der Wetter-Icons für die Nachtstunden.
        + + +
      • setupMeterDev <Meter Device Name> gcon=<Readingname>:<Einheit> contotal=<Readingname>:<Einheit> + gfeedin=<Readingname>:<Einheit> feedtotal=<Readingname>:<Einheit> + [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. + Es kann auch ein Dummy Device mit entsprechenden Readings sein. Die Bedeutung des jeweiligen "Readingname" ist: +

        + +
          +
      • aiProcess Datenanreicherung und Trainingsprozess der KI Unterstützung
        aiData Datennutzung KI im Prognoseprozess
        apiCall Abruf API Schnittstelle ohne Datenausgabe
        batteryManagement Steuerungswerte des Batterie Managements (SoC)
        collectData detailliierte Datensammlung
        consumerPlanning Consumer Einplanungsprozesse
        consumerSwitching Operationen des internen Consumer Schaltmodul
        consumerSwitchingXX Operationen des internen Consumer Schaltmodul für Verbraucher XX
        consumption Verbrauchskalkulation und -nutzung
        dwdComm Kommunikation mit Webseite oder Server des Deutschen Wetterdienst (DWD)
        epiecesCalc Berechnung des spezifischen Energieverbrauchs je Betriebsstunde und Verbraucher
        allStringsFullfilled Erfüllungsstatus der fehlerfreien Generierung aller Strings
        conForecastTillNextSunrise Verbrauchsprognose von aktueller Stunde bis zum kommenden Sonnenaufgang
        currentAPIinterval das aktuelle Abrufintervall der SolCast API (nur Model SolCastAPI) in Sekunden
        currentRunMtsConsumer_XX die Laufzeit (Minuten) des Verbrauchers "XX" seit dem letzten Einschalten. (0 - Verbraucher ist aus)
        currentRunMtsConsumer_XX die Laufzeit (Minuten) des Verbrauchers "XX" seit dem letzten Einschalten. (letzter Laufzyklus)
        dayAfterTomorrowPVforecast liefert die Vorhersage der PV Erzeugung für Übermorgen (sofern verfügbar) ohne Autokorrektur (Rohdaten).
        daysUntilBatteryCare Tage bis zur nächsten Batteriepflege (Erreichen der Ladung 'maxSoC' aus Attribut ctrlBatSocManagement)
        lastretrieval_time der letzte Abrufzeitpunkt der API (nur Model SolCastAPI, ForecastSolarAPI)
        lastretrieval_timestamp der Timestamp der letzen Abrufzeitpunkt der API (nur Model SolCastAPI, ForecastSolarAPI)
        response_message die letzte Statusmeldung der API (nur Model SolCastAPI, ForecastSolarAPI)
        runTimeAvgDayConsumer_XX die durchschnittliche Laufzeit (Minuten) des Verbrauchers "XX" an einem Tag
        runTimeCentralTask die Laufzeit des letzten SolarForecast Intervalls (Gesamtprozess) in Sekunden
        runTimeTrainAI die Laufzeit des letzten KI Trainingszyklus in Sekunden
        runTimeLastAPIAnswer die letzte Antwortzeit des API Abrufs auf einen Request in Sekunden (nur Model SolCastAPI, ForecastSolarAPI)
        consumption Energieverbrauch
        consumptionForecast prognostizierter Energieverbrauch
        energycosts Kosten des Energiebezuges aus dem Netz. Die Währung ist im currentMeterDev, Schlüssel conprice, definiert.
        feedincome Vergütung für die Netzeinspeisung. Die Währung ist im currentMeterDev, Schlüssel feedprice, definiert.
        energycosts Kosten des Energiebezuges aus dem Netz. Die Währung ist im setupMeterDev, Schlüssel conprice, definiert.
        feedincome Vergütung für die Netzeinspeisung. Die Währung ist im setupMeterDev, Schlüssel feedprice, definiert.
        gridconsumption Energiebezug aus dem öffentlichen Netz
        gridfeedin Einspeisung in das öffentliche Netz
        pvReal reale PV-Erzeugung (default für graphicBeam1Content)
        + + + + + + + + + + + + + + + + + + + + +
        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)
        Wird der Zähler zu Beginn des Tages auf '0' zurückgesetzt (Tageszähler), behandelt das Modul diese Situation entsprechend.
        In diesem Fall erfolgt eine Meldung im Log mit verbose 3.
        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)
        Wird der Zähler zu Beginn des Tages auf '0' zurückgesetzt (Tageszähler), behandelt das Modul diese Situation entsprechend.
        In diesem Fall erfolgt eine Meldung im Log mit verbose 3.
        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
        +
      +
      + + Sonderfälle: Sollte das Reading für gcon und gfeedin identisch, aber vorzeichenbehaftet sein, + können die Schlüssel gfeedin und gcon wie folgt definiert werden:

      +
        + gfeedin=-gcon    (ein negativer Wert von gcon wird als gfeedin verwendet)
        + gcon=-gfeedin    (ein negativer Wert von gfeedin wird als gcon verwendet) +
      +
      + + Die Einheit entfällt in dem jeweiligen Sonderfall.

      + +
        + Beispiel:
        + attr <name> setupMeterDev 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, + die Summe der Einspeisung im Reading "EinWirkZaehler" (kWh) +
      + +