From 733f968603667dbcd1f15bd101cc949178dd7714 Mon Sep 17 00:00:00 2001 From: tobiasfaust <> Date: Mon, 10 Dec 2012 18:24:47 +0000 Subject: [PATCH] - feature: new Module 93_DbLog.pm for logging events into Databases. Generating Plots with weblinks are supportet. (by B. Neubert / T. Faust) git-svn-id: https://svn.fhem.de/fhem/trunk@2313 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 3 + fhem/FHEM/93_DbLog.pm | 1026 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1029 insertions(+) create mode 100644 fhem/FHEM/93_DbLog.pm diff --git a/fhem/CHANGED b/fhem/CHANGED index 7a406c6a9..ebc02a9aa 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,4 +1,7 @@ - SVN + - feature: new Module 93_DbLog.pm for logging events into Databases. + Generating Plots with weblinks are supportet. + (by B. Neubert / T. Faust) - feature: new Module 59_HCS.pm for monitoring heating valves (FHT, HM-CC-VD) to contral a central heating unit. I thank Benjamin for his support! (M. Fischer) diff --git a/fhem/FHEM/93_DbLog.pm b/fhem/FHEM/93_DbLog.pm new file mode 100644 index 000000000..c5429d736 --- /dev/null +++ b/fhem/FHEM/93_DbLog.pm @@ -0,0 +1,1026 @@ + +############################################## +# $Id$ +# +# 93_DbLog.pm +# written by Dr. Boris Neubert 2007-12-30 +# e-mail: omega at online dot de +# +# modified by Tobias Faust 2012-06-26 +# e-mail: tobias dot faust at online dot de +# +############################################## + +package main; +use strict; +use warnings; +use DBI; +use Data::Dumper; + +sub DbLog($$$); + +################################################################ +sub +DbLog_Initialize($) +{ + my ($hash) = @_; + + $hash->{DefFn} = "DbLog_Define"; + $hash->{UndefFn} = "DbLog_Undef"; + $hash->{NotifyFn} = "DbLog_Log"; + $hash->{GetFn} = "DbLog_Get"; + $hash->{AttrFn} = "DbLog_Attr"; + $hash->{AttrList} = "disable:0,1 loglevel:0,5"; + +} + +############################################################### +sub +DbLog_Define($@) +{ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + return "wrong syntax: define DbLog configuration regexp" + if(int(@a) != 4); + + my $regexp = $a[3]; + + eval { "Hallo" =~ m/^$regexp$/ }; + return "Bad regexp: $@" if($@); + $hash->{REGEXP} = $regexp; + + $hash->{CONFIGURATION}= $a[2]; + + return "Can't connect to database." if(!DbLog_Connect($hash)); + + $hash->{STATE} = "active"; + + return undef; +} + +##################################### +sub +DbLog_Undef($$) +{ + my ($hash, $name) = @_; + my $dbh= $hash->{DBH}; + $dbh->disconnect() if(defined($dbh)); + return undef; +} + +################################################################ +# +# Wird bei jeder Aenderung eines Attributes dieser +# DbLog-Instanz aufgerufen +# +################################################################ +sub +DbLog_Attr(@) +{ + my @a = @_; + my $do = 0; + + if($a[0] eq "set" && $a[2] eq "disable") { + $do = (!defined($a[3]) || $a[3]) ? 1 : 2; + } + $do = 2 if($a[0] eq "del" && (!$a[2] || $a[2] eq "disable")); + return if(!$do); + + $defs{$a[1]}{STATE} = ($do == 1 ? "disabled" : "active"); + + return undef; +} + +################################################################ +# +# Parsefunktion, abhaengig vom Devicetyp +# +################################################################ +sub +DbLog_ParseEvent($$) +{ + my ($type, $event)= @_; + my @result; + + # split the event into reading and argument + # "day-temp: 22.0 (Celsius)" -> "day-temp", "22.0 (Celsius)" + my @parts = split(/: /,$event); + my $reading = shift @parts; + my $value = join(": ", @parts); + my $unit = ""; + + #default + if(!defined($reading)) { $reading = ""; } + if(!defined($value)) { $value = ""; } + + # the interpretation of the argument depends on the device type + # EMEM, M232Counter, M232Voltage return plain numbers + if(($type eq "M232Voltage") || + ($type eq "M232Counter") || + ($type eq "EMEM")) { + } + + # Onewire + elsif(($type eq "OWAD") || + ($type eq "OWSWITCH") || + ($type eq "OWMULTI")) { + $reading = "data"; + $value = $event; + } + # FS20 + elsif(($type eq "FS20") || + ($type eq "X10")) { + #@parts = split(/ /,$event); + #$reading = shift @parts; + #$value = join(" ", shift @parts); + + if($reading =~ m/^dim(\d+).*/o) { + $value = $1; + $reading= "dim"; + $unit= "%"; + } + if(!defined($value) || $value eq "") {$value=$reading; $reading="data";} + } + # FHT + elsif($type eq "FHT") { + if($reading =~ m(-from[12]\ ) || $reading =~ m(-to[12]\ )) { + @parts= split(/ /,$event); + $reading= $parts[0]; + $value= $parts[1]; + $unit= ""; + } + if($reading =~ m(-temp)) { $value=~ s/ \(Celsius\)//; $unit= "°C"; } + if($reading =~ m(temp-offset)) { $value=~ s/ \(Celsius\)//; $unit= "°C"; } + if($reading =~ m(^actuator[0-9]*)) { + if($value eq "lime-protection") { + $reading= "actuator-lime-protection"; + undef $value; + } + elsif($value =~ m(^offset:)) { + $reading= "actuator-offset"; + @parts= split(/: /,$value); + $value= $parts[1]; + if(defined $value) { + $value=~ s/%//; $value= $value*1.; $unit= "%"; + } + } + elsif($value =~ m(^unknown_)) { + @parts= split(/: /,$value); + $reading= "actuator-" . $parts[0]; + $value= $parts[1]; + if(defined $value) { + $value=~ s/%//; $value= $value*1.; $unit= "%"; + } + } + elsif($value eq "synctime") { + $reading= "actuator-synctime"; + undef $value; + } + elsif($value eq "test") { + $reading= "actuator-test"; + undef $value; + } + elsif($value eq "pair") { + $reading= "actuator-pair"; + undef $value; + } + else { + $value=~ s/%//; $value= $value*1.; $unit= "%"; + } + } + } + # KS300 + elsif($type eq "KS300") { + if($event =~ m(T:.*)) { $reading= "data"; $value= $event; } + if($event =~ m(avg_day)) { $reading= "data"; $value= $event; } + if($event =~ m(avg_month)) { $reading= "data"; $value= $event; } + if($reading eq "temperature") { $value=~ s/ \(Celsius\)//; $unit= "°C"; } + if($reading eq "wind") { $value=~ s/ \(km\/h\)//; $unit= "km/h"; } + if($reading eq "rain") { $value=~ s/ \(l\/m2\)//; $unit= "l/m2"; } + if($reading eq "rain_raw") { $value=~ s/ \(counter\)//; $unit= ""; } + if($reading eq "humidity") { $value=~ s/ \(\%\)//; $unit= "%"; } + if($reading eq "israining") { + $value=~ s/ \(yes\/no\)//; + $value=~ s/no/0/; + $value=~ s/yes/1/; + } + } + # HMS + elsif($type eq "HMS" || + $type eq "CUL_WS" || + $type eq "OWTHERM") { + if($event =~ m(T:.*)) { $reading= "data"; $value= $event; } + if($reading eq "temperature") { $value=~ s/ \(Celsius\)//; $unit= "°C"; } + if($reading eq "temperature") { $value=~ s/([-\.\d]+).*/$1/; $unit= "°C"; } #OWTHERM + if($reading eq "humidity") { $value=~ s/ \(\%\)//; $unit= "%"; } + if($reading eq "battery") { + $value=~ s/ok/1/; + $value=~ s/replaced/1/; + $value=~ s/empty/0/; + } + } + + # BS + elsif($type eq "BS") { + if($event =~ m(brightness:.*)) { + @parts= split(/ /,$event); + $reading= "lux"; + $value= $parts[4]*1.; + $unit= "lux"; + } + } + + # Weather + elsif($type eq "WEATHER") { + if($event =~ m(^wind_condition)) { + @parts= split(/ /,$event); # extract wind direction from event + if(defined $parts[0]) { + $reading = "wind_direction"; + $value= $parts[2]; +# $unit= ""; + } + } + if($reading =~ m(^wind)) { $unit= "km/h"; } # wind, wind_speed + if($reading eq "wind_chill") { $unit= "°C"; } + if($reading eq "wind_direction") { $unit= ""; } + if($reading =~ m(^temperature)) { $unit= "°C"; } # wenn reading mit temperature beginnt + if($reading =~ m(^humidity)) { $unit= "%"; } + if($reading =~ m(^pressure)) { $unit= "hPa"; } + if($reading =~ m(^pressure_trend)) { $unit= ""; } + } + + @result= ($reading,$value,$unit); + return @result; +} + + +################################################################ +# +# Hauptroutine zum Loggen. Wird bei jedem Eventchange +# aufgerufen +# +################################################################ +sub +DbLog_Log($$) +{ + # Log is my entry, Dev is the entry of the changed device + my ($log, $dev) = @_; + + return undef if($log->{STATE} eq "disabled"); + + # name and type required for parsing + my $n= $dev->{NAME}; + my $t= uc($dev->{TYPE}); + + # timestamp in SQL format YYYY-MM-DD hh:mm:ss + #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 $max = int(@{$dev->{CHANGED}}); + for (my $i = 0; $i < $max; $i++) { + my $s = $dev->{CHANGED}[$i]; + $s = "" if(!defined($s)); + if($n =~ m/^$re$/ || "$n:$s" =~ m/^$re$/) { + my $ts = TimeNow(); + $ts = $dev->{CHANGETIME}[$i] if(defined($dev->{CHANGETIME}[$i])); + # $ts is in SQL format YYYY-MM-DD hh:mm:ss + + my @r= DbLog_ParseEvent($t, $s); + my $reading= $r[0]; + my $value= $r[1]; + my $unit= $r[2]; + if(!defined $reading) { $reading= ""; } + if(!defined $value) { $value= ""; } + if(!defined $unit || $unit eq "") { + $unit = AttrVal("$n", "unit", ""); + } + + my $is= "(TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES " . + "('$ts', '$n', '$t', '$s', '$reading', '$value', '$unit')"; + DbLog_ExecSQL($log, "INSERT INTO history" . $is); + DbLog_ExecSQL($log, "DELETE FROM current WHERE (DEVICE='$n') AND (READING='$reading')"); + DbLog_ExecSQL($log, "INSERT INTO current" . $is); + + + + } + } + + return ""; +} + +################################################################ +# +# zerlegt uebergebenes FHEM-Datum in die einzelnen Bestandteile +# und fuegt noch Defaultwerte ein +# uebergebenes SQL-Format: YYYY-MM-DD HH24:MI:SS +# +################################################################ +sub +DbLog_explode_datetime($%) { + my ($t, %def) = @_; + my %retv; + + my (@datetime, @date, @time); + @datetime = split(" ", $t); #Datum und Zeit auftrennen + @date = split("-", $datetime[0]); + @time = split(":", $datetime[1]) if ($datetime[1]); + if ($date[0]) {$retv{year} = $date[0];} else {$retv{year} = $def{year};} + if ($date[1]) {$retv{month} = $date[1];} else {$retv{month} = $def{month};} + if ($date[2]) {$retv{day} = $date[2];} else {$retv{day} = $def{day};} + if ($time[0]) {$retv{hour} = $time[0];} else {$retv{hour} = $def{hour};} + if ($time[1]) {$retv{minute}= $time[1];} else {$retv{minute}= $def{minute};} + if ($time[2]) {$retv{second}= $time[2];} else {$retv{second}= $def{second};} + + $retv{datetime}=DbLog_implode_datetime($retv{year}, $retv{month}, $retv{day}, $retv{hour}, $retv{minute}, $retv{second}); + + #Log 1, Dumper(%retv); + return %retv +} + +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($) +{ + my ($hash)= @_; + + my $configfilename= $hash->{CONFIGURATION}; + if(!open(CONFIG, $configfilename)) { + Log 1, "Cannot open database configuration file $configfilename."; + return 0; } + my @config=; + close(CONFIG); + + my %dbconfig; + eval join("", @config); + + my $dbconn= $dbconfig{connection}; + my $dbuser= $dbconfig{user}; + my $dbpassword= $dbconfig{password}; + + #check the database model + if($dbconn =~ m/pg:/i) { + $hash->{DBMODEL}="POSTGRESQL"; + } elsif ($dbconn =~ m/mysql:/i) { + $hash->{DBMODEL}="MYSQL"; + } elsif ($dbconn =~ m/oracle:/i) { + $hash->{DBMODEL}="ORACLE"; + } elsif ($dbconn =~ m/sqlite:/i) { + $hash->{DBMODEL}="SQLITE"; + } else { + $hash->{DBMODEL}="unknown"; + Log 3, "Unknown dbmodel type in configuration file $configfilename."; + Log 3, "Only Mysql, Postgresql, Oracle, SQLite are fully supported."; + Log 3, "It may cause SQL-Erros during generating plots."; + } + + Log 3, "Connecting to database $dbconn with user $dbuser"; + my $dbh = DBI->connect_cached("dbi:$dbconn", $dbuser, $dbpassword); + if(!$dbh) { + Log 2, "Can't connect to $dbconn: $DBI::errstr"; + return 0; + } + Log 3, "Connection to db $dbconn established"; + $hash->{DBH}= $dbh; + + return 1; +} + +################################################################ +# +# Prozeduren zum Ausfuehren des SQLs +# +################################################################ +sub +DbLog_ExecSQL1($$) +{ + my ($dbh,$sql)= @_; + + my $sth = $dbh->do($sql); + if(!$sth) { + Log 2, "DBLog error: " . $DBI::errstr; + return 0; + } + return $sth; +} + +sub +DbLog_ExecSQL($$) +{ + my ($hash,$sql)= @_; + Log GetLogLevel($hash->{NAME},5), "Executing $sql"; + my $dbh= $hash->{DBH}; + my $sth = DbLog_ExecSQL1($dbh,$sql); + if(!$sth) { + #retry + $dbh->disconnect(); + if(!DbLog_Connect($hash)) { + Log 2, "DBLog reconnect failed."; + return 0; + } + $dbh= $hash->{DBH}; + $sth = DbLog_ExecSQL1($dbh,$sql); + if(!$sth) { + Log 2, "DBLog retry failed."; + return 0; + } + Log 2, "DBLog retry ok."; + } + return $sth; +} + +################################################################ +# +# GET Funktion +# wird zb. zur Generierung der Plots implizit aufgerufen +# +################################################################ +sub +DbLog_Get($@) +{ + my ($hash, @a) = @_; + + return "Usage: get $a[0] ...\n". + " where column_spec is :::\n" . + " see the #DbLog entries in the .gplot files\n" . + " is not used, only for compatibility for FileLog, please use - \n" . + " is a prefix, - means stdout\n" + if(int(@a) < 5); + shift @a; + my $inf = shift @a; + my $outf = 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") { + $outf = "-"; + $internal = 1; + } + + my @readings = (); + my (%sqlspec, %from_datetime, %to_datetime); + + #uebergebenen Timestamp anpassen + #moegliche Formate: YYYY | YYYY-MM | YYYY-MM-DD | YYYY-MM-DD_HH24 + $from =~ s/_/\ /g; + $to =~ s/_/\ /g; + %from_datetime = DbLog_explode_datetime($from, DbLog_explode_datetime("2000-01-01 00:00:00", ())); + %to_datetime = DbLog_explode_datetime($to, DbLog_explode_datetime("2099-01-01 00:00:00", ())); + $from = $from_datetime{datetime}; + $to = $to_datetime{datetime}; + + + my ($retval,$sql_timestamp,$sql_dev,$sql_reading,$sql_value, $type, $event, $unit) = ""; + my $writeout = 0; + my (@min, @max, @sum, @cnt, @lastv, @lastd); + my (%tstamp, %lasttstamp, $out_tstamp, $out_value, $minval, $maxval); #fuer delta-h/d Berechnung + + #extract the Device:Reading arguments into @readings array + for(my $i = 0; $i < int(@a); $i++) { + @fld = split(":", $a[$i], 5); + $readings[$i][0] = $fld[0]; # Device + $readings[$i][1] = $fld[1]; # Reading + $readings[$i][2] = $fld[2]; # Default + $readings[$i][3] = $fld[3]; # function + $readings[$i][4] = $fld[4]; # regexp + } + + my $dbh= $hash->{DBH}; + + #vorbereiten der DB-Abfrage, DB-Modell-abhaengig + if ($hash->{DBMODEL} eq "POSTGRESQL") { + $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)"; + } 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')"; + $sqlspec{to_timestamp} = "TO_TIMESTAMP('$to', 'YYYY-MM-DD HH24:MI:SS')"; + $sqlspec{reading_clause} = "(DEVICE || '|' || READING)"; + } elsif ($hash->{DBMODEL} eq "MYSQL") { + $sqlspec{get_timestamp} = "DATE_FORMAT(TIMESTAMP, '%Y-%m-%d %H:%i:%s')"; + $sqlspec{from_timestamp} = "STR_TO_DATE('$from', '%Y-%m-%d %H:%i:%s')"; + $sqlspec{to_timestamp} = "STR_TO_DATE('$to', '%Y-%m-%d %H:%i:%s')"; + $sqlspec{reading_clause} = "CONCAT(DEVICE,'|',READING)" + } elsif ($hash->{DBMODEL} eq "SQLITE") { + $sqlspec{get_timestamp} = "TIMESTAMP"; + $sqlspec{from_timestamp} = "'$from'"; + $sqlspec{to_timestamp} = "'$to'"; + $sqlspec{reading_clause} = "(DEVICE || '|' || READING)"; + } else { + $sqlspec{get_timestamp} = "TIMESTAMP"; + $sqlspec{from_timestamp} = "'$from'"; + $sqlspec{to_timestamp} = "'$to'"; + $sqlspec{reading_clause} = "(DEVICE || '|' || READING)"; + } + + if(uc($outf) eq "ALL") { + $sqlspec{all} = ",TYPE,EVENT,UNIT"; + } else { + $sqlspec{all} = ""; + } + + for(my $i=0; $i[0]."|".$readings[$i]->[1]."') + AND TIMESTAMP > $sqlspec{from_timestamp} + AND TIMESTAMP < $sqlspec{to_timestamp} + ORDER BY TIMESTAMP"; + + Log GetLogLevel($hash->{NAME},5), "Executing $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") { + $retval .= "Timestamp: Device, Type, Event, Reading, Value, Unit\n"; + $retval .= "=====================================================\n"; + } + while( ($sql_timestamp,$sql_dev,$sql_reading,$sql_value, $type, $event, $unit)= $sth->fetchrow_array) { + $writeout = 0; + $out_value = ""; + $out_tstamp = ""; + + ############ Auswerten des 5. Parameters: Regexp ################### + if($readings[$i]->[4]) { + #evaluate + my $val = $sql_value; + eval("$readings[$i]->[4]"); + $sql_value = $val; + if($@) {Log 3, "DbLog: Fehler in der übergebenen Funktion: <".$readings[$i]->[4].">, Fehler: $@";} + $out_tstamp = $sql_timestamp; + $writeout=1; + } + + ############ Auswerten des 4. Parameters: function ################### + if($readings[$i]->[3] eq "int") { + #nur den integerwert uebernehmen falls zb value=15?C + $out_value = $1 if($sql_value =~ m/^(\d+).*/o); + $out_tstamp = $sql_timestamp; + $writeout=1; + + } elsif ($readings[$i]->[3] =~ m/^int(\d+).*/o) { + #?bernehme den Dezimalwert mit den angegebenen Stellen an Nachkommastellen + $out_value = $1 if($sql_value =~ m/^([-\.\d]+).*/o); + $out_tstamp = $sql_timestamp; + $writeout=1; + + } elsif ($readings[$i]->[3] eq "delta-h") { + #Berechnung eines Stundenwertes + %tstamp = DbLog_explode_datetime($sql_timestamp, ()); + if($lastd[$i] eq "undef") { + %lasttstamp = DbLog_explode_datetime($sql_timestamp, ()); + } else { + %lasttstamp = DbLog_explode_datetime($lastd[$i], ()); + } + if("$tstamp{hour}" ne "$lasttstamp{hour}") { + # Aenderung der stunde, Berechne Delta + $out_value = sprintf("%0.1f", $maxval - $minval); + $out_tstamp = DbLog_implode_datetime($lasttstamp{year}, $lasttstamp{month}, $lasttstamp{day}, $lasttstamp{hour}, "30", "00"); + $minval = 999999; + $maxval = -999999; + $writeout=1; + } + } elsif ($readings[$i]->[3] eq "delta-d") { + #Berechnung eines Tageswertes + %tstamp = DbLog_explode_datetime($sql_timestamp, ()); + if($lastd[$i] eq "undef") { + %lasttstamp = DbLog_explode_datetime($sql_timestamp, ()); + } else { + %lasttstamp = DbLog_explode_datetime($lastd[$i], ()); + } + if("$tstamp{day}" ne "$lasttstamp{day}") { + # Aenderung des Tages, Berechne Delta + $out_value = sprintf("%0.1f", $maxval - $minval); + $out_tstamp = DbLog_implode_datetime($lasttstamp{year}, $lasttstamp{month}, $lasttstamp{day}, "00", "00", "00"); + $minval = 999999; + $maxval = -999999; + $writeout=1; + } + } else { + $out_value = $sql_value; + $out_tstamp = $sql_timestamp; + $writeout=1; + } + + ###################### Ausgabe ########################### + if($writeout) { + if(uc($outf) eq "ALL") { + $retval .= sprintf("%s: %s, %s, %s, %s, %s, %s\n", $out_tstamp, $sql_dev, $type, $event, $sql_reading, $out_value, $unit); + } else { + $out_tstamp =~ s/\ /_/g; #needed by generating plots + $retval .= "$out_tstamp $out_value\n"; + } + } + + if(defined($sql_value) || $sql_value =~ m/^[-\.\d]+$/o){ + #nur setzen wenn nummerisch + $min[$i] = $sql_value if($sql_value < $min[$i]); + $max[$i] = $sql_value if($sql_value > $max[$i]);; + $sum[$i] += $sql_value; + $minval = $sql_value if($sql_value < $minval); + $maxval = $sql_value if($sql_value > $maxval); + } else { + $min[$i] = 0; + $max[$i] = 0; + $sum[$i] = 0; + $minval = 0; + $maxval = 0; + } + $cnt[$i]++; + $lastv[$i] = $sql_value; + $lastd[$i] = $sql_timestamp; + + } #while fetchrow + + ######## den letzten Abschlusssatz rausschreiben ########## + if($readings[$i]->[3] eq "delta-h" || $readings[$i]->[3] eq "delta-d") { + $out_value = sprintf("%0.1f", $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, $sql_dev, $type, $event, $sql_reading, $out_value, $unit); + } else { + $out_tstamp =~ s/\ /_/g; #needed by generating plots + $retval .= "$out_tstamp $out_value\n"; + } + } + # DatenTrenner setzen + $retval .= "#$readings[$i]->[0]:$readings[$i]->[1]:$readings[$i]->[2]:$readings[$i]->[3]:$readings[$i]->[4]\n"; + } #for @readings + + #Ueberfuehren der gesammelten Werte in die globale Variable %data + for(my $j=0; $j +

