2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-04-20 13:26:02 +00:00

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
This commit is contained in:
tpoitzsch 2014-12-09 18:37:31 +00:00
parent 896618c6ac
commit bc11f2ea14

View File

@ -40,9 +40,9 @@ use Time::Local;
sub statistics_PeriodChange($); sub statistics_PeriodChange($);
sub statistics_DoStatisticsAll($$); sub statistics_DoStatisticsAll($$);
sub statistics_DoStatistics ($$$); sub statistics_DoStatistics ($$$);
sub statistics_doStatisticMinMax ($$$$$$); sub statistics_doStatisticMinMax ($$$$$);
sub statistics_doStatisticMinMaxSingle ($$$$$$$); sub statistics_doStatisticMinMaxSingle ($$$$$$);
sub statistics_doStatisticTendency ($$$$); sub statistics_doStatisticTendency ($$$);
sub statistics_doStatisticDelta ($$$$); sub statistics_doStatisticDelta ($$$$);
sub statistics_doStatisticDuration ($$$$); sub statistics_doStatisticDuration ($$$$);
sub statistics_doStatisticDurationSingle ($$$$$$); sub statistics_doStatisticDurationSingle ($$$$$$);
@ -56,34 +56,47 @@ sub statistics_UpdateDevReading($$$$);
my $MODUL = "statistics"; 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) # statisticType: 0=noStatistic | 1=minMaxAvg(daily) | 2=delta | 3=stateDuration | 4=tendency | 5=minMaxAvg(hourly)
############################################################## ##############################################################
my @knownReadings = ( ["brightness", 1, 0] my %knownReadings = (
,["count", 2] "brightness" => 1
,["current", 1, 3] ,"count" => 2
,["energy", 2] ,"current" => 1
,["energy_current", 1, 1] ,"energy" => 2
,["energy_total", 2] ,"energy_current" => 1
,["Total.Energy", 2] ,"energy_total" => 2
,["humidity", 1, 0] ,"Total.Energy" => 2
,["lightsensor", 3] ,"humidity" => 1
,["lock", 3] ,"lightsensor" => 3
,["motion", 3] ,"lock" => 3
,["power", 1, 1] ,"motion" => 3
,["pressure", 4, 1] ,"power" => 1
,["rain", 2] ,"pressure" => 4
,["rain_rate", 1, 1] ,"rain" => 2
,["rain_total", 2] ,"rain_rate" => 1
,["temperature", 1, 1] ,"rain_total" => 2
,["total", 2] ,"temperature" => 1
,["voltage", 1, 1] ,"total" => 2
,["wind", 5, 0] ,"voltage" => 1
,["wind_speed", 5, 1] ,"wind" => 5
,["windSpeed", 5, 0] ,"wind_speed" => 5
,["Window", 3] ,"windSpeed" => 5
,["window", 3] ,"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 ########################################## sub ##########################################
@ -201,8 +214,8 @@ statistics_Set($$@)
return "Unknown argument $cmd, choose one of $list"; return "Unknown argument $cmd, choose one of $list";
} }
sub ######################################## ########################################
statistics_Notify($$) sub statistics_Notify($$)
{ {
my ($hash, $dev) = @_; my ($hash, $dev) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
@ -268,8 +281,8 @@ statistics_Notify($$)
} }
sub ######################################## ########################################
statistics_PeriodChange($) sub statistics_PeriodChange($)
{ {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
@ -370,20 +383,20 @@ sub statistics_DoStatistics($$$)
return if( AttrVal($hashName, "disable", 0 ) == 1 ); return if( AttrVal($hashName, "disable", 0 ) == 1 );
my $readingName;
my $exclReadings = AttrVal($hashName, "excludedReadings", ""); my $exclReadings = AttrVal($hashName, "excludedReadings", "");
my $regExp = '^'.$devName.'$|^'.$devName.',|,'.$devName.'$|,'.$devName.','; my $regExp = '^'.$devName.'$|^'.$devName.',|,'.$devName.'$|,'.$devName.',';
# Return if the notifying device is already served by another statistics instance # Return if the notifying device is already served by another statistics instance with same prefix
if (exists ($dev->{helper}{_98_statistics})) { my $instanceMarker = "_98_statistics_".$hash->{PREFIX};
my $servedBy = $dev->{helper}{_98_statistics}; if (exists ($dev->{helper}{$instanceMarker})) {
my $servedBy = $dev->{helper}{$instanceMarker};
if ($servedBy ne $hashName) { if ($servedBy ne $hashName) {
my $monReadingValue = ReadingsVal($hashName,"monitoredDevicesUnserved",""); my $monReadingValue = ReadingsVal($hashName,"monitoredDevicesUnserved","");
if ($monReadingValue !~ /$regExp/) { if ($monReadingValue !~ /$regExp/) {
if($monReadingValue eq "") { $monReadingValue = $devName;} if($monReadingValue eq "") { $monReadingValue = $devName;}
else {$monReadingValue .= ",".$devName;} else {$monReadingValue .= ",".$devName;}
readingsSingleUpdate($hash,"monitoredDevicesUnserved",$monReadingValue,1); 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; return;
} }
@ -391,66 +404,36 @@ sub statistics_DoStatistics($$$)
$dev->{helper}{_98_statistics}=$hashName; $dev->{helper}{_98_statistics}=$hashName;
} }
readingsBeginUpdate($dev); # Build up Statistic-Reading-Hash, add readings defined in attributes to Statistic-Reading-Hash
my %statReadings = %knownReadings;
# Loop through all known readings while (my ($aName, $statType) = each (%addedReadingsAttr) )
foreach my $f (@knownReadings)
{ {
# notifing device has known reading, no statistic for excluded readings my @addedReadings = split /,/, AttrVal($hashName, $aName, "");
$readingName = $$f[0]; foreach( keys @addedReadings )
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 $completeReadingName = $devName.":".$readingName; $statReadings{$_} = $statType;
next if ($completeReadingName =~ m/^($exclReadings)$/ );
next if not exists ($dev->{READINGS}{$readingName});
statistics_doStatisticTendency ($hash, $dev, $readingName, 1);
$statisticDone = 1;
} }
} }
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 ($statisticDone != 1)
{ {
if ( exists ($dev->{READINGS}{state}) && $dev->{READINGS}{state}{VAL} ne "defined" ) { if ( exists ($dev->{READINGS}{state}) && $dev->{READINGS}{state}{VAL} ne "defined" ) {
@ -495,9 +478,9 @@ sub statistics_DoStatistics($$$)
# Calculates Min/Average/Max Values # Calculates Min/Average/Max Values
sub ######################################## sub ########################################
statistics_doStatisticMinMax ($$$$$$) statistics_doStatisticMinMax ($$$$$)
{ {
my ($hash, $dev, $readingName, $decPlaces, $periodSwitch, $doHourly) = @_; my ($hash, $dev, $readingName, $periodSwitch, $doHourly) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
my $devName = $dev->{NAME}; my $devName = $dev->{NAME};
return if not exists ($dev->{READINGS}{$readingName}); return if not exists ($dev->{READINGS}{$readingName});
@ -507,25 +490,25 @@ statistics_doStatisticMinMax ($$$$$$)
$value =~ s/(-?[\d.]*).*/$1/e; $value =~ s/(-?[\d.]*).*/$1/e;
statistics_Log $hash, 4, "Calculating min/avg/max statistics for '".$dev->{NAME}.":$readingName = $value'"; 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) # 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 # Daily statistic
statistics_doStatisticMinMaxSingle $hash, $dev, $readingName, "Day", $value, ($periodSwitch >= 2), $decPlaces; statistics_doStatisticMinMaxSingle $hash, $dev, $readingName, "Day", $value, ($periodSwitch >= 2);
# Monthly statistic # Monthly statistic
statistics_doStatisticMinMaxSingle $hash, $dev, $readingName, "Month", $value, ($periodSwitch >= 3), $decPlaces; statistics_doStatisticMinMaxSingle $hash, $dev, $readingName, "Month", $value, ($periodSwitch >= 3);
# Yearly statistic # Yearly statistic
statistics_doStatisticMinMaxSingle $hash, $dev, $readingName, "Year", $value, ($periodSwitch == 4), $decPlaces; statistics_doStatisticMinMaxSingle $hash, $dev, $readingName, "Year", $value, ($periodSwitch == 4);
return ; return ;
} }
# Calculates single MaxMin Values and informs about end of day and month # 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 $result;
my $hiddenReadingName = ".".$dev->{NAME}.":".$readingName.$period; my $hiddenReadingName = ".".$dev->{NAME}.":".$readingName.$period;
my $name=$hash->{NAME}; my $name=$hash->{NAME};
@ -555,6 +538,8 @@ statistics_doStatisticMinMaxSingle ($$$$$$$)
if ($value > $stat[5]) { $stat[5]=$value; } # Max if ($value > $stat[5]) { $stat[5]=$value; } # Max
} }
my $decPlaces = statistics_maxDecPlaces($value, $hidden[11]);
# Prepare new current reading # Prepare new current reading
$result = sprintf( "Min: %.".$decPlaces."f Avg: %.".$decPlaces."f Max: %.".$decPlaces."f", $stat[1], $stat[3], $stat[5]); $result = sprintf( "Min: %.".$decPlaces."f Avg: %.".$decPlaces."f Max: %.".$decPlaces."f", $stat[1], $stat[3], $stat[5]);
if ($hidden[9] == 1) { $result .= " (since: $stat[7] )"; } if ($hidden[9] == 1) { $result .= " (since: $stat[7] )"; }
@ -587,20 +572,22 @@ statistics_doStatisticMinMaxSingle ($$$$$$$)
} }
# Store hidden reading # 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); readingsSingleUpdate($hash, $hiddenReadingName, $result, 0);
statistics_Log $hash, 5, "Set '$hiddenReadingName'='$result'"; statistics_Log $hash, 5, "Set '$hiddenReadingName'='$result'";
return; return;
} }
# Calculates tendency values # Calculates tendency values
sub ######################################## ########################################
statistics_doStatisticTendency ($$$$) sub statistics_doStatisticTendency ($$$)
{ {
my ($hash, $dev, $readingName, $decPlaces) = @_; my ($hash, $dev, $readingName) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
my $devName = $dev->{NAME}; my $devName = $dev->{NAME};
my $decPlaces = 0;
return if not exists ($dev->{READINGS}{$readingName}); return if not exists ($dev->{READINGS}{$readingName});
# Get reading, cut out first number without units # Get reading, cut out first number without units
@ -626,6 +613,13 @@ statistics_doStatisticTendency ($$$$)
statistics_Log $hash, 4, "Add $value to $hiddenReadingName"; statistics_Log $hash, 4, "Add $value to $hiddenReadingName";
if (exists ($hash->{READINGS}{$hiddenReadingName}{VAL})) { $result .= " " . $hash->{READINGS}{$hiddenReadingName}{VAL}; } if (exists ($hash->{READINGS}{$hiddenReadingName}{VAL})) { $result .= " " . $hash->{READINGS}{$hiddenReadingName}{VAL}; }
@hidden = split / /, $result; # Internal values @hidden = split / /, $result; # Internal values
# determine decPlaces with stored values
foreach (@hidden)
{
$decPlaces = statistics_maxDecPlaces($_, $decPlaces);
}
if ( exists($hidden[7]) ) { if ( exists($hidden[7]) ) {
statistics_Log $hash, 4, "Remove last value ".$hidden[7]." from '$hiddenReadingName'"; statistics_Log $hash, 4, "Remove last value ".$hidden[7]." from '$hiddenReadingName'";
delete $hidden[7]; delete $hidden[7];
@ -657,8 +651,8 @@ statistics_doStatisticTendency ($$$$)
# Calculates deltas for day, month and year # Calculates deltas for day, month and year
sub ######################################## ########################################
statistics_doStatisticDelta ($$$$) sub statistics_doStatisticDelta ($$$$)
{ {
my ($hash, $dev, $readingName, $periodSwitch) = @_; my ($hash, $dev, $readingName, $periodSwitch) = @_;
my $dummy; my $dummy;
@ -686,7 +680,7 @@ statistics_doStatisticDelta ($$$$)
# Show since-Value and initialize all readings # Show since-Value and initialize all readings
$showDate = 8; $showDate = 8;
@stat = split / /, "Hour: 0 Day: 0 Month: 0 Year: 0"; @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: -"; @last = split / /, "Hour: - Day: - Month: - Year: -";
statistics_Log $hash, 4, "Initializing statistic of '$hiddenReadingName'."; statistics_Log $hash, 4, "Initializing statistic of '$hiddenReadingName'.";
} else { } else {
@ -792,8 +786,8 @@ statistics_doStatisticDelta ($$$$)
} }
# Calculates deltas for period of several hours # Calculates deltas for period of several hours
sub ######################################## ########################################
statistics_doStatisticSpecialPeriod ($$$$$) sub statistics_doStatisticSpecialPeriod ($$$$$)
{ {
my ($hash, $dev, $readingName, $decPlaces, $value) = @_; my ($hash, $dev, $readingName, $decPlaces, $value) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
@ -832,8 +826,8 @@ statistics_doStatisticSpecialPeriod ($$$$$)
} }
# Calculates single Duration Values and informs about end of day and month # Calculates single Duration Values and informs about end of day and month
sub ######################################## ########################################
statistics_doStatisticDuration ($$$$) sub statistics_doStatisticDuration ($$$$)
{ {
my ($hash, $dev, $readingName, $periodSwitch) = @_; my ($hash, $dev, $readingName, $periodSwitch) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
@ -854,8 +848,8 @@ statistics_doStatisticDuration ($$$$)
} }
# Calculates single duration values # Calculates single duration values
sub ######################################## ########################################
statistics_doStatisticDurationSingle ($$$$$$) sub statistics_doStatisticDurationSingle ($$$$$$)
{ {
my ($hash, $dev, $readingName, $period, $state, $saveLast) = @_; my ($hash, $dev, $readingName, $period, $state, $saveLast) = @_;
my $result; 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) = @_; my ($hashName,$singularReadings,$dev,$statReadingName,$readingName,$statType,$period,$statValue,$lastValue,$saveLast) = @_;
return if $singularReadings eq ""; return if $singularReadings eq "";
@ -951,8 +945,8 @@ statistics_storeSingularReadings ($$$$$$$$$$)
} }
sub #################### ####################
statistics_getStoredDevices ($) sub statistics_getStoredDevices ($)
{ {
my ($hash) = @_; my ($hash) = @_;
my $result=""; my $result="";
@ -968,8 +962,8 @@ statistics_getStoredDevices ($)
return $result; return $result;
} }
sub ######################################## ########################################
statistics_FormatDuration($) sub statistics_FormatDuration($)
{ {
my ($value) = @_; my ($value) = @_;
#Tage #Tage
@ -986,8 +980,8 @@ statistics_FormatDuration($)
return $returnstr; return $returnstr;
} }
sub ######################################## ########################################
statistics_maxDecPlaces($$) sub statistics_maxDecPlaces($$)
{ {
my ($value, $decMax) = @_; my ($value, $decMax) = @_;
$decMax = 0 if ! defined $decMax; $decMax = 0 if ! defined $decMax;
@ -998,8 +992,8 @@ statistics_maxDecPlaces($$)
return $decMax; return $decMax;
} }
sub ######################################## ########################################
statistics_UpdateDevReading($$$$) sub statistics_UpdateDevReading($$$$)
{ {
my ($dev, $rname, $val, $event) = @_; my ($dev, $rname, $val, $event) = @_;
$dev->{READINGS}{$rname}{VAL} = $val; $dev->{READINGS}{$rname}{VAL} = $val;
@ -1047,6 +1041,7 @@ statistics_UpdateDevReading($$$$)
</li><br> </li><br>
</ul> </ul>
Further readings can be added via the correspondent <a href="#statisticsattr">attribute</a>. Further readings can be added via the correspondent <a href="#statisticsattr">attribute</a>.
This allows also to assign a reading to another statistic type.
<br>&nbsp; <br>&nbsp;
<br> <br>
@ -1168,7 +1163,8 @@ statistics_UpdateDevReading($$$$)
<br> <br>
<i>lightsensor, lock, motion, Window, window, state (wenn kein anderer Ger&auml;tewert g&uuml;ltig)</i></li> <i>lightsensor, lock, motion, Window, window, state (wenn kein anderer Ger&auml;tewert g&uuml;ltig)</i></li>
</ul> </ul>
Weitere Ger&auml;tewerte k&ouml;nnen &uuml;ber die entsprechenden <a href="#statisticsattr">Attribute</a> hinzugef&uuml;gt werden. &Uuml;ber die entsprechenden <a href="#statisticsattr">Attribute</a> k&ouml;nnen weitere Ger&auml;tewerte hinzugef&uuml;gt oder
einem anderem Statistik-Typ zugeordnet werden.
<br>&nbsp; <br>&nbsp;
<br> <br>