76_SolarForecast.pm: contrib 0.2.0

git-svn-id: https://svn.fhem.de/fhem/trunk/fhem@23540 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
nasseeder1 2021-01-17 19:13:02 +00:00
parent fd6a3d096a
commit 12a3a91c62

View File

@ -3,7 +3,7 @@
######################################################################################################################### #########################################################################################################################
# 76_SolarForecast.pm # 76_SolarForecast.pm
# #
# (c) 2020 by Heiko Maaz e-mail: Heiko dot Maaz at t-online dot de # (c) 2020-2021 by Heiko Maaz e-mail: Heiko dot Maaz at t-online dot de
# #
# This Module is used by module 76_SMAPortal to create graphic devices. # This Module is used by module 76_SMAPortal to create graphic devices.
# It can't be used standalone without any SMAPortal-Device. # It can't be used standalone without any SMAPortal-Device.
@ -34,15 +34,24 @@ use Time::HiRes qw(gettimeofday);
eval "use FHEM::Meta;1" or my $modMetaAbsent = 1; ## no critic 'eval' eval "use FHEM::Meta;1" or my $modMetaAbsent = 1; ## no critic 'eval'
use Encode; use Encode;
use utf8; use utf8;
eval "use JSON;1;" or my $jsonabs = "JSON"; ## no critic 'eval' # Debian: apt-get install libjson-perl
use FHEM::SynoModules::SMUtils qw( evaljson
moduleVersion
); # Hilfsroutinen Modul
use Data::Dumper;
# Run before module compilation # Run before module compilation
BEGIN { BEGIN {
# Import from main:: # Import from main::
GP_Import( GP_Import(
qw( qw(
attr
AnalyzePerlCommand AnalyzePerlCommand
AttrVal AttrVal
AttrNum AttrNum
data
defs defs
delFromDevAttrList delFromDevAttrList
delFromAttrList delFromAttrList
@ -51,6 +60,9 @@ BEGIN {
Debug Debug
fhemTimeLocal fhemTimeLocal
FmtDateTime FmtDateTime
FileWrite
FileRead
FileDelete
FmtTime FmtTime
FW_makeImage FW_makeImage
getKeyValue getKeyValue
@ -102,6 +114,7 @@ BEGIN {
# Versions History intern # Versions History intern
my %vNotesIntern = ( my %vNotesIntern = (
"0.2.0" => "use SMUtils, JSON, implement getter data,html,pvHistory ",
"0.1.0" => "09.12.2020 initial Version " "0.1.0" => "09.12.2020 initial Version "
); );
@ -136,6 +149,13 @@ my %hset = ( # Ha
moduleTiltAngle => { fn => \&_setmoduleTiltAngle }, moduleTiltAngle => { fn => \&_setmoduleTiltAngle },
); );
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 },
);
my %htilt = ( # Faktor für Neigungswinkel der Solarmodule my %htilt = ( # Faktor für Neigungswinkel der Solarmodule
"0" => 1.00, # https://www.labri.fr/perso/billaud/travaux/Helios/Helios2/resources/de04/Chapter_4_DE.pdf "0" => 1.00, # https://www.labri.fr/perso/billaud/travaux/Helios/Helios2/resources/de04/Chapter_4_DE.pdf
"10" => 1.06, "10" => 1.06,
@ -270,6 +290,8 @@ my $definve = 98.3; # de
my $kJtokWh = 0.00027778; # Umrechnungsfaktor kJ in kWh my $kJtokWh = 0.00027778; # Umrechnungsfaktor kJ in kWh
my $defmaxvar = 0.5; # max. Varianz pro Tagesberechnung Autokorrekturfaktor my $defmaxvar = 0.5; # max. Varianz pro Tagesberechnung Autokorrekturfaktor
my $definterval = 70; # Standard Abfrageintervall my $definterval = 70; # Standard Abfrageintervall
my $pvhcache = $attr{global}{modpath}."/FHEM/FhemUtils/PVH_SolarForecast_"; # Filename-Fragment für PV History (wird mit Devicename ergänzt)
################################################################ ################################################################
# Init Fn # Init Fn
@ -282,8 +304,10 @@ sub Initialize {
$hash->{DefFn} = \&Define; $hash->{DefFn} = \&Define;
$hash->{GetFn} = \&Get; $hash->{GetFn} = \&Get;
$hash->{SetFn} = \&Set; $hash->{SetFn} = \&Set;
$hash->{DeleteFn} = \&Delete;
$hash->{FW_summaryFn} = \&FwFn; $hash->{FW_summaryFn} = \&FwFn;
$hash->{FW_detailFn} = \&FwFn; $hash->{FW_detailFn} = \&FwFn;
$hash->{ShutdownFn} = \&Shutdown;
$hash->{AttrFn} = \&Attr; $hash->{AttrFn} = \&Attr;
$hash->{NotifyFn} = \&Notify; $hash->{NotifyFn} = \&Notify;
$hash->{AttrList} = "autoRefresh:selectnumbers,120,0.2,1800,0,log10 ". $hash->{AttrList} = "autoRefresh:selectnumbers,120,0.2,1800,0,log10 ".
@ -336,11 +360,38 @@ sub Define {
my @a = split(/\s+/x, $def); my @a = split(/\s+/x, $def);
return "Error: Perl module ".$jsonabs." is missing. Install it on Debian with: sudo apt-get install libjson-perl" if($jsonabs);
my $name = $hash->{NAME};
my $type = $hash->{TYPE};
$hash->{HELPER}{MODMETAABSENT} = 1 if($modMetaAbsent); # Modul Meta.pm nicht vorhanden $hash->{HELPER}{MODMETAABSENT} = 1 if($modMetaAbsent); # Modul Meta.pm nicht vorhanden
setVersionInfo ($hash); # Versionsinformationen setzen my $params = {
hash => $hash,
notes => \%vNotesIntern,
useAPI => 0,
useSMUtils => 1,
useErrCodes => 0
};
use version 0.77; our $VERSION = moduleVersion ($params); # Versionsinformationen setzen
createNotifyDev ($hash); createNotifyDev ($hash);
my $file = $pvhcache.$name;
my ($error, @content) = FileRead ($file); # Cache File der PV History lesen wenn vorhanden
if(!$error) {
my $json = join "", @content;
my $success = evaljson ($hash, $json);
if($success) {
$data{$type}{$name}{pvhist} = decode_json ($json);
}
else {
Log3($name, 2, qq{$name - WARNING - the content of file "$file" is not readable and may be corrupt});
}
}
readingsSingleUpdate($hash, "state", "initialized", 1); readingsSingleUpdate($hash, "state", "initialized", 1);
centralTask ($hash); # Einstieg in Abfrage centralTask ($hash); # Einstieg in Abfrage
@ -644,26 +695,77 @@ sub Get {
my ($hash, @a) = @_; my ($hash, @a) = @_;
return "\"get X\" needs at least an argument" if ( @a < 2 ); return "\"get X\" needs at least an argument" if ( @a < 2 );
my $name = shift @a; my $name = shift @a;
my $cmd = shift @a; my $opt = shift @a;
my $arg = join " ", map { my $p = $_; $p =~ s/\s//xg; $p; } @a; ## no critic 'Map blocks'
my $getlist = "Unknown argument $cmd, choose one of ". my $getlist = "Unknown argument $opt, choose one of ".
"data:noArg ". "data:noArg ".
"html:noArg ". "html:noArg ".
"ftui:noArg "; "pvHistory:noArg "
;
return if(IsDisabled($name));
my $params = {
hash => $hash,
name => $name,
opt => $opt,
arg => $arg
};
if($hget{$opt} && defined &{$hget{$opt}{fn}}) {
my $ret = q{};
if (!$hash->{CREDENTIALS} && $hget{$opt}{needcred}) {
return qq{Credentials of $name are not set."};
}
$ret = &{$hget{$opt}{fn}} ($params);
return $ret;
}
return $getlist;
}
###############################################################
# Getter data
###############################################################
sub _getdata {
my $paref = shift;
my $hash = $paref->{hash};
if ($cmd eq "data") {
return centralTask ($hash); return centralTask ($hash);
} }
if ($cmd eq "html") { ###############################################################
# Getter html
###############################################################
sub _gethtml {
my $paref = shift;
my $hash = $paref->{hash};
return pageAsHtml ($hash); return pageAsHtml ($hash);
} }
if ($cmd eq "ftui") { ###############################################################
# Getter ftui
# ohne Eintrag in Get-Liste
###############################################################
sub _getftui {
my $paref = shift;
my $hash = $paref->{hash};
return pageAsHtml ($hash,"ftui"); return pageAsHtml ($hash,"ftui");
} }
return; ###############################################################
# Getter listPVHistory
###############################################################
sub _getlistPVHistory {
my $paref = shift;
my $hash = $paref->{hash};
my $ret = listPVHistory ($hash);
return $ret;
} }
################################################################ ################################################################
@ -729,6 +831,55 @@ sub Notify {
return; return;
} }
################################################################
# Shutdown
################################################################
sub Shutdown {
my $hash = shift;
my $name = $hash->{NAME};
my $type = $hash->{TYPE};
if($data{$type}{$name}{pvhist}) { # Cache File für PV History schreiben
my @pvh;
my $json = encode_json ($data{$type}{$name}{pvhist});
push @pvh, $json;
my $file = $pvhcache.$name;
my $error = FileWrite($file, @pvh);
if ($error) {
Log3 ($name, 2, qq{$name - ERROR writing cache file "$file": $error});
}
}
return;
}
#################################################################
# Wenn ein Gerät in FHEM gelöscht wird, wird zuerst die Funktion
# X_Undef aufgerufen um offene Verbindungen zu schließen,
# anschließend wird die Funktion X_Delete aufgerufen.
# Funktion: Aufräumen von dauerhaften Daten, welche durch das
# Modul evtl. für dieses Gerät spezifisch erstellt worden sind.
# Es geht hier also eher darum, alle Spuren sowohl im laufenden
# FHEM-Prozess, als auch dauerhafte Daten bspw. im physikalischen
# Gerät zu löschen die mit dieser Gerätedefinition zu tun haben.
#################################################################
sub Delete {
my $hash = shift;
my $arg = shift;
my $name = $hash->{NAME};
my $file = $pvhcache.$name;
my $error = FileDelete($file); # Cache File der PV History löschen
if ($error) {
Log3 ($name, 2, qq{$name - ERROR deleting cache file "$file": $error});
}
return;
}
################################################################ ################################################################
# Zentraler Datenabruf # Zentraler Datenabruf
################################################################ ################################################################
@ -847,6 +998,11 @@ sub _transferDWDForecastValues {
if($fd == 0 && int $calcpv > 0) { # Vorhersagedaten des aktuellen Tages zum manuellen Vergleich in Reading 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"; push @$daref, "Today_Hour".sprintf("%02d",$fh)."_PVforecast:$calcpv Wh";
} }
if($fd == 0 && sprintf("%02d",$fh) eq $chour) {
$paref->{calcpv} = $calcpv;
setPVhistory ($paref);
}
} }
return; return;
@ -926,7 +1082,7 @@ sub _transferInverterValues {
my $paref = shift; my $paref = shift;
my $hash = $paref->{hash}; my $hash = $paref->{hash};
my $name = $paref->{name}; my $name = $paref->{name};
my $t = $paref->{t}; my $t = $paref->{t}; # aktuelle Unix-Zeit
my $chour = $paref->{chour}; my $chour = $paref->{chour};
my $daref = $paref->{daref}; my $daref = $paref->{daref};
@ -962,15 +1118,16 @@ sub _transferInverterValues {
$edaypast += ReadingsNum ($name, "Today_Hour".sprintf("%02d",$h)."_PVreal", 0); $edaypast += ReadingsNum ($name, "Today_Hour".sprintf("%02d",$h)."_PVreal", 0);
} }
my $ethishour = $etoday - $edaypast; my $ethishour = int ($etoday - $edaypast);
if($chour !~ /^($tlim)$/x) { # nicht setzen wenn Stunde 23 des Tages if($chour !~ /^($tlim)$/x) { # nicht setzen wenn Stunde 23 des Tages
if($ethishour < 0) { if($ethishour < 0) {
push @$daref, "Today_Hour".sprintf("%02d",$chour)."_PVreal:0 Wh"; $ethishour = 0;
} }
else {
push @$daref, "Today_Hour".sprintf("%02d",$chour)."_PVreal:".$ethishour." Wh"; push @$daref, "Today_Hour".sprintf("%02d",$chour)."_PVreal:".$ethishour." Wh";
}
$paref->{ethishour} = $ethishour;
setPVhistory ($paref);
} }
return; return;
@ -1939,6 +2096,8 @@ sub formatVal6 {
sub weather_icon { sub weather_icon {
my $id = shift; my $id = shift;
$id = int $id;
if(defined $weather_ids{$id}) { if(defined $weather_ids{$id}) {
return $weather_ids{$id}{icon}, encode("utf8", $weather_ids{$id}{txtd}); return $weather_ids{$id}{icon}, encode("utf8", $weather_ids{$id}{txtd});
} }
@ -2085,6 +2244,59 @@ sub calcVariance {
return; return;
} }
################################################################
# PV und PV Forecast in History-Hash speichern zur
# Berechnung des Korrekturfaktors über mehrere Tage
################################################################
sub setPVhistory {
my $paref = shift;
my $hash = $paref->{hash};
my $name = $paref->{name};
my $t = $paref->{t}; # aktuelle Unix-Zeit
my $chour = $paref->{chour};
my $ethishour = $paref->{ethishour};
my $calcpv = $paref->{calcpv};
my $type = $hash->{TYPE};
my $day = strftime "%d", localtime($t); # aktueller Tag
$data{$type}{$name}{pvhist}{$day}{$chour}{pvrl} = $ethishour if(defined $ethishour); # realer Energieertrag
$data{$type}{$name}{pvhist}{$day}{$chour}{pvfc} = $calcpv if(defined $calcpv); # prognostizierter Energieertrag
return;
}
################################################################
# liefert aktuelle Einträge des PV Cache
################################################################
sub listPVHistory {
my $hash = shift;
my $name = $hash->{NAME};
my $type = $hash->{TYPE};
my $sub = sub {
my $day = shift;
my $ret;
for my $key (sort{$a<=>$b} keys %{$data{$type}{$name}{pvhist}{$day}}) {
$ret .= "\n " if($ret);
$ret .= $key." => pvreal:".$data{$type}{$name}{pvhist}{$day}{$key}{pvrl}.", pvforecast:".$data{$type}{$name}{pvhist}{$day}{$key}{pvfc};
}
return $ret;
};
if (!keys %{$data{$type}{$name}{pvhist}}) {
return qq{PV cache is empty.};
}
my $sq;
for my $idx (sort{$a<=>$b} keys %{$data{$type}{$name}{pvhist}}) {
$sq .= $idx." => ".$sub->($idx)."\n";
}
return $sq;
}
################################################################ ################################################################
# Zusammenfassungen erstellen # Zusammenfassungen erstellen
################################################################ ################################################################
@ -2403,6 +2615,15 @@ Um eine Anpassung an die persönliche Anlage zu ermöglichen, können Korrekturf
</ul> </ul>
<br> <br>
<ul>
<a name="pvHistory"></a>
<li><b>pvHistory </b> <br>
Listet die PV Werte der letzten Tage sortiert nach dem Tagesdatum und der Stunde des jeweiligen Tages auf.
Dabei sind <b>pvreal</b> der reale und <b>pvforecast</b> der prognostizierte PV Ertrag.
</li>
</ul>
<br>
</ul> </ul>
<br> <br>