2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-03-10 03:06:37 +00:00

93_DbLog: contrib 4.7.2

git-svn-id: https://svn.fhem.de/fhem/trunk@20289 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
nasseeder1 2019-10-02 20:43:29 +00:00
parent 4f0d58405d
commit 11a1b5d4d2

View File

@ -1,5 +1,5 @@
############################################################################################################################################ ############################################################################################################################################
# $Id: 93_DbLog.pm 20093 2019-09-02 07:17:38Z DS_Starter $ # $Id: 93_DbLog.pm 20114 2019-09-06 11:21:03Z DS_Starter $
# #
# 93_DbLog.pm # 93_DbLog.pm
# written by Dr. Boris Neubert 2007-12-30 # written by Dr. Boris Neubert 2007-12-30
@ -30,6 +30,9 @@ no if $] >= 5.017011, warnings => 'experimental::smartmatch';
# Version History intern by DS_Starter: # Version History intern by DS_Starter:
our %DbLog_vNotesIntern = ( our %DbLog_vNotesIntern = (
"4.7.3" => "02.10.2019 Test ",
"4.7.2" => "28.09.2019 change cache from %defs to %data ",
"4.7.1" => "10.09.2019 release the memcache memory: https://www.effectiveperlprogramming.com/2018/09/undef-a-scalar-to-release-its-memory/ in asynchron mode: https://www.effectiveperlprogramming.com/2018/09/undef-a-scalar-to-release-its-memory/ ",
"4.7.0" => "04.09.2019 attribute traceHandles, extract db driver versions in configCheck ", "4.7.0" => "04.09.2019 attribute traceHandles, extract db driver versions in configCheck ",
"4.6.0" => "03.09.2019 add-on parameter \"force\" for MinInterval, Forum: #97148 ", "4.6.0" => "03.09.2019 add-on parameter \"force\" for MinInterval, Forum: #97148 ",
"4.5.0" => "28.08.2019 consider attr global logdir in set exportCache ", "4.5.0" => "28.08.2019 consider attr global logdir in set exportCache ",
@ -224,8 +227,7 @@ sub DbLog_dbReadings($@);
sub DbLog_showChildHandles($$$$); sub DbLog_showChildHandles($$$$);
################################################################ ################################################################
sub DbLog_Initialize($) sub DbLog_Initialize($) {
{
my ($hash) = @_; my ($hash) = @_;
$hash->{DefFn} = "DbLog_Define"; $hash->{DefFn} = "DbLog_Define";
@ -237,36 +239,36 @@ sub DbLog_Initialize($)
$hash->{SVG_regexpFn} = "DbLog_regexpFn"; $hash->{SVG_regexpFn} = "DbLog_regexpFn";
$hash->{DelayedShutdownFn} = "DbLog_DelayedShutdown"; $hash->{DelayedShutdownFn} = "DbLog_DelayedShutdown";
$hash->{AttrList} = "addStateEvent:0,1 ". $hash->{AttrList} = "addStateEvent:0,1 ".
"asyncMode:1,0 ".
"bulkInsert:1,0 ". "bulkInsert:1,0 ".
"commitMode:basic_ta:on,basic_ta:off,ac:on_ta:on,ac:on_ta:off,ac:off_ta:on ". "commitMode:basic_ta:on,basic_ta:off,ac:on_ta:on,ac:on_ta:off,ac:off_ta:on ".
"cacheEvents:2,1,0 ".
"cacheLimit ".
"colEvent ". "colEvent ".
"colReading ". "colReading ".
"colValue ". "colValue ".
"DbLogSelectionMode:Exclude,Include,Exclude/Include ".
"DbLogType:Current,History,Current/History,SampleFill/History ".
"dbSchema ". "dbSchema ".
"disable:1,0 ". "disable:1,0 ".
"DbLogType:Current,History,Current/History,SampleFill/History ".
"suppressUndef:0,1 ".
"verbose4Devs ".
"excludeDevs ". "excludeDevs ".
"expimpdir ". "expimpdir ".
"exportCacheAppend:1,0 ". "exportCacheAppend:1,0 ".
"noSupportPK:1,0 ".
"noNotifyDev:1,0 ". "noNotifyDev:1,0 ".
"showproctime:1,0 ". "showproctime:1,0 ".
"suppressAddLogV3:1,0 ". "suppressAddLogV3:1,0 ".
"traceFlag:SQL,CON,ENC,DBD,TXN,ALL ". "suppressUndef:0,1 ".
"traceLevel:0,1,2,3,4,5,6,7 ".
"traceHandles ".
"asyncMode:1,0 ".
"cacheEvents:2,1,0 ".
"cacheLimit ".
"noSupportPK:1,0 ".
"syncEvents:1,0 ". "syncEvents:1,0 ".
"syncInterval ". "syncInterval ".
"showNotifyTime:1,0 ". "showNotifyTime:1,0 ".
"traceFlag:SQL,CON,ENC,DBD,TXN,ALL ".
"traceLevel:0,1,2,3,4,5,6,7 ".
"traceHandles ".
"timeout ". "timeout ".
"useCharfilter:0,1 ". "useCharfilter:0,1 ".
"valueFn:textField-long ". "valueFn:textField-long ".
"DbLogSelectionMode:Exclude,Include,Exclude/Include ". "verbose4Devs ".
$readingFnAttributes; $readingFnAttributes;
addToAttrList("DbLogInclude"); addToAttrList("DbLogInclude");
@ -282,8 +284,7 @@ return;
} }
############################################################### ###############################################################
sub DbLog_Define($@) sub DbLog_Define($@) {
{
my ($hash, $def) = @_; my ($hash, $def) = @_;
my @a = split("[ \t][ \t]*", $def); my @a = split("[ \t][ \t]*", $def);
@ -293,6 +294,7 @@ sub DbLog_Define($@)
return "wrong syntax: define <name> DbLog configuration regexp" return "wrong syntax: define <name> DbLog configuration regexp"
if(int(@a) != 4); if(int(@a) != 4);
my $name = $hash->{NAME};
$hash->{CONFIGURATION} = $a[2]; $hash->{CONFIGURATION} = $a[2];
my $regexp = $a[3]; my $regexp = $a[3];
@ -316,7 +318,7 @@ sub DbLog_Define($@)
$hash->{PID} = $$; $hash->{PID} = $$;
# CacheIndex für Events zum asynchronen Schreiben in DB # CacheIndex für Events zum asynchronen Schreiben in DB
$hash->{cache}{index} = 0; $data{DbLog}{$name}{cache}{index} = 0;
# read configuration data # read configuration data
my $ret = DbLog_readCfg($hash); my $ret = DbLog_readCfg($hash);
@ -348,6 +350,7 @@ sub DbLog_Undef($$) {
BlockingKill($hash->{HELPER}{DELDAYS_PID}) if($hash->{HELPER}{DELDAYS_PID}); BlockingKill($hash->{HELPER}{DELDAYS_PID}) if($hash->{HELPER}{DELDAYS_PID});
$dbh->disconnect() if(defined($dbh)); $dbh->disconnect() if(defined($dbh));
RemoveInternalTimer($hash); RemoveInternalTimer($hash);
delete $data{DbLog}{$name};
return undef; return undef;
} }
@ -485,7 +488,9 @@ sub DbLog_Attr(@) {
} }
if ($aName eq "traceHandles") { if ($aName eq "traceHandles") {
unless ($aVal =~ /^[0-9]+$/) { return " The Value of $aName is not valid. Use only figures 0-9 without decimal places !";} if($cmd eq "set") {
unless ($aVal =~ /^[0-9]+$/) {return " The Value of $aName is not valid. Use only figures 0-9 without decimal places !";}
}
RemoveInternalTimer($hash, "DbLog_startShowChildhandles"); RemoveInternalTimer($hash, "DbLog_startShowChildhandles");
if($cmd eq "set") { if($cmd eq "set") {
$do = ($aVal) ? 1 : 0; $do = ($aVal) ? 1 : 0;
@ -671,7 +676,7 @@ sub DbLog_Set($@) {
$ret = "Rereadcfg executed."; $ret = "Rereadcfg executed.";
} }
elsif ($a[1] eq 'purgeCache') { elsif ($a[1] eq 'purgeCache') {
delete $hash->{cache}; delete $data{DbLog}{$name}{cache};
readingsSingleUpdate($hash, 'CacheUsage', 0, 1); readingsSingleUpdate($hash, 'CacheUsage', 0, 1);
} }
elsif ($a[1] eq 'commitCache') { elsif ($a[1] eq 'commitCache') {
@ -679,8 +684,8 @@ sub DbLog_Set($@) {
} }
elsif ($a[1] eq 'listCache') { elsif ($a[1] eq 'listCache') {
my $cache; my $cache;
foreach my $key (sort{$a <=>$b}keys%{$hash->{cache}{".memcache"}}) { foreach my $key (sort{$a <=>$b}keys %{$data{DbLog}{$name}{cache}{memcache}}) {
$cache .= $key." => ".$hash->{cache}{".memcache"}{$key}."\n"; $cache .= $key." => ".$data{DbLog}{$name}{cache}{memcache}{$key}."\n";
} }
return $cache; return $cache;
} }
@ -742,8 +747,8 @@ sub DbLog_Set($@) {
} }
if(!$error) { if(!$error) {
foreach my $key (sort(keys%{$hash->{cache}{".memcache"}})) { foreach my $key (sort(keys %{$data{DbLog}{$name}{cache}{memcache}})) {
$cln = $hash->{cache}{".memcache"}{$key}."\n"; $cln = $data{DbLog}{$name}{cache}{memcache}{$key}."\n";
print FH $cln ; print FH $cln ;
$crows++; $crows++;
} }
@ -761,7 +766,7 @@ sub DbLog_Set($@) {
Log3($name, 3, "DbLog $name: $crows cache rows exported to $outfile."); Log3($name, 3, "DbLog $name: $crows cache rows exported to $outfile.");
if (lc($a[-1]) =~ m/^purgecache/i) { if (lc($a[-1]) =~ m/^purgecache/i) {
delete $hash->{cache}; delete $data{DbLog}{$name}{cache};
readingsSingleUpdate($hash, 'CacheUsage', 0, 1); readingsSingleUpdate($hash, 'CacheUsage', 0, 1);
Log3($name, 3, "DbLog $name: Cache purged after exporting rows to $outfile."); Log3($name, 3, "DbLog $name: Cache purged after exporting rows to $outfile.");
} }
@ -1488,11 +1493,11 @@ sub DbLog_Log($$) {
if($async) { if($async) {
# asynchoner non-blocking Mode # asynchoner non-blocking Mode
# Cache & CacheIndex für Events zum asynchronen Schreiben in DB # Cache & CacheIndex für Events zum asynchronen Schreiben in DB
$hash->{cache}{index}++; $data{DbLog}{$name}{cache}{index}++;
my $index = $hash->{cache}{index}; my $index = $data{DbLog}{$name}{cache}{index};
$hash->{cache}{".memcache"}{$index} = $row; $data{DbLog}{$name}{cache}{memcache}{$index} = $row;
my $memcount = $hash->{cache}{".memcache"}?scalar(keys%{$hash->{cache}{".memcache"}}):0; my $memcount = $data{DbLog}{$name}{cache}{memcache}?scalar(keys %{$data{DbLog}{$name}{cache}{memcache}}):0;
if($ce == 1) { if($ce == 1) {
readingsSingleUpdate($hash, "CacheUsage", $memcount, 1); readingsSingleUpdate($hash, "CacheUsage", $memcount, 1);
} else { } else {
@ -1518,7 +1523,7 @@ sub DbLog_Log($$) {
} }
} }
}; };
if(!$async) { if(!$async) {
if(@row_array) { if(@row_array) {
# synchoner Mode # synchoner Mode
return if($hash->{HELPER}{REOPEN_RUNS}); # return wenn "reopen" mit Ablaufzeit gestartet ist return if($hash->{HELPER}{REOPEN_RUNS}); # return wenn "reopen" mit Ablaufzeit gestartet ist
@ -2109,7 +2114,7 @@ sub DbLog_execmemcache ($) {
} }
} }
$memcount = $hash->{cache}{".memcache"}?scalar(keys%{$hash->{cache}{".memcache"}}):0; $memcount = $data{DbLog}{$name}{cache}{memcache}?scalar(keys %{$data{DbLog}{$name}{cache}{memcache}}):0;
if($ce == 2) { if($ce == 2) {
readingsSingleUpdate($hash, "CacheUsage", $memcount, 1); readingsSingleUpdate($hash, "CacheUsage", $memcount, 1);
} else { } else {
@ -2121,12 +2126,13 @@ sub DbLog_execmemcache ($) {
Log3 $name, 4, "DbLog $name -> ### New database processing cycle - asynchronous ###"; Log3 $name, 4, "DbLog $name -> ### New database processing cycle - asynchronous ###";
Log3 $name, 4, "DbLog $name -> ################################################################"; Log3 $name, 4, "DbLog $name -> ################################################################";
Log3 $name, 4, "DbLog $name -> MemCache contains $memcount entries to process"; Log3 $name, 4, "DbLog $name -> MemCache contains $memcount entries to process";
Log3 $name, 4, "DbLog $name -> DbLogType is: $DbLogType"; Log3 $name, 4, "DbLog $name -> DbLogType is: $DbLogType";
foreach my $key (sort(keys%{$hash->{cache}{".memcache"}})) { foreach my $key (sort(keys %{$data{DbLog}{$name}{cache}{memcache}})) {
Log3 $hash->{NAME}, 5, "DbLog $name -> MemCache contains: ".$hash->{cache}{".memcache"}{$key}; Log3 $hash->{NAME}, 5, "DbLog $name -> MemCache contains: ".$data{DbLog}{$name}{cache}{memcache}{$key};
push(@row_array, delete($hash->{cache}{".memcache"}{$key})); push(@row_array, delete($data{DbLog}{$name}{cache}{memcache}{$key}));
} }
delete $data{DbLog}{$name}{cache}{memcache}; # sicherheitshalber Memory freigeben: https://www.effectiveperlprogramming.com/2018/09/undef-a-scalar-to-release-its-memory/
my $rowlist = join('§', @row_array); my $rowlist = join('§', @row_array);
$rowlist = encode_base64($rowlist,""); $rowlist = encode_base64($rowlist,"");
@ -2697,15 +2703,15 @@ sub DbLog_PushAsyncDone ($) {
eval { eval {
foreach my $row (@row_array) { foreach my $row (@row_array) {
# Cache & CacheIndex für Events zum asynchronen Schreiben in DB # Cache & CacheIndex für Events zum asynchronen Schreiben in DB
$hash->{cache}{index}++; $data{DbLog}{$name}{cache}{index}++;
my $index = $hash->{cache}{index}; my $index = $data{DbLog}{$name}{cache}{index};
$hash->{cache}{".memcache"}{$index} = $row; $data{DbLog}{$name}{cache}{memcache}{$index} = $row;
} }
$memcount = scalar(keys%{$hash->{cache}{".memcache"}}); $memcount = scalar(keys %{$data{DbLog}{$name}{cache}{memcache}});
}; };
} }
$memcount = $hash->{cache}{".memcache"}?scalar(keys%{$hash->{cache}{".memcache"}}):0; $memcount = $data{DbLog}{$name}{cache}{memcache}?scalar(keys %{$data{DbLog}{$name}{cache}{memcache}}):0;
readingsSingleUpdate($hash, 'CacheUsage', $memcount, 0); readingsSingleUpdate($hash, 'CacheUsage', $memcount, 0);
if(AttrVal($name, "showproctime", undef) && $bt) { if(AttrVal($name, "showproctime", undef) && $bt) {
@ -3201,6 +3207,7 @@ sub DbLog_Get($@) {
$minval = (~0 >> 1); $minval = (~0 >> 1);
$maxval = -(~0 >> 1); $maxval = -(~0 >> 1);
$deltacalc = 0; $deltacalc = 0;
my $ldetad = ""; # Test
if($readings[$i]->[3] && ($readings[$i]->[3] eq "delta-h" || $readings[$i]->[3] eq "delta-d")) { if($readings[$i]->[3] && ($readings[$i]->[3] eq "delta-h" || $readings[$i]->[3] eq "delta-d")) {
$deltacalc = 1; $deltacalc = 1;
@ -3296,24 +3303,24 @@ sub DbLog_Get($@) {
# Value angepasst. # Value angepasst.
#################################################################### ####################################################################
if($readings[$i]->[4]) { if($readings[$i]->[4]) {
#evaluate #evaluate
my $val = $sql_value; my $val = $sql_value;
my $ts = $sql_timestamp; my $ts = $sql_timestamp;
eval("$readings[$i]->[4]"); eval("$readings[$i]->[4]");
$sql_value = $val; $sql_value = $val;
$sql_timestamp = $ts; $sql_timestamp = $ts;
if($@) {Log3 $hash->{NAME}, 3, "DbLog: Error in inline function: <".$readings[$i]->[4].">, Error: $@";} if($@) {Log3 $hash->{NAME}, 3, "DbLog: Error in inline function: <".$readings[$i]->[4].">, Error: $@";}
} }
if($sql_timestamp lt $from && $deltacalc) { if($sql_timestamp lt $from && $deltacalc) {
if(Scalar::Util::looks_like_number($sql_value)){ if(Scalar::Util::looks_like_number($sql_value)){
#nur setzen wenn nummerisch #nur setzen wenn numerisch
$minval = $sql_value if($sql_value < $minval); $minval = $sql_value if($sql_value < $minval);
$maxval = $sql_value if($sql_value > $maxval); $maxval = $sql_value if($sql_value > $maxval);
$lastv[$i] = $sql_value; $lastv[$i] = $sql_value;
} }
} else { } else {
$writeout = 0; $writeout = 0;
$out_value = ""; $out_value = "";
$out_tstamp = ""; $out_tstamp = "";
@ -3350,93 +3357,100 @@ sub DbLog_Get($@) {
$out_value = sprintf("%02d", $akt_ts - $last_ts); $out_value = sprintf("%02d", $akt_ts - $last_ts);
if(lc($sql_value) =~ m(hide)){$writeout=0;} else {$writeout=1;} if(lc($sql_value) =~ m(hide)){$writeout=0;} else {$writeout=1;}
} elsif ($readings[$i]->[3] && $readings[$i]->[3] eq "delta-h") { } elsif ($readings[$i]->[3] && $readings[$i]->[3] eq "delta-h") { # Berechnung eines Delta-Stundenwertes
#Berechnung eines Stundenwertes %tstamp = DbLog_explode_datetime($sql_timestamp, ());
%tstamp = DbLog_explode_datetime($sql_timestamp, ()); if($lastd[$i] eq "undef") {
if($lastd[$i] eq "undef") { %lasttstamp = DbLog_explode_datetime($sql_timestamp, ());
%lasttstamp = DbLog_explode_datetime($sql_timestamp, ()); $lasttstamp{hour} = "00";
$lasttstamp{hour} = "00"; } else {
} else { %lasttstamp = DbLog_explode_datetime($lastd[$i], ());
%lasttstamp = DbLog_explode_datetime($lastd[$i], ());
}
# 04 01
# 06 23
if("$tstamp{hour}" ne "$lasttstamp{hour}") {
# Aenderung der stunde, Berechne Delta
#wenn die Stundendifferenz größer 1 ist muss ein Dummyeintrag erstellt werden
$retvaldummy = "";
if(($tstamp{hour}-$lasttstamp{hour}) > 1) {
for (my $j=$lasttstamp{hour}+1; $j < $tstamp{hour}; $j++) {
$out_value = "0";
$hour = $j;
$hour = '0'.$j if $j<10;
$cnt[$i]++;
$out_tstamp = DbLog_implode_datetime($tstamp{year}, $tstamp{month}, $tstamp{day}, $hour, "30", "00");
if ($outf =~ m/(all)/) {
# Timestamp: Device, Type, Event, Reading, Value, Unit
$retvaldummy .= sprintf("%s: %s, %s, %s, %s, %s, %s\n", $out_tstamp, $sql_device, $type, $event, $sql_reading, $out_value, $unit);
} elsif ($outf =~ m/(array)/) {
push(@ReturnArray, {"tstamp" => $out_tstamp, "device" => $sql_device, "type" => $type, "event" => $event, "reading" => $sql_reading, "value" => $out_value, "unit" => $unit});
} else {
$out_tstamp =~ s/\ /_/g; #needed by generating plots
$retvaldummy .= "$out_tstamp $out_value\n";
}
}
} }
if(($tstamp{hour}-$lasttstamp{hour}) < 0) { # 04 01
for (my $j=0; $j < $tstamp{hour}; $j++) { # 06 23
$out_value = "0"; if("$tstamp{hour}" ne "$lasttstamp{hour}") {
$hour = $j; # Aenderung der stunde, Berechne Delta
$hour = '0'.$j if $j<10; # wenn die Stundendifferenz größer 1 ist muss ein Dummyeintrag erstellt werden
$cnt[$i]++; $retvaldummy = "";
$out_tstamp = DbLog_implode_datetime($tstamp{year}, $tstamp{month}, $tstamp{day}, $hour, "30", "00"); if(($tstamp{hour}-$lasttstamp{hour}) > 1) {
if ($outf =~ m/(all)/) { for (my $j=$lasttstamp{hour}+1; $j < $tstamp{hour}; $j++) {
# Timestamp: Device, Type, Event, Reading, Value, Unit $out_value = "0";
$retvaldummy .= sprintf("%s: %s, %s, %s, %s, %s, %s\n", $out_tstamp, $sql_device, $type, $event, $sql_reading, $out_value, $unit); $hour = $j;
$hour = '0'.$j if $j<10;
$cnt[$i]++;
$out_tstamp = DbLog_implode_datetime($tstamp{year}, $tstamp{month}, $tstamp{day}, $hour, "30", "00");
if ($outf =~ m/(all)/) {
# Timestamp: Device, Type, Event, Reading, Value, Unit
$retvaldummy .= sprintf("%s: %s, %s, %s, %s, %s, %s\n", $out_tstamp, $sql_device, $type, $event, $sql_reading, $out_value, $unit);
} elsif ($outf =~ m/(array)/) { } elsif ($outf =~ m/(array)/) {
push(@ReturnArray, {"tstamp" => $out_tstamp, "device" => $sql_device, "type" => $type, "event" => $event, "reading" => $sql_reading, "value" => $out_value, "unit" => $unit}); push(@ReturnArray, {"tstamp" => $out_tstamp, "device" => $sql_device, "type" => $type, "event" => $event, "reading" => $sql_reading, "value" => $out_value, "unit" => $unit});
} else { } else {
$out_tstamp =~ s/\ /_/g; #needed by generating plots $out_tstamp =~ s/\ /_/g; #needed by generating plots
$retvaldummy .= "$out_tstamp $out_value\n"; $retvaldummy .= "$out_tstamp $out_value\n";
}
}
} }
} if(($tstamp{hour}-$lasttstamp{hour}) < 0) {
for (my $j=0; $j < $tstamp{hour}; $j++) {
$out_value = "0";
$hour = $j;
$hour = '0'.$j if $j<10;
$cnt[$i]++;
$out_tstamp = DbLog_implode_datetime($tstamp{year}, $tstamp{month}, $tstamp{day}, $hour, "30", "00");
if ($outf =~ m/(all)/) {
# Timestamp: Device, Type, Event, Reading, Value, Unit
$retvaldummy .= sprintf("%s: %s, %s, %s, %s, %s, %s\n", $out_tstamp, $sql_device, $type, $event, $sql_reading, $out_value, $unit);
} elsif ($outf =~ m/(array)/) {
push(@ReturnArray, {"tstamp" => $out_tstamp, "device" => $sql_device, "type" => $type, "event" => $event, "reading" => $sql_reading, "value" => $out_value, "unit" => $unit});
} else {
$out_tstamp =~ s/\ /_/g; #needed by generating plots
$retvaldummy .= "$out_tstamp $out_value\n";
}
}
}
$out_value = sprintf("%g", $maxval - $minval);
$sum[$i] += $out_value;
$cnt[$i]++;
$out_tstamp = DbLog_implode_datetime($lasttstamp{year}, $lasttstamp{month}, $lasttstamp{day}, $lasttstamp{hour}, "30", "00");
# $minval = (~0 >> 1);
$minval = $maxval;
# $maxval = -(~0 >> 1);
$writeout = 1;
$writeout = 0 if($out_value eq $ldetad); # Test
} }
$out_value = sprintf("%g", $maxval - $minval);
$sum[$i] += $out_value;
$cnt[$i]++;
$out_tstamp = DbLog_implode_datetime($lasttstamp{year}, $lasttstamp{month}, $lasttstamp{day}, $lasttstamp{hour}, "30", "00");
#$minval = (~0 >> 1);
$minval = $maxval;
# $maxval = -(~0 >> 1);
$writeout=1;
}
} elsif ($readings[$i]->[3] && $readings[$i]->[3] eq "delta-d") { } elsif ($readings[$i]->[3] && $readings[$i]->[3] eq "delta-d") {
#Berechnung eines Tageswertes #Berechnung eines Tageswertes
%tstamp = DbLog_explode_datetime($sql_timestamp, ()); %tstamp = DbLog_explode_datetime($sql_timestamp, ());
if($lastd[$i] eq "undef") {
%lasttstamp = DbLog_explode_datetime($sql_timestamp, ()); if($lastd[$i] eq "undef") {
} else { %lasttstamp = DbLog_explode_datetime($sql_timestamp, ());
%lasttstamp = DbLog_explode_datetime($lastd[$i], ()); } else {
} %lasttstamp = DbLog_explode_datetime($lastd[$i], ());
if("$tstamp{day}" ne "$lasttstamp{day}") { }
# Aenderung des Tages, Berechne Delta
$out_value = sprintf("%g", $maxval - $minval); if("$tstamp{day}" ne "$lasttstamp{day}") {
$sum[$i] += $out_value; # Aenderung des Tages, Berechne Delta
$cnt[$i]++; $out_value = sprintf("%g", $maxval - $minval);
$out_tstamp = DbLog_implode_datetime($lasttstamp{year}, $lasttstamp{month}, $lasttstamp{day}, "12", "00", "00"); $sum[$i] += $out_value;
# $minval = (~0 >> 1); $cnt[$i]++;
$minval = $maxval; $out_tstamp = DbLog_implode_datetime($lasttstamp{year}, $lasttstamp{month}, $lasttstamp{day}, "12", "00", "00");
# $maxval = -(~0 >> 1); # $minval = (~0 >> 1);
$writeout=1; $minval = $maxval;
} # $maxval = -(~0 >> 1);
$writeout = 1;
$ldetad = $out_value; # Test
}
} else { } else {
$out_value = $sql_value; $out_value = $sql_value;
$out_tstamp = $sql_timestamp; $out_tstamp = $sql_timestamp;
$writeout=1; $writeout = 1;
} }
# Wenn Attr SuppressUndef gesetzt ist, dann ausfiltern aller undef-Werte # Wenn Attr SuppressUndef gesetzt ist, dann ausfiltern aller undef-Werte
@ -3445,22 +3459,22 @@ sub DbLog_Get($@) {
###################### Ausgabe ########################### ###################### Ausgabe ###########################
if($writeout) { if($writeout) {
if ($outf =~ m/(all)/) { if ($outf =~ m/(all)/) {
# Timestamp: Device, Type, Event, Reading, Value, Unit # Timestamp: Device, Type, Event, Reading, Value, Unit
$retval .= sprintf("%s: %s, %s, %s, %s, %s, %s\n", $out_tstamp, $sql_device, $type, $event, $sql_reading, $out_value, $unit); $retval .= sprintf("%s: %s, %s, %s, %s, %s, %s\n", $out_tstamp, $sql_device, $type, $event, $sql_reading, $out_value, $unit);
$retval .= $retvaldummy; $retval .= $retvaldummy;
} elsif ($outf =~ m/(array)/) { } elsif ($outf =~ m/(array)/) {
push(@ReturnArray, {"tstamp" => $out_tstamp, "device" => $sql_device, "type" => $type, "event" => $event, "reading" => $sql_reading, "value" => $out_value, "unit" => $unit}); push(@ReturnArray, {"tstamp" => $out_tstamp, "device" => $sql_device, "type" => $type, "event" => $event, "reading" => $sql_reading, "value" => $out_value, "unit" => $unit});
} else { } else {
$out_tstamp =~ s/\ /_/g; #needed by generating plots $out_tstamp =~ s/\ /_/g; #needed by generating plots
$retval .= "$out_tstamp $out_value\n"; $retval .= "$out_tstamp $out_value\n";
$retval .= $retvaldummy; $retval .= $retvaldummy;
} }
} }
if(Scalar::Util::looks_like_number($sql_value)){ if(Scalar::Util::looks_like_number($sql_value)) {
#nur setzen wenn nummerisch # nur setzen wenn numerisch
if($deltacalc) { if($deltacalc) {
if(Scalar::Util::looks_like_number($out_value)){ if(Scalar::Util::looks_like_number($out_value)){
if($out_value < $min[$i]) { if($out_value < $min[$i]) {
@ -3473,6 +3487,7 @@ sub DbLog_Get($@) {
} }
} }
$maxval = $sql_value; $maxval = $sql_value;
} else { } else {
if($sql_value < $min[$i]) { if($sql_value < $min[$i]) {
$min[$i] = $sql_value; $min[$i] = $sql_value;
@ -3486,6 +3501,7 @@ sub DbLog_Get($@) {
$minval = $sql_value if($sql_value < $minval); $minval = $sql_value if($sql_value < $minval);
$maxval = $sql_value if($sql_value > $maxval); $maxval = $sql_value if($sql_value > $maxval);
} }
} else { } else {
$min[$i] = 0; $min[$i] = 0;
$max[$i] = 0; $max[$i] = 0;
@ -3493,15 +3509,16 @@ sub DbLog_Get($@) {
$minval = 0; $minval = 0;
$maxval = 0; $maxval = 0;
} }
if(!$deltacalc) { if(!$deltacalc) {
$cnt[$i]++; $cnt[$i]++;
$lastv[$i] = $sql_value; $lastv[$i] = $sql_value;
} else { } else {
$lastv[$i] = $out_value if($out_value); $lastv[$i] = $out_value if($out_value);
} }
$lastd[$i] = $sql_timestamp; $lastd[$i] = $sql_timestamp;
} }
} #while fetchrow } # while fetchrow Ende
######## den letzten Abschlusssatz rausschreiben ########## ######## den letzten Abschlusssatz rausschreiben ##########
if($readings[$i]->[3] && ($readings[$i]->[3] eq "delta-h" || $readings[$i]->[3] eq "delta-d")) { if($readings[$i]->[3] && ($readings[$i]->[3] eq "delta-h" || $readings[$i]->[3] eq "delta-d")) {
@ -3752,12 +3769,24 @@ sub DbLog_configcheck($) {
@sr_unt = DbLog_sqlget($hash,"SHOW FIELDS FROM $history where FIELD='UNIT'"); @sr_unt = DbLog_sqlget($hash,"SHOW FIELDS FROM $history where FIELD='UNIT'");
} }
if($dbmodel =~ /POSTGRESQL/) { if($dbmodel =~ /POSTGRESQL/) {
@sr_dev = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$history' and column_name='device'"); my $sch = AttrVal($name, "dbSchema", "");
@sr_typ = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$history' and column_name='type'"); my $h = "history";
@sr_evt = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$history' and column_name='event'"); if($sch) {
@sr_rdg = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$history' and column_name='reading'"); @sr_dev = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$h' and table_schema='$sch' and column_name='device'");
@sr_val = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$history' and column_name='value'"); @sr_typ = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$h' and table_schema='$sch' and column_name='type'");
@sr_unt = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$history' and column_name='unit'"); @sr_evt = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$h' and table_schema='$sch' and column_name='event'");
@sr_rdg = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$h' and table_schema='$sch' and column_name='reading'");
@sr_val = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$h' and table_schema='$sch' and column_name='value'");
@sr_unt = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$h' and table_schema='$sch' and column_name='unit'");
} else {
@sr_dev = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$h' and column_name='device'");
@sr_typ = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$h' and column_name='type'");
@sr_evt = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$h' and column_name='event'");
@sr_rdg = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$h' and column_name='reading'");
@sr_val = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$h' and column_name='value'");
@sr_unt = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$h' and column_name='unit'");
}
} }
if($dbmodel =~ /SQLITE/) { if($dbmodel =~ /SQLITE/) {
my $dev = (DbLog_sqlget($hash,"SELECT sql FROM sqlite_master WHERE name = '$history'"))[0]; my $dev = (DbLog_sqlget($hash,"SELECT sql FROM sqlite_master WHERE name = '$history'"))[0];
@ -3831,12 +3860,24 @@ sub DbLog_configcheck($) {
} }
if($dbmodel =~ /POSTGRESQL/) { if($dbmodel =~ /POSTGRESQL/) {
@sr_dev = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$current' and column_name='device'"); my $sch = AttrVal($name, "dbSchema", "");
@sr_typ = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$current' and column_name='type'"); my $c = "current";
@sr_evt = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$current' and column_name='event'"); if($sch) {
@sr_rdg = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$current' and column_name='reading'"); @sr_dev = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$c' and table_schema='$sch' and column_name='device'");
@sr_val = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$current' and column_name='value'"); @sr_typ = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$c' and table_schema='$sch' and column_name='type'");
@sr_unt = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$current' and column_name='unit'"); @sr_evt = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$c' and table_schema='$sch' and column_name='event'");
@sr_rdg = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$c' and table_schema='$sch' and column_name='reading'");
@sr_val = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$c' and table_schema='$sch' and column_name='value'");
@sr_unt = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$c' and table_schema='$sch' and column_name='unit'");
} else {
@sr_dev = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$c' and column_name='device'");
@sr_typ = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$c' and column_name='type'");
@sr_evt = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$c' and column_name='event'");
@sr_rdg = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$c' and column_name='reading'");
@sr_val = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$c' and column_name='value'");
@sr_unt = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='$c' and column_name='unit'");
}
} }
if($dbmodel =~ /SQLITE/) { if($dbmodel =~ /SQLITE/) {
my $dev = (DbLog_sqlget($hash,"SELECT sql FROM sqlite_master WHERE name = '$current'"))[0]; my $dev = (DbLog_sqlget($hash,"SELECT sql FROM sqlite_master WHERE name = '$current'"))[0];
@ -3894,7 +3935,7 @@ sub DbLog_configcheck($) {
} }
$check .= "<u><b>Result of table '$current' check</u></b><br><br>"; $check .= "<u><b>Result of table '$current' check</u></b><br><br>";
$check .= "Column width set in DB $dbname.current: 'DEVICE' = $cdat_dev, 'TYPE' = $cdat_typ, 'EVENT' = $cdat_evt, 'READING' = $cdat_rdg, 'VALUE' = $cdat_val, 'UNIT' = $cdat_unt <br>"; $check .= "Column width set in DB $current: 'DEVICE' = $cdat_dev, 'TYPE' = $cdat_typ, 'EVENT' = $cdat_evt, 'READING' = $cdat_rdg, 'VALUE' = $cdat_val, 'UNIT' = $cdat_unt <br>";
$check .= "Column width used by $name: 'DEVICE' = $cmod_dev, 'TYPE' = $cmod_typ, 'EVENT' = $cmod_evt, 'READING' = $cmod_rdg, 'VALUE' = $cmod_val, 'UNIT' = $cmod_unt <br>"; $check .= "Column width used by $name: 'DEVICE' = $cmod_dev, 'TYPE' = $cmod_typ, 'EVENT' = $cmod_evt, 'READING' = $cmod_rdg, 'VALUE' = $cmod_val, 'UNIT' = $cmod_unt <br>";
$check .= "<b>Recommendation:</b> $rec <br><br>"; $check .= "<b>Recommendation:</b> $rec <br><br>";
#} #}
@ -4364,10 +4405,10 @@ sub DbLog_AddLog($$$$$) {
if($async) { if($async) {
# asynchoner non-blocking Mode # asynchoner non-blocking Mode
# Cache & CacheIndex für Events zum asynchronen Schreiben in DB # Cache & CacheIndex für Events zum asynchronen Schreiben in DB
$hash->{cache}{index}++; $data{DbLog}{$name}{cache}{index}++;
my $index = $hash->{cache}{index}; my $index = $data{DbLog}{$name}{cache}{index};
$hash->{cache}{".memcache"}{$index} = $row; $data{DbLog}{$name}{cache}{memcache}{$index} = $row;
my $memcount = $hash->{cache}{".memcache"}?scalar(keys%{$hash->{cache}{".memcache"}}):0; my $memcount = $data{DbLog}{$name}{cache}{memcache}?scalar(keys %{$data{DbLog}{$name}{cache}{memcache}}):0;
if($ce == 1) { if($ce == 1) {
readingsSingleUpdate($hash, "CacheUsage", $memcount, 1); readingsSingleUpdate($hash, "CacheUsage", $memcount, 1);
} else { } else {
@ -4451,11 +4492,11 @@ sub DbLog_addCacheLine($$$$$$$$) {
use warnings; use warnings;
eval { # one transaction eval { # one transaction
$hash->{cache}{index}++; $data{DbLog}{$name}{cache}{index}++;
my $index = $hash->{cache}{index}; my $index = $data{DbLog}{$name}{cache}{index};
$hash->{cache}{".memcache"}{$index} = $row; $data{DbLog}{$name}{cache}{memcache}{$index} = $row;
my $memcount = $hash->{cache}{".memcache"}?scalar(keys%{$hash->{cache}{".memcache"}}):0; my $memcount = $data{DbLog}{$name}{cache}{memcache}?scalar(keys %{$data{DbLog}{$name}{cache}{memcache}}):0;
if($ce == 1) { if($ce == 1) {
readingsSingleUpdate($hash, "CacheUsage", $memcount, 1); readingsSingleUpdate($hash, "CacheUsage", $memcount, 1);
} else { } else {
@ -5864,13 +5905,13 @@ sub DbLog_setVersionInfo($) {
if($modules{$type}{META}{x_prereqs_src} && !$hash->{HELPER}{MODMETAABSENT}) { if($modules{$type}{META}{x_prereqs_src} && !$hash->{HELPER}{MODMETAABSENT}) {
# META-Daten sind vorhanden # META-Daten sind vorhanden
$modules{$type}{META}{version} = "v".$v; # Version aus META.json überschreiben, Anzeige mit {Dumper $modules{SMAPortal}{META}} $modules{$type}{META}{version} = "v".$v; # Version aus META.json überschreiben, Anzeige mit {Dumper $modules{DbLog}{META}}
if($modules{$type}{META}{x_version}) { # {x_version} ( nur gesetzt wenn $Id: 93_DbLog.pm 20093 2019-09-02 07:17:38Z DS_Starter $ im Kopf komplett! vorhanden ) if($modules{$type}{META}{x_version}) { # {x_version} ( nur gesetzt wenn $Id: 93_DbLog.pm 20114 2019-09-06 11:21:03Z DS_Starter $ im Kopf komplett! vorhanden )
$modules{$type}{META}{x_version} =~ s/1.1.1/$v/g; $modules{$type}{META}{x_version} =~ s/1.1.1/$v/g;
} else { } else {
$modules{$type}{META}{x_version} = $v; $modules{$type}{META}{x_version} = $v;
} }
return $@ unless (FHEM::Meta::SetInternals($hash)); # FVERSION wird gesetzt ( nur gesetzt wenn $Id: 93_DbLog.pm 20093 2019-09-02 07:17:38Z DS_Starter $ im Kopf komplett! vorhanden ) return $@ unless (FHEM::Meta::SetInternals($hash)); # FVERSION wird gesetzt ( nur gesetzt wenn $Id: 93_DbLog.pm 20114 2019-09-06 11:21:03Z DS_Starter $ im Kopf komplett! vorhanden )
if(__PACKAGE__ eq "FHEM::$type" || __PACKAGE__ eq $type) { if(__PACKAGE__ eq "FHEM::$type" || __PACKAGE__ eq $type) {
# es wird mit Packages gearbeitet -> Perl übliche Modulversion setzen # es wird mit Packages gearbeitet -> Perl übliche Modulversion setzen
# mit {<Modul>->VERSION()} im FHEMWEB kann Modulversion abgefragt werden # mit {<Modul>->VERSION()} im FHEMWEB kann Modulversion abgefragt werden
@ -5912,7 +5953,7 @@ sub DbLog_showChildHandles ($$$$) {
my $t = $h->{Type}."h"; my $t = $h->{Type}."h";
$t = ($t=~/drh/)?"DriverHandle ":($t=~/dbh/)?"DatabaseHandle ":($t=~/sth/)?"StatementHandle":"Undefined"; $t = ($t=~/drh/)?"DriverHandle ":($t=~/dbh/)?"DatabaseHandle ":($t=~/sth/)?"StatementHandle":"Undefined";
Log3($name, 1, "DbLog - traceHandles (system wide) - Driver: ".$key.", ".$t.": ".("\t" x $level).$h); Log3($name, 1, "DbLog $name - traceHandles (system wide) - Driver: ".$key.", ".$t.": ".("\t" x $level).$h);
DbLog_showChildHandles($name, $_, $level + 1, $key) DbLog_showChildHandles($name, $_, $level + 1, $key)
for (grep { defined } @{$h->{ChildHandles}}); for (grep { defined } @{$h->{ChildHandles}});
} }
@ -7051,7 +7092,7 @@ attr SMA_Energymeter DbLogValueFn
<li><b>timeout</b> <li><b>timeout</b>
<ul> <ul>
<code> <code>
attr &lt;device&gt; timeout <n> attr &lt;device&gt; timeout &lt;n&gt;
</code><br> </code><br>
setup timeout of the write cycle into database in asynchronous mode (default 86400s) <br> setup timeout of the write cycle into database in asynchronous mode (default 86400s) <br>
</ul> </ul>
@ -7064,7 +7105,7 @@ attr SMA_Energymeter DbLogValueFn
<li><b>traceFlag</b> <li><b>traceFlag</b>
<ul> <ul>
<code> <code>
attr &lt;device&gt; traceFlag [ALL|SQL|CON|ENC|DBD|TXN] <n> attr &lt;device&gt; traceFlag &lt;ALL|SQL|CON|ENC|DBD|TXN&gt;
</code><br> </code><br>
Trace flags are used to enable tracing of specific activities within the DBI and drivers. The attribute is only used for Trace flags are used to enable tracing of specific activities within the DBI and drivers. The attribute is only used for
tracing of errors in case of support. <br><br> tracing of errors in case of support. <br><br>
@ -7096,7 +7137,7 @@ attr SMA_Energymeter DbLogValueFn
</code><br> </code><br>
If set, every &lt;n&gt; seconds the system wide existing database handles are printed out into the logfile. If set, every &lt;n&gt; seconds the system wide existing database handles are printed out into the logfile.
This attribute is only relevant for support cases. (default: 0 = switch off) <br> This attribute is only relevant in case of support. (default: 0 = switch off) <br>
</ul> </ul>
</li> </li>
@ -7108,7 +7149,7 @@ attr SMA_Energymeter DbLogValueFn
<li><b>traceLevel</b> <li><b>traceLevel</b>
<ul> <ul>
<code> <code>
attr &lt;device&gt; traceLevel [0|1|2|3|4|5|6|7] <n> attr &lt;device&gt; traceLevel &lt;0|1|2|3|4|5|6|7&gt;
</code><br> </code><br>
Switch on the tracing function of the module. <br> Switch on the tracing function of the module. <br>
<b>Caution !</b> The attribute is only used for tracing errors or in case of support. If switched on <b>very much entries</b> <b>Caution !</b> The attribute is only used for tracing errors or in case of support. If switched on <b>very much entries</b>
@ -8410,7 +8451,7 @@ attr SMA_Energymeter DbLogValueFn
<li><b>timeout</b> <li><b>timeout</b>
<ul> <ul>
<code> <code>
attr &lt;device&gt; timeout <n> attr &lt;device&gt; timeout &lt;n&gt;
</code><br> </code><br>
Setzt den Timeout-Wert für den Schreibzyklus in die Datenbank im asynchronen Modus (default 86400s). <br> Setzt den Timeout-Wert für den Schreibzyklus in die Datenbank im asynchronen Modus (default 86400s). <br>
</ul> </ul>
@ -8423,7 +8464,7 @@ attr SMA_Energymeter DbLogValueFn
<li><b>traceFlag</b> <li><b>traceFlag</b>
<ul> <ul>
<code> <code>
attr &lt;device&gt; traceFlag [ALL|SQL|CON|ENC|DBD|TXN] <n> attr &lt;device&gt; traceFlag &lt;ALL|SQL|CON|ENC|DBD|TXN&gt;
</code><br> </code><br>
Bestimmt das Tracing von bestimmten Aktivitäten innerhalb des Datenbankinterfaces und Treibers. Das Attribut ist nur Bestimmt das Tracing von bestimmten Aktivitäten innerhalb des Datenbankinterfaces und Treibers. Das Attribut ist nur
für den Fehler- bzw. Supportfall gedacht. <br><br> für den Fehler- bzw. Supportfall gedacht. <br><br>
@ -8467,7 +8508,7 @@ attr SMA_Energymeter DbLogValueFn
<li><b>traceLevel</b> <li><b>traceLevel</b>
<ul> <ul>
<code> <code>
attr &lt;device&gt; traceLevel [0|1|2|3|4|5|6|7] <n> attr &lt;device&gt; traceLevel &lt;0|1|2|3|4|5|6|7&gt;
</code><br> </code><br>
Schaltet die Trace-Funktion des Moduls ein. <br> Schaltet die Trace-Funktion des Moduls ein. <br>
<b>Achtung !</b> Das Attribut ist nur für den Fehler- bzw. Supportfall gedacht. Es werden <b>sehr viele Einträge</b> in <b>Achtung !</b> Das Attribut ist nur für den Fehler- bzw. Supportfall gedacht. Es werden <b>sehr viele Einträge</b> in
@ -8610,7 +8651,9 @@ attr SMA_Energymeter DbLogValueFn
"Encode": 0 "Encode": 0
}, },
"recommends": { "recommends": {
"FHEM::Meta": 0 "FHEM::Meta": 0,
"Devel::Size": 0,
"Data::Peek": 0
}, },
"suggests": { "suggests": {
"DBD::Pg" :0, "DBD::Pg" :0,