diff --git a/fhem/CHANGED b/fhem/CHANGED index add7992b2..c690eb0b3 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - feature: new module 98_HourCounter added, 99_UtilsHourCounter.pm added to contrib (john) - added: MYSENSORS: connect to serial or Ethernet MySensors Gateway - added: MYSENSORS_DEVICE: represent a MySensors sensor- or actor node - feature: global ATTR/DELETEATTR/MODIFIED events diff --git a/fhem/FHEM/98_HourCounter.pm b/fhem/FHEM/98_HourCounter.pm new file mode 100644 index 000000000..cc21b80f3 --- /dev/null +++ b/fhem/FHEM/98_HourCounter.pm @@ -0,0 +1,930 @@ +# $Id: 98_HourCounter.pm 6802 2014-10-23 18:00:00Z john $ +#################################################################################################### +# +# 98_HourCounter.pm +# The HourCounter accumulates single events to a counter object. +# In the case of binary weighted events pulse- and pause-time are determined +# +# This module is written by john. +# +# This file is part of fhem. +# +# Fhem 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. +# +# Fhem 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. +# +# You should have received a copy of the GNU General Public License +# along with fhem. If not, see . +# +# +# 16.11.13 - 0.99.b +# Loglevel adjusted +# 02.12.13 - 0.99.c +# $readingFnAttributes added +# 03.12.13 - 0.99.d +# missed attribute event-on-change-reading +# 02.02.14 - 1.00 +# command queues +# 04.02.14 - 1.01 +# queue removed +# 17.03.14 - 1.02 +# adjusting log-levels, forceYearChange,HourCounter_RoundYear +# 07.06.14 - 1.03 +# $ID changed +# setter for pulseTimeIncrement, pauseTimeIncrement +# 25.10.14 - 1.0.0.4 +# official part of fhem +# adjusting log-output +# update documentation + + +#################################################################################################### +package main; +use strict; +use warnings; + +use vars qw(%defs); +use vars qw($readingFnAttributes); +use vars qw(%attr); +use vars qw(%modules); + +my $HourCounter_Version="1.0.0.4 - 23.10.2014"; + +my @HourCounter_cmdQeue =(); + +########################## +sub HourCounter_Log($$$) +{ + my ( $hash, $loglevel, $text ) = @_; + my $xline = (caller(0))[2]; + + my $xsubroutine = (caller(1))[3]; + my $sub = (split( ':', $xsubroutine ))[2]; + $sub =~ s/HourCounter_//; + + my $instName = ( ref($hash) eq "HASH" ) ? $hash->{NAME} : "HourCounter"; + Log3 $hash, $loglevel, "HourCounter $instName $sub.$xline " . $text; +} + +########################## +sub HourCounter_AddLog($$$) +{ + my ($logdevice, $readingName,$value) = @_; + + my $cmd=''; + if ($readingName =~ m,state,i) + { + $cmd="trigger $logdevice $value << addLog"; + } + else + { + $cmd="trigger $logdevice $readingName: $value << addLog"; + } + + HourCounter_Log '',3,$cmd; + fhem ($cmd); +} + +########################## +# execute the content of the given parameter +sub HourCounter_Exec($) +{ + my $doit = shift; + my $ret=''; + eval $doit; + $ret = $@ if ($@); + return $ret; +} +########################## +# add command to queue +sub HourCounter_cmdQueueAdd($$) +{ + my ($hash,$cmd)= @_; + push(@{$hash->{helper}{cmdQueue}},$cmd); +} + +########################## +# execute command queue +sub HourCounter_ExecQueue($) +{ + my ($hash)=@_; + my $result; + + my $cnt=$#{$hash->{helper}{cmdQueue}}; + my $loops =0; + my $cntAll=0; + + HourCounter_Log $hash,4,"cnt: $cnt"; + + while ($cnt>=0) + { + for my $i (0 .. $cnt) + { + my $cmd = ${$hash->{helper}{cmdQueue}}[$i]; + ${$hash->{helper}{cmdQueue}}[$i]=''; + $result=HourCounter_Exec($cmd); + if ($result) + { + HourCounter_Log $hash,2,"$result"; + } + else { + HourCounter_Log $hash,4,"exec ok:$cmd"; + } + $cntAll++; + } + + # bearbeitete eintraege loeschen + for (my $i = $cnt; $i > -1; $i--) + { + splice (@{$hash->{helper}{cmdQueue}}, $i, 1) + } + + $cnt=$#HourCounter_cmdQeue; + $loops++; + if ($loops >= 5 || $cntAll>100) + { + HourCounter_Log $hash,2, "!!! too deep recursion"; + last; + } + } +} + + +########################## +# round off the date passed to the hour +sub HourCounter_RoundHour($) +{ + my ($sec,$min,$hour,$mday,$mon,$year) = localtime(shift); + return mktime(0, 0, $hour, $mday, $mon, $year); +} + +########################## +# round off the date passed to the day +sub HourCounter_RoundDay($) +{ + my ($sec,$min,$hour,$mday,$mon,$year) = localtime(shift); + return mktime(0, 0, 0, $mday, $mon, $year); +} + +########################## +# round off the date passed to the week +sub HourCounter_RoundWeek($) +{ + my ($time) = @_; + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($time); + # wday 0 Sonntag 1 Montag ... + $time-=$wday * 86400; + return HourCounter_RoundDay($time); +} + +########################## +# returns the seconds since the start of the day +sub HourCounter_SecondsOfDay() +{ + my $timeToday = gettimeofday(); + return int($timeToday - HourCounter_RoundDay($timeToday)); +} + +########################## +# round off the date passed to the month +sub HourCounter_RoundMonth($) +{ + my ($sec,$min,$hour,$mday,$mon,$year) = localtime(shift); + return mktime(0, 0, 0, 1, $mon, $year); +} +########################## +# round off the date passed to the year +sub HourCounter_RoundYear($) +{ + my ($sec,$min,$hour,$mday,$mon,$year) = localtime(shift); + return mktime(0, 0, 0, 1, 1, $year); +} + +########################## +sub HourCounter_Initialize($) +{ + my ($hash) = @_; + $hash->{DefFn} = "HourCounter_Define"; + $hash->{UndefFn} = "HourCounter_Undef"; + + $hash->{SetFn} = "HourCounter_Set"; + $hash->{GetFn} = "HourCounter_Get"; + $hash->{NotifyFn} = "HourCounter_Notify"; + $hash->{AttrList} = "disable:0,1 ". $readingFnAttributes; + + HourCounter_Log "", 3, "Init Done with Version $HourCounter_Version"; +} + +########################## +sub HourCounter_Define($$$) +{ + my ( $hash, $def ) = @_; + my @a = split( "[ \t][ \t]*", $def ); + my $name = $a[0]; + HourCounter_Log $hash, 4, "parameters: @a"; + if ( @a < 3 ) + { + return "wrong syntax: define HourCounter []"; + } + my $onRegexp = $a[2]; + + my $offRegexp = (@a==4)?$a[3]:undef; + + # Checking for misleading regexps + eval { "Hallo" =~ m/^$onRegexp/ }; + return "Bad regexp_for_ON : $@" if ($@); + if ($offRegexp) + { + eval { "Hallo" =~ m/^$offRegexp/ }; + return "Bad regexp_for_ON : $@" if ($@); + } + + $hash->{helper}{ON_Regexp} = $onRegexp; + $hash->{helper}{OFF_Regexp} = $offRegexp; + $hash->{helper}{isFirstRun} = 1; + $hash->{helper}{value} = -1; + $hash->{helper}{forceHourChange} = ''; + $hash->{helper}{forceDayChange} = ''; + $hash->{helper}{forceWeekChange} = ''; + $hash->{helper}{forceMonthChange} = ''; + $hash->{helper}{forceYearChange} = ''; + + $hash->{helper}{forceClear} = ''; + $hash->{helper}{calledByEvent} = ''; + $hash->{helper}{changedTimestamp} = ''; + @{$hash->{helper}{cmdQueue}} = (); + + $modules{HourCounter}{defptr}{$name}=$hash; + RemoveInternalTimer($name); + InternalTimer( int(gettimeofday() + 15), "HourCounter_Run", $name, 0 ); + return undef; + +} +########################## +sub HourCounter_Undef($$) +{ + my ( $hash, $arg ) = @_; + + HourCounter_Log $hash, 3, "Done"; + return undef; +} +########################### +sub HourCounter_Get($@) +{ + my ( $hash, @a ) = @_; + my $name = $hash->{NAME}; + + my $ret = "Unknown argument $a[1], choose one of version:noArg"; + my $cmd = lc( $a[1] ); + + if ($cmd eq 'version') + { + $ret = "Version : $HourCounter_Version\n"; + } + + return $ret; + +} +########################### +sub HourCounter_Set($@) +{ + my ( $hash, @a ) = @_; + my $name = $hash->{NAME}; + my $reINT = '^([\\+,\\-]?\\d+$)'; # int + + # determine userReadings beginning with app + my @readingNames = keys (%{$hash->{READINGS}}); + my @userReadings = (); + foreach (@readingNames) + { + if ($_ =~ m/app.*/) + { + push (@userReadings,$_); + } + } + my $strUserReadings = join(" ",@userReadings)." "; + + # standard commands with parameter + my @cmdPara =( + "countsOverall","countsPerDay", + "pauseTimeIncrement","pauseTimePerDay","pauseTimeOverall", + "pulseTimeIncrement","pulseTimePerDay","pulseTimeOverall"); + + # standard commands with no parameter + my @cmdNoPara =("clear","forceHourChange","forceDayChange","forceWeekChange","forceMonthChange","forceYearChange"); + + my @allCommands = (@cmdPara,@cmdNoPara,@userReadings); + my $strAllCommands = join(" ",(@cmdPara,@userReadings))." ".join(":noArg ",@cmdNoPara).":noArg "; + #HourCounter_Log $hash, 2, "strAllCommands : $strAllCommands"; + + # stop:noArg + my $usage = + "Unknown argument $a[1], choose one of " + .$strAllCommands; + + # we need at least 2 parameters + return "Need a parameter for set" if ( @a < 2 ); + + my $cmd = $a[1]; + if ($cmd eq "?") + { + return $usage; + } + my $value = $a[2]; + + # is command defined ? + if ( (grep { /$cmd/ } @allCommands) <= 0) + { + HourCounter_Log $hash, 2, "cmd:$cmd no match for : @allCommands"; + return return "unknown command : $cmd"; + } + + # need we a parameter ? + my $hits = scalar grep { /$cmd/ } @cmdNoPara; + my $needPara = ($hits > 0) ? '' : 1; + HourCounter_Log $hash, 4, "hits: $hits needPara:$needPara"; + + # if parameter needed, it must be an integer + return "Value must be an integer" if ($needPara && !($value =~ m/$reINT/)); + + HourCounter_Log $hash, 4, "$cmd $value"; + my $doRun=''; + + if($needPara) + { + readingsSingleUpdate( $hash, $cmd, $value, 1 ); + } + elsif ($cmd eq "forceHourChange") + { + $hash->{helper}{forceHourChange}=1; + $doRun=1; + } + elsif ($cmd eq "forceDayChange") + { + $hash->{helper}{forceDayChange}=1; + $doRun=1; + } + elsif ($cmd eq "forceWeekChange") + { + $hash->{helper}{forceWeekChange}=1; + $doRun=1; + } + elsif ($cmd eq "forceMonthChange") + { + $hash->{helper}{forceMonthChange}=1; + $doRun=1; + } + elsif ($cmd eq "forceYearChange") + { + $hash->{helper}{forceYearChange}=1; + $doRun=1; + } + elsif ($cmd eq "clear") + { + $hash->{helper}{forceClear}=1; + $doRun=1; + } + else + { + return "unknown command (2): $cmd"; + } + + if ($doRun) + { + $hash->{helper}{value}=-1; + $hash->{helper}{calledByEvent}=1; + HourCounter_Run($hash->{NAME}); + } + + return; + +} +########################## +sub HourCounter_Notify($$) +{ + my ( $hash, $dev ) = @_; + my $name = $hash->{NAME}; + my $devName = $dev->{NAME}; + + # return if disabled + if ( AttrVal( $name, 'disable', '0' ) eq '1' ) + { + return ""; + } + my $onRegexp = $hash->{helper}{ON_Regexp}; + my $offRegexp = $hash->{helper}{OFF_Regexp}; + + #HourCounter_Log $hash,5,"Notify by DevName: ".$dev->{NAME}; + my $max = int( @{ $dev->{CHANGED} } ); + for ( my $i = 0 ; $i < $max ; $i++ ) + { + my $s = $dev->{CHANGED}[$i]; # read changed reading + $s = "" if ( !defined($s) ); + my $isOnReading = ( "$devName:$s" =~ m/^$onRegexp$/ ); + my $isOffReading = ($offRegexp) ? ( "$devName:$s" =~ m/^$offRegexp$/ ):''; + + HourCounter_Log $hash, 5, + "devName:$devName; CHANGED:$s; isOnReading:$isOnReading; isOffReading:$isOffReading;"; + next if ( !( $isOnReading || ($isOffReading && $offRegexp) ) ); + + $hash->{helper}{value} = 1 if ($isOnReading); + $hash->{helper}{value} = 0 if ($isOffReading); + $hash->{helper}{calledByEvent}=1; + HourCounter_Run($hash->{NAME}); + } +} + +########################## +# converts the seconds in the date format +sub HourCounter_Seconds2HMS($) +{ + my ( $seconds) = @_; + my ($Sekunde, $Minute, $Stunde, $Monatstag, $Monat, $Jahr, $Wochentag, $Jahrestag, $Sommerzeit) = localtime($seconds); + my $days = int($seconds/86400); + return sprintf( "%d Tage %02d:%02d:%02d", $days,$Stunde - 1, $Minute, $Sekunde ); +} + +########################## +# rounds the timestamp do the beginning of the week +sub HourCounter_weekBase($) +{ + my ($time) = @_; + my $dayDiff = 60*60*24; + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($time); + # wday 0 Sonntag 1 Montag ... + my $a=$time-$wday*$dayDiff; + my $b=int($a/$dayDiff); # auf tage gehen + my $c=$b*$dayDiff; + return $c; +} + +########################## +sub HourCounter_Run($) +{ + # print "xxx TAG A\n" ; + my ($name) = @_; + my $hash = $defs{$name}; + + return if (!defined($hash->{TYPE}) || $hash->{TYPE} ne 'HourCounter'); + + delete($hash->{CHANGETIME}); # timestamps for event-log-file-entries older, than current time + + my $calledByEvent = $hash->{helper}{calledByEvent}; + $hash->{helper}{calledByEvent} = ''; + + # if call was made by timer force value to -1 + my $valuePara = ($calledByEvent)? $hash->{helper}{value}:-1; + + $hash->{helper}{changedTimestamp} = ReadingsTimestamp( $name, "value", TimeNow() ) + if (!$hash->{helper}{changedTimestamp}); + my $sdValue = time_str2num($hash->{helper}{changedTimestamp}); + my $sdCurTime = gettimeofday(); + + my $isOffDefined = ($hash->{helper}{OFF_Regexp})? 1: ''; + + my $timeIncrement = int( $sdCurTime - $sdValue ); # time diff + $timeIncrement = 0 if ($timeIncrement<0); # wrong time offset in case of summer/winter time + + my $valueOld = ReadingsVal( $name, 'value', 0 ); # get the old value + + # variable for reading update + my $value = undef; + my $countsPerDay=undef; + my $countsOverall=undef; + + my $pulseTimeIncrement=undef; + my $pulseTimePerDay=undef; + my $pulseTimeOverall=undef; + + my $pauseTimePerDay=undef; + my $pauseTimeOverall=undef; + my $pauseTimeIncrement=undef; + + my $state=undef; + my $clearDate = undef; + + my $sdRoundHour = HourCounter_RoundHour($sdCurTime); + my $sdRoundHourLast = $hash->{helper}{sdRoundHourLast}; + $sdRoundHourLast = $sdRoundHour if (!$sdRoundHourLast); + my $isHourChanged = ($sdRoundHour != $sdRoundHourLast) || $hash->{helper}{forceHourChange}; + + my $sdRoundDayCurTime = HourCounter_RoundDay($sdCurTime); + my $sdRoundDayValue = HourCounter_RoundDay($sdRoundHourLast); + my $isDayChanged = ($sdRoundDayCurTime != $sdRoundDayValue) || $hash->{helper}{forceDayChange}; + + my $sdRoundWeekCurTime = HourCounter_RoundWeek($sdCurTime); + my $sdRoundWeekValue = HourCounter_RoundWeek($sdRoundHourLast); + my $isWeekChanged = ($sdRoundWeekCurTime != $sdRoundWeekValue) || $hash->{helper}{forceWeekChange}; + + my $sdRoundMonthCurTime = HourCounter_RoundMonth($sdCurTime); + my $sdRoundMonthValue = HourCounter_RoundMonth($sdRoundHourLast); + my $isMonthChanged = ($sdRoundMonthCurTime != $sdRoundMonthValue) || $hash->{helper}{forceMonthChange}; + + my $sdRoundYearCurTime = HourCounter_RoundYear($sdCurTime); + my $sdRoundYearValue = HourCounter_RoundYear($sdRoundHourLast); + my $isYearChanged = ($sdRoundYearCurTime != $sdRoundYearValue) || $hash->{helper}{forceYearChange}; + #HourCounter_Log $hash, 0,"sdRoundYearCurTime : $sdRoundYearCurTime"; + + while (1) + { + # stop if disabled + last if ( AttrVal( $name, 'disable', '0' ) eq '1' ); + + # variables for controlling + my $resetDayCounter=''; + + HourCounter_Log $hash, 5, "value:$valuePara changedTimestamp:".$hash->{helper}{changedTimestamp} ; + + # --------------- basic init after startup of fhem or reload + if ( $hash->{helper}{isFirstRun} ) + { + $hash->{helper}{isFirstRun}=undef; + $hash->{helper}{sdRoundHourLast} = $sdRoundHourLast; + HourCounter_Log $hash, 4, "first run done"; + } + + # ------------ basic init, when first run after initial definition in fhem.cfg + if ( !defined(ReadingsVal( $name, 'value', undef)) || $hash->{helper}{forceClear} ) + { + HourCounter_Log $hash, 4, "counters cleared " + ."forceClear:$hash->{helper}{forceClear}" + ." def(valueOld)".defined($valueOld); + + if (!defined($valueOld)) + { # create readings without triggering + readingsBeginUpdate($hash); + readingsBulkUpdate( $hash, 'tickHour', 0); + readingsBulkUpdate( $hash, 'tickDay', 0); + readingsBulkUpdate( $hash, 'tickWeek', 0); + readingsBulkUpdate( $hash, 'tickMonth',0); + readingsEndUpdate( $hash, 1 ); + } + + if (! ($hash->{helper}{forceClear})) # set value at basic init + { + $valueOld = 0; + $value = 0; + } + + $timeIncrement = 0; + $hash->{helper}{forceClear} = ''; + $countsPerDay = 0; + $countsOverall = 0; + + $pulseTimeIncrement = 0; + $pulseTimePerDay = 0; + $pulseTimeOverall = 0; + + $pauseTimeIncrement = 0; + $pauseTimePerDay = 0; + $pauseTimeOverall = 0; + + $state = 0; + $clearDate = TimeNow(); + } + + # ------------------------- handling of transitions + + my $hasValueChanged = ( $isOffDefined && $valuePara == $valueOld ) ? '' :1; + + # -------------- positive edge + if ( $hasValueChanged && $valuePara == 1 ) + { + $hash->{helper}{changedTimestamp} = TimeNow(); + + $value = $valuePara; + $valueOld = $valuePara; + + # handling of counters + $countsPerDay = ReadingsVal( $name, "countsPerDay", 0 ) + 1; # counter inkrementieren + $countsOverall = ReadingsVal( $name, "countsOverall", 0 ) + 1; # counter inkrementieren + + if ($isOffDefined) # handling of pause + { + $pauseTimeIncrement = $timeIncrement; + + $pauseTimePerDay = ReadingsVal( $name, "pauseTimePerDay", 0 ) + $pauseTimeIncrement; + $pauseTimeOverall = ReadingsVal( $name, "pauseTimeOverall", 0 )+ $pauseTimeIncrement; + my $pulsInc = ReadingsVal( $name, "pulseTimeIncrement", 0 ); + } + HourCounter_Log $hash, 4, "rising edge; countPerDay:$countsPerDay"; + } + + # ------------ negative edge + elsif ($isOffDefined && $hasValueChanged && $valuePara == 0 ) + { + $hash->{helper}{changedTimestamp} = TimeNow(); + $value = $valuePara; + $valueOld = $valuePara; + + # handling of pulse time + $pulseTimeIncrement = $timeIncrement; + $pulseTimePerDay = ReadingsVal( $name, "pulseTimePerDay", 0 ) + $pulseTimeIncrement; + $pulseTimeOverall = ReadingsVal( $name, "pulseTimeOverall", 0 ) + $pulseTimeIncrement; + + HourCounter_Log $hash, 4, "falling edge pulseTimeIncrement:$pulseTimeIncrement"; + } + + + # --------------- Day change, update pauseTime and pulseTime + if ($isDayChanged) + { + HourCounter_Log $hash, 4, "day change isDayChanged:$isDayChanged"; + + ### accumulate incurred times until day change + if ($valueOld==0) + { + $pauseTimeIncrement = $timeIncrement; + $pauseTimePerDay = ReadingsVal( $name, "pauseTimePerDay", 0 ) + $timeIncrement; + $pauseTimeOverall = ReadingsVal( $name, "pauseTimeOverall", 0 ) + $timeIncrement; + } + elsif ($valueOld==1) + { + $pulseTimeIncrement = $timeIncrement; + $pulseTimePerDay = ReadingsVal( $name, "pulseTimePerDay", 0 ) + $timeIncrement; + $pulseTimeOverall = ReadingsVal( $name, "pulseTimeOverall", 0 ) + $timeIncrement; + } + + # update timestamp of reading value with current time + $hash->{helper}{changedTimestamp}=TimeNow(); + + # logabriss vermeiden + $pulseTimeIncrement = ReadingsVal( $name, "pulseTimeIncrement", 0 ) if (!defined($pulseTimeIncrement)); + $pulseTimeOverall = ReadingsVal( $name, "pulseTimeOverall", 0 ) if (!defined($pulseTimeOverall)); + + $pauseTimeIncrement = ReadingsVal( $name, "pauseTimeIncrement", 0 ) if (!defined($pauseTimeIncrement)); + $pauseTimeOverall = ReadingsVal( $name, "pauseTimeOverall", 0 ) if (!defined($pauseTimeOverall)); + + $countsOverall = ReadingsVal( $name, "countsOverall", 0 ) if (!defined($countsOverall)); + + $value = $valueOld; + + HourCounter_Log $hash, 4, "pulseTimeIncrement:$pulseTimeIncrement pauseTimeIncrement:$pauseTimeIncrement"; + $resetDayCounter=1; + } + + $state =$countsPerDay if (defined($countsPerDay) && ReadingsVal( $name, "state", 0 ) !=$countsPerDay); + + + ### -------------- update readings + readingsBeginUpdate($hash); + readingsBulkUpdate( $hash, "countsPerDay", $countsPerDay ) if defined($countsPerDay); + readingsBulkUpdate( $hash, "countsOverall", $countsOverall ) if defined($countsOverall); + + readingsBulkUpdate( $hash, "pulseTimeIncrement",$pulseTimeIncrement ) if defined($pulseTimeIncrement); + readingsBulkUpdate( $hash, "pulseTimePerDay", $pulseTimePerDay ) if defined($pulseTimePerDay); + readingsBulkUpdate( $hash, "pulseTimeOverall", $pulseTimeOverall) if defined($pulseTimeOverall); + + readingsBulkUpdate( $hash, "pauseTimeIncrement",$pauseTimeIncrement) if defined($pauseTimeIncrement); + readingsBulkUpdate( $hash, "pauseTimePerDay", $pauseTimePerDay) if defined($pauseTimePerDay); + readingsBulkUpdate( $hash, "pauseTimeOverall", $pauseTimeOverall) if defined($pauseTimeOverall); + + readingsBulkUpdate( $hash, "value", $value) if defined($value); + readingsBulkUpdate( $hash, 'state', $state) if defined($state); + readingsBulkUpdate( $hash, 'clearDate', $clearDate) if defined($clearDate); + readingsEndUpdate( $hash, 1 ); + + # --------------- fire time interval ticks for hour,day,month + if ($isHourChanged) + { + $hash->{helper}{forceHourChange} = ''; + $hash->{helper}{sdRoundHourLast} = $sdRoundHour; + readingsSingleUpdate( $hash, 'tickHour', 1,1); + HourCounter_Log $hash, 4, "tickHour fired"; + } + + if ($isDayChanged) + { + $hash->{helper}{forceDayChange}= ''; + readingsSingleUpdate( $hash, 'tickDay', 1,1); + HourCounter_Log $hash, 4, "tickDay fired"; + } + + if ($isWeekChanged) + { + $hash->{helper}{forceWeekChange}= ''; + readingsSingleUpdate( $hash, 'tickWeek', 1,1); + HourCounter_Log $hash, 4, "tickWeek fired"; + } + + if ($isMonthChanged) + { + $hash->{helper}{forceMonthChange}= ''; + readingsSingleUpdate( $hash, 'tickMonth', 1,1); + HourCounter_Log $hash, 4, "tickMonth fired"; + } + + if ($isYearChanged) + { + $hash->{helper}{forceYearChange}= ''; + readingsSingleUpdate( $hash, 'tickYear', 1,1); + HourCounter_Log $hash, 4, "tickYear fired"; + } + + HourCounter_ExecQueue($hash); # execute command queue + + # ----------- pending request for resetting all day counters + if ($resetDayCounter) + { + $resetDayCounter=undef; + + ### reset all day counters + readingsBeginUpdate($hash); + readingsBulkUpdate( $hash, "countsPerDay", 0 ); + readingsBulkUpdate( $hash, "pulseTimePerDay", 0 ); + readingsBulkUpdate( $hash, "pauseTimePerDay", 0 ); + readingsEndUpdate( $hash, 1 ); + } + + + last; + } + + # ------------ calculate seconds until next hour starts + my $actTime = int(gettimeofday()); + my ($sec,$min,$hour) = localtime($actTime); + my $nextHourTime = int(($actTime + 3600)/3600)*3600; # round to next hour start + my $nextCall = $nextHourTime - $actTime; + + HourCounter_Log $hash, 5, "nextCall:$nextCall changedTimestamp:" + .$hash->{helper}{changedTimestamp}; + + RemoveInternalTimer($name); + InternalTimer( gettimeofday() + $nextCall, "HourCounter_Run", $hash->{NAME}, 0 ); + + return undef; +} + + +1; + +=pod +=begin html + + +

