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