From 837643a00cdc8d04ad39d4232db8f70c5803cad0 Mon Sep 17 00:00:00 2001
From: Beta-User <>
Date: Sat, 9 Jan 2021 05:51:21 +0000
Subject: [PATCH] WeekdayTimer: add WDT_sendDelay and WDT_eventMap; break
compability to Heating_Control
git-svn-id: https://svn.fhem.de/fhem/trunk@23493 2b470e98-0d58-463d-a4d8-8e2adae1ed80
---
fhem/CHANGED | 2 +
fhem/FHEM/98_WeekdayTimer.pm | 423 +++--
fhem/contrib/deprecated/98_WeekdayTimer.pm | 1628 ++++++++++++++++++++
3 files changed, 1903 insertions(+), 150 deletions(-)
create mode 100644 fhem/contrib/deprecated/98_WeekdayTimer.pm
diff --git a/fhem/CHANGED b/fhem/CHANGED
index 343a15812..23a76437f 100644
--- a/fhem/CHANGED
+++ b/fhem/CHANGED
@@ -1,5 +1,7 @@
# 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: 98_WeekdayTimer: Add options WDT_sendDelay and WDT_eventMap
+ - change: 98_WeekdayTimer: break compability to Heating_Control
- change: 59_Twilight: not triggering at startup
- bugfix: 98_weekprofile: do not update profile data in edit mode
- change: 93_DbRep: set fastStart as default for TYPE Client
diff --git a/fhem/FHEM/98_WeekdayTimer.pm b/fhem/FHEM/98_WeekdayTimer.pm
index 3161362a1..0bfb7ade6 100644
--- a/fhem/FHEM/98_WeekdayTimer.pm
+++ b/fhem/FHEM/98_WeekdayTimer.pm
@@ -24,7 +24,9 @@
#
##############################################################################
##############################################################################
-package main;
+
+package FHEM::WeekdayTimer; ## no critic 'Package declaration'
+
use strict;
use warnings;
@@ -32,19 +34,71 @@ use Time::Local qw( timelocal_nocheck );
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
+use GPUtils qw(GP_Import GP_Export);
+
+## Import der FHEM Funktionen
+#-- Run before package compilation
+BEGIN {
+
+ # Import from main context
+ GP_Import(
+ qw(
+ defs
+ modules
+ attr
+ init_done
+ DAYSECONDS
+ MINUTESECONDS
+ readingFnAttributes
+ readingsSingleUpdate
+ readingsBulkUpdate
+ readingsBeginUpdate
+ readingsEndUpdate
+ AttrVal
+ ReadingsVal
+ InternalVal
+ Value
+ IsWe
+ Log3
+ InternalTimer
+ RemoveInternalTimer
+ CommandAttr
+ CommandDeleteAttr
+ CommandGet
+ getAllSets
+ AnalyzeCommandChain
+ AnalyzePerlCommand
+ EvalSpecials
+ perlSyntaxCheck
+ devspec2array
+ addToDevAttrList
+ FmtDateTime
+ sunrise_abs
+ sunset_abs
+ trim
+ stacktrace
+ decode_json
+ looks_like_number
+ )
+ );
+}
+
+sub main::WeekdayTimer_Initialize { goto &Initialize }
+sub ::WeekdayTimer_SetParm { goto &WeekdayTimer_SetParm }
+sub ::WeekdayTimer_SetAllParms { goto &WeekdayTimer_SetAllParms }
+
################################################################################
-sub WeekdayTimer_Initialize {
+sub Initialize {
my $hash = shift // return;
# Consumer
- $hash->{SetFn} = "WeekdayTimer_Set";
- $hash->{DefFn} = "WeekdayTimer_Define";
- $hash->{UndefFn} = "WeekdayTimer_Undef";
- $hash->{GetFn} = "WeekdayTimer_Get";
- $hash->{AttrFn} = "WeekdayTimer_Attr";
- $hash->{UpdFn} = "WeekdayTimer_Update";
- $hash->{AttrList}= "disable:0,1 delayedExecutionCond WDT_delayedExecutionDevices WDT_Group switchInThePast:0,1 commandTemplate ".
- $readingFnAttributes;
+ $hash->{SetFn} = \&WeekdayTimer_Set;
+ $hash->{DefFn} = \&WeekdayTimer_Define;
+ $hash->{UndefFn} = \&WeekdayTimer_Undef;
+ $hash->{GetFn} = \&WeekdayTimer_Get;
+ $hash->{AttrFn} = \&WeekdayTimer_Attr;
+ $hash->{UpdFn} = \&WeekdayTimer_Update;
+ $hash->{AttrList}= "disable:0,1 delayedExecutionCond WDT_delayedExecutionDevices WDT_Group switchInThePast:0,1 commandTemplate WDT_eventMap:textField-long WDT_sendDelay:slider,0,1,300,1" . $readingFnAttributes;
return;
}
@@ -71,7 +125,7 @@ sub WeekdayTimer_Define {
$hash->{NAME} = $name;
$hash->{DEVICE} = $device;
- my $language = WeekdayTimer_Language ($hash, \@arr);
+ my $language = getWDTLanguage ($hash, \@arr);
my $idx = 0;
@@ -87,7 +141,7 @@ sub WeekdayTimer_Define {
addToDevAttrList($name, "weekprofile") if $def =~ m{weekprofile}xms;
- return InternalTimer(time(), "WeekdayTimer_Start",$hash,0) if !$init_done;
+ return InternalTimer(time(), \&WeekdayTimer_Start,$hash,0) if !$init_done;
return WeekdayTimer_Start($hash);
}
@@ -97,12 +151,12 @@ sub WeekdayTimer_Undef {
my $arg = shift // return;
for my $idx (keys %{$hash->{profil}}) {
- WeekdayTimer_RemoveInternalTimer($idx, $hash);
+ deleteSingleRegisteredInternalTimer($idx, $hash);
}
- WeekdayTimer_RemoveInternalTimer($hash->{VERZOEGRUNG_IDX},$hash) if defined ($hash->{VERZOEGRUNG_IDX});
+ deleteSingleRegisteredInternalTimer($hash->{VERZOEGRUNG_IDX},$hash) if defined ($hash->{VERZOEGRUNG_IDX});
delete $modules{$hash->{TYPE}}{defptr}{$hash->{NAME}};
- return WeekdayTimer_RemoveInternalTimer("SetTimerOfDay", $hash);
+ return deleteSingleRegisteredInternalTimer("SetTimerOfDay", $hash);
}
################################################################################
@@ -110,15 +164,15 @@ sub WeekdayTimer_Start {
my $hash = shift // return;
my $name = $hash->{NAME};
my $def = $hash->{DEF};
- WeekdayTimer_RemoveInternalTimer($hash->{VERZOEGRUNG_IDX},$hash) if defined ($hash->{VERZOEGRUNG_IDX});
+ deleteSingleRegisteredInternalTimer($hash->{VERZOEGRUNG_IDX},$hash) if defined ($hash->{VERZOEGRUNG_IDX});
my @arr = split m{\s+}xms, $def;
my $device = shift @arr;
- my $language = WeekdayTimer_Language ($hash, \@arr);
+ my $language = getWDTLanguage ($hash, \@arr);
WeekdayTimer_GlobalDaylistSpec ($hash, \@arr);
- my @switchingtimes = WeekdayTimer_gatherSwitchingTimes ($hash, \@arr);
+ my @switchingtimes = gatherSwitchingTimes ($hash, \@arr);
my $conditionOrCommand = join (" ", @arr);
my @errors;
# test if device is defined
@@ -150,12 +204,12 @@ sub WeekdayTimer_Start {
$hash->{COMMAND} = $conditionOrCommand;
}
- WeekdayTimer_Profile ($hash);
+ WeekdayTimer_Profile ($hash);
delete $hash->{VERZOEGRUNG};
delete $hash->{VERZOEGRUNG_IDX};
$attr{$name}{commandTemplate} =
- 'set $NAME '. WeekdayTimer_isHeizung($hash) .' $EVENT' if (!defined $attr{$name}{commandTemplate});
+ 'set $NAME '. checkIfDeviceIsHeatingType($hash) .' $EVENT' if (!defined $attr{$name}{commandTemplate});
WeekdayTimer_SetTimerOfDay({ HASH => $hash});
@@ -173,7 +227,7 @@ sub WeekdayTimer_Set {
return "Unknown argument $arr[1], choose one of enable:noArg disable:noArg WDT_Params:single,WDT_Group,all weekprofile" if($arr[1] eq "?");
my $name = shift @arr;
- my $v = join(" ", @arr);
+ my $v = join(" ", @arr);
if ($v eq "enable") {
Log3( $hash, 3, "[$name] set $name $v" );
@@ -188,7 +242,7 @@ sub WeekdayTimer_Set {
Log3( $hash, 3, "[$name] set $name $v" );
return CommandAttr(undef, "$name disable 1");
}
-if ($v =~ m{\AWDT_Params}xms) {
+ if ($v =~ m{\AWDT_Params}xms) {
if ($v =~ m{single}xms) {
Log3( $hash, 4, "[$name] set $name $v called" );
return WeekdayTimer_SetParm($name);
@@ -204,7 +258,7 @@ if ($v =~ m{\AWDT_Params}xms) {
}
if ($v =~ m{\Aweekprofile[ ]([^: ]+):([^:]+):([^: ]+)\b}xms) {
Log3( $hash, 3, "[$name] set $name $v" );
- return if !WeekdayTimer_UpdateWeekprofileReading($hash, $1, $2, $3);
+ return if !updateWeekprofileReading($hash, $1, $2, $3);
WeekdayTimer_DeleteTimer($hash);
return WeekdayTimer_Start($hash);
}
@@ -229,55 +283,72 @@ sub WeekdayTimer_Get {
return "$arr[0] $reading => $value";
}
-################################################################################
-sub WeekdayTimer_GetHashIndirekt {
- my $myHash = shift;
- my $function = shift // return;
- if (!defined($myHash->{HASH})) {
- Log3( $myHash, 3, "[$function] myHash not valid" );
+
+################################################################################
+sub resetRegisteredInternalTimer {
+ my ( $modifier, $tim, $callback, $hash, $waitIfInitNotDone, $oldTime ) = @_;
+ deleteSingleRegisteredInternalTimer( $modifier, $hash, $callback );
+ return setRegisteredInternalTimer ( $modifier, $tim, $callback, $hash, $waitIfInitNotDone );
+}
+
+################################################################################
+sub setRegisteredInternalTimer {
+ my ( $modifier, $tim, $callback, $hash, $waitIfInitNotDone ) = @_;
+
+ my $timerName = "$hash->{NAME}_$modifier";
+ my $fnHash = {
+ HASH => $hash,
+ NAME => $timerName,
+ MODIFIER => $modifier
+ };
+ if ( defined( $hash->{TIMER}{$timerName} ) ) {
+ Log3( $hash, 1, "[$hash->{NAME}] possible overwriting of timer $timerName - please delete it first" );
+ stacktrace();
+ }
+ else {
+ $hash->{TIMER}{$timerName} = $fnHash;
+ }
+
+ Log3( $hash, 5, "[$hash->{NAME}] setting Timer: $timerName " . FmtDateTime($tim) );
+ InternalTimer( $tim, $callback, $fnHash, $waitIfInitNotDone );
+ return $fnHash;
+}
+
+
+################################################################################
+sub deleteSingleRegisteredInternalTimer {
+ my $modifier = shift;
+ my $hash = shift // return;
+ my $callback = shift;
+
+ my $timerName = "$hash->{NAME}_$modifier";
+ my $fnHash = $hash->{TIMER}{$timerName};
+ if ( defined($fnHash) ) {
+ Log3( $hash, 5, "[$hash->{NAME}] removing Timer: $timerName" );
+ RemoveInternalTimer($fnHash);
+ delete $hash->{TIMER}{$timerName};
+ }
return;
- };
- return $myHash->{HASH};
}
################################################################################
-sub WeekdayTimer_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) );
- InternalTimer($tim, $callback, $mHash, $waitIfInitNotDone);
- return $mHash;
+sub deleteAllRegisteredInternalTimer {
+ my $hash = shift // return;
+
+ for my $key ( keys %{ $hash->{TIMER} } ) {
+ deleteSingleRegisteredInternalTimer( $hash->{TIMER}{$key}{MODIFIER}, $hash );
+ }
+ return;
}
-################################################################################
-sub WeekdayTimer_RemoveInternalTimer {
- my $modifier = shift;
- my $hash = shift // return;
-
- my $timerName = "$hash->{NAME}_$modifier";
- my $myHash = $hash->{TIMER}{$timerName};
- if (defined($myHash)) {
- delete $hash->{TIMER}{$timerName};
- Log3( $hash, 5, "[$hash->{NAME}] removing Timer: $timerName" );
- RemoveInternalTimer($myHash);
- }
- return;
-}
################################################################################
sub WeekdayTimer_InitHelper {
my $hash = shift // return;
-
+
+ delete $hash->{setModifier};
+
$hash->{'.longDays'} = { "de" => ["Sonntag", "Montag","Dienstag","Mittwoch", "Donnerstag","Freitag", "Samstag", "Wochenende", "Werktags" ],
"en" => ["Sunday", "Monday","Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "weekend", "weekdays" ],
"fr" => ["Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi","Samedi", "weekend", "jours de la semaine"],
@@ -341,7 +412,7 @@ sub WeekdayTimer_Profile {
$idx++;
$hash->{profil}{$idx}{TIME} = $time;
$hash->{profil}{$idx}{PARA} = $parameter;
- $hash->{profil}{$idx}{EPOCH} = WeekdayTimer_zeitErmitteln ($now, $stunde, $minute, $sekunde, 0);
+ $hash->{profil}{$idx}{EPOCH} = getSwitchtimeEpoch ($now, $stunde, $minute, $sekunde, 0);
$hash->{profil}{$idx}{TAGE} = $tage;
$hash->{profil}{$idx}{WE_Override} = $overrulewday;
}
@@ -476,7 +547,7 @@ sub WeekdayTimer_daylistAsArray {
# Angaben der Tage verarbeiten
# Aufzaehlung 1234 ...
-if ( $daylist =~ m{\A[0-8]{0,9}\z}xms ) {
+ if ( $daylist =~ m{\A[0-8]{0,9}\z}xms ) {
#avoid 78 settings:
if ($daylist =~ m{[7]}xms && $daylist =~ m{[8]}xms) {
@@ -528,7 +599,7 @@ sub WeekdayTimer_EchteZeit {
}
################################################################################
-sub WeekdayTimer_zeitErmitteln {
+sub getSwitchtimeEpoch {
my ($now, $hour, $min, $sec, $days) = @_;
my @jetzt_arr = localtime($now);
@@ -540,7 +611,7 @@ sub WeekdayTimer_zeitErmitteln {
}
################################################################################
-sub WeekdayTimer_gatherSwitchingTimes {
+sub gatherSwitchingTimes {
my $hash = shift;
my $a = shift // return;
@@ -605,7 +676,10 @@ E: while (@$a > 0) {
} elsif ($element =~ m{\Aweekprofile}xms ) {
my @wprof = split m{:}xms, $element;
my $wp_name = $wprof[1];
- my ($unused,$wp_profile) = split m{:}xms, WeekdayTimer_GetWeekprofileReadingTriplett($hash, $wp_name),2;
+ my ($unused,$wp_profile) = split m{:}xms, getWeekprofileReadingTriplett($hash, $wp_name, $wprof[2]),2;
+
+ ($unused,$wp_profile) = split m{:}xms, getWeekprofileReadingTriplett($hash, $wp_name, 'default'),2 if !$wp_profile && $wprof[2] ne 'default';
+
return if !$wp_profile;
my $wp_sunaswe = $wprof[2]//0;
my $wp_profile_data = CommandGet(undef,$wp_name . " profile_data ". $wp_profile);
@@ -614,8 +688,8 @@ E: while (@$a > 0) {
return;
}
my $wp_profile_unpacked;
- my $json = JSON->new->allow_nonref;
- eval { $wp_profile_unpacked = $json->decode($wp_profile_data); };
+ #my $json = JSON->new->allow_nonref;
+ $wp_profile_unpacked = decode_json($wp_profile_data);
$hash->{weekprofiles}{$wp_name} = {'PROFILE'=>$wp_profile,'PROFILE_JSON'=>$wp_profile_data,'SunAsWE'=>$wp_sunaswe,'PROFILE_DATA'=>$wp_profile_unpacked };
my %wp_shortDays = ("Mon"=>1,"Tue"=>2,"Wed"=>3,"Thu"=>4,"Fri"=>5,"Sat"=>6,"Sun"=>0);
for my $wp_days (sort keys %{$hash->{weekprofiles}{$wp_name}{PROFILE_DATA}}) {
@@ -645,7 +719,7 @@ E: while (@$a > 0) {
}
################################################################################
-sub WeekdayTimer_Language {
+sub getWDTLanguage {
my ($hash, $a) = @_;
my $name = $hash->{NAME};
@@ -681,26 +755,24 @@ sub WeekdayTimer_GlobalDaylistSpec {
################################################################################
sub WeekdayTimer_SetTimerForMidnightUpdate {
- my $myHash = shift;
- my $hash = WeekdayTimer_GetHashIndirekt($myHash, (caller(0))[3]);
+ my $fnHash = shift;
+ my $hash = $fnHash->{HASH} // $fnHash;
return if (!defined($hash));
my $now = time();
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($now);
- my $midnightPlus5Seconds = WeekdayTimer_zeitErmitteln ($now, 0, 0, 5, 1);
- #Log3 $hash, 3, "midnightPlus5Seconds------------>".FmtDateTime($midnightPlus5Seconds);+#
- WeekdayTimer_RemoveInternalTimer("SetTimerOfDay", $hash);
- my $newMyHash = WeekdayTimer_InternalTimer ("SetTimerOfDay", $midnightPlus5Seconds, "$hash->{TYPE}_SetTimerOfDay", $hash, 0);
- $newMyHash->{SETTIMERATMIDNIGHT} = 1;
+ my $midnightPlus5Seconds = getSwitchtimeEpoch ($now, 0, 0, 5, 1);
+ resetRegisteredInternalTimer("SetTimerOfDay", $midnightPlus5Seconds, \&WeekdayTimer_SetTimerOfDay, $hash, 0);
+ $hash->{SETTIMERATMIDNIGHT} = 1;
return;
}
################################################################################
sub WeekdayTimer_SetTimerOfDay {
- my $myHash = shift // return;
- my $hash = WeekdayTimer_GetHashIndirekt($myHash, (caller(0))[3]);
+ my $fnHash = shift // return;
+ my $hash = $fnHash->{HASH} // $fnHash;
return if (!defined($hash));
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time());
@@ -740,12 +812,12 @@ sub WeekdayTimer_SetTimerOfDay {
}
}
$hash->{helper}{WEDAYS} = \%wedays;
- $hash->{SETTIMERATMIDNIGHT} = $myHash->{SETTIMERATMIDNIGHT};
- WeekdayTimer_DeleteTimer($hash);
+ $hash->{SETTIMERATMIDNIGHT} = $fnHash->{SETTIMERATMIDNIGHT}; WeekdayTimer_DeleteTimer($hash);
WeekdayTimer_Profile ($hash);
WeekdayTimer_SetTimer ($hash);
delete $hash->{SETTIMERATMIDNIGHT};
- WeekdayTimer_SetTimerForMidnightUpdate( { HASH => $hash} );
+ $fnHash = { HASH => $hash };
+ WeekdayTimer_SetTimerForMidnightUpdate( $fnHash );
return;
}
@@ -756,7 +828,7 @@ sub WeekdayTimer_SetTimer {
my $now = time();
- my $isHeating = WeekdayTimer_isHeizung($hash);
+ my $isHeating = checkIfDeviceIsHeatingType($hash);
my $swip = AttrVal($name, "switchInThePast", 0);
my $switchInThePast = ($swip || $isHeating);
@@ -780,18 +852,18 @@ sub WeekdayTimer_SetTimer {
my $para = $hash->{profil}{$idx}{PARA};
my $overrulewday = $hash->{profil}{$idx}{WE_Override};
- my $isActiveTimer = WeekdayTimer_isAnActiveTimer ($hash, $tage, $para, $overrulewday);
+ my $isActiveTimer = isAnActiveTimer ($hash, $tage, $para, $overrulewday);
readingsSingleUpdate ($hash, "state", "active", 1)
if (!defined $hash->{SETTIMERATMIDNIGHT} && $isActiveTimer);
if ( $timToSwitch - $now > -5 || defined $hash->{SETTIMERATMIDNIGHT} ) {
if($isActiveTimer) {
Log3( $hash, 4, "[$name] setTimer - timer seems to be active today: ".join("",@$tage)."|$time|$para" );
- WeekdayTimer_RemoveInternalTimer("$idx", $hash);
- WeekdayTimer_InternalTimer ("$idx", $timToSwitch, "$hash->{TYPE}_Update", $hash, 0);
+ #WeekdayTimer_RemoveInternalTimer("$idx", $hash);
+ resetRegisteredInternalTimer("$idx", $timToSwitch + AttrVal($name,'WDT_sendDelay',0), \&WeekdayTimer_Update, $hash, 0);
} else {
Log3( $hash, 4, "[$name] setTimer - timer seems to be NOT active today: ".join("",@$tage)."|$time|$para ". $hash->{CONDITION} );
- WeekdayTimer_RemoveInternalTimer("$idx", $hash);
+ deleteSingleRegisteredInternalTimer("$idx", $hash);
}
#WeekdayTimer_RemoveInternalTimer("$idx", $hash);
#WeekdayTimer_InternalTimer ("$idx", $timToSwitch, "$hash->{TYPE}_Update", $hash, 0);
@@ -816,7 +888,7 @@ sub WeekdayTimer_SetTimer {
if ($switchInThePast && defined $aktTime) {
# Fensterkontakte abfragen - wenn einer im Status closed, dann Schaltung um 60 Sekunden verzögern
- if (WeekdayTimer_FensterOffen($hash, $aktParameter, $aktIdx)) {
+ if (checkDelayedExecution($hash, $aktParameter, $aktIdx)) {
return;
}
@@ -830,15 +902,15 @@ sub WeekdayTimer_SetTimer {
my $parameter = $modules{WeekdayTimer}{timerInThePast}{$device}{$aktTime};
$parameter = [] if (!defined $parameter);
- push (@$parameter,["$aktIdx", $aktTime, "$hash->{TYPE}_Update", $hash, 0]);
+ push (@$parameter,["$aktIdx", $aktTime, \&WeekdayTimer_Update, $hash, 0]);
$modules{WeekdayTimer}{timerInThePast}{$device}{$aktTime} = $parameter;
my $tipHash = $modules{WeekdayTimer}{timerInThePastHash};
$tipHash = $hash if (!defined $tipHash);
$modules{WeekdayTimer}{timerInThePastHash} = $tipHash;
- WeekdayTimer_RemoveInternalTimer("delayed", $tipHash);
- WeekdayTimer_InternalTimer ("delayed", time()+5, "WeekdayTimer_delayedTimerInPast", $tipHash, 0);
+ #WeekdayTimer_RemoveInternalTimer("delayed", $tipHash);
+ resetRegisteredInternalTimer("delayed", time()+5, \&WeekdayTimer_delayedTimerInPast, $tipHash, 0);
}
return;
@@ -846,8 +918,9 @@ sub WeekdayTimer_SetTimer {
################################################################################
sub WeekdayTimer_delayedTimerInPast {
- my $myHash = shift;
- my $hash = WeekdayTimer_GetHashIndirekt($myHash, (caller(0))[3]);
+ my $fnHash = shift // return;
+ my ($hash, $modifier) = ($fnHash->{HASH}, $fnHash->{MODIFIER});
+
return if (!defined($hash));
my $tim = time();
@@ -859,8 +932,8 @@ sub WeekdayTimer_delayedTimerInPast {
Log3( $hash, 4, "[$hash->{NAME}] $device ".FmtDateTime($time)." ".($tim-$time)."s " );
for my $para ( @{$tipIpHash->{$device}{$time}} ) {
- WeekdayTimer_RemoveInternalTimer(@$para[0], @$para[3]);
- my $mHash =WeekdayTimer_InternalTimer (@$para[0],@$para[1],@$para[2],@$para[3],@$para[4]);
+ #WeekdayTimer_RemoveInternalTimer(@$para[0], @$para[3]);
+ my $mHash = resetRegisteredInternalTimer(@$para[0],@$para[1],@$para[2],@$para[3],@$para[4]);
$mHash->{forceSwitch} = 1;
}
}
@@ -898,7 +971,7 @@ sub WeekdayTimer_searchAktNext {
$oldIdx = $nextIdx;
$oldTag = $nextTag;
- $nextTime = WeekdayTimer_zeitErmitteln ($now, $stunde, $minute, $sekunde, $relativeDay);
+ $nextTime = getSwitchtimeEpoch ($now, $stunde, $minute, $sekunde, $relativeDay);
$nextPara = $hash->{helper}{SWITCHINGTIME}{$relWday}{$time};
#$nextIdx = $hash->{helper}{SWITCHINGTIME}{$relWday}{$time};
$nextIdx = $hash->{profile_IDX}{$relWday}{$time};
@@ -926,18 +999,19 @@ sub WeekdayTimer_searchAktNext {
################################################################################
sub WeekdayTimer_DeleteTimer {
my $hash = shift // return;
- map {WeekdayTimer_RemoveInternalTimer ($_, $hash)} keys %{$hash->{profil}};
+ map {deleteSingleRegisteredInternalTimer($_, $hash)} keys %{$hash->{profil}};
return;
}
################################################################################
sub WeekdayTimer_Update {
- my $myHash = shift // return;
- my $hash = WeekdayTimer_GetHashIndirekt($myHash, (caller(0))[3]);
+ my $fnHash = shift // return;
+ my $hash = $fnHash->{HASH} // $fnHash;
return if (!defined($hash));
my $name = $hash->{NAME};
- my $idx = $myHash->{MODIFIER};
+ #my $idx = $myHash->{MODIFIER};
+ my $idx = $fnHash->{MODIFIER};
my $now = time();
# Schaltparameter ermitteln
@@ -950,7 +1024,7 @@ sub WeekdayTimer_Update {
#Log3 $hash, 3, "[$name] $idx ". $time . " " . $newParam . " " . join("",@$tage);
# Fenserkontakte abfragen - wenn einer im Status closed, dann Schaltung um 60 Sekunden verzögern
- my $winopen = WeekdayTimer_FensterOffen($hash, $newParam, $idx);
+ my $winopen = checkDelayedExecution($hash, $newParam, $idx);
if ($winopen) {
readingsSingleUpdate ($hash, "state", ($winopen eq "1" or lc($winopen) eq "true") ? "open window" : $winopen, 1);
return;
@@ -959,15 +1033,16 @@ sub WeekdayTimer_Update {
my $dieGanzeWoche = $hash->{helper}{WEDAYS}{0} ? [7]:[8];
my ($activeTimer, $activeTimerState);
- if (defined $myHash->{forceSwitch}) {
-
- $activeTimer = WeekdayTimer_isAnActiveTimer ($hash, $dieGanzeWoche, $newParam, $overrulewday);
- $activeTimerState = WeekdayTimer_isAnActiveTimer ($hash, $tage, $newParam, $overrulewday);
+ #if (defined $myHash->{forceSwitch}) {
+ if (defined $fnHash->{forceSwitch}) {
+
+ $activeTimer = isAnActiveTimer ($hash, $dieGanzeWoche, $newParam, $overrulewday);
+ $activeTimerState = isAnActiveTimer ($hash, $tage, $newParam, $overrulewday);
Log3( $hash, 4, "[$name] Update - past timer activated" );
- WeekdayTimer_RemoveInternalTimer("$idx", $hash);
- WeekdayTimer_InternalTimer ("$idx", $timToSwitch, "$hash->{TYPE}_Update", $hash, 0) if ($timToSwitch > $now && ($activeTimerState||$activeTimer));
+ #WeekdayTimer_RemoveInternalTimer("$idx", $hash);
+ resetRegisteredInternalTimer("$idx", $timToSwitch, \&WeekdayTimer_Update, $hash, 0) if ($timToSwitch > $now && ($activeTimerState||$activeTimer));
} else {
- $activeTimer = WeekdayTimer_isAnActiveTimer ($hash, $tage, $newParam, $overrulewday);
+ $activeTimer = isAnActiveTimer ($hash, $tage, $newParam, $overrulewday);
$activeTimerState = $activeTimer;
Log3( $hash, 4, "[$name] Update - timer seems to be active today: ".join("",@$tage)."|$time|$newParam" ) if ( $activeTimer );
}
@@ -994,14 +1069,14 @@ sub WeekdayTimer_Update {
}
################################################################################
-sub WeekdayTimer_isAnActiveTimer {
+sub isAnActiveTimer {
my ($hash, $tage, $newParam, $overrulewday) = @_;
my $name = $hash->{NAME};
my %specials = ( "%NAME" => $hash->{DEVICE}, "%EVENT" => $newParam);
- my $condition = WeekdayTimer_Condition ($hash, $tage, $overrulewday);
- my $tageAsHash = WeekdayTimer_tageAsHash($hash, $tage);
+ my $condition = checkWDTCondition ($hash, $tage, $overrulewday);
+ my $tageAsHash = getDaysAsHash($hash, $tage);
my $xPression = qq( { $tageAsHash ;; $condition } );
$xPression = EvalSpecials($xPression, %specials);
Log3( $hash, 5, "[$name] condition: $xPression" );
@@ -1012,10 +1087,12 @@ sub WeekdayTimer_isAnActiveTimer {
}
################################################################################
-sub WeekdayTimer_isHeizung {
+sub checkIfDeviceIsHeatingType {
my $hash = shift // return '';
my $name = $hash->{NAME};
+
+ return $hash->{setModifier} if defined $hash->{setModifier};
my $dHash = $defs{$hash->{DEVICE}};
return "" if (!defined $dHash); # vorzeitiges Ende wenn das device nicht existiert
@@ -1031,24 +1108,24 @@ sub WeekdayTimer_isHeizung {
for my $ts (@tempSet) {
if ($allSets =~ m{$ts}xms) {
Log3( $hash, 4, "[$name] device type heating recognized, setModifier:$ts" );
+ $hash->{setModifier} = $ts;
return $ts
}
}
+ $hash->{setModifier} = '';
return '';
}
################################################################################
-sub WeekdayTimer_FensterOffen {
+sub checkDelayedExecution {
my ($hash, $event, $time) = @_;
my $name = $hash->{NAME};
my %specials = (
- '%HEATING_CONTROL' => $hash->{NAME},
'%WEEKDAYTIMER' => $hash->{NAME},
'%NAME' => $hash->{DEVICE},
'%EVENT' => $event,
'%TIME' => $hash->{profil}{$time}{TIME},
- '$HEATING_CONTROL' => $hash->{NAME},
'$WEEKDAYTIMER' => $hash->{NAME},
'$NAME' => $hash->{DEVICE},
'$EVENT' => $event,
@@ -1056,7 +1133,6 @@ sub WeekdayTimer_FensterOffen {
);
my $verzoegerteAusfuehrungCond = AttrVal($hash->{NAME}, "delayedExecutionCond", "0");
- #$verzoegerteAusfuehrungCond = 'xxx(%WEEKDAYTIMER,%NAME,%HEATING_CONTROL,$WEEKDAYTIMER,$EVENT,$NAME,$HEATING_CONTROL)';
my $nextRetry = time()+55+int(rand(10));
my $epoch = $hash->{profil}{$time}{EPOCH};
@@ -1067,7 +1143,7 @@ sub WeekdayTimer_FensterOffen {
}
my $delay = int(time()) - $epoch;
my $nextDelay = int($delay/60.+1.5)*60; # round to multiple of 60sec
- $nextRetry = $epoch + $nextDelay;
+ $nextRetry = $epoch + $nextDelay + AttrVal($name,'WDT_sendDelay',0);
Log3( $hash, 4, "[$name] time=".$hash->{profil}{$time}{TIME}."/$epoch delay=$delay, nextDelay=$nextDelay, nextRetry=$nextRetry" );
map { my $key = $_; $key =~ s{\$}{\\\$}gxms;
@@ -1090,11 +1166,11 @@ sub WeekdayTimer_FensterOffen {
#Prüfen, ob der nächste Timer überhaupt für den aktuellen Tag relevant ist!
Log3( $hash, 3, "[$name] timer at $hash->{profil}{$hash->{VERZOEGRUNG_IDX}}{TIME} skipped by new timer at $hash->{profil}{$time}{TIME}, delayedExecutionCond returned $verzoegerteAusfuehrung" );
- WeekdayTimer_RemoveInternalTimer($hash->{VERZOEGRUNG_IDX},$hash);
+ deleteSingleRegisteredInternalTimer($hash->{VERZOEGRUNG_IDX},$hash);
}
$hash->{VERZOEGRUNG_IDX} = $time;
- WeekdayTimer_RemoveInternalTimer("$time", $hash);
- WeekdayTimer_InternalTimer ("$time", $nextRetry, "$hash->{TYPE}_Update", $hash, 0);
+ #WeekdayTimer_RemoveInternalTimer("$time", $hash);
+ resetRegisteredInternalTimer("$time", $nextRetry, \&WeekdayTimer_Update, $hash, 0);
$hash->{VERZOEGRUNG} = 1;
return $verzoegerteAusfuehrung;
}
@@ -1106,8 +1182,7 @@ sub WeekdayTimer_FensterOffen {
"MAX" => { "READING" => "state", "STATUS" => "(open.*)", "MODEL" => "r" },
"dummy" => { "READING" => "state", "STATUS" => "(([Oo]pen|[Tt]ilt).*)", "MODEL" => "r" },
"HMCCUDEV" => { "READING" => "state", "STATUS" => "(open|tilted)", "MODEL" => "r" },
- "WeekdayTimer" => { "READING" => "delayedExecution","STATUS" => "^1\$", "MODEL" => "a" },
- "Heating_Control" => { "READING" => "delayedExecution","STATUS" => "^1\$", "MODEL" => "a" }
+ "WeekdayTimer" => { "READING" => "delayedExecution","STATUS" => "^1\$", "MODEL" => "a" }
);
my $fensterKontakte = $hash->{NAME} ." ". AttrVal($hash->{NAME}, "WDT_delayedExecutionDevices", "");
@@ -1147,11 +1222,11 @@ sub WeekdayTimer_FensterOffen {
}
if (defined($hash->{VERZOEGRUNG_IDX}) && $hash->{VERZOEGRUNG_IDX}!=$time) {
Log3( $hash, 3, "[$name] timer at $hash->{profil}{$hash->{VERZOEGRUNG_IDX}}{TIME} skipped by new timer at $hash->{profil}{$time}{TIME} while window contact returned open state");
- WeekdayTimer_RemoveInternalTimer($hash->{VERZOEGRUNG_IDX},$hash);
+ deleteSingleRegisteredInternalTimer($hash->{VERZOEGRUNG_IDX},$hash);
}
$hash->{VERZOEGRUNG_IDX} = $time;
- WeekdayTimer_RemoveInternalTimer("$time", $hash);
- WeekdayTimer_InternalTimer ("$time", $nextRetry, "$hash->{TYPE}_Update", $hash, 0);
+ #WeekdayTimer_RemoveInternalTimer("$time", $hash);
+ resetRegisteredInternalTimer("$time", $nextRetry, \&WeekdayTimer_Update, $hash, 0);
$hash->{VERZOEGRUNG} = 1;
return 1
}
@@ -1173,19 +1248,20 @@ sub WeekdayTimer_Switch_Device {
my ($hash, $newParam, $tage) = @_;
my ($command, $condition, $tageAsHash) = "";
- my $name = $hash->{NAME}; ###
- my $dummy = "";
-
+ my $name = $hash->{NAME};
+
my $now = time();
#modifier des Zieldevices auswaehlen
- my $setModifier = WeekdayTimer_isHeizung($hash);
+ my $setModifier = checkIfDeviceIsHeatingType($hash);
$attr{$name}{commandTemplate} =
'set $NAME '. $setModifier .' $EVENT' if (!defined $attr{$name}{commandTemplate});
$command = AttrVal($hash->{NAME}, "commandTemplate", "commandTemplate not found");
+ $command = 'set $NAME $EVENT' if defined $hash->{WDT_EVENTMAP} && defined $hash->{WDT_EVENTMAP}{$newParam};
$command = $hash->{COMMAND} if defined $hash->{COMMAND} && $hash->{COMMAND} ne "";
-
+
+
my $activeTimer = 1;
my $isHeating = $setModifier ? 1 : 0;
@@ -1201,9 +1277,13 @@ sub WeekdayTimer_Switch_Device {
if ($command && !$disabled && $activeTimer
&& $aktParam ne $newParam
) {
- $newParam =~ s{\\:}{|}gxms;
- $newParam =~ s{:}{ }gxms;
- $newParam =~ s{\|}{:}gxms;
+ if (defined $hash->{WDT_EVENTMAP} && defined $hash->{WDT_EVENTMAP}{$newParam}) {
+ $newParam = $hash->{WDT_EVENTMAP}{$newParam};
+ } else {
+ $newParam =~ s{\\:}{|}gxms;
+ $newParam =~ s{:}{ }gxms;
+ $newParam =~ s{\|}{:}gxms;
+ }
my %specials = ( "%NAME" => $hash->{DEVICE}, "%EVENT" => $newParam);
$command = EvalSpecials($command, %specials);
@@ -1216,7 +1296,7 @@ sub WeekdayTimer_Switch_Device {
}
################################################################################
-sub WeekdayTimer_tageAsHash {
+sub getDaysAsHash {
my ($hash, $tage) = @_;
my %days = map {$_ => 1} @$tage;
@@ -1226,7 +1306,7 @@ sub WeekdayTimer_tageAsHash {
}
################################################################################
-sub WeekdayTimer_Condition {
+sub checkWDTCondition {
my ($hash, $tage, $overrulewday) = @_;
my $name = $hash->{NAME};
@@ -1234,14 +1314,14 @@ sub WeekdayTimer_Condition {
my $condition = "( ";
$condition .= (defined $hash->{CONDITION} && $hash->{CONDITION} ne "") ? $hash->{CONDITION} : 1 ;
- $condition .= " && " . WeekdayTimer_TageAsCondition($tage, $overrulewday);
+ $condition .= " && " . getDaysAsCondition($tage, $overrulewday);
$condition .= ")";
return $condition;
}
################################################################################
-sub WeekdayTimer_TageAsCondition {
+sub getDaysAsCondition {
my $tage = shift;
my $overrulewday = shift // return;
@@ -1262,10 +1342,24 @@ sub WeekdayTimer_TageAsCondition {
################################################################################
sub WeekdayTimer_Attr {
my ($cmd, $name, $attrName, $attrVal) = @_;
- return if (!$init_done);
$attrVal = 0 if(!defined $attrVal);
my $hash = $defs{$name};
+ if ( $attrName eq "WDT_eventMap" ) {
+ if($cmd eq "set") {
+ my @ret = split(/[: \r\n]/, $attrVal);
+ return "WDT_eventMap: Odd number of elements" if(int(@ret) % 2);
+ my %ret = @ret;
+ for (keys %ret) {
+ $ret{$_} =~ s{\+}{ }gxms;
+ }
+ $hash->{WDT_EVENTMAP} = \%ret;
+ } else {
+ delete $hash->{WDT_EVENTMAP};
+ }
+ $attr{$name}{$attrName} = $attrVal;
+ }
+ return if (!$init_done);
if( $attrName eq "disable" ) {
readingsSingleUpdate ($hash, "disabled", $attrVal, 1);
return WeekdayTimer_SetTimerOfDay({ HASH => $hash}) if !$attrVal;
@@ -1283,7 +1377,6 @@ sub WeekdayTimer_Attr {
}
if ( $attrName eq "delayedExecutionCond" ) {
my %specials = (
- '$HEATING_CONTROL' => $hash->{NAME},
'$WEEKDAYTIMER' => $hash->{NAME},
'$NAME' => $hash->{DEVICE},
'$EVENT' => '1',
@@ -1293,10 +1386,19 @@ sub WeekdayTimer_Attr {
return $err if ( $err );
$attr{$name}{$attrName} = $attrVal;
}
+ if ($attrName eq "WDT_sendDelay" ) {
+ if ($cmd eq "set" && $init_done ) {
+ return "WDT_sendDelay is in seconds, so only numbers are allowed" if !looks_like_number($attrVal);
+ return "WDT_sendDelay is limited to 300 seconds" if $attrVal > 300;
+ }
+ $attr{$name}{$attrName} = $attrVal;
+ return WeekdayTimer_SetTimerOfDay({ HASH => $hash});
+ }
return;
}
+
################################################################################
sub WeekdayTimer_SetParm {
my $name = shift // return;
@@ -1306,7 +1408,7 @@ sub WeekdayTimer_SetParm {
}
################################################################################
-sub WeekdayTimer_SetAllParms { # {WeekdayTimer_SetAllParms()}
+sub WeekdayTimer_SetAllParms {
my $group = shift // "all";
my @wdtNames = $group eq 'all' ? devspec2array('TYPE=WeekdayTimer')
: devspec2array("TYPE=WeekdayTimer:FILTER=WDT_Group=$group");
@@ -1319,7 +1421,7 @@ sub WeekdayTimer_SetAllParms { # {WeekdayTimer_SetAllParms()}
}
################################################################################
-sub WeekdayTimer_UpdateWeekprofileReading {
+sub updateWeekprofileReading {
my ($hash,$wp_name,$wp_topic,$wp_profile) = @_;
my $name = $hash->{NAME};
if (!defined $defs{$wp_name} || InternalVal($wp_name,"TYPE","false") ne "weekprofile") {
@@ -1338,12 +1440,13 @@ sub WeekdayTimer_UpdateWeekprofileReading {
}
################################################################################
-sub WeekdayTimer_GetWeekprofileReadingTriplett {
- my $hash = shift;
- my $wp_name = shift // return;
- my $name = $hash->{NAME};
+sub getWeekprofileReadingTriplett {
+ my $hash = shift;
+ my $wp_name = shift // return;
+ my $wp_profile = shift //"default";
my $wp_topic = "default";
- my $wp_profile = "default";
+
+ my $name = $hash->{NAME};
if (!defined $defs{$wp_name} || InternalVal($wp_name,"TYPE","false") ne "weekprofile") {
Log3( $hash, 3, "[$name] weekprofile $wp_name not accepted, device seems not to exist or not to be of TYPE weekprofile" );
return;
@@ -1363,6 +1466,8 @@ sub WeekdayTimer_GetWeekprofileReadingTriplett {
################################################################################
1;
+__END__
+
=pod
=encoding utf8
=item helper
@@ -1542,8 +1647,8 @@ sub WeekdayTimer_GetWeekprofileReadingTriplett {
set myWeekprofiles send_to_device holiday:livingrooms wd
Although it's possible to use more than one weekprofile device in a WeekdayTimer, this is explicitly not recommended despite you are exactly knwowing what you are doing.
- Note: The userattr weekprofile will automatically be added to the list and can't be removed. The attribute itself is intended to be set to the corresponding profile name in your weekprofile device allowing easy change using the topic feature.
-
+ Note: The userattr weekprofile will automatically be added to the list and can't be removed. The attribute itself is intended to be set to the corresponding profile name (no topic name, just the second part behind the ":") in your weekprofile device allowing easy change using the topic feature.
+
@@ -1553,7 +1658,7 @@ sub WeekdayTimer_GetWeekprofileReadingTriplett {
Attributes
- delayedExecutionCond
- defines a delay Function. When returning true, the switching of the device is delayed until the function returns a false value. The behavior is just like a windowsensor in Heating_Control.
+ defines a delay Function. When returning true, the switching of the device is delayed until the function returns a false value. The behavior is the same as if one of the WDT_delayedExecutionDevices returns "open".
Example:
@@ -1574,11 +1679,29 @@ sub WeekdayTimer_GetWeekprofileReadingTriplett {
- WDT_delayedExecutionDevices
- Defines a space separated list devices (atm only window sensors are supported). When one of its state readings is open the aktual switch is delayed.
+ May contain a space separated list of devices (atm. only window sensors are supported). When one of them states to be open (typical reading names and values are known) the aktual switch is delayed, until either the window is closed or the next switching time is reached (this one will also be delayed). This is especially intended to prevent heating commands while windows are opened.
- WDT_Group
- Used to generate groups of WeekdayTimer devices to be switched together in case one of them is set to WDT_Params with the WDT_Group modifier, e.g. set wd WDT_Params WDT_Group
.
This is intended to allow former Heating_Control devices to be migrated to WeekdayTimer and replaces the Heating_Control_SetAllTemps() functionality.
+ Used to generate groups of WeekdayTimer devices to be switched together in case one of them is set to WDT_Params with the WDT_Group modifier, e.g. set wd WDT_Params WDT_Group
.
This originally was intended to allow Heating_Control devices to be migrated to WeekdayTimer by offering an alternative to the former Heating_Control_SetAllTemps() functionality.
+
+
+ - WDT_sendDelay
+ This will add some seconds to each of the switching timers to avoid collissions in RF traffic, especially, when weekprofile option is used and e.g. a topic change may affect not only a single target device but many or a single profile is used for many devices.
+ Make sure, the effective switch time for day's last switch is still taking place before midnight, otherwise it may not be executed at all!
+
+
+
+ - WDT_eventMap
+ This will translate parameters from the profile to a different command. Syntax is (space separated): "<parameter>:<new command>", spaces have to be replaced by "+".
+ Example:
+ attr wd WDT_eventMap 22.0:dtp20+01 12.0:dtp20+02 18.0:dtp20+03
+ Notes:
+
+ - New command will be addressed directly to the device, especially commandTemplate content will be ignored. So e.g. if commandTemplate is set to
set $NAME desired-temp $EVENT
, parameter 22.0 will lead to set $NAME dtp20 01
.
+ - When using Perl command syntax for command, $EVENT will be replaced by the new command.
+
+
- switchInThePast
defines that the depending device will be switched in the past in definition and startup phase when the device is not recognized as a heating.
Heatings are always switched in the past.
diff --git a/fhem/contrib/deprecated/98_WeekdayTimer.pm b/fhem/contrib/deprecated/98_WeekdayTimer.pm
new file mode 100644
index 000000000..07249c75f
--- /dev/null
+++ b/fhem/contrib/deprecated/98_WeekdayTimer.pm
@@ -0,0 +1,1628 @@
+# $Id$
+#############################################################################
+#
+# 98_WeekdayTimer.pm
+# written by Dietmar Ortmann
+# modified by Tobias Faust
+# Maintained by Beta-User since 11-2019
+# Thanks Dietmar for all you did for FHEM, RIP
+#
+# 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 .
+#
+##############################################################################
+##############################################################################
+package main;
+use strict;
+use warnings;
+
+use Time::Local qw( timelocal_nocheck );
+use Data::Dumper;
+$Data::Dumper::Sortkeys = 1;
+
+################################################################################
+sub WeekdayTimer_Initialize {
+ my $hash = shift // return;
+
+# Consumer
+ $hash->{SetFn} = "WeekdayTimer_Set";
+ $hash->{DefFn} = "WeekdayTimer_Define";
+ $hash->{UndefFn} = "WeekdayTimer_Undef";
+ $hash->{GetFn} = "WeekdayTimer_Get";
+ $hash->{AttrFn} = "WeekdayTimer_Attr";
+ $hash->{UpdFn} = "WeekdayTimer_Update";
+ $hash->{AttrList}= "disable:0,1 delayedExecutionCond WDT_delayedExecutionDevices WDT_Group switchInThePast:0,1 commandTemplate ".
+ $readingFnAttributes;
+ return;
+}
+
+################################################################################
+sub WeekdayTimer_Define {
+ my $hash = shift;
+ my $def = shift // return;
+ WeekdayTimer_InitHelper($hash);
+ my @arr = split m{\s+}xms, $def;
+
+ return "Usage: define $hash->{TYPE} "
+ if(@arr < 4);
+
+ #fuer den modify Altlasten bereinigen
+ delete($hash->{helper});
+
+ my $name = shift @arr;
+ my $type = shift @arr;
+ my $device = shift @arr;
+
+ WeekdayTimer_DeleteTimer($hash);
+ my $delVariables = "(CONDITION|COMMAND|profile|Profil)";
+ map { delete $hash->{$_} if( $_=~ m{\A$delVariables.*}xms) } keys %{$hash};
+
+ $hash->{NAME} = $name;
+ $hash->{DEVICE} = $device;
+ my $language = WeekdayTimer_Language ($hash, \@arr);
+
+ my $idx = 0;
+
+ $hash->{'.dayNumber'} = {map {$_ => $idx++} @{$hash->{'.shortDays'}{$language}}};
+ $hash->{helper}{daysRegExp} = '(' . join ("|", @{$hash->{'.shortDays'}{$language}}) . ")";
+ $hash->{helper}{daysRegExpMessage} = $hash->{helper}{daysRegExp};
+
+ $hash->{helper}{daysRegExp} =~ s{\$}{\\\$}gxms;
+ $hash->{helper}{daysRegExp} =~ s{\!}{\\\!}gxms;
+
+ $hash->{CONDITION} = "";
+ $hash->{COMMAND} = "";
+
+ addToDevAttrList($name, "weekprofile") if $def =~ m{weekprofile}xms;
+
+ return InternalTimer(time(), "WeekdayTimer_Start",$hash,0) if !$init_done;
+ return WeekdayTimer_Start($hash);
+}
+
+################################################################################
+sub WeekdayTimer_Undef {
+ my $hash = shift;
+ my $arg = shift // return;
+
+ for my $idx (keys %{$hash->{profil}}) {
+ WeekdayTimer_RemoveInternalTimer($idx, $hash);
+ }
+ WeekdayTimer_RemoveInternalTimer($hash->{VERZOEGRUNG_IDX},$hash) if defined ($hash->{VERZOEGRUNG_IDX});
+
+ delete $modules{$hash->{TYPE}}{defptr}{$hash->{NAME}};
+ return WeekdayTimer_RemoveInternalTimer("SetTimerOfDay", $hash);
+}
+
+################################################################################
+sub WeekdayTimer_Start {
+ my $hash = shift // return;
+ my $name = $hash->{NAME};
+ my $def = $hash->{DEF};
+ WeekdayTimer_RemoveInternalTimer($hash->{VERZOEGRUNG_IDX},$hash) if defined ($hash->{VERZOEGRUNG_IDX});
+ my @arr = split m{\s+}xms, $def;
+ my $device = shift @arr;
+
+ my $language = WeekdayTimer_Language ($hash, \@arr);
+
+ WeekdayTimer_GlobalDaylistSpec ($hash, \@arr);
+
+ my @switchingtimes = WeekdayTimer_gatherSwitchingTimes ($hash, \@arr);
+ my $conditionOrCommand = join (" ", @arr);
+ my @errors;
+ # test if device is defined
+ if(!$defs{$device} ) {
+ Log3( $hash, 3, "[$name] device <$device> in fhem not defined, but accepted") ;
+ if($init_done) { push @errors, qq(device <$device> in fhem not defined) };
+ }
+ # wenn keine switchintime angegeben ist, dann Fehler
+ if (@switchingtimes == 0) {
+ Log3( $hash, 3, "[$name] no valid Switchingtime found in <$conditionOrCommand>, check parameters or make sure weekprofile device exists and returns valid data." ) ;
+ if($init_done) { push @errors, qq(no valid switchingtime found in <$conditionOrCommand>, check parameters or make sure weekprofile device exists and returns valid data.) };
+ }
+ $hash->{STILLDONETIME} = 0;
+ $hash->{SWITCHINGTIMES} = \@switchingtimes;
+ $attr{$name}{verbose} = 5 if (!defined $attr{$name}{verbose} && $name =~ m{\Atst.*}xms );
+ $defs{$device}{STILLDONETIME} = 0 if($defs{$device});
+
+ $modules{$hash->{TYPE}}{defptr}{$hash->{NAME}} = $hash;
+
+ if($conditionOrCommand =~ m{\A\(.*\)\z}xms) { #condition (*)
+ $hash->{CONDITION} = $conditionOrCommand;
+ my %specials = ( "%NAME" => $hash->{DEVICE}, "%EVENT" => "0");
+ my $r = perlSyntaxCheck(qq({$conditionOrCommand}),%specials);
+ if ($r) {
+ Log3( $hash, 2, "[$name] check syntax of CONDITION <$conditionOrCommand>" ) ;
+ if($init_done) { push @errors, qq(check syntax of CONDITION <$conditionOrCommand>: $r) };
+ }
+ } elsif(length($conditionOrCommand) > 0 ) {
+ $hash->{COMMAND} = $conditionOrCommand;
+ }
+
+ WeekdayTimer_Profile ($hash);
+ delete $hash->{VERZOEGRUNG};
+ delete $hash->{VERZOEGRUNG_IDX};
+
+ $attr{$name}{commandTemplate} =
+ 'set $NAME '. WeekdayTimer_isHeizung($hash) .' $EVENT' if (!defined $attr{$name}{commandTemplate});
+
+ WeekdayTimer_SetTimerOfDay({ HASH => $hash});
+
+ return if !$init_done;
+ return join("\n", @errors) if(@errors);
+ return;
+
+}
+
+################################################################################
+sub WeekdayTimer_Set {
+ my ($hash,@arr) = @_;
+
+ return "no set value specified" if(int(@arr) < 2);
+ return "Unknown argument $arr[1], choose one of enable:noArg disable:noArg WDT_Params:single,WDT_Group,all weekprofile" if($arr[1] eq "?");
+
+ my $name = shift @arr;
+ my $v = join(" ", @arr);
+
+ if ($v eq "enable") {
+ Log3( $hash, 3, "[$name] set $name $v" );
+ if (AttrVal($name, "disable", 0)) {
+ CommandAttr(undef, "$name disable 0");
+ } else {
+ WeekdayTimer_SetTimerOfDay({ HASH => $hash});
+ }
+ return;
+ }
+ if ($v eq "disable") {
+ Log3( $hash, 3, "[$name] set $name $v" );
+ return CommandAttr(undef, "$name disable 1");
+ }
+if ($v =~ m{\AWDT_Params}xms) {
+ if ($v =~ m{single}xms) {
+ Log3( $hash, 4, "[$name] set $name $v called" );
+ return WeekdayTimer_SetParm($name);
+ }
+ if ($v =~ m{WDT_Group}xms) {
+ my $group = AttrVal($hash->{NAME},"WDT_Group",undef) // return Log3( $hash, 3, "[$name] set $name $v cancelled: group attribute not set for $name!" );
+ return WeekdayTimer_SetAllParms($group);
+ } elsif ($v =~ m{all}xms){
+ Log3( $hash,3, "[$name] set $name $v called; params in all WeekdayTimer instances will be set!" );
+ return WeekdayTimer_SetAllParms("all");
+ }
+ return;
+ }
+ if ($v =~ m{\Aweekprofile[ ]([^: ]+):([^:]+):([^: ]+)\b}xms) {
+ Log3( $hash, 3, "[$name] set $name $v" );
+ return if !WeekdayTimer_UpdateWeekprofileReading($hash, $1, $2, $3);
+ WeekdayTimer_DeleteTimer($hash);
+ return WeekdayTimer_Start($hash);
+ }
+ return;
+}
+
+################################################################################
+sub WeekdayTimer_Get {
+ my ($hash, @arr) = @_;
+ return "argument is missing" if(int(@arr) != 2);
+
+ $hash->{LOCAL} = 1;
+ delete $hash->{LOCAL};
+ my $reading= $arr[1];
+ my $value;
+
+ if(defined($hash->{READINGS}{$reading})) {
+ $value= $hash->{READINGS}{$reading}{VAL};
+ } else {
+ return "no such reading: $reading";
+ }
+ return "$arr[0] $reading => $value";
+}
+
+################################################################################
+sub WeekdayTimer_GetHashIndirekt {
+ my $myHash = shift;
+ my $function = shift // return;
+
+ if (!defined($myHash->{HASH})) {
+ Log3( $myHash, 3, "[$function] myHash not valid" );
+ return;
+ };
+ return $myHash->{HASH};
+}
+
+################################################################################
+sub WeekdayTimer_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) );
+ InternalTimer($tim, $callback, $mHash, $waitIfInitNotDone);
+ return $mHash;
+}
+
+################################################################################
+sub WeekdayTimer_RemoveInternalTimer {
+ my $modifier = shift;
+ my $hash = shift // return;
+
+ my $timerName = "$hash->{NAME}_$modifier";
+ my $myHash = $hash->{TIMER}{$timerName};
+ if (defined($myHash)) {
+ delete $hash->{TIMER}{$timerName};
+ Log3( $hash, 5, "[$hash->{NAME}] removing Timer: $timerName" );
+ RemoveInternalTimer($myHash);
+ }
+ return;
+}
+
+################################################################################
+sub WeekdayTimer_InitHelper {
+ my $hash = shift // return;
+
+ $hash->{'.longDays'} = { "de" => ["Sonntag", "Montag","Dienstag","Mittwoch", "Donnerstag","Freitag", "Samstag", "Wochenende", "Werktags" ],
+ "en" => ["Sunday", "Monday","Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "weekend", "weekdays" ],
+ "fr" => ["Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi","Samedi", "weekend", "jours de la semaine"],
+ "nl" => ["Zondag", "Maandag", "Dinsdag", "Woensdag", "Donderdag", "Vrijdag", "Zaterdag", "weekend", "werkdagen"]};
+ $hash->{'.shortDays'} = { "de" => ["so","mo","di","mi","do","fr","sa",'$we','!$we'],
+ "en" => ["su","mo","tu","we","th","fr","sa",'$we','!$we'],
+ "fr" => ["di","lu","ma","me","je","ve","sa",'$we','!$we'],
+ "nl" => ["zo","ma","di","wo","do","vr","za",'$we','!$we']};
+
+ return;
+}
+
+################################################################################
+
+sub WeekdayTimer_Profile {
+ my $hash = shift // return;
+
+ my $language = $hash->{LANGUAGE};
+ my %longDays = %{$hash->{'.longDays'}};
+
+ delete $hash->{profil};
+ my $now = time();
+ my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime($now);
+
+# ---- Zeitpunkte den Tagen zuordnen -----------------------------------
+ my $idx = 0;
+ for my $st (@{$hash->{SWITCHINGTIMES}}) {
+ my ($tage,$time,$parameter,$overrulewday) = WeekdayTimer_SwitchingTime ($hash, $st);
+
+
+ $idx++;
+ for my $d (@{$tage}) {
+ my @listeDerTage = ($d);
+ push (@listeDerTage, WeekdayTimer_getListeDerTage($hash, $d, $time)) if ($d>=7);
+
+ map { my $day = $_;
+ my $dayOfEchteZeit = $day;
+ #####
+ if ($day < 7) {
+ my $relativeDay = $day - $wday;
+ $relativeDay = $relativeDay + 7 if $relativeDay < 0 ;
+ $dayOfEchteZeit = undef if ($hash->{helper}{WEDAYS}{$relativeDay} && $overrulewday);
+ }
+ $dayOfEchteZeit = ($wday>=1&&$wday<=5) ? 6 : $wday if ($day==7); # ggf. Samstag $wday ~~ [1..5]
+ $dayOfEchteZeit = ($wday==0||$wday==6) ? 1 : $wday if ($day==8); # ggf. Montag $wday ~~ [0, 6]
+ if (defined $dayOfEchteZeit) {
+ my $echtZeit = WeekdayTimer_EchteZeit($hash, $dayOfEchteZeit, $time);
+ $hash->{profile} {$day}{$echtZeit} = $parameter;
+ $hash->{profile_IDX}{$day}{$echtZeit} = $idx;
+ }
+ } @listeDerTage;
+ }
+ }
+# ---- Zeitpunkte des aktuellen Tages mit EPOCH ermitteln --------------
+ $idx = 0;
+ for my $st (@{$hash->{SWITCHINGTIMES}}) {
+ my ($tage,$time,$parameter,$overrulewday) = WeekdayTimer_SwitchingTime ($hash, $st);
+ my $echtZeit = WeekdayTimer_EchteZeit ($hash, $wday, $time);
+ my ($stunde, $minute, $sekunde) = split m{:}xms, $echtZeit;
+
+ $idx++;
+ $hash->{profil}{$idx}{TIME} = $time;
+ $hash->{profil}{$idx}{PARA} = $parameter;
+ $hash->{profil}{$idx}{EPOCH} = WeekdayTimer_zeitErmitteln ($now, $stunde, $minute, $sekunde, 0);
+ $hash->{profil}{$idx}{TAGE} = $tage;
+ $hash->{profil}{$idx}{WE_Override} = $overrulewday;
+ }
+# ---- Texte Readings aufbauen -----------------------------------------
+ Log3( $hash, 4, "[$hash->{NAME}] " . sunrise_abs() . " " . sunset_abs() . " " . $longDays{$language}[$wday] );
+ for my $d (sort keys %{$hash->{profile}}) {
+ my $profiltext = "";
+ for my $t (sort keys %{$hash->{profile}{$d}}) {
+ $profiltext .= "$t " . $hash->{profile}{$d}{$t} . ", ";
+ }
+ my $profilKey = "Profil $d: $longDays{$language}[$d]";
+ $profiltext =~ s{, $}{}xms;
+ $hash->{$profilKey} = $profiltext;
+ Log3( $hash, 4, "[$hash->{NAME}] $profiltext ($profilKey)" );
+ }
+
+ # für logProxy umhaengen
+ $hash->{helper}{SWITCHINGTIME} = $hash->{profile};
+ delete $hash->{profile};
+ return;
+}
+
+################################################################################
+sub WeekdayTimer_getListeDerTage {
+ my ($hash, $d, $time) = @_;
+ my %hdays=();
+ if (AttrVal('global', 'holiday2we', '') !~ m{\bweekEnd\b}xms) {
+ @hdays{(0, 6)} = undef if ($d==7); # sa,so ( $we)
+ @hdays{(1..5)} = undef if ($d==8); # mo-fr (!$we)
+ } else {
+ @hdays{(0..6)} = undef if ($d==8); # mo-fr (!$we)
+ }
+ my ($sec,$min,$hour,$mday,$mon,$year,$nowWday,$yday,$isdst) = localtime(time());
+ for (my $i=0;$i<=6;$i++) {
+ my $relativeDay = $i - $nowWday;
+ $relativeDay = $relativeDay + 7 if $relativeDay < 0 ;
+ if ($hash->{helper}{WEDAYS}{$relativeDay}) {
+ $hdays{$i} = undef if ($d==7); # $we Tag aufnehmen
+ delete $hdays{$i} if ($d==8); # !$we Tag herausnehmen
+ }
+ }
+
+ #Log 3, "result------------>" . join (" ", sort keys %hdays);
+ return keys %hdays;
+}
+
+################################################################################
+sub WeekdayTimer_SwitchingTime {
+ my $hash = shift;
+ my $switchingtime = shift // return;
+
+ my $name = $hash->{NAME};
+ my $globalDaylistSpec = $hash->{GlobalDaylistSpec};
+ my @tageGlobal = @{WeekdayTimer_daylistAsArray($hash, $globalDaylistSpec)};
+
+ my (@st, $daylist, $time, $timeString, $para);
+ @st = split m{\|}xms, $switchingtime;
+ my $overrulewday = 0;
+ if ( @st == 2 || @st == 3 && $st[2] eq "w") {
+ $daylist = ($globalDaylistSpec ne "") ? $globalDaylistSpec : "0123456";
+ $time = $st[0];
+ $para = $st[1];
+ $overrulewday = 1 if defined $st[2] && $st[2] eq "w";
+ } elsif ( @st == 3 || @st == 4) {
+ $daylist = $st[0];
+ $time = $st[1];
+ $para = $st[2];
+ $overrulewday = 1 if defined $st[3] && $st[3] eq "w";
+ }
+
+ my @tage = @{WeekdayTimer_daylistAsArray($hash, $daylist)};
+ my $tage=@tage;
+ if ( $tage==0 ) {
+ Log3( $hash, 1, "[$name] invalid daylist in $name <$daylist> use one of 012345678 or $hash->{helper}{daysRegExpMessage}" );
+ }
+
+ my %hdays=();
+ @hdays{@tageGlobal} = undef;
+ @hdays{@tage} = undef;
+ @tage = sort keys %hdays;
+
+ #Log3 $hash, 3, "Tage: " . Dumper \@tage;
+ return (\@tage,$time,$para,$overrulewday);
+}
+
+################################################################################
+sub WeekdayTimer_daylistAsArray {
+ my ($hash, $daylist) = @_;
+
+ my $name = $hash->{NAME};
+ my @days;
+
+ my %hdays=();
+ $daylist = lc($daylist);
+
+ # Analysis of daylist setting by user
+ # first replace textual settings by numbers
+ if ( $daylist =~ m{\A($hash->{helper}{daysRegExp}(,|-|$)){0,7}\z}gxms ) {
+ my @subDays;
+ my @aufzaehlungen = split m{,}xms, $daylist;
+ for my $einzelAufzaehlung (@aufzaehlungen) {
+ @days = split m{-}xms, $einzelAufzaehlung;
+ if (@days == 1) {
+ #einzelner Tag: Sa
+ $hdays{$hash->{'.dayNumber'}{$days[0]}} = undef;
+ } else {
+ # von bis Angabe: Mo-Di
+ my $von = $hash->{'.dayNumber'}{$days[0]};
+ my $bis = $hash->{'.dayNumber'}{$days[1]};
+ if ($von <= $bis) {
+ @subDays = ($von .. $bis);
+ } else {
+ #@subDays = ($dayNumber{so} .. $bis, $von .. $dayNumber{sa});
+ # was until percritic: @subDays = ( 00 .. $bis, $von .. 06);
+
+ @subDays = ( 0 .. $bis, $von .. 6);
+ }
+ @hdays{@subDays}=undef;
+ }
+ }
+ if ($daylist =~ m{\$we.+\$we}xms && $daylist =~ m{\!\$we}xms) {
+ Log3( $hash, 4, "[$name] useless double setting of textual \$we and !\$we found" );
+ delete $hdays{8};
+ delete $hdays{7};
+ @subDays = (0..6);
+ @hdays{@subDays}=undef;
+ }
+ #replace all text in $daylist by numbers
+ $daylist = join( "", sort keys %hdays);
+
+ }
+
+ # Angaben der Tage verarbeiten
+ # Aufzaehlung 1234 ...
+if ( $daylist =~ m{\A[0-8]{0,9}\z}xms ) {
+
+ #avoid 78 settings:
+ if ($daylist =~ m{[7]}xms && $daylist =~ m{[8]}xms) {
+ Log3( $hash, 4, "[$name] useless double setting of \$we and !\$we found" );
+ $daylist = "0123456";
+ }
+
+ @days = split("", $daylist);
+ @hdays{@days} = undef;
+
+ }
+
+ my @tage = sort keys %hdays;
+
+
+ return \@tage;
+}
+
+################################################################################
+sub WeekdayTimer_EchteZeit {
+ my ($hash, $d, $time) = @_;
+
+ my $name = $hash->{NAME};
+
+ my $now = time();
+ my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime($now);
+
+ my $listOfDays = "";
+
+ # Zeitangabe verarbeiten.
+ $time = '"' . "$time" . '"' if($time !~ m{\A\{.*\}\z}xms);
+ my $date = $now+($d-$wday)*86400;
+ my $timeString = '{ my $date='."$date;" .$time."}";
+ my $eTimeString = AnalyzePerlCommand( $hash, $timeString ); # must deliver HH:MM[:SS]
+
+ if ($@) {
+ $@ =~ s{\n}{ }gxms;
+ Log3( $hash, 3, "[$name] " . $@ . ">>>$timeString<<<" );
+ $eTimeString = "00:00:00";
+ }
+
+ if ($eTimeString =~ m{\A[0-2][0-9]:[0-5][0-9]\z}xms) { # HH:MM
+ $eTimeString .= ":00"; # HH:MM:SS erzeugen
+ } elsif ($eTimeString !~ m{\A[0-2][0-9](:[0-5][0-9]){2,2}\z}xms) { # not HH:MM:SS
+ Log3( $hash, 1, "[$name] invalid time <$eTimeString> HH:MM[:SS]" );
+ $eTimeString = "00:00:00";
+ }
+ return $eTimeString;
+}
+################################################################################
+
+sub WeekdayTimer_zeitErmitteln {
+ my ($now, $hour, $min, $sec, $days) = @_;
+
+ my @jetzt_arr = localtime($now);
+ #Stunden Minuten Sekunden
+ $jetzt_arr[2] = $hour; $jetzt_arr[1] = $min; $jetzt_arr[0] = $sec;
+ $jetzt_arr[3] += $days;
+ my $next = timelocal_nocheck(@jetzt_arr);
+ return $next;
+}
+
+################################################################################
+sub WeekdayTimer_gatherSwitchingTimes {
+ my $hash = shift;
+ my $a = shift // return;
+
+ my $name = $hash->{NAME};
+ my @switchingtimes = ();
+ my $conditionOrCommand;
+
+ # switchingtime einsammeln
+ while (@$a > 0) {
+
+ #pruefen auf Angabe eines Schaltpunktes
+ my $element = "";
+ my @restoreElements = ();
+E: while (@$a > 0) {
+
+ my $actualElement = shift @$a;
+ push @restoreElements, $actualElement;
+ $element = $element . $actualElement . " ";
+ Log3( $hash, 5, "[$name] $element - trying to accept as a switchtime" );
+
+ my $balancedSign1 = $element =~ tr/'//; #'
+ my $balancedSign2 = $element =~ tr/"//; #"
+
+ if ( $balancedSign1 % 2 || $balancedSign2 % 2 ) { # ungerade Anzahl quotes, dann verlängern
+ Log3( $hash, 5, "[$name] $element - unbalanced quotes: $balancedSign1 single and $balancedSign2 double quotes found" );
+ next E;
+ }
+
+ my $balancedSignA1 = $element =~ tr/(//;
+ my $balancedSignA2 = $element =~ tr/)//;
+ my $balancedSignB1 = $element =~ tr/{//;
+ my $balancedSignB2 = $element =~ tr/}//;
+
+ my $balancedSignA = $balancedSignA1 - $balancedSignA2;
+ my $balancedSignB = $balancedSignB1 - $balancedSignB2;
+
+ if ( $balancedSignA || $balancedSignB ) { # öffnende/schließende Klammern nicht gleich, dann verlängern
+ Log3( $hash, 5, "[$name] $element - unbalanced brackets (: $balancedSignA1 ): $balancedSignA2 {: $balancedSignB1 }: $balancedSignB2" );
+ next E;
+ }
+ last;
+ }
+
+ # ein space am Ende wieder abschneiden
+ chop ($element);
+ my @t = split m{\|}xms, $element;
+
+ if ( (@t > 1 && @t < 5) && $t[0] ne "" && $t[1] ne "" ) {
+ Log3( $hash, 4, "[$name] $element - accepted");
+
+ #transform daylist to pure nummeric notation
+ if ( @t > 2) {
+ $t[0] = join( "", @{WeekdayTimer_daylistAsArray($hash, $t[0])} );
+ if ( $t[0] eq "" ) {
+ $t[0] = "0123456" ;
+ Log3( $hash, 2, "[$name] $element seems to be not valid and has been replaced by all days!");
+ }
+ $element = join ("|", @t);
+ }
+
+ push(@switchingtimes, $element);
+ } elsif ($element =~ m{\Aweekprofile}xms ) {
+ my @wprof = split m{:}xms, $element;
+ my $wp_name = $wprof[1];
+ my ($unused,$wp_profile) = split m{:}xms, WeekdayTimer_GetWeekprofileReadingTriplett($hash, $wp_name),2;
+ return if !$wp_profile;
+ my $wp_sunaswe = $wprof[2]//0;
+ my $wp_profile_data = CommandGet(undef,$wp_name . " profile_data ". $wp_profile);
+ if ($wp_profile_data =~ m{(profile.*not.found|usage..profile_data..name)}xms ) {
+ Log3( $hash, 3, "[$name] weekprofile $wp_name: no profile named \"$wp_profile\" available" );
+ return;
+ }
+ my $wp_profile_unpacked;
+ my $json = JSON->new->allow_nonref;
+ eval { $wp_profile_unpacked = $json->decode($wp_profile_data); };
+ $hash->{weekprofiles}{$wp_name} = {'PROFILE'=>$wp_profile,'PROFILE_JSON'=>$wp_profile_data,'SunAsWE'=>$wp_sunaswe,'PROFILE_DATA'=>$wp_profile_unpacked };
+ my %wp_shortDays = ("Mon"=>1,"Tue"=>2,"Wed"=>3,"Thu"=>4,"Fri"=>5,"Sat"=>6,"Sun"=>0);
+ for my $wp_days (sort keys %{$hash->{weekprofiles}{$wp_name}{PROFILE_DATA}}) {
+ my $wp_times = $hash->{weekprofiles}{$wp_name}{PROFILE_DATA}{$wp_days}{time};
+ my $wp_temps = $hash->{weekprofiles}{$wp_name}{PROFILE_DATA}{$wp_days}{temp};
+ my $wp_shortDay = $wp_shortDays{$wp_days};
+ for ( my $i = 0; $i < @{$wp_temps}; $i++ ) {
+ my $itime = "00:10";
+ $itime = $hash->{weekprofiles}{$wp_name}{PROFILE_DATA}{$wp_days}{time}[$i-1] if $i;
+ my $itemp = $hash->{weekprofiles}{$wp_name}{PROFILE_DATA}{$wp_days}{temp}[$i];
+ my $wp_dayprofile = "$wp_shortDay"."|$itime" . "|$itemp";
+ $wp_dayprofile .= "|w" if $wp_sunaswe eq "true";
+ push(@switchingtimes, $wp_dayprofile);
+ if ($wp_sunaswe eq "true" and $wp_shortDay == 0) {
+ $wp_dayprofile = "7|$itime" . "|$itemp";
+ push(@switchingtimes, $wp_dayprofile);
+ }
+ }
+ }
+ } else {
+ Log3( $hash, 4, "[$name] $element - NOT accepted, must be command or condition" );
+ unshift @$a, @restoreElements;
+ last;
+ }
+ }
+ return (@switchingtimes);
+}
+
+################################################################################
+sub WeekdayTimer_Language {
+ my ($hash, $a) = @_;
+
+ my $name = $hash->{NAME};
+
+ # ggf. language optional Parameter
+ my $langRegExp = "(" . join ("|", keys(%{$hash->{'.shortDays'}})) . ")";
+ my $language = shift @$a;
+
+ if ( $language !~ m{\A$langRegExp\z}xms ) {
+ Log3( $hash, 3, "[$name] language: $language not recognized, use one of $langRegExp" ) if ( length($language) == 2 && $language !~ m{\A[0-9]+\z}gmx );
+ unshift @$a, $language;
+ $language = lc(AttrVal("global","language","en"));
+ $language = $language =~ m{\A$langRegExp\z}xms ? $language : "en";
+ }
+ $hash->{LANGUAGE} = $language;
+
+ return ($langRegExp, $language);
+}
+
+################################################################################
+sub WeekdayTimer_GlobalDaylistSpec {
+ my ($hash, $a) = @_;
+
+ my $daylist = shift @$a;
+
+ my @tage = @{ WeekdayTimer_daylistAsArray( $hash, $daylist ) };
+
+ unshift @$a, $daylist if (!@tage);
+
+ $hash->{GlobalDaylistSpec} = join '', @tage;
+ return;
+}
+
+################################################################################
+sub WeekdayTimer_SetTimerForMidnightUpdate {
+ my $myHash = shift;
+ my $hash = WeekdayTimer_GetHashIndirekt($myHash, (caller(0))[3]);
+ return if (!defined($hash));
+
+ my $now = time();
+ my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($now);
+
+ my $midnightPlus5Seconds = WeekdayTimer_zeitErmitteln ($now, 0, 0, 5, 1);
+ #Log3 $hash, 3, "midnightPlus5Seconds------------>".FmtDateTime($midnightPlus5Seconds);+#
+ WeekdayTimer_RemoveInternalTimer("SetTimerOfDay", $hash);
+ my $newMyHash = WeekdayTimer_InternalTimer ("SetTimerOfDay", $midnightPlus5Seconds, "$hash->{TYPE}_SetTimerOfDay", $hash, 0);
+ $newMyHash->{SETTIMERATMIDNIGHT} = 1;
+
+ return;
+}
+
+################################################################################
+sub WeekdayTimer_SetTimerOfDay {
+ my $myHash = shift // return;
+ my $hash = WeekdayTimer_GetHashIndirekt($myHash, (caller(0))[3]);
+ return if (!defined($hash));
+
+ my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time());
+ my $secSinceMidnight = 3600*$hour + 60*$min + $sec;
+
+ my %wedays =();
+
+ my $iswe = IsWe();
+ $wedays{(0)} = $iswe if $iswe;
+ $iswe = IsWe("tomorrow");
+ $wedays{(1)} = $iswe if $iswe;
+
+ for (my $i=2;$i<=6;$i++) {
+ my $noWeekEnd = 0;
+ my $ergebnis = 'none';
+ my $izeit = time() + DAYSECONDS * $i;
+ my ($isec,$imin,$ihour,$imday,$imon,$iyear,$iwday,$iyday,$iisdst) = localtime($izeit);
+
+ for my $h2we (split m{,}xms, AttrVal('global', 'holiday2we', '')) {
+ if($h2we && ( $ergebnis eq 'none' || $h2we eq "noWeekEnd" ) && InternalVal($h2we, 'TYPE', '') eq "holiday" && !$noWeekEnd) {
+ $ergebnis = CommandGet(undef,$h2we . ' ' . sprintf("%02d-%02d",$imon+1,$imday));
+ if ($ergebnis ne 'none' && $h2we eq "noWeekEnd") {
+ $ergebnis = 'none';
+ $noWeekEnd = 1;
+ }
+ }
+ }
+ if ($ergebnis ne 'none') {
+ $wedays{$i} = $ergebnis ;
+ } else {
+ if ($iwday == 0 || $iwday == 6) {
+ $wedays{$i} = 1;
+ delete $wedays{$i} if AttrVal('global', 'holiday2we', '') =~ m{\bweekEnd\b}xms;
+ } else {
+ delete $wedays{$i};
+ }
+ }
+ }
+ $hash->{helper}{WEDAYS} = \%wedays;
+ $hash->{SETTIMERATMIDNIGHT} = $myHash->{SETTIMERATMIDNIGHT};
+ WeekdayTimer_DeleteTimer($hash);
+ WeekdayTimer_Profile ($hash);
+ WeekdayTimer_SetTimer ($hash);
+ delete $hash->{SETTIMERATMIDNIGHT};
+ WeekdayTimer_SetTimerForMidnightUpdate( { HASH => $hash} );
+ return;
+}
+
+################################################################################
+sub WeekdayTimer_SetTimer {
+ my $hash = shift // return;
+ my $name = $hash->{NAME};
+
+ my $now = time();
+
+ my $isHeating = WeekdayTimer_isHeizung($hash);
+ my $swip = AttrVal($name, "switchInThePast", 0);
+ my $switchInThePast = ($swip || $isHeating);
+
+ Log3( $hash, 4, "[$name] Heating recognized - switch in the past activated" ) if ($isHeating);
+ Log3( $hash, 4, "[$name] no switch in the yesterdays because of the devices type($hash->{DEVICE} is not recognized as heating) - use attr switchInThePast" ) if ( !$switchInThePast && !defined $hash->{SETTIMERATMIDNIGHT} );
+
+ my @switches = sort keys %{$hash->{profil}};
+ if (@switches == 0) {
+ Log3( $hash, 3, "[$name] no switches to send, due to possible errors." );
+ return;
+ }
+
+ readingsSingleUpdate ($hash, "state", "inactive", 1) if (!defined $hash->{SETTIMERATMIDNIGHT});
+ for(my $i=0; $i<=$#switches; $i++) {
+
+ my $idx = $switches[$i];
+
+ my $time = $hash->{profil}{$idx}{TIME};
+ my $timToSwitch = $hash->{profil}{$idx}{EPOCH};
+ my $tage = $hash->{profil}{$idx}{TAGE};
+ my $para = $hash->{profil}{$idx}{PARA};
+ my $overrulewday = $hash->{profil}{$idx}{WE_Override};
+
+ my $isActiveTimer = WeekdayTimer_isAnActiveTimer ($hash, $tage, $para, $overrulewday);
+ readingsSingleUpdate ($hash, "state", "active", 1)
+ if (!defined $hash->{SETTIMERATMIDNIGHT} && $isActiveTimer);
+
+ if ( $timToSwitch - $now > -5 || defined $hash->{SETTIMERATMIDNIGHT} ) {
+ if($isActiveTimer) {
+ Log3( $hash, 4, "[$name] setTimer - timer seems to be active today: ".join("",@$tage)."|$time|$para" );
+ WeekdayTimer_RemoveInternalTimer("$idx", $hash);
+ WeekdayTimer_InternalTimer ("$idx", $timToSwitch, "$hash->{TYPE}_Update", $hash, 0);
+ } else {
+ Log3( $hash, 4, "[$name] setTimer - timer seems to be NOT active today: ".join("",@$tage)."|$time|$para ". $hash->{CONDITION} );
+ WeekdayTimer_RemoveInternalTimer("$idx", $hash);
+ }
+ #WeekdayTimer_RemoveInternalTimer("$idx", $hash);
+ #WeekdayTimer_InternalTimer ("$idx", $timToSwitch, "$hash->{TYPE}_Update", $hash, 0);
+ }
+ }
+
+ if (defined $hash->{SETTIMERATMIDNIGHT}) {
+ return;
+ }
+
+ my ($aktIdx,$aktTime,$aktParameter,$nextTime,$nextParameter) =
+ WeekdayTimer_searchAktNext($hash, time()+5);
+ if(!defined $aktTime) {
+ Log3( $hash, 3, "[$name] can not compute past switching time" );
+ }
+
+ readingsBeginUpdate($hash);
+ readingsBulkUpdate ($hash, "nextUpdate", FmtDateTime($nextTime));
+ readingsBulkUpdate ($hash, "nextValue", $nextParameter);
+ readingsBulkUpdate ($hash, "currValue", $aktParameter);
+ readingsEndUpdate ($hash, 1);
+
+ if ($switchInThePast && defined $aktTime) {
+ # Fensterkontakte abfragen - wenn einer im Status closed, dann Schaltung um 60 Sekunden verzögern
+ if (WeekdayTimer_FensterOffen($hash, $aktParameter, $aktIdx)) {
+ return;
+ }
+
+ # alle in der Vergangenheit liegenden Schaltungen sammeln und
+ # nach 5 Sekunden in der Reihenfolge der Schaltzeiten
+ # durch WeekdayTimer_delayedTimerInPast() als Timer einstellen
+ # die Parameter merken wir uns kurzzeitig im hash
+ # modules{WeekdayTimer}{timerInThePast}
+ my $device = $hash->{DEVICE};
+ Log3( $hash, 4, "[$name] past timer on $hash->{DEVICE} at ". FmtDateTime($aktTime). " with $aktParameter activated" );
+
+ my $parameter = $modules{WeekdayTimer}{timerInThePast}{$device}{$aktTime};
+ $parameter = [] if (!defined $parameter);
+ push (@$parameter,["$aktIdx", $aktTime, "$hash->{TYPE}_Update", $hash, 0]);
+ $modules{WeekdayTimer}{timerInThePast}{$device}{$aktTime} = $parameter;
+
+ my $tipHash = $modules{WeekdayTimer}{timerInThePastHash};
+ $tipHash = $hash if (!defined $tipHash);
+ $modules{WeekdayTimer}{timerInThePastHash} = $tipHash;
+
+ WeekdayTimer_RemoveInternalTimer("delayed", $tipHash);
+ WeekdayTimer_InternalTimer ("delayed", time()+5, "WeekdayTimer_delayedTimerInPast", $tipHash, 0);
+
+ }
+ return;
+}
+
+################################################################################
+sub WeekdayTimer_delayedTimerInPast {
+ my $myHash = shift;
+ my $hash = WeekdayTimer_GetHashIndirekt($myHash, (caller(0))[3]);
+ return if (!defined($hash));
+
+ my $tim = time();
+
+ my $tipIpHash = $modules{WeekdayTimer}{timerInThePast};
+
+ for my $device ( keys %$tipIpHash ) {
+ for my $time ( sort keys %{$tipIpHash->{$device}} ) {
+ Log3( $hash, 4, "[$hash->{NAME}] $device ".FmtDateTime($time)." ".($tim-$time)."s " );
+
+ for my $para ( @{$tipIpHash->{$device}{$time}} ) {
+ WeekdayTimer_RemoveInternalTimer(@$para[0], @$para[3]);
+ my $mHash =WeekdayTimer_InternalTimer (@$para[0],@$para[1],@$para[2],@$para[3],@$para[4]);
+ $mHash->{forceSwitch} = 1;
+ }
+ }
+ }
+ delete $modules{WeekdayTimer}{timerInThePast};
+ delete $modules{WeekdayTimer}{timerInThePastHash};
+ return;
+}
+
+################################################################################
+sub WeekdayTimer_searchAktNext {
+ my ($hash, $now) = @_;
+ my $name = $hash->{NAME};
+
+ my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($now);
+ #Log3 $hash, 3, "[$name] such--->".FmtDateTime($now);
+
+ my ($oldTag, $oldTime, $oldPara , $oldIdx);
+ my ($nextTag, $nextTime, $nextPara, $nextIdx);
+
+ my $language = $hash->{LANGUAGE};
+ my %shortDays = %{$hash->{'.shortDays'}};
+
+ my @realativeWdays = ($wday..6,0..$wday-1,$wday..6,0..6);
+ for (my $i=0;$i<=$#realativeWdays;$i++) {
+
+ my $relativeDay = $i-7;
+ my $relWday = $realativeWdays[$i];
+
+ for my $time (sort keys %{$hash->{helper}{SWITCHINGTIME}{$relWday}}) {
+ my ($stunde, $minute, $sekunde) = split m{:}xms, $time;
+
+ $oldTime = $nextTime;
+ $oldPara = $nextPara;
+ $oldIdx = $nextIdx;
+ $oldTag = $nextTag;
+
+ $nextTime = WeekdayTimer_zeitErmitteln ($now, $stunde, $minute, $sekunde, $relativeDay);
+ $nextPara = $hash->{helper}{SWITCHINGTIME}{$relWday}{$time};
+ #$nextIdx = $hash->{helper}{SWITCHINGTIME}{$relWday}{$time};
+ $nextIdx = $hash->{profile_IDX}{$relWday}{$time};
+ $nextTag = $relWday;
+
+ #Log3 $hash, 3, $shortDays{$language}[$nextTag]." ".FmtDateTime($nextTime)." ".$nextPara." ".$nextIdx;
+ my $ignore = 0;
+ my $wend = 0;
+ my $tage = $hash->{profil}{$nextIdx}{TAGE}[0];
+ if ($wday==$relWday) {
+ $wend = $hash->{helper}{WEDAYS}{0};
+ $ignore = (($tage == 7 && !$wend ) || ($tage == 8 && $wend ));
+ } elsif ( $wday==$relWday+1) {
+ $wend = $hash->{helper}{WEDAYS}{1};
+ $ignore = (($tage == 7 && !$wend ) || ($tage == 8 && $wend ));
+ }
+ if (!$ignore && $nextTime >= $now ) {
+ return ($oldIdx, $oldTime, $oldPara, $nextTime, $nextPara);
+ }
+ }
+ }
+ return (undef,undef,undef,undef);
+}
+
+################################################################################
+sub WeekdayTimer_DeleteTimer {
+ my $hash = shift // return;
+ map {WeekdayTimer_RemoveInternalTimer ($_, $hash)} keys %{$hash->{profil}};
+ return;
+}
+
+################################################################################
+sub WeekdayTimer_Update {
+ my $myHash = shift // return;
+ my $hash = WeekdayTimer_GetHashIndirekt($myHash, (caller(0))[3]);
+ return if (!defined($hash));
+
+ my $name = $hash->{NAME};
+ my $idx = $myHash->{MODIFIER};
+ my $now = time();
+
+ # Schaltparameter ermitteln
+ my $tage = $hash->{profil}{$idx}{TAGE};
+ my $time = $hash->{profil}{$idx}{TIME};
+ my $newParam = $hash->{profil}{$idx}{PARA};
+ my $timToSwitch = $hash->{profil}{$idx}{EPOCH};
+ my $overrulewday = $hash->{profil}{$idx}{WE_Override};
+
+ #Log3 $hash, 3, "[$name] $idx ". $time . " " . $newParam . " " . join("",@$tage);
+
+ # Fenserkontakte abfragen - wenn einer im Status closed, dann Schaltung um 60 Sekunden verzögern
+ my $winopen = WeekdayTimer_FensterOffen($hash, $newParam, $idx);
+ if ($winopen) {
+ readingsSingleUpdate ($hash, "state", ($winopen eq "1" or lc($winopen) eq "true") ? "open window" : $winopen, 1);
+ return;
+ }
+
+ my $dieGanzeWoche = $hash->{helper}{WEDAYS}{0} ? [7]:[8];
+
+ my ($activeTimer, $activeTimerState);
+ if (defined $myHash->{forceSwitch}) {
+
+ $activeTimer = WeekdayTimer_isAnActiveTimer ($hash, $dieGanzeWoche, $newParam, $overrulewday);
+ $activeTimerState = WeekdayTimer_isAnActiveTimer ($hash, $tage, $newParam, $overrulewday);
+ Log3( $hash, 4, "[$name] Update - past timer activated" );
+ WeekdayTimer_RemoveInternalTimer("$idx", $hash);
+ WeekdayTimer_InternalTimer ("$idx", $timToSwitch, "$hash->{TYPE}_Update", $hash, 0) if ($timToSwitch > $now && ($activeTimerState||$activeTimer));
+ } else {
+ $activeTimer = WeekdayTimer_isAnActiveTimer ($hash, $tage, $newParam, $overrulewday);
+ $activeTimerState = $activeTimer;
+ Log3( $hash, 4, "[$name] Update - timer seems to be active today: ".join("",@$tage)."|$time|$newParam" ) if ( $activeTimer );
+ }
+ #Log3 $hash, 3, "activeTimer------------>$activeTimer";
+ #Log3 $hash, 3, "activeTimerState------->$activeTimerState";
+ my ($aktIdx, $aktTime, $aktParameter, $nextTime, $nextParameter) =
+ WeekdayTimer_searchAktNext($hash, time()+5);
+
+ my $device = $hash->{DEVICE};
+ my $disabled = AttrVal($hash->{NAME}, "disable", 0);
+
+ # ggf. Device schalten
+ WeekdayTimer_Switch_Device($hash, $newParam, $tage) if($activeTimer);
+
+ readingsBeginUpdate($hash);
+ readingsBulkUpdate ($hash, "nextUpdate", FmtDateTime($nextTime));
+ readingsBulkUpdate ($hash, "nextValue", $nextParameter);
+ readingsBulkUpdate ($hash, "currValue", $aktParameter); # HB
+ readingsBulkUpdate ($hash, "state", $newParam ) if($activeTimerState);
+ readingsEndUpdate ($hash, 1);
+
+ return 1;
+
+}
+
+################################################################################
+sub WeekdayTimer_isAnActiveTimer {
+ my ($hash, $tage, $newParam, $overrulewday) = @_;
+
+ my $name = $hash->{NAME};
+ my %specials = ( "%NAME" => $hash->{DEVICE}, "%EVENT" => $newParam);
+
+ my $condition = WeekdayTimer_Condition ($hash, $tage, $overrulewday);
+ my $tageAsHash = WeekdayTimer_tageAsHash($hash, $tage);
+ my $xPression = qq( { $tageAsHash ;; $condition } );
+ $xPression = EvalSpecials($xPression, %specials);
+ Log3( $hash, 5, "[$name] condition: $xPression" );
+
+ my $ret = AnalyzeCommandChain(undef, $xPression);
+ Log3( $hash, 5, "[$name] result of condition: $ret" );
+ return $ret;
+}
+
+################################################################################
+sub WeekdayTimer_isHeizung {
+ my $hash = shift // return '';
+
+ my $name = $hash->{NAME};
+
+ my $dHash = $defs{$hash->{DEVICE}};
+ return "" if (!defined $dHash); # vorzeitiges Ende wenn das device nicht existiert
+
+ my $dType = $dHash->{TYPE};
+ return "" if (!defined($dType) || $dType eq "dummy" );
+
+ my $dName = $dHash->{NAME};
+
+ my @tempSet = ("desired-temp", "desiredTemperature", "desired", "thermostatSetpointSet");
+ my $allSets = getAllSets($dName);
+
+ for my $ts (@tempSet) {
+ if ($allSets =~ m{$ts}xms) {
+ Log3( $hash, 4, "[$name] device type heating recognized, setModifier:$ts" );
+ return $ts
+ }
+ }
+ return '';
+}
+
+################################################################################
+sub WeekdayTimer_FensterOffen {
+ my ($hash, $event, $time) = @_;
+ my $name = $hash->{NAME};
+
+ my %specials = (
+ '%HEATING_CONTROL' => $hash->{NAME},
+ '%WEEKDAYTIMER' => $hash->{NAME},
+ '%NAME' => $hash->{DEVICE},
+ '%EVENT' => $event,
+ '%TIME' => $hash->{profil}{$time}{TIME},
+ '$HEATING_CONTROL' => $hash->{NAME},
+ '$WEEKDAYTIMER' => $hash->{NAME},
+ '$NAME' => $hash->{DEVICE},
+ '$EVENT' => $event,
+ '$TIME' => $hash->{profil}{$time}{TIME},
+ );
+
+ my $verzoegerteAusfuehrungCond = AttrVal($hash->{NAME}, "delayedExecutionCond", "0");
+ #$verzoegerteAusfuehrungCond = 'xxx(%WEEKDAYTIMER,%NAME,%HEATING_CONTROL,$WEEKDAYTIMER,$EVENT,$NAME,$HEATING_CONTROL)';
+
+ my $nextRetry = time()+55+int(rand(10));
+ my $epoch = $hash->{profil}{$time}{EPOCH};
+ if (!$epoch) { #prevent FHEM crashing when profile is somehow damaged or incomlete, forum #109164
+ my $actual_wp_reading = ReadingsVal($name,"weekprofiles","none");
+ Log3( $hash, 0, "[$name] profile $actual_wp_reading, item $time seems to be somehow damaged or incomlete!" );
+ $epoch = int(time()) - 10*MINUTESECONDS;
+ }
+ my $delay = int(time()) - $epoch;
+ my $nextDelay = int($delay/60.+1.5)*60; # round to multiple of 60sec
+ $nextRetry = $epoch + $nextDelay;
+ Log3( $hash, 4, "[$name] time=".$hash->{profil}{$time}{TIME}."/$epoch delay=$delay, nextDelay=$nextDelay, nextRetry=$nextRetry" );
+
+ map { my $key = $_; $key =~ s{\$}{\\\$}gxms;
+ my $val = $specials{$_};
+ $verzoegerteAusfuehrungCond =~ s{$key}{$val}gxms
+ } keys %specials;
+ Log3( $hash, 4, "[$name] delayedExecutionCond:$verzoegerteAusfuehrungCond" );
+
+# my $verzoegerteAusfuehrung = eval($verzoegerteAusfuehrungCond);
+ my $verzoegerteAusfuehrung = AnalyzePerlCommand( $hash, $verzoegerteAusfuehrungCond );
+
+ my $logtext = $verzoegerteAusfuehrung // 'no condition attribute set';
+ Log3( $hash, 4, "[$name] result of delayedExecutionCond: $logtext" );
+
+ if ($verzoegerteAusfuehrung) {
+ if (!defined($hash->{VERZOEGRUNG})) {
+ Log3( $hash, 3, "[$name] switch of $hash->{DEVICE} delayed - delayedExecutionCond: '$verzoegerteAusfuehrungCond' is TRUE" );
+ }
+ if (defined($hash->{VERZOEGRUNG_IDX}) && $hash->{VERZOEGRUNG_IDX}!=$time) {
+ #Prüfen, ob der nächste Timer überhaupt für den aktuellen Tag relevant ist!
+
+ Log3( $hash, 3, "[$name] timer at $hash->{profil}{$hash->{VERZOEGRUNG_IDX}}{TIME} skipped by new timer at $hash->{profil}{$time}{TIME}, delayedExecutionCond returned $verzoegerteAusfuehrung" );
+ WeekdayTimer_RemoveInternalTimer($hash->{VERZOEGRUNG_IDX},$hash);
+ }
+ $hash->{VERZOEGRUNG_IDX} = $time;
+ WeekdayTimer_RemoveInternalTimer("$time", $hash);
+ WeekdayTimer_InternalTimer ("$time", $nextRetry, "$hash->{TYPE}_Update", $hash, 0);
+ $hash->{VERZOEGRUNG} = 1;
+ return $verzoegerteAusfuehrung;
+ }
+
+ my %contacts = ( "CUL_FHTTK" => { "READING" => "Window", "STATUS" => "(Open)", "MODEL" => "r" },
+ "CUL_HM" => { "READING" => "state", "STATUS" => "(open|tilted)", "MODEL" => "r" },
+ "EnOcean" => { "READING" => "state", "STATUS" => "(open)", "MODEL" => "r" },
+ "ZWave" => { "READING" => "state", "STATUS" => "(open)", "MODEL" => "r" },
+ "MAX" => { "READING" => "state", "STATUS" => "(open.*)", "MODEL" => "r" },
+ "dummy" => { "READING" => "state", "STATUS" => "(([Oo]pen|[Tt]ilt).*)", "MODEL" => "r" },
+ "HMCCUDEV" => { "READING" => "state", "STATUS" => "(open|tilted)", "MODEL" => "r" },
+ "WeekdayTimer" => { "READING" => "delayedExecution","STATUS" => "^1\$", "MODEL" => "a" },
+ "Heating_Control" => { "READING" => "delayedExecution","STATUS" => "^1\$", "MODEL" => "a" }
+ );
+
+ my $fensterKontakte = $hash->{NAME} ." ". AttrVal($hash->{NAME}, "WDT_delayedExecutionDevices", "");
+ my $HC_fensterKontakte = AttrVal($hash->{NAME}, "windowSensor", undef);
+ $fensterKontakte .= " ".$HC_fensterKontakte if defined $HC_fensterKontakte;
+ $fensterKontakte = trim($fensterKontakte);
+
+ Log3( $hash, 4, "[$name] list of window sensors found: '$fensterKontakte'" );
+ if ($fensterKontakte ne "" ) {
+ my @kontakte = split m{\s+}xms, $fensterKontakte;
+ for my $fk (@kontakte) {
+ #hier flexible eigene Angaben ermöglichen?, Schreibweise: Device[:Reading[:ValueToCompare[:Comparator]]]; defaults: Reading=state, ValueToCompare=0/undef/false, all other true, Comparator=eq (options: eq, ne, lt, gt, ==, <,>,<>)
+ my $fk_hash = $defs{$fk};
+ if (!$fk_hash) {
+ Log3( $hash, 3, "[$name] sensor <$fk> not found - check name." );
+ } else {
+ my $fk_typ = $fk_hash->{TYPE};
+ if (!defined($contacts{$fk_typ})) {
+ Log3( $hash, 3, "[$name] TYPE '$fk_typ' of $fk not yet supported, $fk ignored - inform maintainer" );
+ } else {
+
+ my $reading = $contacts{$fk_typ}{READING};
+ my $statusReg = $contacts{$fk_typ}{STATUS};
+ my $model = $contacts{$fk_typ}{MODEL};
+
+ my $windowStatus = $model eq "r" ? ReadingsVal($fk,$reading,"nF")
+ : AttrVal ($fk,$reading,"nF");
+
+ if ($windowStatus eq "nF") {
+ Log3( $hash, 3, "[$name] Reading/Attribute '$reading' of $fk not found, $fk ignored - inform maintainer" ) if ( $model eq "r" );
+ } else {
+ Log3( $hash, 5, "[$name] sensor '$fk' Reading/Attribute '$reading' is '$windowStatus'" );
+
+ if ($windowStatus =~ m{\A$statusReg\z}xms) {
+ if (!defined($hash->{VERZOEGRUNG})) {
+ Log3( $hash, 3, "[$name] switch of $hash->{DEVICE} delayed - sensor '$fk' Reading/Attribute '$reading' is '$windowStatus'" );
+ }
+ if (defined($hash->{VERZOEGRUNG_IDX}) && $hash->{VERZOEGRUNG_IDX}!=$time) {
+ Log3( $hash, 3, "[$name] timer at $hash->{profil}{$hash->{VERZOEGRUNG_IDX}}{TIME} skipped by new timer at $hash->{profil}{$time}{TIME} while window contact returned open state");
+ WeekdayTimer_RemoveInternalTimer($hash->{VERZOEGRUNG_IDX},$hash);
+ }
+ $hash->{VERZOEGRUNG_IDX} = $time;
+ WeekdayTimer_RemoveInternalTimer("$time", $hash);
+ WeekdayTimer_InternalTimer ("$time", $nextRetry, "$hash->{TYPE}_Update", $hash, 0);
+ $hash->{VERZOEGRUNG} = 1;
+ return 1
+ }
+ }
+ }
+ }
+ }
+ }
+ if ($hash->{VERZOEGRUNG}) {
+ Log3( $hash, 3, "[$name] delay of switching $hash->{DEVICE} stopped." );
+ }
+ delete $hash->{VERZOEGRUNG};
+ delete $hash->{VERZOEGRUNG_IDX} if defined($hash->{VERZOEGRUNG_IDX});
+ return 0;
+}
+
+################################################################################
+sub WeekdayTimer_Switch_Device {
+ my ($hash, $newParam, $tage) = @_;
+
+ my ($command, $condition, $tageAsHash) = "";
+ my $name = $hash->{NAME}; ###
+ my $dummy = "";
+
+ my $now = time();
+ #modifier des Zieldevices auswaehlen
+ my $setModifier = WeekdayTimer_isHeizung($hash);
+
+ $attr{$name}{commandTemplate} =
+ 'set $NAME '. $setModifier .' $EVENT' if (!defined $attr{$name}{commandTemplate});
+
+ $command = AttrVal($hash->{NAME}, "commandTemplate", "commandTemplate not found");
+ $command = $hash->{COMMAND} if defined $hash->{COMMAND} && $hash->{COMMAND} ne "";
+
+ my $activeTimer = 1;
+
+ my $isHeating = $setModifier ? 1 : 0;
+ my $aktParam = ReadingsVal($hash->{DEVICE}, $setModifier, "");
+ $aktParam = sprintf("%.1f", $aktParam) if ( $isHeating && $aktParam =~ m{\A[0-9]{1,3}\z}ixms );
+ $newParam = sprintf("%.1f", $newParam) if ( $isHeating && $newParam =~ m{\A[0-9]{1,3}\z}ixms );
+
+ my $disabled = AttrVal($hash->{NAME}, "disable", 0);
+ my $disabled_txt = $disabled ? "" : " not";
+ Log3( $hash, 4, "[$name] aktParam:$aktParam newParam:$newParam - is$disabled_txt disabled" );
+
+ #Kommando ausführen
+ if ($command && !$disabled && $activeTimer
+ && $aktParam ne $newParam
+ ) {
+ $newParam =~ s{\\:}{|}gxms;
+ $newParam =~ s{:}{ }gxms;
+ $newParam =~ s{\|}{:}gxms;
+
+ my %specials = ( "%NAME" => $hash->{DEVICE}, "%EVENT" => $newParam);
+ $command = EvalSpecials($command, %specials);
+
+ Log3( $hash, 4, "[$name] command: '$command' executed with ".join(",", map { "$_=>$specials{$_}" } keys %specials) );
+ my $ret = AnalyzeCommandChain(undef, $command);
+ Log3( $hash, 3, $ret ) if ( $ret );
+ }
+ return;
+}
+
+################################################################################
+sub WeekdayTimer_tageAsHash {
+ my ($hash, $tage) = @_;
+
+ my %days = map {$_ => 1} @$tage;
+ delete @days{7,8};
+
+ return 'my $days={};map{$days->{$_}=1}'.'('.join (",", sort keys %days).')';
+}
+
+################################################################################
+sub WeekdayTimer_Condition {
+ my ($hash, $tage, $overrulewday) = @_;
+
+ my $name = $hash->{NAME};
+ Log3( $hash, 4, "[$name] condition:$hash->{CONDITION} - Tage:".join(",",@$tage) );
+
+ my $condition = "( ";
+ $condition .= (defined $hash->{CONDITION} && $hash->{CONDITION} ne "") ? $hash->{CONDITION} : 1 ;
+ $condition .= " && " . WeekdayTimer_TageAsCondition($tage, $overrulewday);
+ $condition .= ")";
+
+ return $condition;
+}
+
+################################################################################
+sub WeekdayTimer_TageAsCondition {
+ my $tage = shift;
+ my $overrulewday = shift // return;
+
+ my %days = map {$_ => 1} @$tage;
+
+ my $we = $days{7}; delete $days{7}; # $we
+ my $notWe = $days{8}; delete $days{8}; #!$we
+
+ my $tageExp = '(defined $days->{$wday}';
+ $tageExp .= ' && !$we' if $overrulewday;
+ $tageExp .= ' || $we' if defined $we;
+ $tageExp .= ' || !$we' if defined $notWe;
+ $tageExp .= ')';
+
+ return $tageExp;
+}
+
+################################################################################
+sub WeekdayTimer_Attr {
+ my ($cmd, $name, $attrName, $attrVal) = @_;
+ return if (!$init_done);
+ $attrVal = 0 if(!defined $attrVal);
+
+ my $hash = $defs{$name};
+ if( $attrName eq "disable" ) {
+ readingsSingleUpdate ($hash, "disabled", $attrVal, 1);
+ return WeekdayTimer_SetTimerOfDay({ HASH => $hash}) if !$attrVal;
+ }
+ if ( $attrName eq "enable" ) {
+ return WeekdayTimer_SetTimerOfDay({ HASH => $hash});
+ }
+ if ( $attrName eq "weekprofile" ) {
+ $attr{$name}{$attrName} = $attrVal;
+ return WeekdayTimer_Start($hash);
+ }
+ if ( $attrName eq "switchInThePast" ) {
+ $attr{$name}{$attrName} = $attrVal;
+ return WeekdayTimer_SetTimerOfDay({ HASH => $hash});
+ }
+ if ( $attrName eq "delayedExecutionCond" ) {
+ my %specials = (
+ '$HEATING_CONTROL' => $hash->{NAME},
+ '$WEEKDAYTIMER' => $hash->{NAME},
+ '$NAME' => $hash->{DEVICE},
+ '$EVENT' => '1',
+ '$TIME' => '08:08',
+ );
+ my $err = perlSyntaxCheck( $attrVal, %specials );
+ return $err if ( $err );
+ $attr{$name}{$attrName} = $attrVal;
+ }
+
+ return;
+}
+
+################################################################################
+sub WeekdayTimer_SetParm {
+ my $name = shift // return;
+ my $hash = $defs{$name} // return qq(No Device named $name found!);;
+ WeekdayTimer_DeleteTimer($hash);
+ return WeekdayTimer_SetTimer($hash);
+}
+
+################################################################################
+sub WeekdayTimer_SetAllParms { # {WeekdayTimer_SetAllParms()}
+ my $group = shift // "all";
+ my @wdtNames = $group eq 'all' ? devspec2array('TYPE=WeekdayTimer')
+ : devspec2array("TYPE=WeekdayTimer:FILTER=WDT_Group=$group");
+
+ for my $wdName ( @wdtNames ) {
+ WeekdayTimer_SetParm($wdName);
+ }
+ Log3( undef, 3, "WeekdayTimer_SetAllParms() done on: ".join(" ",@wdtNames ) );
+ return;
+}
+
+################################################################################
+sub WeekdayTimer_UpdateWeekprofileReading {
+ my ($hash,$wp_name,$wp_topic,$wp_profile) = @_;
+ my $name = $hash->{NAME};
+ if (!defined $defs{$wp_name} || InternalVal($wp_name,"TYPE","false") ne "weekprofile") {
+ Log3( $hash, 3, "[$name] weekprofile $wp_name not accepted, device seems not to exist or not to be of TYPE weekprofile" );
+ return;
+ }
+ if ($hash->{DEF} !~ m{weekprofile:$wp_name\b}xms) {
+ Log3( $hash, 3, "[$name] weekprofile $wp_name not accepted, device is not correctly listed as weekprofile in the WeekdayTimer definition" );
+ return;
+ }
+ my @t = split m{\s+}xms, ReadingsVal( $name, 'weekprofiles', '');
+ my @newt = ( qq($wp_name:$wp_topic:$wp_profile) );
+ push @newt, grep { $_ !~ m{\A$wp_name\b}xms } @t;
+ readingsSingleUpdate( $hash, 'weekprofiles', join(' ', @newt), 1 );
+ return 1;
+}
+
+################################################################################
+sub WeekdayTimer_GetWeekprofileReadingTriplett {
+ my $hash = shift;
+ my $wp_name = shift // return;
+ my $name = $hash->{NAME};
+ my $wp_topic = "default";
+ my $wp_profile = "default";
+ if (!defined $defs{$wp_name} || InternalVal($wp_name,"TYPE","false") ne "weekprofile") {
+ Log3( $hash, 3, "[$name] weekprofile $wp_name not accepted, device seems not to exist or not to be of TYPE weekprofile" );
+ return;
+ }
+ my $newtriplett = qq($wp_name:$wp_topic:$wp_profile);
+ my $actual_wp_reading = ReadingsVal($name,"weekprofiles",0);
+ if (!$actual_wp_reading) {
+ readingsSingleUpdate ($hash, "weekprofiles", $newtriplett, 0);
+ $actual_wp_reading = $newtriplett;
+ }
+ my @t = split m{\s+}xms, $actual_wp_reading;
+ for my $triplett (@t){
+ return $triplett if $triplett =~ m{$wp_name\b}xms;
+ }
+ return;
+}
+################################################################################
+1;
+
+=pod
+=encoding utf8
+=item helper
+=item summary Compability version for Heating_Control
+=item summary_DE Letzte mit Heating_Control kompatible Version
+=begin html
+
+
+
+WeekdayTimer
+
+This is just the las version compabile with Heating_Control. Only needed if you absolutely want to avoid to change this device type to WeekdayTimer...
+
+
+ disable # disables the WeekdayTimer
+ enable # enables the WeekdayTimer, switching times will be recaltulated.
+ WDT_Params [one of: single, WDT_Group or all]
+ weekprofile <weekprofile-device:topic:profile>
+
+ You may especially use enable in case one of your global holiday2we devices has changed since 5 seconds past midnight.
+
+ Examples:
+
+ set wd disable
+ set wd enable
+ set wd WDT_Params WDT_Group
+ set wd weekprofile myWeekprofiles:holiday:livingrooms
+
+
+ The WDT_Params function can be used to reapply the current switching value to the device, all WDT devices with identical WDT_Group attribute or all WeekdayTimer devices; delay conditions will be obeyed, for non-heating type devices, switchInThePast has to be set.
+
+
+
+ NOTES on weekprofile usage:
+
+ - The weekprofile set will only be successfull, if the <weekprofile-device> is part of the definition of the WeekdayTimer, the mentionned device exists and it provides data for the <topic:profile> combination. If you haven't activated the "topic" feature in the weekprofile device, use "default" as topic.
+ - Once you set a weekprofile for any weekprofile device, you'll find the values set in the reading named "weekprofiles"; for each weekprofile device there's an entry with the set triplett.
+ - As WeekdayTimer will recalculate the switching times for each day a few seconds after midnight, 10 minutes pas midnight will be used as a first switching time for weekpofile usage.
+ - This set is the way the weekprofile module uses to update a WeekdayTimer device. So aforementioned WeekdayTimer command
+ set wd weekprofile myWeekprofiles:holiday:livingrooms
+ is aequivalent to weekprofile command
+ set myWeekprofiles send_to_device holiday:livingrooms wd
+
+ - Although it's possible to use more than one weekprofile device in a WeekdayTimer, this is explicitly not recommended despite you are exactly knwowing what you are doing.
+ - Note: The userattr weekprofile will automatically be added to the list and can't be removed. The attribute itself is intended to be set to the corresponding profile name in your weekprofile device allowing easy change using the topic feature.
+
+
+
+
+ Get
+
+
+ Attributes
+
+ - delayedExecutionCond
+ defines a delay Function. When returning true, the switching of the device is delayed until the function returns a false value. The behavior is just like a windowsensor in Heating_Control.
+
+
+ Example:
+
+ attr wd delayedExecutionCond isDelayed("$WEEKDAYTIMER","$TIME","$NAME","$EVENT")
+
+ the parameter $WEEKDAYTIMER(timer name) $TIME $NAME(device name) $EVENT are replaced at runtime by the correct value.
+
Note: If the function returns "1" or "true", state of the WeekdayTimer will be "open window", other return values will be used as values for state.
+ Example of a function:
+
+ sub isDelayed($$$$) {
+ my($wdt, $tim, $nam, $event ) = @_;
+
+ my $theSunIsStillshining = ...
+
+ return ($tim eq "16:30" && $theSunIsStillshining) ;
+ }
+
+
+ - WDT_delayedExecutionDevices
+ Defines a space separated list devices (atm only window sensors are supported). When one of its state readings is open the aktual switch is delayed.
+
+ - WDT_Group
+ Used to generate groups of WeekdayTimer devices to be switched together in case one of them is set to WDT_Params with the WDT_Group modifier, e.g. set wd WDT_Params WDT_Group
.
This is intended to allow former Heating_Control devices to be migrated to WeekdayTimer and replaces the Heating_Control_SetAllTemps() functionality.
+
+ - switchInThePast
+ defines that the depending device will be switched in the past in definition and startup phase when the device is not recognized as a heating.
+ Heatings are always switched in the past.
+
+
+ - disable
+ - loglevel
+ - event-on-update-reading
+ - event-on-change-reading
+ - stateFormat
+
+
+
+
+=end html
+
+=for :application/json;q=META.json 98_WeekdayTimer.pm
+{
+ "abstract" : "sends parameter to devices at defined times",
+ "x_lang" : {
+ "de" : {
+ "abstract" : "sendet Parameter an Devices zu einer Liste mit festen Zeiten"
+ }
+ },
+ "keywords" : [
+ "heating",
+ "Heizung",
+ "timer",
+ "weekprofile"
+ ],
+ "prereqs" : {
+ "runtime" : {
+ "requires" : {
+ "Data::Dumper" : "0",
+ "Time::Local" : "0",
+ "strict" : "0",
+ "warnings" : "0"
+ }
+ }
+ }
+}
+=end :application/json;q=META.json
+
+=cut