From aa33411037920cb689a0e6a9dfdac0ca2c20782e Mon Sep 17 00:00:00 2001 From: justme1968 Date: Sun, 16 Nov 2014 16:49:31 +0000 Subject: [PATCH] 98_logProxy.pm: new module 98_logProxy.pm added git-svn-id: svn://svn.code.sf.net/p/fhem/code/trunk@6999 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 1 + fhem/FHEM/98_logProxy.pm | 968 +++++++++++++++++++++++++++++++++++++++ fhem/MAINTAINER.txt | 1 + 3 files changed, 970 insertions(+) create mode 100644 fhem/FHEM/98_logProxy.pm diff --git a/fhem/CHANGED b/fhem/CHANGED index 1687da2d7..76da1cfff 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # 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: new module 98_logProxy.pm added (justme1968) - change: 66_ECMD: ReadyFn added (fixes issue under Windows) - change: 02_RSS: use a GUID in RSS; urlq source for img command - feature: 70_PushNotifier improve usebility, configuration without cURL (xusader) diff --git a/fhem/FHEM/98_logProxy.pm b/fhem/FHEM/98_logProxy.pm new file mode 100644 index 000000000..81ce82cc7 --- /dev/null +++ b/fhem/FHEM/98_logProxy.pm @@ -0,0 +1,968 @@ +# $Id$ +############################################################################## +# +# This file 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 . +# +############################################################################## + +package main; + +use strict; +use warnings; + +use SetExtensions; + +sub logProxy_Initialize($) +{ + my ($hash) = @_; + + $hash->{DefFn} = "logProxy_Define"; + $hash->{UndefFn} = "logProxy_Undefine"; + #$hash->{SetFn} = "logProxy_Set"; + $hash->{GetFn} = "logProxy_Get"; + #$hash->{AttrList} = "disable:1 "; + + $hash->{SVG_sampleDataFn} = "logProxy_sampleDataFn"; +} + + +sub logProxy_Define($$) +{ + my ($hash, $def) = @_; + + my @args = split("[ \t]+", $def); + + my $name = $args[0]; + + my $usage = "Usage: define logProxy"; + + return $usage if( int(@args) != 2 ); + + my $d = $modules{logProxy}{defptr}; + return "logProxy device already defined as $d->{NAME}." if( defined($d) ); + $modules{logProxy}{defptr} = $hash; + + $hash->{STATE} = 'Initialized'; + + return undef; +} + +sub logProxy_Undefine($$) +{ + my ($hash,$arg) = @_; + my $name = $hash->{NAME}; + + delete $modules{logProxy}{defptr}; + + return undef; +} + +sub +logProxy_sampleDataFn($$$$$) +{ + my ($ldName, $flog, $max, $conf, $wName) = @_; + + my $desc = "Type,Spec"; + + my $columns = "ConstX,ConstY,Func,FileLog,DbLog"; + + my @htmlArr; + $max = 16 if($max > 16); + for(my $r=0; $r < $max; $r++) { + my @f = split(":", ($flog->[$r] ? $flog->[$r] : ":"), 6); + my $ret = ""; + $ret .= SVG_sel("par_${r}_0", $columns, $f[0]); + $ret .= SVG_txt("par_${r}_1", "", join(":", @f[1..@f-1]), 30); + push @htmlArr, $ret; + } + + my @example; + push @example, 'ConstY:0'; + push @example, 'ConstY:$data{avg1}'; + push @example, 'ConstY:$data{avg2}'; + push @example, 'DbLog:myDB:myReading'; + push @example, 'FileLog:myFileLog:4:myReading'; + push @example, 'FileLog:FileLog_<SPEC1>:4:<SPEC1>.power'; + push @example, 'FileLog:FileLog_<SPEC1>:4:<SPEC1>.consumption'; + push @example, 'Func:logProxy_WeekProfile2Plot("HCB",$from,$to)'; + push @example, 'Func:logProxy_WeekProfile2Plot("myHeatingControl",$from,$to,"(\\d*)\$")'; + push @example, 'ConstX:logProxy_shiftTime($from,60*60*2),$data{min1},$data{max1}'; + +#Log 3, Dumper $desc; +#Log 3, Dumper @htmlArr; +#Log 3, Dumper $example; + + return ($desc, \@htmlArr, join("
", @example)); +} + +sub +logProxy_Set($@) +{ + return undef; +} + +#WeekProfile format: {$wday}{$time}{$value} with 0 = sunday +sub +logProxy_Heating_Controll2WeekProfile($) +{ + my ($d) = @_; + + return undef if( !defined($defs{$d}) ); + return undef if( !defined($defs{$d}->{helper}{SWITCHINGTIME}) ); + + return $defs{$d}->{helper}{SWITCHINGTIME}; +} +sub +logProxy_HM2WeekProfile($;$) +{ + my ($d,$list) = @_; + + return undef if( !defined($defs{$d}) ); + + # default to 1st list of tc-it + $list = "P1" if ( !$list ); + + # if tc-it + my @rl = sort( grep /^R_${list}_[0-7]_tempList...$/,keys %{$defs{$d}{READINGS}} ); + # else cc-tc and rt + @rl = sort( grep /^R_[0-7]_tempList...$/,keys %{$defs{$d}{READINGS}} ) if( !@rl ); + + return undef if( !@rl ); + + my %profile = (); + for(my $i=0; $i<7; ++$i) { + # correct wday + my $reading = ReadingsVal($d,$rl[($i+1)%7],undef); + + # collect 'until' switching times + my %tmp = (); + my @parts = split( ' ', $reading ); + while( @parts ) { + my $time = shift @parts; + $tmp{$time} = shift @parts; + } + + # shift 'until' switching times into 'from' switching times + # can not be done in one step if times are out of order + my %st = (); + my $time = "00:00"; + foreach my $key (sort (keys %tmp)) { + $st{$time} = $tmp{$key}; + $time = $key; + } + + $profile{$i} = \%st; + + } + + return undef if (scalar (keys %profile) != 7); + + return \%profile; +} +sub +logProxy_MAX2WeekProfile($) +{ + my ($d) = @_; + + return undef if( !defined($defs{$d}) ); + + my @rl = sort( grep /^weekprofile-.-...-(temp|time)$/,keys %{$defs{$d}{READINGS}} ); + + return undef if( !@rl ); + + my %profile = (); + for(my $i=0; $i<7; ++$i) { + # correct wday + my $temps = ReadingsVal($d,$rl[(($i+1)%7)*2],undef); + my $times = ReadingsVal($d,$rl[(($i+1)%7)*2+1],undef); + + my %st = (); + + my @temps = split( '/', $temps ); + my @times = split( '/', $times ); + while( @times ) { + my $temp = shift @temps; + $temp =~ s/\s*([\d\.]*).*/$1/; + + my $time = shift @times; + $time =~ s/\s*(\d\d:\d\d).*/$1/; + + $st{$time} = $temp; + } + + $profile{$i} = \%st; + } + + return \%profile; +} +# sample implementaion to plot the week profile of a Heating_Control or HM Thermostat device. +sub +logProxy_WeekProfile2Plot($$$;$) +{ + my ($profile, $from, $to, $regex) = @_; + + return undef if( !$profile ); + + if( $regex ) { + eval { "test" =~ m/$regex/ }; + if( $@ ) { + Log3 undef, 3, "logProxy_WeekProfile2Plot: $regex: $@"; + return undef; + } + } + + if( defined($defs{$profile}) ) { + if( $defs{$profile}{TYPE} eq "Heating_Control" ) { + $profile = logProxy_Heating_Controll2WeekProfile($profile); + } elsif( $defs{$profile}{TYPE} eq "WeekdayTimer" ) { + $profile = logProxy_Heating_Controll2WeekProfile($profile); + } elsif( $defs{$profile}{TYPE} eq "CUL_HM" ) { + my ($p,$l) = split( ',', $profile, 2 ); + $profile = logProxy_HM2WeekProfile($p, $l); + } elsif( $defs{$profile}{TYPE} eq "MAX" ) { + $profile = logProxy_MAX2WeekProfile($profile); + } else { + Log3 undef, 2, "logProxy_WeekProfile2Plot: $profile is not a Heating_Control, WeekdayTimer, CUL_HM or MAX device"; + return undef; + } + } + +#Log 3, Dumper $profile; + + if( ref($profile) ne "HASH" ) { + Log3 undef, 2, "logProxy_WeekProfile2Plot: no profile hash given"; + return undef; + } + + my $fromsec = SVG_time_to_sec($from); + my $tosec = SVG_time_to_sec($to); + + my (undef,undef,undef,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($fromsec); + + my $min = 999999; + my $max = -999999; + + # go back one day to get the start value, TODO: go back multiple days + $mday -= 1; + $wday -= 1; + $wday %= 7; + + my $ret = ""; + my $value; + my $prev_value; + my $sec = $fromsec; + # while not end of plot range reached + while( $sec < $tosec ) { + return undef if( !defined($profile->{$wday}) ); + + # for all switching times of current day + foreach my $st (sort (keys %{ $profile->{$wday} })) { + #remember previous value for start of plot range + $prev_value = $value; + + my ($h, $m, $s) = split( ':', $st ); + $s = 0 if( !$s ); + $value = $profile->{$wday}{$st}; + + if( $regex ) { + if( $value =~ m/$regex/ ) { + Log3 undef, 4, "logProxy_WeekProfile2Plot: $value =~ m/$regex/ => $1"; + $value = $1; + } else { + Log3 undef, 3, "logProxy_WeekProfile2Plot: $value =~ m/$regex/ => no match"; + } + } + + # map some specials to values and eco and comfort to temperatures + $value = 0 if( $value eq "off" ); + $value = 1 if( $value eq "on" ); + $value = 1 if( $value eq "up" ); + $value = 0 if( $value eq "down" ); + $value = 18 if( $value eq "eco" ); + $value = 22 if( $value eq "comfort" ); + + # 'dirty' hack that exploits the feature that $mday can be < 0 and > 31. + # everything should better be based on a real second counter + my $timestamp = sprintf("%04d-%02d-%02d_%02d:%02d:%02d", 1900+$year, 1+$mon, $mday, $h, $m, $s ); + $sec = SVG_time_to_sec($timestamp); + + # skip all values before start of plot range + next if( SVG_time_to_sec($timestamp) < $fromsec ); + + # add first value at start of plot range + if( !$ret && $prev_value ) { + $min = $prev_value if( $prev_value < $min ); + $max = $prev_value if( $prev_value > $max ); + $ret .= "$from $prev_value\n"; + } + + # done if after end of plot range + last if( SVG_time_to_sec($timestamp) > $tosec ); + + $min = $value if( $value < $min ); + $max = $value if( $value > $max ); + + # add actual controll point + $ret .= "$timestamp $value\n"; + } + + # next day + $mday += 1; + $wday += 1; + $wday %= 7; + } + # add last value at end of plot range + $ret .= "$to $prev_value\n"; + + return ($ret,$min,$max,$prev_value); +} + +sub logProxy_hms2dec($){ + my ($h,$m,$s) = split(":", shift); + $m = 0 if(!$m); + $s = 0 if(!$s); + my $t = $m * 60; + $t += $s; + $t /= 3600; + $t += $h; + return ($t) +} + +sub logProxy_dec2hms($){ + my ($t) = @_; + my $h = int($t); + my $r = ($t - $h)*3600; + my $m = int($r/60); + my $s = $r - $m*60; + return sprintf("%02d:%02d:%02d",$h,$m,$s); +} + +sub +logProxy_Range2Zoom($) +{ + my( $range ) = @_; + + return "year" if( $range > 1+60*60*24*28*6); + return "month" if( $range > 1+60*60*24*28); + return "week" if( $range > 1+60*60*24); + return "day" if( $range > 1+60*60*6); + return "qday" if( $range > 1+60*60 ); + return "hour"; +} +my %logProxy_stepDefault = ( year => 60*60*24, + month => 60*60*24, + week => 60*60*6, + day => 60*60, + qday => 60*15, + hour => 60, ); + +# sample implementaion to plot an arbitrary function +sub +logProxy_Func2Plot($$$;$) +{ + my ($from, $to, $func, $step) = @_; + + my $fromsec = SVG_time_to_sec($from); + my $tosec = SVG_time_to_sec($to); + my $secs = $tosec - $fromsec; + + $step = \%logProxy_stepDefault if( !$step ); + $step = eval $step if( $step =~ m/^{.*}$/ ); + $step = $step->{logProxy_Range2Zoom($secs)} if( ref($step) eq "HASH" ); + $step = $logProxy_stepDefault{logProxy_Range2Zoom($secs)} if( !$step ); + + my $min = 999999; + my $max = -999999; + + my $ret = ""; + + my $value; + for(my $sec=$fromsec; $sec<$tosec; $sec+=$step) { + my ($s,$m,$h,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($sec); + + $value = eval $func; + if( $@ ) { + Log3 undef, 1, "logProxy_Func2Plot: $func: $@"; + next; + } + + my $timestamp = sprintf("%04d-%02d-%02d_%02d:%02d:%02d", 1900+$year, 1+$mon, $mday, $h, $m, $s ); + + $min = $value if( $value < $min ); + $max = $value if( $value > $max ); + + # add actual controll point + $ret .= "$timestamp $value\n"; + } + + return ($ret,$min,$max,$value); +} + + +# shift time by offset seconds (or months if offset ends with m) +sub +logProxy_shiftTime($$) +{ + my ($time, $offset) = @_; + + $time =~ s/ /_/; + + if( $offset =~ m/((-)?\d)*m/ ) { + my @t = split("[-_:]", $time); + $time = mktime($t[5],$t[4],$t[3],$t[2],$t[1]-1+$1,$t[0]-1900,0,0,-1);; + } else { + $time = SVG_time_to_sec($time); + $time += $offset; + } + + my @t = localtime($time); + $time = sprintf("%04d-%02d-%02d_%02d:%02d:%02d", $t[5]+1900, $t[4]+1, $t[3], $t[2], $t[1], $t[0]); + + return $time; +} + +# shift plot data by offset +sub +logProxy_shiftData($$;$$) +{ + my ($dp, $offset, $from, $to) = @_; + + my ($dpl,$dpoff,$l) = (length($$dp), 0, ""); + while($dpoff < $dpl) { # using split instead is memory hog + my $ndpoff = index($$dp, "\n", $dpoff); + if($ndpoff == -1) { + $l = substr($$dp, $dpoff); + + } else { + $l = substr($$dp, $dpoff, $ndpoff-$dpoff); + + } + + if($l =~ m/^#/) { + } else { + my ($d, $v) = split(" ", $l); + + $d = logProxy_shiftTime($d, $offset); + + substr($$dp, $dpoff, 19, $d); + + } + + $dpoff = $ndpoff+1; + last if($ndpoff == -1); + } +} + +sub +logProxy_linearInterpolate($$$$$) { + my ($t1, $v1, $t2, $v2, $t ) = @_; + + my $dt = $t2 - $t1; + + return $v1 if( !$dt ); + + my $dv = $v2 - $v1; + + my $v = $v1 + $dv * ( ($t-$t1) / $dt ); + + return $v; +} + +# clip plot data to [$from,$to] range +sub +logProxy_clipData($$$$;$) +{ + my ($dp, $from, $to, $interpolate, $predict) = @_; + + my $ret = ""; + my $comment = ""; + + my ($dpl,$dpoff,$l) = (length($$dp), 0, ""); + my $prev_value; + my $prev_timestamp; + my $next_value; + my $next_timestamp; + while($dpoff < $dpl) { # using split instead is memory hog + my $ndpoff = index($$dp, "\n", $dpoff); + if($ndpoff == -1) { + $l = substr($$dp, $dpoff); + } else { + $l = substr($$dp, $dpoff, $ndpoff-$dpoff); + } + + if($l =~ m/^#/) { + $comment .= "$l\n"; + } else { + my ($d, $v) = split(" ", $l); + + my $sec = SVG_time_to_sec($d); + if( $sec < $from ) { + $prev_timestamp = $d; + $prev_value = $v; + + } elsif( $sec > $to ) { + if( !$next_value ) { + $next_timestamp = $d; + $next_value = $v; + } + + } else { + if( !$ret && $prev_value && $sec < $from ) { + + my $value = $prev_value; + $value = logProxy_linearInterpolate( SVG_time_to_sec($prev_timestamp), $prev_value, SVG_time_to_sec($d), $v, $from ) if( $interpolate ); + + my @t = localtime($from); + my $timestamp = sprintf("%04d-%02d-%02d_%02d:%02d:%02d", $t[5]+1900, $t[4]+1, $t[3], $t[2], $t[1], $t[0]); + $ret .= "$timestamp $value\n"; + } + + $ret .= "$l\n"; + + $prev_timestamp = $d; + $prev_value = $v; + + } + } + + $dpoff = $ndpoff+1; + last if($ndpoff == -1); + } + + #if predict is set -> extend bejond last value + if( defined($predict) && !$next_value ) { + $next_value = $prev_value; + + #if $predict = 0 -> predict to end of plot + my $time = $to; + #else predict by $predict + $time = SVG_time_to_sec($prev_timestamp) + $predict if( $predict ); + + #but not later than now + my ($now) = gettimeofday(); + $to = minNum( $time, $now ); + + my @t = localtime($to); + $next_timestamp = sprintf("%04d-%02d-%02d_%02d:%02d:%02d", $t[5]+1900, $t[4]+1, $t[3], $t[2], $t[1], $t[0]); + } + + if( $next_value ) { + my $value = $next_value; + $value = logProxy_linearInterpolate( SVG_time_to_sec($prev_timestamp), $prev_value, SVG_time_to_sec($next_timestamp), $next_value, $to ) if( $interpolate ); + + my @t = localtime($to); + my $timestamp = sprintf("%04d-%02d-%02d_%02d:%02d:%02d", $t[5]+1900, $t[4]+1, $t[3], $t[2], $t[1], $t[0]); + $ret .= "$timestamp $value\n"; + } + + $ret .= $comment; + + return $ret; +} + +sub +logProxy_Get($@) +{ + my ($hash, $name, @a) = @_; +#Log 3, "logProxy_Get"; +#Log 3, Dumper @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; + + if($outf && $outf eq "INT") { + $outf = "-"; + $internal = 1; + } + + + my $ret = ""; + my %data; + for(my $i = 0; $i < int(@a); $i++) { + + my $j = $i+1; + $data{"min$j"} = undef; + $data{"max$j"} = undef; + $data{"avg$j"} = undef; + $data{"sum$j"} = 0; + $data{"cnt$j"} = undef; + $data{"currval$j"} = 0; + $data{"currdate$j"} = undef; + $data{"mindate$j"} = undef; + $data{"maxdate$j"} = undef; + + my @fld = split(":", $a[$i]); + + if( $a[$i] =~ m/^(FileLog|DbLog):([^:]*):(.*)/ ) { + my @options = split( ',', $fld[1] ); + my $log_dev = $options[0]; + my $infile = $fld[0] eq "DbLog" ? "HISTORY" : "CURRENT"; + my $column_specs = $3; + my $extend; + my $extend_scale; + my $offset; + my $offset_scale; + my $interpolate; + my $clip; + my $predict; + + if( !defined($defs{$log_dev}) ) { + Log3 $hash->{NAME}, 1, "$hash->{NAME}: $log_dev does not exist"; + $ret .= "#$a[$i]\n"; + next; + } + + foreach my $option ( @options[1..@options-1] ) { + my ($name,$value) = split( '=', $option, 2 ); + + if( $value ) { + $value = eval $value; + + if( $@ ) { + Log3 $hash->{NAME}, 1, "$hash->{NAME}: $option: $@"; + $ret .= "#$a[$i]\n"; + next; + } + } + + if( $name eq "extend" ) { + $value =~ m/(-?\d*)(m?)/; + + $extend = $1; + $extend_scale = $2; + $extend_scale = "" if( !$extend_scale ); + + $clip = 1; + + } elsif( $name eq "offset" ) { + $value =~ m/(-?\d*)(m?)/; + + $offset = $1; + $offset_scale = $2; + $offset_scale = "" if( !$offset_scale ); + + } elsif( $name eq "interpolate" ) { + $interpolate = 1; + + } elsif( $name eq "clip" ) { + $clip = 1; + + } elsif( $name eq "predict" ) { + $predict = 0; + $predict = $value if( defined($value) ); + + } else { + Log3 $hash->{NAME}, 2, "$hash->{NAME}: unknown option >$option<"; + + } + } + + my $fromsec = SVG_time_to_sec($from); + my $tosec = SVG_time_to_sec($to); + + my $from = $from; + my $to = $to; + # shift $from and $to + $from = logProxy_shiftTime($from,-$offset.$offset_scale) if( $offset ); + $to = logProxy_shiftTime($to,-$offset.$offset_scale) if( $offset ); + + # extend query range + $from = logProxy_shiftTime($from,-$extend.$extend_scale) if( $extend ); + $to = logProxy_shiftTime($to,$extend.$extend_scale) if( $extend ); + + $internal_data = ""; + my $cmd = "get $log_dev $infile INT $from $to $column_specs"; + Log3 $hash->{NAME}, 4, "$hash->{NAME}: calling $cmd"; + FW_fC($cmd, 1); + + # shift data and specials back + logProxy_shiftData($internal_data,$offset.$offset_scale) if( $offset ); + $main::data{"currdate1"} = logProxy_shiftTime($main::data{"currdate1"},$offset.$offset_scale) if( $offset ); + + # clip extended query range to plot range + if( $clip || defined($predict) ) { + $$internal_data = logProxy_clipData($internal_data,$fromsec,$tosec,$interpolate,$predict); + + } + + if( $$internal_data ) { + $ret .= $$internal_data; + + $data{"min$j"} = $main::data{"min1"}; + $data{"max$j"} = $main::data{"max1"}; + $data{"avg$j"} = $main::data{"avg1"}; + $data{"sum$j"} = $main::data{"sum1"}; + $data{"cnt$j"} = $main::data{"cnt1"}; + $data{"currval$j"} = $main::data{"currval1"}; + $data{"currdate$j"} = $main::data{"currdate1"}; + $data{"mindate$j"} = $main::data{"mindate1"}; + $data{"maxdate$j"} = $main::data{"maxdate1"}; + + } else { + $ret .= "#$column_specs\n"; + + } + + next; + } elsif( $fld[0] eq "ConstX" && $fld[1] ) { + my ($t,$y,$y2) = eval $fld[1]; + if( $@ ) { + Log3 $hash->{NAME}, 1, "$hash->{NAME}: $fld[1]: $@"; + $ret .= "#$a[$i]\n"; + next; + } + + if( !$t || !defined($y) || $y eq "undef" ) { + $ret .= "#$a[$i]\n"; + next; + } + + $t =~ s/ /_/; + + my $from = $t; + my $to = $t; + $y2 = $y if( !defined($y2) ); + + $data{"min$j"} = $y > $y2 ? $y2 : $y; + $data{"max$j"} = $y > $y2 ? $y : $y2; + $data{"avg$j"} = ($y+$y2)/2; + $data{"cnt$j"} = $y != $y2 ? 2 : 1; + $data{"curdval$j"} = $y2; + $data{"curddate$j"} = $to; + $data{"maxdate$j"} = $to; + $data{"mindate$j"} = $to; + + $ret .= "$from $y\n"; + $ret .= "$to $y2\n"; + $ret .= "#$a[$i]\n"; + next; + + } elsif( $fld[0] eq "ConstY" && defined($fld[1]) ) { + my ($y,$f,$t) = eval $fld[1]; + if( $@ ) { + Log3 $hash->{NAME}, 1, "$hash->{NAME}: $fld[1]: $@"; + $ret .= "#$a[$i]\n"; + next; + } + + if( !defined($y) || $y eq "undef" ) { + $ret .= "#$a[$i]\n"; + next; + } + + $f =~ s/ /_/ if( $f ); + $t =~ s/ /_/ if( $t ); + + my $from = $from; + $from = $f if( $f ); + my $to = $to; + $to = $t if( $t ); + + $data{"min$j"} = $y; + $data{"max$j"} = $y; + $data{"avg$j"} = $y; + $data{"cnt$j"} = 2; + $data{"currval$j"} = $y; + $data{"currdate$j"} = $to; + $data{"maxdate$j"} = $to; + $data{"mindate$j"} = $to; + + $ret .= "$from $y\n"; + $ret .= "$to $y\n"; + $ret .= "#$a[$i]\n"; + next; + + } elsif( $fld[0] eq "Func" && $fld[1] ) { + #my $fromsec = SVG_time_to_sec($from); + #my $tosec = SVG_time_to_sec($to); + my ($r,$min,$max,$last) = eval $fld[1]; + if( $@ ) { + Log3 $hash->{NAME}, 1, "$hash->{NAME}: $fld[1]: $@"; + next; + } + + $data{"min$j"} = $min; + $data{"max$j"} = $max; + $data{"currval$j"} = $last; + + $ret .= $r; + $ret .= "#$a[$i]\n"; + next; + } + } + + for(my $i = 0; $i < int(@a); $i++) { + my $j = $i+1; + $main::data{"min$j"} = $data{"min$j"}; + $main::data{"max$j"} = $data{"max$j"}; + $main::data{"avg$j"} = $data{"avg$j"}; + $main::data{"sum$j"} = $data{"sum$j"}; + $main::data{"cnt$j"} = $data{"cnt$j"}; + $main::data{"currval$j"} = $data{"currval$j"}; + $main::data{"currdate$j"} = $data{"currdate$j"}; + $main::data{"mindate$j"} = $data{"mindate$j"}; + $main::data{"maxdate$j"} = $data{"maxdate$j"}; + } + + $internal_data = \$ret; + +#Log 3, Dumper $internal_data; + + return undef; +} + +1; + +=pod +=begin html + + +

