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