HourCounter

+
    + + Define +
      +
      + define <name> HourCounter <pattern_for_ON> [<pattern_for_OFF>] +
      +
      + Hourcounter can detect both the activiy-time and the inactivity-time of a property.
      + The "pattern_for_ON" identifies the events, that signal the activity of the desired property.
      + The "pattern_for_OFF" identifies the events, that signal the inactivity of the desired property.
      +
      + If "pattern_for_OFF" is not defined, any matching event of "patter_for_ON" will be counted.
      + Otherwise only the rising edges of "pattern_for_ON" will be counted.
      + This means a "pattern_for_OFF"-event must be detected before a "pattern_for_ON"-event is accepted.
      +
      + "pattern_for_ON" and "pattern_for_OFF" must be formed using the following structure:
      +
      + device:[regexp] +
      +
      + The forming-rules are the same as for the notify-command.
      +
      + Example:
      +
      +
        + define BurnerCounter HourCounter SHUTTER_TEST:on SHUTTER_TEST:off +
      +
    +
    + + + Set-Commands +
      + +
      + set <name> clear +

      +
        + clears the readings countsPerDay, countsOverall,pauseTimeIncrement, pauseTimePerDay, pauseTimeOverall, + pulseTimeIncrement, pulseTimePerDay, pulseTimeOverall by setting to 0.
        + The reading clearDate is set to the current Date/Time. +

      + +
      + set <name> countsOverall <value> +

      +
        Sets the reading countsOverall to the given value.This is the total-counter.

      + +
      + set <name> countsPerDay <value> +

      +
        Sets the reading countsPerDay to the given value. This reading will automatically be set to 0, after change of day.

      + +
      + set <name> pauseTimeIncrement <value> +

      +
        Sets the reading pauseTimeIncrement to the given value.
        + This reading in seconds is automatically set after a rising edge of the property.

      + +
      + set <name> pauseTimeOverall <value> +

      +
        Sets the reading pauseTimeOverall to the given value.
        + This reading in seconds is automatically adjusted after a change of pauseTimeIncrement.

      + +
      + set <name> pauseTimePerDay <value> +

      +
        Sets the reading pauseTimePerDay to the given value.
        + This reading in seconds is automatically adjusted after a change of pauseTimeIncrement and set to 0 after change of day.

      + +
      + set <name> pulseTimeIncrement <value> +

      +
        Sets the reading pulseTimeIncrement to the given value.
        + This reading in seconds is automatically set after a falling edge of the property.

      + +
      + set <name> pulseTimeOverall <value> +

      +
        Sets the reading pulseTimeOverall to the given value.
        + This reading in seconds is automatically adjusted after a change of pulseTimeIncrement.

      + +
      + set <name> pulseTimePerDay <value> +

      +
        Sets the reading pulseTimePerDay to the given value.
        + This reading in seconds is automatically adjusted after a change of pulseTimeIncrement and set to 0 after change of day.

      + +
      + set <name> forceHourChange +

      +
        This modifies the reading tickHour, which is automatically modified after change of hour.

      + +
      + set <name> forceDayChange +

      +
        This modifies the reading tickDay, which is automatically modified after change of day.

      + +
      + set <name> forceWeekChange +

      +
        This modifies the reading tickWeek, which is automatically modified after change of week.

      + +
      + set <name> forceMonthChange +

      +
        This modifies the reading tickMonth, which is automatically modified after change of month.

      + +
      + set <name> forceYearChange +

      +
        This modifies the reading tickYear, which is automatically modified after change of year.

      + +
      + set <name> app.* <value> +

      +
        Any reading with the leading term "app", can be modified.

        + +
      +
      + + + Get-Commands
      +
        +
        + get <name> version +

        +
          Get the current version of the module.
        +
        +
      +
      + + + Attributes

      + +
      + + Additional information

      +
        +
      • Discussion in FHEM forum

      • +
      • WIKI information in FHEM Wiki

      • +
      • + The file 99_UtilsHourCounter.pm is a reference implementation for user defined extensions.
        + It shows how to create sum values for hours,days, weeks, months and years.
        + This file is located in the sub-folder contrib. For further information take a look to FHEM Wiki.
        +
      • +
      +
    + + +=end html +=cut + diff --git a/fhem/MAINTAINER.txt b/fhem/MAINTAINER.txt index b0bab05b7..c6ea01968 100644 --- a/fhem/MAINTAINER.txt +++ b/fhem/MAINTAINER.txt @@ -250,6 +250,7 @@ FHEM/98_CustomReadings.pm HCS http://forum.fhem.de Unterstue FHEM/98_dewpoint.pm Joachim http://forum.fhem.de Automatisierung FHEM/98_dummy.pm rudolfkoenig http://forum.fhem.de Automatisierung FHEM/98_fheminfo.pm mfr69bs http://forum.fhem.de Sonstiges +FHEM/98_HourCounter.pm john http://forum.fhem.de MAX FHEM/98_notice.pm mfr69bs http://forum.fhem.de Sonstiges FHEM/98_pilight.pm andreas-fey http://forum.fhem.de Unterstuetzende Dienste FHEM/98_rain.pm baumrasen http://forum.fhem.de Sonstiges diff --git a/fhem/contrib/99_UtilsHourCounter.pm b/fhem/contrib/99_UtilsHourCounter.pm new file mode 100644 index 000000000..7f93af983 --- /dev/null +++ b/fhem/contrib/99_UtilsHourCounter.pm @@ -0,0 +1,283 @@ +############################################## +# $Id: 99_UtilsHourCounter.pm 6802 2014-10-25 18:00:00Z john $ +# +# This ist a reference implementation for enhanced features for modul hourCounter +# +# This module is written by john. +# +# This file is part of fhem. +# +# Fhem 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. +# +# Fhem 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. +# +# You should have received a copy of the GNU General Public License +# along with fhem. If not, see . +# +# Changelog +# +# 04.02.14 - 1.00 modul created +# 06.02.14 - 1.01 fixed: wrong timing in assignment appUtilization +# 17.03.14 - 1.01 added: appHC_OnYear + + +package main; + +use strict; +use warnings; +use POSIX; +use vars qw(%defs); +use vars qw(%modules); + +#require "98_HourCounter.pm"; + +my $UtilsHourCounter_Version="1.02 - 17.03.2014 (john)"; +sub Log3($$$); + +# -------------------------------------------------- +sub UtilsHourCounter_Initialize($$) +{ + my ($hash) = @_; + + Log3 '', 3, "[UtilsHourCounter] Init Done with Version $UtilsHourCounter_Version"; +} + +# -------------------------------------------------- +# yearly tasks +# +sub appHC_OnYear($$$) +{ + my ($name,$part0,$part1) = @_; # name objects, name des parameters, wert des parameters + $part0='' if (!defined($part0)); + my $hash = $defs{$name}; + return undef if (!defined ($hash)); + + my $appCountsPerYear = ReadingsVal ($name, 'appCountsPerYearTemp' ,0); + my $appOpHoursPerYear = ReadingsVal ($name, 'appOpHoursPerYearTemp' ,0); + + #--------------- + readingsBeginUpdate($hash); + + readingsBulkUpdate ($hash, 'appCountsPerYearTemp' , 0 ); + readingsBulkUpdate ($hash, 'appCountsPerYear' , $appCountsPerYear ); + + readingsBulkUpdate ($hash, 'appOpHoursPerYearTemp' , 0 ); + readingsBulkUpdate ($hash, 'appOpHoursPerYear' , $appOpHoursPerYear ); + + readingsEndUpdate($hash, 1) +} + +# -------------------------------------------------- +# monthly tasks +# +sub appHC_OnMonth($$$) +{ + my ($name,$part0,$part1) = @_; # name objects, name des parameters, wert des parameters + $part0='' if (!defined($part0)); + my $hash = $defs{$name}; + return undef if (!defined ($hash)); + + my $appCountsPerMonth = ReadingsVal ($name, 'appCountsPerMonthTemp' ,0); + my $appOpHoursPerMonth = ReadingsVal ($name, 'appOpHoursPerMonthTemp' ,0); + + #--------------- + readingsBeginUpdate($hash); + + readingsBulkUpdate ($hash, 'appCountsPerMonthTemp' , 0 ); + readingsBulkUpdate ($hash, 'appCountsPerMonth' , $appCountsPerMonth ); + + readingsBulkUpdate ($hash, 'appOpHoursPerMonthTemp' , 0 ); + readingsBulkUpdate ($hash, 'appOpHoursPerMonth' , $appOpHoursPerMonth ); + + readingsEndUpdate($hash, 1) +} + +# -------------------------------------------------- +# weekly tasks +sub appHC_OnWeek($$$) +{ + my ($name,$part0,$part1) = @_; # name objects, name des parameters, wert des parameters + $part0='' if (!defined($part0)); + my $hash = $defs{$name}; + return undef if (!defined ($hash)); + + my $appCountsPerWeek = ReadingsVal ($name, 'appCountsPerWeekTemp' ,0); + my $appOpHoursPerWeek = ReadingsVal ($name, 'appOpHoursPerWeekTemp' ,0); + + readingsBeginUpdate($hash); + + readingsBulkUpdate ($hash, 'appCountsPerWeekTemp' , 0); + readingsBulkUpdate ($hash, 'appCountsPerWeek' , $appCountsPerWeek); + + readingsBulkUpdate ($hash, 'appOpHoursPerWeekTemp' , 0); + readingsBulkUpdate ($hash, 'appOpHoursPerWeek' , $appOpHoursPerWeek); + + readingsEndUpdate( $hash, 1 ); + +} + +# -------------------------------------------------- +# dayly tasks +sub appHC_OnDay($$$) +{ + my ($name,$part0,$part1) = @_; # name objects, name des parameters, wert des parameters + $part0='' if (!defined($part0)); + my $hash = $defs{$name}; + return undef if (!defined ($hash)); + + my $appCountsPerDay = ReadingsVal($name, 'countsPerDay',0); + my $pulseTimePerDay = ReadingsVal($name, 'pulseTimePerDay',0); + + #HourCounter_Log $hash, 2, "pulseTimePerDay:$pulseTimePerDay"; + + my $appOpHoursPerDay = $pulseTimePerDay/3600; + my $appOpHoursPerWeekTemp = ReadingsVal ($name,'appOpHoursPerWeekTemp',0 )+$appOpHoursPerDay; + my $appOpHoursPerMonthTemp =ReadingsVal ($name,'appOpHoursPerMonthTemp',0 )+$appOpHoursPerDay; + my $appOpHoursPerYearTemp =ReadingsVal ($name,'appOpHoursPerYearTemp',0 )+$appOpHoursPerDay; + + my $appUtilizationTempOld = ReadingsVal ($name,'appUtilizationTempOld',0 ); + + readingsBeginUpdate($hash); + + readingsBulkUpdate ($hash, 'appCountsPerDay' , $appCountsPerDay); + readingsBulkUpdate ($hash, 'appOpHoursPerDay' , $appOpHoursPerDay); + readingsBulkUpdate ($hash, 'appOpHoursPerDayTemp' , 0); + + readingsBulkUpdate ($hash, 'appOpHoursPerWeekTemp' , $appOpHoursPerWeekTemp); + readingsBulkUpdate ($hash, 'appOpHoursPerMonthTemp', $appOpHoursPerMonthTemp); + readingsBulkUpdate ($hash, 'appOpHoursPerYearTemp' , $appOpHoursPerYearTemp); + + readingsBulkUpdate ($hash, 'appUtilization', $appUtilizationTempOld); + + readingsEndUpdate( $hash, 1 ); +} + +# -------------------------------------------------- +# hourly tasks +sub appHC_OnHour($$$) +{ + my ($name,$part0,$part1) = @_; # name objects, name des parameters, wert des parameters + $part0='' if (!defined($part0)); + my $hash = $defs{$name}; + return undef if (!defined ($hash)); + + my $appCountsPerHourTemp = ReadingsVal($name, 'appCountsPerHourTemp',0); + + readingsBeginUpdate($hash); + readingsBulkUpdate ($hash, 'appCountsPerHourTemp', 0 ); + readingsBulkUpdate ($hash, 'appCountsPerHour', $appCountsPerHourTemp); + readingsEndUpdate( $hash, 1 ); + +} +# -------------------------------------------------- +# task on count change +sub appHC_OnCount($$$) +{ + my ($name,$part0,$part1) = @_; # name objects, name des parameters, wert des parameters + $part0='' if (!defined($part0)); + my $hash = $defs{$name}; + return undef if (!defined ($hash)); + + + my $appCountsPerHourTemp = ReadingsVal($name,'appCountsPerHourTemp',0) + 1; + my $appCountsPerWeekTemp = ReadingsVal($name,'appCountsPerWeekTemp',0) + 1; + my $appCountsPerMonthTemp = ReadingsVal($name,'appCountsPerMonthTemp',0)+ 1; + my $appCountsPerYearTemp = ReadingsVal($name,'appCountsPerYearTemp',0) + 1; + + readingsBeginUpdate($hash); + readingsBulkUpdate ($hash, 'appCountsPerHourTemp', $appCountsPerHourTemp ); + readingsBulkUpdate ($hash, 'appCountsPerWeekTemp', $appCountsPerWeekTemp ); + readingsBulkUpdate ($hash, 'appCountsPerMonthTemp',$appCountsPerMonthTemp ); + readingsBulkUpdate ($hash, 'appCountsPerYearTemp',$appCountsPerYearTemp ); + readingsEndUpdate( $hash, 1 ); + +} + +# -------------------------------------------------- +# task on value change +sub appHC_OnValueChanged($$$) +{ + my ($name,$part0,$part1) = @_; # object name, parameter name, parameter value + $part0='' if (!defined($part0)); + my $hash = $defs{$name}; + return undef if (!defined ($hash)); + + # acquire needed values + my $secs= HourCounter_SecondsOfDay(); + my $pulseTimePerDay = ReadingsVal($name,'pulseTimePerDay',0); + + # calc utilization + $secs= 1 if ($secs==0); # no zero division + my $appUtilizationTempOld = ReadingsVal($name,'appUtilizationTemp',0); + my $appUtilizationTemp = $pulseTimePerDay/$secs*100; + + # calc operating hours + my $appOpHoursPerDayTemp =$pulseTimePerDay/3600; # operating time per Day temporary + + readingsBeginUpdate($hash); + readingsBulkUpdate ($hash, 'appOpHoursPerDayTemp' , $appOpHoursPerDayTemp); + readingsBulkUpdate ($hash, 'appUtilizationTemp' , $appUtilizationTemp ); + readingsBulkUpdate ($hash, 'appUtilizationTempOld' , $appUtilizationTempOld ); + readingsEndUpdate( $hash, 1 ); + +} + + +# -------------------------------------------------- +# central event dispatcher +sub appHCNotify($$$) +{ + my ($name,$part0,$part1) = @_; # object name, parameter name, parameter value + $name = "?" if (!defined($name)); + $part0='' if (!defined($part0)); + my $hash = $defs{$name}; + + return undef if (!defined ($hash)); + + # HourCounter_Log $hash, 2, "Name:$name part0:$part0 part1:$part1"; + + # event dispatcher for delayed execution + if ($part0 eq "value:") # trigger CN.Test value: 1 + { + HourCounter_cmdQueueAdd($hash,"appHC_OnValueChanged q($name),q($part0),q($part1)"); + } + elsif ($part0 eq "countsOverall:") + { + HourCounter_cmdQueueAdd($hash,"appHC_OnCount q($name),q($part0),q($part1)"); + } + elsif ($part0 eq "tickHour:") # trigger CN.Test tickHour: 1 + { + HourCounter_cmdQueueAdd($hash,"appHC_OnHour q($name),q($part0),q($part1)"); + } + elsif ($part0 eq "tickDay:") # trigger CN.Test tickDay: 1 + { + HourCounter_cmdQueueAdd($hash,"appHC_OnDay q($name),q($part0),q($part1)"); + } + elsif ($part0 eq "tickWeek:") + { + HourCounter_cmdQueueAdd($hash,"appHC_OnWeek q($name),q($part0),q($part1)"); + } + elsif ($part0 eq "tickMonth:") + { + HourCounter_cmdQueueAdd($hash,"appHC_OnMonth q($name),q($part0),q($part1)"); + } + elsif ($part0 eq "tickYear:") + { + HourCounter_cmdQueueAdd($hash,"appHC_OnYear q($name),q($part0),q($part1)"); + } + + return ''; + +} + + + + +1; + diff --git a/fhem/docs/commandref_frame.html b/fhem/docs/commandref_frame.html index a3d4ff78f..4ac421779 100644 --- a/fhem/docs/commandref_frame.html +++ b/fhem/docs/commandref_frame.html @@ -110,10 +110,10 @@ HTTPSRV   Heating_Control   holiday   + HourCounter   LightScene   mailcheck   notify   - PID   PRESENCE   PachLog   RSS