diff --git a/FHEM/73_GasCalculator.pm b/FHEM/73_GasCalculator.pm index 85dee5f06..39a200f65 100644 --- a/FHEM/73_GasCalculator.pm +++ b/FHEM/73_GasCalculator.pm @@ -7,6 +7,7 @@ # the counter device. # Written and best viewed with Notepad++ v.6.8.6; Language Markup: Perl # +# # Author : Matthias Deeke # e-mail : matthias.deeke(AT)deeke(PUNKT)eu # Fhem Forum : https://forum.fhem.de/index.php/topic,47909.0.html @@ -32,22 +33,29 @@ # Example 1: # define myGasCalculator GasCalculator myGasCounter:CounterA.* # +# +# +# ######################################################################################################################## ######################################################################################################################## # List of open Problems / Issues: # -# - set command to create Plots automatically +# +# +# +# +# # ######################################################################################################################## package main; use strict; use warnings; -my %GasCalculator_gets; -my %GasCalculator_sets; use Time::Local; use FHEM::Meta; +my %GasCalculator_gets; +my %GasCalculator_sets; ###START###### Initialize module ##############################################################################START#### sub GasCalculator_Initialize($) @@ -61,6 +69,7 @@ sub GasCalculator_Initialize($) $hash->{SetFn} = "GasCalculator_Set"; $hash->{AttrFn} = "GasCalculator_Attr"; $hash->{NotifyFn} = "GasCalculator_Notify"; + $hash->{DbLog_splitFn} = "GasCalculator_DbLog_splitFn"; $hash->{NotifyOrderPrefix} = "10-"; # Want to be called before the rest $hash->{AttrList} = "disable:0,1 " . @@ -107,6 +116,7 @@ sub GasCalculator_Define($$$) $hash->{STATE} = "active"; $hash->{REGEXP} = $RegEx; + ### Convert SiPrefixPowerFactor if(defined($attr{$hash}{SiPrefixPower})) { if ($attr{$hash}{SiPrefixPower} eq "W" ) {$hash->{system}{SiPrefixPowerFactor} = 1 ;} @@ -121,7 +131,8 @@ sub GasCalculator_Define($$$) } ### Convert Decimal Places - if(defined($attr{$hash}{DecimalPlace})) { + if(defined($attr{$hash}{DecimalPlace})) + { $hash->{system}{DecimalPlace} = "%." . $attr{$hash}{DecimalPlace} . "f"; } @@ -162,6 +173,10 @@ sub GasCalculator_Undefine($$) my ($hash, $def) = @_; my $name = $hash->{NAME}; + ### Stop internal timer + RemoveInternalTimer($hash); + + ### Write log information Log3 $name, 3, $name. " GasCalculator- The gas calculator has been undefined. Values corresponding to Gas Counter will no longer calculated"; return undef; @@ -190,7 +205,7 @@ sub GasCalculator_Attr(@) } ### Check whether "SiPrefixPower" attribute has been provided - if ($a[2] eq "SiPrefixPower") + elsif ($a[2] eq "SiPrefixPower") { if ($a[3] eq "W" ) {$hash->{system}{SiPrefixPowerFactor} = 1 ;} elsif ($a[3] eq "kW") {$hash->{system}{SiPrefixPowerFactor} = 1000 ;} @@ -200,11 +215,14 @@ sub GasCalculator_Attr(@) } ### Convert Decimal Places - elsif ($a[2] eq "DecimalPlace") { - if (($a[3] >= 3) && ($a[3] <= 8)) { + elsif ($a[2] eq "DecimalPlace") + { + if (($a[3] >= 3) && ($a[3] <= 8)) + { $hash->{system}{DecimalPlace} = "%." . $a[3] . "f"; } - else { + else + { $hash->{system}{DecimalPlace} = "%.3f"; } } @@ -214,6 +232,71 @@ sub GasCalculator_Attr(@) ####END####### Handle attributes after changes via fhem GUI ####################################################END##### +###START###### Provide units for DbLog database via DbLog_splitFn #############################################START#### +sub GasCalculator_DbLog_splitFn($$) +{ + my ($event, $name) = @_; + my ($reading, $value, $unit); + my $hash = $defs{$name}; + my @argument = split("[ \t][ \t]*", $event); + + ### Delete ":" and everything behind in readings name + $argument[0] =~ s/:.*//; + + ### Log entries for debugging + #Log3 $name, 5, $name. " : GasCalculator_DbLog_splitFn - Content of event : " . $event; + #Log3 $name, 5, $name. " : GasCalculator_splitFn - Content of argument[0] : " . $argument[0]; + #Log3 $name, 5, $name. " : GasCalculator_splitFn - Content of argument[1] : " . $argument[1]; + + ### If the reading contains "_EnergyCost" or "_FinanceReserve" + if (($argument[0] =~ /_EnergyCost/) || ($argument[0] =~ /_FinanceReserve/)) + { + ### Log entries for debugging + #Log3 $name, 5, $name. " : GasCalculator - DbLog_splitFn - EnergyCost-Reading detected : " . $argument[0]; + + ### Get values being changed from hash + $reading = $argument[0]; + $value = $argument[1]; + $unit = $attr{$hash}{Currency}; + } + ### If the reading contains "_Power" + elsif ($argument[0] =~ /_Power/) + { + ### Log entries for debugging + #Log3 $name, 5, $name. " : GasCalculator - DbLog_splitFn - Power-Reading detected : " . $argument[0]; + + ### Get values being changed from hash + $reading = $argument[0]; + $value = $argument[1]; + $unit = $attr{$hash}{SiPrefixPower}; + } + ### If the reading contains "_Counter" or "_Last" or ("_Energy" but not "_EnergyCost") or "_PrevRead" + elsif (($argument[0] =~ /_Counter/) || ($argument[0] =~ /_Last/) || (($argument[0] =~ /_Energy/) && ($argument[0] !~ /_EnergyCost/)) || ($argument[0] =~ /_PrevRead/)) + { + ### Log entries for debugging + #Log3 $name, 5, $name. " : GasCalculator - DbLog_splitFn - Counter/Energy-Reading detected : " . $argument[0]; + + ### Get values being changed from hash + $reading = $argument[0]; + $value = $argument[1]; + $unit = $attr{$hash}{Volume}; + } + ### If the reading is unknown + else + { + ### Log entries for debugging + #Log3 $name, 5, $name. " : GasCalculator - DbLog_splitFn - unspecified-Reading detected : " . $argument[0]; + + ### Get values being changed from hash + $reading = $argument[0]; + $value = $argument[1]; + $unit = ""; + } + return ($reading, $value, $unit); +} +####END####### Provide units for DbLog database via DbLog_splitFn ##############################################END##### + + ###START###### Manipulate reading after "get" command by fhem #################################################START#### sub GasCalculator_Get($@) { @@ -342,9 +425,10 @@ sub GasCalculator_Set($@) $ReturnMessage = $GasCalcName . " - Successfully synchromized Counter and Calculator with : " . $value . " kWh"; } ### For Test purpose only - #elsif ($reading eq "Test") { - # GasCalculator_MidnightTimer($hash); - #} + # elsif ($reading eq "Test") + # { + # GasCalculator_MidnightTimer($hash); + # } elsif ($reading ne "?") { ### Create Log entries for debugging @@ -371,12 +455,13 @@ sub GasCalculator_MidnightTimer($) my ($GasCountName, $GasCountReadingRegEx) = split(":", $RegEx, 2); my $GasCountDev = $defs{$GasCountName}; $GasCountReadingRegEx =~ s/[\.\*]+$//; + my $GasCountReadingRegExNeg = $GasCountReadingRegEx . "_"; my @GasCountReadingNameListComplete = keys(%{$GasCountDev->{READINGS}}); my @GasCountReadingNameListFiltered; foreach my $GasCountReadingName (@GasCountReadingNameListComplete) { - if ($GasCountReadingName =~ m[$GasCountReadingRegEx]) { + if (($GasCountReadingName =~ m[$GasCountReadingRegEx]) && ($GasCountReadingName !~ m[$GasCountReadingRegExNeg])) { push(@GasCountReadingNameListFiltered, $GasCountReadingName); } } @@ -398,12 +483,35 @@ sub GasCalculator_MidnightTimer($) Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - Looping through every Counter defined by RegEx"; foreach my $GasCountReadingName (@GasCountReadingNameListFiltered) { + ### Create Readings + my $GasCalcReadingDestinationDeviceName; + my $GasCalcReadingPrefix; + my $GasCalcReadingDestinationDevice; - # ### 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 ); + if ($attr{$GasCalcName}{ReadingDestination} eq "CalculatorDevice") + { + $GasCalcReadingDestinationDeviceName = $GasCalcName; + $GasCalcReadingPrefix = ($GasCountName . "_" . $GasCountReadingName); + $GasCalcReadingDestinationDevice = $GasCalcDev; + + } + elsif ($attr{$GasCalcName}{ReadingDestination} eq "CounterDevice") + { + $GasCalcReadingPrefix = $GasCountReadingName; + $GasCalcReadingDestinationDevice = $GasCountDev; + $GasCalcReadingDestinationDeviceName = $GasCountName; + } + else + { + ### Create Log entries for debugging + Log3 $GasCalcName, 3, $GasCalcName. " : GasCalculator_MidnightTimer - Attribut ReadingDestination has not been set up correctly. Skipping event."; + + ### Skipping event + next; + } + + my $GasCounterReadingValue = ReadingsVal($GasCountName, $GasCountReadingName , "error"); + my $LastUpdateTimestampUnix = ReadingsVal($GasCalcReadingDestinationDeviceName, "." . $GasCalcReadingPrefix . "_LastUpdateTimestampUnix", 0 ); ### Calculate time difference since last update my $DeltaTimeSinceLastUpdate = time() - $LastUpdateTimestampUnix ; @@ -419,12 +527,6 @@ sub GasCalculator_MidnightTimer($) ### 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) { @@ -437,6 +539,7 @@ sub GasCalculator_MidnightTimer($) Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator_MidnightTimer - Last Update : There was an Update in the last 24h!"; } + ### Create Log entries for debugging purpose #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"); @@ -499,7 +602,7 @@ sub GasCalculator_Notify($$) ### Create Log entries for debugging Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator Begin_______________________________________________________________________________________________________________________________"; - Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator - Notify - Trigger Dev Name : " . $GasCountDev->{NAME}; + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator - Notify - Trigger Dev Name : " . $GasCountDev->{NAME}; ### Check whether the gas calculator has been disabled if(IsDisabled($GasCalcName)) @@ -635,7 +738,6 @@ sub GasCalculator_Notify($$) Log3 $GasCalcName, 3, $GasCalcName. " : GasCalculator - The attribute DecimalPlace was missing and has been set to 3"; } - ### For each feedback on in the array of defined regexpression which has been changed for (my $i = 0; $i < $NumberOfChangedEvents; $i++) { @@ -674,6 +776,9 @@ sub GasCalculator_Notify($$) ###Get current Counter and transform in Volume (cubic) as read on mechanic gas meter $GasCountReadingValueCurrent = $1 * $attr{$GasCalcName}{GasCubicPerCounts} + $attr{$GasCalcName}{GasCounterOffset}; my $GasCountReadingTimestampCurrent = ReadingsTimestamp($GasCountName,$GasCountReadingName,0); + + ### Create Log entries for debugging + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator Begin_______________________________________________________________________________________________________________________________"; ### Create name and destination device for general reading prefix my $GasCalcReadingPrefix; @@ -755,7 +860,7 @@ sub GasCalculator_Notify($$) readingsSingleUpdate( $GasCalcReadingDestinationDevice, "." . $GasCalcReadingPrefix . "_WFRDayCount", 0, 1); readingsSingleUpdate( $GasCalcReadingDestinationDevice, $GasCalcReadingPrefix . "_WFRDayMin", 0, 1); readingsSingleUpdate( $GasCalcReadingDestinationDevice, $GasCalcReadingPrefix . "_WFRDayMax", 0, 1); - readingsSingleUpdate( $GasCalcDev, "." . $GasCalcReadingPrefix . "_LastUpdateTimestampUnix", time(), 0); + readingsSingleUpdate( $GasCalcReadingDestinationDevice, "." . $GasCalcReadingPrefix . "_LastUpdateTimestampUnix", time(), 0); ### Create Log entries for debugging Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator - Previous value NOT found. Skipping Loop"; @@ -768,6 +873,9 @@ sub GasCalculator_Notify($$) ### Find out whether the reading for the daily start value has not been written yet if(!defined(ReadingsVal($GasCalcReadingDestinationDeviceName, $GasCalcReadingPrefix . "_Vol1stDay", undef))) { + ### Create Log entries for debugging + Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator - _CounterDay1st value NOT found!"; + ### Save current Volume as first reading of day = first after midnight and reset min, max value, value counter and value sum readingsSingleUpdate( $GasCalcReadingDestinationDevice, $GasCalcReadingPrefix . "_Vol1stDay", $GasCountReadingValueCurrent, 1); readingsSingleUpdate( $GasCalcReadingDestinationDevice, $GasCalcReadingPrefix . "_VolLastDay", $GasCountReadingValuePrevious, 1); @@ -835,8 +943,8 @@ sub GasCalculator_Notify($$) if (($GasCountReadingTimestampCurrentHour < $GasCountReadingTimestampPreviousHour) || ($GasCountReadingLastChangeDelta > 86400)) { - ### Create Log entries for debugging - Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator - First reading of day detected OR last reading is older than 24h!"; + ### 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}; @@ -920,7 +1028,7 @@ sub GasCalculator_Notify($$) Log3 $GasCalcName, 5, $GasCalcName. " : GasCalculator - GasCountReadingTimestampDelta : " . $GasCountReadingTimestampDelta . " s"; ### Continue with calculations only if time difference is not 0 to avoid "Illegal division by zero" - if ($GasCountReadingTimestampDelta != 0) + if ($GasCountReadingTimestampDelta > 0) { ### Calculate DV (Volume difference) of previous and current value / [cubic] my $GasCountReadingValueDelta = sprintf($GasCalcDev->{system}{DecimalPlace}, ($GasCountReadingValueCurrent )) - sprintf($GasCalcDev->{system}{DecimalPlace}, ($GasCountReadingValuePrevious)); @@ -929,7 +1037,7 @@ sub GasCalculator_Notify($$) ### 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); + readingsSingleUpdate($GasCalcReadingDestinationDevice, "." . $GasCalcReadingPrefix . "_LastUpdateTimestampUnix", $GasCountReadingTimestampCurrentRelative, 0); } ### Calculate Current Power P = DV/Dt[cubic/s] * GaszValue * GasNominalHeatingValue[kWh/cubic] * 3600[s/h] / SiPrefixPowerFactor @@ -1005,9 +1113,6 @@ sub GasCalculator_Notify($$) ### Initialize Bulkupdate readingsBeginUpdate($GasCalcReadingDestinationDevice); - ### Write current mechanic meter reading - readingsBulkUpdate($GasCalcReadingDestinationDevice, $GasCalcReadingPrefix . "_Meter", sprintf($GasCalcDev->{system}{DecimalPlace}, ($GasCountReadingValueCurrent))); - ### Write consumed volume (DV) since last measurement readingsBulkUpdate($GasCalcReadingDestinationDevice, "." . $GasCalcReadingPrefix . "_LastDV", sprintf($GasCalcDev->{system}{DecimalPlace}, ($GasCountReadingValueDelta))); @@ -1073,6 +1178,9 @@ sub GasCalculator_Notify($$) ### Write reserves at gas provider based on monthly advance payments within year of gas meter reading readingsBulkUpdate($GasCalcReadingDestinationDevice, $GasCalcReadingPrefix . "_FinanceReserve", sprintf($GasCalcDev->{system}{DecimalPlace}, ($GasCalcReserves))); + ### Write current mechanic meter reading + readingsBulkUpdate($GasCalcReadingDestinationDevice, $GasCalcReadingPrefix . "_Meter", sprintf($GasCalcDev->{system}{DecimalPlace}, ($GasCountReadingValueCurrent))); + ### Write months since last meter reading readingsBulkUpdate($GasCalcReadingDestinationDevice, $GasCalcReadingPrefix . "_MonthMeterReading", sprintf('%.0f', ($GasCalcMeterYearMonth))); @@ -1133,6 +1241,19 @@ sub GasCalculator_Notify($$) + + + + + + + + + + + + +
@@ -1147,7 +1268,6 @@ sub GasCalculator_Notify($$)
Define
    The set - function sets individual values for example to correct values after power loss etc.
    The set - function works for readings which have been stored in the CalculatorDevice and to update the Offset.
    The Readings being stored in the Counter - Device need to be changed individially with the set - command.
    The command "SyncCounter" will calculate and update the Offset. Just enter the value of your mechanical Reader.

- @@ -1207,6 +1327,7 @@ sub GasCalculator_Notify($$) +
Get
    The get - function just returns the individual value of the reading.
    The get - function works only for readings which have been stored in the CalculatorDevice.
    The Readings being stored in the Counter - Device need to be read individially with get - command.
    • <DestinationDevice>_<SourceCounterReading>_VolLastYear
: Volume reading of the previous year.
    • <DestinationDevice>_<SourceCounterReading>_Vol1stMeter
: First volume reading of the first day of the month of the current meter reading period.
    • <DestinationDevice>_<SourceCounterReading>_VolLastMeter
: Volume reading of the first day of the month of the last meter reading period.
@@ -1233,6 +1354,16 @@ sub GasCalculator_Notify($$) + + + + + + + + + +
@@ -1307,6 +1438,7 @@ sub GasCalculator_Notify($$)
Define
    • <DestinationDevice>_<SourceCounterReading>_Vol1stMeter
: Erster Volumenmesswert des Zeitraums seit Anfang des Monats wo der Gas-Versorger den Zähler abliest.
    • <DestinationDevice>_<SourceCounterReading>_VolLastMeter
: Verbrauchtes Volumen des vorherigen Abrechnungszeitraums.
+ =end html_DE