diff --git a/contrib/DS_Starter/76_SolarForecast.pm b/contrib/DS_Starter/76_SolarForecast.pm
index 636e66513..0f15e03b4 100644
--- a/contrib/DS_Starter/76_SolarForecast.pm
+++ b/contrib/DS_Starter/76_SolarForecast.pm
@@ -3,7 +3,7 @@
#########################################################################################################################
# 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.
# 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'
use Encode;
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
BEGIN {
# Import from main::
GP_Import(
qw(
+ attr
AnalyzePerlCommand
AttrVal
AttrNum
+ data
defs
delFromDevAttrList
delFromAttrList
@@ -51,6 +60,9 @@ BEGIN {
Debug
fhemTimeLocal
FmtDateTime
+ FileWrite
+ FileRead
+ FileDelete
FmtTime
FW_makeImage
getKeyValue
@@ -102,6 +114,7 @@ BEGIN {
# Versions History intern
my %vNotesIntern = (
+ "0.2.0" => "use SMUtils, JSON, implement getter data,html,pvHistory ",
"0.1.0" => "09.12.2020 initial Version "
);
@@ -136,6 +149,13 @@ my %hset = ( # Ha
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
"0" => 1.00, # https://www.labri.fr/perso/billaud/travaux/Helios/Helios2/resources/de04/Chapter_4_DE.pdf
"10" => 1.06,
@@ -264,13 +284,15 @@ my %weather_ids = (
'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 $defpvme = 16.52; # default Wirkungsgrad Solarmodule
-my $definve = 98.3; # default Wirkungsgrad Wechselrichter
-my $kJtokWh = 0.00027778; # Umrechnungsfaktor kJ in kWh
-my $defmaxvar = 0.5; # max. Varianz pro Tagesberechnung Autokorrekturfaktor
-my $definterval = 70; # Standard Abfrageintervall
-
+my @chours = (5..21); # Stunden des Tages mit möglichen Korrekturwerten
+my $defpvme = 16.52; # default Wirkungsgrad Solarmodule
+my $definve = 98.3; # default Wirkungsgrad Wechselrichter
+my $kJtokWh = 0.00027778; # Umrechnungsfaktor kJ in kWh
+my $defmaxvar = 0.5; # max. Varianz pro Tagesberechnung Autokorrekturfaktor
+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
################################################################
@@ -282,8 +304,10 @@ sub Initialize {
$hash->{DefFn} = \&Define;
$hash->{GetFn} = \&Get;
$hash->{SetFn} = \&Set;
+ $hash->{DeleteFn} = \&Delete;
$hash->{FW_summaryFn} = \&FwFn;
$hash->{FW_detailFn} = \&FwFn;
+ $hash->{ShutdownFn} = \&Shutdown;
$hash->{AttrFn} = \&Attr;
$hash->{NotifyFn} = \&Notify;
$hash->{AttrList} = "autoRefresh:selectnumbers,120,0.2,1800,0,log10 ".
@@ -335,12 +359,39 @@ sub Define {
my ($hash, $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
- 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);
+ 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);
centralTask ($hash); # Einstieg in Abfrage
@@ -641,29 +692,80 @@ return;
# SolarForecast Get
###############################################################
sub Get {
- my ($hash, @a) = @_;
- return "\"get X\" needs at least an argument" if ( @a < 2 );
- my $name = shift @a;
- my $cmd = shift @a;
+ my ($hash, @a) = @_;
+ return "\"get X\" needs at least an argument" if ( @a < 2 );
+ my $name = 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 ".
- "data:noArg ".
- "html:noArg ".
- "ftui:noArg ";
-
- if ($cmd eq "data") {
- return centralTask ($hash);
- }
-
- if ($cmd eq "html") {
- return pageAsHtml($hash);
- }
+ my $getlist = "Unknown argument $opt, choose one of ".
+ "data:noArg ".
+ "html: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};
+
+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};
- if ($cmd eq "ftui") {
- return pageAsHtml($hash,"ftui");
- }
-
-return;
+ my $ret = listPVHistory ($hash);
+
+return $ret;
}
################################################################
@@ -729,6 +831,55 @@ sub Notify {
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
################################################################
@@ -845,7 +996,12 @@ sub _transferDWDForecastValues {
$hash->{HELPER}{"fc${fd}_".sprintf("%02d",$fh)."_PVforecast"} = $v." Wh"; # original Vorhersagedaten 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";
+ push @$daref, "Today_Hour".sprintf("%02d",$fh)."_PVforecast:$calcpv Wh";
+ }
+
+ if($fd == 0 && sprintf("%02d",$fh) eq $chour) {
+ $paref->{calcpv} = $calcpv;
+ setPVhistory ($paref);
}
}
@@ -926,7 +1082,7 @@ sub _transferInverterValues {
my $paref = shift;
my $hash = $paref->{hash};
my $name = $paref->{name};
- my $t = $paref->{t};
+ my $t = $paref->{t}; # aktuelle Unix-Zeit
my $chour = $paref->{chour};
my $daref = $paref->{daref};
@@ -962,17 +1118,18 @@ sub _transferInverterValues {
$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($ethishour < 0) {
- push @$daref, "Today_Hour".sprintf("%02d",$chour)."_PVreal:0 Wh";
- }
- else {
- push @$daref, "Today_Hour".sprintf("%02d",$chour)."_PVreal:". $ethishour." Wh";
+ $ethishour = 0;
}
+ push @$daref, "Today_Hour".sprintf("%02d",$chour)."_PVreal:".$ethishour." Wh";
+
+ $paref->{ethishour} = $ethishour;
+ setPVhistory ($paref);
}
-
+
return;
}
@@ -1939,6 +2096,8 @@ sub formatVal6 {
sub weather_icon {
my $id = shift;
+ $id = int $id;
+
if(defined $weather_ids{$id}) {
return $weather_ids{$id}{icon}, encode("utf8", $weather_ids{$id}{txtd});
}
@@ -2085,6 +2244,59 @@ sub calcVariance {
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
################################################################
@@ -2403,6 +2615,15 @@ Um eine Anpassung an die persönliche Anlage zu ermöglichen, können Korrekturf
+