From 5d0e862d0bf9b51ed553a6e227b07de0bdd2c76b Mon Sep 17 00:00:00 2001 From: Sailor Date: Sun, 31 Jan 2021 16:03:01 +0000 Subject: [PATCH] 73_GasCalculator: Feature - Midnight Timer implemented git-svn-id: https://svn.fhem.de/fhem/trunk@23650 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/FHEM/73_GasCalculator.pm | 1461 ++++++++------------------------- 1 file changed, 363 insertions(+), 1098 deletions(-) diff --git a/fhem/FHEM/73_GasCalculator.pm b/fhem/FHEM/73_GasCalculator.pm index c00f25477..2959aff85 100644 --- a/fhem/FHEM/73_GasCalculator.pm +++ b/fhem/FHEM/73_GasCalculator.pm @@ -46,6 +46,7 @@ use strict; use warnings; my %GasCalculator_gets; my %GasCalculator_sets; +use FHEM::Meta; ###START###### Initialize module ##############################################################################START#### sub GasCalculator_Initialize($) @@ -77,6 +78,7 @@ sub GasCalculator_Initialize($) "Currency:€,£,$ " . "DecimalPlace:3,4,5,6,7 " . $readingFnAttributes; + return FHEM::Meta::InitMod( __FILE__, $hash ); } ####END####### Initialize module ###############################################################################END##### @@ -135,6 +137,19 @@ sub GasCalculator_Define($$$) ### Writing log entry Log3 $name, 5, $name. " : GasCalculator - Starting to define module"; + ### Start timer for execution around midnight + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); + my $EpochNextMidnight = timelocal(1, 0, 0, $mday, $mon, $year+1900) + 86400; + InternalTimer($EpochNextMidnight, "GasCalculator_MidnightTimer", $hash, 0); + + ### For debugging purpose only + Log3 $name, 5, $name. " : GasCalculator_MidnightTimer - time : " . time(); + Log3 $name, 5, $name. " : GasCalculator_MidnightTimer - year : " . $year; + Log3 $name, 5, $name. " : GasCalculator_MidnightTimer - mon : " . $mon; + Log3 $name, 5, $name. " : GasCalculator_MidnightTimer - day : " . $mday; + Log3 $name, 5, $name. " : GasCalculator_MidnightTimer - timelocal : " . timelocal(1, 0, 0, $mday, $mon, $year+1900); + Log3 $name, 5, $name. " : GasCalculator_MidnightTimer - nextMidnight : " . $EpochNextMidnight; + return undef; } ####END####### Activate module after module has been used via fhem command "define" ############################END##### @@ -338,6 +353,125 @@ sub GasCalculator_Set($@) } ####END####### Manipulate reading after "set" command by fhem ##################################################END##### +###START###### Midnight Routine ###############################################################################START#### +sub GasCalculator_MidnightTimer($) +{ + ### Define variables + my ($GasCalcDev) = @_; + my $GasCalcName = $GasCalcDev->{NAME}; + my $RegEx = $GasCalcDev->{REGEXP}; + my ($GasCountName, $GasCountReadingRegEx) = split(":", $RegEx, 2); + my $GasCountDev = $defs{$GasCountName}; + $GasCountReadingRegEx =~ s/[\.\*]//g; + + my @GasCountReadingNameListComplete = keys(%{$GasCountDev->{READINGS}}); + my @GasCountReadingNameListFiltered; + + foreach my $GasCountReadingName (@GasCountReadingNameListComplete) { + if ($GasCountReadingName =~ m[$GasCountReadingRegEx]) { + push(@GasCountReadingNameListFiltered, $GasCountReadingName); + } + } + + + ### Create Log entries for debugging purpose + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer__________________________________________________________"; + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer : MidnightTimer initiated"; + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - RegEx : " . $RegEx; + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - GasCountName : " . $GasCountName; + #Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - GasCountReadList: " . Dumper(@GasCountReadingNameListFiltered); + + + ### Remove internal timer for GasCalculator_MidnightTimer + RemoveInternalTimer($GasCalcDev, "GasCalculator_MidnightTimer"); + + ### Create Log entries for debugging purpose + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - Looping through every Counter defined by RegEx"; + + foreach my $GasCountReadingName (@GasCountReadingNameListFiltered) { + + # ### Restore Destination of readings + my $GasCalcReadingPrefix = $GasCountName . "_" . $GasCountReadingName; + my $GasCalcReadingDestinationDeviceName = ReadingsVal($GasCalcName, ".ReadingDestinationDeviceName" , "error"); + my $GasCounterReadingValue = ReadingsVal($GasCountName, $GasCountReadingName , "error"); + my $LastUpdateTimestampUnix = ReadingsVal($GasCalcName, "." . $GasCalcReadingPrefix . "_LastUpdateTimestampUnix", 0 ); + + ### Calculate time difference since last update + my $DeltaTimeSinceLastUpdate = time() - $LastUpdateTimestampUnix ; + + ### Create Log entries for debugging purpose + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer ___________Looping________________"; + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - ReadingPrefix : " . $GasCalcReadingPrefix; + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - DeviceName : " . $GasCalcReadingDestinationDeviceName; + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - Timestamp now : " . time(); + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - Timestamp update : " . $LastUpdateTimestampUnix; + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - Timestamp Delta : " . $DeltaTimeSinceLastUpdate; + + + ### If the Readings for midnight settings have been provided + if (($GasCalcReadingPrefix ne "error") && ($GasCalcReadingDestinationDeviceName ne "error") && ($LastUpdateTimestampUnix > 0)){ + + ### Create Log entries for debugging purpose + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - Timestamp update : " . $LastUpdateTimestampUnix; + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - Timestamp Delta : " . $DeltaTimeSinceLastUpdate; + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - ReadingPrefix : " . $GasCalcReadingPrefix; + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - DeviceName : " . $GasCalcReadingDestinationDeviceName; + + ### If there was no update in the last 24h + if ( $DeltaTimeSinceLastUpdate >= 86400) { + ### Create Log entries for debugging purpose + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - Last Update : No Update in the last 24h!"; + + } + else { + ### Create Log entries for debugging purpose + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - Last Update : There was an Update in the last 24h!"; + } + + #Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - GasCalcRDD : \n" . Dumper($GasCalcReadingDestinationDevice); + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - GasCounter : " . $GasCounterReadingValue; + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - Pre WFRDaySum : " . ReadingsVal($GasCalcReadingDestinationDeviceName, "." . $GasCalcReadingPrefix . "_PowerDaySum", "error"); + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - Pre WFRDayCount : " . ReadingsVal($GasCalcReadingDestinationDeviceName, "." . $GasCalcReadingPrefix . "_PowerDayCount", "error"); + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - Pre WFRDayCurrent : " . ReadingsVal($GasCalcReadingDestinationDeviceName, $GasCalcReadingPrefix . "_PowerCurrent", "error"); + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - Pre WFRDayAver : " . ReadingsVal($GasCalcReadingDestinationDeviceName, $GasCalcReadingPrefix . "_PowerDayAver", "error"); + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - Pre WFRDayMax : " . ReadingsVal($GasCalcReadingDestinationDeviceName, $GasCalcReadingPrefix . "_PowerDayMax", "error"); + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - Pre WFRDayMin : " . ReadingsVal($GasCalcReadingDestinationDeviceName, $GasCalcReadingPrefix . "_PowerDayMin", "error"); + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - Pre ConsumDay : " . ReadingsVal($GasCalcReadingDestinationDeviceName, $GasCalcReadingPrefix . "_EnergyDay", "error"); + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - Pre ConsumDayLast : " . ReadingsVal($GasCalcReadingDestinationDeviceName, $GasCalcReadingPrefix . "_EnergyDayLast", "error"); + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - Pre ConsumCstDay : " . ReadingsVal($GasCalcReadingDestinationDeviceName, $GasCalcReadingPrefix . "_EnergyCostDay", "error"); + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - Pre ConsumCstDayL : " . ReadingsVal($GasCalcReadingDestinationDeviceName, $GasCalcReadingPrefix . "_EnergyCostDayLast", "error"); + + + if ($GasCounterReadingValue ne "error") { + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - Writing Counter : " . $GasCounterReadingValue; + readingsSingleUpdate($GasCountDev, $GasCountReadingName, $GasCounterReadingValue, 1); + } + else { + + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - Writing Counter : Error!"; + } + } + ### If the Readings for midnight settings have not been provided + else { + ### Warning Log entry + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - ERROR - There have no information stored about previous readings. Make sure the counter has been delivering at least 2 values to the Calculator device before next midnight!"; + } + } + + ### Start timer for execution around midnight + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); + my $EpochNextMidnight = timelocal(1, 0, 0, $mday, $mon, $year+1900) + 86400; + InternalTimer($EpochNextMidnight, "GasCalculator_MidnightTimer", $GasCalcDev, 0); + + ### For debugging purpose only + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer _______Looping finished___________"; + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - time : " . time(); + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - timelocal : " . timelocal(1, 0, 0, $mday, $mon, $year+1900); + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - nextMidnight : " . $EpochNextMidnight; +} +####END####### Midnight Routine ################################################################################END##### + + ###START###### Calculate gas meter values on changed events ###################################################START#### sub GasCalculator_Notify($$) { @@ -564,10 +698,14 @@ sub GasCalculator_Notify($$) ### Skipping event next; } + + ### Save Destination of readings into hidden readings + readingsSingleUpdate($GasCalcDev, ".ReadingDestinationDeviceName", $GasCalcReadingDestinationDeviceName, 0); ### Restore previous Counter and if not available define it with "undef" - my $GasCountReadingTimestampPrevious = ReadingsTimestamp($GasCalcReadingDestinationDeviceName, "." . $GasCalcReadingPrefix . "_PrevRead", undef); - my $GasCountReadingValuePrevious = ReadingsVal($GasCalcReadingDestinationDeviceName, "." . $GasCalcReadingPrefix . "_PrevRead", undef); + my $GasCountReadingTimestampPrevious = ReadingsTimestamp($GasCalcReadingDestinationDeviceName, "." . $GasCalcReadingPrefix . "_PrevRead", undef); + my $GasCountReadingValuePrevious = ReadingsVal($GasCalcReadingDestinationDeviceName, "." . $GasCalcReadingPrefix . "_PrevRead", undef); + my $GasCountReadingLastChangeDelta = time() - ReadingsVal($GasCalcReadingDestinationDeviceName, "." . $GasCalcReadingPrefix . "_LastUpdateTimestampUnix", undef); ### Create Log entries for debugging Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator - GasCountReadingValuePrevious : " . $GasCountReadingValuePrevious; @@ -588,6 +726,21 @@ sub GasCalculator_Notify($$) ### Write current Volume as previous Voulume for future use in the GasCalc-Device readingsSingleUpdate( $GasCalcReadingDestinationDevice, "." . $GasCalcReadingPrefix. "_PrevRead", sprintf($GasCalcDev->{system}{DecimalPlace}, ($GasCountReadingValueCurrent)),1); + ### Save current Gas Consumption as first reading of day = first after midnight and reset min, max value, value counter and value sum + readingsSingleUpdate( $GasCalcReadingDestinationDevice, $GasCalcReadingPrefix . "_CounterDay1st", sprintf($GasCalcDev->{system}{DecimalPlace}, ($GasCountReadingValueCurrent)),1); + readingsSingleUpdate( $GasCalcReadingDestinationDevice, $GasCalcReadingPrefix . "_CounterDayLast", sprintf($GasCalcDev->{system}{DecimalPlace}, ($GasCountReadingValueCurrent)),1); + readingsSingleUpdate( $GasCalcReadingDestinationDevice, $GasCalcReadingPrefix . "_CounterMonth1st", sprintf($GasCalcDev->{system}{DecimalPlace}, ($GasCountReadingValueCurrent)),1); + readingsSingleUpdate( $GasCalcReadingDestinationDevice, $GasCalcReadingPrefix . "_CounterMonthLast", sprintf($GasCalcDev->{system}{DecimalPlace}, ($GasCountReadingValueCurrent)),1); + readingsSingleUpdate( $GasCalcReadingDestinationDevice, $GasCalcReadingPrefix . "_CounterMeter1st", sprintf($GasCalcDev->{system}{DecimalPlace}, ($GasCountReadingValueCurrent)),1); + readingsSingleUpdate( $GasCalcReadingDestinationDevice, $GasCalcReadingPrefix . "_CounterMeterLast", sprintf($GasCalcDev->{system}{DecimalPlace}, ($GasCountReadingValueCurrent)),1); + readingsSingleUpdate( $GasCalcReadingDestinationDevice, $GasCalcReadingPrefix . "_CounterYear1st", sprintf($GasCalcDev->{system}{DecimalPlace}, ($GasCountReadingValueCurrent)),1); + readingsSingleUpdate( $GasCalcReadingDestinationDevice, $GasCalcReadingPrefix . "_CounterYearLast", sprintf($GasCalcDev->{system}{DecimalPlace}, ($GasCountReadingValueCurrent)),1); + readingsSingleUpdate( $GasCalcReadingDestinationDevice, "." . $GasCalcReadingPrefix . "_WFRDaySum", 0, 1); + readingsSingleUpdate( $GasCalcReadingDestinationDevice, "." . $GasCalcReadingPrefix . "_WFRDayCount", 0, 1); + readingsSingleUpdate( $GasCalcReadingDestinationDevice, $GasCalcReadingPrefix . "_WFRDayMin", 0, 1); + readingsSingleUpdate( $GasCalcReadingDestinationDevice, $GasCalcReadingPrefix . "_WFRDayMax", 0, 1); + readingsSingleUpdate( $GasCalcDev, "." . $GasCalcReadingPrefix . "_LastUpdateTimestampUnix", time(), 0); + ### Create Log entries for debugging Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator - Previous value NOT found. Skipping Loop"; @@ -659,10 +812,14 @@ sub GasCalculator_Notify($$) ####### Check whether Initial readings needs to be written ### Check whether the current value is the first one after change of day = First one after midnight - if ($GasCountReadingTimestampCurrentHour < $GasCountReadingTimestampPreviousHour) + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_Notify GasCountReadTimeCurHour : " . $GasCountReadingTimestampCurrentHour; + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_Notify GasCountReadTimePrevHour : " . $GasCountReadingTimestampPreviousHour; + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_Notify GasCountReadTimeRelDelta : " . $GasCountReadingLastChangeDelta; + + if (($GasCountReadingTimestampCurrentHour < $GasCountReadingTimestampPreviousHour) || ($GasCountReadingLastChangeDelta > 86400)) { - ### Create Log entries for debugging - Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator - First reading of day detected"; + ### Create Log entries for debugging + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator - First reading of day detected OR last reading is older than 24h!"; ### Calculate gas energy of previous day € = (Vprevious[cubic] - V1stDay[cubic]) * GaszValue * GasNominalHeatingValue[kWh/cubic] my $GasCalcEnergyDayLast = ($GasCountReadingValuePrevious - ReadingsVal($GasCalcReadingDestinationDeviceName, $GasCalcReadingPrefix . "_Vol1stDay", "0")) * $attr{$GasCalcName}{GaszValue} * $attr{$GasCalcName}{GasNominalHeatingValue}; @@ -740,7 +897,7 @@ sub GasCalculator_Notify($$) } } - ###### Do calculations + ###### Do calculations ### Calculate DtCurrent (time difference) of previous and current timestamp / [s] my $GasCountReadingTimestampDelta = $GasCountReadingTimestampCurrentRelative - $GasCountReadingTimestampPreviousRelative; Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator - GasCountReadingTimestampDelta : " . $GasCountReadingTimestampDelta . " s"; @@ -752,6 +909,12 @@ sub GasCalculator_Notify($$) my $GasCountReadingValueDelta = sprintf($GasCalcDev->{system}{DecimalPlace}, ($GasCountReadingValueCurrent )) - sprintf($GasCalcDev->{system}{DecimalPlace}, ($GasCountReadingValuePrevious)); Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator - GasCountReadingValueDelta : " . $GasCountReadingValueDelta . " " . $attr{$GasCalcName}{Volume}; + ### If the value has been changed since the last one + if ($GasCountReadingValueDelta > 0) { + ### Save current Timestamp as UNIX epoch into hash if the + readingsSingleUpdate($GasCalcDev, "." . $GasCalcReadingPrefix . "_LastUpdateTimestampUnix", $GasCountReadingTimestampCurrentRelative, 0); + } + ### Calculate Current Power P = DV/Dt[cubic/s] * GaszValue * GasNominalHeatingValue[kWh/cubic] * 3600[s/h] / SiPrefixPowerFactor my $GasCalcPowerCurrent = ($GasCountReadingValueDelta / $GasCountReadingTimestampDelta) * $attr{$GasCalcName}{GaszValue} * $attr{$GasCalcName}{GasNominalHeatingValue} * 3600 / $GasCalcDev->{system}{SiPrefixPowerFactor}; @@ -937,571 +1100,97 @@ sub GasCalculator_Notify($$)

