mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-05-04 22:19:38 +00:00
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:
parent
fd6a3d096a
commit
12a3a91c62
@ -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,
|
||||||
@ -264,12 +284,14 @@ my %weather_ids = (
|
|||||||
'99' => { s => '1', icon => 'weather_storm', txtd => 'starkes Gewitter mit Graupel oder Hagel' },
|
'99' => { s => '1', icon => 'weather_storm', txtd => 'starkes Gewitter mit Graupel oder Hagel' },
|
||||||
);
|
);
|
||||||
|
|
||||||
my @chours = (5..21); # Stunden des Tages mit möglichen Korrekturwerten
|
my @chours = (5..21); # Stunden des Tages mit möglichen Korrekturwerten
|
||||||
my $defpvme = 16.52; # default Wirkungsgrad Solarmodule
|
my $defpvme = 16.52; # default Wirkungsgrad Solarmodule
|
||||||
my $definve = 98.3; # default Wirkungsgrad Wechselrichter
|
my $definve = 98.3; # default Wirkungsgrad Wechselrichter
|
||||||
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
|
||||||
@ -641,29 +692,80 @@ return;
|
|||||||
# SolarForecast Get
|
# SolarForecast Get
|
||||||
###############################################################
|
###############################################################
|
||||||
sub Get {
|
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 "
|
||||||
|
;
|
||||||
|
|
||||||
if ($cmd eq "data") {
|
return if(IsDisabled($name));
|
||||||
return centralTask ($hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($cmd eq "html") {
|
my $params = {
|
||||||
return pageAsHtml($hash);
|
hash => $hash,
|
||||||
}
|
name => $name,
|
||||||
|
opt => $opt,
|
||||||
|
arg => $arg
|
||||||
|
};
|
||||||
|
|
||||||
if ($cmd eq "ftui") {
|
if($hget{$opt} && defined &{$hget{$opt}{fn}}) {
|
||||||
return pageAsHtml($hash,"ftui");
|
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;
|
return $getlist;
|
||||||
|
}
|
||||||
|
|
||||||
|
###############################################################
|
||||||
|
# Getter data
|
||||||
|
###############################################################
|
||||||
|
sub _getdata {
|
||||||
|
my $paref = shift;
|
||||||
|
my $hash = $paref->{hash};
|
||||||
|
|
||||||
|
return centralTask ($hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
###############################################################
|
||||||
|
# Getter html
|
||||||
|
###############################################################
|
||||||
|
sub _gethtml {
|
||||||
|
my $paref = shift;
|
||||||
|
my $hash = $paref->{hash};
|
||||||
|
|
||||||
|
return pageAsHtml ($hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
###############################################################
|
||||||
|
# Getter ftui
|
||||||
|
# ohne Eintrag in Get-Liste
|
||||||
|
###############################################################
|
||||||
|
sub _getftui {
|
||||||
|
my $paref = shift;
|
||||||
|
my $hash = $paref->{hash};
|
||||||
|
|
||||||
|
return pageAsHtml ($hash,"ftui");
|
||||||
|
}
|
||||||
|
|
||||||
|
###############################################################
|
||||||
|
# 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>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user