diff --git a/fhem/CHANGED b/fhem/CHANGED index 369049387..a9a7e03ee 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,7 @@ # 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: set inverterStrings to attr setupInverterStrings + !NOTE! save FHEM config after restart - bufgix: 72_FRITZBOX: Ändern OffSet DECT Heizkörper Termostate Sommerzeit - change: 76_SolarForecast: set currentInverterDev to attr setupInverterDev !NOTE! save FHEM config after restart diff --git a/fhem/FHEM/76_SolarForecast.pm b/fhem/FHEM/76_SolarForecast.pm index 63aa37853..00b3c8e87 100644 --- a/fhem/FHEM/76_SolarForecast.pm +++ b/fhem/FHEM/76_SolarForecast.pm @@ -157,6 +157,7 @@ BEGIN { # Versions History intern my %vNotesIntern = ( + "1.25.0" => "05.06.2024 transformed setter inverterStrings to attr setupInverterStrings, calcTodayPVdeviation: fix continuously calc again ", "1.24.0" => "03.06.2024 transformed setter currentInverterDev to attr setupInverterDev, calcTodayPVdeviation: fix continuously calc ", "1.23.0" => "02.06.2024 transformed setter currentBatteryDev to attr setupBatteryDev, _transferInverterValues: change output for DEBUG ". "new key attrInvChangedTs in circular, prepare transformation of currentInverterDev ". @@ -495,7 +496,6 @@ my @fs = qw( ftui_forecast.css # Anlagenkonfiguration: maßgebliche Readings my @rconfigs = qw( pvCorrectionFactor_Auto currentRadiationAPI - inverterStrings moduleAzimuth modulePeakString moduleDeclination @@ -525,7 +525,7 @@ my @aconfigs = qw( affect70percentRule affectBatteryPreferredCharge affectConsFo graphicHeaderDetail graphicHeaderShow graphicHistoryHour graphicHourCount graphicHourStyle graphicLayoutType graphicSelect graphicShowDiff graphicShowNight graphicShowWeather graphicSpaceSize graphicStartHtml graphicEndHtml graphicWeatherColor graphicWeatherColorNight - setupMeterDev setupBatteryDev setupInverterDev + setupMeterDev setupBatteryDev setupInverterDev setupInverterStrings ); for my $cinit (1..$maxconsumer) { @@ -544,7 +544,6 @@ my %hset = ( # Ha consumerNewPlanning => { fn => \&_setconsumerNewPlanning }, currentRadiationAPI => { fn => \&_setcurrentRadiationAPI }, modulePeakString => { fn => \&_setmodulePeakString }, - inverterStrings => { fn => \&_setinverterStrings }, clientAction => { fn => \&_setclientAction }, energyH4Trigger => { fn => \&_setTrigger }, plantConfiguration => { fn => \&_setplantConfiguration }, @@ -608,6 +607,7 @@ my %hattr = ( # H setupMeterDev => { fn => \&_attrMeterDev }, setupBatteryDev => { fn => \&_attrBatteryDev }, setupInverterDev => { fn => \&_attrInverterDev }, + setupInverterStrings => { fn => \&_attrInverterStrings }, ); my %htr = ( # Hash even/odd für @@ -658,8 +658,8 @@ my %hqtxt = ( DE => qq{Bitte geben sie das Wechselrichter Device mit "attr LINK setupInverterDev" 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} }, + ist => { EN => qq{Please define all of your used string names with "attr LINK setupInverterStrings"}, + DE => qq{Bitte geben sie alle von Ihnen verwendeten Stringnamen mit "attr LINK setupInverterStrings" an} }, mps => { EN => qq{Please enter the DC peak power of each string with "set LINK modulePeakString"}, DE => qq{Bitte geben sie die DC Spitzenleistung von jedem String mit "set LINK modulePeakString" an} }, mdr => { EN => qq{Please specify the module direction with "set LINK moduleAzimuth"}, @@ -1217,6 +1217,7 @@ sub Initialize { "graphicWeatherColor:colorpicker,RGB ". "graphicWeatherColorNight:colorpicker,RGB ". "setupInverterDev:textField-long ". + "setupInverterStrings ". "setupMeterDev:textField-long ". "setupBatteryDev:textField-long ". $consumer. @@ -1417,7 +1418,6 @@ sub Set { consumption currentInverterSet energyH4TriggerSet - inverterStringSet moduleRoofTopSet powerTriggerSet pvCorrection @@ -1469,7 +1469,6 @@ sub Set { "consumerNewPlanning:$coms ". "currentRadiationAPI:$rdd ". "energyH4Trigger:textField-long ". - "inverterStrings ". "modulePeakString ". "operatingMemory:backup,save".$rf." ". "operationMode:active,inactive ". @@ -1799,40 +1798,6 @@ sub _setmoduleRoofTops { ## no critic "not used" return; } -################################################################ -# Setter inverterStrings -################################################################ -sub _setinverterStrings { ## no critic "not used" - my $paref = shift; - my $hash = $paref->{hash}; - my $name = $paref->{name}; - my $prop = $paref->{prop} // return qq{no inverter strings specified}; - - if ($prop =~ /\?/xs) { - return qq{The inverter string designation is wrong. An inverter string name must not contain a '?' character!}; - } - - my $type = $hash->{TYPE}; - - my @istrings = split ",", $prop; - - for my $k (keys %{$data{$type}{$name}{solcastapi}}) { - next if ($k =~ /\?/xs || grep /^$k$/, @istrings); - delete $data{$type}{$name}{solcastapi}{$k}; - } - - readingsSingleUpdate ($hash, 'inverterStrings', $prop, 1); - writeCacheToFile ($hash, 'plantconfig', $plantcfg.$name); # Anlagenkonfiguration File schreiben - - return if(_checkSetupNotComplete ($hash)); # keine Stringkonfiguration wenn Setup noch nicht komplett - - my $ret = qq{NOTE: After setting or changing "inverterStrings" please check }. - qq{/ set all module parameter (e.g. moduleDeclination) again ! \n}. - qq{Use "set $name plantConfiguration check" to validate your Setup.}; - -return $ret; -} - ################################################################ # Setter operationMode ################################################################ @@ -2692,7 +2657,7 @@ sub __getSolCastData { Log3 ($name, 1, "$name DEBUG> SolCast API Call - possible daily API Calls: $madc"); } - $paref->{allstrings} = ReadingsVal ($name, 'inverterStrings', ''); + $paref->{allstrings} = AttrVal ($name, 'setupInverterStrings', ''); $paref->{firstreq} = 1; # 1. Request, V 0.80.18 __solCast_ApiRequest ($paref); @@ -3097,7 +3062,7 @@ sub __getForecastSolarData { } } - $paref->{allstrings} = ReadingsVal($name, 'inverterStrings', ''); + $paref->{allstrings} = AttrVal ($name, 'setupInverterStrings', ''); __forecastSolar_ApiRequest ($paref); @@ -3367,7 +3332,7 @@ sub ___setForeCastAPIcallKeyData { ## Berechnung des optimalen Request Intervalls ################################################ - my $snum = scalar (split ",", ReadingsVal($name, 'inverterStrings', 'Dummy')); # Anzahl der Strings (mindestens ein String als Dummy) + my $snum = scalar (split ",", AttrVal ($name, 'setupInverterStrings', 'Dummy')); # Anzahl der Strings (mindestens ein String als Dummy) my $period = SolCastAPIVal ($hash, '?All', '?All', 'requests_limit_period', 3600); # Requests Limit Periode my $limit = SolCastAPIVal ($hash, '?All', '?All', 'requests_limit', 12); # Request Limit in Periode @@ -3825,7 +3790,7 @@ sub __VictronVRM_ApiResponseForecast { if ($val) { $val = sprintf "%.0f", $val; - my $string = ReadingsVal ($name, 'inverterStrings', '?'); + my $string = AttrVal ($name, 'setupInverterStrings', '?'); $data{$type}{$name}{solcastapi}{$string}{$starttmstr}{pv_estimate50} = $val; } @@ -3848,7 +3813,7 @@ sub __VictronVRM_ApiResponseForecast { if ($val) { $val = sprintf "%.2f", $val; - my $string = ReadingsVal ($name, 'inverterStrings', '?'); + my $string = AttrVal ($name, 'setupInverterStrings', '?'); $data{$type}{$name}{solcastapi}{$string.'_co'}{$starttmstr}{co_estimate} = $val; } @@ -3973,7 +3938,7 @@ sub __getopenMeteoData { debugLog ($paref, 'apiCall', qq{Open-Meteo API Call - the daily API requests -> limited to: $ometmaxreq, done: $donearq}); my $submodel = InternalVal ($hash->{NAME}, 'MODEL', ''); - $paref->{allstrings} = ReadingsVal ($name, 'inverterStrings', ''); + $paref->{allstrings} = AttrVal ($name, 'setupInverterStrings', ''); $paref->{submodel} = $submodel eq 'OpenMeteoDWDAPI' ? 'DWD ICON Seamless' : $submodel eq 'OpenMeteoDWDEnsembleAPI' ? 'DWD ICON Seamless Ensemble' : $submodel eq 'OpenMeteoWorldAPI' ? 'World Best Match' : @@ -5519,7 +5484,7 @@ sub _attrMeterDev { ## no critic "not used" return if(!$init_done); - if ($paref->{cmd} eq 'set' ) { + if ($paref->{cmd} eq 'set') { my ($err, $medev, $h) = isDeviceValid ( { name => $name, obj => $aVal, method => 'string' } ); return $err if($err); @@ -5541,7 +5506,7 @@ sub _attrMeterDev { ## no critic "not used" return qq{Incorrect input for key 'feedprice'. Please consider the commandref.} if(scalar(@afp) != 2 && scalar(@afp) != 3); } } - elsif ($paref->{cmd} eq 'del' ) { + elsif ($paref->{cmd} eq 'del') { readingsDelete ($hash, "Current_GridConsumption"); readingsDelete ($hash, "Current_GridFeedIn"); delete $data{$type}{$name}{circular}{'99'}{initdayfeedin}; @@ -5581,7 +5546,7 @@ sub _attrInverterDev { ## no critic "not used" return if(!$init_done); - if ($paref->{cmd} eq 'set' ) { + if ($paref->{cmd} eq 'set') { my ($err, $indev, $h) = isDeviceValid ( { name => $name, obj => $aVal, method => 'string' } ); return $err if($err); @@ -5595,7 +5560,7 @@ sub _attrInverterDev { ## no critic "not used" $data{$type}{$name}{circular}{99}{attrInvChangedTs} = int time; } - elsif ($paref->{cmd} eq 'del' ) { + elsif ($paref->{cmd} eq 'del') { readingsDelete ($hash, "Current_PV"); deleteReadingspec ($hash, ".*_PVreal" ); undef @{$data{$type}{$name}{current}{genslidereg}}; @@ -5608,6 +5573,37 @@ sub _attrInverterDev { ## no critic "not used" return; } +################################################################ +# Attr setupInverterStrings +################################################################ +sub _attrInverterStrings { ## 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') { + if ($aVal =~ /\?/xs) { + return qq{The inverter string designation is wrong. An inverter string name must not contain a '?' character!}; + } + + my @istrings = split ",", $aVal; + + for my $k (keys %{$data{$type}{$name}{solcastapi}}) { + next if ($k =~ /\?/xs || grep /^$k$/, @istrings); + delete $data{$type}{$name}{solcastapi}{$k}; + } + } + + InternalTimer (gettimeofday() + 3, 'FHEM::SolarForecast::writeCacheToFile', [$name, 'plantconfig', $plantcfg.$name], 0); # Anlagenkonfiguration File schreiben + +return; +} + ################################################################ # Attr setupBatteryDev ################################################################ @@ -5621,7 +5617,7 @@ sub _attrBatteryDev { ## no critic "not used" return if(!$init_done); - if ($paref->{cmd} eq 'set' ) { + if ($paref->{cmd} eq 'set') { my ($err, $badev, $h) = isDeviceValid ( { name => $name, obj => $aVal, method => 'string' } ); return $err if($err); @@ -5638,7 +5634,7 @@ sub _attrBatteryDev { ## no critic "not used" return qq{Incorrect input. It is not allowed that the keys pin and pout refer to each other.}; } } - elsif ($paref->{cmd} eq 'del' ) { + elsif ($paref->{cmd} eq 'del') { readingsDelete ($hash, 'Current_PowerBatIn'); readingsDelete ($hash, 'Current_PowerBatOut'); readingsDelete ($hash, 'Current_BatCharge'); @@ -5674,7 +5670,7 @@ sub _attrWeatherDev { ## no critic "not used" return if(!$init_done); - if ($paref->{cmd} eq 'set' ) { + if ($paref->{cmd} eq 'set') { if ($aVal !~ /^OpenMeteo/xs && (!$defs{$aVal} || $defs{$aVal}{TYPE} ne "DWD_OpenData")) { return qq{The device "$aVal" doesn't exist or has no TYPE 'DWD_OpenData'}; } @@ -6408,6 +6404,12 @@ sub centralTask { CommandAttr (undef, "$name setupInverterDev $val2"); readingsDelete ($hash, 'currentInverterDev'); } + + my $val3 = ReadingsVal ($name, 'inverterStrings', ''); # 05.06.2024 + if ($val3) { + CommandAttr (undef, "$name setupInverterStrings $val3"); + readingsDelete ($hash, 'inverterStrings'); + } ########################################################################################################################## setModel ($hash); # Model setzen @@ -6522,11 +6524,11 @@ sub createStringConfig { ## no critic "not used" delete $data{$type}{$name}{strings}; # Stringhash zurücksetzen $data{$type}{$name}{current}{allStringsFullfilled} = 0; - my @istrings = split ",", ReadingsVal ($name, 'inverterStrings', ''); # Stringbezeichner + my @istrings = split ",", AttrVal ($name, 'setupInverterStrings', ''); # Stringbezeichner $data{$type}{$name}{current}{allstringscount} = scalar @istrings; # Anzahl der Anlagenstrings if (!@istrings) { - return qq{Define all used strings with command "set $name inverterStrings" first.}; + return qq{Define all used strings with command "attr $name setupInverterStrings" first.}; } my $peak = ReadingsVal ($name, 'modulePeakString', ''); # kWp für jeden Stringbezeichner @@ -6541,7 +6543,7 @@ sub createStringConfig { ## no critic "not used" $data{$type}{$name}{current}{allstringspeak} += $pp * 1000; # insgesamt installierte Peakleistung in W } else { - return qq{Check "modulePeakString" -> the stringname "$strg" is not defined as valid string in reading "inverterStrings"}; + return qq{Check "modulePeakString" -> the stringname "$strg" is not defined as valid string in attribute "setupInverterStrings"}; } } @@ -6556,15 +6558,15 @@ sub createStringConfig { ## no critic "not used" $data{$type}{$name}{strings}{$is}{pk} = $pk; } else { - return qq{Check "moduleRoofTops" -> the stringname "$is" is not defined as valid string in reading "inverterStrings"}; + return qq{Check "moduleRoofTops" -> the stringname "$is" is not defined as valid string in attribute "setupInverterStrings"}; } } } elsif (isVictronKiUsed ($hash)) { - my $invs = ReadingsVal ($name, 'inverterStrings', ''); + my $invs = AttrVal ($name, 'setupInverterStrings', ''); if ($invs ne 'KI-based') { - return qq{You use a KI based model. Please set only "KI-based" as String with command "set $name inverterStrings".}; + return qq{You use a KI based model. Please set only "KI-based" as String with command "attr $name setupInverterStrings".}; } } elsif (!isVictronKiUsed ($hash)) { @@ -6578,7 +6580,7 @@ sub createStringConfig { ## no critic "not used" $data{$type}{$name}{strings}{$key}{tilt} = $value; } else { - return qq{Check "moduleDeclination" -> the stringname "$key" is not defined as valid string in reading "inverterStrings"}; + return qq{Check "moduleDeclination" -> the stringname "$key" is not defined as valid string in attribute "setupInverterStrings"}; } } @@ -6594,18 +6596,18 @@ sub createStringConfig { ## no critic "not used" $data{$type}{$name}{strings}{$key}{azimut} = _ident2azimuth ($value) // return $iwrong; } else { - return qq{Check "moduleAzimuth" -> the stringname "$key" is not defined as valid string in reading "inverterStrings"}; + return qq{Check "moduleAzimuth" -> the stringname "$key" is not defined as valid string in attribute "setupInverterStrings"}; } } } if(!keys %{$data{$type}{$name}{strings}}) { return qq{The string configuration seems to be incomplete. \n}. - qq{Please check the settings of inverterStrings, modulePeakString, moduleAzimuth, moduleDeclination }. + qq{Please check the settings of setupInverterStrings, modulePeakString, moduleAzimuth, moduleDeclination }. qq{and/or moduleRoofTops if SolCast-API is used.}; } - my @sca = keys %{$data{$type}{$name}{strings}}; # Gegencheck ob nicht mehr Strings in inverterStrings enthalten sind als eigentlich verwendet + my @sca = keys %{$data{$type}{$name}{strings}}; # Gegencheck ob nicht mehr Strings in setupInverterStrings enthalten sind als eigentlich verwendet my @tom; for my $sn (@istrings) { @@ -6614,7 +6616,7 @@ sub createStringConfig { ## no critic "not used" } if (@tom) { - return qq{Some Strings are not used. Please delete this string names from "inverterStrings" :}.join ",",@tom; + return qq{Some Strings are not used. Please delete this string names from "setupInverterStrings" :}.join ",",@tom; } $data{$type}{$name}{current}{allStringsFullfilled} = 1; @@ -7203,7 +7205,7 @@ sub __delObsoleteAPIData { delete $data{$type}{$name}{solcastapi}{'?All'}{$idx} if($idx =~ /^fc?([0-9]{1,2})_?([0-9]{1,2})$/xs); } - my @as = split ",", ReadingsVal($name, 'inverterStrings', ''); + my @as = split ",", AttrVal ($name, 'setupInverterStrings', ''); return if(!scalar @as); for my $k (keys %{$data{$type}{$name}{strings}}) { # veraltete Strings aus Strings-Hash löschen @@ -10621,7 +10623,8 @@ sub calcTodayPVdeviation { $dp = sprintf "%.2f", (100 - (100 * $pvre / $pvfc)); # V 1.23.0 } else { - $dp = sprintf "%.2f", (100 - (100 * $pvre / $pvfc)); # V 1.24.0 + my $pvfcd = ReadingsNum ($name, 'RestOfDayPVforecast', 0) - $pvfc; # PV Prognose bis jetzt + $dp = sprintf "%.2f", (100 - (100 * $pvre / abs $pvfcd)); # V 1.25.0 } $data{$type}{$name}{circular}{99}{tdayDvtn} = $dp; @@ -11088,7 +11091,7 @@ sub genStatisticReadings { if ($kpi eq 'dayAfterTomorrowPVforecast') { # PV Vorhersage Summe für Übermorgen (falls Werte vorhanden), Forum:#134226 my $dayaftertomorrow = strftime "%Y-%m-%d", localtime($t + 172800); # Datum von Übermorgen - my @allstrings = split ",", ReadingsVal ($name, 'inverterStrings', ''); + my @allstrings = split ",", AttrVal ($name, 'setupInverterStrings', ''); my $fcsumdat = 0; my $type = $paref->{type}; @@ -11509,17 +11512,22 @@ sub _checkSetupNotComplete { if ($val2) { CommandAttr (undef, "$name setupInverterDev $val2"); } + + my $val3 = ReadingsVal ($name, 'inverterStrings', ''); # 05.06.2024 + if ($val3) { + CommandAttr (undef, "$name setupInverterStrings $val3"); + } ########################################################################################## - my $is = ReadingsVal ($name, 'inverterStrings', undef); # String Konfig - my $wedev = AttrVal ($name, 'ctrlWeatherDev1', undef); # Device Vorhersage Wetterdaten (Bewölkung etc.) - my $radev = ReadingsVal ($name, 'currentRadiationAPI', undef); # Device Strahlungsdaten Vorhersage - my $indev = AttrVal ($name, 'setupInverterDev', undef); # Inverter 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) - my $mdec = ReadingsVal ($name, 'moduleDeclination', undef); # Modul Neigungswinkel Konfig - my $mrt = ReadingsVal ($name, 'moduleRoofTops', undef); # RoofTop Konfiguration (SolCast API) + my $is = AttrVal ($name, 'setupInverterStrings', undef); # String Konfig + my $wedev = AttrVal ($name, 'ctrlWeatherDev1', undef); # Device Vorhersage Wetterdaten (Bewölkung etc.) + my $radev = ReadingsVal ($name, 'currentRadiationAPI', undef); # Device Strahlungsdaten Vorhersage + my $indev = AttrVal ($name, 'setupInverterDev', undef); # Inverter 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) + my $mdec = ReadingsVal ($name, 'moduleDeclination', undef); # Modul Neigungswinkel Konfig + my $mrt = ReadingsVal ($name, 'moduleRoofTops', undef); # RoofTop Konfiguration (SolCast API) my $vrmcr = SolCastAPIVal ($hash, '?VRM', '?API', 'credentials', ''); # Victron VRM Credentials gesetzt @@ -18051,7 +18059,7 @@ return $def; # Usage: # SolCastAPIVal ($hash, $tring, $ststr, $key, $def) # -# $tring: Stringname aus "inverterStrings" (?All für allg. Werte) +# $tring: Stringname aus "setupInverterStrings" (?All für allg. Werte) # $ststr: Startzeit der Form YYYY-MM-DD hh:00:00 # $key: pv_estimate50 - PV Schätzung in Wh # Rad1h - vorhergesagte Globalstrahlung (Model DWD) @@ -18171,7 +18179,7 @@ to ensure that the system configuration is correct. setupInverterDev Device which provides PV performance data setupMeterDev Device which supplies network I/O data setupBatteryDev Device which provides battery performance data (if available) - inverterStrings Identifier of the existing plant strings + setupInverterStrings Identifier of the existing plant strings moduleAzimuth Azimuth of the plant strings modulePeakString the DC peak power of the plant strings roofIdentPair the identification data (when using the SolCast API) @@ -18352,7 +18360,7 @@ to ensure that the system configuration is correct. API usage requires one or more API-keys (accounts) and one or more Rooftop-ID's in advance created on the SolCast website. - A rooftop is equivalent to one inverterString + A rooftop is equivalent to one setupInverterStrings in the SolarForecast context.
Free API usage is limited to one daily rate API requests. The number of defined strings (rooftops) increases the number of API requests required. The module optimizes the query cycles with the attribute @@ -18371,7 +18379,7 @@ to ensure that the system configuration is correct. This API can be applied by users of the Victron Energy VRM Portal. This API is AI based. As string the value "AI-based" has to be entered in the setup of the - inverterStrings.
+ setupInverterStrings.
In the Victron Energy VRM Portal, the location of the PV system must be specified as a prerequisite.
See also the blog post Introducing Solar Production Forecast. @@ -18426,31 +18434,13 @@ to ensure that the system configuration is correct.
- -
-