From 88134e831f85de1d4aeec0e4b24f75777ac6fc37 Mon Sep 17 00:00:00 2001 From: DS_Starter Date: Sat, 3 May 2025 13:05:12 +0000 Subject: [PATCH] 76_SolarForecast: Version 1.51.9 git-svn-id: https://svn.fhem.de/fhem/trunk@29920 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/contrib/DS_Starter/76_SolarForecast.pm | 225 ++++++++++++-------- 1 file changed, 134 insertions(+), 91 deletions(-) diff --git a/fhem/contrib/DS_Starter/76_SolarForecast.pm b/fhem/contrib/DS_Starter/76_SolarForecast.pm index 8ef29246c..a6c82cf6a 100644 --- a/fhem/contrib/DS_Starter/76_SolarForecast.pm +++ b/fhem/contrib/DS_Starter/76_SolarForecast.pm @@ -160,7 +160,9 @@ BEGIN { # Versions History intern my %vNotesIntern = ( - "1.51.8" => "02.05.2025 _specialActivities: delete overhanging days at the change of month ". + "1.51.9" => "03.05.2025 An inverter string must not be named 'none', setupInverterDevXX: 'strings=none' is added ". + "valInverter: add isource ", + "1.51.8" => "02.05.2025 _specialActivities: delete overhanging days at the change of month ". "Bugfix: https://forum.fhem.de/index.php?msg=1340666 ", "1.51.7" => "01.05.2025 __createAdditionalEvents: optimized for SVG 'steps', new key plantControl->genPVforecastsToEvent ". "aiAddRawData: add gcons, _listDataPoolCircular: add gcons_a ", @@ -469,7 +471,7 @@ use constant { LAGTIME => 1800, # Nachlaufzeit relativ zu Sunset bis Sperrung API Abruf PRDEF => 1.0, # default Performance Ratio (PR) - STOREFFDEF => 0.9, # default Batterie Effizienz (https://www.energie-experten.org/erneuerbare-energien/photovoltaik/stromspeicher/wirkungsgrad) + STOREFFDEF => 0.90, # default Batterie Effizienz (https://www.energie-experten.org/erneuerbare-energien/photovoltaik/stromspeicher/wirkungsgrad) TEMPCOEFFDEF => -0.45, # default Temperaturkoeffizient Pmpp (%/°C) lt. Datenblatt Solarzelle TEMPMODINC => 25, # default Temperaturerhöhung an Solarzellen gegenüber Umgebungstemperatur bei wolkenlosem Himmel TEMPBASEDEF => 25, # Temperatur Module bei Nominalleistung @@ -6967,11 +6969,23 @@ sub _attrInverterDev { ## no critic "not used" } if ($h->{strings}) { - for my $s (split ',', $h->{strings}) { + my $none = 0; + my @as = split ',', $h->{strings}; + + for my $s (@as) { + if ($s eq 'none') { + $none = 1; + next; + } + if (!grep /^$s$/, keys %{$data{$name}{strings}}) { return qq{The string '$s' is not a valid string name defined in attribute 'setupInverterStrings'.}; } } + + if ($none && scalar(@as) > 1) { + return qq{If 'strings=none' is defined, no other string may be specified.}; + } } $data{$name}{circular}{99}{attrInvChangedTs} = int time; @@ -6982,6 +6996,7 @@ sub _attrInverterDev { ## no critic "not used" delete $data{$name}{inverters}{$in}{istrings}; delete $data{$name}{inverters}{$in}{iasynchron}; delete $data{$name}{inverters}{$in}{ifeed}; + delete $data{$name}{inverters}{$in}{isource}; } elsif ($paref->{cmd} eq 'del') { delete $data{$name}{inverters}{$in}; @@ -7016,10 +7031,14 @@ sub _attrInverterStrings { ## no critic "not used" 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!}; + return qq{The inverter string designation is wrong. An inverter string name must not contain a '?' character}; } my @istrings = split ",", $aVal; + + for my $s (@istrings) { + return qq{An inverter string must not be named 'none'} if($s eq 'none'); + } for my $k (keys %{$data{$name}{solcastapi}}) { next if ($k =~ /\?/xs || grep /^$k$/, @istrings); @@ -9879,7 +9898,8 @@ sub _transferInverterValues { my $etuf = $etunit =~ /^kWh$/xi ? 1000 : 1; my $etotal = ReadingsNum ($indev, $edread, 0) * $etuf; # Erzeugung total (Wh) - my $histetot = HistoryVal ($hash, $day, sprintf("%02d",$nhour), 'etotali'.$in, 0); # etotal zu Beginn einer Stunde + my $histetot = HistoryVal ($hash, $day, sprintf("%02d",$nhour), 'etotali'.$in, 0); # etotal zu Beginn einer Stunde + my $source = defined $h->{strings} && $h->{strings} eq 'none' ? 'bat' : 'pv'; # Energie-Bezug PV oder aus Batterie my ($ethishour, $etotsvd); @@ -9926,9 +9946,10 @@ sub _transferInverterValues { $data{$name}{inverters}{$in}{invertercap} = $h->{capacity} if(defined $h->{capacity}); # optionale Angabe max. WR-Leistung $data{$name}{inverters}{$in}{ilimit} = $h->{limit} // 100; # Wirkleistungsbegrenzung $data{$name}{inverters}{$in}{iicon} = $h->{icon} if($h->{icon}); # Icon des Inverters - $data{$name}{inverters}{$in}{istrings} = $h->{strings} if($h->{strings}); # dem Inverter zugeordnete Strings + $data{$name}{inverters}{$in}{istrings} = $h->{strings} if($h->{strings}); # dem Inverter zugeordnete Strings | none $data{$name}{inverters}{$in}{iasynchron} = $h->{asynchron} if($h->{asynchron}); # Inverter Mode $data{$name}{inverters}{$in}{ifeed} = $feed; # Eigenschaften der Energielieferung + $data{$name}{inverters}{$in}{isource} = $source; # Eigenschaften des Energiebezugs, normal pv $pvsum += $pv; $ethishoursum += $ethishour; @@ -17044,12 +17065,13 @@ sub _flowGraphic { my $pv2node = 0; # Summe PV-Erzeugung alle Inverter my $pv2grid = 0; my $pv2bat = 0; + my $bat2inv = 0; # Sonderfall Speisung Inverter aus Batterie statt PV my $lfn = 0; for my $pn (1..MAXPRODUCER) { $pn = sprintf "%02d", $pn; - my $p = ProducerVal ($hash, $pn, 'pgeneration', undef); - my $feed = ProducerVal ($hash, $pn, 'pfeed', 'default'); + my $p = ProducerVal ($name, $pn, 'pgeneration', undef); + my $feed = ProducerVal ($name, $pn, 'pfeed', 'default'); if (defined $p) { $p = __normDecPlaces ($p); @@ -17064,19 +17086,22 @@ sub _flowGraphic { } for my $in (1..MAXINVERTER) { - $in = sprintf "%02d", $in; - my $p = InverterVal ($hash, $in, 'igeneration', undef); - my $feed = InverterVal ($hash, $in, 'ifeed', 'default'); + $in = sprintf "%02d", $in; + my $p = InverterVal ($name, $in, 'igeneration', undef); + my $feed = InverterVal ($name, $in, 'ifeed', 'default'); + my $isource = InverterVal ($name, $in, 'isource', 'pv'); if (defined $p) { - $p = __normDecPlaces ($p); - $pdcr->{$lfn}{pn} = $in; # Inverternummer - $pdcr->{$lfn}{feed} = $feed; # Eigenschaft der Energielieferung - $pdcr->{$lfn}{ptyp} = 'inverter'; # Typ des Producers - $pdcr->{$lfn}{p} = $p; # aktuelle PV - $pv2node += $p if($feed eq 'default'); # PV-Erzeugung Inverter für das Hausnetz - $pv2grid += $p if($feed eq 'grid'); # PV nur für das öffentliche Netz - $pv2bat += $p if($feed eq 'bat'); # Direktladen PV nur in die Batterie + $p = __normDecPlaces ($p); + $pdcr->{$lfn}{pn} = $in; # Inverternummer + $pdcr->{$lfn}{feed} = $feed; # Eigenschaft der Energielieferung + $pdcr->{$lfn}{isource} = $isource; # Art der Energiequelle (pv oder bat) + $pdcr->{$lfn}{ptyp} = 'inverter'; # Typ des Producers + $pdcr->{$lfn}{p} = $p; # aktuelle PV + $pv2node += $p if($feed eq 'default'); # PV-Erzeugung Inverter für das Hausnetz + $pv2grid += $p if($feed eq 'grid'); # PV nur für das öffentliche Netz + $pv2bat += $p if($feed eq 'bat'); # Direktladen PV nur in die Batterie + $bat2inv += $p if($isource eq 'bat'); # Sonderfall Speisung Inverter aus Batterie statt PV $lfn++; } @@ -17113,9 +17138,9 @@ sub _flowGraphic { } } - ## Producer Koordninaten Steuerhash - ##################################### - my ($togrid, $tonode, $tobat) = __sortProducer ($pdcr); # lfn Producer sortiert nach ptyp und feed + ## Producer / Inverter Koordninaten Steuerhash + ################################################ + my ($togrid, $tonode, $tobat) = __sortProducer ($pdcr); # lfn Producer sortiert nach ptyp und feed my $psorted = { '1togrid' => { xicon => -100, xchain => 150, ychain => 400, step => 30, count => scalar @{$togrid}, sorted => $togrid }, # Producer/PV nur zu Grid @@ -17564,27 +17589,37 @@ return $ret; sub __sortProducer { my $pdcr = shift; # Hashref Producer - my @igrid = (); - my @togrid = (); - my @prod = (); - my @idef = (); - my @tonode = (); - my @ibat = (); - my @tobat = (); + my @igrid = (); + my @togrid = (); + my @prod = (); + my @idef = (); + my @isrcpv = (); + my @isrcbat = (); + my @tonode = (); + my @ibat = (); + my @tobat = (); for my $lfn (sort{$a<=>$b} keys %{$pdcr}) { my $ptyp = $pdcr->{$lfn}{ptyp}; # producer | inverter my $feed = $pdcr->{$lfn}{feed}; # default | grid | bat - push @igrid, $lfn if($ptyp eq 'inverter' && $feed eq 'grid'); + push @igrid, $lfn if($ptyp eq 'inverter' && $feed eq 'grid'); # Lieferung an öffentliches Netz push @prod, $lfn if($ptyp eq 'producer'); - push @idef, $lfn if($ptyp eq 'inverter' && $feed eq 'default'); - push @ibat, $lfn if($ptyp eq 'inverter' && $feed eq 'bat'); + push @idef, $lfn if($ptyp eq 'inverter' && $feed eq 'default'); # Lieferung an Inverterknoten + push @ibat, $lfn if($ptyp eq 'inverter' && $feed eq 'bat'); # Lieferung an Batterie + } + + for my $inv (@idef) { + my $isource = $pdcr->{$inv}{isource}; + + push @isrcpv, $inv if($isource eq 'pv'); # Quelle ist PV-String + push @isrcbat, $inv if($isource eq 'bat'); # Quelle ist Batterie } push @togrid, @igrid; push @tonode, @prod; - push @tonode, @idef; + push @tonode, @isrcpv; + push @tonode, @isrcbat; push @tobat, @ibat; return (\@togrid, \@tonode, \@tobat); @@ -24867,6 +24902,7 @@ to ensure that the system configuration is correct. ialias Alias of the device iname Name of the device invertercap the nominal power (W) of the inverter (if defined) + isource Type of energy source of the inverter istrings List of strings assigned to the inverter (if defined) @@ -25974,35 +26010,38 @@ to ensure that the system configuration is correct.
@@ -27401,6 +27440,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. ialias Alias des Gerätes iname Name des Gerätes invertercap die nominale Leistung (W) des Wechselrichters (falls definiert) + isource Art der Energiequelle des Inverters istrings Liste der dem Wechselrichter zugeordneten Strings (falls definiert) @@ -28508,35 +28548,38 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.