mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-03-01 09:34:51 +00:00
1272 lines
50 KiB
Perl
1272 lines
50 KiB
Perl
# $Id$
|
|
##############################################################################
|
|
#
|
|
# 98_WeekdayTimer.pm
|
|
# written by Dietmar Ortmann
|
|
# modified by Tobias Faust
|
|
#
|
|
# 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/>.
|
|
#
|
|
##############################################################################
|
|
package main;
|
|
use strict;
|
|
use warnings;
|
|
use POSIX;
|
|
|
|
use Time::Local 'timelocal_nocheck';
|
|
|
|
use Data::Dumper;
|
|
$Data::Dumper::Sortkeys = 1;
|
|
|
|
################################################################################
|
|
sub WeekdayTimer_Initialize($){
|
|
my ($hash) = @_;
|
|
|
|
if(!$modules{Twilight}{LOADED} && -f "$attr{global}{modpath}/FHEM/59_Twilight.pm") {
|
|
my $ret = CommandReload(undef, "59_Twilight");
|
|
Log3 undef, 1, $ret if($ret);
|
|
}
|
|
|
|
# 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 switchInThePast:0,1 commandTemplate ".
|
|
$readingFnAttributes;
|
|
}
|
|
################################################################################
|
|
sub WeekdayTimer_InitHelper($) {
|
|
my ($hash) = @_;
|
|
|
|
$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"]};
|
|
$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' ]};
|
|
}
|
|
################################################################################
|
|
sub WeekdayTimer_Set($@) {
|
|
my ($hash, @a) = @_;
|
|
|
|
return "no set value specified" if(int(@a) < 2);
|
|
return "Unknown argument $a[1], choose one of enable disable " if($a[1] eq "?");
|
|
|
|
my $name = shift @a;
|
|
my $v = join(" ", @a);
|
|
|
|
Log3 $hash, 3, "[$name] set $name $v";
|
|
|
|
if ($v eq "enable") {
|
|
fhem("attr $name disable 0");
|
|
} elsif ($v eq "disable") {
|
|
fhem("attr $name disable 1");
|
|
}
|
|
return undef;
|
|
}
|
|
################################################################################
|
|
sub WeekdayTimer_Get($@) {
|
|
my ($hash, @a) = @_;
|
|
return "argument is missing" if(int(@a) != 2);
|
|
|
|
$hash->{LOCAL} = 1;
|
|
delete $hash->{LOCAL};
|
|
my $reading= $a[1];
|
|
my $value;
|
|
|
|
if(defined($hash->{READINGS}{$reading})) {
|
|
$value= $hash->{READINGS}{$reading}{VAL};
|
|
} else {
|
|
return "no such reading: $reading";
|
|
}
|
|
return "$a[0] $reading => $value";
|
|
}
|
|
################################################################################
|
|
sub WeekdayTimer_Undef($$) {
|
|
my ($hash, $arg) = @_;
|
|
|
|
foreach my $idx (keys %{$hash->{profil}}) {
|
|
myRemoveInternalTimer($idx, $hash);
|
|
}
|
|
myRemoveInternalTimer("SetTimerOfDay", $hash);
|
|
delete $modules{$hash->{TYPE}}{defptr}{$hash->{NAME}};
|
|
return undef;
|
|
}
|
|
################################################################################
|
|
sub WeekdayTimer_Define($$) {
|
|
my ($hash, $def) = @_;
|
|
WeekdayTimer_InitHelper($hash);
|
|
|
|
my @a = split("[ \t]+", $def);
|
|
|
|
return "Usage: define <name> $hash->{TYPE} <device> <language> <switching times> <condition|command>"
|
|
if(@a < 4);
|
|
|
|
#fuer den modify Altlasten bereinigen
|
|
delete($hash->{helper});
|
|
|
|
my $name = shift @a;
|
|
my $type = shift @a;
|
|
my $device = shift @a;
|
|
|
|
WeekdayTimer_DeleteTimer($hash);
|
|
my $delVariables = "(CONDITION|COMMAND|profile|Profil)";
|
|
map { delete $hash->{$_} if($_=~ m/^$delVariables.*/g) } keys %{$hash};
|
|
|
|
my $language = WeekdayTimer_Language ($hash, \@a);
|
|
|
|
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/\$/\\\$/g;
|
|
$hash->{helper}{daysRegExp} =~ s/\!/\\\!/g;
|
|
|
|
WeekdayTimer_GlobalDaylistSpec ($hash, \@a);
|
|
|
|
$hash->{NAME} = $name;
|
|
$hash->{DEVICE} = $device;
|
|
|
|
my @switchingtimes = WeekdayTimer_gatherSwitchingTimes ($hash, \@a);
|
|
my $conditionOrCommand = join (" ", @a);
|
|
|
|
# test if device is defined
|
|
Log3 ($hash, 3, "[$name] device <$device> in fhem not defined, but accepted") if(!$defs{$device});
|
|
|
|
# wenn keine switchintime angegeben ist, dann Fehler
|
|
Log3 ($hash, 3, "[$name] no valid Switchingtime found in <$conditionOrCommand>, check first parameter") if (@switchingtimes == 0);
|
|
|
|
$hash->{STILLDONETIME} = 0;
|
|
$hash->{SWITCHINGTIMES} = \@switchingtimes;
|
|
$attr{$name}{verbose} = 5 if (!defined $attr{$name}{verbose} && $name =~ m/^tst.*/ );
|
|
$defs{$device}{STILLDONETIME} = 0 if($defs{$device});
|
|
|
|
$modules{$hash->{TYPE}}{defptr}{$hash->{NAME}} = $hash;
|
|
|
|
$hash->{CONDITION} = ""; $hash->{COMMAND} = "";
|
|
if($conditionOrCommand =~ m/^\(.*\)$/g) { #condition (*)
|
|
$hash->{CONDITION} = $conditionOrCommand;
|
|
} 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});
|
|
|
|
InternalTimer(time(), "$hash->{TYPE}_SetTimer", $hash, 0);
|
|
|
|
WeekdayTimer_SetTimerForMidnightUpdate( { HASH => $hash} );
|
|
|
|
return undef;
|
|
}
|
|
################################################################################
|
|
sub WeekdayTimer_Profile($) {
|
|
my $hash = shift;
|
|
|
|
my $language = $hash->{LANGUAGE};
|
|
my %longDays = %{$hash->{longDays}};
|
|
|
|
delete $hash->{profil};
|
|
my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time());
|
|
|
|
my $now = time();
|
|
# ---- Zeitpunkte den Tagen zuordnen -----------------------------------
|
|
my $idx = 0;
|
|
foreach my $st (@{$hash->{SWITCHINGTIMES}}) {
|
|
my ($tage,$time,$parameter) = WeekdayTimer_SwitchingTime ($hash, $st);
|
|
|
|
$idx++;
|
|
foreach my $d (@{$tage}) {
|
|
|
|
my @listeDerTage = ($d);
|
|
push (@listeDerTage, WeekdayTimer_getListeDerTage($d, $time)) if ($d>=7);
|
|
|
|
map { my $day = $_;
|
|
my $dayOfEchteZeit = $day;
|
|
$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]
|
|
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;
|
|
foreach my $st (@{$hash->{SWITCHINGTIMES}}) {
|
|
my ($tage,$time,$parameter) = WeekdayTimer_SwitchingTime ($hash, $st);
|
|
my $echtZeit = WeekdayTimer_EchteZeit ($hash, $wday, $time);
|
|
my ($stunde, $minute, $sekunde) = split (":",$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;
|
|
}
|
|
# ---- Texte Readings aufbauen -----------------------------------------
|
|
Log3 $hash, 4, "[$hash->{NAME}] " . sunrise_abs() . " " . sunset_abs() . " " . $longDays{$language}[$wday];
|
|
foreach my $d (sort keys %{$hash->{profile}}) {
|
|
my $profiltext = "";
|
|
foreach my $t (sort keys %{$hash->{profile}{$d}}) {
|
|
$profiltext .= "$t " . $hash->{profile}{$d}{$t} . ", ";
|
|
}
|
|
my $profilKey = "Profil $d: $longDays{$language}[$d]";
|
|
$profiltext =~ s/, $//;
|
|
$hash->{$profilKey} = $profiltext;
|
|
Log3 $hash, 4, "[$hash->{NAME}] $profiltext ($profilKey)";
|
|
}
|
|
|
|
# für logProxy umhaengen
|
|
$hash->{helper}{SWITCHINGTIME} = $hash->{profile};
|
|
delete $hash->{profile};
|
|
}
|
|
################################################################################
|
|
sub WeekdayTimer_getListeDerTage($$) {
|
|
my ($d, $time) = @_;
|
|
|
|
my %hdays=();
|
|
@hdays{(0, 6)} = undef if ($d==7); # sa,so ( $we)
|
|
@hdays{(1..5)} = undef if ($d==8); # mo-fr (!$we)
|
|
|
|
my $wday;
|
|
my $now = time();
|
|
my ($sec,$min,$hour,$mday,$mon,$year,$nowWday,$yday,$isdst) = localtime($now);
|
|
|
|
my @realativeWdays = (0..6);
|
|
for (my $i=0;$i<=6;$i++) {
|
|
|
|
my $relativeDay = $i-$nowWday;
|
|
#Log 3, "relativeDay------------>$relativeDay";
|
|
my ($stunde, $minute, $sekunde) = split (":",$time);
|
|
|
|
my $echteZeit = WeekdayTimer_zeitErmitteln ($now, $stunde, $minute, $sekunde, $relativeDay);
|
|
#Log 3, "echteZeit---$i---->>>$relativeDay<<<----->".FmtDateTime($echteZeit);
|
|
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($echteZeit);
|
|
my $h2we = $attr{global}{holiday2we};
|
|
if($h2we) {
|
|
my $ergebnis = fhem("get $h2we ".sprintf("%02d-%02d",$mon+1,$mday),1);
|
|
if ($ergebnis ne "none") {
|
|
#Log 3, "ergebnis-------$i----->$ergebnis";
|
|
$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, $switchingtime) = @_;
|
|
|
|
my $name = $hash->{NAME};
|
|
my $globalDaylistSpec = $hash->{GlobalDaylistSpec};
|
|
my @tageGlobal = @{WeekdayTimer_daylistAsArray($hash, $globalDaylistSpec)};
|
|
|
|
my (@st, $daylist, $time, $timeString, $para);
|
|
@st = split(/\|/, $switchingtime);
|
|
|
|
if ( @st == 2) {
|
|
$daylist = ($globalDaylistSpec gt "") ? $globalDaylistSpec : "0123456";
|
|
$time = $st[0];
|
|
$para = $st[1];
|
|
} elsif ( @st == 3) {
|
|
$daylist = $st[0];
|
|
$time = $st[1];
|
|
$para = $st[2];
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
################################################################################
|
|
sub WeekdayTimer_daylistAsArray($$){
|
|
my ($hash, $daylist) = @_;
|
|
|
|
my $name = $hash->{NAME};
|
|
my @days;
|
|
|
|
my %hdays=();
|
|
|
|
$daylist = lc($daylist);
|
|
# Angaben der Tage verarbeiten
|
|
# Aufzaehlung 1234 ...
|
|
if ( $daylist =~ m/^[0-8]{0,9}$/g) {
|
|
|
|
Log3 ($hash, 3, "[$name] " . '"7" in daylist now means $we(weekend) - see dokumentation!!!' )
|
|
if (index($daylist, '7') != -1);
|
|
|
|
@days = split("", $daylist);
|
|
@hdays{@days} = undef;
|
|
|
|
# Aufzaehlung Sa,So,... | Mo-Di,Do,Fr-Mo
|
|
} elsif ($daylist =~ m/^($hash->{helper}{daysRegExp}(,|-|$)){0,7}$/g ) {
|
|
my @subDays;
|
|
my @aufzaehlungen = split (",", $daylist);
|
|
foreach my $einzelAufzaehlung (@aufzaehlungen) {
|
|
my @days = split ("-", $einzelAufzaehlung);
|
|
my $days = @days;
|
|
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});
|
|
@subDays = ( 00 .. $bis, $von .. 06);
|
|
}
|
|
@hdays{@subDays}=undef;
|
|
}
|
|
}
|
|
} else{
|
|
%hdays = ();
|
|
}
|
|
|
|
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/^\{.*\}$/g);
|
|
my $date = $now+($d-$wday)*86400;
|
|
my $timeString = '{ my $date='."$date;" .$time."}";
|
|
my $eTimeString = eval( $timeString ); # must deliver HH:MM[:SS]
|
|
if ($@) {
|
|
$@ =~ s/\n/ /g;
|
|
Log3 ($hash, 3, "[$name] " . $@ . ">>>$timeString<<<");
|
|
$eTimeString = "00:00:00";
|
|
}
|
|
|
|
if ($eTimeString =~ m/^[0-2][0-9]:[0-5][0-9]$/g) { # HH:MM
|
|
$eTimeString .= ":00"; # HH:MM:SS erzeugen
|
|
} elsif ($eTimeString =~ m/^[0-2][0-9](:[0-5][0-9]){2,2}$/g) { # HH:MM:SS
|
|
; # ok.
|
|
} else {
|
|
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;
|
|
|
|
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";
|
|
|
|
# prüfen ob Anführungszeichen paarig sind
|
|
my @quotes = ('"', "'" );
|
|
foreach my $quote (@quotes){
|
|
my $balancedSign = eval "((\$element =~ tr/$quote//))";
|
|
if ($balancedSign % 2) { # ungerade Anzahl quotes, dann verlängern
|
|
Log3 $hash, 5, "[$name] $element - unbalanced quotes: $balancedSign $quote found";
|
|
next E;
|
|
}
|
|
}
|
|
|
|
# prüfen ob öffnende/schliessende Klammern paarig sind
|
|
my %signs = ('('=>')', '{'=>'}');
|
|
foreach my $signOpened (keys(%signs)) {
|
|
my $signClosed = $signs{$signOpened};
|
|
my $balancedSign = eval "((\$element =~ tr/$signOpened//) - (\$element =~ tr/$signClosed//))";
|
|
if ($balancedSign) { # öffnende/schließende Klammern nicht gleich, dann verlängern
|
|
Log3 $hash, 5, "[$name] $element - unbalanced brackets $signOpened$signClosed:$balancedSign";
|
|
next E;
|
|
}
|
|
}
|
|
last;
|
|
}
|
|
|
|
# ein space am Ende wieder abschneiden
|
|
$element = substr ($element, 0, length($element)-1);
|
|
my @t = split(/\|/, $element);
|
|
my $anzahl = @t;
|
|
|
|
if ( ($anzahl == 2 || $anzahl == 3) && $t[0] gt "" && $t[1] gt "" ) {
|
|
Log3 $hash, 4, "[$name] $element - accepted";
|
|
push(@switchingtimes, $element);
|
|
} 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/^$langRegExp$/g) {
|
|
} else {
|
|
Log3 ($hash, 3, "[$name] language: $language not recognized, use one of $langRegExp") if (length($language) == 2);
|
|
unshift @$a, $language;
|
|
$language = "de";
|
|
}
|
|
$hash->{LANGUAGE} = $language;
|
|
|
|
$language = $hash->{LANGUAGE};
|
|
return ($langRegExp, $language);
|
|
}
|
|
################################################################################
|
|
sub WeekdayTimer_GlobalDaylistSpec {
|
|
my ($hash, $a) = @_;
|
|
|
|
my $daylist = shift @$a;
|
|
|
|
my @tage = @{WeekdayTimer_daylistAsArray($hash, $daylist)};
|
|
my $tage = @tage;
|
|
if ($tage > 0) {
|
|
;
|
|
} else {
|
|
unshift (@$a,$daylist);
|
|
$daylist = "";
|
|
}
|
|
|
|
$hash->{GlobalDaylistSpec} = $daylist;
|
|
}
|
|
################################################################################
|
|
sub WeekdayTimer_SetTimerForMidnightUpdate($) {
|
|
my ($myHash) = @_;
|
|
my $hash = myGetHashIndirekt($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);
|
|
|
|
myRemoveInternalTimer("SetTimerOfDay", $hash);
|
|
my $newMyHash = myInternalTimer ("SetTimerOfDay", $midnightPlus5Seconds, "$hash->{TYPE}_SetTimerOfDay", $hash, 0);
|
|
$newMyHash->{SETTIMERATMIDNIGHT} = 1;
|
|
|
|
}
|
|
################################################################################
|
|
sub WeekdayTimer_SetTimerOfDay($) {
|
|
my ($myHash) = @_;
|
|
my $hash = myGetHashIndirekt($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;
|
|
|
|
$hash->{SETTIMERATMIDNIGHT} = $myHash->{SETTIMERATMIDNIGHT};
|
|
WeekdayTimer_DeleteTimer($hash);
|
|
WeekdayTimer_Profile ($hash);
|
|
WeekdayTimer_SetTimer ($hash);
|
|
delete $hash->{SETTIMERATMIDNIGHT};
|
|
|
|
WeekdayTimer_SetTimerForMidnightUpdate( { HASH => $hash} );
|
|
}
|
|
################################################################################
|
|
sub WeekdayTimer_SetTimer($) {
|
|
my $hash = shift;
|
|
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 $secondsToSwitch = $timToSwitch - $now;
|
|
|
|
my $isActiveTimer = WeekdayTimer_isAnActiveTimer ($hash, $tage, $para);
|
|
readingsSingleUpdate ($hash, "state", "active", 1)
|
|
if (!defined $hash->{SETTIMERATMIDNIGHT} && $isActiveTimer);
|
|
|
|
if ($secondsToSwitch>-5 || defined $hash->{SETTIMERATMIDNIGHT} ) {
|
|
if($isActiveTimer) {
|
|
Log3 $hash, 4, "[$name] setTimer - timer seems to be active today: ".join("",@$tage)."|$time|$para";
|
|
} else {
|
|
Log3 $hash, 4, "[$name] setTimer - timer seems to be NOT active today: ".join("",@$tage)."|$time|$para ". $hash->{CONDITION};
|
|
}
|
|
myRemoveInternalTimer("$idx", $hash);
|
|
myInternalTimer ("$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";
|
|
}
|
|
|
|
readingsSingleUpdate ($hash, "nextUpdate", FmtDateTime($nextTime), 1);
|
|
readingsSingleUpdate ($hash, "nextValue", $nextParameter, 1);
|
|
readingsSingleUpdate ($hash, "currValue", $aktParameter, 1); # HB
|
|
|
|
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;
|
|
|
|
myRemoveInternalTimer("delayed", $tipHash);
|
|
myInternalTimer ("delayed", time()+5, "WeekdayTimer_delayedTimerInPast", $tipHash, 0);
|
|
|
|
}
|
|
}
|
|
################################################################################
|
|
sub WeekdayTimer_delayedTimerInPast($) {
|
|
my ($myHash) = @_;
|
|
my $hash = myGetHashIndirekt($myHash, (caller(0))[3]);
|
|
return if (!defined($hash));
|
|
|
|
my $tim = time();
|
|
my $tipIpHash = $modules{WeekdayTimer}{timerInThePast};
|
|
|
|
foreach my $device ( keys %$tipIpHash ) {
|
|
foreach my $time ( sort keys %{$tipIpHash->{$device}} ) {
|
|
Log3 $hash, 4, "$device ".FmtDateTime($time)." ".($tim-$time)."s ";
|
|
|
|
foreach my $para ( @{$tipIpHash->{$device}{$time}} ) {
|
|
myRemoveInternalTimer(@$para[0], @$para[3]);
|
|
my $mHash =myInternalTimer (@$para[0],@$para[1],@$para[2],@$para[3],@$para[4]);
|
|
$mHash->{immerSchalten} = 1;
|
|
}
|
|
}
|
|
}
|
|
delete $modules{WeekdayTimer}{timerInThePast};
|
|
delete $modules{WeekdayTimer}{timerInThePastHash}
|
|
}
|
|
################################################################################
|
|
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];
|
|
|
|
foreach my $time (sort keys %{$hash->{helper}{SWITCHINGTIME}{$relWday}}) {
|
|
my ($stunde, $minute, $sekunde) = split (":",$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->{profile_IDX}{$relWday}{$time};
|
|
$nextTag = $relWday;
|
|
|
|
#Log3 $hash, 3, $shortDays{$language}[$nextTag]." ".FmtDateTime($nextTime)." ".$nextPara." ".$nextIdx;
|
|
|
|
if ($nextTime >= $now) {
|
|
#Log3 $hash, 3, "oldIdx------------->$oldIdx";
|
|
#Log3 $hash, 3, "oldTime------------>".FmtDateTime($oldTime);
|
|
#Log3 $hash, 3, "oldPara------------>$oldPara";
|
|
return ($oldIdx, $oldTime, $oldPara, $nextTime, $nextPara);
|
|
}
|
|
}
|
|
}
|
|
return (undef,undef,undef,undef);
|
|
}
|
|
################################################################################
|
|
sub WeekdayTimer_DeleteTimer($) {
|
|
my $hash = shift;
|
|
map {myRemoveInternalTimer ($_, $hash)} keys %{$hash->{profil}};
|
|
}
|
|
################################################################################
|
|
sub WeekdayTimer_Update($) {
|
|
my ($myHash) = @_;
|
|
my $hash = myGetHashIndirekt($myHash, (caller(0))[3]);
|
|
return if (!defined($hash));
|
|
|
|
my $name = $hash->{NAME};
|
|
my $idx = $myHash->{MODIFIER};
|
|
my $now = time();
|
|
|
|
#my $sollZeit = $myHash->{TIME};
|
|
|
|
#my $setModifier = WeekdayTimer_isHeizung($hash);
|
|
#my $isHeating = $setModifier gt "";
|
|
|
|
# Schaltparameter ermitteln
|
|
my $tage = $hash->{profil}{$idx}{TAGE};
|
|
my $time = $hash->{profil}{$idx}{TIME};
|
|
#my $newParam = WeekdayTimer_evalAndcleanupParam($hash,$time,$hash->{profil}{$idx}{PARA}, $isHeating );
|
|
my $newParam = $hash->{profil}{$idx}{PARA};
|
|
|
|
#Log3 $hash, 3, "[$name] $idx ". $time . " " . $newParam . " " . join("",@$tage);
|
|
|
|
# Fenserkontakte abfragen - wenn einer im Status closed, dann Schaltung um 60 Sekunden verzögern
|
|
if (WeekdayTimer_FensterOffen($hash, $newParam, $idx)) {
|
|
readingsSingleUpdate ($hash, "state", "open window", 1);
|
|
return;
|
|
}
|
|
|
|
my $dieGanzeWoche = [7,8];
|
|
my ($activeTimer, $activeTimerState);
|
|
if (defined $myHash->{immerSchalten}) {
|
|
$activeTimer = WeekdayTimer_isAnActiveTimer ($hash, $dieGanzeWoche, $newParam);
|
|
$activeTimerState = WeekdayTimer_isAnActiveTimer ($hash, $tage, $newParam);
|
|
Log3 $hash, 4, "[$name] Update - past timer activated";
|
|
} else {
|
|
$activeTimer = WeekdayTimer_isAnActiveTimer ($hash, $tage, $newParam);
|
|
$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_Device_Schalten($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, defined($hash->{LOCAL} ? 0 : 1));
|
|
|
|
return 1;
|
|
|
|
}
|
|
################################################################################
|
|
sub WeekdayTimer_isAnActiveTimer ($$$) {
|
|
my ($hash, $tage, $newParam) = @_;
|
|
|
|
my $name = $hash->{NAME};
|
|
my %specials = ( "%NAME" => $hash->{DEVICE}, "%EVENT" => $newParam);
|
|
|
|
my $condition = WeekdayTimer_Condition ($hash, $tage);
|
|
my $tageAsHash = WeekdayTimer_tageAsHash($hash, $tage);
|
|
my $xPression = "{".$tageAsHash.";;".$condition ."}";
|
|
$xPression = EvalSpecials($xPression, %specials);
|
|
Log3 $hash, 5, "[$name] condition: $xPression";
|
|
|
|
my $ret = AnalyzeCommandChain(undef, $xPression);
|
|
#Log3 $hash, 3, "[$name] condition:>>>$ret<<< $xPression";
|
|
Log3 $hash, 5, "[$name] result of condition:$ret";
|
|
return $ret;
|
|
}
|
|
################################################################################
|
|
# {WeekdayTimer_isHeizung($defs{HeizungKueche_an_wt})}
|
|
sub WeekdayTimer_isHeizung($) {
|
|
my ($hash) = @_;
|
|
|
|
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);
|
|
|
|
foreach my $ts (@tempSet) {
|
|
if ($allSets =~ m/$ts/) {
|
|
Log3 $hash, 4, "[$name] device type heating recognized, setModifier:$ts";
|
|
return $ts
|
|
}
|
|
}
|
|
|
|
}
|
|
################################################################################
|
|
#
|
|
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};
|
|
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/\$/\\\$/g;
|
|
my $val = $specials{$_};
|
|
$verzoegerteAusfuehrungCond =~ s/$key/$val/g
|
|
} keys %specials;
|
|
Log3 $hash, 4, "[$name] delayedExecutionCond:$verzoegerteAusfuehrungCond";
|
|
|
|
my $verzoegerteAusfuehrung = eval($verzoegerteAusfuehrungCond);
|
|
Log3 $hash, 4, "[$name] result of delayedExecutionCond:$verzoegerteAusfuehrung";
|
|
|
|
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) {
|
|
Log3 $hash, 3, "[$name] timer at $hash->{profil}{$hash->{VERZOEGRUNG_IDX}}{TIME} skiped by new timer at $hash->{profil}{$time}{TIME}";
|
|
myRemoveInternalTimer($hash->{VERZOEGRUNG_IDX},$hash);
|
|
}
|
|
$hash->{VERZOEGRUNG_IDX} = $time;
|
|
myRemoveInternalTimer("$time", $hash);
|
|
myInternalTimer ("$time", $nextRetry, "$hash->{TYPE}_Update", $hash, 0);
|
|
$hash->{VERZOEGRUNG} = 1;
|
|
return 1;
|
|
}
|
|
|
|
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" },
|
|
"WeekdayTimer" => { "READING" => "delayedExecution","STATUS" => "^1\$", "MODEL" => "a" },
|
|
"Heating_Control" => { "READING" => "delayedExecution","STATUS" => "^1\$", "MODEL" => "a" }
|
|
);
|
|
|
|
my $fensterKontakte = AttrVal($hash->{NAME}, "windowSensor", "")." ".$hash->{NAME};
|
|
$fensterKontakte =~ s/^\s+//;
|
|
$fensterKontakte =~ s/\s+$//;
|
|
|
|
Log3 $hash, 4, "[$name] list of window sensors found: '$fensterKontakte'";
|
|
if ($fensterKontakte ne "" ) {
|
|
my @kontakte = split("[ \t]+", $fensterKontakte);
|
|
foreach my $fk (@kontakte) {
|
|
if(!$defs{$fk}) {
|
|
Log3 $hash, 3, "[$name] sensor <$fk> not found - check name.";
|
|
} else {
|
|
my $fk_hash = $defs{$fk};
|
|
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;
|
|
if ($model eq "r") { ### Reading, sonst Attribut
|
|
$windowStatus = ReadingsVal($fk,$reading,"nF");
|
|
}else{
|
|
$windowStatus = 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/^$statusReg$/g) {
|
|
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} skiped by new timer at $hash->{profil}{$time}{TIME}";
|
|
myRemoveInternalTimer($hash->{VERZOEGRUNG_IDX},$hash);
|
|
}
|
|
$hash->{VERZOEGRUNG_IDX} = $time;
|
|
myRemoveInternalTimer("$time", $hash);
|
|
myInternalTimer ("$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_evalAndcleanupParam($$$$) {
|
|
my ($hash,$time,$param,$isHeating) = @_;
|
|
|
|
my $name = $hash->{DEVICE} ;
|
|
my $wdName = $hash->{NAME};
|
|
|
|
my $newParam = $param;
|
|
if ($param =~ m/^{.*}$/) {
|
|
|
|
Log3 $hash, 4, "[$wdName] calculating dynamic param before all: ....... $newParam";
|
|
|
|
$newParam =~ s/\$NAME/$hash->{DEVICE}/g;
|
|
$newParam =~ s/\$TIME/$time/g;
|
|
Log3 $hash, 4, "[$wdName] calculating dynamic param after substitutions: $newParam";
|
|
|
|
$newParam = eval $newParam;
|
|
if ($@ || not defined $newParam) {
|
|
Log3 $hash, 1, "[$wdName] problem calculating dynamic param: ........... $param";
|
|
Log3 $hash, 1, "[$wdName] $@";
|
|
} else {
|
|
Log3 $hash, 4, "[$wdName] calculating dynamic param after eval: ........ $newParam";
|
|
}
|
|
|
|
}elsif($isHeating && $param =~ m/^\d{1,3}$/){
|
|
$newParam = sprintf("%.1f", $param);
|
|
}
|
|
return $newParam;
|
|
}
|
|
################################################################################
|
|
sub WeekdayTimer_Device_Schalten($$$) {
|
|
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 ($hash->{COMMAND} gt "");
|
|
|
|
my $activeTimer = 1;
|
|
|
|
my $isHeating = $setModifier gt "";
|
|
my $aktParam = ReadingsVal($hash->{DEVICE}, $setModifier, "");
|
|
$aktParam = sprintf("%.1f", $aktParam) if ($isHeating && $aktParam =~ m/^[0-9]{1,3}$/i);
|
|
$newParam = sprintf("%.1f", $newParam) if ($isHeating && $newParam =~ m/^[0-9]{1,3}$/i);
|
|
# my $aktParam = WeekdayTimer_evalAndcleanupParam($hash,$dummy,ReadingsVal($hash->{DEVICE}, $setModifier, ""),$isHeating);
|
|
# newParam is already processed by evalAndcleanupParam()
|
|
|
|
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/\\:/|/g;
|
|
$newParam =~ s/:/ /g;
|
|
$newParam =~ s/\|/:/g;
|
|
|
|
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);
|
|
}
|
|
}
|
|
################################################################################
|
|
sub WeekdayTimer_tageAsHash($$) {
|
|
my ($hash, $tage) = @_;
|
|
|
|
my %days = map {$_ => 1} @$tage;
|
|
map {delete $days{$_}} (7,8);
|
|
|
|
return 'my $days={};map{$days->{$_}=1}'.'('.join (",", sort keys %days).')';
|
|
}
|
|
################################################################################
|
|
sub WeekdayTimer_Condition($$) {
|
|
my ($hash, $tage) = @_;
|
|
|
|
my $name = $hash->{NAME};
|
|
Log3 $hash, 4, "[$name] condition:$hash->{CONDITION} - Tage:".join(",",@$tage);
|
|
|
|
my $condition = "( ";
|
|
$condition .= ($hash->{CONDITION} gt "") ? $hash->{CONDITION} : 1 ;
|
|
$condition .= " && " . WeekdayTimer_TageAsCondition($tage);
|
|
$condition .= ")";
|
|
|
|
return $condition;
|
|
|
|
}
|
|
################################################################################
|
|
sub WeekdayTimer_TageAsCondition ($) {
|
|
my $tage = shift;
|
|
|
|
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 defined $we;
|
|
$tageExp .= ' || !$we' if defined $notWe;
|
|
$tageExp .= ')';
|
|
|
|
return $tageExp;
|
|
|
|
}
|
|
################################################################################
|
|
sub WeekdayTimer_Attr($$$$) {
|
|
my ($cmd, $name, $attrName, $attrVal) = @_;
|
|
|
|
$attrVal = 0 if(!defined $attrVal);
|
|
|
|
my $hash = $defs{$name};
|
|
if( $attrName eq "disable" ) {
|
|
readingsSingleUpdate ($hash, "disabled", $attrVal, 1);
|
|
} elsif ( $attrName eq "enable" ) {
|
|
WeekdayTimer_SetTimerOfDay({ HASH => $hash});
|
|
} elsif ( $attrName eq "switchInThePast" ) {
|
|
$attr{$name}{$attrName} = $attrVal;
|
|
WeekdayTimer_SetTimerOfDay({ HASH => $hash});
|
|
}
|
|
return undef;
|
|
}
|
|
########################################################################
|
|
sub WeekdayTimer_SetParm($) {
|
|
my ($name) = @_;
|
|
|
|
my $hash = $modules{WeekdayTimer}{defptr}{$name};
|
|
if(defined $hash) {
|
|
WeekdayTimer_DeleteTimer($hash);
|
|
WeekdayTimer_SetTimer($hash);
|
|
}
|
|
}
|
|
################################################################################
|
|
sub WeekdayTimer_SetAllParms() { # {WeekdayTimer_SetAllParms()}
|
|
|
|
my @wdNamen = sort keys %{$modules{WeekdayTimer}{defptr}};
|
|
foreach my $wdName ( @wdNamen ) {
|
|
WeekdayTimer_SetParm($wdName);
|
|
}
|
|
Log3 undef, 3, "WeekdayTimer_SetAllParms() done on: ".join(" ",@wdNamen );
|
|
}
|
|
|
|
1;
|
|
|
|
=pod
|
|
=item device
|
|
=item summary sends parameter to devices at defined times
|
|
=item summary_DE sendet Parameter an devices zu einer Liste mit festen Zeiten
|
|
=begin html
|
|
|
|
<a name="WeekdayTimer"></a>
|
|
<meta content="text/html; charset=ISO-8859-1" http-equiv="content-type">
|
|
<h3>WeekdayTimer</h3>
|
|
<ul>
|
|
<br>
|
|
<a name="weekdayTimer_define"></a>
|
|
<b>Define</b>
|
|
<ul>
|
|
<code>define <name> WeekdayTimer <device> [<language>] [<u>weekdays</u>] <profile> <command>|<condition></code>
|
|
<br><br>
|
|
|
|
to set a weekly profile for <device><br><br>
|
|
|
|
You can define different switchingtimes for every day.<br>
|
|
The new parameter is sent to the <device> automatically with <br><br>
|
|
|
|
<code>set <device> <para></code><br><br>
|
|
|
|
If you have defined a <condition> and this condition is false if the switchingtime has reached, no command will executed.<br>
|
|
An other case is to define an own perl command with <command>.
|
|
<p>
|
|
The following parameter are defined:
|
|
<ul><b>device</b><br>
|
|
The device to switch at the given time.
|
|
</ul>
|
|
<p>
|
|
<ul><b>language</b><br>
|
|
Specifies the language used for definition and profiles.
|
|
de,en,fr are possible. The parameter is optional.
|
|
</ul>
|
|
<p>
|
|
<ul><b>weekdays</b><br>
|
|
Specifies the days for all timer in the <b>WeekdayTimer</b>.
|
|
The parameter is optional. For details see the weekdays part in profile.
|
|
</ul>
|
|
<p>
|
|
<ul><b>profile</b><br>
|
|
Define the weekly profile. All timings are separated by space. A switchingtime is defined
|
|
by the following example: <br><br>
|
|
|
|
<ul><b>[<weekdays>|]<time>|<parameter></b></ul><br>
|
|
|
|
<u>weekdays:</u> optional, if not set every day of the week is used.<br>
|
|
Otherwise you can define a day with its number or its shortname.<br>
|
|
<ul>
|
|
<li>0,su sunday</li>
|
|
<li>1,mo monday</li>
|
|
<li>2,tu tuesday</li>
|
|
<li>3,we wednesday</li>
|
|
<li>4 ...</li>
|
|
<li>7,$we weekend ($we)</li>
|
|
<li>8,!$we weekday (!$we)</li>
|
|
</ul><br>
|
|
It is possible to define $we or !$we in daylist to easily allow weekend an holiday. $we !$we are coded as 7 8, when using a numeric daylist.<br><br>
|
|
<u>time:</u>define the time to switch, format: HH:MM:[SS](HH in 24 hour format) or a Perlfunction like {sunrise_abs()}. Within the {} you can use the variable $date(epoch) to get the exact switchingtimes of the week. Example: {sunrise_abs_dat($date)}<br><br>
|
|
<u>parameter:</u>the parameter to be set, using any text value like <b>on</b>, <b>off</b>, <b>dim30%</b>, <b>eco</b> or <b>comfort</b> - whatever your device understands.<br>
|
|
</ul>
|
|
<p>
|
|
<ul><b>command</b><br>
|
|
If no condition is set, all the rest is interpreted as a command. Perl-code is setting up
|
|
by the well-known Block with {}.<br>
|
|
Note: if a command is defined only this command is executed. In case of executing
|
|
a "set desired-temp" command, you must define the hole commandpart explicitly by yourself.<br>
|
|
<!----------------------------------------------------------------------------- -->
|
|
<!-- -------------------------------------------------------------------------- -->
|
|
The following parameter are replaced:<br>
|
|
<ol>
|
|
<li>$NAME => the device to switch</li>
|
|
<li>$EVENT => the new temperature</li>
|
|
</ol>
|
|
</ul>
|
|
<p>
|
|
<ul><b>condition</b><br>
|
|
if a condition is defined you must declare this with () and a valid perl-code.<br>
|
|
The return value must be boolean.<br>
|
|
The parameters $NAME and $EVENT will be interpreted.
|
|
</ul>
|
|
<p>
|
|
<b>Examples:</b>
|
|
<ul>
|
|
<code>define shutter WeekdayTimer bath 12345|05:20|up 12345|20:30|down</code><br>
|
|
Mo-Fr are setting the shutter at 05:20 to <b>up</b>, and at 20:30 <b>down</b>.<p>
|
|
|
|
<code>define heatingBath WeekdayTimer bath 07:00|16 Mo,Tu,Th-Fr|16:00|18.5 20:00|eco
|
|
{fhem("set dummy on"); fhem("set $NAME desired-temp $EVENT");}</code><br>
|
|
At the given times and weekdays only(!) the command will be executed.<p>
|
|
|
|
<code>define dimmer WeekdayTimer livingRoom Sa-Su,We|07:00|dim30% Sa-Su,We|21:00|dim90% (ReadingsVal("WeAreThere", "state", "no") eq "yes")</code><br>
|
|
The dimmer is only set to dimXX% if the dummy variable WeAreThere is "yes"(not a real live example).<p>
|
|
|
|
If you want to have set all WeekdayTimer their current value (after a temperature lowering phase holidays)
|
|
you can call the function <b>WeekdayTimer_SetParm("WD-device")</b> or <b>WeekdayTimer_SetAllParms()</b>.<br>
|
|
This call can be automatically coupled to a dummy by a notify:<br>
|
|
<code>define dummyNotify notify Dummy:. * {WeekdayTimer_SetAllTemps()}</code>
|
|
<br><p>
|
|
Some definitions without comment:
|
|
<code>
|
|
<pre>
|
|
define wd Weekdaytimer device de 7|23:35|25 34|23:30|22 23:30|16 23:15|22 8|23:45|16
|
|
define wd Weekdaytimer device de fr,$we|23:35|25 34|23:30|22 23:30|16 23:15|22 12|23:45|16
|
|
define wd Weekdaytimer device de 20:35|25 34|14:30|22 21:30|16 21:15|22 12|23:00|16
|
|
|
|
define wd Weekdaytimer device de mo-so, $we|{sunrise_abs_dat($date)}|on mo-so, $we|{sunset_abs_dat($date)}|off
|
|
define wd Weekdaytimer device de mo-so,!$we|{sunrise_abs_dat($date)}|aus mo-so,!$we|{sunset_abs_dat($date)}|aus
|
|
|
|
define wd Weekdaytimer device de {sunrise_abs_dat($date)}|19 {sunset_abs_dat($date)}|21
|
|
define wd Weekdaytimer device de 22:35|25 23:00|16
|
|
</code></pre>
|
|
The daylist can be given globaly for the whole Weekdaytimer:<p>
|
|
<code><pre>
|
|
define wd Weekdaytimer device de !$we 09:00|19 (function("Ein"))
|
|
define wd Weekdaytimer device de $we 09:00|19 (function("Ein"))
|
|
define wd Weekdaytimer device de 78 09:00|19 (function("exit"))
|
|
define wd Weekdaytimer device de 57 09:00|19 (function("exit"))
|
|
define wd Weekdaytimer device de fr,$we 09:00|19 (function("exit"))
|
|
</code></pre>
|
|
</ul>
|
|
</ul>
|
|
|
|
<a name="WeekdayTimerset"></a>
|
|
<b>Set</b>
|
|
|
|
<code><b><font size="+1">set <name> <value></font></b></code>
|
|
<br><br>
|
|
where <code>value</code> is one of:<br>
|
|
<pre>
|
|
<b>disable</b> # disables the Weekday_Timer
|
|
<b>enable</b> # enables the Weekday_Timer
|
|
</pre>
|
|
|
|
<b><font size="+1">Examples</font></b>:
|
|
<ul>
|
|
<code>set wd disable</code><br>
|
|
<code>set wd enable</code><br>
|
|
</ul>
|
|
</ul>
|
|
|
|
<a name="WeekdayTimerget"></a>
|
|
<b>Get</b> <ul>N/A</ul><br>
|
|
|
|
<a name="WeekdayTimerLogattr"></a>
|
|
<b>Attributes</b>
|
|
<ul>
|
|
<li>delayedExecutionCond <br>
|
|
defines a delay Function. When returning true, the switching of the device is delayed until the function retruns a false value. The behavior is just like a windowsensor in Heating_Control.
|
|
|
|
<br><br>
|
|
<b>Example:</b>
|
|
<pre>
|
|
attr wd delayedExecutionCond isDelayed("$HEATING_CONTROL","$WEEKDAYTIMER","$TIME","$NAME","$EVENT")
|
|
</pre>
|
|
the parameter $WEEKDAYTIMER(timer name) $TIME $NAME(device name) $EVENT are replaced at runtime by the correct value.
|
|
|
|
<br><br>
|
|
<b>Example of a function:</b>
|
|
<pre>
|
|
sub isDelayed($$$$$) {
|
|
my($hc, $wdt, $tim, $nam, $event ) = @_;
|
|
|
|
my $theSunIsStillshining = ...
|
|
|
|
return ($tim eq "16:30" && $theSunIsStillshining) ;
|
|
}
|
|
</pre>
|
|
</li>
|
|
<li>switchInThePast<br>
|
|
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.
|
|
</li>
|
|
|
|
<li><a href="#disable">disable</a></li>
|
|
<li><a href="#loglevel">loglevel</a></li>
|
|
<li><a href="#event-on-update-reading">event-on-update-reading</a></li>
|
|
<li><a href="#event-on-change-reading">event-on-change-reading</a></li>
|
|
<li><a href="#stateFormat">stateFormat</a></li>
|
|
</ul><br>
|
|
|
|
=end html
|
|
|
|
=cut |