diff --git a/fhem/CHANGED b/fhem/CHANGED index eaf45db8b..65e9e0606 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,6 +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. - SVN + - feature: DbLog: jokers "%" in device/reading definition are now possible - feature: SYSMON: new CPU Statistics and Plots - feature: changed 10_OWServer.pm and 11_OWDevice.pm to use NOTIFYDEV (justme1968) diff --git a/fhem/FHEM/93_DbLog.pm b/fhem/FHEM/93_DbLog.pm index d4fdf5371..22bc23277 100644 --- a/fhem/FHEM/93_DbLog.pm +++ b/fhem/FHEM/93_DbLog.pm @@ -6,7 +6,7 @@ # written by Dr. Boris Neubert 2007-12-30 # e-mail: omega at online dot de # -# modified by Tobias Faust 2012-06-26 +# modified and maintained by Tobias Faust since 2012-06-26 # e-mail: tobias dot faust at online dot de # ############################################## @@ -17,11 +17,8 @@ use warnings; use DBI; use Data::Dumper; -sub DbLog($$$); - ################################################################ -sub -DbLog_Initialize($) +sub DbLog_Initialize($) { my ($hash) = @_; @@ -36,8 +33,7 @@ DbLog_Initialize($) } ############################################################### -sub -DbLog_Define($@) +sub DbLog_Define($@) { my ($hash, $def) = @_; my @a = split("[ \t][ \t]*", $def); @@ -64,8 +60,7 @@ DbLog_Define($@) } ##################################### -sub -DbLog_Undef($$) +sub DbLog_Undef($$) { my ($hash, $name) = @_; my $dbh= $hash->{DBH}; @@ -79,8 +74,7 @@ DbLog_Undef($$) # DbLog-Instanz aufgerufen # ################################################################ -sub -DbLog_Attr(@) +sub DbLog_Attr(@) { my @a = @_; my $do = 0; @@ -101,8 +95,7 @@ DbLog_Attr(@) # Parsefunktion, abhaengig vom Devicetyp # ################################################################ -sub -DbLog_ParseEvent($$$) +sub DbLog_ParseEvent($$$) { my ($device, $type, $event)= @_; my @result; @@ -159,6 +152,14 @@ DbLog_ParseEvent($$$) } } + # FBDECT + elsif (($type eq "FBDECT")) { + if ( $value=~/([\.\d]+)\s([a-z])/i ) { + $value = $1; + $unit = $2; + } + } + # MAX elsif(($type eq "MAX")) { $unit= "°C" if(lc($reading) =~ m/temp/); @@ -339,6 +340,60 @@ DbLog_ParseEvent($$$) return @result; } +################################################################ +# +# param1: hash +# param2: DbLogType -> Current oder History oder Current/History +# param4: Timestamp +# param5: Device +# param6: Type +# param7: Event +# param8: Reading +# param9: Value +# param10: Unit +# +################################################################ +sub DbLog_Push(@) { + my ($hash, $DbLogType, $timestamp, $device, $type, $event, $reading, $value, $unit) = @_; + + my $dbh= $hash->{DBH}; + $dbh->{RaiseError} = 1; + + $dbh->begin_work(); + + my $sth_ih = $dbh->prepare_cached("INSERT INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)") if (lc($DbLogType) =~ m(history) ); + my $sth_ic = $dbh->prepare_cached("INSERT INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)") if (lc($DbLogType) =~ m(current) ); + my $sth_uc = $dbh->prepare_cached("UPDATE current SET TIMESTAMP=?, TYPE=?, EVENT=?, VALUE=?, UNIT=? WHERE (DEVICE=?) AND (READING=?)") if (lc($DbLogType) =~ m(current) ); + + # insert into history + if (lc($DbLogType) =~ m(history) ) { + my $rv_ih = $sth_ih->execute(($timestamp, $device, $type, $event, $reading, $value, $unit)); + } + + # update or insert current + if (lc($DbLogType) =~ m(current) ) { + my $rv_uc = $sth_uc->execute(($timestamp, $type, $event, $value, $unit, $device, $reading)); + if ($rv_uc == 0) { + my $rv_ic = $sth_ic->execute(($timestamp, $device, $type, $event, $reading, $value, $unit)); + } + } + + $dbh->commit(); + + if ($@) { + Log3 $hash->{NAME}, 2, "DbLog: Failed to insert new readings into database: $@"; + $dbh->{RaiseError} = 0; + $dbh->rollback(); + # reconnect + $dbh->disconnect(); + DbLog_Connect($hash); + } + else { + $dbh->{RaiseError} = 0; + } + + return $dbh->{RaiseError}; +} ################################################################ # @@ -346,13 +401,11 @@ DbLog_ParseEvent($$$) # aufgerufen # ################################################################ -sub -DbLog_Log($$) -{ +sub DbLog_Log($$) { # Log is my entry, Dev is the entry of the changed device - my ($log, $dev) = @_; + my ($hash, $dev) = @_; - return undef if($log->{STATE} eq "disabled"); + return undef if($hash->{STATE} eq "disabled"); # name and type required for parsing my $n= $dev->{NAME}; @@ -362,25 +415,15 @@ DbLog_Log($$) #my ($sec,$min,$hr,$day,$mon,$yr,$wday,$yday,$isdst)= localtime(time); #my $ts= sprintf("%04d-%02d-%02d %02d:%02d:%02d", $yr+1900,$mon+1,$day,$hr,$min,$sec); - my $re = $log->{REGEXP}; + my $re = $hash->{REGEXP}; my $max = int(@{$dev->{CHANGED}}); my $ts_0 = TimeNow(); my $now = gettimeofday(); # get timestamp in seconds since epoch my $DbLogExclude = AttrVal($dev->{NAME}, "DbLogExclude", undef); - - my $dbh= $log->{DBH}; - $dbh->{RaiseError} = 1; - - my $DbLogType = AttrVal($log->{NAME}, "DbLogType", "Current/History"); + my $DbLogType = AttrVal($hash->{NAME}, "DbLogType", "Current/History"); #one Transaction - eval { - $dbh->begin_work(); - - my $sth_ih = $dbh->prepare_cached("INSERT INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)") if ($DbLogType =~ m(History) ); - my $sth_uc = $dbh->prepare_cached("UPDATE current SET TIMESTAMP=?, TYPE=?, EVENT=?, VALUE=?, UNIT=? WHERE (DEVICE=?) AND (READING=?)") if ($DbLogType =~ m(Current) ); - my $sth_ic = $dbh->prepare_cached("INSERT INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)") if ($DbLogType =~ m(Current) ); - + #eval { for (my $i = 0; $i < $max; $i++) { my $s = $dev->{CHANGED}[$i]; $s = "" if(!defined($s)); @@ -409,8 +452,8 @@ DbLog_Log($$) $DoIt = 0 if(!$v2[1] && $reading =~ m/^$v2[0]$/); #Reading matcht auf Regexp, kein MinIntervall angegeben if(($v2[1] && $reading =~ m/^$v2[0]$/) && ($v2[1] =~ m/^(\d+)$/)) { #Regexp matcht und MinIntervall ist angegeben - my $lt = $defs{$dev->{NAME}}{Helper}{DBLOG}{$reading}{$log->{NAME}}{TIME}; - my $lv = $defs{$dev->{NAME}}{Helper}{DBLOG}{$reading}{$log->{NAME}}{VALUE}; + my $lt = $defs{$dev->{NAME}}{Helper}{DBLOG}{$reading}{$hash->{NAME}}{TIME}; + my $lv = $defs{$dev->{NAME}}{Helper}{DBLOG}{$reading}{$hash->{NAME}}{VALUE}; $lt = 0 if(!$lt); $lv = "" if(!$lv); @@ -423,39 +466,14 @@ DbLog_Log($$) } next if($DoIt == 0); - $defs{$dev->{NAME}}{Helper}{DBLOG}{$reading}{$log->{NAME}}{TIME} = $now; - $defs{$dev->{NAME}}{Helper}{DBLOG}{$reading}{$log->{NAME}}{VALUE} = $value; + $defs{$dev->{NAME}}{Helper}{DBLOG}{$reading}{$hash->{NAME}}{TIME} = $now; + $defs{$dev->{NAME}}{Helper}{DBLOG}{$reading}{$hash->{NAME}}{VALUE} = $value; - my @is= ($ts, $n, $t, $s, $reading, $value, $unit); - - # insert into history - if ($DbLogType =~ m(History) ) { - my $rv_ih = $sth_ih->execute(@is); - } - - if ($DbLogType =~ m(Current) ) { - # update or insert current - my $rv_uc = $sth_uc->execute(($ts, $t, $s, $value, $unit, $n, $reading)); - if ($rv_uc == 0) { - my $rv_ic = $sth_ic->execute(@is); - } - } + DbLog_Push($hash, $DbLogType, $ts, $n, $t, $s, $reading, $value, $unit) } } - $dbh->commit(); - }; - if ($@) { - Log3 $dev->{NAME}, 2, "DbLog: Failed to insert new readings into database: $@"; - $dbh->{RaiseError} = 0; - $dbh->rollback(); - # reconnect - $dbh->disconnect(); - DbLog_Connect($log); - } - else { - $dbh->{RaiseError} = 0; - } + #}; return ""; } @@ -467,8 +485,7 @@ DbLog_Log($$) # uebergebenes SQL-Format: YYYY-MM-DD HH24:MI:SS # ################################################################ -sub -DbLog_explode_datetime($%) { +sub DbLog_explode_datetime($%) { my ($t, %def) = @_; my %retv; @@ -489,20 +506,19 @@ DbLog_explode_datetime($%) { return %retv } -sub -DbLog_implode_datetime($$$$$$) { +sub DbLog_implode_datetime($$$$$$) { my ($year, $month, $day, $hour, $minute, $second) = @_; my $retv = $year."-".$month."-".$day." ".$hour.":".$minute.":".$second; return $retv; } + ################################################################ # # Verbindung zur DB aufbauen # ################################################################ -sub -DbLog_Connect($) +sub DbLog_Connect($) { my ($hash)= @_; @@ -575,6 +591,9 @@ DbLog_Connect($) # # Prozeduren zum Ausfuehren des SQLs # +# param1: hash +# param2: pointer : DBFilehandle +# param3: string : SQL ################################################################ sub DbLog_ExecSQL1($$$) @@ -618,6 +637,8 @@ DbLog_ExecSQL($$) # # GET Funktion # wird zb. zur Generierung der Plots implizit aufgerufen +# infile : [-|current|history] +# outfile: [-|ALL|INT|WEBCHART] # ################################################################ sub @@ -632,16 +653,22 @@ DbLog_Get($@) " is a prefix, - means stdout\n" if(int(@a) < 5); shift @a; - my $inf = shift @a; - my $outf = shift @a; + my $inf = lc(shift @a); + my $outf = lc(shift @a); my $from = shift @a; my $to = shift @a; # Now @a contains the list of column_specs my ($internal, @fld); - if($outf eq "INT") { + if($inf eq "-") { + $inf = "history"; + } + + if($outf eq "int") { $outf = "-"; $internal = 1; - } elsif (uc($outf) eq "WEBCHART") { + } elsif($outf eq "array"){ + + } elsif(uc($outf) eq "webchart") { # redirect the get request to the chartQuery function return chartQuery($hash, @_); } @@ -659,7 +686,8 @@ DbLog_Get($@) $to = $to_datetime{datetime}; - my ($retval,$sql_timestamp,$sql_value, $type, $event, $unit) = ""; + my ($retval,$sql_timestamp, $sql_device, $sql_reading, $sql_value, $type, $event, $unit) = ""; + my @ReturnArray; my $writeout = 0; my (@min, @max, @sum, @cnt, @lastv, @lastd); my (%tstamp, %lasttstamp, $out_tstamp, $out_value, $minval, $maxval); #fuer delta-h/d Berechnung @@ -672,6 +700,8 @@ DbLog_Get($@) $readings[$i][2] = $fld[2]; # Default $readings[$i][3] = $fld[3]; # function $readings[$i][4] = $fld[4]; # regexp + + $readings[$i][1] = "%" if(length($readings[$i][1])==0); #falls Reading nicht gefüllt setze Joker } #create new connection for plotfork @@ -687,7 +717,7 @@ DbLog_Get($@) $sqlspec{get_timestamp} = "TO_CHAR(TIMESTAMP, 'YYYY-MM-DD HH24:MI:SS')"; $sqlspec{from_timestamp} = "TO_TIMESTAMP('$from', 'YYYY-MM-DD HH24:MI:SS')"; $sqlspec{to_timestamp} = "TO_TIMESTAMP('$to', 'YYYY-MM-DD HH24:MI:SS')"; - $sqlspec{reading_clause} = "(DEVICE || '|' || READING)"; + #$sqlspec{reading_clause} = "(DEVICE || '|' || READING)"; } elsif ($hash->{DBMODEL} eq "ORACLE") { $sqlspec{get_timestamp} = "TO_CHAR(TIMESTAMP, 'YYYY-MM-DD HH24:MI:SS')"; $sqlspec{from_timestamp} = "TO_TIMESTAMP('$from', 'YYYY-MM-DD HH24:MI:SS')"; @@ -706,7 +736,7 @@ DbLog_Get($@) $sqlspec{to_timestamp} = "'$to'"; } - if(uc($outf) eq "ALL") { + if($outf =~ m/(all|array)/) { $sqlspec{all} = ",TYPE,EVENT,UNIT"; } else { $sqlspec{all} = ""; @@ -724,33 +754,46 @@ DbLog_Get($@) $minval = 999999; $maxval = -999999; - my $stm= "SELECT - $sqlspec{get_timestamp}, - VALUE - $sqlspec{all} - FROM history - WHERE - DEVICE = '".$readings[$i]->[0]."' - AND READING = '".$readings[$i]->[1]."' - AND TIMESTAMP > $sqlspec{from_timestamp} - AND TIMESTAMP < $sqlspec{to_timestamp} - ORDER BY TIMESTAMP"; + my $stm; + $stm = "SELECT + $sqlspec{get_timestamp}, + DEVICE, + READING, + VALUE + $sqlspec{all} "; - Log3 $hash->{NAME}, 5, "Executing $stm"; + $stm .= "FROM current " if($inf eq "current"); + $stm .= "FROM history " if($inf eq "history"); + + $stm .= "WHERE 1=1 "; + + $stm .= "AND DEVICE = '".$readings[$i]->[0]."' " if ($readings[$i]->[0] !~ m(\%)); + $stm .= "AND DEVICE LIKE '".$readings[$i]->[0]."' " if(($readings[$i]->[0] !~ m(^\%$)) && ($readings[$i]->[0] =~ m(\%))); + + $stm .= "AND READING = '".$readings[$i]->[1]."' " if ($readings[$i]->[1] !~ m(\%)); + $stm .= "AND READING LIKE '".$readings[$i]->[1]."' " if(($readings[$i]->[1] !~ m(^%$)) && ($readings[$i]->[1] =~ m(\%))); + + $stm .= "AND TIMESTAMP > $sqlspec{from_timestamp} + AND TIMESTAMP < $sqlspec{to_timestamp} + ORDER BY TIMESTAMP"; + + Log3 $hash->{NAME}, 4, "Processing Statement: $stm"; my $sth= $dbh->prepare($stm) || return "Cannot prepare statement $stm: $DBI::errstr"; my $rc= $sth->execute() || return "Cannot execute statement $stm: $DBI::errstr"; - if(uc($outf) eq "ALL") { - $sth->bind_columns(undef, \$sql_timestamp, \$sql_value, \$type, \$event, \$unit); - - $retval .= "Timestamp: Device, Type, Event, Reading, Value, Unit\n"; - $retval .= "=====================================================\n"; + if($outf =~ m/(all|array)/) { + $sth->bind_columns(undef, \$sql_timestamp, \$sql_device, \$sql_reading, \$sql_value, \$type, \$event, \$unit); } else { - $sth->bind_columns(undef, \$sql_timestamp, \$sql_value); + $sth->bind_columns(undef, \$sql_timestamp, \$sql_value); + } + + if ($outf =~ m/(all)/) { + $retval .= "Timestamp: Device, Type, Event, Reading, Value, Unit\n"; + $retval .= "=====================================================\n"; } while($sth->fetch()) { @@ -840,8 +883,13 @@ DbLog_Get($@) ###################### Ausgabe ########################### if($writeout) { - if(uc($outf) eq "ALL") { - $retval .= sprintf("%s: %s, %s, %s, %s, %s, %s\n", $out_tstamp, $readings[$i]->[0], $type, $event, $readings[$i]->[1], $out_value, $unit); + if ($outf =~ m/(all)/) { + # 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); + + } 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 $retval .= "$out_tstamp $out_value\n"; @@ -873,12 +921,17 @@ DbLog_Get($@) $out_value = sprintf("%g", $maxval - $minval); $out_tstamp = DbLog_implode_datetime($lasttstamp{year}, $lasttstamp{month}, $lasttstamp{day}, $lasttstamp{hour}, "30", "00") if($readings[$i]->[3] eq "delta-h"); $out_tstamp = DbLog_implode_datetime($lasttstamp{year}, $lasttstamp{month}, $lasttstamp{day}, "00", "00", "00") if($readings[$i]->[3] eq "delta-d"); - if(uc($outf) eq "ALL") { - $retval .= sprintf("%s: %s %s %s %s %s %s\n", $out_tstamp, $readings[$i]->[0], $type, $event, $readings[$i]->[1], $out_value, $unit); - } else { - $out_tstamp =~ s/\ /_/g; #needed by generating plots - $retval .= "$out_tstamp $out_value\n"; - } + + if($outf =~ m/(all)/) { + $retval .= 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 + $retval .= "$out_tstamp $out_value\n"; + } } # DatenTrenner setzen $retval .= "#$readings[$i]->[0]"; @@ -911,8 +964,13 @@ DbLog_Get($@) if($internal) { $internal_data = \$retval; return undef; + + } elsif($outf =~ m/(array)/) { + return @ReturnArray; + + } else { + return $retval; } - return $retval; } ################################################################ @@ -1240,18 +1298,17 @@ sub chartQuery($@) { contains the last event for any given reading and device. The columns have the following meaning:
    +
  1. TIMESTAMP: timestamp of event, e.g. 2007-12-30 21:45:22
  2. +
  3. DEVICE: device name, e.g. Wetterstation
  4. +
  5. TYPE: device type, e.g. KS300
  6. +
  7. EVENT: event specification as full string, + e.g. humidity: 71 (%)
  8. +
  9. READING: name of reading extracted from event, + e.g. humidity
  10. -
  11. TIMESTAMP: timestamp of event, e.g. 2007-12-30 21:45:22
  12. -
  13. DEVICE: device name, e.g. Wetterstation
  14. -
  15. TYPE: device type, e.g. KS300
  16. -
  17. EVENT: event specification as full string, - e.g. humidity: 71 (%)
  18. -
  19. READING: name of reading extracted from event, - e.g. humidity
  20. - -
  21. VALUE: actual reading extracted from event, - e.g. 71
  22. -
  23. UNIT: unit extracted from event, e.g. %
  24. +
  25. VALUE: actual reading extracted from event, + e.g. 71
  26. +
  27. UNIT: unit extracted from event, e.g. %
The content of VALUE is optimized for automated post-processing, e.g. yes is translated to 1 @@ -1284,11 +1341,25 @@ sub chartQuery($@) { @@ -1709,11 +1797,11 @@ sub chartQuery($@) {
Wenn DbLog genutzt wird, wird in alle Devices das Attribut DbLogExclude - propagiert. Der Wert des Attributes wird als Regexp ausgewertet und schließt die + propagiert. Der Wert des Attributes wird als Regexp ausgewertet und schliesst die damit matchenden Readings von einem Logging aus. Einzelne Regexp werden durch Kommata getrennt. Ist MinIntervall angegeben, so wird der Logeintrag nur dann nicht geloggt, wenn das Intervall noch nicht erreicht und der Wert des - Readings sich nicht verändert hat. + Readings sich nicht verändert hat.
Beispiele