diff --git a/fhem/FHEM/23_LUXTRONIK2.pm b/fhem/FHEM/23_LUXTRONIK2.pm index b852824d3..70cdd92b9 100644 --- a/fhem/FHEM/23_LUXTRONIK2.pm +++ b/fhem/FHEM/23_LUXTRONIK2.pm @@ -39,7 +39,7 @@ use IO::Socket; use Time::HiRes qw/ time /; use Net::Telnet; -sub LUXTRONIK2_doStatisticThermalPower ($$$$$$$$); +sub LUXTRONIK2_doStatisticThermalPower ($$$$$$$$$); sub LUXTRONIK2_doStatisticMinMax ($$$); sub LUXTRONIK2_doStatisticMinMaxSingle ($$$$); sub LUXTRONIK2_storeReadings ($$$$$$); @@ -47,7 +47,7 @@ sub LUXTRONIK2_doStatisticDelta ($$$$$) ; sub LUXTRONIK2_doStatisticDeltaSingle ($$$$$$); # Modul Version for remote debugging - my $modulVersion = "2014-04-30"; + my $modulVersion = "2014-05-03"; #List of firmware versions that are known to be compatible with this modul my $testedFirmware = "#V1.51#V1.54C#V1.60#V1.69#V1.70#"; @@ -67,8 +67,8 @@ LUXTRONIK2_Initialize($) "allowSetParameter:0,1 ". "autoSynchClock:slider,10,5,300 ". "boilerVolumn ". + "heatPumpElectricalPowerFactor ". "heatPumpElectricalPowerWatt:slider,1000,16000,100 ". - "heatPumpHotWaterElectricalPowerWatt:slider,1000,16000,100 ". "heatRodElectricalPowerWatt:slider,1000,16000,100 ". "compressor2ElectricalPowerWatt:slider,1000,16000,100 ". "doStatistics:0,1 ". @@ -748,6 +748,24 @@ LUXTRONIK2_UpdateDone($) my $returnTemperature = LUXTRONIK2_CalcTemp($a[16]); my $returnTemperatureTarget = LUXTRONIK2_CalcTemp($a[17]); + my $heatPumpPower = 0; + my $heatRodPower = AttrVal($name, "heatRodElectricalPowerWatt", 0); + + #WM[kW] = delta_Temp [K] * Durchfluss [l/h] / ( 3.600 [kJ/kWh] / ( 4,179 [kJ/(kg*K)] (H2O Wärmekapazität bei 30 & 40°C) * 0,994 [kg/l] (H2O Dichte bei 35°C) ) + my $thermalPower = 0; + # 0=Heizen, 5=Brauchwasser, 7=Abtauen, 16=Durchflussüberwachung + if ($a[3] =~ /^(0|5|16)$/ ) { + $thermalPower = abs($flowTemperature - $returnTemperature) * $a[19] / 866.65; + $heatPumpPower = AttrVal($name, "heatPumpElectricalPowerWatt", -1); + $heatPumpPower *= 1 + ($flowTemperature-35) * AttrVal($name, "heatPumpElectricalPowerFactor", 0); + } + readingsBulkUpdate( $hash, "thermalPower", sprintf "%.1f", $thermalPower); + if ($heatPumpPower >-1 ) { readingsBulkUpdate( $hash, "heatPumpElectricalPowerEstimated", sprintf "%.0f", $heatPumpPower); } + if ($heatPumpPower > 0) { + $cop = $thermalPower * 1000 / $heatPumpPower; + readingsBulkUpdate( $hash, "COP", sprintf "%.2f", $cop); + } + # if selected, do all the statistic calculations if ( $doStatistic == 1) { #LUXTRONIK2_doStatisticBoilerHeatUp $hash, $currOpHours, $currHQ, $currTemp, $opState, $target @@ -776,9 +794,9 @@ LUXTRONIK2_UpdateDone($) } # LUXTRONIK2_doStatisticThermalPower: $hash, $MonitoredOpState, $currOpState, $currHeatQuantity, $currOpHours, $currAmbTemp, $currHeatSourceIn, $TargetTemp - $value = LUXTRONIK2_doStatisticThermalPower ($hash, 5, $a[3], $a[37]/10, $a[35], $ambientTemperature, $heatSourceIN,$hotWaterTemperatureTarget); + $value = LUXTRONIK2_doStatisticThermalPower ($hash, 5, $a[3], $a[37]/10, $a[35], $ambientTemperature, $heatSourceIN,$hotWaterTemperatureTarget, $heatPumpPower); if ($value ne "") { readingsBulkUpdate($hash,"statThermalPowerBoiler",$value); } - $value = LUXTRONIK2_doStatisticThermalPower ($hash, 0, $a[3], $a[36]/10, $a[34], $ambientTemperature, $heatSourceIN, $returnTemperatureTarget); + $value = LUXTRONIK2_doStatisticThermalPower ($hash, 0, $a[3], $a[36]/10, $a[34], $ambientTemperature, $heatSourceIN, $returnTemperatureTarget, $heatPumpPower); if ($value ne "") { readingsBulkUpdate($hash,"statThermalPowerHeating",$value); } # LUXTRONIK2_doStatisticMinMax $hash, $readingName, $value @@ -886,15 +904,15 @@ LUXTRONIK2_UpdateDone($) readingsBulkUpdate( $hash, "heatSourceIN",$heatSourceIN); readingsBulkUpdate( $hash, "heatSourceOUT",LUXTRONIK2_CalcTemp($a[24])); readingsBulkUpdate( $hash, "hotGasTemperature",LUXTRONIK2_CalcTemp($a[26])); - + # Operating hours (seconds->hours) and heat quantities - # LUXTRONIK2_storeReadings: $hash, $readingName, $value, $factor, $doStatistic, $tariffType - LUXTRONIK2_storeReadings $hash, "counterHours2ndHeatSource1", $a[32], 3600, $doStatistic, 4; - LUXTRONIK2_storeReadings $hash, "counterHours2ndHeatSource2", $a[38], 3600, $doStatistic, 4; - LUXTRONIK2_storeReadings $hash, "counterHours2ndHeatSource3", $a[39], 3600, $doStatistic, 4; - LUXTRONIK2_storeReadings $hash, "counterHoursHeatPump", $a[33], 3600, $doStatistic, 1; - LUXTRONIK2_storeReadings $hash, "counterHoursHeating", $a[34], 3600, $doStatistic, 2; - LUXTRONIK2_storeReadings $hash, "counterHoursHotWater", $a[35], 3600, $doStatistic, 3; + # LUXTRONIK2_storeReadings: $hash, $readingName, $value, $factor, $doStatistic, $electricalPower + LUXTRONIK2_storeReadings $hash, "counterHours2ndHeatSource1", $a[32], 3600, $doStatistic, $heatRodPower; + LUXTRONIK2_storeReadings $hash, "counterHours2ndHeatSource2", $a[38], 3600, $doStatistic, $heatRodPower; + LUXTRONIK2_storeReadings $hash, "counterHours2ndHeatSource3", $a[39], 3600, $doStatistic, $heatRodPower; + LUXTRONIK2_storeReadings $hash, "counterHoursHeatPump", $a[33], 3600, $doStatistic, $heatPumpPower; + LUXTRONIK2_storeReadings $hash, "counterHoursHeating", $a[34], 3600, $doStatistic, $heatPumpPower; + LUXTRONIK2_storeReadings $hash, "counterHoursHotWater", $a[35], 3600, $doStatistic, $heatPumpPower; LUXTRONIK2_storeReadings $hash, "counterHeatQHeating", $a[36], 10, ($a[19] !~ /no/ ? $doStatistic : 0), 0; LUXTRONIK2_storeReadings $hash, "counterHeatQHotWater", $a[37], 10, ($a[19] !~ /no/ ? $doStatistic : 0), 0; LUXTRONIK2_storeReadings $hash, "counterHeatQTotal", $a[36] + $a[37], 10, ($a[19] !~ /no/ ? $doStatistic : 0), 0; @@ -923,24 +941,7 @@ LUXTRONIK2_UpdateDone($) $value = "unbekannt (".$a[31].")" unless $value; readingsBulkUpdate($hash,"typeHeatpump",$value); - # Monitored operation: 5=Hot Water, 0=Heating - my $devicePower=0; - if ($a[3] == 5) {$devicePower = AttrVal($hash->{NAME}, "heatPumpHotWaterElectricalPowerWatt", 0);} - if ($devicePower == 0) {$devicePower = AttrVal($hash->{NAME}, "heatPumpElectricalPowerWatt", 0);} - - #WM[kW] = delta_Temp [K] * Durchfluss [l/h] / ( 3.600 [kJ/kWh] / ( 4,179 [kJ/(kg*K)] (H2O Wärmekapazität bei 30 & 40°C) * 0,994 [kg/l] (H2O Dichte bei 35°C) ) - my $thermalPower = 0; - # 0=Heizen, 5=Brauchwasser, 7=Abtauen, 16=Durchflussüberwachung - if ($a[3] =~ /^(0|5|16)$/ ) { - $thermalPower = abs($flowTemperature - $returnTemperature) * $a[19] / 866.65; - } - readingsBulkUpdate( $hash, "thermalPower", sprintf "%.1f", $thermalPower); - if ($devicePower > 0) { - $cop = $thermalPower *1000 / $devicePower; - readingsBulkUpdate( $hash, "COP", sprintf "%.2f", $cop); - } - - # Solar + # Solar if ($a[50] !~ /no/) {readingsBulkUpdate($hash, "solarCollectorTemperature", LUXTRONIK2_CalcTemp($a[50]));} if ($a[51] !~ /no/) {readingsBulkUpdate($hash, "solarBufferTemperature", LUXTRONIK2_CalcTemp($a[51]));} if ($a[52] !~ /no/) {readingsBulkUpdate($hash, "counterHoursSolar", sprintf("%.1f", $a[52]/3600));} @@ -958,7 +959,7 @@ LUXTRONIK2_UpdateDone($) $value = "$opStateHeatPump1 $opStateHeatPump2 - $opStateHeatPump3"; if ($thermalPower != 0) { $value .= " (".sprintf ("%.1f", $thermalPower)." kW"; - if ($devicePower>0) {$value .= ", COP: ".sprintf ("%.2f", $cop);} + if ($heatPumpPower>0) {$value .= ", COP: ".sprintf ("%.2f", $cop);} $value .= ")"; } readingsBulkUpdate($hash, "state", $value); @@ -1201,32 +1202,40 @@ LUXTRONIK2_checkFirmware ($) # Calculate heat-up gradients of boiler based on hotWaterTemperature and counterHeatQHeating sub ######################################## -LUXTRONIK2_doStatisticThermalPower ($$$$$$$$) +LUXTRONIK2_doStatisticThermalPower ($$$$$$$$$) { - my ($hash, $MonitoredOpState, $currOpState, $currHeatQuantity, $currOpHours, $currAmbTemp, $currHeatSourceIn, $targetTemp) = @_; + my ($hash, $MonitoredOpState, $currOpState, $currHeatQuantity, $currOpHours, $currAmbTemp, $currHeatSourceIn, $targetTemp, $electricalPower) = @_; my @last = split / /, $hash->{fhem}{"statThermalPowerOpState_".$MonitoredOpState} || "1"; - my $saveCurrent = 0; my $returnStr = ""; my $value1; my $value2; my $value3; - $last[3] += $currAmbTemp; - $last[4] += $currHeatSourceIn; - $last[5]++; - - # Monitored operation: 5=Hot Water, 0=Heating - my $devicePower = 0; - if ($MonitoredOpState == 5) {$devicePower = AttrVal($hash->{NAME}, "heatPumpHotWaterElectricalPowerWatt", 0);} - if ($devicePower == 0) {$devicePower = AttrVal($hash->{NAME}, "heatPumpElectricalPowerWatt", 0);} + my $save = 0; - if ($last[0] != $MonitoredOpState && $currOpState == $MonitoredOpState ) { + if ( $last[0] != $MonitoredOpState && $currOpState == $MonitoredOpState ) { # Save start values at the beginning of the monitored operation (5=Hot Water, 0=Heating) - $saveCurrent = 1; + $save = 1; + $last[0] = $currOpState; + $last[1] = $currHeatQuantity; + $last[2] = $currOpHours; + $last[3] = $currAmbTemp; + $last[4] = $currHeatSourceIn; + $last[5] = 1; $last[6] = $targetTemp; + $last[7] = $electricalPower; + } elsif ($last[0] == $MonitoredOpState && ($currOpState == $MonitoredOpState || $currOpState == 16) ) { #16=Durchflussüberwachung + # Store intermediate values as long as the correct opMode runs + $save = 1; + $last[3] += $currAmbTemp; + $last[4] += $currHeatSourceIn; + $last[5]++; + $last[7] += $electricalPower; + } elsif ($last[0] == $MonitoredOpState && $currOpState != $MonitoredOpState && $currOpState != 16 ) { #16=Durchflussüberwachung - # Do statistics at the end of the monitored operation - $saveCurrent = 1; + # Do statistics at the end of the monitored operation if it run at least 9.5 minutes + $save = 1; + $last[0] = $currOpState; $value2 = ($currOpHours - $last[2])/60; if ($value2 > 9.5) { $value1 = $last[3] / $last[5]; @@ -1239,19 +1248,16 @@ LUXTRONIK2_doStatisticThermalPower ($$$$$$$$) $returnStr .= " thP: " . sprintf "%.1f", $value3; $returnStr .= " DQ: " . sprintf "%.1f", $value1; $returnStr .= " t: " . sprintf "%.0f", $value2; - if ($devicePower>0) { - $value1 = $value3 *1000 / $devicePower; + if ($last[7]>0) { + $value1 = $value3 *1000 / $last[7] * $last[5];; $returnStr .= " COP: " . sprintf "%.2f", $value1; } if ($last[6] > $targetTemp) { $returnStr .= " tTStart: " . sprintf "%.1f", $last[6]; } } } - if ($saveCurrent == 1) { - $last[0] = $currOpState; - $last[1] = $currHeatQuantity; - $last[2] = $currOpHours; - $hash->{fhem}{"statThermalPowerOpState_".$MonitoredOpState} = join( " ", @last); - } + + if ($save == 1) { $hash->{fhem}{"statThermalPowerOpState_".$MonitoredOpState} = join( " ", @last);} + return $returnStr; } @@ -1534,7 +1540,7 @@ LUXTRONIK2_doStatisticMinMaxSingle ($$$$) sub ######################################## LUXTRONIK2_storeReadings($$$$$$) { - my ($hash, $readingName, $value, $factor, $doStatistics, $tariffType) = @_; + my ($hash, $readingName, $value, $factor, $doStatistics, $electricalPower) = @_; if ($value eq "no" || $value == 0 ) { return; } @@ -1542,15 +1548,15 @@ LUXTRONIK2_storeReadings($$$$$$) $readingName =~ s/counter//; - # LUXTRONIK2_doStatisticDelta: $hash, $readingName, $value, $factor, $tariffType - if ( $doStatistics == 1) { LUXTRONIK2_doStatisticDelta $hash, "stat".$readingName, $value, $factor, $tariffType; } + # LUXTRONIK2_doStatisticDelta: $hash, $readingName, $value, $factor, $electricalPower + if ( $doStatistics == 1) { LUXTRONIK2_doStatisticDelta $hash, "stat".$readingName, $value, $factor, $electricalPower; } } # Calculates deltas for day, month and year sub ######################################## LUXTRONIK2_doStatisticDelta ($$$$$) { - my ($hash, $readingName, $value, $factor, $tariffType) = @_; + my ($hash, $readingName, $value, $factor, $electricalPower) = @_; my $name = $hash->{NAME}; my $dummy; my $result; @@ -1599,19 +1605,13 @@ LUXTRONIK2_doStatisticDelta ($$$$$) my $activeTariff = ReadingsVal($name,"activeTariff",0); - if ( $tariffType != 0 ) { + if ( $electricalPower != 0 ) { my $readingNamePower = $readingName; $readingNamePower =~ s/Hours/Electricity/ ; - my $powerValue; - if ( $tariffType == 1 || $tariffType == 2 ) { $powerValue = AttrVal($name,"heatPumpElectricalPowerWatt",0); - } elsif ( $tariffType == 3 ) { - $powerValue = AttrVal($name,"heatPumpHotWaterElectricalPowerWatt",0); - if ($powerValue == 0) { $powerValue = AttrVal($name,"heatPumpElectricalPowerWatt",0); } - } elsif ( $tariffType == 4 ) { $powerValue = AttrVal($name,"heatRodElectricalPowerWatt",0); } - if ($powerValue > 0) { + if ($electricalPower > 0) { foreach (1,2,3,4,5,6,7,8,9) { if ( $previousTariff == $_ ) { - LUXTRONIK2_doStatisticDeltaSingle ($hash, $readingNamePower."Tariff".$_, $deltaValue * $powerValue, $factor, $periodSwitch, $showDate); + LUXTRONIK2_doStatisticDeltaSingle ($hash, $readingNamePower."Tariff".$_, $deltaValue * $electricalPower, $factor, $periodSwitch, $showDate); } elsif ($activeTariff == $_ || ($periodSwitch > 0 && exists($hash->{READINGS}{$readingNamePower . "Tariff".$_}))) { LUXTRONIK2_doStatisticDeltaSingle ($hash, $readingNamePower."Tariff".$_, 0, $factor, $periodSwitch, $showDate); } @@ -1638,7 +1638,8 @@ LUXTRONIK2_doStatisticDeltaSingle ($$$$$$) @curr = split / /, $hash->{READINGS}{".".$readingName}{VAL} || ""; } else { $curr[1] = 0; $curr[3] = 0; $curr[5] = 0; - $curr[7] = strftime "%Y-%m-%d_%H:%M:%S", localtime(); # start + if ($showDate>5) {$curr[7] = strftime "%Y-%m-%d_%H:%M:%S", localtime();} # start + else {$curr[7] = strftime "%Y-%m-%d", localtime();} # start } # get statistic values of previous period @@ -1799,10 +1800,10 @@ LUXTRONIK2_doStatisticDeltaSingle ($$$$$$) Logging and visualisation of the statistic should be done with readings of type 'statReadingNameLast'.
  • heatPumpElectricalPowerWatt
    - Electrical power of the heat pump to calculated coefficency factor and estimate electrical consumption + Electrical power of the heat pump by a flow temperature of 35°C to calculated coefficency factor and estimate electrical consumption

  • -
  • heatPumpHotWaterElectricalPowerWatt
    - Electrical power of the heat pump during hot water preparation to calculated coefficency factor and estimate electrical consumption +
  • heatPumpElectricalPowerFactor
    + Change of electrical power consumption per 1 K flow temperature differenz to 35°C (e.g. 2% per 1 K = 0,02)

  • heatHeatRodElectricalPowerWatt
    Electrical power of the heat rods (2nd heat source) to estimate electrical consumption @@ -1927,13 +1928,12 @@ LUXTRONIK2_doStatisticDeltaSingle ($$$$$$) Für grafische Auswertungen können die Werte der Form 'statReadingNameLast' genutzt werden.

  • heatPumpElectricalPowerWatt
    - Betriebsleistung der Wäremepumpe zur Berechung der Arbeitszahl (erzeugte Wärme pro elektrische Energieeinheit) - und Abschätzung des elektrischen Verbrauches -

  • -
  • heatPumpHotWaterElectricalPowerWatt
    - Betriebsleistung der Wäremepumpe während der Warmwasserbereitung zur Berechung der Arbeitszahl (erzeugte Wärme pro elektrische Energieeinheit) + Betriebsleistung der Wäremepumpe bei einer Vorlauftemperatur von 35 °C zur Berechung der Arbeitszahl (erzeugte Wärme pro elektrische Energieeinheit) und Abschätzung des elektrischen Verbrauches

  • +
  • heatPumpElectricalPowerFactor
    + Änderung der elektrischen Leistungsaufnahme per 1 K Vorlauftemperaturdifferenz zu 35 °C (z.B. 2% pro 1 K = 0,02)
    +

  • heatHeatRodElectricalPowerWatt
    Betriebsleistung der Heizstäbe zur Abschätzung des elektrischen Verbrauches

  • diff --git a/fhem/FHEM/98_statistics.pm b/fhem/FHEM/98_statistics.pm new file mode 100644 index 000000000..0438ce865 --- /dev/null +++ b/fhem/FHEM/98_statistics.pm @@ -0,0 +1,503 @@ +############################################## +# +# 98_statistic.pm +# +# Copyright notice +# +# (c) 2014 Torsten Poitzsch < torsten . poitzsch at gmx . de > +# inspired by 98_rain.pm of Andreas Vogt +# +# This module computes statistic data of and for readings of other modules +# +# This script 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. +# +# The GNU General Public License can be found at +# http://www.gnu.org/copyleft/gpl.html. +# A copy is found in the text file GPL.txt and important notices to the license +# from the author is found in LICENSE.txt distributed with these scripts. +# +# This script 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. +# +# This copyright notice MUST APPEAR in all copies of the script! +# +############################################################################## +# +# define statistics +# +############################################################################## + +package main; +use strict; +use warnings; +use Time::Local; + +sub statistics_doStatisticMinMax ($$$); + +# Modul Version for remote debugging + my $modulVersion = "2014-04-29"; + +############################################################## +# Syntax: deviceType, readingName, statisticType +# statisticType: 0=noStatistic | 1=maxMinAvgStatistic | 2=integralTimeStatistic | 3=onOffTimeCount +############################################################## + my @knownDeviceReadings = ( + ["CUL_WS", "humidity", 1] + ,["CUL_WS", "temperature", 1] + ,["KS300", "humidity", 1] + ,["KS300", "temperature", 1] + ,["KS300", "wind", 1] + ,["KS300", "rain", 2] + ,["FBDECT", "energy", 2] + ,["FBDECT", "power", 1] + ,["FBDECT", "voltage", 1] + ); +############################################################## + +sub ########################################## +statistics_Initialize($) +{ + my ($hash) = @_; + + $hash->{DefFn} = "statistics_Define"; + $hash->{NotifyFn} = "statistics_Notify"; + + $hash->{NotifyOrderPrefix} = "10-"; # Want to be called before the rest + $hash->{AttrList} = "disable:0,1 " + ."DayChangeTime " + ."CorrectionValue " + .$readingFnAttributes; +} + +########################## +sub +statistics_Define($$) +{ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + return "Usage: define statistics [prefix]" + if(3>@a || @a>4); + + my $name = $a[0]; + my $devname = $a[2]; + + if (@a == 4) {$hash->{PREFIX} = $a[3];} + else {$hash->{PREFIX} = "stat";} + + eval { "Hallo" =~ m/^$devname$/ }; + return "Bad regexp: $@" if($@); + $hash->{DEV_REGEXP} = $devname; + + $hash->{STATE} = "active"; + return undef; +} + + +########################## +sub +statistics_Notify($$) +{ + my ($hash, $dev) = @_; + my $hashName = $hash->{NAME}; + my $devName = $dev->{NAME}; + my $devType = $dev->{TYPE}; + + return "" if(AttrVal($hashName, "disable", undef)); + + # Return if the notifying device is not monitored + return "" if(!defined($hash->{DEV_REGEXP})); + my $regexp = $hash->{DEV_REGEXP}; + return "" if($devName !~ m/^($regexp)$/); + + my $output = $devName." (".$devType.")" ; + my $max = int(@{$dev->{CHANGED}}); + my $readingName; + my $value; + # Loop through all known device types and readings + foreach my $f (@knownDeviceReadings) + { + $readingName = $$f[1]; + # notifing device type is known and the device has also the known reading + if ($$f[0] eq $devType && exists ($dev->{READINGS}{$readingName})) { + if ($$f[2] == 1) { statistics_doStatisticMinMax ($hash, $dev, $readingName);} + } + } + + # Record device as monitored + my $monReadingName = "monitoredDevices".$devType; + my $monReadingValue = ReadingsVal($hashName,$monReadingName,""); + my $temp = '^'.$devName.'$|^'.$devName.',|,'.$devName.'$|,'.$devName.','; + if ($monReadingValue !~ /$temp/) { + if($monReadingValue eq "") { $monReadingValue = $devName;} + else {$monReadingValue .= ",".$devName;} + readingsSingleUpdate($hash,$monReadingName,$monReadingValue,0); + } + + return undef; +} + +# Calculates single MaxMin Values and informs about end of day and month +sub ######################################## +statistics_doStatisticMinMax ($$$) +{ + my ($hash, $dev, $readingName) = @_; + my $dummy; + + my $lastReading; + my $lastSums; + my @newReading; + + my $yearLast; + my $monthLast; + my $dayLast; + my $dayNow; + my $monthNow; + my $yearNow; + + my $value = $dev->{READINGS}{$readingName}{VAL}; + my $prefix = $hash->{PREFIX}; + + # Determine date of last and current reading + if (exists($dev->{READINGS}{$prefix.ucfirst($readingName)."Day"}{TIME})) { + ($yearLast, $monthLast, $dayLast) = $dev->{READINGS}{$prefix.ucfirst($readingName)."Day"}{TIME} =~ /^(\d\d\d\d)-(\d\d)-(\d\d)/; + } else { + ($dummy, $dummy, $dummy, $dayLast, $monthLast, $yearLast) = localtime; + $yearLast += 1900; + $monthLast ++; + } + ($dummy, $dummy, $dummy, $dayNow, $monthNow, $yearNow) = localtime; + $yearNow += 1900; + $monthNow ++; + + # Daily Statistic + #statistics_doStatisticMinMaxSingle: $hash, $readingName, $value, $saveLast + statistics_doStatisticMinMaxSingle $hash, $dev, $readingName."Day", $value, ($dayNow != $dayLast); + + # Monthly Statistic + #statistics_doStatisticMinMaxSingle: $hash, $readingName, $value, $saveLast + statistics_doStatisticMinMaxSingle $hash, $dev, $readingName."Month", $value, ($monthNow != $monthLast); + + # Yearly Statistic + #statistics_doStatisticMinMaxSingle: $hash, $readingName, $value, $saveLast + statistics_doStatisticMinMaxSingle $hash, $dev, $readingName."Year", $value, ($yearNow != $yearLast); + + return ; + +} + +# Calculates single MaxMin Values and informs about end of day and month +sub ######################################## +statistics_doStatisticMinMaxSingle ($$$$$) +{ + my ($hash, $dev, $readingName, $value, $saveLast) = @_; + my $result; + my $hiddenReadingName = ".".$dev->{NAME}.".".$readingName; + + my $statReadingName = $hash->{PREFIX}; + $statReadingName .= ucfirst($readingName); + + my $lastReading = $dev->{READINGS}{$statReadingName}{VAL} || ""; + + # Initializing + if ( $lastReading eq "" ) { + my $since = strftime "%Y-%m-%d_%H:%M:%S", localtime(); + $result = "Count: 1 Sum: $value ShowDate: 1"; + readingsSingleUpdate($hash, $hiddenReadingName, $result,0); + $result = "Min: $value Avg: $value Max: $value (since: $since )"; + readingsSingleUpdate($dev, $statReadingName, $result,0); + + # Calculations + } else { + my @a = split / /, $hash->{READINGS}{$hiddenReadingName}{VAL}; # Internal values + my @b = split / /, $lastReading; + # Do calculations + $a[1]++; # Count + $a[3] += $value; # Sum + if ($value < $b[1]) { $b[1]=$value; } # Min + $b[3] = sprintf "%.0f" , $a[3] / $a[1]; # Avg + if ($value > $b[5]) { $b[5]=$value; } # Max + + # in case of period change, save "last" values and reset counters + if ($saveLast) { + $result = "Min: $b[1] Avg: $b[3] Max: $b[5]"; + if ($a[5] == 1) { $result .= " (since: $b[7] )"; } + readingsSingleUpdate($dev, $statReadingName . "Last", $lastReading,0); + $a[1] = 1; $a[3] = $value; $a[5] = 0; + $b[1] = $value; $b[3] = $value; $b[5] = $value; + } + # Store internal calculation values + $result = "Count: $a[1] Sum: $a[3] ShowDate: $a[5]"; + readingsSingleUpdate($hash, $hiddenReadingName, $result,0); + # Store visible Reading + $result = "Min: $b[1] Avg: $b[3] Max: $b[5]"; + if ($a[5] == 1) { $result .= " (since: $b[7] )"; } + readingsSingleUpdate($dev, $statReadingName, $result,0); + } + return; +} + + +# Calculates deltas for day, month and year +sub ######################################## +statistics_doStatisticDelta ($$$$$) +{ + my ($hash, $readingName, $value, $special, $activeTariff) = @_; + my $dummy; + my $result; + + my $deltaValue; + my $previousTariff; + my $showDate; + + # Determine if time period switched (day, month, year) + # Get deltaValue and Tariff of previous call + my $periodSwitch = 0; + my $yearLast; my $monthLast; my $dayLast; my $hourLast; my $hourNow; my $dayNow; my $monthNow; my $yearNow; + if (exists($hash->{READINGS}{"." . $readingName . "Before"})) { + ($yearLast, $monthLast, $dayLast, $hourLast) = ($hash->{READINGS}{"." . $readingName . "Before"}{TIME} =~ /^(\d\d\d\d)-(\d\d)-(\d\d) (\d\d)/); + $yearLast -= 1900; + $monthLast --; + ($dummy, $deltaValue, $dummy, $previousTariff, $dummy, $showDate) = split / /, $hash->{READINGS}{"." . $readingName . "Before"}{VAL} || ""; + $deltaValue = $value - $deltaValue; + } else { + ($dummy, $dummy, $hourLast, $dayLast, $monthLast, $yearLast) = localtime; + $deltaValue = 0; + $previousTariff = 0; + $showDate = 8; + } + ($dummy, $dummy, $hourNow, $dayNow, $monthNow, $yearNow) = localtime; + + if ($yearNow != $yearLast) { $periodSwitch = 4; } + elsif ($monthNow != $monthLast) { $periodSwitch = 3; } + elsif ($dayNow != $dayLast) { $periodSwitch = 2; } + elsif ($hourNow != $hourLast) { $periodSwitch = 1; } + + # Determine if "since" value has to be shown in current and last reading + if ($periodSwitch == 4) { + if ($showDate == 1) { $showDate = 0; } # Do not show the "since:" value for year changes anymore + if ($showDate >= 2) { $showDate = 1; } # Shows the "since:" value for the first year change + } + if ($periodSwitch >= 3){ + if ($showDate == 3) { $showDate = 2; } # Do not show the "since:" value for month changes anymore + if ($showDate >= 4) { $showDate = 3; } # Shows the "since:" value for the first month change + } + if ($periodSwitch >= 2){ + if ($showDate == 5) { $showDate = 4; } # Do not show the "since:" value for day changes anymore + if ($showDate >= 6) { $showDate = 5; } # Shows the "since:" value for the first day change + } + if ($periodSwitch >= 1){ + if ($showDate == 7) { $showDate = 6; } # Do not show the "since:" value for day changes anymore + if ($showDate >= 8) { $showDate = 7; } # Shows the "since:" value for the first hour change + } + + # statistics_doStatisticDeltaSingle; $hash, $readingName, $deltaValue, $special, $periodSwitch, $showDate, $firstCall + statistics_doStatisticDeltaSingle ($hash, $readingName, $deltaValue, $special, $periodSwitch, $showDate); + + foreach (1,2,3,4,5,6,7,8,9) { + if ( $previousTariff == $_ ) { + statistics_doStatisticDeltaSingle ($hash, $readingName."Tariff".$_, $deltaValue, 0, $periodSwitch, $showDate); + } elsif ($activeTariff == $_ || ($periodSwitch > 0 && exists($hash->{READINGS}{$readingName . "Tariff".$_}))) { + statistics_doStatisticDeltaSingle ($hash, $readingName."Tariff".$_, 0, 0 , $periodSwitch, $showDate); + } + } + + # Hidden storage of current values for next call(before values) + $result = "Value: $value Tariff: $activeTariff ShowDate: $showDate "; + readingsBulkUpdate($hash, ".".$readingName."Before", $result); + + return ; +} + +sub ######################################## +statistics_doStatisticDeltaSingle ($$$$$$) +{ + my ($hash, $readingName, $deltaValue, $special, $periodSwitch, $showDate) = @_; + my $dummy; + my $result; + + # get existing statistic reading + my @curr; + if (exists($hash->{READINGS}{$readingName}{VAL})) { + @curr = split / /, $hash->{READINGS}{$readingName}{VAL} || ""; + if ($curr[0] eq "Day:") { $curr[9]=$curr[7]; $curr[7]=$curr[5]; $curr[5]=$curr[3]; $curr[3]=$curr[1]; $curr[1]=0; } + } else { + $curr[1] = 0; $curr[3] = 0; $curr[5] = 0; $curr[7] = 0; + $curr[9] = strftime "%Y-%m-%d_%H:%M:%S", localtime(); # start + } + + # get statistic values of previous period + my @last; + if ($periodSwitch >= 1) { + if (exists ($hash->{READINGS}{$readingName."Last"})) { + @last = split / /, $hash->{READINGS}{$readingName."Last"}{VAL}; + if ($last[0] eq "Day:") { $last[9]=$last[7]; $last[7]=$last[5]; $last[5]=$last[3]; $last[3]=$last[1]; $last[1]="-"; } + } else { + @last = split / /, "Hour: - Day: - Month: - Year: -"; + } + } + + # Do statistic + $curr[1] += $deltaValue; + $curr[3] += $deltaValue; + $curr[5] += $deltaValue; + $curr[7] += $deltaValue; + + # If change of year, change yearly statistic + if ($periodSwitch == 4){ + $last[7] = $curr[7]; + $curr[7] = 0; + if ($showDate == 1) { $last[9] = $curr[9]; } + } + + # If change of month, change monthly statistic + if ($periodSwitch >= 3){ + $last[5] = $curr[5]; + $curr[5] = 0; + if ($showDate == 3) { $last[9] = $curr[9];} + } + + # If change of day, change daily statistic + if ($periodSwitch >= 2){ + $last[3] = $curr[3]; + $curr[3] = 0; + if ($showDate == 5) { + $last[9] = $curr[9]; + # Next monthly and yearly values start at 00:00 and show only date (no time) + $curr[5] = 0; + $curr[7] = 0; + $curr[9] = strftime "%Y-%m-%d", localtime(); # start + } + } + + # If change of hour, change hourly statistic + if ($periodSwitch >= 1){ + $last[1] = $curr[1]; + $curr[1] = 0; + if ($showDate == 7) { $last[9] = $curr[9];} + } + + # Store visible statistic readings (delta values) + $result = "Hour: $curr[1] Day: $curr[3] Month: $curr[5] Year: $curr[7]"; + if ( $showDate >=2 ) { $result .= " (since: $curr[9] )"; } + readingsBulkUpdate($hash,$readingName,$result); + + if ($special == 1) { readingsBulkUpdate($hash,$readingName."Today",$curr[3]) }; + + # if changed, store previous visible statistic (delta) values + if ($periodSwitch >= 1) { + $result = "Hour: $last[1] Day: $last[3] Month: $last[5] Year: $last[7]"; + if ( $showDate =~ /1|3|5|7/ ) { $result .= " (since: $last[9] )";} + readingsBulkUpdate($hash,$readingName."Last",$result); + } +} + +1; + +=pod +=begin html + + +

    statistics

    +
      + This modul calculates for certain readings of given devices statistical values and adds them to the devices. +   +
      + + Define +
        + define <name> statistics <deviceNameRegExp> [Prefix] +
        + Beispiel: define Statistik statistics Sensor_.*|Wettersensor +
          +
      • [Prefix] +
        + Optional. Default is stat +

      • +
      • <DeviceNameRegExp> +
        + Regular expression of device names. !!! Not the device readings !!! +
        + Until now the following device types and readings are analysed: +
        • CUL_WS: humidity, temperature
        • +
        • KS300: humidity, temperature, wind, rain
        • +
        • FBDECT: energy, power, voltage
        • +
        +
      • +
      + +
      + Set +
        not implemented yet +
      +
      + + Get +
        not implemented yet +
      +
      + + + Attributes +
        not implemented yet +
      +
    + +=end html + +=begin html_DE + + +

    statistics

    +
      + Dieses Modul wertet von den angegebenen Geräten bestimmte Werte statistisch aus und fügt sie den jeweiligen Geräten als neue Werte hinzu. +   +
      + + Define +
        + define <Name> statistics <GeräteNameRegExp> [Prefix] +
        + Beispiel: define Statistik statistics Sensor_.*|Wettersensor +
          +
      • [Prefix] +
        + Optional. Standardmässig stat +

      • +
      • <GeräteNameRegExp> +
        + Regularer Ausdruck für den Gerätenamen. !!! Nicht die Gerätewerte !!! +
        + Derzeit werden folgende Gerätetypen und Gerätewerte ausgewertet: +
        • CUL_WS: humidity, temperature
        • +
        • KS300: humidity, temperature, wind, rain
        • +
        • FBDECT: energy, power, voltage
        • +
        +
      • +
      + +
      + Set +
        noch nicht implementiert +
      +
      + + Get +
        noch nicht implementiert +
      +
      + + + Attributes +
        noch nicht implementiert +
      +
    + +=end html_DE + +=cut \ No newline at end of file