mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-04-16 04:36:02 +00:00
76_SolarForecast.pm: contrib 0.5.0
git-svn-id: https://svn.fhem.de/fhem/trunk@23619 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
f9738d6bc4
commit
9b97dab52a
@ -41,7 +41,8 @@ use FHEM::SynoModules::SMUtils qw( evaljson
|
||||
trim
|
||||
); # Hilfsroutinen Modul
|
||||
|
||||
use Data::Dumper;
|
||||
use Data::Dumper;
|
||||
no if $] >= 5.017011, warnings => 'experimental::smartmatch';
|
||||
|
||||
# Run before module compilation
|
||||
BEGIN {
|
||||
@ -115,6 +116,7 @@ BEGIN {
|
||||
|
||||
# Versions History intern
|
||||
my %vNotesIntern = (
|
||||
"0.5.0" => "25.01.2021 add multistring support, add reset inverterStrings ",
|
||||
"0.4.0" => "24.01.2021 setter moduleDirection, add Area factor to calcPVforecast, add reset pvCorrection ",
|
||||
"0.3.0" => "21.01.2021 add cloud correction, add rain correction, add reset pvHistory, setter writeHistory ",
|
||||
"0.2.0" => "20.01.2021 use SMUtils, JSON, implement getter data,html,pvHistory, correct the 'disable' problem ",
|
||||
@ -128,6 +130,7 @@ my %hset = ( # Ha
|
||||
moduleArea => { fn => \&_setmoduleArea },
|
||||
moduleEfficiency => { fn => \&_setmoduleEfficiency },
|
||||
inverterEfficiency => { fn => \&_setinverterEfficiency },
|
||||
inverterStrings => { fn => \&_setinverterStrings },
|
||||
currentInverterDev => { fn => \&_setinverterDevice },
|
||||
currentMeterDev => { fn => \&_setmeterDevice },
|
||||
pvCorrectionFactor_05 => { fn => \&_setpvCorrectionFactor },
|
||||
@ -155,10 +158,11 @@ my %hset = ( # Ha
|
||||
);
|
||||
|
||||
my %hget = ( # Hash für Get-Funktion (needcred => 1: Funktion benötigt gesetzte Credentials)
|
||||
data => { fn => \&_getdata, needcred => 0 },
|
||||
html => { fn => \&_gethtml, needcred => 0 },
|
||||
ftui => { fn => \&_getftui, needcred => 0 },
|
||||
pvHistory => { fn => \&_getlistPVHistory, needcred => 0 },
|
||||
data => { fn => \&_getdata, needcred => 0 },
|
||||
html => { fn => \&_gethtml, needcred => 0 },
|
||||
ftui => { fn => \&_getftui, needcred => 0 },
|
||||
pvHistory => { fn => \&_getlistPVHistory, needcred => 0 },
|
||||
stringConfig => { fn => \&_getstringConfig, needcred => 0 },
|
||||
);
|
||||
|
||||
my %hff = ( # Flächenfaktoren
|
||||
@ -429,7 +433,7 @@ sub Set {
|
||||
my $prop = shift @a;
|
||||
my $prop1 = shift @a;
|
||||
|
||||
my ($setlist,@fcdevs,@indevs,@medevs,@cfs);
|
||||
my ($setlist,@fcdevs,@cfs);
|
||||
my ($fcd,$ind,$med,$cf) = ("","","","");
|
||||
|
||||
return if(IsDisabled($name));
|
||||
@ -441,20 +445,19 @@ sub Set {
|
||||
push @cfs, "pvCorrectionFactor_".sprintf("%02d",$h);
|
||||
}
|
||||
$cf = join " ", @cfs if(@cfs);
|
||||
|
||||
my $tilt = join ",", sort keys %hff;
|
||||
|
||||
$setlist = "Unknown argument $opt, choose one of ".
|
||||
"currentForecastDev:$fcd ".
|
||||
"currentInverterDev:textField-long ".
|
||||
"currentMeterDev:textField-long ".
|
||||
"inverterEfficiency ".
|
||||
"inverterStrings ".
|
||||
"moduleArea ".
|
||||
"moduleEfficiency ".
|
||||
"moduleTiltAngle:$tilt ".
|
||||
"moduleDirection:N,NE,E,SE,S,SW,W,NW ".
|
||||
"moduleTiltAngle ".
|
||||
"moduleDirection ".
|
||||
"pvCorrectionFactor_Auto:on,off ".
|
||||
"reset:currentForecastDev,currentInverterDev,currentMeterDev,pvCorrection,pvHistory ".
|
||||
"reset:currentForecastDev,currentInverterDev,currentMeterDev,inverterStrings,pvCorrection,pvHistory ".
|
||||
"writeHistory:noArg ".
|
||||
$cf
|
||||
;
|
||||
@ -527,6 +530,20 @@ sub _setinverterDevice { ## 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};
|
||||
|
||||
readingsSingleUpdate($hash, "inverterStrings", $prop, 1);
|
||||
|
||||
return qq{REMINDER - After setting or changing "inverterStrings" please check / set all module parameter (e.g. moduleTiltAngle) again !};
|
||||
}
|
||||
|
||||
################################################################
|
||||
# Setter currentMeterDev
|
||||
################################################################
|
||||
@ -565,51 +582,26 @@ sub _setmoduleArea { ## no critic "not used"
|
||||
my $paref = shift;
|
||||
my $hash = $paref->{hash};
|
||||
my $name = $paref->{name};
|
||||
my $prop = $paref->{prop} // return qq{no PV module area specified};
|
||||
|
||||
if($prop !~ /[0-9,.]/x) {
|
||||
return qq{The module area must be specified by numbers and optionally with decimal places};
|
||||
my $arg = $paref->{arg} // return qq{no PV module area specified};
|
||||
|
||||
$arg =~ s/,/./xg;
|
||||
|
||||
my ($a,$h) = parseParams ($arg);
|
||||
|
||||
if(!keys %$h) {
|
||||
return qq{The provided PV module area has wrong format};
|
||||
}
|
||||
|
||||
$prop =~ s/,/./x;
|
||||
|
||||
readingsSingleUpdate($hash, "moduleArea", $prop." qm", 1);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
################################################################
|
||||
# Setter reset
|
||||
################################################################
|
||||
sub _setreset { ## no critic "not used"
|
||||
my $paref = shift;
|
||||
my $hash = $paref->{hash};
|
||||
my $name = $paref->{name};
|
||||
my $prop = $paref->{prop} // return qq{no source specified for reset};
|
||||
|
||||
if($prop eq "pvHistory") {
|
||||
my $type = $hash->{TYPE};
|
||||
delete $data{$type}{$name}{pvhist};
|
||||
return;
|
||||
while (my ($key, $value) = each %$h) {
|
||||
if($value !~ /[0-9.]/x) {
|
||||
return qq{The module area of "$key" must be specified by numbers and optionally with decimal places};
|
||||
}
|
||||
}
|
||||
|
||||
if($prop eq "pvCorrection") {
|
||||
deleteReadingspec ($hash, "pvCorrectionFactor_.*");
|
||||
return;
|
||||
}
|
||||
|
||||
readingsDelete($hash, $prop);
|
||||
readingsSingleUpdate($hash, "moduleArea", $arg, 1);
|
||||
|
||||
if($prop eq "currentMeterDev") {
|
||||
readingsDelete($hash, "Current_GridConsumption");
|
||||
}
|
||||
|
||||
if($prop eq "currentInverterDev") {
|
||||
readingsDelete ($hash, "Current_PV");
|
||||
deleteReadingspec ($hash, ".*_PVreal" );
|
||||
}
|
||||
|
||||
createNotifyDev ($hash);
|
||||
my $ret = createStringConfig ($hash);
|
||||
return $ret if($ret);
|
||||
|
||||
return;
|
||||
}
|
||||
@ -621,15 +613,26 @@ sub _setmoduleEfficiency { ## no critic "not used"
|
||||
my $paref = shift;
|
||||
my $hash = $paref->{hash};
|
||||
my $name = $paref->{name};
|
||||
my $prop = $paref->{prop} // return qq{no PV module efficiency specified};
|
||||
|
||||
if($prop !~ /[0-9,.]/x) {
|
||||
return qq{The module efficiency must be specified by numbers and optionally with decimal places};
|
||||
my $arg = $paref->{arg} // return qq{no PV module efficiency specified};
|
||||
|
||||
my ($a,$h) = parseParams ($arg);
|
||||
|
||||
if(!keys %$h) {
|
||||
return qq{The provided PV module efficiency has wrong format};
|
||||
}
|
||||
|
||||
$prop =~ s/,/./x;
|
||||
$arg =~ s/,/./xg;
|
||||
|
||||
while (my ($key, $value) = each %$h) {
|
||||
if($value !~ /[0-9.]/x) {
|
||||
return qq{The module efficiency of "$key" must be specified by numbers and optionally with decimal places};
|
||||
}
|
||||
}
|
||||
|
||||
readingsSingleUpdate($hash, "moduleEfficiency", $prop, 1);
|
||||
readingsSingleUpdate($hash, "moduleEfficiency", $arg, 1);
|
||||
|
||||
my $ret = createStringConfig ($hash);
|
||||
return $ret if($ret);
|
||||
|
||||
return;
|
||||
}
|
||||
@ -641,13 +644,26 @@ sub _setmoduleTiltAngle { ## no critic "not used"
|
||||
my $paref = shift;
|
||||
my $hash = $paref->{hash};
|
||||
my $name = $paref->{name};
|
||||
my $prop = $paref->{prop} // return qq{no tilt angle was provided};
|
||||
my $arg = $paref->{arg} // return qq{no tilt angle was provided};
|
||||
|
||||
my $tilt = join "|", sort keys %hff;
|
||||
|
||||
if($prop !~ /[0-9]/x) {
|
||||
return qq{The tilt angle must be specified by numbers};
|
||||
my ($a,$h) = parseParams ($arg);
|
||||
|
||||
if(!keys %$h) {
|
||||
return qq{The provided tilt angle has wrong format};
|
||||
}
|
||||
|
||||
readingsSingleUpdate($hash, "moduleTiltAngle", $prop, 1);
|
||||
while (my ($key, $value) = each %$h) {
|
||||
if($value !~ /^($tilt)$/x) {
|
||||
return qq{The tilt angle of "$key" is wrong};
|
||||
}
|
||||
}
|
||||
|
||||
readingsSingleUpdate($hash, "moduleTiltAngle", $arg, 1);
|
||||
|
||||
my $ret = createStringConfig ($hash);
|
||||
return $ret if($ret);
|
||||
|
||||
return;
|
||||
}
|
||||
@ -659,13 +675,26 @@ sub _setmoduleDirection { ## no critic "not used"
|
||||
my $paref = shift;
|
||||
my $hash = $paref->{hash};
|
||||
my $name = $paref->{name};
|
||||
my $prop = $paref->{prop} // return qq{no module direction was provided};
|
||||
my $arg = $paref->{arg} // return qq{no module direction was provided};
|
||||
|
||||
if($prop !~ /N|NE|E|SE|S|SW|W|NW/x) {
|
||||
return qq{No valid value for module direction: $prop};
|
||||
my $dirs = "N|NE|E|SE|S|SW|W|NW"; # mögliche Richtungsangaben
|
||||
|
||||
my ($a,$h) = parseParams ($arg);
|
||||
|
||||
if(!keys %$h) {
|
||||
return qq{The provided module direction has wrong format};
|
||||
}
|
||||
|
||||
while (my ($key, $value) = each %$h) {
|
||||
if($value !~ /^($dirs)$/x) {
|
||||
return qq{The module direction of "$key" is wrong: $value};
|
||||
}
|
||||
}
|
||||
|
||||
readingsSingleUpdate($hash, "moduleDirection", $prop, 1);
|
||||
readingsSingleUpdate($hash, "moduleDirection", $arg, 1);
|
||||
|
||||
my $ret = createStringConfig ($hash);
|
||||
return $ret if($ret);
|
||||
|
||||
return;
|
||||
}
|
||||
@ -749,6 +778,42 @@ sub _setpvCorrectionFactorAuto { ## no critic "not used"
|
||||
return;
|
||||
}
|
||||
|
||||
################################################################
|
||||
# Setter reset
|
||||
################################################################
|
||||
sub _setreset { ## no critic "not used"
|
||||
my $paref = shift;
|
||||
my $hash = $paref->{hash};
|
||||
my $name = $paref->{name};
|
||||
my $prop = $paref->{prop} // return qq{no source specified for reset};
|
||||
|
||||
if($prop eq "pvHistory") {
|
||||
my $type = $hash->{TYPE};
|
||||
delete $data{$type}{$name}{pvhist};
|
||||
return;
|
||||
}
|
||||
|
||||
if($prop eq "pvCorrection") {
|
||||
deleteReadingspec ($hash, "pvCorrectionFactor_.*");
|
||||
return;
|
||||
}
|
||||
|
||||
readingsDelete($hash, $prop);
|
||||
|
||||
if($prop eq "currentMeterDev") {
|
||||
readingsDelete($hash, "Current_GridConsumption");
|
||||
}
|
||||
|
||||
if($prop eq "currentInverterDev") {
|
||||
readingsDelete ($hash, "Current_PV");
|
||||
deleteReadingspec ($hash, ".*_PVreal" );
|
||||
}
|
||||
|
||||
createNotifyDev ($hash);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
################################################################
|
||||
# Setter writeHistory
|
||||
################################################################
|
||||
@ -774,7 +839,8 @@ sub Get {
|
||||
my $getlist = "Unknown argument $opt, choose one of ".
|
||||
"data:noArg ".
|
||||
"html:noArg ".
|
||||
"pvHistory:noArg "
|
||||
"pvHistory:noArg ".
|
||||
"stringConfig:noArg "
|
||||
;
|
||||
|
||||
return if(IsDisabled($name));
|
||||
@ -841,6 +907,18 @@ sub _getlistPVHistory {
|
||||
return $ret;
|
||||
}
|
||||
|
||||
###############################################################
|
||||
# Getter stringConfig
|
||||
###############################################################
|
||||
sub _getstringConfig {
|
||||
my $paref = shift;
|
||||
my $hash = $paref->{hash};
|
||||
|
||||
my $ret = checkStringConfig ($hash);
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
################################################################
|
||||
sub Attr {
|
||||
my ($cmd,$name,$aName,$aVal) = @_;
|
||||
@ -941,6 +1019,7 @@ return;
|
||||
sub centralTask {
|
||||
my $hash = shift;
|
||||
my $name = $hash->{NAME}; # Name des eigenen Devices
|
||||
my $type = $hash->{TYPE};
|
||||
|
||||
RemoveInternalTimer($hash, "FHEM::SolarForecast::centralTask");
|
||||
|
||||
@ -958,7 +1037,16 @@ sub centralTask {
|
||||
|
||||
return if(IsDisabled($name));
|
||||
|
||||
readingsSingleUpdate($hash, "state", "running", 1);
|
||||
readingsSingleUpdate($hash, "state", "running", 1);
|
||||
|
||||
my $stch = $data{$type}{$name}{strings}; # String Config Hash
|
||||
if (!keys %{$stch}) {
|
||||
my $ret = createStringConfig ($hash); # die String Konfiguration erstellen
|
||||
if ($ret) {
|
||||
readingsSingleUpdate($hash, "state", $ret, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
my @da;
|
||||
my $t = time; # aktuelle Unix-Zeit
|
||||
@ -997,6 +1085,90 @@ sub centralTask {
|
||||
return;
|
||||
}
|
||||
|
||||
################################################################
|
||||
# Erstellen der Stringkonfiguration
|
||||
# Stringhash: $data{$type}{$name}{strings}
|
||||
################################################################
|
||||
sub createStringConfig { ## no critic "not used"
|
||||
my $hash = shift;
|
||||
my $name = $hash->{NAME};
|
||||
my $type = $hash->{TYPE};
|
||||
|
||||
delete $data{$type}{$name}{strings}; # Stringhash zurücksetzen
|
||||
|
||||
my @istrings = split ",", ReadingsVal ($name, "inverterStrings", ""); # Stringbezeichner
|
||||
|
||||
if(!@istrings) {
|
||||
return qq{Define all used strings with command "set $name inverterStrings" first.};
|
||||
}
|
||||
|
||||
my $tilt = ReadingsVal ($name, "moduleTiltAngle", ""); # Modul Neigungswinkel für jeden Stringbezeichner
|
||||
my ($at,$ht) = parseParams ($tilt);
|
||||
|
||||
while (my ($key, $value) = each %$ht) {
|
||||
if ($key ~~ @istrings) {
|
||||
$data{$type}{$name}{strings}{"$key"}{tilt} = $value;
|
||||
}
|
||||
else {
|
||||
return qq{Check "moduleTiltAngle" -> the stringname "$key" is not defined as valid string in reading "inverterStrings"};
|
||||
}
|
||||
}
|
||||
|
||||
my $area = ReadingsVal ($name, "moduleArea", ""); # Modul Fläche für jeden Stringbezeichner
|
||||
my ($aa,$ha) = parseParams ($area);
|
||||
|
||||
while (my ($key, $value) = each %$ha) {
|
||||
if ($key ~~ @istrings) {
|
||||
$data{$type}{$name}{strings}{"$key"}{area} = $value;
|
||||
}
|
||||
else {
|
||||
return qq{Check "moduleArea" -> the stringname "$key" is not defined as valid string in reading "inverterStrings"};
|
||||
}
|
||||
}
|
||||
|
||||
my $effi = ReadingsVal ($name, "moduleEfficiency", ""); # Modul Effizienz für jeden Stringbezeichner
|
||||
my ($ae,$he) = parseParams ($effi);
|
||||
|
||||
while (my ($key, $value) = each %$he) {
|
||||
if ($key ~~ @istrings) {
|
||||
$data{$type}{$name}{strings}{"$key"}{effi} = $value;
|
||||
}
|
||||
else {
|
||||
return qq{Check "moduleEfficiency" -> the stringname "$key" is not defined as valid string in reading "inverterStrings"};
|
||||
}
|
||||
}
|
||||
|
||||
my $dir = ReadingsVal ($name, "moduleDirection", ""); # Modul Ausrichtung für jeden Stringbezeichner
|
||||
my ($ad,$hd) = parseParams ($dir);
|
||||
|
||||
while (my ($key, $value) = each %$hd) {
|
||||
if ($key ~~ @istrings) {
|
||||
$data{$type}{$name}{strings}{"$key"}{dir} = $value;
|
||||
}
|
||||
else {
|
||||
return qq{Check "moduleDirection" -> the stringname "$key" is not defined as valid string in reading "inverterStrings"};
|
||||
}
|
||||
}
|
||||
|
||||
if(!keys %{$data{$type}{$name}{strings}}) {
|
||||
return qq{The string configuration is empty.\nPlease check the settings of inverterStrings, moduleArea, moduleDirection, moduleTiltAngle, moduleEfficiency};
|
||||
}
|
||||
|
||||
my @sca = keys %{$data{$type}{$name}{strings}}; # Gegencheck ob nicht mehr Strings in inverterStrings enthalten sind als eigentlich verwendet
|
||||
my @tom;
|
||||
for my $sn (@istrings) {
|
||||
next if ($sn ~~ @sca);
|
||||
push @tom, $sn;
|
||||
}
|
||||
if(@tom) {
|
||||
return qq{Some Strings are not used. Please delete this string names from "inverterStrings" :}.join ",",@tom;
|
||||
}
|
||||
|
||||
# Log3 ($name, 2, "$name - string config: ".Dumper $data{$type}{$name}{strings});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
################################################################
|
||||
# Steuerparameter berechnen / festlegen
|
||||
################################################################
|
||||
@ -1106,7 +1278,7 @@ sub _transferDWDForecastValues {
|
||||
push @$daref, "${time_str}_PVforecast:".$calcpv." Wh";
|
||||
push @$daref, "${time_str}_Time:" .TimeAdjust ($epoche); # Zeit fortschreiben
|
||||
|
||||
$hash->{HELPER}{"fc${fd}_".sprintf("%02d",$fh)."_PVforecast"} = $v." kJ/m2"; # original Vorhersage Strahlungsdaten zur Berechnung Auto-Korrekturfaktor in Helper speichern
|
||||
$hash->{HELPER}{"fc${fd}_".sprintf("%02d",$fh)."_Rad1h"} = $v." kJ/m2"; # nur Info: original Vorhersage Strahlungsdaten zur Berechnung Auto-Korrekturfaktor in Helper speichern
|
||||
|
||||
if($fd == 0 && int $calcpv > 0) { # Vorhersagedaten des aktuellen Tages zum manuellen Vergleich in Reading speichern
|
||||
push @$daref, "Today_Hour".sprintf("%02d",$fh)."_PVforecast:$calcpv Wh";
|
||||
@ -1344,43 +1516,6 @@ sub pageRefresh {
|
||||
return;
|
||||
}
|
||||
|
||||
#############################################################################################
|
||||
# Versionierungen des Moduls setzen
|
||||
# Die Verwendung von Meta.pm und Packages wird berücksichtigt
|
||||
#############################################################################################
|
||||
sub setVersionInfo {
|
||||
my ($hash) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
my $v = (sortTopicNum("desc",keys %vNotesIntern))[0];
|
||||
my $type = $hash->{TYPE};
|
||||
$hash->{HELPER}{PACKAGE} = __PACKAGE__;
|
||||
$hash->{HELPER}{VERSION} = $v;
|
||||
|
||||
if($modules{$type}{META}{x_prereqs_src} && !$hash->{HELPER}{MODMETAABSENT}) {
|
||||
# META-Daten sind vorhanden
|
||||
$modules{$type}{META}{version} = "v".$v; # Version aus META.json überschreiben, Anzeige mit {Dumper $modules{SMAPortal}{META}}
|
||||
if($modules{$type}{META}{x_version}) { # {x_version} ( nur gesetzt wenn $Id: 76_SolarForecast.pm 21735 2020-04-20 20:53:24Z DS_Starter $ im Kopf komplett! vorhanden )
|
||||
$modules{$type}{META}{x_version} =~ s/1\.1\.1/$v/g;
|
||||
}
|
||||
else {
|
||||
$modules{$type}{META}{x_version} = $v;
|
||||
}
|
||||
return $@ unless (FHEM::Meta::SetInternals($hash)); # FVERSION wird gesetzt ( nur gesetzt wenn $Id: 76_SolarForecast.pm 21735 2020-04-20 20:53:24Z DS_Starter $ im Kopf komplett! vorhanden )
|
||||
|
||||
if(__PACKAGE__ eq "FHEM::$type" || __PACKAGE__ eq $type) {
|
||||
# es wird mit Packages gearbeitet -> Perl übliche Modulversion setzen
|
||||
# mit {<Modul>->VERSION()} im FHEMWEB kann Modulversion abgefragt werden
|
||||
use version 0.77; our $VERSION = FHEM::Meta::Get( $hash, 'version' );
|
||||
}
|
||||
}
|
||||
else { # herkömmliche Modulstruktur
|
||||
$hash->{VERSION} = $v;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
################################################################
|
||||
# Grafik als HTML zurück liefern (z.B. für Widget)
|
||||
################################################################
|
||||
@ -1440,34 +1575,43 @@ sub forecastGraphic {
|
||||
my $fcdev = ReadingsVal ($name, "currentForecastDev", ""); # aktuelles Forecast Device
|
||||
my $indev = ReadingsVal ($name, "currentInverterDev", ""); # aktuelles Inverter Device
|
||||
my ($a,$h) = parseParams ($indev);
|
||||
$indev = $a->[0] // "";
|
||||
my $cclv = "L05";
|
||||
$indev = $a->[0] // "";
|
||||
|
||||
my $pv0 = ReadingsNum ($name, "ThisHour_PVforecast", undef);
|
||||
my $ma = ReadingsNum ($name, "moduleArea", 0); # Solar Modulfläche (qm)
|
||||
my $moddir = ReadingsVal ($name, "moduleDirection", ""); # aktuelles Inverter Device
|
||||
my $ta = ReadingsNum ($name, "moduleTiltAngle", undef); # Neigungswinkel Solarmodule
|
||||
|
||||
if(!$fcdev || !$ma || !defined $pv0 || !$moddir || !defined $ta) {
|
||||
$height = AttrNum($name, 'beamHeight', 200);
|
||||
$ret .= "<table class='roomoverview'>";
|
||||
$ret .= "<tr style='height:".$height."px'>";
|
||||
$ret .= "<td>";
|
||||
my $is = ReadingsVal ($name, "inverterStrings", undef); # String Konfig
|
||||
my $ma = ReadingsVal ($name, "moduleArea", undef); # Modulfläche Konfig
|
||||
my $dir = ReadingsVal ($name, "moduleDirection", undef); # Modulausrichtung Konfig
|
||||
my $ta = ReadingsVal ($name, "moduleTiltAngle", undef); # Modul Neigungswinkel Konfig
|
||||
my $eff = ReadingsVal ($name, "moduleEfficiency", undef); # Modul Effizienz Konfig
|
||||
|
||||
if(!$is || !$fcdev || !$ma || !defined $pv0 || !$dir || !$ta || !$eff) {
|
||||
my $link = qq{<a href="/fhem?detail=$name">$name</a>};
|
||||
$height = AttrNum($name, 'beamHeight', 200);
|
||||
$ret .= "<table class='roomoverview'>";
|
||||
$ret .= "<tr style='height:".$height."px'>";
|
||||
$ret .= "<td>";
|
||||
|
||||
if(!$fcdev) {
|
||||
$ret .= qq{Please select a Solar Forecast device of Type "DWD_OpenData" with "set $name currentForecastDev"};
|
||||
$ret .= qq{Please select a Solar Forecast device with "set $link currentForecastDev"};
|
||||
}
|
||||
elsif(!$indev) {
|
||||
$ret .= qq{Please select an Inverter device with "set $name currentInverterDev"};
|
||||
$ret .= qq{Please select an Inverter device with "set $link currentInverterDev"};
|
||||
}
|
||||
elsif(!$is) {
|
||||
$ret .= qq{Please define all of your used string names with "set $link inverterStrings".};
|
||||
}
|
||||
elsif(!$ma) {
|
||||
$ret .= qq{Please specify the total module area with "set $name moduleArea"};
|
||||
$ret .= qq{Please specify the total module area with "set $link moduleArea"};
|
||||
}
|
||||
elsif(!$moddir) {
|
||||
$ret .= qq{Please specify the module Direction with "set $name moduleDirection"};
|
||||
elsif(!$eff) {
|
||||
$ret .= qq{Please specify the module efficiency with "set $link moduleEfficiency"};
|
||||
}
|
||||
elsif(!defined $ta) {
|
||||
$ret .= qq{Please specify the module tilt angle with "set $name moduleTiltAngle"};
|
||||
elsif(!$dir) {
|
||||
$ret .= qq{Please specify the module direction with "set $link moduleDirection"};
|
||||
}
|
||||
elsif(!$ta) {
|
||||
$ret .= qq{Please specify the module tilt angle with "set $link moduleTiltAngle"};
|
||||
}
|
||||
elsif(!defined $pv0) {
|
||||
$ret .= qq{Awaiting data from selected Solar Forecast device ...};
|
||||
@ -1479,6 +1623,7 @@ sub forecastGraphic {
|
||||
return $ret;
|
||||
}
|
||||
|
||||
my $cclv = "L05";
|
||||
@pgCDev = split(',',AttrVal($name,"consumerList","")); # definierte Verbraucher ermitteln
|
||||
my ($legend_style, $legend) = split('_',AttrVal($name,'consumerLegend','icon_top'));
|
||||
|
||||
@ -2296,57 +2441,69 @@ return @aneeded;
|
||||
##################################################################################################
|
||||
sub calcPVforecast {
|
||||
my $name = shift;
|
||||
my $rad = shift; # Nominale Strahlung aus DWD Device
|
||||
my $fh = shift; # Stunde des Tages
|
||||
my $rad = shift; # Nominale Strahlung aus DWD Device
|
||||
my $fh = shift; # Stunde des Tages
|
||||
|
||||
my $hash = $defs{$name};
|
||||
my $type = $hash->{TYPE};
|
||||
my $stch = $data{$type}{$name}{strings}; # String Configuration Hash
|
||||
|
||||
my $ma = ReadingsNum ($name, "moduleArea", 0 ); # Solar Modulfläche (gesamt)
|
||||
my $ta = ReadingsNum ($name, "moduleTiltAngle", 45 ); # Neigungswinkel Solarmodule
|
||||
my $moddir = ReadingsVal ($name, "moduleDirection", "S" ); # Ausrichtung der Solarmodule
|
||||
my $me = ReadingsNum ($name, "moduleEfficiency", $defpvme ); # Solar Modul Wirkungsgrad (%)
|
||||
my $ie = ReadingsNum ($name, "inverterEfficiency", $definve ); # Solar Inverter Wirkungsgrad (%)
|
||||
my $hc = ReadingsNum ($name, "pvCorrectionFactor_".sprintf("%02d",$fh), 1 ); # Korrekturfaktor für die Stunde des Tages
|
||||
my @strings = sort keys %{$stch};
|
||||
|
||||
my $cloudcover = $hash->{HELPER}{"NextHour".sprintf("%02d",$fh)."_CloudCover"} // 0; # effektive Wolkendecke
|
||||
my $ccf = 1 - (($cloudcover - $cloud_base) * $cloudslope / 100); # Cloud Correction Faktor mit Steilheit und Fußpunkt
|
||||
my $cloudcover = $hash->{HELPER}{"NextHour".sprintf("%02d",$fh)."_CloudCover"} // 0; # effektive Wolkendecke
|
||||
my $ccf = 1 - (($cloudcover - $cloud_base) * $cloudslope / 100); # Cloud Correction Faktor mit Steilheit und Fußpunkt
|
||||
|
||||
my $rainprob = $hash->{HELPER}{"NextHour".sprintf("%02d",$fh)."_RainProb"} // 0; # Niederschlagswahrscheinlichkeit> 0,1 mm während der letzten Stunde
|
||||
my $rcf = 1 - (($rainprob - $rain_base) * $rainslope / 100); # Rain Correction Faktor mit Steilheit
|
||||
|
||||
my $af = $hff{$ta}{$moddir} / 100; # Flächenfaktor: http://www.ing-büro-junge.de/html/photovoltaik.html
|
||||
|
||||
$hc = 1 if(1*$hc == 0);
|
||||
|
||||
my $pv = sprintf "%.1f", ($rad * $kJtokWh * $ma * $af * $me/100 * $ie/100 * $hc * $ccf * $rcf * 1000);
|
||||
|
||||
my $kw = AttrVal ($name, 'Wh/kWh', 'Wh');
|
||||
if($kw eq "Wh") {
|
||||
$pv = int $pv;
|
||||
}
|
||||
|
||||
my $lh = { # Log-Hash zur Ausgabe
|
||||
"moduleDirection" => $moddir,
|
||||
"moduleArea" => $ma,
|
||||
"moduleTiltAngle" => $ta,
|
||||
"Area factor" => $af,
|
||||
"Cloudfactor" => $ccf,
|
||||
"Rainfactor" => $rcf,
|
||||
"pvCorrectionFactor" => $hc,
|
||||
"moduleEfficiency" => $me/100,
|
||||
"inverterEfficiency" => $ie/100,
|
||||
"Radiation" => $rad,
|
||||
"Factor kJ to kWh" => $kJtokWh
|
||||
};
|
||||
|
||||
my $sq;
|
||||
for my $idx (sort keys %{$lh}) {
|
||||
$sq .= $idx." => ".$lh->{$idx}."\n";
|
||||
}
|
||||
my $rainprob = $hash->{HELPER}{"NextHour".sprintf("%02d",$fh)."_RainProb"} // 0; # Niederschlagswahrscheinlichkeit> 0,1 mm während der letzten Stunde
|
||||
my $rcf = 1 - (($rainprob - $rain_base) * $rainslope / 100); # Rain Correction Faktor mit Steilheit
|
||||
|
||||
Log3($name, 5, "$name - PV forecast calc factors for hour ".sprintf("%02d",$fh)." ->\n$sq");
|
||||
my $kw = AttrVal ($name, 'Wh/kWh', 'Wh');
|
||||
my $ie = ReadingsNum ($name, "inverterEfficiency", $definve ); # Solar Inverter Wirkungsgrad (%)
|
||||
my $hc = ReadingsNum ($name, "pvCorrectionFactor_".sprintf("%02d",$fh), 1 ); # Korrekturfaktor für die Stunde des Tages
|
||||
|
||||
return $pv;
|
||||
my $pvsum = 0;
|
||||
|
||||
for my $st (@strings) { # für jeden String der Config ..
|
||||
my $ma = $stch->{"$st"}{area}; # Solar Modulfläche String
|
||||
my $ta = $stch->{"$st"}{tilt}; # Neigungswinkel Solarmodule
|
||||
my $moddir = $stch->{"$st"}{dir}; # Ausrichtung der Solarmodule
|
||||
my $me = $stch->{"$st"}{effi}; # Solar Modul Wirkungsgrad (%)
|
||||
|
||||
my $af = $hff{$ta}{$moddir} / 100; # Flächenfaktor: http://www.ing-büro-junge.de/html/photovoltaik.html
|
||||
$hc = 1 if(1*$hc == 0);
|
||||
my $pv = sprintf "%.1f", ($rad * $kJtokWh * $ma * $af * $me/100 * $ie/100 * $hc * $ccf * $rcf * 1000);
|
||||
|
||||
my $lh = { # Log-Hash zur Ausgabe
|
||||
"moduleDirection" => $moddir,
|
||||
"moduleArea" => $ma,
|
||||
"moduleTiltAngle" => $ta,
|
||||
"Area factor" => $af,
|
||||
"Cloudfactor" => $ccf,
|
||||
"Rainfactor" => $rcf,
|
||||
"pvCorrectionFactor" => $hc,
|
||||
"moduleEfficiency" => $me/100,
|
||||
"inverterEfficiency" => $ie/100,
|
||||
"Radiation" => $rad,
|
||||
"Factor kJ to kWh" => $kJtokWh,
|
||||
"PV generation (Wh)" => $pv
|
||||
};
|
||||
|
||||
my $sq;
|
||||
for my $idx (sort keys %{$lh}) {
|
||||
$sq .= $idx." => ".$lh->{$idx}."\n";
|
||||
}
|
||||
|
||||
Log3($name, 5, "$name - PV forecast calc for hour ".sprintf("%02d",$fh)." string: $st ->\n$sq");
|
||||
|
||||
$pvsum += $pv;
|
||||
}
|
||||
|
||||
if($kw eq "Wh") {
|
||||
$pvsum = int $pvsum;
|
||||
}
|
||||
|
||||
Log3($name, 5, "$name - PV forecast calc for hour ".sprintf("%02d",$fh)." summary: $pvsum");
|
||||
|
||||
return $pvsum;
|
||||
}
|
||||
|
||||
################################################################
|
||||
@ -2387,7 +2544,7 @@ sub calcVariance {
|
||||
readingsSingleUpdate($hash, "pvCorrectionFactor_Auto", "on", 0);
|
||||
}
|
||||
|
||||
my $maxvar = AttrVal($name, "maxVariancePerDay", $defmaxvar); # max. Korrekturvarianz
|
||||
my $maxvar = AttrVal($name, "maxVariancePerDay", $defmaxvar); # max. Korrekturvarianz
|
||||
|
||||
my @da;
|
||||
for my $h (1..23) {
|
||||
@ -2400,7 +2557,7 @@ sub calcVariance {
|
||||
|
||||
my $cdone = ReadingsVal ($name, "pvCorrectionFactor_".sprintf("%02d",$h)."_autocalc", "");
|
||||
if($cdone eq "done") {
|
||||
Log3($name, 5, "$name - pvCorrectionFactor Hour: ".sprintf("%02d",$h));
|
||||
Log3($name, 5, "$name - pvCorrectionFactor Hour: ".sprintf("%02d",$h). "already calculated");
|
||||
next;
|
||||
}
|
||||
|
||||
@ -2467,7 +2624,7 @@ sub calcFromHistory {
|
||||
|
||||
Log3 ($name, 4, "$name - PV History -> Day $day has index $idx. Days ($calcd) for calc: ".join " ",@efa);
|
||||
|
||||
my $pvhh = $data{$type}{$name}{pvhist};
|
||||
# my $pvhh = $data{$type}{$name}{pvhist};
|
||||
my $anzavg = scalar(@efa);
|
||||
my ($pvrl,$pvfc) = (0,0);
|
||||
|
||||
@ -2547,6 +2704,49 @@ sub listPVHistory {
|
||||
return $sq;
|
||||
}
|
||||
|
||||
################################################################
|
||||
# liefert aktuelle Stringkonfiguration
|
||||
# inkl. Vollständigkeitscheck
|
||||
################################################################
|
||||
sub checkStringConfig {
|
||||
my $hash = shift;
|
||||
|
||||
my $name = $hash->{NAME};
|
||||
my $type = $hash->{TYPE};
|
||||
my $stch = $data{$type}{$name}{strings};
|
||||
|
||||
my $sub = sub {
|
||||
my $string = shift;
|
||||
my $ret;
|
||||
for my $key (sort keys %{$stch->{"$string"}}) {
|
||||
$ret .= ", " if($ret);
|
||||
$ret .= $key.": ".$stch->{"$string"}{$key};
|
||||
}
|
||||
return $ret;
|
||||
};
|
||||
|
||||
if (!keys %{$stch}) {
|
||||
return qq{String configuration is empty.};
|
||||
}
|
||||
|
||||
my $sc;
|
||||
my $cf = 0;
|
||||
for my $sn (sort keys %{$stch}) {
|
||||
my $sp = $sn." => ".$sub->($sn)."\n";
|
||||
$cf = 1 if($sp !~ /area.*?dir.*?effi.*?tilt/x); # Test Vollständigkeit: z.B. Ostlage => area: 31.04, dir: E, effi: 16.52, tilt: 30
|
||||
$sc .= $sp;
|
||||
}
|
||||
|
||||
if($cf) {
|
||||
$sc .= "\n\nOh no 🙁, your string configuration is inconsistent.\nPlease check the settings of moduleArea, moduleDirection, moduleEfficiency, moduleTiltAngle !";
|
||||
}
|
||||
else {
|
||||
$sc .= "\n\nCongratulations 😊, your string configuration checked without found errors !";
|
||||
}
|
||||
|
||||
return $sc;
|
||||
}
|
||||
|
||||
################################################################
|
||||
# Zusammenfassungen erstellen
|
||||
################################################################
|
||||
@ -2702,13 +2902,15 @@ return;
|
||||
Das Modul SolarForecast erstellt auf Grundlage der Werte aus generischen Quellendevices eine
|
||||
Vorhersage für den solaren Ertrag und weitere Informationen als Grundlage für abhängige Steuerungen. <br>
|
||||
Die Solargrafik kann ebenfalls in FHEM Tablet UI mit dem
|
||||
<a href="https://wiki.fhem.de/wiki/FTUI_Widget_SolarForecast">"SolarForecast Widget"</a> integriert werden. <br><br>
|
||||
<a href="https://wiki.fhem.de/wiki/FTUI_Widget_SMAPortalSPG">"SolarForecast Widget"</a> integriert werden. <br><br>
|
||||
|
||||
Die solare Vorhersage basiert auf der durch den Deutschen Wetterdienst (DWD) prognostizierten Globalstrahlung am
|
||||
Anlagenstandort. Im zugeordneten DWD_OpenData Device ist die passende Wetterstation mit dem Attribut "forecastStation"
|
||||
festzulegen um eine Prognose für diesen Standort zu erhalten. <br>
|
||||
Abhängig von der physikalischen Anlagengestaltung (Ausrichtung, Winkel, Aufteilung in mehrere Strings, u.a.) wird die
|
||||
verfügbare Globalstrahlung ganz spezifisch in elektrische Energie umgewandelt.
|
||||
verfügbare Globalstrahlung ganz spezifisch in elektrische Energie umgewandelt. <br>
|
||||
Ein SolarForecast Device unterstützt einen Wechselrichter mit beliebig vielen angeschlossenen Strings. Weiteren Wechselrichtern
|
||||
werden weitere SolarForecast Devices zugeordnet.
|
||||
|
||||
<ul>
|
||||
<a name="SolarForecastdefine"></a>
|
||||
@ -2814,18 +3016,40 @@ verfügbare Globalstrahlung ganz spezifisch in elektrische Energie umgewandelt.
|
||||
</ul>
|
||||
<br>
|
||||
|
||||
<ul>
|
||||
<a name="inverterStrings"></a>
|
||||
<li><b>inverterStrings <Stringname1>[,<Stringname2>,<Stringname3>,...] </b> <br>
|
||||
Bezeichnungen der am Wechselrichter aktiven Strings. Diese Bezeichnungen werden als Schlüssel in den weiteren
|
||||
Settings verwendet. <br><br>
|
||||
|
||||
<ul>
|
||||
<b>Beispiel: </b> <br>
|
||||
set <name> inverterStrings Ostdach,Südgarage,S3 <br>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<br>
|
||||
|
||||
<ul>
|
||||
<a name="moduleArea"></a>
|
||||
<li><b>moduleArea <Zahl> </b> <br>
|
||||
Gesamte installierte Solarmodulfläche in qm.
|
||||
<li><b>moduleArea <Stringname1>=<Fläche> [<Stringname2>=<Fläche> <Stringname3>=<Fläche> ...] </b> <br>
|
||||
Gesamte an dem String "StringnameX" installierte Solarmodulfläche in qm. Der Stringname ist ein Schlüsselwert des
|
||||
Readings <b>inverterStrings</b>. <br><br>
|
||||
|
||||
<ul>
|
||||
<b>Beispiel: </b> <br>
|
||||
set <name> moduleArea Ostdach=31.04 Südgarage=20 S3=32.05 <br>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<br>
|
||||
|
||||
<ul>
|
||||
<a name="moduleDirection"></a>
|
||||
<li><b>moduleDirection </b> <br>
|
||||
Ausrichtung der Solarmodule. <br><br>
|
||||
<li><b>moduleDirection <Stringname1>=<dir> [<Stringname2>=<dir> <Stringname3>=<dir> ...] </b> <br>
|
||||
Ausrichtung <dir> der Solarmodule im String "StringnameX". Der Stringname ist ein Schlüsselwert des
|
||||
Readings <b>inverterStrings</b>. <br>
|
||||
Die Richtungsangabe <dir> kann eine der folgenden Werte sein: <br><br>
|
||||
|
||||
<ul>
|
||||
<table>
|
||||
@ -2839,6 +3063,12 @@ verfügbare Globalstrahlung ganz spezifisch in elektrische Energie umgewandelt.
|
||||
<tr><td> <b>W</b> </td><td>Westausrichtung </td></tr>
|
||||
<tr><td> <b>NW</b> </td><td>Nord-West Ausrichtung </td></tr>
|
||||
</table>
|
||||
</ul>
|
||||
<br>
|
||||
|
||||
<ul>
|
||||
<b>Beispiel: </b> <br>
|
||||
set <name> moduleDirection Ostdach=E Südgarage=S S3=NW <br>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
@ -2846,18 +3076,28 @@ verfügbare Globalstrahlung ganz spezifisch in elektrische Energie umgewandelt.
|
||||
|
||||
<ul>
|
||||
<a name="moduleEfficiency"></a>
|
||||
<li><b>moduleEfficiency <Zahl> </b> <br>
|
||||
Wirkungsgrad der Solarmodule in % laut Herstellerangabe. <br>
|
||||
(default: 16.52)
|
||||
<li><b>moduleEfficiency <Stringname1>=<Eff> [<Stringname2>=<Eff> <Stringname3>=<Eff> ...] </b> <br>
|
||||
Wirkungsgrad der Solarmodule im String "StringnameX" laut Herstellerangabe in %. Der Stringname ist ein Schlüsselwert des
|
||||
Readings <b>inverterStrings</b>. <br><br>
|
||||
|
||||
<ul>
|
||||
<b>Beispiel: </b> <br>
|
||||
set <name> moduleEfficiency Ostdach=16.52 Südgarage=16.52 S3=16.52 <br>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<br>
|
||||
|
||||
<ul>
|
||||
<a name="moduleTiltAngle"></a>
|
||||
<li><b>moduleTiltAngle </b> <br>
|
||||
Neigungswinkel der Solarmodule (0 = waagerecht, 90 = senkrecht). <br>
|
||||
(default: 45)
|
||||
<li><b>moduleTiltAngle <Stringname1>=<Winkel> [<Stringname2>=<Winkel> <Stringname3>=<Winkel> ...] </b> <br>
|
||||
Neigungswinkel der Solarmodule. Der Stringname ist ein Schlüsselwert des Readings <b>inverterStrings</b>. <br>
|
||||
Mögliche Neigungswinkel sind: 0,10,20,30,40,45,50,60,70,80,90 (0 = waagerecht, 90 = senkrecht). <br><br>
|
||||
|
||||
<ul>
|
||||
<b>Beispiel: </b> <br>
|
||||
set <name> moduleTiltAngle Ostdach=40 Südgarage=60 S3=30 <br>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<br>
|
||||
|
Loading…
x
Reference in New Issue
Block a user