GasCalculator

=end html @@ -1511,575 +1200,151 @@ sub GasCalculator_Notify($$)

GasCalculator

- =end html_DE +=for :application/json;q=META.json 73_GasCalculator.pm +{ + "abstract": "Calculates the gas energy consumption and costs.", + "description": "The GasCalculator Module calculates the gas consumption and costs of one ore more gas counters.
It is not a counter module itself but requires a regular expression (regex or regexp) in order to know where retrieve the counting ticks of one or more mechanical gas counter.
As soon the module has been defined within the fhem.cfg, the module reacts on every event of the specified counter like myOWDEVICE:counter.* etc.
The GasCalculator module provides several current, historical, statistical predictable values around with respect to one or more gas-counter and creates respective readings.
", + "x_lang": { + "de": { + "abstract": "Berechnet den Gas-Energieverbrauch und verbundene Kosten", + "description": "Das GasCalculator Modul berechnet den Gas - Verbrauch und den verbundenen Kosten von einem oder mehreren Gas-Zählern.
Es ist kein eigenes Zählermodul sondern benötigt eine Regular Expression (regex or regexp) um das Reading mit den Zähl-Impulse von einem oder mehreren Gaszählern zu finden.
Sobald das Modul in der fhem.cfg definiert wurde, reagiert das Modul auf jedes durch das regex definierte event wie beispielsweise ein myOWDEVICE:counter.* etc.
Das GasCalculator Modul berechnet augenblickliche, historische statistische und vorhersehbare Werte von einem oder mehreren Gas-Zählern und erstellt die entsprechenden Readings.
" + } + }, + "author": [ + "I am the maintainer matthias.deeke@deeke.eu" + ], + "x_fhem_maintainer": [ + "Sailor" + ], + "keywords": [ + "gas", + "energy", + "calculation", + "consumption", + "cost", + "counter" + ], + "prereqs": { + "runtime": { + "requires": { + "FHEM": 5.00918623, + "FHEM::Meta": 0.001006, + "HttpUtils": 0, + "JSON": 0, + "perl": 5.014 + }, + "recommends": { + }, + "suggests": { + } + } + }, + "resources": { + "x_support_community": { + "rss": "https://forum.fhem.de/index.php/topic,47909.msg", + "web": "https://forum.fhem.de/index.php/topic,47909.msg", + "subCommunity" : { + "rss" : "https://forum.fhem.de/index.php/topic,47909.msg", + "title" : "This sub-board will be first contact point", + "web" : "https://forum.fhem.de/index.php/topic,47909.msg" + } + } + }, + "x_support_status": "supported" +} +=end :application/json;q=META.json + =cut