logProxy

+
    + Allows the manipulation of data to be plotted in an SVG device: +
      +
    • addition of horizontal lines at fixed values
    • +
    • addition of horizontal lines at dynamic values eg: min, max or average values of another plot
    • +
    • addition of vertical lines at fixed or dynamic times between two fixed or dynamic y values
    • +
    • addition of calculated data like week profiles of HeatingControll devices or heating thermostats
    • +
    • merge plot data from different sources. eg. different FileLog devices
    • +
    • horizontaly shifting a (merged) plot to align average or statistic data to the correct day,week and month
    • +
    +
    + + + Define +
      + define <name> logProxy
      +
      + Only one logProxy device can be defined and is needed.

      + + Example: +
        + define myProxy logProxy
        +
      +

    + + + Set +
      +

    + + + Get +
    + + + Attributes +
      +

    +
    + + #logProxy <column_spec>
    + where <column_spec> can be one or more of the following: +
      +
    • FileLog:<log device>[,<options>]:<column_spec>

    • +
    • DbLog:<log device>[,<options>]:<column_spec>

    • +
    • ConstX:<time>,<y>[,<y2>]
      + Will draw a vertical line (or point) at <time> between <y> to <y2>.
      + Everything after the : is evaluated as a perl expression that hast to return one time string and one or two y values.
      + Examples: +
        + #logProxy ConstX:$data{currdate1},$data{currval1}
        + #logProxy ConstX:$data{mindate1},$data{min1},$data{avg1}
        + #logProxy ConstX:$data{maxdate1},$data{max1},$data{avg1}
        + #logProxy ConstX:logProxy_shiftTime($from,60*60*2),$data{min1},$data{max1}
        +

    • +
    • ConstY:<value>[,<from>[,<to>]]
      + Will draw a horizontal line at <value>, optional only between the from and to times.
      + Everything after the : is evaluated as a perl expression that hast to return one value and optionaly one or two time strings.
      + Examples: +
        + #logProxy ConstY:0
        + #logProxy ConstY:1234+15+myFunc(123)
        + #logProxy ConstY:$data{avg1}
        + #logProxy ConstY:$data{avg2},$from,$to
        + #logProxy ConstY:$data{avg2},logProxy_shiftTime($from,60*60*12),logProxy_shiftTime($from,-60*60*12) +

    • +
    • Func:<perl expression>
      + Specifies a perl expression that returns the data to be plotted and its min, max and last value. It can not contain + space or : characters. The data has to be + one string of newline separated entries of the form: yyyy-mm-dd_hh:mm:ss value
      Example: +
        + #logProxy Func:logProxy_WeekProfile2Plot("HCB",$from,$to)
        + #logProxy Func:logProxy_WeekProfile2Plot("myHeatingControll",$from,$to,"(\\d)*\$")
        + #logProxy Func:logProxy_Func2Plot($from,$to,'{logProxy_hms2dec(sunrise_abs_dat($sec))}')
        + #logProxy Func:logProxy_Func2Plot($from,$to,'{logProxy_hms2dec(sunset_abs_dat($sec))}')
        +

      + Notes:
        +
      • logProxy_WeekProfile2Plot is a sample implementation of a function that will plot the week profile + of a Heating_Control, WeekdyTimer, HomeMatic or MAX Thermostat device can be found in the 98_logProxy.pm module file.
      • +
      • logProxy_Func2Plot($from,$to,$func) is a sample implementation of a function that will evaluate the given + function (3rd parameter) for a zoom factor dependent number of times. the current time is given in $sec. + the step width can be given in an optional 4th parameter. either as a number or as an hash with the keys from + the following list: hour,qday,day,week,month,year and the values representing the step with for the zoom level.
      • +
      • The perl expressions have access to $from and $to for the begining and end of the plot range and also to the + SVG specials min, max, avg, cnt, sum, currval (last value) and currdate (last date) values of the individual curves + already plotted are available as $data{<special-n>}.
        +
      • logProxy_Range2Zoom($seconds) can be used to get the approximate zoom step for a plot range of $seconds.
      • +
      • SVG_time_to_sec($timestamp) can be used to convert the timestamp strings to epoch times for calculation.
      • +
      +

    • + options is a comma separated list of zero or more of:
      +
        +
      • clip
        + clip the plot data to the plot window
      • +
      • extend=<value>
        + extend the query range to the log device by <value> seconds (or <value> months if <value> ends in m). + also activates cliping.
      • +
      • interpolate
        + perform a linear interpolation to the values in the extended range to get the values at the plot boundary. only usefull + if plotfunction is lines.
      • +
      • offset=<value>
        + shift plot by <value> seconds (or <value> months if <value> ends in m). + allows alignment of values calculated by average or statsitics module to the correct day, week or month.
      • +
      • predict[=<value>]
        + no value -> extend the last plot value to now.
        + value -> extend the last plot value by <value> but maximal to now.
      • +
      +
      +
    + Please see also the column_spec paragraphs of FileLog, DbLog and SVG.
    +
    + To use any of the logProxy features with an existing plot the associated SVG file hast to be changed to use the logProxy + device and the .gplot file has to be changed in the following way:
    + All existing #FileLog and #Dblog lines have to be changed to #logProxy lines and
    the column_spec of these line has to + be prepended by FileLog:<log device>: or DbLog:<log device>: respectively.
    + Examples: +
      + #DbLog <myDevice>:<myReading>
      + #FileLog 4:<SPEC1>:power\x3a::
      + #FileLog 4:<SPEC1>:consumption\x3a::

      + will become:

      + #logProxy DbLog:<myDb>:<myDevice>:<myReading>
      + #logProxy FileLog:FileLog_<SPEC1>:4:.power\x3a::
      + #logProxy FileLog:FileLog_<SPEC1>:4:.consumption\x3a::
      +
    + +
+ +=end html +=cut diff --git a/fhem/MAINTAINER.txt b/fhem/MAINTAINER.txt index 663652f0e..8595541f0 100644 --- a/fhem/MAINTAINER.txt +++ b/fhem/MAINTAINER.txt @@ -255,6 +255,7 @@ FHEM/98_dewpoint.pm Joachim http://forum.fhem.de Automatis FHEM/98_dummy.pm rudolfkoenig http://forum.fhem.de Automatisierung FHEM/98_fheminfo.pm mfr69bs http://forum.fhem.de Sonstiges FHEM/98_HourCounter.pm john http://forum.fhem.de MAX +FHEM/98_logProxy.pm justme1968 http://forum.fhem.de Frontends FHEM/98_notice.pm mfr69bs http://forum.fhem.de Sonstiges FHEM/98_pilight.pm andreas-fey http://forum.fhem.de Unterstuetzende Dienste FHEM/98_rain.pm baumrasen http://forum.fhem.de Sonstiges