diff --git a/fhem/contrib/DS_Starter/76_SolarForecast.pm b/fhem/contrib/DS_Starter/76_SolarForecast.pm index 46412418d..d1c2b4d29 100644 --- a/fhem/contrib/DS_Starter/76_SolarForecast.pm +++ b/fhem/contrib/DS_Starter/76_SolarForecast.pm @@ -50,14 +50,13 @@ use HttpUtils; eval "use JSON;1;" or my $jsonabs = 'JSON'; ## no critic 'eval' # Debian: sudo apt-get install libjson-perl eval "use AI::DecisionTree;1;" or my $aidtabs = 'AI::DecisionTree'; ## no critic 'eval' -use FHEM::SynoModules::SMUtils qw( - checkModVer +use FHEM::SynoModules::SMUtils qw (checkModVer evaljson getClHash delClHash moduleVersion trim - ); # Hilfsroutinen Modul + ); # Hilfsroutinen Modul use Data::Dumper; use Blocking; @@ -68,8 +67,7 @@ use MIME::Base64; BEGIN { # Import from main:: GP_Import( - qw( - attr + qw (attr asyncOutput AnalyzePerlCommand AnalyzeCommandChain @@ -137,7 +135,7 @@ BEGIN { FW_widgetOverride FW_wname readyfnlist - ) + ) ); # Export to main context with different name @@ -162,7 +160,9 @@ my %vNotesIntern = ( "note in Reading pvCorrectionFactor_XX if AI prediction was used in relevant hour ". "AI usage depending either of available number of rules or difference to api forecast ". "minor fix in ___readCandQ, new experimental attribute ctrlAreaFactorUsage ". - "optional icon in attr setupOtherProducerXX ", + "optional icon in attr setupOtherProducerXX, integrate Producer to _flowGraphic (kask) ". + "don't show Consumer or Producer if it isn't defined any kind of it ". + "Optimization of space in the flow chart above generators and below consumers ", "1.33.1" => "27.09.2024 bugfix of 1.33.0, add aiRulesNumber to pvCircular, limits of AI trained datasets for ". "AI use (aiAccTRNMin, aiSpreadTRNMin)", "1.33.0" => "26.09.2024 substitute area factor hash by ___areaFactorFix function ", @@ -1205,7 +1205,7 @@ sub Initialize { "ctrlBatSocManagement:textField-long ". "ctrlConsRecommendReadings:multiple-strict,$allcs ". "ctrlDebug:multiple-strict,$dm,#14 ". - "ctrlAreaFactorUsage:fix,flex,flexShared ". + "ctrlAreaFactorUsage:fix,trackFull,trackShared ". "ctrlGenPVdeviation:daily,continuously ". "ctrlInterval ". "ctrlLanguage:DE,EN ". @@ -3314,14 +3314,10 @@ return; } ################################################################################################## -# Abruf DWD Strahlungsdaten und Rohdaten ohne Korrektur -# speichern in solcastapi Hash +# Abruf DWD Strahlungsdaten und Rohdaten ohne Korrektur # -# !!!! NACHFOLGENDE INFO GILT NUR BEI DWD RAD1H VERWENDUNG !!!! -# ############################################################# -# -# PV Forecast Rad1h in kWh / Wh -# Berechnung nach Formel 1 aus http://www.ing-büro-junge.de/html/photovoltaik.html: +# Berechnung nach Formel 1 aus http://www.ing-büro-junge.de/html/photovoltaik.html +# als Jahreserträge: # # * Faktor für Umwandlung kJ in kWh: 0.00027778 # * Eigene Modulfläche in qm z.B.: 31,04 @@ -3329,8 +3325,8 @@ return; # * Wirkungsgrad WR in % z.B.: 98,3 # * Korrekturwerte wegen Ausrichtung/Verschattung etc. # -# Die Formel wäre dann: -# Ertrag in Wh = Rad1h * 0.00027778 * 31,04 qm * 16,52% * 98,3% * 100% * 1000 +# Die Formel wäre dann: +# Ertrag in Wh = Rad1h * 0.00027778 * 31,04 qm * 16,52% * 98,3% * 100% * 1000 # # Berechnung nach Formel 2 aus http://www.ing-büro-junge.de/html/photovoltaik.html: # @@ -3348,7 +3344,7 @@ return; # hier beschrieben: # https://www.energie-experten.org/erneuerbare-energien/photovoltaik/planung/sonnenstunden # -# !!! PV Berechnungsgrundlagen !!! +# PV Berechnungsgrundlagen # https://www.energie-experten.org/erneuerbare-energien/photovoltaik/planung/ertrag # http://www.ing-büro-junge.de/html/photovoltaik.html # @@ -3417,7 +3413,7 @@ sub __getDWDSolarData { my ($af, $pv, $sdr, $wcc); - if ($cafd =~ /flex/xs) { # Flächenfaktor Sonnenstand geführt + if ($cafd =~ /track/xs) { # Flächenfaktor Sonnenstand geführt ($af, $sdr, $wcc) = ___areaFactorTrack ( { name => $name, day => $day, dday => $dday, @@ -3429,6 +3425,7 @@ sub __getDWDSolarData { ); $wcc = 0 if(!isNumeric($wcc)); + $wcc = cloud2bin($wcc); debugLog ($paref, "apiProcess", "DWD API - Value of sunaz/sunalt not stored in pvHistory, workaround using 1.00/0.75") if(!isNumeric($af)); @@ -3436,7 +3433,7 @@ sub __getDWDSolarData { $af = 1.00 if(!isNumeric($af)); $sdr = 0.75 if(!isNumeric($sdr)); - if ($cafd eq 'flexShared') { # Direktstrahlung + Diffusstrahlung + if ($cafd eq 'trackShared') { # Direktstrahlung + Diffusstrahlung my $dirrad = $rad * $sdr; # Anteil Direktstrahlung an Globalstrahlung my $difrad = $rad - $dirrad; # Anteil Diffusstrahlung an Globalstrahlung @@ -5682,7 +5679,10 @@ sub __delProducerValues { deleteReadingspec ($hash, ".*_PPreal".$prn); readingsDelete ($hash, 'Current_PP'.$prn); delete $data{$type}{$name}{current}{'generationp'.$prn}; - delete $data{$type}{$name}{current}{'etotalp'.$prn}; + delete $data{$type}{$name}{current}{'etotalp' .$prn}; + delete $data{$type}{$name}{current}{'iconp' .$prn}; + delete $data{$type}{$name}{current}{'namep' .$prn}; + delete $data{$type}{$name}{current}{'aliasp' .$prn}; for my $hod (keys %{$data{$type}{$name}{circular}}) { delete $data{$type}{$name}{circular}{$hod}{'pprl'.$prn}; @@ -5690,7 +5690,7 @@ sub __delProducerValues { for my $dy (sort keys %{$data{$type}{$name}{pvhist}}) { for my $hr (sort keys %{$data{$type}{$name}{pvhist}{$dy}}) { - delete $data{$type}{$name}{pvhist}{$dy}{$hr}{'pprl'.$prn}; + delete $data{$type}{$name}{pvhist}{$dy}{$hr}{'pprl' .$prn}; delete $data{$type}{$name}{pvhist}{$dy}{$hr}{'etotalp'.$prn}; } } @@ -8455,9 +8455,9 @@ sub _transferProducerValues { my $pu = $pcunit =~ /^kW$/xi ? 1000 : 1; my $p = ReadingsNum ($prdev, $pcread, 0) * $pu; # aktuelle Erzeugung (W) - $p = $p < 0 ? 0 : sprintf("%.0f", $p); + $p = $p < 0 ? 0 : $p; - storeReading ('Current_PP'.$prn, $p.' W'); + storeReading ('Current_PP'.$prn, sprintf("%.1f", $p).' W'); $data{$type}{$name}{current}{'generationp'.$prn} = $p; my $etu = $etunit =~ /^kWh$/xi ? 1000 : 1; @@ -8482,7 +8482,9 @@ sub _transferProducerValues { } $data{$type}{$name}{current}{'etotalp'.$prn} = $etotal; # aktuellen etotal des WR speichern - + $data{$type}{$name}{current}{'namep' .$prn} = $prdev; # Name des Producerdevices + $data{$type}{$name}{current}{'aliasp' .$prn} = AttrVal ($prdev, 'alias', $prdev); # Alias Producer + if ($ethishour < 0) { $ethishour = 0; my $vl = 3; @@ -13878,6 +13880,7 @@ return $ret; sub _flowGraphic { my $paref = shift; my $name = $paref->{name}; + my $type = $paref->{type}; my $flowgsize = $paref->{flowgsize}; my $flowgani = $paref->{flowgani}; my $flowgshift = $paref->{flowgshift}; # Verschiebung der Flußgrafikbox (muß negiert werden) @@ -13888,27 +13891,60 @@ sub _flowGraphic { my $consDist = $paref->{flowgconsDist}; my $css = $paref->{css}; + my $hasbat = 1; # initial Batterie vorhanden + my $flowgprods = 1; # Producer in der Energieflußgrafik anzeigen per default + my $hash = $defs{$name}; my $style = 'width:98%; height:'.$flowgsize.'px;'; - my $animation = $flowgani ? '@keyframes dash { to { stroke-dashoffset: 0; } }' : ''; # Animation Ja/Nein - my $cpv = ReadingsNum ($name, 'Current_PV', 0); + my $animation = $flowgani ? '@keyframes dash { to { stroke-dashoffset: 0; } }' : ''; # Animation Ja/Nein my $cgc = ReadingsNum ($name, 'Current_GridConsumption', 0); my $cgfi = ReadingsNum ($name, 'Current_GridFeedIn', 0); my $csc = ReadingsNum ($name, 'Current_SelfConsumption', 0); my $cc = CurrentVal ($hash, 'consumption', 0); - my $gp01 = CurrentVal ($hash, 'generationp01', 0); - my $gp02 = CurrentVal ($hash, 'generationp02', 0); - my $gp03 = CurrentVal ($hash, 'generationp03', 0); - my $cc_dummy = $cc; + my $cpv = ReadingsNum ($name, 'Current_PV', 0); my $batin = ReadingsNum ($name, 'Current_PowerBatIn', undef); my $batout = ReadingsNum ($name, 'Current_PowerBatOut', undef); my $soc = ReadingsNum ($name, 'Current_BatCharge', 100); + my $cc_dummy = $cc; + + ## definierte Producer ermitteln und deren + ## aktuelle Leistung bestimmen + ############################################ + my $producercount = 0; + my $ppall = 0; # Summe Erzeugung alle Poducer + my @producers; + + for my $i (1..$maxproducer) { + my $p = CurrentVal ($hash, 'generationp'.(sprintf "%02d", ($i)), undef); - my $bat_color = $soc < 26 ? 'flowg bat25' : - $soc < 76 ? 'flowg bat50' : - 'flowg bat75'; + if (defined $p) { + push @producers, sprintf "%02d", $i; + $producercount += 1; + $ppall += $p; + } + } + + $ppall = sprintf("%.0f", $ppall); + $flowgprods = 0 if(!$producercount); # Producer Anzeige ausschalten wenn keine Producer definiert + + ## definierte Verbraucher ermitteln + ##################################### + my $consumercount = 0; + my @consumers; + + for my $c (sort{$a<=>$b} keys %{$data{$type}{$name}{consumers}}) { # definierte Verbraucher ermitteln + next if(isConsumerNoshow ($hash, $c) =~ /^[13]$/xs); # auszublendende Consumer nicht berücksichtigen + push @consumers, $c; + $consumercount += 1; + } - my $hasbat = 1; + $flowgcons = 0 if(!$consumercount); # Consumer Anzeige ausschalten wenn keine Consumer definiert + + ## Batterie + Werte festlegen + ############################### + my $bat_color = $soc < 26 ? 'flowg bat25' : + $soc < 76 ? 'flowg bat50' : + 'flowg bat75'; if (!defined($batin) && !defined($batout)) { $hasbat = 0; @@ -13916,55 +13952,56 @@ sub _flowGraphic { $batout = 0; $soc = 0; } - else { - #$csc -= $batout; - } my $grid_color = $cgfi ? 'flowg grid_color1' : 'flowg grid_color2'; - $grid_color = 'flowg grid_color3' if (!$cgfi && !$cgc && $batout); # dritte Farbe my $cgc_style = $cgc ? 'flowg active_in' : 'flowg inactive_in'; my $batout_style = $batout ? 'flowg active_out active_bat_out' : 'flowg inactive_in'; + $grid_color = 'flowg grid_color3' if(!$cgfi && !$cgc && $batout); # dritte Farbe + my $cgc_direction = 'M490,515 L670,590'; # Batterientladung ins Netz - my $cgc_direction = 'M490,305 L670,510'; # Batterientladung ins Netz - - if ($batout) { + if ($batout) { # Batterie wird entladen my $cgfo = $cgfi - $cpv; - if($cgfo > 1) { - $cgc_style = 'flowg active_out'; - $cgc_direction = 'M670,510 L490,305'; - $cgfi -= $cgfo; - $cgc = $cgfo; + if ($cgfo > 1) { + $cgc_style = 'flowg active_out'; + $cgc_direction = 'M670,590 L490,515'; + $cgfi -= $cgfo; + $cgc = $cgfo; } } - my $batout_direction = 'M902,305 L730,510'; # Batterientladung aus Netz + my $batout_direction = 'M902,515 L730,590'; - if ($batin) { + if ($batin) { # Batterie wird geladen my $gbi = $batin - $cpv; - if ($gbi > 1) { + if ($gbi > 1) { # Batterieladung anteilig aus Hausnetz geladen $batin -= $gbi; $batout_style = 'flowg active_in'; - $batout_direction = 'M730,510 L902,305'; + $batout_direction = 'M730,590 L902,515'; $batout = $gbi; } } - my $sun_color = $cpv ? 'flowg sun_active' : 'flowg sun_inactive'; - my $batin_style = $batin ? 'flowg active_in active_bat_in' : 'flowg inactive_out'; - my $csc_style = $csc && $cpv ? 'flowg active_out' : 'flowg inactive_out'; - my $cgfi_style = $cgfi ? 'flowg active_out' : 'flowg inactive_out'; + ## SVG Box initialisieren + ########################### + my $sun_color = $cpv ? 'flowg sun_active' : 'flowg sun_inactive'; + my $batin_style = $batin ? 'flowg active_in active_bat_in' : 'flowg inactive_out'; + my $csc_style = $csc && ($cpv || $ppall) ? 'flowg active_out' : 'flowg inactive_out'; + my $cgfi_style = $cgfi ? 'flowg active_out' : 'flowg inactive_out'; + + my $vbwidth = 800; # width and height specify the viewBox size + my $vbminx = -10 * $flowgshift; # min-x and min-y represent the smallest X and Y coordinates that the viewBox may have + my $vbminy = $flowgprods ? -25 : 100; + + my $vbhight = !$flowgcons ? 380 : + !$flowgconTime ? 590 : + 610; + + $vbhight += 100 if($flowgprods); - my $vbminx = -10 * $flowgshift; # min-x and min-y represent the smallest X and Y coordinates that the viewBox may have - my $vbminy = -25; - my $vbwidth = 800; # width and height specify the viewBox size - my $vbhight = !$flowgcons ? 480 : - $flowgconTime ? 700 : - 680; - my $vbox = "$vbminx $vbminy $vbwidth $vbhight"; - + my $ret = << "END0";