2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-01-31 12:49:34 +00:00
fhem-mirror/fhem/contrib/MadMax/00_DecisionTree.pm
MadMax 0c220e8598 00_DecisionTree.pm:test
git-svn-id: https://svn.fhem.de/fhem/trunk@28052 2b470e98-0d58-463d-a4d8-8e2adae1ed80
2023-10-14 15:56:35 +00:00

1172 lines
36 KiB
Perl

########################################################################################################################
# $Id$
#########################################################################################################################
# 00_DecisionTree.pm
#
# (c) 2023
#
# This script is part of fhem.
#
# Fhem is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# Fhem is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with fhem. If not, see <http://www.gnu.org/licenses/>.
#
#########################################################################################################################
#
# Leerzeichen entfernen: sed -i 's/[[:space:]]*$//' 00_DecisionTree.pm
#
#########################################################################################################################
package FHEM::DecisionTree; ## no critic 'package'
use strict;
use warnings;
use POSIX;
use GPUtils qw(GP_Import GP_Export); # wird für den Import der FHEM Funktionen aus der fhem.pl benötigt
use Time::HiRes qw(gettimeofday tv_interval);
eval "use FHEM::Meta;1" or my $modMetaAbsent = 1; ## no critic 'eval'
eval "use FHEM::Utility::CTZ qw(:all);1;" or my $ctzAbsent = 1; ## no critic 'eval'
use Encode;
use Color;
use utf8;
use HttpUtils;
eval "use JSON;1;" or my $jsonabs = 'JSON'; ## no critic 'eval' # Debian: sudo apt-get install libjson-perl
eval "use Algorithm::DecisionTree;1;" or my $aidtabs = 'Algorithm::DecisionTree'; ## no critic 'eval'
use FHEM::SynoModules::SMUtils qw(
evaljson
getClHash
delClHash
moduleVersion
trim
); # Hilfsroutinen Modul
use Data::Dumper;
use Blocking;
use Storable qw(dclone freeze thaw nstore store retrieve);
use MIME::Base64;
no if $] >= 5.017011, warnings => 'experimental::smartmatch';
# Run before module compilation
BEGIN {
# Import from main::
GP_Import(
qw(
attr
asyncOutput
AnalyzePerlCommand
AnalyzeCommandChain
AttrVal
AttrNum
BlockingCall
BlockingKill
CommandAttr
CommandGet
CommandSet
CommandSetReading
data
defs
delFromDevAttrList
delFromAttrList
devspec2array
deviceEvents
DoTrigger
Debug
fhemTimeLocal
fhemTimeGm
fhem
FileWrite
FileRead
FileDelete
FmtTime
FmtDateTime
FW_makeImage
getKeyValue
HttpUtils_NonblockingGet
init_done
InternalTimer
IsDisabled
Log
Log3
modules
parseParams
readingsSingleUpdate
readingsBulkUpdate
readingsBulkUpdateIfChanged
readingsBeginUpdate
readingsDelete
readingsEndUpdate
ReadingsNum
ReadingsTimestamp
ReadingsVal
RemoveInternalTimer
readingFnAttributes
setKeyValue
sortTopicNum
FW_cmd
FW_directNotify
FW_ME
FW_subdir
FW_room
FW_detail
FW_wname
)
);
# Export to main context with different name
# my $pkg = caller(0);
# my $main = $pkg;
# $main =~ s/^(?:.+::)?([^:]+)$/main::$1\_/g;
# foreach (@_) {
# *{ $main . $_ } = *{ $pkg . '::' . $_ };
# }
GP_Export(
qw(
Initialize
pageAsHtml
NexthoursVal
)
);
}
# Versions History intern
my %vNotesIntern = (
"0.1.0" => "14.10.2023 initial Version "
);
## Konstanten
###############
my $aitrained = $attr{global}{modpath}."/FHEM/FhemUtils/DecisionTree_tra_"; # Filename-Fragment für AI Trainingsdaten (wird mit Devicename ergänzt)
my $airaw = $attr{global}{modpath}."/FHEM/FhemUtils/DecisionTree_raw_"; # Filename-Fragment für AI Input Daten = Raw Trainigsdaten
my $aitrblto = 7200; # KI Training BlockingCall Timeout
my $aibcthhld = 0.2; # Schwelle der KI Trainigszeit ab der BlockingCall benutzt wird
my $aistdudef = 1095; # default Haltezeit KI Raw Daten (Tage)
################################################################
# Init Fn
################################################################
sub Initialize {
my $hash = shift;
my $fwd = join ",", devspec2array("TYPE=FHEMWEB:FILTER=STATE=Initialized");
$hash->{DefFn} = \&Define;
$hash->{UndefFn} = \&Undef;
$hash->{GetFn} = \&Get;
$hash->{SetFn} = \&Set;
$hash->{DeleteFn} = \&Delete;
$hash->{FW_summaryFn} = \&FwFn;
$hash->{FW_detailFn} = \&FwFn;
$hash->{ShutdownFn} = \&Shutdown;
$hash->{DbLog_splitFn} = \&DbLogSplit;
$hash->{AttrFn} = \&Attr;
$hash->{NotifyFn} = \&Notify;
$hash->{AttrList} = "".
$readingFnAttributes;
# $hash->{AttrRenameMap} = { ""
# };
eval { FHEM::Meta::InitMod( __FILE__, $hash ) }; ## no critic 'eval'
return;
}
###############################################################
# DecisionTree Define
###############################################################
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);
return "Error: Perl module ".$aidtabs." is missing. Install it on Debian with: cpanm Algorithm::DecisionTree" if($aidtabs);
# my $name = $hash->{NAME};
# my $type = $hash->{TYPE};
# $hash->{HELPER}{MODMETAABSENT} = 1 if($modMetaAbsent); # Modul Meta.pm nicht vorhanden
# my $params = {
# hash => $hash,
# name => $hash->{NAME},
# type => $hash->{TYPE},
# notes => \%vNotesIntern,
# };
# $params->{file} = $aitrained.$name; # AI Cache File einlesen wenn vorhanden
# $params->{cachename} = 'aitrained';
# _readCacheFile ($params);
# $params->{file} = $airaw.$name; # AI Rawdaten File einlesen wenn vorhanden
# $params->{cachename} = 'airaw';
# _readCacheFile ($params);
return;
}
################################################################
# Cachefile lesen
################################################################
sub _readCacheFile {
my $paref = shift;
my $hash = $paref->{hash};
my $name = $paref->{name};
my $type = $paref->{type};
my $file = $paref->{file};
my $cachename = $paref->{cachename};
if ($cachename eq 'aitrained') {
my ($err, $dtree) = fileRetrieve ($file);
if (!$err && $dtree) {
my $valid = $dtree->isa('AI::DecisionTree');
if ($valid) {
$data{$type}{$name}{aidectree}{aitrained} = $dtree;
$data{$type}{$name}{current}{aitrainstate} = 'ok';
Log3($name, 3, qq{$name - cached data "$cachename" restored});
}
}
return;
}
if ($cachename eq 'airaw') {
my ($err, $data) = fileRetrieve ($file);
if (!$err && $data) {
$data{$type}{$name}{aidectree}{airaw} = $data;
$data{$type}{$name}{current}{aitrawstate} = 'ok';
Log3($name, 3, qq{$name - cached data "$cachename" restored});
}
return;
}
my ($error, @content) = FileRead ($file);
if(!$error) {
my $json = join "", @content;
my ($success) = evaljson ($hash, $json);
if($success) {
$data{$hash->{TYPE}}{$name}{$cachename} = decode_json ($json);
Log3($name, 3, qq{$name - cached data "$cachename" restored});
}
else {
Log3($name, 2, qq{$name - WARNING - The content of file "$file" is not readable and may be corrupt});
}
}
return;
}
################################################################
# Funktion um mit Storable eine Struktur in ein File
# zu schreiben
################################################################
sub fileStore {
my $obj = shift;
my $file = shift;
my $err;
my $ret = eval { nstore ($obj, $file) };
if (!$ret || $@) {
$err = $@ ? $@ : 'I/O problems or other internal error';
}
return $err;
}
################################################################
# Funktion um mit Storable eine Struktur aus einem File
# zu lesen
################################################################
sub fileRetrieve {
my $file = shift;
my ($err, $obj);
if (-e $file) {
eval { $obj = retrieve ($file) };
if (!$obj || $@) {
$err = $@ ? $@ : 'I/O error while reading';
}
}
return ($err, $obj);
}
###############################################################
# DecisionTree Set
###############################################################
sub Set {
my ($hash, @a) = @_;
return "\"set X\" needs at least an argument" if ( @a < 2 );
my $name = shift @a;
my $opt = shift @a;
my @args = @a;
my $arg = join " ", map { my $p = $_; $p =~ s/\s//xg; $p; } @a; ## no critic 'Map blocks'
my $prop = shift @a;
my $prop1 = shift @a;
my $prop2 = shift @a;
return if(IsDisabled($name));
my ($setlist);
$setlist .= "aiDecTree:addInstances,addRawData,train ";
return "$setlist";
}
###############################################################
# 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');
}
if($arg eq 'aiRuleStrings') {
$ret = __getaiRuleStrings ($hash);
}
$ret .= lineFromSpaces ($ret, 5);
return $ret;
}
################################################################
# Gibt eine Liste von Zeichenketten zurück, die den AI
# Entscheidungsbaum in Form von Regeln beschreiben
################################################################
sub __getaiRuleStrings { ## no critic "not used"
my $hash = shift;
return 'the AI usage is not prepared' if(!isPrepared4AI ($hash));
my $dtree = AiDetreeVal ($hash, 'aitrained', undef);
if (!$dtree) {
return 'AI trained object is missed';
}
my $rs = 'no rules delivered';
my @rsl;
eval { @rsl = $dtree->rule_statements()
}
or do { return $@;
};
if (@rsl) {
my $l = scalar @rsl;
$rs = "<b>Number of rules: ".$l."</b>";
$rs .= "\n\n";
$rs .= join "\n", @rsl;
}
return $rs;
}
################################################################
sub Attr {
my $cmd = shift;
my $name = shift;
my $aName = shift;
my $aVal = shift;
my $hash = $defs{$name};
my ($do,$val);
# $cmd can be "del" or "set"
# $name is device name
# aName and aVal are Attribute name and value
if($aName eq 'disable') {
if($cmd eq 'set') {
$do = $aVal ? 1 : 0;
}
$do = 0 if($cmd eq 'del');
$val = ($do == 1 ? 'disabled' : 'initialized');
singleUpdateState ( {hash => $hash, state => $val, evt => 1} );
}
my $params = {
hash => $hash,
name => $name,
type => $hash->{TYPE},
cmd => $cmd,
aName => $aName,
aVal => $aVal
};
return;
}
################################################################
# Daten in File wegschreiben
################################################################
sub writeCacheToFile {
my $hash = shift;
my $cachename = shift;
my $file = shift;
my $name = $hash->{NAME};
my $type = $hash->{TYPE};
my @data;
my ($error, $err, $lw);
if ($cachename eq 'aitrained') {
my $dtree = AiDetreeVal ($hash, 'aitrained', '');
return if(ref $dtree ne 'AI::DecisionTree');
$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};
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 'plantconfig') {
@data = _savePlantConfig ($hash);
return 'Plant configuration is empty, no data where written' if(!@data);
}
else {
return if(!$data{$type}{$name}{$cachename});
my $json = encode_json ($data{$type}{$name}{$cachename});
push @data, $json;
}
$error = FileWrite ($file, @data);
if ($error) {
$err = qq{ERROR writing cache 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;
}
################################################################
# Voraussetzungen zur Nutzung der KI prüfen, Status setzen
# und Prüfungsergebnis (0/1) zurückgeben
################################################################
sub isPrepared4AI {
my $hash = shift;
my $full = shift // q{}; # wenn true -> auch Auswertung ob on_.*_ai gesetzt ist
my $name = $hash->{NAME};
my $type = $hash->{TYPE};
my $acu = isAutoCorrUsed ($name);
my $err;
if(!isDWDUsed ($hash)) {
$err = qq(The selected DecisionTree model cannot use AI support);
}
elsif ($aidtabs) {
$err = qq(The Perl module AI::DecisionTree is missing. Please install it with e.g. "sudo apt-get install libai-decisiontree-perl" for AI support);
}
elsif ($full && $acu !~ /ai/xs) {
$err = 'The setting of pvCorrectionFactor_Auto does not contain AI support';
}
if ($err) {
$data{$type}{$name}{current}{aicanuse} = $err;
return 0;
}
$data{$type}{$name}{current}{aicanuse} = 'ok';
return 1;
}
###################################################################################################
# Wert AI::DecisionTree Objects zurückliefern
# Usage:
# 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} &&
defined $data{$type}{$name}{aidectree}{$key}) {
return $data{$type}{$name}{aidectree}{$key};
}
return $def;
}
################################################################
# AI Instanz für die abgeschlossene Stunde hinzufügen
################################################################
sub _addHourAiRawdata {
my $paref = shift;
my $hash = $paref->{hash};
my $name = $paref->{name};
my $chour = $paref->{chour};
my $daref = $paref->{daref};
for my $h (1..23) {
next if(!$chour || $h > $chour);
my $rho = sprintf "%02d", $h;
my $sr = ReadingsVal ($name, ".signaldone_".$rho, "");
next if($sr eq "done");
$paref->{ood} = 1;
$paref->{rho} = $rho;
aiAddRawData ($paref); # Raw Daten für AI hinzufügen und sichern
delete $paref->{ood};
delete $paref->{rho};
push @$daref, ".signaldone_".sprintf("%02d",$h)."<>done";
}
return;
}
###############################################################
# Eintritt in den KI Train Prozess normal/Blocking
###############################################################
sub manageTrain {
my $paref = shift;
my $hash = $paref->{hash};
my $name = $paref->{name};
delete $hash->{HELPER}{AIBLOCKRUNNING} if(defined $hash->{HELPER}{AIBLOCKRUNNING}{pid} && $hash->{HELPER}{AIBLOCKRUNNING}{pid} =~ /DEAD/xs);
if (defined $hash->{HELPER}{AIBLOCKRUNNING}{pid}) {
Log3 ($name, 3, qq{$name - another AI Training with PID "$hash->{HELPER}{AIBLOCKRUNNING}{pid}" is already running ... start Training aborted});
return;
}
$paref->{block} = 1;
$hash->{HELPER}{AIBLOCKRUNNING} = BlockingCall ( "FHEM::DecisionTree::aiTrain",
$paref,
"FHEM::DecisionTree::finishTrain",
$aitrblto,
"FHEM::DecisionTree::abortTrain",
$hash
);
if (defined $hash->{HELPER}{AIBLOCKRUNNING}) {
$hash->{HELPER}{AIBLOCKRUNNING}{loglevel} = 3; # Forum https://forum.fhem.de/index.php/topic,77057.msg689918.html#msg689918
debugLog ($paref, 'aiProcess', qq{AI Training BlockingCall PID "$hash->{HELPER}{AIBLOCKRUNNING}{pid}" with Timeout "$aitrblto" started});
}
return;
}
###############################################################
# Restaufgaben nach Update
###############################################################
sub finishTrain {
my $serial = decode_base64 (shift);
my $paref = eval { thaw ($serial) }; # Deserialisierung
my $name = $paref->{name};
my $hash = $defs{$name};
my $type = $hash->{TYPE};
delete($hash->{HELPER}{AIBLOCKRUNNING}) if(defined $hash->{HELPER}{AIBLOCKRUNNING});
my $aicanuse = $paref->{aicanuse};
my $aiinitstate = $paref->{aiinitstate};
my $aitrainstate = $paref->{aitrainstate};
my $runTimeTrainAI = $paref->{runTimeTrainAI};
$data{$type}{$name}{current}{aicanuse} = $aicanuse if(defined $aicanuse);
$data{$type}{$name}{current}{aiinitstate} = $aiinitstate if(defined $aiinitstate);
$data{$type}{$name}{circular}{99}{runTimeTrainAI} = $runTimeTrainAI if(defined $runTimeTrainAI); # !! in Circular speichern um zu persistieren, setTimeTracking speichert zunächst in Current !!
if ($aitrainstate eq 'ok') {
_readCacheFile ({ hash => $hash,
name => $name,
type => $type,
file => $aitrained.$name,
cachename => 'aitrained'
}
);
}
return;
}
####################################################################################################
# Abbruchroutine BlockingCall Timeout
####################################################################################################
sub abortTrain {
my $hash = shift;
my $cause = shift // "Timeout: process terminated";
my $name = $hash->{NAME};
my $type = $hash->{TYPE};
Log3 ($name, 1, "$name -> BlockingCall $hash->{HELPER}{AIBLOCKRUNNING}{fn} pid:$hash->{HELPER}{AIBLOCKRUNNING}{pid} aborted: $cause");
delete($hash->{HELPER}{AIBLOCKRUNNING});
$data{$type}{$name}{current}{aitrainstate} = 'Traing (Child) process timed out';
return;
}
################################################################
# KI Instanz(en) aus Raw Daten Hash
# $data{$type}{$name}{aidectree}{airaw} hinzufügen
################################################################
sub aiAddInstance { ## no critic "not used"
my $paref = shift;
my $hash = $paref->{hash};
my $name = $paref->{name};
my $type = $paref->{type};
my $taa = $paref->{taa}; # do train after add
return if(!isPrepared4AI ($hash));
my $err;
my $dtree = AiDetreeVal ($hash, 'object', undef);
if (!$dtree) {
$err = aiInit ($paref);
return if($err);
$dtree = AiDetreeVal ($hash, 'object', undef);
}
for my $idx (sort keys %{$data{$type}{$name}{aidectree}{airaw}}) {
next if(!$idx);
my $pvrl = AiRawdataVal ($hash, $idx, 'pvrl', undef);
next if(!defined $pvrl);
my $hod = AiRawdataVal ($hash, $idx, 'hod', undef);
next if(!defined $hod);
my $rad1h = AiRawdataVal ($hash, $idx, 'rad1h', 0);
next if($rad1h <= 0);
my $temp = AiRawdataVal ($hash, $idx, 'temp', 20);
my $wcc = AiRawdataVal ($hash, $idx, 'wcc', 0);
my $wrp = AiRawdataVal ($hash, $idx, 'wrp', 0);
eval { $dtree->add_instance (attributes => { rad1h => $rad1h,
temp => $temp,
wcc => $wcc,
wrp => $wrp,
hod => $hod
},
result => $pvrl
)
}
or do { Log3 ($name, 1, "$name - aiAddInstance ERROR: $@");
$data{$type}{$name}{current}{aiaddistate} = $@;
return;
};
debugLog ($paref, 'aiProcess', qq{AI Instance added - hod: $hod, rad1h: $rad1h, pvrl: $pvrl, wcc: $wcc, wrp: $wrp, temp: $temp});
}
$data{$type}{$name}{aidectree}{object} = $dtree;
$data{$type}{$name}{current}{aiaddistate} = 'ok';
if ($taa) {
manageTrain ($paref);
}
return;
}
################################################################
# KI trainieren
################################################################
sub aiTrain { ## no critic "not used"
my $paref = shift;
my $hash = $paref->{hash};
my $name = $paref->{name};
my $type = $paref->{type};
my $block = $paref->{block} // 0;
my $serial;
if (!isPrepared4AI ($hash)) {
my $err = CurrentVal ($hash, 'aicanuse', '');
$serial = encode_base64 (Serialize ( {name => $name, aicanuse => $err} ), "");
$block ? return ($serial) : return \&finishTrain ($serial);
}
my $cst = [gettimeofday]; # Zyklus-Startzeit
my $err;
my $dtree = AiDetreeVal ($hash, 'object', undef);
if (!$dtree) {
$err = aiInit ($paref);
if ($err) {
$serial = encode_base64 (Serialize ( {name => $name, aiinitstate => $err} ), "");
$block ? return ($serial) : return \&finishTrain ($serial);
}
$dtree = AiDetreeVal ($hash, 'object', undef);
}
eval { $dtree->train
}
or do { Log3 ($name, 1, "$name - aiTrain ERROR: $@");
$data{$type}{$name}{current}{aitrainstate} = $@;
$serial = encode_base64 (Serialize ( {name => $name, aitrainstate => $@} ), "");
$block ? return ($serial) : return \&finishTrain ($serial);
};
$data{$type}{$name}{aidectree}{aitrained} = $dtree;
$err = writeCacheToFile ($hash, 'aitrained', $aitrained.$name);
if (!$err) {
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';
}
setTimeTracking ($hash, $cst, 'runTimeTrainAI'); # Zyklus-Laufzeit ermitteln
$serial = encode_base64 (Serialize ( {name => $name,
aitrainstate => CurrentVal ($hash, 'aitrainstate', ''),
runTimeTrainAI => CurrentVal ($hash, 'runTimeTrainAI', '')
}
)
, "");
delete $data{$type}{$name}{current}{runTimeTrainAI};
$block ? return ($serial) : return \&finishTrain ($serial);
return;
}
################################################################
# AI Ergebnis für ermitteln
################################################################
sub aiGetResult { ## no critic "not used"
my $paref = shift;
my $hash = $paref->{hash};
my $name = $paref->{name};
my $type = $paref->{type};
my $hod = $paref->{hod};
my $nhidx = $paref->{nhidx};
return 'the AI usage is not prepared' if(!isPrepared4AI ($hash, 'full'));
my $dtree = AiDetreeVal ($hash, 'aitrained', undef);
if (!$dtree) {
return 'AI trained object is missed';
}
my $rad1h = NexthoursVal ($hash, $nhidx, "rad1h", 0);
return "no rad1h for hod: $hod" if($rad1h <= 0);
my $wcc = NexthoursVal ($hash, $nhidx, "cloudcover", 0);
my $wrp = NexthoursVal ($hash, $nhidx, "rainprob", 0);
my $temp = NexthoursVal ($hash, $nhidx, "temp", 20);
my $tbin = temp2bin ($temp);
my $cbin = cloud2bin ($wcc);
my $rbin = rain2bin ($wrp);
my $pvaifc;
eval { $pvaifc = $dtree->get_result (attributes => { rad1h => $rad1h,
temp => $tbin,
wcc => $cbin,
wrp => $rbin,
hod => $hod
}
);
};
if ($@) {
Log3 ($name, 1, "$name - aiGetResult ERROR: $@");
return $@;
}
if (defined $pvaifc) {
debugLog ($paref, 'aiData', qq{result AI: pvaifc: $pvaifc (hod: $hod, rad1h: $rad1h, wcc: $wcc, wrp: $rbin, temp: $tbin)});
return ('', $pvaifc);
}
return 'no decition delivered';
}
################################################################
# KI initialisieren
################################################################
sub aiInit { ## no critic "not used"
my $paref = shift;
my $hash = $paref->{hash};
my $name = $paref->{name};
my $type = $paref->{type};
if (!isPrepared4AI ($hash)) {
my $err = CurrentVal ($hash, 'aicanuse', '');
debugLog ($paref, 'aiProcess', $err);
$data{$type}{$name}{current}{aiinitstate} = $err;
return $err;
}
my $dtree = new AI::DecisionTree ( verbose => 0, noise_mode => 'pick_best' );
$data{$type}{$name}{aidectree}{object} = $dtree;
$data{$type}{$name}{current}{aiinitstate} = 'ok';
Log3 ($name, 3, "$name - AI::DecisionTree initialized");
return;
}
################################################################
# Daten der 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 (current) day
my $rho = $paref->{rho}; # only this hour of day
delete $data{$type}{$name}{current}{aitrawstate};
my ($err, $dosave);
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 $rad1h = HistoryVal ($hash, $pvd, $hod, 'rad1h', undef);
next if(!$rad1h || $rad1h <= 0);
my $pvrl = HistoryVal ($hash, $pvd, $hod, 'pvrl', undef);
next if(!$pvrl || $pvrl <= 0);
my $ridx = _aiMakeIdxRaw ($pvd, $hod);
my $temp = HistoryVal ($hash, $pvd, $hod, 'temp', 20);
my $wcc = HistoryVal ($hash, $pvd, $hod, 'wcc', 0);
my $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;
$dosave = 1;
debugLog ($paref, 'aiProcess', qq{AI Raw data added - idx: $ridx, day: $pvd, hod: $hod, rad1h: $rad1h, pvrl: $pvrl, wcc: $cbin, wrp: $rbin, temp: $tbin});
}
}
if ($dosave) {
$err = writeCacheToFile ($hash, 'airaw', $airaw.$name);
if (!$err) {
$data{$type}{$name}{current}{aitrawstate} = 'ok';
debugLog ($paref, 'aiProcess', qq{AI raw data saved into file: }.$airaw.$name);
}
}
return;
}
################################################################
# Daten aus Raw Datensammlung löschen welche die maximale
# Haltezeit (Tage) überschritten haben
################################################################
sub aiDelRawData {
my $paref = shift;
my $hash = $paref->{hash};
my $name = $paref->{name};
my $type = $paref->{type};
if (!keys %{$data{$type}{$name}{aidectree}{airaw}}) {
return;
}
my $hd = AttrVal ($name, 'ctrlAIdataStorageDuration', $aistdudef); # Haltezeit KI Raw Daten (Tage)
my $ht = time - ($hd * 86400);
my $day = strftime "%d", localtime($ht);
my $didx = _aiMakeIdxRaw ($day, '00', $ht); # Daten mit idx <= $didx löschen
debugLog ($paref, 'aiProcess', qq{AI Raw delete data equal or less than index >$didx<});
delete $data{$type}{$name}{current}{aitrawstate};
my ($err, $dosave);
for my $idx (sort keys %{$data{$type}{$name}{aidectree}{airaw}}) {
next if(!$idx || $idx > $didx);
delete $data{$type}{$name}{aidectree}{airaw}{$idx};
$dosave = 1;
debugLog ($paref, 'aiProcess', qq{AI Raw data deleted - idx: $idx});
}
if ($dosave) {
$err = writeCacheToFile ($hash, 'airaw', $airaw.$name);
if (!$err) {
$data{$type}{$name}{current}{aitrawstate} = 'ok';
debugLog ($paref, 'aiProcess', qq{AI raw data saved into file: }.$airaw.$name);
}
}
return;
}
################################################################
# den Index für AI raw Daten erzeugen
################################################################
sub _aiMakeIdxRaw {
my $day = shift;
my $hod = shift;
my $t = shift // time;
my $ridx = strftime "%Y%m", localtime($t);
$ridx .= $day.$hod;
return $ridx;
}
###################################################################################################
# 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;
}
1;
=pod
=item summary Visualization of solar predictions for PV systems and Consumer control
=item summary_DE Visualisierung von solaren Vorhersagen für PV Anlagen und Verbrauchersteuerung
=begin html
<a id="DecisionTree"></a>
<h3>DecisionTree</h3>
<br>
=end html
=begin html_DE
<a id="DecisionTree"></a>
<h3>DecisionTree</h3>
<br>
=end html_DE
=for :application/json;q=META.json 76_SolarForecast.pm
{
"abstract": "Creation of solar forecasts of PV systems including consumption forecasts and consumer management",
"x_lang": {
"de": {
"abstract": "Erstellung solarer Vorhersagen von PV Anlagen inklusive Verbrauchsvorhersagen und Verbrauchermanagement"
}
},
"keywords": [
"inverter",
"photovoltaik",
"electricity",
"forecast",
"graphics",
"Autarky",
"Consumer",
"PV"
],
"version": "v1.1.1",
"release_status": "testing",
"author": [
"Heiko Maaz <heiko.maaz@t-online.de>"
],
"x_fhem_maintainer": [
"DS_Starter"
],
"x_fhem_maintainer_github": [
"nasseeder1"
],
"prereqs": {
"runtime": {
"requires": {
"FHEM": 5.00918799,
"perl": 5.014,
"POSIX": 0,
"GPUtils": 0,
"Encode": 0,
"Blocking": 0,
"Color": 0,
"utf8": 0,
"HttpUtils": 0,
"JSON": 4.020,
"FHEM::SynoModules::SMUtils": 1.0220,
"Time::HiRes": 0,
"MIME::Base64": 0,
"Storable": 0
},
"recommends": {
"FHEM::Meta": 0,
"FHEM::Utility::CTZ": 1.00,
"DateTime": 0,
"DateTime::Format::Strptime": 0,
"AI::DecisionTree": 0,
"Data::Dumper": 0
},
"suggests": {
}
}
},
"resources": {
"x_wiki": {
"web": "https://wiki.fhem.de/wiki/SolarForecast_-_Solare_Prognose_(PV_Erzeugung)_und_Verbrauchersteuerung",
"title": "DecisionTree - Solare Prognose (PV Erzeugung) und Verbrauchersteuerung"
},
"repository": {
"x_dev": {
"type": "svn",
"url": "https://svn.fhem.de/trac/browser/trunk/fhem/contrib/DS_Starter",
"web": "https://svn.fhem.de/trac/browser/trunk/fhem/contrib/DS_Starter/76_SolarForecast.pm",
"x_branch": "dev",
"x_filepath": "fhem/contrib/",
"x_raw": "https://svn.fhem.de/fhem/trunk/fhem/contrib/DS_Starter/76_SolarForecast.pm"
}
}
}
}
=end :application/json;q=META.json
=cut