From bc11f2ea14a5179b71a657b38b47202c3142f205 Mon Sep 17 00:00:00 2001
From: tpoitzsch <>
Date: Tue, 9 Dec 2014 18:37:31 +0000
Subject: [PATCH] statistics: copes now with double reading definitions and
allows two different instances to serve the same device
git-svn-id: https://svn.fhem.de/fhem/trunk@7174 2b470e98-0d58-463d-a4d8-8e2adae1ed80
---
fhem/FHEM/98_statistics.pm | 248 ++++++++++++++++++-------------------
1 file changed, 122 insertions(+), 126 deletions(-)
diff --git a/fhem/FHEM/98_statistics.pm b/fhem/FHEM/98_statistics.pm
index aa623adf2..866f83ea7 100644
--- a/fhem/FHEM/98_statistics.pm
+++ b/fhem/FHEM/98_statistics.pm
@@ -40,9 +40,9 @@ use Time::Local;
sub statistics_PeriodChange($);
sub statistics_DoStatisticsAll($$);
sub statistics_DoStatistics ($$$);
-sub statistics_doStatisticMinMax ($$$$$$);
-sub statistics_doStatisticMinMaxSingle ($$$$$$$);
-sub statistics_doStatisticTendency ($$$$);
+sub statistics_doStatisticMinMax ($$$$$);
+sub statistics_doStatisticMinMaxSingle ($$$$$$);
+sub statistics_doStatisticTendency ($$$);
sub statistics_doStatisticDelta ($$$$);
sub statistics_doStatisticDuration ($$$$);
sub statistics_doStatisticDurationSingle ($$$$$$);
@@ -56,34 +56,47 @@ sub statistics_UpdateDevReading($$$$);
my $MODUL = "statistics";
##############################################################
-# Syntax: deviceType, readingName, statisticType, decimalPlaces
+# Syntax: readingName => statisticType
# statisticType: 0=noStatistic | 1=minMaxAvg(daily) | 2=delta | 3=stateDuration | 4=tendency | 5=minMaxAvg(hourly)
##############################################################
- my @knownReadings = ( ["brightness", 1, 0]
- ,["count", 2]
- ,["current", 1, 3]
- ,["energy", 2]
- ,["energy_current", 1, 1]
- ,["energy_total", 2]
- ,["Total.Energy", 2]
- ,["humidity", 1, 0]
- ,["lightsensor", 3]
- ,["lock", 3]
- ,["motion", 3]
- ,["power", 1, 1]
- ,["pressure", 4, 1]
- ,["rain", 2]
- ,["rain_rate", 1, 1]
- ,["rain_total", 2]
- ,["temperature", 1, 1]
- ,["total", 2]
- ,["voltage", 1, 1]
- ,["wind", 5, 0]
- ,["wind_speed", 5, 1]
- ,["windSpeed", 5, 0]
- ,["Window", 3]
- ,["window", 3]
+ my %knownReadings = (
+ "brightness" => 1
+ ,"count" => 2
+ ,"current" => 1
+ ,"energy" => 2
+ ,"energy_current" => 1
+ ,"energy_total" => 2
+ ,"Total.Energy" => 2
+ ,"humidity" => 1
+ ,"lightsensor" => 3
+ ,"lock" => 3
+ ,"motion" => 3
+ ,"power" => 1
+ ,"pressure" => 4
+ ,"rain" => 2
+ ,"rain_rate" => 1
+ ,"rain_total" => 2
+ ,"temperature" => 1
+ ,"total" => 2
+ ,"voltage" => 1
+ ,"wind" => 5
+ ,"wind_speed" => 5
+ ,"windSpeed" => 5
+ ,"Window" => 3
+ ,"window" => 3
);
+
+##############################################################
+# Syntax: attributeName => statisticType
+# statisticType: 0=noStatistic | 1=minMaxAvg(daily) | 2=delta | 3=stateDuration | 4=tendency | 5=minMaxAvg(hourly)
+##############################################################
+ my %addedReadingsAttr = (
+ "deltaReadings" => 2
+ ,"durationReadings" => 3
+ ,"minAvgMaxReadings" => 5
+ ,"tendencyReadings" => 4
+ );
+
##############################################################
sub ##########################################
@@ -201,8 +214,8 @@ statistics_Set($$@)
return "Unknown argument $cmd, choose one of $list";
}
-sub ########################################
-statistics_Notify($$)
+########################################
+sub statistics_Notify($$)
{
my ($hash, $dev) = @_;
my $name = $hash->{NAME};
@@ -268,8 +281,8 @@ statistics_Notify($$)
}
-sub ########################################
-statistics_PeriodChange($)
+########################################
+sub statistics_PeriodChange($)
{
my ($hash) = @_;
my $name = $hash->{NAME};
@@ -370,20 +383,20 @@ sub statistics_DoStatistics($$$)
return if( AttrVal($hashName, "disable", 0 ) == 1 );
- my $readingName;
my $exclReadings = AttrVal($hashName, "excludedReadings", "");
my $regExp = '^'.$devName.'$|^'.$devName.',|,'.$devName.'$|,'.$devName.',';
- # Return if the notifying device is already served by another statistics instance
- if (exists ($dev->{helper}{_98_statistics})) {
- my $servedBy = $dev->{helper}{_98_statistics};
+ # Return if the notifying device is already served by another statistics instance with same prefix
+ my $instanceMarker = "_98_statistics_".$hash->{PREFIX};
+ if (exists ($dev->{helper}{$instanceMarker})) {
+ my $servedBy = $dev->{helper}{$instanceMarker};
if ($servedBy ne $hashName) {
my $monReadingValue = ReadingsVal($hashName,"monitoredDevicesUnserved","");
if ($monReadingValue !~ /$regExp/) {
if($monReadingValue eq "") { $monReadingValue = $devName;}
else {$monReadingValue .= ",".$devName;}
readingsSingleUpdate($hash,"monitoredDevicesUnserved",$monReadingValue,1);
- statistics_Log $hash, 3, "Device '$devName' identified as supported but already servered by '$servedBy'.";
+ statistics_Log $hash, 3, "Device '$devName' identified as supported but already servered by '$servedBy' with some reading prefix.";
}
return;
}
@@ -391,66 +404,36 @@ sub statistics_DoStatistics($$$)
$dev->{helper}{_98_statistics}=$hashName;
}
- readingsBeginUpdate($dev);
-
- # Loop through all known readings
- foreach my $f (@knownReadings)
+# Build up Statistic-Reading-Hash, add readings defined in attributes to Statistic-Reading-Hash
+ my %statReadings = %knownReadings;
+ while (my ($aName, $statType) = each (%addedReadingsAttr) )
{
- # notifing device has known reading, no statistic for excluded readings
- $readingName = $$f[0];
- my $completeReadingName = $devName.":".$readingName;
- next if ($completeReadingName =~ m/^($exclReadings)$/ );
- next if not exists ($dev->{READINGS}{$readingName});
- if ($$f[1] == 1) { statistics_doStatisticMinMax ($hash, $dev, $readingName, $$f[2], $periodSwitch, 0);}
- if ($$f[1] == 2) { statistics_doStatisticDelta ($hash, $dev, $readingName, $periodSwitch );}
- if ($$f[1] == 3) { statistics_doStatisticDuration ($hash, $dev, $readingName, $periodSwitch ); }
- if ($$f[1] == 4 && $periodSwitch>=1) { statistics_doStatisticTendency ($hash, $dev, $readingName, $$f[2]);}
- if ($$f[1] == 5) { statistics_doStatisticMinMax ($hash, $dev, $readingName, $$f[2], $periodSwitch, 1);}
- $statisticDone = 1;
- }
-
- my @specialReadings = split /,/, AttrVal($hashName, "deltaReadings", "");
- foreach $readingName (@specialReadings)
- {
- my $completeReadingName = $devName.":".$readingName;
- next if ($completeReadingName =~ m/^($exclReadings)$/ );
- next if not exists ($dev->{READINGS}{$readingName});
- statistics_doStatisticDelta ($hash, $dev, $readingName, $periodSwitch);
- $statisticDone = 1;
- }
-
- @specialReadings = split /,/, AttrVal($hashName, "durationReadings", "");
- foreach $readingName (@specialReadings)
- {
- my $completeReadingName = $devName.":".$readingName;
- next if ($completeReadingName =~ m/^($exclReadings)$/ );
- next if not exists ($dev->{READINGS}{$readingName});
- statistics_doStatisticDuration ($hash, $dev, $readingName, $periodSwitch);
- $statisticDone = 1;
- }
-
- @specialReadings = split /,/, AttrVal($hashName, "minAvgMaxReadings", "");
- foreach $readingName (@specialReadings)
- {
- my $completeReadingName = $devName.":".$readingName;
- next if ($completeReadingName =~ m/^($exclReadings)$/ );
- next if not exists ($dev->{READINGS}{$readingName});
- statistics_doStatisticMinMax ($hash, $dev, $readingName, 1, $periodSwitch, 1);
- $statisticDone = 1;
- }
-
- if ($periodSwitch>=1) {
- @specialReadings = split /,/, AttrVal($hashName, "tendencyReadings", "");
- foreach $readingName (@specialReadings)
+ my @addedReadings = split /,/, AttrVal($hashName, $aName, "");
+ foreach( keys @addedReadings )
{
- my $completeReadingName = $devName.":".$readingName;
- next if ($completeReadingName =~ m/^($exclReadings)$/ );
- next if not exists ($dev->{READINGS}{$readingName});
- statistics_doStatisticTendency ($hash, $dev, $readingName, 1);
- $statisticDone = 1;
+ $statReadings{$_} = $statType;
}
}
+ readingsBeginUpdate($dev);
+
+# Loop through Statistic-Reading-Hash and start statistic calculation if the readings exists in the notifying device
+ while ( my ($rName, $statType) = each (%statReadings) )
+ {
+ # notifing device has known reading, no statistic for excluded readings
+ my $completeReadingName = $devName.":".$rName;
+ next if ($completeReadingName =~ m/^($exclReadings)$/ );
+ next if not exists ($dev->{READINGS}{$rName});
+
+ if ($statType == 1) { statistics_doStatisticMinMax ($hash, $dev, $rName, $periodSwitch, 0);}
+ elsif ($statType == 2) { statistics_doStatisticDelta ($hash, $dev, $rName, $periodSwitch );}
+ elsif ($statType == 3) { statistics_doStatisticDuration ($hash, $dev, $rName, $periodSwitch ); }
+ elsif ($statType == 4 && $periodSwitch>=1) { statistics_doStatisticTendency ($hash, $dev, $rName);}
+ elsif ($statType == 5) { statistics_doStatisticMinMax ($hash, $dev, $rName, $periodSwitch, 1);}
+ $statisticDone = 1;
+ }
+
+# If no statistic-reading has been found, do a duration stat for the device-state
if ($statisticDone != 1)
{
if ( exists ($dev->{READINGS}{state}) && $dev->{READINGS}{state}{VAL} ne "defined" ) {
@@ -495,9 +478,9 @@ sub statistics_DoStatistics($$$)
# Calculates Min/Average/Max Values
sub ########################################
-statistics_doStatisticMinMax ($$$$$$)
+statistics_doStatisticMinMax ($$$$$)
{
- my ($hash, $dev, $readingName, $decPlaces, $periodSwitch, $doHourly) = @_;
+ my ($hash, $dev, $readingName, $periodSwitch, $doHourly) = @_;
my $name = $hash->{NAME};
my $devName = $dev->{NAME};
return if not exists ($dev->{READINGS}{$readingName});
@@ -507,25 +490,25 @@ statistics_doStatisticMinMax ($$$$$$)
$value =~ s/(-?[\d.]*).*/$1/e;
statistics_Log $hash, 4, "Calculating min/avg/max statistics for '".$dev->{NAME}.":$readingName = $value'";
- # statistics_doStatisticMinMaxSingle: $hash, $readingName, $value, $saveLast, decPlaces
+ # statistics_doStatisticMinMaxSingle: $hash, $readingName, $value, $saveLast
# Hourly statistic (if needed)
- if ($doHourly) { statistics_doStatisticMinMaxSingle $hash, $dev, $readingName, "Hour", $value, ($periodSwitch >= 1), $decPlaces; }
+ if ($doHourly) { statistics_doStatisticMinMaxSingle $hash, $dev, $readingName, "Hour", $value, ($periodSwitch >= 1); }
# Daily statistic
- statistics_doStatisticMinMaxSingle $hash, $dev, $readingName, "Day", $value, ($periodSwitch >= 2), $decPlaces;
+ statistics_doStatisticMinMaxSingle $hash, $dev, $readingName, "Day", $value, ($periodSwitch >= 2);
# Monthly statistic
- statistics_doStatisticMinMaxSingle $hash, $dev, $readingName, "Month", $value, ($periodSwitch >= 3), $decPlaces;
+ statistics_doStatisticMinMaxSingle $hash, $dev, $readingName, "Month", $value, ($periodSwitch >= 3);
# Yearly statistic
- statistics_doStatisticMinMaxSingle $hash, $dev, $readingName, "Year", $value, ($periodSwitch == 4), $decPlaces;
+ statistics_doStatisticMinMaxSingle $hash, $dev, $readingName, "Year", $value, ($periodSwitch == 4);
return ;
}
# Calculates single MaxMin Values and informs about end of day and month
-sub ########################################
-statistics_doStatisticMinMaxSingle ($$$$$$$)
+########################################
+sub statistics_doStatisticMinMaxSingle ($$$$$$)
{
- my ($hash, $dev, $readingName, $period, $value, $saveLast, $decPlaces) = @_;
+ my ($hash, $dev, $readingName, $period, $value, $saveLast) = @_;
my $result;
my $hiddenReadingName = ".".$dev->{NAME}.":".$readingName.$period;
my $name=$hash->{NAME};
@@ -554,6 +537,8 @@ statistics_doStatisticMinMaxSingle ($$$$$$$)
if ($hidden[3]>0) {$stat[3] = $hidden[1] / $hidden[3];} # Avg
if ($value > $stat[5]) { $stat[5]=$value; } # Max
}
+
+ my $decPlaces = statistics_maxDecPlaces($value, $hidden[11]);
# Prepare new current reading
$result = sprintf( "Min: %.".$decPlaces."f Avg: %.".$decPlaces."f Max: %.".$decPlaces."f", $stat[1], $stat[3], $stat[5]);
@@ -587,20 +572,22 @@ statistics_doStatisticMinMaxSingle ($$$$$$$)
}
# Store hidden reading
- $result = "Sum: $hidden[1] Time: $hidden[3] LastValue: ".$value." LastTime: ".int(gettimeofday())." ShowDate: $hidden[9]";
+ $result = "Sum: $hidden[1] Time: $hidden[3] LastValue: ".$value." LastTime: ".int(gettimeofday())." ShowDate: $hidden[9] DecPlaces: $decPlaces";
readingsSingleUpdate($hash, $hiddenReadingName, $result, 0);
statistics_Log $hash, 5, "Set '$hiddenReadingName'='$result'";
return;
}
+
# Calculates tendency values
-sub ########################################
-statistics_doStatisticTendency ($$$$)
+########################################
+sub statistics_doStatisticTendency ($$$)
{
- my ($hash, $dev, $readingName, $decPlaces) = @_;
+ my ($hash, $dev, $readingName) = @_;
my $name = $hash->{NAME};
my $devName = $dev->{NAME};
+ my $decPlaces = 0;
return if not exists ($dev->{READINGS}{$readingName});
# Get reading, cut out first number without units
@@ -626,6 +613,13 @@ statistics_doStatisticTendency ($$$$)
statistics_Log $hash, 4, "Add $value to $hiddenReadingName";
if (exists ($hash->{READINGS}{$hiddenReadingName}{VAL})) { $result .= " " . $hash->{READINGS}{$hiddenReadingName}{VAL}; }
@hidden = split / /, $result; # Internal values
+
+# determine decPlaces with stored values
+ foreach (@hidden)
+ {
+ $decPlaces = statistics_maxDecPlaces($_, $decPlaces);
+ }
+
if ( exists($hidden[7]) ) {
statistics_Log $hash, 4, "Remove last value ".$hidden[7]." from '$hiddenReadingName'";
delete $hidden[7];
@@ -657,8 +651,8 @@ statistics_doStatisticTendency ($$$$)
# Calculates deltas for day, month and year
-sub ########################################
-statistics_doStatisticDelta ($$$$)
+########################################
+sub statistics_doStatisticDelta ($$$$)
{
my ($hash, $dev, $readingName, $periodSwitch) = @_;
my $dummy;
@@ -686,7 +680,7 @@ statistics_doStatisticDelta ($$$$)
# Show since-Value and initialize all readings
$showDate = 8;
@stat = split / /, "Hour: 0 Day: 0 Month: 0 Year: 0";
- $stat[9] = strftime ("%Y-%m-%d_%H:%M:%S",localtime() );
+ $stat[9] = strftime "%Y-%m-%d_%H:%M:%S", localtime();
@last = split / /, "Hour: - Day: - Month: - Year: -";
statistics_Log $hash, 4, "Initializing statistic of '$hiddenReadingName'.";
} else {
@@ -792,8 +786,8 @@ statistics_doStatisticDelta ($$$$)
}
# Calculates deltas for period of several hours
-sub ########################################
-statistics_doStatisticSpecialPeriod ($$$$$)
+########################################
+sub statistics_doStatisticSpecialPeriod ($$$$$)
{
my ($hash, $dev, $readingName, $decPlaces, $value) = @_;
my $name = $hash->{NAME};
@@ -832,8 +826,8 @@ statistics_doStatisticSpecialPeriod ($$$$$)
}
# Calculates single Duration Values and informs about end of day and month
-sub ########################################
-statistics_doStatisticDuration ($$$$)
+########################################
+sub statistics_doStatisticDuration ($$$$)
{
my ($hash, $dev, $readingName, $periodSwitch) = @_;
my $name = $hash->{NAME};
@@ -854,8 +848,8 @@ statistics_doStatisticDuration ($$$$)
}
# Calculates single duration values
-sub ########################################
-statistics_doStatisticDurationSingle ($$$$$$)
+########################################
+sub statistics_doStatisticDurationSingle ($$$$$$)
{
my ($hash, $dev, $readingName, $period, $state, $saveLast) = @_;
my $result;
@@ -931,8 +925,8 @@ statistics_doStatisticDurationSingle ($$$$$$)
}
-sub ####################
-statistics_storeSingularReadings ($$$$$$$$$$)
+####################
+sub statistics_storeSingularReadings ($$$$$$$$$$)
{
my ($hashName,$singularReadings,$dev,$statReadingName,$readingName,$statType,$period,$statValue,$lastValue,$saveLast) = @_;
return if $singularReadings eq "";
@@ -951,8 +945,8 @@ statistics_storeSingularReadings ($$$$$$$$$$)
}
-sub ####################
-statistics_getStoredDevices ($)
+####################
+sub statistics_getStoredDevices ($)
{
my ($hash) = @_;
my $result="";
@@ -968,8 +962,8 @@ statistics_getStoredDevices ($)
return $result;
}
-sub ########################################
-statistics_FormatDuration($)
+########################################
+sub statistics_FormatDuration($)
{
my ($value) = @_;
#Tage
@@ -986,8 +980,8 @@ statistics_FormatDuration($)
return $returnstr;
}
-sub ########################################
-statistics_maxDecPlaces($$)
+########################################
+sub statistics_maxDecPlaces($$)
{
my ($value, $decMax) = @_;
$decMax = 0 if ! defined $decMax;
@@ -998,8 +992,8 @@ statistics_maxDecPlaces($$)
return $decMax;
}
-sub ########################################
-statistics_UpdateDevReading($$$$)
+########################################
+sub statistics_UpdateDevReading($$$$)
{
my ($dev, $rname, $val, $event) = @_;
$dev->{READINGS}{$rname}{VAL} = $val;
@@ -1047,6 +1041,7 @@ statistics_UpdateDevReading($$$$)
Further readings can be added via the correspondent attribute.
+ This allows also to assign a reading to another statistic type.
@@ -1168,7 +1163,8 @@ statistics_UpdateDevReading($$$$)
lightsensor, lock, motion, Window, window, state (wenn kein anderer Gerätewert gültig)
- Weitere Gerätewerte können über die entsprechenden Attribute hinzugefügt werden.
+ Über die entsprechenden Attribute können weitere Gerätewerte hinzugefügt oder
+ einem anderem Statistik-Typ zugeordnet werden.