2020-03-25 14:42:42 +01:00
##########################################################################
# $Id: 98_RandomTimer.pm 21449 2020-03-19 11:07:03Z Beta-User $
#
# copyright ###################################################################
#
# 98_RandomTimer.pm
#
# written by Dietmar Ortmann
# Maintained by Beta-User since 11-2019
#
# 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/>.
# packages ####################################################################
2020-03-25 15:33:08 +01:00
## unserer packagename
package FHEM::RandomTimer ;
use strict ;
use warnings ;
no if $] >= 5.017011 , warnings = > 'experimental::smartmatch' ;
use Time::HiRes qw( gettimeofday ) ;
use Time::Local 'timelocal_nocheck' ;
2020-03-25 16:49:54 +01:00
use GPUtils qw( GP_Import GP_Export ) ;
2020-03-25 15:33:08 +01:00
## Import der FHEM Funktionen
#-- Run before package compilation
BEGIN {
# Import from main context
GP_Import (
qw(
readingsSingleUpdate
readingsBulkUpdate
readingsBeginUpdate
readingsEndUpdate
defs
modules
Log
Log3
attr
readingFnAttributes
AttrVal
ReadingsVal
2020-03-25 16:49:54 +01:00
featurelevel
Value
FmtDateTime
strftime
max
stacktrace
2020-03-26 08:19:04 +01:00
GetTimeSpec
2020-03-26 21:01:43 +01:00
AnalyzeCommandChain
2020-03-26 08:19:04 +01:00
SemicolonEscape )
2020-03-25 15:33:08 +01:00
) ;
}
#-- Export to main context with different name
GP_Export (
qw(
Initialize
Exec
)
) ;
2020-03-25 14:42:42 +01:00
# initialize ##################################################################
2020-03-25 15:33:08 +01:00
sub Initialize {
my ( $ hash ) = @ _ ;
$ hash - > { DefFn } = 'FHEM::RandomTimer::Define' ;
$ hash - > { UndefFn } = 'FHEM::RandomTimer::Undef' ;
$ hash - > { SetFn } = 'FHEM::RandomTimer::Set' ;
$ hash - > { AttrFn } = 'FHEM::RandomTimer::Attr' ;
$ hash - > { AttrList } =
'onCmd offCmd switchmode disable:0,1 disableCond disableCondCmd:none,offCmd,onCmd offState '
. 'runonce:0,1 keepDeviceAlive:0,1 forceStoptimeSameDay:0,1 '
. $ readingFnAttributes ;
2020-03-25 14:42:42 +01:00
}
# regular Fn ##################################################################
2020-03-25 15:33:08 +01:00
sub Define {
my ( $ hash , $ def ) = @ _ ;
2020-03-25 16:49:54 +01:00
main:: RemoveInternalTimer ( $ hash ) ;
2020-03-25 15:33:08 +01:00
my ( $ name , $ type , $ timespec_start , $ device , $ timespec_stop , $ timeToSwitch ,
$ variation )
= split ( "[ \t][ \t]*" , $ def ) ;
return
"wrong syntax: define <name> RandomTimer <timespec_start> <device> <timespec_stop> <timeToSwitch> [<variations>]"
if ( ! defined $ timeToSwitch ) ;
return
"Wrong timespec_start <$timespec_start>, use \"[+][*]<time or func>\""
if ( $ timespec_start !~ m/^(\+)?(\*)?(.*)$/i ) ;
my ( $ rel , $ rep , $ tspec ) = ( $ 1 , $ 2 , $ 3 ) ;
my ( $ err , $ hr , $ min , $ sec , $ fn ) = GetTimeSpec ( $ tspec ) ;
return $ err if ( $ err ) ;
$ rel = "" if ( ! defined ( $ rel ) ) ;
$ rep = "" if ( ! defined ( $ rep ) ) ;
return "Wrong timespec_stop <$timespec_stop>, use \"[+][*]<time or func>\""
if ( $ timespec_stop !~ m/^(\+)?(\*)?(.*)$/i ) ;
my ( $ srel , $ srep , $ stspec ) = ( $ 1 , $ 2 , $ 3 ) ;
my ( $ e , $ h , $ m , $ s , $ f ) = GetTimeSpec ( $ stspec ) ;
return $ e if ( $ e ) ;
return "invalid timeToSwitch <$timeToSwitch>, use 9999"
if ( ! ( $ timeToSwitch =~ m/^[0-9]{2,4}$/i ) ) ;
my ( $ varDuration , $ varStart ) ;
$ varDuration = 0 ;
$ varStart = 0 ;
if ( defined $ variation ) {
$ variation =~ /^([\d]+)/ ? $ varDuration = $ 1 : undef ;
$ variation =~ /[:]([\d]+)/ ? $ varStart = $ 1 : undef ;
}
2020-03-25 14:42:42 +01:00
2020-03-25 15:33:08 +01:00
SetSwitchmode ( $ hash , "800/200" )
if ( ! defined $ hash - > { helper } { SWITCHMODE } ) ;
$ hash - > { NAME } = $ name ;
$ hash - > { DEVICE } = $ device ;
$ hash - > { helper } { TIMESPEC_START } = $ timespec_start ;
$ hash - > { helper } { TIMESPEC_STOP } = $ timespec_stop ;
$ hash - > { helper } { TIMETOSWITCH } = $ timeToSwitch ;
$ hash - > { helper } { REP } = $ rep ;
$ hash - > { helper } { REL } = $ rel ;
$ hash - > { helper } { VAR_DURATION } = $ varDuration ;
$ hash - > { helper } { VAR_START } = $ varStart ;
$ hash - > { helper } { S_REL } = $ srel ;
$ hash - > { helper } { S_REL } = $ srel ;
$ hash - > { COMMAND } = Value ( $ hash - > { DEVICE } ) if ( $ featurelevel < 6.1 ) ;
if ( $ featurelevel > 6.0 ) {
$ hash - > { COMMAND } = ReadingsVal ( $ hash - > { DEVICE } , "state" , undef ) ;
$ hash - > { helper } { offRegex } = "off" ;
$ hash - > { helper } { offReading } = "state" ;
}
2020-03-25 14:42:42 +01:00
2020-03-25 15:33:08 +01:00
#$attr{$name}{verbose} = 4;
2020-03-25 14:42:42 +01:00
2020-03-25 15:33:08 +01:00
readingsSingleUpdate ( $ hash , "TimeToSwitch" , $ hash - > { helper } { TIMETOSWITCH } ,
1 ) ;
2020-03-25 14:42:42 +01:00
2020-03-26 08:26:44 +01:00
RemoveInternalTimer ( "SetTimer" , $ hash ) ;
2020-03-25 15:33:08 +01:00
InternalTimer ( "SetTimer" , time ( ) , "FHEM::RandomTimer::SetTimer" , $ hash ,
0 ) ;
2020-03-25 14:42:42 +01:00
2020-03-25 15:33:08 +01:00
return undef ;
}
2020-03-25 14:42:42 +01:00
2020-03-25 15:33:08 +01:00
sub Undef {
2020-03-25 14:42:42 +01:00
2020-03-25 15:33:08 +01:00
my ( $ hash , $ arg ) = @ _ ;
2020-03-25 14:42:42 +01:00
2020-03-26 08:26:44 +01:00
RemoveInternalTimer ( "SetTimer" , $ hash ) ;
RemoveInternalTimer ( "Exec" , $ hash ) ;
2020-03-25 15:33:08 +01:00
delete $ modules { RandomTimer } { defptr } { $ hash - > { NAME } } ;
return undef ;
2020-03-25 14:42:42 +01:00
}
2020-03-25 15:33:08 +01:00
sub Attr {
my ( $ cmd , $ name , $ attrName , $ attrVal ) = @ _ ;
2020-03-25 14:42:42 +01:00
2020-03-25 15:33:08 +01:00
my $ hash = $ defs { $ name } ;
if ( $ attrName ~ ~ [ "switchmode" ] ) {
SetSwitchmode ( $ hash , $ attrVal ) ;
}
2020-03-25 14:42:42 +01:00
2020-03-25 15:33:08 +01:00
if ( $ attrName ~ ~ [ "disable" , "disableCond" ] ) {
2020-03-25 14:42:42 +01:00
2020-03-25 15:33:08 +01:00
# Schaltung vorziehen, damit bei einem disable abgeschaltet wird.
2020-03-26 08:26:44 +01:00
RemoveInternalTimer ( "Exec" , $ hash ) ;
2020-03-25 15:33:08 +01:00
InternalTimer ( "Exec" , time ( ) + 1 , "FHEM::RandomTimer::Exec" , $ hash ,
0 ) ;
2020-03-25 14:42:42 +01:00
}
2020-03-25 15:33:08 +01:00
if ( $ attrName ~ ~ [ "offState" ] ) {
my @ offState = split ( ' ' , $ attrVal ) ;
$ hash - > { helper } { offRegex } = $ offState [ 0 ] ;
$ hash - > { helper } { offReading } = $ offState [ 1 ] // "state" ;
}
return undef ;
2020-03-25 14:42:42 +01:00
}
2020-03-25 15:33:08 +01:00
sub Set {
my ( $ hash , @ a ) = @ _ ;
return "no set value specified" if ( int ( @ a ) < 2 ) ;
return "Unknown argument $a[1], choose one of execNow:noArg"
if ( $ a [ 1 ] eq "?" ) ;
my $ name = shift @ a ;
my $ v = join ( " " , @ a ) ;
if ( $ v eq "execNow" ) {
Log3 ( $ hash , 3 , "[$name] set $name $v" ) ;
if ( AttrVal ( $ name , "disable" , 0 ) ) {
Log3 ( $ hash , 3 , "[$name] is disabled, set execNow not possible" ) ;
}
else {
2020-03-26 08:26:44 +01:00
RemoveInternalTimer ( "Exec" , $ hash ) ;
2020-03-25 15:33:08 +01:00
InternalTimer ( "Exec" , time ( ) + 1 ,
"FHEM::RandomTimer::Exec" , $ hash , 0 ) ;
}
}
return undef ;
}
2020-03-25 14:42:42 +01:00
# module Fn ###################################################################
2020-03-25 15:33:08 +01:00
sub AddDays {
my ( $ now , $ days ) = @ _ ;
2020-03-25 14:42:42 +01:00
2020-03-25 15:33:08 +01:00
my @ jetzt_arr = localtime ( $ now ) ;
$ jetzt_arr [ 3 ] += $ days ;
my $ next = timelocal_nocheck ( @ jetzt_arr ) ;
return $ next ;
2020-03-25 14:42:42 +01:00
}
2020-03-25 15:33:08 +01:00
sub Device_switch {
my ( $ hash ) = @ _ ;
my $ command = "set @ $hash->{COMMAND}" ;
if ( $ hash - > { COMMAND } eq "on" ) {
$ command = AttrVal ( $ hash - > { NAME } , "onCmd" , $ command ) ;
}
else {
$ command = AttrVal ( $ hash - > { NAME } , "offCmd" , $ command ) ;
}
$ command =~ s/@/$hash->{DEVICE}/g ;
$ command = SemicolonEscape ( $ command ) ;
readingsSingleUpdate ( $ hash , 'LastCommand' , $ command , 1 ) ;
Log3 $ hash , 4 , "[" . $ hash - > { NAME } . "]" . " command: $command" ;
my $ ret = AnalyzeCommandChain ( undef , $ command ) ;
Log3 ( $ hash , 3 , "[$hash->{NAME}] ERROR: " . $ ret . " SENDING " . $ command )
if ( $ ret ) ;
#Log3 $hash, 3, "[$hash->{NAME}] Value($hash->{COMMAND})=".Value($hash->{DEVICE});
#$hash->{"$hash->{COMMAND}Value"} = Value($hash->{DEVICE});
2020-03-25 14:42:42 +01:00
}
2020-03-25 15:33:08 +01:00
sub Device_toggle {
2020-03-25 14:42:42 +01:00
my ( $ hash ) = @ _ ;
my $ name = $ hash - > { NAME } ;
2020-03-25 15:33:08 +01:00
#my $attrOffState = AttrVal($name,"offState",undef);
my $ status = Value ( $ hash - > { DEVICE } ) ;
if ( defined $ hash - > { helper } { offRegex } ) {
$ status =
ReadingsVal ( $ hash - > { DEVICE } , $ hash - > { helper } { offReading } , "off" ) ;
my $ attrOffState = $ hash - > { helper } { offRegex } ;
$ status = $ status =~ /^$attrOffState$/ ? "off" : lc ( $ status ) ;
$ status = $ status =~ /off/ ? "off" : "on" ;
2020-03-25 14:42:42 +01:00
}
2020-03-25 15:33:08 +01:00
if ( $ status ne "on" && $ status ne "off" ) {
if ( $ hash - > { helper } { offRegex } ) {
Log3 $ hash , 3 ,
"[$name] result of function ReadingsVal($hash->{DEVICE},\"<offReading>\",undef) must be 'on' or 'off' or set attribute offState accordingly" ;
}
else {
Log3 $ hash , 3 ,
"[$name] result of function Value($hash->{DEVICE}) must be 'on' or 'off'" ;
}
2020-03-25 14:42:42 +01:00
}
2020-03-25 15:33:08 +01:00
my $ sigma =
( $ status eq "on" )
? $ hash - > { helper } { SIGMAWHENON }
: $ hash - > { helper } { SIGMAWHENOFF } ;
2020-03-25 14:42:42 +01:00
2020-03-25 15:33:08 +01:00
my $ zufall = int ( rand ( 1000 ) ) ;
Log3 $ hash , 4 ,
"[$name] IstZustand:$status sigmaWhen-$status:$sigma random:$zufall<$sigma=>"
. ( ( $ zufall < $ sigma ) ? "true" : "false" ) ;
2020-03-25 14:42:42 +01:00
2020-03-25 15:33:08 +01:00
if ( $ zufall < $ sigma ) {
$ hash - > { COMMAND } = ( $ status eq "on" ) ? "off" : "on" ;
Device_switch ( $ hash ) ;
2020-03-25 14:42:42 +01:00
}
}
2020-03-25 15:33:08 +01:00
sub DisableDown {
my ( $ hash ) = @ _ ;
my $ disableCondCmd = AttrVal ( $ hash - > { NAME } , "disableCondCmd" , 0 ) ;
if ( $ disableCondCmd ne "none" ) {
Log3 $ hash , 4 ,
"["
. $ hash - > { NAME } . "]"
. " setting requested disableCondCmd on $hash->{DEVICE}: " ;
$ hash - > { COMMAND } =
AttrVal ( $ hash - > { NAME } , "disableCondCmd" , 0 ) eq "onCmd"
? "on"
: "off" ;
Device_switch ( $ hash ) ;
}
else {
Log3 $ hash , 4 ,
"["
. $ hash - > { NAME } . "]"
. " no action requested on $hash->{DEVICE}: " ;
}
2020-03-25 14:42:42 +01:00
}
2020-03-25 15:33:08 +01:00
sub Down {
my ( $ hash ) = @ _ ;
Log3 $ hash , 4 ,
"["
. $ hash - > { NAME } . "]"
. " setting requested keepDeviceAlive on $hash->{DEVICE}: " ;
$ hash - > { COMMAND } =
AttrVal ( $ hash - > { NAME } , "keepDeviceAlive" , 0 ) ? "on" : "off" ;
Device_switch ( $ hash ) ;
2020-03-25 14:42:42 +01:00
}
2020-03-25 15:33:08 +01:00
sub Exec {
my ( $ myHash ) = @ _ ;
my $ hash = GetHashIndirekt ( $ myHash , ( caller ( 0 ) ) [ 3 ] ) ;
return if ( ! defined ( $ hash ) ) ;
my $ now = time ( ) ;
# Wenn aktiv aber disabled, dann timer abschalten, Meldung ausgeben.
my $ active = IsAktive ( $ hash ) ;
my $ disabled = IsDisabled ( $ hash ) ;
my $ stopTimeReached = StopTimeReached ( $ hash ) ;
if ( $ active ) {
# wenn temporär ausgeschaltet
if ( $ disabled ) {
Log3 $ hash , 3 ,
"["
. $ hash - > { NAME } . "]"
. " disabled before stop-time , ending RandomTimer on $hash->{DEVICE}: "
. strftime ( "%H:%M:%S(%d)" ,
localtime ( $ hash - > { helper } { startTime } ) )
. " - "
. strftime ( "%H:%M:%S(%d)" ,
localtime ( $ hash - > { helper } { stopTime } ) ) ;
DisableDown ( $ hash ) ;
SetActive ( $ hash , 0 ) ;
SetState ( $ hash ) ;
}
# Wenn aktiv und Abschaltzeit erreicht, dann Gerät ausschalten, Meldung ausgeben und Timer schließen
if ( $ stopTimeReached ) {
Log3 $ hash , 3 ,
"["
. $ hash - > { NAME } . "]"
. " stop-time reached, ending RandomTimer on $hash->{DEVICE}: "
. strftime ( "%H:%M:%S(%d)" ,
localtime ( $ hash - > { helper } { startTime } ) )
. " - "
. strftime ( "%H:%M:%S(%d)" ,
localtime ( $ hash - > { helper } { stopTime } ) ) ;
Down ( $ hash ) ;
SetActive ( $ hash , 0 ) ;
if ( AttrVal ( $ hash - > { NAME } , "runonce" , - 1 ) eq 1 ) {
Log 3 , "[" . $ hash - > { NAME } . "]" . "runonceMode" ;
fhem ( "delete $hash->{NAME}" ) ;
}
SetState ( $ hash ) ;
return ;
}
}
else { # !active
if ( $ disabled ) {
Log3 $ hash , 4 ,
"["
. $ hash - > { NAME }
. "] RandomTimer on $hash->{DEVICE} timer disabled - no switch" ;
SetState ( $ hash ) ;
SetActive ( $ hash , 0 ) ;
}
if ( $ stopTimeReached ) {
Log3 $ hash , 4 ,
"["
. $ hash - > { NAME } . "]"
. " definition RandomTimer on $hash->{DEVICE}: "
. strftime ( "%H:%M:%S(%d)" ,
localtime ( $ hash - > { helper } { startTime } ) )
. " - "
. strftime ( "%H:%M:%S(%d)" ,
localtime ( $ hash - > { helper } { stopTime } ) ) ;
SetState ( $ hash ) ;
SetActive ( $ hash , 0 ) ;
return ;
}
if ( ! $ disabled ) {
if ( $ now > $ hash - > { helper } { startTime }
&& $ now < $ hash - > { helper } { stopTime } )
{
Log3 $ hash , 3 ,
"["
. $ hash - > { NAME } . "]"
. " starting RandomTimer on $hash->{DEVICE}: "
. strftime ( "%H:%M:%S(%d)" ,
localtime ( $ hash - > { helper } { startTime } ) )
. " - "
. strftime ( "%H:%M:%S(%d)" ,
localtime ( $ hash - > { helper } { stopTime } ) ) ;
SetActive ( $ hash , 1 ) ;
}
}
}
SetState ( $ hash ) ;
if ( $ now > $ hash - > { helper } { startTime } && $ now < $ hash - > { helper } { stopTime } )
{
Device_toggle ( $ hash ) if ( ! $ disabled ) ;
}
2020-03-25 14:42:42 +01:00
2020-03-25 15:33:08 +01:00
my $ nextSwitch = time ( ) + GetSecsToNextAbschaltTest ( $ hash ) ;
2020-03-26 08:26:44 +01:00
RemoveInternalTimer ( "Exec" , $ hash ) ;
2020-03-25 15:33:08 +01:00
$ hash - > { helper } { NEXT_CHECK } =
strftime ( "%d.%m.%Y %H:%M:%S" , localtime ( $ nextSwitch ) ) ;
InternalTimer ( "Exec" , $ nextSwitch , "FHEM::RandomTimer::Exec" , $ hash , 0 ) ;
2020-03-25 14:42:42 +01:00
}
2020-03-25 15:33:08 +01:00
sub GetSecsToNextAbschaltTest {
my ( $ hash ) = @ _ ;
my $ intervall = $ hash - > { helper } { TIMETOSWITCH } ;
2020-03-25 14:42:42 +01:00
my $ varDuration = $ hash - > { helper } { VAR_DURATION } ;
2020-03-25 15:33:08 +01:00
my $ nextSecs = $ intervall + int ( rand ( $ varDuration ) ) ;
2020-03-25 14:42:42 +01:00
unless ( $ varDuration ) {
2020-03-25 15:33:08 +01:00
my $ proz = 10 ;
my $ delta = $ intervall * $ proz / 100 ;
$ nextSecs = $ intervall - $ delta / 2 + int ( rand ( $ delta ) ) ;
2020-03-25 14:42:42 +01:00
}
return $ nextSecs ;
}
2020-03-25 15:33:08 +01:00
sub IsAktive {
my ( $ hash ) = @ _ ;
return defined ( $ hash - > { helper } { active } ) ? $ hash - > { helper } { active } : 0 ;
2020-03-25 14:42:42 +01:00
}
2020-03-25 15:33:08 +01:00
sub IsDisabled {
my ( $ hash ) = @ _ ;
my $ disable = AttrVal ( $ hash - > { NAME } , "disable" , 0 ) ;
return $ disable if ( $ disable ) ;
my $ disableCond = AttrVal ( $ hash - > { NAME } , "disableCond" , "nf" ) ;
if ( $ disableCond eq "nf" ) {
return 0 ;
}
else {
$ disable = eval ( $ disableCond ) ;
if ( $@ ) {
$@ =~ s/\n/ /g ;
Log3 ( $ hash , 3 ,
"[$hash->{NAME}] ERROR: "
. $@
. " EVALUATING "
. $ disableCond ) ;
}
return $ disable ;
}
2020-03-25 14:42:42 +01:00
}
2020-03-25 15:33:08 +01:00
sub SchaltZeitenErmitteln {
my ( $ hash , $ now ) = @ _ ;
2020-03-25 14:42:42 +01:00
2020-03-25 15:33:08 +01:00
StartZeitErmitteln ( $ hash , $ now ) ;
StopZeitErmitteln ( $ hash , $ now ) ;
readingsBeginUpdate ( $ hash ) ;
2020-03-25 14:42:42 +01:00
# readingsBulkUpdate ($hash, "Startzeit", FmtDateTime($hash->{helper}{startTime}));
# readingsBulkUpdate ($hash, "Stoppzeit", FmtDateTime($hash->{helper}{stopTime}));
2020-03-25 15:33:08 +01:00
readingsBulkUpdate ( $ hash , "StartTime" ,
FmtDateTime ( $ hash - > { helper } { startTime } ) ) ;
readingsBulkUpdate ( $ hash , "StopTime" ,
FmtDateTime ( $ hash - > { helper } { stopTime } ) ) ;
readingsEndUpdate ( $ hash , defined ( $ hash - > { LOCAL } ? 0 : 1 ) ) ;
2020-03-25 14:42:42 +01:00
}
2020-03-25 15:33:08 +01:00
sub SetActive {
my ( $ hash , $ value ) = @ _ ;
$ hash - > { helper } { active } = $ value ;
my $ trigger = ( IsDisabled ( $ hash ) ) ? 0 : 1 ;
readingsSingleUpdate ( $ hash , "active" , $ value , $ trigger ) ;
2020-03-25 14:42:42 +01:00
}
2020-03-25 15:33:08 +01:00
sub SetState {
my ( $ hash ) = @ _ ;
2020-03-25 14:42:42 +01:00
2020-03-25 15:33:08 +01:00
if ( IsDisabled ( $ hash ) ) {
my $ dotrigger =
ReadingsVal ( $ hash - > { NAME } , "state" , "none" ) ne "disabled" ? 1 : 0 ;
readingsSingleUpdate ( $ hash , "state" , "disabled" , $ dotrigger ) ;
}
else {
my $ state = $ hash - > { helper } { active } ? "on" : "off" ;
readingsSingleUpdate ( $ hash , "state" , $ state , 1 ) ;
}
2020-03-25 14:42:42 +01:00
}
2020-03-25 15:33:08 +01:00
sub SetSwitchmode {
2020-03-25 14:42:42 +01:00
2020-03-25 15:33:08 +01:00
my ( $ hash , $ attrVal ) = @ _ ;
my $ mod = "[" . $ hash - > { NAME } . "] " ;
2020-03-25 14:42:42 +01:00
2020-03-25 15:33:08 +01:00
if ( ! ( $ attrVal =~ m/^([0-9]{1,3})\/([0-9]{1,3})$/i ) ) {
Log3 undef , 3 , $ mod . "invalid switchMode <$attrVal>, use 999/999" ;
}
else {
my ( $ sigmaWhenOff , $ sigmaWhenOn ) = ( $ 1 , $ 2 ) ;
$ hash - > { helper } { SWITCHMODE } = $ attrVal ;
$ hash - > { helper } { SIGMAWHENON } = $ sigmaWhenOn ;
$ hash - > { helper } { SIGMAWHENOFF } = $ sigmaWhenOff ;
$ attr { $ hash - > { NAME } } { switchmode } = $ attrVal ;
}
2020-03-25 14:42:42 +01:00
}
2020-03-25 15:33:08 +01:00
sub SetTimer {
my ( $ myHash ) = @ _ ;
my $ hash = GetHashIndirekt ( $ myHash , ( caller ( 0 ) ) [ 3 ] ) ;
return if ( ! defined ( $ hash ) ) ;
my $ now = time ( ) ;
my ( $ sec , $ min , $ hour , $ mday , $ mon , $ year , $ wday , $ yday , $ isdst ) =
localtime ( $ now ) ;
SetActive ( $ hash , 0 ) ;
SchaltZeitenErmitteln ( $ hash , $ now ) ;
SetState ( $ hash ) ;
Log3 $ hash , 4 ,
"["
. $ hash - > { NAME } . "]"
. " timings RandomTimer on $hash->{DEVICE}: "
. strftime ( "%H:%M:%S(%d)" , localtime ( $ hash - > { helper } { startTime } ) )
. " - "
. strftime ( "%H:%M:%S(%d)" , localtime ( $ hash - > { helper } { stopTime } ) ) ;
my $ secToMidnight = 24 * 3600 - ( 3600 * $ hour + 60 * $ min + $ sec ) ;
my $ setExecTime = max ( $ now , $ hash - > { helper } { startTime } ) ;
2020-03-26 08:26:44 +01:00
RemoveInternalTimer ( "Exec" , $ hash ) ;
2020-03-25 15:33:08 +01:00
InternalTimer ( "Exec" , $ setExecTime , "FHEM::RandomTimer::Exec" , $ hash , 0 ) ;
if ( $ hash - > { helper } { REP } gt "" ) {
my $ setTimerTime =
max ( $ now + $ secToMidnight + 15 , $ hash - > { helper } { stopTime } ) +
$ hash - > { helper } { TIMETOSWITCH } + 15 ;
2020-03-26 08:26:44 +01:00
RemoveInternalTimer ( "SetTimer" , $ hash ) ;
2020-03-25 15:33:08 +01:00
InternalTimer ( "SetTimer" , $ setTimerTime ,
"FHEM::RandomTimer::SetTimer" , $ hash , 0 ) ;
}
}
2020-03-25 14:42:42 +01:00
2020-03-25 15:33:08 +01:00
sub StartZeitErmitteln {
my ( $ hash , $ now ) = @ _ ;
2020-03-25 14:42:42 +01:00
2020-03-25 15:33:08 +01:00
my $ timespec_start = $ hash - > { helper } { TIMESPEC_START } ;
2020-03-25 14:42:42 +01:00
2020-03-25 15:33:08 +01:00
return
"Wrong timespec_start <$timespec_start>, use \"[+][*]<time or func>\""
if ( $ timespec_start !~ m/^(\+)?(\*)?(.*)$/i ) ;
my ( $ rel , $ rep , $ tspec ) = ( $ 1 , $ 2 , $ 3 ) ;
2020-03-25 14:42:42 +01:00
2020-03-25 15:33:08 +01:00
my ( $ err , $ hour , $ min , $ sec , $ fn ) = GetTimeSpec ( $ tspec ) ;
return $ err if ( $ err ) ;
2020-03-25 14:42:42 +01:00
2020-03-25 15:33:08 +01:00
my $ startTime ;
if ( $ rel ) {
$ startTime = $ now + 3600 * $ hour + 60 * $ min + $ sec ;
}
else {
$ startTime = ZeitBerechnen ( $ now , $ hour , $ min , $ sec ) ;
}
my $ varStart = $ hash - > { helper } { VAR_START } ;
$ startTime += int ( rand ( $ varStart ) ) ;
2020-03-25 14:42:42 +01:00
2020-03-25 15:33:08 +01:00
$ hash - > { helper } { startTime } = $ startTime ;
$ hash - > { helper } { STARTTIME } =
strftime ( "%d.%m.%Y %H:%M:%S" , localtime ( $ startTime ) ) ;
2020-03-25 14:42:42 +01:00
}
2020-03-25 15:33:08 +01:00
sub StopTimeReached {
my ( $ hash ) = @ _ ;
return ( time ( ) > $ hash - > { helper } { stopTime } ) ;
2020-03-25 14:42:42 +01:00
}
2020-03-25 15:33:08 +01:00
sub StopZeitErmitteln {
my ( $ hash , $ now ) = @ _ ;
2020-03-25 14:42:42 +01:00
2020-03-25 15:33:08 +01:00
my $ timespec_stop = $ hash - > { helper } { TIMESPEC_STOP } ;
2020-03-25 14:42:42 +01:00
2020-03-25 15:33:08 +01:00
return "Wrong timespec_stop <$timespec_stop>, use \"[+][*]<time or func>\""
if ( $ timespec_stop !~ m/^(\+)?(\*)?(.*)$/i ) ;
my ( $ rel , $ rep , $ tspec ) = ( $ 1 , $ 2 , $ 3 ) ;
2020-03-25 14:42:42 +01:00
2020-03-25 15:33:08 +01:00
my ( $ err , $ hour , $ min , $ sec , $ fn ) = GetTimeSpec ( $ tspec ) ;
return $ err if ( $ err ) ;
2020-03-25 14:42:42 +01:00
2020-03-25 15:33:08 +01:00
my $ stopTime ;
if ( $ rel ) {
$ stopTime =
$ hash - > { helper } { startTime } + 3600 * $ hour + 60 * $ min + $ sec ;
}
else {
$ stopTime = ZeitBerechnen ( $ now , $ hour , $ min , $ sec ) ;
}
2020-03-25 14:42:42 +01:00
2020-03-25 15:33:08 +01:00
if ( ! AttrVal ( $ hash - > { NAME } , "forceStoptimeSameDay" , 0 ) ) {
if ( $ hash - > { helper } { startTime } > $ stopTime ) {
$ stopTime = AddDays ( $ stopTime , 1 ) ;
}
}
$ hash - > { helper } { stopTime } = $ stopTime ;
$ hash - > { helper } { STOPTIME } =
strftime ( "%d.%m.%Y %H:%M:%S" , localtime ( $ stopTime ) ) ;
2020-03-25 14:42:42 +01:00
}
2020-03-25 15:33:08 +01:00
sub Wakeup () { # {Wakeup()}
2020-03-25 14:42:42 +01:00
2020-03-25 15:33:08 +01:00
foreach my $ hc ( sort keys % { $ modules { RandomTimer } { defptr } } ) {
my $ hash = $ modules { RandomTimer } { defptr } { $ hc } ;
2020-03-25 14:42:42 +01:00
2020-03-25 15:33:08 +01:00
my $ myHash - > { HASH } = $ hash ;
SetTimer ( $ myHash ) ;
Log3 undef , 3 , "RandomTimer_Wakeup() for $hash->{NAME} done!" ;
}
Log3 undef , 3 , "RandomTimer_Wakeup() done!" ;
2020-03-25 14:42:42 +01:00
}
2020-03-25 15:33:08 +01:00
sub ZeitBerechnen {
my ( $ now , $ hour , $ min , $ sec ) = @ _ ;
2020-03-25 14:42:42 +01:00
2020-03-25 15:33:08 +01:00
my @ jetzt_arr = localtime ( $ now ) ;
#Stunden Minuten Sekunden
$ jetzt_arr [ 2 ] = $ hour ;
$ jetzt_arr [ 1 ] = $ min ;
$ jetzt_arr [ 0 ] = $ sec ;
my $ next = timelocal_nocheck ( @ jetzt_arr ) ;
return $ next ;
2020-03-25 14:42:42 +01:00
}
2020-03-25 15:33:08 +01:00
sub InternalTimer {
my ( $ modifier , $ tim , $ callback , $ hash , $ waitIfInitNotDone ) = @ _ ;
my $ timerName = "$hash->{NAME}_$modifier" ;
my $ mHash = {
HASH = > $ hash ,
NAME = > "$hash->{NAME}_$modifier" ,
MODIFIER = > $ modifier
} ;
if ( defined ( $ hash - > { TIMER } { $ timerName } ) ) {
Log3 $ hash , 1 ,
"[$hash->{NAME}] possible overwriting of timer $timerName - please delete first" ;
stacktrace ( ) ;
}
else {
$ hash - > { TIMER } { $ timerName } = $ mHash ;
}
Log3 $ hash , 5 ,
"[$hash->{NAME}] setting Timer: $timerName " . FmtDateTime ( $ tim ) ;
main:: InternalTimer ( $ tim , $ callback , $ mHash , $ waitIfInitNotDone ) ;
return $ mHash ;
2020-03-25 14:42:42 +01:00
}
################################################################################
2020-03-25 15:33:08 +01:00
sub RemoveInternalTimer {
my ( $ modifier , $ hash ) = @ _ ;
my $ timerName = "$hash->{NAME}_$modifier" ;
2020-03-26 08:26:44 +01:00
my $ myHash = $ hash - > { TIMER } { $ timerName } ;
2020-03-25 15:33:08 +01:00
if ( defined ( $ myHash ) ) {
delete $ hash - > { TIMER } { $ timerName } ;
Log3 $ hash , 5 , "[$hash->{NAME}] removing Timer: $timerName" ;
main:: RemoveInternalTimer ( $ myHash ) ;
}
2020-03-25 14:42:42 +01:00
}
2020-03-25 15:33:08 +01:00
sub GetHashIndirekt ($$) {
my ( $ myHash , $ function ) = @ _ ;
2020-03-25 14:42:42 +01:00
2020-03-25 15:33:08 +01:00
if ( ! defined ( $ myHash - > { HASH } ) ) {
Log 3 , "[$function] myHash not valid" ;
return undef ;
}
return $ myHash - > { HASH } ;
2020-03-25 14:42:42 +01:00
}
1 ;
# commandref ##################################################################
2020-03-25 15:33:08 +01:00
2020-03-25 14:42:42 +01:00
= pod
= encoding utf8
= item helper
= item summary imitates the random switch functionality of a timer clock ( FS20 ZSU )
= item summary_DE bildet die Zufallsfunktion einer Zeitschaltuhr nach
= begin html
< a name = "RandomTimer" > </a>
<h3> RandomTimer </h3>
<div>
<ul>
< a name = "RandomTimerdefine" > </a>
<b> Define </b>
<ul>
<code>
define & lt ; name & gt ; RandomTimer & lt ; timespec_start & gt ; & lt ; device & gt ; & lt ; timespec_stop & gt ; & lt ; timeToSwitch & gt ;
</code>
<br>
Defines a device , that imitates the random switch functionality of a timer clock , like a <b> FS20 ZSU </b> . The idea to create it , came from the problem , that is was always a little bit tricky to install a timer clock before holiday: finding the manual , testing it the days before and three different timer clocks with three different manuals - a horror . <br>
By using it in conjunction with a dummy and a disableCond , I ' m able to switch the always defined timer on every weekend easily from all over the world . <br>
<br>
<b> Description </b>
<ul>
a RandomTimer device starts at timespec_start switching device . Every ( timeToSwitch seconds + - 10 % ) it trys to switch device on / off . The switching period stops when the next time to switch is greater than timespec_stop .
</ul>
<br>
<b> Parameter </b>
<ul>
<li>
<code> timespec_start </code> <br>
The parameter <b> timespec_start </b> defines the start time of the timer with format : HH:MM:SS . It can be a Perlfunction as known from the < a href = "#at" > at </a> timespec .
</li> <br>
<li>
<code> device </code> <br>
The parameter <b> device </b> defines the fhem device that should be switched .
</li> <br>
<li>
<code> timespec_stop </code> <br>
The parameter <b> timespec_stop </b> defines the stop time of the timer with format : HH:MM:SS . It can be a Perlfunction as known from the timespec < a href = "#at" > at </a> .
</li> <br>
<li>
<code> timeToSwitch </code> <br>
The parameter <b> timeToSwitch </b> defines the time in seconds between two on / off switches . <br>
Note: timeToSwitch will randomly vary by + - 10 % by default .
</li> <br>
<li>
<code> variations </code> <br>
The optional parameters <b> variations </b> will modify <i> timeToSwitch </i> and /or <i>timespec_start</i > , syntax is [ VAR_DURATION ] [ : VAR_START ] . <br>
<ul>
<li> VAR_DURATION will turn <i> timeToSwitch </i> to a minimum value with some random seconds between zero and VAR_DURATION will be added . </li>
<li> VAR_START will modify <i> timespec_start </i> by adding some random seconds between zero and VAR_START . </li>
<b> Examples: </b> <br>
Add something between 0 and 10 minutes to <i> timeToSwitch </i> : <br>
<code> defmod Zufall1 RandomTimer * 06 : 00 MYSENSOR_98 22 : 00 : 00 3600 600 </code> <br>
Randomize day ' s first check by half an hour: <br>
<code> defmod Zufall1 RandomTimer * 06 : 00 MYSENSOR_98 22 : 00 : 00 3600 : 1800 </code> <br>
Do both: <br>
<code> defmod Zufall1 RandomTimer * 06 : 00 MYSENSOR_98 22 : 00 : 00 3600 600 : 1800 </code> <br>
</ul>
</li>
</ul>
<br>
<b> Examples </b>
<ul>
<li>
<code>
define ZufallsTimerTisch RandomTimer * { sunset_abs ( ) } StehlampeTisch + 03 : 00 : 00 500
</code> <br>
defines a timer that starts at sunset an ends 3 hous later . The timer trys to switch every 500 seconds ( + - 10 % ) .
</li> <br>
<li>
<code>
define ZufallsTimerTisch RandomTimer * { sunset_abs ( ) } StehlampeTisch * { sunset_abs ( 3 * 3600 ) } 480
</code> <br>
defines a timer that starts at sunset and stops after sunset + 3 hours . The timer trys to switch every 480 seconds ( + - 10 % ) .
</li> <br>
<li>
<code>
define ZufallsTimerTisch RandomTimer * { sunset_abs ( ) } StehlampeTisch 22 : 30 : 00 300
</code> <br>
defines a timer that starts at sunset an ends at 22 : 30 . The timer trys to switch every 300 seconds ( + - 10 % ) .
</li>
</ul>
</ul>
</ul>
<br>
<ul>
< a name = "RandomTimerset" > </a>
<b> Set </b> <br>
<ul>
<code> set & lt ; name & gt ; execNow </code>
<br>
This will force the RandomTimer device to immediately execute the next switch instead of waiting untill timeToSwitch has passed . Use this in case you want immediate reaction on changes of reading values factored in disableCond . As RandomTimer itself will not be notified about any event at all , you ' ll need an additional event handler like notify that listens to relevant events and issues the "execNow" command towards your RandomTimer device ( s ) .
</ul>
</ul> <br>
<ul>
< a name = "RandomTimerAttributes" > </a>
<b> Attributes </b>
<ul>
<li>
<code> disableCond </code> <br>
The default behavior of a RandomTimer is , that it works . To set the Randomtimer out of work , you can specify in the disableCond attibute a condition in perlcode that must evaluate to true . The Condition must be put into round brackets . The best way is to define a function in 99 _utils . <br>
<br>
<b> Examples </b>
<ul>
<li> <code>
attr ZufallsTimerZ disableCond ( ! isVerreist ( ) )
</code> </li>
<li> <code>
attr ZufallsTimerZ disableCond ( Value ( "presenceDummy" ) eq "present" )
</code> </li>
</ul>
</li>
<br>
<li>
<code> forceStoptimeSameDay </code> <br>
When <b> timespec_start </b> is later then <b> timespec_stop </b> , it forces the <b> timespec_stop </b> to end on the current day instead of the next day . See < a href = "https://forum.fhem.de/index.php/topic,72988.0.html" title = "Random Timer in Verbindung mit Twilight, EIN-Schaltzeit nach AUS-Schaltzeit" > forum post </a> for use case . <br>
</li>
<br>
<li>
<code> keepDeviceAlive </code> <br>
The default behavior of a RandomTimer is , that it shuts down the device after stoptime is reached . The <b> keepDeviceAlive </b> attribute changes the behavior . If set , the device status is not changed when the stoptime is reached . <br>
<br>
<b> Examples </b>
<ul>
<li> <code> attr ZufallsTimerZ keepDeviceAlive </code> </li>
</ul>
</li>
<br>
<li>
<code> disableCondCmd </code> <br>
In case the disable condition becomes true while a RandomTimer is already <b> running </b> , by default the same action is executed as when stoptime is reached ( see keepDeviceAlive attribute ) . Setting the <b> disableCondCmd </b> attribute changes this as follows: "none" will lead to no action , "offCmd" means "use off command" , "onCmd" will lead to execution of the "on command" . Delete the attribute to get back to default behaviour . <br>
<br>
<b> Examples </b>
<ul>
<li> <code> attr ZufallsTimerZ disableCondCmd offCmd </code> </li>
</ul>
</li>
<br>
<li>
<code> onCmd , offCmd </code> <br>
Setting the on - / offCmd changes the command sent to the device . Standard is: "set <device> on" . The device can be specified by a @ . < br >
<br>
<b> Examples </b>
<ul>
<li> <code>
attr Timer oncmd { fhem ( "set @ on-for-timer 14" ) }
</code> </li>
<br> NOTE: using on - for - timer commands might lead to irritating results !
<li> <code>
attr Timer offCmd { fhem ( "set @ off 16" ) }
</code> </li>
<li> <code>
attr Timer oncmd set @ pct 65
</code> </li>
<li> <code>
attr Timer offCmd set @ off 12
</code> </li>
</ul>
The decision to switch on or off depends on the state of the device . For $ featurelevel 6.0 and earlier , or if no offState attribute is set , this is evaluated by the funktion Value ( & lt ; device & gt ; ) . Value ( ) must evaluate one of the values "on" or "off" . The behavior of devices that do not evaluate one of those values can be corrected by defining a stateFormat: <br>
<code>
attr stateFormat EDIPlug_01 { ( ReadingsVal ( "EDIPlug_01" , "state" , "nF" ) =~ m/(ON|on)/i ) ? "on" : "off" }
</code> <br>
if a devices Value ( ) funktion does not evalute to on or off ( like WLAN - Steckdose von Edimax ) you get the message: <br>
<code>
[ EDIPlug ] result of function Value ( EDIPlug_01 ) must be 'on' or 'off'
</code>
NOTE: From $ featurelevel 6.1 on or if attribute offState is set , the funktion ReadingsVal ( & lt ; device & gt ; , "state" , undef ) will be used instead of Value ( ) . If "state" of the device exactly matches the regex provided in the attribute "offState" or lowercase of "state" contains a part matching to "off" , device will be considered to be "off" ( or "on" in all other cases respectively ) .
</li>
<li>
<code> offState </code> <br>
Setting this attribute , evaluation of on of will use ReadingsVal ( & lt ; device & gt ; , "state" , undef ) instead of Value ( ) . The attribute value will be used as regex , so e . g . also "dim00" beside "off" may be considered as indication the device is "off" . You may use an optional second parameter ( space separated ) to check a different reading , e . g . for a HUEDevice - group "0 any_on" might be usefull .
<br> NOTE: This will be default behaviour starting with featurelevel 6.1 .
</li>
<br>
<li>
< a href = "#readingFnAttributes" >
<u> <code> readingFnAttributes </code> </u>
</a>
</li>
<br>
<li>
<code> runonce </code> <br>
Deletes the RandomTimer device after <b> timespec_stop </b> is reached .
<br>
</li>
<br>
<li>
<code> switchmode </code> <br>
Setting the switchmode you can influence the behavior of switching on /off. The parameter has the Format 999/ 999 and the default ist 800 / 200 . The values are in "per mill" . The first parameter sets the value of the probability that the device will be switched on when the device is off . The second parameter sets the value of the probability that the device will be switched off when the device is on . <br>
<br>
<b> Examples </b>
<ul>
<li> <code> attr ZufallsTimerZ switchmode 400 /400</co de > </li>
</ul>
</li>
</ul>
</ul>
</div>
= end html
= for : application / json ; q = META . json 98 _RandomTimer . pm
{
"abstract" : "imitates the random switch functionality of a timer clock (FS20 ZSU)" ,
"x_lang" : {
"de" : {
"abstract" : "bildet die Zufallsfunktion einer Zeitschaltuhr nach"
}
} ,
"keywords" : [
] ,
"prereqs" : {
"runtime" : {
"requires" : {
"Time::HiRes" : "0" ,
"Time::Local" : "0" ,
"strict" : "0" ,
"warnings" : "0"
}
}
}
}
= end : application / json ; q = META . json
= cut