diff --git a/fhem/contrib/DS_Starter/76_SolarForecast.pm b/fhem/contrib/DS_Starter/76_SolarForecast.pm index 004b1f41c..9252db308 100644 --- a/fhem/contrib/DS_Starter/76_SolarForecast.pm +++ b/fhem/contrib/DS_Starter/76_SolarForecast.pm @@ -160,8 +160,10 @@ BEGIN { # Versions History intern my %vNotesIntern = ( - "1.57.3" => "24.08.2025 set default Performance Ratio PRDEF to 0.9, prevent crash when Victron API does not return an Array ". - "check global attribute dnsServer in all SF Models, expand plantControl->genPVdeviation for perspective change ", + "1.57.3" => "25.08.2025 set default Performance Ratio PRDEF to 0.9, prevent crash when Victron API does not return an Array ". + "check global attribute dnsServer in all SF Models, expand plantControl->genPVdeviation for perspective change ". + "Household consumption calculation uniformly converted to vector calculation ". + "new ctrlSpecialReadings->dummyConsumption ", "1.57.2" => "15.08.2025 _attrconsumer: The validity of the components of the key etotal is checked ". "_transferMeterValues: modul accept meter reset > 0 at day start ", "1.57.1" => "10.08.2025 fix warning, Forum: https://forum.fhem.de/index.php?msg=1346055 ", @@ -196,7 +198,7 @@ my %vNotesIntern = ( "1.53.0" => "28.06.2025 new battery style (batcontainer), new key setupBatteryDevXX->label, new reading Battery_ChargeUnrestricted_XX ". "attribute graphicShowDiff replaced by graphicControl->showDiff ". "check local coordinates are set in global device and fill message system if failure ". - "consumer Attr key noshow new possible value '9', _beamGraphic: scaleMode log double reduce Discount of z3 ". + "consumer Attr key noshow new possible value '9', _beamGraphic: scaleMode log double reduce Discount of z3 ". "new key plantControl->reductionState, _calcDataEveryFullHour and subs: changeover aln to pvrlvd ". "_getaiDecTree: reduce character size of aiRawData, set ... reset: pvCorrection deletes hidden readings too ", "1.52.18"=> "23.06.2025 ctrlSpecialReadings: new option conForecastComingNight, fix last hour of remainingSurplsHrsMinPwrBat_ ". @@ -972,10 +974,10 @@ my %hqtxt = ( # H DE => qq{produziert wie vorhergesagt 😊} }, pltp => { EN => qq{produced less than predicted 😓}, DE => qq{weniger produziert als vorhergesagt 😓} }, - wusond => { EN => qq{wait until sunset}, - DE => qq{bis zum Sonnenuntergang warten} }, - snbefb => { EN => qq{Should not be empty. Maybe the device has just been redefined.}, - DE => qq{Sollte nicht leer sein. Vielleicht wurde das Device erst neu definiert.} }, + wusond => { EN => qq{waiting for data ...}, + DE => qq{warte auf Daten ...} }, + snbefb => { EN => qq{the data will be available tomorrow}, + DE => qq{die Daten werden morgen verfügbar sein} }, scnp => { EN => qq{Scheduling of the consumer is not provided}, DE => qq{Die Einplanung des Verbrauchers ist nicht vorgesehen} }, vrmcr => { EN => qq{Please set the Victron VRM Portal credentials with "set LINK vrmCredentials".}, @@ -1133,8 +1135,8 @@ my %htitles = ( DE => qq{nicht bewertet} }, aimstt => { EN => qq{Perl module AI::DecisionTree is missing}, DE => qq{Perl Modul AI::DecisionTree ist nicht vorhanden} }, - dumtxt => { EN => qq{Consumption that cannot be allocated to registered consumers}, - DE => qq{Verbrauch der den registrierten Verbrauchern nicht zugeordnet werden kann} }, + dumtxt => { EN => qq{unassignable consumption (takes into account any hidden consumers)}, + DE => qq{nicht zuordenbarer Verbrauch (berücksichtigt evtl. versteckte Verbraucher)} }, rdcactiv => { EN => qq{Plant derating active}, DE => qq{Anlagenabregelung aktiv} }, rdcnoact => { EN => qq{no Plant derating}, @@ -1383,6 +1385,7 @@ my %hcsr = ( runTimeLastAPIAnswer => { fnr => 2, fn => \&CurrentVal, par => '', par1 => '', unit => '', def => '-' }, runTimeLastAPIProc => { fnr => 2, fn => \&CurrentVal, par => '', par1 => '', unit => '', def => '-' }, allStringsFullfilled => { fnr => 2, fn => \&CurrentVal, par => '', par1 => '', unit => '', def => 0 }, + dummyConsumption => { fnr => 2, fn => \&CurrentVal, par => 'dummyConsumption', par1 => '', unit => ' W', def => 0 }, todayConForecastTillSunset => { fnr => 2, fn => \&CurrentVal, par => 'tdConFcTillSunset', par1 => '', unit => ' Wh', def => 0 }, runTimeTrainAI => { fnr => 3, fn => \&CircularVal, par => 99, par1 => '', unit => ' s', def => '-' }, todayConsumption => { fnr => 3, fn => \&CircularVal, par => 99, par1 => '', unit => ' Wh', def => 0 }, @@ -1391,8 +1394,8 @@ my %hcsr = ( BatPowerOut_Sum => { fnr => 5, fn => \&CurrentVal, par => 'batpoweroutsum', par1 => '', unit => ' W', def => '-' }, BatWeightedTotalSOC => { fnr => 2, fn => \&CurrentVal, par => 'batsoctotal', par1 => '', unit => ' %', def => 0 }, SunHours_Remain => { fnr => 5, fn => \&CurrentVal, par => '', par1 => '', unit => '', def => 0 }, # fnr => 3 -> Custom Calc - SunMinutes_Remain => { fnr => 5, fn => \&CurrentVal, par => '', par1 => '', unit => '', def => 0 }, - dayAfterTomorrowPVforecast => { fnr => 5, fn => \&CurrentVal, par => 'dayAfterTomorrowPVfc', par1 => '', unit => ' Wh', def => 0 }, + SunMinutes_Remain => { fnr => 5, fn => \&CurrentVal, par => '', par1 => '', unit => '', def => 0 }, + dayAfterTomorrowPVforecast => { fnr => 5, fn => \&CurrentVal, par => 'dayAfterTomorrowPVfc', par1 => '', unit => ' Wh', def => 0 }, todayGridFeedIn => { fnr => 5, fn => \&CircularVal, par => 99, par1 => '', unit => '', def => 0 }, todayGridConsumption => { fnr => 5, fn => \&CircularVal, par => 99, par1 => '', unit => '', def => 0 }, todayNotOwnerConsumption => { fnr => 5, fn => \&CircularVal, par => 99, par1 => 'todayConsumption', unit => ' Wh', def => 0 }, @@ -6779,7 +6782,7 @@ sub _attrplantControl { ## no critic "not used" feedinPowerLimit => { comp => '\d+', act => 0 }, genPVdeviation => { comp => '^(?:daily|continuously)(?::(?:default|reverse))?$', act => 1 }, genPVforecastsToEvent => { comp => '(adapt4(?:f)?Steps)', act => 0 }, - reductionState => { comp => '[^\s]+:[^\s]+:[^\s]+', act => 1 }, + reductionState => { comp => '[^\s]+:[^\s]+:[^\s]+', act => 1 }, showLink => { comp => '(0|1)', act => 0 }, }; @@ -7717,21 +7720,21 @@ sub __attrKeyAction { } } } - + if ($init_done && $akey eq 'reductionState') { - my $rdcinfo = CurrentVal ($name, 'reductionState', ''); - my ($rdcdev, $rdcrd, $code) = split ":", $rdcinfo; + my $rdcinfo = CurrentVal ($name, 'reductionState', ''); + my ($rdcdev, $rdcrd, $code) = split ":", $rdcinfo; - ($err) = isDeviceValid ( { name => $name, - obj => $rdcdev, - method => 'string', - } - ); + ($err) = isDeviceValid ( { name => $name, + obj => $rdcdev, + method => 'string', + } + ); - if ($err) { - delete $data{$name}{current}{$akey}; - return $err; - } + if ($err) { + delete $data{$name}{current}{$akey}; + return $err; + } if ($code =~ m/^\s*\{.*\}\s*$/xs) { # prüft Perl-Code $code =~ s/\s//xg; @@ -7740,12 +7743,12 @@ sub __attrKeyAction { else { # prüft Regex $err = checkRegex ($code); } - - if ($err) { - delete $data{$name}{current}{$akey}; - return $err; - } - } + + if ($err) { + delete $data{$name}{current}{$akey}; + return $err; + } + } } if ($akey eq 'lcSlot') { @@ -9378,10 +9381,10 @@ sub _specialActivities { my ($fd, $fh) = calcDayHourMove ($chour, $num); my $nhtstr = 'NextHour'.(sprintf "%02d", $num); - if ($fd > 2 && exists $data{$name}{nexthours}{$nhtstr}) { - delete $data{$name}{nexthours}{$nhtstr}; - next; - } + if ($fd > 2 && exists $data{$name}{nexthours}{$nhtstr}) { + delete $data{$name}{nexthours}{$nhtstr}; + next; + } } ## Planungsdaten spezifisch löschen (Anfang und Ende nicht am selben Tag) @@ -10321,8 +10324,8 @@ sub _transferAPIRadiationValues { my ($wtday, $wthour) = $wantdt =~ /(\d{2})\s(\d{2}):/xs; my $hod = sprintf "%02d", int $wthour + 1; # Stunde des Tages my $rad1h = RadiationAPIVal ($name, '?All', $wantdt, 'Rad1h', undef); - - $paref->{wantdt} = $wantdt; + + $paref->{wantdt} = $wantdt; $paref->{wantts} = $wantts; $paref->{wtday} = $wtday; $paref->{hod} = $hod; @@ -12015,24 +12018,37 @@ sub _createSummaries { $node2inv2dc += $pac2dc if($ifeed eq 'hybrid' || ($ifeed eq 'default' && $isource eq 'bat')); # AC->DC (Batterie- oder Hybrid-Wechselrichter) } - my $othprod = 0; # Summe Otherproducer + my $ppall = 0; # Summe Otherproducer for my $pn (1..MAXPRODUCER) { # Erzeugung sonstiger Producer hinzufügen - $pn = sprintf "%02d", $pn; - $othprod += ProducerVal ($name, $pn, 'pgeneration', 0); - } - - my $consumption = sprintf "%.0f", ($pv2node + $pv2bat + $othprod - $gfeedin + $gcon - $batin + $batout); # ohne PV2Grid + $pn = sprintf "%02d", $pn; + $ppall += ProducerVal ($name, $pn, 'pgeneration', 0); + } + + my $vector = __calcVectorConsumption ( { name => $name, + batout => $batout, + batin => $batin, + pv2bat => $pv2bat, + pv2node => $pv2node, + dc2inv2node => $dc2inv2node, + node2inv2dc => $node2inv2dc, + ppall => $ppall, + gfeedin => $gfeedin, + gcon => $gcon + } + ); + + my $consumption = $vector->{vectorconsumption}; my $selfconsumption = sprintf "%.0f", ($pv2node + $pv2bat - $gfeedin - $batin); $selfconsumption = $selfconsumption < 0 ? 0 : $selfconsumption; - my $surplus = sprintf "%.0f", ($pv2node - $pv2grid + $othprod - $consumption); # aktueller Überschuß + my $surplus = sprintf "%.0f", ($pv2node - $pv2grid + $ppall - $consumption); # aktueller Überschuß $surplus = 0 if($surplus < 0); # wegen Vergleich nompower vs. surplus if ($debug =~ /collectData/xs) { - Log3 ($name, 1, "$name DEBUG> current Power values -> PV2Node: $pv2node W, PV2Bat: $pv2bat, PV2Grid: $pv2grid W, Other: $othprod W, GridIn: $gfeedin W, GridCon: $gcon W"); + Log3 ($name, 1, "$name DEBUG> current Power values -> PV2Node: $pv2node W, PV2Bat: $pv2bat, PV2Grid: $pv2grid W, Other: $ppall W, GridIn: $gfeedin W, GridCon: $gcon W"); Log3 ($name, 1, "$name DEBUG> current Power Battery -> BatIn: $batin W (Node2Inv2DC: $node2inv2dc W), BatOut: $batout W (DC2Inv2Node: $dc2inv2node W)"); - Log3 ($name, 1, "$name DEBUG> current Consumption result -> $consumption W"); + Log3 ($name, 1, "$name DEBUG> current Consumption result -> $consumption W"); } my $selfconsumptionrate = 0; @@ -12052,8 +12068,8 @@ sub _createSummaries { push @{$data{$name}{current}{surplusslidereg}}, $surplus; # Schieberegister PV Überschuß limitArray ($data{$name}{current}{surplusslidereg}, SPLSLIDEMAX); - storeReading ('Current_GridFeedIn', (int $gfeedin). ' W'); # V 1.37.0 - storeReading ('Current_GridConsumption', (int $gcon). ' W'); # V 1.37.0 + storeReading ('Current_GridFeedIn', (sprintf "%.0f", $gfeedin). ' W'); + storeReading ('Current_GridConsumption', (sprintf "%.0f", $gcon). ' W'); storeReading ('Current_Consumption', $consumption. ' W'); storeReading ('Current_SelfConsumption', $selfconsumption. ' W'); storeReading ('Current_SelfConsumptionRate', $selfconsumptionrate. ' %'); @@ -12062,19 +12078,81 @@ sub _createSummaries { storeReading ('Today_PVreal', $pvre. ' Wh'); storeReading ('Tomorrow_ConsumptionForecast', $tconsum. ' Wh') if(defined $tconsum); - storeReading ('NextHours_Sum01_PVforecast', (int $next1HoursSum->{PV}). ' Wh'); - storeReading ('NextHours_Sum02_PVforecast', (int $next2HoursSum->{PV}). ' Wh'); - storeReading ('NextHours_Sum03_PVforecast', (int $next3HoursSum->{PV}). ' Wh'); - storeReading ('NextHours_Sum04_PVforecast', (int $next4HoursSum->{PV}). ' Wh'); - storeReading ('RestOfDayPVforecast', (int $restOfDaySum->{PV}). ' Wh'); - storeReading ('Tomorrow_PVforecast', (int $tomorrowSum->{PV}). ' Wh'); - storeReading ('Today_PVforecast', (int $todaySumFc->{PV}). ' Wh'); - storeReading ('NextHours_Sum04_ConsumptionForecast', (int $next4HoursSum->{Consumption}).' Wh'); - storeReading ('RestOfDayConsumptionForecast', (int $restOfDaySum->{Consumption}). ' Wh'); + storeReading ('NextHours_Sum01_PVforecast', (sprintf "%.0f", $next1HoursSum->{PV}). ' Wh'); + storeReading ('NextHours_Sum02_PVforecast', (sprintf "%.0f", $next2HoursSum->{PV}). ' Wh'); + storeReading ('NextHours_Sum03_PVforecast', (sprintf "%.0f", $next3HoursSum->{PV}). ' Wh'); + storeReading ('NextHours_Sum04_PVforecast', (sprintf "%.0f", $next4HoursSum->{PV}). ' Wh'); + storeReading ('RestOfDayPVforecast', (sprintf "%.0f", $restOfDaySum->{PV}). ' Wh'); + storeReading ('Tomorrow_PVforecast', (sprintf "%.0f", $tomorrowSum->{PV}). ' Wh'); + storeReading ('Today_PVforecast', (sprintf "%.0f", $todaySumFc->{PV}). ' Wh'); + storeReading ('NextHours_Sum04_ConsumptionForecast', (sprintf "%.0f", $next4HoursSum->{Consumption}).' Wh'); + storeReading ('RestOfDayConsumptionForecast', (sprintf "%.0f", $restOfDaySum->{Consumption}). ' Wh'); return; } +################################################################ +# Hausverbrauch aus dem Leistungsfluß in/aus Knoten ermitteln +# (erfasst auch Verlustleistung in den Batteriewechselrichtern) +# und alternativ linear +# alles ohne PV2Grid +################################################################ +sub __calcVectorConsumption { + my $paref = shift; + my $name = $paref->{name}; + my $batout = $paref->{batout}; + my $batin = $paref->{batin}; + my $pv2bat = $paref->{pv2bat}; + my $pv2node = $paref->{pv2node}; + my $dc2inv2node = $paref->{dc2inv2node}; + my $node2inv2dc = $paref->{node2inv2dc}; + my $ppall = $paref->{ppall}; + my $gfeedin = $paref->{gfeedin}; + my $gcon = $paref->{gcon}; + + my $vector; + $vector->{batDischarge2HomeNode} = 0; + my $node2bat = 0; # Verbindung Inv.Knoten <-> Batterie ((-) Bat -> Knoten, (+) Knoten -> Bat) + my $bat2home = 0; + + ## Vectorverbrauch + #################### + if ($batout || $batin) { # Batterie wird geladen oder entladen + $node2bat = ($batin - $batout) - $pv2bat + $dc2inv2node - $node2inv2dc; # positiv: Richtung Inverter Knoten -> Bat, negativ: Richtung Bat -> Inverter Knoten + $node2bat = 0 if(($dc2inv2node || $node2inv2dc) && $node2bat != 0); + + if ($node2bat < 0 && !$dc2inv2node && !$pv2bat) { # Batterieentladung direkt ins Hausnetz wenn kein Batterie- / Hybridwechselrichter und kein Batterieladegerät aktiv + $bat2home = abs $node2bat; + $node2bat = 0; + $vector->{batDischarge2HomeNode} = 1; + } + } + else { + $node2bat = $dc2inv2node - $pv2bat; # falls Batterie Idle und Smartloader arbeitet + $node2bat = 0 if($dc2inv2node && $node2bat > 0); # muß negativ (0) sein: Richtung Bat -> Inv.Knoten, wichtig zur Festlegung Richtung und Inv. Knoten Summierung + } + + my $pnodesum = $ppall + $pv2node + $dc2inv2node - $node2inv2dc; # Erzeugung Summe im Inverter-Knoten + $pnodesum += $node2bat < 0 ? abs $node2bat : 0; # z.B. Batterie ist voll und SolarLader liefert an Knoten + $pnodesum = __normDecPlaces ($pnodesum); + + my $node2home = $pnodesum - $gfeedin - ($node2bat > 0 ? $node2bat : 0); # Energiefluß vom Knoten zum Haus + $node2home = __normDecPlaces ($node2home); + + $vector->{vectorconsumption} = sprintf "%.0f", ($gcon + $node2home + $bat2home); # V 1.52.0 Anpassung Consumption wegen Verlustleistungsdifferenzen + + ## Linearverbrauch + #################### + $vector->{linearconsumption} = sprintf "%.0f", ($pv2node + $pv2bat + $ppall - $gfeedin + $gcon - $batin + $batout); + + $vector->{bat2home} = $bat2home; + $vector->{pnodesum} = $pnodesum; + $vector->{node2home} = $node2home; + $vector->{node2bat} = $node2bat; + +return $vector; +} + ################################################################ # Consumer - Energieverbrauch aufnehmen # - Masterdata ergänzen @@ -12090,6 +12168,8 @@ sub _manageConsumerData { my $hash = $defs{$name}; my $nhour = $chour + 1; $paref->{nhour} = sprintf "%02d", $nhour; + + my $pcurrsum = 0; for my $c (sort{$a<=>$b} keys %{$data{$name}{consumers}}) { $paref->{consumer} = $c; @@ -12105,8 +12185,6 @@ sub _manageConsumerData { if ($paread) { my $eup = $up =~ /^kW$/xi ? 1000 : 1; $pcurr = ReadingsNum ($consumer, $paread, 0) * $eup; - - storeReading ("consumer${c}_currentPower", $pcurr.' W'); } ## Verbrauch auslesen + speichern @@ -12130,8 +12208,6 @@ sub _manageConsumerData { $data{$name}{consumers}{$c}{old_etotal} = $etot; $data{$name}{consumers}{$c}{old_etottime} = $t; - - storeReading ("consumer${c}_currentPower", $pcurr.' W'); } if (defined $ehist && $etot >= $ehist && ($etot - $ehist) >= $ethreshold) { @@ -12169,8 +12245,16 @@ sub _manageConsumerData { delete $paref->{val}; } - readingsDelete ($hash, "consumer${c}_currentPower") if(!$etotread && !$paread); - + if (!$etotread && !$paread) { + delete $data{$name}{consumers}{$c}{currpower}; + readingsDelete ($hash, "consumer${c}_currentPower"); + } + else { + $data{$name}{consumers}{$c}{currpower} = $pcurr; + storeReading ("consumer${c}_currentPower", $pcurr.' W'); + } + + $pcurrsum += $pcurr; $paref->{pcurr} = $pcurr; __getAutomaticState ($paref); # Automatic Status des Consumers abfragen @@ -12231,6 +12315,8 @@ sub _manageConsumerData { storeReading ("consumer${c}_planned_start", $starttime) if($starttime); # Consumer Start geplant storeReading ("consumer${c}_planned_stop", $stoptime) if($stoptime); # Consumer Stop geplant } + + $data{$name}{current}{dummyConsumption} = CurrentVal ($name, 'consumption', 0) - $pcurrsum; # aktueller Verbrauch - Summe aller ConsumerPower delete $paref->{consumer}; delete $paref->{nhour}; @@ -14752,8 +14838,8 @@ sub _genSpecialReadings { storeReading ($prpo.'_'.$kpi, (sprintf "%.1f", $dbo).' '.$hcsr{$kpi}{unit}); } - elsif ($kpi eq 'dayAfterTomorrowPVforecast') { # PV Vorhersage Summe für Übermorgen (falls Werte vorhanden), Forum:#134226 - my $datpvfc = &{$hcsr{$kpi}{fn}} ($name, 'dayAfterTomorrowPVfc', $def); + elsif ($kpi eq 'dayAfterTomorrowPVforecast') { # PV Vorhersage Summe für Übermorgen (falls Werte vorhanden), Forum:#134226 + my $datpvfc = &{$hcsr{$kpi}{fn}} ($name, 'dayAfterTomorrowPVfc', $def); if ($datpvfc) { storeReading ($prpo.'_'.$kpi, (sprintf "%.0f", $datpvfc).$hcsr{$kpi}{unit}); @@ -16649,8 +16735,8 @@ sub _graphicConsumerLegend { my $tro = 0; for my $c (@consumers) { - my $noshow = isConsumerNoshow ($hash, $c); - + my $noshow = isConsumerNoshow ($hash, $c); + next if($noshow =~ /[12]/xs); # Consumer ausblenden my $caicon = $paref->{caicon}; # Consumer AdviceIcon @@ -16752,28 +16838,28 @@ sub _graphicConsumerLegend { } if ($noshow !~ /[9]/xs) { # mit $noshow '9' die Schalter im Paneel ausblenden - if (isConsumerPhysOff($hash, $c)) { # Schaltzustand des Consumerdevices off - if ($cmdon) { - $staticon = FW_makeImage('ios_off_fill@red', $htitles{iave}{$lang}); - $swicon = " $staticon"; - } - else { - $staticon = FW_makeImage('ios_off_fill@grey', $htitles{ians}{$lang}); - $swicon = " $staticon"; - } - } + if (isConsumerPhysOff($hash, $c)) { # Schaltzustand des Consumerdevices off + if ($cmdon) { + $staticon = FW_makeImage('ios_off_fill@red', $htitles{iave}{$lang}); + $swicon = " $staticon"; + } + else { + $staticon = FW_makeImage('ios_off_fill@grey', $htitles{ians}{$lang}); + $swicon = " $staticon"; + } + } - if (isConsumerPhysOn($hash, $c)) { # Schaltzustand des Consumerdevices on - if($cmdoff) { - $staticon = FW_makeImage('ios_on_fill@green', $htitles{ieva}{$lang}); - $swicon = " $staticon"; - } - else { - $staticon = FW_makeImage('ios_on_fill@grey', $htitles{iens}{$lang}); - $swicon = " $staticon"; - } - } - } + if (isConsumerPhysOn($hash, $c)) { # Schaltzustand des Consumerdevices on + if($cmdoff) { + $staticon = FW_makeImage('ios_on_fill@green', $htitles{ieva}{$lang}); + $swicon = " $staticon"; + } + else { + $staticon = FW_makeImage('ios_on_fill@grey', $htitles{iens}{$lang}); + $swicon = " $staticon"; + } + } + } if ($clstyle eq 'icon') { $cicon = FW_makeImage($cicon); @@ -17151,8 +17237,8 @@ sub _beamGraphicRemainingHours { $hfcg->{$i}{beam1} //= 0; $hfcg->{$i}{beam2} //= 0; - my %roundable = map { $_ => 1 } qw(pvForecast pvReal consumptionForecast consumption); - my @beams = ($beam1cont, $beam2cont); + my %roundable = map { $_ => 1 } qw(pvForecast pvReal consumptionForecast consumption); + my @beams = ($beam1cont, $beam2cont); $hfcg->{$i}{diff} = sprintf "%.1f", ($hfcg->{$i}{beam1} - $hfcg->{$i}{beam2}); $hfcg->{$i}{diff} = sprintf "%.0f", $hfcg->{$i}{diff} if($kw eq 'Wh' && grep { $roundable{$_} } @beams); @@ -17353,8 +17439,8 @@ sub _beamGraphic { } ) + $spacesz * 10; - $z3 =__normBeamHeight ( { val => $hfcg->{$i}{beam1}, maxVal => $maxVal, height => $height, ground => 0, scalemode => $scm } ); - $titz3 = qq/title="$hfcg->{0}{beam1txt}"/; + $z3 =__normBeamHeight ( { val => $hfcg->{$i}{beam1}, maxVal => $maxVal, height => $height, ground => 0, scalemode => $scm } ); + $titz3 = qq/title="$hfcg->{0}{beam1txt}"/; } if ($lotype eq 'double') { @@ -17390,14 +17476,14 @@ sub _beamGraphic { $z1 = __normBeamHeight ( { val => $mbdf, maxVal => $maxVal, height => $height, ground => 0, scalemode => 'lin' } ); $z2 = __normBeamHeight ( { val => $z2, maxVal => $maxVal, height => $height, ground => 0, scalemode => $scm } ); $z3 = __normBeamHeight ( { val => $z3, maxVal => $maxVal, height => $height, ground => 0, scalemode => $scm } ); - $z2 -= $z3 if($scm eq 'lin'); # effektive Stapelhöhe, da $z2 + $z3 übereinander dargestellt wird + $z2 -= $z3 if($scm eq 'lin'); # effektive Stapelhöhe, da $z2 + $z3 übereinander dargestellt wird - if ($scm eq 'log' && $z2) { + if ($scm eq 'log' && $z2) { my $z3perc = int (100 / $z2 * $z3); - $z3 = int ($z3 / 100 * $z3perc); + $z3 = int ($z3 / 100 * $z3perc); $z3 -= $height * 0.1 if($z3); $z2 -= $z3; - } + } } if ($lotype eq 'diff') { @@ -17898,16 +17984,14 @@ sub _flowGraphic { my $exth2cdist = $paref->{flowgh2cDist}; # vertikaler Abstand Home -> Consumer Zeile my $lang = $paref->{lang}; - my $gconMetered = ReadingsNum ($name, 'Current_GridConsumption', 0); - my $node2gridMetered = ReadingsNum ($name, 'Current_GridFeedIn', 0); # vom Inverter-Knoten zum Grid - my $cselfMetered = ReadingsNum ($name, 'Current_SelfConsumption', 0); - my $consptn = CurrentVal ($name, 'consumption', 0); - my $showgenerators = CurrentVal ($name, 'showGenerators', 0); # Generatoren-Zeile anzeigen + my $gconMetered = CurrentVal ($name, 'gridconsumption', 0); + my $node2gridMetered = CurrentVal ($name, 'gridfeedin', 0); # vom Inverter-Knoten zum Grid + my $cselfMetered = CurrentVal ($name, 'selfconsumption', 0); + my $showgenerators = CurrentVal ($name, 'showGenerators', 0); # Generatoren-Zeile anzeigen - my $cons_dmy = $consptn; - my $scale = FGSCALEDEF; - my $stna = $name; - $stna .= int (rand (1500)); + my $scale = FGSCALEDEF; + my $stna = $name; + $stna .= int (rand (1500)); my ($y_pos, $y_pos1, $y_pos2, $err); @@ -17925,8 +18009,8 @@ sub _flowGraphic { ($err) = isDeviceValid ( { name => $name, obj => 'setupBatteryDev'.$bn, method => 'attr' } ); next if($err); - my $batinpow = ReadingsNum ($name, 'Current_PowerBatIn_'.$bn, undef); - my $batoutpow = ReadingsNum ($name, 'Current_PowerBatOut_'.$bn, undef); + my $batinpow = BatteryVal ($name, $bn, 'bpowerin', undef); + my $batoutpow = BatteryVal ($name, $bn, 'bpowerout', undef); $batin += $batinpow if(defined $batinpow); $batout += $batoutpow if(defined $batoutpow); } @@ -18027,56 +18111,59 @@ sub _flowGraphic { my $gconMetered_direction = "M250,515 L670,590"; my $bat2home_direction = "M1200,515 L730,590"; - my $node2bat = 0; # Verbindung Inv.Knoten <-> Batterie ((-) Bat -> Knoten, (+) Knoten -> Bat) - my $bat2home = 0; - - if ($batout || $batin) { # Batterie wird geladen oder entladen - $node2bat = ($batin - $batout) - $pv2bat + $dc2inv2node - $node2inv2dc; # positiv: Richtung Knoten -> Bat, negativ: Richtung Bat -> Inv.Knoten - $node2bat = 0 if(($dc2inv2node || $node2inv2dc) && $node2bat != 0); + ## Knotensummen Erzeuger - Batterie - Home ermitteln -> Hausverbrauch ermitteln + ################################################################################# + my $vector = __calcVectorConsumption ( { name => $name, + batout => $batout, + batin => $batin, + pv2bat => $pv2bat, + pv2node => $pv2node, + dc2inv2node => $dc2inv2node, + node2inv2dc => $node2inv2dc, + ppall => $ppall, + gfeedin => $node2gridMetered, + gcon => $gconMetered + } + ); - if ($node2bat < 0 && !$dc2inv2node && !$pv2bat) { # Batterieentladung direkt ins Hausnetz wenn kein Batterie- / Hybridwechselrichter und kein Batterieladegerät aktiv - $bat2home = abs $node2bat; - $node2bat = 0; - $bat2home_style = "$stna active_normal"; - $bat2home_direction = "M1200,515 L730,590"; - } - } - else { - $node2bat = $dc2inv2node - $pv2bat; # falls Batterie Idle und Smartloader arbeitet - $node2bat = 0 if($dc2inv2node && $node2bat > 0); # muß negativ (0) sein: Richtung Bat -> Inv.Knoten, wichtig zur Festlegung Richtung und Inv. Knoten Summierung + my $consptn = $vector->{vectorconsumption}; + + if ($vector->{batDischarge2HomeNode}) { + $bat2home_style = "$stna active_normal"; + $bat2home_direction = "M1200,515 L730,590"; } - ## Knotensummen Erzeuger - Batterie - Home ermitteln - ###################################################### - my $pnodesum = $ppall + $pv2node + $dc2inv2node - $node2inv2dc; # Erzeugung Summe im Inverter-Knoten - $pnodesum += $node2bat < 0 ? abs $node2bat : 0; # z.B. Batterie ist voll und SolarLader liefert an Knoten - $pnodesum = __normDecPlaces ($pnodesum); + my $bat2home = $vector->{bat2home}; # Batterie -> Hausknoten + my $pnodesum = $vector->{pnodesum}; # Summe Inverterknoten + my $node2home = $vector->{node2home}; # Inverterknoten -> Haus + my $node2bat = $vector->{node2bat}; # Inverterknoten -> Batterie - my $node2home = $pnodesum - $node2gridMetered - ($node2bat > 0 ? $node2bat : 0); # Energiefluß vom Knoten zum Haus - $node2home = __normDecPlaces ($node2home); - - $consptn = $gconMetered + $node2home + $bat2home; # V 1.52.0 Anpassung Consumption wegen Verlustleistungsdifferenzen ## definierte Verbraucher ermitteln ##################################### - my $cnsmr = {}; # Consumer Hilfshash Referenz - - for my $c (sort{$a<=>$b} keys %{$data{$name}{consumers}}) { # definierte Verbraucher ermitteln - next if(isConsumerNoshow ($hash, $c) =~ /[13]/xs); # auszublendende Consumer nicht berücksichtigen - $cnsmr->{$c}{p} = ReadingsNum ($name, "consumer${c}_currentPower", 0); - $cnsmr->{$c}{shortalias} = ConsumerVal ($name, $c, 'aliasshort', ''); # Consumer Kurzalias + my $cnsmr = {}; # Consumer Hilfshash Referenz + my $concurpsum = 0; # Summierung aller Consumerverbräuche + + for my $c (sort{$a<=>$b} keys %{$data{$name}{consumers}}) { # definierte Verbraucher ermitteln + next if(isConsumerNoshow ($hash, $c) =~ /[13]/xs); # auszublendende Consumer nicht berücksichtigen + $cnsmr->{$c}{p} = ConsumerVal ($name, $c, 'currpower', 0); + $cnsmr->{$c}{shortalias} = ConsumerVal ($name, $c, 'aliasshort', ''); # Consumer Kurzalias $cnsmr->{$c}{ptyp} = 'consumer'; + $concurpsum += $cnsmr->{$c}{p}; } my $consumercount = keys %{$cnsmr}; - $flowgconsumer = 0 if(!$consumercount); # Consumer Anzeige ausschalten wenn keine Consumer definiert + $flowgconsumer = 0 if(!$consumercount); # Consumer Anzeige ausschalten wenn keine Consumer definiert my @consumers = sort{$a<=>$b} keys %{$cnsmr}; my $total_shortalias_length = sum map { my $a = $_->{shortalias} // ''; strlength ($a); # Länge in Zeichen nach Zeichen-Dekodierung } values %{$cnsmr}; - + + ## Verbrauch Dummy bestimmen + ############################## + my $cons_dmy = $consptn - $concurpsum; ## Producer / Inverter Koordinaten Steuerhash ############################################### @@ -18222,7 +18309,6 @@ END0 } ); - $cons_dmy -= $cnsmrpower; $cicon = FW_makeImage ($cicon, ''); ($scale, $cicon) = __normIconScale ($name, $cicon); @@ -18252,7 +18338,7 @@ END1 ## Home Icon ############## - my $car = CurrentVal ($name, 'autarkyrate', undef); + my $car = CurrentVal ($name, 'autarkyrate', undef); my $hmtxt = ''; $hmtxt = $htitles{autarky}{$lang}.': '.$car.' %' if(defined $car); my $hicon = HOMEICONDEF; @@ -18447,7 +18533,7 @@ END3 ## Textangaben an Grafikelementen ################################### - $cons_dmy = sprintf "%.0f", $cons_dmy; # Verbrauch Dummy-Consumer + $cons_dmy = sprintf "%.0f", $cons_dmy; # Verbrauch Dummy-Consumer $bat2home = __normDecPlaces ($bat2home); $dc2inv2node = __normDecPlaces ($dc2inv2node); $node2bat = __normDecPlaces ($node2bat); @@ -19484,10 +19570,10 @@ sub __readFileMessages { } close $fh; - + Log3 ($name, 4, "$name - Notification System - read local Message File >$messagefile< with $count entries."); - - + + $data{$name}{filemessages}{999000}{TS} = time; $data{$name}{filemessages}{999000}{TSNEXT} = $tsnext; @@ -26550,7 +26636,7 @@ to ensure that the system configuration is correct. 2 - the consumer is hidden in the consumer legend 3 - the consumer is hidden in the flow chart 9 - the switching element of the consumer is hidden in the consumer legend - [Device:]Reading - Reading in the consumer or (optionally) an alternative device. + [Device:]Reading - Reading in the consumer or (optionally) an alternative device. If the reading has the value 0 or is not present, the consumer is displayed. The effect of the possible reading values 1, 2 and 3 is as described. @@ -26733,49 +26819,50 @@ to ensure that the system configuration is correct.
@@ -26949,11 +27036,11 @@ to ensure that the system configuration is correct. - + - + @@ -26995,18 +27082,18 @@ to ensure that the system configuration is correct. - + - + - + @@ -27257,7 +27344,7 @@ to ensure that the system configuration is correct. - + @@ -29207,7 +29294,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. - + @@ -29400,7 +29487,8 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. - + + @@ -29607,11 +29695,11 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden.
beamHeightlevel The bar height for each level of the bar chart can be specified.
The specification for a layer consists of the layer number (1..X), a ‘:’ followed by a positive integer > 0.
The specification for a layer consists of the layer number (1..X), a ‘:’ followed by a positive integer > 0.
The numerical value is used as a normalization factor in the height calculation.
Further levels are specified separated by commas (see example).
<Level>:<Integer> - normalization factor (default: 200)
beamPaddingBottom Defines the space in px in the bar chart that is inserted between the last text or icon row of the respective bar chart layer
and the bottom edge of this layer.
The value applies uniformly to all bar chart levels.
scaleMode The scaling mode can be set to linear or logarithmic for each level of the bar chart.
The logarithmic setting emphasizes small values and compresses larger values in the display.
The specification for a level consists of the level number (1..X), a ':' followed by the mode 'lin' or 'log'.
The specification for a level consists of the level number (1..X), a ':' followed by the mode 'lin' or 'log'.
The strings for each level are separated by commas (see example).
<Level>:lin - linear scaling (default)
<Level>:log - logarithmic scaling
<Ebene>:staple - The bars are ‘stacked’, with the secondary bar displayed above the primary bar.
<Ebene>:staple - The bars are ‘stacked’, with the secondary bar displayed above the primary bar.
showDiff Additional numerical display of the difference '<primary bar content> - <secondary bar content>'.
The specification for each level consists of the level number (1..X), a ':' followed by the position 'top' or 'bottom'.
The strings for each level are separated by commas (see example).
<Level>:top - display above the bars
<Level>:bottom - display below the bars
spaceSize Defines how much space in px is kept free above or below the bar (for display type layoutType=diff) to display the
values. For styles with large fonts, the default value may be too small or a bar may slide over the baseline.
In these cases, please increase the value.
genPVdeviation Defines the method for calculating the deviation between forecast and actual PV generation.
The reading Today_PVdeviation is created depending on this setting.
The optional addition ‘:reverse’ specifies that PV generation > forecast is displayed as a positive value instead of a negative value (change of perspective).
The optional addition ':reverse' specifies that PV generation > forecast is evaluated as a positive value instead of a negative value (change of perspective).
daily[:reverse] - Calculation and creation of Today_PVdeviation takes place after sunset (default)
continuously[:reverse] - Calculation and creation of Today_PVdeviation is continuous
2 - der Verbraucher wird in der Verbraucherlegende ausgeblendet
3 - der Verbraucher wird in der Flußgrafik ausgeblendet
9 - das Schaltelement des Verbrauchers wird in der Verbraucherlegende ausgeblendet
[Device:]Reading - Reading im Verbraucher oder (optional) einem alternativen Device.
[Device:]Reading - Reading im Verbraucher oder (optional) einem alternativen Device.
Hat das Reading den Wert 0 oder ist nicht vorhanden, wird der Verbraucher eingeblendet.
Die Wirkung der möglichen Readingwerte 1, 2 und 3 ist wie beschrieben.
conForecastTillNextSunrise Verbrauchsprognose von aktueller Stunde bis zum kommenden Sonnenaufgang
currentAPIinterval das aktuelle Abrufintervall der gewählten Strahlungsdaten-API in Sekunden
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).
dayAfterTomorrowPVforecast Liefert die Vorhersage der PV Erzeugung für Übermorgen (sofern verfügbar) ohne Autokorrektur (Rohdaten).
dummyConsumption Liefert den aktuellen, Verbrauchern nicht zuordenbaren Hausverbrauch. Enthält auch Verlustleistungsanteile.
daysUntilBatteryCare_XX Tage bis zur nächsten Batterie XX Pflege (Erreichen der Ladung 'maxSoC' aus Attribut ctrlBatSocManagementXX)
lastretrieval_time der letzte Abrufzeitpunkt der gewählten Strahlungsdaten-API
lastretrieval_timestamp der Timestamp der letzen Abrufzeitpunkt der gewählten Strahlungsdaten-API
- + - + @@ -29653,18 +29741,18 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. - + - + - + @@ -29913,7 +30001,7 @@ die ordnungsgemäße Anlagenkonfiguration geprüft werden. - +
beamHeightlevel Für jede Ebene der Balkengrafik kann die Balkenhöhe der jeweiligen Ebene festgelegt werden.
Die Angabe für eine Ebene besteht aus der Ebenen-Nummer (1..X), einem ':' gefolgt von einer positiven Ganzzahl > 0.
Die Angabe für eine Ebene besteht aus der Ebenen-Nummer (1..X), einem ':' gefolgt von einer positiven Ganzzahl > 0.
Der Zahlenwert wird als Normierungsfaktor bei der Höhenberechnung verwendet.
Die Angabe für weitere Ebenen erfolgt durch Komma getrennt (siehe Beispiel).
<Ebene>:<Ganzzahl> - Normierungsfaktor (default: 200)
beamPaddingBottom Legt den Platz in px im Balkendiagramm fest, der zwischen der letzten Text- oder Iconreihe der jeweiligen Balkengrafik Ebene
und dem unteren Rand dieser Ebene eingefügt wird.
Der Wert gilt einheitlich für alle Balkengrafik Ebenen.
scaleMode Für jede Ebene der Balkengrafik kann der Skalierungsmodus linear oder logarithmisch festgelegt werden.
Die logarithmische Einstellung hebt kleine Werte stärker an und komprimiert größere Werte in der Darstellung.
Die Angabe für eine Ebene besteht aus der Ebenen-Nummer (1..X), einem ':' gefolgt von dem Modus 'lin' oder 'log'.
Die Angabe für eine Ebene besteht aus der Ebenen-Nummer (1..X), einem ':' gefolgt von dem Modus 'lin' oder 'log'.
Die Strings für jede Ebene werden durch Komma getrennt (siehe Beispiel).
<Ebene>:lin - lineare Skalierung (default)
<Ebene>:log - logarithmische Skalierung
<Ebene>:staple - die Balken werden 'gestapelt', der sekundäre Balken wird über dem primären Balken dargestellt
<Ebene>:staple - die Balken werden 'gestapelt', der sekundäre Balken wird über dem primären Balken dargestellt
showDiff Zusätzliche numerische Anzeige der Differenz '<primärer Balkeninhalt> - <sekundärer Balkeninhalt>'.
Die Angabe für jede Ebene besteht aus der Ebenen-Nummer (1..X), einem ':' gefolgt von der Position 'top' oder 'bottom'.
Die Strings für jede Ebene werden durch Komma getrennt (siehe Beispiel).
<Ebene>:top - Anzeige über den Balken
<Ebene>:bottom - Anzeige unter den Balken
spaceSize Legt fest, wieviel Platz in px über oder unter den Balken (bei Anzeigetyp layoutType=diff) zur Anzeige der
Werte freigehalten wird. Bei Styles mit großen Fonts kann der default-Wert zu klein sein bzw. rutscht ein
Balken u.U. über die Grundlinie. In diesen Fällen bitte den Wert erhöhen.
genPVdeviation Legt die Methode zur Berechnung der Abweichung von prognostizierter und realer PV Erzeugung fest.
Das Reading Today_PVdeviation wird in Abhängigkeit dieser Einstellung erstellt.
Der optionale Zusatz ':reverse' legt fest, dass PV-Erzeugung > Prognose als positiver statt negativer Wert dargestellt wird (Perspektivwechsel).
Der optionale Zusatz ':reverse' legt fest, dass PV-Erzeugung > Prognose als positiver statt negativer Wert gewertet wird (Perspektivwechsel).
daily[:reverse] - Berechnung und Erstellung von Today_PVdeviation erfolgt nach Sonnenuntergang (default)
continuously[:reverse] - Berechnung und Erstellung von Today_PVdeviation erfolgt fortlaufend