| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023 |
- # $Id: 98_HourCounter.pm 11307 2016-04-25 08:02:06Z rudolfkoenig $
- ####################################################################################################
- #
- # 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 <http://www.gnu.org/licenses/>.
- #
- #
- # 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
- # 14.11.14 - 1.0.0.5
- # minor fixes for logging in HourCounter_Set: thanks kubuntufan
- # reformating
- # 17.11.14 - 1.0.0.6
- # cyclic calculation of pulse/pause-duration
- # correctly restores counter values after restart
- # 10.12.14 - 1.0.1.0
- # new readings pulseTimeEdge, pauseTimeEdge hold the last pusle*-Increment value
- # all operative readings beside the tick*-readings are updated every cycle now
- # new reading tickChanged is fired, if the value is changed
- # new reading tickUpdated is fired each time the operative readings are updated
- # some bug fixes concerning duration and calc calculations
- # note, that also 99_UtilsHourCounter needs changes
- # 21.12.14 - 1.0.1.1
- # bug: if OFF is not defined, nothing was counted
- # html : check with tidy
- # 24.12.14 - 1.0.1.2
- # bug: if cvent occurs without value change, wrong calculcation of pulseTimeIncrement, pauseTimeIncrement
- ####################################################################################################
- 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.1.2 - 24.12.2014";
- my @HourCounter_cmdQeue = ();
- my $DEBUG = 1;
- ##########################
- 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->{AttrFn} = "HourCounter_Attr";
- $hash->{AttrList} = "disable:0,1 interval:1,2,3,4,5,10,15,20,30,60 " . $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, ($DEBUG) ? 0 : 4, "parameters: @a";
- if ( @a < 3 )
- {
- return "wrong syntax: define <name> HourCounter <regexp_for_ON> [<regexp_for_OFF>]";
- }
- 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 ($@);
- }
- #
- # some inits
- $hash->{VERSION} = $HourCounter_Version;
- $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);
- # wait until alle readings have been restored
- 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", "calc" );
- 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/ ) );
- my $info = "command : " . $cmd;
- $info .= " " . $value if ($needPara);
- HourCounter_Log $hash, 4, $info;
- 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;
- } elsif ( $cmd eq "calc" )
- {
- $doRun = 1;
- } else
- {
- return "unknown command (2): $cmd";
- }
- # perform run
- if ( $doRun && !$hash->{helper}{isFirstRun} )
- {
- $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};
- 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;
- if ( !$hash->{helper}{isFirstRun} )
- {
- HourCounter_Run( $hash->{NAME} );
- }
- }
- }
- ##########################
- sub HourCounter_Attr($$$$)
- {
- my ( $command, $name, $attribute, $value ) = @_;
- my $msg = undef;
- my $hash = $defs{$name};
- if ( $attribute eq "interval" )
- {
- #HourCounter_Log $hash, 0, "cmd:$command name:$name attribute:$attribute";
- if ( !$hash->{helper}{isFirstRun} )
- {
- HourCounter_Run($name);
- }
- }
- return $msg;
- }
- ##########################
- # 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;
- }
- ##########################
- # this either called by timer for cyclic update
- # or it is called by an event (on/off)
- sub HourCounter_Run($)
- {
- # print "xxx TAG A\n" ;
- my ($name) = @_;
- my $hash = $defs{$name};
- # must be of type hourcounter
- return if ( !defined( $hash->{TYPE} ) || $hash->{TYPE} ne 'HourCounter' );
- # timestamps for event-log-file-entries, older than current time
- delete( $hash->{CHANGETIME} );
- # flag for called by event
- my $calledByEvent = $hash->{helper}{calledByEvent};
- # reset flag
- $hash->{helper}{calledByEvent} = '';
- # if call was made by timer, than force value to -1
- my $valuePara = ($calledByEvent) ? $hash->{helper}{value} : -1;
- # initialize changedTimestamp, if it does not exist
- $hash->{helper}{changedTimestamp} = ReadingsTimestamp( $name, "value", TimeNow() )
- if ( !$hash->{helper}{changedTimestamp} );
- # serial date for changed timestamp
- my $sdValue = time_str2num( $hash->{helper}{changedTimestamp} );
- my $sdCurTime = gettimeofday();
- my $isOffDefined = ( $hash->{helper}{OFF_Regexp} ) ? 1 : '';
- # calc time diff
- my $timeIncrement = int( $sdCurTime - $sdValue );
- # wrong time offset in case of summer/winter time
- $timeIncrement = 0 if ( $timeIncrement < 0 );
- # get the old value
- my $valueOld = ReadingsVal( $name, 'value', 0 );
- # variable for reading update
- my $value = undef;
- my $countsPerDay = ReadingsVal( $name, "countsPerDay", 0 );
- my $countsOverall = ReadingsVal( $name, "countsOverall", 0 );
- my $pulseTimeIncrement = ReadingsVal( $name, "pulseTimeIncrement", 0 );
- my $pulseTimePerDay = ReadingsVal( $name, "pulseTimePerDay", 0 );
- my $pulseTimeOverall = ReadingsVal( $name, "pulseTimeOverall", 0 );
- my $pulseTimeEdge = ReadingsVal( $name, "pulseTimeEdge", 0 );
- my $pauseTimeIncrement = ReadingsVal( $name, "pauseTimeIncrement", 0 );
- my $pauseTimePerDay = ReadingsVal( $name, "pauseTimePerDay", 0 );
- my $pauseTimeOverall = ReadingsVal( $name, "pauseTimeOverall", 0 );
- my $pauseTimeEdge = ReadingsVal( $name, "pauseTimeEdge", 0 );
- my $tickUpdated = ReadingsVal( $name, "tickUpdated", 0 ) + 1;
- $tickUpdated = 1 if ( $tickUpdated >= 1000 );
- my $tickChanged = ReadingsVal( $name, "tickChanged", 0 );
- my $tickHour = ReadingsVal( $name, "tickHour", 0 );
- my $tickDay = ReadingsVal( $name, "tickDay", 0 );
- my $tickWeek = ReadingsVal( $name, "tickWeek", 0 );
- my $tickMonth = ReadingsVal( $name, "tickMonth", 0 );
- my $tickYear = ReadingsVal( $name, "tickYear", 0 );
- my $state = '';
- my $sdTickHour = time_str2num( ReadingsTimestamp( $name, "tickHour", TimeNow() ) );
- # serial date for current hour
- my $sdRoundHour = HourCounter_RoundHour($sdCurTime);
- my $sdRoundHourLast = HourCounter_RoundHour($sdTickHour);
- $sdRoundHourLast = $sdRoundHour if ( !$sdRoundHourLast );
- my $isHourChanged = ( $sdRoundHour != $sdRoundHourLast ) || $hash->{helper}{forceHourChange};
- # serial date for current day
- my $sdRoundDayCurTime = HourCounter_RoundDay($sdCurTime);
- my $sdRoundDayValue = HourCounter_RoundDay($sdRoundHourLast);
- my $isDayChanged = ( $sdRoundDayCurTime != $sdRoundDayValue ) || $hash->{helper}{forceDayChange};
- # serial date for current week
- my $sdRoundWeekCurTime = HourCounter_RoundWeek($sdCurTime);
- my $sdRoundWeekValue = HourCounter_RoundWeek($sdRoundHourLast);
- my $isWeekChanged =
- ( $sdRoundWeekCurTime != $sdRoundWeekValue ) || $hash->{helper}{forceWeekChange};
- # serial date for current month
- my $sdRoundMonthCurTime = HourCounter_RoundMonth($sdCurTime);
- my $sdRoundMonthValue = HourCounter_RoundMonth($sdRoundHourLast);
- my $isMonthChanged =
- ( $sdRoundMonthCurTime != $sdRoundMonthValue ) || $hash->{helper}{forceMonthChange};
- # serial date for current year
- my $sdRoundYearCurTime = HourCounter_RoundYear($sdCurTime);
- my $sdRoundYearValue = HourCounter_RoundYear($sdRoundHourLast);
- my $isYearChanged =
- ( $sdRoundYearCurTime != $sdRoundYearValue ) || $hash->{helper}{forceYearChange};
- # loop forever
- while (1)
- {
- # stop if disabled
- last if ( AttrVal( $name, 'disable', '0' ) eq '1' );
- # variables for controlling
- HourCounter_Log $hash, 5, "value:$valuePara changedTimestamp:" . $hash->{helper}{changedTimestamp};
- # ------------ basic init, when first run
- if ( $hash->{helper}{isFirstRun} )
- {
- $hash->{helper}{isFirstRun} = undef;
- $hash->{helper}{sdRoundHourLast} = $sdRoundHourLast;
- # first init after startup
- readingsBeginUpdate($hash);
- readingsBulkUpdate( $hash, 'tickHour', 0 );
- readingsBulkUpdate( $hash, 'tickDay', 0 );
- readingsBulkUpdate( $hash, 'tickWeek', 0 );
- readingsBulkUpdate( $hash, 'tickMonth', 0 );
- readingsBulkUpdate( $hash, 'tickYear', 0 );
- readingsEndUpdate( $hash, 0 );
- # set initial values
- $value = $valueOld; # value als reading anlegen falls nicht vorhanden
- $timeIncrement = 0;
- HourCounter_Log $hash, 0, "first run done countsOverall:" . $countsOverall; #4
- }
- # -------- force clear request
- if ( $hash->{helper}{forceClear} )
- {
- HourCounter_Log $hash, 0, "force clear request";
- readingsSingleUpdate( $hash, 'clearDate', TimeNow(), 1 );
- # reset all counters
- $countsOverall = 0;
- $countsPerDay = 0;
- $pauseTimeIncrement = 0;
- $pauseTimeEdge = 0;
- $pauseTimeOverall = 0;
- $pauseTimePerDay = 0;
- $pulseTimeIncrement = 0;
- $pulseTimeEdge = 0;
- $pulseTimeOverall = 0;
- $pulseTimePerDay = 0;
- $hash->{helper}{forceClear} = '';
- $timeIncrement = 0;
- }
- # -------------- handling of transitions
- my $hasValueChanged = 0;
- if ( ( $isOffDefined && $valuePara >= 0 && $valuePara != $valueOld )
- || ( !$isOffDefined && $calledByEvent ) )
- {
- $hasValueChanged = 1;
- }
- if ($hasValueChanged)
- {
- $value = $valuePara;
- $valueOld = $valuePara;
- # -------------- positive edge
- if ( $valuePara == 1 )
- {
- # handling of counters
- $countsPerDay += 1;
- $countsOverall += 1;
- # handling of pause time
- if ($isOffDefined)
- {
- # calc the rest of puse-time until edge
- $pauseTimeIncrement += $timeIncrement;
- $pauseTimePerDay += $timeIncrement;
- $pauseTimeOverall += $timeIncrement;
- $pulseTimeIncrement = 0;
- $pauseTimeEdge = $pauseTimeIncrement;
- }
- HourCounter_Log $hash, 4, "rising edge; pauseTimeIncr:$pauseTimeIncrement countPerDay:$countsPerDay";
- }
- # ------------ negative edge
- elsif ( $valuePara == 0 )
- {
- # handlich of pulse time
- if ($isOffDefined)
- {
- $pulseTimeIncrement += $timeIncrement;
- $pulseTimePerDay += $timeIncrement;
- $pulseTimeOverall += $timeIncrement;
- $pauseTimeIncrement = 0;
- $pulseTimeEdge = $pulseTimeIncrement;
- }
- HourCounter_Log $hash, 4, "falling edge pulseTimeIncrement:$pulseTimeIncrement";
- }
- }
- # ------------ no value change
- # it is possible to receive an event without change of value (e.g. Max Shutter does it hourly)
- elsif ($isOffDefined)
- {
- if ( $valueOld == 0 )
- {
- $pauseTimeIncrement += $timeIncrement;
- $pauseTimePerDay += $timeIncrement;
- $pauseTimeOverall += $timeIncrement;
- } elsif ( $valueOld == 1 )
- {
- $pulseTimeIncrement += $timeIncrement;
- $pulseTimePerDay += $timeIncrement;
- $pulseTimeOverall += $timeIncrement;
- }
- }
- $hash->{helper}{changedTimestamp} = TimeNow();
- $value = $valueOld;
- $state = $countsPerDay;
- # ---------update readings, if vars defined
- readingsBeginUpdate($hash);
- readingsBulkUpdate( $hash, "countsPerDay", $countsPerDay );
- readingsBulkUpdate( $hash, "countsOverall", $countsOverall );
- if ($isOffDefined)
- {
- readingsBulkUpdate( $hash, "pulseTimeIncrement", $pulseTimeIncrement );
- readingsBulkUpdate( $hash, "pulseTimeEdge", $pulseTimeEdge );
- readingsBulkUpdate( $hash, "pulseTimePerDay", $pulseTimePerDay );
- readingsBulkUpdate( $hash, "pulseTimeOverall", $pulseTimeOverall );
- readingsBulkUpdate( $hash, "pauseTimeIncrement", $pauseTimeIncrement );
- readingsBulkUpdate( $hash, "pauseTimeEdge", $pauseTimeEdge );
- readingsBulkUpdate( $hash, "pauseTimePerDay", $pauseTimePerDay );
- readingsBulkUpdate( $hash, "pauseTimeOverall", $pauseTimeOverall );
- }
- readingsBulkUpdate( $hash, "value", $value );
- readingsBulkUpdate( $hash, 'state', $state );
- readingsBulkUpdate( $hash, 'tickUpdated', $tickUpdated );
- readingsEndUpdate( $hash, 1 );
- # --------------- fire time interval ticks for hour,day,month
- if ($hasValueChanged)
- {
- $tickChanged++;
- $tickChanged = 1 if ( $tickChanged >= 1000 );
- readingsSingleUpdate( $hash, 'tickChanged', $tickChanged, 1 );
- HourCounter_Log $hash, 4, 'tickChanged fired ';
- }
- if ($isHourChanged)
- {
- $tickHour++;
- $tickHour = 1 if ( $tickHour >= 1000 );
- $hash->{helper}{forceHourChange} = '';
- $hash->{helper}{sdRoundHourLast} = $sdRoundHour;
- readingsSingleUpdate( $hash, 'tickHour', $tickHour, 1 );
- HourCounter_Log $hash, 4, "tickHour fired";
- }
- if ($isDayChanged)
- {
- $tickDay++;
- $tickDay = 1 if ( $tickDay >= 1000 );
- $hash->{helper}{forceDayChange} = '';
- readingsSingleUpdate( $hash, 'tickDay', $tickDay, 1 );
- HourCounter_Log $hash, 4, "tickDay fired";
- }
- if ($isWeekChanged)
- {
- $tickWeek++;
- $tickWeek = 1 if ( $tickWeek >= 1000 );
- $hash->{helper}{forceWeekChange} = '';
- readingsSingleUpdate( $hash, 'tickWeek', $tickWeek, 1 );
- HourCounter_Log $hash, 4, "tickWeek fired";
- }
- if ($isMonthChanged)
- {
- $tickMonth++;
- $tickMonth = 1 if ( $tickMonth >= 1000 );
- $hash->{helper}{forceMonthChange} = '';
- readingsSingleUpdate( $hash, 'tickMonth', $tickMonth, 1 );
- HourCounter_Log $hash, 4, "tickMonth fired";
- }
- if ($isYearChanged)
- {
- $tickYear++;
- $tickYear = 1 if ( $tickYear >= 1000 );
- $hash->{helper}{forceYearChange} = '';
- readingsSingleUpdate( $hash, 'tickYear', $tickYear, 1 );
- HourCounter_Log $hash, 4, "tickYear fired";
- }
- # execute command queue
- HourCounter_ExecQueue($hash);
- # day change, so reset day readings
- if ($isDayChanged)
- {
- ### reset all day counters
- readingsBeginUpdate($hash);
- readingsBulkUpdate( $hash, "countsPerDay", 0 );
- readingsBulkUpdate( $hash, "pulseTimePerDay", 0 );
- readingsBulkUpdate( $hash, "pauseTimePerDay", 0 );
- readingsEndUpdate( $hash, 1 );
- HourCounter_Log $hash, 4, "reset day counters";
- }
- last;
- }
- # ------------ calculate seconds until next hour starts
- my $interval = AttrVal( $name, 'interval', '60' );
- my $actTime = int( gettimeofday() );
- my ( $sec, $min, $hour ) = localtime($actTime);
- # round to next interval
- my $seconds = $interval * 60;
- my $nextHourTime = int( ( $actTime + $seconds ) / $seconds ) * $seconds;
- # calc diff in seconds
- 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
- <div>
- <a name="HourCounter" id="HourCounter"></a>
- <h3>HourCounter</h3>
- <div>
- <a name="HourCounterdefine" id="HourCounterdefine"></a> <b>Define</b>
- <div>
- <br />
- <code>define <name> HourCounter <pattern_for_ON> [<pattern_for_OFF>]</code><br />
- <br />
- Hourcounter can detect both the activiy-time and the inactivity-time of a property.<br />
- The "pattern_for_ON" identifies the events, that signal the activity of the desired property.<br />
- The "pattern_for_OFF" identifies the events, that signal the inactivity of the desired property.<br />
- <br />
- If "pattern_for_OFF" is not defined, any matching event of "patter_for_ON" will be counted.<br />
- Otherwise only the rising edges of "pattern_for_ON" will be counted.<br />
- This means a "pattern_for_OFF"-event must be detected before a "pattern_for_ON"-event is accepted.<br />
- <br />
- "pattern_for_ON" and "pattern_for_OFF" must be formed using the following structure:<br />
- <br />
- <code>device:[regexp]</code><br />
- <br />
- The forming-rules are the same as for the notify-command.<br />
- <br />
- <b>Example:</b><br />
- <br />
- <div>
- <code>define BurnerCounter HourCounter SHUTTER_TEST:on SHUTTER_TEST:off</code>
- </div>
- </div><br />
- <a name="HourCounterset" id="HourCounterset"></a> <b>Set-Commands</b>
- <div>
- <br />
- <code>set <name> calc</code><br />
- <br />
- <div>
- starts the calculation of pulse/pause-time.<br />
- </div><br />
- <br />
- <code>set <name> clear</code><br />
- <br />
- <div>
- clears the readings countsPerDay, countsOverall,pauseTimeIncrement, pauseTimePerDay, pauseTimeOverall,
- pulseTimeIncrement, pulseTimePerDay, pulseTimeOverall by setting to 0.<br />
- The reading clearDate is set to the current Date/Time.
- </div><br />
- <br />
- <code>set <name> countsOverall <value></code><br />
- <br />
- <div>
- Sets the reading countsOverall to the given value.This is the total-counter.
- </div><br />
- <br />
- <code>set <name> countsPerDay <value></code><br />
- <br />
- <div>
- Sets the reading countsPerDay to the given value. This reading will automatically be set to 0, after change
- of day.
- </div><br />
- <br />
- <code>set <name> pauseTimeIncrement <value></code><br />
- <br />
- <div>
- Sets the reading pauseTimeIncrement to the given value.<br />
- This reading in seconds is automatically set after a rising edge.
- </div><br />
- <br />
- <code>set <name> pauseTimeEdge <value></code><br />
- <br />
- <div>
- Sets the reading pauseTimeEdge to the given value.<br />
- This reading in seconds is automatically set after a rising edge.
- </div><br />
- <br />
- <code>set <name> pauseTimeOverall <value></code><br />
- <br />
- <div>
- Sets the reading pauseTimeOverall to the given value.<br />
- This reading in seconds is automatically adjusted after a change of pauseTimeIncrement.
- </div><br />
- <br />
- <code>set <name> pauseTimePerDay <value></code><br />
- <br />
- <div>
- Sets the reading pauseTimePerDay to the given value.<br />
- This reading in seconds is automatically adjusted after a change of pauseTimeIncrement and set to 0 after
- change of day.
- </div><br />
- <br />
- <code>set <name> pulseTimeIncrement <value></code><br />
- <br />
- <div>
- Sets the reading pulseTimeIncrement to the given value.<br />
- This reading in seconds is automatically set after a falling edge of the property.
- </div><br />
- <br />
- <code>set <name> pulseTimeEdge <value></code><br />
- <br />
- <div>
- Sets the reading pulseTimeEdge to the given value.<br />
- This reading in seconds is automatically set after a rising edge.
- </div><br />
- <br />
- <code>set <name> pulseTimeOverall <value></code><br />
- <br />
- <div>
- Sets the reading pulseTimeOverall to the given value.<br />
- This reading in seconds is automatically adjusted after a change of pulseTimeIncrement.
- </div><br />
- <br />
- <code>set <name> pulseTimePerDay <value></code><br />
- <br />
- <div>
- Sets the reading pulseTimePerDay to the given value.<br />
- This reading in seconds is automatically adjusted after a change of pulseTimeIncrement and set to 0 after
- change of day.
- </div><br />
- <br />
- <code>set <name> forceHourChange</code><br />
- <br />
- <div>
- This modifies the reading tickHour, which is automatically modified after change of hour.
- </div><br />
- <br />
- <code>set <name> forceDayChange</code><br />
- <br />
- <div>
- This modifies the reading tickDay, which is automatically modified after change of day.
- </div><br />
- <br />
- <code>set <name> forceWeekChange</code><br />
- <br />
- <div>
- This modifies the reading tickWeek, which is automatically modified after change of week.
- </div><br />
- <br />
- <code>set <name> forceMonthChange</code><br />
- <br />
- <div>
- This modifies the reading tickMonth, which is automatically modified after change of month.
- </div><br />
- <br />
- <code>set <name> forceYearChange</code><br />
- <br />
- <div>
- This modifies the reading tickYear, which is automatically modified after change of year.
- </div><br />
- <br />
- <code>set <name> app.* <value></code><br />
- <br />
- <div>
- Any reading with the leading term "app", can be modified.<br />
- This can be useful for user-readings.
- </div><br />
- </div><br />
- <a name="HourCounterget" id="HourCounterget"></a> <b>Get-Commands</b><br />
- <div>
- <br />
- <code>get <name> version</code><br />
- <br />
- <div>
- Get the current version of the module.
- </div><br />
- </div><br />
- <a name="HourCounterattr" id="HourCounterattr"></a> <b>Attributes</b>
- <br />
- <ul>
- <li><p><b>interval</b><br />
- the update interval for pulse/pause-time in minutes [default 60]</p></li>
- <li><p><a href="#readingFnAttributes">readingFnAttributes</a></p></li>
- </ul>
- <b>Additional information</b>
- <br />
- <ul>
- <li><p><a href="http://forum.fhem.de/index.php/topic,12216.0.html">Discussion in FHEM forum</a></p></li>
- <li><p><a href="http://www.fhemwiki.de/wiki/HourCounter">WIKI information in FHEM Wiki</a></p></li>
- <li><p>The file 99_UtilsHourCounter.pm is a reference implementation for user defined extensions.<br />
- It shows how to create sum values for hours,days, weeks, months and years.<br />
- This file is located in the sub-folder contrib. For further information take a look to FHEM Wiki.</p></li>
- </ul>
- </div>
- </div>
- =end html
- =cut
|