diff --git a/fhem/CHANGED b/fhem/CHANGED
index 626c79a84..deca95f7e 100644
--- a/fhem/CHANGED
+++ b/fhem/CHANGED
@@ -1,5 +1,7 @@
# Add changes at the top of the list. Keep it in ASCII, and 80-char wide.
# Do not insert empty lines here, update check depends on it.
+ - feature 93_DbRep: new module added - reporting of database content
+ written by DbLog, see commandref for details
- feature: new module added: 52_I2C_SHT3x.pm (macs)
- bugfix: 70_Jabber: log OTR empty message if debug-mode == 1 only
- changed: 70_BRAVIA: re-worked state handling
diff --git a/fhem/FHEM/93_DbRep.pm b/fhem/FHEM/93_DbRep.pm
new file mode 100644
index 000000000..a4ce68a39
--- /dev/null
+++ b/fhem/FHEM/93_DbRep.pm
@@ -0,0 +1,1778 @@
+##########################################################################################################
+# $Id$
+##########################################################################################################
+# 93_DbRep.pm
+#
+# (c) 2016 by Heiko Maaz
+# e-mail: Heiko dot Maaz at t-online dot de
+#
+# This Module can be used to select and report content of databases written by 93_DbLog module
+# in different manner.
+#
+# 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 .#
+#
+###########################################################################################################
+#
+# create additional indexes for performance purposes:
+#
+# ALTER TABLE 'fhem'.'history' ADD INDEX `Reading_Time_Idx` (`READING`, `TIMESTAMP`) USING BTREE;
+#
+# Definition: define DbRep
+#
+# This module uses credentials of 93_DbLog.pm - devices
+#
+###########################################################################################################
+# Versions History:
+#
+# 3.2 11.07.2016 handling of db-errors is relocated to blockingcall-subs
+# 3.1.1 10.07.2016 state turns to initialized and connected after attr "disabled" is switched from "1" to "0"
+# 3.1 09.07.2016 new Attr "timeDiffToNow" and change subs according to that
+# 3.0 04.07.2016 no selection if timestamp isn't set and aggregation isn't set with fetchrows, delEntries
+# 2.9.9 03.07.2016 english version of commandref completed
+# 2.9.8 01.07.2016 changed fetchrows_ParseDone to handle readingvalues with whitespaces correctly
+# 2.9.7 30.06.2016 moved {DBLOGDEVICE} to {HELPER}{DBLOGDEVICE}
+# 2.9.6 30.06.2016 sql-call changed for countEntries, averageValue, sumValue avoiding
+# problems if no timestamp is set and aggregation is set
+# 2.9.5 30.06.2016 format of readingnames changed again (substitute ":" with "-" in time)
+# 2.9.4 30.06.2016 change readingmap to readingNameMap, prove of unsupported characters added
+# 2.9.3 27.06.2016 format of readingnames changed avoiding some problems after restart and splitting
+# 2.9.2 27.06.2016 use Time::Local added, DbRep_firstconnect added
+# 2.9.1 26.06.2016 german commandref added
+# 2.9 25.06.2016 attributes showproctime, timeout added
+# 2.8.1 24.06.2016 sql-creation of sumValue, maxValue, fetchrows changed
+# main-routine changed
+# 2.8 24.06.2016 function averageValue changed to nonblocking function
+# 2.7.1 24.06.2016 changed blockingcall routines, changed to unique abort-function
+# 2.7 23.06.2016 changed function countEntries to nonblocking
+# 2.6.3 22.06.2016 abort-routines changed, dbconnect-routines changed
+# 2.6.2 21.06.2016 aggregation week corrected
+# 2.6.1 20.06.2016 routine maxval_ParseDone corrected
+# 2.6 31.05.2016 maxValue changed to nonblocking function
+# 2.5.3 31.05.2016 function delEntries changed
+# 2.5.2 31.05.2016 ping check changed, DbRep_Connect changed
+# 2.5.1 30.05.2016 sleep in nb-functions deleted
+# 2.5 30.05.2016 changed to use own $dbh with DbLog-credentials, function sumValue, fetchrows
+# 2.4.2 29.05.2016 function sumValue changed
+# 2.4.1 29.05.2016 function fetchrow changed
+# 2.4 29.05.2016 changed to nonblocking function for sumValue
+# 2.3 28.05.2016 changed sumValue to "prepare" with placeholders
+# 2.2 27.05.2016 changed fetchrow and delEntries function to "prepare" with placeholders
+# added nonblocking function for delEntries
+# 2.1 25.05.2016 codechange
+# 2.0 24.05.2016 added nonblocking function for fetchrow
+# 1.2 21.05.2016 function and attribute for delEntries added
+# 1.1 20.05.2016 change result-format of "count", move runtime-counter to sub collaggstr
+# 1.0 19.05.2016 Initial
+#
+
+package main;
+
+use strict;
+use warnings;
+use POSIX qw(strftime);
+use Time::HiRes qw(gettimeofday tv_interval);
+use DBI;
+use Blocking;
+use Time::Local;
+no if $] >= 5.017011, warnings => 'experimental';
+
+###################################################################################
+# DbRep_Initialize
+###################################################################################
+sub DbRep_Initialize($) {
+ my ($hash) = @_;
+ $hash->{DefFn} = "DbRep_Define";
+ $hash->{UndefFn} = "DbRep_Undef";
+ $hash->{SetFn} = "DbRep_Set";
+ # $hash->{GetFn} = "DbRep_Get";
+ $hash->{AttrFn} = "DbRep_Attr";
+
+ $hash->{AttrList} = "disable:1,0 ".
+ "reading ".
+ "allowDeletion:1,0 ".
+ "readingNameMap ".
+ "device ".
+ "aggregation:hour,day,week,month,no ".
+ "showproctime:1,0 ".
+ "timestamp_begin ".
+ "timestamp_end ".
+ "timeDiffToNow ".
+ "timeout ".
+ $readingFnAttributes;
+
+return undef;
+}
+
+###################################################################################
+# DbRep_Define
+###################################################################################
+sub DbRep_Define {
+ # Die Define-Funktion eines Moduls wird von Fhem aufgerufen wenn der Define-Befehl für ein Gerät ausgeführt wird
+ # Welche und wie viele Parameter akzeptiert werden ist Sache dieser Funktion. Die Werte werden nach dem übergebenen Hash in ein Array aufgeteilt
+ # define DbRep
+ # ($hash) [1] [2]
+ #
+ my ($hash, $def) = @_;
+ my $name = $hash->{NAME};
+
+ my @a = split("[ \t][ \t]*", $def);
+
+ if(int(@a) < 2) {
+ return "You need to specify more parameters.\n". "Format: define DbRep ";
+ }
+
+ $hash->{HELPER}{DBLOGDEVICE} = $a[2];
+
+ RemoveInternalTimer($hash);
+ InternalTimer(time+5, 'DbRep_firstconnect', $hash, 0);
+
+ Log3 ($name, 3, "DbRep $name - initialized");
+ readingsSingleUpdate($hash, 'state', 'initialized', 1);
+
+return undef;
+}
+
+###################################################################################
+# DbRep_Set
+###################################################################################
+sub DbRep_Set {
+ my ($hash, @a) = @_;
+ return "\"set X\" needs at least an argument" if ( @a < 2 );
+ my $name = $a[0];
+ my $opt = $a[1];
+ my $dbh = $hash->{DBH};
+ my $setlist;
+
+ $setlist = "Unknown argument $opt, choose one of ".
+ "sumValue:noArg ".
+ "averageValue:noArg ".
+ "delEntries:noArg ".
+ "maxValue:noArg ".
+ "fetchrows:noArg ".
+ "countEntries:noArg ";
+
+ return if(IsDisabled($name));
+
+ if ($opt eq "sumValue") {
+ if (!AttrVal($hash->{NAME}, "reading", "")) {
+ return " The attribute reading for analyze is not set !";
+ }
+ sqlexec($hash,"sum");
+
+ } elsif ($opt eq "countEntries") {
+ sqlexec($hash,"count");
+
+ } elsif ($opt eq "averageValue") {
+ if (!AttrVal($hash->{NAME}, "reading", "")) {
+ return " The attribute reading for analyze is not set !";
+ }
+ sqlexec($hash,"average");
+
+ } elsif ($opt eq "fetchrows") {
+ sqlexec($hash,"fetchrows");
+
+ } elsif ($opt eq "maxValue") {
+ if (!AttrVal($hash->{NAME}, "reading", "")) {
+ return " The attribute reading for analyze is not set !";
+ }
+ sqlexec($hash,"max");
+
+ } elsif ($opt eq "delEntries") {
+ if (!AttrVal($hash->{NAME}, "allowDeletion", undef)) {
+ return " Set attribute 'allowDeletion' if you want to allow deletion of any database entries. Use it with care !";
+ }
+ sqlexec($hash,"del");
+
+ }
+ else
+ {
+ return "$setlist";
+ }
+return undef;
+}
+
+###################################################################################
+# DbRep_Attr
+###################################################################################
+sub DbRep_Attr {
+ my ($cmd,$name,$aName,$aVal) = @_;
+ my $hash = $defs{$name};
+ my $do;
+
+ # $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");
+ my $val = ($do == 1 ? "disabled" : "initialized");
+
+ readingsSingleUpdate($hash, "state", $val, 1);
+
+ if ($do == 0) {
+ RemoveInternalTimer($hash);
+ InternalTimer(time+5, 'DbRep_firstconnect', $hash, 0);
+ } else {
+ my $dbh = $hash->{DBH};
+ $dbh->disconnect() if($dbh);
+ }
+
+ }
+
+ if ($cmd eq "set") {
+ if ($aName eq "timestamp_begin" || $aName eq "timestamp_end") {
+ unless ($aVal =~ /(19[0-9][0-9]|2[0-9][0-9][0-9])-(0[1-9]|1[1-2])-(0[1-9]|1[0-9]|2[0-9]|3[0-1]) (0[0-9])|1[1-9]|2[0-3]:([0-5][0-9]):([0-5][0-9])/) {return " The Value for $aName is not valid. Use format YYYY-MM-DD HH:MM:SS !";}
+ my ($yyyy, $mm, $dd, $hh, $min, $sec) = ($aVal =~ /(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)/);
+
+ eval { my $epoch_seconds_begin = timelocal($sec, $min, $hh, $dd, $mm-1, $yyyy-1900); };
+
+ if ($@) {
+ my @l = split (/at/, $@);
+ return " The Value of $aName is out of range - $l[0]";
+ }
+ delete($attr{$name}{timeDiffToNow}) if ($attr{$name}{timeDiffToNow});
+ }
+ if ($aName eq "timeout") {
+ unless ($aVal =~ /^[0-9]+$/) { return " The Value of $aName is not valid. Use only figures 0-9 without decimal places !";}
+ }
+ if ($aName eq "readingNameMap") {
+ unless ($aVal =~ m/^[A-Za-z\d_\.-]+$/) { return " Unsupported character in $aName found. Use only A-Z a-z _ . -";}
+ }
+ if ($aName eq "timeDiffToNow") {
+ unless ($aVal =~ /^[0-9]+$/) { return " The Value of $aName is not valid. Use only figures 0-9 without decimal places. It's the time (seconds) before now for start selection. Refer to commandref !";}
+ delete($attr{$name}{timestamp_begin}) if ($attr{$name}{timestamp_begin});
+ delete($attr{$name}{timestamp_end}) if ($attr{$name}{timestamp_end});
+ }
+ }
+return undef;
+}
+
+###################################################################################
+# DbRep_Undef
+###################################################################################
+sub DbRep_Undef {
+ my ($hash, $arg) = @_;
+
+ RemoveInternalTimer($hash);
+
+ my $dbh = $hash->{DBH};
+ $dbh->disconnect() if(defined($dbh));
+
+ BlockingKill($hash->{helper}{RUNNING_PID}) if (exists($hash->{helper}{RUNNING_PID}));
+
+return undef;
+}
+
+
+###################################################################################
+# First Init DB Connect
+###################################################################################
+sub DbRep_firstconnect {
+ my ($hash)= @_;
+ my $name = $hash->{NAME};
+ my $dblogdevice = $hash->{HELPER}{DBLOGDEVICE};
+ $hash->{dbloghash} = $defs{$dblogdevice};
+ my $dbconn = $hash->{dbloghash}{dbconn};
+
+ if ( !DbRep_Connect($hash) ) {
+ Log3 ($name, 2, "DbRep $name - DB connect failed. Credentials of $hash->{dbloghash}{NAME} are valid and database reachable ?");
+ readingsSingleUpdate($hash, "state", "disconnected", 1);
+ } else {
+ Log3 ($name, 3, "DbRep $name - Test-Connection to db $dbconn was successful");
+ my $dbh = $hash->{DBH};
+ $dbh->disconnect();
+ }
+return;
+}
+
+###################################################################################
+# DB Connect
+###################################################################################
+sub DbRep_Connect {
+ my ($hash)= @_;
+ my $name = $hash->{NAME};
+ my $dbloghash = $hash->{dbloghash};
+
+ my $dbconn = $dbloghash->{dbconn};
+ my $dbuser = $dbloghash->{dbuser};
+ my $dblogname = $dbloghash->{NAME};
+ my $dbpassword = $attr{"sec$dblogname"}{secret};
+
+ my $dbh;
+
+ eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoCommit => 1 });};
+
+ if(!$dbh) {
+ RemoveInternalTimer($hash);
+ Log3 ($name, 3, "DbRep $name - Test connection to database $dbconn with user $dbuser");
+
+ readingsSingleUpdate($hash, 'state', 'disconnected', 1);
+
+ InternalTimer(time+5, 'DbRep_Connect', $hash, 0);
+
+ Log3 ($name, 3, "DbRep $name - Waiting for database connection to test");
+
+ return 0;
+ }
+
+ $hash->{DBH} = $dbh;
+
+ readingsSingleUpdate($hash, "state", "connected", 1);
+
+ return 1;
+}
+
+################################################################################################################
+# Hauptroutine
+################################################################################################################
+
+sub sqlexec {
+ my ($hash,$opt) = @_;
+ my $name = $hash->{NAME};
+ my $to = AttrVal($name, "timeout", "60");
+ my $reading = AttrVal($hash->{NAME}, "reading", undef);
+ my $aggregation = AttrVal($hash->{NAME}, "aggregation", "no"); # wichtig !! aggregation niemals "undef"
+ my $device = AttrVal($hash->{NAME}, "device", undef);
+ my $aggsec;
+
+ # Test-Aufbau DB-Connection
+ #if ( !DbRep_Connect($hash) ) {
+ # Log3 ($name, 2, "DbRep $name - DB connect failed. Database down ? ");
+ # readingsSingleUpdate($hash, "state", "disconnected", 1);
+ # return;
+ #} else {
+ # my $dbh = $hash->{DBH};
+ # $dbh->disconnect;
+ #}
+
+ if (exists($hash->{helper}{RUNNING_PID})) {
+ Log3 ($name, 3, "DbRep $name - Warning: old process $hash->{helper}{RUNNING_PID}{pid} will be killed now to start a new BlockingCall");
+ BlockingKill($hash->{helper}{RUNNING_PID});
+ }
+
+ # alte Readings löschen
+ delete $defs{$name}{READINGS};
+
+ readingsSingleUpdate($hash, "state", "running", 1);
+
+ # only for this block because of warnings if details of readings are not set
+ no warnings 'uninitialized';
+
+ # Ausgaben und Zeitmanipulationen
+ Log3 ($name, 4, "DbRep $name - -------- New selection --------- ");
+ Log3 ($name, 4, "DbRep $name - Aggregation: $aggregation");
+
+ # timestamp in SQL format YYYY-MM-DD hh:mm:ss
+ # Auswertungszeit Beginn (String)
+ my $tsbegin = AttrVal($hash->{NAME}, "timestamp_begin", "");
+
+ # extrahieren der Einzelwerte von Datum/Zeit
+ my ($yyyy1, $mm1, $dd1, $hh1, $min1, $sec1) = ($tsbegin =~ /(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)/);
+
+ # Umwandeln in Epochesekunden bzw. setzen Differenz zur akt. Zeit wenn attr "timeDiffToNow" gesetzt
+ my $epoch_seconds_begin = timelocal($sec1, $min1, $hh1, $dd1, $mm1-1, $yyyy1-1900) if($tsbegin);
+ $epoch_seconds_begin = AttrVal($hash->{NAME}, "timeDiffToNow", undef) ? (time() - AttrVal($hash->{NAME}, "timeDiffToNow", undef)) : $epoch_seconds_begin;
+ Log3 ($name, 4, "DbRep $name - Time difference to current time to calculate Timestamp begin: ".AttrVal($hash->{NAME}, "timeDiffToNow", undef)." sec") if(AttrVal($hash->{NAME}, "timeDiffToNow", undef));
+
+ Log3 ($name, 5, "DbRep $name - Timestamp begin epocheseconds: $epoch_seconds_begin");
+ my $tsbegin_string = strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_begin);
+ Log3 ($name, 4, "DbRep $name - Timestamp begin human readable: $tsbegin_string");
+
+ # Auswertungszeit Ende (String)
+ my $tsend = AttrVal($hash->{NAME}, "timestamp_end", strftime "%Y-%m-%d %H:%M:%S", localtime(time));
+
+ # extrahieren der Einzelwerte von Datum/Zeit
+ my ($yyyy2, $mm2, $dd2, $hh2, $min2, $sec2) = ($tsend =~ /(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)/);
+
+ # Umwandeln in Epochesekunden
+ my $epoch_seconds_end = timelocal($sec2, $min2, $hh2, $dd2, $mm2-1, $yyyy2-1900);
+ Log3 ($name, 5, "DbRep $name - Timestamp end epocheseconds: $epoch_seconds_end");
+ my $tsend_string = strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_end);
+ Log3 ($name, 4, "DbRep $name - Timestamp end human readable: $tsend_string");
+
+
+ # Erstellung Wertehash für "collaggstr"
+ my $runtime = $epoch_seconds_begin; # Schleifenlaufzeit auf Beginn der Zeitselektion setzen
+ my $runtime_string; # Datum/Zeit im SQL-Format für Readingname Teilstring
+ my $runtime_string_first; # Datum/Zeit Auswertungsbeginn im SQL-Format für SQL-Statement
+ my $runtime_string_next; # Datum/Zeit + Periode (Granularität) für Auswertungsende im SQL-Format
+ my $reading_runtime_string; # zusammengesetzter Readingname+Aggregation für Update
+ my $tsstr = strftime "%H:%M:%S", localtime($runtime); # für Berechnung Tagesverschieber / Stundenverschieber
+ my $testr = strftime "%H:%M:%S", localtime($epoch_seconds_end); # für Berechnung Tagesverschieber / Stundenverschieber
+ my $dsstr = strftime "%Y-%m-%d", localtime($runtime); # für Berechnung Tagesverschieber / Stundenverschieber
+ my $destr = strftime "%Y-%m-%d", localtime($epoch_seconds_end); # für Berechnung Tagesverschieber / Stundenverschieber
+ my $msstr = strftime "%m", localtime($runtime); # Startmonat für Berechnung Monatsverschieber
+ my $mestr = strftime "%m", localtime($epoch_seconds_end); # Endemonat für Berechnung Monatsverschieber
+ my $ysstr = strftime "%Y", localtime($runtime); # Startjahr für Berechnung Monatsverschieber
+ my $yestr = strftime "%Y", localtime($epoch_seconds_end); # Endejahr für Berechnung Monatsverschieber
+
+ my $wd = strftime "%a", localtime($runtime); # Wochentag des aktuellen Startdatum/Zeit
+ my $wdadd = 604800 if($wd eq "Mo"); # wenn Start am "Mo" dann nächste Grenze +7 Tage
+ $wdadd = 518400 if($wd eq "Di"); # wenn Start am "Di" dann nächste Grenze +6 Tage
+ $wdadd = 432000 if($wd eq "Mi"); # wenn Start am "Mi" dann nächste Grenze +5 Tage
+ $wdadd = 345600 if($wd eq "Do"); # wenn Start am "Do" dann nächste Grenze +4 Tage
+ $wdadd = 259200 if($wd eq "Fr"); # wenn Start am "Fr" dann nächste Grenze +3 Tage
+ $wdadd = 172800 if($wd eq "Sa"); # wenn Start am "Sa" dann nächste Grenze +2 Tage
+ $wdadd = 86400 if($wd eq "So"); # wenn Start am "So" dann nächste Grenze +1 Tage
+
+ Log3 ($name, 5, "DbRep $name - weekday of start for selection: $wd -> wdadd: $wdadd");
+
+ if ($aggregation eq "hour") {
+ $aggsec = 3600;
+ } elsif ($aggregation eq "day") {
+ $aggsec = 86400;
+ } elsif ($aggregation eq "week") {
+ $aggsec = 604800;
+ } elsif ($aggregation eq "month") {
+ $aggsec = 2678400;
+ } elsif ($aggregation eq "no") {
+ $aggsec = 1;
+ } else {
+ return;
+ }
+
+my %cv = (
+ tsstr => $tsstr,
+ testr => $testr,
+ dsstr => $dsstr,
+ destr => $destr,
+ msstr => $msstr,
+ mestr => $mestr,
+ ysstr => $ysstr,
+ yestr => $yestr,
+ aggsec => $aggsec,
+ aggregation => $aggregation,
+ epoch_seconds_end => $epoch_seconds_end,
+ wdadd => $wdadd
+);
+$hash->{HELPER}{CV} = \%cv;
+
+ my $ts; # für Erstellung Timestamp-Array zur nonblocking SQL-Abarbeitung
+ my $i = 1; # Schleifenzähler -> nur Indikator für ersten Durchlauf -> anderer $runtime_string_first
+ my $ll; # loopindikator, wenn 1 = loopausstieg
+
+ # Aufbau Timestmapstring mit Zeitgrenzen entsprechend Aggregation
+ while (!$ll) {
+
+ # collect aggregation strings
+ ($runtime,$runtime_string,$runtime_string_first,$runtime_string_next,$ll) = collaggstr($hash,$runtime,$i,$runtime_string_next);
+
+ $ts .= $runtime_string."#".$runtime_string_first."#".$runtime_string_next."|";
+
+ $i++;
+ }
+
+ if ($opt eq "sum") {
+ $hash->{helper}{RUNNING_PID} = BlockingCall("sumval_DoParse", "$name§$device§$reading§$ts", "sumval_ParseDone", $to, "ParseAborted", $hash);
+
+ } elsif ($opt eq "count" ) {
+ $hash->{helper}{RUNNING_PID} = BlockingCall("count_DoParse", "$name§$device§$reading§$ts", "count_ParseDone", $to, "ParseAborted", $hash);
+
+ } elsif ($opt eq "average" ) {
+ $hash->{helper}{RUNNING_PID} = BlockingCall("averval_DoParse", "$name§$device§$reading§$ts", "averval_ParseDone", $to, "ParseAborted", $hash);
+
+ } elsif ($opt eq "fetchrows" ) {
+ $runtime_string_first = defined($epoch_seconds_begin) ? strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_begin) : "1970-01-01 01:00:00";
+ $runtime_string_next = strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_end);
+
+ $hash->{helper}{RUNNING_PID} = BlockingCall("fetchrows_DoParse", "$name|$device|$reading|$runtime_string_first|$runtime_string_next", "fetchrows_ParseDone", $to, "ParseAborted", $hash);
+
+ } elsif ($opt eq "max" ) {
+ $hash->{helper}{RUNNING_PID} = BlockingCall("maxval_DoParse", "$name§$device§$reading§$ts", "maxval_ParseDone", $to, "ParseAborted", $hash);
+
+ } elsif ($opt eq "del" ) {
+ $runtime_string_first = AttrVal($hash->{NAME}, "timestamp_begin", undef) ? strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_begin) : "1970-01-01 01:00:00";
+ $runtime_string_next = strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_end);
+
+ $hash->{helper}{RUNNING_PID} = BlockingCall("del_DoParse", "$name|$device|$reading|$runtime_string_first|$runtime_string_next", "del_ParseDone", $to, "ParseAborted", $hash);
+ }
+
+return;
+}
+
+####################################################################################################
+# nichtblockierende DB-Abfrage averageValue
+####################################################################################################
+sub averval_DoParse {
+ my ($string) = @_;
+ my ($name, $device, $reading, $ts) = split("\\§", $string);
+ my $hash = $defs{$name};
+
+ my $dbloghash = $hash->{dbloghash};
+ my $dbconn = $dbloghash->{dbconn};
+ my $dbuser = $dbloghash->{dbuser};
+ my $dblogname = $dbloghash->{NAME};
+ my $dbpassword = $attr{"sec$dblogname"}{secret};
+
+ Log3 ($name, 4, "DbRep $name -> Start BlockingCall averval_DoParse");
+
+ my $dbh;
+ eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1 });};
+
+ if ($@) {
+ Log3 ($name, 2, "DbRep $name - $@");
+ Log3 ($name, 4, "DbRep $name -> BlockingCall averval_DoParse finished");
+ return "$name|''|$device|$reading|''|1";
+ }
+
+ # only for this block because of warnings if details of readings are not set
+ no warnings 'uninitialized';
+
+ # Timestampstring to Array
+ my @ts = split("\\|", $ts);
+
+ # SQL-Startzeit
+ my $st = [gettimeofday];
+
+ # DB-Abfrage zeilenweise für jeden Array-Eintrag
+ my $arrstr;
+ foreach my $row (@ts) {
+
+ my @a = split("#", $row);
+ my $runtime_string = $a[0];
+ my $runtime_string_first = $a[1];
+ my $runtime_string_next = $a[2];
+
+ # SQL zusammenstellen für DB-Abfrage
+ my $sql = "SELECT AVG(VALUE) FROM `history` ";
+ $sql .= "where " if($reading || $device || AttrVal($hash->{NAME}, "timestamp_begin", "") ne "" || AttrVal($hash->{NAME}, "aggregation", "no") ne "no" || AttrVal($hash->{NAME}, "timeDiffToNow", undef) ne undef);
+ $sql .= "DEVICE = '$device' " if($device);
+ $sql .= "AND " if($device && $reading);
+ $sql .= "READING = '$reading' " if($reading);
+ $sql .= "AND " if((AttrVal($hash->{NAME}, "aggregation", "no") ne "no" || AttrVal($hash->{NAME}, "timestamp_begin", undef) ne undef || AttrVal($hash->{NAME}, "timestamp_end", undef) ne undef || AttrVal($hash->{NAME}, "timeDiffToNow", undef) ne undef) && ($device || $reading));
+ $sql .= "TIMESTAMP BETWEEN '$runtime_string_first' AND '$runtime_string_next' " if(AttrVal($hash->{NAME}, "aggregation", "no") ne "no" || AttrVal($hash->{NAME}, "timestamp_begin", undef) ne undef || AttrVal($hash->{NAME}, "timestamp_end", undef) || AttrVal($hash->{NAME}, "timeDiffToNow", undef) ne undef);
+ $sql .= ";";
+
+ Log3 ($name, 4, "DbRep $name - SQL to execute: $sql");
+
+ my $line;
+
+ # DB-Abfrage -> Ergebnis in $arrstr aufnehmen
+ eval {$line = $dbh->selectrow_array($sql);};
+
+ if ($@) {
+ Log3 ($name, 2, "DbRep $name - $@");
+ $dbh->disconnect;
+ Log3 ($name, 4, "DbRep $name -> BlockingCall averval_DoParse finished");
+ return "$name|''|$device|$reading|''|1";
+ } else {
+ Log3 ($name, 5, "DbRep $name - SQL result: $line") if($line);
+ $arrstr .= $runtime_string."#".$line."|";
+ }
+ }
+
+ # SQL-Laufzeit ermitteln
+ my $rt = tv_interval($st);
+
+ $dbh->disconnect;
+
+ # Daten müssen als Einzeiler zurückgegeben werden
+ $arrstr = encode_base64($arrstr,"");
+
+ Log3 ($name, 4, "DbRep $name -> BlockingCall averval_DoParse finished");
+
+ return "$name|$arrstr|$device|$reading|$rt|0";
+}
+
+####################################################################################################
+# Auswertungsroutine der nichtblockierenden DB-Abfrage averageValue
+####################################################################################################
+sub averval_ParseDone {
+ my ($string) = @_;
+ my @a = split("\\|",$string);
+ my $hash = $defs{$a[0]};
+ my $name = $hash->{NAME};
+ my $arrstr = decode_base64($a[1]);
+ my $device = $a[2];
+ my $reading = $a[3];
+ my $rt = $a[4];
+ my $dberr = $a[5];
+ my $reading_runtime_string;
+
+ Log3 ($name, 4, "DbRep $name -> Start BlockingCall averval_ParseDone");
+
+ if ($dberr) {
+ readingsSingleUpdate($hash, "state", "error", 1);
+ delete($hash->{helper}{RUNNING_PID});
+ Log3 ($name, 4, "DbRep $name -> BlockingCall averval_ParseDone finished");
+ return;
+ }
+
+ # only for this block because of warnings if details of readings are not set
+ no warnings 'uninitialized';
+
+ # Readingaufbereitung
+ my @arr = split("\\|", $arrstr);
+ foreach my $row (@arr) {
+ my @a = split("#", $row);
+ my $runtime_string = $a[0];
+ my $c = $a[1];
+
+ if (AttrVal($hash->{NAME}, "readingNameMap", "")) {
+ $reading_runtime_string = AttrVal($hash->{NAME}, "readingNameMap", "")."__".$runtime_string;
+ } else {
+ my $ds = $device."__" if ($device);
+ my $rds = $reading."__" if ($reading);
+ $reading_runtime_string = $ds.$rds."AVERAGE__".$runtime_string;
+ }
+
+ readingsSingleUpdate($hash, $reading_runtime_string, $c?sprintf("%.4f",$c):"-", 1);
+ }
+ readingsSingleUpdate($hash, "state", "done", 1);
+ readingsSingleUpdate($hash, "sql_processing_time", sprintf("%.4f",$rt), 1) if(AttrVal($name, "showproctime", undef));
+
+ delete($hash->{helper}{RUNNING_PID});
+ Log3 ($name, 4, "DbRep $name -> BlockingCall averval_ParseDone finished");
+
+return;
+}
+
+####################################################################################################
+# nichtblockierende DB-Abfrage count
+####################################################################################################
+
+sub count_DoParse {
+ my ($string) = @_;
+ my ($name, $device, $reading, $ts) = split("\\§", $string);
+ my $hash = $defs{$name};
+
+ my $dbloghash = $hash->{dbloghash};
+ my $dbconn = $dbloghash->{dbconn};
+ my $dbuser = $dbloghash->{dbuser};
+ my $dblogname = $dbloghash->{NAME};
+ my $dbpassword = $attr{"sec$dblogname"}{secret};
+
+ Log3 ($name, 4, "DbRep $name -> Start BlockingCall count_DoParse");
+
+ my $dbh;
+ eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1 });};
+
+ if ($@) {
+ Log3 ($name, 2, "DbRep $name - $@");
+ Log3 ($name, 4, "DbRep $name -> BlockingCall count_DoParse finished");
+ return "$name|''|$device|$reading|''|1";
+ }
+
+ # only for this block because of warnings if details of readings are not set
+ no warnings 'uninitialized';
+
+ # Timestampstring to Array
+ my @ts = split("\\|", $ts);
+
+ # SQL-Startzeit
+ my $st = [gettimeofday];
+
+ # DB-Abfrage zeilenweise für jeden Array-Eintrag
+ my $arrstr;
+ foreach my $row (@ts) {
+
+ my @a = split("#", $row);
+ my $runtime_string = $a[0];
+ my $runtime_string_first = $a[1];
+ my $runtime_string_next = $a[2];
+
+ # SQL zusammenstellen für DB-Abfrage
+ my $sql = "SELECT COUNT(*) FROM `history` ";
+ $sql .= "where " if($reading || $device || AttrVal($hash->{NAME}, "timestamp_begin", "") ne "" || AttrVal($hash->{NAME}, "aggregation", "no") ne "no" || AttrVal($hash->{NAME}, "timeDiffToNow", undef) ne undef);
+ $sql .= "DEVICE = '$device' " if($device);
+ $sql .= "AND " if($device && $reading);
+ $sql .= "READING = '$reading' " if($reading);
+ $sql .= "AND " if((AttrVal($hash->{NAME}, "aggregation", "no") ne "no" || AttrVal($hash->{NAME}, "timestamp_begin", undef) ne undef || AttrVal($hash->{NAME}, "timestamp_end", undef) ne undef || AttrVal($hash->{NAME}, "timeDiffToNow", undef) ne undef) && ($device || $reading));
+ $sql .= "TIMESTAMP BETWEEN '$runtime_string_first' AND '$runtime_string_next' " if(AttrVal($hash->{NAME}, "aggregation", "no") ne "no" || AttrVal($hash->{NAME}, "timestamp_begin", undef) ne undef || AttrVal($hash->{NAME}, "timestamp_end", undef) || AttrVal($hash->{NAME}, "timeDiffToNow", undef) ne undef);
+ $sql .= ";";
+
+ Log3($name, 4, "DbRep $name - SQL to execute: $sql");
+
+ my $line;
+ # DB-Abfrage -> Ergebnis in $arrstr aufnehmen
+ eval {$line = $dbh->selectrow_array($sql);};
+
+ if ($@) {
+ Log3 ($name, 2, "DbRep $name - $@");
+ $dbh->disconnect;
+ Log3 ($name, 4, "DbRep $name -> BlockingCall count_DoParse finished");
+ return "$name|''|$device|$reading|''|1";
+ } else {
+ Log3 ($name, 5, "DbRep $name - SQL result: $line") if($line);
+ $arrstr .= $runtime_string."#".$line."|";
+ }
+ }
+
+ # SQL-Laufzeit ermitteln
+ my $rt = tv_interval($st);
+
+ $dbh->disconnect;
+
+ # Daten müssen als Einzeiler zurückgegeben werden
+ $arrstr = encode_base64($arrstr,"");
+
+ Log3 ($name, 4, "DbRep $name -> BlockingCall count_DoParse finished");
+
+ return "$name|$arrstr|$device|$reading|$rt|0";
+}
+
+####################################################################################################
+# Auswertungsroutine der nichtblockierenden DB-Abfrage count
+####################################################################################################
+
+sub count_ParseDone {
+ my ($string) = @_;
+ my @a = split("\\|",$string);
+ my $hash = $defs{$a[0]};
+ my $name = $hash->{NAME};
+ my $arrstr = decode_base64($a[1]);
+ my $device = $a[2];
+ my $reading = $a[3];
+ my $rt = $a[4];
+ my $dberr = $a[5];
+ my $reading_runtime_string;
+
+ Log3 ($name, 4, "DbRep $name -> Start BlockingCall count_ParseDone");
+
+ if ($dberr) {
+ readingsSingleUpdate($hash, "state", "error", 1);
+ delete($hash->{helper}{RUNNING_PID});
+ Log3 ($name, 4, "DbRep $name -> BlockingCall count_ParseDone finished");
+ return;
+ }
+
+ Log3 ($name, 5, "DbRep $name - SQL result decoded: $arrstr") if($arrstr);
+
+ # only for this block because of warnings if details of readings are not set
+ no warnings 'uninitialized';
+
+ # Readingaufbereitung
+ my @arr = split("\\|", $arrstr);
+ foreach my $row (@arr) {
+ my @a = split("#", $row);
+ my $runtime_string = $a[0];
+ my $c = $a[1];
+
+ if (AttrVal($hash->{NAME}, "readingNameMap", "")) {
+ $reading_runtime_string = AttrVal($hash->{NAME}, "readingNameMap", "")."__".$runtime_string;
+ } else {
+ my $ds = $device."__" if ($device);
+ my $rds = $reading."__" if ($reading);
+ $reading_runtime_string = $ds.$rds."COUNT__".$runtime_string;
+ }
+
+ readingsSingleUpdate($hash, $reading_runtime_string, $c?$c:"-", 1);
+ }
+ readingsSingleUpdate($hash, "state", "done", 1);
+ readingsSingleUpdate($hash, "sql_processing_time", sprintf("%.4f",$rt), 1) if(AttrVal($name, "showproctime", undef));
+
+ delete($hash->{helper}{RUNNING_PID});
+ Log3 ($name, 4, "DbRep $name -> BlockingCall count_ParseDone finished");
+
+return;
+}
+
+####################################################################################################
+# nichtblockierende DB-Abfrage maxValue
+####################################################################################################
+
+sub maxval_DoParse {
+ my ($string) = @_;
+ my ($name, $device, $reading, $ts) = split("\\§", $string);
+ my $hash = $defs{$name};
+
+ my $dbloghash = $hash->{dbloghash};
+ my $dbconn = $dbloghash->{dbconn};
+ my $dbuser = $dbloghash->{dbuser};
+ my $dblogname = $dbloghash->{NAME};
+ my $dbpassword = $attr{"sec$dblogname"}{secret};
+
+ Log3 ($name, 4, "DbRep $name -> Start BlockingCall maxval_DoParse");
+
+ my $dbh;
+ eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1 });};
+
+ if ($@) {
+ Log3 ($name, 2, "DbRep $name - $@");
+ Log3 ($name, 4, "DbRep $name -> BlockingCall maxval_DoParse finished");
+ return "$name|''|$device|$reading|''|1";
+ }
+
+ # only for this block because of warnings if details of readings are not set
+ no warnings 'uninitialized';
+
+ # Timestampstring to Array
+ my @ts = split("\\|", $ts);
+
+ # SQL-Startzeit
+ my $st = [gettimeofday];
+
+ # DB-Abfrage zeilenweise für jeden Array-Eintrag
+ my @row_array;
+ foreach my $row (@ts) {
+
+ my @a = split("#", $row);
+ my $runtime_string = $a[0];
+ my $runtime_string_first = $a[1];
+ my $runtime_string_next = $a[2];
+
+ # SQL zusammenstellen für DB-Operation
+ my $sql = "SELECT VALUE,TIMESTAMP FROM `history` where ";
+ $sql .= "`DEVICE` = '$device' AND " if($device);
+ $sql .= "`READING` = '$reading' AND " if($reading);
+ $sql .= "TIMESTAMP BETWEEN ? AND ? ORDER BY TIMESTAMP ;";
+
+ # SQL zusammenstellen für Logausgabe
+ my $sql1 = "SELECT VALUE,TIMESTAMP FROM `history` where ";
+ $sql1 .= "`DEVICE` = '$device' AND " if($device);
+ $sql1 .= "`READING` = '$reading' AND " if($reading);
+ $sql1 .= "TIMESTAMP BETWEEN '$runtime_string_first' AND '$runtime_string_next' ORDER BY TIMESTAMP;";
+
+ Log3 ($name, 4, "DbRep $name - SQL to execute: $sql1");
+
+ $runtime_string = encode_base64($runtime_string,"");
+ my $sth = $dbh->prepare($sql);
+
+ eval {$sth->execute($runtime_string_first, $runtime_string_next);};
+
+ if ($@) {
+ Log3 ($name, 2, "DbRep $name - $@");
+ $dbh->disconnect;
+ Log3 ($name, 4, "DbRep $name -> BlockingCall maxval_DoParse finished");
+ return "$name|''|$device|$reading|''|1";
+ } else {
+ my @array= map { $runtime_string." ".$_ -> [0]." ".$_ -> [1]."\n" } @{ $sth->fetchall_arrayref() };
+ push(@row_array, @array);
+ }
+
+ # $sth->finish();
+ }
+
+ # SQL-Laufzeit ermitteln
+ my $rt = tv_interval($st);
+
+ $dbh->disconnect;
+
+ my $rowlist = join('|', @row_array);
+ Log3 ($name, 5, "DbRep $name -> row_array: @row_array");
+
+ # Daten müssen als Einzeiler zurückgegeben werden
+ $rowlist = encode_base64($rowlist,"");
+
+ Log3 ($name, 4, "DbRep $name -> BlockingCall maxval_DoParse finished");
+
+ return "$name|$rowlist|$device|$reading|$rt|0";
+}
+
+####################################################################################################
+# Auswertungsroutine der nichtblockierenden DB-Abfrage maxValue
+####################################################################################################
+
+sub maxval_ParseDone {
+ my ($string) = @_;
+ my @a = split("\\|",$string);
+ my $hash = $defs{$a[0]};
+ my $name = $hash->{NAME};
+
+ my $rowlist = decode_base64($a[1]);
+ my $device = $a[2];
+ my $reading = $a[3];
+ my $rt = $a[4];
+ my $dberr = $a[5];
+ my $reading_runtime_string;
+
+ Log3 ($name, 4, "DbRep $name -> Start BlockingCall maxval_ParseDone");
+
+ if ($dberr) {
+ readingsSingleUpdate($hash, "state", "error", 1);
+ delete($hash->{helper}{RUNNING_PID});
+ Log3 ($name, 4, "DbRep $name -> BlockingCall maxval_ParseDone finished");
+ return;
+ }
+
+ my @row_array = split("\\|", $rowlist);
+
+ Log3 ($name, 5, "DbRep $name - row_array decoded: @row_array");
+
+ my $i = 1;
+ my %rh = ();
+ my $lastruntimestring;
+ my $row_max_time;
+ my $max_value = 0;
+
+ foreach my $row (@row_array) {
+ my @a = split("[ \t][ \t]*", $row);
+ my $runtime_string = decode_base64($a[0]);
+ $lastruntimestring = $runtime_string if ($i == 1);
+ my $value = $a[1];
+ $a[3] =~ s/:/-/g; # substituieren unsopported characters -> siehe fhem.pl
+ my $timestamp = $a[2]."_".$a[3];
+
+ # Leerzeichen am Ende $timestamp entfernen
+ $timestamp =~ s/\s+$//g;
+
+ Log3 ($name, 4, "DbRep $name - Runtimestring: $runtime_string, DEVICE: $device, READING: $reading, TIMESTAMP: $timestamp, VALUE: $value");
+
+ if ($runtime_string eq $lastruntimestring) {
+ if ($value >= $max_value) {
+ $max_value = $value;
+ $row_max_time = $timestamp;
+ $rh{$runtime_string} = $runtime_string."|".$max_value."|".$row_max_time;
+ }
+ } else {
+ # neuer Zeitabschnitt beginnt, ersten Value-Wert erfassen
+ $lastruntimestring = $runtime_string;
+ $max_value = 0;
+ if ($value >= $max_value) {
+ $max_value = $value;
+ $row_max_time = $timestamp;
+ $rh{$runtime_string} = $runtime_string."|".$max_value."|".$row_max_time;
+ }
+ }
+ $i++;
+ }
+
+ foreach my $key (sort(keys(%rh))) {
+ Log3 ($name, 5, "DbRep $name - runtimestring Key: $key, value: ".$rh{$key});
+ my @k = split("\\|",$rh{$key});
+
+ # Readingaufbereitung
+
+ # only for this block because of warnings if details of readings are not set
+ no warnings 'uninitialized';
+
+ if (AttrVal($hash->{NAME}, "readingNameMap", "")) {
+ $reading_runtime_string = AttrVal($hash->{NAME}, "readingNameMap", "")."__".$k[0];
+ } else {
+ my $rmts = $k[2]."__" if($k[2]);
+ my $ds = $device."__" if ($device);
+ my $rds = $reading."__" if ($reading);
+ $reading_runtime_string = $rmts.$ds.$rds."MAX__".$k[0];
+ }
+
+ readingsSingleUpdate($hash, $reading_runtime_string, $k[1], 1);
+
+ }
+
+ readingsSingleUpdate($hash, "state", "done", 1);
+ readingsSingleUpdate($hash, "sql_processing_time", sprintf("%.4f",$rt), 1) if(AttrVal($name, "showproctime", undef));
+
+ delete($hash->{helper}{RUNNING_PID});
+ Log3 ($name, 4, "DbRep $name -> BlockingCall maxval_ParseDone finished");
+
+return;
+}
+
+####################################################################################################
+# nichtblockierende DB-Abfrage sumValue
+####################################################################################################
+
+sub sumval_DoParse {
+ my ($string) = @_;
+ my ($name, $device, $reading, $ts) = split("\\§", $string);
+ my $hash = $defs{$name};
+ my $dbloghash = $hash->{dbloghash};
+ my $dbconn = $dbloghash->{dbconn};
+ my $dbuser = $dbloghash->{dbuser};
+ my $dblogname = $dbloghash->{NAME};
+ my $dbpassword = $attr{"sec$dblogname"}{secret};
+
+ Log3 ($name, 4, "DbRep $name -> Start BlockingCall sumval_DoParse");
+
+ my $dbh;
+ eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1 });};
+
+ if ($@) {
+ Log3 ($name, 2, "DbRep $name - $@");
+ Log3 ($name, 4, "DbRep $name -> BlockingCall sumval_DoParse finished");
+ return "$name|''|$device|$reading|''|1";
+ }
+
+ # only for this block because of warnings if details of readings are not set
+ no warnings 'uninitialized';
+
+ # Timestampstring to Array
+ my @ts = split("\\|", $ts);
+
+ # SQL-Startzeit
+ my $st = [gettimeofday];
+
+ # DB-Abfrage zeilenweise für jeden Array-Eintrag
+ my $arrstr;
+ foreach my $row (@ts) {
+
+ my @a = split("#", $row);
+ my $runtime_string = $a[0];
+ my $runtime_string_first = $a[1];
+ my $runtime_string_next = $a[2];
+
+ # SQL zusammenstellen für DB-Abfrage
+ my $sql = "SELECT SUM(VALUE) FROM `history` ";
+ $sql .= "where " if($reading || $device || AttrVal($hash->{NAME}, "timestamp_begin", "") ne "" || AttrVal($hash->{NAME}, "aggregation", "no") ne "no" || AttrVal($hash->{NAME}, "timeDiffToNow", undef) ne undef);
+ $sql .= "DEVICE = '$device' " if($device);
+ $sql .= "AND " if($device && $reading);
+ $sql .= "READING = '$reading' " if($reading);
+ $sql .= "AND " if((AttrVal($hash->{NAME}, "aggregation", "no") ne "no" || AttrVal($hash->{NAME}, "timestamp_begin", undef) ne undef || AttrVal($hash->{NAME}, "timestamp_end", undef) ne undef || AttrVal($hash->{NAME}, "timeDiffToNow", undef) ne undef) && ($device || $reading));
+ $sql .= "TIMESTAMP BETWEEN '$runtime_string_first' AND '$runtime_string_next' " if(AttrVal($hash->{NAME}, "aggregation", "no") ne "no" || AttrVal($hash->{NAME}, "timestamp_begin", undef) ne undef || AttrVal($hash->{NAME}, "timestamp_end", undef) || AttrVal($hash->{NAME}, "timeDiffToNow", undef) ne undef);
+ $sql .= ";";
+
+ Log3 ($name, 4, "DbRep $name - SQL to execute: $sql");
+
+ my $line;
+ # DB-Abfrage -> Ergebnis in $arrstr aufnehmen
+ eval {$line = $dbh->selectrow_array($sql);};
+
+ if ($@) {
+ Log3 ($name, 2, "DbRep $name - $@");
+ $dbh->disconnect;
+ Log3 ($name, 4, "DbRep $name -> BlockingCall sumval_DoParse finished");
+ return "$name|''|$device|$reading|''|1";
+ } else {
+ Log3($name, 5, "DbRep $name - SQL result: $line") if($line);
+ $arrstr .= $runtime_string."#".$line."|";
+ }
+ }
+
+ # SQL-Laufzeit ermitteln
+ my $rt = tv_interval($st);
+
+ $dbh->disconnect;
+
+ # Daten müssen als Einzeiler zurückgegeben werden
+ $arrstr = encode_base64($arrstr,"");
+
+ Log3 ($name, 4, "DbRep $name -> BlockingCall sumval_DoParse finished");
+
+ return "$name|$arrstr|$device|$reading|$rt|0";
+}
+
+####################################################################################################
+# Auswertungsroutine der nichtblockierenden DB-Abfrage sumValue
+####################################################################################################
+
+sub sumval_ParseDone {
+ my ($string) = @_;
+ my @a = split("\\|",$string);
+ my $hash = $defs{$a[0]};
+ my $name = $hash->{NAME};
+ my $arrstr = decode_base64($a[1]);
+ my $device = $a[2];
+ my $reading = $a[3];
+ my $rt = $a[4];
+ my $dberr = $a[5];
+ my $reading_runtime_string;
+
+ Log3 ($name, 4, "DbRep $name -> Start BlockingCall sumval_ParseDone");
+
+ if ($dberr) {
+ readingsSingleUpdate($hash, "state", "error", 1);
+ delete($hash->{helper}{RUNNING_PID});
+ Log3 ($name, 4, "DbRep $name -> BlockingCall sumval_ParseDone finished");
+ return;
+ }
+
+ # only for this block because of warnings if details of readings are not set
+ no warnings 'uninitialized';
+
+ # Readingaufbereitung
+ my @arr = split("\\|", $arrstr);
+ foreach my $row (@arr) {
+ my @a = split("#", $row);
+ my $runtime_string = $a[0];
+ my $c = $a[1];
+
+ if (AttrVal($hash->{NAME}, "readingNameMap", "")) {
+ $reading_runtime_string = AttrVal($hash->{NAME}, "readingNameMap", "")."__".$runtime_string;
+ } else {
+ my $ds = $device."__" if ($device);
+ my $rds = $reading."__" if ($reading);
+ $reading_runtime_string = $ds.$rds."SUM__".$runtime_string;
+ }
+
+ readingsSingleUpdate($hash, $reading_runtime_string, $c?sprintf("%.4f",$c):"-", 1);
+ }
+
+ readingsSingleUpdate($hash, "state", "done", 1);
+ readingsSingleUpdate($hash, "sql_processing_time", sprintf("%.4f",$rt), 1) if(AttrVal($name, "showproctime", undef));
+
+ delete($hash->{helper}{RUNNING_PID});
+ Log3 ($name, 4, "DbRep $name -> BlockingCall sumval_ParseDone finished");
+
+return;
+}
+
+####################################################################################################
+# nichtblockierendes DB delete
+####################################################################################################
+
+sub del_DoParse {
+ my ($string) = @_;
+ my ($name, $device, $reading, $runtime_string_first, $runtime_string_next) = split("\\|", $string);
+ my $hash = $defs{$name};
+ my $dbloghash = $hash->{dbloghash};
+ my $dbconn = $dbloghash->{dbconn};
+ my $dbuser = $dbloghash->{dbuser};
+ my $dblogname = $dbloghash->{NAME};
+ my $dbpassword = $attr{"sec$dblogname"}{secret};
+
+ Log3 ($name, 4, "DbRep $name -> Start BlockingCall del_DoParse");
+
+ my $dbh;
+ eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoCommit => 1, AutoInactiveDestroy => 1 });};
+
+ if ($@) {
+ Log3 ($name, 2, "DbRep $name - $@");
+ Log3 ($name, 4, "DbRep $name -> BlockingCall del_DoParse finished");
+ return "$name|''|''|1";
+ }
+
+ # SQL zusammenstellen für DB-Operation
+ my $sql = "DELETE FROM history where ";
+ $sql .= "DEVICE = '$device' AND " if($device);
+ $sql .= "READING = '$reading' AND " if($reading);
+ $sql .= "TIMESTAMP BETWEEN ? AND ?;";
+
+ # SQL zusammenstellen für Logausgabe
+ my $sql1 = "DELETE FROM history where ";
+ $sql1 .= "DEVICE = '$device' AND " if($device);
+ $sql1 .= "READING = '$reading' AND " if($reading);
+ $sql1 .= "TIMESTAMP BETWEEN $runtime_string_first AND $runtime_string_next;";
+
+ Log3 ($name, 4, "DbRep $name - SQL to execute: $sql1");
+
+ # SQL-Startzeit
+ my $st = [gettimeofday];
+
+ my $sth = $dbh->prepare($sql);
+
+ eval {$sth->execute($runtime_string_first, $runtime_string_next);};
+
+ my $rows;
+ if ($@) {
+ Log3 ($name, 2, "DbRep $name - $@");
+ $dbh->disconnect;
+ Log3 ($name, 4, "DbRep $name -> BlockingCall del_DoParse finished");
+ return "$name|''|''|1";
+ } else {
+ $rows = $sth->rows;
+ $dbh->commit() if(!$dbh->{AutoCommit});
+ $dbh->disconnect;
+ }
+
+ # SQL-Laufzeit ermitteln
+ my $rt = tv_interval($st);
+
+ Log3 ($name, 5, "DbRep $name -> Number of deleted rows: $rows");
+ Log3 ($name, 4, "DbRep $name -> BlockingCall del_DoParse finished");
+
+ return "$name|$rows|$rt|0";
+}
+
+####################################################################################################
+# Auswertungsroutine DB delete
+####################################################################################################
+
+sub del_ParseDone {
+ my ($string) = @_;
+ my @a = split("\\|",$string);
+ my $hash = $defs{$a[0]};
+ my $name = $hash->{NAME};
+ my $rows = $a[1];
+ my $rt = $a[2];
+ my $dberr = $a[3];
+
+ Log3 ($name, 4, "DbRep $name -> Start BlockingCall del_ParseDone");
+
+ if ($dberr) {
+ readingsSingleUpdate($hash, "state", "error", 1);
+ delete($hash->{helper}{RUNNING_PID});
+ Log3 ($name, 4, "DbRep $name -> BlockingCall del_ParseDone finished");
+ return;
+ }
+
+ my $reading = AttrVal($hash->{NAME}, "reading", undef);
+ my $device = AttrVal($hash->{NAME}, "device", undef);
+
+ # only for this block because of warnings if details of readings are not set
+ no warnings 'uninitialized';
+
+ my $ds = $device." -- " if ($device);
+ my $rds = $reading." -- " if ($reading);
+ my $reading_runtime_string = $ds.$rds." -- DELETED ROWS -- ";
+
+ readingsSingleUpdate($hash, $reading_runtime_string, $rows, 1);
+
+ $rows = $ds.$rds.$rows;
+
+ Log3 ($name, 3, "DbRep $name - Entries of database $hash->{dbloghash}{NAME} deleted: $rows");
+
+ readingsSingleUpdate($hash, "state", "done", 1);
+ readingsSingleUpdate($hash, "sql_processing_time", sprintf("%.4f",$rt), 1) if(AttrVal($name, "showproctime", undef));
+
+ delete($hash->{helper}{RUNNING_PID});
+ Log3 ($name, 4, "DbRep $name -> BlockingCall del_ParseDone finished");
+
+return;
+}
+
+####################################################################################################
+# nichtblockierende DB-Abfrage fetchrows
+####################################################################################################
+
+sub fetchrows_DoParse {
+ my ($string) = @_;
+ my ($name, $device, $reading, $runtime_string_first, $runtime_string_next) = split("\\|", $string);
+ my $hash = $defs{$name};
+ my $dbloghash = $hash->{dbloghash};
+ my $dbconn = $dbloghash->{dbconn};
+ my $dbuser = $dbloghash->{dbuser};
+ my $dblogname = $dbloghash->{NAME};
+ my $dbpassword = $attr{"sec$dblogname"}{secret};
+
+ Log3 ($name, 4, "DbRep $name -> Start BlockingCall fetchrows_DoParse");
+
+ my $dbh;
+ eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1 });};
+
+ if ($@) {
+ Log3 ($name, 2, "DbRep $name - $@");
+ Log3 ($name, 4, "DbRep $name -> BlockingCall fetchrows_DoParse finished");
+ return "$name|''|''|1";
+ }
+
+ # SQL zusammenstellen
+ my $sql = "SELECT DEVICE,READING,TIMESTAMP,VALUE FROM history where ";
+ $sql .= "DEVICE = '$device' AND " if($device);
+ $sql .= "READING = '$reading' AND " if($reading);
+ $sql .= "TIMESTAMP BETWEEN ? AND ? ORDER BY TIMESTAMP;";
+
+ # SQL zusammenstellen für Logfileausgabe
+ my $sql1 = "SELECT DEVICE,READING,TIMESTAMP,VALUE FROM history where ";
+ $sql1 .= "DEVICE = '$device' AND " if($device);
+ $sql1 .= "READING = '$reading' AND " if($reading);
+ $sql1 .= "TIMESTAMP BETWEEN $runtime_string_first AND $runtime_string_next ORDER BY TIMESTAMP;";
+
+ Log3 ($name, 4, "DbRep $name - SQL to execute: $sql1");
+
+ # SQL-Startzeit
+ my $st = [gettimeofday];
+
+ my $sth = $dbh->prepare($sql);
+
+ eval {$sth->execute($runtime_string_first, $runtime_string_next);};
+
+ my $rowlist;
+ if ($@) {
+ Log3 ($name, 2, "DbRep $name - $@");
+ $dbh->disconnect;
+ Log3 ($name, 4, "DbRep $name -> BlockingCall fetchrows_DoParse finished");
+ return "$name|''|''|1";
+ } else {
+ my @row_array = map { $_ -> [0]." ".$_ -> [1]." ".$_ -> [2]." ".$_ -> [3]."\n" } @{ $sth->fetchall_arrayref() };
+ $rowlist = join('|', @row_array);
+ Log3 ($name, 5, "DbRep $name -> row_array: @row_array");
+ }
+
+ # SQL-Laufzeit ermitteln
+ my $rt = tv_interval($st);
+
+ $dbh->disconnect;
+
+ # Daten müssen als Einzeiler zurückgegeben werden
+ $rowlist = encode_base64($rowlist,"");
+
+ Log3 ($name, 4, "DbRep $name -> BlockingCall fetchrows_DoParse finished");
+
+ return "$name|$rowlist|$rt|0";
+}
+
+####################################################################################################
+# Auswertungsroutine der nichtblockierenden DB-Abfrage fetchrows
+####################################################################################################
+
+sub fetchrows_ParseDone {
+ my ($string) = @_;
+ my @a = split("\\|",$string);
+ my $hash = $defs{$a[0]};
+ my $rowlist = decode_base64($a[1]);
+ my $rt = $a[2];
+ my $dberr = $a[3];
+ my $name = $hash->{NAME};
+ my $reading = AttrVal($name, "reading", undef);
+ my @i;
+ my @row;
+ my $reading_runtime_string;
+
+ Log3 ($name, 4, "DbRep $name -> Start BlockingCall fetchrows_ParseDone");
+
+ if ($dberr) {
+ readingsSingleUpdate($hash, "state", "error", 1);
+ delete($hash->{helper}{RUNNING_PID});
+ Log3 ($name, 4, "DbRep $name -> BlockingCall fetchrows_ParseDone finished");
+ return;
+ }
+
+ my @row_array = split("\\|", $rowlist);
+
+ Log3 ($name, 5, "DbRep $name - row_array decoded: @row_array");
+
+ foreach my $row (@row_array) {
+ my @a = split("[ \t][ \t]*", $row, 5);
+ my $dev = $a[0];
+ my $rea = $a[1];
+ $a[3] =~ s/:/-/g; # substituieren unsopported characters -> siehe fhem.pl
+ my $ts = $a[2]."_".$a[3];
+ my $val = $a[4];
+
+ # Readingaufbereitung
+ if ($reading && AttrVal($hash->{NAME}, "readingNameMap", "")) {
+ $reading_runtime_string = $ts."__".AttrVal($hash->{NAME}, "readingNameMap", "") ;
+ } else {
+ $reading_runtime_string = $ts."__".$dev."__".$rea;
+ }
+
+ readingsSingleUpdate($hash, $reading_runtime_string, $val, 1);
+ }
+
+ readingsSingleUpdate($hash, "state", "done", 1);
+ readingsSingleUpdate($hash, "sql_processing_time", sprintf("%.4f",$rt), 1) if(AttrVal($name, "showproctime", undef));
+
+ delete($hash->{helper}{RUNNING_PID});
+ Log3 ($name, 4, "DbRep $name -> BlockingCall fetchrows_ParseDone finished");
+
+return;
+}
+
+####################################################################################################
+# Abbruchroutine Timeout DB-Abfrage
+####################################################################################################
+sub ParseAborted {
+my ($hash) = @_;
+my $name = $hash->{NAME};
+
+ Log3 ($name, 1, "DbRep $name -> BlockingCall $hash->{helper}{RUNNING_PID}{fn} timed out");
+ readingsSingleUpdate($hash, "state", "timeout", 1);
+ delete($hash->{helper}{RUNNING_PID});
+}
+
+
+
+################################################################################################################
+# Zusammenstellung Aggregationszeiträume
+################################################################################################################
+
+sub collaggstr {
+ my ($hash,$runtime,$i,$runtime_string_next) = @_;
+
+ my $name = $hash->{NAME};
+ my $runtime_string; # Datum/Zeit im SQL-Format für Readingname Teilstring
+ my $runtime_string_first; # Datum/Zeit Auswertungsbeginn im SQL-Format für SQL-Statement
+ my $ll; # loopindikator, wenn 1 = loopausstieg
+ my $runtime_orig; # orig. runtime als Grundlage für Addition mit $aggsec
+ my $tsstr = $hash->{HELPER}{CV}{tsstr}; # für Berechnung Tagesverschieber / Stundenverschieber
+ my $testr = $hash->{HELPER}{CV}{testr}; # für Berechnung Tagesverschieber / Stundenverschieber
+ my $dsstr = $hash->{HELPER}{CV}{dsstr}; # für Berechnung Tagesverschieber / Stundenverschieber
+ my $destr = $hash->{HELPER}{CV}{destr}; # für Berechnung Tagesverschieber / Stundenverschieber
+ my $msstr = $hash->{HELPER}{CV}{msstr}; # Startmonat für Berechnung Monatsverschieber
+ my $mestr = $hash->{HELPER}{CV}{mestr}; # Endemonat für Berechnung Monatsverschieber
+ my $ysstr = $hash->{HELPER}{CV}{ysstr}; # Startjahr für Berechnung Monatsverschieber
+ my $yestr = $hash->{HELPER}{CV}{yestr}; # Endejahr für Berechnung Monatsverschieber
+ my $aggregation = $hash->{HELPER}{CV}{aggregation}; # Aggregation
+ my $aggsec = $hash->{HELPER}{CV}{aggsec}; # laufende Aggregationssekunden
+ my $epoch_seconds_end = $hash->{HELPER}{CV}{epoch_seconds_end};
+ my $wdadd = $hash->{HELPER}{CV}{wdadd}; # Ergänzungstage. Starttag + Ergänzungstage = der folgende Montag (für week-Aggregation)
+
+ # only for this block because of warnings if some values not set
+ no warnings 'uninitialized';
+
+ # keine Aggregation (all between timestamps)
+ if ($aggregation eq "no") {
+ $runtime_string = "all between timestamps"; # für Readingname
+ $runtime_string_first = strftime "%Y-%m-%d %H:%M:%S", localtime($runtime);
+ $runtime_string_next = strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_end);
+ $ll = 1;
+ }
+
+ # Monatsaggregation
+ if ($aggregation eq "month") {
+
+ $runtime_orig = $runtime;
+
+ # Hilfsrechnungen
+ my $rm = strftime "%m", localtime($runtime); # Monat des aktuell laufenden Startdatums d. SQL-Select
+ my $ry = strftime "%Y", localtime($runtime); # Jahr des aktuell laufenden Startdatums d. SQL-Select
+ my $dim = $rm-2?30+($rm*3%7<4):28+!($ry%4||$ry%400*!($ry%100)); # Anzahl Tage des aktuell laufenden Monats f. SQL-Select
+ Log3 ($name, 5, "DbRep $name - act year: $ry, act month: $rm, days in month: $dim, endyear: $yestr, endmonth: $mestr");
+
+
+ $runtime_string = strftime "%Y-%m", localtime($runtime); # für Readingname
+
+ if ($i==1) {
+ # nur im ersten Durchlauf
+ $runtime_string_first = strftime "%Y-%m-%d %H:%M:%S", localtime($runtime);
+ }
+
+ if ($ysstr == $yestr && $msstr == $mestr || $ry == $yestr && $rm == $mestr) {
+ $runtime_string_first = strftime "%Y-%m-01", localtime($runtime) if($i>1);
+ $runtime_string_next = strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_end);
+ $ll=1;
+
+ } else {
+ if(($runtime) > $epoch_seconds_end) {
+ $runtime_string_first = strftime "%Y-%m-01", localtime($runtime) if($i>1);
+ $runtime_string_next = strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_end);
+ $ll=1;
+ } else {
+ $runtime_string_first = strftime "%Y-%m-01", localtime($runtime) if($i>1);
+ $runtime_string_next = strftime "%Y-%m-01", localtime($runtime+($dim*86400));
+
+ }
+ }
+ # my $help_string = strftime "%Y-%m-%d %H:%M:%S", localtime($runtime);
+ my ($yyyy1, $mm1, $dd1) = ($runtime_string_next =~ /(\d+)-(\d+)-(\d+)/);
+ $runtime = timelocal("00", "00", "00", "01", $mm1-1, $yyyy1-1900);
+
+ # neue Beginnzeit in Epoche-Sekunden
+ $runtime = $runtime_orig+$aggsec;
+ }
+
+ # Wochenaggregation
+ if ($aggregation eq "week") {
+
+ $runtime_orig = $runtime;
+
+ my $w = strftime "%V", localtime($runtime); # Wochennummer des aktuellen Startdatum/Zeit
+ $runtime_string = "week_".$w; # für Readingname
+
+ if ($i==1) {
+ # nur im ersten Schleifendurchlauf
+ $runtime_string_first = strftime "%Y-%m-%d %H:%M:%S", localtime($runtime);
+
+ # Korrektur $runtime_orig für Berechnung neue Beginnzeit für nächsten Durchlauf
+ my ($yyyy1, $mm1, $dd1) = ($runtime_string_first =~ /(\d+)-(\d+)-(\d+)/);
+ $runtime = timelocal("00", "00", "00", $dd1, $mm1-1, $yyyy1-1900);
+ $runtime = $runtime+$wdadd;
+ $runtime_orig = $runtime-$aggsec;
+
+ # die Woche Beginn ist gleich der Woche von Ende Auswertung
+ if((strftime "%V", localtime($epoch_seconds_end)) eq $w) {
+ $runtime_string_next = strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_end);
+ $ll=1;
+ } else {
+ $runtime_string_next = strftime "%Y-%m-%d", localtime($runtime);
+ }
+ } else {
+ # weitere Durchläufe
+ if(($runtime+$aggsec) > $epoch_seconds_end) {
+ $runtime_string_first = strftime "%Y-%m-%d", localtime($runtime_orig);
+ $runtime_string_next = strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_end);
+ $ll=1;
+ } else {
+ $runtime_string_first = strftime "%Y-%m-%d", localtime($runtime_orig) ;
+ $runtime_string_next = strftime "%Y-%m-%d", localtime($runtime+$aggsec);
+ }
+ }
+
+ # neue Beginnzeit in Epoche-Sekunden
+ $runtime = $runtime_orig+$aggsec;
+ }
+
+ # Tagesaggregation
+ if ($aggregation eq "day") {
+ $runtime_string = strftime "%Y-%m-%d", localtime($runtime); # für Readingname
+ $runtime_string_first = strftime "%Y-%m-%d %H:%M:%S", localtime($runtime) if($i==1);
+ $runtime_string_first = strftime "%Y-%m-%d", localtime($runtime) if($i>1);
+
+ if((($tsstr gt $testr) ? $runtime : ($runtime+$aggsec)) > $epoch_seconds_end) {
+ $runtime_string_first = strftime "%Y-%m-%d", localtime($runtime);
+ $runtime_string_first = strftime "%Y-%m-%d %H:%M:%S", localtime($runtime) if( $dsstr eq $destr);
+ $runtime_string_next = strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_end);
+ $ll=1;
+ } else {
+ $runtime_string_next = strftime "%Y-%m-%d", localtime($runtime+$aggsec);
+ }
+
+ # neue Beginnzeit in Epoche-Sekunden
+ $runtime = $runtime+$aggsec;
+ }
+
+ # Stundenaggregation
+ if ($aggregation eq "hour") {
+ $runtime_string = strftime "%Y-%m-%d_%H", localtime($runtime); # für Readingname
+ $runtime_string_first = strftime "%Y-%m-%d %H:%M:%S", localtime($runtime) if($i==1);
+ $runtime_string_first = strftime "%Y-%m-%d %H", localtime($runtime) if($i>1);
+
+ my @a = split (":",$tsstr);
+ my $hs = $a[0];
+ my $msstr = $a[1].":".$a[2];
+ @a = split (":",$testr);
+ my $he = $a[0];
+ my $mestr = $a[1].":".$a[2];
+
+ if((($msstr gt $mestr) ? $runtime : ($runtime+$aggsec)) > $epoch_seconds_end) {
+ $runtime_string_first = strftime "%Y-%m-%d %H", localtime($runtime);
+ $runtime_string_first = strftime "%Y-%m-%d %H:%M:%S", localtime($runtime) if( $dsstr eq $destr && $hs eq $he);
+ $runtime_string_next = strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_end);
+ $ll=1;
+ } else {
+ $runtime_string_next = strftime "%Y-%m-%d %H", localtime($runtime+$aggsec);
+ }
+
+ # neue Beginnzeit in Epoche-Sekunden
+ $runtime = $runtime+$aggsec;
+ }
+
+return ($runtime,$runtime_string,$runtime_string_first,$runtime_string_next,$ll);
+}
+
+1;
+
+=pod
+=item helper
+=begin html
+
+
+DbRep
+
+
+ The purpose of this module is browsing of DbLog-databases. The searchresults can be evaluated concerning to various aggregations and the appropriate
+ Readings will be filled.
+ Also a function to deletion of datasets is provided. The dataselection considering is done by declaration of device, reading and the time settings of
+ selection-begin and selection-end.
+
+ All database operations are implemented nonblocking. Optional the execution time of SQL-statements in background can also be determined and provided as reading.
+ (refer to attributes).
+
+ FHEM-Forum:
+ neues Modul 93_DbRep - Auswertungen und Reporting von Datenbankinhalten (DbLog).
+
+ Preparations
+
+ The module requires the usage of a DbLog instance and the credentials of the database definition will be used. (currently tested with MySQL and SQLite).
+ Only the content of table "history" will be included.
+
+ Due to performance reason the following index should be created in addition:
+
+ ALTER TABLE 'fhem'.'history' ADD INDEX `Reading_Time_Idx` (`READING`, `TIMESTAMP`) USING BTREE;
+
+
+
+
+
+Definition
+
+
+
+
+ define <name> DbRep <name of DbLog-instance>
+
+
+
+ (<name of DbLog-instance> - name of the database instance which is wanted to analyze needs to be inserted)
+
+
+
+
+
+
+Set
+
+
+ Currently following set-commands are included. They are used to trigger the evaluations and define the evaluation option option itself.
+ The criteria of searching database content and determine aggregation is carried out by setting several attributes.
+
+
+
+ - averageValue - calculates the average value of readingvalues DB-column "VALUE") between period given by attributes "timestamp_begin", "timestamp_end". The reading to evaluate must be defined using attribute "reading".
+ - countEntries - provides the number of DB-entries between period given by attributes "timestamp_begin", "timestamp_end".(if set). If timestamp-attributes are not set all entries in db will be count. The attributes "device" and "reading" can be used to limit the evaluation.
+ - fetchrows - provides all DB-entries between period given by attributes "timestamp_begin", "timestamp_end". A possibly set aggregation attribute will not considered.
+ - sumValue - calculates the amount of readingvalues DB-column "VALUE") between period given by attributes "timestamp_begin", "timestamp_end". The reading to evaluate must be defined using attribute "reading".
+ - maxValue - calculates the maximum value of readingvalues DB-column "VALUE") between period given by attributes "timestamp_begin", "timestamp_end". The reading to evaluate must be defined using attribute "reading". The evaluation contains the timestamp of the identified max values within the given period.
+ - delEntries - deletes all database entries or only the database entries specified by attributes Device and/or Reading and the entered time period between "timestamp_begin", "timestamp_end" (if set).
+
+ "timestamp_begin" is set: deletes db entries from this timestamp until current date/time
+ "timestamp_end" is set : deletes db entries until this timestamp
+ both Timestamps are set : deletes db entries between these timestamps
+
+
+
+
+
+ Due to security reasons the attribute "allowDeletion" needs to be set to unlock the delete-function.
+
+ For all evaluation variants applies:
+ In addition to the needed reading the device can be complemented to restrict the datasets for reporting / function.
+ If the attributes "timestamp_begin" and "timestamp_end" are not set, the period from '1970-01-01 01:00:00' to the current date/time will be used as selection criterion..
+
+
+ Note
+
+ All database action will excuted in background ! It could be necessary to refresh the browser to see the answer of operation if you are in detail view once the "state = done" is shown.
+
+
+
+
+
+
+Attribute
+
+
+
+ Using the module specific attributes you are able to define the scope of evaluation and the aggregation.
+
+
+ - aggregation - Aggregation of Device/Reading-selections. Possible is hour, day, week, month or "no". Delivers e.g. the count of database entries for a day (countEntries), Summation of difference values of a reading (sumValue) and so on. Using aggregation "no" (default) an aggregation don't happens but the output contaims all values of Device/Reading in the defined time period.
+ - allowDeletion - unlocks the delete-function
+ - device - selection of a particular device
+ - disable - deactivates the module
+ - reading - selection of a particular reading
+ - readingNameMap - the name of the analyzed reading can be overwritten for output
+ - showproctime - if set, the reading "sql_processing_time" shows the required execution time (in seconds) for the sql-requests. This is not calculated for a single sql-statement, but the summary of all sql-statements necessara for within an executed DbRep-function in background.
+ - timestamp_begin - begin of data selection
+ - timestamp_end - end of data selection. If not set the current date/time combination will be used.
+ - timeDiffToNow - if set, the begin of data selection will be set to the value "current time - timeDiffToNow" (in seconds). Thereby always the last <timeDiffToNow>-seconds will be considered (e.g. 86400 if always the last 24 hours should assumed).
+ - timeout - sets the timeout-value for Blocking-Call Routines in background (default 60 seconds)
+
+
+
+ The format of timestamp is as used in DbLog: YYYY-MM-DD HH:MM:SS.
+
+ Note
+
+ If the attribute <timeDiffToNow> will be set, the possibly set attributes <timestamp_begin> respectively <timestamp_end> will deleted.
+ The setting of <timestamp_begin> respectively <timestamp_end> causes the deletion of attribute <timeDiffToNow>, if set.
+
+
+
+
+
+=end html
+=begin html_DE
+
+
+DbRep
+
+
+ Zweck des Moduls ist es, den Inhalt von DbLog-Datenbanken nach bestimmten Kriterien zu durchsuchen und das Ergebnis hinsichtlich verschiedener
+ Aggregationen auszuwerten und als Readings darzustellen. Daneben wird eine Löschfunktionen für Datenbankinhalte zur Verfügung gestellt.
+ Die Daten können aggregiert werden. Die Abgrenzung der zu berücksichtigenden Datenbankinhalte erfolgt durch die Angabe von Device, Reading und
+ die Zeitgrenzen für Auswertungsbeginn bzw. Auswertungsende.
+
+ Alle Datenbankoperationen werden nichtblockierend ausgeführt. Die Ausführungszeit der SQL-Hintergrundoperationen kann optional ebenfalls als Reading bereitgestellt
+ werden (siehe Attribute).
+
+ FHEM-Forum:
+ neues Modul 93_DbRep - Auswertungen und Reporting von Datenbankinhalten (DbLog).
+
+ Voraussetzungen
+
+ Das Modul setzt den Einsatz einer DBLog-Instanz voraus. Es werden die Zugangsdaten dieser Datenbankdefinition genutzt (bisher getestet mit MySQL und SQLite).
+ Es werden nur Inhalte der Tabelle "history" berücksichtigt.
+
+ Aus Performancegründen sollten zusätzlich folgender Index erstellt werden:
+
+ ALTER TABLE 'fhem'.'history' ADD INDEX `Reading_Time_Idx` (`READING`, `TIMESTAMP`) USING BTREE;
+
+
+
+
+
+Definition
+
+
+
+
+ define <name> DbRep <Name der DbLog-instanz>
+
+
+
+ (<Name der DbLog-instanz> - es wird der Name der auszuwertenden DBLog-Datenbankdefinition angegeben)
+
+
+
+
+
+
+Set
+
+
+ Zur Zeit gibt es folgende Set-Kommandos. Über sie werden die Auswertungen angestoßen und definieren selbst die Auswertungsvariante.
+ Nach welchen Kriterien die Datenbankinhalte durchsucht werden und die Aggregation erfolgt, wird durch Attribute gesteuert.
+
+
+
+ - averageValue - berechnet den Durchschnittswert der Readingswerte (DB-Spalte "VALUE") in den Zeitgrenzen (Attribute) "timestamp_begin", "timestamp_end". Es muß das auszuwertende Reading über das Attribut "reading" angegeben sein.
+ - countEntries - liefert die Anzahl der DB-Einträge in den Zeitgrenzen (Attribute) "timestamp_begin", "timestamp_end" (wenn gesetzt). Sind die Timestamps nicht gesetzt werden alle Einträge gezählt. Beschränkungen durch die Attribute Device bzw. Reading gehen in die Selektion mit ein.
+ - fetchrows - liefert alle DB-Einträge in den Zeitgrenzen (Attribute) "timestamp_begin", "timestamp_end". Eine evtl. gesetzte Aggregation wird nicht berücksichtigt.
+ - sumValue - berechnet die Summenwerte der Readingswerte (DB-Spalte "VALUE") in den Zeitgrenzen (Attribute) "timestamp_begin", "timestamp_end". Es muss das auszuwertende Reading über das Attribut "reading" angegeben sein.
+ - maxValue - berechnet den Maximalwert der Readingswerte (DB-Spalte "VALUE") in den Zeitgrenzen (Attribute) "timestamp_begin", "timestamp_end". Es muss das auszuwertende Reading über das Attribut "reading" angegeben sein. Die Auswertung enthält den Zeitstempel des ermittelten Maximalwertes innerhalb der Aggregation bzw. Zeitgrenzen.
+ - delEntries - löscht alle oder die durch die Attribute device und/oder reading definierten Datenbankeinträge. Die Eingrenzung über Timestamps erfolgt folgendermaßen:
+
+ "timestamp_begin" gesetzt: gelöscht werden DB-Einträge ab diesem Zeitpunkt bis zum aktuellen Datum/Zeit
+ "timestamp_end" gesetzt : gelöscht werden DB-Einträge bis bis zu diesem Zeitpunkt
+ beide Timestamps gesetzt : gelöscht werden DB-Einträge zwischen diesen Zeitpunkten
+
+
+
+
+
+ Aus Sicherheitsgründen muss das Attribut "allowDeletion" gesetzt sein um die Löschfunktion freizuschalten.
+
+ Für alle Auswertungsvarianten gilt:
+ Zusätzlich zu dem auszuwertenden Reading kann das Device mit angegeben werden um das Reporting nach diesen Kriterien einzuschränken.
+ Sind die Attribute "timestamp_begin", "timestamp_end" nicht angegeben, wird '1970-01-01 01:00:00' und das aktuelle Datum/Zeit als Zeitgrenze genutzt.
+
+
+ Hinweis
+
+ Da alle DB-Operationen im Hintergrund ausgeführt werden, kann in der Detailansicht ein Browserrefresh nötig sein um die Operationsergebnisse zu sehen sobald "state = done" angezeigt wird.
+
+
+
+
+
+
+Attribute
+
+
+
+ Über die modulspezifischen Attribute wird die Abgrenzung der Auswertung und die Aggregation der Werte gesteuert.
+
+
+ - aggregation - Zusammenfassung der Device/Reading-Selektionen in Stunden,Tages,Kalenderwochen,Kalendermonaten oder "no". Liefert z.B. die Anzahl der DB-Einträge am Tag (countEntries), Summation von Differenzwerten eines Readings (sumValue), usw. Mit Aggregation "no" (default) erfolgt keine Zusammenfassung in einem Zeitraum sondern die Ausgabe ergibt alle Werte eines Device/Readings zwischen den definierten Zeiträumen.
+ - allowDeletion - schaltet die Löschfunktion des Moduls frei
+ - device - Abgrenzung der DB-Selektionen auf ein bestimmtes Device
+ - disable - deaktiviert das Modul
+ - reading - Abgrenzung der DB-Selektionen auf ein bestimmtes Reading
+ - readingNameMap - der Name des ausgewerteten Readings wird mit diesem String für die Anzeige überschrieben
+ - showproctime - wenn gesetzt, zeigt das Reading "sql_processing_time" die benötigte Abarbeitungszeit (in Sekunden) für die SQL-Ausführung der durchgeführten Funktion. Dabei wird nicht ein einzelnes SQl-Statement, sondern die Summe aller notwendigen SQL-Abfragen innerhalb der jeweiligen Funktion betrachtet.
+ - timestamp_begin - der zeitliche Beginn für die Datenselektion
+ - timestamp_end - das zeitliche Ende für die Datenselektion. Wenn nicht gesetzt wird immer die aktuelle Datum/Zeit-Kombi für das Ende der Selektion eingesetzt.
+ - timeDiffToNow - wenn gesetzt, wird der Selektionsbeginn auf den Zeitpunkt "aktuelle Zeit - timeDiffToNow" gesetzt (in Sekunden). Dadurch werden immer die letzten <timeDiffToNow>-Sekunden berücksichtigt (z.b. 86400 wenn immer die letzten 24 Stunden in die Selektion eingehen sollen).
+ - timeout - das Attribut setzt den Timeout-Wert für die Blocking-Call Routinen (Standard 60) in Sekunden
+
+
+
+ Das Format von Timestamp ist wie in DbLog YYYY-MM-DD HH:MM:SS.
+
+ Hinweis
+
+ Wird das Attribut <timeDiffToNow> gesetzt, werden die evtentuell gesetzten Attribute <timestamp_begin> bzw. <timestamp_end> gelöscht.
+ Das Setzen von <timestamp_begin> bzw. <timestamp_end> bedingt die Löschung von Attribut <timeDiffToNow>, wenn gesetzt.
+
+
+
+
+=end html_DE
+=cut
\ No newline at end of file