DbLog

+
    +
    + + + Define +
      + define <name> DbLog <configfilename> <regexp> +

      + + Log events to a database. The database connection is defined in + <configfilename> (see sample configuration file + contrib/dblog/db.conf). The configuration is stored in a separate file + to avoid storing the password in the main configuration file and to have it + visible in the output of the list command. +

      + + The modules DBI and DBD::<dbtype> + need to be installed (use cpan -i <module> + if your distribution does not have it). +

      + + <regexp> is the same as in FileLog. +

      + Sample code to create a MySQL/PostgreSQL database is in + <DBType>_create.sql. + The database contains two tables: current and + history. The latter contains all events whereas the former only + 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. VALUE: actual reading extracted from event, + e.g. 71
      12. +
      13. UNIT: unit extracted from event, e.g. %
      14. +
      + The content of VALUE is optimized for automated post-processing, e.g. + yes is translated to 1 +

      + The current values can be retrieved by the following code like FileLog:
      +
        + get myDbLog - - 2012-11-10 2012-11-10 KS300:temperature:: +
      +

      + Examples: +
        + # log everything to database
        + + define myDbLog DbLog /etc/fhem/db.conf .*:.* +
      +
    + + + + Set
      N/A

    + + + Get +
      + get <name> <infile> <outfile> <from> + <to> <column_spec> +

      + Read data from the Database, used by frontends to plot data without direct + access to the Database.
      + +
        +
      • <in>
        + A dummy parameter for FileLog compatibility. Always set to -
      • +
      • <out>
        + A dummy parameter for FileLog compatibility. Set it to - + to check the output for plot-computing.
        Set it to the special keyword + all to get all columns from Database.
      • +
      • <from> / <to>
        + Used to select the data. Please use the following timeformat or + an initial substring of it:
        +
          YYYY-MM-DD_HH24:MI:SS
      • +
      • <column_spec>
        + For each column_spec return a set of data separated by + a comment line on the current connection.
        + Syntax: <device>:<reading>:<default>:<fn>:<regexp>
        +
          +
        • <device>
          + The name of the device. Case sensitive
        • +
        • <reading>
          + The reading of the given device to select. Case sensitive. +
        • +
        • <default>
          + no implemented yet +
        • +
        • <fn> + One of the following: +
            +
          • int
            + Extract the integer at the beginning of the string. Used e.g. + for constructs like 10%
          • +
          • int<digit>
            + Extract the decimal digits including negative character and + decimal point at the beginning og the string. Used e.g. + for constructs like 15.7°C
          • +
          • delta-h / delta-d
            + Return the delta of the values for a given hour or a given day. + Used if the column contains a counter, as is the case for the + KS300 rain column.
          • +
        • +
        • <regexp>
          + The string is evaluated as a perl expression. $val is the + current value returned from the Database. The regexp is executed + before <fn> parameter.
          + Note: The string/perl expression cannot contain spaces, + as the part after the space will be considered as the + next column_spec. +
        • +
      • +
      +

      + Examples: +
        +
      • get myDbLog - - 2012-11-10 2012-11-20 KS300:temperature
      • +
      • get myDbLog - - 2012-11-10_10 2012-11-10_20 KS300:temperature::int1
        + like from 10am until 08pm at 10.11.2012
      • +
      • get myDbLog - all 2012-11-10 2012-11-20 KS300:temperature
      • +
      • get myDbLog - - 2012-11-10 2012-11-20 KS300:temperature KS300:rain::delta-h KS300:rain::delta-d
      • +
      • get myDbLog - - 2012-11-10 2012-11-20 MyFS20:data:::$val=~s/(on|off).*/$1eq"on"?1:0/eg
        + return 1 for all occurance of on* (on|on-for-timer etc) and 0 for all off*
      • +
      • get myDbLog - - 2012-11-10 2012-11-20 Bodenfeuchte:data:::$val=~s/.*B:\s([-\.\d]+).*/$1/eg
        + Example of OWAD: value like this: "A: 49.527 % B: 66.647 % C: 9.797 % D: 0.097 V"
        + and output for port B is like this: 2012-11-20_10:23:54 66.647
      • +
      +

      +
    + + Attributes
      N/A

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

