2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-04-30 12:07:09 +00:00

76_SolarForecast: contrib 0.82.1

git-svn-id: https://svn.fhem.de/fhem/trunk@27939 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
nasseeder1 2023-09-08 13:26:55 +00:00
parent 8a8048818f
commit 6bef274ea9

View File

@ -140,6 +140,7 @@ BEGIN {
# Versions History intern
my %vNotesIntern = (
"0.82.1" => "08.09.2023 rebuild implementation of DWD AI support ",
"0.82.0" => "02.09.2023 first implementation of DWD AI support, new ctrlDebug aiProcess aiData, reset aiData ",
"0.81.1" => "30.08.2023 show forecast qualities when pressing quality icon in forecast grafic, store rad1h (model DWD) in ".
"pvhistory, removed: affectCloudfactorDamping, affectRainfactorDamping ",
@ -414,7 +415,8 @@ my $pvccache = $attr{global}{modpath}."/FHEM/FhemUtils/PVC_SolarForecast_"
my $plantcfg = $attr{global}{modpath}."/FHEM/FhemUtils/PVCfg_SolarForecast_"; # Filename-Fragment für PV Anlagenkonfiguration (wird mit Devicename ergänzt)
my $csmcache = $attr{global}{modpath}."/FHEM/FhemUtils/PVCsm_SolarForecast_"; # Filename-Fragment für Consumer Status (wird mit Devicename ergänzt)
my $scpicache = $attr{global}{modpath}."/FHEM/FhemUtils/ScApi_SolarForecast_"; # Filename-Fragment für Werte aus SolCast API (wird mit Devicename ergänzt)
my $aicache = $attr{global}{modpath}."/FHEM/FhemUtils/AI_SolarForecast_"; # Filename-Fragment für AI Trainingsdaten (wird mit Devicename ergänzt)
my $aitrained = $attr{global}{modpath}."/FHEM/FhemUtils/AItra_SolarForecast_"; # Filename-Fragment für AI Trainingsdaten (wird mit Devicename ergänzt)
my $airaw = $attr{global}{modpath}."/FHEM/FhemUtils/AIraw_SolarForecast_"; # Filename-Fragment für AI Input Daten = Raw Trainigsdaten
my $calcmaxd = 30; # Anzahl Tage die zur Berechnung Vorhersagekorrektur verwendet werden
my @dweattrmust = qw(TTT Neff R101 ww SunUp SunRise SunSet); # Werte die im Attr forecastProperties des Weather-DWD_Opendata Devices mindestens gesetzt sein müssen
@ -552,6 +554,7 @@ my %hget = ( # Ha
nextHours => { fn => \&_getlistNextHours, needcred => 0 },
rooftopData => { fn => \&_getRoofTopData, needcred => 0 },
solApiData => { fn => \&_getlistSolCastData, needcred => 0 },
valDecTree => { fn => \&_getaiDecTree, needcred => 0 },
);
my %hattr = ( # Hash für Attr-Funktion
@ -912,7 +915,9 @@ my %hcsr = (
# $data{$type}{$name}{consumers} # Consumer Hash
# $data{$type}{$name}{strings} # Stringkonfiguration Hash
# $data{$type}{$name}{solcastapi} # Zwischenspeicher API-Daten
# $data{$type}{$name}{aidectree} # AI Decision Tree Daten
# $data{$type}{$name}{aidectree}{object} # AI Decision Tree Object
# $data{$type}{$name}{aidectree}{aitrained} # AI Decision Tree trainierte Daten
# $data{$type}{$name}{aidectree}{airaw} # Rohdaten für AI Input = Raw Trainigsdaten
################################################################
# Init Fn
@ -1102,8 +1107,8 @@ sub Define {
$params->{cachename} = 'solcastapi';
_readCacheFile ($params);
$params->{file} = $aicache.$name; # AI Cache File einlesen wenn vorhanden
$params->{cachename} = 'aidectree';
$params->{file} = $aitrained.$name; # AI Cache File einlesen wenn vorhanden
$params->{cachename} = 'aitrained';
_readCacheFile ($params);
singleUpdateState ( {hash => $hash, state => 'initialized', evt => 1} );
@ -1125,7 +1130,7 @@ sub _readCacheFile {
my $file = $paref->{file};
my $cachename = $paref->{cachename};
if ($cachename eq 'aidectree') {
if ($cachename eq 'aitrained') {
my ($err, $dtree) = fileRetrieve ($file);
if (!$err && $dtree) {
@ -1133,8 +1138,8 @@ sub _readCacheFile {
my $valid = isa_ok($dtree, 'AI::DecisionTree');
if ($valid == 1) {
$data{$type}{$name}{$cachename} = $dtree;
$data{$type}{$name}{current}{aiinitstate} = 'ok';
$data{$type}{$name}{aidectree}{aitrained} = $dtree;
$data{$type}{$name}{current}{aitrainstate} = 'ok';
Log3($name, 3, qq{$name - cached data "$cachename" restored});
}
}
@ -1262,9 +1267,9 @@ sub Set {
##########################
my $aiist = CurrentVal ($hash, 'aiinitstate', '');
if ($aiist eq 'ok') {
$setlist .= "aiDecTree:addInstances,train ";
}
#if ($aiist eq 'ok') {
$setlist .= "aiDecTree:addInstances,addRawData,train ";
#}
my $params = {
hash => $hash,
@ -2077,17 +2082,15 @@ sub _setreset { ## no critic "not used"
}
if($prop eq 'aiData') {
my $err;
my $dtree = AiDetreeVal ($hash, undef);
if ($dtree) {
delete $data{$type}{$name}{aidectree};
($err, $dtree) = aiInit ($paref);
return $err if($err);
}
delete $data{$type}{$name}{current}{aiinitstate};
delete $data{$type}{$name}{current}{aitrainstate};
delete $data{$type}{$name}{current}{aiaddistate};
delete $data{$type}{$name}{current}{aigetresult};
#my $dtree = AiDetreeVal ($hash, 'object', undef);
#if ($dtree) {
aiInit ($paref);
#}
return;
}
@ -2286,7 +2289,11 @@ sub _setaiDecTree { ## no critic "not used"
my $prop = $paref->{prop} // return;
if($prop eq 'addInstances') {
aiAddInstance ($paref);
aiAddInstance ($paref);
}
if($prop eq 'addRawData') {
aiAddRawData ($paref);
}
if($prop eq 'train') {
@ -2342,6 +2349,14 @@ sub Get {
"solApiData:noArg ".
"valCurrent:noArg "
;
## KI spezifische Getter
##########################
my $aiist = CurrentVal ($hash, 'aiinitstate', '');
if ($aiist eq 'ok') {
$getlist .= "valDecTree:aiRawData ";
}
return if(IsDisabled($name));
@ -3804,6 +3819,24 @@ sub _getlistSolCastData {
return $ret;
}
###############################################################
# Getter aiDecTree
###############################################################
sub _getaiDecTree { ## no critic "not used"
my $paref = shift;
my $hash = $paref->{hash};
my $name = $paref->{name};
my $arg = $paref->{arg} // return;
my $ret;
if($arg eq 'aiRawData') {
$ret = listDataPool ($hash, 'aiRawData');
}
return $ret;
}
################################################################
sub Attr {
my $cmd = shift;
@ -4194,7 +4227,7 @@ sub Shutdown {
writeCacheToFile ($hash, "solcastapi", $scpicache.$name); # Cache File SolCast API Werte schreiben
if (CurrentVal ($hash, 'aitrainstate', '') eq 'ok') {
writeCacheToFile ($hash, "aidectree", $aicache.$name); # AI Cache schreiben
writeCacheToFile ($hash, "aidectree", $aitrained.$name); # AI Cache schreiben
}
return;
@ -4238,7 +4271,7 @@ sub Delete {
$plantcfg.$name,
$csmcache.$name,
$scpicache.$name,
$aicache.$name
$aitrained.$name
);
for my $f (@ftd) {
@ -4284,11 +4317,31 @@ sub writeCacheToFile {
my @data;
my ($error, $err, $lw);
if ($cachename eq 'aidectree') {
return if(ref $data{$type}{$name}{$cachename} ne 'AI::DecisionTree');
if ($cachename eq 'aitrained') {
my $dtree = AiDetreeVal ($hash, 'aitrained', '');
return if(ref $dtree ne 'AI::DecisionTree');
my $dtree = $data{$type}{$name}{$cachename};
$error = fileStore ($dtree, $file);
$error = fileStore ($dtree, $file);
if ($error) {
$err = qq{ERROR while writing AI data to file "$file": $error};
Log3 ($name, 1, "$name - $err");
return $err;
}
$lw = gettimeofday();
$hash->{LCACHEFILE} = "last write time: ".FmtTime($lw)." File: $file";
singleUpdateState ( {hash => $hash, state => "wrote cachefile $cachename successfully", evt => 1} );
return;
}
if ($cachename eq 'airaw') {
my $data = AiRawdataVal ($hash, '', '', '');
if ($data) {
$error = fileStore ($data, $file);
}
if ($error) {
$err = qq{ERROR while writing AI data to file "$file": $error};
@ -10245,6 +10298,7 @@ sub _addHourAiInstance {
$paref->{rho} = $rho;
aiAddInstance ($paref); # AI Instanz hinzufügen
aiAddRawData ($paref);
delete $paref->{ood};
delete $paref->{rho};
@ -10502,16 +10556,18 @@ sub aiAddInstance { ## no critic "not used"
return if(!isDWDUsed ($hash));
my $err;
my $dtree = AiDetreeVal ($hash, undef);
my $dtree = AiDetreeVal ($hash, 'object', undef);
if (!$dtree) {
($err, $dtree) = aiInit ($paref);
$err = aiInit ($paref);
return if($err);
$dtree = AiDetreeVal ($hash, 'object', undef);
}
my ($pvrl, $temp, $wcc, $wrp, $rad1h);
for my $pvd (sort keys %{$data{$type}{$name}{pvhist}}) {
next if(!$pvd);
if ($ood) {
next if($pvd ne $paref->{day});
}
@ -10547,10 +10603,11 @@ sub aiAddInstance { ## no critic "not used"
return;
};
$data{$type}{$name}{current}{aiaddistate} = 'ok';
debugLog ($paref, 'aiProcess', qq{AI Instance added - day: $pvd, hod: $hod, rad1h: $rad1h, pvrl: $pvrl, wcc: $cbin, wrp: $rbin, temp: $tbin});
}
$data{$type}{$name}{aidectree}{object} = $dtree;
$data{$type}{$name}{current}{aiaddistate} = 'ok';
}
if ($taa) {
@ -10572,11 +10629,12 @@ sub aiTrain { ## no critic "not used"
return if(!isDWDUsed ($hash));
my $err;
my $dtree = AiDetreeVal ($hash, undef);
my $dtree = AiDetreeVal ($hash, 'object', undef);
if (!$dtree) {
($err, $dtree) = aiInit ($paref);
$err = aiInit ($paref);
return if($err);
$dtree = AiDetreeVal ($hash, 'object', undef);
}
eval { $dtree->train
@ -10586,12 +10644,12 @@ sub aiTrain { ## no critic "not used"
return;
};
debugLog ($paref, 'aiData', qq{AI trained: }.Dumper $data{$type}{$name}{aidectree});
$err = writeCacheToFile ($hash, 'aidectree', $aicache.$name); # AI Cache schreiben
$data{$type}{$name}{aidectree}{aitrained} = $dtree;
$err = writeCacheToFile ($hash, 'aitrained', $aitrained.$name); # AI Cache schreiben
if (!$err) {
debugLog ($paref, 'aiProcess', qq{AI trained and saved data into file: }.$aicache.$name);
debugLog ($paref, 'aiData', qq{AI trained: }.Dumper $data{$type}{$name}{aidectree}{aitrained});
debugLog ($paref, 'aiProcess', qq{AI trained and saved data into file: }.$aitrained.$name);
debugLog ($paref, 'aiProcess', qq{Training instances and their associated information where purged from the AI object});
$data{$type}{$name}{current}{aitrainstate} = 'ok';
}
@ -10616,11 +10674,10 @@ sub aiGetResult { ## no critic "not used"
return $err if(!isDWDUsed ($hash) || !$hod || !$nhidx);
my $dtree = AiDetreeVal ($hash, undef);
my $dtree = AiDetreeVal ($hash, 'aitrained', undef);
if (!$dtree) {
($err, $dtree) = aiInit ($paref);
return $err if($err);
return 'AI trained object is missed';
}
my $rad1h = NexthoursVal ($hash, $nhidx, "rad1h", undef);
@ -10679,12 +10736,84 @@ sub aiInit { ## no critic "not used"
my $dtree = new AI::DecisionTree ( verbose => 0, noise_mode => 'pick_best' );
$data{$type}{$name}{aidectree} = $dtree;
$data{$type}{$name}{aidectree}{object} = $dtree;
$data{$type}{$name}{current}{aiinitstate} = 'ok';
Log3 ($name, 3, "$name - AI::DecisionTree new initialized");
return ($err, $dtree);
return $err;
}
################################################################
# den Index für AI raw Daten erzeugen
################################################################
sub aiMakeIdxRaw {
my $t = time;
my $day = shift // strftime "%d", localtime($t);
my $hod = shift;
my $ridx = strftime "%Y%m", localtime($t);
$ridx .= $day.$hod;
return $ridx;
}
################################################################
# Daten in die Raw Datensammlung hinzufügen
################################################################
sub aiAddRawData { ## no critic "not used"
my $paref = shift;
my $hash = $paref->{hash};
my $name = $paref->{name};
my $type = $paref->{type};
my $ood = $paref->{ood} // 0; # only one (cuurent) day
my $rho = $paref->{rho}; # only this hour of day
my ($pvrl, $temp, $wcc, $wrp, $rad1h);
for my $pvd (sort keys %{$data{$type}{$name}{pvhist}}) {
next if(!$pvd);
if ($ood) {
next if($pvd ne $paref->{day});
}
for my $hod (sort keys %{$data{$type}{$name}{pvhist}{$pvd}}) {
next if(!$hod || $hod eq '99' || ($rho && $hod ne $rho));
my $ridx = aiMakeIdxRaw ($pvd, $hod);
$pvrl = HistoryVal ($hash, $pvd, $hod, 'pvrl', undef);
next if(!$pvrl);
$rad1h = HistoryVal ($hash, $pvd, $hod, 'rad1h', undef);
next if(!$rad1h);
$temp = HistoryVal ($hash, $pvd, $hod, 'temp', 20);
$wcc = HistoryVal ($hash, $pvd, $hod, 'wcc', 0);
$wrp = HistoryVal ($hash, $pvd, $hod, 'wrp', 0);
my $tbin = temp2bin ($temp);
my $cbin = cloud2bin ($wcc);
my $rbin = rain2bin ($wrp);
$data{$type}{$name}{aidectree}{airaw}{$ridx}{rad1h} = $rad1h;
$data{$type}{$name}{aidectree}{airaw}{$ridx}{temp} = $tbin;
$data{$type}{$name}{aidectree}{airaw}{$ridx}{wcc} = $cbin;
$data{$type}{$name}{aidectree}{airaw}{$ridx}{wrp} = $rbin;
$data{$type}{$name}{aidectree}{airaw}{$ridx}{hod} = $hod;
$data{$type}{$name}{aidectree}{airaw}{$ridx}{pvrl} = $pvrl;
debugLog ($paref, 'aiProcess', qq{AI Raw data added - idx: $ridx, day: $pvd, hod: $hod, rad1h: $rad1h, pvrl: $pvrl, wcc: $cbin, wrp: $rbin, temp: $tbin});
}
}
my $err = writeCacheToFile ($hash, 'airaw', $airaw.$name);
if (!$err) {
debugLog ($paref, 'aiProcess', qq{AI raw data saved into file: }.$airaw.$name);
}
return;
}
################################################################
@ -11325,6 +11454,24 @@ sub listDataPool {
}
}
}
if ($htol eq "aiRawData") {
$h = $data{$type}{$name}{aidectree}{airaw};
if (!keys %{$h}) {
return qq{aiRawData values cache is empty.};
}
for my $idx (sort keys %{$h}) {
my $hod = AiRawdataVal ($hash, $idx, 'hod', "-");
my $rad1h = AiRawdataVal ($hash, $idx, 'rad1h', "-");
my $wcc = AiRawdataVal ($hash, $idx, 'wcc', "-");
my $wrp = AiRawdataVal ($hash, $idx, 'wrp', "-");
my $pvrl = AiRawdataVal ($hash, $idx, 'pvrl', "-");
my $temp = AiRawdataVal ($hash, $idx, 'temp', "-");
$sq .= "\n" if($sq);
$sq .= "$idx => hod: $hod, rad1h: $rad1h, wcc: $wcc, wrp: $wrp, pvrl: $pvrl, temp: $temp";
}
}
return $sq;
}
@ -13121,20 +13268,67 @@ return $def;
###################################################################################################
# Wert AI::DecisionTree Objects zurückliefern
# Usage:
# AiDetreeVal ($hash, $def)
# AiDetreeVal ($hash, key, $def)
#
# key: object - das AI Object
# aitrained - AI trainierte Daten
# airaw - Rohdaten für AI Input = Raw Trainigsdaten
#
# $def: Defaultwert
#
###################################################################################################
sub AiDetreeVal {
my $hash = shift;
my $key = shift;
my $def = shift;
my $name = $hash->{NAME};
my $type = $hash->{TYPE};
if (defined $data{$type}{$name}{aidectree}) {
return $data{$type}{$name}{aidectree};
if (defined $data{$type}{$name}{aidectree} &&
defined $data{$type}{$name}{aidectree}{$key}) {
return $data{$type}{$name}{aidectree}{$key};
}
return $def;
}
###################################################################################################
# Wert AI Raw Data zurückliefern
# Usage:
# AiRawdataVal ($hash, $idx, $key, $def)
# AiRawdataVal ($hash, '', '', $def) -> den gesamten Hash airaw lesen
#
# $idx: - Index
# $key: rad1h - Strahlungsdaten
# temp - Temeperatur als Bin
# wcc - Bewölkung als Bin
# wrp - Regenwert als Bin
# hod - Stunde des Tages
# pvrl - reale PV Erzeugung
#
# $def: Defaultwert
#
###################################################################################################
sub AiRawdataVal {
my $hash = shift;
my $idx = shift;
my $key = shift;
my $def = shift;
my $name = $hash->{NAME};
my $type = $hash->{TYPE};
if (!$idx && !$key) {
if (defined $data{$type}{$name}{aidectree}{airaw}) {
return $data{$type}{$name}{aidectree}{airaw};
}
}
if (defined $data{$type}{$name}{aidectree}{airaw} &&
defined $data{$type}{$name}{aidectree}{airaw}{$idx} &&
defined $data{$type}{$name}{aidectree}{airaw}{$idx}{$key}) {
return $data{$type}{$name}{aidectree}{airaw}{$idx}{$key};
}
return $def;