diff --git a/fhem/contrib/MadMax/00_DecisionTree.pm b/fhem/contrib/MadMax/00_DecisionTree.pm new file mode 100644 index 000000000..fb5027cef --- /dev/null +++ b/fhem/contrib/MadMax/00_DecisionTree.pm @@ -0,0 +1,1171 @@ +######################################################################################################################## +# $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 . +# +######################################################################################################################### +# +# 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 = "Number of rules: ".$l.""; + $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 + + +

DecisionTree

+
+ +=end html +=begin html_DE + + +

DecisionTree

+
+ + +=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 " + ], + "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