DbLog

+
    +
    + + + Define +
      + define <name> DbLog <configfilename> <regexp> +

      + + Speichert Events in eine Datenbank. Die Datenbankverbindungsparameter werden + definiert in <configfilename>. (Vergleiche + Beipspielkonfigurationsdatei in contrib/dblog/db.conf).
      + Die Konfiguration ist in einer sparaten Datei abgelegt um das Datenbankpasswort + nicht in Klartext in der FHEM-Haupt-Konfigurationsdatei speichern zu müssen. + Ansonsten wäre es mittels des list + Befehls einfach auslesbar. +

      + + Die Perl-Module DBI and DBD::<dbtype> + müssen installiert werden (use cpan -i <module> + falls die eigene Distribution diese nicht schon mitbringt). +

      + + <regexp> ist identisch wie FileLog. +

      + Ein Beispielcode zum Erstellen einer MySQL/PostGreSQL Datenbak ist in + contrib/dblog/<DBType>_create.sql zu finden. + Die Datenbank beinhaltet 2 Tabellen: current und + history. Die Tabelle current enthält den letzten Stand + pro Device und Reading. In der Tabelle history sind alle + Events historisch gespeichert. + + Die Tabellenspalten haben folgende Bedeutung: +
        +
      1. TIMESTAMP: Zeitpunkt des Events, z.B. 2007-12-30 21:45:22
      2. +
      3. DEVICE: name des Devices, z.B. Wetterstation
      4. +
      5. TYPE: Type des Devices, z.B. KS300
      6. +
      7. EVENT: das auftretende Event als volle Zeichenkette + z.B. humidity: 71 (%)
      8. +
      9. READING: Name des Readings, ermittelt aus dem Event, + z.B. humidity
      10. + +
      11. VALUE: aktueller Wert des Readings, ermittelt aus dem Event, + z.B. 71
      12. +
      13. UNIT: Einheit, ermittelt aus dem Event, z.B. %
      14. +
      + Der Wert des Rreadings ist optimiert für eine automatisierte Nachverarbeitung + z.B. yes ist transformiert nach 1 +

      + Die gespeicherten Werte können mittels GET Funktion angezeigt werden: +
        + get myDbLog - - 2012-11-10 2012-11-10 KS300:temperature +
      +

      + Beispiel: +
        + Speichert alles in der Datenbank
        + + define myDbLog DbLog /etc/fhem/db.conf .*:.* +
      +
    + + + + Set
      N/A

    + + + Get +
      + get <name> <infile> <outfile> <from> + <to> <column_spec> +

      + Ließt Daten aus der Datenbank. Wird durch die Frontends benutzt um Plots + zu generieren ohne selbst auf die Datenank zugreifen zu müssen. +
      + +
        +
      • <in>
        + Ein Dummy Parameter um eine Kompatibilität zum Filelog herzustellen. + Dieser Parameter ist immer auf - zu setzen. +
      • +
      • <out>
        + Ein Dummy Parameter um eine Kompatibilität zum Filelog herzustellen. + Dieser Parameter ist immer auf - zu setzen um die + Ermittlung der Daten aus der Datenbank für die Plotgenerierung zu prüfen.
        + Durchd ie Angabe des Schlüsselworts all werden alle + Spalten der Datenbank ausgegeben. +
      • +
      • <from> / <to>
        + Wird benutzt um den Zeitraum der Daten einzugrenzen. Es ist das folgende + Zeitformat oder ein Teilstring davon zu benutzen:
        +
          YYYY-MM-DD_HH24:MI:SS
      • +
      • <column_spec>
        + Für jede column_spec Gruppe wird ein Datenset zurückgegeben welches + durch einen Kommentar getrennt wird. Dieser Kommentar repräsentiert + die column_spec.
        + Syntax: <device>:<reading>:<default>:<fn>:<regexp>
        +
          +
        • <device>
          + Der Name des Devices. Achtung: Groß/Kleinschreibung beachten!
        • +
        • <reading>
          + Das REading des angegebenen Devices zur Datenselektion. + Achtung: Groß/Kleinschreibung beachten! +
        • +
        • <default>
          + Zur Zeit noch nicht implementiert. +
        • +
        • <fn> + Angabe einer speziellen Funktion: +
            +
          • int
            + Ermittelt den Zahlenwert ab dem Anfang der Zeichenkette aus der + Spalte "VALUE". Benutzt z.B. für Ausprägungen wie 10%. +
          • +
          • int<digit>
            + Ermittelt den Zahlenwert ab dem Anfang der Zeichenkette aus der + Spalte "VALUE", inclusive negativen Vorzeichen und Dezimaltrenner. + Benutzt z.B. für Ausprägungen wie -5.7°C. +
          • +
          • delta-h / delta-d
            + Ermittelt die relative Veränderung eines Zahlenwertes pro Stunde + oder pro Tag. Wird benutzt z.B. für Spalten die einen + hochlaufenden Zähler enthalten wie im Falle für ein KS300 Regenzähler + oder dem 1-wire Modul OWCOUNT. +
          • +
        • +
        • <regexp>
          + Diese Zeichenkette wird als Perl Befehl ausgewertet. $val ist der + aktuelle Wert die die Datenbank für ein Device/Reading ausgibt. + Die regexp wird vor dem angegebenen <fn> Parameter ausgeführt. +
          + Bitte zur Beachtung: Diese Zeichenkette darf keine Leerzeichen + enthalten da diese sonst als <column_spec> Trennung + interpretiert werden und alles nach dem Leerzeichen als neue + <column_spec> gesehen wird. +
        • +
      • +
      +

      + Beispiele: +
        +
      • get myDbLog - - 2012-11-10 2012-11-20 KS300:temperature
      • +
      • get myDbLog - - 2012-11-10_10 2012-11-10_20 KS300:temperature::int1
        + gibt Daten aus von 10Uhr bis 20Uhr am 10.11.2012
      • +
      • get myDbLog - all 2012-11-10 2012-11-20 KS300:temperature
      • +
      • get myDbLog - - 2012-11-10 2012-11-20 KS300:temperature KS300:rain::delta-h KS300:rain::delta-d
      • +
      • get myDbLog - - 2012-11-10 2012-11-20 MyFS20:data:::$val=~s/(on|off).*/$1eq"on"?1:0/eg
        + gibt 1 zurück für alle Ausprägungen von on* (on|on-for-timer etc) und 0 für alle off*
      • +
      • get myDbLog - - 2012-11-10 2012-11-20 Bodenfeuchte:data:::$val=~s/.*B:\s([-\.\d]+).*/$1/eg
        + Beispiel von OWAD: Ein Wert wie z.B.: "A: 49.527 % B: 66.647 % C: 9.797 % D: 0.097 V"
        + und die Ausgabe ist für das Reading B folgende: 2012-11-20_10:23:54 66.647
      • +
      +

      +
    + + Attributes
      N/A

    +
+=end html_DE +=cut +